当前位置: 首页 > article >正文

苍穹外卖学习笔记

整体概述

1).用户层

本项目中在构建系统管理后台的前端页面,我们会用到H5、Vue.js、ElementUI、apache echarts(展示图表)等技术。而在构建移动端应用时,我们会使用到微信小程序

2).网关层

Nginx是一个服务器,主要用来作为Http服务器,部署静态资源,访问性能高。在Nginx中还有两个比较重要的作用: 反向代理和负载均衡, 在进行项目部署时,要实现Tomcat的负载均衡,就可以通过Nginx来实现。

3).应用层

SpringBoot: 快速构建Spring项目, 采用 “约定优于配置” 的思想, 简化Spring项目的配置开发。

SpringMVC:SpringMVC是spring框架的一个模块,springmvc和spring无需通过中间整合层进行整合,可以无缝集成。

Spring Task: 由Spring提供的定时任务框架。

httpclient: 主要实现了对http请求的发送。

Spring Cache: 由Spring提供的数据缓存框架

JWT: 用于对应用程序上的用户进行身份验证的标记。

阿里云OSS: 对象存储服务,在项目中主要存储文件,如图片等。

Swagger: 可以自动的帮助开发人员生成接口文档,并对接口进行测试。

POI: 封装了对Excel表格的常用操作。

WebSocket: 一种通信网络协议,使客户端和服务器之间的数据交换更加简单,用于项目的来单、催单功能实现。

4).数据库

MySQL: 关系型数据库, 本项目的核心业务数据都会采用MySQL进行存储。

Redis: 基于key-value格式存储的内存数据库, 访问速度快, 经常使用它做缓存。

Mybatis: 本项目持久层将会使用Mybatis开发。

pagehelper: 分页插件。

spring data redis: 简化java代码操作Redis的API。

模块划分

项目整体分为3个模块

在这里插入图片描述

sky-common 模块

在这里插入图片描述

constant存放相关常用量
context存放上下文管理类
enumeration项目的枚举类型类
exception定义了一些常见的异常类型类
json:处理JSON转换的类
properties存放SpringBoot相关配置类属性
result存放返回的结果类的封装
utils存放工具类

sky-pojo模块

在这里插入图片描述

Entity实体,与数据库中的表字段相对应
DTO数据传输对象,通常用于在程序中传递数据
VO视图展示对象,为前端展示数据提供对象
POJO普通Java对象,只有属性和对应的getter和setter

sky-server模块

在这里插入图片描述

annotation自定义注解
aspect自定义切面类
config配置类
controller控制类
interceptor拦截器
mapper存放mepper接口
service服务类
task定时任务类
websocketwebsocket服务类

数据库模块

分类表category

在这里插入图片描述

菜品表dish

在这里插入图片描述

菜品口味表dish_flavor

在这里插入图片描述

员工表employee

在这里插入图片描述

订单详情表order_detail

在这里插入图片描述

订单表orders

在这里插入图片描述

套餐表setmeal

在这里插入图片描述

套餐菜品关系表setmeal_dish

在这里插入图片描述

购物车表shopping_cart

在这里插入图片描述

用户表user

在这里插入图片描述

业务模块

商家端业务功能知识点总结

1.JWT令牌

JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在网络应用环境间以JSON对象安全地传递信息。JWT的信息可以被验证和信任,因为它是数字签名的。JWT通常用于身份验证和信息交换,广泛应用于现代Web应用和API开发中。

JWT的结构

JWT由三部分组成,分别是**Header(头部)**、Payload(载荷)Signature(签名),它们之间用点(.)分隔,格式如下:

Header.Payload.Signature</mark>



2.使用流程

生成JWT的方法

