2019独角兽企业重金招聘Python工程师标准>>>

这周项目要做一个在线聊天系统,感觉不是特别困难,原理也很简单,分享给大家。

技术

Java(Spring)+Mysql+MemCache

Spring做的是事件驱动模型,所有DB,更新缓存操作改成异步的。

MemCache存放缓存,每个用户的聊天记录缓存,好友关系维护。

需求

用户分为虚拟用户,普通用户,高级用户(在线经理人),管理员用户(客服)。

虚拟,普通用户有一个好友列表,好友列表保存着用户的好友,对于虚拟,普通用户来说,他们的好友列表只有高级用户+管理员用户。

高级用户,管理员用户来说只要是用户给我发过消息,我都能看到,并且回复。

效果图

后台提供的接口列表

|--聊天列表
   |--普通用户获取动态聊天列表,目前固定是三位,客服+经理2
   |--特殊用户获取用户对自己提问的列表
|--聊天回复
   |--直接发送消息到后台
|--获取聊天数据
   |--获取该用户跟某用户的聊天记录,带分页
|--定时检查接口
   |--检测此用户是否有新消息提示

提供接口控制器的源码:

@Controller
public class CommunicateCtrl extends BaseController {@RequestMapping("/communicate/ask")@ResponseBodypublic void doAsk(@RequestAttr ResultData resultData, Communicate model, HttpServletRequest request) throws Exception {model.checkChatIdEmpty("聊天对象Id不能为空");model.checkContentEmpty("聊天内容不能为空");model.checkContentIllegal("您的聊天内容带有敏感词");UserInfo userInfo = getUserInfo();if (null != userInfo) { // 如果聊天者已经登录model.setUserId(String.valueOf(userInfo.getUserId()));model.setMobile(userInfo.getMobile());model.setName(StringUtil.isNullOrEmpty(userInfo.getUserName()) ? "" : userInfo.getUserName());} else {model.setUserId(getUserId());if (Str.isEmpty(model.getUserId())) // 当传过来的cookie为空,则生成一个cookie,并使用虚拟userIdgenerateVirUserInfoWhenUserIdEmpty(model);}model.setStatus(1); // 未回复model.setUserType(1); // 普通用户model.setBuildTime(new Date());communicateService.save(model); // 保存DB对象resultData.setData(model);putEvent(model);}@RequestMapping("/communicate/friends")@ResponseBodypublic void doFriendList(@RequestAttr ResultData resultData, HttpServletRequest request) throws Exception {// 如果为普通用户if (null == getUserInfo() || getUserInfo().getType() != 3) {List<UserInfo> userInfos = userInfoService.getList(" and type = 3 "); // 加载特殊角色,提供在线聊天功能for (UserInfo userInfo : userInfos)userInfo.getDicMap().put("userType", 2); // userType 0 虚拟用户 1普通用户 2经纪人resultData.setData(userInfos);return;}// 特殊用户获取好友列表List<String> friendList = communicateHandle.getFriendListCache(getUserId()); // 获取好友列表List<Object> list = new ArrayList<Object>(friendList.size());for (String userId : friendList) {if (Str.isEmpty(userId))continue;Object o = null;if (ZhengzeValidate.isInteger(userId)) { // 普通用户IdUserInfo userInfo = userInfoService.getById(Integer.parseInt(userId));if (null != (o = userInfo)) {userInfo.setHeadImg(Str.isEmpty(userInfo.getHeadImg()) ? defaultImg : userInfo.getHeadImg());userInfo.getDicMap().put("userType", 1); // userType 0 虚拟用户 1普通用户 2经纪人}} else {// user.dicMap.userType// userType 0 虚拟用户 1普通用户 2经纪人o = MapBean.getNew().set("userId", userId).set("headImg", defaultImg).set("dicMap", MapBean.getNew("userType", 0));}list.add(o);}resultData.setData(list);}@RequestMapping("/communicate/check")@ResponseBodypublic void doCheck(@RequestAttr ResultData resultData, String updateStatusUserId) throws Exception {String userId = getUserId();List<MapBean> dataMapList = new ArrayList<MapBean>(); // 用户是否有新消息列表List<String> friendList = communicateHandle.getFriendListCache(userId); // 获取好友列表List<Communicate> chatsList = null;List<Communicate> unReaderList = null; // 未读消息列表,提供给前端// 循环所有好友的聊天数据,检测是否有新数据for (String friendUserId : friendList) {// 110&8_chart_list// 8&110_chart_listchatsList = communicateHandle.getChatsCache(friendUserId, userId); // 取得与每个好友的聊天记录,注意与生成key的顺序区别if (!chatsList.isEmpty()) {// 如果存在聊天数据int size = 0;String lastMsg = null;if (Str.isNotEmpty(updateStatusUserId))unReaderList = new ArrayList<Communicate>();for (Communicate communicate : chatsList) {lastMsg = communicate.getContent();if (!communicate.getUserId().equals(userId) && communicate.getStatus() == 1) { // 只查询我未读的消息,过滤我的消息size += 1;if (communicate.getUserId().equals(updateStatusUserId)) { // 如果聊天对象一致,则更新状态,并返回未读消息列表communicate.setStatus(2);// 内存与db一致Communicate communicateDB = communicateService.getById(communicate.getId());if (communicateDB.getStatus() == 2) // 如果其他线程已更新状态,这里则不返回continue;communicateDB.setStatus(communicate.getStatus());communicateService.updateById(communicateDB);unReaderList.add(communicate);}// // 如果需要更新状态 --- 性能更好的一种批量更新方式// if (Str.isNotEmpty(isUpdateStatus)) {// communicateService.updateStatus(list.get(0), " and user_id = '" + userId + "' and status = " + Communicate.STATUS_MGR_REPLY);// }}}MapBean dataMap = MapBean.getNew("userId", friendUserId, "oper", "normal"); // 返回最后的页数,操作为正常(没有新消息)// 返回新消息if (size > 0) {if (null != unReaderList && !unReaderList.isEmpty())communicateHandle.updateChatsCache(updateStatusUserId, userId, chatsList); // 更新缓存dataMap.set("oper", "new").set("msg", tl("你有:0条未读消息", size));dataMap.set("lastMsg", lastMsg).set("unReadMsgCount", size);dataMap.set("unReaderList", unReaderList);}dataMapList.add(dataMap);}}resultData.setData(dataMapList); // 设置与所有用户聊天数据// 如果出现某一个用户的聊天数据,则返回该用户的聊天数据if (Str.isNotEmpty(updateStatusUserId)) {for (MapBean dataMap : dataMapList) {if (dataMap.getString("userId").equals(updateStatusUserId)) {resultData.setData(dataMap);break;}}}}@RequestMapping("/communicate/chats")@ResponseBodypublic void doChats(@RequestAttr ResultData resultData, String chatId) throws Exception {if (Str.isEmpty(chatId))throw new RuntimeException("聊天对象Id不能为空");int pageSize = Tool.convertInt(RequestTool.getParameter("pageSize"), 10);int msgId = Tool.convertInt(RequestTool.getParameter("msgId"), 0);List<Communicate> chatsList = communicateHandle.getChatsCache(chatId, getUserId()); // 取得与每个好友的聊天记录int size = chatsList.size();int lastIndex = size - 1; // List索引可能出现 1-1=0的情况,if中做兼容if (lastIndex >= 0) { // 如果存在聊天数据if (msgId <= 0) { // 如果消息Id为空,则取最后数据N条if (size <= pageSize)resultData.setData(chatsList);elseresultData.setData(chatsList.subList(size - pageSize, size)); // 倒序,取最后一节数据return;}// 根据msgId来取数据int msgIdIndex = binarySearch(chatsList, msgId);if (msgIdIndex == -1 || msgIdIndex == 0) // -1则表示此msgId不存在,0则表示在它之前已经没有了任何数据return;int subIndex = msgIdIndex - pageSize;resultData.setData(chatsList.subList(subIndex < 0 ? 0 : subIndex, msgIdIndex)); // 取出比msgId小的Id// msgIdIndex += 1;// +1 过滤掉自己// int subSize = msgIdIndex + pageSize;// resultData.setData(chatsList.subList(msgIdIndex, subSize > size ? size : subSize)); //取出比msgId大的Id}}// 二分法查找,查找线性表必须是有序列表int binarySearch(List<Communicate> chatsList, int key) {int low = 0, high = chatsList.size() - 1, mid;while (low <= high) {mid = (low + high) >>> 1;if (key == chatsList.get(mid).getId()) {return mid;} else if (key < chatsList.get(mid).getId()) {high = mid - 1;} else {low = mid + 1;}}return -1;}void generateVirUserInfoWhenUserIdEmpty(Communicate communicate) {communicate.setUserId(UUID.randomUUID().toString().replace("-", "")); // 生成虚拟UUIDHttpUtils.addCookie(RequestTool.getResponse(), Constants.VIR_USER_ID, communicate.getUserId(), 24 * 60 * 60 * 1000 * 7); // 保存cookie一周}String getUserId() {String userId = null;if (null != getUserInfo()) {userId = getUserInfo().getUserId() + "";} else {Cookie cookie = getCookieByName(Constants.VIR_USER_ID);if (null != cookie)userId = cookie.getValue();}// debug模式可以传入用户idString id = RequestTool.getParameter("id");return isDebug() && Str.isNotEmpty(id) ? id : userId;}@SuppressWarnings("unchecked")Map<Class<?>, Set<String>> setResultJsonFilter(Class<?> clazz, Set<String> set) {Map<Class<?>, Set<String>> includeMap = (Map<Class<?>, Set<String>>) RequestTool.getRequest().getAttribute("includeMap");if (null == includeMap)RequestTool.getRequest().setAttribute("includeMap", includeMap = new HashMap<Class<?>, Set<String>>());includeMap.put(clazz, set);RequestTool.getRequest().setAttribute("jsonFilter", new ComplexPropertyPreFilter(includeMap));return includeMap;}void putEvent(Communicate model) {SpringContextUtil.getApplicationContext().publishEvent(new CommunicateEvent(model));}@AutowiredCommunicateService communicateService;@AutowiredCommunicateHandle communicateHandle;@AutowiredUserInfoService userInfoService;String defaultImg = ConfigLoader.loader.getString("user_default_img");
}

Spring异步观察者事件处理:

@Component
@SuppressWarnings("unchecked")
public class CommunicateHandle implements ApplicationListener<CommunicateEvent> {static final String chartsKey = "_chart_list";static final String friendsKey = "_friend_list";@Overridepublic void onApplicationEvent(CommunicateEvent event) {Communicate model = (Communicate) event.getSource();if (null == model.getId())communicateService.save(model); // 保存DB对象// 查询并更新自己的好友列表getAndAddFriendList(model);// 查询并更新聊天对象的好友列表getAndAddFriendList(model, "friend");// 查询并添加自己与聊天对象的记录列表getAndAddChats(model);}List<Communicate> getAndAddChats(Communicate model) {List<Communicate> list = null; // 用户所有的聊天记录try {list = getChatsCache(model.getUserId(), model.getChatId());list.add(model);Collections.sort(list); // 排序此用户的消息队列updateChatsCache(model.getUserId(), model.getChatId(), list);// 保存至缓存} catch (Exception e) {e.printStackTrace();}return list;}List<String> getAndAddFriendList(Communicate model, String... friends) {List<String> list = null; // 所有用户的好友try {list = friends.length == 0 ? getFriendListCache(model.getUserId()) : getFriendListCache(model.getChatId());if (friends.length == 0 ? !list.contains(model.getChatId()) && list.add(model.getChatId()) : !list.contains(model.getUserId()) && list.add(model.getUserId()))setCache(getKey(model, friends) + friendsKey, list); // 自动追加为好友} catch (Exception e) {e.printStackTrace();}return list;}String getKey(Communicate model, String... friends) {String key = model.getUserId();if (friends.length > 0)key = model.getChatId();return key;}public List<String> getFriendListCache(Object userId) throws Exception {List<String> list = (List<String>) MemCacheClient.get(userId + friendsKey);if (Str.isNull(list))list = new ArrayList<String>();return list;}public List<Communicate> getChatsCache(Object userId, Object chatsUserId) throws Exception {List<Communicate> list = (List<Communicate>) MemCacheClient.get(userId + "&" + chatsUserId + chartsKey);if (Str.isNull(list))list = new ArrayList<Communicate>();return list;}public boolean updateChatsCache(Object userId, Object chatsUserId, Object o) throws Exception {setCache(chatsUserId + "&" + userId + chartsKey, o); // 1296000秒 = 15天setCache(userId + "&" + chatsUserId + chartsKey, o); // 1296000秒 = 15天return true;}boolean setCache(String key, Object o) throws Exception {return MemCacheClient.set(key, 1296000, o); // 1296000秒 = 15天}@AutowiredCommunicateService communicateService;
}

项目启动时,根据聊天记录,初始化用户好友列表&用户与用户之间的聊天记录:

@Service
public class CommunicateServiceImpl extends BaseServiceImpl<Communicate, Integer> implements CommunicateService {@Overridepublic void initAllChats() {List<Communicate> allList = getList("");try {// 清除原有缓存for (Communicate communicate : allList) {communicateListener.updateChatsCache(communicate.getUserId(), communicate.getChatId(), new ArrayList<Communicate>());}// 增加聊天记录缓存for (Communicate communicate : allList) {SpringContextUtil.getApplicationContext().publishEvent(new CommunicateEvent(communicate));}} catch (Exception e) {throw new RuntimeException("在线聊天缓存初始化出现了异常:" + e.getMessage());}}@AutowiredCommunicateHandle communicateListener;}

直接将数据push到缓存中,在Spring事件监听里已经做了处理:

if (null == model.getId())communicateService.save(model); // 保存DB对象

有些代码个人感觉写的还是很精妙的,希望你们能找出来,哈哈~

优化版源码

提供接口控制器的源码:

新接口整合了friend与check接口,增加了虚拟用户转成真实用户后,经理人反查此真实用户以前的虚拟用户的聊天信息~ 增加了查询好友列表缓存功能,Boss再也不用担心程序性能~  一切依赖于缓存~~

@Controller
public class CommunicateCtrl extends BaseController {@RequestMapping("/communicate/ask")@ResponseBodypublic void doAsk(@RequestAttr ResultData resultData, Communicate model, HttpServletRequest request) throws Exception {model.checkChatIdEmpty("聊天对象Id不能为空");model.checkContentEmpty("聊天内容不能为空");model.checkContentIllegal("您的聊天内容带有敏感词");UserInfo userInfo = getUserInfo();model.setUserId(getUserId());if (null != userInfo) { // 如果聊天者已经登录model.setMobile(userInfo.getMobile());model.setName(userInfo.getUserName());} else {if (Str.isEmpty(model.getUserId())) // 当传过来的cookie为空,则生成一个cookie,并使用虚拟userIdgenerateVirUserInfoWhenUserIdEmpty(model);}model.setStatus(1); // 未回复model.setUserType(1); // 普通用户model.setBuildTime(new Date());communicateService.save(model); // 保存DB对象SpringContextUtil.getApplicationContext().publishEvent(new CommunicateEvent(model));resultData.setData(model);}@RequestMapping("/communicate/check")@ResponseBodypublic void doCheck(@RequestAttr ResultData resultData, String updateStatusUserId) throws Exception {String userId = getUserId();List<MapBean> dataMapList = new ArrayList<MapBean>(); // 用户是否有新消息列表&用户列表if (null == getUserInfo() || getUserInfo().getType() != 3) { // 如果为普通用户List<UserInfo> userInfos = communicateHandle.getCache("db_friend_list");if (null == userInfos)communicateHandle.setCache("db_friend_list", 3600, userInfos = userInfoService.getList(" and type = 3 ")); // 从DB中加载特殊角色,提供在线聊天功能,3600秒=1小时List<String> friendList = new ArrayList<String>(userInfos.size());for (UserInfo userInfo : userInfos)friendList.add(userInfo.getUserId() + "");dataMapList = getAndUpdateNewMsgList(userId, updateStatusUserId, friendList); // 用户获取特殊好友列表} elsedataMapList = getAndUpdateNewMsgList(userId, updateStatusUserId, communicateHandle.getFriendListCache(userId)); // 特殊用户获取好友列表resultData.setData(dataMapList); // 设置与所有用户聊天数据// 如果出现某一个用户的聊天数据,则返回该用户的聊天数据if (Str.isNotEmpty(updateStatusUserId)) {for (MapBean dataMap : dataMapList) {Object oUser = dataMap.get("user");if (oUser instanceof UserInfo) {if (((UserInfo) oUser).getUserId().toString().equals(updateStatusUserId)) { // 真实用户id是纯数字resultData.setData(dataMap);break;}} else {if (((MapBean) oUser).getString("userId").equals(updateStatusUserId)) { // 虚拟用户id对比resultData.setData(dataMap);break;}}// end else}// end for}// end if}@RequestMapping("/communicate/chats")@ResponseBodypublic void doChats(@RequestAttr ResultData resultData, String chatId) throws Exception {if (Str.isEmpty(chatId))throw new RuntimeException("聊天对象Id不能为空");doCheck(resultData, chatId); // 更新聊天信息int pageSize = Tool.convertInt(RequestTool.getParameter("pageSize"), 10);int msgId = Tool.convertInt(RequestTool.getParameter("msgId"), 0);List<Communicate> chatsList = communicateHandle.getChatsCache(chatId, getUserId(), getVirUserId()); // 取得与每个好友的聊天记录int size = chatsList.size();if (size > 0) { // 如果存在聊天数据if (msgId <= 0) { // 如果消息Id为空,则取最后数据N条if (size <= pageSize)resultData.setData(chatsList);elseresultData.setData(chatsList.subList(size - pageSize, size)); // 倒序,取最后一节数据return;}// 根据msgId来取数据int msgIdIndex = binarySearch(chatsList, msgId);if (msgIdIndex == -1 || msgIdIndex == 0) // -1则表示此msgId不存在,0则表示在它之前已经没有了任何数据return;int subIndex = msgIdIndex - pageSize;resultData.setData(chatsList.subList(subIndex < 0 ? 0 : subIndex, msgIdIndex)); // 取出比msgId小的Id}}// 二分法查找,查找线性表必须是有序列表int binarySearch(List<Communicate> chatsList, int key) {int low = 0, high = chatsList.size() - 1, mid;while (low <= high) {mid = (low + high) >>> 1;if (key == chatsList.get(mid).getId()) {return mid;} else if (key < chatsList.get(mid).getId()) {high = mid - 1;} else {low = mid + 1;}}return -1;}void generateVirUserInfoWhenUserIdEmpty(Communicate communicate) {communicate.setUserId(UUID.randomUUID().toString().replace("-", "")); // 生成虚拟UUIDHttpUtils.addCookie(RequestTool.getResponse(), Constants.VIR_USER_ID, communicate.getUserId(), 0); // 保存cookie永久有效}String getUserId() {String userId = null != getUserInfo() ? userId = getUserInfo().getUserId() + "" : getVirUserId();// debug模式可以传入用户idString id = RequestTool.getParameter("id");return isDebug() && Str.isNotEmpty(id) ? id : userId;}String getVirUserId() {Cookie cookie = getCookieByName(Constants.VIR_USER_ID);if (null != cookie)return cookie.getValue();return null;}List<MapBean> getAndUpdateNewMsgList(String userId, String updateStatusUserId, List<String> friendList) throws Exception {List<MapBean> dataMapList = new ArrayList<MapBean>();List<Communicate> chatsList = null;List<Communicate> unReaderList = null; // 未读消息列表,提供给前端for (String friendUserId : friendList) {if (Str.isEmpty(friendUserId))continue;String userBindVirUser = null; // 真实用户绑定的虚拟用户Object o = null;if (ZhengzeValidate.isInteger(friendUserId)) { // 普通用户IdUserInfo userInfo = communicateHandle.getCache(tl("db:0_friend_list", friendUserId));if (null == userInfo)communicateHandle.setCache(tl("db:0_friend_list", friendUserId), 3600, userInfo = userInfoService.getById(Integer.parseInt(friendUserId))); // 从DB中加载特殊角色,提供在线聊天功能,3600秒=1小时if (null != (o = userInfo)) {userInfo.setHeadImg(Str.isEmpty(userInfo.getHeadImg()) ? defaultImg : userInfo.getHeadImg());userInfo.getDicMap().put("userType", userInfo.getType()); // userType 0 虚拟用户 1普通用户 2经纪人,如果是普通用户获取的特殊用户的好友列表,则用户类型为经纪人,如果是特殊用户获取的用户列表,则获取的用户类型为普通用户userBindVirUser = Str.isNotEmpty(userInfo.getVirUserId()) ? userInfo.getVirUserId() : null; // 取出用户绑定虚拟用户}} elseo = MapBean.getNew().set("userId", friendUserId).set("headImg", defaultImg).set("dicMap", MapBean.getNew("userType", 0)); // userType 0 虚拟用户 1普通用户 2经纪人chatsList = communicateHandle.getChatsCache(friendUserId, userId, userBindVirUser); // 取得与每个好友的聊天记录int size = 0;String lastMsg = null;if (Str.isNotEmpty(updateStatusUserId))unReaderList = new ArrayList<Communicate>();for (Communicate communicate : chatsList) {lastMsg = communicate.getContent();if (!communicate.getUserId().equals(userId) && communicate.getStatus() == 1) { // 只查询我未读的消息,过滤我的消息size += 1;if (communicate.getUserId().equals(updateStatusUserId)) { // 如果聊天对象一致,则更新状态,并返回未读消息列表communicate.setStatus(2);// 内存与db一致Communicate communicateDB = communicateService.getById(communicate.getId());if (communicateDB.getStatus() == 2) // 如果其他线程已更新状态,这里则不返回continue;communicateDB.setStatus(communicate.getStatus());communicateService.updateById(communicateDB);unReaderList.add(communicate);}// // 如果需要更新状态 --- 性能更好的一种批量更新方式// if (Str.isNotEmpty(isUpdateStatus)) {// communicateService.updateStatus(list.get(0), " and user_id = '" + userId + "' and status = " + Communicate.STATUS_MGR_REPLY);// }}}MapBean dataMap = MapBean.getNew("user", o, "oper", "normal"); // 操作为正常(没有新消息)// 返回新消息if (size > 0) {if (null != unReaderList && !unReaderList.isEmpty())communicateHandle.updateChatsCache(updateStatusUserId, userId, chatsList); // 更新缓存dataMap.set("oper", "new").set("msg", tl("你有:0条未读消息", size));dataMap.set("lastMsg", lastMsg).set("unReadMsgCount", size);dataMap.set("unReaderList", unReaderList);}dataMapList.add(dataMap);}return dataMapList;}@AutowiredCommunicateService communicateService;@AutowiredCommunicateHandle communicateHandle;@AutowiredUserInfoService userInfoService;String defaultImg = ConfigLoader.loader.getString("user_default_img");}

转载于:https://my.oschina.net/linapex/blog/518651

刚刚更新:在线聊天系统设计(原理+思路+源码+效果图)相关推荐

