Parcourir la source

打卡广场 筛选

qinxuyang il y a 2 jours
Parent
commit
4579eadaed

+ 71 - 0
alien-entity/src/main/java/shop/alien/mapper/StoreClockInMapper.java

@@ -57,4 +57,75 @@ public interface StoreClockInMapper extends BaseMapper<StoreClockIn> {
       */
     @Select("SELECT store_id as storeId, count(store_id) as count FROM `store_clock_in` where delete_flag = 0 and permission = 1 and img_url is not null GROUP BY store_id ")
     List<Map<Integer, Integer>> getStoreClockInWithCanLookCount();
+
+    /**
+     * 用户打卡记录中出现的年份(降序)。
+     * 时间维度均使用 {@code store_clock_in.created_time}。
+     */
+    @Select("SELECT DISTINCT YEAR(clock.created_time) FROM store_clock_in clock " +
+            "WHERE clock.user_id = #{userId} AND clock.delete_flag = 0 " +
+            "AND (clock.check_flag IS NULL OR clock.check_flag NOT IN (3)) " +
+            "ORDER BY YEAR(clock.created_time) DESC")
+    List<Integer> listDistinctClockInYears(@Param("userId") Integer userId);
+
+    /**
+     * 用户打过卡的城市(门店行政区域市名称,去重)
+     */
+    @Select("SELECT DISTINCT store.administrative_region_city_name FROM store_clock_in clock " +
+            "INNER JOIN store_info store ON store.id = clock.store_id AND store.delete_flag = 0 " +
+            "WHERE clock.user_id = #{userId} AND clock.delete_flag = 0 " +
+            "AND (clock.check_flag IS NULL OR clock.check_flag NOT IN (3)) " +
+            "AND store.administrative_region_city_name IS NOT NULL AND store.administrative_region_city_name <> '' " +
+            "ORDER BY store.administrative_region_city_name")
+    List<String> listDistinctClockInCityNames(@Param("userId") Integer userId);
+
+    /**
+     * 指定年份内有打卡的月份(1-12);传入 city 时仅统计该城市的打卡。
+     * 年份、月份均按 {@code store_clock_in.created_time} 计算。
+     */
+    @Select("<script>" +
+            "SELECT DISTINCT MONTH(clock.created_time) FROM store_clock_in clock " +
+            "INNER JOIN store_info store ON store.id = clock.store_id AND store.delete_flag = 0 " +
+            "WHERE clock.user_id = #{userId} AND clock.delete_flag = 0 " +
+            "AND (clock.check_flag IS NULL OR clock.check_flag NOT IN (3)) " +
+            "AND YEAR(clock.created_time) = #{year} " +
+            "<if test=\"city != null and city != ''\">AND store.administrative_region_city_name = #{city}</if>" +
+            "</script>")
+    List<Integer> listClockInMonthsByYear(@Param("userId") Integer userId,
+                                          @Param("year") Integer year,
+                                          @Param("city") String city);
+
+    /**
+     * 按用户+年份查询去重城市(城市用于页面回显统计)
+     */
+    @Select("<script>" +
+            "SELECT DISTINCT store.administrative_region_city_name FROM store_clock_in clock " +
+            "INNER JOIN store_info store ON store.id = clock.store_id AND store.delete_flag = 0 " +
+            "WHERE clock.user_id = #{userId} AND clock.delete_flag = 0 " +
+            "AND (clock.check_flag IS NULL OR clock.check_flag NOT IN (3)) " +
+            "<if test=\"year != null\">AND YEAR(clock.created_time) = #{year}</if> " +
+            "<if test=\"month != null\">AND MONTH(clock.created_time) = #{month}</if> " +
+            "AND store.administrative_region_city_name IS NOT NULL AND store.administrative_region_city_name != '' " +
+            "ORDER BY store.administrative_region_city_name" +
+            "</script>")
+    List<String> listDistinctClockInCityNamesByYear(@Param("userId") Integer userId,
+                                                    @Param("year") Integer year,
+                                                    @Param("month") Integer month);
+
+    /**
+     * 按用户+年份(+城市)统计去重打卡地点数(门店数)
+     */
+    @Select("<script>" +
+            "SELECT COUNT(DISTINCT clock.store_id) FROM store_clock_in clock " +
+            "INNER JOIN store_info store ON store.id = clock.store_id AND store.delete_flag = 0 " +
+            "WHERE clock.user_id = #{userId} AND clock.delete_flag = 0 " +
+            "AND (clock.check_flag IS NULL OR clock.check_flag NOT IN (3)) " +
+            "<if test=\"year != null\">AND YEAR(clock.created_time) = #{year}</if> " +
+            "<if test=\"month != null\">AND MONTH(clock.created_time) = #{month}</if> " +
+            "<if test=\"city != null and city != ''\">AND store.administrative_region_city_name = #{city}</if> " +
+            "</script>")
+    Integer countDistinctClockInStoreByCondition(@Param("userId") Integer userId,
+                                                 @Param("year") Integer year,
+                                                 @Param("month") Integer month,
+                                                 @Param("city") String city);
 }

+ 45 - 10
alien-store/src/main/java/shop/alien/store/controller/StoreClockInController.java

@@ -9,6 +9,7 @@ import org.springframework.web.bind.annotation.*;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.StoreClockIn;
 import shop.alien.entity.store.UserLoginInfo;
+import shop.alien.entity.store.vo.ClockInRecordValidateVo;
 import shop.alien.entity.store.vo.StoreClockInVo;
 import shop.alien.store.annotation.TrackEvent;
 import shop.alien.store.service.StoreClockInService;
@@ -48,18 +49,28 @@ public class StoreClockInController {
             @ApiImplicitParam(name = "size", value = "分页条数", dataType = "Integer", defaultValue = "10", paramType = "query"),
             @ApiImplicitParam(name = "phoneId", value = "'user_' + 手机号", dataType = "String", paramType = "query"),
             @ApiImplicitParam(name = "mySelf", value = "是否只查看自己(0-否  1-是)", dataType = "Integer", defaultValue = "0", paramType = "query"),
-            @ApiImplicitParam(name = "storeId", value = "店铺id", dataType = "Integer", paramType = "query")
+            @ApiImplicitParam(name = "storeId", value = "店铺id", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "year", value = "年份(取 store_clock_in.created_time 年份)", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "month", value = "月份(1-12,取 store_clock_in.created_time 月份)", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "city", value = "城市(store_info.administrative_region_city_name)", dataType = "String", paramType = "query")
     })
     @GetMapping("/getStoreClockInList")
-    public R<IPage<StoreClockInVo>> getStoreClockInList(Integer userId, int page, int size, String phoneId, int mySelf, @RequestParam(required = false) Integer storeId) {
-        log.info("StoreClockInController.getStoreClockInList?userId={},page={},size={},phoneId={},mySelf={},storeId={}", userId, page, size, phoneId, mySelf,storeId);
-        if (userId==null){
-            JSONObject data = JwtUtil.getCurrentUserInfo();
-            if (null != data) {
-                userId = data.getInteger("userId");
-            }
-        }
-        return R.data(storeClockInService.getStoreClockInList(userId, page, size, phoneId, mySelf,storeId));
+    public R<IPage<StoreClockInVo>> getStoreClockInList(Integer userId, int page, int size, String phoneId, int mySelf,
+                                                        @RequestParam(required = false) Integer storeId,
+                                                        @RequestParam(required = false) Integer year,
+                                                        @RequestParam(required = false) Integer month,
+                                                        @RequestParam(required = false) String city) {
+        log.info("StoreClockInController.getStoreClockInList?userId={},page={},size={},phoneId={},mySelf={},storeId={},year={},month={},city={}",
+                userId, page, size, phoneId, mySelf, storeId, year, month, city);
+//        JSONObject data = JwtUtil.getCurrentUserInfo();
+//        Integer loginUserId = data != null ? data.getInteger("userId") : null;
+//        if (mySelf == 1 && loginUserId != null) {
+//            // 查看“我的打卡”时,统一使用登录用户,避免前端误传 userId 导致查不到数据
+//            userId = loginUserId;
+//        } else if (userId == null) {
+//            userId = loginUserId;
+//        }
+        return R.data(storeClockInService.getStoreClockInList(userId, page, size, phoneId, mySelf, storeId, year, month, city));
     }
 
     @ApiOperation("删除打卡记录")
@@ -104,4 +115,28 @@ public class StoreClockInController {
         log.info("StoreClockInController.getStoreClockInById?userId={},id={}", userId, id);
         return R.data(storeClockInService.getStoreClockInById(id, userId));
     }
+
+    @ApiOperation("打卡记录筛选校验(年份各月是否有打卡、可选城市过滤、用户打卡涉及城市列表)")
+    @ApiOperationSupport(order = 7)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "userId", value = "用户id(可空,空则从登录态解析)", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "year", value = "年份,用于返回该年1~12月是否打卡;不传则 months 为空列表", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "city", value = "行政区域市名称,传入后月份统计仅含该城市下的打卡;不传或空为不限城市", dataType = "String", paramType = "query")
+    })
+    @GetMapping("/getClockInRecordValidate")
+    public R<ClockInRecordValidateVo> getClockInRecordValidate(Integer userId,
+                                                               @RequestParam(required = false) Integer year,
+                                                               @RequestParam(required = false) String city) {
+        if (userId == null) {
+            JSONObject data = JwtUtil.getCurrentUserInfo();
+            if (null != data) {
+                userId = data.getInteger("userId");
+            }
+        }
+        if (userId == null) {
+            return R.fail("用户未登录或缺少 userId");
+        }
+        log.info("StoreClockInController.getClockInRecordValidate?userId={},year={},city={}", userId, year, city);
+        return R.data(storeClockInService.getClockInRecordValidate(userId, year, city));
+    }
 }

