package com.doumee.service.business.impl; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.doumee.config.jwt.JwtTokenUtil; import com.doumee.core.constants.Constants; import com.doumee.core.constants.ResponseStatus; import com.doumee.core.exception.BusinessException; import com.doumee.core.model.PageData; import com.doumee.core.model.PageWrap; import com.doumee.core.utils.Utils; import com.doumee.biz.system.AreasBiz; import com.doumee.biz.system.SystemDictDataBiz; import com.doumee.dao.business.MemberMapper; import com.doumee.dao.business.MultifileMapper; import com.doumee.dao.business.ShopInfoMapper; import com.doumee.dao.business.model.Areas; import com.doumee.dao.business.model.Member; import com.doumee.dao.business.model.Multifile; import com.doumee.dao.business.model.ShopInfo; import com.doumee.dao.dto.*; import com.doumee.dao.system.SystemUserMapper; import com.doumee.dao.system.model.SystemUser; import com.doumee.dao.vo.ShopDetailVO; import com.doumee.dao.vo.ShopLoginVO; import com.doumee.dao.vo.ShopNearbyVO; import com.doumee.dao.vo.ShopWebDetailVO; import com.doumee.service.business.ShopInfoService; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.RandomStringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.*; import java.util.stream.Collectors; /** * 门店信息Service实现 * @author rk * @date 2026/04/08 */ @Service public class ShopInfoServiceImpl implements ShopInfoService { @Autowired private ShopInfoMapper shopInfoMapper; @Autowired private MemberMapper memberMapper; @Autowired private MultifileMapper multifileMapper; @Autowired private SystemDictDataBiz systemDictDataBiz; @Autowired private SystemUserMapper systemUserMapper; @Autowired private AreasBiz areasBiz; @Autowired private RedisTemplate redisTemplate; @Override public Integer create(ShopInfo shopInfo) { shopInfoMapper.insert(shopInfo); return shopInfo.getId(); } @Override public void deleteById(Integer id) { shopInfoMapper.deleteById(id); } @Override public void delete(ShopInfo shopInfo) { UpdateWrapper deleteWrapper = new UpdateWrapper<>(shopInfo); shopInfoMapper.delete(deleteWrapper); } @Override public void deleteByIdInBatch(List ids) { if (ids == null || ids.isEmpty()) { return; } shopInfoMapper.deleteBatchIds(ids); } @Override public void updateById(ShopInfo shopInfo) { shopInfoMapper.updateById(shopInfo); } @Override public void updateByIdInBatch(List shopInfos) { if (shopInfos == null || shopInfos.isEmpty()) { return; } for (ShopInfo shopInfo : shopInfos) { this.updateById(shopInfo); } } @Override public ShopInfo findById(Integer id) { ShopInfo shopInfo = shopInfoMapper.selectById(id); if (Objects.isNull(shopInfo)) { throw new BusinessException(ResponseStatus.DATA_EMPTY); } return shopInfo; } @Override public ShopInfo findOne(ShopInfo shopInfo) { QueryWrapper wrapper = new QueryWrapper<>(shopInfo); return shopInfoMapper.selectOne(wrapper); } @Override public List findList(ShopInfo shopInfo) { QueryWrapper wrapper = new QueryWrapper<>(shopInfo); return shopInfoMapper.selectList(wrapper); } @Override public PageData findPage(PageWrap pageWrap) { IPage page = new Page<>(pageWrap.getPage(), pageWrap.getCapacity()); QueryWrapper queryWrapper = new QueryWrapper<>(); Utils.MP.blankToNull(pageWrap.getModel()); pageWrap.getModel().setDeleted(Constants.ZERO); if (pageWrap.getModel().getId() != null) { queryWrapper.lambda().eq(ShopInfo::getId, pageWrap.getModel().getId()); } if (pageWrap.getModel().getDeleted() != null) { queryWrapper.lambda().eq(ShopInfo::getDeleted, pageWrap.getModel().getDeleted()); } if (pageWrap.getModel().getCreateUser() != null) { queryWrapper.lambda().eq(ShopInfo::getCreateUser, pageWrap.getModel().getCreateUser()); } if (pageWrap.getModel().getCreateTime() != null) { queryWrapper.lambda().ge(ShopInfo::getCreateTime, Utils.Date.getStart(pageWrap.getModel().getCreateTime())); queryWrapper.lambda().le(ShopInfo::getCreateTime, Utils.Date.getEnd(pageWrap.getModel().getCreateTime())); } if (pageWrap.getModel().getCreateStartTime() != null) { queryWrapper.lambda().ge(ShopInfo::getCreateTime, Utils.Date.getStart(pageWrap.getModel().getCreateStartTime())); } if (pageWrap.getModel().getCreateEndTime() != null) { queryWrapper.lambda().le(ShopInfo::getCreateTime, Utils.Date.getEnd(pageWrap.getModel().getCreateEndTime())); } if (pageWrap.getModel().getUpdateUser() != null) { queryWrapper.lambda().eq(ShopInfo::getUpdateUser, pageWrap.getModel().getUpdateUser()); } if (pageWrap.getModel().getUpdateTime() != null) { queryWrapper.lambda().ge(ShopInfo::getUpdateTime, Utils.Date.getStart(pageWrap.getModel().getUpdateTime())); queryWrapper.lambda().le(ShopInfo::getUpdateTime, Utils.Date.getEnd(pageWrap.getModel().getUpdateTime())); } if (pageWrap.getModel().getRemark() != null) { queryWrapper.lambda().eq(ShopInfo::getRemark, pageWrap.getModel().getRemark()); } if (pageWrap.getModel().getCompanyType() != null) { queryWrapper.lambda().eq(ShopInfo::getCompanyType, pageWrap.getModel().getCompanyType()); } if (pageWrap.getModel().getName() != null) { queryWrapper.lambda().like(ShopInfo::getName, pageWrap.getModel().getName()); } if (pageWrap.getModel().getTelephone() != null) { queryWrapper.lambda().like(ShopInfo::getTelephone, pageWrap.getModel().getTelephone()); } if (pageWrap.getModel().getLinkName() != null) { queryWrapper.lambda().like(ShopInfo::getLinkName, pageWrap.getModel().getLinkName()); } if (pageWrap.getModel().getLinkPhone() != null) { queryWrapper.lambda().like(ShopInfo::getLinkPhone, pageWrap.getModel().getLinkPhone()); } if (pageWrap.getModel().getAreaId() != null) { queryWrapper.lambda().eq(ShopInfo::getAreaId, pageWrap.getModel().getAreaId()); } if (pageWrap.getModel().getAuditStatus() != null) { queryWrapper.lambda().eq(ShopInfo::getAuditStatus, pageWrap.getModel().getAuditStatus()); } if (pageWrap.getModel().getAuditStatusList() != null && !pageWrap.getModel().getAuditStatusList().isEmpty()) { queryWrapper.lambda().in(ShopInfo::getAuditStatus, pageWrap.getModel().getAuditStatusList()); } if (pageWrap.getModel().getStatus() != null) { queryWrapper.lambda().eq(ShopInfo::getStatus, pageWrap.getModel().getStatus()); } if (pageWrap.getModel().getAuditTime() != null) { queryWrapper.lambda().ge(ShopInfo::getAuditTime, Utils.Date.getStart(pageWrap.getModel().getAuditTime())); queryWrapper.lambda().le(ShopInfo::getAuditTime, Utils.Date.getEnd(pageWrap.getModel().getAuditTime())); } if (pageWrap.getModel().getAuditUserId() != null) { queryWrapper.lambda().eq(ShopInfo::getAuditUserId, pageWrap.getModel().getAuditUserId()); } if (pageWrap.getModel().getOpenid() != null) { queryWrapper.lambda().like(ShopInfo::getOpenid, pageWrap.getModel().getOpenid()); } for (PageWrap.SortData sortData : pageWrap.getSorts()) { if (sortData.getDirection().equalsIgnoreCase(PageWrap.DESC)) { queryWrapper.orderByDesc(sortData.getProperty()); } else { queryWrapper.orderByAsc(sortData.getProperty()); } } return PageData.from(shopInfoMapper.selectPage(page, queryWrapper)); } @Override public long count(ShopInfo shopInfo) { QueryWrapper wrapper = new QueryWrapper<>(shopInfo); return shopInfoMapper.selectCount(wrapper); } // ========== 门店入驻业务 ========== @Override @Transactional(rollbackFor = Exception.class) public void applyShop(ShopApplyDTO request, Member member) { Integer memberId = member.getId(); // 1. 校验门店手机号唯一性(shop_info.telephone) checkTelephoneUnique(request.getTelephone(), null); // 2. 根据类型校验附件 if (Constants.equalsInteger(request.getCompanyType(), Constants.ZERO)) { // 个人类型:必须上传劳动合同和社保证明 if (CollectionUtils.isEmpty(request.getLaborContractImgs())) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "个人类型必须上传有效劳动合同"); } if (CollectionUtils.isEmpty(request.getSocialSecurityImgs())) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "个人类型必须上传社保缴纳证明"); } } else if (Constants.equalsInteger(request.getCompanyType(), Constants.ONE)) { // 企业类型:必须填写法人信息和营业执照 if (StringUtils.isBlank(request.getBusinessImg())) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "企业类型必须上传营业执照"); } if (StringUtils.isBlank(request.getLegalPersonName())) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "企业类型必须填写法人姓名"); } if (StringUtils.isBlank(request.getLegalPersonPhone())) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "企业类型必须填写法人电话"); } if (StringUtils.isBlank(request.getLegalPersonCard())) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "企业类型必须填写法人身份证号码"); } } Date now = new Date(); String rawPassword = generateDefaultPassword(request.getTelephone()); String salt = RandomStringUtils.randomAlphabetic(6); String encryptedPassword = Utils.Secure.encryptPassword(rawPassword, salt); // 3. 查询该会员是否已有门店记录 QueryWrapper existQw = new QueryWrapper<>(); existQw.lambda() .eq(ShopInfo::getRegionMemberId, memberId) .eq(ShopInfo::getDeleted, Constants.ZERO) .last("limit 1"); ShopInfo existing = shopInfoMapper.selectOne(existQw); Integer shopId; if (existing != null) { // 校验状态:只有待审批(0)和被驳回(2)可修改 if (!Constants.equalsInteger(existing.getAuditStatus(), Constants.ZERO) && !Constants.equalsInteger(existing.getAuditStatus(), Constants.TWO)) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "当前门店状态不允许修改"); } // 校验openid匹配:当前登录会员的openid必须与门店的openid一致 if (existing.getOpenid() != null && !existing.getOpenid().equals(member.getOpenid())) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "无权限操作当前门店信息"); } // 校验手机号唯一性(排除自身) if (!request.getTelephone().equals(existing.getTelephone())) { checkTelephoneUnique(request.getTelephone(), existing.getId()); } // 更新 existing.setCompanyType(request.getCompanyType()); existing.setName(request.getName()); existing.setTelephone(request.getTelephone()); existing.setLinkName(request.getLinkName()); existing.setLinkPhone(request.getLinkPhone()); existing.setIdcard(request.getIdcard()); existing.setAreaId(request.getAreaId()); existing.setLongitude(request.getLongitude()); existing.setLatitude(request.getLatitude()); existing.setAddress(request.getAddress()); existing.setIdcardImg(request.getIdcardImg()); existing.setIdcardImgBack(request.getIdcardImgBack()); existing.setBusinessImg(request.getBusinessImg()); existing.setLegalPersonName(request.getLegalPersonName()); existing.setLegalPersonPhone(request.getLegalPersonPhone()); existing.setLegalPersonCard(request.getLegalPersonCard()); existing.setPassword(encryptedPassword); existing.setSalt(salt); existing.setAuditStatus(Constants.ZERO); existing.setUpdateTime(now); existing.setUpdateUser(memberId); existing.setAuditRemark(null); existing.setAuditTime(null); existing.setAuditUserId(null); shopInfoMapper.updateById(existing); shopId = existing.getId(); } else { // 新建 ShopInfo shopInfo = new ShopInfo(); shopInfo.setCompanyType(request.getCompanyType()); shopInfo.setName(request.getName()); shopInfo.setTelephone(request.getTelephone()); shopInfo.setLinkName(request.getLinkName()); shopInfo.setLinkPhone(request.getLinkPhone()); shopInfo.setIdcard(request.getIdcard()); shopInfo.setAreaId(request.getAreaId()); shopInfo.setLongitude(request.getLongitude()); shopInfo.setLatitude(request.getLatitude()); shopInfo.setAddress(request.getAddress()); shopInfo.setIdcardImg(request.getIdcardImg()); shopInfo.setIdcardImgBack(request.getIdcardImgBack()); shopInfo.setBusinessImg(request.getBusinessImg()); shopInfo.setLegalPersonName(request.getLegalPersonName()); shopInfo.setLegalPersonPhone(request.getLegalPersonPhone()); shopInfo.setLegalPersonCard(request.getLegalPersonCard()); shopInfo.setPassword(encryptedPassword); shopInfo.setSalt(salt); shopInfo.setOpenid(member.getOpenid()); shopInfo.setAuditStatus(Constants.ZERO); shopInfo.setStatus(Constants.ZERO); shopInfo.setDeleted(Constants.ZERO); shopInfo.setCreateTime(now); shopInfo.setUpdateTime(now); shopInfo.setCreateUser(memberId); shopInfo.setRegionMemberId(memberId); shopInfoMapper.insert(shopInfo); shopId = shopInfo.getId(); } // 4. 删除旧附件记录 multifileMapper.delete(new QueryWrapper().lambda() .eq(Multifile::getObjId, shopId) .in(Multifile::getObjType, Constants.FileType.STORE_FRONT.getKey(), Constants.FileType.STORE_INTERIOR.getKey(), Constants.FileType.OTHER_MATERIAL.getKey(), Constants.FileType.LABOR_CONTRACT.getKey(), Constants.FileType.SOCIAL_SECURITY.getKey())); // 5. 保存新附件记录 saveMultifileList(shopId, Constants.FileType.STORE_FRONT.getKey(), request.getStoreFrontImgs(), now); saveMultifileList(shopId, Constants.FileType.STORE_INTERIOR.getKey(), request.getStoreInteriorImgs(), now); saveMultifileList(shopId, Constants.FileType.OTHER_MATERIAL.getKey(), request.getOtherMaterialImgs(), now); saveMultifileList(shopId, Constants.FileType.LABOR_CONTRACT.getKey(), request.getLaborContractImgs(), now); saveMultifileList(shopId, Constants.FileType.SOCIAL_SECURITY.getKey(), request.getSocialSecurityImgs(), now); } @Override public ShopDetailVO getShopDetail(Integer shopId) { ShopInfo shopInfo = shopInfoMapper.selectById(shopId); if (shopInfo == null || Constants.equalsInteger(shopInfo.getDeleted(), Constants.ONE)) { throw new BusinessException(ResponseStatus.DATA_EMPTY); } return buildShopDetailVO(shopInfo); } @Override public ShopDetailVO getMyShop(Integer memberId) { QueryWrapper qw = new QueryWrapper<>(); qw.lambda() .eq(ShopInfo::getRegionMemberId, memberId) .eq(ShopInfo::getDeleted, Constants.ZERO) .last("limit 1"); ShopInfo shopInfo = shopInfoMapper.selectOne(qw); if (shopInfo == null) { return null; } return buildShopDetailVO(shopInfo); } @Override @Transactional(rollbackFor = Exception.class) public void auditShop(AuditDTO auditDTO) { if (auditDTO.getId() == null) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "主键不能为空"); } if (auditDTO.getAuditStatus() == null || (auditDTO.getAuditStatus() != 0 && auditDTO.getAuditStatus() != 1)) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "审批状态参数错误"); } ShopInfo shopInfo = shopInfoMapper.selectById(auditDTO.getId()); if (shopInfo == null || Constants.equalsInteger(shopInfo.getDeleted(), Constants.ONE)) { throw new BusinessException(ResponseStatus.DATA_EMPTY); } if (!Constants.equalsInteger(shopInfo.getAuditStatus(), Constants.ZERO)) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "当前状态不允许审批"); } Date now = new Date(); // auditDTO.auditStatus: 0=通过 → auditStatus=1, 1=驳回 → auditStatus=2 Integer newAuditStatus = Constants.equalsInteger(auditDTO.getAuditStatus(), Constants.ZERO) ? Constants.ONE : Constants.TWO; shopInfo.setAuditStatus(newAuditStatus); shopInfo.setAuditTime(now); shopInfo.setAuditRemark(auditDTO.getAuditRemark()); shopInfo.setAuditUserId(auditDTO.getAuditUser()); shopInfo.setUpdateTime(now); shopInfoMapper.updateById(shopInfo); } @Override public void changeStatus(ChangeStatusDTO dto) { if (dto.getId() == null) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "主键不能为空"); } if (dto.getStatus() == null || (dto.getStatus() != 0 && dto.getStatus() != 1)) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "状态参数错误,0=启用;1=禁用"); } ShopInfo shopInfo = shopInfoMapper.selectById(dto.getId()); if (shopInfo == null || Constants.equalsInteger(shopInfo.getDeleted(), Constants.ONE)) { throw new BusinessException(ResponseStatus.DATA_EMPTY); } shopInfo.setStatus(dto.getStatus()); shopInfo.setUpdateTime(new Date()); shopInfoMapper.updateById(shopInfo); } @Override @Transactional(rollbackFor = Exception.class) public void resetPassword(ResetPasswordDTO dto) { if (dto.getId() == null) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "门店主键不能为空"); } ShopInfo shopInfo = shopInfoMapper.selectById(dto.getId()); if (shopInfo == null || Constants.equalsInteger(shopInfo.getDeleted(), Constants.ONE)) { throw new BusinessException(ResponseStatus.DATA_EMPTY); } String rawPassword = generateDefaultPassword(shopInfo.getTelephone()); String salt = RandomStringUtils.randomAlphabetic(6); shopInfo.setPassword(Utils.Secure.encryptPassword(rawPassword, salt)); shopInfo.setSalt(salt); shopInfo.setUpdateTime(new Date()); shopInfoMapper.updateById(shopInfo); } @Override @Transactional(rollbackFor = Exception.class) public void updateShop(ShopUpdateDTO request) { if (request.getId() == null) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "门店主键不能为空"); } ShopInfo shopInfo = shopInfoMapper.selectById(request.getId()); if (shopInfo == null || Constants.equalsInteger(shopInfo.getDeleted(), Constants.ONE)) { throw new BusinessException(ResponseStatus.DATA_EMPTY); } // 校验手机号唯一性(排除自身) if (!request.getTelephone().equals(shopInfo.getTelephone())) { checkTelephoneUnique(request.getTelephone(), shopInfo.getId()); } // 根据类型校验必填 if (Constants.equalsInteger(request.getCompanyType(), Constants.ZERO)) { // 个人类型:必须上传劳动合同和社保证明 if (CollectionUtils.isEmpty(request.getLaborContractImgs())) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "个人类型必须上传有效劳动合同"); } if (CollectionUtils.isEmpty(request.getSocialSecurityImgs())) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "个人类型必须上传社保缴纳证明"); } } else if (Constants.equalsInteger(request.getCompanyType(), Constants.ONE)) { // 企业类型:必须填写法人信息和营业执照 if (StringUtils.isBlank(request.getBusinessImg())) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "企业类型必须上传营业执照"); } if (StringUtils.isBlank(request.getLegalPersonName())) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "企业类型必须填写法人姓名"); } if (StringUtils.isBlank(request.getLegalPersonPhone())) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "企业类型必须填写法人电话"); } if (StringUtils.isBlank(request.getLegalPersonCard())) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "企业类型必须填写法人身份证号码"); } } Date now = new Date(); // 更新基础信息 shopInfo.setCompanyType(request.getCompanyType()); shopInfo.setName(request.getName()); shopInfo.setTelephone(request.getTelephone()); shopInfo.setLinkName(request.getLinkName()); shopInfo.setLinkPhone(request.getLinkPhone()); shopInfo.setIdcard(request.getIdcard()); shopInfo.setAreaId(request.getAreaId()); shopInfo.setLongitude(request.getLongitude()); shopInfo.setLatitude(request.getLatitude()); shopInfo.setAddress(request.getAddress()); // 企业类信息 shopInfo.setIdcardImg(request.getIdcardImg()); shopInfo.setIdcardImgBack(request.getIdcardImgBack()); shopInfo.setBusinessImg(request.getBusinessImg()); shopInfo.setLegalPersonName(request.getLegalPersonName()); shopInfo.setLegalPersonPhone(request.getLegalPersonPhone()); shopInfo.setLegalPersonCard(request.getLegalPersonCard()); shopInfo.setUpdateTime(now); shopInfoMapper.updateById(shopInfo); // 删除旧附件记录 multifileMapper.delete(new QueryWrapper().lambda() .eq(Multifile::getObjId, shopInfo.getId()) .in(Multifile::getObjType, Constants.FileType.STORE_FRONT.getKey(), Constants.FileType.STORE_INTERIOR.getKey(), Constants.FileType.OTHER_MATERIAL.getKey(), Constants.FileType.LABOR_CONTRACT.getKey(), Constants.FileType.SOCIAL_SECURITY.getKey())); // 保存新附件记录 saveMultifileList(shopInfo.getId(), Constants.FileType.STORE_FRONT.getKey(), request.getStoreFrontImgs(), now); saveMultifileList(shopInfo.getId(), Constants.FileType.STORE_INTERIOR.getKey(), request.getStoreInteriorImgs(), now); saveMultifileList(shopInfo.getId(), Constants.FileType.OTHER_MATERIAL.getKey(), request.getOtherMaterialImgs(), now); saveMultifileList(shopInfo.getId(), Constants.FileType.LABOR_CONTRACT.getKey(), request.getLaborContractImgs(), now); saveMultifileList(shopInfo.getId(), Constants.FileType.SOCIAL_SECURITY.getKey(), request.getSocialSecurityImgs(), now); } // ========== 私有方法 ========== /** * 生成默认密码:手机号后6位 + @123456 */ private String generateDefaultPassword(String telephone) { if (StringUtils.isBlank(telephone) || telephone.length() < 6) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "手机号格式异常,无法生成默认密码"); } return telephone.substring(telephone.length() - 6) + "@123456"; } private void checkTelephoneUnique(String telephone, Integer excludeId) { QueryWrapper qw = new QueryWrapper<>(); qw.lambda() .eq(ShopInfo::getTelephone, telephone) .eq(ShopInfo::getDeleted, Constants.ZERO); if (excludeId != null) { qw.lambda().ne(ShopInfo::getId, excludeId); } long count = shopInfoMapper.selectCount(qw); if (count > 0) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "该手机号已注册门店"); } } private void saveMultifileList(Integer objId, Integer objType, List urls, Date now) { if (CollectionUtils.isEmpty(urls)) { return; } int sortNum = 0; for (String url : urls) { Multifile multifile = new Multifile(); multifile.setObjId(objId); multifile.setObjType(objType); multifile.setType(Constants.ZERO); multifile.setFileurl(url); multifile.setIsdeleted(Constants.ZERO); multifile.setCreateDate(now); multifile.setSortnum(sortNum++); multifileMapper.insert(multifile); } } private ShopDetailVO buildShopDetailVO(ShopInfo shopInfo) { ShopDetailVO vo = new ShopDetailVO(); vo.setId(shopInfo.getId()); vo.setCompanyType(shopInfo.getCompanyType()); vo.setName(shopInfo.getName()); vo.setTelephone(shopInfo.getTelephone()); vo.setLinkName(shopInfo.getLinkName()); vo.setLinkPhone(shopInfo.getLinkPhone()); vo.setIdcard(shopInfo.getIdcard()); vo.setAreaId(shopInfo.getAreaId()); // 填充省市区名称及主键 Areas area = areasBiz.resolveArea(shopInfo.getAreaId()); if (area != null) { vo.setProvinceId(area.getProvinceId()); vo.setCityId(area.getCityId()); vo.setProvinceName(area.getProvinceName()); vo.setCityName(area.getCityName()); vo.setAreaName(area.getName()); } vo.setLongitude(shopInfo.getLongitude()); vo.setLatitude(shopInfo.getLatitude()); vo.setAddress(shopInfo.getAddress()); vo.setLegalPersonName(shopInfo.getLegalPersonName()); vo.setLegalPersonPhone(shopInfo.getLegalPersonPhone()); vo.setLegalPersonCard(shopInfo.getLegalPersonCard()); vo.setAuditStatus(shopInfo.getAuditStatus()); vo.setStatus(shopInfo.getStatus()); vo.setAuditTime(shopInfo.getAuditTime()); if(Objects.nonNull(shopInfo.getAuditUserId())){ SystemUser systemUser = systemUserMapper.selectById(shopInfo.getAuditUserId()); if (systemUser != null) vo.setAuditName(systemUser.getRealname()); } vo.setAuditRemark(shopInfo.getAuditRemark()); vo.setOpenid(shopInfo.getOpenid()); vo.setPayStatus(shopInfo.getPayStatus()); vo.setScore(shopInfo.getScore()); vo.setCreateTime(shopInfo.getCreateTime()); // 拼接图片前缀 String imgPrefix = ""; try { imgPrefix = systemDictDataBiz.queryByCode(Constants.SYSTEM, Constants.RESOURCE_PATH).getCode() + systemDictDataBiz.queryByCode(Constants.SYSTEM, Constants.SHOP_FILES).getCode(); } catch (Exception e) { // 未配置时忽略 } vo.setImgPrefix(imgPrefix); // 单图字段:半路径 + 全路径 vo.setIdcardImg(shopInfo.getIdcardImg()); vo.setIdcardImgUrl(StringUtils.isNotBlank(shopInfo.getIdcardImg()) ? imgPrefix + shopInfo.getIdcardImg() : null); vo.setIdcardImgBack(shopInfo.getIdcardImgBack()); vo.setIdcardImgBackUrl(StringUtils.isNotBlank(shopInfo.getIdcardImgBack()) ? imgPrefix + shopInfo.getIdcardImgBack() : null); vo.setBusinessImg(shopInfo.getBusinessImg()); vo.setBusinessImgUrl(StringUtils.isNotBlank(shopInfo.getBusinessImg()) ? imgPrefix + shopInfo.getBusinessImg() : null); // 查询附件 QueryWrapper fileQw = new QueryWrapper<>(); fileQw.lambda() .eq(Multifile::getObjId, shopInfo.getId()) .eq(Multifile::getIsdeleted, Constants.ZERO) .in(Multifile::getObjType, Constants.FileType.STORE_FRONT.getKey(), Constants.FileType.STORE_INTERIOR.getKey(), Constants.FileType.OTHER_MATERIAL.getKey(), Constants.FileType.LABOR_CONTRACT.getKey(), Constants.FileType.SOCIAL_SECURITY.getKey()) .orderByAsc(Multifile::getObjType, Multifile::getSortnum); List files = multifileMapper.selectList(fileQw); // 按 objType 分组,半路径 + 全路径 Map> fileMap = new HashMap<>(); Map> fileUrlMap = new HashMap<>(); for (Multifile f : files) { fileMap.computeIfAbsent(f.getObjType(), k -> new ArrayList<>()).add(f.getFileurl()); String fullUrl = StringUtils.isNotBlank(f.getFileurl()) ? imgPrefix + f.getFileurl() : f.getFileurl(); fileUrlMap.computeIfAbsent(f.getObjType(), k -> new ArrayList<>()).add(fullUrl); } vo.setStoreFrontImgs(fileMap.getOrDefault(Constants.FileType.STORE_FRONT.getKey(), new ArrayList<>())); vo.setStoreInteriorImgs(fileMap.getOrDefault(Constants.FileType.STORE_INTERIOR.getKey(), new ArrayList<>())); vo.setOtherMaterialImgs(fileMap.getOrDefault(Constants.FileType.OTHER_MATERIAL.getKey(), new ArrayList<>())); vo.setLaborContractImgs(fileMap.getOrDefault(Constants.FileType.LABOR_CONTRACT.getKey(), new ArrayList<>())); vo.setSocialSecurityImgs(fileMap.getOrDefault(Constants.FileType.SOCIAL_SECURITY.getKey(), new ArrayList<>())); vo.setStoreFrontImgUrls(fileUrlMap.getOrDefault(Constants.FileType.STORE_FRONT.getKey(), new ArrayList<>())); vo.setStoreInteriorImgUrls(fileUrlMap.getOrDefault(Constants.FileType.STORE_INTERIOR.getKey(), new ArrayList<>())); vo.setOtherMaterialImgUrls(fileUrlMap.getOrDefault(Constants.FileType.OTHER_MATERIAL.getKey(), new ArrayList<>())); vo.setLaborContractImgUrls(fileUrlMap.getOrDefault(Constants.FileType.LABOR_CONTRACT.getKey(), new ArrayList<>())); vo.setSocialSecurityImgUrls(fileUrlMap.getOrDefault(Constants.FileType.SOCIAL_SECURITY.getKey(), new ArrayList<>())); // 查询绑定开户会员头像(payMemberOpenId 关联 member.openid) if (StringUtils.isNotBlank(shopInfo.getPayMemberOpenId())) { QueryWrapper memberQw = new QueryWrapper<>(); memberQw.lambda().eq(Member::getOpenid, shopInfo.getPayMemberOpenId()).last("limit 1"); Member payMember = memberMapper.selectOne(memberQw); if (payMember != null && StringUtils.isNotBlank(payMember.getCoverImage())) { String memberPrefix = ""; try { memberPrefix = systemDictDataBiz.queryByCode(Constants.SYSTEM, Constants.RESOURCE_PATH).getCode() + systemDictDataBiz.queryByCode(Constants.SYSTEM, Constants.MEMBER_FILES).getCode(); } catch (Exception e) { // 未配置时忽略 } vo.setPayMemberCoverImage(memberPrefix + payMember.getCoverImage()); } } return vo; } @Override public PageData findNearbyShops(PageWrap pageWrap) { IPage page = new Page<>(pageWrap.getPage(), pageWrap.getCapacity()); ShopNearbyDTO dto = pageWrap.getModel(); Double longitude = dto.getLongitude(); Double latitude = dto.getLatitude(); Integer sortType = dto.getSortType(); Integer distanceMeter = dto.getDistance(); // Haversine距离公式(单位km) String distanceFormula = "(6371 * acos(cos(radians(" + latitude + ")) * cos(radians(latitude)) " + "* cos(radians(longitude) - radians(" + longitude + ")) " + "+ sin(radians(" + latitude + ")) * sin(radians(latitude))))"; QueryWrapper qw = new QueryWrapper<>(); qw.lambda() .eq(ShopInfo::getDeleted, Constants.ZERO) .eq(ShopInfo::getStatus, Constants.ZERO) .eq(ShopInfo::getAuditStatus, Constants.THREE); // 门店营业类型筛选 if (dto.getBusinessType() != null) { qw.lambda().eq(ShopInfo::getBusinessType, dto.getBusinessType()); } // 门店名称模糊查询 if (StringUtils.isNotBlank(dto.getName())) { qw.lambda().like(ShopInfo::getName, dto.getName()); } // 距离筛选(单位:米 → 转换为km比较) if (distanceMeter != null && distanceMeter > 0) { double maxKm = distanceMeter / 1000.0; qw.apply(distanceFormula + " <= {0}", maxKm); } // 排序 if (longitude != null && latitude != null) { if (sortType != null && sortType == 2) { // 按评分降序 qw.last("ORDER BY score DESC"); } else { // 默认:按距离升序 qw.last("ORDER BY " + distanceFormula + " ASC"); } } else { qw.lambda().orderByDesc(ShopInfo::getCreateTime); } IPage result = shopInfoMapper.selectPage(page, qw); // 图片前缀 String imgPrefix = getShopPrefix(); List voList = new ArrayList<>(); for (ShopInfo shop : result.getRecords()) { ShopNearbyVO vo = new ShopNearbyVO(); vo.setId(shop.getId()); vo.setName(shop.getName()); vo.setShopHours(shop.getShopHours()); vo.setAddress(shop.getAddress()); vo.setScore(shop.getScore()); // 门头照第一张 vo.setCoverImg(getFirstImage(shop.getId(), Constants.FileType.STORE_FRONT.getKey(), imgPrefix)); // 距离 if (longitude != null && latitude != null && shop.getLongitude() != null && shop.getLatitude() != null) { double distKm = haversine(latitude, longitude, shop.getLatitude(), shop.getLongitude()); vo.setDistanceText(formatDistance(distKm)); } voList.add(vo); } IPage vPage = new Page<>(pageWrap.getPage(), pageWrap.getCapacity()); PageData pageData = PageData.from(vPage); pageData.setRecords(voList); pageData.setTotal(result.getTotal()); pageData.setPage(result.getCurrent()); pageData.setCapacity(result.getSize()); return pageData; } @Override public ShopWebDetailVO getShopWebDetail(ShopDetailQueryDTO dto) { ShopInfo shop = shopInfoMapper.selectById(dto.getId()); if (Objects.isNull(shop) || Constants.equalsInteger(shop.getDeleted(), Constants.ONE)) { throw new BusinessException(ResponseStatus.DATA_EMPTY); } ShopWebDetailVO vo = new ShopWebDetailVO(); vo.setId(shop.getId()); vo.setName(shop.getName()); vo.setAddress(shop.getAddress()); vo.setContent(shop.getContent()); // 门头照 + 内部照 全路径集合 String imgPrefix = getShopPrefix(); List images = new ArrayList<>(); images.addAll(getImageList(dto.getId(), Constants.FileType.STORE_FRONT.getKey(), imgPrefix)); images.addAll(getImageList(dto.getId(), Constants.FileType.STORE_INTERIOR.getKey(), imgPrefix)); vo.setImages(images); // 距离 if (dto.getLongitude() != null && dto.getLatitude() != null && shop.getLongitude() != null && shop.getLatitude() != null) { double distKm = haversine(dto.getLatitude(), dto.getLongitude(), shop.getLatitude(), shop.getLongitude()); vo.setDistanceText(formatDistance(distKm)); } return vo; } @Override public void maintainShopInfo(Integer memberId, ShopInfoMaintainDTO dto) { // 门店主键与会员主键一致 ShopInfo shop = shopInfoMapper.selectById(memberId); if (Objects.isNull(shop) || Constants.equalsInteger(shop.getDeleted(), Constants.ONE)) { throw new BusinessException(ResponseStatus.DATA_EMPTY); } // 需要支付完押金后才可维护(auditStatus >= 2) if (shop.getAuditStatus() == null || shop.getAuditStatus() < Constants.TWO) { throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "请先完成押金支付后再维护门店信息"); } UpdateWrapper updateWrapper = new UpdateWrapper<>(); updateWrapper.lambda() .eq(ShopInfo::getId, shop.getId()) .set(ShopInfo::getUpdateTime, new Date()) .set(dto.getCoverImg() != null, ShopInfo::getCoverImg, dto.getCoverImg()) .set(dto.getContent() != null, ShopInfo::getContent, dto.getContent()) .set(dto.getDepositTypes() != null, ShopInfo::getDepositTypes, dto.getDepositTypes()) .set(dto.getFeeStandard() != null, ShopInfo::getFeeStandard, dto.getFeeStandard()) .set(dto.getDeliveryArea() != null, ShopInfo::getDeliveryArea, dto.getDeliveryArea()) .set(dto.getShopHours() != null, ShopInfo::getShopHours, dto.getShopHours()) .set(dto.getBusinessType() != null, ShopInfo::getBusinessType, dto.getBusinessType()); shopInfoMapper.update(updateWrapper); } @Override public ShopInfoMaintainDTO getShopMaintainInfo(Integer memberId) { ShopInfo shop = shopInfoMapper.selectById(memberId); if (Objects.isNull(shop) || Constants.equalsInteger(shop.getDeleted(), Constants.ONE)) { return null; } ShopInfoMaintainDTO dto = new ShopInfoMaintainDTO(); dto.setCoverImg(shop.getCoverImg()); dto.setContent(shop.getContent()); dto.setDepositTypes(shop.getDepositTypes()); dto.setFeeStandard(shop.getFeeStandard()); dto.setDeliveryArea(shop.getDeliveryArea()); dto.setShopHours(shop.getShopHours()); dto.setBusinessType(shop.getBusinessType()); return dto; } /** * Haversine公式计算两点间距离(km) */ private double haversine(double lat1, double lng1, double lat2, double lng2) { double R = 6371; double dLat = Math.toRadians(lat2 - lat1); double dLng = Math.toRadians(lng2 - lng1); double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) * Math.sin(dLng / 2) * Math.sin(dLng / 2); double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); return R * c; } /** * 格式化距离:小于1km显示米,大于等于1km显示千米(保留1位小数) */ private String formatDistance(double km) { if (km < 1) { return Math.round(km * 1000) + "m"; } return String.format("%.1fkm", km); } /** * 获取门店图片前缀 */ private String getShopPrefix() { try { return systemDictDataBiz.queryByCode(Constants.SYSTEM, Constants.RESOURCE_PATH).getCode() + systemDictDataBiz.queryByCode(Constants.SYSTEM, Constants.SHOP_FILES).getCode(); } catch (Exception e) { return ""; } } /** * 获取门店指定类型的第一张图片全路径 */ private String getFirstImage(Integer shopId, int objType, String imgPrefix) { QueryWrapper qw = new QueryWrapper<>(); qw.lambda() .eq(Multifile::getObjId, shopId) .eq(Multifile::getObjType, objType) .eq(Multifile::getIsdeleted, Constants.ZERO) .orderByAsc(Multifile::getSortnum) .last("limit 1"); Multifile f = multifileMapper.selectOne(qw); return f != null && StringUtils.isNotBlank(f.getFileurl()) ? imgPrefix + f.getFileurl() : null; } /** * 获取门店指定类型的所有图片全路径 */ private List getImageList(Integer shopId, int objType, String imgPrefix) { QueryWrapper qw = new QueryWrapper<>(); qw.lambda() .eq(Multifile::getObjId, shopId) .eq(Multifile::getObjType, objType) .eq(Multifile::getIsdeleted, Constants.ZERO) .orderByAsc(Multifile::getSortnum); List files = multifileMapper.selectList(qw); List urls = new ArrayList<>(); for (Multifile f : files) { if (StringUtils.isNotBlank(f.getFileurl())) { urls.add(imgPrefix + f.getFileurl()); } } return urls; } /** * 商户账号密码登录 * @param dto * @return */ @Override public ShopLoginVO shopPasswordLogin(ShopLoginDTO dto){ if(StringUtils.isBlank(dto.getTelephone()) || StringUtils.isBlank(dto.getPassword()) || StringUtils.isBlank(dto.getOpenid()) ){ throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(),"用户名或密码不能为空"); } ShopInfo shop = shopInfoMapper.selectOne(new QueryWrapper().lambda().eq(ShopInfo::getTelephone, dto.getTelephone()) .eq(ShopInfo::getDeleted,Constants.ZERO) .last("limit 1") ); if(shop==null){ throw new BusinessException(ResponseStatus.ACCOUNT_INCORRECT); } //加密密码 String pwd = Utils.Secure.encryptPassword( dto.getPassword(), shop.getSalt()); if(!pwd.equals(shop.getPassword())){ throw new BusinessException(ResponseStatus.ACCOUNT_INCORRECT); } // 更新当前登录会员的openid到门店 if(StringUtils.isNotBlank(dto.getOpenid())){ shopInfoMapper.update(null,new UpdateWrapper().lambda() .set(ShopInfo::getOpenid,dto.getOpenid()) .eq(ShopInfo::getId,shop.getId()) ); // 清空其他门店的同一openid,保证唯一 shopInfoMapper.update(null,new UpdateWrapper().lambda() .set(ShopInfo::getOpenid,null) .eq(ShopInfo::getOpenid,dto.getOpenid()) .ne(ShopInfo::getId,shop.getId()) ); shop.setOpenid(dto.getOpenid()); } // 创建token(generateTokenForRedis 已自动清除该用户旧token,保证唯一有效) String token = JwtTokenUtil.generateTokenForRedis(shop.getId(), Constants.TWO, JSONObject.toJSONString(shop), redisTemplate); // 构建响应 ShopLoginVO vo = new ShopLoginVO(); vo.setToken(token); vo.setShopId(shop.getId()); vo.setShopName(shop.getName()); vo.setCompanyType(shop.getCompanyType()); // 所属城市名称 Areas area = areasBiz.resolveArea(shop.getAreaId()); if (area != null) { vo.setCityName(area.getCityName()); } return vo; } @Override public ShopLoginVO shopSilentLogin(String openid) { if (StringUtils.isBlank(openid)) { throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "openid不能为空"); } ShopInfo shop = shopInfoMapper.selectOne(new QueryWrapper().lambda() .eq(ShopInfo::getOpenid, openid) .eq(ShopInfo::getDeleted, Constants.ZERO) .last("limit 1")); if (shop == null) { return null; } // 创建token(generateTokenForRedis 已自动清除该用户旧token,保证唯一有效) String token = JwtTokenUtil.generateTokenForRedis(shop.getId(), Constants.TWO, JSONObject.toJSONString(shop), redisTemplate); ShopLoginVO vo = new ShopLoginVO(); vo.setToken(token); vo.setShopId(shop.getId()); vo.setShopName(shop.getName()); vo.setCompanyType(shop.getCompanyType()); Areas area = areasBiz.resolveArea(shop.getAreaId()); if (area != null) { vo.setCityName(area.getCityName()); } return vo; } }