【苍穹外卖|项目】万字总结

前言

前几天就把苍穹外卖写完了,今天就准备用CSDN记录复盘一下,最后再加上我个人的感悟。本文适合打算开始写苍穹外卖的小伙伴阅读,提高对整体项目的认知。

项目介绍

这是一款为餐饮类企业定制的软件产品,分为管理端和用户端。实现用户端点单,管理端处理订单的简易外卖软件

管理端

在这里插入图片描述

用户端

技术选型

在这里插入图片描述

用户层

商家端
     ~~~~     简单的网页,使用了前端三件套,以及ElementUI, echarts 等技术
用户端
     ~~~~     使用微信小程序框架进行开发

网关层

NGINX:
     ~~~~     使用nginx负载均衡来合理请求到多态服务器,减轻服务器压力

应用层

Springboot:
     ~~~~      Springboot是基于Spring的开源框架,简化了各种配置,方便开发人员工作
SpringMVC:
     ~~~~      SpringMVC是基于 MVC(Model-View-Controller)模式的 Web 应用程序开发框架。它提供了一种结构清晰、模块化的方式来构建可扩展的 Web 应用程序
HttpClient:
     ~~~~      Http请求的开源库,常用于后端请求各种接口的时候使用
SpringTask
     ~~~~      Spring框架中用于处理定时任务的工具
SpringCache:
     ~~~~      SpringCache是Spring框架中用于缓存处理的,可以把用户频繁访问的数据放入缓存当中,减少对数据库的IO操作,减轻数据库的压力
JWT:
     ~~~~      校验用户身份是否过期,是否存在等等
阿里云OSS:
     ~~~~      用于存储项目内的图片文件等等
Swagger:
     ~~~~      Swagger是一款RESTFUL接口的文档在线自动生成+功能测试功能软件。Swagger是一个规范和完整的框架,用于生成、描述、调用和可视化RESTfu风格的web服务。目标是使客户端和文件系统作为服务器一同样的速度来更新文件的方法,参数和模型紧密集成到服务器。这个解释简单点来讲就是说,swagger是一款可以根据restful风格生成的接口开发文档,并且支持做测试的一款中间软件。
POI:
     ~~~~      读取和写入Microsoft Office格式文件,如Word文档、Excel电子表格和PowerPoint演示文稿
Websocket:
     ~~~~      实时通信会话技术,用于商家管理端和服务器保持实时通信。
PageHelper:
     ~~~~      分页查询工具
spring data redis:
     ~~~~      Spring框架提供的操作Redis数据库的工具

数据层

MYSQL:
     ~~~~      关系型数据库,以表的形式将数据存入到硬盘当中
Redis:
     ~~~~      键值型存储系统,基于键值对的形式对数据进行存储,数据会被存储到内存当中

本数据库建表依据阿里巴巴开源手册
【依据】表名,字段必须时使用小写字母或数字,禁止出现数字开头。
【依据】表名不考虑复数形式,避免混意
此外,本项目的数据表在处理逻辑关系的时候,采用 使用逻辑外键,舍弃物理外键的形式。也就是说:我们的表之间的关系不通过物理外键的形式来进行关联,而是在代码层面使用代码逻辑的方式进行关联。
简单的讲:不在数据库中创建外键的方式构建表关系,而是在代码处理阶段,用代码逻辑来形成表关联,这样可以降低对数据库的访问压力,而且对数据库的修改也会轻松。

项目第一阶段技术

JWT令牌加密技术

     ~~~~     JWT(JSON WEB TOKEN)是用于身份验证和授权的开放标准,它定义了一种紧凑和自包含的方式,在其紧凑的形式中,JWT由以点(.)分隔的三个部分组成,它们是:Header(头部) Payload(载荷) Signature(签名),其中签名是用来验证令牌的完整性和合法性

