点餐系统

介绍

基础介绍

  • 买家(微信)
    • 微信点餐
    • 微信支付
    • 微信通知(模版消息)
  • 卖家(PC)管理系统
    • 扫描登录
    • 订单
    • 商品
    • 类目

技术要点

前后端分离 —— RestFul

前端

Vue —— WebApp

后端

Spring Boot —— Bootstrap+Freemarker+JQuery

Spring Boot —— 数据库 —— JPA —— MyBatis

Spring Boot —— 缓存 —— Redis —— 分布式Session + 分布式锁

Spring Boot —— 消息推送 —— WebSocket

微信技术

  • 微信扫描登录
  • 模版消息推送
  • 微信支付和退款

项目设计

角色划分

  • 买家
    • 商品列表
    • 订单创建
    • 订单查询
    • 订单取消
  • 卖家
    • 订单管理
    • 商品管理
    • 类目管理

功能模块划分

商品

  • 买家
    • 商品列表
  • 卖家
    • 商品管理

订单

  • 买家
    • 订单创建
    • 订单查询
    • 订单取消
  • 卖家
    • 订单管理

类目

  • 卖家
    • 类目管理

关系

  • 买家 ——查询——> 商品
  • 卖家 ——管理——> 商品
  • 买家 ——创建/查询——> 订单
  • 买家 ——查询/接收——> 订单
  • 买家 <——消息——> 卖家

部署架构

微信 & PC浏览器 ——> NGINX ——> Tomcat ——> Redis & MySQL

数据库设计

表关系

类目表(product_category) ——> 商品表(product_info) ——> 订单详情表(order_detail) n——>1 订单主表(order_master)

卖家信息表(seller_info)

表结构

商品表(product_info)
  • 名称
  • 单价
  • 库存
  • 描述
  • 图片
  • 目录编号
1
2
3
4
5
6
7
8
9
10
11
12
13
CREATE TABLE IF NOT EXISTS `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) NOT NULL DEFAULT '0' COMMENT '商品状态: 0-默认未上架',
`category_type` INT NOT NULL COMMENT '目录编号',
`creat_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`product_id`)
) COMMENT '商品表';
类目表(product_category)
  • 名称
  • 编号
1
2
3
4
5
6
7
8
9
CREATE TABLE IF NOT EXISTS `product_category` (
`category_id` INT NOT NULL auto_increment,
`category_name` VARCHAR ( 64 ) NOT NULL COMMENT '类目名称',
`category_type` INT NOT NULL COMMENT '编号',
`creat_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY ( `category_id` ),
UNIQUE KEY `uqe_category_type` ( `category_type` )
) COMMENT '类目表';
订单表
  • 买家名称
  • 买家电话
  • 买家地址
  • 买家微信id
  • 总金额
  • 订单状态
  • 支付状态
1
2
3
4
5
6
7
8
9
10
11
12
13
14
CREATE TABLE IF NOT EXISTS `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 ( 32 ) NOT NULL COMMENT '买家地址',
`buyer_openid` VARCHAR ( 32 ) NOT NULL COMMENT '买家微信id',
`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-默认未支付',
`creat_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY ( `order_id` ),
KEY `idx_buyer_openid` ( `buyer_openid` )
) COMMENT '订单表';
  • 订单id
  • 商品id
  • 商品名字
  • 商品价格
  • 商品数量
  • 商品照片
1
2
3
4
5
6
7
8
9
10
11
12
13
CREATE TABLE IF NOT EXISTS `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 '商品图片',
`creat_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY ( `detail_id` ),
KEY `idx_order_id` ( `order_id` )
) COMMENT '订单详情表';

开发环境

  • JDK
  • Maven
  • Ide

日志使用

日志框架

什么是日志框架?

  • 是一套能实现日志输出的工具包
  • 能够描述系统运行状态的所有时间都可以算作日志
    • 用户下线
    • 接口超时
    • 数据库崩溃

能力:

  • 定制输出日志
  • 定制输出格式
  • 携带上下文信息
  • 运行时选择性输出
  • 灵活的配置
  • 优异的性能

常见日志框架:

  • 日志门面
    • JUL
    • JCL
    • SLF4j
    • jboss-logging
  • 日志实现

    • Log4j
    • Log4j2
    • Logback

