专栏首页林欣哲SpringBoot的微信点餐系统后台开发要点

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)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

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

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

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Spring Boot 知识点速记

    本文是学习2小时学会Spring Boot和Spring Boot进阶之Web进阶的SpringBoot常用知识点速记。 SpringBoot前置知识: Spr...

    linxinzhe
  • MySQL执行计划(explain)分析

    这里的索引有auditstatus和productid,可以建立联合索引。但是哪个放左边就要计算区分度。

    linxinzhe
  • Redis知识点速查

    NoSQL概述 为什么需要NoSQL 高并发读写 海量数据的高效率存储和访问 高可扩展性和高可用性 NoSQL数据库分类 键值存储。Redis 列存储。HBas...

    linxinzhe
  • 使用mongosync同步数据

    注意: 我下面的这个mongodb版本较低(3.2.16), 还可以用这个工具来同步数据。工具不支持更高版本的mongodb了.

    二狗不要跑
  • 【Go 语言社区】使用 Redis 实现排行榜功能

    排行榜功能是一个很普遍的需求。使用 Redis 中有序集合的特性来实现排行榜是又好又快的选择。 一般排行榜都是有实效性的,比如“用户积分榜”。如果没有实效性一直...

    李海彬
  • python 运维常用脚本

    path = "D:/UASM64/include/" dirs = os.listdir(path) temp=[];

    用户5760343
  • Java1.8新特性 -- 接口默认方法

    以前经常会有面试题问接口里是否能写具体方法,现在依然还有些菜鸟再出这个面试题。Java 8 新增了接口的默认方法。简单说,默认方法就是接口可以有实现方法,而且不...

    Java深度编程
  • Spring+SpringMVC+MyBatis+easyUI整合基础篇(二)牛刀小试

    前言 承接上文,该篇即为项目整合的介绍了。 废话不多说,先把源码和项目地址放上来,重点要写在前面。 项目展示地址,点这里http://ssm-demo.hans...

    我是十三
  • httpstat一个实用的网络工具

    这个工具可以收集一些网站的基础信息,而且它使用ascii字符的方式显示网站各种连接的时间,方便我们根据这个去优化网站,从而提高网站的连接响应速度。

    bboysoul
  • 区块链技术是目前数字版权保护最好的手段

    现今随着数字产业的快速发展,在数字版权保护方面也受到大众的关注,数字版权保护技术也成为了数字内容交易和传播的重要途径。传统的版权保护很少涉及到技术方面,而数字版...

    墨者安全科技

扫码关注云+社区

领取腾讯云代金券