SpringBoot学习笔记-实现微服务:匹配系统(下)

笔记内容转载自 AcWing 的 SpringBoot 框架课讲义,课程链接:AcWing SpringBoot 框架课。

CONTENTS

  • 1. 重构项目
    • 1.1 初始化Spring Cloud项目
    • 1.2 创建匹配系统框架
  • 2. 实现匹配系统微服务
    • 2.1 数据库更新
    • 2.2 Web后端与匹配系统后端通信
    • 2.3 实现匹配逻辑
    • 2.4 Web后端接收匹配结果

1. 重构项目

1.1 初始化Spring Cloud项目

现在需要把匹配系统设计成一个微服务,也就是一个独立的系统,可以认为是一个新的 SpringBoot 后端,当之前的服务器获取到两名玩家的匹配请求后会向后台的匹配系统服务器发送 HTTP 请求,匹配系统类似于之前的 Game,在接收到请求之后也会单独开一个新的线程来匹配,可以设计成每隔一秒扫一遍匹配池中已有的玩家,然后判断能否匹配出来,如果可以就将匹配结果通过 HTTP 请求返回。

匹配系统和网站后端是两个并列的后端项目,因此可以修改一下项目结构,将这两个后端改为子项目,然后新建一个新的父级项目。

我们新建一个 Spring 项目,项目名为 backendcloud,还是选用 Maven 管理项目,组名为 com.kob。注意 2023.11.24 之后 SpringBoot2.X 版本正式弃用,SpringBoot3.X 版本需要 Java17 及以上。我们现在选择 SpringBoot3.2.0 版本,依赖选上 Spring Web 即可。

父级项目是没有逻辑的,因此可以把 src 目录删掉,然后修改一下 pom.xml,首先在 <description>backendcloud</description> 后添加一行:<packaging>pom</packaging>,然后添加 Spring Cloud 的依赖,前往 Maven 仓库,搜索并安装以下依赖:

  • spring-cloud-dependencies

接着在 backendcloud 目录下创建匹配系统子项目,选择新建一个模块(Module),选择空项目,匹配系统的名称为 matchingsystem,在高级设置中将组 ID 设置为 com.kob.matchingsystem。

这个新建的子项目本质上也是一个 SpringBoot,我们将父级目录的 pom.xml 中的 Spring Web 依赖剪切到 matchingsystem 中的 pom.xml。

1.2 创建匹配系统框架

由于有两个 SpringBoot 服务,因此需要修改一下匹配系统的端口,在 resources 目录下创建 application.properties 文件:

server.port=3001

在 com.kob.matchingsystem 包下创建 controller 和 service 包,在 service 包下创建 impl 包。先在 service 包下创建 MatchingService 接口:

package com.kob.matchingsystem.service;public interface MatchingService {String addPlayer(Integer userId, Integer rating);  // 将玩家添加到匹配池中String removePlayer(Integer userId);  // 从匹配池中删除玩家
}

然后简单实现一下 MatchingServiceImpl:

package com.kob.matchingsystem.service.impl;import com.kob.matchingsystem.service.MatchingService;
import org.springframework.stereotype.Service;@Service
public class MatchingServiceImpl implements MatchingService {@Overridepublic String addPlayer(Integer userId, Integer rating) {System.out.println("Add Player: " + userId + ", Rating: " + rating);return "success";}@Overridepublic String removePlayer(Integer userId) {System.out.println("Remove Player: " + userId);return "success";}
}

最后在 controller 包下创建 MatchingController:

