Compare commits

...

21 Commits

Author SHA1 Message Date
yanghongwei ad16758252 细节调整 2025-09-20 17:17:51 +08:00
yanghongwei 21a381cb9e bug 调整 2025-09-20 16:28:08 +08:00
yanghongwei 09785d909a 机型会发生变化,这里只取第一条 2025-08-24 15:22:56 +08:00
yanghongwei 23a128ef0f 预警列表调整 2025-08-23 01:38:27 +08:00
yanghongwei 8c4fca828d 细节调整 2025-08-20 22:37:59 +08:00
yanghongwei f1ebaa68c7 0换成1 2025-08-16 15:31:10 +08:00
yanghongwei e13559115a 分页调整 2025-07-21 17:36:04 +08:00
yanghongwei 8bd8f38962 白名单调整 2025-07-09 20:52:45 +08:00
yanghongwei 6c31a6a72d 缓存更新 2025-06-30 09:07:06 +08:00
yanghongwei 1d9da8d6e6 统计数据调整 2025-06-29 20:55:31 +08:00
yanghongwei 64853e5ffe 细节调整 2025-06-26 23:34:41 +08:00
yanghongwei 70ebc1868d 细节调整 2025-06-26 01:03:05 +08:00
yanghongwei af98ca3e72 细节调整 2025-06-26 00:40:06 +08:00
yanghongwei c4aff6eac0 websocket返回白名单id 2025-06-26 00:10:27 +08:00
yanghongwei 4762ea002b 细节调整,long 转string 2025-06-22 18:11:25 +08:00
yanghongwei c8600c6bd7 登陆之后返回防区 2025-06-18 21:07:09 +08:00
yanghongwei 41df54cebc 细节调整 2025-06-16 17:15:19 +08:00
yanghongwei a32912a460 接收移动端坐标上报 2025-06-16 17:09:38 +08:00
yanghongwei bca6baef65 用户列表防区管理 2025-06-15 18:40:58 +08:00
yanghongwei b21479beed 阵地筛选 2025-06-14 17:36:57 +08:00
yanghongwei 629075f38b 权限 2025-06-14 13:17:14 +08:00
14 changed files with 332 additions and 79 deletions

View File

@ -56,5 +56,13 @@
{
return $"white_list{sn}";
}
/// <summary>
/// 所有用户位置
/// </summary>
/// <returns></returns>
public static string UserLocation(long userId)
{
return $"user_locationbyid_{userId}";
}
}
}

View File

