前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >刚刚更新:在线聊天系统设计(原理+思路+源码+效果图) 顶

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

作者头像
linapex
发布2019-03-26 10:13:14
2.8K0
发布2019-03-26 10:13:14
举报

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

技术

Java(Spring)+Mysql+MemCache

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

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

需求

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

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

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

效果图
后台提供的接口列表

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

提供接口控制器的源码:

@Controller
public class CommunicateCtrl extends BaseController {
 @RequestMapping("/communicate/ask")
 @ResponseBody
 public 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,并使用虚拟userId
    generateVirUserInfoWhenUserIdEmpty(model);
  }
  model.setStatus(1); // 未回复
  model.setUserType(1); // 普通用户
  model.setBuildTime(new Date());
  communicateService.save(model); // 保存DB对象
  resultData.setData(model);
  putEvent(model);
 }
 @RequestMapping("/communicate/friends")
 @ResponseBody
 public 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)) { // 普通用户Id
    UserInfo 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")
 @ResponseBody
 public 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_list
   chatsList = 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")
 @ResponseBody
 public 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);
    else
     resultData.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("-", "")); // 生成虚拟UUID
  HttpUtils.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模式可以传入用户id
  String 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));
 }

 @Autowired
 CommunicateService communicateService;
 @Autowired
 CommunicateHandle communicateHandle;
 @Autowired
 UserInfoService 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";
 @Override
 public 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天
 }
 @Autowired
 CommunicateService communicateService;
}

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

@Service
public class CommunicateServiceImpl extends BaseServiceImpl<Communicate, Integer> implements CommunicateService {

	@Override
	public 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());
		}
	}

	@Autowired
	CommunicateHandle communicateListener;

}

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

if (null == model.getId())
   communicateService.save(model); // 保存DB对象
有些代码个人感觉写的还是很精妙的,希望你们能找出来,哈哈~
优化版源码

提供接口控制器的源码:

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

@Controller
public class CommunicateCtrl extends BaseController {

	@RequestMapping("/communicate/ask")
	@ResponseBody
	public 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,并使用虚拟userId
				generateVirUserInfoWhenUserIdEmpty(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")
	@ResponseBody
	public 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); // 用户获取特殊好友列表
		} else
			dataMapList = 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")
	@ResponseBody
	public 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);
				else
					resultData.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("-", "")); // 生成虚拟UUID
		HttpUtils.addCookie(RequestTool.getResponse(), Constants.VIR_USER_ID, communicate.getUserId(), 0); // 保存cookie永久有效
	}

	String getUserId() {
		String userId = null != getUserInfo() ? userId = getUserInfo().getUserId() + "" : getVirUserId();
		// debug模式可以传入用户id
		String 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)) { // 普通用户Id
				UserInfo 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; // 取出用户绑定虚拟用户
				}
			} else
				o = 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;
	}

	@Autowired
	CommunicateService communicateService;

	@Autowired
	CommunicateHandle communicateHandle;

	@Autowired
	UserInfoService userInfoService;

	String defaultImg = ConfigLoader.loader.getString("user_default_img");

}

(adsbygoogle = window.adsbygoogle || []).push({});

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2015/10/18 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 技术
  • 需求
  • 效果图
  • 后台提供的接口列表
  • 有些代码个人感觉写的还是很精妙的,希望你们能找出来,哈哈~
  • 优化版源码
相关产品与服务
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档