redis相关链接:官网,github
1.reids默认有16个库,从0-15,可以使用下边的语句切换
select [index]
2.刷新数据库FLUSHDB命令,如下
3.String类型的演示
set key value [EX seconds] [PX milliseconds] [NX|XX]
get key
incr key
decr key
4.hash类型数据
hset key field value
hget key field
5.list类型数据
lpush key value [value ...]
llen key
lindex key index
lrange key start stop
lpop key
rpush key value [value ...]
rpop key
6.set类型数据
sadd key member [member ...]
scard key //统计集合中有多少元素,输出个数
spop key [count] //随机从集合中弹出数据
smembers key //相比于scard来说,输出set中所有的member
7.sortset类型数据
zadd key [NX|XX] [CH] [INCR] score member [score member ...]
zcard key
zscore key member
zrank key member //返回排名
zrange key start stop [WITHSCORES]
8.常用命令
keys *
type key
exists key [key ...]
del key
expire key seconds
日常操作你懂得
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
因为RedisTemplate是
RedisConfig 配置类
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){
RedisTemplate<String,Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
//设置key的序列化方式
template.setKeySerializer(RedisSerializer.string());
//设置value的序列化方式
template.setValueSerializer(RedisSerializer.json());
//设置hash的key序列化方式
template.setHashKeySerializer(RedisSerializer.string());
//设置hash的value的序列化方式
template.setHashValueSerializer(RedisSerializer.json());
template.afterPropertiesSet();
return template;
}
}
1.写一个测试类
@SpringBootTest
@ContextConfiguration(classes = CommunityApplication.class)
public class RedisTest {
@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
@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());
}
//声明式事务作用于整个方法,方法中需要查询时就不合适了,所以演示编程式事务
// 编程式事务
@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");
//显示为[],所以一定不要在redis事务中做查询。
System.out.println(operations.opsForSet().members(redisKey));
return operations.exec();
}
});
//这里会显示所有的操作结果
System.out.println(obj);
}
}
public class RedisUtil {
private static final String SPLIT=":";
private static final String PREFIX_ENTITY_LIKE = "like:entity";
//某个实体的赞
//key长这个样:like:entity:entityType:entityId
//value是个set:存的是用户id,为了统计谁给我点了赞
public static String getEntityLikeKey(int entityType,int entityId){
return PREFIX_ENTITY_LIKE+SPLIT+entityType+SPLIT+entityId;
}
}
@Service
public class LikeService {
@Autowired
private RedisTemplate redisTemplate;
//点赞
public void like(int userId,int entityType,int entityId){
String entityLikeKey = RedisUtil.getEntityLikeKey(entityType,entityId);
//第一次点是点赞,第二次点是取消赞
//先判断是否点过赞 value是set集合存的是userId
Boolean isMember = redisTemplate.opsForSet().isMember(entityLikeKey, userId);
if(isMember){
//说明点过赞,这次是取消赞
redisTemplate.opsForSet().remove(entityLikeKey,userId);
}else{
//说明是第一次点赞
redisTemplate.opsForSet().add(entityLikeKey,userId);
}
}
//查询实体点赞的数量
public long findEntityLikeCount(int entityType,int entityId){
String entityLikeKey = RedisUtil.getEntityLikeKey(entityType,entityId);
return redisTemplate.opsForSet().size(entityLikeKey);
}
//查询某人对某实体的点赞状态
//返回int 是为了以后业务扩展 比如点了踩啥的记录状态
public int findEntityLikeStatus(int userId,int entityType,int entityId){
String entityLikeKey = RedisUtil.getEntityLikeKey(entityType,entityId);
return redisTemplate.opsForSet().isMember(entityLikeKey,userId)?1:0;
}
}
@Controller
public class LikeController {
@Autowired
private LikeService likeService;
@Autowired
private HostHolder hostHolder;
@RequestMapping(path = "/like",method = RequestMethod.POST)
@ResponseBody
public String like(int entityType,int entityId){
User user = hostHolder.getUser();
//实现点赞
likeService.like(user.getId(),entityType,entityId);
//统计数量
long likeCount = likeService.findEntityLikeCount(entityType,entityId);
//状态
int likeStatus = likeService.findEntityLikeStatus(user.getId(),entityType,entityId);
Map<String,Object> map = new HashMap<>();
map.put("likeCount",likeCount);
map.put("likeStatus",likeStatus);
return CommunityUtil.getJsonString(0,null,map);
}
}
处理页面
2.评论点赞
function like(btn,entityType,entityId) {
$.post(
CONTEXT_PATH+"/like",
{"entityType":entityType,"entityId":entityId},
function (data) {
/*转化成json*/
data = $.parseJSON(data);
if(data.code==0){
$(btn).children("i").text(data.likeCount);
$(btn).children("b").text(data.likeStatus==1?"已赞":"赞");
}else{
alert(data.msg);
}
}
);
}
1.首页上点赞数量的显示
HomeController中getIndexPage方法
处理index.html
2.帖子详情·页面上点赞数量的显示
页面处理
1.在RedisUtil中加一个key
2.LikeService增加一个操作记录用户获得点赞的数量
并用编程式事务完成。想想为啥不用声明式事务—代码中有答案
//点赞
public void like(int userId,int entityType,int entityId,int entityUserId){ //entityUserId 就是被点赞的user的Id
/* String entityLikeKey = RedisUtil.getEntityLikeKey(entityType,entityId);
//第一次点是点赞,第二次点是取消赞
//先判断是否点过赞 value是set集合存的是userId
Boolean isMember = redisTemplate.opsForSet().isMember(entityLikeKey, userId);
if(isMember){
//说明点过赞,这次是取消赞
redisTemplate.opsForSet().remove(entityLikeKey,userId);
}else{
//说明是第一次点赞
redisTemplate.opsForSet().add(entityLikeKey,userId);
}*/
//编程式事务
redisTemplate.execute(new SessionCallback() {
@Override
public Object execute(RedisOperations redisOperations) throws DataAccessException {
String entityLikeKey = RedisUtil.getEntityLikeKey(entityType,entityId);
String userLikeKey = RedisUtil.getUserLikeKey(entityUserId);
//判断当前用户有没有点赞,这一步应该在事务开启前执行,因为在事务中的查询不会立即得到结果
Boolean isMember = redisOperations.opsForSet().isMember(entityLikeKey, userId);
//事务开启
redisOperations.multi();
if(isMember){
//说明点过赞,这次是取消赞
redisTemplate.opsForSet().remove(entityLikeKey,userId);
//被点赞的用户点赞数量减一
redisOperations.opsForValue().decrement(userLikeKey);
}else{
//说明是第一次点赞
redisTemplate.opsForSet().add(entityLikeKey,userId);
//被点赞的用户点赞数量加一
redisOperations.opsForValue().increment(userLikeKey);
}
return redisOperations.exec();
}
});
}
3.LikeService增加查询某个用户获得赞的数量
4.重构表现层
detail页面
js
1.UserController增加找用户页面的方法
@Autowired
private LikeService likeService;
//个人主页
@RequestMapping(path = "/profile/{userId}",method = RequestMethod.GET)
public String getProfilePage(@PathVariable("userId")int userId,Model model){
User user = userService.findUserById(userId);
if(user==null){
throw new RuntimeException("该用户不存在");
}
//用户
model.addAttribute("user",user);
int likeCount = likeService.findUserLikeCount(user.getId());
model.addAttribute("likeCount",likeCount);
return "/site/profile";
}
2.处理index.html中的链接
所有用户头像也得加链接
这里只处理了首页上的,其他的暂时没有处理。
3.处理profile.html
@Service
public class FollowService {
@Autowired
private RedisTemplate redisTemplate;
//关注
public void follow(int userId,int entityType,int entityId){
//还得依靠事务解决,因为有多次操作
redisTemplate.execute(new SessionCallback() {
@Override
public Object execute(RedisOperations redisOperations) throws DataAccessException {
String followee = RedisUtil.getFolloweeKey(userId,entityType);
String follower = RedisUtil.getFollowerKey(entityType,entityId);
//启用事务
redisOperations.multi();
//userId关注entityId
redisOperations.opsForZSet().add(followee,entityId,System.currentTimeMillis());
//entityId的粉丝是userId
redisOperations.opsForZSet().add(follower,userId,System.currentTimeMillis());
return redisOperations.exec();
}
});
}
//取消关注
public void unFollow(int userId,int entityType,int entityId){
//还得依靠事务解决,因为有多次操作
redisTemplate.execute(new SessionCallback() {
@Override
public Object execute(RedisOperations redisOperations) throws DataAccessException {
String followee = RedisUtil.getFolloweeKey(userId,entityType);
String follower = RedisUtil.getFollowerKey(entityType,entityId);
//启用事务
redisOperations.multi();
//userId没有关注谁
redisOperations.opsForZSet().remove(followee,entityId);
//谁的粉丝没有userId
redisOperations.opsForZSet().remove(follower,userId);
return redisOperations.exec();
}
});
}
//查询关注的实体的数量
public long findFolloweeCount(int userId,int entityType){
String followee = RedisUtil.getFolloweeKey(userId,entityType);
return redisTemplate.opsForZSet().zCard(followee);
}
//查询实体的粉丝数量
public long findFollowerCount(int entityType,int entityId){
String follower = RedisUtil.getFollowerKey(entityType,entityId);
return redisTemplate.opsForZSet().zCard(follower);
}
//查询当前用户是否关注该实体
public boolean hasFollowed(int userId,int entityType,int entityId){
String followee = RedisUtil.getFolloweeKey(userId,entityType);
return redisTemplate.opsForZSet().score(followee,entityId)!=null?true:false;
}
}
FollowController
@Controller
public class FollowController {
@Autowired
private FollowService followService;
@Autowired
private HostHolder hostHolder;
@RequestMapping(path = "/follow",method = RequestMethod.POST)
@ResponseBody
public String follow(int entityType,int entityId){
User user = hostHolder.getUser();
if(user==null){
throw new RuntimeException("用户没有登录");
}
followService.follow(user.getId(),entityType,entityId);
return CommunityUtil.getJsonString(0,"已关注");
}
@RequestMapping(path = "/unfollow",method = RequestMethod.POST)
@ResponseBody
public String unfollow(int entityType,int entityId){
User user = hostHolder.getUser();
if(user==null){
throw new RuntimeException("用户没有登录");
}
followService.unFollow(user.getId(),entityType,entityId);
return CommunityUtil.getJsonString(0,"已取消关注");
}
}
页面处理
因为各种地方都可以关注,此处只演示在用户主页关注人,其他大同小异
继续改成这样,处理关注按钮
在profile.js中写处理逻辑
$(function(){
$(".follow-btn").click(follow);
});
function follow() {
var btn = this;
if($(btn).hasClass("btn-info")) {
// 关注TA
$.post(
CONTEXT_PATH+"/follow",
{"entityType":4,"entityId":$(btn).prev().val()},
function (data) {
data = $.parseJSON(data);
if(data.code==0){
window.location.reload();
}else{
alert(data.msg)
}
}
)
} else {
// 取消关注
$.post(
CONTEXT_PATH+"/unfollow",
{"entityType":4,"entityId":$(btn).prev().val()},
function (data) {
data = $.parseJSON(data);
if(data.code==0){
window.location.reload();
}else{
alert(data.msg)
}
}
)
//$(btn).text("关注TA").removeClass("btn-secondary").addClass("btn-info");
}
}
FollowService中增加方法
@Autowired
private UserService userService;
//查询某个用户关注的人
public List<Map<String,Object>> findFollowees(int userId,int offset,int limit){
String followee = RedisUtil.getFolloweeKey(userId,ENTITY_USER);
Set<Integer> targetIds = redisTemplate.opsForZSet().reverseRange(followee, offset, offset + limit - 1);
if(targetIds==null){
return null;
}
List<Map<String,Object>> list = new ArrayList<>();
for(Integer id:targetIds){
Map<String,Object> map = new HashMap<>();
User user = userService.findUserById(id);
//用户
map.put("user",user);
Double score = redisTemplate.opsForZSet().score(followee, id);
//关注时间
map.put("followTime",new Date(score.longValue()));
list.add(map);
}
return list;
}
//查询某个用户的粉丝
public List<Map<String,Object>> findFollowers(int userId,int offset,int limit){
String follower = RedisUtil.getFollowerKey(ENTITY_USER,userId);
//虽然返回的是set但是是redis内置实现了一个set可以有序排列
Set<Integer> targetIds = redisTemplate.opsForZSet().reverseRange(follower, offset, offset + limit - 1);
if(targetIds==null){
return null;
}
List<Map<String,Object>> list = new ArrayList<>();
for(Integer id:targetIds){
Map<String,Object> map = new HashMap<>();
User user = userService.findUserById(id);
//用户
map.put("user",user);
Double score = redisTemplate.opsForZSet().score(follower, id);
//关注时间
map.put("followTime",new Date(score.longValue()));
list.add(map);
}
return list;
}
1.某个用户关注了谁—FollowController增加如下方法
@Autowired
private UserService userService;
@RequestMapping(path="/followees/{userId}",method = RequestMethod.GET)
public String getFollowees(@PathVariable("userId")int userId, Page page, Model model){
//页面需要用username
User user = userService.findUserById(userId);
if(user==null){
throw new RuntimeException("该用户不存在");
}
model.addAttribute("user",user);
page.setLimit(5);
page.setPath("/followees/"+userId);
page.setRows((int)followService.findFolloweeCount(userId,ENTITY_USER));
List<Map<String, Object>> followees = followService.findFollowees(userId, page.getOffset(), page.getLimit());
if(followees!=null){
for(Map<String,Object> map:followees){
//判段当前用户对这个用户的关注状态
User followeeUser = (User)map.get("user");
boolean hasFollowed = hasFollowed(followeeUser.getId());
map.put("hasFollowed",hasFollowed);
}
}
model.addAttribute("users",followees);
return "/site/followee";
}
private boolean hasFollowed(int userId){
if(hostHolder.getUser()==null){
return false;
}
return followService.hasFollowed(hostHolder.getUser().getId(),CommunityContant.ENTITY_USER,userId);
}
2.某个用户的粉丝
@RequestMapping(path="/followers/{userId}",method = RequestMethod.GET)
public String getFollowers(@PathVariable("userId")int userId, Page page, Model model){
//页面需要用username
User user = userService.findUserById(userId);
if(user==null){
throw new RuntimeException("该用户不存在");
}
model.addAttribute("user",user);
page.setLimit(5);
page.setPath("/followers/"+userId);
page.setRows((int)followService.findFollowerCount(ENTITY_USER,userId));
List<Map<String, Object>> followers = followService.findFollowers(userId, page.getOffset(), page.getLimit());
if(followers!=null){
for(Map<String,Object> map:followers){
//判段当前用户对这个用户的关注状态
User followeeUser = (User)map.get("user");
boolean hasFollowed = hasFollowed(followeeUser.getId());
map.put("hasFollowed",hasFollowed);
}
}
model.addAttribute("users",followers);
return "/site/follower";
}
1.profile.html
2.followee.html
3.follower.html
同理,仔细点尤其是处理已关注未关注按钮那里
最初,我们把验证码存在了session里,这样不好。使用Redis存验证码的好处:
1.RedisUtil中增加存储验证码的key
2.LoginController里方法
修改getKaptcha方法
修改Login方法
最初,我们把登陆凭证存到了MySql里,每次都需要频繁的查询,因为设置了拦截器查询登陆状态。替换用login_ticket表存数据
1.RedisUtil中定义key
2.LoginTicketMapper加@Deprecated注解表名不推荐使用
3.重构代码主要集中在UserService和LoginService中
1.LoginService中login方法
2.LoginService中logout方法
3.UserService中findLoginTicket方法
就是查询用户的时候先从Redis中取,没有的话先从数据库中取然后存到redis中。用户状态变化时直接删除Redis中的数据。
1.RedisUtil中增加key
2.在UserService中封装三个方法:
3.其他涉及查询User的地方调这三个方法
当然还有其他可以修改的