@ -10,6 +10,7 @@ namespace LY.App.Common.Redis
public class RedisService
{
private readonly IDatabase _db;
private readonly IServer _redis;
/// <summary>
/// 构造函数
@ -38,44 +39,82 @@ namespace LY.App.Common.Redis
string jsonData = await _db.StringGetAsync(key);
return jsonData is not null ? JsonSerializer.Deserialize<T>(jsonData) : default;
}
/// <summary>
/// 删除 Key
/// 模糊 查询所有 Key
/// </summary>
public async Task<bool> DeleteAsync(string key)
/// <param name="pattern"></param>
/// <returns></returns>
public Task<List<RedisKey>> GetAllKeysAsync(string pattern)
{
return await _db.KeyDeleteAsync(key);
}
/// <summary>
/// 检查 Key 是否存在
/// </summary>
public async Task<bool> ExistsAsync(string key)
{
return await _db.KeyExistsAsync(key);
}
/// <summary>
/// 获取数据,如果不存在则从数据源获取并存入 Redis
/// </summary>
/// <typeparam name="T">数据类型</typeparam>
/// <param name="key">Redis Key</param>
/// <param name="factory">数据源方法</param>
/// <param name="expiry">可选的过期时间</param>
/// <returns>获取到的值</returns>
public async Task<T?> GetOrSetAsync<T>(string key, Func<Task<T>> factory, TimeSpan? expiry = null)
{
var value = await GetAsync<T>(key);
if (value is not null)
var redis = _db.Multiplexer;
var keys = new List<RedisKey>();
foreach (var endPoint in redis.GetEndPoints())
{
var server = redis.GetServer(endPoint);
if (!server.IsConnected)
continue;
// 使用 SCAN 获取匹配的 key避免 KEYS 阻塞
var scanKeys = server.Keys(pattern: pattern, pageSize: 1000);
keys.AddRange(scanKeys);
}
return Task.FromResult(keys);
//var result = new Dictionary<string, string>();
//if (keys.Count > 0)
//{
// // 一次批量获取 value
// var values = await _db.StringGetAsync(keys.ToArray());
// for (int i = 0; i < keys.Count; i++)
// {
// if (values[i].HasValue)
// {
// result[keys[i]] = values[i];
// }
// }
//}
//return result;
}
/// <summary>
/// 删除 Key
/// </summary>
public async Task<bool> DeleteAsync(string key)
{
return await _db.KeyDeleteAsync(key);
}
/// <summary>
/// 检查 Key 是否存在
/// </summary>
public async Task<bool> ExistsAsync(string key)
{
return await _db.KeyExistsAsync(key);
}
/// <summary>
/// 获取数据,如果不存在则从数据源获取并存入 Redis
/// </summary>
/// <typeparam name="T">数据类型</typeparam>
/// <param name="key">Redis Key</param>
/// <param name="factory">数据源方法</param>
/// <param name="expiry">可选的过期时间</param>
/// <returns>获取到的值</returns>
public async Task<T?> GetOrSetAsync<T>(string key, Func<Task<T>> factory, TimeSpan? expiry = null)
{
var value = await GetAsync<T>(key);
if (value is not null)
{
return value;
}
value = await factory();
if (value is not null)
{
await SetAsync(key, value, expiry);
}
return value;
}
value = await factory();
if (value is not null)
{
await SetAsync(key, value, expiry);
}
return value;
}
}
}

View File

@ -25,7 +25,18 @@ namespace LY.App.Controllers
[HttpGet("list")]
public async Task<IActionResult> List([FromQuery] AlarmReq input)
{
var result = await _alarmService.GetPage(input);
var result = await _alarmService.CreateHistoryPage(input);
return Ok(result);
}
/// <summary>
///列表快速分页
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpGet("list1")]
public async Task<IActionResult> List1([FromQuery] AlarmReq input)
{
var result = await _alarmService.CreateHistoryPage(input);
return Ok(result);
}

View File

@ -4,6 +4,8 @@ using LY.App.Model;
using LY.App.Service;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using StackExchange.Redis;
using System.Reactive.Joins;
namespace LY.App.Controllers
{
@ -48,17 +50,58 @@ namespace LY.App.Controllers
var positions = await _positionService.Index();
var alarmCount = await _redisService.GetOrSetAsync(RedisKeyList.index_data(),
async () => await _alarmService.IndexCount(),
TimeSpan.FromDays(1));
TimeSpan.FromSeconds(GetLeftTime()));
var weather = await _redisService.GetOrSetAsync(RedisKeyList.Index_Weather,
async () => await _weatherService.GetWeather(),
TimeSpan.FromHours(3));
TimeSpan.FromHours(3));
var data = await GetUserLocation();
result.data = new
{
positions,
alarmCount,
weather
weather,
userLocation = data
};
return Ok(result);
}
private int GetLeftTime() {
DateTime now = DateTime.Now;
// 今天的结束时间 23:59:59
DateTime endOfDay = new DateTime(now.Year, now.Month, now.Day, 23, 59, 59);
// 相差时间
TimeSpan remaining = endOfDay - now;
// 剩余秒数
return (int)remaining.TotalSeconds;
}
private async Task<List<SyncLocation>> GetUserLocation()
{
var keys = await _redisService.GetAllKeysAsync("user_locationbyid_*");
List<SyncLocation> result = new List<SyncLocation>();
if (keys.Count > 0)
{
foreach (var item in keys)
{
result.Add(await _redisService.GetAsync<SyncLocation>(item));
}
}
return result;
}
/// <summary>
/// 同步移动端位置信息
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost("syncLocation")]
public async Task<IActionResult> SyncLocation(SyncLocation input)
{
string key = RedisKeyList.UserLocation(input.userId);
await _redisService.SetAsync(key, new SyncLocation() { userId = input.userId, lat = input.lat, lon = input.lon }, TimeSpan.FromMinutes(1));
return Ok(new ApiResult());
}
}
}

