网页五子棋——项目测试

目录

测试用例设计

功能测试

注册功能测试

正常注册

异常注册

登录功能测试

正常登录

异常登录

匹配功能测试

对战功能测试

自动化测试

引入依赖

Utils

注册测试

登录测试

匹配测试

RunTest

界面测试

性能测试

总结


测试用例设计

在本篇文章中,主要进行功能测试、界面测试和性能测试

功能测试

注册功能测试

正常注册

我们以 用户名:李四 密码:123456为例进行注册

输入用户名和密码,点击注册按钮后,页面成功跳转至登录页面

异常注册

我们首先来看 用户名为空的情况:

仅输入密码点击注册:

我们可以发现,虽然弹出了提示信息,但是提示的异常信息过多,其中大多数信息并不是客户端所需要的,因此,我们需要对其进行修改

为什么会打印上述错误信息呢?

由于我们使用**@Validated** 注解来校验参数,而这些参数未通过验证时,就抛出了 MethodArgumentNotValidException 异常,而 MethodArgumentNotValidException被作为未知异常进行捕获:

在处理未知异常时直接将错误信息返回给了前端

因此,我们可以对 MethodArgumentNotValidException 异常进行处理,当捕获到 MethodArgumentNotValidException 异常时,表明传递的参数出现异常,此时我们仅返回我们之前指定的 message信息即可

GlobalErrorCodeConstants中添加全局错误码:

java 复制代码
    ErrorCode BAD_REQUEST = new ErrorCode(400, "客户端请求错误");

GlobalExceptionHandler中添加:

java 复制代码
    /**
     * 捕获 @Valid / @Validated 注解校验异常
     * @param e
     * @return
     */
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public CommonResult<?> validationException(MethodArgumentNotValidException e) {
        // 打印错误日志
        log.info("MethodArgumentNotValidException: ", e);
        // 获取所有字段验证错误
        String errors = e.getBindingResult()
                .getFieldErrors()
                .stream()
                .map(error -> error.getDefaultMessage())
                .collect(Collectors.joining(", "));
        // 构造异常情况下的返回结果
        return CommonResult.fail(GlobalErrorCodeConstants.BAD_REQUEST.getCode(), errors);
    }

调用e.getBindingResult().getFieldErrors() 可以获取所有错误列表 ,再遍历这些错误,获取每个错误的错误消息 ,最后,再使用Collectors.joining(", "),将所有的错误消息连接成一个字符串,以 , 作为分隔符

此时的提示信息就简洁清晰了许多:

此外,我们也可以在客户端发送请求之前对用户名和密码进行校验, 从而提升用户体验并减少不必要的服务器请求:

javascript 复制代码
    <script>
          let btn = document.querySelector('#submit');
          btn.onclick = function() {
            let name =  $("#name").val();
            let password = $("#password").val();
            if (name == "") {
                alert("用户名不能为空");
                return;
            }
            if (null == "") {
                alert("用户密码不能为空");
                return;
            }
            $.ajax({
                url: "/register",
                type: "POST",
                contentType: 'application/json',
                data: JSON.stringify({
                    name: name,
                    password: password,
                }),
                success: function(result) {
                    if(result.code == 200) {
                        // 注册成功,跳转至登录页面
                        location.assign("login.html");
                    }else {
                        alert(result.errorMessage);
                    }
                }
            });
          }
    </script>

我们继续看注册已存在用户的情况:

再次注册 用户名:李四 密码:123456:

此时直接抛出了 SQLIntegrityConstraintViolationException异常,提示插入一条新记录时,发生了违反唯一性约束的错误

这是因为我们在数据入库之前并未对用户名进行校验

添加用户名校验:

java 复制代码
    @Override
    public UserRegisterResultDTO register(UserRegisterParam param) {
        // 参数校验
        if (userMapper.selectByUserName(param.getName()) != null) {
            throw new ServiceException(ServiceErrorCodeConstants.USER_INFO_EXISTS);
        }
        if (!checkPassword(param.getPassword())) {
            throw new ServiceException(ServiceErrorCodeConstants.PASSWORD_CHECK_ERROR);
        }
        // 数据入库
        UserDO userDO = new UserDO();
        userDO.setUserName(param.getName());
        userDO.setPassword(SecurityUtil.encipherPassword(param.getPassword()));
        userMapper.insert(userDO);
        // 构造响应并返回
        UserRegisterResultDTO registerResultDTO = new UserRegisterResultDTO();
        registerResultDTO.setUserId(userDO.getId());
        return registerResultDTO;
    }

添加 service 层错误码:

java 复制代码
ErrorCode USER_INFO_EXISTS = new ErrorCode(102, "用户已存在");

此时再次尝试注册:

接着,我们继续看用户密码为空的情况:

提示信息正确

密码长度过长或过短:

此时的提示信息并不准确:

在密码校验失败时,抛出自定义的PASSWORD_CHECK_ERROR 异常:

因此,我们对异常信息进行修改,分别定义注册和登录密码校验异常信息

此外,在对密码进行校验时,我们仅对密码长度进行了校验

但除了对长度进行校验,我们还应该对其中的字符进行校验,防止其中出现中文或其他非 ASCII 字符的情况,从而出现编码问题,影响用户体验或导致登录失败

因此,我们校验密码为 6 - 12 位,且仅能使用数字或字母:

java 复制代码
    private boolean checkPassword(String password) {
        if (!StringUtils.hasText(password)) {
            return false;
        }
        // 使用正则表达式校验密码长度应为 6-12 位,且只包含数字和字母
        String regex= "^[0-9A-Za-z]{6,12}$";
        return Pattern.matches(regex, password);
    }

登录功能测试

同样的,我们在客户端发送请求之前对用户名和密码进行校验:

正常登录

使用注册的 用户名和密码进行登录:

输入用户名和密码,点击登录按钮后,页面成功跳转至游戏大厅页面

异常登录

用户名为空或用户名错误:

密码为空或密码错误:

用户多开:

再次登录 李四 账号:

点击确定后跳转至登录页面:

匹配功能测试

两个天梯分数相近的玩家进行匹配:

匹配成功,并显示对手信息

天梯分数较高玩家匹配天梯分数较低玩家:

此时,两名玩家分别加入了不同的匹配队列,因此,并不能进行匹配

玩家在匹配过程中取消匹配:

点击取消匹配后,将玩家从对应队列中移除

异常情况:

玩家在匹配过程中退出游戏房间:

将玩家从对应匹配队列中移除

对战功能测试

玩家1进入游戏房间后,等待对手进入房间:

两名玩家均进入游戏房间后,开始游戏:

玩家轮流落子:

胜负判定:

异常情况:

一方玩家中途退出游戏房间:

匹配成功后,玩家1在等待玩家2过程中退出游戏房间:

可以看到,玩家2成功进入游戏房间,但此时玩家1已退出游戏房间

因此,我们需要对对应逻辑进行修改:

在将玩家2加入游戏房间时,我们并未对玩家1的在线状态进行判断,此时,就导致了玩家1已经离开游戏房间,但玩家2仍进入游戏房间,且开始了游戏

修改后端对应逻辑:

修改删除逻辑:

添加错误码:

java 复制代码
ErrorCode GET_RIVAL_ERROR = new ErrorCode(302, "获取对手信息失败");

匹配成功后,玩家1和玩家2均未加入游戏房间:

若匹配成功后,玩家1 和 玩家2 均为加入游戏房间,此时,就会存在空房间

在两个玩家匹配成功时,我们为其创建了游戏房间:

若两个玩家都不加入当前游戏房间,此时空的游戏房间就会一直存在,但这些空房间并不会再次被使用,因此,我们需要对这些空房间进行处理

我们使用定时器定期对这些空房间进行处理:

启用定时任务:

为了保证不误删刚创建的新房间 ,我们还需要对游戏房间的创建时间进行记录,若一个空房间存活时间超过 1h,则表明当前空房间已不会再使用

我们可以将创建时间添加至房间 ID 中:

RoomManager中添加定时任务:

由于匹配成功后,双方玩家均未进入房间这种异常情况出现概率较低,因此,我们仅需要一天执行一次定时任务即可

自动化测试

使用 selenium对五子棋的 注册、登录和匹配功能进行自动化测试,由于对战模块需要模拟真人对战,且落子下标不好定位,因此,就不进行自动化测试了

引入依赖

XML 复制代码
<!--        selenium -->
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-java</artifactId>
            <version>4.0.0</version>
        </dependency>
<!--        驱动管理-->
        <dependency>
            <groupId>io.github.bonigarcia</groupId>
            <artifactId>webdrivermanager</artifactId>
            <version>5.5.3</version>
        </dependency>
<!--        屏幕截图-->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.6</version>
        </dependency>

Utils

接着,我们创建 Utils 类,用于存放自动化代码中的通用方法

java 复制代码
public class Utils {
    public static WebDriver webDriver;

    /**
     * 创建 webDriver 并 访问指定 url
     * @param url
     */
    public Utils(String url) {
        if (null == webDriver) {
            WebDriverManager.edgedriver().setup();
            EdgeOptions options = new EdgeOptions();
            // 允许访问所有连接
            options.addArguments("--remote-allow-origins=*");
            this.webDriver = new EdgeDriver(options);
            // 隐式等待 3 秒
            this.webDriver.manage().timeouts().implicitlyWait(java.time.Duration.ofSeconds(3));
        }
        webDriver.get(url);
    }

    /**
     * 关闭 WebDriver 和浏览器
     */
    public void closeBrowser() {
        if (webDriver != null) {
            webDriver.quit();
        }
    }

    /**
     * 进行屏幕截图并将其保存到自定路径
     * 路径: ./src/tests/image/2025-2-24/LoginPage-17548130.png
     * @param str
     */
    public void getScreenShot(String str) {
        try {
            DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
            DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("HHmmssSS");

            String dirTime = dateFormatter.format(LocalDateTime.now());
            String fileTime = timeFormatter.format(LocalDateTime.now());
            // 使用 File.separator 来适应不同操作系统上的路径分隔符
            String filename = "src" + File.separator + "test" + File.separator + "image" + File.separator + dirTime + File.separator + str + "-" + fileTime + ".png";
            // 创建目录
            Path path = Paths.get(filename).getParent();
            if (path != null && !Files.exists(path)) {
                Files.createDirectories(path);
            }
            // 将截图存放到指定位置
            File srcFile = ((TakesScreenshot)webDriver).getScreenshotAs(OutputType.FILE);
            File destFile = new File(filename);
            FileUtils.copyFile(srcFile, destFile);
        } catch (IOException e) {
            // 更好的错误处理或日志记录
            System.err.println("截图失败: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

注册测试

对注册功能进行测试:

java 复制代码
public class RegisterPage extends Utils {
    private static String url = "http://49.108.48.236:8081/register.html";
    public RegisterPage() {
        super(url);
    }

    /**
     * 检查页面是否加载成功
     */
    public void registerPageRight() {
        // 查看页面元素是否存在
        webDriver.findElement(By.cssSelector("body > div.register-container > div > div:nth-child(3) > span")); // 密码提示信息
        webDriver.findElement(By.cssSelector("body > div.nav")); // 导航栏
        webDriver.findElement(By.cssSelector("body > div.register-container > div > h3")); // 注册标题
    }

    /**
     * 注册成功测试
     */
    public void registerSuc() {
        // 清除输入框内文本
        webDriver.findElement(By.cssSelector("#name")).clear();
        webDriver.findElement(By.cssSelector("#password")).clear();
        // 输入用户名和密码
        webDriver.findElement(By.cssSelector("#name")).sendKeys("zhaoliu");
        webDriver.findElement(By.cssSelector("#password")).sendKeys("123456");
        // 点击登录按钮
        webDriver.findElement(By.cssSelector("#submit")).click();

        // 通过页面标题检查是否登录成功
        String expect = webDriver.getTitle();
        assert expect.equals("登录");
        // 退回到注册页面
        webDriver.navigate().back();
    }

    /**
     * 注册失败 ------ 账号错误 测试
     * 1. 账号为空
     * 2. 用户名已存在
     */
    public void registerNameFail() {
        // 1. 未输入账号
        // 清除输入框内文本
        webDriver.findElement(By.cssSelector("#name")).clear();
        webDriver.findElement(By.cssSelector("#password")).clear();
        webDriver.findElement(By.cssSelector("#password")).sendKeys("123456");
        webDriver.findElement(By.cssSelector("#submit")).click();
        // 处理弹窗
        WebDriverWait wait = new WebDriverWait(webDriver, Duration.ofSeconds(5));
        wait.until(ExpectedConditions.alertIsPresent());// 等待弹窗出现
        Alert alert = webDriver.switchTo().alert();
        alert.accept();

        // 2. 账号已存在
        // 清除输入框内文本
        webDriver.findElement(By.cssSelector("#name")).clear();
        webDriver.findElement(By.cssSelector("#password")).clear();
        webDriver.findElement(By.cssSelector("#name")).sendKeys("zhangsan");
        webDriver.findElement(By.cssSelector("#password")).sendKeys("123456");
        webDriver.findElement(By.cssSelector("#submit")).click();
        // 处理弹窗
        wait = new WebDriverWait(webDriver, Duration.ofSeconds(5));
        wait.until(ExpectedConditions.alertIsPresent());// 等待弹窗出现
        alert = webDriver.switchTo().alert();
        alert.accept();

    }

    /**
     * 注册失败 ------ 密码错误 测试
     * 1. 密码为空
     * 2. 密码过长或过短
     * 3. 密码中包含特殊字符
     */
    public void registerPasswordFail() {
        // 1. 未输入密码
        // 清除输入框内文本
        webDriver.findElement(By.cssSelector("#name")).clear();
        webDriver.findElement(By.cssSelector("#password")).clear();
        webDriver.findElement(By.cssSelector("#name")).sendKeys("admin");
        webDriver.findElement(By.cssSelector("#submit")).click();
        // 处理弹窗
        WebDriverWait wait = new WebDriverWait(webDriver, Duration.ofSeconds(5));
        wait.until(ExpectedConditions.alertIsPresent());// 等待弹窗出现
        Alert alert = webDriver.switchTo().alert();
        alert.accept();

        // 2. 密码过长
        // 清除输入框内文本
        webDriver.findElement(By.cssSelector("#name")).clear();
        webDriver.findElement(By.cssSelector("#password")).clear();
        webDriver.findElement(By.cssSelector("#name")).sendKeys("admin");
        webDriver.findElement(By.cssSelector("#password")).sendKeys("1234591111111");
        webDriver.findElement(By.cssSelector("#submit")).click();
        // 处理弹窗
        wait = new WebDriverWait(webDriver, Duration.ofSeconds(5));
        wait.until(ExpectedConditions.alertIsPresent());// 等待弹窗出现
        alert = webDriver.switchTo().alert();
        alert.accept();

        // 3. 密码过短
        // 清除输入框内文本
        webDriver.findElement(By.cssSelector("#name")).clear();
        webDriver.findElement(By.cssSelector("#password")).clear();
        webDriver.findElement(By.cssSelector("#name")).sendKeys("admin");
        webDriver.findElement(By.cssSelector("#password")).sendKeys("12345");
        webDriver.findElement(By.cssSelector("#submit")).click();
        // 处理弹窗
        wait = new WebDriverWait(webDriver, Duration.ofSeconds(5));
        wait.until(ExpectedConditions.alertIsPresent());// 等待弹窗出现
        alert = webDriver.switchTo().alert();
        alert.accept();

        // 4. 密码中包含特殊字符
        // 清除输入框内文本
        webDriver.findElement(By.cssSelector("#name")).clear();
        webDriver.findElement(By.cssSelector("#password")).clear();
        webDriver.findElement(By.cssSelector("#name")).sendKeys("admin");
        webDriver.findElement(By.cssSelector("#password")).sendKeys("12345@");
        webDriver.findElement(By.cssSelector("#submit")).click();
        // 处理弹窗
        wait = new WebDriverWait(webDriver, Duration.ofSeconds(5));
        wait.until(ExpectedConditions.alertIsPresent());// 等待弹窗出现
        alert = webDriver.switchTo().alert();
        alert.accept();
    }
}

登录测试

java 复制代码
public class LoginPage extends Utils {
    private static String url = "http://49.108.48.236:8081/login.html";
    public LoginPage() {
        super(url);
    }

    /**
     * 检查页面是否加载成功
     */
    public void loginPageRight() {
        // 查看页面元素是否存在
        webDriver.findElement(By.cssSelector("body > div.login-container > div > div:nth-child(2) > span")); // 用户名提示信息
        webDriver.findElement(By.cssSelector("body > div.nav")); // 导航栏
        webDriver.findElement(By.cssSelector("body > div.login-container > div > div.register > a")); // 注册链接
    }

    /**
     * 登录成功测试
     */
    public void loginSuc() {
        // 清除输入框内文本
        webDriver.findElement(By.cssSelector("#name")).clear();
        webDriver.findElement(By.cssSelector("#password")).clear();
        // 输入用户名和密码
        webDriver.findElement(By.cssSelector("#name")).sendKeys("zhangsan");
        webDriver.findElement(By.cssSelector("#password")).sendKeys("123456");
        // 点击登录按钮
        webDriver.findElement(By.cssSelector("#submit")).click();
        // 通过页面标题检查是否登录成功
        String expect = webDriver.getTitle();
        assert expect.equals("游戏大厅");
        getScreenShot(getClass().getName());
        // 退回到登录页面
         webDriver.navigate().back();
    }

    /**
     * 登录失败 ------ 账号错误 测试
     * 1. 账号为空
     * 2. 用户名错误
     */
    public void loginNameFail() {
        // 1. 未输入账号
        // 清除输入框内文本
        webDriver.findElement(By.cssSelector("#name")).clear();
        webDriver.findElement(By.cssSelector("#password")).clear();
        webDriver.findElement(By.cssSelector("#password")).sendKeys("123456");
        webDriver.findElement(By.cssSelector("#submit")).click();
        // 处理弹窗
        WebDriverWait wait = new WebDriverWait(webDriver, Duration.ofSeconds(5));
        wait.until(ExpectedConditions.alertIsPresent());// 等待弹窗出现
        Alert alert = webDriver.switchTo().alert();
        alert.accept();

        // 2. 账号错误
        // 清除输入框内文本
        webDriver.findElement(By.cssSelector("#name")).clear();
        webDriver.findElement(By.cssSelector("#password")).clear();
        webDriver.findElement(By.cssSelector("#name")).sendKeys("admin");
        webDriver.findElement(By.cssSelector("#password")).sendKeys("123456");
        webDriver.findElement(By.cssSelector("#submit")).click();
        // 处理弹窗
        wait = new WebDriverWait(webDriver, Duration.ofSeconds(5));
        wait.until(ExpectedConditions.alertIsPresent());// 等待弹窗出现
        alert = webDriver.switchTo().alert();
        alert.accept();

    }

    /**
     * 登录失败 ------ 密码错误 测试
     * 1. 密码为空
     * 2. 用户名正确,密码错误
     */
    public void loginPasswordFail() {
        // 1. 未输入密码
        // 清除输入框内文本
        webDriver.findElement(By.cssSelector("#name")).clear();
        webDriver.findElement(By.cssSelector("#password")).clear();
        webDriver.findElement(By.cssSelector("#name")).sendKeys("zhangsan");
        webDriver.findElement(By.cssSelector("#submit")).click();
        // 处理弹窗
        WebDriverWait wait = new WebDriverWait(webDriver, Duration.ofSeconds(5));
        wait.until(ExpectedConditions.alertIsPresent());// 等待弹窗出现
        Alert alert = webDriver.switchTo().alert();
        alert.accept();

        // 2. 密码错误
        // 清除输入框内文本
        webDriver.findElement(By.cssSelector("#name")).clear();
        webDriver.findElement(By.cssSelector("#password")).clear();
        webDriver.findElement(By.cssSelector("#name")).sendKeys("zhangsan");
        webDriver.findElement(By.cssSelector("#password")).sendKeys("123459");
        webDriver.findElement(By.cssSelector("#submit")).click();
        // 处理弹窗
        wait = new WebDriverWait(webDriver, Duration.ofSeconds(5));
        wait.until(ExpectedConditions.alertIsPresent());// 等待弹窗出现
        alert = webDriver.switchTo().alert();
        alert.accept();
    }
}

匹配测试

java 复制代码
public class MatchPage extends Utils {
    private static String url = "http://49.108.48.236:8081/game_hall.html";
    public MatchPage() {
        super(url);
    }

    /**
     * 未登录状态下进入游戏大厅
     */
    public void noLoginToMatch() {
        getScreenShot(getClass().getName());
        // 通过页面标题检查是否跳转到登录页面
        String expect = webDriver.getTitle();
        assert expect.equals("登录");
    }

    public void match() {
        // 清除输入框内文本
        webDriver.findElement(By.cssSelector("#name")).clear();
        webDriver.findElement(By.cssSelector("#password")).clear();
        // 1. 进行登录
        webDriver.findElement(By.cssSelector("#name")).sendKeys("zhangsan");
        webDriver.findElement(By.cssSelector("#password")).sendKeys("123456");
        webDriver.findElement(By.cssSelector("#submit")).click();
        // 2. 等待页面加载
        WebDriverWait wait = new WebDriverWait(webDriver, Duration.ofSeconds(5));
        wait.until(ExpectedConditions.elementToBeClickable(By.cssSelector("#match-button")));
        // 3. 开始匹配
        webDriver.findElement(By.cssSelector("#match-button")).click();
        wait.until(ExpectedConditions.textToBe(By.cssSelector("#match-button"), "匹配中...(点击停止)"));
        WebElement element = webDriver.findElement(By.cssSelector("#match-button"));
        String content = element.getText();
        assert content.equals("匹配中...(点击停止)");

        // 4. 停止匹配
        webDriver.findElement(By.cssSelector("#match-button")).click();
        wait.until(ExpectedConditions.textToBe(By.cssSelector("#match-button"), "开始匹配"));
        element = webDriver.findElement(By.cssSelector("#match-button"));
        content = element.getText();
        assert content.equals("开始匹配");
    }
}

RunTest

运行上述接口:

java 复制代码
public class RunTest {
    public static void main(String[] args) {
        // 未登录状态下访问游戏大厅页面
        MatchPage matchPage = new MatchPage();
        matchPage.noLoginToMatch();

        // 注册测试
        RegisterPage registerPage = new RegisterPage();
        registerPage.registerPageRight();
        registerPage.registerNameFail();
        registerPage.registerPasswordFail();
        registerPage.registerSuc();


        // 登录测试
        LoginPage loginPage = new LoginPage();
        loginPage.loginPageRight();
        loginPage.loginNameFail();
        loginPage.loginPasswordFail();
        loginPage.loginSuc();

        // 进行匹配测试
        matchPage.match();
        matchPage.closeBrowser();
    }
}

测试通过:

界面测试

注册登录页面正确显示:

游戏大厅页面正确显示:

对战页面:

可以看到,其中棋盘下方和右侧边缘并不能包含最后一个格子,因此,我们对棋盘进行修改:

此时就能保证正确棋盘的边框被绘制出来:

绘制棋子:

游戏结束:

返回大厅,更新对应信息:

性能测试

使用 JMeter对五子棋的登录接口进行性能测试:

创建梯度压测线程组(Stepping Thread Group),慢慢增大我们对接口的并发请求量:

创建 csv 文件,存放用户名和密码:

导入 csv 文件:

设置请求头:

添加请求:

查看结果:

聚合报告:

测试过程中并未发生异常情况,且 99% 的请求响应时间在 220ms 及以内

最大响应时间达到了1156 ms,这表明在某些时刻系统处理请求的速度显著下降

每秒处理事务数:

事务数在测试开始时逐渐增加,并在一段时间内保持相对稳定

响应时间:

响应时间在测试过程中有较大的波动,尤其是在某些时间段内出现了较高的峰值。这可能表明系统在高负载下存在性能瓶颈

总结

功能测试:

  1. 五子棋游戏的基本功能正常运行,正常流程能够正确执行

  2. 异常情况处理具有缺陷,对异常注册、异常登录 以及 异常进入游戏房间缺陷进行了修改

界面测试:

  1. 所有按钮点击响应及时,页面显示良好,无遮挡或显示错误情况

  2. 棋盘边框显示不完整,对棋盘进行了修改

性能测试:

  1. 响应时间在测试过程中有较大的波动,尤其是在某些时间段内出现了较高的峰值

  2. 可调整测试配置,增加线程数或调整循环次数,以测试更高并发用户数下性能

后续改进:

  1. 针对响应时间的波动,进一步分析系统日志和监控数据 ,找出导致性能瓶颈的具体原因

  2. 增加禁手规则更好地平衡游戏,减少先手玩家的优势

  3. 限制玩家思考时间,从而增加游戏的挑战性和紧张感

相关推荐
计算机毕设定制辅导-无忧学长11 分钟前
Maven 依赖管理基础(二)
java·python·maven
码上淘金1 小时前
Prometheus中PodMonitor配置示例
java·prometheus
猿周LV1 小时前
多线程进阶 : 八股文面试题 一 [Java EE 多线程 锁和死锁相关问题]
java·开发语言·java-ee
茂茂在长安1 小时前
JAVA面试常见题_基础部分_Mysql调优
java·mysql·面试
m0_684598532 小时前
心理咨询小程序的未来发展
java·微信小程序·小程序开发·心理咨询小程序·心理测评小程序
PXM的算法星球2 小时前
(java/Spring boot)使用火山引擎官方推荐方法向大模型发送请求
java·spring boot·火山引擎
web_132334214362 小时前
Java实战:Spring Boot application.yml配置文件详解
java·网络·spring boot
神仙别闹2 小时前
基于C#+SQL Server设计与实现的教学管理信息系统
java·数据库·c#
啾啾Fun3 小时前
[java基础-JVM篇]2_垃圾收集器与内存分配策略
java·开发语言·jvm
计算机毕设指导63 小时前
基于Springboot的游戏分享网站【附源码】
java·spring boot·后端·mysql·spring·游戏·maven