交通局网站建设方案,wordpress 图片 并排,share群组链接分享,网站设计实例教程求分析好友功能是目前社交场景的必备功能之一#xff0c;一般好友相关的功能包含有#xff1a;关注/取关、我#xff08;他#xff09;的关注、我#xff08;他#xff09;的粉丝、共同关注、我关注的人也关注他等这样一些功能。类似于这样的功能我们如果采用数据库做的话…求分析好友功能是目前社交场景的必备功能之一一般好友相关的功能包含有关注/取关、我他的关注、我他的粉丝、共同关注、我关注的人也关注他等这样一些功能。类似于这样的功能我们如果采用数据库做的话只是单纯得到用户的一些粉丝或者关注列表的话是很简单也很容易实现, 但是如果我想要查出两个甚至多个用户共同关注了哪些人或者想要查询两个或者多个用户的共同粉丝的话就会很麻烦效率也不会很高。但是如果你用redis去做的话就会相当的简单而且效率很高。原因是redis自己本身带有专门针对于这种集合的交集、并集、差集的一些操作。设计思路总体思路我们采用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 …]--------共同关注、我关注的人关注了他数据库表设计这个数据库表的结构比较简单主要记录了用户id、用户关注的id和关注状态。CREATE TABLE t_follow ( id int(11) NOT NULL AUTO_INCREMENT, user_id int(11) DEFAULT NULL COMMENT 当前登录用户的id, follow_user_id int(11) DEFAULT NULL COMMENT 当前登录用户关注的用户的id, is_valid tinyint(1) DEFAULT NULL COMMENT 关注状态0-没有关注1-关注了, create_date datetime DEFAULT NULL, update_date datetime DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8 ROW_FORMATCOMPACT COMMENT用户和用户关注表;新建好友功能微服务添加依赖和配置pom依赖如下?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd parent artifactIdredis-seckill/artifactId groupIdcom.zjq/groupId version1.0-SNAPSHOT/version /parent modelVersion4.0.0/modelVersion artifactIdms-follow/artifactId dependencies !-- eureka client -- dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-starter-netflix-eureka-client/artifactId /dependency !-- spring web -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- mysql -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId /dependency !-- spring data redis -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency !-- mybatis -- dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId /dependency !-- commons 公共项目 -- dependency groupIdcom.zjq/groupId artifactIdcommons/artifactId version1.0-SNAPSHOT/version /dependency !-- swagger -- dependency groupIdcom.battcn/groupId artifactIdswagger-spring-boot-starter/artifactId /dependency /dependencies /projectspringboot配置如下server: port: 7004 # 端口 spring: application: name: ms-follow # 应用名 # 数据库 datasource: driver-class-name: com.mysql.cj.jdbc.Driver username: root password: root url: jdbc:mysql://127.0.0.1:3306/seckill?serverTimezoneAsia/ShanghaicharacterEncodingutf8useUnicodetrueuseSSLfalse # Redis redis: port: 6379 host: localhost timeout: 3000 password: 123456 database: 2 # Swagger swagger: base-package: com.zjq.follow title: 好用功能微服务API接口文档 # 配置 Eureka Server 注册中心 eureka: instance: prefer-ip-address: true instance-id: ${spring.cloud.client.ip-address}:${server.port} client: service-url: defaultZone: http://localhost:7000/eureka/ service: name: ms-oauth-server: http://ms-oauth2-server/ ms-diners-server: http://ms-users/ mybatis: configuration: map-underscore-to-camel-case: true # 开启驼峰映射 logging: pattern: console: %d{HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n添加配置类redis配置类package com.zjq.seckill.config; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; /** * RedisTemplate配置类 * author zjq */ Configuration public class RedisTemplateConfiguration { /** * redisTemplate 序列化使用的jdkSerializeable, 存储二进制字节码, 所以自定义序列化类 * * param redisConnectionFactory * return */ Bean public RedisTemplateObject, Object redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplateObject, 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; } }REST配置类关注/取关实现业务逻辑Mapper实现Mapper比较简单主要是查询关注信息、添加关注信息、取关或者再次关注。Service层实现package com.zjq.seckill.service; import cn.hutool.core.bean.BeanUtil; import com.zjq.commons.constant.ApiConstant; import com.zjq.commons.constant.RedisKeyConstant; import com.zjq.commons.exception.ParameterException; import com.zjq.commons.model.domain.ResultInfo; import com.zjq.commons.model.pojo.Follow; import com.zjq.commons.model.vo.SignInUserInfo; import com.zjq.commons.utils.AssertUtil; import com.zjq.commons.utils.ResultInfoUtil; import com.zjq.seckill.mapper.FollowMapper; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import javax.annotation.Resource; import java.util.LinkedHashMap; /** * 关注/取关业务逻辑层 * author zjq */ Service public class FollowService { Value(${service.name.ms-oauth-server}) private String oauthServerName; Value(${service.name.ms-diners-server}) private String dinersServerName; Resource private RestTemplate restTemplate; Resource private FollowMapper followMapper; Resource private RedisTemplate redisTemplate; /** * 关注/取关 * * param followUserId 关注的食客ID * param isFollowed 是否关注 1关注 0取关 * param accessToken 登录用户token * param path 访问地址 * return */ public ResultInfo follow(Integer followUserId, int isFollowed, String accessToken, String path) { // 是否选择了关注对象 AssertUtil.isTrue(followUserId null || followUserId 1, 请选择要关注的人); // 获取登录用户信息 (封装方法) SignInUserInfo dinerInfo loadSignInDinerInfo(accessToken); // 获取当前登录用户与需要关注用户的关注信息 Follow follow followMapper.selectFollow(dinerInfo.getId(), followUserId); // 如果没有关注信息且要进行关注操作 -- 添加关注 if (follow null isFollowed 1) { // 添加关注信息 int count followMapper.save(dinerInfo.getId(), followUserId); // 添加关注列表到 Redis if (count 1) { addToRedisSet(dinerInfo.getId(), followUserId); } return ResultInfoUtil.build(ApiConstant.SUCCESS_CODE, 关注成功, path, 关注成功); } // 如果有关注信息且目前处于关注状态且要进行取关操作 -- 取关关注 if (follow ! null follow.getIsValid() 1 isFollowed 0) { // 取关 int count followMapper.update(follow.getId(), isFollowed); // 移除 Redis 关注列表 if (count 1) { removeFromRedisSet(dinerInfo.getId(), followUserId); } return ResultInfoUtil.build(ApiConstant.SUCCESS_CODE, 成功取关, path, 成功取关); } // 如果有关注信息且目前处于取关状态且要进行关注操作 -- 重新关注 if (follow ! null follow.getIsValid() 0 isFollowed 1) { // 重新关注 int count followMapper.update(follow.getId(), isFollowed); // 添加关注列表到 Redis if (count 1) { addToRedisSet(dinerInfo.getId(), followUserId); } return ResultInfoUtil.build(ApiConstant.SUCCESS_CODE, 关注成功, path, 关注成功); } return ResultInfoUtil.buildSuccess(path, 操作成功); } /** * 添加关注列表到 Redis * * param dinerId * param followUserId */ private void addToRedisSet(Integer dinerId, Integer followUserId) { redisTemplate.opsForSet().add(RedisKeyConstant.following.getKey() dinerId, followUserId); redisTemplate.opsForSet().add(RedisKeyConstant.followers.getKey() followUserId, dinerId); } /** * 移除 Redis 关注列表 * * param dinerId * param followUserId */ private void removeFromRedisSet(Integer dinerId, Integer followUserId) { redisTemplate.opsForSet().remove(RedisKeyConstant.following.getKey() dinerId, followUserId); redisTemplate.opsForSet().remove(RedisKeyConstant.followers.getKey() followUserId, dinerId); } /** * 获取登录用户信息 * * param accessToken * return */ private SignInUserInfo 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()); } SignInUserInfo dinerInfo BeanUtil.fillBeanWithMap((LinkedHashMap) resultInfo.getData(), new SignInUserInfo(), false); return dinerInfo; } }Controller实现package com.zjq.seckill.controller; import com.zjq.commons.model.domain.ResultInfo; import com.zjq.seckill.service.FollowService; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; /** * 关注/取关控制层 * author zjq */ RestController public class FollowController { Resource private FollowService followService; Resource private HttpServletRequest request; /** * 关注/取关 * * param followUserId 关注的用户ID * param isFollowed 是否关注 1关注 0取消 * param access_token 登录用户token * return */ PostMapping(/{followUserId}) public ResultInfo follow(PathVariable Integer followUserId, RequestParam int isFollowed, String access_token) { ResultInfo resultInfo followService.follow(followUserId, isFollowed, access_token, request.getServletPath()); return resultInfo; } }网关配置路由规则spring: application: name: ms-gateway cloud: gateway: discovery: locator: enabled: true # 开启配置注册中心进行路由功能 lower-case-service-id: true # 将服务名称转小写 routes: # 好友功能微服务 - id: ms-follow uri: lb://ms-follow predicates: - Path/follow/** filters: - StripPrefix1测试验证依次启动注册中心、网关、认证中心、好友功能微服务。测试id为5的用户关注id为1的用户。查看redis可以看到有两个集合一个粉丝集合一个关注集合。查看数据库id为5的用户关注了id为1的用户共同关注列表从Redis中读取登录用户的关注列表与查看用户的关注列表然后进行交集操作获取共同关注的用户id然后通过用户服务传入用户id数据获取用户基本信息Controller添加方法/** * 共同关注列表 * * param userId * param access_token * return */ GetMapping(commons/{userId}) public ResultInfo findCommonsFriends(PathVariable Integer userId, String access_token) { return followService.findCommonsFriends(userId, access_token, request.getServletPath()); }Service添加方法/** * 共同关注列表 * * param userId * param accessToken * param path * return */ Transactional(rollbackFor Exception.class) public ResultInfo findCommonsFriends(Integer userId, String accessToken, String path) { // 是否选择了查看对象 AssertUtil.isTrue(userId null || userId 1, 请选择要查看的人); // 获取登录用户信息 SignInUserInfo userInfo loadSignInuserInfo(accessToken); // 获取登录用户的关注信息 String loginuserKey RedisKeyConstant.following.getKey() userInfo.getId(); // 获取登录用户查看对象的关注信息 String userKey RedisKeyConstant.following.getKey() userId; // 计算交集 SetInteger userIds redisTemplate.opsForSet().intersect(loginuserKey, userKey); // 没有 if (userIds null || userIds.isEmpty()) { return ResultInfoUtil.buildSuccess(path, new ArrayListShortUserInfo()); } // 调用食客服务根据 ids 查询食客信息 ResultInfo resultInfo restTemplate.getForObject(usersServerName findByIds?access_token{accessToken}ids{ids}, ResultInfo.class, accessToken, StrUtil.join(,, userIds)); if (resultInfo.getCode() ! ApiConstant.SUCCESS_CODE) { resultInfo.setPath(path); return resultInfo; } // 处理结果集 ListLinkedHashMap dinnerInfoMaps (ArrayList) resultInfo.getData(); ListShortUserInfo userInfos dinnerInfoMaps.stream() .map(user - BeanUtil.fillBeanWithMap(user, new ShortUserInfo(), true)) .collect(Collectors.toList()); return ResultInfoUtil.buildSuccess(path, userInfos); }用户服务新增根据ids查询用户集合Controller:/** * 根据 ids 查询用户信息 * * param ids * return */ GetMapping(findByIds) public ResultInfoListShortUserInfo findByIds(String ids) { ListShortUserInfo dinerInfos userService.findByIds(ids); return ResultInfoUtil.buildSuccess(request.getServletPath(), dinerInfos); }Service/** * 根据 ids 查询食客信息 * * param ids 主键 id多个以逗号分隔逗号之间不用空格 * return */ public ListShortUserInfo findByIds(String ids) { AssertUtil.isNotEmpty(ids); String[] idArr ids.split(,); ListShortUserInfo dinerInfos usersMapper.findByIds(idArr); return dinerInfos; }Mapper:/** * 根据 ID 集合查询多个食客信息 * param ids * return */ 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) ListShortUserInfo findByIds(Param(ids) String[] ids);上面测试已经让id5和7的用户关注了id为1的用户我们继续让id5的用户关注id为3的用户让id5、6、7的用户关注了id为2的用户。redis和数据库信息如下测试验证查询当前登录用户id为5和id为7的共同关注信息查询当前登录用户id为6和id为7的共同关注信息可以看出来5和7共同关注了1和26和7只共同关注了2符合预期。