secretKey是密钥,必须保密

    public static String createJWT(String secretKey, long ttlMillis, Map<String, Object> claims) {// 指定签名的时候使用的签名算法,也就是header那部分SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;// 生成JWT的时间long expMillis = System.currentTimeMillis() + ttlMillis;Date exp = new Date(expMillis);// 设置jwt的bodyJwtBuilder builder = Jwts.builder()// 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的.setClaims(claims)// 设置签名使用的签名算法和签名使用的秘钥.signWith(signatureAlgorithm, secretKey.getBytes(StandardCharsets.UTF_8))// 设置过期时间.setExpiration(exp);return builder.compact();}

JWT解密方法

    public static Claims parseJWT(String secretKey, String token) {// 得到DefaultJwtParserClaims claims = Jwts.parser()// 设置签名的秘钥.setSigningKey(secretKey.getBytes(StandardCharsets.UTF_8))// 设置需要解析的jwt.parseClaimsJws(token).getBody();return claims;}

员工登录时可以为他生成JWT令牌,存放token

        Employee employee = employeeService.login(employeeLoginDTO);//登录成功后,生成jwt令牌Map<String, Object> claims = new HashMap<>();// 将员工id放入jwt令牌中 EMP_ID是自定义的claims.put(JwtClaimsConstant.EMP_ID, employee.getId());// jetProperties是自定义的 通过设置 @Component 注解将其注册为一个Spring Bean 通过// @ConfigurationProperties(prefix = "sky.jwt") 注解将配置文件中的 sky.jwt 前缀的属性值映射到 JwtProperties 类的属性中// 然后通过 @Autowired 注解将其注入到 EmployeeController 类中// 调用 JwtUtil.createJWT 方法生成 JWT 令牌String token = JwtUtil.createJWT(jwtProperties.getAdminSecretKey(),jwtProperties.getAdminTtl(),claims);后续要在当前任务中使用用户的ID时,可以通过上下文来获得当前登录用户的token

在拦截器中从请求头中可以获取到当前用户的token,然后通过上面的解析方法来解释当前用户的toekn,接着将用户id存放在BaseContext中:可以看到BaseContext中创建了一个ThreadLocal实现线程的局部变量,它在执行完一个任务之后会结束。用户在当前任务进程中的任务时候,都可以通过BaseContext.getCurrentId来获得当前用户的id.

//1、从请求头中获取令牌String token = request.getHeader(jwtProperties.getAdminTokenName());//2、校验令牌try {log.info("jwt校验:{}", token);// 解析jwt令牌Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);// 获取jwt令牌中的员工idLong empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());log.info("当前员工id:", empId);BaseContext.setCurrentId(empId);public class BaseContext {// 创建一个ThreadLocal对象public static ThreadLocal<Long> threadLocal = new ThreadLocal<>();// 设置当前登录用户的idpublic static void setCurrentId(Long id) {threadLocal.set(id);}// 获取当前登录用户的idpublic static Long getCurrentId() {return threadLocal.get();}// 移除当前登录用户的idpublic static void removeCurrentId() {threadLocal.remove();}}

2.拦截器

1.什么是拦截器?

    拦截器(Interceptor)是一种特殊的组件,它可以在<mark>请求处理的过程中对请求和响应进行拦截和处理</mark>。拦截器可以在请求到达目标处理器之前、处理器处理请求之后以及视图渲染之前执行特定的操作。<mark>拦截器的主要目的是在不修改原有代码的情况下,实现对请求和响应的统一处理</mark>。

2.拦截器的作用

拦截器可以用于实现以下功能:

权限控制:拦截器可以在请求到达处理器之前进行权限验证,从而实现对不同用户的访问控制。日志记录:拦截器可以在请求处理过程中记录请求和响应的详细信息,便于后期分析和调试。接口幂等性校验:拦截器可以在请求到达处理器之前进行幂等性校验,防止重复提交。数据校验:拦截器可以在请求到达处理器之前对请求数据进行校验,确保数据的合法性。缓存处理:拦截器可以在请求处理之后对响应数据进行缓存,提高系统性能。

3.SpringBoot中拦截器的实现

要在SpringBoot中实现拦截器,首先需要创建一个类并实现HandlerInterceptor接口。HandlerInterceptor接口包含以下三个方法:

preHandle:在请求到达处理器之前执行,可以用于权限验证、数据校验等操作。如果返回true,则继续执行后续操作;如果返回false,则中断请求处理。postHandle:在处理器处理请求之后执行,可以用于日志记录、缓存处理等操作。afterCompletion:在视图渲染之后执行,可以用于资源清理等操作。

public class JwtTokenAdminInterceptor implements HandlerInterceptor {@Autowiredprivate JwtProperties jwtProperties;/*** 校验jwt** @param request* @param response* @param handler* @return* @throws Exception*/public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 当前线程的idSystem.out.println("当前线程的id:" + Thread.currentThread().getId());//判断当前拦截到的是Controller的方法还是其他资源if (!(handler instanceof HandlerMethod)) {//当前拦截到的不是动态方法,直接放行return true;}//1、从请求头中获取令牌String token = request.getHeader(jwtProperties.getAdminTokenName());//2、校验令牌try {log.info("jwt校验:{}", token);// 解析jwt令牌Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);// 获取jwt令牌中的员工idLong empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());log.info("当前员工id:", empId);BaseContext.setCurrentId(empId);//3、通过,放行return true;} catch (Exception ex) {//4、不通过,响应401状态码response.setStatus(401);return false;}}
}
为

为了让拦截器生效我们能还需要将拦截器注册为Bean,在拦截器前加上@Configuration注解然后通过在配置类中注册我们自定义的拦截器。

在这里插入图片描述

3.Swagger

Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务https://swagger.io它的主要作用是:

  • 使得前后端分离开发更加方便,有利于团队协作

  • 接口的文档在线自动生成,降低后端开发人员编写接口文档的负担

  • 功能测试 Spring已经将Swagger纳入自身的标准,建立了Spring-swagger项目,现在叫Springfox。通过在项目中引入Springfox ,即可非常简单快捷的使用Swagger。

knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案,前身是swagger-bootstrap-ui,取名kni4j是希望它能像一把匕首一样小巧,轻量,并且功能强悍!

目前,一般都使用knife4j框架。

使用步骤

1.导入 knife4j 的maven坐标在pom.xml中添加依赖

<dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>

2.在配置类中加入 knife4j 相关配置

 /*** 通过knife4j生成接口文档* @return*/@Beanpublic Docket docket1() {log.info("开始生成接口文档...");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.sky.controller.admin")).paths(PathSelectors.any()).build();return docket;}@Beanpublic Docket docket2() {log.info("开始生成接口文档...");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.sky.controller.user")).paths(PathSelectors.any()).build();return docket;}

3.设置静态资源映射,否则接口文档页面无法访问

    /*** 设置静态资源映射 主要是访问接口文档(html,js,css)* @param registry*/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/");}

使用是:

@Api(tags = “订单相关接口”)注解:加再Controller类上

4.分页查询

