Browse Source

complete pay ok

huodongdong 7 years ago
parent
commit
1fe47abcc0
63 changed files with 1164 additions and 161 deletions
  1. 1 0
      pom.xml
  2. 20 3
      rankin-cms-web/src/main/java/cn/rankin/cmsweb/controller/trade/OrderController.java
  3. 5 5
      rankin-cms-web/src/main/java/cn/rankin/cmsweb/controller/user/MerchantController.java
  4. 0 107
      rankin-cms-web/src/main/java/cn/rankin/cmsweb/service/trade/Order/OrderService.java
  5. 0 20
      rankin-cms-web/src/main/java/cn/rankin/cmsweb/service/trade/Order/OrderServiceFeign.java
  6. 330 0
      rankin-cms-web/src/main/java/cn/rankin/cmsweb/service/trade/order/OrderService.java
  7. 31 0
      rankin-cms-web/src/main/java/cn/rankin/cmsweb/service/trade/order/OrderServiceFeign.java
  8. 7 0
      rankin-cms-web/src/main/java/cn/rankin/cmsweb/service/user/CampusService.java
  9. 4 0
      rankin-cms-web/src/main/java/cn/rankin/cmsweb/service/user/MerchantService.java
  10. 10 0
      rankin-cms-web/src/main/java/cn/rankin/cmsweb/service/user/TerminalUserService.java
  11. 3 0
      rankin-common-utils/src/main/java/cn/rankin/common/utils/api/model/APICode.java
  12. 20 0
      rankin-common-utils/src/main/java/cn/rankin/common/utils/enums/LedgerTypeEnum.java
  13. 2 1
      rankin-common-utils/src/main/java/cn/rankin/common/utils/enums/OrderStatusEnum.java
  14. 7 2
      rankin-common-utils/src/main/java/cn/rankin/common/utils/jpa/BasicJpaRepository.java
  15. 7 3
      rankin-common-utils/src/main/java/cn/rankin/common/utils/jpa/SimpleJpaRepository.java
  16. 1 1
      rankin-common-utils/src/main/java/cn/rankin/common/utils/util/ListUtil.java
  17. 28 0
      rankin-data-api/src/main/java/cn/rankin/data/api/cms/OrderSearchWebDTO.java
  18. 5 0
      rankin-data-api/src/main/java/cn/rankin/data/api/product/entity/Course.java
  19. 3 0
      rankin-data-api/src/main/java/cn/rankin/data/api/product/entity/Product.java
  20. 3 0
      rankin-data-api/src/main/java/cn/rankin/data/api/product/entity/Support.java
  21. 2 0
      rankin-data-api/src/main/java/cn/rankin/data/api/product/vo/CourseVo.java
  22. 4 2
      rankin-data-api/src/main/java/cn/rankin/data/api/product/vo/ProductVo.java
  23. 1 1
      rankin-data-api/src/main/java/cn/rankin/data/api/product/vo/RecommendVo.java
  24. 4 0
      rankin-data-api/src/main/java/cn/rankin/data/api/product/vo/SupportVo.java
  25. 26 0
      rankin-data-api/src/main/java/cn/rankin/data/api/trade/dto/OrderSearchDTO.java
  26. 10 0
      rankin-data-api/src/main/java/cn/rankin/data/api/trade/entity/ProductSoldSnapshot.java
  27. 10 0
      rankin-data-api/src/main/java/cn/rankin/data/api/trade/vo/UserOrderVo.java
  28. 2 0
      rankin-data-api/src/main/java/cn/rankin/data/api/user/dto/CampusSearchDTO.java
  29. 21 0
      rankin-data-api/src/main/java/cn/rankin/data/api/user/dto/MerchantPayDTO.java
  30. 0 1
      rankin-data-api/src/main/java/cn/rankin/data/api/user/dto/TerminalUserDTO.java
  31. 1 1
      rankin-data-api/src/main/java/cn/rankin/data/api/user/entity/Campus.java
  32. 1 0
      rankin-data-api/src/main/java/cn/rankin/data/api/user/entity/CmsUser.java
  33. 58 0
      rankin-data-api/src/main/java/cn/rankin/data/api/user/entity/Ledger.java
  34. 3 0
      rankin-data-api/src/main/java/cn/rankin/data/api/user/entity/Merchant.java
  35. 3 3
      rankin-data-api/src/main/java/cn/rankin/data/api/user/entity/TerminalUser.java
  36. 1 0
      rankin-data-api/src/main/java/cn/rankin/data/api/user/vo/CmsUserVo.java
  37. 2 0
      rankin-data-api/src/main/java/cn/rankin/data/api/user/vo/TerminalUserVo.java
  38. 1 1
      rankin-product-service/src/main/java/cn/rankin/productservice/service/ProductService.java
  39. 102 0
      rankin-task/pom.xml
  40. 18 0
      rankin-task/src/main/java/cn/rankin/task/RankinTaskApplication.java
  41. 0 0
      rankin-task/src/main/resources/application.properties
  42. 16 0
      rankin-task/src/test/java/cn/rankin/task/RankinTaskApplicationTests.java
  43. 8 0
      rankin-trade-service/src/main/java/cn/rankin/tradeservice/code/TradeServiceAPICode.java
  44. 30 0
      rankin-trade-service/src/main/java/cn/rankin/tradeservice/controller/OrderController.java
  45. 22 0
      rankin-trade-service/src/main/java/cn/rankin/tradeservice/repository/OrderRepository.java
  46. 7 0
      rankin-trade-service/src/main/java/cn/rankin/tradeservice/repository/ProductSoldSnapshotRepository.java
  47. 95 0
      rankin-trade-service/src/main/java/cn/rankin/tradeservice/service/OrderService.java
  48. 13 0
      rankin-trade-service/src/main/java/cn/rankin/tradeservice/service/ProductSoldSnapshotService.java
  49. 27 0
      rankin-trade-service/src/test/java/cn/rankin/tradeservice/service/OrderServiceTest.java
  50. 6 1
      rankin-user-service/src/main/java/cn/rankin/userservice/code/UserServiceAPICode.java
  51. 35 4
      rankin-user-service/src/main/java/cn/rankin/userservice/controller/CampusController.java
  52. 1 1
      rankin-user-service/src/main/java/cn/rankin/userservice/controller/CmsUserController.java
  53. 7 0
      rankin-user-service/src/main/java/cn/rankin/userservice/controller/MerchantController.java
  54. 41 2
      rankin-user-service/src/main/java/cn/rankin/userservice/controller/TerminalUserController.java
  55. 7 0
      rankin-user-service/src/main/java/cn/rankin/userservice/repository/LedgerRepository.java
  56. 5 0
      rankin-user-service/src/main/java/cn/rankin/userservice/repository/MerchantRepository.java
  57. 4 0
      rankin-user-service/src/main/java/cn/rankin/userservice/repository/TerminalUserRepository.java
  58. 5 0
      rankin-user-service/src/main/java/cn/rankin/userservice/service/CampusService.java
  59. 10 0
      rankin-user-service/src/main/java/cn/rankin/userservice/service/CmsUserService.java
  60. 25 0
      rankin-user-service/src/main/java/cn/rankin/userservice/service/LedgerService.java
  61. 27 0
      rankin-user-service/src/main/java/cn/rankin/userservice/service/MerchantService.java
  62. 15 1
      rankin-user-service/src/main/java/cn/rankin/userservice/service/TerminalUserService.java
  63. 1 1
      rankin-user-service/src/test/java/cn/rankin/userservice/repository/CampusRepositoryTest.java

+ 1 - 0
pom.xml

@@ -37,6 +37,7 @@
         <module>rankin-cms-gateway</module>
         <module>rankin-common-utils</module>
         <module>rankin-data-api</module>
+        <module>rankin-task</module>
     </modules>
 
 	<dependencies>

+ 20 - 3
rankin-cms-web/src/main/java/cn/rankin/cmsweb/controller/trade/OrderController.java

@@ -1,7 +1,9 @@
 package cn.rankin.cmsweb.controller.trade;
 
-import cn.rankin.cmsweb.service.trade.Order.OrderService;
+import cn.rankin.cmsweb.service.trade.order.OrderService;
 import cn.rankin.common.utils.api.model.APIResult;
+import cn.rankin.common.utils.api.page.Page;
+import cn.rankin.data.api.cms.OrderSearchWebDTO;
 import cn.rankin.data.api.trade.dto.UserOrderDTO;
 import cn.rankin.data.api.trade.vo.UserOrderVo;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -19,8 +21,23 @@ public class OrderController {
         return orderService.create(userOrderDTO);
     }
 
-    @RequestMapping(value = "/{goodId}", method = RequestMethod.GET)
+    @RequestMapping(value = "/{orderId}", method = RequestMethod.GET)
     public APIResult<UserOrderVo> getOrder(@PathVariable String orderId) {
-        return null;
+        return orderService.getOrder(orderId);
+    }
+
+    @RequestMapping(value = {"/list", ""}, method = RequestMethod.GET)
+    public APIResult<Page<UserOrderVo>> search(OrderSearchWebDTO searchDTO) {
+        return orderService.search(searchDTO);
+    }
+
+    @RequestMapping(value = "/pay/{orderId}", method = RequestMethod.POST)
+    public APIResult<Boolean> pay(@PathVariable("orderId") String orderId) {
+        return orderService.pay(orderId);
+    }
+
+    @RequestMapping(value = "/pay/{orderId}", method = RequestMethod.DELETE)
+    public APIResult<Boolean> cancel(@PathVariable("orderId") String orderId) {
+        return orderService.cancel(orderId);
     }
 }

+ 5 - 5
rankin-cms-web/src/main/java/cn/rankin/cmsweb/controller/user/MerchantController.java

@@ -36,7 +36,7 @@ public class MerchantController {
     }
 
     @RequestMapping(method = RequestMethod.PUT)
