1. 苍穹外卖项目介绍
1.1 项目介绍
定位:专门为餐饮企业(餐厅、饭店)定制的一款软件产品
项目架构:体现项目中的业务功能模块
1.2 产品原型
产品原型:用于展示项目的业务功能,一般由产品经理进行设计
1.3 技术选型
技术选型:展示项目中使用到的技术框架和中间件等
2. 开发环境的搭建
2.1 前端环境搭建
vue node js axios
2.2 后端环境搭建
mysql 、 springboot 、springmvc、websocket
2.2.1 使用Git进行版本控制
使用Git进行项目代码的版本控制,具体操作:
- 创建Git本地仓库
1.使用idea点击VCS,创建本地仓库
- 创建Git远程仓库
- 将本地文件推送到Git远程仓库
2.3 实现登录效果
2.3.1 前后端联调
nginx 反向代理的配置方式
server{
listen 80;
server_name localhost;
location /api/ {
proxy_pass http://localhost:8080/admin/;#反向代理
}
}
nginx配置负载均衡
upstream webservers{
server 192.168.100.128:8080;
server 192.168.100.129:8080;
}
server{
listen 80;
server_name localhost;
location /api/ {
proxy_pass http://webservers/admin/;#反向代理
}
}
2.3.2 未完成任务的注释
TODO 注释
2.3.2 完善登录效果
密码是明文存储需要加上md5加密
// md5加密
password = DigestUtils.md5DigestAsHex(password.getBytes());
3. 导入接口文档
3.1 Swagger
介绍:使用swagger你只需要按照他的规范去定义接口及接口相关的信息,就可以做到生成接口文档,以及在线接口调试页面
Knife4j是为java MVC框架集成swagger生成api文档的增强解决方案。
3.2 使用方式
1. 导入knife4j的mavern坐标 pom.xml
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>3.0.2</version>
</dependency>
2. 在配置类中加入 knife4j 相关配置 WebMvcConfiguration.java
@Bean
public Docket docket() {
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;
}
3. 设置静态资源映射,否则接口文档页面无法访问WebMvcConfiguration.java
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/doc.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
3.3 常用注解
通过注解可以控制生成的接口文档,使接口文档拥有更好的可读性,常用注解如下:
注解 | 说明 |
@Api | 用在类上,例如Controller,表示对类的说明 |
@ApiModel | 用在类上,例如entity、DTO、VO |
@ApiModelProperty | 用在属性上,描述属性信息 |
@ApiOpeation | 用在方法上,例如Controller的方法,说明方法的用途、作用 |
4. 员工管理
4.1 新增员工
知识点1:对象的拷贝
//对象的拷贝
BeanUtils.copyProperties(employeeDTO,employee);
知识点2:插入两个相同唯一的值,全局捕获异常 handle/GlobalExceptionHandler
/**
* 处理sql异常
* @param ex
* @return
*/
@ExceptionHandler
public Result exceptionHandler(SQLIntegrityConstraintViolationException ex){
String message = ex.getMessage();
if(message.contains("Duplicate entry")){
String[] split = message.split(" ");
String username = split[2];
String msg = username + MessageConstant.ALREADY_EXISTS;
return Result.error(msg);
}else {
return Result.error(MessageConstant.UNKNOWN_ERROR);
}
}
知识点3:动态获取用户id
ThreadLocal为每个线程提供一份存储空间,具有线程隔离效果,只有在线程内才能获取到对应的值,线程外不能访问。
controller、service、拦截器里面都是同一个。所以可以进行通信
ThreadLocal常用方法:
- public void set(T value) 设置当前的线程局部变量的值
- public T get() 返回当前线程所对应的线程局部变量的值
- public void remove() 移除当前线程的局部变量值
封装一个:工具类BaseContext:
public class BaseContext {
public static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
public static void setCurrentId(Long id) {
threadLocal.set(id);
}
public static Long getCurrentId() {
return threadLocal.get();
}
public static void removeCurrentId() {
threadLocal.remove();
}
}
存储id:
BaseContext.setCurrentId(empId);
取出id:
BaseContext.setCurrentId(empId);
4.2 员工分页查询
4.2.1 分页查询:
配置配置:
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mybatisPlusInterceptor;
}
}
使用:
Page<Employee> employeePage = new Page<>(employeePageQueryDTO.getPage(),employeePageQueryDTO.getPageSize());
LambdaQueryWrapper<Employee> employeeWrapper = new LambdaQueryWrapper<>();
employeeWrapper.like(ObjectUtils.isNotEmpty(employeePageQueryDTO.getName()),Employee::getName,employeePageQueryDTO.getName());
employeeService.page(employeePage,employeeWrapper);
PageResult pageResult = new PageResult();
BeanUtils.copyProperties(employeePage,pageResult);
return Result.success(pageResult);
4.2.2 解决时间显示问题
方式一:在属性上加入注解,对日期格式化
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;")
方式二:在WebMvcConfiguration中扩展Spring MVC 的消息转换器,统一对日期类型进行格式化处理
第一步:在WebMvcConfiguration编写代码
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters){
log.info("开始扩展消息转换器...");
//创建一个消息转化器对象
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
//设置对象转换器,可以将java对象转为json字符串
converter.setObjectMapper(new JacksonObjectMapper());
//将我们自己的转化器放入spring MVC框架容器中
converters.add(0,converter);
}
第二步:新建JacksonObjectMapper工具类中
package com.takeaway.json;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;
/**
* 对象映射器:基于jackson将Java对象转为json,或者将json转为Java对象
* 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象]
* 从Java对象生成JSON的过程称为 [序列化Java对象到JSON]
*/
public class JacksonObjectMapper extends ObjectMapper {
public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
//public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm";
public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";
public JacksonObjectMapper() {
super();
//收到未知属性时不报异常
this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);
//反序列化时,属性不存在的兼容处理
this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
SimpleModule simpleModule = new SimpleModule()
.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)))
.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
//注册功能模块 例如,可以添加自定义序列化器和反序列化器
this.registerModule(simpleModule);
}
}
4.3 员工禁用
用户的id是17位但是前端long没有那么长,会导致精度丢失:
解决方法:配置confg
@Configuration
public class IdJsonConfig {
@Bean
public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
ObjectMapper objectMapper = builder.createXmlMapper(false).build();
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
SimpleModule module = new SimpleModule();
module.addSerializer(Long.class, ToStringSerializer.instance);
module.addSerializer(Long.TYPE, ToStringSerializer.instance);
objectMapper.registerModule(module);
return objectMapper;
}
}
禁用方法:
@PostMapping("/status/{status}")
//public Result startOrStop(@PathVariable("status") Integer status,Long id){ 不一致需要写
public Result startOrStop(@PathVariable Integer status, Long id) {
4.4 编辑员工
4.4.1 获取用户详情
@ApiOperation("获取用户详情")
@GetMapping("/{id}")
public Result<Employee> getById(@PathVariable Long id){
4.4.2 编辑员工
字段设计:
@ApiOperation("新增分类")
@PostMapping
@ApiOperation("分类分页查询")
@GetMapping("/page")
@DeleteMapping
@ApiOperation("删除分类")
@ApiOperation("分类禁用和启用")
@PostMapping("/status/{status}")
@ApiOperation("修改分类")
@PutMapping
5. 菜品管理
5.1 公共字段填充(未实现)
业务表中的公共字段:
crate_time、update_time 自动填充
1. 自定义注解 AutoFile,用于标识需要进行公共字段自动填充的方法
2.自定义切面AutoFillAspect,统一拦截加入了AutoFil 注解的方法,通过反射为公共字段赋值
3.在mapper的方法上加入 AutoFill注解
未完成。。。
6. Redis
6.1 简介
Redis是一个基于内存的 key-value 结构数据库
- 基于内存存储,读写性能高
- 适合存储热点数据(热点商品,咨询,新闻)
- 企业应用广泛
6.2 Redis下载与安装
6.2.1 Redis安装包分为 Windows 版和 Linux 版:
- Windows版下载地址:https://github.com/microsoftarchive/redis/releases
- Linux版下载地址:Index of /releases/
redis属于绿色健康软件:解压就可以用:
目录结构:
redis.windows.conf Redis配置文件
redis-cli.exe Redis运行文件
redis-server.exe Redis服务端
6.3 Redis的使用
第一步:启动服务端,进入redis安装目录,执行如下命令行
D:\develop\redis>redis-server.exe redis.windows.conf
出现这个就启动成功:
第二步:启动客户端,链接redis服务端
D:\develop\redis>redis-cli.exe
出现这个则成功:
或者:可以配置端口和链接主机
D:\develop\redis>redis-cli.exe -h localhost -p 6379
6.4 Redis配置密码
打开:redis.windows.conf找到requirepass解除注释,修改值为密码,就完成了
客户端链接服务器命令:
D:\develop\redis>redis-cli.exe -h localhost -p 6379 -a 123456
6.5 Redis五种常用数据类型介绍
字符串 string、哈希 hash、列表 list、集合 set、有序集合 sorted set / zset
6.6 Redis 常用命令
6.6.1 Redis 字符串类型常用命令
- SET key value 设置指定key的值
- GET key 获取指定key的值
- SETEX key seconds value 设置指定key的值,并将 key 的过期时间设置为 seconds 秒
- SETNX key value 只有在 key 不存在时设置 key 的值
6.6.2 Redis 哈希操作命令
Redis hash 是一个string 类型的 field 和 value 的映射表,hash特别适合用于存储对象,常用命令:
- HSET key field value 将哈希表 key 中的字段 field 的值设为 value
- HGET key field 获取存储在哈希表中指定字段的值
- HDEL key field 删除哈希表中所有字段
- HKEYS key 获取哈希表中所有字段
- HVALS key 获取哈希表中所有值
6.6.2 Redis 列表是简单的字符串列表,按照插入顺序排序,常用命令:
- LPUSH key value1 [value2] 将一个或多个插入到列表头部
- LRANGE key start stop 获取列表指定范围内的元素
- RPOP key 移除并获取列表最后一个元素
- LLEN key 获取列表长度
6.6.3 Redis
6.7 在Java中操作Redis
6.7.1 Spring Data Redis 使用方式
1. 导入Spring Data Redis 的maven坐标 pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2. 配置Redis数据源 application.yml
spring:
redis:
host: localhost
port: 6379
password: 123456
database: 0 #指定数据库,不同的数据中数据是相互隔离的,默认不用配置
3.编写配置类,创建RedisTemplate对象,在config下新建RedisConfiguration.java
@Configuration
@Slf4j
public class RedisConfiguration {
//报错是编译器的原因
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){
log.info("开始创建redis模块对象...");
RedisTemplate redisTemplate = new RedisTemplate();
//设置redis链接工厂对象
redisTemplate.setConnectionFactory(redisConnectionFactory);
//设置redis key的序列化器
redisTemplate.setKeySerializer(new StringRedisSerializer());
return redisTemplate;
}
}
4.通过RedisTemplate对象操作Redis
@SpringBootTest
public class SpringDataRedisTest {
@Autowired
private RedisTemplate redisTemplate;
@Test
public void testRedisTemplate(){
System.out.println(redisTemplate);
}
}
6.7.1 Spring Data Redis 操作字符串
6.7.2 Spring Data Redis 操作Hash
操作都是一样的。
6.8 店铺营业状态设计
最后:
小技巧:
查询:对于查询来说,Result最好写类型,但是对于增删改来说可以用泛型
使用Builder构造类:
在类上加上@
@Builder
public class Employee implements Serializable {
使用:
Employee employee = Employee.builder()
.id(id)
.status(status)
.build();
设置:请求bean名称
log.info("设置店铺营业状态:{}",status == 1?"营业中":"打样中");
配置两个文档:
@Bean
public Docket docket1() {
ApiInfo apiInfo = new ApiInfoBuilder()
.title("苍穹外卖项目接口文档")
.version("2.0")
.description("苍穹外卖项目接口文档")
.build();
Docket docket = new Docket(DocumentationType.SWAGGER_2)
.groupName("管理员")
.apiInfo(apiInfo)
.select()
.apis(RequestHandlerSelectors.basePackage("com.takeaway.controller.admin"))
.paths(PathSelectors.any())
.build();
return docket;
}
@Bean
public Docket docket2() {
ApiInfo apiInfo = new ApiInfoBuilder()
.title("苍穹外卖项目接口文档")
.version("2.0")
.description("苍穹外卖项目接口文档")
.build();
Docket docket = new Docket(DocumentationType.SWAGGER_2)
.groupName("用户")
.apiInfo(apiInfo)
.select()
.apis(RequestHandlerSelectors.basePackage("com.takeaway.controller.user"))
.paths(PathSelectors.any())
.build();
return docket;
}