  1. 每日分享(在线截图生成器 在线聊天对话生成网站源码)

    demo软件园每日更新资源,请看到最后就能获取你想要的: 1.高性能MySQL(第2版)pdf中文版 <高性能MySQL>汇聚了著名MySQL 专家在实践中构建大型系统的多年宝贵经验,指导 ...

  2. 基于 SpringBoot+WebSocket 无DB实现在线聊天室(附源码)

    文章目录 基于 SpringBoot+WebSocket 无DB实现在线聊天室 0 项目说明 0.1 样例展示 0.2 源码地址 1 WebSocket 简介 1.1 HTTP 1.2 WebSock ...

  3. 最新简约轻型在线聊天室留言PHP源码+可当客服

    正文: 自适应电脑/手机,数据使用txt存放,默认显示近50条聊天记录,非常轻量级. 采用jquery+ajax轮询方式,适合小型聊天环境,访问地址加?进入管理模式,发送clear清空聊天记录. 修改 ...

  4. 2023 人工智能在线聊天机器人网页HTML源码

    测试了一下就是对接的外部接口,无加密有能力的可以二开.使用教程,上传主机,服务器后解压使用,当然了,电脑解压后点击index.html也能用.

  5. 人工智能在线AI智能模型聊天AI网站系统源码

    demo软件园每日更新资源,请看到最后就能获取你想要的: ​ 1.人工智能在线AI智能模型聊天AI网站系统源码 PHP网站系统源码 API接口源码 AI网站系统源码仅23kb,实现用户管理,一键添加接 ...

