diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..3729ff0
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,25 @@
+**/.classpath
+**/.dockerignore
+**/.env
+**/.git
+**/.gitignore
+**/.project
+**/.settings
+**/.toolstarget
+**/.vs
+**/.vscode
+**/*.*proj.user
+**/*.dbmdl
+**/*.jfm
+**/azds.yaml
+**/bin
+**/charts
+**/docker-compose*
+**/Dockerfile*
+**/node_modules
+**/npm-debug.log
+**/obj
+**/secrets.dev.yaml
+**/values.dev.yaml
+LICENSE
+README.md
\ No newline at end of file
diff --git a/Common/CoordConvert.cs b/Common/CoordConvert.cs
new file mode 100644
index 0000000..979decb
--- /dev/null
+++ b/Common/CoordConvert.cs
@@ -0,0 +1,57 @@
+using NetTopologySuite.Geometries;
+
+namespace LY.App.Common
+{
+ public static class CoordConvert
+ {
+
+ ///
+ /// 经纬度转Web墨卡托(单位:米)
+ ///
+ /// 经度
+ /// 纬度
+ /// 转换后的位置
+ public static CoordPoint WGS84ToMercator(double lon, double lat)
+ {
+ var x = lon * Math.PI / 180 * Parameters.LongRadius;
+ var param = lat * Math.PI / 180;
+ var y = Parameters.LongRadius / 2 * Math.Log((1.0 + Math.Sin(param)) / (1.0 - Math.Sin(param)));
+ return new CoordPoint(x, y);
+ }
+ ///
+ /// 经纬度转Web墨卡托(单位:米)
+ ///
+ /// 经度
+ /// 纬度
+ /// 转换后的位置
+ public static CoordPoint WGS84ToMercator(Coordinate coord)
+ {
+ return WGS84ToMercator(coord.Lon, coord.Lat);
+ }
+
+ ///
+ /// Web墨卡托转经纬度
+ ///
+ /// X坐标值(单位:米)
+ /// Y坐标值(单位:米)
+ /// 转换后的位置
+ public static Coordinate MercatorToWGS84(double x, double y)
+ {
+ var lon = x / Parameters.MercatorLength * 180;
+ var lat = y / Parameters.MercatorLength * 180;
+ lat = 180 / Math.PI * (2 * Math.Atan(Math.Exp(lat * Math.PI / 180)) - Math.PI / 2);
+ return new Coordinate(lon, lat);
+ }
+
+ ///
+ /// Web墨卡托转经纬度
+ ///
+ /// X坐标值(单位:米)
+ /// Y坐标值(单位:米)
+ /// 转换后的位置
+ public static Coordinate MercatorToWGS84(CoordPoint point)
+ {
+ return MercatorToWGS84(point.X, point.Y);
+ }
+ }
+}
diff --git a/Common/CoordPoint.cs b/Common/CoordPoint.cs
new file mode 100644
index 0000000..0e61ed1
--- /dev/null
+++ b/Common/CoordPoint.cs
@@ -0,0 +1,34 @@
+namespace LY.App.Common
+{
+ public struct CoordPoint
+ {
+ public CoordPoint(double x, double y)
+ {
+ X = x;
+ Y = y;
+ Z = 0;
+ }
+
+ public CoordPoint(double x, double y, double z)
+ {
+ X = x;
+ Y = y;
+ Z = z;
+ }
+
+ ///
+ /// X轴
+ ///
+ public double X { get; set; }
+
+ ///
+ /// Y轴
+ ///
+ public double Y { get; set; }
+
+ ///
+ /// Z轴
+ ///
+ public double Z { get; set; }
+ }
+}
diff --git a/Common/Coordinate.cs b/Common/Coordinate.cs
new file mode 100644
index 0000000..32c3384
--- /dev/null
+++ b/Common/Coordinate.cs
@@ -0,0 +1,34 @@
+namespace LY.App.Common
+{
+ public struct Coordinate
+ {
+ public Coordinate(double lon, double lat)
+ {
+ Lon = lon;
+ Lat = lat;
+ Alt = 0;
+ }
+
+ public Coordinate(double lon, double lat, double alt)
+ {
+ Lon = lon;
+ Lat = lat;
+ Alt = alt;
+ }
+
+ ///
+ /// 经度
+ ///
+ public double Lon { get; set; }
+
+ ///
+ /// 纬度
+ ///
+ public double Lat { get; set; }
+
+ ///
+ /// 高程
+ ///
+ public double Alt { get; set; }
+ }
+}
diff --git a/Common/Cypher/AESCypherUtil.cs b/Common/Cypher/AESCypherUtil.cs
new file mode 100644
index 0000000..6ae1985
--- /dev/null
+++ b/Common/Cypher/AESCypherUtil.cs
@@ -0,0 +1,58 @@
+using System.Security.Cryptography;
+using System.Text;
+
+namespace LY.App.Common.Cypher
+{
+ public class AESCypherUtil
+ {
+ ///
+ /// AES 加密
+ ///
+ /// 明文
+ /// 密钥对
+ /// 密文
+ public static string Encrypt(string str, string key, string iv)
+ {
+ using (var aes = Aes.Create())
+ {
+ aes.Key = Encoding.UTF8.GetBytes(key);
+ aes.IV = Encoding.UTF8.GetBytes(iv);
+ aes.Mode = CipherMode.CBC;
+ aes.Padding = PaddingMode.PKCS7;
+
+ byte[] strbuffer = Encoding.UTF8.GetBytes(str);
+
+ using (var crytTransform = aes.CreateEncryptor(aes.Key, aes.IV))
+ {
+ byte[] buffer = crytTransform.TransformFinalBlock(strbuffer, 0, strbuffer.Length);
+ return Convert.ToBase64String(buffer, 0, buffer.Length);
+ }
+ }
+ }
+
+ ///
+ /// AES 解密
+ ///
+ /// 密文
+ /// 密钥对
+ /// 明文
+ public static string Decrypt(string str, string key, string iv)
+ {
+ using (var aes = Aes.Create())
+ {
+ aes.Key = Encoding.UTF8.GetBytes(key);
+ aes.IV = Encoding.UTF8.GetBytes(iv);
+ aes.Mode = CipherMode.CBC;
+ aes.Padding = PaddingMode.PKCS7;
+
+ byte[] strbuffer = System.Convert.FromBase64String(str);
+
+ using (var crytTransform = aes.CreateDecryptor(aes.Key, aes.IV))
+ {
+ byte[] buffer = crytTransform.TransformFinalBlock(strbuffer, 0, strbuffer.Length);
+ return Encoding.UTF8.GetString(buffer);
+ }
+ }
+ }
+ }
+}
diff --git a/Common/Cypher/MD5CypherUtil.cs b/Common/Cypher/MD5CypherUtil.cs
new file mode 100644
index 0000000..79ce6a9
--- /dev/null
+++ b/Common/Cypher/MD5CypherUtil.cs
@@ -0,0 +1,51 @@
+using System.Security.Cryptography;
+using System.Text;
+
+namespace LY.App.Common.Cypher
+{
+ public class MD5CypherUtil
+ {
+ ///
+ /// MD5加密
+ ///
+ /// 内容
+ ///
+ public static string Hash(string str)
+ {
+ return Hash(Encoding.UTF8.GetBytes(str));
+ }
+
+ ///
+ /// MD5加密
+ ///
+ ///
+ ///
+ public static string Hash(byte[] buffer)
+ {
+ return BitConverter.ToString(BinaryHash(buffer)).Replace("-", "");
+ }
+
+ ///
+ /// MD5加密
+ ///
+ ///
+ ///
+ public static byte[] BinaryHash(string str)
+ {
+ return BinaryHash(Encoding.UTF8.GetBytes(str));
+ }
+
+ ///
+ /// MD5加密
+ ///
+ ///
+ ///
+ public static byte[] BinaryHash(byte[] buffer)
+ {
+ using (var md5 = MD5.Create())
+ {
+ return md5.ComputeHash(buffer);
+ }
+ }
+ }
+}
diff --git a/Common/FreqConvert.cs b/Common/FreqConvert.cs
new file mode 100644
index 0000000..e503e82
--- /dev/null
+++ b/Common/FreqConvert.cs
@@ -0,0 +1,62 @@
+namespace LY.App.Common
+{
+ ///
+ /// 频段转换
+ ///
+ public static class FreqConvert
+ {
+ ///
+ ///返回频段转换结果
+ ///
+ ///
+ ///
+ public static double CoverFreq(double frequency)
+ {
+ double result = 0;
+ if (frequency > 0)
+ {
+ if (370 <= frequency && frequency <= 500)
+ {
+ return 433;
+ }
+ else if (700 <= frequency && frequency <= 890)
+ {
+ return 840;
+ }
+ else if (890 < frequency && frequency <= 1000)
+ {
+ return 915;
+ }
+ else if (1100 <= frequency && frequency <= 1300)
+ {
+ return 1.2;
+ }
+ else if (1300 < frequency && frequency <= 1500)
+ {
+ return 1.4;
+ }
+ else if (1500 < frequency && frequency <= 1700)
+ {
+ return 1.6;
+ }
+ else if (2300 <= frequency && frequency <= 2500)
+ {
+ return 2.4;
+ }
+ else if (5100 <= frequency && frequency <= 5300)
+ {
+ return 5.2;
+ }
+ else if (5700 <= frequency && frequency <= 5900)
+ {
+ return 5.8;
+ }
+ else
+ {
+ return result; // 表示输入频率不在已定义范围内
+ }
+ }
+ return result;
+ }
+ }
+}
diff --git a/Common/GeoJsonHelper.cs b/Common/GeoJsonHelper.cs
new file mode 100644
index 0000000..54875c1
--- /dev/null
+++ b/Common/GeoJsonHelper.cs
@@ -0,0 +1,622 @@
+using LY.App.Model;
+using NetTopologySuite.Features;
+using NetTopologySuite.Geometries;
+using NetTopologySuite.IO;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace LY.App.Common
+{
+ public class GeoJsonHelper
+ {
+ ///
+ /// 格式为json字符串
+ ///
+ ///
+ ///
+ public static string ConvertJsonString(string str)
+ {
+ try
+ {
+ //格式化json字符串
+ JsonSerializer serializer = new JsonSerializer();
+ TextReader tr = new StringReader(str);
+ JsonTextReader jtr = new JsonTextReader(tr);
+ object obj = serializer.Deserialize(jtr);
+ if (obj != null)
+ {
+
+ StringWriter textWriter = new StringWriter();
+ JsonTextWriter jsonWriter = new JsonTextWriter(textWriter)
+ {
+ Formatting = Formatting.Indented,
+ Indentation = 4,
+ IndentChar = ' '
+ };
+ serializer.Serialize(jsonWriter, obj);
+ return textWriter.ToString();
+
+ }
+ else
+ {
+ return str;
+ }
+ }
+ catch (Exception)
+ {
+
+ }
+ return str;
+ }
+
+ ///
+ /// 转换为geojson格式字符串
+ ///
+ ///
+ ///
+ public static string GetGeoJson(object? value)
+ {
+ if (value == null)
+ return "";
+ var serializer = GeoJsonSerializer.Create();
+ using (var sw = new System.IO.StringWriter())
+ {
+ serializer.Serialize(sw, value);
+
+ return sw.ToString();
+ }
+ }
+
+ ///
+ /// 将坐标转换为点
+ ///
+ /// 经度
+ /// 纬度
+ ///
+ public static NetTopologySuite.Geometries.Point ConvertToPoint(double x, double y)
+ {
+ return new NetTopologySuite.Geometries.Point(x, y);
+ }
+
+
+ public static object? GetDynamicFeature(Geometry geometry, IDictionary attributes)
+ {
+ var f = GetGeoFeature(geometry, attributes);
+ var geoJson = GetGeoJson(f);
+ return geoJson;
+ // return JsonConvert.DeserializeObject(geoJson);
+ }
+
+ ///
+ /// 获取集合中能够组成多面的几何数据
+ ///
+ ///
+ ///
+ public static List GetGeoMultiPolygon(FeatureCollection features)
+ {
+ var result = new List();
+ if (features == null)
+ return result;
+ foreach (var feature in features)
+ {
+ if (feature.Geometry is MultiPolygon)
+ {
+ result.Add(feature.Geometry as MultiPolygon);
+ }
+ else if (feature.Geometry is Polygon)
+ {
+ result.Add(new MultiPolygon(new Polygon[] { feature.Geometry as Polygon }));
+ }
+ else if (feature.Geometry is LineString)
+ {
+ var line = (LineString)feature.Geometry;
+ if (line.IsRing)
+ {
+ result.Add(new MultiPolygon(new Polygon[] { new Polygon(new LinearRing(line.Coordinates)) }));
+ }
+ }
+ }
+ return result;
+ }
+
+ ///
+ /// 获取集合中能够组成多线的几何数据
+ ///
+ ///
+ ///
+ public static List GetGeoMultiLineString(FeatureCollection features)
+ {
+ var result = new List();
+ if (features == null)
+ return result;
+ foreach (var feature in features)
+ {
+ if (feature.Geometry is MultiLineString)
+ {
+ result.Add(feature.Geometry as MultiLineString);
+ }
+ else if (feature.Geometry is LineString)
+ {
+ result.Add(new MultiLineString(new LineString[] { feature.Geometry as LineString }));
+ }
+ else if (feature.Geometry is MultiPoint)
+ {
+ var mp = (MultiPoint)feature.Geometry;
+ if (mp.Count() > 1)
+ {
+ result.Add(new MultiLineString(new LineString[] { new LineString(mp.Coordinates) }));
+ }
+ }
+ }
+ return result;
+ }
+
+ ///
+ /// 将geojson转为FeatureCollection,
+ /// 原json支持点、线面等
+ ///
+ ///
+ ///
+ public static FeatureCollection? GetGeoFeatureCollectionBuild(string geoJson)
+ {
+ if (string.IsNullOrWhiteSpace(geoJson)) return null;
+
+ try
+ {
+ var jObj = JObject.Parse(geoJson);
+ if (jObj != null)
+ {
+ if (jObj.TryGetValue("type", out var jToken))
+ {
+ if (jToken != null)
+ {
+ if (string.Compare(jToken.ToString(), "FeatureCollection") == 0)
+ {
+ return GetGeoFeatureCollection(geoJson);
+ }
+ else
+ {
+ var f = GetGeoFeatureBuild(geoJson);
+ if (f != null)
+ {
+ var result = new FeatureCollection();
+ result.Add(f);
+ return result;
+ }
+ }
+ }
+ }
+ }
+ }
+ catch (Exception)
+ {
+
+ throw new Exception("geojson无效");
+ }
+
+ return null;
+ }
+
+ public static Feature? GetGeoFeatureBuild(string geoJson)
+ {
+ var jObj = JObject.Parse(geoJson);
+ if (jObj != null)
+ {
+ //先判断type
+ if (jObj.TryGetValue("type", out var jToken))
+ {
+ if (jToken != null)
+ {
+ if (string.Compare(jToken.ToString(), "Feature") == 0)
+ {
+ return GetGeoFeature(geoJson);
+ }
+
+ Geometry? geometry = null;
+ if (string.Compare(jToken.ToString(), "Point") == 0)
+ {
+ geometry = GetGeoPoint(geoJson);
+ }
+ if (string.Compare(jToken.ToString(), "MultiPoint") == 0)
+ {
+ geometry = GetGeoMultiPoint(geoJson);
+ }
+ if (string.Compare(jToken.ToString(), "LineString") == 0)
+ {
+ geometry = GetGeoLineString(geoJson);
+ }
+ if (string.Compare(jToken.ToString(), "MultiLineString") == 0)
+ {
+ geometry = GetGeoMultiLineString(geoJson);
+ }
+ if (string.Compare(jToken.ToString(), "Polygon") == 0)
+ {
+ geometry = GetGeoPolygon(geoJson);
+ }
+ if (string.Compare(jToken.ToString(), "MultiPolygon") == 0)
+ {
+ geometry = GetGeoMultiPolygon(geoJson);
+ }
+
+ if (geometry != null)
+ return new Feature(geometry, new AttributesTable());
+ }
+ }
+ }
+ return null;
+ }
+
+ public static Feature? GetGeoFeature(Geometry geometry, IDictionary attributes)
+ {
+ AttributesTable attributesTable = new AttributesTable();
+ foreach (var item in attributes)
+ {
+ attributesTable.Add(item.Key, item.Value);
+ }
+ var result = new Feature(geometry, attributesTable);
+ return result;
+ }
+
+
+
+ public static Feature? GetGeoFeature(string geoJson)
+ {
+ var serializer = GeoJsonSerializer.Create();
+ using (var stringReader = new StringReader(geoJson))
+ using (var jsonReader = new JsonTextReader(stringReader))
+ {
+ return serializer.Deserialize(jsonReader);
+ }
+ }
+
+ public static FeatureCollection? GetGeoFeatureCollection(string geoJson)
+ {
+ var serializer = GeoJsonSerializer.Create();
+ using (var stringReader = new StringReader(geoJson))
+ using (var jsonReader = new JsonTextReader(stringReader))
+ {
+ return serializer.Deserialize(jsonReader);
+ }
+ }
+
+ public static NetTopologySuite.Geometries.Point? GetGeoPoint(string geoJson)
+ {
+ var jObj = JObject.Parse(geoJson);
+ if (jObj != null)
+ {
+ //先判断type
+ if (jObj.TryGetValue("type", out var jToken))
+ {
+ if (jToken != null)
+ {
+ if (string.Compare(jToken.ToString(), "Feature") == 0)
+ {
+ var f = GetGeoFeature(geoJson);
+ if (f.Geometry is Point)
+ return f.Geometry as NetTopologySuite.Geometries.Point;
+
+ }
+ else
+ {
+ var serializer = GeoJsonSerializer.Create();
+ using (var stringReader = new StringReader(geoJson))
+ using (var jsonReader = new JsonTextReader(stringReader))
+ {
+ return serializer.Deserialize(jsonReader);
+ }
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ public static NetTopologySuite.Geometries.MultiPoint? GetGeoMultiPoint(string geoJson)
+ {
+ var serializer = GeoJsonSerializer.Create();
+ using (var stringReader = new StringReader(geoJson))
+ using (var jsonReader = new JsonTextReader(stringReader))
+ {
+ return serializer.Deserialize(jsonReader);
+ }
+ }
+
+ public static NetTopologySuite.Geometries.LineString? GetGeoLineString(string geoJson)
+ {
+ var serializer = GeoJsonSerializer.Create();
+ using (var stringReader = new StringReader(geoJson))
+ using (var jsonReader = new JsonTextReader(stringReader))
+ {
+ return serializer.Deserialize(jsonReader);
+ }
+ }
+
+ public static NetTopologySuite.Geometries.MultiLineString? GetGeoMultiLineString(string geoJson)
+ {
+ var serializer = GeoJsonSerializer.Create();
+ using (var stringReader = new StringReader(geoJson))
+ using (var jsonReader = new JsonTextReader(stringReader))
+ {
+ return serializer.Deserialize(jsonReader);
+ }
+ }
+
+ public static NetTopologySuite.Geometries.Polygon? GetGeoPolygon(string geoJson)
+ {
+ var serializer = GeoJsonSerializer.Create();
+ using (var stringReader = new StringReader(geoJson))
+ using (var jsonReader = new JsonTextReader(stringReader))
+ {
+ return serializer.Deserialize(jsonReader);
+ }
+ }
+
+ public static NetTopologySuite.Geometries.MultiPolygon? GetGeoMultiPolygon(string geoJson)
+ {
+ var serializer = GeoJsonSerializer.Create();
+ using (var stringReader = new StringReader(geoJson))
+ using (var jsonReader = new JsonTextReader(stringReader))
+ {
+ return serializer.Deserialize(jsonReader);
+ }
+ }
+
+ public static bool TryGetGeomWKTFromGeoJson(string? geoJson, out MultiPolygon? geometry)
+ {
+ geometry = null;
+ try
+ {
+ if (!string.IsNullOrWhiteSpace(geoJson))
+ {
+ var features = GeoJsonHelper.GetGeoFeatureCollectionBuild(geoJson);
+ var multPolygons = GeoJsonHelper.GetGeoMultiPolygon(features);
+ if (multPolygons?.Count > 0)
+ {
+ geometry = multPolygons[0];
+ geometry.SRID = 4326;
+ return true;
+ }
+ }
+ }
+ catch
+ {
+ }
+ return false;
+ }
+ public static Geometry FromWKT(string wktData)
+ {
+ WKTReader reader = new WKTReader();
+ return reader.Read(wktData);
+ }
+
+ public static Geometry FromWKB(byte[] wkbData)
+ {
+ WKBReader reader = new WKBReader();
+ return reader.Read(wkbData);
+
+ }
+ public static Polygon Transfer2CoordinateByCoords(List Coords)
+ {
+ if (Coords.Any())
+ {
+ var list = Coords.Select(s => s.X + "" + s.Y).ToList();
+ var content = $"POLYGON(({string.Join(",", list)}))";
+ WKTReader wktReader = new WKTReader();
+ Polygon polygon = wktReader.Read(content) as Polygon;
+ return polygon;
+ }
+ return null;
+ }
+
+ #region 坐标相关方法
+
+ private static GeometryFactory _gf = NetTopologySuite.NtsGeometryServices.Instance.CreateGeometryFactory(4326);
+ public static bool IsWithin(BasePoint pnt, BasePoint[] polygon)
+ {
+ if (IsValidPolygon(polygon))
+ {
+ var point = CreatPoint(pnt);
+ var region = CreatePolygon(polygon);
+ return point.Within(region);
+ }
+ return false;
+ }
+ ///
+ /// 判断点是否在多边形内
+ ///
+ ///
+ ///
+ ///
+ public static bool isWithin(BasePoint pnt, MultiPolygon multiPolygon)
+ {
+ var point = CreatPoint(pnt);
+ return point.Within(multiPolygon);
+ }
+
+ ///
+ /// 基本判断点数据是否有效转为面
+ ///
+ ///
+ ///
+ public static bool IsValidPolygon(BasePoint[] polygon)
+ {
+ if (polygon?.Count() >= 4)
+ {
+ //polygon.Distinct();//重复点?
+ return polygon[0].Equal(polygon[polygon.Count() - 1]);
+ }
+ return false;
+ }
+
+ ///
+ /// 基本判断点数据是否有效转为线
+ ///
+ ///
+ ///
+ public static bool IsValidLineString(BasePoint[] polygon)
+ {
+ if (polygon?.Count() >= 2)
+ {
+ //polygon.Distinct();//重复点?
+ return true;
+ }
+ return false;
+ }
+
+ ///
+ /// 创建点
+ ///
+ ///
+ ///
+ public static Point CreatPoint(BasePoint pnt)
+ {
+ return _gf.CreatePoint(GetCoordinate(pnt));
+ }
+ ///
+ /// 创建点
+ ///
+ ///
+ ///
+ public static Point CreatPoint(double x, double y)
+ {
+ return _gf.CreatePoint(GetCoordinate(x, y));
+ }
+
+ ///
+ /// 创建线
+ ///
+ ///
+ ///
+ public static LineString CreateLineString(BasePoint[] points)
+ {
+ return _gf.CreateLineString(GetCoordinate(points));
+ }
+
+ ///
+ /// 创建面
+ ///
+ ///
+ ///
+ public static Polygon CreatePolygon(BasePoint[] polygon)
+ {
+ return _gf.CreatePolygon(GetCoordinate(polygon));
+ }
+
+
+ ///
+ /// 点转换到坐标
+ ///
+ ///
+ ///
+ public static NetTopologySuite.Geometries.Coordinate GetCoordinate(BasePoint pnt)
+ {
+ return new NetTopologySuite.Geometries.Coordinate(pnt.X, pnt.Y);
+ }
+
+ ///
+ /// 点转换到坐标
+ ///
+ ///
+ ///
+ public static NetTopologySuite.Geometries.Coordinate GetCoordinate(double x, double y)
+ {
+ return new NetTopologySuite.Geometries.Coordinate(x, y);
+ }
+
+ ///
+ /// 点组转换坐标组
+ ///
+ ///
+ ///
+ public static NetTopologySuite.Geometries.Coordinate[] GetCoordinate(BasePoint[] polygon)
+ {
+ List coordinates = new List();
+ foreach (var item in polygon)
+ {
+ coordinates.Add(GetCoordinate(item));
+ }
+ return coordinates.ToArray();
+ }
+
+ public static Geometry CreateCircleMercator(double x, double y, double radius)
+ {
+ var geometryFactory = new GeometryFactory();
+ var wbp = CoordConvert.WGS84ToMercator(x, y);
+ var point1 = geometryFactory.CreatePoint(new NetTopologySuite.Geometries.Coordinate(wbp.X, wbp.Y));
+ var geo = point1.Buffer(radius, 80);
+ //var geo1 = point1.Buffer(radius);
+ return geo;
+ }
+
+ ///
+ /// 创建圆形
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static Geometry CreateCircle(double x, double y, double radius, int pnts = 100)
+ {
+ var geo1 = CreateCircleMercator(x, y, radius);
+ var coordinates = geo1.Coordinates;
+ List points = new List();
+ foreach (var item in coordinates)
+ {
+ var wgs84 = CoordConvert.MercatorToWGS84(item.X, item.Y);
+ points.Add(new BasePoint(wgs84.Lon, wgs84.Lat, 0));
+ }
+ return CreatePolygon(points.ToArray());
+ }
+ public static double CalculateCircleAreaKmUnit(double x, double y, double radius)
+ {
+ if (x > 0 && y > 0 && radius > 0)
+ {
+ var area11 = CalculateCircleArea(x, y, radius);
+ return Math.Round(area11 / 1000000, 5);
+ }
+ return 0;
+ }
+ public static double CalculateCircleArea(double x, double y, double radius)
+ {
+ if (x > 0 && y > 0 && radius > 0)
+ {
+ var geo1 = CreateCircleMercator(x, y, radius);
+ return geo1.Area;
+ }
+ return 0;
+ }
+ ///
+ /// 计算合并后的总面积
+ ///
+ ///
+ ///
+ public static double CalculateAreaMercator(Geometry[] geometries)
+ {
+ //将多个 Geometry 合并成一个 Geometry,避免交集重复计算
+ var unionedGeometry = _gf.BuildGeometry(geometries).Union();
+ string geojson = GetGeoJson(unionedGeometry);
+ var area = unionedGeometry.Area;
+ // 计算合并后的总面积
+ return area;
+ }
+ /////
+ ///// 计算合并后的总面积
+ /////
+ /////
+ /////
+ //public static double CalculateArea(Geometry[] geometries)
+ //{
+ // //将多个 Geometry 合并成一个 Geometry,避免交集重复计算
+ // var unionedGeometry = _gf.BuildGeometry(geometries).Union();
+ // string geojson = GetGeoJson(unionedGeometry);
+ // var area = Math.Round(unionedGeometry.ProjectTo(2855).Area / 1000000.0, 5);
+ // // 计算合并后的总面积
+ // return area;
+ //}
+
+ #endregion
+
+
+
+ }
+}
diff --git a/Common/HttpUtil/RequestUtil.cs b/Common/HttpUtil/RequestUtil.cs
new file mode 100644
index 0000000..03c59aa
--- /dev/null
+++ b/Common/HttpUtil/RequestUtil.cs
@@ -0,0 +1,420 @@
+using Newtonsoft.Json;
+using System.Net.Http.Headers;
+using System.Net;
+
+namespace LY.App.Common.HttpUtil
+{
+ public static class RequestUtil
+ {
+ ///
+ /// 是否已经初始化加密协议
+ ///
+ private static bool _securitinit = false;
+ ///
+ /// 初始化加密协议
+ ///
+ public static void InitialSecurity()
+ {
+ if (_securitinit) return;
+
+ try
+ {
+ ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls
+ | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls13;
+ }
+ catch (Exception)
+ {
+ try
+ {
+ ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls
+ | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;
+ }
+ catch (Exception)
+ {
+ try
+ {
+ ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | SecurityProtocolType.Tls11;
+ }
+ catch (Exception)
+ {
+ try
+ {
+ ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls;
+ }
+ catch (Exception) { }
+ }
+ }
+ }
+
+ ServicePointManager.ServerCertificateValidationCallback = (a, b, c, d) => true;
+ _securitinit = true;
+ }
+
+ ///
+ /// 获取HTTP连接
+ ///
+ /// 是否ssl连接
+ ///
+ public static HttpClient GetClient(bool haveSSL)
+ {
+ if (haveSSL)
+ {
+ InitialSecurity();
+ var handle = new HttpClientHandler();
+ handle.ServerCertificateCustomValidationCallback
+ = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
+ return new HttpClient(handle);
+ }
+ else return new HttpClient();
+ }
+
+ ///
+ /// 异步请求GET
+ ///
+ /// 网址
+ /// 头部信息
+ /// 验证信息
+ /// 过期时间
+ ///
+ public static async Task GetAsync(string url,
+ Dictionary headers = null,
+ AuthenticationHeaderValue auth = null, int timeout = 5000)
+ {
+ using (var client = GetClient(url.StartsWith("https")))
+ {
+ client.Timeout = TimeSpan.FromMilliseconds(timeout);
+ if (headers != null && headers.Any())
+ {
+ foreach (var item in headers)
+ {
+ client.DefaultRequestHeaders.Add(item.Key, item.Value);
+ }
+ }
+
+ if (auth != null)
+ client.DefaultRequestHeaders.Authorization = auth;
+
+ var result = await client.GetAsync(url);
+ if (result.IsSuccessStatusCode)
+ return await result.Content.ReadAsStringAsync();
+ }
+
+ return null;
+ }
+
+ ///
+ /// 异步请求GET
+ ///
+ /// 网址
+ /// 头部信息
+ /// 验证信息
+ /// 过期时间
+ ///
+ public static async Task GetStreamAsync(string url,
+ Dictionary headers = null,
+ AuthenticationHeaderValue auth = null, int timeout = 5000)
+ {
+ using (var client = GetClient(url.StartsWith("https")))
+ {
+ client.Timeout = TimeSpan.FromMilliseconds(timeout);
+ if (headers != null && headers.Any())
+ {
+ foreach (var item in headers)
+ {
+ client.DefaultRequestHeaders.Add(item.Key, item.Value);
+ }
+ }
+
+ if (auth != null)
+ client.DefaultRequestHeaders.Authorization = auth;
+
+ var result = await client.GetAsync(url);
+ if (result.IsSuccessStatusCode)
+ return await result.Content.ReadAsStreamAsync();
+ }
+
+ return null;
+ }
+
+ ///
+ /// 删除
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static async Task DeleteAsync(string url,
+ Dictionary headers = null,
+ AuthenticationHeaderValue auth = null, int timeout = 5000)
+ {
+ using (var client = GetClient(url.StartsWith("https")))
+ {
+ client.Timeout = TimeSpan.FromMilliseconds(timeout);
+ if (headers != null && headers.Any())
+ {
+ foreach (var item in headers)
+ {
+ client.DefaultRequestHeaders.Add(item.Key, item.Value);
+ }
+ }
+
+ if (auth != null)
+ client.DefaultRequestHeaders.Authorization = auth;
+
+ var result = await client.DeleteAsync(url);
+ if (result.IsSuccessStatusCode)
+ return await result.Content.ReadAsStringAsync();
+ }
+
+ return null;
+ }
+
+ ///
+ /// 异步请求并解析
+ ///
+ ///
+ /// 网址
+ /// 头部信息
+ /// 验证信息
+ /// 过期时间
+ ///
+ public static async Task GetAsync(string url,
+ Dictionary headers = null,
+ AuthenticationHeaderValue auth = null,
+ int timeout = 5000)
+ {
+ var result = await GetAsync(url, headers, auth, timeout);
+ if (result != null)
+ return JsonConvert.DeserializeObject(result);
+
+ return default;
+ }
+ ///
+ /// 返回btye[]
+ ///
+ ///
+ ///
+ public static async Task GetAsync(string url)
+ {
+ using (var client = GetClient(url.StartsWith("https")))
+ {
+ client.Timeout = TimeSpan.FromMilliseconds(2000);
+ var result = await client.GetAsync(url);
+ if (result.IsSuccessStatusCode)
+ return await result.Content.ReadAsByteArrayAsync();
+ }
+ return null;
+
+ }
+
+ public static AuthenticationHeaderValue GetTokenHeader(string token)
+ {
+ return new AuthenticationHeaderValue("Bearer", token);
+ }
+
+ ///
+ /// 异步请求POST
+ ///
+ /// 网址
+ /// 信息
+ /// 头部信息
+ /// 验证信息
+ /// 过期时间
+ ///
+ public static async Task PostAsync(string url,
+ string dataJson = null,
+ Dictionary headers = null,
+ AuthenticationHeaderValue auth = null, int timeout = 5000)
+ {
+ using (var client = GetClient(url.StartsWith("https")))
+ {
+ client.Timeout = TimeSpan.FromMilliseconds(timeout);
+ if (headers != null && headers.Any())
+ {
+ foreach (var item in headers)
+ {
+ client.DefaultRequestHeaders.Add(item.Key, item.Value);
+ }
+ }
+
+ if (auth != null)
+ client.DefaultRequestHeaders.Authorization = auth;
+
+ StringContent content = null;
+
+ if (dataJson != null)
+ {
+ content = new StringContent(dataJson);
+ content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
+ }
+
+ var result = await client.PostAsync(url, content);
+ if (result.IsSuccessStatusCode)
+ return await result.Content.ReadAsStringAsync();
+ }
+
+ return null;
+ }
+
+ ///
+ /// 异步请求POST
+ ///
+ /// 网址
+ /// 消息
+ /// 头部信息
+ /// 验证信息
+ /// 过期时间
+ ///
+ public static async Task PostAsync(string url,
+ Dictionary data,
+ Dictionary headers = null,
+ AuthenticationHeaderValue auth = null, int timeout = 5000)
+ {
+ return await PostAsync(url, JsonConvert.SerializeObject(data), headers, auth, timeout);
+ }
+
+ ///
+ /// 异步请求POST
+ ///
+ ///
+ /// 网址
+ /// 信息
+ /// 头部信息
+ /// 验证信息
+ /// 过期时间
+ ///
+ public static async Task PostAsync(string url,
+ string dataJson = null,
+ Dictionary headers = null,
+ AuthenticationHeaderValue auth = null, int timeout = 5000)
+ {
+ var result = await PostAsync(url, dataJson, headers, auth, timeout);
+ return JsonConvert.DeserializeObject(result);
+ }
+ ///
+ ///
+ ///
+ ///
+ /// 网址
+ /// 消息
+ /// 头部信息
+ /// 验证信息
+ /// 过期时间
+ ///
+ public static async Task PostAsync(string url,
+ Dictionary data = null,
+ Dictionary headers = null,
+ AuthenticationHeaderValue auth = null, int timeout = 5000)
+ {
+ return await PostAsync(url, JsonConvert.SerializeObject(data), headers, auth, timeout);
+ }
+
+ ///
+ /// 异步推送数据流
+ ///
+ /// 网址
+ /// 数据流
+ /// 头部信息
+ /// 验证信息
+ /// 过期时间
+ ///
+ public static async Task PutStreamAsync(
+ string url, Stream stream,
+ Dictionary headers = null,
+ AuthenticationHeaderValue auth = null, int timeout = 10000)
+ {
+ using (var client = GetClient(url.StartsWith("https")))
+ {
+ client.Timeout = TimeSpan.FromMilliseconds(timeout);
+ if (headers != null && headers.Any())
+ {
+ foreach (var item in headers)
+ {
+ client.DefaultRequestHeaders.Add(item.Key, item.Value);
+ }
+ }
+
+ if (auth != null)
+ client.DefaultRequestHeaders.Authorization = auth;
+
+ StreamContent content = null;
+
+ if (stream != null)
+ {
+ content = new StreamContent(stream);
+ content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
+ }
+
+ var result = await client.PutAsync(url, content);
+ if (result.IsSuccessStatusCode)
+ return await result.Content.ReadAsStringAsync();
+ }
+
+ return null;
+ }
+
+
+ ///
+ /// 异步推送数据流
+ ///
+ /// 网址
+ /// 数据流
+ /// 头部信息
+ /// 验证信息
+ /// 过期时间
+ ///
+ public static async Task PutStreamAsync(
+ string url, Stream stream,
+ Dictionary headers = null,
+ AuthenticationHeaderValue auth = null, int timeout = 10000)
+ {
+ var result = await PutStreamAsync(url, stream, headers, auth, timeout);
+ return JsonConvert.DeserializeObject(result);
+ }
+
+ ///
+ /// PUT请求
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static async Task PutAsync(string url,
+ string dataJson = null,
+ Dictionary headers = null,
+ AuthenticationHeaderValue auth = null, int timeout = 5000)
+ {
+ using (var client = GetClient(url.StartsWith("https")))
+ {
+ client.Timeout = TimeSpan.FromMilliseconds(timeout);
+ if (headers != null && headers.Any())
+ {
+ foreach (var item in headers)
+ {
+ client.DefaultRequestHeaders.Add(item.Key, item.Value);
+ }
+ }
+
+ if (auth != null)
+ client.DefaultRequestHeaders.Authorization = auth;
+
+ StringContent content = null;
+
+ if (dataJson != null)
+ {
+ content = new StringContent(dataJson);
+ content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
+ }
+
+ var result = await client.PutAsync(url, content);
+ if (result.IsSuccessStatusCode)
+ return await result.Content.ReadAsStringAsync();
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/Common/Parameters.cs b/Common/Parameters.cs
new file mode 100644
index 0000000..94367d2
--- /dev/null
+++ b/Common/Parameters.cs
@@ -0,0 +1,30 @@
+namespace LY.App.Common
+{
+ public static class Parameters
+ {
+ ///
+ /// 地球平均半径
+ ///
+ public const double EarthRadius = 6371393;
+
+ ///
+ /// 地球长轴半径
+ ///
+ public const double LongRadius = 6378137;
+
+ ///
+ /// 地球短轴半径
+ ///
+ public const double ShortRadius = 6356752.3142;
+
+ ///
+ /// 地球扁率
+ ///
+ public const double Oblateness = 0.00335281066433;
+
+ ///
+ ///
+ ///
+ public const double MercatorLength = 20037508.34;
+ }
+}
diff --git a/Common/Redis/RedisKeyList.cs b/Common/Redis/RedisKeyList.cs
new file mode 100644
index 0000000..de5bbad
--- /dev/null
+++ b/Common/Redis/RedisKeyList.cs
@@ -0,0 +1,43 @@
+namespace LY.App.Common.Redis
+{
+ public class RedisKeyList
+ {
+ public static string BatchIdBysn(string sn)
+ {
+ return $"batchid_{sn}";
+ }
+ public static string LogNum(string userName)
+ {
+ return $"login_fail_{userName}";
+ }
+ public static string TokenUser(string userName)
+ {
+ return $"token_{userName}";
+ }
+ public static string DeviceInfo(string code)
+ {
+ return $"deviceInfo_{code}";
+ }
+ ///
+ /// 缓存首页数据
+ ///
+ ///
+ public static string index_data()
+ {
+ return $"index_cache";
+ }
+ public static string DeviceStatus(string sn)
+ {
+ return $"device_status_{sn}";
+ }
+ ///
+ /// 缓存设备token
+ ///
+ ///
+ ///
+ public static string DeviceTokenById(long id)
+ {
+ return $"device_token_{id}";
+ }
+ }
+}
diff --git a/Common/Redis/RedisService.cs b/Common/Redis/RedisService.cs
new file mode 100644
index 0000000..9fcde9a
--- /dev/null
+++ b/Common/Redis/RedisService.cs
@@ -0,0 +1,58 @@
+using LY.App.Extensions.DI;
+using StackExchange.Redis;
+using System.Text.Json;
+
+namespace LY.App.Common.Redis
+{
+ ///
+ /// redis 连接服务
+ ///
+ public class RedisService
+ {
+ private readonly IDatabase _db;
+
+ ///
+ /// 构造函数
+ ///
+ ///
+ public RedisService(string connectionString)
+ {
+ var redis = ConnectionMultiplexer.Connect(connectionString);
+ _db = redis.GetDatabase();
+ }
+
+ ///
+ /// 泛型存储数据到 Redis
+ ///
+ public async Task SetAsync(string key, T value, TimeSpan? expiry = null)
+ {
+ string jsonData = JsonSerializer.Serialize(value);
+ return await _db.StringSetAsync(key, jsonData, expiry);
+ }
+
+ ///
+ /// 泛型获取数据
+ ///
+ public async Task GetAsync(string key)
+ {
+ string jsonData = await _db.StringGetAsync(key);
+ return jsonData is not null ? JsonSerializer.Deserialize(jsonData) : default;
+ }
+
+ ///
+ /// 删除 Key
+ ///
+ public async Task DeleteAsync(string key)
+ {
+ return await _db.KeyDeleteAsync(key);
+ }
+
+ ///
+ /// 检查 Key 是否存在
+ ///
+ public async Task ExistsAsync(string key)
+ {
+ return await _db.KeyExistsAsync(key);
+ }
+ }
+}
diff --git a/Common/WebSocket/PushService.cs b/Common/WebSocket/PushService.cs
new file mode 100644
index 0000000..51ce139
--- /dev/null
+++ b/Common/WebSocket/PushService.cs
@@ -0,0 +1,42 @@
+using LY.App.Extensions.DI;
+using Microsoft.AspNetCore.SignalR;
+using Newtonsoft.Json;
+
+namespace LY.App.Common.WebSocket
+{
+ ///
+ /// 客户端push消息
+ ///
+ [ServiceInjection(InjectionType.Singleton)]
+ public class PushService
+ {
+ private readonly IHubContext _hubContext;
+
+ public PushService(IHubContext hubContext)
+ {
+ _hubContext = hubContext;
+ }
+ ///
+ /// 1对1消息
+ ///
+ ///
+ ///
+ ///
+ public async Task SendMessageToClient(string connectionId, object message)
+ {
+ await _hubContext.Clients.Client(connectionId).SendAsync("ReceiveMessage", message);
+ }
+ ///
+ /// 全部消息
+ ///
+ ///
+ ///
+ public async Task SendMessageToAll(object message)
+ {
+
+ var data = JsonConvert.SerializeObject(message);
+ Console.WriteLine($"推送前端消息:{data}");
+ await _hubContext.Clients.All.SendAsync("ReceiveMessage", data);
+ }
+ }
+}
diff --git a/Common/WebSocket/SocketHub.cs b/Common/WebSocket/SocketHub.cs
new file mode 100644
index 0000000..43d03be
--- /dev/null
+++ b/Common/WebSocket/SocketHub.cs
@@ -0,0 +1,46 @@
+using LY.App.Extensions.DI;
+using Microsoft.AspNetCore.SignalR;
+using Newtonsoft.Json;
+
+namespace LY.App.Common.WebSocket
+{
+ ///
+ /// socket
+ ///
+ [ServiceInjection(InjectionType.Singleton)]
+ public class SocketHub : Hub
+ {
+ // 客户端连接事件
+ public override async Task OnConnectedAsync()
+ {
+ var msg = JsonConvert.SerializeObject(new { msgType = "info", data = "hello" });
+ // 发送消息给连接的客户端
+ await Clients.Caller.SendAsync("ReceiveMessage", msg);
+ await base.OnConnectedAsync();
+ }
+ ///
+ /// // 自定义方法,用于向客户端推送消息
+ ///
+ ///
+ ///
+ public async Task SendMessageToClient(object message)
+ {
+ // 发送消息给所有客户端
+
+ var connId = this.Context.ConnectionId;
+ var msg = JsonConvert.SerializeObject(message);
+ await this.Clients.All.SendAsync("ReceiveMessage", msg);
+ }
+ ///
+ /// 向指的用户发送
+ ///
+ ///
+ ///
+ ///
+ public async Task SendMessageToUser(string user, object message)
+ {
+ var msg = JsonConvert.SerializeObject(message);
+ await Clients.All.SendAsync("ReceiveMessage", user, msg);
+ }
+ }
+}
diff --git a/Controllers/AlarmController.cs b/Controllers/AlarmController.cs
new file mode 100644
index 0000000..cd4ce84
--- /dev/null
+++ b/Controllers/AlarmController.cs
@@ -0,0 +1,68 @@
+using LY.App.Model;
+using LY.App.Service;
+using Mapster;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+
+namespace LY.App.Controllers
+{
+ [Route("api/[controller]")]
+ [ApiController]
+ public class AlarmController : ControllerBase
+ {
+
+ private readonly AlarmService _alarmService;
+
+ public AlarmController(AlarmService alarmService)
+ {
+ _alarmService = alarmService;
+ }
+ ///
+ ///列表
+ ///
+ ///
+ ///
+ [HttpGet("list")]
+ public async Task List([FromQuery] AlarmReq input)
+ {
+ var result = await _alarmService.GetPage(input);
+ return Ok(result);
+ }
+
+ ///
+ /// 新增告警
+ ///
+ ///
+ ///
+ [HttpPost("add")]
+ public async Task AddAlarm(RevData input)
+ {
+ var result = await _alarmService.AddAlarm(input);
+ return Ok(result);
+ }
+
+ ///
+ /// 获取指定批次的告警详情
+ ///
+ ///
+ ///
+ [HttpPost("detail")]
+ public async Task detail(long batchid)
+ {
+ var result = await _alarmService.GetByBatchId(batchid);
+ return Ok(result);
+ }
+ ///
+ /// 统计报表
+ ///
+ ///
+ ///
+ ///
+ [HttpGet("report")]
+ public async Task report(DateTime? start, DateTime? end)
+ {
+ var result=await _alarmService.GetReport(start, end);
+ return Ok(result);
+ }
+ }
+}
diff --git a/Controllers/DeviceController.cs b/Controllers/DeviceController.cs
new file mode 100644
index 0000000..ed45feb
--- /dev/null
+++ b/Controllers/DeviceController.cs
@@ -0,0 +1,62 @@
+using LY.App.Model;
+using LY.App.Service;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using System.Drawing.Printing;
+using System.Security.Permissions;
+
+namespace LY.App.Controllers
+{
+ [Route("api/[controller]")]
+ [ApiController]
+ public class DeviceController : ControllerBase
+ {
+ private readonly DeviceService _deviceService;
+ public DeviceController(DeviceService deviceService)
+ {
+ _deviceService = deviceService;
+ }
+
+ ///
+ /// 新增设备
+ ///
+ ///
+ ///
+ [HttpPost("add")]
+ public async Task Add(AddDevice input)
+ {
+ var result =await _deviceService.Add(input);
+ return Ok(result);
+ }
+ ///
+ /// 列表
+ ///
+ ///
+ ///
+ ///
+ ///
+ [HttpGet("list")]
+ public async Task List(int pageNum, int pageSize, string key)
+ {
+ var result = await _deviceService.List(pageNum, pageSize, key);
+ return Ok(result);
+ }
+ ///
+ ///更新
+ ///
+ ///
+ ///
+ [HttpPost("updata")]
+ public async Task Updata(UpdateDevice input)
+ {
+ var result = _deviceService.updata(input);
+ return Ok(result);
+ }
+ [HttpDelete("delete")]
+ public async Task Delete(long id)
+ {
+ var result = await _deviceService.delete(id);
+ return Ok(result);
+ }
+ }
+}
diff --git a/Controllers/HomeController.cs b/Controllers/HomeController.cs
new file mode 100644
index 0000000..5400e01
--- /dev/null
+++ b/Controllers/HomeController.cs
@@ -0,0 +1,40 @@
+using LY.App.Common.Redis;
+using LY.App.Device;
+using LY.App.Model;
+using LY.App.Service;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+
+namespace LY.App.Controllers
+{
+ [Route("api/[controller]")]
+ [ApiController]
+ public class HomeController : ControllerBase
+ {
+ private readonly DeviceService _deviceService;
+ private readonly DeviceManager deviceManager = DeviceManager.Instance;
+ private readonly PositionService _positionService;
+ private readonly RedisService _redisService;
+ public HomeController(DeviceService deviceService, PositionService positionService, RedisService redisService)
+ {
+ _deviceService = deviceService;
+ _positionService = positionService;
+ _redisService = redisService;
+ }
+ ///
+ /// 首页
+ ///
+ ///
+ [HttpGet("view")]
+ public async Task view()
+ {
+ ApiResult result = new ApiResult();
+ var positions = await _positionService.Index();
+ result.data = new
+ {
+ positions
+ };
+ return Ok(result);
+ }
+ }
+}
diff --git a/Controllers/PositionController.cs b/Controllers/PositionController.cs
new file mode 100644
index 0000000..220f87b
--- /dev/null
+++ b/Controllers/PositionController.cs
@@ -0,0 +1,63 @@
+using LY.App.Model;
+using LY.App.Service;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+
+namespace LY.App.Controllers
+{
+ [Route("api/[controller]")]
+ [ApiController]
+ public class PositionController : ControllerBase
+ {
+ private readonly PositionService _positionService;
+
+ public PositionController(PositionService positionService)
+ {
+ _positionService = positionService;
+ }
+ ///
+ /// 获取列表
+ ///
+ ///
+ ///
+ [HttpGet("list")]
+ public async Task Get(PositionQueryInput input)
+ {
+ var positions = _positionService.GetList(input);
+ return Ok(positions);
+ }
+ ///
+ /// 删除
+ ///
+ ///
+ ///
+ [HttpDelete("delete")]
+ public async Task Delete(long id)
+ {
+ var position = await _positionService.Delete(id);
+ return Ok(position);
+ }
+ ///
+ /// 新增
+ ///
+ ///
+ ///
+ [HttpPost("add")]
+ public async Task Add(AddPosition input)
+ {
+ var result = await _positionService.MainAdd(input);
+ return Ok(result);
+ }
+ ///
+ /// 更新
+ ///
+ ///
+ ///
+ [HttpPost("update")]
+ public async Task update(UpdatePosition input)
+ {
+ var result = await _positionService.Update(input);
+ return Ok(result);
+ }
+ }
+}
diff --git a/Controllers/UserController.cs b/Controllers/UserController.cs
new file mode 100644
index 0000000..36a487b
--- /dev/null
+++ b/Controllers/UserController.cs
@@ -0,0 +1,113 @@
+using LY.App.Model;
+using LY.App.Service;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+
+namespace LY.App.Controllers
+{
+ [Route("api/[controller]")]
+ [ApiController]
+ public class UserController : ControllerBase
+ {
+ private readonly UserService _userService;
+ public UserController(UserService userService)
+ {
+ _userService = userService;
+ }
+ #region 增删改查
+ [HttpPost("add")]
+ public async Task Add(AddUser input)
+ {
+ var result = await _userService.Add(input);
+ return Ok(result);
+ }
+ ///
+ /// 列表
+ ///
+ ///
+ [HttpGet("list")]
+ public async Task GetList(int pageSize = 10, int pageNum = 1, string key = "")
+ {
+ var result = await _userService.GetPage(pageSize, pageNum, key);
+ return Ok(new ApiResult() { data = result });
+ }
+ ///
+ /// 删除
+ ///
+ ///
+ ///
+ [HttpDelete("remove")]
+ public async Task Delete(IEnumerable ids)
+ {
+ var result = await _userService.SoftDelete(ids);
+ return Ok(new ApiResult()
+ {
+ code = string.IsNullOrEmpty(result) ? 0 : 1,
+ msg = result
+ });
+ }
+ ///
+ /// 更新用户对象
+ ///
+ ///
+ ///
+ [HttpPost("update")]
+ public async Task Update(UpdateUser input)
+ {
+ var result = await _userService.Update(input);
+ return Ok(new ApiResult()
+ {
+ code = string.IsNullOrEmpty(result) ? 0 : 1,
+ msg = result,
+ });
+ }
+ ///
+ /// 更新密码
+ ///
+ ///
+ ///
+ [HttpPost("updatepwd")]
+ public async Task updatepwd(UpdatePwdDto input)
+ {
+ var result = await _userService.UpdatePwd(input);
+ return Ok(new ApiResult()
+ {
+ code = string.IsNullOrEmpty(result) ? 0 : 1,
+ msg = result
+ });
+ }
+ [HttpGet("detail")]
+ public async Task Detail(long id)
+ {
+ var result = await _userService.GetUserById(id);
+ if (result == null)
+ {
+ return Ok(new ApiResult()
+ {
+ code = 0,
+ data = { },
+ msg = ""
+ });
+ }
+ else
+ {
+ return Ok(new ApiResult()
+ {
+ code = 0,
+ data = result,
+ msg = ""
+ });
+ }
+
+ }
+ #endregion
+ #region 登陆
+ [HttpPost("login")]
+ public async Task Login(LoginModel input)
+ {
+ var result = await _userService.Login(input);
+ return Ok(result);
+ }
+ #endregion
+ }
+}
diff --git a/Device/Command/GraphCommand.cs b/Device/Command/GraphCommand.cs
new file mode 100644
index 0000000..ae7fcbd
--- /dev/null
+++ b/Device/Command/GraphCommand.cs
@@ -0,0 +1,240 @@
+namespace LY.App.Device.Command
+{
+ public static class GraphCommand
+ {
+ ///
+ /// 获取打击状态
+ ///
+ ///
+ public static string AttackStatus()
+ {
+ return @"{
+ widebandJammer {
+ band15
+ band24
+ band58
+ }
+ }";
+ }
+
+ ///
+ /// 获取无人机
+ ///
+ ///
+ public static string GetFindTarget()
+ {
+ return @"{drone {
+ attack_bands,
+ attack_type,
+ attacking,
+ can_attack,
+ can_takeover,
+ created_time,
+ deleted_time,
+ description,
+ direction,
+ distance,
+ has_duplicate,
+ id,
+ initial_location {
+ lat
+ lng
+ },
+ jamming_conflicts,
+ lastseen,
+ latitude,
+ link_id,
+ localization {
+ lat
+ lng
+ },
+ longitude,
+ name,
+ height,
+ distance,
+ seen_sensor {
+ detected_freq_khz
+ noise_dbm
+ sensor_id
+ signal_dbm
+ snr_dB
+ },
+ state,
+ whitelisted
+ }}";
+ }
+
+ ///
+ /// 获取设备状态信息
+ ///
+ public static string GetDeveceStatusInfo()
+ {
+ return @"{
+ devices{
+ id
+ config
+ gps_fixed
+ class
+ }
+ }";
+ }
+ public static string GetAdsb()
+ {
+
+ return @"{
+ droneAdsB {
+ support
+ drone {
+ id
+ speed
+ height
+ latitude
+ longitude
+ yaw_angle
+ confirmed
+ registration
+ typecode
+ manufacturer
+ model
+ owner
+ }
+ }
+}";
+ }
+
+ ///
+ /// 获取传感器状态
+ ///
+ ///
+ public static string GetSensorStatus()
+ {
+ return @"{sensor {
+ config,
+ faults,
+ id,
+ mac,
+ name,
+ sensor_status {
+ built_time
+ component_type
+ first_seen
+ git_hash
+ ip_address
+ last_seen
+ temperature
+ version
+ },
+ state,
+ ttl
+ }}";
+ }
+
+ ///
+ /// 白名单
+ ///
+ ///
+ public static string GetWhitelist()
+ {
+ return @"{whitelist{dronetype,id}}";
+ }
+
+ ///
+ /// 系统能力查询
+ ///
+ ///
+ public static string GetSystemCapability()
+ {
+ return @"{sysCapability}";
+ }
+ ///
+ /// 获取宽带打击的频段
+ ///
+ ///
+ public static string widebanJammer()
+ {
+ return @"{widebandJammer{
+ band12
+ band14
+ band15
+ band18
+ band24
+ band4
+ band58
+ band9}}";
+ }
+ ///
+ /// 添加白名单
+ ///
+ ///
+ ///
+ ///
+ public static string AddWhitelist(string droneId, string droneType)
+ {
+ return "mutation{addWhitelist(id:\"" + droneId + "\"\ndronetype:\"" + droneType + "\"\ntimerange:\"permanent,permanent\")}";
+ }
+
+ ///
+ /// 删除白名单
+ ///
+ ///
+ ///
+ ///
+ public static string DeleteWhitelist(string droneId)
+ {
+ return "mutation{deleteWhitelist(id:\"" + droneId + "\")}";
+ }
+
+ ///
+ /// 精确打击,flse开始,true停止
+ ///
+ ///
+ ///
+ ///
+ public static string AccurateAttack(bool isAttack, string droneId)
+ {
+ string cmdstr = "mutation{ attack( cancel:true \n takeover:false \n id: \"" + droneId + "\"\n )}";
+ if (isAttack)
+ {
+ cmdstr = "mutation{ attack( cancel:false \n takeover:false \n id: \"" + droneId + "\"\n )}";
+ }
+ else
+ {
+ cmdstr = "mutation{ attack( cancel:true \n takeover:false \n id: \"" + droneId + "\"\n )}";
+ }
+ return cmdstr;
+ }
+
+ ///
+ /// 自动打击
+ ///
+ ///
+ ///
+ public static string AutoAttack(bool isAuto)
+ {
+ string cmdstr = "mutation{ autoAttack( on: " + isAuto + " \n wbEnabled: false ) }";
+ if (isAuto)
+ {
+ cmdstr = "mutation{ autoAttack( on:true \n wbEnabled: false ) }";
+ }
+ else
+ {
+ cmdstr = "mutation{ autoAttack( on:false \n wbEnabled: false ) }";
+ }
+ return cmdstr.ToLower();
+ }
+
+ ///
+ /// 宽带打击
+ ///
+ /// 15,24,58
+ /// true,开启,false,关闭
+ ///
+ public static string WidebandAttack(string band, bool status)
+ {
+
+ string cmdstr = "mutation{ wideband_attack( band: \"" + band + "\"\n wb_status:" + status + ")\n" +
+ "{band15\nband24\nband58\nband9}}";
+ return cmdstr.ToLower();
+ }
+ }
+}
diff --git a/Device/DeviceManager.cs b/Device/DeviceManager.cs
new file mode 100644
index 0000000..62e669a
--- /dev/null
+++ b/Device/DeviceManager.cs
@@ -0,0 +1,429 @@
+using System;
+using System.Collections.Concurrent;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading.Tasks;
+using GraphQL.Client.Http;
+using GraphQL.Transport;
+using GraphQL.Client.Serializer.Newtonsoft;
+using GraphQL.Client.Abstractions;
+using MQTTnet;
+using MQTTnet.Client;
+using System.Net.Http.Headers;
+using LY.App.Common.Redis;
+using LY.App.Model;
+using Newtonsoft.Json;
+using StackExchange.Redis;
+using LY.App.Common.HttpUtil;
+using LY.App.Device.Command;
+
+namespace LY.App.Device
+{
+ public enum ProtocolType
+ {
+ TCP,
+ UDP,
+ GraphQL,
+ MQTT
+ }
+
+ public class Device
+ {
+ public long Id { get; set; }
+ public ProtocolType Protocol { get; set; }
+ public string IpAddress { get; set; }
+ public int Port { get; set; }
+ public string GraphQlEndpoint { get; set; }
+ public bool IsConnected { get; set; }
+ public string BrokerAddress { get; set; } // MQTT Broker 地址
+ public string Topic { get; set; } // MQTT 主题
+ public string username { get; set; } // MQTT 用户名
+ public string password { get; set; } // MQTT 密码
+ ///
+ /// 数据接收事件
+ ///
+ public Action DataReceived { get; set; }
+ }
+
+ public class DeviceManager
+ {
+ private readonly ConcurrentDictionary _devices = new();
+ private readonly ConcurrentDictionary _mqttClients = new(); // 维护 MQTT 客户端
+ private static DeviceManager _instance;
+ private readonly RedisService _redis = ServiceLocator.Instance.GetService();
+ private static readonly object _lock = new object();
+
+ private DeviceManager() { }
+ public static DeviceManager Instance
+ {
+ get
+ {
+ lock (_lock)
+ {
+ if (_instance == null)
+ {
+ _instance = new DeviceManager();
+ }
+ return _instance;
+ }
+ }
+ }
+
+ public void AddDevice(Device device)
+ {
+ if (_devices.TryAdd(device.Id, device))
+ {
+ switch (device.Protocol)
+ {
+ case ProtocolType.TCP:
+ Task.Run(() => HandleTcpDevice(device));
+ break;
+ case ProtocolType.UDP:
+ Task.Run(() => HandleUdpDevice(device));
+ break;
+ case ProtocolType.GraphQL:
+ Task.Run(() => HandleGraphQLDevice(device));
+ break;
+ case ProtocolType.MQTT:
+ Task.Run(() => HandleMqttDevice(device));
+ break;
+ }
+ }
+ }
+
+ public async void RemoveDevice(long deviceId)
+ {
+ if (_devices.TryRemove(deviceId, out var device))
+ {
+ device.IsConnected = false; // 终止接收数据的循环
+
+ // 对于 MQTT 协议,断开客户端连接并清理
+ if (device.Protocol == ProtocolType.MQTT && _mqttClients.TryRemove(deviceId, out var mqttClient))
+ {
+ try
+ {
+ if (mqttClient.IsConnected)
+ {
+ await mqttClient.DisconnectAsync();
+ }
+ mqttClient.Dispose();
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"设备 {deviceId} 断开 MQTT 连接时出错: {ex.Message}");
+ }
+ }
+ }
+ }
+
+ public ConcurrentDictionary GetAllDevices()
+ {
+ return _devices;
+ }
+
+ public async Task SendCommand(long deviceId, string command)
+ {
+ if (_devices.TryGetValue(deviceId, out var device))
+ {
+ var data = Encoding.UTF8.GetBytes(command);
+ switch (device.Protocol)
+ {
+ case ProtocolType.TCP:
+ using (var client = new TcpClient())
+ {
+ await client.ConnectAsync(device.IpAddress, device.Port);
+ var stream = client.GetStream();
+ await stream.WriteAsync(data, 0, data.Length);
+ }
+ break;
+ case ProtocolType.UDP:
+ using (var udpClient = new UdpClient())
+ {
+ await udpClient.SendAsync(data, data.Length, device.IpAddress, device.Port);
+ }
+ break;
+ case ProtocolType.GraphQL:
+ using (var graphQLClient = new GraphQLHttpClient(device.GraphQlEndpoint, new NewtonsoftJsonSerializer()))
+ {
+ var token = await _redis.GetAsync(RedisKeyList.DeviceTokenById(deviceId));
+ var response = await Send(device.GraphQlEndpoint, token, command);
+ }
+ break;
+ case ProtocolType.MQTT:
+ if (_mqttClients.TryGetValue(deviceId, out var mqttClient) && mqttClient.IsConnected)
+ {
+ var message = new MqttApplicationMessageBuilder()
+ .WithTopic(device.Topic)
+ .WithPayload(data)
+ .Build();
+ await mqttClient.PublishAsync(message);
+ Console.WriteLine($"设备 {deviceId} (MQTT) 发送命令 {command} 成功");
+ }
+ else
+ {
+ throw new Exception("MQTT 客户端未连接");
+ }
+ break;
+ }
+ }
+ }
+
+ private async Task HandleTcpDevice(Device device)
+ {
+ try
+ {
+ using var client = new TcpClient();
+ await client.ConnectAsync(device.IpAddress, device.Port);
+ device.IsConnected = true;
+ var buffer = new byte[1024];
+ var stream = client.GetStream();
+
+ while (device.IsConnected)
+ {
+ var bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
+ if (bytesRead > 0)
+ {
+ var data = Encoding.UTF8.GetString(buffer, 0, bytesRead);
+ device.DataReceived?.Invoke(data);
+ }
+ }
+ }
+ catch (Exception)
+ {
+ device.IsConnected = false;
+ }
+ }
+
+ private async Task HandleUdpDevice(Device device)
+ {
+ using var udpClient = new UdpClient(device.Port);
+ device.IsConnected = true;
+
+ while (device.IsConnected)
+ {
+ var result = await udpClient.ReceiveAsync();
+ var data = Encoding.UTF8.GetString(result.Buffer);
+ device.DataReceived?.Invoke(data);
+ }
+ }
+
+ private async Task HandleGraphQLDevice(Device device)
+ {
+ try
+ {
+ var user = new
+ {
+ device.username,
+ device.password
+ };
+ var jsonstr = JsonConvert.SerializeObject(user);
+ //登陆 历正设备,获取token
+ var loginUrl = $"https://{device.IpAddress}/login";
+ var req = await RequestUtil.PostAsync(loginUrl, jsonstr);
+ if (req != null)
+ {
+ var userinfo = JsonConvert.DeserializeObject(req);
+ if (userinfo != null)
+ {
+ Console.WriteLine($"设备 {device.Id} 连接成功");
+ string token = userinfo.token;
+ device.IsConnected = true;
+ await _redis.SetAsync(RedisKeyList.DeviceTokenById(device.Id), token);
+ device.GraphQlEndpoint = $"https://{device.IpAddress}/rf/graphql";//更新graphql地址
+ using var graphQLClient = new GraphQLHttpClient(device.GraphQlEndpoint, new NewtonsoftJsonSerializer());
+ while (device.IsConnected)
+ {
+ var response = await Send(device.GraphQlEndpoint, token, GraphCommand.GetFindTarget());
+ device.DataReceived?.Invoke(response.ToString());
+ await Task.Delay(1000);
+ }
+ }
+ }
+ Console.WriteLine("登录失败");
+ }
+ catch (Exception)
+ {
+ device.IsConnected = false;
+ }
+ }
+ private async Task Send(string url, string token, string cmd)
+ {
+ try
+ {
+ var httpClientHandle = new HttpClientHandler();
+ httpClientHandle.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
+ var client = new HttpClient(httpClientHandle);
+ var opt = new GraphQLHttpClientOptions() { EndPoint = new Uri(url) };
+ var graphQLClient = new GraphQLHttpClient(opt, new NewtonsoftJsonSerializer(), client);
+ var request = new GraphQL.GraphQLRequest
+ {
+ Query = cmd
+ };
+
+ graphQLClient.HttpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("Safari", "537.36"));
+ graphQLClient.HttpClient.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);
+ graphQLClient.HttpClient.DefaultRequestHeaders.Add("ContentType", "application/json");
+ var graphQLResponse = await graphQLClient.SendQueryAsync(request);
+ return graphQLResponse.Data;
+ }
+ catch { }
+ return default;
+ }
+ private async Task HandleMqttDevice(Device device)
+ {
+ try
+ {
+ var factory = new MqttFactory();
+ var mqttClient = factory.CreateMqttClient();
+
+ var options = new MqttClientOptionsBuilder()
+ .WithTcpServer(device.IpAddress, device.Port)
+ .WithCredentials(device.username, device.password)
+ .WithClientId(Guid.NewGuid().ToString())
+ .Build();
+
+ mqttClient.ConnectedAsync += async e =>
+ {
+ Console.WriteLine($"设备 {device.Id} 连接成功");
+ await mqttClient.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic(device.Topic).Build());
+ };
+
+ mqttClient.DisconnectedAsync += async e =>
+ {
+ Console.WriteLine($"设备 {device.Id} 掉线,尝试重连...");
+ device.IsConnected = false;
+ await HandleDeviceConnection(device);
+ };
+
+ mqttClient.ApplicationMessageReceivedAsync += e =>
+ {
+ // 接收到 MQTT 消息后,处理数据
+ var message = Encoding.UTF8.GetString(e.ApplicationMessage.PayloadSegment.Array);
+ device.DataReceived?.Invoke(message);
+ return Task.CompletedTask;
+ };
+
+ await mqttClient.ConnectAsync(options);
+ if (mqttClient.IsConnected)
+ {
+ _mqttClients[device.Id] = mqttClient; // 保存 MQTT 客户端实例
+ device.IsConnected = true;
+ }
+
+
+ //while (device.isconnected)
+ //{
+ // await task.delay(1000);
+ //}
+
+ // 循环结束后断开连接
+ //if (!mqttClient.IsConnected)
+ //{
+ // await mqttClient.DisconnectAsync();
+ // mqttClient.Dispose();
+ //}
+
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"设备 {device.Id} 连接失败: {ex.Message}");
+ device.IsConnected = false;
+ }
+ }
+ private async Task HandleDeviceConnection(Device device)
+ {
+ int retryDelay = 1000; // 初始重连间隔(毫秒)
+ int maxDelay = 30000; // 最大重连间隔(30秒)
+
+ while (!device.IsConnected)
+ {
+ try
+ {
+ switch (device.Protocol)
+ {
+ case ProtocolType.TCP:
+ await HandleTcpDevice(device);
+ break;
+ case ProtocolType.UDP:
+ await HandleUdpDevice(device);
+ break;
+ case ProtocolType.GraphQL:
+ await HandleGraphQLDevice(device);
+ break;
+ case ProtocolType.MQTT:
+ await HandleMqttDevice(device);
+ break;
+ }
+ if (device.IsConnected)
+ {
+ // 连接成功,重置重连间隔
+ retryDelay = 1000;
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"设备 {device.Id} 连接失败,{ex.Message},{retryDelay}ms 后重试...");
+ }
+
+ Console.WriteLine($"设备 {device.Id} 连接失败,{retryDelay}ms 后重试...");
+ await Task.Delay(retryDelay);
+ // 指数退避算法,避免频繁重试
+ retryDelay = Math.Min(retryDelay * 2, maxDelay);
+ }
+ }
+ public async Task TestConnection(long deviceId)
+ {
+ if (_devices.TryGetValue(deviceId, out var device))
+ {
+ try
+ {
+ switch (device.Protocol)
+ {
+ case ProtocolType.TCP:
+ using (TcpClient client = new TcpClient())
+ {
+ await client.ConnectAsync(device.IpAddress, device.Port);
+ return true;
+ }
+ case ProtocolType.UDP:
+ using (var udpClient = new UdpClient())
+ {
+ await udpClient.SendAsync(new byte[] { 0 }, 1, device.IpAddress, device.Port);
+ return true;
+ }
+ case ProtocolType.GraphQL:
+ using (var graphQLClient = new GraphQLHttpClient(device.GraphQlEndpoint, new NewtonsoftJsonSerializer()))
+ {
+ var request = new GraphQL.GraphQLRequest { Query = "{ deviceStatus { id status } }" };
+ var response = await graphQLClient.SendQueryAsync(request);
+ return response.Data != null;
+ }
+ case ProtocolType.MQTT:
+ var factory = new MqttFactory();
+ var mqttClient = factory.CreateMqttClient();
+ var options = new MqttClientOptionsBuilder()
+ .WithTcpServer(device.BrokerAddress)
+ .WithClientId(Guid.NewGuid().ToString())
+ .Build();
+ await mqttClient.ConnectAsync(options);
+ await mqttClient.DisconnectAsync();
+ return true;
+ default:
+ return false;
+ }
+ }
+ catch (Exception)
+ {
+ return false;
+ }
+ }
+ return false;
+ }
+
+ public Device GetDevice(long deviceId)
+ {
+ _devices.TryGetValue(deviceId, out var device);
+ return device;
+ }
+ }
+}
diff --git a/Device/Dto/LoginModel.cs b/Device/Dto/LoginModel.cs
new file mode 100644
index 0000000..eaf13eb
--- /dev/null
+++ b/Device/Dto/LoginModel.cs
@@ -0,0 +1,34 @@
+namespace LY.App.Device.Dto
+{
+ public class LoginModel
+ {
+ ///
+ /// 秘钥
+ ///
+ public string token { get; set; }
+ ///
+ /// 用户信息
+ ///
+ public User user { get; set; }
+ }
+
+ public class User
+ {
+ ///
+ ///
+ ///
+ public string id { get; set; }
+ ///
+ ///
+ ///
+ public string displayName { get; set; }
+ ///
+ ///
+ ///
+ public string imageUrl { get; set; }
+ ///
+ ///
+ ///
+ public string role { get; set; }
+ }
+}
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..8bbeab5
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,21 @@
+#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
+
+FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
+WORKDIR /app
+EXPOSE 80
+
+FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
+WORKDIR /src
+COPY ["LY.App.csproj", "."]
+RUN dotnet restore "./LY.App.csproj"
+COPY . .
+WORKDIR "/src/."
+RUN dotnet build "LY.App.csproj" -c Release -o /app/build
+
+FROM build AS publish
+RUN dotnet publish "LY.App.csproj" -c Release -o /app/publish /p:UseAppHost=false
+
+FROM base AS final
+WORKDIR /app
+COPY --from=publish /app/publish .
+ENTRYPOINT ["dotnet", "LY.App.dll"]
\ No newline at end of file
diff --git a/Extensions/DI/InjectionType.cs b/Extensions/DI/InjectionType.cs
new file mode 100644
index 0000000..f84477c
--- /dev/null
+++ b/Extensions/DI/InjectionType.cs
@@ -0,0 +1,23 @@
+namespace LY.App.Extensions.DI
+{
+ ///
+ /// Injection type
+ ///
+ public enum InjectionType
+ {
+ ///
+ /// Transient
+ ///
+ Transient,
+
+ ///
+ /// Scoped
+ ///
+ Scoped,
+
+ ///
+ /// Singleton
+ ///
+ Singleton
+ }
+}
diff --git a/Extensions/DI/ServiceInjectionAttribute.cs b/Extensions/DI/ServiceInjectionAttribute.cs
new file mode 100644
index 0000000..faaec30
--- /dev/null
+++ b/Extensions/DI/ServiceInjectionAttribute.cs
@@ -0,0 +1,43 @@
+namespace LY.App.Extensions.DI
+{
+ [AttributeUsage(AttributeTargets.Class)]
+ public class ServiceInjectionAttribute : Attribute
+ {
+ ///
+ ///
+ ///
+ public Type InterfaceType { get; set; }
+
+ ///
+ /// 注入类型
+ ///
+ public InjectionType InjectionType { get; }
+
+ ///
+ /// 服务注入
+ ///
+ public ServiceInjectionAttribute()
+ {
+ this.InjectionType = InjectionType.Scoped;
+ }
+
+ ///
+ /// 服务注入
+ ///
+ /// 注入类型
+ public ServiceInjectionAttribute(InjectionType injectionType)
+ {
+ this.InjectionType = injectionType;
+ }
+ ///
+ /// 服务注入
+ ///
+ /// 服务的接口类型
+ /// 注入的类型
+ public ServiceInjectionAttribute(Type interfaceType, InjectionType injectionType)
+ {
+ this.InterfaceType = interfaceType;
+ this.InjectionType = injectionType;
+ }
+ }
+}
diff --git a/Extensions/ServicesAutoInjectionExtension.cs b/Extensions/ServicesAutoInjectionExtension.cs
new file mode 100644
index 0000000..554b1f3
--- /dev/null
+++ b/Extensions/ServicesAutoInjectionExtension.cs
@@ -0,0 +1,69 @@
+using LY.App.Extensions.DI;
+using System.Reflection;
+
+namespace LY.App.Extensions
+{
+ public static class AutoDIExtensions
+ {
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static IServiceCollection ServicesAutoInjectionExtension(this IServiceCollection serviceCollection)
+ {
+ var directory = AppDomain.CurrentDomain.BaseDirectory;
+ var types = Directory.GetFiles(directory, "*.dll", SearchOption.TopDirectoryOnly)
+ .Where(s => !s.ToLower().Contains("sql"))
+ .Select(Assembly.LoadFrom)
+ .SelectMany(a => a.GetTypes());
+ Injection(serviceCollection, types);
+ return serviceCollection;
+ }
+ ///
+ /// 服务自动注入
+ ///
+ /// 需要自动注入服务的服务集合
+ /// 应用于每个Assembly的筛选函数
+ /// 指定的注入类型不在可注入的范围内
+ /// 指定注入的类型未实现任何服务
+ /// 输入的参数错误:1、注入的类型未实现指定的服务。2、指定的服务不是Interface类型
+ /// 自动注入服务后的服务集合
+ public static IServiceCollection ServicesAutoInjection(this IServiceCollection serviceCollection, Func selector)
+ {
+ var directory = AppDomain.CurrentDomain.BaseDirectory;
+ var types = Directory.GetFiles(directory, "*.dll", SearchOption.TopDirectoryOnly)
+ .Select(Assembly.LoadFrom)
+ .Where(selector)
+ .SelectMany(a => a.GetTypes());
+
+ Injection(serviceCollection, types);
+
+ return serviceCollection;
+ }
+ // 注入逻辑
+ private static void Injection(IServiceCollection serviceCollection, IEnumerable types)
+ {
+ foreach (var type in types)
+ {
+ var attribute = type.GetCustomAttribute();
+ if (attribute == null) continue;
+ switch (attribute.InjectionType)
+ {
+ case InjectionType.Transient:
+ serviceCollection.AddTransient(type);
+ break;
+ case InjectionType.Scoped:
+ serviceCollection.AddScoped(type);
+ break;
+ case InjectionType.Singleton:
+ serviceCollection.AddSingleton(type);
+ break;
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+
+ }
+ }
+}
diff --git a/LY - Backup.App.csproj b/LY - Backup.App.csproj
new file mode 100644
index 0000000..8e9172b
--- /dev/null
+++ b/LY - Backup.App.csproj
@@ -0,0 +1,24 @@
+
+
+
+ net6.0
+ disable
+ enable
+ Linux
+ .
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/LY.App.csproj b/LY.App.csproj
new file mode 100644
index 0000000..57ddcd1
--- /dev/null
+++ b/LY.App.csproj
@@ -0,0 +1,33 @@
+
+
+
+ net6.0
+ disable
+ enable
+ Linux
+ .
+ True
+ obj\Debug\net6.0\ly.xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/LY.App.sln b/LY.App.sln
new file mode 100644
index 0000000..87be891
--- /dev/null
+++ b/LY.App.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.7.34221.43
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LY.App", "LY.App.csproj", "{0962C31E-CDCF-438A-B1EA-9C0F1E240F93}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {0962C31E-CDCF-438A-B1EA-9C0F1E240F93}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0962C31E-CDCF-438A-B1EA-9C0F1E240F93}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0962C31E-CDCF-438A-B1EA-9C0F1E240F93}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0962C31E-CDCF-438A-B1EA-9C0F1E240F93}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {9AE26013-632A-4ACF-9F3E-307C788F4266}
+ EndGlobalSection
+EndGlobal
diff --git a/MiddleWare/CustomErrorMiddleware.cs b/MiddleWare/CustomErrorMiddleware.cs
new file mode 100644
index 0000000..686fd6b
--- /dev/null
+++ b/MiddleWare/CustomErrorMiddleware.cs
@@ -0,0 +1,104 @@
+using LY.App.Service;
+using System.Text;
+
+namespace LY.App.MiddleWare
+{
+ public class CustomErrorMiddleware
+ {
+ private readonly RequestDelegate _next;
+ private string body = string.Empty;
+ private readonly LogService _logger;
+ public CustomErrorMiddleware(RequestDelegate requestDelegate, LogService logger)
+ {
+ this._next = requestDelegate;
+ _logger = logger;
+ }
+ public async Task Invoke(HttpContext context, IConfiguration configuration)
+ {
+ try
+ {
+ // Check if the request is a form post with a file
+ if (context.Request.HasFormContentType && context.Request.Form.Files.Count > 0)
+ {
+ foreach (var file in context.Request.Form.Files)
+ {
+ // Log file information
+
+ //_logger.LogInformation($"File uploaded - Name: {file.Name}, " +
+ // $"FileName: {file.FileName}, " +
+ // $"ContentType: {file.ContentType}, " +
+ // $"Size: {file.Length} bytes");
+ }
+ }
+ else
+ {
+ context.Request.EnableBuffering();
+ using (var reader = new StreamReader(context.Request.Body,
+ encoding: Encoding.UTF8,
+ detectEncodingFromByteOrderMarks: false,
+ leaveOpen: true))
+ {
+ body = await reader.ReadToEndAsync();
+ // Do some processing with body…
+ // Reset the request body stream position so the next middleware can read it
+ context.Request.Body.Position = 0;
+ }
+ }
+
+
+ await _next.Invoke(context);
+ }
+ catch (Exception ex)
+ {
+ await HandleError(context, ex, configuration);
+ }
+ }
+ ///
+ /// 错误信息处理方法
+ ///
+ ///
+ ///
+ ///
+ private async Task HandleError(HttpContext context, Exception ex, IConfiguration configuration)
+ {
+ string pars = string.Empty;
+ var method = context.Request.Method.ToUpper();
+ var enable = configuration.GetSection("log2db").Value.ToLower();
+ if (enable == "true")
+ {
+ switch (method)
+ {
+ case "GET":
+ pars = context.Request.QueryString.ToString();
+ break;
+ case "POST":
+ // var requestReader = new StreamReader(context.Request.Body);
+ // pars = await requestReader.ReadToEndAsync();
+ pars = body;
+ break;
+ default:
+ break;
+ }
+ await _logger.AddLog(new Model.AddLog()
+ {
+ StackTrace = ex.StackTrace.Length > 1000 ? ex.StackTrace.Substring(0, 999) : ex.StackTrace,
+ Message = ex.Message,
+ Parameters = pars,
+ url = context.Request.Path,
+ });
+ }
+ Console.WriteLine($"错误消息:{ex.Message}错误追踪{ex.StackTrace}");
+ context.Response.StatusCode = 500;
+ context.Response.ContentType = "text/json;charset=utf-8;";
+ await context.Response.WriteAsJsonAsync(new { code = 1, msg = $"抱歉,服务端出错了,错误消息:{ex.Message}", data = "" });
+ }
+ }
+ public static class RequestCultureMiddlewareExtensions
+ {
+ public static IApplicationBuilder UseCustomErrorMiddleware(
+ this IApplicationBuilder builder)
+ {
+ return builder.UseMiddleware();
+ }
+ }
+}
diff --git a/Model/Alarm.cs b/Model/Alarm.cs
new file mode 100644
index 0000000..f79af3b
--- /dev/null
+++ b/Model/Alarm.cs
@@ -0,0 +1,156 @@
+using Newtonsoft.Json;
+using SqlSugar;
+using System.ComponentModel;
+
+namespace LY.App.Model
+{
+ [SplitTable(SplitType.Month)]//按年分表 (自带分表支持 年、季、月、周、日)
+ [SugarTable("ly_alarm_{year}{month}{day}")]
+ public class Alarm : BaseEntity
+ {
+ ///
+ /// 无人机批次id,
+ ///
+ [JsonConverter(typeof(ValueToStringConverter))]
+ [SugarColumn(ColumnName = "batch_id", ColumnDescription = "批次id")]
+ public long BatchId { get; set; }
+ ///
+ /// #无人机的序列号
+ ///
+ public string serial_number { get; set; }
+ ///
+ /// #无人机型号
+ ///
+ public string device_type { get; set; }
+ ///
+ ///
+ ///
+ public int device_type_8 { get; set; }
+ ///
+ /// #遥控器的纬
+ ///
+ public double app_lat { get; set; }
+ public double app_lon { get; set; }
+ public double drone_lat { get; set; }
+ public double drone_lon { get; set; }
+ public double height { get; set; }
+ public double altitude { get; set; }
+ public double home_lat { get; set; }
+ public double home_lon { get; set; }
+
+ ///
+ /// ,#东向速度
+ ///
+ public double speed_E { get; set; }
+ ///
+ /// #北向速度
+ ///
+ public double speed_N { get; set; }
+ public double speed_U { get; set; }
+ ///
+ /// #信号增益
+ ///
+ [SugarColumn(ColumnName = "rssi", ColumnDescription = "信号增益")]
+ public double RSSI { get; set; }
+
+ ///
+ /// 无人机型号
+ ///
+
+ ///
+ /// 位置id
+ ///
+ [JsonConverter(typeof(ValueToStringConverter))]
+ [SugarColumn(ColumnName = "position_id", ColumnDescription = "阵地id")]
+ public long positionId { get; set; }
+ ///
+ /// 位置id
+ ///
+ [SugarColumn(ColumnName = "position_name", ColumnDescription = "阵地名字")]
+ public string PostionName { get; set; }
+
+ [JsonConverter(typeof(ValueToStringConverter))]
+ [SugarColumn(ColumnName = "device_Id", ColumnDescription = "设备id")]
+ public long DeviceId { get; set; }
+
+ ///
+ /// 来源设备名称
+ ///
+ [SugarColumn(ColumnName = "device_name", ColumnDescription = "DeviceName")]
+ public string DeviceName { get; set; }
+ ///
+ /// 频率
+ ///
+ [SugarColumn(ColumnName = "freq", ColumnDescription = "频率")]
+ public double freq { get; set; }
+ public int alarmLevel { get; set; } = 1;
+
+ [SugarColumn(ColumnName = "time", ColumnDescription = "上传时间")]
+ public long Time { get; set; }
+ ///
+ /// 创建时间
+ ///
+ [SplitField]
+ [SugarColumn(IsNullable = true, ColumnDescription = "创建时间")]
+ public override DateTime CreateTime { get; set; } = DateTime.Now;
+ }
+ public class RevData
+ {
+ ///
+ /// 设备序列号
+ ///
+ public string product_ad_id { get; set; }
+ public IEnumerable data { get; set; }
+ ///
+ /// time
+ ///
+ public long time { get; set; }
+ public double product_lat { get; set; }
+ public double product_lon { get; set; }
+ public string product_id { get; set; }
+ }
+
+ ///
+ /// 添加报警
+ ///
+ public class AddAlarm
+ {
+ ///
+ /// #无人机的序列号
+ ///
+ public string serial_number { get; set; }
+ ///
+ /// #无人机型号
+ ///
+ public string device_type { get; set; }
+ ///
+ ///
+ ///
+ public int device_type_8 { get; set; }
+ ///
+ /// #遥控器的纬
+ ///
+ public double app_lat { get; set; }
+ public double app_lon { get; set; }
+ public double drone_lat { get; set; }
+ public double drone_lon { get; set; }
+ public double height { get; set; }
+ public double altitude { get; set; }
+ public double home_lat { get; set; }
+ public double home_lon { get; set; }
+ public double freq { get; set; }
+ ///
+ /// ,#东向速度
+ ///
+ public double speed_E { get; set; }
+ ///
+ /// #北向速度
+ ///
+ public double speed_N { get; set; }
+ public double speed_U { get; set; }
+ ///
+ /// #信号增益
+ ///
+ public double RSSI { get; set; }
+ }
+}
diff --git a/Model/AlarmRepDto.cs b/Model/AlarmRepDto.cs
new file mode 100644
index 0000000..f3bc244
--- /dev/null
+++ b/Model/AlarmRepDto.cs
@@ -0,0 +1,27 @@
+using Newtonsoft.Json;
+using SqlSugar;
+
+namespace LY.App.Model
+{
+ public class AlarmRepDto
+ {
+ public string batchId { get; set; }
+ //非数据库字段
+ public DateTime startTime { get; set; }
+ //非数据库字段
+ public DateTime endTime { get; set; }
+ public double duration { get; set; }
+ public string model { get; set; }
+ public string sn { get; set; }
+ public double Frequency { get; set; }
+ //非数据库字段
+ public bool IsWhitelist { get; set; }
+ [SqlSugar.SugarColumn(IsIgnore = true)]
+ public string Whitelist { get; set; }
+ [JsonConverter(typeof(ValueToStringConverter))]
+ public long positionId { get; set; }
+ public string positionName { get; set; }
+
+ //public long position_id { get; set; }
+ }
+}
diff --git a/Model/AlarmReq.cs b/Model/AlarmReq.cs
new file mode 100644
index 0000000..5824bbb
--- /dev/null
+++ b/Model/AlarmReq.cs
@@ -0,0 +1,42 @@
+namespace LY.App.Model
+{
+ public class AlarmReq
+ {
+ ///
+ /// 开始日期
+ ///
+ public DateTime? strartDate { get; set; }
+ public DateTime? endDate { get; set; }
+
+ public string sn { get; set; }
+ ///
+ /// 时长
+ ///
+ public int? startduration { get; set; }
+ ///
+ /// 结束时长
+ ///
+ public int? endduration { get; set; }
+ ///
+ /// 机型
+ ///
+ public string model { get; set; }
+ ///
+ /// 白名单
+ ///
+ public bool? IsWhitelist { get; set; }
+ ///
+ /// 频率
+ ///
+ public double? Frequency { get; set; }
+ ///
+ /// 页码
+ ///
+ public int pageNum { get; set; } = 1;
+ ///
+ /// 每页条数
+ ///
+ public int pageSize { get; set; } = 10;
+
+ }
+}
diff --git a/Model/ApiResult.cs b/Model/ApiResult.cs
new file mode 100644
index 0000000..fc3714e
--- /dev/null
+++ b/Model/ApiResult.cs
@@ -0,0 +1,30 @@
+namespace LY.App.Model
+{
+ public class ApiResult
+ {
+ ///
+ /// 状态码
+ ///
+ public int code { get; set; } = 0;
+ ///
+ /// 返回信息
+ ///
+ public string msg { get; set; } = "";
+ ///
+ /// 返回数据
+ ///
+ public object data { get; set; }
+
+ public ApiResult() { }
+ public ApiResult(int codeValue, string msgValue)
+ {
+ code = codeValue;
+ msg = msgValue;
+ }
+ public ApiResult(bool sucecss, string msgValue)
+ {
+ code = sucecss == true ? 0 : 1;
+ msg = msgValue;
+ }
+ }
+}
diff --git a/Model/BaseEntity.cs b/Model/BaseEntity.cs
new file mode 100644
index 0000000..a427dab
--- /dev/null
+++ b/Model/BaseEntity.cs
@@ -0,0 +1,29 @@
+using Newtonsoft.Json;
+using SqlSugar;
+
+namespace LY.App.Model
+{
+ public class BaseEntity
+ {
+ ///
+ /// ID
+ /// 精度原因,返回前端时要转成字符串
+ ///
+ [SugarColumn(IsNullable = false, IsPrimaryKey = true)]
+ [JsonConverter(typeof(ValueToStringConverter))]
+ public long Id { get; set; }
+
+ ///
+ /// 创建时间
+ ///
+ [SugarColumn(IsNullable = true, ColumnDescription = "创建时间")]
+ public virtual DateTime CreateTime { get; set; } = DateTime.Now;
+
+ ///
+ /// 是否删除
+ ///
+ [SugarColumn(IsNullable = true, ColumnDescription = "是否删除", DefaultValue = "0")]
+ [JsonIgnore]
+ public bool IsDeleted { get; set; }
+ }
+}
diff --git a/Model/BasePoint.cs b/Model/BasePoint.cs
new file mode 100644
index 0000000..ab09b19
--- /dev/null
+++ b/Model/BasePoint.cs
@@ -0,0 +1,34 @@
+namespace LY.App.Model
+{
+ public struct BasePoint
+ {
+ public BasePoint(double x, double y, double z, string time = null)
+ {
+ this.X = x;
+ this.Y = y;
+ this.Z = z;
+ this.Time = time;
+ }
+
+ public double X { get; set; }
+
+ public double Y { get; set; }
+
+ ///
+ /// 高度
+ ///
+ public double Z { get; set; }
+
+ public string Time { get; set; }
+
+ ///
+ /// 两点值是否相等
+ ///
+ ///
+ ///
+ public bool Equal(BasePoint basePoint)
+ {
+ return basePoint.X == this.X && basePoint.Y == this.Y;
+ }
+ }
+}
diff --git a/Model/DeviceEntity.cs b/Model/DeviceEntity.cs
new file mode 100644
index 0000000..720e65d
--- /dev/null
+++ b/Model/DeviceEntity.cs
@@ -0,0 +1,104 @@
+using LY.App.Device;
+using Newtonsoft.Json;
+using SqlSugar;
+
+namespace LY.App.Model
+{
+ [SugarTable("ly_device_info")]
+ public class DeviceEntity : BaseEntity
+ {
+ ///
+ ///
+ ///
+ [SugarColumn(ColumnName = "device_sn", ColumnDescription = "DeviceSN")]
+ public string DeviceSN { get; set; }
+ [SugarColumn(Length = 63, IsNullable = true, ColumnDescription = "Name")]
+ public string Name { get; set; }
+ [SugarColumn(Length = 63, IsNullable = true, ColumnDescription = "account")]
+ public string account { get; set; }
+ [SugarColumn(Length = 63, IsNullable = true, ColumnDescription = "password")]
+ public string password { get; set; }
+ [JsonConverter(typeof(ValueToStringConverter))]
+ [SugarColumn(ColumnName = "position_id", ColumnDescription = "阵地id")]
+ public long PositionId { get; set; }
+ ///
+ /// 阵地名称
+ ///
+ [SugarColumn(ColumnName = "position_name", ColumnDescription = "阵地名称")]
+ public string PositionName { get; set; }
+ public string ip { get; set; }
+ public ProtocolType Protocol { get; set; }
+ public int Port { get; set; }
+ [SugarColumn(IsNullable = true)]
+ public string GraphQlEndpoint { get; set; }
+ [SugarColumn(IsNullable = true)]
+ public string BrokerAddress { get; set; } // MQTT Broker 地址
+ [SugarColumn(IsNullable = true)]
+ public string Topic { get; set; } // MQTT 主题
+ ///
+ /// 机型
+ ///
+ [SugarColumn(Length = 63, IsNullable = true, ColumnDescription = "机型")]
+ public string Model { get; set; }
+ ///
+ /// 位置
+ ///
+ [SugarColumn(ColumnName = "address", ColumnDescription = "位置", IsNullable = true)]
+ public string Address { get; set; }
+ ///
+ /// 经度
+ ///
+ [SugarColumn(ColumnName = "lon", ColumnDescription = "经度", IsNullable = true)]
+ public double Lon { get; set; }
+ ///
+ /// 纬度
+ ///
+ [SugarColumn(ColumnName = "lat", ColumnDescription = "纬度", IsNullable = true)]
+ public double Lat { get; set; }
+ ///
+ /// 高度
+ ///
+ [SugarColumn(ColumnName = "alt", ColumnDescription = "高度", IsNullable = true)]
+ public double Alt { get; set; }
+
+
+ }
+ public class AddDevice
+ {
+ public long PositionId { get; set; }
+ ///
+ /// 阵地名称
+ ///
+ public string PositionName { get; set; }
+ public string account { get; set; }
+ public string password { get; set; }
+ public string Name { get; set; }
+ public string DeviceSN { get; set; }
+ public string ip { get; set; }
+ public ProtocolType Protocol { get; set; }
+ public int Port { get; set; }
+ public string GraphQlEndpoint { get; set; }
+ public string BrokerAddress { get; set; } // MQTT Broker 地址
+ public string Topic { get; set; } // MQTT 主题
+ ///
+ /// 机型
+ ///
+ public string Model { get; set; }
+ ///
+ /// 位置
+ ///
+ public string Address { get; set; }
+ ///
+ /// 经度
+ ///
+ public double Lon { get; set; }
+ ///
+ /// 纬度
+ ///
+ public double Lat { get; set; }
+ }
+ public class UpdateDevice : AddDevice
+ {
+ public long Id { get; set; }
+ }
+}
diff --git a/Model/LogEntity.cs b/Model/LogEntity.cs
new file mode 100644
index 0000000..0434b9a
--- /dev/null
+++ b/Model/LogEntity.cs
@@ -0,0 +1,21 @@
+using SqlSugar;
+
+namespace LY.App.Model
+{
+ [SugarTable("ly_logInfo")]
+ public class LogEntity : BaseEntity
+ {
+ public string Parameters { get; set; }
+ public string Message { get; set; }
+ public string StackTrace { get; set; }
+ public string url { get; set; }
+ }
+ public class AddLog
+ {
+ public string Message { get; set; }
+ public string Parameters { get; set; }
+
+ public string StackTrace { get; set; }
+ public string url { get; set; }
+ }
+}
diff --git a/Model/LoginModel.cs b/Model/LoginModel.cs
new file mode 100644
index 0000000..fb782c3
--- /dev/null
+++ b/Model/LoginModel.cs
@@ -0,0 +1,10 @@
+namespace LY.App.Model
+{
+ public class LoginModel
+ {
+ public string username { get; set; }
+ public string password { get; set; }
+ public string type { get; set; }
+ public string vertifyCode { get; set; }
+ }
+}
diff --git a/Model/MultPolygonEntity.cs b/Model/MultPolygonEntity.cs
new file mode 100644
index 0000000..5c27ce2
--- /dev/null
+++ b/Model/MultPolygonEntity.cs
@@ -0,0 +1,107 @@
+using LY.App.Common;
+using NetTopologySuite.Algorithm;
+using NetTopologySuite.Geometries;
+using Newtonsoft.Json;
+using Pipelines.Sockets.Unofficial.Arenas;
+using SqlSugar;
+
+namespace LY.App.Model
+{
+ ///
+ /// 多边形实体
+ ///
+ public class MultPolygonEntity : BaseEntity
+ {
+ ///
+ /// 空间位置点数据
+ ///
+ [SugarColumn(QuerySql = "st_astext(location)", ColumnDataType = "point", ColumnName = "location",
+ UpdateSql = "ST_GeomFromText(@location)",
+ InsertSql = "ST_GeomFromText(@location)", IsNullable = true, ColumnDescription = "空间位置点数据")]
+ [JsonIgnore]
+ public string? Location { get; set; } // Geometry
+ ///
+ /// 空间面数据
+ ///
+ [SugarColumn(QuerySql = "st_astext(region)", ColumnDataType = "multipolygon", ColumnName = "region",
+ UpdateSql = "ST_GeomFromText(@region)",
+ InsertSql = "ST_GeomFromText(@region)", IsNullable = true, ColumnDescription = "空间面数据")]
+ [JsonIgnore]
+ public string? Region { get; set; } //MultiPolygon Geometry
+
+ [SugarColumn(IsIgnore = true)]
+ public string RegionJson { get; set; }
+
+ ///
+ /// 空间位置点经度
+ ///
+ public double Lon { get; set; }
+
+ ///
+ /// 空间位置点纬度
+ ///
+ public double Lat { get; set; }
+
+ ///
+ /// 空间位置点Json数据
+ ///
+ public void SetRegionJson()
+ {
+ RegionJson = "";
+ if (!string.IsNullOrWhiteSpace(Region))
+ {
+ var geo = GeoJsonHelper.FromWKT(Region);
+ if (geo != null)
+ {
+ RegionJson = GeoJsonHelper.GetGeoJson(geo);
+ }
+ }
+ }
+ ///
+ /// 设置空间位置点
+ ///
+ ///
+ public void SetRegion(MultiPolygon region)
+ {
+ if (region != null)
+ {
+ Region = region.AsText();
+ }
+ }
+
+ ///
+ /// 设置空间位置点
+ ///
+ ///
+ ///
+ public void SetLocation(double lon, double lat)
+ {
+ Lon = lon;
+ Lat = lat;
+ SetLocation();
+ }
+ ///
+ /// 设置空间位置点
+ ///
+ ///
+ public void SetRegionJson(string region)
+ {
+ RegionJson = "";
+ if (!string.IsNullOrWhiteSpace(region))
+ {
+ var geo = GeoJsonHelper.FromWKT(region);
+ if (geo != null)
+ {
+ RegionJson = GeoJsonHelper.GetGeoJson(geo);
+ }
+ }
+ }
+ ///
+ /// 设置空间位置点
+ ///
+ public void SetLocation()
+ {
+ Location = $"POINT({Lon} {Lat})";
+ }
+ }
+}
diff --git a/Model/PositionDeviceDto.cs b/Model/PositionDeviceDto.cs
new file mode 100644
index 0000000..211d0e0
--- /dev/null
+++ b/Model/PositionDeviceDto.cs
@@ -0,0 +1,22 @@
+using Newtonsoft.Json;
+using SqlSugar;
+
+namespace LY.App.Model
+{
+ public class PositionDeviceDto
+ {
+ public PositionInfo position { get; set; }
+ public List Devices { get; set; }
+ }
+ public class DeviceItem
+ {
+ [JsonConverter(typeof(ValueToStringConverter))]
+ public long Id { get; set; }
+ public string Name { get; set; }
+ public string DeviceSN { get; set; }
+ public double Lat { get; set; }
+ public double Lon { get; set; }
+ public string Model { get; set; }
+
+ }
+}
diff --git a/Model/PositionInfo.cs b/Model/PositionInfo.cs
new file mode 100644
index 0000000..1990ae3
--- /dev/null
+++ b/Model/PositionInfo.cs
@@ -0,0 +1,153 @@
+using SqlSugar;
+
+namespace LY.App.Model
+{
+ ///
+ /// 位置信息
+ ///
+ [SugarTable("ly_position")]
+ public class PositionInfo: MultPolygonEntity
+ {
+ ///
+ /// 名称
+ ///
+ [SugarColumn(Length = 63, IsNullable = true, ColumnDescription = "名称")]
+ public string Name { get; set; }
+ ///
+ /// 地址
+ ///
+ [SugarColumn(IsNullable = true, ColumnDescription = "地址")]
+
+ public string Address { get; set; }
+
+ ///
+ /// 联系人
+ ///
+ [SugarColumn(Length = 63, IsNullable = true, ColumnDescription = "联系人")]
+ public string ContactName { get; set; }
+ ///
+ /// 联系人电话
+ ///
+ [SugarColumn(Length = 31, IsNullable = true, ColumnDescription = "联系人电话")]
+ public string ContactTel { get; set; }
+
+ ///
+ /// 图片文件名
+ ///
+ [SugarColumn(IsNullable = true, ColumnDescription = "图片地址")]
+ public string ImageName { get; set; }
+
+ ///
+ /// 图片地址
+ ///
+ [SugarColumn(IsIgnore = true)]
+ public string ImageUrl { get; set; }
+
+ ///
+ /// 图片缩略图地址
+ ///
+ [SugarColumn(IsIgnore = true)]
+ public string ImageBriefUrl { get; set; }
+
+ ///
+ /// 启用时间
+ ///
+ [SugarColumn(IsNullable = true, ColumnDescription = "启用时间")]
+ public DateTime? EnableTime { get; set; }
+
+ ///
+ /// 是否启用
+ ///
+ [SugarColumn(ColumnDescription = "是否启用")]
+ public bool Enabled { get; set; }
+
+ ///
+ /// 状态
+ ///
+ [SugarColumn(IsNullable = true, ColumnDescription = "状态")]
+ public string Status { get; set; } = "离线";
+
+ ///
+ /// 备注
+ ///
+ [SugarColumn(IsNullable = true, ColumnDescription = "备注")]
+ public string Remarks { get; set; }
+ }
+ ///
+ /// 添加
+ ///
+ public class AddPosition
+ {
+
+ ///
+ /// 名称
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// 空间数据geojson
+ ///
+
+ public string RegionJson { get; set; }
+
+ ///
+ /// 经度
+ ///
+ public double Lon { get; set; }
+
+ ///
+ /// 纬度
+ ///
+ public double Lat { get; set; }
+
+ ///
+ /// 地址
+ ///
+
+ public string Address { get; set; }
+
+ ///
+ /// 联系人
+ ///
+ public string ContactName { get; set; }
+ ///
+ /// 联系人电话
+ ///
+ public string ContactTel { get; set; }
+
+ ///
+ /// 图片文件名
+ ///
+ public string ImageName { get; set; }
+
+ ///
+ /// 启用时间
+ ///
+ public DateTime? EnableTime { get; set; }
+
+ ///
+ /// 是否启用
+ ///
+ public bool Enabled { get; set; }
+
+ ///
+ /// 状态
+ ///
+ public string Status { get; set; } = "离线";
+ ///
+ /// 备注
+ ///
+ public string Remarks { get; set; }
+
+ }
+ ///
+ /// 更新
+ ///
+ public class UpdatePosition : AddPosition
+ {
+ ///
+ /// 主键id
+ ///
+ public long Id { get; set; }
+ }
+}
diff --git a/Model/PositionQueryInput.cs b/Model/PositionQueryInput.cs
new file mode 100644
index 0000000..1999f92
--- /dev/null
+++ b/Model/PositionQueryInput.cs
@@ -0,0 +1,15 @@
+namespace LY.App.Model
+{
+ public class PositionQueryInput
+ {
+ public string Name { get; set; }
+ ///
+ /// 页码
+ ///
+ public int pageNum { get; set; } = 1;
+ ///
+ /// 每页条数
+ ///
+ public int pageSize { get; set; } = 10;
+ }
+}
diff --git a/Model/UserEntity.cs b/Model/UserEntity.cs
new file mode 100644
index 0000000..b530e9f
--- /dev/null
+++ b/Model/UserEntity.cs
@@ -0,0 +1,70 @@
+using SqlSugar;
+
+namespace LY.App.Model
+{
+ [SugarTable("ly_user")]
+ public class UserEntity
+ {
+ [Newtonsoft.Json.JsonConverter(typeof(ValueToStringConverter))]
+ [SugarColumn(IsPrimaryKey = true)]//long类型的主键会自动赋值
+ public long Id { get; set; }
+ ///
+ /// 用户名
+ ///
+ public string Name { get; set; }
+ ///
+ /// 密码
+ ///
+ public string Password { get; set; }
+ ///
+ /// 登录时间
+ ///
+ public DateTime LoginTime { get; set; }
+ ///
+ /// 创建时间
+ ///
+ public DateTime CreateTime { get; set; }
+
+ ///
+ /// 上次更新密码时间
+ ///
+ public DateTime? UpdatePwdTime { get; set; }
+ ///
+ /// 删除状态
+ ///
+ public bool Disable { get; set; }
+ ///
+ /// 邮箱
+ ///
+ public string Email { get; set; }
+ ///
+ /// 是否管理员,如果不是管理员,不可操作
+ ///
+ public bool IsAdmin { get; set; }
+ }
+ public class AddUser
+ {
+ ///
+ /// 用户名
+ ///
+ public string Name { get; set; }
+ ///
+ /// 密码
+ ///
+ public string Password { get; set; }
+ ///
+ /// 是否管理员,如果不是管理员,不可操作
+ ///
+ public bool IsAdmin { get; set; }
+ }
+ public class UpdateUser:AddUser
+ {
+ public long Id { get; set; }
+ }
+ public class UpdatePwdDto
+ {
+ public long Id { get; set; }
+ public string oldPwd { get; set; }
+ public string newPwd { get; set; }
+ }
+}
diff --git a/Program.cs b/Program.cs
new file mode 100644
index 0000000..5fa82c8
--- /dev/null
+++ b/Program.cs
@@ -0,0 +1,142 @@
+using LY.App.Extensions;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.OpenApi.Models;
+using Newtonsoft.Json.Serialization;
+using Newtonsoft.Json;
+using SqlSugar;
+using Microsoft.AspNetCore.Cors.Infrastructure;
+using LY.App.Common.Redis;
+using LY.App.Service;
+using LY.App.Model;
+using LY.App.MiddleWare;
+using LY.App.Common.WebSocket;
+
+var builder = WebApplication.CreateBuilder(args);
+
+// Add services to the container.
+
+builder.Services.AddControllers(options =>
+{
+ // ʽ [Required] ԱǶԷǿɿ͵
+ options.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true;
+}).AddNewtonsoftJson()
+ .AddNewtonsoftJson(NewtonsoftInitialize); ;
+// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
+builder.Services.AddEndpointsApiExplorer();
+builder.Services.AddSwaggerGen(
+ options =>
+ {
+ options.SwaggerDoc("v1", new OpenApiInfo()
+ {
+ Title = "LY.App-Api",
+ Version = "v1",
+ Description = "Apiӿĵ",
+ });
+ var path = Path.Combine(AppContext.BaseDirectory, "ly.xml");
+ options.IncludeXmlComments(path, true);
+ options.OrderActionsBy(_ => _.RelativePath);
+ });
+builder.Services.AddCors(CorsOptionsEvnet);
+//redis
+string redisConnection = builder.Configuration.GetValue("Redis:ConnectionString") ?? "localhost:6379";
+
+// ע RedisService
+builder.Services.AddSingleton(new RedisService(redisConnection));
+////עSignalR
+builder.Services.AddSignalR();
+//עע
+builder.Services.ServicesAutoInjectionExtension();
+//ݿ
+var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
+//sqlsugar
+builder.Services.AddTransient(sp =>
+{
+ return new SqlSugarClient(new ConnectionConfig()
+ {
+ ConnectionString = connectionString,
+ DbType = DbType.MySql,
+ IsAutoCloseConnection = true,
+ ConfigureExternalServices = new ConfigureExternalServices()
+ {
+ EntityService = (x, p) => //
+ {
+ //ųDTO
+ p.DbColumnName = UtilMethods.ToUnderLine(p.DbColumnName);//ToUnderLineշת»߷
+ }
+ },
+ InitKeyType = InitKeyType.Attribute // ʹΪ
+ }, db =>
+ {
+
+#if DEBUG
+ db.Aop.OnLogExecuting = (sql, pars) =>
+ {
+ Console.WriteLine(sql + "ֵ" + db.Utilities.SerializeObject(pars.ToDictionary(it => it.ParameterName, it => it.Value)));
+ };
+ //ݿͱִһ
+ //db.DbMaintenance.CreateDatabase();
+ db.CodeFirst.SetStringDefaultLength(2000).InitTables(typeof(LogEntity));
+#endif
+ //д
+ // db.QueryFilter.AddTableFilter(it => it.IsDeleted == false);
+ });
+});
+
+SnowFlakeSingle.WorkId = Convert.ToInt32(builder.Configuration.GetSection("SnowFlakeWordId")?.Value ?? "1");
+var app = builder.Build();
+ServiceLocator.Instance = app.Services;
+var device = app.Services.GetService();
+device?.Init();
+
+// Configure the HTTP request pipeline.
+if (app.Environment.IsDevelopment())
+{
+ app.UseSwagger();
+ app.UseSwaggerUI();
+}
+//·ƥ
+app.UseRouting();
+app.UseAuthorization();
+app.UseCors("CorsPolicy");
+
+//쳣м
+app.UseMiddleware();
+//ִƥĶ˵
+app.UseEndpoints(endpoints =>
+{
+ endpoints.MapHub("/notification");
+ endpoints.MapControllers();
+});
+
+app.MapControllers();
+app.Run();
+
+static void NewtonsoftInitialize(MvcNewtonsoftJsonOptions options)
+{
+ //ѭ
+ options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
+ //Ĭϸʽ
+ options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss";
+ //һ
+ options.SerializerSettings.ContractResolver = new DefaultContractResolver();
+ //ֵ
+ //options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
+ options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
+}
+///
+/// ¼
+///
+static void CorsOptionsEvnet(CorsOptions options)
+{
+ options.AddPolicy("CorsPolicy", cors =>
+ {
+ cors.AllowAnyOrigin();
+ cors.AllowAnyHeader();
+ cors.AllowAnyMethod();
+ });
+}
+public static class ServiceLocator
+{
+ public static IServiceProvider Instance { get; set; }
+}
+
diff --git a/Properties/launchSettings.json b/Properties/launchSettings.json
new file mode 100644
index 0000000..afda599
--- /dev/null
+++ b/Properties/launchSettings.json
@@ -0,0 +1,40 @@
+{
+ "profiles": {
+ "LY.App": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "dotnetRunMessages": true,
+ "applicationUrl": "http://localhost:5233"
+ },
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "Docker": {
+ "commandName": "Docker",
+ "launchBrowser": true,
+ "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger",
+ "environmentVariables": {
+ "ASPNETCORE_URLS": "http://+:80"
+ },
+ "publishAllPorts": true
+ }
+ },
+ "$schema": "https://json.schemastore.org/launchsettings.json",
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:53161",
+ "sslPort": 0
+ }
+ }
+}
\ No newline at end of file
diff --git a/Service/AlarmService.cs b/Service/AlarmService.cs
new file mode 100644
index 0000000..5e69b68
--- /dev/null
+++ b/Service/AlarmService.cs
@@ -0,0 +1,277 @@
+using LY.App.Common;
+using LY.App.Common.Redis;
+using LY.App.Common.WebSocket;
+using LY.App.Extensions.DI;
+using LY.App.Model;
+using Mapster;
+using Microsoft.AspNetCore.SignalR;
+using SqlSugar;
+using StackExchange.Redis;
+
+namespace LY.App.Service
+{
+ ///
+ /// 报警服务
+ ///
+ [ServiceInjection(InjectionType.Transient)]
+ public class AlarmService
+ {
+ private readonly SqlSugarClient _db;
+ private readonly IConfiguration _config;
+ private readonly RedisService _redisService;
+ private readonly PushService _pushService;
+ public AlarmService(SqlSugarClient db, IConfiguration config, RedisService redisService, PushService pushService)
+ {
+ _db = db;
+ _config = config;
+ _redisService = redisService;
+ _pushService = pushService;
+ }
+ ///
+ /// 新增报警信息
+ ///
+ ///
+ ///
+ public async Task AddAlarm(RevData input)
+ {
+ await SetDeviceStataus(input);
+ if (input.data.Any())
+ {
+ var key = RedisKeyList.DeviceInfo(input.product_ad_id);
+ var deviceinfo = await _redisService.GetAsync(key);
+ if (deviceinfo == null)
+ {
+ deviceinfo = await _db.CopyNew().Queryable().Where(s => s.DeviceSN == input.product_ad_id).FirstAsync();
+ if (deviceinfo == null)
+ {
+ return new ApiResult() { code = 1, msg = "设备不存在" };
+ }
+ await _redisService.SetAsync(key, deviceinfo, TimeSpan.FromDays(1));
+ }
+ var entity = input.data.Adapt>();
+ foreach (var item in entity)
+ {
+ item.BatchId = await GetBatId(item.serial_number);
+ item.DeviceId = deviceinfo.Id;
+ item.DeviceName = deviceinfo.Name;
+ item.positionId = deviceinfo.PositionId;
+ item.PostionName = deviceinfo.PositionName;
+ item.Time = input.time;
+ }
+ await _db.CopyNew().Insertable(entity).SplitTable().ExecuteReturnSnowflakeIdListAsync();
+ //推送报警信息
+ await _pushService.SendMessageToAll(new { msgType = "event", data = entity });
+ }
+ return new ApiResult();
+ }
+ ///
+ /// 推送消息
+ ///
+ ///
+ ///
+ public async Task PushMessage(object message)
+ {
+ //推送前端无人机数据
+ await _pushService.SendMessageToAll(message);
+ }
+ ///
+ /// 设置设备在线状态,并更新数据
+ ///
+ ///
+ ///
+ private async Task SetDeviceStataus(RevData input)
+ {
+ await _redisService.SetAsync(RedisKeyList.DeviceStatus(input.product_ad_id), true, TimeSpan.FromSeconds(5));
+ //更新 设备缓存
+ var key = RedisKeyList.DeviceInfo(input.product_ad_id);
+ var deviceinfo = await _redisService.GetAsync(key);
+ if (deviceinfo == null)
+ {
+ deviceinfo.Lat = input.product_lat;
+ deviceinfo.Lon = input.product_lon;
+ await _redisService.SetAsync(key, deviceinfo);
+ }
+ }
+ private async Task GetBatId(string droneId)
+ {
+ var timeSpan = Convert.ToDouble(_config["BatchId"]);
+ var key = RedisKeyList.BatchIdBysn(droneId);
+ //从redis取出batchid,如果没有,就新加一个,每次访问都重置一下过期时间来模拟滑动过期
+ var batchId = await _redisService.GetAsync(key);
+ if (batchId == 0)
+ {
+ batchId = SnowFlakeSingle.Instance.NextId();
+ }
+ await _redisService.SetAsync(key, batchId, TimeSpan.FromSeconds(timeSpan));
+ return batchId;
+ }
+ ///
+ /// //根据batchId获取报警信息
+ ///
+ ///
+ ///
+ public async Task GetByBatchId(long batchId)
+ {
+ var items = await _db.Queryable().SplitTable()
+ .Where(s => s.BatchId == batchId)
+ .OrderBy(s => s.Id).Select(s => new
+ {
+ Lon = s.drone_lon,
+ Lat = s.drone_lat,
+ Alt = s.height,
+ s.CreateTime,
+ AlarmLevel = s.alarmLevel,
+ }).ToListAsync();
+ return new ApiResult() { data = items };
+ }
+ ///
+ /// 分页
+ ///
+ ///
+ ///
+ public async Task GetPage(AlarmReq input)
+ {
+ var result = await CreatePage(input);
+ return new ApiResult()
+ {
+ code = 0,
+ data = new
+ {
+ total = result.Item1,
+ items = result.Item2
+ }
+ };
+ }
+ ///
+ /// 热力图
+ ///
+ ///
+ public async Task hotmap()
+ {
+ var query = await _db.Queryable().SplitTable()
+ .GroupBy(x => new { Lon = SqlFunc.Round(x.drone_lon, 4), Lat = SqlFunc.Round(x.drone_lat, 4) }) // 按经纬度分组
+ .Select(x => new
+ {
+ Lon = SqlFunc.Round(x.drone_lon, 4),
+ Lat = SqlFunc.Round(x.drone_lat, 4),
+ Count = SqlFunc.AggregateCount(x.BatchId), // 统计去重后的BatchId
+ })
+ .ToListAsync();
+ return new ApiResult() { data = query };
+ }
+ ///
+ /// 列表
+ ///
+ ///
+ ///
+ public async Task>> CreatePage(AlarmReq input)
+ {
+ RefAsync total = 0;
+ var items = await _db.Queryable().SplitTable()
+ .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 })
+ .Select(st => new AlarmRepDto
+ {
+ batchId = st.BatchId.ToString(),
+ startTime = SqlFunc.AggregateMin(st.CreateTime),
+ endTime = SqlFunc.AggregateMax(st.CreateTime),
+ sn = st.serial_number,
+ 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
+ }).MergeTable()//合并查询
+ .ToPageListAsync(input.pageNum, input.pageSize, total);
+ return Tuple.Create(total.Value, items);
+ }
+
+ ///
+ /// 报表统计
+ ///
+ ///
+ public async Task GetReport(DateTime? start, DateTime? end)
+ {
+ start = start.HasValue ? start.Value : DateTime.Now.AddMonths(-1);
+ end = end.HasValue ? end.Value.AddDays(1) : DateTime.Now.Date.AddDays(1);
+ var query = await _db.Queryable().SplitTable()
+ .WhereIF(start.HasValue, st => st.CreateTime >= start.Value)
+ .WhereIF(end.HasValue, st => st.CreateTime <= end.Value.AddDays(1))
+ .GroupBy(s => new { s.BatchId, s.serial_number, s.device_type, s.positionId, s.PostionName, s.DeviceId, s.DeviceName })
+ .Select(st => new
+ {
+ batchId = st.BatchId,
+ startTime = SqlFunc.AggregateMin(st.CreateTime),
+ endTime = SqlFunc.AggregateMax(st.CreateTime),
+ sn = st.serial_number,
+ Frequency = SqlFunc.AggregateMax(st.freq),
+ duration = (SqlFunc.AggregateMax(st.CreateTime) - SqlFunc.AggregateMin(st.CreateTime)).TotalSeconds,
+ model = st.device_type,
+ positionName = st.PostionName,
+ deviceId = st.DeviceId,
+ deviceName = st.DeviceName
+ }).MergeTable()//合并查询
+ .ToListAsync();
+ return new ApiResult()
+ {
+ data = new
+ {
+ freq = query.GroupBy(s => new { freq = FreqConvert.CoverFreq(s.Frequency) })
+ .Select(b => new
+ {
+ b.Key.freq,
+ count = b.Count()
+ }),
+ model = query.GroupBy(s => new { s.model })
+ .Select(b => new
+ {
+ b.Key.model,
+ count = b.Count()
+ }),
+ position = query.GroupBy(s => new { s.positionName })
+ .Select(s => new
+ {
+ s.Key.positionName,
+ count = s.Count()
+ }),
+ hotmap = await GenerateHotMap(query.Select(s => s.batchId).ToList()),
+ device = query.GroupBy(s => new { s.deviceName })
+ .Select(s => new
+ {
+ s.Key.deviceName,
+ count = s.Count()
+ }),
+ date = query.GroupBy(s => s.startTime.ToString("yyyy-MM-dd"))
+ .Select(it => new { it.Key, count = it.Count() })
+ }
+ };
+ }
+ ///
+ /// 热力图,取经纬度4位数
+ ///
+ ///
+ ///
+ private async Task