网页版五子棋——对战模块(服务器端开发②)

news2024/11/14 6:13:26

前一篇文章:网页版五子棋——对战模块(服务器端开发①)-CSDN博客

项目源代码:Java: 利用Java解题与实现部分功能及小项目的代码集合 - Gitee.com

目录

·前言

 一、创建并注册 GameAPI 类

1.创建 GameAPI 类

2.注册 GameAPI 类

二、实现 GameAPI 中继承的方法

1.通知玩家就绪

2.处理连接成功

3.处理落子请求

4.通知对手获胜

5.处理玩家退出

三、测试对战功能

·结尾


·前言

        在前一篇文章中介绍了五子棋项目中核心部分有关落子操作相关的逻辑,本篇文章将继续对五子棋项目中对战模块的服务器端代码进行编写,下面我们要进行 WebSocket 请求入口类的编写,实现其继承的方法,还有对整个对战模块功能的测试,本篇文章中将要新增的代码文件如下图圈起来的文件所示:

        下面就开始本篇文章的内容介绍: 

 一、创建并注册 GameAPI 类

1.创建 GameAPI 类

        创建 GameAPI 类,继承自 TextWebSocketHandler 它是作为处理 WebSocket 请求的入口类,其中要重写的几个方法,及每个方法的用途在前面文章已经进行了介绍,文章链接:网页版五子棋—— WebSocket 协议_网页可以实现websocket吗-CSDN博客 ,下面我们先把 GameAPI 类的一个空架子搭好,并且这里要准备几个对象如下所示:

  • 准备一个 ObjectMapper 对象,用来处理 JSON 数据;
  • 注入 RoomManager 对象,用来获取玩家所在房间,还有进行释放房间的操作;
  • 注入 OnlineUserManager 对象,用来获取当前玩家的在线状态,还有获取玩家的连接信息用于判断当前玩家是否多开,和给玩家返回响应;
  • 注入 UserMapper 对象,用于更新对局结束后玩家的信息。

        GameAPI 类空架子的代码及详细介绍如下所示: 

// 通过这个类来处理对战模块中的 WebSocket 请求
@Component
public class GameAPI extends TextWebSocketHandler {
    // 创建 RoomManager 对象, 用来获取玩家所在房间,还有进行释放房间的操作
    @Autowired
    private RoomManager roomManager;
    // 创建 ObjectMapper 对象, 用来处理 JSON 数据
    private ObjectMapper objectMapper = new ObjectMapper();
    // 创建 OnlineUserManager 用来管理玩家的状态信息
    @Autowired
    private OnlineUserManager onlineUserManager;
    // 创建 UserMapper 对象,用于更新对局结束后玩家的信息
    @Autowired
    private UserMapper userMapper;

    // 连接就绪后就会触发这个方法
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        
    }

    // 客户端/服务器 给 服务器/客户端 发送信息通过这个方法就可以接收到信息
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        
    }

    // 传输出现异常就会触发这个方法
    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        
    }

    // 如果客户端/服务器关闭连接就会执行这个方法
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        
    }

}

2.注册 GameAPI 类

        修改 WebSocketConfig 类,把 GameAPI 注册进去,修改后的 WebSocketConfig 类的具体代码及详细介绍如下所示:

// @EnableWebSocket 注解用来告诉 Spring 这是配置 WebSocket 的类
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
    // 自动注入
    @Autowired
    private TestAPI testAPI;

    @Autowired
    private MatchAPI matchAPI;

    @Autowired
    private GameAPI gameAPI;

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        // 当客户端连接 /test 这样的路径后就会触发 testAPI 进而调用其内部的方法
        registry.addHandler(testAPI,"/test");

        // 当客户端连接 /findMatch 这样的路径后就会触发 matchAPI 进而调用其内部的方法
        registry.addHandler(matchAPI,"/findMatch")
                // 把之前登录过程中往 HttpSession 中存放的 User 对象, 放到 WebSocket 的 session 中
                // 方便后面代码可以获取到当前的用户信息
                .addInterceptors(new HttpSessionHandshakeInterceptor());

        // 当客户端连接 /game 这样的路径后就会触发 gameAPI 进而调用其内部的方法
        registry.addHandler(gameAPI, "/game")
                // 把之前登录过程中往 HttpSession 中存放的 User 对象, 放到 WebSocket 的 session 中
                // 方便后面代码可以获取到当前的用户信息
                .addInterceptors(new HttpSessionHandshakeInterceptor());
    }
}

二、实现 GameAPI 中继承的方法

