WebSocket的那些事(5-Spring STOMP支持之连接外部消息代理)
目录
- 一、序言
- 二、开启RabbitMQ外部消息代理
- 三、代码示例
- 1、Maven依赖项
- 2、相关实体
- 3、自定义用户认证拦截器
- 4、Websocket外部消息代理配置
- 5、ChatController
- 6、前端页面chat.html
- 四、测试示例
- 1、群聊、私聊、后台定时推送测试
- 2、登录RabbitMQ控制台查看队列信息
- 五、结语
一、序言
上节我们在 WebSocket的那些事(4-Spring中的STOMP支持详解) 中详细说明了通过Spring内置消息代理
结合STOMP子协议进行Websocket通信,以及相关注解的使用及原理。
但是Spring内置消息代理会有一些限制,比如只支持STOMP协议的一部分命令,像acks
、receipts
命令都是不支持的,还有由于内置消息代理把消息存储在内存,当应用不可用时,客户端也就订阅不到到后台推送的消息。
这节我们将会使用支持STOMP协议的外部消息代理(RabbitMQ
)进行Websocket通信。
二、开启RabbitMQ外部消息代理
服务端路由发送消息以及客户端订阅消息都要通过STOMP协议与RabbitMQ进行交互,由于RabbitMQ默认没有启动STOMP插件,因此我们需要先启用该插件。
rabbitmq-plugins enable rabbitmq_stomp
启动该插件后,RabbitMQ中STOMP适配器
默认会监听61613
端口,如果是云服务器,需要把该端口在安全组中放开。
关于该插件说明请参考:RabbitMQ中STOMP插件说明。
三、代码示例
我们在 WebSocket的那些事(4-Spring中的STOMP支持详解)中写了一个简单的聊天Demo示例,下面我们对该聊天Demo示例进行改造,将Spring内置消息代理替换成RabbitMQ
外部消息代理。
1、Maven依赖项
服务端和客户端与外部消息代理都是通过TCP进行通信,Spring底层默认使用的是Netty
和Reactor
,因此需要引入相关依赖项。
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-reactor-netty</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
2、相关实体
(1) 请求消息参数
@Data
public class WebSocketMsgDTO {private String name;private String content;
}
(2) 响应消息内容
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class WebSocketMsgVO {private String content;
}
(3) 自定义认证用户信息
@Data
@AllArgsConstructor
@NoArgsConstructor
public class StompAuthenticatedUser implements Principal {/*** 用户唯一ID*/private String userId;/*** 用户昵称*/private String nickName;/*** 用于指定用户消息推送的标识* @return*/@Overridepublic String getName() {return this.userId;}}
3、自定义用户认证拦截器
@Slf4j
public class UserAuthenticationChannelInterceptor implements ChannelInterceptor {private static final String USER_ID = "User-ID";private static final String USER_NAME = "User-Name";@Overridepublic Message<?> preSend(Message<?> message, MessageChannel channel) {StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);// 如果是连接请求,记录userIdif (StompCommand.CONNECT.equals(accessor.getCommand())) {String userID = accessor.getFirstNativeHeader(USER_ID);String username = accessor.getFirstNativeHeader(USER_NAME);log.info("Stomp User-Related headers found, userID: {}, username:{}", userID, username);accessor.setUser(new StompAuthenticatedUser(userID, username));}return message;}}
4、Websocket外部消息代理配置
Spring中与外部消息代理通信的中间方被称之为Broker Relay,它会维护一个系统共享的单一TCP连接和外部消息代理进行通信,该TCP连接仅仅适用于服务端,用来发送消息,而不是接收消息,通过Broker Relay的systemLogin
和systemPasscode
属性可以设置该连接的认证信息。
Broker Relay也会为每个连接的Websocket客户端创建一个TCP连接,该连接用来接收消息,通过clientLogin
和clientPasscode
属性可以设置连接的认证信息。
/*** Websocket连接外部消息代理配置* @author Nick Liu* @date 2023/9/6*/
@Configuration
@EnableWebSocketMessageBroker
public class WebsocketExternalMessageBrokerConfig implements WebSocketMessageBrokerConfigurer {@Overridepublic void configureClientInboundChannel(ChannelRegistration registration) {// 拦截器配置registration.interceptors(new UserAuthenticationChannelInterceptor());}@Overridepublic void registerStompEndpoints(StompEndpointRegistry registry) {registry.addEndpoint("/websocket") // WebSocket握手端口.addInterceptors(new HttpSessionHandshakeInterceptor()).setAllowedOriginPatterns("*") // 设置跨域.withSockJS(); // 开启SockJS回退机制}@Overridepublic void configureMessageBroker(MessageBrokerRegistry registry) {registry.setApplicationDestinationPrefixes("/app") // 发送到服务端目的地前缀.enableStompBrokerRelay("/topic") // 开启外部消息代理,指定消息订阅前缀.setRelayHost("localhost") // 外部消息代理Host.setRelayPort(61613) // 外部消息代理STOMP端口.setSystemLogin("admin") // 共享系统连接用户名,该连接主要用来发送消息.setSystemPasscode("admin") // 共享系统连接密码,该连接主要用来发送消息.setClientLogin("admin") // 客户端连接用户名,该连接主要用来接收消息.setClientPasscode("admin") // 客户端连接密码,该连接主要用来接收消息.setVirtualHost("/stomp"); // RabbitMQ虚拟主机}
}
备注:我们可以为服务端与客户端的连接设置不同的用户,针对客户端连接用户进行权限管控,保证系统的安全性,在这里为了方便测试我们统一用一个用户。
5、ChatController
STOMP协议并没有规定消息代理必须支持哪种类型的Destinations(目的地)
,但是RabbitMQ STOMP适配器只支持一些指定的目的地类型,如下图:
/exchange
:指定交换机和路由key,发送和订阅来自队列的消息。/queue
:发送和订阅受STOMP网关管理的队列的消息,最多只有一个订阅者能到消息。/amq/queue
:发送和订阅不受STOMP网关管理的队列的消息。/topic
:发送和订阅来自临时或者持久Topic的消息,多个订阅者都能接收到消息。/temp-queue/
:发送和订阅来自临时队列的消息。
参考文档见:RabbitMQ中STOMP插件说明。
在下面的示例中,我们选用了/topic
的开头的消息发送和订阅前缀,目的地格式只能为/topic/{routing-key}
,routing-key不能有斜杠,否则会报错。
@Slf4j
@Controller
@RequiredArgsConstructor
public class ChatController {private final SimpUserRegistry simpUserRegistry;private final SimpMessagingTemplate simpMessagingTemplate;/*** 模板引擎为Thymeleaf,需要加上spring-boot-starter-thymeleaf依赖,* @return*/@GetMapping("/page/chat")public ModelAndView turnToChatPage() {return new ModelAndView("chat");}/*** 群聊消息处理* 这里我们通过@SendTo注解指定消息目的地为"/topic/chat/group",如果不加该注解则会自动发送到"/topic" + "/chat/group"* @param webSocketMsgDTO 请求参数,消息处理器会自动将JSON字符串转换为对象* @return 消息内容,方法返回值将会广播给所有订阅"/topic/chat/group"的客户端*/@MessageMapping("/chat/group")@SendTo("/topic/chat-group")public WebSocketMsgVO groupChat(WebSocketMsgDTO webSocketMsgDTO) {log.info("Group chat message received: {}", FastJsonUtils.toJsonString(webSocketMsgDTO));String content = String.format("来自[%s]的群聊消息: %s", webSocketMsgDTO.getName(), webSocketMsgDTO.getContent());return WebSocketMsgVO.builder().content(content).build();}/*** 私聊消息处理* 这里我们通过@SendToUser注解指定消息目的地为"/topic/chat/private",发送目的地默认会拼接上"/user/"前缀* 实际发送目的地为"/user/topic/chat/private"* @param webSocketMsgDTO 请求参数,消息处理器会自动将JSON字符串转换为对象* @return 消息内容,方法返回值将会基于SessionID单播给指定用户*/@MessageMapping("/chat/private")@SendToUser("/topic/chat-private")public WebSocketMsgVO privateChat(WebSocketMsgDTO webSocketMsgDTO) {log.info("Private chat message received: {}", FastJsonUtils.toJsonString(webSocketMsgDTO));String content = "私聊消息回复:" + webSocketMsgDTO.getContent();return WebSocketMsgVO.builder().content(content).build();}/*** 定时消息推送,这里我们会列举所有在线的用户,然后单播给指定用户。* 通过SimpMessagingTemplate实例可以在任何地方推送消息。*/@Scheduled(fixedRate = 10 * 1000)public void pushMessageAtFixedRate() {log.info("当前在线人数: {}", simpUserRegistry.getUserCount());if (simpUserRegistry.getUserCount() <= 0) {return;}// 这里的Principal为StompAuthenticatedUser实例Set<StompAuthenticatedUser> users = simpUserRegistry.getUsers().stream().map(simpUser -> StompAuthenticatedUser.class.cast(simpUser.getPrincipal())).collect(Collectors.toSet());users.forEach(authenticatedUser -> {String userId = authenticatedUser.getUserId();String nickName = authenticatedUser.getNickName();WebSocketMsgVO webSocketMsgVO = new WebSocketMsgVO();webSocketMsgVO.setContent(String.format("定时推送的私聊消息, 接收人: %s, 时间: %s", nickName, LocalDateTime.now()));log.info("开始推送消息给指定用户, userId: {}, 消息内容:{}", userId, FastJsonUtils.toJsonString(webSocketMsgVO));simpMessagingTemplate.convertAndSendToUser(userId, "/topic/chat-push", webSocketMsgVO);});}}
6、前端页面chat.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>chat</title><script src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.6.1/sockjs.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.4/jquery.min.js"></script><style>#mainWrapper {width: 600px;margin: auto;}</style>
</head>
<body>
<div id="mainWrapper"><div><label for="username" style="margin-right: 5px">姓名:</label><input id="username" type="text"/></div><div id="msgWrapper"><p style="vertical-align: top">发送的消息:</p><textarea id="msgSent" style="width: 600px;height: 100px"></textarea><p style="vertical-align: top">收到的群聊消息:</p><textarea id="groupMsgReceived" style="width: 600px;height: 100px"></textarea><p style="vertical-align: top">收到的私聊消息:</p><textarea id="privateMsgReceived" style="width: 600px;height: 200px"></textarea></div><div style="margin-top: 5px;"><button onclick="connect()">连接</button><button onclick="sendGroupMessage()">发送群聊消息</button><button onclick="sendPrivateMessage()">发送私聊消息</button><button onclick="disconnect()">断开连接</button></div>
</div>
<script type="text/javascript">$(() => {$('#msgSent').val('');$("#groupMsgReceived").val('');$("#privateMsgReceived").val('');});let stompClient = null;// 连接服务器const connect = () => {const header = {"User-ID": new Date().getTime().toString(), "User-Name": $('#username').val()};const ws = new SockJS('http://localhost:8080/websocket');stompClient = Stomp.over(ws);stompClient.connect(header, () => subscribeTopic());}// 订阅主题const subscribeTopic = () => {alert("连接成功!");// 订阅广播消息stompClient.subscribe('/topic/chat-group', function (message) {console.log(`Group message received : ${message.body}`);const resp = JSON.parse(message.body);const previousMsg = $("#groupMsgReceived").val();$("#groupMsgReceived").val(`${previousMsg}${resp.content}\n`);});// 订阅单播消息stompClient.subscribe('/user/topic/chat-private', message => {console.log(`Private message received : ${message.body}`);const resp = JSON.parse(message.body);const previousMsg = $("#privateMsgReceived").val();$("#privateMsgReceived").val(`${previousMsg}${resp.content}\n`);});// 订阅定时推送的单播消息stompClient.subscribe(`/user/topic/chat-push`, message => {console.log(`Private message received : ${message.body}`);const resp = JSON.parse(message.body);const previousMsg = $("#privateMsgReceived").val();$("#privateMsgReceived").val(`${previousMsg}${resp.content}\n`);});};// 断连const disconnect = () => {stompClient.disconnect(() => {$("#msgReceived").val('Disconnected from WebSocket server');});}// 发送群聊消息const sendGroupMessage = () => {const msg = {name: $('#username').val(), content: $('#msgSent').val()};stompClient.send('/app/chat/group', {}, JSON.stringify(msg));}// 发送私聊消息const sendPrivateMessage = () => {const msg = {name: $('#username').val(), content: $('#msgSent').val()};stompClient.send('/app/chat/private', {}, JSON.stringify(msg));}
</script>
</body>
</html>
四、测试示例
1、群聊、私聊、后台定时推送测试
启动应用程序,日志打印显示系统连接建立成功,如下:
打开浏览器访问http://localhost:8080/page/chat
可进入聊天页,同时打开两个窗口访问。
2、登录RabbitMQ控制台查看队列信息
可以看到所有消息都发送到了amq.topic
交换机上(Topic类型), RabbitMQ会为每个连接的客户端创建3个队列。
因为我们在ChatController
中定义了三个目的地,Routing Key分别是/topic/chat-group
、/topic/chat-private
、/topic/chat-push
。群聊消息目的地/topic/chat-group
绑定了两个队列,用于实现广播订阅,其它两个Routing Key分别绑定到了不同的队列上,实现唯一订阅。
五、结语
下一节我们将会详细说明RabbitMQ STOMP适配器支持的各种消息目的地类型的区别以及适用场景。
相关文章:
WebSocket的那些事(5-Spring STOMP支持之连接外部消息代理)
目录 一、序言二、开启RabbitMQ外部消息代理三、代码示例1、Maven依赖项2、相关实体3、自定义用户认证拦截器4、Websocket外部消息代理配置5、ChatController6、前端页面chat.html 四、测试示例1、群聊、私聊、后台定时推送测试2、登录RabbitMQ控制台查看队列信息 五、结语 一、…...
【数据结构】单链表详解
当我们学完顺序表的时候,我们发现了好多问题如下: 中间/头部的插入删除,时间复杂度为O(N)增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当…...
dql的执行顺序
在 SQL 查询语言中,DQL(Data Query Language)是用于从数据库中检索数据的部分。SQL 查询的执行顺序通常按照以下步骤进行: FROM 子句:查询首先确定要从哪些表中检索数据。在 FROM 子句中列出的表格被称为源表ÿ…...
java的动态代理如何实现
一. JdkProxy jdkproxy动态代理必须基于接口(interface)实现 接口UserInterface.java public interface UserService {String getUserName(String userCde); }原始实现类:UseServiceImpl.java public class UserServiceImpl implements UserSerice {Overridepub…...
Java--日志管理
日志管理 作用: 设置日志级别,决定什么日志信息应该被输出、什么日志信息应该被忽略。 基本工具 见的日志管理用具有:JDK logging(配置文件:logging.properties) 和log4j(配置文件:log4j.properties) 。…...
Pygame中Sprite类的使用2
4 让僵尸动起来 让僵尸能够动起来,也就是让僵尸从屏幕右边走到屏幕左边,此时只需要使用while循环,改变僵尸图片的x轴坐标即可,代码如下所示。 while True:screen.fill((255,255,255))z1.rect.x - 5z1.draw(screen)z1.update()if…...
排队时延与流量强度
流量强度 设R为传输速率,a表示分组到达队列的平均速率,假定所有分组都是由L比特组成的,则比特到达队列的平均速率为La。比率 L a R \frac{La}{R} RLa被成为流量强度。 根据流量强度的定义,我们可以很直观的得出以下结论&#x…...
mysql:如何设计互相关注业务场景
目录 业务场景 业务问题: 数据库表设计: like(关注表): friend(朋友表) 并发场景下,SQL语句执行逻辑 比较 A 和 B 的大小,如果 A执行下面的逻辑:<&…...
AI伦理:科技发展中的人性之声
文章目录 AI伦理的关键问题1. 隐私问题2. 公平性问题3. 自主性问题4. 伦理教育问题 隐私问题的拓展分析数据收集和滥用隐私泄露和数据安全 公平性问题的拓展分析历史偏见和算法模型可解释性 自主性问题的拓展分析自主AI决策伦理框架 伦理教育的拓展分析伦理培训 结论 …...
Direct3D光照
光照的组成 环境光:这种类型的光经其他表面反射到达物体表面,并照亮整个场景,要想以较低代价粗略模拟这类反射光,环境光是一个很好的选择 漫射光:这种类型光沿着特定的方向传播。当它到达某一表面时,将沿…...
编程语言排行榜
以下是2023年的编程语言排行榜(按照流行度排序): Python:Python一直以来都是非常受欢迎的编程语言,它简洁、易读且功能强大。在数据科学、机器学习、人工智能等领域有广泛应用。 JavaScript:作为前端开发…...
基于语雀编辑器的在线文档编辑与查看
概述 语雀是一个非常优秀的文档和知识库工具,其编辑器更是非常好用,虽无开源版本,但有编译好的可以使用。本文基于语雀编辑器实现在线文档的编辑与文章的预览。 实现效果 实现 参考语雀编辑器官方文档,其实现需要引入以下文件&…...
开箱报告,Simulink Toolbox库模块使用指南(六)——S-Fuction模块(TLC)
文章目录 前言 Target Language Compiler(TLC) C MEX S-Function模块 编写TLC文件 生成代码 Tips 分析和应用 总结 前言 见《开箱报告,Simulink Toolbox库模块使用指南(一)——powergui模块》 见《开箱报告&am…...
Kafka详解
目录 一、消息系统 1、点对点的消息系统 2、发布-订阅消息系统 二、Apache Kafka 简介 三、Apache Kafka基本原理 3.1 分布式和分区(distributed、partitioned) 3.2 副本(replicated ) 3.3 整体数据流程 3.4 消息传送机制…...
rabbitmq+springboot实现幂等性操作
文章目录 1.场景描述 1.1 场景11.2 场景2 2.原理3.实战开发 3.1 建表3.2 集成mybatis-plus3.3 集成RabbitMq 3.3.1 安装mq3.3.2 springBoot集成mq 3.4 具体实现 3.4.1 mq配置类3.4.2 生产者3.4.3 消费者 1.场景描述 消息中间件是分布式系统常用的组件,无论是异…...
ubuntu server 更改时区:上海
1. 打开终端,在命令行中以超级用户或具有sudo权限的用户身份运行以下命令: sudo dpkg-reconfigure tzdata 这会打开一个对话框,用于选择系统的时区设置。 2. 在对话框中,使用上下箭头键在地区列表中选择"Asia"&#x…...
java 整合 swagger-ui 步骤
1.在xml 中添加Swagger 相关依赖 <!-- springfox-swagger2 --><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.9.2</version></dependency><!-- springfox-swa…...
介绍两款生成神经网络架构示意图的工具:NN-SVG和PlotNeuralNet
对于神经网络架构的可视化是很有意义的,可以在很大程度上帮助到我们清晰直观地了解到整个架构,我们在前面的 PyTorch的ONNX结合MNIST手写数字数据集的应用(.pth和.onnx的转换与onnx运行时) 有介绍,可以将模型架构文件(常见的格式都可以)在线上…...
iOS IdiotAVplayer实现视频分片缓存
文章目录 IdiotAVplayer 实现视频切片缓存一 iOS视频边下边播原理一 分片下载的实现1 分片下载的思路2 IdiotAVplayer 实现架构 三 IdiotAVplayer 代码解析IdiotPlayerIdiotResourceLoaderIdiotDownLoader IdiotAVplayer 实现视频切片缓存 一 iOS视频边下边播原理 初始化AVUR…...
SpringBootWeb请求-响应
HTTP请求 前后端分离 在这种模式下,前端技术人员基于"接口文档",开发前端程序;后端技术人员也基于"接口文档",开发后端程序。 由于前后端分离,对我们后端技术人员来讲,在开发过程中&a…...
List集合详解
目录 1、集合是什么? 1.1、集合与集合之间的关系 2、List集合的特点 3、遍历集合的三种方式 3.1、foreach(增强佛如循环遍历) 3.2、for循环遍历 3.3、迭代器遍历 4、LinkedList和ArrayList的区别 4.1、为什么ArrayList查询会快一些? 4.2、为什么LinkedLi…...
投稿指南【NO.12_8】【极易投中】核心期刊投稿(组合机床与自动化加工技术)
近期有不少同学咨询投稿期刊的问题,大部分院校的研究生都有发学术论文的要求,少部分要求高的甚至需要SCI或者多篇核心期刊论文才可以毕业,但是核心期刊要求论文质量高且审稿周期长,所以本博客梳理一些计算机特别是人工智能相关的期…...
解决git无法上传大文件(50MB)
解决方法 使用LFS解决GitHub无法上传大于50MB的文件 LFS简介 Git LFS(Large File Storage)是 Git 的一个扩展,用于管理大型文件,如二进制文件、图像、音频和视频文件等。它的主要目的是解决 Git 对大型二进制文件的版本控制和存…...
用递归实现字符串逆序(不使用库函数)
文章目录 前言一、题目要求二、解题步骤1.大概框架2.如何反向排列?3.模拟实现strlen4.实现反向排列5.递归实现反向排列 总结 前言 嗨,亲爱的读者们!我是艾老虎尤,今天,我们将探索一个题目,这个题目对新手非…...
初学python(一)
一、python的背景和前景 二、 python的一些小事项 1、在Java、C中,2 / 3 0,也就是整数 / 整数 整数,会把小数部分舍掉。而在python中2 / 3 0.66666.... 不会舍掉小数部分。 在编程语言中,浮点数遵循IEEE754标准,不…...
Excel VSTO开发8 -相关控件
版权声明:本文为博主原创文章,转载请在显著位置标明本文出处以及作者网名,未经作者允许不得用于商业目的。 8 相关控件 在VSTO开发中,Ribbon(或称为Ribbon UI)是指Office应用程序中的那个位于顶部的带有选…...
华为数据管理——《华为数据之道》
数据分析与开发 元数据是描述数据的数据,用于打破业务和IT之间的语言障碍,帮助业务更好地理解数据。 元数据是数据中台的重要的基础设施,元数据治理贯彻数据产生、加工、消费的全过程,沉淀了数据资产,搭建了技术和业务…...
Flink CDC 菜鸟教程 -环境篇
本教程将介绍如何使用 Flink CDC 来实现这个需求, 在 Flink SQL CLI 中进行,只涉及 SQL,无需一行 Java/Scala 代码,也无需安装 IDE。 系统的整体架构如下图所示: 环境篇 1、 准备一台Linux 2、准备教程所需要的组件 下载 flink-1.13.2 并将其解压至目录 flink-1.13.2 …...
【线上问题】linux部署docker应用docker-compose启动报端口占用问题(感觉上没有被占用)
目录 一、问题说明二、排查过程 一、问题说明 1.linux服务器使用的不是root用户权限 2.docker应用服务没有关闭的情况下,做了些重装docker,重启docker等操作 3.docker-compose up -d然后docker logs查看日志报端口被占用 4.netstat -ntpl | grep 端口 也…...
解决虚拟机克隆后IP和命名冲突问题
目录 解决IP冲突问题 解决命名冲突 解决IP冲突问题 克隆后的虚拟机和硬件地址和ip和我们原虚拟机的相同,我们需要重新生成硬件地址和定义ip,步骤如下: (1)进入 /etc/sysconfig/network-scripts/ifcfg-ens33 配置文件…...
企业网站硬件建设方案/腾讯企业qq
私信我或关注微信号:狮范课,回复:学习,获取免费学习资源包。前言Android开发过程可能需要用到的代码片段,一共35则。供需要时借鉴参考。精确获取屏幕尺寸例如:3.5、4.0、5.0寸屏幕:public stati…...
内江做网站的公司/百度指数功能模块有哪些
错误提示:System.Data.OleDb.OleDbException: 字段太小而不能接受所要添加的数据的数量。“/”应用程序中的服务器错误。-------------------------------------------------------------------------------- 字段太小而不能接受所要添加的数据的数量。试着插入或粘…...
做微信公众号必备的网站/如何建立独立网站
直接上代码。自己下载吧。 代码下载转载于:https://www.cnblogs.com/puzi0315/archive/2012/05/28/2521485.html...
wordpress a 登录/百度店铺怎么开通
读入一个正整数 n,计算其各位数字之和,用汉语拼音写出和的每一位数字。 输入格式: 每个测试输入包含 1 个测试用例,即给出自然数 n 的值。这里保证 n 小于 10100。 输出格式: 在一行内输出 n 的各位数字之和…...
纺织厂网站模板/seo检测
对于云服务器的使用,dokcer可以说是一个必备的工具,使用docker能够实现业务的快速部署,服务器资源的隔离,本篇文章让我们来看下如何使用docker快速的部署mysql数据库 1.docker启动之后,从docker仓库中,拉取…...
合肥晚报社官方网站/每日新闻最新消息
作为vue的初用者,你可能会像我一样遇到一个问题,对跳转组件是,我们想通过参数不同,调用不同的方法。 例如: app.vue <ul><li class"navList" v-for"index in goods" :key"index.…...