文件上传

将文件上传到redis服务器,断点续传

1.文件上传的到redis里的流程图在这里插入图片描述
2.基本流程
将一个视频,分为大小为4M大小的视频块,将视频块上传到到队列服务器,此次上传的视频块是没有任何排序的,然后将队列中的视频块放到redis服务器中,redis服务器用来排序,在传送到redis中可以不用考虑了丢东西,redis排序完后再传送给队列服务器,此次队列服务器中的视频块是完整的,中间会进行视频的校验,校验的依据就是md5的值,如果md5的值和之前是相等的,说明视频没有丢字节,说明视频是完整的,然后存储到i指定路径中
1.创建视频:2.传入视频元数据3.循环上传视频

/**
	 package cn.tedu.controller;

import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.util.List;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

import cn.tedu.entity.Course;
import cn.tedu.entity.User;
import cn.tedu.entity.Video;
import cn.tedu.util.UUIDUtil;
import cn.tedu.util.UploadFileUtil;
import cn.tedu.util.UrlUtil;
import cn.tedu.vo.ReceiveInfo;
import cn.tedu.vo.UploadInfo;
import cn.tedu.vo.VideoBlockMessage;

@Controller
@RequestMapping("/video")
public class UploadController {
	
	@Autowired
	private UrlUtil urlUtil;
	
	@Resource(name="amqpTemplate")
	private AmqpTemplate amqpTemplate;
	@Resource(name="redisTemplate")
	private RedisTemplate<Serializable,Serializable> redisTemplate;
	
	
	/**
	 * 1.创建视频
	 * @param userId    用户的id    uuid
	 * @param format    默认是json
	 * @param request   
	 * @param response
	 * @return   UploadInfo对象
	 */
	@RequestMapping(value="/create",method=RequestMethod.POST)
	@ResponseBody
	public UploadInfo videoCreate(
			String userId,
			String format,
			HttpServletRequest request,
			HttpServletResponse response) {
		System.out.println(userId+"   "+format);
		UploadInfo uploadInfo=new  UploadInfo();
		//开始创建视频
		if(userId==null || "".equals(userId) || userId.length()!=36) {
			uploadInfo.setMsgerror("用户不合法");
			return uploadInfo;
		}
		//能执行到此处说明用户的id是合法的
		uploadInfo.setUserId(userId);
		uploadInfo.setVideoId(UUIDUtil.getUUID());
		uploadInfo.setMetaurl(this.urlUtil.getMetaurl());
		uploadInfo.setChunkurl(this.urlUtil.getChunkurl());
		uploadInfo.setMsgerror(null);
		return uploadInfo;
	}
	
	//2.上传视频的元数据,即视频的基本信息
	@RequestMapping(value="/uploadmeta",method=RequestMethod.POST)
	@ResponseBody
	public ReceiveInfo videoUploadMeta(
			HttpServletRequest request,
			HttpServletResponse response,
			String userId,
			String videoId,
			String videoTitle,
			String videoTag,
			String videoDescription,
			String videoFileName,
			String videoFileSize,
			String courseId,
			String md5,
			String format,
			String first,
			MultipartFile addPicture) {
		ReceiveInfo receiveInfo=new ReceiveInfo();
		String originalFileName=null;
		try {
			String realPath=request.getServletContext().getRealPath("/videoimage");
			File realFile=new File(realPath);
			if(!realFile.exists()) {
				realFile.mkdir();
			}
			if(addPicture==null || addPicture.isEmpty()) {
				receiveInfo.setResult(-1);
				receiveInfo.setMsg("error");
				return receiveInfo;
			}else {
				//至少上传的视频封面截图是存在
				originalFileName=addPicture.getOriginalFilename();
				boolean flag=UploadFileUtil.uploadImage(addPicture,videoId,true,64,realPath);
				if(!flag) {
					//说明封面截图图片存储失败
					receiveInfo.setResult(-1);
					receiveInfo.setMsg("error");
					return receiveInfo;
				}
			}
			//能执行到此处,说明,封面截图图片上传成功了
			//准备把视频的元数据存储到队列中
			Video video=new Video();
			video.setId(videoId);
			video.setUser(new User());
			video.getUser().setId(userId);
			video.setTitle(videoTitle);
			video.setTag(videoTag);
			video.setIntroduction(videoDescription);
			video.setFileName(videoId+videoFileName.substring(videoFileName.lastIndexOf(".")));
			video.setFileSize(Long.parseLong(videoFileSize));
			video.setCourse(new Course());
			video.getCourse().setId(courseId);
			video.setMd5(md5);
			video.setFirst(first);
			String extName=originalFileName.substring(originalFileName.lastIndexOf(".")+1);
			video.setPicture(videoId+"."+extName);
			if("1".equals(first)) {
				//说明是第一次上传
				//构建head对象,存储到队列中,即把video中的元数据存储到队列中
				this.amqpTemplate.convertAndSend("videoCacheQueue",video);
			}else if("2".equals(first)) {
				//说明非第一次上传,断点续传
				//从redis中获取最后上传的完的那块的信息
				//这个块有可能是head(video),也有可能是videoBlockMessage
				List<Serializable> currentVideoAllBlock=this.redisTemplate.boundListOps(videoId).range(0,-1);
				if (currentVideoAllBlock.size()==1) {
					//说明redis中只有视频的头
					receiveInfo.setBlockIndex(0);
					receiveInfo.setBlockNumber(UploadFileUtil.findBlockNumber(Long.parseLong(videoFileSize)));
					
				}else if (currentVideoAllBlock.size()>1) {
					//说明至少有一个文件块上传成功
					//从list集合中获取最后一个块,且必须是VideoBlockMessage类型
					VideoBlockMessage vbm=(VideoBlockMessage) currentVideoAllBlock.get(currentVideoAllBlock.size()-1);
					receiveInfo.setBlockIndex(vbm.getBlockIndex());
					receiveInfo.setBlockNumber(UploadFileUtil.findBlockNumber(Long.parseLong(videoFileSize)));
				}
			}
			//能执行到此处,说明可能是first==1时候成功
			//也有可能是first==2的时候成功
			receiveInfo.setResult(0);
			receiveInfo.setMsg("success");
			
			
		}catch(Exception e) {
			//一定是有异常了
			receiveInfo.setResult(-1);
			receiveInfo.setMsg("error");
			e.printStackTrace();
		}
		
		
		
		
		return receiveInfo;
	}
	//3.循环上传视频的文件块
	@RequestMapping(value="/chunk",method=RequestMethod.POST)
	@ResponseBody
	public ReceiveInfo VideoChunk(
			String userId,
			String videoId,
			String blockNumber,
			String blockIndex,
			String format,
			MultipartFile file) {
		ReceiveInfo receiveInfo=new ReceiveInfo();
		
		try {
			//构建VideoBlockMessage对象
			VideoBlockMessage videoBlockMessage=new VideoBlockMessage();
			videoBlockMessage.setUserId(userId);
			videoBlockMessage.setVideoId(videoId);
			videoBlockMessage.setBlockIndex(Integer.parseInt(blockIndex));
			videoBlockMessage.setBlockNumber(Integer.parseInt(blockNumber));
			byte[] data=file.getBytes();
			videoBlockMessage.setData(data);
			videoBlockMessage.setLength(data.length);
			
			//把VideoBlockMessage对象发送队列videoCacheQueue中
			this.amqpTemplate.convertAndSend("videoCacheQueue",videoBlockMessage);
			
			receiveInfo.setResult(0);
			receiveInfo.setMsg("success");
			receiveInfo.setBlockIndex(Integer.parseInt(blockIndex));
			receiveInfo.setBlockNumber(Integer.parseInt(blockNumber));
		} catch (Exception e) {
			// TODO Auto-generated catch block
			receiveInfo.setResult(-1);
			receiveInfo.setMsg("error");
			receiveInfo.setBlockIndex(Integer.parseInt(blockIndex));
			receiveInfo.setBlockNumber(Integer.parseInt(blockNumber));
			e.printStackTrace();
		}
		return receiveInfo;
		
	}	
}

