一篇文章快速掌握redis、mp、es、rocketmq使用
仿b站前后端分离微服务项目,
实现了以下功能:
视频的上传、查看与上传时获取封面
视频的点赞、评论、可同时新增和删除多个收藏记录的收藏、多功能的弹幕
用户的个人信息查看编辑、用户之间的关注
用户的个人主页权限修改、查看、由个人主页权限动态决定的用户个人主页内容的获取
手机号、邮箱、图形验证码的多种方式登录
支持临时会话的服务器为代理的一对一实时私聊
基于讯飞星火的文生文、文生图、(全网首发)智能PPT
关注up动态视频、评论、点赞、私聊消息的生成与推送
基于es实现的视频和用户的聚合搜索、推荐视频
网关的路由和统一鉴权与授权
基于双token的七天内无感刷新token
防csrf、xss、抓包、恶意上传脚本攻击
统一处理异常和泛型封装响应体、自定义修改响应序列化值
简易的仿redis缓存读取与数据过期剔除实现
xxl-job+ redis+ rocketmq+ es+ 布隆过滤器的自定义es与mysql数据同步
slueth+zipkin的多服务间请求链路追踪
集中多服务日志到一个文件目录下与按需添加特定内容入日志
多服务的详细接口文档
项目地址LABiliBili,github地址GitHub - aigcbilibili/aigcbilibili: 仿bilibili前后端实现,演示地址https://labilibili.com/video/演示.mp4,如果大家觉得有帮助的话可以去github点个小星星♪(・ω・)ノ
下面是项目中redis、mybatis-plus、es、rocketmq的使用
(由于使用父子模块写法,所以子模块引入依赖的版本都在父pom.xml文件中查看,下方不再给出版本号)
redis的使用
准备工作
引入SpringBoot集成后的依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
配置连接地址
spring: redis: host:localhost:84 # password: 默认为空
在容器中注册bean
@Configuration public class RedisConfig { private final RedisConnectionFactory redisConnectionFactory; public RedisConfig(RedisConnectionFactory redisConnectionFactory) { this.redisConnectionFactory = redisConnectionFactory; } @Bean public <T> RedisTemplate<String, T> objectRedisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, T> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(factory); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new GenericFastJsonRedisSerializer()); return redisTemplate; } @Bean public RedisMessageListenerContainer redisMessageListenerContainer() { RedisMessageListenerContainer redisMessageListenerContainer = new RedisMessageListenerContainer(); redisMessageListenerContainer.setConnectionFactory(redisConnectionFactory); return redisMessageListenerContainer; } @Bean public RedisTemplate<String, byte[]> bytesRedisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<String, byte[]> template = new RedisTemplate<>(); template.setKeySerializer(RedisSerializer.string()); template.setValueSerializer(RedisSerializer.byteArray()); template.setHashKeySerializer(RedisSerializer.string()); template.setHashValueSerializer(RedisSerializer.byteArray()); template.setConnectionFactory(redisConnectionFactory); return template; } @Bean public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<String, String> template = new RedisTemplate<>(); template.setKeySerializer(RedisSerializer.string()); template.setValueSerializer(RedisSerializer.string()); template.setHashKeySerializer(RedisSerializer.string()); template.setHashValueSerializer(RedisSerializer.string()); template.setConnectionFactory(redisConnectionFactory); return template; } }
若要在项目代码基础上做修改需注意在项目代码中用的@Resource注解,而该注解默认是byName,这里的Name指的是配置类中标注了@Bean注解的方法的名字,如使用redis时键值类型都为String类型时则注入redisTemplate,分别为String和object类型时则使用objectRedisTemplate,否则项目运行不会报错但实际调用方法时会报与预期类型不匹配。
使用redis
引入依赖、编写配置文件、编写配置类后就可以在业务层注入需要的某个redisTemplate的Bean来使用了,spring-boot-starter-data-redis针对redis数据结构封装了两层,调用两层后才能针对redis中某种数据结构进行增删改查操作,以最常见的键值数据结构为例
redisTemplate.opsForValue().set(Object key,Object value); 存入一对键值对,默认永不超时 redisTemplate.opsForValue().set(Object key,Object value,long timeout,TimeUnit timeUnit); 存入一对键值对,在以传入的时间单位为单位的前提下过了timeout时间后过期 redisTemplate.opsForValue().get(Object key); 根据键名获取键值对的值
走到这一步已经可以自如使用redis了,而redis的存取方法有相当多的重载版本,可以根据需求去使用
mybatis-plus的自动填充值处理器、条件mapper的使用
引入依赖
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> </dependency>
按特定注解编写实体类
@Data @Accessors(chain = true) @TableName("chat_notice") public class Chat { @TableField("sender_id") private Integer senderId; @TableField("content") private String content; @TableField("receiver_id") private Integer receiverId; @TableField(value = "create_time",fill = FieldFill.INSERT) private LocalDateTime createTime; @TableId(type = IdType.AUTO) private Integer id; @TableField(value = "status",fill = FieldFill.INSERT) private Integer status; }
其中@TableField是一般字段,负责将数据库表的列名与Java对象属性名映射到一起,@TableId是id字段,不同type值决定了id增长策略,默认雪花算法,当前代码是设定为自增主键。@TableName标识该实体类对应数据库中的表。
自动填充值处理器的使用
package ljl.bilibili.handler; import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import org.apache.ibatis.reflection.MetaObject; import org.springframework.stereotype.Component; import java.time.LocalDateTime; import java.util.Date; import java.util.UUID; /** *自动填充处理器 */ @Component public class MyMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { this.setFieldValByName("cover", "https://labilibili.com/user-cover/default.png", metaObject); this.setFieldValByName("nickname", "新用户"+ UUID.randomUUID().toString().substring(0,10), metaObject); this.setFieldValByName("createTime", LocalDateTime.now(), metaObject); this.setFieldValByName("danmakuCount", 0, metaObject); this.setFieldValByName("playCo unt",0, metaObject); this.setFieldValByName("likeCount",0, metaObject); this.setFieldValByName("collectCount",0, metaObject); this.setFieldValByName("commentCount",0, metaObject); this.setFieldValByName("status",0,metaObject); this.setFieldValByName("collectGroup",0,metaObject); this.setFieldValByName("remotelyLike",0,metaObject); this.setFieldValByName("fansList",0,metaObject); this.setFieldValByName("idolList",0,metaObject); } @Override public void updateFill(MetaObject metaObject) { this.setFieldValByName("updateTime", LocalDateTime.now(), metaObject); } }
编写一个实现了MetaObjectHandler接口的处理器类并使用@Component注解注册到容器中,实现insertFill和updateFill实现新增和修改时自动修改值
@TableField(value = "create_time",fill = FieldFill.INSERT) private LocalDateTime createTime; this.setFieldValByName("createTime", LocalDateTime.now(), metaObject);
在需要自动更新的实体属性的@TableField注解中标注fill的类型,然后在实现的方法中将该属性字段名、自动赋予的值填进去,最后一个参数固定为传递进实现方法的参数metaObject。这里需注意自动赋予的值的类型必须和该实体属性的类型一致。
固定格式的mapper
@Mapper public interface CommentNoticeMapper extends BaseMapper<CommentNotice> { }
继承BaseMapper并将泛型的值限定为该Mapper对应操作的实体类的类名,在该Mapper类上标记@Mapper注解即可在业务层引入该Mapper后使用
增删改查操作的使用
前提都是在业务代码中引入了该Mapper
@Resource CommentNoticeMapper commentNoticeMapper;
查询
创建一个查询需要用到的封装查询条件wrapper LambdaQueryWrapper<CommentNotice> wrapper=new LambdaQueryWrapper<>(); 等价于where 列值(↑commentNotice实体对应的属性)=1 wrapper.eq(CommentNotice::getStatus,1); 等价于where 列值(↑commentNotice实体对应的属性)!=1 wrapper.ne(CommentNotice::getStatus,1); 等价于where 列值(↑commentNotice实体对应的属性)in 存id的集合 List<Integer> ids=new ArrayList<>(); wrapper.in(CommentNotice::getVideoId,ids); 等价于where 列值<1 int value=1; wrapper.le(CommentNotice::getStatus,value); 等价于where 列值>0 wrapper.ge(CommentNotice::getStatus,0); 等价于按status来升序排列 wrapper.orderByAsc(CommentNotice::getStatus); 等价于按status来降序排列 wrapper.orderByDesc(CommentNotice::getStatus); 等价于按status来分组 wrapper.groupBy(CommentNotice::getStatus); 等价于按status、videoid来分组 wrapper.groupBy(CommentNotice::getStatus,CommentNotice::getVideoId); 等价于senderName值like "%man" wrapper.like(CommentNotice::getSenderName,"man"); 按条件查询一个对象,注意该查询若查出记录不止一条会报错 CommentNotice commentNotice=commentNoticeMapper.selectOne(wrapper); 按条件查询一个对象集合 List<CommentNotice> commentNoticeList=commentNoticeMapper.selectList(wrapper); 根据id集合查询对象集合 List<CommentNotice> commentNoticeList1=commentNoticeMapper.selectBatchIds(ids); 根据id查询一个对象 CommentNotice commentNotices=commentNoticeMapper.selectById(1);
增加
插入一个对应Mapper泛型的实体类 commentNoticeMapper.insert(new CommentNotice()); 插入后该对象的id会自动赋值,有时可以利用该特性
删除
按条件删除 LambdaQueryWrapper<CommentNotice> wrapper=new LambdaQueryWrapper<>(); commentNoticeMapper.delete(wrapper); wrapper上文已提过,此处省略 List<Integer> ids=new ArrayList<>(); commentNoticeMapper.deleteBatchIds(ids); 根据id集合批量删除 commentNoticeMapper.deleteById(1); 根据实体对象中的id值删除 commentNoticeMapper.deleteById(new CommentNotice());
修改
创建一个用于更新值的wrapper LambdaUpdateWrapper<CommentNotice> wrapper=new LambdaUpdateWrapper<>(); 更新status的值为1 wrapper.set(CommentNotice::getStatus,1); 条件更新方法同查询,eq、ne、in等,不再赘述
es的使用
引入依赖
<dependency> <groupId>org.elasticsearch</groupId> <artifactId>elasticsearch</artifactId> </dependency> <dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>elasticsearch-rest-high-level-client</artifactId> </dependency>
配置连接地址
elasticsearch-client: hosts: localhost:9200
编写配置类
@Component @ConfigurationProperties(prefix = "elasticsearch-client") 用于读取配置文件中elasticsearch-client对应的值 public class ElasticsearchClientProperties { private List<String> hosts; private String username; private String password; public List<String> getHosts() { return hosts; } public void setHosts(List<String> hosts) { this.hosts = hosts; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } } @Configuration @Slf4j public class ElasticsearchClientConfig { 使用Properties类中读取的配置文件的属性 private final ElasticsearchClientProperties properties; public ElasticsearchClientConfig(ElasticsearchClientProperties properties) { this.properties = properties; } @ConditionalOnMissingBean(RestHighLevelClient.class) @Bean public RestHighLevelClient restHighLevelClient() { if(properties==null){ log.error("properties==null"); }else { List<String> list = properties.getHosts(); if(list.size()==0){ log.error("list==0"); } } HttpHost[] hosts = properties.getHosts().stream() .map(HttpHost::create).toArray(HttpHost[]::new); RestHighLevelClient client = new RestHighLevelClient( RestClient.builder(hosts) .setHttpClientConfigCallback(httpClientBuilder -> httpClientBuilder.setDefaultIOReactorConfig(IOReactorConfig.custom() .setIoThreadCount(8).build()) ) .setRequestConfigCallback(requestConfigBuilder -> requestConfigBuilder.setConnectTimeout(300000) .setSocketTimeout(300000) ) ); log.info("Connecting Elasticsearch..."); while (true) { try { if (client.ping(RequestOptions.DEFAULT)) { break; } } catch (Exception e) { log.warn("Connecting Elasticsearch Failed, Retry in 10 seconds..."); try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e1) { log.error("", e1); } } } log.info("Elasticsearch Connected!"); return client; } }
es的使用
es与mysql可以类比理解,es的索引类比mysql的表,es的列和列数据类型类似mysql表的列和列数据类型
使用前提条件是引入了resthighlevelclient对象
@Resource public RestHighLevelClient client;
创建索引
try { 填充索引列名和数据类型 String index = esIndexRequest.getIndexName(); Map<String, String> map = esIndexRequest.getProperties(); XContentBuilder builder = XContentFactory.jsonBuilder(); builder.startObject(); { builder.startObject(INDEX_START_OBJECT_MAPPINGS); { builder.startObject(INDEX_START_OBJECT_PROPERTIES); { // 添加其他字段 for (Map.Entry<String, String> entry : map.entrySet()) { builder.startObject(entry.getKey()); { builder.field(INDEX_FIELD_TYPE, entry.getValue()); } builder.endObject(); } } builder.endObject(); } builder.endObject(); } builder.endObject(); 创造创建索引对象 CreateIndexRequest request = new CreateIndexRequest(index); 将列名数据类型填充进创建索引对象 request.source(builder); client.indices().create(request, RequestOptions.DEFAULT); } catch (Exception e) { e.printStackTrace(); } return true; }
删除索引
创建删除索引对象 GetIndexRequest getIndexRequest = new GetIndexRequest(indexName); boolean exists = client.indices().exists(getIndexRequest, RequestOptions.DEFAULT); if (exists) { DeleteIndexRequest request = new DeleteIndexRequest(indexName); client.indices().delete(request, RequestOptions.DEFAULT); System.out.println("索引删除了"); } else { System.out.println("索引不存在"); } return true; }
查询文档
创建查询对象 SearchRequest searchRequest=new SearchRequest("video"); 创建搜索源 SearchSourceBuilder sourceBuilder=new SearchSourceBuilder(); 创建查询条件对象,例如匹配所有和精确匹配某些字段 MatchAllQueryBuilder matchAllQueryBuilder=new MatchAllQueryBuilder(); MultiMatchQueryBuilder multiMatchQueryBuilder=new MultiMatchQueryBuilder(); sourceBuilder.query(multiMatchQueryBuilder); sourceBuilder.query(matchAllQueryBuilder); 执行查询 client.search(searchRequest,RequestOptions.DEFAULT);
创建文档
绑定索引 IndexRequest indexRequest = new IndexRequest("video"); 对应请求中的map Map map; 添加新增源 indexRequest.source(map, XContentType.JSON); client.index(indexRequest, RequestOptions.DEFAULT);
删除文档
创建删除文档对象 DeleteByQueryRequest deleteRequest = new DeleteByQueryRequest(“video”); 绑定id到要被删除文档,id类型为String deleteRequest.id(id); client.delete(deleteRequest, RequestOptions.DEFAULT);
更新文档
绑定索引 UpdateRequest updateRequest= new IndexRequest().index("video").id("3"); 对应请求中的值的map Map map; 将要被更新的值绑定进去 updateRequest.doc(map); 添加修改源 updateRequest.source(map, XContentType.JSON); client.index(updateRequest, RequestOptions.DEFAULT); map可以理解为一个对象,修改文档值就类似把这个传入的map也就是传入的对象的值一一复刻到文档中去
批量操作文档
IndexRequest indexRequest=new IndexRequest("video"); indexRequest.source(map); UpdateRequest updateRequest=new UpdateRequest().index("video").id("3"); updateRequest.doc(map); 创建批量操作对象 BulkRequest bulkRequest=new BulkRequest(); 添加操作,可在一个批量操作对象中添加多个不同类型的操作 bulkRequest.add(indexRequest); bulkRequest.add(updateRequest); 执行批量操作 client.bulk(bulkRequest, RequestOptions.DEFAULT);
注意,indexRequest其实也可以用作更新,无id时创建新的文档,有id时更新现有文档,和updateRequest的区别在于indexRequest会覆盖原有值为空,比如indexRequest要操作的文档有3个列name,id,cover,若只有name和id有值那么更新原有文档时会将原文档的cover值覆盖为null,但使用updateRequest便不会出现这种情况。
rocketmq的使用
引入依赖
<dependency> <groupId>org.apache.rocketmq</groupId> <artifactId>rocketmq-spring-boot-starter</artifactId> </dependency>
配置地址
rocketmq: name-server: localhost:9876 producer: group: test
rocketmq-spring-boot-starter已经封装好了核心类RocketmqTemplate,因此无需编写配置类即可使用该类。
发送消息
主题名称 String topic="video-encode"; 使用Springboot自带Jackson将对象json化 ObjectMapper objectMapper=new ObjectMapper(); String jsonMessage=objectMapper.writeValueAsString(uploadVideo); 异步发送,可以利用到回调函数检测发送情况 rocketMQTemplate.asyncSend(topic, jsonMessage, new SendCallback() { @Override public void onSuccess(SendResult sendResult) { log.info("Rocket发射成功"); } @Override public void onException(Throwable throwable) { throwable.getMessage(); log.error("坠机:"); } });
消费消息
@Service @RocketMQMessageListener( 主题名 topic = "comment", 消费所在组 consumerGroup = "comment-group", 消费模式,有并发型和按消息发送顺序传递型 consumeMode = ConsumeMode.CONCURRENTLY ) public class CommentConsumer implements RocketMQListener<MessageExt> { @Resource CommentNoticeMapper commentNoticeMapper; @Resource ObjectMapper objectMapper; @Override public void onMessage(MessageExt messageExt) { 发送的json消息经过rocketmq内部流通变成MessageExt,再转成json对象 String json = new String(messageExt.getBody(), StandardCharsets.UTF_8); 使用Jackson反序列化成自定义对象 LikeNotice likeNotice = objectMapper.readValue(json, LikeNoticeAddOrDelete.class);
到此,redis、mybatis-plus、es、rocketmq的常见使用文档就都已完成♪(・ω・)ノ
#25届暑期实习##25届秋招提前批##美团##腾讯##最后再改一次简历#该专栏存放前后端分离仿b站微服务项目相关教程