JWT最常见的用法就是给用户下方令牌,在本项目当中,用户登录成功以后,后端会根据用户名等信息生成一个JWT令牌传给前端用户,前端每次请求的时候都会携带JWT令牌,后端在拦截器里会对用户每次请求的JWT令牌进行解析,如果令牌不完整,被修改或者令牌失效,后端就不会执行业务层代码,直接返回没有登录的信息。

     ~~~~     对称加密:对称加密使用相同的密钥用于加密和解密令牌。发送方使用密钥将头部和载荷部分加密,并生成签名。接收方使用相同的密钥通过解密和验证签名来验证令牌的完整性和真实性。对称加密是较简单和高效的加密方式,但需要确保密钥的安全性。
     ~~~~     非对称加密:非对称加密使用一对密钥,即公钥和私钥,用于加密和解密令牌。发送方使用私钥进行签名和加密,接收方使用公钥进行解密和验证签名。非对称加密提供了更高的安全性和可信任性,因为私钥是保密的。但与对称加密相比,非对称加密的计算开销更大。

请求拦截器

在这里插入图片描述

加密与解密

在这里插入图片描述

Nginx反向代理和负载均衡

负载均衡
     ~~~~     Nginx 的负载均衡功能允许将请求分发给多个应用服务器,以均衡负载和提高系统的可扩展性和可靠性。下面是一些常用的 Nginx 负载均衡配置方法:

  1. 轮询(Round Robin):这是默认的负载均衡策略。Nginx 将请求依次分发给每个后端服务器,确保每个服务器都能获得相同的请求数量。
  2. IP 哈希(IP Hash):Nginx 使用客户端 IP 地址的哈希值来决定将请求发送给哪个后端服务器。这种方式可以确保同一客户端的请求始终发送到同一个后端服务器,适用于某些需要会话保持的场景。
  3. 加权轮询(Weighted Round Robin):可以为每个后端服务器设置权重,高权重的服务器将获得更多的请求。这种方式可以根据服务器的性能和处理能力来分配负载。
  4. 最少连接(Least Connections):Nginx 根据当前连接数来选择最空闲的后端服务器,将请求发送给它。这样可以确保负载更均衡,避免某些服务器过载。

在这里插入图片描述

通过这几行代码就可以吧请求分发到不同的服务器当中,通过后面的参数可以配置请求分发的权重。

反向代理:

  • 有反向代理就有正向代理,而二者的区别很明显:反向代理隐藏服务器,正向代理隐藏客户端
  • 正向代理是客户端发送请求后通过代理服务器访问目标服务器,代理服务器代表客户端发送请求并将响应返回给客户端。正向代理隐藏了客户端的真实身份和位置信息,为客户端提供代理访问互联网的功能。
  • 反向代理是位于目标服务器和客户端之间的代理服务器,它代表服务器接收客户端的请求并将请求转发到真正的目标服务器上,并将得到的响应返回给客户端。反向代理隐藏了服务器的真实身份和位置信息,客户端只知道与反向代理进行通信,而不知道真正的服务器。
    在这里插入图片描述

MD5的加密方式存储密码

为了防止数据库泄露带来的用户账号密码安全性问题,我们即使是在数据库中也不会进行明文存储密码,而是存储MD5加密方法加密后的一串字符串。防止用户密码以明文的形式进行传递

md5加密解密网站

在这里插入图片描述
本系统通过前端传来的明文密码,然后后端进行MD5加密处理与数据库中的密文进行对比。

项目第二阶段技术

TreadLocal线程存储

ThreadLocal叫做线程变量,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的,也就是说该变量是当前线程独有的变量。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。因为每一次请求都是一个个独立的线程,所以我们可以把从JWT中解析到的用户数据存入到线程当中,在业务层代码里需要获得用户数据的时候,即可从线程变量中获取。
在这里插入图片描述
创建ThreadLocal对象:可以通过ThreadLocal的静态方法ThreadLocal.withInitial()来创建ThreadLocal对象,并初始化变量的初始值。
     ~~~~     设置变量值:通过ThreadLocal对象的set()方法可以设置当前线程的变量副本的值。
     ~~~~     获取变量值:通过ThreadLocal对象的get()方法可以获取当前线程的变量副本的值。
     ~~~~     清除变量值:通过ThreadLocal对象的remove()方法可以清除当前线程的变量副本的值,释放资源。