将视频分块:下面代码是将视频分块
//将视频分块的代码
package cn.tedu.util;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.RandomAccessFile;

import org.apache.http.entity.mime.content.AbstractContentBody;

public class BlockStreamBody extends AbstractContentBody{
    //文件块的大小
	private long blockSize=0;
	//上传的文件的名称
    private String fileName;
    
    //以下三个重要的数据,是给重要的方法writeTo
    //重要:文件的总块数
    private int blockNumber;
    //重要:文件的块的索引
    private int blockIndex;
    //重要:要对此文件进行分割
	private File targetFile;
	
	
	public BlockStreamBody(int blockNumber,int blockIndex,File targetFile) {
		super("application/octet-stream");
		this.blockNumber=blockNumber;
		this.blockIndex=blockIndex;
		this.targetFile=targetFile;
		
		this.fileName=this.targetFile.getName();
		if(blockIndex<blockNumber) {
			this.blockSize=GlobalConstant.BLOCK_SIZE;
		}else {
			this.blockSize=targetFile.length()-GlobalConstant.BLOCK_SIZE*(blockNumber-1);
		}		
	}

	@Override
	public String getFilename() {
		// TODO Auto-generated method stub
		return this.fileName;
	}

	/**
	 * 此方法每调用一次,只输出文件中的某一个块的数据
	 */
	@Override
	public void writeTo(OutputStream out) throws IOException {
		byte[] b=new byte[1024];
		RandomAccessFile raf=new RandomAccessFile(this.targetFile,"r");
		if(this.blockIndex==1) {
			//处理第一块
			int n=0;
			long readLength=0;//存储的是读取的字节数
			while(readLength<=this.blockSize-1024) {
				n=raf.read(b,0,1024);
				readLength+=1024;
				out.write(b,0,n);
			}
			if(readLength<this.blockSize) {
				//余下的不足1024个字节在此读取
				n=raf.read(b,0,(int)(blockSize-readLength));
				out.write(b,0,n);
			}
		}else if(blockIndex<blockNumber) {
			//处理的不是第一块,也不是最后一块
			//需要给文件的指针定位
			raf.seek(GlobalConstant.BLOCK_SIZE*(blockIndex-1));
			int n=0;
			long readLength=0;//存储的是读取的字节数
			while(readLength<=this.blockSize-1024) {
				n=raf.read(b,0,1024);
				readLength+=1024;
				out.write(b,0,n);
			}
			if(readLength<this.blockSize) {
				//余下的不足1024个字节在此读取
				n=raf.read(b,0,(int)(blockSize-readLength));
				out.write(b,0,n);
			}			
		}else {
			//处理的是最后一块
			raf.seek(GlobalConstant.BLOCK_SIZE*(blockIndex-1));
			int n=0;
			while((n=raf.read(b,0,1024))!=-1) {
				out.write(b,0,n);
			}
		}
		raf.close();	
	}

