一文迅速掌握minio、xxl-job、jave的使用
一篇文章迅速掌握自建文件床minio、分布式定时任务框架xxl-job、视频转码截屏框架jave、业务层实现多表查询mybatis-plus-join的使用
minio的使用
引入依赖
<dependency> <groupId>io.minio</groupId> <artifactId>minio</artifactId> </dependency> 版本号在项目父pom.xml中,后续依赖同理
配置地址和参数
minio: url: http://localhost:9000 access.name: minioadmin 默认值 access.secret: minioadmin 默认值
注册核心Bean
@Configuration public class MinioConfig { @Value("${minio.url}") private String endpoint; @Value("${minio.access.name}") private String accessKey; @Value("${minio.access.secret}") private String secretKey; @Bean public MinioClient minioClient() { try { 读取配置文件中的参数值,构造者模式注册Bean MinioClient minioClient = MinioClient.builder() .endpoint(endpoint) .credentials(accessKey, secretKey) .build(); return minioClient; } catch (Exception e){ e.printStackTrace(); } return null; } }
使用minio
创建桶
public Boolean createBucket(String name){ try { 查询桶是否存在,不存在则创建 boolean isExist = minioClient.bucketExists(BucketExistsArgs.builder().bucket(name).build()); if (!isExist) { 构造者模式创建桶 minioClient.makeBucket(MakeBucketArgs.builder().bucket(name).build()); } else { } } catch (Exception e) { } return true; }
删除桶
public Boolean deleteBucket(String name){ try { minioClient.removeBucket(RemoveBucketArgs.builder().bucket(name).build()); } catch (Exception e) { e.printStackTrace(); } return true; }
上传文件
public Boolean uploadVideoFile(String fileName, InputStream stream, String contentType) { try { String bucketName="video"; 构造者模式将文件名、桶名、文件类型、文件流构造成一个对象,然后上传该对象到minio minioClient.putObject( PutObjectArgs.builder().bucket(bucketName).object(fileName) .stream(stream, -1, 10485760) .contentType(contentType) .build()); stream.close(); return true; } catch (Exception e) { throw new RuntimeException("上传失败", e); } }
删除文件
public Boolean deleteBucket(String bucketName,String fileName){ try { minioClient.removeBucket(RemoveObjectArgs.builder().bucket(bucketName).object(fileName).build()); } catch (Exception e) { e.printStackTrace(); } return true; }
获取文件
public InputStream getObject(String objectName) { try { GetObjectArgs getObjectArgs = GetObjectArgs.builder() .bucket(bucketName) .object(objectName) .build(); return minioClient.getObject(getObjectArgs); } catch (Exception e) { log.error("错误:" + e.getMessage()); } return null; }
关于minio的使用有几点需要注意:
1.minio中同一个桶内不能有重名文件,类似windows系统一个文件夹下不能有重名文件,但windows会给重名文件中第二个上传的文件默认加后缀(1),而minio会直接覆盖前一个文件,因此有需求是上传同名文件时可以加前缀后缀如uuid区分开来
2.minio版本更新极快,因此不同的minio中间件版本和maven依赖版本组合的结果是不能百分百预测的,可能会出现未知bug,因此保险起见,最好不修改教程中的minio中间件版本和maven依赖,可以保证不出bug正常运行
xxl-job的使用
下载xxl-job执行器的管理器和执行器的jar包(https://labilibili.com/package/xxl-job)
修改xxl-job执行器配置
只需修改xxl-job连接数据库的用户名和密码
在本地数据库中创建xxl-job表
CREATE database if NOT EXISTS `xxl_job` default character set utf8mb4 collate utf8mb4_unicode_ci; use `xxl_job`; SET NAMES utf8mb4; 注意这段代码,这是适配Linux上的建表语句因为在Linux上才需要先指定用哪个数据库,若使用本机部署则可以把这段代码删掉,只需要在远程连接数据库软件比如dbeaver、navicat中先建数据库,然后在该数据库中新建sql编辑器,再将sql复制上去执行,若一次性执行有问题则需要一个表一个表的创建
启动xxl-job
该项目在idea中打开后上方默认就可以启动执行器管理器,启动后在浏览器地址栏输入http://localhost:8080/xxl-job-admin,自动重定向到登录页面
输入用户名admin和密码123456登录
执行器注册进管理器并执行任务
上方下载的执行器已改好配置,无需修改任何地方,在需要执行任务的方法上使用注解即可
@XxlJob("demoJobHandler") public void demoJobHandler() throws Exception { XxlJobHelper.log("XXL-JOB, Hello World."); for (int i = 0; i < 5; i++) { XxlJobHelper.log("beat at:" + i); TimeUnit.SECONDS.sleep(2); } // default success }
启动程序后在管理器中查看是否有注册进去
注册进去后在任务管理的某个任务右侧的操作按钮中选择执行一次
接下来的参数设置出于快速入门的目的不需要输入任何参数直接保存
在执行日志中选择某项任务右侧的操作,可查看日志
jave的使用
引入依赖
<dependency> <groupId>ws.schild</groupId> <artifactId>jave-all-deps</artifactId> </dependency>
转码和获取视频相关信息
给将被转码的视频文件流提供一个文件路径和名字 String filePath = Files.createTempDirectory(".tmp").toString(); String videoFileName="video"; File file = new File(filePath, videoFileName); 将视频文件流转移到该File对象中 Files.copy(videoInputStream,Paths.get(file.getAbsolutePath()), StandardCopyOption.REPLACE_EXISTING); 将该File对象封装进转码工具对象中 MultimediaObject multimediaObject = new MultimediaObject(file); 获取视频信息 VideoInfo videoInfo = multimediaObject.getInfo().getVideo(); 获取视频时长 Integer totalLength = Math.toIntExact(multimediaObject.getInfo().getDuration()) / 1000; String length; 根据时长值拼接最后的字符串形式时长 if (totalLength / 60 < 1) { if (totalLength % 60 < 10) { length = "00:0" + totalLength; } else { length = "00:" + totalLength; } } else { if (totalLength / 60 < 10) { if(totalLength%60<10){ length = "0" + totalLength / 60 + ":0" + totalLength % 60; }else { length = "0" + totalLength / 60 + ":" + totalLength % 60; } } else { length = totalLength / 60 + ":" + totalLength % 60; } } videoMapper.updateById(updateVideo.setLength(length)); String rightFormat = "h264"; 如果视频编码不是h264则浏览器播放不出来,需要转码 if (!rightFormat.equals(videoInfo.getDecoder())) { String contentType = "video/mp4"; String outPutForMatType = "mp4"; VideoAttributes videoAttributes = new VideoAttributes(); 创建转码后的结果文件 String targetFileName = "target"; File target = new File(filePath, targetFileName); 填装转码参数 videoAttributes.setCodec(rightFormat); AudioAttributes audio = new AudioAttributes(); EncodingAttributes attrs = new EncodingAttributes(); 设置转码后的输出格式为mp4 attrs.setOutputFormat(outPutForMatType); attrs.setAudioAttributes(audio); attrs.setVideoAttributes(videoAttributes); 创建核心转码工具类 Encoder encoder = new Encoder(); 执行转码 encoder.encode(multimediaObject, target, attrs); 获取转码后的结果文件的文件流 FileInputStream inputStream = new FileInputStream(target); 以video/mp4格式重新上传minio CustomMultipartFile customMultipartFile = new CustomMultipartFile(inputStream, uploadVideo.getUrl().substring(uploadVideo.getUrl().lastIndexOf("/") + 1)
截屏
public Result<String> getVideoCover(MultipartFile multipartFile) throws IOException, EncoderException { 创建截屏后的文件对象 String filePath = Files.createTempDirectory(".tmp").toString(); String coverFileName="coverFileName.jpg"; String videoFileName="video"; File videoFile=new File(filePath,videoFileName); File coverFile1 = new File(filePath, coverFileName); 将视频流封装进截屏需要用到工具类 Files.copy(multipartFile.getInputStream(), Paths.get(videoFile.getAbsolutePath()), StandardCopyOption.REPLACE_EXISTING); ScreenExtractor screenExtractor = new ScreenExtractor(); MultimediaObject multimediaObject = new MultimediaObject(videoFile); 截屏参数,可以修改1000的值为其他值,该值代表截屏视频第几秒的参数,其他的已设置好建议不要动,改参数输出结果暂时还未测试 screenExtractor.renderOneImage(multimediaObject, -1, -1, 1000, coverFile1, 1); InputStream inputStream1 = new FileInputStream(coverFile1); String img=Base64.encode(IoUtil.readBytes(inputStream1)); videoFile.delete(); coverFile1.delete(); return Result.data(img); }
mybatis-plus-join的使用
mybatis-plus-join是mybatis-plus的增强版,可在业务层实现联表查询,解决了mybatis-plus无法多表查询的问题(作者多次测试结论是该框架在复杂多表查询时还存在欠缺,简单的多表查询可以用,复杂的多表查询不建议),下方mybatis-plus用法有疑问的可以在我之前的教程中学习
引入依赖
<dependency> <groupId>com.github.yulichang</groupId> <artifactId>mybatis-plus-join-boot-starter</artifactId> </dependency>
在mybatis-plus基础上修改mapper写法
@Mapper 改为MpjBaseMapper,原有mybatis-plus单表增删改查功能不受影响 public interface PlayMapper extends MPJBaseMapper<Play> { }
业务中使用该mapper
创建mpj的wrapper,泛型需和mapper一致 MPJLambdaWrapper<Play> wrapper=new MPJLambdaWrapper<>(); 设置查询条件,同mybatis-plus wrapper.eq(Play::getUserId,userId); wrapper.orderByDesc(Play::getCreateTime); 选择查询后封装到的响应类中需要获取的表字段,若表对应的mybatis-plus实体类的java属性与响应类的字段一致则不需要第二个参数 wrapper.select(Play::getCreateTime); 否则需要特地指出如何映射 wrapper.selectAs(Video::getName, HistoryVideoResponse::getVideoName); wrapper.select(Video::getLength); wrapper.selectAs(Video::getId,HistoryVideoResponse::getVideoId); wrapper.selectAs(Video::getCover,HistoryVideoResponse::getVideoCover); wrapper.selectAs(User::getId,HistoryVideoResponse::getAuthorId); wrapper.selectAs(User::getCover,HistoryVideoResponse::getAuthorCover); wrapper.selectAs(User::getNickname, HistoryVideoResponse::getAuthorName); 核心表联接代码,innerJoin对应sql中内连接,leftjoin、rightjoin对应sql中左、右连接,第一二三个参数分别对应联接sql的参数,下方等效 play t inner join video t1 on video.id=play.video_id inner join user t2 on t2.id=t1.user_id,该框架按表出现顺序默认从t开始给表起别名,该特性在单表自连接查询和连接同一个其他表多次时会用到用于区分表 wrapper.innerJoin(Video.class,Video::getId,Play::getVideoId); wrapper.innerJoin(User.class, User::getId,Video::getUserId); List<HistoryVideoResponse> responses= playMapper.selectJoinList(HistoryVideoResponse.class,wrapper); return Result.data(responses); }
最后,宣传一下自己的仿b站前后端分离微服务项目,依赖版本号也在该项目的父pom.xml中
实现了以下功能:
视频的上传、查看与上传时获取封面
视频的点赞、评论、可同时新增和删除多个收藏记录的收藏、多功能的弹幕
用户的个人信息查看编辑、用户之间的关注
用户的个人主页权限修改、查看、由个人主页权限动态决定的用户个人主页内容的获取
手机号、邮箱、图形验证码的多种方式登录
支持临时会话的服务器为代理的一对一实时私聊
基于讯飞星火的文生文、文生图、(全网首发)智能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点个小星星♪(・ω・)ノ
#秋招##美团##腾讯##字节跳动##阿里巴巴#该专栏存放前后端分离仿b站微服务项目相关教程