【基於Spring Boot 企業微信點餐系統學習】


一 .【項目設計】

          ①【角色划分】

                     買家(手機端)      賣家(PC端)

      ②【功能模塊划分】

                     

      

          ③【部署架構】

                     

      

              買家端(手機)和賣家端(PC)都會發出請求到Nginx服務器(搭建在虛擬機上,服務器上包括買家端的前端資源),

                    如果請求的是后端接口,Nginx會轉發到Tomcat服務器(java項目所在),如果接口做了緩沖就會訪問Redis服務,沒有

        緩沖則會訪問到mysql數據庫

          ④【數據庫設計】

                   1》 表與表之間的關系

                         

                  2》 建表sql                  

# 微信點餐數據庫


```sql
-- 類目
create table `product_category` (
    `category_id` int not null auto_increment,
    `category_name` varchar(64) not null comment '類目名字',
    `category_type` int not null comment '類目編號',
    `create_time` timestamp not null default current_timestamp comment '創建時間',
    `update_time` timestamp not null default current_timestamp on update current_timestamp comment '修改時間',
    primary key (`category_id`),
    UNIQUE KEY `uqe_category_type` (`category_type`)
);

-- 商品
create table `product_info` (
    `product_id` varchar(32) not null,
    `product_name` varchar(64) not null comment '商品名稱',
    `product_price` decimal(8,2) not null comment '單價',
    `product_stock` int not null comment '庫存',
    `product_description` varchar(64) comment '描述',
    `product_icon` varchar(512) comment '小圖',
    `product_status` tinyint(3) DEFAULT '0' COMMENT '商品狀態,0正常1下架',
    `category_type` int not null comment '類目編號',
    `create_time` timestamp not null default current_timestamp comment '創建時間',
    `update_time` timestamp not null default current_timestamp on update current_timestamp comment '修改時間',
    primary key (`product_id`)
);

-- 訂單
create table `order_master` (
    `order_id` varchar(32) not null,
    `buyer_name` varchar(32) not null comment '買家名字',
    `buyer_phone` varchar(32) not null comment '買家電話',
    `buyer_address` varchar(128) not null comment '買家地址',
    `buyer_openid` varchar(64) not null comment '買家微信openid',
    `order_amount` decimal(8,2) not null comment '訂單總金額',
    `order_status` tinyint(3) not null default '0' comment '訂單狀態, 默認為新下單',
    `pay_status` tinyint(3) not null default '0' comment '支付狀態, 默認未支付',
    `create_time` timestamp not null default current_timestamp comment '創建時間',
    `update_time` timestamp not null default current_timestamp on update current_timestamp comment '修改時間',
    primary key (`order_id`),
    key `idx_buyer_openid` (`buyer_openid`)
);

-- 訂單商品
create table `order_detail` (
    `detail_id` varchar(32) not null,
    `order_id` varchar(32) not null,
    `product_id` varchar(32) not null,
    `product_name` varchar(64) not null comment '商品名稱',
    `product_price` decimal(8,2) not null comment '當前價格,單位分',
    `product_quantity` int not null comment '數量',
    `product_icon` varchar(512) comment '小圖',
    `create_time` timestamp not null default current_timestamp comment '創建時間',
    `update_time` timestamp not null default current_timestamp on update current_timestamp comment '修改時間',
    primary key (`detail_id`),
    key `idx_order_id` (`order_id`)
);

-- 賣家(登錄后台使用, 賣家登錄之后可能直接采用微信掃碼登錄,不使用賬號密碼)
create table `seller_info` (
    `seller_id` varchar(32) not null,
    `username` varchar(32) not null,
    `password` varchar(32) not null,
    `openid` varchar(64) not null comment '微信openid',
    `create_time` timestamp not null default current_timestamp comment '創建時間',
    `update_time` timestamp not null default current_timestamp on update current_timestamp comment '修改時間',
    primary key (`seller_id`)
) comment '賣家信息表';

```

 

 

二 .【開發環境搭建】

        ①虛擬機centos7.3 ---工具:VirtualBox

                jdk 1.8.0_111

                nginx 1.11.7

                mysql 5.7.17

                redis 3.2.8

          ②IDE:Intellij IDEA 2018.1.3

                jdk: 1.8.0_171

        maven: 3.3.9

        springboot-version: 1.5.21.RELEASE

三 .【開發涉及軟件】

               Postman   測試接口

             Intellij IDEA  代碼開發

     Oracle VM VirtualBox  使用虛擬機

               Fiddler 4   抓包

     natapp  內網穿透工具

       RedisDesktopManager   查看管理redis數據

四 .【項目目錄結構】     

                       

五 .【Maven依賴】     