使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class LoggerTests {

private final Logger logger = LoggerFactory.getLogger(LoggerTests.class);

/**
* 传统方式实现日志
*/
@Test
public void test1() {
logger.debug("debug ...");
logger.info("info ...");
logger.error("error ...");
}

/**
* Slf4j注解方式实现日志
*/
@Test
public void test2() {
String name = "sell";
String password = "***";
log.debug("debug ...");
log.info("info ... name: {}, password: {}", name, password);
log.error("error ...");
}
}

Logback的配置

  • application.yml

  • logback-spring.xml

需求:

  • 区分info和error日志
  • 每天产生一个日志文件

logback-spring.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<?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>
%date %level [%thread] %logger{10} [%file:%line] ------ %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>
%d - %msg%n
</pattern>
</encoder>
<!--滚动策略-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--路径-->
<fileNamePattern>/Users/jigang.duan/logs/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>
%d - %msg%n
</pattern>
</encoder>
<!--滚动策略-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--路径-->
<fileNamePattern>/Users/username/logs/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>

买家类目

DAD

引入JPA依赖

1
2
3
4
5
6
7
8
9
 <dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>

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

数据库配置

1
2
3
4
5
6
7
8
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
username: root
password: root
url: jdbc:mysql://localhost:8889/seil?characterEncoding=utf-8&useSSL=false
jpa:
show-sql: true

Entity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Entity
@DynamicUpdate
@Data
public class ProductCategory {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "category_id")
private Integer id;

@Column(name = "category_name")
private String name;

@Column(name = "category_type")
private Integer type;

private Date creatAt;

private Date updateAt;

public ProductCategory(String name, Integer type) {
this.name = name;
this.type = type;
}

public ProductCategory() {
}
}

Repository

1
2
3
4
public interface ProductCategoryRepository extends JpaRepository<ProductCategory, Integer> {

List<ProductCategory> findByTypeIn(List<Integer> typeList);
}

测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class ProductCategoryRepositoryTest {

@Autowired
private ProductCategoryRepository repository;

@Test
public void findOneTest() {
Optional<ProductCategory> category = repository.findById(1);
String desc = category.map(ProductCategory::toString).orElse("No value found");
log.info(desc);
}

@Test
@Transactional
public void saveTest() {
ProductCategory category = new ProductCategory("测试类目", 1);
ProductCategory result = repository.save(category);
Assert.assertNotNull(result);
}

@Test
public void findByTypeInTest() {
List<Integer> list = Arrays.asList(0, 1, 2);
List<ProductCategory> result = repository.findByTypeIn(list);
log.info(result.toString());
Assert.assertNotEquals(0, result.size());
}

}

Service

服务接口

1
2
3
4
5
6
7
8
9
10
public interface CategoryService {

Optional<ProductCategory> findById(Integer id);

List<ProductCategory> findAll();

List<ProductCategory> findByTypeIn(List<Integer> typeList);

ProductCategory save(ProductCategory category);
}

服务实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Service
public class CategoryServiceImpl implements CategoryService {

@Autowired
private ProductCategoryRepository repository;

@Override
public Optional<ProductCategory> findById(Integer id) {
return repository.findById(id);
}

@Override
public List<ProductCategory> findAll() {
return repository.findAll();
}

@Override
public List<ProductCategory> findByTypeIn(List<Integer> typeList) {
return repository.findByTypeIn(typeList);
}

@Override
public ProductCategory save(ProductCategory category) {
return repository.save(category);
}
}

测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class CategoryServiceImplTest {

@Autowired
private CategoryServiceImpl categoryService;

@Test
public void findById() {
String result = categoryService.findById(1).map(ProductCategory::toString).orElse("No value found");
Assert.assertNotEquals(result, "No value found");
}

@Test
@Transactional
public void test() {
ProductCategory category = new ProductCategory("测试数据", 0);
ProductCategory save = categoryService.save(category);
Assert.assertNotNull(save);

List<ProductCategory> all = categoryService.findAll();
Assert.assertNotEquals(0, all.size());

List<ProductCategory> byTypeIn = categoryService.findByTypeIn(Collections.singletonList(0));
Assert.assertEquals(1, byTypeIn.size());
}
}

买家商品

微信特性

微信授权

获取OpenID

  • 收到方式
  • 第三方SDK方式

微信

微信支付

微信退款

Powered by Hexo and Hexo-theme-hiker

Copyright © 2013 - 2021 朝着牛逼的道路一路狂奔 All Rights Reserved.

访客数 : | 访问量 :