网页五子棋——项目测试

目录

测试用例设计

功能测试

注册功能测试

正常注册

异常注册

登录功能测试

正常登录

异常登录

匹配功能测试

对战功能测试

自动化测试

引入依赖

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. 限制玩家思考时间,从而增加游戏的挑战性和紧张感

相关推荐
cat三三21 分钟前
java之异常
java·开发语言
浙江第二深情26 分钟前
前端性能优化终极指南
java·maven
养乐多072242 分钟前
【Java】IO流
java
俊男无期42 分钟前
超效率工作法
java·前端·数据库
中国胖子风清扬1 小时前
SpringAI和 Langchain4j等 AI 框架之间的差异和开发经验
java·数据库·人工智能·spring boot·spring cloud·ai·langchain
月明长歌1 小时前
【码道初阶】牛客TSINGK110:二叉树遍历(较难)如何根据“扩展先序遍历”构建二叉树?
java·数据结构·算法
用户2190326527351 小时前
Spring Boot + Redis 注解极简教程:5分钟搞定CRUD操作
java·后端
Alice1 小时前
linux scripts
java·linux·服务器
Filotimo_1 小时前
Spring Data JPA 方法名查询特性的使用
java·开发语言·windows
代码栈上的思考1 小时前
MyBatis:注解方式实现数据库 CRUD 全操作
java·开发语言·mybatis