<?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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.21.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.imoooc</groupId>
<artifactId>sell</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>sell</name>
<description>Demo project for Spring Boot</description>

<properties>
<java.version>1.8</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>

<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>

<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-mp</artifactId>
<version>2.7.0</version>
</dependency>

<dependency>
<groupId>cn.springboot</groupId>
<artifactId>best-pay-sdk</artifactId>
<version>1.1.0</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.2.0</version>
</dependency>

</dependencies>

<build>
<finalName>sell</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

六 .【application.yml】

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    username: root
    password: 123456
    url: jdbc:mysql://192.168.11.106/sell?characterEncoding=utf-8&useSSL=false
  jpa:
    show-sql: true
  jackson:
    default-property-inclusion: non_null
  redis:
    host: 192.168.11.106
    port: 6379
server:
  context-path: /sell

wechat:
  # 測試公眾賬號 ,授權
  mpAppId: wx9f*******01398a6
  mpAppSecret: d9db8b20**********f0a2f0eed4fc53

  #師兄openId appid
  openIdPro: oTgZpw**********4upolmjp7Q1c
  mpAppIdPro: wxd******01713c658

  # 開放平台,賣家掃碼登錄用
  openAppId: wx6ad******af67d87
  openAppSecret: 91a2************fb7e9f9079108e2e

  #支付/商戶號
  mchId: 1*****9312
  mchKey: C52***********964D494B0735025
  # 發起支付不需要證書,退款需要
  keyPath: /weChatSystem/weixin_cert/h5.p12
  notifyUrl : http://lcb666.natapp1.cc/sell/pay/notify
  templateId:
      orderStatus: 17d4xlTJDUQ27yERFtS2KDeaZ6OImf_7aqtOMJ9AKe4

projectUrl:
  wechatMpAuthorize: http://lcb666.natapp1.cc
  wechatOpenAuthorize: http://sell.springboot.cn
  sell: http://lcb666.natapp1.cc
logging:
  level:
    com.imoooc.dataobject.mapper: trace
mybatis:
  mapper-locations: classpath:mapper/*.xml

 七 .【開發相關】

         SpringBoot+Hibernate+JPA

               https://www.cnblogs.com/slimshady/p/11309017.html

           ②枚舉的使用(自定義異常)             

@Getter
public class SellException extends RuntimeException {

    private Integer code;

    public SellException(ResultEnum resultEnum) {
        super(resultEnum.getMessage());

        this.code = resultEnum.getCode();
    }

    public SellException(Integer code,String message){
        super(message);
        this.code=code;
    }
}
@Getter
public enum  ResultEnum {

    SUCCESS(0,"成功"),
    PARAM_ERROR(1,"參數不正確"),

    PRODUCT_NOT_EXIST(10,"商品不存在"),
    PRODUCT_STOCK_ERROR(11,"商品庫存不正確"),
    ORDER_NOT_EXIST(12,"訂單不存在"),
    ORDERDETAIL_NOT_EXIST(13,"訂單詳情不存在"),
    ORDER_STATUS_ERROR(14,"訂單狀態不正確"),
    ORDER_UPDATE_FAIL(15,"訂單更新失敗"),
    ORDER_DETAIL_EMPTY(16,"訂單詳情為空"),
    ORDER_PAY_STATUS_ERROR(17,"訂單支付狀態不正確"),
    CART_EMPRT(18,"購物車為空"),
    ORDER_OWENER_ERROR(19,"該訂單不屬於當前用戶"),
    WECHAT_MP_ERROR(20,"微信公眾賬號方面錯誤"),
    WXPAY_NOTIFY_MONEY_VERIFY_ERROR(21,"微信支付異步通知金額校驗不通過"),
    ODERR_CANCEL_SUCCESS(22,"訂單取消成功"),
    ODERR_FINSH_SUCCESS(23,"訂單完結成功"),
    PRODUCT_STATUS_error(24,"商品狀態不正確"),
    LOGIN_FAIL(25,"登錄失敗,登錄信息不正確"),
    LOGOUT_SUCCESS(26,"登出成功"),
    ;
    private Integer code;

    private String message;

    ResultEnum(Integer code, String message) {
        this.code = code;
        this.message = message;
    }

           ③斷言

Assert.assertTrue("查詢所有訂單表",orderDTOPage.getTotalElements()<0);
Assert.assertNotEquals(0,orderDTOPage.getTotalElements());

           ④lambda表達式(java8)     

//1.查詢類目(一次性查詢)
//傳統方法
List<Integer> categoryTypeList=new ArrayList<>();
for (ProductInfo productInfo:productInfoList) {
  categoryTypeList.add(productInfo.getCategoryType());
}
//精簡方法(java8 , lambda)
List<Integer> categoryTypeList= productInfoList.stream().map(e -> e.getCategoryType()).collect(Collectors.toList());

            ⑤Cookies,Redis使用

//2.設置token至redis
    String token = UUID.randomUUID().toString();
    Integer expire = RedisConstant.EXPIRE;
    redisTemplate.opsForValue().set(String.format(RedisConstant.TOKEN_PREFIX,token),openid,expire,TimeUnit.SECONDS);
//3.設置token至cookie
    CookieUtil.set(response,CookieConstant.TOKEN,token,expire);
//2.清除redis
    redisTemplate.opsForValue().getOperations().delete(String.format(RedisConstant.TOKEN_PREFIX, cookie.getValue()));
//3.清除cookie
    CookieUtil.set(response,CookieConstant.TOKEN,null,0);

       ⑥JavaBean讀取配置文件

     application.yml

projectUrl:
  wechatMpAuthorize: http://lcb666.natapp1.cc
  wechatOpenAuthorize: http://sell.springboot.cn
  sell: http://lcb666.natapp1.cc
@Component
@Data
@ConfigurationProperties(prefix = "projectUrl")
public class ProjectUrlConfig {

    /**
     * 微信公眾平台授權url
     */
    public String wechatMpAuthorize;

    /**
     * 微信開放平台授權url
     */
    public String wechatOpenAuthorize;

    /**
     * 項目url
     */
    public String sell;
}

           ⑦攔截登錄異常

                      @ControllerAdvice 注解,可以用於定義@ExceptionHandler、@InitBinder、@ModelAttribute,並應用

               到所有@RequestMapping中,該注解很多時候是應用在全局異常處理中。

                      通過 @ExceptionHandler(value= xxxException.class)實現對異常的捕獲,並獲取異常的詳細信息和錯誤碼。

