SpringBoot的微信点餐系统后台开发要点

项目设计

角色划分

  • 买家(微信端)
  • 卖家(PC端)

功能分析

关系

部署架构

架构和基础框架

演进:单一应用架构->垂直应用架构->分布式服务架构->流动计算架构

国内微服务门派:

  • 阿里系:
    • Duboo
    • Zookeeper
    • SpringMVC or SpringBoot
  • Spring Cloud系:
    • Spring Cloud
    • Netfilx Eureka
    • SpringBoot

数据库设计

数据库表之间的关系

  • 类目表(product_category)
  • 商品表(product_info)
  • 订单主表(order_master)
  • 订单详情表(order_detail)
  • 卖家信息表(order_detail)

数据库表结构

create table `product_info`(
    `product_id` varchar(32) not null, --企业级的用varchar,自己玩的项目可以用自增的但数量大了可能不够用
    `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 '小图',
    `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 '更新时间',  --MYSQL5.7才可以default current_timestamp
    primary key (`product_id`)
) comment '商品表';
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 '更新时间',  --MYSQL5.7才可以default current_timestamp
    primary key (`category_id`)
    unique key `uqe_category_type` (`category_type`) --类目是唯一的
) comment '类目表'
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 '订单状态,默认0新下单',
    `pay_status` tinyint(3) not null default '0' comment '支付状态,默认0未支付',
    `create_time` timestamp not null default current_timestamp comment '创建时间',
    `update_time` timestamp not null default current_timestamp on update current_timestamp comment '更新时间',  --MYSQL5.7才可以default current_timestamp
    primary key (`order_id`),
    key `idx_buyer_openid` (`buyer_openid`)
) comment '订单表';
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 '更新时间',  --MYSQL5.7才可以default current_timestamp
    primary key (`detail_id`),
    key `idx_order_id`(`order_id`)
) comment '订单详情表';

项目起步

日志的使用和配置

  • 选用:logback
  • 配置方式:
    • 在application.yml配置
    • 在logback-spring.xml logback-spring.xml配置示例
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
    <appender name="consoleLog" class="ch.qos.logback.core.ConsoleAppender">
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>
                %d - %msg%n
            </pattern>
        </layout>
    </appender>

    <appender name="fileInfoLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>DENY</onMatch>
            <onMismatch>ACCEPT</onMismatch>
        </filter>
        <encoder>
            <pattern>
                %msg%n
            </pattern>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>/Users/ztev/tomcat/sell/info.%d.log</fileNamePattern>
        </rollingPolicy>
    </appender>

    <appender name="fileErrorLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>ERROR</level>
        </filter>
        <encoder>
            <pattern>
                %msg%n
            </pattern>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>/Users/ztev/tomcat/sell/error.%d.log</fileNamePattern>
        </rollingPolicy>
    </appender>

    <root level="info">
        <appender-ref ref="consoleLog"/>
        <appender-ref ref="fileInfoLog"/>
        <appender-ref ref="fileErrorLog"/>
    </root>


</configuration>

买家端开发

买家端类目模块的开发,按照dao->service->api的顺序开发。贯穿单元测试。

  • SpringBoot JPA把驼峰命名和表映时,小写+下划线,如ProductCategory=product_category
@Entity
public class ProductCategory {

    @Id
    @GeneratedValue
    private Integer categoryId;

    private String categoryName;

    private Integer categoryType;
    ...
}
  • 在测试里增加 @Transactional就可以回滚插入的测试数据
  • Getter和Setter方法的快捷实现,配合IDEA的插件lombok
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>
  • MySQL里的DECIMAL对应java里的BigDecimal
  • 反复用到的类型值如 categoryType可以定义一个枚举
@Getter
public enum ProductStatusEnum {
    UP(0, "在架"),
    DOWN(1, "下架");

    private Integer code;

    private String message;

    ProductStatusEnum(Integer code, String message) {
        this.code = code;
        this.message = message;
    }
}
  • 项目统一前缀
server:
  context-path: /sell
  • 返回给前端和数据库不同字段的话用 VO,通过 @JsonProperty可以起名不一致
@Data
public class ProductVO {

    @JsonProperty("name")
    private String categoryName;

    private Integer type;
}
  • 千万不要把数据库的查询放入for循环,开销巨大,一次性查出来以后再用for循环遍历处理比较合适。
  • OrderMasterOrderDetail之间有关联在创建订单的时候,用一个 OrderDTO的类把他们连接起来
  • 多线程,查库存再扣库存的操作在数据库层面是分两步的,可能存在超卖
  • Controller里传进来的参数用表单验证注解。
@Data
public class OrderForm {
    @NotEmpty(message = "姓名必填")
    private String name;

    @NotEmpty(message = "手机号必填")
    private String phone;

    @NotEmpty(message = "地址必填")
    private String address;

    @NotEmpty(message = "openid必填")
    private String openid;

    @NotEmpty(message = "购物车不能为空")
    private String items;
}
....

public ResultVO<Map<String, String>> create(@Valid OrderForm orderForm, BindingResult bindingResult) {
  • 如果你的类型在返回给json不一致时,可以用自定义的 JsonSerialize
public class Date2LongSerializer extends JsonSerializer<Date> {
    @Override
    public void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
        jsonGenerator.writeNumber(date.getTime() / 1000);
    }
}

//另一个类

