Procházet zdrojové kódy

feat(store): 实现商户店铺管理功能

- 新增店铺入住申请接口,支持提交店铺基本信息、图片、合同等资料
- 实现店铺草稿保存与查询功能,便于商户分步填写申请信息
- 提供店铺详情查询接口,包含门店信息、图片、菜单、营业时间等
- 集成Redis GEO功能,支持店铺地理位置存储与附近搜索- 实现店铺今日收益与订单数统计功能
- 添加行政区域信息自动填充逻辑
- 完善店铺标签初始化与通知推送机制
- 更新Swagger配置,指定平台名称为"Alien web商户平台"
wxd před 1 měsícem
rodič
revize
9346f3d030

+ 5 - 5
alien-store-platform/src/main/java/shop/alien/storeplatform/config/SwaggerConfig.java

@@ -31,7 +31,7 @@ public class SwaggerConfig {
     public Docket docket() {
         return new Docket(DocumentationType.SWAGGER_2)
                 .apiInfo(apiInfo())
-                .groupName("二手平台")
+                .groupName("Alien web商户平台")
                 .select()
                 .apis(RequestHandlerSelectors.any())
                 .paths(PathSelectors.any())
@@ -44,11 +44,11 @@ public class SwaggerConfig {
      * @return api标题Api标题信息
      */
     public ApiInfo apiInfo() {
-        Contact contact = new Contact("二手平台", "https://xxxx.xxxx", "");
+        Contact contact = new Contact("Alien web商户平台", "https://xxxx.xxxx", "");
         return new ApiInfoBuilder()
-                .title("二手平台")
-                .description("二手平台")
-                .license("二手平台")
+                .title("Alien web商户平台")
+                .description("Alien web商户平台")
+                .license("Alien web商户平台")
                 .contact(contact)
                 .termsOfServiceUrl("https://xxxx.xxxx")
                 .version("1.0.0")

+ 137 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/controller/StoreManageController.java

@@ -0,0 +1,137 @@
+package shop.alien.storeplatform.controller;
+
+import io.swagger.annotations.*;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreInfoDraft;
+import shop.alien.entity.store.dto.StoreInfoDto;
+import shop.alien.entity.store.vo.StoreInfoVo;
+import shop.alien.entity.store.vo.StoreMainInfoVo;
+import shop.alien.storeplatform.annotation.LoginRequired;
+import shop.alien.storeplatform.service.StoreManageService;
+
+/**
+ * web端商户店铺管理 前端控制器
+ *
+ * @author ssk
+ * @since 2025-01-xx
+ */
+@Slf4j
+@Api(tags = {"web端商户店铺管理"})
+@ApiSort(4)
+@CrossOrigin
+@RestController
+@RequestMapping("/storeManage")
+@RequiredArgsConstructor
+public class StoreManageController {
+
+    private final StoreManageService storeManageService;
+
+    @ApiOperation("新增店铺入住申请")
+    @ApiOperationSupport(order = 1)
+    @PostMapping("/applyStore")
+    public R<StoreInfoVo> applyStore(@RequestBody StoreInfoDto storeInfoDto) {
+        log.info("StoreManageController.applyStore?storeInfoDto={}", storeInfoDto);
+        try {
+            StoreInfoVo result = storeManageService.applyStore(storeInfoDto);
+            if (result != null) {
+                return R.data(result, "店铺入住申请已提交");
+            }
+            return R.fail("申请失败");
+        } catch (Exception e) {
+            log.error("StoreManageController.applyStore ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("保存店铺入住申请草稿")
+    @ApiOperationSupport(order = 2)
+    @PostMapping("/saveStoreDraft")
+    public R<Boolean> saveStoreDraft(@RequestBody StoreInfoDraft storeInfoDraft) {
+        log.info("StoreManageController.saveStoreDraft?storeInfoDraft={}", storeInfoDraft);
+        try {
+            int result = storeManageService.saveStoreDraft(storeInfoDraft);
+            if (result > 0) {
+                return R.data(true, "草稿保存成功");
+            }
+            return R.fail("草稿保存失败");
+        } catch (Exception e) {
+            log.error("StoreManageController.saveStoreDraft ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("查询店铺入住申请草稿")
+    @ApiOperationSupport(order = 3)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeUserId", value = "商户用户ID", dataType = "int", paramType = "query", required = true)
+    })
+    @GetMapping("/getStoreDraft")
+    public R<StoreInfoDraft> getStoreDraft(@RequestParam("storeUserId") int storeUserId) {
+        log.info("StoreManageController.getStoreDraft?storeUserId={}", storeUserId);
+        try {
+            StoreInfoDraft result = storeManageService.getStoreDraft(storeUserId);
+            return R.data(result);
+        } catch (Exception e) {
+            log.error("StoreManageController.getStoreDraft ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("获取店铺详细信息")
+    @ApiOperationSupport(order = 4)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "店铺ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/getStoreDetail")
+    public R<StoreMainInfoVo> getStoreDetail(@RequestParam("id") Integer id) {
+        log.info("StoreManageController.getStoreDetail?id={}", id);
+        try {
+            StoreMainInfoVo result = storeManageService.getStoreDetail(id);
+            if (result != null) {
+                return R.data(result);
+            }
+            return R.fail("店铺不存在");
+        } catch (Exception e) {
+            log.error("StoreManageController.getStoreDetail ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("获取店铺今日收益")
+    @ApiOperationSupport(order = 5)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeId", value = "店铺ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/getTodayIncome")
+    public R<String> getTodayIncome(@RequestParam("storeId") Integer storeId) {
+        log.info("StoreManageController.getTodayIncome?storeId={}", storeId);
+        try {
+            String income = storeManageService.getTodayIncome(storeId);
+            return R.data(income, "查询成功");
+        } catch (Exception e) {
+            log.error("StoreManageController.getTodayIncome ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("获取店铺今日订单数")
+    @ApiOperationSupport(order = 6)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeId", value = "店铺ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/getTodayOrderCount")
+    public R<Integer> getTodayOrderCount(@RequestParam("storeId") Integer storeId) {
+        log.info("StoreManageController.getTodayOrderCount?storeId={}", storeId);
+        try {
+            Integer count = storeManageService.getTodayOrderCount(storeId);
+            return R.data(count, "查询成功");
+        } catch (Exception e) {
+            log.error("StoreManageController.getTodayOrderCount ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+}
+

+ 65 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/service/StoreManageService.java

@@ -0,0 +1,65 @@
+package shop.alien.storeplatform.service;
+
+import shop.alien.entity.store.StoreInfoDraft;
+import shop.alien.entity.store.dto.StoreInfoDto;
+import shop.alien.entity.store.vo.StoreInfoVo;
+import shop.alien.entity.store.vo.StoreMainInfoVo;
+
+/**
+ * web端商户店铺管理服务接口
+ *
+ * @author ssk
+ * @since 2025-01-xx
+ */
+public interface StoreManageService {
+
+    /**
+     * 新增店铺入住申请
+     *
+     * @param storeInfoDto 店铺信息DTO
+     * @return StoreInfoVo
+     * @throws Exception 异常
+     */
+    StoreInfoVo applyStore(StoreInfoDto storeInfoDto) throws Exception;
+
+    /**
+     * 保存店铺入住申请草稿
+     *
+     * @param storeInfoDraft 店铺草稿信息
+     * @return 影响行数
+     */
+    int saveStoreDraft(StoreInfoDraft storeInfoDraft);
+
+    /**
+     * 查询店铺入住申请草稿
+     *
+     * @param storeUserId 商户用户ID
+     * @return 店铺草稿信息
+     */
+    StoreInfoDraft getStoreDraft(int storeUserId);
+
+    /**
+     * 获取店铺详细信息
+     *
+     * @param id 店铺ID
+     * @return 店铺详细信息
+     */
+    StoreMainInfoVo getStoreDetail(Integer id);
+
+    /**
+     * 获取店铺今日收益
+     *
+     * @param storeId 店铺ID
+     * @return 今日收益(单位:元,格式化后)
+     */
+    String getTodayIncome(Integer storeId);
+
+    /**
+     * 获取店铺今日订单数
+     *
+     * @param storeId 店铺ID
+     * @return 今日订单数
+     */
+    Integer getTodayOrderCount(Integer storeId);
+}
+

+ 644 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/StoreManageServiceImpl.java

@@ -0,0 +1,644 @@
+package shop.alien.storeplatform.service.impl;
+
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.BeanUtils;
+import org.springframework.data.geo.Point;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
+import shop.alien.entity.store.*;
+import shop.alien.entity.store.dto.StoreInfoDto;
+import shop.alien.entity.store.vo.StoreInfoVo;
+import shop.alien.entity.store.vo.StoreMainInfoVo;
+import shop.alien.entity.store.vo.StoreMenuVo;
+import shop.alien.entity.store.vo.WebSocketVo;
+import shop.alien.mapper.*;
+import shop.alien.storeplatform.feign.AlienStoreFeign;
+import shop.alien.storeplatform.service.StoreManageService;
+import shop.alien.storeplatform.util.GaoDeMapApiUtil;
+import shop.alien.storeplatform.util.NearMeUtil;
+import shop.alien.util.common.constant.OrderStatusEnum;
+import shop.alien.util.map.DistanceUtil;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.text.SimpleDateFormat;
+import java.time.LocalDate;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * web端商户店铺管理服务实现类
+ *
+ * @author ssk
+ * @since 2025-01-xx
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class StoreManageServiceImpl implements StoreManageService {
+
+    private final StoreInfoMapper storeInfoMapper;
+    
+    private final StoreDictionaryMapper storeDictionaryMapper;
+    
+    private final EssentialCityCodeMapper essentialCityCodeMapper;
+    
+    private final StoreUserMapper storeUserMapper;
+    
+    private final StoreImgMapper storeImgMapper;
+    
+    private final TagStoreRelationMapper tagStoreRelationMapper;
+    
+    private final LifeNoticeMapper lifeNoticeMapper;
+    
+    private final StoreInfoDraftMapper storeInfoDraftMapper;
+    
+    private final StoreLabelMapper storeLabelMapper;
+    
+    private final StoreBusinessInfoMapper storeBusinessInfoMapper;
+    
+    private final StoreMenuMapper storeMenuMapper;
+    
+    private final StoreIncomeDetailsRecordMapper storeIncomeDetailsRecordMapper;
+    
+    private final LifeUserOrderMapper lifeUserOrderMapper;
+    
+    private final NearMeUtil nearMeUtil;
+    
+    private final AlienStoreFeign alienStoreFeign;
+
+    private final GaoDeMapApiUtil gaoDeMapApiUtil;
+
+    /**
+     * 默认店铺密码
+     */
+    private static final String DEFAULT_PASSWORD = "123456";
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public StoreInfoVo applyStore(StoreInfoDto storeInfoDto) throws Exception {
+        log.info("StoreManageServiceImpl.applyStore?storeInfoDto={}", storeInfoDto);
+
+        // 1. 获取经营板块信息
+        Integer businessSection = storeInfoDto.getBusinessSection();
+        StoreDictionary businessSectionDict = storeDictionaryMapper.selectOne(
+                new LambdaQueryWrapper<StoreDictionary>()
+                        .eq(StoreDictionary::getDictId, businessSection)
+                        .eq(StoreDictionary::getTypeName, "business_section")
+        );
+        if (businessSectionDict == null) {
+            throw new IllegalArgumentException("经营板块不存在:" + businessSection);
+        }
+
+        // 2. 获取经营种类信息
+        List<String> businessTypes = storeInfoDto.getBusinessTypes();
+        List<String> businessTypeNames = new ArrayList<>();
+        for (String businessType : businessTypes) {
+            StoreDictionary typeDict = storeDictionaryMapper.selectOne(
+                    new LambdaQueryWrapper<StoreDictionary>()
+                            .eq(StoreDictionary::getDictId, businessType)
+                            .eq(StoreDictionary::getParentId, businessSectionDict.getId())
+            );
+            if (typeDict != null) {
+                businessTypeNames.add(typeDict.getDictDetail());
+            }
+        }
+
+        // 3. 构建店铺信息对象
+        StoreInfo storeInfo = new StoreInfo();
+        BeanUtils.copyProperties(storeInfoDto, storeInfo);
+
+        // 4. 设置店铺状态
+        storeInfo.setStoreStatus(storeInfoDto.getStoreStatus());
+
+        // 5. 设置经纬度
+        storeInfo.setStorePosition(
+                storeInfoDto.getStorePositionLongitude() + "," + storeInfoDto.getStorePositionLatitude()
+        );
+
+        // 6. 设置店铺密码
+        if (StringUtils.isNotEmpty(storeInfoDto.getStorePass()) 
+                && !DEFAULT_PASSWORD.equals(storeInfoDto.getStorePass())) {
+            storeInfo.setStorePass(storeInfoDto.getStorePass());
+            storeInfo.setPassType(1); // 已修改密码
+        } else {
+            storeInfo.setStorePass(DEFAULT_PASSWORD);
+            storeInfo.setPassType(0); // 初始密码
+        }
+
+        // 7. 设置经营板块及类型
+        storeInfo.setBusinessSection(businessSection);
+        storeInfo.setBusinessSectionName(businessSectionDict.getDictDetail());
+        storeInfo.setBusinessTypes(String.join(",", businessTypes));
+        storeInfo.setBusinessTypesName(String.join(",", businessTypeNames));
+
+        // 8. 设置审批状态为待审批
+        storeInfo.setStoreApplicationStatus(0);
+
+        // 9. 处理行政区域信息
+        setAdministrativeRegion(storeInfo);
+
+        // 10. 设置默认抽成比例
+        if (storeInfo.getCommissionRate() == null) {
+            storeInfo.setCommissionRate("3");
+        }
+
+        // 11. 插入店铺信息
+        storeInfoMapper.insert(storeInfo);
+
+        // 12. 添加地理位置信息到Redis
+        try {
+            nearMeUtil.addGeolocation(
+                    new Point(
+                            Double.parseDouble(storeInfoDto.getStorePositionLongitude()),
+                            Double.parseDouble(storeInfoDto.getStorePositionLatitude())
+                    ),
+                    storeInfo.getId().toString()
+            );
+        } catch (Exception e) {
+            log.warn("添加地理位置信息失败: {}", e.getMessage());
+        }
+
+        // 13. 更新商户用户绑定关系
+        updateStoreUserBinding(storeInfoDto, storeInfo.getId());
+
+        // 14. 保存店铺图片信息
+        saveStoreImages(storeInfoDto, storeInfo.getId());
+
+        // 15. 初始化店铺标签
+        initializeStoreTags(storeInfo.getId());
+
+        // 16. 发送入住申请通知
+        sendApplicationNotice(storeInfoDto.getUserAccount());
+
+        // 17. 返回结果
+        StoreInfoVo result = new StoreInfoVo();
+        result.setId(storeInfo.getId());
+        return result;
+    }
+
+    /**
+     * 设置行政区域信息
+     */
+    private void setAdministrativeRegion(StoreInfo storeInfo) {
+        // 省
+        if (storeInfo.getAdministrativeRegionProvinceAdcode() != null) {
+            EssentialCityCode province = essentialCityCodeMapper.selectOne(
+                    new LambdaQueryWrapper<EssentialCityCode>()
+                            .eq(EssentialCityCode::getAreaCode, storeInfo.getAdministrativeRegionProvinceAdcode())
+            );
+            if (province != null) {
+                storeInfo.setAdministrativeRegionProvinceName(province.getAreaName());
+            }
+        }
+
+        // 市
+        if (storeInfo.getAdministrativeRegionCityAdcode() != null) {
+            EssentialCityCode city = essentialCityCodeMapper.selectOne(
+                    new LambdaQueryWrapper<EssentialCityCode>()
+                            .eq(EssentialCityCode::getAreaCode, storeInfo.getAdministrativeRegionCityAdcode())
+            );
+            if (city != null) {
+                storeInfo.setAdministrativeRegionCityName(city.getAreaName());
+            }
+        }
+
+        // 区/县
+        if (storeInfo.getAdministrativeRegionDistrictAdcode() != null) {
+            EssentialCityCode district = essentialCityCodeMapper.selectOne(
+                    new LambdaQueryWrapper<EssentialCityCode>()
+                            .eq(EssentialCityCode::getAreaCode, storeInfo.getAdministrativeRegionDistrictAdcode())
+            );
+            if (district != null) {
+                storeInfo.setAdministrativeRegionDistrictName(district.getAreaName());
+            }
+        }
+    }
+
+    /**
+     * 更新商户用户绑定关系
+     */
+    private void updateStoreUserBinding(StoreInfoDto storeInfoDto, Integer storeId) {
+        String userAccount = storeInfoDto.getUserAccount();
+        StoreUser storeUser = storeUserMapper.selectById(userAccount);
+        if (storeUser != null) {
+            storeUser.setStoreId(storeId);
+            
+            // 更新联系人信息
+            if (StringUtils.isNotEmpty(storeInfoDto.getStoreContact())) {
+                storeUser.setName(storeInfoDto.getStoreContact());
+            }
+            
+            // 更新身份证信息
+            if (StringUtils.isNotEmpty(storeInfoDto.getIdCard())) {
+                storeUser.setIdCard(storeInfoDto.getIdCard());
+            }
+            
+            storeUserMapper.updateById(storeUser);
+        }
+    }
+
+    /**
+     * 保存店铺图片信息
+     */
+    private void saveStoreImages(StoreInfoDto storeInfoDto, Integer storeId) {
+        // 1. 保存营业执照图片 (imgType=14)
+        List<String> businessLicenseAddress = storeInfoDto.getBusinessLicenseAddress();
+        if (!CollectionUtils.isEmpty(businessLicenseAddress)) {
+            for (String licenseAddress : businessLicenseAddress) {
+                StoreImg storeImg = new StoreImg();
+                storeImg.setStoreId(storeId);
+                storeImg.setImgType(14);
+                storeImg.setImgSort(0);
+                storeImg.setImgDescription("营业执照");
+                storeImg.setImgUrl(licenseAddress);
+                storeImgMapper.insert(storeImg);
+            }
+        }
+
+        // 2. 保存合同图片 (imgType=15)
+        List<String> contractImageList = storeInfoDto.getContractImageList();
+        if (!CollectionUtils.isEmpty(contractImageList)) {
+            // 先删除已存在的合同图片
+            storeImgMapper.delete(
+                    new LambdaQueryWrapper<StoreImg>()
+                            .eq(StoreImg::getStoreId, storeId)
+                            .eq(StoreImg::getImgType, 15)
+            );
+            
+            // 插入新的合同图片
+            for (String contractImage : contractImageList) {
+                StoreImg storeImg = new StoreImg();
+                storeImg.setStoreId(storeId);
+                storeImg.setImgType(15);
+                storeImg.setImgSort(0);
+                storeImg.setImgDescription("合同图片");
+                storeImg.setImgUrl(contractImage);
+                storeImgMapper.insert(storeImg);
+            }
+        }
+
+        // 3. 保存经营许可证图片 (imgType=25)
+        if (StringUtils.isNotEmpty(storeInfoDto.getFoodLicenceUrl())) {
+            StoreImg storeImg = new StoreImg();
+            storeImg.setStoreId(storeId);
+            storeImg.setImgType(25);
+            storeImg.setImgSort(0);
+            storeImg.setImgDescription("经营许可证审核通过图片");
+            storeImg.setImgUrl(storeInfoDto.getFoodLicenceUrl());
+            storeImgMapper.insert(storeImg);
+        }
+    }
+
+    /**
+     * 初始化店铺标签
+     */
+    private void initializeStoreTags(Integer storeId) {
+        // 检查是否已存在标签
+        LambdaQueryWrapper<TagStoreRelation> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(TagStoreRelation::getStoreId, storeId)
+               .eq(TagStoreRelation::getDeleteFlag, 0);
+        int tagCount = tagStoreRelationMapper.selectCount(wrapper);
+        
+        // 如果不存在,创建初始标签
+        if (tagCount == 0) {
+            TagStoreRelation tagStoreRelation = new TagStoreRelation();
+            tagStoreRelation.setStoreId(storeId);
+            tagStoreRelation.setDeleteFlag(0);
+            tagStoreRelationMapper.insert(tagStoreRelation);
+        }
+    }
+
+    /**
+     * 发送入住申请通知
+     */
+    private void sendApplicationNotice(String userAccount) {
+        try {
+            StoreUser storeUser = storeUserMapper.selectById(userAccount);
+            if (storeUser == null || StringUtils.isEmpty(storeUser.getPhone())) {
+                log.warn("未找到商户用户或手机号为空,无法发送通知");
+                return;
+            }
+
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+            String currentDate = sdf.format(new Date());
+            String message = "您在" + currentDate + "提交的入驻店铺申请,平台已受理,1-3个工作日将审核结果发送至应用内的消息-通知中,请注意查收。";
+            
+            JSONObject jsonObject = new JSONObject();
+            jsonObject.put("message", message);
+            
+            String receiverId = "store_" + storeUser.getPhone();
+            
+            // 1. 保存通知消息到数据库
+            LifeNotice lifeNotice = new LifeNotice();
+            lifeNotice.setReceiverId(receiverId);
+            lifeNotice.setContext(jsonObject.toJSONString());
+            lifeNotice.setTitle("入住店铺申请");
+            lifeNotice.setSenderId("system");
+            lifeNotice.setIsRead(0);
+            lifeNotice.setNoticeType(1);
+            lifeNoticeMapper.insert(lifeNotice);
+            
+            // 2. 通过 WebSocket 实时推送消息
+            WebSocketVo websocketVo = new WebSocketVo();
+            websocketVo.setSenderId("system");
+            websocketVo.setReceiverId(receiverId);
+            websocketVo.setCategory("notice");
+            websocketVo.setNoticeType("1");
+            websocketVo.setIsRead(0);
+            websocketVo.setText(JSONObject.toJSONString(lifeNotice));
+            
+            try {
+                alienStoreFeign.sendMsgToClientByPhoneId(receiverId, JSONObject.toJSONString(websocketVo));
+                log.info("WebSocket消息推送成功: receiverId={}", receiverId);
+            } catch (Exception e) {
+                log.error("WebSocket消息推送失败: {}", e.getMessage(), e);
+            }
+        } catch (Exception e) {
+            log.error("发送入住申请通知失败: {}", e.getMessage(), e);
+        }
+    }
+
+    @Override
+    public int saveStoreDraft(StoreInfoDraft storeInfoDraft) {
+        log.info("StoreManageServiceImpl.saveStoreDraft - 开始保存店铺草稿");
+        
+        // 处理行政区域省信息
+        if (storeInfoDraft.getAdministrativeRegionProvinceAdcode() != null) {
+            EssentialCityCode provinceCode = essentialCityCodeMapper.selectOne(
+                    new LambdaQueryWrapper<EssentialCityCode>()
+                            .eq(EssentialCityCode::getAreaCode, storeInfoDraft.getAdministrativeRegionProvinceAdcode())
+            );
+            if (provinceCode != null) {
+                storeInfoDraft.setAdministrativeRegionProvinceName(provinceCode.getAreaName());
+            }
+        }
+        
+        // 处理行政区域市信息
+        if (storeInfoDraft.getAdministrativeRegionCityAdcode() != null) {
+            EssentialCityCode cityCode = essentialCityCodeMapper.selectOne(
+                    new LambdaQueryWrapper<EssentialCityCode>()
+                            .eq(EssentialCityCode::getAreaCode, storeInfoDraft.getAdministrativeRegionCityAdcode())
+            );
+            if (cityCode != null) {
+                storeInfoDraft.setAdministrativeRegionCityName(cityCode.getAreaName());
+            }
+        }
+        
+        // 处理行政区域区信息
+        if (storeInfoDraft.getAdministrativeRegionDistrictAdcode() != null) {
+            EssentialCityCode districtCode = essentialCityCodeMapper.selectOne(
+                    new LambdaQueryWrapper<EssentialCityCode>()
+                            .eq(EssentialCityCode::getAreaCode, storeInfoDraft.getAdministrativeRegionDistrictAdcode())
+            );
+            if (districtCode != null) {
+                storeInfoDraft.setAdministrativeRegionDistrictName(districtCode.getAreaName());
+            }
+        }
+        
+        // 处理空集合字段,设置为 null
+        if (StringUtils.isNotEmpty(storeInfoDraft.getBusinessLicenseUrl()) 
+                && storeInfoDraft.getBusinessLicenseUrl().isEmpty()) {
+            storeInfoDraft.setBusinessLicenseUrl(null);
+        }
+        
+        if (StringUtils.isNotEmpty(storeInfoDraft.getContractUrl()) 
+                && storeInfoDraft.getContractUrl().isEmpty()) {
+            storeInfoDraft.setContractUrl(null);
+        }
+        
+        if (StringUtils.isNotEmpty(storeInfoDraft.getFoodLicenceUrl()) 
+                && storeInfoDraft.getFoodLicenceUrl().isEmpty()) {
+            storeInfoDraft.setFoodLicenceUrl(null);
+        }
+        
+        int result = storeInfoDraftMapper.insert(storeInfoDraft);
+        log.info("StoreManageServiceImpl.saveStoreDraft - 草稿保存完成,影响行数: {}", result);
+        return result;
+    }
+
+    @Override
+    public StoreInfoDraft getStoreDraft(int storeUserId) {
+        log.info("StoreManageServiceImpl.getStoreDraft - 查询商户用户草稿: storeUserId={}", storeUserId);
+        
+        LambdaQueryWrapper<StoreInfoDraft> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(StoreInfoDraft::getStoreUserId, storeUserId);
+        wrapper.orderByDesc(StoreInfoDraft::getCreatedTime);
+        
+        List<StoreInfoDraft> draftList = storeInfoDraftMapper.selectList(wrapper);
+        
+        if (CollectionUtils.isEmpty(draftList)) {
+            log.info("StoreManageServiceImpl.getStoreDraft - 未找到草稿记录");
+            return null;
+        }
+        
+        StoreInfoDraft result = draftList.get(0);
+        log.info("StoreManageServiceImpl.getStoreDraft - 查询到草稿记录: id={}", result.getId());
+        return result;
+    }
+
+    @Override
+    public StoreMainInfoVo getStoreDetail(Integer id) {
+        log.info("StoreManageServiceImpl.getStoreDetail - 查询店铺详细信息: id={}", id);
+        
+        // 1. 检查店铺是否存在
+        StoreInfo storeInfo = storeInfoMapper.selectById(id);
+        if (storeInfo == null) {
+            log.warn("StoreManageServiceImpl.getStoreDetail - 店铺不存在: id={}", id);
+            return null;
+        }
+        
+        // 2. 获取店铺基本信息
+        StoreMainInfoVo storeMainInfoVo = storeInfoMapper.getStoreInfo(id);
+        
+        // 3. 判断门店是否到期
+        if (ObjectUtils.isNotEmpty(storeMainInfoVo.getExpirationTime())) {
+            if (new Date().after(storeMainInfoVo.getExpirationTime())) {
+                storeMainInfoVo.setExpirationFlag(0); // 已到期
+            } else {
+                storeMainInfoVo.setExpirationFlag(1); // 未到期
+            }
+        } else {
+            storeMainInfoVo.setExpirationFlag(1); // 无到期时间,默认未到期
+        }
+        
+        // 4. 设置门店地址
+        storeMainInfoVo.setStoreAddress(storeInfo.getStoreAddress());
+        
+        // 5. 查询入口图(imgType=1)
+        List<StoreImg> inletsUrlList = storeImgMapper.selectList(
+                new LambdaQueryWrapper<StoreImg>()
+                        .eq(StoreImg::getImgType, 1)
+                        .eq(StoreImg::getStoreId, id)
+        );
+        List<String> inletsUrls = inletsUrlList.stream()
+                .sorted(Comparator.comparing(StoreImg::getImgSort))
+                .map(StoreImg::getImgUrl)
+                .collect(Collectors.toList());
+        storeMainInfoVo.setInletsUrl(inletsUrls);
+        
+        // 6. 查询相册(imgType=2)
+        List<StoreImg> albumUrlList = storeImgMapper.selectList(
+                new LambdaQueryWrapper<StoreImg>()
+                        .eq(StoreImg::getImgType, 2)
+                        .eq(StoreImg::getStoreId, id)
+        );
+        List<String> albumUrls = albumUrlList.stream()
+                .sorted(Comparator.comparing(StoreImg::getImgSort))
+                .map(StoreImg::getImgUrl)
+                .collect(Collectors.toList());
+        storeMainInfoVo.setAlbumUrl(albumUrls);
+        
+        // 7. 查询推荐菜(isRecommend=1)
+        List<StoreMenuVo> recommendList = storeMenuMapper.getStoreMenuList(id, 1);
+        List<StoreMenuVo> sortedRecommendList = recommendList.stream()
+                .sorted(Comparator.comparing(StoreMenuVo::getImgSort))
+                .collect(Collectors.toList());
+        storeMainInfoVo.setRecommendUrl(sortedRecommendList);
+        
+        // 8. 查询菜单(isRecommend=0)
+        List<StoreMenuVo> menuList = storeMenuMapper.getStoreMenuList(id, 0);
+        List<StoreMenuVo> sortedMenuList = menuList.stream()
+                .sorted(Comparator.comparing(StoreMenuVo::getImgSort))
+                .collect(Collectors.toList());
+        storeMainInfoVo.setMenuUrl(sortedMenuList);
+        
+        // 9. 查询门店标签
+        StoreLabel storeLabel = storeLabelMapper.selectOne(
+                new LambdaQueryWrapper<StoreLabel>()
+                        .eq(StoreLabel::getStoreId, id)
+        );
+        storeMainInfoVo.setStoreLabel(storeLabel);
+        
+        // 10. 查询营业时间
+        List<StoreBusinessInfo> businessInfoList = storeBusinessInfoMapper.selectList(
+                new LambdaQueryWrapper<StoreBusinessInfo>()
+                        .eq(StoreBusinessInfo::getStoreId, id)
+        );
+        storeMainInfoVo.setStoreBusinessInfo(businessInfoList);
+        
+        // 11. 查询门店头像(imgType=10)
+        StoreImg headImg = storeImgMapper.selectOne(
+                new LambdaQueryWrapper<StoreImg>()
+                        .eq(StoreImg::getImgType, 10)
+                        .eq(StoreImg::getStoreId, id)
+        );
+        storeMainInfoVo.setHeadImgUrl(headImg != null ? headImg.getImgUrl() : "");
+        
+        // 12. 查询商户用户注销状态
+        List<StoreUser> storeUsers = storeUserMapper.selectList(
+                new LambdaQueryWrapper<StoreUser>()
+                        .eq(StoreUser::getStoreId, storeInfo.getId())
+        );
+        for (StoreUser storeUser : storeUsers) {
+            storeMainInfoVo.setLogoutFlagUser(storeUser.getLogoutFlag());
+        }
+        
+        // 13. 计算店铺到最近地铁站的距离
+        try {
+            String[] position = storeMainInfoVo.getStorePosition().split(",");
+            if (position.length == 2) {
+                String longitude = position[0];
+                String latitude = position[1];
+                
+                JSONObject nearbySubway = gaoDeMapApiUtil.getNearbySubway(longitude, latitude);
+                
+                // 地铁站名称
+                String subwayName = nearbySubway.getString("name");
+                storeMainInfoVo.setSubwayName(subwayName);
+                
+                // 地铁站经纬度
+                String location = nearbySubway.getString("location");
+                if (StringUtils.isNotEmpty(location)) {
+                    String[] subwayPosition = location.split(",");
+                    if (subwayPosition.length == 2) {
+                        double subwayLng = Double.parseDouble(subwayPosition[0]);
+                        double subwayLat = Double.parseDouble(subwayPosition[1]);
+                        double storeLng = Double.parseDouble(longitude);
+                        double storeLat = Double.parseDouble(latitude);
+                        
+                        // 计算距离(米)
+                        double distance = DistanceUtil.haversineCalculateDistance(
+                                subwayLng, subwayLat, storeLng, storeLat);
+                        storeMainInfoVo.setDistance2(distance);
+                    } else {
+                        storeMainInfoVo.setDistance2(0);
+                    }
+                } else {
+                    storeMainInfoVo.setDistance2(0);
+                }
+            }
+        } catch (Exception e) {
+            log.error("StoreManageServiceImpl.getStoreDetail - 计算地铁距离失败: {}", e.getMessage(), e);
+            storeMainInfoVo.setSubwayName("");
+            storeMainInfoVo.setDistance2(0);
+        }
+        
+        log.info("StoreManageServiceImpl.getStoreDetail - 查询完成: id={}", id);
+        return storeMainInfoVo;
+    }
+
+    @Override
+    public String getTodayIncome(Integer storeId) {
+        log.info("StoreManageServiceImpl.getTodayIncome - 查询店铺今日收益: storeId={}", storeId);
+        
+        // 1. 构建查询条件
+        LambdaQueryWrapper<StoreIncomeDetailsRecord> wrapper = new LambdaQueryWrapper<>();
+        LocalDate today = LocalDate.now();
+        
+        // 2. 查询今日的收入记录
+        wrapper.eq(StoreIncomeDetailsRecord::getStoreId, storeId)
+               .ge(StoreIncomeDetailsRecord::getCreatedTime, today + " 00:00:00")
+               .le(StoreIncomeDetailsRecord::getCreatedTime, today + " 23:59:59");
+        
+        List<StoreIncomeDetailsRecord> incomeList = storeIncomeDetailsRecordMapper.selectList(wrapper);
+        
+        // 3. 计算总收益(单位:分)
+        int totalIncome = 0;
+        if (incomeList != null && !incomeList.isEmpty()) {
+            totalIncome = incomeList.stream()
+                    .mapToInt(StoreIncomeDetailsRecord::getMoney)
+                    .sum();
+        }
+        
+        // 4. 转换为元,保留两位小数
+        String incomeStr = new BigDecimal(totalIncome)
+                .divide(new BigDecimal(100), 2, RoundingMode.DOWN)
+                .toString();
+        
+        log.info("StoreManageServiceImpl.getTodayIncome - 查询完成: storeId={}, 今日收益={}元", 
+                storeId, incomeStr);
+        return incomeStr;
+    }
+
+    @Override
+    public Integer getTodayOrderCount(Integer storeId) {
+        log.info("StoreManageServiceImpl.getTodayOrderCount - 查询店铺今日订单数: storeId={}", storeId);
+        
+        // 1. 构建查询条件
+        LambdaQueryWrapper<LifeUserOrder> wrapper = new LambdaQueryWrapper<>();
+        LocalDate today = LocalDate.now();
+        
+        // 2. 查询今日的订单,排除待支付、已取消、已过期状态
+        wrapper.eq(LifeUserOrder::getStoreId, storeId)
+               .notIn(LifeUserOrder::getStatus, 
+                      OrderStatusEnum.CANCEL.getStatus(), 
+                      OrderStatusEnum.WAIT_PAY.getStatus(), 
+                      OrderStatusEnum.EXPIRE.getStatus())
+               .between(LifeUserOrder::getBuyTime, today + " 00:00:00", today + " 23:59:59");
+        
+        // 3. 统计订单数量
+        Integer count = lifeUserOrderMapper.selectCount(wrapper);
+        
+        log.info("StoreManageServiceImpl.getTodayOrderCount - 查询完成: storeId={}, 今日订单数={}", 
+                storeId, count);
+        return count;
+    }
+}
+

+ 64 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/util/NearMeUtil.java

@@ -0,0 +1,64 @@
+package shop.alien.storeplatform.util;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.geo.Point;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.stereotype.Component;
+
+/**
+ * 附近商家地理位置工具类
+ *
+ * @author ssk
+ * @version 1.0.0
+ * @since 2025-01-xx
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class NearMeUtil {
+
+    private final StringRedisTemplate stringRedisTemplate;
+
+    /**
+     * Redis GEO 存储key - 店铺ID维度
+     */
+    private static final String GEO_STORE_PRIMARY = "geo:store:primary";
+
+    /**
+     * 添加地理位置信息
+     *
+     * @param point   经纬度坐标
+     * @param content 店铺ID
+     * @return 添加成功的数量
+     */
+    public Long addGeolocation(Point point, String content) {
+        try {
+            if (point != null && content != null) {
+                return stringRedisTemplate.opsForGeo().add(GEO_STORE_PRIMARY, point, content);
+            }
+            return 0L;
+        } catch (Exception e) {
+            log.error("NearMeUtil.addGeolocation ERROR: point={}, content={}, error={}", 
+                    point, content, e.getMessage());
+            return 0L;
+        }
+    }
+
+    /**
+     * 删除地理位置信息
+     *
+     * @param content 店铺ID
+     * @return 删除成功的数量
+     */
+    public Long removeGeolocation(String content) {
+        try {
+            return stringRedisTemplate.opsForGeo().remove(GEO_STORE_PRIMARY, content);
+        } catch (Exception e) {
+            log.error("NearMeUtil.removeGeolocation ERROR: content={}, error={}", 
+                    content, e.getMessage());
+            return 0L;
+        }
+    }
+}
+