在这里插入图片描述
在拦截器中我们可以看到当从JWT解析到了用户ID之后,就把他放到了线程变量当中

在这里插入图片描述
在其中一个业务层代码当中要获取用户ID值,直接调用方法即可

基于消息转换器对时间进行格式化

在开发中我们无法控制前端传来的时间数据的格式,因此我们就需要对前端传来的数据格式进行格式化
而进行格式化,如果时间参数少,我们可以使用 @JsonFormat(pattern=“yyyy-MM-dd HH:mm:ss”)来对某个属性指定格式:
在这里插入图片描述
但是如果时间参数过多,我们再一个一个标注就太麻烦了。因此我们选择在Spirng MVC中再扩展一个消息转换器,统一对前端的发送给后端的时间数据进行处理:
在这里插入图片描述

首先我们先来介绍一下什么是消息转换器:
消息转换器在Spring MVC中负责处理请求和响应的数据格式转换,例如将Java对象转换为JSON格式或者把JSON格式转换为Java。
在上面代码中,我们重写了WebMvcConfigurationSupport中的extendMessageConverters方法,converters是MVC中各个消息转换器对象,这里我们先重新new了一个消息转换器对象,然后将自己写的消息转换器对象添加进去,并将其优先提高到0,这样在处理时间类型的数据的时候,我们的消息转换器就会被优先触发。

在这里插入图片描述

基于PageHelper的分页查询

Meavn坐标

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

     ~~~~     PageHelper是基于java的一个开源框架,用于在MyBatis等持久层框架中方便地进行分页查询操作。它提供了一组简单易用的API和拦截器机制,可以帮助开发者快速集成和使用分页功能。
PageHelper的主要功能包括:

  1. 分页查询支持:PageHelper提供了直接在SQL语句中添加分页相关的信息,如页码、每页记录数等,从而实现分页查询功能。
  2. 参数解析和设置:PageHelper可以解析传入的查询参数,并自动设置分页的相关参数,无需手动计算和设置。
  3. SQL拦截器:PageHelper通过自定义的SQL拦截器拦截和处理查询SQL,自动添加分页的SQL语句,实现分页查询。
  4. 排序支持:PageHelper还提供了对排序的支持,可以在分页查询中指定排序字段和排序方式。
  5. 分页信息返回:PageHelper会将查询结果封装在一个Page对象中,包含了分页的相关信息,如总记录数、总页数等。

在这里插入图片描述
首先PageHelper.startPage中转入两个数据,第一个数是页数,第二个数是一页展示的数据的多少,然后将数据库查出来的数据放入Page对象当中 之后可以返回数据总数和结果

基于注解和AOP的公共字段填充

在我们项目前期,会发现一个问题:我们的项目开发会涉及大量的数据表,而这些数据表中有一些重叠的字段,例如创建人,创建时间,修改人,修改时间。对这一部分字段的填充代码相同。这些填充部分的代码分布在整个项目的四处,不涉及核心功能,却影响了多个模块

聚焦到项目:我们的整体思路为:通过注解的方式标记方法,利用AOP思想创建一个切面,在切面中实现对标记方式中字段的填充,然后再运行原方法。这样就实现了在不改动原方法的前提下,实现了对代码的优化升级。
让我们基于整个项目了解一下基于AOP思想的注解开发:

首先先创建一个注解: AutoFill
在这里插入图片描述
在这里插入图片描述
而这个注解的作用很明显:使用在mapper层,标记对数据库的操作类型。这里是因为公共字段的填充只有修改和新增这两个类业务。我们需要准确的拦截这两个数据库层面的操作,在切面中完成对公共字段的填充。

其次完成切面的代码:

先通过切入点表达式,拦截到带有AutoFill的注解,之后再写通知,本次我们采用的是前置通知