package com.kob.matchingsystem.controller;import com.kob.matchingsystem.service.MatchingService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;import java.util.Objects;@RestController
public class MatchingController {@Autowiredprivate MatchingService matchingService;@PostMapping("/matching/add/")public String addPlayer(@RequestParam MultiValueMap<String, String> data) {  // 注意这边不能用MapInteger userId = Integer.parseInt(Objects.requireNonNull(data.getFirst("user_id")));Integer rating = Integer.parseInt(Objects.requireNonNull(data.getFirst("rating")));return matchingService.addPlayer(userId, rating);}@PostMapping("/matching/remove/")public String removePlayer(@RequestParam MultiValueMap<String, String> data) {Integer userId = Integer.parseInt(Objects.requireNonNull(data.getFirst("user_id")));return matchingService.removePlayer(userId);}
}

现在需要将这个匹配系统子项目变为 Spring 项目,将 Main 改名为 MatchingSystemApplication,然后将其修改为 SpringBoot 的入口:

package com.kob.matchingsystem.service;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class MatchingSystemApplication {public static void main(String[] args) {SpringApplication.run(MatchingSystemApplication.class, args);}
}

2. 实现匹配系统微服务

2.1 数据库更新

我们将 rating 放到用户身上而不是 BOT 上,每个用户对应一个自己的天梯分。在 user 表中创建 rating,并将 bot 表中的 rating 删去,然后需要修改对应的 pojo,还有 service.impl.user.account 包下的 RegisterServiceImpl 类以及 service.impl.user.bot 包下的 AddServiceImpl 和 UpdateServiceImpl 类。

2.2 Web后端与匹配系统后端通信

先在 backend 项目的 config 包下创建 RestTemplateConfig 类,便于之后在其他地方注入 RestTemplate。RestTemplate 能够在应用中调用 REST 服务。它简化了与 HTTP 服务的通信方式,统一了 RESTful 的标准,封装了 HTTP 链接,我们只需要传入 URL 及返回值类型即可:

package com.kob.backend.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;@Configuration
public class RestTemplateConfig {@Beanpublic RestTemplate getRestTemplate() {return new RestTemplate();}
}

我们将 WebSocketServer 的简易匹配代码删去,然后使用 HTTP 请求向 matchingsystem 后端发送匹配请求,注意我们将 startGame() 方法改为 public,因为之后需要在处理匹配成功的 Service 中调用该方法来启动游戏:

package com.kob.backend.consumer;import com.alibaba.fastjson2.JSONObject;
import com.kob.backend.consumer.utils.Game;
import com.kob.backend.consumer.utils.JwtAuthentication;
import com.kob.backend.mapper.RecordMapper;
import com.kob.backend.mapper.UserMapper;
import com.kob.backend.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import jakarta.websocket.*;
import jakarta.websocket.server.PathParam;
import jakarta.websocket.server.ServerEndpoint;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;@Component
@ServerEndpoint("/websocket/{token}")  // 注意不要以'/'结尾
public class WebSocketServer {// ConcurrentHashMap是一个线程安全的哈希表,用于将用户ID映射到WS实例public static final ConcurrentHashMap<Integer, WebSocketServer> users = new ConcurrentHashMap<>();private User user;private Session session = null;private Game game = null;private static UserMapper userMapper;public static RecordMapper recordMapper;  // 要在Game中调用private static RestTemplate restTemplate;  // 用于发送HTTP请求// 向匹配系统发送请求的URLprivate static final String matchingAddPlayerUrl = "http://127.0.0.1:3001/matching/add/";private static final String matchingRemovePlayerUrl = "http://127.0.0.1:3001/matching/remove/";@Autowiredpublic void setUserMapper(UserMapper userMapper) {WebSocketServer.userMapper = userMapper;}@Autowiredpublic void setRecordMapper(RecordMapper recordMapper) {WebSocketServer.recordMapper = recordMapper;}@Autowiredpublic void setRestTemplate(RestTemplate restTemplate) {WebSocketServer.restTemplate = restTemplate;}@OnOpenpublic void onOpen(Session session, @PathParam("token") String token) throws IOException {this.session = session;Integer userId = JwtAuthentication.getUserId(token);user = userMapper.selectById(userId);if (user != null) {users.put(userId, this);System.out.println("Player " + user.getId() + " Connected!");} else {this.session.close();}}@OnClosepublic void onClose() {if (user != null) {users.remove(this.user.getId());System.out.println("Player " + user.getId() + " Disconnected!");}stopMatching();  // 断开连接时取消匹配}@OnMessagepublic void onMessage(String message, Session session) {  // 一般会把onMessage()当作路由JSONObject data = JSONObject.parseObject(message);String event = data.getString("event");  // 取出event的内容if ("start_match".equals(event)) {  // 开始匹配this.startMatching();} else if ("stop_match".equals(event)) {  // 取消匹配this.stopMatching();} else if ("move".equals(event)) {  // 移动move(data.getInteger("direction"));}}@OnErrorpublic void onError(Session session, Throwable error) {error.printStackTrace();}public void sendMessage(String message) {  // 从后端向当前链接发送消息synchronized (session) {  // 由于是异步通信,需要加一个锁try {session.getBasicRemote().sendText(message);} catch (IOException e) {e.printStackTrace();}}}public void startGame(Integer aId, Integer bId) {User a = userMapper.selectById(aId), b = userMapper.selectById(bId);game = new Game(13, 14, 20, a.getId(), b.getId());game.createMap();users.get(a.getId()).game = game;users.get(b.getId()).game = game;game.start();  // 开一个新的线程JSONObject respGame = new JSONObject();respGame.put("a_id", game.getPlayerA().getId());respGame.put("a_sx", game.getPlayerA().getSx());respGame.put("a_sy", game.getPlayerA().getSy());respGame.put("b_id", game.getPlayerB().getId());respGame.put("b_sx", game.getPlayerB().getSx());respGame.put("b_sy", game.getPlayerB().getSy());respGame.put("map", game.getG());JSONObject respA = new JSONObject(), respB = new JSONObject();  // 发送给A/B的信息respA.put("event", "match_success");respA.put("opponent_username", b.getUsername());respA.put("opponent_photo", b.getPhoto());respA.put("game", respGame);users.get(a.getId()).sendMessage(respA.toJSONString());  // A不一定是当前链接,因此要在users中获取respB.put("event", "match_success");respB.put("opponent_username", a.getUsername());respB.put("opponent_photo", a.getPhoto());respB.put("game", respGame);users.get(b.getId()).sendMessage(respB.toJSONString());}private void startMatching() {  // 需要向MatchingSystem发送请求MultiValueMap<String, String> data = new LinkedMultiValueMap<>();data.add("user_id", String.valueOf(user.getId()));data.add("rating", String.valueOf(user.getRating()));String resp = restTemplate.postForObject(matchingAddPlayerUrl, data, String.class);  // 参数为请求地址、数据、返回值的Classif ("success".equals(resp)) {System.out.println("Player " + user.getId() + " start matching!");}}private void stopMatching() {  // 需要向MatchingSystem发送请求MultiValueMap<String, String> data = new LinkedMultiValueMap<>();data.add("user_id", String.valueOf(user.getId()));String resp = restTemplate.postForObject(matchingRemovePlayerUrl, data, String.class);if ("success".equals(resp)) {System.out.println("Player " + user.getId() + " stop matching!");}}private void move(Integer direction) {if (game.getPlayerA().getId().equals(user.getId())) {game.setNextStepA(direction);} else if (game.getPlayerB().getId().equals(user.getId())) {game.setNextStepB(direction);}}
}

现在将两个后端项目都启动起来,可以在 IDEA 下方的服务(Services)选项卡的 Add Service 中点击 Run Configuration Type,然后选中 Spring Boot,这样就能在下方窗口中看到两个 SpringBoot 后端的情况。

尝试在前端中开始匹配,可以看到 matchingsystem 后端控制台输出:Add Player: 1, Rating: 1500。

2.3 实现匹配逻辑

匹配系统需要将当前正在匹配的用户放到一个匹配池中,然后开一个新线程每隔一段时间去扫描一遍匹配池,将能够匹配的玩家匹配在一起,我们的匹配逻辑是匹配两名分值接近的玩家,且随着时间的推移,两名玩家的分差可以越来越大。

首先需要添加 Project Lombok 依赖,我们使用与之前 Web 后端相同的依赖版本:

<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.30</version><scope>provided</scope>
</dependency>

在 matchingsystem 项目的 service.impl 包下创建 utils 包,然后在其中创建 Player 类:

package com.kob.matchingsystem.service.impl.utils;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;@Data
@NoArgsConstructor
@AllArgsConstructor
public class Player {private Integer userId;private Integer rating;private Integer waitingTime;  // 等待时间
}

接着创建 MatchingPool 类用来维护我们的这个新线程:

package com.kob.matchingsystem.service.impl.utils;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.client.RestTemplate;import java.util.*;
import java.util.concurrent.locks.ReentrantLock;@Component  // 为了在类中能够注入Bean
public class MatchingPool extends Thread {private static List<Player> players = new ArrayList<>();  // 我们之后会自己加锁,因此不需要用线程安全的集合private ReentrantLock lock = new ReentrantLock();private static RestTemplate restTemplate;private static final String startGameUrl = "http://127.0.0.1:3000/pk/startgame/";@Autowiredpublic void setRestTemplate(RestTemplate restTemplate) {WebSocketServer.restTemplate = restTemplate;}public void addPlayer(Integer userId, Integer rating) {lock.lock();try {// TODO:创建一个新的Player添加到players中} finally {lock.unlock();}}public void removePlayer(Integer userId) {lock.lock();try {// TODO:将某个Player从players中删掉} finally {lock.unlock();}}private void increaseWaitingTime(Integer waitingTime) {  // 将当前所有等待匹配的玩家等待时间加waitingTime秒for (Player player: players) {player.setWaitingTime(player.getWaitingTime() + waitingTime);}}private boolean checkMatched(Player a, Player b) {  // 判断两名玩家是否能够匹配int ratingDelta = Math.abs(a.getRating() - b.getRating());  // 分差int minWatingTime = Math.min(a.getWaitingTime(), b.getWaitingTime());  // 等待时间较短的玩家符合匹配要求那么等待时间长的也一定符合要求return ratingDelta <= minWatingTime * 10;  // 每多匹配一秒则匹配的分值范围加10}private void sendResult(Player a, Player b) {  // 返回匹配结果给Web后端MultiValueMap<String, String> data = new LinkedMultiValueMap<>();data.add("a_id", String.valueOf(a.getUserId()));data.add("b_id", String.valueOf(b.getUserId()));String resp = restTemplate.postForObject(startGameUrl, data, String.class);}private void matchPlayers() {  // 尝试匹配所有玩家Set<Player> used = new HashSet<>();  // 标记玩家是否已经被匹配for (int i = 0; i < players.size(); i++) {if (used.contains(players.get(i))) continue;for (int j = i + 1; j < players.size(); j++) {if (used.contains(players.get(j))) continue;Player a = players.get(i), b = players.get(j);if (checkMatched(a, b)) {used.add(a);used.add(b);sendResult(a, b);break;}}}// TODO:从players中移除used中的玩家}@Overridepublic void run() {while (true) {try {Thread.sleep(1000);System.out.println(players);  // 输出当前匹配池中的玩家lock.lock();try {increaseWaitingTime(1);matchPlayers();} finally {lock.unlock();}} catch (InterruptedException e) {e.printStackTrace();break;}}}
}

现在即可将这个线程在 MatchingServiceImpl 中定义出来:

package com.kob.matchingsystem.service.impl;import com.kob.matchingsystem.service.MatchingService;
import com.kob.matchingsystem.service.impl.utils.MatchingPool;
import org.springframework.stereotype.Service;@Service
public class MatchingServiceImpl implements MatchingService {public static final MatchingPool matchingPool = new MatchingPool();  // 全局只有一个匹配线程@Overridepublic String addPlayer(Integer userId, Integer rating) {System.out.println("Add Player: " + userId + ", Rating: " + rating);matchingPool.addPlayer(userId, rating);return "success";}@Overridepublic String removePlayer(Integer userId) {System.out.println("Remove Player: " + userId);matchingPool.removePlayer(userId);return "success";}
}

可以在启动 matchingsystem 项目的时候就将该线程启动,即在 MatchingSystemApplication 这个主入口处启动:

package com.kob.matchingsystem;import com.kob.matchingsystem.service.impl.MatchingServiceImpl;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class MatchingSystemApplication {public static void main(String[] args) {MatchingServiceImpl.matchingPool.start();  // 启动匹配线程SpringApplication.run(MatchingSystemApplication.class, args);}
}

2.4 Web后端接收匹配结果

我们的 Web 后端还需要从 matchingsystem 接收请求,即接收匹配系统匹配成功的信息。在 backend 项目的 service 以及 service.impl 包下创建 pk 包,然后在 service.pk 包下创建 StartGameService 接口:

package com.kob.backend.service.pk;public interface StartGameService {String startGame(Integer aId, Integer bId);
}

然后在 service.impl.pk 包下创建接口的实现 StartGameServiceImpl

package com.kob.backend.service.impl.pk;import com.kob.backend.consumer.WebSocketServer;
import com.kob.backend.service.pk.StartGameService;
import org.springframework.stereotype.Service;@Service
public class StartGameServiceImpl implements StartGameService {@Overridepublic String startGame(Integer aId, Integer bId) {System.out.println("Start Game: Player " + aId + " and Player " + bId);WebSocketServer webSocketServer = WebSocketServer.users.get(aId);webSocketServer.startGame(aId, bId);return "success";}
}

接着在 controller.pk 包下创建 StartGameController:

package com.kob.backend.controller.pk;import com.kob.backend.service.pk.StartGameService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;import java.util.Objects;@RestController
public class StartGameController {@Autowiredprivate StartGameService startGameService;@PostMapping("/pk/startgame/")public String startGame(@RequestParam MultiValueMap<String, String> data) {Integer aId = Integer.parseInt(Objects.requireNonNull(data.getFirst("a_id")));Integer bId = Integer.parseInt(Objects.requireNonNull(data.getFirst("b_id")));return startGameService.startGame(aId, bId);}
}

实现完最后别忘了在 SecurityConfig 中放行这个 URL。

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

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

相关文章

《形式语言与自动机理论(第4版)》笔记(二)

文章目录 [toc]前导《形式语言与自动机理论&#xff08;第4版&#xff09;》笔记&#xff08;一&#xff09; 第三章&#xff1a;有穷状态自动机3.1|语言的识别3.2|有穷状态自动机即时描述 s e t ( ) set() set()例题问题 1 1 1解答问题 2 2 2解答 3.3|不确定的有穷状态自动机构…

pandas详细笔记

一&#xff1a;什么是Pandas from matplotlib import pyplot import numpy as np import pandas as pdarange np.arange(1, 10, 2) series pd.Series(arange,indexlist("ABCDE")) print(series)二&#xff1a;索引 三&#xff1a;切片 位置索引切片&#xff08;左闭…

.mallox勒索病毒解密:恢复数据与网络安全对策

引言&#xff1a; 在网络威胁不断演变的今天&#xff0c;恶意软件如.mallox勒索病毒已成为数字安全的一大挑战。本文将深入介绍.mallox勒索病毒&#xff0c;以及如何有效地恢复被其加密的数据文件&#xff0c;并提供一些建议用于预防此类威胁。如不幸感染这个勒索病毒&#xf…

【数据结构(七)】查找算法

文章目录 查找算法介绍1. 线性查找算法2. 二分查找算法2.1. 思路分析2.2. 代码实现2.3. 功能拓展 3. 插值查找算法3.1. 前言3.2. 相关概念3.3. 实例应用 4. 斐波那契(黄金分割法)查找算法4.1. 斐波那契(黄金分割法)原理4.2. 实例应用 查找算法介绍 在 java 中&#xff0c;我们…

Linux快速搭建本地yum更新audit

场景&#xff1a;内网一台服务器上线&#xff0c;需要更新audit版本&#xff0c;因无法与其他服务器通信&#xff0c;需临时配置本地仓库。 1、上传新版本操作系统iso到服务器 2、创建yum仓库文件存储目录 mkdir /opt/myrepo 3、挂载磁盘到/mnt mount /opt/Kylin-Server-V…

电脑CentOS 7.6与Windows系统对比:使用方式、优缺点概述

在多操作系统环境中&#xff0c;CentOS 7.6和Windows系统各自独占鳌头&#xff0c;它们在功能、稳定性、兼容性以及安全性等方面都有着各自的优点。这篇文章将对比分析这两个操作系统&#xff0c;以便用户能更好地了解它们的特点和使用方式。 一、使用方式 CentOS 7.6 CentO…

探索Web前端技术的变革与未来发展

Web前端技术作为构建现代互联网应用的重要一环&#xff0c;自诞生以来已经经历了多轮的发展和变革。本文将回顾过去的进展&#xff0c;介绍当前的前端技术栈&#xff0c;并展望未来前端领域的发展趋势&#xff0c;包括新兴技术和重要概念。 引言 在信息时代的快速发展的背景下&…

【剑指offer|图解|位运算】训练计划VI+撞色搭配

&#x1f308;个人主页&#xff1a;聆风吟 &#x1f525;系列专栏&#xff1a;数据结构、剑指offer每日一练 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 一. ⛳️训练计划VI&#xff08;题目难度&#xff1a;中等&#xff09;1.1 题目1.2 示例1.3 …

读书笔记-《数据结构与算法》-摘要3[选择排序]

选择排序 核心&#xff1a;不断地选择剩余元素中的最小者。 找到数组中最小元素并将其和数组第一个元素交换位置。在剩下的元素中找到最小元素并将其与数组第二个元素交换&#xff0c;直至整个数组排序。 性质&#xff1a; 比较次数(N-1)(N-2)(N-3)…21~N^2/2交换次数N运行…

基于ssm vue的风景文化管理平台源码和论文

摘 要 随着信息化时代的到来&#xff0c;管理系统都趋向于智能化、系统化&#xff0c;基于vue的木里风景文化管理平台也不例外&#xff0c;但目前国内的市场仍都使用人工管理&#xff0c;市场规模越来越大&#xff0c;同时信息量也越来越庞大&#xff0c;人工管理显然已无法应对…

SpringBoot集成mail发送邮件

前言 发送邮件功能&#xff0c;借鉴 刚果商城&#xff0c;根据文档及项目代码实现。整理总结便有了此文&#xff0c;文章有不对的点&#xff0c;请联系博主指出&#xff0c;请多多点赞收藏&#xff0c;您的支持是我最大的动力~ 发送邮件功能主要借助 mail、freemarker以及rocke…

[Firefly-Linux] RK3568修改控制台DEBUG为普通串口UART

文章目录 一、驱动文件二、menuconfig 配置三、dts 配置四、验证一、驱动文件 Rockchip UART作为控制台,使用fiq_debugger流程。rk-linux 一般会将uart2配置为ttyFIQ0设备。使用以下驱动文件: drivers/staging/android/fiq_debugger/fiq_debugger.c # 驱动文件 drivers/soc…

CoreDNS实战(七)-日志处理

本文主要用于介绍CoreDNS用来记录日志的几种方式以及在生产环境中遇到的一些问题和解决方案。 1 log插件 coredns的日志输出并不如nginx那么完善&#xff08;并不能在配置文件中指定输出的文件目录&#xff0c;但是可以指定日志的格式&#xff09;&#xff0c;默认情况下不论…

【Midjourney实战】| 新年礼盒元素设计

文章目录 1 初步提示词2 润色提示词3 提示词发散联想 这期实践任务&#xff0c;我们想去做一个新年礼盒的效果&#xff0c;最后我们想把不同元素拼在一起&#xff0c;方便后期进行新年的相关设计 1 初步提示词 提示词初步我们乍一想&#xff0c;肯定要包括主体元素礼盒 新年礼…

Verilog基础:$time、$stime和$realtime系统函数的使用

相关阅读 Verilog基础https://blog.csdn.net/weixin_45791458/category_12263729.html $time、 $stime和$realtime这三个系统函数提供了返回当前仿真时间方法。注意&#xff0c;这里的仿真时间的最小分辨能力是由仿真时间精度决定的&#xff0c;简单来说&#xff0c;可以理解为…

安卓adb【备忘录】

adb常用命令 第三方包 pm list package -3查看所有包【外部直接进入】 adb shell pm list package退出 exit安装 adb install [路径]卸载 adb uninstall [包名]下载手机中的文件到电脑 adb pull /etc/hosts D:\tmp\电脑上传文件至手机【需要root】 adb push D:\tmp\hos…

gpt阅读论文利器

1. txyz.ai 读论文 严伯钧 3. consensus 两亿科学论文的资源库. 用英文. 中国经济发展, 美国加州没有,减肥没有. 2. chrome插件 gpt sidebar 3. gpt academic 论文润色和学术翻译 ,一键输出公式. 英语口语8000句. 托福备考计划表. 百词斩托福. 薄荷外刊. 分区笔记精读法.…

【STM32】EXTI外部中断

1 中断系统 1.1 中断简介 中断&#xff1a;在主程序运行过程中&#xff0c;出现了特定的中断触发条件&#xff08;中断源&#xff09;&#xff0c;使得CPU暂停当前正在运行的程序&#xff0c;转而去处理中断程序&#xff0c;处理完成后又返回原来被暂停的位置继续运行。 比如&a…

GSLB是什么?谈谈对该技术的一点理解

GSLB是什么&#xff1f;它又称为全局负载均衡&#xff0c;是主流的负载均衡类型之一。众所周知&#xff0c;负载均衡位于服务器的前面&#xff0c;负责将客户端请求路由到所有能够满足这些请求的服务器&#xff0c;同时最大限度地提高速度和资源利用率&#xff0c;并确保无任何…

试着总结一下:pg的vacuum机制

1. 什么是vacuum 1.1. 什么是vacuum 在 PostgreSQL 数据库中&#xff0c;VACUUM 是一种用于管理和维护表的操作。它主要用于两个目的&#xff1a; 1.1.1. 释放未使用的空间 当在表中进行删除、更新或移动行时&#xff0c;PostgreSQL 并不会立即释放磁盘上占用的空间。相反&…