Browse Source

增加通知公告服务

zuohuaijun 3 năm trước cách đây
mục cha
commit
c86a95d440
22 tập tin đã thay đổi với 1642 bổ sung4 xóa
  1. 565 2
      Admin.NET/Admin.NET.Core/Admin.NET.Core.xml
  2. 1 0
      Admin.NET/Admin.NET.Core/AdminNETConfig.json
  3. 72 0
      Admin.NET/Admin.NET.Core/Entity/SysNotice.cs
  4. 32 0
      Admin.NET/Admin.NET.Core/Entity/SysNoticeUser.cs
  5. 31 0
      Admin.NET/Admin.NET.Core/Enum/NoticeStatusEnum.cs
  6. 19 0
      Admin.NET/Admin.NET.Core/Enum/NoticeTypeEnum.cs
  7. 19 0
      Admin.NET/Admin.NET.Core/Enum/NoticeUserStatusEnum.cs
  8. 221 0
      Admin.NET/Admin.NET.Core/Extension/EnumExtension.cs
  9. 1 1
      Admin.NET/Admin.NET.Core/Extension/ObjectExtension.cs
  10. 3 0
      Admin.NET/Admin.NET.Core/Extension/RepositoryExtension.cs
  11. 8 1
      Admin.NET/Admin.NET.Core/Hub/IChatClient.cs
  12. 57 0
      Admin.NET/Admin.NET.Core/Service/Notice/Dto/NoticeBase.cs
  13. 40 0
      Admin.NET/Admin.NET.Core/Service/Notice/Dto/NoticeDetailOutput.cs
  14. 96 0
      Admin.NET/Admin.NET.Core/Service/Notice/Dto/NoticeInput.cs
  15. 22 0
      Admin.NET/Admin.NET.Core/Service/Notice/Dto/NoticeReceiveOutput.cs
  16. 18 0
      Admin.NET/Admin.NET.Core/Service/Notice/ISysNoticeService.cs
  17. 12 0
      Admin.NET/Admin.NET.Core/Service/Notice/ISysNoticeUserService.cs
  18. 257 0
      Admin.NET/Admin.NET.Core/Service/Notice/SysNoticeService.cs
  19. 77 0
      Admin.NET/Admin.NET.Core/Service/Notice/SysNoticeUserService.cs
  20. 10 0
      Admin.NET/Admin.NET.Core/Service/OnlineUser/ISysOnlineUserService.cs
  21. 59 0
      Admin.NET/Admin.NET.Core/Service/OnlineUser/SysOnlineUserService.cs
  22. 22 0
      Admin.NET/Admin.NET.Core/Util/ReflectionUtil.cs

+ 565 - 2
Admin.NET/Admin.NET.Core/Admin.NET.Core.xml

@@ -910,6 +910,86 @@
             菜单子项
             </summary>
         </member>
+        <member name="T:Admin.NET.Core.SysNotice">
+            <summary>
+            系统通知公告表
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.SysNotice.Title">
+            <summary>
+            标题
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.SysNotice.Content">
+            <summary>
+            内容
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.SysNotice.Type">
+            <summary>
+            类型(字典 1通知 2公告)
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.SysNotice.PublicUserId">
+            <summary>
+            发布人Id
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.SysNotice.PublicUserName">
+            <summary>
+            发布人姓名
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.SysNotice.PublicOrgId">
+            <summary>
+            发布机构Id
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.SysNotice.PublicOrgName">
+            <summary>
+            发布机构名称
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.SysNotice.PublicTime">
+            <summary>
+            发布时间
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.SysNotice.CancelTime">
+            <summary>
+            撤回时间
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.SysNotice.Status">
+            <summary>
+            状态(字典 0草稿 1发布 2撤回 3删除)
+            </summary>
+        </member>
+        <member name="T:Admin.NET.Core.SysNoticeUser">
+            <summary>
+            系统通知公告用户表
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.SysNoticeUser.NoticeId">
+            <summary>
+            通知公告Id
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.SysNoticeUser.UserId">
+            <summary>
+            用户Id
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.SysNoticeUser.ReadTime">
+            <summary>
+            阅读时间
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.SysNoticeUser.ReadStatus">
+            <summary>
+            状态(字典 0未读 1已读)
+            </summary>
+        </member>
         <member name="T:Admin.NET.Core.SysOnlineUser">
             <summary>
             系统在线用户表
@@ -2261,6 +2341,61 @@
             错误提示
             </summary>
         </member>
+        <member name="T:Admin.NET.Core.NoticeStatusEnum">
+            <summary>
+            通知公告状态枚举
+            </summary>
+        </member>
+        <member name="F:Admin.NET.Core.NoticeStatusEnum.DRAFT">
+            <summary>
+            草稿
+            </summary>
+        </member>
+        <member name="F:Admin.NET.Core.NoticeStatusEnum.PUBLIC">
+            <summary>
+            发布
+            </summary>
+        </member>
+        <member name="F:Admin.NET.Core.NoticeStatusEnum.CANCEL">
+            <summary>
+            撤回
+            </summary>
+        </member>
+        <member name="F:Admin.NET.Core.NoticeStatusEnum.DELETED">
+            <summary>
+            删除
+            </summary>
+        </member>
+        <member name="T:Admin.NET.Core.NoticeTypeEnum">
+            <summary>
+            通知公告状类型枚举
+            </summary>
+        </member>
+        <member name="F:Admin.NET.Core.NoticeTypeEnum.NOTICE">
+            <summary>
+            通知
+            </summary>
+        </member>
+        <member name="F:Admin.NET.Core.NoticeTypeEnum.ANNOUNCEMENT">
+            <summary>
+            公告
+            </summary>
+        </member>
+        <member name="T:Admin.NET.Core.NoticeUserStatusEnum">
+            <summary>
+            通知公告用户状态枚举
+            </summary>
+        </member>
+        <member name="F:Admin.NET.Core.NoticeUserStatusEnum.UNREAD">
+            <summary>
+            未读
+            </summary>
+        </member>
+        <member name="F:Admin.NET.Core.NoticeUserStatusEnum.READ">
+            <summary>
+            已读
+            </summary>
+        </member>
         <member name="T:Admin.NET.Core.OrgTypeEnum">
             <summary>
             机构类型枚举
@@ -2509,6 +2644,105 @@
             <param name="context"></param>
             <returns></returns>
         </member>
