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

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

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

1.产品展示页面中第一次通过接口去获取数据库的列表数据

  1. /// <summary>
  2. /// 获取指定的商品目录
  3. /// </summary>
  4. /// <param name="pageSize"></param>
  5. /// <param name="pageIndex"></param>
  6. /// <param name="ids"></param>
  7. /// <returns></returns>
  8. [HttpGet]
  9. [Route("items")]
  10. [ProducesResponseType(typeof(PaginatedViewModel<Catalog>), StatusCodes.Status200OK)]
  11. [ProducesResponseType(typeof(IEnumerable<ProductDto>), StatusCodes.Status200OK)]
  12. [ProducesResponseType(StatusCodes.Status400BadRequest)]
  13. public async Task<IActionResult> Catalogs([FromQuery] int pageSize = 10, [FromQuery] int pageIndex = 0, string ids = null)
  14. {
  15. if (!string.IsNullOrEmpty(ids))
  16. {
  17. var items = await GetItemByIds(ids);
  18. if (!items.Any())
  19. {
  20. return BadRequest("ids value invalid. Must be comma-separated list of numbers");
  21. }
  22.  
  23. return Ok(items);
  24. }
  25.  
  26. var totalItems = await _catalogContext.Catalogs
  27. .LongCountAsync();
  28.  
  29. var itemsOnPage = await _catalogContext.Catalogs
  30. .OrderBy(c => c.Name)
  31. .Skip(pageSize * pageIndex)
  32. .Take(pageSize)
  33. .ToListAsync();
  34. var result = itemsOnPage.Select(x => new ProductDto(x.Id.ToString(), x.Name, x.Price.ToString(), x.Stock.ToString(), x.ImgPath));
  35. var model = new PaginatedViewModel<ProductDto>(pageIndex, pageSize, totalItems, result);
  36. return Ok(model);
  37.  
  38. }

2.在前端页面会把当前页面的产品列表id都发送到websocket中去

  1. function updateAndSendProductIds(ids) {
  2. productIds = ids;
  3.  
  4. // Check if the WebSocket is open
  5. if (socket.readyState === WebSocket.OPEN) {
  6. // Send the list of product IDs through the WebSocket connection
  7. socket.send(JSON.stringify(productIds));
  8. }
  9. }
  10.  
  11. function fetchData() {
  12.  
  13. const apiUrl = baseUrl + `/Catalog/items?pageSize=${pageSize}&pageIndex=${currentPage}`;
  14.  
  15. axios.get(apiUrl)
  16. .then(response => {
  17. const data = response.data.data;
  18. displayProducts(baseUrl, data);
  19.  
  20. const newProductIds = data.map(product => product.Id);
  21. // Check if the WebSocket is open
  22. updateAndSendProductIds(newProductIds);
  23. // 从响应中获取总页数
  24. const totalPages = Math.ceil(response.data.count / pageSize);
  25. displayPagination(totalPages);
  26.  
  27. // 更新当前页数的显示
  28. const currentPageElement = document.getElementById('currentPage');
  29. currentPageElement.textContent = `当前页数: ${currentPage + 1} / 总页数: ${totalPages}`;
  30. })
  31. .catch(error => {
  32. console.error('获取数据失败:', error);
  33. });
  34. }

3.websocket拿到了id数据可以精确的把当前页面的产品都查出来再推送给product.html页面,通过下面的ReceiveAsync方法获取html发送的数据,再通过timer定时器每秒钟Send方法实时的往页面发送获取到的数据,当然这个是不断的去从redis中去查的。

  1. using System.Net.WebSockets;
  2. using System.Threading.Tasks;
  3. using System;
  4. using WsServer.Handler;
  5. using WsServer.Manager;
  6. using StackExchange.Redis;
  7. using Microsoft.Extensions.Configuration;
  8. using System.Collections.Generic;
  9. using Catalogs.Domain.Catalogs;
  10. using Catalogs.Domain.Dtos;
  11. using System.Net.Sockets;
  12.  
  13. namespace WebScoket.Server.Services
  14. {
  15. /// <summary>
  16. /// 实时推送产品主要是最新的库存,其他信息也会更新
  17. /// </summary>
  18. public class ProductListHandler : WebSocketHandler
  19. {
  20. private System.Threading.Timer _timer;
  21. private readonly IDatabase _redisDb;
  22. //展示列表推送
  23. private string productIdsStr;
  24. public ProductListHandler(WebSocketConnectionManager webSocketConnectionManager,IConfiguration configuration) : base(webSocketConnectionManager)
  25. {
  26. ConnectionMultiplexer redis = ConnectionMultiplexer.Connect(configuration["DistributedRedis:ConnectionString"] ?? throw new Exception("$未能获取distributedredis连接字符串"));
  27. _redisDb = redis.GetDatabase();
  28. _timer = new System.Threading.Timer(Send, null, TimeSpan.Zero, TimeSpan.FromSeconds(1));
  29. }
  30. private void Send(object state)
  31. {
  32. // 获取当前时间并发送给所有连接的客户端
  33. if (productIdsStr != null)
  34. {
  35. string[] productIds = System.Text.Json.JsonSerializer.Deserialize<string[]>(productIdsStr);
  36. string hashKeyToRetrieve = "products";
  37. List<ProductDto> products = new List<ProductDto>();
  38.  
  39. foreach (var productId in productIds)
  40. {
  41. if(productId == "null") {
  42. continue;
  43. }
  44. string retrievedProductValue = _redisDb.HashGet(hashKeyToRetrieve, productId);
  45. if (!string.IsNullOrEmpty(retrievedProductValue))
  46. {
  47. //反序列化和构造函数冲突,改造了一下Catalog
  48. Catalog catalog = System.Text.Json.JsonSerializer.Deserialize<Catalog>(retrievedProductValue);
  49. products.Add(new ProductDto(catalog.Id.ToString(), catalog.Name, catalog.Price.ToString(), catalog.Stock.ToString(), catalog.ImgPath));
  50. }
  51. }
  52. if (products.Count > 0)
  53. {
  54. SendMessageToAllAsync(System.Text.Json.JsonSerializer.Serialize(products)).Wait();
  55. }
  56. else
  57. {
  58. SendMessageToAllAsync("NoProduct").Wait();
  59. }
  60. }
  61. }
  62. public override async Task ReceiveAsync(WebSocket socket, WebSocketReceiveResult result, byte[] buffer)
  63. {
  64. //每次页面有刷新就会拿到展示的id列表
  65. productIdsStr = System.Text.Encoding.UTF8.GetString(buffer, 0, result.Count);
  66. }
  67. }
  68. }

4.html页面就可以拿到最新数据再去绑定到页面

  1. socket.addEventListener('message', (event) => {
  2. if (event.data == "NoProduct") {
  3. clearProductList();
  4. }
  5. // Handle the received product data and update the product list
  6. const productData = JSON.parse(event.data);
  7. // Update the product list with the received data (call your displayProducts function)
  8. displayProducts(baseUrl, productData);
  9. });

整个流程就这么简单,但是这里需要保持数据库和redis的数据实时同步,否则页面展示的就不是最新的数据就没意义了。

再回到Catalog.Service服务中。

  1. private async Task DeleteCache()
  2. {
  3. //await _redisDb.HashDeleteAsync("products",id); //没必要了
  4. await _channel.Writer.WriteAsync("delete_catalog_fromredis");
  5. }

