添加链接描述开发一个多人聊天室的 demo,包含以下功能
1.用户注册功能:注册成功后持久化用户信息到数据库
2.用户登录功能:校验用户信息成功后,成功登录并且返回 token 信息和缓存至 Redis
3.多人聊天功能 :携带 token 链接 WebSocet 并且链接时校验 token 是否合法,链接成功后进入聊天室可以正常聊天
4.单客户端登录:一个账号只能一个客户端,后登录的客户端需把前面一个客户端踢下线,效果如下:在客户端 A 中用户 1 登录成功并且携带 token 链接 WebSocket 后继续在客户端 B 登录用户 1 账号拿到 token 也链接 WebSocet,这时客户端 A 需要接收到 WebSocket 发来踢下线消息
UserController
@RestController
@RequestMapping("api/v1/user")
public class UserController {
@Autowired
UserService userService;
/**
* 注册
* @param userDto
* @return
*/
@PostMapping("/register")
public JsonData register(@RequestBody UserDto userDto){
JsonData jsonData = userService.register(userDto);
return jsonData;
}
/**
* 登录
* @param userDto
* @return
*/
@PostMapping("/login")
public JsonData login(@RequestBody UserDto userDto){
User user = userService.login(userDto);
String token = userService.geneAccountToken(user);
return JsonData.buildSuccess(token);
}
}
UserServiceImpl
@RestController
@RequestMapping("api/v1/user")
public class UserController {
@Autowired
UserService userService;
/**
* 注册
* @param userDto
* @return
*/
@PostMapping("/register")
public JsonData register(@RequestBody UserDto userDto){
JsonData jsonData = userService.register(userDto);
return jsonData;
}
/**
* 登录
* @param userDto
* @return
*/
@PostMapping("/login")
public JsonData login(@RequestBody UserDto userDto){
User user = userService.login(userDto);
String token = userService.geneAccountToken(user);
return JsonData.buildSuccess(token);
}
}
JWTUtil
@Slf4j
public class JWTUtil {
/**
* 主题
*/
private static final String SUBJECT = "luo";
//客户端加密密钥
private static final String CLIENT_SECRET = "123abc";
//用户令牌前缀
private static final String CLIENT_TOKNE_PREFIX = "abc";
/**
* 用户token过期时间,7天
*/
public static final long CLIENT_EXPIRED = 1000 * 60 * 60 * 24 * 7;
/**
* 生成token
* @param loginAccount
* @return
*/
public static String geneClientJsonWebToken(LoginAccount loginAccount) {
if (loginAccount == null) {
throw new NullPointerException("对象参数为空");
}
log.info("loginAccount:{}",loginAccount);
String token = Jwts.builder().setSubject(SUBJECT)
//配置payload
.claim("id",loginAccount.getId())
.claim("username", loginAccount.getUsername())
.claim("password", loginAccount.getPassword())
.claim("loginDateTime", new Date())
.signWith(SignatureAlgorithm.HS256, CLIENT_SECRET).compact();
//生成token
token = CLIENT_TOKNE_PREFIX + token;
return token;
}
/**
* 解密token
* @param token
* @return
*/
public static Claims checkClientJWT(String token){
try {
final Claims claims =Jwts.parser().setSigningKey(CLIENT_SECRET)
.parseClaimsJws(token.replace(CLIENT_TOKNE_PREFIX,"")).getBody();
return claims;
}catch (Exception e){
log.error("解密失败");
return null;
}
}
WebSocketService
/**
* @author :lwj
* @date : 2023/3/23
*/
@Slf4j
@Component
@ServerEndpoint("/websocket/{token}")
public class WebSocketServer {
/**concurrent包的线程安全集合,也可以map改成set,用来存放每个客户端对应的MyWebSocket对象。*/
public static ConcurrentHashMap<String, Session> webSocketMap = new ConcurrentHashMap<>();
/**
* 连接建立时
*/
@OnOpen
public void onOpen(@PathParam("token") String token, Session session) {
//检验token是否合法
try {
//校验token是否合法
Claims claims = JWTUtil.checkClientJWT(token);
//获取用户id
String id = claims.get("id").toString();
//判断用户是否登录
if (webSocketMap.containsKey(id)){
//向断开连接的客户端发送消息
sendMessage(webSocketMap.get(id),"您已经被下线");
webSocketMap.get(id).close();
webSocketMap.remove(id);
//再次连接
webSocketMap.put(id,session);
log.info("用户登录:{}", id);
}else {
log.info("用户登录:{}", id);
//将自己的信息添加到map集合中
webSocketMap.put(id, session);
}
} catch (Exception e) {
log.error("解析token失败");
}
}
/**
* 客户端接收服务端发来的数据时
*/
@OnMessage
public void onMessage (@PathParam("token") String token, String message) throws IOException {
//校验token是否合法
Claims claims = JWTUtil.checkClientJWT(token);
//获取用户id
String id = claims.get("id").toString();
log.info("【websocket消息】收到客户端用户{}发来的消息:{}", id,message);
sendMessage(webSocketMap.get(id),message);
log.info("发送消息:{}, {}", id, message);
}
/**
* 连接关闭
*/
@OnClose
public void onClose (@PathParam("token") String token, Session session){
//校验token是否合法
Claims claims = JWTUtil.checkClientJWT(token);
//获取用户id
String id = claims.get("id").toString();
//从当前的map集合中移除该用户
webSocketMap.remove(id);
try {
session.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 聊天发生错误
*/
@OnError
public void onError (Session session, Throwable error){
try {
//关闭WebSocket下的该Seesion会话
session.close();
} catch (IOException e) {
log.error("错误原因:"+error.getMessage());
e.printStackTrace();
}
}
/**
* 使用连接发送消息
* @param session 用户的session
* @param message 发送的消息内容
*/
public void sendMessage(Session session,String message) throws IOException {
session.getBasicRemote().sendText(message);
}
}