知识点
非入侵式框架
该框架对被调用者的源代码进行非入侵约束。
使用该框架的应用程序源代码基本不需要修改,也不依赖于该框架的API。
有哪些
-
AOP(面向切面编程):通过配置的方式对业务逻辑的各个部分进行拦截,并在这些拦截点上添加增强处理。代表框架有Spring AOP等。
-
Plugin插件机制:通过插件可以对系统进行扩展,而不需要修改源代码。典型代表是Eclipse的插件机制。
-
拦截器:通过拦截器可以在某个方法或请求被执行前后进行干预,扩展原有功能。如Spring MVC的拦截器等。
-
事件监听机制:当系统中发生某些事件时,触发注册在该事件上的监听器方法。该机制可以在事件发生后进行一些扩展处理。如Spring的事件监听机制等。
-
高级别API:框架提供高级别的API给最终用户使用,而内部使用基础或低级别的API进行实现。外部使用时也不需要关注内部实现,起到解耦作用。
-
中间件:中间件处于不同系统或层之间,通过扩展中间件可以影响多个系统。典型代表有消息队列、流量控制等中间件。
-
代理模式:通过代理对象对原对象进行功能扩展,而客户端调用代理对象的接口,无需关注原对象细节。
-
模板方法模式:通过钩子方法让子类有扩展点,扩展原有逻辑功能。
-
外观模式:框架内部有多个子系统,外观对象提供简单接口给外部,而内部实现调用不同子系统的复杂逻辑。
非入侵式框架主要利用面向切面、插件、代理、外观等设计模式,或事件、拦截器、高级API抽象等机制,对系统进行扩展与增强,而不破坏原有代码结构与逻辑。使得系统具有良好的可扩展性和维护性。
方式实现
- 代理模式:框架通过
代理对象调
用实际对象的方法,然后附加一些额外的操作,实现框架的功能。这不需要修改实际对象的源代码。 - 动态代理:框架在运行期动态生成代理对象,代理对象实现实际对象的接口,调用实际对象的方法,在前后附加额外操作。这也不需要实际对象的任何改变。
- AOP:框架使用AOP编织器,在编译期,加载期或运行期给实际对象织入额外的操作。实际对象的源代码不需要任何改变。
非入侵式框架的优点
- 不需要修改已有系统的源代码,易于集成和升级。
- 降低耦合度,框架与应用程序解耦。
- 更容易重用,可以应用于更多系统。
所以,非入侵式框架可以最大限度地维护系统的原有结构,便于系统的扩展与升级,这是它的重要优势。许多主流框架都采用非入侵式设计,以更好地满足用户的需求并提高使用率。
相比之下,入侵式框架需要应用程序的源代码与框架产生依赖,直接修改或调用框架API,这会增加程序的耦合度,限制框架的重用性,增加升级的难度。
主要特征是
- 不需要修改应用程序的源代码
- 不会产生代码级别的依赖
- 可以使用代理模式、动态代理或AOP实现
- 易于重用和升级,降低耦合度
- 可以最大限度保留系统原有结构
Spring Security安全框架
Spring Security 是 Spring 家族中第一个应用的框架,它提供全面且可定制的安全服务。
它可以用于保护基于Spring的企业应用程序。
执行流程
Spring Security 的主要功能
- 认证(Authentication):验证用户身份,确认用户是谁。
- 授权(Authorization):验证用户是否有权限执行某个操作。
- 会话管理(Session Management):管理用户会话,用于多次请求。
- 防跨站请求伪造(CSRF):防止恶意站点伪造请求。
- 运行时权限管理:根据用户请求动态决定是否授权。
- 安全异常管理:处理授权失败或会话超时等异常。
- 集成各种认证方式:HTTP Basic、HTTP Digest、Form 表单、LDAP、CAS 等。
- 记住我:记住认证后的用户身份,下次自动登录。
Spring Security 的主要组件
- SecurityContextHolder:用于存储认证信息的上下文对象,可以在任何地方访问。
- Authentication:代表一个认证请求或认证的用户
- UserDetails:描述用户信息的接口,包含用户名、密码、权限等信息。
- UserDetailsService:用来加载 UserDetails 的服务接口。
- GrantedAuthority:授权信息的抽象类,代表一个权限。
- AuthenticationManager:认证管理器,处理 Authentication 请求。
- AccessDecisionManager:访问决定管理器,决定是否授权。
- AuthenticationProvider:认证提供者,具体执行认证的接口。
- FilterSecurityInterceptor:安全拦截器,主要负责拦截用户请求并调用 AccessDecisionManager 来决定是否授权。
- ExceptionTranslationFilter:负责捕获 AccessDeniedException 和 AuthenticationException ,并将其翻译成对应的响应。
Redis
Redis 是一个高性能的键值数据库
是一个非关系型、开源、高性能、多功能和丰富特性的 NoSQL 数据库。
特性
- 非关系型:Redis 不支持表与表之间的关系,它的数据模式是 key-value 对。
- 开源:Redis 是完全开源免费的,遵循 BSD 协议。
- 高性能:Redis 能够提供非常高的读写性能,每秒可以处理超过10万次读写操作。
- 持久化:Redis 支持将内存中的数据持久化到磁盘中,重启的时候可以再将数据加载到内存中。
- 多功能:Redis 不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
- 丰富的特性:Redis还支持数据的备份,持久化,内存管理,事务,秒杀等特性。
优点
-
性能高:读的速度是110000次/s,写的速度是81000次/s 。
-
丰富的数据类型:string,hash,list,set,sorted set,bitmaps,hyperloglogs。
-
持久化:将内存中的数据持久化到磁盘中,重启的时候可以再将数据加载到内存中。
使用场景
-
缓存读取频繁的数据,如商品信息、文章内容等。
-
缓存计算结果,如排行榜、好友推荐等。
-
缓存用户登录信息,购物车信息等。
Redis 哨兵(Sentinel)
Redis 哨兵是Redis的高可用解决方案,具有如下功能:
- 监控 redis 实例状态,主观下线失效实例。
- 提供实例挂机时自动故障转移支持。
- 回复故障转移后,自动将从节点设置为主节点的从节点。
- 提供客户端查询功能,客户端可查询哨兵获得主从节点信息。
Redis 哨兵架构由若干个 Sentinel 实例和若干个 Redis 实例组成。其中一个 Redis 实例为主实例,其他实例为从实例,以主从复制方式工作。Sentinel 实例监听主从实例的运行状态,在主实例下线时,会在从实例中选举新的主实例,完成自动故障转移。
Redis 分片(Cluster)
Redis Cluster是Redis的分布式解决方案,具有如下功能:
- 自动分片存储数据到不同的节点。
- 节点自动发现和故障恢复
- 支持多种节点角色:主节点、从节点、哨兵节点等。
- 支持在线扩容节点。
Redis Cluster通过分配一定数量的槽(slot)来表示整个数据的范围,每个节点负责一定数量的槽。客户端将一个键转换为槽的编号,从而得到键所映射到的节点,实现分片效果。
通过在节点之间以主从关系或者分片映射的关系复制数据,从而实现高可用、可扩展的集群方案。
Redis雪崩,穿透,击穿
-
雪崩:大量的key对应的数据过期,导致redis一次性释放大量内存,造成短暂的server hang。
解决方案:
- 降低过期时间,分散过期时间
- 使用惰性删除,过期键不立即删除,只是标记为过期,定期批量删除
- 在高内存阈值时禁止删除操作
-
穿透:查询一个本不存在的key,导致每次请求都通过redis到达DB数据库,造成DB数据库负载过高过载。
解决方案:
- 针对查询为空的key也设置过期时间,避免 key一直不存在导致重复查询
- 使用布隆过滤器提前判断 key 是否存在
- 缓存 null 值,过期时间短
-
击穿:大量并发请求同一个key且该key缓存失效过期,大量请求直接到达数据库,造成数据库过载。
解决方案:
- 设置热点数据永不过期
- 加互斥锁,同一时刻只允许一个线程访问
- 使用加权随机算法,随机选择多个 candidate key,其中只有一个是真实的
提高数据库交互效率
使用 Redis 作为缓存数据库
可以将热点数据或大量读、少量写的数据存入 Redis,以减轻主数据库的压力。
Reads 从 Redis 读取,Writes 同步更新到 Redis 和主数据库。这种架构可以大大提高系统的性能和吞吐量。
使用 Redis 做分布式锁
分布式系统中,可以使用 Redis 的 SETNX(set if not exists) 命令实现分布式锁。这个可以有效的防止并发问题。
分布式锁执行流程
-
获取锁使用 SETNX 命令尝试获取锁。
如果键 lock 不存在,则 SETNX 成功,返回 1 ,表示获取锁成功。如果键 lock 已存在,则返回 0 ,表示获取锁失败。
SETNX lock.redis 123
- 锁过期时间为了避免进程崩溃导致锁无法释放,可以给锁设置一个过期时间。如 1 秒:
SETNX lock.redis 123 EX 1
- 释放锁使用 DEL 命令删除键 lock 来释放锁:
DEL lock.redis
-
锁失效时自动释放
因为我们为锁设置了过期时间,如果锁在过期时间内未被释放,它将自动过期并被 Redis 删除,相当于自动释放了锁
-
解决进程崩溃导致锁无法释放的问题
我们可以设置一个信号量,在进程获取锁后,将信号量设置为 1。如果进程崩溃,信号量不会被清除,值仍为 1。其他进程在尝试获取锁时,首先检查信号量,如果值为 1,表示上一个持有锁的进程崩溃,此时调用 DEL 命令删除锁,然后再设置信号量为 0,获取锁。
这个解决方案可以确保锁总是被正常释放,避免死锁。
利用 Redis 的 SETNX 和 DEL 命令,我们可以很容易实现一个分布式锁,并解决掉分布式锁常见的死锁问题。这在分布式系统和微服务架构中是一个非常实用的技术
使用 Redis 队列消息
可以利用 Redis 的 LIST 类型实现一个消息队列。生产者使用 LPUSH/RPUSH 命令将消息推入队列,消费者使用 LPOP/RPOP 命令将消息从队列弹出。这种架构可以实现异步任务,提高系统效率。
发布订阅
Redis 发布订阅(pub/sub)功能可以实现服务端消息推送给客户端。
生产者作为发布者将消息推送到频道,消费者作为订阅者订阅相应的频道。
这种架构可以实现实时消息推送,满足一对多的通信需求。
Redis 事务
Redis 事务可以一次执行多个命令,这些命令要么全部成功,要么全部失败。这在并发环境下可以确保数据的一致性。事务可以确保批量操作数据的原子性。
分片集群
搭建Redis集群,实现分布式存储和负载均衡。
这样大量的数据和操作可以分配到不同的节点上,既提高了存储能力,也提高了并发处理能力。
Java Servlet技术
Servlet 是 Java EE 规范中定义的服务器端程序。Servlet 运行于服务器上,并由服务器调用。
Java Servlet执行流程(Gif)
Java Servlet执行流程(图文)
- 客户端发送请求给服务器。
- 服务器开始接受,先判断该请求的servlet实例是否存在,如果不存在先装载一个servlet类并创建实例。如果存在则直接调用该servlet的service方法,之后进行判断是调用doGet方法还是doPost方法。
- servlet创建实例后,调用init方法进行初始化。之后调用servce方法,判断是调用doGet方法还是doPost方法。
- 最后判断服务是否关闭,如果关闭则调用destroy方法。
Spring MVC
Spring MVC是Spring框架的一部分,是基于Java实现MVC的轻量级Web框架。
Spring MVC的主要组件如下:
- 前端控制器DispatcherServlet:接收请求,响应结果,请求转发。
- 处理器映射HandlerMapping:根据请求的URL确定使用哪个Controller处理请求。
- 处理器适配器HandlerAdapter:适配DispatcherServlet与具体的Controller。
- 视图解析器ViewResolver:根据逻辑视图名解析成真正的视图。
- 处理器或页面控制器Controller:处理请求和业务逻辑,返回逻辑视图名。
- 视图View:渲染输出结果。
一个简单的Spring MVC应用的流程如下:
- 客户端发送请求至DispatcherServlet。
- DispatcherServlet根据请求信息调用HandlerMapping得到处理请求的Controller。
- DispatcherServlet再根据HandlerAdapter调用实际的Controller。
- Controller执行逻辑并返回一个逻辑视图名给DispatcherServlet。
- DispatcherServlet根据该视图名由ViewResolver解析成真正的View。
- DispatcherServlet利用View渲染输出结果,响应给客户端。
相比于Struts2,Spring MVC的主要优点在于:
- 基于方法设计,更加简介;
- 高度可定制性,如支持自定义类型的转换器、格式化工具、验证器等;
- 容易与Spring的其他框架进行集成,如IOC容器、AOP等;
- 支持RESTful风格的URL请求;
- 强大的视图技术,例如JSP、Velocity、FreeMarker等;
- 简单灵活的过程。
所以,Spring MVC通过将MVC分层架构的不同职责解耦,使得每层都可以自行扩展,或被替换,十分灵活。它简单易学,同时功能也非常强大,是目前最受欢迎的MVC框架之一。
DispatcherServlet (SpringMVC的前端控制器)
Servlet 主要用于
-
接收客户端请求
-
生成动态内容
-
与数据库交互
-
维护会话状态
Servlet 生命周期
- 初始化:执行 init() 方法,只执行一次,用于加载资源
- 服务:执行 service() 方法,每次请求都会调用
- 销毁:执行 destroy() 方法,只执行一次,用于释放资源
Servlet 工作原理
- 客户端发送请求到服务器
- 服务器(TomCat)接收请求,并将其交给 Servlet 容器
- 容器找到请求 URL 对应的 Servlet,并创建其实例(如果不存在)
- 容器调用 Servlet 的 service() 方法,处理客户端请求
- 容器将 Servlet 的响应发送给客户端
Servlet 接口包含 5 个方法:
- init():初始化,创建 Servlet 时调用
- service():对客户端请求作出响应
- destroy():销毁,Servlet 被销毁前调用
- getServletConfig():获取 Servlet 配置
- getServletInfo():获取 Servlet 信息
Servlet 继承体系
Servlet --> GenericServlet -->HttpServlet
我们通常继承 HttpServlet 来处理 HTTP 请求。
Servlet 配置
在 web.xml 中配置 Servlet,指定:
- Servlet 名称
- Servlet 类
- 请求映射 URL
- 初始化参数等
JWT验证登录
JWT(JSON Web Token)是目前最流行的跨域身份验证解决方案。
它可以用于用户登录验证和信息交换。
JWT 的工作流程是(图文)
- 客户端使用用户名和密码请求登录
- 服务器验证用户名和密码成功后,生成一个 JWT,返回给客户端
- 客户端存储 JWT,并在每次请求时携带该 JWT
- 服务器验证JWT的合法性和有效性,如果成功则返回数据(图中4 5 过程)
JWT 包含三部分
- Header:头部,包含签名算法(如HMAC SHA256)和token类型(如JWT)
- Payload:载荷,包含声明(claim)如iss(发行者),exp(过期时间),sub(面向对象)等
- Signature:签名,是前两部分使用header中的签名算法计算出来的结果所以 JWT 的组成如下:
Header.Payload.Signature
每个部分使用 . 分隔,然后使用 BASE64 进行编码,形成最终的 JWT 如:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
服务器使用 JWT 登录验证的流程:
- 客户端 POST 用户名和密码到登录接口
- 服务器验证信息,生成 JWT,如:
const token = jwt.sign({ username: user.username, admin: true }, SECRET_KEY);
- 服务器返回 JWT 给客户端:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"
}
- 客户端在每次请求时,在 Header 中带上:
Authorization: Bearer <token>
- 服务器验证JWT的合法性和有效性,如果成功则处理请求。
JWT 是一种非常简洁而强大的跨域身份验证方案。利用JWT,我们可以轻松实现用户登录和权限验证等功能。
SpringCloud 常用组件
- Eureka:服务注册与发现,一个基于 REST 的服务,用于定位服务,以实现云端中间层服务发现和故障转移。
- Ribbon:客户端负载均衡器,有多种负载均衡算法可以选择,如轮询、随机等。
- Feign:服务调用工具,ရ性服务调用方式。
- Hystrix:服务容错保护工具,避免服务雪崩。
- Zuul:服务网关,提供智能路由、访问过滤等功能。
- Config:服务配置中心,实现配置文件在服务重启时不需要手动更新,配合消息总线实现热更新。
- Bus:消息总线,用于将配置中心和客户端连起来。
- Zipkin:链路追踪系统,用于追踪服务之间调用关系,用于监控分布式系统架构。
- Spring Cloud Alibaba:阿里云的Spring Cloud生态组件,包括服务注册与发现、配置中心、消息总线等。
- Gateway:新一代的API网关服务,比Zuul更加强大。
- OpenFeign: 声明式服务调用工具,比Feign更加强大。
- Sentinel:服务熔断工具,功能更加强大,可以实现限流、降级等。
微服务框架
Spring Cloud是Spring旗下的微服务开发工具,它提供了一系列框架来快速构建微服务系统。
包括
- Eureka:服务注册和发现组件,用来实现微服务实例的注册与发现。
- Ribbon:客户端负载均衡工具,用来实现微服务实例的负载均衡。
- Zuul:API网关,用来实现微服务的路由转发、访问控制等功能。
- Hystrix:断路器,用来实现微服务的故障隔离和容错。
- Config:配置中心,用于集中管理各个微服务的配置文件。
- Bus:消息总线,用于传播配置文件的变化或其他事件。
- DiscoveryClient:服务发现客户端,自动化配置Eureka Client。
- Feign:声明式的HTTP客户端,用来简化RESTful服务的消费。
- Zuul:API网关。
- Stream:用来构建消息驱动的微服务。
- Sleuth:用来实现微服务的分布式请求链路跟踪。
构建步骤
- 使用Eureka作为服务注册中心。各个微服务启动后自动注册到Eureka中。
- 使用Ribbon实现负载均衡,在服务消费方使用@LoadBalanced注解开启负载均衡。
- 使用Feign声明式调用其他微服务。
- 使用Zuul构建API网关。
- 使用Config构建配置中心。
- 使用Spring Boot开发每个微服务,并加上@EnableDiscoveryClient注解。
- 使用Spring Boot Actuator监控微服务。
- 使用Hystrix实现服务容错。
- 使用Stream或Bus构建消息驱动能力。
- 使用Sleuth实现分布式请求链路跟踪。
Dubbo安装和应用
Dubbo 是一个分布式服务框架,提供高性能和透明化的 RPC 远程服务调用方案。
Dubbo 的安装步骤
- 安装 Java 开发环境,Dubbo 支持 JDK 1.6 及以上版本。
- 下载 Dubbo,Dubbo 的发行版本有两种:- dubbo-admin-2.6.4.war:Dubbo 服务治理中心应用,用于管理和监控 Dubbo 服务。- dubbo-2.6.4.jar:Dubbo 的 Java 服务引擎,用于暴露和引用远程服务
- 部署 Dubbo 服务治理中心应用 dubbo-admin-2.6.4.war:
- 解压 war 包,并修改 WEB-INF/dubbo.properties 配置文件。
- 启动 Tomcat 并将 dubbo-admin-2.6.4 部署在 Tomcat 中。
- 访问 http://localhost:8080/dubbo-admin-2.6.4 登陆服务治理中心。
- 在服务提供者消费者中分别引入 dubbo-2.6.4.jar 依赖,并配置 dubbo.properties 配置文件。
- 在服务提供者中配置服务并暴露服务,如:
@Service
public class DemoServiceImpl implements DemoService {
...
}
<!-- 服务提供方应用配置 -->
<dubbo:application name="demo-provider"/>
<dubbo:registry address="zookeeper://127.0.0.1:2181"/>
<dubbo:protocol name="dubbo" port="20880"/>
<dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService"/>
- 在服务消费者中引用服务,如:
<!-- 消费方应用配置 -->
<dubbo:application name="demo-consumer"/>
<dubbo:registry address="zookeeper://127.0.0.1:2181"/>
<dubbo:reference id="demoService" interface="com.alibaba.dubbo.demo.DemoService"/>
- 启动服务提供者和消费者,并在 dubbo-admin 中查看服务注册和调用情况。
Dubbo的调用(代码)
- 定义服务接口,比如:
public interface HelloService {
String sayHello(String name);
}
- 服务提供者实现接口,比如:
public class HelloServiceImpl implements HelloService {
public String sayHello(String name) {
return "Hello " + name;
}
}
- 在服务提供者引用Dubbo,发布服务:
<!-- 引入Dubbo依赖 -->
<dubbo:application name="hello-service-provider" />
<dubbo:registry address="zookeeper://127.0.0.1:2181" />
<dubbo:protocol name="dubbo" port="20880" />
<dubbo:service interface="com.alibaba.HelloService" ref="helloService" />
<bean id="helloService" class="com.alibaba.HelloServiceImpl" />
- 服务消费者引用远程服务:
<!-- 引入Dubbo依赖 -->
<dubbo:application name="hello-service-consumer" />
<dubbo:registry address="zookeeper://127.0.0.1:2181" />
<dubbo:reference id="helloService" interface="com.alibaba.HelloService" />
- 服务消费者调用远程服务:
HelloService helloService = (HelloService)context.getBean("helloService");
String hello = helloService.sayHello("dubbo");
Dubbo的调用过程(文字)
- 服务提供者启动,向注册中心注册服务。
- 服务消费者启动,从注册中心订阅服务。
- 服务消费者调用服务,Dubbo框架客户端按规则从多个提供者中路由调用一台。
- 提供者返回结果,Dubbo框架返回给消费者。
项目技术
三级裂变分销用户绑定sql
三级裂变分销是一种网络营销模式,是指参与者通过自己的努力发展下线,下线再发展下线,形成了三级或多级的网络结构。这种模式需要维护每个参与者的上下级关系,以及计算不同级别参与者的业绩报酬。
为实现这种模式,可以创建如下表:
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL,
`password` varchar(50) NOT NULL,
`level` int(2) NOT NULL DEFAULT '1' COMMENT '用户级别:1 一级 | 2 二级 | 3 三级',
`parent_id` int(11) DEFAULT NULL COMMENT '上级用户id',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
其中:
- id:用户唯一标识
- username/password:用户名和密码
- level:用户级别,1 为一级用户,2 为二级用户,3 为三级用户
- parent_id:上级用户id,除一级用户外,其他用户都有上级当一个用户注册后,需要将其级别和上级绑定:
-
一级用户:level = 1,parent_id = NULL
-
二级用户:level = 2,parent_id = 一级上级用户id
-
三级用户:level = 3,parent_id = 二级上级用户id
所以,当一个用户注册后,可以执行如下SQL进行级别和上级绑定:
-- 二级用户注册,绑定一级上级
INSERT INTO `user` (`username`, `password`, `level`, `parent_id`)
VALUES ('user2', 'pwd2', 2, 1);
-- 三级用户注册,绑定二级上级
INSERT INTO `user` (`username`, `password`, `level`, `parent_id`)
VALUES ('user3', 'pwd3', 3, 2);
EL表达式
EL表达式是JSP页面中的表达式语言,它可以输出数据、执行运算、调用方法等。EL表达式以${}作为标识。
举些例子:
- 输出对象属性:
${user.name}
- 调用方法:
${user.getName()}
- 运算:
${1+2}
- 获取请求参数:
${param.id}
- Req+uest
- Session
- Application
- Implicit Objects
EL表达式要点:
- 可以嵌套使用,如:
${user.address.city}
- 支持各种运算:
+ - * / ()
- 支持逻辑运算:
&& || !
- 支持空判断:
${empty str}
- 支持三元运算:
${age > 18? "成年":"未成年"}
- 获取web开发常用的隐含对象
<%-- 访问变量 --%>
${var}
<%-- 运算 --%>
${5+10} ${varOne > 10}
<%-- 访问对象属性 --%>
${user.name}
<%-- 访问数组元素 --%>
${users[0].name}
<%-- 访问隐式对象 --%>
${pageContext.request.contextPath}
<%-- 调用内建方法 --%>
${fn:toUpperCase('hello')}
分页查询
- PageHelper:Spring Boot中最常用的分页插件,只需要几个注解和PageInfo对象即可完成分页。
// 开启分页
@PageHelper
// 查询列表
List<User> list = userMapper.selectAll();
// 得到分页相关数据
PageInfo<User> page = new PageInfo<>(list);
// 页面相关数据
model.addAttribute("page", page);
- MyBatis分页插件:需要在MyBatis配置文件中配置插件,稍微麻烦一点,用的较少。3. JPA分页:Spring Data JPA自带分页支持,通过Pageable接口即可完成,较为简单。
Page<User> page = userRepository.findAll(PageRequest.of(pageNum, pageSize));
所以,EL表达式和分页插件是JSP和Spring Boot开发时经常会使用到的技术,理解和掌握它们会对我们有很大帮助。
三级分销用户表设计
我们的项目给用户设计了四张表:用户表,分销商表,分销商等级表,佣金表
- 用户表:存储用户的基本信息,如用户名、密码、姓名、手机号等。
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(64) NOT NULL COMMENT '用户名',
`password` varchar(64) NOT NULL COMMENT '密码',
`name` varchar(64) NOT NULL COMMENT '姓名',
`mobile` varchar(11) NOT NULL COMMENT '手机号',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
- 分销商表:存储用户的分销商信息,如是否是分销商、上级分销商id等。
CREATE TABLE `distributor` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL COMMENT '用户id',
`is_distributor` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否是分销商,0-不是,1-是',
`upper_distributor_id` int(11) DEFAULT NULL COMMENT '上级分销商id',
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`),
KEY `upper_distributor_id` (`upper_distributor_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
- 分销商等级表:存储分销商的等级信息,可以设计多级,如一级分销商、二级分销商、三级分销商。
CREATE TABLE `distributor_level` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(32) NOT NULL COMMENT '等级名称',
`commission_rate` decimal(4,2) NOT NULL COMMENT '佣金比例',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
- 佣金记录表:存储分销商的佣金记录,以便进行佣金结算。
CREATE TABLE `commission_record` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`distributor_id` int(11) NOT NULL COMMENT '分销商id',
`amount` decimal(10,2) NOT NULL COMMENT '佣金金额',
`create_time` datetime NOT NULL COMMENT '记录创建时间',
PRIMARY KEY (`id`),
KEY `distributor_id` (`distributor_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
订单生成流程
- 用户发送添加订单的请求,前端将用户的各种信息(电话地址),选中的单个或多个sku商品封装addDto中
- 目前的addDto中信息不完全,给order赋值后,order还要继续补全
- 订单id手动赋值,避免在读写分离的数据库部署模式中,出现两条数据有相同的自增列Id
- 我用的是leaf来生成id,同时也要生成一个订单号,userId也需要赋值,订单状态赋予初始值,表明未支付状态
- 计算商品价格,order的信息补全后还需要为订单项补全信息,将order的id填入订单项表做关联
- 同时根据订单项中的skuId,商品数量调用减少库存的方法,再根据userId和skuId删除购物车中的对应数据
- 然后新增补全信息的订单,及订单项到相应的表中
- 最后把希望给用户呈现的信息(订单id,订单号,钱数等)赋值到addVo中,返回给前端
- 特别的,对于超时未支付的订单,采用延迟队列,订单生成后发到队列,30分钟后检查支付状态,如果未支付则删除订单退回库存