Merge branch 'master' of https://github.com/hchen2020/BotSharp
This commit is contained in:
commit
bca3264ff3
|
|
@ -7,4 +7,5 @@ public enum BroswerActionEnum
|
|||
Typing = 3,
|
||||
Hover = 4,
|
||||
Scroll = 5,
|
||||
DragAndDrop = 6
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,4 +5,9 @@ public class ElementPosition
|
|||
public float X { get; set; } = default!;
|
||||
|
||||
public float Y { get; set; } = default!;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"[{X}, {Y}]";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
namespace BotSharp.Abstraction.Infrastructures.Enums;
|
||||
|
||||
public enum EventPriority
|
||||
{
|
||||
Low = 1,
|
||||
Medium = 2,
|
||||
High = 3
|
||||
}
|
||||
|
|
@ -10,7 +10,11 @@ public interface IEventPublisher
|
|||
/// <returns></returns>
|
||||
Task BroadcastAsync(string channel, string message);
|
||||
|
||||
Task PublishAsync(string channel, string message);
|
||||
Task<string?> PublishAsync(string channel, string message, EventPriority? priority = null);
|
||||
|
||||
Task ReDispatchAsync(string channel, int count = 10, string order = "asc");
|
||||
|
||||
Task ReDispatchPendingAsync(string channel, string group, int count = 10);
|
||||
|
||||
Task RemoveAsync(string channel, int count = 10);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,12 @@
|
|||
using System.Threading;
|
||||
|
||||
namespace BotSharp.Abstraction.Infrastructures.Events;
|
||||
|
||||
public interface IEventSubscriber
|
||||
{
|
||||
Task SubscribeAsync(string channel, Func<string, string, Task> received);
|
||||
|
||||
Task SubscribeAsync(string channel, string group, Func<string, string, Task> received);
|
||||
Task SubscribeAsync(string channel, string group, int? port, bool priorityEnabled,
|
||||
Func<string, string, Task> received,
|
||||
CancellationToken? stoppingToken = null);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,9 @@ public class SharpCacheAttribute : MoAttribute
|
|||
}
|
||||
|
||||
var httpContext = Services.GetRequiredService<IHttpContextAccessor>();
|
||||
if (httpContext.HttpContext.Response.Headers["Cache-Control"].ToString().Contains("no-store"))
|
||||
if (httpContext != null &&
|
||||
httpContext.HttpContext != null &&
|
||||
httpContext.HttpContext.Response.Headers["Cache-Control"].ToString().Contains("no-store"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,18 +2,19 @@ namespace BotSharp.Abstraction.Repositories;
|
|||
|
||||
public class BotSharpDatabaseSettings : DatabaseBasicSettings
|
||||
{
|
||||
public string[] Assemblies { get; set; }
|
||||
public string FileRepository { get; set; }
|
||||
public string BotSharpMongoDb { get; set; }
|
||||
public string TablePrefix { get; set; }
|
||||
public DbConnectionSetting BotSharp { get; set; }
|
||||
public string Redis { get; set; }
|
||||
public string[] Assemblies { get; set; } = [];
|
||||
public string FileRepository { get; set; } = string.Empty;
|
||||
public string BotSharpMongoDb { get; set; } = string.Empty;
|
||||
public string TablePrefix { get; set; } = string.Empty;
|
||||
public DbConnectionSetting BotSharp { get; set; } = new();
|
||||
public string Redis { get; set; } = string.Empty;
|
||||
public bool EnableReplica { get; set; } = true;
|
||||
}
|
||||
|
||||
public class DatabaseBasicSettings
|
||||
{
|
||||
public string Default { get; set; }
|
||||
public DbConnectionSetting DefaultConnection { get; set; }
|
||||
public string Default { get; set; } = string.Empty;
|
||||
public DbConnectionSetting DefaultConnection { get; set; } = new();
|
||||
public bool EnableSqlLog { get; set; }
|
||||
public bool EnableSensitiveDataLogging { get; set; }
|
||||
public bool EnableRetryOnFailure { get; set; }
|
||||
|
|
@ -23,9 +24,11 @@ public class DbConnectionSetting
|
|||
{
|
||||
public string Master { get; set; }
|
||||
public string[] Slavers { get; set; }
|
||||
public int ConnectionTimeout { get; set; } = 30;
|
||||
public int ExecutionTimeout { get; set; } = 30;
|
||||
|
||||
public DbConnectionSetting()
|
||||
{
|
||||
Slavers = new string[0];
|
||||
Slavers = [];
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ using BotSharp.Abstraction.Roles.Models;
|
|||
using BotSharp.Abstraction.Shared;
|
||||
using BotSharp.Abstraction.Tasks.Models;
|
||||
using BotSharp.Abstraction.Translation.Models;
|
||||
using BotSharp.Abstraction.Users.Enums;
|
||||
using BotSharp.Abstraction.Users.Models;
|
||||
using BotSharp.Abstraction.VectorStorage.Models;
|
||||
|
||||
|
|
@ -26,7 +27,7 @@ public interface IBotSharpRepository : IHaveServiceProvider
|
|||
|
||||
#region User
|
||||
User? GetUserByEmail(string email) => throw new NotImplementedException();
|
||||
User? GetUserByPhone(string phone, string role = null, string regionCode = "CN") => throw new NotImplementedException();
|
||||
User? GetUserByPhone(string phone, string type = UserType.Client, string regionCode = "CN") => throw new NotImplementedException();
|
||||
User? GetAffiliateUserByPhone(string phone) => throw new NotImplementedException();
|
||||
User? GetUserById(string id) => throw new NotImplementedException();
|
||||
List<User> GetUserByIds(List<string> ids) => throw new NotImplementedException();
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ public class User
|
|||
public bool Verified { get; set; }
|
||||
public string RegionCode { get; set; } = "CN";
|
||||
public string? AffiliateId { get; set; }
|
||||
public string? ReferralCode { get; set; }
|
||||
public string? EmployeeId { get; set; }
|
||||
public bool IsDisabled { get; set; }
|
||||
public IEnumerable<string> Permissions { get; set; } = [];
|
||||
|
|
|
|||
14
src/Infrastructure/BotSharp.Abstraction/Utilities/MathExt.cs
Normal file
14
src/Infrastructure/BotSharp.Abstraction/Utilities/MathExt.cs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
namespace BotSharp.Abstraction.Utilities;
|
||||
|
||||
public static class MathExt
|
||||
{
|
||||
public static int Max(int a, int b, int c)
|
||||
{
|
||||
return Math.Max(Math.Max(a, b), c);
|
||||
}
|
||||
|
||||
public static long Max(long a, long b, long c)
|
||||
{
|
||||
return Math.Max(Math.Max(a, b), c);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
using BotSharp.Abstraction.Infrastructures;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace BotSharp.Abstraction.Utilities;
|
||||
|
||||
[DebuggerStepThrough]
|
||||
public class Pagination : ICacheKey
|
||||
{
|
||||
private int _page;
|
||||
|
|
|
|||
|
|
@ -190,7 +190,7 @@
|
|||
<ItemGroup>
|
||||
<PackageReference Include="Aspects.Cache" Version="2.0.4" />
|
||||
<PackageReference Include="DistributedLock.Redis" Version="1.0.3" />
|
||||
<PackageReference Include="EntityFrameworkCore.BootKit" Version="8.5.1" />
|
||||
<PackageReference Include="EntityFrameworkCore.BootKit" Version="8.6.0" />
|
||||
<PackageReference Include="Fluid.Core" Version="2.11.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using BotSharp.Abstraction.Infrastructures.Enums;
|
||||
using StackExchange.Redis;
|
||||
|
||||
namespace BotSharp.Core.Infrastructures.Events;
|
||||
|
|
@ -20,14 +21,65 @@ public class RedisPublisher : IEventPublisher
|
|||
await _subscriber.PublishAsync(channel, message);
|
||||
}
|
||||
|
||||
public async Task PublishAsync(string channel, string message)
|
||||
public async Task<string?> PublishAsync(string channel, string message, EventPriority? priority = null)
|
||||
{
|
||||
var db = _redis.GetDatabase();
|
||||
|
||||
// convert to apporiate channel by priority
|
||||
if (priority != null)
|
||||
{
|
||||
channel = $"{channel}-{priority}";
|
||||
}
|
||||
|
||||
if (CheckMessageExists(db, channel, "message", message))
|
||||
{
|
||||
_logger.LogError($"The message already exists {channel} {message}");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Add a message to the stream, keeping only the latest 1 million messages
|
||||
await db.StreamAddAsync(channel, "message", message,
|
||||
var messageId = await db.StreamAddAsync(channel,
|
||||
[
|
||||
new NameValueEntry("message", message),
|
||||
new NameValueEntry("timestamp", DateTime.UtcNow.ToString("o"))
|
||||
],
|
||||
maxLength: 1000 * 10000);
|
||||
|
||||
_logger.LogInformation($"Published message {channel} {message}");
|
||||
_logger.LogInformation($"Published message {channel} {message} ({messageId})");
|
||||
|
||||
return messageId;
|
||||
}
|
||||
|
||||
private bool CheckMessageExists(IDatabase db, string channel, string fieldName, string desiredValue)
|
||||
{
|
||||
// Define the range to fetch all messages
|
||||
RedisValue start = "-"; // Start from the smallest ID
|
||||
RedisValue end = "+"; // End at the largest ID
|
||||
int count = 10; // Number of messages to retrieve
|
||||
|
||||
// Fetch the latest 10 messages
|
||||
var streamEntries = db.StreamRange(channel, start, end, count, Order.Descending);
|
||||
|
||||
if (streamEntries.Length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if any message contains the specific field value
|
||||
bool exists = streamEntries.Any(entry =>
|
||||
{
|
||||
// Each entry contains a collection of Name-Value pairs
|
||||
foreach (var nameValue in entry.Values)
|
||||
{
|
||||
if (nameValue.Name == fieldName && nameValue.Value == desiredValue)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
return exists;
|
||||
}
|
||||
|
||||
public async Task ReDispatchAsync(string channel, int count = 10, string order = "asc")
|
||||
|
|
@ -41,7 +93,10 @@ public class RedisPublisher : IEventPublisher
|
|||
|
||||
try
|
||||
{
|
||||
var messageId = await db.StreamAddAsync(channel, "message", entry.Values[0].Value);
|
||||
var messageId = await db.StreamAddAsync(channel, [
|
||||
new NameValueEntry("message", entry.Values[0].Value),
|
||||
new NameValueEntry("timestamp", DateTime.UtcNow.ToString("o"))
|
||||
]);
|
||||
|
||||
_logger.LogWarning($"ReDispatched message: {channel} {entry.Values[0].Value} ({messageId})");
|
||||
|
||||
|
|
@ -50,7 +105,78 @@ public class RedisPublisher : IEventPublisher
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"Error processing message: {ex.Message}, event id: {channel} {entry.Id}");
|
||||
_logger.LogError($"Error processing message: {ex.Message}, event id: {channel} {entry.Id}\r\n{ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ReDispatchPendingAsync(string channel, string group, int count = 10)
|
||||
{
|
||||
var db = _redis.GetDatabase();
|
||||
|
||||
// Step 1: Fetch pending messages using XPending
|
||||
try
|
||||
{
|
||||
var pendingInfo = await db.StreamPendingAsync(channel, group);
|
||||
|
||||
if (pendingInfo.PendingMessageCount == 0)
|
||||
{
|
||||
Console.WriteLine("No pending messages found.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 2: Fetch pending message IDs
|
||||
List<StreamEntry> pendingMessages = new List<StreamEntry>();
|
||||
foreach (var consumer in pendingInfo.Consumers)
|
||||
{
|
||||
var pendingMessageIds = await db.StreamPendingMessagesAsync(channel, group, count: consumer.PendingMessageCount, consumerName: consumer.Name);
|
||||
var messageIds = pendingMessageIds.Select(x => x.MessageId).ToArray();
|
||||
// Step 3: Use XClaim to fetch the actual message
|
||||
var claimedMessages = await db.StreamClaimAsync(channel, group, consumer.Name, minIdleTimeInMs: 60 * 1000, messageIds);
|
||||
pendingMessages.AddRange(claimedMessages);
|
||||
|
||||
await db.StreamAcknowledgeAsync(channel, group, messageIds);
|
||||
}
|
||||
|
||||
// Step 4: Process the messages
|
||||
foreach (var message in pendingMessages)
|
||||
{
|
||||
/*if (message.IsNull)
|
||||
{
|
||||
await db.StreamAcknowledgeAsync(channel, group, [message.Id]);
|
||||
}
|
||||
else
|
||||
{
|
||||
var messageId = await db.StreamAddAsync(channel, "message", message.Values[0].Value);
|
||||
_logger.LogWarning($"ReDispatched message: {channel} {message.Values[0].Value} ({messageId})");
|
||||
await db.StreamDeleteAsync(channel, [messageId]);
|
||||
}*/
|
||||
}
|
||||
}
|
||||
catch (RedisException ex)
|
||||
{
|
||||
Console.WriteLine($"Redis error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task RemoveAsync(string channel, int count = 10)
|
||||
{
|
||||
var db = _redis.GetDatabase();
|
||||
|
||||
var entries = await db.StreamRangeAsync(channel, "-", "+", count: count, messageOrder: Order.Ascending);
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
_logger.LogInformation($"Fetched message: {channel} {entry.Values[0].Value} ({entry.Id})");
|
||||
|
||||
try
|
||||
{
|
||||
await db.StreamDeleteAsync(channel, [entry.Id]);
|
||||
|
||||
_logger.LogWarning($"Deleted message: {channel} {entry.Values[0].Value} ({entry.Id})");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"Error processing message: {ex.Message}, event id: {channel} {entry.Id}\r\n{ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using BotSharp.Abstraction.Infrastructures.Enums;
|
||||
using StackExchange.Redis;
|
||||
|
||||
namespace BotSharp.Core.Infrastructures.Events;
|
||||
|
|
@ -24,10 +25,86 @@ public class RedisSubscriber : IEventSubscriber
|
|||
});
|
||||
}
|
||||
|
||||
public async Task SubscribeAsync(string channel, string group, Func<string, string, Task> received)
|
||||
public async Task SubscribeAsync(string channel, string group, int? port, bool priorityEnabled,
|
||||
Func<string, string, Task> received,
|
||||
CancellationToken? stoppingToken = null)
|
||||
{
|
||||
var db = _redis.GetDatabase();
|
||||
|
||||
if (priorityEnabled)
|
||||
{
|
||||
await CreateConsumerGroup(db, $"{channel}-{EventPriority.Low}", group);
|
||||
await CreateConsumerGroup(db, $"{channel}-{EventPriority.Medium}", group);
|
||||
await CreateConsumerGroup(db, $"{channel}-{EventPriority.High}", group);
|
||||
}
|
||||
else
|
||||
{
|
||||
await CreateConsumerGroup(db, channel, group);
|
||||
}
|
||||
|
||||
var consumer = Environment.MachineName;
|
||||
if (port.HasValue)
|
||||
{
|
||||
consumer += $"-{port}";
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
await Task.Delay(100);
|
||||
|
||||
if (stoppingToken.HasValue && stoppingToken.Value.IsCancellationRequested)
|
||||
{
|
||||
_logger.LogInformation($"Stopping consumer channel & group: [{channel}, {group}]");
|
||||
break;
|
||||
}
|
||||
|
||||
if (priorityEnabled)
|
||||
{
|
||||
if (await HandleGroupMessage(db, $"{channel}-{EventPriority.High}", group, consumer, received) > 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (await HandleGroupMessage(db, $"{channel}-{EventPriority.Medium}", group, consumer, received) > 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
await HandleGroupMessage(db, $"{channel}-{EventPriority.Low}", group, consumer, received);
|
||||
}
|
||||
else
|
||||
{
|
||||
await HandleGroupMessage(db, channel, group, consumer, received);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<int> HandleGroupMessage(IDatabase db, string channel, string group, string consumer, Func<string, string, Task> received)
|
||||
{
|
||||
var entries = await db.StreamReadGroupAsync(channel, group, consumer, count: 1);
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
_logger.LogInformation($"Consumer {Environment.MachineName} received: {channel} {entry.Values[0].Value}");
|
||||
await db.StreamAcknowledgeAsync(channel, group, entry.Id);
|
||||
|
||||
try
|
||||
{
|
||||
await received(channel, entry.Values[0].Value);
|
||||
|
||||
// Optionally delete the message to save space
|
||||
await db.StreamDeleteAsync(channel, [entry.Id]);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"Error processing message: {ex.Message}, event id: {channel} {entry.Id}\r\n{ex}");
|
||||
}
|
||||
}
|
||||
|
||||
return entries.Length;
|
||||
}
|
||||
|
||||
private async Task CreateConsumerGroup(IDatabase db, string channel, string group)
|
||||
{
|
||||
// Create the consumer group if it doesn't exist
|
||||
try
|
||||
{
|
||||
|
|
@ -43,33 +120,5 @@ public class RedisSubscriber : IEventSubscriber
|
|||
_logger.LogError($"Error creating consumer group: '{group}' {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
var entries = await db.StreamReadGroupAsync(channel, group, Environment.MachineName, count: 1);
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
_logger.LogInformation($"Consumer {Environment.MachineName} received: {channel} {entry.Values[0].Value}");
|
||||
|
||||
try
|
||||
{
|
||||
await received(channel, entry.Values[0].Value);
|
||||
|
||||
// Optionally delete the message to save space
|
||||
await db.StreamDeleteAsync(channel, [entry.Id]);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"Error processing message: {ex.Message}, event id: {channel} {entry.Id}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
await db.StreamAcknowledgeAsync(channel, group, entry.Id);
|
||||
}
|
||||
}
|
||||
|
||||
await Task.Delay(Random.Shared.Next(1, 11) * 100);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,17 +23,23 @@ public static class Utilities
|
|||
|
||||
var data = sha256.ComputeHash(Encoding.UTF8.GetBytes(text));
|
||||
var sb = new StringBuilder();
|
||||
foreach(var c in data)
|
||||
foreach (var c in data)
|
||||
{
|
||||
sb.Append(c.ToString("x2"));
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static (string, string) SplitAsTuple(this string str, string sep)
|
||||
public static (string, string, string) SplitAsTuple(this string str, string sep)
|
||||
{
|
||||
var splits = str.Split(sep);
|
||||
return (splits[0], splits[1]);
|
||||
|
||||
if (splits.Length == 2 || string.IsNullOrWhiteSpace(splits[2]))
|
||||
{
|
||||
return (splits[0], splits[1], "CN");
|
||||
}
|
||||
|
||||
return (splits[0], splits[1], splits[2]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -47,4 +53,36 @@ public static class Utilities
|
|||
memcache.Compact(100);
|
||||
}
|
||||
}
|
||||
|
||||
public static string HideMiddleDigits(string input, bool isEmail = false)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
{
|
||||
return input;
|
||||
}
|
||||
|
||||
if (isEmail)
|
||||
{
|
||||
int atIndex = input.IndexOf('@');
|
||||
if (atIndex > 1)
|
||||
{
|
||||
string localPart = input.Substring(0, atIndex);
|
||||
if (localPart.Length > 2)
|
||||
{
|
||||
string maskedLocalPart = $"{localPart[0]}{new string('*', localPart.Length - 2)}{localPart[^1]}";
|
||||
return $"{maskedLocalPart}@{input.Substring(atIndex + 1)}";
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (input.Length > 6)
|
||||
{
|
||||
return $"{input.Substring(0, 3)}{new string('*', input.Length - 6)}{input.Substring(input.Length - 3)}";
|
||||
}
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,9 +11,21 @@ public partial class FileRepository
|
|||
return Users.FirstOrDefault(x => x.Email == email.ToLower());
|
||||
}
|
||||
|
||||
public User? GetUserByPhone(string phone)
|
||||
public User? GetUserByPhone(string phone, string? type = UserType.Client, string regionCode = "CN")
|
||||
{
|
||||
return Users.FirstOrDefault(x => x.Phone == phone);
|
||||
var query = Users.Where(x => x.Phone == phone);
|
||||
|
||||
if (!string.IsNullOrEmpty(type))
|
||||
{
|
||||
query = query.Where(x => x.Type == type);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(regionCode))
|
||||
{
|
||||
query = query.Where(x => x.RegionCode == regionCode);
|
||||
}
|
||||
|
||||
return query.FirstOrDefault();
|
||||
}
|
||||
|
||||
public User? GetAffiliateUserByPhone(string phone)
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ public class UserService : IUserService
|
|||
public async Task<Token?> GetAffiliateToken(string authorization)
|
||||
{
|
||||
var base64 = Encoding.UTF8.GetString(Convert.FromBase64String(authorization));
|
||||
var (id, password) = base64.SplitAsTuple(":");
|
||||
var (id, password, regionCode) = base64.SplitAsTuple(":");
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var record = db.GetAffiliateUserByPhone(id);
|
||||
var isCanLogin = record != null && !record.IsDisabled && record.Type == UserType.Affiliate;
|
||||
|
|
@ -170,9 +170,9 @@ public class UserService : IUserService
|
|||
public async Task<Token?> GetAdminToken(string authorization)
|
||||
{
|
||||
var base64 = Encoding.UTF8.GetString(Convert.FromBase64String(authorization));
|
||||
var (id, password) = base64.SplitAsTuple(":");
|
||||
var (id, password, regionCode) = base64.SplitAsTuple(":");
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var record = db.GetUserByPhone(id,"admin");
|
||||
var record = db.GetUserByPhone(id, type: UserType.Internal);
|
||||
var isCanLogin = record != null && !record.IsDisabled
|
||||
&& record.Type == UserType.Internal && new List<string>
|
||||
{
|
||||
|
|
@ -210,13 +210,13 @@ public class UserService : IUserService
|
|||
public async Task<Token?> GetToken(string authorization)
|
||||
{
|
||||
var base64 = Encoding.UTF8.GetString(Convert.FromBase64String(authorization));
|
||||
var (id, password) = base64.SplitAsTuple(":");
|
||||
var (id, password, regionCode) = base64.SplitAsTuple(":");
|
||||
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var record = id.Contains("@") ? db.GetUserByEmail(id) : db.GetUserByUserName(id);
|
||||
if (record == null)
|
||||
{
|
||||
record = db.GetUserByPhone(id);
|
||||
record = db.GetUserByPhone(id, regionCode: regionCode);
|
||||
}
|
||||
|
||||
if (record != null && record.Type == UserType.Affiliate)
|
||||
|
|
@ -525,7 +525,7 @@ public class UserService : IUserService
|
|||
|
||||
public async Task<bool> VerifyEmailExisting(string email)
|
||||
{
|
||||
if (string.IsNullOrEmpty(email))
|
||||
if (string.IsNullOrWhiteSpace(email))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
|
@ -542,13 +542,13 @@ public class UserService : IUserService
|
|||
|
||||
public async Task<bool> VerifyPhoneExisting(string phone, string regionCode)
|
||||
{
|
||||
if (string.IsNullOrEmpty(phone))
|
||||
if (string.IsNullOrWhiteSpace(phone))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var UserByphone = db.GetUserByPhone(phone, regionCode);
|
||||
var UserByphone = db.GetUserByPhone(phone, regionCode: regionCode);
|
||||
if (UserByphone != null && UserByphone.Verified)
|
||||
{
|
||||
return true;
|
||||
|
|
@ -570,7 +570,7 @@ public class UserService : IUserService
|
|||
|
||||
if (!string.IsNullOrEmpty(user.Phone))
|
||||
{
|
||||
record = db.GetUserByPhone(user.Phone);
|
||||
record = db.GetUserByPhone(user.Phone, regionCode: user.RegionCode);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(user.Email))
|
||||
|
|
@ -745,7 +745,7 @@ public class UserService : IUserService
|
|||
await Task.CompletedTask;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public async Task<bool> RemoveDashboardConversation(string userId, string conversationId)
|
||||
{
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ public class UserCreationModel
|
|||
public string Type { get; set; } = UserType.Client;
|
||||
public string Role { get; set; } = UserRole.User;
|
||||
public string RegionCode { get; set; } = "CN";
|
||||
public string? ReferralCode { get; set; }
|
||||
public User ToUser()
|
||||
{
|
||||
return new User
|
||||
|
|
@ -25,7 +26,8 @@ public class UserCreationModel
|
|||
Password = Password,
|
||||
Role = Role,
|
||||
Type = Type,
|
||||
RegionCode = RegionCode
|
||||
RegionCode = RegionCode,
|
||||
ReferralCode = ReferralCode
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,6 +57,8 @@ public class UserViewModel
|
|||
UserName = user.UserName,
|
||||
FirstName = user.FirstName,
|
||||
LastName = user.LastName,
|
||||
//Email = Utilities.HideMiddleDigits(user.Email, true),
|
||||
//Phone = Utilities.HideMiddleDigits((!string.IsNullOrWhiteSpace(user.Phone) ? user.Phone.Replace("+86", String.Empty) : user.Phone)),
|
||||
Email = user.Email,
|
||||
Phone = !string.IsNullOrWhiteSpace(user.Phone) ? user.Phone.Replace("+86", String.Empty) : user.Phone,
|
||||
Type = user.Type,
|
||||
|
|
|
|||
|
|
@ -11,8 +11,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Aspire.MongoDB.Driver" Version="8.0.1" />
|
||||
<PackageReference Include="MongoDB.Driver" Version="2.28.0" />
|
||||
<PackageReference Include="MongoDB.Driver" Version="3.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -51,6 +51,8 @@ public class UserDocument : MongoBase
|
|||
Verified = Verified,
|
||||
RegionCode = RegionCode,
|
||||
Permissions = Permissions,
|
||||
CreatedTime = CreatedTime,
|
||||
UpdatedTime = UpdatedTime,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -13,7 +13,7 @@ public partial class MongoRepository
|
|||
return user != null ? user.ToUser() : null;
|
||||
}
|
||||
|
||||
public User? GetUserByPhone(string phone, string role = null, string regionCode = "CN")
|
||||
public User? GetUserByPhone(string phone, string type = UserType.Client, string regionCode = "CN")
|
||||
{
|
||||
string phoneSecond = string.Empty;
|
||||
// if phone number length is less than 4, return null
|
||||
|
|
@ -22,11 +22,18 @@ public partial class MongoRepository
|
|||
return null;
|
||||
}
|
||||
|
||||
phoneSecond = phone.StartsWith("+86") ? phone.Replace("+86", "") : $"+86{phone}";
|
||||
if (regionCode == "CN")
|
||||
{
|
||||
phoneSecond = (phone ?? "").StartsWith("+86") ? (phone ?? "").Replace("+86", "") : ($"+86{phone ?? ""}");
|
||||
}
|
||||
else
|
||||
{
|
||||
phoneSecond = (phone ?? "").Substring(regionCode == "US" ? 2 : 3);
|
||||
}
|
||||
|
||||
var user = _dc.Users.AsQueryable().FirstOrDefault(x => (x.Phone == phone || x.Phone == phoneSecond) && x.Type != UserType.Affiliate
|
||||
&& (x.RegionCode == regionCode || string.IsNullOrWhiteSpace(x.RegionCode))
|
||||
&& (role == "admin" ? x.Role == "admin" || x.Role == "root" : true));
|
||||
var user = _dc.Users.AsQueryable().FirstOrDefault(x => (x.Phone == phone || x.Phone == phoneSecond)
|
||||
&& (x.RegionCode == regionCode || string.IsNullOrWhiteSpace(x.RegionCode))
|
||||
&& (x.Type == type));
|
||||
return user != null ? user.ToUser() : null;
|
||||
}
|
||||
|
||||
|
|
@ -245,7 +252,7 @@ public partial class MongoRepository
|
|||
user.AgentActions = agentActions;
|
||||
return user;
|
||||
}
|
||||
|
||||
|
||||
var agentIds = userAgents.Select(x => x.AgentId)?.Distinct().ToList();
|
||||
if (!agentIds.IsNullOrEmpty())
|
||||
{
|
||||
|
|
|
|||
|
|
@ -79,10 +79,102 @@ public partial class PlaywrightWebDriver
|
|||
{
|
||||
await locator.HoverAsync();
|
||||
}
|
||||
else if (action.Action == BroswerActionEnum.DragAndDrop)
|
||||
{
|
||||
// Locate the element to drag
|
||||
var box = await locator.BoundingBoxAsync();
|
||||
|
||||
if (box != null)
|
||||
{
|
||||
// Calculate start position
|
||||
float startX = box.X + box.Width / 2; // Start at the center of the element
|
||||
float startY = box.Y + box.Height / 2;
|
||||
|
||||
// Drag offsets
|
||||
float offsetX = action.Position.X;
|
||||
// Move horizontally
|
||||
if (action.Position.Y == 0)
|
||||
{
|
||||
// Perform drag-and-move
|
||||
// Move mouse to the start position
|
||||
var mouse = page.Mouse;
|
||||
await mouse.MoveAsync(startX, startY);
|
||||
await mouse.DownAsync();
|
||||
|
||||
// Move mouse smoothly in increments
|
||||
var tracks = GetVelocityTrack(offsetX);
|
||||
foreach (var track in tracks)
|
||||
{
|
||||
startX += track;
|
||||
await page.Mouse.MoveAsync(startX, 0, new MouseMoveOptions
|
||||
{
|
||||
Steps = 3
|
||||
});
|
||||
}
|
||||
|
||||
// Release mouse button
|
||||
await mouse.UpAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (action.WaitTime > 0)
|
||||
{
|
||||
await Task.Delay(1000 * action.WaitTime);
|
||||
}
|
||||
}
|
||||
|
||||
public static List<int> GetVelocityTrack(float distance)
|
||||
{
|
||||
// Initialize the track list to store the movement distances
|
||||
List<int> track = new List<int>();
|
||||
|
||||
// Initialize variables
|
||||
float current = 0; // Current position
|
||||
float mid = distance * 4 / 5; // Deceleration threshold
|
||||
float t = 0.2f; // Time interval
|
||||
float v = 1; // Initial velocity
|
||||
|
||||
// Generate the track
|
||||
while (current < distance)
|
||||
{
|
||||
float a; // Acceleration
|
||||
|
||||
// Determine acceleration based on position
|
||||
if (current < mid)
|
||||
{
|
||||
a = 4; // Accelerate
|
||||
}
|
||||
else
|
||||
{
|
||||
a = -3; // Decelerate
|
||||
}
|
||||
|
||||
// Calculate new velocity
|
||||
float v0 = v;
|
||||
v = v0 + a * t;
|
||||
|
||||
// Calculate the movement during this interval
|
||||
float move = v0 * t + 0.5f * a * t * t;
|
||||
|
||||
// Update current position
|
||||
if (current + move > distance)
|
||||
{
|
||||
move = distance - current;
|
||||
track.Add((int)Math.Round(move));
|
||||
break;
|
||||
}
|
||||
|
||||
current += move;
|
||||
|
||||
// Add rounded movement to the track
|
||||
track.Add((int)Math.Round(move));
|
||||
}
|
||||
|
||||
return track;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ public partial class PlaywrightWebDriver
|
|||
var context = await _instance.GetContext(message.ContextId);
|
||||
try
|
||||
{
|
||||
var page = await _instance.NewPage(message, enableResponseCallback: args.EnableResponseCallback,
|
||||
var page = await _instance.NewPage(message, enableResponseCallback: args.EnableResponseCallback,
|
||||
responseInMemory: args.ResponseInMemory,
|
||||
responseContainer: args.ResponseContainer,
|
||||
excludeResponseUrls: args.ExcludeResponseUrls,
|
||||
|
|
@ -18,7 +18,7 @@ public partial class PlaywrightWebDriver
|
|||
|
||||
if (args.OpenNewTab && page != null && page.Url == "about:blank")
|
||||
{
|
||||
page = await _instance.NewPage(message,
|
||||
page = await _instance.NewPage(message,
|
||||
enableResponseCallback: args.EnableResponseCallback,
|
||||
responseInMemory: args.ResponseInMemory,
|
||||
responseContainer: args.ResponseContainer,
|
||||
|
|
@ -28,7 +28,7 @@ public partial class PlaywrightWebDriver
|
|||
|
||||
if (page == null)
|
||||
{
|
||||
page = await _instance.NewPage(message,
|
||||
page = await _instance.NewPage(message,
|
||||
enableResponseCallback: args.EnableResponseCallback,
|
||||
responseInMemory: args.ResponseInMemory,
|
||||
responseContainer: args.ResponseContainer,
|
||||
|
|
@ -47,7 +47,7 @@ public partial class PlaywrightWebDriver
|
|||
if (args.Selectors != null)
|
||||
{
|
||||
// 使用传入的选择器列表进行并行等待
|
||||
var tasks =args.Selectors.Select(selector =>
|
||||
var tasks = args.Selectors.Select(selector =>
|
||||
page.WaitForSelectorAsync(selector, new PageWaitForSelectorOptions
|
||||
{
|
||||
Timeout = args.Timeout > 0 ? args.Timeout : 30000
|
||||
|
|
@ -92,7 +92,7 @@ public partial class PlaywrightWebDriver
|
|||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
{
|
||||
result.Message = response.StatusText;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ public partial class PlaywrightWebDriver
|
|||
public async Task<BrowserActionResult> ScrollPage(MessageInfo message, PageActionArgs args)
|
||||
{
|
||||
var result = new BrowserActionResult();
|
||||
await _instance.Wait(message.ContextId);
|
||||
var waitTime = args.WaitTime > 0 ? args.WaitTime : 10;
|
||||
await _instance.Wait(message.ContextId, waitTime, args.WaitForNetworkIdle);
|
||||
|
||||
var page = _instance.GetPage(message.ContextId);
|
||||
|
||||
|
|
@ -31,7 +32,7 @@ public partial class PlaywrightWebDriver
|
|||
int scrollY = await page.EvaluateAsync<int>("document.body.scrollHeight");
|
||||
|
||||
// Scroll to the bottom
|
||||
await page.Mouse.WheelAsync(0, -scrollY);
|
||||
await page.Mouse.WheelAsync(0, scrollY);
|
||||
}
|
||||
else if (args.Direction == "top")
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue