如何设计一个ToC的弹窗

本文主要分享了如何设计一个具有高可扩展性的弹窗功能。

本设计参考了优惠券功能的设计思路,有兴趣的朋友可以看看优惠券的分享: juejin.cn/post/722895...

一、需求介绍

假如你的项目需要实现以下弹窗,你会怎么去实现?

需求:

1.展示1张图片,点击图片后跳转到另外1个页面(支持H5、小程序、其他小程序),有个关闭按钮,点击关闭按钮停留在当前页面

2.该弹窗功能提供给运营通过管理后台进行配置

3.弹窗频率需要有所控制,比如1天1次,有优先级,比如运营配置了3个弹窗,不能同时弹,需要按优先级每次进入页面的时候才弹(参考某团的体验)

4.支持灵活配置弹窗条件,比如地域属性、平台属性、时间属性、指定某些用户、用户来源等

5.在配置弹窗过程中有些变量属性,需要运行时才能确定,比如跳转链接中增加当前登录用户ID或者token作为参数

二、数据库设计(存储设计好坏往往决定一个功能日后的可维护、可扩展)

1.错误的表设计(按需求一一列举各种条件作为表结构的字段)

id 有效期 图片url H5链接 小程序链接 频率控制 优先级 条件1 条件2 ... 条件n
1 2023-09-24 image.com/aa/bb.jpg /page/mini 1次/天 99 ios 深圳市

相信很多朋友都是这样设计弹窗表的,这种表设计非常大的问题就是每次需要增加1个新的弹窗条件的话就要新增1个字段,非常不利于功能扩展。

2.正确的表设计(抽象区分'配置数据'和'弹窗条件')

配置数据:使用1个字段,以json结构进行存储,可以做到灵活增加数据或改变数据,配置数据不涉及数据过滤,所以不涉及性能问题

id 有效期 优先级 背景图json
1 2023-09-24 99 {"imgUrl":"image.com/aa/bb.jpg",..."}

弹窗条件:由列式转化为行式存储,增加条件不需要增加字段,而是加1行数据

id popup_id 条件 条件值
1 1 条件1 1次/天
2 1 条件2 ios
3 1 条件3 深圳市

核心设计思想:

1.把数据库表的列式条件转化为行式条件,比如 where a=? and b=?,转化为2行数据作为条件(当然转化后不再是sql能表达的)

2.运用设计模式(类似责任链模式)把1中的行式条件转化为一段代码,可简单可复杂,通过关联关系来做到可插拔

3.完整的表结构设计(remark、create_time、update_time是固定字段)

js 复制代码
CREATE TABLE `mk_popup_pop_condition` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `bean_name` varchar(32) NOT NULL DEFAULT '' COMMENT 'bean名称(PopCondition的实现类)',
  `descrpition` varchar(255) DEFAULT NULL COMMENT '描述',
  `remark` varchar(255) DEFAULT NULL COMMENT '备注',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE KEY `uniq_beanname` (`bean_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='弹窗条件';

CREATE TABLE `mk_popup` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `begin_time` datetime NOT NULL COMMENT '有效期开始时间',
  `end_time` datetime NOT NULL COMMENT '有效期结束时间',
  `status` varchar(8) NOT NULL DEFAULT '' COMMENT '状态(off:下架,on:上架)',
  `priority` int(11) NOT NULL DEFAULT '0' COMMENT '优先级(值越大,优先级越高)',
  `title` varchar(32) NOT NULL DEFAULT '' COMMENT '标题',
  `text` varchar(128) NOT NULL DEFAULT '' COMMENT '文案',
  `bg_img` varchar(255) NOT NULL DEFAULT '' COMMENT '背景图json数据',
  `close_btn` varchar(255) NOT NULL DEFAULT '' COMMENT '关闭按钮json数据',
  `remark` varchar(255) DEFAULT NULL COMMENT '备注',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `idx_begintime` (`begin_time`),
  KEY `idx_endtime` (`end_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='弹窗';

CREATE TABLE `mk_popup_condition` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `popup_id` int(11) NOT NULL COMMENT 'mk_popup.id',
  `pop_condition` varchar(32) NOT NULL DEFAULT '' COMMENT '弹窗条件(bean名称,mk_popup_pop_condition.bean_name)',
  `pop_condition_value` varchar(255) DEFAULT '' COMMENT '弹窗条件值',
  `remark` varchar(255) DEFAULT NULL COMMENT '备注',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
  KEY `idx_popupid` (`popup_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='弹窗-弹窗条件';

CREATE TABLE `mk_popup_log` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `business_type` varchar(64) NOT NULL DEFAULT '' COMMENT '业务类型(popup:mk_popup,user_popup:mk_user_popup)',
  `business_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '业务ID',
  `user_id` int(11) NOT NULL DEFAULT '0' COMMENT 'bu_user_info.id',
  `deviceid` varchar(64) NOT NULL DEFAULT '' COMMENT '设备号',
  `remark` varchar(255) DEFAULT NULL COMMENT '备注',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `idx_userid` (`user_id`),
  KEY `idx_deviceid` (`deviceid`),
  KEY `idx_businessid_businesstype` (`business_id`,`business_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户弹窗记录';

表说明:

mk_popup_pop_condition:主要是记录有哪些弹窗条件,管理后台中配置弹窗时有用

mk_popup:弹窗配置,运营配置数据就往这里写数据

mk_popup_condition:某个弹窗的弹窗条件(1对多),条件组合配置就是利用该表

mk_popup_log:用户弹窗记录,用于控制弹窗频率

数据样例说明:

弹窗配置

弹窗条件

1) 弹窗1只有1个条件FrequencyCondition,可以理解为只控制弹窗频率为2次/天

2) 弹窗2有3个条件,需要符合场景为小程序首页,地域属性为深圳才弹,并且控制弹窗频率为1次/天

三、代码设计

1.抽象弹窗条件,定义一个接口,其子类对应mk_popup_pop_condition的数据

PopParam说明:

2.核心代码(PopService)中提供1个方法'用户最优的弹窗'(此处代码过多,只贴核心部分,完整的可以通过gitee查看源码)

设计思想:

1.查询有效期内上架的所有弹窗(按优先级倒排,找到第1个就是最优的了),遍历每个弹窗的多个弹窗条件,判断有1个条件不满足就放弃该弹窗,进入下一个判断

2.运行时替换一些配置的参数,简单的如{userId},复杂的参数就实现ReplaceParam接口,下文有响应结果demo

tips:此处利用CompletableFuture优化性能,实现了多线程并发判断每个弹窗条件,只要有1个条件不满足就立马返回。

3.如何新增1种弹窗条件?

实现接口PopCondition,重写canPop方法,以下为'场景'弹窗条件的代码

把Bean名称配置到弹窗条件表mk_popup_condition即可使用该条件,无需新增其他代码,也不改动主流程代码

4.Controller固定入口

5.API定义

前端将弹窗封装成1个公共组件,只需请求固定接口/popup/best即可,在需要弹窗的页面直接引用该组件

入参:

字段 说明
sence 前端每个页面定义1个唯一值
经纬度 非固定参数,可不传
地域信息 非固定参数,可不传
渠道信息 非固定参数,可不传
... 非固定参数,可不传

响应:

字段 说明
model 前端渲染模板定义,需要与前端协商,不同的值代表不同的渲染模板
title 标题,本来想用于背景图的标题,但其实可以把标题设计到背景图中
text 文案,同标题
bgImg 背景图JSON,存储背景图位置的数据和动作
bgImg.imgUrl 背景图
bgImg.type 点击背景图做什么?(close:关闭,api:请求API,subscribe:订阅消息,redirect_http:跳转http链接,redirect_mini:跳转小程序链接,redirect_other_mini:跳转其他小程序链接)
bgImg.value 点击背景图做什么携带的信息
closeBtn 关闭按钮JSON,存储关闭按钮位置的动作(一般都是关闭,也可以做一些骚操作,比如点击关闭按钮跳转到某个页面,或者没有关闭按钮,一定要点击背景图)
popupLogId 弹窗记录ID,本设计中查询到弹窗就会往mk_popup_log插入记录,而不是用户看到弹窗才记录,如果需要了解用户是否有真正看到弹窗,需要与前端协商一起实现,这个ID可以作为下个api的传参

示例:

https://domain.com/popup/best?sence=mini_home&cityCode=440300

四、需求升级

1.点击图片后不跳转到另外1个页面,而是实现特殊逻辑(如拉起小程序订阅消息)

解:需要前端协作实现,增加bgImg.type的定义值,数据通过bgImg.value返回给前端

2.点击图片后不跳转到另外1个页面,而是切换到下一张图片,点击后下一张图片后才跳转页面(比如发优惠券,第一张图片展示立即领取,点击后请求api进行发券,随后切换至去使用的图片,点击后再跳转到对应用券页面)

解:增加下一步操作设计,next结构与最外层的响应一样,类似递归结构

3.图片位置除了背景图,还需要展示其他的信息,比如新人礼包弹窗展示优惠券(需要实现ReplaceParam来完成)

1)实现接口ReplaceParam,bean名称为参数名,重写replace方法,以下为'新人券包'的demo代码

2)配置demo(即bg_img字段值),{newUserCoupon}是一个动态参数,运行过程中替换掉:

js 复制代码
{
    "imgUrl": "{"imgUrl":"https://img.domain.com/aaa.png","newUserCoupon":{newUserCoupon}}",
    "type": "api",
    "value": "https://domain.com/api/coupon/recive?id=8"
}

问:imgUrl为什么要配置为json字符串?

答:灵活运用固定字段响应数据给到前端,不同业务场景可定义不同的字段返回,只要跟前端约定好即可

数据响应示例:

4.在运行时给用户做一些弹窗埋点,比如完成某个事件后给用户埋一个弹窗,用户下次进入应用就给他弹

解:增加1张表mk_user_popup,字段与mk_popup差不多,但是不需要配置mk_popup_condition,因为这种弹窗只弹1次已经能满足需求,如有特殊需求也可以特殊设计

js 复制代码
CREATE TABLE `mk_user_popup` (
  `id` int(11) unsigned NOT NULL AUTO\_INCREMENT COMMENT 'ID',
  `user_id` int(11) NOT NULL COMMENT 'bu\_user\_info.id',
  `begin_time` datetime NOT NULL COMMENT '有效期开始时间',
  `end_time` datetime NOT NULL COMMENT '有效期结束时间',
  `status` varchar(8) NOT NULL DEFAULT '' COMMENT '状态(off:下架,on:上架)',
  `priority` int(11) NOT NULL DEFAULT '0' COMMENT '优先级(值越大,优先级越高)',
  `title` varchar(32) NOT NULL DEFAULT '' COMMENT '标题',
  `text` varchar(128) NOT NULL DEFAULT '' COMMENT '文案',
  `bg_img` varchar(255) NOT NULL DEFAULT '' COMMENT '背景图json数据',
  `close_btn` varchar(255) NOT NULL DEFAULT '' COMMENT '关闭按钮json数据',
  `remark` varchar(255) DEFAULT NULL COMMENT '备注',
  `create_time` datetime NOT NULL DEFAULT CURRENT\_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime NOT NULL DEFAULT CURRENT\_TIMESTAMP ON UPDATE CURRENT\_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `idx_userid` (`user_id`),
  KEY `idx_userid_begintime` (`user_id`,`begin_time`),
  KEY `idx_userid_endtime` (`user_id`,`end_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户弹窗';

PopService中增加以下代码

tips:还有些步骤型的逻辑,每步骤都有1个弹窗,比如进行到第1步,给用户埋了1个弹窗,但用户一直没有进入应用,后面流转到第2步了,又埋了1个弹窗。此时如果用户进入应用,就会先后看到2个弹窗,其实第1步的弹窗对于用户来说已经没有意义了(造成不好的用户体验),直接给用户弹第2步的弹窗即可。所以可以设计取消弹窗的操作,在埋第2步弹窗的同时把第1步弹窗取消掉。

扩展思考:

1.弹窗做成不同的表现形式,比如提示框效果,过一段时间自动消失

2.弹窗自动关闭,比如倒计时n秒后自动关闭

怎么样?如果你觉得有用的话,还不快快收藏起来!!!

附:涉及的代码目录

github:github.com/897665787/s...

gitee:gitee.com/jq_di/sprin...

js 复制代码
springcloud-template
└── template-tool
     └──controller
          └── PopupController -- 最优弹窗查询API
     └── popup
          └── PopCondition -- 抽象定义使用条件
          └── PopParam -- 弹窗条件参数
          └── PopService -- 弹窗查询的核心代码
          └── ReplaceParam -- 参数替换接口
          └── condition -- 弹窗条件实现类(条件就新增到这下面)
               └── FrequencyCondition -- 弹窗频率
               └── PushAreaCondition -- 推送地区
               └── ... -- 其他弹窗条件
          └── param -- 参数替换实现类(复杂条件就新增到这下面)
               └──NewUserCouponReplaceParam  -- 新人券包
               └── ... -- 其他参数替换
相关推荐
Tech Synapse2 分钟前
Java根据前端返回的字段名进行查询数据的方法
java·开发语言·后端
.生产的驴2 分钟前
SpringCloud OpenFeign用户转发在请求头中添加用户信息 微服务内部调用
spring boot·后端·spring·spring cloud·微服务·架构
微信-since8119218 分钟前
[ruby on rails] 安装docker
后端·docker·ruby on rails
代码吐槽菌2 小时前
基于SSM的毕业论文管理系统【附源码】
java·开发语言·数据库·后端·ssm
豌豆花下猫2 小时前
Python 潮流周刊#78:async/await 是糟糕的设计(摘要)
后端·python·ai
YMWM_2 小时前
第一章 Go语言简介
开发语言·后端·golang
码蜂窝编程官方3 小时前
【含开题报告+文档+PPT+源码】基于SpringBoot+Vue的虎鲸旅游攻略网的设计与实现
java·vue.js·spring boot·后端·spring·旅游
hummhumm3 小时前
第 25 章 - Golang 项目结构
java·开发语言·前端·后端·python·elasticsearch·golang
J老熊3 小时前
JavaFX:简介、使用场景、常见问题及对比其他框架分析
java·开发语言·后端·面试·系统架构·软件工程
AuroraI'ncoding3 小时前
时间请求参数、响应
java·后端·spring