再做更新、新增、删除等动作的时候就调用一下DeleteCache方法,往后台服务发送一个channel,当后台收到后就做redis删除并且从初始化sqlserver到redis列表同步的操作

  1. using System.Reflection;
  2. using System.Threading.Channels;
  3. using Catalogs.Infrastructure.Database;
  4. using Microsoft.EntityFrameworkCore;
  5. using Microsoft.Extensions.Hosting;
  6. using Microsoft.Extensions.Logging;
  7. using StackExchange.Redis;
  8.  
  9. namespace Catalogs.WebApi.BackgroudServices
  10. {
  11. /// <summary>
  12. /// 记得任何删除了或者购买了产品后需要删除改产品的键
  13. /// </summary>
  14. public class InitProductListToRedisService : BackgroundService
  15. {
  16. private readonly IServiceScopeFactory _serviceScopeFactory;
  17. private readonly IDatabase _redisDb;
  18. private readonly Channel<string> _channel;
  19. private readonly ILogger _logger;
  20. public InitProductListToRedisService(IServiceScopeFactory serviceScopeFactory, IConfiguration configuration, Channel<string> channel, ILogger<InitProductListToRedisService> logger)
  21. {
  22. _serviceScopeFactory = serviceScopeFactory;
  23. ConnectionMultiplexer redis = ConnectionMultiplexer.Connect(configuration["DistributedRedis:ConnectionString"] ?? throw new Exception("$未能获取distributedredis连接字符串"));
  24. _redisDb = redis.GetDatabase();
  25. _channel = channel;
  26. _logger = logger;
  27. }
  28. protected override async Task ExecuteAsync(CancellationToken stoppingToken)
  29. {
  30. await Init();
  31.  
  32. while (!_channel.Reader.Completion.IsCompleted)
  33. {
  34. var msg = await _channel.Reader.ReadAsync();
  35. if(msg == "delete_catalog_fromredis")
  36. {
  37. await Init();
  38. }
  39. }
  40. }
  41.  
  42. private async Task Init()
  43. {
  44. using var scope = _serviceScopeFactory.CreateScope();
  45. try
  46. {
  47. CatalogContext _context = scope.ServiceProvider.GetRequiredService<CatalogContext>();
  48. string hashKey = "products";
  49. var products = await _context.Catalogs.ToListAsync();
  50.  
  51. await _redisDb.KeyDeleteAsync(hashKey);
  52.  
  53. foreach (var product in products)
  54. {
  55.  
  56. string productField = product.Id.ToString();
  57. string productValue = System.Text.Json.JsonSerializer.Serialize(product);
  58.  
  59. _redisDb.HashSet(hashKey, new HashEntry[] { new HashEntry(productField, productValue) });
  60. }
  61.  
  62. _logger.LogInformation($"ProductList is over stored in Redis Hash.");
  63. }
  64. catch(Exception ex)
  65. {
  66. _logger.LogError($"ProductLis stored in Redis Hash error.");
  67. }
  68. }
  69. }
  70. }

这里还有优化的空间可以只针对怕products的hashset的某个id去更新、删除、新增一条数据。

示例代码:

liuzhixin405/efcore-template (github.com)

aspnetcore使用websocket实时更新商品信息的更多相关文章

  1. WebSocket 实时更新mysql数据到页面

    使用websocket的初衷是,要实时更新mysql中的报警信息到web页面显示 没怎么碰过web,代码写的是真烂,不过也算是功能实现了,放在这里也是鞭策自己,web也要多下些功夫 准备 引入依赖 & ...

  2. HTML5 WebSocket 实时推送信息测试demo

    测试一下HTML5的websocket功能,实现了客户端→服务器实时推送信息到客户端,包括推送图片: websocket实现MessageInbound类 onTextMessage()/onBina ...

  3. C#Winform实时更新数据库信息Demo(使用Scoket)

    最近在贴吧上看到有个提问就是关于怎么在Winform上实时的更新数据 提问者提到的是利用Timer去轮询,但最后经过网上查了下资料,感觉Socket也是可行的, 于是就写了这个Demo 这个Demo的 ...

  4. 第十二章——SQLServer统计信息(1)——创建和更新统计信息

    原文:第十二章--SQLServer统计信息(1)--创建和更新统计信息 简介: 查询的统计信息: 目前为止,已经介绍了选择索引.维护索引.如果有合适的索引并实时更新统计信息,那么优化器会选择有用的索 ...

  5. web页面实时更新页面的原理--WebSocket

    原文:https://www.jianshu.com/p/8f956cd4d42b angular-cli启动的项目也可以自动刷新,底下应该也是应用的websocket的原理. ----------- ...

  6. 使用Node.js+Socket.IO搭建WebSocket实时应用

    Web领域的实时推送技术,也被称作Realtime技术.这种技术要达到的目的是让用户不需要刷新浏览器就可以获得实时更新.它有着广泛的应用场景,比如在线聊天室.在线客服系统.评论系统.WebIM等. W ...

  7. (转)使用Node.js+Socket.IO搭建WebSocket实时应用

    Web领域的实时推送技术,也被称作Realtime技术.这种技术要达到的目的是让用户不需要刷新浏览器就可以获得实时更新.它有着广泛的应用场景,比如在线聊天室.在线客服系统.评论系统.WebIM等. W ...

  8. Web网站数据”实时”更新设计

    请注意这个实时打上了双引号,没有绝对的实时,只是时间的颗粒不一样罢了(1ms,1s,1m). 服务器数据有更新可以快速通知客户端.Web 基于取得模式,而服务器建立大量的和客户端连接来提供数据实时更新 ...

  9. 使用Node.js+Socket.IO搭建WebSocket实时应用【转载】

    原文:http://www.jianshu.com/p/d9b1273a93fd Web领域的实时推送技术,也被称作Realtime技术.这种技术要达到的目的是让用户不需要刷新浏览器就可以获得实时更新 ...

  10. 【用户交互】APP没有退出前台但改变系统属性如何实时更新UI?监听系统广播,让用户交互更舒心~

    前日,一小伙伴问我一个问题,说它解决了半天都没解决这个问题,截图如下: 大概楼主理解如下: 如果在应用中有一个判断wifi的开关和一个当前音量大小的seekbar以及一个获取当前电量多少的按钮,想知道 ...

随机推荐

  1. 使用js开发一个快速打开前端项目的alfred插件

    使用js开发一个快速打开前端项目的插件 目录 前言 使用的技术栈 步骤 问题发现 待优化 前言 一直以来开发都是先打开vscode,然后选择项目,在项目多的情况下会觉得挺繁琐:如果同时打开了许多vsc ...

  2. Markdown 包含其他文件静态渲染工具

    1. 前言 在 GitHub 上写文档,很多时候要插入 uml,像 mermaid 这种可以直接在 GitHub/GitLab 中渲染的一般直接写个 code block 进去,但是这样造成一个问题就 ...

  3. CSS 多行文本超链接下划线动效

    先看效果 乍一看,是不是感觉很简单,仔细一瞅发现事情好像没有那么简单. 如果十分钟还没想出怎么实现,那就把简历上的"精通css"改成"了解css"-- 大部分人 ...

  4. Python:利用math和random模块实现RSA加密算法

    实验五报告: 利用math和random模块实现RSA加密算法 实验目标 本实验的主要目标是熟悉RSA(Rivest-Shamir-Adleman)密码算法的编写,其中包括求最大公因子.模逆的扩展欧几 ...

  5. 比赛总结:Japan Registry Services (JPRS) Programming Contest 2023 (AtCoder Beginner Contest 324)

    比赛:Japan Registry Services (JPRS) Programming Contest 2023 (AtCoder Beginner Contest 324) A-same 1.常 ...

  6. 2023 SHCTF-校外赛道 Crypto—Wp

    WEEK1 立正 wl hgrfhg 4gNUx4NgQgEUb4NC64NHxZLg636V6CDBiDNUHw8HkapH :jdoi vl vlkw ~xrb wd nrrT Y: 凯撒解密,偏 ...

  7. SSL证书链及使用

    什么是证书链 证书链简单来说是域名钥证书.CA公钥.根证书形成的一个颁发链条,属于公钥的一部分. 更白话一点,就是证书链文件包含一系列CA机构公钥的证书. 证书链格式 一般证书链格式是.chain,证 ...

  8. [C++]二叉链-二叉树存储

    二叉链存二叉树 预备知识 指针的熟练掌握 Bolg template模板的知识 Bolg 二叉树的基本知识 感谢: 代码参考:CSDN博主「云雨澄枫」的原创文章 链接 代码解析 结构体 BiNode ...

  9. Java初始化顺序及使用Spring情况下初始化

    Java初始化顺序 1  无继承情况下的Java初始化顺序: class Sample {       Sample(String s)       {             System.out. ...

  10. vue3源码学习api-createApp-amount

    vue3 地址 https://github.com/vuejs/core 首先看看vue文档什么是 Vue? ​ Vue (发音为 /vjuː/,类似 view) 是一款用于构建用户界面的 Java ...