Redis實現商品熱賣榜


Redis系列

redis相關介紹

redis是一個key-value存儲系統。和Memcached類似,它支持存儲的value類型相對更多,包括string(字符串)、list(鏈表)、set(集合)、zset(sorted set --有序集合)和hash(哈希類型)。這些數據類型都支持push/popadd/remove及取交集並集和差集及更豐富的操作,而且這些操作都是原子性的。在此基礎上,redis支持各種不同方式的排序。與memcached一樣,為了保證效率,數據都是緩存在內存中。區別的是redis會周期性的把更新的數據寫入磁盤或者把修改操作寫入追加的記錄文件,並且在此基礎上實現了master-slave(主從)同步。
Redis 是一個高性能的key-value數據庫。 redis的出現,很大程度補償了memcached這類key/value存儲的不足,在部 分場合可以對關系數據庫起到很好的補充作用。它提供了JavaC/C++C#PHPJavaScriptPerlObject-CPythonRubyErlang等客戶端,使用很方便.


今天我們來利用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

如果覺得筆者的內容對你有用,請關注微信公眾號,歡迎點贊評論。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM