😎优化发送消息流程

This commit is contained in:
zuohuaijun 2025-08-13 03:36:50 +08:00
parent afa8993a7d
commit c228339050
13 changed files with 8394 additions and 5811 deletions

View File

@ -14,6 +14,11 @@ namespace Admin.NET.Core;
[LogTable]
public partial class SysLogMsg : EntityTenant
{
/// <summary>
/// 消息类型
/// </summary>
public string MessageType { get; set; }
/// <summary>
/// 消息标题
/// </summary>

View File

@ -115,11 +115,11 @@ public class OnlineUserHub : Hub<IOnlineUserHub>
}
/// <summary>
/// 发送信息给某
/// 发送信息给某
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
public async Task ClientsSendMessage(MessageInput message)
public async Task SendMessageToUser(MessageInput message)
{
await _sysMessageService.SendUser(message);
}
@ -129,28 +129,8 @@ public class OnlineUserHub : Hub<IOnlineUserHub>
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
public async Task ClientsSendMessageToAll(MessageInput message)
public async Task SendMessageToAllUser(MessageInput message)
{
await _sysMessageService.SendAllUser(message);
}
/// <summary>
/// 发送消息给某些人(除了本人)
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
public async Task ClientsSendMessageToOther(MessageInput message)
{
await _sysMessageService.SendOtherUser(message);
}
/// <summary>
/// 发送消息给某些人
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
public async Task ClientsSendMessageToUsers(MessageInput message)
{
await _sysMessageService.SendUsers(message);
}
}

View File

@ -8,16 +8,16 @@ namespace Admin.NET.Core;
public class MessageInput
{
/// <summary>
/// 消息类型
/// </summary>
public MessageTypeEnum MessageType { get; set; }
/// <summary>
/// 消息标题
/// </summary>
public string Title { get; set; }
///// <summary>
///// 消息类型
///// </summary>
//public MessageTypeEnum MessageType { get; set; }
/// <summary>
/// 消息内容
/// </summary>
@ -28,13 +28,8 @@ public class MessageInput
/// </summary>
public long SendUserId { get; set; }
/// <summary>
/// 接收者Id
/// </summary>
public long ReceiveUserId { get; set; }
/// <summary>
/// 接收者Id集合
/// </summary>
public List<long> UserIds { get; set; }
public List<long> ReceiveUserIds { get; set; }
}

View File

