Redis实现商品热卖榜
Redis
系列
redis
相关介绍
redis
是一个key-value
存储系统。和Memcached
类似,它支持存储的value
类型相对更多,包括string
(字符串)、list
(链表)、set
(集合)、zset
(sorted set
--有序集合)和hash
(哈希类型)。这些数据类型都支持push/pop
、add/remove
及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(
主从)同步。Redis
是一个高性能的key-value
数据库。 redis
的出现,很大程度补偿了memcached
这类key/value
存储的不足,在部 分场合可以对关系数据库起到很好的补充作用。它提供了Java
,C/C++
,C#
,PHP
,JavaScript
,Perl
,Object-C
,Python
,Ruby
,Erlang
等客户端,使用很方便.
今天我们来利用Redis
来实现一个大家在工作中都可能遇到的需求,热卖排行榜
话不多说开干!!!!!
需求说明
1、本次我们实现一个每日热卖商品排行榜的需求
2、简单的设计俩张表:goods(商品表)、sell(销售记录表)
3、主要是将当日热卖的商品查询出来,利用Redis
的有序集合进行大到小排序显示在前端页面
技术列表
Springboot 2.1.2.RELEASE
Redis
freemarker
mybatis-plus 3.2.0
hutool-all
搭建项目基础环境
首先我们来创建一个
springboot
项目,并且引入需要的依赖,后续需要的依赖后面用到在引入所有的依赖如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--mp-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.2.0</version>
</dependency>
<!--代码生成器-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.2.0</version>
</dependency>
<!-- sql分析器 -->
<dependency>
<groupId>p6spy</groupId>
<artifactId>p6spy</artifactId>
<version>3.8.6</version>
</dependency>
<!-- commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
<!--工具类-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>4.1.17</version>
</dependency>
由于我们项目用到的模本引擎是
freemarker
下面简单的介绍一下这个模版引擎
FreeMarker
是一款模板引擎:即一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页、配置文件、源代码等)的通用工具。它不是面向最终用户的,而是一个Java类库,是一款程序员可以嵌入他们所开发产品的组件.
能够很方便的对后端的数据进行渲染
敲黑板重点!!FreeMarker
的宏:宏是在模板中使用macro
指令定义
宏是和某个变量关联的模板片断,以便在模板中通过用户定义指令使用该变量。
大白话:就是提高前端的代码的重用
如下是本项目中用到的
FreeMarker
的宏
<#macro layout title>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>${title}</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta name="keywords" content="公众号:北漂码农有话说">
<meta name="description" content="公众号:北漂码农有话说致力于输出优质技术文章">
<link rel="stylesheet" href="/res/layui/css/layui.css">
<link rel="stylesheet" href="/res/css/global.css">
</head>
<body>
<#include "/include/header.ftl" />
<#nested >
<#include "/include/footer.ftl" />
<script src="/res/layui/layui.js"></script>
<script>
layui.cache.page = '';
layui.cache.user = {
username: '游客'
,uid: -1
,avatar: '../res/images/avatar/00.jpg'
,experience: 83
,sex: '男'
};
layui.config({
version: "3.0.0"
,base: '../res/mods/' //这里实际使用时,建议改成绝对路径
}).extend({
fly: 'index'
}).use('fly');
</script>
<script type="text/javascript">var cnzz_protocol = (("https:" == document.location.protocol) ? " https://" : " http://");document.write(unescape("%3Cspan id='cnzz_stat_icon_30088308'%3E%3C/span%3E%3Cscript src='" + cnzz_protocol + "w.cnzz.com/c.php%3Fid%3D30088308' type='text/javascript'%3E%3C/script%3E"));</script>
</body>
</html>
</#macro>
由于选择的是mybatis-plus开发所以我们将代码进行生成
参考官网:mybatis-plus官网
找到代码生成器将代码拷贝到你的项目下进行必要的配置执行main方法,生成代码。如下:
代码如下:
public class CodeGenerator {
/**
* <p>
* 读取控制台内容
* </p>
*/
public static String scanner(String tip) {
Scanner scanner = new Scanner(System.in);
StringBuilder help = new StringBuilder();
help.append("请输入" + tip + ":");
System.out.println(help.toString());
if (scanner.hasNext()) {
String ipt = scanner.next();
if (StringUtils.isNotEmpty(ipt)) {
return ipt;
}
}
throw new MybatisPlusException("请输入正确的" + tip + "!");
}
public static void main(String[] args) {
// 代码生成器
AutoGenerator mpg = new AutoGenerator();
// 全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
gc.setOutputDir(projectPath + "/src/main/java");
// gc.setOutputDir("D:\\test");
gc.setAuthor("公众号:北漂码农有话说");
gc.setOpen(false);
// gc.setSwagger2(true); 实体属性 Swagger2 注解
gc.setServiceName("%sService");
mpg.setGlobalConfig(gc);
// 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/triumphxxtop?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=UTC");
// dsc.setSchemaName("public");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("root");
mpg.setDataSource(dsc);
// 包配置
PackageConfig pc = new PackageConfig();
pc.setModuleName(null);
pc.setParent("com.triumphxx");
mpg.setPackageInfo(pc);
// 自定义配置
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
// to do nothing
}
};
// 如果模板引擎是 freemarker
String templatePath = "/templates/mapper.xml.ftl";
// 如果模板引擎是 velocity
// String templatePath = "/templates/mapper.xml.vm";
// 自定义输出配置
List<FileOutConfig> focList = new ArrayList<>();
// 自定义配置会被优先输出
focList.add(new FileOutConfig(templatePath) {
@Override
public String outputFile(TableInfo tableInfo) {
// 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
return projectPath + "/src/main/resources/mapper/"
+ "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
}
});
cfg.setFileOutConfigList(focList);
mpg.setCfg(cfg);
// 配置模板
TemplateConfig templateConfig = new TemplateConfig();
// 配置自定义输出模板
//指定自定义模板路径,注意不要带上.ftl/.vm, 会根据使用的模板引擎自动识别
// templateConfig.setEntity("templates/entity2.java");
// templateConfig.setService();
// templateConfig.setController();
templateConfig.setXml(null);
mpg.setTemplate(templateConfig);
// 策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
strategy.setSuperEntityClass("com.triumphxx.entity.BaseEntity");
strategy.setEntityLombokModel(true);
strategy.setRestControllerStyle(true);
strategy.setSuperControllerClass("com.triumphxx.controller.BaseController");
strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
strategy.setSuperEntityColumns("id", "created", "modified", "status");
strategy.setControllerMappingHyphenStyle(true);
strategy.setTablePrefix(pc.getModuleName() + "_");
mpg.setStrategy(strategy);
mpg.setTemplateEngine(new FreemarkerTemplateEngine());
mpg.execute();
}
}
执行
main
方法,如下,输入你的表名,生成你的代码生成代码
由于我们使用的是mp所以需要配置sql分析器,将sql打印出来,便于分析,当然配置sql分析器是有一定的性能损耗,所以不建议在产线上配置,配置如下:
首先添加依赖
<!-- sql分析器 -->
<dependency>
<groupId>p6spy</groupId>
<artifactId>p6spy</artifactId>
<version>3.8.6</version>
</dependency>
修改数据库配置信息,driver-class-name:com.p6spy.engine.spy.P6SpyDriver,
jdbc
后边加上p6spy
spring:
datasource:
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
url: jdbc:p6spy:mysql://localhost:3306/triumphxxtop?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=UTC
username: root
password: root
引入配置文件
项目设计思路,在项目启动的时候,将今日的热卖商品数据缓存到redis中,代码如下:
/**
* @author:triumphxx
* @Date:2020/5/16
* @Time:7:40 上午
* @微信公众号:北漂码农有话说
* @desc:系统启动加载类
**/
@Component
public class ContextStartUp implements ApplicationRunner {
@Autowired
SellService sellService;
/**
* 服务启动是加载执行的的方法
* 将每天热卖的商品进行展示
* @param args
* @throws Exception
*/
@Override
public void run(ApplicationArguments args) throws Exception {
sellService.initGoodsSellTop();
}
}
核心代码如下,每一步都有详细的说明。
@Autowired
GoodsService goodsService;
@Autowired
RedisUtil redisUtil;
@Autowired
com.triumphxx.util.DateUtil dateUtil;
@Override
public void initGoodsSellTop() {
//获取当天内销售量大于80的商品id和销售记录id
List<Sell> sells = this.list(new QueryWrapper<Sell>()
.ge("sell_num",80)
.ge("sell_date", DateUtil.format(new Date(),"yyyy-MM-dd"))
.select("goods_id","sell_num")
);
List<GoodsVo> goodsVos = new ArrayList<>();
for (Sell sell : sells) {
//1、根据销售量大于80的销售记录查询出货物的信息
GoodsVo goods = goodsService.selectSellTop(new QueryWrapper<Goods>().eq("g.goods_id",sell.getGoodsId())
.eq("s.sell_date",DateUtil.format(new Date(),"yyyy-MM-dd")));
//2、将销售货物一天的销售的数量进行数据缓存
redisUtil.zSet(Constants.REDIS_KEY.GOODS_TOP_KEY+goods.getGoodsId(),goods.getGoodsId(),goods.getSellNum());
//设置过期时间 当天有效
//0点的时间减去现在的时间换算层毫秒数
Long expireTime = dateUtil.initDateByDay()-DateUtil.currentSeconds();
redisUtil.expire(Constants.REDIS_KEY.GOODS_TOP_KEY+goods.getGoodsId(),expireTime);
//同时缓存一下货物的基本信息 货物id 货物名称 销售货物数量
this.hasCacheGoods(goods,expireTime);
goodsVos.add(goods);
}
//做并集
this.zuionOneDayTop(goodsVos);
}
/**
*合并当天热卖榜
*/
private void zuionOneDayTop(List<GoodsVo> goodsVos) {
List<String> otherKeys = new ArrayList<>();
for (GoodsVo goodsVo : goodsVos) {
String temp = Constants.REDIS_KEY.GOODS_TOP_KEY +goodsVo.getGoodsId();
otherKeys.add(temp);
}
redisUtil.zUnionAndStore(Constants.REDIS_KEY.GOODS_TOP_KEY,
otherKeys,Constants.REDIS_KEY.GOODS_ONE_DAY_RANK);
}
/**
* 缓存货物的基本信息
* @param goods
* @param expireTime
*/
private void hasCacheGoods(GoodsVo goods, Long expireTime) {
//构造货物基本信息key
String goodsKey =Constants.REDIS_KEY.GOODS_KEY+goods.getGoodsId();
boolean isKey = redisUtil.hasKey(goodsKey);
if (!isKey){
redisUtil.hset(goodsKey,Constants.REDIS_KEY.GOODS_KEY_ID,goods.getGoodsId(),expireTime);
redisUtil.hset(goodsKey,Constants.REDIS_KEY.GOODS_KEY_NAME,goods.getGoodsName(),expireTime);
redisUtil.hset(goodsKey,Constants.REDIS_KEY.GOODS_KEY_SELL_NUM,goods.getSellNum(),expireTime);
}
}
代码中设计的常量类如下
/**
* @author:triumphxx
* @Date:2020/5/16
* @Time:9:43 上午
* @微信公众号:北漂码农有话说
* @desc:常量类
**/
public class Constants {
public static class REDIS_KEY {
/**
* 货物销量key 每天某一个货物的销售量
*/
public static final String GOODS_TOP_KEY= "goods:rank:";
/**
* 货物的基本信息key
*/
public static final String GOODS_KEY= "goods:";
/**
* 货物id
*/
public static final String GOODS_KEY_ID= "goods:id:";
/**
* 货物名称
*/
public static final String GOODS_KEY_NAME= "goods:name:";
/**
* 货物销售数量
*/
public static final String GOODS_KEY_SELL_NUM= "goods:sellnum:";
/**
* 每日销售并集后的key
*/
public static final String GOODS_ONE_DAY_RANK= "oneday:rank";
}
}
项目中涉及的其他技术点比如:自定义标签等有机会专题讨论
项目中的具体细节请移步GitHub分析相关源码
项目最终效果如图
本文的案例,本被人已经上传到 GitHub上了,地址:https://github.com/triumphxx/triumphxxtop
如果觉得笔者的内容对你有用,请关注微信公众号,欢迎点赞评论。
Redis实现商品热卖榜的更多相关文章
- 想知道谁是你的最佳用户?基于Redis实现排行榜周期榜与最近N期榜
本文由云+社区发表 前言 业务已基于Redis实现了一个高可用的排行榜服务,长期以来相安无事.有一天,产品说:我要一个按周排名的排行榜,以反映本周内用户的活跃情况.于是周榜(按周重置更新的榜单)诞生了 ...
- 使用redis防止商品超发
redis不仅仅是单纯的缓存,它还有一些特殊的功能,在一些特殊场景上很好用.redis中key的原子自增incrby和判断key不存在再写入的setnx方法,可以有效的防止超发. 下面使用两个不同的方 ...
- redis解决商品秒杀问题
博主最近在项目中遇到了抢购问题!现在分享下.抢购.秒杀是如今很常见的一个应用场景,主要需要解决的问题有两个:1 高并发对数据库产生的压力2 竞争状态下如何解决库存的正确减少("超卖" ...
- Redis案例——商品秒杀,购物车
秒杀案例: <?php header("content-type:text/html;charset=utf-8"); $redis = new redis(); $resu ...
- .NetCore+Jexus代理+Redis模拟秒杀商品活动
开篇叙 本篇将和大家分享一下秒杀商品活动架构,采用的架构方案正如标题名称.NetCore+Jexus代理+Redis,由于精力有限所以这里只设计到商品添加,抢购,订单查询,处理队列抢购订单的功能:有不 ...
- Redis常见的应用场景解析
Redis是一个key-value存储系统,现在在各种系统中的使用越来越多,大部分情况下是因为其高性能的特性,被当做缓存使用,这里介绍下Redis经常遇到的使用场景. Redis特性 一个产品的使用场 ...
- redis介绍及常见问题总结
1.redis c语言编写的一个开源软件,使用字典结构存储数据,支持多种类型数据类型 数据类型:字符串,字典,列表,集合,有序集合 2.redis特点 速度快:c语言实现的,所有数据都存储在计算机内存 ...
- ThinkPHP5+Redis单例型购物车
<?php /** * Redis + 单例型购物车 * param $basket 存储商品信息 * param $ins 存储实例化对象 */ namespace lib; use redi ...
- 细谈Redis五大数据类型
文章原创于公众号:程序猿周先森.本平台不定时更新,喜欢我的文章,欢迎关注我的微信公众号. 上一篇文章有提到,Redis中使用最频繁的有5种数据类型:String.List.Hash.Set.SortS ...
随机推荐
- 微信网页授权access_token和普通access_token爬坑
两者的区别: 1.相同点:两者的有效期都是7200s 2.不同点: ①.网页授权access_token无需用户授权,无需用户关注,在网页授权回调域名下可获取到code,通过code换取网页授权acc ...
- List的isEmpty与==null的区别
集合的判空一般判定方法 ArrayList<Person> list = null; System.out.println(null == list);//return true Syst ...
- meta viewport相关
<!DOCTYPE html> H5标准声明,使用 HTML5 doctype,不区分大小写 <head lang=”en”> 标准的 lang 属性写法 <meta c ...
- python 输出日志到文件和控制台
import logging # 第一步,创建一个logger logger = logging.getLogger() logger.setLevel(logging.INFO) # Log等级总开 ...
- java 面向对象(三十三):泛型二 泛型在集合中的使用
1. 在集合中使用泛型之前的例子 @Test public void test1(){ ArrayList list = new ArrayList(); //需求:存放学生的成绩 list.add( ...
- Flex移动布局中单行和双行布局的区别以及使用
这里是单行布局 使用ul>li 来布局 <ul class="local-nav"> <li> <a ...
- PHP基础:(常量变量,数据类型,类型转换)
预定义变量(系统变量) $_GET:get方式提交的数据 $_POST:post方式提交的数据 $_REQUEST:$_GET,$_POST数据综合 $GLOBALS:PHP中所有的全局变量 $_SE ...
- bzoj3436小K的农场
bzoj3436小K的农场 题意: n个数,知道m条关系:a-b≥c.a-b≤c或a==b.问是否存在满足所有关系的情况.n≤10000,m≤10000. 题解: 差分约束.因为只要求是否满足,因此最 ...
- ASP.NET CORE之中间件-自定义异常中间件
参考资料:https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/middleware/?view=aspnetcore-3.1 1.一般A ...
- Windows搭建Redis集群-详细教程
一.集群知识 1.集群的概念 所谓的集群,就是通过添加服务器的数量,提供相同的服务,从而让服务器达到一个稳定.高效的状态. 2.使用redis集群的必要性 问题:我们已经部署好了redis,并且能启动 ...