在 Web 应用开发中,分页查询是非常常见的需求,特别是在涉及大量数据的应用场景中,通过分页可以减少数据加载压力,提升系统性能。然而,手动实现分页查询需要编写繁琐的 SQL 语句和逻辑代码,容易出现错误。为了简化分页实现,我们可以借助 PageHelper 这一优秀的分页插件,它能够无缝整合进 Spring Boot 项目,快速实现分页功能。本文将详细介绍如何在 Spring Boot 中整合 PageHelper,并通过示例演示如何进行分页查询。访问网址:[MyBatis 分页插件 PageHelper](https://pagehelper.github.io/

(1).添加PageHelper依赖

首先,在 Spring Boot 项目中的 pom.xml 文件中添加 PageHelper 的依赖项。本文使用 pagehelper-spring-boot-starter 作为依赖包,该依赖能够自动配置 PageHelper,减少手动配置的复杂性。以下是 Maven 依赖的示例:<dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper-spring-boot-starter</artifactId><version>1.4.6</version>
</dependency>

(2).设置统一接受对象

为啥要封装一个对象接受呢?客户端需要两条数据,一个是分页查询的数据,还有一个是分页查询的总条数,但是返回值只能返回一个,因此要封装在一个对象中,代码如下。/*** 封装分页查询结果*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PageResult implements Serializable {private long total; //总记录数private List records; //当前页数据集合}

(3).使用方法

controller层(不变):

接受请求,请求数据=页码+每页数量+查询条件(非必须)调用业务层完成分页查询将结果响应给前端

service层:

调用PageHelper中的 startPage(参数页码,每页数量) 方法,然后返回一个Page对象。调用数据层完成分页查询直接用Page对象中的方法封装结果(数据+数量)返回给controller层

    @Overridepublic PageResult pageQuery(EmployeePageQueryDTO employeeQueryDTO) {// 使用插件pagehelper (mybatis提供)// 开始分页查询    1.页码 2.每页记录数PageHelper.startPage(employeeQueryDTO.getPage(), employeeQueryDTO.getPageSize());// 2.PageHelper会自动将查询结果封装为Page对象Page<Employee> page = employeeMapper.pageQuery(employeeQueryDTO);// 3.等page对象进行处理得到pageresult对象long total = page.getTotal(); // 总记录数List<Employee> records = page.getResult(); // 当前页数据列表return new PageResult(total, records);}

mapper层:

直接动态SQL拼接带查询条件的查询(SQL语句中不用使用limit)

    <!--分页查询--><select id="pageQuery" resultType="com.sky.entity.Employee">select * from employee<where><if test="name != null and name != ''">and name like concat('%', #{name}, '%')</if></where>order by create_time desc</select>



5.MD5加密

数据传输可使用密钥对的方式进行加密解密,使用签名方式验证数据是否可靠,而密码加密存储可使用MD5等一些算法对数据进行单向加密

使用方法直接调用Springboot提供的调用 DigestUtils.md5DigestAsHex 方法进行 MD5 加密,最后返回加密后的十六进制字符串即可

在这里插入图片描述

6.AOP技术

我们已经完成了后台系统的**员工管理功能**和**菜品分类功能**的开发,在**新增员工**或者**新增菜品分类**时需要设置创建时间、创建人、修改时间、修改人等字段,在**编辑员工**或者**编辑菜品分类**时需要设置修改时间、修改人等字段。这些字段属于公共字段,也就是也就是在我们的系统中很多表中都会有这些字段.每次调用service方法时都要执行set方法来设置新的修改时间,代码重复冗余,因此我们可以实现公共字段的填充

实现步骤:

1). 自定义注解 AutoFill,用于标识需要进行公共字段自动填充的方法

2). 自定义切面类 AutoFillAspect,统一拦截加入了 AutoFill 注解的方法,通过反射为公共字段赋值

3). 在 Mapper 的方法上加入 AutoFill 注解

自定义注解AutoFill

通过枚举来表示可以进行切面操作的两个数据库的操作方法

public enum OperationType {/*** 更新操作*/UPDATE,/*** 插入操作*/INSERT}/*** 自定义注解 用于标识某个方法需要进行公共字段自动填充处理*/
@Target({ElementType.METHOD})   // 注解添加的位置
@Retention(RetentionPolicy.RUNTIME) // 注解的生命周期
public @interface AutoFill {//指定数据库操作的类型 => insert update 可以通过枚举来指定OperationType value();
}

自定义切面类

/*** 自定义切面类,实现公共字段自动填充处理逻辑*/
@Aspect // 标识该类是一个切面类
@Component   // 将该类交给Spring管理 标识它是一个bean
@Slf4j   // 日志
public class AutoFillAspect {/*** 切入点*/// 拦截到所有的mapper包下的所有方法 (希望拦截insert和update方法) && 拦截到带有AutoFill注解的方法 ==>实现自动填充功能@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")public void autoFillPointCut() {}/*** 拦截到以后,在通知中进行公共字段的赋值* @param joinPoint 连接点* 前置通知*/@Before("autoFillPointCut()")public void autoFill(JoinPoint joinPoint) {log.info("开始进行公共字段自动填充...");// 1.获取当前拦截的类型是update还是insertMethodSignature signature  =(MethodSignature) joinPoint.getSignature(); // 方法签名对象AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class); // 获取方法上的注解对象OperationType operationType =  autoFill.value(); // 获得当前数据库的操作类型// 2.获取到方法的实体类Object[] args = joinPoint.getArgs(); // 获取方法的参数if(args == null || args.length == 0) { // 判断当前方法的参数是否为空return;}// 规定Object entity = args[0]; // 获取方法的第一个参数,也就是实体类对象// 3.为实体对象的公共属性赋值// 4.准备赋值的数据LocalDateTime now = LocalDateTime.now(); // 获取当前时间Long currentId = BaseContext.getCurrentId(); // 获取当前登录用户的id// 5.更据当前不同的操作类型,为对应的属性通过反射来赋值if(operationType == OperationType.INSERT) {// 为4个公共字段赋值 => 通过反射赋值 (获得set方法)try {Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);// 通过反射调用set方法setCreateTime.invoke(entity, now);setCreateUser.invoke(entity, currentId);setUpdateTime.invoke(entity, now);setUpdateUser.invoke(entity, currentId);} catch (Exception e) {e.printStackTrace();}}else if(operationType == OperationType.UPDATE) {// 为2个公共字段赋值try {Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);// 通过反射调用set方法setUpdateTime.invoke(entity, now);setUpdateUser.invoke(entity, currentId);} catch (Exception e) {e.printStackTrace();}}}
}

在mapper接口上调用注解

    /*** 更据主键动态修改属性* @param employee*/@AutoFill(value = OperationType.UPDATE)void update(Employee employee);

7.Redis

1.什么是Redis

