首页 > 编程语言 >aspnetcore使用websocket实时更新商品信息

aspnetcore使用websocket实时更新商品信息

时间:2024-01-05 17:46:10浏览次数:39  
标签:product 商品信息 websocket string aspnetcore System private products using

先演示一下效果,再展示代码逻辑。

中间几次调用过程省略。。。

暂时只用到了下面四个项目

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)

 

标签:product,商品信息,websocket,string,aspnetcore,System,private,products,using
From: https://www.cnblogs.com/morec/p/17947739

相关文章

  • H5中为什么要有websocket
    在线网络游戏,是最重要的游戏形式,通过互联网,玩家可以一起联网对战,能带来很多的乐趣。H5微信小游戏,抖音小游戏怎么样做联网对战和传统的有什么不一样的? 首先我们来看传统的联网,我们直接用TCPsocket, 而H5的标准里面,我们用的是websocket,今天我们来说一说websocket 到底是什......
  • uniapp:全局消息是推送,实现app在线更新,WebSocket,apk上传
    全局消息是推送,实现app在线更新,WebSocket1.在main.js中定义全局的WebSocket2.java后端建立和发送WebSocket3.通知所有用户更新背景:开发人员开发后app后打包成.apk文件,上传后通知厂区在线用户更新app。那么没在线的怎么办?因为我们在上一篇博客中写了,在app打开的时候回去校验是否......
  • uniapp:实现手机端APP登录强制更新,从本地服务器下载新的apk更新,并使用WebSocket,实时强
    实现登录即更新,或实时监听更新本文介绍的是在App打开启动的时候调用更新,点击下方链接,查看使用WebSocket实现实时通知在线用户更新。uniapp:全局消息是推送,实现app在线更新,WebSocket,apk上传:登录更新流程1.在app每次启动的时候请求java后端,2.后端接口获取最新的版本:3.打开更新页面4......
  • WebSocket连接实现实时数据推送
    WebSocket连接实现实时数据推送1、前端1-1、webSocket.js//暴露自定义websocket对象exportconstsocket={//后台请求路径url:"",//websocket对象websocket:null,//websocket状态websocketState:false,//重新连接次数reconnectN......
  • AspNetCore的单元测试
    一、单元测试项目如何创建VisualStudio新建项目,项目类型选择测试。 可以看到有许多选项,都大差不差。这里选择xUnit。项目名使用要测试的项目名加上“.Tests”后缀。二、进行单元测试首先明确测试对象,通常是针对一个类型进行测试,新建Test类,类名为测试对象的类名加上“Test......
  • 记一下在IIS中部署WebSocket服务的经验
    因业务需求需要使用长连接推送数据,这边选择使用.NET框架原生支持的WebSocket技术。 一、版本要求 对于IIS的版本必须是IIS8.0及以上 .NETFramework版本必须为4.5及以上PS:低于上述版本,需要自己实现协议或寻找第三方实现 二、IIS配置在服务器IIS上安装“WebSoc......
  • unigui显示websocket服务端向客户端发送信息【15】
    用WebSocket从服务端直接发送消息给all客户端。1、在ServerModule放TUniThreadTimer 2、timerevent:procedureTUniServerModule.UniThreadTimer1Timer(Sender:TObject);beginBroadcastMessage('update',[......
  • ChatGPT对话为什么不用WebSocket而使用EventSource?
    文章目录1.引言2.WebSocket和EventSource简介2.1WebSocket2.2EventSource3.ChatGPT对话系统的特点4.EventSource的优势4.1简单易用4.2容错性强4.3兼容性良好5.为何选择EventSource而非WebSocket?5.1单向通信模式5.2长轮询模式5.3简化部署和维护6.使用EventSource的代......
  • 用Python进行websocket接口测试
    我们在做接口测试时,除了常见的http接口,还有一种比较多见,就是socket接口,今天讲解下怎么用Python进行websocket接口测试。现在大多数用的都是websocket,那我们就先来安装一下websocket的安装包。pipinstallwebsocket-client 安装完之后,我们就开始我们的websocket之旅了。我们先来看......
  • websocket++
    一、介绍版本:WebSocket++(0.8.2)1、readme.md参照readme.mdWebSocket++是一个只有头文件(只有hpp文件)的c++库,它实现了RFC6455(WebSocket协议)。它允许将WebSocket客户端和服务端集成到c++程序中。它使用了可互换的网络传输模块,包括:基于原始字符缓冲区的模块基于c++的iostre......