【基于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