Java牛客项目课_仿牛客网讨论区_第四章

文章目录

    • 第四章:Redis,一站式高性能存储方案(6379端口)
        • 4.1、Redis入门
        • 4.7、Spring整合Redis
        • 4.10、点赞
        • 4.13、我收到的赞
        • 4.16、关注、取消关注
        • 4.19、关注列表、粉丝列表
        • 4.23、优化登录模块(用Redis)

第四章:Redis,一站式高性能存储方案(6379端口)

4.1、Redis入门

Redis两个单词之间用冒号":"连接,就像数据库中用下划线连接,Java中用驼峰式。
Java牛客项目课_仿牛客网讨论区_第四章_第1张图片

list:命令用l、r开头
Set:命令用s开头
Hash:命令用h开头
Zset(有序集合):命令用z开头

zadd test 10 aaa 20 bbb 30 ccc:设置有序集合test。
zscore test ccc:查询test中ccc的分数,返回30。

# 有序集合
127.0.0.1:6379> zadd test 10 aaa 20 bbb 30 ccc 40 ddd 50 eee
(integer) 5
127.0.0.1:6379> zcard test		# 集合元素个数
(integer) 5
127.0.0.1:6379> zscore test ccc		# ccc的分数
"30"
127.0.0.1:6379> zrank test ccc		# 按分数从小到大排序,从0开始,ccc排在2
(integer) 2
127.0.0.1:6379> zrange test 0 2		# 打印从0到2的内容
1) "aaa"
2) "bbb"
3) "ccc"
# 有16个库
keys *	# 查看这个库里的所有key
keys test*	#查看这个库里所有以test开头的数据

127.0.0.1:6379> type test	# 查看test这个key的类型
zset
127.0.0.1:6379> exists test		# 查看test这个key是否存在,1表示存在,0表示不存在 
(integer) 1
127.0.0.1:6379> expire test 10		# 10秒后删除test
(integer) 1
127.0.0.1:6379> del test		# del可以删除任何类型的key
(integer) 1

4.7、Spring整合Redis

在这里插入图片描述
当你定义的一个Bean(上面有@Bean注释),需要某个被Spring接管的对象,你可以直接在Bean的参数里写它,Spring就会把这个对象自动给你注入进来。

Redis事务不满足ACID,它的机制是:你启动事务,再去执行Redis命令,它不会立刻执行命令,它会把你后面传入的命令都放到队列里,等你提交事务时,它会把你的命令一股脑发给Redis服务器,一起执行。所以不要在Redis事务的中间做查询,不然会查出来的是空数据。

package com.nowcoder.community;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.*;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.concurrent.TimeUnit;

@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes = CommunityApplication.class)
public class RedisTests {

    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    public void testStrings() {
        String redisKey = "test:count";

        redisTemplate.opsForValue().set(redisKey, 1);

        System.out.println(redisTemplate.opsForValue().get(redisKey));
        System.out.println(redisTemplate.opsForValue().increment(redisKey));
        System.out.println(redisTemplate.opsForValue().decrement(redisKey));
    }

    @Test
    public void testHashes() {
        String redisKey = "test:user";

        redisTemplate.opsForHash().put(redisKey, "id", 1);
        redisTemplate.opsForHash().put(redisKey, "username", "zhangsan");

        System.out.println(redisTemplate.opsForHash().get(redisKey, "id"));
        System.out.println(redisTemplate.opsForHash().get(redisKey, "username"));
    }

    @Test
    public void testLists() {
        String redisKey = "test:ids";

        redisTemplate.opsForList().leftPush(redisKey, 101);
        redisTemplate.opsForList().leftPush(redisKey, 102);
        redisTemplate.opsForList().leftPush(redisKey, 103);

        System.out.println(redisTemplate.opsForList().size(redisKey));
        System.out.println(redisTemplate.opsForList().index(redisKey, 0));
        System.out.println(redisTemplate.opsForList().range(redisKey, 0, 2));

        System.out.println(redisTemplate.opsForList().leftPop(redisKey));
        System.out.println(redisTemplate.opsForList().leftPop(redisKey));
        System.out.println(redisTemplate.opsForList().leftPop(redisKey));
    }

    @Test
    public void testSets() {
        String redisKey = "test:teachers";

        redisTemplate.opsForSet().add(redisKey, "刘备", "关羽", "张飞", "赵云", "诸葛亮");

        System.out.println(redisTemplate.opsForSet().size(redisKey));
        System.out.println(redisTemplate.opsForSet().pop(redisKey));
        System.out.println(redisTemplate.opsForSet().members(redisKey));
    }

    @Test
    public void testSortedSets() {
        String redisKey = "test:students";

        redisTemplate.opsForZSet().add(redisKey, "唐僧", 80);
        redisTemplate.opsForZSet().add(redisKey, "悟空", 90);
        redisTemplate.opsForZSet().add(redisKey, "八戒", 50);
        redisTemplate.opsForZSet().add(redisKey, "沙僧", 70);
        redisTemplate.opsForZSet().add(redisKey, "白龙马", 60);

        System.out.println(redisTemplate.opsForZSet().zCard(redisKey));
        System.out.println(redisTemplate.opsForZSet().score(redisKey, "八戒"));
        System.out.println(redisTemplate.opsForZSet().reverseRank(redisKey, "八戒"));
        System.out.println(redisTemplate.opsForZSet().reverseRange(redisKey, 0, 2));
    }

    @Test
    public void testKeys() {
        redisTemplate.delete("test:user");

        System.out.println(redisTemplate.hasKey("test:user"));

        redisTemplate.expire("test:students", 10, TimeUnit.SECONDS);
    }