Redis是一个开源的基于内存的键值对数据库,它的主要特征和作用包括:https://redis.io/

1、基于内存,读写速度极快,可以处理大量读写请求。

2、支持多种数据结构,如字符串、哈希、列表、集合、有序集合等,具有丰富的数据表示能力。 3、支持主从复制,提供数据冗余和故障恢复能力。

4、支持持久化,可以将内存数据保存到磁盘中。

5、支持事务,可以一次执行多个命令。

6、丰富的功能,可用于缓存、消息队列等场景。

主要应用场景包括:

1、缓存常见的使用场景,比如缓存查询结果、热点数据等,大大降低数据库负载。

2、处理大量的读写请求,比如访问统计、消息队列等。

3、排行榜、计数器等功能的实现。

4、pub/sub消息订阅。

5、QUE计划任务

6、分布式锁等。

综上,Redis是一个性能极高的内存数据库,支持丰富数据结构,提供持久化、事务等功能,非常适合缓存、消息队列等场景,被广泛应用于各种大型系统中。它的高性能、丰富功能使其成为非关系型数据库的重要选择之一。

2.常用的redis命令

Redis存储的是key-value结构的数据,其中key是字符串类型,value有5种常用的数据类型:

  • 字符串 string
  • 哈希 hash
  • 列表 list
  • 集合 set
  • 有序集合 sorted set / zset

Redis 中字符串类型常用命令:

  • SET key value 设置指定key的值
  • GET key 获取指定key的值
  • SETEX key seconds value 设置指定key的值,并将 key 的过期时间设为 seconds 秒
  • SETNX key value 只有在 key 不存在时设置 key 的值


在这里插入图片描述

Redis hash 是一个string类型的 field 和 value 的映射表,hash特别适合用于存储对象,常用命令:

  • HSET key field value 将哈希表 key 中的字段 field 的值设为 value
  • HGET key field 获取存储在哈希表中指定字段的值
  • HDEL key field 删除存储在哈希表中的指定字段
  • HKEYS key 获取哈希表中所有字段
  • HVALS key 获取哈希表中所有值

在这里插入图片描述

Redis 列表是简单的字符串列表,按照插入顺序排序,常用命令:

  • LPUSH key value1 [value2] 将一个或多个值插入到列表头部
  • LRANGE key start stop 获取列表指定范围内的元素
  • RPOP key 移除并获取列表最后一个元素
  • LLEN key 获取列表长度
  • BRPOP key1 [key2 ] timeout 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超 时或发现可弹出元素为止


在这里插入图片描述

Redis set 是string类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据,常用命令:

  • SADD key member1 [member2] 向集合添加一个或多个成员
  • SMEMBERS key 返回集合中的所有成员
  • SCARD key 获取集合的成员数
  • SINTER key1 [key2] 返回给定所有集合的交集
  • SUNION key1 [key2] 返回所有给定集合的并集
  • SREM key member1 [member2] 移除集合中一个或多个成员

在这里插入图片描述

Redis有序集合是string类型元素的集合,且不允许有重复成员。每个元素都会关联一个double类型的分数。常用命令:

常用命令:

  • ZADD key score1 member1 [score2 member2] 向有序集合添加一个或多个成员
  • ZRANGE key start stop [WITHSCORES] 通过索引区间返回有序集合中指定区间内的成员
  • ZINCRBY key increment member 有序集合中对指定成员的分数加上增量 increment
  • ZREM key member [member …] 移除有序集合中的一个或多个成员

Redis的通用命令是不分数据类型的,都可以使用的命令:

  • KEYS pattern 查找所有符合给定模式( pattern)的 key
  • EXISTS key 检查给定 key 是否存在
  • TYPE key 返回 key 所储存的值的类型
  • DEL key 该命令用于在 key 存在是删除 key

在Java中操作Redis

Spring Boot提供了对应的Starter,maven坐标:

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

Spring Data Redis中提供了一个高度封装的类:RedisTemplate,对相关api进行了归类封装,将同一类型操作封装为operation接口,具体分类如下:

  • ValueOperations:string数据操作
  • SetOperations:set类型数据操作
  • ZSetOperations:zset类型数据操作
  • HashOperations:hash类型的数据操作
  • ListOperations:list类型的数据操作

环境搭建

1). 导入Spring Data Redis的maven坐标(已完成) org.springframework.boot spring-boot-starter-data-redis

2). 配置Redis数据源

在application-dev.yml中添加 sky: redis: host: localhost port: 6379 password: 123456 database: 10

3). 编写配置类,创建RedisTemplate对象

@Configuration
@Slf4j
public class RedisConfiguration {// 提供一个方法返回一个模板对象@Beanpublic RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){log.info("开始创建redisTemplate对象");// 1.创建一个redisTemplate对象RedisTemplate redisTemplate =  new RedisTemplate();// 2.与连接工厂对象关联redisTemplate.setConnectionFactory(redisConnectionFactory);// 3.设置redis的key序列化器redisTemplate.setKeySerializer(new StringRedisSerializer());return redisTemplate;}
}

Java中实现Redis中的所有操作