+        <member name="T:Admin.NET.Core.EnumExtension">
+            <summary>
+            枚举拓展
+            </summary>
+        </member>
+        <member name="M:Admin.NET.Core.EnumExtension.GetEnumDictionary(System.Type)">
+            <summary>
+            获取枚举对象Key与名称的字典(缓存)
+            </summary>
+            <param name="enumType"></param>
+            <returns></returns>
+        </member>
+        <member name="M:Admin.NET.Core.EnumExtension.GetEnumDictionaryItems(System.Type)">
+            <summary>
+            获取枚举对象Key与名称的字典
+            </summary>
+            <param name="enumType"></param>
+            <returns></returns>
+        </member>
+        <member name="M:Admin.NET.Core.EnumExtension.GetEnumDescDictionary(System.Type)">
+            <summary>
+            获取枚举类型key与描述的字典(缓存)
+            </summary>
+            <param name="enumType"></param>
+            <returns></returns>
+            <exception cref="T:System.Exception"></exception>
+        </member>
+        <member name="M:Admin.NET.Core.EnumExtension.GetEnumDescDictionaryItems(System.Type)">
+            <summary>
+            获取枚举类型key与描述的字典(没有描述则获取name)
+            </summary>
+            <param name="enumType"></param>
+            <returns></returns>
+            <exception cref="T:System.Exception"></exception>
+        </member>
+        <member name="M:Admin.NET.Core.EnumExtension.TryToGetEnumType(System.Reflection.Assembly,System.String)">
+            <summary>
+            从程序集中查找指定枚举类型
+            </summary>
+            <param name="assembly"></param>
+            <param name="typeName"></param>
+            <returns></returns>
+        </member>
+        <member name="M:Admin.NET.Core.EnumExtension.LoadEnumTypeDict(System.Reflection.Assembly)">
+            <summary>
+            从程序集中加载所有枚举类型
+            </summary>
+            <param name="assembly"></param>
+            <returns></returns>
+        </member>
+        <member name="M:Admin.NET.Core.EnumExtension.GetDescription(System.Enum)">
+            <summary>
+            获取枚举的Description
+            </summary>
+            <param name="value"></param>
+            <returns></returns>
+        </member>
+        <member name="M:Admin.NET.Core.EnumExtension.GetDescription(System.Object)">
+            <summary>
+            获取枚举的Description
+            </summary>
+            <param name="value"></param>
+            <returns></returns>
+        </member>
+        <member name="M:Admin.NET.Core.EnumExtension.EnumToList(System.Type)">
+            <summary>
+            将枚举转成枚举信息集合
+            </summary>
+            <param name="type"></param>
+            <returns></returns>
+        </member>
+        <member name="M:Admin.NET.Core.EnumExtension.EnumToList``1(System.Type)">
+            <summary>
+            枚举ToList
+            </summary>
+            <typeparam name="T"></typeparam>
+            <param name="type"></param>
+            <returns></returns>
+        </member>
+        <member name="T:Admin.NET.Core.EnumEntity">
+            <summary>
+            枚举实体
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.EnumEntity.Describe">
+            <summary>
+            枚举的描述
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.EnumEntity.Name">
+            <summary>
+            枚举名称
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.EnumEntity.Value">
+            <summary>
+            枚举对象的值
+            </summary>
+        </member>
         <member name="T:Admin.NET.Core.LogoExtension">
             <summary>
             logo显示
@@ -2516,7 +2750,7 @@
         </member>
         <member name="T:Admin.NET.Core.ObjectExtension">
             <summary>
-            对象拓展
+            对象拓展
             </summary>
         </member>
         <member name="M:Admin.NET.Core.ObjectExtension.HasImplementedRawGeneric(System.Type,System.Type)">
@@ -2572,6 +2806,11 @@
             <param name="pi"></param>
             <returns></returns>
         </member>
+        <member name="T:Admin.NET.Core.RepositoryExtension">
+            <summary>
+            仓储拓展
+            </summary>
+        </member>
         <member name="M:Admin.NET.Core.RepositoryExtension.FakeDelete``1(SqlSugar.ISugarRepository,``0)">
             <summary>
             实体假删除 _rep.FakeDelete(entity)
@@ -2798,7 +3037,7 @@
             聊天客户端接口定义
             </summary>
         </member>
-        <member name="M:Admin.NET.Core.IChatClient.ForceExist">
+        <member name="M:Admin.NET.Core.IChatClient.ForceExist(System.String)">
             <summary>
             强制下线
             </summary>
@@ -2811,6 +3050,13 @@
             <param name="context"></param>
             <returns></returns>
         </member>
+        <member name="M:Admin.NET.Core.IChatClient.AppendNotice(Admin.NET.Core.SysNotice)">
+            <summary>
+            组合信息
+            </summary>
+            <param name="notice"></param>
+            <returns></returns>
+        </member>
         <member name="T:Admin.NET.Core.JobTimer">
             <summary>
             任务调度
@@ -5227,6 +5473,310 @@
             </summary>
             <returns></returns>
         </member>
+        <member name="T:Admin.NET.Core.Service.NoticeBase">
+            <summary>
+            通知公告参数
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.Service.NoticeBase.Title">
+            <summary>
+            标题
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.Service.NoticeBase.Content">
+            <summary>
+            内容
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.Service.NoticeBase.Type">
+            <summary>
+            类型(字典 1通知 2公告)
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.Service.NoticeBase.PublicUserId">
+            <summary>
+            发布人Id
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.Service.NoticeBase.PublicUserName">
+            <summary>
+            发布人姓名
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.Service.NoticeBase.PublicOrgId">
+            <summary>
+            发布机构Id
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.Service.NoticeBase.PublicOrgName">
+            <summary>
+            发布机构名称
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.Service.NoticeBase.PublicTime">
+            <summary>
+            发布时间
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.Service.NoticeBase.CancelTime">
+            <summary>
+            撤回时间
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.Service.NoticeBase.Status">
+            <summary>
+            状态(字典 0草稿 1发布 2撤回 3删除)
+            </summary>
+        </member>
+        <member name="T:Admin.NET.Core.Service.NoticeDetailOutput">
+            <summary>
+            系统通知公告详情参数
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.Service.NoticeDetailOutput.NoticeUserIdList">
+            <summary>
+            通知到的用户Id集合
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.Service.NoticeDetailOutput.NoticeUserReadInfoList">
+            <summary>
+            通知到的用户阅读信息集合
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.Service.NoticeUserRead.UserId">
+            <summary>
+            用户Id
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.Service.NoticeUserRead.UserName">
+            <summary>
+            用户名称
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.Service.NoticeUserRead.ReadStatus">
+            <summary>
+            状态(字典 0未读 1已读)
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.Service.NoticeUserRead.ReadTime">
+            <summary>
+            阅读时间
+            </summary>
+        </member>
+        <member name="T:Admin.NET.Core.Service.NoticeInput">
+            <summary>
+            通知公告参数
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.Service.NoticeInput.Title">
+            <summary>
+            标题
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.Service.NoticeInput.Content">
+            <summary>
+            内容
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.Service.NoticeInput.Type">
+            <summary>
+            类型(字典 1通知 2公告)
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.Service.NoticeInput.Status">
+            <summary>
+            状态(字典 0草稿 1发布 2撤回 3删除)
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.Service.NoticeInput.NoticeUserIdList">
+            <summary>
+            通知到的人
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.Service.AddNoticeInput.Title">
+            <summary>
+            标题
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.Service.AddNoticeInput.Content">
+            <summary>
+            内容
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.Service.AddNoticeInput.Type">
+            <summary>
+            类型(字典 1通知 2公告)
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.Service.AddNoticeInput.Status">
+            <summary>
+            状态(字典 0草稿 1发布 2撤回 3删除)
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.Service.AddNoticeInput.NoticeUserIdList">
+            <summary>
+            通知到的人
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.Service.DeleteNoticeInput.Id">
+            <summary>
+            Id
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.Service.UpdateNoticeInput.Id">
+            <summary>
+            Id
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.Service.ChangeStatusNoticeInput.Status">
+            <summary>
+            状态(字典 0草稿 1发布 2撤回 3删除)
+            </summary>
+        </member>
+        <member name="T:Admin.NET.Core.Service.NoticeReceiveOutput">
+            <summary>
+            通知公告接收参数
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.Service.NoticeReceiveOutput.Id">
+            <summary>
+            Id
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.Service.NoticeReceiveOutput.ReadStatus">
+            <summary>
+            阅读状态(字典 0未读 1已读)
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.Service.NoticeReceiveOutput.ReadTime">
+            <summary>
+            阅读时间
+            </summary>
+        </member>
+        <member name="T:Admin.NET.Core.Service.SysNoticeService">
+            <summary>
+            系统通知公告服务
+            </summary>
+        </member>
+        <member name="M:Admin.NET.Core.Service.SysNoticeService.QueryNoticePageList(Admin.NET.Core.Service.NoticeInput)">
+            <summary>
+            分页查询通知公告
+            </summary>
+            <param name="input"></param>
+            <returns></returns>
+        </member>
+        <member name="M:Admin.NET.Core.Service.SysNoticeService.AddNotice(Admin.NET.Core.Service.AddNoticeInput)">
+            <summary>
+            增加通知公告
+            </summary>
+            <param name="input"></param>
+            <returns></returns>
+        </member>
+        <member name="M:Admin.NET.Core.Service.SysNoticeService.DeleteNotice(Admin.NET.Core.Service.DeleteNoticeInput)">
+            <summary>
+            删除通知公告
+            </summary>
+            <param name="input"></param>
+            <returns></returns>
+        </member>
+        <member name="M:Admin.NET.Core.Service.SysNoticeService.UpdateNotice(Admin.NET.Core.Service.UpdateNoticeInput)">
+            <summary>
+            更新通知公告
+            </summary>
+            <param name="input"></param>
+            <returns></returns>
+        </member>
+        <member name="M:Admin.NET.Core.Service.SysNoticeService.GetNotice(Admin.NET.Core.Service.QueryNoticeInput)">
+            <summary>
+            获取通知公告详情
+            </summary>
+            <param name="input"></param>
+            <returns></returns>
+        </member>
+        <member name="M:Admin.NET.Core.Service.SysNoticeService.ChangeStatus(Admin.NET.Core.Service.ChangeStatusNoticeInput)">
+            <summary>
+            修改通知公告状态
+            </summary>
+            <param name="input"></param>
+            <returns></returns>
+        </member>
+        <member name="M:Admin.NET.Core.Service.SysNoticeService.ReceivedNoticePageList(Admin.NET.Core.Service.NoticeInput)">
+            <summary>
+            获取接收的通知公告
+            </summary>
+            <param name="input"></param>
+            <returns></returns>
+        </member>
+        <member name="M:Admin.NET.Core.Service.SysNoticeService.UnReadNoticeList(Admin.NET.Core.Service.NoticeInput)">
+            <summary>
+            未处理消息
+            </summary>
+            <param name="input"></param>
+            <returns></returns>
+        </member>
+        <member name="M:Admin.NET.Core.Service.SysNoticeService.UpdatePublicInfo(Admin.NET.Core.SysNotice)">
+            <summary>
+            更新发布信息
+            </summary>
+            <param name="notice"></param>
+        </member>
+        <member name="T:Admin.NET.Core.Service.SysNoticeUserService">
+            <summary>
+            系统通知公告用户
+            </summary>
+        </member>
+        <member name="M:Admin.NET.Core.Service.SysNoticeUserService.Add(System.Int64,System.Collections.Generic.List{System.Int64},Admin.NET.Core.NoticeUserStatusEnum)">
+            <summary>
+            增加
+            </summary>
+            <param name="noticeId"></param>
+            <param name="noticeUserIdList"></param>
+            <param name="noticeUserStatus"></param>
+            <returns></returns>
+        </member>
+        <member name="M:Admin.NET.Core.Service.SysNoticeUserService.Update(System.Int64,System.Collections.Generic.List{System.Int64},Admin.NET.Core.NoticeUserStatusEnum)">
+            <summary>
+            更新
+            </summary>
+            <param name="noticeId"></param>
+            <param name="noticeUserIdList"></param>
+            <param name="noticeUserStatus"></param>
+            <returns></returns>
+        </member>
+        <member name="M:Admin.NET.Core.Service.SysNoticeUserService.GetNoticeUserListByNoticeId(System.Int64)">
+            <summary>
+            获取通知公告用户列表
+            </summary>
+            <param name="noticeId"></param>
+            <returns></returns>
+        </member>
+        <member name="M:Admin.NET.Core.Service.SysNoticeUserService.Read(System.Int64,System.Int64,Admin.NET.Core.NoticeUserStatusEnum)">
+            <summary>
+            设置通知公告读取状态
+            </summary>
+            <param name="noticeId"></param>
+            <param name="userId"></param>
+            <param name="status"></param>
+            <returns></returns>
+        </member>
+        <member name="T:Admin.NET.Core.Service.SysOnlineUserService">
+            <summary>
+            系统在线用户服务
+            </summary>
+        </member>
+        <member name="M:Admin.NET.Core.Service.SysOnlineUserService.List(Admin.NET.Core.BasePageInput)">
+            <summary>
+            获取在线用户信息
+            </summary>
+            <returns></returns>
+        </member>
+        <member name="M:Admin.NET.Core.Service.SysOnlineUserService.ForceExist(Admin.NET.Core.SysOnlineUser)">
+            <summary>
+            强制下线
+            </summary>
+            <param name="user"></param>
+            <returns></returns>
+        </member>
         <member name="P:Admin.NET.Core.Service.OrgInput.Pid">
             <summary>
             父Id
@@ -6903,6 +7453,19 @@
             <param name="path"></param>
             <returns></returns>
         </member>
+        <member name="T:Admin.NET.Core.ReflectionUtil">
+            <summary>
+            反射工具类
+            </summary>
+        </member>
+        <member name="M:Admin.NET.Core.ReflectionUtil.GetDescriptionValue``1(System.Reflection.FieldInfo)">
+            <summary>
+            获取字段特性
+            </summary>
+            <param name="field"></param>
+            <typeparam name="T"></typeparam>
+            <returns></returns>
+        </member>
         <member name="T:Admin.NET.Core.ServerUtil">
             <summary>
             服务器信息

+ 1 - 0
Admin.NET/Admin.NET.Core/AdminNETConfig.json

@@ -69,6 +69,7 @@
     "ExpiredTime": 20160 // 过期时间单位分钟(一般 refresh_token 的有效时间 > 2 * access_token 的有效时间)
   },
   "CorsAccessorSettings": {
+    "WithExposedHeaders": [ "Content-Disposition" ], // 如果前端不代理且是axios请求
     "SignalRSupport": true // 启用 SignalR 跨域支持
   },
   "Cache": {

+ 72 - 0
Admin.NET/Admin.NET.Core/Entity/SysNotice.cs

@@ -0,0 +1,72 @@
+namespace Admin.NET.Core;
+
+/// <summary>
+/// 系统通知公告表
+/// </summary>
+[SugarTable("sys_notice", "通知公告表")]
+public class SysNotice : EntityBase
+{
+    /// <summary>
+    /// 标题
+    /// </summary>
+    [SugarColumn(ColumnDescription = "标题", Length = 32)]
+    [Required, MaxLength(32)]
+    public string Title { get; set; }
+
+    /// <summary>
+    /// 内容
+    /// </summary>
+    [SugarColumn(ColumnDescription = "内容", ColumnDataType = "longtext,text,clob")]
+    [Required]
+    public string Content { get; set; }
+
+    /// <summary>
+    /// 类型(字典 1通知 2公告)
+    /// </summary>
+    [SugarColumn(ColumnDescription = "类型(字典 1通知 2公告)")]
+    public NoticeTypeEnum Type { get; set; }
+
+    /// <summary>
+    /// 发布人Id
+    /// </summary>
+    [SugarColumn(ColumnDescription = "发布人Id")]
+    public long PublicUserId { get; set; }
+
+    /// <summary>
+    /// 发布人姓名
+    /// </summary>
+    [SugarColumn(ColumnDescription = "发布人姓名", Length = 32)]
+    [MaxLength(32)]
+    public string PublicUserName { get; set; }
+
+    /// <summary>
+    /// 发布机构Id
+    /// </summary>
+    [SugarColumn(ColumnDescription = "发布机构Id")]
+    public long PublicOrgId { get; set; }
+
+    /// <summary>
+    /// 发布机构名称
+    /// </summary>
+    [SugarColumn(ColumnDescription = "发布机构名称", Length = 64)]
+    [MaxLength(64)]
+    public string PublicOrgName { get; set; }
+
+    /// <summary>
+    /// 发布时间
+    /// </summary>
+    [SugarColumn(ColumnDescription = "发布时间")]
+    public DateTime PublicTime { get; set; }
+
+    /// <summary>
+    /// 撤回时间
+    /// </summary>
+    [SugarColumn(ColumnDescription = "撤回时间")]
+    public DateTime CancelTime { get; set; }
+
+    /// <summary>
+    /// 状态(字典 0草稿 1发布 2撤回 3删除)
+    /// </summary>
+    [SugarColumn(ColumnDescription = "状态(字典 0草稿 1发布 2撤回 3删除)")]
+    public NoticeStatusEnum Status { get; set; }
+}

+ 32 - 0
Admin.NET/Admin.NET.Core/Entity/SysNoticeUser.cs

@@ -0,0 +1,32 @@
+namespace Admin.NET.Core;
+
+/// <summary>
+/// 系统通知公告用户表
+/// </summary>
+[SugarTable("sys_notice_user", "通知公告用户表")]
+public class SysNoticeUser
+{
+    /// <summary>
+    /// 通知公告Id
+    /// </summary>
+    [SugarColumn(ColumnDescription = "通知公告Id")]
+    public long NoticeId { get; set; }
+
+    /// <summary>
+    /// 用户Id
+    /// </summary>
+    [SugarColumn(ColumnDescription = "用户Id")]
+    public long UserId { get; set; }
+
+    /// <summary>
+    /// 阅读时间
+    /// </summary>
+    [SugarColumn(ColumnDescription = "阅读时间")]
+    public DateTime ReadTime { get; set; }
+
+    /// <summary>
+    /// 状态(字典 0未读 1已读)
+    /// </summary>
+    [SugarColumn(ColumnDescription = "状态(字典 0未读 1已读)")]
+    public NoticeUserStatusEnum ReadStatus { get; set; }
+}

+ 31 - 0
Admin.NET/Admin.NET.Core/Enum/NoticeStatusEnum.cs

@@ -0,0 +1,31 @@
+namespace Admin.NET.Core;
+
+/// <summary>
+/// 通知公告状态枚举
+/// </summary>
+public enum NoticeStatusEnum
+{
+    /// <summary>
+    /// 草稿
+    /// </summary>
+    [Description("草稿")]
+    DRAFT = 0,
+
+    /// <summary>
+    /// 发布
+    /// </summary>
+    [Description("发布")]
+    PUBLIC = 1,
+
+    /// <summary>
+    /// 撤回
+    /// </summary>
+    [Description("撤回")]
+    CANCEL = 2,
+
+    /// <summary>
+    /// 删除
+    /// </summary>
+    [Description("删除")]
+    DELETED = 3
+}

+ 19 - 0
Admin.NET/Admin.NET.Core/Enum/NoticeTypeEnum.cs

@@ -0,0 +1,19 @@
+namespace Admin.NET.Core;
+
+/// <summary>
+/// 通知公告状类型枚举
+/// </summary>
+public enum NoticeTypeEnum
+{
+    /// <summary>
+    /// 通知
+    /// </summary>
+    [Description("通知")]
+    NOTICE = 1,
+
+    /// <summary>
+    /// 公告
+    /// </summary>
+    [Description("公告")]
+    ANNOUNCEMENT = 2,
+}

+ 19 - 0
Admin.NET/Admin.NET.Core/Enum/NoticeUserStatusEnum.cs

@@ -0,0 +1,19 @@
+namespace Admin.NET.Core;
+
+/// <summary>
+/// 通知公告用户状态枚举
+/// </summary>
+public enum NoticeUserStatusEnum
+{
+    /// <summary>
+    /// 未读
+    /// </summary>
+    [Description("未读")]
+    UNREAD = 0,
+
+    /// <summary>
+    /// 已读
+    /// </summary>
+    [Description("已读")]
+    READ = 1
+}

+ 221 - 0
Admin.NET/Admin.NET.Core/Extension/EnumExtension.cs

@@ -0,0 +1,221 @@
+using System.Collections.Concurrent;
+
+namespace Admin.NET.Core;
+
+/// <summary>
+/// 枚举拓展
+/// </summary>
+public static class EnumExtension
+{
+    // 枚举显示字典缓存
+    private static readonly ConcurrentDictionary<Type, Dictionary<int, string>> EnumDisplayValueDict = new();
+
+    // 枚举值字典缓存
+    private static readonly ConcurrentDictionary<Type, Dictionary<int, string>> EnumNameValueDict = new();
+
+    // 枚举类型缓存
+    private static ConcurrentDictionary<string, Type> _enumTypeDict;
+
+    /// <summary>
+    /// 获取枚举对象Key与名称的字典(缓存)
+    /// </summary>
+    /// <param name="enumType"></param>
+    /// <returns></returns>
+    public static Dictionary<int, string> GetEnumDictionary(this Type enumType)
+    {
+        if (!enumType.IsEnum)
+            throw new ArgumentException("Type '" + enumType.Name + "' is not an enum.");
+
+        // 查询缓存
+        var enumDic = EnumNameValueDict.ContainsKey(enumType) ? EnumNameValueDict[enumType] : new Dictionary<int, string>();
+        if (enumDic.Count != 0)
+            return enumDic;
+        // 取枚举类型的Key/Value字典集合
+        enumDic = GetEnumDictionaryItems(enumType);
+
+        // 缓存
+        EnumNameValueDict[enumType] = enumDic;
+
+        return enumDic;
+    }
+
+    /// <summary>
+    /// 获取枚举对象Key与名称的字典
+    /// </summary>
+    /// <param name="enumType"></param>
+    /// <returns></returns>
+    private static Dictionary<int, string> GetEnumDictionaryItems(this Type enumType)
+    {
+        // 获取类型的字段,初始化一个有限长度的字典
+        var enumFields = enumType.GetFields(BindingFlags.Public | BindingFlags.Static);
+        Dictionary<int, string> enumDic = new(enumFields.Length);
+
+        // 遍历字段数组获取key和name
+        foreach (var enumField in enumFields)
+        {
+            var intValue = (int)enumField.GetValue(enumType);
+            enumDic[intValue] = enumField.Name;
+        }
+
+        return enumDic;
+    }
+
+    /// <summary>
+    /// 获取枚举类型key与描述的字典(缓存)
+    /// </summary>
+    /// <param name="enumType"></param>
+    /// <returns></returns>
+    /// <exception cref="Exception"></exception>
+    public static Dictionary<int, string> GetEnumDescDictionary(this Type enumType)
+    {
+        if (!enumType.IsEnum)
+            throw new ArgumentException("Type '" + enumType.Name + "' is not an enum.");
+
+        // 查询缓存
+        var enumDic = EnumDisplayValueDict.ContainsKey(enumType)
+            ? EnumDisplayValueDict[enumType]
+            : new Dictionary<int, string>();
+        if (enumDic.Count != 0)
+            return enumDic;
+        // 取枚举类型的Key/Value字典集合
+        enumDic = GetEnumDescDictionaryItems(enumType);
+
+        // 缓存
+        EnumDisplayValueDict[enumType] = enumDic;
+
+        return enumDic;
+    }
+
+    /// <summary>
+    /// 获取枚举类型key与描述的字典(没有描述则获取name)
+    /// </summary>
+    /// <param name="enumType"></param>
+    /// <returns></returns>
+    /// <exception cref="Exception"></exception>
+    private static Dictionary<int, string> GetEnumDescDictionaryItems(this Type enumType)
+    {
+        // 获取类型的字段,初始化一个有限长度的字典
+        var enumFields = enumType.GetFields(BindingFlags.Public | BindingFlags.Static);
+        Dictionary<int, string> enumDic = new(enumFields.Length);
+
+        // 遍历字段数组获取key和name
+        foreach (var enumField in enumFields)
+        {
+            var intValue = (int)enumField.GetValue(enumType);
+            var desc = enumField.GetDescriptionValue<DescriptionAttribute>();
+            enumDic[intValue] = desc != null && !string.IsNullOrEmpty(desc.Description) ? desc.Description : enumField.Name;
+        }
+
+        return enumDic;
+    }
+
+    /// <summary>
+    /// 从程序集中查找指定枚举类型
+    /// </summary>
+    /// <param name="assembly"></param>
+    /// <param name="typeName"></param>
+    /// <returns></returns>
+    public static Type TryToGetEnumType(Assembly assembly, string typeName)
+    {
+        // 枚举缓存为空则重新加载枚举类型字典
+        _enumTypeDict ??= LoadEnumTypeDict(assembly);
+
+        // 按名称查找
+        return _enumTypeDict.ContainsKey(typeName) ? _enumTypeDict[typeName] : null;
+    }
+
+    /// <summary>
+    /// 从程序集中加载所有枚举类型
+    /// </summary>
+    /// <param name="assembly"></param>
+    /// <returns></returns>
+    private static ConcurrentDictionary<string, Type> LoadEnumTypeDict(Assembly assembly)
+    {
+        // 取程序集中所有类型
+        var typeArray = assembly.GetTypes();
+
+        // 过滤非枚举类型,转成字典格式并返回
+        var dict = typeArray.Where(o => o.IsEnum).ToDictionary(o => o.Name, o => o);
+        ConcurrentDictionary<string, Type> enumTypeDict = new(dict);
+        return enumTypeDict;
+    }
+
+    /// <summary>
+    /// 获取枚举的Description
+    /// </summary>
+    /// <param name="value"></param>
+    /// <returns></returns>
+    public static string GetDescription(this System.Enum value)
+    {
+        return value.GetType().GetMember(value.ToString()).FirstOrDefault()?.GetCustomAttribute<DescriptionAttribute>()
+            ?.Description;
+    }
+
+    /// <summary>
+    /// 获取枚举的Description
+    /// </summary>
+    /// <param name="value"></param>
+    /// <returns></returns>
+    public static string GetDescription(this object value)
+    {
+        return value.GetType().GetMember(value.ToString() ?? string.Empty).FirstOrDefault()
+            ?.GetCustomAttribute<DescriptionAttribute>()?.Description;
+    }
+
+    /// <summary>
+    /// 将枚举转成枚举信息集合
+    /// </summary>
+    /// <param name="type"></param>
+    /// <returns></returns>
+    public static List<EnumEntity> EnumToList(this Type type)
+    {
+        if (!type.IsEnum)
+            throw new ArgumentException("Type '" + type.Name + "' is not an enum.");
+        var arr = System.Enum.GetNames(type);
+        return arr.Select(sl =>
+        {
+            var item = System.Enum.Parse(type, sl);
+            return new EnumEntity
+            {
+                Name = item.ToString(),
+                Describe = item.GetDescription(),
+                Value = item.GetHashCode()
+            };
+        }).ToList();
+    }
+
+    /// <summary>
+    /// 枚举ToList
+    /// </summary>
+    /// <typeparam name="T"></typeparam>
+    /// <param name="type"></param>
+    /// <returns></returns>
+    public static List<T> EnumToList<T>(this Type type)
+    {
+        if (!type.IsEnum)
+            throw new ArgumentException("Type '" + type.Name + "' is not an enum.");
+        var arr = System.Enum.GetNames(type);
+        return arr.Select(name => (T)System.Enum.Parse(type, name)).ToList();
+    }
+}
+
+/// <summary>
+/// 枚举实体
+/// </summary>
+public class EnumEntity
+{
+    /// <summary>
+    /// 枚举的描述
+    /// </summary>
+    public string Describe { set; get; }
+
+    /// <summary>
+    /// 枚举名称
+    /// </summary>
+    public string Name { set; get; }
+
+    /// <summary>
+    /// 枚举对象的值
+    /// </summary>
+    public int Value { set; get; }
+}

+ 1 - 1
Admin.NET/Admin.NET.Core/Extension/ObjectExtension.cs

@@ -1,7 +1,7 @@
 namespace Admin.NET.Core;
 
 /// <summary>
-/// 对象拓展
+/// 对象拓展
 /// </summary>
 [SuppressSniffer]
 public static class ObjectExtension

+ 3 - 0
Admin.NET/Admin.NET.Core/Extension/RepositoryExtension.cs

@@ -1,5 +1,8 @@
 namespace Admin.NET.Core;
 
+/// <summary>
+/// 仓储拓展
+/// </summary>
 public static class RepositoryExtension
 {
     /// <summary>

+ 8 - 1
Admin.NET/Admin.NET.Core/Hub/IChatClient.cs

@@ -9,7 +9,7 @@ public interface IChatClient
     /// 强制下线
     /// </summary>
     /// <returns></returns>
-    Task ForceExist();
+    Task ForceExist(string str);
 
     /// <summary>
     /// 发送信息
@@ -17,4 +17,11 @@ public interface IChatClient
     /// <param name="context"></param>
     /// <returns></returns>
     Task ReceiveMessage(object context);
+
+    /// <summary>
+    /// 组合信息
+    /// </summary>
+    /// <param name="notice"></param>
+    /// <returns></returns>
+    Task AppendNotice(SysNotice notice);
 }

+ 57 - 0
Admin.NET/Admin.NET.Core/Service/Notice/Dto/NoticeBase.cs

@@ -0,0 +1,57 @@
+namespace Admin.NET.Core.Service;
+
+/// <summary>
+/// 通知公告参数
+/// </summary>
+public class NoticeBase
+{
+    /// <summary>
+    /// 标题
+    /// </summary>
+    public string Title { get; set; }
+
+    /// <summary>
+    /// 内容
+    /// </summary>
+    public string Content { get; set; }
+
+    /// <summary>
+    /// 类型(字典 1通知 2公告)
+    /// </summary>
+    public int Type { get; set; }
+
+    /// <summary>
+    /// 发布人Id
+    /// </summary>
+    public long PublicUserId { get; set; }
+
+    /// <summary>
+    /// 发布人姓名
+    /// </summary>
+    public string PublicUserName { get; set; }
+
+    /// <summary>
+    /// 发布机构Id
+    /// </summary>
+    public long PublicOrgId { get; set; }
+
+    /// <summary>
+    /// 发布机构名称
+    /// </summary>
+    public string PublicOrgName { get; set; }
+
+    /// <summary>
+    /// 发布时间
+    /// </summary>
+    public DateTime PublicTime { get; set; }
+
+    /// <summary>
+    /// 撤回时间
+    /// </summary>
+    public DateTime CancelTime { get; set; }
+
+    /// <summary>
+    /// 状态(字典 0草稿 1发布 2撤回 3删除)
+    /// </summary>
+    public int Status { get; set; }
+}

+ 40 - 0
Admin.NET/Admin.NET.Core/Service/Notice/Dto/NoticeDetailOutput.cs

@@ -0,0 +1,40 @@
+namespace Admin.NET.Core.Service;
+
+/// <summary>
+/// 系统通知公告详情参数
+/// </summary>
+public class NoticeDetailOutput : NoticeBase
+{
+    /// <summary>
+    /// 通知到的用户Id集合
+    /// </summary>
+    public List<string> NoticeUserIdList { get; set; }
+
+    /// <summary>
+    /// 通知到的用户阅读信息集合
+    /// </summary>
+    public List<NoticeUserRead> NoticeUserReadInfoList { get; set; }
+}
+
+public class NoticeUserRead
+{
+    /// <summary>
+    /// 用户Id
+    /// </summary>
+    public long UserId { get; set; }
+
+    /// <summary>
+    /// 用户名称
+    /// </summary>
+    public string UserName { get; set; }
+
+    /// <summary>
+    /// 状态(字典 0未读 1已读)
+    /// </summary>
+    public NoticeUserStatusEnum ReadStatus { get; set; }
+
+    /// <summary>
+    /// 阅读时间
+    /// </summary>
+    public DateTime ReadTime { get; set; }
+}

+ 96 - 0
Admin.NET/Admin.NET.Core/Service/Notice/Dto/NoticeInput.cs

@@ -0,0 +1,96 @@
+namespace Admin.NET.Core.Service;
+
+/// <summary>
+/// 通知公告参数
+/// </summary>
+public class NoticeInput : BasePageInput
+{
+    /// <summary>
+    /// 标题
+    /// </summary>
+    public virtual string Title { get; set; }
+
+    /// <summary>
+    /// 内容
+    /// </summary>
+    public virtual string Content { get; set; }
+
+    /// <summary>
+    /// 类型(字典 1通知 2公告)
+    /// </summary>
+    public virtual NoticeTypeEnum Type { get; set; }
+
+    /// <summary>
+    /// 状态(字典 0草稿 1发布 2撤回 3删除)
+    /// </summary>
+    public virtual NoticeStatusEnum Status { get; set; }
+
+    /// <summary>
+    /// 通知到的人
+    /// </summary>
+    public virtual List<long> NoticeUserIdList { get; set; }
+}
+
+public class AddNoticeInput : NoticeInput
+{
+    /// <summary>
+    /// 标题
+    /// </summary>
+    [Required(ErrorMessage = "标题不能为空")]
+    public override string Title { get; set; }
+
+    /// <summary>
+    /// 内容
+    /// </summary>
+    [Required(ErrorMessage = "内容不能为空")]
+    public override string Content { get; set; }
+
+    /// <summary>
+    /// 类型(字典 1通知 2公告)
+    /// </summary>
+    [Required(ErrorMessage = "类型不能为空")]
+    public override NoticeTypeEnum Type { get; set; }
+
+    /// <summary>
+    /// 状态(字典 0草稿 1发布 2撤回 3删除)
+    /// </summary>
+    [Required(ErrorMessage = "状态不能为空")]
+    public override NoticeStatusEnum Status { get; set; }
+
+    /// <summary>
+    /// 通知到的人
+    /// </summary>
+    [Required(ErrorMessage = "通知到的人不能为空")]
+    public override List<long> NoticeUserIdList { get; set; }
+}
+
+public class DeleteNoticeInput
+{
+    /// <summary>
+    /// Id
+    /// </summary>
+    [Required(ErrorMessage = "通知公告Id不能为空")]
+    public long Id { get; set; }
+}
+
+public class UpdateNoticeInput : AddNoticeInput
+{
+    /// <summary>
+    /// Id
+    /// </summary>
+    [Required(ErrorMessage = "通知公告Id不能为空")]
+    public long Id { get; set; }
+}
+
+public class QueryNoticeInput : DeleteNoticeInput
+{
+}
+
+public class ChangeStatusNoticeInput : DeleteNoticeInput
+{
+    /// <summary>
+    /// 状态(字典 0草稿 1发布 2撤回 3删除)
+    /// </summary>
+    [Required(ErrorMessage = "状态不能为空")]
+    public NoticeStatusEnum Status { get; set; }
+}

+ 22 - 0
Admin.NET/Admin.NET.Core/Service/Notice/Dto/NoticeReceiveOutput.cs

@@ -0,0 +1,22 @@
+namespace Admin.NET.Core.Service;
+
+/// <summary>
+/// 通知公告接收参数
+/// </summary>
+public class NoticeReceiveOutput : NoticeBase
+{
+    /// <summary>
+    /// Id
+    /// </summary>
+    public long Id { get; set; }
+
+    /// <summary>
+    /// 阅读状态(字典 0未读 1已读)
+    /// </summary>
+    public int ReadStatus { get; set; }
+
+    /// <summary>
+    /// 阅读时间
+    /// </summary>
+    public DateTime ReadTime { get; set; }
+}

+ 18 - 0
Admin.NET/Admin.NET.Core/Service/Notice/ISysNoticeService.cs

@@ -0,0 +1,18 @@
+namespace Admin.NET.Core.Service;
+
+public interface ISysNoticeService
+{
+    Task AddNotice(AddNoticeInput input);
+
+    Task ChangeStatus(ChangeStatusNoticeInput input);
+
+    Task DeleteNotice(DeleteNoticeInput input);
+
+    Task<NoticeDetailOutput> GetNotice([FromQuery] QueryNoticeInput input);
+
+    Task<dynamic> QueryNoticePageList([FromQuery] NoticeInput input);
+
+    Task<dynamic> ReceivedNoticePageList([FromQuery] NoticeInput input);
+
+    Task UpdateNotice(UpdateNoticeInput input);
+}

+ 12 - 0
Admin.NET/Admin.NET.Core/Service/Notice/ISysNoticeUserService.cs

@@ -0,0 +1,12 @@
+namespace Admin.NET.Core.Service;
+
+public interface ISysNoticeUserService
+{
+    Task Add(long noticeId, List<long> noticeUserIdList, NoticeUserStatusEnum noticeUserStatus);
+
+    Task<List<SysNoticeUser>> GetNoticeUserListByNoticeId(long noticeId);
+
+    Task Read(long noticeId, long userId, NoticeUserStatusEnum status);
+
+    Task Update(long noticeId, List<long> noticeUserIdList, NoticeUserStatusEnum noticeUserStatus);
+}

+ 257 - 0
Admin.NET/Admin.NET.Core/Service/Notice/SysNoticeService.cs

@@ -0,0 +1,257 @@
+namespace Admin.NET.Core.Service;
+
+/// <summary>
+/// 系统通知公告服务
+/// </summary>
+[ApiDescriptionSettings(Name = "通知公告", Order = 100)]
+public class SysNoticeService : ISysNoticeService, IDynamicApiController, ITransient
+{
+    private readonly SqlSugarRepository<SysNotice> _sysNoticeRep;
+    private readonly ISysOnlineUserService _sysOnlineUserService;
+
+    private readonly ISysNoticeUserService _sysNoticeUserService;
+    private readonly SqlSugarRepository<SysUser> _sysUserRep;
+
+    private readonly IUserManager _userManager;
+
+    public SysNoticeService(SqlSugarRepository<SysNotice> sysNoticeRep,
+        ISysNoticeUserService sysNoticeUserService,
+        ISysOnlineUserService sysOnlineUserService,
+        SqlSugarRepository<SysUser> sysUserRep,
+        IUserManager userManager)
+    {
+        _sysNoticeRep = sysNoticeRep;
+        _sysNoticeUserService = sysNoticeUserService;
+        _sysOnlineUserService = sysOnlineUserService;
+        _sysUserRep = sysUserRep;
+        _userManager = userManager;
+    }
+
+    /// <summary>
+    /// 分页查询通知公告
+    /// </summary>
+    /// <param name="input"></param>
+    /// <returns></returns>
+    [HttpGet("/sysNotice/page")]
+    public async Task<dynamic> QueryNoticePageList([FromQuery] NoticeInput input)
+    {
+        return await _sysNoticeRep.Context.Queryable<SysNotice>()
+            .WhereIF(!string.IsNullOrWhiteSpace(input.Title), u => u.Title.Contains(input.Title.Trim()))
+            .WhereIF(!string.IsNullOrWhiteSpace(input.Content), u => u.Content.Contains(input.Content.Trim()))
+            .WhereIF(input.Type > 0, u => u.Type == input.Type)
+            .Where(u => u.Status != NoticeStatusEnum.DELETED)
+            .ToPagedListAsync(input.Page, input.PageSize);
+    }
+
+    /// <summary>
+    /// 增加通知公告
+    /// </summary>
+    /// <param name="input"></param>
+    /// <returns></returns>
+    [HttpPost("/sysNotice/add")]
+    public async Task AddNotice(AddNoticeInput input)
+    {
+        if (input.Status != NoticeStatusEnum.DRAFT && input.Status != NoticeStatusEnum.PUBLIC)
+            throw Oops.Oh(ErrorCodeEnum.D7000);
+
+        var notice = input.Adapt<SysNotice>();
+        await UpdatePublicInfo(notice);
+        // 如果是发布,则设置发布时间
+        if (input.Status == NoticeStatusEnum.PUBLIC)
+            notice.PublicTime = DateTime.Now;
+        var newItem = await _sysNoticeRep.AsInsertable(notice).ExecuteReturnEntityAsync();
+
+        // 通知到的人
+        var noticeUserIdList = input.NoticeUserIdList;
+        var noticeUserStatus = NoticeUserStatusEnum.UNREAD;
+        await _sysNoticeUserService.Add(newItem.Id, noticeUserIdList, noticeUserStatus);
+        if (newItem.Status == NoticeStatusEnum.PUBLIC)
+        {
+            await _sysOnlineUserService.PushNotice(newItem, noticeUserIdList);
+        }
+    }
+
+    /// <summary>
+    /// 删除通知公告
+    /// </summary>
+    /// <param name="input"></param>
+    /// <returns></returns>
+    [HttpPost("/sysNotice/delete")]
+    public async Task DeleteNotice(DeleteNoticeInput input)
+    {
+        var notice = await _sysNoticeRep.GetFirstAsync(u => u.Id == input.Id);
+        if (notice.Status != NoticeStatusEnum.DRAFT && notice.Status != NoticeStatusEnum.CANCEL) // 只能删除草稿和撤回的
+            throw Oops.Oh(ErrorCodeEnum.D7001);
+
+        await _sysNoticeRep.DeleteAsync(notice);
+    }
+
+    /// <summary>
+    /// 更新通知公告
+    /// </summary>
+    /// <param name="input"></param>
+    /// <returns></returns>
+    [HttpPost("/sysNotice/edit")]
+    public async Task UpdateNotice(UpdateNoticeInput input)
+    {
+        if (input.Status != NoticeStatusEnum.DRAFT && input.Status != NoticeStatusEnum.PUBLIC)
+            throw Oops.Oh(ErrorCodeEnum.D7000);
+
+        //  非草稿状态
+        if (input.Status != NoticeStatusEnum.DRAFT)
+            throw Oops.Oh(ErrorCodeEnum.D7002);
+
+        var notice = input.Adapt<SysNotice>();
+        if (input.Status == NoticeStatusEnum.PUBLIC)
+        {
+            notice.PublicTime = DateTime.Now;
+            await UpdatePublicInfo(notice);
+        }
+        await _sysNoticeRep.UpdateAsync(notice);
+
+        // 通知到的人
+        var noticeUserIdList = input.NoticeUserIdList;
+        var noticeUserStatus = NoticeUserStatusEnum.UNREAD;
+        await _sysNoticeUserService.Update(input.Id, noticeUserIdList, noticeUserStatus);
+        if (notice.Status == NoticeStatusEnum.PUBLIC)
+        {
+            await _sysOnlineUserService.PushNotice(notice, noticeUserIdList);
+        }
+    }
+
+    /// <summary>
+    /// 获取通知公告详情
+    /// </summary>
+    /// <param name="input"></param>
+    /// <returns></returns>
+    [HttpGet("/sysNotice/detail")]
+    public async Task<NoticeDetailOutput> GetNotice([FromQuery] QueryNoticeInput input)
+    {
+        var notice = await _sysNoticeRep.GetFirstAsync(u => u.Id == input.Id);
+
+        // 获取通知到的用户
+        var noticeUserList = await _sysNoticeUserService.GetNoticeUserListByNoticeId(input.Id);
+        var noticeUserIdList = new List<string>();
+        var noticeUserReadInfoList = new List<NoticeUserRead>();
+        if (noticeUserList != null)
+        {
+            noticeUserList.ForEach(u =>
+            {
+                noticeUserIdList.Add(u.UserId.ToString());
+                var noticeUserRead = new NoticeUserRead
+                {
+                    UserId = u.UserId,
+                    UserName = _userManager.UserName,
+                    ReadStatus = u.ReadStatus,
+                    ReadTime = u.ReadTime
+                };
+                noticeUserReadInfoList.Add(noticeUserRead);
+            });
+        }
+        var noticeResult = notice.Adapt<NoticeDetailOutput>();
+        noticeResult.NoticeUserIdList = noticeUserIdList;
+        noticeResult.NoticeUserReadInfoList = noticeUserReadInfoList;
+        // 如果该条通知公告为已发布,则将当前用户的该条通知公告设置为已读
+        if (notice.Status == NoticeStatusEnum.PUBLIC)
+            await _sysNoticeUserService.Read(notice.Id, _userManager.UserId, NoticeUserStatusEnum.READ);
+        return noticeResult;
+    }
+
+    /// <summary>
+    /// 修改通知公告状态
+    /// </summary>
+    /// <param name="input"></param>
+    /// <returns></returns>
+    [HttpPost("/sysNotice/changeStatus")]
+    public async Task ChangeStatus(ChangeStatusNoticeInput input)
+    {
+        // 状态应为撤回或删除或发布
+        if (input.Status != NoticeStatusEnum.CANCEL && input.Status != NoticeStatusEnum.DELETED && input.Status != NoticeStatusEnum.PUBLIC)
+            throw Oops.Oh(ErrorCodeEnum.D7000);
+
+        var notice = await _sysNoticeRep.GetFirstAsync(u => u.Id == input.Id);
+        notice.Status = input.Status;
+
+        if (input.Status == NoticeStatusEnum.CANCEL)
+        {
+            notice.CancelTime = DateTime.Now;
+        }
+        else if (input.Status == NoticeStatusEnum.PUBLIC)
+        {
+            notice.PublicTime = DateTime.Now;
+        }
+        await _sysNoticeRep.UpdateAsync(notice);
+        if (notice.Status == NoticeStatusEnum.PUBLIC)
+        {
+            // 获取通知到的用户
+            var noticeUserList = await _sysNoticeUserService.GetNoticeUserListByNoticeId(input.Id);
+            await _sysOnlineUserService.PushNotice(notice, noticeUserList.Select(m => m.UserId).ToList());
+        }
+    }
+
+    /// <summary>
+    /// 获取接收的通知公告
+    /// </summary>
+    /// <param name="input"></param>
+    /// <returns></returns>
+    [HttpGet("/sysNotice/received")]
+    public async Task<dynamic> ReceivedNoticePageList([FromQuery] NoticeInput input)
+    {
+        return await _sysNoticeRep.Context.Queryable<SysNotice, SysNoticeUser>((n, u) => new JoinQueryInfos(JoinType.Inner, n.Id == u.NoticeId))
+          .Where((n, u) => u.UserId == _userManager.UserId)
+          .WhereIF(!string.IsNullOrWhiteSpace(input.Title), u => u.Title.Contains(input.Title.Trim()))
+          .WhereIF(!string.IsNullOrWhiteSpace(input.Content), u => u.Title.Contains(input.Content.Trim()))
+          .WhereIF(input.Type > 0, (n, u) => n.Type == input.Type)
+          .Select<NoticeReceiveOutput>()
+          .ToPagedListAsync(input.Page, input.PageSize);
+    }
+
+    /// <summary>
+    /// 未处理消息
+    /// </summary>
+    /// <param name="input"></param>
+    /// <returns></returns>
+    [HttpGet("/sysNotice/unread")]
+    public async Task<dynamic> UnReadNoticeList([FromQuery] NoticeInput input)
+    {
+        var dic = typeof(NoticeTypeEnum).EnumToList();
+        var notices = await _sysNoticeRep.Context.Queryable<SysNotice, SysNoticeUser>((n, u) => new JoinQueryInfos(JoinType.Inner, n.Id == u.NoticeId))
+            .Where((n, u) => u.UserId == _userManager.UserId && u.ReadStatus == NoticeUserStatusEnum.UNREAD)
+            .PartitionBy(n => n.Type).OrderBy(n => n.CreateTime, OrderByType.Desc)
+            .Take(input.PageSize).Select<NoticeReceiveOutput>().ToListAsync();
+        var count = await _sysNoticeRep.Context.Queryable<SysNotice, SysNoticeUser>((n, u) => new JoinQueryInfos(JoinType.Inner, n.Id == u.NoticeId))
+            .Where((n, u) => u.UserId == _userManager.UserId && u.ReadStatus == NoticeUserStatusEnum.UNREAD).CountAsync();
+
+        var noticeClays = new List<dynamic>();
+        int index = 0;
+        foreach (var item in dic)
+        {
+            noticeClays.Add(new
+            {
+                Index = index++,
+                Key = item.Describe,
+                Value = item.Value,
+                NoticeData = notices.Where(m => m.Type == item.Value).ToList()
+            });
+        }
+        return new
+        {
+            Rows = noticeClays,
+            TotalRows = count
+        };
+    }
+
+    /// <summary>
+    /// 更新发布信息
+    /// </summary>
+    /// <param name="notice"></param>
+    [NonAction]
+    private async Task UpdatePublicInfo(SysNotice notice)
+    {
+        var user = await _sysUserRep.GetFirstAsync(u => u.Id == _userManager.UserId);
+        notice.PublicUserId = _userManager.UserId;
+        notice.PublicUserName = _userManager.UserName;
+        notice.PublicOrgId = user.OrgId;
+        // notice.PublicOrgName = user.OrgName;
+    }
+}

+ 77 - 0
Admin.NET/Admin.NET.Core/Service/Notice/SysNoticeUserService.cs

@@ -0,0 +1,77 @@
+namespace Admin.NET.Core.Service;
+
+/// <summary>
+/// 系统通知公告用户
+/// </summary>
+[ApiDescriptionSettings(Name = "通知公告用户", Order = 100)]
+public class SysNoticeUserService : ISysNoticeUserService, ITransient
+{
+    private readonly SqlSugarRepository<SysNoticeUser> _sysNoticeUserRep;  // 通知公告用户表仓储
+
+    public SysNoticeUserService(SqlSugarRepository<SysNoticeUser> sysNoticeUserRep)
+    {
+        _sysNoticeUserRep = sysNoticeUserRep;
+    }
+
+    /// <summary>
+    /// 增加
+    /// </summary>
+    /// <param name="noticeId"></param>
+    /// <param name="noticeUserIdList"></param>
+    /// <param name="noticeUserStatus"></param>
+    /// <returns></returns>
+    public async Task Add(long noticeId, List<long> noticeUserIdList, NoticeUserStatusEnum noticeUserStatus)
+    {
+        var list = new List<SysNoticeUser>();
+        noticeUserIdList.ForEach(u =>
+        {
+            list.Add(new SysNoticeUser
+            {
+                NoticeId = noticeId,
+                UserId = u,
+                ReadStatus = noticeUserStatus
+            });
+        });
+        await _sysNoticeUserRep.InsertRangeAsync(list);
+    }
+
+    /// <summary>
+    /// 更新
+    /// </summary>
+    /// <param name="noticeId"></param>
+    /// <param name="noticeUserIdList"></param>
+    /// <param name="noticeUserStatus"></param>
+    /// <returns></returns>
+    public async Task Update(long noticeId, List<long> noticeUserIdList, NoticeUserStatusEnum noticeUserStatus)
+    {
+        await _sysNoticeUserRep.DeleteAsync(u => u.NoticeId == noticeId);
+
+        await Add(noticeId, noticeUserIdList, noticeUserStatus);
+    }
+
+    /// <summary>
+    /// 获取通知公告用户列表
+    /// </summary>
+    /// <param name="noticeId"></param>
+    /// <returns></returns>
+    public async Task<List<SysNoticeUser>> GetNoticeUserListByNoticeId(long noticeId)
+    {
+        return await _sysNoticeUserRep.GetListAsync(u => u.NoticeId == noticeId);
+    }
+
+    /// <summary>
+    /// 设置通知公告读取状态
+    /// </summary>
+    /// <param name="noticeId"></param>
+    /// <param name="userId"></param>
+    /// <param name="status"></param>
+    /// <returns></returns>
+    public async Task Read(long noticeId, long userId, NoticeUserStatusEnum status)
+    {
+        await _sysNoticeUserRep.UpdateAsync(u => new SysNoticeUser
+        {
+            ReadStatus = status,
+            ReadTime = DateTime.Now
+        }, u => u.NoticeId == noticeId && u.UserId == userId);
+    }
+}

+ 10 - 0
Admin.NET/Admin.NET.Core/Service/OnlineUser/ISysOnlineUserService.cs

@@ -0,0 +1,10 @@
+namespace Admin.NET.Core.Service;
+
+public interface ISysOnlineUserService
+{
+    Task<dynamic> List(BasePageInput input);
+
+    Task ForceExist(SysOnlineUser user);
+
+    Task PushNotice(SysNotice notice, List<long> userIds);
+}

+ 59 - 0
Admin.NET/Admin.NET.Core/Service/OnlineUser/SysOnlineUserService.cs

@@ -0,0 +1,59 @@
+using Microsoft.AspNetCore.SignalR;
+
+namespace Admin.NET.Core.Service;
+
+/// <summary>
+/// 系统在线用户服务
+/// </summary>
+[ApiDescriptionSettings(Name = "在线用户", Order = 100)]
+public class SysOnlineUserService : ISysOnlineUserService, IDynamicApiController, ITransient
+{
+    private readonly ISysCacheService _sysCacheService;
+    private readonly SqlSugarRepository<SysOnlineUser> _sysOnlineUerRep;
+    private readonly IHubContext<ChatHub, IChatClient> _chatHubContext;
+
+    public SysOnlineUserService(ISysCacheService sysCacheService,
+        SqlSugarRepository<SysOnlineUser> sysOnlineUerRep,
+        IHubContext<ChatHub, IChatClient> chatHubContext)
+    {
+        _sysCacheService = sysCacheService;
+        _sysOnlineUerRep = sysOnlineUerRep;
+        _chatHubContext = chatHubContext;
+    }
+
+    /// <summary>
+    /// 获取在线用户信息
+    /// </summary>
+    /// <returns></returns>
+    [HttpGet("/sysOnlineUser/page")]
+    public async Task<dynamic> List([FromQuery] BasePageInput input)
+    {
+        return await _sysOnlineUerRep.AsQueryable().ToPagedListAsync(input.Page, input.PageSize);
+    }
+
+    /// <summary>
+    /// 强制下线
+    /// </summary>
+    /// <param name="user"></param>
+    /// <returns></returns>
+    [HttpPost("/sysOnlineUser/forceExist")]
+    [NonValidation]
+    public async Task ForceExist(SysOnlineUser user)
+    {
+        await _chatHubContext.Clients.Client(user.ConnectionId).ForceExist("00000000");
+        await _sysOnlineUerRep.DeleteAsync(user);
+    }
+
+    [NonAction]
+    public async Task PushNotice(SysNotice notice, List<long> userIds)
+    {
+        var userList = await _sysOnlineUerRep.GetListAsync(m => userIds.Contains(m.UserId));
+        if (userList.Any())
+        {
+            foreach (var item in userList)
+            {
+                await _chatHubContext.Clients.Client(item.ConnectionId).AppendNotice(notice);
+            }
+        }
+    }
+}

+ 22 - 0
Admin.NET/Admin.NET.Core/Util/ReflectionUtil.cs

@@ -0,0 +1,22 @@
+namespace Admin.NET.Core;
+
+/// <summary>
+/// 反射工具类
+/// </summary>
+public static class ReflectionUtil
+{
+    /// <summary>
+    /// 获取字段特性
+    /// </summary>
+    /// <param name="field"></param>
+    /// <typeparam name="T"></typeparam>
+    /// <returns></returns>
+    public static T GetDescriptionValue<T>(this FieldInfo field) where T : Attribute
+    {
+        // 获取字段的指定特性,不包含继承中的特性
+        object[] customAttributes = field.GetCustomAttributes(typeof(T), false);
+
+        // 如果没有数据返回null
+        return customAttributes.Length > 0 ? (T)customAttributes[0] : null;
+    }
+}