常驻后台的数据导出服务/常驻后台的数据导出服务.csproj
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
</ItemGroup>
<ItemGroup>
<Content Update="appsettings.Development.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
</ItemGroup>
</Project>
常驻后台的数据导出服务/SeerSocket.cs
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
namespace Seer.SeerSocket;
public struct SeerMessageHead
{
public byte sync; // 0x5A
public byte version; // 0x01
public ushort number; // 0-65536
public uint length; // 数据区长度,json序列化数据长度
public ushort type; // 报文的类型
#pragma warning disable 0414 //禁用警告
#pragma warning disable 0169 //禁用警告
private readonly byte ref0; //保留
private readonly byte ref1; //保留
private readonly byte ref2; //保留
private readonly byte ref3; //保留
private readonly byte ref4; //保留
private readonly byte ref5; //保留
#pragma warning restore 0414 //恢复警告
#pragma warning restore 0169 //恢复警告
public SeerMessageHead()
{
this.sync = 0x5A;
this.version = 0x01;
this.number = 0x0001;
}
public SeerMessageHead(ushort type)
{
this.type = type;
}
public readonly byte[] ToBytes()
{
int size = Marshal.SizeOf(this);
byte[] bytes = new byte[size];
IntPtr ptr = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(this, ptr, true);
Marshal.Copy(ptr, bytes, 0, size);
Marshal.FreeHGlobal(ptr);
if (BitConverter.IsLittleEndian)
{
Array.Reverse(bytes, 2, 2);
Array.Reverse(bytes, 4, 4);
Array.Reverse(bytes, 8, 2);
}
return bytes;
}
};
public class SeerSocket
{
public string ipAddress;
private readonly Dictionary<int, TcpClient> clients;
public SeerSocket(string ipAddress, bool onlyRead = true)
{
this.ipAddress = ipAddress;
this.clients = new Dictionary<int, TcpClient>
{
{ 19204, new TcpClient(this.ipAddress, 19204) }
};
if (onlyRead) return;
this.clients.Add(19205, new TcpClient(this.ipAddress, 19205));
this.clients.Add(19206, new TcpClient(this.ipAddress, 19206));
this.clients.Add(19207, new TcpClient(this.ipAddress, 19207));
this.clients.Add(19210, new TcpClient(this.ipAddress, 19210));
}
// 使用键来访问特定的TcpClient对象
private TcpClient GetClient(int port)
{
return clients[port];
}
public string Send(ushort type, string msg = "", int port = 19204)
{
var msgBody = NormalStrToHexByte(msg);
var msgHead = CreateMsgHead(type, (uint)msgBody.Length);
var response = this.SendRaw(msgHead, msgBody, port);
return response;
}
private string SendRaw(byte[] msgHead, byte[] msgBody, int port)
{
try
{
TcpClient client = this.GetClient(port);
if (!client.Connected)
{
this.clients[port] = new TcpClient(ipAddress, port);
Console.WriteLine($"{ipAddress}:{port}:断开连接");
return "fail";
}
NetworkStream serverStream = client.GetStream();
serverStream.Write(msgHead, 0, msgHead.Length);
serverStream.Write(msgBody, 0, msgBody.Length);
serverStream.Flush();
byte[] inStream = new byte[16];
while (16 != serverStream.Read(inStream, 0, 16))
{
Thread.Sleep(20);
}
var recv_head = BytesToStructure<SeerMessageHead>(inStream);
byte[] recvbyte = BitConverter.GetBytes(recv_head.length);
Array.Reverse(recvbyte);
// 获取数据区的长度
var dsize = BitConverter.ToUInt32(recvbyte, 0);
const int bufferSize = 512;
List<byte> datalist = new List<byte>();
int count = 0;
while (true)
{
byte[] buffer = new byte[bufferSize];
int readSize = serverStream.Read(buffer, 0, bufferSize);
count += readSize;
datalist.AddRange(buffer);
if (count == dsize)
{
break;
}
}
// var content = BitConverter.ToString(SeerMessageHeadToBytes(recv_head)).Replace("-", " ");
// Console.WriteLine(content);
string resMsg = System.Text.Encoding.UTF8.GetString(datalist.ToArray());
return resMsg;
}
catch (SocketException)
{
return "fail";
}
catch (IOException)
{
return "fail";
}
}
public void CloseAll()
{
foreach (var client in this.clients.Values)
{
client.Close();
}
}
private T BytesToStructure<T>(byte[] bytesBuffer)
{
if (bytesBuffer.Length < Marshal.SizeOf(typeof(T)))
{
throw new ArgumentException("size error");
}
nint bufferHandler = Marshal.AllocHGlobal(bytesBuffer.Length);
for (int index = 0; index < bytesBuffer.Length; index++)
{
Marshal.WriteByte(bufferHandler, index, bytesBuffer[index]);
}
// T? structObject = (T)Marshal.PtrToStructure(bufferHandler, typeof(T));
T? structObject = Marshal.PtrToStructure<T>(bufferHandler);
Marshal.FreeHGlobal(bufferHandler);
if (structObject == null)
{
throw new InvalidOperationException("Failed to convert bytes to structure.");
}
return structObject!;
}
private byte[] NormalStrToHexByte(string str)
{
byte[] result = new byte[str.Length];
byte[] buffer = System.Text.Encoding.UTF8.GetBytes(str);
for (int i = 0; i < buffer.Length; i++)
{
result[i] = Convert.ToByte(buffer[i].ToString("X2"), 16);
}
return result;
}
private byte[] CreateMsgHead(ushort type, uint msgLen)
{
var msgHead = new SeerMessageHead
{
type = type,
length = msgLen
};
return msgHead.ToBytes();
}
public static void PrintBytes(byte[] bytes)
{
foreach (byte b in bytes)
{
Console.Write("{0:X2} ", b);
}
}
}
常驻后台的数据导出服务/ExplortStatisticBgService.cs
using System.Text;
using System.Text.Json;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Seer.SeerSocket;
public class ExplortStatisticBgService : BackgroundService
{
private readonly SqliteDbContext ctx;
private readonly ILogger<ExplortStatisticBgService> logger;
private readonly IServiceScope serviceScope;
private readonly SeerSocket seer;
public ExplortStatisticBgService(IServiceScopeFactory scopeFactory)
{
this.serviceScope = scopeFactory.CreateScope();
var sp = serviceScope.ServiceProvider;
this.ctx = sp.GetRequiredService<SqliteDbContext>();
this.logger = sp.GetRequiredService<ILogger<ExplortStatisticBgService>>();
this.seer = sp.GetRequiredService<SeerSocket>();
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
await DoExecuteAsync();
await Task.Delay(2000);
}
catch (Exception ex)
{
logger.LogError(ex, "获取用户统计数据失败");
await Task.Delay(1000);
}
}
}
private async Task DoExecuteAsync()
{
var msg = seer.Send(0x0410, """{"motor_names": ["motor1"]}""", 19204);
System.Console.WriteLine(msg);
// 文件路径
string filePath = "/Users/song/Code/Dotnet.Seer/常驻后台的数据导出服务/Db/all1.json";
// 使用UTF-8编码写入文件
using (FileStream stream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.ReadWrite))
{
using (StreamWriter writer = new StreamWriter(stream, Encoding.UTF8))
{
writer.Write(msg);
}
}
// JObject jsonObject = JObject.Parse(File.ReadAllText(filePath));
// Console.WriteLine(jsonObject.ToString());
// var users = await this.ctx.Users.ToListAsync();
// foreach(var user in users){
// Console.WriteLine($"{DateTime.Now} : User : {user.NickName}");
// break;
// }
}
public override void Dispose()
{
base.Dispose();
serviceScope.Dispose();
}
}
常驻后台的数据导出服务/Request.http
# Request.http
```sh
@hostname = localhost
@port = 5015
@host = http://{{hostname}}:{{port}}
### GET
GET {{host}}/user HTTP/1.1
### post
POST {{host}}/user HTTP/1.1
content-type: application/json
{
"nickName": "Bruce"
}
### GET 中国
GET {{host}}/WeatherForecast/中国 HTTP/1.1
### GET 新西兰
GET {{host}}/WeatherForecast/新西兰 HTTP/1.1
# `常驻后台的数据导出服务/User.cs`
```cs
public record User
{
public Guid guid { get; set; }
public string NickName { get; set; }
}
常驻后台的数据导出服务/appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"Default": "Data Source=./Db/test.db;",
"AgvIpAddress": "127.0.0.1"
},
"Agv":{
"AgvIp": "127.0.0.1"
}
}
常驻后台的数据导出服务/UserContracts.cs
public record UserRequest(string NickName);
常驻后台的数据导出服务/UserConfig.cs
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
class UserConfig : IEntityTypeConfiguration<User>
{
public void Configure(EntityTypeBuilder<User> builder)
{
builder.ToTable("T_Users");
builder.HasKey(u => u.guid);
}
}
常驻后台的数据导出服务/MyDesignTimeDbContextFactory.cs
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
class MyDesignTimeDbContextFactory : IDesignTimeDbContextFactory<SqliteDbContext>
{
public SqliteDbContext CreateDbContext(string[] args)
{
DbContextOptionsBuilder<SqliteDbContext> builder = new();
// string connStr = Environment.GetEnvironmentVariable("ConnectionStrings:Default");
// builder.UseSqlServer(connStr);
string connStr = "Data Source=Db/test.db";
builder.UseSqlite(connStr);
return new SqliteDbContext(builder.Options);
}
}
常驻后台的数据导出服务/appsettings.Development.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
常驻后台的数据导出服务/SqliteDbContext.cs
using Microsoft.EntityFrameworkCore;
public class SqliteDbContext : DbContext
{
public DbSet<User> Users { get; set; }
public SqliteDbContext(DbContextOptions<SqliteDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);
}
}
常驻后台的数据导出服务/Program.cs
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Seer.SeerSocket;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddSingleton<SeerSocket>(sp=>new SeerSocket("127.0.0.1",true));
IServiceCollection services = builder.Services;
services.AddHostedService<ExplortStatisticBgService>();
services.AddDbContext<SqliteDbContext>(opt => {
string connStr = builder.Configuration.GetConnectionString("Default");
opt.UseSqlite(connStr);
});
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
常驻后台的数据导出服务/Db/all1.json
{"motor_names": ["motor1"]}�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������
常驻后台的数据导出服务/Controllers/UserController.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Seer.SeerSocket;
namespace 常驻后台的数据导出服务.Controllers;
[ApiController]
[Route("[controller]")]
public class UserController : ControllerBase
{
private readonly ILogger<UserController> logger;
private readonly SqliteDbContext dbContext;
public UserController(ILogger<UserController> logger, SqliteDbContext dbContext,SeerSocket seer)
{
this.logger = logger;
this.dbContext = dbContext;
}
[HttpGet]
public async Task<IEnumerable<User>> Get()
{
return await dbContext.Users.ToListAsync();
}
[HttpPost]
public async Task Save(UserRequest user)
{
dbContext.Users.Add(new User { NickName = user.NickName });
await dbContext.SaveChangesAsync();
}
}