Эх сурвалжийг харах

Merge remote-tracking branch 'origin/dev'

limeng 4 сар өмнө
parent
commit
32ae9f0c4f
100 өөрчлөгдсөн 13374 нэмэгдсэн , 747 устгасан
  1. 43 1
      application-webadmin/pom.xml
  2. 1 0
      application-webadmin/src/main/java/com/tourism/webadmin/WebAdminApplication.java
  3. 42 38
      application-webadmin/src/main/java/com/tourism/webadmin/app/website/controller/BasicToWebController.java
  4. 79 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/website/controller/CleanDataController.java
  5. 77 17
      application-webadmin/src/main/java/com/tourism/webadmin/app/website/controller/JobProjectToWebController.java
  6. 50 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/website/controller/JobRecordController.java
  7. 628 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/website/controller/LoginToWebsiteController.java
  8. 108 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/website/controller/TourismOrderController.java
  9. 57 30
      application-webadmin/src/main/java/com/tourism/webadmin/app/website/controller/TourismProjectToWebController.java
  10. 242 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/website/controller/WebsiteTourismProjectTravelNotesController.java
  11. 30 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/website/dto/CheckImageDto.java
  12. 13 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/website/dto/DatePriceSaveDto.java
  13. 26 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/website/dto/PageDto.java
  14. 95 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/website/dto/TourUserRegisterDto.java
  15. 29 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/website/dto/TourismBookProjectDto.java
  16. 85 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/website/dto/TourismProjectToWebDto.java
  17. 80 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/website/dto/TourismProjectTravelNotesToWebDto.java
  18. 54 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/website/dto/TourismRestaurantFoodInfoToWebDto.java
  19. 60 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/website/dto/TourismRestaurantFoodTypeToWebDto.java
  20. 62 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/website/dto/TourismRestaurantInfoToWebDto.java
  21. 55 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/website/dto/TourismRestaurantTypeToWebDto.java
  22. 36 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/website/service/BasicToWebService.java
  23. 6 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/website/service/CleanDataService.java
  24. 14 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/website/service/JobRecordService.java
  25. 45 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/website/service/LoginToWebsiteService.java
  26. 23 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/website/service/TourismProjectToWebService.java
  27. 321 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/website/service/impl/BasicToWebServiceImpl.java
  28. 254 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/website/service/impl/CleanDataServiceImpl.java
  29. 57 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/website/service/impl/JobRecordServiceImpl.java
  30. 193 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/website/service/impl/LoginToWebsiteServiceImpl.java
  31. 243 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/website/service/impl/TourismProjectToWebServiceImpl.java
  32. 13 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/website/vo/DateRange.java
  33. 26 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/website/vo/DateRangePriceVo.java
  34. 19 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/website/vo/DateRangesPriceVo.java
  35. 28 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/website/vo/TourTravelNotesDirectoryCountryVo.java
  36. 37 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/website/vo/TourTravelNotesDirectoryVo.java
  37. 43 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/website/vo/TourismProjectDatePriceVo.java
  38. 27 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/website/vo/WebSiteCaptchaImageVo.java
  39. 16 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/website/vo/WebSiteProjectDatePriceVo.java
  40. 328 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/controller/TakeOutProjectController.java
  41. 144 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/controller/TourProjectController.java
  42. 218 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/controller/WechatDeliveryAddressController.java
  43. 188 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/controller/WechatDeliveryOrderController.java
  44. 384 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/controller/WechatFoodIndexController.java
  45. 198 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/controller/WechatHomeController.java
  46. 95 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/controller/WechatJobIndexController.java
  47. 199 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/controller/WechatMapController.java
  48. 400 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/controller/WechatTourismIndexController.java
  49. 35 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/dto/BestNewShopDto.java
  50. 28 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/dto/DeliveryDefaultAddressDto.java
  51. 30 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/dto/MapDto.java
  52. 33 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/dto/PageInfo.java
  53. 37 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/dto/ShopFoodDto.java
  54. 40 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/dto/TourRestaurantCartChangeDto.java
  55. 44 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/dto/WechatIndexJobProjectDto.java
  56. 92 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/model/RestaurantFoodType.java
  57. 95 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/model/WechatRestaurantCart.java
  58. 57 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/vo/shopfood/SelectShopFoodVo.java
  59. 72 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/vo/shopfood/WechatBestNewShopVo.java
  60. 37 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/vo/shopfood/WechatQueryIndexBannerRestaurantVo.java
  61. 30 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/vo/shopfood/WechatRestaurantCartTotalVo.java
  62. 90 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/vo/shopfood/WechatRestaurantCartVo.java
  63. 104 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/vo/shopfood/WechatSelectShopVo.java
  64. 38 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/vo/shopfood/WechatShopAndFoodsVo.java
  65. 35 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/vo/tourism/IndexBannerTravelNoteVo.java
  66. 265 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/vo/tourism/IndexDestinationProjectVo.java
  67. 61 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/vo/tourism/IndexHotDestinationVo.java
  68. 58 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/vo/tourism/IndexHotProjectTravelNoteVo.java
  69. 29 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/vo/tourism/TourismNoteDetailVo.java
  70. 39 0
      application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/vo/tourism/WechatProjectDetailVo.java
  71. 4 2
      application-webadmin/src/main/java/com/tourism/webadmin/back/controller/BannerInfoController.java
  72. 3 2
      application-webadmin/src/main/java/com/tourism/webadmin/back/controller/CompanyInfoController.java
  73. 258 0
      application-webadmin/src/main/java/com/tourism/webadmin/back/controller/DeliveryAddressController.java
  74. 317 0
      application-webadmin/src/main/java/com/tourism/webadmin/back/controller/DeliveryOrderController.java
  75. 252 0
      application-webadmin/src/main/java/com/tourism/webadmin/back/controller/DeliveryOrderItemsController.java
  76. 138 4
      application-webadmin/src/main/java/com/tourism/webadmin/back/controller/DirectoryInfoController.java
  77. 322 0
      application-webadmin/src/main/java/com/tourism/webadmin/back/controller/ExtraController.java
  78. 366 0
      application-webadmin/src/main/java/com/tourism/webadmin/back/controller/IconController.java
  79. 1 2
      application-webadmin/src/main/java/com/tourism/webadmin/back/controller/JobFileController.java
  80. 4 2
      application-webadmin/src/main/java/com/tourism/webadmin/back/controller/JobProjectController.java
  81. 0 620
      application-webadmin/src/main/java/com/tourism/webadmin/back/controller/LoginToWebController.java
  82. 252 0
      application-webadmin/src/main/java/com/tourism/webadmin/back/controller/RestaurantCartController.java
  83. 404 0
      application-webadmin/src/main/java/com/tourism/webadmin/back/controller/RestaurantFoodInfoController.java
  84. 332 0
      application-webadmin/src/main/java/com/tourism/webadmin/back/controller/RestaurantFoodTypeController.java
  85. 556 0
      application-webadmin/src/main/java/com/tourism/webadmin/back/controller/RestaurantInfoController.java
  86. 398 0
      application-webadmin/src/main/java/com/tourism/webadmin/back/controller/RestaurantTypeController.java
  87. 269 0
      application-webadmin/src/main/java/com/tourism/webadmin/back/controller/TourBookInfoController.java
  88. 276 0
      application-webadmin/src/main/java/com/tourism/webadmin/back/controller/TourCountryCodeController.java
  89. 200 0
      application-webadmin/src/main/java/com/tourism/webadmin/back/controller/TourOrderController.java
  90. 180 0
      application-webadmin/src/main/java/com/tourism/webadmin/back/controller/TourOrderPassenageController.java
  91. 591 0
      application-webadmin/src/main/java/com/tourism/webadmin/back/controller/TourTourismProjectTravelNotesController.java
  92. 179 0
      application-webadmin/src/main/java/com/tourism/webadmin/back/controller/TourTourismTravelNotesContentController.java
  93. 294 0
      application-webadmin/src/main/java/com/tourism/webadmin/back/controller/TourTourismTravelNotesFileController.java
  94. 179 0
      application-webadmin/src/main/java/com/tourism/webadmin/back/controller/TourTravelNotesProjectRelationController.java
  95. 368 0
      application-webadmin/src/main/java/com/tourism/webadmin/back/controller/TourUserController.java
  96. 179 0
      application-webadmin/src/main/java/com/tourism/webadmin/back/controller/TourUserLikeTravelNotesController.java
  97. 2 2
      application-webadmin/src/main/java/com/tourism/webadmin/back/controller/TourismContentController.java
  98. 195 0
      application-webadmin/src/main/java/com/tourism/webadmin/back/controller/TourismDatePriceController.java
  99. 6 4
      application-webadmin/src/main/java/com/tourism/webadmin/back/controller/TourismFileController.java
  100. 29 23
      application-webadmin/src/main/java/com/tourism/webadmin/back/controller/TourismProjectController.java

+ 43 - 1
application-webadmin/pom.xml

@@ -80,7 +80,49 @@
             <artifactId>common-dict</artifactId>
             <version>1.0.0</version>
         </dependency>
-<!--        <dependency>-->
+        <dependency>
+            <groupId>com.tourism</groupId>
+            <artifactId>common-additional</artifactId>
+            <version>1.0.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>javax.annotation</groupId>
+            <artifactId>javax.annotation-api</artifactId>
+            <version>1.3</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara.sms4j</groupId>
+            <artifactId>sms4j-spring-boot-starter</artifactId>
+            <version>3.3.3</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.binarywang</groupId>
+            <artifactId>wx-java-miniapp-spring-boot-starter</artifactId>  <!-- 微信登录(小程序) -->
+            <version>4.6.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.volcengine</groupId>
+            <artifactId>volc-sdk-java</artifactId>
+            <version>1.0.105</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.ibm.icu</groupId>
+            <artifactId>icu4j</artifactId>
+            <version>69.1</version>
+        </dependency>
+
+        <dependency>
+            <groupId>commons-codec</groupId>
+            <artifactId>commons-codec</artifactId>
+            <version>1.15</version> <!-- 请根据实际情况调整版本号 -->
+        </dependency>
+
+        <!--        <dependency>-->
 <!--            <groupId>com.anji-plus</groupId>-->
 <!--            <artifactId>spring-boot-starter-captcha</artifactId>-->
 <!--            <version>1.3.0</version>-->

+ 1 - 0
application-webadmin/src/main/java/com/tourism/webadmin/WebAdminApplication.java

@@ -15,6 +15,7 @@ import org.springframework.scheduling.annotation.EnableAsync;
  */
 @EnableAsync
 @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
+@MapperScan("com.tourism.webadmin.app.website.dao")
 //@MapperScan({"com.tourism.back.dao.mapper","com.tourism.upms.dao.mapper"})
 @ComponentScan("com.tourism")
 public class WebAdminApplication {

+ 42 - 38
application-webadmin/src/main/java/com/tourism/webadmin/app/website/controller/BasicToWebController.java

@@ -1,81 +1,85 @@
 package com.tourism.webadmin.app.website.controller;
 
 import cn.dev33.satoken.annotation.SaIgnore;
-import com.tourism.common.core.object.MyOrderParam;
 import com.tourism.common.core.object.MyPageData;
 import com.tourism.common.core.object.ResponseResult;
-import com.tourism.common.core.util.MyPageUtil;
-import com.tourism.webadmin.back.model.BannerInfo;
-import com.tourism.webadmin.back.model.CompanyInfo;
-import com.tourism.webadmin.back.model.DirectoryInfo;
-import com.tourism.webadmin.back.service.BannerInfoService;
-import com.tourism.webadmin.back.service.CompanyInfoService;
-import com.tourism.webadmin.back.service.DirectoryInfoService;
+import com.tourism.common.log.annotation.OperationLog;
+import com.tourism.common.log.model.constant.SysOperationLogType;
+import com.tourism.webadmin.app.website.service.BasicToWebService;
 import com.tourism.webadmin.back.vo.BannerInfoVo;
-import com.tourism.webadmin.back.vo.CompanyInfoVo;
 import com.tourism.webadmin.back.vo.DirectoryInfoVo;
+import com.tourism.webadmin.back.vo.TourTourismProjectTravelNotesVo;
 import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.constraints.NotNull;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
 import java.util.List;
 
-@Tag(name = "提供网页基础信息")
+@Tag(name = "门户网站基础信息接口")
 @Slf4j
 @RestController
 @SaIgnore
+@Validated
+@ConditionalOnProperty(name = "common-online.operationEnabled", havingValue = "true")
 @RequestMapping("/website/basic")
 public class BasicToWebController {
 
     @Autowired
-    private BannerInfoService bannerInfoService;
-    @Autowired
-    private CompanyInfoService companyInfoService;
-    @Autowired
-    private DirectoryInfoService directoryInfoService;
+    private BasicToWebService basicToWebServicel;
 
     /**
      * 列出符合过滤条件的banner管理列表。
      * @return 应答结果对象,包含查询结果集。
      */
+    @OperationLog(type = SysOperationLogType.LIST)
     @GetMapping("/bannerList")
-    public ResponseResult<MyPageData<BannerInfoVo>> bannerInfoList(@RequestParam("belongTab") Long belongTab) {
-        BannerInfo bannerInfoFilter = new BannerInfo();
-        bannerInfoFilter.setBelongTab(belongTab);
-        String orderBy = MyOrderParam.buildOrderBy(new MyOrderParam(), BannerInfo.class);
-        List<BannerInfo> bannerInfoList = bannerInfoService.getBannerInfoListWithRelation(bannerInfoFilter, orderBy);
-        return ResponseResult.success(MyPageUtil.makeResponseData(bannerInfoList, BannerInfoVo.class));
+    public ResponseResult<MyPageData<BannerInfoVo>> bannerInfoList(@NotNull(message = "所属分类(belongTab)不能为空") Long belongTab) {
+
+        MyPageData<BannerInfoVo> bannerInfoVoMyPageData = basicToWebServicel.getBannerInfoList(belongTab);
+        return ResponseResult.success(bannerInfoVoMyPageData);
     }
 
     /**
      * 列出符合过滤条件的门户菜单管理列表。
      * @return 应答结果对象,包含查询结果集。
      */
+    @OperationLog(type = SysOperationLogType.LIST)
     @GetMapping("/directoryList")
-    public ResponseResult<MyPageData<DirectoryInfoVo>> directoryInfoList(@RequestParam("parentId") Long parentId) {
+    public ResponseResult<MyPageData<DirectoryInfoVo>> directoryInfoList(Long parentId,Integer isHotspot,@RequestParam(required = false) Integer isAll) {
 //        if (ObjectUtils.isEmpty(parentId)){
 //            return ResponseResult.error("NO-ERROR","父级Id为空");
 //        }
-        DirectoryInfo directoryInfoFilter = new DirectoryInfo();
-        directoryInfoFilter.setParentId(parentId);
-        String orderBy = MyOrderParam.buildOrderBy(new MyOrderParam(), DirectoryInfo.class);
-        List<DirectoryInfo> directoryInfoList =
-                directoryInfoService.getDirectoryInfoListWithRelation(directoryInfoFilter, orderBy);
-        return ResponseResult.success(MyPageUtil.makeResponseData(directoryInfoList, DirectoryInfoVo.class));
+        MyPageData<DirectoryInfoVo> directoryInfoVoMyPageData = basicToWebServicel.getDirectoryInfoList(parentId, isHotspot, isAll);
+        return ResponseResult.success(directoryInfoVoMyPageData);
     }
 
+//    /**
+//     * 列出符合过滤条件的公司信息管理列表。
+//     *  @return 应答结果对象,包含查询结果集。
+//     */
+//    @GetMapping("/companyList")
+//    public ResponseResult<MyPageData<CompanyInfoVo>> companyInfoList(@NotNull(message = "公司类型(companyType)不能为空") Integer companyType) {
+//        CompanyInfo companyInfoFilter = new CompanyInfo();
+//        companyInfoFilter.setCompanyType(companyType);
+//        String orderBy = MyOrderParam.buildOrderBy(new MyOrderParam(), CompanyInfo.class);
+//        List<CompanyInfo> companyInfoList =
+//                companyInfoService.getCompanyInfoListWithRelation(companyInfoFilter, orderBy);
+//        return ResponseResult.success(MyPageUtil.makeResponseData(companyInfoList, CompanyInfoVo.class));
+//    }
+
+
+
     /**
-     * 列出符合过滤条件的公司信息管理列表。
-     *  @return 应答结果对象,包含查询结果集。
+     * 生成随机的浏览量,点赞数和热度值
+     * @return 应答结果对象,包含查询结果集。
      */
-    @GetMapping("/companyList")
-    public ResponseResult<MyPageData<CompanyInfoVo>> companyInfoList(@RequestParam("companyType") Integer companyType) {
-        CompanyInfo companyInfoFilter = new CompanyInfo();
-        companyInfoFilter.setCompanyType(companyType);
-        String orderBy = MyOrderParam.buildOrderBy(new MyOrderParam(), CompanyInfo.class);
-        List<CompanyInfo> companyInfoList =
-                companyInfoService.getCompanyInfoListWithRelation(companyInfoFilter, orderBy);
-        return ResponseResult.success(MyPageUtil.makeResponseData(companyInfoList, CompanyInfoVo.class));
+    @OperationLog(type = SysOperationLogType.UPDATE)
+    @GetMapping("/generate")
+    public void generate(){
+        basicToWebServicel.generate();
     }
 }

+ 79 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/website/controller/CleanDataController.java

@@ -0,0 +1,79 @@
+//package com.tourism.webadmin.app.website.controller;
+//
+//import cn.dev33.satoken.annotation.SaIgnore;
+//import com.tourism.common.core.util.MyDateUtil;
+//import com.tourism.webadmin.back.model.TourismDatePrice;
+//import com.tourism.webadmin.back.model.TourismProject;
+//import com.tourism.webadmin.back.service.TourismDatePriceService;
+//import com.tourism.webadmin.back.service.TourismProjectService;
+//import io.swagger.v3.oas.annotations.tags.Tag;
+//import lombok.extern.slf4j.Slf4j;
+//import org.apache.commons.lang3.time.DateUtils;
+//import org.springframework.beans.factory.annotation.Autowired;
+//import org.springframework.web.bind.annotation.GetMapping;
+//import org.springframework.web.bind.annotation.PostMapping;
+//import org.springframework.web.bind.annotation.RequestMapping;
+//import org.springframework.web.bind.annotation.RestController;
+//import com.tourism.webadmin.app.website.service.CleanDataService;
+//
+//import java.util.ArrayList;
+//import java.util.Date;
+//import java.util.List;
+//
+///**
+// * 数据清洗接口。
+// *
+// * @author 吃饭睡觉
+// * @date 2024-09-06
+// */
+//@Tag(name = "数据清洗接口")
+//@Slf4j
+//@RestController
+//@RequestMapping("/cleanData")
+//public class CleanDataController {
+//
+//    @Autowired
+//    private CleanDataService cleanDataService;
+//    @Autowired
+//    private TourismProjectService tourismProjectService;
+//    @Autowired
+//    private TourismDatePriceService tourismDatePriceService;
+//
+//    /**
+//     * 列出符合过滤条件个人订单列表。
+//     *
+//     *
+//     * @return 应答结果对象,包含查询结果集。
+//     */
+//    @SaIgnore
+//    @GetMapping("/cleanRichTextData")
+//    public void cleanRichTextData() throws Exception{
+//        cleanDataService.cleanRichTextData();
+//    }
+//
+//    /**
+//     * 根据项目生成未来三个月的日历价格。
+//     * @return 应答结果对象,包含查询结果集。
+//     */
+//    @PostMapping("/generateDatePrice")
+//    public void generateDatePrice(){
+//        TourismProject tourismProject = new TourismProject();
+//        List<TourismProject> tourismProjectList =
+//                tourismProjectService.getTourismProjectList(tourismProject, "");
+//        //for循环根据日期遍历
+//        tourismProjectList.stream().forEach(item->{
+//            Date date = DateUtils.addMonths(MyDateUtil.truncateToDay(new Date()),3);
+//            List<TourismDatePrice> tourismDatePriceList = new ArrayList<>();
+//            for(Date date1 = MyDateUtil.truncateToDay(new Date());date1.before(date);date1 = DateUtils.addDays(date1,1)) {
+//                TourismDatePrice tourismDatePrice = new TourismDatePrice();
+//                tourismDatePrice.setProjectId(item.getId());
+//                tourismDatePrice.setAdultPrice(item.getPrice());
+//                tourismDatePrice.setChildrenPrice(item.getPrice());
+//                tourismDatePrice.setDepartureDate(date1);
+//                tourismDatePriceList.add(tourismDatePrice);
+//            }
+//            tourismDatePriceService.saveNewBatch(tourismDatePriceList);
+//        });
+//
+//    }
+//}

+ 77 - 17
application-webadmin/src/main/java/com/tourism/webadmin/app/website/controller/JobProjectToWebController.java

@@ -1,31 +1,41 @@
 package com.tourism.webadmin.app.website.controller;
 
-import cn.dev33.satoken.annotation.SaCheckPermission;
 import cn.dev33.satoken.annotation.SaIgnore;
+import com.fasterxml.jackson.databind.ObjectMapper;
 import com.github.pagehelper.page.PageMethod;
-import com.tourism.common.core.annotation.MyRequestBody;
+import com.tourism.common.additional.utils.MapConvertUtils;
+import com.tourism.common.additional.utils.StringUtils;
+import com.tourism.common.additional.utils.UrlConvertUtils;
 import com.tourism.common.core.constant.ErrorCodeEnum;
 import com.tourism.common.core.object.*;
 import com.tourism.common.core.util.MyModelUtil;
 import com.tourism.common.core.util.MyPageUtil;
-import com.tourism.webadmin.back.dto.JobProjectDto;
+import com.tourism.webadmin.app.website.dto.TourismProjectToWebDto;
+import com.tourism.webadmin.back.model.DirectoryInfo;
 import com.tourism.webadmin.back.model.JobProject;
+import com.tourism.webadmin.back.model.TourismFile;
 import com.tourism.webadmin.back.service.JobProjectService;
+import com.tourism.webadmin.back.service.TourismProjectService;
 import com.tourism.webadmin.back.vo.JobProjectVo;
+import com.tourism.common.additional.config.ApplicationConfig;
 import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
 import java.util.List;
 
+import static java.util.stream.Collectors.toList;
+
 /**
  * 网页劳务项目管理操作控制器类。
  *
  * @author 吃饭睡觉
  * @date 2024-09-06
  */
-@Tag(name = "网页劳务项目管理接口")
+@Tag(name = "门户劳务项目管理接口")
 @Slf4j
 @RestController
 @SaIgnore
@@ -34,27 +44,57 @@ public class JobProjectToWebController {
 
     @Autowired
     private JobProjectService jobProjectService;
-
+    @Autowired
+    private TourismProjectService tourismProjectService;
+    @Autowired
+    private ApplicationConfig applicationConfig;
     /**
      * 列出符合过滤条件的劳务项目管理列表。
      *
-     * @param jobProjectDtoFilter 过滤对象。
-     * @param orderParam 排序参数。
-     * @param pageParam 分页参数。
+     * @param tourismProjectDtoFilter 过滤对象。
      * @return 应答结果对象,包含查询结果集。
      */
-    @PostMapping("/list")
+    @GetMapping("/list")
     public ResponseResult<MyPageData<JobProjectVo>> list(
-            @MyRequestBody JobProjectDto jobProjectDtoFilter,
-            @MyRequestBody MyOrderParam orderParam,
-            @MyRequestBody MyPageParam pageParam) {
-        if (pageParam != null) {
-            PageMethod.startPage(pageParam.getPageNum(), pageParam.getPageSize(), pageParam.getCount());
+            @Valid @ModelAttribute TourismProjectToWebDto tourismProjectDtoFilter) {
+        if(tourismProjectDtoFilter.getBelongTab() == null){
+            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST,"所属分类(belongTab)不能为空!");
+        }
+        //如果belongTab小于1000并且大于10.则表明查询的是一级菜单
+        if(tourismProjectDtoFilter.getBelongTab()>=10 && tourismProjectDtoFilter.getBelongTab()<1000){
+            DirectoryInfo filter = new DirectoryInfo();
+            filter.setParentId(tourismProjectDtoFilter.getBelongTab());
+            filter.setEnable(1);
+            List<DirectoryInfo> directoryInfoList = tourismProjectService.getDirectoryInfoList(filter, null);
+            tourismProjectDtoFilter.setBelongTab(null);
+            tourismProjectDtoFilter.setDirectoryInfoIds(directoryInfoList.stream().map(DirectoryInfo::getId).collect(toList()));
+        }
+        JobProject jobProjectFilter = MyModelUtil.copyTo(tourismProjectDtoFilter, JobProject.class);
+        //首页展示的为启用的内容
+        jobProjectFilter.setEnable(1);
+        if(tourismProjectDtoFilter.getPageSize() != null && tourismProjectDtoFilter.getPageNum() != null){
+            PageMethod.startPage(tourismProjectDtoFilter.getPageNum(), tourismProjectDtoFilter.getPageSize(), true);
         }
-        JobProject jobProjectFilter = MyModelUtil.copyTo(jobProjectDtoFilter, JobProject.class);
-        String orderBy = MyOrderParam.buildOrderBy(orderParam, JobProject.class);
+
+        MyOrderParam myOrderParam = new MyOrderParam();
+            myOrderParam.add(new MyOrderParam.OrderInfo("isHotspot",false,null));
+            myOrderParam.add(new MyOrderParam.OrderInfo("showOrder",false,null));
+        String orderBy = MyOrderParam.buildOrderBy(myOrderParam, JobProject.class);
+
         List<JobProject> jobProjectList = jobProjectService.getJobProjectListWithRelation(jobProjectFilter, orderBy);
-        return ResponseResult.success(MyPageUtil.makeResponseData(jobProjectList, JobProjectVo.class));
+
+        MyPageData<JobProjectVo> jobProjectVoMyPageData = MyPageUtil.makeResponseData(jobProjectList, JobProjectVo.class);
+        List<JobProjectVo> dataList = jobProjectVoMyPageData.getDataList();
+        //先把imgUrl由jaon转换为List<FileUrlObject>
+        dataList.stream().forEach(item ->
+        {
+            if (StringUtils.isNotEmpty(item.getJobUrl())) {
+                List<String> jobUrl = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), item.getJobUrl());
+                item.setJobUrlsAfterConvert(jobUrl);
+            }
+        });
+
+        return ResponseResult.success(jobProjectVoMyPageData);
     }
 
     /**
@@ -71,6 +111,26 @@ public class JobProjectToWebController {
             return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
         }
         JobProjectVo jobProjectVo = MyModelUtil.copyTo(jobProject, JobProjectVo.class);
+        List<String> jobUrl = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), jobProjectVo.getJobUrl());
+        jobProjectVo.setJobUrlsAfterConvert(jobUrl);
+        if(jobProjectVo.getJobFile() != null) {
+            // 使用 Jackson 的 ObjectMapper 进行转换
+            ObjectMapper objectMapper = new ObjectMapper();
+            TourismFile tourismFile = objectMapper.convertValue(jobProjectVo.getJobFile(), TourismFile.class);
+            if (StringUtils.isNotEmpty(tourismFile.getFileUrl())) {
+                tourismFile.setFileUrlsAfterConvert(UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), tourismFile.getFileUrl()));
+                jobProjectVo.setJobFile(MapConvertUtils.convertObjectToMap(tourismFile));
+            }
+        }
+        if(jobProjectVo != null) {
+            jobProjectVo.setContactCodeConvert("https://v.xiaoyaotravel.com/image/ContactQRCode/labor_service.png");
+        }
+        //二维码路径赋值
+//        jobProjectVo.setContactCode("[{\"name\":\"微信图片_20241021161423.jpg\",\"downloadUri\":\"/admin/app/jobProject/download\",\"filename\":\"4bfb830a6091456d823db0b441c4edd1.jpg\",\"uploadPath\":\"image/JobProject/contactCode\"}]");
+//        List<String> urlConvertList = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), jobProjectVo.getContactCode());
+//        if(CollectionUtils.isNotEmpty(urlConvertList)) {
+//            jobProjectVo.setContactCodeConvert(urlConvertList.get(0));
+//        }
         return ResponseResult.success(jobProjectVo);
     }
 }

+ 50 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/website/controller/JobRecordController.java

@@ -0,0 +1,50 @@
+package com.tourism.webadmin.app.website.controller;
+
+import com.tourism.common.core.constant.ErrorCodeEnum;
+import com.tourism.common.core.object.MyPageData;
+import com.tourism.common.core.object.ResponseResult;
+import com.tourism.common.core.object.TokenData;
+import com.tourism.webadmin.app.website.dto.PageDto;
+import com.tourism.webadmin.app.website.service.JobRecordService;
+import com.tourism.webadmin.back.model.TourUser;
+import com.tourism.webadmin.back.service.TourUserService;
+import com.tourism.webadmin.back.vo.TourBookInfoVo;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@Tag(name = "劳务记录接口")
+@Slf4j
+@RestController
+@Validated
+@RequestMapping("/website/jobRecord")
+public class JobRecordController {
+    @Autowired
+    private TourUserService tourUserService;
+    @Autowired
+    private JobRecordService jobRecordService;
+
+    /**
+     * 列出符合条件的劳务记录列表。
+     *
+     * @return 应答结果对象,包含查询结果集。
+     */
+    @GetMapping("/list")
+    public ResponseResult<MyPageData<TourBookInfoVo>> list(PageDto pageDto) {
+        TokenData tokenData = TokenData.takeFromRequest();
+        if(tokenData == null){
+            return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED);
+        }
+        Long userId = tokenData.getUserId();
+        TourUser tourUser = tourUserService.getById(userId);
+        if(tourUser == null){
+            return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED);
+        }
+        MyPageData<TourBookInfoVo> tourBookInfoVoMyPageData = jobRecordService.jobRecordList(tourUser.getMobile(), pageDto);
+        return ResponseResult.success(tourBookInfoVoMyPageData);
+    }
+}

+ 628 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/website/controller/LoginToWebsiteController.java

@@ -0,0 +1,628 @@
+package com.tourism.webadmin.app.website.controller;
+
+import cn.binarywang.wx.miniapp.api.WxMaService;
+import cn.binarywang.wx.miniapp.api.WxMaUserService;
+import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
+import cn.dev33.satoken.annotation.SaIgnore;
+import cn.dev33.satoken.session.SaSession;
+import cn.dev33.satoken.stp.StpUtil;
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.BooleanUtil;
+import cn.hutool.core.util.StrUtil;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.tourism.common.additional.constant.CacheConstants;
+import com.tourism.common.additional.constant.Constants;
+import com.tourism.common.additional.sign.Base64;
+import com.tourism.common.additional.uuid.IdUtils;
+import com.tourism.common.core.constant.ApplicationConstant;
+import com.tourism.common.core.constant.ErrorCodeEnum;
+import com.tourism.common.core.object.*;
+import com.tourism.common.core.upload.BaseUpDownloader;
+import com.tourism.common.core.upload.UpDownloaderFactory;
+import com.tourism.common.core.upload.UploadResponseInfo;
+import com.tourism.common.core.upload.UploadStoreInfo;
+import com.tourism.common.core.util.*;
+import com.tourism.common.log.annotation.OperationLog;
+import com.tourism.common.log.model.constant.SysOperationLogType;
+import com.tourism.common.redis.cache.SessionCacheHelper;
+import com.tourism.common.satoken.util.SaTokenUtil;
+import com.tourism.webadmin.app.website.dto.CheckImageDto;
+import com.tourism.webadmin.app.website.vo.WebSiteCaptchaImageVo;
+import com.tourism.webadmin.back.dto.TourBookInfoDto;
+import com.tourism.webadmin.app.website.dto.TourUserRegisterDto;
+import com.tourism.webadmin.back.model.TourBookInfo;
+import com.tourism.webadmin.back.model.TourCountryCode;
+import com.tourism.webadmin.back.model.TourUser;
+import com.tourism.webadmin.back.model.constant.TourUserStatus;
+import com.tourism.webadmin.app.website.service.LoginToWebsiteService;
+import com.tourism.webadmin.back.service.TourCountryCodeService;
+import com.tourism.webadmin.back.service.TourUserService;
+import com.tourism.webadmin.back.service.impl.TourBookInfoServiceImpl;
+import com.tourism.webadmin.back.util.SMSUtils;
+import com.tourism.common.additional.config.ApplicationConfig;
+import com.tourism.webadmin.upms.model.SysUser;
+import io.jsonwebtoken.lang.Collections;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import com.tourism.common.core.annotation.DisableDataFilter;
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.Valid;
+import lombok.extern.slf4j.Slf4j;
+import me.chanjar.weixin.common.error.WxErrorException;
+import org.apache.commons.lang3.StringUtils;
+import org.redisson.api.RBucket;
+import org.redisson.api.RedissonClient;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.FastByteArrayOutputStream;
+import org.springframework.web.bind.annotation.*;
+import com.google.code.kaptcha.Producer;
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+import com.tourism.common.core.annotation.MyRequestBody;
+import org.springframework.web.multipart.MultipartFile;
+
+/**
+ * 登录接口控制器类。
+ *
+ * @author chenchen
+ * @date 2024-09-18
+ */
+@Tag(name = "门户用户登录接口")
+@DisableDataFilter
+@Slf4j
+@RestController
+@RequestMapping("/website/web")
+public class LoginToWebsiteController {
+    @Autowired
+    private TourUserService tourUserService;
+    @Autowired
+    private LoginToWebsiteService loginToWebsiteService;
+    @Resource(name = "captchaProducerMath")
+    private Producer captchaProducerMath;
+    @Autowired
+    private PasswordEncoder passwordEncoder;
+    @Autowired
+    private ApplicationConfig appConfig;
+    @Autowired
+    private RedissonClient redissonClient;
+    @Autowired
+    private SessionCacheHelper cacheHelper;
+    @Autowired
+    private UpDownloaderFactory upDownloaderFactory;
+    @Autowired
+    private WxMaService wxMaService;
+
+    @Autowired
+    private TourCountryCodeService tourCountryCodeService;
+
+    private static final String SHOW_NAME_FIELD = "showName";
+    private static final String HEAD_IMAGE_URL_FIELD = "headImageUrl";
+    @Autowired
+    private TourBookInfoServiceImpl tourBookInfoService;
+
+
+    /**
+     * 列出符合过滤条件的国家地区代码表列表。
+     *
+     * @return 应答结果对象,包含查询结果集。
+     */
+    @SaIgnore
+    @GetMapping("/list")
+    public ResponseResult<Map<String, List<TourCountryCode>>> list() {
+        List<TourCountryCode> tourCountryCodeList =
+                tourCountryCodeService.getTourCountryCodeList(new TourCountryCode(), "");
+        Map<String, List<TourCountryCode>> collect = tourCountryCodeList.stream().collect(Collectors.groupingBy(TourCountryCode::getFirstCode));
+        return ResponseResult.success(collect);
+    }
+
+
+    /**
+     * 门户网站留存信息
+     * 返回值:1预订成功 2预定失败
+     */
+    @Transactional(rollbackFor = Exception.class)
+    @PostMapping("/saveBookInfo")
+    public ResponseResult<Integer> saveBookInfo(HttpServletRequest request,
+                                             @MyRequestBody TourBookInfoDto tourBookInfoDto){
+
+        TokenData tokenData = TokenData.takeFromRequest();
+        if(tokenData == null){
+            return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED);
+        }
+        Long userId = tokenData.getUserId();
+        TourUser tourUser = tourUserService.getById(userId);
+        if(tourUser == null){
+            return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED);
+        }
+
+        //在redis中查看key为该ip的缓存对象是否大于6个;>=6个则不保存了;<6个则进行保存并缓存数据(缓存超时时间为1分钟)
+        String remoteIpAddress = IpUtil.getRemoteIpAddress(request);
+        String startKey = CacheConstants.TOUR_BOOK_IP.concat(remoteIpAddress+tourBookInfoDto.getProjectId()+tourBookInfoDto.getType());
+        String convertStartKey = com.tourism.common.additional.utils.StringUtils.replacePattern(startKey, ":", "_");
+        Iterable<String> keysByPattern = redissonClient.getKeys().getKeysByPattern(convertStartKey.concat("*"));
+
+        // 将 Iterable 转换为 List
+        List<String> keyList = new ArrayList<>();
+        for (String key : keysByPattern) {
+            keyList.add(key);
+        }
+        if(Collections.isEmpty(keyList)) {
+            String key = convertStartKey;
+            // 获取 RBucket 对象
+            RBucket<String> bucket = redissonClient.getBucket(key);
+            // 设置缓存对象
+            bucket.set(tourUser.getMobile());
+            // 设置过期时间
+            bucket.expire(1, TimeUnit.MINUTES);
+            //根据手机号+项目id判断是否已经预约过
+            TourBookInfo tourBookInfo = new TourBookInfo();
+            tourBookInfo.setBookMobile(tourUser.getMobile());
+            tourBookInfo.setType(tourBookInfoDto.getType());
+            tourBookInfo.setProjectId(tourBookInfoDto.getProjectId());
+            boolean b = tourBookInfoService.existByFilter(tourBookInfo);
+            if(b){
+                return ResponseResult.success(2);
+            }
+
+            tourBookInfoDto.setBookMobile(tourUser.getMobile());
+            boolean isSave =
+                    loginToWebsiteService.saveBookInfo(request, tourBookInfoDto);
+            if(!isSave){
+                return ResponseResult.success(2);
+            }
+            return ResponseResult.success(1);
+        }else {
+            return ResponseResult.success(2);
+        }
+    }
+    /**
+     * 生成验证码(math)
+     */
+    @SaIgnore
+    @GetMapping("/captchaImage")
+    public ResponseResult<WebSiteCaptchaImageVo> getCode() throws IOException
+    {
+        // 保存验证码信息
+        String uuid = IdUtils.simpleUUID();
+        String verifyKey = CacheConstants.getCaptchaCodeKey(uuid);
+
+        String capStr = null, code = null;
+        BufferedImage image = null;
+
+        // 生成验证码
+        String capText = captchaProducerMath.createText();
+        capStr = capText.substring(0, capText.lastIndexOf("@"));
+        code = capText.substring(capText.lastIndexOf("@") + 1);
+        image = captchaProducerMath.createImage(capStr);
+
+        // 获取 RBucket 对象
+        RBucket<String> bucket = redissonClient.getBucket(verifyKey);
+
+        // 设置缓存对象并设置过期时间
+        bucket.set(code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);
+
+//        redisCache.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);
+        // 转换流信息写出
+        FastByteArrayOutputStream os = new FastByteArrayOutputStream();
+        try
+        {
+            ImageIO.write(image, "jpg", os);
+        }
+        catch (IOException e)
+        {
+            return ResponseResult.error(ErrorCodeEnum.PICTURE_CODE_ERR);
+        }
+
+        WebSiteCaptchaImageVo webSiteCaptchaImageVo = new WebSiteCaptchaImageVo();
+        webSiteCaptchaImageVo.setUuid(uuid);
+        webSiteCaptchaImageVo.setImg(Base64.encode(os.toByteArray()));
+        return ResponseResult.success(webSiteCaptchaImageVo);
+    }
+
+    /**
+     * 校验图形验证码,校验成功,发送手机短信到手机
+     */
+    @SaIgnore
+    @GetMapping("/validateCaptchaAndSendSms")
+    public ResponseResult<Void> validateCaptcha(@Valid CheckImageDto checkImageDto){
+        // 校验图形验证码
+        String verifyKey = CacheConstants.getCaptchaCodeKey(checkImageDto.getUuid());
+        RBucket<String> bucket = redissonClient.getBucket(verifyKey);
+        String code = bucket.get();
+        if (!(code.equals(checkImageDto.getCode()))){
+            return ResponseResult.error(ErrorCodeEnum.PICTURE_CODE_ERR);
+        }
+
+        // 检查一分钟内是否发送过验证码
+        boolean exists = redissonClient.getBucket(CacheConstants.getSmsRepeatLimitKey(checkImageDto.getPhoneNumber())).isExists();
+        if(exists){
+            return ResponseResult.error(ErrorCodeEnum.SMS_CODE_SEND_REPEAT_LIMIT_ERR);
+        }
+
+        // 生成随机验证码数字
+        String random = String.valueOf(new Random().nextInt(9000) + 1000);
+//        String code = String.valueOf(rondom);
+        // 校验成功后,发短信到手机
+//        SmsResponse huawei = SmsFactory.getSmsBlend("huawei").sendMessage(checkImageDto.getPhoneNumber(), random);
+        SMSUtils.sendMsg(checkImageDto.getCountryCode(), checkImageDto.getPhoneNumber(), random);
+        String smsCodeKey = CacheConstants.getSmsCodeKey(checkImageDto.getPhoneNumber());
+        //5分钟有效时长
+        redissonClient.getBucket(smsCodeKey).set(random, CacheConstants.getSmsEffectiveSeconds(), TimeUnit.SECONDS);
+        //控制一分钟内不允许重复发送
+        redissonClient.getBucket(CacheConstants.getSmsRepeatLimitKey(checkImageDto.getPhoneNumber())).set(true, 60, TimeUnit.SECONDS);
+        return ResponseResult.success();
+    }
+
+    /**
+     * 验证手机验证码,登录
+     */
+    @SaIgnore
+    @OperationLog(type = SysOperationLogType.LOGIN, saveResponse = false)
+    @PostMapping("/doLoginBySms")
+    public ResponseResult<JSONObject> doLoginBySms(
+            @MyRequestBody String loginMoblie,
+            @MyRequestBody String smsCode,
+            @MyRequestBody String countryCode) throws UnsupportedEncodingException
+    {
+        // 校验短信验证码
+//        String smsCodeKey = CacheConstants.getSmsCodeKey(loginMoblie);
+//        RBucket<String> bucket = redissonClient.getBucket(smsCodeKey);
+//        String redisCode = bucket.get();
+//        if(StringUtils.isBlank(redisCode) || !redisCode.equals(smsCode)){
+//            return ResponseResult.error(ErrorCodeEnum.SMS_CODE_ERR);
+//        }
+//        // 短信验证码校验成功,清除验证码
+//        bucket.delete();
+        // 查询用户信息
+        TourUser tourUser = tourUserService.getOne(Wrappers.<TourUser>lambdaQuery().eq(TourUser::getMobile, loginMoblie));
+        // 如果用户信息不存在,则新建
+        if(tourUser == null){
+            tourUser = new TourUser();
+            tourUser.setLoginName(loginMoblie);
+            tourUser.setUserStatus(TourUserStatus.STATUS_NORMAL);
+            tourUser.setMobile(loginMoblie);
+            tourUser.setCountryCode(StringUtils.isBlank(countryCode)? "86" : countryCode);
+            tourUser.setPassword(passwordEncoder.encode("123456"));
+            tourUser = tourUserService.saveNew(tourUser);
+        }
+        // 生成token
+        JSONObject jsonObject = this.buildTourLoginDataAndLogin(tourUser);
+        return ResponseResult.success(jsonObject);
+    }
+
+    /**
+     * 小程序根据phoneCode登录
+     */
+    @SaIgnore
+    @OperationLog(type = SysOperationLogType.LOGIN, saveResponse = false)
+    @GetMapping("/doLoginByWxMini")
+    public ResponseResult<JSONObject> doLoginByWxMini(String phoneCode) throws WxErrorException {
+        log.info("小程序获取手机号code:" + phoneCode);
+        WxMaUserService userService = wxMaService.getUserService();
+        WxMaPhoneNumberInfo phoneNoInfo = userService.getPhoneNoInfo(phoneCode);
+
+        log.info("小程序获取手机号信息:" + phoneNoInfo);
+        TourUser tourUser = tourUserService.getOne(Wrappers.<TourUser>lambdaQuery().eq(TourUser::getMobile, phoneNoInfo.getPhoneNumber()));
+        // 如果用户信息不存在,则新建
+        if(tourUser == null){
+            tourUser = new TourUser();
+            tourUser.setLoginName(phoneNoInfo.getPhoneNumber());
+            tourUser.setUserStatus(TourUserStatus.STATUS_NORMAL);
+            tourUser.setMobile(phoneNoInfo.getPhoneNumber());
+            tourUser.setPassword(passwordEncoder.encode("123456"));
+            tourUser.setCountryCode(phoneNoInfo.getCountryCode());
+            tourUser = tourUserService.saveNew(tourUser);
+        }
+        // 生成token
+        JSONObject jsonObject = this.buildTourLoginDataAndLogin(tourUser);
+        jsonObject.put("userInfo", tourUser);
+        return ResponseResult.success(jsonObject);
+    }
+
+
+
+    /**
+     * 用户注册
+     * @return
+     * @throws
+     */
+    @SaIgnore
+    @OperationLog(type = SysOperationLogType.WEBSITE_Register, saveResponse = false)
+    @Transactional(rollbackFor = Exception.class)
+    @PostMapping("/doRegister")
+    public ResponseResult doRegister(@MyRequestBody TourUserRegisterDto tourUserRegisterDto) {
+
+        String errorMessage = MyCommonUtil.getModelValidationError(tourUserRegisterDto, false);
+        if (errorMessage != null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
+        }
+//        //验证图形验证码
+//        if(!loginToWebsiteService.validateCaptcha(tourUserRegisterDto.getUuid(),tourUserRegisterDto.getCode())){
+//            return ResponseResult.error(ErrorCodeEnum.PICTURE_CODE_ERR);
+//        }
+//        // 验证短信验证码
+//        if (!loginToWebsiteService.validateSmsCode(tourUserRegisterDto.getMobile(),tourUserRegisterDto.getSmsCode())) {
+//            return ResponseResult.error(ErrorCodeEnum.SMSCODE_ERR);
+//        }
+        //查看用户的手机号是否已经注册;注册的话,返回该手机号已存在的提示
+        if (tourUserService.getTourUserByLoginMobile(tourUserRegisterDto.getMobile()) != null){
+            return ResponseResult.error(ErrorCodeEnum.MOBILE_EXIST);
+        }
+        loginToWebsiteService.saveRegisterUserInfo(tourUserRegisterDto);
+        return ResponseResult.success();
+
+    }
+
+
+    /**
+     * 登录接口。
+     *
+     * @param loginName 登录名。
+     * @param password  密码。
+     * @return 应答结果对象,其中包括Token数据,以及菜单列表。
+     */
+    @SaIgnore
+    @OperationLog(type = SysOperationLogType.LOGIN, saveResponse = false)
+    @PostMapping("/doLoginByLonginName")
+    public ResponseResult<JSONObject> doLoginByLonginName(
+            @MyRequestBody String loginName,
+            @MyRequestBody String password) throws UnsupportedEncodingException {
+        if (MyCommonUtil.existBlankArgument(loginName, password)) {
+            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
+        }
+        ResponseResult<TourUser> verifyResult = this.verifyAndHandleLoginUser(loginName, password);
+        if (!verifyResult.isSuccess()) {
+            return ResponseResult.errorFrom(verifyResult);
+        }
+        JSONObject jsonData = this.buildTourLoginDataAndLogin(verifyResult.getData());
+        //返回token
+        return ResponseResult.success(jsonData);
+    }
+
+
+    /**
+     * 手机号登录接口。
+     *
+     * @param loginMoblie 手机号。
+     * @param password  密码。
+     * @return 应答结果对象,其中包括Token数据,以及菜单列表。
+     */
+    @SaIgnore
+    @OperationLog(type = SysOperationLogType.LOGIN, saveResponse = false)
+    @PostMapping("/doLoginByMobile")
+    public ResponseResult<JSONObject> doLoginByMobile(
+            @MyRequestBody String loginMoblie,
+            @MyRequestBody String password) throws UnsupportedEncodingException {
+        if (MyCommonUtil.existBlankArgument(loginMoblie, password)) {
+            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
+        }
+        ResponseResult<TourUser> verifyResult = this.verifyAndHandleLoginUserByMoblie(loginMoblie, password);
+        if (!verifyResult.isSuccess()) {
+            return ResponseResult.errorFrom(verifyResult);
+        }
+        JSONObject jsonData = this.buildTourLoginDataAndLogin(verifyResult.getData());
+        //返回token
+        return ResponseResult.success(jsonData);
+    }
+
+    /**
+     * 登出操作。同时将Session相关的信息从缓存中删除。
+     *
+     * @return 应答结果对象。
+     */
+    @OperationLog(type = SysOperationLogType.LOGOUT)
+    @PostMapping("/doLogout")
+    public ResponseResult<Void> doLogout() {
+        String sessionId = TokenData.takeFromRequest().getSessionId();
+        redissonClient.getBucket(TokenData.takeFromRequest().getMySessionId()).deleteAsync();
+        redissonClient.getBucket(RedisKeyUtil.makeSessionPermCodeKey(sessionId)).deleteAsync();
+        redissonClient.getBucket(RedisKeyUtil.makeSessionPermIdKey(sessionId)).deleteAsync();
+//        sysDataPermService.removeDataPermCache(sessionId);
+        cacheHelper.removeAllSessionCache(sessionId);
+        StpUtil.logout();
+        return ResponseResult.success();
+    }
+
+    /**
+     * 在登录之后,通过token再次获取登录信息。
+     * 用于在当前浏览器登录系统后,在新tab页中可以免密登录。
+     *
+     * @return 应答结果对象,其中包括JWT的Token数据,以及菜单列表。
+     */
+    @GetMapping("/getLoginInfo")
+    public ResponseResult<JSONObject> getLoginInfo() {
+        TokenData tokenData = TokenData.takeFromRequest();
+        JSONObject jsonData = new JSONObject();
+        jsonData.put(SHOW_NAME_FIELD, tokenData.getShowName());
+        if (StrUtil.isNotBlank(tokenData.getHeadImageUrl())) {
+            jsonData.put(HEAD_IMAGE_URL_FIELD, tokenData.getHeadImageUrl());
+        }
+        return ResponseResult.success(jsonData);
+    }
+
+    /**
+     * 用户修改自己的密码。
+     *
+     * @param oldPass 原有密码。
+     * @param newPass 新密码。
+     * @return 应答结果对象。
+     */
+    @PostMapping("/changePassword")
+    public ResponseResult<Void> changePassword(
+            @MyRequestBody String oldPass, @MyRequestBody String newPass) throws UnsupportedEncodingException {
+        if (MyCommonUtil.existBlankArgument(newPass, oldPass)) {
+            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
+        }
+        TokenData tokenData = TokenData.takeFromRequest();
+        TourUser tourUser = tourUserService.getById(tokenData.getUserId());
+        oldPass = URLDecoder.decode(oldPass, StandardCharsets.UTF_8.name());
+        // NOTE: 第一次使用时,请务必阅读ApplicationConstant.PRIVATE_KEY的代码注释。
+        // 执行RsaUtil工具类中的main函数,可以生成新的公钥和私钥。
+        oldPass = RsaUtil.decrypt(oldPass, ApplicationConstant.PRIVATE_KEY);
+        if (tourUser == null || !passwordEncoder.matches(oldPass, tourUser.getPassword())) {
+            return ResponseResult.error(ErrorCodeEnum.INVALID_USERNAME_PASSWORD);
+        }
+        newPass = URLDecoder.decode(newPass, StandardCharsets.UTF_8.name());
+        newPass = RsaUtil.decrypt(newPass, ApplicationConstant.PRIVATE_KEY);
+        if (!tourUserService.changePassword(tokenData.getUserId(), newPass)) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
+        }
+        return ResponseResult.success();
+    }
+
+    /**
+     * 上传并修改用户头像。
+     *
+     * @param uploadFile 上传的头像文件。
+     */
+    @PostMapping("/changeHeadImage")
+    public void changeHeadImage(@RequestParam("uploadFile") MultipartFile uploadFile) throws IOException {
+        UploadStoreInfo storeInfo = MyModelUtil.getUploadStoreInfo(TourUser.class, HEAD_IMAGE_URL_FIELD);
+        BaseUpDownloader upDownloader = upDownloaderFactory.get(storeInfo.getStoreType());
+        UploadResponseInfo responseInfo = upDownloader.doUpload(null,
+                appConfig.getUploadFileBaseDir(), TourUser.class.getSimpleName(), HEAD_IMAGE_URL_FIELD, true, uploadFile);
+        if (BooleanUtil.isTrue(responseInfo.getUploadFailed())) {
+            ResponseResult.output(HttpServletResponse.SC_FORBIDDEN,
+                    ResponseResult.error(ErrorCodeEnum.UPLOAD_FAILED, responseInfo.getErrorMessage()));
+            return;
+        }
+        responseInfo.setDownloadUri("/admin/upms/login/downloadHeadImage");
+        String newHeadImage = JSONArray.toJSONString(CollUtil.newArrayList(responseInfo));
+        if (!tourUserService.changeHeadImage(TokenData.takeFromRequest().getUserId(), newHeadImage)) {
+            ResponseResult.output(HttpServletResponse.SC_FORBIDDEN, ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST));
+            return;
+        }
+        ResponseResult.output(ResponseResult.success(responseInfo));
+    }
+
+    /**
+     * 下载用户头像。
+     *
+     * @param filename 文件名。如果没有提供该参数,就从当前记录的指定字段中读取。
+     * @param response Http 应答对象。
+     */
+    @GetMapping("/downloadHeadImage")
+    public void downloadHeadImage(String filename, HttpServletResponse response) {
+        try {
+            UploadStoreInfo storeInfo = MyModelUtil.getUploadStoreInfo(SysUser.class, HEAD_IMAGE_URL_FIELD);
+            BaseUpDownloader upDownloader = upDownloaderFactory.get(storeInfo.getStoreType());
+            upDownloader.doDownload(appConfig.getUploadFileBaseDir(),
+                    TourUser.class.getSimpleName(), HEAD_IMAGE_URL_FIELD, filename, true, response);
+        } catch (Exception e) {
+            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+            log.error(e.getMessage(), e);
+        }
+    }
+    private JSONObject buildTourLoginDataAndLogin(TourUser tourUser) {
+        TokenData tokenData = this.loginAndCreateToken(tourUser);
+        // 这里手动将TokenData存入request,便于OperationLogAspect统一处理操作日志。
+        TokenData.addToRequest(tokenData);
+        JSONObject jsonData = this.createResponseData(tourUser);
+        return jsonData;
+    }
+
+
+    private JSONObject createResponseData(TourUser tourUser) {
+        JSONObject jsonData = new JSONObject();
+        jsonData.put(TokenData.REQUEST_ATTRIBUTE_NAME, StpUtil.getTokenValue());
+        jsonData.put(SHOW_NAME_FIELD, tourUser.getShowName());
+        if (StrUtil.isNotBlank(tourUser.getHeadImageUrl())) {
+            jsonData.put(HEAD_IMAGE_URL_FIELD, tourUser.getHeadImageUrl());
+        }
+        return jsonData;
+    }
+
+    private TokenData loginAndCreateToken(TourUser tourUser) {
+        String deviceType = MyCommonUtil.getDeviceTypeWithString();
+        LoginUserInfo userInfo = BeanUtil.copyProperties(tourUser, LoginUserInfo.class);
+        String loginId = SaTokenUtil.makeLoginId(userInfo);
+        StpUtil.login(loginId, deviceType);
+        SaSession session = StpUtil.getTokenSession();
+        TokenData tokenData = this.buildTokenData(tourUser, session.getId(), StpUtil.getLoginDevice());
+        String mySessionId = RedisKeyUtil.getSessionIdPrefix(tokenData, tourUser.getLoginName()) + MyCommonUtil.generateUuid();
+        tokenData.setMySessionId(mySessionId);
+        tokenData.setToken(session.getToken());
+        redissonClient.getBucket(mySessionId)
+                .set(JSON.toJSONString(tokenData), appConfig.getSessionExpiredSeconds(), TimeUnit.SECONDS);
+        session.set(TokenData.REQUEST_ATTRIBUTE_NAME, tokenData);
+        return tokenData;
+    }
+
+    private TokenData buildTokenData(TourUser tourUser, String sessionId, String deviceType) {
+        TokenData tokenData = new TokenData();
+        tokenData.setSessionId(sessionId);
+        tokenData.setUserId(tourUser.getUserId());
+        tokenData.setLoginName(tourUser.getLoginName());
+        tokenData.setShowName(tourUser.getShowName());
+        tokenData.setLoginIp(IpUtil.getRemoteIpAddress(ContextUtil.getHttpRequest()));
+        tokenData.setLoginTime(new Date());
+        tokenData.setDeviceType(deviceType);
+        tokenData.setHeadImageUrl(tourUser.getHeadImageUrl());
+        return tokenData;
+    }
+
+    private ResponseResult<TourUser> verifyAndHandleLoginUser(
+            String loginName, String password) throws UnsupportedEncodingException {
+        String errorMessage;
+        TourUser tourUser = tourUserService.getTourUserByLoginName(loginName);
+        password = URLDecoder.decode(password, StandardCharsets.UTF_8.name());
+        // NOTE: 第一次使用时,请务必阅读ApplicationConstant.PRIVATE_KEY的代码注释。
+        // 执行RsaUtil工具类中的main函数,可以生成新的公钥和私钥。
+        password = RsaUtil.decrypt(password, ApplicationConstant.PRIVATE_KEY);
+        if (tourUser == null || !passwordEncoder.matches(password, tourUser.getPassword())) {
+            return ResponseResult.error(ErrorCodeEnum.INVALID_USERNAME_PASSWORD);
+        }
+        if (tourUser.getUserStatus() == TourUserStatus.STATUS_LOCKED) {
+            errorMessage = "登录失败,用户账号被锁定!";
+            return ResponseResult.error(ErrorCodeEnum.INVALID_USER_STATUS, errorMessage);
+        }
+        //本项目是否开启排他登录
+        if (BooleanUtil.isTrue(appConfig.getExcludeLogin())) {
+            String deviceType = MyCommonUtil.getDeviceTypeWithString();
+            LoginUserInfo userInfo = BeanUtil.copyProperties(tourUser, LoginUserInfo.class);
+            String loginId = SaTokenUtil.makeLoginId(userInfo);
+            StpUtil.kickout(loginId, deviceType);
+        }
+        return ResponseResult.success(tourUser);
+    }
+
+    private ResponseResult<TourUser> verifyAndHandleLoginUserByMoblie(
+            String loginMoblie, String password) throws UnsupportedEncodingException {
+        String errorMessage;
+        TourUser tourUser = tourUserService.getTourUserByLoginMobile(loginMoblie);
+        password = URLDecoder.decode(password, StandardCharsets.UTF_8.name());
+        // NOTE: 第一次使用时,请务必阅读ApplicationConstant.PRIVATE_KEY的代码注释。
+        // 执行RsaUtil工具类中的main函数,可以生成新的公钥和私钥。
+        password = RsaUtil.decrypt(password, ApplicationConstant.PRIVATE_KEY);
+        if (tourUser == null || !passwordEncoder.matches(password, tourUser.getPassword())) {
+            return ResponseResult.error(ErrorCodeEnum.INVALID_USERNAME_PASSWORD);
+        }
+        if (tourUser.getUserStatus() == TourUserStatus.STATUS_LOCKED) {
+            errorMessage = "登录失败,用户账号被锁定!";
+            return ResponseResult.error(ErrorCodeEnum.INVALID_USER_STATUS, errorMessage);
+        }
+        //本项目是否开启排他登录
+        if (BooleanUtil.isTrue(appConfig.getExcludeLogin())) {
+            String deviceType = MyCommonUtil.getDeviceTypeWithString();
+            LoginUserInfo userInfo = BeanUtil.copyProperties(tourUser, LoginUserInfo.class);
+            String loginId = SaTokenUtil.makeLoginId(userInfo);
+            StpUtil.kickout(loginId, deviceType);
+        }
+        return ResponseResult.success(tourUser);
+    }
+}

+ 108 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/website/controller/TourismOrderController.java

@@ -0,0 +1,108 @@
+package com.tourism.webadmin.app.website.controller;
+
+import com.tourism.common.core.object.MyOrderParam;
+import com.tourism.common.core.object.MyPageData;
+import com.tourism.common.core.object.ResponseResult;
+import com.tourism.common.core.object.TokenData;
+import com.tourism.common.core.util.MyModelUtil;
+import com.tourism.common.core.util.MyPageUtil;
+import com.tourism.webadmin.back.dto.TourOrderDto;
+import com.tourism.webadmin.back.model.TourOrder;
+import com.tourism.webadmin.back.model.TourOrderPassenage;
+import com.tourism.webadmin.back.model.TourismProject;
+import com.tourism.webadmin.back.service.TourOrderPassenageService;
+import com.tourism.webadmin.back.service.TourOrderService;
+import com.tourism.webadmin.back.service.TourismProjectService;
+import com.tourism.webadmin.back.vo.TourOrderVo;
+import com.tourism.webadmin.back.vo.TourOrderPassenageVo;
+import com.tourism.webadmin.back.vo.TourismProjectVo;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * 个人订单控制器类。
+ *
+ * @author 吃饭睡觉
+ * @date 2024-09-06
+ */
+@Tag(name = "我的订单管理接口")
+@Slf4j
+@RestController
+@RequestMapping("/website/tourism/myOrder")
+public class TourismOrderController {
+
+    @Autowired
+    private TourOrderService tourOrderService;
+    @Autowired
+    private TourOrderPassenageService tourOrderPassenageService;
+    @Autowired
+    private TourismProjectService tourismProjectService;
+
+    /**
+     * 列出符合过滤条件个人订单列表。
+     *
+     * @param tourOrderDto 旅游订单Dto对象
+     * @return 应答结果对象,包含查询结果集。
+     */
+    @GetMapping("/list")
+    public ResponseResult<MyPageData<TourOrderVo>> list(TourOrderDto tourOrderDto){
+        TourOrder tourOder = new TourOrder();
+        if(tourOrderDto.getOrderStatus() != null){
+            tourOder.setOrderStatus(tourOrderDto.getOrderStatus());
+        }
+        TokenData tokenData = TokenData.takeFromRequest();
+        tourOder.setCustomerMobile(tokenData.getLoginName());
+        MyOrderParam myOrderParam = new MyOrderParam();
+        myOrderParam.add(new MyOrderParam.OrderInfo("createTime",false,null));
+        String orderBy = MyOrderParam.buildOrderBy(myOrderParam, TourOrder.class);
+        List<TourOrder> tourOderList = tourOrderService.getTourOrderList(tourOder, orderBy);
+        MyPageData<TourOrderVo> tourOderVoMyPageData = MyPageUtil.makeResponseData(tourOderList, TourOrderVo.class);
+
+        List<TourOrderVo> dataList = tourOderVoMyPageData.getDataList();
+        dataList.stream().forEach(item->{
+            if(item.getProjectId() != null){
+                TourismProject tourismProject = tourismProjectService.getById(item.getProjectId());
+                TourismProjectVo tourismProjectVo = MyModelUtil.copyTo(tourismProject, TourismProjectVo.class);
+                item.setTourismProjectVo(tourismProjectVo);
+            }
+        });
+        return ResponseResult.success(tourOderVoMyPageData);
+    }
+
+    /**
+     * 列出符合过滤条件个人订单详情。
+     *
+     * @param orderId 订单id
+     * @return 应答结果对象,包含查询结果集。
+     */
+    @GetMapping("/detail")
+    public ResponseResult<TourOrderVo> detail(@RequestParam Long orderId){
+        TourOrder tourOder = new TourOrder();
+        tourOder.setId(orderId);
+        TokenData tokenData = TokenData.takeFromRequest();
+        tourOder.setCustomerMobile(tokenData.getLoginName());
+        TourOrder tourOder1 = tourOrderService.getOne(tourOder);
+        TourOrderPassenage tourOrderPassenage = new TourOrderPassenage();
+        tourOrderPassenage.setOrderId(orderId);
+        List<TourOrderPassenage> tourOrderPassenageList = tourOrderPassenageService.getTourOrderPassenageListWithRelation(tourOrderPassenage, "");
+        TourOrderVo tourOrderVo = MyModelUtil.copyTo(tourOder1, TourOrderVo.class);
+
+        TourismProject tourismProject = new TourismProject();
+        tourismProject.setId(tourOrderVo.getProjectId());
+        TourismProject tourismProject1 = tourismProjectService.getOne(tourismProject);
+        TourismProjectVo tourismProjectVo = MyModelUtil.copyTo(tourismProject1, TourismProjectVo.class);
+        tourOrderVo.setTourismProjectVo(tourismProjectVo);
+        List<TourOrderPassenageVo> tourOrderPassenageVoList = MyModelUtil.copyCollectionTo(tourOrderPassenageList, TourOrderPassenageVo.class);
+        tourOrderVo.setDetailList(tourOrderPassenageVoList);
+        return ResponseResult.success(tourOrderVo);
+    }
+
+
+}

+ 57 - 30
application-webadmin/src/main/java/com/tourism/webadmin/app/website/controller/TourismProjectToWebController.java

@@ -1,64 +1,59 @@
 package com.tourism.webadmin.app.website.controller;
 
-import cn.dev33.satoken.annotation.SaCheckPermission;
 import cn.dev33.satoken.annotation.SaIgnore;
-import com.github.pagehelper.page.PageMethod;
 import com.tourism.common.core.annotation.MyRequestBody;
 import com.tourism.common.core.constant.ErrorCodeEnum;
 import com.tourism.common.core.object.*;
-import com.tourism.common.core.upload.UpDownloaderFactory;
-import com.tourism.common.core.util.MyModelUtil;
-import com.tourism.common.core.util.MyPageUtil;
-import com.tourism.common.redis.cache.SessionCacheHelper;
-import com.tourism.webadmin.back.dto.TourismProjectDto;
-import com.tourism.webadmin.back.model.TourismProject;
+import com.tourism.common.core.util.MyCommonUtil;
+import com.tourism.common.core.validator.UpdateGroup;
+import com.tourism.webadmin.app.website.dto.TourismBookProjectDto;
+import com.tourism.webadmin.app.website.dto.TourismProjectToWebDto;
+import com.tourism.webadmin.app.website.service.TourismProjectToWebService;
+import com.tourism.webadmin.app.website.vo.WebSiteProjectDatePriceVo;
+import com.tourism.webadmin.back.model.*;
 import com.tourism.webadmin.back.service.TourismProjectService;
 import com.tourism.webadmin.back.vo.TourismProjectVo;
-import com.tourism.webadmin.config.ApplicationConfig;
 import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.groups.Default;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
-import java.util.List;
-
 /**
  * 旅游项目管理操作控制器类。
  *
  * @author 吃饭睡觉
  * @date 2024-09-06
  */
-@Tag(name = "网页旅游项目管理接口")
+@Tag(name = "门户旅游项目管理接口")
 @Slf4j
 @RestController
-@SaIgnore
 @RequestMapping("/website/tourism/project")
+@Validated
 public class TourismProjectToWebController {
 
     @Autowired
     private TourismProjectService tourismProjectService;
 
+    @Autowired
+    private TourismProjectToWebService tourismProjectToWebService;
+
     /**
      * 列出符合过滤条件的旅游项目管理列表。
      *
      * @param tourismProjectDtoFilter 过滤对象。
-     * @param orderParam 排序参数。
-     * @param pageParam 分页参数。
      * @return 应答结果对象,包含查询结果集。
      */
-    @PostMapping("/list")
+    @SaIgnore
+    @GetMapping("/list")
     public ResponseResult<MyPageData<TourismProjectVo>> list(
-            @MyRequestBody TourismProjectDto tourismProjectDtoFilter,
-            @MyRequestBody MyOrderParam orderParam,
-            @MyRequestBody MyPageParam pageParam) {
-        if (pageParam != null) {
-            PageMethod.startPage(pageParam.getPageNum(), pageParam.getPageSize(), pageParam.getCount());
-        }
-        TourismProject tourismProjectFilter = MyModelUtil.copyTo(tourismProjectDtoFilter, TourismProject.class);
-        String orderBy = MyOrderParam.buildOrderBy(orderParam, TourismProject.class);
-        List<TourismProject> tourismProjectList =
-                tourismProjectService.getTourismProjectListWithRelation(tourismProjectFilter, orderBy);
-        return ResponseResult.success(MyPageUtil.makeResponseData(tourismProjectList, TourismProjectVo.class));
+            @Valid @ModelAttribute TourismProjectToWebDto tourismProjectDtoFilter) {
+        MyPageData<TourismProjectVo> tourismProjectVoMyPageData = tourismProjectToWebService.list(tourismProjectDtoFilter);
+        return ResponseResult.success(tourismProjectVoMyPageData);
     }
 
     /**
@@ -67,14 +62,46 @@ public class TourismProjectToWebController {
      * @param id 指定对象主键Id。
      * @return 应答结果对象,包含对象详情。
      */
-//    @SaCheckPermission("tourismProject.view")
+    @SaIgnore
     @GetMapping("/detail")
-    public ResponseResult<TourismProjectVo> detail(@RequestParam Long id) {
+    public ResponseResult<TourismProjectVo> detail(@NotNull(message = "所属目录(id)不能为空") String id) {
         TourismProject tourismProject = tourismProjectService.getByIdWithRelation(id, MyRelationParam.full());
         if (tourismProject == null) {
             return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
         }
-        TourismProjectVo tourismProjectVo = MyModelUtil.copyTo(tourismProject, TourismProjectVo.class);
+        TourismProjectVo tourismProjectVo = tourismProjectToWebService.detail(tourismProject);
         return ResponseResult.success(tourismProjectVo);
     }
+    /**
+     * 查看指定旅游项目管理对象详情。
+     *
+     * @param projectId 项目id。
+     * @return 应答结果对象,包含对象详情。
+     */
+    @SaIgnore
+    @GetMapping("/viewDatePrice")
+    public ResponseResult<WebSiteProjectDatePriceVo> view(@RequestParam String projectId) {
+        WebSiteProjectDatePriceVo webSiteProjectDatePriceVo = tourismProjectToWebService.view(projectId);
+        return ResponseResult.success(webSiteProjectDatePriceVo);
+    }
+
+    /**
+    * 项目详情页面的预定接口
+    *
+    * @param tourBookInfoDto 预定项目的Dto
+    * @return 应答结果对象,包含对象详情。
+    */
+    @PostMapping("/bookProject")
+    public ResponseResult<Integer> bookProject(HttpServletRequest request,@MyRequestBody TourismBookProjectDto tourBookInfoDto) {
+        String errorMessage = MyCommonUtil.getModelValidationError(tourBookInfoDto, Default.class, UpdateGroup.class);
+        if (errorMessage != null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
+        }
+        Boolean state = tourismProjectToWebService.bookProject(request, tourBookInfoDto);
+        if(!state){
+            return ResponseResult.success(2);
+        }
+        return ResponseResult.success(1);
+    }
+
 }

+ 242 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/website/controller/WebsiteTourismProjectTravelNotesController.java

@@ -0,0 +1,242 @@
+package com.tourism.webadmin.app.website.controller;
+
+import cn.dev33.satoken.annotation.SaIgnore;
+import com.tourism.common.additional.utils.UrlConvertUtils;
+import com.tourism.common.core.object.MyOrderParam;
+import com.tourism.common.core.object.MyPageData;
+import com.tourism.common.core.object.ResponseResult;
+import com.tourism.common.core.util.MyModelUtil;
+import com.tourism.common.log.annotation.OperationLog;
+import com.tourism.common.log.model.constant.SysOperationLogType;
+import com.tourism.webadmin.app.website.dto.TourismProjectTravelNotesToWebDto;
+import com.tourism.webadmin.app.website.service.BasicToWebService;
+import com.tourism.webadmin.app.website.vo.TourTravelNotesDirectoryCountryVo;
+import com.tourism.webadmin.app.website.vo.TourTravelNotesDirectoryVo;
+import com.tourism.webadmin.back.model.*;
+import com.tourism.webadmin.back.service.*;
+import com.tourism.webadmin.back.vo.TourTourismProjectTravelNotesVo;
+import com.tourism.webadmin.back.vo.TourismProjectVo;
+import com.tourism.common.additional.config.ApplicationConfig;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Tag(name = "门户网站旅游游记接口")
+@Slf4j
+@RestController
+@Validated
+@RequestMapping("/website/tourism/projectTravelNotes")
+public class WebsiteTourismProjectTravelNotesController {
+
+    @Autowired
+    private BasicToWebService basicToWebServicel;
+    @Autowired
+    private DirectoryInfoService directoryInfoService;
+    @Autowired
+    private TourTravelNotesProjectRelationService tourTravelNotesProjectRelationService;
+    @Autowired
+    private TourismProjectService tourismProjectService;
+    @Autowired
+    private TourTourismProjectTravelNotesService tourTourismProjectTravelNotesService;
+    @Autowired
+    private ApplicationConfig applicationConfig;
+
+
+    /**
+     * 列出旅游游记列表。(首页)
+     * @return 应答结果对象,包含查询结果集。
+     */
+    @OperationLog(type = SysOperationLogType.LIST)
+    @GetMapping("/homeList")
+    @SaIgnore
+    public ResponseResult<MyPageData<TourTourismProjectTravelNotesVo>> tourTourismTravelNotesList(TourismProjectTravelNotesToWebDto tourismProjectTravelNotesToWebDto) {
+        MyPageData<TourTourismProjectTravelNotesVo> tourTourismProjectTravelNotesList = basicToWebServicel.getTourTourismProjectTravelNotesList(tourismProjectTravelNotesToWebDto);
+
+        return ResponseResult.success(tourTourismProjectTravelNotesList);
+    }
+
+    /**
+     * 列出符合过滤条件的旅游游记列表。(游记页)
+     * @return 应答结果对象,包含查询结果集。
+     */
+    @OperationLog(type = SysOperationLogType.LIST)
+    @GetMapping("/travelNotesPageList")
+    public ResponseResult<MyPageData<TourTourismProjectTravelNotesVo>> travelNotesPageList(TourismProjectTravelNotesToWebDto tourismProjectTravelNotesToWebDto) {
+        MyPageData<TourTourismProjectTravelNotesVo> tourTourismProjectTravelNotesList = basicToWebServicel.getTravelNotesPageList(tourismProjectTravelNotesToWebDto);
+
+        return ResponseResult.success(tourTourismProjectTravelNotesList);
+    }
+
+    /**
+     * 通过旅游游记的id查询游记的详情。
+     * @return 应答结果对象,包含查询结果集。
+     */
+    @OperationLog(type = SysOperationLogType.LIST)
+    @GetMapping("/travelNotesDetail")
+    @SaIgnore
+    public ResponseResult<TourTourismProjectTravelNotesVo> travelNotesDetail(@RequestParam String id) {
+
+        TourTourismProjectTravelNotesVo tourTourismProjectTravelNotesVo = basicToWebServicel.travelNotesDetail(id);
+//        tourTourismProjectTravelNotesVo.setContactCode("[{\"name\":\"微信图片_20241021154757.png\",\"downloadUri\":\"/admin/app/tourTourismProjectTravelNotes/download\",\"filename\":\"6b92b75edcc04da1bd6e4af056911730.png\",\"uploadPath\":\"image/TourTourismProjectTravelNotes/contactCode\"}]");
+//        List<String> urlConvertList = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), tourTourismProjectTravelNotesVo.getContactCode());
+//        if(CollectionUtils.isNotEmpty(urlConvertList)) {
+//            tourTourismProjectTravelNotesVo.setContactCodeConvert(urlConvertList.get(0));
+//        }
+//
+        if(tourTourismProjectTravelNotesVo != null) {
+            tourTourismProjectTravelNotesVo.setContactCodeConvert("https://v.xiaoyaotravel.com/image/ContactQRCode/tourism.png");
+        }
+        return ResponseResult.success(tourTourismProjectTravelNotesVo);
+    }
+
+    /**
+     * 用户游记点赞
+     * param travelNotesId 游记id
+     * param type 0取消点赞 1点赞
+     * @return 应答结果对象,包含查询结果集。
+     */
+    @OperationLog(type = SysOperationLogType.UPDATE)
+    @GetMapping("/userLikeTravelNotesUpdate")
+    public ResponseResult<Void> userLikeTravelNotesUpdate(@RequestParam Long travelNotesId,Integer type) {
+
+        basicToWebServicel.userLikeTravelNotesUpdate(travelNotesId,type);
+        return ResponseResult.success();
+    }
+
+    /**
+     * 用户是否对该游记进行点赞
+     * param travelNotesId 游记id
+     * @return 应答结果对象,包含查询结果集。
+     */
+    @OperationLog(type = SysOperationLogType.LIST)
+    @GetMapping("/isLikeTravelNotes")
+    public ResponseResult<Boolean> isLikeTravelNotes(@RequestParam Long travelNotesId) {
+        return ResponseResult.success(basicToWebServicel.isLikeTravelNotes(travelNotesId));
+    }
+
+
+    /**
+     * 用户游记增加浏览量
+     * param travelNotesId 游记id
+     * @return 应答结果对象,包含查询结果集。
+     */
+    @OperationLog(type = SysOperationLogType.ADD)
+    @GetMapping("/travelNotesViewCountAdd")
+    public ResponseResult<Void> travelNotesViewCountAdd(@RequestParam Long travelNotesId) {
+        basicToWebServicel.travelNotesViewCountAdd(travelNotesId);
+        return ResponseResult.success();
+    }
+
+
+    /**
+     * 游记菜单列表(二维数组)
+     * param
+     * @return 应答结果对象,包含查询结果集。
+     */
+    @OperationLog(type = SysOperationLogType.LIST)
+    @GetMapping("/travelNotesDirectoryList")
+    @SaIgnore
+    public ResponseResult<List<TourTravelNotesDirectoryVo>> travelNotesDirectoryList() {
+
+        //设置排序
+        MyOrderParam myOrderParam = new MyOrderParam();
+        MyOrderParam.OrderInfo orderInfo = new MyOrderParam.OrderInfo();
+        orderInfo.setAsc(true);
+        orderInfo.setFieldName("showOrder");
+        myOrderParam.add(orderInfo);
+        String orderBy = MyOrderParam.buildOrderBy(myOrderParam, DirectoryInfo.class);
+
+        //查询一级菜单
+        DirectoryInfo directoryInfo = new DirectoryInfo();
+        directoryInfo.setParentId(0L);
+        directoryInfo.setEnable(1);
+        directoryInfo.setDirectoryType(1);
+        List<DirectoryInfo> directoryInfoList = directoryInfoService.getDirectoryInfoList(directoryInfo, orderBy);
+        List<TourTravelNotesDirectoryVo> tourTravelNotesDirectoryVoList= new ArrayList<>();
+        directoryInfoList.stream().forEach(item->{
+            TourTravelNotesDirectoryVo tourTravelNotesDirectoryVo = new TourTravelNotesDirectoryVo();
+            tourTravelNotesDirectoryVo.setAreaId(item.getId().toString());
+            tourTravelNotesDirectoryVo.setAreaName(item.getMenuName());
+
+            DirectoryInfo directoryInfo1 = new DirectoryInfo();
+            directoryInfo1.setEnable(1);
+            directoryInfo1.setParentId(item.getId());
+            directoryInfo1.setDirectoryType(1);
+            List<DirectoryInfo> countryList = directoryInfoService.getDirectoryInfoList(directoryInfo1,orderBy);
+            List<TourTravelNotesDirectoryCountryVo> tourTravelNotesDirectoryCountryVoList = new ArrayList<>();
+            for(DirectoryInfo country:countryList){
+                TourTravelNotesDirectoryCountryVo tourTravelNotesDirectoryCountryVo = new TourTravelNotesDirectoryCountryVo();
+                tourTravelNotesDirectoryCountryVo.setCountryId(country.getId().toString());
+                tourTravelNotesDirectoryCountryVo.setCountryName(country.getMenuName().toString());
+                tourTravelNotesDirectoryCountryVoList.add(tourTravelNotesDirectoryCountryVo);
+            }
+            tourTravelNotesDirectoryVo.setChildren(tourTravelNotesDirectoryCountryVoList);
+            tourTravelNotesDirectoryVoList.add(tourTravelNotesDirectoryVo);
+        });
+        return ResponseResult.success(tourTravelNotesDirectoryVoList);
+    }
+
+    /**
+     * 相关项目
+     * param travelNotesId 游记id
+     * @return 应答结果对象,包含查询结果集。
+     */
+    @OperationLog(type = SysOperationLogType.LIST)
+    @GetMapping("/relateProjectList")
+    @SaIgnore
+    public ResponseResult<List<TourismProjectVo>> relateProjectList(@RequestParam(required = true) String travelNotesId) {
+
+        TourTravelNotesProjectRelation tourTravelNotesProjectRelation = new TourTravelNotesProjectRelation();
+        tourTravelNotesProjectRelation.setTravelNotesId(travelNotesId);
+        List<TourTravelNotesProjectRelation> tourTravelNotesProjectRelationList =
+                tourTravelNotesProjectRelationService.getTourTravelNotesProjectRelationList(tourTravelNotesProjectRelation, "");
+        ArrayList<String> relateProjectList =
+                tourTravelNotesProjectRelationList.stream().map(TourTravelNotesProjectRelation::getProjectId).collect(Collectors.toCollection(ArrayList::new));
+
+        if(relateProjectList.size() == 0) {
+            return ResponseResult.success(new ArrayList<>());
+        }else {
+            List<TourismProject> tourismProjectList = tourismProjectService.getInList(new HashSet<>(relateProjectList));
+            List<TourismProjectVo> tourismProjectVoList = MyModelUtil.copyCollectionTo(tourismProjectList, TourismProjectVo.class);
+            //先把imgUrl由jaon转换为List<FileUrlObject>
+            tourismProjectVoList.stream().forEach(item ->
+            {
+                List<String> urlList = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), item.getHomeHotPicture());
+                item.setHomeHotPicturesAfterConvert(urlList);
+                List<String> arrayList = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), item.getTourismUrl());
+                item.setTourismUrlsAfterConvert(arrayList);
+                List<String> arrayList1 = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), item.getTravelNotesBanner());
+                item.setTravelNotesBannerAfterConvert(arrayList1);
+            });
+            return ResponseResult.success(tourismProjectVoList);
+        }
+    }
+
+
+
+    /**
+     * 获取点赞数量
+     * param travelNotesId 游记id
+     * @return 应答结果对象,包含查询结果集。
+     */
+    @OperationLog(type = SysOperationLogType.LIST)
+    @GetMapping("/getLikeCount")
+    @SaIgnore
+    public ResponseResult<Integer> getLikeCount(@RequestParam(required = true) Long travelNotesId) {
+
+        TourTourismProjectTravelNotes tourTourismProjectTravelNotes = tourTourismProjectTravelNotesService.getById(travelNotesId);
+        Integer likeCount = tourTourismProjectTravelNotes.getLikeCount();
+        return ResponseResult.success(likeCount);
+    }
+}

+ 30 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/website/dto/CheckImageDto.java

@@ -0,0 +1,30 @@
+package com.tourism.webadmin.app.website.dto;
+
+import com.tourism.common.core.validator.AddGroup;
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+
+@Data
+public class CheckImageDto {
+    /**
+     * 验证码标识
+     */
+    @NotBlank(message = "验证码标识uuid不能为空!", groups = {AddGroup.class})
+    private String uuid;
+    /**
+     * 验证码
+     */
+    @NotBlank(message = "验证码不能为空!", groups = {AddGroup.class})
+    private String code;
+    /**
+     * 手机号
+     */
+    @NotBlank(message = "手机号不能为空!", groups = {AddGroup.class})
+    private String phoneNumber;
+
+    /**
+     * 地区编号
+     */
+    @NotBlank(message = "地区编号不能为空!", groups = {AddGroup.class})
+    private String countryCode;
+}

+ 13 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/website/dto/DatePriceSaveDto.java

@@ -0,0 +1,13 @@
+package com.tourism.webadmin.app.website.dto;
+
+import com.tourism.webadmin.app.website.vo.DateRangesPriceVo;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class DatePriceSaveDto {
+
+    private String projectId;
+    private List<DateRangesPriceVo> dateRangesPriceVoList;
+}

+ 26 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/website/dto/PageDto.java

@@ -0,0 +1,26 @@
+package com.tourism.webadmin.app.website.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Data
+public class PageDto {
+
+    /**
+     * 分页号码,从1开始计数。
+     */
+    @Schema(description = "分页号码。")
+    private Integer pageNum;
+
+    /**
+     * 每页大小。
+     */
+    @Schema(description = "每页大小。")
+    private Integer pageSize;
+
+    /**
+     * 是否统计totalCount。
+     */
+    @Schema(description = "是否统计totalCount。")
+    private Boolean count;
+}

+ 95 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/website/dto/TourUserRegisterDto.java

@@ -0,0 +1,95 @@
+package com.tourism.webadmin.app.website.dto;
+
+import com.tourism.common.core.validator.ConstDictRef;
+import com.tourism.common.core.validator.UpdateGroup;
+import com.tourism.webadmin.upms.model.constant.SysUserStatus;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+/**
+ * 门户网站用户管理Dto对象。
+ *
+ * @author 吃饭睡觉
+ * @date 2024-09-06
+ */
+@Schema(description = "TourUserDto对象")
+@Data
+public class TourUserRegisterDto {
+
+    /**
+     * 主键Id。
+     */
+    @Schema(description = "主键Id。", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "数据验证失败,主键Id不能为空!", groups = {UpdateGroup.class})
+    private Long userId;
+
+    /**
+     * 用户登录名称。
+     * NOTE: 可支持等于操作符的列表数据过滤。
+     */
+    @Schema(description = "用户登录名称。可支持等于操作符的列表数据过滤。", requiredMode = Schema.RequiredMode.REQUIRED)
+    private String loginName;
+
+    /**
+     * 密码。
+     */
+    @Schema(description = "密码。", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotBlank(message = "数据验证失败,密码不能为空!")
+    private String password;
+
+    /**
+     * 用户显示名称。
+     * NOTE: 可支持等于操作符的列表数据过滤。
+     */
+    @Schema(description = "用户显示名称。可支持等于操作符的列表数据过滤。", requiredMode = Schema.RequiredMode.REQUIRED)
+    private String showName;
+
+    /**
+     * 用户头像的Url。
+     */
+    @Schema(description = "用户头像的Url。")
+    private String headImageUrl;
+
+    /**
+     * 状态(0: 正常 1: 锁定)。
+     * NOTE: 可支持等于操作符的列表数据过滤。
+     */
+    @Schema(description = "状态(0: 正常 1: 锁定)。可支持等于操作符的列表数据过滤。", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ConstDictRef(constDictClass = SysUserStatus.class, message = "数据验证失败,状态(0: 正常 1: 锁定)为无效值!")
+    private Integer userStatus;
+
+    /**
+     * 用户邮箱。
+     * NOTE: 可支持等于操作符的列表数据过滤。
+     */
+    @Schema(description = "用户邮箱。可支持等于操作符的列表数据过滤。")
+    private String email;
+
+    /**
+     * 用户手机。
+     * NOTE: 可支持等于操作符的列表数据过滤。
+     */
+    @Schema(description = "用户手机。可支持等于操作符的列表数据过滤。")
+    @NotBlank(message = "数据验证失败,手机号不能为空!")
+    private String mobile;
+
+    /**
+     * 图形验证码的uuid。
+     */
+//    @NotBlank(message = "数据验证失败,uuid不能为空!")
+    private String uuid;
+
+    /**
+     * 图形验证码的结果。
+     */
+//    @NotBlank(message = "数据验证失败,结果不能为空!")
+    private String code;
+
+    /**
+     * 短信验证码。
+     */
+//    @NotBlank(message = "数据验证失败,短信验证码不能为空!")
+    private String smsCode;
+}

+ 29 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/website/dto/TourismBookProjectDto.java

@@ -0,0 +1,29 @@
+package com.tourism.webadmin.app.website.dto;
+
+import com.tourism.common.core.validator.UpdateGroup;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.util.Date;
+
+@Data
+public class TourismBookProjectDto {
+
+    @NotNull(message = "type不能为空!", groups = {UpdateGroup.class})
+    private Integer type;
+
+    @NotNull(message = "项目不能为空!", groups = {UpdateGroup.class})
+    private String projectId;
+
+    @NotNull(message = "成人数量不能为空!", groups = {UpdateGroup.class})
+    private Integer adultNumber;
+
+    private Integer childrenNumber;
+
+    @NotNull(message = "预定日期不能为空!", groups = {UpdateGroup.class})
+    private Date startDate;
+
+    public String getString() {
+        return type + "_" + projectId + "_" + adultNumber + "_" + childrenNumber + "_" + startDate;
+    }
+}

+ 85 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/website/dto/TourismProjectToWebDto.java

@@ -0,0 +1,85 @@
+package com.tourism.webadmin.app.website.dto;
+
+import com.tourism.common.core.object.MyOrderParam;
+import com.tourism.common.core.validator.ConstDictRef;
+import com.tourism.webadmin.back.model.constant.Hotspot;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 门户预定管理Dto对象。
+ *
+ * @author 吃饭睡觉
+ * @date 2024-09-06
+ */
+@Schema(description = "TourismProjectToWebDto对象")
+@Data
+public class TourismProjectToWebDto {
+
+
+    /**
+     * 所属分类。
+     */
+    @Schema(description = "所属分类。")
+    private Long belongTab;
+
+    /**
+     * 是否为热点项目。
+     */
+    @Schema(description = "是否热点,0非热点,1热点。")
+    private Integer isHotspot;
+
+    /**
+     * 分页号码,从1开始计数。
+     */
+    @Schema(description = "分页号码。")
+    private Integer pageNum;
+
+    /**
+     * 每页大小。
+     */
+    @Schema(description = "每页大小。")
+    private Integer pageSize;
+
+    /**
+     * 是否统计totalCount。
+     */
+    @Schema(description = "是否统计totalCount。")
+    private Boolean count;
+
+    /**
+     * 排序。
+     */
+    @Schema(description = "排序。")
+    private MyOrderParam orderParamList;
+
+    /**
+     * 菜单id。
+     */
+    private List<Long> directoryInfoIds;
+
+    /**
+     * banner_name LIKE搜索字符串。
+     * NOTE: 可支持LIKE操作符的列表数据过滤。
+     */
+    @Schema(description = "LIKE模糊搜索字符串")
+    private String searchString;
+
+    /**
+     * 是否急聘(0:否 1:是)。
+     */
+    @Schema(description = "是否急聘(0:否 1:是)")
+    private Integer isUrgent;
+
+    /**
+     * 是否最新(0:否 1:是)。
+     */
+    @Schema(description = "是否最新(0:否 1:是)")
+    private Integer isNew;
+
+
+
+}

+ 80 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/website/dto/TourismProjectTravelNotesToWebDto.java

@@ -0,0 +1,80 @@
+package com.tourism.webadmin.app.website.dto;
+
+import com.tourism.common.core.object.MyOrderParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 门户预定管理Dto对象。
+ *
+ * @author 吃饭睡觉
+ * @date 2024-09-06
+ */
+@Schema(description = "TourismProjectTravelNotesToWebDto对象")
+@Data
+public class TourismProjectTravelNotesToWebDto {
+
+    /**
+     * 所属分类。
+     */
+    @Schema(description = "所属分类。")
+    private Long belongTab;
+
+    /**
+     * 地区id。
+     */
+    @Schema(description = "地区id。")
+    private String areaId;
+
+    /**
+     * 国家id。
+     */
+    @Schema(description = "国家id。")
+    private String countryId;
+
+    /**
+     * 是否为热点项目。
+     */
+    @Schema(description = "是否热点,0非热点,1热点。")
+    private Integer isHotspot;
+
+    /**
+     * 分页号码,从1开始计数。
+     */
+    @Schema(description = "分页号码。")
+    private Integer pageNum;
+
+    /**
+     * 每页大小。
+     */
+    @Schema(description = "每页大小。")
+    private Integer pageSize;
+
+    /**
+     * 是否统计totalCount。
+     */
+    @Schema(description = "是否统计totalCount。")
+    private Boolean count;
+
+    /**
+     * 排序。
+     */
+    @Schema(description = "排序。")
+    private MyOrderParam orderParamList;
+
+    /**
+     * 菜单id。
+     */
+    private List<Long> directoryInfoIds;
+
+    /**
+     * banner_name LIKE搜索字符串。
+     * NOTE: 可支持LIKE操作符的列表数据过滤。
+     */
+    @Schema(description = "LIKE模糊搜索字符串")
+    private String searchString;
+
+}

+ 54 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/website/dto/TourismRestaurantFoodInfoToWebDto.java

@@ -0,0 +1,54 @@
+package com.tourism.webadmin.app.website.dto;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.tourism.common.core.object.MyOrderParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+/**
+ * 美食Dto对象。
+ *
+ * @author 吃饭睡觉
+ * @date 2024-09-06
+ */
+@Schema(description = "TourismRestaurantFoodInfoToWebDto对象")
+@Data
+public class TourismRestaurantFoodInfoToWebDto {
+
+    /**
+     * 是否启用。
+     */
+    @Schema(description = "是否启用。")
+    private Integer enable;
+
+    /**
+     * 美食名称。
+     */
+    @Schema(description = "美食名称。")
+    private String name;
+
+    /**
+     * 分页号码,从1开始计数。
+     */
+    @Schema(description = "分页号码。")
+    private Integer pageNum;
+
+    /**
+     * 每页大小。
+     */
+    @Schema(description = "每页大小。")
+    private Integer pageSize;
+
+    /**
+     * 排序。
+     */
+    @Schema(description = "排序。")
+    private MyOrderParam orderParamList;
+
+    /**
+     * 饭馆美食种类id。
+     */
+    @Schema(description = "饭馆美食种类id。")
+    private Long foodTypeId;
+
+}

+ 60 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/website/dto/TourismRestaurantFoodTypeToWebDto.java

@@ -0,0 +1,60 @@
+package com.tourism.webadmin.app.website.dto;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.tourism.common.core.object.MyOrderParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+/**
+ * 美食类型Dto对象。
+ *
+ * @author 吃饭睡觉
+ * @date 2024-09-06
+ */
+@Schema(description = "TourismRestaurantFoodTypeToWebDto对象")
+@Data
+public class TourismRestaurantFoodTypeToWebDto {
+
+    /**
+     * 是否启用。
+     */
+    @Schema(description = "是否启用。")
+    private Integer enable;
+
+    /**
+     * 美食类型名称。
+     */
+    @Schema(description = "美食类型名称。")
+    private String name;
+
+    /**
+     * 分页号码,从1开始计数。
+     */
+    @Schema(description = "分页号码。")
+    private Integer pageNum;
+
+    /**
+     * 每页大小。
+     */
+    @Schema(description = "每页大小。")
+    private Integer pageSize;
+
+    /**
+     * 排序。
+     */
+    @Schema(description = "排序。")
+    private MyOrderParam orderParamList;
+
+    /**
+     * 餐馆id。
+     */
+    @Schema(description = "餐馆id。")
+    private Long restaurantId;
+
+    /**
+     * 餐馆id。
+     */
+    @Schema(description = "餐馆id。")
+    private Long id;
+
+}

+ 62 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/website/dto/TourismRestaurantInfoToWebDto.java

@@ -0,0 +1,62 @@
+package com.tourism.webadmin.app.website.dto;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.tourism.common.core.object.MyOrderParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+/**
+ * 餐馆类型Dto对象。
+ *
+ * @author 吃饭睡觉
+ * @date 2024-09-06
+ */
+@Schema(description = "TourismRestaurantTypeToWebDto对象")
+@Data
+public class TourismRestaurantInfoToWebDto {
+
+    /**
+     * 店铺种类。
+     */
+    @Schema(description = "店铺种类。")
+    private String typeId;
+
+    /**
+     * 是否启用。
+     */
+    @Schema(description = "是否启用。")
+    private Integer enable;
+
+    /**
+     * 店铺名称。
+     */
+    @Schema(description = "店铺名称。")
+    private String name;
+
+    /**
+     * 分页号码,从1开始计数。
+     */
+    @Schema(description = "分页号码。")
+    private Integer pageNum;
+
+    /**
+     * 每页大小。
+     */
+    @Schema(description = "每页大小。")
+    private Integer pageSize;
+
+    /**
+     * 是否统计totalCount。
+     */
+    @Schema(description = "是否统计totalCount。")
+    private Boolean count;
+
+    /**
+     * 排序。
+     */
+    @Schema(description = "排序。")
+    private MyOrderParam orderParamList;
+    @Schema(description = " 商品名")
+    private String foodName;
+
+}

+ 55 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/website/dto/TourismRestaurantTypeToWebDto.java

@@ -0,0 +1,55 @@
+package com.tourism.webadmin.app.website.dto;
+
+import com.tourism.common.core.object.MyOrderParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 餐馆类型Dto对象。
+ *
+ * @author 吃饭睡觉
+ * @date 2024-09-06
+ */
+@Schema(description = "TourismRestaurantTypeToWebDto对象")
+@Data
+public class TourismRestaurantTypeToWebDto {
+
+    /**
+     * 是否启用。
+     */
+    @Schema(description = "是否启用。")
+    private Integer enable;
+
+    /**
+     * 餐馆类型名称。
+     */
+    @Schema(description = "餐馆类型名称。")
+    private String name;
+
+    /**
+     * 分页号码,从1开始计数。
+     */
+    @Schema(description = "分页号码。")
+    private Integer pageNum;
+
+    /**
+     * 每页大小。
+     */
+    @Schema(description = "每页大小。")
+    private Integer pageSize;
+
+    /**
+     * 是否统计totalCount。
+     */
+    @Schema(description = "是否统计totalCount。")
+    private Boolean count;
+
+    /**
+     * 排序。
+     */
+    @Schema(description = "排序。")
+    private MyOrderParam orderParamList;
+
+}

+ 36 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/website/service/BasicToWebService.java

@@ -0,0 +1,36 @@
+package com.tourism.webadmin.app.website.service;
+
+import com.tourism.common.core.object.MyPageData;
+import com.tourism.common.core.object.ResponseResult;
+import com.tourism.webadmin.app.website.dto.TourismProjectTravelNotesToWebDto;
+import com.tourism.webadmin.back.model.TourTourismProjectTravelNotes;
+import com.tourism.webadmin.back.vo.BannerInfoVo;
+import com.tourism.webadmin.back.vo.DirectoryInfoVo;
+import com.tourism.webadmin.back.vo.TourTourismProjectTravelNotesVo;
+import org.springframework.web.bind.annotation.RequestParam;
+
+/**
+ * 门户网站首页Service
+ * 
+ * @author tourism
+ */
+public interface BasicToWebService
+{
+    MyPageData<BannerInfoVo> getBannerInfoList(Long belongTab);
+
+    MyPageData<DirectoryInfoVo> getDirectoryInfoList(Long parentId, Integer isHotspot, Integer isAll);
+
+    MyPageData<TourTourismProjectTravelNotesVo> getTourTourismProjectTravelNotesList(TourismProjectTravelNotesToWebDto tourismProjectTravelNotesToWebDto);
+
+    MyPageData<TourTourismProjectTravelNotesVo> getTravelNotesPageList(TourismProjectTravelNotesToWebDto tourismProjectTravelNotesToWebDto);
+
+    TourTourismProjectTravelNotesVo travelNotesDetail(String id);
+
+    void userLikeTravelNotesUpdate(Long travelNotesId,Integer type);
+
+    Boolean isLikeTravelNotes(Long travelNotesId);
+
+    void travelNotesViewCountAdd(Long travelNotesId);
+
+    void generate();
+}

+ 6 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/website/service/CleanDataService.java

@@ -0,0 +1,6 @@
+package com.tourism.webadmin.app.website.service;
+
+public interface CleanDataService {
+
+    void cleanRichTextData() throws Exception;
+}

+ 14 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/website/service/JobRecordService.java

@@ -0,0 +1,14 @@
+package com.tourism.webadmin.app.website.service;
+
+import com.tourism.common.core.object.MyPageData;
+import com.tourism.webadmin.app.website.dto.PageDto;
+import com.tourism.webadmin.back.vo.TourBookInfoVo;
+
+/**
+ * 劳务记录Service
+ *
+ * @author tourism
+ */
+public interface JobRecordService {
+    MyPageData<TourBookInfoVo> jobRecordList(String mobile, PageDto pageDto);
+}

+ 45 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/website/service/LoginToWebsiteService.java

@@ -0,0 +1,45 @@
+package com.tourism.webadmin.app.website.service;
+
+import com.tourism.webadmin.app.website.dto.TourUserRegisterDto;
+import com.tourism.webadmin.back.dto.TourBookInfoDto;
+import jakarta.servlet.http.HttpServletRequest;
+
+/**
+ * 注册校验方法
+ * 
+ * @author tourism
+ */
+public interface LoginToWebsiteService
+{
+    /**
+     * 校验图形验证码
+     *
+     * @param code 验证码
+     * @param uuid 唯一标识
+     * @return 结果
+     */
+    public boolean validateCaptcha(String uuid, String code);
+    /**
+     * 校验短信验证码
+     *
+     * @param mobile 手机号
+     * @param smsCode 短信验证码
+     * @return 结果
+     */
+    public boolean validateSmsCode(String mobile,String smsCode);
+    /**
+     * 保存用户的预约信息
+     *
+     * @param tourBookInfoDto 门户预定管理Dto对象
+     * @return 结果
+     */
+    public boolean saveBookInfo(HttpServletRequest request, TourBookInfoDto tourBookInfoDto);
+    /**
+     * 保存用户的注册信息
+     *
+     * @param tourUserRegisterDto 预约手机号
+     * @return 结果
+     */
+    public boolean saveRegisterUserInfo(TourUserRegisterDto tourUserRegisterDto);
+
+}

+ 23 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/website/service/TourismProjectToWebService.java

@@ -0,0 +1,23 @@
+package com.tourism.webadmin.app.website.service;
+
+import com.tourism.common.core.object.MyPageData;
+import com.tourism.webadmin.app.website.dto.TourismBookProjectDto;
+import com.tourism.webadmin.app.website.dto.TourismProjectToWebDto;
+import com.tourism.webadmin.app.website.vo.WebSiteProjectDatePriceVo;
+import com.tourism.webadmin.back.model.TourismProject;
+import com.tourism.webadmin.back.vo.TourismProjectVo;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.validation.constraints.NotNull;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestParam;
+
+public interface TourismProjectToWebService {
+
+    MyPageData<TourismProjectVo> list(TourismProjectToWebDto tourismProjectDtoFilter);
+
+    TourismProjectVo detail(TourismProject tourismProject);
+
+    WebSiteProjectDatePriceVo view(@RequestParam String projectId);
+
+    Boolean bookProject(HttpServletRequest request, @RequestBody TourismBookProjectDto tourismBookProjectDto);
+}

+ 321 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/website/service/impl/BasicToWebServiceImpl.java

@@ -0,0 +1,321 @@
+package com.tourism.webadmin.app.website.service.impl;
+
+import com.github.pagehelper.page.PageMethod;
+import com.tourism.common.additional.utils.StringUtils;
+import com.tourism.common.additional.utils.UrlConvertUtils;
+import com.tourism.common.core.object.MyOrderParam;
+import com.tourism.common.core.object.MyPageData;
+import com.tourism.common.core.object.MyRelationParam;
+import com.tourism.common.core.object.TokenData;
+import com.tourism.common.core.util.MyModelUtil;
+import com.tourism.common.core.util.MyPageUtil;
+import com.tourism.webadmin.app.website.dto.TourismProjectTravelNotesToWebDto;
+import com.tourism.webadmin.app.website.service.BasicToWebService;
+import com.tourism.webadmin.back.model.BannerInfo;
+import com.tourism.webadmin.back.model.DirectoryInfo;
+import com.tourism.webadmin.back.model.TourTourismProjectTravelNotes;
+import com.tourism.webadmin.back.model.TourUserLikeTravelNotes;
+import com.tourism.webadmin.back.service.BannerInfoService;
+import com.tourism.webadmin.back.service.DirectoryInfoService;
+import com.tourism.webadmin.back.service.TourTourismProjectTravelNotesService;
+import com.tourism.webadmin.back.service.TourUserLikeTravelNotesService;
+import com.tourism.webadmin.back.vo.BannerInfoVo;
+import com.tourism.webadmin.back.vo.DirectoryInfoVo;
+import com.tourism.webadmin.back.vo.TourTourismProjectTravelNotesVo;
+import com.tourism.common.additional.config.ApplicationConfig;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+import java.util.Random;
+import java.util.stream.Collectors;
+
+/**
+ * 门户网站首页Service
+ * 
+ * @author tourism
+ */
+@Slf4j
+@Service("BasicToWebService")
+public class BasicToWebServiceImpl implements BasicToWebService
+{
+
+    @Autowired
+    private BannerInfoService bannerInfoService;
+    @Autowired
+    private DirectoryInfoService directoryInfoService;
+    @Autowired
+    private ApplicationConfig applicationConfig;
+    @Autowired
+    private TourTourismProjectTravelNotesService tourTourismProjectTravelNotesService;
+    @Autowired
+    private TourUserLikeTravelNotesService tourUserLikeTravelNotesService;
+
+    @Override
+    public MyPageData<BannerInfoVo> getBannerInfoList(Long belongTab) {
+        BannerInfo bannerInfoFilter = new BannerInfo();
+        bannerInfoFilter.setBelongTab(belongTab);
+        //首页展示的话,只能展示启用的banner图
+        bannerInfoFilter.setEnable(1);
+        MyOrderParam.OrderInfo orderInfo = new MyOrderParam.OrderInfo();
+        orderInfo.setFieldName("mainBanner");
+        orderInfo.setAsc(false);
+        MyOrderParam myOrderParam  = new MyOrderParam();
+        myOrderParam.add(orderInfo);
+        myOrderParam.add(new MyOrderParam.OrderInfo("updateTime",false,null));
+        String orderBy = MyOrderParam.buildOrderBy(myOrderParam, BannerInfo.class);
+        List<BannerInfo> bannerInfoList = bannerInfoService.getBannerInfoListWithRelation(bannerInfoFilter, orderBy);
+        MyPageData<BannerInfoVo> bannerInfoVoMyPageData = MyPageUtil.makeResponseData(bannerInfoList, BannerInfoVo.class);
+
+        List<BannerInfoVo> dataList = bannerInfoVoMyPageData.getDataList();
+            //先把imgUrl由jaon转换为List<FileUrlObject>
+        dataList.stream().forEach(item ->{
+            if (StringUtils.isNotEmpty(item.getImgUrl())) {
+                item.setImgUrlsAfterConvert(UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), item.getImgUrl()));
+            }
+        });
+
+        return bannerInfoVoMyPageData;
+    }
+
+    @Override
+    public MyPageData<DirectoryInfoVo> getDirectoryInfoList(Long parentId, Integer isHotspot, Integer isAll) {
+
+        DirectoryInfo directoryInfoFilter = new DirectoryInfo();
+        //当directoryId为1000时,则表明查询三级菜单
+        if(parentId != null){
+            directoryInfoFilter.setParentId(parentId);
+        }
+        if(isAll!= null && isAll == 1){
+            directoryInfoFilter.setDirectoryId(1000L);
+            //菜单类型:1:旅游,2:劳务
+            directoryInfoFilter.setDirectoryType(1);
+        }
+        if(isHotspot != null){
+            directoryInfoFilter.setIsHotspot(isHotspot);
+        }
+        MyOrderParam myOrderParam = new MyOrderParam();
+        MyOrderParam.OrderInfo orderInfo = new MyOrderParam.OrderInfo();
+        //先根据是否热点排序,再根据展示顺序排序
+        orderInfo.setFieldName("isHotspot");
+        orderInfo.setAsc(false);
+        myOrderParam.add(orderInfo);
+        orderInfo.setFieldName("showOrder");
+        orderInfo.setAsc(true);
+        myOrderParam.add(orderInfo);
+        myOrderParam.add(new MyOrderParam.OrderInfo("updateTime",false,null));
+        String orderBy = MyOrderParam.buildOrderBy(myOrderParam, DirectoryInfo.class);
+        //看到的项目为启用的项目
+        directoryInfoFilter.setEnable(1);
+        List<DirectoryInfo> directoryInfoList =
+                directoryInfoService.getDirectoryInfoListWithRelation(directoryInfoFilter, orderBy);
+//        List<DirectoryInfo> directoryInfoList =
+//                directoryInfoService.getDirectoryInfoListWithRelation(directoryInfoFilter, orderBy);
+
+        MyPageData<DirectoryInfoVo> directoryInfoVoMyPageData = MyPageUtil.makeResponseData(directoryInfoList, DirectoryInfoVo.class);
+
+        List<DirectoryInfoVo> dataList = directoryInfoVoMyPageData.getDataList();
+        //先把imgUrl由jaon转换为List<FileUrlObject>
+        dataList.stream().forEach(item ->{
+            if (StringUtils.isNotEmpty(item.getHotPictureUrl())) {
+                item.setHotPictureUrlsAfterConvert(UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), item.getHotPictureUrl()));
+            }
+        });
+        return directoryInfoVoMyPageData;
+    }
+
+    @Override
+    public MyPageData<TourTourismProjectTravelNotesVo> getTourTourismProjectTravelNotesList(TourismProjectTravelNotesToWebDto tourismProjectTravelNotesToWebDto){
+
+        TourTourismProjectTravelNotes tourTourismProjectTravelNotesFilter = new TourTourismProjectTravelNotes();
+        tourTourismProjectTravelNotesFilter.setEnable(1);
+        tourTourismProjectTravelNotesFilter.setIsHotspot(1);
+        if(tourismProjectTravelNotesToWebDto.getPageNum() != null && tourismProjectTravelNotesToWebDto.getPageSize() != null) {
+            PageMethod.startPage(tourismProjectTravelNotesToWebDto.getPageNum(), tourismProjectTravelNotesToWebDto.getPageSize(), true);
+        }
+        MyOrderParam myOrderParam = new MyOrderParam();
+        myOrderParam.add(new MyOrderParam.OrderInfo("showOrder",true,null));
+        myOrderParam.add(new MyOrderParam.OrderInfo("updateTime",false,null));
+        String orderBy = MyOrderParam.buildOrderBy(myOrderParam, TourTourismProjectTravelNotes.class);
+        List<TourTourismProjectTravelNotes> tourTourismProjectTravelNotesList = tourTourismProjectTravelNotesService.getTourTourismProjectTravelNotesList(tourTourismProjectTravelNotesFilter, orderBy);
+        MyPageData<TourTourismProjectTravelNotesVo> tourTourismProjectTravelNotesVoMyPageData = MyPageUtil.makeResponseData(tourTourismProjectTravelNotesList, TourTourismProjectTravelNotesVo.class);
+        List<TourTourismProjectTravelNotesVo> dataList = tourTourismProjectTravelNotesVoMyPageData.getDataList();
+        dataList.stream().forEach(item->{
+            if (StringUtils.isNotEmpty(item.getHomeHotPicture())) {
+                item.setHomeHotPicturesAfterConvert(UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), item.getHomeHotPicture()));
+            }
+        });
+
+        return tourTourismProjectTravelNotesVoMyPageData;
+    }
+
+    @Override
+    public MyPageData<TourTourismProjectTravelNotesVo> getTravelNotesPageList(TourismProjectTravelNotesToWebDto tourismProjectTravelNotesToWebDto){
+        //当belongTab为1的时候,则表明查询全部的游记
+        MyOrderParam myOrderParam = new MyOrderParam();
+        myOrderParam.add(new MyOrderParam.OrderInfo("isHotspot",false,null));
+        myOrderParam.add(new MyOrderParam.OrderInfo("showOrder",true,null));
+        myOrderParam.add(new MyOrderParam.OrderInfo("updateTime",false,null));
+        String orderBy = MyOrderParam.buildOrderBy(myOrderParam, TourTourismProjectTravelNotes.class);
+
+        TourTourismProjectTravelNotes tourTourismProjectTravelNotes = new TourTourismProjectTravelNotes();
+
+        if(StringUtils.isEmpty(tourismProjectTravelNotesToWebDto.getAreaId()) && StringUtils.isEmpty(tourismProjectTravelNotesToWebDto.getCountryId())){
+            //判断是否为全部的地区
+            tourTourismProjectTravelNotes.setEnable(1);
+            tourTourismProjectTravelNotes.setBelongTab(null);
+        }else if(StringUtils.isNotEmpty(tourismProjectTravelNotesToWebDto.getAreaId()) && StringUtils.isEmpty(tourismProjectTravelNotesToWebDto.getCountryId())){
+            //判断是否为该地区的所有国家
+            DirectoryInfo directoryInfo = new DirectoryInfo();
+            directoryInfo.setParentId(Long.parseLong(tourismProjectTravelNotesToWebDto.getAreaId()));
+            directoryInfo.setEnable(1);
+            List<DirectoryInfo> directoryInfoList =
+                    directoryInfoService.getDirectoryInfoList(directoryInfo, "");
+            List<Long> directoryIdList =
+                    directoryInfoList.stream().map(DirectoryInfo::getId).collect(Collectors.toList());
+            tourTourismProjectTravelNotes.setBelongTab(null);
+            tourTourismProjectTravelNotes.setDirectoryInfoIds(directoryIdList);
+        }else {
+            //如果查询的是某个国家的游记的话
+            if(StringUtils.isNotEmpty(tourismProjectTravelNotesToWebDto.getCountryId())) {
+                tourTourismProjectTravelNotes.setBelongTab(Long.parseLong(tourismProjectTravelNotesToWebDto.getCountryId()));
+            }
+        }
+
+        if(tourismProjectTravelNotesToWebDto.getPageNum() != null && tourismProjectTravelNotesToWebDto.getPageSize() != null) {
+            PageMethod.startPage(tourismProjectTravelNotesToWebDto.getPageNum(), tourismProjectTravelNotesToWebDto.getPageSize(), true);
+        }
+
+        List<TourTourismProjectTravelNotes> tourTourismProjectTravelNotesList =
+                tourTourismProjectTravelNotesService.getTourTourismProjectTravelNotesList(tourTourismProjectTravelNotes, orderBy);
+
+        MyPageData<TourTourismProjectTravelNotesVo> tourTourismProjectTravelNotesVoMyPageData =
+                MyPageUtil.makeResponseData(tourTourismProjectTravelNotesList, TourTourismProjectTravelNotesVo.class);
+
+        List<TourTourismProjectTravelNotesVo> dataList = tourTourismProjectTravelNotesVoMyPageData.getDataList();
+
+        dataList.stream().forEach(item->{
+            if(StringUtils.isNotEmpty(item.getTourismUrl())){
+                item.setTourismUrlsAfterConvert(UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), item.getTourismUrl()));
+            }
+        });
+
+        return tourTourismProjectTravelNotesVoMyPageData;
+    }
+
+    @Override
+    public TourTourismProjectTravelNotesVo travelNotesDetail(String id){
+        TourTourismProjectTravelNotes tourTourismProjectTravelNotes =
+                tourTourismProjectTravelNotesService.getByIdWithRelation(id, MyRelationParam.full());
+
+        TourTourismProjectTravelNotesVo tourTourismProjectTravelNotesVo =
+                MyModelUtil.copyTo(tourTourismProjectTravelNotes, TourTourismProjectTravelNotesVo.class);
+
+        if(tourTourismProjectTravelNotesVo != null) {
+            if (StringUtils.isNotEmpty(tourTourismProjectTravelNotesVo.getTourismUrl())) {
+                tourTourismProjectTravelNotesVo.setTourismUrlsAfterConvert(UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), tourTourismProjectTravelNotesVo.getTourismUrl()));
+            }
+
+            if (StringUtils.isNotEmpty(tourTourismProjectTravelNotesVo.getHomeHotPicture())) {
+                tourTourismProjectTravelNotesVo.setHomeHotPicturesAfterConvert(UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), tourTourismProjectTravelNotesVo.getHomeHotPicture()));
+            }
+
+            if (StringUtils.isNotEmpty(tourTourismProjectTravelNotesVo.getTravelNotesBanner())) {
+                tourTourismProjectTravelNotesVo.setTravelNotesBannerAfterConvert(UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), tourTourismProjectTravelNotesVo.getTravelNotesBanner()));
+            }
+        }
+        return tourTourismProjectTravelNotesVo;
+    }
+
+    @Override
+    @Transactional
+    public void userLikeTravelNotesUpdate(Long travelNotesId,Integer type){
+
+        TourUserLikeTravelNotes tourUserLikeTravelNotes =
+                new TourUserLikeTravelNotes();
+        tourUserLikeTravelNotes.setTravelNotesId(travelNotesId);
+        tourUserLikeTravelNotes.setUserId(TokenData.takeFromRequest().getUserId());
+        TourTourismProjectTravelNotes tourTourismProjectTravelNotes = tourTourismProjectTravelNotesService.getById(travelNotesId);
+        if(tourTourismProjectTravelNotes.getLikeCount() != null) {
+            if(type == 1) {
+                tourTourismProjectTravelNotes.setLikeCount(tourTourismProjectTravelNotes.getLikeCount() + 1);
+                if(tourTourismProjectTravelNotes.getHotValue() != null) {
+                    tourTourismProjectTravelNotes.setHotValue(tourTourismProjectTravelNotes.getHotValue() + 100);
+                }else {
+                    tourTourismProjectTravelNotes.setHotValue(100);
+                }
+            }else {
+                tourTourismProjectTravelNotes.setLikeCount(tourTourismProjectTravelNotes.getLikeCount() - 1);
+            }
+        }else {
+            tourTourismProjectTravelNotes.setLikeCount(1);
+        }
+        if(type == 0){
+            tourUserLikeTravelNotesService.removeBy(tourUserLikeTravelNotes);
+        }else {
+            tourUserLikeTravelNotesService.saveNew(tourUserLikeTravelNotes);
+        }
+        tourTourismProjectTravelNotesService.updateById(tourTourismProjectTravelNotes);
+    }
+
+    @Override
+    public Boolean isLikeTravelNotes(Long travelNotesId){
+        TourUserLikeTravelNotes tourUserLikeTravelNotes = new TourUserLikeTravelNotes();
+        tourUserLikeTravelNotes.setTravelNotesId(travelNotesId);
+        tourUserLikeTravelNotes.setUserId(TokenData.takeFromRequest().getUserId());
+        return tourUserLikeTravelNotesService.existByFilter(tourUserLikeTravelNotes);
+
+    }
+
+    @Override
+    public void travelNotesViewCountAdd(Long travelNotesId){
+
+        TourTourismProjectTravelNotes tourTourismProjectTravelNotes = tourTourismProjectTravelNotesService.getById(travelNotesId);
+        if(tourTourismProjectTravelNotes.getPageViewCount() != null) {
+            tourTourismProjectTravelNotes.setPageViewCount(tourTourismProjectTravelNotes.getPageViewCount() + 1);
+        }else {
+            tourTourismProjectTravelNotes.setPageViewCount(1);
+        }
+        //每有一人浏览,则增加50热度值
+        if(tourTourismProjectTravelNotes.getHotValue() != null) {
+            tourTourismProjectTravelNotes.setHotValue(tourTourismProjectTravelNotes.getHotValue() + 50);
+        }else {
+            tourTourismProjectTravelNotes.setHotValue(50);
+        }
+        tourTourismProjectTravelNotesService.updateById(tourTourismProjectTravelNotes);
+    }
+
+    @Override
+    public void generate(){
+        //随机生成1k-2w的浏览量,浏览量除以2为(点赞数的上限)浏览量除以3为(点赞数的下限),热度值为浏览量的二倍以上-三倍以下随机数,这三个数值都为整数
+        List<TourTourismProjectTravelNotes> tourTourismProjectTravelNotesList =
+                tourTourismProjectTravelNotesService.getTourTourismProjectTravelNotesList(new TourTourismProjectTravelNotes(), "");
+
+        tourTourismProjectTravelNotesList.stream().forEach(item->{
+
+            Random random = new Random();
+            // 生成1k-2w的浏览量
+            int minViews = 1000;
+            int maxViews = 20000;
+            int views = random.nextInt(maxViews - minViews + 1) + minViews;
+
+            // 计算点赞数的上限和下限
+            int maxLikes = views / 3;
+            int minLikes = views / 4;
+            int likes = random.nextInt(maxLikes - minLikes + 1) + minLikes;
+
+            // 生成热度值()
+            int heat = (views*50)+(likes*100);
+
+            item.setHotValue(heat);
+            item.setLikeCount(likes);
+            item.setPageViewCount(views);
+
+            tourTourismProjectTravelNotesService.updateById(item);
+        });
+    }
+
+}
+

+ 254 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/website/service/impl/CleanDataServiceImpl.java

@@ -0,0 +1,254 @@
+package com.tourism.webadmin.app.website.service.impl;
+
+
+import com.tourism.common.additional.utils.StringUtils;
+import com.tourism.common.core.upload.BaseUpDownloader;
+import com.tourism.common.core.upload.UpDownloaderFactory;
+import com.tourism.common.core.upload.UploadResponseInfo;
+import com.tourism.common.core.upload.UploadStoreTypeEnum;
+import com.tourism.webadmin.app.website.service.CleanDataService;
+import com.tourism.webadmin.back.dao.JobContentMapper;
+import com.tourism.webadmin.back.dao.TourTourismTravelNotesContentMapper;
+import com.tourism.webadmin.back.dao.TourismContentMapper;
+import com.tourism.webadmin.back.dto.MultipartFileDto;
+import com.tourism.webadmin.back.model.JobContent;
+import com.tourism.webadmin.back.model.TourTourismTravelNotesContent;
+import com.tourism.webadmin.back.model.TourismContent;
+import com.tourism.webadmin.back.service.JobContentService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.tourism.webadmin.back.service.*;
+import com.tourism.common.additional.config.ApplicationConfig;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.Base64;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * 门户网站首页Service
+ *
+ * @author tourism
+ */
+@Slf4j
+@Service("CleanDataService")
+public class CleanDataServiceImpl implements CleanDataService {
+
+    @Autowired
+    private ApplicationConfig applicationConfig;
+    @Autowired
+    private TourismContentService tourismContentService;
+    @Autowired
+    private TourTourismTravelNotesContentService tourTourismTravelNotesContentService;
+    @Autowired
+    private JobContentService jobContentService;
+    @Autowired
+    private UpDownloaderFactory upDownloaderFactory;
+    @Autowired
+    private TourTourismTravelNotesContentMapper tourTourismTravelNotesContentMapper;
+    @Autowired
+    private TourismContentMapper tourismContentMapper;
+    @Autowired
+    private JobContentMapper jobContentMapper;
+    @Override
+    public void cleanRichTextData() throws Exception {
+
+        //对项目关联的富文本表进行数据清洗
+        List<Long> tourismContentIdList = tourismContentMapper.selectAllIds();
+        for (Long id : tourismContentIdList) {
+            TourismContent tourismContent = tourismContentService.getById(id);
+            if(tourismContent != null && StringUtils.isNotEmpty(tourismContent.getContent())){
+                String content = tourismContent.getContent();
+                if(StringUtils.isNotBlank(content)){
+                    String processedTextContent = replaceImagesWithUrlsTourismContent(content);
+                    tourismContent.setContent(processedTextContent);
+                }
+
+                String bookingNotice = tourismContent.getBookingNotice();
+                if(StringUtils.isNotEmpty(bookingNotice)){
+                    String processedTextBookingNotice = replaceImagesWithUrlsTourismContent(bookingNotice);
+                    tourismContent.setBookingNotice(processedTextBookingNotice);
+                }
+
+                String costDescription = tourismContent.getCostDescription();
+                if(StringUtils.isNotEmpty(costDescription)){
+                    String processedTextCostDescription = replaceImagesWithUrlsTourismContent(costDescription);
+                    tourismContent.setBookingNotice(processedTextCostDescription);
+                }
+                tourismContentService.updateById(tourismContent);
+            }
+        }
+//        List<TourismContent> allList = tourismContentService.getAllList();
+//        for(TourismContent tourismContent : allList){
+//
+//        }
+
+        //对旅游游记的富文本表进行数据清洗
+        List<Long> travelNotesIdList = tourTourismTravelNotesContentMapper.selectAllIds();
+        for(Long travelNotesId : travelNotesIdList) {
+            TourTourismTravelNotesContent tourTourismTravelNotesContent = tourTourismTravelNotesContentService.getById(travelNotesId);
+            if (tourTourismTravelNotesContent != null && StringUtils.isNotEmpty(tourTourismTravelNotesContent.getContent())) {
+                String content = tourTourismTravelNotesContent.getContent();
+                if (StringUtils.isNotBlank(content)) {
+                    String processedTextContent = replaceImagesWithUrlsTravelNotesContent(content);
+                    tourTourismTravelNotesContent.setContent(processedTextContent);
+                }
+
+                String bookingNotice = tourTourismTravelNotesContent.getBookingNotice();
+                if (StringUtils.isNotEmpty(bookingNotice)) {
+                    String processedTextBookingNotice = replaceImagesWithUrlsTravelNotesContent(bookingNotice);
+                    tourTourismTravelNotesContent.setBookingNotice(processedTextBookingNotice);
+                }
+
+                String costDescription = tourTourismTravelNotesContent.getCostDescription();
+                if (StringUtils.isNotEmpty(costDescription)) {
+                    String processedTextCostDescription = replaceImagesWithUrlsTravelNotesContent(costDescription);
+                    tourTourismTravelNotesContent.setBookingNotice(processedTextCostDescription);
+                }
+                tourTourismTravelNotesContentService.updateById(tourTourismTravelNotesContent);
+            }
+        }
+//        List<TourTourismTravelNotesContent> allListTravelNotesContent = tourTourismTravelNotesContentService.getAllList();
+//        for(TourTourismTravelNotesContent tourTourismTravelNotesContent : allListTravelNotesContent){
+//        }
+
+        //对劳务富文本表进行数据清洗
+        List<Long> jobContentIdList = jobContentMapper.selectAllIds();
+        for(Long jobContentId : jobContentIdList) {
+            JobContent jobContent = jobContentService.getById(jobContentId);
+            if (jobContent != null && StringUtils.isNotEmpty(jobContent.getContent())) {
+                String content = jobContent.getContent();
+                if (StringUtils.isNotBlank(content)) {
+                    String processedTextContent = replaceImagesWithUrlsJobContentContent(content);
+                    jobContent.setContent(processedTextContent);
+                }
+                jobContentService.updateById(jobContent);
+            }
+        }
+//        List<JobContent> allListJonContent = jobContentService.getAllList();
+//        for(JobContent jobContent : allListJonContent) {
+//            String content = jobContent.getContent();
+//            if (StringUtils.isNotEmpty(content)) {
+//                String processedTextContent = replaceImagesWithUrls(content);
+//                jobContent.setContent(processedTextContent);
+//            }
+//        jobContentService.updateById(jobContent);
+//        }
+    }
+
+
+
+//    public static String extractText(String richText) {
+//        // 使用正则表达式提取汉字
+//        Pattern textPattern = Pattern.compile("[\\u4e00-\\u9fa5]+");
+//        Matcher textMatcher = textPattern.matcher(richText);
+//        StringBuilder textBuilder = new StringBuilder();
+//
+//        while (textMatcher.find()) {
+//            textBuilder.append(textMatcher.group());
+//        }
+//
+//        return textBuilder.toString();
+//    }
+
+
+    public String replaceImagesWithUrlsTourismContent(String richText) throws Exception {
+        // 使用正则表达式提取图片的 Base64 数据
+        Pattern imgPattern = Pattern.compile("<img.*?src\\s*=\\s*\"data:image/([^;]+);base64,([^\"]+)\"[^>]*?>", Pattern.CASE_INSENSITIVE);
+        Matcher imgMatcher = imgPattern.matcher(richText);
+        StringBuffer sb = new StringBuffer();
+
+        while (imgMatcher.find()) {
+//            String mimeType = imgMatcher.group(1);
+            String base64Data = imgMatcher.group(2);
+//            byte[] imageData = Base64.getDecoder().decode(base64Data);
+//             根据字段注解中的存储类型,通过工厂方法获取匹配的上传下载实现类,从而解耦。
+            BaseUpDownloader upDownloader = upDownloaderFactory.get(UploadStoreTypeEnum.HUAWEI_OBS_SYSTEM);
+            MultipartFile multipartFile = base64ToMultipartFile(base64Data);
+            UploadResponseInfo responseInfo = upDownloader.doUpload(null,
+                    applicationConfig.getUploadFileBaseDir(), TourismContent.class.getSimpleName(), "ContentImages", true, multipartFile);
+            String uploadPath = responseInfo.getUploadPath();
+
+            // 替换图片的 src 属性
+            //示例:https://v.xiaoyaotravel.com/image/TourTourismProjectTravelNotes/homeHotPicture/0063b1b69810401c9cef2a11c36dd14c.webp
+            imgMatcher.appendReplacement(sb, "<img src=\"" + "https://v.xiaoyaotravel.com/" + uploadPath + "/"+responseInfo.getFilename() +"\"/>");
+        }
+
+        imgMatcher.appendTail(sb);
+        return sb.toString();
+    }
+
+    public String replaceImagesWithUrlsTravelNotesContent(String richText) throws Exception {
+        // 使用正则表达式提取图片的 Base64 数据
+        Pattern imgPattern = Pattern.compile("<img.*?src\\s*=\\s*\"data:image/([^;]+);base64,([^\"]+)\"[^>]*?>", Pattern.CASE_INSENSITIVE);
+        Matcher imgMatcher = imgPattern.matcher(richText);
+        StringBuffer sb = new StringBuffer();
+
+        while (imgMatcher.find()) {
+//            String mimeType = imgMatcher.group(1);
+            String base64Data = imgMatcher.group(2);
+//            byte[] imageData = Base64.getDecoder().decode(base64Data);
+//             根据字段注解中的存储类型,通过工厂方法获取匹配的上传下载实现类,从而解耦。
+            BaseUpDownloader upDownloader = upDownloaderFactory.get(UploadStoreTypeEnum.HUAWEI_OBS_SYSTEM);
+            MultipartFile multipartFile = base64ToMultipartFile(base64Data);
+            UploadResponseInfo responseInfo = upDownloader.doUpload(null,
+                    applicationConfig.getUploadFileBaseDir(), TourTourismTravelNotesContent.class.getSimpleName(), "ContentImages", true, multipartFile);
+            String uploadPath = responseInfo.getUploadPath();
+
+            // 替换图片的 src 属性
+            //示例:https://v.xiaoyaotravel.com/image/TourTourismProjectTravelNotes/homeHotPicture/0063b1b69810401c9cef2a11c36dd14c.webp
+            imgMatcher.appendReplacement(sb, "<img src=\"" + "https://v.xiaoyaotravel.com/" + uploadPath + "/"+responseInfo.getFilename() +"\"/>");
+        }
+
+        imgMatcher.appendTail(sb);
+        return sb.toString();
+    }
+
+    public String replaceImagesWithUrlsJobContentContent(String richText) throws Exception {
+        // 使用正则表达式提取图片的 Base64 数据
+        Pattern imgPattern = Pattern.compile("<img.*?src\\s*=\\s*\"data:image/([^;]+);base64,([^\"]+)\"[^>]*?>", Pattern.CASE_INSENSITIVE);
+        Matcher imgMatcher = imgPattern.matcher(richText);
+        StringBuffer sb = new StringBuffer();
+
+        while (imgMatcher.find()) {
+//            String mimeType = imgMatcher.group(1);
+            String base64Data = imgMatcher.group(2);
+//            byte[] imageData = Base64.getDecoder().decode(base64Data);
+//             根据字段注解中的存储类型,通过工厂方法获取匹配的上传下载实现类,从而解耦。
+            BaseUpDownloader upDownloader = upDownloaderFactory.get(UploadStoreTypeEnum.HUAWEI_OBS_SYSTEM);
+            MultipartFile multipartFile = base64ToMultipartFile(base64Data);
+            UploadResponseInfo responseInfo = upDownloader.doUpload(null,
+                    applicationConfig.getUploadFileBaseDir(), JobContent.class.getSimpleName(), "ContentImages", true, multipartFile);
+            String uploadPath = responseInfo.getUploadPath();
+
+            // 替换图片的 src 属性
+            //示例:https://v.xiaoyaotravel.com/image/TourTourismProjectTravelNotes/homeHotPicture/0063b1b69810401c9cef2a11c36dd14c.webp
+            imgMatcher.appendReplacement(sb, "<img src=\"" + "https://v.xiaoyaotravel.com/" + uploadPath + "/"+responseInfo.getFilename() +"\"/>");
+        }
+
+        imgMatcher.appendTail(sb);
+        return sb.toString();
+    }
+
+
+    private static MultipartFile base64ToMultipartFile(String base64) throws Exception {
+        // 去除可能存在的数据头(例如:"data:image/png;base64,")
+//        String base64Data = base64.split(",")[1];
+
+        // 解码Base64字符串到字节数组
+        byte[] decodedBytes = Base64.getDecoder().decode(base64);
+
+        // 创建一个输入流
+        InputStream inputStream = new ByteArrayInputStream(decodedBytes);
+
+        // 使用MockMultipartFile创建MultipartFile对象
+        // 参数分别为:文件名、原始文件名、媒体类型、字节流
+        MultipartFile multipartFile = new MultipartFileDto("file", "originalFileName.png", "image/png", inputStream);
+
+        return multipartFile;
+    }
+}

+ 57 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/website/service/impl/JobRecordServiceImpl.java

@@ -0,0 +1,57 @@
+package com.tourism.webadmin.app.website.service.impl;
+
+import com.github.pagehelper.page.PageMethod;
+import com.tourism.common.core.object.MyOrderParam;
+import com.tourism.common.core.object.MyPageData;
+import com.tourism.common.core.util.MyModelUtil;
+import com.tourism.common.core.util.MyPageUtil;
+import com.tourism.webadmin.app.website.dto.PageDto;
+import com.tourism.webadmin.app.website.service.JobRecordService;
+import com.tourism.webadmin.back.model.JobProject;
+import com.tourism.webadmin.back.model.TourBookInfo;
+import com.tourism.webadmin.back.service.JobProjectService;
+import com.tourism.webadmin.back.service.TourBookInfoService;
+import com.tourism.webadmin.back.vo.JobProjectVo;
+import com.tourism.webadmin.back.vo.TourBookInfoVo;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+import java.util.List;
+
+@Slf4j
+@Service("JobRecordService")
+public class JobRecordServiceImpl implements JobRecordService {
+    @Autowired
+    private TourBookInfoService tourBookInfoService;
+    @Autowired
+    private JobProjectService jobProjectService;
+
+    public MyPageData<TourBookInfoVo> jobRecordList(String mobile, PageDto pageDto){
+
+        TourBookInfo tourBookInfo = new TourBookInfo();
+        tourBookInfo.setBookMobile(mobile);
+        tourBookInfo.setType(2);
+
+        if(pageDto.getPageSize() != null && pageDto.getPageNum() != null){
+            PageMethod.startPage(pageDto.getPageNum(), pageDto.getPageSize(), true);
+        }
+        MyOrderParam myOrderParam = new MyOrderParam();
+        myOrderParam.add(new MyOrderParam.OrderInfo("createTime",false,null));
+        String orderBy = MyOrderParam.buildOrderBy(myOrderParam, TourBookInfo.class);
+        List<TourBookInfo> tourBookInfoList = tourBookInfoService.getTourBookInfoList(tourBookInfo, orderBy);
+        MyPageData<TourBookInfoVo> tourBookInfoVoMyPageData = MyPageUtil.makeResponseData(tourBookInfoList, TourBookInfoVo.class);
+        if(tourBookInfoVoMyPageData != null && !CollectionUtils.isEmpty(tourBookInfoVoMyPageData.getDataList())){
+            List<TourBookInfoVo> dataList = tourBookInfoVoMyPageData.getDataList();
+            dataList.stream().forEach(item->{
+                if(item.getProjectId() != null){
+                    JobProject jobProject = jobProjectService.getById(item.getProjectId());
+                    JobProjectVo jobProjectVo = MyModelUtil.copyTo(jobProject, JobProjectVo.class);
+                    item.setJobProjectVo(jobProjectVo);
+                }
+            });
+        }
+        return tourBookInfoVoMyPageData;
+    }
+}

+ 193 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/website/service/impl/LoginToWebsiteServiceImpl.java

@@ -0,0 +1,193 @@
+package com.tourism.webadmin.app.website.service.impl;
+
+import cn.hutool.core.util.ReflectUtil;
+import com.tourism.common.additional.constant.CacheConstants;
+import com.tourism.common.additional.utils.StringUtils;
+import com.tourism.common.core.constant.GlobalDeletedFlag;
+import com.tourism.common.core.object.TokenData;
+import com.tourism.common.core.util.*;
+import com.tourism.common.sequence.wrapper.IdGeneratorWrapper;
+import com.tourism.webadmin.back.dao.TourBookInfoMapper;
+import com.tourism.webadmin.app.website.dto.TourUserRegisterDto;
+import com.tourism.webadmin.back.dto.TourBookInfoDto;
+import com.tourism.webadmin.back.model.TourBookInfo;
+import com.tourism.webadmin.back.model.TourUser;
+import com.tourism.webadmin.back.model.constant.TourUserStatus;
+import com.tourism.webadmin.app.website.service.LoginToWebsiteService;
+import com.tourism.webadmin.back.service.TourBookInfoService;
+import com.tourism.webadmin.back.service.TourUserService;
+import io.jsonwebtoken.lang.Collections;
+import jakarta.servlet.http.HttpServletRequest;
+import lombok.extern.slf4j.Slf4j;
+import org.redisson.api.RBucket;
+import org.redisson.api.RedissonClient;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.stereotype.Service;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 注册校验方法
+ * 
+ * @author tourism
+ */
+@Slf4j
+@Service("LoginToWebsiteService")
+public class LoginToWebsiteServiceImpl implements LoginToWebsiteService
+{
+    @Autowired
+    private TourBookInfoService tourBookInfoService;
+    @Autowired
+    private TourUserService tourUserService;
+    @Autowired
+    private PasswordEncoder passwordEncoder;
+    @Autowired
+    private IdGeneratorWrapper idGenerator;
+    @Autowired
+    private TourBookInfoMapper tourBookInfoMapper;
+    @Autowired
+    private RedissonClient redissonClient;
+    /**
+     * 整个工程的实体对象中,创建者Id字段的Java对象名。
+     */
+    public static final String CREATE_USER_ID_FIELD_NAME = "createUserId";
+    /**
+     * 整个工程的实体对象中,创建时间字段的Java对象名。
+     */
+    public static final String CREATE_TIME_FIELD_NAME = "createTime";
+    /**
+     * 整个工程的实体对象中,更新者Id字段的Java对象名。
+     */
+    public static final String UPDATE_USER_ID_FIELD_NAME = "updateUserId";
+    /**
+     * 整个工程的实体对象中,更新时间字段的Java对象名。
+     */
+    public static final String UPDATE_TIME_FIELD_NAME = "updateTime";
+
+    /**
+     * 校验验证码
+     *
+     * @param code 验证码
+     * @param uuid 唯一标识
+     * @return 结果
+     */
+    @Override
+    public boolean validateCaptcha(String uuid, String code)
+    {
+        String verifyKey = CacheConstants.getCaptchaCodeKey(StringUtils.nvl(uuid, ""));
+//        String captcha = redisCache.getCacheObject(verifyKey);
+        // 获取 RBucket 对象
+        RBucket<String> bucket = redissonClient.getBucket(verifyKey);
+
+        // 从缓存中获取对象
+        String captcha = bucket.get();
+
+//        redisCache.deleteObject(verifyKey);
+        bucket.delete();
+        if (captcha == null || !(captcha.equals(code.toLowerCase())))
+        {
+            return false;
+        }
+        return true;
+    }
+    /**
+     * 校验短信验证码
+     *
+     * @param mobile 手机号
+     * @param smsCode 短信验证码
+     * @return 结果
+     */
+    @Override
+    public boolean validateSmsCode(String mobile,String smsCode){
+//        String cacheObject = redisCache.getCacheObject(mobile);
+
+        // 获取 RBucket 对象
+        RBucket<String> bucket = redissonClient.getBucket(mobile);
+
+        // 从缓存中获取对象
+        String cacheObject = bucket.get();
+//        redisCache.deleteObject(mobile);
+        bucket.delete();
+        if(cacheObject == null || !(smsCode.equals(cacheObject.toLowerCase())) ){
+            return false;
+        }
+        return true;
+    }
+    /**
+     * 保存用户的预约信息
+     *
+     * @param tourBookInfoDto 门户预定管理Dto对象
+     * @return 结果
+     */
+    @Override
+    public boolean saveBookInfo(HttpServletRequest request, TourBookInfoDto tourBookInfoDto){
+        String bookMobile = tourBookInfoDto.getBookMobile();
+            TourBookInfo tourBookInfo = new TourBookInfo();
+            tourBookInfo.setBookMobile(bookMobile);
+            tourBookInfo.setBookTime(new Date());
+//            tourBookInfo.setBookName(bookName);
+            tourBookInfo.setBookIp(IpUtil.getRemoteIpAddress(ContextUtil.getHttpRequest()));
+            tourBookInfo.setIsHandle("0");
+            tourBookInfo.setProjectId(tourBookInfoDto.getProjectId());
+            tourBookInfo.setType(tourBookInfoDto.getType());
+            tourBookInfoMapper.insert(buildDefaultValue(tourBookInfo));
+            return true;
+    }
+
+    private TourBookInfo buildDefaultValue(TourBookInfo tourBookInfo) {
+        if (tourBookInfo.getId() == null) {
+            tourBookInfo.setId(idGenerator.nextLongId());
+        }
+        fillCommonsForInsert(tourBookInfo);
+        tourBookInfo.setDeletedFlag(GlobalDeletedFlag.NORMAL);
+        return tourBookInfo;
+    }
+
+    /**
+     * 在插入实体对象数据之前,可以调用该方法,初始化通用字段的数据。
+     * 在登录之前的操作,所以createdId和updatedId都设置为0.
+     * @param data 实体对象。
+     * @param <M>  实体对象类型。
+     */
+    public static <M> void fillCommonsForInsert(M data) {
+        Field createdByField = ReflectUtil.getField(data.getClass(), CREATE_USER_ID_FIELD_NAME);
+        if (createdByField != null) {
+            ReflectUtil.setFieldValue(data, createdByField, 0L);
+        }
+        Field createTimeField = ReflectUtil.getField(data.getClass(), CREATE_TIME_FIELD_NAME);
+        if (createTimeField != null) {
+            ReflectUtil.setFieldValue(data, createTimeField, new Date());
+        }
+        Field updatedByField = ReflectUtil.getField(data.getClass(), UPDATE_USER_ID_FIELD_NAME);
+        if (updatedByField != null) {
+            ReflectUtil.setFieldValue(data, updatedByField, 0L);
+        }
+        Field updateTimeField = ReflectUtil.getField(data.getClass(), UPDATE_TIME_FIELD_NAME);
+        if (updateTimeField != null) {
+            ReflectUtil.setFieldValue(data, updateTimeField, new Date());
+        }
+    }
+    /**
+     * 保存用户的注册信息
+     *
+     * @param tourUserRegisterDto 预约手机号
+     * @return 结果
+     */
+    @Override
+    public boolean saveRegisterUserInfo(TourUserRegisterDto tourUserRegisterDto){
+        //存储用户信息
+        TourUser tourUser = new TourUser();
+        //手机号注册,默认用户名为手机号
+        tourUser.setLoginName(tourUserRegisterDto.getMobile());
+        tourUser.setUserStatus(TourUserStatus.STATUS_NORMAL);
+        tourUser.setPassword(passwordEncoder.encode(tourUser.getPassword()));
+        tourUserService.saveNew(tourUser);
+        return true;
+    }
+}

+ 243 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/website/service/impl/TourismProjectToWebServiceImpl.java

@@ -0,0 +1,243 @@
+package com.tourism.webadmin.app.website.service.impl;
+
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.github.pagehelper.page.PageMethod;
+import com.tourism.common.additional.config.ApplicationConfig;
+import com.tourism.common.additional.utils.MapConvertUtils;
+import com.tourism.common.additional.utils.StringUtils;
+import com.tourism.common.additional.utils.UrlConvertUtils;
+import com.tourism.common.core.annotation.MyRequestBody;
+import com.tourism.common.core.constant.ErrorCodeEnum;
+import com.tourism.common.core.object.*;
+import com.tourism.common.core.upload.BaseUpDownloader;
+import com.tourism.common.core.upload.UpDownloaderFactory;
+import com.tourism.common.core.upload.UploadResponseInfo;
+import com.tourism.common.core.upload.UploadStoreTypeEnum;
+import com.tourism.common.core.util.IpUtil;
+import com.tourism.common.core.util.MyModelUtil;
+import com.tourism.common.core.util.MyPageUtil;
+import com.tourism.webadmin.app.website.dto.TourismBookProjectDto;
+import com.tourism.webadmin.app.website.dto.TourismProjectToWebDto;
+import com.tourism.webadmin.app.website.service.CleanDataService;
+import com.tourism.webadmin.app.website.service.TourismProjectToWebService;
+import com.tourism.webadmin.app.website.vo.DateRange;
+import com.tourism.webadmin.app.website.vo.TourismProjectDatePriceVo;
+import com.tourism.webadmin.app.website.vo.WebSiteProjectDatePriceVo;
+import com.tourism.webadmin.back.dao.JobContentMapper;
+import com.tourism.webadmin.back.dao.TourTourismTravelNotesContentMapper;
+import com.tourism.webadmin.back.dao.TourismContentMapper;
+import com.tourism.webadmin.back.dto.MultipartFileDto;
+import com.tourism.webadmin.back.model.*;
+import com.tourism.webadmin.back.service.*;
+import com.tourism.webadmin.back.vo.TourismProjectVo;
+import jakarta.servlet.http.HttpServletRequest;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.time.DateUtils;
+import org.redisson.api.RBucket;
+import org.redisson.api.RedissonClient;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import static java.util.stream.Collectors.toList;
+
+/**
+ * 门户网站首页Service
+ *
+ * @author tourism
+ */
+@Slf4j
+@Service("TourismProjectToWebService")
+public class TourismProjectToWebServiceImpl implements TourismProjectToWebService {
+
+    @Autowired
+    private TourismProjectService tourismProjectService;
+    @Autowired
+    private ApplicationConfig applicationConfig;
+    @Autowired
+    private TourismDatePriceService tourismDatePriceService;
+    @Autowired
+    private TourUserService tourUserService;
+    @Autowired
+    private TourBookInfoService tourBookInfoService;
+    @Autowired
+    private RedissonClient redissonClient;
+
+
+    @Override
+    public MyPageData<TourismProjectVo> list(TourismProjectToWebDto tourismProjectDtoFilter) {
+//        if(tourismProjectDtoFilter.getBelongTab() == null){
+//            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST,"所属分类(belongTab)不能为空!");
+//        }
+        //如果belongTab小于1000并且大于10.则表明查询的是一级菜单
+        if(tourismProjectDtoFilter.getBelongTab() !=null && tourismProjectDtoFilter.getBelongTab()>=10 && tourismProjectDtoFilter.getBelongTab()<1000){
+            DirectoryInfo filter = new DirectoryInfo();
+            filter.setParentId(tourismProjectDtoFilter.getBelongTab());
+            filter.setEnable(1);
+            List<DirectoryInfo> directoryInfoList = tourismProjectService.getDirectoryInfoList(filter, null);
+            tourismProjectDtoFilter.setBelongTab(null);
+            tourismProjectDtoFilter.setDirectoryInfoIds(directoryInfoList.stream().map(DirectoryInfo::getId).collect(toList()));
+        }
+        TourismProject tourismProjectFilter = MyModelUtil.copyTo(tourismProjectDtoFilter, TourismProject.class);
+        //首页展示的为启用的内容
+        tourismProjectFilter.setEnable(1);
+        String orderBy = MyOrderParam.buildOrderBy(tourismProjectDtoFilter.getOrderParamList(), TourismProject.class);
+        if (tourismProjectDtoFilter.getPageNum() != null && tourismProjectDtoFilter.getPageSize() != null ) {
+            PageMethod.startPage(tourismProjectDtoFilter.getPageNum(), tourismProjectDtoFilter.getPageSize(), true);
+        }
+        List<TourismProject> tourismProjectList =
+                tourismProjectService.getTourismProjectList(tourismProjectFilter, orderBy);
+//        List<TourismProject> tourismProjectList =
+//                tourismProjectService.getTourismProjectListWithRelation(tourismProjectFilter, orderBy);
+        MyPageData<TourismProjectVo> tourismProjectVoMyPageData = MyPageUtil.makeResponseData(tourismProjectList, TourismProjectVo.class);
+
+        List<TourismProjectVo> dataList = tourismProjectVoMyPageData.getDataList();
+        //先把imgUrl由jaon转换为List<FileUrlObject>
+        dataList.stream().forEach(item ->
+        {
+            List<String> urlList = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), item.getHomeHotPicture());
+            item.setHomeHotPicturesAfterConvert(urlList);
+            List<String> arrayList = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), item.getTourismUrl());
+            item.setTourismUrlsAfterConvert(arrayList);
+            List<String> arrayList1 = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), item.getTravelNotesBanner());
+            item.setTravelNotesBannerAfterConvert(arrayList1);
+        });
+        return tourismProjectVoMyPageData;
+    }
+
+    @Override
+    public TourismProjectVo detail(TourismProject tourismProject) {
+        TourismProjectVo tourismProjectVo = MyModelUtil.copyTo(tourismProject, TourismProjectVo.class);
+        List<String> arrayList = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), tourismProjectVo.getTourismUrl());
+        tourismProjectVo.setTourismUrlsAfterConvert(arrayList);
+        List<String> arrayList1 = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), tourismProjectVo.getHomeHotPicture());
+        tourismProjectVo.setHomeHotPicturesAfterConvert(arrayList1);
+        List<String> arrayList2 = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), tourismProjectVo.getTravelNotesBanner());
+        tourismProjectVo.setTravelNotesBannerAfterConvert(arrayList2);
+        if(tourismProjectVo.getTourismFile() != null){
+            // 使用 Jackson 的 ObjectMapper 进行转换
+            ObjectMapper objectMapper = new ObjectMapper();
+            TourismFile tourismFile = objectMapper.convertValue(tourismProjectVo.getTourismFile(), TourismFile.class);
+            if(StringUtils.isNotEmpty(tourismFile.getFileUrl())){
+                tourismFile.setFileUrlsAfterConvert(UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), tourismFile.getFileUrl()));
+                tourismProjectVo.setTourismFile(MapConvertUtils.convertObjectToMap(tourismFile));
+            }
+        }
+        //二维码路径赋值
+//        tourismProjectVo.setContactCode("[{\"name\":\"微信图片_20241021154757.png\",\"downloadUri\":\"/admin/app/tourTourismProjectTravelNotes/download\",\"filename\":\"6b92b75edcc04da1bd6e4af056911730.png\",\"uploadPath\":\"image/TourTourismProjectTravelNotes/contactCode\"}]");
+//        List<String> urlConvertList = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), tourismProjectVo.getContactCode());
+//        if(CollectionUtils.isNotEmpty(urlConvertList)) {
+//            tourismProjectVo.setContactCodeConvert(urlConvertList.get(0));
+//        }
+        if(tourismProjectVo != null) {
+            tourismProjectVo.setContactCodeConvert("https://v.xiaoyaotravel.com/image/ContactQRCode/tourism.png");
+        }
+        return tourismProjectVo;
+    }
+
+    @Override
+    public WebSiteProjectDatePriceVo view(String projectId) {
+
+        TourismDatePrice tourismDatePrice = new TourismDatePrice();
+        tourismDatePrice.setProjectId(projectId);
+        tourismDatePrice.setNowDate(new Date());
+
+        //查询进行排序
+        MyOrderParam myOrderParam = new MyOrderParam();
+        MyOrderParam.OrderInfo orderInfo = new MyOrderParam.OrderInfo();
+        orderInfo.setFieldName("departureDate");
+        orderInfo.setAsc(true);
+        myOrderParam.add(orderInfo);
+        String orderBy = MyOrderParam.buildOrderBy(myOrderParam, TourismDatePrice.class);
+        List<TourismDatePrice> tourismDatePriceList =
+                tourismDatePriceService.getTourismDatePriceList(tourismDatePrice, orderBy);
+//        List<TourismDatePrice> tourismDatePriceSortList = tourismDatePriceList.stream()
+//                .sorted(Comparator.comparing(TourismDatePrice::getDepartureDate))
+//                .collect(Collectors.toList());
+        List<TourismProjectDatePriceVo> tourismProjectDatePriceVos = MyModelUtil.copyCollectionTo(tourismDatePriceList, TourismProjectDatePriceVo.class);
+
+        DateRange dateRange = new DateRange();
+        dateRange.setStartDate(new Date().toInstant().atZone(ZoneId.systemDefault()).toLocalDate());
+        if(!CollectionUtils.isEmpty(tourismDatePriceList)) {
+            Optional<Date> max = tourismDatePriceList.stream()
+                    .map(TourismDatePrice::getDepartureDate)
+                    .max(Comparator.naturalOrder());
+            dateRange.setEndDate(max.get().toInstant().atZone(ZoneId.systemDefault()).toLocalDate());
+        }else {
+            dateRange.setEndDate(DateUtils.addDays(new Date(), 30).toInstant().atZone(ZoneId.systemDefault()).toLocalDate());
+        }
+//        //查询项目的默认价格
+//        TourismProject tourismProject = tourismProjectService.getById(projectId);
+//        if(tourismProject != null){
+//            tourismProjectDatePriceVos.stream().forEach(item->{
+//                item.setProjectPrice(tourismProject.getPrice().toString().concat(tourismProject.getPriceUnit()));
+//            });
+//        }
+        WebSiteProjectDatePriceVo webSiteProjectDatePriceVo = new WebSiteProjectDatePriceVo();
+        // 使用 Stream API 将列表转换为 Map
+        Map<LocalDate, TourismProjectDatePriceVo> map = tourismProjectDatePriceVos.stream()
+                .collect(Collectors.toMap(
+                        TourismProjectDatePriceVo::getDepartureDate, // key
+                        vo -> vo // value
+                ));
+        webSiteProjectDatePriceVo.setTourismProjectDatePriceVos(map);
+        webSiteProjectDatePriceVo.setDateRange(dateRange);
+        return webSiteProjectDatePriceVo;
+    }
+
+    @Override
+    public Boolean bookProject(HttpServletRequest request, TourismBookProjectDto tourBookInfoDto) {
+
+        //获取用户的手机号
+        Long userId = TokenData.takeFromRequest().getUserId();
+        TourUser tourUser = tourUserService.getById(userId);
+        String mobile = tourUser.getMobile();
+
+        RBucket<Object> bucket = redissonClient.getBucket(tourBookInfoDto.getString().concat(mobile));
+        if(bucket.get() != null){
+            return false;
+        }
+
+        if(tourBookInfoDto == null){
+            throw new RuntimeException("预定参数为空!");
+        }
+
+        //根据当前日期,查询当天的项目日历价格
+        TourismDatePrice tourismDatePrice = new TourismDatePrice();
+        tourismDatePrice.setProjectId(tourBookInfoDto.getProjectId());
+        tourismDatePrice.setDepartureDate(tourBookInfoDto.getStartDate());
+        TourismDatePrice tourismDatePriceOne = tourismDatePriceService.getOne(tourismDatePrice);
+
+
+        //构建预定的数据进行保存
+        TourBookInfo tourBookInfo = new TourBookInfo();
+        tourBookInfo.setType(tourBookInfoDto.getType());
+        tourBookInfo.setProjectId(Long.parseLong(tourBookInfoDto.getProjectId()));
+        tourBookInfo.setAdultNumber(tourBookInfoDto.getAdultNumber());
+        tourBookInfo.setChildrenNumber(tourBookInfoDto.getChildrenNumber());
+        tourBookInfo.setAdultPrice(tourismDatePriceOne.getAdultPrice());
+        tourBookInfo.setChildrenPrice(tourismDatePriceOne.getChildrenPrice());
+        tourBookInfo.setBookMobile(mobile);
+        tourBookInfo.setBookName(mobile);
+        tourBookInfo.setIsHandle("0");
+        tourBookInfo.setBookTime(tourBookInfoDto.getStartDate());
+        tourBookInfo.setTotalPrice( tourismDatePriceOne.getAdultPrice().multiply(BigDecimal.valueOf(tourBookInfoDto.getAdultNumber()))
+                .add(tourismDatePriceOne.getChildrenPrice().multiply(BigDecimal.valueOf(tourBookInfoDto.getChildrenNumber()))));
+        tourBookInfo.setBookIp(IpUtil.getRemoteIpAddress(request));
+        tourBookInfoService.saveNew(tourBookInfo);
+        bucket.set("预约成功!");
+        return true;
+    }
+}

+ 13 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/website/vo/DateRange.java

@@ -0,0 +1,13 @@
+package com.tourism.webadmin.app.website.vo;
+
+import lombok.Data;
+
+import java.time.LocalDate;
+
+@Data
+public class DateRange {
+
+    private LocalDate startDate;
+
+    private LocalDate endDate;
+}

+ 26 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/website/vo/DateRangePriceVo.java

@@ -0,0 +1,26 @@
+package com.tourism.webadmin.app.website.vo;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.Date;
+
+@Data
+public class DateRangePriceVo {
+
+    private LocalDate startDate;
+
+    private LocalDate endDate;
+
+    private BigDecimal adultPrice;
+
+    private BigDecimal childrenPrice;
+
+    public DateRangePriceVo(LocalDate startDate, LocalDate endDate, BigDecimal adultPrice, BigDecimal childrenPrice) {
+        this.startDate = startDate;
+        this.endDate = endDate;
+        this.adultPrice = adultPrice;
+        this.childrenPrice = childrenPrice;
+    }
+}

+ 19 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/website/vo/DateRangesPriceVo.java

@@ -0,0 +1,19 @@
+package com.tourism.webadmin.app.website.vo;
+
+import ch.qos.logback.core.joran.sanity.Pair;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.Date;
+import java.util.List;
+@Data
+public class DateRangesPriceVo {
+
+    private List<LocalDate> dateRange;
+
+    private BigDecimal adultPrice;
+
+    private BigDecimal childrenPrice;
+
+}

+ 28 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/website/vo/TourTravelNotesDirectoryCountryVo.java

@@ -0,0 +1,28 @@
+package com.tourism.webadmin.app.website.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+/**
+ * 门户网站中游记的二维菜单城市对象。
+ *
+ * @author 吃饭睡觉
+ * @date 2024-09-06
+ */
+@Schema(description = "TourTravelNotesDirectoryCountryVo视图对象")
+@Data
+public class TourTravelNotesDirectoryCountryVo {
+
+    /**
+     * 国家id。
+     */
+    @Schema(description = "国家id")
+    private String countryId;
+
+    /**
+     * 国家名称。
+     */
+    @Schema(description = "国家名称")
+    private String countryName;
+
+}

+ 37 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/website/vo/TourTravelNotesDirectoryVo.java

@@ -0,0 +1,37 @@
+package com.tourism.webadmin.app.website.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.List;
+
+/**
+ * 门户网站中游记的二维菜单地区对象。
+ *
+ * @author 吃饭睡觉
+ * @date 2024-10-12
+ */
+@Schema(description = "TourTravelNotesDirectoryVo视图对象")
+@Data
+public class TourTravelNotesDirectoryVo {
+
+    /**
+     * 地区id。
+     */
+    @Schema(description = "地区id")
+    private String areaId;
+
+    /**
+     * 地区名称。
+     */
+    @Schema(description = "地区名称")
+    private String areaName;
+
+    /**
+     * 城市。
+     */
+    @Schema(description = "城市")
+    private List<TourTravelNotesDirectoryCountryVo> children;
+
+}

+ 43 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/website/vo/TourismProjectDatePriceVo.java

@@ -0,0 +1,43 @@
+package com.tourism.webadmin.app.website.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.Date;
+
+@Schema(description = "TourismProjectDatePrice视图对象")
+@Data
+public class TourismProjectDatePriceVo {
+
+//    /**
+//     * id。
+//     */
+//    @Schema(description = "id")
+//    private Long id;
+
+    /**
+     * 出发日期。
+     */
+    @Schema(description = "出发日期")
+    private LocalDate departureDate;
+
+    /**
+     * 成人价格。
+     */
+    @Schema(description = "成人价格")
+    private BigDecimal adultPrice;
+
+    /**
+     * 儿童价格。
+     */
+    @Schema(description = "儿童价格")
+    private BigDecimal childrenPrice;
+
+//    /**
+//     * 项目价格。
+//     */
+//    @Schema(description = "项目价格")
+//    private String projectPrice;
+}

+ 27 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/website/vo/WebSiteCaptchaImageVo.java

@@ -0,0 +1,27 @@
+package com.tourism.webadmin.app.website.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+/**
+ * 门户网站中游记的二维菜单地区对象。
+ *
+ * @author 吃饭睡觉
+ * @date 2024-10-12
+ */
+@Schema(description = "TourTravelNotesDirectoryVo视图对象")
+@Data
+public class WebSiteCaptchaImageVo {
+
+    /**
+     * uuid。
+     */
+    @Schema(description = "uuid")
+    public String uuid;
+
+    /**
+     * 验证码img。
+     */
+    @Schema(description = "验证码img")
+    public String img;
+}

+ 16 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/website/vo/WebSiteProjectDatePriceVo.java

@@ -0,0 +1,16 @@
+package com.tourism.webadmin.app.website.vo;
+
+import lombok.Data;
+
+import java.time.LocalDate;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+@Data
+public class WebSiteProjectDatePriceVo {
+
+    private DateRange dateRange;
+
+    private Map<LocalDate, TourismProjectDatePriceVo> tourismProjectDatePriceVos;
+}

+ 328 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/controller/TakeOutProjectController.java

@@ -0,0 +1,328 @@
+package com.tourism.webadmin.app.wechat.controller;
+
+import cn.dev33.satoken.annotation.SaIgnore;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.github.pagehelper.page.PageMethod;
+import com.tourism.common.additional.utils.StringUtils;
+import com.tourism.common.additional.utils.UrlConvertUtils;
+import com.tourism.common.core.annotation.DisableDataFilter;
+import com.tourism.common.core.constant.ErrorCodeEnum;
+import com.tourism.common.core.object.MyOrderParam;
+import com.tourism.common.core.object.MyPageData;
+import com.tourism.common.core.object.ResponseResult;
+import com.tourism.common.core.util.MyModelUtil;
+import com.tourism.common.core.util.MyPageUtil;
+import com.tourism.common.log.annotation.OperationLog;
+import com.tourism.common.log.model.constant.SysOperationLogType;
+import com.tourism.webadmin.app.website.dto.*;
+import com.tourism.webadmin.app.wechat.dto.TourRestaurantCartChangeDto;
+import com.tourism.webadmin.app.wechat.model.WechatRestaurantCart;
+import com.tourism.webadmin.app.wechat.vo.shopfood.WechatRestaurantCartTotalVo;
+import com.tourism.webadmin.app.wechat.vo.shopfood.WechatRestaurantCartVo;
+import com.tourism.webadmin.back.dto.RestaurantCartDto;
+import com.tourism.webadmin.back.model.*;
+import com.tourism.webadmin.back.service.*;
+import com.tourism.webadmin.back.vo.*;
+import com.tourism.common.additional.config.ApplicationConfig;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.util.ObjectUtils;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * 旅游项目页面接口。
+ *
+ * @author chenchen
+ * @date 2024-09-26
+ */
+@Tag(name = "小程序美食板块接口")
+@Slf4j
+@RestController
+@RequestMapping("/wechat/foodProject")
+@DisableDataFilter
+public class TakeOutProjectController {
+
+    @Autowired
+    private RestaurantTypeService restaurantTypeService;
+    @Autowired
+    private RestaurantInfoService restaurantInfoService;
+    @Autowired
+    private RestaurantFoodTypeService restaurantFoodTypeService;
+    @Autowired
+    private RestaurantFoodInfoService restaurantFoodInfoService;
+    @Autowired
+    private RestaurantCartService restaurantCartService;
+    @Autowired
+    private ApplicationConfig applicationConfig;
+
+    /**
+     * 列出符合过滤条件的店铺分类列表。
+     * 接口:旅游页面的接口(searchString: 标题模糊查询;pageNum: 页码;pageSize: 每页大小;count: true)
+     * @return 应答结果对象,包含查询结果集。
+     */
+    @SaIgnore
+    @OperationLog(type = SysOperationLogType.LIST)
+    @GetMapping("/restaurantTypeList")
+    public ResponseResult<MyPageData<RestaurantTypeVo>> restaurantTypeList(TourismRestaurantTypeToWebDto tourismRestaurantTypeToWebDto){
+
+        //小程序看到的为启用的餐馆
+        tourismRestaurantTypeToWebDto.setEnable(1);
+        if (tourismRestaurantTypeToWebDto.getPageNum() != null && tourismRestaurantTypeToWebDto.getPageSize() != null) {
+            PageMethod.startPage(tourismRestaurantTypeToWebDto.getPageNum(), tourismRestaurantTypeToWebDto.getPageSize(), true);
+        }
+        RestaurantType restaurantType = MyModelUtil.copyTo(tourismRestaurantTypeToWebDto, RestaurantType.class);
+//        String orderBy = MyOrderParam.buildOrderBy(tourismRestaurantTypeToWebDto.getOrderParamList(), RestaurantType.class);
+        List<RestaurantType> restaurantFoodTypeList =
+                restaurantTypeService.getRestaurantTypeListWithRelation(restaurantType, "tour_restaurant_type.show_order");
+
+
+        MyPageData<RestaurantTypeVo> restaurantTypeVoMyPageData = MyPageUtil.makeResponseData(restaurantFoodTypeList, RestaurantTypeVo.class);
+
+
+        List<RestaurantTypeVo> dataList = restaurantTypeVoMyPageData.getDataList();
+        dataList.stream().forEach(item ->
+        {
+            if(StringUtils.isNotEmpty(item.getUrl())) {
+                List<String> arrayList2 = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), item.getUrl());
+                item.setUrlAfterConvert(arrayList2);
+            }
+        });
+
+        return ResponseResult.success(restaurantTypeVoMyPageData);
+    }
+
+
+    /**
+     * 列出符合过滤条件的店铺列表。
+     * 接口:旅游页面的接口(searchString: 标题模糊查询;pageNum: 页码;pageSize: 每页大小;count: true)
+     * @return 应答结果对象,包含查询结果集。
+     */
+    @SaIgnore
+    @OperationLog(type = SysOperationLogType.LIST)
+    @GetMapping("/restaurantInfoList")
+    public ResponseResult<MyPageData<RestaurantInfoVo>>        restaurantInfoList(TourismRestaurantInfoToWebDto tourismRestaurantInfoToWebDto){
+
+        //小程序看到的为启用的店铺
+        tourismRestaurantInfoToWebDto.setEnable(1);
+        if (tourismRestaurantInfoToWebDto.getPageNum() != null && tourismRestaurantInfoToWebDto.getPageSize() != null) {
+            PageMethod.startPage(tourismRestaurantInfoToWebDto.getPageNum(), tourismRestaurantInfoToWebDto.getPageSize(), true);
+        }
+        RestaurantInfo restaurantInfo = MyModelUtil.copyTo(tourismRestaurantInfoToWebDto, RestaurantInfo.class);
+        String orderBy = MyOrderParam.buildOrderBy(tourismRestaurantInfoToWebDto.getOrderParamList(), RestaurantInfo.class);
+        List<RestaurantInfo> restaurantInfoList =
+                restaurantInfoService.getRestaurantInfoList(restaurantInfo, orderBy);
+        MyPageData<RestaurantInfoVo> restaurantInfoVoMyPageData = MyPageUtil.makeResponseData(restaurantInfoList, RestaurantInfoVo.class);
+        List<RestaurantInfoVo> dataList = restaurantInfoVoMyPageData.getDataList();
+        dataList.stream().forEach(item ->
+        {
+            if(StringUtils.isNotEmpty(item.getUrl())) {
+                List<String> arrayList2 = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), item.getUrl());
+                item.setUrlAfterConvert(arrayList2);
+            }
+            if(StringUtils.isNotEmpty(item.getEnvironmentImage())) {
+                List<String> arrayList2 = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), item.getEnvironmentImage());
+                item.setEnvironmentImageAfterConvert(arrayList2);
+            }
+        });
+        return ResponseResult.success(restaurantInfoVoMyPageData);
+    }
+
+    /**
+     * 列出符合过滤条件的美食类型列表。
+     * 接口:旅游页面的接口(searchString: 标题模糊查询;pageNum: 页码;pageSize: 每页大小;count: true)
+     * @return 应答结果对象,包含查询结果集。
+     */
+    @SaIgnore
+    @OperationLog(type = SysOperationLogType.LIST)
+    @GetMapping("/restaurantFoodTypeList")
+    public ResponseResult<MyPageData<RestaurantFoodTypeVo>> restaurantFoodTypeList(TourismRestaurantFoodTypeToWebDto tourismRestaurantFoodTypeToWebDto){
+
+        //小程序看到的为启用的美食类型列表
+        tourismRestaurantFoodTypeToWebDto.setEnable(1);
+        if (tourismRestaurantFoodTypeToWebDto.getPageNum() != null && tourismRestaurantFoodTypeToWebDto.getPageSize() != null) {
+            PageMethod.startPage(tourismRestaurantFoodTypeToWebDto.getPageNum(), tourismRestaurantFoodTypeToWebDto.getPageSize(), true);
+        }
+        RestaurantFoodType restaurantFoodType = MyModelUtil.copyTo(tourismRestaurantFoodTypeToWebDto, RestaurantFoodType.class);
+        String orderBy = MyOrderParam.buildOrderBy(tourismRestaurantFoodTypeToWebDto.getOrderParamList(), RestaurantFoodType.class);
+        List<RestaurantFoodType> restaurantFoodTypeList =
+                restaurantFoodTypeService.getRestaurantFoodTypeList(restaurantFoodType, orderBy);
+        return ResponseResult.success(MyPageUtil.makeResponseData(restaurantFoodTypeList, RestaurantFoodTypeVo.class));
+    }
+
+
+    /**
+     * 列出符合过滤条件的美食列表。
+     * 接口:旅游页面的接口(searchString: 标题模糊查询;pageNum: 页码;pageSize: 每页大小;count: true)
+     * @return 应答结果对象,包含查询结果集。
+     */
+    @SaIgnore
+    @OperationLog(type = SysOperationLogType.LIST)
+    @GetMapping("/restaurantFoodList")
+    public ResponseResult<MyPageData<RestaurantFoodInfoVo>> restaurantFoodList(TourismRestaurantFoodInfoToWebDto tourismRestaurantFoodInfoToWebDto){
+
+        //小程序看到的为启用的美食列表
+        tourismRestaurantFoodInfoToWebDto.setEnable(1);
+        if (tourismRestaurantFoodInfoToWebDto.getPageNum() != null && tourismRestaurantFoodInfoToWebDto.getPageSize() != null) {
+            PageMethod.startPage(tourismRestaurantFoodInfoToWebDto.getPageNum(), tourismRestaurantFoodInfoToWebDto.getPageSize(), true);
+        }
+        RestaurantFoodInfo restaurantFoodinfo = MyModelUtil.copyTo(tourismRestaurantFoodInfoToWebDto, RestaurantFoodInfo.class);
+        String orderBy = MyOrderParam.buildOrderBy(tourismRestaurantFoodInfoToWebDto.getOrderParamList(), RestaurantFoodInfo.class);
+        List<RestaurantFoodInfo> restaurantFoodList =
+                restaurantFoodInfoService.getRestaurantFoodInfoList(restaurantFoodinfo, orderBy);
+
+        MyPageData<RestaurantFoodInfoVo> restaurantInfoVoMyPageData = MyPageUtil.makeResponseData(restaurantFoodList, RestaurantFoodInfoVo.class);
+
+
+        List<RestaurantFoodInfoVo> dataList = restaurantInfoVoMyPageData.getDataList();
+        dataList.stream().forEach(item ->
+        {
+            if(StringUtils.isNotEmpty(item.getUrl())) {
+                List<String> arrayList2 = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), item.getUrl());
+                item.setUrlAfterConvert(arrayList2);
+            }
+        });
+
+        return ResponseResult.success(restaurantInfoVoMyPageData);
+    }
+
+
+    /**
+     * 店铺详情。
+     * 接口:店铺详情接口(id)
+     * @return 应答结果对象,包含查询结果集。
+     */
+    @SaIgnore
+    @OperationLog(type = SysOperationLogType.LIST)
+    @GetMapping("/restaurantInfoById")
+    public ResponseResult<RestaurantInfoVo> restaurantInfoById(@RequestParam(required = true) String id){
+
+        //小程序看到的为启用的店铺
+
+//        RestaurantInfo filter = new RestaurantInfo();
+//        filter.setId(id);
+//        filter.setEnable(1);
+        QueryWrapper queryWrapper = new QueryWrapper();
+        queryWrapper.eq("id",id);
+        queryWrapper.eq("enable",1);
+        RestaurantInfo restaurantInfo = restaurantInfoService.getOne(queryWrapper);
+        if (restaurantInfo == null) {
+            return ResponseResult.success(null);
+        }
+        RestaurantInfoVo restaurantInfoVo = MyModelUtil.copyTo(restaurantInfo, RestaurantInfoVo.class);
+        if(StringUtils.isNotEmpty(restaurantInfoVo.getUrl())) {
+            List<String> arrayList2 = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), restaurantInfoVo.getUrl());
+            restaurantInfoVo.setUrlAfterConvert(arrayList2);
+        }
+        if(StringUtils.isNotEmpty(restaurantInfoVo.getEnvironmentImage())) {
+            List<String> arrayList2 = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), restaurantInfoVo.getEnvironmentImage());
+            restaurantInfoVo.setEnvironmentImageAfterConvert(arrayList2);
+        }
+        if(StringUtils.isNotEmpty(restaurantInfoVo.getRestaurantBanner())) {
+            List<String> arrayList2 = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), restaurantInfoVo.getRestaurantBanner());
+            restaurantInfoVo.setRestaurantBannerAfterConvert(arrayList2);
+        }
+        return ResponseResult.success(restaurantInfoVo);
+    }
+
+
+    /**
+     * 列出符合过滤条件的购物车列表。
+     * restaurantId必传
+     * 接口:购物车的接口(restaurantCartDto)
+     * @return 应答结果对象,包含查询结果集。
+     */
+    @OperationLog(type = SysOperationLogType.LIST)
+    @GetMapping("/foodCartList")
+    public ResponseResult<WechatRestaurantCartTotalVo> foodCartList(RestaurantCartDto restaurantCartDto){
+
+        if(restaurantCartDto.getRestaurantId() == null){
+            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST,"数据验证失败,餐馆id不能为空!");
+        }
+//        TokenData tokenData = TokenData.takeFromRequest();
+//        restaurantCartDto.setUserId(tokenData.getUserId());
+        restaurantCartDto.setUserId(1L);
+        WechatRestaurantCart wechatRestaurantCart = MyModelUtil.copyTo(restaurantCartDto, WechatRestaurantCart.class);
+        String orderBy = MyOrderParam.buildOrderBy(null, RestaurantCart.class);
+        //获取该用户在该餐馆的购物车记录
+        List<WechatRestaurantCart> wechatRestaurantCartList = restaurantCartService.getRestaurantCartListAndFoodDetail(wechatRestaurantCart, orderBy);
+        List<WechatRestaurantCartVo> wechatRestaurantCartVo = MyModelUtil.copyCollectionTo(wechatRestaurantCartList, WechatRestaurantCartVo.class);
+
+        BigDecimal totalPrice = BigDecimal.ZERO;
+        wechatRestaurantCartVo.stream().forEach(item->{
+            BigDecimal decimalCount = BigDecimal.valueOf(item.getCount());
+            totalPrice.add(item.getPrice().multiply(decimalCount));
+        });
+        WechatRestaurantCartTotalVo wechatRestaurantCartTotalVo = new WechatRestaurantCartTotalVo();
+        wechatRestaurantCartTotalVo.setTotalPirce(totalPrice);
+        wechatRestaurantCartTotalVo.setWechatRestaurantCartVoList(wechatRestaurantCartVo);
+        return ResponseResult.success(wechatRestaurantCartTotalVo);
+    }
+
+
+    /**
+     * 购物车数量变化。
+     * restaurantFoodId必传
+     * 接口:购物车的接口(restaurantCartDto)
+     * @return 应答结果对象,包含查询结果集。
+     */
+    @OperationLog(type = SysOperationLogType.UPDATE)
+    @GetMapping("/foodCartChange")
+    public ResponseResult foodCartChange(@Valid TourRestaurantCartChangeDto restaurantCartChangeDto){
+        //不允许传负数
+        if(restaurantCartChangeDto.getCount()<0){
+            return ResponseResult.error(ErrorCodeEnum.UPLOAD_FAILED);
+        }
+        //新增,则查询是否有购物车记录;没有的话,则新增;有的话,则修改+1
+        RestaurantCart filter = new RestaurantCart();
+//            TokenData tokenData = TokenData.takeFromRequest();
+//            filter.setUserId(tokenData.getUserId());
+            filter.setUserId(1L);
+            filter.setRestaurantFoodId(restaurantCartChangeDto.getRestaurantFoodId());
+            RestaurantCart restaurantCart = restaurantCartService.getOne(filter);
+            //能查到user和菜品的购物车记录
+            if(!ObjectUtils.isEmpty(restaurantCart)){
+                //如果传递的数量是0的话,则应该删除该条购物车记录
+                if(restaurantCartChangeDto.getCount() == 0) {
+                    boolean remove = restaurantCartService.remove(restaurantCart.getId());
+                    //删除失败
+                    if(!remove){
+                        return ResponseResult.error(ErrorCodeEnum.DATA_SAVE_FAILED);
+                    }else {
+                        return ResponseResult.success();
+                    }
+                }else {
+                    //如果传递的数量不是0的话,则应该更新记录
+                    restaurantCart.setCount(restaurantCartChangeDto.getCount());
+                    boolean remove = restaurantCartService.updateById(restaurantCart);
+                    //更新失败
+                    if(!remove){
+                        return ResponseResult.error(ErrorCodeEnum.DATA_SAVE_FAILED);
+                    }else {
+                        return ResponseResult.success();
+                    }
+                }
+            }else {
+                //当查不到购物车记录时,则表明为新增
+                RestaurantCart restaurantCart1 = new RestaurantCart();
+//                restaurantCart1.setUserId(tokenData.getUserId());
+                restaurantCart1.setUserId(1L);
+                restaurantCart1.setRestaurantFoodId(restaurantCartChangeDto.getRestaurantFoodId());
+                restaurantCart1.setRestaurantId(restaurantCartChangeDto.getRestaurantId());
+                restaurantCart1.setCount(restaurantCartChangeDto.getCount());
+                RestaurantCart restaurantCart2 = restaurantCartService.saveNew(restaurantCart1);
+                //更新失败
+                if(ObjectUtils.isEmpty(restaurantCart2)){
+                    return ResponseResult.error(ErrorCodeEnum.DATA_SAVE_FAILED);
+                }else {
+                    return ResponseResult.success();
+                }
+            }
+    }
+}

+ 144 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/controller/TourProjectController.java

@@ -0,0 +1,144 @@
+package com.tourism.webadmin.app.wechat.controller;
+
+import cn.dev33.satoken.annotation.SaIgnore;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.github.pagehelper.page.PageMethod;
+import com.tourism.common.additional.utils.MapConvertUtils;
+import com.tourism.common.additional.utils.StringUtils;
+import com.tourism.common.additional.utils.UrlConvertUtils;
+import com.tourism.common.core.annotation.MyRequestBody;
+import com.tourism.common.core.constant.ErrorCodeEnum;
+import com.tourism.common.core.object.MyOrderParam;
+import com.tourism.common.core.object.MyPageData;
+import com.tourism.common.core.object.ResponseResult;
+import com.tourism.common.core.util.MyModelUtil;
+import com.tourism.common.core.util.MyPageUtil;
+import com.tourism.common.log.annotation.OperationLog;
+import com.tourism.common.log.model.constant.SysOperationLogType;
+import com.tourism.webadmin.app.website.dto.TourismProjectToWebDto;
+import com.tourism.webadmin.app.website.service.LoginToWebsiteService;
+import com.tourism.webadmin.back.dto.TourBookInfoDto;
+import com.tourism.webadmin.back.model.DirectoryInfo;
+import com.tourism.webadmin.back.model.TourismFile;
+import com.tourism.webadmin.back.model.TourismProject;
+import com.tourism.webadmin.back.service.TourismProjectService;
+import com.tourism.webadmin.back.vo.TourismProjectVo;
+import com.tourism.common.additional.config.ApplicationConfig;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.servlet.http.HttpServletRequest;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+import static java.util.stream.Collectors.toList;
+
+/**
+ * 旅游项目页面接口。
+ *
+ * @author chenchen
+ * @date 2024-09-26
+ */
+@Tag(name = "小程序旅游板块接口")
+@Slf4j
+@RestController
+@RequestMapping("/wechat/tourProject")
+public class TourProjectController {
+    @Autowired
+    private ApplicationConfig applicationConfig;
+    @Autowired
+    private TourismProjectService tourismProjectService;
+    @Autowired
+    private LoginToWebsiteService loginToWebsiteService;
+    /**
+     * 列出符合过滤条件的旅游项目列表。
+     * 接口:旅游页面的接口(searchString: 标题模糊查询;pageNum: 页码;pageSize: 每页大小;count: true)
+     * @return 应答结果对象,包含查询结果集。
+     */
+    @OperationLog(type = SysOperationLogType.LIST)
+    @GetMapping("/projectList")
+    @SaIgnore
+    public ResponseResult<MyPageData<TourismProjectVo>> projectList(TourismProjectToWebDto tourismProjectDtoFilter){
+//        if(tourismProjectDtoFilter.getBelongTab() == null){
+//            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST,"所属分类(belongTab)不能为空!");
+//        }
+        //如果belongTab小于1000并且大于10.则表明查询的是一级菜单
+        if(tourismProjectDtoFilter.getBelongTab() !=null && tourismProjectDtoFilter.getBelongTab()>=10 && tourismProjectDtoFilter.getBelongTab()<1000){
+            DirectoryInfo filter = new DirectoryInfo();
+            filter.setParentId(tourismProjectDtoFilter.getBelongTab());
+            filter.setEnable(1);
+            List<DirectoryInfo> directoryInfoList = tourismProjectService.getDirectoryInfoList(filter, null);
+            tourismProjectDtoFilter.setBelongTab(null);
+            tourismProjectDtoFilter.setDirectoryInfoIds(directoryInfoList.stream().map(DirectoryInfo::getId).collect(toList()));
+        }
+        TourismProject tourismProjectFilter = MyModelUtil.copyTo(tourismProjectDtoFilter, TourismProject.class);
+        //首页展示的为启用的内容
+        tourismProjectFilter.setEnable(1);
+        String orderBy = MyOrderParam.buildOrderBy(tourismProjectDtoFilter.getOrderParamList(), TourismProject.class);
+        if (tourismProjectDtoFilter.getPageNum() != null && tourismProjectDtoFilter.getPageSize() != null && tourismProjectDtoFilter.getCount() != null) {
+            PageMethod.startPage(tourismProjectDtoFilter.getPageNum(), tourismProjectDtoFilter.getPageSize(), tourismProjectDtoFilter.getCount());
+        }
+        List<TourismProject> tourismProjectList =
+                tourismProjectService.getTourismProjectListWithRelation(tourismProjectFilter, orderBy);
+        MyPageData<TourismProjectVo> tourismProjectVoMyPageData = MyPageUtil.makeResponseData(tourismProjectList, TourismProjectVo.class);
+
+        List<TourismProjectVo> dataList = tourismProjectVoMyPageData.getDataList();
+        //先把imgUrl由jaon转换为List<FileUrlObject>
+        dataList.stream().forEach(item ->
+        {
+            List<String> arrayList2 = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), item.getHomeHotPicture());
+            item.setHomeHotPicturesAfterConvert(arrayList2);
+            List<String> arrayList3 = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), item.getTourismUrl());
+            item.setTourismUrlsAfterConvert(arrayList3);
+        });
+        return ResponseResult.success(tourismProjectVoMyPageData);
+    }
+
+    /**
+     * 列出符合过滤条件的旅游项目列表。
+     * 接口:旅游页面的接口(searchString: 标题模糊查询;pageNum: 页码;pageSize: 每页大小;count: true)
+     * @return 应答结果对象,包含查询结果集。
+     */
+    @GetMapping("/detail")
+    @SaIgnore
+    public ResponseResult<TourismProjectVo> detail(@RequestParam Long id){
+        TourismProject tourismProject = tourismProjectService.getById(id);
+        if (tourismProject == null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
+        }
+        TourismProjectVo tourismProjectVo = MyModelUtil.copyTo(tourismProject, TourismProjectVo.class);
+        List<String> arrayList3 = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), tourismProjectVo.getTourismUrl());
+        tourismProjectVo.setTourismUrlsAfterConvert(arrayList3);
+        List<String> arrayList = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), tourismProjectVo.getHomeHotPicture());
+        tourismProjectVo.setHomeHotPicturesAfterConvert(arrayList);
+        if(tourismProjectVo.getTourismFile() != null){
+            // 使用 Jackson 的 ObjectMapper 进行转换
+            ObjectMapper objectMapper = new ObjectMapper();
+            TourismFile tourismFile = objectMapper.convertValue(tourismProjectVo.getTourismFile(), TourismFile.class);
+            if(StringUtils.isNotEmpty(tourismFile.getFileUrl())){
+                List<String> arrayList1 = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), tourismFile.getFileUrl());
+                tourismFile.setFileUrlsAfterConvert(arrayList1);
+                tourismProjectVo.setTourismFile(MapConvertUtils.convertObjectToMap(tourismFile));
+            }
+        }
+        return ResponseResult.success(tourismProjectVo);
+    }
+
+    /**
+     * 小程序保存报名信息
+     */
+    @SaIgnore
+    @Transactional(rollbackFor = Exception.class)
+    @PostMapping("/saveBookInfo")
+    public ResponseResult<Void> saveBookInfo(HttpServletRequest request,
+                                             @MyRequestBody TourBookInfoDto tourBookInfoDto){
+        boolean isSave =
+                loginToWebsiteService.saveBookInfo(request, tourBookInfoDto);
+        if(!isSave){
+            return ResponseResult.error(ErrorCodeEnum.DATA_SAVE_FAILED);
+        }
+        return ResponseResult.success();
+    }
+}

+ 218 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/controller/WechatDeliveryAddressController.java

@@ -0,0 +1,218 @@
+package com.tourism.webadmin.app.wechat.controller;
+
+import cn.dev33.satoken.annotation.SaIgnore;
+import com.github.pagehelper.page.PageMethod;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import com.tourism.common.core.annotation.DisableDataFilter;
+import com.tourism.common.core.annotation.MyRequestBody;
+import com.tourism.common.core.constant.ErrorCodeEnum;
+import com.tourism.common.core.object.MyPageData;
+import com.tourism.common.core.object.MyRelationParam;
+import com.tourism.common.core.object.ResponseResult;
+import com.tourism.common.core.object.TokenData;
+import com.tourism.common.core.util.MyCommonUtil;
+import com.tourism.common.core.util.MyModelUtil;
+import com.tourism.common.core.util.MyPageUtil;
+import com.tourism.common.log.annotation.OperationLog;
+import com.tourism.common.log.model.constant.SysOperationLogType;
+import com.tourism.webadmin.app.wechat.dto.DeliveryDefaultAddressDto;
+import com.tourism.webadmin.back.dto.DeliveryAddressDto;
+import com.tourism.webadmin.back.dto.DeliveryAddressWebDto;
+import com.tourism.webadmin.back.model.DeliveryAddress;
+import com.tourism.webadmin.back.service.DeliveryAddressService;
+import com.tourism.webadmin.back.vo.DeliveryAddressVo;
+import com.tourism.common.additional.config.ApplicationConfig;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@Tag(name = "外卖地址管理管理接口")
+@Slf4j
+@RestController
+@RequestMapping("/wechat/delivery/address")
+public class WechatDeliveryAddressController {
+    @Autowired
+    private ApplicationConfig appConfig;
+    @Autowired
+    private DeliveryAddressService deliveryAddressService;
+
+    /**
+     * 新增外卖地址管理数据。
+     *
+     * @param deliveryAddressDto 新增对象。
+     * @return 应答结果对象,包含新增对象主键Id。
+     */
+    @ApiOperationSupport(ignoreParameters = {"deliveryAddressDto.id"})
+//    @SaCheckPermission("deliveryAddress.add")
+    @OperationLog(type = SysOperationLogType.ADD)
+    @PostMapping("/add")
+    public ResponseResult<String> add(@RequestBody DeliveryAddressDto deliveryAddressDto) {
+        String errorMessage = MyCommonUtil.getModelValidationError(deliveryAddressDto, false);
+        if (errorMessage != null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
+        }
+        DeliveryAddress deliveryAddress = MyModelUtil.copyTo(deliveryAddressDto, DeliveryAddress.class);
+        deliveryAddress = deliveryAddressService.saveNew(deliveryAddress);
+        return ResponseResult.success(deliveryAddress.getId());
+    }
+
+    /**
+     * 更新外卖地址管理数据。
+     *
+     * @param deliveryAddressDto 更新对象。
+     * @return 应答结果对象。
+     */
+//    @SaCheckPermission("deliveryAddress.update")
+    @OperationLog(type = SysOperationLogType.UPDATE)
+    @PostMapping("/update")
+    @DisableDataFilter
+    public ResponseResult<Void> update(@RequestBody DeliveryAddressDto deliveryAddressDto) {
+        String errorMessage = MyCommonUtil.getModelValidationError(deliveryAddressDto, true);
+        if (errorMessage != null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
+        }
+        DeliveryAddress deliveryAddress = MyModelUtil.copyTo(deliveryAddressDto, DeliveryAddress.class);
+        DeliveryAddress originalDeliveryAddress = deliveryAddressService.getById(deliveryAddress.getId());
+        if (originalDeliveryAddress == null) {
+            // NOTE: 修改下面方括号中的话述
+            errorMessage = "数据验证失败,当前 [数据] 并不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        if (!deliveryAddressService.update(deliveryAddress, originalDeliveryAddress)) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
+        }
+        return ResponseResult.success();
+    }
+
+    /**
+     * 删除外卖地址管理数据。
+     *
+     * @param id 删除对象主键Id。
+     * @return 应答结果对象。
+     */
+//    @SaCheckPermission("deliveryAddress.delete")
+    @OperationLog(type = SysOperationLogType.DELETE)
+    @PostMapping("/delete")
+    @DisableDataFilter
+    public ResponseResult<Void> delete(@MyRequestBody String id) {
+        if (MyCommonUtil.existBlankArgument(id)) {
+            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
+        }
+        return this.doDelete(id);
+    }
+
+    /**
+     * 批量删除外卖地址管理数据。
+     *
+     * @param idList 待删除对象的主键Id列表。
+     * @return 应答结果对象。
+     */
+//    @SaCheckPermission("deliveryAddress.delete")
+    @OperationLog(type = SysOperationLogType.DELETE_BATCH)
+    @PostMapping("/deleteBatch")
+    @DisableDataFilter
+    public ResponseResult<Void> deleteBatch(@MyRequestBody List<String> idList) {
+        if (MyCommonUtil.existBlankArgument(idList)) {
+            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
+        }
+        for (String id : idList) {
+            ResponseResult<Void> responseResult = this.doDelete(id);
+            if (!responseResult.isSuccess()) {
+                return responseResult;
+            }
+        }
+        return ResponseResult.success();
+    }
+
+    /**
+     * 列出符合过滤条件的外卖地址管理列表。
+     *
+     * @param deliveryAddressDtoFilter 过滤对象。
+     * @return 应答结果对象,包含查询结果集。
+     */
+//    @SaCheckPermission("deliveryAddress.view")
+    @GetMapping("/list")
+    public ResponseResult<MyPageData<DeliveryAddressVo>> list(
+            @Valid @ModelAttribute DeliveryAddressWebDto deliveryAddressDtoFilter) {
+        if (deliveryAddressDtoFilter != null) {
+            PageMethod.startPage(deliveryAddressDtoFilter.getPageNum(), deliveryAddressDtoFilter.getPageSize(), true);
+        }
+        DeliveryAddress deliveryAddressFilter = MyModelUtil.copyTo(deliveryAddressDtoFilter, DeliveryAddress.class);
+        Long userId = TokenData.takeFromRequest().getUserId();
+        deliveryAddressFilter.setCreateUserId(userId);
+//        String orderBy = MyOrderParam.buildOrderBy(orderParam, DeliveryAddress.class);
+        List<DeliveryAddress> deliveryAddressList =
+                deliveryAddressService.getDeliveryAddressListWithRelation(deliveryAddressFilter, "create_time");
+//        deliveryAddressService.maskFieldDataList(deliveryAddressList, null);
+        return ResponseResult.success(MyPageUtil.makeResponseData(deliveryAddressList, DeliveryAddressVo.class));
+    }
+
+    /**
+     * 查看指定外卖地址管理对象详情。
+     *
+     * @param id 指定对象主键Id。
+     * @return 应答结果对象,包含对象详情。
+     */
+//    @SaCheckPermission("deliveryAddress.view")
+    @GetMapping("/view")
+    public ResponseResult<DeliveryAddressVo> view(@RequestParam String id) {
+        DeliveryAddress deliveryAddress = deliveryAddressService.getByIdWithRelation(id, MyRelationParam.full());
+        if (deliveryAddress == null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
+        }
+        deliveryAddressService.maskFieldData(deliveryAddress, null);
+        DeliveryAddressVo deliveryAddressVo = MyModelUtil.copyTo(deliveryAddress, DeliveryAddressVo.class);
+        return ResponseResult.success(deliveryAddressVo);
+    }
+
+    private ResponseResult<Void> doDelete(String id) {
+        String errorMessage;
+        // 验证关联Id的数据合法性
+        DeliveryAddress originalDeliveryAddress = deliveryAddressService.getById(id);
+        if (originalDeliveryAddress == null) {
+            // NOTE: 修改下面方括号中的话述
+            errorMessage = "数据验证失败,当前 [对象] 并不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        if (!deliveryAddressService.remove(id)) {
+            errorMessage = "数据操作失败,删除的对象不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        return ResponseResult.success();
+    }
+
+    /**
+     * 更新默认地址。
+     *
+     * @param deliveryDefaultAddressDto 更新对象。
+     * @return 应答结果对象。
+     */
+//    @SaCheckPermission("deliveryAddress.update")
+    @OperationLog(type = SysOperationLogType.UPDATE)
+    @SaIgnore
+    @PostMapping("/updateIsDefaultAddress")
+    @DisableDataFilter
+    public ResponseResult<Void> updateIsDefaultAddress(@RequestBody DeliveryDefaultAddressDto deliveryDefaultAddressDto) {
+
+        DeliveryAddress address = deliveryAddressService.getById(deliveryDefaultAddressDto.getId());
+        if (address==null) return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
+
+        DeliveryAddress filter = new DeliveryAddress();
+        filter.setIsDefault(deliveryDefaultAddressDto.getIsDefault());
+        filter.setDataState(1);
+        List<DeliveryAddress> list = deliveryAddressService.getDeliveryAddressList(filter, null);
+        for (DeliveryAddress adr : list) {
+            DeliveryAddress adrNew = adr;
+            adrNew.setIsDefault(deliveryDefaultAddressDto.getIsDefault()==1?2:1);
+            deliveryAddressService.update(adrNew,adr);
+        }
+        DeliveryAddress addressNew = address;
+        addressNew.setIsDefault(deliveryDefaultAddressDto.getIsDefault());
+        deliveryAddressService.update(addressNew,address);
+        return ResponseResult.success();
+    }
+}

+ 188 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/controller/WechatDeliveryOrderController.java

@@ -0,0 +1,188 @@
+package com.tourism.webadmin.app.wechat.controller;
+
+import cn.dev33.satoken.annotation.SaIgnore;
+import cn.hutool.core.lang.Snowflake;
+import com.alibaba.fastjson.JSONObject;
+import com.github.pagehelper.page.PageMethod;
+import com.tourism.common.additional.utils.StringUtils;
+import com.tourism.common.core.annotation.DisableDataFilter;
+import com.tourism.common.core.annotation.MyRequestBody;
+import com.tourism.common.core.constant.ErrorCodeEnum;
+import com.tourism.common.core.object.*;
+import com.tourism.common.core.util.MathCalculateUtils;
+import com.tourism.common.core.util.MyCommonUtil;
+import com.tourism.common.core.util.MyModelUtil;
+import com.tourism.common.core.util.MyPageUtil;
+import com.tourism.webadmin.back.dto.DeliveryOrderDto;
+import com.tourism.webadmin.back.dto.DeliveryOrderItemsDto;
+import com.tourism.webadmin.back.dto.DeliveryOrderWebDto;
+import com.tourism.webadmin.back.model.DeliveryOrder;
+import com.tourism.webadmin.back.model.DeliveryOrderItems;
+import com.tourism.webadmin.back.service.DeliveryOrderItemsService;
+import com.tourism.webadmin.back.service.DeliveryOrderService;
+import com.tourism.webadmin.back.vo.DeliveryOrderItemsVo;
+import com.tourism.webadmin.back.vo.DeliveryOrderVo;
+import com.tourism.common.additional.config.ApplicationConfig;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.Ignore;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+@Tag(name = "外卖订单接口")
+@Slf4j
+@RestController
+@RequestMapping("/wechat/delivery/order")
+//@SaIgnore
+public class WechatDeliveryOrderController {
+
+    @Autowired
+    private ApplicationConfig appConfig;
+    @Autowired
+    private DeliveryOrderService deliveryOrderService;
+    @Autowired
+    private DeliveryOrderItemsService deliveryOrderItemsService;
+    /**
+     * 创建订单
+     */
+//    @SaIgnore
+    @PostMapping("/createOrder")
+    public ResponseResult<String> createOrder(@MyRequestBody DeliveryOrderDto deliveryOrderDto,
+                              @MyRequestBody List<DeliveryOrderItemsDto> deliveryOrderItemsDtoList) {
+        ResponseResult<Tuple2<DeliveryOrder, JSONObject>> verifyResult =
+                this.doBusinessDataVerifyAndConvert(deliveryOrderDto, false, deliveryOrderItemsDtoList);
+        if (!verifyResult.isSuccess()) {
+            return ResponseResult.errorFrom(verifyResult);
+        }
+        Tuple2<DeliveryOrder, JSONObject> bizData = verifyResult.getData();
+        DeliveryOrder deliveryOrder = bizData.getFirst();
+        deliveryOrder.setOrderNo(deliveryOrder.getId());
+        deliveryOrder = deliveryOrderService.saveNewWithRelation(deliveryOrder, bizData.getSecond());
+        return ResponseResult.success(deliveryOrder.getId().toString());
+    }
+
+    /**
+     * 订单列表
+     */
+//    @SaIgnore
+    @GetMapping("/orderList")
+    @DisableDataFilter
+    public ResponseResult<MyPageData<DeliveryOrderVo>> orderList(DeliveryOrderWebDto deliveryOrderDtoFilter) {
+        if (deliveryOrderDtoFilter != null) {
+            PageMethod.startPage(deliveryOrderDtoFilter.getPageNum(), deliveryOrderDtoFilter.getPageSize(), true);
+        }
+        DeliveryOrder deliveryOrderFilter = MyModelUtil.copyTo(deliveryOrderDtoFilter, DeliveryOrder.class);
+        Long userId = TokenData.takeFromRequest().getUserId();
+        deliveryOrderFilter.setCreateUserId(userId);
+//        String orderBy = MyOrderParam.buildOrderBy(orderParam, DeliveryOrder.class);
+        List<DeliveryOrder> deliveryOrderList =
+                deliveryOrderService.getDeliveryOrderListWithRelation(deliveryOrderFilter, "create_time desc");
+        deliveryOrderService.maskFieldDataList(deliveryOrderList, null);
+
+        //查询订单详情
+        if (!deliveryOrderList.isEmpty()) {
+            Set<String> orderIds = deliveryOrderList.stream().map(e -> e.getId()).collect(Collectors.toSet());
+            List<DeliveryOrderItems> items = deliveryOrderItemsService.getDeliveryOrderItemsVoList(orderIds);
+            List<DeliveryOrderItemsVo> deliveryOrderItemsVos = MyModelUtil.copyCollectionTo(items, DeliveryOrderItemsVo.class);
+            //做成map,方便匹配
+            Map<String, List<DeliveryOrderItemsVo>> collect1 = deliveryOrderItemsVos.stream().collect(Collectors.groupingBy(DeliveryOrderItemsVo::getOrderId));
+            MyPageData<DeliveryOrderVo> pageData = MyPageUtil.makeResponseData(deliveryOrderList, DeliveryOrderVo.class);
+            pageData.getDataList().forEach(e -> {
+                e.setItemList(collect1.get(e.getId()));
+            });
+            List<DeliveryOrderVo> dataList = pageData.getDataList();
+            dataList.sort(new Comparator<DeliveryOrderVo>() {
+                @Override
+                public int compare(DeliveryOrderVo o1, DeliveryOrderVo o2) {
+                    int compareTo = o2.getCreateTime().compareTo(o1.getCreateTime());
+                    return compareTo;
+                }
+            });
+            pageData.setDataList(dataList);
+            return ResponseResult.success(pageData);
+
+        }
+        MyPageData<DeliveryOrderVo> pageData = new MyPageData<>();
+        pageData.setDataList(new ArrayList<>());
+        pageData.setTotalCount(0l);
+        return ResponseResult.success(pageData);
+    }
+
+    /**
+     * 订单详情
+     */
+//    @SaIgnore
+    @RequestMapping("/orderDetail")
+    public ResponseResult<DeliveryOrderVo> orderDetail(@RequestParam String id) {
+        DeliveryOrder deliveryOrder = deliveryOrderService.getByIdWithRelation(id, MyRelationParam.full());
+        if (deliveryOrder == null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
+        }
+        deliveryOrderService.maskFieldData(deliveryOrder, null);
+        DeliveryOrderVo deliveryOrderVo = MyModelUtil.copyTo(deliveryOrder, DeliveryOrderVo.class);
+        DeliveryOrderItems build = DeliveryOrderItems.builder().orderId(deliveryOrderVo.getId()).build();
+        List<DeliveryOrderItems> items = deliveryOrderItemsService.getDeliveryOrderItemsList(build, "create_time");
+        //items转为vo
+        deliveryOrderVo.setItemList(MyModelUtil.copyCollectionTo(items, DeliveryOrderItemsVo.class));
+        return ResponseResult.success(deliveryOrderVo);
+    }
+
+    /**
+     * 计算两个经纬度直接距离
+     * @param initLongitude 初始点位经度
+     * @param initLatitude 初始点位纬度
+     * @param lastLongitude 目标点经度
+     * @param lastLatitude 目标点纬度
+     * @return
+     * @throws Exception
+     */
+    @RequestMapping("/getCalculatedDistance")
+    @SaIgnore
+    public ResponseResult<String> getCalculatedDistance(@RequestParam("initLongitude") String initLongitude,
+                                                        @RequestParam("initLatitude") String initLatitude,
+                                                        @RequestParam("lastLongitude") String lastLongitude,
+                                                        @RequestParam("lastLatitude") String lastLatitude
+    ) throws Exception {
+        if (StringUtils.isBlank(initLongitude) || StringUtils.isBlank(initLatitude) || StringUtils.isBlank(lastLongitude)) {
+            throw new Exception("起始经纬度或目标经纬度有一个为空!!!");
+        }
+       Double v = MathCalculateUtils.calculateDistance(Double.parseDouble(initLongitude), Double.parseDouble(initLatitude), Double.parseDouble(lastLongitude), Double.parseDouble(lastLatitude));
+        return ResponseResult.success(v.toString());
+    }
+    private ResponseResult<Tuple2<DeliveryOrder, JSONObject>> doBusinessDataVerifyAndConvert(
+            DeliveryOrderDto deliveryOrderDto,
+            boolean forUpdate,
+            List<DeliveryOrderItemsDto> deliveryOrderItemsDtoList) {
+        ErrorCodeEnum errorCode = ErrorCodeEnum.DATA_VALIDATED_FAILED;
+        String errorMessage = MyCommonUtil.getModelValidationError(deliveryOrderDto, false);
+        if (errorMessage != null) {
+            return ResponseResult.error(errorCode, errorMessage);
+        }
+        errorMessage = MyCommonUtil.getModelValidationError(deliveryOrderItemsDtoList);
+        if (errorMessage != null) {
+            return ResponseResult.error(errorCode, "参数 [deliveryOrderItemsDtoList] " + errorMessage);
+        }
+        // 全部关联从表数据的验证和转换
+        JSONObject relationData = new JSONObject();
+        CallResult verifyResult;
+        // 下面是输入参数中,主表关联数据的验证。
+        DeliveryOrder deliveryOrder = MyModelUtil.copyTo(deliveryOrderDto, DeliveryOrder.class);
+        DeliveryOrder originalData;
+        if (forUpdate && deliveryOrder != null) {
+            originalData = deliveryOrderService.getById(deliveryOrder.getId());
+            if (originalData == null) {
+                return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
+            }
+            relationData.put("originalData", originalData);
+        }
+        // 处理主表的一对多关联 [DeliveryOrderItems]
+        List<DeliveryOrderItems> deliveryOrderItemsList =
+                MyModelUtil.copyCollectionTo(deliveryOrderItemsDtoList, DeliveryOrderItems.class);
+        relationData.put("deliveryOrderItemsList", deliveryOrderItemsList);
+        return ResponseResult.success(new Tuple2<>(deliveryOrder, relationData));
+    }
+
+}

+ 384 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/controller/WechatFoodIndexController.java

@@ -0,0 +1,384 @@
+package com.tourism.webadmin.app.wechat.controller;
+
+import cn.dev33.satoken.annotation.SaIgnore;
+import com.github.pagehelper.page.PageMethod;
+import com.tourism.common.additional.config.ApplicationConfig;
+import com.tourism.common.additional.utils.StringUtils;
+import com.tourism.common.additional.utils.UrlConvertUtils;
+import com.tourism.common.core.annotation.DisableDataFilter;
+import com.tourism.common.core.annotation.MyRequestBody;
+import com.tourism.common.core.object.MyOrderParam;
+import com.tourism.common.core.object.MyPageData;
+import com.tourism.common.core.object.ResponseResult;
+import com.tourism.common.core.util.MyModelUtil;
+import com.tourism.common.core.util.MyPageUtil;
+import com.tourism.webadmin.app.wechat.dto.BestNewShopDto;
+import com.tourism.webadmin.app.wechat.dto.PageInfo;
+import com.tourism.webadmin.app.wechat.dto.ShopFoodDto;
+import com.tourism.webadmin.app.wechat.vo.shopfood.*;
+import com.tourism.webadmin.back.dto.RestaurantInfoDto;
+import com.tourism.webadmin.back.model.JobProject;
+import com.tourism.webadmin.back.model.RestaurantFoodInfo;
+import com.tourism.webadmin.back.model.RestaurantInfo;
+import com.tourism.webadmin.back.service.RestaurantFoodInfoService;
+import com.tourism.webadmin.back.service.RestaurantInfoService;
+import com.tourism.webadmin.back.vo.RestaurantFoodInfoVo;
+import com.tourism.webadmin.back.vo.RestaurantInfoVo;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * 外卖美食主页接口。
+ *
+ * @author huangwen
+ * @date 2024/10/22
+ */
+@Tag(name = "外卖美食主页接口")
+@Slf4j
+@RestController
+@SaIgnore
+@DisableDataFilter
+@RequestMapping("/wechat/food")
+public class WechatFoodIndexController {
+    @Autowired
+    private RestaurantInfoService restaurantInfoService;
+
+    @Autowired
+    private RestaurantFoodInfoService restaurantFoodInfoService;
+
+    @Autowired
+    private ApplicationConfig applicationConfig;
+
+
+    /**
+     * 外卖美食首页banner展示。
+     *
+     * @return 应答结果对象。
+     */
+    @GetMapping("/queryIndexBannerRestaurant")
+    public ResponseResult<List<WechatQueryIndexBannerRestaurantVo>> queryIndexBannerRestaurant() {
+        RestaurantInfo filter = new RestaurantInfo();
+        filter.setDataState(1);
+        filter.setEnable(1);
+        filter.setIsRecommendedShop(1);
+        PageMethod.startPage(1, 4, true);
+
+        MyOrderParam myOrderParam = new MyOrderParam();
+        myOrderParam.add(new MyOrderParam.OrderInfo("recommendationRate",false,null));
+        String orderBy = MyOrderParam.buildOrderBy(myOrderParam, RestaurantInfo.class);
+
+        List<RestaurantInfo> infoList = restaurantInfoService.getRestaurantInfoList(filter, orderBy);
+
+        List<WechatQueryIndexBannerRestaurantVo> list = MyModelUtil.copyCollectionTo(infoList,WechatQueryIndexBannerRestaurantVo.class);
+        list.stream().forEach(o-> {
+            List<String> strings = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), o.getRestaurantBanner());
+            if (!strings.isEmpty()){
+                o.setRestaurantBannerAfterConvert(strings.get(0));
+            }
+            });
+        return ResponseResult.success(list);
+    }
+
+
+    /**
+     * 超火美食首页4条
+     * @return 应答结果对象。
+     */
+    @GetMapping("/queryIndexHotFood")
+    public ResponseResult<List<RestaurantFoodInfoVo>> queryIndexHotFood() {
+        RestaurantFoodInfo filter = new RestaurantFoodInfo();
+        filter.setDataState(1);
+        filter.setEnable(1);
+        filter.setIsHotFood(1);
+
+//        MyOrderParam myOrderParam = new MyOrderParam();
+//        myOrderParam.add(new MyOrderParam.OrderInfo("totalSales",false,null));
+//        String orderBy = MyOrderParam.buildOrderBy(myOrderParam, RestaurantInfo.class);
+
+        PageMethod.startPage(1, 4, true);
+        List<RestaurantFoodInfo> foodInfoList = restaurantFoodInfoService.getRestaurantFoodInfoList(filter, null);
+        MyPageData<RestaurantFoodInfoVo> pageData = MyPageUtil.makeResponseData(foodInfoList, RestaurantFoodInfoVo.class);
+        List<RestaurantFoodInfoVo> list = pageData.getDataList();
+        list.stream().forEach(o->{
+            List<String> strings = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), o.getUrl());
+            o.setUrlAfterConvert(strings);
+        });
+        return ResponseResult.success(list);
+    }
+
+//    /**
+//     * 超火美食列表
+//     * @return 应答结果对象。
+//     */
+//    @GetMapping("/queryHotFoodList")
+//    public ResponseResult<MyPageData<RestaurantFoodInfoVo>> queryHotFoodList( PageInfo pageInfo) {
+//        RestaurantFoodInfo filter = new RestaurantFoodInfo();
+//        filter.setDataState(1);
+//        filter.setEnable(1);
+//        filter.setIsHotFood(1);
+//        if (pageInfo.getPageNum() != null && pageInfo.getPageSize() != null && pageInfo.getCount() != null) {
+//            PageMethod.startPage(pageInfo.getPageNum(), pageInfo.getPageSize(), pageInfo.getCount());
+//        }
+//        List<RestaurantFoodInfo> foodInfoList = restaurantFoodInfoService.getRestaurantFoodInfoList(filter, null);
+//        MyPageData<RestaurantFoodInfoVo> pageData = MyPageUtil.makeResponseData(foodInfoList, RestaurantFoodInfoVo.class);
+//        List<RestaurantFoodInfoVo> list = pageData.getDataList();
+//        list.stream().forEach(o->{
+//            List<String> strings = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), o.getUrl());
+//            o.setUrlAfterConvert(strings);
+//        });
+//        return  ResponseResult.success(pageData);
+//    }
+
+    /**
+     * 美味店铺首页
+     *
+     * @return 应答结果对象。
+     */
+    @GetMapping("/queryIndexDeliciousShop")
+    public ResponseResult<List<RestaurantInfoVo>> queryIndexDeliciousShop() {
+        PageMethod.startPage(1,3,null);
+        RestaurantInfo filter = new RestaurantInfo();
+        filter.setDataState(1);
+        filter.setEnable(1);
+        filter.setIsDeliciousShop(1);
+
+        MyOrderParam myOrderParam = new MyOrderParam();
+        myOrderParam.add(new MyOrderParam.OrderInfo("recommendationRate",false,null));
+        String orderBy = MyOrderParam.buildOrderBy(myOrderParam, RestaurantInfo.class);
+
+        List<RestaurantInfo> list = restaurantInfoService.getRestaurantInfoList(filter, orderBy);
+        List<RestaurantInfoVo> voList = MyPageUtil.makeResponseData(list,RestaurantInfoVo.class).getDataList();
+        voList.stream().forEach(o-> {
+            List<String> strings = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), o.getUrl());
+            o.setUrlAfterConvert(strings);
+        });
+        return ResponseResult.success(voList);
+    }
+
+    /**
+     * 精选好店首页
+     *
+     * @return 应答结果对象。
+     */
+    @GetMapping("/queryIndexSelectShop")
+    public ResponseResult<MyPageData<WechatSelectShopVo>> queryIndexSelectShop(BestNewShopDto bestNewShopVoDto) {
+
+        RestaurantInfo filter = new RestaurantInfo();
+        filter.setDataState(1);
+        filter.setEnable(1);
+
+        Integer orderNum = 0;
+        if (bestNewShopVoDto.getIsSelectShop()!=null &&bestNewShopVoDto.getIsSelectShop()==1){
+            filter.setIsSelectShop(1);
+            orderNum = 1;
+        }
+        if (bestNewShopVoDto.getIsBestNewShop()!=null &&bestNewShopVoDto.getIsBestNewShop()==1){
+            filter.setIsBestNewShop(1);
+            orderNum = 2;
+        }
+
+        //店铺
+        if (bestNewShopVoDto.getPageNum() != null && bestNewShopVoDto.getPageSize() != null && bestNewShopVoDto.getCount() != null) {
+            PageMethod.startPage(bestNewShopVoDto.getPageNum(), bestNewShopVoDto.getPageSize(), bestNewShopVoDto.getCount());
+        }
+
+        MyOrderParam myOrderParam = new MyOrderParam();
+        if (orderNum==1) myOrderParam.add(new MyOrderParam.OrderInfo("totalSales",false,null));
+        if (orderNum==2) myOrderParam.add(new MyOrderParam.OrderInfo("createTime",false,null));
+        String orderBy = MyOrderParam.buildOrderBy(myOrderParam, RestaurantInfo.class);
+
+        List<RestaurantInfo> list = restaurantInfoService.getRestaurantInfoList(filter, orderBy);
+        MyPageData<WechatSelectShopVo> pageData = MyPageUtil.makeResponseData(list, WechatSelectShopVo.class);
+        if (pageData.getDataList() == null || pageData.getDataList().isEmpty()) {
+            return ResponseResult.success(null);
+        }
+        List<WechatSelectShopVo> dataList = pageData.getDataList();
+        for (WechatSelectShopVo shopVo : dataList) {
+            if(shopVo.getTag()!=null||!shopVo.getTag().equals("")){
+                String[] tags = shopVo.getTag().split("&");
+                shopVo.setTags(tags);
+            }
+            //json->图片
+            List<String> strings = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), shopVo.getUrl());
+            shopVo.setUrlAfterConvert(strings);
+            //美食列表
+            PageMethod.startPage(1,3,null);
+            RestaurantFoodInfo filter1 = new RestaurantFoodInfo();
+            filter1.setEnable(1);
+            filter1.setDataState(1);
+            filter1.setRestaurantId(shopVo.getId());
+            List<RestaurantFoodInfo> infoList = restaurantFoodInfoService.getRestaurantFoodInfoList(filter1, null);
+            List<SelectShopFoodVo> foodVos = MyPageUtil.makeResponseData(infoList, SelectShopFoodVo.class).getDataList();
+
+            foodVos.stream().forEach(o->{
+
+                List<String> strings1 = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), o.getUrl());
+                o.setUrlAfterConvert(strings1);
+            });
+            foodVos.sort(new Comparator<SelectShopFoodVo>() {
+                @Override
+                public int compare(SelectShopFoodVo o1, SelectShopFoodVo o2) {
+                    return o2.getSales().compareTo(o1.getSales());
+                }
+            });
+            shopVo.setFoodList(foodVos);
+        }
+
+        return ResponseResult.success(pageData);
+    }
+
+
+    /**
+     * 优选新店四个
+     *
+     * @return 应答结果对象。
+     */
+    @GetMapping("/queryBestNewShop")
+    public ResponseResult<List<WechatBestNewShopVo>> queryBestNewShop() {
+
+        RestaurantInfo filter = new RestaurantInfo();
+        filter.setDataState(1);
+        filter.setEnable(1);
+        filter.setIsBestNewShop(1);
+
+        PageMethod.startPage(1,4,null);
+        MyOrderParam myOrderParam = new MyOrderParam();
+        myOrderParam.add(new MyOrderParam.OrderInfo("createTime",false,null));
+        String orderBy = MyOrderParam.buildOrderBy(myOrderParam, RestaurantInfo.class);
+
+        List<RestaurantInfo> infoList = restaurantInfoService.getRestaurantInfoList(filter, orderBy);
+        List<WechatBestNewShopVo> shopVos = MyPageUtil.makeResponseData(infoList, WechatBestNewShopVo.class).getDataList();
+        shopVos.stream().forEach(o->{
+            List<String> strings = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), o.getUrl());
+            o.setUrlAfterConvert(strings);
+        });
+        shopVos.sort(new Comparator<WechatBestNewShopVo>() {
+
+            @Override
+            public int compare(WechatBestNewShopVo o1, WechatBestNewShopVo o2) {
+                if (Objects.isNull(o2.getTotalSales()) || Objects.isNull(o1.getTotalSales())) {
+                    return 1;
+                }
+                return o2.getTotalSales().compareTo(o1.getTotalSales());
+            }
+        });
+        return ResponseResult.success(shopVos);
+    }
+
+    /**
+     * 优选新店Go 优选新店列表时间倒序
+     *
+     * @return 应答结果对象。
+     */
+    @GetMapping("/queryBestNewShopList")
+    public ResponseResult<MyPageData<WechatBestNewShopVo>> queryBestNewShopList(PageInfo pageInfo) {
+        RestaurantInfo filter = new RestaurantInfo();
+        filter.setDataState(1);
+        filter.setEnable(1);
+        filter.setIsBestNewShop(1);
+        if (pageInfo.getPageNum() != null && pageInfo.getPageSize() != null && pageInfo.getCount() != null) {
+            PageMethod.startPage(pageInfo.getPageNum(), pageInfo.getPageSize(), pageInfo.getCount());
+        }
+
+        MyOrderParam myOrderParam = new MyOrderParam();
+        myOrderParam.add(new MyOrderParam.OrderInfo("createTime",false,null));
+        String orderBy = MyOrderParam.buildOrderBy(myOrderParam, RestaurantInfo.class);
+
+        List<RestaurantInfo> infoList = restaurantInfoService.getRestaurantInfoList(filter, orderBy);
+        MyPageData<WechatBestNewShopVo> pageData = MyPageUtil.makeResponseData(infoList, WechatBestNewShopVo.class);
+        List<WechatBestNewShopVo> shopVos = pageData.getDataList();
+
+        shopVos.stream().forEach(o->{
+            List<String> strings = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), o.getUrl());
+            o.setUrlAfterConvert(strings);
+        });
+        return ResponseResult.success(pageData);
+    }
+
+//    /**
+//     * 优选新店Go id拿优选新店详情和美食列表
+//     *
+//     * @return 应答结果对象。
+//     */
+//    @GetMapping("/queryBestNewShopAndFoodList")
+//    public ResponseResult<WechatShopAndFoodsVo> queryBestNewShopAndFoodList(ShopFoodDto shopFoodDto) {
+//
+//        RestaurantInfo filter = new RestaurantInfo();
+//        filter.setEnable(1);
+//        filter.setDataState(1);
+//        filter.setId(shopFoodDto.getRestaurantId());
+//        RestaurantInfo restaurantInfo = restaurantInfoService.getRestaurantInfoList(filter, null).get(0);
+//        RestaurantInfoVo restaurantInfoVo = MyModelUtil.copyTo(restaurantInfo, RestaurantInfoVo.class);
+//        List<String> strings1 = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), restaurantInfoVo.getUrl());
+//        restaurantInfoVo.setUrlAfterConvert(strings1);
+//
+//        RestaurantFoodInfo filter1 = MyModelUtil.copyTo(shopFoodDto, RestaurantFoodInfo.class);
+//        filter1.setDataState(1);
+//        filter1.setEnable(1);
+//        filter1.setRestaurantId(shopFoodDto.getRestaurantId());
+//
+//        if (shopFoodDto.getPageNum() != null && shopFoodDto.getPageSize() != null && shopFoodDto.getCount() != null) {
+//            PageMethod.startPage(shopFoodDto.getPageNum(), shopFoodDto.getPageSize(), shopFoodDto.getCount());
+//        }
+//
+//        List<RestaurantFoodInfo> infoList = restaurantFoodInfoService.getRestaurantFoodInfoList(filter1, null);
+//        MyPageData<RestaurantFoodInfoVo> pageData = MyPageUtil.makeResponseData(infoList, RestaurantFoodInfoVo.class);
+//        List<RestaurantFoodInfoVo> shopVos = pageData.getDataList();
+//
+//        shopVos.stream().forEach(o->{
+//            if(StringUtils.isNotEmpty(o.getUrl())) {
+//                List<String> strings = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), o.getUrl());
+//                o.setUrlAfterConvert(strings);
+//            }
+//        });
+//
+//        WechatShopAndFoodsVo vo = new WechatShopAndFoodsVo();
+//        vo.setRestaurantFoodInfoVoMyPageData(pageData);
+//        vo.setRestaurantInfoVo(restaurantInfoVo);
+//
+//        return ResponseResult.success(vo);
+//    }
+
+
+    /**
+     * id拿美食列表
+     *
+     * @return 应答结果对象。
+     */
+    @GetMapping("/queryRestaurantFoodListById")
+    public ResponseResult<MyPageData<RestaurantFoodInfoVo>> queryBestNewShopAndFoodList(ShopFoodDto shopFoodDto) {
+
+        RestaurantFoodInfo filter1 = MyModelUtil.copyTo(shopFoodDto, RestaurantFoodInfo.class);
+        filter1.setDataState(1);
+        filter1.setEnable(1);
+        filter1.setRestaurantId(shopFoodDto.getRestaurantId());
+
+        if (shopFoodDto.getPageNum() != null && shopFoodDto.getPageSize() != null) {
+            PageMethod.startPage(shopFoodDto.getPageNum(), shopFoodDto.getPageSize(), true);
+        }
+
+        List<RestaurantFoodInfo> infoList = restaurantFoodInfoService.getRestaurantFoodInfoList(filter1, null);
+        MyPageData<RestaurantFoodInfoVo> pageData = MyPageUtil.makeResponseData(infoList, RestaurantFoodInfoVo.class);
+        List<RestaurantFoodInfoVo> infoVos = pageData.getDataList();
+
+        infoVos.stream().forEach(o->{
+            if(StringUtils.isNotEmpty(o.getUrl())) {
+                List<String> strings = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), o.getUrl());
+                o.setUrlAfterConvert(strings);
+            }
+        });
+
+        return ResponseResult.success(pageData);
+    }
+
+}

+ 198 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/controller/WechatHomeController.java

@@ -0,0 +1,198 @@
+package com.tourism.webadmin.app.wechat.controller;
+
+import cn.dev33.satoken.annotation.SaIgnore;
+import com.github.pagehelper.page.PageMethod;
+import com.tourism.common.additional.utils.UrlConvertUtils;
+import com.tourism.common.core.object.MyOrderParam;
+import com.tourism.common.core.object.MyPageData;
+import com.tourism.common.core.object.ResponseResult;
+import com.tourism.common.core.object.TokenData;
+import com.tourism.common.core.util.MyModelUtil;
+import com.tourism.common.core.util.MyPageUtil;
+import com.tourism.common.log.annotation.OperationLog;
+import com.tourism.common.log.model.constant.SysOperationLogType;
+import com.tourism.common.satoken.util.SaTokenUtil;
+import com.tourism.webadmin.app.website.dto.TourismProjectToWebDto;
+import com.tourism.webadmin.back.model.BannerInfo;
+import com.tourism.webadmin.back.model.DirectoryInfo;
+import com.tourism.webadmin.back.model.Icon;
+import com.tourism.webadmin.back.model.TourismProject;
+import com.tourism.webadmin.back.service.*;
+import com.tourism.webadmin.back.vo.BannerInfoVo;
+import com.tourism.webadmin.back.vo.DirectoryInfoVo;
+import com.tourism.webadmin.back.vo.IconVo;
+import com.tourism.webadmin.back.vo.TourismProjectVo;
+import com.tourism.common.additional.config.ApplicationConfig;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotNull;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.util.CollectionUtils;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+import static java.util.stream.Collectors.toList;
+
+/**
+ * 小程序首页接口。
+ *
+ * @author chenchen
+ * @date 2024-09-26
+ */
+@Tag(name = "小程序首页接口")
+@Slf4j
+@RestController
+@RequestMapping("/wechat/home")
+public class WechatHomeController {
+
+    @Autowired
+    private BannerInfoService bannerInfoService;
+    @Autowired
+    private DirectoryInfoService directoryInfoService;
+    @Autowired
+    private ApplicationConfig applicationConfig;
+    @Autowired
+    private IconService iconService;
+    @Autowired
+    private TourismProjectService tourismProjectService;
+    @Autowired
+    private TourUserService tourUserService;
+    @OperationLog(type = SysOperationLogType.LIST)
+    @GetMapping("/getUserInfo")
+    public ResponseResult getUserInfo() {
+        TokenData tokenData = TokenData.takeFromRequest();
+        return ResponseResult.success(tokenData);
+    }
+    /**
+     * 列出符合过滤条件的banner列表。
+     * @return 应答结果对象,包含查询结果集。
+     */
+    @OperationLog(type = SysOperationLogType.LIST)
+    @GetMapping("/bannerList")
+    @SaIgnore
+    public ResponseResult<MyPageData<BannerInfoVo>> bannerInfoList(@NotNull(message = "所属分类(belongTab)不能为空") Long belongTab) {
+        BannerInfo bannerInfoFilter = new BannerInfo();
+        bannerInfoFilter.setBelongTab(belongTab);
+        //首页展示的话,只能展示启用的banner图
+        bannerInfoFilter.setEnable(1);
+        String orderBy = MyOrderParam.buildOrderBy(new MyOrderParam(), BannerInfo.class);
+        List<BannerInfo> bannerInfoList = bannerInfoService.getBannerInfoListWithRelation(bannerInfoFilter, orderBy);
+        MyPageData<BannerInfoVo> bannerInfoVoMyPageData = MyPageUtil.makeResponseData(bannerInfoList, BannerInfoVo.class);
+
+        List<BannerInfoVo> dataList = bannerInfoVoMyPageData.getDataList();
+        //先把imgUrl由jaon转换为List<FileUrlObject>
+        dataList.stream().forEach(item ->
+        {
+            List<String> arrayList = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), item.getImgUrl());
+            item.setImgUrlsAfterConvert(arrayList);
+        });
+        return ResponseResult.success(bannerInfoVoMyPageData);
+    }
+
+    /**
+     * 列出符合过滤条件的icon列表。
+     * @return 应答结果对象,包含查询结果集。
+     */
+    @OperationLog(type = SysOperationLogType.LIST)
+    @GetMapping("/iconList")
+    @SaIgnore
+    public ResponseResult<MyPageData<IconVo>> iconList(){
+        Icon filter = new Icon();
+        String orderBy = "";
+        List<Icon> icnoList = iconService.getIconList(filter, orderBy);
+        MyPageData<IconVo> icnoInfoVoMyPageData = MyPageUtil.makeResponseData(icnoList, IconVo.class);
+        List<IconVo> dataList = icnoInfoVoMyPageData.getDataList();
+        if(!CollectionUtils.isEmpty(dataList)){
+            dataList.stream().forEach(item->{
+                List<String> arrayList = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), item.getIcon());
+                item.setIconAfterConvert(arrayList);
+            });
+        };
+        return ResponseResult.success(icnoInfoVoMyPageData);
+    }
+    /**
+     * 列出符合过滤条件的旅游项目管理列表。
+     * 接口:热门项目(isHotspot:1)
+     *
+     * @param tourismProjectDtoFilter 过滤对象。
+     * @return 应答结果对象,包含查询结果集。
+     */
+    @OperationLog(type = SysOperationLogType.LIST)
+    @GetMapping("/projectList")
+    @SaIgnore
+    public ResponseResult<MyPageData<TourismProjectVo>> projectList(
+            @Valid @ModelAttribute TourismProjectToWebDto tourismProjectDtoFilter) {
+//        if(tourismProjectDtoFilter.getBelongTab() == null){
+//            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST,"所属分类(belongTab)不能为空!");
+//        }
+        //如果belongTab小于1000并且大于10.则表明查询的是一级菜单
+        if(tourismProjectDtoFilter.getBelongTab() !=null && tourismProjectDtoFilter.getBelongTab()>=10 && tourismProjectDtoFilter.getBelongTab()<1000){
+            DirectoryInfo filter = new DirectoryInfo();
+            filter.setParentId(tourismProjectDtoFilter.getBelongTab());
+            filter.setEnable(1);
+            List<DirectoryInfo> directoryInfoList = tourismProjectService.getDirectoryInfoList(filter, null);
+            tourismProjectDtoFilter.setBelongTab(null);
+            tourismProjectDtoFilter.setDirectoryInfoIds(directoryInfoList.stream().map(DirectoryInfo::getId).collect(toList()));
+        }
+        TourismProject tourismProjectFilter = MyModelUtil.copyTo(tourismProjectDtoFilter, TourismProject.class);
+        //首页展示的为启用的内容
+        tourismProjectFilter.setEnable(1);
+        String orderBy = MyOrderParam.buildOrderBy(tourismProjectDtoFilter.getOrderParamList(), TourismProject.class);
+        if (tourismProjectDtoFilter.getPageNum() != null && tourismProjectDtoFilter.getPageSize() != null && tourismProjectDtoFilter.getCount() != null) {
+            PageMethod.startPage(tourismProjectDtoFilter.getPageNum(), tourismProjectDtoFilter.getPageSize(), tourismProjectDtoFilter.getCount());
+        }
+        List<TourismProject> tourismProjectList =
+                tourismProjectService.getTourismProjectListWithRelation(tourismProjectFilter, orderBy);
+        MyPageData<TourismProjectVo> tourismProjectVoMyPageData = MyPageUtil.makeResponseData(tourismProjectList, TourismProjectVo.class);
+
+        List<TourismProjectVo> dataList = tourismProjectVoMyPageData.getDataList();
+        //先把imgUrl由jaon转换为List<FileUrlObject>
+        dataList.stream().forEach(item ->{
+            List<String> arrayList = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(),item.getHomeHotPicture());
+            item.setHomeHotPicturesAfterConvert(arrayList);
+            List<String> arrayList1 = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), item.getTourismUrl());
+            item.setTourismUrlsAfterConvert(arrayList1);
+        });
+        return ResponseResult.success(tourismProjectVoMyPageData);
+    }
+
+    /**
+     * 列出符合过滤条件的门户菜单管理列表。
+     * 接口:热门目的地 (isHotspot:1)
+     * @return 应答结果对象,包含查询结果集。
+     */
+    @OperationLog(type = SysOperationLogType.LIST)
+    @GetMapping("/directoryList")
+    @SaIgnore
+    public ResponseResult<MyPageData<DirectoryInfoVo>> directoryInfoList(Long parentId, Integer isHotspot) {
+//        if (ObjectUtils.isEmpty(parentId)){
+//            return ResponseResult.error("NO-ERROR","父级Id为空");
+//        }
+        DirectoryInfo directoryInfoFilter = new DirectoryInfo();
+        if(parentId != null){
+            directoryInfoFilter.setParentId(parentId);
+        }
+        if(isHotspot != null){
+            directoryInfoFilter.setIsHotspot(isHotspot);
+        }
+        String orderBy = MyOrderParam.buildOrderBy(new MyOrderParam(), DirectoryInfo.class);
+        List<DirectoryInfo> directoryInfoList =
+                directoryInfoService.getDirectoryInfoListWithRelation(directoryInfoFilter, orderBy);
+
+        MyPageData<DirectoryInfoVo> directoryInfoVoMyPageData = MyPageUtil.makeResponseData(directoryInfoList, DirectoryInfoVo.class);
+
+        List<DirectoryInfoVo> dataList = directoryInfoVoMyPageData.getDataList();
+        //先把imgUrl由jaon转换为List<FileUrlObject>
+        dataList.stream().forEach(item ->{
+            List<String> arrayList1 = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), item.getHotPictureUrl());
+            item.setHotPictureUrlsAfterConvert(arrayList1);
+        });
+
+        return ResponseResult.success(directoryInfoVoMyPageData);
+    }
+}

+ 95 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/controller/WechatJobIndexController.java

@@ -0,0 +1,95 @@
+package com.tourism.webadmin.app.wechat.controller;
+
+import cn.dev33.satoken.annotation.SaIgnore;
+import com.github.pagehelper.page.PageMethod;
+import com.tourism.common.additional.config.ApplicationConfig;
+import com.tourism.common.additional.utils.StringUtils;
+import com.tourism.common.additional.utils.UrlConvertUtils;
+import com.tourism.common.core.annotation.DisableDataFilter;
+import com.tourism.common.core.constant.ErrorCodeEnum;
+import com.tourism.common.core.object.MyOrderParam;
+import com.tourism.common.core.object.MyPageData;
+import com.tourism.common.core.object.ResponseResult;
+import com.tourism.common.core.util.MyModelUtil;
+import com.tourism.common.core.util.MyPageUtil;
+import com.tourism.webadmin.app.website.dto.TourismProjectToWebDto;
+import com.tourism.webadmin.app.wechat.dto.WechatIndexJobProjectDto;
+import com.tourism.webadmin.back.model.DirectoryInfo;
+import com.tourism.webadmin.back.model.JobProject;
+import com.tourism.webadmin.back.service.JobProjectService;
+import com.tourism.webadmin.back.service.TourismProjectService;
+import com.tourism.webadmin.back.vo.JobProjectVo;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+import static java.util.stream.Collectors.toList;
+
+/**
+ * WechatJobIndexController接口。
+ *
+ * @author huangwen
+ * @date 2024/11/1
+ */
+@Tag(name = "全球劳务首页接口")
+@Slf4j
+@RestController
+@SaIgnore
+@DisableDataFilter
+@RequestMapping("/wechat/job")
+public class WechatJobIndexController {
+    @Autowired
+    private JobProjectService jobProjectService;
+    @Autowired
+    private TourismProjectService tourismProjectService;
+    @Autowired
+    private ApplicationConfig applicationConfig;
+    /**
+     * 列出符合过滤条件的劳务项目管理列表。
+     *
+     * @param tourismProjectDtoFilter 过滤对象。
+     * @return 应答结果对象,包含查询结果集。
+     */
+    @GetMapping("/list")
+    public ResponseResult<MyPageData<JobProjectVo>> list(
+             @ModelAttribute WechatIndexJobProjectDto tourismProjectDtoFilter) {
+
+        JobProject jobProjectFilter = MyModelUtil.copyTo(tourismProjectDtoFilter, JobProject.class);
+        //首页展示的为启用的内容
+        jobProjectFilter.setEnable(1);
+        if(tourismProjectDtoFilter.getPageSize() != null && tourismProjectDtoFilter.getPageNum() != null){
+            PageMethod.startPage(tourismProjectDtoFilter.getPageNum(), tourismProjectDtoFilter.getPageSize(), true);
+        }
+
+        MyOrderParam myOrderParam = new MyOrderParam();
+        if (tourismProjectDtoFilter.getIsNew()!=null && tourismProjectDtoFilter.getIsNew()==1){
+            myOrderParam.add(new MyOrderParam.OrderInfo("createTime",false,null));
+        }else {
+            myOrderParam.add(new MyOrderParam.OrderInfo("isHotspot",false,null));
+            myOrderParam.add(new MyOrderParam.OrderInfo("showOrder",false,null));
+        }
+        String orderBy = MyOrderParam.buildOrderBy(myOrderParam, JobProject.class);
+
+        List<JobProject> jobProjectList = jobProjectService.getJobProjectListWithRelation(jobProjectFilter, orderBy);
+
+        MyPageData<JobProjectVo> jobProjectVoMyPageData = MyPageUtil.makeResponseData(jobProjectList, JobProjectVo.class);
+        List<JobProjectVo> dataList = jobProjectVoMyPageData.getDataList();
+        //先把imgUrl由jaon转换为List<FileUrlObject>
+        dataList.stream().forEach(item ->
+        {
+            if (StringUtils.isNotEmpty(item.getJobUrl())) {
+                List<String> jobUrl = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), item.getJobUrl());
+                item.setJobUrlsAfterConvert(jobUrl);
+            }
+        });
+
+        return ResponseResult.success(jobProjectVoMyPageData);
+    }
+}

+ 199 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/controller/WechatMapController.java

@@ -0,0 +1,199 @@
+package com.tourism.webadmin.app.wechat.controller;
+
+import cn.dev33.satoken.annotation.SaIgnore;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.tourism.common.core.object.ResponseResult;
+import com.tourism.webadmin.app.wechat.dto.MapDto;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.util.UriUtils;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.*;
+
+@Tag(name = "小程序地图板块接口")
+@Slf4j
+@SaIgnore
+@RestController
+@RequestMapping("/wechat/map")
+public class WechatMapController {
+    public static String REVERSEGEO = "http://restapi.amap.com/v3/geocode/regeo?";
+
+    public static String SEARCHPOI = "http://restapi.amap.com/v3/place/text?";
+
+    public static String AK = "3f9549681920d6d9c504fa73186c416e";
+    /**
+     * 逆地理解析接口
+     */
+    @SaIgnore
+    @GetMapping("reverse/geo")
+    public ResponseResult<Map<String, Object>> reverseGeo() throws Exception{
+
+
+        Map params = new LinkedHashMap<String, String>();
+        params.put("key", AK);
+        params.put("location", "9.585539,49.431014");
+        String result = this.requestGetAK(REVERSEGEO, params);
+
+        Map<String, Object> map = new HashMap<>();
+        // 解析JSON字符串
+        try {
+            JSONObject jsonObject = JSONObject.parseObject(result);
+            // 获取状态码
+            String status = jsonObject.getString("status");
+            if ("1".equals(status)) {
+                // 获取信息
+//                String info = jsonObject.getString("info");
+//                String infocode = jsonObject.getString("infocode");
+                JSONObject regeocode = JSONObject.parseObject(jsonObject.getString("regeocode"));
+                // 获取地址信息
+                String formattedAddress = regeocode.getString("formatted_address");
+                map.put("address", formattedAddress);
+                String country = regeocode.getString("country");
+                String province = regeocode.getString("province");
+                String city = regeocode.getString("city");
+                String adcode = regeocode.getString("adcode");
+                String rectangle = regeocode.getString("rectangle");
+                String level = regeocode.getString("level");
+
+                // 输出地址信息
+                System.out.println("Formatted Address: " + formattedAddress);
+                System.out.println("Country: " + country);
+                System.out.println("Province: " + province);
+                System.out.println("City: " + city);
+                System.out.println("Adcode: " + adcode);
+                System.out.println("Rectangle: " + rectangle);
+                System.out.println("Level: " + level);
+            } else {
+                System.out.println("请求失败,状态码: " + status);
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return ResponseResult.success(map);
+    }
+
+    /**
+     * POI搜索接口
+     */
+    @SaIgnore
+    @GetMapping("searchPoi")
+    public ResponseResult<List<Map<String, Object>>> searchPOI(@Valid MapDto mapDto) throws Exception{
+        List<Map<String, Object>> list = new ArrayList<>();
+        Map param = new LinkedHashMap<String, String>();
+        param.put("key", AK);
+        param.put("keywords", mapDto.getKeywords());
+        param.put("city", mapDto.getCity());
+        param.put("offset", "20");
+        param.put("citylimit", "true");
+        String result = this.requestGetAK(SEARCHPOI, param);
+        try {
+            JSONObject jsonObject = JSONObject.parseObject(result);
+
+            // 获取状态码
+            String status = jsonObject.getString("status");
+            if ("1".equals(status)) {
+                // 获取信息
+//                String info = jsonObject.getString("info");
+//                String infocode = jsonObject.getString("infocode");
+                int count = jsonObject.getInteger("count");
+
+                // 获取pois数组
+                JSONArray pois = jsonObject.getJSONArray("pois");
+
+                // 遍历pois数组
+                for (int i = 0; i < pois.size(); i++) {
+                    Map<String, Object> map = new HashMap<>();
+                    JSONObject poi = pois.getJSONObject(i);
+
+                    // 获取POI信息
+//                    String id = poi.getString("id");
+                    String name = poi.getString("name");
+                    map.put("address", name);
+//                    String location = poi.getString("location");
+//                    String address = poi.getString("address");
+//                    String type = poi.getString("type");
+//                    String adname = poi.getString("adname");
+//                    String adcode = poi.getString("adcode");
+//                    String businessArea = poi.getString("business_area");
+//                    String poiWeight = poi.getString("poiweight");
+//                    String entrLocation = poi.getString("entr_location");
+//                    String exitLocation = poi.getString("exit_location");
+//                    String gridcode = poi.getString("gridcode");
+//                    String naviPoiid = poi.getString("navi_poiid");
+//                    String distance = poi.getString("distance");
+//                    String bizType = poi.getString("biz_type");
+//                    String tel = poi.getString("tel");
+//                    String website = poi.getString("website");
+//                    String email = poi.getString("email");
+//                    String postcode = poi.getString("postcode");
+//                    String tag = poi.getString("tag");
+
+                    // 获取照片数组
+//                    JSONArray photos = poi.getJSONArray("photos");
+//                    for (int j = 0; j < photos.size(); j++) {
+//                        JSONObject photo = photos.getJSONObject(j);
+//                        String photoUrl = photo.getString("url");
+//                        String photoTitle = photo.getString("title");
+//                        String photoWidth = photo.getString("width");
+//                        String photoHeight = photo.getString("height");
+//                    }
+                    list.add(map);
+                }
+            } else {
+                System.out.println("请求失败,状态码: " + status);
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+
+        return ResponseResult.success(list);
+    }
+
+
+    public String requestGetAK(String strUrl, Map<String, String> param) throws Exception {
+        if (strUrl == null || strUrl.length() <= 0 || param == null || param.size() <= 0) {
+            return "";
+        }
+
+        StringBuffer queryString = new StringBuffer();
+        queryString.append(strUrl);
+        for (Map.Entry<?, ?> pair : param.entrySet()) {
+            queryString.append(pair.getKey() + "=");
+            //    第一种方式使用的 jdk 自带的转码方式  第二种方式使用的 spring 的转码方法 两种均可
+            //    queryString.append(URLEncoder.encode((String) pair.getValue(), "UTF-8").replace("+", "%20") + "&");
+            queryString.append(UriUtils.encode((String) pair.getValue(), "UTF-8") + "&");
+        }
+
+        if (queryString.length() > 0) {
+            queryString.deleteCharAt(queryString.length() - 1);
+        }
+
+        java.net.URL url = new URL(queryString.toString());
+        System.out.println(queryString.toString());
+        URLConnection httpConnection = (HttpURLConnection) url.openConnection();
+        httpConnection.connect();
+
+        InputStreamReader isr = new InputStreamReader(httpConnection.getInputStream());
+        BufferedReader reader = new BufferedReader(isr);
+        StringBuffer buffer = new StringBuffer();
+        String line;
+        while ((line = reader.readLine()) != null) {
+            buffer.append(line);
+        }
+        reader.close();
+        isr.close();
+        System.out.println("AK: " + buffer.toString());
+        return buffer.toString();
+    }
+}

+ 400 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/controller/WechatTourismIndexController.java

@@ -0,0 +1,400 @@
+package com.tourism.webadmin.app.wechat.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.dev33.satoken.annotation.SaIgnore;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.github.pagehelper.page.PageMethod;
+import com.tourism.common.additional.config.ApplicationConfig;
+import com.tourism.common.additional.utils.MapConvertUtils;
+import com.tourism.common.additional.utils.StringUtils;
+import com.tourism.common.additional.utils.UrlConvertUtils;
+import com.tourism.common.core.annotation.DisableDataFilter;
+import com.tourism.common.core.annotation.MyRequestBody;
+import com.tourism.common.core.constant.ErrorCodeEnum;
+import com.tourism.common.core.object.MyOrderParam;
+import com.tourism.common.core.object.MyPageData;
+import com.tourism.common.core.object.MyRelationParam;
+import com.tourism.common.core.object.ResponseResult;
+import com.tourism.common.core.util.MyModelUtil;
+import com.tourism.common.core.util.MyPageUtil;
+import com.tourism.common.log.annotation.OperationLog;
+import com.tourism.common.log.model.constant.SysOperationLogType;
+import com.tourism.webadmin.app.wechat.dto.PageInfo;
+import com.tourism.webadmin.app.wechat.vo.tourism.*;
+import com.tourism.webadmin.back.dto.TourTourismTravelNotesContentDto;
+import com.tourism.webadmin.back.model.*;
+import com.tourism.webadmin.back.service.*;
+import com.tourism.webadmin.back.vo.*;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.constraints.Max;
+import jakarta.validation.constraints.Min;
+import jakarta.validation.constraints.NotNull;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * 全球游主页接口。
+ *
+ * @author huangwen
+ * @date 2024/10/22
+ */
+@Tag(name = "全球游主页接口")
+@Slf4j
+@RestController
+@SaIgnore
+@DisableDataFilter
+@RequestMapping("/wechat/tourism")
+public class WechatTourismIndexController {
+
+   @Autowired
+   private TourTourismProjectTravelNotesService tourTourismProjectTravelNotesService;
+
+   @Autowired
+   private ApplicationConfig applicationConfig;
+
+   @Autowired
+   private DirectoryInfoService directoryInfoService;
+
+   @Autowired
+   private TourismProjectService tourismProjectService;
+
+   @Autowired
+   private TourismFileService tourismFileService;
+
+   @Autowired
+   private TourismContentService tourismContentService;
+
+   @Autowired
+   private TourTourismTravelNotesContentService tourTourismTravelNotesContentService;
+
+
+    /**
+     * 全球游热门游记banner4条。
+     *
+     * @return 应答结果对象。
+     */
+    @GetMapping("/queryIndexBannerTravelNotes")
+    public ResponseResult<List<IndexBannerTravelNoteVo>> queryIndexBannerAndTravelNotes() {
+
+        TourTourismProjectTravelNotes filter = new TourTourismProjectTravelNotes();
+        filter.setEnable(1);
+        filter.setDataState(1);
+        filter.setIsHotspot(1);
+
+        MyOrderParam myOrderParam = new MyOrderParam();
+        myOrderParam.add(new MyOrderParam.OrderInfo("hotValue",true,null));
+        String orderBy = MyOrderParam.buildOrderBy(myOrderParam, TourTourismProjectTravelNotes.class);
+
+        PageMethod.startPage(1,4,true);
+        List<TourTourismProjectTravelNotes> list = tourTourismProjectTravelNotesService.getTourTourismProjectTravelNotesList(filter, orderBy);
+        List<IndexBannerTravelNoteVo> dataList = MyPageUtil.makeResponseData(list, IndexBannerTravelNoteVo.class).getDataList();
+
+        dataList.stream().forEach(o->{
+            List<String> strings = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), o.getTravelNotesBanner());
+            o.setTravelNotesBannerAfterConvert(strings);
+        });
+
+        return ResponseResult.success(dataList);
+    }
+
+    /**
+     * 全球游热门游记id跳转游记。
+     *
+     * @return 应答结果对象。
+     */
+    @GetMapping("/queryIndexBannerToTravelNote")
+    public ResponseResult<TourTourismProjectTravelNotesVo> queryIndexBannerToTravelNote(@RequestParam @NotNull(message = "首页跳转游记id不能为null") String id) {
+
+        System.out.println(id);
+        TourTourismProjectTravelNotes filter = new TourTourismProjectTravelNotes();
+        filter.setId(id);
+        filter.setEnable(1);
+        filter.setDataState(1);
+
+        TourTourismProjectTravelNotes notes = tourTourismProjectTravelNotesService.getTourTourismProjectTravelNotesList(filter, "").get(0);
+        TourTourismProjectTravelNotesVo vo = MyModelUtil.copyTo(notes, TourTourismProjectTravelNotesVo.class);
+        if (vo.getTourismUrl()!=null){
+            List<String> strings = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), vo.getTourismUrl());
+            vo.setTourismUrlsAfterConvert(strings);
+        }
+       if (vo.getTravelNotesBanner()!=null){
+           List<String> strings1 = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), vo.getTravelNotesBanner());
+           vo.setTravelNotesBannerAfterConvert(strings1);
+       }
+        return ResponseResult.success(vo);
+    }
+
+    /**
+     * 某月份最火目的地。
+     *
+     * @return 应答结果对象。
+     */
+    @GetMapping("/queryIndexHotDestination")
+    public ResponseResult<List<IndexHotDestinationVo>> queryIndexHotDestination() {
+
+        TourismProject filter = new TourismProject();
+        filter.setDataState(1);
+        filter.setEnable(1);
+        filter.setIsHotspot(1);
+
+        MyOrderParam myOrderParam = new MyOrderParam();
+        new MyOrderParam.OrderInfo("createTime",true, null);
+        new MyOrderParam.OrderInfo("showOrder",true, null);
+        String orderBy = MyOrderParam.buildOrderBy(myOrderParam, DirectoryInfo.class);
+        List<TourismProject> list = tourismProjectService.getTourismProjectList(filter, orderBy);
+
+        List<IndexHotDestinationVo> dataList = MyModelUtil.copyCollectionTo(list, IndexHotDestinationVo.class);
+
+        dataList.forEach(o->{
+            List<String> strings = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), o.getHomeHotPicture());
+            o.setHomeHotPicturesAfterConvert(strings);
+            DirectoryInfo filter1 = new DirectoryInfo();
+            filter1.setEnable(1);
+            filter1.setDataState(1);
+            filter1.setId(o.getBelongTab());
+            List<DirectoryInfo> list1 = directoryInfoService.getDirectoryInfoList(filter1, null);
+            o.setMenuName(list1.get(0).getMenuName());
+        });
+
+        return  ResponseResult.success(dataList.stream().limit(3).collect(Collectors.toList()));
+    }
+
+    /**
+     * 首页精选目的地。
+     *
+     * @return 应答结果对象。
+     */
+    @GetMapping("/queryIndexWinnowDestination")
+    public ResponseResult<DirectoryInfoVo> queryIndexWinnowDestination() {
+
+        DirectoryInfo filter = new DirectoryInfo();
+        filter.setDataState(1);
+        filter.setEnable(1);
+        filter.setIsWinnowDestination(1);
+
+        DirectoryInfo info = directoryInfoService.getDirectoryInfoList(filter, "").get(0);
+        DirectoryInfoVo vo = MyModelUtil.copyTo(info, DirectoryInfoVo.class);
+
+        List<String> strings = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), vo.getHotPictureUrl());
+        vo.setHotPictureUrlsAfterConvert(strings);
+
+        return  ResponseResult.success(vo);
+    }
+
+    /**
+     * 首页热推项目。
+     *
+     * @return 应答结果对象。
+     */
+    @GetMapping("/queryIndexHotProject")
+    public ResponseResult<List<TourismProjectVo>> queryIndexHotProject() {
+        TourismProject filter = new TourismProject();
+        filter.setEnable(1);
+        filter.setDataState(1);
+        filter.setIsHotspot(1);
+
+        MyOrderParam myOrderParam = new MyOrderParam();
+        new MyOrderParam.OrderInfo("recommendationRate",true, null);
+        new MyOrderParam.OrderInfo("pageViewCount",true, null);
+        new MyOrderParam.OrderInfo("likeCount",true, null);
+        String orderBy = MyOrderParam.buildOrderBy(myOrderParam, TourismProject.class);
+
+        System.out.println(orderBy);
+
+        PageMethod.startPage(1,2,true);
+        List<TourismProject> list = tourismProjectService.getTourismProjectList(filter, orderBy);
+        List<TourismProjectVo> voList = MyModelUtil.copyCollectionTo(list, TourismProjectVo.class);
+        voList.stream().forEach(o->{
+            List<String> strings = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), o.getHomeHotPicture());
+            o.setHomeHotPicturesAfterConvert(strings);
+        });
+        return  ResponseResult.success(voList);
+    }
+
+    /**
+     * 爆款游记(游记3-6篇)。
+     *
+     * @return 应答结果对象。
+     */
+    @GetMapping("/queryIndexHotTravelNote")
+    public ResponseResult<Map<String, List<IndexHotProjectTravelNoteVo>>> queryIndexHotTravelNote() {
+        //一级 limit6
+        DirectoryInfo filter = new DirectoryInfo();
+        filter.setDataState(1);
+        filter.setEnable(1);
+        filter.setParentId(0L);
+        MyOrderParam myOrderParam = new MyOrderParam();
+        new MyOrderParam.OrderInfo("showOrder", false, null);
+        String orderBy = MyOrderParam.buildOrderBy(myOrderParam, DirectoryInfo.class);
+        List<DirectoryInfo> list = directoryInfoService.getDirectoryInfoList(filter, orderBy).stream().limit(6).collect(Collectors.toList());
+
+        HashMap<String, List<IndexHotProjectTravelNoteVo>> map = new HashMap<>();
+
+        // 遍历一级
+        for (DirectoryInfo info : list) {
+            // 获取该一级下的二级
+            DirectoryInfo filter1 = new DirectoryInfo();
+            filter1.setDataState(1);
+            filter1.setEnable(1);
+            filter1.setIsHotspot(1);
+            filter1.setParentId(info.getId());
+            List<Long> subIds = directoryInfoService.getDirectoryInfoList(filter1, null).stream().map(DirectoryInfo::getId).collect(Collectors.toList());
+
+            List<IndexHotProjectTravelNoteVo> sumList = new ArrayList<>();
+
+            // 遍历每个二级
+            for (Long subId : subIds) {
+                TourTourismProjectTravelNotes filter2 = new TourTourismProjectTravelNotes();
+                filter2.setBelongTab(subId);
+                filter2.setEnable(1);
+                filter2.setDataState(1);
+                filter2.setIsHotspot(1);
+
+                myOrderParam = new MyOrderParam();
+                new MyOrderParam.OrderInfo("hotValue", false, null);
+                orderBy = MyOrderParam.buildOrderBy(myOrderParam, TourTourismProjectTravelNotes.class);
+
+                List<TourTourismProjectTravelNotes> noteList = tourTourismProjectTravelNotesService.getTourTourismProjectTravelNotesList(filter2, orderBy);
+                if (noteList != null && !noteList.isEmpty()) {
+                    List<IndexHotProjectTravelNoteVo> vo = MyModelUtil.copyCollectionTo(noteList, IndexHotProjectTravelNoteVo.class);
+                    if (vo != null && !vo.isEmpty()) {
+                        vo.forEach(o -> {
+                            List<String> strings = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), o.getHomeHotPicture());
+                            o.setHomeHotPicturesAfterConvert(strings);
+                        });
+                        sumList.addAll(vo);
+                    }
+                }
+            }
+            map.put(info.getMenuName(), sumList==null||sumList.isEmpty()?null:sumList.stream().limit(6).collect(Collectors.toList()));
+        }
+        return ResponseResult.success(map);
+    }
+
+    /**
+     * 首页优质推荐项目。
+     *
+     * @return 应答结果对象。
+     */
+    @GetMapping("/queryIndexRecommendationProject")
+    public ResponseResult<MyPageData<IndexDestinationProjectVo>> queryIndexRecommendationProject( PageInfo pageInfo) {
+
+        TourismProject filter = new TourismProject();
+        filter.setEnable(1);
+        filter.setDataState(1);
+
+        MyOrderParam myOrderParam = new MyOrderParam();
+        new MyOrderParam.OrderInfo("recommendationRate",true, null);
+        new MyOrderParam.OrderInfo("pageViewCount",true, null);
+        new MyOrderParam.OrderInfo("likeCount",true, null);
+        String orderBy = MyOrderParam.buildOrderBy(myOrderParam, TourismProject.class);
+
+
+        int pageNum = pageInfo.getPageNum() != null ? pageInfo.getPageNum() : 1;
+        int pageSize = pageInfo.getPageSize() != null ? pageInfo.getPageSize() : 8;
+
+        int offset = (pageNum - 1) * pageSize + 2; // 跳过前两条数据
+
+        PageMethod.startPage(offset / pageSize + 1, pageSize);
+
+        List<TourismProject> list = tourismProjectService.getTourismProjectList(filter, orderBy);
+
+        MyPageData<IndexDestinationProjectVo> pageData = MyPageUtil.makeResponseData(list, IndexDestinationProjectVo.class);
+        List<IndexDestinationProjectVo> voList = pageData.getDataList();
+
+        voList.stream().forEach(o->{
+
+            TourismFile filter1 = new TourismFile();
+            filter1.setDataState(1);
+            filter1.setAssociationTableId(o.getId());
+            List<TourismFile> fileList = tourismFileService.getTourismFileList(filter1, null);
+
+            fileList.stream().forEach(file->{
+                List<String> string = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), file.getFileUrl());
+                file.setFileUrlsAfterConvert(string);
+            });
+            o.setFileList(fileList);
+
+            List<String> strings = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), o.getTourismUrl());
+            o.setTourismUrlsAfterConvert(strings);
+            List<String> strings1 = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), o.getHomeHotPicture());
+            o.setHomeHotPicturesAfterConvert(strings1);
+        });
+        return ResponseResult.success(pageData);
+    }
+
+
+    /**
+     * 旅游项目详情byId。
+     *
+     * @return 应答结果对象。
+     */
+    @GetMapping("/queryTourismProjectDetailById")
+    public ResponseResult<TourismProjectVo> queryTourismProjectDetailById(@NotNull(message = "所属目录(id)不能为空") String id) {
+
+        TourismProject tourismProject = tourismProjectService.getByIdWithRelation(id, MyRelationParam.full());
+        if (tourismProject == null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
+        }
+        TourismProjectVo tourismProjectVo = MyModelUtil.copyTo(tourismProject, TourismProjectVo.class);
+
+        List<String> arrayList = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), tourismProjectVo.getTourismUrl());
+        tourismProjectVo.setTourismUrlsAfterConvert(arrayList);
+        List<String> arrayList1 = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), tourismProjectVo.getHomeHotPicture());
+        tourismProjectVo.setHomeHotPicturesAfterConvert(arrayList1);
+        List<String> arrayList2 = UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), tourismProjectVo.getTravelNotesBanner());
+        tourismProjectVo.setTravelNotesBannerAfterConvert(arrayList2);
+
+        if(tourismProjectVo.getTourismFile() != null){
+            // 使用 Jackson 的 ObjectMapper 进行转换
+            ObjectMapper objectMapper = new ObjectMapper();
+            TourismFile tourismFile = objectMapper.convertValue(tourismProjectVo.getTourismFile(), TourismFile.class);
+            if(StringUtils.isNotEmpty(tourismFile.getFileUrl())){
+                tourismFile.setFileUrlsAfterConvert(UrlConvertUtils.urlConvert(applicationConfig.getHostIpPort(), tourismFile.getFileUrl()));
+                tourismProjectVo.setTourismFile(MapConvertUtils.convertObjectToMap(tourismFile));
+            }
+        }
+
+        if (tourismProjectVo.getTourismContent()!=null){
+            ObjectMapper mapper = new ObjectMapper();
+            TourismContent tourismContent = mapper.convertValue(tourismProjectVo.getTourismContent(), TourismContent.class);
+            tourismProjectVo.setTourismContent(MapConvertUtils.convertObjectToMap(tourismContent));
+        }
+
+
+        return ResponseResult.success(tourismProjectVo);
+    }
+
+    /**
+     * 游记详情byId。
+     *
+     * @return 应答结果对象。
+     */
+    @GetMapping("/queryTourismNoteDetailById")
+    public ResponseResult<TourismNoteDetailVo> queryTourismNoteDetailById(@NotNull(message = "所属目录(id)不能为空") String id) {
+
+        TourismNoteDetailVo detailVo = new TourismNoteDetailVo();
+        TourTourismProjectTravelNotes note = tourTourismProjectTravelNotesService.getByIdWithRelation(id, MyRelationParam.normal());
+
+        TourTourismProjectTravelNotesVo vo1 = MyModelUtil.copyTo(note, TourTourismProjectTravelNotesVo.class);
+        TourTourismTravelNotesContent filter = new TourTourismTravelNotesContent();
+        filter.setAssociationId(id);
+        filter.setDataState(1);
+        List<TourTourismTravelNotesContent> list = tourTourismTravelNotesContentService.getTourTourismTravelNotesContentList(filter, null);
+        if (list!=null&&!list.isEmpty()){
+            TourTourismTravelNotesContent content = list.get(0);
+            TourTourismTravelNotesContentVo vo = MyModelUtil.copyTo(content, TourTourismTravelNotesContentVo.class);
+            detailVo.setTourTourismTravelNotesContentVo(vo);
+        }
+        detailVo.setTourTourismProjectTravelNotesVo(vo1);
+        return ResponseResult.success(detailVo);
+    }
+
+}

+ 35 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/dto/BestNewShopDto.java

@@ -0,0 +1,35 @@
+package com.tourism.webadmin.app.wechat.dto;
+
+import com.tourism.common.core.validator.ConstDictRef;
+import com.tourism.webadmin.back.model.constant.IsBestNewShop;
+import com.tourism.webadmin.back.model.constant.IsSelectShop;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+/**
+ * BestNewShopDto。
+ *
+ * @author huangwen
+ * @date 2024/11/2
+ */
+@Schema(description = "BestNewShopDto")
+@Data
+public class BestNewShopDto extends PageInfo {
+
+    /**
+     * 精选好店 标记 1是 2 不是。
+     * NOTE: 可支持等于操作符的列表数据过滤。
+     */
+    @Schema(description = "精选好店 标记 1是 2 不是。可支持等于操作符的列表数据过滤。")
+    @ConstDictRef(constDictClass = IsSelectShop.class, message = "数据验证失败,精选好店 标记 1是 2 不是为无效值!")
+    private Integer isSelectShop;
+
+    /**
+     * 优选新店 标记 1是 2 不是。
+     * NOTE: 可支持等于操作符的列表数据过滤。
+     */
+    @Schema(description = "优选新店 标记 1是 2 不是。可支持等于操作符的列表数据过滤。")
+    @ConstDictRef(constDictClass = IsBestNewShop.class, message = "数据验证失败,优选新店 标记 1是 2 不是为无效值!")
+    private Integer isBestNewShop;
+
+}

+ 28 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/dto/DeliveryDefaultAddressDto.java

@@ -0,0 +1,28 @@
+package com.tourism.webadmin.app.wechat.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+/**
+ * DeliveryDefaultAddressDto接口。
+ *
+ * @author huangwen
+ * @date 2024/11/2
+ */
+@Schema(description = "DeliveryDefaultAddressDto对象")
+@Data
+public class DeliveryDefaultAddressDto {
+
+    /**
+     * 地址id。
+     */
+    @Schema(description = "地址id。")
+    private String id;
+
+    /**
+     * 是否默认地址 1默认。
+     */
+    @Schema(description = "是否默认地址 1默认 2不默认")
+    private Integer isDefault;
+
+}

+ 30 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/dto/MapDto.java

@@ -0,0 +1,30 @@
+package com.tourism.webadmin.app.wechat.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+/**
+ * POI搜索参数对象。
+ *
+ * @author limeng
+ * @date 2024-09-30
+ */
+@Schema(description = "MapDto对象")
+@Data
+public class MapDto {
+
+    /**
+     * 限定城市。
+     */
+    @Schema(description = "限定城市")
+    @NotNull(message = "限定城市不能为空!")
+    private String city;
+
+    /**
+     * 搜索关键字。
+     */
+    @Schema(description = "搜索关键字")
+    @NotNull(message = "搜索关键字不能为空!")
+    private String keywords;
+}

+ 33 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/dto/PageInfo.java

@@ -0,0 +1,33 @@
+package com.tourism.webadmin.app.wechat.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+/**
+ * PageInfo接口。
+ *
+ * @author huangwen
+ * @date 2024/10/23
+ */
+@Data
+public class PageInfo {
+
+    /**
+     * 分页号码,从1开始计数。
+     */
+    @Schema(description = "分页号码。")
+    private Integer pageNum;
+
+    /**
+     * 每页大小。
+     */
+    @Schema(description = "每页大小。")
+    private Integer pageSize;
+
+    /**
+     * 是否统计totalCount。
+     */
+    @Schema(description = "是否统计totalCount。")
+    private Boolean count;
+}

+ 37 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/dto/ShopFoodDto.java

@@ -0,0 +1,37 @@
+package com.tourism.webadmin.app.wechat.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import shade.jetbrains.annotations.NotNull;
+
+import static io.lettuce.core.pubsub.PubSubOutput.Type.message;
+
+/**
+ * ShopFoodDto接口。
+ *
+ * @author huangwen
+ * @date 2024/10/23
+ */
+@Schema(description = "MapDto对象")
+@Data
+public class ShopFoodDto extends PageInfo{
+    /**
+     * 店铺restaurantId。
+     */
+    @NotNull
+    @Schema(description = "店铺restaurantId")
+    private String restaurantId;
+
+    /**
+     * 饭馆美食种类id。
+     */
+    @Schema(description = "饭馆美食种类id")
+    private Long foodTypeId;
+
+    /**
+     * 菜品名称。
+     */
+    @Schema(description = "菜品名称")
+    private String name;
+
+}

+ 40 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/dto/TourRestaurantCartChangeDto.java

@@ -0,0 +1,40 @@
+package com.tourism.webadmin.app.wechat.dto;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+/**
+ * 美食购物车修改Dto对象。
+ *
+ * @author chenchen
+ * @date 2024-09-30
+ */
+@Schema(description = "TourRestaurantCartChangeDto对象")
+@Data
+public class TourRestaurantCartChangeDto {
+
+    /**
+     * 购物车的数量。
+     */
+    @Schema(description = "购物车的数量")
+    @NotNull(message = "购物车的数量,必传")
+    private Integer count;
+
+    /**
+     * 餐馆id。
+     */
+    @Schema(description = "餐馆id")
+    @NotNull(message = "餐馆id,必传")
+    private Long restaurantId;
+
+    /**
+     * 美食id。
+     */
+    @Schema(description = "美食id")
+    @NotNull(message = "美食id,必传")
+    private Long restaurantFoodId;
+
+
+}

+ 44 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/dto/WechatIndexJobProjectDto.java

@@ -0,0 +1,44 @@
+package com.tourism.webadmin.app.wechat.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+/**
+ * WechatIndexJobProjectVo接口。
+ *
+ * @author huangwen
+ * @date 2024/11/1
+ */
+@Schema(description = "WechatIndexJobProjectVo视图对象")
+@Data
+public class WechatIndexJobProjectDto extends PageInfo{
+    /**
+     * 是否急聘(0:否 1:是)。
+     */
+    @Schema(description = "是否急聘(0:否 1:是)")
+    private Integer isUrgent;
+
+    /**
+     * 是否热点。
+     */
+    @Schema(description = "是否热点,0非热点,1热点")
+    private Integer isHotspot;
+
+    /**
+     * 是否最新。
+     */
+    @Schema(description = "是否最新,1最新")
+    private Integer isNew;
+
+    /**
+     * 是否启用。
+     */
+    @Schema(description = "是否启用,0禁用,1启用")
+    private Integer enable;
+
+    /**
+     * 项目Title。
+     */
+    @Schema(description = "项目Title")
+    private String jobTitle;
+}

+ 92 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/model/RestaurantFoodType.java

@@ -0,0 +1,92 @@
+package com.tourism.webadmin.app.wechat.model;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.tourism.webadmin.back.model.RestaurantInfo;
+import com.tourism.webadmin.back.model.constant.Enable;
+import com.tourism.common.core.util.MyCommonUtil;
+import com.tourism.common.core.annotation.*;
+import com.tourism.common.core.base.model.BaseModel;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.Map;
+
+/**
+ * 餐馆美食类型实体对象。
+ *
+ * @author 吃饭睡觉
+ * @date 2024-09-06
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName(value = "tour_restaurant_food_type")
+public class RestaurantFoodType extends BaseModel {
+
+    /**
+     * 主键id。
+     */
+    @TableId(value = "id")
+    private Long id;
+
+    /**
+     * 种类名称。
+     */
+    @TableField(value = "name")
+    private String name;
+
+    /**
+     * 餐馆id。
+     */
+    @DeptFilterColumn
+    @TableField(value = "restaurant_id")
+    private Long restaurantId;
+
+    /**
+     * 是否启用,0禁用,1启用。
+     */
+    @TableField(value = "enable")
+    private Integer enable;
+
+    /**
+     * 显示顺序。
+     */
+    @TableField(value = "show_order")
+    private Integer showOrder;
+
+    /**
+     * 描述。
+     */
+    @TableField(value = "description")
+    private String description;
+
+    /**
+     * 逻辑删除标记字段(1: 正常 -1: 已删除)。
+     */
+    @TableLogic
+    @TableField(value = "data_state")
+    private Integer dataState;
+
+    /**
+     * name LIKE搜索字符串。
+     */
+    @TableField(exist = false)
+    private String searchString;
+
+    public void setSearchString(String searchString) {
+        this.searchString = MyCommonUtil.replaceSqlWildcard(searchString);
+    }
+
+    @RelationDict(
+            masterIdField = "restaurantId",
+            slaveModelClass = RestaurantInfo.class,
+            slaveIdField = "id",
+            slaveNameField = "name")
+    @TableField(exist = false)
+    private Map<String, Object> restaurantIdDictMap;
+
+    @RelationConstDict(
+            masterIdField = "enable",
+            constantDictClass = Enable.class)
+    @TableField(exist = false)
+    private Map<String, Object> enableDictMap;
+}

+ 95 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/model/WechatRestaurantCart.java

@@ -0,0 +1,95 @@
+package com.tourism.webadmin.app.wechat.model;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.tourism.common.core.base.vo.BaseVo;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.math.BigDecimal;
+
+/**
+ * 外卖购物车VO视图对象。
+ *
+ * @author 吃饭睡觉
+ * @date 2024-09-06
+ */
+@Schema(description = "RestaurantCartVO视图对象")
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class WechatRestaurantCart extends BaseVo {
+
+    /**
+     * 主键id。
+     */
+    @TableId(value = "id")
+    private Long id;
+
+    /**
+     * 用户id。
+     */
+    @TableId(value = "userId")
+    private Long userId;
+
+    /**
+     * 餐馆id。
+     */
+    @TableId(value = "restaurant_id")
+    private Long restaurantId;
+
+    /**
+     * 美食id。
+     */
+    @TableId(value = "restaurant_food_id")
+    private Long restaurantFoodId;
+
+    /**
+     * 数量。
+     */
+    @TableId(value = "count")
+    private Integer count;
+
+    /**
+     * 价格。
+     */
+    @TableId(value = "price")
+    private BigDecimal price;
+
+    /**
+     * 菜品名称。
+     */
+    @TableId(value = "name")
+    private String name;
+
+    /**
+     * 菜品图片。
+     */
+    @TableId(value = "url")
+    private String url;
+
+    /**
+     * 菜品销量。
+     */
+    @TableId(value = "sales")
+    private Integer sales;
+
+    /**
+     * 价格单位。
+     */
+    @TableField(value = "price_unit")
+    private String priceUnit;
+
+    /**
+     * 打包费。
+     */
+    @TableField(value = "baling_charge")
+    private BigDecimal balingCharge;
+
+    /**
+     * 是否禁用。
+     */
+    @TableField(value = "enable")
+    private Integer enable;
+
+}

+ 57 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/vo/shopfood/SelectShopFoodVo.java

@@ -0,0 +1,57 @@
+package com.tourism.webadmin.app.wechat.vo.shopfood;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * SelectShopFoodVo接口。
+ *
+ * @author huangwen
+ * @date 2024/10/23
+ */
+@Schema(description = "SelectShopFoodVo视图对象")
+@Data
+public class SelectShopFoodVo {
+
+    /**
+     * 主键id。
+     */
+    @Schema(description = "美食id")
+    private String Id;
+
+    /**
+     * 菜品名称。
+     */
+    @Schema(description = "菜品名称")
+    private String name;
+
+    /**
+     * 菜品图片。
+     */
+    @Schema(description = "菜品图片")
+    private String url;
+
+    /**
+     * urlAfterConvert url转换后的地址。
+     */
+    @Schema(description = "urlAfterConvert url转换后的地址")
+    private List<String> urlAfterConvert;
+
+    /**
+     * 菜品价格。
+     */
+    @Schema(description = "菜品价格")
+    private BigDecimal price;
+
+    /**
+     * 价格单位。
+     */
+    @Schema(description = "价格单位")
+    private String priceUnit;
+    @Schema(description = "销量")
+    private Integer sales;
+}

+ 72 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/vo/shopfood/WechatBestNewShopVo.java

@@ -0,0 +1,72 @@
+package com.tourism.webadmin.app.wechat.vo.shopfood;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * 优选新店Vo。
+ *
+ * @author huangwen
+ * @date 2024/10/22
+ */
+@Schema(description = "优选新店Vo")
+@Data
+public class WechatBestNewShopVo {
+
+    /**
+     * 主键id。
+     */
+    @Schema(description = "主键id")
+    private String id;
+
+    /**
+     * 店铺名称。
+     */
+    @Schema(description = "店铺名称")
+    private String name;
+
+    /**
+     * 店铺地址。
+     */
+    @Schema(description = "店铺地址")
+    private String address;
+
+    /**
+     * 店铺图片。
+     */
+    @Schema(description = "店铺图片")
+    private String url;
+
+    /**
+     * urlAfterConvert url转换后的地址。
+     */
+    @Schema(description = "urlAfterConvert url转换后的地址")
+    private List<String> urlAfterConvert;
+
+    /**
+     * 推荐指数。
+     */
+    @TableField(value = "recommendation_rate")
+    private Long recommendationRate;
+    /**
+     * 经度。
+     */
+    @TableField(value = "longitude")
+    private BigDecimal longitude;
+
+    /**
+     * 纬度。
+     */
+    @TableField(value = "latitude")
+    private BigDecimal latitude;
+    /**
+     * 销量。
+     */
+    @TableField(value = "total_sales")
+    private Long totalSales;
+
+}

+ 37 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/vo/shopfood/WechatQueryIndexBannerRestaurantVo.java

@@ -0,0 +1,37 @@
+package com.tourism.webadmin.app.wechat.vo.shopfood;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * WechatQueryIndexBannerRestaurantVo接口。
+ *
+ * @author huangwen
+ * @date 2024/10/22
+ */
+@Schema(description = "RestaurantCartVO视图对象")
+@Data
+public class WechatQueryIndexBannerRestaurantVo {
+
+    /**
+     * id。
+     */
+    @Schema(description = "店铺id")
+    private String id;
+
+    /**
+     * restaurant_banner。
+     */
+    @Schema(description = "restaurant_banner")
+    private String restaurantBanner;
+
+
+    /**
+     * restaurantBannerAfterConvert environmentImage转换后的地址。
+     */
+    @Schema(description = "restaurantBannerAfterConvert url转换后的地址")
+    private String restaurantBannerAfterConvert;
+}

+ 30 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/vo/shopfood/WechatRestaurantCartTotalVo.java

@@ -0,0 +1,30 @@
+package com.tourism.webadmin.app.wechat.vo.shopfood;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * 外卖购物车TotalVO视图对象。
+ *
+ * @author 吃饭睡觉
+ * @date 2024-09-06
+ */
+@Schema(description = "RestaurantCartVO视图对象")
+@Data
+public class WechatRestaurantCartTotalVo {
+
+    /**
+     * 总价格。
+     */
+    @Schema(description = "总价格")
+    private BigDecimal totalPirce;
+
+    /**
+     * 购物车的餐品列表。
+     */
+    @Schema(description = "购物车的餐品列表")
+    private List<WechatRestaurantCartVo> wechatRestaurantCartVoList;
+}

+ 90 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/vo/shopfood/WechatRestaurantCartVo.java

@@ -0,0 +1,90 @@
+package com.tourism.webadmin.app.wechat.vo.shopfood;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.tourism.common.core.base.vo.BaseVo;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.math.BigDecimal;
+
+/**
+ * 外卖购物车VO视图对象。
+ *
+ * @author 吃饭睡觉
+ * @date 2024-09-06
+ */
+@Schema(description = "RestaurantCartVO视图对象")
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class WechatRestaurantCartVo extends BaseVo {
+
+    /**
+     * 主键id。
+     */
+    @Schema(description = "主键id")
+    private Long id;
+
+    /**
+     * 用户id。
+     */
+    @Schema(description = "用户id")
+    private Long userId;
+
+    /**
+     * 餐馆id。
+     */
+    @Schema(description = "餐馆id")
+    private Long restaurantId;
+
+    /**
+     * 美食id。
+     */
+    @Schema(description = "美食id")
+    private Long restaurantFoodId;
+
+    /**
+     * 数量。
+     */
+    @Schema(description = "数量")
+    private Integer count;
+
+    /**
+     * 价格。
+     */
+    @Schema(description = "价格")
+    private BigDecimal price;
+
+    /**
+     * 菜品名称。
+     */
+    @Schema(description = "菜品名称")
+    private String name;
+
+    /**
+     * 菜品图片。
+     */
+    @Schema(description = "菜品图片")
+    private String url;
+
+    /**
+     * 菜品销量。
+     */
+    @Schema(description = "菜品销量")
+    private Integer sales;
+
+    /**
+     * 价格单位。
+     */
+    @Schema(description = "价格单位")
+    @TableField(value = "price_unit")
+    private String priceUnit;
+
+    /**
+     * 打包费。
+     */
+    @Schema(description = "打包费。")
+    @TableField(value = "baling_charge")
+    private BigDecimal balingCharge;
+
+}

+ 104 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/vo/shopfood/WechatSelectShopVo.java

@@ -0,0 +1,104 @@
+package com.tourism.webadmin.app.wechat.vo.shopfood;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * 精选好店Vo。
+ *
+ * @author huangwen
+ * @date 2024/10/22
+ */
+@Schema(description = "精选好店Vo")
+@Data
+public class WechatSelectShopVo {
+
+    /**
+     * 主键id。
+     */
+    @Schema(description = "店铺id")
+    private String id;
+
+    /**
+     * 店铺名称。
+     */
+    @Schema(description = "店铺名称")
+    private String name;
+
+    /**
+     * 店铺标签。
+     */
+    @Schema(description = "店铺标签")
+    private String tag;
+
+    /**
+     * 店铺标签列表。
+     */
+    @Schema(description = "店铺标签")
+    private String[] tags;
+
+
+    /**
+     * 送餐时间/min。
+     */
+    @TableField(value = "delivery_time")
+    private Integer deliveryTime;
+
+
+    /**
+     * 店铺地址。
+     */
+    @Schema(description = "店铺地址")
+    private String address;
+
+    /**
+     * 店铺图片。
+     */
+    @Schema(description = "店铺图片")
+    private String url;
+
+    /**
+     * 推荐指数。
+     */
+    @Schema(description = "推荐指数")
+    private String recommendationRate;
+
+    /**
+     * urlAfterConvert url转换后的地址。
+     */
+    @Schema(description = "urlAfterConvert url转换后的地址")
+    private List<String> urlAfterConvert;
+
+    /**
+     * foodList 精选好店美食列表。
+     */
+    @Schema(description = "foodList 精选好店美食列表")
+    private List<SelectShopFoodVo> foodList;
+
+    /**
+     * 工作时间段(例:8:00-19:00)。
+     */
+    @Schema(description = "工作时间段(例:8:00-19:00)")
+    private String workTime;
+    /**
+     * 经度。
+     */
+    @Schema(description = "经度")
+    private BigDecimal longitude;
+
+    /**
+     * 纬度。
+     */
+    @Schema(description = "纬度")
+    private BigDecimal latitude;
+    /**
+     * 销量。
+     */
+    @Schema(description = "销量")
+    private Long totalSales;
+
+}

+ 38 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/vo/shopfood/WechatShopAndFoodsVo.java

@@ -0,0 +1,38 @@
+package com.tourism.webadmin.app.wechat.vo.shopfood;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.tourism.common.core.object.MyPageData;
+import com.tourism.common.core.util.MyDateUtil;
+import com.tourism.webadmin.back.vo.RestaurantFoodInfoVo;
+import com.tourism.webadmin.back.vo.RestaurantInfoVo;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * WechatShopAndFoodsVo接口。
+ *
+ * @author huangwen
+ * @date 2024/10/23
+ */
+
+
+@Schema(description = "店铺以及食品列表")
+@Data
+public class WechatShopAndFoodsVo {
+
+    /**
+     * 店铺对象。
+     */
+    @Schema(description = "店铺")
+    private RestaurantInfoVo restaurantInfoVo;
+
+    /**
+     * 店铺内食品列表。
+     */
+    @Schema(description = "店铺内食品列表")
+    private MyPageData<RestaurantFoodInfoVo> restaurantFoodInfoVoMyPageData;
+
+
+    }

+ 35 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/vo/tourism/IndexBannerTravelNoteVo.java

@@ -0,0 +1,35 @@
+package com.tourism.webadmin.app.wechat.vo.tourism;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * IndexBannerTravelNoteVo接口。
+ *
+ * @author huangwen
+ * @date 2024/10/22
+ */
+@Schema(description = "IndexBannerTravelNoteVo视图对象")
+@Data
+public class IndexBannerTravelNoteVo {
+
+    /**
+     * id。
+     */
+    @Schema(description = "游记id")
+    private Long id;
+
+    /**
+     * banner图片。
+     */
+    @Schema(description = "banner图片")
+    private String travelNotesBanner ;
+
+    /**
+     * travelNotesBanner 转换后的图片对象。
+     */
+    @Schema(description = "travelNotesBanner转换后的图片对象")
+    private List<String> travelNotesBannerAfterConvert;
+}

+ 265 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/vo/tourism/IndexDestinationProjectVo.java

@@ -0,0 +1,265 @@
+package com.tourism.webadmin.app.wechat.vo.tourism;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.tourism.webadmin.back.model.TourismFile;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * IndexDestinationProjectVo对象。
+ *
+ * @author huangwen
+ * @date 2024/10/29
+ */
+@Schema(description = "IndexDestinationProjectVo")
+@Data
+public class IndexDestinationProjectVo {
+
+    /**
+     * 主键id。
+     */
+    @Schema(description = "主键id")
+    private String id;
+
+    /**
+     * 项目标题。
+     */
+    @Schema(description = "项目标题")
+    private String projectTitle;
+
+    /**
+     * 项目简述。
+     */
+    @Schema(description = "项目简述")
+    private String remarks;
+
+    /**
+     * 是否热点。
+     */
+    @Schema(description = "是否热点,0非热点,1热点")
+    private Integer isHotspot;
+
+    /**
+     * 标签。
+     */
+    @Schema(description = "标签")
+    private String projectLabel;
+
+    /**
+     * 显示顺序。
+     */
+    @Schema(description = "显示顺序")
+    private Integer showOrder;
+
+    /**
+     * 是否启用。
+     */
+    @Schema(description = "是否启用,0禁用,1启用")
+    private Integer enable;
+
+    /**
+     * 所属分类。
+     */
+    @Schema(description = "所属分类")
+    private String belongTab;
+
+    /**
+     * 出发地。
+     */
+    @Schema(description = "出发地")
+    private String startPlace;
+
+    /**
+     * 目的地。
+     */
+    @Schema(description = "目的地")
+    private String endPlace;
+
+    /**
+     * 总天数。
+     */
+    @Schema(description = "总天数")
+    private String countTimes;
+
+    /**
+     * 项目价格。
+     */
+    @Schema(description = "项目价格")
+    private BigDecimal price;
+
+    /**
+     * 项目展示图片。
+     */
+    @Schema(description = "项目展示图片")
+    private String tourismUrl;
+
+    /**
+     * 价格单位。
+     */
+    @Schema(description = "价格单位")
+    private String priceUnit;
+
+    /**
+     * 服务保障。
+     */
+    @Schema(description = "服务保障")
+    private String serviceEnsure;
+
+    /**
+     * 联系电话。
+     */
+    @Schema(description = "联系电话")
+    private String contactNumber;
+
+    /**
+     * 产品卖点。
+     */
+    @Schema(description = "产品卖点")
+    private String sellingPoint;
+
+    /**
+     * 短标题。
+     */
+    @Schema(description = "短标题")
+    private String shortTitle;
+
+    /**
+     * 短描述。
+     */
+    @Schema(description = "短描述")
+    private String shortDescription;
+
+    /**
+     * 首页热门图片。
+     */
+    @Schema(description = "首页热门图片")
+    private String homeHotPicture;
+
+    /**
+     * 联系人描述。
+     */
+    @Schema(description = "联系人描述")
+    private String contactDescription;
+
+    /**
+     * 游记banner。
+     */
+    @Schema(description = "游记banner")
+    private String travelNotesBanner;
+
+    /**
+     * 点赞量。
+     */
+    @Schema(description = "点赞量")
+    private Integer likeCount;
+
+    /**
+     * 浏览量。
+     */
+    @Schema(description = "浏览量")
+    private Integer pageViewCount;
+
+    /**
+     * 人物关系。
+     */
+    @Schema(description = "人物关系")
+    private String role;
+
+    /**
+     * 出发时间。
+     */
+    @Schema(description = "出发时间")
+    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
+    private LocalDate departureTime;
+
+    /**
+     * 人均费用。
+     */
+    @Schema(description = "人均费用")
+    private String averageCost;
+
+    /**
+     * 推荐指数。
+     */
+    @Schema(description = "推荐指数")
+    private Integer recommendationRate;
+
+//    /**
+//     * 上传的联系二维码。
+//     */
+//    @Schema(description = "上传的联系二维码")
+//    private String contactCode;
+
+//    /**
+//     * 是否优质推荐。
+//     */
+//    @Schema(description = "是否优质推荐")
+//    private Integer isQualityRecommendation;
+
+
+    /**
+     * id 的一对一关联数据对象,数据对应类型为TourismFileVo。
+     */
+    @Schema(description = "id 的一对一关联数据对象,数据对应类型为TourismFileVo")
+    private Map<String, Object> tourismFile;
+
+    /**
+     * id 的一对一关联数据对象,数据对应类型为TourismContentVo。
+     */
+    @Schema(description = "id 的一对一关联数据对象,数据对应类型为TourismContentVo")
+    private Map<String, Object> tourismContent;
+
+    /**
+     * belongTab 字典关联数据。
+     */
+    @Schema(description = "belongTab 字典关联数据")
+    private Map<String, Object> belongTabDictMap;
+
+    /**
+     * isHotspot 常量字典关联数据。
+     */
+    @Schema(description = "isHotspot 常量字典关联数据")
+    private Map<String, Object> isHotspotDictMap;
+
+    /**
+     * enable 常量字典关联数据。
+     */
+    @Schema(description = "enable 常量字典关联数据")
+    private Map<String, Object> enableDictMap;
+
+    /**
+     * tourismUrl 转换后的图片对象。
+     */
+    @Schema(description = "tourismUrl转换后的图片对象")
+    private List<String> tourismUrlsAfterConvert;
+
+    /**
+     * homeHotPicture 转换后的图片对象。
+     */
+    @Schema(description = "homeHotPicture转换后的图片对象")
+    private List<String> homeHotPicturesAfterConvert;
+
+    /**
+     * travelNotesBanner 转换后的图片对象。
+     */
+    @Schema(description = "travelNotesBanner转换后的图片对象")
+    private List<String> travelNotesBannerAfterConvert;
+
+//    /**
+//     * contactCode 上传的联系二维码。
+//     */
+//    @Schema(description = "上传的联系二维码 contactCode转换后的图片对象")
+//    private String contactCodeConvert;
+
+
+    /**
+     * fileList 项目图集。
+     */
+    @Schema(description = "fileList 项目图集")
+    private List<TourismFile> fileList;
+}

+ 61 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/vo/tourism/IndexHotDestinationVo.java

@@ -0,0 +1,61 @@
+package com.tourism.webadmin.app.wechat.vo.tourism;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * IndexHotDestinationVo接口。
+ *
+ * @author huangwen
+ * @date 2024/10/23
+ */
+
+@Schema(description = "IndexHotDestinationVo视图对象")
+@Data
+public class IndexHotDestinationVo {
+
+    /**
+     * 项目id。
+     */
+    @Schema(description = "项目id")
+    private String id;
+
+    /**
+     * 一级分类id。
+     */
+    @Schema(description = "一级分类id")
+    private Long belongTab;
+
+    /**
+     * 一级分类名称。
+     */
+    @Schema(description = "一级分类名称")
+    private String menuName;
+
+    /**
+     * 目的地。
+     */
+    @Schema(description = "目的地")
+    private String endPlace;
+
+    /**
+     * 热点图片。
+     */
+    @Schema(description = "热点图片")
+    private String homeHotPicture;
+
+    /**
+     * 标签。
+     */
+    @Schema(description = "标签")
+    private String projectLabel;
+
+    /**
+     * hotPictureUrlsAfterConvert 转换后的热点图片url。
+     */
+    @Schema(description = "转换后的热点图片url")
+    private List<String> homeHotPicturesAfterConvert;
+
+}

+ 58 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/vo/tourism/IndexHotProjectTravelNoteVo.java

@@ -0,0 +1,58 @@
+package com.tourism.webadmin.app.wechat.vo.tourism;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * IndexBannerTravelNoteVo接口。
+ *
+ * @author huangwen
+ * @date 2024/10/22
+ */
+@Schema(description = "IndexBannerTravelNoteVo视图对象")
+@Data
+public class IndexHotProjectTravelNoteVo {
+
+    /**
+     * id。
+     */
+    @Schema(description = "游记id")
+    private Long id;
+
+    /**
+     * 点赞量。
+     */
+    @Schema(description = "点赞量")
+    private Integer likeCount;
+
+    /**
+     * 浏览量。
+     */
+    @Schema(description = "浏览量")
+    private Integer pageViewCount;
+    /**
+     * 项目标题。
+     */
+    @Schema(description = "项目标题")
+    private String projectTitle;
+
+    /**
+     * 短标题。
+     */
+    @Schema(description = "短标题")
+    private String shortTitle;
+
+    /**
+     * 热门图片。
+     */
+    @Schema(description = "热门图片")
+    private String homeHotPicture ;
+
+    /**
+     * homeHotPicture 转换后的图片对象。
+     */
+    @Schema(description = "homeHotPicture转换后的图片对象")
+    private List<String> homeHotPicturesAfterConvert;;
+}

+ 29 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/vo/tourism/TourismNoteDetailVo.java

@@ -0,0 +1,29 @@
+package com.tourism.webadmin.app.wechat.vo.tourism;
+
+import com.tourism.webadmin.back.vo.TourTourismProjectTravelNotesVo;
+import com.tourism.webadmin.back.vo.TourTourismTravelNotesContentVo;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+/**
+ * TourismNoteDetailVo接口。
+ *
+ * @author huangwen
+ * @date 2024/11/2
+ */
+@Schema(description = "TourismNoteDetailVo视图对象")
+@Data
+public class TourismNoteDetailVo {
+
+    /**
+     * 旅游游记。
+     */
+    @Schema(description = "游记vo")
+    private TourTourismProjectTravelNotesVo tourTourismProjectTravelNotesVo;
+
+    /**
+     * 游记富文本。
+     */
+    @Schema(description = "富文本vo")
+    private TourTourismTravelNotesContentVo tourTourismTravelNotesContentVo;
+}

+ 39 - 0
application-webadmin/src/main/java/com/tourism/webadmin/app/wechat/vo/tourism/WechatProjectDetailVo.java

@@ -0,0 +1,39 @@
+package com.tourism.webadmin.app.wechat.vo.tourism;
+
+import com.tourism.webadmin.back.model.TourismContent;
+import com.tourism.webadmin.back.model.TourismFile;
+import com.tourism.webadmin.back.vo.TourismContentVo;
+import com.tourism.webadmin.back.vo.TourismFileVo;
+import com.tourism.webadmin.back.vo.TourismProjectVo;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+/**
+ * WechatProjectDetailVo接口。
+ *
+ * @author huangwen
+ * @date 2024/11/1
+ */
+@Schema(description = "IndexBannerTravelNoteVo视图对象")
+@Data
+public class WechatProjectDetailVo {
+
+    /**
+     * 项目详情信息
+     */
+    @Schema(description = "项目详情信息")
+    private TourismProjectVo tourismProjectVo;
+
+    /**
+     * 项目图集
+     */
+    @Schema(description = "项目图集")
+    private TourismFileVo tourismFileVo;
+
+    /**
+     * 行程路线、费用说明、预订须知
+     */
+    @Schema(description = "行程路线、费用说明、预订须知")
+    private TourismContentVo tourismContentVo;
+
+}

+ 4 - 2
application-webadmin/src/main/java/com/tourism/webadmin/back/controller/BannerInfoController.java

@@ -1,6 +1,7 @@
 package com.tourism.webadmin.back.controller;
 
 import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.dev33.satoken.annotation.SaIgnore;
 import cn.hutool.core.util.ReflectUtil;
 import com.tourism.common.core.upload.BaseUpDownloader;
 import com.tourism.common.core.upload.UpDownloaderFactory;
@@ -18,7 +19,7 @@ import com.tourism.common.core.util.*;
 import com.tourism.common.core.constant.*;
 import com.tourism.common.core.annotation.MyRequestBody;
 import com.tourism.common.redis.cache.SessionCacheHelper;
-import com.tourism.webadmin.config.ApplicationConfig;
+import com.tourism.common.additional.config.ApplicationConfig;
 import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import lombok.extern.slf4j.Slf4j;
@@ -273,7 +274,8 @@ public class BannerInfoController {
      * @param asImage   下载文件是否为图片。
      * @param response  Http 应答对象。
      */
-    @SaCheckPermission("bannerInfo.view")
+//    @SaCheckPermission("bannerInfo.view")
+    @SaIgnore
     @OperationLog(type = SysOperationLogType.DOWNLOAD, saveResponse = false)
     @GetMapping("/download")
     public void download(

+ 3 - 2
application-webadmin/src/main/java/com/tourism/webadmin/back/controller/CompanyInfoController.java

@@ -1,6 +1,7 @@
 package com.tourism.webadmin.back.controller;
 
 import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.dev33.satoken.annotation.SaIgnore;
 import com.alibaba.fastjson.JSONObject;
 import cn.hutool.core.util.ReflectUtil;
 import com.tourism.common.core.upload.BaseUpDownloader;
@@ -19,7 +20,7 @@ import com.tourism.common.core.util.*;
 import com.tourism.common.core.constant.*;
 import com.tourism.common.core.annotation.MyRequestBody;
 import com.tourism.common.redis.cache.SessionCacheHelper;
-import com.tourism.webadmin.config.ApplicationConfig;
+import com.tourism.common.additional.config.ApplicationConfig;
 import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import lombok.extern.slf4j.Slf4j;
@@ -272,7 +273,7 @@ public class CompanyInfoController {
      * @param asImage   下载文件是否为图片。
      * @param response  Http 应答对象。
      */
-    @SaCheckPermission("companyInfo.view")
+    @SaIgnore
     @OperationLog(type = SysOperationLogType.DOWNLOAD, saveResponse = false)
     @GetMapping("/download")
     public void download(

+ 258 - 0
application-webadmin/src/main/java/com/tourism/webadmin/back/controller/DeliveryAddressController.java

@@ -0,0 +1,258 @@
+package com.tourism.webadmin.back.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.dev33.satoken.annotation.SaIgnore;
+import com.tourism.common.log.annotation.OperationLog;
+import com.tourism.common.log.model.constant.SysOperationLogType;
+import com.github.pagehelper.page.PageMethod;
+import com.tourism.common.core.object.*;
+import com.tourism.common.core.util.*;
+import com.tourism.common.core.constant.*;
+import com.tourism.common.core.annotation.MyRequestBody;
+import com.tourism.webadmin.back.dto.DeliveryAddressDto;
+
+import com.tourism.webadmin.back.model.DeliveryAddress;
+import com.tourism.webadmin.back.service.DeliveryAddressService;
+import com.tourism.webadmin.back.vo.DeliveryAddressVo;
+import com.tourism.common.additional.config.ApplicationConfig;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * 外卖地址管理操作控制器类。
+ *
+ * @author 吃饭睡觉
+ * @date 2024-09-06
+ */
+@Tag(name = "外卖地址管理管理接口")
+@Slf4j
+@RestController
+@RequestMapping("/admin/app/deliveryAddress")
+public class DeliveryAddressController {
+
+    @Autowired
+    private ApplicationConfig appConfig;
+    @Autowired
+    private DeliveryAddressService deliveryAddressService;
+
+    /**
+     * 新增外卖地址管理数据。
+     *
+     * @param deliveryAddressDto 新增对象。
+     * @return 应答结果对象,包含新增对象主键Id。
+     */
+    @ApiOperationSupport(ignoreParameters = {"deliveryAddressDto.id"})
+    @SaCheckPermission("deliveryAddress.add")
+    @OperationLog(type = SysOperationLogType.ADD)
+    @PostMapping("/add")
+    public ResponseResult<String> add(@MyRequestBody DeliveryAddressDto deliveryAddressDto) {
+        String errorMessage = MyCommonUtil.getModelValidationError(deliveryAddressDto, false);
+        if (errorMessage != null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
+        }
+        DeliveryAddress deliveryAddress = MyModelUtil.copyTo(deliveryAddressDto, DeliveryAddress.class);
+        deliveryAddress = deliveryAddressService.saveNew(deliveryAddress);
+        return ResponseResult.success(deliveryAddress.getId());
+    }
+
+    /**
+     * 更新外卖地址管理数据。
+     *
+     * @param deliveryAddressDto 更新对象。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("deliveryAddress.update")
+    @OperationLog(type = SysOperationLogType.UPDATE)
+    @PostMapping("/update")
+    public ResponseResult<Void> update(@MyRequestBody DeliveryAddressDto deliveryAddressDto) {
+        String errorMessage = MyCommonUtil.getModelValidationError(deliveryAddressDto, true);
+        if (errorMessage != null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
+        }
+        DeliveryAddress deliveryAddress = MyModelUtil.copyTo(deliveryAddressDto, DeliveryAddress.class);
+        DeliveryAddress originalDeliveryAddress = deliveryAddressService.getById(deliveryAddress.getId());
+        if (originalDeliveryAddress == null) {
+            // NOTE: 修改下面方括号中的话述
+            errorMessage = "数据验证失败,当前 [数据] 并不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        if (!deliveryAddressService.update(deliveryAddress, originalDeliveryAddress)) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
+        }
+        return ResponseResult.success();
+    }
+
+    /**
+     * 删除外卖地址管理数据。
+     *
+     * @param id 删除对象主键Id。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("deliveryAddress.delete")
+    @OperationLog(type = SysOperationLogType.DELETE)
+    @PostMapping("/delete")
+    public ResponseResult<Void> delete(@MyRequestBody String id) {
+        if (MyCommonUtil.existBlankArgument(id)) {
+            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
+        }
+        return this.doDelete(id);
+    }
+
+    /**
+     * 批量删除外卖地址管理数据。
+     *
+     * @param idList 待删除对象的主键Id列表。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("deliveryAddress.delete")
+    @OperationLog(type = SysOperationLogType.DELETE_BATCH)
+    @PostMapping("/deleteBatch")
+    public ResponseResult<Void> deleteBatch(@MyRequestBody List<String> idList) {
+        if (MyCommonUtil.existBlankArgument(idList)) {
+            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
+        }
+        for (String id : idList) {
+            ResponseResult<Void> responseResult = this.doDelete(id);
+            if (!responseResult.isSuccess()) {
+                return responseResult;
+            }
+        }
+        return ResponseResult.success();
+    }
+
+    /**
+     * 列出符合过滤条件的外卖地址管理列表。
+     *
+     * @param deliveryAddressDtoFilter 过滤对象。
+     * @param orderParam 排序参数。
+     * @param pageParam 分页参数。
+     * @return 应答结果对象,包含查询结果集。
+     */
+    @SaCheckPermission("deliveryAddress.view")
+    @PostMapping("/list")
+    public ResponseResult<MyPageData<DeliveryAddressVo>> list(
+            @MyRequestBody DeliveryAddressDto deliveryAddressDtoFilter,
+            @MyRequestBody MyOrderParam orderParam,
+            @MyRequestBody MyPageParam pageParam) {
+        if (pageParam != null) {
+            PageMethod.startPage(pageParam.getPageNum(), pageParam.getPageSize(), pageParam.getCount());
+        }
+        DeliveryAddress deliveryAddressFilter = MyModelUtil.copyTo(deliveryAddressDtoFilter, DeliveryAddress.class);
+        String orderBy = MyOrderParam.buildOrderBy(orderParam, DeliveryAddress.class);
+        List<DeliveryAddress> deliveryAddressList =
+                deliveryAddressService.getDeliveryAddressListWithRelation(deliveryAddressFilter, orderBy);
+        deliveryAddressService.maskFieldDataList(deliveryAddressList, null);
+        return ResponseResult.success(MyPageUtil.makeResponseData(deliveryAddressList, DeliveryAddressVo.class));
+    }
+
+    /**
+     * 导入主表数据列表。
+     *
+     * @param importFile 上传的文件,目前仅仅支持xlsx和xls两种格式。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("deliveryAddress.import")
+    @OperationLog(type = SysOperationLogType.IMPORT)
+    @PostMapping("/import")
+    public ResponseResult<Void> importBatch(
+            @RequestParam Boolean skipHeader,
+            @RequestParam("importFile") MultipartFile importFile) throws IOException {
+        String filename = ImportUtil.saveImportFile(appConfig.getUploadFileBaseDir(), null, importFile);
+        // 这里可以指定需要忽略导入的字段集合。如创建时间、创建人、更新时间、更新人、主键Id和逻辑删除,
+        // 以及一些存在缺省值且无需导入的字段。其中主键字段和逻辑删除字段不需要在这里设置,批量插入逻辑会自动处理的。
+        Set<String> ignoreFieldSet = new HashSet<>();
+        ignoreFieldSet.add("createUserId");
+        ignoreFieldSet.add("createTime");
+        ignoreFieldSet.add("updateUserId");
+        ignoreFieldSet.add("updateTime");
+        List<ImportUtil.ImportHeaderInfo> headerInfoList = ImportUtil.makeHeaderInfoList(DeliveryAddress.class, ignoreFieldSet);
+        // 下面是导入时需要注意的地方,如果我们缺省生成的代码,与实际情况存在差异,请手动修改。
+        // 1. 头信息数据字段,我们只是根据当前的主表实体对象生成了缺省数组,开发者可根据实际情况,对headerInfoList进行修改。
+        ImportUtil.ImportHeaderInfo[] headerInfos = headerInfoList.toArray(new ImportUtil.ImportHeaderInfo[]{});
+        // 2. 这里需要根据实际情况决定,导入文件中第一行是否为中文头信息,如果是可以跳过。这里我们默认为true。
+        // 这里根据自己的实际需求,为doImport的最后一个参数,传递需要进行字典转换的字段集合。
+        // 注意,集合中包含需要翻译的Java字段名,如: gradeId。
+        Set<String> translatedDictFieldSet = new HashSet<>();
+        List<DeliveryAddress> dataList =
+                ImportUtil.doImport(headerInfos, skipHeader, filename, DeliveryAddress.class, translatedDictFieldSet);
+        deliveryAddressService.saveNewBatch(dataList, -1);
+        return ResponseResult.success();
+    }
+
+    /**
+     * 导出符合过滤条件的外卖地址管理列表。
+     *
+     * @param deliveryAddressDtoFilter 过滤对象。
+     * @param orderParam 排序参数。
+     * @throws IOException 文件读写失败。
+     */
+    @SaCheckPermission("deliveryAddress.export")
+    @OperationLog(type = SysOperationLogType.EXPORT, saveResponse = false)
+    @PostMapping("/export")
+    public void export(
+            @MyRequestBody DeliveryAddressDto deliveryAddressDtoFilter,
+            @MyRequestBody MyOrderParam orderParam) throws IOException {
+        DeliveryAddress deliveryAddressFilter = MyModelUtil.copyTo(deliveryAddressDtoFilter, DeliveryAddress.class);
+        String orderBy = MyOrderParam.buildOrderBy(orderParam, DeliveryAddress.class);
+        List<DeliveryAddress> resultList =
+                deliveryAddressService.getDeliveryAddressListWithRelation(deliveryAddressFilter, orderBy);
+        // 导出文件的标题数组
+        // NOTE: 下面的代码中仅仅导出了主表数据,主表聚合计算数据和主表关联字典的数据。
+        // 一对一从表数据的导出,可根据需要自行添加。如:headerMap.put("slaveFieldName.xxxField", "标题名称")
+        Map<String, String> headerMap = new LinkedHashMap<>(12);
+        headerMap.put("id", "主键");
+        headerMap.put("houseNumber", "门牌号");
+        headerMap.put("label", "标签");
+        headerMap.put("contacts", "联系人");
+        headerMap.put("phone", "手机号");
+        headerMap.put("lon", "经度");
+        headerMap.put("lat", "纬度");
+        headerMap.put("createUserId", "创建用户");
+        headerMap.put("createTime", "创建时间");
+        headerMap.put("updateUserId", "更新用户");
+        headerMap.put("updateTime", "更新时间");
+        headerMap.put("dataState", "删除标记(1: 正常 -1: 已删除)");
+        ExportUtil.doExport(resultList, headerMap, "deliveryAddress.xlsx");
+    }
+
+    /**
+     * 查看指定外卖地址管理对象详情。
+     *
+     * @param id 指定对象主键Id。
+     * @return 应答结果对象,包含对象详情。
+     */
+    @SaCheckPermission("deliveryAddress.view")
+    @GetMapping("/view")
+    public ResponseResult<DeliveryAddressVo> view(@RequestParam String id) {
+        DeliveryAddress deliveryAddress = deliveryAddressService.getByIdWithRelation(id, MyRelationParam.full());
+        if (deliveryAddress == null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
+        }
+        deliveryAddressService.maskFieldData(deliveryAddress, null);
+        DeliveryAddressVo deliveryAddressVo = MyModelUtil.copyTo(deliveryAddress, DeliveryAddressVo.class);
+        return ResponseResult.success(deliveryAddressVo);
+    }
+
+    private ResponseResult<Void> doDelete(String id) {
+        String errorMessage;
+        // 验证关联Id的数据合法性
+        DeliveryAddress originalDeliveryAddress = deliveryAddressService.getById(id);
+        if (originalDeliveryAddress == null) {
+            // NOTE: 修改下面方括号中的话述
+            errorMessage = "数据验证失败,当前 [对象] 并不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        if (!deliveryAddressService.remove(id)) {
+            errorMessage = "数据操作失败,删除的对象不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        return ResponseResult.success();
+    }
+}

+ 317 - 0
application-webadmin/src/main/java/com/tourism/webadmin/back/controller/DeliveryOrderController.java

@@ -0,0 +1,317 @@
+package com.tourism.webadmin.back.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import com.alibaba.fastjson.JSONObject;
+import com.tourism.common.log.annotation.OperationLog;
+import com.tourism.common.log.model.constant.SysOperationLogType;
+import com.github.pagehelper.page.PageMethod;
+import com.tourism.webadmin.back.vo.*;
+import com.tourism.webadmin.back.dto.*;
+import com.tourism.webadmin.back.model.*;
+import com.tourism.webadmin.back.service.*;
+import com.tourism.common.core.object.*;
+import com.tourism.common.core.util.*;
+import com.tourism.common.core.constant.*;
+import com.tourism.common.core.annotation.MyRequestBody;
+import com.tourism.common.additional.config.ApplicationConfig;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * 外卖订单管理操作控制器类。
+ *
+ * @author 吃饭睡觉
+ * @date 2024-09-06
+ */
+@Tag(name = "外卖订单管理管理接口")
+@Slf4j
+@RestController
+@RequestMapping("/admin/app/deliveryOrder")
+public class DeliveryOrderController {
+
+    @Autowired
+    private ApplicationConfig appConfig;
+    @Autowired
+    private DeliveryOrderService deliveryOrderService;
+
+    /**
+     * 新增外卖订单管理数据,及其关联的从表数据。
+     *
+     * @param deliveryOrderDto 新增主表对象。
+     * @param deliveryOrderItemsDtoList 一对多外卖订单明细管理从表列表。
+     * @return 应答结果对象,包含新增对象主键Id。
+     */
+    @ApiOperationSupport(ignoreParameters = {"deliveryOrderDto.id"})
+    @SaCheckPermission("deliveryOrder.add")
+    @OperationLog(type = SysOperationLogType.ADD)
+    @PostMapping("/add")
+    public ResponseResult<String> add(
+            @MyRequestBody DeliveryOrderDto deliveryOrderDto,
+            @MyRequestBody List<DeliveryOrderItemsDto> deliveryOrderItemsDtoList) {
+        ResponseResult<Tuple2<DeliveryOrder, JSONObject>> verifyResult =
+                this.doBusinessDataVerifyAndConvert(deliveryOrderDto, false, deliveryOrderItemsDtoList);
+        if (!verifyResult.isSuccess()) {
+            return ResponseResult.errorFrom(verifyResult);
+        }
+        Tuple2<DeliveryOrder, JSONObject> bizData = verifyResult.getData();
+        DeliveryOrder deliveryOrder = bizData.getFirst();
+        deliveryOrder = deliveryOrderService.saveNewWithRelation(deliveryOrder, bizData.getSecond());
+        return ResponseResult.success(deliveryOrder.getId());
+    }
+
+    /**
+     * 修改外卖订单管理数据,及其关联的从表数据。
+     *
+     * @param deliveryOrderDto 修改后的对象。
+     * @param deliveryOrderItemsDtoList 一对多外卖订单明细管理从表列表。
+     * @return 应答结果对象,包含新增对象主键Id。
+     */
+    @ApiOperationSupport(ignoreParameters = {"deliveryOrderDto.id"})
+    @SaCheckPermission("deliveryOrder.update")
+    @OperationLog(type = SysOperationLogType.UPDATE)
+    @PostMapping("/update")
+    public ResponseResult<String> update(
+            @MyRequestBody DeliveryOrderDto deliveryOrderDto,
+            @MyRequestBody List<DeliveryOrderItemsDto> deliveryOrderItemsDtoList) {
+        String errorMessage;
+        ResponseResult<Tuple2<DeliveryOrder, JSONObject>> verifyResult =
+                this.doBusinessDataVerifyAndConvert(deliveryOrderDto, true, deliveryOrderItemsDtoList);
+        if (!verifyResult.isSuccess()) {
+            return ResponseResult.errorFrom(verifyResult);
+        }
+        Tuple2<DeliveryOrder, JSONObject> bizData = verifyResult.getData();
+        DeliveryOrder originalDeliveryOrder = bizData.getSecond().getObject("originalData", DeliveryOrder.class);
+        DeliveryOrder deliveryOrder = bizData.getFirst();
+        if (!deliveryOrderService.updateWithRelation(deliveryOrder, originalDeliveryOrder, bizData.getSecond())) {
+            errorMessage = "数据验证失败,[DeliveryOrder] 数据不存在!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        return ResponseResult.success(deliveryOrder.getId());
+    }
+
+    /**
+     * 删除外卖订单管理数据。
+     *
+     * @param id 删除对象主键Id。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("deliveryOrder.delete")
+    @OperationLog(type = SysOperationLogType.DELETE)
+    @PostMapping("/delete")
+    public ResponseResult<Void> delete(@MyRequestBody String id) {
+        if (MyCommonUtil.existBlankArgument(id)) {
+            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
+        }
+        return this.doDelete(id);
+    }
+
+    /**
+     * 批量删除外卖订单管理数据。
+     *
+     * @param idList 待删除对象的主键Id列表。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("deliveryOrder.delete")
+    @OperationLog(type = SysOperationLogType.DELETE_BATCH)
+    @PostMapping("/deleteBatch")
+    public ResponseResult<Void> deleteBatch(@MyRequestBody List<String> idList) {
+        if (MyCommonUtil.existBlankArgument(idList)) {
+            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
+        }
+        for (String id : idList) {
+            ResponseResult<Void> responseResult = this.doDelete(id);
+            if (!responseResult.isSuccess()) {
+                return responseResult;
+            }
+        }
+        return ResponseResult.success();
+    }
+
+    /**
+     * 列出符合过滤条件的外卖订单管理列表。
+     *
+     * @param deliveryOrderDtoFilter 过滤对象。
+     * @param orderParam 排序参数。
+     * @param pageParam 分页参数。
+     * @return 应答结果对象,包含查询结果集。
+     */
+    @SaCheckPermission("deliveryOrder.view")
+    @PostMapping("/list")
+    public ResponseResult<MyPageData<DeliveryOrderVo>> list(
+            @MyRequestBody DeliveryOrderDto deliveryOrderDtoFilter,
+            @MyRequestBody MyOrderParam orderParam,
+            @MyRequestBody MyPageParam pageParam) {
+        if (pageParam != null) {
+            PageMethod.startPage(pageParam.getPageNum(), pageParam.getPageSize(), pageParam.getCount());
+        }
+        DeliveryOrder deliveryOrderFilter = MyModelUtil.copyTo(deliveryOrderDtoFilter, DeliveryOrder.class);
+        String orderBy = MyOrderParam.buildOrderBy(orderParam, DeliveryOrder.class);
+        List<DeliveryOrder> deliveryOrderList =
+                deliveryOrderService.getDeliveryOrderListWithRelation(deliveryOrderFilter, orderBy);
+        deliveryOrderService.maskFieldDataList(deliveryOrderList, null);
+        return ResponseResult.success(MyPageUtil.makeResponseData(deliveryOrderList, DeliveryOrderVo.class));
+    }
+
+    /**
+     * 导入主表数据列表。
+     *
+     * @param importFile 上传的文件,目前仅仅支持xlsx和xls两种格式。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("deliveryOrder.import")
+    @OperationLog(type = SysOperationLogType.IMPORT)
+    @PostMapping("/import")
+    public ResponseResult<Void> importBatch(
+            @RequestParam Boolean skipHeader,
+            @RequestParam("importFile") MultipartFile importFile) throws IOException {
+        String filename = ImportUtil.saveImportFile(appConfig.getUploadFileBaseDir(), null, importFile);
+        // 这里可以指定需要忽略导入的字段集合。如创建时间、创建人、更新时间、更新人、主键Id和逻辑删除,
+        // 以及一些存在缺省值且无需导入的字段。其中主键字段和逻辑删除字段不需要在这里设置,批量插入逻辑会自动处理的。
+        Set<String> ignoreFieldSet = new HashSet<>();
+        ignoreFieldSet.add("createUserId");
+        ignoreFieldSet.add("createTime");
+        ignoreFieldSet.add("updateUserId");
+        ignoreFieldSet.add("updateTime");
+        List<ImportUtil.ImportHeaderInfo> headerInfoList = ImportUtil.makeHeaderInfoList(DeliveryOrder.class, ignoreFieldSet);
+        // 下面是导入时需要注意的地方,如果我们缺省生成的代码,与实际情况存在差异,请手动修改。
+        // 1. 头信息数据字段,我们只是根据当前的主表实体对象生成了缺省数组,开发者可根据实际情况,对headerInfoList进行修改。
+        ImportUtil.ImportHeaderInfo[] headerInfos = headerInfoList.toArray(new ImportUtil.ImportHeaderInfo[]{});
+        // 2. 这里需要根据实际情况决定,导入文件中第一行是否为中文头信息,如果是可以跳过。这里我们默认为true。
+        // 这里根据自己的实际需求,为doImport的最后一个参数,传递需要进行字典转换的字段集合。
+        // 注意,集合中包含需要翻译的Java字段名,如: gradeId。
+        Set<String> translatedDictFieldSet = new HashSet<>();
+        List<DeliveryOrder> dataList =
+                ImportUtil.doImport(headerInfos, skipHeader, filename, DeliveryOrder.class, translatedDictFieldSet);
+        CallResult result = deliveryOrderService.verifyImportList(dataList, translatedDictFieldSet);
+        if (!result.isSuccess()) {
+            // result中返回了具体的验证失败对象,如果需要返回更加详细的错误,可根据实际情况手动修改。
+            return ResponseResult.errorFrom(result);
+        }
+        deliveryOrderService.saveNewBatch(dataList, -1);
+        return ResponseResult.success();
+    }
+
+    /**
+     * 导出符合过滤条件的外卖订单管理列表。
+     *
+     * @param deliveryOrderDtoFilter 过滤对象。
+     * @param orderParam 排序参数。
+     * @throws IOException 文件读写失败。
+     */
+    @SaCheckPermission("deliveryOrder.export")
+    @OperationLog(type = SysOperationLogType.EXPORT, saveResponse = false)
+    @PostMapping("/export")
+    public void export(
+            @MyRequestBody DeliveryOrderDto deliveryOrderDtoFilter,
+            @MyRequestBody MyOrderParam orderParam) throws IOException {
+        DeliveryOrder deliveryOrderFilter = MyModelUtil.copyTo(deliveryOrderDtoFilter, DeliveryOrder.class);
+        String orderBy = MyOrderParam.buildOrderBy(orderParam, DeliveryOrder.class);
+        List<DeliveryOrder> resultList =
+                deliveryOrderService.getDeliveryOrderListWithRelation(deliveryOrderFilter, orderBy);
+        // 导出文件的标题数组
+        // NOTE: 下面的代码中仅仅导出了主表数据,主表聚合计算数据和主表关联字典的数据。
+        // 一对一从表数据的导出,可根据需要自行添加。如:headerMap.put("slaveFieldName.xxxField", "标题名称")
+        Map<String, String> headerMap = new LinkedHashMap<>(22);
+        headerMap.put("id", "主键");
+        headerMap.put("deliveryTime", "配送时间");
+        headerMap.put("deliveryAddress", "配送地址");
+        headerMap.put("deliveryPhone", "收货电话");
+        headerMap.put("deliveryRemark", "备注");
+        headerMap.put("tableware", "餐具数量");
+        headerMap.put("orderNo", "订单号");
+        headerMap.put("orderTime", "下单时间");
+        headerMap.put("payTypeDictMap.name", "支付方式");
+        headerMap.put("orderAmount", "订单金额");
+        headerMap.put("payAmount", "实付金额");
+        headerMap.put("orderStatusDictMap.name", "订单状态");
+        headerMap.put("customerId", "用户id");
+        headerMap.put("refundStatusDictMap.name", "退款状态");
+        headerMap.put("packCharge", "打包费");
+        headerMap.put("deliveryFee", "配送费");
+        headerMap.put("createUserId", "创建用户");
+        headerMap.put("createTime", "创建时间");
+        headerMap.put("updateUserId", "更新用户");
+        headerMap.put("updateTime", "更新时间");
+        headerMap.put("dataState", "删除标记(1: 正常 -1: 已删除)");
+        headerMap.put("shopName", "店铺名称");
+        ExportUtil.doExport(resultList, headerMap, "deliveryOrder.xlsx");
+    }
+
+    /**
+     * 查看指定外卖订单管理对象详情。
+     *
+     * @param id 指定对象主键Id。
+     * @return 应答结果对象,包含对象详情。
+     */
+    @SaCheckPermission("deliveryOrder.view")
+    @GetMapping("/view")
+    public ResponseResult<DeliveryOrderVo> view(@RequestParam String id) {
+        DeliveryOrder deliveryOrder = deliveryOrderService.getByIdWithRelation(id, MyRelationParam.full());
+        if (deliveryOrder == null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
+        }
+        deliveryOrderService.maskFieldData(deliveryOrder, null);
+        DeliveryOrderVo deliveryOrderVo = MyModelUtil.copyTo(deliveryOrder, DeliveryOrderVo.class);
+        return ResponseResult.success(deliveryOrderVo);
+    }
+
+    private ResponseResult<Tuple2<DeliveryOrder, JSONObject>> doBusinessDataVerifyAndConvert(
+            DeliveryOrderDto deliveryOrderDto,
+            boolean forUpdate,
+            List<DeliveryOrderItemsDto> deliveryOrderItemsDtoList) {
+        ErrorCodeEnum errorCode = ErrorCodeEnum.DATA_VALIDATED_FAILED;
+        String errorMessage = MyCommonUtil.getModelValidationError(deliveryOrderDto, false);
+        if (errorMessage != null) {
+            return ResponseResult.error(errorCode, errorMessage);
+        }
+        errorMessage = MyCommonUtil.getModelValidationError(deliveryOrderItemsDtoList);
+        if (errorMessage != null) {
+            return ResponseResult.error(errorCode, "参数 [deliveryOrderItemsDtoList] " + errorMessage);
+        }
+        // 全部关联从表数据的验证和转换
+        JSONObject relationData = new JSONObject();
+        CallResult verifyResult;
+        // 下面是输入参数中,主表关联数据的验证。
+        DeliveryOrder deliveryOrder = MyModelUtil.copyTo(deliveryOrderDto, DeliveryOrder.class);
+        DeliveryOrder originalData = null;
+        if (forUpdate && deliveryOrder != null) {
+            originalData = deliveryOrderService.getById(deliveryOrder.getId());
+            if (originalData == null) {
+                return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
+            }
+            relationData.put("originalData", originalData);
+        }
+        verifyResult = deliveryOrderService.verifyRelatedData(deliveryOrder, originalData);
+        if (!verifyResult.isSuccess()) {
+            return ResponseResult.errorFrom(verifyResult);
+        }
+        // 处理主表的一对多关联 [DeliveryOrderItems]
+        List<DeliveryOrderItems> deliveryOrderItemsList =
+                MyModelUtil.copyCollectionTo(deliveryOrderItemsDtoList, DeliveryOrderItems.class);
+        relationData.put("deliveryOrderItemsList", deliveryOrderItemsList);
+        return ResponseResult.success(new Tuple2<>(deliveryOrder, relationData));
+    }
+
+    private ResponseResult<Void> doDelete(String id) {
+        String errorMessage;
+        // 验证关联Id的数据合法性
+        DeliveryOrder originalDeliveryOrder = deliveryOrderService.getById(id);
+        if (originalDeliveryOrder == null) {
+            // NOTE: 修改下面方括号中的话述
+            errorMessage = "数据验证失败,当前 [对象] 并不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        if (!deliveryOrderService.remove(id)) {
+            errorMessage = "数据操作失败,删除的对象不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        return ResponseResult.success();
+    }
+}

+ 252 - 0
application-webadmin/src/main/java/com/tourism/webadmin/back/controller/DeliveryOrderItemsController.java

@@ -0,0 +1,252 @@
+package com.tourism.webadmin.back.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import com.tourism.common.log.annotation.OperationLog;
+import com.tourism.common.log.model.constant.SysOperationLogType;
+import com.github.pagehelper.page.PageMethod;
+import com.tourism.webadmin.back.vo.*;
+import com.tourism.webadmin.back.dto.*;
+import com.tourism.webadmin.back.model.*;
+import com.tourism.webadmin.back.service.*;
+import com.tourism.common.core.object.*;
+import com.tourism.common.core.util.*;
+import com.tourism.common.core.constant.*;
+import com.tourism.common.core.annotation.MyRequestBody;
+import com.tourism.common.additional.config.ApplicationConfig;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * 外卖订单明细管理操作控制器类。
+ *
+ * @author 吃饭睡觉
+ * @date 2024-09-06
+ */
+@Tag(name = "外卖订单明细管理管理接口")
+@Slf4j
+@RestController
+@RequestMapping("/admin/app/deliveryOrderItems")
+public class DeliveryOrderItemsController {
+
+    @Autowired
+    private ApplicationConfig appConfig;
+    @Autowired
+    private DeliveryOrderItemsService deliveryOrderItemsService;
+
+    /**
+     * 新增外卖订单明细管理数据。
+     *
+     * @param deliveryOrderItemsDto 新增对象。
+     * @return 应答结果对象,包含新增对象主键Id。
+     */
+    @ApiOperationSupport(ignoreParameters = {"deliveryOrderItemsDto.id"})
+    @SaCheckPermission("deliveryOrderItems.add")
+    @OperationLog(type = SysOperationLogType.ADD)
+    @PostMapping("/add")
+    public ResponseResult<String> add(@MyRequestBody DeliveryOrderItemsDto deliveryOrderItemsDto) {
+        String errorMessage = MyCommonUtil.getModelValidationError(deliveryOrderItemsDto, false);
+        if (errorMessage != null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
+        }
+        DeliveryOrderItems deliveryOrderItems = MyModelUtil.copyTo(deliveryOrderItemsDto, DeliveryOrderItems.class);
+        deliveryOrderItems = deliveryOrderItemsService.saveNew(deliveryOrderItems);
+        return ResponseResult.success(deliveryOrderItems.getId());
+    }
+
+    /**
+     * 更新外卖订单明细管理数据。
+     *
+     * @param deliveryOrderItemsDto 更新对象。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("deliveryOrderItems.update")
+    @OperationLog(type = SysOperationLogType.UPDATE)
+    @PostMapping("/update")
+    public ResponseResult<Void> update(@MyRequestBody DeliveryOrderItemsDto deliveryOrderItemsDto) {
+        String errorMessage = MyCommonUtil.getModelValidationError(deliveryOrderItemsDto, true);
+        if (errorMessage != null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
+        }
+        DeliveryOrderItems deliveryOrderItems = MyModelUtil.copyTo(deliveryOrderItemsDto, DeliveryOrderItems.class);
+        DeliveryOrderItems originalDeliveryOrderItems = deliveryOrderItemsService.getById(deliveryOrderItems.getId());
+        if (originalDeliveryOrderItems == null) {
+            // NOTE: 修改下面方括号中的话述
+            errorMessage = "数据验证失败,当前 [数据] 并不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        if (!deliveryOrderItemsService.update(deliveryOrderItems, originalDeliveryOrderItems)) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
+        }
+        return ResponseResult.success();
+    }
+
+    /**
+     * 删除外卖订单明细管理数据。
+     *
+     * @param id 删除对象主键Id。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("deliveryOrderItems.delete")
+    @OperationLog(type = SysOperationLogType.DELETE)
+    @PostMapping("/delete")
+    public ResponseResult<Void> delete(@MyRequestBody Long id) {
+        if (MyCommonUtil.existBlankArgument(id)) {
+            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
+        }
+        return this.doDelete(id);
+    }
+
+    /**
+     * 批量删除外卖订单明细管理数据。
+     *
+     * @param idList 待删除对象的主键Id列表。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("deliveryOrderItems.delete")
+    @OperationLog(type = SysOperationLogType.DELETE_BATCH)
+    @PostMapping("/deleteBatch")
+    public ResponseResult<Void> deleteBatch(@MyRequestBody List<Long> idList) {
+        if (MyCommonUtil.existBlankArgument(idList)) {
+            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
+        }
+        for (Long id : idList) {
+            ResponseResult<Void> responseResult = this.doDelete(id);
+            if (!responseResult.isSuccess()) {
+                return responseResult;
+            }
+        }
+        return ResponseResult.success();
+    }
+
+    /**
+     * 列出符合过滤条件的外卖订单明细管理列表。
+     *
+     * @param deliveryOrderItemsDtoFilter 过滤对象。
+     * @param orderParam 排序参数。
+     * @param pageParam 分页参数。
+     * @return 应答结果对象,包含查询结果集。
+     */
+    @SaCheckPermission("deliveryOrderItems.view")
+    @PostMapping("/list")
+    public ResponseResult<MyPageData<DeliveryOrderItemsVo>> list(
+            @MyRequestBody DeliveryOrderItemsDto deliveryOrderItemsDtoFilter,
+            @MyRequestBody MyOrderParam orderParam,
+            @MyRequestBody MyPageParam pageParam) {
+        if (pageParam != null) {
+            PageMethod.startPage(pageParam.getPageNum(), pageParam.getPageSize(), pageParam.getCount());
+        }
+        DeliveryOrderItems deliveryOrderItemsFilter = MyModelUtil.copyTo(deliveryOrderItemsDtoFilter, DeliveryOrderItems.class);
+        String orderBy = MyOrderParam.buildOrderBy(orderParam, DeliveryOrderItems.class);
+        List<DeliveryOrderItems> deliveryOrderItemsList =
+                deliveryOrderItemsService.getDeliveryOrderItemsListWithRelation(deliveryOrderItemsFilter, orderBy);
+        return ResponseResult.success(MyPageUtil.makeResponseData(deliveryOrderItemsList, DeliveryOrderItemsVo.class));
+    }
+
+    /**
+     * 导入主表数据列表。
+     *
+     * @param importFile 上传的文件,目前仅仅支持xlsx和xls两种格式。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("deliveryOrderItems.import")
+    @OperationLog(type = SysOperationLogType.IMPORT)
+    @PostMapping("/import")
+    public ResponseResult<Void> importBatch(
+            @RequestParam Boolean skipHeader,
+            @RequestParam("importFile") MultipartFile importFile) throws IOException {
+        String filename = ImportUtil.saveImportFile(appConfig.getUploadFileBaseDir(), null, importFile);
+        // 这里可以指定需要忽略导入的字段集合。如创建时间、创建人、更新时间、更新人、主键Id和逻辑删除,
+        // 以及一些存在缺省值且无需导入的字段。其中主键字段和逻辑删除字段不需要在这里设置,批量插入逻辑会自动处理的。
+        Set<String> ignoreFieldSet = new HashSet<>();
+        ignoreFieldSet.add("createUserId");
+        ignoreFieldSet.add("createTime");
+        ignoreFieldSet.add("updateUserId");
+        ignoreFieldSet.add("updateTime");
+        List<ImportUtil.ImportHeaderInfo> headerInfoList = ImportUtil.makeHeaderInfoList(DeliveryOrderItems.class, ignoreFieldSet);
+        // 下面是导入时需要注意的地方,如果我们缺省生成的代码,与实际情况存在差异,请手动修改。
+        // 1. 头信息数据字段,我们只是根据当前的主表实体对象生成了缺省数组,开发者可根据实际情况,对headerInfoList进行修改。
+        ImportUtil.ImportHeaderInfo[] headerInfos = headerInfoList.toArray(new ImportUtil.ImportHeaderInfo[]{});
+        // 2. 这里需要根据实际情况决定,导入文件中第一行是否为中文头信息,如果是可以跳过。这里我们默认为true。
+        // 这里根据自己的实际需求,为doImport的最后一个参数,传递需要进行字典转换的字段集合。
+        // 注意,集合中包含需要翻译的Java字段名,如: gradeId。
+        Set<String> translatedDictFieldSet = new HashSet<>();
+        List<DeliveryOrderItems> dataList =
+                ImportUtil.doImport(headerInfos, skipHeader, filename, DeliveryOrderItems.class, translatedDictFieldSet);
+        deliveryOrderItemsService.saveNewBatch(dataList, -1);
+        return ResponseResult.success();
+    }
+
+    /**
+     * 导出符合过滤条件的外卖订单明细管理列表。
+     *
+     * @param deliveryOrderItemsDtoFilter 过滤对象。
+     * @param orderParam 排序参数。
+     * @throws IOException 文件读写失败。
+     */
+    @SaCheckPermission("deliveryOrderItems.export")
+    @OperationLog(type = SysOperationLogType.EXPORT, saveResponse = false)
+    @PostMapping("/export")
+    public void export(
+            @MyRequestBody DeliveryOrderItemsDto deliveryOrderItemsDtoFilter,
+            @MyRequestBody MyOrderParam orderParam) throws IOException {
+        DeliveryOrderItems deliveryOrderItemsFilter = MyModelUtil.copyTo(deliveryOrderItemsDtoFilter, DeliveryOrderItems.class);
+        String orderBy = MyOrderParam.buildOrderBy(orderParam, DeliveryOrderItems.class);
+        List<DeliveryOrderItems> resultList =
+                deliveryOrderItemsService.getDeliveryOrderItemsListWithRelation(deliveryOrderItemsFilter, orderBy);
+        // 导出文件的标题数组
+        // NOTE: 下面的代码中仅仅导出了主表数据,主表聚合计算数据和主表关联字典的数据。
+        // 一对一从表数据的导出,可根据需要自行添加。如:headerMap.put("slaveFieldName.xxxField", "标题名称")
+        Map<String, String> headerMap = new LinkedHashMap<>(10);
+        headerMap.put("id", "主键");
+        headerMap.put("foodName", "食品名称");
+        headerMap.put("orderId", "订单id");
+        headerMap.put("price", "价格");
+        headerMap.put("number", "数量");
+        headerMap.put("createUserId", "创建用户");
+        headerMap.put("createTime", "创建时间");
+        headerMap.put("updateUserId", "更新用户");
+        headerMap.put("updateTime", "更新时间");
+        headerMap.put("dataState", "删除标记(1: 正常 -1: 已删除)");
+        ExportUtil.doExport(resultList, headerMap, "deliveryOrderItems.xlsx");
+    }
+
+    /**
+     * 查看指定外卖订单明细管理对象详情。
+     *
+     * @param id 指定对象主键Id。
+     * @return 应答结果对象,包含对象详情。
+     */
+    @SaCheckPermission("deliveryOrderItems.view")
+    @GetMapping("/view")
+    public ResponseResult<DeliveryOrderItemsVo> view(@RequestParam String id) {
+        DeliveryOrderItems deliveryOrderItems = deliveryOrderItemsService.getByIdWithRelation(id, MyRelationParam.full());
+        if (deliveryOrderItems == null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
+        }
+        DeliveryOrderItemsVo deliveryOrderItemsVo = MyModelUtil.copyTo(deliveryOrderItems, DeliveryOrderItemsVo.class);
+        return ResponseResult.success(deliveryOrderItemsVo);
+    }
+
+    private ResponseResult<Void> doDelete(Long id) {
+        String errorMessage;
+        // 验证关联Id的数据合法性
+        DeliveryOrderItems originalDeliveryOrderItems = deliveryOrderItemsService.getById(id);
+        if (originalDeliveryOrderItems == null) {
+            // NOTE: 修改下面方括号中的话述
+            errorMessage = "数据验证失败,当前 [对象] 并不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        if (!deliveryOrderItemsService.remove(id)) {
+            errorMessage = "数据操作失败,删除的对象不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        return ResponseResult.success();
+    }
+}

+ 138 - 4
application-webadmin/src/main/java/com/tourism/webadmin/back/controller/DirectoryInfoController.java

@@ -1,7 +1,13 @@
 package com.tourism.webadmin.back.controller;
 
 import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.dev33.satoken.annotation.SaIgnore;
 import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.ReflectUtil;
+import com.tourism.common.core.upload.BaseUpDownloader;
+import com.tourism.common.core.upload.UpDownloaderFactory;
+import com.tourism.common.core.upload.UploadResponseInfo;
+import com.tourism.common.core.upload.UploadStoreInfo;
 import com.tourism.common.log.annotation.OperationLog;
 import com.tourism.common.log.model.constant.SysOperationLogType;
 import com.github.pagehelper.page.PageMethod;
@@ -13,7 +19,8 @@ import com.tourism.common.core.object.*;
 import com.tourism.common.core.util.*;
 import com.tourism.common.core.constant.*;
 import com.tourism.common.core.annotation.MyRequestBody;
-import com.tourism.webadmin.config.ApplicationConfig;
+import com.tourism.common.redis.cache.SessionCacheHelper;
+import com.tourism.common.additional.config.ApplicationConfig;
 import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import lombok.extern.slf4j.Slf4j;
@@ -22,6 +29,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 
+import jakarta.servlet.http.HttpServletResponse;
 import java.io.IOException;
 import java.util.*;
 
@@ -40,6 +48,10 @@ public class DirectoryInfoController {
     @Autowired
     private ApplicationConfig appConfig;
     @Autowired
+    private SessionCacheHelper cacheHelper;
+    @Autowired
+    private UpDownloaderFactory upDownloaderFactory;
+    @Autowired
     private DirectoryInfoService directoryInfoService;
 
     /**
@@ -240,7 +252,7 @@ public class DirectoryInfoController {
         // 导出文件的标题数组
         // NOTE: 下面的代码中仅仅导出了主表数据,主表聚合计算数据和主表关联字典的数据。
         // 一对一从表数据的导出,可根据需要自行添加。如:headerMap.put("slaveFieldName.xxxField", "标题名称")
-        Map<String, String> headerMap = new LinkedHashMap<>(11);
+        Map<String, String> headerMap = new LinkedHashMap<>(14);
         headerMap.put("id", "主键id");
         headerMap.put("parentIdDictMap.name", "父级id");
         headerMap.put("menuName", "菜单名称");
@@ -252,6 +264,9 @@ public class DirectoryInfoController {
         headerMap.put("updateUserId", "更新用户");
         headerMap.put("updateTime", "更新时间");
         headerMap.put("dataState", "删除标记(1: 正常 -1: 已删除)");
+        headerMap.put("isHotspotDictMap.name", "是否热点,0非热点,1热点");
+        headerMap.put("hotOrder", "热门排序");
+        headerMap.put("hotPictureUrl", "劳务图片");
         ExportUtil.doExport(resultList, headerMap, "directoryInfo.xlsx");
     }
 
@@ -273,6 +288,104 @@ public class DirectoryInfoController {
     }
 
     /**
+     * 附件文件下载。
+     * 这里将图片和其他类型的附件文件放到不同的父目录下,主要为了便于今后图片文件的迁移。
+     *
+     * @param id 附件所在记录的主键Id。
+     * @param fieldName 附件所属的字段名。
+     * @param filename  文件名。如果没有提供该参数,就从当前记录的指定字段中读取。
+     * @param asImage   下载文件是否为图片。
+     * @param response  Http 应答对象。
+     */
+    @SaIgnore
+    @OperationLog(type = SysOperationLogType.DOWNLOAD, saveResponse = false)
+    @GetMapping("/download")
+    public void download(
+            @RequestParam(required = false) Long id,
+            @RequestParam String fieldName,
+            @RequestParam String filename,
+            @RequestParam Boolean asImage,
+            HttpServletResponse response) {
+        if (MyCommonUtil.existBlankArgument(fieldName, filename, asImage)) {
+            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+            return;
+        }
+        // 使用try来捕获异常,是为了保证一旦出现异常可以返回500的错误状态,便于调试。
+        // 否则有可能给前端返回的是200的错误码。
+        try {
+            // 如果请求参数中没有包含主键Id,就判断该文件是否为当前session上传的。
+            if (id == null) {
+                if (!cacheHelper.existSessionUploadFile(filename)) {
+                    ResponseResult.output(HttpServletResponse.SC_FORBIDDEN);
+                    return;
+                }
+            } else {
+                DirectoryInfo directoryInfo = directoryInfoService.getById(id);
+                if (directoryInfo == null) {
+                    ResponseResult.output(HttpServletResponse.SC_NOT_FOUND);
+                    return;
+                }
+                String fieldJsonData = (String) ReflectUtil.getFieldValue(directoryInfo, fieldName);
+                if (fieldJsonData == null && !cacheHelper.existSessionUploadFile(filename)) {
+                    ResponseResult.output(HttpServletResponse.SC_BAD_REQUEST);
+                    return;
+                }
+                if (!BaseUpDownloader.containFile(fieldJsonData, filename)
+                        && !cacheHelper.existSessionUploadFile(filename)) {
+                    ResponseResult.output(HttpServletResponse.SC_FORBIDDEN);
+                    return;
+                }
+            }
+            UploadStoreInfo storeInfo = MyModelUtil.getUploadStoreInfo(DirectoryInfo.class, fieldName);
+            if (!storeInfo.isSupportUpload()) {
+                ResponseResult.output(HttpServletResponse.SC_NOT_IMPLEMENTED,
+                        ResponseResult.error(ErrorCodeEnum.INVALID_UPLOAD_FIELD));
+                return;
+            }
+            BaseUpDownloader upDownloader = upDownloaderFactory.get(storeInfo.getStoreType());
+            upDownloader.doDownload(appConfig.getUploadFileBaseDir(),
+                    DirectoryInfo.class.getSimpleName(), fieldName, filename, asImage, response);
+        } catch (Exception e) {
+            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+            log.error(e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 文件上传操作。
+     *
+     * @param fieldName  上传文件名。
+     * @param asImage    是否作为图片上传。如果是图片,今后下载的时候无需权限验证。否则就是附件上传,下载时需要权限验证。
+     * @param uploadFile 上传文件对象。
+     */
+    @SaCheckPermission("directoryInfo.view")
+    @OperationLog(type = SysOperationLogType.UPLOAD, saveResponse = false)
+    @PostMapping("/upload")
+    public void upload(
+            @RequestParam String fieldName,
+            @RequestParam Boolean asImage,
+            @RequestParam("uploadFile") MultipartFile uploadFile) throws IOException {
+        UploadStoreInfo storeInfo = MyModelUtil.getUploadStoreInfo(DirectoryInfo.class, fieldName);
+        // 这里就会判断参数中指定的字段,是否支持上传操作。
+        if (!storeInfo.isSupportUpload()) {
+            ResponseResult.output(HttpServletResponse.SC_FORBIDDEN,
+                    ResponseResult.error(ErrorCodeEnum.INVALID_UPLOAD_FIELD));
+            return;
+        }
+        // 根据字段注解中的存储类型,通过工厂方法获取匹配的上传下载实现类,从而解耦。
+        BaseUpDownloader upDownloader = upDownloaderFactory.get(storeInfo.getStoreType());
+        UploadResponseInfo responseInfo = upDownloader.doUpload(null,
+                appConfig.getUploadFileBaseDir(), DirectoryInfo.class.getSimpleName(), fieldName, asImage, uploadFile);
+        if (Boolean.TRUE.equals(responseInfo.getUploadFailed())) {
+            ResponseResult.output(HttpServletResponse.SC_FORBIDDEN,
+                    ResponseResult.error(ErrorCodeEnum.UPLOAD_FAILED, responseInfo.getErrorMessage()));
+            return;
+        }
+        cacheHelper.putSessionUploadFile(responseInfo.getFilename());
+        ResponseResult.output(ResponseResult.success(responseInfo));
+    }
+
+    /**
      * 以字典形式返回全部门户菜单管理数据集合。字典的键值为[id, menuName]。
      * 白名单接口,登录用户均可访问。
      *
@@ -283,8 +396,9 @@ public class DirectoryInfoController {
     public ResponseResult<List<Map<String, Object>>> listDict(@ParameterObject DirectoryInfoDto filter) {
         List<DirectoryInfo> resultList =
                 directoryInfoService.getListByFilter(MyModelUtil.copyTo(filter, DirectoryInfo.class));
+        List<DirectoryInfoVo> directoryInfoVos = MyModelUtil.copyCollectionTo(resultList, DirectoryInfoVo.class);
         return ResponseResult.success(MyCommonUtil.toDictDataList(
-                resultList, DirectoryInfo::getId, DirectoryInfo::getMenuName, DirectoryInfo::getParentId));
+                directoryInfoVos, DirectoryInfoVo::getId, DirectoryInfoVo::getMenuName, DirectoryInfoVo::getParentId));
     }
 
     /**
@@ -307,11 +421,31 @@ public class DirectoryInfoController {
      * @param parentId 父主键Id。
      * @return 按照字典的形式返回下级数据列表。
      */
+    @SaIgnore
     @GetMapping("/listDictByParentId")
     public ResponseResult<List<Map<String, Object>>> listDictByParentId(@RequestParam(required = false) Long parentId) {
         List<DirectoryInfo> resultList = directoryInfoService.getListByParentId("parentId", parentId);
+        List<DirectoryInfoVo> directoryInfoVos = MyModelUtil.copyCollectionTo(resultList, DirectoryInfoVo.class);
         return ResponseResult.success(MyCommonUtil.toDictDataList(
-                resultList, DirectoryInfo::getId, DirectoryInfo::getMenuName, DirectoryInfo::getParentId));
+                directoryInfoVos, DirectoryInfoVo::getId, DirectoryInfoVo::getMenuName, DirectoryInfoVo::getParentId));
+    }
+
+    /**
+     * 根据菜单类型,以字典的形式返回数据列表。
+     * 白名单接口,登录用户均可访问。
+     *
+     * @param directoryType 菜单类型。
+     * @return 按照字典的形式返回数据列表。
+     */
+    @SaIgnore
+    @GetMapping("/listDictByType")
+    public ResponseResult<List<Map<String, Object>>> listDictByType(@RequestParam(required = false) Integer directoryType) {
+        DirectoryInfo directoryInfo = new DirectoryInfo();
+        directoryInfo.setDirectoryType(directoryType);
+        List<DirectoryInfo> resultList = directoryInfoService.getDirectoryInfoList(directoryInfo,"");
+        List<DirectoryInfoVo> directoryInfoVos = MyModelUtil.copyCollectionTo(resultList, DirectoryInfoVo.class);
+        return ResponseResult.success(MyCommonUtil.toDictDataList(
+                directoryInfoVos, DirectoryInfoVo::getId, DirectoryInfoVo::getMenuName, DirectoryInfoVo::getParentId));
     }
 
     private ResponseResult<Void> doDelete(Long id) {

+ 322 - 0
application-webadmin/src/main/java/com/tourism/webadmin/back/controller/ExtraController.java

@@ -0,0 +1,322 @@
+package com.tourism.webadmin.back.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import com.tourism.common.core.annotation.MyRequestBody;
+import com.tourism.common.core.constant.ErrorCodeEnum;
+import com.tourism.common.core.object.MyOrderParam;
+import com.tourism.common.core.object.ResponseResult;
+import com.tourism.common.core.util.MyDateUtil;
+import com.tourism.common.core.util.MyModelUtil;
+import com.tourism.webadmin.app.website.dto.DatePriceSaveDto;
+import com.tourism.webadmin.app.website.vo.DateRangePriceVo;
+import com.tourism.webadmin.app.website.vo.DateRangesPriceVo;
+import com.tourism.webadmin.back.dto.TourBookInfoDto;
+import com.tourism.webadmin.back.dto.TourTourismProjectTravelNotesDto;
+import com.tourism.webadmin.back.dto.TourismProjectDto;
+import com.tourism.webadmin.back.model.TourBookInfo;
+import com.tourism.webadmin.back.model.TourTourismProjectTravelNotes;
+import com.tourism.webadmin.back.model.TourismDatePrice;
+import com.tourism.webadmin.back.model.TourismProject;
+import com.tourism.webadmin.back.service.TourBookInfoService;
+import com.tourism.webadmin.back.service.TourTourismProjectTravelNotesService;
+import com.tourism.webadmin.back.service.TourismDatePriceService;
+import com.tourism.webadmin.back.service.TourismProjectService;
+import com.tourism.webadmin.back.vo.TourBookInfoVo;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.bind.annotation.*;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * extra 后台管理系统的除生成代码外额外的controller。
+ *
+ * @author 吃饭睡觉
+ * @date 2024-10-31
+ */
+@Tag(name = "extra接口")
+@Slf4j
+@RestController
+@RequestMapping("/admin/app/extra")
+public class ExtraController {
+
+    @Autowired
+    private TourismDatePriceService tourismDatePriceService;
+    @Autowired
+    private TourismProjectService tourismProjectService;
+    @Autowired
+    private TourTourismProjectTravelNotesService tourTourismProjectTravelNotesService;
+    @Autowired
+    private TourBookInfoService tourBookInfoService;
+    /**
+     * 查看指定旅游项目的日历价格。
+     *
+     * @param projectId 项目id。
+     * @return 应答结果对象,包含对象详情。
+     */
+    @SaCheckPermission("tourismProject.view")
+    @GetMapping("/viewDatePrice")
+    public ResponseResult<List<DateRangesPriceVo>> view(@RequestParam String projectId) {
+
+        TourismDatePrice tourismDatePrice = new TourismDatePrice();
+        tourismDatePrice.setProjectId(projectId);
+        tourismDatePrice.setNowDate(new Date());
+
+        MyOrderParam myOrderParam = new MyOrderParam();
+        MyOrderParam.OrderInfo orderInfo = new MyOrderParam.OrderInfo();
+        orderInfo.setFieldName("departureDate");
+        orderInfo.setAsc(true);
+        myOrderParam.add(orderInfo);
+        String orderBy = MyOrderParam.buildOrderBy(myOrderParam, TourismDatePrice.class);
+        List<TourismDatePrice> tourismDatePriceList =
+                tourismDatePriceService.getTourismDatePriceList(tourismDatePrice, orderBy);
+
+        List<DateRangePriceVo> result = new ArrayList<>();
+        Date startDate = null;
+        Date endDate = null;
+        BigDecimal currentAdultPrice = null;
+        BigDecimal currentChildrenPrice = null;
+        Date lastDate = null;
+        for (int i = 0; i < tourismDatePriceList.size(); i++) {
+            TourismDatePrice current = tourismDatePriceList.get(i);
+            Date currentDate = current.getDepartureDate();
+            BigDecimal currentPrice = current.getAdultPrice();
+            BigDecimal currentChildPrice = current.getChildrenPrice();
+
+            if (i == 0 || (currentDate.equals(new Date(lastDate.getTime() + 24 * 60 * 60 * 1000)) && currentPrice.equals(currentAdultPrice) && currentChildPrice.equals(currentChildrenPrice))) {
+                if (startDate == null) {
+                    startDate = currentDate;
+                }
+                endDate = currentDate;
+                currentAdultPrice = currentPrice;
+                currentChildrenPrice = currentChildPrice;
+                lastDate = currentDate;
+            } else {
+                if (startDate != null) {
+                    result.add(new DateRangePriceVo(startDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(), endDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(), currentAdultPrice, currentChildrenPrice));
+                }
+                startDate = currentDate;
+                endDate = currentDate;
+                currentAdultPrice = currentPrice;
+                currentChildrenPrice = currentChildPrice;
+                lastDate = currentDate;
+            }
+        }
+
+        // 添加最后一个范围
+        if (startDate != null) {
+            result.add(new DateRangePriceVo(startDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(), endDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(), currentAdultPrice, currentChildrenPrice));
+        }
+
+        List<DateRangesPriceVo> dateRangesPriceVoList = new ArrayList<>();
+
+        result.stream().forEach(u->{
+            DateRangesPriceVo dateRangesPriceVo = new DateRangesPriceVo();
+            dateRangesPriceVo.setAdultPrice(u.getAdultPrice());
+            dateRangesPriceVo.setChildrenPrice(u.getChildrenPrice());
+            List<LocalDate> list = new ArrayList<>();
+            list.add(u.getStartDate());list.add(u.getEndDate());
+            dateRangesPriceVo.setDateRange(list);
+            dateRangesPriceVoList.add(dateRangesPriceVo);
+        });
+        return ResponseResult.success(dateRangesPriceVoList);
+
+    }
+    /**
+     * 保存指定旅游项目的日历价格。
+     *
+     * @param datePriceSaveDto 日历价格数组。
+     * @return 应答结果对象,包含对象详情。
+     */
+    @Transactional
+    @SaCheckPermission("tourismProject.view")
+    @PostMapping("/saveDatePrice")
+    public ResponseResult<Void> saveDatePrice(@RequestBody DatePriceSaveDto datePriceSaveDto) {
+        //dateRangesPriceVoList为空的话,则删除全部的日历价格
+        if(CollectionUtils.isEmpty(datePriceSaveDto.getDateRangesPriceVoList())){
+            TourismProject tourismProject = tourismProjectService.getById(datePriceSaveDto.getProjectId());
+            if(tourismProject == null){
+                return ResponseResult.error(ErrorCodeEnum.DATA_SAVE_FAILED,"旅游项目不存在");
+                }else {
+                TourismDatePrice filter = new TourismDatePrice();
+                filter.setProjectId(datePriceSaveDto.getProjectId());
+                filter.setNowDate(MyDateUtil.truncateToDay(new Date()));
+                List<TourismDatePrice> tourismDatePriceList = tourismDatePriceService.getTourismDatePriceList(filter, "");
+                tourismDatePriceList.stream().map(TourismDatePrice::getId).forEach(item->{
+                    tourismDatePriceService.removeById(item);
+                });
+            }
+        }
+        //取旅游项目的价格,来对列表数据进行对比,如果列表数据低于旅游项目的价格,则进行提示
+        TourismProject tourismProject = tourismProjectService.getById(datePriceSaveDto.getProjectId());
+        if(tourismProject == null){
+            return ResponseResult.error(ErrorCodeEnum.DATA_SAVE_FAILED,"旅游项目不存在");
+        }else {
+            ArrayList<BigDecimal> adultPriceList =
+                    datePriceSaveDto.getDateRangesPriceVoList().stream().map(DateRangesPriceVo::getAdultPrice).collect(Collectors.toCollection(ArrayList::new));
+            ArrayList<BigDecimal> childrenPriceList =
+                    datePriceSaveDto.getDateRangesPriceVoList().stream().map(DateRangesPriceVo::getChildrenPrice).collect(Collectors.toCollection(ArrayList::new));
+
+            for(BigDecimal adultPrice : adultPriceList){
+                if(tourismProject.getPrice().compareTo(adultPrice) > 0){
+                return ResponseResult.error(ErrorCodeEnum.PRICE_ERROR,"成人价格不能低于旅游项目的价格");
+                }
+            }
+            for(BigDecimal childrenPrice : childrenPriceList){
+                if(tourismProject.getPrice().compareTo(childrenPrice) > 0){
+                    return ResponseResult.error(ErrorCodeEnum.PRICE_ERROR,"儿童价格不能低于旅游项目的价格");
+                }
+            }
+        }
+
+        //进行时间范围的判断,判断时间段是否交错;交错的话,则返回时间交错的信息
+        List<DateRangePriceVo> result = new ArrayList<>();
+        for (DateRangesPriceVo dateRangesPriceVo : datePriceSaveDto.getDateRangesPriceVoList()) {
+            DateRangePriceVo dateRangePriceVo = new DateRangePriceVo(
+                    dateRangesPriceVo.getDateRange().get(0),dateRangesPriceVo.getDateRange().get(1),
+                    dateRangesPriceVo.getAdultPrice(),dateRangesPriceVo.getChildrenPrice());
+            result.add(dateRangePriceVo);
+        }
+        //转换出来时间后,判断时间是否交错
+        for(int i=0;i<result.size();i++) {
+            if (result.get(i).getStartDate().isEqual(result.get(i).getEndDate()) || result.get(i).getStartDate().isBefore(result.get(i).getEndDate())){
+                for (int j = 0; j < result.size(); j++) {
+                    if (i == j) {
+                        continue;
+                    }
+                    //如果两条数据的开始日期或者结束日期一致的话,则进行报错返回
+                    if (result.get(i).getStartDate().isEqual(result.get(j).getStartDate()) || (result.get(i).getStartDate().isEqual(result.get(j).getEndDate()))) {
+//                        return ResponseResult.error(ErrorCodeEnum.DATE_OVERLAP, "开始日期为:" + result.get(i).getStartDate() + "和" + result.get(j).getStartDate() + "的时间段有重叠");
+                        return ResponseResult.error(ErrorCodeEnum.DATE_OVERLAP, "第"+ (i+1) +"条数据和第" + (j+1) + "条数据的时间段有重叠");
+                    }
+                    if (result.get(i).getStartDate().isAfter(result.get(j).getStartDate()) && result.get(i).getStartDate().isBefore(result.get(j).getEndDate())) {
+//                        return ResponseResult.error(ErrorCodeEnum.DATE_OVERLAP, "开始日期为:" + result.get(i).getStartDate() + "和" + result.get(j).getStartDate() + "的时间段有重叠");
+                        return ResponseResult.error(ErrorCodeEnum.DATE_OVERLAP, "第"+ (i+1) +"条数据和第" + (j+1) + "条数据的时间段有重叠");
+                    }
+                    if (result.get(i).getEndDate().isAfter(result.get(j).getStartDate()) && result.get(i).getEndDate().isBefore(result.get(j).getEndDate())) {
+//                        return ResponseResult.error(ErrorCodeEnum.DATE_OVERLAP, "开始日期为:" + result.get(i).getStartDate() + "和" + result.get(j).getStartDate() + "的时间段有重叠");
+                        return ResponseResult.error(ErrorCodeEnum.DATE_OVERLAP, "第"+ (i+1) +"条数据和第" + (j+1) + "条数据的时间段有重叠");
+                    }
+                }
+        }else {
+                return ResponseResult.error(ErrorCodeEnum.DATE_OVERLAP, "开始日期为:" + result.get(i).getStartDate() + ",结束日期为:" + result.get(i).getEndDate() + "的时间段有误,请重新输入");
+            }
+        }
+        //时间没有问题的话,则进行项目日历的查询,先进行删除
+        TourismDatePrice filter = new TourismDatePrice();
+        filter.setProjectId(datePriceSaveDto.getProjectId());
+        filter.setNowDate(MyDateUtil.truncateToDay(new Date()));
+        List<TourismDatePrice> tourismDatePriceList = tourismDatePriceService.getTourismDatePriceList(filter, "");
+        if(!CollectionUtils.isEmpty(tourismDatePriceList)){
+            tourismDatePriceService.removeByIds(tourismDatePriceList.stream().map(TourismDatePrice::getId).collect(Collectors.toList()));
+        }
+        //根据result构建新增的数据,再进行新增
+        result.stream().forEach(item-> {
+            if (item.getStartDate().equals(item.getEndDate())) {
+                TourismDatePrice tourismDatePrice = new TourismDatePrice();
+                tourismDatePrice.setProjectId(datePriceSaveDto.getProjectId());
+                tourismDatePrice.setDepartureDate(Date.from(item.getStartDate().atStartOfDay(ZoneId.systemDefault()).toInstant()));
+                tourismDatePrice.setAdultPrice(item.getAdultPrice());
+                tourismDatePrice.setChildrenPrice(item.getChildrenPrice());
+                tourismDatePriceService.saveNew(tourismDatePrice);
+            } else {
+                //对item的开始日期进行新增,直到新增到item的endDate,把中间的日期进行汇总,形成一个新的list
+                List<TourismDatePrice> tourismDatePriceList1 = new ArrayList<>();
+                for (LocalDate date = item.getStartDate(); date.isBefore(item.getEndDate().plusDays(1)); date = date.plusDays(1)) {
+                    TourismDatePrice tourismDatePrice = new TourismDatePrice();
+                    tourismDatePrice.setProjectId(datePriceSaveDto.getProjectId());
+                    tourismDatePrice.setDepartureDate(Date.from(date.atStartOfDay(ZoneId.systemDefault()).toInstant()));
+                    tourismDatePrice.setAdultPrice(item.getAdultPrice());
+                    tourismDatePrice.setChildrenPrice(item.getChildrenPrice());
+                    tourismDatePriceList1.add(tourismDatePrice);
+                }
+                //批量新增
+                tourismDatePriceService.saveNewBatch(tourismDatePriceList1);
+            }
+        });
+        return ResponseResult.success();
+    }
+    /**
+     * 更新项目的状态(1.启用;0.禁用。)
+     *
+     * @param tourismProjectDto 旅游项目管理Dto对象。
+     * @return void。
+     */
+    @Transactional
+    @SaCheckPermission("tourismProject.update")
+    @PostMapping("/updateProjectState")
+    public ResponseResult<Void> updateProjectState(@MyRequestBody TourismProjectDto tourismProjectDto) {
+        TourismProject tourismProject = tourismProjectService.getById(tourismProjectDto.getId());
+        if (tourismProject == null) {
+            return ResponseResult.error(ErrorCodeEnum.PROJECT_NOT_EXIST);
+        }
+        if(tourismProjectDto.getEnable() == 1) {
+            //查看该项目是否设置日历;设置日历,则允许修改项目状态;若未设置日历,则不允许修改项目状态
+            TourismDatePrice tourismDatePrice = new TourismDatePrice();
+            tourismDatePrice.setProjectId(tourismProject.getId());
+            tourismDatePrice.setNowDate(new Date());
+            List<TourismDatePrice> listByFilter = tourismDatePriceService.getTourismDatePriceList(tourismDatePrice,"");
+            if (listByFilter.isEmpty()) {
+                return ResponseResult.error(ErrorCodeEnum.DATE_PRICE_NOTEXIST);
+            }
+        }
+        TourismProject originalTourismProject = MyModelUtil.copyTo(tourismProject, TourismProject.class);
+        tourismProject.setEnable(tourismProjectDto.getEnable());
+        tourismProjectService.update(tourismProject,originalTourismProject);
+        return ResponseResult.success();
+    }
+
+    /**
+     * 更新游记是否原创的字段(1.原创;0.非原创。)
+     *
+     * @param tourTourismProjectTravelNotesDto 旅游项目游记管理Dto对象。
+     * @return void。
+     */
+    @Transactional
+    @SaCheckPermission("tourTourismProjectTravelNotes.update")
+    @PostMapping("/updateIsOriginalChange")
+    public ResponseResult<Void> updateIsOriginalChange(@MyRequestBody TourTourismProjectTravelNotesDto tourTourismProjectTravelNotesDto) {
+
+        TourTourismProjectTravelNotes tourTourismProjectTravelNote =
+                tourTourismProjectTravelNotesService.getById(tourTourismProjectTravelNotesDto.getId());
+        if(tourTourismProjectTravelNote == null){
+            return ResponseResult.error(ErrorCodeEnum.PROJECT_NOT_EXIST);
+        }
+        TourTourismProjectTravelNotes tourTourismProjectTravelNotes =
+                MyModelUtil.copyTo(tourTourismProjectTravelNote, TourTourismProjectTravelNotes.class);
+        tourTourismProjectTravelNotes.setIsOriginal(tourTourismProjectTravelNotesDto.getIsOriginal());
+        tourTourismProjectTravelNotesService.update(tourTourismProjectTravelNotes,tourTourismProjectTravelNote);
+        return ResponseResult.success();
+    }
+
+
+    /**
+     * 查看指定门户预定管理对象详情。
+     *
+     * @param tourBookInfoDtoFilter 过滤对象。
+     * @return 应答结果对象,包含对象详情。
+     */
+    @SaCheckPermission("tourBookInfo.view")
+    @GetMapping("/isHandleChange")
+    public ResponseResult<Void> isHandleChange(@MyRequestBody TourBookInfoDto tourBookInfoDtoFilter) {
+        TourBookInfo tourBookInfo = tourBookInfoService.getById(tourBookInfoDtoFilter.getId());
+        if(tourBookInfo == null){
+            return ResponseResult.error(ErrorCodeEnum.PROJECT_NOT_EXIST);
+        }
+        TourBookInfo tourBookInfo1 =
+                MyModelUtil.copyTo(tourBookInfo, TourBookInfo.class);
+        tourBookInfo1.setIsHandle(tourBookInfoDtoFilter.getIsHandle());
+        tourBookInfoService.update(tourBookInfo1,tourBookInfo);
+        return ResponseResult.success();
+    }
+
+}

+ 366 - 0
application-webadmin/src/main/java/com/tourism/webadmin/back/controller/IconController.java

@@ -0,0 +1,366 @@
+package com.tourism.webadmin.back.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.hutool.core.util.ReflectUtil;
+import com.tourism.common.core.upload.BaseUpDownloader;
+import com.tourism.common.core.upload.UpDownloaderFactory;
+import com.tourism.common.core.upload.UploadResponseInfo;
+import com.tourism.common.core.upload.UploadStoreInfo;
+import com.tourism.common.log.annotation.OperationLog;
+import com.tourism.common.log.model.constant.SysOperationLogType;
+import com.github.pagehelper.page.PageMethod;
+import com.tourism.webadmin.back.vo.*;
+import com.tourism.webadmin.back.dto.*;
+import com.tourism.webadmin.back.model.*;
+import com.tourism.webadmin.back.service.*;
+import com.tourism.common.core.object.*;
+import com.tourism.common.core.util.*;
+import com.tourism.common.core.constant.*;
+import com.tourism.common.core.annotation.MyRequestBody;
+import com.tourism.common.redis.cache.SessionCacheHelper;
+import com.tourism.common.additional.config.ApplicationConfig;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import jakarta.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * icon信息操作控制器类。
+ *
+ * @author 吃饭睡觉
+ * @date 2024-09-06
+ */
+@Tag(name = "icon信息管理接口")
+@Slf4j
+@RestController
+@RequestMapping("/admin/app/icon")
+public class IconController {
+
+    @Autowired
+    private ApplicationConfig appConfig;
+    @Autowired
+    private SessionCacheHelper cacheHelper;
+    @Autowired
+    private UpDownloaderFactory upDownloaderFactory;
+    @Autowired
+    private IconService iconService;
+
+    /**
+     * 新增icon信息数据。
+     *
+     * @param iconDto 新增对象。
+     * @return 应答结果对象,包含新增对象主键Id。
+     */
+    @ApiOperationSupport(ignoreParameters = {"iconDto.id", "iconDto.searchString"})
+    @SaCheckPermission("icon.add")
+    @OperationLog(type = SysOperationLogType.ADD)
+    @PostMapping("/add")
+    public ResponseResult<Long> add(@MyRequestBody IconDto iconDto) {
+        String errorMessage = MyCommonUtil.getModelValidationError(iconDto, false);
+        if (errorMessage != null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
+        }
+        Icon icon = MyModelUtil.copyTo(iconDto, Icon.class);
+        icon = iconService.saveNew(icon);
+        return ResponseResult.success(icon.getId());
+    }
+
+    /**
+     * 更新icon信息数据。
+     *
+     * @param iconDto 更新对象。
+     * @return 应答结果对象。
+     */
+    @ApiOperationSupport(ignoreParameters = {"iconDto.searchString"})
+    @SaCheckPermission("icon.update")
+    @OperationLog(type = SysOperationLogType.UPDATE)
+    @PostMapping("/update")
+    public ResponseResult<Void> update(@MyRequestBody IconDto iconDto) {
+        String errorMessage = MyCommonUtil.getModelValidationError(iconDto, true);
+        if (errorMessage != null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
+        }
+        Icon icon = MyModelUtil.copyTo(iconDto, Icon.class);
+        Icon originalIcon = iconService.getById(icon.getId());
+        if (originalIcon == null) {
+            // NOTE: 修改下面方括号中的话述
+            errorMessage = "数据验证失败,当前 [数据] 并不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        if (!iconService.update(icon, originalIcon)) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
+        }
+        return ResponseResult.success();
+    }
+
+    /**
+     * 删除icon信息数据。
+     *
+     * @param id 删除对象主键Id。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("icon.delete")
+    @OperationLog(type = SysOperationLogType.DELETE)
+    @PostMapping("/delete")
+    public ResponseResult<Void> delete(@MyRequestBody Long id) {
+        if (MyCommonUtil.existBlankArgument(id)) {
+            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
+        }
+        return this.doDelete(id);
+    }
+
+    /**
+     * 批量删除icon信息数据。
+     *
+     * @param idList 待删除对象的主键Id列表。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("icon.delete")
+    @OperationLog(type = SysOperationLogType.DELETE_BATCH)
+    @PostMapping("/deleteBatch")
+    public ResponseResult<Void> deleteBatch(@MyRequestBody List<Long> idList) {
+        if (MyCommonUtil.existBlankArgument(idList)) {
+            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
+        }
+        for (Long id : idList) {
+            ResponseResult<Void> responseResult = this.doDelete(id);
+            if (!responseResult.isSuccess()) {
+                return responseResult;
+            }
+        }
+        return ResponseResult.success();
+    }
+
+    /**
+     * 列出符合过滤条件的icon信息列表。
+     *
+     * @param iconDtoFilter 过滤对象。
+     * @param orderParam 排序参数。
+     * @param pageParam 分页参数。
+     * @return 应答结果对象,包含查询结果集。
+     */
+    @SaCheckPermission("icon.view")
+    @PostMapping("/list")
+    public ResponseResult<MyPageData<IconVo>> list(
+            @MyRequestBody IconDto iconDtoFilter,
+            @MyRequestBody MyOrderParam orderParam,
+            @MyRequestBody MyPageParam pageParam) {
+        if (pageParam != null) {
+            PageMethod.startPage(pageParam.getPageNum(), pageParam.getPageSize(), pageParam.getCount());
+        }
+        Icon iconFilter = MyModelUtil.copyTo(iconDtoFilter, Icon.class);
+        String orderBy = MyOrderParam.buildOrderBy(orderParam, Icon.class);
+        List<Icon> iconList = iconService.getIconListWithRelation(iconFilter, orderBy);
+        return ResponseResult.success(MyPageUtil.makeResponseData(iconList, IconVo.class));
+    }
+
+    /**
+     * 导入主表数据列表。
+     *
+     * @param importFile 上传的文件,目前仅仅支持xlsx和xls两种格式。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("icon.import")
+    @OperationLog(type = SysOperationLogType.IMPORT)
+    @PostMapping("/import")
+    public ResponseResult<Void> importBatch(
+            @RequestParam Boolean skipHeader,
+            @RequestParam("importFile") MultipartFile importFile) throws IOException {
+        String filename = ImportUtil.saveImportFile(appConfig.getUploadFileBaseDir(), null, importFile);
+        // 这里可以指定需要忽略导入的字段集合。如创建时间、创建人、更新时间、更新人、主键Id和逻辑删除,
+        // 以及一些存在缺省值且无需导入的字段。其中主键字段和逻辑删除字段不需要在这里设置,批量插入逻辑会自动处理的。
+        Set<String> ignoreFieldSet = new HashSet<>();
+        ignoreFieldSet.add("createUserId");
+        ignoreFieldSet.add("createTime");
+        ignoreFieldSet.add("updateUserId");
+        ignoreFieldSet.add("updateTime");
+        List<ImportUtil.ImportHeaderInfo> headerInfoList = ImportUtil.makeHeaderInfoList(Icon.class, ignoreFieldSet);
+        // 下面是导入时需要注意的地方,如果我们缺省生成的代码,与实际情况存在差异,请手动修改。
+        // 1. 头信息数据字段,我们只是根据当前的主表实体对象生成了缺省数组,开发者可根据实际情况,对headerInfoList进行修改。
+        ImportUtil.ImportHeaderInfo[] headerInfos = headerInfoList.toArray(new ImportUtil.ImportHeaderInfo[]{});
+        // 2. 这里需要根据实际情况决定,导入文件中第一行是否为中文头信息,如果是可以跳过。这里我们默认为true。
+        // 这里根据自己的实际需求,为doImport的最后一个参数,传递需要进行字典转换的字段集合。
+        // 注意,集合中包含需要翻译的Java字段名,如: gradeId。
+        Set<String> translatedDictFieldSet = new HashSet<>();
+        List<Icon> dataList =
+                ImportUtil.doImport(headerInfos, skipHeader, filename, Icon.class, translatedDictFieldSet);
+        CallResult result = iconService.verifyImportList(dataList, translatedDictFieldSet);
+        if (!result.isSuccess()) {
+            // result中返回了具体的验证失败对象,如果需要返回更加详细的错误,可根据实际情况手动修改。
+            return ResponseResult.errorFrom(result);
+        }
+        iconService.saveNewBatch(dataList, -1);
+        return ResponseResult.success();
+    }
+
+    /**
+     * 导出符合过滤条件的icon信息列表。
+     *
+     * @param iconDtoFilter 过滤对象。
+     * @param orderParam 排序参数。
+     * @throws IOException 文件读写失败。
+     */
+    @SaCheckPermission("icon.export")
+    @OperationLog(type = SysOperationLogType.EXPORT, saveResponse = false)
+    @PostMapping("/export")
+    public void export(
+            @MyRequestBody IconDto iconDtoFilter,
+            @MyRequestBody MyOrderParam orderParam) throws IOException {
+        Icon iconFilter = MyModelUtil.copyTo(iconDtoFilter, Icon.class);
+        String orderBy = MyOrderParam.buildOrderBy(orderParam, Icon.class);
+        List<Icon> resultList =
+                iconService.getIconListWithRelation(iconFilter, orderBy);
+        // 导出文件的标题数组
+        // NOTE: 下面的代码中仅仅导出了主表数据,主表聚合计算数据和主表关联字典的数据。
+        // 一对一从表数据的导出,可根据需要自行添加。如:headerMap.put("slaveFieldName.xxxField", "标题名称")
+        Map<String, String> headerMap = new LinkedHashMap<>(10);
+        headerMap.put("id", "主键id");
+        headerMap.put("icon", "icno图标");
+        headerMap.put("iconName", "图标名称");
+        headerMap.put("showOrder", "显示顺序");
+        headerMap.put("enableDictMap.name", "是否启用,0禁用,1启用");
+        headerMap.put("createUserId", "创建用户");
+        headerMap.put("createTime", "创建时间");
+        headerMap.put("updateUserId", "更新用户");
+        headerMap.put("updateTime", "更新时间");
+        headerMap.put("dataState", "删除标记(1: 正常 -1: 已删除)");
+        ExportUtil.doExport(resultList, headerMap, "icon.xlsx");
+    }
+
+    /**
+     * 查看指定icon信息对象详情。
+     *
+     * @param id 指定对象主键Id。
+     * @return 应答结果对象,包含对象详情。
+     */
+    @SaCheckPermission("icon.view")
+    @GetMapping("/view")
+    public ResponseResult<IconVo> view(@RequestParam Long id) {
+        Icon icon = iconService.getByIdWithRelation(id, MyRelationParam.full());
+        if (icon == null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
+        }
+        IconVo iconVo = MyModelUtil.copyTo(icon, IconVo.class);
+        return ResponseResult.success(iconVo);
+    }
+
+    /**
+     * 附件文件下载。
+     * 这里将图片和其他类型的附件文件放到不同的父目录下,主要为了便于今后图片文件的迁移。
+     *
+     * @param id 附件所在记录的主键Id。
+     * @param fieldName 附件所属的字段名。
+     * @param filename  文件名。如果没有提供该参数,就从当前记录的指定字段中读取。
+     * @param asImage   下载文件是否为图片。
+     * @param response  Http 应答对象。
+     */
+    @SaCheckPermission("icon.view")
+    @OperationLog(type = SysOperationLogType.DOWNLOAD, saveResponse = false)
+    @GetMapping("/download")
+    public void download(
+            @RequestParam(required = false) Long id,
+            @RequestParam String fieldName,
+            @RequestParam String filename,
+            @RequestParam Boolean asImage,
+            HttpServletResponse response) {
+        if (MyCommonUtil.existBlankArgument(fieldName, filename, asImage)) {
+            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+            return;
+        }
+        // 使用try来捕获异常,是为了保证一旦出现异常可以返回500的错误状态,便于调试。
+        // 否则有可能给前端返回的是200的错误码。
+        try {
+            // 如果请求参数中没有包含主键Id,就判断该文件是否为当前session上传的。
+            if (id == null) {
+                if (!cacheHelper.existSessionUploadFile(filename)) {
+                    ResponseResult.output(HttpServletResponse.SC_FORBIDDEN);
+                    return;
+                }
+            } else {
+                Icon icon = iconService.getById(id);
+                if (icon == null) {
+                    ResponseResult.output(HttpServletResponse.SC_NOT_FOUND);
+                    return;
+                }
+                String fieldJsonData = (String) ReflectUtil.getFieldValue(icon, fieldName);
+                if (fieldJsonData == null && !cacheHelper.existSessionUploadFile(filename)) {
+                    ResponseResult.output(HttpServletResponse.SC_BAD_REQUEST);
+                    return;
+                }
+                if (!BaseUpDownloader.containFile(fieldJsonData, filename)
+                        && !cacheHelper.existSessionUploadFile(filename)) {
+                    ResponseResult.output(HttpServletResponse.SC_FORBIDDEN);
+                    return;
+                }
+            }
+            UploadStoreInfo storeInfo = MyModelUtil.getUploadStoreInfo(Icon.class, fieldName);
+            if (!storeInfo.isSupportUpload()) {
+                ResponseResult.output(HttpServletResponse.SC_NOT_IMPLEMENTED,
+                        ResponseResult.error(ErrorCodeEnum.INVALID_UPLOAD_FIELD));
+                return;
+            }
+            BaseUpDownloader upDownloader = upDownloaderFactory.get(storeInfo.getStoreType());
+            upDownloader.doDownload(appConfig.getUploadFileBaseDir(),
+                    Icon.class.getSimpleName(), fieldName, filename, asImage, response);
+        } catch (Exception e) {
+            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+            log.error(e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 文件上传操作。
+     *
+     * @param fieldName  上传文件名。
+     * @param asImage    是否作为图片上传。如果是图片,今后下载的时候无需权限验证。否则就是附件上传,下载时需要权限验证。
+     * @param uploadFile 上传文件对象。
+     */
+    @SaCheckPermission("icon.view")
+    @OperationLog(type = SysOperationLogType.UPLOAD, saveResponse = false)
+    @PostMapping("/upload")
+    public void upload(
+            @RequestParam String fieldName,
+            @RequestParam Boolean asImage,
+            @RequestParam("uploadFile") MultipartFile uploadFile) throws IOException {
+        UploadStoreInfo storeInfo = MyModelUtil.getUploadStoreInfo(Icon.class, fieldName);
+        // 这里就会判断参数中指定的字段,是否支持上传操作。
+        if (!storeInfo.isSupportUpload()) {
+            ResponseResult.output(HttpServletResponse.SC_FORBIDDEN,
+                    ResponseResult.error(ErrorCodeEnum.INVALID_UPLOAD_FIELD));
+            return;
+        }
+        // 根据字段注解中的存储类型,通过工厂方法获取匹配的上传下载实现类,从而解耦。
+        BaseUpDownloader upDownloader = upDownloaderFactory.get(storeInfo.getStoreType());
+        UploadResponseInfo responseInfo = upDownloader.doUpload(null,
+                appConfig.getUploadFileBaseDir(), Icon.class.getSimpleName(), fieldName, asImage, uploadFile);
+        if (Boolean.TRUE.equals(responseInfo.getUploadFailed())) {
+            ResponseResult.output(HttpServletResponse.SC_FORBIDDEN,
+                    ResponseResult.error(ErrorCodeEnum.UPLOAD_FAILED, responseInfo.getErrorMessage()));
+            return;
+        }
+        cacheHelper.putSessionUploadFile(responseInfo.getFilename());
+        ResponseResult.output(ResponseResult.success(responseInfo));
+    }
+
+    private ResponseResult<Void> doDelete(Long id) {
+        String errorMessage;
+        // 验证关联Id的数据合法性
+        Icon originalIcon = iconService.getById(id);
+        if (originalIcon == null) {
+            // NOTE: 修改下面方括号中的话述
+            errorMessage = "数据验证失败,当前 [对象] 并不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        if (!iconService.remove(id)) {
+            errorMessage = "数据操作失败,删除的对象不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        return ResponseResult.success();
+    }
+}

+ 1 - 2
application-webadmin/src/main/java/com/tourism/webadmin/back/controller/JobFileController.java

@@ -18,7 +18,7 @@ import com.tourism.common.core.util.*;
 import com.tourism.common.core.constant.*;
 import com.tourism.common.core.annotation.MyRequestBody;
 import com.tourism.common.redis.cache.SessionCacheHelper;
-import com.tourism.webadmin.config.ApplicationConfig;
+import com.tourism.common.additional.config.ApplicationConfig;
 import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import lombok.extern.slf4j.Slf4j;
@@ -186,7 +186,6 @@ public class JobFileController {
      * @param asImage   下载文件是否为图片。
      * @param response  Http 应答对象。
      */
-    @SaCheckPermission("jobFile.view")
     @OperationLog(type = SysOperationLogType.DOWNLOAD, saveResponse = false)
     @GetMapping("/download")
     public void download(

+ 4 - 2
application-webadmin/src/main/java/com/tourism/webadmin/back/controller/JobProjectController.java

@@ -1,6 +1,7 @@
 package com.tourism.webadmin.back.controller;
 
 import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.dev33.satoken.annotation.SaIgnore;
 import com.alibaba.fastjson.JSONObject;
 import cn.hutool.core.util.ReflectUtil;
 import com.tourism.common.core.upload.BaseUpDownloader;
@@ -19,7 +20,7 @@ import com.tourism.common.core.util.*;
 import com.tourism.common.core.constant.*;
 import com.tourism.common.core.annotation.MyRequestBody;
 import com.tourism.common.redis.cache.SessionCacheHelper;
-import com.tourism.webadmin.config.ApplicationConfig;
+import com.tourism.common.additional.config.ApplicationConfig;
 import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import lombok.extern.slf4j.Slf4j;
@@ -288,7 +289,8 @@ public class JobProjectController {
      * @param asImage   下载文件是否为图片。
      * @param response  Http 应答对象。
      */
-    @SaCheckPermission("jobProject.view")
+//    @SaCheckPermission("jobProject.view")
+    @SaIgnore
     @OperationLog(type = SysOperationLogType.DOWNLOAD, saveResponse = false)
     @GetMapping("/download")
     public void download(

+ 0 - 620
application-webadmin/src/main/java/com/tourism/webadmin/back/controller/LoginToWebController.java

@@ -1,620 +0,0 @@
-package com.tourism.webadmin.back.controller;
-
-import cn.dev33.satoken.annotation.SaIgnore;
-import cn.dev33.satoken.session.SaSession;
-import cn.dev33.satoken.stp.StpUtil;
-import cn.hutool.core.bean.BeanUtil;
-import cn.hutool.core.collection.CollUtil;
-import cn.hutool.core.util.BooleanUtil;
-import cn.hutool.core.util.StrUtil;
-import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.JSONObject;
-import com.tourism.common.core.annotation.DisableDataFilter;
-import com.tourism.common.core.annotation.MyRequestBody;
-import com.tourism.common.core.constant.ApplicationConstant;
-import com.tourism.common.core.constant.ErrorCodeEnum;
-import com.tourism.common.core.object.LoginUserInfo;
-import com.tourism.common.core.object.ResponseResult;
-import com.tourism.common.core.object.TokenData;
-import com.tourism.common.core.upload.BaseUpDownloader;
-import com.tourism.common.core.upload.UpDownloaderFactory;
-import com.tourism.common.core.upload.UploadStoreInfo;
-import com.tourism.common.core.util.*;
-import com.tourism.common.flow.online.service.FlowOnlineOperationService;
-import com.tourism.common.log.annotation.OperationLog;
-import com.tourism.common.log.model.constant.SysOperationLogType;
-import com.tourism.common.mobile.model.MobileEntry;
-import com.tourism.common.online.service.OnlineOperationService;
-import com.tourism.common.redis.cache.SessionCacheHelper;
-import com.tourism.common.report.service.ReportOperationService;
-import com.tourism.common.satoken.util.SaTokenUtil;
-import com.tourism.webadmin.back.model.SysUserWeb;
-import com.tourism.webadmin.back.service.SmsService;
-import com.tourism.webadmin.back.service.SysUserWebService;
-import com.tourism.webadmin.config.ApplicationConfig;
-import com.tourism.webadmin.back.dto.SysUserWebDto;
-import com.tourism.webadmin.upms.model.*;
-import com.tourism.webadmin.upms.model.constant.SysMenuType;
-import com.tourism.webadmin.upms.model.constant.SysOnlineMenuPermType;
-import com.tourism.webadmin.upms.model.constant.SysUserStatus;
-import com.tourism.webadmin.upms.service.*;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import jakarta.servlet.http.HttpServletResponse;
-import lombok.Data;
-import lombok.extern.slf4j.Slf4j;
-import org.redisson.api.RSet;
-import org.redisson.api.RedissonClient;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.crypto.password.PasswordEncoder;
-import org.springframework.transaction.annotation.Transactional;
-import org.springframework.util.ObjectUtils;
-import org.springframework.web.bind.annotation.*;
-
-import java.io.UnsupportedEncodingException;
-import java.net.URLDecoder;
-import java.nio.charset.StandardCharsets;
-import java.util.*;
-import java.util.concurrent.TimeUnit;
-import java.util.stream.Collectors;
-
-/**
- * 登录接口控制器类。
- *
- * @author 吃饭睡觉
- * @date 2024-09-06
- */
-@Tag(name = "网页用户登录接口")
-@DisableDataFilter
-@Slf4j
-@RestController
-@RequestMapping("/admin/web/login")
-public class LoginToWebController {
-
-
-    @Autowired
-    private SysUserWebService sysUserWebService;
-    @Autowired
-    private SysDataPermService sysDataPermService;
-    @Autowired
-    private OnlineOperationService onlineOperationService;
-    @Autowired
-    private FlowOnlineOperationService flowOnlineOperationService;
-    @Autowired
-    private ReportOperationService reportOperationService;
-    @Autowired
-    private ApplicationConfig appConfig;
-    @Autowired
-    private RedissonClient redissonClient;
-    @Autowired
-    private SessionCacheHelper cacheHelper;
-    @Autowired
-    private PasswordEncoder passwordEncoder;
-    @Autowired
-    private UpDownloaderFactory upDownloaderFactory;
-    @Autowired
-    private SaTokenUtil saTokenUtil;
-    @Autowired
-    private SmsService smsService;
-
-    private static final String IS_ADMIN = "isAdmin";
-    private static final String SHOW_NAME_FIELD = "showName";
-    private static final String SHOW_ORDER_FIELD = "showOrder";
-    private static final String HEAD_IMAGE_URL_FIELD = "headImageUrl";
-
-    /**
-     * 用户注册
-     * @return
-     * @throws UnsupportedEncodingException
-     */
-    @SaIgnore
-    @OperationLog(type = SysOperationLogType.Register, saveResponse = false)
-    @Transactional(rollbackFor = Exception.class)
-    @PostMapping("/doRegister")
-    public ResponseResult doRegister(@RequestBody SysUserWebDto sysUserWebDto) {
-
-        if (MyCommonUtil.existBlankArgument(sysUserWebDto.getLoginName(), sysUserWebDto.getPassword())) {
-            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
-        }
-
-        // 验证短信验证码
-        if (!smsService.validateSmsCode(sysUserWebDto.getSmsCode(),sysUserWebDto.getMobile())) {
-            return ResponseResult.error(ErrorCodeEnum.SMSCODE_ERR);
-        }
-
-        //存储用户信息
-        SysUserWeb sysUserWeb = new SysUserWeb();
-        BeanUtil.copyProperties(sysUserWebDto,sysUserWeb);
-        sysUserWebService.saveNew(sysUserWeb);
-
-        return ResponseResult.success();
-    }
-
-    private ResponseResult<SysUserWeb> verifyAndHandleLoginUser(
-            String loginName, String password) throws UnsupportedEncodingException {
-        String errorMessage;
-        SysUserWeb user = sysUserWebService.getSysUserByLoginName(loginName);
-        password = URLDecoder.decode(password, StandardCharsets.UTF_8.name());
-
-        // NOTE: 第一次使用时,请务必阅读ApplicationConstant.PRIVATE_KEY的代码注释。
-        // 执行RsaUtil工具类中的main函数,可以生成新的公钥和私钥。
-        password = RsaUtil.decrypt(password, ApplicationConstant.PRIVATE_KEY);
-        if (user == null || !passwordEncoder.matches(password, user.getPassword())) {
-            return ResponseResult.error(ErrorCodeEnum.INVALID_USERNAME_PASSWORD);
-        }
-        if (user.getUserStatus() == SysUserStatus.STATUS_LOCKED) {
-            errorMessage = "登录失败,用户账号被锁定!";
-            return ResponseResult.error(ErrorCodeEnum.INVALID_USER_STATUS, errorMessage);
-        }
-        if (BooleanUtil.isTrue(appConfig.getExcludeLogin())) {
-            String deviceType = MyCommonUtil.getDeviceTypeWithString();
-            LoginUserInfo userInfo = BeanUtil.copyProperties(user, LoginUserInfo.class);
-            String loginId = SaTokenUtil.makeLoginIdByWeb(userInfo);
-            StpUtil.kickout(loginId, deviceType);
-        }
-        return ResponseResult.success(user);
-    }
-
-    /**
-     * 登录接口。
-     *
-     * @param loginName 登录名。
-     * @param password  密码。
-     * @return 应答结果对象,其中包括Token数据,以及菜单列表。
-     */
-    @SaIgnore
-    @OperationLog(type = SysOperationLogType.LOGIN_WEB, saveResponse = false)
-    @PostMapping("/doLogin")
-    public ResponseResult<JSONObject> doLogin(
-            @MyRequestBody String loginName,
-            @MyRequestBody String password) throws UnsupportedEncodingException {
-        if (MyCommonUtil.existBlankArgument(loginName, password)) {
-            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
-        }
-        ResponseResult<SysUserWeb> verifyResult = this.verifyAndHandleLoginUser(loginName, password);
-        if (!verifyResult.isSuccess()) {
-            return ResponseResult.errorFrom(verifyResult);
-        }
-        JSONObject jsonData = this.buildLoginDataAndLogin(verifyResult.getData());
-        return ResponseResult.success(jsonData);
-    }
-
-    /**
-     * 手机号登录接口。
-     * @return 应答结果对象,其中包括Token数据,以及菜单列表。
-     */
-    @SaIgnore
-    @OperationLog(type = SysOperationLogType.LOGIN_WEB, saveResponse = false)
-    @PostMapping("/doLoginByPhone")
-    public ResponseResult<JSONObject> doLoginByPhone(
-            @MyRequestBody String mobile,
-            @MyRequestBody String smsCode) throws UnsupportedEncodingException {
-        String errorMessage;
-        if (MyCommonUtil.existBlankArgument(mobile, smsCode)) {
-            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
-        }
-        //查询手机号是否存在
-        SysUserWeb sysUserByMobile = sysUserWebService.getSysUserByMobile(mobile);
-        if (ObjectUtils.isEmpty(sysUserByMobile)){
-            return ResponseResult.error(ErrorCodeEnum.INVALID_MOBILE_CODE);
-        }
-
-        // 验证短信验证码
-        if (!smsService.validateSmsCode(smsCode,mobile)) {
-            return ResponseResult.error(ErrorCodeEnum.SMSCODE_ERR);
-        }
-
-        if (sysUserByMobile.getUserStatus() == SysUserStatus.STATUS_LOCKED) {
-            errorMessage = "登录失败,用户账号被锁定!";
-            return ResponseResult.error(ErrorCodeEnum.INVALID_USER_STATUS, errorMessage);
-        }
-        if (BooleanUtil.isTrue(appConfig.getExcludeLogin())) {
-            String deviceType = MyCommonUtil.getDeviceTypeWithString();
-            LoginUserInfo userInfo = BeanUtil.copyProperties(sysUserByMobile, LoginUserInfo.class);
-            String loginId = SaTokenUtil.makeLoginIdByWeb(userInfo);
-            StpUtil.kickout(loginId, deviceType);
-        }
-        JSONObject jsonData = this.buildLoginDataAndLogin(sysUserByMobile);
-        return ResponseResult.success(jsonData);
-    }
-
-
-
-
-
-    /**
-     * 登录移动端接口。
-     *
-     * @param loginName 登录名。
-     * @param password  密码。
-     * @return 应答结果对象,其中包括Token数据,以及菜单列表。
-     */
-//    @Parameter(name = "loginName", example = "admin")
-//    @Parameter(name = "password", example = "IP3ccke3GhH45iGHB5qP9p7iZw6xUyj28Ju10rnBiPKOI35sc%2BjI7%2FdsjOkHWMfUwGYGfz8ik31HC2Ruk%2Fhkd9f6RPULTHj7VpFdNdde2P9M4mQQnFBAiPM7VT9iW3RyCtPlJexQ3nAiA09OqG%2F0sIf1kcyveSrulxembARDbDo%3D")
-//    @SaIgnore
-//    @OperationLog(type = SysOperationLogType.LOGIN_MOBILE, saveResponse = false)
-//    @PostMapping("/doMobileLogin")
-//    public ResponseResult<JSONObject> doMobileLogin(
-//            @MyRequestBody String loginName,
-//            @MyRequestBody String password) throws UnsupportedEncodingException {
-//        if (MyCommonUtil.existBlankArgument(loginName, password)) {
-//            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
-//        }
-//        ResponseResult<SysUser> verifyResult = this.verifyAndHandleLoginUser(loginName, password);
-//        if (!verifyResult.isSuccess()) {
-//            return ResponseResult.errorFrom(verifyResult);
-//        }
-//        JSONObject jsonData = this.buildMobileLoginDataAndLogin(verifyResult.getData());
-//        return ResponseResult.success(jsonData);
-//    }
-
-    /**
-     * 登出操作。同时将Session相关的信息从缓存中删除。
-     *
-     * @return 应答结果对象。
-     */
-    @OperationLog(type = SysOperationLogType.LOGOUT)
-    @PostMapping("/doLogout")
-    public ResponseResult<Void> doLogout() {
-        String sessionId = TokenData.takeFromRequest().getSessionId();
-        redissonClient.getBucket(TokenData.takeFromRequest().getMySessionId()).deleteAsync();
-        redissonClient.getBucket(RedisKeyUtil.makeSessionPermCodeKey(sessionId)).deleteAsync();
-        redissonClient.getBucket(RedisKeyUtil.makeSessionPermIdKey(sessionId)).deleteAsync();
-        sysDataPermService.removeDataPermCache(sessionId);
-        cacheHelper.removeAllSessionCache(sessionId);
-        StpUtil.logout();
-        return ResponseResult.success();
-    }
-
-    /**
-     * 在登录之后,通过token再次获取登录信息。
-     * 用于在当前浏览器登录系统后,在新tab页中可以免密登录。
-     *
-     * @return 应答结果对象,其中包括JWT的Token数据,以及菜单列表。
-     */
-    @GetMapping("/getLoginInfo")
-    public ResponseResult<JSONObject> getLoginInfo() {
-        TokenData tokenData = TokenData.takeFromRequest();
-        JSONObject jsonData = new JSONObject();
-        jsonData.put(SHOW_NAME_FIELD, tokenData.getShowName());
-        jsonData.put(IS_ADMIN, tokenData.getIsAdmin());
-        if (StrUtil.isNotBlank(tokenData.getHeadImageUrl())) {
-            jsonData.put(HEAD_IMAGE_URL_FIELD, tokenData.getHeadImageUrl());
-        }
-        return ResponseResult.success(jsonData);
-    }
-
-    /**
-     * 用户修改自己的密码。
-     *
-     * @param oldPass 原有密码。
-     * @param newPass 新密码。
-     * @return 应答结果对象。
-     */
-    @PostMapping("/changePassword")
-    public ResponseResult<Void> changePassword(
-            @MyRequestBody String oldPass, @MyRequestBody String newPass) throws UnsupportedEncodingException {
-        //校验密码是否为空
-        if (MyCommonUtil.existBlankArgument(newPass, oldPass)) {
-            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
-        }
-
-        TokenData tokenData = TokenData.takeFromRequest();
-        SysUserWeb user = sysUserWebService.getById(tokenData.getUserId());
-        oldPass = URLDecoder.decode(oldPass, StandardCharsets.UTF_8.name());
-        // NOTE: 第一次使用时,请务必阅读ApplicationConstant.PRIVATE_KEY的代码注释。
-        // 执行RsaUtil工具类中的main函数,可以生成新的公钥和私钥。
-        oldPass = RsaUtil.decrypt(oldPass, ApplicationConstant.PRIVATE_KEY);
-        if (user == null || !passwordEncoder.matches(oldPass, user.getPassword())) {
-            return ResponseResult.error(ErrorCodeEnum.INVALID_USERNAME_PASSWORD);
-        }
-        newPass = URLDecoder.decode(newPass, StandardCharsets.UTF_8.name());
-        newPass = RsaUtil.decrypt(newPass, ApplicationConstant.PRIVATE_KEY);
-        if (!sysUserWebService.changePassword(tokenData.getUserId(), newPass)) {
-            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
-        }
-        return ResponseResult.success();
-    }
-
-    /**
-     * 忘记密码。
-     * @return 应答结果对象。
-     */
-    @PostMapping("/findPassword")
-    public ResponseResult<String> findPassword(
-            @MyRequestBody String smsCode, @MyRequestBody String mobile) throws UnsupportedEncodingException {
-        //校验是否为空
-        if (MyCommonUtil.existBlankArgument(smsCode, mobile)) {
-            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
-        }
-
-        SysUserWeb sysUserByMobile = sysUserWebService.getSysUserByMobile(mobile);
-//        if (ObjectUtils.isEmpty(sysUserByMobile)){
-//            return ResponseResult.error(ErrorCodeEnum.INVALID_MOBILE_CODE);
-//        }
-
-        // 验证短信验证码
-        if (!smsService.validateSmsCode(smsCode,mobile)) {
-            return ResponseResult.error(ErrorCodeEnum.SMSCODE_ERR);
-        }
-
-        return ResponseResult.success(sysUserByMobile.getPassword());
-    }
-
-
-    /**
-     * 上传并修改用户头像。
-     *
-     * @param uploadFile 上传的头像文件。
-     */
-//    @PostMapping("/changeHeadImage")
-//    public void changeHeadImage(@RequestParam("uploadFile") MultipartFile uploadFile) throws IOException {
-//        UploadStoreInfo storeInfo = MyModelUtil.getUploadStoreInfo(SysUser.class, HEAD_IMAGE_URL_FIELD);
-//        BaseUpDownloader upDownloader = upDownloaderFactory.get(storeInfo.getStoreType());
-//        UploadResponseInfo responseInfo = upDownloader.doUpload(null,
-//                appConfig.getUploadFileBaseDir(), SysUser.class.getSimpleName(), HEAD_IMAGE_URL_FIELD, true, uploadFile);
-//        if (BooleanUtil.isTrue(responseInfo.getUploadFailed())) {
-//            ResponseResult.output(HttpServletResponse.SC_FORBIDDEN,
-//                    ResponseResult.error(ErrorCodeEnum.UPLOAD_FAILED, responseInfo.getErrorMessage()));
-//            return;
-//        }
-//        responseInfo.setDownloadUri("/admin/upms/login/downloadHeadImage");
-//        String newHeadImage = JSONArray.toJSONString(CollUtil.newArrayList(responseInfo));
-//        if (!sysUserService.changeHeadImage(TokenData.takeFromRequest().getUserId(), newHeadImage)) {
-//            ResponseResult.output(HttpServletResponse.SC_FORBIDDEN, ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST));
-//            return;
-//        }
-//        ResponseResult.output(ResponseResult.success(responseInfo));
-//    }
-
-    /**
-     * 下载用户头像。
-     *
-     * @param filename 文件名。如果没有提供该参数,就从当前记录的指定字段中读取。
-     * @param response Http 应答对象。
-     */
-    @GetMapping("/downloadHeadImage")
-    public void downloadHeadImage(String filename, HttpServletResponse response) {
-        try {
-            UploadStoreInfo storeInfo = MyModelUtil.getUploadStoreInfo(SysUser.class, HEAD_IMAGE_URL_FIELD);
-            BaseUpDownloader upDownloader = upDownloaderFactory.get(storeInfo.getStoreType());
-            upDownloader.doDownload(appConfig.getUploadFileBaseDir(),
-                    SysUser.class.getSimpleName(), HEAD_IMAGE_URL_FIELD, filename, true, response);
-        } catch (Exception e) {
-            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
-            log.error(e.getMessage(), e);
-        }
-    }
-
-//    private ResponseResult<SysUser> verifyAndHandleLoginUser(
-//            String loginName, String password) throws UnsupportedEncodingException {
-//        String errorMessage;
-//        SysUser user = sysUserService.getSysUserByLoginName(loginName);
-//        password = URLDecoder.decode(password, StandardCharsets.UTF_8.name());
-//        // NOTE: 第一次使用时,请务必阅读ApplicationConstant.PRIVATE_KEY的代码注释。
-//        // 执行RsaUtil工具类中的main函数,可以生成新的公钥和私钥。
-//        password = RsaUtil.decrypt(password, ApplicationConstant.PRIVATE_KEY);
-//        if (user == null || !passwordEncoder.matches(password, user.getPassword())) {
-//            return ResponseResult.error(ErrorCodeEnum.INVALID_USERNAME_PASSWORD);
-//        }
-//        if (user.getUserStatus() == SysUserStatus.STATUS_LOCKED) {
-//            errorMessage = "登录失败,用户账号被锁定!";
-//            return ResponseResult.error(ErrorCodeEnum.INVALID_USER_STATUS, errorMessage);
-//        }
-//        if (BooleanUtil.isTrue(appConfig.getExcludeLogin())) {
-//            String deviceType = MyCommonUtil.getDeviceTypeWithString();
-//            LoginUserInfo userInfo = BeanUtil.copyProperties(user, LoginUserInfo.class);
-//            String loginId = SaTokenUtil.makeLoginId(userInfo);
-//            StpUtil.kickout(loginId, deviceType);
-//        }
-//        return ResponseResult.success(user);
-//    }
-
-    private JSONObject buildLoginDataAndLogin(SysUserWeb user) {
-        TokenData tokenData = this.loginAndCreateToken(user);
-        // 这里手动将TokenData存入request,便于OperationLogAspect统一处理操作日志。
-        TokenData.addToRequest(tokenData);
-        JSONObject jsonData = this.createResponseData(user);
-        Collection<SysMenu> allMenuList;
-        return jsonData;
-    }
-
-    private JSONObject buildMobileLoginDataAndLogin(SysUserWeb user) {
-        TokenData tokenData = this.loginAndCreateToken(user);
-        // 这里手动将TokenData存入request,便于OperationLogAspect统一处理操作日志。
-        TokenData.addToRequest(tokenData);
-        JSONObject jsonData = this.createResponseData(user);
-        List<MobileEntry> mobileEntryList;
-
-        return jsonData;
-    }
-
-    private TokenData loginAndCreateToken(SysUserWeb user) {
-        String deviceType = MyCommonUtil.getDeviceTypeWithString();
-        LoginUserInfo userInfo = BeanUtil.copyProperties(user, LoginUserInfo.class);
-        String loginId = SaTokenUtil.makeLoginIdByWeb(userInfo);
-        StpUtil.login(loginId, deviceType);
-        SaSession session = StpUtil.getTokenSession();
-        TokenData tokenData = this.buildTokenData(user, session.getId(), StpUtil.getLoginDevice());
-        String mySessionId = RedisKeyUtil.getSessionIdPrefix(tokenData, user.getLoginName()) + MyCommonUtil.generateUuid();
-        tokenData.setMySessionId(mySessionId);
-        tokenData.setToken(session.getToken());
-        redissonClient.getBucket(mySessionId)
-                .set(JSON.toJSONString(tokenData), appConfig.getSessionExpiredSeconds(), TimeUnit.SECONDS);
-        session.set(TokenData.REQUEST_ATTRIBUTE_NAME, tokenData);
-        return tokenData;
-    }
-
-    private JSONObject createResponseData(SysUserWeb user) {
-        JSONObject jsonData = new JSONObject();
-        jsonData.put(TokenData.REQUEST_ATTRIBUTE_NAME, StpUtil.getTokenValue());
-        jsonData.put(SHOW_NAME_FIELD, user.getShowName());
-        if (StrUtil.isNotBlank(user.getHeadImageUrl())) {
-            jsonData.put(HEAD_IMAGE_URL_FIELD, user.getHeadImageUrl());
-        }
-        return jsonData;
-    }
-
-    private void appendResponseMenuAndPermCodeData(
-            JSONObject responseData, Collection<SysMenu> allMenuList, Collection<String> menuCodeList) {
-        allMenuList.stream()
-                .filter(m -> m.getExtraObject() != null && StrUtil.isNotBlank(m.getExtraObject().getMenuCode()))
-                .forEach(m -> CollUtil.addAll(menuCodeList, m.getExtraObject().getMenuCode()));
-        List<SysMenu> menuList = allMenuList.stream()
-                .filter(m -> m.getMenuType() <= SysMenuType.TYPE_MENU).collect(Collectors.toList());
-        responseData.put("menuList", menuList);
-        responseData.put("permCodeList", menuCodeList);
-    }
-
-    private TokenData buildTokenData(SysUserWeb user, String sessionId, String deviceType) {
-        TokenData tokenData = new TokenData();
-        tokenData.setSessionId(sessionId);
-        tokenData.setUserId(user.getUserId());
-        tokenData.setLoginName(user.getLoginName());
-        tokenData.setShowName(user.getShowName());
-        tokenData.setLoginIp(IpUtil.getRemoteIpAddress(ContextUtil.getHttpRequest()));
-        tokenData.setLoginTime(new Date());
-        tokenData.setDeviceType(deviceType);
-        tokenData.setHeadImageUrl(user.getHeadImageUrl());
-        return tokenData;
-    }
-
-    private void putUserSysPermCache(String sessionId, Collection<String> permUrlSet) {
-        if (CollUtil.isEmpty(permUrlSet)) {
-            return;
-        }
-        String sessionPermKey = RedisKeyUtil.makeSessionPermIdKey(sessionId);
-        RSet<String> redisPermSet = redissonClient.getSet(sessionPermKey);
-        redisPermSet.addAll(permUrlSet);
-        redisPermSet.expire(appConfig.getSessionExpiredSeconds(), TimeUnit.SECONDS);
-    }
-
-    private void putUserSysPermCodeCache(String sessionId, Collection<String> permCodeSet) {
-        if (CollUtil.isEmpty(permCodeSet)) {
-            return;
-        }
-        String sessionPermCodeKey = RedisKeyUtil.makeSessionPermCodeKey(sessionId);
-        RSet<String> redisPermSet = redissonClient.getSet(sessionPermCodeKey);
-        redisPermSet.addAll(permCodeSet);
-        redisPermSet.expire(appConfig.getSessionExpiredSeconds(), TimeUnit.SECONDS);
-    }
-
-    private OnlinePermData getOnlineMenuPermData(Collection<SysMenu> allMenuList) {
-        List<SysMenu> onlineMenuList = allMenuList.stream()
-                .filter(m -> m.getOnlineFormId() != null && m.getMenuType().equals(SysMenuType.TYPE_BUTTON))
-                .collect(Collectors.toList());
-        if (CollUtil.isEmpty(onlineMenuList)) {
-            return new OnlinePermData();
-        }
-        Set<Long> formIds = allMenuList.stream()
-                .filter(m -> m.getOnlineFormId() != null
-                        && m.getOnlineFlowEntryId() == null
-                        && m.getMenuType().equals(SysMenuType.TYPE_MENU))
-                .map(SysMenu::getOnlineFormId)
-                .collect(Collectors.toSet());
-        Set<Long> viewFormIds = onlineMenuList.stream()
-                .filter(m -> m.getOnlineMenuPermType() == SysOnlineMenuPermType.TYPE_VIEW)
-                .map(SysMenu::getOnlineFormId)
-                .collect(Collectors.toSet());
-        Set<Long> editFormIds = onlineMenuList.stream()
-                .filter(m -> m.getOnlineMenuPermType() == SysOnlineMenuPermType.TYPE_EDIT)
-                .map(SysMenu::getOnlineFormId)
-                .collect(Collectors.toSet());
-        Map<String, Object> permDataMap =
-                onlineOperationService.calculatePermData(formIds, viewFormIds, editFormIds);
-        OnlinePermData permData = BeanUtil.mapToBean(permDataMap, OnlinePermData.class, false, null);
-        permData.permUrlSet.addAll(permData.onlineWhitelistUrls);
-        return permData;
-    }
-
-    private OnlinePermData getFlowOnlineMenuPermData(Collection<SysMenu> allMenuList) {
-        List<SysMenu> flowOnlineMenuList = allMenuList.stream()
-                .filter(m -> m.getOnlineFlowEntryId() != null).collect(Collectors.toList());
-        Set<Long> entryIds = flowOnlineMenuList.stream()
-                .map(SysMenu::getOnlineFlowEntryId).collect(Collectors.toSet());
-        List<Map<String, Object>> flowPermDataList = flowOnlineOperationService.calculatePermData(entryIds);
-        List<OnlineFlowPermData> flowOnlinePermDataList =
-                MyModelUtil.mapToBeanList(flowPermDataList, OnlineFlowPermData.class);
-        OnlinePermData permData = new OnlinePermData();
-        flowOnlinePermDataList.forEach(perm -> {
-            permData.permCodeSet.addAll(perm.getPermCodeList());
-            permData.permUrlSet.addAll(perm.getPermList());
-        });
-        return permData;
-    }
-
-    private Set<String> getReportMenuPermData(Collection<SysMenu> allMenuList) {
-        Set<String> permSet = new HashSet<>();
-        List<SysMenu> reportMenuList = allMenuList.stream()
-                .filter(m -> m.getReportPageId() != null).collect(Collectors.toList());
-        if (CollUtil.isEmpty(reportMenuList)) {
-            return permSet;
-        }
-        Set<Long> pageIds = reportMenuList.stream().map(SysMenu::getReportPageId).collect(Collectors.toSet());
-        Map<Long, Set<String>> reportPermDataMap = reportOperationService.calculatePermData(pageIds);
-        for (Long pageId : pageIds) {
-            CollUtil.addAll(permSet, reportPermDataMap.get(pageId));
-        }
-        return permSet;
-    }
-
-    private Set<String> getOnlineMobileEntryPermData(Collection<MobileEntry> mobileEntryList) {
-        List<MobileEntry> onlineMobileEntryList = mobileEntryList.stream()
-                .filter(m -> m.getExtraObject() != null
-                        && m.getExtraObject().getOnlineFormId() != null
-                        && m.getExtraObject().getOnlineFlowEntryId() == null)
-                .collect(Collectors.toList());
-        if (CollUtil.isEmpty(onlineMobileEntryList)) {
-            return CollUtil.newHashSet();
-        }
-        Set<Long> onlineFormIds = onlineMobileEntryList.stream()
-                .map(m -> m.getExtraObject().getOnlineFormId()).collect(Collectors.toSet());
-        Map<String, Object> permDataMap =
-                onlineOperationService.calculatePermData(onlineFormIds, onlineFormIds, onlineFormIds);
-        OnlinePermData permData = BeanUtil.mapToBean(permDataMap, OnlinePermData.class, false, null);
-        permData.permUrlSet.addAll(permData.onlineWhitelistUrls);
-        return permData.permUrlSet;
-    }
-
-    private Set<String> getFlowOnlineMobileEntryPermData(Collection<MobileEntry> mobileEntryList) {
-        List<MobileEntry> flowOnlineMobileEntryList = mobileEntryList.stream()
-                .filter(m -> m.getExtraData() != null && m.getExtraObject().getOnlineFlowEntryId() != null)
-                .collect(Collectors.toList());
-        Set<Long> entryIds = flowOnlineMobileEntryList.stream()
-                .map(m -> m.getExtraObject().getOnlineFlowEntryId()).collect(Collectors.toSet());
-        List<Map<String, Object>> flowPermDataList = flowOnlineOperationService.calculatePermData(entryIds);
-        List<OnlineFlowPermData> flowOnlinePermDataList =
-                MyModelUtil.mapToBeanList(flowPermDataList, OnlineFlowPermData.class);
-        Set<String> permSet = new HashSet<>();
-        flowOnlinePermDataList.forEach(perm -> permSet.addAll(perm.getPermList()));
-        return permSet;
-    }
-
-    private Set<String> getReportMobileEntryPermData(Collection<MobileEntry> mobileEntryList) {
-        Set<String> permSet = new HashSet<>();
-        List<MobileEntry> reportMobileEntryList = mobileEntryList.stream()
-                .filter(m -> m.getExtraObject() != null && m.getExtraObject().getReportPageId() != null)
-                .collect(Collectors.toList());
-        if (CollUtil.isEmpty(reportMobileEntryList)) {
-            return permSet;
-        }
-        Set<Long> pageIds = reportMobileEntryList.stream()
-                .map(m -> m.getExtraObject().getReportPageId()).collect(Collectors.toSet());
-        Map<Long, Set<String>> reportPermDataMap = reportOperationService.calculatePermData(pageIds);
-        for (Long pageId : pageIds) {
-            CollUtil.addAll(permSet, reportPermDataMap.get(pageId));
-        }
-        return permSet;
-    }
-
-    static class OnlinePermData {
-        public final Set<String> permCodeSet = new HashSet<>();
-        public final Set<String> permUrlSet = new HashSet<>();
-        public final List<String> onlineWhitelistUrls = new LinkedList<>();
-    }
-
-    @Data
-    static class OnlineFlowPermData {
-        private List<String> permCodeList;
-        private List<String> permList;
-    }
-}

+ 252 - 0
application-webadmin/src/main/java/com/tourism/webadmin/back/controller/RestaurantCartController.java

@@ -0,0 +1,252 @@
+package com.tourism.webadmin.app.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import com.tourism.common.log.annotation.OperationLog;
+import com.tourism.common.log.model.constant.SysOperationLogType;
+import com.github.pagehelper.page.PageMethod;
+import com.tourism.webadmin.back.vo.*;
+import com.tourism.webadmin.back.dto.*;
+import com.tourism.webadmin.back.model.*;
+import com.tourism.webadmin.back.service.*;
+import com.tourism.common.core.object.*;
+import com.tourism.common.core.util.*;
+import com.tourism.common.core.constant.*;
+import com.tourism.common.core.annotation.MyRequestBody;
+import com.tourism.common.additional.config.ApplicationConfig;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * 外卖购物车操作控制器类。
+ *
+ * @author 吃饭睡觉
+ * @date 2024-09-06
+ */
+@Tag(name = "外卖购物车管理接口")
+@Slf4j
+@RestController
+@RequestMapping("/admin/app/restaurantCart")
+public class RestaurantCartController {
+
+    @Autowired
+    private ApplicationConfig appConfig;
+    @Autowired
+    private RestaurantCartService restaurantCartService;
+
+    /**
+     * 新增外卖购物车数据。
+     *
+     * @param restaurantCartDto 新增对象。
+     * @return 应答结果对象,包含新增对象主键Id。
+     */
+    @ApiOperationSupport(ignoreParameters = {"restaurantCartDto.id"})
+    @SaCheckPermission("restaurantCart.add")
+    @OperationLog(type = SysOperationLogType.ADD)
+    @PostMapping("/add")
+    public ResponseResult<Long> add(@MyRequestBody RestaurantCartDto restaurantCartDto) {
+        String errorMessage = MyCommonUtil.getModelValidationError(restaurantCartDto, false);
+        if (errorMessage != null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
+        }
+        RestaurantCart restaurantCart = MyModelUtil.copyTo(restaurantCartDto, RestaurantCart.class);
+        restaurantCart = restaurantCartService.saveNew(restaurantCart);
+        return ResponseResult.success(restaurantCart.getId());
+    }
+
+    /**
+     * 更新外卖购物车数据。
+     *
+     * @param restaurantCartDto 更新对象。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("restaurantCart.update")
+    @OperationLog(type = SysOperationLogType.UPDATE)
+    @PostMapping("/update")
+    public ResponseResult<Void> update(@MyRequestBody RestaurantCartDto restaurantCartDto) {
+        String errorMessage = MyCommonUtil.getModelValidationError(restaurantCartDto, true);
+        if (errorMessage != null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
+        }
+        RestaurantCart restaurantCart = MyModelUtil.copyTo(restaurantCartDto, RestaurantCart.class);
+        RestaurantCart originalRestaurantCart = restaurantCartService.getById(restaurantCart.getId());
+        if (originalRestaurantCart == null) {
+            // NOTE: 修改下面方括号中的话述
+            errorMessage = "数据验证失败,当前 [数据] 并不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        if (!restaurantCartService.update(restaurantCart, originalRestaurantCart)) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
+        }
+        return ResponseResult.success();
+    }
+
+    /**
+     * 删除外卖购物车数据。
+     *
+     * @param id 删除对象主键Id。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("restaurantCart.delete")
+    @OperationLog(type = SysOperationLogType.DELETE)
+    @PostMapping("/delete")
+    public ResponseResult<Void> delete(@MyRequestBody Long id) {
+        if (MyCommonUtil.existBlankArgument(id)) {
+            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
+        }
+        return this.doDelete(id);
+    }
+
+    /**
+     * 批量删除外卖购物车数据。
+     *
+     * @param idList 待删除对象的主键Id列表。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("restaurantCart.delete")
+    @OperationLog(type = SysOperationLogType.DELETE_BATCH)
+    @PostMapping("/deleteBatch")
+    public ResponseResult<Void> deleteBatch(@MyRequestBody List<Long> idList) {
+        if (MyCommonUtil.existBlankArgument(idList)) {
+            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
+        }
+        for (Long id : idList) {
+            ResponseResult<Void> responseResult = this.doDelete(id);
+            if (!responseResult.isSuccess()) {
+                return responseResult;
+            }
+        }
+        return ResponseResult.success();
+    }
+
+    /**
+     * 列出符合过滤条件的外卖购物车列表。
+     *
+     * @param restaurantCartDtoFilter 过滤对象。
+     * @param orderParam 排序参数。
+     * @param pageParam 分页参数。
+     * @return 应答结果对象,包含查询结果集。
+     */
+    @SaCheckPermission("restaurantCart.view")
+    @PostMapping("/list")
+    public ResponseResult<MyPageData<RestaurantCartVo>> list(
+            @MyRequestBody RestaurantCartDto restaurantCartDtoFilter,
+            @MyRequestBody MyOrderParam orderParam,
+            @MyRequestBody MyPageParam pageParam) {
+        if (pageParam != null) {
+            PageMethod.startPage(pageParam.getPageNum(), pageParam.getPageSize(), pageParam.getCount());
+        }
+        RestaurantCart restaurantCartFilter = MyModelUtil.copyTo(restaurantCartDtoFilter, RestaurantCart.class);
+        String orderBy = MyOrderParam.buildOrderBy(orderParam, RestaurantCart.class);
+        List<RestaurantCart> restaurantCartList =
+                restaurantCartService.getRestaurantCartListWithRelation(restaurantCartFilter, orderBy);
+        return ResponseResult.success(MyPageUtil.makeResponseData(restaurantCartList, RestaurantCartVo.class));
+    }
+
+    /**
+     * 导入主表数据列表。
+     *
+     * @param importFile 上传的文件,目前仅仅支持xlsx和xls两种格式。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("restaurantCart.import")
+    @OperationLog(type = SysOperationLogType.IMPORT)
+    @PostMapping("/import")
+    public ResponseResult<Void> importBatch(
+            @RequestParam Boolean skipHeader,
+            @RequestParam("importFile") MultipartFile importFile) throws IOException {
+        String filename = ImportUtil.saveImportFile(appConfig.getUploadFileBaseDir(), null, importFile);
+        // 这里可以指定需要忽略导入的字段集合。如创建时间、创建人、更新时间、更新人、主键Id和逻辑删除,
+        // 以及一些存在缺省值且无需导入的字段。其中主键字段和逻辑删除字段不需要在这里设置,批量插入逻辑会自动处理的。
+        Set<String> ignoreFieldSet = new HashSet<>();
+        ignoreFieldSet.add("createUserId");
+        ignoreFieldSet.add("createTime");
+        ignoreFieldSet.add("updateUserId");
+        ignoreFieldSet.add("updateTime");
+        List<ImportUtil.ImportHeaderInfo> headerInfoList = ImportUtil.makeHeaderInfoList(RestaurantCart.class, ignoreFieldSet);
+        // 下面是导入时需要注意的地方,如果我们缺省生成的代码,与实际情况存在差异,请手动修改。
+        // 1. 头信息数据字段,我们只是根据当前的主表实体对象生成了缺省数组,开发者可根据实际情况,对headerInfoList进行修改。
+        ImportUtil.ImportHeaderInfo[] headerInfos = headerInfoList.toArray(new ImportUtil.ImportHeaderInfo[]{});
+        // 2. 这里需要根据实际情况决定,导入文件中第一行是否为中文头信息,如果是可以跳过。这里我们默认为true。
+        // 这里根据自己的实际需求,为doImport的最后一个参数,传递需要进行字典转换的字段集合。
+        // 注意,集合中包含需要翻译的Java字段名,如: gradeId。
+        Set<String> translatedDictFieldSet = new HashSet<>();
+        List<RestaurantCart> dataList =
+                ImportUtil.doImport(headerInfos, skipHeader, filename, RestaurantCart.class, translatedDictFieldSet);
+        restaurantCartService.saveNewBatch(dataList, -1);
+        return ResponseResult.success();
+    }
+
+    /**
+     * 导出符合过滤条件的外卖购物车列表。
+     *
+     * @param restaurantCartDtoFilter 过滤对象。
+     * @param orderParam 排序参数。
+     * @throws IOException 文件读写失败。
+     */
+    @SaCheckPermission("restaurantCart.export")
+    @OperationLog(type = SysOperationLogType.EXPORT, saveResponse = false)
+    @PostMapping("/export")
+    public void export(
+            @MyRequestBody RestaurantCartDto restaurantCartDtoFilter,
+            @MyRequestBody MyOrderParam orderParam) throws IOException {
+        RestaurantCart restaurantCartFilter = MyModelUtil.copyTo(restaurantCartDtoFilter, RestaurantCart.class);
+        String orderBy = MyOrderParam.buildOrderBy(orderParam, RestaurantCart.class);
+        List<RestaurantCart> resultList =
+                restaurantCartService.getRestaurantCartListWithRelation(restaurantCartFilter, orderBy);
+        // 导出文件的标题数组
+        // NOTE: 下面的代码中仅仅导出了主表数据,主表聚合计算数据和主表关联字典的数据。
+        // 一对一从表数据的导出,可根据需要自行添加。如:headerMap.put("slaveFieldName.xxxField", "标题名称")
+        Map<String, String> headerMap = new LinkedHashMap<>(10);
+        headerMap.put("id", "主键id");
+        headerMap.put("userId", "用户id");
+        headerMap.put("restaurantId", "餐馆id");
+        headerMap.put("restaurantFoodId", "美食id");
+        headerMap.put("count", "数量");
+        headerMap.put("createUserId", "创建者Id");
+        headerMap.put("createTime", "创建时间");
+        headerMap.put("updateUserId", "更新者Id");
+        headerMap.put("updateTime", "最后更新时间");
+        headerMap.put("deletedFlag", "删除标记(1: 正常 -1: 已删除)");
+        ExportUtil.doExport(resultList, headerMap, "restaurantCart.xlsx");
+    }
+
+    /**
+     * 查看指定外卖购物车对象详情。
+     *
+     * @param id 指定对象主键Id。
+     * @return 应答结果对象,包含对象详情。
+     */
+    @SaCheckPermission("restaurantCart.view")
+    @GetMapping("/view")
+    public ResponseResult<RestaurantCartVo> view(@RequestParam Long id) {
+        RestaurantCart restaurantCart = restaurantCartService.getByIdWithRelation(id, MyRelationParam.full());
+        if (restaurantCart == null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
+        }
+        RestaurantCartVo restaurantCartVo = MyModelUtil.copyTo(restaurantCart, RestaurantCartVo.class);
+        return ResponseResult.success(restaurantCartVo);
+    }
+
+    private ResponseResult<Void> doDelete(Long id) {
+        String errorMessage;
+        // 验证关联Id的数据合法性
+        RestaurantCart originalRestaurantCart = restaurantCartService.getById(id);
+        if (originalRestaurantCart == null) {
+            // NOTE: 修改下面方括号中的话述
+            errorMessage = "数据验证失败,当前 [对象] 并不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        if (!restaurantCartService.remove(id)) {
+            errorMessage = "数据操作失败,删除的对象不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        return ResponseResult.success();
+    }
+}

+ 404 - 0
application-webadmin/src/main/java/com/tourism/webadmin/back/controller/RestaurantFoodInfoController.java

@@ -0,0 +1,404 @@
+package com.tourism.webadmin.back.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.dev33.satoken.annotation.SaIgnore;
+import cn.hutool.core.util.BooleanUtil;
+import cn.hutool.core.util.ReflectUtil;
+import com.tourism.common.core.upload.BaseUpDownloader;
+import com.tourism.common.core.upload.UpDownloaderFactory;
+import com.tourism.common.core.upload.UploadResponseInfo;
+import com.tourism.common.core.upload.UploadStoreInfo;
+import com.tourism.common.log.annotation.OperationLog;
+import com.tourism.common.log.model.constant.SysOperationLogType;
+import com.github.pagehelper.page.PageMethod;
+import com.tourism.webadmin.back.vo.*;
+import com.tourism.webadmin.back.dto.*;
+import com.tourism.webadmin.back.model.*;
+import com.tourism.webadmin.back.service.*;
+import com.tourism.common.core.object.*;
+import com.tourism.common.core.util.*;
+import com.tourism.common.core.constant.*;
+import com.tourism.common.core.annotation.MyRequestBody;
+import com.tourism.common.redis.cache.SessionCacheHelper;
+import com.tourism.common.additional.config.ApplicationConfig;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import com.tourism.webadmin.upms.service.SysDeptService;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import jakarta.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 餐馆美食信息操作控制器类。
+ *
+ * @author 吃饭睡觉
+ * @date 2024-09-06
+ */
+@Tag(name = "餐馆美食信息管理接口")
+@Slf4j
+@RestController
+@RequestMapping("/admin/app/restaurantFoodInfo")
+public class RestaurantFoodInfoController {
+
+    @Autowired
+    private ApplicationConfig appConfig;
+    @Autowired
+    private SessionCacheHelper cacheHelper;
+    @Autowired
+    private UpDownloaderFactory upDownloaderFactory;
+    @Autowired
+    private RestaurantFoodInfoService restaurantFoodInfoService;
+    @Autowired
+    private SysDeptService sysDeptService;
+    @Autowired
+    private RestaurantInfoService restaurantService;
+
+    /**
+     * 新增餐馆美食信息数据。
+     *
+     * @param restaurantFoodInfoDto 新增对象。
+     * @return 应答结果对象,包含新增对象主键Id。
+     */
+    @ApiOperationSupport(ignoreParameters = {"restaurantFoodInfoDto.id", "restaurantFoodInfoDto.searchString"})
+    @SaCheckPermission("restaurantFoodInfo.add")
+    @OperationLog(type = SysOperationLogType.ADD)
+    @PostMapping("/add")
+    public ResponseResult<String> add(@MyRequestBody RestaurantFoodInfoDto restaurantFoodInfoDto) {
+        String errorMessage = MyCommonUtil.getModelValidationError(restaurantFoodInfoDto, false);
+        if (errorMessage != null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
+        }
+        RestaurantFoodInfo restaurantFoodInfo = MyModelUtil.copyTo(restaurantFoodInfoDto, RestaurantFoodInfo.class);
+        // 验证关联Id的数据合法性
+        CallResult callResult = restaurantFoodInfoService.verifyRelatedData(restaurantFoodInfo, null);
+        if (!callResult.isSuccess()) {
+            return ResponseResult.errorFrom(callResult);
+        }
+        restaurantFoodInfo = restaurantFoodInfoService.saveNew(restaurantFoodInfo);
+        return ResponseResult.success(restaurantFoodInfo.getId());
+    }
+
+    /**
+     * 更新餐馆美食信息数据。
+     *
+     * @param restaurantFoodInfoDto 更新对象。
+     * @return 应答结果对象。
+     */
+    @ApiOperationSupport(ignoreParameters = {"restaurantFoodInfoDto.searchString"})
+    @SaCheckPermission("restaurantFoodInfo.update")
+    @OperationLog(type = SysOperationLogType.UPDATE)
+    @PostMapping("/update")
+    public ResponseResult<Void> update(@MyRequestBody RestaurantFoodInfoDto restaurantFoodInfoDto) {
+        String errorMessage = MyCommonUtil.getModelValidationError(restaurantFoodInfoDto, true);
+        if (errorMessage != null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
+        }
+        RestaurantFoodInfo restaurantFoodInfo = MyModelUtil.copyTo(restaurantFoodInfoDto, RestaurantFoodInfo.class);
+        RestaurantFoodInfo originalRestaurantFoodInfo = restaurantFoodInfoService.getById(restaurantFoodInfo.getId());
+        if (originalRestaurantFoodInfo == null) {
+            // NOTE: 修改下面方括号中的话述
+            errorMessage = "数据验证失败,当前 [数据] 并不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        // 验证关联Id的数据合法性
+        CallResult callResult = restaurantFoodInfoService.verifyRelatedData(restaurantFoodInfo, originalRestaurantFoodInfo);
+        if (!callResult.isSuccess()) {
+            return ResponseResult.errorFrom(callResult);
+        }
+        if (!restaurantFoodInfoService.update(restaurantFoodInfo, originalRestaurantFoodInfo)) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
+        }
+        return ResponseResult.success();
+    }
+
+    /**
+     * 删除餐馆美食信息数据。
+     *
+     * @param id 删除对象主键Id。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("restaurantFoodInfo.delete")
+    @OperationLog(type = SysOperationLogType.DELETE)
+    @PostMapping("/delete")
+    public ResponseResult<Void> delete(@MyRequestBody Long id) {
+        if (MyCommonUtil.existBlankArgument(id)) {
+            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
+        }
+        return this.doDelete(id);
+    }
+
+    /**
+     * 批量删除餐馆美食信息数据。
+     *
+     * @param idList 待删除对象的主键Id列表。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("restaurantFoodInfo.delete")
+    @OperationLog(type = SysOperationLogType.DELETE_BATCH)
+    @PostMapping("/deleteBatch")
+    public ResponseResult<Void> deleteBatch(@MyRequestBody List<Long> idList) {
+        if (MyCommonUtil.existBlankArgument(idList)) {
+            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
+        }
+        for (Long id : idList) {
+            ResponseResult<Void> responseResult = this.doDelete(id);
+            if (!responseResult.isSuccess()) {
+                return responseResult;
+            }
+        }
+        return ResponseResult.success();
+    }
+
+    /**
+     * 列出符合过滤条件的餐馆美食信息列表。
+     *
+     * @param restaurantFoodInfoDtoFilter 过滤对象。
+     * @param orderParam 排序参数。
+     * @param pageParam 分页参数。
+     * @return 应答结果对象,包含查询结果集。
+     */
+    @SaCheckPermission("restaurantFoodInfo.view")
+    @PostMapping("/list")
+    public ResponseResult<MyPageData<RestaurantFoodInfoVo>> list(
+            @MyRequestBody RestaurantFoodInfoDto restaurantFoodInfoDtoFilter,
+            @MyRequestBody MyOrderParam orderParam,
+            @MyRequestBody MyPageParam pageParam) {
+
+        TokenData tokenData = TokenData.takeFromRequest();
+        //如果不是管理员角色的话,
+        if(BooleanUtil.isFalse(tokenData.getIsAdmin())){
+            List<Long> parentIds = new ArrayList<>();
+            parentIds.add(tokenData.getDeptId());
+            List<Long> allChildDeptIdByParentIds = sysDeptService.getAllChildDeptIdByParentIds(parentIds);
+            // 使用HashSet构造函数将List转换为Set
+            Set<Long> set = new HashSet<>(allChildDeptIdByParentIds);
+            List<RestaurantInfo> restaurantInfoList = restaurantService.getInList("deptId", set);
+            List<String> restaurantIdList = restaurantInfoList.stream().map(RestaurantInfo::getId).collect(Collectors.toList());
+            restaurantFoodInfoDtoFilter.setRestaurantIdList(restaurantIdList);
+        }
+
+        if (pageParam != null) {
+            PageMethod.startPage(pageParam.getPageNum(), pageParam.getPageSize(), pageParam.getCount());
+        }
+        RestaurantFoodInfo restaurantFoodInfoFilter = MyModelUtil.copyTo(restaurantFoodInfoDtoFilter, RestaurantFoodInfo.class);
+        String orderBy = MyOrderParam.buildOrderBy(orderParam, RestaurantFoodInfo.class);
+        List<RestaurantFoodInfo> restaurantFoodInfoList =
+                restaurantFoodInfoService.getRestaurantFoodInfoListWithRelation(restaurantFoodInfoFilter, orderBy);
+        return ResponseResult.success(MyPageUtil.makeResponseData(restaurantFoodInfoList, RestaurantFoodInfoVo.class));
+    }
+
+    /**
+     * 导入主表数据列表。
+     *
+     * @param importFile 上传的文件,目前仅仅支持xlsx和xls两种格式。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("restaurantFoodInfo.import")
+    @OperationLog(type = SysOperationLogType.IMPORT)
+    @PostMapping("/import")
+    public ResponseResult<Void> importBatch(
+            @RequestParam Boolean skipHeader,
+            @RequestParam("importFile") MultipartFile importFile) throws IOException {
+        String filename = ImportUtil.saveImportFile(appConfig.getUploadFileBaseDir(), null, importFile);
+        // 这里可以指定需要忽略导入的字段集合。如创建时间、创建人、更新时间、更新人、主键Id和逻辑删除,
+        // 以及一些存在缺省值且无需导入的字段。其中主键字段和逻辑删除字段不需要在这里设置,批量插入逻辑会自动处理的。
+        Set<String> ignoreFieldSet = new HashSet<>();
+        ignoreFieldSet.add("createUserId");
+        ignoreFieldSet.add("createTime");
+        ignoreFieldSet.add("updateUserId");
+        ignoreFieldSet.add("updateTime");
+        List<ImportUtil.ImportHeaderInfo> headerInfoList = ImportUtil.makeHeaderInfoList(RestaurantFoodInfo.class, ignoreFieldSet);
+        // 下面是导入时需要注意的地方,如果我们缺省生成的代码,与实际情况存在差异,请手动修改。
+        // 1. 头信息数据字段,我们只是根据当前的主表实体对象生成了缺省数组,开发者可根据实际情况,对headerInfoList进行修改。
+        ImportUtil.ImportHeaderInfo[] headerInfos = headerInfoList.toArray(new ImportUtil.ImportHeaderInfo[]{});
+        // 2. 这里需要根据实际情况决定,导入文件中第一行是否为中文头信息,如果是可以跳过。这里我们默认为true。
+        // 这里根据自己的实际需求,为doImport的最后一个参数,传递需要进行字典转换的字段集合。
+        // 注意,集合中包含需要翻译的Java字段名,如: gradeId。
+        Set<String> translatedDictFieldSet = new HashSet<>();
+        List<RestaurantFoodInfo> dataList =
+                ImportUtil.doImport(headerInfos, skipHeader, filename, RestaurantFoodInfo.class, translatedDictFieldSet);
+        CallResult result = restaurantFoodInfoService.verifyImportList(dataList, translatedDictFieldSet);
+        if (!result.isSuccess()) {
+            // result中返回了具体的验证失败对象,如果需要返回更加详细的错误,可根据实际情况手动修改。
+            return ResponseResult.errorFrom(result);
+        }
+        restaurantFoodInfoService.saveNewBatch(dataList, -1);
+        return ResponseResult.success();
+    }
+
+    /**
+     * 导出符合过滤条件的餐馆美食信息列表。
+     *
+     * @param restaurantFoodInfoDtoFilter 过滤对象。
+     * @param orderParam 排序参数。
+     * @throws IOException 文件读写失败。
+     */
+    @SaCheckPermission("restaurantFoodInfo.export")
+    @OperationLog(type = SysOperationLogType.EXPORT, saveResponse = false)
+    @PostMapping("/export")
+    public void export(
+            @MyRequestBody RestaurantFoodInfoDto restaurantFoodInfoDtoFilter,
+            @MyRequestBody MyOrderParam orderParam) throws IOException {
+        RestaurantFoodInfo restaurantFoodInfoFilter = MyModelUtil.copyTo(restaurantFoodInfoDtoFilter, RestaurantFoodInfo.class);
+        String orderBy = MyOrderParam.buildOrderBy(orderParam, RestaurantFoodInfo.class);
+        List<RestaurantFoodInfo> resultList =
+                restaurantFoodInfoService.getRestaurantFoodInfoListWithRelation(restaurantFoodInfoFilter, orderBy);
+        // 导出文件的标题数组
+        // NOTE: 下面的代码中仅仅导出了主表数据,主表聚合计算数据和主表关联字典的数据。
+        // 一对一从表数据的导出,可根据需要自行添加。如:headerMap.put("slaveFieldName.xxxField", "标题名称")
+        Map<String, String> headerMap = new LinkedHashMap<>(14);
+        headerMap.put("id", "主键id");
+        headerMap.put("foodTypeIdDictMap.name", "饭馆美食种类id");
+        headerMap.put("name", "菜品名称");
+        headerMap.put("url", "菜品图片");
+        headerMap.put("enableDictMap.name", "是否启用,0禁用,1启用");
+        headerMap.put("sales", "菜品销量");
+        headerMap.put("price", "菜品价格");
+        headerMap.put("createUserId", "创建用户");
+        headerMap.put("priceUnit", "价格单位");
+        headerMap.put("createTime", "创建时间");
+        headerMap.put("deptIdDictMap.name", "用户所在部门Id");
+        headerMap.put("updateUserId", "更新用户");
+        headerMap.put("updateTime", "更新时间");
+        headerMap.put("dataState", "删除标记(1: 正常 -1: 已删除)");
+        ExportUtil.doExport(resultList, headerMap, "restaurantFoodInfo.xlsx");
+    }
+
+    /**
+     * 查看指定餐馆美食信息对象详情。
+     *
+     * @param id 指定对象主键Id。
+     * @return 应答结果对象,包含对象详情。
+     */
+    @SaCheckPermission("restaurantFoodInfo.view")
+    @GetMapping("/view")
+    public ResponseResult<RestaurantFoodInfoVo> view(@RequestParam String id) {
+        RestaurantFoodInfo restaurantFoodInfo = restaurantFoodInfoService.getByIdWithRelation(id, MyRelationParam.full());
+        if (restaurantFoodInfo == null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
+        }
+        RestaurantFoodInfoVo restaurantFoodInfoVo = MyModelUtil.copyTo(restaurantFoodInfo, RestaurantFoodInfoVo.class);
+        return ResponseResult.success(restaurantFoodInfoVo);
+    }
+
+    /**
+     * 附件文件下载。
+     * 这里将图片和其他类型的附件文件放到不同的父目录下,主要为了便于今后图片文件的迁移。
+     *
+     * @param id 附件所在记录的主键Id。
+     * @param fieldName 附件所属的字段名。
+     * @param filename  文件名。如果没有提供该参数,就从当前记录的指定字段中读取。
+     * @param asImage   下载文件是否为图片。
+     * @param response  Http 应答对象。
+     */
+//    @SaCheckPermission("restaurantFoodInfo.view")
+    @SaIgnore
+    @OperationLog(type = SysOperationLogType.DOWNLOAD, saveResponse = false)
+    @GetMapping("/download")
+    public void download(
+            @RequestParam(required = false) Long id,
+            @RequestParam String fieldName,
+            @RequestParam String filename,
+            @RequestParam Boolean asImage,
+            HttpServletResponse response) {
+        if (MyCommonUtil.existBlankArgument(fieldName, filename, asImage)) {
+            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+            return;
+        }
+        // 使用try来捕获异常,是为了保证一旦出现异常可以返回500的错误状态,便于调试。
+        // 否则有可能给前端返回的是200的错误码。
+        try {
+            // 如果请求参数中没有包含主键Id,就判断该文件是否为当前session上传的。
+            if (id == null) {
+                if (!cacheHelper.existSessionUploadFile(filename)) {
+                    ResponseResult.output(HttpServletResponse.SC_FORBIDDEN);
+                    return;
+                }
+            } else {
+                RestaurantFoodInfo restaurantFoodInfo = restaurantFoodInfoService.getById(id);
+                if (restaurantFoodInfo == null) {
+                    ResponseResult.output(HttpServletResponse.SC_NOT_FOUND);
+                    return;
+                }
+                String fieldJsonData = (String) ReflectUtil.getFieldValue(restaurantFoodInfo, fieldName);
+                if (fieldJsonData == null && !cacheHelper.existSessionUploadFile(filename)) {
+                    ResponseResult.output(HttpServletResponse.SC_BAD_REQUEST);
+                    return;
+                }
+                if (!BaseUpDownloader.containFile(fieldJsonData, filename)
+                        && !cacheHelper.existSessionUploadFile(filename)) {
+                    ResponseResult.output(HttpServletResponse.SC_FORBIDDEN);
+                    return;
+                }
+            }
+            UploadStoreInfo storeInfo = MyModelUtil.getUploadStoreInfo(RestaurantFoodInfo.class, fieldName);
+            if (!storeInfo.isSupportUpload()) {
+                ResponseResult.output(HttpServletResponse.SC_NOT_IMPLEMENTED,
+                        ResponseResult.error(ErrorCodeEnum.INVALID_UPLOAD_FIELD));
+                return;
+            }
+            BaseUpDownloader upDownloader = upDownloaderFactory.get(storeInfo.getStoreType());
+            upDownloader.doDownload(appConfig.getUploadFileBaseDir(),
+                    RestaurantFoodInfo.class.getSimpleName(), fieldName, filename, asImage, response);
+        } catch (Exception e) {
+            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+            log.error(e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 文件上传操作。
+     *
+     * @param fieldName  上传文件名。
+     * @param asImage    是否作为图片上传。如果是图片,今后下载的时候无需权限验证。否则就是附件上传,下载时需要权限验证。
+     * @param uploadFile 上传文件对象。
+     */
+    @SaCheckPermission("restaurantFoodInfo.view")
+    @OperationLog(type = SysOperationLogType.UPLOAD, saveResponse = false)
+    @PostMapping("/upload")
+    public void upload(
+            @RequestParam String fieldName,
+            @RequestParam Boolean asImage,
+            @RequestParam("uploadFile") MultipartFile uploadFile) throws IOException {
+        UploadStoreInfo storeInfo = MyModelUtil.getUploadStoreInfo(RestaurantFoodInfo.class, fieldName);
+        // 这里就会判断参数中指定的字段,是否支持上传操作。
+        if (!storeInfo.isSupportUpload()) {
+            ResponseResult.output(HttpServletResponse.SC_FORBIDDEN,
+                    ResponseResult.error(ErrorCodeEnum.INVALID_UPLOAD_FIELD));
+            return;
+        }
+        // 根据字段注解中的存储类型,通过工厂方法获取匹配的上传下载实现类,从而解耦。
+        BaseUpDownloader upDownloader = upDownloaderFactory.get(storeInfo.getStoreType());
+        UploadResponseInfo responseInfo = upDownloader.doUpload(null,
+                appConfig.getUploadFileBaseDir(), RestaurantFoodInfo.class.getSimpleName(), fieldName, asImage, uploadFile);
+        if (Boolean.TRUE.equals(responseInfo.getUploadFailed())) {
+            ResponseResult.output(HttpServletResponse.SC_FORBIDDEN,
+                    ResponseResult.error(ErrorCodeEnum.UPLOAD_FAILED, responseInfo.getErrorMessage()));
+            return;
+        }
+        cacheHelper.putSessionUploadFile(responseInfo.getFilename());
+        ResponseResult.output(ResponseResult.success(responseInfo));
+    }
+
+    private ResponseResult<Void> doDelete(Long id) {
+        String errorMessage;
+        // 验证关联Id的数据合法性
+        RestaurantFoodInfo originalRestaurantFoodInfo = restaurantFoodInfoService.getById(id);
+        if (originalRestaurantFoodInfo == null) {
+            // NOTE: 修改下面方括号中的话述
+            errorMessage = "数据验证失败,当前 [对象] 并不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        if (!restaurantFoodInfoService.remove(id)) {
+            errorMessage = "数据操作失败,删除的对象不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        return ResponseResult.success();
+    }
+}

+ 332 - 0
application-webadmin/src/main/java/com/tourism/webadmin/back/controller/RestaurantFoodTypeController.java

@@ -0,0 +1,332 @@
+package com.tourism.webadmin.back.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.hutool.core.util.BooleanUtil;
+import com.tourism.common.log.annotation.OperationLog;
+import com.tourism.common.log.model.constant.SysOperationLogType;
+import com.github.pagehelper.page.PageMethod;
+import com.tourism.webadmin.back.vo.*;
+import com.tourism.webadmin.back.dto.*;
+import com.tourism.webadmin.back.model.*;
+import com.tourism.webadmin.back.service.*;
+import com.tourism.common.core.object.*;
+import com.tourism.common.core.util.*;
+import com.tourism.common.core.constant.*;
+import com.tourism.common.core.annotation.MyRequestBody;
+import com.tourism.common.additional.config.ApplicationConfig;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import com.tourism.webadmin.upms.service.SysDeptService;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.springdoc.core.annotations.ParameterObject;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 餐馆美食类型操作控制器类。
+ *
+ * @author 吃饭睡觉
+ * @date 2024-09-06
+ */
+@Tag(name = "餐馆美食类型管理接口")
+@Slf4j
+@RestController
+@RequestMapping("/admin/app/restaurantFoodType")
+public class RestaurantFoodTypeController {
+
+    @Autowired
+    private ApplicationConfig appConfig;
+    @Autowired
+    private RestaurantFoodTypeService restaurantFoodTypeService;
+    @Autowired
+    private SysDeptService sysDeptService;
+    @Autowired
+    private RestaurantInfoService restaurantService;
+
+
+    /**
+     * 新增餐馆美食类型数据。
+     *
+     * @param restaurantFoodTypeDto 新增对象。
+     * @return 应答结果对象,包含新增对象主键Id。
+     */
+    @ApiOperationSupport(ignoreParameters = {"restaurantFoodTypeDto.id", "restaurantFoodTypeDto.searchString"})
+    @SaCheckPermission("restaurantFoodType.add")
+    @OperationLog(type = SysOperationLogType.ADD)
+    @PostMapping("/add")
+    public ResponseResult<String> add(@MyRequestBody RestaurantFoodTypeDto restaurantFoodTypeDto) {
+        String errorMessage = MyCommonUtil.getModelValidationError(restaurantFoodTypeDto, false);
+        if (errorMessage != null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
+        }
+        RestaurantFoodType restaurantFoodType = MyModelUtil.copyTo(restaurantFoodTypeDto, RestaurantFoodType.class);
+        // 验证关联Id的数据合法性
+        CallResult callResult = restaurantFoodTypeService.verifyRelatedData(restaurantFoodType, null);
+        if (!callResult.isSuccess()) {
+            return ResponseResult.errorFrom(callResult);
+        }
+        restaurantFoodType = restaurantFoodTypeService.saveNew(restaurantFoodType);
+        return ResponseResult.success(restaurantFoodType.getId());
+    }
+
+    /**
+     * 更新餐馆美食类型数据。
+     *
+     * @param restaurantFoodTypeDto 更新对象。
+     * @return 应答结果对象。
+     */
+    @ApiOperationSupport(ignoreParameters = {"restaurantFoodTypeDto.searchString"})
+    @SaCheckPermission("restaurantFoodType.update")
+    @OperationLog(type = SysOperationLogType.UPDATE)
+    @PostMapping("/update")
+    public ResponseResult<Void> update(@MyRequestBody RestaurantFoodTypeDto restaurantFoodTypeDto) {
+        String errorMessage = MyCommonUtil.getModelValidationError(restaurantFoodTypeDto, true);
+        if (errorMessage != null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
+        }
+        RestaurantFoodType restaurantFoodType = MyModelUtil.copyTo(restaurantFoodTypeDto, RestaurantFoodType.class);
+        RestaurantFoodType originalRestaurantFoodType = restaurantFoodTypeService.getById(restaurantFoodType.getId());
+        if (originalRestaurantFoodType == null) {
+            // NOTE: 修改下面方括号中的话述
+            errorMessage = "数据验证失败,当前 [数据] 并不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        // 验证关联Id的数据合法性
+        CallResult callResult = restaurantFoodTypeService.verifyRelatedData(restaurantFoodType, originalRestaurantFoodType);
+        if (!callResult.isSuccess()) {
+            return ResponseResult.errorFrom(callResult);
+        }
+        if (!restaurantFoodTypeService.update(restaurantFoodType, originalRestaurantFoodType)) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
+        }
+        return ResponseResult.success();
+    }
+
+    /**
+     * 删除餐馆美食类型数据。
+     *
+     * @param id 删除对象主键Id。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("restaurantFoodType.delete")
+    @OperationLog(type = SysOperationLogType.DELETE)
+    @PostMapping("/delete")
+    public ResponseResult<Void> delete(@MyRequestBody Long id) {
+        if (MyCommonUtil.existBlankArgument(id)) {
+            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
+        }
+        return this.doDelete(id);
+    }
+
+    /**
+     * 批量删除餐馆美食类型数据。
+     *
+     * @param idList 待删除对象的主键Id列表。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("restaurantFoodType.delete")
+    @OperationLog(type = SysOperationLogType.DELETE_BATCH)
+    @PostMapping("/deleteBatch")
+    public ResponseResult<Void> deleteBatch(@MyRequestBody List<Long> idList) {
+        if (MyCommonUtil.existBlankArgument(idList)) {
+            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
+        }
+        for (Long id : idList) {
+            ResponseResult<Void> responseResult = this.doDelete(id);
+            if (!responseResult.isSuccess()) {
+                return responseResult;
+            }
+        }
+        return ResponseResult.success();
+    }
+
+    /**
+     * 列出符合过滤条件的餐馆美食类型列表。
+     *
+     * @param restaurantFoodTypeDtoFilter 过滤对象。
+     * @param orderParam 排序参数。
+     * @param pageParam 分页参数。
+     * @return 应答结果对象,包含查询结果集。
+     */
+    @SaCheckPermission("restaurantFoodType.view")
+    @PostMapping("/list")
+    public ResponseResult<MyPageData<RestaurantFoodTypeVo>> list(
+            @MyRequestBody RestaurantFoodTypeDto restaurantFoodTypeDtoFilter,
+            @MyRequestBody MyOrderParam orderParam,
+            @MyRequestBody MyPageParam pageParam) {
+        TokenData tokenData = TokenData.takeFromRequest();
+        //如果不是管理员角色的话,
+        if(BooleanUtil.isFalse(tokenData.getIsAdmin())){
+            List<Long> parentIds = new ArrayList<>();
+            parentIds.add(tokenData.getDeptId());
+            List<Long> allChildDeptIdByParentIds = sysDeptService.getAllChildDeptIdByParentIds(parentIds);
+            // 使用HashSet构造函数将List转换为Set
+            Set<Long> set = new HashSet<>(allChildDeptIdByParentIds);
+            List<RestaurantInfo> restaurantInfoList = restaurantService.getInList("deptId", set);
+            List<String> restaurantIdList = restaurantInfoList.stream().map(RestaurantInfo::getId).collect(Collectors.toList());
+            restaurantFoodTypeDtoFilter.setRestaurantIdList(restaurantIdList);
+        }
+        if (pageParam != null) {
+            PageMethod.startPage(pageParam.getPageNum(), pageParam.getPageSize(), pageParam.getCount());
+        }
+        RestaurantFoodType restaurantFoodTypeFilter = MyModelUtil.copyTo(restaurantFoodTypeDtoFilter, RestaurantFoodType.class);
+        String orderBy = MyOrderParam.buildOrderBy(orderParam, RestaurantFoodType.class);
+        List<RestaurantFoodType> restaurantFoodTypeList =
+                restaurantFoodTypeService.getRestaurantFoodTypeListWithRelation(restaurantFoodTypeFilter, orderBy);
+        return ResponseResult.success(MyPageUtil.makeResponseData(restaurantFoodTypeList, RestaurantFoodTypeVo.class));
+    }
+
+    /**
+     * 导入主表数据列表。
+     *
+     * @param importFile 上传的文件,目前仅仅支持xlsx和xls两种格式。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("restaurantFoodType.import")
+    @OperationLog(type = SysOperationLogType.IMPORT)
+    @PostMapping("/import")
+    public ResponseResult<Void> importBatch(
+            @RequestParam Boolean skipHeader,
+            @RequestParam("importFile") MultipartFile importFile) throws IOException {
+        String filename = ImportUtil.saveImportFile(appConfig.getUploadFileBaseDir(), null, importFile);
+        // 这里可以指定需要忽略导入的字段集合。如创建时间、创建人、更新时间、更新人、主键Id和逻辑删除,
+        // 以及一些存在缺省值且无需导入的字段。其中主键字段和逻辑删除字段不需要在这里设置,批量插入逻辑会自动处理的。
+        Set<String> ignoreFieldSet = new HashSet<>();
+        ignoreFieldSet.add("createUserId");
+        ignoreFieldSet.add("createTime");
+        ignoreFieldSet.add("updateUserId");
+        ignoreFieldSet.add("updateTime");
+        List<ImportUtil.ImportHeaderInfo> headerInfoList = ImportUtil.makeHeaderInfoList(RestaurantFoodType.class, ignoreFieldSet);
+        // 下面是导入时需要注意的地方,如果我们缺省生成的代码,与实际情况存在差异,请手动修改。
+        // 1. 头信息数据字段,我们只是根据当前的主表实体对象生成了缺省数组,开发者可根据实际情况,对headerInfoList进行修改。
+        ImportUtil.ImportHeaderInfo[] headerInfos = headerInfoList.toArray(new ImportUtil.ImportHeaderInfo[]{});
+        // 2. 这里需要根据实际情况决定,导入文件中第一行是否为中文头信息,如果是可以跳过。这里我们默认为true。
+        // 这里根据自己的实际需求,为doImport的最后一个参数,传递需要进行字典转换的字段集合。
+        // 注意,集合中包含需要翻译的Java字段名,如: gradeId。
+        Set<String> translatedDictFieldSet = new HashSet<>();
+        List<RestaurantFoodType> dataList =
+                ImportUtil.doImport(headerInfos, skipHeader, filename, RestaurantFoodType.class, translatedDictFieldSet);
+        CallResult result = restaurantFoodTypeService.verifyImportList(dataList, translatedDictFieldSet);
+        if (!result.isSuccess()) {
+            // result中返回了具体的验证失败对象,如果需要返回更加详细的错误,可根据实际情况手动修改。
+            return ResponseResult.errorFrom(result);
+        }
+        restaurantFoodTypeService.saveNewBatch(dataList, -1);
+        return ResponseResult.success();
+    }
+
+    /**
+     * 导出符合过滤条件的餐馆美食类型列表。
+     *
+     * @param restaurantFoodTypeDtoFilter 过滤对象。
+     * @param orderParam 排序参数。
+     * @throws IOException 文件读写失败。
+     */
+    @SaCheckPermission("restaurantFoodType.export")
+    @OperationLog(type = SysOperationLogType.EXPORT, saveResponse = false)
+    @PostMapping("/export")
+    public void export(
+            @MyRequestBody RestaurantFoodTypeDto restaurantFoodTypeDtoFilter,
+            @MyRequestBody MyOrderParam orderParam) throws IOException {
+        RestaurantFoodType restaurantFoodTypeFilter = MyModelUtil.copyTo(restaurantFoodTypeDtoFilter, RestaurantFoodType.class);
+        String orderBy = MyOrderParam.buildOrderBy(orderParam, RestaurantFoodType.class);
+        List<RestaurantFoodType> resultList =
+                restaurantFoodTypeService.getRestaurantFoodTypeListWithRelation(restaurantFoodTypeFilter, orderBy);
+        // 导出文件的标题数组
+        // NOTE: 下面的代码中仅仅导出了主表数据,主表聚合计算数据和主表关联字典的数据。
+        // 一对一从表数据的导出,可根据需要自行添加。如:headerMap.put("slaveFieldName.xxxField", "标题名称")
+        Map<String, String> headerMap = new LinkedHashMap<>(11);
+        headerMap.put("id", "主键id");
+        headerMap.put("name", "种类名称");
+        headerMap.put("restaurantIdDictMap.name", "餐馆id");
+        headerMap.put("enable", "是否启用,0禁用,1启用");
+        headerMap.put("showOrder", "显示顺序");
+        headerMap.put("description", "描述");
+        headerMap.put("createUserId", "创建用户");
+        headerMap.put("createTime", "创建时间");
+        headerMap.put("updateUserId", "更新用户");
+        headerMap.put("updateTime", "更新时间");
+        headerMap.put("dataState", "删除标记(1: 正常 -1: 已删除)");
+        ExportUtil.doExport(resultList, headerMap, "restaurantFoodType.xlsx");
+    }
+
+    /**
+     * 查看指定餐馆美食类型对象详情。
+     *
+     * @param id 指定对象主键Id。
+     * @return 应答结果对象,包含对象详情。
+     */
+    @SaCheckPermission("restaurantFoodType.view")
+    @GetMapping("/view")
+    public ResponseResult<RestaurantFoodTypeVo> view(@RequestParam String id) {
+        RestaurantFoodType restaurantFoodType = restaurantFoodTypeService.getByIdWithRelation(id, MyRelationParam.full());
+        if (restaurantFoodType == null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
+        }
+        RestaurantFoodTypeVo restaurantFoodTypeVo = MyModelUtil.copyTo(restaurantFoodType, RestaurantFoodTypeVo.class);
+        return ResponseResult.success(restaurantFoodTypeVo);
+    }
+
+    /**
+     * 以字典形式返回全部餐馆美食类型数据集合。字典的键值为[id, name]。
+     * 白名单接口,登录用户均可访问。
+     *
+     * @param filter 过滤对象。
+     * @return 应答结果对象,包含的数据为 List<Map<String, String>>,map中包含两条记录,key的值分别是id和name,value对应具体数据。
+     */
+    @GetMapping("/listDict")
+    public ResponseResult<List<Map<String, Object>>> listDict(@ParameterObject RestaurantFoodTypeDto filter) {
+
+        TokenData tokenData = TokenData.takeFromRequest();
+        //如果不是管理员角色的话,
+        if(BooleanUtil.isFalse(tokenData.getIsAdmin())){
+            List<Long> parentIds = new ArrayList<>();
+            parentIds.add(tokenData.getDeptId());
+            List<Long> allChildDeptIdByParentIds = sysDeptService.getAllChildDeptIdByParentIds(parentIds);
+            // 使用HashSet构造函数将List转换为Set
+            Set<Long> set = new HashSet<>(allChildDeptIdByParentIds);
+            List<RestaurantInfo> restaurantInfoList = restaurantService.getInList("deptId", set);
+            List<String> restaurantIdList = restaurantInfoList.stream().map(RestaurantInfo::getId).collect(Collectors.toList());
+            filter.setRestaurantIdList(restaurantIdList);
+        }
+
+        List<RestaurantFoodType> resultList =
+                restaurantFoodTypeService.getRestaurantFoodTypeList(MyModelUtil.copyTo(filter, RestaurantFoodType.class),"");
+        return ResponseResult.success(
+                MyCommonUtil.toDictDataList(resultList, RestaurantFoodType::getId, RestaurantFoodType::getName));
+    }
+
+    /**
+     * 根据字典Id集合,获取查询后的字典数据。
+     *
+     * @param dictIds 字典Id集合。
+     * @return 应答结果对象,包含字典形式的数据集合。
+     */
+    @GetMapping("/listDictByIds")
+    public ResponseResult<List<Map<String, Object>>> listDictByIds(@RequestParam List<String> dictIds) {
+        List<RestaurantFoodType> resultList = restaurantFoodTypeService.getInList(new HashSet<>(dictIds));
+        return ResponseResult.success(
+                MyCommonUtil.toDictDataList(resultList, RestaurantFoodType::getId, RestaurantFoodType::getName));
+    }
+
+    private ResponseResult<Void> doDelete(Long id) {
+        String errorMessage;
+        // 验证关联Id的数据合法性
+        RestaurantFoodType originalRestaurantFoodType = restaurantFoodTypeService.getById(id);
+        if (originalRestaurantFoodType == null) {
+            // NOTE: 修改下面方括号中的话述
+            errorMessage = "数据验证失败,当前 [对象] 并不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        if (!restaurantFoodTypeService.remove(id)) {
+            errorMessage = "数据操作失败,删除的对象不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        return ResponseResult.success();
+    }
+}

+ 556 - 0
application-webadmin/src/main/java/com/tourism/webadmin/back/controller/RestaurantInfoController.java

@@ -0,0 +1,556 @@
+package com.tourism.webadmin.back.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.dev33.satoken.annotation.SaIgnore;
+import cn.hutool.core.util.ReflectUtil;
+import com.tourism.common.additional.utils.StringUtils;
+import com.tourism.common.core.upload.BaseUpDownloader;
+import com.tourism.common.core.upload.UpDownloaderFactory;
+import com.tourism.common.core.upload.UploadResponseInfo;
+import com.tourism.common.core.upload.UploadStoreInfo;
+import com.tourism.common.log.annotation.OperationLog;
+import com.tourism.common.log.model.constant.SysOperationLogType;
+import com.github.pagehelper.page.PageMethod;
+import com.tourism.webadmin.back.vo.*;
+import com.tourism.webadmin.back.dto.*;
+import com.tourism.webadmin.back.model.*;
+import com.tourism.webadmin.back.service.*;
+import com.tourism.common.core.object.*;
+import com.tourism.common.core.util.*;
+import com.tourism.common.core.constant.*;
+import com.tourism.common.core.annotation.MyRequestBody;
+import com.tourism.common.redis.cache.SessionCacheHelper;
+import com.tourism.common.additional.config.ApplicationConfig;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import com.tourism.webadmin.upms.service.SysDeptService;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.springdoc.core.annotations.ParameterObject;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.util.CollectionUtils;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import jakarta.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 餐馆信息操作控制器类。
+ *
+ * @author 吃饭睡觉
+ * @date 2024-09-06
+ */
+@Tag(name = "餐馆信息管理接口")
+@Slf4j
+@RestController
+@RequestMapping("/admin/app/restaurantInfo")
+public class RestaurantInfoController {
+
+    @Autowired
+    private ApplicationConfig appConfig;
+    @Autowired
+    private SessionCacheHelper cacheHelper;
+    @Autowired
+    private UpDownloaderFactory upDownloaderFactory;
+    @Autowired
+    private RestaurantInfoService restaurantInfoService;
+    @Autowired
+    private RestaurantTypeService restaurantTypeService;
+    @Autowired
+    private SysDeptService sysDeptService;
+    @Autowired
+    private RestaurantInfoService restaurantService;
+
+    /**
+     * 新增餐馆信息数据。
+     *
+     * @param restaurantInfoDto 新增对象。
+     * @return 应答结果对象,包含新增对象主键Id。
+     */
+    @ApiOperationSupport(ignoreParameters = {"restaurantInfoDto.id", "restaurantInfoDto.searchString"})
+    @SaCheckPermission("restaurantInfo.add")
+    @OperationLog(type = SysOperationLogType.ADD)
+    @PostMapping("/add")
+    public ResponseResult<String> add(@MyRequestBody RestaurantInfoDto restaurantInfoDto) {
+        String errorMessage = MyCommonUtil.getModelValidationError(restaurantInfoDto, false);
+        if (errorMessage != null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
+        }
+        RestaurantInfo restaurantInfo = MyModelUtil.copyTo(restaurantInfoDto, RestaurantInfo.class);
+        // 验证关联Id的数据合法性
+        CallResult callResult = restaurantInfoService.verifyRelatedData(restaurantInfo, null);
+        if (!callResult.isSuccess()) {
+            return ResponseResult.errorFrom(callResult);
+        }
+        restaurantInfo = restaurantInfoService.saveNew(restaurantInfo);
+        return ResponseResult.success(restaurantInfo.getId());
+    }
+
+    /**
+     * 更新餐馆信息数据。
+     *
+     * @param restaurantInfoDto 更新对象。
+     * @return 应答结果对象。
+     */
+    @ApiOperationSupport(ignoreParameters = {"restaurantInfoDto.searchString"})
+    @SaCheckPermission("restaurantInfo.update")
+    @OperationLog(type = SysOperationLogType.UPDATE)
+    @PostMapping("/update")
+    public ResponseResult<Void> update(@MyRequestBody RestaurantInfoDto restaurantInfoDto) {
+        String errorMessage = MyCommonUtil.getModelValidationError(restaurantInfoDto, true);
+        if (errorMessage != null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
+        }
+        RestaurantInfo restaurantInfo = MyModelUtil.copyTo(restaurantInfoDto, RestaurantInfo.class);
+        RestaurantInfo originalRestaurantInfo = restaurantInfoService.getById(restaurantInfo.getId());
+        if (originalRestaurantInfo == null) {
+            // NOTE: 修改下面方括号中的话述
+            errorMessage = "数据验证失败,当前 [数据] 并不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        // 验证关联Id的数据合法性
+        CallResult callResult = restaurantInfoService.verifyRelatedData(restaurantInfo, originalRestaurantInfo);
+        if (!callResult.isSuccess()) {
+            return ResponseResult.errorFrom(callResult);
+        }
+        if (!restaurantInfoService.update(restaurantInfo, originalRestaurantInfo)) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
+        }
+        return ResponseResult.success();
+    }
+
+    /**
+     * 删除餐馆信息数据。
+     *
+     * @param id 删除对象主键Id。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("restaurantInfo.delete")
+    @OperationLog(type = SysOperationLogType.DELETE)
+    @PostMapping("/delete")
+    public ResponseResult<Void> delete(@MyRequestBody Long id) {
+        if (MyCommonUtil.existBlankArgument(id)) {
+            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
+        }
+        return this.doDelete(id);
+    }
+
+    /**
+     * 批量删除餐馆信息数据。
+     *
+     * @param idList 待删除对象的主键Id列表。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("restaurantInfo.delete")
+    @OperationLog(type = SysOperationLogType.DELETE_BATCH)
+    @PostMapping("/deleteBatch")
+    public ResponseResult<Void> deleteBatch(@MyRequestBody List<Long> idList) {
+        if (MyCommonUtil.existBlankArgument(idList)) {
+            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
+        }
+        for (Long id : idList) {
+            ResponseResult<Void> responseResult = this.doDelete(id);
+            if (!responseResult.isSuccess()) {
+                return responseResult;
+            }
+        }
+        return ResponseResult.success();
+    }
+
+    /**
+     * 列出符合过滤条件的餐馆信息列表。
+     *
+     * @param restaurantInfoDtoFilter 过滤对象。
+     * @param orderParam 排序参数。
+     * @param pageParam 分页参数。
+     * @return 应答结果对象,包含查询结果集。
+     */
+    @SaCheckPermission("restaurantInfo.view")
+    @PostMapping("/list")
+    public ResponseResult<MyPageData<RestaurantInfoVo>> list(
+            @MyRequestBody RestaurantInfoDto restaurantInfoDtoFilter,
+            @MyRequestBody MyOrderParam orderParam,
+            @MyRequestBody MyPageParam pageParam) {
+        if (pageParam != null) {
+            PageMethod.startPage(pageParam.getPageNum(), pageParam.getPageSize(), pageParam.getCount());
+        }
+        RestaurantInfo restaurantInfoFilter = MyModelUtil.copyTo(restaurantInfoDtoFilter, RestaurantInfo.class);
+        String orderBy = MyOrderParam.buildOrderBy(orderParam, RestaurantInfo.class);
+        List<RestaurantInfo> restaurantInfoList =
+                restaurantInfoService.getRestaurantInfoListWithRelation(restaurantInfoFilter, orderBy);
+        MyPageData<RestaurantInfoVo> restaurantInfoVoMyPageData = MyPageUtil.makeResponseData(restaurantInfoList, RestaurantInfoVo.class);
+        List<RestaurantInfoVo> dataList = restaurantInfoVoMyPageData.getDataList();
+        Set<Long> restaurantTypeIdSet = new HashSet<>();
+        for(RestaurantInfoVo i: dataList){
+            if(StringUtils.isNotEmpty(i.getTypeId())){
+                String[] split = i.getTypeId().split(",");
+                for(String j: split){
+                    restaurantTypeIdSet.add(Long.parseLong(j));
+                }
+            }
+        }
+        List<RestaurantType> inList = restaurantTypeService.getInList("id", restaurantTypeIdSet);
+        if(!CollectionUtils.isEmpty(inList)) {
+            Map<Long, String> collect = inList.stream().collect(Collectors.toMap(RestaurantType::getId, RestaurantType::getName));
+            dataList.stream().forEach(i -> {
+                if (StringUtils.isNotEmpty(i.getTypeId())) {
+                    String[] split = i.getTypeId().split(",");
+                    for (int j = 0; j < split.length; j++) {
+                        if(j==0 && split.length == 1){
+                            i.setTypeName(collect.get(Long.parseLong(split[j])));
+                        } else if (j==0 && split.length>1) {
+                            i.setTypeName(collect.get(Long.parseLong(split[j]))+",");
+                        } else if(j==split.length-1){
+                            i.setTypeName(i.getTypeName()+collect.get(Long.parseLong(split[j])));
+                        } else {
+                            i.setTypeName(i.getTypeName()+collect.get(Long.parseLong(split[j]))+",");
+                        }
+                    }
+
+                }
+            });
+        }
+        return ResponseResult.success(restaurantInfoVoMyPageData);
+    }
+
+    /**
+     * 列出符合过滤条件的餐馆信息列表。
+     *
+     * @param restaurantInfoDtoFilter 过滤对象。
+     * @param orderParam 排序参数。
+     * @param pageParam 分页参数。
+     * @return 应答结果对象,包含查询结果集。
+     */
+    @SaCheckPermission("restaurantInfo.view")
+    @PostMapping("/listA")
+    public ResponseResult<MyPageData<RestaurantInfoVo>> listA(
+            @MyRequestBody RestaurantInfoDto restaurantInfoDtoFilter,
+            @MyRequestBody MyOrderParam orderParam,
+            @MyRequestBody MyPageParam pageParam) {
+
+        if (pageParam != null) {
+            PageMethod.startPage(pageParam.getPageNum(), pageParam.getPageSize(), pageParam.getCount());
+        }
+
+        RestaurantInfo restaurantInfoFilter = MyModelUtil.copyTo(restaurantInfoDtoFilter, RestaurantInfo.class);
+
+        String orderBy = MyOrderParam.buildOrderBy(orderParam, RestaurantInfo.class);
+
+        List<RestaurantInfo> restaurantInfoList =
+                restaurantInfoService.getRestaurantInfoListWithRelation(restaurantInfoFilter, orderBy);
+
+        MyPageData<RestaurantInfoVo> restaurantInfoVoMyPageData = MyPageUtil.makeResponseData(restaurantInfoList, RestaurantInfoVo.class);
+        List<RestaurantInfoVo> dataList = restaurantInfoVoMyPageData.getDataList();
+
+        Set<Long> restaurantTypeIdSet = new HashSet<>();
+        for (RestaurantInfoVo i : dataList) {
+            if (StringUtils.isNotEmpty(i.getTypeId())) {
+                String[] split = i.getTypeId().split(",");
+                for (String j : split) {
+                    restaurantTypeIdSet.add(Long.parseLong(j));
+                }
+            }
+        }
+
+        List<RestaurantType> inList = restaurantTypeService.getInList("id", restaurantTypeIdSet);
+        if (!CollectionUtils.isEmpty(inList)) {
+            Map<Long, String> collect = inList.stream()
+                    .collect(Collectors.toMap(RestaurantType::getId, RestaurantType::getName));
+
+            dataList.stream().forEach(i -> {
+                if (StringUtils.isNotEmpty(i.getTypeId())) {
+                    String[] split = i.getTypeId().split(",");
+
+                    i.setTypeIdList(Arrays.asList(split));
+
+                    Map<String, Object> typeIdDict = new HashMap<>();
+                    for (String id : split) {
+                        Long typeId = Long.parseLong(id);
+                        String typeName = collect.get(typeId);
+                        if (typeName != null) {
+                            typeIdDict.put(id, typeName);
+                        }
+                    }
+                    i.setTypeIdDictMap(typeIdDict);
+
+                    List<Map<String, Object>> typeIdDictList = new ArrayList<>();
+                    for (String id : split) {
+                        Map<String, Object> map = new HashMap<>();
+                        Long typeId = Long.parseLong(id);
+                        map.put("id", typeId);
+                        map.put("name", collect.get(typeId));
+                        typeIdDictList.add(map);
+                    }
+                    i.setTypeIdDictMapList(typeIdDictList);
+
+                    StringBuilder typeNameBuilder = new StringBuilder();
+                    for (int j = 0; j < split.length; j++) {
+                        String typeName = collect.get(Long.parseLong(split[j]));
+                        if (typeName != null) {
+                            if (j == 0) {
+                                typeNameBuilder.append(typeName);
+                            } else {
+                                typeNameBuilder.append(",").append(typeName);
+                            }
+                        }
+                    }
+                    i.setTypeName(typeNameBuilder.toString());
+                }
+            });
+        }
+
+        return ResponseResult.success(restaurantInfoVoMyPageData);
+    }
+
+
+    /**
+     * 导入主表数据列表。
+     *
+     * @param importFile 上传的文件,目前仅仅支持xlsx和xls两种格式。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("restaurantInfo.import")
+    @OperationLog(type = SysOperationLogType.IMPORT)
+    @PostMapping("/import")
+    public ResponseResult<Void> importBatch(
+            @RequestParam Boolean skipHeader,
+            @RequestParam("importFile") MultipartFile importFile) throws IOException {
+        String filename = ImportUtil.saveImportFile(appConfig.getUploadFileBaseDir(), null, importFile);
+        // 这里可以指定需要忽略导入的字段集合。如创建时间、创建人、更新时间、更新人、主键Id和逻辑删除,
+        // 以及一些存在缺省值且无需导入的字段。其中主键字段和逻辑删除字段不需要在这里设置,批量插入逻辑会自动处理的。
+        Set<String> ignoreFieldSet = new HashSet<>();
+        ignoreFieldSet.add("createUserId");
+        ignoreFieldSet.add("createTime");
+        ignoreFieldSet.add("updateUserId");
+        ignoreFieldSet.add("updateTime");
+        List<ImportUtil.ImportHeaderInfo> headerInfoList = ImportUtil.makeHeaderInfoList(RestaurantInfo.class, ignoreFieldSet);
+        // 下面是导入时需要注意的地方,如果我们缺省生成的代码,与实际情况存在差异,请手动修改。
+        // 1. 头信息数据字段,我们只是根据当前的主表实体对象生成了缺省数组,开发者可根据实际情况,对headerInfoList进行修改。
+        ImportUtil.ImportHeaderInfo[] headerInfos = headerInfoList.toArray(new ImportUtil.ImportHeaderInfo[]{});
+        // 2. 这里需要根据实际情况决定,导入文件中第一行是否为中文头信息,如果是可以跳过。这里我们默认为true。
+        // 这里根据自己的实际需求,为doImport的最后一个参数,传递需要进行字典转换的字段集合。
+        // 注意,集合中包含需要翻译的Java字段名,如: gradeId。
+        Set<String> translatedDictFieldSet = new HashSet<>();
+        List<RestaurantInfo> dataList =
+                ImportUtil.doImport(headerInfos, skipHeader, filename, RestaurantInfo.class, translatedDictFieldSet);
+        CallResult result = restaurantInfoService.verifyImportList(dataList, translatedDictFieldSet);
+        if (!result.isSuccess()) {
+            // result中返回了具体的验证失败对象,如果需要返回更加详细的错误,可根据实际情况手动修改。
+            return ResponseResult.errorFrom(result);
+        }
+        restaurantInfoService.saveNewBatch(dataList, -1);
+        return ResponseResult.success();
+    }
+
+    /**
+     * 导出符合过滤条件的餐馆信息列表。
+     *
+     * @param restaurantInfoDtoFilter 过滤对象。
+     * @param orderParam 排序参数。
+     * @throws IOException 文件读写失败。
+     */
+    @SaCheckPermission("restaurantInfo.export")
+    @OperationLog(type = SysOperationLogType.EXPORT, saveResponse = false)
+    @PostMapping("/export")
+    public void export(
+            @MyRequestBody RestaurantInfoDto restaurantInfoDtoFilter,
+            @MyRequestBody MyOrderParam orderParam) throws IOException {
+        RestaurantInfo restaurantInfoFilter = MyModelUtil.copyTo(restaurantInfoDtoFilter, RestaurantInfo.class);
+        String orderBy = MyOrderParam.buildOrderBy(orderParam, RestaurantInfo.class);
+        List<RestaurantInfo> resultList =
+                restaurantInfoService.getRestaurantInfoListWithRelation(restaurantInfoFilter, orderBy);
+        // 导出文件的标题数组
+        // NOTE: 下面的代码中仅仅导出了主表数据,主表聚合计算数据和主表关联字典的数据。
+        // 一对一从表数据的导出,可根据需要自行添加。如:headerMap.put("slaveFieldName.xxxField", "标题名称")
+        Map<String, String> headerMap = new LinkedHashMap<>(22);
+        headerMap.put("id", "主键id");
+        headerMap.put("name", "店铺名称");
+        headerMap.put("tag", "店铺标签");
+        headerMap.put("typeIdDictMap.name", "店铺种类");
+        headerMap.put("announcement", "店铺公告");
+        headerMap.put("workTime", "工作时间段(例:8:00-19:00)");
+        headerMap.put("adress", "店铺地址");
+        headerMap.put("enableDictMap.name", "是否启用,0禁用,1启用");
+        headerMap.put("url", "店铺图片");
+        headerMap.put("environmentImage", "店铺环境图片");
+        headerMap.put("contactInformation", "联系方式");
+        headerMap.put("longitude", "经度");
+        headerMap.put("latitude", "纬度");
+        headerMap.put("totalSales", "销量");
+        headerMap.put("contry", "所处国家");
+        headerMap.put("createUserId", "创建用户");
+        headerMap.put("city", "所处城市");
+        headerMap.put("createTime", "创建时间");
+        headerMap.put("updateUserId", "更新用户");
+        headerMap.put("updateTime", "更新时间");
+        headerMap.put("dataState", "删除标记(1: 正常 -1: 已删除)");
+        headerMap.put("deptId", "用户所在部门Id");
+        ExportUtil.doExport(resultList, headerMap, "restaurantInfo.xlsx");
+    }
+
+    /**
+     * 查看指定餐馆信息对象详情。
+     *
+     * @param id 指定对象主键Id。
+     * @return 应答结果对象,包含对象详情。
+     */
+    @SaCheckPermission("restaurantInfo.view")
+    @GetMapping("/view")
+    public ResponseResult<RestaurantInfoVo> view(@RequestParam String id) {
+        RestaurantInfo restaurantInfo = restaurantInfoService.getByIdWithRelation(id, MyRelationParam.full());
+        if (restaurantInfo == null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
+        }
+        RestaurantInfoVo restaurantInfoVo = MyModelUtil.copyTo(restaurantInfo, RestaurantInfoVo.class);
+        if(StringUtils.isNotEmpty(restaurantInfoVo.getTypeId())) {
+            restaurantInfoVo.setTypeIdList(Arrays.asList(restaurantInfoVo.getTypeId().split(",")));
+        }
+        return ResponseResult.success(restaurantInfoVo);
+    }
+
+    /**
+     * 附件文件下载。
+     * 这里将图片和其他类型的附件文件放到不同的父目录下,主要为了便于今后图片文件的迁移。
+     *
+     * @param id 附件所在记录的主键Id。
+     * @param fieldName 附件所属的字段名。
+     * @param filename  文件名。如果没有提供该参数,就从当前记录的指定字段中读取。
+     * @param asImage   下载文件是否为图片。
+     * @param response  Http 应答对象。
+     */
+//    @SaCheckPermission("restaurantInfo.view")
+    @SaIgnore
+    @OperationLog(type = SysOperationLogType.DOWNLOAD, saveResponse = false)
+    @GetMapping("/download")
+    public void download(
+            @RequestParam(required = false) Long id,
+            @RequestParam String fieldName,
+            @RequestParam String filename,
+            @RequestParam Boolean asImage,
+            HttpServletResponse response) {
+        if (MyCommonUtil.existBlankArgument(fieldName, filename, asImage)) {
+            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+            return;
+        }
+        // 使用try来捕获异常,是为了保证一旦出现异常可以返回500的错误状态,便于调试。
+        // 否则有可能给前端返回的是200的错误码。
+        try {
+            // 如果请求参数中没有包含主键Id,就判断该文件是否为当前session上传的。
+            if (id == null) {
+                if (!cacheHelper.existSessionUploadFile(filename)) {
+                    ResponseResult.output(HttpServletResponse.SC_FORBIDDEN);
+                    return;
+                }
+            } else {
+                RestaurantInfo restaurantInfo = restaurantInfoService.getById(id);
+                if (restaurantInfo == null) {
+                    ResponseResult.output(HttpServletResponse.SC_NOT_FOUND);
+                    return;
+                }
+                String fieldJsonData = (String) ReflectUtil.getFieldValue(restaurantInfo, fieldName);
+                if (fieldJsonData == null && !cacheHelper.existSessionUploadFile(filename)) {
+                    ResponseResult.output(HttpServletResponse.SC_BAD_REQUEST);
+                    return;
+                }
+                if (!BaseUpDownloader.containFile(fieldJsonData, filename)
+                        && !cacheHelper.existSessionUploadFile(filename)) {
+                    ResponseResult.output(HttpServletResponse.SC_FORBIDDEN);
+                    return;
+                }
+            }
+            UploadStoreInfo storeInfo = MyModelUtil.getUploadStoreInfo(RestaurantInfo.class, fieldName);
+            if (!storeInfo.isSupportUpload()) {
+                ResponseResult.output(HttpServletResponse.SC_NOT_IMPLEMENTED,
+                        ResponseResult.error(ErrorCodeEnum.INVALID_UPLOAD_FIELD));
+                return;
+            }
+            BaseUpDownloader upDownloader = upDownloaderFactory.get(storeInfo.getStoreType());
+            upDownloader.doDownload(appConfig.getUploadFileBaseDir(),
+                    RestaurantInfo.class.getSimpleName(), fieldName, filename, asImage, response);
+        } catch (Exception e) {
+            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+            log.error(e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 文件上传操作。
+     *
+     * @param fieldName  上传文件名。
+     * @param asImage    是否作为图片上传。如果是图片,今后下载的时候无需权限验证。否则就是附件上传,下载时需要权限验证。
+     * @param uploadFile 上传文件对象。
+     */
+    @SaCheckPermission("restaurantInfo.view")
+    @OperationLog(type = SysOperationLogType.UPLOAD, saveResponse = false)
+    @PostMapping("/upload")
+    public void upload(
+            @RequestParam String fieldName,
+            @RequestParam Boolean asImage,
+            @RequestParam("uploadFile") MultipartFile uploadFile) throws IOException {
+        UploadStoreInfo storeInfo = MyModelUtil.getUploadStoreInfo(RestaurantInfo.class, fieldName);
+        // 这里就会判断参数中指定的字段,是否支持上传操作。
+        if (!storeInfo.isSupportUpload()) {
+            ResponseResult.output(HttpServletResponse.SC_FORBIDDEN,
+                    ResponseResult.error(ErrorCodeEnum.INVALID_UPLOAD_FIELD));
+            return;
+        }
+        // 根据字段注解中的存储类型,通过工厂方法获取匹配的上传下载实现类,从而解耦。
+        BaseUpDownloader upDownloader = upDownloaderFactory.get(storeInfo.getStoreType());
+        UploadResponseInfo responseInfo = upDownloader.doUpload(null,
+                appConfig.getUploadFileBaseDir(), RestaurantInfo.class.getSimpleName(), fieldName, asImage, uploadFile);
+        if (Boolean.TRUE.equals(responseInfo.getUploadFailed())) {
+            ResponseResult.output(HttpServletResponse.SC_FORBIDDEN,
+                    ResponseResult.error(ErrorCodeEnum.UPLOAD_FAILED, responseInfo.getErrorMessage()));
+            return;
+        }
+        cacheHelper.putSessionUploadFile(responseInfo.getFilename());
+        ResponseResult.output(ResponseResult.success(responseInfo));
+    }
+
+    /**
+     * 以字典形式返回全部餐馆信息数据集合。字典的键值为[id, name]。
+     * 白名单接口,登录用户均可访问。
+     *
+     * @param filter 过滤对象。
+     * @return 应答结果对象,包含的数据为 List<Map<String, String>>,map中包含两条记录,key的值分别是id和name,value对应具体数据。
+     */
+    @GetMapping("/listDict")
+    public ResponseResult<List<Map<String, Object>>> listDict(@ParameterObject RestaurantInfoDto filter) {
+
+        List<RestaurantInfo> resultList =
+                restaurantInfoService.getRestaurantInfoList(MyModelUtil.copyTo(filter, RestaurantInfo.class),"");
+        return ResponseResult.success(
+                MyCommonUtil.toDictDataList(resultList, RestaurantInfo::getId, RestaurantInfo::getName));
+    }
+
+    /**
+     * 根据字典Id集合,获取查询后的字典数据。
+     *
+     * @param dictIds 字典Id集合。
+     * @return 应答结果对象,包含字典形式的数据集合。
+     */
+    @GetMapping("/listDictByIds")
+    public ResponseResult<List<Map<String, Object>>> listDictByIds(@RequestParam List<String> dictIds) {
+        List<RestaurantInfo> resultList = restaurantInfoService.getInList(new HashSet<>(dictIds));
+        return ResponseResult.success(
+                MyCommonUtil.toDictDataList(resultList, RestaurantInfo::getId, RestaurantInfo::getName));
+    }
+
+    private ResponseResult<Void> doDelete(Long id) {
+        String errorMessage;
+        // 验证关联Id的数据合法性
+        RestaurantInfo originalRestaurantInfo = restaurantInfoService.getById(id);
+        if (originalRestaurantInfo == null) {
+            // NOTE: 修改下面方括号中的话述
+            errorMessage = "数据验证失败,当前 [对象] 并不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        if (!restaurantInfoService.remove(id)) {
+            errorMessage = "数据操作失败,删除的对象不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        return ResponseResult.success();
+    }
+}

+ 398 - 0
application-webadmin/src/main/java/com/tourism/webadmin/back/controller/RestaurantTypeController.java

@@ -0,0 +1,398 @@
+package com.tourism.webadmin.back.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.dev33.satoken.annotation.SaIgnore;
+import cn.hutool.core.util.ReflectUtil;
+import com.tourism.common.core.upload.BaseUpDownloader;
+import com.tourism.common.core.upload.UpDownloaderFactory;
+import com.tourism.common.core.upload.UploadResponseInfo;
+import com.tourism.common.core.upload.UploadStoreInfo;
+import com.tourism.common.log.annotation.OperationLog;
+import com.tourism.common.log.model.constant.SysOperationLogType;
+import com.github.pagehelper.page.PageMethod;
+import com.tourism.webadmin.back.vo.*;
+import com.tourism.webadmin.back.dto.*;
+import com.tourism.webadmin.back.model.*;
+import com.tourism.webadmin.back.service.*;
+import com.tourism.common.core.object.*;
+import com.tourism.common.core.util.*;
+import com.tourism.common.core.constant.*;
+import com.tourism.common.core.annotation.MyRequestBody;
+import com.tourism.common.redis.cache.SessionCacheHelper;
+import com.tourism.common.additional.config.ApplicationConfig;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.springdoc.core.annotations.ParameterObject;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import jakarta.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * 餐馆类型操作控制器类。
+ *
+ * @author 吃饭睡觉
+ * @date 2024-09-06
+ */
+@Tag(name = "餐馆类型管理接口")
+@Slf4j
+@RestController
+@RequestMapping("/admin/app/restaurantType")
+public class RestaurantTypeController {
+
+    @Autowired
+    private ApplicationConfig appConfig;
+    @Autowired
+    private SessionCacheHelper cacheHelper;
+    @Autowired
+    private UpDownloaderFactory upDownloaderFactory;
+    @Autowired
+    private RestaurantTypeService restaurantTypeService;
+
+    /**
+     * 新增餐馆类型数据。
+     *
+     * @param restaurantTypeDto 新增对象。
+     * @return 应答结果对象,包含新增对象主键Id。
+     */
+    @ApiOperationSupport(ignoreParameters = {"restaurantTypeDto.id", "restaurantTypeDto.searchString"})
+    @SaCheckPermission("restaurantType.add")
+    @OperationLog(type = SysOperationLogType.ADD)
+    @PostMapping("/add")
+    public ResponseResult<Long> add(@MyRequestBody RestaurantTypeDto restaurantTypeDto) {
+        String errorMessage = MyCommonUtil.getModelValidationError(restaurantTypeDto, false);
+        if (errorMessage != null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
+        }
+        RestaurantType restaurantType = MyModelUtil.copyTo(restaurantTypeDto, RestaurantType.class);
+        restaurantType = restaurantTypeService.saveNew(restaurantType);
+        return ResponseResult.success(restaurantType.getId());
+    }
+
+    /**
+     * 更新餐馆类型数据。
+     *
+     * @param restaurantTypeDto 更新对象。
+     * @return 应答结果对象。
+     */
+    @ApiOperationSupport(ignoreParameters = {"restaurantTypeDto.searchString"})
+    @SaCheckPermission("restaurantType.update")
+    @OperationLog(type = SysOperationLogType.UPDATE)
+    @PostMapping("/update")
+    public ResponseResult<Void> update(@MyRequestBody RestaurantTypeDto restaurantTypeDto) {
+        String errorMessage = MyCommonUtil.getModelValidationError(restaurantTypeDto, true);
+        if (errorMessage != null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
+        }
+        RestaurantType restaurantType = MyModelUtil.copyTo(restaurantTypeDto, RestaurantType.class);
+        RestaurantType originalRestaurantType = restaurantTypeService.getById(restaurantType.getId());
+        if (originalRestaurantType == null) {
+            // NOTE: 修改下面方括号中的话述
+            errorMessage = "数据验证失败,当前 [数据] 并不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        if (!restaurantTypeService.update(restaurantType, originalRestaurantType)) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
+        }
+        return ResponseResult.success();
+    }
+
+    /**
+     * 删除餐馆类型数据。
+     *
+     * @param id 删除对象主键Id。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("restaurantType.delete")
+    @OperationLog(type = SysOperationLogType.DELETE)
+    @PostMapping("/delete")
+    public ResponseResult<Void> delete(@MyRequestBody Long id) {
+        if (MyCommonUtil.existBlankArgument(id)) {
+            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
+        }
+        return this.doDelete(id);
+    }
+
+    /**
+     * 批量删除餐馆类型数据。
+     *
+     * @param idList 待删除对象的主键Id列表。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("restaurantType.delete")
+    @OperationLog(type = SysOperationLogType.DELETE_BATCH)
+    @PostMapping("/deleteBatch")
+    public ResponseResult<Void> deleteBatch(@MyRequestBody List<Long> idList) {
+        if (MyCommonUtil.existBlankArgument(idList)) {
+            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
+        }
+        for (Long id : idList) {
+            ResponseResult<Void> responseResult = this.doDelete(id);
+            if (!responseResult.isSuccess()) {
+                return responseResult;
+            }
+        }
+        return ResponseResult.success();
+    }
+
+    /**
+     * 列出符合过滤条件的餐馆类型列表。
+     *
+     * @param restaurantTypeDtoFilter 过滤对象。
+     * @param orderParam 排序参数。
+     * @param pageParam 分页参数。
+     * @return 应答结果对象,包含查询结果集。
+     */
+    @SaCheckPermission("restaurantType.view")
+    @PostMapping("/list")
+    public ResponseResult<MyPageData<RestaurantTypeVo>> list(
+            @MyRequestBody RestaurantTypeDto restaurantTypeDtoFilter,
+            @MyRequestBody MyOrderParam orderParam,
+            @MyRequestBody MyPageParam pageParam) {
+        if (pageParam != null) {
+            PageMethod.startPage(pageParam.getPageNum(), pageParam.getPageSize(), pageParam.getCount());
+        }
+        RestaurantType restaurantTypeFilter = MyModelUtil.copyTo(restaurantTypeDtoFilter, RestaurantType.class);
+//        String orderBy = MyOrderParam.buildOrderBy(orderParam, RestaurantType.class);
+        List<RestaurantType> restaurantTypeList =
+                restaurantTypeService.getRestaurantTypeListWithRelation(restaurantTypeFilter, "tour_restaurant_type.show_order");
+        return ResponseResult.success(MyPageUtil.makeResponseData(restaurantTypeList, RestaurantTypeVo.class));
+    }
+
+    /**
+     * 导入主表数据列表。
+     *
+     * @param importFile 上传的文件,目前仅仅支持xlsx和xls两种格式。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("restaurantType.import")
+    @OperationLog(type = SysOperationLogType.IMPORT)
+    @PostMapping("/import")
+    public ResponseResult<Void> importBatch(
+            @RequestParam Boolean skipHeader,
+            @RequestParam("importFile") MultipartFile importFile) throws IOException {
+        String filename = ImportUtil.saveImportFile(appConfig.getUploadFileBaseDir(), null, importFile);
+        // 这里可以指定需要忽略导入的字段集合。如创建时间、创建人、更新时间、更新人、主键Id和逻辑删除,
+        // 以及一些存在缺省值且无需导入的字段。其中主键字段和逻辑删除字段不需要在这里设置,批量插入逻辑会自动处理的。
+        Set<String> ignoreFieldSet = new HashSet<>();
+        ignoreFieldSet.add("createUserId");
+        ignoreFieldSet.add("createTime");
+        ignoreFieldSet.add("updateUserId");
+        ignoreFieldSet.add("updateTime");
+        List<ImportUtil.ImportHeaderInfo> headerInfoList = ImportUtil.makeHeaderInfoList(RestaurantType.class, ignoreFieldSet);
+        // 下面是导入时需要注意的地方,如果我们缺省生成的代码,与实际情况存在差异,请手动修改。
+        // 1. 头信息数据字段,我们只是根据当前的主表实体对象生成了缺省数组,开发者可根据实际情况,对headerInfoList进行修改。
+        ImportUtil.ImportHeaderInfo[] headerInfos = headerInfoList.toArray(new ImportUtil.ImportHeaderInfo[]{});
+        // 2. 这里需要根据实际情况决定,导入文件中第一行是否为中文头信息,如果是可以跳过。这里我们默认为true。
+        // 这里根据自己的实际需求,为doImport的最后一个参数,传递需要进行字典转换的字段集合。
+        // 注意,集合中包含需要翻译的Java字段名,如: gradeId。
+        Set<String> translatedDictFieldSet = new HashSet<>();
+        List<RestaurantType> dataList =
+                ImportUtil.doImport(headerInfos, skipHeader, filename, RestaurantType.class, translatedDictFieldSet);
+        CallResult result = restaurantTypeService.verifyImportList(dataList, translatedDictFieldSet);
+        if (!result.isSuccess()) {
+            // result中返回了具体的验证失败对象,如果需要返回更加详细的错误,可根据实际情况手动修改。
+            return ResponseResult.errorFrom(result);
+        }
+        restaurantTypeService.saveNewBatch(dataList, -1);
+        return ResponseResult.success();
+    }
+
+    /**
+     * 导出符合过滤条件的餐馆类型列表。
+     *
+     * @param restaurantTypeDtoFilter 过滤对象。
+     * @param orderParam 排序参数。
+     * @throws IOException 文件读写失败。
+     */
+    @SaCheckPermission("restaurantType.export")
+    @OperationLog(type = SysOperationLogType.EXPORT, saveResponse = false)
+    @PostMapping("/export")
+    public void export(
+            @MyRequestBody RestaurantTypeDto restaurantTypeDtoFilter,
+            @MyRequestBody MyOrderParam orderParam) throws IOException {
+        RestaurantType restaurantTypeFilter = MyModelUtil.copyTo(restaurantTypeDtoFilter, RestaurantType.class);
+        String orderBy = MyOrderParam.buildOrderBy(orderParam, RestaurantType.class);
+        List<RestaurantType> resultList =
+                restaurantTypeService.getRestaurantTypeListWithRelation(restaurantTypeFilter, orderBy);
+        // 导出文件的标题数组
+        // NOTE: 下面的代码中仅仅导出了主表数据,主表聚合计算数据和主表关联字典的数据。
+        // 一对一从表数据的导出,可根据需要自行添加。如:headerMap.put("slaveFieldName.xxxField", "标题名称")
+        Map<String, String> headerMap = new LinkedHashMap<>(10);
+        headerMap.put("id", "主键id");
+        headerMap.put("name", "种类名称");
+        headerMap.put("url", "种类图标");
+        headerMap.put("enableDictMap.name", "是否启用,0禁用,1启用");
+        headerMap.put("showOrder", "显示顺序");
+        headerMap.put("createUserId", "创建用户");
+        headerMap.put("createTime", "创建时间");
+        headerMap.put("updateUserId", "更新用户");
+        headerMap.put("updateTime", "更新时间");
+        headerMap.put("dataState", "删除标记(1: 正常 -1: 已删除)");
+        ExportUtil.doExport(resultList, headerMap, "restaurantType.xlsx");
+    }
+
+    /**
+     * 查看指定餐馆类型对象详情。
+     *
+     * @param id 指定对象主键Id。
+     * @return 应答结果对象,包含对象详情。
+     */
+    @SaCheckPermission("restaurantType.view")
+    @GetMapping("/view")
+    public ResponseResult<RestaurantTypeVo> view(@RequestParam Long id) {
+        RestaurantType restaurantType = restaurantTypeService.getByIdWithRelation(id, MyRelationParam.full());
+        if (restaurantType == null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
+        }
+        RestaurantTypeVo restaurantTypeVo = MyModelUtil.copyTo(restaurantType, RestaurantTypeVo.class);
+        return ResponseResult.success(restaurantTypeVo);
+    }
+
+    /**
+     * 附件文件下载。
+     * 这里将图片和其他类型的附件文件放到不同的父目录下,主要为了便于今后图片文件的迁移。
+     *
+     * @param id 附件所在记录的主键Id。
+     * @param fieldName 附件所属的字段名。
+     * @param filename  文件名。如果没有提供该参数,就从当前记录的指定字段中读取。
+     * @param asImage   下载文件是否为图片。
+     * @param response  Http 应答对象。
+     */
+//    @SaCheckPermission("restaurantType.view")
+    @SaIgnore
+    @OperationLog(type = SysOperationLogType.DOWNLOAD, saveResponse = false)
+    @GetMapping("/download")
+    public void download(
+            @RequestParam(required = false) Long id,
+            @RequestParam String fieldName,
+            @RequestParam String filename,
+            @RequestParam Boolean asImage,
+            HttpServletResponse response) {
+        if (MyCommonUtil.existBlankArgument(fieldName, filename, asImage)) {
+            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+            return;
+        }
+        // 使用try来捕获异常,是为了保证一旦出现异常可以返回500的错误状态,便于调试。
+        // 否则有可能给前端返回的是200的错误码。
+        try {
+            // 如果请求参数中没有包含主键Id,就判断该文件是否为当前session上传的。
+            if (id == null) {
+                if (!cacheHelper.existSessionUploadFile(filename)) {
+                    ResponseResult.output(HttpServletResponse.SC_FORBIDDEN);
+                    return;
+                }
+            } else {
+                RestaurantType restaurantType = restaurantTypeService.getById(id);
+                if (restaurantType == null) {
+                    ResponseResult.output(HttpServletResponse.SC_NOT_FOUND);
+                    return;
+                }
+                String fieldJsonData = (String) ReflectUtil.getFieldValue(restaurantType, fieldName);
+                if (fieldJsonData == null && !cacheHelper.existSessionUploadFile(filename)) {
+                    ResponseResult.output(HttpServletResponse.SC_BAD_REQUEST);
+                    return;
+                }
+                if (!BaseUpDownloader.containFile(fieldJsonData, filename)
+                        && !cacheHelper.existSessionUploadFile(filename)) {
+                    ResponseResult.output(HttpServletResponse.SC_FORBIDDEN);
+                    return;
+                }
+            }
+            UploadStoreInfo storeInfo = MyModelUtil.getUploadStoreInfo(RestaurantType.class, fieldName);
+            if (!storeInfo.isSupportUpload()) {
+                ResponseResult.output(HttpServletResponse.SC_NOT_IMPLEMENTED,
+                        ResponseResult.error(ErrorCodeEnum.INVALID_UPLOAD_FIELD));
+                return;
+            }
+            BaseUpDownloader upDownloader = upDownloaderFactory.get(storeInfo.getStoreType());
+            upDownloader.doDownload(appConfig.getUploadFileBaseDir(),
+                    RestaurantType.class.getSimpleName(), fieldName, filename, asImage, response);
+        } catch (Exception e) {
+            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+            log.error(e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 文件上传操作。
+     *
+     * @param fieldName  上传文件名。
+     * @param asImage    是否作为图片上传。如果是图片,今后下载的时候无需权限验证。否则就是附件上传,下载时需要权限验证。
+     * @param uploadFile 上传文件对象。
+     */
+    @SaCheckPermission("restaurantType.view")
+    @OperationLog(type = SysOperationLogType.UPLOAD, saveResponse = false)
+    @PostMapping("/upload")
+    public void upload(
+            @RequestParam String fieldName,
+            @RequestParam Boolean asImage,
+            @RequestParam("uploadFile") MultipartFile uploadFile) throws IOException {
+        UploadStoreInfo storeInfo = MyModelUtil.getUploadStoreInfo(RestaurantType.class, fieldName);
+        // 这里就会判断参数中指定的字段,是否支持上传操作。
+        if (!storeInfo.isSupportUpload()) {
+            ResponseResult.output(HttpServletResponse.SC_FORBIDDEN,
+                    ResponseResult.error(ErrorCodeEnum.INVALID_UPLOAD_FIELD));
+            return;
+        }
+        // 根据字段注解中的存储类型,通过工厂方法获取匹配的上传下载实现类,从而解耦。
+        BaseUpDownloader upDownloader = upDownloaderFactory.get(storeInfo.getStoreType());
+        UploadResponseInfo responseInfo = upDownloader.doUpload(null,
+                appConfig.getUploadFileBaseDir(), RestaurantType.class.getSimpleName(), fieldName, asImage, uploadFile);
+        if (Boolean.TRUE.equals(responseInfo.getUploadFailed())) {
+            ResponseResult.output(HttpServletResponse.SC_FORBIDDEN,
+                    ResponseResult.error(ErrorCodeEnum.UPLOAD_FAILED, responseInfo.getErrorMessage()));
+            return;
+        }
+        cacheHelper.putSessionUploadFile(responseInfo.getFilename());
+        ResponseResult.output(ResponseResult.success(responseInfo));
+    }
+
+    /**
+     * 以字典形式返回全部餐馆类型数据集合。字典的键值为[id, name]。
+     * 白名单接口,登录用户均可访问。
+     *
+     * @param filter 过滤对象。
+     * @return 应答结果对象,包含的数据为 List<Map<String, String>>,map中包含两条记录,key的值分别是id和name,value对应具体数据。
+     */
+    @GetMapping("/listDict")
+    public ResponseResult<List<Map<String, Object>>> listDict(@ParameterObject RestaurantTypeDto filter) {
+        List<RestaurantType> resultList =
+                restaurantTypeService.getListByFilter(MyModelUtil.copyTo(filter, RestaurantType.class));
+        return ResponseResult.success(
+                MyCommonUtil.toDictDataList(resultList, RestaurantType::getId, RestaurantType::getName));
+    }
+
+    /**
+     * 根据字典Id集合,获取查询后的字典数据。
+     *
+     * @param dictIds 字典Id集合。
+     * @return 应答结果对象,包含字典形式的数据集合。
+     */
+    @GetMapping("/listDictByIds")
+    public ResponseResult<List<Map<String, Object>>> listDictByIds(@RequestParam List<Long> dictIds) {
+        List<RestaurantType> resultList = restaurantTypeService.getInList(new HashSet<>(dictIds));
+        return ResponseResult.success(
+                MyCommonUtil.toDictDataList(resultList, RestaurantType::getId, RestaurantType::getName));
+    }
+
+    private ResponseResult<Void> doDelete(Long id) {
+        String errorMessage;
+        // 验证关联Id的数据合法性
+        RestaurantType originalRestaurantType = restaurantTypeService.getById(id);
+        if (originalRestaurantType == null) {
+            // NOTE: 修改下面方括号中的话述
+            errorMessage = "数据验证失败,当前 [对象] 并不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        if (!restaurantTypeService.remove(id)) {
+            errorMessage = "数据操作失败,删除的对象不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        return ResponseResult.success();
+    }
+}

+ 269 - 0
application-webadmin/src/main/java/com/tourism/webadmin/back/controller/TourBookInfoController.java

@@ -0,0 +1,269 @@
+package com.tourism.webadmin.back.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import com.github.pagehelper.page.PageMethod;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import com.tourism.common.core.annotation.MyRequestBody;
+import com.tourism.common.core.constant.ErrorCodeEnum;
+import com.tourism.common.core.object.*;
+import com.tourism.common.core.util.*;
+import com.tourism.common.log.annotation.OperationLog;
+import com.tourism.common.log.model.constant.SysOperationLogType;
+import com.tourism.webadmin.back.dto.TourBookInfoDto;
+import com.tourism.webadmin.back.model.TourBookInfo;
+import com.tourism.webadmin.back.service.TourBookInfoService;
+import com.tourism.webadmin.back.vo.TourBookInfoVo;
+import com.tourism.common.additional.config.ApplicationConfig;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * 门户预定管理操作控制器类。
+ *
+ * @author 吃饭睡觉
+ * @date 2024-09-06
+ */
+@Tag(name = "门户预定管理管理接口")
+@Slf4j
+@RestController
+@RequestMapping("/admin/app/tourBookInfo")
+public class TourBookInfoController {
+
+    @Autowired
+    private ApplicationConfig appConfig;
+    @Autowired
+    private TourBookInfoService tourBookInfoService;
+
+    /**
+     * 新增门户预定管理数据。
+     *
+     * @param tourBookInfoDto 新增对象。
+     * @return 应答结果对象,包含新增对象主键Id。
+     */
+    @ApiOperationSupport(ignoreParameters = {"tourBookInfoDto.id", "tourBookInfoDto.searchString"})
+    @SaCheckPermission("tourBookInfo.add")
+    @OperationLog(type = SysOperationLogType.ADD)
+    @PostMapping("/add")
+    public ResponseResult<Long> add(@MyRequestBody TourBookInfoDto tourBookInfoDto) {
+        String errorMessage = MyCommonUtil.getModelValidationError(tourBookInfoDto, false);
+        if (errorMessage != null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
+        }
+        TourBookInfo tourBookInfo = MyModelUtil.copyTo(tourBookInfoDto, TourBookInfo.class);
+        // 验证关联Id的数据合法性
+        CallResult callResult = tourBookInfoService.verifyRelatedData(tourBookInfo, null);
+        if (!callResult.isSuccess()) {
+            return ResponseResult.errorFrom(callResult);
+        }
+        tourBookInfo = tourBookInfoService.saveNew(tourBookInfo);
+        return ResponseResult.success(tourBookInfo.getId());
+    }
+
+    /**
+     * 更新门户预定管理数据。
+     *
+     * @param tourBookInfoDto 更新对象。
+     * @return 应答结果对象。
+     */
+    @ApiOperationSupport(ignoreParameters = {"tourBookInfoDto.searchString"})
+    @SaCheckPermission("tourBookInfo.update")
+    @OperationLog(type = SysOperationLogType.UPDATE)
+    @PostMapping("/update")
+    public ResponseResult<Void> update(@MyRequestBody TourBookInfoDto tourBookInfoDto) {
+        String errorMessage = MyCommonUtil.getModelValidationError(tourBookInfoDto, true);
+        if (errorMessage != null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
+        }
+        TourBookInfo tourBookInfo = MyModelUtil.copyTo(tourBookInfoDto, TourBookInfo.class);
+        TourBookInfo originalTourBookInfo = tourBookInfoService.getById(tourBookInfo.getId());
+        if (originalTourBookInfo == null) {
+            // NOTE: 修改下面方括号中的话述
+            errorMessage = "数据验证失败,当前 [数据] 并不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        // 验证关联Id的数据合法性
+        CallResult callResult = tourBookInfoService.verifyRelatedData(tourBookInfo, originalTourBookInfo);
+        if (!callResult.isSuccess()) {
+            return ResponseResult.errorFrom(callResult);
+        }
+        if (!tourBookInfoService.update(tourBookInfo, originalTourBookInfo)) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
+        }
+        return ResponseResult.success();
+    }
+
+    /**
+     * 删除门户预定管理数据。
+     *
+     * @param id 删除对象主键Id。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("tourBookInfo.delete")
+    @OperationLog(type = SysOperationLogType.DELETE)
+    @PostMapping("/delete")
+    public ResponseResult<Void> delete(@MyRequestBody Long id) {
+        if (MyCommonUtil.existBlankArgument(id)) {
+            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
+        }
+        return this.doDelete(id);
+    }
+
+    /**
+     * 批量删除门户预定管理数据。
+     *
+     * @param idList 待删除对象的主键Id列表。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("tourBookInfo.delete")
+    @OperationLog(type = SysOperationLogType.DELETE_BATCH)
+    @PostMapping("/deleteBatch")
+    public ResponseResult<Void> deleteBatch(@MyRequestBody List<Long> idList) {
+        if (MyCommonUtil.existBlankArgument(idList)) {
+            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
+        }
+        for (Long id : idList) {
+            ResponseResult<Void> responseResult = this.doDelete(id);
+            if (!responseResult.isSuccess()) {
+                return responseResult;
+            }
+        }
+        return ResponseResult.success();
+    }
+
+    /**
+     * 列出符合过滤条件的门户预定管理列表。
+     *
+     * @param tourBookInfoDtoFilter 过滤对象。
+     * @param orderParam 排序参数。
+     * @param pageParam 分页参数。
+     * @return 应答结果对象,包含查询结果集。
+     */
+    @SaCheckPermission("tourBookInfo.view")
+    @PostMapping("/list")
+    public ResponseResult<MyPageData<TourBookInfoVo>> list(
+            @MyRequestBody TourBookInfoDto tourBookInfoDtoFilter,
+            @MyRequestBody MyOrderParam orderParam,
+            @MyRequestBody MyPageParam pageParam) {
+        if (pageParam != null) {
+            PageMethod.startPage(pageParam.getPageNum(), pageParam.getPageSize(), pageParam.getCount());
+        }
+        TourBookInfo tourBookInfoFilter = MyModelUtil.copyTo(tourBookInfoDtoFilter, TourBookInfo.class);
+        String orderBy = MyOrderParam.buildOrderBy(orderParam, TourBookInfo.class);
+        List<TourBookInfo> tourBookInfoList =
+                tourBookInfoService.getTourBookInfoListWithRelation(tourBookInfoFilter, orderBy);
+        return ResponseResult.success(MyPageUtil.makeResponseData(tourBookInfoList, TourBookInfoVo.class));
+    }
+
+    /**
+     * 导入主表数据列表。
+     *
+     * @param importFile 上传的文件,目前仅仅支持xlsx和xls两种格式。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("tourBookInfo.import")
+    @OperationLog(type = SysOperationLogType.IMPORT)
+    @PostMapping("/import")
+    public ResponseResult<Void> importBatch(
+            @RequestParam Boolean skipHeader,
+            @RequestParam("importFile") MultipartFile importFile) throws IOException {
+        String filename = ImportUtil.saveImportFile(appConfig.getUploadFileBaseDir(), null, importFile);
+        // 这里可以指定需要忽略导入的字段集合。如创建时间、创建人、更新时间、更新人、主键Id和逻辑删除,
+        // 以及一些存在缺省值且无需导入的字段。其中主键字段和逻辑删除字段不需要在这里设置,批量插入逻辑会自动处理的。
+        Set<String> ignoreFieldSet = new HashSet<>();
+        ignoreFieldSet.add("createUserId");
+        ignoreFieldSet.add("createTime");
+        ignoreFieldSet.add("updateUserId");
+        ignoreFieldSet.add("updateTime");
+        List<ImportUtil.ImportHeaderInfo> headerInfoList = ImportUtil.makeHeaderInfoList(TourBookInfo.class, ignoreFieldSet);
+        // 下面是导入时需要注意的地方,如果我们缺省生成的代码,与实际情况存在差异,请手动修改。
+        // 1. 头信息数据字段,我们只是根据当前的主表实体对象生成了缺省数组,开发者可根据实际情况,对headerInfoList进行修改。
+        ImportUtil.ImportHeaderInfo[] headerInfos = headerInfoList.toArray(new ImportUtil.ImportHeaderInfo[]{});
+        // 2. 这里需要根据实际情况决定,导入文件中第一行是否为中文头信息,如果是可以跳过。这里我们默认为true。
+        // 这里根据自己的实际需求,为doImport的最后一个参数,传递需要进行字典转换的字段集合。
+        // 注意,集合中包含需要翻译的Java字段名,如: gradeId。
+        Set<String> translatedDictFieldSet = new HashSet<>();
+        List<TourBookInfo> dataList =
+                ImportUtil.doImport(headerInfos, skipHeader, filename, TourBookInfo.class, translatedDictFieldSet);
+        CallResult result = tourBookInfoService.verifyImportList(dataList, translatedDictFieldSet);
+        if (!result.isSuccess()) {
+            // result中返回了具体的验证失败对象,如果需要返回更加详细的错误,可根据实际情况手动修改。
+            return ResponseResult.errorFrom(result);
+        }
+        tourBookInfoService.saveNewBatch(dataList, -1);
+        return ResponseResult.success();
+    }
+
+    /**
+     * 导出符合过滤条件的门户预定管理列表。
+     *
+     * @param tourBookInfoDtoFilter 过滤对象。
+     * @param orderParam 排序参数。
+     * @throws IOException 文件读写失败。
+     */
+    @SaCheckPermission("tourBookInfo.export")
+    @OperationLog(type = SysOperationLogType.EXPORT, saveResponse = false)
+    @PostMapping("/export")
+    public void export(
+            @MyRequestBody TourBookInfoDto tourBookInfoDtoFilter,
+            @MyRequestBody MyOrderParam orderParam) throws IOException {
+        TourBookInfo tourBookInfoFilter = MyModelUtil.copyTo(tourBookInfoDtoFilter, TourBookInfo.class);
+        String orderBy = MyOrderParam.buildOrderBy(orderParam, TourBookInfo.class);
+        List<TourBookInfo> resultList =
+                tourBookInfoService.getTourBookInfoListWithRelation(tourBookInfoFilter, orderBy);
+        // 导出文件的标题数组
+        // NOTE: 下面的代码中仅仅导出了主表数据,主表聚合计算数据和主表关联字典的数据。
+        // 一对一从表数据的导出,可根据需要自行添加。如:headerMap.put("slaveFieldName.xxxField", "标题名称")
+        Map<String, String> headerMap = new LinkedHashMap<>(11);
+        headerMap.put("id", "主键id");
+        headerMap.put("bookMobile", "预定电话");
+        headerMap.put("bookName", "预定姓名");
+        headerMap.put("bookIp", "预定的ip");
+        headerMap.put("bookTime", "预定时间");
+        headerMap.put("isHandleDictMap.name", "是否处理(0:未处理 1:已处理)");
+        headerMap.put("createUserId", "创建者Id");
+        headerMap.put("createTime", "创建时间");
+        headerMap.put("updateUserId", "更新者Id");
+        headerMap.put("updateTime", "最后更新时间");
+        headerMap.put("deletedFlag", "删除标记(1: 正常 -1: 已删除)");
+        ExportUtil.doExport(resultList, headerMap, "tourBookInfo.xlsx");
+    }
+
+    /**
+     * 查看指定门户预定管理对象详情。
+     *
+     * @param id 指定对象主键Id。
+     * @return 应答结果对象,包含对象详情。
+     */
+    @SaCheckPermission("tourBookInfo.view")
+    @GetMapping("/view")
+    public ResponseResult<TourBookInfoVo> view(@RequestParam Long id) {
+        TourBookInfo tourBookInfo = tourBookInfoService.getByIdWithRelation(id, MyRelationParam.full());
+        if (tourBookInfo == null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
+        }
+        TourBookInfoVo tourBookInfoVo = MyModelUtil.copyTo(tourBookInfo, TourBookInfoVo.class);
+        return ResponseResult.success(tourBookInfoVo);
+    }
+
+    private ResponseResult<Void> doDelete(Long id) {
+        String errorMessage;
+        // 验证关联Id的数据合法性
+        TourBookInfo originalTourBookInfo = tourBookInfoService.getById(id);
+        if (originalTourBookInfo == null) {
+            // NOTE: 修改下面方括号中的话述
+            errorMessage = "数据验证失败,当前 [对象] 并不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        if (!tourBookInfoService.remove(id)) {
+            errorMessage = "数据操作失败,删除的对象不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        return ResponseResult.success();
+    }
+}

+ 276 - 0
application-webadmin/src/main/java/com/tourism/webadmin/back/controller/TourCountryCodeController.java

@@ -0,0 +1,276 @@
+package com.tourism.webadmin.back.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.dev33.satoken.annotation.SaIgnore;
+import com.ibm.icu.text.Transliterator;
+import com.tourism.common.log.annotation.OperationLog;
+import com.tourism.common.log.model.constant.SysOperationLogType;
+import com.github.pagehelper.page.PageMethod;
+import com.tourism.common.core.object.*;
+import com.tourism.common.core.util.*;
+import com.tourism.common.core.constant.*;
+import com.tourism.common.core.annotation.MyRequestBody;
+import com.tourism.webadmin.back.dto.TourCountryCodeDto;
+import com.tourism.webadmin.back.model.TourCountryCode;
+import com.tourism.webadmin.back.service.TourCountryCodeService;
+import com.tourism.webadmin.back.vo.TourCountryCodeVo;
+import com.tourism.common.additional.config.ApplicationConfig;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * 国家地区代码表操作控制器类。
+ *
+ * @author 吃饭睡觉
+ * @date 2024-09-06
+ */
+@Tag(name = "国家地区代码表管理接口")
+@Slf4j
+@RestController
+@RequestMapping("/admin/app/tourCountryCode")
+public class TourCountryCodeController {
+
+    @Autowired
+    private ApplicationConfig appConfig;
+    @Autowired
+    private TourCountryCodeService tourCountryCodeService;
+
+    /**
+     * 新增国家地区代码表数据。
+     *
+     * @param tourCountryCodeDto 新增对象。
+     * @return 应答结果对象,包含新增对象主键Id。
+     */
+    @ApiOperationSupport(ignoreParameters = {"tourCountryCodeDto.id"})
+    @SaCheckPermission("tourCountryCode.add")
+    @OperationLog(type = SysOperationLogType.ADD)
+    @PostMapping("/add")
+    public ResponseResult<Long> add(@MyRequestBody TourCountryCodeDto tourCountryCodeDto) {
+        String errorMessage = MyCommonUtil.getModelValidationError(tourCountryCodeDto, false);
+        if (errorMessage != null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
+        }
+        TourCountryCode tourCountryCode = MyModelUtil.copyTo(tourCountryCodeDto, TourCountryCode.class);
+        tourCountryCode = tourCountryCodeService.saveNew(tourCountryCode);
+        return ResponseResult.success(tourCountryCode.getId());
+    }
+
+    /**
+     * 更新国家地区代码表数据。
+     *
+     * @param tourCountryCodeDto 更新对象。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("tourCountryCode.update")
+    @OperationLog(type = SysOperationLogType.UPDATE)
+    @PostMapping("/update")
+    public ResponseResult<Void> update(@MyRequestBody TourCountryCodeDto tourCountryCodeDto) {
+        String errorMessage = MyCommonUtil.getModelValidationError(tourCountryCodeDto, true);
+        if (errorMessage != null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
+        }
+        TourCountryCode tourCountryCode = MyModelUtil.copyTo(tourCountryCodeDto, TourCountryCode.class);
+        TourCountryCode originalTourCountryCode = tourCountryCodeService.getById(tourCountryCode.getId());
+        if (originalTourCountryCode == null) {
+            // NOTE: 修改下面方括号中的话述
+            errorMessage = "数据验证失败,当前 [数据] 并不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        if (!tourCountryCodeService.update(tourCountryCode, originalTourCountryCode)) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
+        }
+        return ResponseResult.success();
+    }
+
+    /**
+     * 删除国家地区代码表数据。
+     *
+     * @param id 删除对象主键Id。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("tourCountryCode.delete")
+    @OperationLog(type = SysOperationLogType.DELETE)
+    @PostMapping("/delete")
+    public ResponseResult<Void> delete(@MyRequestBody Long id) {
+        if (MyCommonUtil.existBlankArgument(id)) {
+            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
+        }
+        return this.doDelete(id);
+    }
+
+    /**
+     * 批量删除国家地区代码表数据。
+     *
+     * @param idList 待删除对象的主键Id列表。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("tourCountryCode.delete")
+    @OperationLog(type = SysOperationLogType.DELETE_BATCH)
+    @PostMapping("/deleteBatch")
+    public ResponseResult<Void> deleteBatch(@MyRequestBody List<Long> idList) {
+        if (MyCommonUtil.existBlankArgument(idList)) {
+            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
+        }
+        for (Long id : idList) {
+            ResponseResult<Void> responseResult = this.doDelete(id);
+            if (!responseResult.isSuccess()) {
+                return responseResult;
+            }
+        }
+        return ResponseResult.success();
+    }
+
+    /**
+     * 列出符合过滤条件的国家地区代码表列表。
+     *
+     * @param tourCountryCodeDtoFilter 过滤对象。
+     * @param orderParam 排序参数。
+     * @param pageParam 分页参数。
+     * @return 应答结果对象,包含查询结果集。
+     */
+    @SaCheckPermission("tourCountryCode.view")
+    @PostMapping("/list")
+    public ResponseResult<MyPageData<TourCountryCodeVo>> list(
+            @MyRequestBody TourCountryCodeDto tourCountryCodeDtoFilter,
+            @MyRequestBody MyOrderParam orderParam,
+            @MyRequestBody MyPageParam pageParam) {
+        if (pageParam != null) {
+            PageMethod.startPage(pageParam.getPageNum(), pageParam.getPageSize(), pageParam.getCount());
+        }
+        TourCountryCode tourCountryCodeFilter = MyModelUtil.copyTo(tourCountryCodeDtoFilter, TourCountryCode.class);
+        String orderBy = MyOrderParam.buildOrderBy(orderParam, TourCountryCode.class);
+        List<TourCountryCode> tourCountryCodeList =
+                tourCountryCodeService.getTourCountryCodeListWithRelation(tourCountryCodeFilter, orderBy);
+        return ResponseResult.success(MyPageUtil.makeResponseData(tourCountryCodeList, TourCountryCodeVo.class));
+    }
+
+    /**
+     * 导入主表数据列表。
+     *
+     * @param importFile 上传的文件,目前仅仅支持xlsx和xls两种格式。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("tourCountryCode.import")
+    @OperationLog(type = SysOperationLogType.IMPORT)
+    @PostMapping("/import")
+    public ResponseResult<Void> importBatch(
+            @RequestParam Boolean skipHeader,
+            @RequestParam("importFile") MultipartFile importFile) throws IOException {
+        String filename = ImportUtil.saveImportFile(appConfig.getUploadFileBaseDir(), null, importFile);
+        // 这里可以指定需要忽略导入的字段集合。如创建时间、创建人、更新时间、更新人、主键Id和逻辑删除,
+        // 以及一些存在缺省值且无需导入的字段。其中主键字段和逻辑删除字段不需要在这里设置,批量插入逻辑会自动处理的。
+        Set<String> ignoreFieldSet = new HashSet<>();
+        ignoreFieldSet.add("createUserId");
+        ignoreFieldSet.add("createTime");
+        ignoreFieldSet.add("updateUserId");
+        ignoreFieldSet.add("updateTime");
+        List<ImportUtil.ImportHeaderInfo> headerInfoList = ImportUtil.makeHeaderInfoList(TourCountryCode.class, ignoreFieldSet);
+        // 下面是导入时需要注意的地方,如果我们缺省生成的代码,与实际情况存在差异,请手动修改。
+        // 1. 头信息数据字段,我们只是根据当前的主表实体对象生成了缺省数组,开发者可根据实际情况,对headerInfoList进行修改。
+        ImportUtil.ImportHeaderInfo[] headerInfos = headerInfoList.toArray(new ImportUtil.ImportHeaderInfo[]{});
+        // 2. 这里需要根据实际情况决定,导入文件中第一行是否为中文头信息,如果是可以跳过。这里我们默认为true。
+        // 这里根据自己的实际需求,为doImport的最后一个参数,传递需要进行字典转换的字段集合。
+        // 注意,集合中包含需要翻译的Java字段名,如: gradeId。
+        Set<String> translatedDictFieldSet = new HashSet<>();
+        List<TourCountryCode> dataList =
+                ImportUtil.doImport(headerInfos, skipHeader, filename, TourCountryCode.class, translatedDictFieldSet);
+        tourCountryCodeService.saveNewBatch(dataList, -1);
+        return ResponseResult.success();
+    }
+
+    /**
+     * 导出符合过滤条件的国家地区代码表列表。
+     *
+     * @param tourCountryCodeDtoFilter 过滤对象。
+     * @param orderParam 排序参数。
+     * @throws IOException 文件读写失败。
+     */
+    @SaCheckPermission("tourCountryCode.export")
+    @OperationLog(type = SysOperationLogType.EXPORT, saveResponse = false)
+    @PostMapping("/export")
+    public void export(
+            @MyRequestBody TourCountryCodeDto tourCountryCodeDtoFilter,
+            @MyRequestBody MyOrderParam orderParam) throws IOException {
+        TourCountryCode tourCountryCodeFilter = MyModelUtil.copyTo(tourCountryCodeDtoFilter, TourCountryCode.class);
+        String orderBy = MyOrderParam.buildOrderBy(orderParam, TourCountryCode.class);
+        List<TourCountryCode> resultList =
+                tourCountryCodeService.getTourCountryCodeListWithRelation(tourCountryCodeFilter, orderBy);
+        // 导出文件的标题数组
+        // NOTE: 下面的代码中仅仅导出了主表数据,主表聚合计算数据和主表关联字典的数据。
+        // 一对一从表数据的导出,可根据需要自行添加。如:headerMap.put("slaveFieldName.xxxField", "标题名称")
+        Map<String, String> headerMap = new LinkedHashMap<>(12);
+        headerMap.put("id", "主键");
+        headerMap.put("englishName", "英文名称");
+        headerMap.put("country", "国家/地区");
+        headerMap.put("countryCode", "国家代码");
+        headerMap.put("areaCode", "区号");
+        headerMap.put("dataState", "数据状态");
+        headerMap.put("pinYin", "拼音");
+        headerMap.put("createUserId", "创建用户");
+        headerMap.put("firstCode", "拼音首字母");
+        headerMap.put("createTime", "创建时间");
+        headerMap.put("updateUserId", "更新用户");
+        headerMap.put("updateTime", "更新时间");
+        ExportUtil.doExport(resultList, headerMap, "tourCountryCode.xlsx");
+    }
+
+    /**
+     * 查看指定国家地区代码表对象详情。
+     *
+     * @param id 指定对象主键Id。
+     * @return 应答结果对象,包含对象详情。
+     */
+    @SaCheckPermission("tourCountryCode.view")
+    @GetMapping("/view")
+    public ResponseResult<TourCountryCodeVo> view(@RequestParam Long id) {
+        TourCountryCode tourCountryCode = tourCountryCodeService.getByIdWithRelation(id, MyRelationParam.full());
+        if (tourCountryCode == null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
+        }
+        TourCountryCodeVo tourCountryCodeVo = MyModelUtil.copyTo(tourCountryCode, TourCountryCodeVo.class);
+        return ResponseResult.success(tourCountryCodeVo);
+    }
+
+    /**
+     * 查询tour_country_code表的数据,把country汉字翻译成拼音和拼音首字母,分别填充到pin_yin和first_code字段上
+     */
+    @SaIgnore
+    @GetMapping("gener")
+    public void gerner(){
+        List<TourCountryCode> tourCountryCodeList = tourCountryCodeService.list();
+        Transliterator transliterator = Transliterator.getInstance("Han-Latin; NFD; [:Nonspacing Mark:] Remove; NFC");
+        tourCountryCodeList.forEach(tourCountryCode -> {
+            String country = tourCountryCode.getCountry();
+
+            String pinYin =  transliterator.transliterate(country).replaceAll("\\s+", "").toUpperCase();
+//            String pinYin = PinYinUtil.getPinYin(country);
+            String firstCode = pinYin.substring(0, 1);
+            tourCountryCode.setPinYin(pinYin);
+            tourCountryCode.setFirstCode(firstCode);
+        });
+        tourCountryCodeService.updateBatch(tourCountryCodeList);
+    }
+
+    private ResponseResult<Void> doDelete(Long id) {
+        String errorMessage;
+        // 验证关联Id的数据合法性
+        TourCountryCode originalTourCountryCode = tourCountryCodeService.getById(id);
+        if (originalTourCountryCode == null) {
+            // NOTE: 修改下面方括号中的话述
+            errorMessage = "数据验证失败,当前 [对象] 并不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        if (!tourCountryCodeService.remove(id)) {
+            errorMessage = "数据操作失败,删除的对象不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        return ResponseResult.success();
+    }
+}

+ 200 - 0
application-webadmin/src/main/java/com/tourism/webadmin/back/controller/TourOrderController.java

@@ -0,0 +1,200 @@
+package com.tourism.webadmin.back.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import com.tourism.common.log.annotation.OperationLog;
+import com.tourism.common.log.model.constant.SysOperationLogType;
+import com.github.pagehelper.page.PageMethod;
+import com.tourism.webadmin.back.vo.*;
+import com.tourism.webadmin.back.dto.*;
+import com.tourism.webadmin.back.model.*;
+import com.tourism.webadmin.back.service.*;
+import com.tourism.common.core.object.*;
+import com.tourism.common.core.util.*;
+import com.tourism.common.core.constant.*;
+import com.tourism.common.core.annotation.MyRequestBody;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.*;
+
+/**
+ * 旅游订单操作控制器类。
+ *
+ * @author 吃饭睡觉
+ * @date 2024-09-06
+ */
+@Tag(name = "旅游订单管理接口")
+@Slf4j
+@RestController
+@RequestMapping("/admin/app/tourOrder")
+public class TourOrderController {
+
+    @Autowired
+    private TourOrderService tourOrderService;
+
+    /**
+     * 新增旅游订单数据。
+     *
+     * @param tourOrderDto 新增对象。
+     * @return 应答结果对象,包含新增对象主键Id。
+     */
+    @ApiOperationSupport(ignoreParameters = {
+            "tourOrderDto.id",
+            "tourOrderDto.searchString",
+            "tourOrderDto.departureDateStart",
+            "tourOrderDto.departureDateEnd",
+            "tourOrderDto.endDateStart",
+            "tourOrderDto.endDateEnd"})
+    @SaCheckPermission("tourOrder.add")
+    @OperationLog(type = SysOperationLogType.ADD)
+    @PostMapping("/add")
+    public ResponseResult<Long> add(@MyRequestBody TourOrderDto tourOrderDto) {
+        String errorMessage = MyCommonUtil.getModelValidationError(tourOrderDto, false);
+        if (errorMessage != null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
+        }
+        TourOrder tourOrder = MyModelUtil.copyTo(tourOrderDto, TourOrder.class);
+        // 验证关联Id的数据合法性
+        CallResult callResult = tourOrderService.verifyRelatedData(tourOrder, null);
+        if (!callResult.isSuccess()) {
+            return ResponseResult.errorFrom(callResult);
+        }
+        tourOrder = tourOrderService.saveNew(tourOrder);
+        return ResponseResult.success(tourOrder.getId());
+    }
+
+    /**
+     * 更新旅游订单数据。
+     *
+     * @param tourOrderDto 更新对象。
+     * @return 应答结果对象。
+     */
+    @ApiOperationSupport(ignoreParameters = {
+            "tourOrderDto.searchString",
+            "tourOrderDto.departureDateStart",
+            "tourOrderDto.departureDateEnd",
+            "tourOrderDto.endDateStart",
+            "tourOrderDto.endDateEnd"})
+    @SaCheckPermission("tourOrder.update")
+    @OperationLog(type = SysOperationLogType.UPDATE)
+    @PostMapping("/update")
+    public ResponseResult<Void> update(@MyRequestBody TourOrderDto tourOrderDto) {
+        String errorMessage = MyCommonUtil.getModelValidationError(tourOrderDto, true);
+        if (errorMessage != null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
+        }
+        TourOrder tourOrder = MyModelUtil.copyTo(tourOrderDto, TourOrder.class);
+        TourOrder originalTourOrder = tourOrderService.getById(tourOrder.getId());
+        if (originalTourOrder == null) {
+            // NOTE: 修改下面方括号中的话述
+            errorMessage = "数据验证失败,当前 [数据] 并不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        // 验证关联Id的数据合法性
+        CallResult callResult = tourOrderService.verifyRelatedData(tourOrder, originalTourOrder);
+        if (!callResult.isSuccess()) {
+            return ResponseResult.errorFrom(callResult);
+        }
+        if (!tourOrderService.update(tourOrder, originalTourOrder)) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
+        }
+        return ResponseResult.success();
+    }
+
+    /**
+     * 删除旅游订单数据。
+     *
+     * @param id 删除对象主键Id。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("tourOrder.delete")
+    @OperationLog(type = SysOperationLogType.DELETE)
+    @PostMapping("/delete")
+    public ResponseResult<Void> delete(@MyRequestBody Long id) {
+        if (MyCommonUtil.existBlankArgument(id)) {
+            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
+        }
+        return this.doDelete(id);
+    }
+
+    /**
+     * 批量删除旅游订单数据。
+     *
+     * @param idList 待删除对象的主键Id列表。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("tourOrder.delete")
+    @OperationLog(type = SysOperationLogType.DELETE_BATCH)
+    @PostMapping("/deleteBatch")
+    public ResponseResult<Void> deleteBatch(@MyRequestBody List<Long> idList) {
+        if (MyCommonUtil.existBlankArgument(idList)) {
+            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
+        }
+        for (Long id : idList) {
+            ResponseResult<Void> responseResult = this.doDelete(id);
+            if (!responseResult.isSuccess()) {
+                return responseResult;
+            }
+        }
+        return ResponseResult.success();
+    }
+
+    /**
+     * 列出符合过滤条件的旅游订单列表。
+     *
+     * @param tourOrderDtoFilter 过滤对象。
+     * @param orderParam 排序参数。
+     * @param pageParam 分页参数。
+     * @return 应答结果对象,包含查询结果集。
+     */
+    @SaCheckPermission("tourOrder.view")
+    @PostMapping("/list")
+    public ResponseResult<MyPageData<TourOrderVo>> list(
+            @MyRequestBody TourOrderDto tourOrderDtoFilter,
+            @MyRequestBody MyOrderParam orderParam,
+            @MyRequestBody MyPageParam pageParam) {
+        if (pageParam != null) {
+            PageMethod.startPage(pageParam.getPageNum(), pageParam.getPageSize(), pageParam.getCount());
+        }
+        TourOrder tourOrderFilter = MyModelUtil.copyTo(tourOrderDtoFilter, TourOrder.class);
+        String orderBy = MyOrderParam.buildOrderBy(orderParam, TourOrder.class);
+        List<TourOrder> tourOrderList = tourOrderService.getTourOrderListWithRelation(tourOrderFilter, orderBy);
+        return ResponseResult.success(MyPageUtil.makeResponseData(tourOrderList, TourOrderVo.class));
+    }
+
+    /**
+     * 查看指定旅游订单对象详情。
+     *
+     * @param id 指定对象主键Id。
+     * @return 应答结果对象,包含对象详情。
+     */
+    @SaCheckPermission("tourOrder.view")
+    @GetMapping("/view")
+    public ResponseResult<TourOrderVo> view(@RequestParam Long id) {
+        TourOrder tourOrder = tourOrderService.getByIdWithRelation(id, MyRelationParam.full());
+        if (tourOrder == null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
+        }
+        TourOrderVo tourOrderVo = MyModelUtil.copyTo(tourOrder, TourOrderVo.class);
+        return ResponseResult.success(tourOrderVo);
+    }
+
+    private ResponseResult<Void> doDelete(Long id) {
+        String errorMessage;
+        // 验证关联Id的数据合法性
+        TourOrder originalTourOrder = tourOrderService.getById(id);
+        if (originalTourOrder == null) {
+            // NOTE: 修改下面方括号中的话述
+            errorMessage = "数据验证失败,当前 [对象] 并不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        if (!tourOrderService.remove(id)) {
+            errorMessage = "数据操作失败,删除的对象不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        return ResponseResult.success();
+    }
+}

+ 180 - 0
application-webadmin/src/main/java/com/tourism/webadmin/back/controller/TourOrderPassenageController.java

@@ -0,0 +1,180 @@
+package com.tourism.webadmin.back.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import com.tourism.common.log.annotation.OperationLog;
+import com.tourism.common.log.model.constant.SysOperationLogType;
+import com.github.pagehelper.page.PageMethod;
+import com.tourism.webadmin.back.vo.*;
+import com.tourism.webadmin.back.dto.*;
+import com.tourism.webadmin.back.model.*;
+import com.tourism.webadmin.back.service.*;
+import com.tourism.common.core.object.*;
+import com.tourism.common.core.util.*;
+import com.tourism.common.core.constant.*;
+import com.tourism.common.core.annotation.MyRequestBody;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.*;
+
+/**
+ * 旅游订单游客操作控制器类。
+ *
+ * @author 吃饭睡觉
+ * @date 2024-09-06
+ */
+@Tag(name = "旅游订单游客管理接口")
+@Slf4j
+@RestController
+@RequestMapping("/admin/app/tourOrderPassenage")
+public class TourOrderPassenageController {
+
+    @Autowired
+    private TourOrderPassenageService tourOrderPassenageService;
+
+    /**
+     * 新增旅游订单游客数据。
+     *
+     * @param tourOrderPassenageDto 新增对象。
+     * @return 应答结果对象,包含新增对象主键Id。
+     */
+    @ApiOperationSupport(ignoreParameters = {"tourOrderPassenageDto.id", "tourOrderPassenageDto.searchString"})
+    @SaCheckPermission("tourOrderPassenage.add")
+    @OperationLog(type = SysOperationLogType.ADD)
+    @PostMapping("/add")
+    public ResponseResult<Long> add(@MyRequestBody TourOrderPassenageDto tourOrderPassenageDto) {
+        String errorMessage = MyCommonUtil.getModelValidationError(tourOrderPassenageDto, false);
+        if (errorMessage != null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
+        }
+        TourOrderPassenage tourOrderPassenage = MyModelUtil.copyTo(tourOrderPassenageDto, TourOrderPassenage.class);
+        tourOrderPassenage = tourOrderPassenageService.saveNew(tourOrderPassenage);
+        return ResponseResult.success(tourOrderPassenage.getId());
+    }
+
+    /**
+     * 更新旅游订单游客数据。
+     *
+     * @param tourOrderPassenageDto 更新对象。
+     * @return 应答结果对象。
+     */
+    @ApiOperationSupport(ignoreParameters = {"tourOrderPassenageDto.searchString"})
+    @SaCheckPermission("tourOrderPassenage.update")
+    @OperationLog(type = SysOperationLogType.UPDATE)
+    @PostMapping("/update")
+    public ResponseResult<Void> update(@MyRequestBody TourOrderPassenageDto tourOrderPassenageDto) {
+        String errorMessage = MyCommonUtil.getModelValidationError(tourOrderPassenageDto, true);
+        if (errorMessage != null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
+        }
+        TourOrderPassenage tourOrderPassenage = MyModelUtil.copyTo(tourOrderPassenageDto, TourOrderPassenage.class);
+        TourOrderPassenage originalTourOrderPassenage = tourOrderPassenageService.getById(tourOrderPassenage.getId());
+        if (originalTourOrderPassenage == null) {
+            // NOTE: 修改下面方括号中的话述
+            errorMessage = "数据验证失败,当前 [数据] 并不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        if (!tourOrderPassenageService.update(tourOrderPassenage, originalTourOrderPassenage)) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
+        }
+        return ResponseResult.success();
+    }
+
+    /**
+     * 删除旅游订单游客数据。
+     *
+     * @param id 删除对象主键Id。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("tourOrderPassenage.delete")
+    @OperationLog(type = SysOperationLogType.DELETE)
+    @PostMapping("/delete")
+    public ResponseResult<Void> delete(@MyRequestBody Long id) {
+        if (MyCommonUtil.existBlankArgument(id)) {
+            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
+        }
+        return this.doDelete(id);
+    }
+
+    /**
+     * 批量删除旅游订单游客数据。
+     *
+     * @param idList 待删除对象的主键Id列表。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("tourOrderPassenage.delete")
+    @OperationLog(type = SysOperationLogType.DELETE_BATCH)
+    @PostMapping("/deleteBatch")
+    public ResponseResult<Void> deleteBatch(@MyRequestBody List<Long> idList) {
+        if (MyCommonUtil.existBlankArgument(idList)) {
+            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
+        }
+        for (Long id : idList) {
+            ResponseResult<Void> responseResult = this.doDelete(id);
+            if (!responseResult.isSuccess()) {
+                return responseResult;
+            }
+        }
+        return ResponseResult.success();
+    }
+
+    /**
+     * 列出符合过滤条件的旅游订单游客列表。
+     *
+     * @param tourOrderPassenageDtoFilter 过滤对象。
+     * @param orderParam 排序参数。
+     * @param pageParam 分页参数。
+     * @return 应答结果对象,包含查询结果集。
+     */
+    @SaCheckPermission("tourOrderPassenage.view")
+    @PostMapping("/list")
+    public ResponseResult<MyPageData<TourOrderPassenageVo>> list(
+            @MyRequestBody TourOrderPassenageDto tourOrderPassenageDtoFilter,
+            @MyRequestBody MyOrderParam orderParam,
+            @MyRequestBody MyPageParam pageParam) {
+        if (pageParam != null) {
+            PageMethod.startPage(pageParam.getPageNum(), pageParam.getPageSize(), pageParam.getCount());
+        }
+        TourOrderPassenage tourOrderPassenageFilter = MyModelUtil.copyTo(tourOrderPassenageDtoFilter, TourOrderPassenage.class);
+        String orderBy = MyOrderParam.buildOrderBy(orderParam, TourOrderPassenage.class);
+        List<TourOrderPassenage> tourOrderPassenageList =
+                tourOrderPassenageService.getTourOrderPassenageListWithRelation(tourOrderPassenageFilter, orderBy);
+        return ResponseResult.success(MyPageUtil.makeResponseData(tourOrderPassenageList, TourOrderPassenageVo.class));
+    }
+
+    /**
+     * 查看指定旅游订单游客对象详情。
+     *
+     * @param id 指定对象主键Id。
+     * @return 应答结果对象,包含对象详情。
+     */
+    @SaCheckPermission("tourOrderPassenage.view")
+    @GetMapping("/view")
+    public ResponseResult<TourOrderPassenageVo> view(@RequestParam Long id) {
+        TourOrderPassenage tourOrderPassenage = tourOrderPassenageService.getByIdWithRelation(id, MyRelationParam.full());
+        if (tourOrderPassenage == null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
+        }
+        TourOrderPassenageVo tourOrderPassenageVo = MyModelUtil.copyTo(tourOrderPassenage, TourOrderPassenageVo.class);
+        return ResponseResult.success(tourOrderPassenageVo);
+    }
+
+    private ResponseResult<Void> doDelete(Long id) {
+        String errorMessage;
+        // 验证关联Id的数据合法性
+        TourOrderPassenage originalTourOrderPassenage = tourOrderPassenageService.getById(id);
+        if (originalTourOrderPassenage == null) {
+            // NOTE: 修改下面方括号中的话述
+            errorMessage = "数据验证失败,当前 [对象] 并不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        if (!tourOrderPassenageService.remove(id)) {
+            errorMessage = "数据操作失败,删除的对象不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        return ResponseResult.success();
+    }
+}

+ 591 - 0
application-webadmin/src/main/java/com/tourism/webadmin/back/controller/TourTourismProjectTravelNotesController.java

@@ -0,0 +1,591 @@
+package com.tourism.webadmin.back.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.dev33.satoken.annotation.SaIgnore;
+import com.alibaba.fastjson.JSONObject;
+import cn.hutool.core.util.ReflectUtil;
+import com.tourism.common.core.upload.BaseUpDownloader;
+import com.tourism.common.core.upload.UpDownloaderFactory;
+import com.tourism.common.core.upload.UploadResponseInfo;
+import com.tourism.common.core.upload.UploadStoreInfo;
+import com.tourism.common.log.annotation.OperationLog;
+import com.tourism.common.log.model.constant.SysOperationLogType;
+import com.github.pagehelper.page.PageMethod;
+import com.tourism.webadmin.back.vo.*;
+import com.tourism.webadmin.back.dto.*;
+import com.tourism.webadmin.back.model.*;
+import com.tourism.webadmin.back.service.*;
+import com.tourism.common.core.object.*;
+import com.tourism.common.core.util.*;
+import com.tourism.common.core.constant.*;
+import com.tourism.common.core.annotation.MyRequestBody;
+import com.tourism.common.redis.cache.SessionCacheHelper;
+import com.tourism.common.additional.config.ApplicationConfig;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import jakarta.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 旅游项目游记管理操作控制器类。
+ *
+ * @author 吃饭睡觉
+ * @date 2024-09-06
+ */
+@Tag(name = "旅游项目游记管理管理接口")
+@Slf4j
+@RestController
+@RequestMapping("/admin/app/tourTourismProjectTravelNotes")
+public class TourTourismProjectTravelNotesController {
+
+    @Autowired
+    private TourismProjectService tourismProjectService;
+    @Autowired
+    private ApplicationConfig appConfig;
+    @Autowired
+    private SessionCacheHelper cacheHelper;
+    @Autowired
+    private UpDownloaderFactory upDownloaderFactory;
+    @Autowired
+    private TourTourismProjectTravelNotesService tourTourismProjectTravelNotesService;
+    @Autowired
+    private TourTravelNotesProjectRelationService tourTravelNotesProjectRelationService;
+
+    /**
+     * 新增旅游项目游记管理数据,及其关联的从表数据。
+     *
+     * @param tourTourismProjectTravelNotesDto 新增主表对象。
+     * @param tourTourismTravelNotesFileDto 一对一旅游项目游记文件从表Dto。
+     * @param tourTourismTravelNotesContentDto 一对一旅游项目游记富文本从表Dto。
+     * @return 应答结果对象,包含新增对象主键Id。
+     */
+    @ApiOperationSupport(ignoreParameters = {"tourTourismProjectTravelNotesDto.id", "tourTourismProjectTravelNotesDto.searchString"})
+    @SaCheckPermission("tourTourismProjectTravelNotes.add")
+    @OperationLog(type = SysOperationLogType.ADD)
+    @PostMapping("/add")
+    public ResponseResult<String> add(
+            @MyRequestBody TourTourismProjectTravelNotesDto tourTourismProjectTravelNotesDto,
+            @MyRequestBody TourTourismTravelNotesFileDto tourTourismTravelNotesFileDto,
+            @MyRequestBody TourTourismTravelNotesContentDto tourTourismTravelNotesContentDto) {
+        ResponseResult<Tuple2<TourTourismProjectTravelNotes, JSONObject>> verifyResult = this.doBusinessDataVerifyAndConvert(
+                tourTourismProjectTravelNotesDto, false, tourTourismTravelNotesFileDto, tourTourismTravelNotesContentDto);
+        if (!verifyResult.isSuccess()) {
+            return ResponseResult.errorFrom(verifyResult);
+        }
+        Tuple2<TourTourismProjectTravelNotes, JSONObject> bizData = verifyResult.getData();
+        TourTourismProjectTravelNotes tourTourismProjectTravelNotes = bizData.getFirst();
+        tourTourismProjectTravelNotes = tourTourismProjectTravelNotesService.saveNewWithRelation(tourTourismProjectTravelNotes, bizData.getSecond());
+        return ResponseResult.success(tourTourismProjectTravelNotes.getId());
+    }
+
+    /**
+     * 修改旅游项目游记管理数据,及其关联的从表数据。
+     *
+     * @param tourTourismProjectTravelNotesDto 修改后的对象。
+     * @param tourTourismTravelNotesFileDto 一对一旅游项目游记文件从表Dto。
+     * @param tourTourismTravelNotesContentDto 一对一旅游项目游记富文本从表Dto。
+     * @return 应答结果对象,包含新增对象主键Id。
+     */
+    @ApiOperationSupport(ignoreParameters = {"tourTourismProjectTravelNotesDto.id", "tourTourismProjectTravelNotesDto.searchString"})
+    @SaCheckPermission("tourTourismProjectTravelNotes.update")
+    @OperationLog(type = SysOperationLogType.UPDATE)
+    @PostMapping("/update")
+    public ResponseResult<String> update(
+            @MyRequestBody TourTourismProjectTravelNotesDto tourTourismProjectTravelNotesDto,
+            @MyRequestBody TourTourismTravelNotesFileDto tourTourismTravelNotesFileDto,
+            @MyRequestBody TourTourismTravelNotesContentDto tourTourismTravelNotesContentDto) {
+        String errorMessage;
+        ResponseResult<Tuple2<TourTourismProjectTravelNotes, JSONObject>> verifyResult = this.doBusinessDataVerifyAndConvert(
+                tourTourismProjectTravelNotesDto, true, tourTourismTravelNotesFileDto, tourTourismTravelNotesContentDto);
+        if (!verifyResult.isSuccess()) {
+            return ResponseResult.errorFrom(verifyResult);
+        }
+        Tuple2<TourTourismProjectTravelNotes, JSONObject> bizData = verifyResult.getData();
+        TourTourismProjectTravelNotes originalTourTourismProjectTravelNotes = bizData.getSecond().getObject("originalData", TourTourismProjectTravelNotes.class);
+        TourTourismProjectTravelNotes tourTourismProjectTravelNotes = bizData.getFirst();
+        if (!tourTourismProjectTravelNotesService.updateWithRelation(tourTourismProjectTravelNotes, originalTourTourismProjectTravelNotes, bizData.getSecond())) {
+            errorMessage = "数据验证失败,[TourTourismProjectTravelNotes] 数据不存在!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        return ResponseResult.success(tourTourismProjectTravelNotes.getId());
+    }
+
+    /**
+     * 删除旅游项目游记管理数据。
+     *
+     * @param id 删除对象主键Id。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("tourTourismProjectTravelNotes.delete")
+    @OperationLog(type = SysOperationLogType.DELETE)
+    @PostMapping("/delete")
+    public ResponseResult<Void> delete(@MyRequestBody String id) {
+        if (MyCommonUtil.existBlankArgument(id)) {
+            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
+        }
+        return this.doDelete(id);
+    }
+
+    /**
+     * 批量删除旅游项目游记管理数据。
+     *
+     * @param idList 待删除对象的主键Id列表。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("tourTourismProjectTravelNotes.delete")
+    @OperationLog(type = SysOperationLogType.DELETE_BATCH)
+    @PostMapping("/deleteBatch")
+    public ResponseResult<Void> deleteBatch(@MyRequestBody List<String> idList) {
+        if (MyCommonUtil.existBlankArgument(idList)) {
+            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
+        }
+        for (String id : idList) {
+            ResponseResult<Void> responseResult = this.doDelete(id);
+            if (!responseResult.isSuccess()) {
+                return responseResult;
+            }
+        }
+        return ResponseResult.success();
+    }
+
+    /**
+     * 列出符合过滤条件的旅游项目游记管理列表。
+     *
+     * @param tourTourismProjectTravelNotesDtoFilter 过滤对象。
+     * @param orderParam 排序参数。
+     * @param pageParam 分页参数。
+     * @return 应答结果对象,包含查询结果集。
+     */
+    @SaCheckPermission("tourTourismProjectTravelNotes.view")
+    @PostMapping("/list")
+    public ResponseResult<MyPageData<TourTourismProjectTravelNotesVo>> list(
+            @MyRequestBody TourTourismProjectTravelNotesDto tourTourismProjectTravelNotesDtoFilter,
+            @MyRequestBody MyOrderParam orderParam,
+            @MyRequestBody MyPageParam pageParam) {
+        if (pageParam != null) {
+            PageMethod.startPage(pageParam.getPageNum(), pageParam.getPageSize(), pageParam.getCount());
+        }
+        TourTourismProjectTravelNotes tourTourismProjectTravelNotesFilter = MyModelUtil.copyTo(tourTourismProjectTravelNotesDtoFilter, TourTourismProjectTravelNotes.class);
+        String orderBy = MyOrderParam.buildOrderBy(orderParam, TourTourismProjectTravelNotes.class);
+        List<TourTourismProjectTravelNotes> tourTourismProjectTravelNotesList =
+                tourTourismProjectTravelNotesService.getTourTourismProjectTravelNotesListWithRelation(tourTourismProjectTravelNotesFilter, orderBy);
+        return ResponseResult.success(MyPageUtil.makeResponseData(tourTourismProjectTravelNotesList, TourTourismProjectTravelNotesVo.class));
+    }
+
+    /**
+     * 导出符合过滤条件的旅游项目游记管理列表。
+     *
+     * @param tourTourismProjectTravelNotesDtoFilter 过滤对象。
+     * @param orderParam 排序参数。
+     * @throws IOException 文件读写失败。
+     */
+    @SaCheckPermission("tourTourismProjectTravelNotes.export")
+    @OperationLog(type = SysOperationLogType.EXPORT, saveResponse = false)
+    @PostMapping("/export")
+    public void export(
+            @MyRequestBody TourTourismProjectTravelNotesDto tourTourismProjectTravelNotesDtoFilter,
+            @MyRequestBody MyOrderParam orderParam) throws IOException {
+        TourTourismProjectTravelNotes tourTourismProjectTravelNotesFilter = MyModelUtil.copyTo(tourTourismProjectTravelNotesDtoFilter, TourTourismProjectTravelNotes.class);
+        String orderBy = MyOrderParam.buildOrderBy(orderParam, TourTourismProjectTravelNotes.class);
+        List<TourTourismProjectTravelNotes> resultList =
+                tourTourismProjectTravelNotesService.getTourTourismProjectTravelNotesListWithRelation(tourTourismProjectTravelNotesFilter, orderBy);
+        // 导出文件的标题数组
+        // NOTE: 下面的代码中仅仅导出了主表数据,主表聚合计算数据和主表关联字典的数据。
+        // 一对一从表数据的导出,可根据需要自行添加。如:headerMap.put("slaveFieldName.xxxField", "标题名称")
+        Map<String, String> headerMap = new LinkedHashMap<>(34);
+        headerMap.put("id", "主键id");
+        headerMap.put("projectTitle", "项目标题");
+        headerMap.put("remarks", "项目简述");
+        headerMap.put("isHotspotDictMap.name", "是否热点,0非热点,1热点");
+        headerMap.put("projectLabel", "标签");
+        headerMap.put("showOrder", "显示顺序");
+        headerMap.put("enableDictMap.name", "是否启用,0禁用,1启用");
+        headerMap.put("belongTabDictMap.name", "所属分类");
+        headerMap.put("startPlace", "出发地");
+        headerMap.put("endPlace", "目的地");
+        headerMap.put("countTimes", "总天数");
+        headerMap.put("price", "项目价格");
+        headerMap.put("tourismUrl", "项目展示图片");
+        headerMap.put("createUserId", "创建用户");
+        headerMap.put("createTime", "创建时间");
+        headerMap.put("updateUserId", "更新用户");
+        headerMap.put("updateTime", "更新时间");
+        headerMap.put("dataState", "删除标记(1: 正常 -1: 已删除)");
+        headerMap.put("priceUnit", "价格单位");
+        headerMap.put("serviceEnsure", "服务保障");
+        headerMap.put("contactNumber", "联系电话");
+        headerMap.put("sellingPoint", "产品卖点");
+        headerMap.put("shortTitle", "短标题");
+        headerMap.put("shortDescription", "短描述");
+        headerMap.put("homeHotPicture", "首页热门图片");
+        headerMap.put("contactDescription", "联系人描述");
+        headerMap.put("travelNotesBanner", "游记banner");
+        headerMap.put("likeCount", "点赞量");
+        headerMap.put("pageViewCount", "浏览量");
+        headerMap.put("role", "人物");
+        headerMap.put("departureTime", "出发时间");
+        headerMap.put("averageCost", "人均费用");
+        headerMap.put("recommendationRate", "推荐指数");
+//        headerMap.put("projectIds", "旅游项目(以逗号分割,最多为三个项目)");
+        ExportUtil.doExport(resultList, headerMap, "tourTourismProjectTravelNotes.xlsx");
+    }
+
+    /**
+     * 查看指定旅游项目游记管理对象详情。
+     *
+     * @param id 指定对象主键Id。
+     * @return 应答结果对象,包含对象详情。
+     */
+    @SaCheckPermission("tourTourismProjectTravelNotes.view")
+    @GetMapping("/view")
+    public ResponseResult<TourTourismProjectTravelNotesVo> view(@RequestParam String id) {
+        TourTourismProjectTravelNotes tourTourismProjectTravelNotes = tourTourismProjectTravelNotesService.getByIdWithRelation(id, MyRelationParam.full());
+        if (tourTourismProjectTravelNotes == null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
+        }
+        TourTourismProjectTravelNotesVo tourTourismProjectTravelNotesVo = MyModelUtil.copyTo(tourTourismProjectTravelNotes, TourTourismProjectTravelNotesVo.class);
+        return ResponseResult.success(tourTourismProjectTravelNotesVo);
+    }
+
+    /**
+     * 列出不与指定旅游项目游记管理存在多对多关系的 [旅游项目管理] 列表数据。通常用于查看添加新 [旅游项目管理] 对象的候选列表。
+     *
+     * @param id 主表关联字段。
+     * @param tourismProjectDtoFilter [旅游项目管理] 过滤对象。
+     * @param orderParam 排序参数。
+     * @param pageParam 分页参数。
+     * @return 应答结果对象,返回符合条件的数据列表。
+     */
+    @SaCheckPermission("tourTourismProjectTravelNotes.update")
+    @PostMapping("/listNotInTourTravelNotesProjectRelation")
+    public ResponseResult<MyPageData<TourismProjectVo>> listNotInTourTravelNotesProjectRelation(
+            @MyRequestBody String id,
+            @MyRequestBody TourismProjectDto tourismProjectDtoFilter,
+            @MyRequestBody MyOrderParam orderParam,
+            @MyRequestBody MyPageParam pageParam) {
+        if (MyCommonUtil.isNotBlankOrNull(id) && !tourTourismProjectTravelNotesService.existId(id)) {
+            return ResponseResult.error(ErrorCodeEnum.INVALID_RELATED_RECORD_ID);
+        }
+        if (pageParam != null) {
+            PageMethod.startPage(pageParam.getPageNum(), pageParam.getPageSize(), pageParam.getCount());
+        }
+        TourismProject filter = MyModelUtil.copyTo(tourismProjectDtoFilter, TourismProject.class);
+        String orderBy = MyOrderParam.buildOrderBy(orderParam, TourismProject.class);
+        List<TourismProject> tourismProjectList =
+                tourismProjectService.getNotInTourismProjectListByTravelNotesId(id, filter, orderBy);
+        return ResponseResult.success(MyPageUtil.makeResponseData(tourismProjectList, TourismProjectVo.class));
+    }
+
+    /**
+     * 列出与指定旅游项目游记管理存在多对多关系的 [旅游项目管理] 列表数据。
+     *
+     * @param id 主表关联字段。
+     * @param tourismProjectDtoFilter [旅游项目管理] 过滤对象。
+     * @param orderParam 排序参数。
+     * @param pageParam 分页参数。
+     * @return 应答结果对象,返回符合条件的数据列表。
+     */
+    @SaCheckPermission("tourTourismProjectTravelNotes.view")
+    @PostMapping("/listTourTravelNotesProjectRelation")
+    public ResponseResult<MyPageData<TourismProjectVo>> listTourTravelNotesProjectRelation(
+            @MyRequestBody(required = true) String id,
+            @MyRequestBody TourismProjectDto tourismProjectDtoFilter,
+            @MyRequestBody MyOrderParam orderParam,
+            @MyRequestBody MyPageParam pageParam) {
+        if (!tourTourismProjectTravelNotesService.existId(id)) {
+            return ResponseResult.error(ErrorCodeEnum.INVALID_RELATED_RECORD_ID);
+        }
+        if (pageParam != null) {
+            PageMethod.startPage(pageParam.getPageNum(), pageParam.getPageSize(), pageParam.getCount());
+        }
+        TourismProject filter = MyModelUtil.copyTo(tourismProjectDtoFilter, TourismProject.class);
+        String orderBy = MyOrderParam.buildOrderBy(orderParam, TourismProject.class);
+        List<TourismProject> tourismProjectList =
+                tourismProjectService.getTourismProjectListByTravelNotesId(id, filter, orderBy);
+        return ResponseResult.success(MyPageUtil.makeResponseData(tourismProjectList, TourismProjectVo.class));
+    }
+
+    /**
+     * 批量添加旅游项目游记管理和 [旅游项目管理] 对象的多对多关联关系数据。
+     *
+     * @param travelNotesId 主表主键Id。
+     * @param tourTravelNotesProjectRelationDtoList 关联对象列表。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("tourTourismProjectTravelNotes.update")
+    @OperationLog(type = SysOperationLogType.ADD_M2M)
+    @PostMapping("/addTourTravelNotesProjectRelation")
+    public ResponseResult<Void> addTourTravelNotesProjectRelation(
+            @MyRequestBody String travelNotesId,
+            @MyRequestBody List<TourTravelNotesProjectRelationDto> tourTravelNotesProjectRelationDtoList) {
+
+        //先验证绑定的项目是否超过三个
+        TourTravelNotesProjectRelation tourTravelNotesProjectRelation =
+                new TourTravelNotesProjectRelation();
+        tourTravelNotesProjectRelation.setTravelNotesId(travelNotesId);
+        long countByFilter = tourTravelNotesProjectRelationService.getCountByFilter(tourTravelNotesProjectRelation);
+        if(countByFilter>=3){
+            return ResponseResult.error(ErrorCodeEnum.DUPLICATED_UNIQUE_KEY,"游记最多绑定三个项目,请先移除绑定项目");
+        }
+        if (MyCommonUtil.existBlankArgument(travelNotesId, tourTravelNotesProjectRelationDtoList)) {
+            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
+        }
+        Set<Long> projectIdSet =
+                tourTravelNotesProjectRelationDtoList.stream().map(TourTravelNotesProjectRelationDto::getProjectId).collect(Collectors.toSet());
+        if (!tourTourismProjectTravelNotesService.existId(travelNotesId)
+                || !tourismProjectService.existUniqueKeyList("id", projectIdSet)) {
+            return ResponseResult.error(ErrorCodeEnum.INVALID_RELATED_RECORD_ID);
+        }
+        List<TourTravelNotesProjectRelation> tourTravelNotesProjectRelationList =
+                MyModelUtil.copyCollectionTo(tourTravelNotesProjectRelationDtoList, TourTravelNotesProjectRelation.class);
+        tourTourismProjectTravelNotesService.addTourTravelNotesProjectRelationList(tourTravelNotesProjectRelationList, travelNotesId);
+        return ResponseResult.success();
+    }
+
+    /**
+     * 更新指定旅游项目游记管理和指定 [旅游项目管理] 的多对多关联数据。
+     *
+     * @param tourTravelNotesProjectRelationDto 对多对中间表对象。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("tourTourismProjectTravelNotes.update")
+    @OperationLog(type = SysOperationLogType.UPDATE)
+    @PostMapping("/updateTourTravelNotesProjectRelation")
+    public ResponseResult<Void> updateTourTravelNotesProjectRelation(
+            @MyRequestBody TourTravelNotesProjectRelationDto tourTravelNotesProjectRelationDto) {
+        String errorMessage = MyCommonUtil.getModelValidationError(tourTravelNotesProjectRelationDto);
+        if (errorMessage != null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
+        }
+        TourTravelNotesProjectRelation tourTravelNotesProjectRelation = MyModelUtil.copyTo(tourTravelNotesProjectRelationDto, TourTravelNotesProjectRelation.class);
+        if (!tourTourismProjectTravelNotesService.updateTourTravelNotesProjectRelation(tourTravelNotesProjectRelation)) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
+        }
+        return ResponseResult.success();
+    }
+
+    /**
+     * 显示旅游项目游记管理和指定 [旅游项目管理] 的多对多关联详情数据。
+     *
+     * @param travelNotesId 主表主键Id。
+     * @param projectId 从表主键Id。
+     * @return 应答结果对象,包括中间表详情。
+     */
+    @SaCheckPermission("tourTourismProjectTravelNotes.update")
+    @GetMapping("/viewTourTravelNotesProjectRelation")
+    public ResponseResult<TourTravelNotesProjectRelationVo> viewTourTravelNotesProjectRelation(
+            @RequestParam String travelNotesId, @RequestParam String projectId) {
+        TourTravelNotesProjectRelation tourTravelNotesProjectRelation = tourTourismProjectTravelNotesService.getTourTravelNotesProjectRelation(travelNotesId, projectId);
+        if (tourTravelNotesProjectRelation == null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
+        }
+        TourTravelNotesProjectRelationVo tourTravelNotesProjectRelationVo = MyModelUtil.copyTo(tourTravelNotesProjectRelation, TourTravelNotesProjectRelationVo.class);
+        return ResponseResult.success(tourTravelNotesProjectRelationVo);
+    }
+
+    /**
+     * 移除指定旅游项目游记管理和指定 [旅游项目管理] 的多对多关联关系。
+     *
+     * @param travelNotesId 主表主键Id。
+     * @param projectId 从表主键Id。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("tourTourismProjectTravelNotes.update")
+    @OperationLog(type = SysOperationLogType.DELETE_M2M)
+    @PostMapping("/deleteTourTravelNotesProjectRelation")
+    public ResponseResult<Void> deleteTourTravelNotesProjectRelation(
+            @MyRequestBody String travelNotesId, @MyRequestBody String projectId) {
+        if (MyCommonUtil.existBlankArgument(travelNotesId, projectId)) {
+            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
+        }
+        if (!tourTourismProjectTravelNotesService.removeTourTravelNotesProjectRelation(travelNotesId, projectId)) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
+        }
+        return ResponseResult.success();
+    }
+
+    /**
+     * 批量移除指定旅游项目游记管理和指定 [旅游项目管理] 的多对多关联关系。
+     *
+     * @param travelNotesId 主表主键Id。
+     * @param projectIdList 从表主键Id列表。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("tourTourismProjectTravelNotes.update")
+    @OperationLog(type = SysOperationLogType.DELETE_M2M_BATCH)
+    @PostMapping("/deleteTourTravelNotesProjectRelationList")
+    public ResponseResult<Void> deleteTourTravelNotesProjectRelationList(
+            @MyRequestBody String travelNotesId, @MyRequestBody List<String> projectIdList) {
+        if (MyCommonUtil.existBlankArgument(travelNotesId, projectIdList)) {
+            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
+        }
+        tourTourismProjectTravelNotesService.removeTourTravelNotesProjectRelationList(travelNotesId, projectIdList);
+        return ResponseResult.success();
+    }
+
+    /**
+     * 附件文件下载。
+     * 这里将图片和其他类型的附件文件放到不同的父目录下,主要为了便于今后图片文件的迁移。
+     *
+     * @param id 附件所在记录的主键Id。
+     * @param fieldName 附件所属的字段名。
+     * @param filename  文件名。如果没有提供该参数,就从当前记录的指定字段中读取。
+     * @param asImage   下载文件是否为图片。
+     * @param response  Http 应答对象。
+     */
+//    @SaCheckPermission("tourTourismProjectTravelNotes.view")
+    @SaIgnore
+    @OperationLog(type = SysOperationLogType.DOWNLOAD, saveResponse = false)
+    @GetMapping("/download")
+    public void download(
+            @RequestParam(required = false) Long id,
+            @RequestParam String fieldName,
+            @RequestParam String filename,
+            @RequestParam Boolean asImage,
+            HttpServletResponse response) {
+        if (MyCommonUtil.existBlankArgument(fieldName, filename, asImage)) {
+            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+            return;
+        }
+        // 使用try来捕获异常,是为了保证一旦出现异常可以返回500的错误状态,便于调试。
+        // 否则有可能给前端返回的是200的错误码。
+        try {
+            // 如果请求参数中没有包含主键Id,就判断该文件是否为当前session上传的。
+            if (id == null) {
+                if (!cacheHelper.existSessionUploadFile(filename)) {
+                    ResponseResult.output(HttpServletResponse.SC_FORBIDDEN);
+                    return;
+                }
+            } else {
+                TourTourismProjectTravelNotes tourTourismProjectTravelNotes = tourTourismProjectTravelNotesService.getById(id);
+                if (tourTourismProjectTravelNotes == null) {
+                    ResponseResult.output(HttpServletResponse.SC_NOT_FOUND);
+                    return;
+                }
+                String fieldJsonData = (String) ReflectUtil.getFieldValue(tourTourismProjectTravelNotes, fieldName);
+                if (fieldJsonData == null && !cacheHelper.existSessionUploadFile(filename)) {
+                    ResponseResult.output(HttpServletResponse.SC_BAD_REQUEST);
+                    return;
+                }
+                if (!BaseUpDownloader.containFile(fieldJsonData, filename)
+                        && !cacheHelper.existSessionUploadFile(filename)) {
+                    ResponseResult.output(HttpServletResponse.SC_FORBIDDEN);
+                    return;
+                }
+            }
+            UploadStoreInfo storeInfo = MyModelUtil.getUploadStoreInfo(TourTourismProjectTravelNotes.class, fieldName);
+            if (!storeInfo.isSupportUpload()) {
+                ResponseResult.output(HttpServletResponse.SC_NOT_IMPLEMENTED,
+                        ResponseResult.error(ErrorCodeEnum.INVALID_UPLOAD_FIELD));
+                return;
+            }
+            BaseUpDownloader upDownloader = upDownloaderFactory.get(storeInfo.getStoreType());
+            upDownloader.doDownload(appConfig.getUploadFileBaseDir(),
+                    TourTourismProjectTravelNotes.class.getSimpleName(), fieldName, filename, asImage, response);
+        } catch (Exception e) {
+            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+            log.error(e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 文件上传操作。
+     *
+     * @param fieldName  上传文件名。
+     * @param asImage    是否作为图片上传。如果是图片,今后下载的时候无需权限验证。否则就是附件上传,下载时需要权限验证。
+     * @param uploadFile 上传文件对象。
+     */
+    @SaCheckPermission("tourTourismProjectTravelNotes.view")
+    @OperationLog(type = SysOperationLogType.UPLOAD, saveResponse = false)
+    @PostMapping("/upload")
+    public void upload(
+            @RequestParam String fieldName,
+            @RequestParam Boolean asImage,
+            @RequestParam("uploadFile") MultipartFile uploadFile) throws IOException {
+        UploadStoreInfo storeInfo = MyModelUtil.getUploadStoreInfo(TourTourismProjectTravelNotes.class, fieldName);
+        // 这里就会判断参数中指定的字段,是否支持上传操作。
+        if (!storeInfo.isSupportUpload()) {
+            ResponseResult.output(HttpServletResponse.SC_FORBIDDEN,
+                    ResponseResult.error(ErrorCodeEnum.INVALID_UPLOAD_FIELD));
+            return;
+        }
+        // 根据字段注解中的存储类型,通过工厂方法获取匹配的上传下载实现类,从而解耦。
+        BaseUpDownloader upDownloader = upDownloaderFactory.get(storeInfo.getStoreType());
+        UploadResponseInfo responseInfo = upDownloader.doUpload(null,
+                appConfig.getUploadFileBaseDir(), TourTourismProjectTravelNotes.class.getSimpleName(), fieldName, asImage, uploadFile);
+        if (Boolean.TRUE.equals(responseInfo.getUploadFailed())) {
+            ResponseResult.output(HttpServletResponse.SC_FORBIDDEN,
+                    ResponseResult.error(ErrorCodeEnum.UPLOAD_FAILED, responseInfo.getErrorMessage()));
+            return;
+        }
+        cacheHelper.putSessionUploadFile(responseInfo.getFilename());
+        ResponseResult.output(ResponseResult.success(responseInfo));
+    }
+
+    private ResponseResult<Tuple2<TourTourismProjectTravelNotes, JSONObject>> doBusinessDataVerifyAndConvert(
+            TourTourismProjectTravelNotesDto tourTourismProjectTravelNotesDto,
+            boolean forUpdate,
+            TourTourismTravelNotesFileDto tourTourismTravelNotesFileDto,
+            TourTourismTravelNotesContentDto tourTourismTravelNotesContentDto) {
+        ErrorCodeEnum errorCode = ErrorCodeEnum.DATA_VALIDATED_FAILED;
+        String errorMessage = MyCommonUtil.getModelValidationError(tourTourismProjectTravelNotesDto, false);
+        if (errorMessage != null) {
+            return ResponseResult.error(errorCode, errorMessage);
+        }
+        errorMessage = MyCommonUtil.getModelValidationError(tourTourismTravelNotesFileDto);
+        if (errorMessage != null) {
+            return ResponseResult.error(errorCode, "参数 [tourTourismTravelNotesFileDto] " + errorMessage);
+        }
+        errorMessage = MyCommonUtil.getModelValidationError(tourTourismTravelNotesContentDto);
+        if (errorMessage != null) {
+            return ResponseResult.error(errorCode, "参数 [tourTourismTravelNotesContentDto] " + errorMessage);
+        }
+        // 全部关联从表数据的验证和转换
+        JSONObject relationData = new JSONObject();
+        CallResult verifyResult;
+        // 下面是输入参数中,主表关联数据的验证。
+        TourTourismProjectTravelNotes tourTourismProjectTravelNotes = MyModelUtil.copyTo(tourTourismProjectTravelNotesDto, TourTourismProjectTravelNotes.class);
+        TourTourismProjectTravelNotes originalData = null;
+        if (forUpdate && tourTourismProjectTravelNotes != null) {
+            originalData = tourTourismProjectTravelNotesService.getById(tourTourismProjectTravelNotes.getId());
+            if (originalData == null) {
+                return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
+            }
+            relationData.put("originalData", originalData);
+        }
+        verifyResult = tourTourismProjectTravelNotesService.verifyRelatedData(tourTourismProjectTravelNotes, originalData);
+        if (!verifyResult.isSuccess()) {
+            return ResponseResult.errorFrom(verifyResult);
+        }
+        // 处理主表的一对一关联 [TourTourismTravelNotesFile]
+        TourTourismTravelNotesFile tourTourismTravelNotesFile = MyModelUtil.copyTo(tourTourismTravelNotesFileDto, TourTourismTravelNotesFile.class);
+        relationData.put("tourTourismTravelNotesFile", tourTourismTravelNotesFile);
+        // 处理主表的一对一关联 [TourTourismTravelNotesContent]
+        TourTourismTravelNotesContent tourTourismTravelNotesContent = MyModelUtil.copyTo(tourTourismTravelNotesContentDto, TourTourismTravelNotesContent.class);
+        relationData.put("tourTourismTravelNotesContent", tourTourismTravelNotesContent);
+        return ResponseResult.success(new Tuple2<>(tourTourismProjectTravelNotes, relationData));
+    }
+
+    private ResponseResult<Void> doDelete(String id) {
+        String errorMessage;
+        // 验证关联Id的数据合法性
+        TourTourismProjectTravelNotes originalTourTourismProjectTravelNotes = tourTourismProjectTravelNotesService.getById(id);
+        if (originalTourTourismProjectTravelNotes == null) {
+            // NOTE: 修改下面方括号中的话述
+            errorMessage = "数据验证失败,当前 [对象] 并不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        if (!tourTourismProjectTravelNotesService.remove(id)) {
+            errorMessage = "数据操作失败,删除的对象不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        return ResponseResult.success();
+    }
+}

+ 179 - 0
application-webadmin/src/main/java/com/tourism/webadmin/back/controller/TourTourismTravelNotesContentController.java

@@ -0,0 +1,179 @@
+package com.tourism.webadmin.back.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import com.tourism.common.log.annotation.OperationLog;
+import com.tourism.common.log.model.constant.SysOperationLogType;
+import com.github.pagehelper.page.PageMethod;
+import com.tourism.webadmin.back.vo.*;
+import com.tourism.webadmin.back.dto.*;
+import com.tourism.webadmin.back.model.*;
+import com.tourism.webadmin.back.service.*;
+import com.tourism.common.core.object.*;
+import com.tourism.common.core.util.*;
+import com.tourism.common.core.constant.*;
+import com.tourism.common.core.annotation.MyRequestBody;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.*;
+
+/**
+ * 旅游项目游记富文本操作控制器类。
+ *
+ * @author 吃饭睡觉
+ * @date 2024-09-06
+ */
+@Tag(name = "旅游项目游记富文本管理接口")
+@Slf4j
+@RestController
+@RequestMapping("/admin/app/tourTourismTravelNotesContent")
+public class TourTourismTravelNotesContentController {
+
+    @Autowired
+    private TourTourismTravelNotesContentService tourTourismTravelNotesContentService;
+
+    /**
+     * 新增旅游项目游记富文本数据。
+     *
+     * @param tourTourismTravelNotesContentDto 新增对象。
+     * @return 应答结果对象,包含新增对象主键Id。
+     */
+    @ApiOperationSupport(ignoreParameters = {"tourTourismTravelNotesContentDto.id"})
+    @SaCheckPermission("tourTourismTravelNotesContent.add")
+    @OperationLog(type = SysOperationLogType.ADD)
+    @PostMapping("/add")
+    public ResponseResult<Long> add(@MyRequestBody TourTourismTravelNotesContentDto tourTourismTravelNotesContentDto) {
+        String errorMessage = MyCommonUtil.getModelValidationError(tourTourismTravelNotesContentDto, false);
+        if (errorMessage != null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
+        }
+        TourTourismTravelNotesContent tourTourismTravelNotesContent = MyModelUtil.copyTo(tourTourismTravelNotesContentDto, TourTourismTravelNotesContent.class);
+        tourTourismTravelNotesContent = tourTourismTravelNotesContentService.saveNew(tourTourismTravelNotesContent);
+        return ResponseResult.success(tourTourismTravelNotesContent.getId());
+    }
+
+    /**
+     * 更新旅游项目游记富文本数据。
+     *
+     * @param tourTourismTravelNotesContentDto 更新对象。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("tourTourismTravelNotesContent.update")
+    @OperationLog(type = SysOperationLogType.UPDATE)
+    @PostMapping("/update")
+    public ResponseResult<Void> update(@MyRequestBody TourTourismTravelNotesContentDto tourTourismTravelNotesContentDto) {
+        String errorMessage = MyCommonUtil.getModelValidationError(tourTourismTravelNotesContentDto, true);
+        if (errorMessage != null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
+        }
+        TourTourismTravelNotesContent tourTourismTravelNotesContent = MyModelUtil.copyTo(tourTourismTravelNotesContentDto, TourTourismTravelNotesContent.class);
+        TourTourismTravelNotesContent originalTourTourismTravelNotesContent = tourTourismTravelNotesContentService.getById(tourTourismTravelNotesContent.getId());
+        if (originalTourTourismTravelNotesContent == null) {
+            // NOTE: 修改下面方括号中的话述
+            errorMessage = "数据验证失败,当前 [数据] 并不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        if (!tourTourismTravelNotesContentService.update(tourTourismTravelNotesContent, originalTourTourismTravelNotesContent)) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
+        }
+        return ResponseResult.success();
+    }
+
+    /**
+     * 删除旅游项目游记富文本数据。
+     *
+     * @param id 删除对象主键Id。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("tourTourismTravelNotesContent.delete")
+    @OperationLog(type = SysOperationLogType.DELETE)
+    @PostMapping("/delete")
+    public ResponseResult<Void> delete(@MyRequestBody Long id) {
+        if (MyCommonUtil.existBlankArgument(id)) {
+            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
+        }
+        return this.doDelete(id);
+    }
+
+    /**
+     * 批量删除旅游项目游记富文本数据。
+     *
+     * @param idList 待删除对象的主键Id列表。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("tourTourismTravelNotesContent.delete")
+    @OperationLog(type = SysOperationLogType.DELETE_BATCH)
+    @PostMapping("/deleteBatch")
+    public ResponseResult<Void> deleteBatch(@MyRequestBody List<Long> idList) {
+        if (MyCommonUtil.existBlankArgument(idList)) {
+            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
+        }
+        for (Long id : idList) {
+            ResponseResult<Void> responseResult = this.doDelete(id);
+            if (!responseResult.isSuccess()) {
+                return responseResult;
+            }
+        }
+        return ResponseResult.success();
+    }
+
+    /**
+     * 列出符合过滤条件的旅游项目游记富文本列表。
+     *
+     * @param tourTourismTravelNotesContentDtoFilter 过滤对象。
+     * @param orderParam 排序参数。
+     * @param pageParam 分页参数。
+     * @return 应答结果对象,包含查询结果集。
+     */
+    @SaCheckPermission("tourTourismTravelNotesContent.view")
+    @PostMapping("/list")
+    public ResponseResult<MyPageData<TourTourismTravelNotesContentVo>> list(
+            @MyRequestBody TourTourismTravelNotesContentDto tourTourismTravelNotesContentDtoFilter,
+            @MyRequestBody MyOrderParam orderParam,
+            @MyRequestBody MyPageParam pageParam) {
+        if (pageParam != null) {
+            PageMethod.startPage(pageParam.getPageNum(), pageParam.getPageSize(), pageParam.getCount());
+        }
+        TourTourismTravelNotesContent tourTourismTravelNotesContentFilter = MyModelUtil.copyTo(tourTourismTravelNotesContentDtoFilter, TourTourismTravelNotesContent.class);
+        String orderBy = MyOrderParam.buildOrderBy(orderParam, TourTourismTravelNotesContent.class);
+        List<TourTourismTravelNotesContent> tourTourismTravelNotesContentList =
+                tourTourismTravelNotesContentService.getTourTourismTravelNotesContentListWithRelation(tourTourismTravelNotesContentFilter, orderBy);
+        return ResponseResult.success(MyPageUtil.makeResponseData(tourTourismTravelNotesContentList, TourTourismTravelNotesContentVo.class));
+    }
+
+    /**
+     * 查看指定旅游项目游记富文本对象详情。
+     *
+     * @param id 指定对象主键Id。
+     * @return 应答结果对象,包含对象详情。
+     */
+    @SaCheckPermission("tourTourismTravelNotesContent.view")
+    @GetMapping("/view")
+    public ResponseResult<TourTourismTravelNotesContentVo> view(@RequestParam Long id) {
+        TourTourismTravelNotesContent tourTourismTravelNotesContent = tourTourismTravelNotesContentService.getByIdWithRelation(id, MyRelationParam.full());
+        if (tourTourismTravelNotesContent == null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
+        }
+        TourTourismTravelNotesContentVo tourTourismTravelNotesContentVo = MyModelUtil.copyTo(tourTourismTravelNotesContent, TourTourismTravelNotesContentVo.class);
+        return ResponseResult.success(tourTourismTravelNotesContentVo);
+    }
+
+    private ResponseResult<Void> doDelete(Long id) {
+        String errorMessage;
+        // 验证关联Id的数据合法性
+        TourTourismTravelNotesContent originalTourTourismTravelNotesContent = tourTourismTravelNotesContentService.getById(id);
+        if (originalTourTourismTravelNotesContent == null) {
+            // NOTE: 修改下面方括号中的话述
+            errorMessage = "数据验证失败,当前 [对象] 并不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        if (!tourTourismTravelNotesContentService.remove(id)) {
+            errorMessage = "数据操作失败,删除的对象不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        return ResponseResult.success();
+    }
+}

+ 294 - 0
application-webadmin/src/main/java/com/tourism/webadmin/back/controller/TourTourismTravelNotesFileController.java

@@ -0,0 +1,294 @@
+package com.tourism.webadmin.back.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.dev33.satoken.annotation.SaIgnore;
+import cn.hutool.core.util.ReflectUtil;
+import com.tourism.common.core.upload.BaseUpDownloader;
+import com.tourism.common.core.upload.UpDownloaderFactory;
+import com.tourism.common.core.upload.UploadResponseInfo;
+import com.tourism.common.core.upload.UploadStoreInfo;
+import com.tourism.common.log.annotation.OperationLog;
+import com.tourism.common.log.model.constant.SysOperationLogType;
+import com.github.pagehelper.page.PageMethod;
+import com.tourism.webadmin.back.vo.*;
+import com.tourism.webadmin.back.dto.*;
+import com.tourism.webadmin.back.model.*;
+import com.tourism.webadmin.back.service.*;
+import com.tourism.common.core.object.*;
+import com.tourism.common.core.util.*;
+import com.tourism.common.core.constant.*;
+import com.tourism.common.core.annotation.MyRequestBody;
+import com.tourism.common.redis.cache.SessionCacheHelper;
+import com.tourism.common.additional.config.ApplicationConfig;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import jakarta.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * 旅游项目游记文件操作控制器类。
+ *
+ * @author 吃饭睡觉
+ * @date 2024-09-06
+ */
+@Tag(name = "旅游项目游记文件管理接口")
+@Slf4j
+@RestController
+@RequestMapping("/admin/app/tourTourismTravelNotesFile")
+public class TourTourismTravelNotesFileController {
+
+    @Autowired
+    private ApplicationConfig appConfig;
+    @Autowired
+    private SessionCacheHelper cacheHelper;
+    @Autowired
+    private UpDownloaderFactory upDownloaderFactory;
+    @Autowired
+    private TourTourismTravelNotesFileService tourTourismTravelNotesFileService;
+
+    /**
+     * 新增旅游项目游记文件数据。
+     *
+     * @param tourTourismTravelNotesFileDto 新增对象。
+     * @return 应答结果对象,包含新增对象主键Id。
+     */
+    @ApiOperationSupport(ignoreParameters = {"tourTourismTravelNotesFileDto.id"})
+    @SaCheckPermission("tourTourismTravelNotesFile.add")
+    @OperationLog(type = SysOperationLogType.ADD)
+    @PostMapping("/add")
+    public ResponseResult<Long> add(@MyRequestBody TourTourismTravelNotesFileDto tourTourismTravelNotesFileDto) {
+        String errorMessage = MyCommonUtil.getModelValidationError(tourTourismTravelNotesFileDto, false);
+        if (errorMessage != null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
+        }
+        TourTourismTravelNotesFile tourTourismTravelNotesFile = MyModelUtil.copyTo(tourTourismTravelNotesFileDto, TourTourismTravelNotesFile.class);
+        tourTourismTravelNotesFile = tourTourismTravelNotesFileService.saveNew(tourTourismTravelNotesFile);
+        return ResponseResult.success(tourTourismTravelNotesFile.getId());
+    }
+
+    /**
+     * 更新旅游项目游记文件数据。
+     *
+     * @param tourTourismTravelNotesFileDto 更新对象。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("tourTourismTravelNotesFile.update")
+    @OperationLog(type = SysOperationLogType.UPDATE)
+    @PostMapping("/update")
+    public ResponseResult<Void> update(@MyRequestBody TourTourismTravelNotesFileDto tourTourismTravelNotesFileDto) {
+        String errorMessage = MyCommonUtil.getModelValidationError(tourTourismTravelNotesFileDto, true);
+        if (errorMessage != null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
+        }
+        TourTourismTravelNotesFile tourTourismTravelNotesFile = MyModelUtil.copyTo(tourTourismTravelNotesFileDto, TourTourismTravelNotesFile.class);
+        TourTourismTravelNotesFile originalTourTourismTravelNotesFile = tourTourismTravelNotesFileService.getById(tourTourismTravelNotesFile.getId());
+        if (originalTourTourismTravelNotesFile == null) {
+            // NOTE: 修改下面方括号中的话述
+            errorMessage = "数据验证失败,当前 [数据] 并不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        if (!tourTourismTravelNotesFileService.update(tourTourismTravelNotesFile, originalTourTourismTravelNotesFile)) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
+        }
+        return ResponseResult.success();
+    }
+
+    /**
+     * 删除旅游项目游记文件数据。
+     *
+     * @param id 删除对象主键Id。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("tourTourismTravelNotesFile.delete")
+    @OperationLog(type = SysOperationLogType.DELETE)
+    @PostMapping("/delete")
+    public ResponseResult<Void> delete(@MyRequestBody Long id) {
+        if (MyCommonUtil.existBlankArgument(id)) {
+            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
+        }
+        return this.doDelete(id);
+    }
+
+    /**
+     * 批量删除旅游项目游记文件数据。
+     *
+     * @param idList 待删除对象的主键Id列表。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("tourTourismTravelNotesFile.delete")
+    @OperationLog(type = SysOperationLogType.DELETE_BATCH)
+    @PostMapping("/deleteBatch")
+    public ResponseResult<Void> deleteBatch(@MyRequestBody List<Long> idList) {
+        if (MyCommonUtil.existBlankArgument(idList)) {
+            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
+        }
+        for (Long id : idList) {
+            ResponseResult<Void> responseResult = this.doDelete(id);
+            if (!responseResult.isSuccess()) {
+                return responseResult;
+            }
+        }
+        return ResponseResult.success();
+    }
+
+    /**
+     * 列出符合过滤条件的旅游项目游记文件列表。
+     *
+     * @param tourTourismTravelNotesFileDtoFilter 过滤对象。
+     * @param orderParam 排序参数。
+     * @param pageParam 分页参数。
+     * @return 应答结果对象,包含查询结果集。
+     */
+    @SaCheckPermission("tourTourismTravelNotesFile.view")
+    @PostMapping("/list")
+    public ResponseResult<MyPageData<TourTourismTravelNotesFileVo>> list(
+            @MyRequestBody TourTourismTravelNotesFileDto tourTourismTravelNotesFileDtoFilter,
+            @MyRequestBody MyOrderParam orderParam,
+            @MyRequestBody MyPageParam pageParam) {
+        if (pageParam != null) {
+            PageMethod.startPage(pageParam.getPageNum(), pageParam.getPageSize(), pageParam.getCount());
+        }
+        TourTourismTravelNotesFile tourTourismTravelNotesFileFilter = MyModelUtil.copyTo(tourTourismTravelNotesFileDtoFilter, TourTourismTravelNotesFile.class);
+        String orderBy = MyOrderParam.buildOrderBy(orderParam, TourTourismTravelNotesFile.class);
+        List<TourTourismTravelNotesFile> tourTourismTravelNotesFileList =
+                tourTourismTravelNotesFileService.getTourTourismTravelNotesFileListWithRelation(tourTourismTravelNotesFileFilter, orderBy);
+        return ResponseResult.success(MyPageUtil.makeResponseData(tourTourismTravelNotesFileList, TourTourismTravelNotesFileVo.class));
+    }
+
+    /**
+     * 查看指定旅游项目游记文件对象详情。
+     *
+     * @param id 指定对象主键Id。
+     * @return 应答结果对象,包含对象详情。
+     */
+    @SaCheckPermission("tourTourismTravelNotesFile.view")
+    @GetMapping("/view")
+    public ResponseResult<TourTourismTravelNotesFileVo> view(@RequestParam Long id) {
+        TourTourismTravelNotesFile tourTourismTravelNotesFile = tourTourismTravelNotesFileService.getByIdWithRelation(id, MyRelationParam.full());
+        if (tourTourismTravelNotesFile == null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
+        }
+        TourTourismTravelNotesFileVo tourTourismTravelNotesFileVo = MyModelUtil.copyTo(tourTourismTravelNotesFile, TourTourismTravelNotesFileVo.class);
+        return ResponseResult.success(tourTourismTravelNotesFileVo);
+    }
+
+    /**
+     * 附件文件下载。
+     * 这里将图片和其他类型的附件文件放到不同的父目录下,主要为了便于今后图片文件的迁移。
+     *
+     * @param id 附件所在记录的主键Id。
+     * @param fieldName 附件所属的字段名。
+     * @param filename  文件名。如果没有提供该参数,就从当前记录的指定字段中读取。
+     * @param asImage   下载文件是否为图片。
+     * @param response  Http 应答对象。
+     */
+    @SaIgnore
+    @OperationLog(type = SysOperationLogType.DOWNLOAD, saveResponse = false)
+    @GetMapping("/download")
+    public void download(
+            @RequestParam(required = false) Long id,
+            @RequestParam String fieldName,
+            @RequestParam String filename,
+            @RequestParam Boolean asImage,
+            HttpServletResponse response) {
+        if (MyCommonUtil.existBlankArgument(fieldName, filename, asImage)) {
+            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+            return;
+        }
+        // 使用try来捕获异常,是为了保证一旦出现异常可以返回500的错误状态,便于调试。
+        // 否则有可能给前端返回的是200的错误码。
+        try {
+            // 如果请求参数中没有包含主键Id,就判断该文件是否为当前session上传的。
+            if (id == null) {
+                if (!cacheHelper.existSessionUploadFile(filename)) {
+                    ResponseResult.output(HttpServletResponse.SC_FORBIDDEN);
+                    return;
+                }
+            } else {
+                TourTourismTravelNotesFile tourTourismTravelNotesFile = tourTourismTravelNotesFileService.getById(id);
+                if (tourTourismTravelNotesFile == null) {
+                    ResponseResult.output(HttpServletResponse.SC_NOT_FOUND);
+                    return;
+                }
+                String fieldJsonData = (String) ReflectUtil.getFieldValue(tourTourismTravelNotesFile, fieldName);
+                if (fieldJsonData == null && !cacheHelper.existSessionUploadFile(filename)) {
+                    ResponseResult.output(HttpServletResponse.SC_BAD_REQUEST);
+                    return;
+                }
+                if (!BaseUpDownloader.containFile(fieldJsonData, filename)
+                        && !cacheHelper.existSessionUploadFile(filename)) {
+                    ResponseResult.output(HttpServletResponse.SC_FORBIDDEN);
+                    return;
+                }
+            }
+            UploadStoreInfo storeInfo = MyModelUtil.getUploadStoreInfo(TourTourismTravelNotesFile.class, fieldName);
+            if (!storeInfo.isSupportUpload()) {
+                ResponseResult.output(HttpServletResponse.SC_NOT_IMPLEMENTED,
+                        ResponseResult.error(ErrorCodeEnum.INVALID_UPLOAD_FIELD));
+                return;
+            }
+            BaseUpDownloader upDownloader = upDownloaderFactory.get(storeInfo.getStoreType());
+            upDownloader.doDownload(appConfig.getUploadFileBaseDir(),
+                    TourTourismTravelNotesFile.class.getSimpleName(), fieldName, filename, asImage, response);
+        } catch (Exception e) {
+            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+            log.error(e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 文件上传操作。
+     *
+     * @param fieldName  上传文件名。
+     * @param asImage    是否作为图片上传。如果是图片,今后下载的时候无需权限验证。否则就是附件上传,下载时需要权限验证。
+     * @param uploadFile 上传文件对象。
+     */
+    @SaCheckPermission("tourTourismTravelNotesFile.view")
+    @OperationLog(type = SysOperationLogType.UPLOAD, saveResponse = false)
+    @PostMapping("/upload")
+    public void upload(
+            @RequestParam String fieldName,
+            @RequestParam Boolean asImage,
+            @RequestParam("uploadFile") MultipartFile uploadFile) throws IOException {
+        UploadStoreInfo storeInfo = MyModelUtil.getUploadStoreInfo(TourTourismTravelNotesFile.class, fieldName);
+        // 这里就会判断参数中指定的字段,是否支持上传操作。
+        if (!storeInfo.isSupportUpload()) {
+            ResponseResult.output(HttpServletResponse.SC_FORBIDDEN,
+                    ResponseResult.error(ErrorCodeEnum.INVALID_UPLOAD_FIELD));
+            return;
+        }
+        // 根据字段注解中的存储类型,通过工厂方法获取匹配的上传下载实现类,从而解耦。
+        BaseUpDownloader upDownloader = upDownloaderFactory.get(storeInfo.getStoreType());
+        UploadResponseInfo responseInfo = upDownloader.doUpload(null,
+                appConfig.getUploadFileBaseDir(), TourTourismTravelNotesFile.class.getSimpleName(), fieldName, asImage, uploadFile);
+        if (Boolean.TRUE.equals(responseInfo.getUploadFailed())) {
+            ResponseResult.output(HttpServletResponse.SC_FORBIDDEN,
+                    ResponseResult.error(ErrorCodeEnum.UPLOAD_FAILED, responseInfo.getErrorMessage()));
+            return;
+        }
+        cacheHelper.putSessionUploadFile(responseInfo.getFilename());
+        ResponseResult.output(ResponseResult.success(responseInfo));
+    }
+
+    private ResponseResult<Void> doDelete(Long id) {
+        String errorMessage;
+        // 验证关联Id的数据合法性
+        TourTourismTravelNotesFile originalTourTourismTravelNotesFile = tourTourismTravelNotesFileService.getById(id);
+        if (originalTourTourismTravelNotesFile == null) {
+            // NOTE: 修改下面方括号中的话述
+            errorMessage = "数据验证失败,当前 [对象] 并不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        if (!tourTourismTravelNotesFileService.remove(id)) {
+            errorMessage = "数据操作失败,删除的对象不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        return ResponseResult.success();
+    }
+}

+ 179 - 0
application-webadmin/src/main/java/com/tourism/webadmin/back/controller/TourTravelNotesProjectRelationController.java

@@ -0,0 +1,179 @@
+package com.tourism.webadmin.back.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import com.tourism.common.log.annotation.OperationLog;
+import com.tourism.common.log.model.constant.SysOperationLogType;
+import com.github.pagehelper.page.PageMethod;
+import com.tourism.webadmin.back.vo.*;
+import com.tourism.webadmin.back.dto.*;
+import com.tourism.webadmin.back.model.*;
+import com.tourism.webadmin.back.service.*;
+import com.tourism.common.core.object.*;
+import com.tourism.common.core.util.*;
+import com.tourism.common.core.constant.*;
+import com.tourism.common.core.annotation.MyRequestBody;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.*;
+
+/**
+ * 游记与项目关联操作控制器类。
+ *
+ * @author 吃饭睡觉
+ * @date 2024-09-06
+ */
+@Tag(name = "游记与项目关联管理接口")
+@Slf4j
+@RestController
+@RequestMapping("/admin/app/tourTravelNotesProjectRelation")
+public class TourTravelNotesProjectRelationController {
+
+    @Autowired
+    private TourTravelNotesProjectRelationService tourTravelNotesProjectRelationService;
+
+    /**
+     * 新增游记与项目关联数据。
+     *
+     * @param tourTravelNotesProjectRelationDto 新增对象。
+     * @return 应答结果对象,包含新增对象主键Id。
+     */
+    @ApiOperationSupport(ignoreParameters = {"tourTravelNotesProjectRelationDto.id"})
+    @SaCheckPermission("tourTravelNotesProjectRelation.add")
+    @OperationLog(type = SysOperationLogType.ADD)
+    @PostMapping("/add")
+    public ResponseResult<String> add(@MyRequestBody TourTravelNotesProjectRelationDto tourTravelNotesProjectRelationDto) {
+        String errorMessage = MyCommonUtil.getModelValidationError(tourTravelNotesProjectRelationDto, false);
+        if (errorMessage != null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
+        }
+        TourTravelNotesProjectRelation tourTravelNotesProjectRelation = MyModelUtil.copyTo(tourTravelNotesProjectRelationDto, TourTravelNotesProjectRelation.class);
+        tourTravelNotesProjectRelation = tourTravelNotesProjectRelationService.saveNew(tourTravelNotesProjectRelation);
+        return ResponseResult.success(tourTravelNotesProjectRelation.getId());
+    }
+
+    /**
+     * 更新游记与项目关联数据。
+     *
+     * @param tourTravelNotesProjectRelationDto 更新对象。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("tourTravelNotesProjectRelation.update")
+    @OperationLog(type = SysOperationLogType.UPDATE)
+    @PostMapping("/update")
+    public ResponseResult<Void> update(@MyRequestBody TourTravelNotesProjectRelationDto tourTravelNotesProjectRelationDto) {
+        String errorMessage = MyCommonUtil.getModelValidationError(tourTravelNotesProjectRelationDto, true);
+        if (errorMessage != null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
+        }
+        TourTravelNotesProjectRelation tourTravelNotesProjectRelation = MyModelUtil.copyTo(tourTravelNotesProjectRelationDto, TourTravelNotesProjectRelation.class);
+        TourTravelNotesProjectRelation originalTourTravelNotesProjectRelation = tourTravelNotesProjectRelationService.getById(tourTravelNotesProjectRelation.getId());
+        if (originalTourTravelNotesProjectRelation == null) {
+            // NOTE: 修改下面方括号中的话述
+            errorMessage = "数据验证失败,当前 [数据] 并不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        if (!tourTravelNotesProjectRelationService.update(tourTravelNotesProjectRelation, originalTourTravelNotesProjectRelation)) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
+        }
+        return ResponseResult.success();
+    }
+
+    /**
+     * 删除游记与项目关联数据。
+     *
+     * @param id 删除对象主键Id。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("tourTravelNotesProjectRelation.delete")
+    @OperationLog(type = SysOperationLogType.DELETE)
+    @PostMapping("/delete")
+    public ResponseResult<Void> delete(@MyRequestBody Long id) {
+        if (MyCommonUtil.existBlankArgument(id)) {
+            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
+        }
+        return this.doDelete(id);
+    }
+
+    /**
+     * 批量删除游记与项目关联数据。
+     *
+     * @param idList 待删除对象的主键Id列表。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("tourTravelNotesProjectRelation.delete")
+    @OperationLog(type = SysOperationLogType.DELETE_BATCH)
+    @PostMapping("/deleteBatch")
+    public ResponseResult<Void> deleteBatch(@MyRequestBody List<Long> idList) {
+        if (MyCommonUtil.existBlankArgument(idList)) {
+            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
+        }
+        for (Long id : idList) {
+            ResponseResult<Void> responseResult = this.doDelete(id);
+            if (!responseResult.isSuccess()) {
+                return responseResult;
+            }
+        }
+        return ResponseResult.success();
+    }
+
+    /**
+     * 列出符合过滤条件的游记与项目关联列表。
+     *
+     * @param tourTravelNotesProjectRelationDtoFilter 过滤对象。
+     * @param orderParam 排序参数。
+     * @param pageParam 分页参数。
+     * @return 应答结果对象,包含查询结果集。
+     */
+    @SaCheckPermission("tourTravelNotesProjectRelation.view")
+    @PostMapping("/list")
+    public ResponseResult<MyPageData<TourTravelNotesProjectRelationVo>> list(
+            @MyRequestBody TourTravelNotesProjectRelationDto tourTravelNotesProjectRelationDtoFilter,
+            @MyRequestBody MyOrderParam orderParam,
+            @MyRequestBody MyPageParam pageParam) {
+        if (pageParam != null) {
+            PageMethod.startPage(pageParam.getPageNum(), pageParam.getPageSize(), pageParam.getCount());
+        }
+        TourTravelNotesProjectRelation tourTravelNotesProjectRelationFilter = MyModelUtil.copyTo(tourTravelNotesProjectRelationDtoFilter, TourTravelNotesProjectRelation.class);
+        String orderBy = MyOrderParam.buildOrderBy(orderParam, TourTravelNotesProjectRelation.class);
+        List<TourTravelNotesProjectRelation> tourTravelNotesProjectRelationList =
+                tourTravelNotesProjectRelationService.getTourTravelNotesProjectRelationListWithRelation(tourTravelNotesProjectRelationFilter, orderBy);
+        return ResponseResult.success(MyPageUtil.makeResponseData(tourTravelNotesProjectRelationList, TourTravelNotesProjectRelationVo.class));
+    }
+
+    /**
+     * 查看指定游记与项目关联对象详情。
+     *
+     * @param id 指定对象主键Id。
+     * @return 应答结果对象,包含对象详情。
+     */
+    @SaCheckPermission("tourTravelNotesProjectRelation.view")
+    @GetMapping("/view")
+    public ResponseResult<TourTravelNotesProjectRelationVo> view(@RequestParam String id) {
+        TourTravelNotesProjectRelation tourTravelNotesProjectRelation = tourTravelNotesProjectRelationService.getByIdWithRelation(id, MyRelationParam.full());
+        if (tourTravelNotesProjectRelation == null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
+        }
+        TourTravelNotesProjectRelationVo tourTravelNotesProjectRelationVo = MyModelUtil.copyTo(tourTravelNotesProjectRelation, TourTravelNotesProjectRelationVo.class);
+        return ResponseResult.success(tourTravelNotesProjectRelationVo);
+    }
+
+    private ResponseResult<Void> doDelete(Long id) {
+        String errorMessage;
+        // 验证关联Id的数据合法性
+        TourTravelNotesProjectRelation originalTourTravelNotesProjectRelation = tourTravelNotesProjectRelationService.getById(id);
+        if (originalTourTravelNotesProjectRelation == null) {
+            // NOTE: 修改下面方括号中的话述
+            errorMessage = "数据验证失败,当前 [对象] 并不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        if (!tourTravelNotesProjectRelationService.remove(id)) {
+            errorMessage = "数据操作失败,删除的对象不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        return ResponseResult.success();
+    }
+}

+ 368 - 0
application-webadmin/src/main/java/com/tourism/webadmin/back/controller/TourUserController.java

@@ -0,0 +1,368 @@
+package com.tourism.webadmin.back.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.hutool.core.util.ReflectUtil;
+import com.github.pagehelper.page.PageMethod;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import com.tourism.common.core.annotation.MyRequestBody;
+import com.tourism.common.core.constant.ErrorCodeEnum;
+import com.tourism.common.core.object.*;
+import com.tourism.common.core.upload.BaseUpDownloader;
+import com.tourism.common.core.upload.UpDownloaderFactory;
+import com.tourism.common.core.upload.UploadResponseInfo;
+import com.tourism.common.core.upload.UploadStoreInfo;
+import com.tourism.common.core.util.*;
+import com.tourism.common.log.annotation.OperationLog;
+import com.tourism.common.log.model.constant.SysOperationLogType;
+import com.tourism.common.redis.cache.SessionCacheHelper;
+import com.tourism.webadmin.back.dto.TourUserDto;
+import com.tourism.webadmin.back.model.TourUser;
+import com.tourism.webadmin.back.service.TourUserService;
+import com.tourism.webadmin.back.vo.TourUserVo;
+import com.tourism.common.additional.config.ApplicationConfig;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * 门户网站用户管理操作控制器类。
+ *
+ * @author 吃饭睡觉
+ * @date 2024-09-06
+ */
+@Tag(name = "门户用户管理管理接口")
+@Slf4j
+@RestController
+@RequestMapping("/website/tour/user")
+public class TourUserController {
+
+    @Autowired
+    private ApplicationConfig appConfig;
+    @Autowired
+    private SessionCacheHelper cacheHelper;
+    @Autowired
+    private UpDownloaderFactory upDownloaderFactory;
+    @Autowired
+    private TourUserService tourUserService;
+
+    /**
+     * 新增门户用户管理数据。
+     *
+     * @param tourUserDto 新增对象。
+     * @return 应答结果对象,包含新增对象主键Id。
+     */
+    @ApiOperationSupport(ignoreParameters = {"tourUserDto.userId"})
+    @SaCheckPermission("tourUser.add")
+    @OperationLog(type = SysOperationLogType.ADD)
+    @PostMapping("/add")
+    public ResponseResult<Long> add(@MyRequestBody TourUserDto tourUserDto) {
+        String errorMessage = MyCommonUtil.getModelValidationError(tourUserDto, false);
+        if (errorMessage != null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
+        }
+        TourUser tourUser = MyModelUtil.copyTo(tourUserDto, TourUser.class);
+        tourUser = tourUserService.saveNew(tourUser);
+        return ResponseResult.success(tourUser.getUserId());
+    }
+
+    /**
+     * 更新门户用户管理数据。
+     *
+     * @param tourUserDto 更新对象。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("tourUser.update")
+    @OperationLog(type = SysOperationLogType.UPDATE)
+    @PostMapping("/update")
+    public ResponseResult<Void> update(@MyRequestBody TourUserDto tourUserDto) {
+        String errorMessage = MyCommonUtil.getModelValidationError(tourUserDto, true);
+        if (errorMessage != null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
+        }
+        TourUser tourUser = MyModelUtil.copyTo(tourUserDto, TourUser.class);
+        TourUser originalTourUser = tourUserService.getById(tourUser.getUserId());
+        if (originalTourUser == null) {
+            // NOTE: 修改下面方括号中的话述
+            errorMessage = "数据验证失败,当前 [数据] 并不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        if (!tourUserService.update(tourUser, originalTourUser)) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
+        }
+        return ResponseResult.success();
+    }
+
+    /**
+     * 删除门户用户管理数据。
+     *
+     * @param userId 删除对象主键Id。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("tourUser.delete")
+    @OperationLog(type = SysOperationLogType.DELETE)
+    @PostMapping("/delete")
+    public ResponseResult<Void> delete(@MyRequestBody Long userId) {
+        if (MyCommonUtil.existBlankArgument(userId)) {
+            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
+        }
+        return this.doDelete(userId);
+    }
+
+    /**
+     * 批量删除门户用户管理数据。
+     *
+     * @param userIdList 待删除对象的主键Id列表。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("tourUser.delete")
+    @OperationLog(type = SysOperationLogType.DELETE_BATCH)
+    @PostMapping("/deleteBatch")
+    public ResponseResult<Void> deleteBatch(@MyRequestBody List<Long> userIdList) {
+        if (MyCommonUtil.existBlankArgument(userIdList)) {
+            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
+        }
+        for (Long userId : userIdList) {
+            ResponseResult<Void> responseResult = this.doDelete(userId);
+            if (!responseResult.isSuccess()) {
+                return responseResult;
+            }
+        }
+        return ResponseResult.success();
+    }
+
+    /**
+     * 列出符合过滤条件的门户用户管理列表。
+     *
+     * @param tourUserDtoFilter 过滤对象。
+     * @param orderParam 排序参数。
+     * @param pageParam 分页参数。
+     * @return 应答结果对象,包含查询结果集。
+     */
+    @SaCheckPermission("tourUser.view")
+    @PostMapping("/list")
+    public ResponseResult<MyPageData<TourUserVo>> list(
+            @MyRequestBody TourUserDto tourUserDtoFilter,
+            @MyRequestBody MyOrderParam orderParam,
+            @MyRequestBody MyPageParam pageParam) {
+        if (pageParam != null) {
+            PageMethod.startPage(pageParam.getPageNum(), pageParam.getPageSize(), pageParam.getCount());
+        }
+        TourUser tourUserFilter = MyModelUtil.copyTo(tourUserDtoFilter, TourUser.class);
+        String orderBy = MyOrderParam.buildOrderBy(orderParam, TourUser.class);
+        List<TourUser> tourUserList = tourUserService.getTourUserListWithRelation(tourUserFilter, orderBy);
+        return ResponseResult.success(MyPageUtil.makeResponseData(tourUserList, TourUserVo.class));
+    }
+
+    /**
+     * 导入主表数据列表。
+     *
+     * @param importFile 上传的文件,目前仅仅支持xlsx和xls两种格式。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("tourUser.import")
+    @OperationLog(type = SysOperationLogType.IMPORT)
+    @PostMapping("/import")
+    public ResponseResult<Void> importBatch(
+            @RequestParam Boolean skipHeader,
+            @RequestParam("importFile") MultipartFile importFile) throws IOException {
+        String filename = ImportUtil.saveImportFile(appConfig.getUploadFileBaseDir(), null, importFile);
+        // 这里可以指定需要忽略导入的字段集合。如创建时间、创建人、更新时间、更新人、主键Id和逻辑删除,
+        // 以及一些存在缺省值且无需导入的字段。其中主键字段和逻辑删除字段不需要在这里设置,批量插入逻辑会自动处理的。
+        Set<String> ignoreFieldSet = new HashSet<>();
+        ignoreFieldSet.add("createUserId");
+        ignoreFieldSet.add("createTime");
+        ignoreFieldSet.add("updateUserId");
+        ignoreFieldSet.add("updateTime");
+        List<ImportUtil.ImportHeaderInfo> headerInfoList = ImportUtil.makeHeaderInfoList(TourUser.class, ignoreFieldSet);
+        // 下面是导入时需要注意的地方,如果我们缺省生成的代码,与实际情况存在差异,请手动修改。
+        // 1. 头信息数据字段,我们只是根据当前的主表实体对象生成了缺省数组,开发者可根据实际情况,对headerInfoList进行修改。
+        ImportUtil.ImportHeaderInfo[] headerInfos = headerInfoList.toArray(new ImportUtil.ImportHeaderInfo[]{});
+        // 2. 这里需要根据实际情况决定,导入文件中第一行是否为中文头信息,如果是可以跳过。这里我们默认为true。
+        // 这里根据自己的实际需求,为doImport的最后一个参数,传递需要进行字典转换的字段集合。
+        // 注意,集合中包含需要翻译的Java字段名,如: gradeId。
+        Set<String> translatedDictFieldSet = new HashSet<>();
+        List<TourUser> dataList =
+                ImportUtil.doImport(headerInfos, skipHeader, filename, TourUser.class, translatedDictFieldSet);
+        CallResult result = tourUserService.verifyImportList(dataList, translatedDictFieldSet);
+        if (!result.isSuccess()) {
+            // result中返回了具体的验证失败对象,如果需要返回更加详细的错误,可根据实际情况手动修改。
+            return ResponseResult.errorFrom(result);
+        }
+        tourUserService.saveNewBatch(dataList, -1);
+        return ResponseResult.success();
+    }
+
+    /**
+     * 导出符合过滤条件的门户用户管理列表。
+     *
+     * @param tourUserDtoFilter 过滤对象。
+     * @param orderParam 排序参数。
+     * @throws IOException 文件读写失败。
+     */
+    @SaCheckPermission("tourUser.export")
+    @OperationLog(type = SysOperationLogType.EXPORT, saveResponse = false)
+    @PostMapping("/export")
+    public void export(
+            @MyRequestBody TourUserDto tourUserDtoFilter,
+            @MyRequestBody MyOrderParam orderParam) throws IOException {
+        TourUser tourUserFilter = MyModelUtil.copyTo(tourUserDtoFilter, TourUser.class);
+        String orderBy = MyOrderParam.buildOrderBy(orderParam, TourUser.class);
+        List<TourUser> resultList =
+                tourUserService.getTourUserListWithRelation(tourUserFilter, orderBy);
+        // 导出文件的标题数组
+        // NOTE: 下面的代码中仅仅导出了主表数据,主表聚合计算数据和主表关联字典的数据。
+        // 一对一从表数据的导出,可根据需要自行添加。如:headerMap.put("slaveFieldName.xxxField", "标题名称")
+        Map<String, String> headerMap = new LinkedHashMap<>(13);
+        headerMap.put("userId", "主键Id");
+        headerMap.put("loginName", "用户登录名称");
+        headerMap.put("password", "密码");
+        headerMap.put("showName", "用户显示名称");
+        headerMap.put("headImageUrl", "用户头像的Url");
+        headerMap.put("userStatusDictMap.name", "状态(0: 正常 1: 锁定)");
+        headerMap.put("email", "用户邮箱");
+        headerMap.put("mobile", "用户手机");
+        headerMap.put("createUserId", "创建者Id");
+        headerMap.put("createTime", "创建时间");
+        headerMap.put("updateUserId", "更新者Id");
+        headerMap.put("updateTime", "最后更新时间");
+        headerMap.put("deletedFlag", "删除标记(1: 正常 -1: 已删除)");
+        ExportUtil.doExport(resultList, headerMap, "tourUser.xlsx");
+    }
+
+    /**
+     * 查看指定门户用户管理对象详情。
+     *
+     * @param userId 指定对象主键Id。
+     * @return 应答结果对象,包含对象详情。
+     */
+    @SaCheckPermission("tourUser.view")
+    @GetMapping("/view")
+    public ResponseResult<TourUserVo> view(@RequestParam Long userId) {
+        TourUser tourUser = tourUserService.getByIdWithRelation(userId, MyRelationParam.full());
+        if (tourUser == null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
+        }
+        TourUserVo tourUserVo = MyModelUtil.copyTo(tourUser, TourUserVo.class);
+        return ResponseResult.success(tourUserVo);
+    }
+
+    /**
+     * 附件文件下载。
+     * 这里将图片和其他类型的附件文件放到不同的父目录下,主要为了便于今后图片文件的迁移。
+     *
+     * @param userId 附件所在记录的主键Id。
+     * @param fieldName 附件所属的字段名。
+     * @param filename  文件名。如果没有提供该参数,就从当前记录的指定字段中读取。
+     * @param asImage   下载文件是否为图片。
+     * @param response  Http 应答对象。
+     */
+    @SaCheckPermission("tourUser.view")
+    @OperationLog(type = SysOperationLogType.DOWNLOAD, saveResponse = false)
+    @GetMapping("/download")
+    public void download(
+            @RequestParam(required = false) Long userId,
+            @RequestParam String fieldName,
+            @RequestParam String filename,
+            @RequestParam Boolean asImage,
+            HttpServletResponse response) {
+        if (MyCommonUtil.existBlankArgument(fieldName, filename, asImage)) {
+            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+            return;
+        }
+        // 使用try来捕获异常,是为了保证一旦出现异常可以返回500的错误状态,便于调试。
+        // 否则有可能给前端返回的是200的错误码。
+        try {
+            // 如果请求参数中没有包含主键Id,就判断该文件是否为当前session上传的。
+            if (userId == null) {
+                if (!cacheHelper.existSessionUploadFile(filename)) {
+                    ResponseResult.output(HttpServletResponse.SC_FORBIDDEN);
+                    return;
+                }
+            } else {
+                TourUser tourUser = tourUserService.getById(userId);
+                if (tourUser == null) {
+                    ResponseResult.output(HttpServletResponse.SC_NOT_FOUND);
+                    return;
+                }
+                String fieldJsonData = (String) ReflectUtil.getFieldValue(tourUser, fieldName);
+                if (fieldJsonData == null && !cacheHelper.existSessionUploadFile(filename)) {
+                    ResponseResult.output(HttpServletResponse.SC_BAD_REQUEST);
+                    return;
+                }
+                if (!BaseUpDownloader.containFile(fieldJsonData, filename)
+                        && !cacheHelper.existSessionUploadFile(filename)) {
+                    ResponseResult.output(HttpServletResponse.SC_FORBIDDEN);
+                    return;
+                }
+            }
+            UploadStoreInfo storeInfo = MyModelUtil.getUploadStoreInfo(TourUser.class, fieldName);
+            if (!storeInfo.isSupportUpload()) {
+                ResponseResult.output(HttpServletResponse.SC_NOT_IMPLEMENTED,
+                        ResponseResult.error(ErrorCodeEnum.INVALID_UPLOAD_FIELD));
+                return;
+            }
+            BaseUpDownloader upDownloader = upDownloaderFactory.get(storeInfo.getStoreType());
+            upDownloader.doDownload(appConfig.getUploadFileBaseDir(),
+                    TourUser.class.getSimpleName(), fieldName, filename, asImage, response);
+        } catch (Exception e) {
+            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+            log.error(e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 文件上传操作。
+     *
+     * @param fieldName  上传文件名。
+     * @param asImage    是否作为图片上传。如果是图片,今后下载的时候无需权限验证。否则就是附件上传,下载时需要权限验证。
+     * @param uploadFile 上传文件对象。
+     */
+    @SaCheckPermission("tourUser.view")
+    @OperationLog(type = SysOperationLogType.UPLOAD, saveResponse = false)
+    @PostMapping("/upload")
+    public void upload(
+            @RequestParam String fieldName,
+            @RequestParam Boolean asImage,
+            @RequestParam("uploadFile") MultipartFile uploadFile) throws IOException {
+        UploadStoreInfo storeInfo = MyModelUtil.getUploadStoreInfo(TourUser.class, fieldName);
+        // 这里就会判断参数中指定的字段,是否支持上传操作。
+        if (!storeInfo.isSupportUpload()) {
+            ResponseResult.output(HttpServletResponse.SC_FORBIDDEN,
+                    ResponseResult.error(ErrorCodeEnum.INVALID_UPLOAD_FIELD));
+            return;
+        }
+        // 根据字段注解中的存储类型,通过工厂方法获取匹配的上传下载实现类,从而解耦。
+        BaseUpDownloader upDownloader = upDownloaderFactory.get(storeInfo.getStoreType());
+        UploadResponseInfo responseInfo = upDownloader.doUpload(null,
+                appConfig.getUploadFileBaseDir(), TourUser.class.getSimpleName(), fieldName, asImage, uploadFile);
+        if (Boolean.TRUE.equals(responseInfo.getUploadFailed())) {
+            ResponseResult.output(HttpServletResponse.SC_FORBIDDEN,
+                    ResponseResult.error(ErrorCodeEnum.UPLOAD_FAILED, responseInfo.getErrorMessage()));
+            return;
+        }
+        cacheHelper.putSessionUploadFile(responseInfo.getFilename());
+        ResponseResult.output(ResponseResult.success(responseInfo));
+    }
+
+    private ResponseResult<Void> doDelete(Long userId) {
+        String errorMessage;
+        // 验证关联Id的数据合法性
+        TourUser originalTourUser = tourUserService.getById(userId);
+        if (originalTourUser == null) {
+            // NOTE: 修改下面方括号中的话述
+            errorMessage = "数据验证失败,当前 [对象] 并不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        if (!tourUserService.remove(userId)) {
+            errorMessage = "数据操作失败,删除的对象不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        return ResponseResult.success();
+    }
+}

+ 179 - 0
application-webadmin/src/main/java/com/tourism/webadmin/back/controller/TourUserLikeTravelNotesController.java

@@ -0,0 +1,179 @@
+package com.tourism.webadmin.back.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import com.tourism.common.log.annotation.OperationLog;
+import com.tourism.common.log.model.constant.SysOperationLogType;
+import com.github.pagehelper.page.PageMethod;
+import com.tourism.webadmin.back.vo.*;
+import com.tourism.webadmin.back.dto.*;
+import com.tourism.webadmin.back.model.*;
+import com.tourism.webadmin.back.service.*;
+import com.tourism.common.core.object.*;
+import com.tourism.common.core.util.*;
+import com.tourism.common.core.constant.*;
+import com.tourism.common.core.annotation.MyRequestBody;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.*;
+
+/**
+ * 用户游记点赞表操作控制器类。
+ *
+ * @author 吃饭睡觉
+ * @date 2024-09-06
+ */
+@Tag(name = "用户游记点赞表管理接口")
+@Slf4j
+@RestController
+@RequestMapping("/admin/app/tourUserLikeTravelNotes")
+public class TourUserLikeTravelNotesController {
+
+    @Autowired
+    private TourUserLikeTravelNotesService tourUserLikeTravelNotesService;
+
+//    /**
+//     * 新增用户游记点赞表数据。
+//     *
+//     * @param tourUserLikeTravelNotesDto 新增对象。
+//     * @return 应答结果对象,包含新增对象主键Id。
+//     */
+//    @ApiOperationSupport(ignoreParameters = {"tourUserLikeTravelNotesDto.id"})
+////    @SaCheckPermission("tourUserLikeTravelNotes.add")
+//    @OperationLog(type = SysOperationLogType.ADD)
+//    @PostMapping("/add")
+//    public ResponseResult<Long> add(@MyRequestBody TourUserLikeTravelNotesDto tourUserLikeTravelNotesDto) {
+//        String errorMessage = MyCommonUtil.getModelValidationError(tourUserLikeTravelNotesDto, false);
+//        if (errorMessage != null) {
+//            return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
+//        }
+//        TourUserLikeTravelNotes tourUserLikeTravelNotes = MyModelUtil.copyTo(tourUserLikeTravelNotesDto, TourUserLikeTravelNotes.class);
+//        tourUserLikeTravelNotes = tourUserLikeTravelNotesService.saveNew(tourUserLikeTravelNotes);
+//        return ResponseResult.success(tourUserLikeTravelNotes.getId());
+//    }
+//
+//    /**
+//     * 更新用户游记点赞表数据。
+//     *
+//     * @param tourUserLikeTravelNotesDto 更新对象。
+//     * @return 应答结果对象。
+//     */
+//    @SaCheckPermission("tourUserLikeTravelNotes.update")
+//    @OperationLog(type = SysOperationLogType.UPDATE)
+//    @PostMapping("/update")
+//    public ResponseResult<Void> update(@MyRequestBody TourUserLikeTravelNotesDto tourUserLikeTravelNotesDto) {
+//        String errorMessage = MyCommonUtil.getModelValidationError(tourUserLikeTravelNotesDto, true);
+//        if (errorMessage != null) {
+//            return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
+//        }
+//        TourUserLikeTravelNotes tourUserLikeTravelNotes = MyModelUtil.copyTo(tourUserLikeTravelNotesDto, TourUserLikeTravelNotes.class);
+//        TourUserLikeTravelNotes originalTourUserLikeTravelNotes = tourUserLikeTravelNotesService.getById(tourUserLikeTravelNotes.getId());
+//        if (originalTourUserLikeTravelNotes == null) {
+//            // NOTE: 修改下面方括号中的话述
+//            errorMessage = "数据验证失败,当前 [数据] 并不存在,请刷新后重试!";
+//            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+//        }
+//        if (!tourUserLikeTravelNotesService.update(tourUserLikeTravelNotes, originalTourUserLikeTravelNotes)) {
+//            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
+//        }
+//        return ResponseResult.success();
+//    }
+
+    /**
+     * 删除用户游记点赞表数据。
+     *
+     * @param id 删除对象主键Id。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("tourUserLikeTravelNotes.delete")
+    @OperationLog(type = SysOperationLogType.DELETE)
+    @PostMapping("/delete")
+    public ResponseResult<Void> delete(@MyRequestBody Long id) {
+        if (MyCommonUtil.existBlankArgument(id)) {
+            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
+        }
+        return this.doDelete(id);
+    }
+
+    /**
+     * 批量删除用户游记点赞表数据。
+     *
+     * @param idList 待删除对象的主键Id列表。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("tourUserLikeTravelNotes.delete")
+    @OperationLog(type = SysOperationLogType.DELETE_BATCH)
+    @PostMapping("/deleteBatch")
+    public ResponseResult<Void> deleteBatch(@MyRequestBody List<Long> idList) {
+        if (MyCommonUtil.existBlankArgument(idList)) {
+            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
+        }
+        for (Long id : idList) {
+            ResponseResult<Void> responseResult = this.doDelete(id);
+            if (!responseResult.isSuccess()) {
+                return responseResult;
+            }
+        }
+        return ResponseResult.success();
+    }
+
+    /**
+     * 列出符合过滤条件的用户游记点赞表列表。
+     *
+     * @param tourUserLikeTravelNotesDtoFilter 过滤对象。
+     * @param orderParam 排序参数。
+     * @param pageParam 分页参数。
+     * @return 应答结果对象,包含查询结果集。
+     */
+    @SaCheckPermission("tourUserLikeTravelNotes.view")
+    @PostMapping("/list")
+    public ResponseResult<MyPageData<TourUserLikeTravelNotesVo>> list(
+            @MyRequestBody TourUserLikeTravelNotesDto tourUserLikeTravelNotesDtoFilter,
+            @MyRequestBody MyOrderParam orderParam,
+            @MyRequestBody MyPageParam pageParam) {
+        if (pageParam != null) {
+            PageMethod.startPage(pageParam.getPageNum(), pageParam.getPageSize(), pageParam.getCount());
+        }
+        TourUserLikeTravelNotes tourUserLikeTravelNotesFilter = MyModelUtil.copyTo(tourUserLikeTravelNotesDtoFilter, TourUserLikeTravelNotes.class);
+        String orderBy = MyOrderParam.buildOrderBy(orderParam, TourUserLikeTravelNotes.class);
+        List<TourUserLikeTravelNotes> tourUserLikeTravelNotesList =
+                tourUserLikeTravelNotesService.getTourUserLikeTravelNotesListWithRelation(tourUserLikeTravelNotesFilter, orderBy);
+        return ResponseResult.success(MyPageUtil.makeResponseData(tourUserLikeTravelNotesList, TourUserLikeTravelNotesVo.class));
+    }
+
+    /**
+     * 查看指定用户游记点赞表对象详情。
+     *
+     * @param id 指定对象主键Id。
+     * @return 应答结果对象,包含对象详情。
+     */
+    @SaCheckPermission("tourUserLikeTravelNotes.view")
+    @GetMapping("/view")
+    public ResponseResult<TourUserLikeTravelNotesVo> view(@RequestParam Long id) {
+        TourUserLikeTravelNotes tourUserLikeTravelNotes = tourUserLikeTravelNotesService.getByIdWithRelation(id, MyRelationParam.full());
+        if (tourUserLikeTravelNotes == null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
+        }
+        TourUserLikeTravelNotesVo tourUserLikeTravelNotesVo = MyModelUtil.copyTo(tourUserLikeTravelNotes, TourUserLikeTravelNotesVo.class);
+        return ResponseResult.success(tourUserLikeTravelNotesVo);
+    }
+
+    private ResponseResult<Void> doDelete(Long id) {
+        String errorMessage;
+        // 验证关联Id的数据合法性
+        TourUserLikeTravelNotes originalTourUserLikeTravelNotes = tourUserLikeTravelNotesService.getById(id);
+        if (originalTourUserLikeTravelNotes == null) {
+            // NOTE: 修改下面方括号中的话述
+            errorMessage = "数据验证失败,当前 [对象] 并不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        if (!tourUserLikeTravelNotesService.remove(id)) {
+            errorMessage = "数据操作失败,删除的对象不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        return ResponseResult.success();
+    }
+}

+ 2 - 2
application-webadmin/src/main/java/com/tourism/webadmin/back/controller/TourismContentController.java

@@ -45,7 +45,7 @@ public class TourismContentController {
     @SaCheckPermission("tourismContent.add")
     @OperationLog(type = SysOperationLogType.ADD)
     @PostMapping("/add")
-    public ResponseResult<Long> add(@MyRequestBody TourismContentDto tourismContentDto) {
+    public ResponseResult<String> add(@MyRequestBody TourismContentDto tourismContentDto) {
         String errorMessage = MyCommonUtil.getModelValidationError(tourismContentDto, false);
         if (errorMessage != null) {
             return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
@@ -152,7 +152,7 @@ public class TourismContentController {
      */
     @SaCheckPermission("tourismContent.view")
     @GetMapping("/view")
-    public ResponseResult<TourismContentVo> view(@RequestParam Long id) {
+    public ResponseResult<TourismContentVo> view(@RequestParam String id) {
         TourismContent tourismContent = tourismContentService.getByIdWithRelation(id, MyRelationParam.full());
         if (tourismContent == null) {
             return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);

+ 195 - 0
application-webadmin/src/main/java/com/tourism/webadmin/back/controller/TourismDatePriceController.java

@@ -0,0 +1,195 @@
+package com.tourism.webadmin.back.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import com.tourism.common.log.annotation.OperationLog;
+import com.tourism.common.log.model.constant.SysOperationLogType;
+import com.github.pagehelper.page.PageMethod;
+import com.tourism.webadmin.back.vo.*;
+import com.tourism.webadmin.back.dto.*;
+import com.tourism.webadmin.back.model.*;
+import com.tourism.webadmin.back.service.*;
+import com.tourism.common.core.object.*;
+import com.tourism.common.core.util.*;
+import com.tourism.common.core.constant.*;
+import com.tourism.common.core.annotation.MyRequestBody;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.*;
+
+/**
+ * 旅游项目日历价格操作控制器类。
+ *
+ * @author 吃饭睡觉
+ * @date 2024-09-06
+ */
+@Tag(name = "旅游项目日历价格管理接口")
+@Slf4j
+@RestController
+@RequestMapping("/admin/app/tourismDatePrice")
+public class TourismDatePriceController {
+
+    @Autowired
+    private TourismDatePriceService tourismDatePriceService;
+
+    /**
+     * 新增旅游项目日历价格数据。
+     *
+     * @param tourismDatePriceDto 新增对象。
+     * @return 应答结果对象,包含新增对象主键Id。
+     */
+    @ApiOperationSupport(ignoreParameters = {
+            "tourismDatePriceDto.id",
+            "tourismDatePriceDto.departureDateStart",
+            "tourismDatePriceDto.departureDateEnd"})
+    @SaCheckPermission("tourismDatePrice.add")
+    @OperationLog(type = SysOperationLogType.ADD)
+    @PostMapping("/add")
+    public ResponseResult<String> add(@MyRequestBody TourismDatePriceDto tourismDatePriceDto) {
+        String errorMessage = MyCommonUtil.getModelValidationError(tourismDatePriceDto, false);
+        if (errorMessage != null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
+        }
+        TourismDatePrice tourismDatePrice = MyModelUtil.copyTo(tourismDatePriceDto, TourismDatePrice.class);
+        // 验证关联Id的数据合法性
+        CallResult callResult = tourismDatePriceService.verifyRelatedData(tourismDatePrice, null);
+        if (!callResult.isSuccess()) {
+            return ResponseResult.errorFrom(callResult);
+        }
+        tourismDatePrice = tourismDatePriceService.saveNew(tourismDatePrice);
+        return ResponseResult.success(tourismDatePrice.getId());
+    }
+
+    /**
+     * 更新旅游项目日历价格数据。
+     *
+     * @param tourismDatePriceDto 更新对象。
+     * @return 应答结果对象。
+     */
+    @ApiOperationSupport(ignoreParameters = {
+            "tourismDatePriceDto.departureDateStart",
+            "tourismDatePriceDto.departureDateEnd"})
+    @SaCheckPermission("tourismDatePrice.update")
+    @OperationLog(type = SysOperationLogType.UPDATE)
+    @PostMapping("/update")
+    public ResponseResult<Void> update(@MyRequestBody TourismDatePriceDto tourismDatePriceDto) {
+        String errorMessage = MyCommonUtil.getModelValidationError(tourismDatePriceDto, true);
+        if (errorMessage != null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
+        }
+        TourismDatePrice tourismDatePrice = MyModelUtil.copyTo(tourismDatePriceDto, TourismDatePrice.class);
+        TourismDatePrice originalTourismDatePrice = tourismDatePriceService.getById(tourismDatePrice.getId());
+        if (originalTourismDatePrice == null) {
+            // NOTE: 修改下面方括号中的话述
+            errorMessage = "数据验证失败,当前 [数据] 并不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        // 验证关联Id的数据合法性
+        CallResult callResult = tourismDatePriceService.verifyRelatedData(tourismDatePrice, originalTourismDatePrice);
+        if (!callResult.isSuccess()) {
+            return ResponseResult.errorFrom(callResult);
+        }
+        if (!tourismDatePriceService.update(tourismDatePrice, originalTourismDatePrice)) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
+        }
+        return ResponseResult.success();
+    }
+
+    /**
+     * 删除旅游项目日历价格数据。
+     *
+     * @param id 删除对象主键Id。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("tourismDatePrice.delete")
+    @OperationLog(type = SysOperationLogType.DELETE)
+    @PostMapping("/delete")
+    public ResponseResult<Void> delete(@MyRequestBody Long id) {
+        if (MyCommonUtil.existBlankArgument(id)) {
+            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
+        }
+        return this.doDelete(id);
+    }
+
+    /**
+     * 批量删除旅游项目日历价格数据。
+     *
+     * @param idList 待删除对象的主键Id列表。
+     * @return 应答结果对象。
+     */
+    @SaCheckPermission("tourismDatePrice.delete")
+    @OperationLog(type = SysOperationLogType.DELETE_BATCH)
+    @PostMapping("/deleteBatch")
+    public ResponseResult<Void> deleteBatch(@MyRequestBody List<Long> idList) {
+        if (MyCommonUtil.existBlankArgument(idList)) {
+            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
+        }
+        for (Long id : idList) {
+            ResponseResult<Void> responseResult = this.doDelete(id);
+            if (!responseResult.isSuccess()) {
+                return responseResult;
+            }
+        }
+        return ResponseResult.success();
+    }
+
+    /**
+     * 列出符合过滤条件的旅游项目日历价格列表。
+     *
+     * @param tourismDatePriceDtoFilter 过滤对象。
+     * @param orderParam 排序参数。
+     * @param pageParam 分页参数。
+     * @return 应答结果对象,包含查询结果集。
+     */
+    @SaCheckPermission("tourismDatePrice.view")
+    @PostMapping("/list")
+    public ResponseResult<MyPageData<TourismDatePriceVo>> list(
+            @MyRequestBody TourismDatePriceDto tourismDatePriceDtoFilter,
+            @MyRequestBody MyOrderParam orderParam,
+            @MyRequestBody MyPageParam pageParam) {
+        if (pageParam != null) {
+            PageMethod.startPage(pageParam.getPageNum(), pageParam.getPageSize(), pageParam.getCount());
+        }
+        TourismDatePrice tourismDatePriceFilter = MyModelUtil.copyTo(tourismDatePriceDtoFilter, TourismDatePrice.class);
+        String orderBy = MyOrderParam.buildOrderBy(orderParam, TourismDatePrice.class);
+        List<TourismDatePrice> tourismDatePriceList =
+                tourismDatePriceService.getTourismDatePriceListWithRelation(tourismDatePriceFilter, orderBy);
+        return ResponseResult.success(MyPageUtil.makeResponseData(tourismDatePriceList, TourismDatePriceVo.class));
+    }
+
+    /**
+     * 查看指定旅游项目日历价格对象详情。
+     *
+     * @param id 指定对象主键Id。
+     * @return 应答结果对象,包含对象详情。
+     */
+    @SaCheckPermission("tourismDatePrice.view")
+    @GetMapping("/view")
+    public ResponseResult<TourismDatePriceVo> view(@RequestParam String id) {
+        TourismDatePrice tourismDatePrice = tourismDatePriceService.getByIdWithRelation(id, MyRelationParam.full());
+        if (tourismDatePrice == null) {
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
+        }
+        TourismDatePriceVo tourismDatePriceVo = MyModelUtil.copyTo(tourismDatePrice, TourismDatePriceVo.class);
+        return ResponseResult.success(tourismDatePriceVo);
+    }
+
+    private ResponseResult<Void> doDelete(Long id) {
+        String errorMessage;
+        // 验证关联Id的数据合法性
+        TourismDatePrice originalTourismDatePrice = tourismDatePriceService.getById(id);
+        if (originalTourismDatePrice == null) {
+            // NOTE: 修改下面方括号中的话述
+            errorMessage = "数据验证失败,当前 [对象] 并不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        if (!tourismDatePriceService.remove(id)) {
+            errorMessage = "数据操作失败,删除的对象不存在,请刷新后重试!";
+            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
+        }
+        return ResponseResult.success();
+    }
+}

+ 6 - 4
application-webadmin/src/main/java/com/tourism/webadmin/back/controller/TourismFileController.java

@@ -1,6 +1,7 @@
 package com.tourism.webadmin.back.controller;
 
 import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.dev33.satoken.annotation.SaIgnore;
 import cn.hutool.core.util.ReflectUtil;
 import com.tourism.common.core.upload.BaseUpDownloader;
 import com.tourism.common.core.upload.UpDownloaderFactory;
@@ -18,7 +19,7 @@ import com.tourism.common.core.util.*;
 import com.tourism.common.core.constant.*;
 import com.tourism.common.core.annotation.MyRequestBody;
 import com.tourism.common.redis.cache.SessionCacheHelper;
-import com.tourism.webadmin.config.ApplicationConfig;
+import com.tourism.common.additional.config.ApplicationConfig;
 import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import lombok.extern.slf4j.Slf4j;
@@ -61,7 +62,7 @@ public class TourismFileController {
     @SaCheckPermission("tourismFile.add")
     @OperationLog(type = SysOperationLogType.ADD)
     @PostMapping("/add")
-    public ResponseResult<Long> add(@MyRequestBody TourismFileDto tourismFileDto) {
+    public ResponseResult<String> add(@MyRequestBody TourismFileDto tourismFileDto) {
         String errorMessage = MyCommonUtil.getModelValidationError(tourismFileDto, false);
         if (errorMessage != null) {
             return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
@@ -168,7 +169,7 @@ public class TourismFileController {
      */
     @SaCheckPermission("tourismFile.view")
     @GetMapping("/view")
-    public ResponseResult<TourismFileVo> view(@RequestParam Long id) {
+    public ResponseResult<TourismFileVo> view(@RequestParam String id) {
         TourismFile tourismFile = tourismFileService.getByIdWithRelation(id, MyRelationParam.full());
         if (tourismFile == null) {
             return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
@@ -187,7 +188,8 @@ public class TourismFileController {
      * @param asImage   下载文件是否为图片。
      * @param response  Http 应答对象。
      */
-    @SaCheckPermission("tourismFile.view")
+//    @SaCheckPermission("tourismFile.view")
+    @SaIgnore
     @OperationLog(type = SysOperationLogType.DOWNLOAD, saveResponse = false)
     @GetMapping("/download")
     public void download(

+ 29 - 23
application-webadmin/src/main/java/com/tourism/webadmin/back/controller/TourismProjectController.java

@@ -1,33 +1,38 @@
 package com.tourism.webadmin.back.controller;
 
 import cn.dev33.satoken.annotation.SaCheckPermission;
-import com.alibaba.fastjson.JSONObject;
+import cn.dev33.satoken.annotation.SaIgnore;
 import cn.hutool.core.util.ReflectUtil;
+import com.alibaba.fastjson.JSONObject;
+import com.github.pagehelper.page.PageMethod;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import com.tourism.common.additional.config.ApplicationConfig;
+import com.tourism.common.core.annotation.MyRequestBody;
+import com.tourism.common.core.constant.ErrorCodeEnum;
+import com.tourism.common.core.object.*;
 import com.tourism.common.core.upload.BaseUpDownloader;
 import com.tourism.common.core.upload.UpDownloaderFactory;
 import com.tourism.common.core.upload.UploadResponseInfo;
 import com.tourism.common.core.upload.UploadStoreInfo;
+import com.tourism.common.core.util.*;
 import com.tourism.common.log.annotation.OperationLog;
 import com.tourism.common.log.model.constant.SysOperationLogType;
-import com.github.pagehelper.page.PageMethod;
-import com.tourism.webadmin.back.vo.*;
-import com.tourism.webadmin.back.dto.*;
-import com.tourism.webadmin.back.model.*;
-import com.tourism.webadmin.back.service.*;
-import com.tourism.common.core.object.*;
-import com.tourism.common.core.util.*;
-import com.tourism.common.core.constant.*;
-import com.tourism.common.core.annotation.MyRequestBody;
 import com.tourism.common.redis.cache.SessionCacheHelper;
-import com.tourism.webadmin.config.ApplicationConfig;
-import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import com.tourism.webadmin.back.dto.TourismContentDto;
+import com.tourism.webadmin.back.dto.TourismFileDto;
+import com.tourism.webadmin.back.dto.TourismProjectDto;
+import com.tourism.webadmin.back.model.TourismContent;
+import com.tourism.webadmin.back.model.TourismFile;
+import com.tourism.webadmin.back.model.TourismProject;
+import com.tourism.webadmin.back.service.TourismProjectService;
+import com.tourism.webadmin.back.vo.TourismProjectVo;
 import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.servlet.http.HttpServletResponse;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 
-import jakarta.servlet.http.HttpServletResponse;
 import java.io.IOException;
 import java.util.*;
 
@@ -64,10 +69,10 @@ public class TourismProjectController {
     @SaCheckPermission("tourismProject.add")
     @OperationLog(type = SysOperationLogType.ADD)
     @PostMapping("/add")
-    public ResponseResult<Long> add(
+    public ResponseResult<String> add(
             @MyRequestBody TourismProjectDto tourismProjectDto,
             @MyRequestBody TourismFileDto tourismFileDto,
-            @MyRequestBody TourismContentDto tourismContentDto) {
+            @MyRequestBody TourismContentDto tourismContentDto) throws Exception {
         ResponseResult<Tuple2<TourismProject, JSONObject>> verifyResult = this.doBusinessDataVerifyAndConvert(
                 tourismProjectDto, false, tourismFileDto, tourismContentDto);
         if (!verifyResult.isSuccess()) {
@@ -91,10 +96,10 @@ public class TourismProjectController {
     @SaCheckPermission("tourismProject.update")
     @OperationLog(type = SysOperationLogType.UPDATE)
     @PostMapping("/update")
-    public ResponseResult<Long> update(
+    public ResponseResult<String> update(
             @MyRequestBody TourismProjectDto tourismProjectDto,
             @MyRequestBody TourismFileDto tourismFileDto,
-            @MyRequestBody TourismContentDto tourismContentDto) {
+            @MyRequestBody TourismContentDto tourismContentDto) throws Exception {
         String errorMessage;
         ResponseResult<Tuple2<TourismProject, JSONObject>> verifyResult = this.doBusinessDataVerifyAndConvert(
                 tourismProjectDto, true, tourismFileDto, tourismContentDto);
@@ -120,7 +125,7 @@ public class TourismProjectController {
     @SaCheckPermission("tourismProject.delete")
     @OperationLog(type = SysOperationLogType.DELETE)
     @PostMapping("/delete")
-    public ResponseResult<Void> delete(@MyRequestBody Long id) {
+    public ResponseResult<Void> delete(@MyRequestBody String id) {
         if (MyCommonUtil.existBlankArgument(id)) {
             return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
         }
@@ -136,11 +141,11 @@ public class TourismProjectController {
     @SaCheckPermission("tourismProject.delete")
     @OperationLog(type = SysOperationLogType.DELETE_BATCH)
     @PostMapping("/deleteBatch")
-    public ResponseResult<Void> deleteBatch(@MyRequestBody List<Long> idList) {
+    public ResponseResult<Void> deleteBatch(@MyRequestBody List<String> idList) {
         if (MyCommonUtil.existBlankArgument(idList)) {
             return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
         }
-        for (Long id : idList) {
+        for (String id : idList) {
             ResponseResult<Void> responseResult = this.doDelete(id);
             if (!responseResult.isSuccess()) {
                 return responseResult;
@@ -262,7 +267,7 @@ public class TourismProjectController {
      */
     @SaCheckPermission("tourismProject.view")
     @GetMapping("/view")
-    public ResponseResult<TourismProjectVo> view(@RequestParam Long id) {
+    public ResponseResult<TourismProjectVo> view(@RequestParam String id) {
         TourismProject tourismProject = tourismProjectService.getByIdWithRelation(id, MyRelationParam.full());
         if (tourismProject == null) {
             return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
@@ -281,7 +286,8 @@ public class TourismProjectController {
      * @param asImage   下载文件是否为图片。
      * @param response  Http 应答对象。
      */
-    @SaCheckPermission("tourismProject.view")
+//    @SaCheckPermission("tourismProject.view")
+    @SaIgnore
     @OperationLog(type = SysOperationLogType.DOWNLOAD, saveResponse = false)
     @GetMapping("/download")
     public void download(
@@ -413,7 +419,7 @@ public class TourismProjectController {
         return ResponseResult.success(new Tuple2<>(tourismProject, relationData));
     }
 
-    private ResponseResult<Void> doDelete(Long id) {
+    private ResponseResult<Void> doDelete(String id) {
         String errorMessage;
         // 验证关联Id的数据合法性
         TourismProject originalTourismProject = tourismProjectService.getById(id);

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно