P4. 微服务: 匹配系统(下)

P4. 微服务: 匹配系统 下

    • 0 概述
    • 1 游戏同步系统
      • 1.1 游戏同步的设计
      • 1.2 游戏同步的实现
    • 2 匹配系统微服务的实现
      • 2.1 微服务概述
      • 2.2 匹配系统接口url的实现
      • 2.3 微服务之间的通信
      • 2.4 匹配逻辑的实现
      • 2.5 匹配系统的权限控制
    • 3 bug的解决
      • 3.1 自己匹配自己
      • 3.2 断开连接问题

0 概述

  • 本章是匹配系统的后篇,前篇见P4. 微服务: 匹配系统(上),前篇主要介绍了前后端之间的 websocket 通信,后篇主要介绍了整个游戏系统的实现,以及如何实现匹配系统独立成微服务Spring 中微服务之间如何进行通信如何进行 url 的访问权限控制
  • 整个匹配系统相对来说还是比较复杂的,涉及到每个模块内部的逻辑,各个模块之间的通信(前后端的websocket,微服务之间的restTemplate),如何新开一个线程,新开的线程如何加锁保证不出现冲突,等等。
  • 检验是否完全理解整个模块,我认为需要自己能够自行编码实现匹配系统和游戏同步系统,知道每个工具怎么使用,包括 websocket, Thread, restTemplate, ReentrantLock 等等。

1 游戏同步系统

1.1 游戏同步的设计

在上一节P4. 微服务: 匹配系统(上)的末尾实现了蛇和棋盘的同步,也就是匹配在一起的两名玩家会收到相同的棋盘。现在还需要实现游戏具体逻辑的判断过程,接收用户输入过程

每一局游戏一共包含三个棋盘,每位用户各一个棋盘,后端维护一个棋盘,基本想法是: (1) 两名用户分别输入下一步操作给后端,(2) 后端执行游戏逻辑过程,(3) 把每一轮的执行结果同步广播给两个前端。

较为复杂的点在于第(2)步的实现,第(2)步的具体整个过程: (1) 匹配成功之后,new 一个 game 对象维护游戏信息,(2) 创建地图并广播给两位用户,(3) 读取两名玩家的输入,(4) 根据输入执行每一轮的游戏逻辑。

会发现有个问题,如果有多名玩家匹配成功,例如有4名玩家匹配成功,分别有两个游戏 game1, game2,然而一般的程序都是单线程的,也就是在 game1 执行完成后才会执行 game2,因此 game 不能用单线程来处理,于是就涉及到线程的通信和加锁问题。

在这里插入图片描述


1.2 游戏同步的实现

  • 首先将 Game 类继承 Thread 类以实现多线程,需要在 Game 中重写 run 方法,run 方法是开启新线程的入口函数,在两名玩家匹配成功后通过 game.start() 进入一个新线程(之后执行 Game 中的 run),每个 websocket 连接维护一个自己的 game 棋盘。

    Game game = new Game(13, 14, 20, a.getId(), b.getId());
    
  • run 中要实现的是具体游戏逻辑,由于游戏一定会在1000轮以内结束,因此循环1000次代替死循环。

    每一轮执行的内容和设计图中的一样,首先判断双方是否都有输入,如果一方没有输入则直接判负,记录败者,广播给前端结果;如果都有输入,则进入裁判逻辑进行局面判断,判断有结果了则结束游戏,记录败者,广播给前端结果,否则把双方的下一步操作同步广播给游戏双方的前端,再在前端渲染。

    @Override
    public void run() {for (int i = 0; i < 1000; i ++ ) {if (nextStep()) {// 双方都有输入judge();if ("playing".equals(status)) {sendMove();} else {sendResult();break;}} else {// 有一方没有输入操作,超时判输status = "finished";lock.lock();try {if (nextStepA == null && nextStepB == null) {loser = "all";} else if (nextStepA == null) {loser = "A";} else if (nextStepB == null) {loser = "B";}} finally {lock.unlock();}sendResult(); // 向两名玩家广播结果break;}}
    }
    
  • nextStep() 是判断获取双方的下一步操作,那就要在 Game 中通过 nextStepA, nextStepB 来记录每名玩家的下一步操作,在外部线程中要修改这两个变量,内部线程中要读取这两个变量,因此就出现了两个线程同时读写同一个变量的问题,于是要加锁解决。

    前端获取输入 → 发送消息给后端 → 后端维护的 websocket 连接调用 setNextStepA()Game 中读取 nextStepA

    private ReentrantLock lock = new ReentrantLock();public void setNextStepA(Integer nextStepA) {lock.lock();try {this.nextStepA = nextStepA;} finally {lock.unlock();}
    }
    

    这边设定如果超过5s未获取用户输入,则判定该用户超时,直接判负。

    /* 判断双方是否都有输入,如果有则记录下输入并返回 true,如果有一方没有输入则返回 false */
    private boolean nextStep() {for (int i = 0; i < 5; i ++ ) {try {sleep(1000);lock.lock();try {if (nextStepA != null && nextStepB != null) {playerA.getSteps().add(nextStepA);playerB.getSteps().add(nextStepB);return true;}} finally {lock.unlock();}} catch (InterruptedException e) {e.printStackTrace();}}return false;
    }
    
  • 在双方都有输入后,需要向前端广播两者的输入,再让前端进行渲染;如果有没有输入,也需要向前端返回结果。于是又涉及到前后端之间通过 websocket 进行通信,在P4. 微服务: 匹配系统(上)有详细说明。

    private void sendAllMessage(String message) {WebSocketServer.users.get(playerA.getId()).sendMessage(message);WebSocketServer.users.get(playerB.getId()).sendMessage(message);
    }private void sendMove() {lock.lock();try {JSONObject resp = new JSONObject();resp.put("event", "move");resp.put("a_direction", nextStepA);resp.put("b_direction", nextStepB);nextStepA = nextStepB = null;sendAllMessage(resp.toJSONString());} finally {lock.unlock();}
    }private void sendResult() {JSONObject resp = new JSONObject();resp.put("event", "result");resp.put("loser", loser);sendAllMessage(resp.toJSONString());
    }
    
  • 前端向后端通信,对应的是设置 game 线程中的 nextStepA, nextStepB

    // 前端通过 socket.send 发送 JSON 格式消息给后端
    add_listening_events() {this.ctx.canvas.focus();const [snake0, snake1] = this.snakes;this.ctx.canvas.addEventListener("keydown", e => {let d = -1;if (e.key === 'w') d = 2;else if (e.key === 'd') d = 1;else if (e.key === 's') d = 0;else if (e.key === 'a') d = 3;if (d >= 0) {this.store.state.pk.socket.send(JSON.stringify({event: "move",direction: d,}));}});
    }
  • 之后在前端进行调试,通过 onmessage 接收到后端传来的消息后,通过 event 进行判断,如果是 move 则渲染蛇移动的方向,如果是 result 则渲染蛇已经死亡的情况。

    为了方便调试,可以自行在前端写一下 a, b 的位置信息。

    socket.onmessage = msg => {const data = JSON.parse(msg.data);if (data.event === "match_success") {/* ... */} else if (data.event === "move") {console.log(data);const game = store.state.pk.gameObject;const [snake0, snake1] = game.snakes;snake0.set_direction(data.a_direction);snake1.set_direction(data.b_direction);} else if (data.event === "result") {console.log(data);const game = store.state.pk.gameObject;const [snake0, snake1] = game.snakes;if (data.loser === "all" || data.loser === "A") {snake0.status = "die";}if (data.loser === "all" || data.loser === "B") {snake1.status = "die";}}
    }
    
    private void move(int direction) {if (game.getPlayerA().getId().equals(user.getId())) {game.setNextStepA(direction);} else if (game.getPlayerB().getId().equals(user.getId())) {game.setNextStepB(direction);}
    }@OnMessage
    public void onMessage(String message, Session session) {System.out.println("received!");JSONObject data = JSONObject.parseObject(message);String event = data.getString("event");if ("start-matching".equals(event)) {startMatching();} else if ("stop-matching".equals(event)) {stopMatching();} else if ("move".equals(event)) {move(data.getInteger("direction"));}
    }
    
  • 最后在后端翻译一下P1.创建菜单与游戏界面中介绍的游戏裁判逻辑实现 judge 就完成了整个游戏同步系统。

    private boolean check_valid(List<Cell> cellsA, List<Cell> cellsB) {int n = cellsA.size();Cell cell = cellsA.get(n - 1);if (g[cell.x][cell.y] == 1) return false;for (int i = 0; i < n - 1; i ++ ) {if (cellsA.get(i).x == cell.x && cellsA.get(i).y == cell.y)return false;}for (int i = 0; i < n - 1; i ++ ) {if (cellsB.get(i).x == cell.x && cellsB.get(i).y == cell.y)return false;}return true;
    }private void judge() {  // 判断两名玩家下一步操作是否合法List<Cell> cellsA = playerA.getCells();List<Cell> cellsB = playerB.getCells();boolean validA = check_valid(cellsA, cellsB);boolean validB = check_valid(cellsB, cellsA);if (!validA || !validB) {status = "finished";if (!validA && !validB) {loser = "all";} else if (!validA) {loser = "A";} else {loser = "B";}}
    }
    
  • 前端中计分板,重新匹配按钮等小细节的实现就略过了,请自行实现。


2 匹配系统微服务的实现

2.1 微服务概述

微服务可以理解成为一个独立的程序,本质上是新开了一个 Springboot。和原来实现的后端服务 backend 呈并列关系,也就是两个独立的 SpringBoot,均可以接收和发送信息,在 Spring 中通过 url 进行 http 通信。

在 King of Bots 中选择把匹配系统单独拉出来做一个微服务,其实也可以开一个新线程直接实现,但是为了学习新技术就拉出来做一个微服务,学习一下怎么创建微服务,微服务如何和之前实现的后端 backend 进行通信。

微服务的创建就是新建一个父项目,包含 matchingsystem, backend 两个模块,父项目要添加 springcloud 依赖。

在这里插入图片描述


2.2 匹配系统接口url的实现

MatchingSystem 一共要实现两个接口 addPlayer, removePlayer,都是通过 controller, service, service.impl 的步骤实现。

service 中的接口:

public interface MatchingService {String addPlayer(Integer userId, Integer rating);String removePlayer(Integer userId);
}

service.impl 先简单的调试一下:

@Service
public class MatchingServiceImpl implements MatchingService {@Overridepublic String addPlayer(Integer userId, Integer rating) {System.out.println("Player: " + userId + " rating: " + rating + "add!");return "add player success";}@Overridepublic String removePlayer(Integer userId) {System.out.println("Player: " + userId + " remove!");return "remove player success";}
}

controller 定义一下 url:

@RestController
public class MatchingController {@Autowiredprivate MatchingService matchingService;@PostMapping("/player/add/")public String addPlayer(@RequestParam MultiValueMap<String, String> data) {Integer userId = Integer.parseInt(Objects.requireNonNull(data.getFirst("user_id")));Integer rating = Integer.parseInt(Objects.requireNonNull(data.getFirst("rating")));return matchingService.addPlayer(userId, rating);}@PostMapping("player/remove/")public String removePlayer(@RequestParam MultiValueMap<String, String> data) {Integer userId = Integer.parseInt(Objects.requireNonNull(data.getFirst("user_id")));return matchingService.removePlayer(userId);}
}

2.3 微服务之间的通信

之前在P4. 微服务: 匹配系统(上)中 WebsocketServer 后端本地实现了一个傻瓜式匹配,现在开始匹配 startMatching 之后,应该向微服务发送一个请求,表示传一个玩家过去;在取消匹配 stopMatching 之后,应该发送一个请求,表示取消当前玩家的匹配。

向后端发请求会用到 SpringBoot 中的一个工具 RestTemplate,需要先进行配置。

RestTemplate 用于在两个 SpringBoot 之间进行通信。

@Configuration
public class RestTemplateConfig {@Beanpublic RestTemplate getRestTemplate() {return new RestTemplate();}
}

之后直接通过 restTemplate.postForObject 调用另一个微服务的 url,一共有3个参数 url, data, 返回值的class.

private static RestTemplate restTemplate;
private final String addPlayerUrl = "http://127.0.0.1:3001/player/add/";@Autowired
private void setRestTemplate(RestTemplate restTemplate) {WebSocketServer.restTemplate = restTemplate;
}private void startMatching() {System.out.println("Start Matching!");MultiValueMap<String, String> data = new LinkedMultiValueMap<>();data.put("user_id", Collections.singletonList(this.user.getId().toString()));data.put("rating", Collections.singletonList(this.user.getRating().toString()));restTemplate.postForObject(addPlayerUrl, data, String.class);
}

2.4 匹配逻辑的实现

在匹配中通过匹配池 MatchingPool 进行匹配,创建 service.impl.utils.MatchingPool 继承 Thread,和之前的一样,重写 run方法实现多线程,这边用多线程是因为每秒都要看有没有玩家可以匹配,是一个死循环,如果单线程就会卡死在这个循环中。

线程的启动设置成在 SpringBoot 服务开启的时候启动:

@SpringBootApplication
public class MatchingSystemApplication {public static void main(String[] args) {MatchingServiceImpl.matchingPool.start();SpringApplication.run(MatchingSystemApplication.class, args);}
}

在接收到 backendWebsocketServer 调用 /player/add/url 后,找到 serviceImpl 中对应的方法,该方法调用辅助类 MatchingPooladdPlayer 方法,向匹配池中添加一位玩家,removePlayer 同理。

@Service
public class MatchingServiceImpl implements MatchingService {public final static MatchingPool matchingPool = new MatchingPool();@Overridepublic String addPlayer(Integer userId, Integer rating) {System.out.println("Player: " + userId + " rating: " + rating + " add!");matchingPool.addPlayer(userId, rating);return "add player success";}@Overridepublic String removePlayer(Integer userId) {System.out.println("Player: " + userId + " remove!");matchingPool.removePlayer(userId);return "remove player success";}
}
// 辅助类 MatchingPool
public class MatchingPool extends Thread {private static List<Player> players = new ArrayList<>();private ReentrantLock lock = new ReentrantLock();public void addPlayer(Integer userId, Integer rating) {lock.lock();try {players.add(new Player(userId, rating, 0));} finally {lock.unlock();}}public void removePlayer(Integer userId) {lock.lock();try {List<Player> newPlayers = new ArrayList<>();for (Player player : players)if (!player.getUserId().equals(userId))newPlayers.add(player);players = newPlayers;} finally {lock.unlock();}}@Overridepublic void run() {/* 具体匹配逻辑,看个人喜好实现就行 */}
}

具体的匹配逻辑在 run 中实现,通常是通过 while(true)Thread.sleep(1000) 每秒对匹配池中所有玩家进行检查,符合条件的玩家对会进行匹配。

匹配完成之后 matchingsystem 需要 sendResultWebsocketServer,所以 backend 中需要写个 url 可以接收到这个消息,其中 service 的逻辑如下,startGame 为之前写的逻辑,具体是开始一场游戏,并传信息给前端。

要记得在 SecurityConfig 中对权限进行设置,只允许本地调用。

@Override
public String startGame(Integer aId, Integer bId) {System.out.println("start game: " + aId + " " + bId);WebSocketServer.startGame(aId, bId);return "game start success!";
}

最后在 sendResult 中进行调用,一样使用 RestTemplate 实现,

private final static String startGameUrl = "http://127.0.0.1:3000/pk/game/start/";private void sendResult(Player a, Player b) {MultiValueMap<String, String> data = new LinkedMultiValueMap<>();data.put("a_id", Collections.singletonList(a.getUserId().toString()));data.put("b_id", Collections.singletonList(b.getUserId().toString()));restTemplate.postForObject(startGameUrl, data, String.class);
}

2.5 匹配系统的权限控制

匹配系统的 url 只能允许本地进行访问,不能让外部进行访问,避免恶意行为,因此使用 Spring Security 进行权限控制。

我们希望只能后端服务器 backend 访问 /player/add/, /player/remove/,和之前的一样,在 matchingsystem 中添加依赖并写一个网关 SecurityConfig 配置。

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests().antMatchers("/player/add/", "/player/remove/").hasIpAddress("127.0.0.1").antMatchers(HttpMethod.OPTIONS).permitAll().anyRequest().authenticated();}
}

3 bug的解决

3.1 自己匹配自己

先点击匹配,进入匹配池,再刷新页面(此时已经断开连接并建立新连接),再次点击匹配,会出现自己匹配自己的问题。

解决方法: 在断开连接的时候调用微服务的 removePlayer

@OnClose
public void onClose() {System.out.println("disconnected!");if (this.user != null) {users.remove(this.user.getId());MultiValueMap<String, String> data = new LinkedMultiValueMap<>();data.put("user_id", Collections.singletonList(user.getId().toString()));restTemplate.postForObject(removePlayerUrl, data, String.class);}
}

3.2 断开连接问题

会出现某位玩家突然断电导致断开连接,或者妈妈回来了,紧急按 alt + F4 结束进程,等等这种情况。

会导致该玩家仍然在匹配池里,能够匹配成功,但是后端和前端无法通过 websocket 进行通信,导致报错。

因此,在用到 users 的地方都要进行非空判断,解决这种情况,以下举个例子:

if (users.get(a.getId()) != null) users.get(a.getId()).game = game;
if (users.get(b.getId()) != null) users.get(b.getId()).game = game;

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

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

相关文章

大数据之Linux部署常用命令脚本封装

文章目录 编写集群命令执行脚本xcall集群命令执行1. 脚本需求2. 需求分析3. 脚本实现3.1 创建脚本存放目录3.2 编写xcall脚本3.3 修改脚本执行权限3.4 测试脚本 编写集群分发脚本xsync集群分发脚本1. 脚本需求2. 需求分析3. 脚本实现3.1 创建脚本存放目录3.2 编写xsync脚本3.3 …

计算机毕业设计PyFlink+Spark+Hive民宿推荐系统 酒店推荐系统 民宿酒店数据分析可视化大屏 民宿爬虫 民宿大数据 知识图谱 机器学习

本科毕业设计(论文) 开题报告 学院 &#xff1a; 计算机学院 课题名称 &#xff1a; 民宿数据可视化分析系统的设计与实现 姓名 &#xff1a; 庄贵远 学号 &#xff1a; 2020135232 专业 &#xff1a; 数据科学与大数据技术 班级 &#xff1a; 20大数据本科2…

重温react-06(初识函数组件和快速生成格式的插件使用方式)

开始 函数组件必然成为未来发展的趋势(个人见解),总之努力的去学习,才能赚更多的钱.加油呀! 函数组件的格式 import React from reactexport default function LearnFunction01() {return (<div>LearnFunction01</div>) }以上是函数式组件的组基本的方式 快捷生…

基于DSMM数据安全能力建设方案的落地性评估指标

写在前面&#xff1a; 随着信息技术的迅猛发展&#xff0c;数据已成为企业最宝贵的资产之一。然而&#xff0c;数据安全问题也随之而来&#xff0c;如何确保数据的安全性、完整性和可用性&#xff0c;已成为企业面临的重要挑战。DSMM&#xff08;数据安全能力成熟度模型&#x…

混合专家模型(MoE)的前世今生

在文章《聊聊最近很火的混合专家模型&#xff08;MoE&#xff09;》中&#xff0c;我们简单介绍了MoE模型的定义和设计&#xff0c;并且比较了MoE和Dense模型的区别&#xff0c;今天我们继续来回顾一下MoE模型发展的历史和最新的发展现状。 从去年GPT-4发布至今&#xff0c;MoE…

[C++][设计模式][中介者模式]详细讲解

目录 1.动机2.模式定义3.要点总结 1.动机 在软件构建过程中&#xff0c;经常会出现多个对象相互关联的情况&#xff0c;对象之间常常会维持一种复杂的引用关系&#xff0c;如果遇到一些需求的更改&#xff0c;这种直接的引用关系将面临不断的变化在这种情况下&#xff0c;可以…

SpringDataJPA系列(2)Commons核心Repository

SpringDataJPA系列(2)Commons核心Repository Spring Data Commons依赖关系 我们通过 Gradle 看一下项目依赖&#xff0c;了解一下 Spring Data Common 的依赖关系 通过上图的项目依赖&#xff0c;不难发现&#xff0c;数据库连接用的是 JDBC&#xff0c;连接池用的是 HikariC…

使用label-studio对OCR数据进行预标注

导读 label-studio作为一款数据标注工具相信大家都不陌生&#xff0c;对于需要进行web数据标注协同来说应该是必备工具了&#xff0c;标注的数据类型很全涉及AI的各个任务(图像、语音、NLP、视频等)&#xff0c;还支持自定义涉及模版。 然而&#xff0c;我们在标注数据的过程…

Veno File Manager(VFM)v4.2.7 中文包整理

Veno File Manager&#xff08;VFM&#xff09;是一个简单灵活的即插即用文件管理器&#xff0c;易于使用且具有许多选项。将文件发送给您的客户&#xff0c;使用专用文件夹创建新用户&#xff0c;或仅用作您的个人文件云。从任何设备访问&#xff0c;用户管理和从直观的管理面…

OpenCV 调用自定义训练的 YOLO-V8 Onnx 模型

一、YOLO-V8 转 Onnx 在本专栏的前面几篇文章中&#xff0c;我们使用 ultralytics 公司开源发布的 YOLO-V8 模型&#xff0c;分别 Fine-Tuning 实验了 目标检测、关键点检测、分类 任务&#xff0c;实验后发现效果都非常的不错&#xff0c;但是前面的演示都是基于 ultralytics…

C语言分支和循环(下)

C语言分支和循环&#xff08;下&#xff09; 1. 随机数生成1.1 rand1.2 srand1.3 time1.4 设置随机数的范围 2. 猜数字游戏实现 掌握了前面学习的这些知识&#xff0c;我们就可以写⼀些稍微有趣的代码了&#xff0c;比如&#xff1a; 写⼀个猜数字游戏 游戏要求&#xff1a; 电…

第6章 复制

文章目录 前言1.配置1.1建立复制1.2断开复制1.3 安全性1.4 只读1.5 传输延迟 2. 拓扑2.1.一主一从结构2.2.一主多从结构2.3.树状主从结构 3.原理3.1复制过程3.2数据同步3.3全量复制 前言 复制功能&#xff0c;实现了相同数据的多个Redis副本。复制功能是高可用Redis的基础&…

智能交通(2)——IntelliLight智能交通灯

论文分享&#xff1a;IntelliLight | Proceedings of the 24th ACM SIGKDD International Conference on Knowledge Discovery & Data Mininghttps://dl.acm.org/doi/10.1145/3219819.3220096摘要 智能交通灯控制对于高效的交通系统至关重要。目前现有的交通信号灯大多由手…

【Python系列】列表推导式:简洁而强大的数据操作工具

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

已成功与服务器建立连接,但是在登录过程中发生错误。(provider: SSL提供程序,error:0-证书链是由不受信任的颁发机构颁发的。)

已成功与服务器建立连接&#xff0c;但是在登录过程中发生错误。(provider: SSL提供程序,error:0-证书链是由不受信任的颁发机构颁发的。) 在连接SQL Server2008R2数据库时发生错误。 连接字符串&#xff1a;server127.0.0.1;uidsa;pwd1;databasedb; 解决办法&#xff1a; 方…

PySide(PyQt)在图像上画线

1、按鼠标左键任意画线 import sys from PySide6.QtWidgets import QApplication, QLabel, QVBoxLayout, QWidget from PySide6.QtGui import QPainter, QPixmap, QMouseEvent, QColor, QPen from PySide6.QtCore import Qt, QPointclass PaintLabel(QLabel):def __init__(self…

如何使用FlowUs打造爆款自媒体内容?内容资产管理沉淀的先进工具选息流

FlowUs 是一款流行的在线协作工具&#xff0c;它以其灵活的块编辑器、看板视图、数据库管理等功能受到众多个人和团队的喜爱。将其应用于内容资产管理&#xff0c;尤其是对于追求打造爆款自媒体的创作者而言&#xff0c;可以极大地提升内容创作、组织、分发及分析的效率。 内容…

无刷直流电机(BLDCM)仿真建模

无刷直流电机&#xff0c;即BLDCM在各个行业应用非常广泛。在汽车电子领域&#xff0c;BLDCM被广泛用于电动汽车、混合动力汽车、电动自行车等车辆的驱动系统中。由于BLDCM具有高效率、高力矩密度和快速响应的优势&#xff0c;它可以提供可靠的动力输出&#xff0c;并且可以通过…

idea常用配置 | 快捷注释

idea快速注释 一、类上快速注释 &#xff08;本方法是IDEA环境自带的&#xff0c;设置特别方便简单易使用&#xff09; 1、偏好设置->编辑器->文件和代码模版 | File-Settings-Editor-File and Code Templates 2、右下方的“描述”中有相对应的自动注注释配置格式 贴…

力扣 单词规律

所用数据结构 哈希表 核心方法 判断字符串pattern 和字符串s 是否存在一对一的映射关系&#xff0c;按照题意&#xff0c;双向连接的对应规律。 思路以及实现步骤 1.字符串s带有空格&#xff0c;因此需要转换成字符数组进行更方便的操作&#xff0c;将字符串s拆分成单词列表…