依是一套全部開源的快速開發(fā)平臺,毫無保留給個人及企業(yè)免費使用。
微言聊天室是基于前后端分離,采用SpringBoot+Vue框架開發(fā)的網(wǎng)頁版聊天室。使用了Spring Security安全框架進行密碼的加密存儲和登錄登出等邏輯的處理,以WebSocket+Socket.js+Stomp.js實現(xiàn)消息的發(fā)送與接收,監(jiān)聽。搭建FastDFS文件服務器用于保存圖片,使用EasyExcel導出數(shù)據(jù),使用Vue.js結合Element UI進行顯示彈窗和數(shù)據(jù)表格分頁等功能,以及整個系統(tǒng)的界面進行UI設計,并且使用MyBatis結合數(shù)據(jù)庫MySQL進行開發(fā)。最后使用了Nginx進行部署前后端分離項目。
功能實現(xiàn):群聊,單聊,郵件發(fā)送,emoji表情發(fā)送,圖片發(fā)送,用戶管理,群聊記錄管理,Excel的導出。關注Java項目分享
客戶端界面-群聊主界面
客戶端界面-私聊界面
管理端界面-用戶管理
管理端界面-群聊消息管理
點贊轉發(fā)本文后私信【0724】四個數(shù)字即可獲取前后端代碼和操作教學視頻
前端代碼
后端代碼
操作視頻
文由作者“大白菜”分享,有較多修訂和改動。注意:本系列是給IM初學者的文章,IM老油條們還望海涵,勿噴!
接上兩篇《IM系統(tǒng)設計篇》、《編碼實踐篇(單聊功能)》,本篇主要講解的是通過實戰(zhàn)編碼實現(xiàn)IM的群聊功能,內(nèi)容涉及群聊技術實現(xiàn)原理、編碼實踐等知識。
建議你在閱讀本文之前,務必先讀本系列的前兩篇《IM系統(tǒng)設計篇》、《編碼實踐篇(單聊功能)》,在著重理解IM系統(tǒng)的理論設計思路之后,再來閱讀實戰(zhàn)代碼則效果更好。
最后,在開始本文之前,請您務必提前了解Netty的相關基礎知識,可從本系列首篇《IM系統(tǒng)設計篇》中的“知識準備”一章開始。
本文是系列文章的第3篇,以下是系列目錄:
在上篇《編碼實踐篇(單聊功能)》中,我們主要實現(xiàn)了IM的單聊功能,本節(jié)主要是實現(xiàn)IM群聊功能。
本篇涉及的群聊核心功能,大致如下所示:
其實群聊和單聊,整體上原理是一樣的,只是做了一下細節(jié)上的升級。
在首篇《IM系統(tǒng)設計篇》的“6、IM群聊思路設計”設計部分也做了詳細的說明了。
群聊的大概流程就是:根據(jù)群組 ID 查找到所有的成員集合,然后再遍歷找到每個成員對應的連接通道。
具體的群聊架構思路如下圖:
補充說明:因為本系列文章主要目的是引導IM初學者在基于Netty的情況下,如何一步一步從零寫出IM的邏輯和思維能力,因而為了簡化編碼實現(xiàn),本篇中編碼實現(xiàn)的客戶端都是基于控制臺實現(xiàn)的(希望不要被嫌棄),因為理解技術的本質顯然比炫酷的外在表現(xiàn)形式更為重要。
用戶登錄效果圖:
服務端映射關系的管理,分別是:
主要通過兩個 Map 去維護,具體如下:
public class ServerChatGroupHandler extends ChannelInboundHandlerAdapter {
private static Map<Integer, Channel> map=new HashMap<Integer, Channel>();
private static Map<Integer, Group> groups=new HashMap<Integer, Group>();
}
//組和成員列表關系實體
@Data
public class Group implements Serializable {
private String groupName;
private List<GroupMember> members=new ArrayList<GroupMember>();
}
//成員和連接通道的關系實體
public class GroupMember implements Serializable {
private Integer userid;
private Channel channel;
}
我們準備好相應的實體,以及實體和指令的映射關系,具體如下所示:
private static Map<Byte, Class<? extends BaseBean>> map=new HashMap<Byte,Class<? extends BaseBean>>();
static{
//登錄的請求和響應實體
map.put(1, LoginReqBean.class);
map.put(2, LoginResBean.class);
//創(chuàng)建群組的請求和響應實體
map.put(3, GroupCreateReqBean.class);
map.put(4, GroupCreateResBean.class);
//查看群組的請求和響應實體
map.put(5, GroupListReqBean.class);
map.put(6, GroupListResBean.class);
//加入群組的請求和響應實體
map.put(7,GroupAddReqBean.class);
map.put(8,GroupAddResBean.class);
//退出群組的請求和響應實體
map.put(9,GroupQuitReqBean.class);
map.put(10,GroupQuitResBean.class);
//查看成員列表的請求和響應實體
map.put(11,GroupMemberReqBean.class);
map.put(12,GroupMemberResBean.class);
//發(fā)送響應的實體(發(fā)送消息、發(fā)送響應、接受消息)
map.put(13,GroupSendMsgReqBean.class);
map.put(14,GroupSendMsgResBean.class);
map.put(15,GroupRecMsgBean.class);
}
通過下面這張圖,能看的更清晰一些:
IM群聊功能的實現(xiàn),我們需要兩個兩個業(yè)務 Handler:
客戶端 Handler,主要是通過判斷實體類型來做不同的業(yè)務操作,當然也可以使用 SimpleChannelInboundHandler 去進行 Handler 拆分。
public class ClientChatGroupHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//在鏈接就緒時登錄
login(ctx.channel());
}
//主要是“接受服務端”的響應信息
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if(msg instanceof LoginResBean){
LoginResBean res=(LoginResBean) msg;
System.out.println("登錄響應:"+res.getMsg());
if(res.getStatus()==0){
//登錄成功
//1.給通道綁定身份
ctx.channel().attr(AttributeKey.valueOf("userid")).set(res.getUserid());
//2.顯示操作類型【請看下面】
deal(ctx.channel());
}else{
//登錄失敗,繼續(xù)登錄
login(ctx.channel());
}
}else if(msg instanceof GroupCreateResBean){
GroupCreateResBean res=(GroupCreateResBean)msg;
System.out.println("創(chuàng)建響應群組:"+res.getMsg());
}else if(msg instanceofGroupListResBean){
GroupListResBean res=(GroupListResBean)msg;
System.out.println("查看群組列表:"+res.getLists());
}elseif(msg instanceofGroupAddResBean){
GroupAddResBean res=(GroupAddResBean)msg;
System.out.println("加入群組響應:"+res.getMsg());
}elseif(msg instanceof GroupQuitResBean){
GroupQuitResBean res=(GroupQuitResBean)msg;
System.out.println("退群群組響應:"+res.getMsg());
}else if(msg instanceof GroupMemberResBean){
GroupMemberResBean res=(GroupMemberResBean)msg;
if(res.getCode()==1){
System.out.println("查看成員列表:"+res.getMsg());
}else{
System.out.println("查看成員列表:"+res.getLists());
}
}else if(msg instanceof GroupSendMsgResBean){
GroupSendMsgResBean res=(GroupSendMsgResBean)msg;
System.out.println("群發(fā)消息響應:"+res.getMsg());
}else if(msg instanceof GroupRecMsgBean){
GroupRecMsgBean res=(GroupRecMsgBean)msg;
System.out.println("收到消息fromuserid="+
res.getFromuserid()+
",msg="+res.getMsg());
}
}
}
通過子線程循環(huán)向輸出控制臺輸出操作類型的方法,以下方法目前都是空方法,下面將詳細講解。
private void deal(final Channel channel){
final Scanner scanner=new Scanner(System.in);
new Thread(new Runnable() {
public void run() {
while(true){
System.out.println("請選擇類型:0創(chuàng)建群組,1查看群組,2加入群組,3退出群組,4查看群成員,5群發(fā)消息");
int type=scanner.nextInt();
switch(type){
case 0:
createGroup(scanner,channel);
break;
case 1:
listGroup(scanner,channel);
break;
case 2:
addGroup(scanner,channel);
break;
case 3:
quitGroup(scanner,channel);
break;
case 4:
listMembers(scanner,channel);
break;
case 5:
sendMsgToGroup(scanner,channel);
break;
default:
System.out.println("輸入的類型不存在!");
}
}
}
}).start();
}
服務端 Handler,主要是通過判斷實體類型來做不同的業(yè)務操作,當然也可以使用 SimpleChannelInboundHandler 去進行 Handler 拆分。
以下方法目前都是空方法,下面將詳細講解。
public class ServerChatGroupHandler extends ChannelInboundHandlerAdapter {
private static Map<Integer, Channel> map=new HashMap<Integer, Channel>();
private static Map<Integer, Group> groups=new HashMap<Integer, Group>();
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if(msg instanceof LoginReqBean) {
//登錄
login((LoginReqBean) msg, ctx.channel());
}else if(msg instanceof GroupCreateReqBean){
//創(chuàng)建群組
createGroup((GroupCreateReqBean)msg,ctx.channel());
}else if(msg instanceof GroupListReqBean){
//查看群組列表
listGroup((GroupListReqBean)msg,ctx.channel());
}else if(msg instanceof GroupAddReqBean){
//加入群組
addGroup((GroupAddReqBean)msg,ctx.channel());
}else if(msg instanceof GroupQuitReqBean){
//退出群組
quitGroup((GroupQuitReqBean)msg,ctx.channel());
}else if(msg instanceof GroupMemberReqBean){
//查看成員列表
listMember((GroupMemberReqBean)msg,ctx.channel());
}else if(msg instanceof GroupSendMsgReqBean){
//消息發(fā)送
sendMsg((GroupSendMsgReqBean) msg,ctx.channel());
}
}
}
客戶端請求:
private void createGroup(Scanner scanner,Channel channel){
System.out.println("請輸入群組ID");
Integer groupId=scanner.nextInt();
System.out.println("請輸入群組名稱");
String groupName=scanner.next();
GroupCreateReqBean bean=new GroupCreateReqBean();
bean.setGroupId(groupId);
bean.setGroupName(groupName);
channel.writeAndFlush(bean);
}
服務端處理:
public class ServerChatGroupHandler extends ChannelInboundHandlerAdapter {
private static Map<Integer, Channel> map=new HashMap<Integer, Channel>();
private static Map<Integer, Group> groups=new HashMap<Integer, Group>();
private void createGroup(GroupCreateReqBean bean,Channel channel){
//定義一個響應實體
GroupCreateResBean res=new GroupCreateResBean();
//查詢groups是否已經(jīng)存在
Group group=groups.get(bean.getGroupId());
//判斷是否已經(jīng)存在
if(group==null){
//定義群組實體
Group g=new Group();
//定義一個集合,專門存儲成員
List<GroupMember> members=new ArrayList<GroupMember>();
//屬性賦值
g.setGroupName(bean.getGroupName());
g.setMembers(members);
//添加到Map里面
groups.put(bean.getGroupId(),g);
//響應信息
res.setCode(0);
res.setMsg("創(chuàng)建群組成功");
}else{
res.setCode(1);
res.setMsg("該群組已經(jīng)存在!");
}
channel.writeAndFlush(res);
}
}
客戶端請求:
private void listGroup(Scanner scanner,Channel channel){
GroupListReqBean bean=new GroupListReqBean();
bean.setType("list");
channel.writeAndFlush(bean);
}
服務端處理:
public class ServerChatGroupHandler extends ChannelInboundHandlerAdapter {
private static Map<Integer, Channel> map=new HashMap<Integer, Channel>();
private static Map<Integer, Group> groups=new HashMap<Integer, Group>();
private void listGroup(GroupListReqBean bean,Channel channel){
if("list".equals(bean.getType())){
//定義一個響應實體
GroupListResBean res=new GroupListResBean();
//定義一個集合
List<GroupInfo> lists=new ArrayList<GroupInfo>();
//變量groups Map集合
for(Map.Entry<Integer, Group> entry : groups.entrySet()){
Integer mapKey = entry.getKey();
Group mapValue = entry.getValue();
GroupInfo gi=new GroupInfo();
gi.setGroupId(mapKey);
gi.setGroupName(mapValue.getGroupName());
lists.add(gi);
}
//把集合添加到響應實體里面
res.setLists(lists);
//開始寫到客戶端
channel.writeAndFlush(res);
}
}
}
客戶端請求:
private void addGroup(Scanner scanner,Channel channel){
System.out.println("請輸入加入的群組ID");
int groupId=scanner.nextInt();
Integer userId=(Integer) channel.attr(AttributeKey.valueOf("userid")).get();
GroupAddReqBean bean=new GroupAddReqBean();
bean.setUserId(userId);
bean.setGroupId(groupId);
channel.writeAndFlush(bean);
}
服務端處理:
public class ServerChatGroupHandler extends ChannelInboundHandlerAdapter {
private static Map<Integer, Channel> map=new HashMap<Integer, Channel>();
private static Map<Integer, Group> groups=new HashMap<Integer, Group>();
private void addGroup(GroupAddReqBean bean,Channel channel){
GroupAddResBean res=new GroupAddResBean();
//1.根據(jù)“群組ID”獲取對應的“組信息”
Group group=groups.get(bean.getGroupId());
//2.“群組”不存在
if(group==null){
res.setCode(1);
res.setMsg("groupId="+bean.getGroupId()+",不存在!");
channel.writeAndFlush(res);
return;
}
//3.“群組”存在,則獲取其底下的“成員集合”
List<GroupMember> members=group.getMembers();
boolean flag=false;
//4.遍歷集合,判斷“用戶”是否已經(jīng)存在了
for(GroupMember gm:members){
if(gm.getUserid()==bean.getUserId()){
flag=true;
break;
}
}
if(flag){
res.setCode(1);
res.setMsg("已經(jīng)在群組里面,無法再次加入!");
}else{
//1.用戶信息
GroupMember gm=new GroupMember();
gm.setUserid(bean.getUserId());
gm.setChannel(channel);
//2.添加到集合里面
members.add(gm);
//3.給“群組”重新賦值
group.setMembers(members);
res.setCode(0);
res.setMsg("加入群組成功");
}
channel.writeAndFlush(res);
}
}
客戶端請求:
private void quitGroup(Scanner scanner,Channel channel){
System.out.println("請輸入退出的群組ID");
int groupId=scanner.nextInt();
Integer userId=(Integer) channel.attr(AttributeKey.valueOf("userid")).get();
GroupQuitReqBean bean=new GroupQuitReqBean();
bean.setUserId(userId);
bean.setGroupId(groupId);
channel.writeAndFlush(bean);
}
服務端處理:
public class ServerChatGroupHandler extends ChannelInboundHandlerAdapter {
private static Map<Integer, Channel> map=new HashMap<Integer, Channel>();
private static Map<Integer, Group> groups=new HashMap<Integer, Group>();
private void quitGroup(GroupQuitReqBean bean,Channel channel){
GroupQuitResBean res=new GroupQuitResBean();
//1.根據(jù)“群組ID”獲取對應的“組信息”
Group group=groups.get(bean.getGroupId());
if(group==null){
//2.群組不存在
res.setCode(1);
res.setMsg("groupId="+bean.getGroupId()+",不存在!");
channel.writeAndFlush(res);
return;
}
//3.群組存在,則獲取其底下“成員集合”
List<GroupMember> members=group.getMembers();
//4.遍歷集合,找到“當前用戶”在集合的序號
int index=-1;
for(inti=0;i<members.size();i++){
if(members.get(i).getUserid()==bean.getUserId()){
index=i;
break;
}
}
//5.如果序號等于-1,則表示“當前用戶”不存在集合里面
if(index==-1){
res.setCode(1);
res.setMsg("userid="+bean.getUserId()+",不存在該群組里面!");
channel.writeAndFlush(res);
return;
}
//6.從集合里面刪除“當前用戶”
members.remove(index);
//7.給“群組”的“成員列表”重新賦值
group.setMembers(members);
res.setCode(0);
res.setMsg("退出群組成功");
channel.writeAndFlush(res);
}
}
客戶端請求:
private void listMembers(Scanner scanner,Channel channel){
System.out.println("請輸入群組ID:");
int groupId=scanner.nextInt();
GroupMemberReqBean bean=new GroupMemberReqBean();
bean.setGroupId(groupId);
channel.writeAndFlush(bean);
}
服務端處理:
public class ServerChatGroupHandler extends ChannelInboundHandlerAdapter {
private static Map<Integer, Channel> map=new HashMap<Integer, Channel>();
private static Map<Integer, Group> groups=new HashMap<Integer, Group>();
private void listMember(GroupMemberReqBean bean,Channel channel){
GroupMemberResBean res=new GroupMemberResBean();
List<Integer> lists=new ArrayList<Integer>();
//1.根據(jù)“群組ID”獲取對應的“組信息”
Group group=groups.get(bean.getGroupId());
if(group==null){
//2.查詢的群組不存在
res.setCode(1);
res.setMsg("groupId="+bean.getGroupId()+",不存在!");
channel.writeAndFlush(res);
}else{
//3.群組存在,則變量其底層的成員
for(Map.Entry<Integer, Group> entry : groups.entrySet()){
Group g = entry.getValue();
List<GroupMember> members=g.getMembers();
for(GroupMember gm:members){
lists.add(gm.getUserid());
}
}
res.setCode(0);
res.setMsg("查詢成功");
res.setLists(lists);
channel.writeAndFlush(res);
}
}
}
客戶端請求:
private void sendMsgToGroup(Scanner scanner,Channel channel){
System.out.println("請輸入群組ID:");
int groupId=scanner.nextInt();
System.out.println("請輸入發(fā)送消息內(nèi)容:");
String msg=scanner.next();
Integer userId=(Integer) channel.attr(AttributeKey.valueOf("userid")).get();
GroupSendMsgReqBean bean=new GroupSendMsgReqBean();
bean.setFromuserid(userId);
bean.setTogroupid(groupId);
bean.setMsg(msg);
channel.writeAndFlush(bean);
}
服務端處理:
public class ServerChatGroupHandler extends ChannelInboundHandlerAdapter {
private static Map<Integer, Channel> map=new HashMap<Integer, Channel>();
private static Map<Integer, Group> groups=new HashMap<Integer, Group>();
privatevoidsendMsg(GroupSendMsgReqBean bean,Channel channel){
GroupSendMsgResBean res=new GroupSendMsgResBean();
//1.根據(jù)“群組ID”獲取對應的“組信息”
Group group=groups.get(bean.getTogroupid());
//2.給“發(fā)送人”響應,通知其發(fā)送的消息是否成功
if(group==null){
res.setCode(1);
res.setMsg("groupId="+bean.getTogroupid()+",不存在!");
channel.writeAndFlush(res);
return;
}else{
res.setCode(0);
res.setMsg("群發(fā)消息成功");
channel.writeAndFlush(res);
}
//3.根據(jù)“組”下面的“成員”,變量并且逐個推送消息
List<GroupMember> members=group.getMembers();
for(GroupMember gm:members){
GroupRecMsgBean rec=new GroupRecMsgBean();
rec.setFromuserid(bean.getFromuserid());
rec.setMsg(bean.getMsg());
gm.getChannel().writeAndFlush(rec);
}
}
}
本篇中涉及的功能點稍微有點多,主要是實現(xiàn)了群聊的幾個核心功能,分別是:創(chuàng)建群組、查看群組列表、加入群組、退出群組、查看成員列表、群發(fā)消息。
這些功能經(jīng)過拆解,看起來就不是那么復雜了,希望大家都可以親自動手實現(xiàn)一遍,加深理解,提高學習效果。
實際上,真正的產(chǎn)品級IM中,群聊涉及的技術細節(jié)是非常多的,有興趣可以詳讀下面這幾篇:
[1] 手把手教你用Netty實現(xiàn)心跳機制、斷線重連機制
[2] 自已開發(fā)IM很難?手把手教你擼一個Andriod版IM
[3] 基于Netty,從零開發(fā)一個IM服務端
[4] 拿起鍵盤就是干,教你徒手開發(fā)一套分布式IM系統(tǒng)
[5] 正確理解IM長連接、心跳及重連機制,并動手實現(xiàn)
[6] 手把手教你用Go快速搭建高性能、可擴展的IM系統(tǒng)
[7] 手把手教你用WebSocket打造Web端IM聊天
[8] 萬字長文,手把手教你用Netty打造IM聊天
[9] 基于Netty實現(xiàn)一套分布式IM系統(tǒng)
[10] 基于Netty,搭建高性能IM集群(含技術思路+源碼)
[11] SpringBoot集成開源IM框架MobileIMSDK,實現(xiàn)即時通訊IM聊天功能
學習交流:
- 移動端IM開發(fā)入門文章:《新手入門一篇就夠:從零開發(fā)移動端IM》
- 開源IM框架源碼:https://github.com/JackJiang2011/MobileIMSDK(備用地址點此)
(本文已同步發(fā)布于:http://www.52im.net/thread-3981-1-1.html)
TML實現(xiàn)文件夾的上傳和下載,前端如何用HTML5實現(xiàn)分片上傳GB級大文件,網(wǎng)頁中實現(xiàn)文件上傳下載的三種解決方案(推薦),HTML5實現(xiàn)文件批量上傳組件,JQUERY 實現(xiàn)文件夾上傳(保留目錄結構),B/S大文件上傳支持斷點上傳,WebService 大文件上傳,斷點續(xù)傳分片,HTML+AJAX實現(xiàn)上傳大文件問題,用HTML實現(xiàn)本地文件的上傳,HTML5實現(xiàn)大文件上傳,HTML5實現(xiàn)大文件分片上傳思路,利用HTML5分片上傳超大文件思路,
WEBUPLOADER 支持 超大上G,多附件上傳,JS 大文件分割/分片上傳,
百度WEBUPLOADER上傳視頻等大文件,WEBUPLOAD組件實現(xiàn)文件上傳功能和下載功能,JS大文件切片上傳,斷點續(xù)傳實現(xiàn)DEMO,前端上傳大文件的解決方案,前端上傳大文件處理(切片、斷點續(xù)傳),前端大文件上傳優(yōu)化方案——分片上傳,vue大文件上傳解決方案,vue大文件上傳解決方案10G,vue大文件上傳解決方案50G,vue大文件上傳解決方案100G,html5如何實現(xiàn)大文件斷點續(xù)傳、秒傳,
java如何實現(xiàn)大文件斷點續(xù)傳、秒傳,SpringBoot如何實現(xiàn)大文件斷點續(xù)傳、秒傳,SpringMVC如何實現(xiàn)大文件斷點續(xù)傳、秒傳,SpringCloud如何實現(xiàn)大文件斷點續(xù)傳、秒傳,
webuploader如何實現(xiàn)大文件斷點續(xù)傳、秒傳,百度webuploader如何實現(xiàn)大文件斷點續(xù)傳、秒傳,html5實現(xiàn)大文件斷點續(xù)傳、秒傳,vue如何實現(xiàn)大文件斷點續(xù)傳、秒傳,前端如何實現(xiàn)大文件斷點續(xù)傳、秒傳,JavaScript如何實現(xiàn)大文件斷點續(xù)傳、秒傳,
html5大文件斷點續(xù)傳、秒傳解決方案,html5大文件斷點續(xù)傳、加密上傳解決方案,html5大文件斷點續(xù)傳、加密存儲解決方案,html5大文件斷點續(xù)傳分片解決方案,html5大文件斷點續(xù)傳分塊解決方案,html5大文件斷點續(xù)傳分割解決方案,html5大文件斷點續(xù)傳切割解決方案,
后端我們公司項目組選的是JAVA,因為公司有自己的產(chǎn)品,所以直接使用就行了,針對客戶需求這塊是進行擴展。
客戶這邊實際上要傳的文件單個大小就有50G左右,所以需要支持斷點續(xù)傳和分片上傳,并且要支持多線程上傳,能夠充分利用帶寬資源。
之前在網(wǎng)上找過相關的資料,論壇里面也有網(wǎng)友交流過,還加過很多QQ群和微信群,但是結果都不太令人滿意。
技術選型的話用的是jquery,也是一個企業(yè)內(nèi)網(wǎng)系統(tǒng),用的是之前公司的框架,只是進行功能擴展
分片網(wǎng)上討論的很多,基本上全部都是用的HTML5的API,這個方案也不是不能用,但是在IE下面就不行了,兼容性差了點,并且也不能進行擴展,不能進行二次開發(fā),限制性非常大,我們技術同事提的要求是需要提供產(chǎn)品完整源代碼,
網(wǎng)上的文章全部都沒有提供文件夾上傳和續(xù)傳的代碼,也沒有提供數(shù)據(jù)庫操作的代碼,
另外這塊我們是要求必須提供產(chǎn)品完整源代碼,因為后面我們需要自已來維護,同時是要求能夠自主可控的
研發(fā)部門的同事調研過百度的webuploader這個組件,發(fā)現(xiàn)他實際上就是對Flash和HTML5進行了一個封裝,本質還是調的HTML5的API,之前在項目中也用過,嘗試過,但是最終都不太滿意,一個是兼容性非常差,說的是兼容IE,但是在IE用的是Flash,在很多用戶的電腦上用不了,卡頓崩潰發(fā)生的太頻繁,文件上傳的數(shù)量一多比如幾千個,前端頁面就開始卡了,用戶體驗非常差。這些問題研發(fā)部的同事都向百度反應過,但是百度webuploader那邊一直沒人回,基本上沒人管,領導說要求付費提供技術支持,那邊也是沒人回,聯(lián)系不上他們?nèi)魏稳恕?/p>
webuploader這邊連個開發(fā)人員都聯(lián)系不到,這個是怎么回事?
用戶上傳的文件比較大,有20G左右,直接用HTML傳的話容易失敗,服務器也容易出錯,需要分片,分塊,分割上傳。也就是將一個大的文件分成若干個小文件塊來上傳,另外就是需要實現(xiàn)秒傳功能和防重復功能,秒傳就是用戶如果上傳過這個文件,那么直接在數(shù)據(jù)庫中查找記錄就行了,不用再上傳一次,節(jié)省時間,實現(xiàn)的思路是對文件做MD5計算,將MD5值保存到數(shù)據(jù)庫,算法可以用MD5,或者CRC,或者SHA1,這個隨便哪個算法都行。
切片的話還有一點就是在服務器上合并,一個文件的所有分片數(shù)據(jù)上傳完后需要在服務器端進行合并操作。
視頻教程:https://www.ixigua.com/7227314770696012322
導入項目:
導入到Eclipse:http://www.ncmem.com/doc/view.aspx?id=9da9c7c2b91b40b7b09768eeb282e647
導入到IDEA:http://www.ncmem.com/doc/view.aspx?id=9fee385dfc0742448b56679420f22162
springboot統(tǒng)一配置:http://www.ncmem.com/doc/view.aspx?id=7768eec9284b48e3abe08f032f554ea2
下載示例:
https://gitee.com/xproer/up6-jsp-eclipse/tree/6.5.40/
工程
NOSQL
NOSQL示例不需要任何配置,可以直接訪問測試
創(chuàng)建數(shù)據(jù)表
選擇對應的數(shù)據(jù)表腳本,這里以SQL為例
修改數(shù)據(jù)庫連接信息
訪問頁面進行測試
文件存儲路徑
up6/upload/年/月/日/guid/filename
相關問題:
1.javax.servlet.http.HttpServlet錯誤
2.項目無法發(fā)布到tomcat
3.md5計算完畢后卡住
4.服務器找不到config.json文件
相關參考:
文件保存位置
源碼工程文檔:https://drive.weixin.qq.com/s?k=ACoAYgezAAw1dWofra
源碼報價單:https://drive.weixin.qq.com/s?k=ACoAYgezAAwoiul8gl
OEM版報價單:https://drive.weixin.qq.com/s?k=ACoAYgezAAwuzp4W0a
產(chǎn)品源代碼:https://drive.weixin.qq.com/s?k=ACoAYgezAAwbdKCskc
授權生成器:https://drive.weixin.qq.com/s?k=ACoAYgezAAwTIcFph1
*請認真填寫需求信息,我們會在24小時內(nèi)與您取得聯(lián)系。