最新消息: USBMI致力于为网友们分享Windows、安卓、IOS等主流手机系统相关的资讯以及评测、同时提供相关教程、应用、软件下载等服务。

Redis实战

IT圈 admin 2浏览 0评论

Redis实战

文章目录

    • 1. 环境搭建
      • 1. 需求分析
      • 2. 设计思路
      • 3. 数据库表结构
      • 4. 创建子模块 ms-follow 服务
      • 5. 配置类 RedisTemplateConfiguration
      • 6. 配置类 RestTemplateConfiguration
    • 2. 关注取关功能
      • 1. 业务逻辑
      • 2. 关注/取关控制层 FollowController
      • 3. 关注/取关业务逻辑层 FollowService
      • 4. 关注/取关 FollowMapper
      • 5. 官网配置 ms-gateway
      • 6. 启动项目测试
    • 3. 共同关注列表
      • 1. 关注/取关控制层 FollowController
      • 2. 关注/取关业务逻辑层 FollowService
      • 3. 食客服务查询用户信息 ms-diners
        • 1. 关注食客信息 ShortDinerInfo
        • 2. 食客服务控制层 DinersController
        • 3. 食客服务业务逻辑层 DinersService
        • 4. 食客 DinersMapper
        • 5. 访问测试
      • 4. 启动项目测试

1. 环境搭建

1. 需求分析

好友功能是目前社交场景的必备功能之一,一般好友相关的功能包含有:关注/取关、我(他)的关注、我(他)的粉丝、共同关注、我关注的人也关注他等这样一些功能。

类似于这样的功能我们如果采用数据库做的话只是单纯得到用户的一些粉丝或者关注列表的话是很简单也很容易实现, 但是如果我想要查出两个甚至多个用户共同关注了哪些人或者想要查询两个或者多个用户的共同粉丝的话就会很麻烦, 效率也不会很高。但是如果你用redis去做的话就会相当的简单而且效率很高。原因是redis自己本身带有专门针对于这种集合的交集、并集、差集的一些操作。

2. 设计思路

总体思路我们采用MySQL + Redis的方式结合完成。MySQL主要是保存落地数据,而利用Redis的Sets进行集合操作。Sets拥有去重(我们不能多次关注同一用户)功能。一个用户我们存贮两个集合:一个是保存用户关注的人 另一个是保存关注用户的人。

  • SADD 添加成员;命令格式: SADD key member [member …] ----- 关注
  • SREM 移除某个成员;命令格式: SREM key member [member …] -------取关
  • SCARD 统计集合内的成员数;命令格式: SCARD key -------关注/粉丝个数
  • SISMEMBER 判断是否是集合成员;命令格式:SISMEMBER key member ---------判断是否关注(如果关注那么只可以点击取关)
  • SMEMBERS 查询集合内的成员;命令格式: SMEMBERS key -------列表使用(关注列表和粉丝列表)
  • SINTER 查询集合的交集;命令格式: SINTER key [key …] --------共同关注、我关注的人关注了他

3. 数据库表结构

CREATE TABLE `t_follow` (
`id`  int(11) NOT NULL AUTO_INCREMENT ,
`diner_id`  int(11) NULL DEFAULT NULL COMMENT '当前登录用户的id' ,
`follow_diner_id`  int(11) NULL DEFAULT NULL COMMENT '当前登录用户关注的用户的id' ,
`is_valid`  tinyint(1) NULL DEFAULT NULL COMMENT '关注状态,0-没有关注,1-关注了' ,
`create_date`  datetime NULL DEFAULT NULL ,
`update_date`  datetime NULL DEFAULT NULL ,
PRIMARY KEY (`id`)
)ENGINE=InnoDB
DEFAULT CHARACTER SET=utf8 COLLATE=utf8_general_ci
AUTO_INCREMENT=6
ROW_FORMAT=COMPACT;

4. 创建子模块 ms-follow 服务

① 导入依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns=".0.0"xmlns:xsi=""xsi:schemaLocation=".0.0 .0.0.xsd"><parent><artifactId>food-social-contact-parent</artifactId><groupId>com.imooc</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>ms-follow</artifactId><dependencies><!-- eureka client --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency><!-- spring web --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- mysql --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><!-- spring data redis --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- mybatis --><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId></dependency><!-- commons 公共项目 --><dependency><groupId>com.imooc</groupId><artifactId>commons</artifactId><version>1.0-SNAPSHOT</version></dependency><!-- swagger --><dependency><groupId>com.battcn</groupId><artifactId>swagger-spring-boot-starter</artifactId></dependency></dependencies>
</project>

② SpringBoot 配置:

server:port: 8084 # 端口spring:application:name: ms-follow # 应用名# 数据库datasource:driver-class-name: com.mysql.cj.jdbc.Driverusername: rootpassword: rooturl: jdbc:mysql://127.0.0.1:3306/orgnization?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useUnicode=true&useSSL=false# Redisredis:port: 6379host: 192.168.38.22timeout: 3000database: 1# swaggerswagger:base-package: com.hh.followtitle: 美食社交食客API接口文档# 配置 Eureka Server 注册中心
eureka:instance:prefer-ip-address: trueinstance-id: ${spring.cloud.client.ip-address}:${server.port}client:service-url:defaultZone: http://localhost:8080/eureka/service:name:ms-oauth-server: http://ms-oauth2-server/ms-diners-server: http://ms-diners/mybatis:configuration:map-underscore-to-camel-case: true # 开启驼峰映射logging:pattern:console: '%d{HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n'

5. 配置类 RedisTemplateConfiguration

package com.hh.follow.config;@Configuration
public class RedisTemplateConfiguration {/*** redisTemplate 序列化使用的jdkSerializeable, 存储二进制字节码, 所以自定义序列化类*/@Beanpublic RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();redisTemplate.setConnectionFactory(redisConnectionFactory);// 使用Jackson2JsonRedisSerialize 替换默认序列化Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);ObjectMapper objectMapper = new ObjectMapper();objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);jackson2JsonRedisSerializer.setObjectMapper(objectMapper);// 设置key和value的序列化规则redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);redisTemplate.setKeySerializer(new StringRedisSerializer());redisTemplate.setHashKeySerializer(new StringRedisSerializer());redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);redisTemplate.afterPropertiesSet();return redisTemplate;}
}

6. 配置类 RestTemplateConfiguration

@Configuration
public class RestTemplateConfiguration {@LoadBalanced@Beanpublic RestTemplate restTemplate() {RestTemplate restTemplate = new RestTemplate();MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();converter.setSupportedMediaTypes(Collections.singletonList(MediaType.TEXT_PLAIN));restTemplate.getMessageConverters().add(converter);return restTemplate;}
}

2. 关注取关功能

1. 业务逻辑

2. 关注/取关控制层 FollowController

package com.hh.follow.controller;/*** 关注/取关控制层*/
@RestController
public class FollowController {@Resourceprivate FollowService followService;@Resourceprivate HttpServletRequest request;/*** 关注/取关** @param followDinerId 关注的食客ID* @param isFollowed    是否关注 1=关注 0=取消* @param access_token  登录用户token*/@PostMapping("/{followDinerId}")public ResultInfo follow(@PathVariable Integer followDinerId,@RequestParam int isFollowed,String access_token) {ResultInfo resultInfo = followService.follow(followDinerId,isFollowed, access_token, request.getServletPath());return resultInfo;}
}

3. 关注/取关业务逻辑层 FollowService

package com.hh.follow.service;/*** 关注/取关业务逻辑层*/
@Service
public class FollowService {@Value("${service.name.ms-oauth-server}")private String oauthServerName;@Value("${service.name.ms-diners-server}")private String dinersServerName;@Resourceprivate RestTemplate restTemplate;@Resourceprivate FollowMapper followMapper;@Resourceprivate RedisTemplate redisTemplate;/*** 关注/取关** @param followDinerId 关注的食客ID* @param isFoolowed    是否关注 1=关注 0=取关* @param accessToken   登录用户token* @param path          访问地址*/public ResultInfo follow(Integer followDinerId, int isFoolowed,String accessToken, String path) {// 是否选择了关注对象AssertUtil.isTrue(followDinerId == null || followDinerId < 1, "请选择要关注的人");// 获取登录用户信息 (封装方法)SignInDinerInfo dinerInfo = loadSignInDinerInfo(accessToken);// 获取当前登录用户与需要关注用户的关注信息Follow follow = followMapper.selectFollow(dinerInfo.getId(), followDinerId);// 如果没有关注信息,且要进行关注操作 -- 添加关注if (follow == null && isFoolowed == 1) {// 添加关注信息int count = followMapper.save(dinerInfo.getId(), followDinerId);// 添加关注列表到 Redisif (count == 1) {addToRedisSet(dinerInfo.getId(), followDinerId);}return ResultInfoUtil.build(ApiConstant.SUCCESS_CODE, "关注成功", path, "关注成功");}// 如果有关注信息,且目前处于关注状态,且要进行取关操作 -- 取关关注if (follow != null && follow.getIsValid() == 1 && isFoolowed == 0) {// 取关int count = followMapper.update(follow.getId(), isFoolowed);// 移除 Redis 关注列表if (count == 1) {removeFromRedisSet(dinerInfo.getId(), followDinerId);}return ResultInfoUtil.build(ApiConstant.SUCCESS_CODE, "成功取关", path, "成功取关");}// 如果有关注信息,且目前处于取关状态,且要进行关注操作 -- 重新关注if (follow != null && follow.getIsValid() == 0 && isFoolowed == 1) {// 重新关注int count = followMapper.update(follow.getId(), isFoolowed);// 添加关注列表到 Redisif (count == 1) {addToRedisSet(dinerInfo.getId(), followDinerId);}return ResultInfoUtil.build(ApiConstant.SUCCESS_CODE, "关注成功", path, "关注成功");}return ResultInfoUtil.buildSuccess(path, "操作成功");}/*** 添加关注列表到 Redis** @param dinerId 登录用户* @param followDinerId 要关注的用户*/private void addToRedisSet(Integer dinerId, Integer followDinerId) {// 将关注的用户添加到set集合中redisTemplate.opsForSet().add(RedisKeyConstant.following.getKey() + dinerId, followDinerId);redisTemplate.opsForSet().add(RedisKeyConstant.followers.getKey() + followDinerId, dinerId);}/*** 移除 Redis 关注列表** @param dinerId 登录用户* @param followDinerId 要关注的用户*/private void removeFromRedisSet(Integer dinerId, Integer followDinerId) {// 将取消关注的用户从set集合中移除redisTemplate.opsForSet().remove(RedisKeyConstant.following.getKey() + dinerId, followDinerId);redisTemplate.opsForSet().remove(RedisKeyConstant.followers.getKey() + followDinerId, dinerId);}/*** 获取登录用户信息** @param accessToken 登录用户的token*/private SignInDinerInfo loadSignInDinerInfo(String accessToken) {// 必须登录AssertUtil.mustLogin(accessToken);String url = oauthServerName + "user/me?access_token={accessToken}";ResultInfo resultInfo = restTemplate.getForObject(url, ResultInfo.class, accessToken);if (resultInfo.getCode() != ApiConstant.SUCCESS_CODE) {throw new ParameterException(resultInfo.getMessage());}SignInDinerInfo dinerInfo = BeanUtil.fillBeanWithMap((LinkedHashMap) resultInfo.getData(),new SignInDinerInfo(), false);return dinerInfo;}
}
public class AssertUtil {/*** 必须登录*/public static void mustLogin(String accessToken) {if (StrUtil.isBlank(accessToken)) {throw new ParameterException(ApiConstant.NO_LOGIN_CODE, ApiConstant.NO_LOGIN_MESSAGE);}}
}

4. 关注/取关 FollowMapper

package com.hh.follow.mapper;/*** 关注/取关 Mapper*/
public interface FollowMapper {// 查询关注信息@Select("select id, diner_id, follow_diner_id, is_valid from t_follow " +"where diner_id = #{dinerId} and follow_diner_id = #{followDinerId}")Follow selectFollow(@Param("dinerId") Integer dinerId, @Param("followDinerId") Integer followDinerId);// 添加关注信息@Insert("insert into t_follow (diner_id, follow_diner_id, is_valid, create_date, update_date)" +" values(#{dinerId}, #{followDinerId}, 1, now(), now())")int save(@Param("dinerId") Integer dinerId, @Param("followDinerId") Integer followDinerId);// 修改关注信息@Update("update t_follow set is_valid = #{isFollowed}, update_date = now() where id = #{id}")int update(@Param("id") Integer id, @Param("isFollowed") int isFollowed);
}

5. 官网配置 ms-gateway

spring:application:name: ms-gatewaycloud:gateway:discovery:locator:enabled: true # 开启配置注册中心进行路由功能lower-case-service-id: true # 将服务名称转小写routes:- id: ms-followuri: lb://ms-followpredicates:- Path=/follow/**filters:- StripPrefix=1

6. 启动项目测试

① 根据登录用户的access_token获取当前登录用户的信息:id=10

② 让登录用户关注 id=1的用户:

③ 让 id=11 的登录用户关注id=1,id=3,id=4的用户

根据登录用户的access_token获取当前登录用户的信息:id=11

让登录用户关注 id=1 的用户:

让登录用户关注 id=2 的用户:

让登录用户关注 id=3 的用户:

数据库中的关注信息:

redis中的关注信息:

3. 共同关注列表

  • 从Redis中读取登录用户的关注列表与查看用户的关注列表,然后进行交集操作,获取共同关注的用户id
  • 然后通过食客服务传入用户id数据获取用户基本信息

1. 关注/取关控制层 FollowController

/*** 关注/取关控制层*/
@RestController
public class FollowController {@Resourceprivate FollowService followService;@Resourceprivate HttpServletRequest request;/*** 共同关注列表*/@GetMapping("commons/{dinerId}")public ResultInfo findCommonsFriends(@PathVariable Integer dinerId,String access_token) {return followService.findCommonsFriends(dinerId, access_token, request.getServletPath());}
}

2. 关注/取关业务逻辑层 FollowService

package com.hh.follow.service;/*** 关注/取关业务逻辑层*/
@Service
public class FollowService {@Value("${service.name.ms-oauth-server}")private String oauthServerName;@Value("${service.name.ms-diners-server}")private String dinersServerName;@Resourceprivate RestTemplate restTemplate;@Resourceprivate FollowMapper followMapper;@Resourceprivate RedisTemplate redisTemplate;/*** 共同关注列表*/@Transactional(rollbackFor = Exception.class)public ResultInfo findCommonsFriends(Integer dinerId, String accessToken, String path) {// 是否选择了查看对象AssertUtil.isTrue(dinerId == null || dinerId < 1,"请选择要查看的人");// 获取登录用户信息SignInDinerInfo dinerInfo = loadSignInDinerInfo(accessToken);// 获取登录用户的关注信息String loginDinerKey = RedisKeyConstant.following.getKey() + dinerInfo.getId();// 获取登录用户查看对象的关注信息String dinerKey = RedisKeyConstant.following.getKey() + dinerId;// 计算交集Set<Integer> dinerIds = redisTemplate.opsForSet().intersect(loginDinerKey, dinerKey);// 没有交集if (dinerIds == null || dinerIds.isEmpty()) {return ResultInfoUtil.buildSuccess(path, new ArrayList<ShortDinerInfo>());}// 调用食客服务根据 ids 查询食客信息ResultInfo resultInfo = restTemplate.getForObject(dinersServerName + "findByIds?access_token={accessToken}&ids={ids}",ResultInfo.class, accessToken, StrUtil.join(",", dinerIds));if (resultInfo.getCode() != ApiConstant.SUCCESS_CODE) {resultInfo.setPath(path);return resultInfo;}// 处理结果集List<LinkedHashMap> dinnerInfoMaps = (ArrayList) resultInfo.getData();List<ShortDinerInfo> dinerInfos = dinnerInfoMaps.stream().map(diner -> BeanUtil.fillBeanWithMap(diner, new ShortDinerInfo(), true)).collect(Collectors.toList());return ResultInfoUtil.buildSuccess(path, dinerInfos);}/*** 获取登录用户信息** @param accessToken 登录用户的token*/private SignInDinerInfo loadSignInDinerInfo(String accessToken) {// 必须登录AssertUtil.mustLogin(accessToken);String url = oauthServerName + "user/me?access_token={accessToken}";ResultInfo resultInfo = restTemplate.getForObject(url, ResultInfo.class, accessToken);if (resultInfo.getCode() != ApiConstant.SUCCESS_CODE) {throw new ParameterException(resultInfo.getMessage());}SignInDinerInfo dinerInfo = BeanUtil.fillBeanWithMap((LinkedHashMap) resultInfo.getData(),new SignInDinerInfo(), false);return dinerInfo;}
}

3. 食客服务查询用户信息 ms-diners

1. 关注食客信息 ShortDinerInfo

@Getter
@Setter
@ApiModel(description = "关注食客信息")
public class ShortDinerInfo implements Serializable {@ApiModelProperty("主键")public Integer id;@ApiModelProperty("昵称")private String nickname;@ApiModelProperty("头像")private String avatarUrl;}

2. 食客服务控制层 DinersController

package com.hh.diners.controller;/*** 食客服务控制层*/
@RestController
@Api(tags = "食客相关接口")
public class DinersController {@Resourceprivate DinersService dinersService;@Resourceprivate HttpServletRequest request;/*** 根据 ids 查询食客信息*/@GetMapping("findByIds")public ResultInfo<List<ShortDinerInfo>> findByIds(String ids) {List<ShortDinerInfo> dinerInfos = dinersService.findByIds(ids);return ResultInfoUtil.buildSuccess(request.getServletPath(), dinerInfos);}
}

3. 食客服务业务逻辑层 DinersService

package com.hh.diners.service;/*** 食客服务业务逻辑层*/
@Service
public class DinersService {@Resourceprivate DinersMapper dinersMapper;/*** 根据 ids 查询食客信息** @param ids 主键 id,多个以逗号分隔,逗号之间不用空格* @return*/public List<ShortDinerInfo> findByIds(String ids) {AssertUtil.isNotEmpty(ids);String[] idArr = ids.split(",");List<ShortDinerInfo> dinerInfos = dinersMapper.findByIds(idArr);return dinerInfos;}
}

4. 食客 DinersMapper

package com.hh.diners.mapper;/*** 食客 Mapper*/
public interface DinersMapper {// 根据 ID 集合查询多个食客信息@Select("<script> " +" select id, nickname, avatar_url from t_diners " +" where is_valid = 1 and id in " +" <foreach item=\"id\" collection=\"ids\" open=\"(\" separator=\",\" close=\")\"> " +"   #{id} " +" </foreach> " +" </script>")List<ShortDinerInfo> findByIds(@Param("ids") String[] ids);
}

5. 访问测试

http://localhost/diners/findByIds?ids=10&ids=11&ids=12&access_token=8363c8ea-d359-4cc7-ae00-77738f29fb17

4. 启动项目测试

http://localhost/follow/commons/11?access_token=539e933c-1626-435e-bb8b-38613d3e7acd

查询当前登录用户id=10 和 查看的用户 id=11的共同好友,可以看到共同关注为 id=1

从数据库中可以看出共同含有为id=1的食客用户:

Redis实战

文章目录

    • 1. 环境搭建
      • 1. 需求分析
      • 2. 设计思路
      • 3. 数据库表结构
      • 4. 创建子模块 ms-follow 服务
      • 5. 配置类 RedisTemplateConfiguration
      • 6. 配置类 RestTemplateConfiguration
    • 2. 关注取关功能
      • 1. 业务逻辑
      • 2. 关注/取关控制层 FollowController
      • 3. 关注/取关业务逻辑层 FollowService
      • 4. 关注/取关 FollowMapper
      • 5. 官网配置 ms-gateway
      • 6. 启动项目测试
    • 3. 共同关注列表
      • 1. 关注/取关控制层 FollowController
      • 2. 关注/取关业务逻辑层 FollowService
      • 3. 食客服务查询用户信息 ms-diners
        • 1. 关注食客信息 ShortDinerInfo
        • 2. 食客服务控制层 DinersController
        • 3. 食客服务业务逻辑层 DinersService
        • 4. 食客 DinersMapper
        • 5. 访问测试
      • 4. 启动项目测试

1. 环境搭建

1. 需求分析

好友功能是目前社交场景的必备功能之一,一般好友相关的功能包含有:关注/取关、我(他)的关注、我(他)的粉丝、共同关注、我关注的人也关注他等这样一些功能。

类似于这样的功能我们如果采用数据库做的话只是单纯得到用户的一些粉丝或者关注列表的话是很简单也很容易实现, 但是如果我想要查出两个甚至多个用户共同关注了哪些人或者想要查询两个或者多个用户的共同粉丝的话就会很麻烦, 效率也不会很高。但是如果你用redis去做的话就会相当的简单而且效率很高。原因是redis自己本身带有专门针对于这种集合的交集、并集、差集的一些操作。

2. 设计思路

总体思路我们采用MySQL + Redis的方式结合完成。MySQL主要是保存落地数据,而利用Redis的Sets进行集合操作。Sets拥有去重(我们不能多次关注同一用户)功能。一个用户我们存贮两个集合:一个是保存用户关注的人 另一个是保存关注用户的人。

  • SADD 添加成员;命令格式: SADD key member [member …] ----- 关注
  • SREM 移除某个成员;命令格式: SREM key member [member …] -------取关
  • SCARD 统计集合内的成员数;命令格式: SCARD key -------关注/粉丝个数
  • SISMEMBER 判断是否是集合成员;命令格式:SISMEMBER key member ---------判断是否关注(如果关注那么只可以点击取关)
  • SMEMBERS 查询集合内的成员;命令格式: SMEMBERS key -------列表使用(关注列表和粉丝列表)
  • SINTER 查询集合的交集;命令格式: SINTER key [key …] --------共同关注、我关注的人关注了他

3. 数据库表结构

CREATE TABLE `t_follow` (
`id`  int(11) NOT NULL AUTO_INCREMENT ,
`diner_id`  int(11) NULL DEFAULT NULL COMMENT '当前登录用户的id' ,
`follow_diner_id`  int(11) NULL DEFAULT NULL COMMENT '当前登录用户关注的用户的id' ,
`is_valid`  tinyint(1) NULL DEFAULT NULL COMMENT '关注状态,0-没有关注,1-关注了' ,
`create_date`  datetime NULL DEFAULT NULL ,
`update_date`  datetime NULL DEFAULT NULL ,
PRIMARY KEY (`id`)
)ENGINE=InnoDB
DEFAULT CHARACTER SET=utf8 COLLATE=utf8_general_ci
AUTO_INCREMENT=6
ROW_FORMAT=COMPACT;

4. 创建子模块 ms-follow 服务

① 导入依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns=".0.0"xmlns:xsi=""xsi:schemaLocation=".0.0 .0.0.xsd"><parent><artifactId>food-social-contact-parent</artifactId><groupId>com.imooc</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>ms-follow</artifactId><dependencies><!-- eureka client --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency><!-- spring web --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- mysql --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><!-- spring data redis --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- mybatis --><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId></dependency><!-- commons 公共项目 --><dependency><groupId>com.imooc</groupId><artifactId>commons</artifactId><version>1.0-SNAPSHOT</version></dependency><!-- swagger --><dependency><groupId>com.battcn</groupId><artifactId>swagger-spring-boot-starter</artifactId></dependency></dependencies>
</project>

② SpringBoot 配置:

server:port: 8084 # 端口spring:application:name: ms-follow # 应用名# 数据库datasource:driver-class-name: com.mysql.cj.jdbc.Driverusername: rootpassword: rooturl: jdbc:mysql://127.0.0.1:3306/orgnization?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useUnicode=true&useSSL=false# Redisredis:port: 6379host: 192.168.38.22timeout: 3000database: 1# swaggerswagger:base-package: com.hh.followtitle: 美食社交食客API接口文档# 配置 Eureka Server 注册中心
eureka:instance:prefer-ip-address: trueinstance-id: ${spring.cloud.client.ip-address}:${server.port}client:service-url:defaultZone: http://localhost:8080/eureka/service:name:ms-oauth-server: http://ms-oauth2-server/ms-diners-server: http://ms-diners/mybatis:configuration:map-underscore-to-camel-case: true # 开启驼峰映射logging:pattern:console: '%d{HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n'

5. 配置类 RedisTemplateConfiguration

package com.hh.follow.config;@Configuration
public class RedisTemplateConfiguration {/*** redisTemplate 序列化使用的jdkSerializeable, 存储二进制字节码, 所以自定义序列化类*/@Beanpublic RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();redisTemplate.setConnectionFactory(redisConnectionFactory);// 使用Jackson2JsonRedisSerialize 替换默认序列化Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);ObjectMapper objectMapper = new ObjectMapper();objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);jackson2JsonRedisSerializer.setObjectMapper(objectMapper);// 设置key和value的序列化规则redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);redisTemplate.setKeySerializer(new StringRedisSerializer());redisTemplate.setHashKeySerializer(new StringRedisSerializer());redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);redisTemplate.afterPropertiesSet();return redisTemplate;}
}

6. 配置类 RestTemplateConfiguration

@Configuration
public class RestTemplateConfiguration {@LoadBalanced@Beanpublic RestTemplate restTemplate() {RestTemplate restTemplate = new RestTemplate();MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();converter.setSupportedMediaTypes(Collections.singletonList(MediaType.TEXT_PLAIN));restTemplate.getMessageConverters().add(converter);return restTemplate;}
}

2. 关注取关功能

1. 业务逻辑

2. 关注/取关控制层 FollowController

package com.hh.follow.controller;/*** 关注/取关控制层*/
@RestController
public class FollowController {@Resourceprivate FollowService followService;@Resourceprivate HttpServletRequest request;/*** 关注/取关** @param followDinerId 关注的食客ID* @param isFollowed    是否关注 1=关注 0=取消* @param access_token  登录用户token*/@PostMapping("/{followDinerId}")public ResultInfo follow(@PathVariable Integer followDinerId,@RequestParam int isFollowed,String access_token) {ResultInfo resultInfo = followService.follow(followDinerId,isFollowed, access_token, request.getServletPath());return resultInfo;}
}

3. 关注/取关业务逻辑层 FollowService

package com.hh.follow.service;/*** 关注/取关业务逻辑层*/
@Service
public class FollowService {@Value("${service.name.ms-oauth-server}")private String oauthServerName;@Value("${service.name.ms-diners-server}")private String dinersServerName;@Resourceprivate RestTemplate restTemplate;@Resourceprivate FollowMapper followMapper;@Resourceprivate RedisTemplate redisTemplate;/*** 关注/取关** @param followDinerId 关注的食客ID* @param isFoolowed    是否关注 1=关注 0=取关* @param accessToken   登录用户token* @param path          访问地址*/public ResultInfo follow(Integer followDinerId, int isFoolowed,String accessToken, String path) {// 是否选择了关注对象AssertUtil.isTrue(followDinerId == null || followDinerId < 1, "请选择要关注的人");// 获取登录用户信息 (封装方法)SignInDinerInfo dinerInfo = loadSignInDinerInfo(accessToken);// 获取当前登录用户与需要关注用户的关注信息Follow follow = followMapper.selectFollow(dinerInfo.getId(), followDinerId);// 如果没有关注信息,且要进行关注操作 -- 添加关注if (follow == null && isFoolowed == 1) {// 添加关注信息int count = followMapper.save(dinerInfo.getId(), followDinerId);// 添加关注列表到 Redisif (count == 1) {addToRedisSet(dinerInfo.getId(), followDinerId);}return ResultInfoUtil.build(ApiConstant.SUCCESS_CODE, "关注成功", path, "关注成功");}// 如果有关注信息,且目前处于关注状态,且要进行取关操作 -- 取关关注if (follow != null && follow.getIsValid() == 1 && isFoolowed == 0) {// 取关int count = followMapper.update(follow.getId(), isFoolowed);// 移除 Redis 关注列表if (count == 1) {removeFromRedisSet(dinerInfo.getId(), followDinerId);}return ResultInfoUtil.build(ApiConstant.SUCCESS_CODE, "成功取关", path, "成功取关");}// 如果有关注信息,且目前处于取关状态,且要进行关注操作 -- 重新关注if (follow != null && follow.getIsValid() == 0 && isFoolowed == 1) {// 重新关注int count = followMapper.update(follow.getId(), isFoolowed);// 添加关注列表到 Redisif (count == 1) {addToRedisSet(dinerInfo.getId(), followDinerId);}return ResultInfoUtil.build(ApiConstant.SUCCESS_CODE, "关注成功", path, "关注成功");}return ResultInfoUtil.buildSuccess(path, "操作成功");}/*** 添加关注列表到 Redis** @param dinerId 登录用户* @param followDinerId 要关注的用户*/private void addToRedisSet(Integer dinerId, Integer followDinerId) {// 将关注的用户添加到set集合中redisTemplate.opsForSet().add(RedisKeyConstant.following.getKey() + dinerId, followDinerId);redisTemplate.opsForSet().add(RedisKeyConstant.followers.getKey() + followDinerId, dinerId);}/*** 移除 Redis 关注列表** @param dinerId 登录用户* @param followDinerId 要关注的用户*/private void removeFromRedisSet(Integer dinerId, Integer followDinerId) {// 将取消关注的用户从set集合中移除redisTemplate.opsForSet().remove(RedisKeyConstant.following.getKey() + dinerId, followDinerId);redisTemplate.opsForSet().remove(RedisKeyConstant.followers.getKey() + followDinerId, dinerId);}/*** 获取登录用户信息** @param accessToken 登录用户的token*/private SignInDinerInfo loadSignInDinerInfo(String accessToken) {// 必须登录AssertUtil.mustLogin(accessToken);String url = oauthServerName + "user/me?access_token={accessToken}";ResultInfo resultInfo = restTemplate.getForObject(url, ResultInfo.class, accessToken);if (resultInfo.getCode() != ApiConstant.SUCCESS_CODE) {throw new ParameterException(resultInfo.getMessage());}SignInDinerInfo dinerInfo = BeanUtil.fillBeanWithMap((LinkedHashMap) resultInfo.getData(),new SignInDinerInfo(), false);return dinerInfo;}
}
public class AssertUtil {/*** 必须登录*/public static void mustLogin(String accessToken) {if (StrUtil.isBlank(accessToken)) {throw new ParameterException(ApiConstant.NO_LOGIN_CODE, ApiConstant.NO_LOGIN_MESSAGE);}}
}

4. 关注/取关 FollowMapper

package com.hh.follow.mapper;/*** 关注/取关 Mapper*/
public interface FollowMapper {// 查询关注信息@Select("select id, diner_id, follow_diner_id, is_valid from t_follow " +"where diner_id = #{dinerId} and follow_diner_id = #{followDinerId}")Follow selectFollow(@Param("dinerId") Integer dinerId, @Param("followDinerId") Integer followDinerId);// 添加关注信息@Insert("insert into t_follow (diner_id, follow_diner_id, is_valid, create_date, update_date)" +" values(#{dinerId}, #{followDinerId}, 1, now(), now())")int save(@Param("dinerId") Integer dinerId, @Param("followDinerId") Integer followDinerId);// 修改关注信息@Update("update t_follow set is_valid = #{isFollowed}, update_date = now() where id = #{id}")int update(@Param("id") Integer id, @Param("isFollowed") int isFollowed);
}

5. 官网配置 ms-gateway

spring:application:name: ms-gatewaycloud:gateway:discovery:locator:enabled: true # 开启配置注册中心进行路由功能lower-case-service-id: true # 将服务名称转小写routes:- id: ms-followuri: lb://ms-followpredicates:- Path=/follow/**filters:- StripPrefix=1

6. 启动项目测试

① 根据登录用户的access_token获取当前登录用户的信息:id=10

② 让登录用户关注 id=1的用户:

③ 让 id=11 的登录用户关注id=1,id=3,id=4的用户

根据登录用户的access_token获取当前登录用户的信息:id=11

让登录用户关注 id=1 的用户:

让登录用户关注 id=2 的用户:

让登录用户关注 id=3 的用户:

数据库中的关注信息:

redis中的关注信息:

3. 共同关注列表

  • 从Redis中读取登录用户的关注列表与查看用户的关注列表,然后进行交集操作,获取共同关注的用户id
  • 然后通过食客服务传入用户id数据获取用户基本信息

1. 关注/取关控制层 FollowController

/*** 关注/取关控制层*/
@RestController
public class FollowController {@Resourceprivate FollowService followService;@Resourceprivate HttpServletRequest request;/*** 共同关注列表*/@GetMapping("commons/{dinerId}")public ResultInfo findCommonsFriends(@PathVariable Integer dinerId,String access_token) {return followService.findCommonsFriends(dinerId, access_token, request.getServletPath());}
}

2. 关注/取关业务逻辑层 FollowService

package com.hh.follow.service;/*** 关注/取关业务逻辑层*/
@Service
public class FollowService {@Value("${service.name.ms-oauth-server}")private String oauthServerName;@Value("${service.name.ms-diners-server}")private String dinersServerName;@Resourceprivate RestTemplate restTemplate;@Resourceprivate FollowMapper followMapper;@Resourceprivate RedisTemplate redisTemplate;/*** 共同关注列表*/@Transactional(rollbackFor = Exception.class)public ResultInfo findCommonsFriends(Integer dinerId, String accessToken, String path) {// 是否选择了查看对象AssertUtil.isTrue(dinerId == null || dinerId < 1,"请选择要查看的人");// 获取登录用户信息SignInDinerInfo dinerInfo = loadSignInDinerInfo(accessToken);// 获取登录用户的关注信息String loginDinerKey = RedisKeyConstant.following.getKey() + dinerInfo.getId();// 获取登录用户查看对象的关注信息String dinerKey = RedisKeyConstant.following.getKey() + dinerId;// 计算交集Set<Integer> dinerIds = redisTemplate.opsForSet().intersect(loginDinerKey, dinerKey);// 没有交集if (dinerIds == null || dinerIds.isEmpty()) {return ResultInfoUtil.buildSuccess(path, new ArrayList<ShortDinerInfo>());}// 调用食客服务根据 ids 查询食客信息ResultInfo resultInfo = restTemplate.getForObject(dinersServerName + "findByIds?access_token={accessToken}&ids={ids}",ResultInfo.class, accessToken, StrUtil.join(",", dinerIds));if (resultInfo.getCode() != ApiConstant.SUCCESS_CODE) {resultInfo.setPath(path);return resultInfo;}// 处理结果集List<LinkedHashMap> dinnerInfoMaps = (ArrayList) resultInfo.getData();List<ShortDinerInfo> dinerInfos = dinnerInfoMaps.stream().map(diner -> BeanUtil.fillBeanWithMap(diner, new ShortDinerInfo(), true)).collect(Collectors.toList());return ResultInfoUtil.buildSuccess(path, dinerInfos);}/*** 获取登录用户信息** @param accessToken 登录用户的token*/private SignInDinerInfo loadSignInDinerInfo(String accessToken) {// 必须登录AssertUtil.mustLogin(accessToken);String url = oauthServerName + "user/me?access_token={accessToken}";ResultInfo resultInfo = restTemplate.getForObject(url, ResultInfo.class, accessToken);if (resultInfo.getCode() != ApiConstant.SUCCESS_CODE) {throw new ParameterException(resultInfo.getMessage());}SignInDinerInfo dinerInfo = BeanUtil.fillBeanWithMap((LinkedHashMap) resultInfo.getData(),new SignInDinerInfo(), false);return dinerInfo;}
}

3. 食客服务查询用户信息 ms-diners

1. 关注食客信息 ShortDinerInfo

@Getter
@Setter
@ApiModel(description = "关注食客信息")
public class ShortDinerInfo implements Serializable {@ApiModelProperty("主键")public Integer id;@ApiModelProperty("昵称")private String nickname;@ApiModelProperty("头像")private String avatarUrl;}

2. 食客服务控制层 DinersController

package com.hh.diners.controller;/*** 食客服务控制层*/
@RestController
@Api(tags = "食客相关接口")
public class DinersController {@Resourceprivate DinersService dinersService;@Resourceprivate HttpServletRequest request;/*** 根据 ids 查询食客信息*/@GetMapping("findByIds")public ResultInfo<List<ShortDinerInfo>> findByIds(String ids) {List<ShortDinerInfo> dinerInfos = dinersService.findByIds(ids);return ResultInfoUtil.buildSuccess(request.getServletPath(), dinerInfos);}
}

3. 食客服务业务逻辑层 DinersService

package com.hh.diners.service;/*** 食客服务业务逻辑层*/
@Service
public class DinersService {@Resourceprivate DinersMapper dinersMapper;/*** 根据 ids 查询食客信息** @param ids 主键 id,多个以逗号分隔,逗号之间不用空格* @return*/public List<ShortDinerInfo> findByIds(String ids) {AssertUtil.isNotEmpty(ids);String[] idArr = ids.split(",");List<ShortDinerInfo> dinerInfos = dinersMapper.findByIds(idArr);return dinerInfos;}
}

4. 食客 DinersMapper

package com.hh.diners.mapper;/*** 食客 Mapper*/
public interface DinersMapper {// 根据 ID 集合查询多个食客信息@Select("<script> " +" select id, nickname, avatar_url from t_diners " +" where is_valid = 1 and id in " +" <foreach item=\"id\" collection=\"ids\" open=\"(\" separator=\",\" close=\")\"> " +"   #{id} " +" </foreach> " +" </script>")List<ShortDinerInfo> findByIds(@Param("ids") String[] ids);
}

5. 访问测试

http://localhost/diners/findByIds?ids=10&ids=11&ids=12&access_token=8363c8ea-d359-4cc7-ae00-77738f29fb17

4. 启动项目测试

http://localhost/follow/commons/11?access_token=539e933c-1626-435e-bb8b-38613d3e7acd

查询当前登录用户id=10 和 查看的用户 id=11的共同好友,可以看到共同关注为 id=1

从数据库中可以看出共同含有为id=1的食客用户:

与本文相关的文章

发布评论

评论列表 (0)

  1. 暂无评论