    // 多次访问同一个key。
    // 简化要调用的方法,比如redisTemplate.opsForValue().increment(redisKey)->operations.increment();
    @Test
    public void testBoundOperations() {
        String redisKey = "test:count";
        BoundValueOperations operations = redisTemplate.boundValueOps(redisKey);
        operations.increment();
        operations.increment();
        operations.increment();
        operations.increment();
        operations.increment();
        System.out.println(operations.get());
    }

    // 编程式事务
    // Redis不满足ACID原则,Redis会把操作放队列里,完成后统一提交。因此,不能把查询任务放到事务里,不然会查出来的是空数据。
    @Test
    public void testTransactional() {
        Object obj = redisTemplate.execute(new SessionCallback() {
            @Override
            public Object execute(RedisOperations operations) throws DataAccessException {
                String redisKey = "test:tx";

                operations.multi();//启动事务

                operations.opsForSet().add(redisKey, "zhangsan");
                operations.opsForSet().add(redisKey, "lisi");
                operations.opsForSet().add(redisKey, "wangwu");

                System.out.println(operations.opsForSet().members(redisKey));

                return operations.exec();//提交事务
            }
        });
        System.out.println(obj);
    }
}

4.10、点赞

异步请求,就是不刷新整个页面,在后台返回后,页面局部刷新,像ajax(jQuery的一种)那样。异步请求的后台的Controller要加@ResponseBody,不是跳转到某个页面,而是返回给页面某个字符串,页面显示出这个字符串。
貌似加@ResponseBody的都是post请求(@RequestMapping(path = “/letter/send”, method = RequestMethod.POST)),没加的都是get请求。

4.13、我收到的赞

开发用户主页,显示用户收到了多少个赞。

4.16、关注、取消关注

zset用关注时间作为分数,可以把关注的用户按照关注的时间顺序列举出来。RedisKeyUtil类:

    // 某个实体拥有的粉丝
    // follower:entityType:entityId -> zset(userId,now)
    public static String getFollowerKey(int entityType, int entityId) {
        return PREFIX_FOLLOWER + SPLIT + entityType + SPLIT + entityId;
    }

4.19、关注列表、粉丝列表

FollowService类的public List> findFollowers(int userId, int offset, int limit) 方法的Set targetIds = redisTemplate.opsForZSet().reverseRange(followerKey, offset, offset + limit - 1);。这句中Redis整合SpringBoot的redisTemplate.opsForZSet()的range方法/reverseRange方法,返回的是按分数从小到大/从大到小排序的Set。你可能奇怪,Set接口在Java中是个无序集合,但这里的Set接口的实现类是Redis自己实现的,是个有序的Set。

课后作业(第四章最下面有给答案):
开发我的帖子功能,即在个人主页上,显示当前用户曾经发布过的帖子。
开发我的回复功能,即在个人主页上,显示当前用户曾经为帖子发布过的评论。

4.23、优化登录模块(用Redis)

RedisKeyUtil类的private static final String PREFIX_KAPTCHA = “kaptcha”;//验证码的英文是captcha。但生成验证码的jar包是Kaptcha。

LoginController类的
@RequestMapping(path = “/kaptcha”, method = RequestMethod.GET)
public void getKaptcha(HttpServletResponse response/, HttpSession session/)方法:

        // 验证码的归属
        String kaptchaOwner = CommunityUtil.generateUUID();
        Cookie cookie = new Cookie("kaptchaOwner", kaptchaOwner);
        cookie.setMaxAge(60);//cookie的有效时间:1分钟
        cookie.setPath(contextPath);//cookie的有效路径:整个项目下都有效
        response.addCookie(cookie);//把cookie发送给客户端
        // 将验证码存入Redis
        String redisKey = RedisKeyUtil.getKaptchaKey(kaptchaOwner);//得到key
        redisTemplate.opsForValue().set(redisKey, text, 60, TimeUnit.SECONDS);//60秒生效时间

LoginController类的方法

    @RequestMapping(path = "/login", method = RequestMethod.POST)
    public String login(String username, String password, String code, boolean rememberme,
                        Model model, /*HttpSession session, */HttpServletResponse response,
                        @CookieValue("kaptchaOwner") String kaptchaOwner) {//接受变量名为kaptchaOwner的cookie

UserService类(Redis的value可以存取对象):

//UserService类的public Map login(String username, String password, int expiredSeconds)方法:
redisTemplate.opsForValue().set(redisKey, loginTicket);//Redis会把loginTicket这个对象序列化为JSON字符串来保存

//UserService类的public void logout(String ticket)方法
LoginTicket loginTicket = (LoginTicket)redisTemplate.opsForValue().get(redisKey);//返回的是个Object对象,强转为LoginTicket对象

使用Redis存储登录凭证:cookie里的ticket和LoginTicket loginTicket里的ticket都是CommunityUtil.generateUUID()。而把LoginTicket loginTicket存到Redis里的key是String redisKey = RedisKeyUtil.getTicketKey(ticket);,即ticket:CommunityUtil.generateUUID()。
LoginController类里的登录/退出登录的方法存取cookie里的ticket。UserService类里的登录/退出登录的方法,用由cookie里的ticket拼接出的redisKey作为key,来存取Redis里的loginTicket。

UserService类:

    public int updateHeader(int userId, String headerUrl) {
//        return userMapper.updateHeader(userId, headerUrl);
        int rows = userMapper.updateHeader(userId, headerUrl);//更新数据库和清理缓存。先更新数据库,后清理缓存,因为如果更新数据库失败了,就不用清理缓存了
        clearCache(userId);
        return rows;
    }

你可能感兴趣的:(项目:Java仿牛客网讨论区,redis,java,数据库)