  6. 基于java的在线聊天系统设计

    基于java的在线聊天系统设计 在进入20世纪90年代以后,信息量就以几何倍的数量进行增长,而互联网的出现则促进了信息的传播,21世纪更是一个信息大爆炸的时代,计算机及互联网已被广泛运用到各个领域及地 ...

  7. 类似QQ聊天软件也能用Java实现啦!(多人聊天原理附源码)

    多人聊天室原理图 源码 工具类: 该类用于关闭各种流. public class CloseUtil {public static void CloseAll(Closeable... closeab ...

  8. Python + Django开发在线考试管理系统(附源码)

    本文最终实现一个Web在线考试管理系统,可作为Python Web,Django的练手项目,也可以作为计算机毕业设计参考项目. 文章目录 系统功能需求分析 系统设计及实现思路 源码分享&系统实 ...

  9. java计算机毕业设计Vue潍坊学院宿舍管理系统设计与实现源码+mysql数据库+系统+lw文档+部署

    java计算机毕业设计Vue潍坊学院宿舍管理系统设计与实现源码+mysql数据库+系统+lw文档+部署 java计算机毕业设计Vue潍坊学院宿舍管理系统设计与实现源码+mysql数据库+系统+lw文档 ...

最新文章

  1. oracle SEQUENCE 创建, 修改,删除
  2. [概统]本科二年级 概率论与数理统计 第五讲 二元随机变量
  3. java 长轮询_java – Spring中的长轮询
  4. Linux运行级详解
  5. 阿里云助力浙江大学信息化建设,以实时数据驱动校园智能管理
  6. 记录一次java.lang.ClassCastException的java类型转换异常解决方案-附最终解决方案
  7. 初中英语听力软件测试,初中英语听力训练
  8. ExtFrame的特点与缺点
  9. svn 把本地的项目,上传到服务器端
  10. vs2015配置boost c++
  11. 算法与数据中台:网约车业务实践
  12. ab753变频器参数怎么拷贝到面板_20款常用变频器密码,想成为电工老师傅你一定需要,纯干货分享!...
  13. 联合国devnet_联合国人权可能会适用于人工智能,如果是这样,考虑一下自动驾驶汽车的奇怪案例
  14. 计算机网络局域网的组建实验报告,小型局域网组建实验报告
  15. 动态规划dp算法经典包子凑数java
  16. linux启动项修复工具,Boot Repair Tool: 可以修复与启动相关的大部分问题
  17. 同步时序逻辑电路功能分析之同步五进制加法计数器
  18. 大文件异步分片上传到Seaweed服务器
  19. Lucas定理——推导及证明
  20. 缺陷管理工具bugfree快速安装配置

热门文章

  1. 面试的算法2(C语言)(整理)
  2. 用于构建集成式桌面应用程序的新指南和新工具(转载于MSDN)
  3. 移动端web,tap与click事件
  4. 实现ARM——Linux的自动登录
  5. 求高精度幂(java)
  6. 如何学习linux编程(转载)
  7. AlphaZero进化论:从零开始,制霸所有棋类游戏
  8. Canny边缘检测原理及C#程序实现
  9. ossfs工具将OSS挂载到阿里云linux系统目录例子
  10. 用异或来交换两个变量能提快速度是错误的