Design and Implementation of an Online Course Purchase System(18)

Complete the dynamic display function of home page rotation image

Posted by Chen Xingxu on June 3, 2020

易课寄在线购课系统开发笔记(十八)–完成首页轮播图动态展示功能

首页轮播图动态展示

功能分析

根据分类 id 查询内容列表,把内容展示到首页。

内容分类 id 需要是固定的。可以配置到属性文件中。

展示首页之前,先查询内容列表,然后展示到首页。

Dao层

单表查询。可以使用逆向工程。

Service层

参数:内容分类id

返回值:List<EcoursesContent>

业务逻辑:

根据分类 id 查询内容列表

@Override
public List<EcoursesContent> getContentListByCid(long cid) {
    //查询缓存
    try {
        //如果缓存中有直接响应结果
        String json = jedisClient.hget(CONTENT_LIST, cid + "");
        if (StringUtils.isNotBlank(json)) {
            List<EcoursesContent> list = JsonUtils.jsonToList(json, EcoursesContent.class);
            return list;
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    //如果没有查询数据库
    EcoursesContentExample example = new EcoursesContentExample();
    Criteria criteria = example.createCriteria();
    //设置查询条件
    criteria.andCategoryIdEqualTo(cid);
    //执行查询
    List<EcoursesContent> list = contentMapper.selectByExampleWithBLOBs(example);
    //把结果添加到缓存
    try {
        jedisClient.hset(CONTENT_LIST, cid + "", JsonUtils.objectToJson(list));
    } catch (Exception e) {
        e.printStackTrace();
    }
    return list;
}

需要把接口安装到本地仓库。

表现层

引用服务:

springmvc.xml 中添加引用:

<dubbo:reference interface="cn.ecourses.content.service.ContentService" id="contentService" />

需要在展示首页的 Controller 中添加业务逻辑。

Redis

Redis 的安装

请参阅下面这篇笔记:

CentOS 6学习笔记(九)–CentOS6环境安装Redis

Redis 五种数据类型

String:key-value(做缓存)

Redis 中所有的数据都是字符串。命令不区分大小写,key 是区分大小写的。Redis 是单线程的,Redis 中不适合保存内容大的数据。

get、set、

incr:加一(生成 id)

Decr:减一

Hash:key-fields-values(做缓存)

相当于一个 key 对应一个 map,map 中还有 key-value

使用 hash 对 key 进行归类。

Hset:向 hash 中添加内容

Hget:从 hash 中取内容

List:有顺序可重复

> lpush list1 a b c d
(integer) 4
> lrange list1 0 -1
1) "d"
2) "c"
3) "b"
4) "a"
> rpush list1 1 2 3 4
(integer) 8
> lrange list1 0 -1
1) "d"
2) "c"
3) "b"
4) "a"
5) "1"
6) "2"
7) "3"
8) "4"
> 
> lpop list1 
"d"
> lrange list1 0 -1
1) "c"
2) "b"
3) "a"
4) "1"
5) "2"
6) "3"
7) "4"
> rpop list1
"4"
> lrange list1 0 -1
1) "c"
2) "b"
3) "a"
4) "1"
5) "2"
6) "3"
> 

Set:元素无顺序,不能重复

> sadd set1 a b c c c d
(integer) 4
> smembers set1
1) "b"
2) "c"
3) "d"
4) "a"
> srem set1 a
(integer) 1
> smembers set1
1) "b"
2) "c"
3) "d"
> 

还有集合运算命令。

SortedSet(zset):有顺序,不能重复

> zadd zset1 2 a 5 b 1 c 6 d
(integer) 4
> zrange zset1 0 -1
1) "c"
2) "a"
3) "b"
4) "d"
> zrem zset1 a
(integer) 1
> zrange zset1 0 -1
1) "c"
2) "b"
3) "d"
> zrevrange zset1 0 -1
1) "d"
2) "b"
3) "c"
> zrange zset1 0 -1 withscores
1) "c"
2) "1"
3) "b"
4) "5"
5) "d"
6) "6"
> zrevrange zset1 0 -1 withscores
1) "d"
2) "6"
3) "b"
4) "5"
5) "c"
6) "1"
> 

Key命令

设置key的过期时间。

Expire key second:设置 key 的过期时间

Ttl key:查看 key 的有效期

Persist key:清除 key 的过期时间。key 持久化。

> expire Hello 100
(integer) 1
> ttl Hello
(integer) 77

Redis的持久化方案

Redis 的所有数据都是保存到内存中的。

Rdb:快照形式,定期把内存中当前时刻的数据保存到磁盘。Redis 默认支持的持久化方案。

aof 形式:append only file。把所有对 Redis 数据库操作的命令,增删改操作的命令,保存到文件中。数据库恢复时把所有的命令执行一遍即可。

在redis.conf配置文件中配置。

Rdb:

aof 的配置:

两种持久化方案同时开启,使用 aof 文件来恢复数据库。

Redis 集群的搭建

请参阅下面这篇笔记:

CentOS 6学习笔记(十二)–CentOS6环境搭建Redis集群

架构细节: (1) 所有的 Redis 节点彼此互联 (PING-PONG 机制),内部使用二进制协议优化传输速度和带宽。 (2) 节点的 fail 是通过集群中超过半数的节点检测失效时才生效。 (3) 客户端与 Redis 节点直连,不需要中间 proxy 层。客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。 (4) redis-cluster 把所有的物理节点映射到 [0-16383]slot 上,cluster 负责维护 node<->slot<->value

Redis 集群中内置了 16384 个哈希槽,当需要在 Redis 集群中放置一个 key-value 时,Redis 先对 key 使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,Redis 会根据节点数量大致均等的将哈希槽映射到不同的节点。

Jedis

需要把 Jedis 依赖的 jar 包添加到工程中。Maven 工程中需要把 Jedis 的坐标添加到依赖。

<dependency>
  <groupId>redis.clients</groupId>
  <artifactId>jedis</artifactId>
</dependency>

向业务逻辑中添加缓存

接口封装

常用的操作 Redis 的方法提取出一个接口,分别对应单机版和集群版创建两个实现类。

接口定义

package cn.ecourses.common.jedis;
public interface JedisClient {
	String set(String key, String value);
	String get(String key);
	Boolean exists(String key);
	Long expire(String key, int seconds);
	Long ttl(String key);
	Long incr(String key);
	Long hset(String key, String field, String value);
	String hget(String key, String field);
	Long hdel(String key, String... field);
	Boolean hexists(String key, String field);
	List<String> hvals(String key);
	Long del(String key);
}

单机版实现类

package cn.ecourses.common.jedis;
public class JedisClientPool implements JedisClient {

	private JedisPool jedisPool;

	public JedisPool getJedisPool() {
		return jedisPool;
	}

	public void setJedisPool(JedisPool jedisPool) {
		this.jedisPool = jedisPool;
	}

	@Override
	public String set(String key, String value) {
		Jedis jedis = jedisPool.getResource();
		String result = jedis.set(key, value);
		jedis.close();
		return result;
	}

	@Override
	public String get(String key) {
		Jedis jedis = jedisPool.getResource();
		String result = jedis.get(key);
		jedis.close();
		return result;
	}

	@Override
	public Boolean exists(String key) {
		Jedis jedis = jedisPool.getResource();
		Boolean result = jedis.exists(key);
		jedis.close();
		return result;
	}

	@Override
	public Long expire(String key, int seconds) {
		Jedis jedis = jedisPool.getResource();
		Long result = jedis.expire(key, seconds);
		jedis.close();
		return result;
	}

	@Override
	public Long ttl(String key) {
		Jedis jedis = jedisPool.getResource();
		Long result = jedis.ttl(key);
		jedis.close();
		return result;
	}

	@Override
	public Long incr(String key) {
		Jedis jedis = jedisPool.getResource();
		Long result = jedis.incr(key);
		jedis.close();
		return result;
	}

	@Override
	public Long hset(String key, String field, String value) {
		Jedis jedis = jedisPool.getResource();
		Long result = jedis.hset(key, field, value);
		jedis.close();
		return result;
	}

	@Override
	public String hget(String key, String field) {
		Jedis jedis = jedisPool.getResource();
		String result = jedis.hget(key, field);
		jedis.close();
		return result;
	}

	@Override
	public Long hdel(String key, String... field) {
		Jedis jedis = jedisPool.getResource();
		Long result = jedis.hdel(key, field);
		jedis.close();
		return result;
	}

	@Override
	public Boolean hexists(String key, String field) {
		Jedis jedis = jedisPool.getResource();
		Boolean result = jedis.hexists(key, field);
		jedis.close();
		return result;
	}

	@Override
	public List<String> hvals(String key) {
		Jedis jedis = jedisPool.getResource();
		List<String> result = jedis.hvals(key);
		jedis.close();
		return result;
	}

	@Override
	public Long del(String key) {
		Jedis jedis = jedisPool.getResource();
		Long result = jedis.del(key);
		jedis.close();
		return result;
	}

}

配置:applicationContext-redis.xml

<bean id="jedisClientPool" class="cn.ecourses.common.jedis.JedisClientPool">
    <property name="jedisPool" ref="jedisPool"></property>
</bean>
<bean id="jedisPool" class="redis.clients.jedis.JedisPool">
    <constructor-arg name="host" value="redis"/>
    <constructor-arg name="port" value="6379"/>
</bean>

集群版实现类

package cn.ecourses.common.jedis;
public class JedisClientCluster implements JedisClient {

	private JedisCluster jedisCluster;
	
	public JedisCluster getJedisCluster() {
		return jedisCluster;
	}

	public void setJedisCluster(JedisCluster jedisCluster) {
		this.jedisCluster = jedisCluster;
	}

	@Override
	public String set(String key, String value) {
		return jedisCluster.set(key, value);
	}

	@Override
	public String get(String key) {
		return jedisCluster.get(key);
	}

	@Override
	public Boolean exists(String key) {
		return jedisCluster.exists(key);
	}

	@Override
	public Long expire(String key, int seconds) {
		return jedisCluster.expire(key, seconds);
	}

	@Override
	public Long ttl(String key) {
		return jedisCluster.ttl(key);
	}

	@Override
	public Long incr(String key) {
		return jedisCluster.incr(key);
	}

	@Override
	public Long hset(String key, String field, String value) {
		return jedisCluster.hset(key, field, value);
	}

	@Override
	public String hget(String key, String field) {
		return jedisCluster.hget(key, field);
	}

	@Override
	public Long hdel(String key, String... field) {
		return jedisCluster.hdel(key, field);
	}

	@Override
	public Boolean hexists(String key, String field) {
		return jedisCluster.hexists(key, field);
	}

	@Override
	public List<String> hvals(String key) {
		return jedisCluster.hvals(key);
	}

	@Override
	public Long del(String key) {
		return jedisCluster.del(key);
	}
}

配置:applicationContext-redis.xml

<!-- 连接redis集群 -->
<bean id="jedisClientCluster" class="cn.ecourses.common.jedis.JedisClientCluster">
    <property name="jedisCluster" ref="jedisCluster"/>
</bean>
<bean id="jedisCluster" class="redis.clients.jedis.JedisCluster">
    <constructor-arg name="nodes">
        <set>
            <bean class="redis.clients.jedis.HostAndPort">
                <constructor-arg name="host" value="redis"></constructor-arg>
                <constructor-arg name="port" value="7001"></constructor-arg>
            </bean> 
            <bean class="redis.clients.jedis.HostAndPort">
                <constructor-arg name="host" value="redis"></constructor-arg>
                <constructor-arg name="port" value="7002"></constructor-arg>
            </bean> 
            <bean class="redis.clients.jedis.HostAndPort">
                <constructor-arg name="host" value="redis"></constructor-arg>
                <constructor-arg name="port" value="7003"></constructor-arg>
            </bean> 
            <bean class="redis.clients.jedis.HostAndPort">
                <constructor-arg name="host" value="redis"></constructor-arg>
                <constructor-arg name="port" value="7004"></constructor-arg>
            </bean> 
            <bean class="redis.clients.jedis.HostAndPort">
                <constructor-arg name="host" value="redis"></constructor-arg>
                <constructor-arg name="port" value="7005"></constructor-arg>
            </bean> 
            <bean class="redis.clients.jedis.HostAndPort">
                <constructor-arg name="host" value="redis"></constructor-arg>
                <constructor-arg name="port" value="7006"></constructor-arg>
            </bean> 
        </set>
    </constructor-arg>
</bean> 

注意:单机版和集群版不能共存,使用单机版时注释集群版的配置。使用集群版时,要把单机版注释。

添加缓存

功能分析

查询内容列表时添加缓存:

1、查询数据库之前先查询缓存;

2、查询到结果,直接响应结果;

3、查询不到,缓存中没有需要查询数据库;

4、把查询结果添加到缓存中;

5、返回结果。

向 Redis 中添加缓存:

Key:cid

Value:内容列表。需要把 Java 对象转换成 JSON。

使用 hash 对 key 进行归类。

  • HASH_KEY:HASH
    • KEY:VALUE
    • KEY:VALUE
    • KEY:VALUE
    • KEY:VALUE

注意:添加缓存不能影响正常业务逻辑。

代码实现

@Override
public List<EcoursesContent> getContentListByCid(long cid) {
    //查询缓存
    try {
        //如果缓存中有直接响应结果
        String json = jedisClient.hget(CONTENT_LIST, cid + "");
        if (StringUtils.isNotBlank(json)) {
            List<EcoursesContent> list = JsonUtils.jsonToList(json, EcoursesContent.class);
            return list;
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    //如果没有查询数据库
    EcoursesContentExample example = new EcoursesContentExample();
    Criteria criteria = example.createCriteria();
    //设置查询条件
    criteria.andCategoryIdEqualTo(cid);
    //执行查询
    List<EcoursesContent> list = contentMapper.selectByExampleWithBLOBs(example);
    //把结果添加到缓存
    try {
        jedisClient.hset(CONTENT_LIST, cid + "", JsonUtils.objectToJson(list));
    } catch (Exception e) {
        e.printStackTrace();
    }
    return list;
}

缓存同步

对内容信息做增删改操作后只需要把对应缓存删除即可。

可以根据 cid 删除。

@Override
public ECoursesResult addContent(EcoursesContent content) {
    //将内容数据插入到内容表
    content.setCreated(new Date());
    content.setUpdated(new Date());
    //插入到数据库
    contentMapper.insert(content);
    //缓存同步,删除缓存中对应的数据。
    jedisClient.hdel(CONTENT_LIST, content.getCategoryId().toString());
    return ECoursesResult.ok();
}