1.通知玩家就绪

        这里我们在 GameAPI 中写一个通知玩家就绪的方法 —— noticeGameReady() ,通过这个方法我们来给连接游戏房间页面成功的两个玩家返回 “准备就绪” 的响应,关于这个方法的具体代码及详细介绍如下所示:

    // 用来给客户端返回 "准备就绪" 的响应
    private void noticeGameReady(Room room, User thisUser, User thatUser) throws IOException {
        // 构造 GameReadyResponse 作为返回的响应对象, 来返回响应
        GameReadyResponse response = new GameReadyResponse();
        response.setMessage("gameReady");
        response.setOk(true);
        response.setReason("");
        response.setRoomId(room.getRoomId());
        response.setThisUserId(thisUser.getUserId());
        response.setThatUserId(thatUser.getUserId());
        response.setWhiteUser(room.getWhiteUser());
        // 把当前的响应数据传回给对应的玩家
        // 获取当前玩家的连接信息
        WebSocketSession webSocketSession = onlineUserManager.getFromGameRoom(thisUser.getUserId());
        // 通过连接给当前玩家返回响应
        webSocketSession.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));
    }

2.处理连接成功

        这里我们来实现 GameAPI 中的 afterConnectionEstablished 方法,我们要在 afterConnectionEstablished 方法中完成以下工作:

  1. 需要检测用户的登录状态,从 Session 中拿到当前用户的信息;
  2. 需要判断当前玩家是否在房间中;
  3. 需要对多开进行判定,如果玩家已经在游戏中,就不能再进行连接;
  4. 需要把两个玩家放到对应的房间对象中,当两个玩家都建立了连接,房间就放满了,这时调用 noticeGameReady 方法,来通知两个玩家双方都准备就绪;
  5. 如果有第三个玩家尝试加入房间,就需要给出提示:“房间已经满了”;

        在编写代码之前,我们要注意在上面需要完成的工作中,把两个玩家放到对应的房间对象过程中会涉及到线程安全问题,我们在这里会规定先进入房间的玩家是先手,当两个玩家对同一个房间对象进行操作时就会出现如下图所示的情况:        此时,玩家1 与 玩家2 都认为自己是先手方,就会出现线程安全的问题,为了解决这个问题,我们就需要进行加锁操作,下面我们就需要考虑对谁进行加锁,这里我们加锁的原则是对竞争的资源进行加锁,很显然,此时两位玩家竞争的资源就是这个房间对象,所以在执行上图中代码逻辑时,要在我们用 synchronized 对 room 进行加锁操作。

        解决完线程安全的问题后,我们就开始进行实现 afterConnectionEstablished 方法代码的编写,关于这个方法的具体代码及详细介绍如下所示:

    // 连接就绪后就会触发这个方法
    @Override
    public 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) {
            // 如果为 null ,当前没有找到对应的房间, 该玩家没有匹配成功.
            response.setOk(false);
            response.setReason("用户尚未匹配到对手!");
            session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));
            return;
        }
        // 3. 判断当前是不是多开 (该用户是不是已经在其他地方进入游戏了)
        //    前面准备了一个 OnlineUserManager
        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. 把两个玩家加入到游戏房间中.
        //    前面的创建房间/匹配过程, 是在 game_hall.html 页面中完成的.
        //    因此前面匹配到对手之后, 需要经过页面跳转, 来到 game_room.html 才算是正式进入游戏房间
        //    当前这个逻辑是在 game_room.html 页面加载的时候进行的.
        //    执行到当前逻辑, 说明玩家已经页面跳转成功了!!
        //    页面跳转, 要进行很多的操作, 很可能出现 "失败" 的情况
        synchronized (room) {
            if (room.getUser1() == null) {
                // 第一个玩家尚未加入房间.
                // 就把当前连上 WebSocket 的玩家作为 user1, 加入到房间中.
                room.setUser1(user);
                // 把先连入房间的玩家作为先手方.
                room.setWhiteUser(user.getUserId());
                System.out.println("玩家 " + user.getUsername() + " 已经准备就绪! 作为玩家1 ");
                return;
            }
            if (room.getUser2() == null) {
                // 如果进入这个逻辑, 说明玩家1 已经加入到房间, 现在要把当前玩家作为玩家2
                room.setUser2(user);
                System.out.println("玩家 " + user.getUsername() + " 已经准备就绪! 作为玩家2 ");
                // 当两个玩家都加入成功之后, 就要让服务器给这两个玩家都返回 WebSocket 的响应数据
                // 通知这两个玩家, 游戏双方都已经准备好了.
                // 通知玩家1
                noticeGameReady(room, room.getUser1(), room.getUser2());
                // 通知玩家2, 注意这里的传参顺序
                noticeGameReady(room, room.getUser2(), room.getUser1());
                return;
            }
        }
        // 6. 此处如果又有玩家尝试连接同一个房间, 就提示报错.
        //    这种情况理论上是不存在的, 为了让程序更加的健壮, 这里再做一个判断和提示.
        response.setOk(false);
        response.setReason("当前房间人数已满,您不能加入房间!");
        session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));
    }

3.处理落子请求

        这里我们来实现 GameAPI 中的 handleTextMessage 方法,我们要在 handleTextMessage 方法中完成以下工作:

  1. 需要检测用户的登录状态,从 Session 中拿到当前用户的信息;
  2. 需要根据玩家的 id 来获取到房间对象;
  3. 需要解析客户端发来的请求,通过房间对象调用 putChess 方法来处理这次具体的请求。

        关于 handleTextMessage 方法的具体代码及详细介绍如下所示:

    // 客户端/服务器 给 服务器/客户端 发送信息通过这个方法就可以接收到信息
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        GameReadyResponse response = new GameReadyResponse();
        // 1. 先从 session 里拿到当前用户的身份信息
        User user = (User) session.getAttributes().get("user");
        if (user == null) {
            System.out.println("[handleTextMessage] 当前玩家尚未登录! ");
            response.setOk(false);
            response.setReason("用户尚未登录!");
            session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));
            return;
        }
        // 2. 根据玩家 id 获取到房间对象
        Room room = roomManager.getRoomByUserId(user.getUserId());
        // 3. 通过 room 对象来处理这次具体的请求
        room.putChess(message.getPayload());
    }

4.通知对手获胜

        在进行五子棋对战的过程中,一方玩家很可能在进行对弈过程中由于网络或自身的原因退出了当前游戏页面,此时我们就需要通知另一方玩家获胜,不要让另一方玩家一直处于等待,下面我们在 GameAPI 中写一个通知对手获胜的方法 —— noticeThatUserWin(),在这个方法中,我们要完成以下的工作:

  1. 根据当前玩家找到玩家所在的房间,如果房间不存在,说明对局已经正常结束,就不用进行后续的操作了;
  2. 根据房间寻找对手,查看对手在线状态,如果对手也掉线了,那就不用再进行通知;
  3. 构造响应,通知对手获胜;
  4. 判断胜负后更改玩家的信息;
  5. 释放房间对象。

        关于 noticeThatUserWin 方法的具体代码及详细介绍如下所示:

    // 通知对手获胜
    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 response = new GameResponse();
        response.setMessage("putChess");
        response.setUserId(thatUser.getUserId());
        response.setWinner(thatUser.getUserId());
        webSocketSession.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));

        // 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());
    }

5.处理玩家退出

        这里我们要实现 GameAPI 中 handleTransportError 与 afterConnectionClosed 两个方法,在玩家下线或对局结束退出会触发这个方法,这两个方法中做的工作是一致的,需要完成以下的工作:

  1. 主要的工作是把玩家从 OnlineUserManager 的对象中进行移除;
  2. 退出的时候需要判断当前玩家退出的原因是不是因为多开的情况(一个 userId 对应到两个 WebSocket 连接),如果一个玩家开启了第二个 WebSocket 连接,那么这第二个 WebSocket 连接不会影响到玩家从 OnlineUserManager 中退出;
  3. 如果当前玩家退出游戏,就需要调用 noticeThatUserWin 方法,来判断对局是否是正常结束,如果对局没有结束是玩家主动退出,就需要通知对手获胜。

        关于 handleTransportError 与 afterConnectionClosed 方法的具体代码及详细介绍如下所示:

    // 传输出现异常就会触发这个方法
    @Override
    public 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 (exitSession == session) {
            // 加上这个判断, 目的是为了避免在多开的情况下, 第二个用户退出连接动作,导致第一个用户受到影响
            onlineUserManager.exitGameRoom(user.getUserId());
            // 通知对手获胜了
            noticeThatUserWin(user);
        }
        System.out.println("当前玩家: " + user.getUsername() + " 游戏房间连接异常");
    }

    // 如果客户端/服务器关闭连接就会执行这个方法
    @Override
    public 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 (exitSession == session) {
            // 加上这个判断, 目的是为了避免在多开的情况下, 第二个用户退出连接动作,导致第一个用户受到影响
            onlineUserManager.exitGameRoom(user.getUserId());
            // 通知对手获胜了
            noticeThatUserWin(user);
        }
        System.out.println("当前玩家: " + user.getUsername() + " 已经离开游戏房间");
    }