在上文我们已经拿到了目标方法的第一个参数(我们人为约定把需要填充的字段放在第一个字段上,主要是为了简化操作,不然我们就需要通过反射拿到所有的参数,再逐个判断哪一个是需要进进行填充的字段)
通过反射的思想进行字段赋值。

这样下来,我们就实现了公共字段的自动填充,我们回顾整个代码逻辑,可以把整个过程总结为两步:

     ~~~~      1,自定义注解(标记类型),通过注解快速标记目标方法
     ~~~~      2,完成切面的逻辑代码,通过切入点表达式快速捕捉需要进行切入的方法,对这些方法进行处理

开启事务管理

在这里插入图片描述

首先以注解的方式开启事务管理

开启事务注解之后,我们只需要在需要捆绑成为一个事务的方法上添加 @Transactional

这样就把两张表捆绑为一个事务,当其中代码出现运行错误的时候,事务就会进行回滚

最后再介绍一些@Transactional 的常见属性

     ~~~~      1,value:可以用于指定事务管理器的名称或ID,用来管理当前方法的事务。例如:@Transactional(value = “transactionManager”)。
     ~~~~      2,readOnly:用于指定事务是否是只读的。如果将 readOnly 设置为 true,则事务只能进行读操作,而不能进行写操作。默认值为 false。
     ~~~~      3,timeout:用于指定事务的超时时间,单位为秒。在指定的时间内,如果事务还未提交或回滚,则事务将被强制回滚。
     ~~~~      4,isolation:用于指定事务的隔离级别。常见的隔离级别包括 DEFAULT、READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ 和 SERIALIZABLE。
     ~~~~      5,propagation:用于指定事务的传播行为。常见的传播行为包括 REQUIRED、REQUIRES_NEW、SUPPORTS、NOT_SUPPORTED、NEVER 和 MANDATORY。
     ~~~~      6,rollbackFor:用于指定需要回滚事务的异常类型数组。当方法抛出指定的异常时,事务将被回滚。
     ~~~~      7,noRollbackFor:用于指定不需要回滚事务的异常类型数组。当方法抛出指定的异常时,事务将不会被回滚。

隔离级别(Isolation Level)指的是在并发环境下,对于事务之间的数据访问与修改的隔离程度。常见的隔离级别包括:
DEFAULT:使用数据库默认的隔离级别。
READ_UNCOMMITTED:最低的隔离级别,允许读取未提交的数据(脏读),可能导致数据不一致。
READ_COMMITTED:要求一个事务只能读取其他已提交的数据,避免了脏读,但可能存在不可重复读和幻读的问题。
REPEATABLE_READ:要求一个事务读取的数据集合在事务执行期间保持一致,避免了脏读和不可重复读,但可能存在幻读的问题。
SERIALIZABLE:最高的隔离级别,要求读取数据时对其加锁,避免了脏读、不可重复读和幻读,但可能导致并发性能下降。

传播行为(Propagation)指的是事务在不同方法间进行传播的行为。常见的传播行为包括:

REQUIRED:如果当前存在事务,则加入到当前事务中,如果没有事务,则创建一个新的事务。
REQUIRES_NEW:无论当前是否存在事务,都创建一个新的事务,并挂起当前事务。
SUPPORTS:如果当前存在事务,则加入到当前事务中,如果没有事务,则以非事务的方式执行。
NOT_SUPPORTED:以非事务的方式执行,如果当前存在事务,则挂起该事务。
NEVER:以非事务的方式执行,如果当前存在事务,则抛出异常。
MANDATORY:要求当前必须存在事务,否则抛出异常。
根据具体的业务需求和并发情况,需要选择适当的隔离级别和传播行为来实现对事务的控制和管理。需要注意的是,隔离级别越高,数据一致性的保证越好,但并发性能可能会降低。而传播行为可以用于定义事务在多个方法间的传递方式,以确保事务的一致性和完整性。

项目第三阶段

Redis