View File

@ -1,4 +1,5 @@
using LY.App.Model;
using GraphQL;
using LY.App.Model;
using LY.App.Service;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
@ -23,7 +24,18 @@ namespace LY.App.Controllers
[HttpGet("list")]
public async Task<IActionResult> Get([FromQuery] PositionQueryInput input)
{
var positions =await _positionService.GetList(input);
var positions = await _positionService.GetList(input);
return Ok(positions);
}
/// <summary>
/// 登陆 后获取当前防区
/// </summary>
/// <param name="ids"></param>
/// <returns></returns>
[HttpPost("Details")]
public async Task<IActionResult> detail(IEnumerable<long> ids)
{
var positions = await _positionService.Detail(ids);
return Ok(positions);
}
/// <summary>

View File

@ -51,6 +51,12 @@ namespace LY.App.Model
[SugarColumn(ColumnName = "is_whitelist", ColumnDescription = "是否白名单")]
public bool IsWhitelist { get; set; }
/// <summary>
/// 白名单id
/// </summary>
[JsonConverter(typeof(ValueToStringConverter))]
[SugarColumn(IsIgnore = true)]
public long WhiteListId { get; set; }
/// <summary>
/// ,#东向速度
/// </summary>
public double speed_E { get; set; }

View File

@ -21,6 +21,7 @@ namespace LY.App.Model
[JsonConverter(typeof(ValueToStringConverter))]
public long positionId { get; set; }
public string positionName { get; set; }
public int alarmLevel { get; set; }
//public long position_id { get; set; }
}

View File

@ -37,6 +37,7 @@
/// 每页条数
/// </summary>
public int pageSize { get; set; } = 10;
public long? positionId { get; set; }
}
}

20
Model/SyncLocation.cs Normal file
View File

@ -0,0 +1,20 @@
namespace LY.App.Model
{
/// <summary>
/// 移动端同步坐标信息
/// </summary>
public class SyncLocation
{
public long userId { get; set; }
public double lon { get; set; }
public double lat { get; set; }
}
/// <summary>
/// web 端同步坐标信息
/// </summary>
public class Location
{
public double lon { get; set; }
public double lat { get; set; }
}
}

View File

@ -41,6 +41,16 @@ namespace LY.App.Model
/// 是否管理员,如果不是管理员,不可操作
/// </summary>
public bool IsAdmin { get; set; }
/// <summary>
/// 用户关键防区数组
/// </summary>
[SugarColumn(ColumnName = "position_id", ColumnDescription = "阵地ids", IsJson = true)]
public List<string> positionId { get; set; }
[SugarColumn(IsIgnore = true)]
/// <summary>
/// 用户关键防区名称数组
/// </summary>
public List<string> positionName { get; set; }
}
public class AddUser
{
@ -56,8 +66,9 @@ namespace LY.App.Model
/// 是否管理员,如果不是管理员,不可操作
/// </summary>
public bool IsAdmin { get; set; }
public List<long> positionId { get; set; }
}
public class UpdateUser:AddUser
public class UpdateUser : AddUser
{
public long Id { get; set; }
}

View File