@ -36,79 +36,43 @@ public class SysMessageService : IDynamicApiController, ITransient
}
/// <summary>
/// 发送消息给除了发送人的其他人 🔖
/// 发送消息给人 🔖
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[DisplayName("发送消息给除了发送人的其他人")]
public async Task SendOtherUser(MessageInput input)
{
var hashKey = SysCacheService.HashGetAll<SysOnlineUser>(CacheConst.KeyUserOnline);
var exceptReceiveUsers = hashKey.Where(u => u.Value.UserId == input.ReceiveUserId).Select(u => u.Value).ToList();
await _chatHubContext.Clients.AllExcept(exceptReceiveUsers.Select(t => t.ConnectionId)).ReceiveMessage(input);
}
/// <summary>
/// 发送消息给某个人 🔖
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[DisplayName("发送消息给某个人")]
[DisplayName("发送消息给某人")]
public async Task SendUser(MessageInput input)
{
var sysLogMsg = await SaveMsgLog(input);
var hashKey = SysCacheService.HashGetAll<SysOnlineUser>(CacheConst.KeyUserOnline);
var receiveUser = hashKey.Where(u => u.Value.UserId == input.ReceiveUserId).Select(u => u.Value).FirstOrDefault();
await _chatHubContext.Clients.Client(receiveUser.ConnectionId ?? "").ReceiveMessage(sysLogMsg);
}
/// <summary>
/// 发送消息给某些人 🔖
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[DisplayName("发送消息给某些人")]
public async Task SendUsers(MessageInput input)
{
var hashKey = SysCacheService.HashGetAll<SysOnlineUser>(CacheConst.KeyUserOnline);
var receiveUsers = hashKey.Where(u => input.UserIds.Any(a => a == u.Value.UserId)).Select(u => u.Value).ToList();
foreach (var user in receiveUsers)
var sendUser = hashKey.Where(u => u.Value.UserId == input.SendUserId).Select(u => u.Value).FirstOrDefault();
var receiveUsers = hashKey.Where(u => input.ReceiveUserIds.Any(a => a == u.Value.UserId)).Select(u => u.Value).ToList();
foreach (var receiveUser in receiveUsers)
{
await _chatHubContext.Clients.Client(user.ConnectionId ?? "").ReceiveMessage(input);
var logMsg = new SysLogMsg
{
MessageType = input.MessageType.ToString(),
Title = input.Title,
Message = input.Message,
ReceiveUserId = receiveUser.UserId,
ReceiveUserName = receiveUser.RealName,
ReceiveIp = receiveUser.Ip,
ReceiveBrowser = receiveUser.Browser,
ReceiveOs = receiveUser.Os,
ReceiveDevice = receiveUser.Device,
SendUserId = sendUser.UserId,
SendUserName = sendUser.RealName,
SendIp = sendUser.Ip,
SendBrowser = sendUser.Browser,
SendOs = sendUser.Os,
SendDevice = sendUser.Device,
SendTime = DateTime.Now
};
// 发送消息
await _chatHubContext.Clients.Client(receiveUser.ConnectionId ?? "").ReceiveMessage(logMsg);
// 保存消息日志
await _eventPublisher.PublishAsync(CommonConst.AddMsgLog, logMsg);
}
}
/// <summary>
/// 记录消息日志
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
private async Task<SysLogMsg> SaveMsgLog(MessageInput input)
{
var hashKey = SysCacheService.HashGetAll<SysOnlineUser>(CacheConst.KeyUserOnline);
var receiveUser = hashKey.Where(u => u.Value.UserId == input.ReceiveUserId).Select(u => u.Value).FirstOrDefault();
var sendUser = hashKey.Where(u => u.Value.UserId == input.SendUserId).Select(u => u.Value).FirstOrDefault();
var sysLogMsg = new SysLogMsg
{
Title = input.Title,
Message = input.Message,
ReceiveUserId = receiveUser.UserId,
ReceiveUserName = receiveUser.RealName + "/" + receiveUser.UserName,
ReceiveIp = receiveUser.Ip,
ReceiveBrowser = receiveUser.Browser,
ReceiveOs = receiveUser.Os,
ReceiveDevice = receiveUser.Device,
SendUserId = sendUser.UserId,
SendUserName = sendUser.RealName + "/" + sendUser.UserName,
SendIp = sendUser.Ip,
SendBrowser = sendUser.Browser,
SendOs = sendUser.Os,
SendDevice = sendUser.Device,
SendTime = DateTime.Now
};
await _eventPublisher.PublishAsync(CommonConst.AddMsgLog, sysLogMsg);
return sysLogMsg;
}
}

View File

@ -60,7 +60,9 @@
})();
// 定义语言映射对象
const langMap = {
'en': (globalThis && globalThis.lang && globalThis.lang.en) ? globalThis.lang.en : globalThis._getJSONKey('en', langJSON),
'zhhk': (globalThis && globalThis.lang && globalThis.lang.zhhk) ? globalThis.lang.zhhk : globalThis._getJSONKey('zh-hk', langJSON),
'zhtw': (globalThis && globalThis.lang && globalThis.lang.zhtw) ? globalThis.lang.zhtw : globalThis._getJSONKey('zh-tw', langJSON),
'en': (globalThis && globalThis.lang && globalThis.lang.en) ? globalThis.lang.en : globalThis._getJSONKey('en', langJSON),
'zhcn': (globalThis && globalThis.lang && globalThis.lang.zhcn) ? globalThis.lang.zhcn : globalThis._getJSONKey('zh-cn', langJSON)
};
globalThis.langMap = langMap;

File diff suppressed because it is too large Load Diff

View File

@ -74,55 +74,7 @@ export const SysMessageApiAxiosParamCreator = function (configuration?: Configur
},
/**
*
* @summary 🔖
* @param {MessageInput} [body]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
apiSysMessageSendOtherUserPost: async (body?: MessageInput, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
const localVarPath = `/api/sysMessage/sendOtherUser`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, 'https://example.com');
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions :AxiosRequestConfig = { method: 'POST', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
// authentication Bearer required
// http bearer authentication required
if (configuration && configuration.accessToken) {
const accessToken = typeof configuration.accessToken === 'function'
? await configuration.accessToken()
: await configuration.accessToken;
localVarHeaderParameter["Authorization"] = "Bearer " + accessToken;
}
localVarHeaderParameter['Content-Type'] = 'application/json-patch+json';
const query = new URLSearchParams(localVarUrlObj.search);
for (const key in localVarQueryParameter) {
query.set(key, localVarQueryParameter[key]);
}
for (const key in options.params) {
query.set(key, options.params[key]);
}
localVarUrlObj.search = (new URLSearchParams(query)).toString();
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
const needsSerialization = (typeof body !== "string") || localVarRequestOptions.headers['Content-Type'] === 'application/json';
localVarRequestOptions.data = needsSerialization ? JSON.stringify(body !== undefined ? body : {}) : (body || "");
return {
url: localVarUrlObj.pathname + localVarUrlObj.search + localVarUrlObj.hash,
options: localVarRequestOptions,
};
},
/**
*
* @summary 🔖
* @summary 🔖
* @param {MessageInput} [body]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
@ -163,54 +115,6 @@ export const SysMessageApiAxiosParamCreator = function (configuration?: Configur
const needsSerialization = (typeof body !== "string") || localVarRequestOptions.headers['Content-Type'] === 'application/json';
localVarRequestOptions.data = needsSerialization ? JSON.stringify(body !== undefined ? body : {}) : (body || "");
return {
url: localVarUrlObj.pathname + localVarUrlObj.search + localVarUrlObj.hash,
options: localVarRequestOptions,
};
},
/**
*
* @summary 🔖
* @param {MessageInput} [body]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
apiSysMessageSendUsersPost: async (body?: MessageInput, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
const localVarPath = `/api/sysMessage/sendUsers`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, 'https://example.com');
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions :AxiosRequestConfig = { method: 'POST', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
// authentication Bearer required
// http bearer authentication required
if (configuration && configuration.accessToken) {
const accessToken = typeof configuration.accessToken === 'function'
? await configuration.accessToken()
: await configuration.accessToken;
localVarHeaderParameter["Authorization"] = "Bearer " + accessToken;
}
localVarHeaderParameter['Content-Type'] = 'application/json-patch+json';
const query = new URLSearchParams(localVarUrlObj.search);
for (const key in localVarQueryParameter) {
query.set(key, localVarQueryParameter[key]);
}
for (const key in options.params) {
query.set(key, options.params[key]);
}
localVarUrlObj.search = (new URLSearchParams(query)).toString();
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
const needsSerialization = (typeof body !== "string") || localVarRequestOptions.headers['Content-Type'] === 'application/json';
localVarRequestOptions.data = needsSerialization ? JSON.stringify(body !== undefined ? body : {}) : (body || "");
return {
url: localVarUrlObj.pathname + localVarUrlObj.search + localVarUrlObj.hash,
options: localVarRequestOptions,
@ -241,21 +145,7 @@ export const SysMessageApiFp = function(configuration?: Configuration) {
},
/**
*
* @summary 🔖
* @param {MessageInput} [body]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async apiSysMessageSendOtherUserPost(body?: MessageInput, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise<AxiosResponse<void>>> {
const localVarAxiosArgs = await SysMessageApiAxiosParamCreator(configuration).apiSysMessageSendOtherUserPost(body, options);
return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
const axiosRequestArgs :AxiosRequestConfig = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url};
return axios.request(axiosRequestArgs);
};
},
/**
*
* @summary 🔖
* @summary 🔖
* @param {MessageInput} [body]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
@ -267,20 +157,6 @@ export const SysMessageApiFp = function(configuration?: Configuration) {
return axios.request(axiosRequestArgs);
};
},
/**
*
* @summary 🔖
* @param {MessageInput} [body]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async apiSysMessageSendUsersPost(body?: MessageInput, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise<AxiosResponse<void>>> {
const localVarAxiosArgs = await SysMessageApiAxiosParamCreator(configuration).apiSysMessageSendUsersPost(body, options);
return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
const axiosRequestArgs :AxiosRequestConfig = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url};
return axios.request(axiosRequestArgs);
};
},
}
};
@ -302,17 +178,7 @@ export const SysMessageApiFactory = function (configuration?: Configuration, bas
},
/**
*
* @summary 🔖
* @param {MessageInput} [body]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async apiSysMessageSendOtherUserPost(body?: MessageInput, options?: AxiosRequestConfig): Promise<AxiosResponse<void>> {
return SysMessageApiFp(configuration).apiSysMessageSendOtherUserPost(body, options).then((request) => request(axios, basePath));
},
/**
*
* @summary 🔖
* @summary 🔖
* @param {MessageInput} [body]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
@ -320,16 +186,6 @@ export const SysMessageApiFactory = function (configuration?: Configuration, bas
async apiSysMessageSendUserPost(body?: MessageInput, options?: AxiosRequestConfig): Promise<AxiosResponse<void>> {
return SysMessageApiFp(configuration).apiSysMessageSendUserPost(body, options).then((request) => request(axios, basePath));
},
/**
*
* @summary 🔖
* @param {MessageInput} [body]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async apiSysMessageSendUsersPost(body?: MessageInput, options?: AxiosRequestConfig): Promise<AxiosResponse<void>> {
return SysMessageApiFp(configuration).apiSysMessageSendUsersPost(body, options).then((request) => request(axios, basePath));
},
};
};
@ -353,18 +209,7 @@ export class SysMessageApi extends BaseAPI {
}
/**
*
* @summary 🔖
* @param {MessageInput} [body]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof SysMessageApi
*/
public async apiSysMessageSendOtherUserPost(body?: MessageInput, options?: AxiosRequestConfig) : Promise<AxiosResponse<void>> {
return SysMessageApiFp(this.configuration).apiSysMessageSendOtherUserPost(body, options).then((request) => request(this.axios, this.basePath));
}
/**
*
* @summary 🔖
* @summary 🔖
* @param {MessageInput} [body]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
@ -373,15 +218,4 @@ export class SysMessageApi extends BaseAPI {
public async apiSysMessageSendUserPost(body?: MessageInput, options?: AxiosRequestConfig) : Promise<AxiosResponse<void>> {
return SysMessageApiFp(this.configuration).apiSysMessageSendUserPost(body, options).then((request) => request(this.axios, this.basePath));
}
/**
*
* @summary 🔖
* @param {MessageInput} [body]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof SysMessageApi
*/
public async apiSysMessageSendUsersPost(body?: MessageInput, options?: AxiosRequestConfig) : Promise<AxiosResponse<void>> {
return SysMessageApiFp(this.configuration).apiSysMessageSendUsersPost(body, options).then((request) => request(this.axios, this.basePath));
}
}

View File

@ -318,6 +318,7 @@ export * from './menu-output';
export * from './menu-type-enum';
export * from './message-input';
export * from './message-template-send-input';
export * from './message-type-enum';
export * from './method-attributes';
export * from './method-base';
export * from './method-impl-attributes';

View File

@ -12,6 +12,7 @@
* Do not edit the class manually.
*/
import { MessageTypeEnum } from './message-type-enum';
/**
*
*
@ -20,6 +21,12 @@
*/
export interface MessageInput {
/**
* @type {MessageTypeEnum}
* @memberof MessageInput
*/
messageType?: MessageTypeEnum;
/**
*
*
@ -44,19 +51,11 @@ export interface MessageInput {
*/
sendUserId?: number;
/**
* Id
*
* @type {number}
* @memberof MessageInput
*/
receiveUserId?: number;
/**
* Id集合
*
* @type {Array<number>}
* @memberof MessageInput
*/
userIds?: Array<number> | null;
receiveUserIds?: Array<number> | null;
}

View File

@ -0,0 +1,26 @@
/* tslint:disable */
/* eslint-disable */
/**
* Admin.NET
* .NET <br/><u><b><font color='FF0000'> 👮</font></b></u>
*
* OpenAPI spec version: 1.0.0
*
*
* NOTE: This class is auto generated by the swagger code generator program.
* https://github.com/swagger-api/swagger-codegen.git
* Do not edit the class manually.
*/
/**
* <br />&nbsp; Info = 0<br />&nbsp; Success = 1<br />&nbsp; Warning = 2<br />&nbsp; Error = 3<br />
* @export
* @enum {string}
*/
export enum MessageTypeEnum {
NUMBER_0 = 0,
NUMBER_1 = 1,
NUMBER_2 = 2,
NUMBER_3 = 3
}