-    public APIResult<MerchantVo> update(@RequestBody @Valid MerchantDTO merchantDTO) {
+    public APIResult<MerchantVo> update(@RequestBody MerchantDTO merchantDTO) {
         return merchantService.update(merchantDTO);
     }
 
@@ -51,13 +51,13 @@ public class MerchantController {
     }
 
     // 推荐位设置
-    @RequestMapping(value = "/recommend", method = RequestMethod.GET)
-    public APIResult<List<RecommendVo>> getRecommend(@RequestParam("merchantId") String merchantId) {
+    @RequestMapping(value = "/recommend/{merchantId}", method = RequestMethod.GET)
+    public APIResult<List<RecommendVo>> getRecommend(@PathVariable("merchantId") String merchantId) {
         return recommendService.get(merchantId);
     }
 
-    @RequestMapping(value = "/recommend", method = RequestMethod.PUT)
-    public APIResult<List<RecommendVo>> updateRecommend(@RequestParam("merchantId") String merchantId, @RequestBody List<String> recommendIdList) {
+    @RequestMapping(value = "/recommend/{merchantId}", method = RequestMethod.PUT)
+    public APIResult<List<RecommendVo>> updateRecommend(@PathVariable("merchantId") String merchantId, @RequestBody List<String> recommendIdList) {
         return recommendService.put(merchantId, recommendIdList);
     }
 

+ 0 - 107
rankin-cms-web/src/main/java/cn/rankin/cmsweb/service/trade/Order/OrderService.java

@@ -1,107 +0,0 @@
-package cn.rankin.cmsweb.service.trade.Order;
-
-import cn.rankin.cmsweb.code.CmsWebAPICode;
-import cn.rankin.cmsweb.service.product.GoodsService;
-import cn.rankin.cmsweb.service.user.TerminalUserService;
-import cn.rankin.common.utils.api.model.APIResult;
-import cn.rankin.common.utils.api.model.BaseCode;
-import cn.rankin.data.api.product.vo.GoodsVo;
-import cn.rankin.data.api.trade.dto.UserOrderDTO;
-import cn.rankin.data.api.trade.dto.OrderGoodsDTO;
-import cn.rankin.data.api.trade.entity.OrderGoods;
-import cn.rankin.data.api.trade.entity.UserOrder;
-import cn.rankin.data.api.trade.vo.UserOrderVo;
-import cn.rankin.data.api.user.vo.TerminalUserVo;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-import org.springframework.util.CollectionUtils;
-
-import java.math.BigDecimal;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-@Service
-public class OrderService {
-
-    @Autowired
-    private OrderServiceFeign orderServiceFeign;
-
-    @Autowired
-    private GoodsService goodsService;
-
-    @Autowired
-    private TerminalUserService terminalUserService;
-
-    public APIResult<UserOrderVo> create(UserOrderDTO userOrderDTO) {
-        String uid = userOrderDTO.getUid();
-        APIResult<TerminalUserVo> userVoAPIResult = terminalUserService.getUser(uid);
-        if (!userVoAPIResult.getSuccess()) {
-            return APIResult.error(CmsWebAPICode.error("用户不存在"));
-        }
-
-        TerminalUserVo terminalUserVo = userVoAPIResult.getData();
-
-        List<OrderGoodsDTO> orderGoodsDTOList = userOrderDTO.getGoods();
-        if (CollectionUtils.isEmpty(orderGoodsDTOList)) {
-            return APIResult.error(CmsWebAPICode.error("商品为空"));
-        }
-
-        List<String> goodsIdList = new ArrayList<>();
-        Map<String, Integer> goodsCountMap = new HashMap<>();
-        orderGoodsDTOList.forEach(goods -> {
-            String goodsId = goods.getGoodsId();
-            goodsIdList.add(goodsId);
-            goodsCountMap.put(goodsId, goods.getQuantity());
-        });
-
-        APIResult<List<GoodsVo>> goodsVoAPIResult = goodsService.findByIds(goodsIdList);
-        if (!goodsVoAPIResult.getSuccess()) {
-            return APIResult.error(CmsWebAPICode.error("查询商品错误"));
-        }
-
-        List<GoodsVo> goodsVoList = goodsVoAPIResult.getData();
-        if (goodsVoList.size() != orderGoodsDTOList.size()) {
-            return APIResult.error(CmsWebAPICode.error("包含不可售卖的商品"));
-        }
-
-        BigDecimal totalPrice = BigDecimal.ZERO;
-        List<OrderGoodsDTO> finalGoodsList = new ArrayList<>();
-        for (GoodsVo goodsVo : goodsVoList) {
-            String goodsMerchantId = goodsVo.getMerchantId();
-            if (!terminalUserVo.getMerchantId().equals(goodsMerchantId)) {
-                return APIResult.error(CmsWebAPICode.error("不可以购买其他渠道商品"));
-            }
-            String goodsId = goodsVo.getId();
-            OrderGoodsDTO orderGoodsDTO = new OrderGoodsDTO();
-            orderGoodsDTO.setGoodsId(goodsId);
-            orderGoodsDTO.setPid(goodsVo.getPid());
-            orderGoodsDTO.setType(goodsVo.getType());
-            orderGoodsDTO.setQuantity(goodsCountMap.get(goodsId));
-            finalGoodsList.add(orderGoodsDTO);
-            totalPrice = totalPrice.add(goodsVo.getMerchantPrice());
-        }
-        userOrderDTO.setFinalPrice(totalPrice.subtract(userOrderDTO.getAdjustPrice()));
-        userOrderDTO.setOriginPrice(totalPrice);
-        userOrderDTO.setGoods(finalGoodsList);
-
-        APIResult userOrderAPIResult = orderServiceFeign.create(userOrderDTO);
-        return userOrderAPIResult;
-    }
-
-    public APIResult<UserOrderVo> getOrder(String orderId) {
-        APIResult<UserOrder> userOrderVoAPIResult = orderServiceFeign.getOrder(orderId);
-        if (!userOrderVoAPIResult.getSuccess()) {
-            return APIResult.error(new BaseCode(userOrderVoAPIResult.getCode(), userOrderVoAPIResult.getMessage()));
-        }
-
-        UserOrder userOrder = userOrderVoAPIResult.getData();
-        List<OrderGoods> orderGoodsList = userOrder.getGoods();
-        if (CollectionUtils.isEmpty(orderGoodsList)) {
-            return APIResult.ok();
-        }
-        return null;
-
-    }
-}

+ 0 - 20
rankin-cms-web/src/main/java/cn/rankin/cmsweb/service/trade/Order/OrderServiceFeign.java

@@ -1,20 +0,0 @@
-package cn.rankin.cmsweb.service.trade.Order;
-
-import cn.rankin.common.utils.api.model.APIResult;
-import cn.rankin.data.api.trade.dto.UserOrderDTO;
-import cn.rankin.data.api.trade.entity.UserOrder;
-import org.springframework.cloud.netflix.feign.FeignClient;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestMethod;
-
-@FeignClient(name = "${service.trade.name}")
-public interface OrderServiceFeign {
-
-    @RequestMapping(value = "/order", method = RequestMethod.POST)
-    APIResult<UserOrder> create(@RequestBody UserOrderDTO userOrderDTO);
-
-    @RequestMapping(value = "/order/{orderId}", method = RequestMethod.GET)
-    APIResult<UserOrder> getOrder(@PathVariable("orderId") String orderId);
-}

+ 330 - 0
rankin-cms-web/src/main/java/cn/rankin/cmsweb/service/trade/order/OrderService.java

@@ -0,0 +1,330 @@
+package cn.rankin.cmsweb.service.trade.order;
+
+import cn.rankin.cmsweb.code.CmsWebAPICode;
+import cn.rankin.cmsweb.service.product.GoodsService;
+import cn.rankin.cmsweb.service.user.CampusService;
+import cn.rankin.cmsweb.service.user.MerchantService;
+import cn.rankin.cmsweb.service.user.TerminalUserService;
+import cn.rankin.common.utils.api.model.APIResult;
+import cn.rankin.common.utils.api.model.BaseCode;
+import cn.rankin.common.utils.api.page.Page;
+import cn.rankin.common.utils.enums.LedgerTypeEnum;
+import cn.rankin.common.utils.enums.OrderStatusEnum;
+import cn.rankin.common.utils.enums.ProductTypeEnum;
+import cn.rankin.common.utils.util.BeanUtil;
+import cn.rankin.data.api.cms.OrderSearchWebDTO;
+import cn.rankin.data.api.product.vo.GoodsVo;
+import cn.rankin.data.api.trade.dto.UserOrderDTO;
+import cn.rankin.data.api.trade.dto.OrderGoodsDTO;
+import cn.rankin.data.api.trade.entity.OrderGoods;
+import cn.rankin.data.api.trade.entity.UserOrder;
+import cn.rankin.data.api.trade.vo.UserOrderVo;
+import cn.rankin.data.api.user.dto.CampusSearchDTO;
+import cn.rankin.data.api.user.dto.MerchantPayDTO;
+import cn.rankin.data.api.user.vo.CampusVo;
+import cn.rankin.data.api.user.vo.TerminalUserVo;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static cn.rankin.cmsweb.utils.DTOConverter.convert;
+
+@Slf4j
+@Service
+public class OrderService {
+
+    @Autowired
+    private OrderServiceFeign orderServiceFeign;
+
+    @Autowired
+    private GoodsService goodsService;
+
+    @Autowired
+    private TerminalUserService terminalUserService;
+
+    @Autowired
+    private CampusService campusService;
+
+    @Autowired
+    private MerchantService merchantService;
+
+    public APIResult<Boolean> pay(String orderId) {
+        APIResult<UserOrder> userOrderAPIResult = orderServiceFeign.getOrder(orderId);
+        UserOrder userOrder = userOrderAPIResult.getData();
+        if (userOrder == null || !userOrderAPIResult.getSuccess()) {
+            log.error("查询订单错误, orderId={}, {}", orderId, userOrderAPIResult.getMessage());
+            return APIResult.error(new BaseCode(userOrderAPIResult.getCode(), userOrderAPIResult.getMessage()));
+        }
+
+        String uid = userOrder.getUid();
+        APIResult<TerminalUserVo> userVoAPIResult = terminalUserService.getUser(uid);
+        TerminalUserVo userVo = userVoAPIResult.getData();
+        if (!userVoAPIResult.getSuccess() || userVo == null) {
+            log.error("查询用户错误, orderId={}, {}", orderId, userVoAPIResult.getMessage());
+            return APIResult.error(new BaseCode(userVoAPIResult.getCode(), userVoAPIResult.getMessage()));
+        }
+
+        // 开始扣款
+        String merchantId = userVo.getMerchantId();
+        MerchantPayDTO payDTO = new MerchantPayDTO();
+        payDTO.setQuantity(userOrder.getFinalPrice());
+        payDTO.setType(LedgerTypeEnum.COURSE);
+        payDTO.setReceiptId(userOrder.getId());
+
+        APIResult<Boolean> merchantPayAPIResult = merchantService.pay(merchantId, payDTO);
+        if (!merchantPayAPIResult.getSuccess()) {
+            log.error("扣款失败, orderId={}, {}", orderId, merchantPayAPIResult.getMessage());
+            return APIResult.error(new BaseCode(merchantPayAPIResult.getCode(), merchantPayAPIResult.getMessage()));
+        }
+
+        // 更新订单状态
+        APIResult<Boolean> orderAPIResult = orderServiceFeign.pay(orderId, OrderStatusEnum.PAYOK);
+        if (!orderAPIResult.getSuccess()) {
+            return APIResult.error(new BaseCode(orderAPIResult.getCode(), orderAPIResult.getMessage()));
+        }
+
+        // 生成交易商品快照, 还要插入鉴权记录, 放入循环任务中进行
+
+        return APIResult.ok();
+    }
+
+    public APIResult<Boolean> cancel(String orderId) {
+        return orderServiceFeign.cancel(orderId);
+    }
+
+    public APIResult<UserOrderVo> create(UserOrderDTO userOrderDTO) {
+        String uid = userOrderDTO.getUid();
+        APIResult<TerminalUserVo> userVoAPIResult = terminalUserService.getUser(uid);
+        if (!userVoAPIResult.getSuccess()) {
+            return APIResult.error(CmsWebAPICode.error("用户不存在"));
+        }
+
+        TerminalUserVo terminalUserVo = userVoAPIResult.getData();
+
+        List<OrderGoodsDTO> orderGoodsDTOList = userOrderDTO.getGoods();
+        if (CollectionUtils.isEmpty(orderGoodsDTOList)) {
+            return APIResult.error(CmsWebAPICode.error("商品为空"));
+        }
+
+        List<String> goodsIdList = new ArrayList<>();
+        Map<String, Integer> goodsCountMap = new HashMap<>();
+        orderGoodsDTOList.forEach(goods -> {
+            String goodsId = goods.getGoodsId();
+            goodsIdList.add(goodsId);
+            goodsCountMap.put(goodsId, goods.getQuantity());
+        });
+
+        APIResult<List<GoodsVo>> goodsVoAPIResult = goodsService.findByIds(goodsIdList);
+        if (!goodsVoAPIResult.getSuccess()) {
+            return APIResult.error(CmsWebAPICode.error("查询商品错误"));
+        }
+
+        List<GoodsVo> goodsVoList = goodsVoAPIResult.getData();
+        if (goodsVoList.size() != orderGoodsDTOList.size()) {
+            return APIResult.error(CmsWebAPICode.error("包含不可售卖的商品"));
+        }
+
+        BigDecimal totalPrice = BigDecimal.ZERO;
+        List<OrderGoodsDTO> finalGoodsList = new ArrayList<>();
+        for (GoodsVo goodsVo : goodsVoList) {
+            String goodsMerchantId = goodsVo.getMerchantId();
+            if (!terminalUserVo.getMerchantId().equals(goodsMerchantId)) {
+                return APIResult.error(CmsWebAPICode.error("不可以购买其他渠道商品"));
+            }
+            if (goodsVo.getType().equals(ProductTypeEnum.PACKAGE)) {
+                return APIResult.error(CmsWebAPICode.error("包含课程包"));
+            }
+            String goodsId = goodsVo.getId();
+            Integer count = goodsCountMap.get(goodsId);
+            OrderGoodsDTO orderGoodsDTO = new OrderGoodsDTO();
+            orderGoodsDTO.setGoodsId(goodsId);
+            orderGoodsDTO.setPid(goodsVo.getPid());
+            orderGoodsDTO.setType(goodsVo.getType());
+            orderGoodsDTO.setQuantity(count);
+            finalGoodsList.add(orderGoodsDTO);
+            totalPrice = totalPrice.add(goodsVo.getMerchantPrice().multiply(new BigDecimal(count)));
+        }
+        userOrderDTO.setFinalPrice(totalPrice.subtract(userOrderDTO.getAdjustPrice()));
+        userOrderDTO.setOriginPrice(totalPrice);
+        userOrderDTO.setGoods(finalGoodsList);
+
+        APIResult userOrderAPIResult = orderServiceFeign.create(userOrderDTO);
+        return userOrderAPIResult;
+    }
+
+    public APIResult<UserOrderVo> getOrder(String orderId) {
+        APIResult<UserOrder> userOrderVoAPIResult = orderServiceFeign.getOrder(orderId);
+        if (!userOrderVoAPIResult.getSuccess()) {
+            return APIResult.error(new BaseCode(userOrderVoAPIResult.getCode(), userOrderVoAPIResult.getMessage()));
+        }
+
+        UserOrder userOrder = userOrderVoAPIResult.getData();
+        UserOrderVo userOrderVo = convert(userOrder);
+        List<OrderGoods> orderGoodsList = userOrder.getGoods();
+        if (CollectionUtils.isEmpty(orderGoodsList)) {
+            return APIResult.ok(userOrderVo);
+        }
+
+        List<String> goodsIdList = new ArrayList<>();
+        orderGoodsList.forEach(orderGoods -> goodsIdList.add(orderGoods.getGoodsId()));
+        APIResult<List<GoodsVo>> goodsAPIResult = goodsService.findByIds(goodsIdList);
+        if (!goodsAPIResult.getSuccess()) {
+            log.error("goods api error: {}", goodsAPIResult.getMessage());
+        }
+        userOrderVo.setGoods(goodsAPIResult.getData());
+        return APIResult.ok(userOrderVo);
+    }
+
+    public APIResult<Page<UserOrderVo>> search(OrderSearchWebDTO searchWebDTO) {
+        String userCode = searchWebDTO.getUserCode();
+        String campusCode = searchWebDTO.getCampusCode();
+        String campusName = searchWebDTO.getCampusName();
+
+        List<String> uids = new ArrayList<>();
+
+        // 用户及校区的Map
+        Map<String, TerminalUserVo> userVoMap = new HashMap<>();
+        Map<String, CampusVo> campusVoMap = new HashMap<>();
+
+        if (!StringUtils.isEmpty(userCode)) {
+            // 按用户查询
+            Map<String, Object> searchDTO = new HashMap<>();
+            searchDTO.put("code", userCode);
+            APIResult<List<TerminalUserVo>> userAPIResult = terminalUserService.findByCondition(searchDTO);
+            if (!userAPIResult.getSuccess()) {
+                return APIResult.error(new BaseCode(userAPIResult.getCode(), userAPIResult.getMessage()));
+            }
+
+            List<TerminalUserVo> userVoList = userAPIResult.getData();
+            if (CollectionUtils.isEmpty(userVoList)) {
+                return APIResult.ok(new Page<>());
+            }
+
+            List<String> campusIdList = new ArrayList<>();
+            // 记录数据,查到数据后要set回去
+            userVoList.forEach(userVo -> {
+                uids.add(userVo.getId());
+                campusIdList.add(userVo.getCampusId());
+                userVoMap.put(userVo.getId(), userVo);
+            });
+
+            APIResult<Map<String, CampusVo>> campusAPIResult = campusService.findByIds(campusIdList);
+            if (campusAPIResult.getSuccess()) {
+                campusVoMap = campusAPIResult.getData();
+            }
+        } else if (!StringUtils.isEmpty(campusCode) || !StringUtils.isEmpty(campusName)) {
+            // 按校区查询
+            CampusSearchDTO campusSearchDTO = new CampusSearchDTO();
+            campusSearchDTO.setCode(campusCode);
+            campusSearchDTO.setName(campusName);
+            APIResult<List<CampusVo>> campusAPIResult = campusService.findByCondition(BeanUtil.convertToMap(campusSearchDTO));
+            if (!campusAPIResult.getSuccess()) {
+                return APIResult.error(new BaseCode(campusAPIResult.getCode(), campusAPIResult.getMessage()));
+            }
+            List<CampusVo> campusVoList = campusAPIResult.getData();
+            if (CollectionUtils.isEmpty(campusVoList)) {
+                return APIResult.ok(new Page<>());
+            }
+
+            List<String> campusIdList = new ArrayList<>();
+            for (CampusVo campusVo : campusVoList) {
+                campusIdList.add(campusVo.getId());
+                campusVoMap.put(campusVo.getId(), campusVo);
+            }
+
+            APIResult<List<TerminalUserVo>> userAPIResult = terminalUserService.findByCampusIds(campusIdList);
+            if (!userAPIResult.getSuccess()) {
+                return APIResult.error(new BaseCode(userAPIResult.getCode(), userAPIResult.getMessage()));
+            }
+            List<TerminalUserVo> userVoList = userAPIResult.getData();
+            for (TerminalUserVo userVo : userVoList) {
+                uids.add(userVo.getId());
+                userVoMap.put(userVo.getId(), userVo);
+            }
+        }
+
+        Map<String, Object> searchParams = new HashMap<>();
+        searchParams.put("pageNo", searchWebDTO.getPageNo());
+        searchParams.put("pageSize", searchWebDTO.getPageSize());
+        searchParams.put("beginTime", searchWebDTO.getBeginTime());
+        searchParams.put("endTime", searchWebDTO.getEndTime());
+        searchParams.put("status", searchWebDTO.getStatus());
+        searchParams.put("uid", uids);
+
+        APIResult<Page<UserOrder>> orderAPIResult = orderServiceFeign.search(searchParams);
+        if (!orderAPIResult.getSuccess()) {
+            log.error("order query error: {}", orderAPIResult.getMessage());
+            return APIResult.error(new BaseCode(orderAPIResult.getCode(), orderAPIResult.getMessage()));
+        }
+
+        Page<UserOrder> userOrderPage = orderAPIResult.getData();
+        Page<UserOrderVo> page = new Page<>(userOrderPage.getTotalSize(), userOrderPage.getPageNo(), userOrderPage.getPageSize());
+
+        List<UserOrder> userOrderList = orderAPIResult.getData().getList();
+        List<UserOrderVo> userOrderVoList = new ArrayList<>();
+        if (CollectionUtils.isEmpty(userOrderList)) {
+            return APIResult.ok(page);
+        }
+
+        if (uids.isEmpty() && !CollectionUtils.isEmpty(userOrderList)) {
+            userOrderList.forEach(userOrderVo -> uids.add(userOrderVo.getUid()));
+            APIResult<List<TerminalUserVo>> userAPIResult = terminalUserService.findByIds(uids);
+            if (!userAPIResult.getSuccess()) {
+                return APIResult.error(new BaseCode(userAPIResult.getCode(), userAPIResult.getMessage()));
+            }
+
+            List<TerminalUserVo> userVoList = userAPIResult.getData();
+            if (CollectionUtils.isEmpty(userVoList)) {
+                return APIResult.error(CmsWebAPICode.error("用户信息查询错误"));
+            }
+
+            List<String> campusIdList = new ArrayList<>();
+            // 记录数据,查到数据后要set回去
+            for (TerminalUserVo userVo : userVoList) {
+                campusIdList.add(userVo.getCampusId());
+                userVoMap.put(userVo.getId(), userVo);
+            }
+
+            if (CollectionUtils.isEmpty(campusIdList)) {
+                return APIResult.error(CmsWebAPICode.error("查询校区信息错误"));
+            }
+            APIResult<Map<String, CampusVo>> campusAPIResult = campusService.findByIds(campusIdList);
+            if (campusAPIResult.getSuccess()) {
+                campusVoMap = campusAPIResult.getData();
+            }
+        }
+
+        for (UserOrder userOrder : userOrderList) {
+            UserOrderVo userOrderVo = convert(userOrder);
+            String uid = userOrderVo.getUid();
+
+            TerminalUserVo terminalUserVo = userVoMap.get(uid);
+            if (terminalUserVo == null) {
+                log.error("用户不存在: {}", uid);
+                continue;
+            }
+            userOrderVo.setMerchantId(terminalUserVo.getMerchantId());
+            userOrderVo.setMerchantName(terminalUserVo.getMerchantName());
+            userOrderVo.setUserCode(terminalUserVo.getCode());
+            userOrderVo.setClassroomName(terminalUserVo.getName());
+
+            String campusId = terminalUserVo.getCampusId();
+            CampusVo campusVo = campusVoMap.get(campusId);
+            userOrderVo.setProvinceCode(campusVo.getProvinceCode());
+            userOrderVo.setCityName(campusVo.getCityName());
+
+            userOrderVoList.add(userOrderVo);
+        }
+
+        page.setList(userOrderVoList);
+        return APIResult.ok(page);
+    }
+}

+ 31 - 0
rankin-cms-web/src/main/java/cn/rankin/cmsweb/service/trade/order/OrderServiceFeign.java

@@ -0,0 +1,31 @@
+package cn.rankin.cmsweb.service.trade.order;
+
+import cn.rankin.common.utils.api.model.APIResult;
+import cn.rankin.common.utils.api.page.Page;
+import cn.rankin.common.utils.enums.OrderStatusEnum;
+import cn.rankin.data.api.trade.dto.UserOrderDTO;
+import cn.rankin.data.api.trade.entity.UserOrder;
+import org.springframework.cloud.netflix.feign.FeignClient;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Map;
+
+@FeignClient(name = "${service.trade.name}")
+public interface OrderServiceFeign {
+
+    // 此处返回值不用Vo,需要进一步处理
+    @RequestMapping(value = "/order/list", method = RequestMethod.GET)
+    APIResult<Page<UserOrder>> search(@RequestParam Map<String, Object> searchDTO);
+
+    @RequestMapping(value = "/order", method = RequestMethod.POST)
+    APIResult<UserOrder> create(@RequestBody UserOrderDTO userOrderDTO);
+
+    @RequestMapping(value = "/order/{orderId}", method = RequestMethod.GET)
+    APIResult<UserOrder> getOrder(@PathVariable("orderId") String orderId);
+
+    @RequestMapping(value = "/order/pay/{orderId}", method = RequestMethod.POST)
+    APIResult<Boolean> pay(@PathVariable("orderId") String orderId, @RequestParam("status") OrderStatusEnum status);
+
+    @RequestMapping(value = "/order/pay/{orderId}", method = RequestMethod.DELETE)
+    APIResult<Boolean> cancel(@PathVariable("orderId") String orderId);
+}

+ 7 - 0
rankin-cms-web/src/main/java/cn/rankin/cmsweb/service/user/CampusService.java

@@ -7,6 +7,7 @@ import cn.rankin.data.api.user.vo.CampusVo;
 import org.springframework.cloud.netflix.feign.FeignClient;
 import org.springframework.web.bind.annotation.*;
 
+import java.util.List;
 import java.util.Map;
 
 @FeignClient(name = "${service.user.name}")
@@ -15,6 +16,9 @@ public interface CampusService {
     @RequestMapping(value = "/campus/list", method = RequestMethod.GET)
     APIResult<Page<CampusVo>> search(@RequestParam Map<String, Object> searchDTO);
 
+    @RequestMapping(value = "/campus", method = RequestMethod.GET)
+    APIResult<List<CampusVo>> findByCondition(@RequestParam Map<String, Object> params);
+
     @RequestMapping(value = "/campus", method = RequestMethod.POST)//, consumes = MediaType.APPLICATION_JSON_VALUE)//, consumes = "application/json")
     APIResult<CampusVo> create(@RequestBody CampusDTO campusDTO);
 
@@ -26,4 +30,7 @@ public interface CampusService {
 
     @RequestMapping(value = "/campus/{id}", method = RequestMethod.GET)
     APIResult<CampusVo> getCampus(@PathVariable("id") String id);
+
+    @RequestMapping(value = "/campus/ids", method = RequestMethod.GET)
+    APIResult<Map<String, CampusVo>> findByIds(@RequestParam("id") List<String> ids);
 }

+ 4 - 0
rankin-cms-web/src/main/java/cn/rankin/cmsweb/service/user/MerchantService.java

@@ -3,6 +3,7 @@ package cn.rankin.cmsweb.service.user;
 import cn.rankin.common.utils.api.model.APIResult;
 import cn.rankin.common.utils.api.page.Page;
 import cn.rankin.data.api.user.dto.MerchantDTO;
+import cn.rankin.data.api.user.dto.MerchantPayDTO;
 import cn.rankin.data.api.user.vo.MerchantVo;
 import org.springframework.cloud.netflix.feign.FeignClient;
 import org.springframework.web.bind.annotation.*;
@@ -31,4 +32,7 @@ public interface MerchantService {
     @RequestMapping(value = "/merchant/ids", method = RequestMethod.GET)
     APIResult<Map<String, MerchantVo>> findByIds(@RequestParam("id") List<String> ids);
 
+    @RequestMapping(value = "/merchant/pay/{merchantId}", method = RequestMethod.POST)
+    APIResult<Boolean> pay(@PathVariable("merchantId") String merchantId, @RequestBody MerchantPayDTO payDTO);
+
 }

+ 10 - 0
rankin-cms-web/src/main/java/cn/rankin/cmsweb/service/user/TerminalUserService.java

@@ -7,6 +7,7 @@ import cn.rankin.data.api.user.vo.TerminalUserVo;
 import org.springframework.cloud.netflix.feign.FeignClient;
 import org.springframework.web.bind.annotation.*;
 
+import java.util.List;
 import java.util.Map;
 
 @FeignClient(name = "${service.user.name}")
@@ -27,4 +28,13 @@ public interface TerminalUserService {
     @RequestMapping(value = "/user/{id}", method = RequestMethod.GET)
     APIResult<TerminalUserVo> getUser(@PathVariable("id") String id);
 
+    @RequestMapping(value = "/user", method = RequestMethod.GET)
+    APIResult<List<TerminalUserVo>> findByCondition(@RequestParam Map<String, Object> searchDTO);
+
+    @RequestMapping(value = "/campus/ids", method = RequestMethod.GET)
+    APIResult<List<TerminalUserVo>> findByCampusIds(@RequestParam("id") List<String> ids);
+
+    @RequestMapping(value = "/user/ids", method = RequestMethod.GET)
+    APIResult<List<TerminalUserVo>> findByIds(@RequestParam("id") List<String> ids);
+
 }

+ 3 - 0
rankin-common-utils/src/main/java/cn/rankin/common/utils/api/model/APICode.java

@@ -50,6 +50,9 @@ public class APICode {
     // product flag code
     public final static int _PRODUCT_SERVICE_FLAG = 40000;
 
+    // trade flag code
+    public final static int _TRADE_SERVICE_FLAG = 50000;
+
     public static BaseCode ok(String message) {
         return new BaseCode(_C_OK, message);
     }

+ 20 - 0
rankin-common-utils/src/main/java/cn/rankin/common/utils/enums/LedgerTypeEnum.java

@@ -0,0 +1,20 @@
+package cn.rankin.common.utils.enums;
+
+public enum LedgerTypeEnum {
+    COURSE("购买课程");
+
+    String name;
+
+    LedgerTypeEnum(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public String toString() {
+        return this.name;
+    }
+}

+ 2 - 1
rankin-common-utils/src/main/java/cn/rankin/common/utils/enums/OrderStatusEnum.java

@@ -2,7 +2,8 @@ package cn.rankin.common.utils.enums;
 
 public enum OrderStatusEnum {
 
-    UNPAID("待支付"), PAYOK("已支付"), CANCEL("取消"), REFUND("退款");
+    UNPAID("待支付"), PAYOK("已支付"), CANCEL("取消"), REFUND("退款"), SEND("发货"),
+    RECEIVED("收货"), COMPLETE("完成");
 
     String name;
 

+ 7 - 2
rankin-common-utils/src/main/java/cn/rankin/common/utils/jpa/BasicJpaRepository.java

@@ -1,6 +1,7 @@
 package cn.rankin.common.utils.jpa;
 
 import org.springframework.data.domain.Sort;
+import org.springframework.data.jpa.domain.Specification;
 import org.springframework.data.jpa.repository.JpaRepository;
 
 import java.io.Serializable;
@@ -24,6 +25,8 @@ public interface BasicJpaRepository<E, ID extends Serializable> extends JpaRepos
 	 * @version 2016年3月31日 下午4:56:01 <br/>
 	 */
 	long count(E sample);
+
+	long count(Specification<E> spec);
 	
 	/**
 	 * 功能:检查是否存在 <br/>
@@ -103,7 +106,7 @@ public interface BasicJpaRepository<E, ID extends Serializable> extends JpaRepos
 	 * @version 2016年3月31日 上午11:58:31 <br/>
 	 */
 	List<E> find(E sample, Long start, Integer offset);
-	
+
 	/**
 	 * 功能:分页查询,且排序 <br/>
 	 *
@@ -111,7 +114,9 @@ public interface BasicJpaRepository<E, ID extends Serializable> extends JpaRepos
 	 * @version 2016年7月29日 上午9:44:26 <br/>
 	 */
 	List<E> find(E sample, Long start, Integer offset, Sort sort);
-	
+
+    List<E> find(Specification<E> spec, Long start, Integer offset, Sort sort);
+
 	/**
 	 * 功能:更新操作,不更新为null的字段 <br/>
 	 *

+ 7 - 3
rankin-common-utils/src/main/java/cn/rankin/common/utils/jpa/SimpleJpaRepository.java

@@ -77,7 +77,8 @@ public class SimpleJpaRepository<E, ID extends Serializable> extends org.springf
 
     @Override
     public List<E> find(Long start, Integer offset, Sort sort) {
-        return find(null, start, offset, sort);
+        Specification<E> spec = null;
+        return find(spec, start, offset, sort);
     }
 
     @Override
@@ -88,7 +89,6 @@ public class SimpleJpaRepository<E, ID extends Serializable> extends org.springf
     @Override
     public E findFirst(E sample, Sort sort) {
         List<E> list = find(sample, 0L, 1, sort);
-
         return null == list || list.isEmpty() ? null : list.get(0);
     }
 
@@ -111,7 +111,12 @@ public class SimpleJpaRepository<E, ID extends Serializable> extends org.springf
     public List<E> find(E sample, Long start, Integer offset, Sort sort) {
         // 创建qbe
         Specification<E> spec = getSpecification(sample);
+        return find(spec, start, offset, sort);
+    }
+
 
+    @Override
+    public List<E> find(Specification<E> spec, Long start, Integer offset, Sort sort) {
         if (null == sort) {
             // 默认按id降序排序
             sort = new Sort(new Order(Direction.DESC, entityInformation.getIdAttribute().getName()));
@@ -135,7 +140,6 @@ public class SimpleJpaRepository<E, ID extends Serializable> extends org.springf
         return query.getResultList();
     }
 
-
     @Transactional
     public E update(E entity) {
         return update(entity, true);

+ 1 - 1
rankin-common-utils/src/main/java/cn/rankin/common/utils/util/ListUtil.java

@@ -46,7 +46,7 @@ public class ListUtil {
 
         List<T> result = new ArrayList<>();
         source.forEach( s -> {
-            if (!target.contains(s)) {
+            if (!target.contains(s) && !result.contains(s)) {
                 result.add(s);
             }
         });

+ 28 - 0
rankin-data-api/src/main/java/cn/rankin/data/api/cms/OrderSearchWebDTO.java

@@ -0,0 +1,28 @@
+package cn.rankin.data.api.cms;
+
+import cn.rankin.common.utils.enums.OrderStatusEnum;
+import lombok.Data;
+import lombok.ToString;
+
+import java.io.Serializable;
+
+@Data
+@ToString
+public class OrderSearchWebDTO implements Serializable {
+
+    private String userCode;
+
+    private String campusName;
+
+    private String campusCode;
+
+    protected String beginTime;
+
+    protected String endTime;
+
+    protected OrderStatusEnum status;
+
+    protected Integer pageNo = 1;
+
+    protected Integer pageSize = 10;
+}

+ 5 - 0
rankin-data-api/src/main/java/cn/rankin/data/api/product/entity/Course.java

@@ -42,10 +42,15 @@ public class Course implements Serializable{
     @Column
     private String keyword;
 
+    @Column(name = "cover_url")
     private String coverUrl;
 
+    @Column(name = "bg_url")
     private String bgUrl;
 
+    @Column(name = "cp_id")
+    private String cpId;
+
     @Enumerated(EnumType.ORDINAL)
     private ProductTypeEnum type;
 

+ 3 - 0
rankin-data-api/src/main/java/cn/rankin/data/api/product/entity/Product.java

@@ -29,6 +29,9 @@ public class Product implements Serializable {
 
     private String name;
 
+    @Column(name = "cp_id")
+    private String cpId;
+
     @Enumerated(EnumType.ORDINAL)
     private ProductTypeEnum type;
 

+ 3 - 0
rankin-data-api/src/main/java/cn/rankin/data/api/product/entity/Support.java

@@ -43,6 +43,9 @@ public class Support implements Serializable {
     @Convert(converter = ListAttributeConverter.class)
     private List<String> imgList;
 
+    @Column(name = "cp_id")
+    private String cpId;
+
     @Enumerated(EnumType.ORDINAL)
     private ProductTypeEnum type;
 

+ 2 - 0
rankin-data-api/src/main/java/cn/rankin/data/api/product/vo/CourseVo.java

@@ -27,6 +27,8 @@ public class CourseVo implements Serializable {
 
     private String bgUrl;
 
+    private String cpId;
+
     private BaseStatusEnum status;
 
     private List<CourseSubItemVo> subItemList;

+ 4 - 2
rankin-data-api/src/main/java/cn/rankin/data/api/product/vo/ProductVo.java

@@ -2,14 +2,14 @@ package cn.rankin.data.api.product.vo;
 
 import cn.rankin.common.utils.enums.BaseStatusEnum;
 import cn.rankin.common.utils.enums.ProductTypeEnum;
-import cn.rankin.data.api.product.entity.Goods;
 import lombok.Data;
+import lombok.ToString;
 
 import java.io.Serializable;
 import java.util.Date;
-import java.util.List;
 
 @Data
+@ToString
 public class ProductVo implements Serializable {
 
     private String id;
@@ -20,6 +20,8 @@ public class ProductVo implements Serializable {
 
     private String name;
 
+    private String cpId;
+
     private ProductTypeEnum type;
 
     private BaseStatusEnum status;

+ 1 - 1
rankin-data-api/src/main/java/cn/rankin/data/api/product/vo/RecommendVo.java

@@ -14,7 +14,7 @@ public class RecommendVo implements Serializable {
 
     private String merchantId;
 
-    private String itemId;
+    private String pid;
 
     private BaseStatusEnum status;
 

+ 4 - 0
rankin-data-api/src/main/java/cn/rankin/data/api/product/vo/SupportVo.java

@@ -22,6 +22,10 @@ public class SupportVo implements Serializable {
 
     private String detail;
 
+    private String coverUrl;
+
+    private String cpId;
+
     private List<String> imgList;
 
     private BaseStatusEnum status;

+ 26 - 0
rankin-data-api/src/main/java/cn/rankin/data/api/trade/dto/OrderSearchDTO.java

@@ -0,0 +1,26 @@
+package cn.rankin.data.api.trade.dto;
+
+import cn.rankin.common.utils.enums.OrderStatusEnum;
+import lombok.Data;
+import lombok.ToString;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+@Data
+@ToString
+public class OrderSearchDTO implements Serializable {
+
+    protected List<String> uid;
+
+    protected Date beginTime;
+
+    protected Date endTime;
+
+    protected OrderStatusEnum status;
+
+    protected Integer pageNo = 1;
+
+    protected Integer pageSize = 10;
+}

+ 10 - 0
rankin-data-api/src/main/java/cn/rankin/data/api/trade/entity/ProductSoldSnapshot.java

@@ -4,6 +4,8 @@ import cn.rankin.common.utils.enums.BaseStatusEnum;
 import cn.rankin.common.utils.enums.ProductTypeEnum;
 import lombok.Data;
 import lombok.ToString;
+import org.hibernate.annotations.DynamicInsert;
+import org.hibernate.annotations.DynamicUpdate;
 
 import javax.persistence.*;
 import java.io.Serializable;
@@ -39,6 +41,10 @@ import java.util.Date;
  */
 @Data
 @ToString
+@Entity
+@Table(name = "product_sold_snapshot")
+@DynamicInsert
+@DynamicUpdate
 public class ProductSoldSnapshot implements Serializable {
 
     @Id
@@ -113,6 +119,10 @@ public class ProductSoldSnapshot implements Serializable {
     @Enumerated(EnumType.ORDINAL)
     private BaseStatusEnum status;
 
+    @Column(name = "gmt_modified", updatable = false, insertable = false, columnDefinition = "timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP")
+    @Temporal(TemporalType.TIMESTAMP)
+    private Date gmtModified;
+
     @Temporal(TemporalType.TIMESTAMP)
     @Column(name = "gmt_created", updatable = false, insertable = false, columnDefinition = "timestamp NULL DEFAULT CURRENT_TIMESTAMP")
     private Date gmtCreated;

+ 10 - 0
rankin-data-api/src/main/java/cn/rankin/data/api/trade/vo/UserOrderVo.java

@@ -31,6 +31,16 @@ public class UserOrderVo implements Serializable {
 
     private OrderStatusEnum status;
 
+    private String userCode;
+
+    private String provinceCode;
+
+    private String cityName;
+
+    private String zoneName;
+
+    private String classroomName;
+
     private String note;
 
     private Date gmtCreated;

+ 2 - 0
rankin-data-api/src/main/java/cn/rankin/data/api/user/dto/CampusSearchDTO.java

@@ -14,6 +14,8 @@ public class CampusSearchDTO implements Serializable {
 
     private String merchantId;
 
+    protected String code;
+
     private String sort;
 
     private Integer by;

+ 21 - 0
rankin-data-api/src/main/java/cn/rankin/data/api/user/dto/MerchantPayDTO.java

@@ -0,0 +1,21 @@
+package cn.rankin.data.api.user.dto;
+
+import cn.rankin.common.utils.enums.LedgerTypeEnum;
+import lombok.Data;
+import lombok.ToString;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+@Data
+@ToString
+public class MerchantPayDTO implements Serializable {
+
+    private BigDecimal quantity;
+
+    private String receiptId;
+
+    private LedgerTypeEnum type;
+
+    private String note;
+}

+ 0 - 1
rankin-data-api/src/main/java/cn/rankin/data/api/user/dto/TerminalUserDTO.java

@@ -4,7 +4,6 @@ import cn.rankin.common.utils.enums.BaseStatusEnum;
 import lombok.Data;
 import lombok.ToString;
 
-import javax.validation.constraints.NotNull;
 import java.io.Serializable;
 
 @Data

+ 1 - 1
rankin-data-api/src/main/java/cn/rankin/data/api/user/entity/Campus.java

@@ -14,7 +14,7 @@ import java.util.Date;
 @Data
 @ToString
 @Entity
-@Table(name = "campus", uniqueConstraints = {@UniqueConstraint(columnNames = {"merchant_id", "serial_no"})})
+@Table(name = "campus", uniqueConstraints = {@UniqueConstraint(columnNames = {"merchant_id", "serial_no", "province_code"})})
 @DynamicInsert
 @DynamicUpdate
 public class Campus implements Serializable {

+ 1 - 0
rankin-data-api/src/main/java/cn/rankin/data/api/user/entity/CmsUser.java

@@ -3,6 +3,7 @@ package cn.rankin.data.api.user.entity;
 
 import cn.rankin.common.utils.enums.BaseStatusEnum;
 import cn.rankin.common.utils.enums.GenderEnum;
+import com.fasterxml.jackson.annotation.JsonIgnore;
 import lombok.Data;
 import lombok.ToString;
 import org.hibernate.annotations.DynamicInsert;

+ 58 - 0
rankin-data-api/src/main/java/cn/rankin/data/api/user/entity/Ledger.java

@@ -0,0 +1,58 @@
+package cn.rankin.data.api.user.entity;
+
+import cn.rankin.common.utils.enums.LedgerTypeEnum;
+import lombok.Data;
+import lombok.ToString;
+import org.hibernate.annotations.DynamicInsert;
+import org.hibernate.annotations.DynamicUpdate;
+
+import javax.persistence.*;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Date;
+
+@Data
+@ToString
+@Entity
+@Table(name = "ledger")
+@DynamicInsert
+@DynamicUpdate
+public class Ledger implements Serializable {
+
+    @Id
+    private String id;
+
+    @Column(name = "merchant_id")
+    private String merchantId;
+
+    private BigDecimal quantity;
+
+    @Column(name = "balance_before")
+    private BigDecimal balanceBefore;
+
+    @Column(name = "balance_after")
+    private BigDecimal balanceAfter;
+
+    @Column(name = "receipt_id")
+    private String receiptId;
+
+    @Enumerated(EnumType.ORDINAL)
+    private LedgerTypeEnum type;
+
+    private String note;
+
+    @Column(name = "gmt_created", updatable = false, insertable = false, columnDefinition = "timestamp NULL DEFAULT CURRENT_TIMESTAMP")
+    @Temporal(TemporalType.TIMESTAMP)
+    private Date gmtCreated;
+
+    public Ledger(String merchantId, BigDecimal balanceBefore, BigDecimal quantity, LedgerTypeEnum type,
+                  String receiptId, String note) {
+        this.merchantId = merchantId;
+        this.balanceBefore = balanceBefore;
+        this.quantity = quantity;
+        this.balanceAfter = balanceBefore.add(quantity);
+        this.type = type;
+        this.receiptId = receiptId;
+        this.note = note;
+    }
+}

+ 3 - 0
rankin-data-api/src/main/java/cn/rankin/data/api/user/entity/Merchant.java

@@ -12,6 +12,7 @@ import org.hibernate.annotations.DynamicUpdate;
 import javax.persistence.*;
 import javax.validation.constraints.Pattern;
 import java.io.Serializable;
+import java.math.BigDecimal;
 import java.util.Date;
 import java.util.List;
 
@@ -48,6 +49,8 @@ public class Merchant implements Serializable {
     @Column(name = "deposit_bank", nullable = false)
     private String depositBank;
 
+    private BigDecimal balance;
+
     @Column(nullable = false)
     @Enumerated(EnumType.ORDINAL)
     private ReceiptTypeEnum receiptType;

+ 3 - 3
rankin-data-api/src/main/java/cn/rankin/data/api/user/entity/TerminalUser.java

@@ -66,8 +66,8 @@ public class TerminalUser implements Serializable {
     @Transient
     private String campusName;
 
-    public void setCampusName(String campusName) {
-        this.campusName = String.format("%s-%s", campusName, name);
-    }
+//    public void setCampusName(String campusName) {
+//        this.campusName = String.format("%s-%s", campusName, name);
+//    }
 
 }

+ 1 - 0
rankin-data-api/src/main/java/cn/rankin/data/api/user/vo/CmsUserVo.java

@@ -2,6 +2,7 @@ package cn.rankin.data.api.user.vo;
 
 import cn.rankin.common.utils.enums.BaseStatusEnum;
 import cn.rankin.common.utils.enums.GenderEnum;
+import com.fasterxml.jackson.annotation.JsonIgnore;
 import lombok.Data;
 import lombok.ToString;
 

+ 2 - 0
rankin-data-api/src/main/java/cn/rankin/data/api/user/vo/TerminalUserVo.java

@@ -38,4 +38,6 @@ public class TerminalUserVo implements Serializable {
     private String contactName;
 
     private String mobile;
+
+    private String campusName;
 }

+ 1 - 1
rankin-product-service/src/main/java/cn/rankin/productservice/service/ProductService.java

@@ -47,7 +47,7 @@ public class ProductService {
         Page<Product> page = new Page<>(count, pageNo, pageSize);
 
         Long start = page.getStart();
-        if (count <= start) {
+        if (count == 0) {
             return APIResult.ok(page);
         }
 

+ 102 - 0
rankin-task/pom.xml

@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+
+	<groupId>cn.rankin</groupId>
+	<artifactId>rankin-cms-web</artifactId>
+	<version>0.0.1-SNAPSHOT</version>
+	<packaging>jar</packaging>
+
+	<name>rankin-task</name>
+	<description>Backend Task For Spring Cloud</description>
+
+	<parent>
+        <groupId>cn.rankin</groupId>
+        <artifactId>rankin</artifactId>
+        <version>0.0.1-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+	</parent>
+
+	<properties>
+		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+		<java.version>1.8</java.version>
+		<spring-cloud.version>Dalston.SR4</spring-cloud.version>
+	</properties>
+
+	<dependencies>
+		<dependency>
+			<groupId>org.springframework.cloud</groupId>
+			<artifactId>spring-cloud-starter-config</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework.cloud</groupId>
+			<artifactId>spring-cloud-starter-eureka</artifactId>
+		</dependency>
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-feign</artifactId>
+        </dependency>
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-web</artifactId>
+		</dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <version>1.16.16</version>
+        </dependency>
+        <dependency>
+            <groupId>cn.rankin</groupId>
+            <artifactId>rankin-common-utils</artifactId>
+            <version>0.0.1-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>cn.rankin</groupId>
+            <artifactId>rankin-data-api</artifactId>
+            <version>0.0.1-SNAPSHOT</version>
+        </dependency>
+
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-test</artifactId>
+			<scope>test</scope>
+		</dependency>
+        <!--httpClient-->
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+            <version>4.5.3</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.netflix.feign</groupId>
+            <artifactId>feign-httpclient</artifactId>
+            <version>RELEASE</version>
+        </dependency>
+    </dependencies>
+
+	<dependencyManagement>
+		<dependencies>
+			<dependency>
+				<groupId>org.springframework.cloud</groupId>
+				<artifactId>spring-cloud-dependencies</artifactId>
+				<version>${spring-cloud.version}</version>
+				<type>pom</type>
+				<scope>import</scope>
+			</dependency>
+		</dependencies>
+	</dependencyManagement>
+
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.springframework.boot</groupId>
+				<artifactId>spring-boot-maven-plugin</artifactId>
+			</plugin>
+		</plugins>
+	</build>
+
+
+</project>

+ 18 - 0
rankin-task/src/main/java/cn/rankin/task/RankinTaskApplication.java

@@ -0,0 +1,18 @@
+package cn.rankin.task;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
+import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+import org.springframework.cloud.netflix.feign.EnableFeignClients;
+
+@EnableFeignClients
+@EnableDiscoveryClient
+@SpringBootApplication(exclude={DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class})
+public class RankinTaskApplication {
+
+	public static void main(String[] args) {
+		SpringApplication.run(RankinTaskApplication.class, args);
+	}
+}

+ 0 - 0
rankin-task/src/main/resources/application.properties


+ 16 - 0
rankin-task/src/test/java/cn/rankin/task/RankinTaskApplicationTests.java

@@ -0,0 +1,16 @@
+package cn.rankin.task;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class RankinTaskApplicationTests {
+
+	@Test
+	public void contextLoads() {
+	}
+
+}

+ 8 - 0
rankin-trade-service/src/main/java/cn/rankin/tradeservice/code/TradeServiceAPICode.java

@@ -1,6 +1,14 @@
 package cn.rankin.tradeservice.code;
 
 import cn.rankin.common.utils.api.model.APICode;
+import cn.rankin.common.utils.api.model.BaseCode;
 
 public class TradeServiceAPICode extends APICode {
+
+    public final static int _PAY_FAILED = 50100;
+    public final static BaseCode PAY_FAILED = new BaseCode(_PAY_FAILED, "支付失败");
+
+    public final static int _CANCEL_ERROR = 50101;
+    public final static BaseCode CANCEL_ERROR = new BaseCode(_CANCEL_ERROR, "取消失败");
+
 }

+ 30 - 0
rankin-trade-service/src/main/java/cn/rankin/tradeservice/controller/OrderController.java

@@ -1,16 +1,31 @@
 package cn.rankin.tradeservice.controller;
 
 import cn.rankin.common.utils.api.model.APIResult;
+import cn.rankin.common.utils.api.page.Page;
+import cn.rankin.common.utils.enums.OrderStatusEnum;
+import cn.rankin.data.api.trade.dto.OrderSearchDTO;
 import cn.rankin.data.api.trade.dto.UserOrderDTO;
 import cn.rankin.data.api.trade.entity.UserOrder;
 import cn.rankin.tradeservice.service.OrderService;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.propertyeditors.CustomDateEditor;
+import org.springframework.web.bind.WebDataBinder;
 import org.springframework.web.bind.annotation.*;
 
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
 @RestController
 @RequestMapping(value = "/order")
 public class OrderController {
 
+    @InitBinder
+    public void initBinder(WebDataBinder binder) {
+        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
+        dateFormat.setLenient(false);
+        binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));   //true:允许输入空值,false:不能为空值
+    }
+
     @Autowired
     private OrderService orderService;
 
@@ -24,4 +39,19 @@ public class OrderController {
     public APIResult<UserOrder> getOrder(@PathVariable String orderId) {
         return orderService.getOrder(orderId);
     }
+
+    @RequestMapping(value = "/list", method = RequestMethod.GET)
+    public APIResult<Page<UserOrder>> search(OrderSearchDTO searchDTO) {
+        return orderService.search(searchDTO);
+    }
+
+    @RequestMapping(value = "/pay/{orderId}", method = RequestMethod.POST)
+    public APIResult<Boolean> pay(@PathVariable("orderId") String orderId, @RequestParam("status") OrderStatusEnum status) {
+        return orderService.pay(orderId, status);
+    }
+
+    @RequestMapping(value = "/pay/{orderId}", method = RequestMethod.DELETE)
+    public APIResult<Boolean> cancel(@PathVariable("orderId") String orderId) {
+        return orderService.cancel(orderId);
+    }
 }

+ 22 - 0
rankin-trade-service/src/main/java/cn/rankin/tradeservice/repository/OrderRepository.java

@@ -2,6 +2,28 @@ package cn.rankin.tradeservice.repository;
 
 import cn.rankin.common.utils.jpa.BasicJpaRepository;
 import cn.rankin.data.api.trade.entity.UserOrder;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
 
 public interface OrderRepository extends BasicJpaRepository<UserOrder, String> {
+
+    @Modifying(clearAutomatically = true)
+    @Query(value = "update UserOrder o set o.status = 1 where o.status = 0 and o.id = ?1")
+    Integer pay(String orderId);
+
+    @Modifying(clearAutomatically = true)
+    @Query(value = "update UserOrder o set o.status = 4 where o.status = 1 and o.id = ?1")
+    Integer send(String orderId);
+
+    @Modifying(clearAutomatically = true)
+    @Query(value = "update UserOrder o set o.status = 5 where o.status = 4 and o.id = ?1")
+    Integer received(String orderId);
+
+    @Modifying(clearAutomatically = true)
+    @Query(value = "update UserOrder o set o.status = 6 where o.status = 5 and o.id = ?1")
+    Integer complete(String orderId);
+
+    @Modifying(clearAutomatically = true)
+    @Query(value = "update UserOrder o set o.status = 2 where o.status = 0 and o.id = ?1")
+    Integer cancel(String orderId);
 }

+ 7 - 0
rankin-trade-service/src/main/java/cn/rankin/tradeservice/repository/ProductSoldSnapshotRepository.java

@@ -0,0 +1,7 @@
+package cn.rankin.tradeservice.repository;
+
+import cn.rankin.common.utils.jpa.BasicJpaRepository;
+import cn.rankin.data.api.trade.entity.ProductSoldSnapshot;
+
+public interface ProductSoldSnapshotRepository extends BasicJpaRepository<ProductSoldSnapshot, String> {
+}

+ 95 - 0
rankin-trade-service/src/main/java/cn/rankin/tradeservice/service/OrderService.java

@@ -1,7 +1,11 @@
 package cn.rankin.tradeservice.service;
 
 import cn.rankin.common.utils.api.model.APIResult;
+import cn.rankin.common.utils.api.page.Page;
+import cn.rankin.common.utils.enums.BaseOrderEnum;
 import cn.rankin.common.utils.enums.OrderStatusEnum;
+import cn.rankin.common.utils.util.JpaSortUtil;
+import cn.rankin.data.api.trade.dto.OrderSearchDTO;
 import cn.rankin.data.api.trade.dto.UserOrderDTO;
 import cn.rankin.data.api.trade.dto.OrderGoodsDTO;
 import cn.rankin.data.api.trade.entity.OrderGoods;
@@ -9,9 +13,18 @@ import cn.rankin.data.api.trade.entity.UserOrder;
 import cn.rankin.tradeservice.code.TradeServiceAPICode;
 import cn.rankin.tradeservice.repository.OrderRepository;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.jpa.domain.Specification;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
 
+import javax.persistence.criteria.CriteriaBuilder;
+import javax.persistence.criteria.CriteriaQuery;
+import javax.persistence.criteria.Predicate;
+import javax.persistence.criteria.Root;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.LinkedHashMap;
 import java.util.List;
 
 import static cn.rankin.tradeservice.utils.DTOConverter.convert;
@@ -26,6 +39,33 @@ public class OrderService {
     private OrderGoodsService orderGoodsService;
 
     @Transactional
+    public APIResult<Boolean> pay(String orderId, OrderStatusEnum status) {
+        Integer count = 0;
+        if (status.equals(OrderStatusEnum.PAYOK)) {
+            count = orderRepository.pay(orderId);
+        }else if (status.equals(OrderStatusEnum.SEND)) {
+            count = orderRepository.send(orderId);
+        }else if (status.equals(OrderStatusEnum.RECEIVED)) {
+            count = orderRepository.received(orderId);
+        }else if (status.equals(OrderStatusEnum.COMPLETE)) {
+            count = orderRepository.complete(orderId);
+        }
+        if (count > 0) {
+            return APIResult.ok();
+        }
+        return APIResult.error(TradeServiceAPICode.PAY_FAILED);
+    }
+
+    @Transactional
+    public APIResult<Boolean> cancel(String orderId) {
+        Integer count = orderRepository.cancel(orderId);
+        if (count > 0) {
+            return APIResult.ok();
+        }
+        return APIResult.error(TradeServiceAPICode.CANCEL_ERROR);
+    }
+
+    @Transactional
     public APIResult<UserOrder> create(UserOrderDTO userOrderDTO) {
         UserOrder userOrder = convert(userOrderDTO);
         userOrder.setStatus(OrderStatusEnum.UNPAID);
@@ -49,4 +89,59 @@ public class OrderService {
         return APIResult.ok(userOrder);
     }
 
+    public APIResult<Page<UserOrder>> search(OrderSearchDTO searchDTO) {
+        Specification<UserOrder> specification = new Specification<UserOrder>() {
+            @Override
+            public Predicate toPredicate(Root<UserOrder> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
+                return OrderService.this.toPredicate(root, query, cb, searchDTO);
+            }
+        };
+
+        Long count = orderRepository.count(specification);
+        Page<UserOrder> page = new Page<>(count, searchDTO.getPageNo(), searchDTO.getPageSize());
+
+        Long start = page.getStart();
+        if (count == 0) {
+            return APIResult.ok(page);
+        }
+
+        LinkedHashMap<String, BaseOrderEnum> sort = new LinkedHashMap<>();
+        sort.put("gmtModified", BaseOrderEnum.DESC);
+        List<UserOrder> userOrderList = orderRepository.find(specification, start, page.getPageSize(), JpaSortUtil.sort(sort));
+        page.setList(userOrderList);
+
+        return APIResult.ok(page);
+    }
+
+    public Predicate toPredicate(Root<UserOrder> root, CriteriaQuery<?> query, CriteriaBuilder cb, OrderSearchDTO searchDTO) {
+        // 存放查询条件
+        List<Predicate> list = new ArrayList<Predicate>();
+        Predicate tmp = null;
+
+        List<String> userList = searchDTO.getUid();
+        if (!CollectionUtils.isEmpty(userList)) {
+            tmp = root.get("uid").in(userList);
+            list.add(tmp);
+        }
+
+        OrderStatusEnum status = searchDTO.getStatus();
+        if (status != null) {
+            tmp = cb.equal(root.get("status"), status);
+            list.add(tmp);
+        }
+
+        Date beginTime = searchDTO.getBeginTime();
+        if (beginTime != null) {
+            tmp = cb.greaterThanOrEqualTo(root.get("gmtCreated"), beginTime);
+            list.add(tmp);
+        }
+
+        Date endTime = searchDTO.getEndTime();
+        if (endTime != null) {
+            tmp = cb.lessThanOrEqualTo(root.get("gmtCreated"), endTime);
+            list.add(tmp);
+        }
+        // where条件
+        return list.isEmpty() ? null : cb.and(list.toArray(new Predicate[list.size()]));
+    }
 }

+ 13 - 0
rankin-trade-service/src/main/java/cn/rankin/tradeservice/service/ProductSoldSnapshotService.java

@@ -0,0 +1,13 @@
+package cn.rankin.tradeservice.service;
+
+import cn.rankin.tradeservice.repository.ProductSoldSnapshotRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class ProductSoldSnapshotService {
+
+    @Autowired
+    private ProductSoldSnapshotRepository productSoldSnapshotRepository;
+
+}

+ 27 - 0
rankin-trade-service/src/test/java/cn/rankin/tradeservice/service/OrderServiceTest.java

@@ -0,0 +1,27 @@
+package cn.rankin.tradeservice.service;
+
+import cn.rankin.common.utils.api.model.APIResult;
+import cn.rankin.common.utils.api.page.Page;
+import cn.rankin.data.api.trade.dto.OrderSearchDTO;
+import cn.rankin.data.api.trade.entity.UserOrder;
+import cn.rankin.tradeservice.TradeServiceApplication;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+import static org.junit.Assert.*;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@SpringBootTest(classes = TradeServiceApplication.class)
+public class OrderServiceTest {
+
+    @Autowired
+    private OrderService orderService;
+
+    @Test
+    public void search() throws Exception {
+    }
+
+}

+ 6 - 1
rankin-user-service/src/main/java/cn/rankin/userservice/code/UserServiceAPICode.java

@@ -11,7 +11,12 @@ public class UserServiceAPICode extends APICode {
     public final static int _C_REMOTE_USER_REGISTER_ERROR = 21001;
     public final static BaseCode REMOTE_USER_REGISTER_ERROR = new BaseCode(_C_REMOTE_USER_REGISTER_ERROR, "远程注册用户失败");
 
-    public final static int _C_REMOTE_USER_LOGIN_ERROR = 21001;
+    public final static int _C_REMOTE_USER_LOGIN_ERROR = 21002;
     public final static BaseCode REMOTE_USER_LOGIN_ERROR = new BaseCode(_C_REMOTE_USER_LOGIN_ERROR, "远程登录用户失败");
 
+    public final static int _C_MONEY_PAY_FAILED = 22001;
+    public final static BaseCode PAY_FAILED = new BaseCode(_C_MONEY_PAY_FAILED, "支付失败");
+
+    public final static int _C_MONEY_NOT_ENOUGH = 22002;
+    public final static BaseCode MONEY_NOT_ENOUGH = new BaseCode(_C_MONEY_NOT_ENOUGH, "余额不足");
 }

+ 35 - 4
rankin-user-service/src/main/java/cn/rankin/userservice/controller/CampusController.java

@@ -14,11 +14,9 @@ import cn.rankin.userservice.utils.StudentNo;
 import cn.rankin.userservice.utils.StudentNumberUtil;
 import org.apache.commons.lang.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.CollectionUtils;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestMethod;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
 
 import javax.validation.Valid;
 import java.util.*;
@@ -42,6 +40,11 @@ public class CampusController {
             campus.setName("%" + name + "%");
         }
 
+        String code = searchDTO.getCode();
+        if (!StringUtils.isEmpty(code)) {
+            campus.setCode("%" + code + "%");
+        }
+
         String merchantId = searchDTO.getMerchantId();
         if (!StringUtils.isEmpty(merchantId)) {
             campus.setMerchantId(merchantId);
@@ -61,6 +64,27 @@ public class CampusController {
         return apiResult;
     }
 
+    @RequestMapping(method = RequestMethod.GET)
+    public APIResult<List<Campus>> findByCondition(CampusSearchDTO searchDTO) {
+        Campus campus = new Campus();
+
+        String name = searchDTO.getName();
+        if (!StringUtils.isEmpty(name)) {
+            campus.setName("%" + name + "%");
+        }
+
+        String code = searchDTO.getCode();
+        if (!StringUtils.isEmpty(code)) {
+            campus.setCode("%" + code + "%");
+        }
+
+        // sort
+        LinkedHashMap<String, BaseOrderEnum> sort = new LinkedHashMap<>();
+        sort.put("gmtModified", BaseOrderEnum.DESC);
+
+        return campusService.find(campus, sort);
+    }
+
     @RequestMapping(value = "/{id}", method = RequestMethod.GET)
     public APIResult<Campus> getCampus(String id) {
         APIResult<Campus> result = campusService.getCampus(id);
@@ -72,6 +96,7 @@ public class CampusController {
         return result;
     }
 
+    @Transactional
     @RequestMapping(method = RequestMethod.POST)
     public APIResult<Campus> create(@RequestBody @Valid CampusDTO campusDTO) {
         String merchantId = campusDTO.getMerchantId();
@@ -108,6 +133,12 @@ public class CampusController {
         return result;
     }
 
+    @RequestMapping(value = "/ids", method = RequestMethod.GET)
+    public APIResult<Map<String, Campus>> findByIds(@RequestParam("id") List<String> campusIds) {
+        Map<String, Campus> campusMap = campusService.getCampusMap(campusIds);
+        return APIResult.ok(campusMap);
+    }
+
     @RequestMapping(method = RequestMethod.PUT)
     public APIResult<Campus> update(@RequestBody @Valid CampusDTO campusDTO) {
         String id = campusDTO.getId();

+ 1 - 1
rankin-user-service/src/main/java/cn/rankin/userservice/controller/CmsUserController.java

@@ -70,7 +70,7 @@ public class CmsUserController {
     }
 
     @RequestMapping(value = "/user", method = RequestMethod.POST)
-    public APIResult<CmsUser> create(CmsUserDTO userDTO) {
+    public APIResult<CmsUser> create(@RequestBody CmsUserDTO userDTO) {
         String merchantId = userDTO.getMerchantId();
         if (StringUtils.isEmpty(merchantId)) {
             return APIResult.error(UserServiceAPICode.PARAMETER_ERROR);

+ 7 - 0
rankin-user-service/src/main/java/cn/rankin/userservice/controller/MerchantController.java

@@ -6,6 +6,7 @@ import cn.rankin.common.utils.api.page.Page;
 import cn.rankin.data.api.user.dto.MerchantDTO;
 import cn.rankin.data.api.user.dto.MerchantSearchDTO;
 import cn.rankin.common.utils.enums.BaseOrderEnum;
+import cn.rankin.data.api.user.dto.MerchantPayDTO;
 import cn.rankin.data.api.user.entity.Merchant;
 import cn.rankin.data.api.user.entity.CmsUser;
 import cn.rankin.userservice.service.MerchantService;
@@ -47,6 +48,7 @@ public class MerchantController {
         }
 
         merchant.setStatus(searchDTO.getStatus());
+        merchant.setDomain(searchDTO.getDomain());
 
         // sort
         LinkedHashMap<String, BaseOrderEnum> sort = new LinkedHashMap<>();
@@ -90,6 +92,11 @@ public class MerchantController {
         return result;
     }
 
+    @RequestMapping(value = "/pay/{merchantId}", method = RequestMethod.POST)
+    public APIResult<Boolean> pay(@PathVariable("merchantId") String merchantId, @RequestBody MerchantPayDTO payDTO) {
+        return merchantService.pay(merchantId, payDTO.getQuantity(), payDTO.getType(), payDTO.getReceiptId(), payDTO.getNote());
+    }
+
     @RequestMapping(value = "/ids", method = RequestMethod.GET)
     public APIResult<Map<String, Merchant>> getMerchantList(@RequestParam("id") List<String> idList) {
         return merchantService.findByIds(idList);

+ 41 - 2
rankin-user-service/src/main/java/cn/rankin/userservice/controller/TerminalUserController.java

@@ -77,6 +77,31 @@ public class TerminalUserController {
         return result;
     }
 
+    @RequestMapping(method = RequestMethod.GET)
+    public APIResult<List<TerminalUser>> findByCondition(TerminalUserSearchDTO terminalUserSearchDTO) {
+        TerminalUser terminalUser = new TerminalUser();
+
+        String code = terminalUserSearchDTO.getCode();
+        String campusId = terminalUserSearchDTO.getCampusId();
+        if (!StringUtils.isEmpty(code)) {
+            terminalUser.setCode("%" + code + "%");
+        } else if (!StringUtils.isEmpty(campusId)) {
+            terminalUser.setCampusId(campusId);
+        }
+
+        String merchantId = terminalUserSearchDTO.getMerchantId();
+        if (!StringUtils.isEmpty(merchantId)) {
+            terminalUser.setMerchantId(merchantId);
+        }
+
+        LinkedHashMap<String, BaseOrderEnum> sort = new LinkedHashMap<>();
+        sort.put("gmtModified", BaseOrderEnum.DESC);
+
+        List<TerminalUser> terminalUserList = terminalUserService.find(terminalUser, sort);
+        setUserInfo(terminalUserList);
+        return APIResult.ok(terminalUserList);
+    }
+
     @Transactional
     @RequestMapping(method = RequestMethod.POST)
     public APIResult<TerminalUser> create(HttpServletRequest request, @RequestBody @Valid TerminalUserDTO terminalUserDTO) {
@@ -141,7 +166,7 @@ public class TerminalUserController {
             return APIResult.error(UserServiceAPICode.PARAMETER_ERROR);
         }
 
-        TerminalUser terminalUser = terminalUserService.findOne(id);
+        TerminalUser terminalUser = terminalUserService.find(id);
         if (terminalUser == null) {
             return APIResult.error(UserServiceAPICode.NOT_EXISTS);
         }
@@ -167,7 +192,7 @@ public class TerminalUserController {
 
     @RequestMapping(value = "/{id}", method = RequestMethod.GET)
     public APIResult<TerminalUser> getUser(@PathVariable("id") String id) {
-        TerminalUser terminalUser = terminalUserService.findOne(id);
+        TerminalUser terminalUser = terminalUserService.find(id);
         if (terminalUser == null) {
             return APIResult.error(UserServiceAPICode.NOT_EXISTS);
         }
@@ -176,6 +201,20 @@ public class TerminalUserController {
         return APIResult.ok(terminalUser);
     }
 
+    @RequestMapping(value = "/campus/ids", method = RequestMethod.GET)
+    public APIResult<List<TerminalUser>> findByCampusIds(@RequestParam("id") List<String> campusIds) {
+        List<TerminalUser> terminalUserList = terminalUserService.findByCampusIds(campusIds);
+        setUserInfo(terminalUserList);
+        return APIResult.ok(terminalUserList);
+    }
+
+    @RequestMapping(value = "/ids", method = RequestMethod.GET)
+    public APIResult<List<TerminalUser>> findByIds(@RequestParam("id") List<String> ids) {
+        List<TerminalUser> terminalUserList = terminalUserService.findByIds(ids);
+        setUserInfo(terminalUserList);
+        return APIResult.ok(terminalUserList);
+    }
+
     // 禁用终端用户
     @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
     public APIResult<Boolean> delete(@PathVariable("id") String id) {

+ 7 - 0
rankin-user-service/src/main/java/cn/rankin/userservice/repository/LedgerRepository.java

@@ -0,0 +1,7 @@
+package cn.rankin.userservice.repository;
+
+import cn.rankin.common.utils.jpa.BasicJpaRepository;
+import cn.rankin.data.api.user.entity.Ledger;
+
+public interface LedgerRepository extends BasicJpaRepository<Ledger, String> {
+}

+ 5 - 0
rankin-user-service/src/main/java/cn/rankin/userservice/repository/MerchantRepository.java

@@ -9,6 +9,7 @@ import org.springframework.data.repository.query.Param;
 import org.springframework.stereotype.Repository;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.math.BigDecimal;
 import java.util.List;
 
 @Repository
@@ -31,4 +32,8 @@ public interface MerchantRepository  extends BasicJpaRepository<Merchant, String
     @Modifying(clearAutomatically = true)
     @Query("update Merchant m set m.status = :status where m.id = :id ")
     int updateStatusById(@Param("id") String id, @Param("status") BaseStatusEnum status);
+
+    @Modifying(clearAutomatically = true)
+    @Query(value = "update Merchant m set m.balance = m.balance - ?2 where m.balance - ?2 > 0 and m.id = ?1")
+    Integer pay(String merchantId, BigDecimal quantity);
 }

+ 4 - 0
rankin-user-service/src/main/java/cn/rankin/userservice/repository/TerminalUserRepository.java

@@ -7,11 +7,15 @@ import org.springframework.data.jpa.repository.Modifying;
 import org.springframework.data.jpa.repository.Query;
 import org.springframework.data.repository.query.Param;
 
+import java.util.List;
+
 public interface TerminalUserRepository extends BasicJpaRepository<TerminalUser, String> {
 
     @Query(value = "select max(t.serialNo) from TerminalUser t where t.campusId = :campusId")
     Integer findMaxNo(@Param("campusId") String campusId);
 
+    List<TerminalUser> findByCampusIdIn(List<String> campusIds);
+
     @Modifying(clearAutomatically = true)
     @Query(value = "update TerminalUser t set t.status = :status where t.id = :id")
     Integer updateStatusById(@Param("status") BaseStatusEnum status, @Param("id") String id);

+ 5 - 0
rankin-user-service/src/main/java/cn/rankin/userservice/service/CampusService.java

@@ -62,6 +62,11 @@ public class CampusService {
         return APIResult.ok(page);
     }
 
+    public APIResult<List<Campus>> find(Campus campus, LinkedHashMap<String, BaseOrderEnum> sort) {
+        List<Campus> campusList = campusRepository.find(campus, JpaSortUtil.sort(sort));
+        return APIResult.ok(campusList);
+    }
+
     public APIResult<Campus> getCampus(String campusId) {
         Campus campus = campusRepository.find(campusId);
         if (campus != null) {

+ 10 - 0
rankin-user-service/src/main/java/cn/rankin/userservice/service/CmsUserService.java

@@ -6,6 +6,7 @@ import cn.rankin.data.api.user.dto.CmsUserDTO;
 import cn.rankin.common.utils.enums.BaseOrderEnum;
 import cn.rankin.common.utils.enums.BaseStatusEnum;
 import cn.rankin.common.utils.util.JpaSortUtil;
+import cn.rankin.data.api.user.entity.Merchant;
 import cn.rankin.userservice.code.UserServiceAPICode;
 import cn.rankin.data.api.user.entity.CmsUser;
 import cn.rankin.userservice.repository.CmsUserRepository;
@@ -23,6 +24,9 @@ public class CmsUserService {
     @Autowired
     private CmsUserRepository cmsUserRepository;
 
+    @Autowired
+    private MerchantService merchantService;
+
     public List<CmsUser> findMerchantAdminList(String merchantId) {
         CmsUser cmsUser = new CmsUser();
         cmsUser.setIsAdmin(Boolean.TRUE);
@@ -56,6 +60,12 @@ public class CmsUserService {
     @Transactional
     public APIResult<CmsUser> create(CmsUserDTO userDTO) {
         CmsUser user = convert(userDTO);
+        String merchantId = user.getMerchantId();
+        Merchant merchant = merchantService.findOne(merchantId);
+        if (merchant == null) {
+            return APIResult.error(UserServiceAPICode.NOT_EXISTS);
+        }
+        user.setDomain(merchant.getDomain());
         CmsUser result = cmsUserRepository.save(user);
         return APIResult.ok(result);
     }

+ 25 - 0
rankin-user-service/src/main/java/cn/rankin/userservice/service/LedgerService.java

@@ -0,0 +1,25 @@
+package cn.rankin.userservice.service;
+
+import cn.rankin.common.utils.enums.LedgerTypeEnum;
+import cn.rankin.data.api.user.entity.Ledger;
+import cn.rankin.userservice.repository.LedgerRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import javax.transaction.Transactional;
+import java.math.BigDecimal;
+
+@Service
+public class LedgerService {
+
+    @Autowired
+    private LedgerRepository ledgerRepository;
+
+    @Transactional
+    public void insert(String merchantId, BigDecimal quantity, BigDecimal balanceBefore,
+                       LedgerTypeEnum type, String receiptId, String note) {
+        Ledger ledger = new Ledger(merchantId, balanceBefore, quantity, type, receiptId, note);
+        ledgerRepository.save(ledger);
+    }
+
+}

+ 27 - 0
rankin-user-service/src/main/java/cn/rankin/userservice/service/MerchantService.java

@@ -3,6 +3,7 @@ package cn.rankin.userservice.service;
 import cn.rankin.common.utils.api.model.APICode;
 import cn.rankin.common.utils.api.model.APIResult;
 import cn.rankin.common.utils.api.page.Page;
+import cn.rankin.common.utils.enums.LedgerTypeEnum;
 import cn.rankin.data.api.user.dto.MerchantDTO;
 import cn.rankin.common.utils.enums.BaseOrderEnum;
 import cn.rankin.common.utils.enums.BaseStatusEnum;
@@ -15,6 +16,7 @@ import org.springframework.stereotype.Service;
 import org.springframework.util.CollectionUtils;
 
 import javax.transaction.Transactional;
+import java.math.BigDecimal;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -28,6 +30,31 @@ public class MerchantService {
     @Autowired
     private MerchantRepository merchantRepository;
 
+    @Autowired
+    private LedgerService ledgerService;
+
+    @Transactional
+    public APIResult<Boolean> pay(String merchantId, BigDecimal quantity, LedgerTypeEnum type, String receiptId, String note) {
+        Merchant merchant = merchantRepository.find(merchantId);
+        if (merchant == null || !merchant.getStatus().equals(BaseStatusEnum.NORMAL)) {
+            return APIResult.error(UserServiceAPICode.NOT_EXISTS);
+        }
+
+        if (merchant.getBalance().compareTo(quantity) < 0) {
+            return APIResult.error(UserServiceAPICode.MONEY_NOT_ENOUGH);
+        }
+
+        Integer count = merchantRepository.pay(merchantId, quantity);
+        if (count <= 0) {
+            return APIResult.error(UserServiceAPICode.PAY_FAILED);
+        }
+
+        // 插入流水
+        ledgerService.insert(merchantId, quantity.negate(), merchant.getBalance(), type, receiptId, note);
+
+        return APIResult.ok();
+    }
+
     public APIResult<Page<Merchant>> search(Merchant merchant, Integer pageNo, Integer pageSize, LinkedHashMap<String, BaseOrderEnum> sort) {
         Long count = merchantRepository.count(merchant);
         Page<Merchant> page = new Page<>(count, pageNo, pageSize);

+ 15 - 1
rankin-user-service/src/main/java/cn/rankin/userservice/service/TerminalUserService.java

@@ -36,10 +36,24 @@ public class TerminalUserService {
         return serialNo + 1;
     }
 
-    public TerminalUser findOne(String id) {
+    public TerminalUser find(String id) {
         return terminalUserRepository.find(id);
     }
 
+    public List<TerminalUser> find(TerminalUser terminalUser, LinkedHashMap<String, BaseOrderEnum> sort) {
+        List<TerminalUser> terminalUserList = terminalUserRepository.find(terminalUser, JpaSortUtil.sort(sort));
+        return terminalUserList;
+    }
+
+    public List<TerminalUser> findByIds(List<String> ids) {
+        return terminalUserRepository.findByIds(ids);
+    }
+
+    public List<TerminalUser> findByCampusIds(List<String> campusIds) {
+        List<TerminalUser> terminalUserList = terminalUserRepository.findByCampusIdIn(campusIds);
+        return terminalUserList;
+    }
+
     public APIResult<Page<TerminalUser>> search(TerminalUser terminalUser, Integer pageNo, Integer pageSize, LinkedHashMap<String, BaseOrderEnum> sort) {
         Long count = terminalUserRepository.count(terminalUser);
         Page<TerminalUser> page = new Page<>(count, pageNo, pageSize);

+ 1 - 1
rankin-user-service/src/test/java/cn/rankin/userservice/repository/CampusRepositoryTest.java

@@ -17,7 +17,7 @@ public class CampusRepositoryTest {
 
     @Test
     public void findMaxNo() {
-        Integer max = campusRepository.findMaxNo("1749ad85c0494c27a7cf2b6f10cd064", "11");
+        Integer max = campusRepository.findMaxNo("1749ad85c0494c27a7cf2b6f10cd0648", "14");
     }
 
     @Test