|
|
@@ -11,15 +11,16 @@ import com.baomidou.mybatisplus.core.toolkit.StringUtils;
|
|
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
|
import lombok.RequiredArgsConstructor;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
import org.springframework.beans.BeanUtils;
|
|
|
import org.springframework.beans.factory.annotation.Value;
|
|
|
import org.springframework.data.geo.Point;
|
|
|
-import org.springframework.http.HttpHeaders;
|
|
|
-import org.springframework.http.HttpStatus;
|
|
|
-import org.springframework.http.ResponseEntity;
|
|
|
+import org.springframework.http.*;
|
|
|
+import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
import org.springframework.util.CollectionUtils;
|
|
|
+import org.springframework.web.client.RestTemplate;
|
|
|
import org.springframework.web.multipart.MultipartFile;
|
|
|
import org.springframework.web.multipart.MultipartRequest;
|
|
|
import shop.alien.entity.store.*;
|
|
|
@@ -30,7 +31,9 @@ import shop.alien.entity.store.excelVo.StoreInfoExpiredRecordsExcelVo;
|
|
|
import shop.alien.entity.store.excelVo.util.ExcelExporter;
|
|
|
import shop.alien.entity.store.excelVo.util.ExcelGenerator;
|
|
|
import shop.alien.entity.store.vo.*;
|
|
|
+import shop.alien.entity.storePlatform.StoreLicenseHistory;
|
|
|
import shop.alien.mapper.*;
|
|
|
+import shop.alien.mapper.storePlantform.StoreLicenseHistoryMapper;
|
|
|
import shop.alien.store.config.BaseRedisService;
|
|
|
import shop.alien.store.config.GaoDeMapUtil;
|
|
|
import shop.alien.store.config.WebSocketProcess;
|
|
|
@@ -38,10 +41,12 @@ import shop.alien.store.service.*;
|
|
|
import shop.alien.store.util.CommonConstant;
|
|
|
import shop.alien.store.util.FileUploadUtil;
|
|
|
import shop.alien.store.util.GroupConstant;
|
|
|
+import shop.alien.store.util.ai.AiAuthTokenUtil;
|
|
|
import shop.alien.util.ali.AliOSSUtil;
|
|
|
import shop.alien.util.common.DistanceUtil;
|
|
|
import shop.alien.util.common.constant.CouponStatusEnum;
|
|
|
import shop.alien.util.common.constant.CouponTypeEnum;
|
|
|
+import shop.alien.util.common.constant.OcrTypeEnum;
|
|
|
import shop.alien.util.common.constant.OrderStatusEnum;
|
|
|
|
|
|
import javax.annotation.Resource;
|
|
|
@@ -67,9 +72,12 @@ import java.util.stream.Collectors;
|
|
|
* @since 2024-12-05
|
|
|
*/
|
|
|
@Service
|
|
|
+@Slf4j
|
|
|
@RequiredArgsConstructor
|
|
|
@Transactional
|
|
|
public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo> implements StoreInfoService {
|
|
|
+ private static final int DEFAULT_DISTANCE_METER = 1000;
|
|
|
+
|
|
|
|
|
|
private final String DEFAULT_PASSWORD = "123456";
|
|
|
|
|
|
@@ -135,6 +143,18 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
|
|
|
|
|
|
private final LifeBlacklistMapper lifeBlacklistMapper;
|
|
|
|
|
|
+ private final OcrImageUploadMapper ocrImageUploadMapper;
|
|
|
+
|
|
|
+ private final AiAuthTokenUtil aiAuthTokenUtil;
|
|
|
+
|
|
|
+ private final RestTemplate restTemplate;
|
|
|
+
|
|
|
+ private final StoreImgService storeImgService;
|
|
|
+
|
|
|
+
|
|
|
+ /** 商户证照历史记录数据访问对象 */
|
|
|
+ private final StoreLicenseHistoryMapper licenseHistoryMapper;
|
|
|
+
|
|
|
@Resource
|
|
|
private StoreIncomeDetailsRecordService storeIncomeDetailsRecordService;
|
|
|
|
|
|
@@ -145,6 +165,12 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
|
|
|
@Value("${spring.web.resources.excel-generate-path}")
|
|
|
private String excelGeneratePath;
|
|
|
|
|
|
+ @Value("${third-party-identification.base-url}")
|
|
|
+ private String identificationPath;
|
|
|
+
|
|
|
+ @Value("${third-party-applications.base-url}")
|
|
|
+ private String applicationsPath;
|
|
|
+
|
|
|
/**
|
|
|
* 懒得查, 留着导出Excel
|
|
|
*/
|
|
|
@@ -191,9 +217,16 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
|
|
|
List<StoreImg> albumUrlList = storeImgMapper.selectList(albumUrlQueryWrapper);
|
|
|
storeMainInfoVo.setAlbumUrl(albumUrlList.stream().sorted(Comparator.comparing(StoreImg::getImgSort)).map(StoreImg::getImgUrl).collect(Collectors.toList()));
|
|
|
//推荐菜
|
|
|
- storeMainInfoVo.setRecommendUrl(storeMenuMapper.getStoreMenuList(id, 1).stream().sorted(Comparator.comparing(StoreMenuVo::getImgSort)).collect(Collectors.toList()));
|
|
|
+ storeMainInfoVo.setRecommendUrl(storeMenuMapper.getStoreMenuList(id, 1,1).stream().sorted(Comparator.comparing(StoreMenuVo::getImgSort)).collect(Collectors.toList()));
|
|
|
//菜单
|
|
|
- storeMainInfoVo.setMenuUrl(storeMenuMapper.getStoreMenuList(id, 0).stream().sorted(Comparator.comparing(StoreMenuVo::getImgSort)).collect(Collectors.toList()));
|
|
|
+ storeMainInfoVo.setMenuUrl(storeMenuMapper.getStoreMenuList(id, 0,1).stream().sorted(Comparator.comparing(StoreMenuVo::getImgSort)).collect(Collectors.toList()));
|
|
|
+
|
|
|
+ // todo 需要根据门店类型来判断
|
|
|
+ // 推荐酒水
|
|
|
+ storeMainInfoVo.setRecommendBeverageUrl(storeMenuMapper.getStoreMenuList(id, 1,1).stream().sorted(Comparator.comparing(StoreMenuVo::getImgSort)).collect(Collectors.toList()));
|
|
|
+ //酒单
|
|
|
+ storeMainInfoVo.setBeverageUrl(storeMenuMapper.getStoreMenuList(id, 0,1).stream().sorted(Comparator.comparing(StoreMenuVo::getImgSort)).collect(Collectors.toList()));
|
|
|
+
|
|
|
//门店标签
|
|
|
storeMainInfoVo.setStoreLabel(storeLabelMapper.selectOne(new LambdaQueryWrapper<StoreLabel>().eq(StoreLabel::getStoreId, id)));
|
|
|
//营业时间
|
|
|
@@ -476,6 +509,180 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
|
|
|
return storeInfoVoPage;
|
|
|
}
|
|
|
|
|
|
+ @Override
|
|
|
+ public IPage<StoreInfoVo> getNewStorePage(int page, int size, String storeName, String storeContact, String id, String storePhone, String storeType, String expiredState, String storeApplicationStatus, String storeStatus, String businessSection, String jingdu, String weidu, String renewContractStatus, String foodLicenceStatus, String foodLicenceWhetherExpiredStatus) {
|
|
|
+ checkAndUpdateExpiredRecords();
|
|
|
+ IPage<StoreInfoVo> iPage = new Page<>(page, size);
|
|
|
+ QueryWrapper<StoreInfoVo> queryWrapper = new QueryWrapper<>();
|
|
|
+ queryWrapper.like(storeName != null && !storeName.isEmpty(), "a.store_name", storeName).like(storeContact != null && !storeContact.isEmpty(), "b.name", storeContact).like(storePhone != null && !storePhone.isEmpty(), "b.phone", storePhone).like(storeType != null && !storeType.isEmpty(), "a.store_type", storeType).eq(StringUtils.isNotEmpty(storeApplicationStatus), "a.store_application_status", storeApplicationStatus).eq(StringUtils.isNotEmpty(businessSection), "a.business_section", businessSection).eq(StringUtils.isNotEmpty(storeStatus), "a.store_status", storeStatus).eq(StringUtils.isNotEmpty(renewContractStatus), "a.renew_contract_status", renewContractStatus).eq(StringUtils.isNotEmpty(foodLicenceStatus), "a.food_licence_status", foodLicenceStatus).like(StringUtils.isNotEmpty(id), "a.id", id).eq("a.delete_flag", 0).eq("b.delete_flag", 0).orderByDesc("a.created_time");
|
|
|
+ //如果查询未过期
|
|
|
+ // 获取当前时刻
|
|
|
+ Date currentDate = new Date();
|
|
|
+ // 获取当前日期和时间
|
|
|
+ Calendar calendar = Calendar.getInstance();
|
|
|
+ // 将时间设置为 0 点
|
|
|
+ calendar.set(Calendar.HOUR_OF_DAY, 0);
|
|
|
+ calendar.set(Calendar.MINUTE, 0);
|
|
|
+ calendar.set(Calendar.SECOND, 0);
|
|
|
+ calendar.set(Calendar.MILLISECOND, 0);
|
|
|
+ // 加上 30 天
|
|
|
+ calendar.add(Calendar.DAY_OF_MONTH, 30);
|
|
|
+ // 获取 Date 对象
|
|
|
+ Date thirtyDaysAgo = calendar.getTime();
|
|
|
+ if ("0".equals(expiredState)) {
|
|
|
+ queryWrapper.ge("a.expiration_time", thirtyDaysAgo);
|
|
|
+ } else if ("1".equals(expiredState)) {
|
|
|
+ queryWrapper.lt("a.expiration_time", thirtyDaysAgo);
|
|
|
+ queryWrapper.ge("a.expiration_time", currentDate);
|
|
|
+ } else if ("2".equals(expiredState)) {
|
|
|
+ queryWrapper.lt("a.expiration_time", currentDate);
|
|
|
+ }
|
|
|
+ // 获取当前时间
|
|
|
+ LocalDate nowDay = LocalDate.now();
|
|
|
+ //经营许可证到期状态筛选 0 查询食品经营许可证已到期
|
|
|
+ if (CommonConstant.FOOD_LICENCE_EXPIRE_STATUS.equals(foodLicenceWhetherExpiredStatus)) {
|
|
|
+ queryWrapper.lt("a.food_licence_expiration_time", nowDay);
|
|
|
+ } else if (CommonConstant.FOOD_LICENCE_ABOUT_TO_EXPIRE_STATUS.equals(foodLicenceWhetherExpiredStatus)) {//经营许可证筛选状态 1 查询食品经营许可证即将到期 距离到期时间30天内
|
|
|
+ queryWrapper.lt("a.food_licence_expiration_time", nowDay.plusDays(31))
|
|
|
+ .ge("a.food_licence_expiration_time", nowDay);
|
|
|
+ } else if (CommonConstant.FOOD_LICENCE_NOT_EXPIRED_STATUS.equals(foodLicenceWhetherExpiredStatus)) {//经营许可证筛选状态 2 查询食品经营许可证未到期 距离到期时间大于30天以上
|
|
|
+ queryWrapper.gt("a.food_licence_expiration_time", nowDay.plusDays(31));
|
|
|
+ }
|
|
|
+ //查出所有店铺
|
|
|
+ IPage<StoreInfoVo> storeInfoVoPage = storeInfoMapper.getNewStoreInfoVoPage(iPage, queryWrapper);
|
|
|
+ List<StoreInfoVo> records = storeInfoVoPage.getRecords();
|
|
|
+ if (!records.isEmpty()) {
|
|
|
+ // 提前查询所有需要的字典数据
|
|
|
+ List<StoreInfoVo> collect = records.stream().filter(record -> StringUtils.isNotEmpty(record.getStoreType())).collect(Collectors.toList());
|
|
|
+ Set<String> allTypes = collect.stream().map(StoreInfoVo::getStoreType).flatMap(type -> Arrays.stream(type.split(","))).collect(Collectors.toSet());
|
|
|
+
|
|
|
+ List<StoreDictionary> storeDictionaries = storeDictionaryMapper.selectList(new LambdaQueryWrapper<StoreDictionary>().eq(StoreDictionary::getTypeName, "storeType").isNull(StoreDictionary::getParentId).in(!allTypes.isEmpty(), StoreDictionary::getDictId, allTypes));
|
|
|
+ Map<String, String> typeMap = storeDictionaries.stream().collect(Collectors.toMap(StoreDictionary::getDictId, StoreDictionary::getDictDetail));
|
|
|
+
|
|
|
+ Map<String, List<LifeCoupon>> quanListByStoreId = new HashMap<>();
|
|
|
+ Map<Object, List<Map<String, Object>>> avgScoreMap = new HashMap<>();
|
|
|
+ Map<Object, List<Map<String, Object>>> avgPriceMap = new HashMap<>();
|
|
|
+ Map<Integer, List<StoreComment>> commentMap = new HashMap<>();
|
|
|
+ // 如果获取美食列表进行以下前置操作
|
|
|
+ if (StringUtils.isNotEmpty(businessSection) && businessSection.equals("1")) {
|
|
|
+ List<Integer> storeIds = records.stream().map(StoreInfoVo::getId).collect(Collectors.toList());
|
|
|
+ LambdaUpdateWrapper<LifeCoupon> quanWrapper = new LambdaUpdateWrapper<>();
|
|
|
+ quanWrapper.in(LifeCoupon::getStoreId, storeIds).eq(LifeCoupon::getStatus, 1).orderByDesc(LifeCoupon::getCreatedTime);
|
|
|
+ List<LifeCoupon> quanList = lifeCouponMapper.selectList(quanWrapper);
|
|
|
+ quanListByStoreId = quanList.stream().collect(Collectors.groupingBy(LifeCoupon::getStoreId));
|
|
|
+ // 获取全部店铺的评分与平均花销
|
|
|
+ // TODO sotre_comment 没有花销 怎么算平均花销
|
|
|
+ avgScoreMap = storeEvaluationMapper.allStoreAvgScore().stream().collect(Collectors.groupingBy(o -> o.get("store_id")));
|
|
|
+ commentMap = storeCommentMapper.selectList(new QueryWrapper<StoreComment>().eq("business_type", "5").eq("delete_flag", 0)).stream().collect(Collectors.groupingBy(StoreComment::getStoreId));
|
|
|
+ avgPriceMap = lifeUserOrderMapper.allStoreAvgPrice().stream().collect(Collectors.groupingBy(o -> o.get("store_id")));
|
|
|
+ }
|
|
|
+ for (StoreInfoVo record : records) {
|
|
|
+ //处理类型
|
|
|
+ if (StringUtils.isNotEmpty(record.getStoreType())) {
|
|
|
+ String[] types = record.getStoreType().split(",");
|
|
|
+ List<String> typeDetails = Arrays.stream(types).map(typeMap::get).filter(Objects::nonNull).collect(Collectors.toList());
|
|
|
+ record.setStoreTypeStr(String.join(",", typeDetails));
|
|
|
+ record.setStoreTypeList(Arrays.asList(types));
|
|
|
+ }
|
|
|
+ //写经纬度
|
|
|
+ String[] split = record.getStorePosition().split(",");
|
|
|
+ record.setStorePositionLongitude(split[0]);
|
|
|
+ record.setStorePositionLatitude(split[1]);
|
|
|
+ //处理一下到期状态
|
|
|
+ Date expirationTime = record.getExpirationTime();
|
|
|
+ if (expirationTime != null) {
|
|
|
+ // 获取当前时间
|
|
|
+ Calendar now = Calendar.getInstance();
|
|
|
+ Date nowCurrentDate = now.getTime();
|
|
|
+ // 计算 30 天后的时间
|
|
|
+ now.add(Calendar.DAY_OF_YEAR, 30);
|
|
|
+ Date thirtyDaysLater = now.getTime();
|
|
|
+ // 比较两个日期
|
|
|
+ if (expirationTime.after(currentDate)) {
|
|
|
+ record.setExpiredState("0");
|
|
|
+ if (expirationTime != null && (expirationTime.after(nowCurrentDate) || expirationTime.equals(nowCurrentDate)) && expirationTime.before(thirtyDaysLater)) {
|
|
|
+ record.setExpiredState("1");
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ record.setExpiredState("2");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取当前时间
|
|
|
+ LocalDate nowLocal = LocalDate.now();
|
|
|
+ // 将 expirationTime 转换为 LocalDate
|
|
|
+ LocalDate expDate = expirationTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
|
|
|
+ // 计算距离到期的天数
|
|
|
+ long daysToExpire = ChronoUnit.DAYS.between(nowLocal, expDate);
|
|
|
+ record.setDaysToExpire(daysToExpire);
|
|
|
+ }
|
|
|
+ // 处理经营许可证到期时间回显状态 根据经营许可证筛选状态筛选
|
|
|
+ Date foodDate = record.getFoodLicenceExpirationTime();
|
|
|
+ if (foodDate != null) {
|
|
|
+ // 获取当前时间
|
|
|
+ LocalDate nowLocal = LocalDate.now();
|
|
|
+ LocalDate localDate = foodDate.toInstant() // Date -> Instant(UTC时间戳)
|
|
|
+ .atZone(ZoneId.systemDefault()) // Instant -> ZonedDateTime(系统默认时区)
|
|
|
+ .toLocalDate();
|
|
|
+ long remainingDays = ChronoUnit.DAYS.between(nowLocal, localDate);
|
|
|
+ if (remainingDays == -1) {
|
|
|
+ record.setFoodLicenceRemainingDays("0天");
|
|
|
+ record.setFoodLicenceExpireStatus("已到期");
|
|
|
+ } else if (remainingDays >= 0 && remainingDays <= 30) {
|
|
|
+ record.setFoodLicenceRemainingDays(remainingDays + "天");
|
|
|
+ record.setFoodLicenceExpireStatus("即将到期");
|
|
|
+ } else if (remainingDays > 30) {
|
|
|
+ record.setFoodLicenceRemainingDays(remainingDays + "天");
|
|
|
+ record.setFoodLicenceExpireStatus("未到期");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ // 处理状态
|
|
|
+ if (record.getLogoutFlag() == 1) {
|
|
|
+ record.setStoreStatusStr("已注销");
|
|
|
+ record.setStoreStatus(2);
|
|
|
+ }
|
|
|
+ // 根据八大类不同进行个性化操作
|
|
|
+ if (StringUtils.isNotEmpty(businessSection)) {
|
|
|
+ switch (businessSection) {
|
|
|
+ case "1":
|
|
|
+ record.setAvgScore("0");
|
|
|
+ record.setAvgPrice("0");
|
|
|
+ record.setTotalNum("0");
|
|
|
+
|
|
|
+ if ((jingdu != null && !jingdu.isEmpty()) && (weidu != null && !weidu.isEmpty())) {
|
|
|
+ double storeJing = Double.parseDouble(split[0]);
|
|
|
+ double storeWei = Double.parseDouble(split[1]);
|
|
|
+ double storeDistance = DistanceUtil.haversineCalculateDistance(Double.parseDouble(jingdu), Double.parseDouble(weidu), storeJing, storeWei);
|
|
|
+ record.setDistance(storeDistance);
|
|
|
+ } else {
|
|
|
+ record.setDistance(0);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置店铺得分,设置店铺人均消费,设置总评论数
|
|
|
+ if (avgScoreMap.containsKey(String.valueOf(record.getId()))) {
|
|
|
+ record.setAvgScore(String.valueOf(avgScoreMap.get(String.valueOf(record.getId())).get(0).get("avg_score")));
|
|
|
+ record.setAvgPrice(String.valueOf(avgPriceMap.get(String.valueOf(record.getId())).get(0).get("avg_price")));
|
|
|
+ }
|
|
|
+ // 设置店铺得分,设置店铺人均消费,设置总评论数
|
|
|
+ if (commentMap.containsKey(record.getId())) {
|
|
|
+ record.setTotalNum(String.valueOf(commentMap.get(record.getId()).size()));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置店铺优惠券列表
|
|
|
+ if (!quanListByStoreId.isEmpty() && quanListByStoreId.containsKey(String.valueOf(record.getId()))) {
|
|
|
+ record.setQuanList(quanListByStoreId.get(String.valueOf(record.getId())));
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ toExcel = storeInfoVoPage.getRecords();
|
|
|
+ return storeInfoVoPage;
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 重置门店密码
|
|
|
*
|
|
|
@@ -667,6 +874,16 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
|
|
|
storeImg.setImgUrl(storeInfoDto.getFoodLicenceUrl());
|
|
|
storeImgMapper.insert(storeImg);
|
|
|
}
|
|
|
+ //存入店铺娱乐经营许可证图片
|
|
|
+ if (StringUtils.isNotEmpty(storeInfoDto.getEntertainmentLicenceUrl())) {
|
|
|
+ StoreImg storeImg = new StoreImg();
|
|
|
+ storeImg.setStoreId(storeInfo.getId());
|
|
|
+ storeImg.setImgType(26);
|
|
|
+ storeImg.setImgSort(0);
|
|
|
+ storeImg.setImgDescription("娱乐经营许可证审核通过图片");
|
|
|
+ storeImg.setImgUrl(storeInfoDto.getEntertainmentLicenceUrl());
|
|
|
+ storeImgMapper.insert(storeImg);
|
|
|
+ }
|
|
|
|
|
|
//初始化标签数据
|
|
|
LambdaQueryWrapper<TagStoreRelation> tagStoreRelationLambdaQueryWrapper = new LambdaQueryWrapper<>();
|
|
|
@@ -1297,6 +1514,14 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
|
|
|
if (!storeDictionaries.isEmpty()) {
|
|
|
result.setBusinessStatusStr(storeDictionaries.get(0).getDictDetail());
|
|
|
}
|
|
|
+ // TODO 之后修改********** 正常OcrType由前端传存储ocr表要加新字段。传参要由前端传。
|
|
|
+ // 查询并设置各类证件OCR信息
|
|
|
+ result.setJyxkz(convertOcrResultToJson(storeUser.getId(), "BUSINESS_LICENSE", "娱乐", true));
|
|
|
+ result.setIdcardFace(convertOcrResultToJson(storeUser.getId(), "ID_CARD", "face", true));
|
|
|
+ result.setIdcardBack(convertOcrResultToJson(storeUser.getId(), "ID_CARD", "back", true));
|
|
|
+ result.setFoodLicence(convertOcrResultToJson(storeUser.getId(), "FOOD_MANAGE_LICENSE", null, true));
|
|
|
+ result.setEntertainmentLicence(convertOcrResultToJson(storeUser.getId(), "BUSINESS_LICENSE", "营业执照", false));
|
|
|
+
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
@@ -2196,6 +2421,16 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
|
|
|
storeImg.setImgType(24);
|
|
|
storeImg.setImgDescription("经营许可证审核通过前图片");
|
|
|
storeImgMapper.insert(storeImg);
|
|
|
+
|
|
|
+ // 经营许可证历史表插入
|
|
|
+ StoreLicenseHistory licenseHistory = new StoreLicenseHistory();
|
|
|
+ licenseHistory.setStoreId(storeImg.getStoreId());
|
|
|
+ licenseHistory.setLicenseStatus(2);
|
|
|
+ licenseHistory.setLicenseExecuteStatus(2);
|
|
|
+ licenseHistory.setImgUrl(storeImg.getImgUrl());
|
|
|
+ licenseHistory.setDeleteFlag(0);
|
|
|
+ licenseHistoryMapper.insert(licenseHistory);
|
|
|
+
|
|
|
//更新店铺
|
|
|
StoreInfo storeInfo = new StoreInfo();
|
|
|
storeInfo.setFoodLicenceStatus(2);
|
|
|
@@ -2284,6 +2519,26 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
|
|
|
LambdaUpdateWrapper<StoreImg> imgLambdaUpdateWrapper = new LambdaUpdateWrapper();
|
|
|
imgLambdaUpdateWrapper.in(StoreImg::getId, imgList).set(StoreImg::getImgType, 25).set(StoreImg::getImgDescription,"经营许可证审核通过图片");
|
|
|
int num = storeImgMapper.update(null, imgLambdaUpdateWrapper);
|
|
|
+
|
|
|
+
|
|
|
+ // 将原来的食品经营许可证历史表数据删除
|
|
|
+ LambdaUpdateWrapper<StoreLicenseHistory> wrapper = new LambdaUpdateWrapper<>();
|
|
|
+ wrapper.eq(StoreLicenseHistory::getStoreId, id);
|
|
|
+ wrapper.eq(StoreLicenseHistory::getLicenseStatus, 2);
|
|
|
+ wrapper.eq(StoreLicenseHistory::getLicenseExecuteStatus, 1);
|
|
|
+ wrapper.eq(StoreLicenseHistory::getDeleteFlag, 0);
|
|
|
+ wrapper.set(StoreLicenseHistory::getDeleteFlag, 1);
|
|
|
+ licenseHistoryMapper.update(null, wrapper);
|
|
|
+
|
|
|
+ // 将新的食品经营许可证历史表数据变为审核通过
|
|
|
+ LambdaUpdateWrapper<StoreLicenseHistory> wrapper1 = new LambdaUpdateWrapper<>();
|
|
|
+ wrapper1.eq(StoreLicenseHistory::getStoreId, id);
|
|
|
+ wrapper1.eq(StoreLicenseHistory::getLicenseStatus, 2);
|
|
|
+ wrapper1.eq(StoreLicenseHistory::getLicenseExecuteStatus, 2);
|
|
|
+ wrapper1.eq(StoreLicenseHistory::getDeleteFlag, 0);
|
|
|
+ wrapper1.set(StoreLicenseHistory::getLicenseExecuteStatus, 1);
|
|
|
+ licenseHistoryMapper.update(null, wrapper1);
|
|
|
+
|
|
|
return num;
|
|
|
}
|
|
|
|
|
|
@@ -2321,4 +2576,2129 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
|
|
|
}
|
|
|
return result;
|
|
|
}
|
|
|
+
|
|
|
+ public void updateLicenseHistoryInfo(Integer storeId, Integer foodLicenceStatus) {
|
|
|
+ if (foodLicenceStatus == 3) {
|
|
|
+ // 审核拒绝时修改提交记录
|
|
|
+ LambdaUpdateWrapper<StoreLicenseHistory> wrapper = new LambdaUpdateWrapper<>();
|
|
|
+ wrapper.eq(StoreLicenseHistory::getStoreId, storeId);
|
|
|
+ wrapper.eq(StoreLicenseHistory::getLicenseStatus, 1);
|
|
|
+ wrapper.eq(StoreLicenseHistory::getLicenseExecuteStatus, 2);
|
|
|
+ wrapper.set(StoreLicenseHistory::getLicenseExecuteStatus, 3);
|
|
|
+ wrapper.set(StoreLicenseHistory::getReasonRefusal, storeId);
|
|
|
+ wrapper.set(StoreLicenseHistory::getDeleteFlag, 1);
|
|
|
+ licenseHistoryMapper.update(null, wrapper);
|
|
|
+ } else {
|
|
|
+ // 将原来的食品经营许可证历史表数据删除
|
|
|
+ LambdaUpdateWrapper<StoreLicenseHistory> wrapper = new LambdaUpdateWrapper<>();
|
|
|
+ wrapper.eq(StoreLicenseHistory::getStoreId, storeId);
|
|
|
+ wrapper.eq(StoreLicenseHistory::getLicenseStatus, 1);
|
|
|
+ wrapper.eq(StoreLicenseHistory::getLicenseExecuteStatus, 1);
|
|
|
+ wrapper.eq(StoreLicenseHistory::getDeleteFlag, 0);
|
|
|
+ wrapper.set(StoreLicenseHistory::getDeleteFlag, 1);
|
|
|
+ licenseHistoryMapper.update(null, wrapper);
|
|
|
+
|
|
|
+ // 将新的食品经营许可证历史表数据变为审核通过
|
|
|
+ LambdaUpdateWrapper<StoreLicenseHistory> wrapper1 = new LambdaUpdateWrapper<>();
|
|
|
+ wrapper1.eq(StoreLicenseHistory::getStoreId, storeId);
|
|
|
+ wrapper1.eq(StoreLicenseHistory::getLicenseStatus, 1);
|
|
|
+ wrapper1.eq(StoreLicenseHistory::getLicenseExecuteStatus, 2);
|
|
|
+ wrapper1.eq(StoreLicenseHistory::getDeleteFlag, 0);
|
|
|
+ wrapper1.set(StoreLicenseHistory::getLicenseExecuteStatus, 1);
|
|
|
+ licenseHistoryMapper.update(null, wrapper1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void aiApproveStoreInfo(AiApproveStoreInfo aiApproveStoreInfo) {
|
|
|
+ HttpHeaders aiHeaders = new HttpHeaders();
|
|
|
+ aiHeaders.setContentType(MediaType.APPLICATION_JSON);
|
|
|
+ aiHeaders.set("Authorization", "Bearer " + aiAuthTokenUtil.getAccessToken());
|
|
|
+
|
|
|
+ HttpEntity<AiApproveStoreInfo> request = new HttpEntity<>(aiApproveStoreInfo, aiHeaders);
|
|
|
+ ResponseEntity<String> response = null;
|
|
|
+ try {
|
|
|
+ response = restTemplate.postForEntity(applicationsPath, request, String.class);
|
|
|
+ if (response.getStatusCodeValue() != 200) {
|
|
|
+ throw new RuntimeException("AI门店审核接口调用失败 http状态:" + response.getStatusCode());
|
|
|
+ }
|
|
|
+ if (StringUtils.isNotEmpty(response.getBody())) {
|
|
|
+ JSONObject jsonObject = JSONObject.parseObject(response.getBody());
|
|
|
+ if (jsonObject.getInteger("code") == 200) {
|
|
|
+ JSONObject data = jsonObject.getJSONObject("data");
|
|
|
+ List<StoreInfo> storeInfos = storeInfoMapper.selectList(new LambdaQueryWrapper<StoreInfo>()
|
|
|
+ .eq(StoreInfo::getCreatedUserId, aiApproveStoreInfo.getUserId()).eq(StoreInfo::getStoreApplicationStatus, 0).eq(StoreInfo::getDeleteFlag, 0));
|
|
|
+ for (StoreInfo storeInfo : storeInfos) {
|
|
|
+ if ("approved".equals(data.getString("status"))) {
|
|
|
+ approveStoreInfo(storeInfo.getId().toString(),1, "审核通过");
|
|
|
+ } else if ("rejected".equals(data.getString("status"))) {
|
|
|
+ approveStoreInfo(storeInfo.getId().toString(),2, data.getString("audit_summary"));
|
|
|
+ } else {
|
|
|
+ System.out.println("未知状态");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ throw new RuntimeException("AI门店审核接口调用失败 code:" + jsonObject.getInteger("code"));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (Exception e){
|
|
|
+ throw new RuntimeException("调用门店审核接口异常", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Map<String, Object> getStoreOcrData(String storeUserId, String imageUrl) {
|
|
|
+ Map<String, Object> map = new HashMap<>();
|
|
|
+ LambdaQueryWrapper<OcrImageUpload> queryWrapper = new LambdaQueryWrapper<>();
|
|
|
+ queryWrapper.eq(OcrImageUpload::getStoreUserId, storeUserId)
|
|
|
+ .eq(OcrImageUpload::getOcrType, OcrTypeEnum.BUSINESS_LICENSE.getCode());
|
|
|
+ OcrImageUpload ocrImageUploads = ocrImageUploadMapper.selectOne(queryWrapper);
|
|
|
+ if(ocrImageUploads== null){
|
|
|
+ throw new RuntimeException("未找到OCI识别数据!");
|
|
|
+ }
|
|
|
+ String accessToken = aiAuthTokenUtil.getAccessToken();
|
|
|
+
|
|
|
+ HttpHeaders aiHeaders = new HttpHeaders();
|
|
|
+ aiHeaders.setContentType(MediaType.APPLICATION_JSON);
|
|
|
+ aiHeaders.set("Authorization", "Bearer " + accessToken);
|
|
|
+ Map<String, Object> jsonBody = new HashMap<>();
|
|
|
+ List<String> imageUrls = new ArrayList<>();
|
|
|
+ imageUrls.add(imageUrl);
|
|
|
+ jsonBody.put("image_urls", imageUrls);
|
|
|
+ String ocrResult = ocrImageUploads.getOcrResult();
|
|
|
+ JSONObject jsonObject = JSONObject.parseObject(ocrResult);
|
|
|
+ jsonBody.put("merchant_name", jsonObject.get("companyName"));
|
|
|
+
|
|
|
+ HttpEntity<Map<String, Object>> request = new HttpEntity<>(jsonBody, aiHeaders);
|
|
|
+ ResponseEntity<String> response = null;
|
|
|
+ HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
|
|
|
+ factory.setConnectTimeout(5000);
|
|
|
+ factory.setReadTimeout(60000);
|
|
|
+ restTemplate.setRequestFactory(factory);
|
|
|
+ try {
|
|
|
+ response = restTemplate.postForEntity(identificationPath, request, String.class);
|
|
|
+ if (response.getStatusCodeValue() != 200) {
|
|
|
+ throw new RuntimeException("AI内容审核接口调用失败 http状态:" + response.getStatusCode());
|
|
|
+ }
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ throw new RuntimeException("AI接口调用失败");
|
|
|
+ }
|
|
|
+ com.alibaba.fastjson2.JSONObject responseNode = com.alibaba.fastjson2.JSONObject.parseObject(response.getBody());
|
|
|
+ if (responseNode == null) {
|
|
|
+ throw new RuntimeException("AI接口调用失败,响应内容为空");
|
|
|
+ } else {
|
|
|
+ Integer code = responseNode.getInteger("code");
|
|
|
+ if (code == 200) {
|
|
|
+ Map<String, Object> dataMap = (Map<String, Object>) responseNode.get("data");
|
|
|
+ if (Objects.nonNull(dataMap)) {
|
|
|
+ // 获取match_results(JSON中是数组,反序列化为List<Map>)
|
|
|
+ List<Map<String, Object>> matchResults = (List<Map<String, Object>>) dataMap.get("match_results");
|
|
|
+ map.put("overall_match", dataMap.get("overall_match"));
|
|
|
+ if (Objects.nonNull(matchResults) && !matchResults.isEmpty()) {
|
|
|
+ map.put("match_reason", matchResults.get(0).get("match_reason"));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ throw new RuntimeException("AI接口调用失败,错误码: " + code);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return map;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取活动banner图
|
|
|
+ */
|
|
|
+
|
|
|
+ public List<StoreImg> getBannerUrl(String storeId){
|
|
|
+ LambdaQueryWrapper<StoreImg> queryWrapper = new LambdaQueryWrapper<StoreImg>()
|
|
|
+ .eq(StoreImg::getStoreId, Integer.parseInt(storeId))
|
|
|
+ .eq(StoreImg::getImgType, 26)
|
|
|
+ .eq(StoreImg::getDeleteFlag, 0);
|
|
|
+ return storeImgMapper.selectList(queryWrapper);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取活动详情banner图
|
|
|
+ */
|
|
|
+
|
|
|
+ public List<StoreImg> getBannerUrlInfo(String storeId, Integer businessId){
|
|
|
+ LambdaQueryWrapper<StoreImg> queryWrapper = new LambdaQueryWrapper<StoreImg>()
|
|
|
+ .eq(StoreImg::getStoreId, Integer.parseInt(storeId))
|
|
|
+ .eq(StoreImg::getImgType, 27)
|
|
|
+ .eq(StoreImg::getDeleteFlag, 0)
|
|
|
+ .eq(StoreImg::getBusinessId, businessId);
|
|
|
+ return storeImgMapper.selectList(queryWrapper);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public IPage<StoreInfoVo> getLifeServicesByDistance(Double lon, Double lat, Double distance, Integer sortType, Integer businessType, Integer categoryId, int pageNum, int pageSize) {
|
|
|
+ // 参数校验
|
|
|
+ if (lon == null || lat == null) {
|
|
|
+ throw new IllegalArgumentException("经纬度参数不能为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 智能检测:如果参数可能传反了(lat超出范围但lon在范围内),给出提示
|
|
|
+ if (Math.abs(lat) > 90 && Math.abs(lon) <= 90) {
|
|
|
+ throw new IllegalArgumentException(
|
|
|
+ String.format("参数可能传反了!当前值: lon=%s, lat=%s。经度范围: [-180, 180],纬度范围: [-90, 90]。请检查参数顺序。", lon, lat)
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ // 校验经纬度范围:经度 [-180, 180],纬度 [-90, 90]
|
|
|
+ if (lon < -180 || lon > 180) {
|
|
|
+ throw new IllegalArgumentException("经度参数超出有效范围 [-180, 180],当前值: " + lon);
|
|
|
+ }
|
|
|
+ if (lat < -90 || lat > 90) {
|
|
|
+ throw new IllegalArgumentException("纬度参数超出有效范围 [-90, 90],当前值: " + lat);
|
|
|
+ }
|
|
|
+ final int finalSortType = (sortType == null || sortType < 1 || sortType > 3) ? 1 : sortType; // 默认智能排序
|
|
|
+ if (pageNum <= 0) {
|
|
|
+ pageNum = 1;
|
|
|
+ }
|
|
|
+ if (pageSize <= 0) {
|
|
|
+ pageSize = 10;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构建查询条件
|
|
|
+ QueryWrapper<StoreInfoVo> queryWrapper = new QueryWrapper<>();
|
|
|
+
|
|
|
+ // 如果传入了dictId,根据字典id查询经营板块、经营种类、分类并匹配店铺
|
|
|
+ if (categoryId != null) {
|
|
|
+ // 根据categoryId查询字典表记录
|
|
|
+ StoreDictionary dict = storeDictionaryMapper.selectById(categoryId);
|
|
|
+ if (dict == null || dict.getDeleteFlag() == 1) {
|
|
|
+ throw new IllegalArgumentException("字典id不存在或已删除: " + categoryId);
|
|
|
+ }
|
|
|
+
|
|
|
+ String typeName = dict.getTypeName();
|
|
|
+ String dictIdStr =dict.getDictId();
|
|
|
+
|
|
|
+ if ("business_section".equals(typeName)) {
|
|
|
+ // 如果是经营板块,直接匹配business_section
|
|
|
+ try {
|
|
|
+ Integer sectionId = Integer.parseInt(dictIdStr);
|
|
|
+ queryWrapper.eq("a.business_section", sectionId);
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ throw new IllegalArgumentException("经营板块dictId格式错误: " + dictIdStr);
|
|
|
+ }
|
|
|
+ } else if ("business_type".equals(typeName)) {
|
|
|
+ // 如果是经营种类,需要:
|
|
|
+ // 1. 向上查找父级(经营板块)
|
|
|
+ // 2. 匹配business_section和business_types
|
|
|
+ StoreDictionary parentDict = storeDictionaryMapper.selectById(dict.getParentId());
|
|
|
+ if (parentDict == null || !"business_section".equals(parentDict.getTypeName())) {
|
|
|
+ throw new IllegalArgumentException("经营种类的父级不是经营板块,categoryId: " + categoryId);
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ Integer sectionId = Integer.parseInt(parentDict.getDictId());
|
|
|
+ queryWrapper.eq("a.business_section", sectionId);
|
|
|
+ // 使用FIND_IN_SET匹配business_types字段(逗号分隔)
|
|
|
+ queryWrapper.apply("FIND_IN_SET({0}, a.business_types) > 0", dictIdStr);
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ throw new IllegalArgumentException("经营板块或经营种类dictId格式错误");
|
|
|
+ }
|
|
|
+ } else if ("business_classify".equals(typeName)) {
|
|
|
+ // 如果是分类,需要:
|
|
|
+ // 1. 向上查找父级(经营种类)
|
|
|
+ // 2. 向上查找祖父级(经营板块)
|
|
|
+ // 3. 匹配business_section、business_types和business_classify
|
|
|
+ StoreDictionary parentDict = storeDictionaryMapper.selectById(dict.getParentId());
|
|
|
+ if (parentDict == null || !"business_type".equals(parentDict.getTypeName())) {
|
|
|
+ throw new IllegalArgumentException("分类的父级不是经营种类,categoryId: " + categoryId);
|
|
|
+ }
|
|
|
+
|
|
|
+ StoreDictionary grandParentDict = storeDictionaryMapper.selectById(parentDict.getParentId());
|
|
|
+ if (grandParentDict == null || !"business_section".equals(grandParentDict.getTypeName())) {
|
|
|
+ throw new IllegalArgumentException("经营种类的父级不是经营板块,categoryId: " + categoryId);
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ Integer sectionId = Integer.parseInt(grandParentDict.getDictId());
|
|
|
+ queryWrapper.eq("a.business_section", sectionId);
|
|
|
+ // 使用FIND_IN_SET匹配business_types字段
|
|
|
+ queryWrapper.apply("FIND_IN_SET({0}, a.business_types) > 0", parentDict.getDictId());
|
|
|
+ // 使用FIND_IN_SET匹配business_classify字段
|
|
|
+ queryWrapper.apply("FIND_IN_SET({0}, a.business_classify) > 0", dictIdStr);
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ throw new IllegalArgumentException("经营板块、经营种类或分类dictId格式错误");
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ throw new IllegalArgumentException("不支持的字典类型: " + typeName + ", categoryId: " + categoryId);
|
|
|
+ }
|
|
|
+ } else if (businessType != null) {
|
|
|
+ // 如果指定了businessType,则根据传入的数值进行筛选
|
|
|
+ // 直接使用传入的数值作为business_section进行筛选
|
|
|
+ // KTV=3、洗浴汗蒸=4、按摩足浴=5,酒吧的数值未知(由调用方传入)
|
|
|
+ queryWrapper.eq("a.business_section", businessType);
|
|
|
+ } else {
|
|
|
+ // 如果没有指定businessType,则查询所有两种类型的店铺
|
|
|
+ // 需要查询字典表获取所有四种类型的dictId
|
|
|
+ List<String> storeTypeNames = Arrays.asList("丽人美发","运动健身");
|
|
|
+ List<StoreDictionary> storeDictionaries = storeDictionaryMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<StoreDictionary>()
|
|
|
+ .eq(StoreDictionary::getTypeName, "business_section")
|
|
|
+ .in(StoreDictionary::getDictDetail, storeTypeNames)
|
|
|
+ .eq(StoreDictionary::getDeleteFlag, 0)
|
|
|
+ );
|
|
|
+
|
|
|
+ List<Integer> businessSectionIds = storeDictionaries.stream()
|
|
|
+ .filter(d -> StringUtils.isNotEmpty(d.getDictId()))
|
|
|
+ .map(StoreDictionary::getDictId)
|
|
|
+ .map(dictIdStr -> {
|
|
|
+ try {
|
|
|
+ return Integer.parseInt(dictIdStr);
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .filter(Objects::nonNull)
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ // 使用business_section字段进行筛选
|
|
|
+ queryWrapper.in("a.business_section", businessSectionIds);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 过滤已删除的门店
|
|
|
+ queryWrapper.eq("a.delete_flag", 0);
|
|
|
+ // 过滤注销的门店
|
|
|
+ queryWrapper.eq("a.logout_flag", 0);
|
|
|
+ // 过滤禁用的门店
|
|
|
+ queryWrapper.ne("a.store_status", 0);
|
|
|
+ // 过滤永久关门的店铺
|
|
|
+ queryWrapper.ne("a.business_status", 99);
|
|
|
+ // 过滤过期的经营许可证
|
|
|
+ queryWrapper.ge("a.entertainment_licence_expiration_time", new Date());
|
|
|
+
|
|
|
+ // 距离优先模式:只显示10公里内且3.5星以上的店铺
|
|
|
+ final Double finalDistance;
|
|
|
+ if (finalSortType == 3) {
|
|
|
+ queryWrapper.ge("a.score_avg", 3.5);
|
|
|
+ if (distance == null || distance <= 0) {
|
|
|
+ finalDistance = 10.0; // 默认10公里
|
|
|
+ } else if (distance > 10) {
|
|
|
+ finalDistance = 10.0; // 距离优先最多10公里
|
|
|
+ } else {
|
|
|
+ finalDistance = distance;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ finalDistance = distance;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 先按距离排序获取所有数据(用于计算距离)
|
|
|
+ queryWrapper.orderByAsc("dist");
|
|
|
+
|
|
|
+ // 创建分页对象(先获取更多数据用于排序计算)
|
|
|
+ IPage<StoreInfoVo> page = new Page<>(1, 1000); // 先获取足够多的数据
|
|
|
+ IPage<StoreInfoVo> storeInfoIPage = storeInfoMapper.getPageForDistance(page, lon + "," + lat, queryWrapper);
|
|
|
+ List<StoreInfoVo> storeInfoVoList = storeInfoIPage.getRecords();
|
|
|
+
|
|
|
+ // 如果指定了距离范围,进行距离筛选
|
|
|
+ if (finalDistance != null && finalDistance > 0) {
|
|
|
+ storeInfoVoList = storeInfoVoList.stream()
|
|
|
+ .filter(store -> {
|
|
|
+ String distStr = store.getDist();
|
|
|
+ if (distStr == null || distStr.isEmpty()) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ double storeDistance = Double.parseDouble(distStr);
|
|
|
+ return storeDistance <= finalDistance;
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算时间范围
|
|
|
+ LocalDateTime now = LocalDateTime.now();
|
|
|
+ LocalDateTime sevenDaysAgo = now.minusDays(7);
|
|
|
+ LocalDateTime thirtyDaysAgo = now.minusDays(30);
|
|
|
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
|
|
+ String sevenDaysAgoStr = sevenDaysAgo.format(formatter);
|
|
|
+ String thirtyDaysAgoStr = thirtyDaysAgo.format(formatter);
|
|
|
+
|
|
|
+ // 提取所有门店ID
|
|
|
+ List<Integer> storeIds = storeInfoVoList.stream().map(StoreInfoVo::getId).collect(Collectors.toList());
|
|
|
+
|
|
|
+ if (CollectionUtils.isEmpty(storeIds)) {
|
|
|
+ // 如果没有符合条件的店铺,返回空结果
|
|
|
+ IPage<StoreInfoVo> emptyPage = new Page<>(pageNum, pageSize);
|
|
|
+ emptyPage.setRecords(new ArrayList<>());
|
|
|
+ emptyPage.setTotal(0);
|
|
|
+ return emptyPage;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 查询近7天销量(订单数)
|
|
|
+ Map<Integer, Long> sales7DaysMap = new HashMap<>();
|
|
|
+ if (finalSortType == 1) { // 智能排序需要销量数据
|
|
|
+ LambdaQueryWrapper<LifeUserOrder> orderWrapper = new LambdaQueryWrapper<>();
|
|
|
+ orderWrapper.in(LifeUserOrder::getStoreId, storeIds)
|
|
|
+ .and(w -> w.and(w1 -> w1.eq(LifeUserOrder::getStatus, 7)
|
|
|
+ .ge(LifeUserOrder::getFinishTime, sevenDaysAgoStr))
|
|
|
+ .or(w2 -> w2.eq(LifeUserOrder::getStatus, 1)
|
|
|
+ .ge(LifeUserOrder::getPayTime, sevenDaysAgoStr)))
|
|
|
+ .eq(LifeUserOrder::getDeleteFlag, 0);
|
|
|
+ List<LifeUserOrder> orders7Days = lifeUserOrderMapper.selectList(orderWrapper);
|
|
|
+ Map<Integer, Long> tempSalesMap = orders7Days.stream()
|
|
|
+ .filter(order -> order.getStoreId() != null)
|
|
|
+ .collect(Collectors.groupingBy(
|
|
|
+ order -> Integer.parseInt(order.getStoreId()),
|
|
|
+ Collectors.counting()));
|
|
|
+ sales7DaysMap.putAll(tempSalesMap);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 查询近30天好评数(score >= 4.5)
|
|
|
+ Map<Integer, Long> goodComment30DaysMap = new HashMap<>();
|
|
|
+ // 查询近7天评论数
|
|
|
+ Map<Integer, Long> comment7DaysMap = new HashMap<>();
|
|
|
+ // 查询总评论数
|
|
|
+ Map<Integer, Long> totalCommentMap = new HashMap<>();
|
|
|
+
|
|
|
+ if (finalSortType == 2) { // 好评优先需要评论数据
|
|
|
+ // 近30天好评数
|
|
|
+ LambdaQueryWrapper<StoreComment> goodCommentWrapper = new LambdaQueryWrapper<>();
|
|
|
+ goodCommentWrapper.in(StoreComment::getStoreId, storeIds)
|
|
|
+ .eq(StoreComment::getBusinessType, 5)
|
|
|
+ .eq(StoreComment::getDeleteFlag, 0)
|
|
|
+ .isNull(StoreComment::getReplyId)
|
|
|
+ .ge(StoreComment::getScore, 4.5)
|
|
|
+ .ge(StoreComment::getCreatedTime, thirtyDaysAgoStr);
|
|
|
+ List<StoreComment> goodComments30Days = storeCommentMapper.selectList(goodCommentWrapper);
|
|
|
+ Map<Integer, Long> tempGoodCommentMap = goodComments30Days.stream()
|
|
|
+ .collect(Collectors.groupingBy(StoreComment::getStoreId, Collectors.counting()));
|
|
|
+ goodComment30DaysMap.putAll(tempGoodCommentMap);
|
|
|
+
|
|
|
+ // 近7天评论数
|
|
|
+ LambdaQueryWrapper<StoreComment> comment7DaysWrapper = new LambdaQueryWrapper<>();
|
|
|
+ comment7DaysWrapper.in(StoreComment::getStoreId, storeIds)
|
|
|
+ .eq(StoreComment::getBusinessType, 5)
|
|
|
+ .eq(StoreComment::getDeleteFlag, 0)
|
|
|
+ .isNull(StoreComment::getReplyId)
|
|
|
+ .ge(StoreComment::getCreatedTime, sevenDaysAgoStr);
|
|
|
+ List<StoreComment> comments7Days = storeCommentMapper.selectList(comment7DaysWrapper);
|
|
|
+ Map<Integer, Long> tempComment7DaysMap = comments7Days.stream()
|
|
|
+ .collect(Collectors.groupingBy(StoreComment::getStoreId, Collectors.counting()));
|
|
|
+ comment7DaysMap.putAll(tempComment7DaysMap);
|
|
|
+
|
|
|
+ // 总评论数
|
|
|
+ LambdaQueryWrapper<StoreComment> totalCommentWrapper = new LambdaQueryWrapper<>();
|
|
|
+ totalCommentWrapper.in(StoreComment::getStoreId, storeIds)
|
|
|
+ .eq(StoreComment::getBusinessType, 5)
|
|
|
+ .eq(StoreComment::getDeleteFlag, 0)
|
|
|
+ .isNull(StoreComment::getReplyId);
|
|
|
+ List<StoreComment> totalComments = storeCommentMapper.selectList(totalCommentWrapper);
|
|
|
+ Map<Integer, Long> tempTotalCommentMap = totalComments.stream()
|
|
|
+ .collect(Collectors.groupingBy(StoreComment::getStoreId, Collectors.counting()));
|
|
|
+ totalCommentMap.putAll(tempTotalCommentMap);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取全部店铺的评分
|
|
|
+ Map<String, List<Map<String, Object>>> avgScoreMap = storeEvaluationMapper.allStoreAvgScore().stream()
|
|
|
+ .collect(Collectors.groupingBy(o -> o.get("store_id").toString()));
|
|
|
+ Map<String, List<Map<String, Object>>> avgPriceMap = lifeUserOrderMapper.allStoreAvgPrice().stream()
|
|
|
+ .collect(Collectors.groupingBy(o -> o.get("store_id").toString()));
|
|
|
+
|
|
|
+ // 计算综合得分并排序
|
|
|
+ List<StoreInfoVo> sortedList = storeInfoVoList.stream()
|
|
|
+ .map(store -> {
|
|
|
+ // 获取基础评分(score_avg × 2,标准化为0-10分)
|
|
|
+ Double scoreAvg = store.getScoreAvg() != null ? store.getScoreAvg() : 0.0;
|
|
|
+ double baseScore = Math.min(scoreAvg * 2, 10.0); // 基础评分,最高10分
|
|
|
+
|
|
|
+ // 获取距离
|
|
|
+ double storeDistance = 999999;
|
|
|
+ try {
|
|
|
+ String distStr = store.getDist();
|
|
|
+ if (distStr != null && !distStr.isEmpty()) {
|
|
|
+ storeDistance = Double.parseDouble(distStr);
|
|
|
+ }
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ // 忽略
|
|
|
+ }
|
|
|
+
|
|
|
+ double finalScore = 0.0;
|
|
|
+
|
|
|
+ if (finalSortType == 1) {
|
|
|
+ // 智能排序:综合评分×50% + 近7天销量×30% + 距离得分×20%
|
|
|
+ // 综合评分(基础评分)
|
|
|
+ double scorePart = baseScore * 0.5;
|
|
|
+
|
|
|
+ // 近7天销量(需要标准化,假设最大销量为100)
|
|
|
+ long sales7Days = sales7DaysMap.getOrDefault(store.getId(), 0L);
|
|
|
+ double salesScore = Math.min(sales7Days / 100.0 * 10, 10.0); // 标准化到0-10
|
|
|
+ double salesPart = salesScore * 0.3;
|
|
|
+
|
|
|
+ // 距离得分(距离越近得分越高,10公里内计算)
|
|
|
+ double distanceScore = storeDistance <= 10 ? (10 - storeDistance) / 10.0 * 10 : 0;
|
|
|
+ double distancePart = distanceScore * 0.2;
|
|
|
+
|
|
|
+ finalScore = scorePart + salesPart + distancePart;
|
|
|
+ } else if (finalSortType == 2) {
|
|
|
+ // 好评优先:综合评分×50% + 近30天好评数×35% + 近7天新评占比×15%
|
|
|
+ double scorePart = baseScore * 0.5;
|
|
|
+
|
|
|
+ // 近30天好评数(需要标准化,假设最大好评数为50)
|
|
|
+ long goodComment30Days = goodComment30DaysMap.getOrDefault(store.getId(), 0L);
|
|
|
+ double goodCommentScore = Math.min(goodComment30Days / 50.0 * 10, 10.0);
|
|
|
+ double goodCommentPart = goodCommentScore * 0.35;
|
|
|
+
|
|
|
+ // 近7天新评占比
|
|
|
+ long comment7Days = comment7DaysMap.getOrDefault(store.getId(), 0L);
|
|
|
+ long totalComment = totalCommentMap.getOrDefault(store.getId(), 1L); // 避免除0
|
|
|
+ double newCommentRatio = (double) comment7Days / totalComment;
|
|
|
+ double newCommentPart = newCommentRatio * 10 * 0.15; // 占比转换为0-10分
|
|
|
+
|
|
|
+ finalScore = scorePart + goodCommentPart + newCommentPart;
|
|
|
+ } else if (finalSortType == 3) {
|
|
|
+ // 距离优先:距离得分 = (10 - 实际距离) × 80% + 基础评分 × 20%(10公里内计算)
|
|
|
+ if (storeDistance <= 10) {
|
|
|
+ double distanceScore = (10 - storeDistance) / 10.0 * 10; // 标准化到0-10
|
|
|
+ double distancePart = distanceScore * 0.8;
|
|
|
+ double scorePart = baseScore * 0.2;
|
|
|
+ finalScore = distancePart + scorePart;
|
|
|
+ } else {
|
|
|
+ finalScore = -1; // 超出范围,不展示
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置综合得分(用于排序)
|
|
|
+ store.setDistance(storeDistance);
|
|
|
+ // 使用反射或扩展字段存储finalScore,这里我们使用一个临时字段
|
|
|
+ // 由于StoreInfoVo没有finalScore字段,我们使用distance字段临时存储,排序后再恢复
|
|
|
+ return new Object[] { store, finalScore };
|
|
|
+ })
|
|
|
+ .filter(item -> {
|
|
|
+ // 距离优先模式:过滤掉超出范围的
|
|
|
+ if (finalSortType == 3) {
|
|
|
+ return ((Double) item[1]) >= 0;
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ })
|
|
|
+ .sorted((a, b) -> Double.compare((Double) b[1], (Double) a[1])) // 按得分降序
|
|
|
+ .map(item -> (StoreInfoVo) item[0])
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ // 分页处理
|
|
|
+ long total = sortedList.size();
|
|
|
+ int start = (pageNum - 1) * pageSize;
|
|
|
+ int end = Math.min(start + pageSize, sortedList.size());
|
|
|
+ List<StoreInfoVo> pagedList;
|
|
|
+ if (start < sortedList.size()) {
|
|
|
+ pagedList = sortedList.subList(start, end);
|
|
|
+ } else {
|
|
|
+ pagedList = new ArrayList<>();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 查询每个店铺的最新代金券(只显示一个,最新创建的)
|
|
|
+ Map<Integer, LifeCoupon> latestCouponMap = new HashMap<>();
|
|
|
+ if (!CollectionUtils.isEmpty(pagedList)) {
|
|
|
+ List<Integer> pagedStoreIds = pagedList.stream().map(StoreInfoVo::getId).collect(Collectors.toList());
|
|
|
+ // 查询所有店铺的代金券,按创建时间降序
|
|
|
+ LambdaQueryWrapper<LifeCoupon> couponWrapper = new LambdaQueryWrapper<>();
|
|
|
+ couponWrapper.in(LifeCoupon::getStoreId, pagedStoreIds.stream().map(String::valueOf).collect(Collectors.toList()))
|
|
|
+ .eq(LifeCoupon::getStatus, CouponStatusEnum.ONGOING.getCode())
|
|
|
+ .eq(LifeCoupon::getType, 1) // 代金券类型
|
|
|
+ .eq(LifeCoupon::getDeleteFlag, 0)
|
|
|
+ .orderByDesc(LifeCoupon::getCreatedTime);
|
|
|
+ List<LifeCoupon> allCoupons = lifeCouponMapper.selectList(couponWrapper);
|
|
|
+
|
|
|
+ // 为每个店铺只保留最新创建的一个代金券
|
|
|
+ for (LifeCoupon coupon : allCoupons) {
|
|
|
+ if (coupon.getStoreId() != null) {
|
|
|
+ try {
|
|
|
+ Integer storeId = Integer.parseInt(coupon.getStoreId());
|
|
|
+ // 如果该店铺还没有代金券,或者当前代金券更新,则更新
|
|
|
+ if (!latestCouponMap.containsKey(storeId)) {
|
|
|
+ latestCouponMap.put(storeId, coupon);
|
|
|
+ }
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ // 忽略无效的storeId
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置评分、平均消费和代金券
|
|
|
+ for (StoreInfoVo store : pagedList) {
|
|
|
+ if (avgScoreMap.containsKey(String.valueOf(store.getId()))) {
|
|
|
+ store.setAvgScore(new BigDecimal(avgScoreMap.get(String.valueOf(store.getId())).get(0).get("avg_score").toString())
|
|
|
+ .setScale(1, RoundingMode.HALF_UP).toString());
|
|
|
+ store.setTotalNum(avgScoreMap.get(String.valueOf(store.getId())).get(0).get("total_num").toString());
|
|
|
+ } else {
|
|
|
+ store.setAvgScore("0");
|
|
|
+ store.setTotalNum("0");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (avgPriceMap.containsKey(String.valueOf(store.getId()))) {
|
|
|
+ store.setAvgPrice(avgPriceMap.get(String.valueOf(store.getId())).get(0).get("avg_price").toString());
|
|
|
+ } else {
|
|
|
+ store.setAvgPrice("0");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置最新代金券(只显示一个)
|
|
|
+ if (latestCouponMap.containsKey(store.getId())) {
|
|
|
+ LifeCoupon latestCoupon = latestCouponMap.get(store.getId());
|
|
|
+ LifeCouponVo couponVo = new LifeCouponVo();
|
|
|
+ BeanUtils.copyProperties(latestCoupon, couponVo);
|
|
|
+ // 只设置一个代金券到列表中
|
|
|
+ List<LifeCouponVo> couponList = new ArrayList<>();
|
|
|
+ couponList.add(couponVo);
|
|
|
+ store.setCouponList(couponList);
|
|
|
+ } else {
|
|
|
+ // 如果没有代金券,设置为空列表
|
|
|
+ store.setCouponList(new ArrayList<>());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建分页对象
|
|
|
+ IPage<StoreInfoVo> resultPage = new Page<>(pageNum, pageSize);
|
|
|
+ resultPage.setRecords(pagedList);
|
|
|
+ resultPage.setTotal(total);
|
|
|
+ resultPage.setCurrent(pageNum);
|
|
|
+ resultPage.setSize(pageSize);
|
|
|
+ resultPage.setPages((total + pageSize - 1) / pageSize);
|
|
|
+
|
|
|
+ return resultPage;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 查询OCR图片上传记录并转换为JSONObject
|
|
|
+ *
|
|
|
+ * @param storeUserId 店铺用户ID
|
|
|
+ * @param ocrType OCR类型
|
|
|
+ * @param likeKeyword 模糊查询关键词,可为null
|
|
|
+ * @param includeImageUrl 是否包含imageUrl字段
|
|
|
+ * @return JSONObject,如果查询结果为空则返回空的JSONObject
|
|
|
+ */
|
|
|
+ private com.alibaba.fastjson2.JSONObject convertOcrResultToJson(Integer storeUserId, String ocrType, String likeKeyword, boolean includeImageUrl) {
|
|
|
+ LambdaQueryWrapper<OcrImageUpload> wrapper = new LambdaQueryWrapper<OcrImageUpload>()
|
|
|
+ .eq(OcrImageUpload::getStoreUserId, storeUserId)
|
|
|
+ .eq(OcrImageUpload::getOcrType, ocrType)
|
|
|
+ .orderByDesc(OcrImageUpload::getCreateTime)
|
|
|
+ .last("limit 1");
|
|
|
+
|
|
|
+ if (StringUtils.isNotEmpty(likeKeyword)) {
|
|
|
+ wrapper.like(OcrImageUpload::getOcrResult, likeKeyword);
|
|
|
+ }
|
|
|
+
|
|
|
+ OcrImageUpload ocrImageUpload = ocrImageUploadMapper.selectOne(wrapper);
|
|
|
+
|
|
|
+ if (ocrImageUpload == null || StringUtils.isEmpty(ocrImageUpload.getOcrResult())) {
|
|
|
+ return new com.alibaba.fastjson2.JSONObject();
|
|
|
+ }
|
|
|
+
|
|
|
+ com.alibaba.fastjson2.JSONObject jsonObject = com.alibaba.fastjson2.JSONObject.parseObject(ocrImageUpload.getOcrResult());
|
|
|
+ if (includeImageUrl && StringUtils.isNotEmpty(ocrImageUpload.getImageUrl())) {
|
|
|
+ jsonObject.put("imageUrl", ocrImageUpload.getImageUrl());
|
|
|
+ }
|
|
|
+
|
|
|
+ return jsonObject;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 你可能还喜欢(推荐店铺)
|
|
|
+ * 根据一级分类、二级分类、三级分类进行店铺筛选
|
|
|
+ * 筛选顺序:先三级,然后二级,最后一级
|
|
|
+ *
|
|
|
+ * @param businessSection 一级分类(经营板块)
|
|
|
+ * @param businessTypes 二级分类(经营种类)
|
|
|
+ * @param businessClassify 三级分类(分类)
|
|
|
+ * @param lon 经度
|
|
|
+ * @param lat 纬度
|
|
|
+ * @return List<StoreInfoVo> 店铺信息列表
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public List<StoreInfoVo> getRecommendedStores(String businessSection, String businessTypes, String businessClassify, Double lon, Double lat) {
|
|
|
+ log.info("StoreInfoServiceImpl.getRecommendedStores?businessSection={},businessTypes={},businessClassify={},lon={},lat={}",
|
|
|
+ businessSection, businessTypes, businessClassify, lon, lat);
|
|
|
+
|
|
|
+ QueryWrapper<StoreInfoVo> queryWrapper = new QueryWrapper<>();
|
|
|
+ // 基础条件:未删除、已启用
|
|
|
+ queryWrapper.eq("a.delete_flag", 0)
|
|
|
+ .eq("b.delete_flag", 0)
|
|
|
+ .ne("a.business_status", 99) // 过滤永久关门的店铺
|
|
|
+ .ne("a.store_status", 0); // 过滤禁用的店铺
|
|
|
+
|
|
|
+ // 按照三级->二级->一级的顺序筛选
|
|
|
+
|
|
|
+ // 1. 先按三级分类(business_classify)筛选
|
|
|
+ if (StringUtils.isNotEmpty(businessClassify)) {
|
|
|
+ // 解析businessClassify参数(格式:1,2,3)
|
|
|
+ String[] classifyArray = businessClassify.split(",");
|
|
|
+ // 使用FIND_IN_SET函数检查数据库字段是否包含参数中的任何一个值
|
|
|
+ queryWrapper.and(wrapper -> {
|
|
|
+ for (int i = 0; i < classifyArray.length; i++) {
|
|
|
+ String classify = classifyArray[i].trim();
|
|
|
+ if (StringUtils.isNotEmpty(classify)) {
|
|
|
+ if (i == 0) {
|
|
|
+ wrapper.apply("FIND_IN_SET({0}, a.business_classify) > 0", classify);
|
|
|
+ } else {
|
|
|
+ wrapper.or().apply("FIND_IN_SET({0}, a.business_classify) > 0", classify);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 然后按二级分类(business_types)筛选
|
|
|
+ if (StringUtils.isNotEmpty(businessTypes)) {
|
|
|
+ // business_types可能是逗号分隔的字符串,使用FIND_IN_SET处理
|
|
|
+ String[] typesArray = businessTypes.split(",");
|
|
|
+ queryWrapper.and(wrapper -> {
|
|
|
+ for (int i = 0; i < typesArray.length; i++) {
|
|
|
+ String type = typesArray[i].trim();
|
|
|
+ if (StringUtils.isNotEmpty(type)) {
|
|
|
+ if (i == 0) {
|
|
|
+ wrapper.apply("FIND_IN_SET({0}, a.business_types) > 0", type);
|
|
|
+ } else {
|
|
|
+ wrapper.or().apply("FIND_IN_SET({0}, a.business_types) > 0", type);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 最后按一级分类(business_section)筛选
|
|
|
+ if (StringUtils.isNotEmpty(businessSection)) {
|
|
|
+ try {
|
|
|
+ Integer sectionId = Integer.parseInt(businessSection.trim());
|
|
|
+ queryWrapper.eq("a.business_section", sectionId);
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ log.warn("一级分类参数格式错误: {}", businessSection);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 查询店铺列表
|
|
|
+ List<StoreInfoVo> storeInfoVoList;
|
|
|
+ if (lon != null && lat != null) {
|
|
|
+ // 如果提供了经纬度,使用带距离计算的查询方法
|
|
|
+ String position = lon + "," + lat;
|
|
|
+ queryWrapper.isNotNull("a.store_position")
|
|
|
+ .ne("a.store_position", "");
|
|
|
+ // 按距离排序
|
|
|
+ queryWrapper.orderByAsc("dist");
|
|
|
+ storeInfoVoList = storeInfoMapper.getStoreInfoVoListNew(queryWrapper, position);
|
|
|
+ } else {
|
|
|
+ // 如果没有提供经纬度,使用普通查询方法
|
|
|
+ queryWrapper.orderByDesc("a.created_time");
|
|
|
+ storeInfoVoList = storeInfoMapper.getStoreInfoVoList(queryWrapper);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (CollectionUtils.isEmpty(storeInfoVoList)) {
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理店铺信息,设置评分等
|
|
|
+ // 提前查询所有需要的字典数据
|
|
|
+ List<StoreInfoVo> collect = storeInfoVoList.stream()
|
|
|
+ .filter(record -> StringUtils.isNotEmpty(record.getStoreType()))
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ Set<String> allTypes = collect.stream()
|
|
|
+ .map(StoreInfoVo::getStoreType)
|
|
|
+ .flatMap(type -> Arrays.stream(type.split(",")))
|
|
|
+ .collect(Collectors.toSet());
|
|
|
+
|
|
|
+ List<StoreDictionary> storeDictionaries = storeDictionaryMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<StoreDictionary>()
|
|
|
+ .eq(StoreDictionary::getTypeName, "storeType")
|
|
|
+ .isNull(StoreDictionary::getParentId)
|
|
|
+ .in(!allTypes.isEmpty(), StoreDictionary::getDictId, allTypes));
|
|
|
+ Map<String, String> typeMap = storeDictionaries.stream()
|
|
|
+ .collect(Collectors.toMap(StoreDictionary::getDictId, StoreDictionary::getDictDetail));
|
|
|
+
|
|
|
+ // 计算平均分和评价
|
|
|
+ Map<String, List<Map<String, Object>>> avgScoreMap = new HashMap<>();
|
|
|
+ Map<Integer, List<StoreComment>> commentMap = new HashMap<>();
|
|
|
+
|
|
|
+ avgScoreMap = storeEvaluationMapper.allStoreAvgScore().stream()
|
|
|
+ .collect(Collectors.groupingBy(o -> o.get("store_id").toString()));
|
|
|
+ commentMap = storeCommentMapper.selectList(
|
|
|
+ new QueryWrapper<StoreComment>()
|
|
|
+ .eq("business_type", "5")
|
|
|
+ .eq("delete_flag", 0))
|
|
|
+ .stream()
|
|
|
+ .collect(Collectors.groupingBy(StoreComment::getStoreId));
|
|
|
+
|
|
|
+ for (StoreInfoVo record : storeInfoVoList) {
|
|
|
+ // 处理类型
|
|
|
+ if (StringUtils.isNotEmpty(record.getStoreType())) {
|
|
|
+ String[] types = record.getStoreType().split(",");
|
|
|
+ List<String> typeDetails = Arrays.stream(types)
|
|
|
+ .map(typeMap::get)
|
|
|
+ .filter(Objects::nonNull)
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ record.setStoreTypeStr(String.join(",", typeDetails));
|
|
|
+ record.setStoreTypeList(Arrays.asList(types));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理经纬度
|
|
|
+ if (StringUtils.isNotEmpty(record.getStorePosition())) {
|
|
|
+ String[] split = record.getStorePosition().split(",");
|
|
|
+ if (split.length >= 2) {
|
|
|
+ record.setStorePositionLongitude(split[0]);
|
|
|
+ record.setStorePositionLatitude(split[1]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果提供了经纬度,处理距离信息
|
|
|
+ if (lon != null && lat != null && StringUtils.isNotEmpty(record.getStorePosition())) {
|
|
|
+ try {
|
|
|
+ // 手动计算距离(单位:公里)
|
|
|
+ String[] positionArray = record.getStorePosition().split(",");
|
|
|
+ if (positionArray.length >= 2) {
|
|
|
+ double storeLon = Double.parseDouble(positionArray[0].trim());
|
|
|
+ double storeLat = Double.parseDouble(positionArray[1].trim());
|
|
|
+ // 计算距离(单位:公里)
|
|
|
+ double distanceKm = DistanceUtil.haversineCalculateDistance(lon, lat, storeLon, storeLat);
|
|
|
+ record.setDistance(distanceKm);
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("计算店铺距离失败,storeId={},error={}", record.getId(), e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理到期状态
|
|
|
+ Date expirationTime = record.getExpirationTime();
|
|
|
+ if (expirationTime != null) {
|
|
|
+ Date currentDate = new Date();
|
|
|
+ Calendar now = Calendar.getInstance();
|
|
|
+ Date nowCurrentDate = now.getTime();
|
|
|
+ now.add(Calendar.DAY_OF_YEAR, 30);
|
|
|
+ Date thirtyDaysLater = now.getTime();
|
|
|
+
|
|
|
+ if (expirationTime.after(currentDate)) {
|
|
|
+ record.setExpiredState("0");
|
|
|
+ if ((expirationTime.after(nowCurrentDate) || expirationTime.equals(nowCurrentDate))
|
|
|
+ && expirationTime.before(thirtyDaysLater)) {
|
|
|
+ record.setExpiredState("1");
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ record.setExpiredState("2");
|
|
|
+ }
|
|
|
+
|
|
|
+ LocalDate nowLocal = LocalDate.now();
|
|
|
+ LocalDate expDate = expirationTime.toInstant()
|
|
|
+ .atZone(ZoneId.systemDefault())
|
|
|
+ .toLocalDate();
|
|
|
+ long daysToExpire = ChronoUnit.DAYS.between(nowLocal, expDate);
|
|
|
+ record.setDaysToExpire(daysToExpire);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置店铺得分和总评论数
|
|
|
+ if (avgScoreMap.containsKey(String.valueOf(record.getId()))) {
|
|
|
+ record.setAvgScore(String.valueOf(
|
|
|
+ avgScoreMap.get(String.valueOf(record.getId())).get(0).get("avg_score")));
|
|
|
+ }
|
|
|
+ if (commentMap.containsKey(record.getId())) {
|
|
|
+ record.setTotalNum(String.valueOf(commentMap.get(record.getId()).size()));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return storeInfoVoList;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建树形结构(优化版)
|
|
|
+ *
|
|
|
+ * @param flatList 扁平列表
|
|
|
+ * @return 树形结构列表
|
|
|
+ */
|
|
|
+ private List<StoreDictionary> buildTreeOptimized(List<StoreDictionary> flatList) {
|
|
|
+ if (flatList == null || flatList.isEmpty()) {
|
|
|
+ return new ArrayList<>();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建三个存储结构
|
|
|
+ Map<Integer, StoreDictionary> nodeMap = new HashMap<>(); // ID到节点的映射
|
|
|
+ Map<Integer, List<StoreDictionary>> parentChildMap = new HashMap<>(); // 父ID到子节点列表的映射
|
|
|
+ List<StoreDictionary> result = new ArrayList<>(); // 结果列表
|
|
|
+
|
|
|
+ // 填充nodeMap和parentChildMap
|
|
|
+ for (StoreDictionary entity : flatList) {
|
|
|
+ Integer id = entity.getId();
|
|
|
+ Integer parentId = entity.getParentId();
|
|
|
+
|
|
|
+ // 存入节点映射
|
|
|
+ nodeMap.put(id, entity);
|
|
|
+
|
|
|
+ // 初始化子节点列表
|
|
|
+ entity.setStoreDictionaryList(new ArrayList<>());
|
|
|
+
|
|
|
+ // 如果是根节点(parentId为null或0),直接添加到结果
|
|
|
+ if (parentId == null || parentId == 0) {
|
|
|
+ result.add(entity);
|
|
|
+ } else {
|
|
|
+ // 否则,记录父子关系
|
|
|
+ parentChildMap.computeIfAbsent(parentId, k -> new ArrayList<>()).add(entity);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 建立父子关系
|
|
|
+ for (StoreDictionary entity : flatList) {
|
|
|
+ Integer id = entity.getId();
|
|
|
+ if (parentChildMap.containsKey(id)) {
|
|
|
+ entity.getStoreDictionaryList().addAll(parentChildMap.get(id));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<StoreDictionaryVo> getLeisureEntertainmentCategories() {
|
|
|
+ // 定义四种主分类名称
|
|
|
+ List<String> mainCategoryNames = Arrays.asList("酒吧", "KTV", "洗浴汗蒸", "按摩足疗");
|
|
|
+
|
|
|
+ // 查询所有主分类(business_section类型)
|
|
|
+ List<StoreDictionary> allMainCategories = storeDictionaryMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<StoreDictionary>()
|
|
|
+ .eq(StoreDictionary::getTypeName, "business_section")
|
|
|
+ .in(StoreDictionary::getDictDetail, mainCategoryNames)
|
|
|
+ .eq(StoreDictionary::getDeleteFlag, 0)
|
|
|
+ );
|
|
|
+
|
|
|
+ // 构建主分类列表,包含"全部"选项
|
|
|
+ List<StoreDictionaryVo> result = new ArrayList<>();
|
|
|
+
|
|
|
+ // 添加"全部"选项
|
|
|
+ StoreDictionaryVo allCategory = new StoreDictionaryVo();
|
|
|
+ allCategory.setId(0);
|
|
|
+ allCategory.setDictId("0");
|
|
|
+ allCategory.setDictDetail("全部");
|
|
|
+ allCategory.setTypeName("business_section");
|
|
|
+ allCategory.setSubDataList(new ArrayList<>());
|
|
|
+ result.add(allCategory);
|
|
|
+
|
|
|
+ // 查询所有子分类(根据parent_id关联)
|
|
|
+ Map<Integer, List<StoreDictionary>> subCategoryMap = new HashMap<>();
|
|
|
+ if (!CollectionUtils.isEmpty(allMainCategories)) {
|
|
|
+ List<Integer> mainCategoryIds = allMainCategories.stream()
|
|
|
+ .map(StoreDictionary::getId)
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ // 查询所有子分类
|
|
|
+ List<StoreDictionary> allSubCategories = storeDictionaryMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<StoreDictionary>()
|
|
|
+ .in(StoreDictionary::getParentId, mainCategoryIds)
|
|
|
+ .eq(StoreDictionary::getDeleteFlag, 0)
|
|
|
+ .in(StoreDictionary::getTypeName, "business_section","business_type","business_classify")
|
|
|
+ );
|
|
|
+
|
|
|
+ // 按parentId分组
|
|
|
+ subCategoryMap = allSubCategories.stream()
|
|
|
+ .collect(Collectors.groupingBy(StoreDictionary::getParentId));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构建主分类及其子分类
|
|
|
+ for (StoreDictionary mainCategory : allMainCategories) {
|
|
|
+ StoreDictionaryVo mainVo = new StoreDictionaryVo();
|
|
|
+ BeanUtils.copyProperties(mainCategory, mainVo);
|
|
|
+
|
|
|
+ // 获取该主分类下的子分类
|
|
|
+ List<StoreDictionary> subCategories = subCategoryMap.getOrDefault(mainCategory.getId(), new ArrayList<>());
|
|
|
+ List<StoreDictionaryVo> subVoList = new ArrayList<>();
|
|
|
+
|
|
|
+ for (StoreDictionary subCategory : subCategories) {
|
|
|
+ StoreDictionaryVo subVo = new StoreDictionaryVo();
|
|
|
+ BeanUtils.copyProperties(subCategory, subVo);
|
|
|
+ subVoList.add(subVo);
|
|
|
+ }
|
|
|
+
|
|
|
+ mainVo.setSubDataList(subVoList);
|
|
|
+ result.add(mainVo);
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public IPage<StoreInfoVo> getSpecialTypeStoresByDistance(Double lon, Double lat, Double distance, Integer sortType, Integer businessType, Integer categoryId, int pageNum, int pageSize) {
|
|
|
+ // 参数校验
|
|
|
+ if (lon == null || lat == null) {
|
|
|
+ throw new IllegalArgumentException("经纬度参数不能为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 智能检测:如果参数可能传反了(lat超出范围但lon在范围内),给出提示
|
|
|
+ if (Math.abs(lat) > 90 && Math.abs(lon) <= 90) {
|
|
|
+ throw new IllegalArgumentException(
|
|
|
+ String.format("参数可能传反了!当前值: lon=%s, lat=%s。经度范围: [-180, 180],纬度范围: [-90, 90]。请检查参数顺序。", lon, lat)
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ // 校验经纬度范围:经度 [-180, 180],纬度 [-90, 90]
|
|
|
+ if (lon < -180 || lon > 180) {
|
|
|
+ throw new IllegalArgumentException("经度参数超出有效范围 [-180, 180],当前值: " + lon);
|
|
|
+ }
|
|
|
+ if (lat < -90 || lat > 90) {
|
|
|
+ throw new IllegalArgumentException("纬度参数超出有效范围 [-90, 90],当前值: " + lat);
|
|
|
+ }
|
|
|
+ final int finalSortType = (sortType == null || sortType < 1 || sortType > 3) ? 1 : sortType; // 默认智能排序
|
|
|
+ if (pageNum <= 0) {
|
|
|
+ pageNum = 1;
|
|
|
+ }
|
|
|
+ if (pageSize <= 0) {
|
|
|
+ pageSize = 10;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构建查询条件
|
|
|
+ QueryWrapper<StoreInfoVo> queryWrapper = new QueryWrapper<>();
|
|
|
+
|
|
|
+ // 如果传入了dictId,根据字典id查询经营板块、经营种类、分类并匹配店铺
|
|
|
+ if (categoryId != null) {
|
|
|
+ // 根据categoryId查询字典表记录
|
|
|
+ StoreDictionary dict = storeDictionaryMapper.selectById(categoryId);
|
|
|
+ if (dict == null || dict.getDeleteFlag() == 1) {
|
|
|
+ throw new IllegalArgumentException("字典id不存在或已删除: " + categoryId);
|
|
|
+ }
|
|
|
+
|
|
|
+ String typeName = dict.getTypeName();
|
|
|
+ String dictIdStr =dict.getDictId();
|
|
|
+
|
|
|
+ if ("business_section".equals(typeName)) {
|
|
|
+ // 如果是经营板块,直接匹配business_section
|
|
|
+ try {
|
|
|
+ Integer sectionId = Integer.parseInt(dictIdStr);
|
|
|
+ queryWrapper.eq("a.business_section", sectionId);
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ throw new IllegalArgumentException("经营板块dictId格式错误: " + dictIdStr);
|
|
|
+ }
|
|
|
+ } else if ("business_type".equals(typeName)) {
|
|
|
+ // 如果是经营种类,需要:
|
|
|
+ // 1. 向上查找父级(经营板块)
|
|
|
+ // 2. 匹配business_section和business_types
|
|
|
+ StoreDictionary parentDict = storeDictionaryMapper.selectById(dict.getParentId());
|
|
|
+ if (parentDict == null || !"business_section".equals(parentDict.getTypeName())) {
|
|
|
+ throw new IllegalArgumentException("经营种类的父级不是经营板块,categoryId: " + categoryId);
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ Integer sectionId = Integer.parseInt(parentDict.getDictId());
|
|
|
+ queryWrapper.eq("a.business_section", sectionId);
|
|
|
+ // 使用FIND_IN_SET匹配business_types字段(逗号分隔)
|
|
|
+ queryWrapper.apply("FIND_IN_SET({0}, a.business_types) > 0", dictIdStr);
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ throw new IllegalArgumentException("经营板块或经营种类dictId格式错误");
|
|
|
+ }
|
|
|
+ } else if ("business_classify".equals(typeName)) {
|
|
|
+ // 如果是分类,需要:
|
|
|
+ // 1. 向上查找父级(经营种类)
|
|
|
+ // 2. 向上查找祖父级(经营板块)
|
|
|
+ // 3. 匹配business_section、business_types和business_classify
|
|
|
+ StoreDictionary parentDict = storeDictionaryMapper.selectById(dict.getParentId());
|
|
|
+ if (parentDict == null || !"business_type".equals(parentDict.getTypeName())) {
|
|
|
+ throw new IllegalArgumentException("分类的父级不是经营种类,categoryId: " + categoryId);
|
|
|
+ }
|
|
|
+
|
|
|
+ StoreDictionary grandParentDict = storeDictionaryMapper.selectById(parentDict.getParentId());
|
|
|
+ if (grandParentDict == null || !"business_section".equals(grandParentDict.getTypeName())) {
|
|
|
+ throw new IllegalArgumentException("经营种类的父级不是经营板块,categoryId: " + categoryId);
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ Integer sectionId = Integer.parseInt(grandParentDict.getDictId());
|
|
|
+ queryWrapper.eq("a.business_section", sectionId);
|
|
|
+ // 使用FIND_IN_SET匹配business_types字段
|
|
|
+ queryWrapper.apply("FIND_IN_SET({0}, a.business_types) > 0", parentDict.getDictId());
|
|
|
+ // 使用FIND_IN_SET匹配business_classify字段
|
|
|
+ queryWrapper.apply("FIND_IN_SET({0}, a.business_classify) > 0", dictIdStr);
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ throw new IllegalArgumentException("经营板块、经营种类或分类dictId格式错误");
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ throw new IllegalArgumentException("不支持的字典类型: " + typeName + ", categoryId: " + categoryId);
|
|
|
+ }
|
|
|
+ } else if (businessType != null) {
|
|
|
+ // 如果指定了businessType,则根据传入的数值进行筛选
|
|
|
+ // 直接使用传入的数值作为business_section进行筛选
|
|
|
+ // KTV=3、洗浴汗蒸=4、按摩足浴=5,酒吧的数值未知(由调用方传入)
|
|
|
+ queryWrapper.eq("a.business_section", businessType);
|
|
|
+ } else {
|
|
|
+ // 如果没有指定businessType,则查询所有四种类型的店铺
|
|
|
+ // 需要查询字典表获取所有四种类型的dictId
|
|
|
+ List<String> storeTypeNames = Arrays.asList("酒吧", "KTV", "洗浴汗蒸", "按摩足疗");
|
|
|
+ List<StoreDictionary> storeDictionaries = storeDictionaryMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<StoreDictionary>()
|
|
|
+ .eq(StoreDictionary::getTypeName, "business_section")
|
|
|
+ .in(StoreDictionary::getDictDetail, storeTypeNames)
|
|
|
+ .eq(StoreDictionary::getDeleteFlag, 0)
|
|
|
+ );
|
|
|
+
|
|
|
+ List<Integer> businessSectionIds = storeDictionaries.stream()
|
|
|
+ .filter(d -> StringUtils.isNotEmpty(d.getDictId()))
|
|
|
+ .map(StoreDictionary::getDictId)
|
|
|
+ .map(dictIdStr -> {
|
|
|
+ try {
|
|
|
+ return Integer.parseInt(dictIdStr);
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .filter(Objects::nonNull)
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ // 使用business_section字段进行筛选
|
|
|
+ queryWrapper.in("a.business_section", businessSectionIds);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 过滤已删除的门店
|
|
|
+ queryWrapper.eq("a.delete_flag", 0);
|
|
|
+ // 过滤注销的门店
|
|
|
+ queryWrapper.eq("a.logout_flag", 0);
|
|
|
+ // 过滤禁用的门店
|
|
|
+ queryWrapper.ne("a.store_status", 0);
|
|
|
+ // 过滤永久关门的店铺
|
|
|
+ queryWrapper.ne("a.business_status", 99);
|
|
|
+ // 过滤过期的经营许可证
|
|
|
+ queryWrapper.ge("a.entertainment_licence_expiration_time", new Date());
|
|
|
+
|
|
|
+ // 距离优先模式:只显示10公里内且3.5星以上的店铺
|
|
|
+ final Double finalDistance;
|
|
|
+ if (finalSortType == 3) {
|
|
|
+ queryWrapper.ge("a.score_avg", 3.5);
|
|
|
+ if (distance == null || distance <= 0) {
|
|
|
+ finalDistance = 10.0; // 默认10公里
|
|
|
+ } else if (distance > 10) {
|
|
|
+ finalDistance = 10.0; // 距离优先最多10公里
|
|
|
+ } else {
|
|
|
+ finalDistance = distance;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ finalDistance = distance;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 先按距离排序获取所有数据(用于计算距离)
|
|
|
+ queryWrapper.orderByAsc("dist");
|
|
|
+
|
|
|
+ // 创建分页对象(先获取更多数据用于排序计算)
|
|
|
+ IPage<StoreInfoVo> page = new Page<>(1, 1000); // 先获取足够多的数据
|
|
|
+ IPage<StoreInfoVo> storeInfoIPage = storeInfoMapper.getPageForDistance(page, lon + "," + lat, queryWrapper);
|
|
|
+ List<StoreInfoVo> storeInfoVoList = storeInfoIPage.getRecords();
|
|
|
+
|
|
|
+ // 如果指定了距离范围,进行距离筛选
|
|
|
+ if (finalDistance != null && finalDistance > 0) {
|
|
|
+ storeInfoVoList = storeInfoVoList.stream()
|
|
|
+ .filter(store -> {
|
|
|
+ String distStr = store.getDist();
|
|
|
+ if (distStr == null || distStr.isEmpty()) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ double storeDistance = Double.parseDouble(distStr);
|
|
|
+ return storeDistance <= finalDistance;
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算时间范围
|
|
|
+ LocalDateTime now = LocalDateTime.now();
|
|
|
+ LocalDateTime sevenDaysAgo = now.minusDays(7);
|
|
|
+ LocalDateTime thirtyDaysAgo = now.minusDays(30);
|
|
|
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
|
|
+ String sevenDaysAgoStr = sevenDaysAgo.format(formatter);
|
|
|
+ String thirtyDaysAgoStr = thirtyDaysAgo.format(formatter);
|
|
|
+
|
|
|
+ // 提取所有门店ID
|
|
|
+ List<Integer> storeIds = storeInfoVoList.stream().map(StoreInfoVo::getId).collect(Collectors.toList());
|
|
|
+
|
|
|
+ if (CollectionUtils.isEmpty(storeIds)) {
|
|
|
+ // 如果没有符合条件的店铺,返回空结果
|
|
|
+ IPage<StoreInfoVo> emptyPage = new Page<>(pageNum, pageSize);
|
|
|
+ emptyPage.setRecords(new ArrayList<>());
|
|
|
+ emptyPage.setTotal(0);
|
|
|
+ return emptyPage;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 查询近7天销量(订单数)
|
|
|
+ Map<Integer, Long> sales7DaysMap = new HashMap<>();
|
|
|
+ if (finalSortType == 1) { // 智能排序需要销量数据
|
|
|
+ LambdaQueryWrapper<LifeUserOrder> orderWrapper = new LambdaQueryWrapper<>();
|
|
|
+ orderWrapper.in(LifeUserOrder::getStoreId, storeIds)
|
|
|
+ .and(w -> w.and(w1 -> w1.eq(LifeUserOrder::getStatus, 7)
|
|
|
+ .ge(LifeUserOrder::getFinishTime, sevenDaysAgoStr))
|
|
|
+ .or(w2 -> w2.eq(LifeUserOrder::getStatus, 1)
|
|
|
+ .ge(LifeUserOrder::getPayTime, sevenDaysAgoStr)))
|
|
|
+ .eq(LifeUserOrder::getDeleteFlag, 0);
|
|
|
+ List<LifeUserOrder> orders7Days = lifeUserOrderMapper.selectList(orderWrapper);
|
|
|
+ Map<Integer, Long> tempSalesMap = orders7Days.stream()
|
|
|
+ .filter(order -> order.getStoreId() != null)
|
|
|
+ .collect(Collectors.groupingBy(
|
|
|
+ order -> Integer.parseInt(order.getStoreId()),
|
|
|
+ Collectors.counting()));
|
|
|
+ sales7DaysMap.putAll(tempSalesMap);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 查询近30天好评数(score >= 4.5)
|
|
|
+ Map<Integer, Long> goodComment30DaysMap = new HashMap<>();
|
|
|
+ // 查询近7天评论数
|
|
|
+ Map<Integer, Long> comment7DaysMap = new HashMap<>();
|
|
|
+ // 查询总评论数
|
|
|
+ Map<Integer, Long> totalCommentMap = new HashMap<>();
|
|
|
+
|
|
|
+ if (finalSortType == 2) { // 好评优先需要评论数据
|
|
|
+ // 近30天好评数
|
|
|
+ LambdaQueryWrapper<StoreComment> goodCommentWrapper = new LambdaQueryWrapper<>();
|
|
|
+ goodCommentWrapper.in(StoreComment::getStoreId, storeIds)
|
|
|
+ .eq(StoreComment::getBusinessType, 5)
|
|
|
+ .eq(StoreComment::getDeleteFlag, 0)
|
|
|
+ .isNull(StoreComment::getReplyId)
|
|
|
+ .ge(StoreComment::getScore, 4.5)
|
|
|
+ .ge(StoreComment::getCreatedTime, thirtyDaysAgoStr);
|
|
|
+ List<StoreComment> goodComments30Days = storeCommentMapper.selectList(goodCommentWrapper);
|
|
|
+ Map<Integer, Long> tempGoodCommentMap = goodComments30Days.stream()
|
|
|
+ .collect(Collectors.groupingBy(StoreComment::getStoreId, Collectors.counting()));
|
|
|
+ goodComment30DaysMap.putAll(tempGoodCommentMap);
|
|
|
+
|
|
|
+ // 近7天评论数
|
|
|
+ LambdaQueryWrapper<StoreComment> comment7DaysWrapper = new LambdaQueryWrapper<>();
|
|
|
+ comment7DaysWrapper.in(StoreComment::getStoreId, storeIds)
|
|
|
+ .eq(StoreComment::getBusinessType, 5)
|
|
|
+ .eq(StoreComment::getDeleteFlag, 0)
|
|
|
+ .isNull(StoreComment::getReplyId)
|
|
|
+ .ge(StoreComment::getCreatedTime, sevenDaysAgoStr);
|
|
|
+ List<StoreComment> comments7Days = storeCommentMapper.selectList(comment7DaysWrapper);
|
|
|
+ Map<Integer, Long> tempComment7DaysMap = comments7Days.stream()
|
|
|
+ .collect(Collectors.groupingBy(StoreComment::getStoreId, Collectors.counting()));
|
|
|
+ comment7DaysMap.putAll(tempComment7DaysMap);
|
|
|
+
|
|
|
+ // 总评论数
|
|
|
+ LambdaQueryWrapper<StoreComment> totalCommentWrapper = new LambdaQueryWrapper<>();
|
|
|
+ totalCommentWrapper.in(StoreComment::getStoreId, storeIds)
|
|
|
+ .eq(StoreComment::getBusinessType, 5)
|
|
|
+ .eq(StoreComment::getDeleteFlag, 0)
|
|
|
+ .isNull(StoreComment::getReplyId);
|
|
|
+ List<StoreComment> totalComments = storeCommentMapper.selectList(totalCommentWrapper);
|
|
|
+ Map<Integer, Long> tempTotalCommentMap = totalComments.stream()
|
|
|
+ .collect(Collectors.groupingBy(StoreComment::getStoreId, Collectors.counting()));
|
|
|
+ totalCommentMap.putAll(tempTotalCommentMap);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取全部店铺的评分
|
|
|
+ Map<String, List<Map<String, Object>>> avgScoreMap = storeEvaluationMapper.allStoreAvgScore().stream()
|
|
|
+ .collect(Collectors.groupingBy(o -> o.get("store_id").toString()));
|
|
|
+ Map<String, List<Map<String, Object>>> avgPriceMap = lifeUserOrderMapper.allStoreAvgPrice().stream()
|
|
|
+ .collect(Collectors.groupingBy(o -> o.get("store_id").toString()));
|
|
|
+
|
|
|
+ // 计算综合得分并排序
|
|
|
+ List<StoreInfoVo> sortedList = storeInfoVoList.stream()
|
|
|
+ .map(store -> {
|
|
|
+ // 获取基础评分(score_avg × 2,标准化为0-10分)
|
|
|
+ Double scoreAvg = store.getScoreAvg() != null ? store.getScoreAvg() : 0.0;
|
|
|
+ double baseScore = Math.min(scoreAvg * 2, 10.0); // 基础评分,最高10分
|
|
|
+
|
|
|
+ // 获取距离
|
|
|
+ double storeDistance = 999999;
|
|
|
+ try {
|
|
|
+ String distStr = store.getDist();
|
|
|
+ if (distStr != null && !distStr.isEmpty()) {
|
|
|
+ storeDistance = Double.parseDouble(distStr);
|
|
|
+ }
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ // 忽略
|
|
|
+ }
|
|
|
+
|
|
|
+ double finalScore = 0.0;
|
|
|
+
|
|
|
+ if (finalSortType == 1) {
|
|
|
+ // 智能排序:综合评分×50% + 近7天销量×30% + 距离得分×20%
|
|
|
+ // 综合评分(基础评分)
|
|
|
+ double scorePart = baseScore * 0.5;
|
|
|
+
|
|
|
+ // 近7天销量(需要标准化,假设最大销量为100)
|
|
|
+ long sales7Days = sales7DaysMap.getOrDefault(store.getId(), 0L);
|
|
|
+ double salesScore = Math.min(sales7Days / 100.0 * 10, 10.0); // 标准化到0-10
|
|
|
+ double salesPart = salesScore * 0.3;
|
|
|
+
|
|
|
+ // 距离得分(距离越近得分越高,10公里内计算)
|
|
|
+ double distanceScore = storeDistance <= 10 ? (10 - storeDistance) / 10.0 * 10 : 0;
|
|
|
+ double distancePart = distanceScore * 0.2;
|
|
|
+
|
|
|
+ finalScore = scorePart + salesPart + distancePart;
|
|
|
+ } else if (finalSortType == 2) {
|
|
|
+ // 好评优先:综合评分×50% + 近30天好评数×35% + 近7天新评占比×15%
|
|
|
+ double scorePart = baseScore * 0.5;
|
|
|
+
|
|
|
+ // 近30天好评数(需要标准化,假设最大好评数为50)
|
|
|
+ long goodComment30Days = goodComment30DaysMap.getOrDefault(store.getId(), 0L);
|
|
|
+ double goodCommentScore = Math.min(goodComment30Days / 50.0 * 10, 10.0);
|
|
|
+ double goodCommentPart = goodCommentScore * 0.35;
|
|
|
+
|
|
|
+ // 近7天新评占比
|
|
|
+ long comment7Days = comment7DaysMap.getOrDefault(store.getId(), 0L);
|
|
|
+ long totalComment = totalCommentMap.getOrDefault(store.getId(), 1L); // 避免除0
|
|
|
+ double newCommentRatio = (double) comment7Days / totalComment;
|
|
|
+ double newCommentPart = newCommentRatio * 10 * 0.15; // 占比转换为0-10分
|
|
|
+
|
|
|
+ finalScore = scorePart + goodCommentPart + newCommentPart;
|
|
|
+ } else if (finalSortType == 3) {
|
|
|
+ // 距离优先:距离得分 = (10 - 实际距离) × 80% + 基础评分 × 20%(10公里内计算)
|
|
|
+ if (storeDistance <= 10) {
|
|
|
+ double distanceScore = (10 - storeDistance) / 10.0 * 10; // 标准化到0-10
|
|
|
+ double distancePart = distanceScore * 0.8;
|
|
|
+ double scorePart = baseScore * 0.2;
|
|
|
+ finalScore = distancePart + scorePart;
|
|
|
+ } else {
|
|
|
+ finalScore = -1; // 超出范围,不展示
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置综合得分(用于排序)
|
|
|
+ store.setDistance(storeDistance);
|
|
|
+ // 使用反射或扩展字段存储finalScore,这里我们使用一个临时字段
|
|
|
+ // 由于StoreInfoVo没有finalScore字段,我们使用distance字段临时存储,排序后再恢复
|
|
|
+ return new Object[] { store, finalScore };
|
|
|
+ })
|
|
|
+ .filter(item -> {
|
|
|
+ // 距离优先模式:过滤掉超出范围的
|
|
|
+ if (finalSortType == 3) {
|
|
|
+ return ((Double) item[1]) >= 0;
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ })
|
|
|
+ .sorted((a, b) -> Double.compare((Double) b[1], (Double) a[1])) // 按得分降序
|
|
|
+ .map(item -> (StoreInfoVo) item[0])
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ // 分页处理
|
|
|
+ long total = sortedList.size();
|
|
|
+ int start = (pageNum - 1) * pageSize;
|
|
|
+ int end = Math.min(start + pageSize, sortedList.size());
|
|
|
+ List<StoreInfoVo> pagedList;
|
|
|
+ if (start < sortedList.size()) {
|
|
|
+ pagedList = sortedList.subList(start, end);
|
|
|
+ } else {
|
|
|
+ pagedList = new ArrayList<>();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 查询每个店铺的最新代金券(只显示一个,最新创建的)
|
|
|
+ Map<Integer, LifeCoupon> latestCouponMap = new HashMap<>();
|
|
|
+ if (!CollectionUtils.isEmpty(pagedList)) {
|
|
|
+ List<Integer> pagedStoreIds = pagedList.stream().map(StoreInfoVo::getId).collect(Collectors.toList());
|
|
|
+ // 查询所有店铺的代金券,按创建时间降序
|
|
|
+ LambdaQueryWrapper<LifeCoupon> couponWrapper = new LambdaQueryWrapper<>();
|
|
|
+ couponWrapper.in(LifeCoupon::getStoreId, pagedStoreIds.stream().map(String::valueOf).collect(Collectors.toList()))
|
|
|
+ .eq(LifeCoupon::getStatus, CouponStatusEnum.ONGOING.getCode())
|
|
|
+ .eq(LifeCoupon::getType, 1) // 代金券类型
|
|
|
+ .eq(LifeCoupon::getDeleteFlag, 0)
|
|
|
+ .orderByDesc(LifeCoupon::getCreatedTime);
|
|
|
+ List<LifeCoupon> allCoupons = lifeCouponMapper.selectList(couponWrapper);
|
|
|
+
|
|
|
+ // 为每个店铺只保留最新创建的一个代金券
|
|
|
+ for (LifeCoupon coupon : allCoupons) {
|
|
|
+ if (coupon.getStoreId() != null) {
|
|
|
+ try {
|
|
|
+ Integer storeId = Integer.parseInt(coupon.getStoreId());
|
|
|
+ // 如果该店铺还没有代金券,或者当前代金券更新,则更新
|
|
|
+ if (!latestCouponMap.containsKey(storeId)) {
|
|
|
+ latestCouponMap.put(storeId, coupon);
|
|
|
+ }
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ // 忽略无效的storeId
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置评分、平均消费和代金券
|
|
|
+ for (StoreInfoVo store : pagedList) {
|
|
|
+ if (avgScoreMap.containsKey(String.valueOf(store.getId()))) {
|
|
|
+ store.setAvgScore(new BigDecimal(avgScoreMap.get(String.valueOf(store.getId())).get(0).get("avg_score").toString())
|
|
|
+ .setScale(1, RoundingMode.HALF_UP).toString());
|
|
|
+ store.setTotalNum(avgScoreMap.get(String.valueOf(store.getId())).get(0).get("total_num").toString());
|
|
|
+ } else {
|
|
|
+ store.setAvgScore("0");
|
|
|
+ store.setTotalNum("0");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (avgPriceMap.containsKey(String.valueOf(store.getId()))) {
|
|
|
+ store.setAvgPrice(avgPriceMap.get(String.valueOf(store.getId())).get(0).get("avg_price").toString());
|
|
|
+ } else {
|
|
|
+ store.setAvgPrice("0");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置最新代金券(只显示一个)
|
|
|
+ if (latestCouponMap.containsKey(store.getId())) {
|
|
|
+ LifeCoupon latestCoupon = latestCouponMap.get(store.getId());
|
|
|
+ LifeCouponVo couponVo = new LifeCouponVo();
|
|
|
+ BeanUtils.copyProperties(latestCoupon, couponVo);
|
|
|
+ // 只设置一个代金券到列表中
|
|
|
+ List<LifeCouponVo> couponList = new ArrayList<>();
|
|
|
+ couponList.add(couponVo);
|
|
|
+ store.setCouponList(couponList);
|
|
|
+ } else {
|
|
|
+ // 如果没有代金券,设置为空列表
|
|
|
+ store.setCouponList(new ArrayList<>());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建分页对象
|
|
|
+ IPage<StoreInfoVo> resultPage = new Page<>(pageNum, pageSize);
|
|
|
+ resultPage.setRecords(pagedList);
|
|
|
+ resultPage.setTotal(total);
|
|
|
+ resultPage.setCurrent(pageNum);
|
|
|
+ resultPage.setSize(pageSize);
|
|
|
+ resultPage.setPages((total + pageSize - 1) / pageSize);
|
|
|
+
|
|
|
+ return resultPage;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<StoreDictionary> getAllBusinessSection() {
|
|
|
+ // 查询所有经营种类数据
|
|
|
+ LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
|
|
|
+ queryWrapper.in(StoreDictionary::getTypeName, "business_section","business_type","business_classify");
|
|
|
+ queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
|
|
|
+ queryWrapper.orderByAsc(StoreDictionary::getSortId);
|
|
|
+ List<StoreDictionary> storeDictionaryList = storeDictionaryMapper.selectList(queryWrapper);
|
|
|
+
|
|
|
+ // 构建三级树形结构
|
|
|
+ return buildTreeOptimized(storeDictionaryList);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<StoreDictionary> getAllBusinessSection(String businessSection) {
|
|
|
+ // 如果没有传入一级分类参数,返回所有分类
|
|
|
+ if (businessSection == null || businessSection.trim().isEmpty()) {
|
|
|
+ return getAllBusinessSection();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 1. 根据dictId查询一级分类
|
|
|
+ StoreDictionary firstLevelDict = storeDictionaryMapper.selectOne(
|
|
|
+ new LambdaQueryWrapper<StoreDictionary>()
|
|
|
+ .eq(StoreDictionary::getDictId, businessSection.trim())
|
|
|
+ .eq(StoreDictionary::getTypeName, "business_section")
|
|
|
+ .eq(StoreDictionary::getDeleteFlag, 0)
|
|
|
+ );
|
|
|
+
|
|
|
+ if (firstLevelDict == null) {
|
|
|
+ log.warn("未找到一级分类,businessSection={}", businessSection);
|
|
|
+ return new ArrayList<>();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 查询该一级分类下的所有二级分类(business_type)
|
|
|
+ List<StoreDictionary> secondLevelDicts = storeDictionaryMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<StoreDictionary>()
|
|
|
+ .eq(StoreDictionary::getTypeName, "business_type")
|
|
|
+ .eq(StoreDictionary::getParentId, firstLevelDict.getId())
|
|
|
+ .eq(StoreDictionary::getDeleteFlag, 0)
|
|
|
+ .orderByAsc(StoreDictionary::getSortId)
|
|
|
+ );
|
|
|
+
|
|
|
+ if (secondLevelDicts.isEmpty()) {
|
|
|
+ // 如果没有二级分类,只返回一级分类
|
|
|
+ firstLevelDict.setStoreDictionaryList(new ArrayList<>());
|
|
|
+ return Collections.singletonList(firstLevelDict);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 获取所有二级分类的ID
|
|
|
+ List<Integer> secondLevelIds = secondLevelDicts.stream()
|
|
|
+ .map(StoreDictionary::getId)
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ // 4. 查询这些二级分类下的所有三级分类(business_classify)
|
|
|
+ List<StoreDictionary> thirdLevelDicts = storeDictionaryMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<StoreDictionary>()
|
|
|
+ .eq(StoreDictionary::getTypeName, "business_classify")
|
|
|
+ .in(StoreDictionary::getParentId, secondLevelIds)
|
|
|
+ .eq(StoreDictionary::getDeleteFlag, 0)
|
|
|
+ .orderByAsc(StoreDictionary::getSortId)
|
|
|
+ );
|
|
|
+
|
|
|
+ // 5. 构建树形结构
|
|
|
+ // 将一级分类、二级分类、三级分类合并
|
|
|
+ List<StoreDictionary> allDicts = new ArrayList<>();
|
|
|
+ allDicts.add(firstLevelDict);
|
|
|
+ allDicts.addAll(secondLevelDicts);
|
|
|
+ allDicts.addAll(thirdLevelDicts);
|
|
|
+
|
|
|
+ // 构建树形结构
|
|
|
+ return buildTreeOptimized(allDicts);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * web-分页查询店铺信息
|
|
|
+ *
|
|
|
+
|
|
|
+ * @return IPage<StoreInfoVo>
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public List<StoreInfoVo> getMoreRecommendedStores(Double lon , Double lat, String businessSection, String businessTypes, String businessClassify) {
|
|
|
+ // 参数校验
|
|
|
+ if (lon == null || lat == null) {
|
|
|
+ log.warn("获取更多推荐店铺失败,经纬度为空");
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+
|
|
|
+ QueryWrapper<StoreInfoVo> queryWrapper = new QueryWrapper<>();
|
|
|
+ queryWrapper.eq("a.delete_flag", 0).eq("b.delete_flag", 0);
|
|
|
+ //如果查询未过期
|
|
|
+ // 获取当前时刻
|
|
|
+ Date currentDate = new Date();
|
|
|
+ // 获取当前日期和时间
|
|
|
+ Calendar calendar = Calendar.getInstance();
|
|
|
+ // 将时间设置为 0 点
|
|
|
+ calendar.set(Calendar.HOUR_OF_DAY, 0);
|
|
|
+ calendar.set(Calendar.MINUTE, 0);
|
|
|
+ calendar.set(Calendar.SECOND, 0);
|
|
|
+ calendar.set(Calendar.MILLISECOND, 0);
|
|
|
+ // 加上 30 天
|
|
|
+ calendar.add(Calendar.DAY_OF_MONTH, 30);
|
|
|
+ // 如果 expiration_time 为空则不做过期判断;如果不为空则要求大于当前时间
|
|
|
+ queryWrapper.and(w -> w.isNull("a.expiration_time")
|
|
|
+ .or()
|
|
|
+ .gt("a.expiration_time", currentDate));
|
|
|
+
|
|
|
+ // 如果 food_licence_expiration_time 为空则不做过期判断;如果不为空则要求大于当前时间
|
|
|
+ queryWrapper.and(w -> w.isNull("a.food_licence_expiration_time")
|
|
|
+ .or()
|
|
|
+ .gt("a.food_licence_expiration_time", currentDate));
|
|
|
+
|
|
|
+ // 构建一级分类
|
|
|
+ if(StringUtils.isNotEmpty(businessSection)){
|
|
|
+ queryWrapper.eq("a.business_section", businessSection);
|
|
|
+ // 构建二级分类
|
|
|
+ if(StringUtils.isNotEmpty(businessTypes)){
|
|
|
+ queryWrapper.eq("a.business_types", businessTypes);
|
|
|
+ // 构建三级分类
|
|
|
+ if(StringUtils.isNotEmpty(businessClassify)){
|
|
|
+ // 解析businessClassify参数(格式:1,2,3)
|
|
|
+ String[] classifyArray = businessClassify.split(",");
|
|
|
+ // 使用FIND_IN_SET函数检查数据库字段是否包含参数中的任何一个值
|
|
|
+ queryWrapper.and(wrapper -> {
|
|
|
+ for (int i = 0; i < classifyArray.length; i++) {
|
|
|
+ String classify = classifyArray[i].trim();
|
|
|
+ if (StringUtils.isNotEmpty(classify)) {
|
|
|
+ if (i == 0) {
|
|
|
+ wrapper.apply("FIND_IN_SET({0}, a.business_classify) > 0", classify);
|
|
|
+ } else {
|
|
|
+ wrapper.or().apply("FIND_IN_SET({0}, a.business_classify) > 0", classify);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构建position参数(格式:经度,纬度)
|
|
|
+ String position = lon + "," + lat;
|
|
|
+ List<StoreInfoVo> storeInfoVoList = storeInfoMapper.getMoreRecommendedStores(queryWrapper, position);
|
|
|
+ if (CollectionUtils.isEmpty(storeInfoVoList)) {
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+ // 提前查询所有需要的字典数据
|
|
|
+ List<StoreInfoVo> collect = storeInfoVoList.stream().filter(record -> StringUtils.isNotEmpty(record.getStoreType())).collect(Collectors.toList());
|
|
|
+ Set<String> allTypes = collect.stream().map(StoreInfoVo::getStoreType).flatMap(type -> Arrays.stream(type.split(","))).collect(Collectors.toSet());
|
|
|
+
|
|
|
+ List<StoreDictionary> storeDictionaries = storeDictionaryMapper.selectList(new LambdaQueryWrapper<StoreDictionary>().eq(StoreDictionary::getTypeName, "storeType").isNull(StoreDictionary::getParentId).in(!allTypes.isEmpty(), StoreDictionary::getDictId, allTypes));
|
|
|
+ Map<String, String> typeMap = storeDictionaries.stream().collect(Collectors.toMap(StoreDictionary::getDictId, StoreDictionary::getDictDetail));
|
|
|
+
|
|
|
+
|
|
|
+ // 计算平均分和评价
|
|
|
+ Map<String, List<Map<String, Object>>> avgScoreMap = new HashMap<>();
|
|
|
+ Map<Integer, List<StoreComment>> commentMap = new HashMap<>();
|
|
|
+
|
|
|
+ // 注意:需要将store_id转换为String类型,与后续containsKey判断保持一致
|
|
|
+ avgScoreMap = storeEvaluationMapper.allStoreAvgScore().stream().collect(Collectors.groupingBy(o -> o.get("store_id").toString()));
|
|
|
+ commentMap = storeCommentMapper.selectList(new QueryWrapper<StoreComment>().eq("business_type", "5").eq("delete_flag", 0)).stream().collect(Collectors.groupingBy(StoreComment::getStoreId));
|
|
|
+
|
|
|
+
|
|
|
+ // 查询入口头图
|
|
|
+ List<Integer> storeIds = storeInfoVoList.stream()
|
|
|
+ .map(StoreInfoVo::getId) // 假设 StoreImg 有 getStoreUrl() 方法
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ List<StoreImg> storeImgList = storeImgMapper.selectList(new QueryWrapper<StoreImg>().in("store_id", storeIds).eq("img_type", 1));
|
|
|
+ Map<Integer, List<StoreImg>> storeCollect = storeImgList.stream()
|
|
|
+ .collect(Collectors.groupingBy(StoreImg::getStoreId));
|
|
|
+
|
|
|
+
|
|
|
+ for (StoreInfoVo record : storeInfoVoList) {
|
|
|
+ //处理类型
|
|
|
+ if (StringUtils.isNotEmpty(record.getStoreType())) {
|
|
|
+ String[] types = record.getStoreType().split(",");
|
|
|
+ List<String> typeDetails = Arrays.stream(types).map(typeMap::get).filter(Objects::nonNull).collect(Collectors.toList());
|
|
|
+ record.setStoreTypeStr(String.join(",", typeDetails));
|
|
|
+ record.setStoreTypeList(Arrays.asList(types));
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ // 加入头图
|
|
|
+ if(!CollectionUtils.isEmpty(storeCollect) && storeCollect.containsKey(record.getId())){
|
|
|
+ List<StoreImg> storeImgs = storeCollect.get(record.getId());
|
|
|
+ if(!CollectionUtils.isEmpty(storeImgs)){
|
|
|
+ record.setEntranceImage(storeImgs.get(0).getImgUrl());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ //写经纬度
|
|
|
+ String[] split = record.getStorePosition().split(",");
|
|
|
+ record.setStorePositionLongitude(split[0]);
|
|
|
+ record.setStorePositionLatitude(split[1]);
|
|
|
+ // 格式化距离,移除无意义的小数位
|
|
|
+ if (!StringUtils.isEmpty(record.getDistance3())) {
|
|
|
+ try {
|
|
|
+ BigDecimal distanceValue = new BigDecimal(record.getDistance3());
|
|
|
+ record.setDistance3(distanceValue.stripTrailingZeros().toPlainString());
|
|
|
+ } catch (NumberFormatException ex) {
|
|
|
+ log.warn("店铺距离格式化失败, storeId: {}, distance3: {}", record.getId(), record.getDistance3(), ex);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ //处理一下到期状态
|
|
|
+ Date expirationTime = record.getExpirationTime();
|
|
|
+ if (expirationTime != null) {
|
|
|
+ // 获取当前时间
|
|
|
+ Calendar now = Calendar.getInstance();
|
|
|
+ Date nowCurrentDate = now.getTime();
|
|
|
+ // 计算 30 天后的时间
|
|
|
+ now.add(Calendar.DAY_OF_YEAR, 30);
|
|
|
+ Date thirtyDaysLater = now.getTime();
|
|
|
+ // 比较两个日期
|
|
|
+ if (expirationTime.after(currentDate)) {
|
|
|
+ record.setExpiredState("0");
|
|
|
+ if ((expirationTime.after(nowCurrentDate) || expirationTime.equals(nowCurrentDate)) && expirationTime.before(thirtyDaysLater)) {
|
|
|
+ record.setExpiredState("1");
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ record.setExpiredState("2");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取当前时间
|
|
|
+ LocalDate nowLocal = LocalDate.now();
|
|
|
+ // 将 expirationTime 转换为 LocalDate
|
|
|
+ LocalDate expDate = expirationTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
|
|
|
+ // 计算距离到期的天数
|
|
|
+ long daysToExpire = ChronoUnit.DAYS.between(nowLocal, expDate);
|
|
|
+ record.setDaysToExpire(daysToExpire);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置店铺得分,设置店铺人均消费,设置总评论数
|
|
|
+ if (avgScoreMap.containsKey(String.valueOf(record.getId()))) {
|
|
|
+ record.setAvgScore(String.valueOf(avgScoreMap.get(String.valueOf(record.getId())).get(0).get("avg_score")));
|
|
|
+ }
|
|
|
+ // 设置店铺得分,设置店铺人均消费,设置总评论数
|
|
|
+ if (commentMap.containsKey(record.getId())) {
|
|
|
+ record.setTotalNum(String.valueOf(commentMap.get(record.getId()).size()));
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ // SQL已经实现了距离过滤和排序,直接返回结果
|
|
|
+ return storeInfoVoList;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public StoreInfoVo getClientStoreDetail(String storeId, String userId, String jingdu, String weidu) {
|
|
|
+ StoreInfoVo result = new StoreInfoVo();
|
|
|
+ StoreInfo storeInfo = storeInfoMapper.selectById(storeId);
|
|
|
+ BeanUtils.copyProperties(storeInfo, result);
|
|
|
+ //将经营板块和种类拆分成集合
|
|
|
+ String businessTypes = storeInfo.getBusinessTypes();
|
|
|
+ if (StringUtils.isNotEmpty(businessTypes)) {
|
|
|
+ String[] split = businessTypes.split(",");
|
|
|
+ List<String> list = Arrays.asList(split);
|
|
|
+ result.setBusinessTypesList(list);
|
|
|
+ }
|
|
|
+ //存入用户账户
|
|
|
+ StoreUser storeUser = storeUserMapper.selectOne(new LambdaQueryWrapper<StoreUser>().eq(StoreUser::getStoreId, storeId));
|
|
|
+ if (storeUser != null) {
|
|
|
+ result.setUserAccount(storeUser.getId().toString());
|
|
|
+ result.setStorePhone(storeUser.getPhone());
|
|
|
+ result.setStoreUserName(storeUser.getName());
|
|
|
+ result.setIdCard(storeUser.getIdCard());
|
|
|
+ }
|
|
|
+// //存入执照图片地址
|
|
|
+// List<StoreImg> storeImgs = storeImgMapper.selectList(new LambdaQueryWrapper<StoreImg>().eq(StoreImg::getStoreId, storeId).eq(StoreImg::getImgType, 14));
|
|
|
+// List<String> storeImgPaths = new ArrayList<>();
|
|
|
+// for (StoreImg storeImg : storeImgs) {
|
|
|
+// storeImgPaths.add(storeImg.getImgUrl());
|
|
|
+// }
|
|
|
+// result.setBusinessLicenseAddress(storeImgPaths);
|
|
|
+// //存入合同图片地址
|
|
|
+// List<StoreImg> storeContractImageImgs = storeImgMapper.selectList(new LambdaQueryWrapper<StoreImg>().eq(StoreImg::getStoreId, storeId).eq(StoreImg::getImgType, 15));
|
|
|
+// List<String> storeContractImagePathImgs = new ArrayList<>();
|
|
|
+// for (StoreImg storeImg : storeContractImageImgs) {
|
|
|
+// storeContractImagePathImgs.add(storeImg.getImgUrl());
|
|
|
+// }
|
|
|
+// result.setContractImageList(storeContractImagePathImgs);
|
|
|
+// //存入续签合同地址
|
|
|
+// List<StoreImg> renewContractImgs = storeImgMapper.selectList(new LambdaQueryWrapper<StoreImg>().eq(StoreImg::getStoreId, storeId).eq(StoreImg::getImgType, 22));
|
|
|
+// List<String> renewContractImagePathImgs = new ArrayList<>();
|
|
|
+// for (StoreImg storeImg : renewContractImgs) {
|
|
|
+// renewContractImagePathImgs.add(storeImg.getImgUrl());
|
|
|
+// }
|
|
|
+// result.setRenewContractImageList(renewContractImagePathImgs);
|
|
|
+// //存入经营许可证通过地址
|
|
|
+// List<StoreImg> foodLicenceImgs = storeImgMapper.selectList(new LambdaQueryWrapper<StoreImg>().eq(StoreImg::getStoreId, storeId).eq(StoreImg::getImgType, 25));
|
|
|
+// List<String> foodLicenceImgsPathImgs = new ArrayList<>();
|
|
|
+// for (StoreImg storeImg : foodLicenceImgs) {
|
|
|
+// foodLicenceImgsPathImgs.add(storeImg.getImgUrl());
|
|
|
+// }
|
|
|
+// result.setFoodLicenceImageList(foodLicenceImgsPathImgs);
|
|
|
+// //存入经营许可证未通过地址
|
|
|
+// List<StoreImg> notPassFoodLicenceImgs = storeImgMapper.selectList(new LambdaQueryWrapper<StoreImg>().eq(StoreImg::getStoreId, storeId).eq(StoreImg::getImgType, 24));
|
|
|
+// List<String> notPassFoodLicenceList = new ArrayList<>();
|
|
|
+// for (StoreImg storeImg : notPassFoodLicenceImgs) {
|
|
|
+// notPassFoodLicenceList.add(storeImg.getImgUrl());
|
|
|
+// }
|
|
|
+// result.setNotPassFoodLicenceImageList(notPassFoodLicenceList);
|
|
|
+ // 存放商家入口图
|
|
|
+ List<StoreImg> storeEntranceImageImgs = storeImgMapper.selectList(new LambdaQueryWrapper<StoreImg>().eq(StoreImg::getStoreId, storeId).eq(StoreImg::getImgType, 1));
|
|
|
+ if (!storeEntranceImageImgs.isEmpty()) {
|
|
|
+ result.setEntranceImage(storeEntranceImageImgs.get(0).getImgUrl());
|
|
|
+ } else {
|
|
|
+ result.setEntranceImage("null");
|
|
|
+ }
|
|
|
+ // 存放商家头像
|
|
|
+ List<StoreImg> storeImgs1 = storeImgMapper.selectList(new LambdaQueryWrapper<StoreImg>().eq(StoreImg::getStoreId, storeId).eq(StoreImg::getImgType, 10));
|
|
|
+ if (!storeImgs1.isEmpty()) {
|
|
|
+ result.setImgUrl(storeImgs1.get(0).getImgUrl());
|
|
|
+ } else {
|
|
|
+ result.setImgUrl("null");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取店铺相册
|
|
|
+ List<StoreImg> storeAlbumList = new ArrayList<>();
|
|
|
+ if(storeInfo.getImgMode() != null && storeInfo.getImgMode() == 0){
|
|
|
+ storeAlbumList = storeImgService.getStoreImg(Integer.parseInt(storeId), 20);
|
|
|
+ } else {
|
|
|
+ storeAlbumList = storeImgService.getStoreImg(Integer.parseInt(storeId), 21);
|
|
|
+ }
|
|
|
+
|
|
|
+ if(!CollectionUtils.isEmpty(storeAlbumList)){
|
|
|
+ List<String> storeAlbumUrlList = storeAlbumList.stream().map(StoreImg::getImgUrl) // 假设 StoreImg 有 getStoreUrl() 方法
|
|
|
+ .filter(url -> url != null && !url.trim().isEmpty()) // 过滤空值
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ result.setStoreAlbumUrlList(storeAlbumUrlList);
|
|
|
+ } else {
|
|
|
+ result.setStoreAlbumUrlList(new ArrayList<>());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置经纬度
|
|
|
+ result.setStorePositionLongitude(result.getStorePosition().split(",")[0]);
|
|
|
+ result.setStorePositionLatitude(result.getStorePosition().split(",")[1]);
|
|
|
+ // 设置距离
|
|
|
+ if ((jingdu != null && !jingdu.isEmpty()) && (weidu != null && !weidu.isEmpty())) {
|
|
|
+ /*double storeJing = Double.parseDouble(result.getStorePosition().split(",")[0]);
|
|
|
+ double storeWei = Double.parseDouble(result.getStorePosition().split(",")[1]);
|
|
|
+ double storeDistance = DistanceUtil.haversineCalculateDistance(Double.parseDouble(jingdu), Double.parseDouble(weidu), storeJing, storeWei);*/
|
|
|
+
|
|
|
+ Double distance = storeInfoMapper.getStoreDistance(jingdu + "," + weidu,result.getId());
|
|
|
+
|
|
|
+ result.setDistance(distance);
|
|
|
+ }
|
|
|
+ // 计算店铺到最近地铁站的距离
|
|
|
+ JSONObject nearbySubway = gaoDeMapUtil.getNearbySubway(result.getStorePosition().split(",")[0], result.getStorePosition().split(",")[1]);
|
|
|
+ // 地铁名
|
|
|
+ String subWayName = nearbySubway.getString("name");
|
|
|
+ result.setSubwayName(subWayName);
|
|
|
+ // 地铁站经纬度
|
|
|
+ String subWayJing = nearbySubway.getString("location") == null ? null : nearbySubway.getString("location").split(",")[0];
|
|
|
+ String subWayWei = nearbySubway.getString("location") == null ? null : nearbySubway.getString("location").split(",")[1];
|
|
|
+ if ((subWayJing != null && !subWayJing.isEmpty()) && (subWayWei != null && !subWayWei.isEmpty())) {
|
|
|
+ double storeJing = Double.parseDouble(result.getStorePosition().split(",")[0]);
|
|
|
+ double storeWei = Double.parseDouble(result.getStorePosition().split(",")[1]);
|
|
|
+ double storeDistance2 = DistanceUtil.haversineCalculateDistance(Double.parseDouble(subWayJing), Double.parseDouble(subWayWei), storeJing, storeWei);
|
|
|
+ result.setDistance2(storeDistance2);
|
|
|
+ } else {
|
|
|
+ result.setDistance2(0);
|
|
|
+ }
|
|
|
+ // 当前登录用户是否收藏
|
|
|
+ LambdaUpdateWrapper<LifeCollect> shouCangWrapper = new LambdaUpdateWrapper<>();
|
|
|
+ shouCangWrapper.eq(LifeCollect::getUserId, userId).eq(LifeCollect::getStoreId, storeId);
|
|
|
+ List<LifeCollect> shouCangList = lifeCollectMapper.selectList(shouCangWrapper);
|
|
|
+ if (null == shouCangList || shouCangList.isEmpty()) {
|
|
|
+ result.setCollection(0);
|
|
|
+ } else {
|
|
|
+ result.setCollection(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 该用户的打卡记录
|
|
|
+ LambdaQueryWrapper<StoreClockIn> clockInWrapper = new LambdaQueryWrapper<>();
|
|
|
+ clockInWrapper.eq(StoreClockIn::getUserId, userId);
|
|
|
+ List<StoreClockIn> clockInList = storeClockInMapper.selectList(clockInWrapper);
|
|
|
+
|
|
|
+ List<StoreClockIn> clockStoreList = clockInList.stream().filter(item -> item.getStoreId() == Integer.parseInt(storeId)).collect(Collectors.toList());
|
|
|
+ // 该用户是否在该店铺打过卡
|
|
|
+ if (!clockStoreList.isEmpty()) {
|
|
|
+ result.setClockInStore(1);
|
|
|
+ } else {
|
|
|
+ result.setClockInStore(0);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 今天在该店铺是否打过卡
|
|
|
+ int today = (int) clockStoreList.stream().filter(item -> item.getCreatedTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDate().equals(LocalDate.now())).count();
|
|
|
+ if (today > 0) {
|
|
|
+ result.setClockInStoreToday(1);
|
|
|
+ } else {
|
|
|
+ result.setClockInStoreToday(0);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ Map<String, Object> commitCountAndScore = storeCommentService.getCommitCountAndScore(null, 5, Integer.parseInt(storeId), null, null);
|
|
|
+ result.setScore(Double.parseDouble(commitCountAndScore.get("score").toString()));
|
|
|
+ result.setCommitCount(commitCountAndScore.get("commitCount").toString());
|
|
|
+
|
|
|
+
|
|
|
+ // 在该店铺的打卡次数
|
|
|
+ result.setClockInStoreNum(clockStoreList.size());
|
|
|
+
|
|
|
+ // 该用户打卡的所有店铺次数(一个店铺只算一次)
|
|
|
+ int clockInNum = (int) clockInList.stream().map(StoreClockIn::getStoreId).distinct().count();
|
|
|
+ result.setClockInNum(clockInNum);
|
|
|
+
|
|
|
+// // 获取店铺动态列表
|
|
|
+// QueryWrapper<LifeUserDynamics> dynamicsWrapper = new QueryWrapper<>();
|
|
|
+// dynamicsWrapper.eq("phone_id", "store_" + result.getStorePhone()).orderByDesc("lud.created_time");
|
|
|
+// dynamicsWrapper.eq("lud.delete_flag", 0);
|
|
|
+//
|
|
|
+// LambdaQueryWrapper<LifeBlacklist> lambdaQueryWrapper1 = new LambdaQueryWrapper<>();
|
|
|
+// lambdaQueryWrapper1.eq(LifeBlacklist :: getBlockerId, userId);
|
|
|
+// lambdaQueryWrapper1.eq(LifeBlacklist :: getBlockedPhoneId, "store_" + result.getStorePhone());
|
|
|
+// LifeBlacklist blacklist = lifeBlacklistMapper.selectOne(lambdaQueryWrapper1);
|
|
|
+// List<LifeUserDynamicsVo> storeDynamicslist = new ArrayList<>();
|
|
|
+
|
|
|
+// //判断没有拉黑当前门店账户 查出门店动态
|
|
|
+// if(blacklist == null){
|
|
|
+// storeDynamicslist = lifeUserDynamicsMapper.getStoreDynamicslist(userId, "store_" + result.getStorePhone());
|
|
|
+// }
|
|
|
+//
|
|
|
+// List<String> followList = new ArrayList<>();
|
|
|
+// List<String> fansList = new ArrayList<>();
|
|
|
+
|
|
|
+// if (StringUtils.isNotEmpty(userId)) {
|
|
|
+// LifeUser lifeUser = lifeUserMapper.selectById(userId);
|
|
|
+// if (lifeUser != null && StringUtils.isNotEmpty(lifeUser.getUserPhone())) {
|
|
|
+// // 查询我的关注信息,构建关注者ID列表
|
|
|
+// LambdaQueryWrapper<LifeFans> lifeFansWrapper = new LambdaQueryWrapper<>();
|
|
|
+// lifeFansWrapper.eq(LifeFans::getFansId, "user_" + result.getStorePhone());
|
|
|
+// List<LifeFans> lifeFansList = lifeFansMapper.selectList(lifeFansWrapper);
|
|
|
+// if (!CollectionUtils.isEmpty(lifeFansList)) {
|
|
|
+// followList = lifeFansList.stream().map(LifeFans::getFollowedId).collect(Collectors.toList());
|
|
|
+// }
|
|
|
+//
|
|
|
+// // 查询我的粉丝信息,构建粉丝ID列表
|
|
|
+// lifeFansWrapper = new LambdaQueryWrapper<>();
|
|
|
+// lifeFansWrapper.eq(LifeFans::getFollowedId, "user_" + result.getStorePhone());
|
|
|
+// lifeFansList = lifeFansMapper.selectList(lifeFansWrapper);
|
|
|
+// if (!CollectionUtils.isEmpty(lifeFansList)) {
|
|
|
+// fansList = lifeFansList.stream().map(LifeFans::getFansId).collect(Collectors.toList());
|
|
|
+// }
|
|
|
+// }
|
|
|
+// }
|
|
|
+
|
|
|
+// for (LifeUserDynamicsVo vo : storeDynamicslist) {
|
|
|
+// if (followList.contains(vo.getPhoneId())) {
|
|
|
+// vo.setIsFollowThis("1");
|
|
|
+// } else {
|
|
|
+// vo.setIsFollowThis("0");
|
|
|
+// }
|
|
|
+// if (fansList.contains(vo.getPhoneId())) {
|
|
|
+// vo.setIsFollowMe("1");
|
|
|
+// } else {
|
|
|
+// vo.setIsFollowMe("0");
|
|
|
+// }
|
|
|
+// }
|
|
|
+
|
|
|
+// // 返回动态最新的5条
|
|
|
+// List<LifeUserDynamicsVo> storeDynamicslist2 = storeDynamicslist.stream()
|
|
|
+// .limit(5).collect(Collectors.toList());
|
|
|
+// result.setDynamicsList(storeDynamicslist2);
|
|
|
+// //设置动态条数
|
|
|
+// Integer dynamicsNum = storeDynamicslist2.size();
|
|
|
+// result.setDynamicsNum(dynamicsNum);
|
|
|
+//
|
|
|
+// // 获取店铺动态总数
|
|
|
+// result.setTotalDynamicsNum(storeDynamicslist.size());
|
|
|
+
|
|
|
+ //营业时间
|
|
|
+ List<StoreBusinessInfo> storeBusinessInfos = storeBusinessInfoMapper.selectList(new LambdaQueryWrapper<StoreBusinessInfo>().eq(StoreBusinessInfo::getStoreId, storeId).eq(StoreBusinessInfo::getDeleteFlag, 0));
|
|
|
+ if (ObjectUtils.isNotEmpty(storeBusinessInfos)) {
|
|
|
+ result.setStoreBusinessInfo(storeBusinessInfos.get(0));
|
|
|
+ result.setStoreBusinessInfos(storeBusinessInfos);
|
|
|
+ StoreBusinessInfo storeBusinessInfo = result.getStoreBusinessInfos().stream().filter(item -> item.getBusinessType() == 1).findFirst().orElse(null);
|
|
|
+ if (ObjectUtils.isNotEmpty(storeBusinessInfo)) {
|
|
|
+
|
|
|
+ Calendar calendar = Calendar.getInstance(); // 获取Calendar实例
|
|
|
+ int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK); // 获取星期几,注意Calendar中的DAY_OF_WEEK是从1(代表星期天)开始的
|
|
|
+ String[] days = {"7", "1", "2", "3", "4", "5", "6"};
|
|
|
+ String day = days[dayOfWeek - 1];
|
|
|
+ if (storeBusinessInfo.getBusinessDate().contains(day)) {
|
|
|
+ if (StringUtils.isNotEmpty(storeBusinessInfo.getStartTime()) && StringUtils.isNotEmpty(storeBusinessInfo.getEndTime())) {
|
|
|
+ LocalTime now = LocalTime.now();
|
|
|
+ List<String> startList = Arrays.asList(storeBusinessInfo.getStartTime().split(":"));
|
|
|
+ List<String> endList = Arrays.asList(storeBusinessInfo.getEndTime().split(":"));
|
|
|
+ LocalTime start = LocalTime.of(Integer.parseInt(startList.get(0)), Integer.parseInt(startList.get(1)));
|
|
|
+ LocalTime end = LocalTime.of(Integer.parseInt(endList.get(0)), Integer.parseInt(startList.get(1)));
|
|
|
+ if (now.isAfter(start) && now.isBefore(end)) {
|
|
|
+ result.setYyFlag(1);
|
|
|
+ } else {
|
|
|
+ result.setYyFlag(0);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ result.setYyFlag(0);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ LambdaQueryWrapper<StoreDictionary> storeDictionaryLambdaQueryWrapper = new LambdaQueryWrapper<>();
|
|
|
+ storeDictionaryLambdaQueryWrapper.eq(StoreDictionary::getTypeName, "businessStatus")
|
|
|
+ .eq(StringUtils.isNotEmpty(result.getBusinessStatus().toString()), StoreDictionary::getDictId, result.getBusinessStatus());
|
|
|
+ List<StoreDictionary> storeDictionaries = storeDictionaryMapper.selectList(storeDictionaryLambdaQueryWrapper);
|
|
|
+ if (!storeDictionaries.isEmpty()) {
|
|
|
+ result.setBusinessStatusStr(storeDictionaries.get(0).getDictDetail());
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<LifeCouponVo> getStoreCouponList(String storeId) {
|
|
|
+ // 获取店铺代金券列表
|
|
|
+ LambdaUpdateWrapper<LifeCoupon> quanWrapper = new LambdaUpdateWrapper<>();
|
|
|
+ quanWrapper.eq(LifeCoupon::getStoreId, storeId).eq(LifeCoupon::getStatus, CouponStatusEnum.ONGOING.getCode()).eq(LifeCoupon::getType, 1);
|
|
|
+ List<LifeCoupon> quanList = lifeCouponMapper.selectList(quanWrapper);
|
|
|
+ List<LifeCouponVo> quanVoList = new ArrayList<>();
|
|
|
+ List<String> collect = quanList.stream().map(LifeCoupon::getId).collect(Collectors.toList());
|
|
|
+ // 设置已售数量
|
|
|
+ // 定义需要的订单状态集合
|
|
|
+ Set<Integer> excludeStatuses = new HashSet<>(Arrays.asList(
|
|
|
+ OrderStatusEnum.WAIT_PAY.getStatus(),
|
|
|
+ OrderStatusEnum.WAIT_USE.getStatus(),
|
|
|
+ OrderStatusEnum.USED.getStatus()
|
|
|
+ ));
|
|
|
+ if (!collect.isEmpty()) {
|
|
|
+ List<LifeUserOrderVo> quanCount = lifeUserOrderMapper.getQuanCount(new QueryWrapper<LifeUserOrderVo>()
|
|
|
+ .eq("luo.store_id", storeId)
|
|
|
+ .eq("luo.coupon_type", CouponTypeEnum.COUPON.getCode())
|
|
|
+ .eq("luo.delete_flag", 0)
|
|
|
+ .in("ocm.status", excludeStatuses)
|
|
|
+ .groupBy("ocm.coupon_id"));
|
|
|
+ quanList.forEach(a -> {
|
|
|
+ LifeCouponVo lifeCouponVo = new LifeCouponVo();
|
|
|
+ BeanUtils.copyProperties(a, lifeCouponVo);
|
|
|
+ quanCount.forEach(item -> {
|
|
|
+ if (a.getId().equals(item.getCouponId().toString())) {
|
|
|
+ lifeCouponVo.setCount(item.getCount());
|
|
|
+ }
|
|
|
+ });
|
|
|
+ quanVoList.add(lifeCouponVo);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ return quanVoList;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public StoreInfoVo getNewStoreDetail(String storeId) {
|
|
|
+
|
|
|
+ StoreInfoVo result = new StoreInfoVo();
|
|
|
+ StoreInfo storeInfo = storeInfoMapper.selectById(storeId);
|
|
|
+ BeanUtils.copyProperties(storeInfo, result);
|
|
|
+ //将经营板块和种类拆分成集合
|
|
|
+ String businessTypes = storeInfo.getBusinessTypes();
|
|
|
+ if (StringUtils.isNotEmpty(businessTypes)) {
|
|
|
+ String[] split = businessTypes.split(",");
|
|
|
+ List<String> list = Arrays.asList(split);
|
|
|
+ result.setBusinessTypesList(list);
|
|
|
+ }
|
|
|
+ //存入用户账户
|
|
|
+ StoreUser storeUser = storeUserMapper.selectOne(new LambdaQueryWrapper<StoreUser>().eq(StoreUser::getStoreId, storeId));
|
|
|
+ if (storeUser != null) {
|
|
|
+ result.setUserAccount(storeUser.getId().toString());
|
|
|
+ result.setStorePhone(storeUser.getPhone());
|
|
|
+ result.setStoreUserName(storeUser.getName());
|
|
|
+ result.setIdCard(storeUser.getIdCard());
|
|
|
+ }
|
|
|
+ //存入执照图片地址
|
|
|
+ List<StoreImg> storeImgs = storeImgMapper.selectList(new LambdaQueryWrapper<StoreImg>().eq(StoreImg::getStoreId, storeId).eq(StoreImg::getImgType, 14));
|
|
|
+ List<String> storeImgPaths = new ArrayList<>();
|
|
|
+ for (StoreImg storeImg : storeImgs) {
|
|
|
+ storeImgPaths.add(storeImg.getImgUrl());
|
|
|
+ }
|
|
|
+ result.setBusinessLicenseAddress(storeImgPaths);
|
|
|
+ //存入合同图片地址
|
|
|
+ List<StoreImg> storeContractImageImgs = storeImgMapper.selectList(new LambdaQueryWrapper<StoreImg>().eq(StoreImg::getStoreId, storeId).eq(StoreImg::getImgType, 15));
|
|
|
+ List<String> storeContractImagePathImgs = new ArrayList<>();
|
|
|
+ for (StoreImg storeImg : storeContractImageImgs) {
|
|
|
+ storeContractImagePathImgs.add(storeImg.getImgUrl());
|
|
|
+ }
|
|
|
+ result.setContractImageList(storeContractImagePathImgs);
|
|
|
+ //存入续签合同地址
|
|
|
+ List<StoreImg> renewContractImgs = storeImgMapper.selectList(new LambdaQueryWrapper<StoreImg>().eq(StoreImg::getStoreId, storeId).eq(StoreImg::getImgType, 22));
|
|
|
+ List<String> renewContractImagePathImgs = new ArrayList<>();
|
|
|
+ for (StoreImg storeImg : renewContractImgs) {
|
|
|
+ renewContractImagePathImgs.add(storeImg.getImgUrl());
|
|
|
+ }
|
|
|
+ result.setRenewContractImageList(renewContractImagePathImgs);
|
|
|
+ //存入经营许可证通过地址
|
|
|
+ List<StoreImg> foodLicenceImgs = storeImgMapper.selectList(new LambdaQueryWrapper<StoreImg>().eq(StoreImg::getStoreId, storeId).eq(StoreImg::getImgType, 25));
|
|
|
+ List<String> foodLicenceImgsPathImgs = new ArrayList<>();
|
|
|
+ for (StoreImg storeImg : foodLicenceImgs) {
|
|
|
+ foodLicenceImgsPathImgs.add(storeImg.getImgUrl());
|
|
|
+ }
|
|
|
+ result.setFoodLicenceImageList(foodLicenceImgsPathImgs);
|
|
|
+ //存入经营许可证未通过地址
|
|
|
+ List<StoreImg> notPassFoodLicenceImgs = storeImgMapper.selectList(new LambdaQueryWrapper<StoreImg>().eq(StoreImg::getStoreId, storeId).eq(StoreImg::getImgType, 24));
|
|
|
+ List<String> notPassFoodLicenceList = new ArrayList<>();
|
|
|
+ for (StoreImg storeImg : notPassFoodLicenceImgs) {
|
|
|
+ notPassFoodLicenceList.add(storeImg.getImgUrl());
|
|
|
+ }
|
|
|
+ result.setNotPassFoodLicenceImageList(notPassFoodLicenceList);
|
|
|
+ // 存放商家入口图
|
|
|
+ List<StoreImg> storeEntranceImageImgs = storeImgMapper.selectList(new LambdaQueryWrapper<StoreImg>().eq(StoreImg::getStoreId, storeId).eq(StoreImg::getImgType, 1));
|
|
|
+ if (!storeEntranceImageImgs.isEmpty()) {
|
|
|
+ result.setEntranceImage(storeEntranceImageImgs.get(0).getImgUrl());
|
|
|
+ } else {
|
|
|
+ result.setEntranceImage("null");
|
|
|
+ }
|
|
|
+ // 存放商家头像
|
|
|
+ List<StoreImg> storeImgs1 = storeImgMapper.selectList(new LambdaQueryWrapper<StoreImg>().eq(StoreImg::getStoreId, storeId).eq(StoreImg::getImgType, 10));
|
|
|
+ if (!storeImgs1.isEmpty()) {
|
|
|
+ result.setImgUrl(storeImgs1.get(0).getImgUrl());
|
|
|
+ } else {
|
|
|
+ result.setImgUrl("null");
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ // 店铺平均分
|
|
|
+ /*Map<Object, List<Map<String, Object>>> avgScoreMap = storeEvaluationMapper.allStoreAvgScore().stream().collect(Collectors.groupingBy(o -> o.get("store_id")));
|
|
|
+ if (avgScoreMap.containsKey(String.valueOf(result.getId()))) {
|
|
|
+ result.setScore(Double.parseDouble(avgScoreMap.get(String.valueOf(result.getId())).get(0).get("avg_score").toString()));
|
|
|
+ }*/
|
|
|
+
|
|
|
+// Map<String, Object> commitCountAndScore = storeCommentService.getCommitCountAndScore(null, 5, Integer.parseInt(storeId), null, null);
|
|
|
+// result.setScore(Double.parseDouble(commitCountAndScore.get("score").toString()));
|
|
|
+// result.setCommitCount(commitCountAndScore.get("commitCount").toString());
|
|
|
+//
|
|
|
+
|
|
|
+
|
|
|
+ //营业时间
|
|
|
+ List<StoreBusinessInfo> storeBusinessInfos = storeBusinessInfoMapper.selectList(new LambdaQueryWrapper<StoreBusinessInfo>().eq(StoreBusinessInfo::getStoreId, storeId).eq(StoreBusinessInfo::getDeleteFlag, 0));
|
|
|
+ if (ObjectUtils.isNotEmpty(storeBusinessInfos)) {
|
|
|
+ result.setStoreBusinessInfo(storeBusinessInfos.get(0));
|
|
|
+ result.setStoreBusinessInfos(storeBusinessInfos);
|
|
|
+ //StoreBusinessInfo storeBusinessInfo = result.getStoreBusinessInfo();
|
|
|
+ StoreBusinessInfo storeBusinessInfo = result.getStoreBusinessInfos().stream().filter(item -> item.getBusinessType() == 1).findFirst().orElse(null);
|
|
|
+ if (ObjectUtils.isNotEmpty(storeBusinessInfo)) {
|
|
|
+
|
|
|
+ Calendar calendar = Calendar.getInstance(); // 获取Calendar实例
|
|
|
+ int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK); // 获取星期几,注意Calendar中的DAY_OF_WEEK是从1(代表星期天)开始的
|
|
|
+ String[] days = {"7", "1", "2", "3", "4", "5", "6"};
|
|
|
+ String day = days[dayOfWeek - 1];
|
|
|
+ if (storeBusinessInfo.getBusinessDate().contains(day)) {
|
|
|
+ if (StringUtils.isNotEmpty(storeBusinessInfo.getStartTime()) && StringUtils.isNotEmpty(storeBusinessInfo.getEndTime())) {
|
|
|
+ LocalTime now = LocalTime.now();
|
|
|
+ List<String> startList = Arrays.asList(storeBusinessInfo.getStartTime().split(":"));
|
|
|
+ List<String> endList = Arrays.asList(storeBusinessInfo.getEndTime().split(":"));
|
|
|
+ LocalTime start = LocalTime.of(Integer.parseInt(startList.get(0)), Integer.parseInt(startList.get(1)));
|
|
|
+ LocalTime end = LocalTime.of(Integer.parseInt(endList.get(0)), Integer.parseInt(startList.get(1)));
|
|
|
+ if (now.isAfter(start) && now.isBefore(end)) {
|
|
|
+ result.setYyFlag(1);
|
|
|
+ } else {
|
|
|
+ result.setYyFlag(0);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ result.setYyFlag(0);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ LambdaQueryWrapper<StoreDictionary> storeDictionaryLambdaQueryWrapper = new LambdaQueryWrapper<>();
|
|
|
+ storeDictionaryLambdaQueryWrapper.eq(StoreDictionary::getTypeName, "businessStatus")
|
|
|
+ .eq(StringUtils.isNotEmpty(result.getBusinessStatus().toString()), StoreDictionary::getDictId, result.getBusinessStatus());
|
|
|
+ List<StoreDictionary> storeDictionaries = storeDictionaryMapper.selectList(storeDictionaryLambdaQueryWrapper);
|
|
|
+ if (!storeDictionaries.isEmpty()) {
|
|
|
+ result.setBusinessStatusStr(storeDictionaries.get(0).getDictDetail());
|
|
|
+ }
|
|
|
+ // TODO 之后修改********** 正常OcrType由前端传存储ocr表要加新字段。传参要由前端传。
|
|
|
+ // 查询并设置各类证件OCR信息
|
|
|
+ result.setJyxkz(convertOcrResultToJson(storeUser.getId(), "BUSINESS_LICENSE", "娱乐", true));
|
|
|
+ result.setIdcardFace(convertOcrResultToJson(storeUser.getId(), "ID_CARD", "face", true));
|
|
|
+ result.setIdcardBack(convertOcrResultToJson(storeUser.getId(), "ID_CARD", "back", true));
|
|
|
+ result.setFoodLicence(convertOcrResultToJson(storeUser.getId(), "FOOD_MANAGE_LICENSE", null, true));
|
|
|
+ result.setEntertainmentLicence(convertOcrResultToJson(storeUser.getId(), "BUSINESS_LICENSE", "营业执照", false));
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public StoreInfoVo editNewStoreInfo(StoreInfoDto storeInfoDto) {
|
|
|
+
|
|
|
+ //获取经营板块id
|
|
|
+ Integer businessSection = storeInfoDto.getBusinessSection();
|
|
|
+ //查询经营板块名称
|
|
|
+ StoreDictionary businessSectionName = storeDictionaryMapper.selectOne(new LambdaQueryWrapper<StoreDictionary>().eq(StoreDictionary::getDictId, businessSection).eq(StoreDictionary::getTypeName, "business_section"));
|
|
|
+ //查询经营种类
|
|
|
+ List<String> businessTypes = storeInfoDto.getBusinessTypes();
|
|
|
+ List<String> businessTypeNames = new ArrayList<>();
|
|
|
+ //获取经营种类名称
|
|
|
+ for (String businessType : businessTypes) {
|
|
|
+ StoreDictionary storeDictionary = storeDictionaryMapper.selectOne(new LambdaQueryWrapper<StoreDictionary>().eq(StoreDictionary::getDictId, businessType).eq(StoreDictionary::getParentId, businessSectionName.getId()));
|
|
|
+ businessTypeNames.add(storeDictionary.getDictDetail());
|
|
|
+ }
|
|
|
+
|
|
|
+ StoreInfoVo result = new StoreInfoVo();
|
|
|
+ StoreInfo storeInfo = new StoreInfo();
|
|
|
+ BeanUtils.copyProperties(storeInfoDto, storeInfo);
|
|
|
+// List<String> storeTypeList = storeInfoDto.getStoreTypeList();
|
|
|
+// String storeType = String.join(",", storeTypeList);
|
|
|
+ //存入营运类型
|
|
|
+// storeInfo.setStoreType(storeType);
|
|
|
+
|
|
|
+ //板块及类型
|
|
|
+ storeInfo.setBusinessSection(businessSection);
|
|
|
+ storeInfo.setBusinessSectionName(businessSectionName.getDictDetail());
|
|
|
+ storeInfo.setBusinessTypes(String.join(",", businessTypes));
|
|
|
+ storeInfo.setBusinessTypesName(String.join(",", businessTypeNames));
|
|
|
+
|
|
|
+ //处理分类信息
|
|
|
+ List<String> businessClassify = storeInfoDto.getBusinessClassify();
|
|
|
+ if (!CollectionUtils.isEmpty(businessClassify)) {
|
|
|
+ List<String> businessClassifyNames = new ArrayList<>();
|
|
|
+ //批量查询分类名称
|
|
|
+ List<StoreDictionary> classifyDicts = storeDictionaryMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<StoreDictionary>()
|
|
|
+ .in(StoreDictionary::getDictId, businessClassify)
|
|
|
+ .eq(StoreDictionary::getTypeName, "business_classify")
|
|
|
+ .eq(StoreDictionary::getDeleteFlag, 0)
|
|
|
+ );
|
|
|
+ //转为Map方便快速获取
|
|
|
+ Map<String, StoreDictionary> classifyDictMap = classifyDicts.stream()
|
|
|
+ .collect(Collectors.toMap(
|
|
|
+ dict -> dict.getDictId().toString(),
|
|
|
+ Function.identity(),
|
|
|
+ (existing, replacement) -> existing
|
|
|
+ ));
|
|
|
+ //提取分类名称
|
|
|
+ for (String classifyId : businessClassify) {
|
|
|
+ StoreDictionary dict = classifyDictMap.get(classifyId);
|
|
|
+ if (Objects.nonNull(dict)) {
|
|
|
+ businessClassifyNames.add(dict.getDictDetail());
|
|
|
+ } else {
|
|
|
+ log.warn("无效的分类id:" + classifyId);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ storeInfo.setBusinessClassify(String.join(",", businessClassify));
|
|
|
+ storeInfo.setBusinessClassifyName(String.join(",", businessClassifyNames));
|
|
|
+ }
|
|
|
+
|
|
|
+ storeInfoMapper.updateById(storeInfo);
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
}
|