Parcourir la source

[fix]
完善拼团逻辑

chenchen il y a 2 mois
Parent
commit
5a6c39845a

+ 120 - 0
application-webadmin/src/main/java/com/tourism/webadmin/back/controller/TourProjectGroupPurchaseController.java

@@ -1,6 +1,11 @@
 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;
@@ -12,12 +17,17 @@ 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.*;
 
 /**
@@ -33,6 +43,12 @@ import java.util.*;
 public class TourProjectGroupPurchaseController {
 
     @Autowired
+    private ApplicationConfig appConfig;
+    @Autowired
+    private SessionCacheHelper cacheHelper;
+    @Autowired
+    private UpDownloaderFactory upDownloaderFactory;
+    @Autowired
     private TourProjectGroupPurchaseService tourProjectGroupPurchaseService;
 
     /**
@@ -197,6 +213,104 @@ public class TourProjectGroupPurchaseController {
         return ResponseResult.success(tourProjectGroupPurchaseVo);
     }
 
+    /**
+     * 附件文件下载。
+     * 这里将图片和其他类型的附件文件放到不同的父目录下,主要为了便于今后图片文件的迁移。
+     *
+     * @param id 附件所在记录的主键Id。
+     * @param fieldName 附件所属的字段名。
+     * @param filename  文件名。如果没有提供该参数,就从当前记录的指定字段中读取。
+     * @param asImage   下载文件是否为图片。
+     * @param response  Http 应答对象。
+     */
+    @SaCheckPermission("tourProjectGroupPurchase.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 {
+                TourProjectGroupPurchase tourProjectGroupPurchase = tourProjectGroupPurchaseService.getById(id);
+                if (tourProjectGroupPurchase == null) {
+                    ResponseResult.output(HttpServletResponse.SC_NOT_FOUND);
+                    return;
+                }
+                String fieldJsonData = (String) ReflectUtil.getFieldValue(tourProjectGroupPurchase, 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(TourProjectGroupPurchase.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(),
+                    TourProjectGroupPurchase.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("tourProjectGroupPurchase.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(TourProjectGroupPurchase.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(), TourProjectGroupPurchase.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的数据合法性
@@ -206,6 +320,12 @@ public class TourProjectGroupPurchaseController {
             errorMessage = "数据验证失败,当前 [对象] 并不存在,请刷新后重试!";
             return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
         }
+        // NOTE: 如果该对象的删除前数据一致性验证和实际需求有偏差,可以根据需求调整验证字段,甚至也可以直接删除下面的验证。
+        // 删除前,先主动验证是否存在关联的从表数据。
+        CallResult callResult = tourProjectGroupPurchaseService.verifyRelatedDataBeforeDelete(originalTourProjectGroupPurchase);
+        if (!callResult.isSuccess()) {
+            return ResponseResult.errorFrom(callResult);
+        }
         if (!tourProjectGroupPurchaseService.remove(id)) {
             errorMessage = "数据操作失败,删除的对象不存在,请刷新后重试!";
             return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);

+ 14 - 14
application-webadmin/src/main/java/com/tourism/webadmin/back/dao/mapper/TourProjectGroupPurchaseDetailMapper.xml

@@ -17,17 +17,17 @@
 
     <insert id="insertList">
         INSERT INTO tour_project_group_purchase_detail
-            (id,
-            group_purchase_code,
-            create_user_id,
-            create_time,
-            update_user_id,
-            update_time,
-            data_state,
-            adult_price,
-            children_price,
-            min_count,
-            max_count)
+        (id,
+        group_purchase_code,
+        create_user_id,
+        create_time,
+        update_user_id,
+        update_time,
+        data_state,
+        adult_price,
+        children_price,
+        min_count,
+        max_count)
         VALUES
         <foreach collection="list" index="index" item="item" separator="," >
             (#{item.id},
@@ -97,15 +97,15 @@
     <!-- 支持基于一对一或者一对多从表字段过滤的SQL_ID -->
     <select id="getTourProjectGroupPurchaseDetailListEx" resultMap="BaseResultMap" >
         SELECT
-            tour_project_group_purchase_detail.*
+        tour_project_group_purchase_detail.*
         FROM
-            tour_project_group_purchase_detail
+        tour_project_group_purchase_detail
         <where>
             <include refid="filterRef"/>
             <if test="tourProjectGroupPurchaseFilter != null">
                 AND EXISTS (SELECT * FROM tour_project_group_purchase
                 <where>
-                    tour_project_group_purchase_detail.group_purchase_id = tour_project_group_purchase.code
+                    tour_project_group_purchase_detail.group_purchase_code = tour_project_group_purchase.code
                     <include refid="com.tourism.webadmin.back.dao.TourProjectGroupPurchaseMapper.filterRef"/>
                 </where>)
             </if>

+ 5 - 2
application-webadmin/src/main/java/com/tourism/webadmin/back/dao/mapper/TourProjectGroupPurchaseMapper.xml

@@ -22,6 +22,7 @@
         <result column="success" jdbcType="TINYINT" property="success"/>
         <result column="title" jdbcType="VARCHAR" property="title"/>
         <result column="image" jdbcType="VARCHAR" property="image"/>
+        <result column="price_unit" jdbcType="VARCHAR" property="priceUnit"/>
     </resultMap>
 
     <insert id="insertList">
@@ -45,7 +46,8 @@
             now_count,
             success,
             title,
-            image)
+            image,
+            price_unit)
         VALUES
         <foreach collection="list" index="index" item="item" separator="," >
             (#{item.id},
@@ -67,7 +69,8 @@
             #{item.nowCount},
             #{item.success},
             #{item.title},
-            #{item.image})
+            #{item.image},
+            #{item.priceUnit})
         </foreach>
     </insert>
 

+ 8 - 2
application-webadmin/src/main/java/com/tourism/webadmin/back/dto/TourProjectGroupPurchaseDto.java

@@ -105,9 +105,9 @@ public class TourProjectGroupPurchaseDto {
     private Integer nowCount;
 
     /**
-     * 拼团是否成功(0.失败;1.成功)。
+     * 拼团是否成功(0.正在拼团;1.成功)。
      */
-    @Schema(description = "拼团是否成功(0.失败;1.成功)。")
+    @Schema(description = "拼团是否成功(0.正在拼团;1.成功)。")
     private Integer success;
 
     /**
@@ -123,6 +123,12 @@ public class TourProjectGroupPurchaseDto {
     private String image;
 
     /**
+     * 价格单位。
+     */
+    @Schema(description = "价格单位。")
+    private String priceUnit;
+
+    /**
      * endTime 范围过滤起始值(>=)。
      * NOTE: 可支持范围操作符的列表数据过滤。
      */

+ 14 - 1
application-webadmin/src/main/java/com/tourism/webadmin/back/model/TourProjectGroupPurchase.java

@@ -5,6 +5,7 @@ import com.tourism.common.core.upload.UploadStoreTypeEnum;
 import com.tourism.webadmin.back.model.constant.Enable;
 import com.tourism.common.core.annotation.*;
 import com.tourism.common.core.base.model.BaseModel;
+import com.tourism.webadmin.back.model.constant.Success;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 
@@ -103,7 +104,7 @@ public class TourProjectGroupPurchase extends BaseModel {
     private Integer nowCount;
 
     /**
-     * 拼团是否成功(0.失败;1.成功)。
+     * 拼团是否成功(0.正在拼团;1.成功)。
      */
     @TableField(value = "success")
     private Integer success;
@@ -122,6 +123,12 @@ public class TourProjectGroupPurchase extends BaseModel {
     private String image;
 
     /**
+     * 价格单位。
+     */
+    @TableField(value = "price_unit")
+    private String priceUnit;
+
+    /**
      * endTime 范围过滤起始值(>=)。
      */
     @TableField(exist = false)
@@ -214,4 +221,10 @@ public class TourProjectGroupPurchase extends BaseModel {
             constantDictClass = Enable.class)
     @TableField(exist = false)
     private Map<String, Object> stateDictMap;
+
+    @RelationConstDict(
+            masterIdField = "success",
+            constantDictClass = Success.class)
+    @TableField(exist = false)
+    private Map<String, Object> successDictMap;
 }

+ 44 - 0
application-webadmin/src/main/java/com/tourism/webadmin/back/model/constant/Success.java

@@ -0,0 +1,44 @@
+package com.tourism.webadmin.back.model.constant;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 拼团是否成功常量字典对象。
+ *
+ * @author 吃饭睡觉
+ * @date 2024-09-06
+ */
+public final class Success {
+
+    /**
+     * 正在拼团。
+     */
+    public static final int FAIL = 0;
+    /**
+     * 成功。
+     */
+    public static final int SUCCESS = 1;
+
+    private static final Map<Object, String> DICT_MAP = new HashMap<>(2);
+    static {
+        DICT_MAP.put(FAIL, "正在拼团");
+        DICT_MAP.put(SUCCESS, "成功");
+    }
+
+    /**
+     * 判断参数是否为当前常量字典的合法值。
+     *
+     * @param value 待验证的参数值。
+     * @return 合法返回true,否则false。
+     */
+    public static boolean isValid(Integer value) {
+        return value != null && DICT_MAP.containsKey(value);
+    }
+
+    /**
+     * 私有构造函数,明确标识该常量类的作用。
+     */
+    private Success() {
+    }
+}

+ 10 - 0
application-webadmin/src/main/java/com/tourism/webadmin/back/service/TourProjectGroupPurchaseService.java

@@ -1,6 +1,7 @@
 package com.tourism.webadmin.back.service;
 
 import com.tourism.webadmin.back.model.*;
+import com.tourism.common.core.object.CallResult;
 import com.tourism.common.core.base.service.IBaseService;
 
 import java.util.*;
@@ -65,4 +66,13 @@ public interface TourProjectGroupPurchaseService extends IBaseService<TourProjec
      * @return 查询结果集。
      */
     List<TourProjectGroupPurchase> getTourProjectGroupPurchaseListWithRelation(TourProjectGroupPurchase filter, String orderBy);
+
+    /**
+     * 判断从表数据是否存在,如果存在就不能删除主对象,否则可以删除主对象。
+     * 适用于主表对从表不是强制级联删除的场景。
+     *
+     * @param tourProjectGroupPurchase 主表对象。
+     * @return 没有关联数据返回true,否则false,同时返回具体的提示信息。
+     */
+    CallResult verifyRelatedDataBeforeDelete(TourProjectGroupPurchase tourProjectGroupPurchase);
 }

+ 11 - 0
application-webadmin/src/main/java/com/tourism/webadmin/back/service/impl/TourProjectGroupPurchaseDetailServiceImpl.java

@@ -107,6 +107,17 @@ public class TourProjectGroupPurchaseDetailServiceImpl extends BaseService<TourP
         return CallResult.ok();
     }
 
+    @Override
+    public CallResult verifyRelatedData(TourProjectGroupPurchaseDetail tourProjectGroupPurchaseDetail, TourProjectGroupPurchaseDetail originalTourProjectGroupPurchaseDetail) {
+        String errorMessageFormat = "数据验证失败,关联的%s并不存在,请刷新后重试!";
+        //这里是一对多的验证
+        if (this.needToVerify(tourProjectGroupPurchaseDetail, originalTourProjectGroupPurchaseDetail, TourProjectGroupPurchaseDetail::getGroupPurchaseCode)
+                && !tourProjectGroupPurchaseService.existOne("code", tourProjectGroupPurchaseDetail.getGroupPurchaseCode())) {
+            return CallResult.error(String.format(errorMessageFormat, "关联 拼团设置的id"));
+        }
+        return CallResult.ok();
+    }
+
     private TourProjectGroupPurchaseDetail buildDefaultValue(TourProjectGroupPurchaseDetail tourProjectGroupPurchaseDetail) {
         if (tourProjectGroupPurchaseDetail.getId() == null) {
             tourProjectGroupPurchaseDetail.setId(idGenerator.nextLongId());

+ 14 - 1
application-webadmin/src/main/java/com/tourism/webadmin/back/service/impl/TourProjectGroupPurchaseServiceImpl.java

@@ -98,6 +98,17 @@ public class TourProjectGroupPurchaseServiceImpl extends BaseService<TourProject
     }
 
     @Override
+    public CallResult verifyRelatedDataBeforeDelete(TourProjectGroupPurchase tourProjectGroupPurchase) {
+        TourProjectGroupPurchaseDetail tourProjectGroupPurchaseDetail = new TourProjectGroupPurchaseDetail();
+        tourProjectGroupPurchaseDetail.setGroupPurchaseCode(tourProjectGroupPurchase.getCode());
+        if (tourProjectGroupPurchaseDetailService.getCountByFilter(tourProjectGroupPurchaseDetail) > 0) {
+            // NOTE: 可以根据需求修改下面方括号中的提示信息。
+            return CallResult.error("数据验证失败,[TourProjectGroupPurchaseDetail] 存在关联数据!");
+        }
+        return CallResult.ok();
+    }
+
+    @Override
     public CallResult verifyRelatedData(TourProjectGroupPurchase tourProjectGroupPurchase, TourProjectGroupPurchase originalTourProjectGroupPurchase) {
         String errorMessageFormat = "数据验证失败,关联的%s并不存在,请刷新后重试!";
         //这里是基于字典的验证。
@@ -107,7 +118,7 @@ public class TourProjectGroupPurchaseServiceImpl extends BaseService<TourProject
         }
         //这里是一对多的验证
         if (this.needToVerify(tourProjectGroupPurchase, originalTourProjectGroupPurchase, TourProjectGroupPurchase::getCode)
-                && !tourProjectGroupPurchaseDetailService.existOne("groupPurchaseId", tourProjectGroupPurchase.getCode())) {
+                && !tourProjectGroupPurchaseDetailService.existOne("groupPurchaseCode", tourProjectGroupPurchase.getCode())) {
             return CallResult.error(String.format(errorMessageFormat, "关联字段(与从表)"));
         }
         return CallResult.ok();
@@ -116,10 +127,12 @@ public class TourProjectGroupPurchaseServiceImpl extends BaseService<TourProject
     private TourProjectGroupPurchase buildDefaultValue(TourProjectGroupPurchase tourProjectGroupPurchase) {
         if (tourProjectGroupPurchase.getId() == null) {
             tourProjectGroupPurchase.setId(idGenerator.nextLongId());
+            tourProjectGroupPurchase.setCode(idGenerator.nextLongId());
         }
         MyModelUtil.fillCommonsForInsert(tourProjectGroupPurchase);
         tourProjectGroupPurchase.setDataState(GlobalDeletedFlag.NORMAL);
         MyModelUtil.setDefaultValue(tourProjectGroupPurchase, "title", "");
+        MyModelUtil.setDefaultValue(tourProjectGroupPurchase, "image", "");
         return tourProjectGroupPurchase;
     }
 }

+ 14 - 2
application-webadmin/src/main/java/com/tourism/webadmin/back/vo/TourProjectGroupPurchaseVo.java

@@ -93,9 +93,9 @@ public class TourProjectGroupPurchaseVo extends BaseVo {
     private Integer nowCount;
 
     /**
-     * 拼团是否成功(0.失败;1.成功)。
+     * 拼团是否成功(0.正在拼团;1.成功)。
      */
-    @Schema(description = "拼团是否成功(0.失败;1.成功)")
+    @Schema(description = "拼团是否成功(0.正在拼团;1.成功)")
     private Integer success;
 
     /**
@@ -111,6 +111,12 @@ public class TourProjectGroupPurchaseVo extends BaseVo {
     private String image;
 
     /**
+     * 价格单位。
+     */
+    @Schema(description = "价格单位")
+    private String priceUnit;
+
+    /**
      * projectId 的一对一关联数据对象,数据对应类型为TourismProjectVo。
      */
     @Schema(description = "projectId 的一对一关联数据对象,数据对应类型为TourismProjectVo")
@@ -127,4 +133,10 @@ public class TourProjectGroupPurchaseVo extends BaseVo {
      */
     @Schema(description = "state 常量字典关联数据")
     private Map<String, Object> stateDictMap;
+
+    /**
+     * success 常量字典关联数据。
+     */
+    @Schema(description = "success 常量字典关联数据")
+    private Map<String, Object> successDictMap;
 }