@ -7,6 +7,7 @@ using Mapster;
using NetTopologySuite.Geometries;
using NetTopologySuite.IO;
using SqlSugar;
using System.ComponentModel.DataAnnotations;
namespace LY.App.Service
{
@ -54,8 +55,15 @@ namespace LY.App.Service
entity = entity.Where(s => checkDistance(s.drone_lat, s.drone_lon, deviceinfo.Lat, deviceinfo.Lon) == true).ToList();
if (O4entity.Any())
{
entity.AddRange(O4entity);
foreach (var item in O4entity)
{
if (!entity.Exists(s => s.serial_number == item.serial_number))
{
entity.Add(item);
}
}
}
entity = entity.Distinct().ToList();
if (entity.Any())
{
foreach (var item in entity)
@ -67,7 +75,9 @@ namespace LY.App.Service
item.PostionName = deviceinfo.PositionName;
item.Time = input.time;
item.distance = GisHelper.HaversineDistance(item.drone_lat, item.drone_lon, item.app_lat, item.app_lon);
item.IsWhitelist = await Iswhitlist(item.serial_number, item.drone_lat, item.drone_lon);
var temp = await Iswhitlist(item.serial_number, item.drone_lat, item.drone_lon);
item.IsWhitelist = temp.Item1;
item.WhiteListId = temp.Item2;
item.alarmLevel = item.IsWhitelist == true ? 0 : await GetAlarmLevel(deviceinfo.PositionId, item.drone_lon, item.drone_lat);
item.centerdistance = await GetCenterDistance(item.drone_lat, item.drone_lon, item.positionId);
}
@ -131,41 +141,24 @@ namespace LY.App.Service
/// <param name="lat"></param>
/// <param name="lon"></param>
/// <returns></returns>
private async Task<bool> Iswhitlist(string serial_number, double lat, double lon)
private async Task<Tuple<bool, long>> Iswhitlist(string serial_number, double lat, double lon)
{
string key = RedisKeyList.white_list(serial_number);
if (!await _redisService.ExistsAsync(key))
return false;
else
if (await _redisService.ExistsAsync(key))
{
var entity = await _redisService.GetAsync<Whitelist>(key);
//判断 是否在防区内
if (entity.positionId.Any())
//判断时间是否在区在
if (entity.allDay)
{
foreach (var item in entity.positionId)
{
var region = await _redisService.GetAsync<PositionInfo>(RedisKeyList.PositioinRegion(item));
if (region != null)
{
region.SetRegionJson();
if (IsPointInGeoJson(lat, lon, region.RegionJson))
{
//判断时间是否在区在
if (entity.allDay)
{
return true;
}
else
{
return entity.startTime <= DateTime.Now && DateTime.Now <= entity.endTime;
}
}
}
}
return new Tuple<bool, long>(true, entity.Id);
}
else
{
var has = entity.startTime <= DateTime.Now && DateTime.Now <= entity.endTime;
return new Tuple<bool, long>(has, has ? entity.Id : 0);
}
}
return false;
return new Tuple<bool, long>(false, 0); ;
}
static bool IsPointInGeoJson(double latitude, double longitude, string geoJson)
@ -368,29 +361,111 @@ namespace LY.App.Service
{
RefAsync<int> total = 0;
var items = await _db.Queryable<Alarm>().SplitTable()
.WhereIF(input.Frequency.HasValue, st => st.freq == input.Frequency.Value)
.WhereIF(input.positionId.HasValue, st => st.positionId == input.positionId.Value)
// .WhereIF(input.Frequency.HasValue, st => st.freq == input.Frequency.Value)
.WhereIF(!string.IsNullOrEmpty(input.sn), s => s.serial_number.Contains(input.sn))
.WhereIF(!string.IsNullOrEmpty(input.model), st => st.device_type == input.model)
.WhereIF(input.strartDate.HasValue, st => st.CreateTime >= input.strartDate.Value)
.WhereIF(input.endDate.HasValue, st => st.CreateTime <= input.endDate.Value.AddDays(1))
.OrderBy(s => s.BatchId, OrderByType.Desc)
.GroupBy(s => new { s.BatchId, s.serial_number, s.device_type, s.positionId, s.PostionName, s.freq })
.GroupBy(s => new { s.BatchId, s.serial_number, s.device_type })
.Select(st => new AlarmRepDto
{
batchId = st.BatchId.ToString(),
startTime = SqlFunc.AggregateMin(st.CreateTime),
endTime = SqlFunc.AggregateMax(st.CreateTime),
sn = st.serial_number,
Frequency = st.freq,
Frequency = SqlFunc.AggregateMax(st.freq),
duration = (SqlFunc.AggregateMax(st.CreateTime) - SqlFunc.AggregateMin(st.CreateTime)).TotalSeconds,
model = st.device_type,
positionId = st.positionId,
positionName = st.PostionName
positionId = SqlFunc.AggregateMax(st.positionId),
}).MergeTable()//合并查询
.ToPageListAsync(input.pageNum, input.pageSize, total);
var ids = items.Select(s => s.positionId).Distinct().ToList();
var positionNames = await _db.Queryable<PositionInfo>()
.Where(s => ids.Contains(s.Id))
.Select(s => new { s.Id, s.Name })
.ToListAsync();
foreach (var item in items)
{
var positionName = positionNames.FirstOrDefault(s => s.Id == item.positionId)?.Name;
if (!string.IsNullOrEmpty(positionName))
{
item.positionName = positionName;
}
}
return Tuple.Create(total.Value, items);
}
/// <summary>
/// 快速分页
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public async Task<ApiResult> CreateHistoryPage(AlarmReq input)
{
var tables = _db.SplitHelper<Alarm>().GetTables();
string coutsql = @" SELECT COUNT(*)
FROM (
SELECT batch_id
FROM (
{0}
) AS unionTable
GROUP BY batch_id
) AS CountTable;";
string page = @" SELECT *
FROM (
SELECT batch_id
FROM (
{0}
) AS unionTable
GROUP BY batch_id ORDER BY batch_id desc LIMIT {1},{2}
) AS CountTable ";
string tablesql = string.Join(" UNION ALL ", tables.Select(item => $"SELECT batch_id FROM {item.TableName} GROUP BY batch_id"));
if (!string.IsNullOrEmpty(input.sn))
{
tablesql = string.Join(" UNION ALL ", tables.Select(item => $"SELECT batch_id FROM {item.TableName} where serial_number like '%{input.sn}%' GROUP BY batch_id"));
}
var pageitem = await _db.Ado.SqlQueryAsync<int, long>(string.Format(coutsql, tablesql) + string.Format(page, tablesql, (input.pageNum - 1) * input.pageSize, input.pageSize));
var temp = await _db.Queryable<Alarm>()
.Where(s => pageitem.Item2.Contains(s.BatchId)).SplitTable().Select(s => new { s.BatchId, s.freq, s.Id, s.positionId, s.PostionName, s.alarmLevel, s.device_type }).ToListAsync();
var query = await _db.Queryable<Alarm>()
.Where(s => pageitem.Item2.Contains(s.BatchId)).SplitTable()
.GroupBy(s => new { s.BatchId, s.serial_number })
.Select(st => new AlarmRepDto
{
batchId = st.BatchId.ToString(),
startTime = SqlFunc.AggregateMin(st.CreateTime),
endTime = SqlFunc.AggregateMax(st.CreateTime),
sn = st.serial_number,
Frequency = 0,
duration = (SqlFunc.AggregateMax(st.CreateTime) - SqlFunc.AggregateMin(st.CreateTime)).TotalSeconds,
IsWhitelist = SqlFunc.AggregateMax(st.IsWhitelist),
}).OrderByDescending(s => s.batchId).ToListAsync();
query.ForEach(s =>
{
s.duration = s.duration == 0 ? 1 : s.duration;
s.Frequency = temp.Where(m => m.BatchId == long.Parse(s.batchId)).OrderByDescending(o => o.Id).FirstOrDefault().freq;
s.positionId = temp.Where(m => m.BatchId == long.Parse(s.batchId)).OrderByDescending(o => o.Id).FirstOrDefault().positionId;
s.positionName = temp.Where(m => m.BatchId == long.Parse(s.batchId)).OrderByDescending(o => o.Id).FirstOrDefault().PostionName;
s.alarmLevel = temp.Where(m => m.BatchId == long.Parse(s.batchId)).Any(o => o.alarmLevel == 1) ? 1 : 0;
s.model = temp.Where(m => m.BatchId == long.Parse(s.batchId)).OrderByDescending(o => o.Id).FirstOrDefault().device_type;
});
return new ApiResult()
{
code = 0,
data = new
{
total = pageitem.Item1.First(),
items = query
}
};
}
/// <summary>
/// 报表统计

View File

@ -109,11 +109,27 @@ namespace LY.App.Service
var entity = await _db.Queryable<PositionInfo>().FirstAsync(a => a.Id == id && a.IsDeleted != true);
if (entity != null)
{
entity.SetRegionJson();
return new ApiResult() { data = entity };
}
return new ApiResult(false, "未找到要获取的对象");
}
/// <summary>
/// Details
/// </summary>
/// <param name="ids"></param>
/// <returns></returns>
public async Task<ApiResult> Detail(IEnumerable<long> ids)
{
var entity = await _db.Queryable<PositionInfo>()
.Where(a => ids.Contains(a.Id) && a.IsDeleted != true).ToListAsync();
entity.ForEach(a =>
{
a.SetRegionJson();
});
return new ApiResult() { data = entity };
}
/// <summary>
/// 获取列表
/// </summary>
/// <param name="input"></param>