@JsonSerialize(using = Date2LongSerializer.class)
private Date updateTime;
  • 不想把null的传给前端
    • 类上用`@JsonInclude(JsonInclude.Include.NON_NULL)
    • 你也可以全局配置在 application.yml上用 jackson:default-property-inclusion:non_null
  • 如果null的情况下想返回空,则在类里的成员变量里用上初始化,如stirng就用""

卖家端开发

  • freemarker的模版更新时,只需要在IDEA的IDE里面重新build项目即可。不需要重启整个web进程
  • 使用微信登陆的原理是,openid相当于密码

分布式系统下的session

  • 定义:多个自治的处理元素,不共享内存,通过消息通信合作。
  • 分布式系统,强调的是不同的功能,组成一整套不同功能的集合,比如后厨里,炒菜和洗菜的构成分布式系统
  • 功能类似的互为备份的就是集群。比如后厨里,两个洗菜的
  • 分布式计算:例如hadoop的mapreduce,比如后厨里,两个炒菜的炒完放一个篮子里分不清谁是谁的。
  • 分布式系统统一取一个redis集群里取session
  • 应对大用户量
    • ip哈希,同一个用户一直访问同一台服务器,但是那个挂了的没辙了
    • 通用方案是有个专门的Redis服务集群,都去他这里找
    • 垂直拆分就是把服务拆成多份
    • 横向拆分就是把同一个功能做集群
    • 但是都会面临session的问题

项目优化

MyBatis

  • 基于注解
  • 基于XML方式,老传统,官方已不再推荐

JPA和MyBatis的注意

  • 建表用sql,jpa建表可控性较差,慎用@OneToMany和@ManyToOne以后不容易分库分表

压测

  • 使用简易工具Apache ab
  • ab -n 100-c 100http://www.baidu.com -n是请求,-c是并发,相当于100个人同时访问
  • ab -t 60-c 100http://www.baidu.com -t是60秒,连续60秒并发100个

秒杀

  • 秒杀时,会有线程并发的问题
    • 支持分布式
    • 可以更细粒度的控制
    • 多台机器上多个进程对一个数据进行操作的互斥
    • 是一种解决方法
    • 无法细粒度控制
    • 只适合单点的情况
    • 使用方法锁synchronized
    • Redis实现分布式锁

Redis缓存

  • 命中 @Cacheable(cacheNames ="product",key ="123")
  • 失效
  • 更新 @CachePut(cacheNames ="product",key="123")
  • 结合场景,避免乱用。缓存和更新要及时。

源码:

https://github.com/linxinzhe/java-springboot-sell

原文发布于微信公众号 - 林欣哲(gh_aba6caba3ac7)

原文发表时间:2018-04-04

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏更流畅、简洁的软件开发方式

实体类的变形【1】—— 餐盘原理

    在亚历山大同学的post里面我说可以让实体类和表不必一一对应,但是并没有详细说明如何来做,也有人想问我是怎么做的,那么我就说一下。先说一个简单一点的,那...

1947
来自专栏云计算

使用Akka HTTP构建微服务:CDC方法

原文地址:https://dzone.com/articles/building-microservices-with-akka-http-a-cdc-appr...

3225
来自专栏JavaEdge

Druid入门应用场景存储系统选择Druid介绍

设计一个系统来预估未来一年的广告流量,不是总流量,是任意时间段任何定向(Targeting)条件约束情况下的流量。定向条件有近百种(内容类别,设备平台,用户地域...

3794
来自专栏大数据挖掘DT机器学习

Python实现爬取知乎神回复

这篇文章主要介绍了Python实现爬取知乎神回复简单爬虫代码分享,本文实现了爬取知乎的“如何正确地吐槽”收藏夹,是对个人的一个兴趣实现,需要的朋友可以参考下。 ...

3385
来自专栏EAWorld

揭秘:RESTEasy如何完美支持JAVA 微服务中的多种数据格式

本文获得stackify.com授权翻译发表,转载需要注明来自公众号EAWorld。

1454
来自专栏信安之路

我是如何找到 Google Colaboratory 中的一个 xss 漏洞的

在本文中,我来讲讲我碰到的一个有趣的 XSS。2018 年 2 月,我在 google 的一个网络应用中发现了这个 XSS。这篇文章我不希望只是直接写出这个 X...

890
来自专栏JAVA烂猪皮

金三银四跳槽季,上周刚面试回来后的面试总结

上周陪同之前一起工作的同事去面试(乔治,小袁,鹏飞(面试人)),第一站是去深圳,第二站上海,第三站杭州。面试什么公司我在这里就不多说了,你们知道是一线公司就行。...

1192
来自专栏hbbliyong

JAVA试练塔之试炼技能图

1.计算机基础: 1.1数据机构基础: 主要学习: 1.向量,链表,栈,队列和堆,词典。熟悉 2.树,二叉搜索树。熟悉 3.图,有向图,无向图,基本概念 4.二...

3747
来自专栏黑泽君的专栏

从零讲JAVA ,给你一条清晰地学习道路!该学什么就学什么!!

 原文链接:https://zhuanlan.zhihu.com/p/25296859

932
来自专栏java架构学习交流

如何用比较快速的方法掌握Spring的核心——依赖注入,Java web轻量级开发面试教程 读书笔记

      我们知道,Java方面的高级程序员一定得掌握Spring的技能,其中包括Spring 依赖注入(IOC),面向切面(AOP),和数据库的整合(比如和...

20810

扫码关注云+社区