三、测试对战功能

        编写完上述代码之后,我们就把五子棋项目中对战模块的代码编写完毕了,与客户端交互的代码在上一篇文章中已经编写完毕,下面我们来启动服务器,在浏览器中输入:http://127.0.0.1:8080/login.html 进入登录页面,通过登录页面进入游戏大厅页面,在游戏大厅页面中点击匹配,进入游戏房间测试对战功能,下面我来测试一下对战功能是否存在问题,测试过程如下图所示:

        经过上述的测试,结果都符合预期,对战功能就是一个正常的功能了。

·结尾

        文章到这里就要结束了,本篇文章主要介绍了 GameAPI 类的代码编写,作为 WebSocket 连接请求的入口类,其中包含的方法是客户端进入游戏房间页面后第一时间要进行调用的,这里需要对很多情况进行判断,防止对局过程中出现问题,那么到这,五子棋项目中的对战模块就编写完成了,结合前面用户模块与匹配模块,网页版五子棋这个项目就算是大功告成了,文章中新增模块对前面代码的一些细微修改可能在文章中并没有很好的体现出来,如果大家对本项目感兴趣,也欢迎在我的码云中获取项目的完整代码,项目源码链接:Java: 利用Java解题与实现部分功能及小项目的代码集合 - Gitee.com ,如果对文章内容有所疑惑,欢迎在评论区进行留言,如果感觉本篇文章还不错希望能收到你的三连支持,那么我们下一篇文章再见吧~~~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2239976.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

★ C++进阶篇 ★ 异常

Ciallo&#xff5e;(∠・ω< )⌒☆ ~ 今天&#xff0c;我将和大家一起学习C中的异常 ~ ​❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️ 澄岚主页&#xff1a;椎名澄嵐-CSDN博客 C基础篇专栏&#xff1a;★ C基础篇 ★_椎名澄嵐的博客-CSDN博客 C进阶篇专栏&am…

MFC图形函数学习08——绘图函数的重载介绍

在《MFC图形函数学习06——画椭圆弧线函数》中介绍了CPoint类、POINT结构体&#xff1b;在《MFC图形函数学习07——画扇形函数》中介绍了CRect类、RECT结构体。在介绍完后&#xff0c;没有介绍它们怎样使用。实际上&#xff0c;这些类和结构体对象或指针也是我们学习过的绘图函…

尽量通俗易懂地概述.Net U nity跨语言/跨平台相关知识

本文参考来自唐老狮,Unity3D高级编程:主程手记,ai等途径 仅作学习笔记交流分享 目录 1. .Net是什么? 2. .Net框架的核心要点? 跨语言和跨平台 .Net x Unity跨平台发展史 Net Framework 2002 Unity跨平台之 Mono 2004 Unity跨平台之 IL2CPP 2015 二者区别 .NET Core …

Flink执行sql时报错

[ERROR] Could not execute SQL statement. Reason: java.lang.ClassNotFoundException: org.apache.flink.table.planner.delegation.ParserFactory flink-1.15.4的lib里面存在flink-sql-connector-hive-3.1.2_2.12-1.15.4.jar时&#xff0c;似乎会跟hdfs产生冲突&#xff0c…

实现API接口的自动化

API接口自动化测试的最佳实践有哪些&#xff1f; API接口自动化测试的最佳实践包括以下几个方面&#xff1a; 确定测试范围和目标&#xff1a;明确需要测试的API接口和功能点&#xff0c;确定测试的目标和预期结果 编写测试用例&#xff1a;根据API文档和需求&#xff0c;编…

uni-app中使用 unicloud 云开发平台③

文章目录 六、hbuilderX 中使用 unicloud 云开发平台文档传统业务开发流程什么是 unicloudunicloud 优点开发流程uncloud 构成云数据库云存储及 CDN创建云函数工程七、unicloud api 操作云函数调用云函数实现云数据库基本增删改查1. 获取数据库引用云存储操作六、hbuilderX 中使…

【缓存策略】你知道 Refresh-ahead(预刷新)这个缓存策略吗?

👉博主介绍: 博主从事应用安全和大数据领域,有8年研发经验,5年面试官经验,Java技术专家,WEB架构师,阿里云专家博主,华为云云享专家,51CTO 专家博主 ⛪️ 个人社区:个人社区 💞 个人主页:个人主页 🙉 专栏地址: ✅ Java 中级 🙉八股文专题:剑指大厂,手撕 J…

零基础Java第十六期:抽象类接口(二)

目录 一、接口&#xff08;补&#xff09; 1.1. 数组对象排序 1.2. 克隆接口 1.3. 浅拷贝和深拷贝 1.4. 抽象类和接口的区别 一、接口&#xff08;补&#xff09; 1.1. 数组对象排序 我们在讲一维数组的时候&#xff0c;使用到冒泡排序来对数组里的元素进行从小到大或从大…

django入门【05】模型介绍(二)——字段选项

文章目录 1、null 和 blank示例说明⭐ null 和 blank 结合使用的几种情况总结&#xff1a; 2、choices**choices 在 Django 中有以下几种形式&#xff1a;**&#xff08;1&#xff09; **简单的列表或元组形式**&#xff08;2&#xff09; **字典映射形式**&#xff08;3&#…

数据量大Excel卡顿严重?选对报表工具提高10倍效率

当几万行的数据把软件频频跑崩&#xff0c;当珍贵的数据资源无法便捷复用&#xff0c;当数据填报的本地文档在各个电脑中传来传去……在各大岗位要求中频频出现的Excel&#xff0c;作为个人办公软件绝无仅有&#xff0c;但作为企业场景下的报表工具&#xff0c;效率显然不足。 …

如何用WordPress和Shopify提升SEO表现?

选择合适的建站程序对于SEO优化非常重要。目前&#xff0c;WordPress和Shopify是两种备受推崇的建站平台&#xff0c;各有优势。 WordPress最大的优点是灵活性。它支持大量SEO插件&#xff0c;帮助你调整元标签、生成站点地图、优化内容结构等。这些功能让你能够轻松地提升网站…

vue 计算属性get set

<template><div id"app"><h1>用户信息</h1><p>全名&#xff1a;{{ fullName }}</p><input v-model"fullName" placeholder"请输入全名" /><p>姓&#xff1a;{{ firstName }}</p><p>…

PHP搭建开发环境(Windows系统)

要搭建一个完整的PHP动态网站&#xff0c;离不开操作系统、Web服务器、数据库、和PHP软件。 虽然有不错方便的方式&#xff0c;比如使用phpstudio等等等等许多面板都是非常快速不错的方式&#xff0c;但是这里是教会大家如何配置而不只是依赖别人整合好的面板软件&#xff0c;…

7.2 图像复原之空间滤波

图像复原&#xff08;只存在噪声的复原&#xff09;之空间滤波 文章目录 图像复原&#xff08;只存在噪声的复原&#xff09;之空间滤波前言1. 均值滤波器1.1 算术平均滤波器1.2 几何均值滤波器1.3 谐波平均滤波器1.4 反谐波平均滤波器 总结 前言 当一幅图像仅被加性噪声退化时…

化工防爆巡检机器人:在挑战中成长,为化工安全保驾护航

随着全球能源需求的不断攀升&#xff0c;化工行业的安全性与高效性愈发受到关注。化工设施规模巨大&#xff0c;而且其中多数存在高风险因素&#xff0c;像是易燃易爆化学物质、高温环境、有毒有害物质以及高压设备等。仅2023年&#xff0c;国内危化品事故就多达652起&#xff…

【数字图像处理+MATLAB】对图片进行伽马校正(Gamma Correction):使用幂律变换公式进行伽马变换

引言 伽马校正&#xff08;Gamma Correction&#xff09;是一种用于图像处理的技术&#xff0c;主要用于调整图像的亮度或对比度。其基本原理是对图像的每一个像素应用一个非线性变换&#xff0c;以更好地适应人眼的视觉感知。在数字图像处理中&#xff0c;伽马校正通常用于调…

scala 迭代更新

在Scala中&#xff0c;迭代器&#xff08;Iterator&#xff09;是一种用于遍历集合&#xff08;如数组、列表、集合等&#xff09;的元素而不暴露其底层表示的对象。迭代器提供了一种统一的方法来访问集合中的元素&#xff0c;而无需关心集合的具体实现。 在Scala中&#xff0c…

部署zabbix遇到问题: cannot find a valid baseurl for repo:centos-sclo-rh/x86 64 怎么解决 ?

安装 Zabbix 前端包&#xff0c;提示cannot find a valid baseurl for repo&#xff1a;centos-sclo-rh/x86 64 安装zabbix前端包 # yum install zabbix-web-mysql-scl zabbix-apache-conf-scl 解决办法&#xff1a; 原因是&#xff1a;CentOS7的SCL源在2024年6月30日停止维护…

SpringBoot(十九)创建多模块Springboot项目(完整版)

之前我有记录过一次SpringBoot多模块项目的搭建,但是那一次只是做了一个小小的测试。只是把各模块联通之后就结束了。 最近要增加业务开发,要将目前的单模块项目改成多模块项目,我就参照了一下我上次搭建的流程,发现总是有报错。上次搭建的比较顺利,很多细枝末节也没有仔细…

基于Python的智能无人超市管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…