营销型网站设计价格,业务外包的优势和劣势,用html做网站,企业邮箱一般用什么文章目录 前言一、项目描述项目演示链接 二、实现的功能与操作1.登录注册2.游戏大厅线程安全问题多开处理 3.五子棋对战 三、项目测试1.测试用例2.测试技术点3.部分测试用例展示#xff08;1#xff09;注册页面#xff08;2#xff09;登录页面#xff08;3#xff09;游… 文章目录 前言一、项目描述项目演示链接 二、实现的功能与操作1.登录注册2.游戏大厅线程安全问题多开处理 3.五子棋对战 三、项目测试1.测试用例2.测试技术点3.部分测试用例展示1注册页面2登录页面3游戏大厅页面4游戏对战页面 4.项目测试的结果视频演示链接 总结 前言
五子棋对战网页应用是一个基于Web技术的在线多人五子棋游戏。该应用提供了用户注册、登录、匹配对手、对战等功能旨在为用户提供轻松愉快的游戏体验。本篇文章主要用来记录我的网页版五子棋项目包括项目介绍、实现功能、测试用例、自动化测试等。 一、项目描述
该项目支持玩家随时随地与其他在线玩家进行五子棋对战实现实时的棋局同步和即时通讯。利用 WebSocket 技术实现双向通信保证游戏过程中的实时性和即时交互提供更流畅的游戏体验。 提供游戏大厅展示在线玩家信息、排名和当前对局状态。内置匹配系统自动匹配合适的对手确保玩家能够享受到公平有趣的游戏。
技术栈 前端 使用HTML5、CSS3、JavaScript以及现代前端框架如React、Vue等构建直观、响应式的用户界面。 后端 基于Spring MVC、Spring Boot等后端框架使用WebSocket实现实时通信处理用户认证、游戏逻辑和数据存储。 数据库 使用关系型数据库MySQL存储用户信息、对战历史等数据。 实时通信 使用WebSocket技术实现玩家之间的实时通信和对战同步。 webSocket是一个应用层协议。四个核心点建立连接之后、收到消息之后、出现连接异常、出现连接关闭。客户端通过webSocket.send()发送消息服务器使用sendMessage()发送消息。
项目演示链接
二、实现的功能与操作
现在已实现注册登录功能、游戏匹配功能、下棋功能。
1.登录注册
登录主要是判定用户是不是合法登录注册这一块比登录多一个确认密码以及注册的这个人是不是在数据库中存在。 后端代码如下示例
RequestMapping(/login)public Object login(HttpServletRequest request, String username, String password) {// 1.从数据库中找到匹配用户User user userMapper.selectByName(username);System.out.println(当前登录用户user user);if (user null || !user.getPassword().equals(password)) {// 登录失败System.out.println(登录失败);return new User();}HttpSession httpSession request.getSession(true);httpSession.setAttribute(user, user);return user;}RequestMapping(/register)public Object register(String username, String password) {try {User user new User();user.setUsername(username);user.setPassword(password);userMapper.insert(user);return user;} catch (org.springframework.dao.DuplicateKeyException e) {User user new User();return user;}}注册前端代码示例 scriptfunction mysub() {var username jQuery(#username);var password jQuery(#password);var password2 jQuery(#password2);if(username.val()) {alert(请先输入用户名);username.focus();return;}if(password.val()) {alert(请先输入密码);password.focus();return;}if(password2.val()) {alert(请确认密码);password2.focus();return;}if(password2.val()!password.val()) {alert(两次密码输入不一致);password.focus();return;}jQuery.ajax({url:/user/register,type:POST,data:{username:username.val(),password:password.val()},success:function(result) {if(result result.username) {if(confirm(恭喜注册成功是否跳转到登录页面)) {location.href /login.html;}}else {alert(抱歉注册失败请重试);}}});}/script2.游戏大厅
游戏大厅需要先实现其大厅页面里面包括玩家信息、开始匹配按钮而最重要的则是一旦玩家点击开始匹配之后需要给该按钮添加一个点击事件这里就涉及我们的核心游戏匹配我们创建了两个个优先级队列玩家登录成功将该玩家加入进一个玩家在线的HashMap玩家点击匹配按钮后将其加入另一个表示游戏匹配的HashMap。这里的我们需要考虑线程安全问题和多开问题。实现思路如下图所示
游戏匹配后端代码如下示例 // 建立连接之后Overridepublic void afterConnectionEstablished(WebSocketSession session) throws Exception {// 玩家上线加入到onlineUserManagertry {//1.先获取当前用户的身份信息// webSocketSession通过addInterceptors获取httpsession中的AttributesUser user (User) session.getAttributes().get(user);//2.判定当前用户是否是已经在线状态if (onlineUserManager.getFromGameHall(user.getUserId()) ! null|| onlineUserManager.getFromGameRoom(user.getUserId()) ! null) {//说明当前用户已经登录//告知客户端重复登录MatchResponse response new MatchResponse();response.setOk(true);response.setReason(当前禁止多开);response.setMessage(repeatConnection);session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));// 直接关闭有些太激进//session.close();return;}//3.将玩家设置为在线状态onlineUserManager.enterGameHall(user.getUserId(), session);System.out.println(玩家 user.getUsername() 进入游戏大厅);} catch (NullPointerException e) {System.out.println([MatchAPI.afterConnectionEstablished]当前用户未登录);}}// 处理传输信息Overrideprotected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {// 处理开始匹配请求和处理停止匹配请求User user (User) session.getAttributes().get(user);// 获取客户端发送给服务器的数据String payload message.getPayload();// 将JSON搁置的字符串转换为对象MatchRequest request objectMapper.readValue(payload, MatchRequest.class);MatchResponse response new MatchResponse();if (request.getMessage().equals(startMatch)) {//1进入匹配队列//2创建一个类表示匹配队列把当前用户加进去matcher.add(user);response.setOk(true);response.setMessage(startMatch);} else if (request.getMessage().equals(stopMatch)) {// 1退出匹配队列// 2创建一个类表示匹配队列把当前用户从队列中移除matcher.remove(user);// 移除之后返回一个响应给客户端response.setOk(true);response.setMessage(stopMatch);} else {// 非法情况response.setOk(false);response.setReason(非法的匹配请求);}String jsonString objectMapper.writeValueAsString(response);session.sendMessage(new TextMessage(jsonString));}// 处理传输异常Overridepublic void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {// 玩家下线从 onlineUserManager 中删除try {User user (User) session.getAttributes().get(user);WebSocketSession tmpSession onlineUserManager.getFromGameHall(user.getUserId());// 保证退出的人和登录的人是一个人if (tmpSession session) {onlineUserManager.exitGameHall(user.getUserId());System.out.println(玩家 user.getUsername() 退出游戏大厅);}// 如果玩家正在匹配中而 websocket 连接断开matcher.remove(user);} catch (NullPointerException e) {System.out.println([MatchAPI.handleTransportError]当前用户未登录);}}// 处理传输关闭Overridepublic void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {try {// 玩家下线从 onlineUserManager 中删除User user (User) session.getAttributes().get(user);WebSocketSession tmpSession onlineUserManager.getFromGameHall(user.getUserId());// 保证退出的人和登录的人是一个人if (tmpSession session) {onlineUserManager.exitGameHall(user.getUserId());System.out.println(玩家 user.getUsername() 退出游戏大厅);}// 如果玩家正在匹配中而 websocket 连接断开matcher.remove(user);} catch (NullPointerException e) {System.out.println([MatchAPI.afterConnectionClosed]当前用户未登录);}}线程安全问题
我们对玩家进入游戏使用的是一个HashMap玩家点击匹配按钮后被加入另一个HashMap而我们的系统涉及多个用户同时使用则多线程访问同一个HashMap容易产生线程安全问题这里我们将HashMap改为ConcurrentHashMap来处理线程安全问题ConcurrentHashMap是线程安全的它通过使用锁分段segmented-locking技术来提高并发访问性能。它允许多个修改操作并发进行而不会导致数据不一致。
多开处理
1一个账户多个地方登录那这种对于我们系统是不允许存在的我们通过seesion判断当前用户是否存在并且不允许一个客户端同时匹配两个用户。 2我的用户是按照分数划分为三个梯队由于 handlerMatch 是在单独的线程中调用因此要考虑到访问队列的线程安全问题.需要加上锁。
3.五子棋对战
当游戏匹配已经有两个或两个以上的玩家则会匹配成功进入同一个游戏房间进行游戏对战。进入到游戏房间这里的棋盘我们是用canvas画上去的双方下棋的时候使用websocket给双方用户通知提示当前对手信息重写websocket四个核心方法也要判断游戏输赢后通知给玩家。实现过程如下图所示 重写websocket的四个核心方法并实现通知玩家谁输谁赢的方法noticeThatUserWin。 游戏对战后端代码如下示例
Overridepublic void afterConnectionEstablished(WebSocketSession session) throws Exception {GameReadyResponse response new GameReadyResponse();// 1.先获取到用户的身份信息从httpsession中拿到当前用户的对象User user (User) session.getAttributes().get(user);if (user null) {response.setOk(false);response.setReason(用户尚未登录);session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));return;}// 2.判断当前用户是否已经进入房间Room room roomManager.getRoomByUserId(user.getUserId());if (room null) {// 该玩家还没有匹配到对手不应该进入房间response.setOk(false);response.setReason(用户尚未匹配到);session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));return;}// 3.判断当前是不是多开用户if (onlineUserManager.getFromGameHall(user.getUserId()) ! null|| onlineUserManager.getFromGameRoom(user.getUserId()) ! null) {response.setOk(true);response.setReason(禁止多开游戏页面);response.setMessage(repeatConnection);session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));return;}// 4.设置当前玩家上线onlineUserManager.enterGameRoom(user.getUserId(), session);// 5.把两个玩家加入到游戏房间中synchronized (room) {if (room.getUser1() null) {room.setUser1(user);// 把先连入房间的玩家作为先手方room.setWhiteUser(user.getUserId());System.out.println(玩家1 user.getUsername() 进入游戏房间已经准备就绪);return;}if (room.getUser2() null) {// 进入到这里说明user1已经进入房间room.setUser2(user);System.out.println(玩家2 user.getUsername() 进入游戏房间已经准备就绪);// 两个玩家就绪后// 通知玩家1noticeGameReady(room, room.getUser1(), room.getUser2());// 通知玩家2noticeGameReady(room, room.getUser2(), room.getUser1());return;}}// 6.此处如果又有玩家尝试连接同一个房间理论上是不存在的response.setOk(false);response.setReason(当前房间已满您不能加入房间);session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));}private void noticeGameReady(Room room, User thisUser, User thatUser) throws IOException {GameReadyResponse resp new GameReadyResponse();resp.setOk(true);resp.setReason();resp.setMessage(gameReady);resp.setReason(room.getRoomId());resp.setThisUserId(thisUser.getUserId());resp.setThatUserId(thatUser.getUserId());resp.setWhiteUser(room.getWhiteUser());// 把当前的响应数据传回给对应的玩家WebSocketSession webSocketSession onlineUserManager.getFromGameRoom(thisUser.getUserId());webSocketSession.sendMessage(new TextMessage(objectMapper.writeValueAsString(resp)));}Overrideprotected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {// 1.从session里拿到当前用户信息User user (User) session.getAttributes().get(user);if (user null) {System.out.println([handleTextMessage] 当前玩家尚未登录);return;}// 2.根据玩家id获取到房间对象Room room roomManager.getRoomByUserId(user.getUserId());// 3.通过room对象来处理这次具体的请求room.putChess(message.getPayload());}Overridepublic void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {User user (User) session.getAttributes().get(user);if (user null) {return;}WebSocketSession exitSession onlineUserManager.getFromGameRoom(user.getUserId());if (session exitSession) {onlineUserManager.exitGameRoom(user.getUserId());}System.out.println(当前用户 user.getUsername() 游戏房间连接异常);// 通知对手获胜了noticeThatUserWin(user);}Overridepublic void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {User user (User) session.getAttributes().get(user);if (user null) {return;}WebSocketSession exitSession onlineUserManager.getFromGameRoom(user.getUserId());if (session exitSession) {onlineUserManager.exitGameRoom(user.getUserId());}System.out.println(当前用户 user.getUsername() 离开游戏房间);// 通知对手获胜了noticeThatUserWin(user);}// 判断输赢并通知给玩家private void noticeThatUserWin(User user) throws IOException {// 1.根据当前玩家找到玩家所在的房间Room room roomManager.getRoomByUserId(user.getUserId());if (room null) {System.out.println(当前房间已经释放无需通知对手);return;}// 2.根据房间找到对手User thatUser (user room.getUser1()) ? room.getUser2() : room.getUser1();// 3.找到对手的在线状态WebSocketSession webSocketSession onlineUserManager.getFromGameRoom(thatUser.getUserId());if (webSocketSession null) {// 对手也掉线了System.out.println(对手已经掉线无需通知);return;}// 4.构造一个响应通知对手你是获胜方GameResponse resp new GameResponse();resp.setMessage(putChess);resp.setUserId(thatUser.getUserId());resp.setWinner(thatUser.getUserId());webSocketSession.sendMessage(new TextMessage(objectMapper.writeValueAsString(resp)));// 5.更新玩家分数信息int winUserId thatUser.getUserId();int loseUserId user.getUserId();userMapper.userWin(winUserId);userMapper.userLose(loseUserId);// 6.释放房间对象roomManager.remove(room.getRoomId(), room.getUser1().getUserId(), room.getUser2().getUserId());}三、项目测试
1.测试用例 2.测试技术点
测试工具Selenium4Junit5 1使用定位元素定位、元素操作findElement、cssSelector、sendKeys实现页面交互 2获取页面相关元素并使用断言Assertions判断元素或者文本信息与预期是否符合 3使用BeforeEach、BeforeAll、AfterAll等注解降低代码冗余 4使用参数化方法CsvSource批量测试多组数据 5使用Order注解设置测试内部执行顺序 7使用套件suit将测试用例连起来以便批量运行测试用例。
3.部分测试用例展示
1注册页面
根据对注册页面的测试用例编写分别对注册页面可否正常打开、注册失败、注册成功进行测试。 这里写出测试注册成功的逻辑 1首先清空登录框以防多组登录 2使用参数测试将CsvSource准备好的多组测试数据使用cssSelector选择器选中输入框用sendKey输入准备好的数据 3输入完注册信息后再选中登录按钮后用click点击注册按钮 4由于点击注册按钮后会alert出一个弹窗提示所以此时将driver切换到alert 5切换到alert后提取到弹窗信息根据弹窗信息判断是否注册成功 6注册成功后跳转到登录页面此时使用写好的截图方法记录下此刻。 ParameterizedTestCsvSource({刘云,123,1234})Order(2)public void registerFail(String name, String password, String password2) throws IOException {//清空登录框以防多组登录driver.findElement(By.cssSelector(#username)).clear();driver.findElement(By.cssSelector(#password)).clear();driver.findElement(By.cssSelector(#password2)).clear();//进行注册driver.findElement(By.cssSelector(#username)).sendKeys(name);driver.findElement(By.cssSelector(#password)).sendKeys(password);driver.findElement(By.cssSelector(#password2)).sendKeys(password2);driver.findElement(By.cssSelector(#submit)).click();//driver转到alertAlert alert driver.switchTo().alert();//获取弹窗中的提示信息String actual alert.getText();if (actual.equals(请先输入用户名)) {System.out.println(用户名为空);} else if (actual.equals(请先输入密码)){System.out.println(密码为空);} else if (actual.equals(请确认密码)) {System.out.println(再次输入密码为空);} else if (actual.equals(两次密码输入不一致)) {System.out.println(两次密码不一致);} else if(actual.equals(抱歉注册失败请重试)){System.out.println(已有该用户请重试);} else if(actual.equals(恭喜注册成功是否跳转到登录页面)) {System.out.println(注册成功跳转到登录页面);} else {System.out.println(注册异常);}alert.accept();getScreenShot(getClass().getName());}2登录页面
根据测试用例我们对登录页面是否正常、登录失败、登录成功分别测试。 这里写出测试登录失败的逻辑 1清空登录框。防止多组登录 2登录使用cssSelector选择器和sendkeys输入文本csvSource准备好测试数据 3获取弹窗和弹窗中的提示信息 4使用assertion判断实际信息和提示信息是否一致一致则登录成功。 ParameterizedTestCsvSource({张三,1234})Order(2)public void loginFail(String name, String password) throws IOException {//登录失败try {// 清空登录框以防多组登录driver.findElement(By.cssSelector(#username)).clear();driver.findElement(By.cssSelector(#password)).clear();//登录driver.findElement(By.cssSelector(#username)).sendKeys(name);driver.findElement(By.cssSelector(#password)).sendKeys(password);getScreenShot(getClass().getName());driver.findElement(By.cssSelector(#submit)).click();//获取警告框文本String expect 登录失败;Alert alert driver.switchTo().alert();String actual alert.getText();System.out.println(actual);Assertions.assertEquals(expect, actual);alert.accept();} catch (TimeoutException e) {System.out.println(登录失败未出现警告框);} catch (UnhandledAlertException e) {System.out.println(警告框文本与预期不符 e.getMessage());}}3游戏大厅页面
根据游戏大厅的测试用例编写页面加载正常、检测用户登录、匹配按钮的测试代码。 其匹配按钮的测试代码逻辑如下 1由于点击匹配按钮需要是登录的状态下所以先进行用户的登录 2登录后点击匹配按钮 3查看点击后的匹配按钮的状态是否发生变化 4再次点击按钮 5查看按钮是否恢复为开始匹配状态。 TestOrder(3)public void isMatch() throws IOException, InterruptedException {// 1.先登录driver.get(http://127.0.0.1:8081/login.html);driver.findElement(By.cssSelector(#username)).clear();driver.findElement(By.cssSelector(#password)).clear();driver.findElement(By.cssSelector(#username)).sendKeys(张三);driver.findElement(By.cssSelector(#password)).sendKeys(123);driver.findElement(By.cssSelector(#submit)).click();// 2.进入到游戏大厅页面后点击匹配按钮driver.findElement(By.cssSelector(#match-button)).click();// 3.获取点击匹配后的信息String actual 匹配中...(点击停止);getScreenShot(getClass().getName());String clickAfter driver.findElement(By.cssSelector(#match-button)).getText();// 4.判断Assertions.assertEquals(clickAfter, actual);// 5.再次点击driver.findElement(By.cssSelector(#match-button)).click();String clickAgain driver.findElement(By.cssSelector(#match-button)).getText();// 6.判断actual 开始匹配;Assertions.assertEquals(clickAgain, actual);getScreenShot(getClass().getName());}4游戏对战页面
根据测试用例对游戏匹配成功后游戏对战页面进行测试主要测试点是页面上的棋盘和显示屏元素。 测试步骤 1防止多开创建两个浏览器驱动edgeDriver、chromeDriver) 2分别在两个浏览器上登录不同的账户 3登录成功后进入游戏大厅页面分别点击开始匹配按钮 4匹配成功后进入游戏对战页面寻找棋盘和显示屏两个标志元素。 // 创建驱动private static EdgeDriver edgeDriver createEdgeDriver();private static ChromeDriver chromeDriver createChromeDriver();// 将两个不同系统的用户匹配进同一游戏房间public void userLogin() {// user1edgeDriver.get(http://127.0.0.1:8081/login.html);edgeDriver.findElement(By.cssSelector(#username)).sendKeys(六1);edgeDriver.findElement(By.cssSelector(#password)).sendKeys(123);edgeDriver.findElement(By.cssSelector(#submit)).click();// user2chromeDriver.get(http://127.0.0.1:8081/login.html);chromeDriver.findElement(By.cssSelector(#username)).sendKeys(王五);chromeDriver.findElement(By.cssSelector(#password)).sendKeys(123);chromeDriver.findElement(By.cssSelector(#submit)).click();// user1和user2进同一游戏房间edgeDriver.findElement(By.cssSelector(#match-button)).click();chromeDriver.findElement(By.cssSelector(#match-button)).click();}/*** 测试游戏对战页面* 检查点有棋盘、提示版元素*/TestOrder(1)public void testGameBoard() {// 不同系统的两个用户匹配到同一个房间userLogin();// 测试游戏对战页面edgeDriver.findElement(By.cssSelector(#chess));chromeDriver.findElement(By.cssSelector(#chess));}4.项目测试的结果
视频演示链接 完整项目代码可联系博主。
总结
这篇文章记录了五子棋项目现在已实现的功能及web页面自动化测试后续将对该项目扩充聊天功能同一个房间的用户可以发送消息及测试方法。