public class SpringDataRedisTest {// 注入redisTemplate@Autowiredprivate RedisTemplate redisTemplate;/***  测试redisTemplate*/@Testpublic void TestRedisTemplate(){System.out.println(redisTemplate);// 封装了5类接口// 1.操作字符串类型ValueOperations valueOperation = redisTemplate.opsForValue();// 2.操作Hash类型HashOperations hashOperation = redisTemplate.opsForHash();// 3.操作List类型ListOperations listOperation = redisTemplate.opsForList();// 4.操作Set类型SetOperations setOperation = redisTemplate.opsForSet();// 5.操作ZSet类型ZSetOperations zSetOperation = redisTemplate.opsForZSet();}/*** 操作字符串类型*/@Testpublic void TestString(){// set get set-ex set-nxValueOperations valueOperations = redisTemplate.opsForValue();valueOperations.set("name","张三");String name = (String) valueOperations.get("name");System.out.println(name);// 设置过期时间valueOperations.set("code","123654",100, TimeUnit.SECONDS);// 不存在才设置valueOperations.setIfAbsent("lock","1");valueOperations.setIfAbsent("lock","2");}/*** 操作Hash类型*/@Testpublic void testHash(){// h-set h-get h-del h-keys h-valsHashOperations hashOperations = redisTemplate.opsForHash();hashOperations.put("user","name","张三");hashOperations.put("user","age","18");String name = (String) hashOperations.get("user","name");System.out.println(name);Set keys = hashOperations.keys("user");System.out.println(keys);List values = hashOperations.values("user");System.out.println(values);hashOperations.delete("user","age");}/*** 操作List类型数据*/@Testpublic void TestList(){// lpush lrange rpop llenListOperations listOperations = redisTemplate.opsForList();// 插入多个listOperations.leftPushAll("mylist","a","b","c","d");// 插入单个listOperations.leftPush("mylist","e");// 查询列表 0 -1 表示查询所有listOperations.range("mylist",0,-1);// 输出整个集合System.out.println("mylist");// 从列表右侧移除元素listOperations.rightPop("mylist");// 获取列表长度long size = listOperations.size("mylist");System.out.println(size);}/***  操作集合类型数据*/@Testpublic void TestSet(){// sadd:添加 smembers:查看集合所有成员 scard:元素个数 sinter:交集 sunion:并集 srem:删除元素SetOperations setOperations = redisTemplate.opsForSet();setOperations.add("myset1","a","b","c","d");setOperations.add("myset2","a","b","x","y");// 获取集合所有成员Set members = setOperations.members("myset1");System.out.println(members);//获取集合大小long size = setOperations.size("myset1");System.out.println(size);// 求交集Set set = setOperations.intersect("myset1", "myset2");System.out.println(set);// 求并集Set set1 = setOperations.union("myset1", "myset2");System.out.println(set1);// 删除元素setOperations.remove("myset1","a","b");}/*** 操作有序集合*/@Testpublic void TestZSet(){// zadd zrange:获取指定范围的数据 zincrby:给某个元素加分数 ZremZSetOperations zSetOperations = redisTemplate.opsForZSet();zSetOperations.add("myzset","a",100);zSetOperations.add("myzset","b",80);zSetOperations.add("myzset","c",90);zSetOperations.add("myzset","d",70);// 获取指定范围的数据Set range = zSetOperations.range("myzset", 0, -1);System.out.println(range);// 给某个元素加分数zSetOperations.incrementScore("myzset","a",10);// 删除元素zSetOperations.remove("myzset","a");}/*** 通用命令操作*/@Testpublic void TestCommon(){// keys exits type delSet keys = redisTemplate.keys("*");System.out.println(keys);Boolean exists = redisTemplate.hasKey("mylist");for(Object key:keys){DataType type = redisTemplate.type(key);System.out.println(type);}redisTemplate.delete("mylist");}
}

使用:修改店铺状态

调用redisTemplate来设置一个Key对应店铺状态

    @PutMapping("{status}")@ApiOperation("店铺状态修改")public Result setStatus(@PathVariable Integer status){log.info("店铺状态修改:{}",status == 1?"营业中":"打烊中");redisTemplate.opsForValue().set(KEY,status);return Result.success();}

查询店铺状态

调用redisTemplate.opsForValue()方法来获得店铺状态

    @GetMapping("/status")@ApiOperation("店铺营业状态查询")public Result<Integer> getStatus() {Integer status = (Integer) redisTemplate.opsForValue().get(KEY);log.info("店铺营业状态:{}",status == 1?"营业中":"打烊中");return Result.success(status);}

注意的是:

application.yml 是一种常用于配置文件的格式,通常用于 Spring Boot 等框架中,有一些格式要求,以确保配置信息能够被正确解析和使用:

  1. 基本格式
  • 缩进application.yml 文件使用缩进来表示层级关系。通常使用两个空格进行缩进,而不是制表符(Tab)。例如:

    server:
    port: 8080
    address: localhost

  • 键值对:配置项以键值对的形式存在,键和值之间用冒号(:)分隔。冒号后面通常有一个空格,然后是值。例如:

    spring:
    datasource:
    url: jdbc:mysql://localhost:3306/mydb
    username: root
    password: password

  1. 文件结构
  • 顶层配置:文件的顶层可以包含多个顶层配置项,每个配置项之间通过换行分隔。例如:

    server:
    port: 8080

    spring:
    datasource:
    url: jdbc:mysql://localhost:3306/mydb
    username: root
    password: password

  • 配置文件的顺序:虽然顺序通常不影响配置的解析,但为了可读性,建议按照逻辑顺序排列配置项。

8.webSocket

1.什么是webSocket

WebSocket是一种协议,用于在Web应用程序和服务器之间建立实时、双向的通信连接。它通过一个单一的TCP连接提供了持久化连接,这使得Web应用程序可以更加实时地传递数据。WebSocket协议最初由W3C开发,并于2011年成为标准。

WebSocket的优势包括:

  • 实时性: 由于WebSocket的持久化连接,它可以实现实时的数据传输,避免了Web应用程序需要不断地发送请求以获取最新数据的情况。

  • 双向通信: WebSocket协议支持双向通信,这意味着服务器可以主动向客户端发送数据,而不需要客户端发送请求。

  • 减少网络负载: 由于WebSocket的持久化连接,它可以减少HTTP请求的数量,从而减少了网络负载。

WebSocket的劣势包括:

  • 需要浏览器和服务器都支持: WebSocket是一种相对新的技术,需要浏览器和服务器都支持。一些旧的浏览器和服务器可能不支持WebSocket。

  • 需要额外的开销: WebSocket需要在服务器上维护长时间的连接,这需要额外的开销,包括内存和CPU。

  • 安全问题: 由于WebSocket允许服务器主动向客户端发送数据,可能会存在安全问题。服务器必须保证只向合法的客户端发送数据。

WebSocket 生命周期描述了 WebSocket 连接从创建到关闭的过程。一个 WebSocket 连接包含以下四个主要阶段:

  • 连接建立阶段(Connection Establishment): 在这个阶段,客户端和服务器之间的 WebSocket 连接被建立。客户端发送一个 WebSocket 握手请求,服务器响应一个握手响应,然后连接就被建立了。

  • 连接开放阶段(Connection Open): 在这个阶段,WebSocket 连接已经建立并开放,客户端和服务器可以在连接上互相发送数据。

  • 连接关闭阶段(Connection Closing): 在这个阶段,一个 WebSocket 连接即将被关闭。它可以被客户端或服务器发起,通过发送一个关闭帧来关闭连接。

  • 连接关闭完成阶段(Connection Closed): 在这个阶段,WebSocket 连接已经完全关闭。客户端和服务器之间的任何交互都将无效。

图片

在Java中使用WebSocket

1.导入依赖坐标

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

2.使用Java Webocket API编写服务端

/*** WebSocket服务*/
@Component
@ServerEndpoint("/ws/{sid}")
public class WebSocketServer {//存放会话对象private static Map<String, Session> sessionMap = new HashMap();/*** 连接建立成功调用的方法*/@OnOpenpublic void onOpen(Session session, @PathParam("sid") String sid) {System.out.println("客户端:" + sid + "建立连接");sessionMap.put(sid, session);}/*** 收到客户端消息后调用的方法** @param message 客户端发送过来的消息*/@OnMessagepublic void onMessage(String message, @PathParam("sid") String sid) {System.out.println("收到来自客户端:" + sid + "的信息:" + message);}/*** 连接关闭调用的方法** @param sid*/@OnClosepublic void onClose(@PathParam("sid") String sid) {System.out.println("连接断开:" + sid);sessionMap.remove(sid);}/*** 群发** @param message*/public void sendToAllClient(String message) {Collection<Session> sessions = sessionMap.values();for (Session session : sessions) {try {//服务器向客户端发送消息session.getBasicRemote().sendText(message);} catch (Exception e) {e.printStackTrace();}}}}

客户端界面的代码

WebSocket Demo 发送消息 关闭连接

9.自定义定时任务类

 通常,在我们的项目中需要定时给前台发送一些提示性消息或者我们想要的定时信息,这个时候就需要使用定时任务来实现这一功能

首先要在项目启动类上添加注解@EnableScheduling 用来开启任务调度

然后,就可以开始编写一个简单的自定义定时任务的逻辑

@Component
@Slf4j
public class MyTask {/*** 定时任务  每5秒执行一次*/@Scheduled(cron = "0/5 * * * * ?") // 0/5表示从0秒开始每隔5秒执行一次public void exectuTask(){log.info("定时任务执行了");}
}
  1. @Scheduled(fixedRate = 5000)
  • 每隔 5000 毫秒(5 秒)执行一次任务。

  • 任务的执行时间间隔是固定的,不考虑任务的执行时间。

  1. @Scheduled(fixedDelay = 30000)
  • 每次任务执行完成后,延迟 30000 毫秒(30 秒)再执行下一次。

  • 任务的延迟时间是从上一次任务完成开始计算的。

  1. @Scheduled(cron = "0 0/30 * * * ?")
  • 使用 Cron 表达式定义任务的执行时间。

  • 示例中的 Cron 表达式表示每 30 分钟执行一次。

项目中的运用:定时处理订单任务的状态

1.每天凌晨处理还在派送中的订单

2.每隔5分钟处理超时订单(因为已经派送到了,可能还没有点)

@Component
@Slf4j
public class OrderTask {@Autowiredprivate OrderMapper orderMapper;/*** 超时订单处理* 定时处理超时订单* 每分钟出发一次*/@Scheduled(cron = "0 * * * * ?") // 每分钟执行一次public void processTimeoutOrder(){log.info("定时处理超时订单:{}", LocalDateTime.now());// select * from orders where status = ? and order_time < (当前时间-15分钟)List<Orders> list = orderMapper.getByStatusAndOrderTimeLt(Orders.PENDING_PAYMENT, LocalDateTime.now().minusMinutes(15));// 处理超时订单if(list != null && list.size() > 0){for(Orders orders : list){orders.setStatus(Orders.CANCELLED);orders.setCancelReason("订单超时,自动取消");orders.setCancelTime(LocalDateTime.now());orderMapper.update(orders);}}}/*** 处理一直处于派送中的订单* 每天凌晨1点出发一次*/@Scheduled(cron = "0 0 1 * * ?") // 每天凌晨1点执行一次public void processDeliveryOrder(){log.info("定时处理一直处于派送中的订单:{}", LocalDateTime.now());// select * from orders where status =? and order_time < (当前时间-60分钟)List<Orders> list = orderMapper.getByStatusAndOrderTimeLt(Orders.DELIVERY_IN_PROGRESS, LocalDateTime.now().minusMinutes(60));// 处理超时订单if(list!= null && list.size() > 0){for(Orders orders : list){orders.setStatus(Orders.COMPLETED);orderMapper.update(orders);}}}
}

相关文章:

苍穹外卖学习笔记

整体概述 1).用户层 本项目中在构建系统管理后台的前端页面&#xff0c;我们会用到H5、Vue.js、ElementUI、apache echarts(展示图表)等技术。而在构建移动端应用时&#xff0c;我们会使用到微信小程序 2).网关层 Nginx是一个服务器&#xff0c;主要用来作为Http服务器&…...

Spring常用注解汇总

1. IOC容器与Bean管理 注解说明示例Component通用注解&#xff0c;标记类为Spring Bean Component public class MyService { ... } Controller标记Web控制器&#xff08;应用在MVC的控制层&#xff09; Controller public class UserController { ... } Service标记业务逻辑层…...

深度强化学习中的深度神经网络优化策略:挑战与解决方案

I. 引言 深度强化学习&#xff08;Deep Reinforcement Learning&#xff0c;DRL&#xff09;结合了强化学习&#xff08;Reinforcement Learning&#xff0c;RL&#xff09;和深度学习&#xff08;Deep Learning&#xff09;的优点&#xff0c;使得智能体能够在复杂的环境中学…...

每日一题力扣2974.最小数字游戏c++

2974. 最小数字游戏 - 力扣&#xff08;LeetCode&#xff09; class Solution { public:vector<int> numberGame(vector<int>& nums) {vector<int> arr(nums.size());sort(nums.begin(),nums.end());for(size_t i0;i<nums.size();i2){arr[i]nums[i1]…...

软考中级-软件设计师 准备

软考中级-软件设计师 准备 一、软考相关1.1、考试时间1.2、考试时长1.3、题型和分值&#xff1a; 二、软考备考2.1、相关书籍2.2、推荐课程&#xff1a;B站up主zst_20012.3、学习路线 一、软考相关 1.1、考试时间 一年有两次软考&#xff0c;一般是五月末和十一月的中旬 以下…...

EasyRTC嵌入式音视频通信SDK:WebRTC技术下的硬件与软件协同演进,开启通信新时代

在当今数字化时代&#xff0c;智能设备的普及和人们对实时通信需求的不断增长&#xff0c;推动了嵌入式音视频通信技术的快速发。EasyRTC嵌入式音视频通信SDK凭借其独特的技术特点和应用优势&#xff0c;在嵌入式设备和多平台实时通信领域脱颖而出。 1、轻量级设计与高性能 Ea…...

lua垃圾回收

lua垃圾回收 lua 垃圾回收 lua 垃圾回收 collectgarbage(“count”)获取当前lua脚本占用内存字节数(单位为KB)。 collectgarbage(“collect”)执行一次垃圾回收。 xxxnil 将变量置为空&#xff0c;会释放内存。 lua中的机制和c#中回收机制很类似 解除羁绊(置为空)。 --垃圾回…...

Lineageos 22.1(Android 15)实现负一屏

一、前言 方案是参考的这位大佬的&#xff0c;大家可以去付费订阅支持一波。我大概理一下Android15的修改。 大佬的方案代码 二、Android15适配调整 1.bp调整&#xff0c;加入aidl引入&#xff0c;这样make之后就可以索引代码了 filegroup {name: "launcher-src"…...

《深度学习》——YOLOv3详解

文章目录 YOLOv3简介YOLOv3核心原理YOLOv3改进YOLOv3网络结构 YOLOv3简介 YOLOv3&#xff08;You Only Look Once, version 3&#xff09;是一种先进的实时目标检测算法&#xff0c;由 Joseph Redmon 和 Ali Farhadi 开发。它在目标检测领域表现出色&#xff0c;具有速度快、精…...

【设计模式】三十一、状态模式

系列文章|源码 https://github.com/tyronczt/design-mode-learn 文章目录 系列文章|源码一、模式核心思想二、模式结构三、Java代码示例&#xff1a;订单状态管理1. 定义状态接口2. 实现具体状态类3. 上下文类&#xff08;Context&#xff09;4. 客户端调用5. 运行截图 四、状…...

vue 获取当前时间并自动刷新

新增需求&#xff0c;需要在大屏的右上角展示当前时间&#xff0c;并实时按秒刷新&#xff0c;通过通义千问搜索关键js代码后&#xff0c;整理出如下代码。 【效果图】 【HTML】 <div class"time-wrap">{{ formattedDateTime }}<span> {{ weekTime }}&…...

C 语 言 --- 扫 雷 游 戏(初 阶 版)

C 语 言 --- 扫 雷 游 戏 初 阶 版 代 码 全 貌 与 功 能 介 绍扫雷游戏的功能说明游 戏 效 果 展 示游 戏 代 码 详 解game.htest.cgame.c 总结 &#x1f4bb;作 者 简 介&#xff1a;曾 与 你 一 样 迷 茫&#xff0c;现 以 经 验 助 你 入 门 C 语 言 &#x1f4a1;个 人 主…...

WebDeveloper靶机详解

一、主机发现 arp-scan -l靶机ip为192.168.55.163 二、端口扫描、目录枚举、漏洞扫描、指纹识别 2.1端口扫描 nmap --min-rate 10000 -p- 192.168.55.163发现并无特殊端口开放 扫描一下UDP端口 nmap -sU --min-rate 10000 -p- 192.168.55.163没有扫描到UDP端口 2.2目录枚…...

Cursor IDE 入门指南

什么是 Cursor? Cursor 是一款集成了 AI 功能的现代代码编辑器&#xff0c;基于 VSCode 开发&#xff0c;专为提高开发效率而设计。它内置强大的 AI 助手功能&#xff0c;能够理解代码、生成代码、解决问题&#xff0c;帮助开发者更快、更智能地完成编程任务。 基础功能 1.…...

来源于胡椒的亚甲二氧桥CYP450-文献精读119