	@Override
	public String getTransferEncoding() {
		// TODO Auto-generated method stub
		return "binary";
	}

	@Override
	public long getContentLength() {
		// TODO Auto-generated method stub
		return this.blockSize;
	}
	
}

	什么是md5值:就相当于一个安全校验码,在视频上传一开始的时候会生成一个md5码,如果这个视频在上传的过程中发生了改变,md5的值就会跟着改变.
	下面的代码是三种生成md5值的方法
package cn.tedu.util;

import java.io.File;
import java.io.FileInputStream;
import java.math.BigInteger;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.security.MessageDigest;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.IOUtils;

/**
 * 生成md5的工具
 * 可以生成某个字符串的md5
 * 可以生成某个文件的md5
 * @author Administrator
 *
 */
public class MD5Util {
	/**
	 * jdk方式
	 * 生成某个文件的md5值
	 * @param file   指定某个文件
	 * @return  文件的md5值
	 * @throws Exception
	 */
	public static String getMd5(File file)throws Exception{
		String md5String=null;
		FileInputStream fis=new FileInputStream(file);
		MappedByteBuffer byteBuffer=fis.getChannel().map(FileChannel.MapMode.READ_ONLY,0,file.length());
		MessageDigest md5=MessageDigest.getInstance("MD5");
		md5.update(byteBuffer);
		BigInteger bi=new BigInteger(1,md5.digest());
		md5String=bi.toString(16);
		fis.close();
		return md5String;
	}
	/**
	 * apache 的commons codec 组件
	 * 生成某个文件的md5值
	 * @param path   指定某个文件的路径
	 * @return   文件的md5值
	 * @throws Exception
	 */
	public static String getMd5(String path)throws Exception{
		String md5String=null;
		FileInputStream fis=new FileInputStream(path);
		md5String=DigestUtils.md5Hex(IOUtils.toByteArray(fis));
		IOUtils.closeQuietly(fis);
		return md5String;
	}
	/**
	 * 生成某个字符串的md5值
	 * @param str   源字符串
	 * @param salt  盐
	 * @return  字符串的md5
	 * @throws Exception
	 */
	public static String getMd5(String str,String salt)throws Exception{
		String md5String=null;
		MessageDigest md5=MessageDigest.getInstance("MD5");
		md5.update(str.getBytes());;
		md5.update(salt.getBytes());
		byte[] digest=md5.digest();
		md5String=org.apache.commons.codec.binary.Hex.encodeHexString(digest);
		return md5String;
	}
	public static void main(String[] args) {
		try {
			System.out.println(MD5Util.getMd5(new  File("d:/ks.mp4")));
			System.out.println(MD5Util.getMd5("d:/ks.mp4"));
			System.out.println(MD5Util.getMd5("123","admin"));
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
}

3.将队列服务器中的视频头和视频块传送到redis服务器中VideoCache类

package cn.tedu.mq.listen;

import java.io.Serializable;
import java.security.MessageDigest;
import java.util.List;
import java.util.concurrent.TimeUnit;

import javax.annotation.Resource;

import org.apache.commons.codec.binary.Hex;
import org.springframework.data.redis.core.RedisTemplate;


import cn.tedu.entity.Video;
import cn.tedu.service.VideoService;
import cn.tedu.util.AssembledFileUtil;
import cn.tedu.util.GlobalConstant;
import cn.tedu.vo.VideoBlockMessage;

public class VideoCache {
	@Resource(name = "videoService")
	private VideoService videoService;
	@Resource(name="redisTemplate")
	private RedisTemplate<Serializable,Serializable> redisTemplate;
	public void videoCacheListen(Object obj) {
		System.out.println("VideoCache.videoCacheListen()-->"+obj.getClass());
		try {
			if(obj.getClass()==Video.class) {
				//说明处理视频缓存队列中的视频head   Video类型
				System.out.println("拿到了Video对象");
			    //把拿到的Video的对昂临时存储在redis中
				Video video=(Video)obj;
				this.redisTemplate.boundListOps(video.getId()).rightPush(video);
				//在redis服务器中对videoState状态置为正在上传
				this.redisTemplate.boundHashOps("videoState").put(video.getId(),GlobalConstant.UPLOADING);
				//定时清除redis中的指定的数据
				this.redisTemplate.expire(video.getId(),1,TimeUnit.DAYS);
				this.redisTemplate.boundHashOps("videoState").expire(7,TimeUnit.DAYS);
			}else if(obj.getClass()==VideoBlockMessage.class) {
				//说明处理的是队列中的VideoBlockMessage对象
				System.out.println("拿到了VideoBlockMessage对象");
				//将视频块从队列服务器取出
				VideoBlockMessage videoBlockMessage=(VideoBlockMessage)obj;
				
				//只要任何一个环节上传失败,则忽略队列中的后面的所以的视频块
				//此处可能出现的问题,就是数据存储失败,和md5不相等
				//此处从redis中获取videoState中的某一个视频id对应的状态
				String state=(String)this.redisTemplate.boundHashOps("videoState").get(videoBlockMessage.getVideoId());
				if (state.equals(GlobalConstant.UPLOAD_FAILURE)) {
					return;
				}
				//不是failure,即上传成功,把视频块对象存储给rides
				this.redisTemplate.boundListOps(videoBlockMessage.getVideoId()).rightPush(videoBlockMessage);
				//无论上传视频头还是视频块的对象存储给redis,都说明是正在上传给rides
				this.redisTemplate.boundHashOps("videoState").put(videoBlockMessage.getVideoId(),GlobalConstant.UPLOADING);
				//判断blockIndex是否等于bilockNumber
				if (videoBlockMessage.getBlockIndex()==videoBlockMessage.getBlockNumber()) {
					//说明是最后一块了,视频文件数据宽已经全部存储redis中
					
					//从rides中取出视频id对应所以视频头和视频块的数据
					List<Serializable> currentVideoAllBlock=this.redisTemplate.boundListOps(videoBlockMessage.getVideoId()).range(0, -1);
					//取出视频头
					Video metaData=(Video)currentVideoAllBlock.get(0);
					//获取med5的消息摘要
					MessageDigest md5_messageDigest=MessageDigest.getInstance("MD5");
					//取出后面的对象都是videoBlockMessage类型对象
					for (int i = 1; i <currentVideoAllBlock.size(); i++) {
						//每循环一次取出的是一个视频块的对象
						VideoBlockMessage vbm=(VideoBlockMessage)currentVideoAllBlock.get(i);
						md5_messageDigest.update(vbm.getData());
					}
					byte[] digest=md5_messageDigest.digest();
					String newMd5=Hex.encodeHexString(digest);
					//判断取出的视频文件的md5值和原始的视频文件md5的值是否相等
					if (metaData.getMd5().equals(newMd5)) {
						System.out.println("文件是完整的");
						//如果文件是完整的要更新数据库,要把数据插入t_video表
						boolean flag=this.videoService.save(metaData);
						if (flag) {
							System.out.println("视频信息存储到数据库T_video表成功");
							metaData.setState(Integer.parseInt((GlobalConstant.UPLOAD_OVER)));
							//给redis的视频状态设置成uploader
							this.redisTemplate.boundHashOps("videoState").put(videoBlockMessage.getVideoId(),GlobalConstant.UPLOAD_OVER);
							//说明视频文件全部上传成功,且md5也相等,可以合并文件块,
							//合并完成后的视频给内网使用
							AssembledFileUtil.assembledFile(currentVideoAllBlock);
							
						}else {
							System.out.println("视频信息存储数据库失败");
							//给rides中的视频状态设置为failure
							this.redisTemplate.boundHashOps("videoState").put(videoBlockMessage.getVideoId(),GlobalConstant.UPLOAD_FAILURE);
						}
					}else {
						System.out.println("md5不相等");
						//设置视频上传失败
						this.redisTemplate.boundHashOps("videoState").put(videoBlockMessage.getVideoId(),GlobalConstant.UPLOAD_FAILURE);
						return;
					}
				}
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} 
		
	}
	
}

创建service接口用于编写存储视频的方法

package cn.tedu.service;

import cn.tedu.entity.Video;
import cn.tedu.vo.Page;
import cn.tedu.vo.Result;

public interface VideoService {
	/**
	 * 存储视频信息的业务方法
	 * @param metaData 视频对象
	 * @return true视频存储成功
	 * 			false:视频存储失败
	 */
	public boolean save(Video metaData);

}

定义一个serviceimpl类实现service接口,用来实现save方法来给数据库添加数据

//此处的@Transactional必须写因为对数据库的数据进行了改变
@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
	@Override
	public boolean save(Video metaData) {
		boolean flag=false;
		this.videoMapper.save(metaData);
		flag=true;
		return flag;
	}

定义VidoeMapper接口

package cn.tedu.dao;

import java.util.List;

import cn.tedu.entity.Video;
import cn.tedu.vo.Page;

public interface VideoMapper {
	/**
	 * geit_video表添加视频数据
	 * @param metaData
	 */
	public void save(Video metaData);

}

创建一个VideoMapperImpl类用来实现VideoMapper接口的save方法此方法用来执行数据库

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.tedu.dao.VideoMapper">

    <!-- role的ResultMap -->
	<resultMap type="Role" id="roleMap">
	    <!-- 注意:从表中的外键id,不能跟主表的主键id名字相同 -->
		<id property="id" column="role_id" />
		<result property="name" column="role_name"/>
	</resultMap>
	<!-- user的resultMap -->
	<resultMap type="User" id="userMap">
		<id property="id" column="user_id" jdbcType="VARCHAR"/>
		<result property="loginName" column="user_loginname" jdbcType="VARCHAR"/>
		<result property="loginType" column="user_logintype" jdbcType="VARCHAR"/>
		<result property="nickName" column="user_nickname" jdbcType="VARCHAR"/>
		<result property="password" column="user_password" jdbcType="VARCHAR"/>
		<result property="type" column="user_type" jdbcType="INTEGER"/>
		<result property="head" column="user_head" jdbcType="VARCHAR"/>
		<result property="score" column="user_score" jdbcType="INTEGER"/>
		<result property="isLock" column="user_islock" jdbcType="CHAR"/>
		<result property="pwdState" column="user_pwdstate" jdbcType="VARCHAR"/>
		<result property="regDate" column="user_regdate" jdbcType="TIMESTAMP"/>
		<result property="age" column="user_age" jdbcType="INTEGER"/>
		<result property="sex" column="user_sex" jdbcType="CHAR"/>
		<result property="introduction" column="user_introduction" jdbcType="VARCHAR"/>
		<collection property="roles" ofType="Role" 
		            javaType="java.util.List"
		            resultMap="roleMap"></collection>
	</resultMap>
	
	<!-- Course的resultMap -->
	<resultMap type="Course" id="courseMap">
		<id property="id" column="course_id"/>
		<result property="name" column="course_name"/>
		<result property="picture" column="course_picture"/>
		<result property="order" column="course_order" jdbcType="INTEGER"/>
		<result property="desc" column="course_desc"/>
	</resultMap>
	
	
	<resultMap type="Video" id="videoMap">
		<id property="id" column="video_id" jdbcType="CHAR"/>
		<result property="title" column="video_title" jdbcType="VARCHAR"/>
		<result property="special" column="video_special" jdbcType="INTEGER"/>
		<result property="forsale" column="video_forsale" jdbcType="CHAR"/>
		<result property="count" column="video_click_count" jdbcType="BIGINT"/>
		<result property="introduction" column="video_introduction" jdbcType="VARCHAR"/>
		<result property="picture" column="video_picture" jdbcType="VARCHAR"/>
		<result property="picturecc" column="video_picture_cc" jdbcType="VARCHAR"/>
		<result property="fileName" column="video_filename" jdbcType="VARCHAR"/>
		<result property="videoUrlcc" column="video_url_cc" jdbcType="VARCHAR"/>
		<result property="state" column="video_state" jdbcType="INTEGER"/>
		<result property="ontime" column="video_ontime" jdbcType="TIMESTAMP"/>
		<result property="difficulty" column="video_difficulty" jdbcType="INTEGER"/>
		<result property="md5" column="md5" jdbcType="VARCHAR"/>
		<result property="tag" column="video_tag" jdbcType="VARCHAR"/>
		<result property="fileSize" column="video_filesize" jdbcType="BIGINT"/>
		<result property="metaurl" column="metaurl" jdbcType="VARCHAR"/>
		<result property="chunkurl" column="chunkurl" jdbcType="VARCHAR"/>
		<result property="ccvid" column="ccvid" jdbcType="VARCHAR"/>
		<association property="user" column="user_id"
		             javaType="User" resultMap="userMap">		
		</association>
		<association property="course" column="course_id"
		             javaType="Course" resultMap="courseMap">
		
		</association>
	</resultMap>
	<!-- 插入视频数据 -->
	<insert id="save"
	        parameterType="Video">
	   insert into t_video
	    (
	      video_id,
	      video_title,
	      video_tag,
	      course_id,
	      user_id,
	      video_introduction,
	      video_filename,
	      video_filesize,
	      video_picture,
	      video_state,
	      md5
	    )
	      values
	      (
	       #{id},
	       #{title},
	       #{tag},
	       #{course.id},
	       #{user.id},
	       #{introduction},
	       #{fileName},
	       #{fileSize,jdbcType=BIGINT},
	       #{picture},
	       #{state},
	       #{md5}
	      )
	</insert>
</mapper>

将视频合并

package cn.tedu.util;

import java.io.File;
import java.io.RandomAccessFile;
import java.io.Serializable;
import java.util.List;

import cn.tedu.entity.Video;
import cn.tedu.vo.VideoBlockMessage;

/**
 * 专门用来合并文件的工具类
 * @author Mechrev
 * d:/upload/userId/videoid/videoid.mp4
 */

public class AssembledFileUtil {
	
	public static void assembledFile(List<Serializable> currentVideoAllBlock) {
		String userId="";
		String videoId="";
		int blockNumber=0;
		int blockIndex=0;
		try {
			if (currentVideoAllBlock!=null&&currentVideoAllBlock.size()>0) {
				Video video=(Video)currentVideoAllBlock.get(0);
				userId=video.getUser().getId();
				videoId=video.getId();
				//构建文件存储路径d:/upload/userId/videoId/
				String uploadedURL=GlobalConstant.UPLOAD_ABSTRACT_PATH
						+File.separator+userId+File.separator+videoId+File.separator;
				File realPath=new File(uploadedURL);
				if (!realPath.exists()) {
					realPath.mkdirs();
					
				}
				//文件的真实名字
				String fileName=video.getFileName();
				//截取拓展名字
				String extName=fileName.substring(fileName.lastIndexOf(".")+1);
				//准备开始合并文件,拼装路径
				RandomAccessFile fileWrite=new RandomAccessFile(uploadedURL+videoId+"."+extName,"rw");
				//将视频块存储到byte集合中
				for (int i = 1; i < currentVideoAllBlock.size(); i++) {
					VideoBlockMessage vbm=(VideoBlockMessage)currentVideoAllBlock.get(i);
					if (vbm!=null) {
						byte[] block=vbm.getData();
						int read=block.length;
						long offset=(i-1)*GlobalConstant.BLOCK_SIZE;
						fileWrite.seek(offset);
						//写入文件
						fileWrite.write(block,0,read);
					}
				}
				fileWrite.close();
			}
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
		
	}
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
}

4.测试类

package cn.tedu.test;

import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.ContentBody;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.CharsetUtils;
import org.apache.http.util.EntityUtils;

import com.alibaba.fastjson.JSON;

import cn.tedu.util.BlockStreamBody;
import cn.tedu.util.MD5Util;
import cn.tedu.util.UploadFileUtil;

public class TestUploadController {
	/**
	 * 此方法是一个私有的方法,用来给MultipartEntityBuilder存储数据的
	 * 注意,数据仅限文本
	 * @param params  要提交的数据 
	 * @param multipartEntityBuilder  把提交的数据存储到此对象中
	 */
	private void buildMultipartEntity(Map<String, String> params, MultipartEntityBuilder multipartEntityBuilder) {

		//解决中文乱码问题
		ContentType contentType=ContentType.create(HTTP.PLAIN_TEXT_TYPE,HTTP.UTF_8);
		
		//把map集合中的key转换成list集合
		List<String> keys=new ArrayList<String>(params.keySet());
		//把list集合中的所有的key排序,不区分大小的排序
		Collections.sort(keys,String.CASE_INSENSITIVE_ORDER);
		for(Iterator<String> iterator=keys.iterator();iterator.hasNext();) {
			String key=iterator.next();
			String value=params.get(key);
			if(StringUtils.isNoneBlank(value)) {
				multipartEntityBuilder.addTextBody(key, value, contentType);
			}
		}
	}
	
	
	
	//1.创建视频的方法
	public Map<String,String> videoCreate(
			Map<String,String> params,
			String serverUrlCreate){
		Map<String,String> returnMap=null;
		String content=null;
		try {
			//用HttpClent模拟http请求,相当于用浏览器模拟发送请求,接收返回数据
			HttpClient httpClient=HttpClients.createDefault();
			HttpPost httpPost=new HttpPost(serverUrlCreate);
			
			//给post方式添加提交的数据
			MultipartEntityBuilder multipartEntityBuilder=MultipartEntityBuilder.create();
			multipartEntityBuilder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
			
			this.buildMultipartEntity(params,multipartEntityBuilder);
			
			httpPost.setEntity(multipartEntityBuilder.build());
						
			//发送一个post请求,返回的记过是HttpResponse对象
			HttpResponse response=httpClient.execute(httpPost);
			//从response对象中解析出来返回的结果数据
			//状态码就是象 200 404 500之类的
			int status=response.getStatusLine().getStatusCode();
			if(status==HttpStatus.SC_OK) {
				//sc_ok值是200,说明响应成功,则获取响应的实体内容
				HttpEntity entity=response.getEntity();
				content=EntityUtils.toString(entity);
				//关闭httpClient连接
				httpClient.getConnectionManager().shutdown();
			}
			//输出响应的结果数据
			//System.out.println("content="+content);
			if(content!=null) {
				returnMap=(Map<String,String>)JSON.parse(content.trim());
				//System.out.println("contentMap="+contentMap);
			}
		}catch(Exception e) {
			e.printStackTrace();
		}		
		return returnMap;
	}
	
	/**
	 * 2.上传视频元数据的方法
	 * @param metaParams    传递给服务器的参数数据,即视频文件的信息
	 * @param videoPicture  视频的封面截图
	 * @param serverUrl     上传视频元数据的服务器地址
	 * @return  返回的结果是map集合
	 *          result    0:成功   -1失败
	 *          msg       返回消息提示
	 *          blockNumber   视频文件的总块数
	 *          blockIndex    最后上传成功的那个快的索引
	 */
	public Map<String, Object> uploadMetaData(Map<String, String> metaParams, File videoPicture, String serverUrl) {
		Map<String,Object> returnMetaMap=null;
		String content=null;
		try {
			//用HttpClent模拟http请求,相当于用浏览器模拟发送请求,接收返回数据
			HttpClient httpClient=HttpClients.createDefault();
			HttpPost httpPost=new HttpPost(serverUrl);
			
			//给post方式添加提交的数据
			MultipartEntityBuilder multipartEntityBuilder=MultipartEntityBuilder.create();
			multipartEntityBuilder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
			
			//添加文本,把metaParams中的文本数据,存储给multipartEntityBuilder对象
			this.buildMultipartEntity(metaParams,multipartEntityBuilder);
			
			//添加文件
			ContentBody contentBody=new FileBody(videoPicture);
			multipartEntityBuilder.addPart("addPicture",contentBody);
			
			httpPost.setEntity(multipartEntityBuilder.build());
						
			//发送一个post请求,返回的记过是HttpResponse对象
			HttpResponse response=httpClient.execute(httpPost);
			//从response对象中解析出来返回的结果数据
			//状态码就是象 200 404 500之类的
			int status=response.getStatusLine().getStatusCode();
			if(status==HttpStatus.SC_OK) {
				//sc_ok值是200,说明响应成功,则获取响应的实体内容
				HttpEntity entity=response.getEntity();
				content=EntityUtils.toString(entity);
				//关闭httpClient连接
				httpClient.getConnectionManager().shutdown();
			}
			//输出响应的结果数据
			//System.out.println("content="+content);
			if(content!=null) {
				returnMetaMap=(Map<String,Object>)JSON.parse(content.trim());
				//System.out.println("contentMap="+contentMap);
			}
		}catch(Exception e) {
			e.printStackTrace();
		}		
		return returnMetaMap;
	}
	
	//
	/**
	 * 3.上传文件块的方法 
	 * @param chunkParams 服务器需要的参数
	 * @param filePath  文件的实际路径,
	 * @param serverUrl  服务器url地址
	 * @return  返回的结果
	 *         result
	 *         msg
	 *         blockNumber
	 *         blockIndex
	 */
	private Map<String, String> uploadChunkToServer(
			Map<String, String> chunkParams,
			String filePath,
			String serverUrl) {
		Map<String,String> returnMap=null;
		String content=null;
		try {
			//用HttpClent模拟http请求,相当于用浏览器模拟发送请求,接收返回数据
			HttpClient httpClient=HttpClients.createDefault();
			HttpPost httpPost=new HttpPost(serverUrl);
			
			//给post方式添加提交的数据
			MultipartEntityBuilder multipartEntityBuilder=MultipartEntityBuilder.create();
			multipartEntityBuilder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
			
			//添加文本,把metaParams中的文本数据,存储给multipartEntityBuilder对象
			this.buildMultipartEntity(chunkParams,multipartEntityBuilder);
			
			//添加文件块,指的是文件中的某一个块
			int blockNumber=Integer.parseInt(chunkParams.get("blockNumber"));
			int blockIndex=Integer.parseInt(chunkParams.get("blockIndex"));
			
			//构建文件块(4M)
			ContentBody contentBody=new BlockStreamBody(blockNumber,blockIndex,new File(filePath));
			multipartEntityBuilder.addPart("file",contentBody);
			multipartEntityBuilder.setCharset(CharsetUtils.get("UTF-8"));
			
			
			httpPost.setEntity(multipartEntityBuilder.build());
						
			//发送一个post请求,返回的记过是HttpResponse对象
			HttpResponse response=httpClient.execute(httpPost);
			//从response对象中解析出来返回的结果数据
			//状态码就是象 200 404 500之类的
			int status=response.getStatusLine().getStatusCode();
			if(status==HttpStatus.SC_OK) {
				//sc_ok值是200,说明响应成功,则获取响应的实体内容
				HttpEntity entity=response.getEntity();
				content=EntityUtils.toString(entity);
				//关闭httpClient连接
				httpClient.getConnectionManager().shutdown();
			}
			//输出响应的结果数据
			//System.out.println("content="+content);
			if(content!=null) {
				return returnMap=(Map<String,String>)JSON.parse(content.trim());
				//System.out.println("contentMap="+contentMap);
			}
		}catch(Exception e) {
			e.printStackTrace();
		}
		
		return returnMap;
	}
	
	
	

	public static void main(String[] args) {
		try {
			//emulator:模拟器   模拟上传所有上传视频的所有过程
			TestUploadController emulator=new TestUploadController();
			//0.准备一些必要的数据
			String filePath="d:/ks.mp4";
			String fileName="ks.mp4";
			long fileSize=0;
			File targetFile=new File(filePath);
			if(targetFile.exists()) {
				fileSize=targetFile.length();
				//1.调用创建视频的方法
				//给创建视频准备一些必要的数据
				String serverUrlCreate="http://localhost:8080/tes_server/video/create";
				Map<String,String> params=new HashMap<String,String>();
				params.put("userId", "7008ffa6-e01d-48ed-a460-dbf2a4908bfa");
				params.put("format", "json");
				//调用创建视频的方法
				Map<String,String> createMap=emulator.videoCreate(params, serverUrlCreate);
				System.out.println("createMap-->"+createMap);
				String msgerror=createMap.get("msgerror");
				
				if(msgerror==null) {
					//没有错误信息,说明创建视频是成功的
					//2.调用上传视频的元数据,即视频文件的基本信息
					//准备视频文件的一些基本信息
					Map<String,String> metaParams=new HashMap<String,String>(createMap);
					String serverUrl=metaParams.get("metaurl");
					
					metaParams.put("videoTitle", "快闪视频");
					metaParams.put("videoTag", "java娱乐");
					metaParams.put("videoDescription", "很好玩的视频");
					metaParams.put("videoFileName", fileName);
					metaParams.put("videoFileSize", fileSize+"");
					metaParams.put("courseId", "8c2ded0e-0455-4631-a3c4-b3c50aeda12f");
					metaParams.put("md5", MD5Util.getMd5(targetFile));
					metaParams.put("format", "json");
					metaParams.put("first", "1");
					metaParams.remove("metaurl");
					metaParams.remove("chunkurl");
					metaParams.remove("msgerror");
					File videoPicture=new File("d:/video.png");//视频的封面
					
					Map<String,Object> returnMetaMap=emulator.uploadMetaData(metaParams,videoPicture,serverUrl);
					System.out.println("returnMetaMap-->"+returnMetaMap);
					//3.调用上传视频的文件块
					//上传视频文件之前,要先把视频文件分块
					String msg=returnMetaMap.get("msg").toString();
					if("success".equals(msg)) {
						//说明元数据上传成功
						//准备一些数据
						//blockIndex代表的是最后一个正确上传的文件块的索引
						//blockNumber代表文件分块的总数
						//这两个值是从上传元数据成功后的map集合获取的
						int blockIndex=(Integer)returnMetaMap.get("blockIndex")+1;
						int blockNumber=(Integer)returnMetaMap.get("blockNumber");
						if(blockNumber==0) {
							//说明first==1的情况
							blockNumber=UploadFileUtil.findBlockNumber(fileSize);
						}
						Map<String,String> chunkParams=new HashMap<String,String>();
						chunkParams.put("userId", "7008ffa6-e01d-48ed-a460-dbf2a4908bfa");
						chunkParams.put("videoId", createMap.get("videoId"));
						chunkParams.put("blockNumber", blockNumber+"");
						chunkParams.put("format", "json");
						
						serverUrl=createMap.get("chunkurl");
						//上传视频块
						for(int i=blockIndex;i<=blockNumber;i++) {
							chunkParams.put("blockIndex", i+"");
							Map<String,String> returnChunkMap=emulator.uploadChunkToServer(chunkParams,filePath,serverUrl);
							System.out.println("returnChunkMap-->"+returnChunkMap);
							if("error".equals(returnChunkMap.get("msg"))) {
								//说明上传的某个文件块失败,不在继续上传后续的文件块了
								break;
							}
						}
						
						
						
						
						
						
						
						
						
						
					}
				}
				
				
				
			}			
		}catch(Exception e) {
			e.printStackTrace();
		}finally {
			
		}
		
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值