h5聊天工具的开发过程及思路
这个产品的主要技术栈有,网易nim即时通信,vue-cli,muse-ui
1、在拿到这个需求时,脑袋里空的,什么想法都没有,完全懵逼,进了网易云通信的官网api查看,由于我做的是客户端的,所以重点看了客户端的api,当然,服务端的也看了一点,自己用nodejs实现的接口,后面也会贴出来。这个是api的地址:http://dev.netease.im/docs/interface/%E5%8D%B3%E6%97%B6%E9%80%9A%E8%AE%AFWeb%E7%AB%AF/NIMSDK-Web/NIM.html
这个是官网提供的初始化的代码,后来证明,这个代码很扯淡,在我的vue工程里是很扯淡的
var data = {};
var nim = new NIM({
// 初始化SDK
// debug: true
appKey: 'appKey',
account: 'account',
token: 'token',
onconnect: onConnect,
onerror: onError,
onwillreconnect: onWillReconnect,
ondisconnect: onDisconnect,
// 多端
onloginportschange: onLoginPortsChange,
// 用户关系
onblacklist: onBlacklist,
onsyncmarkinblacklist: onMarkInBlacklist,
onmutelist: onMutelist,
onsyncmarkinmutelist: onMarkInMutelist,
// 好友关系
onfriends: onFriends,
onsyncfriendaction: onSyncFriendAction,
// 用户名片
onmyinfo: onMyInfo,
onupdatemyinfo: onUpdateMyInfo,
onusers: onUsers,
onupdateuser: onUpdateUser,
// 群组
onteams: onTeams,
onsynccreateteam: onCreateTeam,
onteammembers: onTeamMembers,
onsyncteammembersdone: onSyncTeamMembersDone,
onupdateteammember: onUpdateTeamMember,
// 会话
onsessions: onSessions,
onupdatesession: onUpdateSession,
// 消息
onroamingmsgs: onRoamingMsgs,
onofflinemsgs: onOfflineMsgs,
onmsg: onMsg,
// 系统通知
onofflinesysmsgs: onOfflineSysMsgs,
onsysmsg: onSysMsg,
onupdatesysmsg: onUpdateSysMsg,
onsysmsgunread: onSysMsgUnread,
onupdatesysmsgunread: onUpdateSysMsgUnread,
onofflinecustomsysmsgs: onOfflineCustomSysMsgs,
oncustomsysmsg: onCustomSysMsg,
// 同步完成
onsyncdone: onSyncDone
}); function onConnect() {
console.log('连接成功');
}
function onWillReconnect(obj) {
// 此时说明 `SDK` 已经断开连接, 请开发者在界面上提示用户连接已断开, 而且正在重新建立连接
console.log('即将重连', obj);
}
function onDisconnect(error) {
// 此时说明 `SDK` 处于断开状态, 开发者此时应该根据错误码提示相应的错误信息, 并且跳转到登录页面
console.log('连接断开', error);
if (error) {
switch (error.code) {
// 账号或者密码错误, 请跳转到登录页面并提示错误
case 302:
break;
// 重复登录, 已经在其它端登录了, 请跳转到登录页面并提示错误
case 417:
break;
// 被踢, 请提示错误后跳转到登录页面
case 'kicked':
break;
default:
break;
}
}
}
function onError(error, obj) {
console.log('发生错误', error, obj);
} function onLoginPortsChange(loginPorts) {
console.log('当前登录帐号在其它端的状态发生改变了', loginPorts);
} function onBlacklist(blacklist) {
console.log('收到黑名单', blacklist);
data.blacklist = nim.mergeRelations(data.blacklist, blacklist);
data.blacklist = nim.cutRelations(data.blacklist, blacklist.invalid);
refreshBlacklistUI();
}
function onMarkInBlacklist(obj) {
console.log(obj.account + '被你' + (obj.isAdd ? '加入' : '移除') + '黑名单', obj);
if (obj.isAdd) {
addToBlacklist(obj);
} else {
removeFromBlacklist(obj);
}
}
function addToBlacklist(obj) {
data.blacklist = nim.mergeRelations(data.blacklist, obj.record);
refreshBlacklistUI();
}
function removeFromBlacklist(obj) {
data.blacklist = nim.cutRelations(data.blacklist, obj.record);
refreshBlacklistUI();
}
function refreshBlacklistUI() {
// 刷新界面
}
function onMutelist(mutelist) {
console.log('收到静音列表', mutelist);
data.mutelist = nim.mergeRelations(data.mutelist, mutelist);
data.mutelist = nim.cutRelations(data.mutelist, mutelist.invalid);
refreshMutelistUI();
}
function onMarkInMutelist(obj) {
console.log(obj.account + '被你' + (obj.isAdd ? '加入' : '移除') + '静音列表', obj);
if (obj.isAdd) {
addToMutelist(obj);
} else {
removeFromMutelist(obj);
}
}
function addToMutelist(obj) {
data.mutelist = nim.mergeRelations(data.mutelist, obj.record);
refreshMutelistUI();
}
function removeFromMutelist(obj) {
data.mutelist = nim.cutRelations(data.mutelist, obj.record);
refreshMutelistUI();
}
function refreshMutelistUI() {
// 刷新界面
} function onFriends(friends) {
console.log('收到好友列表', friends);
data.friends = nim.mergeFriends(data.friends, friends);
data.friends = nim.cutFriends(data.friends, friends.invalid);
refreshFriendsUI();
}
function onSyncFriendAction(obj) {
console.log('收到好友操作', obj);
switch (obj.type) {
case 'addFriend':
console.log('你在其它端直接加了一个好友' + obj);
onAddFriend(obj.friend);
break;
case 'applyFriend':
console.log('你在其它端申请加了一个好友' + obj);
break;
case 'passFriendApply':
console.log('你在其它端通过了一个好友申请' + obj);
onAddFriend(obj.friend);
break;
case 'rejectFriendApply':
console.log('你在其它端拒绝了一个好友申请' + obj);
break;
case 'deleteFriend':
console.log('你在其它端删了一个好友' + obj);
onDeleteFriend(obj.account);
break;
case 'updateFriend':
console.log('你在其它端更新了一个好友', obj);
onUpdateFriend(obj.friend);
break;
}
}
function onAddFriend(friend) {
data.friends = nim.mergeFriends(data.friends, friend);
refreshFriendsUI();
}
function onDeleteFriend(account) {
data.friends = nim.cutFriendsByAccounts(data.friends, account);
refreshFriendsUI();
}
function onUpdateFriend(friend) {
data.friends = nim.mergeFriends(data.friends, friend);
refreshFriendsUI();
}
function refreshFriendsUI() {
// 刷新界面
} function onMyInfo(user) {
console.log('收到我的名片', user);
data.myInfo = user;
updateMyInfoUI();
}
function onUpdateMyInfo(user) {
console.log('我的名片更新了', user);
data.myInfo = NIM.util.merge(data.myInfo, user);
updateMyInfoUI();
}
function updateMyInfoUI() {
// 刷新界面
}
function onUsers(users) {
console.log('收到用户名片列表', users);
data.users = nim.mergeUsers(data.users, users);
}
function onUpdateUser(user) {
console.log('用户名片更新了', user);
data.users = nim.mergeUsers(data.users, user);
} function onTeams(teams) {
console.log('群列表', teams);
data.teams = nim.mergeTeams(data.teams, teams);
onInvalidTeams(teams.invalid);
}
function onInvalidTeams(teams) {
data.teams = nim.cutTeams(data.teams, teams);
data.invalidTeams = nim.mergeTeams(data.invalidTeams, teams);
refreshTeamsUI();
}
function onCreateTeam(team) {
console.log('你创建了一个群', team);
data.teams = nim.mergeTeams(data.teams, team);
refreshTeamsUI();
onTeamMembers({
teamId: team.teamId,
members: owner
});
}
function refreshTeamsUI() {
// 刷新界面
}
function onTeamMembers(obj) {
console.log('收到群成员', obj);
var teamId = obj.teamId;
var members = obj.members;
data.teamMembers = data.teamMembers || {};
data.teamMembers[teamId] = nim.mergeTeamMembers(data.teamMembers[teamId], members);
data.teamMembers[teamId] = nim.cutTeamMembers(data.teamMembers[teamId], members.invalid);
refreshTeamMembersUI();
}
function onSyncTeamMembersDone() {
console.log('同步群列表完成');
}
function onUpdateTeamMember(teamMember) {
console.log('群成员信息更新了', teamMember);
onTeamMembers({
teamId: teamMember.teamId,
members: teamMember
});
}
function refreshTeamMembersUI() {
// 刷新界面
} function onSessions(sessions) {
console.log('收到会话列表', sessions);
data.sessions = nim.mergeSessions(data.sessions, sessions);
updateSessionsUI();
}
function onUpdateSession(session) {
console.log('会话更新了', session);
data.sessions = nim.mergeSessions(data.sessions, session);
updateSessionsUI();
}
function updateSessionsUI() {
// 刷新界面
} function onRoamingMsgs(obj) {
console.log('漫游消息', obj);
pushMsg(obj.msgs);
}
function onOfflineMsgs(obj) {
console.log('离线消息', obj);
pushMsg(obj.msgs);
}
function onMsg(msg) {
console.log('收到消息', msg.scene, msg.type, msg);
pushMsg(msg);
}
function pushMsg(msgs) {
if (!Array.isArray(msgs)) { msgs = [msgs]; }
var sessionId = msgs[0].sessionId;
data.msgs = data.msgs || {};
data.msgs[sessionId] = nim.mergeMsgs(data.msgs[sessionId], msgs);
} function onOfflineSysMsgs(sysMsgs) {
console.log('收到离线系统通知', sysMsgs);
pushSysMsgs(sysMsgs);
}
function onSysMsg(sysMsg) {
console.log('收到系统通知', sysMsg)
pushSysMsgs(sysMsg);
}
function onUpdateSysMsg(sysMsg) {
pushSysMsgs(sysMsg);
}
function pushSysMsgs(sysMsgs) {
data.sysMsgs = nim.mergeSysMsgs(data.sysMsgs, sysMsgs);
refreshSysMsgsUI();
}
function onSysMsgUnread(obj) {
console.log('收到系统通知未读数', obj);
data.sysMsgUnread = obj;
refreshSysMsgsUI();
}
function onUpdateSysMsgUnread(obj) {
console.log('系统通知未读数更新了', obj);
data.sysMsgUnread = obj;
refreshSysMsgsUI();
}
function refreshSysMsgsUI() {
// 刷新界面
}
function onOfflineCustomSysMsgs(sysMsgs) {
console.log('收到离线自定义系统通知', sysMsgs);
}
function onCustomSysMsg(sysMsg) {
console.log('收到自定义系统通知', sysMsg);
} function onSyncDone() {
console.log('同步完成');
}
2、看了api之后,就找怎样引入sdk,参考链接如下:
http://dev.netease.im/docs/product/IM%E5%8D%B3%E6%97%B6%E9%80%9A%E8%AE%AF/SDK%E5%BC%80%E5%8F%91%E9%9B%86%E6%88%90/Web%E5%BC%80%E5%8F%91%E9%9B%86%E6%88%90/%E9%9B%86%E6%88%90%E6%96%B9%E5%BC%8F
我采用的是cmd的方式引入
import SDK from 'NIM_Web_SDK.js'
const nim = SDK.NIM.getInstance({
// ...
})
引入之后毫无反应,继续往下看,如果开发者选用 webpack/babel 来打包, 那么请使用 exclude 将 SDK 文件排除, 避免 babel 二次打包引起的错误:
// Webpack 参考配置
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /NIM_Web_SDK.*\.js/,
query: {
presets: [
// ...
],
// ...
}
// ...
},
// ...
],
// ...
}
这样就成功的引入了网易的sdk
3、之后就开始进行组件的开发,开发聊天工具,我需要一个表情包的库,这个库,我去网上搜了一些,不怎么满意,其实也有一些好的,
比如twemoji,emojify,react-emoji,react-emojify,感觉也不错
总之自己找了一圈之后感觉没有什么很好的选择,就自己动手丰衣足食了
倒腾半天写出来了github地址如下:https://github.com/Windseek/vue-emoji
写的很粗糙,但是很简单,就是弄个表情图片库,然后做个json库,每个表情地址对应一个解释文字,然后组件里会提供一个解析表情与文字排版的方法。
4、组件准备好之后,就进行页面布局了,产品很简单就两个页面,一个聊天列表页,一个聊天内容页,我起的名字分别叫chatList,和chatContent,在页面布局的时候遇到一些坑,就是,点击input框的时候,要保证键盘弹出来,页面往上移动,点击表情按钮,表情层会从下面弹出来,这个时候,页面整体布局绝对不能使用绝对定位或者fixed布局,一定要使用正常的定位,relative,或者static。
5、这两个交互的页面画好之后,我就开始了后台对接,第一个列表页没什么难度,搜索,上滑分页,这些都是现成的控件,拿来用就好了,当onsession钩子里有更新时就更新数据,当然此时还要判断是在chatList页面时才能进行数据请求更新,不然的化后台会很有很大压力,之后就是chatContent页面了,这里又很多的坑,因为要在业务上区分是直接进来的,还是通过列表页点进来的,直接进来可能是客服自己跟自己聊,可能是普通用户跟客服聊,可能是外面推送的消息点进来的,所以,业务很多。。。。上代码看逻辑:
created(){
let vue=this;
//每次进来时清空store里的数据,防止聊天记录闪一下
vue.$store.commit('chatContent/clearMsgs');
//取消微信分享
wx.ready(function(){
// config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在ready函数中。
wx.hideMenuItems({
menuList: ['menuItem:share:timeline'] // 要隐藏的菜单项,只能隐藏“传播类”和“保护类”按钮,所有menu项见附录3
});
});
//根据用户信息,进行分别取历史消息
if((localStorage.getItem("csFlag")-0)){
//是客服进来的
//初始化并传入连接后的回调函数,这个回调函数在连接后,调用nim的历史消息
function nimHistory(){
nimInit(vue,_=>{
//这个to是存放在路由里的,保证刷新后聊天记录也在
//是客服进来的,客服也可能从外面微信推送的消息点入进来的,此时的to就是自己
vue.to=vue.$route.query.to||localStorage.getItem("wangyiaccid");
const to = vue.to;
vue.nim.getHistoryMsgs({
scene: 'p2p',
to: to,
done: getHistoryMsgsDone
});
function getHistoryMsgsDone(error, obj){
console.log("vue.fromHeader.img",vue.fromHeader.img);
if (!error&&obj.msgs.length!=0) {
let opt={
merge:vue.nim.mergeMsgs,
msgs:obj.msgs,
sessionId:obj.msgs[0].sessionId
}
//客户聊天列表页点击后要更新状态管理器中的sessionId
vue.$store.commit('chatContent/setSessionId',opt.sessionId);
//客户聊天列表页点击后要更新状态管理器中的消息
vue.$store.commit('chatContent/updateMsgs',opt);
//数据改变后,聊天列表要滚动到最下边
vue.$nextTick(()=>{
vue.resetScroll();
})
}else{
console.log(error);
}
}
});
}
//拿到自己的头像和昵称
function getMyNickName(){
return mlkAxiosFactory.mobileAxios.get("/vchat/user_info/"+localStorage.getItem("wangyiaccid")).then((data)=>{
vue.fromHeader.img=data.data.retData[0].localimgurl;
vue.fromHeader.nickname=data.data.retData[0].nickname;
})
}
getMyNickName(); //拿到客服头像和昵称
function getCsNickName() {
return mlkAxiosFactory.mobileAxios.get("/vchat/user_info/"+vue.to).then((data)=>{
vue.toHeader.img=data.data.retData[0].localimgurl;
vue.toHeader.nickname=data.data.retData[0].nickname;
})
}
getCsNickName();
Promise.all([getMyNickName(),getCsNickName()]).then(()=>{
nimHistory()
})
}else{
//是普通用户进来的
//连接后调用回调
function getHistoryList(){
nimInit(vue,_=>{
vue.nim.getHistoryMsgs({
scene: 'p2p',
to: vue.to,
done: getHistoryMsgsDone,
limit:vue.limit-0,
beginTime:wangyistamp
});
});
function getHistoryMsgsDone(error, obj){
if (!error&&obj.msgs.length) {
let opt={
merge:vue.nim.mergeMsgs,
msgs:obj.msgs,
sessionId:obj.msgs[0].sessionId
}
//普通用户进来后创建会话后要更新状态管理器中的sessionId
vue.$store.commit('chatContent/setSessionId',opt.sessionId);
//普通用户进来后后创建会话后要更新状态管理器中的消息
vue.$store.commit('chatContent/updateMsgs',opt);
//普通用户进来后创建会话后获取自己的头像和昵称,这样在localstorage里就有了accid
getNickName();
vue.$nextTick(()=>{
vue.resetScroll();
})
}else{
console.log("error",error)
}
}
} let tenantId=localStorage.getItem("tenantId");
let getNickName;
//拿到自己的头像和昵称
getNickName=function(){
//保证是登录状态
//更新数据,dom图后进行滚动到底部
//拿到自己的头像和昵称
mlkAxiosFactory.mobileAxios.get("/vchat/user_info/"+localStorage.getItem("wangyiaccid")).then((data)=>{
vue.fromHeader.img=data.data.retData[0].localimgurl;
vue.fromHeader.nickname=data.data.retData[0].nickname;
})
//拿到客服头像和昵称
mlkAxiosFactory.mobileAxios.get("/vchat/user_info/"+vue.to).then((data)=>{
vue.toHeader.img=data.data.retData[0].localimgurl;
vue.toHeader.nickname=data.data.retData[0].nickname;
})
}
mlkAxiosFactory.mobileAxios.get('/vchat/get_cs',{params:{tenantId}}).then((data)=>{
//拿到客服的账号信息后,赋值给to,这样在查看历史消息
vue.to=data.data.retData[0].accid;
//创建会话,并且拿到历史消息
getHistoryList();
})
}
},
6、在做chatcontent遇到很多的技术难点,以前没有遇到的,还好这次解决了,最大的就是滑动了,因为,在聊天的时候,如果有一条新的消息进来,我要判断,当前聊天者阅读到哪个位置了,如果在最后一条位置,那就往下滚动到最后,如果在上面的化,就不让滚动了。
7、仍然后chatContent页面,这个问题直接导致了我第一次上线失败,在发消息的时候,会发现串消息,就a,b同时跟c发消息,c都收到了,结果是在一个会话框里收到的,搞得跟群聊一样,然后查到半夜1点,大致知道了原因,由于自己实在没有精力弄了,就走了,然后第二天,跟身边那哥们闹了些不愉快,这个很可能导致我走掉。第二天过公司来,我讲sessionID分开来写,然后,收到消息后分组,分别进入不同的sessionId里,这样就不会错了
上代码:
function onMsg(msg) {
console.log('收到消息', msg.scene, msg.type, msg);
pushMsg(msg);
function pushMsg(msgs) {
//判断收到的消息,类型是否是数组,如果是就不做处理,如果不是就转化成数组
if (!Array.isArray(msgs)) { msgs = [msgs]; }
//获取到收到消息的sessionId
var sessionId = msgs[0].sessionId;
let opt={
//网易提供的merge方法,最好用网易的,自己写的话,会merge不到nim对象的其他属性
merge:nim.mergeMsgs,
msgs:msgs,
sessionId:sessionId
}
//拼装成网易需要的数据结构,放入状态管理器里,更新消息列表
gContext.$store.commit('chatContent/updateMsgs',opt);
}
//如果用户是客服且当前页面是chatList页面才进行接口更新,否则不更新
//此处根据route对象的name属性来进行判断页面
if(gContext.$route.name=="chatList"&&(localStorage.getItem("csFlag")-0)){
console.log("如果用户是客服且当前页面是chatList页面才进行接口更新,否则不更新");
//更新chatList列表数据,并保存到gContext对象里
gContext.mobileAxios.get('/vchat/history?openid='+openid+'&name='+name+'&tenantId='+tenantId).then((data)=>{
if(data.data&&data.data.retCode=="0000"){
gContext.listData=data.data.retData;
}
})
}
}
updateMsgs: function (state, opt) {
//从状态管理器里拿到消息,这时的状态管理器应该还没有收到消息
state.msgs = state.msgs || {};
//过滤掉新的消息放到对应的session会话里,通过state.seesionId来区分
//在聊天列表页里点击会获取一个sessionId,在普通用户里也会生成一个sessionId
//判断收到的消息,类型是否是数组,如果是就不做处理,如果不是就转化成数组
if (!Array.isArray(opt.msgs)) { opt.msgs = [opt.msgs]; }
var arr=opt.msgs.filter((item,index)=>{
return item.sessionId===state.sessionId
})
//如果当前会话什么消息都没有,就什么都不做
if(arr.length==0){
return
}
//如果有了新消息
//根据sessionid进行添加消息,此时是添加到msgs里面了,相当于维护了msgs的信息
state.msgs[opt.sessionId] = opt.merge.call(Vue.prototype.nim, state.msgs[opt.sessionId], arr) || [];
//清空排序后并且格式化后的消息
state.sortMsgs = [];
//循环新的消息列表,排序后并且进行格式化
state.msgs[opt.sessionId].forEach(item => {
let unforMatItem = Object.assign({}, item);
//格式化表情包与文字
unforMatItem.text = emoUtil.formatText(unforMatItem.text);
//格式化时间
unforMatItem.time=formatTime(unforMatItem.time);
//将格式化后的消息push进排序数组
state.sortMsgs.push(unforMatItem);
});
console.log("state.sortMsgs", state.sortMsgs)
}
8、当然,这还不算完,要能再手机上测试,手机上点击,在本地代码还能debugger这样才行,这时就用了natapp外网穿透技术,就是把本地服务映射到外网,通过外网访问本地服务,本地改动后,在手机上立马能看到,类似与react开发的那个,expo工具,是将本地服务映射到手机上。
h5聊天工具的开发过程及思路的更多相关文章
- 用Socket做一个局域网聊天工具(转)
原文:http://www.cnblogs.com/technology/archive/2010/08/15/1799858.html 程序设计成为简单的服务端和客户端之间的通信, 但通过一些方法可 ...
- Java之简单的聊天工具
今天整理资料的时候,找出自己几年前刚学Java时做过的一个简易的聊天工具,有服务器也有客户端,能发送文字消息和文件,但是用户上线并未存入数据库,而只是简单的缓存在服务器的一个数组中,所以,只要服务器一 ...
- 基于WebServices简易网络聊天工具的设计与实现
基于WebServices简易网络聊天工具的设计与实现 Copyright 朱向洋 Sunsea ALL Right Reserved 一.项目内容 本次课程实现一个类似QQ的网络聊天软件的功能:服务 ...
- 一个Java编写的小玩意儿---多人在线聊天工具
这个在线聊天工具小项目使用JAVA编写,用JAVA来做图形界面本来就是出了名的低效和丑陋.不过这不是重点.写这个小项目的目的在于串一串J2SE的知识,把当时写这个项目的时候的思路梳理一下.时间有点久了 ...
- RDIFramework.NET ━ .NET快速信息化系统开发框架 V2.8 版本━新增企业通(内部简易聊天工具)
RDIFramework.NET ━ .NET快速信息化系统开发框架 V2.8 版本 新增企业通(内部简易聊天工具) RDIFramework.NET,基于.NET的快速信息化系统开发.整合框架,给用 ...
- Web版的各种聊天工具
直到近期为止,我们经常使用的即时聊天工具(QQ.msn等)了Web版,大家不用下载庞大软件,直接打开网页就能够与自己的好友聊天,非常方便.在此将时汇总 便于大家查找 节约大 ...
- python 开发简单的聊天工具
python 太强大了,以至于它什么都可以做,哈哈,开个玩笑.但是今天要讲的真的是一个非常神奇的应用. 使用python写一个聊天工具 其实大家平时用的QQ类似的聊天工具,也是使用socket进行聊天 ...
- 基于Nodejs开发的web即时聊天工具
由于公司需要开发web即时聊天的功能,开始时我们主要的实施方法是用jquery的ajax定时(10秒)轮询向服务器请求,由于是轮询请求,对 服务器的压力比较大.我们网站上线的时间不长,访问量不是很大, ...
- 聊天工具mychat
python学习,自己写了个简单聊天工具mychat 最近在学习python,自己写了个最最简单的聊天工具mychatv0.1. 第一版,完成基本的聊天功能. GUI用的是自带的TKinter,用到的 ...
随机推荐
- 404. Sum of Left Leaves 左叶子之和
[抄题]: Find the sum of all left leaves in a given binary tree. Example: 3 / \ 9 20 / \ 15 7 There are ...
- spring+springmvc+mybatis+redis 实现两重数据缓存
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http:// ...
- Hibernate入门级实例
一.开发环境 Win8 + jdk1.7 + MyEclipse + Tomcat5.0 + MySQL 说明:其实Hibernate是非常独立的框架,根本不需要MyEclipse,Eclipse,T ...
- spring boot☞Swagger2文档构建及单元测试
首先,回顾并详细说明一下在快速入门中使用的@Controller.@RestController.@RequestMapping注解.如果您对Spring MVC不熟悉并且还没有尝试过快速入门案例,建 ...
- Web测试实践-任务进度-Day02
小组成员 华同学.郭同学.覃同学.刘同学.穆同学.沈同学 任务进度 在经过任务分配阶段后,大家都投入到了各自的任务中,以下是大家今天任务的进度情况汇总. 华同学 & 刘同学(任务1) 1.对爱 ...
- nginx与tomcat 组合 实现静态文件和jsp组合访问
主要修改nginx的配置文件: 设置代理 location /{proxy_pass http://47.94.158.2:8080;proxy_redirect off;proxy_set_head ...
- win10安装MySQL免安装版
一:下载并解压 这个就不多说了https://dev.mysql.com/downloads/mysql/,简单的很 二:MySQL根目录下新建my.ini [mysql] # 设置mysql客户端默 ...
- kcp流模式与消息模式对比
kcp的流模式,和消息模式 流模式: 更高的网络利用率 更大的传输速度 解析数据相对更复杂 消息模式: 更小的网络利用率 更小的传输速度 解析数据相对更简单 消息模式的示意图 http://www.p ...
- 一些..C#知识点总结
C# 知识点汇总 (其实C#与Java多少有区别,对于咱这个幼儿园大班生来说) 1.认识C#程序 (1)namespqce关键字 namespqce(命名空间)是C#组织代码的方式,它的作用类似于Ja ...
- 浅谈https\ssl\数字证书
全球可信的SSL数字证书申请:http://www.shuzizhengshu.com 在互联网安全通信方式上,目前用的最多的就是https配合ssl和数字证书来保证传输和认证安全了.本文追本溯源围绕 ...