首页 文章 精选 留言 我的

精选列表

搜索[设计],共10008篇文章
优秀的个人博客,低调大师

基于Spring Boot的“课程设计”的设计与实现

这是一个集电影,音乐和书籍于一体的Java web应用 Java 1.8 框架:使用Spring Boot 集成Spring,Spring MVC,MyBatis(前期),Spring Data(后期) 数据库:MySQL 5.6 缓存:Redis 4.0 版本控制:Maven 3.5 页面解析框架:Thymeleaf 负载均衡:Nginx - 端口80 服务器:Tomcat 端口8080和8181(可以使用单个tomcat) PS:音乐来源-网易云;电影来源-豆瓣、猫眼;书籍来源-豆瓣 ================================================== 项目结构 com.wsk.movie aspect:切面应用 bean:回显的实体类 celebrity:json影人条目信息 maoyan:猫眼 cinema:json单个电影院信息 cinemas:json多个电影院信息 movie:json电影信息 config:spring启动加载配置 controller:链接控制 webSocket:websocket相关配置和实现 dao:Mybatis接口 error:自定义异常处理 music:网易云音乐 bean:网易云音乐json解析类 entity:数据库实体类 service:操作数据库 thread:线程相关 pojo:电影相关的数据库实体 redis:redis操作类 impl:接口的实现 service:电影相关的服务操作 impl:接口的实现 session:session存活时间配置 springdata:网易云音乐spring data操作 entity:网易云音乐的数据库实体类 task:自定义的定时器 entity:数据库实体类 runnable:任务 service:数据库相关操作 tool:工具类 token:token生成器 tool:工具类 bean:百度图片识别json结果 write:文件读写操作 resources mapping:mybatis相关的xml文件 static:静态资源文件 css:样式 image:本地图片 js:JAVASCRIPT templates:页面 forget:忘记密码 hot:热门电影 information:个人相关信息详情 movie:电影相关信息 registered:注册 setting:设置12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849 1. 系统结构 2. 业务流程 客户端 管理员 4. 数据库 (1) 数据库表汇总 数据库表汇总 名称表名注释管理员操作记录表adminaction记录管理员操作管理员信息表admininformation记录管理员信息书籍表book记录书籍、图书户收藏表collectioncritic记录用户收藏的信息说说评论表commentcritic记录说说的评论举报信息表critic_report记录举报信息点赞信息表goodcritic记录说说的点赞情况积分来源表integralsource记录积分的来源通讯信息表message记录用户之间的通讯电影名称表moviename记录电影名好友表myfriends记录用户之间的好友关系任务表mytask记录后台定时任务任务错误信息表mytaskerror记录后台任务错误信息任务日志表mytasklog记录后台任务运行情况说说表publishcritic记录用户发布的说说用户信息表userinformation记录用户的信息用户信誉积分表userintegral记录用户的信誉积分用户等级表userlevel记录用户的等级用户密码表userpassword记录用户的密码用户二维码表userqrcode记录用户的二维码音乐专辑表wangyialbum记录音乐专辑音乐信息表wangyimusic记录音乐信息音乐歌手表wangyisinger记录歌手信息 5. 部分流程图 5.1 用户登录 5.2 发表说说 5.3 欣赏电影,聆听音乐,阅读书籍 5.4 用户信息互动 5.5 管理管理用户,说说和举报审核 6 具体实现细节 6.1 项目技术架构 6.2 登录界面的实现 6.3 首页的实现 图17 首页界面 6.4 热门说说 图18 热门说说 6.5 用户之间的通讯 图19 用户通讯 6.6 用户个人中心设置 图20 个人设置中心 6.7 个人主页 图21 个人界面 6.8 我的说说,评论,收藏,点赞 图22我的说说 图23 我的评论 图24 我的收藏 图25 我的点赞 6.9 说说评论 图26 评论界面 6.10 搜索 图27 搜索 图28 电影搜索结果 图29 电影详情 图30 音乐搜索 图31 图书搜索 6.11 音乐系统 图32 热门音乐 6.12 图书系统 图33 图书推荐 图34 图书详细信息 6.13 查看正在上映的电影 图35 热映电影详情 图36 热映电影评论 7 备注 下载地址:https://download.csdn.net/download/wsk1103/10484796 github地址:https://github.com/wsk1103/movie-boot 首次启动项目 win系统安装Java 1.8 , IDEA软件,MySQL数据库,redis,Nginx。 打开MySQL,执行sql文件,将数据导入到MySQL中。 将项目导入到IDEA中,构建为MAVEN项目。 配置Nginx文件,使其负载均衡。 待项目构建完成后,运行redis和Nginx(或者跳过Nginx)。 修改resource文件中的application.properties,配置其中的数据库信息 修改com.wsk.movie.email.Send文件中的用户账号和密码信息。 由于使用了百度提供的图片识别功能,所以需要修改com.wsk.movie.tool.AuthService中百度提供的clientId和clientSecret(或者直接注释掉该类) 将image.rar文件解压到D:/image,这个文件是存放图片和敏感词的重要文件。 运行com.wsk.movie.MovieApplication的main方法。 访问localhost 欢迎加入Java高级架构学习交流群:375989619 本群提供免费的学习指导 架构资料 以及免费的解答 不懂得问题都可以在本群提出来 之后还会有职业生涯规划以及面试指导 进群修改群备注:开发年限-地区-经验 方便架构师解答问题 免费领取架构师全套视频!!!!!!!!

优秀的个人博客,低调大师

幂等设计详解

导读 本文主要从研发人员的角度,结合研发人员日常常见的各类业务场景,从经典系统框架的每一层入手分析幂等处理的时机。希望通过这篇文章的分析,让开发者在日常开发中对幂等的处理不再陌生。抓住导致请求、接口不幂等的本质,在工作中避免再陷入这个陷阱中。 幂等、幂等性这词,作为一个研发人员是再熟悉不过的,那是否有深入思考过幂等产生的背景、为什么需要幂等,如何做才是幂等的?今天将结合业务场景及请求的过程来分析解决幂等(性)的方法。 1 概念 幂等这个概念,是一个数学上的概念,即:f……(f(f(x))) = f(x)。用在计算机领域,指的是系统里的接口或方法对外的一种承诺,使用相同参数对同一资源重复调用某个接口或方法的结果与调用一次的结果相同。 2 业务场景 从业务场景上来说,如:现在互联网电商的下单服务,同一个用户在短时间内调用某一个下单服务,只能下单成功一次;银行账户之间的转账,A账户给B账户转账,无论系统出现什么问题或故障,也只能转账成功一次;前端页面对相同表单的内容多次向后端发起提交请求,后端只能给出一个相同的结果等都属于幂等的范畴。 试想一下,如果提供的这些服务不是幂等的,客户在下单时由于网络不稳定或是连续点了几次下单按钮,实际客户只下了一单,结果系统里给客户生成了多单,那平台/商家将是无法承受的,如果被“羊毛党”盯上,损失是无可估量的;银行之间的转账,A账户本来实际给B账户只转了一百万,结果B账户收到了几百万,这在业务上是不可接受的。分析这些业务场景,开发者发现,无论是下单服务、转账服务还是表单提交都是一个个业务请求,提供这些业务服务的接口或方法都应该保证无论服务是超时、重试或有故障等异常情况,都要满足业务上的处理结果是正确的。业务上的一次或多次请求,最终的处理结果是一致的,即:在一定时间内,服务的幂等其实就是请求的幂等。 3 架构分析 从系统架构上进行分析,幂等该在哪一层去做,怎么做? 图1 经典系统框架图 上图为一个最常见的经典系统框架图,Web端发起一个请求到后端,幂等该在哪一层来处理呢?不妨一层一层地分析。 Nginx是否需要做幂等,Nginx的主要功能是做Web服务器、反向代理、负载均衡等,把请求转发到后端的服务器上,本身不参与具体的业务,所以Nginx是不需要做幂等处理的;Gateway是负责权限校验、安全防御、认证鉴权、流量控制、协议转换、日志审计、监控等,本身也不含对任何业务的处理,所以其也不需要做幂等处理;Service层通常是对业务逻辑进行处理、编排,可能会改变数据,但对于数据的改变结果,最终也还是需要通过数据访问层,写入到数据库,所以Service层也不需要做数据幂等;DAO层主要是和数据库交互,把Service层的结果写入数据库,对Service层提供读取、写入数据库的功能。 在写入数据库的时候,针对每一次的写入,可能返回不同的结果,此时就需要按场景进行具体的分析对待;DataBase层,主要提供数据的存储,并不参与具体的业务逻辑计算。所以,通过对该架构的每一层的功能分析,得出对于请求的幂等处理,需要在DAO层做处理,以便保证多次请求和一次请求的结果是一致的。 4 数据库操作分析 通过上面的分析,得出幂等需要在DAO层来处理,再进一步分析,得出DAO层的操作主要就是CRUD。下面逐一对每一种操作分析是否需要做幂等,以及怎么做。 R(read):对应的操作SQL语句为select。只要查询条件不变,在一定的时间内,执行一次和执行多次返回的结果肯定是相同的,所以其本身是幂等的,不需要再做处理。 select * from user where id = 1; 查询一次或多次结果是一致的,所以是幂等的。 C(create):对应的操作SQL语句为insert。此时,需要分情况,如果用到的数据库主键为数据库自增,不考虑业务主键防重的情况下,每一次写入数据库就不是幂等的,所以为了保证幂等,需要在数据insert前做业务防重或是在数据库表上对业务主键加唯一索引。如果数据库主键不是自增,是由业务系统写入的,需要在业务系统里把数据库主键和业务主键做一对一映射,或是由独立服务提供数据库主键和业务主键的映射关系,保证多次请求获取到的数据库主键和业务主键是一致的,确保写入数据库操作是幂等的。综合来说,就是相同的数据多次写入数据库后,能否保证只有一条数据。 insert into user (id,age,sex,ts) values(1,10,‘male’,2021-07-20 10:22:23); U(update):对应的操作SQL语句为update。更新操作时,一定是要用绝对值进行更新操作,而不要用相对值进行更新,相对值更新可能导致更新操作不幂等。 幂等: update user set age = 10 where id = 1; 非幂等: update user set age++ where id = 1; D(delete):对应的操作SQL语句为delete。删除操作时,如果删除的是一个范围,生产上最好是禁止该类操作;比较推荐的做法是把按范围操作删除转换为先按范围查询,再按查询的主键进行删除。而且按范围删除的操作不是幂等的。 幂等: delete from user where id = 1; 非幂等:该类操作要禁止。 delete from user where id in (select id from user order by id desc limit 10); 5 常见业务场景 保证幂等的实现方式有多种,此处例举几类常见的业务场景,在实际应用中,根据业务场景进行选用。 图2 页面token机制处理流程 前端页面提交时,页面token机制。进入页面时,从服务器获取token,在服务器端把token进行存储,提交时把token带到服务器端进行验证;常见的处理流程如下: 乐观锁机制,使用数据库的版本号实现乐观锁,数据库更新时,判断版本号是否与查询时保持一致,一致更新成功,否则更新失败; select+insert,数据写入前,先查询数据是否存在,存在直接返回,不存在则写入数据,保证写入数据库的数据正确性;常用于并发不高的一些后台系统或是防止任务的重复执行; 悲观锁机制,一般id为主键或唯一索引,仅锁定当前记录; select*fromtablewhereid='1234'forupdate; 去重表,每一次写入或更新业务表时,先查询去重表是否已经存在记录,再操作业务表。 数据库唯一索引,为业务表建立唯一索引,避免业务数据多次写入; 状态机,业务状态在变更之前是有条件的,必须按设定的状态条件进行更新; 在实际开发中,保证提供的接口或服务的幂等(性),是一个最基本的技术要求,希望通过该分析,能对还未理解幂等(性)的研发人员有所帮助。

优秀的个人博客,低调大师

MASA Framework - EventBus设计

概述 利用发布订阅模式来解耦不同架构层级,亦可用于解决隔离业务之间的交互 优点: 松耦合 横切关注点 可测试性 事件驱动 <!-- more --> 发布订阅模式 发布者通过调度中心将消息发送给订阅者。调度中心解决发布与订阅者之间的关系,保证消息可以送达订阅者手中。 发布者与订阅者互不相识,发布者只管向调度中心发布消息,而订阅者只关心自己订阅的消息类型 多订阅者保序执行 在常见的发布订阅模式中,的确很少见到类似的说法。但在实际业务中我们会有类似的需求,一个消息由调度中心协调多个订阅者按照顺序执行消息,同时还可以将上一个订阅者处理过的消息传递给下一个订阅者。这样既可以保留发布订阅模式的特性,又有了顺序执行逻辑的特性。 一个小思考:如果 EventBus 的配置支持动态调整的话,是否业务的执行顺序也可以被动态排列组合? 换句话说它或许可以为进程内工作流提供了一个可能性 Event Sourcing(事件溯源) 一种事件驱动的架构模式,可以用于审计和溯源 基于事件驱动架构 以事件为事实 业务数据由事件计算产生的视图,可以持久化也可以不持久化 CQRS(命令查询的责任分离) CQRS 是一种架构模式,能够使改变模型与查询模型的实现分离 Event Sourcing & CQRS 事件溯源可以与 CQRS 很好的配合 在 Command Handler 中持久化事件到 Event Store 的同时实时计算一个最终视图给 View DB 用于查询展示 在 Query 中既可以通过 View DB 获取最新状态,也可以通过 Event Store 来重放事件来校验 View 或用于更严谨的业务 Saga Saga 是一个长活事务被分解成可以交错运行的子事务集合。其中每个子事务都是一个保持数据库一致性的真实事务 每个 Saga 由一系列 sub-transaction Ti 组成 每个 Ti 都有对应的补偿动作 Ci,补偿动作用于撤销 Ti 造成的结果 两种执行顺序 T1, T2, T3...[Tx retry]...,Tn T1, T2, ..., Tj, Cj,..., C2, C1 两种恢复策略 backward recovery,向后恢复,补偿所有已完成的事务,如果任一子事务失败。即上面提到的第二种执行顺序,其中 j 是发生错误的 sub-transaction,这种做法的效果是撤销掉之前所有成功的 sub-transation,使得整个 Saga 的执行结果撤销 forward recovery,向前恢复,重试失败的事务,假设每个子事务最终都会成功。适用于必须要成功的场景,执行顺序是类似于这样的:T1, T2, ..., Tj(失败), Tj(重试),..., Tn,其中 j 是发生错误的 sub-transaction。该情况下不需要 Ci BuildingBlocks 的类视图 作为接口标准,BuildingBlocks 中并没有过多的干涉实现方式,它只保留了最基础的功能流程限制,以达到最小 EventBus 的功能集合。至于最终是基于接口还是特性来实现订阅关系的,交还给 Contrib 自行决定。 事件 用于本地事件的发布/订阅 IEvent:事件接口,IEvent<TResult>为带返回值的基础事件接口 IEventHanldler<TEvent>:事件处理器接口,ISagaEventHandler<TEvent>为 Saga 的实现提供了基础接口要求 IMiddleware<TEvent>:中间件接口,允许在事件执行前挂载预处理动作和时间执行后的收尾动作 IEventBus:事件总线接口,用于发送事件,并提供订阅关系维护和附加功能执行 集成事件 用于跨进程事件的发布/订阅 IntegrationEventLog:集成事件日志,用于实现本地消息表的消息模型 IIntegrationEventLogService:集成事件日志服务接口 ITopic:发布/订阅的主题 IIntegrationEvent:集成事件接口 IIntegrationEventBus:集成事件总线,用于跨进程调用的事件总线 CQRS 用于使改变模型与查询模型的实现分离 IQuery<TResult>:查询的接口 IQueryHandler<TCommand,TResult>:查询处理器接口 ICommand:可用于增删改等指令的接口 ICommandHandler<TCommand>:指令处理器接口 Event Bus 要完成上述的这些功能,我们需要借助于 EventBus,它需要有以下基础功能 接收事件 维护订阅关系 转发事件 接收与转发事件 这两个功能其实可以合并为一个接口,由发布者调用 Publish,再由 Event Bus 根据订阅关系转发即可 维护订阅关系 在.Net 项目中,我们常见的用于扫描自动注册的方式是接口和特性 MediatR 支持接口的方式去扫描事件订阅关系,举个例子:IRequestHandler<,> public class PingHandler : IRequestHandler<Ping, string> { public Task<string> Handle(Ping request, CancellationToken cancellationToken) { return Task.FromResult("Pong"); } } 如果你的代码洁癖程度没有高的离谱,或许你希望是这样 public class NetHandler : IRequestHandler<Ping, string>, IRequestHandler<Telnet, string> { public Task<string> Handle(Ping request, CancellationToken cancellationToken) { return Task.FromResult("Pong"); } public Task<string> Handle(Telnet request, CancellationToken cancellationToken) { return Task.FromResult("Success"); } } 看着好像还行?如果很多呢? 那有没有办法解决这个问题? 特性!我们来看个例子 public class NetHandler { [EventHandler] public Task PingAsync(PingEvent @event) { //TODO } [EventHandler] public Task TelnetAsync(TelnetEvent @event) { //TODO } } 似乎我们找到了一个出路 多订阅者保序执行 通过事件层层推进确实可以满足顺序执行的场景,但如果你被大量无限套娃的事件包围的时候或许你需要另外一个出路,看下例子: public class NetHandler { [EventHandler(0)] public Task PingAsync(PingEvent @event) { //TODO } [EventHandler(1)] public Task LogAsync(PingEvent @event) { //TODO } } 只要参数是同一个 Event 就会按照 EventHandler 的 Order 顺序执行。 Saga 那执行失败了怎么办,如果两个方法因为其中一个需要调用远程服务而无法跟本地事务结合,能帮我回滚吗? 来吧,SAGA 走起,帮你再做个取消动作,同时还支持重试机制,以及是否忽略当前步骤的取消动作。 我们先来预设一下场景: 调用 CheckBalanceAsync 来检查余额 调用 WithdrawAsync, 抛出 exception 重试 WithdrawAsync 3 次 调用 CancelWithdrawAsync 代码如下: public class TransferHandler { [EventHandler(1)] public Task CheckBalanceAsync(TransferEvent @event) { //TODO } [EventHandler(2, FailureLevels.ThrowAndCancel, enableRetry: true, retryTimes: 3)] public Task WithdrawAsync(TransferEvent @event) { //TODO throw new Exception(); } [EventHandler(2, FailureLevels.Ignore, enableRetry: false, isCancel: true)] public Task CancelWithdrawAsync(TransferEvent @event) { //TODO } } AOP 举个业务场景,给所有 Command 在执行前增加一个参数验证 我们提供了 Middleware,允许像俄罗斯套娃一样(.Net Middleware)做横切关注点的相关的事情 public class LoggingMiddleware<TEvent> : IMiddleware<TEvent> where TEvent : notnull, IEvent { private readonly ILogger<LoggingMiddleware<TEvent>> _logger; public LoggingMiddleware(ILogger<LoggingMiddleware<TEvent>> logger) => _logger = logger; public async Task HandleAsync(TEvent @event, EventHandlerDelegate next) { _logger.LogInformation("----- Handling command {EventName} ({@Event})", typeof(TEvent).FullName, @event); await next(); } } 注册 DI builder.Services.AddTransient(typeof(IMiddleware<>), typeof(LoggingMiddleware<>)) MASA EventBus 完整功能列表 接收事件 维护订阅关系 - 接口 维护订阅关系 - 特性 多订阅者顺序执行 转发事件 Saga AOP UoW 自动开启和关闭事务 Integration Event Bus 用于跨服务的 Event Bus,支持最终一致性,本地消息表 Pub/Sub 提供了 Pub Sub 接口,并基于 Dapr Pub/Sub 提供默认实现 本地消息表 提供了本地消息保存和 UoW 联动接口,并基于 EF Core 提供默认实现 使用方法 启用 Dapr Event Bus builder.Services .AddDaprEventBus<IntegrationEventLogService>(options=> { options.UseUoW<CatalogDbContext>(dbOptions => dbOptions.UseSqlServer("server=localhost;uid=sa;pwd=Password;database=test")) .UseEventLog<CatalogDbContext>(); ) }); 定义 Integration Event public class DemoIntegrationEvent : IntegrationEvent { public override string Topic { get; set; } = nameof(DemoIntegrationEvent);//dapr topic name //todo other properties } 定义 DbContext(非必须,定义 DbContext 可以将本地消息表与业务事务联动) public class CustomDbContext : IntegrationEventLogContext { public DbSet<User> Users { get; set; } = null!; public CustomDbContext(MasaDbContextOptions<CustomDbContext> options) : base(options) { } } 发送 Event IIntegrationEventBus eventBus; // from DI await eventBus.PublishAsync(new DemoIntegrationEvent()); 订阅 Event(基于 Dapr Pub/Sub 的版本) [Topic("pubsub", nameof(DomeIntegrationEvent))] public async Task DomeIntegrationEventHandleAsync(DomeIntegrationEvent @event) { //todo } Domain Event Bus 在领域中同时提供 Event Bus 和 Integration Event Bus 的能力,允许实时发送事件或在 Save 时一次性触发 Domain Event Bus 是最完整的能力,所以使用 Domain Event Bus 相当于已经开启了 Event Bus 和 Integration Event Bus,在 Domain Event Bus 内部会自动协调事件分类往 Event Bus 和 Integration Event Bus 分流 启用 Domain Event Bus builder.Services .AddDomainEventBus(options => { options.UseEventBus()//Use in-process events .UseUoW<CustomDbContext>(dbOptions => dbOptions.UseSqlServer("server=localhost;uid=sa;pwd=P@ssw0rd;database=idientity")) .UseDaprEventBus<IntegrationEventLogService>()///Use cross-process events .UseEventLog<LocalMessageDbContext>() .UseRepository<CustomDbContext>(); }) 添加 DomainCommand Domain Event 是进程内事件,IntegrationDomainEvent 是跨进程事件 public class RegisterUserSucceededIntegrationEvent : IntegrationDomainEvent { public override string Topic { get; set; } = nameof(RegisterUserSucceededIntegrationEvent); public string Account { get; set; } = default!; } public class RegisterUserSucceededEvent : DomainEvent { public string Account { get; set; } = default!; } 进程内事件订阅 [EventHandler] public Task RegisterUserHandlerAsync(RegisterUserDomainCommand command) { //TODO } 跨进程事件订阅 [Topic("pubsub", nameof(RegisterUserSucceededIntegrationEvent))] public async Task RegisterUserSucceededHandlerAsync(RegisterUserSucceededIntegrationEvent @event) { //todo } 发送 DomainCommand IDomainEventBus eventBus;//from DI await eventBus.PublishAsync(new RegisterUserDomainCommand()); 使用场景 兼顾遗留系统对接 游走在云与非云中 流计算 微服务解耦和跨集群通信(需要将 Dapr Pub/Sub 改为 Dapr Binding,不难) 部分 AOP 类场景 总结 事件驱动可以解决一些特定场景的问题,凡事都有两面性,在本来就很简单的业务场景中使用如此复杂的模式会带来不小的负担。 学以致用,学无止境。 开源地址 MASA.BuildingBlocks:https://github.com/masastack/MASA.BuildingBlocks MASA.Contrib:https://github.com/masastack/MASA.Contrib MASA.Utils:https://github.com/masastack/MASA.Utils MASA.EShop:https://github.com/masalabs/MASA.EShop 如果你对我们的 MASA Framework 感兴趣,无论是代码贡献、使用、提 Issue,欢迎联系我们

优秀的个人博客,低调大师

设计模式——管道模式

管道(执行流)模型由 Pipeline(管道)/ Valve(阀门)/ Context(上下文) 组成 概念 我们把特定的业务,比如订单业务中的临时订单、订单提交以及订单支付等,抽象成一组Pipeline(管道); 拿生成临时订单业务来说,执行流程包括:1参数校验->2业务数据校验->3业务处理,这里的三段子流程是严格按照顺序执行的,我们用Valve(阀门)定义它们,每一个子流程即一个Valve; 在管道模式中,我们要处理的对象是一组业务数据,即概念中的Context(上下文),Context贯穿于整个执行流程。 意义 管道模式是多步流程业务很好的抽象,内部基于单链表实现顺序执行,具有强顺序性; 管道模式对于整体流程的拆分,使得业务的扩展性大大增强,当业务需求发生变化,只需要确定需要加入/删除的子流程位置即可,就像从单链表中增加/删除一个节点。 实现 模块结构 PipeLineContext 实现管道上下文的概念,内部记录一个所处阀门在管道中的索引;使用HashMap存储业务数据,用户流程进行时的数据传递。 public class PipeLineContext { private PipeLineContext() { } @Getter private int index; @Getter private Map<String, Object> context; public PipeLineContext(int size) { this.index = 0; this.context = new HashMap<>(size); } public void put(String key, Object value) { context.put(key, value); } public void get(String key) { context.get(key); } @JSONField(serialize = false) public int getAndIncrement() { this.index++; return index; } @Override public String toString() { return "{\"index\":\"" + index + "\", \"context\":\"" + JSON.toJSONString(context) + "\"}"; } } PipeLine 管道接口,包括添加阀门方法以及开启管道方法 public interface PipeLine { /** * 添加阀门 * @param valve 阀门 */ void addValve(Valve valve); /** * 开启管道 * @param pipeLineContext 管道上下文 * @return FlowResult */ FlowResult start(PipeLineContext pipeLineContext); } Valve 阀门接口,阀门都需实现该接口或者该接口的扩展接口 public interface Valve { /** * 获取下一个阀门 * @return Valve 阀门 */ Valve getNext(); /** * 设置下一个阀门 * @param valve 阀门 */ void setNext(Valve valve); /** * 执行管道 * @param pipeLineContext 管道上下文 * @return FlowResult */ FlowResult invoke(PipeLineContext pipeLineContext); } NormalPipeLine PipeLine接口通用实现 @Component public class NormalPipeLine implements PipeLine { private Valve head = null; private Valve next = null; @Override public void addValve(Valve valve) { if (head == null) { head = valve; valve.setNext(next); } else { Valve current = head; while (current != null) { if (current.getNext() == next) { current.setNext(valve); valve.setNext(next); break; } current = current.getNext(); } } } @Override public FlowResult start(PipeLineContext pipeLineContext) { if (pipeLineContext == null) { return FlowResult.fail("pipeLineContext should be not null!"); } if (head == null) { return FlowResult.fail("there's no valve in current pipeLine!"); } return head.invoke(pipeLineContext); } } NormalValve Valve接口通用实现 public class NormalValve implements Valve { protected Valve next = null; @Override public Valve getNext() { return next; } @Override public void setNext(Valve valve) { this.next = valve; } @Override public FlowResult invoke(PipeLineContext pipeLineContext) { return processContinue(pipeLineContext); } protected FlowResult processContinue(PipeLineContext pipeLineContext) { return next == null ? FlowResult.ok() : getNext().invoke(pipeLineContext); } } Validator 订单-临时订单前置参数校验 @Slf4j @Component public class Validator extends NormalValve { @Override public FlowResult invoke(PipeLineContext pipeLineContext) { pipeLineContext.put("param", "1"); return processContinue(pipeLineContext); } } 使用AOP织入阀门,跟踪执行流 @Slf4j @Aspect @Component public class PipeLineAspect { /** * 定义阀门invoke切点 */ @Pointcut(value = "this(com.nooice.order.common.pipeline.Valve) " + "&& execution(* invoke(com.nooice.order.common.pipeline.model.PipeLineContext)) && args((pipeLineContext))", argNames = "pipeLineContext") public void valveInvokeCutOffPoint(PipeLineContext pipeLineContext) { } @Before(value = "valveInvokeCutOffPoint(pipeLineContext)", argNames = "point,pipeLineContext") public void doBefore(JoinPoint point, PipeLineContext pipeLineContext) { int currentIndex = pipeLineContext.getAndIncrement(); String className = point.getTarget().getClass().getName(); log.info("管道前置通知-{}号阀门({})进入执行, pipeLineContext={}", currentIndex, className, pipeLineContext.toString()); } } 测试 @Slf4j @RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = Application.class) public class PipelineTest { @Autowired private NormalPipeLine normalPipeLine; @Autowired private Validator validator; @Autowired private OrderPreviewValidator orderPreviewValidator; @Autowired private Processor processor; @Test public void testUserController() { // 定义上下文 PipeLineContext pipeLineContext = new PipeLineContext(0); pipeLineContext.put("index", "0"); // 增加阀门 normalPipeLine.addValve(validator); // 参数校验阀门 normalPipeLine.addValve(orderPreviewValidator); // 业务校验阀门 normalPipeLine.addValve(processor); // 业务处理阀门 // 管道执行 FlowResult flowResult = normalPipeLine.start(pipeLineContext); log.info(JSON.toJSONString(flowResult)); } } 管道前置通知-1号阀门(com.nooice.order.common.pipeline.validator.Validator)进入执行, pipeLineContext=管道前置通知-2号阀门(com.nooice.order.common.pipeline.validator.OrderPreviewValidator)进入执行, pipeLineContext={"index":"2", "context":"{"index":"0","param":"1"}"} 管道前置通知-3号阀门(com.nooice.order.common.pipeline.processor.Processor)进入执行, pipeLineContext={"index":"3", "context":"{"index":"0","param":"2"}"} PipelineTest: {"code":1,"message":"成功"}

优秀的个人博客,低调大师

责任链设计模式

责任链的应用场景 Servlet API 中的 Filter 过滤器 MVC 框架中的拦截器 . . . 简单使用责任链模式拆分 Servlet API 中的过滤器 模拟 Servlet 中的 Request 对象 /** * @desc <b>模拟 Servlet 中的 Request 对象</b> * * @author jiang ru yi */ public class HttpServletRequest { private String requestContext; private Map<String, Object> requestParam = new HashMap<>(); public String getRequestContext() { return requestContext; } public void setRequestContext(String requestContext) { this.requestContext = requestContext; } public void setRequestParam(Map<String, Object> requestParam) { this.requestParam = requestParam; } public Object setAttribute(String key, Object value) { return requestParam.put(key, value); } public Object getAttribute(String key) { return requestParam.get(key); } public Object removeAttribute(String key) { return requestParam.remove(key); } } 模拟 Servlet 中的 Response 对象 /** * @desc <b>模拟 Servlet 中的 Response 对象</b> * * @author jiang ru yi */ public class HttpServletResponse { private String responseContext; public String getResponseContext() { return responseContext; } public void setResponseContext(String responseContext) { this.responseContext = responseContext; } } 过滤器抽象层 /** * @desc <b>公用的过滤器抽象层</b> * * @author jiang ru yi */ public abstract class HttpFilter { public abstract void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain); } 过滤器调度 /** * @desc <b>过滤器的调度器</b> * * @author jiang ru yi */ public class FilterChain { private List<HttpFilter> filters = new ArrayList<>(); private int currFilter; public boolean addFilter(HttpFilter filter) { return filters.add(filter); } public boolean removeFilter(HttpFilter filter) { return filters.remove(filter); } public void doFilter(HttpServletRequest request, HttpServletResponse response) { if (currFilter++ == filters.size()) return; filters.get(currFilter - 1).doFilter(request, response, this); } } Junit 测试 public static void main(String[] args) { HttpServletRequest request = new HttpServletRequest(); request.setRequestContext("<EvE>, Y(OvO)Y"); request.setAttribute("user", "administrator"); HttpServletResponse response = new HttpServletResponse(); FilterChain chain = new FilterChain(); chain.addFilter(new CharacterSetFilter()); chain.addFilter(new PowerFilter()); chain.doFilter(request, response); System.out.println(request.getRequestContext()); } 抽象层子类 : 字符过滤器 /** * @desc <b>过滤请求中的危险符号( < > )</b> * * @author jiang ru yi */ public class CharacterSetFilter extends HttpFilter { @Override public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) { String context = request.getRequestContext(); String result = context.replaceAll("<", "&le;").replaceAll(">", "&lt;"); request.setRequestContext(result); chain.doFilter(request, response); } } 抽象层子类 : 校验用户是否登录 /** * @desc <b>过滤用户是否登录</b> * * @author jiang ru yi */ public class PowerFilter extends HttpFilter { @Override public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) { Object attribute = request.getAttribute("user"); if (null != attribute) { chain.doFilter(request, response); } else { throw new RuntimeException("user not login"); } } }

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册