+ 12 - 1
alien-store/src/main/java/shop/alien/store/service/StoreClockInService.java

@@ -3,6 +3,7 @@ package shop.alien.store.service;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.service.IService;
 import shop.alien.entity.store.StoreClockIn;
+import shop.alien.entity.store.vo.ClockInRecordValidateVo;
 import shop.alien.entity.store.vo.StoreClockInVo;
 
 import java.util.List;
@@ -20,7 +21,7 @@ public interface StoreClockInService extends IService<StoreClockIn> {
 
     StoreClockIn addStoreClockIn(StoreClockIn storeClockIn);
 
-    IPage<StoreClockInVo> getStoreClockInList(Integer userId, int page, int size, String phoneId, int mySelf,Integer storeId);
+    IPage<StoreClockInVo> getStoreClockInList(Integer userId, int page, int size, String phoneId, int mySelf, Integer storeId, Integer year, Integer month, String city);
 
     int deleteClockIn(Integer id);
 
@@ -45,4 +46,14 @@ public interface StoreClockInService extends IService<StoreClockIn> {
      */
     StoreClockInVo getStoreClockInById(Integer id, Integer userId);
 
+    /**
+     * 打卡记录筛选:年份对应的月份打卡情况、用户打卡涉及的城市列表等。
+     * 年、月统计均以 {@code store_clock_in.created_time} 为准。
+     *
+     * @param userId 用户 id
+     * @param year   年份(用于生成 1~12 月是否有打卡);可为 null,此时 months 为空列表
+     * @param city   城市名称(行政区域市);为空或 null 表示不限城市
+     */
+    ClockInRecordValidateVo getClockInRecordValidate(Integer userId, Integer year, String city);
+
 }

+ 60 - 6
alien-store/src/main/java/shop/alien/store/service/impl/StoreClockInServiceImpl.java

@@ -14,6 +14,9 @@ import org.apache.commons.lang3.StringUtils;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import shop.alien.entity.store.*;
+import shop.alien.entity.store.vo.ClockInRecordValidateVo;
+import shop.alien.entity.store.vo.MonthClockInItem;
+import shop.alien.entity.store.vo.StoreClockInPageVo;
 import shop.alien.entity.store.vo.StoreClockInVo;
 import shop.alien.entity.store.vo.WebSocketVo;
 import shop.alien.mapper.*;
@@ -132,8 +135,9 @@ public class StoreClockInServiceImpl extends ServiceImpl<StoreClockInMapper, Sto
     }
 
     @Override
-    public IPage<StoreClockInVo> getStoreClockInList(Integer userId, int page, int size, String phoneId, int mySelf,Integer storeId) {
+    public IPage<StoreClockInVo> getStoreClockInList(Integer userId, int page, int size, String phoneId, int mySelf, Integer storeId, Integer year, Integer month, String city) {
         IPage<StoreClockIn> iPage = new Page<>(page, size);
+        String cityFilter = normalizeCityFilter(city);
         
         // 优化:提前查询所有需要的数据,避免在循环中查询
         // 查询我的点赞 - 使用Set提高查找效率
@@ -150,9 +154,12 @@ public class StoreClockInServiceImpl extends ServiceImpl<StoreClockInMapper, Sto
             IPage<StoreClockInVo> storeClockInIPage1 = storeClockInMapper.getStoreClockInList(iPage, new QueryWrapper<StoreClockIn>()
                     .eq("clock.store_id", storeId)
                     .eq("clock.delete_flag", 0)
-                    .ne("clock.check_flag", 3)
+                    .and(wrapper1 -> wrapper1.isNull("clock.check_flag").or().ne("clock.check_flag", 3))
                     .eq("user.delete_flag", 0)
                     .eq("store.delete_flag", 0)
+                    .apply(Objects.nonNull(year), "YEAR(clock.created_time) = {0}", year)
+                    .apply(Objects.nonNull(month), "MONTH(clock.created_time) = {0}", month)
+                    .eq(StringUtils.isNotBlank(cityFilter), "store.administrative_region_city_name", cityFilter)
                     .isNotNull(0 == mySelf, "clock.img_url")
                     .and(wrapper1 ->
                             wrapper1.eq("clock.permission", 1)
@@ -176,16 +183,19 @@ public class StoreClockInServiceImpl extends ServiceImpl<StoreClockInMapper, Sto
                     setStoreTypeNew(vo);
                 });
             }
-            return storeClockInIPage1;
+            return attachClockInEchoFields(storeClockInIPage1, userId, year, month, cityFilter);
         }
         
         QueryWrapper<StoreClockIn> wrapper = new QueryWrapper<>();
         wrapper.eq(1 == mySelf, "clock.user_id", userId);
         wrapper.isNotNull(0 == mySelf, "clock.img_url");
         wrapper.eq("clock.delete_flag", 0);
