Commit 146da27e04d663c833f373b4484b4ead1a508cc9
1 parent
33eee1ba
新增多城市支持逻辑,完善店铺管理功能并新增商家管理相关接口
Showing
7 changed files
with
197 additions
and
32 deletions
src/main/java/com/diligrp/rider/controller/AdminMerchantController.java
0 → 100644
| 1 | +package com.diligrp.rider.controller; | |
| 2 | + | |
| 3 | +import com.diligrp.rider.common.exception.BizException; | |
| 4 | +import com.diligrp.rider.common.result.Result; | |
| 5 | +import com.diligrp.rider.dto.MerchantStoreDTO; | |
| 6 | +import com.diligrp.rider.entity.MerchantStore; | |
| 7 | +import com.diligrp.rider.service.MerchantService; | |
| 8 | +import jakarta.servlet.http.HttpServletRequest; | |
| 9 | +import jakarta.validation.Valid; | |
| 10 | +import lombok.RequiredArgsConstructor; | |
| 11 | +import org.springframework.web.bind.annotation.*; | |
| 12 | + | |
| 13 | +import java.math.BigDecimal; | |
| 14 | +import java.util.List; | |
| 15 | + | |
| 16 | +@RestController | |
| 17 | +@RequestMapping("/api/admin/merchant") | |
| 18 | +@RequiredArgsConstructor | |
| 19 | +public class AdminMerchantController { | |
| 20 | + | |
| 21 | + private final MerchantService merchantService; | |
| 22 | + | |
| 23 | + @GetMapping("/store/list") | |
| 24 | + public Result<List<MerchantStore>> storeList(@RequestParam(required = false) Long cityId, | |
| 25 | + @RequestParam(required = false) String keyword, | |
| 26 | + @RequestParam(defaultValue = "1") int page, | |
| 27 | + HttpServletRequest request) { | |
| 28 | + return Result.success(merchantService.storeList(resolveQueryCityId(request, cityId), keyword, page)); | |
| 29 | + } | |
| 30 | + | |
| 31 | + @PostMapping("/store/add") | |
| 32 | + public Result<Long> addStore(@Valid @RequestBody MerchantStoreDTO dto, HttpServletRequest request) { | |
| 33 | + return Result.success(merchantService.addStore(dto, resolveScopedCityId(request))); | |
| 34 | + } | |
| 35 | + | |
| 36 | + @PutMapping("/store/edit") | |
| 37 | + public Result<Void> editStore(@Valid @RequestBody MerchantStoreDTO dto, HttpServletRequest request) { | |
| 38 | + merchantService.editStore(dto, resolveScopedCityId(request)); | |
| 39 | + return Result.success(); | |
| 40 | + } | |
| 41 | + | |
| 42 | + @GetMapping("/store/detail") | |
| 43 | + public Result<MerchantStore> storeDetail(@RequestParam Long storeId, HttpServletRequest request) { | |
| 44 | + return Result.success(merchantService.getStore(storeId, resolveScopedCityId(request))); | |
| 45 | + } | |
| 46 | + | |
| 47 | + @PostMapping("/store/setOperatingState") | |
| 48 | + public Result<Void> setOperatingState(@RequestParam Long storeId, | |
| 49 | + @RequestParam int state, | |
| 50 | + HttpServletRequest request) { | |
| 51 | + merchantService.setOperatingState(storeId, state, resolveScopedCityId(request)); | |
| 52 | + return Result.success(); | |
| 53 | + } | |
| 54 | + | |
| 55 | + @PostMapping("/store/setAutoOrder") | |
| 56 | + public Result<Void> setAutoOrder(@RequestParam Long storeId, | |
| 57 | + @RequestParam int auto, | |
| 58 | + HttpServletRequest request) { | |
| 59 | + merchantService.setAutoOrder(storeId, auto); | |
| 60 | + return Result.success(); | |
| 61 | + } | |
| 62 | + | |
| 63 | + @PostMapping("/store/updateFeeConfig") | |
| 64 | + public Result<Void> updateFeeConfig(@RequestParam Long storeId, | |
| 65 | + @RequestParam(defaultValue = "0") BigDecimal freeShipping, | |
| 66 | + @RequestParam(defaultValue = "0") BigDecimal upToSend, | |
| 67 | + HttpServletRequest request) { | |
| 68 | + merchantService.updateFeeConfig(storeId, freeShipping, upToSend, resolveScopedCityId(request)); | |
| 69 | + return Result.success(); | |
| 70 | + } | |
| 71 | + | |
| 72 | + @DeleteMapping("/store/del") | |
| 73 | + public Result<Void> delStore(@RequestParam Long storeId, HttpServletRequest request) { | |
| 74 | + merchantService.delStore(storeId, resolveScopedCityId(request)); | |
| 75 | + return Result.success(); | |
| 76 | + } | |
| 77 | + | |
| 78 | + private Long resolveScopedCityId(HttpServletRequest request) { | |
| 79 | + if (!"substation".equals(request.getAttribute("role"))) { | |
| 80 | + return null; | |
| 81 | + } | |
| 82 | + Long cityId = (Long) request.getAttribute("cityId"); | |
| 83 | + if (cityId == null || cityId < 1) { | |
| 84 | + throw new BizException("当前账号未绑定有效城市"); | |
| 85 | + } | |
| 86 | + return cityId; | |
| 87 | + } | |
| 88 | + | |
| 89 | + private Long resolveQueryCityId(HttpServletRequest request, Long queryCityId) { | |
| 90 | + Long scopedCityId = resolveScopedCityId(request); | |
| 91 | + return scopedCityId != null ? scopedCityId : queryCityId; | |
| 92 | + } | |
| 93 | +} | ... | ... |
src/main/java/com/diligrp/rider/dto/AdminRiderAddDTO.java
src/main/java/com/diligrp/rider/service/MerchantService.java
| ... | ... | @@ -3,32 +3,24 @@ package com.diligrp.rider.service; |
| 3 | 3 | import com.diligrp.rider.dto.MerchantStoreDTO; |
| 4 | 4 | import com.diligrp.rider.entity.MerchantStore; |
| 5 | 5 | |
| 6 | +import java.math.BigDecimal; | |
| 6 | 7 | import java.util.List; |
| 7 | 8 | |
| 8 | 9 | public interface MerchantService { |
| 9 | - // ---- 店铺管理 ---- | |
| 10 | - /** 新增店铺(平台后台直接新建,无需审核) */ | |
| 11 | 10 | Long addStore(MerchantStoreDTO dto); |
| 12 | - /** 编辑店铺 */ | |
| 11 | + Long addStore(MerchantStoreDTO dto, Long scopedCityId); | |
| 13 | 12 | void editStore(MerchantStoreDTO dto); |
| 14 | - /** 店铺列表 */ | |
| 13 | + void editStore(MerchantStoreDTO dto, Long scopedCityId); | |
| 15 | 14 | List<MerchantStore> storeList(Long cityId, String keyword, int page); |
| 16 | - /** 获取店铺详情 */ | |
| 17 | 15 | MerchantStore getStore(Long storeId); |
| 18 | - /** 设置营业/打烊 */ | |
| 16 | + MerchantStore getStore(Long storeId, Long scopedCityId); | |
| 19 | 17 | void setOperatingState(Long storeId, int state); |
| 20 | - /** 设置自动接单 */ | |
| 18 | + void setOperatingState(Long storeId, int state, Long scopedCityId); | |
| 21 | 19 | void setAutoOrder(Long storeId, int auto); |
| 22 | - /** 更新免运费和起送金额 */ | |
| 23 | - void updateFeeConfig(Long storeId, java.math.BigDecimal freeShipping, java.math.BigDecimal upToSend); | |
| 24 | - /** 删除店铺 */ | |
| 20 | + void updateFeeConfig(Long storeId, BigDecimal freeShipping, BigDecimal upToSend); | |
| 21 | + void updateFeeConfig(Long storeId, BigDecimal freeShipping, BigDecimal upToSend, Long scopedCityId); | |
| 25 | 22 | void delStore(Long storeId); |
| 26 | - /** | |
| 27 | - * 外部系统同步门店(新增或更新,以 appKey+outStoreId 为唯一键) | |
| 28 | - */ | |
| 23 | + void delStore(Long storeId, Long scopedCityId); | |
| 29 | 24 | MerchantStore syncStore(String appKey, MerchantStoreDTO dto); |
| 30 | - /** | |
| 31 | - * 根据 appKey + outStoreId 查询门店 | |
| 32 | - */ | |
| 33 | 25 | MerchantStore getByOutStoreId(String appKey, String outStoreId); |
| 34 | 26 | } | ... | ... |
src/main/java/com/diligrp/rider/service/impl/AdminRiderServiceImpl.java
| ... | ... | @@ -57,6 +57,7 @@ public class AdminRiderServiceImpl implements AdminRiderService { |
| 57 | 57 | Rider rider = new Rider(); |
| 58 | 58 | rider.setUserNickname(dto.getUserNickname()); |
| 59 | 59 | rider.setMobile(dto.getMobile()); |
| 60 | + rider.setIdNo(dto.getIdNo()); | |
| 60 | 61 | rider.setUserPass(encryptPass(dto.getPassword())); |
| 61 | 62 | rider.setCityId(cityId); |
| 62 | 63 | rider.setUserStatus(1); | ... | ... |
src/main/java/com/diligrp/rider/service/impl/MenuBootstrapServiceImpl.java
| ... | ... | @@ -61,9 +61,9 @@ public class MenuBootstrapServiceImpl implements MenuBootstrapService { |
| 61 | 61 | defaults.add(menu("city.manage", "租户管理", "MENU", "/city", "GlobalOutlined", 0L, MenuScopeEnum.PLATFORM, 20)); |
| 62 | 62 | defaults.add(menu("substation.manage", "分站管理", "MENU", "/substation", "ApartmentOutlined", 0L, MenuScopeEnum.PLATFORM, 30)); |
| 63 | 63 | defaults.add(menu("substation.user", "分站账号", "MENU", "/substation/user", "TeamOutlined", 0L, MenuScopeEnum.SUBSTATION, 31)); |
| 64 | - defaults.add(menu("merchant.root", "商家管理", "DIR", "", "ShopOutlined", 0L, MenuScopeEnum.PLATFORM, 40)); | |
| 65 | - defaults.add(menu("merchant.enter", "入驻申请", "MENU", "/merchant/enter", "", 0L, MenuScopeEnum.PLATFORM, 41)); | |
| 66 | - defaults.add(menu("merchant.store", "店铺管理", "MENU", "/merchant/store", "", 0L, MenuScopeEnum.PLATFORM, 42)); | |
| 64 | + defaults.add(menu("merchant.root", "商家管理", "DIR", "", "ShopOutlined", 0L, MenuScopeEnum.BOTH, 40)); | |
| 65 | + defaults.add(menu("merchant.enter", "入驻申请", "MENU", "/merchant/enter", "", 0L, MenuScopeEnum.BOTH, 41)); | |
| 66 | + defaults.add(menu("merchant.store", "店铺管理", "MENU", "/merchant/store", "", 0L, MenuScopeEnum.BOTH, 42)); | |
| 67 | 67 | defaults.add(menu("rider.list", "骑手管理", "MENU", "/rider", "UserOutlined", 0L, MenuScopeEnum.BOTH, 50)); |
| 68 | 68 | defaults.add(menu("rider.level", "骑手等级", "MENU", "/rider/level", "TrophyOutlined", 0L, MenuScopeEnum.BOTH, 55)); |
| 69 | 69 | defaults.add(menu("rider.evaluate", "骑手评价", "MENU", "/rider/evaluate", "StarOutlined", 0L, MenuScopeEnum.BOTH, 60)); | ... | ... |
src/main/java/com/diligrp/rider/service/impl/MerchantServiceImpl.java
| ... | ... | @@ -30,8 +30,15 @@ public class MerchantServiceImpl implements MerchantService { |
| 30 | 30 | @Override |
| 31 | 31 | @Transactional |
| 32 | 32 | public Long addStore(MerchantStoreDTO dto) { |
| 33 | - var city = cityService.getById(dto.getCityId()); | |
| 34 | - if (city == null) throw new BizException("城市不存在"); | |
| 33 | + return addStore(dto, null); | |
| 34 | + } | |
| 35 | + | |
| 36 | + @Override | |
| 37 | + @Transactional | |
| 38 | + public Long addStore(MerchantStoreDTO dto, Long scopedCityId) { | |
| 39 | + Long effectiveCityId = resolveCityId(dto.getCityId(), scopedCityId); | |
| 40 | + dto.setCityId(effectiveCityId); | |
| 41 | + ensureCityExists(effectiveCityId); | |
| 35 | 42 | |
| 36 | 43 | MerchantStore store = toEntity(dto); |
| 37 | 44 | store.setAddTime(System.currentTimeMillis() / 1000); |
| ... | ... | @@ -56,8 +63,18 @@ public class MerchantServiceImpl implements MerchantService { |
| 56 | 63 | |
| 57 | 64 | @Override |
| 58 | 65 | public void editStore(MerchantStoreDTO dto) { |
| 59 | - MerchantStore existing = storeMapper.selectById(dto.getId()); | |
| 60 | - if (existing == null) throw new BizException("店铺不存在"); | |
| 66 | + editStore(dto, null); | |
| 67 | + } | |
| 68 | + | |
| 69 | + @Override | |
| 70 | + public void editStore(MerchantStoreDTO dto, Long scopedCityId) { | |
| 71 | + MerchantStore existing = requireStore(dto.getId(), scopedCityId); | |
| 72 | + Long effectiveCityId = resolveCityId(dto.getCityId(), scopedCityId); | |
| 73 | + if (scopedCityId != null && !scopedCityId.equals(existing.getCityId())) { | |
| 74 | + throw new BizException("无权操作其他租户的店铺"); | |
| 75 | + } | |
| 76 | + dto.setCityId(effectiveCityId); | |
| 77 | + ensureCityExists(effectiveCityId); | |
| 61 | 78 | MerchantStore store = toEntity(dto); |
| 62 | 79 | store.setId(dto.getId()); |
| 63 | 80 | storeMapper.updateById(store); |
| ... | ... | @@ -71,42 +88,67 @@ public class MerchantServiceImpl implements MerchantService { |
| 71 | 88 | .orderByDesc(MerchantStore::getId); |
| 72 | 89 | if (cityId != null && cityId > 0) wrapper.eq(MerchantStore::getCityId, cityId); |
| 73 | 90 | if (keyword != null && !keyword.isBlank()) wrapper.like(MerchantStore::getName, keyword); |
| 74 | - int offset = (page - 1) * PAGE_SIZE; | |
| 91 | + int currentPage = Math.max(page, 1); | |
| 92 | + int offset = (currentPage - 1) * PAGE_SIZE; | |
| 75 | 93 | wrapper.last("LIMIT " + offset + "," + PAGE_SIZE); |
| 76 | 94 | return storeMapper.selectList(wrapper); |
| 77 | 95 | } |
| 78 | 96 | |
| 79 | 97 | @Override |
| 80 | 98 | public MerchantStore getStore(Long storeId) { |
| 81 | - return storeMapper.selectById(storeId); | |
| 99 | + return getStore(storeId, null); | |
| 100 | + } | |
| 101 | + | |
| 102 | + @Override | |
| 103 | + public MerchantStore getStore(Long storeId, Long scopedCityId) { | |
| 104 | + return requireStore(storeId, scopedCityId); | |
| 82 | 105 | } |
| 83 | 106 | |
| 84 | 107 | @Override |
| 85 | 108 | public void setOperatingState(Long storeId, int state) { |
| 109 | + setOperatingState(storeId, state, null); | |
| 110 | + } | |
| 111 | + | |
| 112 | + @Override | |
| 113 | + public void setOperatingState(Long storeId, int state, Long scopedCityId) { | |
| 114 | + MerchantStore store = requireStore(storeId, scopedCityId); | |
| 86 | 115 | storeMapper.update(null, new LambdaUpdateWrapper<MerchantStore>() |
| 87 | - .eq(MerchantStore::getId, storeId) | |
| 116 | + .eq(MerchantStore::getId, store.getId()) | |
| 88 | 117 | .set(MerchantStore::getOperatingState, state)); |
| 89 | 118 | } |
| 90 | 119 | |
| 91 | 120 | @Override |
| 92 | 121 | public void setAutoOrder(Long storeId, int auto) { |
| 122 | + MerchantStore store = requireStore(storeId, null); | |
| 93 | 123 | storeMapper.update(null, new LambdaUpdateWrapper<MerchantStore>() |
| 94 | - .eq(MerchantStore::getId, storeId) | |
| 124 | + .eq(MerchantStore::getId, store.getId()) | |
| 95 | 125 | .set(MerchantStore::getAutomaticOrder, auto)); |
| 96 | 126 | } |
| 97 | 127 | |
| 98 | 128 | @Override |
| 99 | 129 | public void updateFeeConfig(Long storeId, BigDecimal freeShipping, BigDecimal upToSend) { |
| 130 | + updateFeeConfig(storeId, freeShipping, upToSend, null); | |
| 131 | + } | |
| 132 | + | |
| 133 | + @Override | |
| 134 | + public void updateFeeConfig(Long storeId, BigDecimal freeShipping, BigDecimal upToSend, Long scopedCityId) { | |
| 135 | + MerchantStore store = requireStore(storeId, scopedCityId); | |
| 100 | 136 | storeMapper.update(null, new LambdaUpdateWrapper<MerchantStore>() |
| 101 | - .eq(MerchantStore::getId, storeId) | |
| 137 | + .eq(MerchantStore::getId, store.getId()) | |
| 102 | 138 | .set(MerchantStore::getFreeShipping, freeShipping) |
| 103 | 139 | .set(MerchantStore::getUpToSend, upToSend)); |
| 104 | 140 | } |
| 105 | 141 | |
| 106 | 142 | @Override |
| 107 | 143 | public void delStore(Long storeId) { |
| 144 | + delStore(storeId, null); | |
| 145 | + } | |
| 146 | + | |
| 147 | + @Override | |
| 148 | + public void delStore(Long storeId, Long scopedCityId) { | |
| 149 | + MerchantStore store = requireStore(storeId, scopedCityId); | |
| 108 | 150 | storeMapper.update(null, new LambdaUpdateWrapper<MerchantStore>() |
| 109 | - .eq(MerchantStore::getId, storeId) | |
| 151 | + .eq(MerchantStore::getId, store.getId()) | |
| 110 | 152 | .set(MerchantStore::getIsDel, 1)); |
| 111 | 153 | } |
| 112 | 154 | |
| ... | ... | @@ -115,6 +157,7 @@ public class MerchantServiceImpl implements MerchantService { |
| 115 | 157 | public MerchantStore syncStore(String appKey, MerchantStoreDTO dto) { |
| 116 | 158 | if (appKey == null || appKey.isBlank()) throw new BizException("AppKey不能为空"); |
| 117 | 159 | if (dto.getOutStoreId() == null || dto.getOutStoreId().isBlank()) throw new BizException("外部门店编号不能为空"); |
| 160 | + ensureCityExists(dto.getCityId()); | |
| 118 | 161 | |
| 119 | 162 | long now = System.currentTimeMillis() / 1000; |
| 120 | 163 | MerchantStore existing = getByOutStoreId(appKey, dto.getOutStoreId()); |
| ... | ... | @@ -144,6 +187,40 @@ public class MerchantServiceImpl implements MerchantService { |
| 144 | 187 | .last("LIMIT 1")); |
| 145 | 188 | } |
| 146 | 189 | |
| 190 | + private void ensureCityExists(Long cityId) { | |
| 191 | + if (cityId == null || cityId < 1) { | |
| 192 | + throw new BizException("城市不能为空"); | |
| 193 | + } | |
| 194 | + var city = cityService.getById(cityId); | |
| 195 | + if (city == null) { | |
| 196 | + throw new BizException("城市不存在"); | |
| 197 | + } | |
| 198 | + } | |
| 199 | + | |
| 200 | + private Long resolveCityId(Long dtoCityId, Long scopedCityId) { | |
| 201 | + if (scopedCityId != null) { | |
| 202 | + return scopedCityId; | |
| 203 | + } | |
| 204 | + if (dtoCityId == null || dtoCityId < 1) { | |
| 205 | + throw new BizException("城市不能为空"); | |
| 206 | + } | |
| 207 | + return dtoCityId; | |
| 208 | + } | |
| 209 | + | |
| 210 | + private MerchantStore requireStore(Long storeId, Long scopedCityId) { | |
| 211 | + MerchantStore store = storeMapper.selectOne(new LambdaQueryWrapper<MerchantStore>() | |
| 212 | + .eq(MerchantStore::getId, storeId) | |
| 213 | + .eq(MerchantStore::getIsDel, 0) | |
| 214 | + .last("LIMIT 1")); | |
| 215 | + if (store == null) { | |
| 216 | + throw new BizException("店铺不存在"); | |
| 217 | + } | |
| 218 | + if (scopedCityId != null && !scopedCityId.equals(store.getCityId())) { | |
| 219 | + throw new BizException("无权操作其他租户的店铺"); | |
| 220 | + } | |
| 221 | + return store; | |
| 222 | + } | |
| 223 | + | |
| 147 | 224 | private MerchantStore toEntity(MerchantStoreDTO dto) { |
| 148 | 225 | MerchantStore store = new MerchantStore(); |
| 149 | 226 | store.setName(dto.getName()); | ... | ... |
src/main/resources/data-init.sql
| ... | ... | @@ -72,9 +72,9 @@ INSERT INTO `sys_menu` (`code`, `name`, `type`, `path`, `icon`, `parent_id`, `me |
| 72 | 72 | ('dashboard', '工作台', 'MENU', '/dashboard', 'HomeOutlined', 0, 'BOTH', 10, 1, 1, UNIX_TIMESTAMP()), |
| 73 | 73 | ('city.manage', '租户管理', 'MENU', '/city', 'GlobalOutlined', 0, 'PLATFORM', 20, 1, 1, UNIX_TIMESTAMP()), |
| 74 | 74 | ('substation.manage', '分站管理', 'MENU', '/substation', 'ApartmentOutlined', 0, 'PLATFORM', 30, 1, 1, UNIX_TIMESTAMP()), |
| 75 | -('merchant.root', '商家管理', 'DIR', '', 'ShopOutlined', 0, 'PLATFORM', 40, 1, 1, UNIX_TIMESTAMP()), | |
| 76 | -('merchant.enter', '入驻申请', 'MENU', '/merchant/enter', '', 4, 'PLATFORM', 41, 1, 1, UNIX_TIMESTAMP()), | |
| 77 | -('merchant.store', '店铺管理', 'MENU', '/merchant/store', '', 4, 'PLATFORM', 42, 1, 1, UNIX_TIMESTAMP()), | |
| 75 | +('merchant.root', '商家管理', 'DIR', '', 'ShopOutlined', 0, 'BOTH', 40, 1, 1, UNIX_TIMESTAMP()), | |
| 76 | +('merchant.enter', '入驻申请', 'MENU', '/merchant/enter', '', 4, 'BOTH', 41, 1, 1, UNIX_TIMESTAMP()), | |
| 77 | +('merchant.store', '店铺管理', 'MENU', '/merchant/store', '', 4, 'BOTH', 42, 1, 1, UNIX_TIMESTAMP()), | |
| 78 | 78 | ('rider.list', '骑手管理', 'MENU', '/rider', 'UserOutlined', 0, 'BOTH', 50, 1, 1, UNIX_TIMESTAMP()), |
| 79 | 79 | ('rider.evaluate', '骑手评价', 'MENU', '/rider/evaluate', 'StarOutlined', 0, 'BOTH', 60, 1, 1, UNIX_TIMESTAMP()), |
| 80 | 80 | ('order.root', '订单管理', 'DIR', '', 'UnorderedListOutlined', 0, 'BOTH', 70, 1, 1, UNIX_TIMESTAMP()), | ... | ... |