@ControllerAdvice public class SellerExceptionHandler {

    @Autowired
    private ProjectUrlConfig projectUrlConfig;

    //攔截登錄異常並返回指定界面
    @ExceptionHandler(value=SellerAuthorizeException.class)
    public ModelAndView handlerAuthorizeException(){
        return new ModelAndView("redirect:"
        .concat(projectUrlConfig.getWechatOpenAuthorize())
        .concat("/sell/wechat/qrAuthorize")
        .concat("?returnUrl=")
        .concat(projectUrlConfig.getSell())
        .concat("/sell/seller/login"));
    }

    @ExceptionHandler(value=SellException.class)
    @ResponseBody
    public ResultVO handlerSellException(SellException e){
        return ResultVOUtil.error(e.getCode(),e.getMessage());
    }
}

            ⑧Aspect切面  AOP實現身份驗證

          AOP是spring框架中的一個重要內容,AOP稱為面向切面編程,在程序開發中主要用來解決一些系統層面上的

             問題,比如日志,事務,權限等待。  使用@Aspect聲明一個切面類。  使用注解完成切點表達式、環繞通知、前置

             通知、后置通知的聲明。  通過切點表達式,實現對訪問請求的攔截,並進行切面中邏輯的處理,這里進行的是登錄

     校驗。

@Aspect
@Component
@Slf4j
public class SellerAuthorizeAspect {

    @Autowired
    private StringRedisTemplate redisTemplate;


    //切入點
    @Pointcut("execution(public * com.imoooc.controller.Seller*.*(..))"+
    "&& !execution(public * com.imoooc.controller.SellerUserController.*(..))")
    public void verify(){

    };
  
  
   //前置通知 @Before(
"verify()") public void doVerify(){ ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); //查詢cookie Cookie cookie = CookieUtil.get(request,CookieConstant.TOKEN); if(cookie == null){ log.warn("【登錄校驗】Cookie中查不到token"); throw new SellerAuthorizeException(); } //去redis里查詢 String tokenValue = redisTemplate.opsForValue().get(String.format(RedisConstant.TOKEN_PREFIX,cookie.getValue())) ; if(StringUtils.isEmpty(tokenValue)){ log.warn("登錄校驗】Redis中查不到token"); throw new SellerAuthorizeException(); } } }

    ⑨HTML5 webSocket 賣家端消息推送

              依賴

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>

 

              前段代碼  (list.ftl)

<#--彈窗-->
<div class="modal fade" id="myModal" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
    <div class="modal-dialog">
        <div class="modal-content">
            <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
                <h4 class="modal-title" id="myModalLabel">
                    提醒
                </h4>
            </div>
            <div class="modal-body">
                你有新的訂單
            </div>
            <div class="modal-footer">
                <button onclick="javascript:document.getElementById('notice').pause()" type="button" class="btn btn-default" data-dismiss="modal">關閉</button>
                <button onclick="location.reload()" type="button" class="btn btn-primary">查看新的訂單</button>
            </div>
        </div>
    </div>