View File

@ -92,6 +92,14 @@ export interface SysLogMsg {
*/
tenantId?: number | null;
/**
*
*
* @type {string}
* @memberof SysLogMsg
*/
messageType?: string | null;
/**
*
*

View File

@ -10,13 +10,23 @@
<el-form :model="state.ruleForm" ref="ruleFormRef" label-width="auto">
<el-row :gutter="10">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item :label="$t('message.list.recipient')" prop="receiveUserName" :rules="[{ required: true, message: $t('message.list.recipientRequired'), trigger: 'blur' }]">
<el-form-item :label="$t('message.list.title')" prop="title" :rules="[{ required: true, message: $t('message.list.titleRequired'), trigger: 'blur' }]">
<el-input v-model="state.ruleForm.title" :placeholder="$t('message.list.title')" clearable />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item :label="$t('message.list.type')">
<g-sys-dict v-model="state.ruleForm.messageType" code="MessageTypeEnum" render-as="radio" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item :label="$t('message.list.recipient')">
<el-input v-model="state.ruleForm.receiveUserName" :placeholder="$t('message.list.recipient')" disabled />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item :label="$t('message.list.title')" prop="title" :rules="[{ required: true, message: $t('message.list.titleRequired'), trigger: 'blur' }]">
<el-input v-model="state.ruleForm.title" :placeholder="$t('message.list.title')" clearable />
<el-form-item :label="$t('message.list.ipAddress')">
<el-input v-model="state.ruleForm.receiveUserId" :placeholder="$t('message.list.ipAddress')" disabled />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
@ -60,10 +70,10 @@ const openDialog = (row: any) => {
ruleFormRef.value?.resetFields();
var receiveUser = JSON.parse(JSON.stringify(row));
state.ruleForm.receiveUserId = receiveUser.userId;
state.ruleForm.receiveUserIds = [receiveUser.userId];
state.ruleForm.receiveUserName = receiveUser.realName + '/' + receiveUser.userName;
state.ruleForm.receiveUserId = receiveUser.ip;
state.ruleForm.connectionId = receiveUser.connectionId;
state.ruleForm.sendUserId = userInfos.value.id;
state.isShowDialog = true;
};
@ -73,8 +83,8 @@ const submit = () => {
ruleFormRef.value.validate(async (valid: boolean) => {
if (!valid) return;
//
signalR.send('ClientsSendMessage', state.ruleForm);
// Hub
signalR.send('SendMessageToUser', state.ruleForm);
});
};

View File

@ -46,13 +46,12 @@ connection.on('OnlineUserList', () => {});
// 接收消息
connection.on('ReceiveMessage', (message: any) => {
var tmpMsg = `<div style="white-space: pre-wrap;">${message.message}<div><br/>`;
tmpMsg += `<p style="color:#808080; font-size:12px">发送人员:${message.sendUserName}<p>`;
tmpMsg += `<p style="color:#808080; font-size:12px">发送时间:${message.sendTime}<p>`;
tmpMsg += `<p style="color:#808080; font-size:10px;float:right"> ${message.sendUserName} ${message.sendTime}<p>`;
ElNotification({
title: `消息【${message.title}`,
title: `${message.title}`,
message: tmpMsg,
type: 'info',
type: message.messageType.toString().toLowerCase(),
position: 'top-right',
dangerouslyUseHTMLString: true,
duration: 0,