活动营销系统
一、整体架构图
二、核心业务系统介绍
2.1.接入层统一异常处理逻辑
2.2.邀请服务逻辑
2.3.权益发放服务
2.4.排行榜服务
2.4.1.榜单服务数据结构
数据结构分为两块:
- 配置中心数据,因为排行榜没有后台配置平台,只能将配置数据放到配置中心,具备实时更改配置的能力
- 数据表,主要是排行榜核心数据结构
-- 配置中心信息
榜单配置信息数据:
榜单周期id:
-- 数据库表结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | { "rank_code" : { "name" : "榜单名称" , "rank_type" : "用于榜单归类" , "rank_default" : { "rank_alias" : "default" , "rank_biz_id" : "默认榜单筛选条件:能量值" , "rank_sub_biz_id" : "none,对应过滤值" }, "rank_children" : [ { "rank_alias" : "出行能量值:consumption" , "rank_sub_biz_id" : "consumption" }, { "rank_alias" : "邀请能量值:invite" , "rank_sub_biz_id" : "invite" } ], "rand_period" : [ { "periodType" : "day" }, { "periodType" : "week" }, { "periodType" : "month" }, { "periodType" : "year" }, { "periodType" : "range" } ], "beginTime" : "开始时间" , "endTime" : "结束时间" } }day:当前日期yyyy-MM-dd; week:w-当前周第一天; month:yyyy-MM; year:yyyy; rang: 开始时间-结束时间,CREATE TABLE `activity_rank_data_source_detail` ( `id` bigint( 20 ) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id' , `request_id` varchar( 128 ) NOT NULL DEFAULT '' COMMENT '唯一键' , `entity_id` varchar( 64 ) NOT NULL DEFAULT '' COMMENT '实体id' , `entity_type` smallint( 6 ) NOT NULLactivity_rank_period_count DEFAULT '0' COMMENT '实体类型,1:用户id' , `entity_data_real_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '该数据发生的真实时间' , `entity_data_biz_id` varchar( 128 ) NOT NULL DEFAULT '' COMMENT '业务线,1:能量值' , `entity_data_sub_biz_id` varchar( 128 ) NOT NULL DEFAULT '' COMMENT '二级业务:能耗、助力' , `entity_data_num` int ( 11 ) NOT NULL DEFAULT '0' 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`), UNIQUE KEY `uk_request_id` (`request_id`), KEY `idx_biz_id` (`entity_data_biz_id`,`entity_data_sub_biz_id`,`entity_id`,`entity_data_real_time`,`entity_data_num`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT= '活动榜单-榜单数据更新明细' ; CREATE TABLE `activity_rank_period_count` ( `id` bigint( 20 ) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id' , `rank_biz_id` varchar( 128 ) NOT NULL DEFAULT '' COMMENT '榜单业务id' , `rank_sub_biz_id` varchar( 128 ) NOT NULL DEFAULT '' COMMENT '榜单业务id' , `rank_code` varchar( 128 ) NOT NULL DEFAULT '' COMMENT '榜单code' , `rank_name` varchar( 128 ) NOT NULL DEFAULT '' COMMENT '榜单名称' ,--待定 `entity_id` varchar( 64 ) NOT NULL DEFAULT '' COMMENT '实体id' , `entity_type` smallint( 6 ) NOT NULL DEFAULT '0' COMMENT '实体类型,1:用户id' , `period_type` smallint( 6 ) NOT NULL DEFAULT '0' COMMENT '周期类型,1:日、2:周、3:月,4:年、5:固定区间' , `period_id` varchar( 64 ) NOT NULL DEFAULT '' COMMENT '周期唯一code' , `period_begin_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '周期开始时间,所有的周期开始-结束时间为左闭右开' , `period_count` int ( 11 ) NOT NULL DEFAULT '0' 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`), UNIQUE KEY `uk_period_entity_id` (`rank_biz_id`,`period_id`,`entity_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT= '活动榜单-榜单周期数据统计' ; |
2.4.2.榜单服务更新流程
说明:
-
redis中会缓存一份topN的数据
-
更新时机
- 用户数据变更时,实时更新
- 定时从DB中加载对应的topN数据
- topN数据只能根据统计后的用户-周期数据进行更新。
-
redis内存数据结构:
1 2 | rank_source_biz_id(榜单类型)_day(周期类型)_period_code: zset rank_top_biz_id(榜单类型)_day(周期类型)_period_code(当前): List<String> ,user_id,能量值,排名 |
数据注意事项:
- rank_source_xxx避免元素数量过多(目前暂时忽略,元素个数在1w以内)
-
rank_top_xxx
- 并列排名的问题,topN实际元素可能是N+n (是否要做限制)
2.4.3.多维度榜单设计
zset value:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | long 类型 最大值为: 111111111111111111111111111111111111111111111111111111111111111 63 位 value需要包括: 排行榜值count 和 时间戳 所以我们可以根据我们业务的值来选择 使用多少bit位来表示排行榜值,和时间戳 假如我们业务内,count取值范围在 0 - 10000 , 时间戳在 2025 - 01 - 01 00 : 00 : 00 ~ 2026 - 01 - 01 00 : 00 : 00 那么我们可以这样设计 10000 的二进制表示为: 10011100010000 共 14 位 2025 - 01 - 01 00 : 00 : 00 日期表示 毫秒级: 11001010000011101011100100000100000000000 41 位 秒: 1100111011101000001010100000000 31 位 2026 - 01 - 01 00 : 00 : 00 日期表示 毫秒级: 11001101101110101001000110011010000000000 41 位 秒: 1101001010101010100100010000000 31 位 所以说如果表示这一年时间,我们最多可以使用 41 位 ,假设我们在榜单上配置一个基准时间,例如,就是 2025 - 01 - 01 00 : 00 : 00 的毫秒, 那么存储时间,就可以设置为该基准时间的相对时间, 2026 - 01 - 01 00 : 00 : 00 和 2025 - 01 - 01 00 : 00 : 00 之间最大时间差为: 11101010111101100010010110000000000 共 35 位 如果只精确到秒: 1111000010011001110000000 共 25 位 这是我们就可以在一个用一个 long 类型数据表示这两部分数据 高位用 15 位表示 count ,低位用 40 位表示时间. 以 count = 1000 ,time = 2025 - 06 - 01 10 : 12 : 30 基准时间为: 2025 - 01 - 01 00 : 00 : 00 ,精确秒来计算: 合并计算: //偏移位数 int timeShift = 45 ; Date date1 = DateUtil.parse( "2025-01-01 00:00:00" , DatePattern.NORM_DATETIME_FORMAT); Date date2 = DateUtil.parse( "2025-06-01 10:12:30" , DatePattern.NORM_DATETIME_FORMAT); long diff =Long.valueOf(( date2.getTime() -date1.getTime()) / 1000 ); int count = 1000 ; long result = 0L; result = (result | count) << timeShift; result = result | diff; System.out.println( "001:" +Long.toBinaryString(result)); //反解析 int count1 = ( int )(result >> 45 ); long diff1 = result^((count1 | 0L)<< 45 ); System.out.println( "002:" +Long.toBinaryString(count1)); System.out.println( "022:" +Long.toBinaryString((count1 | 0L) << timeShift)); System.out.println( "003:" + Long.toBinaryString(diff)); System.out.println( "004:" + Long.toBinaryString(diff1)); |
三、服务治理
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库