</div>

<#--播放音樂-->
<audio id="notice" loop="loop">
    <source src="/sell/mp3/song.mp3" type="audio/mpeg" />
</audio>

<script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
<script>
    var websocket = null;
    if('WebSocket' in window){
        websocket = new WebSocket('ws://lcb666.natapp1.cc/sell/webSocket');
    }else{
        alert('該瀏覽器不支持websocket');
    }

    websocket.onopen = function (event) {
        console.log('建立連接');
    }

    websocket.onclose = function (event) {
        console.log('連接關閉');
    }

    websocket.onmessage = function (event) {
        console.log('收到消息:' + event.data)
        //彈窗提醒,播放音樂
        $('#myModal').modal('show');
        document.getElementById('notice').play();

    }

    websocket.onerror = function (event) {
        alert('websocket通信發生錯誤!');
    }

    window.onbeforeunload = function () {
        websocket.close();
    }

</script>

              后端代碼

@Component
public class WebSocketConfig {

   @Bean
    public ServerEndpointExporter serverEndpointExporter(){
        return new ServerEndpointExporter();
    }
}
@Component
@ServerEndpoint("/webSocket")
@Slf4j
public class WebSocket {

    private Session session;

    private static CopyOnWriteArraySet<WebSocket> webSocketSet = new CopyOnWriteArraySet<>();
    @OnOpen
    public void onOpen(Session session){
        this.session = session;
        webSocketSet.add(this);
        log.info("【websocket消息】 有新的連接,總數:{}",webSocketSet.size());
    }

    @OnClose
    public void onClose(){
        webSocketSet.remove(this);
        log.info("【websocket消息】 連接斷開,總數:{}",webSocketSet.size());
    }

    @OnMessage
    public void onMessage(String message){
        log.info("【websocket消息】 收到客戶端發來的消息:{}",message);
    }

    public void sendMesage(String message){
        for (WebSocket webSocket:webSocketSet){
            log.info("【websocket消息】 廣播消息,message={}",message);
            try{
                webSocket.session.getBasicRemote().sendText(message);
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

}
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
    @Autowired
    private ProductService productService;

    @Autowired
    private OrderDetailRepository orderDetailRepository;

    @Autowired
    private OrdreMasterRepository ordreMasterRepository;

    @Autowired
    private PayService payService;

    @Autowired
    private PushMessageService pushMessageService;

    @Autowired
    private WebSocket webSocket;

    @Override
    @Transactional
    public OrderDTO create(OrderDTO orderDTO) {

        String orderId=KeyUtil.getUniqueKey();
        BigDecimal orderAmount=new BigDecimal(BigInteger.ZERO);
         //1.查詢商品(數量,價格)
        for(OrderDetail orderDetail:orderDTO.getOrderDetailList()){
            ProductInfo productInfo=productService.finOne(orderDetail.getProductId());
            if(productInfo==null){
                throw new SellException(ResultEnum.PRODUCT_NOT_EXIST);
            }
            //2.計算訂單總價
            orderAmount=productInfo.getProductPrice()
                    .multiply(new BigDecimal(orderDetail.getProductQuantity()))
                    .add(orderAmount);
            //訂單詳情入庫(OrderDetail)
            orderDetail.setDetailId(KeyUtil.getUniqueKey());
            orderDetail.setOrderId(orderId);
            BeanUtils.copyProperties(productInfo,orderDetail);
            orderDetailRepository.save(orderDetail);
        }
        //3.寫入訂單數據庫(OrderMaster)
        OrderMaster orderMaster=new OrderMaster();
        orderDTO.setOrderId(orderId);
        BeanUtils.copyProperties(orderDTO,orderMaster);
        orderMaster.setOrderAmount(orderAmount);
        orderMaster.setOrderStatus(OrderStatusEnum.NEW.getCode());
        orderMaster.setPayStatus(PayStatusEnum.WAIT.getCode());
        ordreMasterRepository.save(orderMaster);

        //4.扣庫存
        List<CartDTO> cartDTOList=orderDTO.getOrderDetailList().stream().map(e ->
                new CartDTO(e.getProductId(),e.getProductQuantity()))
                .collect(Collectors.toList());
        productService.decreaseStock(cartDTOList);

        //發送websocket消息
        webSocket.sendMesage(orderDTO.getOrderId());

        return orderDTO;
    }

            ⑩微信接口技術

      • 微信支付
      • 微信掃碼登錄
      • 微信模板消息推送  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM