Ver código fonte

完成微信小程序接口 在APP端的feign调用

lutong 3 semanas atrás
pai
commit
7e6898f1d2

+ 38 - 4
alien-store/src/main/java/shop/alien/store/controller/PaymentController.java

@@ -10,10 +10,13 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.web.bind.annotation.CrossOrigin;
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 import shop.alien.entity.result.R;
+import shop.alien.store.feign.DiningServiceFeign;
 import shop.alien.store.strategy.payment.PaymentStrategyFactory;
 
+import javax.servlet.http.HttpServletRequest;
 import java.util.Map;
 
 /**
@@ -30,18 +33,43 @@ import java.util.Map;
 public class PaymentController {
 
     private final PaymentStrategyFactory paymentStrategyFactory;
+    private final DiningServiceFeign diningServiceFeign;
 
+    private static String authHeader(HttpServletRequest request) {
+        String h = request.getHeader("Authorization");
+        if (h == null || h.isEmpty()) {
+            h = request.getHeader("authorization");
+        }
+        return h;
+    }
 
     @ApiOperation("创建预支付订单")
     @ApiImplicitParams({
             @ApiImplicitParam(name = "price", value = "订单金额", required = true, paramType = "query", dataType = "String"),
             @ApiImplicitParam(name = "subject", value = "订单标题", required = true, paramType = "query", dataType = "String"),
-            @ApiImplicitParam(name = "payType", value = "支付类型(alipay:支付宝, wechatPay:微信支付)", required = true, paramType = "query", dataType = "String")
+            @ApiImplicitParam(name = "payType", value = "支付类型(alipay:支付宝, wechatPay:微信支付, wechatPayMininProgram:微信小程序点餐)", required = true, paramType = "query", dataType = "String")
     })
     @RequestMapping("/prePay")
-    public R prePay(String price, String subject, String payType) {
-        log.info("PaymentController:prePay, price: {}, subject: {}, payType: {}", price, subject, payType);
+    public R prePay(
+            HttpServletRequest request,
+            @RequestParam String price,
+            @RequestParam String subject,
+            @RequestParam String payType,
+            @RequestParam(required = false) String payer,
+            @RequestParam(required = false) String orderNo,
+            @RequestParam(required = false) Integer storeId,
+            @RequestParam(required = false) Integer couponId,
+            @RequestParam(required = false) Integer payerId,
+            @RequestParam(required = false) String tablewareFee,
+            @RequestParam(required = false) String discountAmount,
+            @RequestParam(required = false) String payAmount) {
+        log.info("PaymentController:prePay, price: {}, subject: {}, payType: {}, orderNo: {}, storeId: {}", price, subject, payType, orderNo, storeId);
         try {
+            if ("wechatPayMininProgram".equals(payType) && storeId != null && orderNo != null && payer != null) {
+                return diningServiceFeign.prePay(
+                        authHeader(request), price, subject, payType, payer, orderNo, storeId,
+                        couponId, payerId, tablewareFee, discountAmount, payAmount);
+            }
             return paymentStrategyFactory.getStrategy(payType).createPrePayOrder(price, subject);
         } catch (Exception e) {
             return R.fail(e.getMessage());
@@ -65,8 +93,14 @@ public class PaymentController {
      * @return 订单状态信息
      */
     @RequestMapping("/searchOrderByOutTradeNoPath")
-    public R searchOrderByOutTradeNoPath(String transactionId, String payType) {
+    public R searchOrderByOutTradeNoPath(
+            @RequestParam String transactionId,
+            @RequestParam String payType,
+            @RequestParam(required = false) Integer storeId) {
         try {
+            if ("wechatPayMininProgram".equals(payType) && storeId != null) {
+                return diningServiceFeign.searchOrderByOutTradeNoPath(transactionId, payType, storeId);
+            }
             return paymentStrategyFactory.getStrategy(payType).searchOrderByOutTradeNoPath(transactionId);
         } catch (Exception e) {
             return R.fail(e.getMessage());

+ 147 - 0
alien-store/src/main/java/shop/alien/store/controller/dining/DiningCouponPathProxyController.java

@@ -0,0 +1,147 @@
+package shop.alien.store.controller.dining;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+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.vo.LifeDiscountCouponVo;
+import shop.alien.store.feign.DiningServiceFeign;
+
+import javax.servlet.http.HttpServletRequest;
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 与微信小程序一致 {@code /dining/coupon},透传 alien-dining。
+ */
+@Slf4j
+@Api(tags = {"APP/统一网关-点餐优惠券(与小程序路径一致)"})
+@CrossOrigin
+@RestController
+@RequestMapping("/dining/coupon")
+@RequiredArgsConstructor
+public class DiningCouponPathProxyController {
+
+    private final DiningServiceFeign diningServiceFeign;
+
+    private String auth(HttpServletRequest request) {
+        String h = request.getHeader("Authorization");
+        if (h == null || h.isEmpty()) {
+            h = request.getHeader("authorization");
+        }
+        return h;
+    }
+
+    @GetMapping("/getUserCouponList")
+    public R<List<LifeDiscountCouponVo>> getUserCouponList(
+            HttpServletRequest request,
+            @RequestParam(defaultValue = "1") int page,
+            @RequestParam(defaultValue = "10") int size,
+            @RequestParam String tabType,
+            @RequestParam(required = false) Integer type,
+            @RequestParam(required = false) Integer couponType,
+            @RequestParam(required = false) String storeId) {
+        try {
+            return diningServiceFeign.couponGetUserCouponList(auth(request), page, size, tabType, type, couponType, storeId);
+        } catch (Exception e) {
+            log.error("getUserCouponList: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @GetMapping("/detail")
+    public R<LifeDiscountCouponVo> getCounponDetailById(
+            HttpServletRequest request,
+            @RequestParam("counponId") String counponId) {
+        try {
+            return diningServiceFeign.couponGetDetail(auth(request), counponId);
+        } catch (Exception e) {
+            log.error("coupon detail: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @GetMapping("/storeUsableList")
+    public R<Map<String, Object>> getStoreUserUsableCouponList(
+            HttpServletRequest request,
+            @RequestParam String storeId,
+            @RequestParam BigDecimal amount,
+            @RequestParam(required = false) Integer couponType) {
+        try {
+            return diningServiceFeign.couponStoreUsableList(auth(request), storeId, amount, couponType);
+        } catch (Exception e) {
+            log.error("storeUsableList: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @GetMapping("/userOwned")
+    public R<List<LifeDiscountCouponVo>> getUserOwnedCoupons(
+            @RequestParam(required = false) String storeId,
+            @RequestParam(required = false) BigDecimal amount) {
+        try {
+            return diningServiceFeign.couponUserOwned(storeId, amount);
+        } catch (Exception e) {
+            log.error("userOwned: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @GetMapping("/getStoreUserCouponList")
+    public R<List<LifeDiscountCouponVo>> getStoreUserCouponList(
+            HttpServletRequest request,
+            @RequestParam String storeId,
+            @RequestParam(required = false) Integer couponType) {
+        try {
+            return diningServiceFeign.couponGetStoreUserCouponList(auth(request), storeId, couponType);
+        } catch (Exception e) {
+            log.error("getStoreUserCouponList: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @GetMapping("/userOwnedByStore")
+    public R<List<LifeDiscountCouponVo>> getUserOwnedCouponsByStore(
+            @RequestParam(required = false) String storeId,
+            @RequestParam(required = false) Integer couponType) {
+        try {
+            return diningServiceFeign.couponUserOwnedByStore(storeId, couponType);
+        } catch (Exception e) {
+            log.error("userOwnedByStore: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("门店全部优惠券分页")
+    @GetMapping("/getStoreAllCouponList")
+    public R<IPage<LifeDiscountCouponVo>> getStoreAllCouponList(
+            HttpServletRequest request,
+            @RequestParam(defaultValue = "1") int page,
+            @RequestParam(defaultValue = "10") int size,
+            @RequestParam String storeId,
+            @RequestParam(required = false) String couponName,
+            @RequestParam String tab,
+            @RequestParam(defaultValue = "1") int couponsFromType,
+            @RequestParam(defaultValue = "1") int couponStatus,
+            @RequestParam(required = false) Integer couponType) {
+        try {
+            R<Page<LifeDiscountCouponVo>> r = diningServiceFeign.couponGetStoreAllCouponList(
+                    auth(request), page, size, storeId, couponName, tab, couponsFromType, couponStatus, couponType);
+            if (r == null) {
+                return R.fail("服务无响应");
+            }
+            if (!R.isSuccess(r)) {
+                return R.fail(r.getMsg());
+            }
+            return R.data(r.getData());
+        } catch (Exception e) {
+            log.error("getStoreAllCouponList: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+}

+ 116 - 0
alien-store/src/main/java/shop/alien/store/controller/dining/DiningUserPathProxyController.java

@@ -0,0 +1,116 @@
+package shop.alien.store.controller.dining;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+import shop.alien.entity.result.R;
+import shop.alien.store.feign.DiningServiceFeign;
+import shop.alien.store.feign.dto.DiningChangePhoneBody;
+import shop.alien.store.feign.dto.DiningProfileUpdateBody;
+import shop.alien.store.feign.dto.DiningVerifyTokenBody;
+import shop.alien.store.feign.dto.DiningWeChatLoginBody;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.validation.Valid;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 与微信小程序一致 {@code /dining/user},透传 alien-dining。
+ */
+@Slf4j
+@Api(tags = {"APP/统一网关-点餐用户(与小程序路径一致)"})
+@CrossOrigin
+@RestController
+@RequestMapping("/dining/user")
+@RequiredArgsConstructor
+public class DiningUserPathProxyController {
+
+    private final DiningServiceFeign diningServiceFeign;
+
+    private String auth(HttpServletRequest request) {
+        String h = request.getHeader("Authorization");
+        if (h == null || h.isEmpty()) {
+            h = request.getHeader("authorization");
+        }
+        return h;
+    }
+
+    @ApiOperation("微信登录(小程序;APP 如需请走统一用户体系映射后再发 token)")
+    @PostMapping("/wechatLogin")
+    public R<Object> wechatLogin(@Valid @RequestBody DiningWeChatLoginBody body) {
+        try {
+            return diningServiceFeign.wechatLogin(body);
+        } catch (Exception e) {
+            log.error("wechatLogin: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("当前用户信息")
+    @GetMapping("/getUserInfo")
+    public R<Object> getUserInfo(HttpServletRequest request) {
+        try {
+            return diningServiceFeign.getUserInfo(auth(request));
+        } catch (Exception e) {
+            log.error("getUserInfo: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("更新资料")
+    @PostMapping("/updateProfile")
+    public R<Object> updateProfile(HttpServletRequest request, @Valid @RequestBody DiningProfileUpdateBody body) {
+        try {
+            return diningServiceFeign.updateProfile(auth(request), body);
+        } catch (Exception e) {
+            log.error("updateProfile: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("更换手机号")
+    @PostMapping("/changePhone")
+    public R<Object> changePhone(HttpServletRequest request, @Valid @RequestBody DiningChangePhoneBody body) {
+        try {
+            return diningServiceFeign.changePhone(auth(request), body);
+        } catch (Exception e) {
+            log.error("changePhone: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("校验 Token")
+    @PostMapping("/verifyToken")
+    public R<Object> verifyToken(HttpServletRequest request, @RequestBody(required = false) DiningVerifyTokenBody dto) {
+        try {
+            Map<String, String> body = null;
+            if (dto != null && dto.getToken() != null) {
+                body = new HashMap<>(2);
+                body.put("token", dto.getToken());
+            }
+            return diningServiceFeign.verifyToken(auth(request), body);
+        } catch (Exception e) {
+            log.error("verifyToken: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("解析 Token(调试)")
+    @PostMapping("/parseToken")
+    public R<Object> parseToken(HttpServletRequest request, @RequestBody(required = false) DiningVerifyTokenBody dto) {
+        try {
+            Map<String, String> body = null;
+            if (dto != null && dto.getToken() != null) {
+                body = new HashMap<>(2);
+                body.put("token", dto.getToken());
+            }
+            return diningServiceFeign.parseToken(auth(request), body);
+        } catch (Exception e) {
+            log.error("parseToken: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+}

+ 208 - 0
alien-store/src/main/java/shop/alien/store/controller/dining/StoreDiningPathProxyController.java

@@ -0,0 +1,208 @@
+package shop.alien.store.controller.dining;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+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.vo.*;
+import shop.alien.store.feign.DiningServiceFeign;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.List;
+
+/**
+ * 与微信小程序一致的路径前缀 {@code /store/dining},透传 alien-dining。
+ */
+@Slf4j
+@Api(tags = {"APP/统一网关-点餐(与小程序路径一致)"})
+@CrossOrigin
+@RestController
+@RequestMapping("/store/dining")
+@RequiredArgsConstructor
+public class StoreDiningPathProxyController {
+
+    private final DiningServiceFeign diningServiceFeign;
+
+    private String auth(HttpServletRequest request) {
+        String h = request.getHeader("Authorization");
+        if (h == null || h.isEmpty()) {
+            h = request.getHeader("authorization");
+        }
+        return h;
+    }
+
+    @ApiOperation("查询餐桌是否处于就餐中")
+    @GetMapping("/table-dining-status")
+    public R<TableDiningStatusVO> getTableDiningStatus(@RequestParam Integer tableId) {
+        try {
+            return diningServiceFeign.getTableDiningStatus(tableId);
+        } catch (Exception e) {
+            log.error("getTableDiningStatus: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("获取点餐页面信息")
+    @GetMapping("/page-info")
+    public R<DiningPageInfoVO> getDiningPageInfo(
+            HttpServletRequest request,
+            @RequestParam Integer tableId,
+            @RequestParam(required = false) Integer dinerCount) {
+        try {
+            return diningServiceFeign.getDiningPageInfo(auth(request), tableId, dinerCount);
+        } catch (Exception e) {
+            log.error("getDiningPageInfo: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("搜索菜品")
+    @GetMapping("/search")
+    public R<List<CuisineListVO>> searchCuisines(
+            HttpServletRequest request,
+            @RequestParam Integer storeId,
+            @RequestParam(required = false) String keyword,
+            @RequestParam Integer tableId) {
+        try {
+            return diningServiceFeign.searchCuisines(auth(request), storeId, keyword, tableId);
+        } catch (Exception e) {
+            log.error("searchCuisines: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("按分类获取菜品列表")
+    @GetMapping("/cuisines")
+    public R<List<CuisineListVO>> getCuisinesByCategory(
+            HttpServletRequest request,
+            @RequestParam Integer storeId,
+            @RequestParam(required = false) Integer categoryId,
+            @RequestParam Integer tableId,
+            @RequestParam(defaultValue = "1") Integer page,
+            @RequestParam(defaultValue = "12") Integer size) {
+        try {
+            return diningServiceFeign.getCuisinesByCategory(auth(request), storeId, categoryId, tableId, page, size);
+        } catch (Exception e) {
+            log.error("getCuisinesByCategory: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("菜品详情")
+    @GetMapping("/cuisine/{cuisineId}")
+    public R<CuisineDetailVO> getCuisineDetail(
+            HttpServletRequest request,
+            @PathVariable Integer cuisineId,
+            @RequestParam Integer tableId) {
+        try {
+            return diningServiceFeign.getCuisineDetail(auth(request), cuisineId, tableId);
+        } catch (Exception e) {
+            log.error("getCuisineDetail: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("可领取的优惠券列表")
+    @GetMapping("/coupons/available")
+    public R<List<AvailableCouponVO>> getAvailableCoupons(HttpServletRequest request, @RequestParam Integer storeId) {
+        try {
+            return diningServiceFeign.getAvailableCoupons(auth(request), storeId);
+        } catch (Exception e) {
+            log.error("getAvailableCoupons: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("领取优惠券")
+    @PostMapping("/coupon/receive")
+    public R<Boolean> receiveCoupon(HttpServletRequest request, @RequestParam Integer couponId) {
+        try {
+            return diningServiceFeign.receiveCoupon(auth(request), couponId);
+        } catch (Exception e) {
+            log.error("receiveCoupon: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("订单确认页信息")
+    @GetMapping("/order/confirm")
+    public R<OrderConfirmVO> getOrderConfirmInfo(
+            HttpServletRequest request,
+            @RequestParam Integer tableId,
+            @RequestParam Integer dinerCount) {
+        try {
+            return diningServiceFeign.getOrderConfirmInfo(auth(request), tableId, dinerCount);
+        } catch (Exception e) {
+            log.error("getOrderConfirmInfo: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("锁定订单(同桌防并发)")
+    @PostMapping("/order/lock")
+    public R<Boolean> lockOrder(HttpServletRequest request, @RequestParam Integer tableId) {
+        try {
+            return diningServiceFeign.lockOrder(auth(request), tableId);
+        } catch (Exception e) {
+            log.error("lockOrder: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("解锁订单")
+    @PostMapping("/order/unlock")
+    public R<Boolean> unlockOrder(HttpServletRequest request, @RequestParam Integer tableId) {
+        try {
+            return diningServiceFeign.unlockOrder(auth(request), tableId);
+        } catch (Exception e) {
+            log.error("unlockOrder: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("检查订单锁定状态")
+    @GetMapping("/order/check-lock")
+    public R<Integer> checkOrderLock(HttpServletRequest request, @RequestParam Integer tableId) {
+        try {
+            return diningServiceFeign.checkOrderLock(auth(request), tableId);
+        } catch (Exception e) {
+            log.error("checkOrderLock: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("结算页信息")
+    @GetMapping("/order/settlement")
+    public R<OrderSettlementVO> getOrderSettlementInfo(HttpServletRequest request, @RequestParam Integer orderId) {
+        try {
+            return diningServiceFeign.getOrderSettlementInfo(auth(request), orderId);
+        } catch (Exception e) {
+            log.error("getOrderSettlementInfo: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("结算锁定")
+    @PostMapping("/order/settlement/lock")
+    public R<Boolean> lockSettlement(HttpServletRequest request, @RequestParam Integer orderId) {
+        try {
+            return diningServiceFeign.lockSettlement(auth(request), orderId);
+        } catch (Exception e) {
+            log.error("lockSettlement: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("结算解锁")
+    @PostMapping("/order/settlement/unlock")
+    public R<Boolean> unlockSettlement(HttpServletRequest request, @RequestParam Integer orderId) {
+        try {
+            return diningServiceFeign.unlockSettlement(auth(request), orderId);
+        } catch (Exception e) {
+            log.error("unlockSettlement: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+}

+ 87 - 0
alien-store/src/main/java/shop/alien/store/controller/dining/StoreInfoDiningPathProxyController.java

@@ -0,0 +1,87 @@
+package shop.alien.store.controller.dining;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+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.StoreCuisine;
+import shop.alien.entity.store.StoreCuisineCategory;
+import shop.alien.entity.store.StoreTable;
+import shop.alien.entity.store.dto.StoreInfoWithHomepageCuisinesDTO;
+import shop.alien.entity.store.vo.CategoryWithCuisinesVO;
+import shop.alien.store.feign.DiningServiceFeign;
+
+import java.util.List;
+
+/**
+ * 点餐场景下与小程序一致的 {@code /store/info/*},转发至 alien-dining(仅当 APP 只访问 store 时需要)。
+ */
+@Slf4j
+@Api(tags = {"APP/统一网关-门店菜品树(点餐,与小程序路径一致)"})
+@CrossOrigin
+@RestController
+@RequestMapping("/store/info")
+@RequiredArgsConstructor
+public class StoreInfoDiningPathProxyController {
+
+    private final DiningServiceFeign diningServiceFeign;
+
+    @ApiOperation("门店详情+首页价目(与小程序 GetStoreDetail 一致)")
+    @GetMapping("/detail/{storeId}")
+    public R<StoreInfoWithHomepageCuisinesDTO> getStoreDetail(@PathVariable Integer storeId) {
+        try {
+            return diningServiceFeign.getStoreInfoWithHomepageCuisines(storeId);
+        } catch (Exception e) {
+            log.error("store/info/detail: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("菜品种类列表(与小程序 GetStoreCategories 一致)")
+    @GetMapping("/categories")
+    public R<List<StoreCuisineCategory>> getCategories(@RequestParam Integer storeId) {
+        try {
+            return diningServiceFeign.getStoreInfoCategories(storeId);
+        } catch (Exception e) {
+            log.error("store/info/categories: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("某分类下菜品(与小程序 GetStoreCuisines 一致)")
+    @GetMapping("/cuisines")
+    public R<List<StoreCuisine>> getCuisines(@RequestParam Integer categoryId) {
+        try {
+            return diningServiceFeign.getStoreInfoCuisinesByCategoryId(categoryId);
+        } catch (Exception e) {
+            log.error("store/info/cuisines: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("桌台列表(扫码选桌等)")
+    @GetMapping("/tables")
+    public R<List<StoreTable>> getTables(@RequestParam Integer storeId) {
+        try {
+            return diningServiceFeign.getTablesByStoreId(storeId);
+        } catch (Exception e) {
+            log.error("store/info/tables: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("门店分类及分类下菜品(点餐页左侧+右侧)")
+    @GetMapping("/categories-with-cuisines")
+    public R<List<CategoryWithCuisinesVO>> getCategoriesWithCuisines(
+            @RequestParam Integer storeId,
+            @RequestParam(required = false) String keyword) {
+        try {
+            return diningServiceFeign.getCategoriesWithCuisinesByStoreId(storeId, keyword);
+        } catch (Exception e) {
+            log.error("categories-with-cuisines: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+}

+ 347 - 0
alien-store/src/main/java/shop/alien/store/controller/dining/StoreOrderPathProxyController.java

@@ -0,0 +1,347 @@
+package shop.alien.store.controller.dining;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreOrder;
+import shop.alien.entity.store.StoreOrderDetail;
+import shop.alien.entity.store.dto.AddCartItemDTO;
+import shop.alien.entity.store.dto.CartDTO;
+import shop.alien.entity.store.dto.ChangeTableDTO;
+import shop.alien.entity.store.dto.CreateOrderDTO;
+import shop.alien.entity.store.dto.UpdateOrderCouponDTO;
+import shop.alien.entity.store.vo.OrderChangeLogBatchVO;
+import shop.alien.entity.store.vo.OrderDetailWithChangeLogVO;
+import shop.alien.entity.store.vo.OrderInfoVO;
+import shop.alien.entity.store.vo.OrderSuccessVO;
+import shop.alien.entity.store.vo.StoreOrderPageVO;
+import shop.alien.store.feign.DiningServiceFeign;
+import shop.alien.store.service.dining.DiningSseProxyService;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.validation.Valid;
+import java.util.List;
+
+/**
+ * 与微信小程序一致的路径前缀 {@code /store/order},透传 alien-dining。
+ */
+@Slf4j
+@Api(tags = {"APP/统一网关-订单购物车(与小程序路径一致)"})
+@CrossOrigin
+@RestController
+@RequestMapping("/store/order")
+@RequiredArgsConstructor
+public class StoreOrderPathProxyController {
+
+    private final DiningServiceFeign diningServiceFeign;
+    private final DiningSseProxyService diningSseProxyService;
+
+    private String auth(HttpServletRequest request) {
+        String h = request.getHeader("Authorization");
+        if (h == null || h.isEmpty()) {
+            h = request.getHeader("authorization");
+        }
+        return h;
+    }
+
+    @ApiOperation("购物车")
+    @GetMapping("/cart/{tableId}")
+    public R<CartDTO> getCart(HttpServletRequest request, @PathVariable Integer tableId) {
+        try {
+            return diningServiceFeign.getCart(auth(request), tableId);
+        } catch (Exception e) {
+            log.error("getCart: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("加购")
+    @PostMapping("/cart/add")
+    public R<CartDTO> addCartItem(HttpServletRequest request, @Valid @RequestBody AddCartItemDTO dto) {
+        try {
+            return diningServiceFeign.addCartItem(auth(request), dto);
+        } catch (Exception e) {
+            log.error("addCartItem: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("更新购物车数量")
+    @PutMapping("/cart/update")
+    public R<CartDTO> updateCartItem(
+            HttpServletRequest request,
+            @RequestParam Integer tableId,
+            @RequestParam Integer cuisineId,
+            @RequestParam Integer quantity) {
+        try {
+            return diningServiceFeign.updateCartItem(auth(request), tableId, cuisineId, quantity);
+        } catch (Exception e) {
+            log.error("updateCartItem: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("删除购物车行")
+    @DeleteMapping("/cart/remove")
+    public R<CartDTO> removeCartItem(
+            HttpServletRequest request,
+            @RequestParam Integer tableId,
+            @RequestParam Integer cuisineId) {
+        try {
+            return diningServiceFeign.removeCartItem(auth(request), tableId, cuisineId);
+        } catch (Exception e) {
+            log.error("removeCartItem: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("清空购物车")
+    @DeleteMapping("/cart/clear")
+    public R<CartDTO> clearCart(HttpServletRequest request, @RequestParam Integer tableId) {
+        try {
+            return diningServiceFeign.clearCart(auth(request), tableId);
+        } catch (Exception e) {
+            log.error("clearCart: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("设置就餐人数(餐具)")
+    @PostMapping("/cart/set-diner-count")
+    public R<CartDTO> setDinerCount(
+            HttpServletRequest request,
+            @RequestParam Integer tableId,
+            @RequestParam Integer dinerCount) {
+        try {
+            return diningServiceFeign.setDinerCount(auth(request), tableId, dinerCount);
+        } catch (Exception e) {
+            log.error("setDinerCount: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("更新餐具数量")
+    @PutMapping("/cart/update-tableware")
+    public R<CartDTO> updateTablewareQuantity(
+            HttpServletRequest request,
+            @RequestParam Integer tableId,
+            @RequestParam(required = false) Integer quantity) {
+        try {
+            return diningServiceFeign.updateTablewareQuantity(auth(request), tableId, quantity);
+        } catch (Exception e) {
+            log.error("updateTablewareQuantity: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("创建订单")
+    @PostMapping("/create")
+    public R<OrderSuccessVO> createOrder(HttpServletRequest request, @Valid @RequestBody CreateOrderDTO dto) {
+        try {
+            return diningServiceFeign.createOrder(auth(request), dto);
+        } catch (Exception e) {
+            log.error("createOrder: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("支付订单(非微信预下单,见 dining 实现)")
+    @PostMapping("/pay/{orderId}")
+    public R<Object> payOrder(
+            HttpServletRequest request,
+            @PathVariable Integer orderId,
+            @RequestParam Integer payType) {
+        try {
+            return diningServiceFeign.payOrder(auth(request), orderId, payType);
+        } catch (Exception e) {
+            log.error("payOrder: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("取消订单")
+    @PostMapping("/cancel/{orderId}")
+    public R<Boolean> cancelOrder(HttpServletRequest request, @PathVariable Integer orderId) {
+        try {
+            return diningServiceFeign.cancelOrder(auth(request), orderId);
+        } catch (Exception e) {
+            log.error("cancelOrder: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("订单详情(含变更日志)")
+    @GetMapping("/detail/{orderId}")
+    public R<OrderDetailWithChangeLogVO> getOrderDetail(HttpServletRequest request, @PathVariable Integer orderId) {
+        try {
+            return diningServiceFeign.getOrderDetail(auth(request), orderId);
+        } catch (Exception e) {
+            log.error("getOrderDetail: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("订单明细行列表")
+    @GetMapping("/detail/list/{orderId}")
+    public R<List<StoreOrderDetail>> getOrderDetailList(HttpServletRequest request, @PathVariable Integer orderId) {
+        try {
+            return diningServiceFeign.getOrderDetailList(auth(request), orderId);
+        } catch (Exception e) {
+            log.error("getOrderDetailList: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("订单信息(结算等)")
+    @GetMapping("/info/{orderId}")
+    public R<OrderInfoVO> getOrderInfo(HttpServletRequest request, @PathVariable Integer orderId) {
+        try {
+            return diningServiceFeign.getOrderInfo(auth(request), orderId);
+        } catch (Exception e) {
+            log.error("getOrderInfo: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("变更记录")
+    @GetMapping("/change-log/{orderId}")
+    public R<List<OrderChangeLogBatchVO>> getOrderChangeLogs(HttpServletRequest request, @PathVariable Integer orderId) {
+        try {
+            return diningServiceFeign.getOrderChangeLogs(auth(request), orderId);
+        } catch (Exception e) {
+            log.error("getOrderChangeLogs: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("分页订单列表")
+    @GetMapping("/page")
+    public R<IPage<StoreOrderPageVO>> getOrderPage(
+            HttpServletRequest request,
+            @RequestParam(defaultValue = "1") Long current,
+            @RequestParam(defaultValue = "10") Long size,
+            @RequestParam(required = false) Integer storeId,
+            @RequestParam(required = false) Integer tableId,
+            @RequestParam(required = false) Integer orderStatus,
+            @RequestParam(required = false) String keyword) {
+        try {
+            R<Page<StoreOrderPageVO>> result = diningServiceFeign.getOrderPage(
+                    auth(request), current, size, storeId, tableId, orderStatus, keyword);
+            if (result == null) {
+                return R.fail("服务无响应");
+            }
+            if (!R.isSuccess(result)) {
+                return R.fail(result.getMsg());
+            }
+            return R.data(result.getData());
+        } catch (Exception e) {
+            log.error("getOrderPage: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("订单列表(旧)")
+    @GetMapping("/list")
+    public R<IPage<StoreOrderPageVO>> getOrderList(
+            HttpServletRequest request,
+            @RequestParam(defaultValue = "1") Integer page,
+            @RequestParam(defaultValue = "10") Integer size,
+            @RequestParam(required = false) Integer status) {
+        try {
+            return diningServiceFeign.getOrderList(auth(request), page, size, status);
+        } catch (Exception e) {
+            log.error("getOrderList: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("我的订单")
+    @GetMapping("/my-orders")
+    public R<IPage<StoreOrderPageVO>> getMyOrders(
+            HttpServletRequest request,
+            @RequestParam(defaultValue = "1") Long current,
+            @RequestParam(defaultValue = "10") Long size,
+            @RequestParam String type) {
+        try {
+            R<Page<StoreOrderPageVO>> result = diningServiceFeign.getMyOrders(auth(request), current, size, type);
+            if (result == null) {
+                return R.fail("服务无响应");
+            }
+            if (!R.isSuccess(result)) {
+                return R.fail(result.getMsg());
+            }
+            return R.data(result.getData());
+        } catch (Exception e) {
+            log.error("getMyOrders: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("换桌")
+    @PostMapping("/change-table")
+    public R<CartDTO> changeTable(HttpServletRequest request, @Valid @RequestBody ChangeTableDTO dto) {
+        try {
+            return diningServiceFeign.changeTable(auth(request), dto);
+        } catch (Exception e) {
+            log.error("changeTable: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation(value = "SSE", notes = "透传至 alien-dining 购物车推送")
+    @GetMapping(value = "/sse/{tableId}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
+    public SseEmitter sse(HttpServletRequest request, @PathVariable Integer tableId) {
+        return diningSseProxyService.proxyOrderSse(tableId, auth(request));
+    }
+
+    @ApiOperation("完成订单")
+    @PostMapping("/complete/{orderId}")
+    public R<Boolean> completeOrder(HttpServletRequest request, @PathVariable Integer orderId) {
+        try {
+            return diningServiceFeign.completeOrder(auth(request), orderId);
+        } catch (Exception e) {
+            log.error("completeOrder: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("商家完成订单")
+    @PostMapping("/complete-by-merchant/{orderId}")
+    public R<Boolean> completeOrderByMerchant(HttpServletRequest request, @PathVariable Integer orderId) {
+        try {
+            return diningServiceFeign.completeOrderByMerchant(auth(request), orderId);
+        } catch (Exception e) {
+            log.error("completeOrderByMerchant: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("更新订单优惠券")
+    @PostMapping("/update-coupon")
+    public R<StoreOrder> updateOrderCoupon(HttpServletRequest request, @Valid @RequestBody UpdateOrderCouponDTO dto) {
+        try {
+            return diningServiceFeign.updateOrderCoupon(auth(request), dto);
+        } catch (Exception e) {
+            log.error("updateOrderCoupon: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("管理员重置桌台(慎用)")
+    @PostMapping("/admin/reset-table/{tableId}")
+    public R<Boolean> resetTable(HttpServletRequest request, @PathVariable Integer tableId) {
+        try {
+            return diningServiceFeign.resetTable(auth(request), tableId);
+        } catch (Exception e) {
+            log.error("resetTable: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+}

+ 243 - 1
alien-store/src/main/java/shop/alien/store/feign/DiningServiceFeign.java

@@ -7,12 +7,25 @@ import org.springframework.http.MediaType;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreCuisine;
+import shop.alien.entity.store.StoreCuisineCategory;
+import shop.alien.entity.store.StoreOrder;
+import shop.alien.entity.store.StoreOrderDetail;
 import shop.alien.entity.store.StoreTable;
+import shop.alien.entity.store.dto.StoreInfoWithHomepageCuisinesDTO;
+import shop.alien.entity.store.dto.AddCartItemDTO;
 import shop.alien.entity.store.dto.CartDTO;
 import shop.alien.entity.store.dto.ChangeTableDTO;
+import shop.alien.entity.store.dto.CreateOrderDTO;
+import shop.alien.entity.store.dto.UpdateOrderCouponDTO;
 import shop.alien.entity.store.vo.*;
+import shop.alien.store.feign.dto.DiningChangePhoneBody;
+import shop.alien.store.feign.dto.DiningProfileUpdateBody;
+import shop.alien.store.feign.dto.DiningWeChatLoginBody;
 
+import java.math.BigDecimal;
 import java.util.List;
+import java.util.Map;
 
 /**
  * 点餐服务 Feign 客户端
@@ -63,7 +76,7 @@ public interface DiningServiceFeign {
     R<List<CuisineListVO>> searchCuisines(
             @RequestHeader(value = "Authorization", required = false) String authorization,
             @RequestParam("storeId") Integer storeId,
-            @RequestParam("keyword") String keyword,
+            @RequestParam(value = "keyword", required = false) String keyword,
             @RequestParam("tableId") Integer tableId);
 
     /**
@@ -112,6 +125,47 @@ public interface DiningServiceFeign {
             @RequestHeader(value = "Authorization", required = false) String authorization,
             @RequestParam("storeId") Integer storeId);
 
+    @PostMapping("/store/dining/coupon/receive")
+    R<Boolean> receiveCoupon(
+            @RequestHeader(value = "Authorization", required = false) String authorization,
+            @RequestParam("couponId") Integer couponId);
+
+    @GetMapping("/store/dining/order/confirm")
+    R<OrderConfirmVO> getOrderConfirmInfo(
+            @RequestHeader(value = "Authorization", required = false) String authorization,
+            @RequestParam("tableId") Integer tableId,
+            @RequestParam("dinerCount") Integer dinerCount);
+
+    @PostMapping("/store/dining/order/lock")
+    R<Boolean> lockOrder(
+            @RequestHeader(value = "Authorization", required = false) String authorization,
+            @RequestParam("tableId") Integer tableId);
+
+    @PostMapping("/store/dining/order/unlock")
+    R<Boolean> unlockOrder(
+            @RequestHeader(value = "Authorization", required = false) String authorization,
+            @RequestParam("tableId") Integer tableId);
+
+    @GetMapping("/store/dining/order/check-lock")
+    R<Integer> checkOrderLock(
+            @RequestHeader(value = "Authorization", required = false) String authorization,
+            @RequestParam("tableId") Integer tableId);
+
+    @GetMapping("/store/dining/order/settlement")
+    R<OrderSettlementVO> getOrderSettlementInfo(
+            @RequestHeader(value = "Authorization", required = false) String authorization,
+            @RequestParam("orderId") Integer orderId);
+
+    @PostMapping("/store/dining/order/settlement/lock")
+    R<Boolean> lockSettlement(
+            @RequestHeader(value = "Authorization", required = false) String authorization,
+            @RequestParam("orderId") Integer orderId);
+
+    @PostMapping("/store/dining/order/settlement/unlock")
+    R<Boolean> unlockSettlement(
+            @RequestHeader(value = "Authorization", required = false) String authorization,
+            @RequestParam("orderId") Integer orderId);
+
     // ==================== 订单相关接口 ====================
 
     /**
@@ -126,6 +180,89 @@ public interface DiningServiceFeign {
             @RequestHeader(value = "Authorization", required = false) String authorization,
             @PathVariable("tableId") Integer tableId);
 
+    @PostMapping("/store/order/cart/add")
+    R<CartDTO> addCartItem(
+            @RequestHeader(value = "Authorization", required = false) String authorization,
+            @RequestBody AddCartItemDTO dto);
+
+    @PutMapping("/store/order/cart/update")
+    R<CartDTO> updateCartItem(
+            @RequestHeader(value = "Authorization", required = false) String authorization,
+            @RequestParam("tableId") Integer tableId,
+            @RequestParam("cuisineId") Integer cuisineId,
+            @RequestParam("quantity") Integer quantity);
+
+    @DeleteMapping("/store/order/cart/remove")
+    R<CartDTO> removeCartItem(
+            @RequestHeader(value = "Authorization", required = false) String authorization,
+            @RequestParam("tableId") Integer tableId,
+            @RequestParam("cuisineId") Integer cuisineId);
+
+    @DeleteMapping("/store/order/cart/clear")
+    R<CartDTO> clearCart(
+            @RequestHeader(value = "Authorization", required = false) String authorization,
+            @RequestParam("tableId") Integer tableId);
+
+    @PostMapping("/store/order/cart/set-diner-count")
+    R<CartDTO> setDinerCount(
+            @RequestHeader(value = "Authorization", required = false) String authorization,
+            @RequestParam("tableId") Integer tableId,
+            @RequestParam("dinerCount") Integer dinerCount);
+
+    @PutMapping("/store/order/cart/update-tableware")
+    R<CartDTO> updateTablewareQuantity(
+            @RequestHeader(value = "Authorization", required = false) String authorization,
+            @RequestParam("tableId") Integer tableId,
+            @RequestParam(value = "quantity", required = false) Integer quantity);
+
+    @PostMapping("/store/order/create")
+    R<OrderSuccessVO> createOrder(
+            @RequestHeader(value = "Authorization", required = false) String authorization,
+            @RequestBody CreateOrderDTO dto);
+
+    @PostMapping("/store/order/pay/{orderId}")
+    R<Object> payOrder(
+            @RequestHeader(value = "Authorization", required = false) String authorization,
+            @PathVariable("orderId") Integer orderId,
+            @RequestParam("payType") Integer payType);
+
+    @PostMapping("/store/order/cancel/{orderId}")
+    R<Boolean> cancelOrder(
+            @RequestHeader(value = "Authorization", required = false) String authorization,
+            @PathVariable("orderId") Integer orderId);
+
+    @GetMapping("/store/order/info/{orderId}")
+    R<OrderInfoVO> getOrderInfo(
+            @RequestHeader(value = "Authorization", required = false) String authorization,
+            @PathVariable("orderId") Integer orderId);
+
+    @GetMapping("/store/order/detail/list/{orderId}")
+    R<List<StoreOrderDetail>> getOrderDetailList(
+            @RequestHeader(value = "Authorization", required = false) String authorization,
+            @PathVariable("orderId") Integer orderId);
+
+    @GetMapping("/store/order/my-orders")
+    R<Page<StoreOrderPageVO>> getMyOrders(
+            @RequestHeader(value = "Authorization", required = false) String authorization,
+            @RequestParam(value = "current", defaultValue = "1") Long current,
+            @RequestParam(value = "size", defaultValue = "10") Long size,
+            @RequestParam("type") String type);
+
+    @PostMapping("/store/order/complete/{orderId}")
+    R<Boolean> completeOrder(
+            @RequestHeader(value = "Authorization", required = false) String authorization,
+            @PathVariable("orderId") Integer orderId);
+
+    @PostMapping("/store/order/update-coupon")
+    R<StoreOrder> updateOrderCoupon(
+            @RequestHeader(value = "Authorization", required = false) String authorization,
+            @RequestBody UpdateOrderCouponDTO dto);
+
+    @PostMapping("/store/order/admin/reset-table/{tableId}")
+    R<Boolean> resetTable(
+            @RequestHeader(value = "Authorization", required = false) String authorization,
+            @PathVariable("tableId") Integer tableId);
+
     /**
      * 换桌(迁移购物车、未完成订单及关联数据,支持点餐未下单场景,无需订单号)
      *
@@ -227,8 +364,113 @@ public interface DiningServiceFeign {
     R<Object> getUserInfo(
             @RequestHeader(value = "Authorization", required = false) String authorization);
 
+    @PostMapping("/dining/user/wechatLogin")
+    R<Object> wechatLogin(@RequestBody DiningWeChatLoginBody body);
+
+    @PostMapping("/dining/user/updateProfile")
+    R<Object> updateProfile(
+            @RequestHeader(value = "Authorization", required = false) String authorization,
+            @RequestBody DiningProfileUpdateBody body);
+
+    @PostMapping("/dining/user/changePhone")
+    R<Object> changePhone(
+            @RequestHeader(value = "Authorization", required = false) String authorization,
+            @RequestBody DiningChangePhoneBody body);
+
+    @PostMapping("/dining/user/verifyToken")
+    R<Object> verifyToken(
+            @RequestHeader(value = "Authorization", required = false) String authorization,
+            @RequestBody(required = false) Map<String, String> body);
+
+    @PostMapping("/dining/user/parseToken")
+    R<Object> parseToken(
+            @RequestHeader(value = "Authorization", required = false) String authorization,
+            @RequestBody(required = false) Map<String, String> body);
+
+    @GetMapping("/dining/coupon/getUserCouponList")
+    R<List<LifeDiscountCouponVo>> couponGetUserCouponList(
+            @RequestHeader(value = "Authorization", required = false) String authorization,
+            @RequestParam(value = "page", defaultValue = "1") int page,
+            @RequestParam(value = "size", defaultValue = "10") int size,
+            @RequestParam("tabType") String tabType,
+            @RequestParam(value = "type", required = false) Integer type,
+            @RequestParam(value = "couponType", required = false) Integer couponType,
+            @RequestParam(value = "storeId", required = false) String storeId);
+
+    @GetMapping("/dining/coupon/detail")
+    R<LifeDiscountCouponVo> couponGetDetail(
+            @RequestHeader(value = "Authorization", required = false) String authorization,
+            @RequestParam("counponId") String counponId);
+
+    @GetMapping("/dining/coupon/storeUsableList")
+    R<Map<String, Object>> couponStoreUsableList(
+            @RequestHeader(value = "Authorization", required = false) String authorization,
+            @RequestParam("storeId") String storeId,
+            @RequestParam("amount") BigDecimal amount,
+            @RequestParam(value = "couponType", required = false) Integer couponType);
+
+    @GetMapping("/dining/coupon/userOwned")
+    R<List<LifeDiscountCouponVo>> couponUserOwned(
+            @RequestParam(value = "storeId", required = false) String storeId,
+            @RequestParam(value = "amount", required = false) BigDecimal amount);
+
+    @GetMapping("/dining/coupon/getStoreUserCouponList")
+    R<List<LifeDiscountCouponVo>> couponGetStoreUserCouponList(
+            @RequestHeader(value = "Authorization", required = false) String authorization,
+            @RequestParam("storeId") String storeId,
+            @RequestParam(value = "couponType", required = false) Integer couponType);
+
+    @GetMapping("/dining/coupon/userOwnedByStore")
+    R<List<LifeDiscountCouponVo>> couponUserOwnedByStore(
+            @RequestParam(value = "storeId", required = false) String storeId,
+            @RequestParam(value = "couponType", required = false) Integer couponType);
+
+    @GetMapping("/dining/coupon/getStoreAllCouponList")
+    R<Page<LifeDiscountCouponVo>> couponGetStoreAllCouponList(
+            @RequestHeader(value = "Authorization", required = false) String authorization,
+            @RequestParam(value = "page", defaultValue = "1") int page,
+            @RequestParam(value = "size", defaultValue = "10") int size,
+            @RequestParam("storeId") String storeId,
+            @RequestParam(value = "couponName", required = false) String couponName,
+            @RequestParam("tab") String tab,
+            @RequestParam(value = "couponsFromType", defaultValue = "1") int couponsFromType,
+            @RequestParam(value = "couponStatus", defaultValue = "1") int couponStatus,
+            @RequestParam(value = "couponType", required = false) Integer couponType);
+
+    // ==================== 支付(与点餐同域) ====================
+
+    @GetMapping("/payment/prePay")
+    R<Object> prePay(
+            @RequestHeader(value = "Authorization", required = false) String authorization,
+            @RequestParam("price") String price,
+            @RequestParam("subject") String subject,
+            @RequestParam("payType") String payType,
+            @RequestParam("payer") String payer,
+            @RequestParam("orderNo") String orderNo,
+            @RequestParam("storeId") Integer storeId,
+            @RequestParam(value = "couponId", required = false) Integer couponId,
+            @RequestParam(value = "payerId", required = false) Integer payerId,
+            @RequestParam(value = "tablewareFee", required = false) String tablewareFee,
+            @RequestParam(value = "discountAmount", required = false) String discountAmount,
+            @RequestParam(value = "payAmount", required = false) String payAmount);
+
+    @GetMapping("/payment/searchOrderByOutTradeNoPath")
+    R<Object> searchOrderByOutTradeNoPath(
+            @RequestParam("transactionId") String transactionId,
+            @RequestParam("payType") String payType,
+            @RequestParam("storeId") Integer storeId);
+
     // ==================== 门店信息接口 ====================
 
+    @GetMapping("/store/info/detail/{storeId}")
+    R<StoreInfoWithHomepageCuisinesDTO> getStoreInfoWithHomepageCuisines(@PathVariable("storeId") Integer storeId);
+
+    @GetMapping("/store/info/categories")
+    R<List<StoreCuisineCategory>> getStoreInfoCategories(@RequestParam("storeId") Integer storeId);
+
+    @GetMapping("/store/info/cuisines")
+    R<List<StoreCuisine>> getStoreInfoCuisinesByCategoryId(@RequestParam("categoryId") Integer categoryId);
+
     /**
      * 根据门店ID查询桌号列表
      *

+ 27 - 0
alien-store/src/main/java/shop/alien/store/feign/dto/DiningChangePhoneBody.java

@@ -0,0 +1,27 @@
+package shop.alien.store.feign.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Pattern;
+
+@Data
+@ApiModel("点餐用户:更换手机号")
+public class DiningChangePhoneBody {
+
+    @ApiModelProperty(required = true)
+    @NotNull(message = "用户ID不能为空")
+    private Long userId;
+
+    @ApiModelProperty(required = true)
+    @NotBlank(message = "新手机号不能为空")
+    @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
+    private String newPhone;
+
+    @ApiModelProperty(required = true)
+    @NotBlank(message = "验证码不能为空")
+    private String code;
+}

+ 32 - 0
alien-store/src/main/java/shop/alien/store/feign/dto/DiningProfileUpdateBody.java

@@ -0,0 +1,32 @@
+package shop.alien.store.feign.dto;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+import java.util.Date;
+
+@Data
+@ApiModel("点餐用户:更新资料")
+public class DiningProfileUpdateBody {
+
+    @ApiModelProperty(value = "用户ID(透传 dining;服务端以 token 为准)", required = true)
+    @NotNull(message = "用户ID不能为空")
+    private Long userId;
+
+    private String nickName;
+    private String avatarUrl;
+    private String gender;
+
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    private Date birthday;
+
+    private String realName;
+    private String province;
+    private String city;
+    private String district;
+    private String address;
+    private String jianjie;
+}

+ 13 - 0
alien-store/src/main/java/shop/alien/store/feign/dto/DiningVerifyTokenBody.java

@@ -0,0 +1,13 @@
+package shop.alien.store.feign.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@Data
+@ApiModel("Token 校验")
+public class DiningVerifyTokenBody {
+
+    @ApiModelProperty("不传则从请求头 Authorization 读取")
+    private String token;
+}

+ 19 - 0
alien-store/src/main/java/shop/alien/store/feign/dto/DiningWeChatLoginBody.java

@@ -0,0 +1,19 @@
+package shop.alien.store.feign.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+
+@Data
+@ApiModel("微信小程序:点餐登录 code")
+public class DiningWeChatLoginBody {
+
+    @ApiModelProperty(value = "wx.login() 的 code", required = true)
+    @NotBlank(message = "code不能为空")
+    private String code;
+
+    @ApiModelProperty("wx.getPhoneNumber 的 phoneCode,可选")
+    private String phoneCode;
+}

+ 90 - 0
alien-store/src/main/java/shop/alien/store/service/dining/DiningSseProxyService.java

@@ -0,0 +1,90 @@
+package shop.alien.store.service.dining;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * 将 APP/网关对 alien-store 的 SSE 请求透传到 alien-dining(Feign 不适合流式响应)。
+ */
+@Slf4j
+@Service
+public class DiningSseProxyService {
+
+    private static final ExecutorService EXECUTOR = Executors.newCachedThreadPool(r -> {
+        Thread t = new Thread(r, "dining-sse-proxy");
+        t.setDaemon(true);
+        return t;
+    });
+
+    @Value("${feign.alienDining.url:}")
+    private String diningBaseUrl;
+
+    public SseEmitter proxyOrderSse(Integer tableId, String authorization) {
+        SseEmitter emitter = new SseEmitter(0L);
+        String base = diningBaseUrl == null ? "" : diningBaseUrl.trim();
+        if (base.isEmpty()) {
+            emitter.completeWithError(new IllegalStateException("未配置 feign.alienDining.url,无法透传点餐 SSE"));
+            return emitter;
+        }
+        if (base.endsWith("/")) {
+            base = base.substring(0, base.length() - 1);
+        }
+        final String urlStr = base + "/store/order/sse/" + tableId;
+        EXECUTOR.execute(() -> {
+            HttpURLConnection conn = null;
+            try {
+                URL url = new URL(urlStr);
+                conn = (HttpURLConnection) url.openConnection();
+                conn.setRequestMethod("GET");
+                conn.setRequestProperty("Accept", "text/event-stream");
+                if (authorization != null && !authorization.isEmpty()) {
+                    conn.setRequestProperty("Authorization", authorization);
+                }
+                conn.setConnectTimeout(30000);
+                conn.setReadTimeout(0);
+                conn.connect();
+                try (BufferedReader reader = new BufferedReader(
+                        new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
+                    String line;
+                    StringBuilder dataBuf = new StringBuilder();
+                    while ((line = reader.readLine()) != null) {
+                        if (line.startsWith("data:")) {
+                            String payload = line.length() > 5 ? line.substring(5).trim() : "";
+                            if (dataBuf.length() > 0) {
+                                dataBuf.append('\n');
+                            }
+                            dataBuf.append(payload);
+                        } else if (line.isEmpty()) {
+                            if (dataBuf.length() > 0) {
+                                emitter.send(SseEmitter.event().data(dataBuf.toString()));
+                                dataBuf.setLength(0);
+                            }
+                        }
+                    }
+                }
+                emitter.complete();
+            } catch (Exception e) {
+                log.warn("点餐 SSE 透传结束: tableId={}, {}", tableId, e.getMessage());
+                try {
+                    emitter.completeWithError(e);
+                } catch (Exception ignored) {
+                }
+            } finally {
+                if (conn != null) {
+                    conn.disconnect();
+                }
+            }
+        });
+        return emitter;
+    }
+}