Redis(Remote Dictionary Server)是一个开源的内存存储系统,常用于构建高性能、高可扩展性的应用程序。它支持多种数据结构,如字符串、哈希表、列表、集合、有序集合等,并提供了丰富的操作命令,使开发人员能够快速、灵活地处理数据。
而我们的项目中引入Redis的地方是:查询店铺营业状态 ,像这种店铺营业状态,本项目无非就两个状态:营业中/打样。而且它属于高频查询。只要用户浏览到这个店铺,前端就要自动发送请求到后端查询店铺状态。

像这样只存储一个店铺状态的数据,我们就不需要MYSQL了,因为MYSQL的高频IO会给服务器造成不小的压力

而Redis就刚好帮助我们解决了这个痛点。 正如我们前面介绍的:Redis是基于键值对进行存储的。

而键值对这种形式就符合我们对于店铺营业状态数据格式的理想存储状态,Redis也把数据放到缓存中,而不是磁盘,有效缓解了这种高频查询给磁盘带来的压力
明白了基本思路之后,我们来看一下思路如何转化为JAVA代码:
首先,先要导入Spring Data Redis的依赖:

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

编写配置类,创建RedisTemplate对象:
在这里插入图片描述
使用RedisTemplate 对象操作Redis

可以在Java中操作Redis了。只需要创建一个Redis对象,利用Spring Data Redis 就可以对Redis进行操作。

利用Redis来对业务优化

用户一旦点进店铺,店铺就需要向用户展示菜品,套餐等等数据。这种通过少量的操作可以调起大量后端操作的行为,是一个很危险的杠杆操作。而在高并发环境下,这无疑又是在拷打服务器。

而且这种重复查询的请求,正是我们要优化的目标。

我们的思路很简单:缓存请求相应内容,如果小程序又发送相同请求,那么我们就从缓存中直接返回相应内容。这样就减少了直接对后端的数据库的查询。

而这句话中,解决问题的重点两个字就可以概括:缓存!

这与我们上面讲到的Redis的职能岂不是相同嘛?我们在初识Redis的时候就说过:Redis是高性能的,基于键值对的,写入缓存的 内存存储系统。

那么对我们上述的思路进行细节化补充:在高频查询控制器上额外利用Redis缓存一份响应结果,等到后端接受到了相同的请求的时候,我们就查询Redis中有无对应的数据,如果有就从Redis中拿取,如果没有数据就进入server层,从数据库拿,拿了之后缓存到Redis中。


但是这种数据库不参与的获取数据的方式存在问题

因为我们在添加redis作为缓冲区之后,如果缓冲区中存在数据,我们是直接从缓冲区拿数据的,如果我们更改了数据库,可能就会造成数据库与缓冲区数据不一致的情况。

     ~~~~     读写双写(Write-through):在更新数据库时,同时更新Redis缓存。这意味着在写入数据库的同时,将相同的数据写入Redis缓存。这种方式确保了数据库和缓存中的数据始终保持一致。但是需要注意的是,双写操作会增加系统的写入负载和延迟,并且需要保证写入操作的原子性。
     ~~~~     读写更新(Write-behind):在更新数据库时,延迟更新Redis缓存。这种方式先更新数据库,然后异步地更新Redis缓存,以提高写入的性能和响应速度。在这种情况下,可能会出现一小段时间内数据库和缓存数据的不一致,但后续的读取操作会从数据库中获取最新的数据并更新缓存。
     ~~~~     缓存失效策略:通过在缓存中设置适当的过期时间或失效策略,确保缓存中的数据在一定时间后会过期并从数据库中重新加载。这样可以保证在数据更新或过期后,下一次读取操作将从数据库中获取最新的数据,并更新缓存。这种方式适用于数据变化不频繁、对数据实时性要求不高的场景。
     ~~~~     发布订阅模式(Pub/Sub):使用Redis的发布订阅功能,当数据库中的数据发生变化时,通过发布消息的方式通知订阅者(Redis缓存)进行更新。这样可以保证在数据发生变化时,及时通知Redis缓存更新,以保持数据的一致性。

利用Spring cache来对业务进行优化:

首先导入坐标

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

Spring Cache 是 Spring Framework 提供的缓存抽象和实现框架。它为应用程序提供了一种统一的缓存抽象,支持多种缓存技术的集成,并支持 AOP 机制实现基于方法的缓存,从而简化了缓存的使用和管理。

下面是 Spring Cache 的一些特点和常用功能:

缓存技术支持:Spring Cache 支持多种主流的缓存技术,包括 EHCache、Redis、Guava 等。

基于注解的缓存:Spring Cache 提供了基于注解的缓存,可以在方法上直接使用 @Cacheable、@CachePut、@CacheEvict 等注解,实现对方法结果的自动缓存和更新。
在这里插入图片描述

@CachePut(cacheNames = "userCache",key = "#user.id")  // key的生成,userCache::user.id 动态获取用户id
// 存入缓存
@CacheEvict(cacheNames = "userCache",key = "#id")
@CacheEvict(cacheNames = "userCache",allEntries = true)
// 删除缓存数据 删除一条或者多条
@Cacheable(cacheNames = "userCache ",key = "#id")
// 获得缓存 如果有则从缓存获取,如果没有则从数据库获取然后存入缓存当中

HttpClient

     ~~~~      HttpClient是一个用于服务端通信的库,用于后端发送各种请求

// 例子
    @Test
    public void test_get() throws Exception {
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpGet httpGet = new HttpGet("http://127.0.0.1:8080/user/shop/status");
        CloseableHttpResponse response = httpClient.execute(httpGet);
        // 获取请求状态码
        int statusCode = response.getStatusLine().getStatusCode();
        System.out.println("服务器响应码" + statusCode);
        // 获取响应数据
        HttpEntity entity = response.getEntity();
        String body = EntityUtils.toString(entity);
        System.out.println("服务端响应数据" + body);
        response.close();
        httpClient.close();
    }

    @Test
    public void test_post() throws Exception {
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpPost httpPost = new HttpPost("http://127.0.0.1:8080/admin/employee/login");
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("username", "admin");
        jsonObject.put("password", "123456");
        StringEntity entry = new StringEntity(jsonObject.toString());
        // 指定请求数据和编码
        entry.setContentType("application/json");
        entry.setContentEncoding("UTF-8");
        httpPost.setEntity(entry);
        CloseableHttpResponse response = httpClient.execute(httpPost);
        int statusCode = response.getStatusLine().getStatusCode();
        System.out.println("服务器响应码" + statusCode);
        HttpEntity entity = response.getEntity();
        String body = EntityUtils.toString(entity);
        System.out.println("服务端响应数据" + body);
        response.close();
        httpClient.close();
    }

微信登录

在这里插入图片描述
通过这张图,我们可以了解实现微信登录的基本流程

1.我们的小程序会调用wx.login()来获得一个code。该 code 的作用是用于后续的用户身份验证和获取用户信息。

2.小程序的wx.request会把code发送给后端,后端再打包自己的小程序ID(appid)和小程序密钥(appsecert) 最后加上小程序发送给自己的code,利用Httpclient从后端发送给微信接口服务。而微信接口服务会在校验之后返回session_key和openid

微信接口服务返回的session_key和openid具有以下用途:
用户身份识别:通过openid,可以唯一标识用户的身份。开发者可以将openid与用户在自己的系统中的账号进行关联,实现用户的登录、注册等功能。
数据加密解密:session_key是用于对用户敏感数据进行加密和解密的密钥。开发者可以使用session_key对用户的敏感数据进行加密,确保数据在传输过程中的安全性;同时,也可以使用session_key对加密后的数据进行解密,获取原始数据。
用户信息获取:通过openid和session_key,开发者可以向微信接口服务发送请求,获取用户的详细信息,如用户昵称、头像等。这些信息可以用于个性化展示、社交分享等功能

3.在后端获取到微信接口服务发送给自己的session_key和openid,自定义用户登录态,并且发送给小程序

自定义用户登录态指的是在用户登录时,后端根据一定的规则生成一个唯一的标识符(如token),并将其返回给前端。前端在接下来的请求中,需要带上这个标识符,以便后端可以识别当前请求的用户身份。
在微信小程序中,用户登录后,后端会返回一个session_key和openid,这两个值可以用于生成一个唯一的标识符,作为用户的登录态。具体实现方式可以是将session_key和openid拼接起来,再进行加密处理,生成一个token,并将其返回给小程序。小程序在接下来的请求中,需要在请求头或者请求参数中携带该token,以便后端可以验证用户的身份。
自定义用户登录态的好处是可以在后端实现用户身份验证和权限控制,保护系统的安全性。同时,由于token是由后端生成的,可以有效防止恶意攻击者伪造用户身份。

微信支付接口:

在这里插入图片描述
通过这张图我们可以初步了解微信支付的具体流程:
通过这张图,我们可以了解到小程序调用微信支付的基本流程

1.用户进入小程序下单 ,小程序会发送下单请求给商家系统后台,商家后端会生成平台订单,请求微信支付系统的下单接口,创建订单。微信支付系统接收到商家系统后台的请求后,会生成预付单,并且返回预付单标识给商家系统后台,此时商户系统后台利用算法生成带签名支付信息,并且把相关支付参数返回给商家小程序

2.当小程序接收到相关的参数之后,就会调用wx.requestPayment发起微信支付,此时小程序会先向微信支付系统发送请求,检查当前用户身份,微信支付系统在检验当前用户符合权限之后,会给小程序返回支付授权,允许小程序调起支付页面。

3.当小程序调起支付页面之后,用户输入密码确定支付,此时微信小程序会打包相关参数给微信支付系统,校验身份通过之后,就异步通知平台支付交易结果给商家后端,商家后端对其进行保存通知处理,并且在这同时返回支付结果,并且发送微信消息提醒。

4.上述已经完整的介绍了一次微信小程序调用微信支付的具体过程。如果我们后续要查询判断微信支付结果,就在后端调用查寻订单接口,查询支付结果。而微信支付平台就会为商家后端返送支付结果,供我们进行各种判断。

当然这里我们普通人没有店铺,无法正常调用微信支付这个接口,因此我们需要修改一下微信小程序的代码和管理端的代码可以直接绕过微信支付具体操作可以看另一个博主的文章

《苍穹外卖》电商实战项目实操笔记系列(P66~P122)【中】

项目第四阶段

SpringTask

Spring Task 是 Spring 框架提供的一种任务调度工具,用于在应用程序中执行定时任务或者周期性任务。它基于线程池机制,可以创建并管理多个线程来执行任务。

通过 Spring Task,开发人员可以通过注解或者配置的方式定义需要执行的任务,并设置执行的时间间隔或者执行时间点。Spring Task 提供了灵活的任务调度能力,可以满足各种任务执行的需求,例如定时的数据同步、定时的报表生成、定时的缓存清理等。

简单的说:Spring Task为我们提供了一种基于注解的方式来使得我们的后端具有定时处理任务的能力,这项功能可以说是十分常见:我们CSDN的每日周报,就是定时任务。

而这项工具,在我们的项目中的主要作用是:处理异常订单

在我们的数据库中,总会有一些异常订单,例如用户一直未点击送达的订单,而我们需要对这些订单进行集中的处理。

需要注意的是,这个依赖他自己很小,小到并不会独立作为一个依赖包需要导入,而是属于 spring context 的一个附属依赖

接下来我们看一下他在代码中的使用步骤:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>版本号</version>
</dependency>

在这里插入图片描述

通过特定的表达式来制定多长时间间隔执行一次代码,即Cron 表达式,这个表达式有特定的网站可以去生成,因此我们不必去记

引入WebSocket实现用户端和商家端实时通信

在项目中有两个地方需要使用用户下单用户催单

在这种思路中我们发现最关键的就在于:后端如何与商家端建立链接,实现实时通信?

基于这样的一个问题,我们使用了:Websocket 来实现这关键点:

WebSocket 是一种在 Web 应用程序中实现双向通信的协议。它允许客户端和服务器之间建立持久的、双向的通信通道,使得服务器可以主动向客户端推送消息,而无需客户端发送请求。

传统的 HTTP 协议是一种请求-响应模式,客户端需要定期发送请求并等待服务器的响应。但在某些场景下,需要实时地将数据推送给客户端,如聊天应用、实时数据监控等。这时就可以使用 WebSocket 协议。

WebSocket 协议通过在客户端和服务器之间建立一个持久的连接,实现了双向通信。它使用 HTTP 升级请求来升级到 WebSocket 连接,并在连接建立后,使用轻量级的帧来传递数据。与 HTTP 相比,WebSocket 具有更低的开销和更高的性能。

使用 WebSocket,客户端和服务器之间可以实时地发送消息和接收消息,不需要频繁地发起请求。这样可以减少网络流量和延迟,并提供更好的用户体验。在开发中,可以使用各种编程语言和框架来实现 WebSocket,如Java中的Spring WebSocket、Node.js中的Socket.io等。

总之,WebSocket 提供了一种简单、高效的方式,使得 Web 应用程序可以实现实时的双向通信。它在很多场景下都能发挥重要作用,特别是需要实时数据传输和服务器主动推送的应用场景。

我们展示一下代码:
在这里插入图片描述在这里插入图片描述
当用户下单和用户催单的时候,就会调用这个类当中SendToAllClient这个方法,把信息发给所有管理端
在这里插入图片描述

Apache POI技术实现导出文件:

Apache POI(Poor Obfuscation Implementation)是一个用于处理Microsoft Office格式文档的开源Java库。POI提供了一组可以读取、写入和操作各种Office文件的API,包括Word文档(.doc和.docx)、Excel电子表格(.xls和.xlsx)以及PowerPoint演示文稿(.ppt和.pptx)。

通过POI,开发者可以在Java应用程序中读取和编辑Office文档,实现对文档内容、样式、格式和元数据的操作。它提供了向现有文档添加新内容、修改现有内容、删除内容以及进行格式设置和样式调整等功能。

而在本项目中,我们并不使用ApachePOI建表,这样无疑是在拷打自己。我们的想法是直接就提供一张创建好的模板表,这样我们只需要使用ApachePOI来实现填充数据就好了。

package com.sky.test;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import java.io.*;

public class SpringDateRedisTest {

    public static void main(String[] args) throws IOException {
        readInExcel();
    }

    public static void writeInExel() throws IOException {
        XSSFWorkbook excel = new XSSFWorkbook();
        XSSFSheet sheet = excel.createSheet("info");
        XSSFRow row = sheet.createRow(0);
        row.createCell(0).setCellValue("姓名");
        row.createCell(1).setCellValue("年龄");
        row = sheet.createRow(1);
        row.createCell(0).setCellValue("王钰滔");
        row.createCell(1).setCellValue("20");
        FileOutputStream out = new FileOutputStream(new File("Z:\\java_project\\sky-take-out\\info.xlsx"));
        excel.write(out);
        excel.close();
        out.close();
    }

    public static void readInExcel() throws IOException {
        InputStream in = new FileInputStream(new File("Z:\\java_project\\sky-take-out\\info.xlsx"));
        XSSFWorkbook excel = new XSSFWorkbook(in);
        XSSFSheet sheet = excel.getSheetAt(0);
        int lastRowNum = sheet.getLastRowNum();
        for (int i = 0; i <= lastRowNum; i++) {
            XSSFRow row = sheet.getRow(i);
            System.out.println(row.getCell(0).getStringCellValue());
            System.out.println(row.getCell(1).getStringCellValue());
        }
        in.close();
        excel.close();
    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值