这个产品的主要技术栈有,网易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: trueappKey: '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}//客户聊天列表页点击后要更新状态管理器中的sessionIdvue.$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}//普通用户进来后创建会话后要更新状态管理器中的sessionIdvue.$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]; }//获取到收到消息的sessionIdvar 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工具,是将本地服务映射到手机上。

转载于:https://www.cnblogs.com/windseek/p/8392200.html

h5聊天工具的开发过程及思路相关推荐

  1. 服务器如何向c winform推送信息,C局域网聊天工具消息推送实现思路与源码.doc

    C局域网聊天工具消息推送实现思路与源码 C#局域网聊天工具怎么实现? 网络通讯编程的基础便是协议,信息的发送常用的协议有面向连接的TCP协议,以及不面向连接的UDP协议TCP:Transmission ...

  2. 实现一个类似QQ的社交聊天工具

    今日科技快讯 日前,中国智能手机品牌vivo宣布,与国际足球联合会(FIFA)达成为期6年的FIFA世界杯全球赞助合作,贯穿两届比赛.这意味着vivo将连续成为2018年及2022年两届世界杯全球官方 ...

  3. python socket能做什么_用python写一个聊天小程序!和女朋友的专属聊天工具!

    原标题:用python写一个聊天小程序!和女朋友的专属聊天工具! 1.UDP简介 Internet协议集支持一个无连接的传输协议,该协议称为用户数据报协议(UDP).UDP为应用程序提供了无需建立就可 ...

  4. 【干货】超强整理19个辅助制作H5的工具/网站

    本期分享从文案策划到交互设计共包含19个工具/网站,把控你的H5制作. 关于H5制作流程 笔者接触的H5制作辅助制作工作基本构成分为文案策划.设计交流学习.制图.视频编辑与剪辑.原型.H5动效/动画制 ...

  5. 基于JAVA的聊天工具开发

         基于JAVA的聊天工具开发 转眼大四,在紧张的考研备战间隙,我开始整理大学三年来的学习资料与感悟,希望与大家分享一些我的总结与感悟. 以下的报告是大二下学期参加计算机学院工程训练后所写.这个 ...

  6. [NUAA]Python用UDP协议建立带有私聊功能的网络聊天室-建立聊天工具

    文章目录 前言 1.网络聊天室的基本架构是什么? 1.1 客户端和服务器的架构 1.2 通信协议的选择以及多线程通信 1.2.1 多线程通信 1.2.2 通信协议选择 1.3 前后端功能设计思路 1. ...

  7. Kotlin第5篇项目实战2:开发【Kotlin】版QQ2006聊天工具-关东升-专题视频课程

    Kotlin第5篇项目实战2:开发[Kotlin]版QQ2006聊天工具-420人已学习 课程介绍         本视频是智捷课堂推出的一套"Kotlin语言学习立体教程"的视频 ...

  8. WinForm实现多人聊天工具完整源码

    一.聊天工具运行截图 二.聊天工具实现思路 用事件表达多个窗体间的传值: 一.思路{ Teacher:共同都拥有一个方法:public void Display(string msg) Student ...

  9. 零基础快速打造一个属于自己的微信聊天工具

    " 零基础快速打造一个属于自己的微信聊天工具" 打开微信,我们可以和别人进行聊天,发送消息.非常方便,那微信是怎么来的呢​?这个本质的问题让人突发奇想,我们能不能做一个属于自己的微 ...

最新文章

  1. 列表渲染 wx:key 的作用、条件渲染 wx:if 与 hidden 的区别
  2. Apache HTTP服务器和支持程序 —— apachectl
  3. 那传说中的P、NP以及NPC问题
  4. Go服务迁到K8s后老抽风重启? 记一次完整的线上问题解决过程
  5. C++ 流的操作 | 初识IO类、文件流、string流的使用
  6. Java环境的正确配置你会了吗?
  7. Hadoop MapReduce InputFormat基础
  8. NYOJ-01串(dp)
  9. STL sort的危险之处
  10. MySQL 入门(二)—— MySQL理论基础
  11. html和js根据年份计算年龄,JS实现根据出生年月计算年龄
  12. 主页被锁定为 hao.360.cn
  13. Python百度文库爬虫之doc文件
  14. 西南大学计算机辅助设计试题,西南大学 1906 课程名称:(9123)《计算机辅助设计》机考 答案-奥...
  15. HDU1870 愚人节的礼物【堆栈+输入输出】
  16. 银联支付页面怎么调起 php,php银联网页支付实现方法
  17. git 清除版本库中的忽略文件
  18. Java接口的定义与实现
  19. # linux下openssl版本问题 /lib64/libcrypto.so.10: version `OPENSSL_1.0.2‘ not found
  20. 入职阿里,一位女测试工程师的心声

热门文章

  1. NGINX访问日志和错误日志
  2. scala 连接符_Scala标识符示例教程
  3. CAA创建自定义CATIA工具栏按钮和菜单
  4. C# 浏览器控件 谷歌、火狐内核
  5. 开课吧之Java常见面试题之RandomAccess接口
  6. nodejs爬虫基础(二)
  7. 戴尔PowerEdge 4路服务器全面升级 实现企业应用与核心业务工作负载的优异性能...
  8. 最小二乘原理求解线性回归方程
  9. 获得执行计划方法三-sql_trace
  10. svn ignore 命令行用法