|
|
@@ -0,0 +1,468 @@
|
|
|
+# 夜市用户端 API 实现计划
|
|
|
+
|
|
|
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
|
+
|
|
|
+**Goal:** 为用户端夜市功能提供后端接口:附近商家列表(含搜索)、常购/关注列表、店铺详情。
|
|
|
+
|
|
|
+**Architecture:** 新建 `NightMarketController` 作为用户端夜市入口,复用已有的 `PosStoreEnrichService`(评分/月售/营业状态)、`PosStoreMapper`(距离计算)、`PosCollectService`(关注)和 `PosFoodService`(商品)。夜市特有的查询(按 nightMarketId 过滤的摊位列表、搜索)通过在 PosStoreMapper 中新增 SQL 实现。
|
|
|
+
|
|
|
+**Tech Stack:** Java 21, Spring Boot 3.3.5, MyBatis-Plus, MySQL ST_Distance 距离计算
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## File Structure
|
|
|
+
|
|
|
+| File | Action | Responsibility |
|
|
|
+|------|--------|----------------|
|
|
|
+| `ruoyi-admin/.../app/nightmarket/NightMarketController.java` | Create | 用户端夜市 API 入口(列表/搜索/关注/详情) |
|
|
|
+| `ruoyi-system/.../mapper/PosStoreMapper.java` | Modify | 新增夜市摊位列表查询(含搜索、距离排序) |
|
|
|
+| `ruoyi-system/.../mapper/PosStoreMapper.xml` | Modify | 新增夜市摊位列表 SQL |
|
|
|
+| `ruoyi-system/.../mapper/PosFoodMapper.java` | Modify | 新增按店铺查商品(含分类) |
|
|
|
+| `ruoyi-system/.../mapper/PosFoodMapper.xml` | Modify | 新增按店铺查商品 SQL |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### Task 1: 夜市附近商家列表(含搜索)
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Modify: `ruoyi-system/src/main/java/com/ruoyi/system/mapper/PosStoreMapper.java`
|
|
|
+- Modify: `ruoyi-system/src/main/resources/mapper/PosStoreMapper.xml`
|
|
|
+- Create: `ruoyi-admin/src/main/java/com/ruoyi/app/nightmarket/NightMarketController.java`
|
|
|
+
|
|
|
+- [ ] **Step 1: 在 PosStoreMapper.java 新增夜市摊位列表查询方法**
|
|
|
+
|
|
|
+在 `PosStoreMapper.java` 中新增两个方法:
|
|
|
+
|
|
|
+```java
|
|
|
+/**
|
|
|
+ * 夜市附近摊位列表(含搜索)
|
|
|
+ * @param longitude 用户经度
|
|
|
+ * @param latitude 用户纬度
|
|
|
+ * @param nightMarketId 夜市ID
|
|
|
+ * @param keyword 搜索关键词(可为null)
|
|
|
+ * @param offset 分页偏移
|
|
|
+ * @return 摊位列表(含距离juli)
|
|
|
+ */
|
|
|
+List<PosStore> getNightMarketStalls(@Param("longitude") BigDecimal longitude,
|
|
|
+ @Param("latitude") BigDecimal latitude,
|
|
|
+ @Param("nightMarketId") Long nightMarketId,
|
|
|
+ @Param("keyword") String keyword,
|
|
|
+ @Param("offset") Integer offset);
|
|
|
+
|
|
|
+/**
|
|
|
+ * 夜市摊位总数(含搜索)
|
|
|
+ */
|
|
|
+int getNightMarketStallsCount(@Param("nightMarketId") Long nightMarketId,
|
|
|
+ @Param("keyword") String keyword);
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 2: 在 PosStoreMapper.xml 新增对应 SQL**
|
|
|
+
|
|
|
+在 `PosStoreMapper.xml` 的 `</mapper>` 标签前添加:
|
|
|
+
|
|
|
+```xml
|
|
|
+<!-- 夜市附近摊位列表(含搜索、距离排序) -->
|
|
|
+<select id="getNightMarketStalls" resultMap="PosStoreResult">
|
|
|
+ SELECT *,(st_distance(point(longitude,latitude),point(#{longitude},#{latitude}))*111195/1000) as juli
|
|
|
+ FROM pos_store
|
|
|
+ WHERE del_flag='0' AND off_shelf='0' AND is_stall=1 AND night_market_id=#{nightMarketId}
|
|
|
+ <if test="keyword != null and keyword != ''">
|
|
|
+ AND pos_name LIKE CONCAT('%', #{keyword}, '%')
|
|
|
+ </if>
|
|
|
+ ORDER BY juli ASC
|
|
|
+ LIMIT #{offset}, 20
|
|
|
+</select>
|
|
|
+
|
|
|
+<!-- 夜市摊位总数(含搜索) -->
|
|
|
+<select id="getNightMarketStallsCount" resultType="int">
|
|
|
+ SELECT COUNT(*) FROM pos_store
|
|
|
+ WHERE del_flag='0' AND off_shelf='0' AND is_stall=1 AND night_market_id=#{nightMarketId}
|
|
|
+ <if test="keyword != null and keyword != ''">
|
|
|
+ AND pos_name LIKE CONCAT('%', #{keyword}, '%')
|
|
|
+ </if>
|
|
|
+</select>
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 3: 创建 NightMarketController.java**
|
|
|
+
|
|
|
+创建文件 `ruoyi-admin/src/main/java/com/ruoyi/app/nightmarket/NightMarketController.java`:
|
|
|
+
|
|
|
+```java
|
|
|
+package com.ruoyi.app.nightmarket;
|
|
|
+
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|
|
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
+import com.ruoyi.app.mendian.PosStoreEnrichService;
|
|
|
+import com.ruoyi.app.user.dto.StoreOutput;
|
|
|
+import com.ruoyi.common.annotation.Anonymous;
|
|
|
+import com.ruoyi.common.core.controller.BaseController;
|
|
|
+import com.ruoyi.common.core.domain.AjaxResult;
|
|
|
+import com.ruoyi.system.domain.*;
|
|
|
+import com.ruoyi.system.mapper.PosStoreMapper;
|
|
|
+import com.ruoyi.system.service.*;
|
|
|
+import com.ruoyi.system.utils.Auth;
|
|
|
+import com.ruoyi.system.utils.JwtUtil;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.web.bind.annotation.*;
|
|
|
+
|
|
|
+import java.math.BigDecimal;
|
|
|
+import java.util.List;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 用户端夜市Controller
|
|
|
+ */
|
|
|
+@RestController
|
|
|
+@RequestMapping("/nightmarket")
|
|
|
+public class NightMarketController extends BaseController {
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private PosStoreMapper posStoreMapper;
|
|
|
+ @Autowired
|
|
|
+ private PosStoreEnrichService posStoreEnrichService;
|
|
|
+ @Autowired
|
|
|
+ private IPosCollectService posCollectService;
|
|
|
+ @Autowired
|
|
|
+ private IPosOrderService posOrderService;
|
|
|
+ @Autowired
|
|
|
+ private IInfoUserService infoUserService;
|
|
|
+ @Autowired
|
|
|
+ private INightMarketService nightMarketService;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 夜市附近商家列表(含搜索)
|
|
|
+ * @param nightMarketId 夜市ID
|
|
|
+ * @param longitude 用户经度
|
|
|
+ * @param latitude 用户纬度
|
|
|
+ * @param page 页码(从1开始)
|
|
|
+ * @param keyword 搜索关键词(可选)
|
|
|
+ * @param language 语言(默认zh-TW)
|
|
|
+ */
|
|
|
+ @Anonymous
|
|
|
+ @GetMapping("/stores")
|
|
|
+ public AjaxResult nearbyStores(@RequestParam Long nightMarketId,
|
|
|
+ @RequestParam BigDecimal longitude,
|
|
|
+ @RequestParam BigDecimal latitude,
|
|
|
+ @RequestParam(defaultValue = "1") Integer page,
|
|
|
+ @RequestParam(required = false) String keyword,
|
|
|
+ @RequestParam(defaultValue = "zh-TW", required = false) String language) {
|
|
|
+ int offset = (page - 1) * 20;
|
|
|
+ List<PosStore> list = posStoreMapper.getNightMarketStalls(longitude, latitude, nightMarketId, keyword, offset);
|
|
|
+ posStoreEnrichService.enrichStoreList(list);
|
|
|
+ Page<StoreOutput> result = posStoreEnrichService.buildStoreOutputPage(list, language, page, list.size());
|
|
|
+ return success(result);
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 4: 编译验证**
|
|
|
+
|
|
|
+Run: `cd E:\QtwCode\foodie\foodie_server && mvn compile -pl ruoyi-admin -am -q`
|
|
|
+Expected: BUILD SUCCESS
|
|
|
+
|
|
|
+- [ ] **Step 5: 提交**
|
|
|
+
|
|
|
+```bash
|
|
|
+git add ruoyi-admin/src/main/java/com/ruoyi/app/nightmarket/NightMarketController.java ruoyi-system/src/main/java/com/ruoyi/system/mapper/PosStoreMapper.java ruoyi-system/src/main/resources/mapper/PosStoreMapper.xml
|
|
|
+git commit -m "feat(nightmarket): 夜市附近商家列表接口(含搜索)"
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### Task 2: 常购/关注列表
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Modify: `ruoyi-admin/src/main/java/com/ruoyi/app/nightmarket/NightMarketController.java`
|
|
|
+
|
|
|
+- [ ] **Step 1: 在 PosStoreMapper.java 新增常购/关注查询方法**
|
|
|
+
|
|
|
+```java
|
|
|
+/**
|
|
|
+ * 夜市下用户关注的摊位列表
|
|
|
+ */
|
|
|
+List<PosStore> getFollowedStalls(@Param("longitude") BigDecimal longitude,
|
|
|
+ @Param("latitude") BigDecimal latitude,
|
|
|
+ @Param("nightMarketId") Long nightMarketId,
|
|
|
+ @Param("userId") Long userId,
|
|
|
+ @Param("offset") Integer offset);
|
|
|
+
|
|
|
+/**
|
|
|
+ * 夜市下用户常购的摊位列表(按下单次数排序)
|
|
|
+ */
|
|
|
+List<PosStore> getFrequentStalls(@Param("longitude") BigDecimal longitude,
|
|
|
+ @Param("latitude") BigDecimal latitude,
|
|
|
+ @Param("nightMarketId") Long nightMarketId,
|
|
|
+ @Param("userId") Long userId,
|
|
|
+ @Param("offset") Integer offset);
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 2: 在 PosStoreMapper.xml 新增对应 SQL**
|
|
|
+
|
|
|
+```xml
|
|
|
+<!-- 夜市下用户关注的摊位 -->
|
|
|
+<select id="getFollowedStalls" resultMap="PosStoreResult">
|
|
|
+ SELECT ps.*, (st_distance(point(ps.longitude,ps.latitude),point(#{longitude},#{latitude}))*111195/1000) as juli
|
|
|
+ FROM pos_store ps
|
|
|
+ INNER JOIN pos_collect pc ON ps.id = pc.md_id
|
|
|
+ WHERE ps.del_flag='0' AND ps.off_shelf='0' AND ps.is_stall=1
|
|
|
+ AND ps.night_market_id=#{nightMarketId}
|
|
|
+ AND pc.user_id=#{userId}
|
|
|
+ ORDER BY pc.cretim DESC
|
|
|
+ LIMIT #{offset}, 20
|
|
|
+</select>
|
|
|
+
|
|
|
+<!-- 夜市下用户常购的摊位(按近30天下单次数排序) -->
|
|
|
+<select id="getFrequentStalls" resultMap="PosStoreResult">
|
|
|
+ SELECT ps.*, (st_distance(point(ps.longitude,ps.latitude),point(#{longitude},#{latitude}))*111195/1000) as juli
|
|
|
+ FROM pos_store ps
|
|
|
+ INNER JOIN (
|
|
|
+ SELECT md_id, COUNT(*) as order_count
|
|
|
+ FROM pos_order
|
|
|
+ WHERE user_id=#{userId}
|
|
|
+ AND DATE_FORMAT(cretim,'%Y-%m-%d') >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
|
|
|
+ GROUP BY md_id
|
|
|
+ ORDER BY order_count DESC
|
|
|
+ ) po ON ps.id = po.md_id
|
|
|
+ WHERE ps.del_flag='0' AND ps.off_shelf='0' AND ps.is_stall=1
|
|
|
+ AND ps.night_market_id=#{nightMarketId}
|
|
|
+ ORDER BY po.order_count DESC
|
|
|
+ LIMIT #{offset}, 20
|
|
|
+</select>
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 3: 在 NightMarketController 新增常购/关注接口**
|
|
|
+
|
|
|
+在 `NightMarketController` 中添加两个接口方法:
|
|
|
+
|
|
|
+```java
|
|
|
+/**
|
|
|
+ * 夜市关注的摊位列表
|
|
|
+ */
|
|
|
+@Anonymous
|
|
|
+@Auth
|
|
|
+@GetMapping("/followed")
|
|
|
+public AjaxResult followedStores(@RequestHeader String token,
|
|
|
+ @RequestParam Long nightMarketId,
|
|
|
+ @RequestParam BigDecimal longitude,
|
|
|
+ @RequestParam BigDecimal latitude,
|
|
|
+ @RequestParam(defaultValue = "1") Integer page,
|
|
|
+ @RequestParam(defaultValue = "zh-TW", required = false) String language) {
|
|
|
+ JwtUtil jwtUtil = new JwtUtil();
|
|
|
+ String userId = jwtUtil.getusid(token);
|
|
|
+ int offset = (page - 1) * 20;
|
|
|
+ List<PosStore> list = posStoreMapper.getFollowedStalls(longitude, latitude, nightMarketId, Long.valueOf(userId), offset);
|
|
|
+ posStoreEnrichService.enrichStoreList(list);
|
|
|
+ Page<StoreOutput> result = posStoreEnrichService.buildStoreOutputPage(list, language, page, list.size());
|
|
|
+ return success(result);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 夜市常购的摊位列表
|
|
|
+ */
|
|
|
+@Anonymous
|
|
|
+@Auth
|
|
|
+@GetMapping("/frequent")
|
|
|
+public AjaxResult frequentStores(@RequestHeader String token,
|
|
|
+ @RequestParam Long nightMarketId,
|
|
|
+ @RequestParam BigDecimal longitude,
|
|
|
+ @RequestParam BigDecimal latitude,
|
|
|
+ @RequestParam(defaultValue = "1") Integer page,
|
|
|
+ @RequestParam(defaultValue = "zh-TW", required = false) String language) {
|
|
|
+ JwtUtil jwtUtil = new JwtUtil();
|
|
|
+ String userId = jwtUtil.getusid(token);
|
|
|
+ int offset = (page - 1) * 20;
|
|
|
+ List<PosStore> list = posStoreMapper.getFrequentStalls(longitude, latitude, nightMarketId, Long.valueOf(userId), offset);
|
|
|
+ posStoreEnrichService.enrichStoreList(list);
|
|
|
+ Page<StoreOutput> result = posStoreEnrichService.buildStoreOutputPage(list, language, page, list.size());
|
|
|
+ return success(result);
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+别忘了在文件顶部增加 `@Autowired` 注入(如 Task 1 中已注入则跳过)。
|
|
|
+
|
|
|
+- [ ] **Step 4: 编译验证**
|
|
|
+
|
|
|
+Run: `cd E:\QtwCode\foodie\foodie_server && mvn compile -pl ruoyi-admin -am -q`
|
|
|
+Expected: BUILD SUCCESS
|
|
|
+
|
|
|
+- [ ] **Step 5: 提交**
|
|
|
+
|
|
|
+```bash
|
|
|
+git add ruoyi-admin/src/main/java/com/ruoyi/app/nightmarket/NightMarketController.java ruoyi-system/src/main/java/com/ruoyi/system/mapper/PosStoreMapper.java ruoyi-system/src/main/resources/mapper/PosStoreMapper.xml
|
|
|
+git commit -m "feat(nightmarket): 常购/关注摊位列表接口"
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### Task 3: 店铺详情(含商品分类列表)
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Modify: `ruoyi-admin/src/main/java/com/ruoyi/app/nightmarket/NightMarketController.java`
|
|
|
+- Modify: `ruoyi-system/src/main/java/com/ruoyi/system/mapper/PosFoodMapper.java`
|
|
|
+- Modify: `ruoyi-system/src/main/resources/mapper/PosFoodMapper.xml`(如果存在)
|
|
|
+
|
|
|
+- [ ] **Step 1: 在 PosFoodMapper.java 新增按店铺查商品方法**
|
|
|
+
|
|
|
+```java
|
|
|
+/**
|
|
|
+ * 按店铺查商品列表(审核通过、上架状态)
|
|
|
+ */
|
|
|
+List<PosFood> selectFoodsByStoreId(@Param("storeId") Long storeId,
|
|
|
+ @Param("language") String language);
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 2: 在 PosFoodMapper.xml 新增 SQL**
|
|
|
+
|
|
|
+找到 `PosFoodMapper.xml` 并添加:
|
|
|
+
|
|
|
+```xml
|
|
|
+<select id="selectFoodsByStoreId" resultMap="PosFoodResult">
|
|
|
+ SELECT id, fl_id, mdid, name, image, price, introduce, recommend, sort, language, stacking_up, to_examine
|
|
|
+ FROM pos_food
|
|
|
+ WHERE mdid = #{storeId}
|
|
|
+ AND to_examine = '1'
|
|
|
+ AND stacking_up = '0'
|
|
|
+ AND language = #{language}
|
|
|
+ ORDER BY sort ASC, id DESC
|
|
|
+</select>
|
|
|
+```
|
|
|
+
|
|
|
+如果 PosFoodMapper.xml 中没有 `PosFoodResult` resultMap,使用 `resultType="PosFood"` 代替。
|
|
|
+
|
|
|
+- [ ] **Step 3: 在 NightMarketController 新增店铺详情接口**
|
|
|
+
|
|
|
+在 `NightMarketController` 中新增必要的注入和详情方法:
|
|
|
+
|
|
|
+```java
|
|
|
+@Autowired
|
|
|
+private IPosStoreService posStoreService;
|
|
|
+
|
|
|
+@Autowired
|
|
|
+private IOperatingHoursService operatingHoursService;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 夜市摊位详情(含商品分类列表)
|
|
|
+ * @param storeId 摊位ID
|
|
|
+ * @param token 用户token(可选,用于判断是否已关注)
|
|
|
+ * @param language 语言
|
|
|
+ */
|
|
|
+@Anonymous
|
|
|
+@GetMapping("/store/detail")
|
|
|
+public AjaxResult storeDetail(@RequestParam Long storeId,
|
|
|
+ @RequestHeader(defaultValue = "") String token,
|
|
|
+ @RequestParam(defaultValue = "zh-TW", required = false) String language) {
|
|
|
+ PosStore store = posStoreService.getById(storeId);
|
|
|
+ if (store == null || !"0".equals(store.getDelFlag()) || store.getIsStall() == null || store.getIsStall() != 1) {
|
|
|
+ return error("摊位不存在");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 判断是否已关注
|
|
|
+ boolean collected = false;
|
|
|
+ if (token != null && !token.isEmpty()) {
|
|
|
+ try {
|
|
|
+ JwtUtil jwtUtil = new JwtUtil();
|
|
|
+ String userId = jwtUtil.getusid(token);
|
|
|
+ QueryWrapper<PosCollect> qw = new QueryWrapper<>();
|
|
|
+ qw.eq("user_id", userId).eq("md_id", storeId);
|
|
|
+ collected = posCollectService.count(qw) > 0;
|
|
|
+ } catch (Exception ignored) {}
|
|
|
+ }
|
|
|
+
|
|
|
+ // 评分
|
|
|
+ QueryWrapper<PosReview> reviewQw = new QueryWrapper<>();
|
|
|
+ reviewQw.eq("md_id", storeId);
|
|
|
+ List<PosReview> reviews = posReviewService.list(reviewQw);
|
|
|
+ double rating = reviews.isEmpty() ? 4.5d :
|
|
|
+ Math.round(reviews.stream().mapToDouble(PosReview::getScore).average().orElse(4.5d) * 2.0) / 2.0;
|
|
|
+
|
|
|
+ // 月售
|
|
|
+ QueryWrapper<PosOrder> orderQw = new QueryWrapper<>();
|
|
|
+ orderQw.eq("md_id", storeId).apply("DATE_FORMAT(cretim,'%Y-%m') = DATE_FORMAT(NOW(),'%Y-%m')");
|
|
|
+ long monthlySales = posOrderService.count(orderQw);
|
|
|
+
|
|
|
+ // 商品列表
|
|
|
+ String langCode = "zh-CN".equals(language) ? "2" : ("en-US".equals(language) ? "1" : "3");
|
|
|
+ List<PosFood> foods = posFoodMapper.selectFoodsByStoreId(storeId, langCode);
|
|
|
+
|
|
|
+ // 组装返回
|
|
|
+ com.alibaba.fastjson.JSONObject result = new com.alibaba.fastjson.JSONObject();
|
|
|
+ result.put("id", store.getId());
|
|
|
+ result.put("posName", store.getPosName());
|
|
|
+ result.put("image", store.getImage());
|
|
|
+ result.put("logo", store.getLogo());
|
|
|
+ result.put("address", store.getAddress());
|
|
|
+ result.put("longitude", store.getLongitude());
|
|
|
+ result.put("latitude", store.getLatitude());
|
|
|
+ result.put("briefIntroduction", store.getBriefIntroduction());
|
|
|
+ result.put("telephone", store.getTelephone());
|
|
|
+ result.put("openBusiness", store.getOpenBusiness());
|
|
|
+ result.put("windingUp", store.getWindingUp());
|
|
|
+ result.put("rating", rating);
|
|
|
+ result.put("monthlySales", monthlySales);
|
|
|
+ result.put("collected", collected ? 1 : 0);
|
|
|
+ result.put("foodList", foods);
|
|
|
+ result.put("nightMarketId", store.getNightMarketId());
|
|
|
+
|
|
|
+ return success(result);
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+需要在类顶部额外注入(如尚未注入):
|
|
|
+
|
|
|
+```java
|
|
|
+@Autowired
|
|
|
+private IPosReviewService posReviewService;
|
|
|
+
|
|
|
+@Autowired
|
|
|
+private PosFoodMapper posFoodMapper;
|
|
|
+```
|
|
|
+
|
|
|
+并确保有以下 import:
|
|
|
+
|
|
|
+```java
|
|
|
+import com.ruoyi.system.domain.PosFood;
|
|
|
+import com.ruoyi.system.domain.PosReview;
|
|
|
+import com.ruoyi.system.mapper.PosFoodMapper;
|
|
|
+import com.alibaba.fastjson.JSONObject;
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 4: 编译验证**
|
|
|
+
|
|
|
+Run: `cd E:\QtwCode\foodie\foodie_server && mvn compile -pl ruoyi-admin -am -q`
|
|
|
+Expected: BUILD SUCCESS
|
|
|
+
|
|
|
+- [ ] **Step 5: 提交**
|
|
|
+
|
|
|
+```bash
|
|
|
+git add ruoyi-admin/src/main/java/com/ruoyi/app/nightmarket/NightMarketController.java ruoyi-system/src/main/java/com/ruoyi/system/mapper/PosFoodMapper.java ruoyi-system/src/main/resources/mapper/PosFoodMapper.xml
|
|
|
+git commit -m "feat(nightmarket): 摊位详情接口(含商品分类列表、评分、关注状态)"
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## API Summary
|
|
|
+
|
|
|
+| Endpoint | Method | Auth | Description |
|
|
|
+|----------|--------|------|-------------|
|
|
|
+| `/nightmarket/stores` | GET | No | 夜市附近商家列表(支持搜索) |
|
|
|
+| `/nightmarket/followed` | GET | Yes | 关注的摊位列表 |
|
|
|
+| `/nightmarket/frequent` | GET | Yes | 常购的摊位列表 |
|
|
|
+| `/nightmarket/store/detail` | GET | Optional | 摊位详情(含商品) |
|
|
|
+
|
|
|
+### Request/Response
|
|
|
+
|
|
|
+**GET /nightmarket/stores**
|
|
|
+- Request: `nightMarketId`, `longitude`, `latitude`, `page`(默认1), `keyword`(可选), `language`(默认zh-TW)
|
|
|
+- Response: `Page<StoreOutput>` — 含摊位信息 + foodList
|
|
|
+
|
|
|
+**GET /nightmarket/followed**
|
|
|
+- Request: `token`(header), `nightMarketId`, `longitude`, `latitude`, `page`, `language`
|
|
|
+- Response: `Page<StoreOutput>` — 用户关注的摊位列表
|
|
|
+
|
|
|
+**GET /nightmarket/frequent**
|
|
|
+- Request: `token`(header), `nightMarketId`, `longitude`, `latitude`, `page`, `language`
|
|
|
+- Response: `Page<StoreOutput>` — 用户常购的摊位列表(按近30天下单次数排序)
|
|
|
+
|
|
|
+**GET /nightmarket/store/detail**
|
|
|
+- Request: `storeId`, `token`(header,可选), `language`(默认zh-TW)
|
|
|
+- Response: JSONObject — 摊位详情 + rating + monthlySales + collected + foodList
|