仓库地址:https://github.com/Shmily33/sky-take-out
1 项目介绍
1.1 功能架构
1.2 技术选型
1.3 后端环境搭建
sky-common
sky-pojo
sky-server
1.4 数据库环境
1.5 前后端联调
思考
反向代理
好处
方向代理配置方式
负载均衡配置方式
负载均衡策略
1.6 完善登录功能
1.7 Swagger
1.7.1 介绍
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
1.7.2 使用方式
1.7.3 常用注解
package com.sky.config;
import com.sky.interceptor.JwtTokenAdminInterceptor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
/**
* 配置类,注册web层相关组件
*/
@Configuration
@Slf4j
public class WebMvcConfiguration extends WebMvcConfigurationSupport {
@Autowired
private JwtTokenAdminInterceptor jwtTokenAdminInterceptor;
/**
* 注册自定义拦截器
*
* @param registry
*/
@Override
protected void addInterceptors(InterceptorRegistry registry) {
log.info("开始注册自定义拦截器...");
registry.addInterceptor(jwtTokenAdminInterceptor)
.addPathPatterns("/admin/**")
.excludePathPatterns("/admin/employee/login");
}
/**
* 通过knife4j生成接口文档
* @return
*/
@Bean
public Docket docket() {
log.info("准备生成接口文档。。。");
ApiInfo apiInfo = new ApiInfoBuilder()
.title("苍穹外卖项目接口文档")
.version("2.0")
.description("苍穹外卖项目接口文档")
.build();
Docket docket = new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo)
.select()
.apis(RequestHandlerSelectors.basePackage("com.sky.controller"))
.paths(PathSelectors.any())
.build();
return docket;
}
/**
* 设置静态资源映射
* @param registry
*/
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
log.info("开始设置静态资源映射。。。");
registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}
1.8 DTO与实体类
/*
* 在controller使用的是EmployeeDTO(Data Transfer Object),这样可以与视图层数据一致
* 到service就可以操作真正的Employee实体,交由mapper层与数据库操作
*
* 对象属性拷贝
BeanUtils.copyProperties(employeeDTO, employee); // 源->目标
Employee employee = Employee.builder()
.id(id).status(status).build();
builer插件方便赋值 等同set
*/
2 员工管理、分类管理
2.1 ThreadLocal
问题
JWT认证流程
ThreadLocal
2.2 日期格式化
3 菜品管理
3.1 公共字段自动填充–AOP+反射运用
问题
解决
代码
3.2 新增菜品
逻辑外键–代码:通过上一张表的插入语句,回显赋值id作为下一张表的dish_id
@Transactional
@Override
public void saveWithFlavor(DishDTO dishDTO) { // 两张表操作,保持数据一致性
Dish dish = new Dish();
BeanUtils.copyProperties(dishDTO, dish);
// 菜品表插入1条数据
dishMapper.insert(dish);
// 获取insert语句生成的主键值
// useGeneratedKeys="true" keyProperty="id" 使用产生的主键赋值为id
Long dishId = dish.getId();
// 口味表插入n条数据
List<DishFlavor> flavors = dishDTO.getFlavors();
if (flavors != null && flavors.size() > 0) { // 不空且有数据存在
flavors.forEach(f -> {
f.setDishId(dishId);
});
dishFlavorMapper.insertBatch(flavors); // 传入对象批量插入
}
}
两张表操作,数据一致性问题
@Transactional 保证方法是原子性 – 方法上添加
@EnableTransactionManagement //开启注解方式的事务管理 --启动类上添加
3.3 修改菜品
插入口味表时,不确定是否有或者需要增删改–先删除后插入
public void updateWithFlavor(DishDTO dishDTO) {
Dish dish = new Dish();
BeanUtils.copyProperties(dishDTO, dish);
dishMapper.update(dish);
// 口味有可能增删该了。那么我们处理方式先删掉原来的,在插入新的 等于覆盖
dishFlavorMapper.deleteByDishId(dishDTO.getId());
List<DishFlavor> dishFlavors = dishDTO.getFlavors();
// 口味表插入n条数据
List<DishFlavor> flavors = dishDTO.getFlavors();
if (flavors != null && flavors.size() > 0) { // 不空且有数据存在
flavors.forEach(f -> {
f.setDishId(dishDTO.getId());
});
dishFlavorMapper.insertBatch(flavors); // 传入对象批量插入
}
}
4 Redis
4.1 简介
mysql是基于数据文件,存在磁盘上,二维表结构方式
4.2 入门
启动命令
redis-server.exe redis.windows.conf 启动redis
ctrl c 关闭
redis-cli.exe 启动客服端
redis-cli.exe -h () -p () -a () 启动指定的redis服务 -a 后是密码
密码设置
4.3 常用数据类型
特点
4.4 常用命令
字符串
Hash
列表
集合
有序集合
通用命令
4.5 在Java中操作redis
Spring Data Redis
使用方式
4.6 缓存商品
4.6.1 缓存菜品
问题说明
实现思路
4.6.2 缓存套餐
Spring Cache
常用注解
实现思路
5 HttpClient
5.1 介绍
5.2 入门
6 微信小程序
6.1 注意
6.2 目录结构
6.3 微信支付
参考:https://pay.weixin.qq.com/static/product/product_index.shtml
6.3.1 时序图
调用过程如何保证数据安全?
微信后台如何调用到商户系统?
6.3.2 JSAPI
https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_1.shtml
6.4 获取临时域名
cpolar:https://dashboard.cpolar.com/login
配置
成功示例
6.5 关于微信支付的代码跳过步骤–否则管理端的订单功能无法测试
6.5.1 小程序端代码改动
注释发送得支付请求功能代码,直接把其中成功跳转页面代码提上来
ctrl+f 搜索handleSave 替换其中的else分支代码
else {
// 如果支付成功进入成功页
clearTimeout(this.times);
var params = {
orderNumber: this.orderDataInfo.orderNumber,
payMethod: this.activeRadio === 0 ? 1 : 2 };
(0, _api.paymentOrder)(params).then(function (res) {
if (res.code === 1) {
wx.showModal({
title: '提示',
content: '支付成功',
success:function(){
uni.redirectTo({url: '/pages/success/index?orderId=' + _this.orderId });
}
})
console.log('支付成功!')
// wx.requestPayment({
// nonceStr: res.data.nonceStr,
// package: res.data.packageStr,
// paySign: res.data.paySign,
// timeStamp: res.data.timeStamp,
// signType: res.data.signType,
// success:function(res){
// wx.showModal({
// title: '提示',
// content: '支付成功',
// success:function(){
// uni.redirectTo({url: '/pages/success/index?orderId=' + _this.orderId });
// }
// })
// console.log('支付成功!')
// }
// })
//uni.redirectTo({url: '/pages/success/index?orderId=' + _this.orderId });
} else {
wx.showModal({
title: '提示',
content: res.msg
})
}
});
}
6.5.2 后端代码改动
OrderController
注释原先代码,直接按照接口文档返回String类型的预计送达时间
@PutMapping("/payment")
@ApiOperation("订单支付")
public Result<String> payment2(@RequestBody OrdersPaymentDTO ordersPaymentDTO) throws Exception {
log.info("订单支付:{}", ordersPaymentDTO);
// OrderPaymentVO orderPaymentVO = orderService.payment(ordersPaymentDTO);
LocalDateTime time = LocalDateTime.now().plusHours(1);
// 定义日期时间格式化器
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String estimatedDeliveryTime = time.format(formatter);
orderService.updateStatus(ordersPaymentDTO);
return Result.success(estimatedDeliveryTime);
}
OrderService
OrderServiceImpl
@Override
public void updateStatus(OrdersPaymentDTO ordersPaymentDTO) {
Orders orders = orderMapper.getByNumber(ordersPaymentDTO.getOrderNumber());
orders.setStatus(Orders.TO_BE_CONFIRMED);
orders.setPayStatus(Orders.PAID);
orderMapper.update(orders);
}