aspnetcore使用websocket实时更新商品信息
先演示一下效果,再展示代码逻辑。


中间几次调用过程省略。。。
暂时只用到了下面四个项目

1.产品展示页面中第一次通过接口去获取数据库的列表数据
/// <summary>
/// 获取指定的商品目录
/// </summary>
/// <param name="pageSize"></param>
/// <param name="pageIndex"></param>
/// <param name="ids"></param>
/// <returns></returns>
[HttpGet]
[Route("items")]
[ProducesResponseType(typeof(PaginatedViewModel<Catalog>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(IEnumerable<ProductDto>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> Catalogs([FromQuery] int pageSize = 10, [FromQuery] int pageIndex = 0, string ids = null)
{
if (!string.IsNullOrEmpty(ids))
{
var items = await GetItemByIds(ids);
if (!items.Any())
{
return BadRequest("ids value invalid. Must be comma-separated list of numbers");
} return Ok(items);
} var totalItems = await _catalogContext.Catalogs
.LongCountAsync(); var itemsOnPage = await _catalogContext.Catalogs
.OrderBy(c => c.Name)
.Skip(pageSize * pageIndex)
.Take(pageSize)
.ToListAsync();
var result = itemsOnPage.Select(x => new ProductDto(x.Id.ToString(), x.Name, x.Price.ToString(), x.Stock.ToString(), x.ImgPath));
var model = new PaginatedViewModel<ProductDto>(pageIndex, pageSize, totalItems, result);
return Ok(model); }
2.在前端页面会把当前页面的产品列表id都发送到websocket中去
function updateAndSendProductIds(ids) {
productIds = ids;
// Check if the WebSocket is open
if (socket.readyState === WebSocket.OPEN) {
// Send the list of product IDs through the WebSocket connection
socket.send(JSON.stringify(productIds));
}
}
function fetchData() {
const apiUrl = baseUrl + `/Catalog/items?pageSize=${pageSize}&pageIndex=${currentPage}`;
axios.get(apiUrl)
.then(response => {
const data = response.data.data;
displayProducts(baseUrl, data);
const newProductIds = data.map(product => product.Id);
// Check if the WebSocket is open
updateAndSendProductIds(newProductIds);
// 从响应中获取总页数
const totalPages = Math.ceil(response.data.count / pageSize);
displayPagination(totalPages);
// 更新当前页数的显示
const currentPageElement = document.getElementById('currentPage');
currentPageElement.textContent = `当前页数: ${currentPage + 1} / 总页数: ${totalPages}`;
})
.catch(error => {
console.error('获取数据失败:', error);
});
}
3.websocket拿到了id数据可以精确的把当前页面的产品都查出来再推送给product.html页面,通过下面的ReceiveAsync方法获取html发送的数据,再通过timer定时器每秒钟Send方法实时的往页面发送获取到的数据,当然这个是不断的去从redis中去查的。
using System.Net.WebSockets;
using System.Threading.Tasks;
using System;
using WsServer.Handler;
using WsServer.Manager;
using StackExchange.Redis;
using Microsoft.Extensions.Configuration;
using System.Collections.Generic;
using Catalogs.Domain.Catalogs;
using Catalogs.Domain.Dtos;
using System.Net.Sockets; namespace WebScoket.Server.Services
{
/// <summary>
/// 实时推送产品主要是最新的库存,其他信息也会更新
/// </summary>
public class ProductListHandler : WebSocketHandler
{
private System.Threading.Timer _timer;
private readonly IDatabase _redisDb;
//展示列表推送
private string productIdsStr;
public ProductListHandler(WebSocketConnectionManager webSocketConnectionManager,IConfiguration configuration) : base(webSocketConnectionManager)
{
ConnectionMultiplexer redis = ConnectionMultiplexer.Connect(configuration["DistributedRedis:ConnectionString"] ?? throw new Exception("$未能获取distributedredis连接字符串"));
_redisDb = redis.GetDatabase();
_timer = new System.Threading.Timer(Send, null, TimeSpan.Zero, TimeSpan.FromSeconds(1));
}
private void Send(object state)
{
// 获取当前时间并发送给所有连接的客户端
if (productIdsStr != null)
{
string[] productIds = System.Text.Json.JsonSerializer.Deserialize<string[]>(productIdsStr);
string hashKeyToRetrieve = "products";
List<ProductDto> products = new List<ProductDto>(); foreach (var productId in productIds)
{
if(productId == "null") {
continue;
}
string retrievedProductValue = _redisDb.HashGet(hashKeyToRetrieve, productId);
if (!string.IsNullOrEmpty(retrievedProductValue))
{
//反序列化和构造函数冲突,改造了一下Catalog
Catalog catalog = System.Text.Json.JsonSerializer.Deserialize<Catalog>(retrievedProductValue);
products.Add(new ProductDto(catalog.Id.ToString(), catalog.Name, catalog.Price.ToString(), catalog.Stock.ToString(), catalog.ImgPath));
}
}
if (products.Count > 0)
{
SendMessageToAllAsync(System.Text.Json.JsonSerializer.Serialize(products)).Wait();
}
else
{
SendMessageToAllAsync("NoProduct").Wait();
}
}
}
public override async Task ReceiveAsync(WebSocket socket, WebSocketReceiveResult result, byte[] buffer)
{
//每次页面有刷新就会拿到展示的id列表
productIdsStr = System.Text.Encoding.UTF8.GetString(buffer, 0, result.Count);
}
}
}
4.html页面就可以拿到最新数据再去绑定到页面
socket.addEventListener('message', (event) => {
if (event.data == "NoProduct") {
clearProductList();
}
// Handle the received product data and update the product list
const productData = JSON.parse(event.data);
// Update the product list with the received data (call your displayProducts function)
displayProducts(baseUrl, productData);
});
整个流程就这么简单,但是这里需要保持数据库和redis的数据实时同步,否则页面展示的就不是最新的数据就没意义了。
再回到Catalog.Service服务中。
private async Task DeleteCache()
{
//await _redisDb.HashDeleteAsync("products",id); //没必要了
await _channel.Writer.WriteAsync("delete_catalog_fromredis");
}
再做更新、新增、删除等动作的时候就调用一下DeleteCache方法,往后台服务发送一个channel,当后台收到后就做redis删除并且从初始化sqlserver到redis列表同步的操作
using System.Reflection;
using System.Threading.Channels;
using Catalogs.Infrastructure.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using StackExchange.Redis; namespace Catalogs.WebApi.BackgroudServices
{
/// <summary>
/// 记得任何删除了或者购买了产品后需要删除改产品的键
/// </summary>
public class InitProductListToRedisService : BackgroundService
{
private readonly IServiceScopeFactory _serviceScopeFactory;
private readonly IDatabase _redisDb;
private readonly Channel<string> _channel;
private readonly ILogger _logger;
public InitProductListToRedisService(IServiceScopeFactory serviceScopeFactory, IConfiguration configuration, Channel<string> channel, ILogger<InitProductListToRedisService> logger)
{
_serviceScopeFactory = serviceScopeFactory;
ConnectionMultiplexer redis = ConnectionMultiplexer.Connect(configuration["DistributedRedis:ConnectionString"] ?? throw new Exception("$未能获取distributedredis连接字符串"));
_redisDb = redis.GetDatabase();
_channel = channel;
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await Init(); while (!_channel.Reader.Completion.IsCompleted)
{
var msg = await _channel.Reader.ReadAsync();
if(msg == "delete_catalog_fromredis")
{
await Init();
}
}
} private async Task Init()
{
using var scope = _serviceScopeFactory.CreateScope();
try
{
CatalogContext _context = scope.ServiceProvider.GetRequiredService<CatalogContext>();
string hashKey = "products";
var products = await _context.Catalogs.ToListAsync(); await _redisDb.KeyDeleteAsync(hashKey); foreach (var product in products)
{ string productField = product.Id.ToString();
string productValue = System.Text.Json.JsonSerializer.Serialize(product); _redisDb.HashSet(hashKey, new HashEntry[] { new HashEntry(productField, productValue) });
} _logger.LogInformation($"ProductList is over stored in Redis Hash.");
}
catch(Exception ex)
{
_logger.LogError($"ProductLis stored in Redis Hash error.");
}
}
}
}
这里还有优化的空间可以只针对怕products的hashset的某个id去更新、删除、新增一条数据。
示例代码:
liuzhixin405/efcore-template (github.com)
aspnetcore使用websocket实时更新商品信息的更多相关文章
- WebSocket 实时更新mysql数据到页面
使用websocket的初衷是,要实时更新mysql中的报警信息到web页面显示 没怎么碰过web,代码写的是真烂,不过也算是功能实现了,放在这里也是鞭策自己,web也要多下些功夫 准备 引入依赖 & ...
- HTML5 WebSocket 实时推送信息测试demo
测试一下HTML5的websocket功能,实现了客户端→服务器实时推送信息到客户端,包括推送图片: websocket实现MessageInbound类 onTextMessage()/onBina ...
- C#Winform实时更新数据库信息Demo(使用Scoket)
最近在贴吧上看到有个提问就是关于怎么在Winform上实时的更新数据 提问者提到的是利用Timer去轮询,但最后经过网上查了下资料,感觉Socket也是可行的, 于是就写了这个Demo 这个Demo的 ...
- 第十二章——SQLServer统计信息(1)——创建和更新统计信息
原文:第十二章--SQLServer统计信息(1)--创建和更新统计信息 简介: 查询的统计信息: 目前为止,已经介绍了选择索引.维护索引.如果有合适的索引并实时更新统计信息,那么优化器会选择有用的索 ...
- web页面实时更新页面的原理--WebSocket
原文:https://www.jianshu.com/p/8f956cd4d42b angular-cli启动的项目也可以自动刷新,底下应该也是应用的websocket的原理. ----------- ...
- 使用Node.js+Socket.IO搭建WebSocket实时应用
Web领域的实时推送技术,也被称作Realtime技术.这种技术要达到的目的是让用户不需要刷新浏览器就可以获得实时更新.它有着广泛的应用场景,比如在线聊天室.在线客服系统.评论系统.WebIM等. W ...
- (转)使用Node.js+Socket.IO搭建WebSocket实时应用
Web领域的实时推送技术,也被称作Realtime技术.这种技术要达到的目的是让用户不需要刷新浏览器就可以获得实时更新.它有着广泛的应用场景,比如在线聊天室.在线客服系统.评论系统.WebIM等. W ...
- Web网站数据”实时”更新设计
请注意这个实时打上了双引号,没有绝对的实时,只是时间的颗粒不一样罢了(1ms,1s,1m). 服务器数据有更新可以快速通知客户端.Web 基于取得模式,而服务器建立大量的和客户端连接来提供数据实时更新 ...
- 使用Node.js+Socket.IO搭建WebSocket实时应用【转载】
原文:http://www.jianshu.com/p/d9b1273a93fd Web领域的实时推送技术,也被称作Realtime技术.这种技术要达到的目的是让用户不需要刷新浏览器就可以获得实时更新 ...
- 【用户交互】APP没有退出前台但改变系统属性如何实时更新UI?监听系统广播,让用户交互更舒心~
前日,一小伙伴问我一个问题,说它解决了半天都没解决这个问题,截图如下: 大概楼主理解如下: 如果在应用中有一个判断wifi的开关和一个当前音量大小的seekbar以及一个获取当前电量多少的按钮,想知道 ...
随机推荐
- 使用shuffle sharding增加容错性
使用shuffle sharding增加容错性 最近在看kubernetes的API Priority and Fairness,它使用shuffle sharding来为请求选择处理队列,以此防止高 ...
- 基于react18+vite4+arco.design搭建极简版后台管理模板
趁着国庆前夕整了一个vite4结合react18搭建后台管理模板,搭配上字节团队react组件库ArcoDesign,整体操作功能非常丝滑.目前功能支持多种模板布局.暗黑/亮色模式.国际化.权限验证. ...
- Django-rest-framework框架——路由组件、认证权限频率
@ 目录 一 路由Routers 1.1 使用方法 1.2 代码演示 1.2 视图集中附加action的声明 1.3 路由router形成URL的方式 认证权限频率 一 认证Authenticatio ...
- LUSH & LUXURIOUS
明亮色系Punchy & Bright 明亮.有着强烈对比的颜色更引人注目. 这种大胆的色彩组合要谨慎地利用,所以在明亮色系中的调和色一般用中性色. 其中不同的色彩饱和度,表现出不同的氛围和意 ...
- 2023-10-18:用go语言,给定一个数组arr,长度为n,表示有0~n-1号设备, arr[i]表示i号设备的型号,型号的种类从0~k-1,一共k种型号, 给定一个k*k的矩阵map,来表示型号
2023-10-18:用go语言,给定一个数组arr,长度为n,表示有0~n-1号设备, arr[i]表示i号设备的型号,型号的种类从0~k-1,一共k种型号, 给定一个k*k的矩阵map,来表示型号 ...
- Linux 回收站
聊一聊执行 rm -rf 数据恢复以及建立 Linux 回收站 误删除 rm -rf 如果在Linux 平台下,执行 rm -rf 误删除文件,我们可以做哪些数据恢复的工作以及我们该如何应对不小心删除 ...
- OpenWrt主题在菜单中不显示
问题: 路径中有对应的主题,但是make menuconfig中不显示 原因: 需要建立软连接 1. 在路径 SDK-DR232-20221220/package/feeds/luci 中运行 ls ...
- "拍牌神器"是怎样炼成的(二)--- 键鼠模拟之AutoIt
不同于上一篇的WinAPI方法,这次让我们来看另一个更简单.有效的键鼠模拟方案,即通过COM组件AutoItX实现键鼠模拟. AutoIt AutoIt是一个免费软件,它使用一种类似BASIC的脚本语 ...
- Centos7.5镜像获取
Centos.7.5镜像可从以下地址获取 镜像源地址:https://mirrors.tuna.tsinghua.edu.cn/ 1.下拉找到cc目录下的centos-vault 2.点击进入下一级目 ...
- 如何优雅使用 vuex
大纲 本文内容更多的是讲讲使用 vuex 的一些心得想法,所以大概会讲述下面这些点: Q1:我为什么会想使用 vuex 来管理数据状态交互? Q2:使用 vuex 框架有哪些缺点或者说副作用? Q3: ...