-        wrapper.ne("clock.check_flag", 3);
+        wrapper.and(wrapper1 -> wrapper1.isNull("clock.check_flag").or().ne("clock.check_flag", 3));
         wrapper.eq("user.delete_flag", 0);
         wrapper.eq("store.delete_flag", 0);
+        wrapper.apply(Objects.nonNull(year), "YEAR(clock.created_time) = {0}", year);
+        wrapper.apply(Objects.nonNull(month), "MONTH(clock.created_time) = {0}", month);
+        wrapper.eq(StringUtils.isNotBlank(cityFilter), "store.administrative_region_city_name", cityFilter);
         wrapper.and(wrapper1 ->
                 wrapper1.eq("clock.permission", 1)
                         .or(wSub ->
@@ -199,7 +209,7 @@ public class StoreClockInServiceImpl extends ServiceImpl<StoreClockInMapper, Sto
         List<StoreClockInVo> records = storeClockInIPage.getRecords();
         
         if (records.isEmpty()) {
-            return storeClockInIPage;
+            return attachClockInEchoFields(storeClockInIPage, userId, year, month, cityFilter);
         }
 
         // 优化:批量查询所有需要的数据
@@ -243,7 +253,28 @@ public class StoreClockInServiceImpl extends ServiceImpl<StoreClockInMapper, Sto
             setStoreTypeNew(vo);
         });
 
-        return storeClockInIPage;
+        return attachClockInEchoFields(storeClockInIPage, userId, year, month, cityFilter);
+    }
+
+    private IPage<StoreClockInVo> attachClockInEchoFields(IPage<StoreClockInVo> pageData, Integer userId, Integer year, Integer month, String cityFilter) {
+        StoreClockInPageVo resultVo = new StoreClockInPageVo();
+        resultVo.setCurrent(pageData.getCurrent());
+        resultVo.setSize(pageData.getSize());
+        resultVo.setTotal(pageData.getTotal());
+        resultVo.setRecords(pageData.getRecords());
+        List<String> cities = storeClockInMapper.listDistinctClockInCityNamesByYear(userId, year, month);
+        resultVo.setClockInCities(cities == null ? Collections.emptyList() : cities);
+        Integer placeCount = storeClockInMapper.countDistinctClockInStoreByCondition(userId, year, month, cityFilter);
+        resultVo.setPlaceCount(placeCount == null ? 0 : placeCount);
+        return resultVo;
+    }
+
+    private String normalizeCityFilter(String city) {
+        if (StringUtils.isBlank(city)) {
+            return null;
+        }
+        String trimCity = city.trim();
+        return "全部城市".equals(trimCity) ? null : trimCity;
     }
 
     /**
@@ -496,4 +527,27 @@ public class StoreClockInServiceImpl extends ServiceImpl<StoreClockInMapper, Sto
         storeClockInVo.setBusinessSectionName(storeInfo.getBusinessSectionName());
         return storeClockInVo;
     }
+
+    @Override
+    public ClockInRecordValidateVo getClockInRecordValidate(Integer userId, Integer year, String city) {
+        ClockInRecordValidateVo vo = new ClockInRecordValidateVo();
+        vo.setYears(storeClockInMapper.listDistinctClockInYears(userId));
+        vo.setCities(storeClockInMapper.listDistinctClockInCityNames(userId));
+        if (year == null) {
+            vo.setMonths(Collections.emptyList());
+            return vo;
+        }
+        String cityFilter = StringUtils.isBlank(city) ? null : city.trim();
+        List<Integer> monthsWithRecord = storeClockInMapper.listClockInMonthsByYear(userId, year, cityFilter);
+        Set<Integer> monthSet = new HashSet<>(monthsWithRecord != null ? monthsWithRecord : Collections.emptyList());
+        List<MonthClockInItem> monthItems = new ArrayList<>(12);
+        for (int m = 1; m <= 12; m++) {
+            MonthClockInItem item = new MonthClockInItem();
+            item.setMonth(m);
+            item.setHasClockIn(monthSet.contains(m));
+            monthItems.add(item);
+        }
+        vo.setMonths(monthItems);
+        return vo;
+    }
 }