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
@Beanpublic 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*/@ExceptionHandlerpublic 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 {@Beanpublic 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 {@Beanpublic 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: localhostport: 6379password: 123456database: 0 #指定数据库,不同的数据中数据是相互隔离的,默认不用配置
3.编写配置类,创建RedisTemplate对象,在config下新建RedisConfiguration.java
@Configuration
@Slf4j
public class RedisConfiguration {//报错是编译器的原因@Beanpublic 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 {@Autowiredprivate RedisTemplate redisTemplate;@Testpublic 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?"营业中":"打样中");
配置两个文档:
@Beanpublic 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;}@Beanpublic 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;}