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 GenerateHotMap(List batchIds) + { + return await _db.Queryable().SplitTable() + .Where(s => s.drone_lat > 0 && s.drone_lon > 0 && batchIds.Contains(s.BatchId)) + .Select(s => new + { + drone_lon = SqlFunc.Round(s.drone_lon, 4), + drone_lat = SqlFunc.Round(s.drone_lat, 4), + }) + .GroupBy(s => new { s.drone_lat, s.drone_lon }) + .Select(s => new + { + Key = new { Lon = SqlFunc.Round(s.drone_lat, 4), Lat = SqlFunc.Round(s.drone_lon, 4) }, + val = SqlFunc.AggregateCount(s) + }) + .ToListAsync(); + } + } +} diff --git a/Service/DeviceService.cs b/Service/DeviceService.cs new file mode 100644 index 0000000..8ef8b3b --- /dev/null +++ b/Service/DeviceService.cs @@ -0,0 +1,191 @@ +using GraphQL; +using LY.App.Common.Redis; +using LY.App.Device; +using LY.App.Extensions.DI; +using LY.App.Model; +using Mapster; +using Newtonsoft.Json; +using SqlSugar; + +namespace LY.App.Service +{ + [ServiceInjection(InjectionType.Transient)] + public class DeviceService + { + private readonly SqlSugarClient _db; + private readonly IConfiguration _config; + private readonly RedisService _redisService; + private readonly AlarmService _alarmService; + private readonly DeviceManager deviceManager = DeviceManager.Instance; + public DeviceService(SqlSugarClient db, IConfiguration config, RedisService redisService, AlarmService alarmService) + { + _db = db; + _config = config; + _redisService = redisService; + _alarmService = alarmService; + } + + /// + /// 分页查询 + /// + /// + /// + /// + /// + public async Task List(int pageNum, int pageSize, string key) + { + RefAsync total = 0; + var items = await _db.Queryable() + .Where(s => s.IsDeleted == false) + .WhereIF(!string.IsNullOrEmpty(key), s => s.Name.Contains(key) || s.DeviceSN.Contains(key)) + .OrderByDescending(s => s.Id) + .ToPageListAsync(pageNum, pageSize, total); + return new + { + total = total.Value, + items + }; + } + /// + /// 新增设备 + /// + /// + /// + public async Task Add(AddDevice input) + { + var entity = input.Adapt(); + var id = await _db.Insertable(entity).ExecuteReturnSnowflakeIdAsync(); + entity.Id = id; + await AddDevice2Manager(entity); + var key = RedisKeyList.DeviceInfo(entity.DeviceSN); + await _redisService.SetAsync(key, entity, TimeSpan.FromDays(10)); + return new ApiResult() + { + code = 0, + msg = "success" + }; + } + /// + /// 更新设备信息 + /// + /// + /// + public async Task updata(UpdateDevice input) + { + var entity = input.Adapt(); + await _db.Updateable(entity).ExecuteCommandAsync(); + return new ApiResult() + { + code = 0, + msg = "success" + }; + } + /// + /// 删除设备 + /// + /// + /// + public async Task delete(long Id) + { + var entity = await _db.Queryable().FirstAsync(s => s.Id == Id); + entity.IsDeleted = true; + await _db.Updateable(entity).ExecuteCommandAsync(); + deviceManager.RemoveDevice(entity.Id); + return new ApiResult() + { + code = 0, + msg = "success" + }; + } + /// + /// 取单个设备详情 + /// + /// + /// + public async Task detail(long Id) + { + return await _db.Queryable() + .FirstAsync(s => s.Id == Id); + } + + /// + /// 获取首页数据 + /// + /// + public async Task> All() + { + var result = await _db.Queryable() + .Where(s => s.IsDeleted == false) + .ToListAsync(); + return result; + } + + private Task AddDevice2Manager(DeviceEntity entity) + { + var device = new Device.Device() + { + Id = entity.Id, + Port = entity.Port, + GraphQlEndpoint = entity.GraphQlEndpoint, + Topic = entity.Topic, + IpAddress = entity.ip, + Protocol = entity.Protocol, + username = entity.account, + password = entity.password, + DataReceived = async (data) => + { + try + { + var input = JsonConvert.DeserializeObject(data); + Console.WriteLine($"rev data:{data}"); + await _alarmService.AddAlarm(input); + } + catch (Exception ex) + { + Console.WriteLine($"error:{ex.Message},data:{data}"); + } + } + }; + deviceManager.AddDevice(device); + return Task.CompletedTask; + } + + /// + /// 初始化,加载所有设备 + /// + /// + public async Task Init() + { + var devices = await _db.Queryable().Where(s => s.IsDeleted == false).ToListAsync(); + foreach (var item in devices) + { + var key = RedisKeyList.DeviceInfo(item.DeviceSN); + await _redisService.SetAsync(key, item, TimeSpan.FromDays(10)); + await AddDevice2Manager(item); + } + //deviceManager.AddDevice(new Device.Device() + //{ + // Id = 1111, + // Port = 1883, + // GraphQlEndpoint = "http://localhost:5000/graphql", + // Topic = "F010/uav_dynamic_data/beijing", + // IpAddress = "110.42.53.32", + // Protocol = ProtocolType.MQTT, + // username = "yangzi", + // password = "64B7qvztS38l", + // DataReceived = async (data) => + // { + // try + // { + // var input = JsonConvert.DeserializeObject(data); + // await _alarmService.AddAlarm(input); + // } + // catch (Exception ex) + // { + // Console.WriteLine($"error:{ex.Message},data:{data}"); + // } + // } + //}); + } + } +} diff --git a/Service/LogService.cs b/Service/LogService.cs new file mode 100644 index 0000000..8f84f82 --- /dev/null +++ b/Service/LogService.cs @@ -0,0 +1,24 @@ +using Azure.Core; +using LY.App.Extensions.DI; +using LY.App.Model; +using Mapster; +using SqlSugar; +using System.Security.AccessControl; + +namespace LY.App.Service +{ + [ServiceInjection(InjectionType.Transient)] + public class LogService + { + private readonly SqlSugarClient _db; + public LogService(SqlSugarClient sqlSugarClient) + { + _db = sqlSugarClient; + } + public async Task AddLog(AddLog input) + { + var entity = input.Adapt(); + await _db.Insertable(entity).ExecuteReturnSnowflakeIdAsync(); + } + } +} diff --git a/Service/PositionService.cs b/Service/PositionService.cs new file mode 100644 index 0000000..ff6458c --- /dev/null +++ b/Service/PositionService.cs @@ -0,0 +1,156 @@ +using LY.App.Common; +using LY.App.Extensions.DI; +using LY.App.Model; +using Mapster; +using Microsoft.AspNetCore.Http; +using NetTopologySuite.Geometries; +using NetTopologySuite.GeometriesGraph; +using SqlSugar; +using System.Xml.Schema; + +namespace LY.App.Service +{ + /// + /// 阵地服务 + /// + [ServiceInjection(InjectionType.Transient)] + public class PositionService + { + private readonly SqlSugarClient _db; + public PositionService(SqlSugarClient db) + { + _db = db; + } + /// + /// 添加 + /// + /// + /// + public async Task MainAdd(AddPosition input) + { + var entity = input.Adapt(); + entity.CreateTime = DateTime.Now; + //名称重复判断 + var isNameExist = await _db.Queryable().CountAsync(a => a.Name == input.Name && a.IsDeleted != true); + if (isNameExist > 0) + { + return new ApiResult(false, "名称已存在"); + } + if (!string.IsNullOrWhiteSpace(entity.RegionJson)) + { + if (GeoJsonHelper.TryGetGeomWKTFromGeoJson(entity.RegionJson, out MultiPolygon geo)) + { + entity.SetRegion(geo); + } + else + { + return new ApiResult(false, "空间数据无效"); + } + } + // 位置 + entity.SetLocation(); + await _db.Insertable(entity).ExecuteReturnSnowflakeIdAsync(); + return new ApiResult(true, "添加成功"); + } + public async Task Update(UpdatePosition input) + { + var entity = input.Adapt(); + await _db.Queryable().FirstAsync(a => a.Id == input.Id && a.IsDeleted != true); + if (entity != null) + { + if (!string.IsNullOrWhiteSpace(entity.RegionJson)) + { + if (GeoJsonHelper.TryGetGeomWKTFromGeoJson(entity.RegionJson, out MultiPolygon geo)) + { + entity.SetRegion(geo); + } + else + { + return new ApiResult(false, "空间数据无效"); + } + } + // 位置 + entity.SetLocation(); + await _db.Updateable(entity).ExecuteCommandAsync(); + return new ApiResult(true, "添加成功"); + } + return new ApiResult(false, "未找到要更新的对象"); + } + /// + /// + /// + /// + /// + public async Task Delete(long id) + { + var entity = await _db.Queryable().FirstAsync(a => a.Id == id && a.IsDeleted != true); + if (entity != null) + { + entity.IsDeleted = true; + await _db.Updateable(entity).ExecuteCommandAsync(); + return new ApiResult(true, "删除成功"); + } + return new ApiResult(false, "未找到要删除的对象"); + } + /// + /// 获取 + /// + /// + /// + public async Task Get(int id) + { + var entity = await _db.Queryable().FirstAsync(a => a.Id == id && a.IsDeleted != true); + if (entity != null) + { + return new ApiResult() { data = entity }; + } + return new ApiResult(false, "未找到要获取的对象"); + } + /// + /// 获取列表 + /// + /// + /// + public async Task GetList(PositionQueryInput input) + { + var query = _db.Queryable() + .Where(a => a.IsDeleted != true) + .WhereIF(!string.IsNullOrWhiteSpace(input.Name), a => a.Name.Contains(input.Name)) + .OrderBy(a => a.Id, OrderByType.Desc); + var result = await query.ToPageListAsync(input.pageNum, input.pageSize); + return new ApiResult() + { + data = new + { + items = result, + totalCount = query.Count() + } + }; + } + + /// + /// 首页数据 + /// + /// + public async Task> Index() + { + var query = await _db.Queryable() + .Where(a => a.IsDeleted != true).ToListAsync(); + var positionIds = query.Select(s => s.Id).ToList(); + var deviceList = await _db.Queryable() + .Where(s => positionIds.Contains(s.PositionId)) + .Where(s => s.IsDeleted == false) + .ToListAsync(); + List result = new List(); + foreach (var item in query) + { + result.Add(new PositionDeviceDto() + { + position = item, + Devices = deviceList.Where(s => s.PositionId == item.Id).ToList().Adapt>() + }); + } + return result; + } + } +} diff --git a/Service/UserService.cs b/Service/UserService.cs new file mode 100644 index 0000000..c06d684 --- /dev/null +++ b/Service/UserService.cs @@ -0,0 +1,237 @@ +using GraphQL; +using LY.App.Common.Cypher; +using LY.App.Common.Redis; +using LY.App.Model; +using Mapster; +using Microsoft.IdentityModel.Tokens; +using SqlSugar; +using StackExchange.Redis; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; + +namespace LY.App.Service +{ + public class UserService + { + private readonly SqlSugarClient _db; + private readonly IConfiguration _config; + private readonly RedisService _redisService; + public UserService(SqlSugarClient db, IConfiguration config, RedisService redisService) + { + _db = db; + _config = config; + _redisService = redisService; + } + #region 增删改查 + + public async Task Add(AddUser input) + { + var exists = await _db.Queryable().AnyAsync(s => s.Name == input.Name && s.Disable == false); + if (!exists) + { + var entity = input.Adapt(); + // var entity = _mapper.Map(input); + entity.CreateTime = DateTime.Now; + entity.UpdatePwdTime = DateTime.Now; + entity.Password = MD5CypherUtil.Hash("ly_" + input.Password); + var Id = await _db.Insertable(entity).ExecuteReturnSnowflakeIdAsync(); + return new ApiResult() { code = 0, data = Id.ToString() }; + } + else + { + return new ApiResult() { code = 1, msg = "已存在该用户名" }; + } + } + /// + /// 删除用户,标记删除,不是物理删除 + /// + /// + /// + public async Task SoftDelete(IEnumerable ids) + { + if (ids.Any()) + { + foreach (var item in ids) + { + var entity = await _db.Queryable().FirstAsync(s => s.Id == item); + if (entity?.Name != "admin") + { + entity.Disable = entity.Disable == true ? false : true; + await _db.Updateable(entity).ExecuteCommandAsync(); + } + else + return "admin 不可删除!"; + } + return ""; + } + return "ids不能为空"; + } + public async Task GetUserById(long userId) + { + var entity = await _db.Queryable().FirstAsync(s => s.Id == userId); + if (entity != null) + { + entity.Password = ""; + return new ApiResult() { code = 0, data = entity }; + } + return null; + } + /// + /// 监测登录次数 + /// + /// + /// + private async Task CheckLoginNum(string username) + { + int num = 0; + // var node = SettingHelper.Instance.GetNode("Vertify", "LoginFailed"); + var node = _config["Vertify"]; + var key = RedisKeyList.LogNum(username); + var val = await _redisService.GetAsync(key); + if (num >= int.Parse(node)) + { + return true; + } + return false; + } + public async Task Login(LoginModel input) + { + if (await CheckLoginNum(input.username)) + { + return new ApiResult() + { + code = 1, + msg = "连续登录失败5次,锁定30分钟" + }; + } + var password = MD5CypherUtil.Hash("ly_" + input.password); + var users = await _db.Queryable().ToListAsync(); + var entity = await _db.Queryable() + .Where(s => s.Disable == false && + s.Name == input.username && s.Password == password).FirstAsync(); + if (entity == null) + { + await SetLoginNum(input.username); + return new ApiResult() + { + code = 1, + msg = "请检查用户名跟密码!" + }; + } + entity.LoginTime = DateTime.Now; + await _db.Updateable(entity).ExecuteCommandAsync(); + await RemoveLoginNum(input.username); + string token = CreateToken(entity); + //加入redis + await _redisService.SetAsync(RedisKeyList.TokenUser(input.username), + token, TimeSpan.FromSeconds(60 * 60 * 24 * 7)); + return new ApiResult() + { + code = 1, + data = new + { + token, + expires = DateTime.UtcNow.AddSeconds(60*60*24*7), + isAdmin = entity.IsAdmin, + userid = entity.Id.ToString() + } + }; + } + private async Task RemoveLoginNum(string username) + { + var key = $"login_num_{username}"; + await _redisService.DeleteAsync(key); + } + private async Task SetLoginNum(string username) + { + var key = RedisKeyList.LogNum(username); + var num = await _redisService.GetAsync(key); + await _redisService.SetAsync(key, num + 1, TimeSpan.FromMinutes(30)); + } + private string CreateToken(UserEntity user) + { + // 创建JWT的密钥 + var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Token:SecretKey"])); + var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256); + + // 创建Claims + var claims = new[] + { + new Claim(JwtRegisteredClaimNames.Sub,user.Name), + new Claim(JwtRegisteredClaimNames.Sid, user.Id.ToString()) + // 还可以添加其他需要的Claims + }; + + // 创建Token + var token = new JwtSecurityToken( + issuer: _config["Token:Issuer"], + audience: _config["Token:Audience"], + claims: claims, + expires: DateTime.UtcNow.AddSeconds(60 * 60 * 24 * 7), // 设置Token过期时间 + signingCredentials: credentials + ); + // 生成Token字符串 + var tokenString = new JwtSecurityTokenHandler().WriteToken(token); + return tokenString; + } + /// + /// + /// + /// + /// + /// + /// + public async Task GetPage(int pageSize, int pageNum, string key) + { + RefAsync total = 0; + var query = await _db.Queryable() + .Where(s => s.Disable == false).ToPageListAsync(pageNum, pageSize, total); + return new + { + total = total.Value, + items = query + }; + } + + /// + /// 更新密码 + /// + /// + /// + public async Task UpdatePwd(UpdatePwdDto input) + { + var md5 = MD5CypherUtil.Hash("ly_" + input.oldPwd); + var entity = await _db.Queryable().FirstAsync(s => s.Id == input.Id); + if (entity.Password == md5) + { + entity.Password = MD5CypherUtil.Hash("ly_" + input.newPwd); + entity.UpdatePwdTime = DateTime.Now; + await _db.Updateable(entity).UpdateColumns(s => new + { + s.Password, + s.UpdatePwdTime + }).ExecuteCommandAsync(); + return ""; + } + return "原密码不正确"; + } + public async Task Update(UpdateUser input) + { + if (!string.IsNullOrEmpty(input.Name)) + { + var entity = input.Adapt(); + await _db.Updateable(entity).UpdateColumns(it => new + { + it.UpdatePwdTime, + it.Password, + it.Email, + it.IsAdmin + }).ExecuteCommandAsync(); + return null; + } + return "用户名不能为空"; + } + #endregion + } +} diff --git a/WeatherForecast.cs b/WeatherForecast.cs new file mode 100644 index 0000000..968a67c --- /dev/null +++ b/WeatherForecast.cs @@ -0,0 +1,13 @@ +namespace LY.App +{ + public class WeatherForecast + { + public DateTime Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public string? Summary { get; set; } + } +} \ No newline at end of file diff --git a/appsettings.Development.json b/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/appsettings.json b/appsettings.json new file mode 100644 index 0000000..36b653f --- /dev/null +++ b/appsettings.json @@ -0,0 +1,32 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "log2db": true, //是否记录 + "AllowedHosts": "*", + "ConnectionStrings": { + "DefaultConnection": "server=101.43.201.20;port=3307;database=lyapp;user=root;password=Aa123;Pooling=true;" + }, + "Token": { + "SecretKey": "HWLSNPM+OhlFe4wwEV/teSWsxGjrWbxKnHonxW5Z+mFlQq3zonv5", + "Issuer": "ly_server_issuer", + "Audience": "ly_server_audience", + "AccessExpires": 86400, + "RefreshExpires": 43200, + "Admin": "GYJlKgVaHPThTGvw8iB9n/SQiApYPiVaisHHzs9z47eBI3TxseU7WYihNRLGTMcekVfgtcaD05r+hdOTpA0obMlMpb0I6lVFtaw2iUGXqpwleqtBOy5ajlE/cG3THTDUhkYJTwpAYt3mwPnvFc2gVskVUpzA3g9n33jHKhQQyIM=" + }, + "Swagger": { + "Title": "LY.App-Api", + "Description": "接口文档", + "Version": "v1.0" + }, + "Redis": { + "ConnectionString": "101.43.201.20:6379,password=Aa123,abortConnect =false" + }, + "Vertify": 5, //登录失败次数 + "BatchId": 60, //无人机批次连续时间 + "SnowFlakeWordId": 1 //雪花算法的wordId,多台服务器时,需要配置不同的wordId,最多32个节点,可以考虑加到环境变量中 +}