Piper nigrum CYP719A37 Catalyzes the Decisive Methylenedioxy Bridge Formation in Piperine Biosynthesis 胡椒 (Piper nigrum) CYP719A37 催化胡椒碱生物合成中关键的亚甲二氧桥形成 摘要 胡椒 (Piper nigrum) 是世界上最受欢迎的香料之一。其主要辛辣成分胡椒碱 (piper…...

STM32八股【1】-----启动流程和startup文件理解

启动流程 知识点 MCU 上电复位。MSP从向量表第0个地址读取一个32位&#xff08;2字节&#xff09;的值并保存&#xff0c;该值为栈顶地址。PC计数器从第1个地址读取一个两字节的值并保存&#xff0c;该值为程序入口&#xff0c;一般是Reset_Handler。想了解FLASH地址映射可以…...

Docker与K8S是什么该怎么选?

用了很久的容器化&#xff0c;最近突然看到一个问题问&#xff1a; docker和K8S究竟有什么区别&#xff0c;到底该怎么选&#xff1f;我认真思考了一会&#xff0c;发现一时间还真说不明白&#xff0c;于是就研究了一段时间发布今天的博文&#xff01; Docker vs Kubernetes&a…...

梦回杭州...

她对我说&#xff0c;烟雨中的西湖更别有情趣&#xff0c;我也怀着对‘人间天堂’的憧憬踏上了向往之旅。第一次亲密接触没有感觉中那么好&#xff0c;现在想起来是那时的人和心情都没能安静下来&#xff0c;去慢慢品味它的美。 六下杭州&#xff0c;亲历每一片风景&#xff0c…...

NAT 实验:多私网环境下 NAPT、Easy IP 配置及 FTP 服务公网映射

NAT基本概念 定义&#xff1a;网络地址转换&#xff08;Network Address Translation&#xff0c;NAT&#xff09;是一种将私有&#xff08;保留&#xff09;地址转化为合法公网 IP 地址的转换技术&#xff0c;它被广泛应用于各种类型 Internet 接入方式和各种类型的网络中。作…...

SEED XSS 实验环境搭建步骤《精简版》

目录 1. 启动 SEED Ubuntu VM 2. 配置 /etc/hosts 3. 下载并解压 Labsetup.zip 4. 使用 Docker Compose 启动实验环境 5. 确保容器正常运行 6. 访问 Elgg Web 应用 7. 账户信息 8. 进入容器内部 9.实验环境搭建完成 &#x1f389; 10. 关闭实验 11.&#x1f4a1; 重…...

YOLO数据集分割训练集、测试集和验证集

记录一下自己的分割代码。 注意&#xff1a; 这是在windows环境&#xff0c;请Linux的同学们注意。标签为txt&#xff0c;图像为jpg&#xff0c;其他的我没试过喔。 训练集、验证集、测试集&#xff08;7:2:1&#xff09; import os import shutil import random from tqdm…...

Debug-037-table列表勾选回显方案

效果展示&#xff1a; 图1 图2 最近实现一个支持勾选的el-table可以回显之前勾选项的功能。实现了一个“编辑”的功能&#xff1a; 在图1中的列表中有三行数据&#xff0c;当点击“更换设备”按钮时&#xff0c;打开抽屉显示el-table组件如图2所示&#xff0c;可以直接回显勾选…...

使用 libevent 构建高性能网络应用

使用 libevent 构建高性能网络应用 在现代网络编程中&#xff0c;高性能和可扩展性是开发者追求的核心目标。为了实现这一目标&#xff0c;许多开发者选择使用事件驱动库来管理 I/O 操作和事件处理。libevent 是一个轻量级、高性能的事件通知库&#xff0c;广泛应用于网络服务…...

人脸表情识别系统分享(基于深度学习+OpenCV+PyQt5)

最近终于把毕业大论文忙完了&#xff0c;众所周知硕士大论文需要有三个工作点&#xff0c;表情识别领域的第三个工作点一般是做一个表情识别系统出来&#xff0c;如下图所示。 这里分享一下这个表情识别系统&#xff1a; 采用 深度学习OpenCVPyQt5 构建&#xff0c;主要功能包…...

AtCoder - arc086_d Shift and Decrement分析与实现

分析与思路 可以把操作流程表示成下图 以进行四次除法操作为例&#xff1a; 这里有一个关键点&#xff1a;对于每个p_i (0< i <x-1) &#xff0c;x是除法操作的次数&#xff0c;如果p_i>2&#xff0c;可以将2个p_i的减法操作去掉&#xff0c;在p_(i1)中增加一个减法…...

学习111

项目名称项目简介主要功能技术原理GitHub地址browser-use智能浏览器工具&#xff0c;让AI像人类一样操作浏览器&#xff0c;实现网页自动化网页浏览与操作、多标签页管理、视觉识别与内容提取、操作记录与重复执行、自定义动作支持、主流LLM模型支持为大语言模型服务的创新Pyth…...

Android Jetpack Compose介绍

Android Jetpack Compose Android Jetpack Compose 是 Google 推出的现代 UI 工具包&#xff0c;用于以声明式的方式构建 Android 应用的 UI。它摒弃了传统的 XML 布局方式&#xff0c;完全基于 Kotlin 编写&#xff0c;提供了更简洁、更强大的 UI 开发体验。以下是 Compose 的…...

tcping 命令的使用,ping IP 和端口

1. ‌Windows系统安装‌ ‌下载tcping工具‌&#xff1a;根据系统位数&#xff08;32位或64位&#xff09;下载对应的tcping.exe文件。‌安装步骤‌&#xff1a; 将下载的tcping.exe文件复制到C:\Windows\System32目录下。如果下载的是64位版本&#xff0c;需将文件名改为tcpi…...

天地图InfoWindow插入React自定义组件

截至2025年03月21日天地图的Marker不支持添加Label; 同时Label和Icon是不支持自定义HTMLElement只支持String&#xff1b;目前只有InfoWindow支持自定义HTMLElement; 效果图 React核心api import ReactDOM from react-dom/client const content document.createElement(div);…...

003-掌控命令行-CLI11-C++开源库108杰

首选的现代C风格命令行参数解析器! &#xff08;本课程包含两段教学视频。&#xff09; 以文件对象监控程序为实例&#xff0c;五分钟实现从命令行读入多个监控目标路径&#xff1b;区分两大时机&#xff0c;学习 CLI11 构建与解析参数两大场景下的异常处理&#xff1b;区分三…...