571
社区成员
发帖
与我相关
我的任务
分享
主要有三种数据实体:




采用MongoDB数据库保存这些实体,它是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。它支持的数据结构非常松散,是类似json的bson格式,因此可以存储比较复杂的数据类型。MongoDB最大的特点是它支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能。
前端主要包括三个页面:登录页面、注册页面、聊天页面(主页面)。
前端页面主要通过 Vue 框架实现:
后端使用 Java + SpringBoot框架实现。
后端采用经典的三层架构:Controller层负责接收前端的请求并将结果发送给前端;Service层负责具体的业务实现;Repository层负责与数据库的交互。
接口设计尽量采用了RESTful风格。
(1)UserController
@RequestMapping("/users")
public class UserController {
......
/* 登录 */
@PostMapping("login")
public JsonResult<User> login(@RequestBody LoginUserInfo loginUserInfo) {......}
/* 注册 */
@PostMapping("register")
public JsonResult<User> register(@RequestBody RegisterUserInfo registerUserInfo) {......}
/* 获取用户加入的全部群聊信息 */
@GetMapping("{userId}/groups")
public JsonResult<List<Group>> getAllGroups(@PathVariable("userId") String userId) {......}
/* 加入一个群聊 */
@PostMapping("{userId}/groups/{groupId}")
public JsonResult<Group> joinGroup(@PathVariable("userId") String userId, @PathVariable("groupId") String groupId) {......}
/* 退出一个群聊 */
@DeleteMapping("{userId}/groups/{groupId}")
public JsonResult<Boolean> removeGroup(@PathVariable("userId") String userId, @PathVariable("groupId") String groupId) {......}
}
(2)GroupController
@RequestMapping("/groups")
public class GroupController {
......
/* 获取某个群聊的信息 */
@GetMapping("{id}")
public JsonResult<GroupDetail> getGroup(@PathVariable("id") String groupId) {......}
/* 获取名字包含关键词的群聊信息 */
@GetMapping("")
public JsonResult<List<Group>> searchGroups(@PathParam("keywords") String keywords) {......}
/* 创建一个群聊 */
@PostMapping("")
public JsonResult<Group> createGroup(@RequestBody NewGroupInfo groupInfo) {......}
}
(1)登录

/* UserController */
@PostMapping("login")
public JsonResult<User> login(@RequestBody LoginUserInfo loginUserInfo) {
User user = userService.login(loginUserInfo);
if (user == null) {
return ResultTool.fail(ResultCode.USER_CREDENTIALS_ERROR, new User());
} else {
return ResultTool.success(user);
}
}
/* UserService */
public User login(LoginUserInfo userInfo) {
return userRepository.findByAccountAndPassword(userInfo.getAccount(), userInfo.getPassword());
}
(2)注册

/* UserController */
@PostMapping("register")
public JsonResult<User> register(@RequestBody RegisterUserInfo registerUserInfo) {
User user = userService.register(registerUserInfo);
if (user == null) {
return ResultTool.fail(ResultCode.COMMON_FAIL, new User());
} else {
return ResultTool.success(user);
}
}
/* UserService */
public User register(RegisterUserInfo userInfo) {
User user = new User();
BeanUtil.copyProperties(userInfo, user);
user.setGroups(new ArrayList<>());
return userRepository.insert(user);
}
(3)加入群聊

public void joinGroup(String userId, String groupId) {
/* 在群聊中加入此用户 */
Group group = groupRepository.findById(groupId).orElse(new Group());
User user = userRepository.findById(userId).orElse(new User());
group.getMembers().add(new GroupUser(userId, user.getName()));
groupRepository.save(group);
/* 在用户信息中加入群聊 */
user.getGroups().add(groupId);
userRepository.save(user);
}
(4)离开群聊

public Boolean leaveGroup(String userId, String groupId) {
/* 从群聊中删除此用户 */
Group group = groupRepository.findById(groupId).orElse(null);
if (group == null) {return true;}
List<GroupUser> members = group.getMembers();
members.removeIf(user -> user.getId().equals(userId));
group.setMembers(members);
/* 如果群聊中没人了,删除群聊 */
if (members.size() == 0) {
groupRepository.deleteById(groupId);
} else {
groupRepository.save(group);
}
/* 从用户信息中删除群聊 */
User user = userRepository.findById(userId).orElse(null);
if (user == null) {return false;}
List<String> groups = user.getGroups();
groups.remove(groupId);
userRepository.save(user);
return true;
}
(5)创建群聊

public Group createGroup(NewGroupInfo newGroup) {
Group group = new Group();
BeanUtil.copyProperties(newGroup, group);
group.setAdminNickname(group.getAdminName());
/* 初始化群成员 */
List<GroupUser> members = new ArrayList<>();
members.add(new GroupUser(newGroup.getAdminId(), newGroup.getAdminName()));
group.setMembers(members);
/* 初始化群消息 */
Message message = new Message();
messageRepository.insert(message);
group.setMessagesId(message.getId());
/* 插入群组 */
groupRepository.insert(group);
/* 群主新增群组 */
mongoTemplate.updateMulti(
new Query(Criteria.where("_id").is(newGroup.getAdminId())),
new Update().push("groups").value(group.getId()),
"users");
return group;
}
(6)聊天
聊天功能的实现使用到了WebSocket库,它是一种在单个TCP连接上进行全双工通信的协议,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
在SpringBoot框架中,只需完成一些对接口的编程,框架就可以自动在合适的时候调用这些接口。
创建一个MyWebSocket类,它有一个Map类型的静态成员变量clients,其中存放了当前所有连接到服务器的会话,键为用户ID,值为会话信息。
MyWebSocket类还实现了WebSocket的一系列接口:
这里着重讲述onMessage的实现。
websocket请求路径为“/websocket/{用户ID}”,用户发来的消息格式为:<群聊ID>#<消息内容>,用户ID可以从请求路径参数中获得。
系统收到用户发来的消息后,取出用户ID、群聊ID、消息内容。然后根据群聊ID从数据库中取得群聊信息,再从群聊信息中获得所有群聊成员ID,最后将消息依次转发给这些群员。转发的消息格式为“<消息发送者ID>#<消息内容>”
@OnMessage
public void onMessage(String message, @PathParam("userId") String userId) {
int idx = message.indexOf("#");
String groupId = message.substring(0, idx);
message = message.substring(idx + 1);
/* 消息保存至数据库 */
Message.Record record = messageService.storeMessage(userId, message, groupId);
/* 向在线用户群发消息 */
Query query = new Query(Criteria.where("_id").is(groupId));
query.fields().include("members");
Group group = mongoTemplate.findOne(query, Group.class, "groups");
message = groupId + "#" + JSONUtil.toJsonStr(record);
for (GroupUser member: group.getMembers()) {
String memberId = member.getId();
if (clients.containsKey(memberId)) {
clients.get(memberId).getAsyncRemote().sendText(message);
}
}
}
(1)注册

(2)登录

(3)加入群聊

(4)聊天

作者:NP368