View File

@ -142,15 +142,17 @@ namespace LY.App.Service
//加入redis
await _redisService.SetAsync<string>(RedisKeyList.TokenUser(input.username),
token, TimeSpan.FromSeconds(60 * 60 * 24 * 7));
var positionIds = entity.positionId?.Select(s => s.ToString()).ToList();
return new ApiResult()
{
code = 1,
data = new
{
token,
expires = DateTime.UtcNow.AddSeconds(60*60*24*7),
expires = DateTime.UtcNow.AddSeconds(60 * 60 * 24 * 7),
isAdmin = entity.IsAdmin,
userid = entity.Id.ToString()
userid = entity.Id.ToString(),
positionIds
}
};
}
@ -206,6 +208,13 @@ namespace LY.App.Service
.WhereIF(!string.IsNullOrEmpty(key), s => s.Name.Contains(key))
.OrderBy(s => s.Id)
.ToPageListAsync(pageNum, pageSize, total);
query.ForEach(s =>
{
if (s.positionId is not null && s.positionId.Any())
{
s.positionName = _db.Queryable<PositionInfo>().Where(p => s.positionId.Contains(p.Id.ToString())).Select(p => p.Name).ToList();
}
});
return new
{
total = total.Value,
@ -242,7 +251,8 @@ namespace LY.App.Service
var entity = input.Adapt<UserEntity>();
await _db.Updateable(entity).UpdateColumns(it => new
{
it.IsAdmin
it.IsAdmin,
it.positionId
}).ExecuteCommandAsync();
return null;
}

View File

@ -8,7 +8,7 @@
"log2db": true, //
"AllowedHosts": "*",
"ConnectionStrings": {
"DefaultConnection": "server=114.66.57.139;port=13306;database=lyapp;user=root;password=dklymysql;Pooling=true;"
"DefaultConnection": "server=110.42.35.89;port=13306;database=lyapp;user=root;password=dklymysql;Pooling=true;"
},
"Token": {
"SecretKey": "HWLSNPM+OhlFe4wwEV/teSWsxGjrWbxKnHonxW5Z+mFlQq3zonv5",