From 050b991508b258e521d1756e598ce51fa18ddeb7 Mon Sep 17 00:00:00 2001 From: faker <1784913417@qq.com> Date: Mon, 8 May 2023 16:22:04 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=A1=B9=E7=9B=AE=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .ionide/symbolCache.db | Bin 0 -> 8192 bytes .../DesignTimeBuild/.dtbcache.v2 | Bin 0 -> 321503 bytes .../config/applicationhost.config | 998 ++++ .vs/Znyc.Dispatching/v16/.suo | Bin 0 -> 269824 bytes .vscode/launch.json | 69 + .vscode/solution-explorer/class.cs-template | 8 + .vscode/solution-explorer/class.ts-template | 3 + .vscode/solution-explorer/class.vb-template | 9 + .vscode/solution-explorer/default.ts-template | 3 + .vscode/solution-explorer/enum.cs-template | 8 + .../solution-explorer/interface.cs-template | 8 + .../solution-explorer/interface.ts-template | 3 + .vscode/solution-explorer/template-list.json | 46 + .../solution-explorer/template-parameters.js | 17 + .vscode/tasks.json | 24 + docker/Dockerfile | 34 + src/.dockerignore | 25 + src/.gitignore | 28 + src/.vscode/launch.json | 34 + .../solution-explorer/class.cs-template | 8 + .../solution-explorer/class.ts-template | 3 + .../solution-explorer/class.vb-template | 9 + .../solution-explorer/default.ts-template | 3 + .../solution-explorer/enum.cs-template | 8 + .../solution-explorer/interface.cs-template | 8 + .../solution-explorer/interface.ts-template | 3 + .../solution-explorer/template-list.json | 46 + .../solution-explorer/template-parameters.js | 17 + src/.vscode/tasks.json | 42 + src/Dockerfile | 33 + .../ServiceTests/LoginServiceTest.cs | 18 + .../ServiceTests/VehicleServiceTest.cs | 77 + src/Znyc.Dispatchimg.Test/Startup.cs | 32 + src/Znyc.Dispatchimg.Test/UnitTest1.cs | 13 + .../Znyc.Dispatching.Tests.csproj | 26 + .../Cache/Services/CacheService.cs | 1235 ++++ .../Cache/Services/ICacheService.cs | 554 ++ .../Company/Dto/Input/CompanyAddInput.cs | 59 + .../Company/Dto/Input/CompanyUpdateInput.cs | 9 + .../Company/Dto/Input/RegisterInuput.cs | 61 + .../Company/Dto/OutPut/CompanyOutput.cs | 81 + .../Company/Services/CompanyService.cs | 307 + .../Company/Services/ICompanyService.cs | 68 + .../Dto/Input/ConstructionAddInput.cs | 32 + .../Dto/Input/ConstructionUpdateInput.cs | 14 + .../Dto/Output/ConstructionOutput.cs | 56 + .../Services/ConstructionService.cs | 151 + .../Services/IConstructionService.cs | 31 + .../Dto/Input/DictionaryAddInput.cs | 38 + .../Dto/Input/DictionaryUpdateInput.cs | 18 + .../Dictionary/Dto/Output/DictionaryOutput.cs | 30 + .../Dictionary/Services/DictionaryService.cs | 52 + .../Dictionary/Services/IDictionaryService.cs | 22 + .../Document/Dto/Output/BucketTokenOutput.cs | 24 + .../Document/Services/DocumentService.cs | 58 + .../Document/Services/IDocumentService.cs | 11 + .../Employee/Dto/Input/EmployeeAddInput.cs | 33 + .../Employee/Dto/Input/EmployeeUpdateInput.cs | 17 + .../Employee/Dto/OutPut/EmployeeListOutput.cs | 58 + .../Employee/Dto/OutPut/EmployeeOutput.cs | 52 + .../Employee/Dto/OutPut/NoticeOutput.cs | 7 + .../Employee/Services/EmployeeService.cs | 305 + .../Employee/Services/IEmployeeService.cs | 50 + .../Job/Dto/Input/JobInput.cs | 83 + .../Job/Dto/Output/JobOutput.cs | 78 + .../Job/IJobService.cs | 10 + .../Job/JobService.cs | 277 + .../LogAudit/Services/ILogAuditService.cs | 6 + .../LogAudit/Services/LogAuditService.cs | 14 + .../LogEx/Services/ILogExService.cs | 6 + .../LogEx/Services/LogExService.cs | 22 + .../LogOp/Services/ILogOpService.cs | 6 + .../LogOp/Services/LogOpService.cs | 14 + .../Login/Dto/Input/LoginInput.cs | 34 + .../Login/Dto/Output/LoginOutput.cs | 22 + .../Login/Services/ILoginService.cs | 14 + .../Login/Services/LoginService.cs | 182 + .../Mapper/Mapper.cs | 22 + .../Menu/Dto/Input/MenuInput.cs | 91 + .../Menu/Dto/OutPut/MenuOutput.cs | 20 + .../Menu/Services/IMenuService.cs | 10 + .../Menu/Services/MenuService.cs | 80 + .../Message/Dto/Input/MessageAddInput.cs | 27 + .../Message/Dto/OutPut/MessageOutput.cs | 27 + .../Message/Services/IMessageService.cs | 6 + .../Message/Services/MessageService.cs | 61 + .../Dto/Input/MessageRecordAddInput.cs | 23 + .../Dto/OutPut/MessageRecordOutput.cs | 22 + .../Services/IMessageRecordService.cs | 6 + .../Services/MessageRecordService.cs | 31 + .../Oil/Dto/Input/OilAddInput.cs | 51 + .../Oil/Dto/Input/OilUpdateInput.cs | 9 + .../Oil/Dto/OutPut/CensusOilOutput.cs | 59 + .../Oil/Dto/OutPut/OilOutput.cs | 42 + .../Oil/Services/IOilService.cs | 59 + .../Oil/Services/OilService.cs | 169 + .../Dto/Input/Assign/AssignOrderInput.cs | 49 + .../Dto/Input/Assign/AssignUpdateInput.cs | 14 + .../Order/Dto/Input/OrderAddInput.cs | 88 + .../Order/Dto/Input/OrderUpdateInput.cs | 9 + .../Order/Dto/Input/Sign/SignInput.cs | 23 + .../Order/Dto/Input/Sign/SignUpdateInput.cs | 10 + .../Dto/Input/StayAssign/StayAssignInput.cs | 70 + .../Input/StayAssign/StayAssignUpdateInput.cs | 14 + .../Order/Dto/OutPut/AdminOrderListOutput.cs | 17 + .../Order/Dto/OutPut/OrderListOutput.cs | 148 + .../Order/Dto/OutPut/OrderOutput.cs | 225 + .../OrderStatisticsConstructionOutput.cs | 53 + .../Order/Dto/OutPut/OrderStatisticsOutput.cs | 53 + .../Order/Services/IOrderService.cs | 56 + .../Order/Services/OrderService.cs | 1644 ++++++ .../Dto/Input/OrderVehicleAddInput.cs | 28 + .../Dto/OutPut/OrderVehicleListOutput .cs | 49 + .../Dto/OutPut/OrderVehicleOutput.cs | 28 + .../OrderVisa/Dto/Input/OrderVisaInput.cs | 12 + .../OrderVisa/Dto/OutPut/OrderVisaOutput.cs | 13 + .../Project/Dto/Input/ProjectAddInput.cs | 57 + .../Project/Dto/Input/ProjectUpdateInput.cs | 9 + .../Project/Dto/OutPut/ProjectListOutput.cs | 66 + .../Project/Dto/OutPut/ProjectOutput.cs | 63 + .../Project/Services/IProjectService.cs | 48 + .../Project/Services/ProjectService.cs | 294 + .../Dto/Input/ProjectPersonAddInput.cs | 25 + .../Dto/Input/ProjectPersonUpdateInput.cs | 9 + .../Dto/OutPut/ProjectPersonOutput.cs | 22 + .../Services/IProjectPersonService.cs | 10 + .../Services/ProjectPersonService.cs | 44 + .../Report/Dto/Input/ReportAddInput.cs | 13 + .../Report/Services/IReportService.cs | 9 + .../Report/Services/ReportService.cs | 51 + .../Role/Dto/OutPut/RoleOutput.cs | 23 + .../Role/Services/IRoleService.cs | 10 + .../Role/Services/RoleService.cs | 58 + .../RoleMenu/Dto/OutPut/RoleMenuListOutput.cs | 11 + .../RoleMenu/Services/IRoleMenuService.cs | 27 + .../RoleMenu/Services/RoleMenuService.cs | 132 + .../User/Dto/Input/DecryptPhoneAddInput.cs | 23 + .../User/Dto/Input/UserAddInput.cs | 41 + .../User/Dto/Input/UserChangePwdInput.cs | 37 + .../User/Dto/Input/UserUpdateInput.cs | 32 + .../User/Dto/Output/UserOutput.cs | 42 + .../User/Services/IUserService.cs | 22 + .../User/Services/UserService.cs | 440 ++ .../UserRole/Services/IUserRoleService.cs | 13 + .../UserRole/Services/UserRoleService.cs | 61 + .../Dto/Input/BasicsVehicleUpdateInput.cs | 37 + .../Dto/Input/TrackPlayBackCorrectInput.cs | 43 + .../Vehicle/Dto/Input/VehicleAddInput.cs | 95 + .../Vehicle/Dto/Input/VehicleUpdateInput.cs | 36 + .../Vehicle/Dto/OutPut/AlarmOutput.cs | 27 + .../Dto/OutPut/MapMonitorDataOutput.cs | 242 + .../Dto/OutPut/PgyAlertStraightDtlOutput.cs | 75 + .../Dto/OutPut/TrackPlayBackCorrectOutput.cs | 76 + .../Vehicle/Dto/OutPut/TrackPlaybackOutput.cs | 184 + .../Dto/OutPut/VehicleBaiscListOutput.cs | 57 + .../Vehicle/Dto/OutPut/VehicleGpsOutput.cs | 207 + .../Vehicle/Dto/OutPut/VehicleListOutput.cs | 89 + .../Vehicle/Services/IVehicleService.cs | 125 + .../Vehicle/Services/VehicleService.cs | 1255 ++++ .../Dto/Input/VehicleGroupAddInput.cs | 20 + .../Dto/OutPut/VehicleGroupOutput.cs | 32 + .../Services/IVehicleGroupService.cs | 15 + .../Services/VehicleGroupService.cs | 90 + .../Dto/Input/VehiclePersonAddInput.cs | 32 + .../Dto/Input/VehiclePersonUpdateInput.cs | 11 + .../Dto/OutPut/VehiclePersonOutput.cs | 42 + .../Services/IVehiclePersonService.cs | 10 + .../Services/VehiclePersonService.cs | 38 + .../Dto/Input/VehicleTypeInsertInput.cs | 19 + .../Dto/Input/VehicleTypeUpdateInput.cs | 14 + .../Dto/OutPut/VehicleTypeOutput.cs | 59 + .../Services/IVehicleTypeService.cs | 14 + .../Services/VehicleTypeService.cs | 113 + .../Services/IWxUserRelationService.cs | 15 + .../Services/WxUserRelationService.cs | 50 + .../Yard/Dto/Input/YardAddInput.cs | 41 + .../Yard/Dto/Input/YardUpdateInput.cs | 9 + .../Yard/Dto/OutPut/YardListOutput.cs | 28 + .../Yard/Dto/OutPut/YardOutput.cs | 47 + .../Yard/Services/IYardService.cs | 7 + .../Yard/Services/YardService.cs | 202 + .../Znyc.Dispatching.Application.csproj | 59 + .../Znyc.Dispatching.Application.xml | 5205 +++++++++++++++++ .../applicationconfig.Development.json | 15 + .../applicationconfig.Production.json | 15 + .../applicationconfig.Staging.json | 15 + .../Extensions/GenericTypeExtensions.cs | 30 + .../Extensions/LinqExtensions.cs | 27 + .../Znyc.Dispatching.Common.csproj | 17 + .../Cache/CacheOptions.cs | 15 + .../Cache/IRedisCache.cs | 154 + src/Znyc.Dispatching.Core/Cache/RedisCache.cs | 167 + .../Config/SmsProviderOptions.cs | 33 + .../Config/UploadInfoOptions.cs | 21 + .../Config/UploadOptions.cs | 94 + .../Config/WeixinSettingOptions.cs | 51 + .../Const/ApiGroupConsts.cs | 22 + src/Znyc.Dispatching.Core/Const/ClaimConst.cs | 35 + .../Const/CommonConst.cs | 233 + src/Znyc.Dispatching.Core/Entitys/Company.cs | 106 + .../Entitys/Construction.cs | 39 + .../Entitys/DEntityBase.cs | 76 + .../Entitys/Dictionary.cs | 62 + src/Znyc.Dispatching.Core/Entitys/Employee.cs | 78 + src/Znyc.Dispatching.Core/Entitys/Job.cs | 81 + src/Znyc.Dispatching.Core/Entitys/LogAudit.cs | 61 + src/Znyc.Dispatching.Core/Entitys/LogEx.cs | 81 + src/Znyc.Dispatching.Core/Entitys/LogOp.cs | 123 + src/Znyc.Dispatching.Core/Entitys/LogVis.cs | 83 + src/Znyc.Dispatching.Core/Entitys/Menu.cs | 145 + src/Znyc.Dispatching.Core/Entitys/Message.cs | 58 + .../Entitys/MessageRecord.cs | 37 + src/Znyc.Dispatching.Core/Entitys/Oil.cs | 55 + src/Znyc.Dispatching.Core/Entitys/Order.cs | 164 + .../Entitys/OrderVehicle.cs | 43 + .../Entitys/OrderVisa.cs | 30 + src/Znyc.Dispatching.Core/Entitys/Project.cs | 64 + .../Entitys/ProjectPerson.cs | 35 + src/Znyc.Dispatching.Core/Entitys/Report.cs | 50 + src/Znyc.Dispatching.Core/Entitys/Role.cs | 43 + src/Znyc.Dispatching.Core/Entitys/RoleMenu.cs | 26 + src/Znyc.Dispatching.Core/Entitys/Salesman.cs | 28 + src/Znyc.Dispatching.Core/Entitys/User.cs | 49 + src/Znyc.Dispatching.Core/Entitys/UserRole.cs | 26 + src/Znyc.Dispatching.Core/Entitys/UserYard.cs | 37 + src/Znyc.Dispatching.Core/Entitys/Vehicle.cs | 180 + .../Entitys/VehicleGroup.cs | 37 + .../Entitys/VehiclePerson.cs | 46 + .../Entitys/VehicleType.cs | 37 + .../Entitys/WxUserRelation.cs | 45 + src/Znyc.Dispatching.Core/Entitys/Yard.cs | 58 + .../Enums/AccStatusEnum.cs | 20 + .../Enums/CommonStatusEnum.cs | 30 + .../Enums/DataOpTypeEnum.cs | 78 + src/Znyc.Dispatching.Core/Enums/ErrorCode.cs | 379 ++ .../Enums/FileLocationEnum.cs | 30 + .../Enums/GpsStatusEnum.cs | 25 + .../Enums/HangFireQueuesConfig.cs | 29 + .../Enums/HttpStatusCodeEnum.cs | 212 + .../Enums/LoginTypeEnum.cs | 35 + .../Enums/MenuTypeEnum.cs | 25 + .../Enums/NoticeUserStatusEnum.cs | 20 + .../Enums/OrderSourceEnum.cs | 24 + .../Enums/OrderStatus.cs | 82 + .../Enums/PlatformTypeEnum.cs | 22 + .../Enums/QueryTypeEnum.cs | 50 + .../Enums/RequestTypeEnum.cs | 33 + .../Enums/RoleStatusEnum.cs | 65 + .../Enums/StopStatusEnum.cs | 25 + .../Enums/TerminalTypeEnum.cs | 15 + .../Enums/WorkStatusEnum.cs | 20 + .../Enums/YesOrNotEnum.cs | 20 + .../Extension/DateTimeExtensions.cs | 36 + .../Extension/DictionaryExtensions.cs | 52 + .../Extension/EnumExtensions.cs | 317 + .../Extension/InputBase.cs | 71 + .../Extension/ObjectExtensions.cs | 233 + .../Extension/PageInputOrder.cs | 28 + .../Extension/RestfulResultProvider.cs | 159 + .../Extension/ReturnPageResult.cs | 45 + .../Extension/StringExtensions.cs | 110 + src/Znyc.Dispatching.Core/Files/FileInfo.cs | 82 + src/Znyc.Dispatching.Core/Files/FileSize.cs | 97 + .../Files/FileSizeUnit.cs | 30 + .../Filter/LogExceptionHandler.cs | 56 + .../Filter/RequestActionFilter.cs | 79 + .../Filter/XSSFilterAttribute.cs | 85 + .../Helpers/ByteConvertHelper.cs | 45 + .../Helpers/ConvertGps.cs | 154 + .../Helpers/ConvertHelper.cs | 868 +++ .../Helpers/DateTimeHelper.cs | 47 + .../Helpers/DatetimeJsonConverter.cs | 74 + .../Helpers/HtmlHelper.cs | 70 + .../Helpers/HttpContextHelper.cs | 27 + .../Helpers/HttpRequestHelper.cs | 221 + .../Helpers/MapHelper/DistanceHelper.cs | 213 + .../Helpers/MapHelper/MapHelper.cs | 353 ++ .../MapHelper/MapModel/LocationModel.cs | 20 + .../Helpers/MapHelper/MapModel/MapLocation.cs | 59 + .../Helpers/MapHelper/MapModel/Mileage.cs | 34 + .../MapHelper/MapModel/PathPlanning.cs | 120 + .../MapHelper/MapModel/PathPlanningModel.cs | 86 + .../Helpers/MapHelper/MapModel/Point.cs | 29 + .../Helpers/MapHelper/MapModel/PointLatLng.cs | 18 + .../Helpers/RegexHelper.cs | 257 + .../Helpers/SmsHelper.cs | 68 + .../Helpers/Snowflake.cs | 155 + .../Helpers/StringHelper.cs | 244 + .../Helpers/TimeHelper.cs | 125 + .../Helpers/UploadHelper.cs | 132 + .../Helpers/UtilConvert.cs | 252 + .../Helpers/XSSHelper.cs | 28 + .../HostedService/LogHostedService.cs | 130 + .../Manager/IUserManager.cs | 13 + .../Manager/UserManager.cs | 129 + .../Options/RefreshTokenSettingOptions.cs | 15 + src/Znyc.Dispatching.Core/Rpc/IHttp.cs | 11 + .../SimpleQueue/IConcurrentQueue.cs | 13 + .../SimpleQueue/SimpleQueue.cs | 55 + .../SqlProxy/Company/ICompanySql .cs | 25 + .../Util/OSSClientUtil.cs | 300 + .../Util/ReflectionUtil.cs | 26 + .../Znyc.Dispatching.Core.csproj | 46 + .../Znyc.Dispatching.Core.xml | 4995 ++++++++++++++++ .../coreconfig.Development.json | 36 + .../coreconfig.Production.json | 35 + .../coreconfig.Staging.json | 34 + ...6113493f7a4e73b20c145787e2b4ff.failure.txt | 8 + ...nyc.Dispatching.Database.Migrations.csproj | 10 + .../DbContexts/DefaultDbContext.cs | 177 + .../EntityFrameworkStartup.cs | 24 + ...yc.Dispatching.EntityFramework.Core.csproj | 36 + .../dbsettings.Development.json | 7 + .../dbsettings.Production.json | 6 + .../dbsettings.Staging.json | 5 + .../InMemoryEventBusSubscriptionsManager.cs | 174 + .../EventBusSubscriptions/SubscriptionInfo.cs | 29 + .../IDynamicIntegrationEventHandler.cs | 13 + .../Eventbus/IEventBus.cs | 49 + .../Eventbus/IEventBusSubscriptionsManager.cs | 44 + .../Eventbus/IIntegrationEventHandler.cs | 23 + .../Eventbus/IntegrationEvent.cs | 29 + .../RabbitMQPersistent/EventBusRabbitMQ.cs | 336 ++ .../IRabbitMQPersistentConnection.cs | 19 + .../RabbitMQPersistentConnection.cs | 161 + .../Znyc.Dispatching.Evenbus.csproj | 21 + .../Collection/GpsCarAccDuration.cs | 58 + .../Collection/GpsRealTime.cs | 149 + .../Collection/PgyAlertStraightDtl.cs | 57 + .../Collection/PgyAlertStraightLis.cs | 72 + .../Collection/StopPointCalculation.cs | 51 + .../Collection/VehicleGps.cs | 95 + .../IRepositorys/IGpsRealTimeRepository.cs | 23 + .../IRepositorys/IMongodbBaseRepository.cs | 54 + .../IPgyAlertStraightDtlRepository.cs | 32 + .../IPgyAlertStraightLisRepository.cs | 13 + .../MongoContext.cs | 23 + .../MongoDbStartup.cs | 28 + .../Repositorys/GpsRealTimeRepository.cs | 86 + .../Repositorys/MongodbBaseRepository.cs | 97 + .../PgyAlertStraightDtlRepository.cs | 87 + .../PgyAlertStraightLisRepository.cs | 71 + ...Znyc.Dispatching.MongoDb.Repository.csproj | 34 + .../mongodbsettings.Development.json | 7 + .../mongodbsettings.Production.json | 6 + .../mongodbsettings.Staging.json | 7 + src/Znyc.Dispatching.Tasks/HangfireDispose.cs | 47 + ...utoEveryNightTrackPlaybackCorrectionJob.cs | 23 + .../TaskJobs/AutoOrderJob.cs | 28 + .../TaskJobs/AutoRoleMenuJob.cs | 38 + .../AutoTrackPlaybackCorrectionJob.cs | 23 + src/Znyc.Dispatching.Tasks/TasksStartup.cs | 69 + .../Znyc.Dispatching.Tasks.csproj | 19 + .../WxApplet/WxAppletSubscribeMessage.cs | 109 + .../Weixin/WeixinTemplateApi.cs | 8 + .../Weixin/WeixinTemplate_AssignOrder.cs | 51 + .../TemplateMessage/WxOpen/CommonApi.cs | 230 + .../TemplateMessage/WxOpen/SnsApi.cs | 35 + .../WxOpenTemplateMessage_AssignOrder.cs | 43 + .../Helper/SnsHelper.cs | 62 + .../WeChatStartup.cs | 33 + .../Znyc.Dispatching.WeChat.Core.csproj | 36 + .../wechatsettings.Development.json | 18 + .../wechatsettings.Production.json | 19 + .../wechatsettings.Staging.json | 18 + .../Handlers/JwtHandler.cs | 96 + src/Znyc.Dispatching.Web.Core/Startup.cs | 96 + .../Znyc.Dispatching.WeChat.Core.xml | 30 + .../Znyc.Dispatching.Web.Core.csproj | 19 + .../Znyc.Dispatching.Web.Core.xml | 34 + src/Znyc.Dispatching.Web.Entry/Program.cs | 13 + .../PublishProfiles/FolderProfile.pubxml | 16 + .../PublishProfiles/FolderProfile1.pubxml | 16 + .../Properties/launchSettings.json | 36 + .../Znyc.Dispatching.Web.Entry.csproj | 52 + .../appsettings.Development.json | 89 + .../appsettings.Production.json | 93 + .../appsettings.Staging.json | 89 + src/Znyc.Dispatching.Web.Entry/nlog.config | 30 + src/Znyc.Dispatching.sln | 85 + 380 files changed, 38926 insertions(+) create mode 100644 .ionide/symbolCache.db create mode 100644 .vs/Znyc.Dispatching/DesignTimeBuild/.dtbcache.v2 create mode 100644 .vs/Znyc.Dispatching/config/applicationhost.config create mode 100644 .vs/Znyc.Dispatching/v16/.suo create mode 100644 .vscode/launch.json create mode 100644 .vscode/solution-explorer/class.cs-template create mode 100644 .vscode/solution-explorer/class.ts-template create mode 100644 .vscode/solution-explorer/class.vb-template create mode 100644 .vscode/solution-explorer/default.ts-template create mode 100644 .vscode/solution-explorer/enum.cs-template create mode 100644 .vscode/solution-explorer/interface.cs-template create mode 100644 .vscode/solution-explorer/interface.ts-template create mode 100644 .vscode/solution-explorer/template-list.json create mode 100644 .vscode/solution-explorer/template-parameters.js create mode 100644 .vscode/tasks.json create mode 100644 docker/Dockerfile create mode 100644 src/.dockerignore create mode 100644 src/.gitignore create mode 100644 src/.vscode/launch.json create mode 100644 src/.vscode/solution-explorer/class.cs-template create mode 100644 src/.vscode/solution-explorer/class.ts-template create mode 100644 src/.vscode/solution-explorer/class.vb-template create mode 100644 src/.vscode/solution-explorer/default.ts-template create mode 100644 src/.vscode/solution-explorer/enum.cs-template create mode 100644 src/.vscode/solution-explorer/interface.cs-template create mode 100644 src/.vscode/solution-explorer/interface.ts-template create mode 100644 src/.vscode/solution-explorer/template-list.json create mode 100644 src/.vscode/solution-explorer/template-parameters.js create mode 100644 src/.vscode/tasks.json create mode 100644 src/Dockerfile create mode 100644 src/Znyc.Dispatchimg.Test/ServiceTests/LoginServiceTest.cs create mode 100644 src/Znyc.Dispatchimg.Test/ServiceTests/VehicleServiceTest.cs create mode 100644 src/Znyc.Dispatchimg.Test/Startup.cs create mode 100644 src/Znyc.Dispatchimg.Test/UnitTest1.cs create mode 100644 src/Znyc.Dispatchimg.Test/Znyc.Dispatching.Tests.csproj create mode 100644 src/Znyc.Dispatching.Application/Cache/Services/CacheService.cs create mode 100644 src/Znyc.Dispatching.Application/Cache/Services/ICacheService.cs create mode 100644 src/Znyc.Dispatching.Application/Company/Dto/Input/CompanyAddInput.cs create mode 100644 src/Znyc.Dispatching.Application/Company/Dto/Input/CompanyUpdateInput.cs create mode 100644 src/Znyc.Dispatching.Application/Company/Dto/Input/RegisterInuput.cs create mode 100644 src/Znyc.Dispatching.Application/Company/Dto/OutPut/CompanyOutput.cs create mode 100644 src/Znyc.Dispatching.Application/Company/Services/CompanyService.cs create mode 100644 src/Znyc.Dispatching.Application/Company/Services/ICompanyService.cs create mode 100644 src/Znyc.Dispatching.Application/Construction/Dto/Input/ConstructionAddInput.cs create mode 100644 src/Znyc.Dispatching.Application/Construction/Dto/Input/ConstructionUpdateInput.cs create mode 100644 src/Znyc.Dispatching.Application/Construction/Dto/Output/ConstructionOutput.cs create mode 100644 src/Znyc.Dispatching.Application/Construction/Services/ConstructionService.cs create mode 100644 src/Znyc.Dispatching.Application/Construction/Services/IConstructionService.cs create mode 100644 src/Znyc.Dispatching.Application/Dictionary/Dto/Input/DictionaryAddInput.cs create mode 100644 src/Znyc.Dispatching.Application/Dictionary/Dto/Input/DictionaryUpdateInput.cs create mode 100644 src/Znyc.Dispatching.Application/Dictionary/Dto/Output/DictionaryOutput.cs create mode 100644 src/Znyc.Dispatching.Application/Dictionary/Services/DictionaryService.cs create mode 100644 src/Znyc.Dispatching.Application/Dictionary/Services/IDictionaryService.cs create mode 100644 src/Znyc.Dispatching.Application/Document/Dto/Output/BucketTokenOutput.cs create mode 100644 src/Znyc.Dispatching.Application/Document/Services/DocumentService.cs create mode 100644 src/Znyc.Dispatching.Application/Document/Services/IDocumentService.cs create mode 100644 src/Znyc.Dispatching.Application/Employee/Dto/Input/EmployeeAddInput.cs create mode 100644 src/Znyc.Dispatching.Application/Employee/Dto/Input/EmployeeUpdateInput.cs create mode 100644 src/Znyc.Dispatching.Application/Employee/Dto/OutPut/EmployeeListOutput.cs create mode 100644 src/Znyc.Dispatching.Application/Employee/Dto/OutPut/EmployeeOutput.cs create mode 100644 src/Znyc.Dispatching.Application/Employee/Dto/OutPut/NoticeOutput.cs create mode 100644 src/Znyc.Dispatching.Application/Employee/Services/EmployeeService.cs create mode 100644 src/Znyc.Dispatching.Application/Employee/Services/IEmployeeService.cs create mode 100644 src/Znyc.Dispatching.Application/Job/Dto/Input/JobInput.cs create mode 100644 src/Znyc.Dispatching.Application/Job/Dto/Output/JobOutput.cs create mode 100644 src/Znyc.Dispatching.Application/Job/IJobService.cs create mode 100644 src/Znyc.Dispatching.Application/Job/JobService.cs create mode 100644 src/Znyc.Dispatching.Application/LogAudit/Services/ILogAuditService.cs create mode 100644 src/Znyc.Dispatching.Application/LogAudit/Services/LogAuditService.cs create mode 100644 src/Znyc.Dispatching.Application/LogEx/Services/ILogExService.cs create mode 100644 src/Znyc.Dispatching.Application/LogEx/Services/LogExService.cs create mode 100644 src/Znyc.Dispatching.Application/LogOp/Services/ILogOpService.cs create mode 100644 src/Znyc.Dispatching.Application/LogOp/Services/LogOpService.cs create mode 100644 src/Znyc.Dispatching.Application/Login/Dto/Input/LoginInput.cs create mode 100644 src/Znyc.Dispatching.Application/Login/Dto/Output/LoginOutput.cs create mode 100644 src/Znyc.Dispatching.Application/Login/Services/ILoginService.cs create mode 100644 src/Znyc.Dispatching.Application/Login/Services/LoginService.cs create mode 100644 src/Znyc.Dispatching.Application/Mapper/Mapper.cs create mode 100644 src/Znyc.Dispatching.Application/Menu/Dto/Input/MenuInput.cs create mode 100644 src/Znyc.Dispatching.Application/Menu/Dto/OutPut/MenuOutput.cs create mode 100644 src/Znyc.Dispatching.Application/Menu/Services/IMenuService.cs create mode 100644 src/Znyc.Dispatching.Application/Menu/Services/MenuService.cs create mode 100644 src/Znyc.Dispatching.Application/Message/Dto/Input/MessageAddInput.cs create mode 100644 src/Znyc.Dispatching.Application/Message/Dto/OutPut/MessageOutput.cs create mode 100644 src/Znyc.Dispatching.Application/Message/Services/IMessageService.cs create mode 100644 src/Znyc.Dispatching.Application/Message/Services/MessageService.cs create mode 100644 src/Znyc.Dispatching.Application/MessageRecord/Dto/Input/MessageRecordAddInput.cs create mode 100644 src/Znyc.Dispatching.Application/MessageRecord/Dto/OutPut/MessageRecordOutput.cs create mode 100644 src/Znyc.Dispatching.Application/MessageRecord/Services/IMessageRecordService.cs create mode 100644 src/Znyc.Dispatching.Application/MessageRecord/Services/MessageRecordService.cs create mode 100644 src/Znyc.Dispatching.Application/Oil/Dto/Input/OilAddInput.cs create mode 100644 src/Znyc.Dispatching.Application/Oil/Dto/Input/OilUpdateInput.cs create mode 100644 src/Znyc.Dispatching.Application/Oil/Dto/OutPut/CensusOilOutput.cs create mode 100644 src/Znyc.Dispatching.Application/Oil/Dto/OutPut/OilOutput.cs create mode 100644 src/Znyc.Dispatching.Application/Oil/Services/IOilService.cs create mode 100644 src/Znyc.Dispatching.Application/Oil/Services/OilService.cs create mode 100644 src/Znyc.Dispatching.Application/Order/Dto/Input/Assign/AssignOrderInput.cs create mode 100644 src/Znyc.Dispatching.Application/Order/Dto/Input/Assign/AssignUpdateInput.cs create mode 100644 src/Znyc.Dispatching.Application/Order/Dto/Input/OrderAddInput.cs create mode 100644 src/Znyc.Dispatching.Application/Order/Dto/Input/OrderUpdateInput.cs create mode 100644 src/Znyc.Dispatching.Application/Order/Dto/Input/Sign/SignInput.cs create mode 100644 src/Znyc.Dispatching.Application/Order/Dto/Input/Sign/SignUpdateInput.cs create mode 100644 src/Znyc.Dispatching.Application/Order/Dto/Input/StayAssign/StayAssignInput.cs create mode 100644 src/Znyc.Dispatching.Application/Order/Dto/Input/StayAssign/StayAssignUpdateInput.cs create mode 100644 src/Znyc.Dispatching.Application/Order/Dto/OutPut/AdminOrderListOutput.cs create mode 100644 src/Znyc.Dispatching.Application/Order/Dto/OutPut/OrderListOutput.cs create mode 100644 src/Znyc.Dispatching.Application/Order/Dto/OutPut/OrderOutput.cs create mode 100644 src/Znyc.Dispatching.Application/Order/Dto/OutPut/OrderStatisticsConstructionOutput.cs create mode 100644 src/Znyc.Dispatching.Application/Order/Dto/OutPut/OrderStatisticsOutput.cs create mode 100644 src/Znyc.Dispatching.Application/Order/Services/IOrderService.cs create mode 100644 src/Znyc.Dispatching.Application/Order/Services/OrderService.cs create mode 100644 src/Znyc.Dispatching.Application/OrderVehicle/Dto/Input/OrderVehicleAddInput.cs create mode 100644 src/Znyc.Dispatching.Application/OrderVehicle/Dto/OutPut/OrderVehicleListOutput .cs create mode 100644 src/Znyc.Dispatching.Application/OrderVehicle/Dto/OutPut/OrderVehicleOutput.cs create mode 100644 src/Znyc.Dispatching.Application/OrderVisa/Dto/Input/OrderVisaInput.cs create mode 100644 src/Znyc.Dispatching.Application/OrderVisa/Dto/OutPut/OrderVisaOutput.cs create mode 100644 src/Znyc.Dispatching.Application/Project/Dto/Input/ProjectAddInput.cs create mode 100644 src/Znyc.Dispatching.Application/Project/Dto/Input/ProjectUpdateInput.cs create mode 100644 src/Znyc.Dispatching.Application/Project/Dto/OutPut/ProjectListOutput.cs create mode 100644 src/Znyc.Dispatching.Application/Project/Dto/OutPut/ProjectOutput.cs create mode 100644 src/Znyc.Dispatching.Application/Project/Services/IProjectService.cs create mode 100644 src/Znyc.Dispatching.Application/Project/Services/ProjectService.cs create mode 100644 src/Znyc.Dispatching.Application/ProjectPerson/Dto/Input/ProjectPersonAddInput.cs create mode 100644 src/Znyc.Dispatching.Application/ProjectPerson/Dto/Input/ProjectPersonUpdateInput.cs create mode 100644 src/Znyc.Dispatching.Application/ProjectPerson/Dto/OutPut/ProjectPersonOutput.cs create mode 100644 src/Znyc.Dispatching.Application/ProjectPerson/Services/IProjectPersonService.cs create mode 100644 src/Znyc.Dispatching.Application/ProjectPerson/Services/ProjectPersonService.cs create mode 100644 src/Znyc.Dispatching.Application/Report/Dto/Input/ReportAddInput.cs create mode 100644 src/Znyc.Dispatching.Application/Report/Services/IReportService.cs create mode 100644 src/Znyc.Dispatching.Application/Report/Services/ReportService.cs create mode 100644 src/Znyc.Dispatching.Application/Role/Dto/OutPut/RoleOutput.cs create mode 100644 src/Znyc.Dispatching.Application/Role/Services/IRoleService.cs create mode 100644 src/Znyc.Dispatching.Application/Role/Services/RoleService.cs create mode 100644 src/Znyc.Dispatching.Application/RoleMenu/Dto/OutPut/RoleMenuListOutput.cs create mode 100644 src/Znyc.Dispatching.Application/RoleMenu/Services/IRoleMenuService.cs create mode 100644 src/Znyc.Dispatching.Application/RoleMenu/Services/RoleMenuService.cs create mode 100644 src/Znyc.Dispatching.Application/User/Dto/Input/DecryptPhoneAddInput.cs create mode 100644 src/Znyc.Dispatching.Application/User/Dto/Input/UserAddInput.cs create mode 100644 src/Znyc.Dispatching.Application/User/Dto/Input/UserChangePwdInput.cs create mode 100644 src/Znyc.Dispatching.Application/User/Dto/Input/UserUpdateInput.cs create mode 100644 src/Znyc.Dispatching.Application/User/Dto/Output/UserOutput.cs create mode 100644 src/Znyc.Dispatching.Application/User/Services/IUserService.cs create mode 100644 src/Znyc.Dispatching.Application/User/Services/UserService.cs create mode 100644 src/Znyc.Dispatching.Application/UserRole/Services/IUserRoleService.cs create mode 100644 src/Znyc.Dispatching.Application/UserRole/Services/UserRoleService.cs create mode 100644 src/Znyc.Dispatching.Application/Vehicle/Dto/Input/BasicsVehicleUpdateInput.cs create mode 100644 src/Znyc.Dispatching.Application/Vehicle/Dto/Input/TrackPlayBackCorrectInput.cs create mode 100644 src/Znyc.Dispatching.Application/Vehicle/Dto/Input/VehicleAddInput.cs create mode 100644 src/Znyc.Dispatching.Application/Vehicle/Dto/Input/VehicleUpdateInput.cs create mode 100644 src/Znyc.Dispatching.Application/Vehicle/Dto/OutPut/AlarmOutput.cs create mode 100644 src/Znyc.Dispatching.Application/Vehicle/Dto/OutPut/MapMonitorDataOutput.cs create mode 100644 src/Znyc.Dispatching.Application/Vehicle/Dto/OutPut/PgyAlertStraightDtlOutput.cs create mode 100644 src/Znyc.Dispatching.Application/Vehicle/Dto/OutPut/TrackPlayBackCorrectOutput.cs create mode 100644 src/Znyc.Dispatching.Application/Vehicle/Dto/OutPut/TrackPlaybackOutput.cs create mode 100644 src/Znyc.Dispatching.Application/Vehicle/Dto/OutPut/VehicleBaiscListOutput.cs create mode 100644 src/Znyc.Dispatching.Application/Vehicle/Dto/OutPut/VehicleGpsOutput.cs create mode 100644 src/Znyc.Dispatching.Application/Vehicle/Dto/OutPut/VehicleListOutput.cs create mode 100644 src/Znyc.Dispatching.Application/Vehicle/Services/IVehicleService.cs create mode 100644 src/Znyc.Dispatching.Application/Vehicle/Services/VehicleService.cs create mode 100644 src/Znyc.Dispatching.Application/VehicleGroup/Dto/Input/VehicleGroupAddInput.cs create mode 100644 src/Znyc.Dispatching.Application/VehicleGroup/Dto/OutPut/VehicleGroupOutput.cs create mode 100644 src/Znyc.Dispatching.Application/VehicleGroup/Services/IVehicleGroupService.cs create mode 100644 src/Znyc.Dispatching.Application/VehicleGroup/Services/VehicleGroupService.cs create mode 100644 src/Znyc.Dispatching.Application/VehiclePerson/Dto/Input/VehiclePersonAddInput.cs create mode 100644 src/Znyc.Dispatching.Application/VehiclePerson/Dto/Input/VehiclePersonUpdateInput.cs create mode 100644 src/Znyc.Dispatching.Application/VehiclePerson/Dto/OutPut/VehiclePersonOutput.cs create mode 100644 src/Znyc.Dispatching.Application/VehiclePerson/Services/IVehiclePersonService.cs create mode 100644 src/Znyc.Dispatching.Application/VehiclePerson/Services/VehiclePersonService.cs create mode 100644 src/Znyc.Dispatching.Application/VehicleType/Dto/Input/VehicleTypeInsertInput.cs create mode 100644 src/Znyc.Dispatching.Application/VehicleType/Dto/Input/VehicleTypeUpdateInput.cs create mode 100644 src/Znyc.Dispatching.Application/VehicleType/Dto/OutPut/VehicleTypeOutput.cs create mode 100644 src/Znyc.Dispatching.Application/VehicleType/Services/IVehicleTypeService.cs create mode 100644 src/Znyc.Dispatching.Application/VehicleType/Services/VehicleTypeService.cs create mode 100644 src/Znyc.Dispatching.Application/WxUserRelation/Services/IWxUserRelationService.cs create mode 100644 src/Znyc.Dispatching.Application/WxUserRelation/Services/WxUserRelationService.cs create mode 100644 src/Znyc.Dispatching.Application/Yard/Dto/Input/YardAddInput.cs create mode 100644 src/Znyc.Dispatching.Application/Yard/Dto/Input/YardUpdateInput.cs create mode 100644 src/Znyc.Dispatching.Application/Yard/Dto/OutPut/YardListOutput.cs create mode 100644 src/Znyc.Dispatching.Application/Yard/Dto/OutPut/YardOutput.cs create mode 100644 src/Znyc.Dispatching.Application/Yard/Services/IYardService.cs create mode 100644 src/Znyc.Dispatching.Application/Yard/Services/YardService.cs create mode 100644 src/Znyc.Dispatching.Application/Znyc.Dispatching.Application.csproj create mode 100644 src/Znyc.Dispatching.Application/Znyc.Dispatching.Application.xml create mode 100644 src/Znyc.Dispatching.Application/applicationconfig.Development.json create mode 100644 src/Znyc.Dispatching.Application/applicationconfig.Production.json create mode 100644 src/Znyc.Dispatching.Application/applicationconfig.Staging.json create mode 100644 src/Znyc.Dispatching.Common/Extensions/GenericTypeExtensions.cs create mode 100644 src/Znyc.Dispatching.Common/Extensions/LinqExtensions.cs create mode 100644 src/Znyc.Dispatching.Common/Znyc.Dispatching.Common.csproj create mode 100644 src/Znyc.Dispatching.Core/Cache/CacheOptions.cs create mode 100644 src/Znyc.Dispatching.Core/Cache/IRedisCache.cs create mode 100644 src/Znyc.Dispatching.Core/Cache/RedisCache.cs create mode 100644 src/Znyc.Dispatching.Core/Config/SmsProviderOptions.cs create mode 100644 src/Znyc.Dispatching.Core/Config/UploadInfoOptions.cs create mode 100644 src/Znyc.Dispatching.Core/Config/UploadOptions.cs create mode 100644 src/Znyc.Dispatching.Core/Config/WeixinSettingOptions.cs create mode 100644 src/Znyc.Dispatching.Core/Const/ApiGroupConsts.cs create mode 100644 src/Znyc.Dispatching.Core/Const/ClaimConst.cs create mode 100644 src/Znyc.Dispatching.Core/Const/CommonConst.cs create mode 100644 src/Znyc.Dispatching.Core/Entitys/Company.cs create mode 100644 src/Znyc.Dispatching.Core/Entitys/Construction.cs create mode 100644 src/Znyc.Dispatching.Core/Entitys/DEntityBase.cs create mode 100644 src/Znyc.Dispatching.Core/Entitys/Dictionary.cs create mode 100644 src/Znyc.Dispatching.Core/Entitys/Employee.cs create mode 100644 src/Znyc.Dispatching.Core/Entitys/Job.cs create mode 100644 src/Znyc.Dispatching.Core/Entitys/LogAudit.cs create mode 100644 src/Znyc.Dispatching.Core/Entitys/LogEx.cs create mode 100644 src/Znyc.Dispatching.Core/Entitys/LogOp.cs create mode 100644 src/Znyc.Dispatching.Core/Entitys/LogVis.cs create mode 100644 src/Znyc.Dispatching.Core/Entitys/Menu.cs create mode 100644 src/Znyc.Dispatching.Core/Entitys/Message.cs create mode 100644 src/Znyc.Dispatching.Core/Entitys/MessageRecord.cs create mode 100644 src/Znyc.Dispatching.Core/Entitys/Oil.cs create mode 100644 src/Znyc.Dispatching.Core/Entitys/Order.cs create mode 100644 src/Znyc.Dispatching.Core/Entitys/OrderVehicle.cs create mode 100644 src/Znyc.Dispatching.Core/Entitys/OrderVisa.cs create mode 100644 src/Znyc.Dispatching.Core/Entitys/Project.cs create mode 100644 src/Znyc.Dispatching.Core/Entitys/ProjectPerson.cs create mode 100644 src/Znyc.Dispatching.Core/Entitys/Report.cs create mode 100644 src/Znyc.Dispatching.Core/Entitys/Role.cs create mode 100644 src/Znyc.Dispatching.Core/Entitys/RoleMenu.cs create mode 100644 src/Znyc.Dispatching.Core/Entitys/Salesman.cs create mode 100644 src/Znyc.Dispatching.Core/Entitys/User.cs create mode 100644 src/Znyc.Dispatching.Core/Entitys/UserRole.cs create mode 100644 src/Znyc.Dispatching.Core/Entitys/UserYard.cs create mode 100644 src/Znyc.Dispatching.Core/Entitys/Vehicle.cs create mode 100644 src/Znyc.Dispatching.Core/Entitys/VehicleGroup.cs create mode 100644 src/Znyc.Dispatching.Core/Entitys/VehiclePerson.cs create mode 100644 src/Znyc.Dispatching.Core/Entitys/VehicleType.cs create mode 100644 src/Znyc.Dispatching.Core/Entitys/WxUserRelation.cs create mode 100644 src/Znyc.Dispatching.Core/Entitys/Yard.cs create mode 100644 src/Znyc.Dispatching.Core/Enums/AccStatusEnum.cs create mode 100644 src/Znyc.Dispatching.Core/Enums/CommonStatusEnum.cs create mode 100644 src/Znyc.Dispatching.Core/Enums/DataOpTypeEnum.cs create mode 100644 src/Znyc.Dispatching.Core/Enums/ErrorCode.cs create mode 100644 src/Znyc.Dispatching.Core/Enums/FileLocationEnum.cs create mode 100644 src/Znyc.Dispatching.Core/Enums/GpsStatusEnum.cs create mode 100644 src/Znyc.Dispatching.Core/Enums/HangFireQueuesConfig.cs create mode 100644 src/Znyc.Dispatching.Core/Enums/HttpStatusCodeEnum.cs create mode 100644 src/Znyc.Dispatching.Core/Enums/LoginTypeEnum.cs create mode 100644 src/Znyc.Dispatching.Core/Enums/MenuTypeEnum.cs create mode 100644 src/Znyc.Dispatching.Core/Enums/NoticeUserStatusEnum.cs create mode 100644 src/Znyc.Dispatching.Core/Enums/OrderSourceEnum.cs create mode 100644 src/Znyc.Dispatching.Core/Enums/OrderStatus.cs create mode 100644 src/Znyc.Dispatching.Core/Enums/PlatformTypeEnum.cs create mode 100644 src/Znyc.Dispatching.Core/Enums/QueryTypeEnum.cs create mode 100644 src/Znyc.Dispatching.Core/Enums/RequestTypeEnum.cs create mode 100644 src/Znyc.Dispatching.Core/Enums/RoleStatusEnum.cs create mode 100644 src/Znyc.Dispatching.Core/Enums/StopStatusEnum.cs create mode 100644 src/Znyc.Dispatching.Core/Enums/TerminalTypeEnum.cs create mode 100644 src/Znyc.Dispatching.Core/Enums/WorkStatusEnum.cs create mode 100644 src/Znyc.Dispatching.Core/Enums/YesOrNotEnum.cs create mode 100644 src/Znyc.Dispatching.Core/Extension/DateTimeExtensions.cs create mode 100644 src/Znyc.Dispatching.Core/Extension/DictionaryExtensions.cs create mode 100644 src/Znyc.Dispatching.Core/Extension/EnumExtensions.cs create mode 100644 src/Znyc.Dispatching.Core/Extension/InputBase.cs create mode 100644 src/Znyc.Dispatching.Core/Extension/ObjectExtensions.cs create mode 100644 src/Znyc.Dispatching.Core/Extension/PageInputOrder.cs create mode 100644 src/Znyc.Dispatching.Core/Extension/RestfulResultProvider.cs create mode 100644 src/Znyc.Dispatching.Core/Extension/ReturnPageResult.cs create mode 100644 src/Znyc.Dispatching.Core/Extension/StringExtensions.cs create mode 100644 src/Znyc.Dispatching.Core/Files/FileInfo.cs create mode 100644 src/Znyc.Dispatching.Core/Files/FileSize.cs create mode 100644 src/Znyc.Dispatching.Core/Files/FileSizeUnit.cs create mode 100644 src/Znyc.Dispatching.Core/Filter/LogExceptionHandler.cs create mode 100644 src/Znyc.Dispatching.Core/Filter/RequestActionFilter.cs create mode 100644 src/Znyc.Dispatching.Core/Filter/XSSFilterAttribute.cs create mode 100644 src/Znyc.Dispatching.Core/Helpers/ByteConvertHelper.cs create mode 100644 src/Znyc.Dispatching.Core/Helpers/ConvertGps.cs create mode 100644 src/Znyc.Dispatching.Core/Helpers/ConvertHelper.cs create mode 100644 src/Znyc.Dispatching.Core/Helpers/DateTimeHelper.cs create mode 100644 src/Znyc.Dispatching.Core/Helpers/DatetimeJsonConverter.cs create mode 100644 src/Znyc.Dispatching.Core/Helpers/HtmlHelper.cs create mode 100644 src/Znyc.Dispatching.Core/Helpers/HttpContextHelper.cs create mode 100644 src/Znyc.Dispatching.Core/Helpers/HttpRequestHelper.cs create mode 100644 src/Znyc.Dispatching.Core/Helpers/MapHelper/DistanceHelper.cs create mode 100644 src/Znyc.Dispatching.Core/Helpers/MapHelper/MapHelper.cs create mode 100644 src/Znyc.Dispatching.Core/Helpers/MapHelper/MapModel/LocationModel.cs create mode 100644 src/Znyc.Dispatching.Core/Helpers/MapHelper/MapModel/MapLocation.cs create mode 100644 src/Znyc.Dispatching.Core/Helpers/MapHelper/MapModel/Mileage.cs create mode 100644 src/Znyc.Dispatching.Core/Helpers/MapHelper/MapModel/PathPlanning.cs create mode 100644 src/Znyc.Dispatching.Core/Helpers/MapHelper/MapModel/PathPlanningModel.cs create mode 100644 src/Znyc.Dispatching.Core/Helpers/MapHelper/MapModel/Point.cs create mode 100644 src/Znyc.Dispatching.Core/Helpers/MapHelper/MapModel/PointLatLng.cs create mode 100644 src/Znyc.Dispatching.Core/Helpers/RegexHelper.cs create mode 100644 src/Znyc.Dispatching.Core/Helpers/SmsHelper.cs create mode 100644 src/Znyc.Dispatching.Core/Helpers/Snowflake.cs create mode 100644 src/Znyc.Dispatching.Core/Helpers/StringHelper.cs create mode 100644 src/Znyc.Dispatching.Core/Helpers/TimeHelper.cs create mode 100644 src/Znyc.Dispatching.Core/Helpers/UploadHelper.cs create mode 100644 src/Znyc.Dispatching.Core/Helpers/UtilConvert.cs create mode 100644 src/Znyc.Dispatching.Core/Helpers/XSSHelper.cs create mode 100644 src/Znyc.Dispatching.Core/HostedService/LogHostedService.cs create mode 100644 src/Znyc.Dispatching.Core/Manager/IUserManager.cs create mode 100644 src/Znyc.Dispatching.Core/Manager/UserManager.cs create mode 100644 src/Znyc.Dispatching.Core/Options/RefreshTokenSettingOptions.cs create mode 100644 src/Znyc.Dispatching.Core/Rpc/IHttp.cs create mode 100644 src/Znyc.Dispatching.Core/SimpleQueue/IConcurrentQueue.cs create mode 100644 src/Znyc.Dispatching.Core/SimpleQueue/SimpleQueue.cs create mode 100644 src/Znyc.Dispatching.Core/SqlProxy/Company/ICompanySql .cs create mode 100644 src/Znyc.Dispatching.Core/Util/OSSClientUtil.cs create mode 100644 src/Znyc.Dispatching.Core/Util/ReflectionUtil.cs create mode 100644 src/Znyc.Dispatching.Core/Znyc.Dispatching.Core.csproj create mode 100644 src/Znyc.Dispatching.Core/Znyc.Dispatching.Core.xml create mode 100644 src/Znyc.Dispatching.Core/coreconfig.Development.json create mode 100644 src/Znyc.Dispatching.Core/coreconfig.Production.json create mode 100644 src/Znyc.Dispatching.Core/coreconfig.Staging.json create mode 100644 src/Znyc.Dispatching.Database.Migrations/MSBuild_Logs/MSBuild_pid-18752_4d6113493f7a4e73b20c145787e2b4ff.failure.txt create mode 100644 src/Znyc.Dispatching.Database.Migrations/Znyc.Dispatching.Database.Migrations.csproj create mode 100644 src/Znyc.Dispatching.EntityFramework.Core/DbContexts/DefaultDbContext.cs create mode 100644 src/Znyc.Dispatching.EntityFramework.Core/EntityFrameworkStartup.cs create mode 100644 src/Znyc.Dispatching.EntityFramework.Core/Znyc.Dispatching.EntityFramework.Core.csproj create mode 100644 src/Znyc.Dispatching.EntityFramework.Core/dbsettings.Development.json create mode 100644 src/Znyc.Dispatching.EntityFramework.Core/dbsettings.Production.json create mode 100644 src/Znyc.Dispatching.EntityFramework.Core/dbsettings.Staging.json create mode 100644 src/Znyc.Dispatching.Evenbus/EventBusSubscriptions/InMemoryEventBusSubscriptionsManager.cs create mode 100644 src/Znyc.Dispatching.Evenbus/EventBusSubscriptions/SubscriptionInfo.cs create mode 100644 src/Znyc.Dispatching.Evenbus/Eventbus/IDynamicIntegrationEventHandler.cs create mode 100644 src/Znyc.Dispatching.Evenbus/Eventbus/IEventBus.cs create mode 100644 src/Znyc.Dispatching.Evenbus/Eventbus/IEventBusSubscriptionsManager.cs create mode 100644 src/Znyc.Dispatching.Evenbus/Eventbus/IIntegrationEventHandler.cs create mode 100644 src/Znyc.Dispatching.Evenbus/Eventbus/IntegrationEvent.cs create mode 100644 src/Znyc.Dispatching.Evenbus/RabbitMQPersistent/EventBusRabbitMQ.cs create mode 100644 src/Znyc.Dispatching.Evenbus/RabbitMQPersistent/IRabbitMQPersistentConnection.cs create mode 100644 src/Znyc.Dispatching.Evenbus/RabbitMQPersistent/RabbitMQPersistentConnection.cs create mode 100644 src/Znyc.Dispatching.Evenbus/Znyc.Dispatching.Evenbus.csproj create mode 100644 src/Znyc.Dispatching.MongoDb.Repository/Collection/GpsCarAccDuration.cs create mode 100644 src/Znyc.Dispatching.MongoDb.Repository/Collection/GpsRealTime.cs create mode 100644 src/Znyc.Dispatching.MongoDb.Repository/Collection/PgyAlertStraightDtl.cs create mode 100644 src/Znyc.Dispatching.MongoDb.Repository/Collection/PgyAlertStraightLis.cs create mode 100644 src/Znyc.Dispatching.MongoDb.Repository/Collection/StopPointCalculation.cs create mode 100644 src/Znyc.Dispatching.MongoDb.Repository/Collection/VehicleGps.cs create mode 100644 src/Znyc.Dispatching.MongoDb.Repository/IRepositorys/IGpsRealTimeRepository.cs create mode 100644 src/Znyc.Dispatching.MongoDb.Repository/IRepositorys/IMongodbBaseRepository.cs create mode 100644 src/Znyc.Dispatching.MongoDb.Repository/IRepositorys/IPgyAlertStraightDtlRepository.cs create mode 100644 src/Znyc.Dispatching.MongoDb.Repository/IRepositorys/IPgyAlertStraightLisRepository.cs create mode 100644 src/Znyc.Dispatching.MongoDb.Repository/MongoContext.cs create mode 100644 src/Znyc.Dispatching.MongoDb.Repository/MongoDbStartup.cs create mode 100644 src/Znyc.Dispatching.MongoDb.Repository/Repositorys/GpsRealTimeRepository.cs create mode 100644 src/Znyc.Dispatching.MongoDb.Repository/Repositorys/MongodbBaseRepository.cs create mode 100644 src/Znyc.Dispatching.MongoDb.Repository/Repositorys/PgyAlertStraightDtlRepository.cs create mode 100644 src/Znyc.Dispatching.MongoDb.Repository/Repositorys/PgyAlertStraightLisRepository.cs create mode 100644 src/Znyc.Dispatching.MongoDb.Repository/Znyc.Dispatching.MongoDb.Repository.csproj create mode 100644 src/Znyc.Dispatching.MongoDb.Repository/mongodbsettings.Development.json create mode 100644 src/Znyc.Dispatching.MongoDb.Repository/mongodbsettings.Production.json create mode 100644 src/Znyc.Dispatching.MongoDb.Repository/mongodbsettings.Staging.json create mode 100644 src/Znyc.Dispatching.Tasks/HangfireDispose.cs create mode 100644 src/Znyc.Dispatching.Tasks/TaskJobs/AutoEveryNightTrackPlaybackCorrectionJob.cs create mode 100644 src/Znyc.Dispatching.Tasks/TaskJobs/AutoOrderJob.cs create mode 100644 src/Znyc.Dispatching.Tasks/TaskJobs/AutoRoleMenuJob.cs create mode 100644 src/Znyc.Dispatching.Tasks/TaskJobs/AutoTrackPlaybackCorrectionJob.cs create mode 100644 src/Znyc.Dispatching.Tasks/TasksStartup.cs create mode 100644 src/Znyc.Dispatching.Tasks/Znyc.Dispatching.Tasks.csproj create mode 100644 src/Znyc.Dispatching.WeChat.Core/CommonService/SubscribeMessage/WxApplet/WxAppletSubscribeMessage.cs create mode 100644 src/Znyc.Dispatching.WeChat.Core/CommonService/TemplateMessage/Weixin/WeixinTemplateApi.cs create mode 100644 src/Znyc.Dispatching.WeChat.Core/CommonService/TemplateMessage/Weixin/WeixinTemplate_AssignOrder.cs create mode 100644 src/Znyc.Dispatching.WeChat.Core/CommonService/TemplateMessage/WxOpen/CommonApi.cs create mode 100644 src/Znyc.Dispatching.WeChat.Core/CommonService/TemplateMessage/WxOpen/SnsApi.cs create mode 100644 src/Znyc.Dispatching.WeChat.Core/CommonService/TemplateMessage/WxOpen/WxOpenTemplateMessage_AssignOrder.cs create mode 100644 src/Znyc.Dispatching.WeChat.Core/Helper/SnsHelper.cs create mode 100644 src/Znyc.Dispatching.WeChat.Core/WeChatStartup.cs create mode 100644 src/Znyc.Dispatching.WeChat.Core/Znyc.Dispatching.WeChat.Core.csproj create mode 100644 src/Znyc.Dispatching.WeChat.Core/wechatsettings.Development.json create mode 100644 src/Znyc.Dispatching.WeChat.Core/wechatsettings.Production.json create mode 100644 src/Znyc.Dispatching.WeChat.Core/wechatsettings.Staging.json create mode 100644 src/Znyc.Dispatching.Web.Core/Handlers/JwtHandler.cs create mode 100644 src/Znyc.Dispatching.Web.Core/Startup.cs create mode 100644 src/Znyc.Dispatching.Web.Core/Znyc.Dispatching.WeChat.Core.xml create mode 100644 src/Znyc.Dispatching.Web.Core/Znyc.Dispatching.Web.Core.csproj create mode 100644 src/Znyc.Dispatching.Web.Core/Znyc.Dispatching.Web.Core.xml create mode 100644 src/Znyc.Dispatching.Web.Entry/Program.cs create mode 100644 src/Znyc.Dispatching.Web.Entry/Properties/PublishProfiles/FolderProfile.pubxml create mode 100644 src/Znyc.Dispatching.Web.Entry/Properties/PublishProfiles/FolderProfile1.pubxml create mode 100644 src/Znyc.Dispatching.Web.Entry/Properties/launchSettings.json create mode 100644 src/Znyc.Dispatching.Web.Entry/Znyc.Dispatching.Web.Entry.csproj create mode 100644 src/Znyc.Dispatching.Web.Entry/appsettings.Development.json create mode 100644 src/Znyc.Dispatching.Web.Entry/appsettings.Production.json create mode 100644 src/Znyc.Dispatching.Web.Entry/appsettings.Staging.json create mode 100644 src/Znyc.Dispatching.Web.Entry/nlog.config create mode 100644 src/Znyc.Dispatching.sln diff --git a/.ionide/symbolCache.db b/.ionide/symbolCache.db new file mode 100644 index 0000000000000000000000000000000000000000..8a73c4f77ed2b2c7b869d888125b966bab395731 GIT binary patch literal 8192 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
  • 9z4sKp3U3=J9RhpI{A_dh10DIZ~A&{n{+ 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 + + + + + +
    +
    +
    +
    +
    +
    +
    +
    + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    + + +
    +
    +
    +
    +
    +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.vs/Znyc.Dispatching/v16/.suo b/.vs/Znyc.Dispatching/v16/.suo new file mode 100644 index 0000000000000000000000000000000000000000..c4114baf69fda04e1a7539ebc74e7ddf75e74008 GIT binary patch literal 269824 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 Pcb)gL4!buGNqhc3qU4&@ literal 0 HcmV?d00001 diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..8524d96 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,69 @@ +{ + // 使用 IntelliSense 了解相关属性。 + // 悬停以查看现有属性的描述。 + // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": ".NET Core Launch (console)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/bin/Debug//", + "args": [], + "cwd": "${workspaceFolder}", + "stopAtEntry": false, + "console": "internalConsole", + "pipeTransport": { + "pipeCwd": "${workspaceFolder}", + "pipeProgram": "enter the fully qualified path for the pipe program name, for example '/usr/bin/ssh'", + "pipeArgs": [], + "debuggerPath": "enter the path for the debugger on the target machine, for example ~/vsdbg/vsdbg" + } + }, + { + "name": ".NET Core Launch (web)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/bin/Debug//", + "args": [], + "cwd": "${workspaceFolder}", + "stopAtEntry": false, + "serverReadyAction": { + "action": "openExternally", + "pattern": "\\bNow listening on:\\s+(https?://\\S+)" + }, + "env": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "sourceFileMap": { + "/Views": "${workspaceFolder}/Views" + } + }, + { + "name": ".NET Core Launch (console)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/bin/Debug//", + "args": [], + "cwd": "${workspaceFolder}", + "stopAtEntry": false, + "console": "internalConsole" + }, + + { + "name": ".NET Core Launch (console)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/bin/Debug//", + "args": [], + "cwd": "${workspaceFolder}", + "stopAtEntry": false, + "console": "internalConsole" + } + + ] +} \ No newline at end of file diff --git a/.vscode/solution-explorer/class.cs-template b/.vscode/solution-explorer/class.cs-template new file mode 100644 index 0000000..015da46 --- /dev/null +++ b/.vscode/solution-explorer/class.cs-template @@ -0,0 +1,8 @@ +using System; + +namespace {{namespace}} +{ + public class {{name}} + { + } +} diff --git a/.vscode/solution-explorer/class.ts-template b/.vscode/solution-explorer/class.ts-template new file mode 100644 index 0000000..ff2edef --- /dev/null +++ b/.vscode/solution-explorer/class.ts-template @@ -0,0 +1,3 @@ +export class {{name}} { + +} \ No newline at end of file diff --git a/.vscode/solution-explorer/class.vb-template b/.vscode/solution-explorer/class.vb-template new file mode 100644 index 0000000..38ef67f --- /dev/null +++ b/.vscode/solution-explorer/class.vb-template @@ -0,0 +1,9 @@ +Imports System + +Namespace {{namespace}} + + Public Class {{name}} + + End Class + +End Namespace diff --git a/.vscode/solution-explorer/default.ts-template b/.vscode/solution-explorer/default.ts-template new file mode 100644 index 0000000..04af870 --- /dev/null +++ b/.vscode/solution-explorer/default.ts-template @@ -0,0 +1,3 @@ +export default {{name}} { + +} \ No newline at end of file diff --git a/.vscode/solution-explorer/enum.cs-template b/.vscode/solution-explorer/enum.cs-template new file mode 100644 index 0000000..7d4cdee --- /dev/null +++ b/.vscode/solution-explorer/enum.cs-template @@ -0,0 +1,8 @@ +using System; + +namespace {{namespace}} +{ + public enum {{name}} + { + } +} diff --git a/.vscode/solution-explorer/interface.cs-template b/.vscode/solution-explorer/interface.cs-template new file mode 100644 index 0000000..6b5dec1 --- /dev/null +++ b/.vscode/solution-explorer/interface.cs-template @@ -0,0 +1,8 @@ +using System; + +namespace {{namespace}} +{ + public interface {{name}} + { + } +} diff --git a/.vscode/solution-explorer/interface.ts-template b/.vscode/solution-explorer/interface.ts-template new file mode 100644 index 0000000..3ea404b --- /dev/null +++ b/.vscode/solution-explorer/interface.ts-template @@ -0,0 +1,3 @@ +export interface {{name}} { + +} \ No newline at end of file diff --git a/.vscode/solution-explorer/template-list.json b/.vscode/solution-explorer/template-list.json new file mode 100644 index 0000000..2849622 --- /dev/null +++ b/.vscode/solution-explorer/template-list.json @@ -0,0 +1,46 @@ +{ + "templates": [ + { + "name": "Class", + "extension": "cs", + "file": "./class.cs-template", + "parameters": "./template-parameters.js" + }, + { + "name": "Interface", + "extension": "cs", + "file": "./interface.cs-template", + "parameters": "./template-parameters.js" + }, + { + "name": "Enum", + "extension": "cs", + "file": "./enum.cs-template", + "parameters": "./template-parameters.js" + }, + { + "name": "Class", + "extension": "ts", + "file": "./class.ts-template", + "parameters": "./template-parameters.js" + }, + { + "name": "Interface", + "extension": "ts", + "file": "./interface.ts-template", + "parameters": "./template-parameters.js" + }, + { + "name": "Default", + "extension": "ts", + "file": "./default.ts-template", + "parameters": "./template-parameters.js" + }, + { + "name": "Class", + "extension": "vb", + "file": "./class.vb-template", + "parameters": "./template-parameters.js" + } + ] +} \ No newline at end of file diff --git a/.vscode/solution-explorer/template-parameters.js b/.vscode/solution-explorer/template-parameters.js new file mode 100644 index 0000000..daba8b2 --- /dev/null +++ b/.vscode/solution-explorer/template-parameters.js @@ -0,0 +1,17 @@ +var path = require("path"); + +module.exports = function(filename, projectPath, folderPath) { + var namespace = "Unknown"; + if (projectPath) { + namespace = path.basename(projectPath, path.extname(projectPath)); + if (folderPath) { + namespace += "." + folderPath.replace(path.dirname(projectPath), "").substring(1).replace(/[\\\/]/g, "."); + } + namespace = namespace.replace(/[\\\-]/g, "_"); + } + + return { + namespace: namespace, + name: path.basename(filename, path.extname(filename)) + } +}; \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..31c32bd --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,24 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "shell", + "args": [ + "build", + // Ask dotnet build to generate full paths for file names. + "/property:GenerateFullPaths=true", + // Do not generate summary otherwise it leads to duplicate errors in Problems panel + "/consoleloggerparameters:NoSummary" + ], + "group": "build", + "presentation": { + "reveal": "silent" + }, + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..0012440 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,34 @@ +#See https://aka.ms/containerfastmode to understand 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 +EXPOSE 443 + +ENV TZ=Asia/Shanghai +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + + + +FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build +WORKDIR /src +COPY ["Znyc.Dispatching.Web.Entry/Znyc.Dispatching.Web.Entry.csproj", "Znyc.Dispatching.Web.Entry/"] +COPY ["Znyc.Dispatching.Web.Core/Znyc.Dispatching.Web.Core.csproj", "Znyc.Dispatching.Web.Core/"] +COPY ["Znyc.Dispatching.Database.Migrations/Znyc.Dispatching.Database.Migrations.csproj", "Znyc.Dispatching.Database.Migrations/"] +COPY ["Znyc.Dispatching.EntityFramework.Core/Znyc.Dispatching.EntityFramework.Core.csproj", "Znyc.Dispatching.EntityFramework.Core/"] +COPY ["Znyc.Dispatching.Core/Znyc.Dispatching.Core.csproj", "Znyc.Dispatching.Core/"] +COPY ["Znyc.Dispatching.Application/Znyc.Dispatching.Application.csproj", "Znyc.Dispatching.Application/"] +COPY ["Znyc.Dispatching.MongoDb.Repository/Znyc.Dispatching.MongoDb.Repository.csproj", "Znyc.Dispatching.MongoDb.Repository/"] +COPY ["Znyc.Dispatching.WeChat.Core/Znyc.Dispatching.WeChat.Core.csproj", "Znyc.Dispatching.WeChat.Core/"] +RUN dotnet restore "Znyc.Dispatching.Web.Entry/Znyc.Dispatching.Web.Entry.csproj" +COPY . . +WORKDIR "/src/Znyc.Dispatching.Web.Entry" +RUN dotnet build "Znyc.Dispatching.Web.Entry.csproj" -c Release -o /app/build + +FROM build AS publish +RUN dotnet publish "Znyc.Dispatching.Web.Entry.csproj" -c Release -o /app/publish + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "Znyc.Dispatching.Web.Entry.dll"] \ No newline at end of file diff --git a/src/.dockerignore b/src/.dockerignore new file mode 100644 index 0000000..3729ff0 --- /dev/null +++ b/src/.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/src/.gitignore b/src/.gitignore new file mode 100644 index 0000000..be42968 --- /dev/null +++ b/src/.gitignore @@ -0,0 +1,28 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates +*.log + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the \ No newline at end of file diff --git a/src/.vscode/launch.json b/src/.vscode/launch.json new file mode 100644 index 0000000..69f0818 --- /dev/null +++ b/src/.vscode/launch.json @@ -0,0 +1,34 @@ +{ + // 使用 IntelliSense 了解相关属性。 + // 悬停以查看现有属性的描述。 + // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": ".NET Core Launch (web)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/Znyc.Dispatching.Web.Entry/bin/Debug/net5.0/Znyc.Dispatching.Web.Entry.dll", + "args": [], + "cwd": "${workspaceFolder}/Znyc.Dispatching.Web.Entry", + "stopAtEntry": false, + "serverReadyAction": { + "action": "openExternally", + "pattern": "\\bNow listening on:\\s+(https?://\\S+)" + }, + "env": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "sourceFileMap": { + "/Views": "${workspaceFolder}/Views" + } + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach", + "processId": "${command:pickProcess}" + } + ] +} \ No newline at end of file diff --git a/src/.vscode/solution-explorer/class.cs-template b/src/.vscode/solution-explorer/class.cs-template new file mode 100644 index 0000000..015da46 --- /dev/null +++ b/src/.vscode/solution-explorer/class.cs-template @@ -0,0 +1,8 @@ +using System; + +namespace {{namespace}} +{ + public class {{name}} + { + } +} diff --git a/src/.vscode/solution-explorer/class.ts-template b/src/.vscode/solution-explorer/class.ts-template new file mode 100644 index 0000000..ff2edef --- /dev/null +++ b/src/.vscode/solution-explorer/class.ts-template @@ -0,0 +1,3 @@ +export class {{name}} { + +} \ No newline at end of file diff --git a/src/.vscode/solution-explorer/class.vb-template b/src/.vscode/solution-explorer/class.vb-template new file mode 100644 index 0000000..38ef67f --- /dev/null +++ b/src/.vscode/solution-explorer/class.vb-template @@ -0,0 +1,9 @@ +Imports System + +Namespace {{namespace}} + + Public Class {{name}} + + End Class + +End Namespace diff --git a/src/.vscode/solution-explorer/default.ts-template b/src/.vscode/solution-explorer/default.ts-template new file mode 100644 index 0000000..04af870 --- /dev/null +++ b/src/.vscode/solution-explorer/default.ts-template @@ -0,0 +1,3 @@ +export default {{name}} { + +} \ No newline at end of file diff --git a/src/.vscode/solution-explorer/enum.cs-template b/src/.vscode/solution-explorer/enum.cs-template new file mode 100644 index 0000000..7d4cdee --- /dev/null +++ b/src/.vscode/solution-explorer/enum.cs-template @@ -0,0 +1,8 @@ +using System; + +namespace {{namespace}} +{ + public enum {{name}} + { + } +} diff --git a/src/.vscode/solution-explorer/interface.cs-template b/src/.vscode/solution-explorer/interface.cs-template new file mode 100644 index 0000000..6b5dec1 --- /dev/null +++ b/src/.vscode/solution-explorer/interface.cs-template @@ -0,0 +1,8 @@ +using System; + +namespace {{namespace}} +{ + public interface {{name}} + { + } +} diff --git a/src/.vscode/solution-explorer/interface.ts-template b/src/.vscode/solution-explorer/interface.ts-template new file mode 100644 index 0000000..3ea404b --- /dev/null +++ b/src/.vscode/solution-explorer/interface.ts-template @@ -0,0 +1,3 @@ +export interface {{name}} { + +} \ No newline at end of file diff --git a/src/.vscode/solution-explorer/template-list.json b/src/.vscode/solution-explorer/template-list.json new file mode 100644 index 0000000..2849622 --- /dev/null +++ b/src/.vscode/solution-explorer/template-list.json @@ -0,0 +1,46 @@ +{ + "templates": [ + { + "name": "Class", + "extension": "cs", + "file": "./class.cs-template", + "parameters": "./template-parameters.js" + }, + { + "name": "Interface", + "extension": "cs", + "file": "./interface.cs-template", + "parameters": "./template-parameters.js" + }, + { + "name": "Enum", + "extension": "cs", + "file": "./enum.cs-template", + "parameters": "./template-parameters.js" + }, + { + "name": "Class", + "extension": "ts", + "file": "./class.ts-template", + "parameters": "./template-parameters.js" + }, + { + "name": "Interface", + "extension": "ts", + "file": "./interface.ts-template", + "parameters": "./template-parameters.js" + }, + { + "name": "Default", + "extension": "ts", + "file": "./default.ts-template", + "parameters": "./template-parameters.js" + }, + { + "name": "Class", + "extension": "vb", + "file": "./class.vb-template", + "parameters": "./template-parameters.js" + } + ] +} \ No newline at end of file diff --git a/src/.vscode/solution-explorer/template-parameters.js b/src/.vscode/solution-explorer/template-parameters.js new file mode 100644 index 0000000..daba8b2 --- /dev/null +++ b/src/.vscode/solution-explorer/template-parameters.js @@ -0,0 +1,17 @@ +var path = require("path"); + +module.exports = function(filename, projectPath, folderPath) { + var namespace = "Unknown"; + if (projectPath) { + namespace = path.basename(projectPath, path.extname(projectPath)); + if (folderPath) { + namespace += "." + folderPath.replace(path.dirname(projectPath), "").substring(1).replace(/[\\\/]/g, "."); + } + namespace = namespace.replace(/[\\\-]/g, "_"); + } + + return { + namespace: namespace, + name: path.basename(filename, path.extname(filename)) + } +}; \ No newline at end of file diff --git a/src/.vscode/tasks.json b/src/.vscode/tasks.json new file mode 100644 index 0000000..85740d9 --- /dev/null +++ b/src/.vscode/tasks.json @@ -0,0 +1,42 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/Znyc.Dispatching.Web.Entry/Znyc.Dispatching.Web.Entry.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/Znyc.Dispatching.Web.Entry/Znyc.Dispatching.Web.Entry.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "${workspaceFolder}/Znyc.Dispatching.Web.Entry/Znyc.Dispatching.Web.Entry.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/src/Dockerfile b/src/Dockerfile new file mode 100644 index 0000000..c221dd2 --- /dev/null +++ b/src/Dockerfile @@ -0,0 +1,33 @@ +#See https://aka.ms/containerfastmode to understand 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 +EXPOSE 443 + +ENV TZ=Asia/Shanghai +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + + +FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build +WORKDIR /src +COPY ["Znyc.Dispatching.Web.Entry/Znyc.Dispatching.Web.Entry.csproj", "Znyc.Dispatching.Web.Entry/"] +COPY ["Znyc.Dispatching.Web.Core/Znyc.Dispatching.Web.Core.csproj", "Znyc.Dispatching.Web.Core/"] +COPY ["Znyc.Dispatching.Database.Migrations/Znyc.Dispatching.Database.Migrations.csproj", "Znyc.Dispatching.Database.Migrations/"] +COPY ["Znyc.Dispatching.EntityFramework.Core/Znyc.Dispatching.EntityFramework.Core.csproj", "Znyc.Dispatching.EntityFramework.Core/"] +COPY ["Znyc.Dispatching.Core/Znyc.Dispatching.Core.csproj", "Znyc.Dispatching.Core/"] +COPY ["Znyc.Dispatching.Application/Znyc.Dispatching.Application.csproj", "Znyc.Dispatching.Application/"] +COPY ["Znyc.Dispatching.MongoDb.Repository/Znyc.Dispatching.MongoDb.Repository.csproj", "Znyc.Dispatching.MongoDb.Repository/"] +COPY ["Znyc.Dispatching.WeChat.Core/Znyc.Dispatching.WeChat.Core.csproj", "Znyc.Dispatching.WeChat.Core/"] +RUN dotnet restore "Znyc.Dispatching.Web.Entry/Znyc.Dispatching.Web.Entry.csproj" +COPY . . +WORKDIR "/src/Znyc.Dispatching.Web.Entry" +RUN dotnet build "Znyc.Dispatching.Web.Entry.csproj" -c Release -o /app/build + +FROM build AS publish +RUN dotnet publish "Znyc.Dispatching.Web.Entry.csproj" -c Release -o /app/publish + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "Znyc.Dispatching.Web.Entry.dll"] \ No newline at end of file diff --git a/src/Znyc.Dispatchimg.Test/ServiceTests/LoginServiceTest.cs b/src/Znyc.Dispatchimg.Test/ServiceTests/LoginServiceTest.cs new file mode 100644 index 0000000..d9aecc3 --- /dev/null +++ b/src/Znyc.Dispatchimg.Test/ServiceTests/LoginServiceTest.cs @@ -0,0 +1,18 @@ +using Furion; +using Znyc.Dispatching.Application; + +namespace Znyc.Dispatching.Tests.ServiceTests +{ + /// + /// 登录单元测试 + /// + public class LoginServiceTest + { + private readonly ILoginService _loginService; + public LoginServiceTest() + { + _loginService = App.GetService(); + } + + } +} diff --git a/src/Znyc.Dispatchimg.Test/ServiceTests/VehicleServiceTest.cs b/src/Znyc.Dispatchimg.Test/ServiceTests/VehicleServiceTest.cs new file mode 100644 index 0000000..a801d57 --- /dev/null +++ b/src/Znyc.Dispatchimg.Test/ServiceTests/VehicleServiceTest.cs @@ -0,0 +1,77 @@ +using Furion; +using Furion.DatabaseAccessor; +using Xunit; +using Znyc.Dispatching.Core.Entitys; + +namespace Znyc.Dispatching.Tests +{ + /// + /// 车辆测试 + /// + public class VehicleServiceTest + { + private readonly IRepository _employeeRepository; + + /// private readonly ilo _vehicleService; + public VehicleServiceTest() + { + _employeeRepository = App.GetService>(); + } + + /// + /// 根据id获取车辆信息 + /// + [Fact] + public async void GetVehicleByIdAsync() + { + Employee res = await _employeeRepository.FindAsync(162538803421253); + Assert.NotNull(res); + } + + ///// + ///// 根据id获取车辆Gps信息 + ///// + //[Fact] + //public async void GetVehicleGpsByIdAsync() + //{ + // var res = await _vehicleService.GetVehicleGpsByIdAsync(162538803421253); + // Assert.NotNull(res); + //} + ///// + ///// 获取地图信息 + ///// + //[Fact] + //public async void GetMapMonitorAsync() + //{ + // var res = await _vehicleService.GetMapMonitorAsync(); + // Assert.NotNull(res); + //} + + ///// + ///// 分页查询 + ///// + //[Fact] + //public async void PageAsync() + //{ + // var res = await _vehicleService.PageAsync(0, 1, 10); + // Assert.NotNull(res); + //} + + ///// + ///// 修改车辆信息 + ///// + //[Fact] + //public async void UpdateAsync() + //{ + // VehicleUpdateInput input = new VehicleUpdateInput() + // { + // VehicleCode = "Z01", + // VehiclePlate = "粤", + // ContactPerson = "单元", + // ContactPhone = "13111111111" + // }; + // await _vehicleService.UpdateAsync(input); + // Assert.True(true); + //} + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatchimg.Test/Startup.cs b/src/Znyc.Dispatchimg.Test/Startup.cs new file mode 100644 index 0000000..29ba202 --- /dev/null +++ b/src/Znyc.Dispatchimg.Test/Startup.cs @@ -0,0 +1,32 @@ +using Furion; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Xunit; +using Xunit.Abstractions; +using Xunit.Sdk; +using Znyc.Dispatching.Core; + +// 配置启动类类型,第一个参数是 Startup 类完整限定名,第二个参数是当前项目程序集名称 +[assembly: TestFramework("Znyc.Dispatching.Tests.Startup", "Znyc.Dispatching.Tests")] + +namespace Znyc.Dispatching.Tests +{ + /// + /// 单元测试启动类 + /// + /// 在这里可以使用 Furion 几乎所有功能 + public sealed class Startup : XunitTestFramework + { + public Startup(IMessageSink messageSink) : base(messageSink) + { + // 初始化 IServiceCollection 对象 + IServiceCollection services = Inject.Create(); + + services.AddScoped(); + services.TryAddSingleton(); + // 构建 ServiceProvider 对象 + services.Build(); + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatchimg.Test/UnitTest1.cs b/src/Znyc.Dispatchimg.Test/UnitTest1.cs new file mode 100644 index 0000000..a3acb36 --- /dev/null +++ b/src/Znyc.Dispatchimg.Test/UnitTest1.cs @@ -0,0 +1,13 @@ +using Xunit; + +namespace Znyc.Dispatching.Tests +{ + public class UnitTest1 + { + [Fact] + public void Test1() + { + Assert.Equal(2, 1 + 1); + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatchimg.Test/Znyc.Dispatching.Tests.csproj b/src/Znyc.Dispatchimg.Test/Znyc.Dispatching.Tests.csproj new file mode 100644 index 0000000..49b9cb3 --- /dev/null +++ b/src/Znyc.Dispatchimg.Test/Znyc.Dispatching.Tests.csproj @@ -0,0 +1,26 @@ + + + + net6.0 + false + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Cache/Services/CacheService.cs b/src/Znyc.Dispatching.Application/Cache/Services/CacheService.cs new file mode 100644 index 0000000..e5ba643 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Cache/Services/CacheService.cs @@ -0,0 +1,1235 @@ +using CSRedis; +using Furion.DatabaseAccessor; +using Furion.DependencyInjection; +using Furion.DynamicApiController; +using Mapster; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Znyc.Dispatching.Core; +using Znyc.Dispatching.Core.Cache; +using Znyc.Dispatching.Core.Entitys; +using Znyc.Dispatching.Core.Extension; +using Znyc.Dispatching.MongoDb.Repository.Collection; + +namespace Znyc.Dispatching.Application +{ + /// + /// 系统缓存服务 + /// + [ApiDescriptionSettings(Name = "cache", Order = 100)] + public class CacheService : ICacheService, IDynamicApiController, ITransient + { + private readonly IRepository _companyRepository; + private readonly IRepository _dictionaryRepository; + private readonly IRepository _employeeRepository; + private readonly IRedisCache _redisCache; + private readonly IRepository _vehicleRepository; + private readonly IRepository _yardRepository; + private readonly IRepository _userYardRepository; + private readonly IRepository _projectRepository; + private readonly IRepository _projectPersonRepository; + private readonly IRepository _orderRepository; + private readonly IRepository _vehiclePersonRepository; + private readonly IRepository _userRepository; + + public CacheService(IRedisCache redisCache, + IRepository vehicleRepository, + IRepository companyRepository, + IRepository dictionaryRepository, + IRepository yardRepository, + IRepository userYardRepository, + IRepository employeeRepository, + IRepository vehiclePersonRepositor, + IRepository projectPersonRepository, + IRepository projectRepository, + IRepository userRepository, + IRepository orderRepository) + { + _redisCache = redisCache; + _vehicleRepository = vehicleRepository; + _companyRepository = companyRepository; + _dictionaryRepository = dictionaryRepository; + _employeeRepository = employeeRepository; + _yardRepository = yardRepository; + _userYardRepository = userYardRepository; + _projectRepository = projectRepository; + _projectPersonRepository = projectPersonRepository; + _vehiclePersonRepository = vehiclePersonRepositor; + _userRepository = userRepository; + _orderRepository = orderRepository; + } + + #region 短信验证码 + + /// + /// 获取短信验证码缓存 + /// + /// + /// + [NonAction] + public async Task GetSmsCodeAsync(string phone) + { + string cacheKey = CommonConst.CACHE_KEY_CODE + $"{phone}"; + return await _redisCache.GetAsync(cacheKey); + } + + /// + /// 设置短信验证码缓存 + /// + /// + /// + /// + [NonAction] + public async Task SetSmsCodeAsync(string phone, string smsCode) + { + string cacheKey = CommonConst.CACHE_KEY_CODE + $"{phone}"; + return await _redisCache.SetAsync(cacheKey, smsCode, TimeSpan.FromMinutes(5)); + } + + /// + /// 删除短信验证码缓存 + /// + /// + /// + [NonAction] + public async Task RemoveSmsCodeAsync(string phone) + { + string cacheKey = CommonConst.CACHE_KEY_CODE + $"{phone}"; + await _redisCache.DelAsync(cacheKey); + } + + #endregion 短信验证码 + + #region 权限 + + /// + /// 获取权限缓存(按钮) + /// + /// + /// + [NonAction] + public async Task> GetPermissionAsync(long userId) + { + string cacheKey = CommonConst.CACHE_KEY_PERMISSION + $"{userId}"; + return await _redisCache.GetAsync>(cacheKey); + } + + /// + /// 缓存权限 + /// + /// + /// + /// + [NonAction] + public async Task SetPermissionAsync(long userId, List permissions) + { + string cacheKey = CommonConst.CACHE_KEY_PERMISSION + $"{userId}"; + await _redisCache.SetAsync(cacheKey, permissions); + } + + #endregion 权限 + + #region 车辆分组 + + /// + /// 同步车辆分组缓存 + /// + /// + /// + /// + [NonAction] + public async Task SetVehicleGroupListAsync(long companyId, List vehicleGroup) + { + string cacheKey = CommonConst.CACHE_KEY_PERMISSION + $"{companyId}"; + await _redisCache.SetAsync(cacheKey, vehicleGroup, TimeSpan.FromDays(31)); + } + + /// + /// 获取车辆分组缓存 + /// + /// + /// + [NonAction] + public async Task> GetVehicleGroupListAsync(long companyId) + { + string cacheKey = CommonConst.CACHE_KEY_PERMISSION + $"{companyId}"; + return await _redisCache.GetAsync>(cacheKey); + } + + /// + /// 同步车辆类型缓存 + /// + /// + /// + [NonAction] + public async Task SetVehicleTypeListAsync(List vehicleType) + { + string cacheKey = CommonConst.CACHE_KEY_VEHICLETYPE; + await _redisCache.SetAsync(cacheKey, vehicleType, TimeSpan.FromDays(33)); + } + + /// + /// 获取车辆分组缓存 + /// + /// + [NonAction] + public async Task> GetVehicleTypeListAsync() + { + string cacheKey = CommonConst.CACHE_KEY_VEHICLETYPE; + + return await _redisCache.GetAsync>(cacheKey); + } + + #endregion 车辆分组 + + #region 角色菜单 + + /// + /// 同步角色菜单缓存 + /// + /// + /// + /// + [NonAction] + public async Task SetRoleMenuAsync(long roleId, List menuList) + { + string cacheKey = CommonConst.CACHE_KEY_ROLEMENU + $"{roleId}"; + await _redisCache.SetAsync(cacheKey, menuList, TimeSpan.FromDays(34)); + } + + /// + /// 获取角色菜单缓存 + /// + /// + [NonAction] + public async Task> GetRoleMenuAsync(long roleId) + { + string cacheKey = CommonConst.CACHE_KEY_ROLEMENU + $"{roleId}"; + + return await _redisCache.GetAsync>(cacheKey); + } + + /// + /// 删除角色菜单缓存 + /// + /// + /// + [NonAction] + public async Task RemoveRoleMenuAsync(long roleId) + { + string cacheKey = CommonConst.CACHE_KEY_ROLEMENU + $"{roleId}"; + await _redisCache.DelAsync(cacheKey); + } + + /// + /// 同步角色缓存 + /// + /// + /// + [NonAction] + public async Task SetRoleAsync(List role) + { + string cacheKey = CommonConst.CACHE_KEY_ROLE; + await _redisCache.SetAsync(cacheKey, role, TimeSpan.FromDays(35)); + } + + /// + /// 获取角色缓存 + /// + /// + [NonAction] + public async Task> GetRoleAsync() + { + string cacheKey = CommonConst.CACHE_KEY_ROLE; + return await _redisCache.GetAsync>(cacheKey); + } + + #endregion 角色菜单 + + #region 字典类型 + + /// + /// 同步字典类型缓存 + /// + /// + /// + [NonAction] + public async Task SetDictionaryListAsync(List dictionaryLists) + { + string cacheKey = CommonConst.CACHE_KEY_DICTIONARY; + await _redisCache.SetAsync(cacheKey, dictionaryLists); + } + + /// + /// 获取字典缓存 + /// + /// + [NonAction] + public async Task> GetDictionaryListAsync() + { + string cacheKey = CommonConst.CACHE_KEY_DICTIONARY; + return await _redisCache.CacheShellAsync(cacheKey, (int)TimeSpan.FromDays(365).TotalSeconds, + () => _dictionaryRepository.DetachedEntities + .Where(x => x.IsEnabled == (int)CommonStatusEnum.ENABLE) + .OrderBy(x => x.Sort) + .Select(x => new DictionaryOutput + { + Id = x.Id, + Code = x.Code, + Value = x.Value, + Name = x.Name, + Sort = x.Sort + }) + .ToListAsync()); + } + + #endregion 字典类型 + + #region 公司 + + /// + /// 同步公司缓存 + /// + /// + /// + [NonAction] + public async Task SetComanyAsync(CompanyOutput company) + { + string cacheKey = CommonConst.CACHE_KEY_COMPANY + $"{company.Id}"; + await _redisCache.SetAsync(cacheKey, company, TimeSpan.FromDays(36)); + } + + /// + /// 获取公司缓存 + /// + /// + /// + /// + [NonAction] + public async Task GetCompanyAsync(long companyId, long userId) + { + string cacheKey = CommonConst.CACHE_KEY_COMPANY + $"{companyId}"; + //CompanyOutput companyOutput = await _redisCache.GetAsync(cacheKey); + var companyOutput = await _redisCache.CacheShellAsync(cacheKey, (int)TimeSpan.FromDays(30).TotalSeconds, + () => _companyRepository.Where(x => x.Id == companyId).Select(x => new CompanyOutput + { + Id = x.Id, + CompanyName = x.CompanyName, + Longitude = x.Longitude, + Latitude = x.Latitude, + Address = x.Address, + Status = x.Status, + StopTag = x.StopTag, + IsOpenDispatch = x.IsOpenDispatch, + IsSchedulingAddProject = x.IsSchedulingAddProject, + IsOpenVehicleType = x.IsOpenVehicleType + }).FirstOrDefaultAsync()); + if (companyOutput.IsNotNull()) + { + List yardList = await GetYardListAsync(companyId); + UserYard userYard = await _userYardRepository.DetachedEntities + .FirstOrDefaultAsync(x => x.UserId == userId && x.CompanyId == companyId && x.IsDefault == true); + List listOutputs = yardList.Adapt>(); + if (userYard.IsNotNull()) + { + foreach (var item in listOutputs) + { + if (item.Id == userYard.YardId) + { + item.IsDefault = true; + } + } + listOutputs = listOutputs.OrderByDescending(x => x.IsDefault).ToList(); + } + companyOutput.yardList = yardList.Count != 0 ? listOutputs : new List() { + new YardListOutput() + { + Id = 0, + Address = companyOutput.Address, + Latitude = companyOutput.Latitude, + Longitude = companyOutput.Longitude, + IsDefault = false + } + }; + } + return companyOutput; + } + + /// + /// 删除公司缓存 + /// + /// + /// + [NonAction] + public async Task RemoveCompanyAsync(long companyId) + { + string cacheKey = CommonConst.CACHE_KEY_COMPANY + $"{companyId}"; + await _redisCache.DelAsync(cacheKey); + } + + #endregion 公司 + + #region 用户 + + /// + /// 同步用户缓存 + /// + /// + /// + [NonAction] + public async Task SetUserAsync(UserOutput user) + { + string cacheKey = CommonConst.CACHE_KEY_USER + $"{user.Id}"; + await _redisCache.SetAsync(cacheKey, user, TimeSpan.FromDays(41)); + } + + /// + /// 获取用户缓存 + /// + /// + [NonAction] + public async Task GetUserAsync(long userId) + { + string cacheKey = CommonConst.CACHE_KEY_USER + $"{userId}"; + var userOutput = await _redisCache.GetAsync(cacheKey); + return userOutput; + } + + /// + /// 移除用户缓存 + /// + /// + /// + [NonAction] + public async Task RemoveUserAsync(long userId) + { + string cacheKey = CommonConst.CACHE_KEY_USER + $"{userId}"; + await _redisCache.DelAsync(cacheKey); + } + + /// + /// 删除用户所有缓存 + /// + /// + [NonAction] + public async Task RemoveCacheByUserIdAsync(long userId) + { + await RemoveUserAsync(userId); + await RemoveCompanyAsync(userId); + await RemoveTokenAsync(userId); + } + + #endregion 用户 + + #region Token + + /// + /// 写入token + /// + /// + /// + /// + [NonAction] + public async Task SetTokenAsync(long userId, string token) + { + string cacheKey = string.Format(CommonConst.CACHE_KEY_TOKEN, userId); + await _redisCache.SetAsync(cacheKey, token, TimeSpan.FromDays(60)); + } + + /// + /// 移除token + /// + /// + /// + [NonAction] + public async Task RemoveTokenAsync(long userId) + { + string cacheKey = string.Format(CommonConst.CACHE_KEY_TOKEN, userId); + await _redisCache.DelAsync(cacheKey); + } + + /// + /// 获取token + /// + /// + /// + [NonAction] + public async Task GetTokenAsync(long userId) + { + string cacheKey = string.Format(CommonConst.CACHE_KEY_TOKEN, userId); + return await _redisCache.GetAsync(cacheKey); + } + + #endregion Token + + #region 公司车辆 + + /// + /// 设置公司车辆缓存 + /// + /// + /// + /// + [NonAction] + public async Task SetVehicleByCompanyIdAsync(long companyId, List vehicles) + { + string cacheKey = CommonConst.CACHE_KEY_VEHICLEBYCOMPANYID + $"{companyId}"; + await _redisCache.SetAsync(cacheKey, vehicles, TimeSpan.FromDays(7)); + } + + /// + /// 删除公司车辆缓存 + /// + /// + /// + [NonAction] + public async Task RemoveVehicleByCompanyIdAsync(long companyId) + { + string cacheKey = CommonConst.CACHE_KEY_VEHICLEBYCOMPANYID + $"{companyId}"; + await _redisCache.DelAsync(cacheKey); + } + + /// + /// 删除公司车辆缓存 + /// + /// + /// + [NonAction] + public async Task RemoveAllVehicleByCompanyIdAsync() + { + string cacheKey = CommonConst.CACHE_KEY_VEHICLEBYCOMPANYID + $"*"; + await _redisCache.DelAsync(cacheKey); + } + + /// + /// 获取公司车辆缓存 + /// + /// + /// + [NonAction] + public async Task> GetVehicleByCompanyIdAsync(long companyId) + { + string cacheKey = CommonConst.CACHE_KEY_VEHICLEBYCOMPANYID + $"{companyId}"; + return await _redisCache.CacheShellAsync(cacheKey, (int)TimeSpan.FromDays(60).TotalSeconds, () => + _vehicleRepository + .Where(x => x.CompanyId == companyId) + .Select(x => new VehicleGpsOutput + { + Id = x.Id, + CompanyId = x.CompanyId, + VehiclePlate = x.VehiclePlate, + VehicleCode = x.VehicleCode, + ContactPerson = x.ContactPerson, + ContactPhone = x.ContactPhone, + Status = x.Status, + IsActivate = x.IsActivate, + IsGps = x.IsGps, + SimNo = x.SimNo, + IsOverspeedAlarm = x.IsOverspeedAlarm, + Overspeed = x.Overspeed, + ImeiNo = x.ImeiNo, + OpenTime = x.OpenTime, + ExpireTime = x.ExpireTime, + Sort = x.Sort, + }) + .OrderBy(x => x.Sort) + .ToListAsync()); + } + + /// + /// 获取公司车辆缓存 + /// + /// + /// + [NonAction] + public async Task> GetAllVehicleByCompanyIdAsync() + { + string cacheKey = CommonConst.CACHE_KEY_VEHICLEBYCOMPANYID + $"*"; + return await _redisCache.CacheShellAsync(cacheKey, (int)TimeSpan.FromDays(1).TotalSeconds, () => + _vehicleRepository + .Where(x => x.CompanyId != 0) + .Select(x => new VehicleGpsOutput + { + Id = x.Id, + CompanyId = x.CompanyId, + VehiclePlate = x.VehiclePlate, + VehicleCode = x.VehicleCode, + ContactPerson = x.ContactPerson, + ContactPhone = x.ContactPhone, + Status = x.Status, + IsActivate = x.IsActivate, + IsGps = x.IsGps, + SimNo = x.SimNo, + IsOverspeedAlarm = x.IsOverspeedAlarm, + Overspeed = x.Overspeed, + ImeiNo = x.ImeiNo, + OpenTime = x.OpenTime, + ExpireTime = x.ExpireTime, + Sort = x.Sort, + }) + .OrderBy(x => x.Sort) + .ToListAsync()); + } + + + + #endregion 公司车辆 + + #region 车辆 + + /// + /// 设置车辆缓存 + /// + /// + /// + [NonAction] + public async Task SetVehicleAsync(Vehicle vehicle) + { + string cacheKey = CommonConst.CACHE_KEY_VEHICLE + $"{vehicle.Id}"; + await _redisCache.SetAsync(cacheKey, vehicle, TimeSpan.FromDays(8)); + } + + /// + /// 删除车辆缓存 + /// + /// + /// + [NonAction] + public async Task RemoveVehicleAsync(long vehicleId) + { + string cacheKey = CommonConst.CACHE_KEY_VEHICLE + $"{vehicleId}"; + await _redisCache.DelAsync(cacheKey); + } + + /// + /// 获取车辆缓存 + /// + /// + /// + [NonAction] + public async Task GetVehicleAsync(long vehicleId) + { + string cacheKey = CommonConst.CACHE_KEY_VEHICLE + $"{vehicleId}"; + return await _redisCache.CacheShellAsync(cacheKey, (int)TimeSpan.FromDays(90).TotalSeconds, + () => _vehicleRepository.Where(x => x.Id == vehicleId).FirstOrDefaultAsync()); + } + + + #endregion 车辆 + + #region 员工 + + /// + /// 设置员工缓存 + /// + /// + /// + [NonAction] + public async Task SetEmployeeAsync(EmployeeOutput employee) + { + string cacheKey = string.Format(CommonConst.CACHE_KEY_EMPLOYEE, employee.Id); + await _redisCache.SetAsync(cacheKey, employee, TimeSpan.FromDays(11)); + } + + /// + /// 删除员工缓存 + /// + /// + /// + [NonAction] + public async Task RemoveEmployeeAsync(long employeeId) + { + string cacheKey = string.Format(CommonConst.CACHE_KEY_EMPLOYEE, employeeId); + await _redisCache.DelAsync(cacheKey); + } + + /// + /// 获取员工缓存 + /// + /// + /// + [NonAction] + public async Task GetEmployeeAsync(long employeeId) + { + string cacheKey = string.Format(CommonConst.CACHE_KEY_EMPLOYEE, employeeId); + return await _redisCache.CacheShellAsync(cacheKey, (int)TimeSpan.FromDays(90).TotalSeconds, () => + _employeeRepository.Where(x => x.Id == employeeId).Select(x => new EmployeeOutput + { + Id = x.Id, + UserId = x.UserId, + AvatarUrl = x.AvatarUrl, + RoleId = x.RoleId, + RoleName = x.RoleName, + CompanyId = x.CompanyId, + EmployeeName = x.EmployeeName, + EmployeePhone = x.EmployeePhone, + Status = x.Status + }).FirstOrDefaultAsync()); + } + + /// + /// 设置员工列表缓存 + /// + /// + /// + /// + [NonAction] + public async Task SetEmployeeListAsync(long companyId, List employees) + { + string cacheKey = string.Format(CommonConst.CACHE_KEY_EMPLOYEES, companyId); + await _redisCache.SetAsync(cacheKey, employees, TimeSpan.FromDays(13)); + } + + /// + /// 删除员工列表缓存 + /// + /// + /// + [NonAction] + public async Task RemoveEmployeeListAsync(long companyId) + { + string cacheKey = string.Format(CommonConst.CACHE_KEY_EMPLOYEES, companyId); + await _redisCache.DelAsync(cacheKey); + } + + /// + /// 获取员工列表缓存 + /// + /// + /// + [NonAction] + public async Task> GetEmployeeListAsync(long companyId) + { + string cacheKey = string.Format(CommonConst.CACHE_KEY_EMPLOYEES, companyId); + + return await _redisCache.CacheShellAsync(cacheKey, (int)TimeSpan.FromDays(95).TotalSeconds, () => + _employeeRepository.DetachedEntities + .Where(x => x.CompanyId == companyId + && x.IsDeleted == false) + .Select(x => new EmployeeOutput + { + Id = x.Id, + UserId = x.UserId, + RoleId = x.RoleId, + RoleName = x.RoleName, + CompanyId = x.CompanyId, + EmployeeName = x.EmployeeName, + EmployeePhone = x.EmployeePhone, + Status = x.Status + }).ToListAsync()); + } + + public async Task PublishAsync(string channel, string message) + { + return await _redisCache.PublishAsync(channel, message); + } + + public CSRedisClient.SubscribeObject Subscribe( + params (string, Action)[] channels) + { + return _redisCache.Subscribe(channels); + } + + #endregion 员工 + + #region 未读警报提醒 + + /// + /// 设置未读警报提醒缓存 + /// + /// + /// + public async Task SetUnreadAlarmAsync(long userId) + { + string key = string.Format(CommonConst.CACHE_KEY_UNREADALARM, userId); + await _redisCache.SetAsync(key, DateTime.Now, TimeSpan.FromDays(5)); + } + + /// + /// 删除未读警报提醒缓存 + /// + /// + /// + public async Task RemoveUnreadAlarmAsync(long userId) + { + string key = string.Format(CommonConst.CACHE_KEY_UNREADALARM, userId); + await _redisCache.DelAsync(key); + } + + /// + /// 获取未读警报提醒缓存 + /// + /// + public async Task GetUnreadAlarmAsync(long userId) + { + string key = string.Format(CommonConst.CACHE_KEY_UNREADALARM, userId); + return await _redisCache.GetAsync(key); + } + + #endregion + + #region 轨迹回放 + /// + /// 同步轨迹回放缓存 + /// + /// + /// + /// + /// + /// + /// + public async Task SeTrackPlaybackAsync(TrackPlaybackListOutput trackPlaybackListOutput, long vehicleId, DateTime startTime, DateTime endTime, TimeSpan expire) + { + string cacheKey = CommonConst.CACHE_KEY_TRACKPLAYBACK + $"{vehicleId}{startTime.ToTimestamp()}{endTime.ToTimestamp()}"; + await _redisCache.SetAsync(cacheKey, trackPlaybackListOutput, expire); + } + + /// + /// 获取轨迹回放缓存 + /// + /// + /// + /// + /// + public async Task GetTrackPlaybackAsync(long vehicleId, DateTime startTime, DateTime endTime) + { + string cacheKey = CommonConst.CACHE_KEY_TRACKPLAYBACK + $"{vehicleId}{startTime.ToTimestamp()}{endTime.ToTimestamp()}"; + return await _redisCache.GetAsync(cacheKey); + + } + + /// + /// 删除轨迹回放缓存 + /// + /// + /// + /// + /// + public async Task RemoveTrackPlaybackAsync(long vehicleId, DateTime startTime, DateTime endTime) + { + string cacheKey = CommonConst.CACHE_KEY_TRACKPLAYBACK + $"{vehicleId}{startTime.ToTimestamp()}{endTime.ToTimestamp()}"; + await _redisCache.DelAsync(cacheKey); + } + #endregion + + #region 车场 + /// + /// 设置车场缓存 + /// + /// + /// + [NonAction] + public async Task SetYardAsync(YardOutput yard) + { + string cacheKey = string.Format(CommonConst.CACHE_KEY_YARD, yard.Id); + await _redisCache.SetAsync(cacheKey, yard, TimeSpan.FromDays(11)); + } + + /// + /// 删除车场缓存 + /// + /// + /// + [NonAction] + public async Task RemoveYardAsync(long yardId) + { + string cacheKey = string.Format(CommonConst.CACHE_KEY_YARD, yardId); + await _redisCache.DelAsync(cacheKey); + } + + /// + /// 获取车场缓存 + /// + /// + /// + [NonAction] + public async Task GetYardAsync(long yardId) + { + string cacheKey = string.Format(CommonConst.CACHE_KEY_YARD, yardId); + return await _redisCache.CacheShellAsync(cacheKey, (int)TimeSpan.FromDays(90).TotalSeconds, () => + _yardRepository.Where(x => x.Id == yardId).Select(x => new YardOutput + { + Id = x.Id, + ContactPerson = x.ContactPerson, + ContactPhone = x.ContactPhone, + IsDefault = false, + Latitude = x.Latitude, + Longitude = x.Longitude, + Address = x.Address, + }).FirstOrDefaultAsync()); + } + + /// + /// 设置车场列表缓存 + /// + /// + /// + /// + [NonAction] + public async Task SetYardListAsync(long companyId, List yards) + { + string cacheKey = string.Format(CommonConst.CACHE_KEY_YARDS, companyId); + await _redisCache.SetAsync(cacheKey, yards, TimeSpan.FromDays(13)); + } + + /// + /// 删除车场列表缓存 + /// + /// + /// + [NonAction] + public async Task RemoveYardListAsync(long companyId) + { + string cacheKey = string.Format(CommonConst.CACHE_KEY_YARDS, companyId); + await _redisCache.DelAsync(cacheKey); + } + + /// + /// 获取车场列表缓存 + /// + /// + /// + [NonAction] + public async Task> GetYardListAsync(long companyId) + { + string cacheKey = string.Format(CommonConst.CACHE_KEY_YARDS, companyId); + return await _redisCache.CacheShellAsync(cacheKey, (int)TimeSpan.FromDays(95).TotalSeconds, () => + _yardRepository.DetachedEntities + .Where(x => x.CompanyId == companyId && x.Status == (int)CommonStatusEnum.ENABLE) + .Select(x => new YardOutput + { + Id = x.Id, + ContactPerson = x.ContactPerson, + ContactPhone = x.ContactPhone, + IsDefault = false, + Latitude = x.Latitude, + Longitude = x.Longitude, + Address = x.Address, + Status = x.Status + }).ToListAsync()); + } + #endregion + + #region 工程信息 + /// + /// 同步工程列表缓存 + /// + /// + /// + /// + [NonAction] + public async Task SetProjectListAsync(List projects, long companyId) + { + string cacheKey = string.Format(CommonConst.CACHE_KEY_PROJECTS, companyId); + await _redisCache.SetAsync(cacheKey, projects, TimeSpan.FromDays(15)); + } + + /// + /// 获取工程列表缓存 + /// + /// + /// + [NonAction] + public async Task> GetProjectListAsync(long companyId) + { + string cacheKey = string.Format(CommonConst.CACHE_KEY_PROJECTS, companyId); + var projectOutput = await _redisCache.GetAsync>(cacheKey); + if (projectOutput.IsNull()) + { + projectOutput = await _projectRepository.DetachedEntities + .Where(x => x.IsDeleted == false && x.CompanyId == companyId) + .Select(x => new ProjectListOutput + { + Id = x.Id, + ProjectName = x.ProjectName, + SalesmanId = x.SalesmanId, + Address = x.Address, + Latitude = x.Latitude, + Longitude = x.Longitude, + IsEnabled = x.IsEnabled, + ConstructionId = x.ConstructionId + }).ToListAsync(); + long[] projectIds = projectOutput.Select(x => x.Id).Distinct().ToArray(); + List projectPersons = await _projectPersonRepository.DetachedEntities + .Where(x => projectIds.Contains(x.ProjectId) && x.IsDeleted == false) + .ToListAsync(); + projectOutput.ForEach(item => + { + item.ProjectPeoples = projectPersons.Where(x => x.ProjectId == item.Id).Select(x => new ProjectPersonOutput + { + ProjectPersonId = x.ProjectPersonId, + ProjectPersonName = x.ProjectPersonName, + ProjectPersonPhone = x.ProjectPersonPhone + }).ToList(); + }); + await SetProjectListAsync(projectOutput, companyId); + } + + return projectOutput; + } + + /// + /// 删除工程列表缓存 + /// + /// + /// + [NonAction] + public async Task RemoveProjectListAsync(long companyId) + { + string cacheKey = string.Format(CommonConst.CACHE_KEY_PROJECTS, companyId); + await _redisCache.DelAsync(cacheKey); + } + + /// + /// 设置工程缓存 + /// + /// + /// + [NonAction] + public async Task SetProjectAsync(ProjectOutput project) + { + string cacheKey = string.Format(CommonConst.CACHE_KEY_PROJECT, project.Id); + await _redisCache.SetAsync(cacheKey, project, TimeSpan.FromDays(11)); + } + + /// + /// 删除工程缓存 + /// + /// + /// + [NonAction] + public async Task RemoveProjectAsync(long projectId) + { + string cacheKey = string.Format(CommonConst.CACHE_KEY_PROJECT, projectId); + await _redisCache.DelAsync(cacheKey); + } + + /// + /// 获取工程缓存 + /// + /// + /// + [NonAction] + public async Task GetProjectAsync(long projectId) + { + string cacheKey = string.Format(CommonConst.CACHE_KEY_PROJECT, projectId); + List projectPeoples = await _projectPersonRepository.DetachedEntities + .Where(x => x.ProjectId == projectId && x.IsDeleted == false) + .Select(x => new ProjectPersonOutput + { + ProjectPersonId = x.ProjectPersonId, + ProjectPersonName = x.ProjectPersonName, + ProjectPersonPhone = x.ProjectPersonPhone + }).ToListAsync(); + return await _redisCache.CacheShellAsync(cacheKey, (int)TimeSpan.FromDays(90).TotalSeconds, () => + _projectRepository.Where(x => x.Id == projectId).Select(x => new ProjectOutput + { + Id = x.Id, + ProjectName = x.ProjectName, + SalesmanId = x.SalesmanId, + Longitude = x.Longitude, + Latitude = x.Latitude, + Address = x.Address, + IsEnabled = x.IsEnabled, + ProjectPeoples = projectPeoples, + ConstructionId = x.ConstructionId, + ConstructionName = x.ConstructionName + }).FirstOrDefaultAsync()); + } + + #endregion + + + + #region 订单 + + /// + /// 设置订单列表缓存 + /// + /// + /// + /// + [NonAction] + public async Task SetOrderListAsync(long companyId, List orders) + { + string cacheKey = string.Format(CommonConst.CACHE_KEY_EMPLOYEES, companyId); + await _redisCache.SetAsync(cacheKey, orders, TimeSpan.FromDays(5)); + } + + /// + /// 删除订单列表缓存 + /// + /// + /// + [NonAction] + public async Task RemoveOrderListAsync(long companyId) + { + string cacheKey = string.Format(CommonConst.CACHE_KEY_ORDERS, companyId); + await _redisCache.DelAsync(cacheKey); + } + + /// + /// 获取订单列表缓存 + /// + /// + /// + [NonAction] + public async Task> GetOrderListAsync(long companyId) + { + string cacheKey = string.Format(CommonConst.CACHE_KEY_ORDERS, companyId); + + return await _redisCache.CacheShellAsync(cacheKey, (int)TimeSpan.FromDays(30).TotalSeconds, () => + _orderRepository.DetachedEntities + .Where(x => x.CompanyId == companyId) + .Select(x => new OrderListOutput + { + OrderSourceName = "222" + //RoleId = x.RoleId, + //RoleName = x.RoleName, + //CompanyId = x.CompanyId, + //EmployeeName = x.EmployeeName, + //OrderContent = x.or + }).ToListAsync()); + } + #endregion + + #region 车组人员 + /// + /// 设置车辆车组人员缓存 + /// + /// + /// + /// + [NonAction] + public async Task SetVehiclePersonAsync(List vehiclePerson, long vehicleId) + { + string cacheKey = CommonConst.CACHE_KEY_VEHICLEPERSON + $"{vehicleId}"; + await _redisCache.SetAsync(cacheKey, vehiclePerson, TimeSpan.FromDays(8)); + } + + /// + /// 删除车辆车组人员缓存 + /// + /// + /// + [NonAction] + public async Task RemoveVehiclePersonAsync(long vehicleId) + { + string cacheKey = CommonConst.CACHE_KEY_VEHICLEPERSON + $"{vehicleId}"; + await _redisCache.DelAsync(cacheKey); + } + + /// + /// 获取车辆车组人员缓存 + /// + /// + /// + [NonAction] + public async Task> GetVehiclePersonAsync(long vehicleId) + { + string cacheKey = CommonConst.CACHE_KEY_VEHICLEPERSON + $"{vehicleId}"; + return await _redisCache.CacheShellAsync(cacheKey, (int)TimeSpan.FromDays(90).TotalSeconds, + () => _vehiclePersonRepository.Where(x => x.IsDeleted == false).Select(x => new VehiclePersonOutput + { + Id = x.Id, + VehicleId = x.VehicleId, + UserId = x.UserId, + UserName = x.UserName, + UserPhone = x.UserPhone, + IsDriver = x.IsDriver + }).Where(x => x.VehicleId == vehicleId).ToListAsync()); + } + + #endregion + + + #region Order + /// + /// 设置缓存 + /// + /// + /// + /// + public async Task SetVehicleGpsAsync(long orderId, dynamic clay) + { + string cacheKey = CommonConst.CACHE_KEY_GPSREALTIME + $"{orderId}"; + await _redisCache.SetAsync(cacheKey, clay, TimeSpan.FromDays(1)); + + } + + /// + /// 删除缓存 + /// + /// + /// + public async Task RemoveVehicleGpsAsync(long orderId) + { + string cacheKey = CommonConst.CACHE_KEY_GPSREALTIME + $"{orderId}"; + await _redisCache.DelAsync(cacheKey); + + } + + /// + /// 获取缓存 + /// + /// + /// + public async Task GetVehicleGpsAsync(long orderId) + { + string cacheKey = CommonConst.CACHE_KEY_GPSREALTIME + $"{orderId}"; + return await _redisCache.GetAsync(cacheKey); + } + #endregion + + + + /// + /// 设置缓存 + /// + /// + /// + public async Task SetOrderReadAsync(long orderId, string messageType) + { + string cacheKey = CommonConst.CACHE_KEY_ORDERREAD + $"{orderId}"; + await _redisCache.SetAsync(cacheKey, messageType); + } + + /// + /// 删除缓存 + /// + /// + /// + public async Task RemoveOrderReadAsync(long orderId) + { + string cacheKey = CommonConst.CACHE_KEY_ORDERREAD + $"{orderId}"; + await _redisCache.DelAsync(cacheKey); + } + + /// + /// 获取缓存 + /// + /// + /// + public async Task GetOrderReadAsync(long orderId) + { + string cacheKey = CommonConst.CACHE_KEY_ORDERREAD + $"{orderId}"; + return await _redisCache.GetAsync(cacheKey); + } + + /// + /// 获取缓存 + /// + /// + /// + public async Task ExistsOrderReadAsync(long orderId) + { + string cacheKey = CommonConst.CACHE_KEY_ORDERREAD + $"{orderId}"; + return await _redisCache.ExistsAsync(cacheKey); + } + + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Cache/Services/ICacheService.cs b/src/Znyc.Dispatching.Application/Cache/Services/ICacheService.cs new file mode 100644 index 0000000..d6375ab --- /dev/null +++ b/src/Znyc.Dispatching.Application/Cache/Services/ICacheService.cs @@ -0,0 +1,554 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Znyc.Dispatching.Core.Entitys; +using Znyc.Dispatching.MongoDb.Repository.Collection; +using static CSRedis.CSRedisClient; + +namespace Znyc.Dispatching.Application +{ + public interface ICacheService + { + /// + /// 获取短信验证码 + /// + /// + /// + Task GetSmsCodeAsync(string phone); + + /// + /// 设置验证码缓存 + /// + /// + /// + /// + Task SetSmsCodeAsync(string phone, string smsCode); + + /// + /// 删除验证码 + /// + /// + /// + Task RemoveSmsCodeAsync(string phone); + + /// + /// 获取权限缓存(按钮) + /// + /// + /// + Task> GetPermissionAsync(long userId); + + /// + /// 缓存权限 + /// + /// + /// + /// + Task SetPermissionAsync(long userId, List permissions); + + /// + /// 同步车辆分组缓存 + /// + /// + /// + /// + Task SetVehicleGroupListAsync(long companyId, List vehicleGroup); + + /// + /// 获取车辆分组缓存 + /// + /// + /// + Task> GetVehicleGroupListAsync(long companyId); + + /// + /// 同步车辆类型缓存 + /// + /// + /// + Task SetVehicleTypeListAsync(List vehicleType); + + /// + /// 获取车辆类型缓存 + /// + /// + Task> GetVehicleTypeListAsync(); + + /// + /// 同步角色菜单缓存 + /// + /// + /// + /// + Task SetRoleMenuAsync(long roleId, List menuList); + + /// + /// 获取角色菜单缓存 + /// + /// + /// + Task> GetRoleMenuAsync(long roleId); + + /// + /// 删除角色菜单缓存 + /// + /// + /// + Task RemoveRoleMenuAsync(long roleId); + + /// + /// 同步角色缓存 + /// + /// + /// + Task SetRoleAsync(List role); + + /// + /// 获取角色缓存 + /// + /// + Task> GetRoleAsync(); + + /// + /// 获取字典缓存 + /// + /// + Task> GetDictionaryListAsync(); + + /// + /// 同步字典缓存 + /// + /// + /// + Task SetDictionaryListAsync(List dictionaryLists); + + /// + /// 同步公司缓存 + /// + /// + /// + Task SetComanyAsync(CompanyOutput company); + + /// + /// 获取公司缓存 + /// + /// + /// + /// + Task GetCompanyAsync(long companyId, long userId); + + /// + /// 删除公司缓存 + /// + /// + /// + Task RemoveCompanyAsync(long companyId); + + /// + /// 同步用户缓存 + /// + /// + /// + Task SetUserAsync(UserOutput user); + + /// + /// 获取用户缓存 + /// + /// + /// + Task GetUserAsync(long userId); + + /// + /// 移除用户缓存 + /// + /// + /// + Task RemoveUserAsync(long userId); + + /// + /// 删除用户所有缓存 + /// + /// + Task RemoveCacheByUserIdAsync(long userId); + + /// + /// 写入token + /// + /// + /// + /// + Task SetTokenAsync(long userId, string token); + + /// + /// 移除token + /// + /// + /// + Task RemoveTokenAsync(long userId); + + /// + /// 获取token + /// + /// + /// + Task GetTokenAsync(long userId); + + + /// + /// 发布 + /// + /// + /// + /// + Task PublishAsync(string channel, string message); + + + /// + /// 订阅 + /// + /// + /// + SubscribeObject Subscribe(params (string, Action)[] channels); + + #region 公司车辆 + + /// + /// 设置公司车辆缓存 + /// + /// + /// + /// + Task SetVehicleByCompanyIdAsync(long companyId, List vehicles); + + /// + /// 删除公司车辆缓存 + /// + /// + /// + Task RemoveVehicleByCompanyIdAsync(long companyId); + + + /// + /// 删除公司车辆缓存 + /// + /// + /// + Task RemoveAllVehicleByCompanyIdAsync(); + + + /// + /// 获取所有公司车辆缓存 + /// + /// + /// + Task> GetAllVehicleByCompanyIdAsync(); + + /// + /// 获取公司车辆缓存 + /// + /// + /// + Task> GetVehicleByCompanyIdAsync(long companyId); + + #endregion 公司车辆 + + #region 车辆 + + /// + /// 设置车辆缓存 + /// + /// + /// + Task SetVehicleAsync(Vehicle vehicle); + + /// + /// 删除车辆缓存 + /// + /// + /// + Task RemoveVehicleAsync(long vehicleId); + + /// + /// 获取车辆缓存 + /// + /// + /// + Task GetVehicleAsync(long vehicleId); + + #endregion 车辆 + + #region 员工 + + /// + /// 设置车辆缓存 + /// + /// + /// + Task SetEmployeeAsync(EmployeeOutput employee); + + /// + /// 删除员工缓存 + /// + /// + /// + Task RemoveEmployeeAsync(long employeeId); + + /// + /// 获取员工缓存 + /// + /// + /// + Task GetEmployeeAsync(long employeeId); + + /// + /// 设置员工列表缓存 + /// + /// + /// + /// + Task SetEmployeeListAsync(long companyId, List employees); + + /// + /// 删除员工列表缓存 + /// + /// + /// + Task RemoveEmployeeListAsync(long companyId); + + /// + /// 获取员工列表缓存 + /// + /// + /// + Task> GetEmployeeListAsync(long companyId); + + #endregion 员工 + + #region 未读警报提醒 + + /// + /// 设置未读警报提醒缓存 + /// + /// + /// + Task SetUnreadAlarmAsync(long userId); + + /// + /// 删除未读警报提醒缓存 + /// + /// + /// + Task RemoveUnreadAlarmAsync(long userId); + + /// + /// 获取未读警报提醒缓存 + /// + /// + Task GetUnreadAlarmAsync(long userId); + + #endregion + + #region 轨迹回放 + /// + /// 同步轨迹回放缓存 + /// + /// + /// + /// + /// + /// + /// + Task SeTrackPlaybackAsync(TrackPlaybackListOutput trackPlaybackListOutput, long vehicleId, DateTime startTime, DateTime endTime, TimeSpan expire); + + /// + /// 获取轨迹回放缓存 + /// + /// + /// + /// + /// + Task GetTrackPlaybackAsync(long vehicleId, DateTime startTime, DateTime endTime); + + /// + /// 删除轨迹回放缓存 + /// + /// + /// + /// + /// + Task RemoveTrackPlaybackAsync(long vehicleId, DateTime startTime, DateTime endTime); + #endregion + + #region 车场 + /// + /// 设置车场缓存 + /// + /// + /// + Task SetYardAsync(YardOutput yard); + + /// + /// 删除车场缓存 + /// + /// + /// + Task RemoveYardAsync(long yardId); + + /// + /// 获取车场缓存 + /// + /// + /// + Task GetYardAsync(long yardId); + + /// + /// 设置车场列表缓存 + /// + /// + /// + /// + Task SetYardListAsync(long companyId, List yards); + + /// + /// 删除车场列表缓存 + /// + /// + /// + Task RemoveYardListAsync(long companyId); + + /// + /// 获取车场列表缓存 + /// + /// + /// + Task> GetYardListAsync(long companyId); + #endregion + + + #region 工程信息 + /// + /// 同步工程列表缓存 + /// + /// + /// + /// + Task SetProjectListAsync(List projects, long companyId); + + /// + /// 获取工程列表缓存 + /// + /// + /// + Task> GetProjectListAsync(long companyId); + + /// + /// 删除工程列表缓存 + /// + /// + /// + Task RemoveProjectListAsync(long companyId); + + /// + /// 设置工程缓存 + /// + /// + /// + Task SetProjectAsync(ProjectOutput project); + + /// + /// 删除工程缓存 + /// + /// + /// + Task RemoveProjectAsync(long projectId); + + /// + /// 获取工程缓存 + /// + /// + /// + Task GetProjectAsync(long projectId); + #endregion + + #region 车组人员 + /// + /// 设置车辆车组人员缓存 + /// + /// + /// + /// + Task SetVehiclePersonAsync(List vehiclePerson, long vehicleId); + + /// + /// 删除车辆车组人员缓存 + /// + /// + /// + Task RemoveVehiclePersonAsync(long vehicleId); + + /// + /// 获取车辆车组人员缓存 + /// + /// + /// + Task> GetVehiclePersonAsync(long vehicleId); + + #endregion + + #region Order + /// + /// 设置缓存 + /// + /// + /// + /// + Task SetVehicleGpsAsync(long orderId, dynamic clay); + + /// + /// 删除缓存 + /// + /// + /// + Task RemoveVehicleGpsAsync(long orderId); + + /// + /// 获取缓存 + /// + /// + /// + Task GetVehicleGpsAsync(long orderId); + + + /// + /// 设置缓存 + /// + /// + /// + /// + Task SetOrderReadAsync(long orderId, string messageType); + + /// + /// 删除缓存 + /// + /// + /// + Task RemoveOrderReadAsync(long orderId); + + /// + /// 获取缓存 + /// + /// + /// + Task GetOrderReadAsync(long orderId); + + + + /// + /// 是否存在 + /// + /// + /// + Task ExistsOrderReadAsync(long orderId); + #endregion + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Company/Dto/Input/CompanyAddInput.cs b/src/Znyc.Dispatching.Application/Company/Dto/Input/CompanyAddInput.cs new file mode 100644 index 0000000..3008b70 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Company/Dto/Input/CompanyAddInput.cs @@ -0,0 +1,59 @@ +using System.ComponentModel.DataAnnotations; + +namespace Znyc.Dispatching.Application +{ + /// + /// 公司输入实体 + /// + public class CompanyAddInput + { + /// + /// 公司名称 + /// + [Required(ErrorMessage = "公司名称不能为空!")] + [MaxLength(18, ErrorMessage = "公司名称不得超过18个字")] + public string CompanyName { get; set; } + + ///// + ///// 公司Logo + ///// + // public string CompanyLogo { get; set; } + + /// + /// 联系人 + /// + [MaxLength(8, ErrorMessage = "联系人不得超过8个字")] + public string ContactPerson { get; set; } + + /// + /// 联系人电话 + /// + //[Required(ErrorMessage = "联系人电话不能为空!"), MaxLength(11, ErrorMessage = "联系电话不能超过11位")] + //[RegularExpression("^[1][3,4,5,6,7,8,9][0-9]{9}$", ErrorMessage = "请输入正确的手机号码")] + public string ContactPhone { get; set; } + + /// + /// 精度 + /// + [Required(ErrorMessage = "地址不能为空!")] + public decimal Longitude { get; set; } + + /// + /// 纬度 + /// + [Required(ErrorMessage = "地址不能为空!")] + public decimal Latitude { get; set; } + + /// + /// 地址 + /// + [Required(ErrorMessage = "地址不能为空!")] + [MaxLength(35, ErrorMessage = "地址不得超过35个字")] + public string Address { get; set; } + + /// + /// OpenId + /// + public string JsCode { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Company/Dto/Input/CompanyUpdateInput.cs b/src/Znyc.Dispatching.Application/Company/Dto/Input/CompanyUpdateInput.cs new file mode 100644 index 0000000..1b6b05e --- /dev/null +++ b/src/Znyc.Dispatching.Application/Company/Dto/Input/CompanyUpdateInput.cs @@ -0,0 +1,9 @@ +namespace Znyc.Dispatching.Application +{ + /// + /// + public class CompanyUpdateInput : CompanyAddInput + { + public long Id { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Company/Dto/Input/RegisterInuput.cs b/src/Znyc.Dispatching.Application/Company/Dto/Input/RegisterInuput.cs new file mode 100644 index 0000000..e996f2d --- /dev/null +++ b/src/Znyc.Dispatching.Application/Company/Dto/Input/RegisterInuput.cs @@ -0,0 +1,61 @@ +using System.ComponentModel.DataAnnotations; + +namespace Znyc.Dispatching.Application +{ + /// + /// 平台入驻 + /// + public class RegisterInuput + { + + /// + /// JsCode + /// + public string JsCode { get; set; } + + /// + /// 用户名 + /// + [MaxLength(8, ErrorMessage = "用户名不得超过8个字")] + public string UserName { get; set; } + + /// + /// 头像 + /// + public string AvatarUrl { get; set; } + + /// + /// 手机号 + /// + [MaxLength(11, ErrorMessage = "手机号输入格式不正确")] + public string Phone { get; set; } + + + /// + /// 公司名称 + /// + [Required(ErrorMessage = "公司名称不能为空!")] + [MaxLength(18, ErrorMessage = "公司名称不得超过18个字")] + public string CompanyName { get; set; } + + + /// + /// 精度 + /// + [Required(ErrorMessage = "地址不能为空!")] + public decimal Longitude { get; set; } + + /// + /// 纬度 + /// + [Required(ErrorMessage = "地址不能为空!")] + public decimal Latitude { get; set; } + + /// + /// 地址 + /// + [Required(ErrorMessage = "地址不能为空!")] + [MaxLength(35, ErrorMessage = "地址不得超过35个字")] + public string Address { get; set; } + } +} diff --git a/src/Znyc.Dispatching.Application/Company/Dto/OutPut/CompanyOutput.cs b/src/Znyc.Dispatching.Application/Company/Dto/OutPut/CompanyOutput.cs new file mode 100644 index 0000000..81b5fd9 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Company/Dto/OutPut/CompanyOutput.cs @@ -0,0 +1,81 @@ +using System.Collections.Generic; + +namespace Znyc.Dispatching.Application +{ + public class CompanyOutput + { + /// + /// 主键 + /// + public long Id { get; set; } + + /// + /// 公司名称 + /// + public string CompanyName { get; set; } + + /// + /// 公司Logo + /// + public string CompanyLogo { get; set; } + + /// + /// 精度 + /// + public decimal Longitude { get; set; } + + /// + /// 纬度 + /// + + public decimal Latitude { get; set; } + + /// + /// 地址 + /// + public string Address { get; set; } + + /// + /// 状态(字典 0正常 1停用 2删除) + /// + public int Status { get; set; } + + /// + /// 停留标示Id + /// + public long StopTag { get; set; } + + /// + /// 车场 + /// + public List yardList { get; set; } + + + #region #v1.2.3 + + + /// + /// 一键派工是否显示工程名称 + /// + public bool IsOpenDispatch { get; set; } + + + /// + /// 调度角色是否有添 加工程名称的权限 + /// + public bool IsSchedulingAddProject { get; set; } + + #endregion + + + + #region v1.2.7 + + /// + /// 是否启用任务车型选项,默认为关 + /// + public bool IsOpenVehicleType { get; set; } + + #endregion + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Company/Services/CompanyService.cs b/src/Znyc.Dispatching.Application/Company/Services/CompanyService.cs new file mode 100644 index 0000000..8f4f6f1 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Company/Services/CompanyService.cs @@ -0,0 +1,307 @@ +using Furion.DatabaseAccessor; +using Furion.DependencyInjection; +using Furion.DynamicApiController; +using Furion.FriendlyException; +using Mapster; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Options; +using Senparc.Weixin.WxOpen.AdvancedAPIs.Sns; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; +using Znyc.Dispatching.Core; +using Znyc.Dispatching.Core.Entitys; +using Znyc.Dispatching.Core.Extension; +using Znyc.Dispatching.Core.Helpers; + +namespace Znyc.Dispatching.Application +{ + /// + /// 公司服务 + /// + [ApiDescriptionSettings(Name = "company", Order = 30)] + public class CompanyService : ICompanyService, IDynamicApiController, ITransient + { + private readonly ICacheService _cacheService; + private readonly IRepository _employeeRepository; + private readonly IRepository _repository; + private readonly IUserManager _userManager; + private readonly IRepository _userRepository; + private readonly IRepository _vehicleGroupRepository; + private readonly WeixinSettingOptions _weixinSettingOptions; + private readonly IRepository _yardRepository; + private readonly IRepository _userYardRepository; + + public CompanyService( + IRepository repository, + IRepository employeeRepository, + IRepository userRepository, + IRepository vehicleGroupRepository, + IUserManager userManager, + ICacheService cacheService, + IRepository yardRepository, + IOptions weixinSettingOptions, + IRepository userYardRepository + ) + { + _employeeRepository = employeeRepository; + _userRepository = userRepository; + _vehicleGroupRepository = vehicleGroupRepository; + _repository = repository; + _userManager = userManager; + _cacheService = cacheService; + _yardRepository = yardRepository; + _weixinSettingOptions = weixinSettingOptions.Value; + _userYardRepository = userYardRepository; + } + + + + /// + /// 获取当前公司信息 + /// + /// + [HttpGet] + [Route("api/v1/company")] + public async Task GetAsync() + { + return await _cacheService.GetCompanyAsync(_userManager.CompanyId, _userManager.UserId); + } + + /// + /// 根据Id获取公司信息 + /// + /// + /// + [HttpGet] + [Route("api/v1/company/{companyId}")] + public async Task GetByIdAsync(long companyId) + { + return await _cacheService.GetCompanyAsync(companyId, _userManager.UserId); + } + + /// + /// 更新公司信息 + /// + /// + /// + [HttpPut] + [Route("api/v1/company")] + public async Task UpdateAsync(CompanyUpdateInput companyUpdate) + { + var company = await _repository.FindAsync(companyUpdate.Id); + if (company.IsNotNull()) + { + company.CompanyName = companyUpdate.CompanyName; + company.Latitude = companyUpdate.Latitude; + company.Longitude = companyUpdate.Longitude; + company.Address = companyUpdate.Address; + } + var companyId = (await _repository.UpdateNowAsync(company)).Entity.Id; + if (companyId == 0) + { + throw Oops.Bah("更新失败"); + } + await _cacheService.RemoveCompanyAsync(_userManager.CompanyId); + await _cacheService.RemoveUserAsync(_userManager.UserId); + } + + /// + /// 平台入驻 + /// + /// + /// + [AllowAnonymous] + [UnitOfWork] + [HttpPost] + [Route("api/v1/register")] + public async Task RegisterAsync(RegisterInuput registerInuput) + { + var jsCode2JsonResult = + await SnsApi.JsCode2JsonAsync(_weixinSettingOptions.WxOpenAppId, _weixinSettingOptions.WxOpenAppSecret, + registerInuput.JsCode); + if (jsCode2JsonResult.IsNull()) + { + throw Oops.Bah(ErrorCode.D1506); + } + var user = await _userRepository.FirstOrDefaultAsync(x => x.OpenId == jsCode2JsonResult.openid); + if (user.IsNotNull()) + { + var employeeList = await _employeeRepository.DetachedEntities + .Where(x => x.UserId == user.Id && x.Status == (int)CommonStatusEnum.ENABLE) + .ToListAsync(); + var cIds = employeeList.Select(x => x.CompanyId).ToArray(); + + //如果未包含管理员权限,可以一直注册 + if (employeeList.Where(x => x.RoleId == (long)RoleStatusEnum.Administrator && x.Status == (int)CommonStatusEnum.ENABLE).Any()) + { + throw Oops.Bah("您已注册公司"); + } + + } + + var entity = registerInuput.Adapt(); + entity.Status = (int)CommonStatusEnum.REVIEW; + entity.ContactPerson = registerInuput.UserName ?? "企业管理员"; + entity.ContactPhone = registerInuput.Phone; + entity.CompanyLogo = CommonConst.DEFAULT_AVATARURL; + entity.StopTag = CommonConst.DEFAULT_STAPTAG; + var companyId = (await _repository.InsertNowAsync(entity)).Entity.Id; + if (companyId > 0) + { + var userId = user.IsNull() ? 0 : user.Id; + if (user.IsNull()) + { + //用户 + userId = (await _userRepository.InsertNowAsync(new User + { + OpenId = jsCode2JsonResult.openid, + AvatarUrl = registerInuput.AvatarUrl, + UserName = registerInuput.UserName ?? "企业管理员", + Phone = registerInuput.Phone, + Status = CommonStatusEnum.ENABLE + })).Entity.Id; + } + //员工 + await _employeeRepository.InsertNowAsync(new Employee + { + UserId = userId, + AvatarUrl = CommonConst.DEFAULT_AVATARURL, + EmployeeName = registerInuput.UserName ?? "企业管理员", + EmployeePhone = registerInuput.Phone, + CompanyId = companyId, + RoleId = (int)RoleStatusEnum.Administrator, + RoleName = typeof(RoleStatusEnum).GetDescription((int)RoleStatusEnum.Administrator), + Status = (int)CommonStatusEnum.ENABLE, + IsDefault = true + }); + //车辆分组 + await _vehicleGroupRepository.InsertNowAsync(new VehicleGroup + { + CompanyId = companyId, + Name = "默认分组", + ParentId = 0 + }); + //车场 + var yardId = (await _yardRepository.InsertNowAsync(new Yard + { + CompanyId = companyId, + Status = (int)CommonStatusEnum.ENABLE, + ContactPerson = registerInuput.UserName ?? "企业管理员", + ContactPhone = registerInuput.Phone, + Longitude = registerInuput.Longitude, + Latitude = registerInuput.Latitude, + Address = registerInuput.Address, + + })).Entity.Id; + + //用户车场映射 + await _userYardRepository.InsertNowAsync(new UserYard + { + UserId = userId, + YardId = yardId, + CompanyId = companyId, + IsDefault = true + }); + } + } + + + /// + /// 获取位置名称 + /// + /// + /// + /// + [HttpGet] + [AllowAnonymous] + [Route("api/v1/address/{longitude}/{latitude}")] + public string GetAddress([Required] decimal longitude, [Required] decimal latitude) + { + string adress = MapHelper.GetLocationByLngLat(longitude, latitude); + return adress; + } + + /// + /// 修改公司停留标示 + /// + /// + /// + [HttpPut] + [Route("api/v1/company/stoptag/{stopTag}")] + public async Task UpdatStopTagAsync([Required] long stopTag) + { + var company = await _repository.FirstOrDefaultAsync(x => x.Id == _userManager.CompanyId); + if (company.IsNull()) + { + throw Oops.Bah("公司不存在"); + } + company.StopTag = stopTag; + await _repository.UpdateNowAsync(company); + await _cacheService.RemoveCompanyAsync(_userManager.CompanyId); + } + + + + + /// + /// 一键派工是否显示工程名称 + /// + /// + [HttpPut] + [Route("api/v1/company/opendispatch")] + public async Task UpdateIsOpenDispatchAsync() + { + var company = await _repository.FirstOrDefaultAsync(x => x.Id == _userManager.CompanyId); + if (company.IsNull()) + { + throw Oops.Bah("公司不存在"); + + } + company.IsOpenDispatch = !company.IsOpenDispatch; + await _repository.UpdateNowAsync(company); + await _cacheService.RemoveCompanyAsync(_userManager.CompanyId); + } + + + /// + /// 调度角色是否有添 加工程名称的权限 + /// + /// + [HttpPut] + [Route("api/v1/company/schedulingaddproject")] + public async Task UpdateIsSchedulingAddProjectAsync() + { + var company = await _repository.FirstOrDefaultAsync(x => x.Id == _userManager.CompanyId); + if (company.IsNull()) + { + throw Oops.Bah("公司不存在"); + + } + company.IsSchedulingAddProject = !company.IsSchedulingAddProject; + await _repository.UpdateNowAsync(company); + await _cacheService.RemoveCompanyAsync(_userManager.CompanyId); + } + + /// + /// + /// + /// + [HttpPut] + [Route("api/v1/company/isOpenVehicleType")] + public async Task UpdateIsOpenVehicleTypeAsync() + { + var company = await _repository.FirstOrDefaultAsync(x => x.Id == _userManager.CompanyId); + if (company.IsNull()) + { + throw Oops.Bah("公司不存在"); + + } + company.IsOpenVehicleType = !company.IsOpenVehicleType; + await _repository.UpdateNowAsync(company); + await _cacheService.RemoveCompanyAsync(_userManager.CompanyId); + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Company/Services/ICompanyService.cs b/src/Znyc.Dispatching.Application/Company/Services/ICompanyService.cs new file mode 100644 index 0000000..82cc206 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Company/Services/ICompanyService.cs @@ -0,0 +1,68 @@ +using System.Threading.Tasks; + +namespace Znyc.Dispatching.Application +{ + public interface ICompanyService + { + /// + /// 注册公司 + /// + /// + /// + Task RegisterAsync(RegisterInuput registerInuput); + + /// + /// 获取当前公司信息 + /// + /// + Task GetAsync(); + + /// + /// 根据Id获取公司信息 + /// + /// + Task GetByIdAsync(long companyId); + + /// + /// 更新公司信息 + /// + /// + /// + Task UpdateAsync(CompanyUpdateInput input); + + /// + /// 获取位置名称 + /// + /// + /// + /// + string GetAddress(decimal longitude, decimal latitude); + + /// + /// 修改公司停留标示 + /// + /// + /// + Task UpdatStopTagAsync(long stopTag); + + + /// + /// 一键派工是否显示工程名称 + /// + /// + Task UpdateIsOpenDispatchAsync(); + + /// + /// 调度角色是否有添 加工程名称的权限 + /// + /// + Task UpdateIsSchedulingAddProjectAsync(); + + + /// + /// 新增是否启用任务车型选项,默认为关 + /// + /// + Task UpdateIsOpenVehicleTypeAsync(); + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Construction/Dto/Input/ConstructionAddInput.cs b/src/Znyc.Dispatching.Application/Construction/Dto/Input/ConstructionAddInput.cs new file mode 100644 index 0000000..2df89bd --- /dev/null +++ b/src/Znyc.Dispatching.Application/Construction/Dto/Input/ConstructionAddInput.cs @@ -0,0 +1,32 @@ +using System.ComponentModel.DataAnnotations; + +namespace ZNYC.Recruitment.Service.Dictionary +{ + /// + /// 添加 + /// + public class ConstructionAddInput + { + /// + /// 施工单位 + /// + [Required(ErrorMessage = "请填写施工单位")] + public string ConstructionName { get; set; } + + /// + /// 联系人 + /// + public string ContactName { get; set; } + + /// + /// 联系电话 + /// + public string ContactPhone { get; set; } + + /// + /// 状态 + /// + public bool Status { get; set; } + + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Construction/Dto/Input/ConstructionUpdateInput.cs b/src/Znyc.Dispatching.Application/Construction/Dto/Input/ConstructionUpdateInput.cs new file mode 100644 index 0000000..5462746 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Construction/Dto/Input/ConstructionUpdateInput.cs @@ -0,0 +1,14 @@ +namespace ZNYC.Recruitment.Service.Dictionary +{ + /// + /// 修改 + /// + public class ConstructionUpdateInput : ConstructionAddInput + { + /// + /// 主键Id + /// + public long Id { get; set; } + + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Construction/Dto/Output/ConstructionOutput.cs b/src/Znyc.Dispatching.Application/Construction/Dto/Output/ConstructionOutput.cs new file mode 100644 index 0000000..305f659 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Construction/Dto/Output/ConstructionOutput.cs @@ -0,0 +1,56 @@ +using System.Collections.Generic; + +namespace Znyc.Dispatching.Application +{ + public class ConstructionBaiscList + { + public long Total { get; set; } + + public List ConstructionLetterOutputs { get; set; } + } + + + public class ConstructionLetterOutput + { + public string Char { get; set; } + + public List List { get; set; } + } + + public class ConstructionOutput + { + + /// + /// Id + /// + public long Id { get; set; } + + /// + /// 匹配索引列表,与车组人员共用一个列表,所以需增加UserId来进行匹配 + /// + public long UserId + { + get { return Id; } + set { Id = value; } + } + /// + /// 施工单位 + /// + public string ConstructionName { get; set; } + + /// + /// 联系人 + /// + public string ContactName { get; set; } + + /// + /// 联系电话 + /// + public string ContactPhone { get; set; } + + /// + /// 状态 + /// + public bool Status { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Construction/Services/ConstructionService.cs b/src/Znyc.Dispatching.Application/Construction/Services/ConstructionService.cs new file mode 100644 index 0000000..6594c9f --- /dev/null +++ b/src/Znyc.Dispatching.Application/Construction/Services/ConstructionService.cs @@ -0,0 +1,151 @@ +using Furion.DatabaseAccessor; +using Furion.DependencyInjection; +using Furion.DynamicApiController; +using Furion.FriendlyException; +using Mapster; +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Znyc.Dispatching.Core; +using Znyc.Dispatching.Core.Entitys; +using Znyc.Dispatching.Core.Extension; +using Znyc.Dispatching.Core.Helpers; +using ZNYC.Recruitment.Service.Dictionary; + +namespace Znyc.Dispatching.Application.Services +{ + /// + /// 施工单位 + /// + [ApiDescriptionSettings(Name = "construction", Order = 259)] + public class ConstructionService : IConstructionService, IDynamicApiController, ITransient + { + private readonly ICacheService _cacheService; + private readonly IRepository _repository; + private readonly IRepository _projectRepository; + + private readonly IUserManager _userManager; + private string[] INDEX_STRINGS = {"A", "B", "C", "D", "E", "F", "G", "H", "I", + "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", + "W", "X", "Y", "Z", "#"}; + public ConstructionService( + IRepository repository, + ICacheService cacheService, + IUserManager userManager, + IRepository projectRepository) + { + _repository = repository; + _cacheService = cacheService; + _userManager = userManager; + _projectRepository = projectRepository; + } + + /// + /// InsertAsync + /// + /// + /// + [HttpPost] + [Route("api/v1/construction")] + public async Task InsertAsync(ConstructionAddInput constructionAddInput) + { + var result = false; + if (constructionAddInput.IsNotNull()) + { + var isRepeatConstructionName = await _repository.AnyAsync(x => x.ConstructionName == constructionAddInput.ConstructionName && x.CompanyId == _userManager.CompanyId); + if (isRepeatConstructionName) + { + throw Oops.Bah($"施工单位重名,请修改"); + } + var construction = constructionAddInput.Adapt(); + construction.CompanyId = _userManager.CompanyId; + long id = (await _repository.InsertNowAsync(construction)).Entity.Id; + if (id > 0) + { + result = true; + } + } + return result; + } + + + /// + /// UpdateAsync + /// + /// + /// + [HttpPut] + [Route("api/v1/construction")] + public async Task UpdateAsync(ConstructionUpdateInput constructionUpdateInput) + { + var result = false; + if (constructionUpdateInput.IsNotNull()) + { + var construction = await _repository.FindAsync(constructionUpdateInput.Id); + if (construction.IsNotNull()) + { + var isRepeatConstructionName = await _repository.AnyAsync(x => x.ConstructionName == constructionUpdateInput.ConstructionName && x.Id != constructionUpdateInput.Id && x.CompanyId == _userManager.CompanyId); + if (isRepeatConstructionName) + { + throw Oops.Bah($"施工单位重名,请修改"); + } + construction.ConstructionName = constructionUpdateInput.ConstructionName; + construction.ContactName = constructionUpdateInput.ContactName; + construction.ContactPhone = constructionUpdateInput.ContactPhone; + construction.Status = constructionUpdateInput.Status; + long id = (await _repository.UpdateNowAsync(construction)).Entity.Id; + if (id > 0) + { + //同步工程列表中施工单位名称 + var projects = _projectRepository.Where(x => x.ConstructionId == id).ToList(); + if (projects.Count > 0) + { + foreach (var project in projects) + { + project.ConstructionName = construction.ConstructionName; + await _projectRepository.UpdateNowAsync(project); + await _cacheService.RemoveProjectAsync(project.Id); + } + } + await _cacheService.RemoveProjectListAsync(_userManager.CompanyId); + + result = true; + } + } + } + return result; + } + + + + /// + /// ListAsync + /// + /// + [HttpGet] + [Route("api/v1/constructions/search")] + public async Task ListAsync(string key, int status) + { + var constructions = _repository.Where(x => x.CompanyId == _userManager.CompanyId) + .Where(key.IsNotEmptyOrNull(), x => x.ConstructionName.Contains(key)) + .Where(status > 0, x => x.Status) + .ToList().Adapt>(); + var constructionOutputs = new List(); + foreach (var INDEX_STRING in INDEX_STRINGS) + { + constructionOutputs.Add(new ConstructionLetterOutput() + { + Char = INDEX_STRING, + List = constructions.Where(x => StringHelper.GetStringFirstSpell(x.ConstructionName) == INDEX_STRING).ToList() + }); + }; + var data = new ConstructionBaiscList() + { + ConstructionLetterOutputs = constructionOutputs, + Total = constructions.Count + }; + return data; + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Construction/Services/IConstructionService.cs b/src/Znyc.Dispatching.Application/Construction/Services/IConstructionService.cs new file mode 100644 index 0000000..780d092 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Construction/Services/IConstructionService.cs @@ -0,0 +1,31 @@ +using System.Threading.Tasks; +using ZNYC.Recruitment.Service.Dictionary; + +namespace Znyc.Dispatching.Application.Services +{ + public interface IConstructionService + { + + /// + /// InsertAsync + /// + /// + /// + public Task InsertAsync(ConstructionAddInput constructionAddInput); + + + /// + /// UpdateAsync + /// + /// + /// + public Task UpdateAsync(ConstructionUpdateInput constructionUpdateInput); + + + /// + /// + /// + /// + Task ListAsync(string key, int status); + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Dictionary/Dto/Input/DictionaryAddInput.cs b/src/Znyc.Dispatching.Application/Dictionary/Dto/Input/DictionaryAddInput.cs new file mode 100644 index 0000000..10bbd5c --- /dev/null +++ b/src/Znyc.Dispatching.Application/Dictionary/Dto/Input/DictionaryAddInput.cs @@ -0,0 +1,38 @@ +namespace ZNYC.Recruitment.Service.Dictionary +{ + /// + /// 添加 + /// + public class DictionaryAddInput + { + /// + /// 字典父级 + /// + public int ParentId { get; set; } + + /// + /// 字典名称 + /// + public string Name { get; set; } + + /// + /// 字典编码 + /// + public string Code { get; set; } + + /// + /// 字典值 + /// + public string Value { get; set; } + + /// + /// 描述 + /// + public string Description { get; set; } + + /// + /// 启用 + /// + public bool IsEnable { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Dictionary/Dto/Input/DictionaryUpdateInput.cs b/src/Znyc.Dispatching.Application/Dictionary/Dto/Input/DictionaryUpdateInput.cs new file mode 100644 index 0000000..931fd9d --- /dev/null +++ b/src/Znyc.Dispatching.Application/Dictionary/Dto/Input/DictionaryUpdateInput.cs @@ -0,0 +1,18 @@ +namespace ZNYC.Recruitment.Service.Dictionary +{ + /// + /// 修改 + /// + public class DictionaryUpdateInput : DictionaryAddInput + { + /// + /// 主键Id + /// + public int Id { get; set; } + + /// + /// 版本 + /// + public int Version { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Dictionary/Dto/Output/DictionaryOutput.cs b/src/Znyc.Dispatching.Application/Dictionary/Dto/Output/DictionaryOutput.cs new file mode 100644 index 0000000..ca9cf48 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Dictionary/Dto/Output/DictionaryOutput.cs @@ -0,0 +1,30 @@ +namespace Znyc.Dispatching.Application +{ + public class DictionaryOutput + { + /// + /// 主键Id + /// + public long Id { get; set; } + + /// + /// 字典编码 + /// + public string Code { get; set; } + + /// + /// 字典值 + /// + public string Value { get; set; } + + /// + /// 字典名称 + /// + public string Name { get; set; } + + /// + /// 排序 + /// + public int Sort { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Dictionary/Services/DictionaryService.cs b/src/Znyc.Dispatching.Application/Dictionary/Services/DictionaryService.cs new file mode 100644 index 0000000..8301aa5 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Dictionary/Services/DictionaryService.cs @@ -0,0 +1,52 @@ +using Furion.DatabaseAccessor; +using Furion.DependencyInjection; +using Furion.DynamicApiController; +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; +using System.Threading.Tasks; +using Znyc.Dispatching.Core.Entitys; + +namespace Znyc.Dispatching.Application.Services +{ + /// + /// 字典服务 + /// + [ApiDescriptionSettings(Name = "dictionary", Order = 200)] + public class DictionaryService : IDictionaryService, IDynamicApiController, ITransient + { + private readonly ICacheService _cacheService; + private readonly IRepository _repository; + + public DictionaryService( + IRepository repository, + ICacheService cacheService) + { + _repository = repository; + _cacheService = cacheService; + } + + /// + /// 根据Id获取字典 + /// + /// + /// + [HttpGet] + public async Task GetAsync(long id) + { + List dictionarys = await _cacheService.GetDictionaryListAsync(); + return dictionarys.Find(x => x.Id == id); + } + + /// + /// 根据code获取字典 + /// + /// + /// + [HttpGet] + public async Task> ListAsync(string code) + { + List dictionarys = await _cacheService.GetDictionaryListAsync(); + return dictionarys.FindAll(x => x.Code == code); + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Dictionary/Services/IDictionaryService.cs b/src/Znyc.Dispatching.Application/Dictionary/Services/IDictionaryService.cs new file mode 100644 index 0000000..5844d34 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Dictionary/Services/IDictionaryService.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Znyc.Dispatching.Application.Services +{ + public interface IDictionaryService + { + /// + /// 根据Id获取字典 + /// + /// + /// + Task GetAsync(long id); + + /// + /// 根据code获取字典 + /// + /// + /// + Task> ListAsync(string code); + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Document/Dto/Output/BucketTokenOutput.cs b/src/Znyc.Dispatching.Application/Document/Dto/Output/BucketTokenOutput.cs new file mode 100644 index 0000000..a7b7a08 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Document/Dto/Output/BucketTokenOutput.cs @@ -0,0 +1,24 @@ +namespace Znyc.Dispatching.Application +{ + public class BucketTokenOutput + { + public Credentials Credentials { get; set; } + + public string Expiration { get; set; } + + public int ExpiredTime { get; set; } + + public string RequestId { get; set; } + + public int StartTime { get; set; } + } + + public class Credentials + { + public string TmpSecretId { get; set; } + + public string TmpSecretKey { get; set; } + + public string Token { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Document/Services/DocumentService.cs b/src/Znyc.Dispatching.Application/Document/Services/DocumentService.cs new file mode 100644 index 0000000..e2f5917 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Document/Services/DocumentService.cs @@ -0,0 +1,58 @@ +using COSSTS; +using Furion.DependencyInjection; +using Furion.DynamicApiController; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using Znyc.Dispatching.Core; + +namespace Znyc.Dispatching.Application +{ + /// + /// Cos服务 + /// + [ApiDescriptionSettings(Name = "document", Order = 150, IgnoreApi = false)] + public class DocumentService : IDocumentService, IDynamicApiController, ITransient + { + private readonly UploadInfoOptions _uploadInfoOptions; + + public DocumentService( + IOptions options + ) + { + _uploadInfoOptions = options.Value; + } + + /// + /// 获取COS_Token + /// + /// + [HttpGet] + [AllowAnonymous] + [Route("api/v1/costoken")] + public BucketTokenOutput GetBucketTokenAsync() + { + Dictionary values = new Dictionary + { + ["allowActions"] = _uploadInfoOptions.allowActions, + ["bucket"] = _uploadInfoOptions.bucket, + ["region"] = _uploadInfoOptions.region, + ["allowPrefix"] = _uploadInfoOptions.allowPrefix, + ["allowActions"] = _uploadInfoOptions.allowActions, + ["durationSeconds"] = _uploadInfoOptions.durationSeconds, + ["secretId"] = _uploadInfoOptions.secretId, + ["secretKey"] = _uploadInfoOptions.secretKey + }; + Dictionary credential = STSClient.genCredential(values); + foreach (KeyValuePair kvp in credential) + { + Console.WriteLine("{0} = {1}", kvp.Key, kvp.Value); + } + + return JsonConvert.DeserializeObject(JsonConvert.SerializeObject(credential)); + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Document/Services/IDocumentService.cs b/src/Znyc.Dispatching.Application/Document/Services/IDocumentService.cs new file mode 100644 index 0000000..6be73ad --- /dev/null +++ b/src/Znyc.Dispatching.Application/Document/Services/IDocumentService.cs @@ -0,0 +1,11 @@ +namespace Znyc.Dispatching.Application +{ + public interface IDocumentService + { + /// + /// 获取COS_Token + /// + /// + BucketTokenOutput GetBucketTokenAsync(); + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Employee/Dto/Input/EmployeeAddInput.cs b/src/Znyc.Dispatching.Application/Employee/Dto/Input/EmployeeAddInput.cs new file mode 100644 index 0000000..34f0368 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Employee/Dto/Input/EmployeeAddInput.cs @@ -0,0 +1,33 @@ +using System.ComponentModel.DataAnnotations; + +namespace Znyc.Dispatching.Application +{ + public class EmployeeAddInput + { + /// + /// 员工名 + /// + [Required(ErrorMessage = "员工名不能为空!")] + public string EmployeeName { get; set; } + + /// + /// 手机号 + /// + [Required(ErrorMessage = "请输入手机号")] + [MaxLength(11, ErrorMessage = "手机号不能超过11位")] + [RegularExpression("^[1][3,4,5,6,7,8,9][0-9]{9}$", ErrorMessage = "请输入正确的手机号码")] + public string EmployeePhone { get; set; } + + /// + /// 角色 + /// + public int RoleId { get; set; } + + /// + /// 角色名称 + /// + [Required(ErrorMessage = "角色名称不能为空!")] + [MaxLength(20, ErrorMessage = "角色名称不得超过8个字")] + public string RoleName { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Employee/Dto/Input/EmployeeUpdateInput.cs b/src/Znyc.Dispatching.Application/Employee/Dto/Input/EmployeeUpdateInput.cs new file mode 100644 index 0000000..2080557 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Employee/Dto/Input/EmployeeUpdateInput.cs @@ -0,0 +1,17 @@ +namespace Znyc.Dispatching.Application +{ + public class EmployeeUpdateInput : EmployeeAddInput + { + public long Id { get; set; } + + /// + /// 状态 + /// + public int Status { get; set; } + + /// + /// 员工头像 + /// + public string AvatarUrl { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Employee/Dto/OutPut/EmployeeListOutput.cs b/src/Znyc.Dispatching.Application/Employee/Dto/OutPut/EmployeeListOutput.cs new file mode 100644 index 0000000..632d5dc --- /dev/null +++ b/src/Znyc.Dispatching.Application/Employee/Dto/OutPut/EmployeeListOutput.cs @@ -0,0 +1,58 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace Znyc.Dispatching.Application +{ + public class EmployeeListOutput + { + /// + /// + public long Id { get; set; } + + /// + /// 用户Id + /// + public long UserId { get; set; } + + + /// + /// + /// + public int RoleId { get; set; } + + /// + /// + /// + public string RoleName { get; set; } + + /// + /// 员工名 + /// + public string EmployeeName { get; set; } + + /// + /// 手机号 + /// + [MaxLength(11)] + public string EmployeePhone { get; set; } + + + public int Status { get; set; } + + } + + public class EmployeeListOutputs + { + public string Char { get; set; } + + public List List { get; set; } + + } + + public class EmployeeListPage + { + public List EmployeeList { get; set; } + + public long Total { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Employee/Dto/OutPut/EmployeeOutput.cs b/src/Znyc.Dispatching.Application/Employee/Dto/OutPut/EmployeeOutput.cs new file mode 100644 index 0000000..2e2ddf6 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Employee/Dto/OutPut/EmployeeOutput.cs @@ -0,0 +1,52 @@ +using System.ComponentModel.DataAnnotations; + +namespace Znyc.Dispatching.Application +{ + public class EmployeeOutput + { + /// + /// + public long Id { get; set; } + + /// + /// 用户Id + /// + public long UserId { get; set; } + + /// + /// 公司Id + /// + public long CompanyId { get; set; } + + /// + /// 员工名 + /// + public string EmployeeName { get; set; } + + /// + /// 手机号 + /// + [MaxLength(11)] + public string EmployeePhone { get; set; } + + /// + /// 角色 + /// + public long RoleId { get; set; } + + /// + /// 角色名称 + /// + public string RoleName { get; set; } + + /// + /// 状态 + /// + public int Status { get; set; } + + /// + /// 员工头像 + /// + public string AvatarUrl { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Employee/Dto/OutPut/NoticeOutput.cs b/src/Znyc.Dispatching.Application/Employee/Dto/OutPut/NoticeOutput.cs new file mode 100644 index 0000000..9e39a27 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Employee/Dto/OutPut/NoticeOutput.cs @@ -0,0 +1,7 @@ +namespace Znyc.Dispatching.Application +{ + public class NoticeOutput + { + public string OpenId { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Employee/Services/EmployeeService.cs b/src/Znyc.Dispatching.Application/Employee/Services/EmployeeService.cs new file mode 100644 index 0000000..c756bdf --- /dev/null +++ b/src/Znyc.Dispatching.Application/Employee/Services/EmployeeService.cs @@ -0,0 +1,305 @@ +using Furion.DatabaseAccessor; +using Furion.DependencyInjection; +using Furion.DynamicApiController; +using Furion.FriendlyException; +using Mapster; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Options; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; +using Znyc.Dispatching.Common.Extensions; +using Znyc.Dispatching.Core; +using Znyc.Dispatching.Core.Entitys; +using Znyc.Dispatching.Core.Extension; +using Znyc.Dispatching.Core.Helpers; + +namespace Znyc.Dispatching.Application +{ + /// + /// 员工服务 + /// + [ApiDescriptionSettings(Name = "employee", Order = 20)] + public class EmployeeService : IEmployeeService, IDynamicApiController, ITransient + { + private string[] INDEX_STRINGS = {"A", "B", "C", "D", "E", "F", "G", "H", "I", + "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", + "W", "X", "Y", "Z", "#"}; + + private string[] ROLEINDEX_STRINGS = { "C", "D", "J", "G", "W", "Y", "X", "#" }; + + + private readonly ICacheService _cacheService; + private readonly IRepository _repository; + private readonly IRepository _userRepository; + private readonly IRepository _userRoleRepository; + private readonly IRepository _companyRepository; + private readonly IUserManager _userManager; + private readonly IRepository _vehiclePersonRepository; + private readonly IRepository _projectPersonRepository; + + + public EmployeeService( + IRepository repository, + IRepository userRepository, + IRepository companyRepository, + IUserManager userManager, + ICacheService cacheService, + IRepository userRoleRepository, + IOptions smsProviderOptions, + IRepository vehiclePersonRepository, + IRepository projectPersonRepository + ) + { + _repository = repository; + _userRepository = userRepository; + _companyRepository = companyRepository; + _userManager = userManager; + _cacheService = cacheService; + _userRoleRepository = userRoleRepository; + _vehiclePersonRepository = vehiclePersonRepository; + _projectPersonRepository = projectPersonRepository; + } + + /// + /// 员工信息 + /// + /// + /// + /// + /// 0全部,停用-1,正常1 + /// + /// + /// + [HttpGet] + [Route("api/v1/employees/search")] + public async Task PageAsync([Required] int currentPage, [Required] int pageSize, [FromQuery] int status, [Required] int roleId, [FromQuery] string orderby, [FromQuery] string key = "") + { + + var employees = (await _cacheService.GetEmployeeListAsync(_userManager.CompanyId)) + .WhereIf(status != 0, x => x.Status == status) + .WhereIf(roleId > 0, x => x.RoleId == roleId) + .WhereIf(new long[] { (long)RoleStatusEnum.Scheduling, (long)RoleStatusEnum.CarCaptain }.Contains(_userManager.RoleId), + x => new long[] { (long)RoleStatusEnum.CrewMembers, (long)RoleStatusEnum.Outside, + (long)RoleStatusEnum.PartTimeSalesman,(long)RoleStatusEnum.Salesman }.Contains(x.RoleId)) + .WhereIf(key.IsNotEmptyOrNull(), x => x.EmployeeName.Contains(key) || x.EmployeePhone.Contains(key)) + .Adapt>(); + var employeeOutputs = new List(); + + if (orderby == "role") + { + employees = employees.OrderBy(x => x.RoleName).ToList(); + foreach (var INDEX_STRING in ROLEINDEX_STRINGS) + { + employeeOutputs.Add(new EmployeeListOutputs() + { + Char = INDEX_STRING, + List = employees.Where(x => StringHelper.GetStringFirstSpell(x.RoleName) == INDEX_STRING).ToList() + }); + }; + } + else + { + employees = employees.OrderBy(x => x.EmployeeName).ToList(); + + foreach (var INDEX_STRING in INDEX_STRINGS) + { + employeeOutputs.Add(new EmployeeListOutputs() + { + Char = INDEX_STRING, + List = employees.Where(x => StringHelper.GetStringFirstSpell(x.EmployeeName) == INDEX_STRING).ToList() + }); + }; + } + var data = new EmployeeListPage() + { + EmployeeList = employeeOutputs, + Total = employees.Count + }; + return data; + } + + /// + /// 根据id获取员工资料 + /// + /// + [HttpGet] + [Route("api/v1/employee/{id}")] + public async Task GetByIdAsync(long id) + { + return await _cacheService.GetEmployeeAsync(id); + } + + /// + /// 添加员工信息 + /// + /// + /// + [HttpPost] + [UnitOfWork] + [NonAction] + //[Route("api/v1/employee")] + public async Task AddAsync(EmployeeAddInput employeeAdd) + { + var user = await _userRepository.Where(x => x.Phone == employeeAdd.EmployeePhone).FirstOrDefaultAsync(); + if (user.IsNotNull()) + { + return user.Id; + } + + var company = await _companyRepository.FindOrDefaultAsync(_userManager.CompanyId); + if (company.IsNotNull()) + { + //用户 + var userEntity = await _userRepository.InsertNowAsync(new User + { + OpenId = "", + UserName = employeeAdd.EmployeeName, + AvatarUrl = CommonConst.DEFAULT_AVATARURL_ALL, + Phone = employeeAdd.EmployeePhone, + Status = CommonStatusEnum.ENABLE + }); + //员工 + var employee = await _repository.InsertNowAsync(new Employee + { + UserId = userEntity.Entity.Id, + AvatarUrl = CommonConst.DEFAULT_AVATARURL, + EmployeeName = employeeAdd.EmployeeName, + EmployeePhone = employeeAdd.EmployeePhone, + CompanyId = company.Id, + RoleId = employeeAdd.RoleId, + RoleName = employeeAdd.RoleName, + IsDefault = true, + Status = (int)CommonStatusEnum.ENABLE + }); + //用户角色 + await _userRoleRepository.InsertNowAsync(new UserRole + { + UserId = userEntity.Entity.Id, + RoleId = employeeAdd.RoleId, + }); + //清除员工列表Cache + await _cacheService.RemoveEmployeeListAsync(_userManager.CompanyId); + return employee.Entity.UserId; + } + else + { + return 0; + } + } + + /// + /// 编辑员工信息 + /// + /// + [HttpPut] + [UnitOfWork] + [Route("api/v1/employee")] + public async Task UpdateAsync(EmployeeUpdateInput input) + { + var employee = await _repository.FindOrDefaultAsync(input.Id); + //权限变动,清除对应缓存 + if (!employee.RoleId.Equals(input.RoleId)) + { + await _cacheService.RemoveTokenAsync(employee.UserId); + } + employee = input.Adapt(employee); + employee.AvatarUrl = CommonConst.Prefix_AvataUrl + employee.AvatarUrl[(employee.AvatarUrl.LastIndexOf("/") + 1)..]; + var result = await _repository.UpdateNowAsync(employee); + if (result.IsNull()) + { + throw Oops.Oh("更新员工信息失败"); + } + //同步更新user + var user = await _userRepository.FirstOrDefaultAsync(x => x.Id == employee.UserId); + if (user.IsNotNull()) + { + user.UserName = input.EmployeeName; + user.Phone = input.EmployeePhone; + user.AvatarUrl = employee.AvatarUrl; + await _userRepository.UpdateNowAsync(user); + await _cacheService.RemoveUserAsync(user.Id); + } + + //更新当前用户下所有角色姓名 + var employees = await _repository.Where(x => x.UserId == user.Id).ToListAsync(); + foreach (var employee2 in employees) + { + if (employee.IsNotNull()) + { + employee.EmployeePhone = input.EmployeePhone; + employee.EmployeeName = input.EmployeeName; + employee.AvatarUrl = CommonConst.Prefix_AvataUrl + input.AvatarUrl[(input.AvatarUrl.LastIndexOf("/") + 1)..]; + await _repository.UpdateNowAsync(employee2); + await _cacheService.RemoveEmployeeAsync(employee2.Id); + await _cacheService.RemoveEmployeeListAsync(employee2.CompanyId); + } + } + + + //清除员工Cache + await _cacheService.RemoveEmployeeAsync(employee.Id); + //清除用户Cache + await _cacheService.RemoveUserAsync(employee.UserId); + //清除员工列表Cache + await _cacheService.RemoveEmployeeListAsync(_userManager.CompanyId); + //同步车组人员表 + var vehiclePersons = await _vehiclePersonRepository.Where(x => x.IsDeleted == false && x.UserId == user.Id).ToListAsync(); + foreach (var item in vehiclePersons) + { + //解除车组人员关联 + if (input.Status == (int)CommonStatusEnum.DISABLE) + { + item.IsDeleted = true; + } + item.UserName = input.EmployeeName; + item.UserPhone = input.EmployeePhone; + await _vehiclePersonRepository.UpdateNowAsync(item); + //删除车组人员Cache + await _cacheService.RemoveVehiclePersonAsync(item.VehicleId); + } + //同步工程联系人表 + var projectPersons = await _projectPersonRepository.Where(x => x.IsDeleted == false && x.ProjectPersonId == user.Id).ToListAsync(); + foreach (var item in projectPersons) + { + item.ProjectPersonName = input.EmployeeName; + item.ProjectPersonPhone = input.EmployeePhone; + await _projectPersonRepository.UpdateNowAsync(item); + //清除工程信息Cache + await _cacheService.RemoveProjectAsync(item.ProjectId); + //清除工程列表信息Cache + await _cacheService.RemoveProjectListAsync(_userManager.CompanyId); + } + //同步用户角色表 + //var userRole = await _userRoleRepository.FirstOrDefaultAsync(x => x.UserId == employee.UserId); + //userRole.RoleId = employee.RoleId; + //await _userRoleRepository.UpdateNowAsync(userRole); + + } + + + /// + /// 软删除员工信息 + /// + /// + /// + [HttpDelete] + [Route("api/v1/employee")] + + public async Task DeleteByIdAsync(long id) + { + Employee employee = await _repository + .FirstOrDefaultAsync(x => x.Id == id && x.CompanyId == _userManager.CompanyId); + employee.IsDeleted = true; + await _repository.UpdateNowAsync(employee); + await _cacheService.RemoveCacheByUserIdAsync(employee.UserId); + await _cacheService.RemoveEmployeeAsync(employee.Id); + await _cacheService.RemoveEmployeeListAsync(_userManager.CompanyId); + await _cacheService.RemoveUserAsync(employee.UserId); + } + + + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Employee/Services/IEmployeeService.cs b/src/Znyc.Dispatching.Application/Employee/Services/IEmployeeService.cs new file mode 100644 index 0000000..e4df847 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Employee/Services/IEmployeeService.cs @@ -0,0 +1,50 @@ +using System.Threading.Tasks; + +namespace Znyc.Dispatching.Application +{ + public interface IEmployeeService + { + /// + /// 员工信息 + /// + /// + /// + /// + /// + /// + /// + /// + Task PageAsync(int currentPage, int pageSize, int status, int roleId, string key, string orderby); + + + /// + /// 根据id获取员工资料 + /// + /// + Task GetByIdAsync(long id); + + /// + /// 添加员工信息 + /// + /// + /// + Task AddAsync(EmployeeAddInput input); + + /// + /// 编辑员工信息 + /// + /// + Task UpdateAsync(EmployeeUpdateInput input); + + + /// + /// 软删除员工信息 + /// + /// + /// + Task DeleteByIdAsync(long id); + + + + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Job/Dto/Input/JobInput.cs b/src/Znyc.Dispatching.Application/Job/Dto/Input/JobInput.cs new file mode 100644 index 0000000..910ada6 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Job/Dto/Input/JobInput.cs @@ -0,0 +1,83 @@ +using Furion.DataValidation; +using Furion.TaskScheduler; +using System.ComponentModel.DataAnnotations; +using Znyc.Dispatching.Core; + +namespace Dilon.Core.Service +{ + /// + /// 任务调度参数 + /// + public class JobInput : PageInputBase + { + /// + /// 任务名称 + /// + public string JobName { get; set; } + + /// + /// 执行间隔时间(单位秒) + /// + /// 5 + public int Interval { get; set; } + + /// + /// Cron表达式 + /// + public string Cron { get; set; } + + /// + /// 定时器类型 + /// + public SpareTimeTypes TimerType { get; set; } + + /// + /// 执行次数 + /// + public int? RunNumber { get; set; } + + /// + /// 请求url + /// + public string RequestUrl { get; set; } + + /// + /// 请求参数(Post,Put请求用) + /// + public string RequestParameters { get; set; } + + /// + /// Headers(可以包含如:Authorization授权认证) + /// 格式:{"Authorization":"userpassword.."} + /// + public string Headers { get; set; } + + /// + /// 请求类型 + /// + public RequestTypeEnum RequestType { get; set; } + + /// + /// 备注 + /// + public string Remark { get; set; } + } + + public class DeleteJobInput : JobInput + { + /// + /// 任务Id + /// + [Required(ErrorMessage = "任务Id不能为空")] + [DataValidation(ValidationTypes.Numeric)] + public long Id { get; set; } + } + + public class UpdateJobInput : DeleteJobInput + { + } + + public class QueryJobInput : DeleteJobInput + { + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Job/Dto/Output/JobOutput.cs b/src/Znyc.Dispatching.Application/Job/Dto/Output/JobOutput.cs new file mode 100644 index 0000000..c83a32a --- /dev/null +++ b/src/Znyc.Dispatching.Application/Job/Dto/Output/JobOutput.cs @@ -0,0 +1,78 @@ +using Furion.TaskScheduler; +using Znyc.Dispatching.Core; + +namespace Dilon.Core.Service +{ + /// + /// 任务信息---任务详情 + /// + public class JobOutput + { + /// + /// Id + /// + public long Id { get; set; } + + /// + /// 任务名称 + /// + public string JobName { get; set; } + + /// + /// 执行间隔时间(单位秒) + /// + public int Interval { get; set; } + + /// + /// Cron表达式 + /// + public string Cron { get; set; } + + /// + /// 定时器类型 + /// + public SpareTimeTypes TimerType { get; set; } + + /// + /// 执行次数 + /// + public long? RunNumber { get; set; } + + /// + /// 请求url + /// + public string RequestUrl { get; set; } + + /// + /// 请求参数(Post,Put请求用) + /// + public string RequestParameters { get; set; } + + /// + /// Headers(可以包含如:Authorization授权认证) + /// 格式:{"Authorization":"userpassword.."} + /// + public string Headers { get; set; } + + /// + /// 请求类型 + /// + /// 2 + public RequestTypeEnum RequestType { get; set; } + + /// + /// 备注 + /// + public string Remark { get; set; } + + /// + /// 定时器状态 + /// + public SpareTimeStatus TimerStatus { get; set; } = SpareTimeStatus.Stopped; + + /// + /// 异常信息 + /// + public string Exception { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Job/IJobService.cs b/src/Znyc.Dispatching.Application/Job/IJobService.cs new file mode 100644 index 0000000..225b82b --- /dev/null +++ b/src/Znyc.Dispatching.Application/Job/IJobService.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; + +namespace Dilon.Core.Service +{ + public interface IJobService + { + + Task SyncOrderStatusAsync(); + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Job/JobService.cs b/src/Znyc.Dispatching.Application/Job/JobService.cs new file mode 100644 index 0000000..bc45e24 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Job/JobService.cs @@ -0,0 +1,277 @@ +using Furion.DatabaseAccessor; +using Furion.DependencyInjection; +using Furion.DynamicApiController; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Options; +using Newtonsoft.Json; +using Senparc.Weixin.MP.AdvancedAPIs; +using Senparc.Weixin.MP.AdvancedAPIs.TemplateMessage; +using System; +using System.Linq; +using System.Threading.Tasks; +using Znyc.Dispatching.Application; +using Znyc.Dispatching.Core; +using Znyc.Dispatching.Core.Entitys; +using Znyc.Dispatching.Core.Extension; +using Znyc.Dispatching.Core.Helpers; +using Znyc.Dispatching.MongoDb.Repository.Repositorys; +using Znyc.Dispatching.WeChat.Core.CommonService.TemplateMessage; + +namespace Dilon.Core.Service +{ + /// + /// 任务调度服务 + /// + [ApiDescriptionSettings(Name = "job", Order = 40)] + public class JobService : IJobService, IDynamicApiController, IScoped + { + private readonly IRepository _roleRepository; + private readonly IVehicleService _vehicleService; + private readonly IRepository _menuRepository; + private readonly IRepository _roleMenuRepository; + private readonly IRepository _orderRepository; + private readonly IGpsRealTimeRepository _gpsRealTimeRepository; + private readonly ICacheService _cacheService; + private readonly IRepository _employeeRepository; + private readonly IRepository _orderVehicleRepository; + private readonly IRepository _wxUserRelationRepository; + private readonly WeixinSettingOptions _weixinSettingOptions; + private readonly IRepository _yardRepository; + private readonly IRepository _companyRepository; + public JobService( + IRepository roleRepository, + IVehicleService vehicleService, + ICacheService cacheService, + IRepository menuRepository, + IRepository orderRepository, + IRepository roleMenuRepository, + IGpsRealTimeRepository gpsRealTimeRepository, + IRepository employeeRepository, + IRepository orderVehicleRepository, + IRepository wxUserRelationRepository, + IOptions weixinSettingOptions, + IRepository yardRepository, + IRepository companyRepository) + { + _roleRepository = roleRepository; + _vehicleService = vehicleService; + _menuRepository = menuRepository; + _cacheService = cacheService; + _orderRepository = orderRepository; + _roleMenuRepository = roleMenuRepository; + _gpsRealTimeRepository = gpsRealTimeRepository; + _employeeRepository = employeeRepository; + _orderVehicleRepository = orderVehicleRepository; + _wxUserRelationRepository = wxUserRelationRepository; + _weixinSettingOptions = weixinSettingOptions.Value; + _yardRepository = yardRepository; + _companyRepository = companyRepository; + } + + #region Order + + [HttpGet] + [AllowAnonymous] + public async Task SyncOrderStatusAsync() + { + var orders = _orderRepository.Where(x => x.IsDeleted == false && new int[] { (int)OrderStatus.Receive, (int)OrderStatus.Travel, (int)OrderStatus.Appear, (int)OrderStatus.Sign, (int)OrderStatus.Leave }.Contains(x.Status)).ToList(); + var vehicleGpss = await _gpsRealTimeRepository.GetGpsRealTimeListByVehicleId(orders.Select(x => x.VehicleId).ToList()); + var arriveOrders = orders.Where(x => x.Status == (int)OrderStatus.Receive && x.Latitude > 0 && x.Longitude > 0).ToList(); + + foreach (var arriveOrder in arriveOrders) + { + var vehicleGps = vehicleGpss.Where(x => x.VehicleId == arriveOrder.VehicleId).FirstOrDefault(); + if (vehicleGps.IsNotNull()) + { + //已出发 + var vehicleTravelGps = await _cacheService.GetVehicleGpsAsync(arriveOrder.Id); + if (vehicleTravelGps.IsNotNull()) + { + var distance = MapHelper.GetDistance((double)vehicleGps.Latitude, (double)vehicleGps.Longitude, (double)vehicleTravelGps.Latitude, (double)vehicleTravelGps.Longitude); + if (distance > 3000) + { + arriveOrder.Status = (int)OrderStatus.Travel; + arriveOrder.TravelDate = DateTime.Now; + var orderId = (await _orderRepository.UpdateNowAsync(arriveOrder)).Entity.Id; + if (orderId > 0) + { + var orderContent = arriveOrder.OrderContent.Length > 45 ? HtmlHelper.ReplaceHtmlMark(arriveOrder.OrderContent.Substring(0, 45)) + "..." : HtmlHelper.ReplaceHtmlMark(arriveOrder.OrderContent) + "..."; + + //管理员 + var adminUserIds = await _employeeRepository.DetachedEntities.Where(x => x.CompanyId == arriveOrder.CompanyId && x.RoleId == (int)RoleStatusEnum.Administrator && x.Status == 1 && !x.IsDeleted).Select(x => x.UserId).ToListAsync(); + //派单人 + adminUserIds.Add(arriveOrder.AssignUserId); + //获取订单所有车组人员信息 + var orderVehicles = await _orderVehicleRepository.DetachedEntities.Where(x => x.OrderId == arriveOrder.Id).ToListAsync(); + var orderVehicleDriverName = orderVehicles.FirstOrDefault(x => x.IsDriver).UserName; + var orderVehicleCrewsName = string.Join(",", orderVehicles.Where(x => !x.IsDriver).Select(x => x.UserName).ToList()); + foreach (var adminUserId in adminUserIds) + { + var wxAdminUser = _wxUserRelationRepository.Where(x => x.UserId == adminUserId).FirstOrDefault(); + if (wxAdminUser.IsNotNull() && wxAdminUser.WxOfficialOpenId.IsNotNull()) + { + var data = new WeixinTemplate_AssignOrder("已出发提醒", arriveOrder.ProjectName, "出车任务", orderContent, arriveOrder.ArriveDate.ToString("yyyy -MM-dd HH:mm:ss"), $"[{arriveOrder.VehicleCode}]号车,{orderVehicleDriverName},{orderVehicleCrewsName}已出发"); + TemplateModel_MiniProgram miniProgram = new TemplateModel_MiniProgram(); + miniProgram.appid = _weixinSettingOptions.WxOpenAppId; + miniProgram.pagepath = $"pages/taskInfo/taskInfo?id={arriveOrder.Id}&isDriver=false&type=2"; + var result = await TemplateApi.SendTemplateMessageAsync(_weixinSettingOptions.WeixinAppId, wxAdminUser.WxOfficialOpenId, data.TemplateId, "pages/index/index", data, miniProgram); + Console.WriteLine($"{miniProgram.pagepath}"); + Console.WriteLine($"已出发提醒{JsonConvert.SerializeObject(result)}"); + } + } + } + await _cacheService.RemoveVehicleGpsAsync(arriveOrder.Id); + } + } + + + } + } + + //已到达 + var appearOrders = orders.Where(x => x.Status == (int)OrderStatus.Travel && x.Latitude > 0 && x.Longitude > 0).ToList(); + foreach (var appearOrder in appearOrders) + { + var vehicleGps = vehicleGpss.Where(x => x.VehicleId == appearOrder.VehicleId).FirstOrDefault(); + if (vehicleGps.IsNotNull()) + { + var distance = MapHelper.GetDistance((double)vehicleGps.Latitude, (double)vehicleGps.Longitude, (double)appearOrder.Latitude, (double)appearOrder.Longitude); + if (distance < 200) + { + appearOrder.Status = (int)OrderStatus.Appear; + appearOrder.AppearDate = DateTime.Now; + var orderId = (await _orderRepository.UpdateNowAsync(appearOrder)).Entity.Id; + if (orderId > 0) + { + var orderContent = appearOrder.OrderContent.Length > 45 ? HtmlHelper.ReplaceHtmlMark(appearOrder.OrderContent.Substring(0, 45)) + "..." : HtmlHelper.ReplaceHtmlMark(appearOrder.OrderContent) + "..."; + + //管理员 + var adminUserIds = await _employeeRepository.DetachedEntities.Where(x => x.CompanyId == appearOrder.CompanyId && x.RoleId == (int)RoleStatusEnum.Administrator && x.Status == 1 && !x.IsDeleted).Select(x => x.UserId).ToListAsync(); + //派单人 + adminUserIds.Add(appearOrder.AssignUserId); + //获取订单所有车组人员信息 + var orderVehicles = await _orderVehicleRepository.DetachedEntities.Where(x => x.OrderId == appearOrder.Id).ToListAsync(); + var orderVehicleDriverName = orderVehicles.FirstOrDefault(x => x.IsDriver).UserName; + var orderVehicleCrewsName = string.Join(",", orderVehicles.Where(x => !x.IsDriver).Select(x => x.UserName).ToList()); + foreach (var adminUserId in adminUserIds) + { + var wxAdminUser = _wxUserRelationRepository.Where(x => x.UserId == adminUserId).FirstOrDefault(); + if (wxAdminUser.IsNotNull() && wxAdminUser.WxOfficialOpenId.IsNotNull()) + { + var data = new WeixinTemplate_AssignOrder("已到达提醒", appearOrder.ProjectName, "出车任务", orderContent, appearOrder.ArriveDate.ToString("yyyy -MM-dd HH:mm:ss"), $"[{appearOrder.VehicleCode}]号车,{orderVehicleDriverName},{orderVehicleCrewsName}已到达施工地点"); + TemplateModel_MiniProgram miniProgram = new TemplateModel_MiniProgram(); + miniProgram.appid = _weixinSettingOptions.WxOpenAppId; + miniProgram.pagepath = $"pages/taskInfo/taskInfo?id={appearOrder.Id}&isDriver=false&type=2"; + var result = await TemplateApi.SendTemplateMessageAsync(_weixinSettingOptions.WeixinAppId, wxAdminUser.WxOfficialOpenId, data.TemplateId, "pages/index/index", data, miniProgram); + Console.WriteLine($"{miniProgram.pagepath}"); + Console.WriteLine($"已到达提醒{JsonConvert.SerializeObject(result)}"); + } + } + } + } + } + } + + //已离开 + var signeOrders = orders.Where(x => x.Status == (int)OrderStatus.Sign && x.Latitude > 0 && x.Longitude > 0).ToList(); + foreach (var signeOrder in signeOrders) + { + var vehicleGps = vehicleGpss.Where(x => x.VehicleId == signeOrder.VehicleId).FirstOrDefault(); + if (vehicleGps.IsNotNull()) + { + var distance = MapHelper.GetDistance((double)vehicleGps.Latitude, (double)vehicleGps.Longitude, (double)signeOrder.Latitude, (double)signeOrder.Longitude); + if (distance > 200) + { + signeOrder.Status = (int)OrderStatus.Leave; + signeOrder.LeaveDate = DateTime.Now; + var orderId = (await _orderRepository.UpdateNowAsync(signeOrder)).Entity.Id; + if (orderId > 0) + { + var orderContent = signeOrder.OrderContent.Length > 45 ? HtmlHelper.ReplaceHtmlMark(signeOrder.OrderContent.Substring(0, 45)) + "..." : HtmlHelper.ReplaceHtmlMark(signeOrder.OrderContent) + "..."; + + //管理员 + var adminUserIds = await _employeeRepository.DetachedEntities.Where(x => x.CompanyId == signeOrder.CompanyId && x.RoleId == (int)RoleStatusEnum.Administrator && x.Status == 1 && !x.IsDeleted).Select(x => x.UserId).ToListAsync(); + //派单人 + adminUserIds.Add(signeOrder.AssignUserId); + //获取订单所有车组人员信息 + var orderVehicles = await _orderVehicleRepository.DetachedEntities.Where(x => x.OrderId == signeOrder.Id).ToListAsync(); + var orderVehicleDriverName = orderVehicles.FirstOrDefault(x => x.IsDriver).UserName; + var orderVehicleCrewsName = string.Join(",", orderVehicles.Where(x => !x.IsDriver).Select(x => x.UserName).ToList()); + foreach (var adminUserId in adminUserIds) + { + var wxAdminUser = _wxUserRelationRepository.Where(x => x.UserId == adminUserId).FirstOrDefault(); + if (wxAdminUser.IsNotNull() && wxAdminUser.WxOfficialOpenId.IsNotNull()) + { + var data = new WeixinTemplate_AssignOrder("完工返回提醒", signeOrder.ProjectName, "出车任务", orderContent, signeOrder.ArriveDate.ToString("yyyy -MM-dd HH:mm:ss"), $"[{signeOrder.VehicleCode}]号车,{orderVehicleDriverName},{orderVehicleCrewsName}已完工返回"); + TemplateModel_MiniProgram miniProgram = new TemplateModel_MiniProgram(); + miniProgram.appid = _weixinSettingOptions.WxOpenAppId; + miniProgram.pagepath = $"pages/taskInfo/taskInfo?id={signeOrder.Id}&isDriver=false&type=2"; + var result = await TemplateApi.SendTemplateMessageAsync(_weixinSettingOptions.WeixinAppId, wxAdminUser.WxOfficialOpenId, data.TemplateId, "pages/index/index", data, miniProgram); + Console.WriteLine($"{miniProgram.pagepath}"); + Console.WriteLine($"已到达提醒{JsonConvert.SerializeObject(result)}"); + } + } + } + } + } + } + + + //已到家 + var homeOrders = orders.Where(x => x.Status == (int)OrderStatus.Leave && x.Latitude > 0 && x.Longitude > 0).ToList(); + foreach (var homeOrder in homeOrders) + { + var vehicleGps = vehicleGpss.Where(x => x.VehicleId == homeOrder.VehicleId).FirstOrDefault(); + + //查询所有车场位置 + var yards = await _yardRepository.DetachedEntities.Where(x => x.CompanyId == homeOrder.CompanyId).ToListAsync(); + foreach (var yard in yards) + { + if (vehicleGps.IsNotNull()) + { + var distance = MapHelper.GetDistance((double)vehicleGps.Latitude, (double)vehicleGps.Longitude, (double)yard.Latitude, (double)yard.Longitude); + if (distance < 200) + { + + homeOrder.Status = (int)OrderStatus.ArriveHome; + homeOrder.ArriveHomeDate = DateTime.Now; + var orderId = (await _orderRepository.UpdateNowAsync(homeOrder)).Entity.Id; + if (orderId > 0) + { + + var orderContent = homeOrder.OrderContent.Length > 45 ? HtmlHelper.ReplaceHtmlMark(homeOrder.OrderContent.Substring(0, 45)) + "..." : HtmlHelper.ReplaceHtmlMark(homeOrder.OrderContent) + "..."; + //管理员 + var adminUserIds = await _employeeRepository.DetachedEntities.Where(x => x.CompanyId == homeOrder.CompanyId && x.RoleId == (int)RoleStatusEnum.Administrator && x.Status == 1 && !x.IsDeleted).Select(x => x.UserId).ToListAsync(); + //派单人 + adminUserIds.Add(homeOrder.AssignUserId); + //获取订单所有车组人员信息 + var orderVehicles = await _orderVehicleRepository.DetachedEntities.Where(x => x.OrderId == homeOrder.Id).ToListAsync(); + var orderVehicleDriverName = orderVehicles.FirstOrDefault(x => x.IsDriver).UserName; + var orderVehicleCrewsName = string.Join(",", orderVehicles.Where(x => !x.IsDriver).Select(x => x.UserName).ToList()); + foreach (var adminUserId in adminUserIds) + { + var wxAdminUser = _wxUserRelationRepository.Where(x => x.UserId == adminUserId).FirstOrDefault(); + if (wxAdminUser.IsNotNull() && wxAdminUser.WxOfficialOpenId.IsNotNull()) + { + var data = new WeixinTemplate_AssignOrder("完工到家提醒", homeOrder.ProjectName, "出车任务", orderContent, homeOrder.ArriveDate.ToString("yyyy -MM-dd HH:mm:ss"), $"[{homeOrder.VehicleCode}]号车,{orderVehicleDriverName},{orderVehicleCrewsName}已完工返回"); + TemplateModel_MiniProgram miniProgram = new TemplateModel_MiniProgram(); + miniProgram.appid = _weixinSettingOptions.WxOpenAppId; + miniProgram.pagepath = $"pages/taskInfo/taskInfo?id={homeOrder.Id}&isDriver=false&type=2"; + var result = await TemplateApi.SendTemplateMessageAsync(_weixinSettingOptions.WeixinAppId, wxAdminUser.WxOfficialOpenId, data.TemplateId, "pages/index/index", data, miniProgram); + Console.WriteLine($"{miniProgram.pagepath}"); + Console.WriteLine($"完工到家提醒{JsonConvert.SerializeObject(result)}"); + } + } + } + + } + } + + } + } + } + #endregion + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/LogAudit/Services/ILogAuditService.cs b/src/Znyc.Dispatching.Application/LogAudit/Services/ILogAuditService.cs new file mode 100644 index 0000000..a0ee123 --- /dev/null +++ b/src/Znyc.Dispatching.Application/LogAudit/Services/ILogAuditService.cs @@ -0,0 +1,6 @@ +namespace Znyc.Dispatching.Application +{ + public interface ILogAuditService + { + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/LogAudit/Services/LogAuditService.cs b/src/Znyc.Dispatching.Application/LogAudit/Services/LogAuditService.cs new file mode 100644 index 0000000..f51884c --- /dev/null +++ b/src/Znyc.Dispatching.Application/LogAudit/Services/LogAuditService.cs @@ -0,0 +1,14 @@ +using Furion.DependencyInjection; +using Furion.DynamicApiController; +using Microsoft.AspNetCore.Mvc; + +namespace Znyc.Dispatching.Application +{ + /// + /// 审计日志服务 + /// + [ApiDescriptionSettings(Name = "audit", Order = 60, IgnoreApi = true)] + public class LogAuditService : ILogAuditService, IDynamicApiController, ITransient + { + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/LogEx/Services/ILogExService.cs b/src/Znyc.Dispatching.Application/LogEx/Services/ILogExService.cs new file mode 100644 index 0000000..6b8efee --- /dev/null +++ b/src/Znyc.Dispatching.Application/LogEx/Services/ILogExService.cs @@ -0,0 +1,6 @@ +namespace Znyc.Dispatching.Application +{ + public interface ILogExService + { + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/LogEx/Services/LogExService.cs b/src/Znyc.Dispatching.Application/LogEx/Services/LogExService.cs new file mode 100644 index 0000000..aa56e80 --- /dev/null +++ b/src/Znyc.Dispatching.Application/LogEx/Services/LogExService.cs @@ -0,0 +1,22 @@ +using Furion.DatabaseAccessor; +using Furion.DependencyInjection; +using Furion.DynamicApiController; +using Microsoft.AspNetCore.Mvc; +using Znyc.Dispatching.Core.Entitys; + +namespace Znyc.Dispatching.Application +{ + /// + /// 异常日志服务 + /// + [ApiDescriptionSettings(Name = "ex", Order = 70, IgnoreApi = true)] + public class LogExService : ILogExService, IDynamicApiController, ITransient + { + private readonly IRepository _repository; + + public LogExService(IRepository repository) + { + _repository = repository; + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/LogOp/Services/ILogOpService.cs b/src/Znyc.Dispatching.Application/LogOp/Services/ILogOpService.cs new file mode 100644 index 0000000..39b5200 --- /dev/null +++ b/src/Znyc.Dispatching.Application/LogOp/Services/ILogOpService.cs @@ -0,0 +1,6 @@ +namespace Znyc.Dispatching.Application +{ + public interface ILogOpService + { + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/LogOp/Services/LogOpService.cs b/src/Znyc.Dispatching.Application/LogOp/Services/LogOpService.cs new file mode 100644 index 0000000..fd781f0 --- /dev/null +++ b/src/Znyc.Dispatching.Application/LogOp/Services/LogOpService.cs @@ -0,0 +1,14 @@ +using Furion.DependencyInjection; +using Furion.DynamicApiController; +using Microsoft.AspNetCore.Mvc; + +namespace Znyc.Dispatching.Application +{ + /// + /// 操作日志服务 + /// + [ApiDescriptionSettings(Name = "op", Order = 80, IgnoreApi = true)] + public class LogOpService : ILogOpService, IDynamicApiController, ITransient + { + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Login/Dto/Input/LoginInput.cs b/src/Znyc.Dispatching.Application/Login/Dto/Input/LoginInput.cs new file mode 100644 index 0000000..1d2adce --- /dev/null +++ b/src/Znyc.Dispatching.Application/Login/Dto/Input/LoginInput.cs @@ -0,0 +1,34 @@ +using System.ComponentModel.DataAnnotations; + +namespace Znyc.Dispatching.Application +{ + /// + /// 登录实体 + /// + public class LoginInput + { + [Required(ErrorMessage = "JsCode不能为空!")] + public string JsCode { get; set; } + + + /// + /// getuserinfo信息 + /// + [Required(ErrorMessage = "未查询到用户信息")] + public UserInfo UserInfo { get; set; } + } + + public class UserInfo + { + /// + /// 昵称 + /// + public string NickName { get; set; } + + /// + /// 头像地址 + /// + public string AvatarUrl { get; set; } + + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Login/Dto/Output/LoginOutput.cs b/src/Znyc.Dispatching.Application/Login/Dto/Output/LoginOutput.cs new file mode 100644 index 0000000..7bdd6e9 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Login/Dto/Output/LoginOutput.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; + +namespace Znyc.Dispatching.Application +{ + public class LoginOutput + { + /// + /// Token + /// + public string Token { get; set; } + + /// + /// 跳转路径 + /// + public string Url { get; set; } + + /// + /// 角色菜单列表 + /// + public List Menus { get; set; } + } +} diff --git a/src/Znyc.Dispatching.Application/Login/Services/ILoginService.cs b/src/Znyc.Dispatching.Application/Login/Services/ILoginService.cs new file mode 100644 index 0000000..f8c82f4 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Login/Services/ILoginService.cs @@ -0,0 +1,14 @@ +using System.Threading.Tasks; + +namespace Znyc.Dispatching.Application +{ + public interface ILoginService + { + /// + /// 用户登录 + /// + /// + /// + Task Login(LoginInput loginInput); + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Login/Services/LoginService.cs b/src/Znyc.Dispatching.Application/Login/Services/LoginService.cs new file mode 100644 index 0000000..35b8e5c --- /dev/null +++ b/src/Znyc.Dispatching.Application/Login/Services/LoginService.cs @@ -0,0 +1,182 @@ +using Furion.DatabaseAccessor; +using Furion.DataEncryption; +using Furion.DependencyInjection; +using Furion.DynamicApiController; +using Furion.FriendlyException; +using MapsterMapper; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Znyc.Dispatching.Core; +using Znyc.Dispatching.Core.Entitys; +using Znyc.Dispatching.Core.Extension; +using Znyc.Dispatching.WeChat.Core.CommonService.TemplateMessage.WxOpen; + +namespace Znyc.Dispatching.Application +{ + /// + /// 登录服务 + /// + [ApiDescriptionSettings(Name = "Login", Order = 146, IgnoreApi = false)] + public class LoginService : ILoginService, IDynamicApiController, ITransient + { + private readonly ICacheService _cacheService; + private readonly IRepository _companyRepository; + private readonly IRepository _employeeRepository; + private readonly IUserManager _userManager; + private readonly IRepository _userRepository; + private readonly WeixinSettingOptions _weixinSettingOptions; + private readonly IWxUserRelationService _wxUserRelationService; + private readonly ILogger _logger; + private readonly IRoleMenuService _roleMenuService; + private readonly IMapper _mapper; + + public LoginService( + IRepository userRepository, + IRepository employeeRepository, + IRepository companyRepository, + IUserManager userManager, + ICacheService cacheService, + IOptions weixinSettingOptions, + IWxUserRelationService wxUserRelationService, + ILogger logger, + IMapper mapper, + IRoleMenuService roleMenuService + ) + { + _userRepository = userRepository; + _employeeRepository = employeeRepository; + _companyRepository = companyRepository; + _cacheService = cacheService; + _userManager = userManager; + _weixinSettingOptions = weixinSettingOptions.Value; + _wxUserRelationService = wxUserRelationService; + _logger = logger; + _mapper = mapper; + _roleMenuService = roleMenuService; + } + + /// + /// 用户登录 + /// + /// + /// + [HttpPost] + [UnitOfWork] + [AllowAnonymous] + [Route("api/v1/login")] + public async Task Login(LoginInput loginInput) + { + var jsCode2JsonResult = + await SnsApi.JsCode2JsonAsync(_weixinSettingOptions.WxOpenAppId, _weixinSettingOptions.WxOpenAppSecret, + loginInput.JsCode); + if (jsCode2JsonResult.IsNull()) + { + throw Oops.Bah("jsCode2JsonResult为空"); + } + var user = await _userRepository.Where(x => x.OpenId == jsCode2JsonResult.openid).Select(x => new + { + x.Id, + x.UserName, + x.OpenId + }).FirstOrDefaultAsync(); + + if (user.IsNull()) + { + throw Oops.Bah("您还未注册用户!").StatusCode(4012); + }; + + + var employeeList = await _employeeRepository.Where(x => x.UserId == user.Id && x.Status == (int)CommonStatusEnum.ENABLE).Select(x => new + { + x.UserId, + x.CompanyId, + x.RoleId, + x.Status, + x.IsDefault + }).ToListAsync(); + + if (employeeList.Any(x => x.RoleId == (int)RoleStatusEnum.Outside)) + { + employeeList.Remove(employeeList.Find(x => x.RoleId == (int)RoleStatusEnum.Outside)); + } + var cIds = employeeList.Select(x => x.CompanyId).ToArray(); + //公司 + var company = await _companyRepository.Where(x => cIds.Contains(x.Id) && x.Status == (int)CommonStatusEnum.ENABLE).Select(x => new + { + x.Id, + x.Status + }).OrderByDescending(x => x.Id).FirstOrDefaultAsync(); + + if (company.IsNull()) + { + throw Oops.Bah("").StatusCode(4012); + } + var employee = employeeList.Find(x => x.CompanyId == company.Id); + if (employee.Status == (int)CommonStatusEnum.DISABLE) + { + throw Oops.Bah("此账号已被管理员停用"); + } + + //用户中间表 + await _wxUserRelationService.AddOrUpdateAsync(user.Id, jsCode2JsonResult.openid, jsCode2JsonResult.unionid); + // 生成 token + string accessToken = JWTEncryption.Encrypt(new Dictionary + { + {ClaimConst.CLAINM_USERID, user.Id}, + {ClaimConst.CLAINM_COMPANYID, employee.CompanyId}, + {ClaimConst.CLAINM_ROLEID, employee.RoleId}, + {ClaimConst.CLAINM_USERNAME, user.UserName}, + {ClaimConst.CLAINM_SUPERADMIN, new long[] { (long)RoleStatusEnum.Administrator, (long)RoleStatusEnum.Scheduling,(long)RoleStatusEnum.CarCaptain }.Contains(employee.RoleId)} + }); + //RedisToken + await _cacheService.SetTokenAsync(user.Id, accessToken); + LoginOutput loginOutput = new LoginOutput() + { + Token = accessToken, + Menus = await _roleMenuService.GetRoleMenuListByIdAsync(employee.RoleId) + }; + switch (employee.RoleId) + { + case (long)RoleStatusEnum.Administrator: + case (long)RoleStatusEnum.Scheduling: + case (long)RoleStatusEnum.CarCaptain: + case (long)RoleStatusEnum.Salesman: + case (long)RoleStatusEnum.Financial: + case (long)RoleStatusEnum.WareHouse: + loginOutput.Url = CommonConst.DEFAULT_SUPERADMIN_INDEX; + break; + case (long)RoleStatusEnum.CrewMembers: + case (long)RoleStatusEnum.ProjectPerson: + case (long)RoleStatusEnum.PartTimeSalesman: + loginOutput.Url = CommonConst.DEFAULT_DRIVER_INDEX; + break; + case (long)RoleStatusEnum.Outside: + loginOutput.Url = CommonConst.DEFAULT_OUTSIDE_INDEX; + break; + default: + loginOutput.Url = CommonConst.DEFAULT_DRIVER_INDEX; + break; + } + return loginOutput; + } + + + + + /// + /// 清理缓存 + /// + /// + [HttpGet] + public async Task ClearCache() + { + await _cacheService.RemoveCacheByUserIdAsync(_userManager.UserId); + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Mapper/Mapper.cs b/src/Znyc.Dispatching.Application/Mapper/Mapper.cs new file mode 100644 index 0000000..9c9aa51 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Mapper/Mapper.cs @@ -0,0 +1,22 @@ +using Mapster; +using Znyc.Dispatching.Core; +using Znyc.Dispatching.Core.Entitys; + +namespace Znyc.Dispatching.Application.Mapper +{ + public class Mapper : IRegister + { + public void Register(TypeAdapterConfig config) + { + config.ForType() + .Map(dest => dest.OrderSourceName, src => typeof(OrderSourceEnum).GetDescription(src.OrderSource)); + + + + config.ForType() + .Map(dest => dest.Picture, src => CommonConst.DEFAULT_ORDERVISA + src.Picture); + + + } + } +} diff --git a/src/Znyc.Dispatching.Application/Menu/Dto/Input/MenuInput.cs b/src/Znyc.Dispatching.Application/Menu/Dto/Input/MenuInput.cs new file mode 100644 index 0000000..4de9dfa --- /dev/null +++ b/src/Znyc.Dispatching.Application/Menu/Dto/Input/MenuInput.cs @@ -0,0 +1,91 @@ +using System.ComponentModel.DataAnnotations; +using Znyc.Dispatching.Core; + +namespace Znyc.Dispatching.Application +{ + /// + /// 菜单参数 + /// + public class MenuInput + { + /// + /// 父Id + /// + public long ParentId { get; set; } + + /// + /// 所属层级 + /// + public int Layers { get; set; } + + /// + /// 名称 + /// + [Required] + [MaxLength(20)] + public string Name { get; set; } + + /// + /// 编码 + /// + [Required] + [MaxLength(50)] + public string Code { get; set; } + + /// + /// 菜单类型(字典 0目录 1菜单 2按钮) + /// + public MenuTypeEnum Type { get; set; } + + /// + /// 图标 + /// + [MaxLength(20)] + public string Icon { get; set; } + + /// + /// 路由地址 + /// + [MaxLength(100)] + public string Router { get; set; } + + /// + /// 权限标识 + /// + public string Permission { get; set; } + + /// + /// 是否外链 + /// + [MaxLength(5)] + public bool IsFrame { get; set; } + + /// + /// 是否展开 + /// + [MaxLength(5)] + public bool IsExpand { get; set; } + + /// + /// 是否显示 + /// + [MaxLength(5)] + public bool IsShow { get; set; } + + /// + /// 排序 + /// + public int Sort { get; set; } + + /// + /// 详细描述 + /// + [MaxLength(100)] + public string Description { get; set; } + + /// + /// CommonStatus + /// + public CommonStatusEnum Status { get; set; } = CommonStatusEnum.ENABLE; + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Menu/Dto/OutPut/MenuOutput.cs b/src/Znyc.Dispatching.Application/Menu/Dto/OutPut/MenuOutput.cs new file mode 100644 index 0000000..a224aa5 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Menu/Dto/OutPut/MenuOutput.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; + +namespace Znyc.Dispatching.Application +{ + /// + /// 菜单树(列表形式) + /// + public class MenuOutput : MenuInput + { + /// + /// 菜单Id + /// + public long Id { get; set; } + + /// + /// 子集 + /// + public ICollection Childrens { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Menu/Services/IMenuService.cs b/src/Znyc.Dispatching.Application/Menu/Services/IMenuService.cs new file mode 100644 index 0000000..18a541f --- /dev/null +++ b/src/Znyc.Dispatching.Application/Menu/Services/IMenuService.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Znyc.Dispatching.Application +{ + public interface IMenuService + { + Task> GetLoginPermissionList(long userId); + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Menu/Services/MenuService.cs b/src/Znyc.Dispatching.Application/Menu/Services/MenuService.cs new file mode 100644 index 0000000..58d0a33 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Menu/Services/MenuService.cs @@ -0,0 +1,80 @@ +using Furion.DatabaseAccessor; +using Furion.DependencyInjection; +using Furion.DynamicApiController; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Znyc.Dispatching.Core; +using Znyc.Dispatching.Core.Entitys; +using Znyc.Dispatching.Core.Extension; + +namespace Znyc.Dispatching.Application +{ + /// + /// 系统菜单服务 + /// + [ApiDescriptionSettings(Name = "menu", Order = 40, IgnoreApi = false)] + public class MenuService : IMenuService, IDynamicApiController, ITransient + { + private readonly ICacheService _cacheService; + private readonly IRepository _repository; + private readonly IRepository _roleMenuRepository; + private readonly IRoleMenuService _roleMenuService; + private readonly IRepository _roleRepository; + private readonly IUserManager _userManager; + private readonly IUserRoleService _userRoleService; + + public MenuService(IRepository repository, + IRepository roleMenuRepository, + ICacheService cacheService, + IUserManager userManager, + IUserRoleService userRoleService, + IRoleMenuService roleMenuService, + IRepository roleRepository) + { + _repository = repository; + _cacheService = cacheService; + _userManager = userManager; + _userRoleService = userRoleService; + _roleMenuService = roleMenuService; + _roleMenuRepository = roleMenuRepository; + _roleRepository = roleRepository; + } + + /// + /// 获取用户权限(按钮权限标识集合) + /// + /// + /// + [NonAction] + public async Task> GetLoginPermissionList(long userId) + { + List permissions = await _cacheService.GetPermissionAsync(userId); // 先从缓存里面读取 + if (permissions.IsNull() || permissions.Count < 1) + { + if (!_userManager.SuperAdmin) + { + List roleIdList = await _userRoleService.GetUserRoleIdList(userId); + List menuIdList = await _roleMenuService.GetRoleMenuIdList(roleIdList); + permissions = await _repository.Where(u => menuIdList.Contains(u.Id)) + .Where(u => u.Type == MenuTypeEnum.BTN) + .Where(u => u.Status == CommonStatusEnum.ENABLE) + .Select(u => u.Permission).ToListAsync(); + } + else + { + permissions = await _repository.DetachedEntities + .Where(u => u.Type == MenuTypeEnum.BTN) + .Where(u => u.Status == CommonStatusEnum.ENABLE) + .Select(u => u.Permission).ToListAsync(); + } + + await _cacheService.SetPermissionAsync(userId, permissions); // 缓存结果 + } + + return permissions; + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Message/Dto/Input/MessageAddInput.cs b/src/Znyc.Dispatching.Application/Message/Dto/Input/MessageAddInput.cs new file mode 100644 index 0000000..bf8e8e9 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Message/Dto/Input/MessageAddInput.cs @@ -0,0 +1,27 @@ +using System.ComponentModel.DataAnnotations; + +namespace Znyc.Dispatching.Application +{ + /// + /// + public class MessageAddInput + { + /// + /// 消息标题 + /// + [Required] + [MaxLength(12)] + public string MessageTitle { get; set; } + + /// + /// 产品类型 + /// + public int ProductType { get; set; } + + /// + /// 消息内容 + /// + [Required] + public string Content { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Message/Dto/OutPut/MessageOutput.cs b/src/Znyc.Dispatching.Application/Message/Dto/OutPut/MessageOutput.cs new file mode 100644 index 0000000..a2da52f --- /dev/null +++ b/src/Znyc.Dispatching.Application/Message/Dto/OutPut/MessageOutput.cs @@ -0,0 +1,27 @@ +namespace Znyc.Dispatching.Application +{ + /// + /// + public class MessageOutput + { + /// + /// 主键 + /// + public long Id { get; set; } + + /// + /// 消息类型 + /// + public int MessageType { get; set; } + + /// + /// 消息标题 + /// + public string MessageTitle { get; set; } + + /// + /// 消息内容 + /// + public string Content { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Message/Services/IMessageService.cs b/src/Znyc.Dispatching.Application/Message/Services/IMessageService.cs new file mode 100644 index 0000000..126495a --- /dev/null +++ b/src/Znyc.Dispatching.Application/Message/Services/IMessageService.cs @@ -0,0 +1,6 @@ +namespace Znyc.Dispatching.Application +{ + public interface IMessageService + { + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Message/Services/MessageService.cs b/src/Znyc.Dispatching.Application/Message/Services/MessageService.cs new file mode 100644 index 0000000..ce67879 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Message/Services/MessageService.cs @@ -0,0 +1,61 @@ +using Furion.DatabaseAccessor; +using Furion.DependencyInjection; +using Furion.DynamicApiController; +using Furion.FriendlyException; +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Znyc.Dispatching.Core.Entitys; +using Znyc.Dispatching.Core.Extension; + +namespace Znyc.Dispatching.Application +{ + /// + /// 站内消息服务 + /// + [ApiDescriptionSettings(Name = "message", Order = 110)] + public class MessageService : IMessageService, IDynamicApiController, ITransient + { + private readonly IRepository _repository; + + public MessageService(IRepository repository) + { + _repository = repository; + } + + /// + /// 分页查询系统消息通知 + /// + /// + /// + /// + /// + [HttpGet] + public async Task> PageAsync(int messageType, int currentPage = 1, int pageSize = 10) + { + PagedList pageList = await _repository + .Where(x => x.Type == 1 && x.MessageType == messageType) + .OrderByDescending(x => x.SendTime) + .ToPagedListAsync(currentPage, pageSize); + return pageList; + } + + /// + /// 获取消息通知详情 + /// + /// + /// + [HttpGet] + public async Task GetMessageById(long id) + { + Message result = await _repository.FindOrDefaultAsync(id); + if (result.IsNull()) + { + throw Oops.Oh("消息通知不存在"); + } + + return result; + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/MessageRecord/Dto/Input/MessageRecordAddInput.cs b/src/Znyc.Dispatching.Application/MessageRecord/Dto/Input/MessageRecordAddInput.cs new file mode 100644 index 0000000..77aaa79 --- /dev/null +++ b/src/Znyc.Dispatching.Application/MessageRecord/Dto/Input/MessageRecordAddInput.cs @@ -0,0 +1,23 @@ +namespace Znyc.Dispatching.Application +{ + /// + /// 消息记录 + /// + public class MessageRecordAddInput + { + /// + /// 接收者Id + /// + public long ReceiverId { get; set; } + + /// + /// 消息Id + /// + public long MessageId { get; set; } + + /// + /// 状态 + /// + public int Status { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/MessageRecord/Dto/OutPut/MessageRecordOutput.cs b/src/Znyc.Dispatching.Application/MessageRecord/Dto/OutPut/MessageRecordOutput.cs new file mode 100644 index 0000000..0361256 --- /dev/null +++ b/src/Znyc.Dispatching.Application/MessageRecord/Dto/OutPut/MessageRecordOutput.cs @@ -0,0 +1,22 @@ +namespace Znyc.Dispatching.Application +{ + /// + /// + public class MessageRecordOutput + { + /// + /// 主键 + /// + public long Id { get; set; } + + /// + /// 消息标题 + /// + public string MessageTitle { get; set; } + + /// + /// 消息内容 + /// + public string Content { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/MessageRecord/Services/IMessageRecordService.cs b/src/Znyc.Dispatching.Application/MessageRecord/Services/IMessageRecordService.cs new file mode 100644 index 0000000..4bc835a --- /dev/null +++ b/src/Znyc.Dispatching.Application/MessageRecord/Services/IMessageRecordService.cs @@ -0,0 +1,6 @@ +namespace Znyc.Dispatching.Application +{ + public interface IMessageRecordService + { + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/MessageRecord/Services/MessageRecordService.cs b/src/Znyc.Dispatching.Application/MessageRecord/Services/MessageRecordService.cs new file mode 100644 index 0000000..82ae425 --- /dev/null +++ b/src/Znyc.Dispatching.Application/MessageRecord/Services/MessageRecordService.cs @@ -0,0 +1,31 @@ +using Furion.DatabaseAccessor; +using Furion.DependencyInjection; +using Furion.DynamicApiController; +using Microsoft.AspNetCore.Mvc; +using Znyc.Dispatching.Core; +using Znyc.Dispatching.Core.Entitys; + +namespace Znyc.Dispatching.Application +{ + /// + /// 消息记录服务 + /// + [ApiDescriptionSettings(Name = "messagerecord", Order = 120, IgnoreApi = true)] + public class MessageRecordService : IMessageRecordService, IDynamicApiController, ITransient + { + private readonly IRepository _messageRepository; + private readonly IRepository _repository; + private readonly IUserManager _userManager; + + public MessageRecordService( + IRepository repository, + IUserManager userManager, + IRepository messageRepository + ) + { + _repository = repository; + _userManager = userManager; + _messageRepository = messageRepository; + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Oil/Dto/Input/OilAddInput.cs b/src/Znyc.Dispatching.Application/Oil/Dto/Input/OilAddInput.cs new file mode 100644 index 0000000..ee61730 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Oil/Dto/Input/OilAddInput.cs @@ -0,0 +1,51 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace Znyc.Dispatching.Application +{ + /// + /// + /// + public class OilAddInput + { + /// + /// 加油日期 + /// + [Required(ErrorMessage = "请选择加油日期")] + public DateTime PlusOilDate { get; set; } + + /// + /// 车辆Id + /// + [Required(ErrorMessage = "请选择车辆编号")] + public long VehicleId { get; set; } + + /// + /// 车俩编号 + /// + [Required(ErrorMessage = "请选择车辆编号")] + public string VehicleCode { get; set; } + + /// + /// 加油单号 + /// + public string PlusOilOrder { get; set; } + + /// + /// 加油量 + /// + [Required(ErrorMessage = "请填写加油量")] + [MaxLength(10, ErrorMessage = "加油量输入不正确")] + public string PlusOilAmount { get; set; } + + /// + /// 油单价 + /// + public decimal OilPrice { get; set; } + + /// + /// 金额 + /// + public decimal AmountMoney { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Oil/Dto/Input/OilUpdateInput.cs b/src/Znyc.Dispatching.Application/Oil/Dto/Input/OilUpdateInput.cs new file mode 100644 index 0000000..3d3ecf0 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Oil/Dto/Input/OilUpdateInput.cs @@ -0,0 +1,9 @@ +namespace Znyc.Dispatching.Application +{ + /// + /// + public class OilUpdateInput : OilAddInput + { + public long Id { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Oil/Dto/OutPut/CensusOilOutput.cs b/src/Znyc.Dispatching.Application/Oil/Dto/OutPut/CensusOilOutput.cs new file mode 100644 index 0000000..d3e353a --- /dev/null +++ b/src/Znyc.Dispatching.Application/Oil/Dto/OutPut/CensusOilOutput.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; + +namespace Znyc.Dispatching.Application +{ + /// + /// 油耗统计 + /// + public class CensusOilListOutput + { + /// + /// 主键 + /// + public long VehicleId { get; set; } + + /// + /// 车俩编号 + /// + public string VehicleCode { get; set; } + + /// + /// 加油量 + /// + public string PlusOilAmount { get; set; } + + /// + /// 金额 + /// + public decimal AmountMoney { get; set; } + + /// + /// 加油次数 + /// + public int PlusOilCount { get; set; } + } + + + public class CensusOilOutput + { + /// + /// 加油总次数 + /// + public int AllPlusOilCount { get; set; } + + /// + /// 加油总量 + /// + public int AllPlusOilAmount { get; set; } + + /// + /// 总金额 + /// + public decimal AllAmountMoney { get; set; } + + /// + /// 油耗统计 + /// + public List CensusOilList { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Oil/Dto/OutPut/OilOutput.cs b/src/Znyc.Dispatching.Application/Oil/Dto/OutPut/OilOutput.cs new file mode 100644 index 0000000..2fa0068 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Oil/Dto/OutPut/OilOutput.cs @@ -0,0 +1,42 @@ +namespace Znyc.Dispatching.Application +{ + public class OilOutput + { + /// + /// 主键 + /// + public long Id { get; set; } + + public long VehicleId { get; set; } + + /// + /// 加油日期 + /// + public string PlusOilDate { get; set; } + + /// + /// 车俩编号 + /// + public string VehicleCode { get; set; } + + /// + /// 加油单号 + /// + public string PlusOilOrder { get; set; } + + /// + /// 加油量 + /// + public string PlusOilAmount { get; set; } + + /// + /// 油单价 + /// + public decimal OilPrice { get; set; } + + /// + /// 金额 + /// + public decimal AmountMoney { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Oil/Services/IOilService.cs b/src/Znyc.Dispatching.Application/Oil/Services/IOilService.cs new file mode 100644 index 0000000..57f68d9 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Oil/Services/IOilService.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Znyc.Dispatching.Application +{ + /// + /// 油耗服务 + /// + public interface IOilService + { + + /// + /// 加油记录 + /// + /// + /// + /// + /// + /// + Task> PlusOilPageAsync(int currentPage, int pageSize, DateTime startTime, DateTime endTime); + + /// + /// 油耗统计 + /// + /// + /// + /// + Task OilCensusPageAsync(DateTime startTime, DateTime endTime); + + /// + /// 根据Id获油耗信息 + /// + /// + Task GetByIdAsync(long companyId); + + /// + /// 添加油耗信息 + /// + /// + /// + Task InsertAsync(OilAddInput input); + + /// + /// 修改油耗信息 + /// + /// + /// + Task UpdateAsync(OilUpdateInput input); + + /// + /// 删除油耗信息 + /// + /// + /// + Task DeleteAsync(long oilId); + + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Oil/Services/OilService.cs b/src/Znyc.Dispatching.Application/Oil/Services/OilService.cs new file mode 100644 index 0000000..c252534 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Oil/Services/OilService.cs @@ -0,0 +1,169 @@ +using Furion.DatabaseAccessor; +using Furion.DatabaseAccessor.Extensions; +using Furion.DependencyInjection; +using Furion.DynamicApiController; +using Furion.FriendlyException; +using Mapster; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Znyc.Dispatching.Core; +using Znyc.Dispatching.Core.Entitys; +using Znyc.Dispatching.Core.Extension; + +namespace Znyc.Dispatching.Application +{ + /// + /// 油耗管理 + /// + [ApiDescriptionSettings(Name = "oil", Order = 30)] + public class OilService : IOilService, IDynamicApiController, ITransient + { + private readonly IRepository _repository; + private readonly IUserManager _userManager; + + public OilService( + IRepository repository, + IUserManager userManager + ) + { + _repository = repository; + _userManager = userManager; + } + + + /// + /// 加油记录 + /// + /// + /// + /// + /// + /// + [HttpGet] + [Route("api/v1/plus/oils/search")] + public async Task> PlusOilPageAsync(int currentPage, int pageSize, DateTime startTime, DateTime endTime) + { + var oils = (await _repository.DetachedEntities + .Where(x => x.CompanyId == _userManager.CompanyId && x.IsDeleted == false) + .Where(startTime.IsNotNull(), x => x.CreatedTime >= startTime) + .Where(endTime.IsNotNull(), x => x.CreatedTime <= endTime) + .OrderByDescending(x => x.PlusOilDate) + .Select(x => new OilOutput() + { + Id = x.Id, + VehicleId = x.VehicleId, + PlusOilDate = x.PlusOilDate.ToString("yyyy-MM-dd"), + VehicleCode = x.VehicleCode, + PlusOilOrder = x.PlusOilOrder, + PlusOilAmount = x.PlusOilAmount, + OilPrice = x.OilPrice, + AmountMoney = x.AmountMoney + }) + .ToPagedListAsync(currentPage, pageSize)).Adapt>(); + return oils; + } + + /// + /// 油耗统计 + /// + /// + /// + /// + [HttpGet] + [Route("api/v1/census/oils/search")] + public async Task OilCensusPageAsync(DateTime startTime, DateTime endTime) + { + var oilList = await @"SELECT VehicleCode,VehicleId, + Count(CASE WHEN IsDeleted=FALSE THEN VehicleId END) AS PlusOilCount, + SUM(AmountMoney) AS AmountMoney, + SUM(PlusOilAmount)AS PlusOilAmount + FROM dc_oil WHERE CompanyId = @companyId AND IsDeleted=FALSE AND VehicleId > 0 And PlusOilDate >= @startTime And PlusOilDate <= @endTime + GROUP BY VehicleId,VehicleCode + ORDER BY PlusOilCount DESC,PlusOilAmount DESC,AmountMoney DESC" + .SqlQueryAsync(new { companyId = _userManager.CompanyId, startTime = startTime, endTime = endTime }); + CensusOilOutput data = new CensusOilOutput() { CensusOilList = oilList }; + foreach (var item in oilList) + { + data.AllPlusOilCount += item.PlusOilCount; + data.AllPlusOilAmount += item.PlusOilAmount.ObjToInt(); + data.AllAmountMoney += item.AmountMoney; + } + return data; + } + + /// + /// 根据Id获油耗信息 + /// + /// + [HttpGet] + [Route("api/v1/oil/{id}")] + public async Task GetByIdAsync(long id) + { + Oil oil = await _repository.DetachedEntities.FirstOrDefaultAsync(x => x.Id == id); + var result = oil.Adapt(); + result.PlusOilDate = oil.PlusOilDate.ToString("yyyy-MM-dd"); + return result; + } + + + /// + /// 添加油耗信息 + /// + /// + /// + [HttpPost] + [Route("api/v1/oil")] + public async Task InsertAsync(OilAddInput input) + { + var oil = input.Adapt(); + oil.CompanyId = _userManager.CompanyId; + var result = await _repository.InsertNowAsync(oil); + if (result.IsNull()) + { + throw Oops.Oh("添加油耗信息失败"); + } + } + + /// + /// 修改油耗信息 + /// + /// + /// + [HttpPut] + [Route("api/v1/oil")] + public async Task UpdateAsync(OilUpdateInput input) + { + var oil = await _repository.FirstOrDefaultAsync(x => x.Id == input.Id); + if (oil.IsNull()) throw Oops.Oh("暂无该信息"); + var result = await _repository.UpdateNowAsync(input.Adapt(oil)); + if (result.IsNull()) + { + throw Oops.Oh("修改油耗信息失败"); + } + } + + /// + /// 删除油耗信息 + /// + /// + /// + [HttpDelete] + [Route("api/v1/oil/{id}")] + public async Task DeleteAsync(long id) + { + var oil = await _repository.FirstOrDefaultAsync(x => x.Id == id); + if (oil.IsNull()) throw Oops.Oh("暂无该信息"); + oil.IsDeleted = true; + var result = await _repository.UpdateNowAsync(oil); + if (result.IsNull()) + { + throw Oops.Oh("删除油耗信息失败"); + } + } + + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Order/Dto/Input/Assign/AssignOrderInput.cs b/src/Znyc.Dispatching.Application/Order/Dto/Input/Assign/AssignOrderInput.cs new file mode 100644 index 0000000..13b1c27 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Order/Dto/Input/Assign/AssignOrderInput.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; + +namespace Znyc.Dispatching.Application +{ + /// + /// 指派订单实体 + /// + public class AssignOrderInput : StayAssignInput + { + + /// + /// OrderId + /// + public long Id { get; set; } + + /// + /// 车辆Id + /// + public long VehicleId { get; set; } + + /// + /// 车辆编号 + /// + public string VehicleCode { get; set; } + + + /// + /// 是否外请车 + /// + public bool IsOutside { get; set; } + + /// + /// 车组人员 + /// + public string OrderVehiclePerson { get; set; } + + + /// + /// 是否替换车组人员 + /// + public bool IsReplaceOrderVehicle { get; set; } + + + /// + /// 车组人员 + /// + public List OrderVehicles { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Order/Dto/Input/Assign/AssignUpdateInput.cs b/src/Znyc.Dispatching.Application/Order/Dto/Input/Assign/AssignUpdateInput.cs new file mode 100644 index 0000000..7f985ef --- /dev/null +++ b/src/Znyc.Dispatching.Application/Order/Dto/Input/Assign/AssignUpdateInput.cs @@ -0,0 +1,14 @@ +namespace Znyc.Dispatching.Application +{ + /// + /// 指派订单实体 + /// + public class AssignUpdateInput : AssignOrderInput + { + /// + /// OrderId + /// + public long Id { get; set; } + + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Order/Dto/Input/OrderAddInput.cs b/src/Znyc.Dispatching.Application/Order/Dto/Input/OrderAddInput.cs new file mode 100644 index 0000000..d938506 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Order/Dto/Input/OrderAddInput.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace Znyc.Dispatching.Application +{ + /// + /// 派单订单表实体 + /// + public class OrderAddInput + { + + #region 待指派 + + /// + /// 工程Id + /// + [Required(ErrorMessage = "请选择工程")] + public long ProjectId { get; set; } + + /// + /// 工程名称 + /// + public string ProjectName { get; set; } + + /// + /// 业务员Id + /// + public long SalesmanId { get; set; } + + /// + /// 到场时间 + /// + [Required(ErrorMessage = "请选择到场时间")] + public DateTime ArriveDate { get; set; } + + /// + /// 经度 + /// + public decimal Longitude { get; set; } + + /// + /// 纬度 + /// + public decimal Latitude { get; set; } + + /// + /// 地址 + /// + [Required(ErrorMessage = "请选择所在位置")] + public string Address { get; set; } + + /// + /// 订单内容 + /// + [Required(ErrorMessage = "请输入派单内容")] + public string OrderContent { get; set; } + #endregion + + + + #region 一键指派 + + /// + /// 车辆Id + /// + public long VehicleId { get; set; } + + /// + /// 是否外请车 + /// + public bool IsOutside { get; set; } + + /// + /// 车组人员 + /// + public List orderVehicles { get; set; } + + #endregion + + /// + /// 任务车型 + /// + public long VehicleType { get; set; } + + + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Order/Dto/Input/OrderUpdateInput.cs b/src/Znyc.Dispatching.Application/Order/Dto/Input/OrderUpdateInput.cs new file mode 100644 index 0000000..8296862 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Order/Dto/Input/OrderUpdateInput.cs @@ -0,0 +1,9 @@ +namespace Znyc.Dispatching.Application +{ + /// + /// + public class OrderUpdateInput : OrderAddInput + { + public long Id { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Order/Dto/Input/Sign/SignInput.cs b/src/Znyc.Dispatching.Application/Order/Dto/Input/Sign/SignInput.cs new file mode 100644 index 0000000..1974516 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Order/Dto/Input/Sign/SignInput.cs @@ -0,0 +1,23 @@ +using System.ComponentModel.DataAnnotations; + +namespace Znyc.Dispatching.Application +{ + /// + /// 签单 + /// + public class SignInput + { + + /// + /// OrderId + /// + public long OrderId { get; set; } + + + /// + /// 图片路径 + /// + [Required(ErrorMessage = "请上传签证单")] + public string Picture { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Order/Dto/Input/Sign/SignUpdateInput.cs b/src/Znyc.Dispatching.Application/Order/Dto/Input/Sign/SignUpdateInput.cs new file mode 100644 index 0000000..47d0f50 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Order/Dto/Input/Sign/SignUpdateInput.cs @@ -0,0 +1,10 @@ +namespace Znyc.Dispatching.Application +{ + /// + /// 签单 + /// + public class SignUpdateInput : SignInput + { + + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Order/Dto/Input/StayAssign/StayAssignInput.cs b/src/Znyc.Dispatching.Application/Order/Dto/Input/StayAssign/StayAssignInput.cs new file mode 100644 index 0000000..7d1e09d --- /dev/null +++ b/src/Znyc.Dispatching.Application/Order/Dto/Input/StayAssign/StayAssignInput.cs @@ -0,0 +1,70 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace Znyc.Dispatching.Application +{ + /// + /// 待指派订单实体 + /// + public class StayAssignInput + { + public long Id { get; set; } + + /// + /// 工程Id + /// + public long ProjectId { get; set; } + + /// + /// 工程名称 + /// + public string ProjectName { get; set; } + + /// + /// 业务员Id + /// + public long SalesmanId { get; set; } + + /// + /// 到场时间 + /// + [Required(ErrorMessage = "请选择到场时间")] + public DateTime ArriveDate { get; set; } + + /// + /// 经度 + /// + public decimal Longitude { get; set; } + + /// + /// 纬度 + /// + public decimal Latitude { get; set; } + + /// + /// 地址 + /// + public string Address { get; set; } + + /// + /// 订单内容 + /// + public string OrderContent { get; set; } + + /// + /// 任务车型 + /// + public long VehicleType { get; set; } + + + /// + /// 施工单位 + /// + public long ConstructionId { get; set; } + + /// + /// 施工单位 + /// + public string ConstructionName { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Order/Dto/Input/StayAssign/StayAssignUpdateInput.cs b/src/Znyc.Dispatching.Application/Order/Dto/Input/StayAssign/StayAssignUpdateInput.cs new file mode 100644 index 0000000..055c146 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Order/Dto/Input/StayAssign/StayAssignUpdateInput.cs @@ -0,0 +1,14 @@ +namespace Znyc.Dispatching.Application +{ + /// + /// 待指派订单实体 + /// + public class StayAssignUpdateInput : StayAssignInput + { + /// + /// OrderId + /// + public long Id { get; set; } + + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Order/Dto/OutPut/AdminOrderListOutput.cs b/src/Znyc.Dispatching.Application/Order/Dto/OutPut/AdminOrderListOutput.cs new file mode 100644 index 0000000..0e7eba1 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Order/Dto/OutPut/AdminOrderListOutput.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; + +namespace Znyc.Dispatching.Application +{ + /// + /// 订单列表返回 + /// + public class AdminOrderListOutput + { + public int StayAssignCount { get; set; } + public int AssignCount { get; set; } + public int CompleteCount { get; set; } + public int EvaluateCount { get; set; } + public PagedList PagedList { get; set; } + + } +} diff --git a/src/Znyc.Dispatching.Application/Order/Dto/OutPut/OrderListOutput.cs b/src/Znyc.Dispatching.Application/Order/Dto/OutPut/OrderListOutput.cs new file mode 100644 index 0000000..1c2bd95 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Order/Dto/OutPut/OrderListOutput.cs @@ -0,0 +1,148 @@ +using System; +using System.Collections.Generic; + +namespace Znyc.Dispatching.Application +{ + /// + /// 订单列表返回 + /// + public class OrderListOutput + { + /// + /// OrderId + /// + public long Id { get; set; } + + /// + /// 车辆Id,,冗余查询车辆列表状态 + /// + public long VehicleId { get; set; } + + /// + /// 车辆编号 + /// + public string VehicleCode { get; set; } + + /// + /// 订单标题 + /// + public string OrderTitle { get; set; } + + /// + /// 订单内容 + /// + public string OrderContent { get; set; } + + /// + /// 到场时间 + /// + public DateTime ArriveDate { get; set; } + + /// + /// 订单来源 + /// + public int OrderSource { get; set; } + + /// + /// 订单来源明细 + /// + public string OrderSourceName { get; set; } + + + /// + /// 是否外请车 + /// + public bool IsOutside { get; set; } + /// + /// 状态, + /// + public int Status { get; set; } + + /// + /// 状态名称 + /// + public string StatusName { get; set; } + + /// + /// 经度 + /// + public decimal Longitude { get; set; } + + /// + /// 纬度 + /// + public decimal Latitude { get; set; } + + /// + /// 地址 + /// + public string Address { get; set; } + + /// + /// 工程Id + /// + public long ProjectId { get; set; } + + /// + /// 工程名称 + /// + public string ProjectName { get; set; } + + /// + /// 车组人员 + /// + public string OrderVehiclePerson { get; set; } + + /// + /// + /// + public List OrderVehicles { get; set; } + + + /// + /// 车组人员姓名 + /// + public string OrderVehicleDriverName { get; set; } + + + /// + /// 车组人员电话 + /// + public string OrderVehicleDriverPhone { get; set; } + + /// + /// 指派人名称 + /// + public string AssignUserName { get; set; } + + /// + /// 指派人Id + /// + public long AssignUserId { get; set; } + + /// + /// 任务车型 + /// + public long VehicleType { get; set; } + + + /// + /// 任务车型名称 + /// + public string VehicleTypeName { get; set; } + + + /// + /// 距离 + /// + public string Distance { get; set; } + + + + /// + /// 预计到到时间 + /// + public string ExpectArriveDate { get; set; } + + } +} diff --git a/src/Znyc.Dispatching.Application/Order/Dto/OutPut/OrderOutput.cs b/src/Znyc.Dispatching.Application/Order/Dto/OutPut/OrderOutput.cs new file mode 100644 index 0000000..b5fc6ec --- /dev/null +++ b/src/Znyc.Dispatching.Application/Order/Dto/OutPut/OrderOutput.cs @@ -0,0 +1,225 @@ +using System; +using System.Collections.Generic; + +namespace Znyc.Dispatching.Application +{ + /// + /// + /// + public class OrderOutput + { + + /// + /// OrderId + /// + public long Id { get; set; } + + /// + /// 车辆Id + /// + public long VehicleId { get; set; } + + + /// + /// 车俩编号 + /// + public string VehicleCode { get; set; } + + + /// + /// 工程Id + /// + public long ProjectId { get; set; } + + + /// + /// 工程名称 + /// + public string ProjectName { get; set; } + + /// + /// 业务员Id + /// + public long SalesmanId { get; set; } + + /// + /// 到场时间 + /// + public DateTime ArriveDate { get; set; } + + /// + /// 经度 + /// + public decimal Longitude { get; set; } + + /// + /// 纬度 + /// + public decimal Latitude { get; set; } + + /// + /// 地址 + /// + public string Address { get; set; } + + + /// + /// 订单标题 + /// + public string OrderTitle { get; set; } + + + /// + /// 订单内容 + /// + public string OrderContent { get; set; } + + /// + /// 状态,,10=待指派,20=已指派,未接单,30=已接单,40=已出发,50=已完成(已签单),60=已离开,70=已评价 + /// + public int Status { get; set; } + + /// + /// 状态 + /// + public string StatusName { get; set; } + + /// + /// 订单来源,10=录入,20=报单 + /// + public int OrderSource { get; set; } + + /// + /// 订单来源 + /// + public string OrderSourceName { get; set; } + + ///// + ///// 指派时间 + ///// + //public DateTime? AssignDate { get; set; } + + ///// + ///// 指派人 + ///// + //public long AssignUserId { get; set; } + + ///// + ///// 接单时间 + ///// + //public DateTime? ReceiveDate { get; set; } + + ///// + ///// 出发时间 + ///// + //public DateTime? TravelDate { get; set; } + + + ///// + ///// 到达时间 + ///// + //public DateTime? AppearDate { get; set; } + + ///// + ///// 签单时间 + ///// + //public DateTime? SignDate { get; set; } + + + ///// + ///// 完成时间 + ///// + //public DateTime? CompleteDate { get; set; } + + ///// + ///// 评价时间 + ///// + //public DateTime? EvaluateDate { get; set; } + + ///// + ///// 离开时间 + ///// + //public DateTime? LeaveDate { get; set; } + + + /// + /// 车组人员 + /// + public string OrderVehiclePerson { get; set; } + + + public List> OrderStatusDates { get; set; } + + /// + /// 车组人员信息 + /// + public List OrderVehicles { get; set; } + + + /// + /// 签证明细 + /// + public List OrderVisas { get; set; } + + /// + /// 是否外请车 + /// + public bool IsOutside { get; set; } + + + /// + /// + /// + public long VehicleType { get; set; } + + + /// + /// + /// + public string VehicleTypeName { get; set; } + + /// + /// 距离 + /// + public string Distance { get; set; } + + + + /// + /// 预计到到时间 + /// + public string ExpectArriveDate { get; set; } + + + /// + /// 施工单位 + /// + public long ConstructionId { get; set; } + + /// + /// 施工单位 + /// + public string ConstructionName { get; set; } + /// + /// 到家时间 + /// + public DateTime? ArriveHomeDate { get; set; } + + } + + /// + /// + /// + public class OrderStatusDate + { + /// + /// + /// + public string StatusName { get; set; } + + /// + /// + /// + public DateTime? DateTime { get; set; } + } +} diff --git a/src/Znyc.Dispatching.Application/Order/Dto/OutPut/OrderStatisticsConstructionOutput.cs b/src/Znyc.Dispatching.Application/Order/Dto/OutPut/OrderStatisticsConstructionOutput.cs new file mode 100644 index 0000000..75f2da1 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Order/Dto/OutPut/OrderStatisticsConstructionOutput.cs @@ -0,0 +1,53 @@ + +using System.Collections.Generic; + +namespace Znyc.Dispatching.Application +{ + public class OrderStatisticsConstructionOutput + { + public long ConstructionId { get; set; } + + // public string ConstructionName { get; set; } + + // public long ProjectId { get; set; } + + public string ProjectName { get; set; } + /// + /// 派单量 + /// + public long AssignCount { get; set; } + /// + /// 完成量 + /// + public long CompleteCount { get; set; } + + /// + /// 服务评价 + /// + public double Evaluation { get; set; } + + } + + public class OrderStatisticsConstructionOutputs + { + public string ConstructionName { get; set; } + /// + /// 派单量 + /// + public long AssignCount { get; set; } + /// + /// 完成量 + /// + public long CompleteCount { get; set; } + + /// + /// 服务评价 + /// + public double Evaluation { get; set; } + + public List Projects { get; set; } + } + + + +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Order/Dto/OutPut/OrderStatisticsOutput.cs b/src/Znyc.Dispatching.Application/Order/Dto/OutPut/OrderStatisticsOutput.cs new file mode 100644 index 0000000..a21ec49 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Order/Dto/OutPut/OrderStatisticsOutput.cs @@ -0,0 +1,53 @@ + +namespace Znyc.Dispatching.Application +{ + public class OrderStatisticsOutput + { + public long VehicleId { get; set; } + + public long ProjectId { get; set; } + + public long UserId { get; set; } + + /// + /// 设备号 + /// + public string VehicleCode { get; set; } + + /// + /// 项目名称 + /// + public string ProjectName { get; set; } + + /// + /// 员工姓名 + /// + public string UserName { get; set; } + + /// + /// 派单量 + /// + public int AssignCount { get; set; } + + /// + /// 接单量 + /// + public int ReceiveCount { get; set; } + + /// + /// 完成量 + /// + public int CompleteCount { get; set; } + + /// + /// 服务评价 + /// + public double Evaluation { get; set; } + + #region V1.2.7 + + public long ConstructionId { get; set; } + + #endregion + } +} diff --git a/src/Znyc.Dispatching.Application/Order/Services/IOrderService.cs b/src/Znyc.Dispatching.Application/Order/Services/IOrderService.cs new file mode 100644 index 0000000..b963777 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Order/Services/IOrderService.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Znyc.Dispatching.Application +{ + public interface IOrderService + { + #region 回单查询 + + /// + /// 回单查询列表 + /// + /// 0全部,1未接单,2已结单,3已完成,4已评价 + /// + /// + /// + /// + /// + /// + Task> ReceiptPageAsync(DateTime startTime, DateTime endTime, int status = 0, int currentPage = 1, int pageSize = 10, string key = ""); + #endregion + + #region 任务统计 + /// + /// 任务统计--车辆 + /// + /// + /// + /// + /// + /// + Task> GetOrderStatisticsByVehicleAsync(DateTime startTime, DateTime endTime, int currentPage = 1, int pageSize = 10); + + /// + /// 任务统计--工程 + /// + /// + /// + /// + /// + /// + Task> GetOrderStatisticsByProjectAsync(DateTime startTime, DateTime endTime, int currentPage = 1, int pageSize = 10); + + /// + /// 任务统计--人员 + /// + /// + /// + /// + /// + /// + Task> GetOrderStatisticsByEmployeeAsync(DateTime startTime, DateTime endTime, int currentPage = 1, int pageSize = 10); + #endregion + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Order/Services/OrderService.cs b/src/Znyc.Dispatching.Application/Order/Services/OrderService.cs new file mode 100644 index 0000000..4964b6d --- /dev/null +++ b/src/Znyc.Dispatching.Application/Order/Services/OrderService.cs @@ -0,0 +1,1644 @@ +using Furion.ClayObject; +using Furion.DatabaseAccessor; +using Furion.DatabaseAccessor.Extensions; +using Furion.DependencyInjection; +using Furion.DynamicApiController; +using Furion.FriendlyException; +using Mapster; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Newtonsoft.Json; +using Senparc.Weixin.MP.AdvancedAPIs; +using Senparc.Weixin.MP.AdvancedAPIs.TemplateMessage; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Znyc.Dispatching.Common.Extensions; +using Znyc.Dispatching.Core; +using Znyc.Dispatching.Core.Entitys; +using Znyc.Dispatching.Core.Extension; +using Znyc.Dispatching.Core.Helpers; +using Znyc.Dispatching.MongoDb.Repository.Repositorys; +using Znyc.Dispatching.WeChat.Core.CommonService.TemplateMessage; + + +namespace Znyc.Dispatching.Application +{ + /// + /// 派工订单管理 + /// + [ApiDescriptionSettings(Name = "order", Order = 32)] + public class OrderService : IOrderService, IDynamicApiController, ITransient + { + private readonly ICacheService _cacheService; + private readonly IRepository _repository; + private readonly IUserManager _userManager; + private readonly IRepository _orderVehicleRepository; + private readonly IRepository _vehicleRepository; + private readonly IRepository _orderVisaRepository; + private readonly WeixinSettingOptions _weixinSettingOptions; + private readonly IRepository _wxUserRelationRepository; + private readonly IGpsRealTimeRepository _gpsRealTimeRepository; + private readonly IRepository _companyRepository; + private readonly IRepository _userRepository; + private readonly IEmployeeService _employeeService; + private readonly IRepository _vehiclePersonitory; + private readonly ILogger _logger; + private readonly IRepository _projectPersonRepository; + private readonly IRepository _vehicleTypeRepository; + private readonly IRepository _constructionRpository; + private readonly IRepository _employeeRepository; + private readonly IRepository _projectRepository; + + public OrderService( + IUserManager userManager, + ICacheService cacheService, + IRepository repository, + IRepository orderVisaRepository, + IRepository vehicleRepository, + IRepository orderVehicleRepository, + IOptions weixinSettingOptions, + IRepository wxUserRelationRepository, + IGpsRealTimeRepository gpsRealTimeRepository, + IRepository companyRepository, + IRepository userRepository, + IEmployeeService employeeService, + IRepository vehiclePersonitory, + ILogger logger, + IRepository projectPersonRepository, + IRepository vehicleTypeRepository, + IRepository constructionRpository, + IRepository employeeRepository, + IRepository projectRepository) + { + _repository = repository; + _userManager = userManager; + _cacheService = cacheService; + _vehicleRepository = vehicleRepository; + _orderVisaRepository = orderVisaRepository; + _orderVehicleRepository = orderVehicleRepository; + _weixinSettingOptions = weixinSettingOptions.Value; + _wxUserRelationRepository = wxUserRelationRepository; + _gpsRealTimeRepository = gpsRealTimeRepository; + _companyRepository = companyRepository; + _userRepository = userRepository; + _employeeService = employeeService; + _vehiclePersonitory = vehiclePersonitory; + _logger = logger; + _projectPersonRepository = projectPersonRepository; + _vehicleTypeRepository = vehicleTypeRepository; + _constructionRpository = constructionRpository; + _employeeRepository = employeeRepository; + _projectRepository = projectRepository; + } + + + #region 管理员/调度端/业务员端 + + /// + /// 管理端任务列表 + /// + /// + /// + /// + /// + /// + [HttpGet] + [Route("api/v1/admin/orders/search")] + public async Task PageAsync(int status = 0, int currentPage = 1, int pageSize = 10, string key = "") + { + + //所有订单 + var allOrders = _repository.Where(x => x.CompanyId == _userManager.CompanyId && !new int[] { (int)OrderStatus.Cancel }.Contains(x.Status)).ToList(); + + var orders = allOrders + .WhereIf(status == 1, x => x.Status == (int)OrderStatus.StayAssign) + .WhereIf(status == 2, x => new int[] { (int)OrderStatus.Assign, (int)OrderStatus.Receive, (int)OrderStatus.Travel, (int)OrderStatus.Appear }.Contains(x.Status)) + .WhereIf(status == 3, x => new int[] { (int)OrderStatus.Complete, (int)OrderStatus.Leave, (int)OrderStatus.Sign, (int)OrderStatus.ArriveHome }.Contains(x.Status)) + .WhereIf(status == 4, x => x.Status == (int)OrderStatus.Evaluate) + .WhereIf(new long[] { (long)RoleStatusEnum.Salesman, (long)RoleStatusEnum.PartTimeSalesman }.Contains(_userManager.RoleId), + x => x.CreatedUserId == _userManager.UserId || x.SalesmanId == _userManager.UserId) + .WhereIf(key.IsNotNull(), x => x.OrderVehiclePerson.Contains(key) || x.VehicleCode.Contains(key) || x.OrderContent.Contains(key) || x.ProjectName.Contains(key)) + .OrderByDescending(x => x.ArriveDate) + .Skip((currentPage - 1) * pageSize) + .Take(pageSize) + .ToList().Adapt>(); + + + if (orders.IsNotNull() && orders.Count > 0) + { + //查询车组司机列表 + var orderVehicles = await _orderVehicleRepository.Where(x => orders.Select(x => x.Id).Contains(x.OrderId) && x.IsDriver == true).ToListAsync(); + var users = await _userRepository.Where(x => orders.Select(x => x.VehicleId).Contains(x.Id)).ToListAsync(); + //查询所有车型 + var vehicleTypes = _vehicleTypeRepository.DetachedEntities.Where(x => x.CompanyId == _userManager.CompanyId).ToList(); + //车辆Gps + var gpsRealTimes = await _gpsRealTimeRepository.GetGpsRealTimeByCompanyId(_userManager.CompanyId); + + foreach (var order in orders) + { + //外请车,车辆Id等于UserId + if (order.IsOutside) + { + if (users.IsNotNull() && users.Count > 0) + { + order.OrderVehicleDriverName = users.Where(x => x.Id == order.VehicleId).FirstOrDefault()?.UserName; + order.OrderVehicleDriverPhone = users.Where(x => x.Id == order.VehicleId).FirstOrDefault()?.Phone; + } + } + else + { + if (orderVehicles.IsNotNull() && orderVehicles.Count > 0) + { + order.OrderVehicleDriverName = orderVehicles.Where(x => x.OrderId == order.Id).FirstOrDefault()?.UserName; + order.OrderVehicleDriverPhone = orderVehicles.Where(x => x.OrderId == order.Id).FirstOrDefault()?.UserPhone; + } + } + order.StatusName = typeof(OrderStatus).GetDescription(order.Status); + string vehicleTypeName = ""; + if (order.VehicleType > 0) + { + vehicleTypeName = vehicleTypes.Where(x => x.Id == order.VehicleType).FirstOrDefault()?.Name; + } + order.VehicleTypeName = vehicleTypeName; + order.OrderTitle = order.ProjectName.IsNull() ? (order.OrderContent.Length >= 13 ? order.OrderContent.Substring(0, 13).Trim().ToString() : order.OrderContent.Trim()) : (order.ProjectName.Length >= 13 ? order.ProjectName.Substring(0, 13).Trim().ToString() : order.ProjectName.Trim()); + + //查询当前车辆是否有任务 + try + { + if (new long[] { (long)OrderStatus.Receive, (long)OrderStatus.Travel }.Contains(order.Status)) + { + var gpsRealTime = gpsRealTimes.FirstOrDefault(x => x.VehicleId == order.VehicleId); + string origin = (double)gpsRealTime.Longitude + "," + (double)gpsRealTime.Latitude; + string destination = (double)order.Longitude + "," + (double)order.Latitude; + var pathPlanningModel = MapHelper.GetPathPlanningByDriving(origin, destination); + if (pathPlanningModel.status == "1" && pathPlanningModel.info == "OK") + { + order.Distance = $"{ Convert.ToDouble(pathPlanningModel.route.paths[0].distance) / 1000}KM"; + order.ExpectArriveDate = DateTimeHelper.ToChineseDate(Convert.ToInt32(pathPlanningModel.route.paths[0].duration)); + } + } + } + catch (Exception ex) + { + Console.WriteLine($"GetMapMonitorAsync:{ex.StackTrace}"); + } + + + } + } + + var adminOrderListOutput = new AdminOrderListOutput + { + PagedList = new PagedList + { + Items = orders, + TotalCount = orders.Count + }, + StayAssignCount = allOrders.Count(x => x.Status == (int)OrderStatus.StayAssign), + AssignCount = allOrders.Count(x => new int[] { (int)OrderStatus.Assign, (int)OrderStatus.Receive, (int)OrderStatus.Travel, (int)OrderStatus.Appear }.Contains(x.Status)), + CompleteCount = allOrders.Count(x => new int[] { (int)OrderStatus.Complete, (int)OrderStatus.Sign, (int)OrderStatus.Leave, (int)OrderStatus.ArriveHome }.Contains(x.Status)), + EvaluateCount = allOrders.Count(x => x.Status == (int)OrderStatus.Evaluate) + }; + return adminOrderListOutput; + } + + + + /// + /// 保存待指派 + /// + /// + /// + [HttpPost] + [Route("api/v1/order/stayassign")] + public async Task StayAssignAsync(StayAssignInput stayAssign) + { + var order = await _repository.FindOrDefaultAsync(stayAssign.Id); + long constructionId = 0; + long projectId = stayAssign.ProjectId; + if (order.IsNotNull()) + { + + //工程名称是否存在,不存在新增,返回工程信息 + if (stayAssign.ProjectId == 0) + { + projectId = await GetProjectId(stayAssign.Latitude, stayAssign.Longitude, stayAssign.Address, stayAssign.ProjectName); + } + + constructionId = stayAssign.ConstructionId; + //施工单位是否存在,不存在新增,返回工程信息 + if (stayAssign.ConstructionName.IsNotEmptyOrNull()) + { + constructionId = await GetConstructionId(stayAssign.ConstructionId, stayAssign.ConstructionName); + } + + order.ProjectId = projectId; + order.ProjectName = stayAssign.ProjectName; + order.ArriveDate = stayAssign.ArriveDate; + order.Longitude = stayAssign.Longitude; + order.Latitude = stayAssign.Latitude; + order.Address = stayAssign.Address; + order.ConstructionId = constructionId; + order.VehicleType = stayAssign.VehicleType; + order.OrderContent = HtmlHelper.ReplaceHtmlMark(stayAssign.OrderContent); + order.VehicleCode = ""; + order.OrderVehiclePerson = ""; + await _repository.UpdateNowAsync(order); + } + else + { + order = stayAssign.Adapt(); + //工程名称是否存在,不存在新增,返回工程信息 + if (stayAssign.ProjectId == 0) + { + projectId = await GetProjectId(stayAssign.Latitude, stayAssign.Longitude, stayAssign.Address, stayAssign.ProjectName); + } + + constructionId = stayAssign.ConstructionId; + //施工单位是否存在,不存在新增,返回工程信息 + if (stayAssign.ConstructionName.IsNotEmptyOrNull()) + { + constructionId = await GetConstructionId(stayAssign.ConstructionId, stayAssign.ConstructionName); + } + order.CompanyId = _userManager.CompanyId; + order.Status = (int)OrderStatus.StayAssign; + order.OrderSource = (int)OrderSourceEnum.Input; + order.ArriveDate = stayAssign.ArriveDate; + order.ConstructionId = constructionId; + order.OrderContent = HtmlHelper.ReplaceHtmlMark(stayAssign.OrderContent); + order.Address = stayAssign.Address.IsNull() ? "" : stayAssign.Address; + order.ProjectId = projectId; + order.VehicleCode = ""; + order.OrderVehiclePerson = ""; + var orderId = (await _repository.InsertNowAsync(order)).Entity.Id; + if (orderId <= 0) + { + throw Oops.Bah($"任务保存失败"); + } + } + await InsertProjectPersonAsync(stayAssign.OrderContent, projectId, constructionId); + await UpdateProjectAddressAsync(projectId, stayAssign.Latitude, stayAssign.Longitude, stayAssign.Address); + } + + + + + /// + /// 指派订单 + /// + /// + /// + [HttpPost] + [UnitOfWork] + [Route("api/v1/order/assign")] + public async Task AssignAsync(AssignOrderInput assign) + { + + var order = await _repository.FindOrDefaultAsync(assign.Id); + long constructionId = 0; + //是否更改工程 + bool isChangeProject = false; + //施工更改订单内容 + bool isChangeOrderContent = false; + //是否更改到场时间 + bool isChangeArrive = false; + //是否更改任务车型 + bool isChangeVehicleType = false; + //是否更改施工地点 + bool isChangeAddress = false; + //是否更改指派车辆 + bool isChangeVehicle = false; + //是否更改人员 + bool isChangePersonnel = false; + + bool isChange = false; + + #region order + + long projectId = assign.ProjectId; + //工程名称是否存在,不存在新增,返回工程信息 + if (assign.ProjectId == 0) + { + projectId = await GetProjectId(assign.Latitude, assign.Longitude, assign.Address, assign.ProjectName); + } + constructionId = assign.ConstructionId; + //施工单位是否存在,不存在新增,返回工程信息 + if (assign.ConstructionName.IsNotEmptyOrNull()) + { + constructionId = await GetConstructionId(assign.ConstructionId, assign.ConstructionName); + } + //未保存直接指派 + if (order.IsNull()) + { + var entity = new Order(); + entity = assign.Adapt(); + entity.Status = (int)OrderStatus.Assign; + entity.CompanyId = _userManager.CompanyId; + entity.OrderSource = (int)OrderSourceEnum.Input; + entity.AssignDate = DateTime.Now; + entity.AssignUserId = _userManager.UserId; + entity.VehicleId = assign.VehicleId; + entity.VehicleCode = assign.VehicleCode; + entity.IsOutside = assign.IsOutside; + entity.VehicleType = assign.VehicleType; + entity.OrderContent = HtmlHelper.ReplaceHtmlMark(assign.OrderContent); + entity.ConstructionId = constructionId; + entity.ProjectId = projectId; + entity.VehicleCode = assign.VehicleCode; + entity.OrderVehiclePerson = assign.OrderVehiclePerson; + order = (await _repository.InsertNowAsync(entity)).Entity; + if (order.Id <= 0) + { + throw Oops.Bah($"指派失败"); + } + + var orderContent = assign.OrderContent.Length > 45 ? HtmlHelper.ReplaceHtmlMark((assign.OrderContent.Substring(0, 45))) + "..." : HtmlHelper.ReplaceHtmlMark(assign.OrderContent) + "..."; + if (assign.IsOutside) + { + var wxUser = _wxUserRelationRepository.Where(x => x.UserId == assign.VehicleId).FirstOrDefault(); + if (wxUser.IsNotNull() && wxUser.WxOfficialOpenId.IsNotEmptyOrNull()) + { + var company = await _companyRepository.Where(x => x.Id == _userManager.CompanyId).FirstOrDefaultAsync(); + var user = await _userRepository.Where(x => x.Id == _userManager.UserId).FirstOrDefaultAsync(); + //消息推送 + var data = new WeixinTemplate_AssignOrder("同行叫车通知", assign.ProjectName, "同行叫车", orderContent, assign.ArriveDate.ToString("yyyy-MM-dd HH:mm:ss"), $"{company.CompanyName}公司请车,请点击接单并准时出车,谢谢!"); + TemplateModel_MiniProgram miniProgram = new TemplateModel_MiniProgram(); + miniProgram.appid = _weixinSettingOptions.WxOpenAppId; + miniProgram.pagepath = $"pages/taskInfo/taskInfo?id={order.Id}&isDriver=false&type=1&isOutSide=true"; + var result = await TemplateApi.SendTemplateMessageAsync(_weixinSettingOptions.WeixinAppId, wxUser.WxOfficialOpenId, data.TemplateId, "pages/index/index", data, miniProgram); + Console.WriteLine($"{miniProgram.pagepath}"); + Console.WriteLine($"未保存直接指派{JsonConvert.SerializeObject(result)}"); + } + } + else + { + + var orderVehicles = assign.OrderVehicles.Adapt>(); + + foreach (var oVehicle in orderVehicles) + { + var orderVehicle = (await _orderVehicleRepository.InsertNowAsync(oVehicle)).Entity; + if (orderVehicle.Id > 0) + { + orderVehicle.OrderId = order.Id; + await _orderVehicleRepository.UpdateNowAsync(orderVehicle); + var wxUser = _wxUserRelationRepository.Where(x => x.UserId == orderVehicle.UserId).FirstOrDefault(); + if (wxUser.IsNotNull() && wxUser.WxOfficialOpenId.IsNotEmptyOrNull()) + { + //消息推送 + var data = new WeixinTemplate_AssignOrder("出车任务通知", assign.ProjectName, "出车任务", HtmlHelper.ReplaceHtmlTag(orderContent), assign.ArriveDate.ToString("yyyy-MM-dd HH:mm:ss"), "请点击接单并准时出车"); + TemplateModel_MiniProgram miniProgram = new TemplateModel_MiniProgram(); + miniProgram.appid = _weixinSettingOptions.WxOpenAppId; + miniProgram.pagepath = $"pages/taskInfo/taskInfo?id={order.Id}&isDriver={orderVehicle.IsDriver.ToString().ToLower()}&type=1"; + var result = await TemplateApi.SendTemplateMessageAsync(_weixinSettingOptions.WeixinAppId, wxUser.WxOfficialOpenId, data.TemplateId, "pages/index/index", data, miniProgram); + Console.WriteLine($"{miniProgram.pagepath}"); + Console.WriteLine($"保存直接指派{JsonConvert.SerializeObject(result)}"); + + } + + } + } + } + } + //保存后指派 + else + { + + var orderContent = HtmlHelper.ReplaceHtmlMark(assign.OrderContent); + //定义消息通知message + string message = ""; + if (order.ProjectId != assign.ProjectId && order.ProjectId > 0) + { + isChangeProject = true; + isChange = true; + message += "[项目名称]"; + } + if (order.ArriveDate != assign.ArriveDate) + { + isChangeArrive = true; + isChange = true; + message += "[到场时间]"; + } + if (order.OrderContent != orderContent) + { + isChangeOrderContent = true; + isChange = true; + message += "[任务内容]"; + + } + if (order.VehicleType != assign.VehicleType && order.VehicleType > 0) + { + isChangeVehicleType = true; + isChange = true; + message += "[任务车型]"; + + } + if (order.Address != assign.Address) + { + isChangeAddress = true; + isChange = true; + message += "[施工地点]"; + + } + if (order.VehicleId != assign.VehicleId && order.VehicleId>0) + { + isChangeVehicle = true; + isChange = true; + message += "[出车车辆]"; + + } + if (order.Status <= 20) + { + order.Status = (int)OrderStatus.Assign; + } + order.ProjectId = assign.ProjectId; + order.ProjectName = assign.ProjectName; + order.AssignDate = DateTime.Now; + order.Latitude = assign.Latitude; + order.Longitude = assign.Longitude; + order.Address = assign.Address; + order.OrderContent = orderContent; + order.CompanyId = _userManager.CompanyId; + order.OrderSource = (int)OrderSourceEnum.Input; + order.AssignUserId = _userManager.UserId; + order.VehicleId = assign.VehicleId; + order.VehicleCode = assign.VehicleCode; + order.IsOutside = assign.IsOutside; + order.VehicleType = assign.VehicleType; + order.OrderVehiclePerson = assign.OrderVehiclePerson; + order.ArriveDate = assign.ArriveDate; + order.ConstructionId = constructionId; + order.ProjectId = projectId; + await _repository.UpdateNowAsync(order); + var newOrderVehicles = assign.OrderVehicles.Adapt>(); + var oldOrderVehicles = _orderVehicleRepository.Where(x => x.OrderId == order.Id).ToList(); + orderContent = assign.OrderContent.Length > 45 ? HtmlHelper.ReplaceHtmlMark(assign.OrderContent.Substring(0, 45)) + "..." : HtmlHelper.ReplaceHtmlMark(assign.OrderContent) + "..."; + + if (order.IsOutside) + { + var wxUser = _wxUserRelationRepository.Where(x => x.UserId == assign.VehicleId).FirstOrDefault(); + if (wxUser.IsNotNull() && wxUser.WxOfficialOpenId.IsNotEmptyOrNull()) + { + var company = await _companyRepository.Where(x => x.Id == _userManager.CompanyId).FirstOrDefaultAsync(); + var user = await _userRepository.Where(x => x.Id == _userManager.UserId).FirstOrDefaultAsync(); + //消息推送 + var data = new WeixinTemplate_AssignOrder("同行叫车通知", assign.ProjectName, "同行叫车", orderContent, assign.ArriveDate.ToString("yyyy-MM-dd HH:mm:ss"), $"{company.CompanyName}公司请车,请点击接单并准时出车,谢谢!"); + TemplateModel_MiniProgram miniProgram = new TemplateModel_MiniProgram(); + miniProgram.appid = _weixinSettingOptions.WxOpenAppId; + miniProgram.pagepath = $"pages/taskInfo/taskInfo?id={order.Id}&isDriver=false&type=1&isOutSide=true"; + var result = await TemplateApi.SendTemplateMessageAsync(_weixinSettingOptions.WeixinAppId, wxUser.WxOfficialOpenId, data.TemplateId, "pages/index/index", data, miniProgram); + Console.WriteLine($"{miniProgram.pagepath}"); + Console.WriteLine($"未保存直接指派{JsonConvert.SerializeObject(result)}"); + } + } + + //需撤销的list + var revokeOldOrderVehicles = oldOrderVehicles.Except(newOrderVehicles, new OrderVehicleCompara()).ToList(); + //是否替换司机 + isChangePersonnel= revokeOldOrderVehicles.Any(x => x.IsDriver); + foreach (var revokeOldOrderVehicle in revokeOldOrderVehicles) + { + + await _orderVehicleRepository.DeleteNowAsync(revokeOldOrderVehicle); + var wxUser = _wxUserRelationRepository.Where(x => x.UserId == revokeOldOrderVehicle.UserId).FirstOrDefault(); + if (wxUser.IsNotNull() && wxUser.WxOfficialOpenId.IsNotNull()) + { + var data = new WeixinTemplate_AssignOrder("出车任务撤销通知", order.ProjectName, "出车撤销", orderContent, order.ArriveDate.ToString("yyyy-MM-dd HH:mm:ss"), "您的出车任务已被取消,请点击查看"); + TemplateModel_MiniProgram miniProgram = new TemplateModel_MiniProgram(); + miniProgram.appid = _weixinSettingOptions.WxOpenAppId; + miniProgram.pagepath = $"pages/taskInfo/taskInfo?id={order.Id}&isDriver={revokeOldOrderVehicle.IsDriver.ToString().ToLower()}&type=1"; + var result = await TemplateApi.SendTemplateMessageAsync(_weixinSettingOptions.WeixinAppId, wxUser.WxOfficialOpenId, data.TemplateId, "pages/index/index", data, miniProgram); + Console.WriteLine($"{miniProgram.pagepath}"); + Console.WriteLine($"出车任务撤销通知{JsonConvert.SerializeObject(result)}"); + + } + + } + + //需新增的list + var newlyOrderVehicles = newOrderVehicles.Except(oldOrderVehicles, new OrderVehicleCompara()).ToList(); + foreach (var newlyOrderVehicle in newlyOrderVehicles) + { + var newVehicleId = (await _orderVehicleRepository.InsertNowAsync(newlyOrderVehicle)).Entity.Id; + if (newVehicleId > 0) + { + newlyOrderVehicle.OrderId = order.Id; + await _orderVehicleRepository.UpdateNowAsync(newlyOrderVehicle); + var wxUser = _wxUserRelationRepository.Where(x => x.UserId == newlyOrderVehicle.UserId).FirstOrDefault(); + if (wxUser.IsNotNull() && wxUser.WxOfficialOpenId.IsNotEmptyOrNull()) + { + ////消息推送 + var data = new WeixinTemplate_AssignOrder("出车任务通知", order.ProjectName, "出车任务", orderContent, order.ArriveDate.ToString("yyyy-MM-dd HH:mm:ss"), "请点击接单并准时出车"); + TemplateModel_MiniProgram miniProgram = new TemplateModel_MiniProgram(); + miniProgram.appid = _weixinSettingOptions.WxOpenAppId; + miniProgram.pagepath = $"pages/taskInfo/taskInfo?id={order.Id}&isDriver={newlyOrderVehicle.IsDriver.ToString().ToLower()}&type=1"; + var result = await TemplateApi.SendTemplateMessageAsync(_weixinSettingOptions.WeixinAppId, wxUser.WxOfficialOpenId, data.TemplateId, "pages/index/index", data, miniProgram); + Console.WriteLine($"{miniProgram.pagepath}"); + Console.WriteLine($"出车任务通知{JsonConvert.SerializeObject(result)}"); + // isChangePersonnel = true; + + } + + } + } + + //更改出车人员变更任务状态 + if (isChangePersonnel) + { + await "update dc_order set Status=20,ReceiveDate=null where id=@id".SqlNonQueryAsync(new { id = order.Id }); + } + string message2 = message; + if (isChange) + { + var orderVehicles = _orderVehicleRepository.Where(x => x.OrderId == order.Id).ToList(); + foreach (var orderVehicle in orderVehicles) + { + var wxUser = _wxUserRelationRepository.Where(x => x.UserId == orderVehicle.UserId).FirstOrDefault(); + if (wxUser.IsNotNull() && wxUser.WxOfficialOpenId.IsNotEmptyOrNull()) + { + var data = new WeixinTemplate_AssignOrder($"出车任务变更通知", order.ProjectName, "出车任务变更", orderContent, order.ArriveDate.ToString("yyyy-MM-dd HH:mm:ss"), message += "有调整,请点击查看并相互告知"); + TemplateModel_MiniProgram miniProgram = new TemplateModel_MiniProgram(); + miniProgram.appid = _weixinSettingOptions.WxOpenAppId; + miniProgram.pagepath = $"pages/taskInfo/taskInfo?id={order.Id}&isDriver={orderVehicle.IsDriver.ToString().ToLower()}&type=1"; + var result = await TemplateApi.SendTemplateMessageAsync(_weixinSettingOptions.WeixinAppId, wxUser.WxOfficialOpenId, data.TemplateId, "pages/index/index", data, miniProgram); + } + } + await _cacheService.SetOrderReadAsync(order.Id, message2); + }; + } + { + //新增工地联系人 + await InsertProjectPersonAsync(assign.OrderContent, projectId, constructionId); + await UpdateProjectAddressAsync(projectId, assign.Latitude, assign.Longitude, assign.Address); + + //同步车组人员 + if (assign.IsReplaceOrderVehicle) + { + var orderVehicles = await _orderVehicleRepository.Where(x => x.OrderId == order.Id).ToListAsync(); + await _vehiclePersonitory.Context.DeleteRangeAsync(b => b.VehicleId == order.VehicleId); ; + foreach (var orderVehicle in orderVehicles) + { + var vehiclePerson = new VehiclePerson() + { + VehicleId = order.VehicleId, + UserId = orderVehicle.UserId, + UserName = orderVehicle.UserName, + UserPhone = orderVehicle.UserPhone, + IsDriver = orderVehicle.IsDriver + + }; + await _vehiclePersonitory.InsertNowAsync(vehiclePerson); + } + await _cacheService.RemoveVehiclePersonAsync(order.VehicleId); + } + } + + #endregion + } + + + + + /// + /// 完成订单 + /// + /// + /// + [HttpGet] + [Route("api/v1/order/complete/{orderId}")] + public async Task CompleteAsync(long orderId) + { + var order = await _repository.FindOrDefaultAsync(orderId); + if (order.IsNotNull()) + { + var orderContent = order.OrderContent.Length > 45 ? HtmlHelper.ReplaceHtmlMark(order.OrderContent.Substring(0, 45)) + "..." : HtmlHelper.ReplaceHtmlMark(order.OrderContent) + "..."; + + if (_userManager.SuperAdmin) + { + order.CompleteDate = DateTime.Now; + order.Status = (int)OrderStatus.Complete; + await _repository.UpdateNowAsync(order); + + //推送至所有车组人员已完工通知 + var orderVehicles = _orderVehicleRepository.Where(x => x.OrderId == order.Id).ToList(); + if (orderVehicles.IsNotNull() && orderVehicles.Count > 0) + { + foreach (var orderVehicle in orderVehicles) + { + var wxUser = _wxUserRelationRepository.Where(x => x.UserId == orderVehicle.UserId).FirstOrDefault(); + if (wxUser.IsNotNull() && wxUser.WxOfficialOpenId.IsNotNull()) + { + var data = new WeixinTemplate_AssignOrder("已完工通知", order.ProjectName, "已完工", orderContent, order.ArriveDate.ToString("yyyy-MM-dd HH:mm:ss"), "恭喜任务已完成!返回时记得交单哦,辛苦 了~"); + TemplateModel_MiniProgram miniProgram = new TemplateModel_MiniProgram(); + miniProgram.appid = _weixinSettingOptions.WxOpenAppId; + miniProgram.pagepath = $"pages/taskInfo/taskInfo?id={order.Id}&isDriver={orderVehicle.IsDriver.ToString().ToLower()}&type=1"; + var result = await TemplateApi.SendTemplateMessageAsync(_weixinSettingOptions.WeixinAppId, wxUser.WxOfficialOpenId, data.TemplateId, "pages/index/index", data, miniProgram); + Console.WriteLine($"{miniProgram.pagepath}"); + Console.WriteLine($"已完工通知{JsonConvert.SerializeObject(result)}"); + } + } + } + { + //推送至派单人 + var wxUser = _wxUserRelationRepository.Where(x => x.UserId == order.AssignUserId).FirstOrDefault(); + if (wxUser.IsNotNull() && wxUser.WxOfficialOpenId.IsNotNull()) + { + var data = new WeixinTemplate_AssignOrder("已完工通知", order.ProjectName, "已完工", orderContent, order.ArriveDate.ToString("yyyy-MM-dd HH:mm:ss"), "恭喜任务已完成!返回时记得交单哦,辛苦 了~"); + TemplateModel_MiniProgram miniProgram = new TemplateModel_MiniProgram(); + miniProgram.appid = _weixinSettingOptions.WxOpenAppId; + miniProgram.pagepath = $"pages/taskInfo/taskInfo?id={order.Id}&isDriver=false&type=2"; + var result = await TemplateApi.SendTemplateMessageAsync(_weixinSettingOptions.WeixinAppId, wxUser.WxOfficialOpenId, data.TemplateId, "pages/index/index", data, miniProgram); + Console.WriteLine($"{miniProgram.pagepath}"); + Console.WriteLine($"已完工通知{JsonConvert.SerializeObject(result)}"); + } + } + + } + else + { + throw Oops.Bah("暂无权限"); + } + } + else + { + throw Oops.Bah("订单不存在"); + } + } + + + /// + /// 删除订单 + /// + /// + /// + [HttpDelete] + [Route("api/v1/order/{orderId}")] + public async Task DeleteAsync(long orderId) + { + var order = await _repository.FindOrDefaultAsync(orderId); + if (order.IsNotNull()) + { + if (_userManager.SuperAdmin) + { + order.IsDeleted = true; + await _repository.UpdateNowAsync(order); + } + else + { + throw Oops.Bah("暂无权限"); + } + } + else + { + throw Oops.Bah("订单不存在"); + } + } + + + /// + /// 撤销订单 + /// + /// + /// + [HttpPut] + [Route("api/v1/cancel/order/{orderId}")] + public async Task CancelAsync(long orderId) + { + var order = await _repository.FindOrDefaultAsync(orderId); + if (order.IsNotNull()) + { + var orderContent = order.OrderContent.Length > 45 ? HtmlHelper.ReplaceHtmlMark(order.OrderContent.Substring(0, 45)) + "..." : HtmlHelper.ReplaceHtmlMark(order.OrderContent) + "..."; + + if (_userManager.SuperAdmin) + { + order.Status = (int)OrderStatus.Cancel; + await _repository.UpdateNowAsync(order); + + //推送至所有车组人员已完工通知 + var orderVehicles = _orderVehicleRepository.Where(x => x.OrderId == order.Id).ToList(); + if (orderVehicles.IsNotNull() && orderVehicles.Count > 0) + { + foreach (var orderVehicle in orderVehicles) + { + var wxUser = _wxUserRelationRepository.Where(x => x.UserId == orderVehicle.UserId).FirstOrDefault(); + if (wxUser.IsNotNull() && wxUser.WxOfficialOpenId.IsNotNull()) + { + var data = new WeixinTemplate_AssignOrder("出车任务撤销通知", order.ProjectName, "出车任务撤销", orderContent, order.ArriveDate.ToString("yyyy-MM-dd HH:mm:ss"), "出车任务已撤销,请悉知并抓紧时间返回!"); + TemplateModel_MiniProgram miniProgram = new TemplateModel_MiniProgram(); + miniProgram.appid = _weixinSettingOptions.WxOpenAppId; + miniProgram.pagepath = $"pages/taskInfo/taskInfo?id={order.Id}&isDriver={orderVehicle.IsDriver.ToString().ToLower()}&type=1"; + var result = await TemplateApi.SendTemplateMessageAsync(_weixinSettingOptions.WeixinAppId, wxUser.WxOfficialOpenId, data.TemplateId, "pages/index/index", data, miniProgram); + Console.WriteLine($"{miniProgram.pagepath}"); + Console.WriteLine($"出车任务撤销通知{JsonConvert.SerializeObject(result)}"); + + } + } + } + } + else + { + throw Oops.Bah("暂无权限"); + } + } + else + { + throw Oops.Bah("订单不存在"); + } + } + + + + #endregion + + #region 司机端 + + /// + /// 接单 + /// + /// + /// + [HttpGet] + [Route("api/v1/order/receive/{orderId}")] + public async Task ReceiveAsync(long orderId) + { + var order = await _repository.FindOrDefaultAsync(orderId); + if (order.IsNotNull()) + { + var orderContent = order.OrderContent.Length > 45 ? HtmlHelper.ReplaceHtmlMark(order.OrderContent.Substring(0, 45)) + "..." : HtmlHelper.ReplaceHtmlMark(order.OrderContent) + "..."; + var isDriver = _orderVehicleRepository.Any(x => x.OrderId == orderId && x.IsDriver == true && x.UserId == _userManager.UserId); + if (isDriver) + { + order.ReceiveDate = DateTime.Now; + order.Status = (int)OrderStatus.Receive; + //存储当前定位信息 + var vehicleGps = await _gpsRealTimeRepository.GetGpsRealTimeByVehicleId(order.VehicleId); + if (vehicleGps.IsNotNull()) + { + dynamic clay = Clay.Object(new + { + Latitude = vehicleGps.Latitude, + Longitude = vehicleGps.Longitude + }); + await _cacheService.SetVehicleGpsAsync(orderId, clay); + } + await _repository.UpdateNowAsync(order); + //推送完工提交签证单通知给司机 + var wxUser = _wxUserRelationRepository.Where(x => x.UserId == _userManager.UserId).FirstOrDefault(); + if (wxUser.IsNotNull() && wxUser.WxOfficialOpenId.IsNotNull()) + { + var data = new WeixinTemplate_AssignOrder("完工提交签证单通知", order.ProjectName, "完工提交签证单", orderContent, order.ArriveDate.ToString("yyyy -MM-dd HH:mm:ss"), "完工后请点击这里,拍照回传签证单!"); + TemplateModel_MiniProgram miniProgram = new TemplateModel_MiniProgram(); + miniProgram.appid = _weixinSettingOptions.WxOpenAppId; + miniProgram.pagepath = $"pages/taskInfo/taskInfo?id={order.Id}&isDriver=true&type=1"; + var result = await TemplateApi.SendTemplateMessageAsync(_weixinSettingOptions.WeixinAppId, wxUser.WxOfficialOpenId, data.TemplateId, "pages/index/index", data, miniProgram); + Console.WriteLine($"{miniProgram.pagepath}"); + Console.WriteLine($"完工提交签证单通知{JsonConvert.SerializeObject(result)}"); + + } + //v1.2.7 推送至派单调度以及管理员 + { + //管理员 + var adminUserIds = await _employeeRepository.DetachedEntities.Where(x => x.CompanyId == _userManager.CompanyId && x.RoleId == (int)RoleStatusEnum.Administrator && x.Status == 1 && !x.IsDeleted).Select(x => x.UserId).ToListAsync(); + //派单人 + adminUserIds.Add(order.AssignUserId); + //获取订单所有车组人员信息 + var orderVehicles = await _orderVehicleRepository.DetachedEntities.Where(x => x.OrderId == order.Id).ToListAsync(); + var orderVehicleDriverName = orderVehicles.FirstOrDefault(x => x.IsDriver).UserName; + var orderVehicleCrewsName = string.Join(",", orderVehicles.Where(x => !x.IsDriver).Select(x => x.UserName).ToList()); + foreach (var adminUserId in adminUserIds) + { + var wxAdminUser = _wxUserRelationRepository.Where(x => x.UserId == adminUserId).FirstOrDefault(); + if (wxAdminUser.IsNotNull() && wxAdminUser.WxOfficialOpenId.IsNotNull()) + { + var data = new WeixinTemplate_AssignOrder("已接单提醒", order.ProjectName, "出车任务", orderContent, order.ArriveDate.ToString("yyyy -MM-dd HH:mm:ss"), $"[{order.VehicleCode}]号车,{orderVehicleDriverName}已接单,{orderVehicleCrewsName}已阅读"); + TemplateModel_MiniProgram miniProgram = new TemplateModel_MiniProgram(); + miniProgram.appid = _weixinSettingOptions.WxOpenAppId; + miniProgram.pagepath = $"pages/taskInfo/taskInfo?id={order.Id}&isDriver=false&type=2"; + var result = await TemplateApi.SendTemplateMessageAsync(_weixinSettingOptions.WeixinAppId, wxAdminUser.WxOfficialOpenId, data.TemplateId, "pages/index/index", data, miniProgram); + Console.WriteLine($"{miniProgram.pagepath}"); + Console.WriteLine($"已接单提醒{JsonConvert.SerializeObject(result)}"); + } + } + } + //v1.2.8推送变更提醒 + { + var message = await _cacheService.GetOrderReadAsync(order.Id); + if (message.IsNotEmptyOrNull()) + { + //管理员 + var adminUserIds = await _employeeRepository.DetachedEntities.Where(x => x.CompanyId == _userManager.CompanyId && x.RoleId == (int)RoleStatusEnum.Administrator && x.Status == 1 && !x.IsDeleted).Select(x => x.UserId).ToListAsync(); + //派单人 + adminUserIds.Add(order.AssignUserId); + //业务员 + if (order.SalesmanId > 0) adminUserIds.Add(order.SalesmanId); + //获取订单所有车组人员信息 + var orderVehicles = await _orderVehicleRepository.DetachedEntities.Where(x => x.OrderId == order.Id).ToListAsync(); + var orderVehicleDriverName = orderVehicles.FirstOrDefault(x => x.IsDriver).UserName; + var orderVehicleCrewsName = string.Join(",", orderVehicles.Where(x => !x.IsDriver).Select(x => x.UserName).ToList()); + foreach (var adminUserId in adminUserIds) + { + var wxAdminUser = _wxUserRelationRepository.Where(x => x.UserId == adminUserId).FirstOrDefault(); + if (wxAdminUser.IsNotNull() && wxAdminUser.WxOfficialOpenId.IsNotNull()) + { + var data = new WeixinTemplate_AssignOrder("已接单提醒", order.ProjectName, "出车任务", orderContent, order.ArriveDate.ToString("yyyy -MM-dd HH:mm:ss"), $"{message}变更通知,[{order.VehicleCode}]车,{orderVehicleDriverName},{orderVehicleCrewsName}已收到已查看"); + TemplateModel_MiniProgram miniProgram = new TemplateModel_MiniProgram(); + miniProgram.appid = _weixinSettingOptions.WxOpenAppId; + miniProgram.pagepath = $"pages/taskInfo/taskInfo?id={order.Id}&isDriver=false&type=2"; + var result = await TemplateApi.SendTemplateMessageAsync(_weixinSettingOptions.WeixinAppId, wxAdminUser.WxOfficialOpenId, data.TemplateId, "pages/index/index", data, miniProgram); + Console.WriteLine($"{miniProgram.pagepath}"); + Console.WriteLine($"已接单提醒{JsonConvert.SerializeObject(result)}"); + } + } + await _cacheService.RemoveOrderReadAsync(order.Id); + + } + } + } + else + { + throw Oops.Bah("暂无权限"); + } + } + else + { + throw Oops.Bah("订单不存在"); + } + } + + /// + /// 签单 + /// + /// + /// + [HttpPost] + [UnitOfWork] + [Route("api/v1/order/sign")] + public async Task SignAsync(SignInput sign) + { + + var order = await _repository.FindOrDefaultAsync(sign.OrderId); + if (order.IsNotNull()) + { + var orderContent = order.OrderContent.Length > 45 ? HtmlHelper.ReplaceHtmlMark(order.OrderContent.Substring(0, 45)) + "..." : HtmlHelper.ReplaceHtmlMark(order.OrderContent) + "..."; + + var isDriver = _orderVehicleRepository.Any(x => x.OrderId == sign.OrderId && x.IsDriver == true); + if (isDriver) + { + order.SignDate = DateTime.Now; + order.Status = (int)OrderStatus.Sign; + await _repository.UpdateNowAsync(order); + //推送通知 + var ordervisa = await _orderVisaRepository.Where(x => x.OrderId == sign.OrderId).FirstOrDefaultAsync(); + if (ordervisa.IsNotNull()) + { + await _orderVisaRepository.DeleteNowAsync(ordervisa); + } + var orderVisaId = (await _orderVisaRepository.InsertNowAsync(new OrderVisa() + { + OrderId = sign.OrderId, + Picture = sign.Picture, + })).Entity.Id; + + if (orderVisaId <= 0) + { + throw Oops.Bah("签单失败"); + } + //推送至所有车组人员已完工通知 + var orderVehicles = _orderVehicleRepository.Where(x => x.OrderId == order.Id).ToList(); + if (orderVehicles.IsNotNull() && orderVehicles.Count > 0) + { + foreach (var orderVehicle in orderVehicles) + { + var wxUser = _wxUserRelationRepository.Where(x => x.UserId == orderVehicle.UserId).FirstOrDefault(); + if (wxUser.IsNotNull() && wxUser.WxOfficialOpenId.IsNotNull()) + { + var data = new WeixinTemplate_AssignOrder("已完工通知", order.ProjectName, "已完工", orderContent, order.ArriveDate.ToString("yyyy -MM-dd HH:mm:ss"), "恭喜任务已完成!返回时记得交单哦,辛苦了~"); + TemplateModel_MiniProgram miniProgram = new TemplateModel_MiniProgram(); + miniProgram.appid = _weixinSettingOptions.WxOpenAppId; + miniProgram.pagepath = $"pages/taskInfo/taskInfo?id={order.Id}&isDriver={orderVehicle.IsDriver.ToString().ToLower()}&type=1"; + var result = await TemplateApi.SendTemplateMessageAsync(_weixinSettingOptions.WeixinAppId, wxUser.WxOfficialOpenId, data.TemplateId, "pages/index/index", data, miniProgram); + Console.WriteLine($"{miniProgram.pagepath}"); + Console.WriteLine($"已完工通知{JsonConvert.SerializeObject(result)}"); + + } + } + } + { + //推送至派单人 + var wxUser = _wxUserRelationRepository.Where(x => x.UserId == order.AssignUserId).FirstOrDefault(); + if (wxUser.IsNotNull() && wxUser.WxOfficialOpenId.IsNotNull()) + { + var data = new WeixinTemplate_AssignOrder("已完工通知", order.ProjectName, "已完工", orderContent, order.ArriveDate.ToString("yyyy -MM-dd HH:mm:ss"), "恭喜任务已完成!返回时记得交单哦,辛苦了~"); + TemplateModel_MiniProgram miniProgram = new TemplateModel_MiniProgram(); + miniProgram.appid = _weixinSettingOptions.WxOpenAppId; + miniProgram.pagepath = $"pages/taskInfo/taskInfo?id={order.Id}&isDriver=false&type=2"; + await TemplateApi.SendTemplateMessageAsync(_weixinSettingOptions.WeixinAppId, wxUser.WxOfficialOpenId, data.TemplateId, "pages/index/index", data, miniProgram); + Console.WriteLine($"{miniProgram.pagepath}"); + } + } + } + else + { + throw Oops.Bah("暂无权限"); + } + } + else + { + throw Oops.Bah("订单不存在"); + } + } + + + + + + + /// + /// 车组人员端任务列表 + /// + /// + /// + /// + [HttpGet] + [Route("api/v1/driver/orders/search")] + public async Task> DriverPageAsync(int currentPage = 1, int pageSize = 10) + { + var orders = await (from ov in _orderVehicleRepository.Where(x => x.UserId == _userManager.UserId && x.IsDeleted == false).AsQueryable() + join o in _repository.Where(x => x.CompanyId == _userManager.CompanyId && x.IsDeleted == false && x.Status != (int)OrderStatus.Cancel).AsQueryable() on ov.OrderId equals o.Id + select new OrderVehicleListOutput + { + Id = ov.OrderId, + OrderContent = o.OrderContent, + Status = o.Status, + VehicleCode = o.VehicleCode, + OrderVehiclePerson = o.OrderVehiclePerson, + IsDriver = ov.IsDriver, + CreatedTime = o.CreatedTime + }) + .OrderByDescending(x => x.CreatedTime) + // .OrderBy(x => x.Status) + .ToPagedListAsync(currentPage, pageSize); + + + foreach (var order in orders.Items) + { + order.OrderContent = HtmlHelper.ReplaceHtmlMark(order.OrderContent); + order.StatusName = typeof(OrderStatus).GetDescription(order.Status); + if (new int[] { (int)OrderStatus.Assign }.Contains(order.Status)) + { + order.StatusName = "新任务"; + } + if (new int[] { (int)OrderStatus.Receive, (int)OrderStatus.Travel, (int)OrderStatus.Appear }.Contains(order.Status)) + { + order.StatusName = "已接单"; + } + if (new int[] { (int)OrderStatus.Complete, (int)OrderStatus.Sign, (int)OrderStatus.Leave, (int)OrderStatus.Evaluate, (int)OrderStatus.ArriveHome }.Contains(order.Status)) + { + order.StatusName = "已完工"; + } + } + + return orders; + } + #endregion + + #region 外租伙伴 + + /// + /// 外租伙伴端任务列表 + /// + /// + /// + /// + [HttpGet] + [Route("api/v1/train/orders/search")] + public async Task> TrainPageAsync(int currentPage = 1, int pageSize = 10) + { + //公司内部派单 + var orders = await (from o in _repository.Where(x => x.VehicleId == _userManager.UserId && x.IsOutside == true && x.IsDeleted == false).AsQueryable() + select new OrderVehicleListOutput + { + Id = o.Id, + OrderContent = o.OrderContent, + Status = o.Status, + VehicleCode = o.VehicleCode, + OrderVehiclePerson = o.OrderVehiclePerson, + IsDriver = false, + CreatedTime = o.CreatedTime + }) + .OrderByDescending(x => x.CreatedTime) + .ToPagedListAsync(currentPage, pageSize); + + foreach (var order in orders.Items) + { + order.OrderContent = HtmlHelper.ReplaceHtmlMark(order.OrderContent); + } + return orders; + } + + #endregion + + #region 公共方法 + + /// + /// 查询订单明细 + /// + /// + /// + [HttpGet] + [Route("api/v1/order/{id}")] + [AllowAnonymous] + public async Task GetByIdAsync(long id) + { + var order = await _repository.FindOrDefaultAsync(id); + if (order.IsNotNull()) + { + //如果订单状态未接单,且权限为车组人员,且指派为司机 + if (_userManager.RoleId == (int)RoleStatusEnum.CrewMembers && order.Status == (int)OrderStatus.Assign) + { + var isAnyAsync = await _orderVehicleRepository.AnyAsync(x => x.OrderId == id && x.IsDriver && x.UserId == _userManager.UserId); + if (isAnyAsync) + { + + await ReceiveAsync(id); + } + + } + + var orderOutput = new OrderOutput(); + orderOutput.Id = order.Id; + orderOutput.ProjectId = order.ProjectId; + orderOutput.ProjectName = order.ProjectName; + orderOutput.ConstructionId = order.ConstructionId; + orderOutput.ConstructionName = (await _constructionRpository.FirstOrDefaultAsync(x => x.Id == order.ConstructionId))?.ConstructionName; + orderOutput.SalesmanId = order.SalesmanId; + orderOutput.VehicleId = order.VehicleId; + orderOutput.VehicleCode = order.VehicleCode; + orderOutput.Status = order.Status; + orderOutput.StatusName = typeof(OrderStatus).GetDescription(order.Status); + orderOutput.ArriveDate = order.ArriveDate; + if (order.ProjectName.IsNotEmptyOrNull()) + { + orderOutput.OrderTitle = order.ProjectName; + } + else + { + orderOutput.OrderTitle = order.OrderContent.Length >= 13 ? order.OrderContent.Substring(0, 13).Trim().ToString() : order.OrderContent.Trim(); + } + orderOutput.OrderContent = order.OrderContent; + orderOutput.OrderSource = order.OrderSource; + orderOutput.OrderSourceName = typeof(OrderSourceEnum).GetDescription(order.OrderSource); + orderOutput.OrderVehiclePerson = order.OrderVehiclePerson; + orderOutput.Latitude = order.Latitude; + orderOutput.Longitude = order.Longitude; + orderOutput.Address = order.Address; + orderOutput.IsOutside = order.IsOutside; + #region OrderStatusDates + Dictionary orderStatusDate = new Dictionary(); + if (_userManager.SuperAdmin) + { + orderStatusDate.Add("派单", order.AssignDate.IsNull() ? "" : order.AssignDate.ToDateTime().ToString("yyyy-MM-dd HH:mm:ss")); + orderStatusDate.Add("接单", order.ReceiveDate.IsNull() ? "" : order.ReceiveDate.ToDateTime().ToString("yyyy-MM-dd HH:mm:ss")); + orderStatusDate.Add("出发", order.TravelDate.IsNull() ? "" : order.TravelDate.ToDateTime().ToString("yyyy-MM-dd HH:mm:ss")); + orderStatusDate.Add("到达", order.AppearDate.IsNull() ? "" : order.AppearDate.ToDateTime().ToString("yyyy-MM-dd HH:mm:ss")); + orderStatusDate.Add("完工", order.CompleteDate.IsNull() ? "" : order.CompleteDate.ToDateTime().ToString("yyyy-MM-dd HH:mm:ss")); + orderStatusDate.Add("签单", order.SignDate.IsNull() ? "" : order.SignDate.ToDateTime().ToString("yyyy-MM-dd HH:mm:ss")); + orderStatusDate.Add("离开", order.LeaveDate.IsNull() ? "" : order.LeaveDate.ToDateTime().ToString("yyyy-MM-dd HH:mm:ss")); + orderStatusDate.Add("评价", order.EvaluateDate.IsNull() ? "" : order.EvaluateDate.ToDateTime().ToString("yyyy-MM-dd HH:mm:ss")); + orderStatusDate.Add("到家", order.ArriveHomeDate.IsNull() ? "" : order.ArriveHomeDate.ToDateTime().ToString("yyyy-MM-dd HH:mm:ss")); + } + else + { + orderStatusDate.Add("派单", order.AssignDate.IsNull() ? "" : order.AssignDate.ToDateTime().ToString("yyyy-MM-dd HH:mm:ss")); + if (order.Status == (int)OrderStatus.Receive) + { + orderStatusDate.Add("接单", order.ReceiveDate.IsNull() ? "" : order.ReceiveDate.ToDateTime().ToString("yyyy-MM-dd HH:mm:ss")); + } + orderStatusDate.Add("签单", order.SignDate.IsNull() ? "" : order.SignDate.ToDateTime().ToString("yyyy-MM-dd HH:mm:ss")); + } + orderOutput.OrderStatusDates = orderStatusDate.ToList(); + #endregion + + orderOutput.VehicleType = order.VehicleType; + orderOutput.VehicleTypeName = _vehicleTypeRepository.Where(x => x.Id == order.VehicleType).FirstOrDefault()?.Name; + + + if (order.IsOutside) + { + var user = await _userRepository.Where(x => x.Id == order.VehicleId).FirstOrDefaultAsync(); + var orderVehicles = new List(); + if (user.IsNotNull()) + { + orderVehicles.Add(new OrderVehicleOutput() + { + UserId = user.Id, + UserName = user.UserName, + UserPhone = user.Phone, + IsDriver = false + }); + } + orderOutput.OrderVehicles = orderVehicles; + } + else + { + var orderVehicles = await _orderVehicleRepository.Where(x => x.OrderId == order.Id).ToListAsync(); + if (orderVehicles.IsNotNull() && orderVehicles.Count > 0) + { + orderOutput.OrderVehicles = orderVehicles.Adapt>(); + } + } + + //签证单 + var orderVisas = _orderVisaRepository.Where(x => x.OrderId == order.Id).ToList(); + if (orderVisas.Count > 0) + { + orderOutput.OrderVisas = orderVisas.Adapt>(); + } + orderOutput.Distance = ""; + orderOutput.ExpectArriveDate = ""; + //查询当前车辆是否有任务 + try + { + if (new long[] { (long)OrderStatus.Receive, (long)OrderStatus.Travel }.Contains(order.Status)) + { + //车辆Gps + var gpsRealTime = await _gpsRealTimeRepository.GetGpsRealTimeByVehicleId(order.VehicleId); + string origin = (double)gpsRealTime.Longitude + "," + (double)gpsRealTime.Latitude; + string destination = (double)order.Longitude + "," + (double)order.Latitude; + var pathPlanningModel = MapHelper.GetPathPlanningByDriving(origin, destination); + if (pathPlanningModel.status == "1" && pathPlanningModel.info == "OK") + { + orderOutput.Distance = $"{ Convert.ToDouble(pathPlanningModel.route.paths[0].distance) / 1000}KM"; + orderOutput.ExpectArriveDate = DateTimeHelper.ToChineseDate(Convert.ToInt32(pathPlanningModel.route.paths[0].duration)); + } + } + } + catch (Exception ex) + { + Console.WriteLine($"GetPathPlanningByDriving:{ex.Message}"); + } + + return orderOutput; + } + else + { + + throw Oops.Bah("该任务不存在"); + } + + } + + + + + /// + /// v1.2.2 工地联系人调整为非必填项,如果用户先录入 了工程名称基础数据,而且工程名称派工开关 设置为打开,而且用户派工时选择了工程名称 的情况下,则用户录入派工任务保存或指派成 功后,自动将任务内容里该工地联系人关联至 该工程的工地联系人这里 + /// + /// + /// + /// + /// + private async Task InsertProjectPersonAsync(string orderContent, long projectId, long constructionId) + { + string[] phones = RegexHelper.ReturnPhones(orderContent); + if (phones.Length > 0) + { + foreach (var phone in phones) + { + var userId = await _employeeService.AddAsync(new EmployeeAddInput() + { + EmployeeName = "工地联系人", + EmployeePhone = phone.Trim(), + RoleId = (int)RoleStatusEnum.ProjectPerson, + RoleName = typeof(RoleStatusEnum).GetDescription((int)RoleStatusEnum.ProjectPerson) + }); + + if (projectId > 0) + { + var projectPerson = await _projectPersonRepository.FirstOrDefaultAsync(x => x.ProjectId == projectId && x.ProjectPersonPhone == phone); + if (projectPerson.IsNull()) + { + ProjectPerson person = new ProjectPerson(); + person.ProjectId = projectId; + person.ProjectPersonId = userId; + person.ProjectPersonName = "工地联系人"; + person.ProjectPersonPhone = phone; + await _projectPersonRepository.InsertNowAsync(person); + + } + + + var project = await _projectRepository.FirstOrDefaultAsync(x => x.Id == projectId); + if (project.IsNotNull()) + { + var construction = await _constructionRpository.FirstOrDefaultAsync(x => x.Id == constructionId); + if (constructionId > 0) + { + project.ConstructionId = constructionId; + project.ConstructionName = construction.ConstructionName; + await _projectRepository.UpdateNowAsync(project); + } + + } + await _cacheService.RemoveProjectAsync(projectId); + await _cacheService.RemoveProjectListAsync(projectId); + } + } + + } + } + + + #region v1.2.7 更新回执 + /// + /// 更新签证单 + /// + /// + /// + [HttpPut] + [Route("api/v1/ordervisa")] + public async Task OrderVisaAsync(SignInput sign) + { + + var order = await _repository.FindOrDefaultAsync(sign.OrderId); + if (order.IsNotNull()) + { + + var isDriver = _orderVehicleRepository.Any(x => x.OrderId == sign.OrderId && x.IsDriver == true); + if (isDriver || _userManager.SuperAdmin) + { + var ordervisa = await _orderVisaRepository.Where(x => x.OrderId == sign.OrderId).FirstOrDefaultAsync(); + if (ordervisa.IsNotNull()) + { + await _orderVisaRepository.DeleteNowAsync(ordervisa.Id); + } + var orderVisaId = (await _orderVisaRepository.InsertNowAsync(new OrderVisa() + { + OrderId = sign.OrderId, + Picture = sign.Picture, + })).Entity.Id; + + if (orderVisaId <= 0) + { + throw Oops.Bah("更新回执失败"); + } + } + else + { + throw Oops.Bah("暂无权限"); + } + } + else + { + throw Oops.Bah("订单不存在"); + } + } + #endregion + + #endregion + + #region 回单查询 + + /// + /// 回单查询列表 + /// + /// 0全部,1未接单,2已结单,3已完成,4已评价 + /// + /// + /// + /// + /// + /// + [HttpGet] + [Route("api/v1/receipt/orders/search")] + public async Task> ReceiptPageAsync(DateTime startTime, DateTime endTime, int status = 0, int currentPage = 1, int pageSize = 10, string key = "") + { + var orders = await _repository.DetachedEntities + .WhereIf(new long[] { (long)RoleStatusEnum.Salesman, (long)RoleStatusEnum.PartTimeSalesman }.Contains(_userManager.RoleId), + x => x.CreatedUserId == _userManager.UserId || x.SalesmanId == _userManager.UserId) + .Where(x => x.CompanyId == _userManager.CompanyId && x.IsDeleted == false && !new int[] { (int)OrderStatus.StayAssign, (int)OrderStatus.Cancel }.Contains(x.Status)) + .Where(status == 1, x => x.Status == (int)OrderStatus.Assign) + .Where(status == 2, x => new int[] { (int)OrderStatus.Receive, (int)OrderStatus.Travel, (int)OrderStatus.Appear }.Contains(x.Status)) + .Where(status == 3, x => new int[] { (int)OrderStatus.Complete, (int)OrderStatus.Leave, (int)OrderStatus.Sign, (int)OrderStatus.ArriveHome }.Contains(x.Status)) + .Where(status == 4, x => x.Status == (int)OrderStatus.Evaluate) + .Where(startTime.IsNotNull(), x => x.CreatedTime >= startTime) + .Where(endTime.IsNotNull(), x => x.CreatedTime <= endTime) + .Where(key.IsNotNull(), x => x.OrderVehiclePerson.Contains(key) || x.VehicleCode.Contains(key) || x.OrderContent.Contains(key) || x.ProjectName.Contains(key)) + .OrderByDescending(x => x.ArriveDate) + .Select(x => new OrderListOutput + { + Id = x.Id, + VehicleCode = x.VehicleCode, + ArriveDate = x.ArriveDate, + Status = x.Status, + OrderContent = x.OrderContent, + OrderSource = x.OrderSource, + OrderVehiclePerson = x.OrderVehiclePerson, + AssignUserId = x.AssignUserId, + ProjectId = x.ProjectId, + ProjectName = x.ProjectName + }) + .ToPagedListAsync(currentPage, pageSize); + var userIds = orders.Items.Select(x => x.AssignUserId).ToArray(); + var users = await _userRepository.DetachedEntities + .Where(x => userIds.Contains(x.Id)) + .Select(x => new User + { + Id = x.Id, + UserName = x.UserName + }) + .ToListAsync(); + + + + foreach (var item in orders.Items) + { + item.OrderContent = HtmlHelper.ReplaceHtmlMark(item.OrderContent); + item.AssignUserName = users.Find(x => x.Id == item.AssignUserId)?.UserName; + item.StatusName = typeof(OrderStatus).GetDescription(item.Status); + if (new int[] { (int)OrderStatus.Assign }.Contains(item.Status)) + { + item.StatusName = "未接单"; + } + if (new int[] { (int)OrderStatus.Receive, (int)OrderStatus.Travel, (int)OrderStatus.Appear }.Contains(item.Status)) + { + item.StatusName = "已接单"; + } + if (new int[] { (int)OrderStatus.Complete, (int)OrderStatus.Sign, (int)OrderStatus.Leave, (int)OrderStatus.ArriveHome }.Contains(item.Status)) + { + item.StatusName = "已完工"; + } + + item.OrderVehicleDriverName = (await _orderVehicleRepository.DetachedEntities.FirstOrDefaultAsync(x => x.OrderId == item.Id && x.IsDriver == true))?.UserName; + item.OrderTitle = item.ProjectName.IsNull() ? (item.OrderContent.Length >= 13 ? item.OrderContent.Substring(0, 13).Trim().ToString() : item.ProjectName.Trim()) : (item.ProjectName.Length >= 13 ? item.ProjectName.Substring(0, 13).Trim().ToString() : item.ProjectName.Trim()); + } + return orders; + } + + #endregion + + #region 任务统计 + /// + /// 任务统计--车辆 + /// + /// + /// + /// + /// + /// + [HttpGet] + [Route("api/v1/order/statistics/vehicle/search")] + public async Task> GetOrderStatisticsByVehicleAsync(DateTime startTime, DateTime endTime, int currentPage = 1, int pageSize = 10) + { + string where = ""; + if (new long[] { (long)RoleStatusEnum.Salesman, (long)RoleStatusEnum.PartTimeSalesman }.Contains(_userManager.RoleId)) + { + where = $"AND SalesmanId={_userManager.UserId} "; + } + var orderList = await @$"SELECT VehicleCode,VehicleId, + Count(CASE WHEN `Status`>=20 THEN VehicleId END) AS AssignCount, + Count(CASE WHEN `Status`>=30 THEN VehicleId END) AS ReceiveCount, + COUNT(CASE WHEN `Status`>=50 THEN VehicleId END) AS CompleteCount + FROM dc_order WHERE `Status`!=101 AND CompanyId = @companyId AND IsOutside = FALSE AND VehicleId > 0 AND ArriveDate >= @startTime AND ArriveDate <= @endTime {where} + GROUP BY VehicleId,VehicleCode + ORDER BY AssignCount DESC,ReceiveCount DESC,CompleteCount DESC" + .SqlQueryAsync(new { companyId = _userManager.CompanyId, startTime = startTime, endTime = endTime }); + PagedList pagedList = new PagedList + { + Items = orderList.Skip((currentPage - 1) * pageSize).Take(pageSize).ToList(), + TotalCount = orderList.Count() + }; + return pagedList; + } + + /// + /// 任务统计--工程 + /// + /// + /// + /// + /// + /// + [HttpGet] + [Route("api/v1/order/statistics/project/search")] + public async Task> GetOrderStatisticsByProjectAsync(DateTime startTime, DateTime endTime, int currentPage = 1, int pageSize = 10) + { + string where = ""; + if (new long[] { (long)RoleStatusEnum.Salesman, (long)RoleStatusEnum.PartTimeSalesman }.Contains(_userManager.RoleId)) + { + where = $"AND SalesmanId={_userManager.UserId} "; + } + var orderList = await @$"SELECT ProjectName,ProjectId, + Count(CASE WHEN `Status`>=10 THEN ProjectId END) AS AssignCount, + Count(CASE WHEN `Status`>=30 THEN ProjectId END) AS ReceiveCount, + COUNT(CASE WHEN `Status`>=50 THEN ProjectId END) AS CompleteCount + FROM dc_order WHERE `Status`!=101 AND CompanyId=@companyId AND ProjectId > 0 AND ArriveDate >= @startTime AND ArriveDate <= @endTime {where} + GROUP BY ProjectId,ProjectName + ORDER BY AssignCount DESC,ReceiveCount DESC,CompleteCount DESC" + .SqlQueryAsync(new { companyId = _userManager.CompanyId, startTime = startTime, endTime = endTime }); + PagedList pagedList = new PagedList + { + Items = orderList.Skip((currentPage - 1) * pageSize).Take(pageSize).ToList(), + TotalCount = orderList.Count() + }; + return pagedList; + } + + /// + /// 任务统计--人员 + /// + /// + /// + /// + /// + /// + [HttpGet] + [Route("api/v1/order/statistics/employee/search")] + public async Task> GetOrderStatisticsByEmployeeAsync(DateTime startTime, DateTime endTime, int currentPage = 1, int pageSize = 10) + { + string where = ""; + if (new long[] { (long)RoleStatusEnum.Salesman, (long)RoleStatusEnum.PartTimeSalesman }.Contains(_userManager.RoleId)) + { + where = $"AND o.SalesmanId={_userManager.UserId} "; + } + var orderList = await @$"SELECT ov.UserName,ov.UserId, + Count(CASE WHEN o.`Status`>=10 THEN ov.UserId END) AS AssignCount, + Count(CASE WHEN o.`Status`>=30 THEN ov.UserId END) AS ReceiveCount, + COUNT(CASE WHEN o.`Status`>=50 THEN ov.UserId END) AS CompleteCount + FROM dc_order o + INNER JOIN dc_order_vehicle ov ON o.Id=ov.OrderId + WHERE o.`Status`!=101 AND o.CompanyId=@companyId AND ov.UserId > 0 AND o.ArriveDate >= @startTime AND o.ArriveDate <= @endTime {where} + GROUP BY ov.UserId,ov.UserName + ORDER BY AssignCount DESC,ReceiveCount DESC,CompleteCount DESC" + .SqlQueryAsync(new { companyId = _userManager.CompanyId, startTime = startTime, endTime = endTime }); + PagedList pagedList = new PagedList + { + Items = orderList.Skip((currentPage - 1) * pageSize).Take(pageSize).ToList(), + TotalCount = orderList.Count() + }; + return pagedList; + } + + + + /// + /// 任务统计--施工单位 + /// + /// + /// + /// + [HttpGet] + [Route("api/v1/order/statistics/construction/search")] + [AllowAnonymous] + public async Task> GetOrderStatisticsByConstructionAsync(DateTime startTime, DateTime endTime) + { + + var orders = await _repository.DetachedEntities + .Where(x => x.ConstructionId > 0 && x.ProjectId > 0 && x.CompanyId == 265147171168325 && x.ArriveDate >= startTime && x.ArriveDate < endTime) + .GroupBy(x => new { x.ConstructionId, x.ProjectName }).Select(g => new OrderStatisticsConstructionOutput + { + ConstructionId = g.Key.ConstructionId, + ProjectName = g.Key.ProjectName, + AssignCount = g.Count(x => x.Status < 60), + CompleteCount = g.Count(x => x.Status >= 60), + Evaluation = 0 + }).ToListAsync(); + + List orderStatisticsConstructionOutputList = new List(); + var constructionIds = orders.Select(x => x.ConstructionId).Distinct().ToList(); + foreach (var constructionId in constructionIds) + { + OrderStatisticsConstructionOutputs orderStatisticsConstructionOutput = new OrderStatisticsConstructionOutputs(); + orderStatisticsConstructionOutput.ConstructionName = (await _constructionRpository.FirstOrDefaultAsync(x => x.Id == constructionId)).ConstructionName; + orderStatisticsConstructionOutput.AssignCount = orders.Where(x => x.ConstructionId == constructionId).Select(x => x.AssignCount).Sum(); + orderStatisticsConstructionOutput.CompleteCount = orders.Where(x => x.ConstructionId == constructionId).Select(x => x.CompleteCount).Sum(); + orderStatisticsConstructionOutput.Evaluation = 0; + orderStatisticsConstructionOutput.Projects = orders.Where(x => x.ConstructionId == constructionId).ToList(); + orderStatisticsConstructionOutputList.Add(orderStatisticsConstructionOutput); + } + return orderStatisticsConstructionOutputList; + } + + + + #endregion + + #region 公共类 + public class OrderVehicleCompara : IEqualityComparer + { + public bool Equals(OrderVehicle x, OrderVehicle y) + { + bool result = true; + if (x.UserId != y.UserId || x.IsDriver != y.IsDriver) + { + result = false; + } + return result; + } + + public int GetHashCode(OrderVehicle obj) + { + return 0; + } + } + + + + + /// + /// + /// + /// + /// + /// + /// + /// + public async Task GetProjectId(decimal latitude, decimal longitude, string address, string projectName) + { + + var project = await _projectRepository.FirstOrDefaultAsync(x => x.ProjectName == projectName && x.CompanyId == _userManager.CompanyId && x.IsEnabled); + if (project.IsNull()) + { + project = (await _projectRepository.InsertNowAsync(new Project() + { + CompanyId = _userManager.CompanyId, + ProjectName = projectName, + SalesmanId = 0, + Longitude = longitude, + Latitude = latitude, + Address = address, + IsEnabled = true, + ConstructionId = 0, + ConstructionName = "" + })).Entity; + } + await _cacheService.RemoveProjectListAsync(_userManager.CompanyId); + await _cacheService.RemoveProjectAsync(project.Id); + + return project.Id; + + + } + + + /// + /// + /// + /// + /// + /// + public async Task GetConstructionId(long constructionId, string constructionName) + { + + var construction = await _constructionRpository.FirstOrDefaultAsync(x => x.Id == constructionId && x.ConstructionName == constructionName && x.Status); + if (construction.IsNull()) + { + construction = (await _constructionRpository.InsertNowAsync(new Construction() + { + CompanyId = _userManager.CompanyId, + ConstructionName = constructionName, + ContactName = "", + ContactPhone = "", + Status = true + })).Entity; + } + return construction.Id; + } + + + /// + /// + /// + /// + /// + /// . + /// + /// + private async Task UpdateProjectAddressAsync(long projectId, decimal latitude, decimal longitude, string address) + { + + var project = await _projectRepository.FirstOrDefaultAsync(x => x.Id == projectId); + if (project.IsNotNull()) + { + project.Latitude = latitude; + project.Longitude = longitude; + project.Address = address; + await _projectRepository.UpdateNowAsync(project); + + } + await _cacheService.RemoveProjectAsync(projectId); + await _cacheService.RemoveProjectListAsync(_userManager.CompanyId); + } + } +} + + + + + #endregion + \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/OrderVehicle/Dto/Input/OrderVehicleAddInput.cs b/src/Znyc.Dispatching.Application/OrderVehicle/Dto/Input/OrderVehicleAddInput.cs new file mode 100644 index 0000000..6ead555 --- /dev/null +++ b/src/Znyc.Dispatching.Application/OrderVehicle/Dto/Input/OrderVehicleAddInput.cs @@ -0,0 +1,28 @@ +namespace Znyc.Dispatching.Application +{ + /// + /// + public class OrderVehicleAddInput + { + + /// + /// 指派任务人员Id + /// + public long UserId { get; set; } + + /// + /// 姓名 + /// + public string UserName { get; set; } + + /// + /// 电话 + /// + public string UserPhone { get; set; } + + /// + /// 是否司机 + /// + public bool IsDriver { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/OrderVehicle/Dto/OutPut/OrderVehicleListOutput .cs b/src/Znyc.Dispatching.Application/OrderVehicle/Dto/OutPut/OrderVehicleListOutput .cs new file mode 100644 index 0000000..72855bc --- /dev/null +++ b/src/Znyc.Dispatching.Application/OrderVehicle/Dto/OutPut/OrderVehicleListOutput .cs @@ -0,0 +1,49 @@ +using System; + +namespace Znyc.Dispatching.Application +{ + /// + /// + public class OrderVehicleListOutput + { + /// + /// OrderId + /// + public long Id { get; set; } + + /// + /// 订单内容 + /// + public string OrderContent { get; set; } + + /// + /// 状态,,10=待指派,20=已指派,未接单,30=已接单,40=已出发,50=已完成(已签单),60=已离开,70=已评价 + /// + public int Status { get; set; } + + /// + /// 状态名称 + /// + public string StatusName { get; set; } + + /// + /// 车辆编号 + /// + public string VehicleCode { get; set; } + + /// + /// 车组人员 + /// + public string OrderVehiclePerson { get; set; } + + /// + /// 是否司机 + /// + public bool IsDriver { get; set; } + + /// + /// 创建时间 + /// + public DateTime CreatedTime { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/OrderVehicle/Dto/OutPut/OrderVehicleOutput.cs b/src/Znyc.Dispatching.Application/OrderVehicle/Dto/OutPut/OrderVehicleOutput.cs new file mode 100644 index 0000000..e576ba4 --- /dev/null +++ b/src/Znyc.Dispatching.Application/OrderVehicle/Dto/OutPut/OrderVehicleOutput.cs @@ -0,0 +1,28 @@ +namespace Znyc.Dispatching.Application +{ + /// + /// + public class OrderVehicleOutput + { + + /// + /// UserId + /// + public long UserId { get; set; } + + /// + /// 姓名 + /// + public string UserName { get; set; } + + /// + /// 电话 + /// + public string UserPhone { get; set; } + + /// + /// 是否司机 + /// + public bool IsDriver { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/OrderVisa/Dto/Input/OrderVisaInput.cs b/src/Znyc.Dispatching.Application/OrderVisa/Dto/Input/OrderVisaInput.cs new file mode 100644 index 0000000..5feaa40 --- /dev/null +++ b/src/Znyc.Dispatching.Application/OrderVisa/Dto/Input/OrderVisaInput.cs @@ -0,0 +1,12 @@ +namespace Znyc.Dispatching.Application +{ + /// + /// + public class OrderVisaInput + { + /// + /// 图片路径 + /// + public string Picture { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/OrderVisa/Dto/OutPut/OrderVisaOutput.cs b/src/Znyc.Dispatching.Application/OrderVisa/Dto/OutPut/OrderVisaOutput.cs new file mode 100644 index 0000000..7b7cada --- /dev/null +++ b/src/Znyc.Dispatching.Application/OrderVisa/Dto/OutPut/OrderVisaOutput.cs @@ -0,0 +1,13 @@ +namespace Znyc.Dispatching.Application +{ + /// + /// + public class OrderVisaOutput + { + + /// + /// 图片路径 + /// + public string Picture { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Project/Dto/Input/ProjectAddInput.cs b/src/Znyc.Dispatching.Application/Project/Dto/Input/ProjectAddInput.cs new file mode 100644 index 0000000..ad1efa9 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Project/Dto/Input/ProjectAddInput.cs @@ -0,0 +1,57 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace Znyc.Dispatching.Application +{ + /// + /// 工程输入实体 + /// + public class ProjectAddInput + { + /// + /// 工程名称 + /// + [Required(ErrorMessage = "请录入工程名称!")] + public string ProjectName { get; set; } + + /// + /// 业务员Id + /// + public long SalesmanId { get; set; } + + /// + /// 经度 + /// + public decimal Longitude { get; set; } + + /// + /// 纬度 + /// + public decimal Latitude { get; set; } + + /// + /// 地址 + /// + public string Address { get; set; } + + /// + /// 工地联系人Id + /// + public List projectPersons { get; set; } + + /// + /// 是否启用 + /// + public bool IsEnabled { get; set; } + + /// + /// 施工单位 + /// + public long ConstructionId { get; set; } + + /// + /// 施工单位 + /// + public string ConstructionName { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Project/Dto/Input/ProjectUpdateInput.cs b/src/Znyc.Dispatching.Application/Project/Dto/Input/ProjectUpdateInput.cs new file mode 100644 index 0000000..8bc6a13 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Project/Dto/Input/ProjectUpdateInput.cs @@ -0,0 +1,9 @@ +namespace Znyc.Dispatching.Application +{ + /// + /// + public class ProjectUpdateInput : ProjectAddInput + { + public long Id { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Project/Dto/OutPut/ProjectListOutput.cs b/src/Znyc.Dispatching.Application/Project/Dto/OutPut/ProjectListOutput.cs new file mode 100644 index 0000000..fd8f4ec --- /dev/null +++ b/src/Znyc.Dispatching.Application/Project/Dto/OutPut/ProjectListOutput.cs @@ -0,0 +1,66 @@ +using System.Collections.Generic; + +namespace Znyc.Dispatching.Application +{ + public class ProjectListOutput + { + /// + /// Id + /// + public long Id { get; set; } + + /// + /// 工程名称 + /// + public string ProjectName { get; set; } + + /// + /// 工程地址 + /// + public string Address { get; set; } + + /// + /// 经度 + /// + public decimal Longitude { get; set; } + + /// + /// 纬度 + /// + public decimal Latitude { get; set; } + + /// + /// 工地联系人(报单人) + /// + public List ProjectPeoples { get; set; } + + /// + /// 是否启用 + /// + public bool IsEnabled { get; set; } + + /// + /// 业务员Id + /// + public long SalesmanId { get; set; } + + /// + /// 施工单位 + /// + public long ConstructionId { get; set; } + } + + public class ProjectListOutputs + { + public string Char { get; set; } + + public List List { get; set; } + } + + public class ProjectListPage + { + public long Total { get; set; } + + public List ProjectList { get; set; } + } +} diff --git a/src/Znyc.Dispatching.Application/Project/Dto/OutPut/ProjectOutput.cs b/src/Znyc.Dispatching.Application/Project/Dto/OutPut/ProjectOutput.cs new file mode 100644 index 0000000..cdbb3eb --- /dev/null +++ b/src/Znyc.Dispatching.Application/Project/Dto/OutPut/ProjectOutput.cs @@ -0,0 +1,63 @@ +using System.Collections.Generic; + +namespace Znyc.Dispatching.Application +{ + public class ProjectOutput + { + + /// + /// Id + /// + public long Id { get; set; } + /// + /// 工程名称 + /// + public string ProjectName { get; set; } + + /// + /// 业务员Id + /// + public long SalesmanId { get; set; } + + /// + /// 业务员 + /// + public string Salesman { get; set; } + + /// + /// 经度 + /// + public decimal Longitude { get; set; } + + /// + /// 纬度 + /// + public decimal Latitude { get; set; } + + /// + /// 地址 + /// + public string Address { get; set; } + + /// + /// 工地联系人(报单人) + /// + public List ProjectPeoples { get; set; } + + /// + /// 是否启用 + /// + public bool IsEnabled { get; set; } + + /// + /// 施工单位 + /// + public long ConstructionId { get; set; } + + + /// + /// 施工单位 + /// + public string ConstructionName { get; set; } + } +} diff --git a/src/Znyc.Dispatching.Application/Project/Services/IProjectService.cs b/src/Znyc.Dispatching.Application/Project/Services/IProjectService.cs new file mode 100644 index 0000000..90b82a6 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Project/Services/IProjectService.cs @@ -0,0 +1,48 @@ +using System.Threading.Tasks; + +namespace Znyc.Dispatching.Application +{ + /// + /// 工程服务 + /// + public interface IProjectService + { + + /// + /// 查询单个工程信息 + /// + /// + Task GetByIdAsync(long projectId); + + /// + /// 工程列表 + /// + /// + /// + /// + /// + /// + Task PageAsync(int currentPage, int pageSize, int isEnabled, string key); + + /// + /// 新增工程信息 + /// + /// + /// + Task AddAsync(ProjectAddInput input); + + /// + /// 更新公司信息 + /// + /// + /// + Task UpdateAsync(ProjectUpdateInput input); + + /// + /// 停用工程信息 + /// + /// + /// + Task UpdateIsEnabledAsync(long projectId); + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Project/Services/ProjectService.cs b/src/Znyc.Dispatching.Application/Project/Services/ProjectService.cs new file mode 100644 index 0000000..b66a0b2 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Project/Services/ProjectService.cs @@ -0,0 +1,294 @@ +using Furion.DatabaseAccessor; +using Furion.DatabaseAccessor.Extensions; +using Furion.DependencyInjection; +using Furion.DynamicApiController; +using Furion.FriendlyException; +using Mapster; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; +using Znyc.Dispatching.Common.Extensions; +using Znyc.Dispatching.Core; +using Znyc.Dispatching.Core.Entitys; +using Znyc.Dispatching.Core.Extension; +using Znyc.Dispatching.Core.Helpers; + +namespace Znyc.Dispatching.Application +{ + /// + /// 工程管理 + /// + [ApiDescriptionSettings(Name = "project", Order = 32)] + public class ProjectService : IProjectService, IDynamicApiController, ITransient + { + private string[] INDEX_STRINGS = {"A", "B", "C", "D", "E", "F", "G", "H", "I", + "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", + "W", "X", "Y", "Z", "#"}; + private readonly ICacheService _cacheService; + private readonly IRepository _repository; + private readonly IUserManager _userManager; + private readonly IRepository _projectPersonRepository; + private readonly IRepository _constructionRepository; + public ProjectService( + IUserManager userManager, + ICacheService cacheService, + IRepository projectPersonRepository, + IRepository repository, + IRepository constructionRepository) + { + _repository = repository; + _userManager = userManager; + _cacheService = cacheService; + _projectPersonRepository = projectPersonRepository; + _constructionRepository = constructionRepository; + } + + + /// + /// 查询单个工程信息 + /// + /// + [HttpGet] + [Route("api/v1/project/{projectId}")] + public async Task GetByIdAsync(long projectId) + { + ProjectOutput project = await _cacheService.GetProjectAsync(projectId); + project.Salesman = (await _cacheService.GetEmployeeListAsync(_userManager.CompanyId)).Find(x => x.UserId == project.SalesmanId)?.EmployeeName; + return project; + } + + + /// + /// 工程列表 + /// + /// -1全部,停用0,正常1 + /// + /// + /// + /// + [HttpGet] + [Route("api/v1/projects/search")] + public async Task PageAsync([Required] int currentPage, [Required] int pageSize, [FromQuery] int isEnabled, [FromQuery] string key) + { + var projects = (await _cacheService.GetProjectListAsync(_userManager.CompanyId)) + .WhereIf(isEnabled >= 0, x => x.IsEnabled == (isEnabled == 0 ? false : true)) + .WhereIf(new long[] { (long)RoleStatusEnum.Salesman, (long)RoleStatusEnum.PartTimeSalesman }.Contains(_userManager.RoleId), + x => x.SalesmanId == _userManager.UserId) + .WhereIf(key.IsNotEmptyOrNull(), x => x.ProjectName.Contains(key)) + .Adapt>(); + + var projectOutputs = new List(); + foreach (var INDEX_STRING in INDEX_STRINGS) + { + projectOutputs.Add(new ProjectListOutputs() + { + Char = INDEX_STRING, + List = projects.Where(x => StringHelper.GetStringFirstSpell(x.ProjectName) == INDEX_STRING).ToList() + }); + }; + var data = new ProjectListPage() + { + ProjectList = projectOutputs, + Total = projects.Count + }; + return data; + } + + + /// + /// 新增工程信息 + /// + /// + /// + [HttpPost] + [Route("api/v1/project")] + public async Task AddAsync(ProjectAddInput input) + { + var entity = await _repository.Where(x => x.ProjectName == input.ProjectName && x.CompanyId == _userManager.CompanyId).FirstOrDefaultAsync(); + if (entity.IsNotNull()) + { + if (entity.IsEnabled) + { + throw Oops.Bah("该名称" + input.ProjectName + "已存在"); + } + else + { + throw Oops.Bah("该名称" + input.ProjectName + "已存在,且您已停用"); + } + } + var project = input.Adapt(); + project.CompanyId = _userManager.CompanyId; + project.IsEnabled = true; + if (new long[] { (long)RoleStatusEnum.Salesman, (long)RoleStatusEnum.PartTimeSalesman }.Contains(_userManager.RoleId)) + { + project.SalesmanId = _userManager.UserId; + } + var result = await _repository.InsertNowAsync(project); + foreach (var item in input.projectPersons) + { + var projectPersons = item.Adapt(); + projectPersons.ProjectId = result.Entity.Id; + await _projectPersonRepository.InsertNowAsync(projectPersons); + } + + await _cacheService.RemoveProjectAsync(_userManager.CompanyId); + await _cacheService.RemoveProjectListAsync(_userManager.CompanyId); + } + + /// + /// 更新工程信息 + /// + /// + /// + [HttpPut] + [Route("api/v1/project")] + [UnitOfWork] + public async Task UpdateAsync(ProjectUpdateInput input) + { + var project = await _repository.FirstOrDefaultAsync(x => x.Id == input.Id); + if (project.IsNull()) throw Oops.Oh("暂无该信息"); + var repeatProject = await _repository.DetachedEntities + .Where(x => x.ProjectName == input.ProjectName && x.Id != input.Id && + x.CompanyId == _userManager.CompanyId) + .FirstOrDefaultAsync(); + if (repeatProject.IsNotNull()) + { + if (repeatProject.IsEnabled) + { + throw Oops.Bah("该名称" + input.ProjectName + "已存在"); + } + else + { + throw Oops.Bah("该名称" + input.ProjectName + "已存在,且您已停用"); + } + } + #region 修改工程联系人 + var oldProjectPersons = await _projectPersonRepository.DetachedEntities + .Where(x => x.ProjectId == input.Id && x.IsDeleted == false) + .ToListAsync(); + var oldProjectPersonIds = oldProjectPersons.Select(x => x.ProjectPersonId).ToArray(); + var newProjectPersonIds = input.projectPersons.Select(x => x.ProjectPersonId).ToArray(); + var deleteProjectPersonIds = oldProjectPersonIds.Except(newProjectPersonIds).ToArray(); + if (deleteProjectPersonIds.Length > 0) + { + for (int i = 0; i < deleteProjectPersonIds.Length; i++) + { + var projectPerson = oldProjectPersons.FirstOrDefault(x => x.ProjectPersonId == deleteProjectPersonIds[i]); + projectPerson.IsDeleted = true; + await _projectPersonRepository.UpdateNowAsync(projectPerson); + } + } + foreach (var item in input.projectPersons) + { + var projectPerson = oldProjectPersons.Find(x => x.ProjectPersonId == item.ProjectPersonId); + if (projectPerson == null) + { + var projectPersons = item.Adapt(); + projectPersons.ProjectId = input.Id; + await _projectPersonRepository.InsertNowAsync(projectPersons); + } + } + #endregion + if (new long[] { (long)RoleStatusEnum.Salesman, (long)RoleStatusEnum.PartTimeSalesman }.Contains(_userManager.RoleId)) + { + input.SalesmanId = _userManager.UserId; + } + if (input.ProjectName != project.ProjectName || input.SalesmanId != project.SalesmanId) + { + //同步修改订单表工程信息 + await "update dc_order set ProjectName=@projectName,SalesmanId=@salesmanId where ProjectId=@id And IsDeleted=0" + .SqlNonQueryAsync(new { id = input.Id, projectName = input.ProjectName, salesmanId = input.SalesmanId }); + } + var result = await _repository.UpdateNowAsync(input.Adapt(project)); + if (result.IsNull()) + { + throw Oops.Oh("修改工程信息失败"); + } + + await _cacheService.RemoveProjectListAsync(_userManager.CompanyId); + await _cacheService.RemoveProjectAsync(input.Id); + } + + + /// + /// 停用工程信息 + /// + /// + /// + [HttpPut] + [Route("api/v1/project/{projectId}")] + public async Task UpdateIsEnabledAsync(long projectId) + { + var project = await _repository.FindOrDefaultAsync(projectId); + if (project.IsNull()) throw Oops.Oh("暂无该信息"); + project.IsEnabled = false; + var result = await _repository.UpdateNowAsync(project); + if (result.IsNull()) + { + throw Oops.Oh("停用工程信息失败"); + } + await _cacheService.RemoveProjectListAsync(_userManager.CompanyId); + await _cacheService.RemoveProjectAsync(projectId); + } + + + #region v1.2.7 + + + [HttpGet] + [UnitOfWork] + [Route("api/v1/project/autoinsert/{projectName}/{constructionName}")] + public async Task AutoInsertProjectAsync([Required] string projectName, [Required] string constructionName) + { + projectName = projectName.Trim(); + constructionName = constructionName.Trim(); + //新增施工单位 + var constructionEntity = (await _constructionRepository.DetachedEntities.FirstOrDefaultAsync(x => x.CompanyId == _userManager.CompanyId && x.ConstructionName == constructionName)); + if (constructionEntity.IsNull()) + { + Construction construction = new Construction(); + construction.CompanyId = _userManager.CompanyId; + construction.ConstructionName = constructionName; + construction.ContactName = ""; + construction.ContactPhone = ""; + construction.Status = true; + constructionEntity = (await _constructionRepository.InsertNowAsync(construction))?.Entity; + if (constructionEntity.Id < 0) + { + throw Oops.Oh("新增施工单位失败"); + } + } + //新增工程 + var projectEntity = (await _repository.DetachedEntities.FirstOrDefaultAsync(x => x.CompanyId == _userManager.CompanyId && x.ProjectName == projectName)); + if (projectEntity.IsNull()) + { + Project project = new Project(); + project.CompanyId = _userManager.CompanyId; + project.ProjectName = projectName; + project.SalesmanId = 0; + project.Longitude = 0; + project.Latitude = 0; + project.Address = ""; + project.IsEnabled = true; + project.ConstructionId = constructionEntity.Id; + project.ConstructionName = constructionName; + projectEntity = (await _repository.InsertNowAsync(project)).Entity; + } + + await _cacheService.RemoveProjectListAsync(_userManager.CompanyId); + await _cacheService.RemoveProjectAsync(projectEntity.Id); + + return new ProjectOutput() + { + ConstructionId = constructionEntity.Id, + ConstructionName = constructionName, + Id = projectEntity.Id, + ProjectName = projectName, + }; + } + #endregion + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/ProjectPerson/Dto/Input/ProjectPersonAddInput.cs b/src/Znyc.Dispatching.Application/ProjectPerson/Dto/Input/ProjectPersonAddInput.cs new file mode 100644 index 0000000..170b2fa --- /dev/null +++ b/src/Znyc.Dispatching.Application/ProjectPerson/Dto/Input/ProjectPersonAddInput.cs @@ -0,0 +1,25 @@ +namespace Znyc.Dispatching.Application +{ + /// + /// 公司输入实体 + /// + public class ProjectPersonAddInput + { + /// + /// 联系人Id,链接员工表Id + /// + public long ProjectPersonId { get; set; } + + /// + /// 联系人姓名 + /// + + public string ProjectPersonName { get; set; } + + /// + /// 联系人电话 + /// + + public string ProjectPersonPhone { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/ProjectPerson/Dto/Input/ProjectPersonUpdateInput.cs b/src/Znyc.Dispatching.Application/ProjectPerson/Dto/Input/ProjectPersonUpdateInput.cs new file mode 100644 index 0000000..dd550d3 --- /dev/null +++ b/src/Znyc.Dispatching.Application/ProjectPerson/Dto/Input/ProjectPersonUpdateInput.cs @@ -0,0 +1,9 @@ +namespace Znyc.Dispatching.Application +{ + /// + /// + public class ProjectPersonUpdateInput : ProjectPersonAddInput + { + public long Id { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/ProjectPerson/Dto/OutPut/ProjectPersonOutput.cs b/src/Znyc.Dispatching.Application/ProjectPerson/Dto/OutPut/ProjectPersonOutput.cs new file mode 100644 index 0000000..b0d4ca5 --- /dev/null +++ b/src/Znyc.Dispatching.Application/ProjectPerson/Dto/OutPut/ProjectPersonOutput.cs @@ -0,0 +1,22 @@ +namespace Znyc.Dispatching.Application +{ + public class ProjectPersonOutput + { + /// + /// 联系人Id,链接员工表Id + /// + public long ProjectPersonId { get; set; } + + /// + /// 联系人姓名 + /// + + public string ProjectPersonName { get; set; } + + /// + /// 联系人电话 + /// + + public string ProjectPersonPhone { get; set; } + } +} diff --git a/src/Znyc.Dispatching.Application/ProjectPerson/Services/IProjectPersonService.cs b/src/Znyc.Dispatching.Application/ProjectPerson/Services/IProjectPersonService.cs new file mode 100644 index 0000000..ace83ee --- /dev/null +++ b/src/Znyc.Dispatching.Application/ProjectPerson/Services/IProjectPersonService.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Znyc.Dispatching.Application +{ + public interface IProjectPersonService + { + Task> GetByIdAsync(long id); + } +} diff --git a/src/Znyc.Dispatching.Application/ProjectPerson/Services/ProjectPersonService.cs b/src/Znyc.Dispatching.Application/ProjectPerson/Services/ProjectPersonService.cs new file mode 100644 index 0000000..73b83c8 --- /dev/null +++ b/src/Znyc.Dispatching.Application/ProjectPerson/Services/ProjectPersonService.cs @@ -0,0 +1,44 @@ +using Furion.DatabaseAccessor; +using Furion.DependencyInjection; +using Furion.DynamicApiController; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Znyc.Dispatching.Core.Entitys; + +namespace Znyc.Dispatching.Application +{ + /// + ///工程信息联系人管理 + /// + [ApiDescriptionSettings(Name = "projectperson", Order = 31)] + public class ProjectPersonService : IProjectPersonService, IDynamicApiController, ITransient + { + private readonly IRepository _repository; + + public ProjectPersonService(IRepository repository) + { + _repository = repository; + } + + /// + /// 根据工程Id查询工程联系人 + /// + /// + [HttpGet] + [Route("api/v1/project/persons/{projectId}")] + public async Task> GetByIdAsync(long projectId) + { + List projectPeoples = await _repository.DetachedEntities + .Where(x => x.ProjectId == projectId && x.IsDeleted == false) + .Select(x => new ProjectPersonOutput + { + ProjectPersonName = x.ProjectPersonName, + ProjectPersonPhone = x.ProjectPersonPhone + }).ToListAsync(); + return projectPeoples; + } + } +} diff --git a/src/Znyc.Dispatching.Application/Report/Dto/Input/ReportAddInput.cs b/src/Znyc.Dispatching.Application/Report/Dto/Input/ReportAddInput.cs new file mode 100644 index 0000000..9146696 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Report/Dto/Input/ReportAddInput.cs @@ -0,0 +1,13 @@ +using System.ComponentModel.DataAnnotations; + +namespace Znyc.Dispatching.Application +{ + public class ReportAddInput + { + /// + /// 反馈明细 + /// + [Required] + public string ReportContent { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Report/Services/IReportService.cs b/src/Znyc.Dispatching.Application/Report/Services/IReportService.cs new file mode 100644 index 0000000..9ed109a --- /dev/null +++ b/src/Znyc.Dispatching.Application/Report/Services/IReportService.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Znyc.Dispatching.Application +{ + public interface IReportService + { + Task InsertAsync(ReportAddInput input); + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Report/Services/ReportService.cs b/src/Znyc.Dispatching.Application/Report/Services/ReportService.cs new file mode 100644 index 0000000..bc8ff4f --- /dev/null +++ b/src/Znyc.Dispatching.Application/Report/Services/ReportService.cs @@ -0,0 +1,51 @@ +using Furion.DatabaseAccessor; +using Furion.DependencyInjection; +using Furion.DynamicApiController; +using Mapster; +using Microsoft.AspNetCore.Mvc; +using System; +using System.Threading.Tasks; +using Znyc.Dispatching.Core; +using Znyc.Dispatching.Core.Entitys; + +namespace Znyc.Dispatching.Application +{ + /// + /// 意见反馈服务 + /// + [ApiDescriptionSettings(Name = "report", Order = 100)] + public class ReportService : IReportService, IDynamicApiController, ITransient + { + private readonly IProjectService _companyService; + private readonly IRepository _employeeRepository; + private readonly IRepository _repository; + private readonly UserManager _userManager; + + public ReportService(IRepository repository, + IRepository employeeRepository, + UserManager userManager, + IProjectService companyService + ) + { + _repository = repository; + _employeeRepository = employeeRepository; + _userManager = userManager; + _companyService = companyService; + } + + /// + /// 添加意见反馈 + /// + /// + /// + [HttpPost] + public async Task InsertAsync(ReportAddInput input) + { + Report report = input.Adapt(); + report.ReportUserId = _userManager.UserId; + report.ReportTime = DateTime.Now; + report.Note = ""; + await _repository.InsertNowAsync(report); + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Role/Dto/OutPut/RoleOutput.cs b/src/Znyc.Dispatching.Application/Role/Dto/OutPut/RoleOutput.cs new file mode 100644 index 0000000..c0b35e1 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Role/Dto/OutPut/RoleOutput.cs @@ -0,0 +1,23 @@ +namespace Znyc.Dispatching.Application +{ + /// + /// RoleOutput + /// + public class RoleOutput + { + /// + /// Id + /// + public long Id { get; set; } + + /// + /// 名称 + /// + public string Name { get; set; } + + /// + /// 权限备注 + /// + public string Intro { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Role/Services/IRoleService.cs b/src/Znyc.Dispatching.Application/Role/Services/IRoleService.cs new file mode 100644 index 0000000..4c7a612 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Role/Services/IRoleService.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Znyc.Dispatching.Application +{ + public interface IRoleService + { + Task> GetRoleAsync(); + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Role/Services/RoleService.cs b/src/Znyc.Dispatching.Application/Role/Services/RoleService.cs new file mode 100644 index 0000000..0903e78 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Role/Services/RoleService.cs @@ -0,0 +1,58 @@ +using Furion.DatabaseAccessor; +using Furion.DependencyInjection; +using Furion.DynamicApiController; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Znyc.Dispatching.Core; +using Znyc.Dispatching.Core.Entitys; +using Znyc.Dispatching.Core.Extension; + +namespace Znyc.Dispatching.Application +{ + /// + /// 角色服务 + /// + [ApiDescriptionSettings(Name = "role", Order = 40)] + public class RoleService : IRoleService, IDynamicApiController, ITransient + { + private readonly ICacheService _cache; + private readonly IRepository _repository; + + public RoleService( + IRepository repository, + ICacheService cache + ) + { + _repository = repository; + _cache = cache; + } + + /// + /// 获取角色 + /// + /// + [HttpGet] + [AllowAnonymous] + public async Task> GetRoleAsync() + { + List roles = await _cache.GetRoleAsync(); + if (roles.IsNull()) + { + roles = await _repository.DetachedEntities + .Where(x => x.Status == CommonStatusEnum.ENABLE) + .Select(x => new RoleOutput + { + Id = x.Id, + Name = x.Name + }).ToListAsync(); + await _cache.SetRoleAsync(roles); + } + + return roles; + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/RoleMenu/Dto/OutPut/RoleMenuListOutput.cs b/src/Znyc.Dispatching.Application/RoleMenu/Dto/OutPut/RoleMenuListOutput.cs new file mode 100644 index 0000000..db75842 --- /dev/null +++ b/src/Znyc.Dispatching.Application/RoleMenu/Dto/OutPut/RoleMenuListOutput.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace Znyc.Dispatching.Application +{ + public class RoleMenuListOutput + { + public long RoleId { get; set; } + + public List MenuList { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/RoleMenu/Services/IRoleMenuService.cs b/src/Znyc.Dispatching.Application/RoleMenu/Services/IRoleMenuService.cs new file mode 100644 index 0000000..723b9cb --- /dev/null +++ b/src/Znyc.Dispatching.Application/RoleMenu/Services/IRoleMenuService.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Znyc.Dispatching.Application +{ + public interface IRoleMenuService + { + /// + /// 获取角色的菜单Id集合 + /// + /// + /// + Task> GetRoleMenuIdList(List roleIdList); + + /// + /// 获取角色菜单 + /// + /// + Task> GetRoleMenuListAsync(); + + /// + /// 同步角色菜单 + /// + /// + Task> GetRoleMenuListByIdAsync(long roleId); + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/RoleMenu/Services/RoleMenuService.cs b/src/Znyc.Dispatching.Application/RoleMenu/Services/RoleMenuService.cs new file mode 100644 index 0000000..78ad7ae --- /dev/null +++ b/src/Znyc.Dispatching.Application/RoleMenu/Services/RoleMenuService.cs @@ -0,0 +1,132 @@ +using Furion.DatabaseAccessor; +using Furion.DependencyInjection; +using Furion.DynamicApiController; +using Mapster; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Znyc.Dispatching.Core; +using Znyc.Dispatching.Core.Entitys; +using Znyc.Dispatching.Core.Extension; + +namespace Znyc.Dispatching.Application +{ + /// + /// 角色菜单服务 + /// + [ApiDescriptionSettings(Name = "rolemenu", Order = 146)] + public class RoleMenuService : IRoleMenuService, IDynamicApiController, ITransient + { + private readonly ICacheService _cacheService; + private readonly IRepository _menuRepository; + private readonly IRepository _repository; + private readonly IUserManager _userManager; + + public RoleMenuService( + IRepository repository, + IRepository menuRepository, + IUserManager userManager, + ICacheService cacheService) + { + _repository = repository; + _cacheService = cacheService; + _menuRepository = menuRepository; + _userManager = userManager; + } + + /// + /// 获取角色的菜单Id集合 + /// + /// + /// + [HttpGet] + public async Task> GetRoleMenuIdList(List roleIdList) + { + return await _repository.DetachedEntities + .Where(u => roleIdList.Contains(u.RoleId)) + .Select(u => u.MenuId).ToListAsync(); + } + + /// + /// 获取角色的菜单 + /// + /// + [HttpGet] + [Route("api/v1/menus")] + public async Task> GetRoleMenuListAsync() + { + var list = await _cacheService.GetRoleMenuAsync(_userManager.RoleId); + if (list.IsNull() || list.Count < 1) + { + var menus = await (from p in _repository.Where(x => x.RoleId == _userManager.RoleId && x.IsDeleted == false) + .AsQueryable() + join d in _menuRepository.AsQueryable().Include(u => u.Childrens) + .Where(x => x.ParentId == 0 && x.IsShow && x.IsDeleted == false).AsQueryable() on p.MenuId equals d.Id + select new Menu + { + Id = d.Id, + Name = d.Name, + ParentId = d.ParentId, + Layers = d.Layers, + Code = d.Code, + Type = d.Type, + Icon = d.Icon, + Router = d.Router, + Permission = d.Permission, + IsFrame = d.IsFrame, + IsExpand = d.IsExpand, + IsShow = d.IsShow, + Sort = d.Sort, + Childrens = d.Childrens + }).OrderBy(x => x.Sort).ToListAsync(); + + list = menus.Adapt>(); + await _cacheService.SetRoleMenuAsync(_userManager.RoleId, list); + } + return list; + } + + + /// + /// 同步角色菜单 + /// + /// + [HttpGet] + [AllowAnonymous] + public async Task> GetRoleMenuListByIdAsync(long roleId) + { + var list = await _cacheService.GetRoleMenuAsync(roleId); + if (list.IsNull() || list.Count < 1) + { + var menus = await (from p in _repository.Where(x => x.RoleId == roleId && x.IsDeleted == false) + .AsQueryable() + join d in _menuRepository.AsQueryable().Include(u => u.Childrens) + .Where(x => x.ParentId == 0 && x.IsShow == true && x.IsDeleted == false).AsQueryable() on p.MenuId equals d.Id + select new Menu + { + Id = d.Id, + Name = d.Name, + ParentId = d.ParentId, + Layers = d.Layers, + Code = d.Code, + Type = d.Type, + Icon = d.Icon, + Router = d.Router, + Permission = d.Permission, + IsFrame = d.IsFrame, + IsExpand = d.IsExpand, + IsShow = d.IsShow, + Sort = d.Sort, + Childrens = d.Childrens + }).OrderBy(x => x.Sort).ToListAsync(); + + list = menus.Adapt>(); + await _cacheService.SetRoleMenuAsync(roleId, list); + } + return list; + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/User/Dto/Input/DecryptPhoneAddInput.cs b/src/Znyc.Dispatching.Application/User/Dto/Input/DecryptPhoneAddInput.cs new file mode 100644 index 0000000..40ca9ac --- /dev/null +++ b/src/Znyc.Dispatching.Application/User/Dto/Input/DecryptPhoneAddInput.cs @@ -0,0 +1,23 @@ +namespace Znyc.Dispatching.Application +{ + /// + /// + /// + public class DecryptPhoneAddInput + { + /// + /// jscode + /// + public string JsCode { get; set; } + + /// + /// EncryptedData + /// + public string EncryptedData { get; set; } + + /// + /// Iv + /// + public string Iv { get; set; } + } +} diff --git a/src/Znyc.Dispatching.Application/User/Dto/Input/UserAddInput.cs b/src/Znyc.Dispatching.Application/User/Dto/Input/UserAddInput.cs new file mode 100644 index 0000000..8dbc27f --- /dev/null +++ b/src/Znyc.Dispatching.Application/User/Dto/Input/UserAddInput.cs @@ -0,0 +1,41 @@ +using System.ComponentModel.DataAnnotations; + +namespace Znyc.Dispatching.Application.Dto.Input +{ + /// + /// 用户输入实体 + /// + public class UserAddInput + { + /// + /// 用户名 + /// + [Required(ErrorMessage = "用户名不能为空")] + public string UserName { get; set; } + + /// + /// 头像 + /// + [Required(ErrorMessage = "头像不能为空")] + public string AvatarUrl { get; set; } + + /// + /// + /// + [Required(ErrorMessage = "DecryptPhone不能为空")] + public DecryptPhoneAddInput DecryptPhoneAddInput { get; set; } + + /// + /// 公司Id + /// + [Required(ErrorMessage = "请确认要加入的公司")] + public long CompanyId { get; set; } + + /// + /// 角色Id + /// + [Required(ErrorMessage = "请选择角色")] + public long RoleId { get; set; } + } + +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/User/Dto/Input/UserChangePwdInput.cs b/src/Znyc.Dispatching.Application/User/Dto/Input/UserChangePwdInput.cs new file mode 100644 index 0000000..7d71fbc --- /dev/null +++ b/src/Znyc.Dispatching.Application/User/Dto/Input/UserChangePwdInput.cs @@ -0,0 +1,37 @@ +using System.ComponentModel.DataAnnotations; + +namespace Znyc.Dispatching.Application.Dto.Input +{ + /// + /// 用户修改密码 + /// + public class UserChangePwdInput + { + /// + /// 手机号 + /// + [MaxLength(11, ErrorMessage = "手机号不能超过11位")] + [RegularExpression("^[1][3,4,5,6,7,8,9][0-9]{9}$", ErrorMessage = "请输入正确的手机号码")] + public string Account { get; set; } + + /// + /// 旧密码 + /// + public string OldPassWord { get; set; } + + /// + /// 新密码 + /// + public string PassWord { get; set; } + + /// + /// 确认密码 + /// + public string RePassWord { get; set; } + + /// + /// 验证码 + /// + public string VerifyCode { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/User/Dto/Input/UserUpdateInput.cs b/src/Znyc.Dispatching.Application/User/Dto/Input/UserUpdateInput.cs new file mode 100644 index 0000000..eea35de --- /dev/null +++ b/src/Znyc.Dispatching.Application/User/Dto/Input/UserUpdateInput.cs @@ -0,0 +1,32 @@ +using System.ComponentModel.DataAnnotations; + +namespace Znyc.Dispatching.Application.Dto.Input +{ + /// + /// 用户输入实体 + /// + public class UserUpdateInput + { + public long Id { get; set; } + + /// + /// 用户名 + /// + [MaxLength(8)] + [Required(ErrorMessage = "用户名不能为空")] + public string UserName { get; set; } + + /// + /// 头像 + /// + [Required(ErrorMessage = "头像不能为空")] + public string AvatarUrl { get; set; } + + /// + /// 电话 + /// + [MaxLength(11)] + [Required(ErrorMessage = "电话不能为空")] + public string Phone { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/User/Dto/Output/UserOutput.cs b/src/Znyc.Dispatching.Application/User/Dto/Output/UserOutput.cs new file mode 100644 index 0000000..dd55d12 --- /dev/null +++ b/src/Znyc.Dispatching.Application/User/Dto/Output/UserOutput.cs @@ -0,0 +1,42 @@ +namespace Znyc.Dispatching.Application +{ + /// + /// 用户输出实体 + /// + public class UserOutput + { + /// + /// + public long Id { get; set; } + + /// + /// + public string AvatarUrl { get; set; } + + /// + /// + public string UserName { get; set; } + + /// + /// + public long RoleId { get; set; } + + /// + /// + public string RoleName { get; set; } + + /// + /// + public long CompanyId { get; set; } + + /// + /// + public string CompanyName { get; set; } + + /// + /// 电话 + /// + public string Phone { get; set; } + + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/User/Services/IUserService.cs b/src/Znyc.Dispatching.Application/User/Services/IUserService.cs new file mode 100644 index 0000000..aa630d6 --- /dev/null +++ b/src/Znyc.Dispatching.Application/User/Services/IUserService.cs @@ -0,0 +1,22 @@ +using System.Threading.Tasks; +using Znyc.Dispatching.Application.Dto.Input; + +namespace Znyc.Dispatching.Application +{ + public interface IUserService + { + /// + /// 邀请用户注册并登陆 + /// + /// + /// + Task RegisterUserAsync(UserAddInput input); + + /// + /// 修改用户信息 + /// + /// + /// + Task UpdateAsync(UserUpdateInput input); + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/User/Services/UserService.cs b/src/Znyc.Dispatching.Application/User/Services/UserService.cs new file mode 100644 index 0000000..fb7ee02 --- /dev/null +++ b/src/Znyc.Dispatching.Application/User/Services/UserService.cs @@ -0,0 +1,440 @@ +using Furion.DatabaseAccessor; +using Furion.DataEncryption; +using Furion.DependencyInjection; +using Furion.DynamicApiController; +using Furion.FriendlyException; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Newtonsoft.Json; +using Senparc.Weixin.WxOpen.Helpers; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Threading.Tasks; +using Znyc.Dispatching.Application.Dto.Input; +using Znyc.Dispatching.Core; +using Znyc.Dispatching.Core.Entitys; +using Znyc.Dispatching.Core.Extension; +using Znyc.Dispatching.WeChat.Core.CommonService.TemplateMessage.WxOpen; + +namespace Znyc.Dispatching.Application +{ + /// + /// 用户服务 + /// + [ApiDescriptionSettings(Name = "user", Order = 10)] + public class UserService : IUserService, IDynamicApiController, ITransient + { + private readonly ICacheService _cacheService; + private readonly IRepository _companyRepository; + private readonly IRepository _employeeRepository; + private readonly IRepository _repository; + private readonly SmsProviderOptions _smsProviderOptions; + private readonly IUserManager _userManager; + private readonly WeixinSettingOptions _weixinSettingOptions; + private readonly IRepository _userRoleRepository; + private readonly IWxUserRelationService _wxUserRelationService; + private readonly IRoleMenuService _roleMenuService; + private readonly ILogger _logger; + private readonly IRepository _vehiclePersonRepository; + private readonly IRepository _projectPersonRepository; + public UserService( + IUserManager userManager, + ICacheService cacheService, + IRepository companyRepository, + IRepository repository, + IRepository employeeRepository, + IRepository userRoleRepository, + IOptions smsProviderOptions, + IWxUserRelationService wxUserRelationService, + IRoleMenuService roleMenuService, + IOptions weixinSettingOptions, + ILogger logger, + IRepository vehiclePersonRepository, + IRepository projectPersonRepository) + { + _repository = repository; + _userManager = userManager; + _cacheService = cacheService; + _companyRepository = companyRepository; + _employeeRepository = employeeRepository; + _smsProviderOptions = smsProviderOptions.Value; + _weixinSettingOptions = weixinSettingOptions.Value; + _userRoleRepository = userRoleRepository; + _wxUserRelationService = wxUserRelationService; + _roleMenuService = roleMenuService; + _logger = logger; + _vehiclePersonRepository = vehiclePersonRepository; + _projectPersonRepository = projectPersonRepository; + } + + /// + /// 个人资料 + /// + /// + [HttpGet] + [Route("api/v1/user")] + public async Task GetUserAsync() + { + var userOutput = await _cacheService.GetUserAsync(_userManager.UserId); + if (userOutput.IsNull()) + { + var user = await _repository.Where(x => x.Id == _userManager.UserId).Select(x => new + { + x.Id, + x.UserName, + x.Phone, + x.AvatarUrl + }).FirstOrDefaultAsync(); + + var employeeList = await _employeeRepository.Where(x => x.UserId == user.Id && x.Status == (int)CommonStatusEnum.ENABLE).ToListAsync(); + var cIds = employeeList.Select(x => x.CompanyId).ToArray(); + //有管理员优先登陆管理员 + if (employeeList.Any(x => x.RoleId == (int)RoleStatusEnum.Administrator)) + { + cIds = employeeList.Where(x => x.RoleId == (long)RoleStatusEnum.Administrator).Select(x => x.CompanyId).ToArray(); + } + var company = await _companyRepository.Where(x => cIds.Contains(x.Id) && x.Status == (int)CommonStatusEnum.ENABLE).Select(x => new + { + x.Id, + x.CompanyName + }).FirstOrDefaultAsync(); + var employee = employeeList.Where(x => x.CompanyId == company.Id).Select(x => new + { + x.RoleId, + x.RoleName, + x.CompanyId, + x.UserId, + x.EmployeeName, + x.IsDefault + }).FirstOrDefault(); + + userOutput = new UserOutput + { + Id = user.Id, + AvatarUrl = user.AvatarUrl, + UserName = employee.EmployeeName, + RoleId = employee.RoleId, + RoleName = employee.RoleName, + CompanyId = employee.CompanyId, + CompanyName = company.CompanyName, + Phone = user.Phone + }; + await _cacheService.SetUserAsync(userOutput); + } + return userOutput; + } + + /// + /// 邀请用户注册并登陆 + /// + /// + /// + [HttpPost] + [AllowAnonymous] + [UnitOfWork] + [Route("api/v1/user/register")] + public async Task RegisterUserAsync(UserAddInput input) + { + var jsCode2JsonResult = + await SnsApi.JsCode2JsonAsync(_weixinSettingOptions.WxOpenAppId, _weixinSettingOptions.WxOpenAppSecret, + input.DecryptPhoneAddInput.JsCode); + if (jsCode2JsonResult.IsNull()) + { + throw Oops.Bah("jsCode2JsonResult为空"); + } + var company = await _companyRepository.FindOrDefaultAsync(input.CompanyId); + if (company.IsNull()) throw Oops.Bah("公司不存在"); + + if (company.Status == (int)CommonStatusEnum.REVIEW) + { + throw Oops.Bah("您注册公司正在审核中,请稍等").StatusCode(4012); + } + if (company.Status == (int)CommonStatusEnum.DISABLE) + { + throw Oops.Bah("公司已被停用,请联系管理员"); + } + var user = await _repository.FirstOrDefaultAsync(x => x.OpenId == jsCode2JsonResult.openid); + var employee = new Employee(); + if (user.IsNull()) + { + //解密电话 + string phone = " "; + if (input.DecryptPhoneAddInput.EncryptedData.IsNotNull() && input.DecryptPhoneAddInput.Iv.IsNotNull()) + { + try + { + var rijndael = new RijndaelManaged + { + BlockSize = 128, + Mode = CipherMode.CBC, + Padding = PaddingMode.PKCS7 + }; + + var encryptedData = Convert.FromBase64String(input.DecryptPhoneAddInput.EncryptedData); + var key = Convert.FromBase64String(jsCode2JsonResult.session_key);//第一步获取到的session_key + var iv = Convert.FromBase64String(input.DecryptPhoneAddInput.Iv); + + var decryptor = rijndael.CreateDecryptor(key, iv); + using (var msDecrypt = new MemoryStream(encryptedData)) + { + using (var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read)) + { + using (var srDecrypt = new StreamReader(csDecrypt)) + { + var plaintext = srDecrypt.ReadToEnd(); + var json = Newtonsoft.Json.JsonConvert.DeserializeObject(plaintext); + var phoneNumber = json.phoneNumber; + var purePhoneNumber = json.purePhoneNumber; + //至此,成功获取到手机号 + phone = Convert.ToString(purePhoneNumber); + } + } + } + } + catch (Exception ex) + { + Console.WriteLine($"RegisterUserAsync:{ex.StackTrace}"); + _logger.LogError(ex.StackTrace); + } + } + + //用户 + var userEntity = await _repository.InsertNowAsync(new User + { + OpenId = jsCode2JsonResult.openid, + UserName = input.UserName, + AvatarUrl = input.AvatarUrl, + Phone = phone, + Status = CommonStatusEnum.ENABLE + }); + var isDefault = input.RoleId == (int)RoleStatusEnum.Outside ? false : true; + //员工 + var employeeEntity = await _employeeRepository.InsertNowAsync(new Employee + { + UserId = userEntity.Entity.Id, + AvatarUrl = input.AvatarUrl, + EmployeeName = input.UserName, + EmployeePhone = phone, + CompanyId = input.CompanyId, + RoleId = input.RoleId, + RoleName = typeof(RoleStatusEnum).GetDescription(input.RoleId.ObjToInt()), + IsDefault = isDefault, + Status = (int)CommonStatusEnum.ENABLE + }); + //用户角色 + //await _userRoleRepository.InsertNowAsync(new UserRole + //{ + // UserId = userEntity.Entity.Id, + // RoleId = input.RoleId + //}); + //清除员工列表Cache + await _cacheService.RemoveEmployeeListAsync(input.CompanyId); + //用户中间表 + await _wxUserRelationService.AddOrUpdateAsync(userEntity.Entity.Id, jsCode2JsonResult.openid, jsCode2JsonResult.unionid); + user = userEntity.Entity; + employee = employeeEntity.Entity; + } + else + { + employee = await _employeeRepository.Where(x => x.UserId == user.Id && x.Status == (int)CommonStatusEnum.ENABLE && x.CompanyId == input.CompanyId).FirstOrDefaultAsync(); + if (employee.IsNull()) + { + var employeenew = new Employee + { + UserId = user.Id, + AvatarUrl = input.AvatarUrl, + EmployeeName = input.UserName, + EmployeePhone = user.Phone, + CompanyId = input.CompanyId, + RoleId = input.RoleId, + RoleName = typeof(RoleStatusEnum).GetDescription((int)input.RoleId), + IsDefault = true, + Status = (int)CommonStatusEnum.ENABLE + }; + + Console.WriteLine($"{JsonConvert.SerializeObject(employeenew)}"); + var isDefault = input.RoleId == (int)RoleStatusEnum.Outside ? false : true; + + //员工 + var employeeEntity = await _employeeRepository.InsertNowAsync(new Employee + { + UserId = user.Id, + AvatarUrl = input.AvatarUrl, + EmployeeName = input.UserName, + EmployeePhone = user.Phone, + CompanyId = input.CompanyId, + RoleId = input.RoleId, + RoleName = typeof(RoleStatusEnum).GetDescription(input.RoleId.ObjToInt()), + IsDefault = isDefault, + Status = (int)CommonStatusEnum.ENABLE + }); + employee = employeeEntity.Entity; + company = new Company() { Id = input.CompanyId }; + } + } + // 生成 token + string accessToken = JWTEncryption.Encrypt(new Dictionary + { + {ClaimConst.CLAINM_USERID, user.Id}, + {ClaimConst.CLAINM_COMPANYID, company.Id}, + {ClaimConst.CLAINM_ROLEID, employee.RoleId}, + {ClaimConst.CLAINM_USERNAME, user.UserName}, + {ClaimConst.CLAINM_SUPERADMIN,new long[] { (long)RoleStatusEnum.Administrator, (long)RoleStatusEnum.Scheduling,(long)RoleStatusEnum.CarCaptain }.Contains(employee.RoleId) } + }); + //RedisToken + await _cacheService.SetTokenAsync(user.Id, accessToken); + + await _cacheService.RemoveEmployeeListAsync(input.CompanyId); + LoginOutput loginOutput = new LoginOutput() + { + Token = accessToken, + Menus = await _roleMenuService.GetRoleMenuListByIdAsync(employee.RoleId) + }; + switch (employee.RoleId) + { + case (long)RoleStatusEnum.Administrator: + case (long)RoleStatusEnum.Scheduling: + case (long)RoleStatusEnum.CarCaptain: + case (long)RoleStatusEnum.Salesman: + case (long)RoleStatusEnum.Financial: + case (long)RoleStatusEnum.WareHouse: + case (long)RoleStatusEnum.PartTimeSalesman: + loginOutput.Url = CommonConst.DEFAULT_SUPERADMIN_INDEX; + break; + case (long)RoleStatusEnum.CrewMembers: + case (long)RoleStatusEnum.ProjectPerson: + loginOutput.Url = CommonConst.DEFAULT_DRIVER_INDEX; + break; + case (long)RoleStatusEnum.Outside: + loginOutput.Url = CommonConst.DEFAULT_OUTSIDE_INDEX; + break; + default: + loginOutput.Url = CommonConst.DEFAULT_DRIVER_INDEX; + break; + } + return loginOutput; + + + } + + /// + /// 修改用户信息 + /// + /// + /// + [HttpPut] + [Route("api/v1/user")] + public async Task UpdateAsync(UserUpdateInput input) + { + var user = await _repository.FirstOrDefaultAsync(x => x.Id == input.Id); + if (user.IsNull()) + { + throw Oops.Oh("用户不存在"); + } + var repeatPhone = await _repository.DetachedEntities.FirstOrDefaultAsync(x => x.Id != input.Id && x.Phone == input.Phone); + if (repeatPhone.IsNotNull()) + { + throw Oops.Bah("电话已存在"); + } + user.Phone = input.Phone; + user.UserName = input.UserName; + user.AvatarUrl = CommonConst.Prefix_AvataUrl + input.AvatarUrl[(input.AvatarUrl.LastIndexOf("/") + 1)..]; + await _repository.UpdateNowAsync(user); + + //更新当前用户下所有角色姓名 + var employees =await _employeeRepository.Where(x => x.UserId == user.Id).ToListAsync(); + foreach (var employee in employees) + { + if (employee.IsNotNull()) + { + employee.EmployeePhone = input.Phone; + employee.EmployeeName = input.UserName; + employee.AvatarUrl = CommonConst.Prefix_AvataUrl + input.AvatarUrl[(input.AvatarUrl.LastIndexOf("/") + 1)..]; + await _employeeRepository.UpdateNowAsync(employee); + await _cacheService.RemoveEmployeeAsync(employee.Id); + await _cacheService.RemoveEmployeeListAsync(employee.CompanyId); + } + } + await _cacheService.RemoveUserAsync(user.Id); + //同步车组人员表 + var vehiclePersons = await _vehiclePersonRepository.Where(x => x.IsDeleted == false && x.UserId == input.Id).ToListAsync(); + foreach (var item in vehiclePersons) + { + item.UserName = input.UserName; + item.UserPhone = input.Phone; + await _vehiclePersonRepository.UpdateNowAsync(item); + //删除车组人员Cache + await _cacheService.RemoveVehiclePersonAsync(item.VehicleId); + } + //同步工程联系人表 + var projectPersons = await _projectPersonRepository.Where(x => x.IsDeleted == false && x.ProjectPersonId == input.Id).ToListAsync(); + foreach (var item in projectPersons) + { + item.ProjectPersonName = input.UserName; + item.ProjectPersonPhone = input.Phone; + await _projectPersonRepository.UpdateNowAsync(item); + //清除工程信息Cache + await _cacheService.RemoveProjectAsync(item.ProjectId); + //清除工程列表信息Cache + await _cacheService.RemoveProjectListAsync(_userManager.CompanyId); + } + } + + + + + + /// + /// 更新用户信息 + /// + /// + /// + /// + [HttpPut] + [AllowAnonymous] + public async Task UpdateAsync(string openId, string account) + { + //var user = await _repository.FirstOrDefaultAsync(x => x.Account == account); + //if (user.IsNotNull()) + //{ + // user.OpenId = openId; + // await _repository.UpdateNowAsync(user); + //} + } + + + + /// + /// 解密电话号码 + /// + /// + /// + [HttpPost] + [AllowAnonymous] + [Route("api/v1/decryptphone")] + public async Task DecryptPhoneNumber([FromBody] DecryptPhoneAddInput decryptPhone) + { + var jsCode2JsonResult = await SnsApi.JsCode2JsonAsync(_weixinSettingOptions.WxOpenAppId, _weixinSettingOptions.WxOpenAppSecret, decryptPhone.JsCode); + if (jsCode2JsonResult.IsNull()) + { + throw Oops.Oh("jsCode2JsonResult为空!"); + } + string phone = ""; + if (decryptPhone.EncryptedData.IsNotNull() && decryptPhone.Iv.IsNotNull()) + { + var phoneNumber = EncryptHelper.DecryptPhoneNumberBySessionKey(jsCode2JsonResult.session_key, decryptPhone.EncryptedData, decryptPhone.Iv); + if (phoneNumber.IsNull()) + { + phone = phoneNumber.purePhoneNumber; + } + } + return phone; + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/UserRole/Services/IUserRoleService.cs b/src/Znyc.Dispatching.Application/UserRole/Services/IUserRoleService.cs new file mode 100644 index 0000000..6adb413 --- /dev/null +++ b/src/Znyc.Dispatching.Application/UserRole/Services/IUserRoleService.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Znyc.Dispatching.Application +{ + public interface IUserRoleService + { + Task> GetUserRoleIdList(long userId); + + + Task AddAsync(); + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/UserRole/Services/UserRoleService.cs b/src/Znyc.Dispatching.Application/UserRole/Services/UserRoleService.cs new file mode 100644 index 0000000..7ddb2c3 --- /dev/null +++ b/src/Znyc.Dispatching.Application/UserRole/Services/UserRoleService.cs @@ -0,0 +1,61 @@ +using Furion.DatabaseAccessor; +using Furion.DependencyInjection; +using Furion.DynamicApiController; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Znyc.Dispatching.Core; +using Znyc.Dispatching.Core.Entitys; + +namespace Znyc.Dispatching.Application +{ + /// + /// 用户角色服务 + /// + [ApiDescriptionSettings(Name = "userrole", Order = 50)] + public class UserRoleService : IUserRoleService, IDynamicApiController, ITransient + { + private readonly IRepository _repository; + private readonly IUserManager _userManager; + + public UserRoleService( + IRepository repository, + IUserManager userManager + ) + { + _repository = repository; + _userManager = userManager; + } + + /// + /// 获取用户的角色Id集合 + /// + /// + /// + [NonAction] + public async Task> GetUserRoleIdList(long userId) + { + return await _repository.Where(u => u.UserId == userId).Select(u => u.RoleId) + .ToListAsync(); + } + + /// + /// + /// + /// + [HttpGet] + [AllowAnonymous] + public async Task AddAsync() + { + UserRole userRole = new UserRole() + { + UserId = 123123123, + RoleId = CommonConst.DEFAULT_ROLEID_1001 + }; + await _repository.InsertNowAsync(userRole); + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Vehicle/Dto/Input/BasicsVehicleUpdateInput.cs b/src/Znyc.Dispatching.Application/Vehicle/Dto/Input/BasicsVehicleUpdateInput.cs new file mode 100644 index 0000000..6fa89a3 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Vehicle/Dto/Input/BasicsVehicleUpdateInput.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace Znyc.Dispatching.Application +{ + /// + /// 基础信息-车辆编辑 + /// + public class BasicsVehicleUpdateInput + { + public long Id { get; set; } + + /// + /// 车辆编号 + /// + [Required(ErrorMessage = "请输入设备名称!")] + [MaxLength(8, ErrorMessage = "设备名称不能超过8位")] + public string VehicleCode { get; set; } + + /// + /// 车牌号 + /// + [Required(ErrorMessage = "请输入车牌号!")] + [MaxLength(8, ErrorMessage = "车牌号不能超过8位")] + public string VehiclePlate { get; set; } + + /// + /// 状态 + /// + public int Status { get; set; } + + /// + /// 车组人员 + /// + public List VehiclePersons { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Vehicle/Dto/Input/TrackPlayBackCorrectInput.cs b/src/Znyc.Dispatching.Application/Vehicle/Dto/Input/TrackPlayBackCorrectInput.cs new file mode 100644 index 0000000..374bd6f --- /dev/null +++ b/src/Znyc.Dispatching.Application/Vehicle/Dto/Input/TrackPlayBackCorrectInput.cs @@ -0,0 +1,43 @@ +using Newtonsoft.Json; + +namespace Znyc.Dispatching.Application +{ + + /// + /// 轨迹纠偏入参 + /// + public class TrackPlayBackCorrectInput + { + /// + /// 维度 + /// + [JsonProperty("y")] + public decimal Latitude { get; set; } + + /// + /// 精度 + /// + [JsonProperty("x")] + public decimal Longitude { get; set; } + + /// + /// 角度 + /// + [JsonProperty("ag")] + public int Direct { get; set; } + + /// + /// 速度 + /// + [JsonProperty("sp")] + public int Speed { get; set; } + + /// + /// 时间 + /// + [JsonProperty("tm")] + public int Timestamp { get; set; } + + + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Vehicle/Dto/Input/VehicleAddInput.cs b/src/Znyc.Dispatching.Application/Vehicle/Dto/Input/VehicleAddInput.cs new file mode 100644 index 0000000..42b7cc2 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Vehicle/Dto/Input/VehicleAddInput.cs @@ -0,0 +1,95 @@ +using System; +using Znyc.Dispatching.Core; + +namespace Znyc.Dispatching.Application +{ + public class VehicleAddInput + { + /// + /// 所属公司 + /// + public long CompanyId { get; set; } + + /// + /// 车辆编号 + /// + public string VehicleCode { get; set; } + + /// + /// 车牌号 + /// + public string VehiclePlate { get; set; } + + /// + /// 车辆类型 + /// + public long VehicleType { get; set; } + + /// + /// 所属车组 + /// + public long VehicleGroup { get; set; } + + /// + /// SIM卡号 + /// + public long SimNo { get; set; } + + /// + /// 设备类型 + /// + public long TerminalType { get; set; } + + /// + /// 设备号 + /// + public long TerminalNo { get; set; } + + /// + /// 联系人 + /// + public string ContactPerson { get; set; } + + /// + /// 司机电话 + /// + public string ContactPhone { get; set; } + + /// + /// 状态(字典 0正常 1停用 2删除) + /// + public CommonStatusEnum Status { get; set; } = CommonStatusEnum.ENABLE; + + /// + /// 打火状态 + /// + public AccStatusEnum Acc { get; set; } = AccStatusEnum.NORMAL; + + /// + /// 工作状态 + /// + public WorkStatusEnum Work { get; set; } = WorkStatusEnum.NORMAL; + + /// + /// IEML号 + /// + public string ImeiNo { get; set; } + + /// + /// GPS时间 + /// + public DateTime? GpsTime { get; set; } + + /// + /// 开通时间 + /// + public DateTime OpenTime { get; set; } + + /// + /// 到期时间 + /// + public DateTime ExpireTime { get; set; } + + public int CardType { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Vehicle/Dto/Input/VehicleUpdateInput.cs b/src/Znyc.Dispatching.Application/Vehicle/Dto/Input/VehicleUpdateInput.cs new file mode 100644 index 0000000..4f6cde2 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Vehicle/Dto/Input/VehicleUpdateInput.cs @@ -0,0 +1,36 @@ +using System.ComponentModel.DataAnnotations; + +namespace Znyc.Dispatching.Application +{ + public class VehicleUpdateInput + { + public long Id { get; set; } + + /// + /// 车辆编号 + /// + [Required(ErrorMessage = "车辆编号不能为空!")] + [MaxLength(8, ErrorMessage = "车辆编号不能超过8位")] + public string VehicleCode { get; set; } + + /// + /// 车牌号 + /// + [Required(ErrorMessage = "车牌号不能为空!")] + [MaxLength(8, ErrorMessage = "车牌号不能超过8位")] + public string VehiclePlate { get; set; } + + /// + /// 联系人 + /// + [MaxLength(8, ErrorMessage = "联系人过长")] + public string ContactPerson { get; set; } + + /// + /// 联系人电话 + /// + [MaxLength(11, ErrorMessage = "联系电话不能超过11位")] + [RegularExpression("^[1][3,4,5,6,7,8,9][0-9]{9}$", ErrorMessage = "请输入正确的手机号码")] + public string ContactPhone { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Vehicle/Dto/OutPut/AlarmOutput.cs b/src/Znyc.Dispatching.Application/Vehicle/Dto/OutPut/AlarmOutput.cs new file mode 100644 index 0000000..904f3b1 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Vehicle/Dto/OutPut/AlarmOutput.cs @@ -0,0 +1,27 @@ +using System; + +namespace Znyc.Dispatching.Application +{ + public class AlarmOutput + { + /// + /// 超速最新未读时间 + /// + public DateTime? OverspeedTime { get; set; } + + /// + /// 超速未读数 + /// + public int OverspeedCount { get; set; } + + /// + /// 自编号 + /// + public string VehicleCode { get; set; } + + /// + /// 车牌号 + /// + public string VehiclePlate { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Vehicle/Dto/OutPut/MapMonitorDataOutput.cs b/src/Znyc.Dispatching.Application/Vehicle/Dto/OutPut/MapMonitorDataOutput.cs new file mode 100644 index 0000000..f704aeb --- /dev/null +++ b/src/Znyc.Dispatching.Application/Vehicle/Dto/OutPut/MapMonitorDataOutput.cs @@ -0,0 +1,242 @@ +using System; + +namespace Znyc.Dispatching.Application +{ + /// + /// 地图 + /// + public class MapMonitorDataOutput + { + public long Id { get; set; } + + /// + /// 车辆编号 + /// + public string VehicleCode { get; set; } + + /// + /// 维度 + /// + public decimal Latitude { get; set; } + + /// + /// 精度 + /// + public decimal Longitude { get; set; } + + + /// + /// 图标 + /// + public string IconPath { get; set; } + + + /// + /// + /// + public string Width { get; set; } + + + /// + /// + /// + public string Height { get; set; } + + + + public int Rotate { get; set; } + + + + /// + /// + /// + public Anchor Anchor { get; set; } + + + + /// + /// + /// + public CustomCallout CustomCallout { get; set; } + + + + + ///// + ///// 所属公司 + ///// + //public long CompanyId { get; set; } + + + ///// + ///// 车牌号 + ///// + //public string VehiclePlate { get; set; } + + ///// + ///// 联系人电话 + ///// + //public long VehicleType { get; set; } + + ///// + ///// 联系人 + ///// + //public string ContactPerson { get; set; } + + ///// + ///// 联系电话 + ///// + //public string ContactPhone { get; set; } + + ///// + ///// SimNo + ///// + //public string SimNo { get; set; } + + ///// + ///// 状态(字典 0正常 1停用 2删除) + ///// + //public int Status { get; set; } = 0; + + ///// + ///// 打火状态 + ///// + //public int Acc { get; set; } = 0; + + ///// + ///// 工作状态 + ///// + //public int Work { get; set; } = 0; + + ///// + ///// GPS时间 + ///// + //public string GpsTime { get; set; } + + ///// + ///// Gps服务器接收时间 + ///// + //public string RecTime { get; set; } + + ///// + ///// gps状态 + ///// + //public int GpsState { get; set; } = 0; + + ///// + ///// 方向 + ///// + //public int Direct { get; set; } = 0; + + ///// + ///// 速度 + ///// + //public int Speed { get; set; } = 0; + + ///// + ///// 静止时长 + ///// + //public string DurationTime { get; set; } + + ///// + ///// acc工作时长 + ///// + //public string AccDurationTime { get; set; } + + + + + + ///// + ///// 地点 + ///// + //public string Address { get; set; } + + ///// + ///// work工作时长 + ///// + //public string WorkDurationTime { get; set; } + + ///// + ///// 离线时长 + ///// + //public string OfflineTime { get; set; } + + ///// + ///// 更新时间 + ///// + //public DateTime ModifiedTime { get; set; } + + ///// + ///// IMEL号 + ///// + //public string ImeiNo { get; set; } + + + ///// + ///// 设备类型 + ///// + //public int TerminalType { get; set; } + + ///// + ///// 开通时间 + ///// + //public DateTime OpenTime { get; set; } + + ///// + ///// 到期时间 + ///// + //public DateTime ExpireTime { get; set; } + + ///// + ///// 设备类型 + ///// + //public string TerminalTypeName { get; set; } + + ///// + ///// Gps信号源 + ///// + //public string GpsSignalSource { get; set; } + + + ///// + ///// Gps信号模式 1 GPS,2 基站 + ///// + //public int GpsMode { get; set; } + + ///// + ///// 位置基本信息报警标志位 --欠压 + ///// + //public int GpsAlert { get; set; } + + ///// + ///// 掉电 + ///// + //public int Useing { get; set; } + + + } + + public class Anchor + { + public string X { get; set; } + + public string Y { get; set; } + + } + + public class CustomCallout + { + + public string AnchorY { get; set; } + + public string AnchorX { get; set; } + + public string Display { get; set; } = "ALWAYS"; + + + + } + +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Vehicle/Dto/OutPut/PgyAlertStraightDtlOutput.cs b/src/Znyc.Dispatching.Application/Vehicle/Dto/OutPut/PgyAlertStraightDtlOutput.cs new file mode 100644 index 0000000..a00ded3 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Vehicle/Dto/OutPut/PgyAlertStraightDtlOutput.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; + +namespace Znyc.Dispatching.Application +{ + public class PgyAlertStraightDtlListOutput + { + /// + /// 总数 + /// + public int TotalCount { get; set; } + + /// + /// 超速记录输出集合 + /// + public List List { get; set; } + } + + /// + /// 超速记录输出实体 + /// + [Serializable] + public class PgyAlertStraightDtlOutput + { + /// + /// 所属车辆 + /// + public long VehicleId { get; set; } + + /// + /// 所属公司 + /// + public long CompanyId { get; set; } + + /// + /// 车辆编号 + /// + public string VehicleCode { get; set; } + + /// + /// GPS时间 + /// + public DateTime GpsTime { get; set; } + + /// + /// 车牌号 + /// + public string VehiclePlate { get; set; } + + /// + /// 经度 + /// + public decimal Longitude { get; set; } + + /// + /// 纬度 + /// + public decimal Latitude { get; set; } + + /// + /// 方向 + /// + public int Direct { get; set; } + + /// + /// 速度 + /// + public int Speed { get; set; } = 0; + + /// + /// 地点 + /// + public string Address { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Vehicle/Dto/OutPut/TrackPlayBackCorrectOutput.cs b/src/Znyc.Dispatching.Application/Vehicle/Dto/OutPut/TrackPlayBackCorrectOutput.cs new file mode 100644 index 0000000..88f7a28 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Vehicle/Dto/OutPut/TrackPlayBackCorrectOutput.cs @@ -0,0 +1,76 @@ +using Newtonsoft.Json; +using System.Collections.Generic; + +namespace Znyc.Dispatching.Application +{ + + /// + /// 轨迹纠偏出参 + /// + public class TrackPlayBackCorrectOutput + { + + /// + /// 数据体 + /// + [JsonProperty("data")] + public Data data { get; set; } + + /// + /// 30001错误表示抓路失败。当传入点数较少或较稀疏时,可能会导致抓路失败。 + /// + [JsonProperty("errcode")] + public int ErrCode { get; set; } + + /// + /// + /// + [JsonProperty("errdetail")] + public string ErrDetail { get; set; } + + /// + /// + /// + [JsonProperty("errmsg")] + public string ErrMsg { get; set; } + + + } + + /// + /// 数据体 + /// + public class Data + { + /// + /// 总距离 + /// + [JsonProperty("distance")] + public decimal Distance { get; set; } + + + /// + /// 返回坐标合集 + /// + [JsonProperty("points")] + public List points { get; set; } + } + + /// + /// 返回坐标合集 + /// + public class Points + { + /// + /// 维度 + /// + [JsonProperty("y")] + public decimal Latitude { get; set; } + + /// + /// 经度 + /// + [JsonProperty("x")] + public decimal Longitude { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Vehicle/Dto/OutPut/TrackPlaybackOutput.cs b/src/Znyc.Dispatching.Application/Vehicle/Dto/OutPut/TrackPlaybackOutput.cs new file mode 100644 index 0000000..77e479b --- /dev/null +++ b/src/Znyc.Dispatching.Application/Vehicle/Dto/OutPut/TrackPlaybackOutput.cs @@ -0,0 +1,184 @@ +using System; +using System.Collections.Generic; + +namespace Znyc.Dispatching.Application +{ + public class TrackPlaybackListOutput + { + /// + /// 车辆编号 + /// + public string VehicleCode { get; set; } + + /// + /// 车牌号 + /// + public string VehiclePlate { get; set; } + + /// + /// 总公里 + /// + public decimal TotalDistance { get; set; } + + /// + /// 总停留时间 + /// + public string TotalStopTime { get; set; } + + /// + /// 开始时间 + /// + //public DateTime StartTime { get; set; } + + /// + /// 结束时间 + /// + //public DateTime EndTime { get; set; } + + /// + /// 轨迹点输出实体 + /// + public List trackPlaybackOutputs { get; set; } + + /// + /// 停留点输出实体 + /// + public List Stops { get; set; } + } + + public class TrackPlaybackOutput + { + /// + /// 经度 + /// + public decimal Longitude { get; set; } + + /// + /// 纬度 + /// + public decimal Latitude { get; set; } + + /// + /// GPS时间 + /// + public DateTime GpsTime { get; set; } + + /// + /// 速度 + /// + public int Speed { get; set; } + + /// + /// 地址 + /// + public string Address { get; set; } + + /// + /// 方向 + /// + public int Direct { get; set; } + + /// + /// 开始时间 + /// + public DateTime StartTime { get; set; } + + /// + /// 结束时间 + /// + public DateTime EndTime { get; set; } + + /// + /// 静止时长 + /// + public string DurationTime { get; set; } + + /// + /// 是否是停留点 + /// + public bool IsStopPoint { get; set; } + + /// + /// 停留次数 + /// + public int StopPointIdx { get; set; } + + /// + /// 打火状态 + /// + public int Acc { get; set; } + + /// + /// ACC持续时长 + /// + public string AccDurationTime { get; set; } + + /// + /// 工作状态 + /// + public int Work { get; set; } + + /// + /// 工作持续时长 + /// + public string WorkDurationTime { get; set; } + + /// + /// 总里程 + /// + public decimal TotalDistance { get; set; } + } + + public class Stop + { + /// + /// 经度 + /// + public decimal Longitude { get; set; } + + /// + /// 纬度 + /// + public decimal Latitude { get; set; } + + /// + /// 地址 + /// + public string Address { get; set; } + + /// + /// 开始时间 + /// + public DateTime StartTime { get; set; } + + /// + /// 结束时间 + /// + public DateTime EndTime { get; set; } + + /// + /// GPS时间 + /// + public DateTime GpsTime { get; set; } + + /// + /// 状态 + /// + public int Status { get; set; } + + /// + /// 停留点描述 + /// + public string State { get; set; } + + /// + /// 停留时长描述 + /// + public string Behavior { get; set; } + + /// + /// 总里程 + /// + public decimal TotalDistance { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Vehicle/Dto/OutPut/VehicleBaiscListOutput.cs b/src/Znyc.Dispatching.Application/Vehicle/Dto/OutPut/VehicleBaiscListOutput.cs new file mode 100644 index 0000000..121168a --- /dev/null +++ b/src/Znyc.Dispatching.Application/Vehicle/Dto/OutPut/VehicleBaiscListOutput.cs @@ -0,0 +1,57 @@ +using System.Collections.Generic; + +namespace Znyc.Dispatching.Application +{ + public class VehicleBaiscListOutput + { + public long Id { get; set; } + + /// + /// 车辆编号 + /// + public string VehicleCode { get; set; } + + /// + /// 车牌号 + /// + public string VehiclePlate { get; set; } + + /// + /// 状态 + /// + public int Status { get; set; } + + /// + /// 车组人员 + /// + public List VehiclePerson { get; set; } + + /// + /// 车组人员姓名 + /// + public string VehiclePersonName { get; set; } + + /// + /// 车组人员电话 + /// + public string VehiclePersonPhone { get; set; } + } + + public class VehicleBaiscListOutputs + { + public string Char { get; set; } + + public List List { get; set; } + + public List VehicleGpsList { get; set; } + + } + + public class VehicleBaiscListPage + { + public long Total { get; set; } + + public List VehicleBaiscList { get; set; } + } + +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Vehicle/Dto/OutPut/VehicleGpsOutput.cs b/src/Znyc.Dispatching.Application/Vehicle/Dto/OutPut/VehicleGpsOutput.cs new file mode 100644 index 0000000..ff8897f --- /dev/null +++ b/src/Znyc.Dispatching.Application/Vehicle/Dto/OutPut/VehicleGpsOutput.cs @@ -0,0 +1,207 @@ +using System; + +namespace Znyc.Dispatching.Application +{ + public class VehicleGpsOutput + { + public long Id { get; set; } + + /// + /// 所属公司 + /// + public long CompanyId { get; set; } + + /// + /// 车辆编号 + /// + public string VehicleCode { get; set; } + + /// + /// 车牌号 + /// + public string VehiclePlate { get; set; } + + /// + /// 联系人 + /// + public string ContactPerson { get; set; } + + /// + /// 联系电话 + /// + public string ContactPhone { get; set; } + + /// + /// 状态(字典 1正常 -1停用 -2删除 0审核中) + /// + public int Status { get; set; } + + + /// + /// 打火状态 + /// + public int Acc { get; set; } + + /// + /// 工作状态 + /// + public int Work { get; set; } + + /// + /// Gps状态 + /// + public int GpsState { get; set; } + + /// + /// Gps状态名称 + /// + public string GpsStateName { get; set; } + + /// + /// GPS时间 + /// + public string GpsTime { get; set; } + + /// + /// 最后通讯时间 + /// + public string RecTime { get; set; } + + /// + /// 速度 + /// + public int Speed { get; set; } + + /// + /// 地址 + /// + public string Address { get; set; } + + /// + /// ACC持续时长 + /// + public string AccDurationTime { get; set; } + + /// + /// 经度 + /// + public decimal Longitude { get; set; } + + /// + /// 纬度 + /// + public decimal Latitude { get; set; } + + /// + /// IMEI号 + /// + public string ImeiNo { get; set; } + + /// + /// 设备类型 + /// + public int TerminalType { get; set; } + + /// + /// 开通时间 + /// + public DateTime OpenTime { get; set; } + + /// + /// 到期时间 + /// + public DateTime ExpireTime { get; set; } + + /// + /// 设备类型 + /// + public string TerminalTypeName { get; set; } + + /// + /// 离线时长 + /// + public string OfflineTime { get; set; } + + + /// + /// work工作时长 + /// + public string WorkDurationTime { get; set; } + + /// + /// 静止时长 + /// + public string DurationTime { get; set; } + + /// + /// 是否开启超速报警 + /// + public bool IsOverspeedAlarm { get; set; } + + /// + /// 超速速度 + /// + public int Overspeed { get; set; } + + /// + /// 是否激活 + /// + public bool IsActivate { get; set; } + + /// + /// 是否开启GPS + /// + public bool IsGps { get; set; } + + /// + /// Sim卡号 + /// + public string SimNo { get; set; } + + /// + /// 更新时间 + /// + public DateTime ModifiedTime { get; set; } + + /// + /// Gps信号模式 1 GPS,2 基站 + /// + public int GpsMode { get; set; } + + /// + /// 车辆任务状态 + /// + public int VehicleTaskStatus { get; set; } + + /// + /// 车组人员 + /// + //public List VehiclePerson { get; set; } + public string VehiclePerson { get; set; } + + /// + /// 位置基本信息报警标志位 --欠压 + /// + public int GpsAlert { get; set; } + + /// + /// Gps信号源 + /// + public string GpsSignalSource { get; set; } + + /// + /// 掉电 + /// + public int Useing { get; set; } + + /// + /// 排序 + /// + public int Sort { get; set; } + /// + /// 方向 + /// + public int Direct { get; set; } = 0; + + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Vehicle/Dto/OutPut/VehicleListOutput.cs b/src/Znyc.Dispatching.Application/Vehicle/Dto/OutPut/VehicleListOutput.cs new file mode 100644 index 0000000..4c15bd3 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Vehicle/Dto/OutPut/VehicleListOutput.cs @@ -0,0 +1,89 @@ +using System.Collections.Generic; + +namespace Znyc.Dispatching.Application +{ + public class VehicleListOutput + { + /// + /// 全部数 + /// + public int AllCount { get; set; } + + /// + /// 离线数 + /// + public int OfflineCount { get; set; } + + /// + /// 静止数 + /// + public int StillCount { get; set; } + + /// + /// 行驶数 + /// + public int DrivingCount { get; set; } + + public List vehicleGpsOutput { get; set; } + } + + //public class VehicleOutput + //{ + // public long Id { get; set; } + + // /// + // /// 所属公司 + // /// + // public long CompanyId { get; set; } + + // /// + // /// 车辆编号 + // /// + // public string VehicleCode { get; set; } + + // /// + // /// 车牌号 + // /// + // public string VehiclePlate { get; set; } + + // /// + // /// 联系人 + // /// + // public string VehicleDriverName { get; set; } + + // /// + // /// 司机电话 + // /// + // public string DriverPhone { get; set; } + + // /// + // /// 状态(字典 0正常 1停用 2删除) + // /// + // public int Status { get; set; } + + // /// + // /// gps状态 + // /// + // public int GpsState { get; set; } + + // /// + // /// 速度 + // /// + // public int Speed { get; set; } + + // /// + // /// 工作时长 + // /// + // public string DurationTime { get; set; } + + // /// + // /// acc工作时长 + // /// + // public string AccDurationTime { get; set; } + + // /// + // /// work工作时长 + // /// + // public string WorkDurationTime { get; set; } + //} +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Vehicle/Services/IVehicleService.cs b/src/Znyc.Dispatching.Application/Vehicle/Services/IVehicleService.cs new file mode 100644 index 0000000..86a24c1 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Vehicle/Services/IVehicleService.cs @@ -0,0 +1,125 @@ +using Microsoft.AspNetCore.Mvc; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Threading.Tasks; + +namespace Znyc.Dispatching.Application +{ + public interface IVehicleService + { + + #region v1.2.0 + /// + /// 根据id获取车辆Gps信息 + /// + /// + /// + Task GetVehicleGpsByIdAsync(long vehicleId); + + /// + /// 获取地图信息 + /// + /// + Task> GetMapMonitorAsync(); + + /// + /// 分页查询 + /// + /// + /// + /// + /// + /// + //Task PageAsync([Required] int gpsState, [Required] int currentPage, [Required] int pageSize, [FromQuery] string key); + + /// + /// 更新车辆信息 + /// + /// + /// + Task UpdateAsync(VehicleUpdateInput input); + + /// + /// 轨迹回放纠偏 + /// + /// + /// + /// + /// + Task TrackPlaybackCorrectionAsync(long vehicleId, DateTime startTime, DateTime endTime); + + + /// + /// 修改超速报警/速度 + /// + /// + /// + /// + /// + Task UpdateOverSpeedAsync(long vehicleId, bool isOverspeedAlarm, int overspeed); + + /// + /// 超速记录 + /// + /// + /// + /// + Task MbAlertSpeedByTime([Required] int currentPage = 1, + [Required] int pageSize = 10); + + /// + /// 未读报警消息数 + /// + /// + Task UnreadAlarmCountAsync(); + + + /// + /// 同步轨迹 + /// + /// + Task SyncTrackPlaybackCorrectionAsync(DateTime startTime, DateTime endTime, string type); + + #endregion + + + + #region v1.2.1 + /// + /// 基础数据-车辆管理列表 + /// + /// + /// + /// + /// + /// + Task BasicsPageAsync([Required] int currentPage, [Required] int pageSize, [FromQuery] int status, [FromQuery] string key); + + /// + /// 基础数据-车辆信息 + /// + /// + /// + Task GetVehicleBasicsByIdAsync(long vehicleId); + + /// + /// 更新车辆基础信息 + /// + /// + /// + Task BasicsUpdateAsync(BasicsVehicleUpdateInput input); + + /// + /// 分页查询 + /// + /// + /// 0全部,1任务中,2空闲,3停用 + /// + /// + /// + Task PageAsync([Required] int status = 0, [Required] int currentPage = 1, + [Required] int pageSize = 10, [FromQuery] string key = null); + #endregion + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Vehicle/Services/VehicleService.cs b/src/Znyc.Dispatching.Application/Vehicle/Services/VehicleService.cs new file mode 100644 index 0000000..f547f1f --- /dev/null +++ b/src/Znyc.Dispatching.Application/Vehicle/Services/VehicleService.cs @@ -0,0 +1,1255 @@ +using Furion.DatabaseAccessor; +using Furion.DatabaseAccessor.Extensions; +using Furion.DataValidation; +using Furion.DependencyInjection; +using Furion.DynamicApiController; +using Furion.FriendlyException; +using Mapster; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using MongoDB.Driver; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; +using Znyc.Dispatching.Application.Services; +using Znyc.Dispatching.Common.Extensions; +using Znyc.Dispatching.Core; +using Znyc.Dispatching.Core.Entitys; +using Znyc.Dispatching.Core.Extension; +using Znyc.Dispatching.Core.Helpers; +using Znyc.Dispatching.MongoDb.Repository.Collection; +using Znyc.Dispatching.MongoDb.Repository.Repositorys; + +namespace Znyc.Dispatching.Application +{ + /// + /// 车辆信息服务 + /// + [ApiDescriptionSettings(Name = "vehicle", Order = 146)] + public class VehicleService : IVehicleService, IDynamicApiController, ITransient + { + private string[] INDEX_STRINGS = {"A", "B", "C", "D", "E", "F", "G", "H", "I", + "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", + "W", "X", "Y", "Z", "#"}; + private readonly ICacheService _cacheService; + private readonly IRepository _employeeRepository; + private readonly IGpsRealTimeRepository _gpsRealTimeRepository; + private readonly IMongodbBaseRepository _mongodbBaseRepository; + private readonly IPgyAlertStraightDtlRepository _pgyAlertStraightGtlRepository; + private readonly IRepository _repository; + private readonly IUserManager _userManager; + private readonly IRepository _userRepository; + private readonly IDictionaryService _dictionaryService; + private readonly ILogger _logger; + private readonly IRepository _vehiclePersonRepository; + private readonly IRepository _orderRepository; + private readonly IRepository _oilRepository; + + public VehicleService( + IRepository repository, + IGpsRealTimeRepository gpsRealTimeRepository, + IRepository employeeRepository, + IRepository userRepository, + IPgyAlertStraightDtlRepository pgyAlertStraightGtlRepository, + ICacheService cacheService, + IUserManager userManager, + IDictionaryService dictionaryService, + IMongodbBaseRepository mongodbBaseRepository, + IRepository vehiclePersonRepository, + IRepository orderRepository, + IRepository oilRepository, + ILogger logger) + { + _repository = repository; + _userManager = userManager; + _gpsRealTimeRepository = gpsRealTimeRepository; + _employeeRepository = employeeRepository; + _userRepository = userRepository; + _pgyAlertStraightGtlRepository = pgyAlertStraightGtlRepository; + _cacheService = cacheService; + _mongodbBaseRepository = mongodbBaseRepository; + _dictionaryService = dictionaryService; + _logger = logger; + _vehiclePersonRepository = vehiclePersonRepository; + _orderRepository = orderRepository; + _oilRepository = oilRepository; + } + + /// + /// 根据id获取车辆Gps信息 + /// + /// + /// + [HttpGet] + [Route("api/v1/vehicle/gps/{vehicleId}")] + public async Task GetVehicleGpsByIdAsync(long vehicleId) + { + //车辆 + Vehicle vehicle = await _cacheService.GetVehicleAsync(vehicleId); + //公司 + CompanyOutput company = await _cacheService.GetCompanyAsync(_userManager.CompanyId, _userManager.UserId); + //gps数据 + GpsRealTime gpsRealTime = await _gpsRealTimeRepository.GetGpsRealTimeByVehicleId(vehicleId); + + if(gpsRealTime.IsNotNull()) + { + var Timeres = TimeHelper.ExecDateDiff(gpsRealTime.GpsTime.AddHours(8), DateTime.Now); + if (Timeres > 15) + gpsRealTime.GpsState = 0; + } + + VehicleGpsOutput vehicleGpsOutput = new VehicleGpsOutput + { + Id = vehicle.Id, + CompanyId = vehicle.CompanyId, + ContactPerson = vehicle.ContactPerson, + ContactPhone = vehicle.ContactPhone, + VehicleCode = vehicle.VehicleCode, + VehiclePlate = vehicle.VehiclePlate, + Acc = gpsRealTime?.Acc ?? 0, + Work = gpsRealTime?.Work ?? 0, + Status = vehicle.Status, + ImeiNo = vehicle.ImeiNo, + Speed = gpsRealTime?.Speed ?? 0, + TerminalType = (int)vehicle.TerminalType, + OpenTime = vehicle.OpenTime, + Direct = gpsRealTime?.Direct ?? 0, + ExpireTime = vehicle.ExpireTime, + AccDurationTime = gpsRealTime?.AccDurationTime ?? "0分钟", + IsOverspeedAlarm = vehicle.IsOverspeedAlarm, + GpsTime = gpsRealTime?.GpsTime.AddHours(8).ToString("yyyy-MM-dd HH:mm:ss") ?? "未激活", + Address = gpsRealTime?.Address?? "暂无位置", + Overspeed = vehicle.Overspeed, + GpsMode = gpsRealTime?.GpsMode??2, + GpsAlert = gpsRealTime.IsNull()? 1 :gpsRealTime.GpsAlert, + Useing = gpsRealTime.IsNull() ? 1:gpsRealTime.Useing, + GpsState = gpsRealTime.IsNull() ? 0:gpsRealTime.GpsState, + WorkDurationTime = gpsRealTime?.WorkDurationTime ?? "0分钟", + DurationTime = gpsRealTime?.DurationTime ?? "0分钟", + RecTime = gpsRealTime?.RecTime.AddHours(8).ToString("yyyy-MM-dd HH:mm-ss") ?? "未激活", + OfflineTime = gpsRealTime.IsNull() || + gpsRealTime?.RecTime < Convert.ToDateTime("2000-01-01 00:00:00.000") + ? "未激活" + : StringHelper.ShowTimeInterval(gpsRealTime?.RecTime.AddHours(8)), + + GpsSignalSource = "卫星", + TerminalTypeName = + typeof(TerminalTypeEnum).GetDescription((int)TerminalTypeEnum.BSJKGM_08) ?? "未知设备型号", + Latitude = gpsRealTime.IsNull() ? company.Latitude : gpsRealTime.Latitude, + Longitude = gpsRealTime.IsNull() ? company.Longitude : gpsRealTime.Longitude + }; + return vehicleGpsOutput; + } + + /// + /// 获取地图信息 + /// + /// + [HttpGet] + [Route("api/v1/maps")] + public async Task> GetMapMonitorAsync() + { + List vehicleGpsOutput = new List(); + List gpsRealTimels = new List(); + + if(_userManager.CompanyId != 0) + { + //车辆 + vehicleGpsOutput = (await _cacheService.GetVehicleByCompanyIdAsync(_userManager.CompanyId)) + .Where(x => x.IsActivate && x.IsGps).ToList(); + gpsRealTimels = await _gpsRealTimeRepository.GetGpsRealTimeByCompanyId(_userManager.CompanyId); + } + else + { + await _cacheService.RemoveAllVehicleByCompanyIdAsync(); + //车辆 + vehicleGpsOutput = (await _cacheService.GetAllVehicleByCompanyIdAsync()) + .Where(x => x.IsActivate && x.IsGps).ToList(); + var carID = vehicleGpsOutput.Select(x =>x.Id).ToList(); + gpsRealTimels = await _gpsRealTimeRepository.GetGpsRealTimeListByVehicleId(carID); + } + + //公司 + var company = await _cacheService.GetCompanyAsync(_userManager.CompanyId, _userManager.UserId); + //返回地图数据 + var mapMonitorList = new List(); + foreach (VehicleGpsOutput vehicle in vehicleGpsOutput) + { + var mapMonitorDataOutput = new MapMonitorDataOutput + { + Id = vehicle.Id, + VehicleCode = vehicle.VehicleCode, + Longitude = vehicle.Longitude, + Latitude = vehicle.Latitude, + + Width = "17px", + Height = "30px", + Anchor = new Anchor() + { + X = ".5", + Y = ".5" + }, + CustomCallout = new CustomCallout() + { + AnchorX = "0", + AnchorY = ".5", + Display = "ALWAYS" + } + + }; + var gpsRealTime = gpsRealTimels.FirstOrDefault(x => x.VehicleId == vehicle.Id); + if (gpsRealTime.IsNotNull()) + { + var Path = gpsRealTime.GpsState == 0 ? "stop" : gpsRealTime.GpsState == 1 ? "road" : "static"; + + mapMonitorDataOutput.Rotate = gpsRealTime?.Direct ?? 0; + mapMonitorDataOutput.Longitude = gpsRealTime?.Longitude ?? company.Longitude; + mapMonitorDataOutput.Latitude = gpsRealTime?.Latitude ?? company.Latitude; + mapMonitorDataOutput.IconPath = "/map/static/images/index/" + Path + ".png"; + }; + mapMonitorList.Add(mapMonitorDataOutput); + } + return mapMonitorList; + } + + /// + /// 分页查询 + /// + /// + /// 0全部,1任务中,2空闲,3停用 + /// + /// + /// + [HttpGet] + [Route("api/v1/vehicles/search")] + public async Task PageAsync([Required] int status = -1, [Required] int currentPage = 1, + [Required] int pageSize = 10, [FromQuery] string key = null) + { + + + List vehicles = new List(); + if (_userManager.CompanyId != 0) + { + //车辆 + vehicles = (await _cacheService.GetVehicleByCompanyIdAsync(_userManager.CompanyId)) + .Where(x => x.IsActivate && x.IsGps).ToList(); + } + else + { + //车辆 + vehicles = (await _cacheService.GetAllVehicleByCompanyIdAsync()) + .Where(x => x.IsActivate && x.IsGps).ToList(); + } + + //gps车辆数据 + List gpsRealTimes = await _gpsRealTimeRepository.GetGpsRealTimeByCompanyId(_userManager.CompanyId); + //车辆列表 + //List vehicles = (await _cacheService.GetVehicleByCompanyIdAsync(_userManager.CompanyId)).Where(x => x.IsActivate && x.IsGps).ToList(); + + List vehicleGpsOutputsList = new List(); + foreach (VehicleGpsOutput vehicle in vehicles) + { + GpsRealTime gpsRealTime = gpsRealTimes.FirstOrDefault(x => x.VehicleId == vehicle.Id); + VehicleGpsOutput vehicleGpsOutput = new VehicleGpsOutput + { + Id = vehicle.Id, + VehiclePlate = vehicle.VehiclePlate, + VehicleCode = vehicle.VehicleCode, + Status = vehicle.Status, + CompanyId = vehicle.CompanyId, + Speed = gpsRealTime?.Speed ?? 0, + GpsState = gpsRealTime?.GpsState ?? 0, + AccDurationTime = gpsRealTime?.AccDurationTime ?? "0分钟", + DurationTime = gpsRealTime?.DurationTime ?? "0分钟", + OfflineTime = + gpsRealTime.IsNull() || gpsRealTime?.RecTime < Convert.ToDateTime("2000-01-01 00:00:00.000") + ? "未激活" + : StringHelper.ShowTimeInterval(gpsRealTime?.RecTime.AddHours(8)), + GpsTime = gpsRealTime.IsNull() || + gpsRealTime?.GpsTime < Convert.ToDateTime("2000-01-01 00:00:00.000") + ? "2000-01-01 00:00:00.000" + : gpsRealTime?.GpsTime.ToString("yyyy-MM-dd HH:mm-ss"), + ContactPerson = vehicle.ContactPerson, + ContactPhone = vehicle.ContactPhone, + Address = gpsRealTime.IsNull() || gpsRealTime.Address.IsNull() ? "暂无位置" : gpsRealTime?.Address, + + }; + vehicleGpsOutputsList.Add(vehicleGpsOutput); + } + + int allCount = vehicleGpsOutputsList.Count; + int stillCount = vehicleGpsOutputsList.Count(x => x.GpsState == (int)GpsStatusEnum.STILL); + int drivingCount = vehicleGpsOutputsList.Count(x => x.GpsState == (int)GpsStatusEnum.DRIVING); + int offlineCount = vehicleGpsOutputsList.Count(x => x.GpsState == (int)GpsStatusEnum.OFFLINE); + VehicleListOutput result = new VehicleListOutput + { + vehicleGpsOutput = vehicleGpsOutputsList + .WhereIf(status > -1, x => x.GpsState == status) + .WhereIf(key.IsNotEmptyOrNull(), x => x.VehicleCode.Contains(key) || x.VehiclePlate.Contains(key)) + .OrderBy(x => x.VehicleCode) + .Skip((currentPage - 1) * pageSize) + .Take(pageSize) + .ToList(), + AllCount = allCount, + StillCount = stillCount, + DrivingCount = drivingCount, + OfflineCount = offlineCount + }; + return result; + } + + /// + /// 修改车辆信息 + /// + /// + /// + [HttpPut] + [Route("api/v1/vehicle")] + public async Task UpdateAsync(VehicleUpdateInput input) + { + await _repository.UpdateIncludeNowAsync(input.Adapt(), + new[] + { + nameof(Vehicle.VehicleCode), nameof(Vehicle.VehiclePlate), nameof(Vehicle.ContactPerson), + nameof(Vehicle.ContactPhone) + }, + true); + //同步修改油耗表设备号 + await "update dc_oil set VehicleCode=@vehicleCode where VehicleId=@id And IsDeleted=0".SqlNonQueryAsync(new { id = input.Id, vehicleCode = input.VehicleCode }); + //同步修改订单表设备号 + await "update dc_order set VehicleCode=@vehicleCode where VehicleId=@id And IsDeleted=0".SqlNonQueryAsync(new { id = input.Id, vehicleCode = input.VehicleCode }); + + await _cacheService.RemoveVehicleAsync(input.Id); + await _cacheService.RemoveVehicleByCompanyIdAsync(_userManager.CompanyId); + } + + /// + /// 轨迹回放纠偏 + /// + /// + /// + /// + /// + [HttpGet] + [Route("api/v1/trackplayback")] + public async Task TrackPlaybackCorrectionAsync(long vehicleId, DateTime startTime, + DateTime endTime) + { + //查询Cache + TrackPlaybackListOutput trackPlaybackListOutput = await _cacheService.GetTrackPlaybackAsync(vehicleId, startTime, endTime); + if (trackPlaybackListOutput.IsNull()) + { + //返回轨迹实体 + List reult = new List(); + //公司数据 + CompanyOutput company = await _cacheService.GetCompanyAsync(_userManager.CompanyId, _userManager.UserId); + //车辆 + Vehicle vehicle = await _cacheService.GetVehicleAsync(vehicleId); + //停留标示数据 + int stopTag = (await _dictionaryService.GetAsync(company.StopTag)).Value.ToInt(); + //车辆行驶记录 + List list = await FindGpsByTime(vehicleId, startTime, endTime); + //过滤数据 + List finalGpsList = GpsDataProcessing(list); //处理数据 + + //纠偏后数据 + List newFinalGpsList = new List(); //处理数据 + + List stopList = new List(); + + TimeSpan stopTimeSpan = new TimeSpan(); + if (finalGpsList.Count < 30) + { + throw Oops.Oh("暂无轨迹"); + } + int firstGpsTime = finalGpsList[0].GpsTime.ToTimestamp(); + List rectifyList = finalGpsList.Select(x => new TrackPlayBackCorrectInput + { + Longitude = x.Longitude.ToString("f6").ToDecimal(), + Latitude = x.Latitude.ToString("f6").ToDecimal(), + Direct = x.Direct, + Timestamp = x.GpsTime.ToTimestamp() > firstGpsTime ? x.GpsTime.ToTimestamp() - firstGpsTime : firstGpsTime, + Speed = x.Speed + }).ToList(); + string c = JsonConvert.SerializeObject(rectifyList); + List rectifyOutput = new List(); + int size = 500; + //原始轨迹点 + int reultCount = finalGpsList.Count; + int integerCount = (int)Math.Ceiling((decimal)reultCount / size); + + for (int i = 0; i < integerCount; i++) + { + TrackPlayBackCorrectOutput res = JsonConvert.DeserializeObject(HttpRequestHelper.HttpPost("https://restapi.amap.com/v4/grasproad/driving?key=89981abaf32ae94b09a639c54845e089", + rectifyList.Skip(i * size).Take(size))); + if (res.ErrCode == 0) + { + + rectifyOutput.AddRange(res.data.points.ToList()); + } + else + { + List residuePoints = rectifyList.Skip(i * size).Select(x => new Points + { + Latitude = x.Latitude, + Longitude = x.Longitude + }).ToList(); + rectifyOutput.AddRange(residuePoints); + } + } + + decimal totalDistance = 0;//总里程 + int totalPoints = rectifyOutput.Count;//纠偏后的点总数 + float index = (float)totalPoints / finalGpsList.Count; + int multiple = totalPoints % finalGpsList.Count == 0 ? totalPoints / finalGpsList.Count : totalPoints / finalGpsList.Count + 1; + for (int i = 0; i < rectifyOutput.Count; i++) + { + TrackPlaybackOutput output = new TrackPlaybackOutput(); + int k = (int)Math.Round(i / index); + if (k >= finalGpsList.Count) + { + k = finalGpsList.Count - 1; + } + VehicleGps gps = finalGpsList[k]; + gps.Longitude = rectifyOutput[i].Longitude; + gps.Latitude = rectifyOutput[i].Latitude; + gps.Speed = gps.Speed == 0 ? 21 : gps.Speed; + newFinalGpsList.Add(gps); + } + #region 计算停留点 + int stopIdx = 0; + VehicleGps firstGps = new VehicleGps(); + newFinalGpsList = newFinalGpsList.Distinct().ToList(); + for (int i = 0; i < newFinalGpsList.Count; i++) + { + TrackPlaybackOutput output = new TrackPlaybackOutput(); + VehicleGps gps = newFinalGpsList[i]; + output.Longitude = gps.Longitude; + output.Latitude = gps.Latitude; + output.GpsTime = gps.GpsTime.AddHours(8); + output.StartTime = gps.GpsTime.AddHours(8); + output.EndTime = gps.GpsTime.AddHours(8); + output.Speed = gps.Speed; + output.Direct = gps.Direct; + output.DurationTime = ""; + output.IsStopPoint = false; + output.StopPointIdx = -1; + output.Address = gps.Address; + output.Acc = gps.Acc; + output.AccDurationTime = ""; + output.Work = gps.Work; + output.WorkDurationTime = ""; + output.TotalDistance = 0; + if (i != 0) + { + VehicleGps preGps = newFinalGpsList[i - 1]; + totalDistance += DistanceHelper.GetDistance(Convert.ToDouble(gps.Latitude), + Convert.ToDouble(gps.Longitude), + Convert.ToDouble(preGps.Latitude), Convert.ToDouble(preGps.Longitude)); + output.TotalDistance = totalDistance; + //用于计算停留点 + if (gps.GpsTime.ConvertToTimestamp() - preGps.GpsTime.ConvertToTimestamp() > 300) + { + string durationTime = TimeHelper.TimeSpanFormat(gps.GpsTime - preGps.GpsTime, 3); + string address = MapHelper.GetLocationByLngLat(reult.LastOrDefault().Longitude, + reult.LastOrDefault().Latitude); + //停留点 + reult.LastOrDefault().DurationTime = durationTime; + if (firstGps.GpsTime.ConvertToTimestamp() != preGps.GpsTime.ConvertToTimestamp()) + { + reult.LastOrDefault().StopPointIdx = ++stopIdx; + reult.LastOrDefault().IsStopPoint = true; + } + reult.LastOrDefault().Address = address; + reult.LastOrDefault().StartTime = preGps.GpsTime.AddHours(8); + reult.LastOrDefault().EndTime = gps.GpsTime.AddHours(8); + stopTimeSpan += (gps.GpsTime - preGps.GpsTime); + //如果第一点是停留点,则将停留时间更新到第一个点上 + if (firstGps.GpsTime == preGps.GpsTime) + { + stopList.LastOrDefault().Behavior = "停留" + durationTime; + } + else + { + //停留点插入轨迹动态列表 + Stop stop = new Stop(); + stop.GpsTime = preGps.GpsTime.AddHours(8); + stop.Latitude = preGps.Latitude; + stop.Longitude = preGps.Longitude; + stop.Address = address; + stop.State = "停" + stopIdx; + stop.Status = 1; + stop.Behavior = "停留" + durationTime; + stop.TotalDistance = totalDistance != 0 ? totalDistance : 0; + stop.StartTime = preGps.GpsTime; + stop.EndTime = output.GpsTime; + stopList.Add(stop); + } + } + if (i == newFinalGpsList.Count - 1) + { + Stop stop = new Stop(); + VehicleGps lastGps = newFinalGpsList[i]; + string address = lastGps.Address; + output.Address = address; + stop.Latitude = lastGps.Latitude; + stop.Longitude = lastGps.Longitude; + stop.GpsTime = lastGps.GpsTime.AddHours(8); + stop.Address = lastGps.Address; + stop.State = "终点"; + stop.Status = 3; + stop.Behavior = ""; + stop.TotalDistance = totalDistance != 0 ? totalDistance : 0; + stopList.Add(stop); + } + } + else + { + firstGps = newFinalGpsList[0]; + string address = firstGps.Address; + Stop stop = new Stop + { + Latitude = firstGps.Latitude, + Longitude = firstGps.Longitude, + GpsTime = firstGps.GpsTime.AddHours(8), + Address = address, + State = "起点", + Status = 0, + Behavior = "", + TotalDistance = 0 + }; + stopList.Add(stop); + output.Address = address; + } + if (newFinalGpsList.Count == 1) + { + firstGps = newFinalGpsList[0]; + string address = firstGps.Address; + Stop stop = new Stop(); + stop.Latitude = firstGps.Latitude; + stop.Longitude = firstGps.Longitude; + stop.GpsTime = endTime; + stop.Address = address; + stop.State = "终点"; + stop.Status = 3; + stop.Behavior = ""; + stop.TotalDistance = 0; + stopList.Add(stop); + } + reult.Add(output); + } + #endregion 计算停留点 + + trackPlaybackListOutput = new TrackPlaybackListOutput + { + VehicleCode = vehicle.VehicleCode, + VehiclePlate = vehicle.VehiclePlate, + TotalDistance = totalDistance != 0 ? totalDistance : 0, + TotalStopTime = TimeHelper.TimeSpanFormat(stopTimeSpan, 3), + trackPlaybackOutputs = reult, + Stops = stopList + }; + //若查询时间包含今天,缓存录入10分钟 + TimeSpan expire = DateTimeHelper.IsToday(endTime) ? expire = TimeSpan.FromMinutes(15) : TimeSpan.FromDays(7); + await _cacheService.SeTrackPlaybackAsync(trackPlaybackListOutput, vehicleId, startTime, endTime, expire); + } + return trackPlaybackListOutput; + } + + /// + /// 修改超速报警/速度 + /// + /// + /// + /// + /// + [HttpPut] + [Route("api/v1/vehicle/overspeed/{vehicleId}/{isOverspeedAlarm}/{overSpeed}")] + + public async Task UpdateOverSpeedAsync(long vehicleId, bool isOverspeedAlarm, [DataValidation(ValidationTypes.PositiveNumber, ErrorMessage = "超速报警时速必须为数字")] int overSpeed) + { + Vehicle vehicle = await _cacheService.GetVehicleAsync(vehicleId); + vehicle.Overspeed = overSpeed; + vehicle.IsOverspeedAlarm = isOverspeedAlarm; + await _repository.UpdateIncludeNowAsync(vehicle, + new[] { nameof(Vehicle.IsOverspeedAlarm), nameof(Vehicle.Overspeed) }, true); + await _cacheService.RemoveVehicleAsync(vehicleId); + await _cacheService.RemoveVehicleByCompanyIdAsync(_userManager.CompanyId); + return true; + } + + /// + /// 超速报警列表 + /// + /// + /// + /// + [HttpGet] + [UnitOfWork] + [Route("api/v1/vehicles/alertspeeds/{currentPage}/{pageSize}")] + public async Task MbAlertSpeedByTime([Required] int currentPage = 1, + [Required] int pageSize = 10) + { + List vehicleList = await _cacheService.GetVehicleByCompanyIdAsync(_userManager.CompanyId); + if (vehicleList.IsNull() || vehicleList.Count() <= 0) + { + throw Oops.Oh("暂无超速记录"); + } + List alarmList = await _pgyAlertStraightGtlRepository.FindAlarmPageAsync(_userManager.CompanyId, currentPage, pageSize); + List list = alarmList + .Select(x => new PgyAlertStraightDtlOutput + { + VehicleId = vehicleList.Where(v => v.VehicleCode == x.VehicleCode && v.VehiclePlate == x.VehiclePlate).FirstOrDefault().Id, + CompanyId = x.CompanyId, + VehicleCode = x.VehicleCode, + VehiclePlate = x.VehiclePlate, + GpsTime = x.GpsTime.AddHours(8), + Latitude = x.Latitude, + Longitude = x.Longitude, + Direct = x.Direct, + Speed = x.Speed, + Address = x.Address + }) + .ToList(); + PgyAlertStraightDtlListOutput result = new PgyAlertStraightDtlListOutput() + { + TotalCount = (await _pgyAlertStraightGtlRepository.FindAlarmCountAsync(_userManager.CompanyId)).ToInt(), + List = list + }; + await _cacheService.SetUnreadAlarmAsync(_userManager.UserId); + return result; + } + + /// + /// 未读报警消息数 + /// + /// + [HttpGet] + [Route("api/v1/unreadalarm")] + + public async Task UnreadAlarmCountAsync() + { + DateTime readTime = await _cacheService.GetUnreadAlarmAsync(_userManager.UserId); + if (readTime.IsNull() || readTime == Convert.ToDateTime("0001-01-01 00:00:00.000")) + { + readTime = DateTime.Now.Date; + } + + IQueryable vehicleList = _repository.DetachedEntities + .Where(x => x.IsOverspeedAlarm == true && x.CompanyId == _userManager.CompanyId + && x.Status == (int)CommonStatusEnum.ENABLE); + AlarmOutput result = new AlarmOutput(); + if (vehicleList.IsNull() || !vehicleList.Any()) + { + return result; + } + List alarmList = await _pgyAlertStraightGtlRepository.FindAlarmListByTime(_userManager.CompanyId); + if (alarmList.IsNull()) + { + return result; + } + alarmList = alarmList.OrderByDescending(x => x.GpsTime).ToList(); + result.OverspeedCount = alarmList.Count(x => x.GpsTime > readTime); + result.OverspeedTime = alarmList.FirstOrDefault()?.GpsTime.AddHours(8); + result.VehicleCode = alarmList.FirstOrDefault()?.VehicleCode; + result.VehiclePlate = alarmList.FirstOrDefault()?.VehiclePlate; + return result; + } + + /// + /// 获取车辆GPS信息 + /// + /// 车辆Id + /// 开始时间 + /// 结束时间 + /// + private async Task> FindGpsByTime(long vehicleId, DateTime startTime, DateTime endTime) + { + string smon = startTime.ToString("MM"); + string emon = endTime.ToString("MM"); + string syear = startTime.ToString("yyyyMM"); + string eyear = endTime.ToString("yyyyMM"); + List list = new List(); + if (smon == emon) + { + FilterDefinition filter = Builders.Filter.Gte("GpsTime", startTime) & + Builders.Filter.Lt("GpsTime", endTime) & + Builders.Filter.Eq("Acc", 1); + string tbName = "Car_" + vehicleId + "_" + syear; + list = await _mongodbBaseRepository.GetVehicleGpsListAsync(tbName, filter); + } + else + { + //取开始时间到次月第一天 + DateTime etimeone = DateTime.Parse(startTime.AddMonths(1).ToString("yyyy-MM-01")); //次月第一天 + FilterDefinition filter1 = Builders.Filter.Gt("GpsTime", startTime) & + Builders.Filter.Lte("GpsTime", etimeone) & + Builders.Filter.Eq("Acc", 1); + string tbName1 = "Car_" + vehicleId + "_" + syear; + List list1 = await _mongodbBaseRepository.GetVehicleGpsListAsync(tbName1, filter1); + + //取结束时间月份第一天到结束时间 + DateTime etimetwo = DateTime.Parse(endTime.ToString("yyyy-MM-01")); //结束月份第一天 + FilterDefinition filter2 = Builders.Filter.Gt("GpsTime", etimetwo) & + Builders.Filter.Lte("GpsTime", endTime) & + Builders.Filter.Eq("Acc", 1); + string tbName2 = "Car_" + vehicleId + "_" + eyear; + List list2 = await _mongodbBaseRepository.GetVehicleGpsListAsync(tbName2, filter2); + list.AddRange(list1); + list.AddRange(list2); + } + return list.Count > 0 ? list : new List(); + } + + /// + /// 轨迹点预处理(车辆) + /// + /// + /// + private List GpsDataProcessing(List list) + { + List carGpsList = new List(); + int moveDistance = 40; + //轨迹点数小于10个, 则提示暂无轨迹 + if (list.Count < 20) + { + return new List(); + } + //判断前五个点是否正常 + for (int i = 0; i < 5; i++) + { + List firstToTenGps = new List(); + + for (int j = 0; j < 10 - i; j++) + { + VehicleGps model = list[j]; + firstToTenGps.Add(model); + } + + if (!firstToTenGps.Any()) + { + continue; + } + { + //平均经纬度 + double avgLat = Convert.ToDouble(firstToTenGps.Average(x => x.Latitude)); + double avgLng = Convert.ToDouble(firstToTenGps.Average(x => x.Longitude)); + //所有点距离 + List distanceList = + firstToTenGps.Select( + model => + Convert.ToDouble(DistanceHelper.GetDistance(avgLat, avgLng, + Convert.ToDouble(model.Latitude), + Convert.ToDouble(model.Longitude))) * 1000).ToList(); + //找出最大值的位置 + double max = distanceList.Max(); + int maxIndex = distanceList.IndexOf(max); + //最大值是否是第一个点, 以及距离大于625米, 150 km/h 乘 15秒 + if (maxIndex == 0 && max > 625) + { + list.Remove(list.FirstOrDefault()); + } + else + { + break; + } + } + } + + //去掉太远的点 + VehicleGps preGps = null; + foreach (VehicleGps model in list) + { + if (preGps != null) + { + decimal distance = DistanceHelper.GetDistance((double)preGps.Latitude, (double)preGps.Longitude, + (double)model.Latitude, (double)model.Longitude) * 1000; + long ts = model.GpsTime.ConvertToTimestamp() - preGps.GpsTime.ConvertToTimestamp(); + if (distance > 0 && distance < 50 * 1000) + { + if (distance < (decimal)(150 / 3.6 * ts)) + { + carGpsList.Add(model); + preGps = model; + } + } + } + else + { + carGpsList.Add(model); + preGps = model; + } + } + List finalGpsList = new List(); //处理完后的最终集合 + + //去掉距离太近的点, 去重 + preGps = null; + foreach (VehicleGps gps in carGpsList) + { + if (preGps != null) + { + decimal distance = DistanceHelper.GetDistance((double)preGps.Latitude, (double)preGps.Longitude, + (double)gps.Latitude, (double)gps.Longitude) * 1000; + if (distance > moveDistance) + { + finalGpsList.Add(gps); + preGps = gps; + } + } + else + { + finalGpsList.Add(gps); + preGps = gps; + } + } + + return carGpsList; + } + + + /// + /// 离线车辆报警 + /// + /// + [HttpGet] + public async Task VehicleOfflineAsync() + { + List gpsRealTimeList = await _gpsRealTimeRepository.GetOfflineGpsRealTime(); + foreach (GpsRealTime item in gpsRealTimeList) + { + IQueryable users = from e in _employeeRepository.Where(x => + x.CompanyId == item.CompanyId && x.RoleId == 1 && x.Status == (int)CommonStatusEnum.ENABLE) + .AsQueryable() + join u in _userRepository.Where(x => x.Status == CommonStatusEnum.ENABLE).AsQueryable() + on e.UserId equals u.Id + select new NoticeOutput + { + OpenId = u.OpenId + }; + foreach (NoticeOutput i in users) + { + // WxUserRelation wxUserRelation = await _wxUserRelationRepository.DetachedEntities + // .Where(x => x.OpenId == i.OpenId).FirstOrDefaultAsync(); + // if (wxUserRelation.WxOfficialOpenId.IsNotNull()) + // { + // WeixinTemplate_OfflineNotice data = new WeixinTemplate_OfflineNotice("尊敬的用户,你的车辆已离线", + // item.VehicleCode, item.VehiclePlate, item.Address); + // AccessTokenResult accessTokenResult = await CommonApi.GetTokenAsync(App.Configuration["SenparcWeixinSetting:WeixinAppId"], + // App.Configuration["SenparcWeixinSetting:WeixinAppSecret"]); + // SendTemplateMessageResult result = await TemplateApi.SendTemplateMessageAsync(accessTokenResult.access_token, + // wxUserRelation.WxOfficialOpenId, data.TemplateId, "", data); + // } + } + } + + return true; + } + + + #region 轨迹 + + /// + /// 同步轨迹 + /// + /// + [HttpGet] + [AllowAnonymous] + public async Task SyncTrackPlaybackCorrectionAsync(DateTime startTime, DateTime endTime, string type) + { + List vehicles = await _repository.Where(x => x.Status == (int)CommonStatusEnum.ENABLE && x.IsGps == true && x.IsActivate == true).ToListAsync(); + foreach (Vehicle vehicle in vehicles) + { + await SetTrackPlaybackCorrectionAsync(vehicle.Id, vehicle.CompanyId, startTime, endTime, type); + } + } + + /// + /// 轨迹回放纠偏 + /// + /// + /// + /// + /// + /// + /// + private async Task SetTrackPlaybackCorrectionAsync(long vehicleId, long companyId, DateTime startTime, DateTime endTime, string type) + { + //查询Cache + TrackPlaybackListOutput trackPlaybackListOutput = new TrackPlaybackListOutput(); + + //返回轨迹实体 + List reult = new List(); + //公司数据 + CompanyOutput company = await _cacheService.GetCompanyAsync(companyId, _userManager.UserId); + //车辆 + Vehicle vehicle = await _cacheService.GetVehicleAsync(vehicleId); + //停留标示数据 + int stopTag = (await _dictionaryService.GetAsync(company.StopTag)).Value.ToInt(); + //车辆行驶记录 + List list = await FindGpsByTime(vehicleId, startTime, endTime); + //过滤数据 + Console.WriteLine(startTime); Console.WriteLine(endTime); + + List finalGpsList = GpsDataProcessing(list); //处理数据 + + //纠偏后数据 + List newFinalGpsList = new List(); //处理数据 + + List stopList = new List(); + + TimeSpan stopTimeSpan = new TimeSpan(); + if (finalGpsList.Count < 30) + { + return; + } + int firstGpsTime = finalGpsList[0].GpsTime.ToTimestamp(); + List rectifyList = finalGpsList.Select(x => new TrackPlayBackCorrectInput + { + Longitude = x.Longitude.ToString("f6").ToDecimal(), + Latitude = x.Latitude.ToString("f6").ToDecimal(), + Direct = x.Direct, + Timestamp = x.GpsTime.ToTimestamp() > firstGpsTime ? x.GpsTime.ToTimestamp() - firstGpsTime : firstGpsTime, + Speed = x.Speed + }).ToList(); + string c = JsonConvert.SerializeObject(rectifyList); + List rectifyOutput = new List(); + int size = 500; + //原始轨迹点 + int reultCount = finalGpsList.Count; + int integerCount = (int)Math.Ceiling((decimal)reultCount / size); + + for (int i = 0; i < integerCount; i++) + { + TrackPlayBackCorrectOutput res = JsonConvert.DeserializeObject(HttpRequestHelper.HttpPost("https://restapi.amap.com/v4/grasproad/driving?key=89981abaf32ae94b09a639c54845e089", + rectifyList.Skip(i * size).Take(size))); + if (res.ErrCode == 0) + { + + rectifyOutput.AddRange(res.data.points.ToList()); + } + else + { + List residuePoints = rectifyList.Skip(i * size).Select(x => new Points + { + Latitude = x.Latitude, + Longitude = x.Longitude + }).ToList(); + rectifyOutput.AddRange(residuePoints); + } + } + + decimal totalDistance = 0;//总里程 + int totalPoints = rectifyOutput.Count;//纠偏后的点总数 + float index = (float)totalPoints / finalGpsList.Count; + int multiple = totalPoints % finalGpsList.Count == 0 ? totalPoints / finalGpsList.Count : totalPoints / finalGpsList.Count + 1; + for (int i = 0; i < rectifyOutput.Count; i++) + { + TrackPlaybackOutput output = new TrackPlaybackOutput(); + int k = (int)Math.Round(i / index); + if (k >= finalGpsList.Count) + { + k = finalGpsList.Count - 1; + } + VehicleGps gps = finalGpsList[k]; + gps.Longitude = rectifyOutput[i].Longitude; + gps.Latitude = rectifyOutput[i].Latitude; + gps.Speed = gps.Speed == 0 ? 21 : gps.Speed; + newFinalGpsList.Add(gps); + } + #region 计算停留点 + int stopIdx = 0; + VehicleGps firstGps = new VehicleGps(); + newFinalGpsList = newFinalGpsList.Distinct().ToList(); + for (int i = 0; i < newFinalGpsList.Count; i++) + { + TrackPlaybackOutput output = new TrackPlaybackOutput(); + VehicleGps gps = newFinalGpsList[i]; + output.Longitude = gps.Longitude; + output.Latitude = gps.Latitude; + output.GpsTime = gps.GpsTime.AddHours(8); + output.StartTime = gps.GpsTime.AddHours(8); + output.EndTime = gps.GpsTime.AddHours(8); + output.Speed = gps.Speed; + output.Direct = gps.Direct; + output.DurationTime = ""; + output.IsStopPoint = false; + output.StopPointIdx = -1; + output.Address = gps.Address; + output.Acc = gps.Acc; + output.AccDurationTime = ""; + output.Work = gps.Work; + output.WorkDurationTime = ""; + output.TotalDistance = 0; + if (i != 0) + { + VehicleGps preGps = newFinalGpsList[i - 1]; + totalDistance += DistanceHelper.GetDistance(Convert.ToDouble(gps.Latitude), + Convert.ToDouble(gps.Longitude), + Convert.ToDouble(preGps.Latitude), Convert.ToDouble(preGps.Longitude)); + output.TotalDistance = totalDistance; + //用于计算停留点 + if (gps.GpsTime.ConvertToTimestamp() - preGps.GpsTime.ConvertToTimestamp() > 300) + { + string durationTime = TimeHelper.TimeSpanFormat(gps.GpsTime - preGps.GpsTime, 3); + string address = MapHelper.GetLocationByLngLat(reult.LastOrDefault().Longitude, + reult.LastOrDefault().Latitude); + //停留点 + reult.LastOrDefault().DurationTime = durationTime; + if (firstGps.GpsTime.ConvertToTimestamp() != preGps.GpsTime.ConvertToTimestamp()) + { + reult.LastOrDefault().StopPointIdx = ++stopIdx; + reult.LastOrDefault().IsStopPoint = true; + } + reult.LastOrDefault().Address = address; + reult.LastOrDefault().StartTime = preGps.GpsTime.AddHours(8); + reult.LastOrDefault().EndTime = gps.GpsTime.AddHours(8); + stopTimeSpan += (gps.GpsTime - preGps.GpsTime); + //如果第一点是停留点,则将停留时间更新到第一个点上 + if (firstGps.GpsTime == preGps.GpsTime) + { + stopList.LastOrDefault().Behavior = "停留" + durationTime; + } + else + { + //停留点插入轨迹动态列表 + Stop stop = new Stop(); + stop.GpsTime = preGps.GpsTime.AddHours(8); + stop.Latitude = preGps.Latitude; + stop.Longitude = preGps.Longitude; + stop.Address = address; + stop.State = "停" + stopIdx; + stop.Status = 1; + stop.Behavior = "停留" + durationTime; + stop.TotalDistance = totalDistance != 0 ? totalDistance : 0; + stop.StartTime = preGps.GpsTime; + stop.EndTime = output.GpsTime; + stopList.Add(stop); + } + } + if (i == newFinalGpsList.Count - 1) + { + Stop stop = new Stop(); + VehicleGps lastGps = newFinalGpsList[i]; + string address = lastGps.Address; + output.Address = address; + stop.Latitude = lastGps.Latitude; + stop.Longitude = lastGps.Longitude; + stop.GpsTime = lastGps.GpsTime.AddHours(8); + stop.Address = lastGps.Address; + stop.State = "终点"; + stop.Status = 3; + stop.Behavior = ""; + stop.TotalDistance = totalDistance != 0 ? totalDistance : 0; + stopList.Add(stop); + } + } + else + { + firstGps = newFinalGpsList[0]; + string address = firstGps.Address; + Stop stop = new Stop + { + Latitude = firstGps.Latitude, + Longitude = firstGps.Longitude, + GpsTime = firstGps.GpsTime.AddHours(8), + Address = address, + State = "起点", + Status = 0, + Behavior = "", + TotalDistance = 0 + }; + stopList.Add(stop); + output.Address = address; + } + if (newFinalGpsList.Count == 1) + { + firstGps = newFinalGpsList[0]; + string address = firstGps.Address; + Stop stop = new Stop(); + stop.Latitude = firstGps.Latitude; + stop.Longitude = firstGps.Longitude; + stop.GpsTime = endTime; + stop.Address = address; + stop.State = "终点"; + stop.Status = 3; + stop.Behavior = ""; + stop.TotalDistance = 0; + stopList.Add(stop); + } + reult.Add(output); + } + #endregion 计算停留点 + + trackPlaybackListOutput = new TrackPlaybackListOutput + { + VehicleCode = vehicle.VehicleCode, + VehiclePlate = vehicle.VehiclePlate, + TotalDistance = totalDistance != 0 ? totalDistance : 0, + TotalStopTime = TimeHelper.TimeSpanFormat(stopTimeSpan, 3), + trackPlaybackOutputs = reult, + Stops = stopList + }; + + + + TimeSpan timeSpan = type == "Today" ? TimeSpan.FromMinutes(10) : TimeSpan.FromDays(3); + await _cacheService.SeTrackPlaybackAsync(trackPlaybackListOutput, vehicleId, startTime, endTime, timeSpan); + } + #endregion + + #region v1.2.1 + /// + /// 基础数据-车辆管理列表 + /// + /// + /// + /// + /// 0全部,停用-1,正常1 + /// + [HttpGet] + [Route("api/v1/basics/vehicles/search")] + public async Task BasicsPageAsync([Required] int currentPage, [Required] int pageSize, [FromQuery] int status, [FromQuery] string key) + { + var vehicleGpsList = (await _cacheService.GetVehicleByCompanyIdAsync(_userManager.CompanyId)) + .WhereIf(status > 0, x => x.Status == status) + .WhereIf(key.IsNotEmptyOrNull(), x => x.VehicleCode.Contains(key)).Adapt>(); + foreach (var item in vehicleGpsList) + { + item.VehiclePerson = await _cacheService.GetVehiclePersonAsync(item.Id); + item.VehiclePersonName = item.VehiclePerson.Find(x => x.IsDriver == true)?.UserName; + item.VehiclePersonPhone = item.VehiclePerson.Find(x => x.IsDriver == true)?.UserPhone; + } + var vehicleOutputs = new List(); + foreach (var INDEX_STRING in INDEX_STRINGS) + { + vehicleOutputs.Add(new VehicleBaiscListOutputs() + { + Char = INDEX_STRING, + List = vehicleGpsList.Where(x => StringHelper.GetStringFirstSpell(x.VehicleCode) == INDEX_STRING).ToList() + }); + }; + var data = new VehicleBaiscListPage() + { + VehicleBaiscList = vehicleOutputs, + Total = vehicleGpsList.Count + }; + return data; + } + + /// + /// 基础数据-车辆信息 + /// + /// + /// + [HttpGet] + [Route("api/v1/basics/vehicle/{vehicleId}")] + public async Task GetVehicleBasicsByIdAsync(long vehicleId) + { + //车辆 + Vehicle vehicle = await _cacheService.GetVehicleAsync(vehicleId); + if (vehicle.IsNull()) + { + Oops.Oh("暂无车辆信息"); + } + VehicleBaiscListOutput VehicleBaisc = vehicle.Adapt(); + VehicleBaisc.VehiclePerson = await _cacheService.GetVehiclePersonAsync(vehicleId); + return VehicleBaisc; + } + + /// + /// 更新车辆基础信息 + /// + /// + /// + [UnitOfWork] + [HttpPut] + [Route("api/v1/basics/vehicle")] + public async Task BasicsUpdateAsync(BasicsVehicleUpdateInput input) + { + var vehicle = await _repository.FirstOrDefaultAsync(x => x.Id == input.Id); + if (vehicle.IsNull()) throw Oops.Oh("暂无该信息"); + var repeatVehicle = await _repository.DetachedEntities + .Where(x => x.Id != input.Id && ((x.VehicleCode == input.VehicleCode && x.CompanyId == _userManager.CompanyId) || + (x.VehiclePlate == input.VehiclePlate && x.Status == (int)CommonStatusEnum.ENABLE))) + .FirstOrDefaultAsync(); + if (repeatVehicle.IsNotNull()) + { + throw Oops.Bah("车牌号或设备名称重复"); + } + //车组人员人数无上限,至少存在一个司机才可保存 + if (input.VehiclePersons.Count > 0) + { + var isDriver = input.VehiclePersons.Find(x => x.IsDriver == true); + if (isDriver.IsNull()) + { + throw Oops.Bah("车组人员至少需要一个司机"); + } + } + + #region 修改车组人员 + var oldVehiclePersons = await _vehiclePersonRepository.DetachedEntities + .Where(x => x.VehicleId == input.Id && x.IsDeleted == false) + .ToListAsync(); + var oldVehiclePersonIds = oldVehiclePersons.IsNotNull() ? oldVehiclePersons.Select(x => x.UserId).ToArray() : new long[] { }; + var newVehiclePersonIds = input.VehiclePersons.IsNotNull() ? input.VehiclePersons.Select(x => x.UserId).ToArray() : new long[] { }; + var deleteVehiclePersonIds = oldVehiclePersonIds.Except(newVehiclePersonIds).ToArray(); + if (deleteVehiclePersonIds.Length > 0) + { + for (int i = 0; i < deleteVehiclePersonIds.Length; i++) + { + var vehiclePerson = oldVehiclePersons.FirstOrDefault(x => x.UserId == deleteVehiclePersonIds[i]); + vehiclePerson.IsDeleted = true; + await _vehiclePersonRepository.UpdateNowAsync(vehiclePerson); + } + } + if (input.VehiclePersons.IsNotNull()) + { + foreach (var item in input.VehiclePersons) + { + var oldvehiclePerson = oldVehiclePersons.Find(x => x.UserId == item.UserId); + if (oldvehiclePerson.IsNull()) + { + var vehiclePerson = item.Adapt(); + vehiclePerson.VehicleId = input.Id; + await _vehiclePersonRepository.InsertNowAsync(vehiclePerson); + } + else + { + var vehiclePerson = item.Adapt(); + vehiclePerson.Id = oldvehiclePerson.Id; + await _vehiclePersonRepository.UpdateNowAsync(vehiclePerson); + } + } + } + + #endregion + + var result = await _repository.UpdateNowAsync(input.Adapt(vehicle)); + if (result.IsNull()) + { + throw Oops.Oh("修改车辆基础信息失败"); + } + + //同步修改油耗表和订单表设备号 + if (input.VehicleCode != vehicle.VehicleCode) + { + //油耗表 + await "update dc_oil set VehicleCode=@vehicleCode where VehicleId=@id And IsDeleted=0".SqlNonQueryAsync(new { id = input.Id, vehicleCode = input.VehicleCode }); + //订单表 + await "update dc_order set VehicleCode=@vehicleCode where VehicleId=@id And IsDeleted=0".SqlNonQueryAsync(new { id = input.Id, vehicleCode = input.VehicleCode }); + } + await _cacheService.RemoveVehiclePersonAsync(input.Id); + await _cacheService.RemoveVehicleAsync(input.Id); + await _cacheService.RemoveVehicleByCompanyIdAsync(_userManager.CompanyId); + } + #endregion + + + } + +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/VehicleGroup/Dto/Input/VehicleGroupAddInput.cs b/src/Znyc.Dispatching.Application/VehicleGroup/Dto/Input/VehicleGroupAddInput.cs new file mode 100644 index 0000000..d24707b --- /dev/null +++ b/src/Znyc.Dispatching.Application/VehicleGroup/Dto/Input/VehicleGroupAddInput.cs @@ -0,0 +1,20 @@ +namespace Znyc.Dispatching.Application +{ + public class VehicleGroupAddInput + { + /// + /// 公司Id + /// + public long CompanyId { get; set; } + + /// + /// 名称 + /// + public string Name { get; set; } + + /// + /// 父级权限Id + /// + public long ParentId { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/VehicleGroup/Dto/OutPut/VehicleGroupOutput.cs b/src/Znyc.Dispatching.Application/VehicleGroup/Dto/OutPut/VehicleGroupOutput.cs new file mode 100644 index 0000000..c2659bc --- /dev/null +++ b/src/Znyc.Dispatching.Application/VehicleGroup/Dto/OutPut/VehicleGroupOutput.cs @@ -0,0 +1,32 @@ +namespace Znyc.Dispatching.Application +{ + /// + /// + public class VehicleGroupOutput + { + /// + /// Id + /// + public long Id { get; set; } + + /// + /// 公司Id + /// + public long CompanyId { get; set; } + + /// + /// 名称 + /// + public string Name { get; set; } + + /// + /// 父级权限Id + /// + public long ParentId { get; set; } + + /// + /// 状态(字典 0正常 1停用 2删除) + /// + public int Status { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/VehicleGroup/Services/IVehicleGroupService.cs b/src/Znyc.Dispatching.Application/VehicleGroup/Services/IVehicleGroupService.cs new file mode 100644 index 0000000..ffecc31 --- /dev/null +++ b/src/Znyc.Dispatching.Application/VehicleGroup/Services/IVehicleGroupService.cs @@ -0,0 +1,15 @@ +using System.Threading.Tasks; +using Znyc.Dispatching.Core.Entitys; + +namespace Znyc.Dispatching.Application +{ + public interface IVehicleGroupService + { + /// + /// 新增车辆分组 + /// + /// + /// + Task InsertAsync(VehicleGroupAddInput input); + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/VehicleGroup/Services/VehicleGroupService.cs b/src/Znyc.Dispatching.Application/VehicleGroup/Services/VehicleGroupService.cs new file mode 100644 index 0000000..fb8177d --- /dev/null +++ b/src/Znyc.Dispatching.Application/VehicleGroup/Services/VehicleGroupService.cs @@ -0,0 +1,90 @@ +using Furion.DatabaseAccessor; +using Furion.DependencyInjection; +using Furion.DynamicApiController; +using Furion.FriendlyException; +using Mapster; +using MapsterMapper; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Znyc.Dispatching.Core; +using Znyc.Dispatching.Core.Entitys; +using Znyc.Dispatching.Core.Extension; + +namespace Znyc.Dispatching.Application +{ + /// + /// 车辆分组服务 + /// + [ApiDescriptionSettings(Name = "VehicleGroup", Order = 146, IgnoreApi = true)] + public class VehicleGroupService : IVehicleGroupService, IDynamicApiController, ITransient + { + private readonly ICacheService _cacheService; + private readonly IMapper _mapper; + private readonly IRepository _repository; + private readonly IUserManager _userManager; + + public VehicleGroupService( + IRepository repository, + IMapper mapper, + ICacheService cacheService, + IUserManager userManager + ) + { + _repository = repository; + _mapper = mapper; + _cacheService = cacheService; + _userManager = userManager; + } + + /// + /// 新增车辆分组 + /// + /// + /// + public async Task InsertAsync(VehicleGroupAddInput input) + { + VehicleGroup vehicleGroup = _mapper.Map(input); + Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntry result = await _repository.InsertNowAsync(vehicleGroup); + if (result.IsNull()) + { + throw Oops.Oh(ErrorCode.D1011); + } + + return result.Entity; + } + + /// + /// 获取车辆分组 + /// + /// + [HttpGet] + public async Task> GetVehicleGroupListAsync() + { + List list = await _cacheService.GetVehicleGroupListAsync(_userManager.CompanyId); + if (list.IsNull()) + { + list = await SyncVehicleGroupListCache(); + } + + return list.Adapt>(); + ; + } + + /// + /// 同步车辆分组缓存 + /// + /// + [NonAction] + private async Task> SyncVehicleGroupListCache() + { + List list = await _repository.DetachedEntities + .Where(x => x.Status == CommonStatusEnum.ENABLE && x.CompanyId == _userManager.CompanyId) + .ToListAsync(); + await _cacheService.SetVehicleGroupListAsync(_userManager.CompanyId, list); + return list; + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/VehiclePerson/Dto/Input/VehiclePersonAddInput.cs b/src/Znyc.Dispatching.Application/VehiclePerson/Dto/Input/VehiclePersonAddInput.cs new file mode 100644 index 0000000..df28407 --- /dev/null +++ b/src/Znyc.Dispatching.Application/VehiclePerson/Dto/Input/VehiclePersonAddInput.cs @@ -0,0 +1,32 @@ +namespace Znyc.Dispatching.Application +{ + public class VehiclePersonAddInput + { + /// + /// 车辆 + /// + public long VehicleId { get; set; } + + /// + /// 车组人员Id + /// + public long UserId { get; set; } + + + /// + /// 员工名 + /// + public string UserName { get; set; } + + + /// + /// 手机号 + /// + public string UserPhone { get; set; } + + /// + /// 是否司机 + /// + public bool IsDriver { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/VehiclePerson/Dto/Input/VehiclePersonUpdateInput.cs b/src/Znyc.Dispatching.Application/VehiclePerson/Dto/Input/VehiclePersonUpdateInput.cs new file mode 100644 index 0000000..0b3b4dd --- /dev/null +++ b/src/Znyc.Dispatching.Application/VehiclePerson/Dto/Input/VehiclePersonUpdateInput.cs @@ -0,0 +1,11 @@ +namespace Znyc.Dispatching.Application +{ + public class VehiclePersonUpdateInput : VehiclePersonAddInput + { + /// + /// + /// + public long Id { get; set; } + + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/VehiclePerson/Dto/OutPut/VehiclePersonOutput.cs b/src/Znyc.Dispatching.Application/VehiclePerson/Dto/OutPut/VehiclePersonOutput.cs new file mode 100644 index 0000000..18dc39f --- /dev/null +++ b/src/Znyc.Dispatching.Application/VehiclePerson/Dto/OutPut/VehiclePersonOutput.cs @@ -0,0 +1,42 @@ +namespace Znyc.Dispatching.Application +{ + /// + /// + public class VehiclePersonOutput + { + /// + /// Id + /// + public long Id { get; set; } + + /// + /// 车辆Id + /// + public long VehicleId { get; set; } + + /// + /// 员工Id + /// + public long UserId { get; set; } + + /// + /// 员工名称 + /// + public string UserName { get; set; } + + /// + /// 手机号 + /// + public string UserPhone { get; set; } + + /// + /// 是否司机 + /// + public bool IsDriver { get; set; } + + /// + /// + /// + public bool IsDeleted { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/VehiclePerson/Services/IVehiclePersonService.cs b/src/Znyc.Dispatching.Application/VehiclePerson/Services/IVehiclePersonService.cs new file mode 100644 index 0000000..0d8d556 --- /dev/null +++ b/src/Znyc.Dispatching.Application/VehiclePerson/Services/IVehiclePersonService.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Znyc.Dispatching.Application +{ + public interface IVehiclePersonService + { + Task> GetByIdAsync(long id); + } +} diff --git a/src/Znyc.Dispatching.Application/VehiclePerson/Services/VehiclePersonService.cs b/src/Znyc.Dispatching.Application/VehiclePerson/Services/VehiclePersonService.cs new file mode 100644 index 0000000..d6b2558 --- /dev/null +++ b/src/Znyc.Dispatching.Application/VehiclePerson/Services/VehiclePersonService.cs @@ -0,0 +1,38 @@ +using Furion.DatabaseAccessor; +using Furion.DependencyInjection; +using Furion.DynamicApiController; +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; +using System.Threading.Tasks; +using Znyc.Dispatching.Core.Entitys; + +namespace Znyc.Dispatching.Application +{ + /// + ///车组人员信息联系人管理 + /// + [ApiDescriptionSettings(Name = "vehicleperson", Order = 145)] + public class VehiclePersonService : IVehiclePersonService, IDynamicApiController, ITransient + { + private readonly IRepository _repository; + private readonly ICacheService _cacheService; + + public VehiclePersonService(IRepository repository, + ICacheService cacheService) + { + _repository = repository; + _cacheService = cacheService; + } + + /// + /// 根据车辆Id查询车组人员信息 + /// + /// + [HttpGet] + [Route("api/v1/vehicle/persons/{vehicleId}")] + public async Task> GetByIdAsync(long vehicleId) + { + return await _cacheService.GetVehiclePersonAsync(vehicleId); + } + } +} diff --git a/src/Znyc.Dispatching.Application/VehicleType/Dto/Input/VehicleTypeInsertInput.cs b/src/Znyc.Dispatching.Application/VehicleType/Dto/Input/VehicleTypeInsertInput.cs new file mode 100644 index 0000000..fd1590b --- /dev/null +++ b/src/Znyc.Dispatching.Application/VehicleType/Dto/Input/VehicleTypeInsertInput.cs @@ -0,0 +1,19 @@ +namespace Znyc.Dispatching.Application +{ + /// + /// 新增车型 + /// + public class VehicleTypeInsertInput + { + /// + /// 名称 + /// + public string Name { get; set; } + + + /// + /// 状态(字典 0正常 1停用 2删除) + /// + public bool Status { get; set; } + } +} diff --git a/src/Znyc.Dispatching.Application/VehicleType/Dto/Input/VehicleTypeUpdateInput.cs b/src/Znyc.Dispatching.Application/VehicleType/Dto/Input/VehicleTypeUpdateInput.cs new file mode 100644 index 0000000..5f6b20f --- /dev/null +++ b/src/Znyc.Dispatching.Application/VehicleType/Dto/Input/VehicleTypeUpdateInput.cs @@ -0,0 +1,14 @@ +namespace Znyc.Dispatching.Application +{ + /// + /// + /// + public class VehicleTypeUpdateInput : VehicleTypeInsertInput + { + /// + /// + /// + public long Id { get; set; } + + } +} diff --git a/src/Znyc.Dispatching.Application/VehicleType/Dto/OutPut/VehicleTypeOutput.cs b/src/Znyc.Dispatching.Application/VehicleType/Dto/OutPut/VehicleTypeOutput.cs new file mode 100644 index 0000000..3393864 --- /dev/null +++ b/src/Znyc.Dispatching.Application/VehicleType/Dto/OutPut/VehicleTypeOutput.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; + +namespace Znyc.Dispatching.Application +{ + + public class VehicleTypeBaiscList + { + public long Total { get; set; } + + public List VehicleTypeLetterOutputs { get; set; } + } + + + public class VehicleTypeLetterOutput + { + + public string Char { get; set; } + + public List List { get; set; } + } + + public class VehicleTypeOutput + { + /// + /// + public long Id { get; set; } + + + /// + /// 匹配索引列表,与车组人员共用一个列表,所以需增加UserId来进行匹配 + /// + public long UserId + { + get + { + return Id; + } + set + { + Id = value; + } + } + /// + /// 名称 + /// + public string Name { get; set; } + + ///// + ///// 父级权限Id + ///// + //public long ParentId { get; set; } + + ///// + ///// 状态(字典 0正常 1停用 2删除) + ///// + public int Status { get; set; } + } + +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/VehicleType/Services/IVehicleTypeService.cs b/src/Znyc.Dispatching.Application/VehicleType/Services/IVehicleTypeService.cs new file mode 100644 index 0000000..b222aa2 --- /dev/null +++ b/src/Znyc.Dispatching.Application/VehicleType/Services/IVehicleTypeService.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Znyc.Dispatching.Application +{ + public interface IVehicleTypeService + { + Task> ListAsync(string key, int status, int currentPage = 1, int pageSize = 10); + + public Task InsertAsync(VehicleTypeInsertInput vehicleTypeInsertInput); + + public Task UpdateAsync(VehicleTypeUpdateInput vehicleTypeUpdateInput); + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/VehicleType/Services/VehicleTypeService.cs b/src/Znyc.Dispatching.Application/VehicleType/Services/VehicleTypeService.cs new file mode 100644 index 0000000..5510916 --- /dev/null +++ b/src/Znyc.Dispatching.Application/VehicleType/Services/VehicleTypeService.cs @@ -0,0 +1,113 @@ +using Furion.DatabaseAccessor; +using Furion.DependencyInjection; +using Furion.DynamicApiController; +using Mapster; +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Znyc.Dispatching.Core; +using Znyc.Dispatching.Core.Entitys; +using Znyc.Dispatching.Core.Extension; +using Znyc.Dispatching.Core.Helpers; + +namespace Znyc.Dispatching.Application +{ + /// + /// 车辆类型服务 + /// + [ApiDescriptionSettings(Name = "VehicleType", Order = 146)] + public class VehicleTypeService : IVehicleTypeService, IDynamicApiController, ITransient + { + private readonly ICacheService _cacheService; + private readonly IRepository _repository; + private readonly IUserManager _userManager; + private string[] INDEX_STRINGS = {"A", "B", "C", "D", "E", "F", "G", "H", "I", + "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", + "W", "X", "Y", "Z", "#"}; + + + + public VehicleTypeService( + IRepository repository, + ICacheService cacheService, + IUserManager userManager + ) + { + _repository = repository; + _cacheService = cacheService; + _userManager = userManager; + } + + + /// + /// + /// + /// + /// + [HttpPost] + [Route("api/v1/vehicletype")] + public async Task InsertAsync(VehicleTypeInsertInput vehicleTypeInsertInput) + { + var result = false; + if (vehicleTypeInsertInput.IsNotNull()) + { + var vehicleType = vehicleTypeInsertInput.Adapt(); + vehicleType.CompanyId = _userManager.CompanyId; + vehicleType.ParentId = 0; + long id = (await _repository.InsertNowAsync(vehicleType)).Entity.Id; + if (id > 0) + { + result = true; + } + } + return result; + } + + + /// + /// + /// + /// + [HttpGet] + [Route("api/v1/vehicletypes/search")] + public async Task> ListAsync(string key, int status, int currentPage = 1, int pageSize = 10) + { + var vehicleTypes = (await _repository.DetachedEntities.Where(x => x.CompanyId == _userManager.CompanyId) + .Where(key.IsNotEmptyOrNull(), x => x.Name.Contains(key)) + .Where(status > 0, x => x.Status) + .OrderBy(x => x.Name) + .ToPagedListAsync(currentPage, pageSize)).Adapt>(); + + return vehicleTypes; + } + + + /// + /// UpdateAsync + /// + /// + /// + [HttpPut] + [Route("api/v1/vehicletype")] + public async Task UpdateAsync(VehicleTypeUpdateInput vehicleTypeUpdateInput) + { + var result = false; + if (vehicleTypeUpdateInput.IsNotNull()) + { + var vehicleType = await _repository.FindAsync(vehicleTypeUpdateInput.Id); + if (vehicleType.IsNotNull()) + { + vehicleType.Name = vehicleTypeUpdateInput.Name; + vehicleType.Status = vehicleTypeUpdateInput.Status; + long id = (await _repository.UpdateNowAsync(vehicleType)).Entity.Id; + if (id > 0) + { + result = true; + } + } + } + return result; + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/WxUserRelation/Services/IWxUserRelationService.cs b/src/Znyc.Dispatching.Application/WxUserRelation/Services/IWxUserRelationService.cs new file mode 100644 index 0000000..2c882cc --- /dev/null +++ b/src/Znyc.Dispatching.Application/WxUserRelation/Services/IWxUserRelationService.cs @@ -0,0 +1,15 @@ +using System.Threading.Tasks; + +namespace Znyc.Dispatching.Application +{ + public interface IWxUserRelationService + { + /// + /// + /// + /// + /// + /// + Task AddOrUpdateAsync(long userId, string openId, string unionId); + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/WxUserRelation/Services/WxUserRelationService.cs b/src/Znyc.Dispatching.Application/WxUserRelation/Services/WxUserRelationService.cs new file mode 100644 index 0000000..3d3bf63 --- /dev/null +++ b/src/Znyc.Dispatching.Application/WxUserRelation/Services/WxUserRelationService.cs @@ -0,0 +1,50 @@ +using Furion.DatabaseAccessor; +using Furion.DependencyInjection; +using Furion.DynamicApiController; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System.Threading.Tasks; +using Znyc.Dispatching.Core.Entitys; +using Znyc.Dispatching.Core.Extension; + +namespace Znyc.Dispatching.Application +{ + /// + /// 车辆类型服务 + /// + [ApiDescriptionSettings(Name = "wxUserRelation", Order = 10, IgnoreApi = true)] + public class WxUserRelationService : IWxUserRelationService, IDynamicApiController, ITransient + { + private readonly IRepository _repository; + + public WxUserRelationService( + IRepository repository + ) + { + _repository = repository; + } + + [NonAction] + public async Task AddOrUpdateAsync(long userId, string openId, string unionId) + { + var wxUserRelation = + await _repository.Where(x => x.UnionId == unionId).FirstOrDefaultAsync(); + if (wxUserRelation.IsNull()) + { + await _repository.InsertNowAsync(new WxUserRelation + { + UnionId = unionId, + OpenId = openId, + UserId = userId, + WxOfficialOpenId = "" + }); + } + else + { + wxUserRelation.OpenId = openId; + wxUserRelation.UserId = userId; + await _repository.UpdateNowAsync(wxUserRelation); + } + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Yard/Dto/Input/YardAddInput.cs b/src/Znyc.Dispatching.Application/Yard/Dto/Input/YardAddInput.cs new file mode 100644 index 0000000..964a986 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Yard/Dto/Input/YardAddInput.cs @@ -0,0 +1,41 @@ +using System.ComponentModel.DataAnnotations; + +namespace Znyc.Dispatching.Application +{ + /// + /// 车场输入实体 + /// + public class YardAddInput + { + /// + /// 精度 + /// + [Required(ErrorMessage = "地址不能为空!")] + public decimal Longitude { get; set; } + + /// + /// 纬度 + /// + [Required(ErrorMessage = "地址不能为空!")] + public decimal Latitude { get; set; } + + /// + /// 地址 + /// + public string Address { get; set; } + + /// + /// 联系人 + /// + [MaxLength(8, ErrorMessage = "联系人不得超过8个字")] + public string ContactPerson { get; set; } + + /// + /// 联系人电话 + /// + //[Required(ErrorMessage = "联系人电话不能为空!"), MaxLength(11, ErrorMessage = "联系电话不能超过11位")] + [RegularExpression("^[1][3,4,5,6,7,8,9][0-9]{9}$", ErrorMessage = "请输入正确的手机号码")] + [MaxLength(11, ErrorMessage = "联系电话不能超过11位")] + public string ContactPhone { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Yard/Dto/Input/YardUpdateInput.cs b/src/Znyc.Dispatching.Application/Yard/Dto/Input/YardUpdateInput.cs new file mode 100644 index 0000000..6fad033 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Yard/Dto/Input/YardUpdateInput.cs @@ -0,0 +1,9 @@ +namespace Znyc.Dispatching.Application +{ + /// + /// + public class YardUpdateInput : YardAddInput + { + public long Id { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Yard/Dto/OutPut/YardListOutput.cs b/src/Znyc.Dispatching.Application/Yard/Dto/OutPut/YardListOutput.cs new file mode 100644 index 0000000..4f56a48 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Yard/Dto/OutPut/YardListOutput.cs @@ -0,0 +1,28 @@ + +namespace Znyc.Dispatching.Application +{ + public class YardListOutput + { + public long Id { get; set; } + /// + /// 精度 + /// + public decimal Longitude { get; set; } + + /// + /// 纬度 + /// + + public decimal Latitude { get; set; } + + /// + /// 地址 + /// + public string Address { get; set; } + + /// + /// 是否默认 + /// + public bool IsDefault { get; set; } + } +} diff --git a/src/Znyc.Dispatching.Application/Yard/Dto/OutPut/YardOutput.cs b/src/Znyc.Dispatching.Application/Yard/Dto/OutPut/YardOutput.cs new file mode 100644 index 0000000..ac7daa1 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Yard/Dto/OutPut/YardOutput.cs @@ -0,0 +1,47 @@ +namespace Znyc.Dispatching.Application +{ + public class YardOutput + { + /// + /// 主键 + /// + public long Id { get; set; } + + /// + /// 精度 + /// + public decimal Longitude { get; set; } + + /// + /// 纬度 + /// + + public decimal Latitude { get; set; } + + /// + /// 地址 + /// + public string Address { get; set; } + + /// + /// 联系人 + /// + public string ContactPerson { get; set; } + + /// + /// 联系人电话 + /// + public string ContactPhone { get; set; } + + /// + /// 是否默认 + /// + public bool IsDefault { get; set; } + + /// + /// 状态(字典 0正常 1停用 2删除) + /// + public int Status { get; set; } + + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Yard/Services/IYardService.cs b/src/Znyc.Dispatching.Application/Yard/Services/IYardService.cs new file mode 100644 index 0000000..f9e2513 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Yard/Services/IYardService.cs @@ -0,0 +1,7 @@ +namespace Znyc.Dispatching.Application +{ + public interface IYardService + { + + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Yard/Services/YardService.cs b/src/Znyc.Dispatching.Application/Yard/Services/YardService.cs new file mode 100644 index 0000000..421be04 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Yard/Services/YardService.cs @@ -0,0 +1,202 @@ +using Furion.DatabaseAccessor; +using Furion.DependencyInjection; +using Furion.DynamicApiController; +using Furion.FriendlyException; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Znyc.Dispatching.Core; +using Znyc.Dispatching.Core.Entitys; +using Znyc.Dispatching.Core.Extension; + +namespace Znyc.Dispatching.Application +{ + /// + /// 车场服务 + /// + [ApiDescriptionSettings(Name = "yard", Order = 30)] + public class YardService : IYardService, IDynamicApiController, ITransient + { + private readonly ICacheService _cacheService; + private readonly IRepository _repository; + private readonly IUserManager _userManager; + private readonly IRepository _userYardRepository; + private readonly IUserService _userService; + + public YardService( + IRepository repository, + IUserManager userManager, + ICacheService cacheService, + IRepository userYardRepository, + IUserService userService + ) + { + _repository = repository; + _userManager = userManager; + _cacheService = cacheService; + _userYardRepository = userYardRepository; + _userService = userService; + } + + /// + /// 车场信息 + /// + /// + /// + /// + [HttpGet] + public async Task> PageAsync(int currentPage, int pageSize) + { + List yards = await _cacheService.GetYardListAsync(_userManager.CompanyId); + UserYard userYard = await _userYardRepository.DetachedEntities + .FirstOrDefaultAsync(x => x.UserId == _userManager.UserId && x.CompanyId == _userManager.CompanyId && x.IsDefault == true); + if (userYard.IsNotNull()) + { + foreach (var item in yards) + { + if (item.Id == userYard.YardId) + { + item.IsDefault = true; + } + } + } + PagedList data = new PagedList + { + Items = yards.Skip((currentPage - 1) * pageSize).Take(pageSize).ToList(), + TotalCount = yards.Count + }; + return data; + } + + /// + /// 根据id获取车场信息 + /// + /// + [HttpGet] + public async Task GetYardByIdAsync(long id) + { + return await _cacheService.GetYardAsync(id); + } + + /// + /// 添加车场信息 + /// + /// + /// + [HttpPost] + [UnitOfWork] + public async Task InsertAsync(YardAddInput input) + { + await _repository.InsertAsync(new Yard + { + CompanyId = _userManager.CompanyId, + ContactPerson = input.ContactPerson, + ContactPhone = input.ContactPhone, + Longitude = input.Longitude, + Latitude = input.Latitude, + Address = input.Address, + Status = (int)CommonStatusEnum.ENABLE + }); + //清除车场列表Cache + await _cacheService.RemoveYardListAsync(_userManager.CompanyId); + //清楚公司Cache + await _cacheService.RemoveCompanyAsync(_userManager.CompanyId); + } + + /// + /// 编辑车场信息 + /// + /// + [HttpPut] + [UnitOfWork] + public async Task UpdateAsync(YardUpdateInput input) + { + Yard yard = await _repository.FindOrDefaultAsync(input.Id); + yard.ContactPerson = input.ContactPerson; + yard.ContactPhone = input.ContactPhone; + yard.Latitude = input.Latitude; + yard.Longitude = input.Longitude; + yard.Address = input.Address; + Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntry result = await _repository.UpdateNowAsync(yard); + if (result.IsNull()) + { + throw Oops.Oh("更新车场信息失败"); + } + + //清除车场Cache + await _cacheService.RemoveYardAsync(yard.Id); + //清除车场列表Cache + await _cacheService.RemoveYardListAsync(_userManager.CompanyId); + //清除公司Cache + await _cacheService.RemoveCompanyAsync(_userManager.CompanyId); + } + + /// + /// 删除车场信息 + /// + /// + /// + [HttpDelete] + public async Task RemoveYardByIdAsync(long id) + { + Yard yard = await _repository + .FirstOrDefaultAsync(x => x.Id == id && x.CompanyId == _userManager.CompanyId); + if (yard.IsNull()) + { + throw Oops.Oh("车场不存在"); + } + //删除与车场相关 + List userYards = await _userYardRepository.Where(x => x.YardId == id).ToListAsync(); + userYards.ForEach(x => { x.IsDeleted = true; }); + await _userYardRepository.UpdateNowAsync(userYards); + + yard.IsDeleted = true; + await _repository.UpdateIncludeNowAsync(yard, new[] { nameof(Yard.IsDeleted) }); + await _cacheService.RemoveYardAsync(id); + await _cacheService.RemoveYardListAsync(_userManager.CompanyId); + //清除公司Cache + await _cacheService.RemoveCompanyAsync(_userManager.CompanyId); + } + + /// + /// 修改默认车场信息 + /// + /// + [HttpPut] + [UnitOfWork] + public async Task UpdateIsDefaultAsync(long id) + { + UserYard oldUserYard = await _userYardRepository.FirstOrDefaultAsync(x => x.IsDefault == true + && x.CompanyId == _userManager.CompanyId && x.UserId == _userManager.UserId); + if (oldUserYard.IsNotNull()) + { + oldUserYard.IsDefault = false; + await _userYardRepository.UpdateNowAsync(oldUserYard); + } + UserYard newUserYard = await _userYardRepository.FirstOrDefaultAsync(x => x.YardId == id + && x.CompanyId == _userManager.CompanyId && x.UserId == _userManager.UserId); + if (newUserYard.IsNotNull()) + { + newUserYard.IsDefault = true; + await _userYardRepository.UpdateNowAsync(newUserYard); + } + else + { + await _userYardRepository.InsertAsync(new UserYard + { + UserId = _userManager.UserId, + YardId = id, + CompanyId = _userManager.CompanyId, + IsDefault = true + }); + } + + //清除公司Cache + await _cacheService.RemoveCompanyAsync(_userManager.CompanyId); + } + } + + +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Znyc.Dispatching.Application.csproj b/src/Znyc.Dispatching.Application/Znyc.Dispatching.Application.csproj new file mode 100644 index 0000000..fe90e2b --- /dev/null +++ b/src/Znyc.Dispatching.Application/Znyc.Dispatching.Application.csproj @@ -0,0 +1,59 @@ + + + net6.0 + 1701;1702;1591 + Znyc.Dispatching.Application.xml + + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/Znyc.Dispatching.Application.xml b/src/Znyc.Dispatching.Application/Znyc.Dispatching.Application.xml new file mode 100644 index 0000000..c51ed68 --- /dev/null +++ b/src/Znyc.Dispatching.Application/Znyc.Dispatching.Application.xml @@ -0,0 +1,5205 @@ + + + + Znyc.Dispatching.Application + + + + + 系统缓存服务 + + + + + 获取短信验证码缓存 + + + + + + + 设置短信验证码缓存 + + + + + + + + 删除短信验证码缓存 + + + + + + + 获取权限缓存(按钮) + + + + + + + 缓存权限 + + + + + + + + 同步车辆分组缓存 + + + + + + + + 获取车辆分组缓存 + + + + + + + 同步车辆类型缓存 + + + + + + + 获取车辆分组缓存 + + + + + + 同步角色菜单缓存 + + + + + + + + 获取角色菜单缓存 + + + + + + 删除角色菜单缓存 + + + + + + + 同步角色缓存 + + + + + + + 获取角色缓存 + + + + + + 同步字典类型缓存 + + + + + + + 获取字典缓存 + + + + + + 同步公司缓存 + + + + + + + 获取公司缓存 + + + + + + + + 删除公司缓存 + + + + + + + 同步用户缓存 + + + + + + + 获取用户缓存 + + + + + + 移除用户缓存 + + + + + + + 删除用户所有缓存 + + + + + + 写入token + + + + + + + + 移除token + + + + + + + 获取token + + + + + + + 设置公司车辆缓存 + + + + + + + + 删除公司车辆缓存 + + + + + + + 删除公司车辆缓存 + + + + + + + 获取公司车辆缓存 + + + + + + + 获取公司车辆缓存 + + + + + + + 设置车辆缓存 + + + + + + + 删除车辆缓存 + + + + + + + 获取车辆缓存 + + + + + + + 设置员工缓存 + + + + + + + 删除员工缓存 + + + + + + + 获取员工缓存 + + + + + + + 设置员工列表缓存 + + + + + + + + 删除员工列表缓存 + + + + + + + 获取员工列表缓存 + + + + + + + 设置未读警报提醒缓存 + + + + + + + 删除未读警报提醒缓存 + + + + + + + 获取未读警报提醒缓存 + + + + + + 同步轨迹回放缓存 + + + + + + + + + + + 获取轨迹回放缓存 + + + + + + + + + 删除轨迹回放缓存 + + + + + + + + + 设置车场缓存 + + + + + + + 删除车场缓存 + + + + + + + 获取车场缓存 + + + + + + + 设置车场列表缓存 + + + + + + + + 删除车场列表缓存 + + + + + + + 获取车场列表缓存 + + + + + + + 同步工程列表缓存 + + + + + + + + 获取工程列表缓存 + + + + + + + 删除工程列表缓存 + + + + + + + 设置工程缓存 + + + + + + + 删除工程缓存 + + + + + + + 获取工程缓存 + + + + + + + 设置订单列表缓存 + + + + + + + + 删除订单列表缓存 + + + + + + + 获取订单列表缓存 + + + + + + + 设置车辆车组人员缓存 + + + + + + + + 删除车辆车组人员缓存 + + + + + + + 获取车辆车组人员缓存 + + + + + + + 设置缓存 + + + + + + + + 删除缓存 + + + + + + + 获取缓存 + + + + + + + 设置缓存 + + + + + + + 删除缓存 + + + + + + + 获取缓存 + + + + + + + 获取缓存 + + + + + + + 获取短信验证码 + + + + + + + 设置验证码缓存 + + + + + + + + 删除验证码 + + + + + + + 获取权限缓存(按钮) + + + + + + + 缓存权限 + + + + + + + + 同步车辆分组缓存 + + + + + + + + 获取车辆分组缓存 + + + + + + + 同步车辆类型缓存 + + + + + + + 获取车辆类型缓存 + + + + + + 同步角色菜单缓存 + + + + + + + + 获取角色菜单缓存 + + + + + + + 删除角色菜单缓存 + + + + + + + 同步角色缓存 + + + + + + + 获取角色缓存 + + + + + + 获取字典缓存 + + + + + + 同步字典缓存 + + + + + + + 同步公司缓存 + + + + + + + 获取公司缓存 + + + + + + + + 删除公司缓存 + + + + + + + 同步用户缓存 + + + + + + + 获取用户缓存 + + + + + + + 移除用户缓存 + + + + + + + 删除用户所有缓存 + + + + + + 写入token + + + + + + + + 移除token + + + + + + + 获取token + + + + + + + 发布 + + + + + + + + 订阅 + + + + + + + 设置公司车辆缓存 + + + + + + + + 删除公司车辆缓存 + + + + + + + 删除公司车辆缓存 + + + + + + + 获取所有公司车辆缓存 + + + + + + + 获取公司车辆缓存 + + + + + + + 设置车辆缓存 + + + + + + + 删除车辆缓存 + + + + + + + 获取车辆缓存 + + + + + + + 设置车辆缓存 + + + + + + + 删除员工缓存 + + + + + + + 获取员工缓存 + + + + + + + 设置员工列表缓存 + + + + + + + + 删除员工列表缓存 + + + + + + + 获取员工列表缓存 + + + + + + + 设置未读警报提醒缓存 + + + + + + + 删除未读警报提醒缓存 + + + + + + + 获取未读警报提醒缓存 + + + + + + 同步轨迹回放缓存 + + + + + + + + + + + 获取轨迹回放缓存 + + + + + + + + + 删除轨迹回放缓存 + + + + + + + + + 设置车场缓存 + + + + + + + 删除车场缓存 + + + + + + + 获取车场缓存 + + + + + + + 设置车场列表缓存 + + + + + + + + 删除车场列表缓存 + + + + + + + 获取车场列表缓存 + + + + + + + 同步工程列表缓存 + + + + + + + + 获取工程列表缓存 + + + + + + + 删除工程列表缓存 + + + + + + + 设置工程缓存 + + + + + + + 删除工程缓存 + + + + + + + 获取工程缓存 + + + + + + + 设置车辆车组人员缓存 + + + + + + + + 删除车辆车组人员缓存 + + + + + + + 获取车辆车组人员缓存 + + + + + + + 设置缓存 + + + + + + + + 删除缓存 + + + + + + + 获取缓存 + + + + + + + 设置缓存 + + + + + + + + 删除缓存 + + + + + + + 获取缓存 + + + + + + + 是否存在 + + + + + + + 公司输入实体 + + + + + 公司名称 + + + + + 联系人 + + + + + 联系人电话 + + + + + 精度 + + + + + 纬度 + + + + + 地址 + + + + + OpenId + + + + + + + + + 平台入驻 + + + + + JsCode + + + + + 用户名 + + + + + 头像 + + + + + 手机号 + + + + + 公司名称 + + + + + 精度 + + + + + 纬度 + + + + + 地址 + + + + + 主键 + + + + + 公司名称 + + + + + 公司Logo + + + + + 精度 + + + + + 纬度 + + + + + 地址 + + + + + 状态(字典 0正常 1停用 2删除) + + + + + 停留标示Id + + + + + 车场 + + + + + 一键派工是否显示工程名称 + + + + + 调度角色是否有添 加工程名称的权限 + + + + + 是否启用任务车型选项,默认为关 + + + + + 公司服务 + + + + + 获取当前公司信息 + + + + + + 根据Id获取公司信息 + + + + + + + 更新公司信息 + + + + + + + 平台入驻 + + + + + + + 获取位置名称 + + + + + + + + 修改公司停留标示 + + + + + + + 一键派工是否显示工程名称 + + + + + + 调度角色是否有添 加工程名称的权限 + + + + + + + + + + + + 注册公司 + + + + + + + 获取当前公司信息 + + + + + + 根据Id获取公司信息 + + + + + + 更新公司信息 + + + + + + + 获取位置名称 + + + + + + + + 修改公司停留标示 + + + + + + + 一键派工是否显示工程名称 + + + + + + 调度角色是否有添 加工程名称的权限 + + + + + + 新增是否启用任务车型选项,默认为关 + + + + + + Id + + + + + 匹配索引列表,与车组人员共用一个列表,所以需增加UserId来进行匹配 + + + + + 施工单位 + + + + + 联系人 + + + + + 联系电话 + + + + + 状态 + + + + + 施工单位 + + + + + InsertAsync + + + + + + + UpdateAsync + + + + + + + ListAsync + + + + + + InsertAsync + + + + + + + UpdateAsync + + + + + + + + + + + + + 字典服务 + + + + + 根据Id获取字典 + + + + + + + 根据code获取字典 + + + + + + + 根据Id获取字典 + + + + + + + 根据code获取字典 + + + + + + + 主键Id + + + + + 字典编码 + + + + + 字典值 + + + + + 字典名称 + + + + + 排序 + + + + + Cos服务 + + + + + 获取COS_Token + + + + + + 获取COS_Token + + + + + + 员工名 + + + + + 手机号 + + + + + 角色 + + + + + 角色名称 + + + + + 状态 + + + + + 员工头像 + + + + + + + + + 用户Id + + + + + + + + + + + + + + + 员工名 + + + + + 手机号 + + + + + + + + + 用户Id + + + + + 公司Id + + + + + 员工名 + + + + + 手机号 + + + + + 角色 + + + + + 角色名称 + + + + + 状态 + + + + + 员工头像 + + + + + 员工服务 + + + + + 员工信息 + + + + + 0全部,停用-1,正常1 + + + + + + + 根据id获取员工资料 + + + + + + 添加员工信息 + + + + + + + 编辑员工信息 + + + + + + 软删除员工信息 + + + + + + + 员工信息 + + + + + + + + + + + + 根据id获取员工资料 + + + + + + 添加员工信息 + + + + + + + 编辑员工信息 + + + + + + 软删除员工信息 + + + + + + + 审计日志服务 + + + + + 异常日志服务 + + + + + 登录实体 + + + + + getuserinfo信息 + + + + + 昵称 + + + + + 头像地址 + + + + + Token + + + + + 跳转路径 + + + + + 角色菜单列表 + + + + + 用户登录 + + + + + + + 登录服务 + + + + + 用户登录 + + + + + + + 清理缓存 + + + + + + 操作日志服务 + + + + + 菜单参数 + + + + + 父Id + + + + + 所属层级 + + + + + 名称 + + + + + 编码 + + + + + 菜单类型(字典 0目录 1菜单 2按钮) + + + + + 图标 + + + + + 路由地址 + + + + + 权限标识 + + + + + 是否外链 + + + + + 是否展开 + + + + + 是否显示 + + + + + 排序 + + + + + 详细描述 + + + + + CommonStatus + + + + + 菜单树(列表形式) + + + + + 菜单Id + + + + + 子集 + + + + + 系统菜单服务 + + + + + 获取用户权限(按钮权限标识集合) + + + + + + + 消息记录 + + + + + 接收者Id + + + + + 消息Id + + + + + 状态 + + + + + + + + + 主键 + + + + + 消息标题 + + + + + 消息内容 + + + + + 消息记录服务 + + + + + + + + + 消息标题 + + + + + 产品类型 + + + + + 消息内容 + + + + + + + + + 主键 + + + + + 消息类型 + + + + + 消息标题 + + + + + 消息内容 + + + + + 站内消息服务 + + + + + 分页查询系统消息通知 + + + + + + + + + 获取消息通知详情 + + + + + + + + + + + + 加油日期 + + + + + 车辆Id + + + + + 车俩编号 + + + + + 加油单号 + + + + + 加油量 + + + + + 油单价 + + + + + 金额 + + + + + + + + + 油耗统计 + + + + + 主键 + + + + + 车俩编号 + + + + + 加油量 + + + + + 金额 + + + + + 加油次数 + + + + + 加油总次数 + + + + + 加油总量 + + + + + 总金额 + + + + + 油耗统计 + + + + + 主键 + + + + + 加油日期 + + + + + 车俩编号 + + + + + 加油单号 + + + + + 加油量 + + + + + 油单价 + + + + + 金额 + + + + + 油耗服务 + + + + + 加油记录 + + + + + + + + + + 油耗统计 + + + + + + + + 根据Id获油耗信息 + + + + + + 添加油耗信息 + + + + + + + 修改油耗信息 + + + + + + + 删除油耗信息 + + + + + + + 油耗管理 + + + + + 加油记录 + + + + + + + + + + 油耗统计 + + + + + + + + 根据Id获油耗信息 + + + + + + 添加油耗信息 + + + + + + + 修改油耗信息 + + + + + + + 删除油耗信息 + + + + + + + + + + + 指派任务人员Id + + + + + 姓名 + + + + + 电话 + + + + + 是否司机 + + + + + + + + + OrderId + + + + + 订单内容 + + + + + 状态,,10=待指派,20=已指派,未接单,30=已接单,40=已出发,50=已完成(已签单),60=已离开,70=已评价 + + + + + 状态名称 + + + + + 车辆编号 + + + + + 车组人员 + + + + + 是否司机 + + + + + 创建时间 + + + + + + + + + UserId + + + + + 姓名 + + + + + 电话 + + + + + 是否司机 + + + + + + + + + 图片路径 + + + + + + + + + 图片路径 + + + + + 指派订单实体 + + + + + OrderId + + + + + 车辆Id + + + + + 车辆编号 + + + + + 是否外请车 + + + + + 车组人员 + + + + + 是否替换车组人员 + + + + + 车组人员 + + + + + 指派订单实体 + + + + + OrderId + + + + + 派单订单表实体 + + + + + 工程Id + + + + + 工程名称 + + + + + 业务员Id + + + + + 到场时间 + + + + + 经度 + + + + + 纬度 + + + + + 地址 + + + + + 订单内容 + + + + + 车辆Id + + + + + 是否外请车 + + + + + 车组人员 + + + + + 任务车型 + + + + + + + + + 签单 + + + + + OrderId + + + + + 图片路径 + + + + + 签单 + + + + + 待指派订单实体 + + + + + 工程Id + + + + + 工程名称 + + + + + 业务员Id + + + + + 到场时间 + + + + + 经度 + + + + + 纬度 + + + + + 地址 + + + + + 订单内容 + + + + + 任务车型 + + + + + 施工单位 + + + + + 施工单位 + + + + + 待指派订单实体 + + + + + OrderId + + + + + 订单列表返回 + + + + + 订单列表返回 + + + + + OrderId + + + + + 车辆Id,,冗余查询车辆列表状态 + + + + + 车辆编号 + + + + + 订单标题 + + + + + 订单内容 + + + + + 到场时间 + + + + + 订单来源 + + + + + 订单来源明细 + + + + + 是否外请车 + + + + + 状态, + + + + + 状态名称 + + + + + 经度 + + + + + 纬度 + + + + + 地址 + + + + + 工程Id + + + + + 工程名称 + + + + + 车组人员 + + + + + + + + + + 车组人员姓名 + + + + + 车组人员电话 + + + + + 指派人名称 + + + + + 指派人Id + + + + + 任务车型 + + + + + 任务车型名称 + + + + + 距离 + + + + + 预计到到时间 + + + + + + + + + + OrderId + + + + + 车辆Id + + + + + 车俩编号 + + + + + 工程Id + + + + + 工程名称 + + + + + 业务员Id + + + + + 到场时间 + + + + + 经度 + + + + + 纬度 + + + + + 地址 + + + + + 订单标题 + + + + + 订单内容 + + + + + 状态,,10=待指派,20=已指派,未接单,30=已接单,40=已出发,50=已完成(已签单),60=已离开,70=已评价 + + + + + 状态 + + + + + 订单来源,10=录入,20=报单 + + + + + 订单来源 + + + + + 车组人员 + + + + + 车组人员信息 + + + + + 签证明细 + + + + + 是否外请车 + + + + + + + + + + + + + + + 距离 + + + + + 预计到到时间 + + + + + 施工单位 + + + + + 施工单位 + + + + + 到家时间 + + + + + + + + + + + + + + + + + + + + 派单量 + + + + + 完成量 + + + + + 服务评价 + + + + + 派单量 + + + + + 完成量 + + + + + 服务评价 + + + + + 设备号 + + + + + 项目名称 + + + + + 员工姓名 + + + + + 派单量 + + + + + 接单量 + + + + + 完成量 + + + + + 服务评价 + + + + + 回单查询列表 + + 0全部,1未接单,2已结单,3已完成,4已评价 + + + + + + + + + + 任务统计--车辆 + + + + + + + + + + 任务统计--工程 + + + + + + + + + + 任务统计--人员 + + + + + + + + + + 派工订单管理 + + + + + 管理端任务列表 + + + + + + + + + + 保存待指派 + + + + + + + 指派订单 + + + + + + + 完成订单 + + + + + + + 删除订单 + + + + + + + 撤销订单 + + + + + + + 接单 + + + + + + + 签单 + + + + + + + 车组人员端任务列表 + + + + + + + + 外租伙伴端任务列表 + + + + + + + + 查询订单明细 + + + + + + + v1.2.2 工地联系人调整为非必填项,如果用户先录入 了工程名称基础数据,而且工程名称派工开关 设置为打开,而且用户派工时选择了工程名称 的情况下,则用户录入派工任务保存或指派成 功后,自动将任务内容里该工地联系人关联至 该工程的工地联系人这里 + + + + + + + + + 更新签证单 + + + + + + + 回单查询列表 + + 0全部,1未接单,2已结单,3已完成,4已评价 + + + + + + + + + + 任务统计--车辆 + + + + + + + + + + 任务统计--工程 + + + + + + + + + + 任务统计--人员 + + + + + + + + + + 任务统计--施工单位 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + . + + + + + + 公司输入实体 + + + + + 联系人Id,链接员工表Id + + + + + 联系人姓名 + + + + + 联系人电话 + + + + + + + + + 联系人Id,链接员工表Id + + + + + 联系人姓名 + + + + + 联系人电话 + + + + + 工程信息联系人管理 + + + + + 根据工程Id查询工程联系人 + + + + + + 工程输入实体 + + + + + 工程名称 + + + + + 业务员Id + + + + + 经度 + + + + + 纬度 + + + + + 地址 + + + + + 工地联系人Id + + + + + 是否启用 + + + + + 施工单位 + + + + + 施工单位 + + + + + + + + + Id + + + + + 工程名称 + + + + + 工程地址 + + + + + 经度 + + + + + 纬度 + + + + + 工地联系人(报单人) + + + + + 是否启用 + + + + + 业务员Id + + + + + 施工单位 + + + + + Id + + + + + 工程名称 + + + + + 业务员Id + + + + + 业务员 + + + + + 经度 + + + + + 纬度 + + + + + 地址 + + + + + 工地联系人(报单人) + + + + + 是否启用 + + + + + 施工单位 + + + + + 施工单位 + + + + + 工程服务 + + + + + 查询单个工程信息 + + + + + + 工程列表 + + + + + + + + + + 新增工程信息 + + + + + + + 更新公司信息 + + + + + + + 停用工程信息 + + + + + + + 工程管理 + + + + + 查询单个工程信息 + + + + + + 工程列表 + + -1全部,停用0,正常1 + + + + + + + + 新增工程信息 + + + + + + + 更新工程信息 + + + + + + + 停用工程信息 + + + + + + + 反馈明细 + + + + + 意见反馈服务 + + + + + 添加意见反馈 + + + + + + + 获取角色的菜单Id集合 + + + + + + + 获取角色菜单 + + + + + + 同步角色菜单 + + + + + + 角色菜单服务 + + + + + 获取角色的菜单Id集合 + + + + + + + 获取角色的菜单 + + + + + + 同步角色菜单 + + + + + + RoleOutput + + + + + Id + + + + + 名称 + + + + + 权限备注 + + + + + 角色服务 + + + + + 获取角色 + + + + + + 用户角色服务 + + + + + 获取用户的角色Id集合 + + + + + + + + + + + + + + + + + + jscode + + + + + EncryptedData + + + + + Iv + + + + + 用户输入实体 + + + + + 用户名 + + + + + 头像 + + + + + + + + + + 公司Id + + + + + 角色Id + + + + + 用户修改密码 + + + + + 手机号 + + + + + 旧密码 + + + + + 新密码 + + + + + 确认密码 + + + + + 验证码 + + + + + 用户输入实体 + + + + + 用户名 + + + + + 头像 + + + + + 电话 + + + + + 用户输出实体 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 电话 + + + + + 邀请用户注册并登陆 + + + + + + + 修改用户信息 + + + + + + + 用户服务 + + + + + 个人资料 + + + + + + 邀请用户注册并登陆 + + + + + + + 修改用户信息 + + + + + + + 更新用户信息 + + + + + + + + 解密电话号码 + + + + + + + 公司Id + + + + + 名称 + + + + + 父级权限Id + + + + + + + + + Id + + + + + 公司Id + + + + + 名称 + + + + + 父级权限Id + + + + + 状态(字典 0正常 1停用 2删除) + + + + + 新增车辆分组 + + + + + + + 车辆分组服务 + + + + + 新增车辆分组 + + + + + + + 获取车辆分组 + + + + + + 同步车辆分组缓存 + + + + + + 车辆 + + + + + 车组人员Id + + + + + 员工名 + + + + + 手机号 + + + + + 是否司机 + + + + + + + + + + + + + + Id + + + + + 车辆Id + + + + + 员工Id + + + + + 员工名称 + + + + + 手机号 + + + + + 是否司机 + + + + + + + + + + 车组人员信息联系人管理 + + + + + 根据车辆Id查询车组人员信息 + + + + + + 新增车型 + + + + + 名称 + + + + + 状态(字典 0正常 1停用 2删除) + + + + + + + + + + + + + + + + + + + 匹配索引列表,与车组人员共用一个列表,所以需增加UserId来进行匹配 + + + + + 名称 + + + + + 车辆类型服务 + + + + + + + + + + + + + + + + + + UpdateAsync + + + + + + + 基础信息-车辆编辑 + + + + + 车辆编号 + + + + + 车牌号 + + + + + 状态 + + + + + 车组人员 + + + + + 轨迹纠偏入参 + + + + + 维度 + + + + + 精度 + + + + + 角度 + + + + + 速度 + + + + + 时间 + + + + + 所属公司 + + + + + 车辆编号 + + + + + 车牌号 + + + + + 车辆类型 + + + + + 所属车组 + + + + + SIM卡号 + + + + + 设备类型 + + + + + 设备号 + + + + + 联系人 + + + + + 司机电话 + + + + + 状态(字典 0正常 1停用 2删除) + + + + + 打火状态 + + + + + 工作状态 + + + + + IEML号 + + + + + GPS时间 + + + + + 开通时间 + + + + + 到期时间 + + + + + 车辆编号 + + + + + 车牌号 + + + + + 联系人 + + + + + 联系人电话 + + + + + 超速最新未读时间 + + + + + 超速未读数 + + + + + 自编号 + + + + + 车牌号 + + + + + 地图 + + + + + 车辆编号 + + + + + 精度 + + + + + 图标 + + + + + + + + + + + + + + + + + + + + + + + + + 总数 + + + + + 超速记录输出集合 + + + + + 超速记录输出实体 + + + + + 所属车辆 + + + + + 所属公司 + + + + + 车辆编号 + + + + + GPS时间 + + + + + 车牌号 + + + + + 经度 + + + + + 纬度 + + + + + 方向 + + + + + 速度 + + + + + 地点 + + + + + 轨迹纠偏出参 + + + + + 数据体 + + + + + 30001错误表示抓路失败。当传入点数较少或较稀疏时,可能会导致抓路失败。 + + + + + + + + + + + + + + + 数据体 + + + + + 总距离 + + + + + 返回坐标合集 + + + + + 返回坐标合集 + + + + + 维度 + + + + + 经度 + + + + + 车辆编号 + + + + + 车牌号 + + + + + 总公里 + + + + + 总停留时间 + + + + + 轨迹点输出实体 + + + + + 停留点输出实体 + + + + + 经度 + + + + + 纬度 + + + + + GPS时间 + + + + + 速度 + + + + + 地址 + + + + + 方向 + + + + + 开始时间 + + + + + 结束时间 + + + + + 静止时长 + + + + + 是否是停留点 + + + + + 停留次数 + + + + + 打火状态 + + + + + ACC持续时长 + + + + + 工作状态 + + + + + 工作持续时长 + + + + + 总里程 + + + + + 经度 + + + + + 纬度 + + + + + 地址 + + + + + 开始时间 + + + + + 结束时间 + + + + + GPS时间 + + + + + 状态 + + + + + 停留点描述 + + + + + 停留时长描述 + + + + + 总里程 + + + + + 车辆编号 + + + + + 车牌号 + + + + + 状态 + + + + + 车组人员 + + + + + 车组人员姓名 + + + + + 车组人员电话 + + + + + 所属公司 + + + + + 车辆编号 + + + + + 车牌号 + + + + + 联系人 + + + + + 联系电话 + + + + + 状态(字典 1正常 -1停用 -2删除 0审核中) + + + + + 打火状态 + + + + + 工作状态 + + + + + Gps状态 + + + + + Gps状态名称 + + + + + GPS时间 + + + + + 最后通讯时间 + + + + + 速度 + + + + + 地址 + + + + + ACC持续时长 + + + + + 经度 + + + + + 纬度 + + + + + IMEI号 + + + + + 设备类型 + + + + + 开通时间 + + + + + 到期时间 + + + + + 设备类型 + + + + + 离线时长 + + + + + work工作时长 + + + + + 静止时长 + + + + + 是否开启超速报警 + + + + + 超速速度 + + + + + 是否激活 + + + + + 是否开启GPS + + + + + Sim卡号 + + + + + 更新时间 + + + + + Gps信号模式 1 GPS,2 基站 + + + + + 车辆任务状态 + + + + + 车组人员 + + + + + 位置基本信息报警标志位 --欠压 + + + + + Gps信号源 + + + + + 掉电 + + + + + 排序 + + + + + 方向 + + + + + 全部数 + + + + + 离线数 + + + + + 静止数 + + + + + 行驶数 + + + + + 根据id获取车辆Gps信息 + + + + + + + 获取地图信息 + + + + + + 更新车辆信息 + + + + + + + 轨迹回放纠偏 + + + + + + + + + 修改超速报警/速度 + + + + + + + + + 超速记录 + + + + + + + + 未读报警消息数 + + + + + + 同步轨迹 + + + + + + 基础数据-车辆管理列表 + + + + + + + + + + 基础数据-车辆信息 + + + + + + + 更新车辆基础信息 + + + + + + + 分页查询 + + + 0全部,1任务中,2空闲,3停用 + + + + + + + 车辆信息服务 + + + + + 根据id获取车辆Gps信息 + + + + + + + 获取地图信息 + + + + + + 分页查询 + + + 0全部,1任务中,2空闲,3停用 + + + + + + + 修改车辆信息 + + + + + + + 轨迹回放纠偏 + + + + + + + + + 修改超速报警/速度 + + + + + + + + + 超速报警列表 + + + + + + + + 未读报警消息数 + + + + + + 获取车辆GPS信息 + + 车辆Id + 开始时间 + 结束时间 + + + + + 轨迹点预处理(车辆) + + + + + + + 离线车辆报警 + + + + + + 同步轨迹 + + + + + + 轨迹回放纠偏 + + + + + + + + + + + 基础数据-车辆管理列表 + + + + + 0全部,停用-1,正常1 + + + + + 基础数据-车辆信息 + + + + + + + 更新车辆基础信息 + + + + + + + + + + + + + + + 车辆类型服务 + + + + + 车场输入实体 + + + + + 精度 + + + + + 纬度 + + + + + 地址 + + + + + 联系人 + + + + + 联系人电话 + + + + + + + + + 精度 + + + + + 纬度 + + + + + 地址 + + + + + 是否默认 + + + + + 主键 + + + + + 精度 + + + + + 纬度 + + + + + 地址 + + + + + 联系人 + + + + + 联系人电话 + + + + + 是否默认 + + + + + 状态(字典 0正常 1停用 2删除) + + + + + 车场服务 + + + + + 车场信息 + + + + + + + + 根据id获取车场信息 + + + + + + 添加车场信息 + + + + + + + 编辑车场信息 + + + + + + 删除车场信息 + + + + + + + 修改默认车场信息 + + + + + + 添加 + + + + + 施工单位 + + + + + 联系人 + + + + + 联系电话 + + + + + 状态 + + + + + 修改 + + + + + 主键Id + + + + + 添加 + + + + + 字典父级 + + + + + 字典名称 + + + + + 字典编码 + + + + + 字典值 + + + + + 描述 + + + + + 启用 + + + + + 修改 + + + + + 主键Id + + + + + 版本 + + + + + 任务调度参数 + + + + + 任务名称 + + + + + 执行间隔时间(单位秒) + + 5 + + + + Cron表达式 + + + + + 定时器类型 + + + + + 执行次数 + + + + + 请求url + + + + + 请求参数(Post,Put请求用) + + + + + Headers(可以包含如:Authorization授权认证) + 格式:{"Authorization":"userpassword.."} + + + + + 请求类型 + + + + + 备注 + + + + + 任务Id + + + + + 任务信息---任务详情 + + + + + Id + + + + + 任务名称 + + + + + 执行间隔时间(单位秒) + + + + + Cron表达式 + + + + + 定时器类型 + + + + + 执行次数 + + + + + 请求url + + + + + 请求参数(Post,Put请求用) + + + + + Headers(可以包含如:Authorization授权认证) + 格式:{"Authorization":"userpassword.."} + + + + + 请求类型 + + 2 + + + + 备注 + + + + + 定时器状态 + + + + + 异常信息 + + + + + 任务调度服务 + + + + diff --git a/src/Znyc.Dispatching.Application/applicationconfig.Development.json b/src/Znyc.Dispatching.Application/applicationconfig.Development.json new file mode 100644 index 0000000..beacb07 --- /dev/null +++ b/src/Znyc.Dispatching.Application/applicationconfig.Development.json @@ -0,0 +1,15 @@ +//v1.1.8业务要求设置为永不过期 +{ + "JWTSettings": { + "ValidateIssuerSigningKey": true, // 是否验证密钥,bool 类型,默认true + "IssuerSigningKey": + "3c1cbc3f546eda35168c3aa3cb91780fbe703f0996c6d123ea96dc85c70bbc0a", // 密钥,string 类型,必须是复杂密钥,长度大于16 + "ValidateIssuer": true, // 是否验证签发方,bool 类型,默认true + "ValidIssuer": "znyc", // 签发方,string 类型 + "ValidateAudience": true, // 是否验证签收方,bool 类型,默认true + "ValidAudience": "znyc", // 签收方,string 类型 + "ValidateLifetime": true, // 是否验证过期时间,bool 类型,默认true,建议true + "ExpiredTime": 864000, // 过期时间,long 类型,单位分钟 + "ClockSkew": 5 // 过期时间容错值,long 类型,单位秒,默认5秒 + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/applicationconfig.Production.json b/src/Znyc.Dispatching.Application/applicationconfig.Production.json new file mode 100644 index 0000000..e1a9c69 --- /dev/null +++ b/src/Znyc.Dispatching.Application/applicationconfig.Production.json @@ -0,0 +1,15 @@ +//v1.1.8业务要求设置为永不过期 +{ + "JWTSettings": { + "ValidateIssuerSigningKey": true, // 是否验证密钥,bool 类型,默认true + "IssuerSigningKey": + "3c1cbc3f546eda35168c3aa3cb91780fbe703f0996c6d123ea96dc85c70bbc0a", // 密钥,string 类型,必须是复杂密钥,长度大于16 + "ValidateIssuer": true, // 是否验证签发方,bool 类型,默认true + "ValidIssuer": "znyc", // 签发方,string 类型 + "ValidateAudience": true, // 是否验证签收方,bool 类型,默认true + "ValidAudience": "znyc", // 签收方,string 类型 + "ValidateLifetime": true, // 是否验证过期时间,bool 类型,默认true,建议true + "ExpiredTime": 864000, // 过期时间,long 类型,单位分钟,默认20分钟 + "ClockSkew": 5 // 过期时间容错值,long 类型,单位秒,默认5秒 + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Application/applicationconfig.Staging.json b/src/Znyc.Dispatching.Application/applicationconfig.Staging.json new file mode 100644 index 0000000..beacb07 --- /dev/null +++ b/src/Znyc.Dispatching.Application/applicationconfig.Staging.json @@ -0,0 +1,15 @@ +//v1.1.8业务要求设置为永不过期 +{ + "JWTSettings": { + "ValidateIssuerSigningKey": true, // 是否验证密钥,bool 类型,默认true + "IssuerSigningKey": + "3c1cbc3f546eda35168c3aa3cb91780fbe703f0996c6d123ea96dc85c70bbc0a", // 密钥,string 类型,必须是复杂密钥,长度大于16 + "ValidateIssuer": true, // 是否验证签发方,bool 类型,默认true + "ValidIssuer": "znyc", // 签发方,string 类型 + "ValidateAudience": true, // 是否验证签收方,bool 类型,默认true + "ValidAudience": "znyc", // 签收方,string 类型 + "ValidateLifetime": true, // 是否验证过期时间,bool 类型,默认true,建议true + "ExpiredTime": 864000, // 过期时间,long 类型,单位分钟 + "ClockSkew": 5 // 过期时间容错值,long 类型,单位秒,默认5秒 + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Common/Extensions/GenericTypeExtensions.cs b/src/Znyc.Dispatching.Common/Extensions/GenericTypeExtensions.cs new file mode 100644 index 0000000..49b736b --- /dev/null +++ b/src/Znyc.Dispatching.Common/Extensions/GenericTypeExtensions.cs @@ -0,0 +1,30 @@ +using System; +using System.Linq; + +namespace Znyc.Dispatching.Common.Extensions +{ + public static class GenericTypeExtensions + { + public static string GetGenericTypeName(this Type type) + { + string? typeName = string.Empty; + + if (type.IsGenericType) + { + string? genericTypes = string.Join(",", type.GetGenericArguments().Select(t => t.Name).ToArray()); + typeName = $"{type.Name.Remove(type.Name.IndexOf('`'))}<{genericTypes}>"; + } + else + { + typeName = type.Name; + } + + return typeName; + } + + public static string GetGenericTypeName(this object @object) + { + return @object.GetType().GetGenericTypeName(); + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Common/Extensions/LinqExtensions.cs b/src/Znyc.Dispatching.Common/Extensions/LinqExtensions.cs new file mode 100644 index 0000000..05767ce --- /dev/null +++ b/src/Znyc.Dispatching.Common/Extensions/LinqExtensions.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; + +namespace Znyc.Dispatching.Common.Extensions +{ + public static class LinqExtensions + { + public static IQueryable WhereIf(this IQueryable query, bool condition, + Expression> predicate) + { + return condition ? query.Where(predicate) : query; + } + + public static IQueryable WhereIf(this IQueryable query, bool condition, + Expression> predicate) + { + return condition ? query.Where(predicate) : query; + } + + public static IEnumerable WhereIf(this IEnumerable query, bool condition, Func predicate) + { + return condition ? query.Where(predicate) : query; + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Common/Znyc.Dispatching.Common.csproj b/src/Znyc.Dispatching.Common/Znyc.Dispatching.Common.csproj new file mode 100644 index 0000000..3d282fb --- /dev/null +++ b/src/Znyc.Dispatching.Common/Znyc.Dispatching.Common.csproj @@ -0,0 +1,17 @@ + + + + net6.0 + enable + + + + + + + + + + + + diff --git a/src/Znyc.Dispatching.Core/Cache/CacheOptions.cs b/src/Znyc.Dispatching.Core/Cache/CacheOptions.cs new file mode 100644 index 0000000..7da0dcc --- /dev/null +++ b/src/Znyc.Dispatching.Core/Cache/CacheOptions.cs @@ -0,0 +1,15 @@ +using Furion.ConfigurableOptions; + +namespace Znyc.Dispatching.Core.Cache +{ + /// + /// 缓存配置 + /// + public class CacheOptions : IConfigurableOptions + { + /// + /// Redis配置 + /// + public string RedisConnectionString { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Cache/IRedisCache.cs b/src/Znyc.Dispatching.Core/Cache/IRedisCache.cs new file mode 100644 index 0000000..00b44af --- /dev/null +++ b/src/Znyc.Dispatching.Core/Cache/IRedisCache.cs @@ -0,0 +1,154 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using static CSRedis.CSRedisClient; + +namespace Znyc.Dispatching.Core.Cache +{ + /// + /// 缓存接口 + /// + public interface IRedisCache + { + /// + /// 用于在 key 存在时删除 key + /// + /// 键 + long Del(params string[] key); + + /// + /// 用于在 key 存在时删除 key + /// + /// 键 + /// + Task DelAsync(params string[] key); + + /// + /// 用于在 key 模板存在时删除 + /// + /// key模板 + /// + Task DelByPatternAsync(string pattern); + + /// + /// 检查给定 key 是否存在 + /// + /// 键 + /// + bool Exists(string key); + + /// + /// 检查给定 key 是否存在 + /// + /// 键 + /// + Task ExistsAsync(string key); + + /// + /// 获取指定 key 的值 + /// + /// 键 + /// + string Get(string key); + + /// + /// 获取指定 key 的值 + /// + /// byte[] 或其他类型 + /// 键 + /// + T Get(string key); + + /// + /// 获取指定 key 的值 + /// + /// 键 + /// + Task GetAsync(string key); + + /// + /// 获取指定 key 的值 + /// + /// byte[] 或其他类型 + /// 键 + /// + Task GetAsync(string key); + + /// + /// 设置指定 key 的值,所有写入参数object都支持string | byte[] | 数值 | 对象 + /// + /// 键 + /// 值 + bool Set(string key, object value); + + /// + /// 设置指定 key 的值,所有写入参数object都支持string | byte[] | 数值 | 对象 + /// + /// 键 + /// 值 + /// 有效期 + bool Set(string key, object value, TimeSpan expire); + + /// + /// 设置指定 key 的值,所有写入参数object都支持string | byte[] | 数值 | 对象 + /// + /// 键 + /// 值 + /// + Task SetAsync(string key, object value); + + /// + /// 设置指定 key 的值,所有写入参数object都支持string | byte[] | 数值 | 对象 + /// + /// 键 + /// 值 + /// 有效期 + /// + Task SetAsync(string key, object value, TimeSpan expire); + + /// + /// 获取所有缓存 + /// + /// + List GetAllKeys(); + + + /// + /// 缓存壳 + /// + /// + /// + /// + /// + /// + T CacheShell(string key, int timeoutSeconds, Func getDataAsync); + + + /// + /// 缓存壳 + /// + /// + /// + /// + /// + /// + Task CacheShellAsync(string key, int timeoutSeconds, Func> getDataAsync); + + + /// + /// 发布 + /// + /// + /// + /// + Task PublishAsync(string channel, string message); + + + /// + /// 订阅 + /// + /// + /// + SubscribeObject Subscribe(params (string, Action)[] channels); + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Cache/RedisCache.cs b/src/Znyc.Dispatching.Core/Cache/RedisCache.cs new file mode 100644 index 0000000..f482836 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Cache/RedisCache.cs @@ -0,0 +1,167 @@ +using CSRedis; +using Furion.DependencyInjection; +using Microsoft.Extensions.Options; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using static CSRedis.CSRedisClient; + +namespace Znyc.Dispatching.Core.Cache +{ + /// + /// Redis缓存 + /// + public class RedisCache : IRedisCache, ISingleton + { + public RedisCache(IOptions cacheOptions) + { + CSRedisClient csredis = new CSRedisClient(cacheOptions.Value.RedisConnectionString); + RedisHelper.Initialization(csredis); + } + + public long Del(params string[] key) + { + return RedisHelper.Del(key); + } + + public Task DelAsync(params string[] key) + { + return RedisHelper.DelAsync(key); + } + + public async Task DelByPatternAsync(string pattern) + { + if (string.IsNullOrEmpty(pattern)) + { + return default; + } + + //pattern = Regex.Replace(pattern, @"\{.*\}", "*"); + string[] keys = await RedisHelper.KeysAsync(pattern); + if (keys != null && keys.Length > 0) + { + return await RedisHelper.DelAsync(keys); + } + + return default; + } + + public bool Exists(string key) + { + return RedisHelper.Exists(key); + } + + public Task ExistsAsync(string key) + { + return RedisHelper.ExistsAsync(key); + } + + public string Get(string key) + { + return RedisHelper.Get(key); + } + + public T Get(string key) + { + return RedisHelper.Get(key); + } + + public Task GetAsync(string key) + { + return RedisHelper.GetAsync(key); + } + + public Task GetAsync(string key) + { + return RedisHelper.GetAsync(key); + } + + public bool Set(string key, object value) + { + return RedisHelper.Set(key, value); + } + + public bool Set(string key, object value, TimeSpan expire) + { + return RedisHelper.Set(key, value, expire); + } + + public Task SetAsync(string key, object value) + { + return RedisHelper.SetAsync(key, value); + } + + public Task SetAsync(string key, object value, TimeSpan expire) + { + return RedisHelper.SetAsync(key, value, expire); + } + + public List GetAllKeys() + { + return RedisHelper.Keys("").ToList(); + } + + + /// + /// 缓存壳 + /// + /// + /// + /// + /// + /// + public T CacheShell(string key, int timeoutSeconds, Func getDataAsync) + { + return RedisHelper.CacheShell(key, timeoutSeconds, getDataAsync); + } + + /// + /// 缓存壳 + /// + /// + /// + /// + /// + /// + public Task CacheShellAsync(string key, int timeoutSeconds, Func> getDataAsync) + { + return RedisHelper.CacheShellAsync(key, timeoutSeconds, getDataAsync); + } + + + /// + /// 发布 + /// + /// + /// + /// + public async Task PublishAsync(string channel, string message) + { + return await RedisHelper.PublishAsync(channel, message); + } + + /// + /// 订阅 + /// + /// + /// + public SubscribeObject Subscribe(params (string, Action)[] channels) + { + return RedisHelper.Subscribe(channels); + } + + + /// + /// 发布 + /// + /// + /// + /// + public long Publish(string channel, string message) + { + return RedisHelper.Publish(channel, message); + } + + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Config/SmsProviderOptions.cs b/src/Znyc.Dispatching.Core/Config/SmsProviderOptions.cs new file mode 100644 index 0000000..4bb70da --- /dev/null +++ b/src/Znyc.Dispatching.Core/Config/SmsProviderOptions.cs @@ -0,0 +1,33 @@ +using Furion.ConfigurableOptions; +using System.Collections.Generic; + +namespace Znyc.Dispatching.Core +{ + /// + /// 发送短信配置 + /// + [OptionsSettings("SMSProvider")] + public class SmsProviderOptions : IConfigurableOptions + { + public string SecretId { get; set; } + + public string SecretKey { get; set; } + + public string SmsSdkAppid { get; set; } + + public string Sign { get; set; } + + public string Region { get; set; } + + public List TemplateList { get; set; } + } + + public class TemplateList + { + public string ChangePassWordTemplateId { get; set; } + + public string RegisterTemplateId { get; set; } + + public string AddEmployeeTemplateId { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Config/UploadInfoOptions.cs b/src/Znyc.Dispatching.Core/Config/UploadInfoOptions.cs new file mode 100644 index 0000000..dde6b90 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Config/UploadInfoOptions.cs @@ -0,0 +1,21 @@ +using Furion.ConfigurableOptions; + +namespace Znyc.Dispatching.Core +{ + public class UploadInfoOptions : IConfigurableOptions + { + public string secretId { get; set; } + + public string secretKey { get; set; } + + public string bucket { get; set; } + + public string region { get; set; } + + public string allowPrefix { get; set; } + + public int durationSeconds { get; set; } + + public string[] allowActions { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Config/UploadOptions.cs b/src/Znyc.Dispatching.Core/Config/UploadOptions.cs new file mode 100644 index 0000000..34779ef --- /dev/null +++ b/src/Znyc.Dispatching.Core/Config/UploadOptions.cs @@ -0,0 +1,94 @@ +using Furion.ConfigurableOptions; +using System; +using System.IO; +using Znyc.Dispatching.Core.Extension; + +namespace Znyc.Dispatching.Core +{ + /// + /// 上传配置 + /// + // [OptionsSettings("AppSettings:Upload")] + public class UploadOptions : IConfigurableOptions + { + public string SecretId { get; set; } + public string SecretKey { get; set; } + public string Bucket { get; set; } + public string Region { get; set; } + + /// + /// 头像上传配置 + /// + public FileUploadConfig Avatar { get; set; } + + /// + /// 文档图片上传配置 + /// + public FileUploadConfig Document { get; set; } + } + + /// + /// 文件上传配置 + /// + public class FileUploadConfig + { + private string _uploadPath; + + /// + /// 上传路径 + /// + public string UploadPath + { + get + { + if (_uploadPath.IsNull()) + { + _uploadPath = Path.Combine(AppContext.BaseDirectory, "upload").ToPath(); + } + + if (!Path.IsPathRooted(_uploadPath)) + { + _uploadPath = Path.Combine(AppContext.BaseDirectory, _uploadPath).ToPath(); + } + + return _uploadPath; + } + set => _uploadPath = value; + } + + /// + /// 请求路径 + /// + public string RequestPath { get; set; } + + /// + /// 读取路径 + /// + public string ReadPath { get; set; } + + /// + /// 路径格式 + /// + public string Format { get; set; } + + /// + /// 路径日期格式 + /// + public string DateTimeFormat { get; set; } + + /// + /// 文件大小 10M = 10 * 1024 * 1024 + /// + public int MaxSize { get; set; } + + /// + /// 最大允许上传个数 -1不限制 + /// + public int Limit { get; set; } = -1; + + /// + /// 文件格式 + /// + public string[] ContentType { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Config/WeixinSettingOptions.cs b/src/Znyc.Dispatching.Core/Config/WeixinSettingOptions.cs new file mode 100644 index 0000000..6faf6bf --- /dev/null +++ b/src/Znyc.Dispatching.Core/Config/WeixinSettingOptions.cs @@ -0,0 +1,51 @@ +using Furion.ConfigurableOptions; + +namespace Znyc.Dispatching.Core +{ + /// + /// 微信配置 + /// + [OptionsSettings("SenparcWeixinSetting")] + public class WeixinSettingOptions : IConfigurableOptions + { + /// + /// 是否调试模式 + /// + public bool IsDebug { get; set; } + + /// + /// 小程序 + /// + public string WxOpenAppId { get; set; } + + /// + /// + public string WxOpenAppSecret { get; set; } + + /// + /// + public string WxOpenToken { get; set; } + + /// + /// + public string WxOpenEncodingAESKey { get; set; } + + /// + /// + public string Token { get; set; } + + /// + /// + public string EncodingAESKey { get; set; } + + /// + /// 公众号 + /// + public string WeixinAppId { get; set; } + + /// + /// 公众号 + /// + public string WeixinAppSecret { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Const/ApiGroupConsts.cs b/src/Znyc.Dispatching.Core/Const/ApiGroupConsts.cs new file mode 100644 index 0000000..e8ab5df --- /dev/null +++ b/src/Znyc.Dispatching.Core/Const/ApiGroupConsts.cs @@ -0,0 +1,22 @@ +namespace Znyc.Dispatching.Core +{ + /// + /// + public class ApiGroupConsts + { + /// + /// 用户 + /// + public const string USER_CENTER = "UserCenter"; + + /// + /// 日志 + /// + public const string LOG_CENTER = "LogCenter"; + + /// + /// 系统中心 + /// + public const string SYSTEM_CENTER = "SystemCenter"; + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Const/ClaimConst.cs b/src/Znyc.Dispatching.Core/Const/ClaimConst.cs new file mode 100644 index 0000000..2e32b33 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Const/ClaimConst.cs @@ -0,0 +1,35 @@ +namespace Znyc.Dispatching.Core +{ + public class ClaimConst + { + /// + /// 用户Id + /// + public const string CLAINM_USERID = "UserId"; + + /// + /// 账号 + /// + public const string CLAINM_ACCOUNT = "Account"; + + /// + /// 名称 + /// + public const string CLAINM_USERNAME = "UserName"; + + /// + /// 是否超级管理 + /// + public const string CLAINM_SUPERADMIN = "SuperAdmin"; + + /// + /// 角色Id + /// + public const string CLAINM_ROLEID = "RoleId"; + + /// + /// 公司Id + /// + public const string CLAINM_COMPANYID = "CompanyId"; + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Const/CommonConst.cs b/src/Znyc.Dispatching.Core/Const/CommonConst.cs new file mode 100644 index 0000000..639a7ca --- /dev/null +++ b/src/Znyc.Dispatching.Core/Const/CommonConst.cs @@ -0,0 +1,233 @@ +namespace Znyc.Dispatching.Core +{ + public class CommonConst + { + /// + /// 默认密码 + /// + public const string DEFAULT_PASSWORD = "a123456"; + + /// + /// 默认超速速度 + /// + public const int DEFAULT_OVERSPEED = 80; + + /// + /// 默认停留标示Id + /// + public const long DEFAULT_STAPTAG = 1003; + + /// + /// 默认角色Id + /// + public const long DEFAULT_ROLEID_1001 = 1001; + + /// + /// 默认角色名称 + /// + public const string DEFAULT_ROLENAME_1001 = "车组人员"; + + /// + /// 车主人员默认首页路径 + /// + public const string DEFAULT_DRIVER_INDEX = "/pages/driverIndex/driverIndex"; + + + /// + /// 外租伙伴默认首页路径 + /// + public const string DEFAULT_OUTSIDE_INDEX = "/pages/follow_gzh/follow_gzh"; + + + /// + /// 管理员默认首页路径 + /// + public const string DEFAULT_SUPERADMIN_INDEX = "/map/pages/index/index"; + + /// + /// 默认加密盐 + /// + public const string DEFAULT_PASSWORD_SALT = "a4abd4acbdbdcc1c04b92735b6b22740"; + + /// + /// mongoDB超速报警表前缀 + /// + public const string DEFAULT_ALARMSTRAIGHT_PREFIX = "AlertStraightDtl_"; + + /// + /// 用户缓存 + /// + public const string CACHE_KEY_USER = "user_"; + + /// + /// 菜单缓存 + /// + public const string CACHE_KEY_MENU = "menu_"; + + /// + /// 权限缓存 + /// + public const string CACHE_KEY_PERMISSION = "permission_"; + + /// + /// 数据范围缓存 + /// + public const string CACHE_KEY_DATASCOPE = "datascope_"; + + /// + /// 验证码缓存 + /// + public const string CACHE_KEY_CODE = "vercode_"; + + /// + /// 角色缓存 + /// + public const string CACHE_KEY_ROLE = "rolelist"; + + /// + /// 角色菜单缓存 + /// + public const string CACHE_KEY_ROLEMENU = "rolemenulist_"; + + /// + /// 车辆类型缓存 + /// + public const string CACHE_KEY_VEHICLETYPE = "vehicletypelist"; + + /// + /// 车辆分组缓存 + /// + public const string CACHE_KEY_VEHICLEGROUP = "vehiclegroup_"; + + /// + /// 字典缓存 + /// + public const string CACHE_KEY_DICTIONARY = "dictionarylist"; + + /// + /// 默认logo地址 + /// + public const string DEFAULT_COMPANYLOG = "https://zhongnengyunche.com/dispatching/wx/upload/avatar/"; + + /// + /// 头像地址前缀 + /// + public const string Prefix_AvataUrl = "https://zhongnengyunche.com/dispatching/wx/upload/avatar/"; + + /// + /// 默认头像地址 + /// + public const string DEFAULT_AVATARURL = "Default_AvatarUrl.png"; + + /// + /// 默认头像全部地址 + /// + public const string DEFAULT_AVATARURL_ALL = "https://zhongnengyunche.com/dispatching/wx/upload/avatar/Default_AvatarUrl.png"; + + /// + /// 公司缓存 + /// + public const string CACHE_KEY_COMPANY = "company_"; + + /// + /// JWT + /// + public const string CACHE_KEY_JWT = "jwt_"; + + /// + /// token + /// + public const string CACHE_KEY_TOKEN = "token:{0}"; + + /// + /// 公司车辆缓存 + /// + public const string CACHE_KEY_VEHICLEBYCOMPANYID = "vehicles:company_"; + + /// + /// 车辆缓存 + /// + public const string CACHE_KEY_VEHICLE = "vehicle:_"; + + /// + /// 车辆GPS信息缓存 + /// + public const string CACHE_KEY_VEHICLEGPS = "vehiclegps_{0}:{1}"; + + /// + /// 超速记录缓存 + /// + public const string CACHE_KEY_VEHICLEALARM = "vehiclealarm_{0}:{1}:{2}"; + + /// + /// 员工缓存 + /// + public const string CACHE_KEY_EMPLOYEE = "employee:{0}"; + + /// + /// 员工列表缓存 + /// + public const string CACHE_KEY_EMPLOYEES = "employees:{0}"; + + /// + /// 未读报警消息 + /// + public const string CACHE_KEY_UNREADALARM = "unread:alarm:{0}"; + + /// + /// 轨迹回放 + /// + public const string CACHE_KEY_TRACKPLAYBACK = "vehicles:trackplayback_"; + + /// + /// 车场列表缓存 + /// + public const string CACHE_KEY_YARDS = "yards:{0}"; + + /// + /// 车场缓存 + /// + public const string CACHE_KEY_YARD = "yard:{0}"; + + #region 工程信息 + /// + /// 工程列表缓存 + /// + public const string CACHE_KEY_PROJECTS = "projects:{0}"; + + /// + /// 工程缓存 + /// + public const string CACHE_KEY_PROJECT = "project:{0}"; + #endregion + + /// + /// 车组人员缓存 + /// + public const string CACHE_KEY_VEHICLEPERSON = "vehicle:person:_"; + + #region 订单 + + /// + /// + /// + public const string CACHE_KEY_ORDERS = "orders:{0}"; + + /// + /// + /// + public const string CACHE_KEY_GPSREALTIME = "vehicle:gps_"; + + /// + /// 订单签证地址 + /// + public const string DEFAULT_ORDERVISA = "https://zhongnengyunche.com/dispatching/wx/upload/ordervisa/"; + + /// + /// + /// + public const string CACHE_KEY_ORDERREAD = "orderread_"; + #endregion + + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Entitys/Company.cs b/src/Znyc.Dispatching.Core/Entitys/Company.cs new file mode 100644 index 0000000..27adbaf --- /dev/null +++ b/src/Znyc.Dispatching.Core/Entitys/Company.cs @@ -0,0 +1,106 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Znyc.Dispatching.Core.Entitys +{ + /// + /// 公司表 + /// + [Table("dc_company")] + [Comment("公司表")] + public class Company : DEntityBase + { + /// + /// 公司名称 + /// + [Comment("公司名称")] + [Required] + [MaxLength(8)] + public string CompanyName { get; set; } + + /// + /// 公司Logo + /// + [Comment("公司Logo")] + public string CompanyLogo { get; set; } + + /// + /// 联系人 + /// + [Comment("联系人")] + [Required] + [MaxLength(30)] + public string ContactPerson { get; set; } + + /// + /// 联系人电话 + /// + [Comment("联系人电话")] + [Required] + [MaxLength(11)] + public string ContactPhone { get; set; } + + /// + /// 精度 + /// + [Comment("精度")] + public decimal Longitude { get; set; } + + /// + /// 纬度 + /// + [Comment("纬度")] + public decimal Latitude { get; set; } + + /// + /// 地址 + /// + [Comment("地址")] + [MaxLength(35)] + public string Address { get; set; } + + /// + /// 审核时间 + /// + [Comment("审核时间")] + public DateTime AuditTime { get; set; } + + /// + /// 状态(字典 0正常 1停用 2删除) + /// + [Comment("状态")] + public int Status { get; set; } + + /// + /// 停留标示Id + /// + [Comment("停留标示Id")] + public long StopTag { get; set; } + + + /// + /// 一键派工是否显示工程名称 + /// + [Comment("一键派工是否显示工程名称")] + + public bool IsOpenDispatch { get; set; } + + + /// + /// 调度角色是否有添加工程名称的权限 + /// + [Comment("调度角色是否有添加工程名称的权限")] + + public bool IsSchedulingAddProject { get; set; } + + + /// + /// 是否启用任务车型选项,默认为关 + /// + [Comment("是否启用任务车型选项")] + + public bool IsOpenVehicleType { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Entitys/Construction.cs b/src/Znyc.Dispatching.Core/Entitys/Construction.cs new file mode 100644 index 0000000..7e30f41 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Entitys/Construction.cs @@ -0,0 +1,39 @@ +using Microsoft.EntityFrameworkCore; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Znyc.Dispatching.Core.Entitys +{ + /// + /// 施工单位表 + /// + [Table("dc_construction")] + [Comment("施工单位表")] + public class Construction : DEntityBase + { + + /// + /// 所属公司 + /// + public long CompanyId { get; set; } + + /// + /// 施工单位 + /// + public string ConstructionName { get; set; } + + /// + /// 联系人 + /// + public string ContactName { get; set; } + + /// + /// 联系电话 + /// + public string ContactPhone { get; set; } + + /// + /// 状态 + /// + public bool Status { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Entitys/DEntityBase.cs b/src/Znyc.Dispatching.Core/Entitys/DEntityBase.cs new file mode 100644 index 0000000..92b895e --- /dev/null +++ b/src/Znyc.Dispatching.Core/Entitys/DEntityBase.cs @@ -0,0 +1,76 @@ +using Furion.DatabaseAccessor; +using Microsoft.EntityFrameworkCore; +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Text.Json.Serialization; + +namespace Znyc.Dispatching.Core.Entitys +{ + /// + /// 自定义实体基类 + /// + public abstract class DEntityBase : DEntityBase + { + } + + public abstract class DEntityBase : DEntityBase + { + } + + public abstract class DEntityBase : PrivateDEntityBase + where TDbContextLocator1 : class, IDbContextLocator + { + } + + public abstract class DEntityBase : PrivateDEntityBase + where TDbContextLocator1 : class, IDbContextLocator + where TDbContextLocator2 : class, IDbContextLocator + { + } + + /// + /// 自定义实体基类 + /// + public abstract class PrivateDEntityBase : IPrivateEntity + { + /// + /// 主键Id + /// + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.None)] + [Comment("Id主键")] + public virtual long Id { get; set; } + + /// + /// 创建时间 + /// + [Comment("创建时间")] + public virtual DateTime CreatedTime { get; set; } + + /// + /// 更新时间 + /// + [Comment("更新时间")] + public virtual DateTime ModifiedTime { get; set; } + + /// + /// 创建者Id + /// + [Comment("创建者Id")] + public virtual long CreatedUserId { get; set; } + + /// + /// 修改者Id + /// + [Comment("修改者Id")] + public virtual long ModifiedUserId { get; set; } + + /// + /// 软删除 + /// + [JsonIgnore] + [Comment("软删除标记")] + public virtual bool IsDeleted { get; set; } = false; + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Entitys/Dictionary.cs b/src/Znyc.Dispatching.Core/Entitys/Dictionary.cs new file mode 100644 index 0000000..cf057e7 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Entitys/Dictionary.cs @@ -0,0 +1,62 @@ +using Furion.DatabaseAccessor; +using Microsoft.EntityFrameworkCore; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Znyc.Dispatching.Core.Entitys +{ + /// + /// 数据字典表 + /// + [SuppressChangedListener] + [Table("dc_dictionary")] + [Comment("数据字典表")] + public class Dictionary : DEntityBase + { + /// + /// 字典父级 + /// + [Comment("字典父级")] + public long ParentId { get; set; } + + /// + /// 字典编码 + /// + [Comment("字典编码")] + [MaxLength(25)] + public string Code { get; set; } + + /// + /// 字典值 + /// + [Comment("字典值")] + [MaxLength(25)] + public string Value { get; set; } + + /// + /// 字典描述 + /// + [Comment("字典描述")] + [MaxLength(55)] + public string Description { get; set; } + + /// + /// 字典名称 + /// + [Comment("字典名称")] + [MaxLength(25)] + public string Name { get; set; } + + /// + /// 是否启用 + /// + [Comment("是否启用")] + public int IsEnabled { get; set; } + + /// + /// 字典排序 + /// + [Comment("字典排序")] + public int Sort { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Entitys/Employee.cs b/src/Znyc.Dispatching.Core/Entitys/Employee.cs new file mode 100644 index 0000000..8b8864b --- /dev/null +++ b/src/Znyc.Dispatching.Core/Entitys/Employee.cs @@ -0,0 +1,78 @@ +using Furion.DatabaseAccessor; +using Microsoft.EntityFrameworkCore; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Znyc.Dispatching.Core.Entitys +{ + /// + /// 员工表 + /// + [SuppressChangedListener] + [Table("dc_employee")] + [Comment("员工表")] + public class Employee : DEntityBase + { + /// + /// 用户Id + /// + public long UserId { get; set; } + + /// + /// 员工头像 + /// + [Comment("员工头像")] + [MaxLength(125)] + public string AvatarUrl { get; set; } + + /// + /// 员工名 + /// + [Comment("员工名")] + public string EmployeeName { get; set; } + + /// + /// 手机号 + /// + [Comment("手机号")] + [MaxLength(11)] + public string EmployeePhone { get; set; } + + /// + /// 机构Id + /// + [Comment("所属公司")] + public long CompanyId { get; set; } + + ///// + ///// 机构名称 + ///// + ////[Comment("所属公司名称")] + ////[MaxLength(50)] + //public string CompanyName { get; set; } + + /// + /// CommonStatus + /// + [Comment("状态")] + public int Status { get; set; } + + /// + /// RoleStatus + /// + [Comment("角色")] + public long RoleId { get; set; } + + /// + /// RoleStatus + /// + [Comment("角色名称")] + public string RoleName { get; set; } + + /// + /// 是否默认登录 + /// + [Comment("是否默认登录")] + public bool IsDefault { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Entitys/Job.cs b/src/Znyc.Dispatching.Core/Entitys/Job.cs new file mode 100644 index 0000000..7dd9bf9 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Entitys/Job.cs @@ -0,0 +1,81 @@ +using Furion.TaskScheduler; +using Microsoft.EntityFrameworkCore; +using System.ComponentModel.DataAnnotations.Schema; +using Znyc.Dispatching.Core; +using Znyc.Dispatching.Core.Entitys; + +namespace Dilon.Core +{ + /// + /// 定时任务 + /// + [Table("dc_job")] + [Comment("定时任务表")] + public class Job : DEntityBase + { + /// + /// 任务名称 + /// + /// dilon + [Comment("任务名称")] + public string JobName { get; set; } + + /// + /// 执行间隔时间(单位秒) + /// + /// 5 + [Comment("间隔时间")] + public int? Interval { get; set; } = 5; + + /// + /// Cron表达式 + /// + /// + [Comment("Cron表达式")] + public string Cron { get; set; } + + /// + /// 定时器类型 + /// + [Comment("定时器类型")] + public SpareTimeTypes TimerType { get; set; } = SpareTimeTypes.Interval; + + /// + /// 执行次数 + /// + [Comment("执行次数")] + public int? RunNumber { get; set; } + + /// + /// 请求url + /// + [Comment("请求url")] + public string RequestUrl { get; set; } + + /// + /// 请求参数(Post,Put请求用) + /// + [Comment("请求参数")] + public string RequestParameters { get; set; } + + /// + /// Headers(可以包含如:Authorization授权认证) + /// 格式:{"Authorization":"userpassword.."} + /// + [Comment("Headers")] + public string Headers { get; set; } + + /// + /// 请求类型 + /// + /// 2 + [Comment("请求类型")] + public RequestTypeEnum RequestType { get; set; } = RequestTypeEnum.Post; + + /// + /// 备注 + /// + [Comment("备注")] + public string Remark { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Entitys/LogAudit.cs b/src/Znyc.Dispatching.Core/Entitys/LogAudit.cs new file mode 100644 index 0000000..102d7a3 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Entitys/LogAudit.cs @@ -0,0 +1,61 @@ +using Furion.DatabaseAccessor; +using Microsoft.EntityFrameworkCore; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Znyc.Dispatching.Core.Entitys +{ + /// + /// 系统操作/审计日志表 + /// + [SuppressChangedListener] + [Table("dc_log_audit")] + [Comment("审计日志表")] + public class LogAudit : DEntityBase + { + /// + /// 表名 + /// + [Comment("表名")] + [MaxLength(50)] + public string TableName { get; set; } + + /// + /// 列名 + /// + [Comment("列名")] + [MaxLength(50)] + public string ColumnName { get; set; } + + /// + /// 新值 + /// + [Comment("新值")] + public string NewValue { get; set; } + + /// + /// 旧值 + /// + [Comment("旧值")] + public string OldValue { get; set; } + + /// + /// 操作人Id + /// + [Comment("操作人Id")] + public long UserId { get; set; } + + /// + /// 操作人名称 + /// + [Comment("操作人名称")] + [MaxLength(20)] + public string UserName { get; set; } + + /// + /// 操作方式:新增、更新、删除 + /// + [Comment("操作方式")] + public string Operate { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Entitys/LogEx.cs b/src/Znyc.Dispatching.Core/Entitys/LogEx.cs new file mode 100644 index 0000000..7d0f5c9 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Entitys/LogEx.cs @@ -0,0 +1,81 @@ +using Furion.DatabaseAccessor; +using Microsoft.EntityFrameworkCore; +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Znyc.Dispatching.Core.Entitys +{ + /// + /// 异常日志 + /// + [SuppressChangedListener] + [Table("dc_log_ex")] + [Comment("异常日志表")] + public class LogEx : DEntityBase + { + /// + /// 操作人 + /// + [Comment("操作人")] + [MaxLength(20)] + public string Account { get; set; } + + /// + /// 名称 + /// + [Comment("名称")] + [MaxLength(100)] + public string Name { get; set; } + + /// + /// 类名 + /// + [Comment("类名")] + [MaxLength(100)] + public string ClassName { get; set; } + + /// + /// 方法名 + /// + [Comment("方法名")] + [MaxLength(100)] + public string MethodName { get; set; } + + /// + /// 异常名称 + /// + [Comment("异常名称")] + public string ExceptionName { get; set; } + + /// + /// 异常信息 + /// + [Comment("异常信息")] + public string ExceptionMsg { get; set; } + + /// + /// 异常源 + /// + [Comment("异常源")] + public string ExceptionSource { get; set; } + + /// + /// 堆栈信息 + /// + [Comment("堆栈信息")] + public string StackTrace { get; set; } + + /// + /// 参数对象 + /// + [Comment("参数对象")] + public string ParamsObj { get; set; } + + /// + /// 异常时间 + /// + [Comment("异常时间")] + public DateTime ExceptionTime { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Entitys/LogOp.cs b/src/Znyc.Dispatching.Core/Entitys/LogOp.cs new file mode 100644 index 0000000..e06456b --- /dev/null +++ b/src/Znyc.Dispatching.Core/Entitys/LogOp.cs @@ -0,0 +1,123 @@ +using Furion.DatabaseAccessor; +using Microsoft.EntityFrameworkCore; +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Znyc.Dispatching.Core.Entitys +{ + /// + /// 操作日志表 + /// + [SuppressChangedListener] + [Table("dc_log_op")] + [Comment("操作日志表")] + public class LogOp : DEntityBase + { + /// + /// 名称 + /// + [Comment("名称")] + [MaxLength(100)] + public string Name { get; set; } + + /// + /// 是否执行成功(Y-是,N-否) + /// + [Comment("是否执行成功")] + public YesOrNotEnum Success { get; set; } + + /// + /// 具体消息 + /// + [Comment("具体消息")] + public string Message { get; set; } + + /// + /// IP + /// + [Comment("IP")] + [MaxLength(20)] + public string Ip { get; set; } + + /// + /// 地址 + /// + [Comment("地址")] + [MaxLength(500)] + public string Location { get; set; } + + /// + /// 浏览器 + /// + [Comment("浏览器")] + [MaxLength(100)] + public string Browser { get; set; } + + /// + /// 操作系统 + /// + [Comment("操作系统")] + [MaxLength(100)] + public string Os { get; set; } + + /// + /// 请求地址 + /// + [Comment("请求地址")] + [MaxLength(100)] + public string Url { get; set; } + + /// + /// 类名称 + /// + [Comment("类名称")] + [MaxLength(100)] + public string ClassName { get; set; } + + /// + /// 方法名称 + /// + [Comment("方法名称")] + [MaxLength(100)] + public string MethodName { get; set; } + + /// + /// 请求方式(GET POST PUT DELETE) + /// + [Comment("请求方式")] + [MaxLength(10)] + public string ReqMethod { get; set; } + + /// + /// 请求参数 + /// + [Comment("请求参数")] + public string Param { get; set; } + + /// + /// 返回结果 + /// + [Comment("返回结果")] + public string Result { get; set; } + + /// + /// 耗时(毫秒) + /// + [Comment("耗时")] + public long ElapsedTime { get; set; } + + /// + /// 操作时间 + /// + [Comment("操作时间")] + public DateTime OpTime { get; set; } + + /// + /// 操作人 + /// + [Comment("操作人")] + [MaxLength(20)] + public string Account { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Entitys/LogVis.cs b/src/Znyc.Dispatching.Core/Entitys/LogVis.cs new file mode 100644 index 0000000..0662e65 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Entitys/LogVis.cs @@ -0,0 +1,83 @@ +using Furion.DatabaseAccessor; +using Microsoft.EntityFrameworkCore; +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Znyc.Dispatching.Core.Entitys +{ + /// + /// 访问日志表 + /// + [SuppressChangedListener] + [Table("dc_log_vis")] + [Comment("访问日志表")] + public class LogVis : DEntityBase + { + /// + /// 名称 + /// + [Comment("名称")] + [MaxLength(100)] + public string Name { get; set; } + + /// + /// 是否执行成功(Y-是,N-否) + /// + [Comment("是否执行成功")] + public YesOrNotEnum Success { get; set; } + + /// + /// 具体消息 + /// + [Comment("具体消息")] + public string Message { get; set; } + + /// + /// IP + /// + [Comment("IP")] + [MaxLength(20)] + public string Ip { get; set; } + + /// + /// 地址 + /// + [Comment("地址")] + [MaxLength(100)] + public string Location { get; set; } + + /// + /// 浏览器 + /// + [Comment("浏览器")] + [MaxLength(100)] + public string Browser { get; set; } + + /// + /// 操作系统 + /// + [Comment("操作系统")] + [MaxLength(100)] + public string Os { get; set; } + + /// + /// 访问类型 + /// + [Comment("访问类型")] + public LoginTypeEnum VisType { get; set; } + + /// + /// 访问时间 + /// + [Comment("访问时间")] + public DateTime VisTime { get; set; } + + /// + /// 访问人 + /// + [Comment("访问人")] + [MaxLength(20)] + public string Account { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Entitys/Menu.cs b/src/Znyc.Dispatching.Core/Entitys/Menu.cs new file mode 100644 index 0000000..ffd3338 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Entitys/Menu.cs @@ -0,0 +1,145 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Znyc.Dispatching.Core.Entitys +{ + /// + /// 菜单表 + /// + [Table("dc_menu")] + [Comment("菜单表")] + public class Menu : DEntityBase + { + /// + /// 父Id + /// + [Comment("父Id")] + public long ParentId { get; set; } + + /// + /// 所属层级 + /// + [Comment("所属层级")] + public int Layers { get; set; } + + /// + /// 名称 + /// + [Comment("名称")] + [Required] + [MaxLength(20)] + public string Name { get; set; } + + /// + /// 编码 + /// + [Comment("编码")] + [Required] + [MaxLength(50)] + public string Code { get; set; } + + /// + /// 菜单类型(字典 0目录 1菜单 2按钮) + /// + [Comment("菜单类型")] + public MenuTypeEnum Type { get; set; } + + /// + /// 图标 + /// + [Comment("图标")] + [MaxLength(20)] + public string Icon { get; set; } + + /// + /// 路由地址 + /// + [Comment("路由地址")] + [MaxLength(100)] + public string Router { get; set; } + + /// + /// 权限标识 + /// + [Comment("权限标识")] + public string Permission { get; set; } + + /// + /// 是否外链 + /// + [Comment("是否外链")] + [MaxLength(5)] + public bool IsFrame { get; set; } + + /// + /// 是否展开 + /// + [Comment("是否展开")] + [MaxLength(5)] + public bool IsExpand { get; set; } + + /// + /// 是否显示 + /// + [Comment("是否显示")] + [MaxLength(5)] + public bool IsShow { get; set; } + + /// + /// 排序 + /// + [Comment("排序")] + public int Sort { get; set; } + + /// + /// 详细描述 + /// + [Comment("详细描述")] + [MaxLength(100)] + public string Description { get; set; } + + /// + /// CommonStatus + /// + public CommonStatusEnum Status { get; set; } = CommonStatusEnum.ENABLE; + + /// + /// 多对多(角色) + /// + public ICollection Roles { get; set; } + + /// + /// 多对多中间表(用户角色) + /// + public List RoleMenus { get; set; } + + /// + /// 上级 + /// + public virtual Menu Parent { get; set; } + + /// + /// 子集 + /// + public virtual ICollection Childrens { get; set; } + + /// + /// 配置实体关系 + /// + /// + /// + /// + public void Configure(EntityTypeBuilder entityBuilder, DbContext dbContext, Type dbContextLocator) + { + entityBuilder + .HasMany(x => x.Childrens) + .WithOne(x => x.Parent) + .HasForeignKey(x => x.ParentId) + .OnDelete(DeleteBehavior.ClientSetNull); // 必须设置这一行 + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Entitys/Message.cs b/src/Znyc.Dispatching.Core/Entitys/Message.cs new file mode 100644 index 0000000..f7f01aa --- /dev/null +++ b/src/Znyc.Dispatching.Core/Entitys/Message.cs @@ -0,0 +1,58 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Znyc.Dispatching.Core.Entitys +{ + /// + /// 站内消息表 + /// + [Table("dc_message")] + [Comment("站内消息表")] + public class Message : DEntityBase + { + /// + /// 发送者Id + /// + [Comment("发送者Id")] + public long SendId { get; set; } + + /// + /// 消息标题 + /// + [Comment("消息标题")] + [Required] + [MaxLength(12)] + public string MessageTitle { get; set; } + + /// + /// 消息类型 + /// + [Comment("产品类型")] + public int MessageType { get; set; } + + /// + /// 发送类型 + /// + [Comment("发送类型")] + public int Type { get; set; } + + /// + /// 组Id + /// + public int GroupId { get; set; } + + /// + /// 消息内容 + /// + [Comment("消息内容")] + public string Content { get; set; } + + /// + /// 发送时间 + /// + [Comment("发送时间")] + public DateTime SendTime { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Entitys/MessageRecord.cs b/src/Znyc.Dispatching.Core/Entitys/MessageRecord.cs new file mode 100644 index 0000000..9317de1 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Entitys/MessageRecord.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Znyc.Dispatching.Core.Entitys +{ + /// + /// 消息记录表 + /// + [Table("dc_message_record")] + [Comment("消息记录表")] + public class MessageRecord : DEntityBase + { + /// + /// 接收者Id + /// + [Comment("接收者Id")] + public long ReceiverId { get; set; } + + /// + /// 消息Id + /// + [Comment("消息Id")] + public long MessageId { get; set; } + + /// + /// 状态 + /// + [Comment("状态")] + public int Status { get; set; } + + /// + /// 站内消息 + /// + [Comment("站内消息")] + public Message Message { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Entitys/Oil.cs b/src/Znyc.Dispatching.Core/Entitys/Oil.cs new file mode 100644 index 0000000..71242f1 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Entitys/Oil.cs @@ -0,0 +1,55 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Znyc.Dispatching.Core.Entitys +{ + /// + /// 油耗表 + /// + [Table("dc_oil")] + [Comment("油耗表")] + public class Oil : DEntityBase + { + /// + /// 公司Id + /// + public long CompanyId { get; set; } + + /// + /// 加油日期 + /// + public DateTime PlusOilDate { get; set; } + + /// + /// 车辆Id + /// + public long VehicleId { get; set; } + + /// + /// 车俩编号 + /// + public string VehicleCode { get; set; } + + /// + /// 加油单号 + /// + public string PlusOilOrder { get; set; } + + /// + /// 加油量 + /// + public string PlusOilAmount { get; set; } + + /// + /// 油单价 + /// + public decimal OilPrice { get; set; } + + /// + /// 金额 + /// + public decimal AmountMoney { get; set; } + + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Entitys/Order.cs b/src/Znyc.Dispatching.Core/Entitys/Order.cs new file mode 100644 index 0000000..7edf4bd --- /dev/null +++ b/src/Znyc.Dispatching.Core/Entitys/Order.cs @@ -0,0 +1,164 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Znyc.Dispatching.Core.Entitys +{ + /// + /// 派工订单表 + /// + [Table("dc_order")] + [Comment("派工订单表")] + public class Order : DEntityBase + { + + /// + /// 公司Id + /// + public long CompanyId { get; set; } + + /// + /// 车辆Id,,冗余查询车辆列表状态 + /// + public long VehicleId { get; set; } + + /// + /// 车辆编号 + /// + public string VehicleCode { get; set; } + + /// + /// 到场时间 + /// + public DateTime ArriveDate { get; set; } + + /// + /// 经度 + /// + public decimal Longitude { get; set; } + + /// + /// 纬度 + /// + public decimal Latitude { get; set; } + + /// + /// 地址 + /// + public string Address { get; set; } + + /// + /// 状态,10=待指派,20=已指派,未接单,30=已接单,40=已出发,50=已完成,60=已签单,70=已离开,80=已评价,101=撤销 + /// + public int Status { get; set; } + + /// + /// 工程Id + /// + public long ProjectId { get; set; } + + /// + /// 工程名称 + /// + public string ProjectName { get; set; } + + /// + /// 业务员Id + /// + public long SalesmanId { get; set; } + + /// + /// 订单来源,10=录入,20=报单 + /// + public int OrderSource { get; set; } + + /// + /// 订单内容 + /// + public string OrderContent { get; set; } + + /// + /// 是否外请车 + /// + public bool IsOutside { get; set; } + + /// + /// 指派时间 + /// + public DateTime? AssignDate { get; set; } + + + /// + /// 指派人 + /// + public long AssignUserId { get; set; } + + /// + /// 接单时间 + /// + public DateTime? ReceiveDate { get; set; } + + /// + /// 出发时间 + /// + public DateTime? TravelDate { get; set; } + + /// + /// 到达时间 + /// + public DateTime? AppearDate { get; set; } + + /// + /// 签单时间 + /// + public DateTime? SignDate { get; set; } + /// + /// 完成时间 + /// + public DateTime? CompleteDate { get; set; } + + /// + /// 评价时间 + /// + public DateTime? EvaluateDate { get; set; } + + /// + /// 离开时间 + /// + public DateTime? LeaveDate { get; set; } + + + /// + /// 到家时间 + /// + public DateTime? ArriveHomeDate { get; set; } + + + /// + /// 车组人员 + /// + public string OrderVehiclePerson { get; set; } + + + + /// + /// 任务车型 + /// + public long VehicleType { get; set; } + + /// + /// 施工单位 + /// + public long ConstructionId { get; set; } + + + + + /// + /// 承租公司 + /// + //public long TenantCompanyId { get; set; } + + + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Entitys/OrderVehicle.cs b/src/Znyc.Dispatching.Core/Entitys/OrderVehicle.cs new file mode 100644 index 0000000..692d5d8 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Entitys/OrderVehicle.cs @@ -0,0 +1,43 @@ +using Microsoft.EntityFrameworkCore; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Znyc.Dispatching.Core.Entitys +{ + /// + /// 派工订单车组人员表 + /// + [Table("dc_order_vehicle")] + [Comment("派工订单车组人员表")] + public class OrderVehicle : DEntityBase + { + + /// + /// 订单Id + /// + public long OrderId { get; set; } + + + /// + /// 指派任务人员Id + /// + public long UserId { get; set; } + + /// + /// 姓名 + /// + public string UserName { get; set; } + + + /// + /// 电话 + /// + public string UserPhone { get; set; } + + /// + /// 是否司机 + /// + public bool IsDriver { get; set; } + + + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Entitys/OrderVisa.cs b/src/Znyc.Dispatching.Core/Entitys/OrderVisa.cs new file mode 100644 index 0000000..7ecab98 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Entitys/OrderVisa.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Znyc.Dispatching.Core.Entitys +{ + /// + /// 订单签证表 + /// + [Table("dc_order_visa")] + [Comment("订单签证表")] + public class OrderVisa : DEntityBase + { + + /// + /// 订单Id + /// + public long OrderId { get; set; } + + + + /// + /// 图片路径 + /// + public string Picture { get; set; } + + + + + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Entitys/Project.cs b/src/Znyc.Dispatching.Core/Entitys/Project.cs new file mode 100644 index 0000000..a10c76b --- /dev/null +++ b/src/Znyc.Dispatching.Core/Entitys/Project.cs @@ -0,0 +1,64 @@ +using Microsoft.EntityFrameworkCore; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Znyc.Dispatching.Core.Entitys +{ + /// + /// 工程信息表 + /// + [Table("dc_project")] + [Comment("工程信息表")] + public class Project : DEntityBase + { + + /// + /// 所属公司 + /// + public long CompanyId { get; set; } + + /// + /// 工程名称 + /// + public string ProjectName { get; set; } + + /// + /// 业务员Id + /// + public long SalesmanId { get; set; } + + /// + /// 经度 + /// + public decimal Longitude { get; set; } + + /// + /// 纬度 + /// + public decimal Latitude { get; set; } + + /// + /// 地址 + /// + public string Address { get; set; } + + + /// + /// 是否启用 + /// + public bool IsEnabled { get; set; } + + + #region v1.2.7 + /// + /// 施工单位 + /// + public long ConstructionId { get; set; } + + + /// + /// 施工单位名称 + /// + public string ConstructionName { get; set; } + #endregion + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Entitys/ProjectPerson.cs b/src/Znyc.Dispatching.Core/Entitys/ProjectPerson.cs new file mode 100644 index 0000000..d7e4bf9 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Entitys/ProjectPerson.cs @@ -0,0 +1,35 @@ +using Microsoft.EntityFrameworkCore; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Znyc.Dispatching.Core.Entitys +{ + /// + /// 工程信息联系人表 + /// + [Table("dc_project_person")] + [Comment("工程信息联系人表")] + public class ProjectPerson : DEntityBase + { + /// + /// 工程Id + /// + public long ProjectId { get; set; } + + /// + /// 联系人Id,链接员工表Id + /// + public long ProjectPersonId { get; set; } + + /// + /// 联系人姓名 + /// + public string ProjectPersonName { get; set; } + + /// + /// 联系人电话 + /// + public string ProjectPersonPhone { get; set; } + + + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Entitys/Report.cs b/src/Znyc.Dispatching.Core/Entitys/Report.cs new file mode 100644 index 0000000..099fd21 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Entitys/Report.cs @@ -0,0 +1,50 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Znyc.Dispatching.Core.Entitys +{ + /// + /// 意见反馈表 + /// + [Table("dc_report")] + [Comment("意见反馈表")] + public class Report : DEntityBase + { + /// + /// 投诉人Id + /// + [Comment("投诉人Id")] + public long ReportUserId { get; set; } + + /// + /// 投诉内容 + /// + [Comment("投诉内容")] + public string ReportContent { get; set; } + + /// + /// 投诉时间 + /// + [Comment("投诉时间")] + public DateTime ReportTime { get; set; } + + /// + /// 举报状态 + /// + [Comment("举报状态")] + public int ReportStatus { get; set; } + + /// + /// 处理状态 + /// + [Comment("处理状态")] + public int HandleStatus { get; set; } + + /// + /// 处理结果 + /// + [Comment("处理结果")] + public string Note { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Entitys/Role.cs b/src/Znyc.Dispatching.Core/Entitys/Role.cs new file mode 100644 index 0000000..83ddbcb --- /dev/null +++ b/src/Znyc.Dispatching.Core/Entitys/Role.cs @@ -0,0 +1,43 @@ +using Microsoft.EntityFrameworkCore; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Znyc.Dispatching.Core.Entitys +{ + /// + /// 角色表 + /// + [Table("dc_role")] + [Comment("角色表")] + public class Role : DEntityBase + { + /// + /// 名称 + /// + [Comment("名称")] + [Required] + [MaxLength(10)] + public string Name { get; set; } + + /// + /// 编码 + /// + [Comment("父级权限Id")] + [Required] + public long ParentId { get; set; } + + /// + /// 角色说明 + /// + [Comment("角色说明")] + public string Intro { get; set; } + + /// + /// 状态(字典 0正常 1停用 2删除) + /// + [Comment("状态")] + public CommonStatusEnum Status { get; set; } = CommonStatusEnum.ENABLE; + + + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Entitys/RoleMenu.cs b/src/Znyc.Dispatching.Core/Entitys/RoleMenu.cs new file mode 100644 index 0000000..9760578 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Entitys/RoleMenu.cs @@ -0,0 +1,26 @@ +using Microsoft.EntityFrameworkCore; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Znyc.Dispatching.Core.Entitys +{ + /// + /// 角色菜单表 + /// + [Table("dc_role_menu")] + [Comment("角色菜单表")] + public class RoleMenu : DEntityBase + { + /// + /// 角色Id + /// + [Comment("角色Id")] + public long RoleId { get; set; } + + /// + /// 菜单Id + /// + [Comment("菜单Id")] + public long MenuId { get; set; } + + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Entitys/Salesman.cs b/src/Znyc.Dispatching.Core/Entitys/Salesman.cs new file mode 100644 index 0000000..fc6eaf2 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Entitys/Salesman.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Znyc.Dispatching.Core.Entitys +{ + /// + /// 业务员 + /// + [Table("dc_salesman")] + [Comment("业务员息表")] + public class Salesman : DEntityBase + { + /// + /// 业务员姓名 + /// + public string SalesmanName { get; set; } + + /// + /// 业务员电话 + /// + public long SalesmanPhone { get; set; } + + /// + /// 是否启用 + /// + public bool IsEnabled { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Entitys/User.cs b/src/Znyc.Dispatching.Core/Entitys/User.cs new file mode 100644 index 0000000..e45d263 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Entitys/User.cs @@ -0,0 +1,49 @@ +using Microsoft.EntityFrameworkCore; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Znyc.Dispatching.Core.Entitys +{ + /// + /// 用户表 + /// + [Table("dc_user")] + [Comment("用户表")] + public class User : DEntityBase + { + /// + /// OpenId + /// + [Comment("OpenId")] + [Required] + [MaxLength(32)] + public string OpenId { get; set; } + + /// + /// 头像 + /// + [Comment("头像")] + public string AvatarUrl { get; set; } + + /// + /// 用户名 + /// + [Comment("用户名")] + [MaxLength(38)] + public string UserName { get; set; } + + /// + /// 电话 + /// + [Comment("电话")] + [MaxLength(11)] + public string Phone { get; set; } + + /// + /// 状态 + /// + [Comment("状态")] + public CommonStatusEnum Status { get; set; } = CommonStatusEnum.ENABLE; + + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Entitys/UserRole.cs b/src/Znyc.Dispatching.Core/Entitys/UserRole.cs new file mode 100644 index 0000000..a85db0b --- /dev/null +++ b/src/Znyc.Dispatching.Core/Entitys/UserRole.cs @@ -0,0 +1,26 @@ +using Microsoft.EntityFrameworkCore; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Znyc.Dispatching.Core.Entitys +{ + /// + /// 用户角色表 + /// + [Table("dc_user_role")] + [Comment("用户角色表")] + public class UserRole : DEntityBase + { + /// + /// 用户Id + /// + [Comment("用户Id")] + public long UserId { get; set; } + + /// + /// 系统角色Id + /// + [Comment("角色Id")] + public long RoleId { get; set; } + + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Entitys/UserYard.cs b/src/Znyc.Dispatching.Core/Entitys/UserYard.cs new file mode 100644 index 0000000..8b9b561 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Entitys/UserYard.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Znyc.Dispatching.Core.Entitys +{ + /// + /// 车场表 + /// + [Table("dc_user_yard")] + [Comment("用户车场映射表")] + public class UserYard : DEntityBase + { + /// + /// 用户Id + /// + [Comment("用户Id")] + public long UserId { get; set; } + + /// + /// 车场Id + /// + [Comment("车场Id")] + public long YardId { get; set; } + + /// + /// 公司Id + /// + [Comment("公司Id")] + public long CompanyId { get; set; } + + /// + /// 是否默认 + /// + [Comment("是否默认")] + public bool IsDefault { get; set; } + } +} diff --git a/src/Znyc.Dispatching.Core/Entitys/Vehicle.cs b/src/Znyc.Dispatching.Core/Entitys/Vehicle.cs new file mode 100644 index 0000000..20f1b3c --- /dev/null +++ b/src/Znyc.Dispatching.Core/Entitys/Vehicle.cs @@ -0,0 +1,180 @@ +using Furion.DataValidation; +using Microsoft.EntityFrameworkCore; +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Znyc.Dispatching.Core.Entitys +{ + /// + /// 车辆表 + /// + [Table("dc_vehicle")] + [Comment("车辆表")] + public class Vehicle : DEntityBase + { + /// + /// 所属公司 + /// + [Comment("所属公司")] + public long CompanyId { get; set; } + + /// + /// 车辆编号 + /// + [Comment("车辆编号")] + public string VehicleCode { get; set; } + + /// + /// 车牌号 + /// + [Comment("车牌号")] + public string VehiclePlate { get; set; } + + /// + /// 车辆类型 + /// + [Comment("车辆类型")] + public long VehicleType { get; set; } + + /// + /// 所属车组 + /// + [Comment("所属车组")] + public long VehicleGroup { get; set; } + + /// + /// SIM卡号 + /// + [Comment("SIM卡号")] + public string SimNo { get; set; } + + /// + /// 设备类型 + /// + [Comment("设备类型")] + public TerminalTypeEnum TerminalType { get; set; } = TerminalTypeEnum.BSJKGM_08; + + /// + /// 设备号 + /// + [Comment("设备号")] + public string TerminalNo { get; set; } + + /// + /// GPS协议号 + /// + [Comment("GPS协议号")] + public string AgreementNo { get; set; } + + /// + /// 是否激活 + /// + [Comment("是否激活")] + public bool IsActivate { get; set; } + + /// + /// 是否开启GPS + /// + [Comment("是否开启GPS")] + public bool IsGps { get; set; } + + /// + /// 所属司机 + /// + [Comment("所属司机")] + public long VehicleDriver { get; set; } + + /// + /// 司机电话 + /// + [Comment("司机电话")] + public string DriverPhone { get; set; } + + /// + /// 联系人 + /// + [Comment("联系人")] + public string ContactPerson { get; set; } + + /// + /// 联系电话 + /// + [Comment("联系电话")] + public string ContactPhone { get; set; } + + /// + /// IEML号 + /// + [Comment("IEML号")] + public string ImeiNo { get; set; } + + /// + /// 状态 + /// + [Comment("状态")] + public int Status { get; set; } + + /// + /// 打火状态 + /// + [Comment("打火状态")] + public int Acc { get; set; } + + /// + /// 工作状态 + /// + [Comment("工作状态")] + public int Work { get; set; } + + /// + /// Gps状态 + /// + [Comment("Gps状态")] + public int GpsState { get; set; } + + /// + /// GPS时间 + /// + [Comment("GPS时间")] + public DateTime? GpsTime { get; set; } + + /// + /// 开通时间 + /// + [Comment("开通时间")] + public DateTime OpenTime { get; set; } + + /// + /// 到期时间 + /// + [Comment("到期时间")] + public DateTime ExpireTime { get; set; } + + /// + /// 物联卡类型 + /// + [Comment("物联卡类型")] + public int CardType { get; set; } + + /// + /// 是否开启超速报警 + /// + [Comment("是否开启超速报警")] + public bool IsOverspeedAlarm { get; set; } + + /// + /// 超速速度 + /// + [Comment("超速速度")] + [DataValidation(ValidationTypes.PositiveNumber)] + public int Overspeed { get; set; } + + + /// + /// 排序 + /// + [Comment("排序字段")] + public int Sort { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Entitys/VehicleGroup.cs b/src/Znyc.Dispatching.Core/Entitys/VehicleGroup.cs new file mode 100644 index 0000000..e7889d5 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Entitys/VehicleGroup.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Znyc.Dispatching.Core.Entitys +{ + /// + /// 车辆表分组 + /// + [Table("dc_vehicle_group")] + [Comment("车辆表分组")] + public class VehicleGroup : DEntityBase + { + /// + /// 公司Id + /// + [Comment("公司Id")] + public long CompanyId { get; set; } + + /// + /// 名称 + /// + [Comment("名称")] + public string Name { get; set; } + + /// + /// 父级权限Id + /// + [Comment("父级权限Id")] + public long ParentId { get; set; } + + /// + /// 状态(字典 0正常 1停用 2删除) + /// + [Comment("状态")] + public CommonStatusEnum Status { get; set; } = CommonStatusEnum.ENABLE; + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Entitys/VehiclePerson.cs b/src/Znyc.Dispatching.Core/Entitys/VehiclePerson.cs new file mode 100644 index 0000000..fabde1c --- /dev/null +++ b/src/Znyc.Dispatching.Core/Entitys/VehiclePerson.cs @@ -0,0 +1,46 @@ +using Microsoft.EntityFrameworkCore; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Znyc.Dispatching.Core.Entitys +{ + /// + /// 车组人员信息表 + /// + [Table("dc_vehicle_person")] + [Comment("车组人员信息表")] + public class VehiclePerson : DEntityBase + { + + /// + /// 公司Id + /// + [Comment("车辆Id")] + public long VehicleId { get; set; } + + /// + /// 车组人员Id + /// + [Comment("车组人员Id")] + public long UserId { get; set; } + + + /// + /// 员工名 + /// + [Comment("员工名")] + public string UserName { get; set; } + + + /// + /// 手机号 + /// + [Comment("手机号")] + public string UserPhone { get; set; } + + /// + /// 是否司机 + /// + [Comment("是否司机")] + public bool IsDriver { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Entitys/VehicleType.cs b/src/Znyc.Dispatching.Core/Entitys/VehicleType.cs new file mode 100644 index 0000000..c85f2dd --- /dev/null +++ b/src/Znyc.Dispatching.Core/Entitys/VehicleType.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Znyc.Dispatching.Core.Entitys +{ + /// + /// 车辆类型表 + /// + [Table("dc_vehicle_type")] + [Comment("车辆表")] + public class VehicleType : DEntityBase + { + + + /// + /// 公司Id + /// + public long CompanyId { get; set; } + /// + /// 名称 + /// + [Comment("名称")] + public string Name { get; set; } + + /// + /// 父级权限Id + /// + [Comment("父级权限Id")] + public long ParentId { get; set; } + + /// + /// 状态(字典 0正常 1停用 2删除) + /// + [Comment("状态")] + public bool Status { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Entitys/WxUserRelation.cs b/src/Znyc.Dispatching.Core/Entitys/WxUserRelation.cs new file mode 100644 index 0000000..fcbba4a --- /dev/null +++ b/src/Znyc.Dispatching.Core/Entitys/WxUserRelation.cs @@ -0,0 +1,45 @@ +using Microsoft.EntityFrameworkCore; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Znyc.Dispatching.Core.Entitys +{ + /// + /// 微信中间表 + /// + [Table("dc_wxuserrelation")] + [Comment("微信中间表")] + public class WxUserRelation : DEntityBase + { + /// + /// UnionId + /// + [Comment("UnionId")] + [Required] + [MaxLength(32)] + public string UnionId { get; set; } + + /// + /// OpenId + /// + [Comment("OpenId")] + [Required] + [MaxLength(32)] + public string OpenId { get; set; } + + /// + /// 用户Id + /// + + [Comment("用户Id")] + public long UserId { get; set; } + + /// + /// 公众号OpenId + /// + + [Comment("公众号OpenId")] + [MaxLength(32)] + public string WxOfficialOpenId { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Entitys/Yard.cs b/src/Znyc.Dispatching.Core/Entitys/Yard.cs new file mode 100644 index 0000000..eb474a5 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Entitys/Yard.cs @@ -0,0 +1,58 @@ +using Microsoft.EntityFrameworkCore; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Znyc.Dispatching.Core.Entitys +{ + /// + /// 车场表 + /// + [Table("dc_yard")] + [Comment("车场表")] + public class Yard : DEntityBase + { + /// + /// 公司Id + /// + [Comment("公司Id")] + public long CompanyId { get; set; } + + /// + /// 联系人 + /// + [Comment("联系人")] + [MaxLength(30)] + public string ContactPerson { get; set; } + + /// + /// 联系人电话 + /// + [Comment("联系人电话")] + [MaxLength(11)] + public string ContactPhone { get; set; } + + /// + /// 精度 + /// + [Comment("精度")] + public decimal Longitude { get; set; } + + /// + /// 纬度 + /// + [Comment("纬度")] + public decimal Latitude { get; set; } + + /// + /// 地址 + /// + [Comment("地址")] + public string Address { get; set; } + + /// + /// 状态(字典 1正常 0停用 -1删除) + /// + [Comment("状态")] + public int Status { get; set; } + } +} diff --git a/src/Znyc.Dispatching.Core/Enums/AccStatusEnum.cs b/src/Znyc.Dispatching.Core/Enums/AccStatusEnum.cs new file mode 100644 index 0000000..cf9a62a --- /dev/null +++ b/src/Znyc.Dispatching.Core/Enums/AccStatusEnum.cs @@ -0,0 +1,20 @@ +using System.ComponentModel; + +namespace Znyc.Dispatching.Core +{ + /// + /// 打火状态 + /// + public enum AccStatusEnum + { + /// + /// 关闭 + /// + [Description("关闭")] CLOSE = 0, + + /// + /// 正常打火 + /// + [Description("正常打火")] NORMAL = 1 + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Enums/CommonStatusEnum.cs b/src/Znyc.Dispatching.Core/Enums/CommonStatusEnum.cs new file mode 100644 index 0000000..358bdeb --- /dev/null +++ b/src/Znyc.Dispatching.Core/Enums/CommonStatusEnum.cs @@ -0,0 +1,30 @@ +using System.ComponentModel; + +namespace Znyc.Dispatching.Core +{ + /// + /// 公共状态 + /// + public enum CommonStatusEnum + { + /// + /// 正常 + /// + [Description("正常")] ENABLE = 1, + + /// + /// 停用 + /// + [Description("停用")] DISABLE = -1, + + /// + /// 删除 + /// + [Description("删除")] DELETED = -2, + + /// + /// 审核中 + /// + [Description("审核中")] REVIEW = 0 + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Enums/DataOpTypeEnum.cs b/src/Znyc.Dispatching.Core/Enums/DataOpTypeEnum.cs new file mode 100644 index 0000000..45f87a3 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Enums/DataOpTypeEnum.cs @@ -0,0 +1,78 @@ +namespace Znyc.Dispatching.Core +{ + /// + /// 数据操作类型 + /// + public enum DataOpTypeEnum + { + /// + /// 其它 + /// + OTHER, + + /// + /// 增加 + /// + ADD, + + /// + /// 删除 + /// + DELETE, + + /// + /// 编辑 + /// + EDIT, + + /// + /// 更新 + /// + UPDATE, + + /// + /// 查询 + /// + QUERY, + + /// + /// 详情 + /// + DETAIL, + + /// + /// 树 + /// + TREE, + + /// + /// 导入 + /// + IMPORT, + + /// + /// 导出 + /// + EXPORT, + + /// + /// 授权 + /// + GRANT, + + /// + /// 强退 + /// + FORCE, + + /// + /// 清空 + /// + CLEAN, + + /// + /// 修改状态 + /// + CHANGE_STATUS + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Enums/ErrorCode.cs b/src/Znyc.Dispatching.Core/Enums/ErrorCode.cs new file mode 100644 index 0000000..d2a5ec6 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Enums/ErrorCode.cs @@ -0,0 +1,379 @@ +using Furion.FriendlyException; + +namespace Znyc.Dispatching.Core +{ + /// + /// 系统错误码 + /// + [ErrorCodeType] + public enum ErrorCode + { + /// + /// 用户名或密码不正确 + /// + [ErrorCodeItemMetadata("用户名或密码不正确")] D1000, + + /// + /// 非法操作!禁止删除自己 + /// + [ErrorCodeItemMetadata("非法操作,禁止删除自己")] D1001, + + /// + /// 记录不存在 + /// + [ErrorCodeItemMetadata("记录不存在")] D1002, + + /// + /// 账号已存在 + /// + [ErrorCodeItemMetadata("账号已存在")] D1003, + + /// + /// 旧密码不匹配 + /// + [ErrorCodeItemMetadata("旧密码输入错误")] D1004, + + /// + /// 测试数据禁止更改admin密码 + /// + [ErrorCodeItemMetadata("测试数据禁止更改用户【admin】密码")] + D1005, + + /// + /// 数据已存在 + /// + [ErrorCodeItemMetadata("数据已存在")] D1006, + + /// + /// 数据不存在或含有关联引用,禁止删除 + /// + [ErrorCodeItemMetadata("数据不存在或含有关联引用,禁止删除")] + D1007, + + /// + /// 禁止为管理员分配角色 + /// + [ErrorCodeItemMetadata("禁止为管理员分配角色")] D1008, + + /// + /// 重复数据或记录含有不存在数据 + /// + [ErrorCodeItemMetadata("重复数据或记录含有不存在数据")] + D1009, + + /// + /// 禁止为超级管理员角色分配权限 + /// + [ErrorCodeItemMetadata("禁止为超级管理员角色分配权限")] + D1010, + + /// + /// 非法数据 + /// + [ErrorCodeItemMetadata("非法数据")] D1011, + + /// + /// Id不能为空 + /// + [ErrorCodeItemMetadata("Id不能为空")] D1012, + + /// + /// 所属机构不在自己的数据范围内 + /// + [ErrorCodeItemMetadata("没有权限操作该数据")] D1013, + + /// + /// 禁止删除超级管理员 + /// + [ErrorCodeItemMetadata("禁止删除超级管理员")] D1014, + + /// + /// 禁止修改超级管理员状态 + /// + [ErrorCodeItemMetadata("禁止修改超级管理员状态")] D1015, + + /// + /// 没有权限 + /// + [ErrorCodeItemMetadata("没有权限")] D1016, + + /// + /// 账号已冻结 + /// + [ErrorCodeItemMetadata("账号已冻结")] D1017, + + /// + /// 验证码错误 + /// + [ErrorCodeItemMetadata("验证码错误")] D1018, + + /// + /// 账号不存在 + /// + [ErrorCodeItemMetadata("账号不存在")] D1019, + + /// + /// 公司审核中 + /// + [ErrorCodeItemMetadata("公司审核中")] D1020, + + /// + /// 验证码失效 + /// + [ErrorCodeItemMetadata("验证码失效")] D1021, + + /// + /// 新密码不一致 + /// + [ErrorCodeItemMetadata("新密码不一致")] D1022, + + /// + /// 父机构不存在 + /// + [ErrorCodeItemMetadata("父机构不存在")] D2000, + + /// + /// 当前机构Id不能与父机构Id相同 + /// + [ErrorCodeItemMetadata("当前机构Id不能与父机构Id相同")] + D2001, + + /// + /// 已有相同组织机构,编码或名称相同 + /// + [ErrorCodeItemMetadata("已有相同组织机构,编码或名称相同")] + D2002, + + /// + /// 没有权限操作机构 + /// + [ErrorCodeItemMetadata("没有权限操作机构")] D2003, + + /// + /// 该机构下有员工禁止删除 + /// + [ErrorCodeItemMetadata("该机构下有员工禁止删除")] D2004, + + /// + /// 附属机构下有员工禁止删除 + /// + [ErrorCodeItemMetadata("附属机构下有员工禁止删除")] + D2005, + + /// + /// 只能增加下级机构 + /// + [ErrorCodeItemMetadata("只能增加下级机构")] D2006, + + /// + /// 字典类型不存在 + /// + [ErrorCodeItemMetadata("字典类型不存在")] D3000, + + /// + /// 字典类型已存在 + /// + [ErrorCodeItemMetadata("字典类型已存在,名称或编码重复")] + D3001, + + /// + /// 字典类型下面有字典值禁止删除 + /// + [ErrorCodeItemMetadata("字典类型下面有字典值禁止删除")] + D3002, + + /// + /// 字典值已存在 + /// + [ErrorCodeItemMetadata("字典值已存在,名称或编码重复")] + D3003, + + /// + /// 字典值不存在 + /// + [ErrorCodeItemMetadata("字典值不存在")] D3004, + + /// + /// 字典状态错误 + /// + [ErrorCodeItemMetadata("字典状态错误")] D3005, + + /// + /// 菜单已存在 + /// + [ErrorCodeItemMetadata("菜单已存在")] D4000, + + /// + /// 路由地址为空 + /// + [ErrorCodeItemMetadata("路由地址为空")] D4001, + + /// + /// 打开方式为空 + /// + [ErrorCodeItemMetadata("打开方式为空")] D4002, + + /// + /// 权限标识格式为空 + /// + [ErrorCodeItemMetadata("权限标识格式为空")] D4003, + + /// + /// 权限标识格式错误 + /// + [ErrorCodeItemMetadata("权限标识格式错误")] D4004, + + /// + /// 权限不存在 + /// + [ErrorCodeItemMetadata("权限不存在")] D4005, + + /// + /// 父级菜单不能为当前节点,请重新选择父级菜单 + /// + [ErrorCodeItemMetadata("父级菜单不能为当前节点,请重新选择父级菜单")] + D4006, + + /// + /// 不能移动根节点 + /// + [ErrorCodeItemMetadata("不能移动根节点")] D4007, + + /// + /// 已存在同名或同编码应用 + /// + [ErrorCodeItemMetadata("已存在同名或同编码应用")] D5000, + + /// + /// 默认激活系统只能有一个 + /// + [ErrorCodeItemMetadata("默认激活系统只能有一个")] D5001, + + /// + /// 该应用下有菜单禁止删除 + /// + [ErrorCodeItemMetadata("该应用下有菜单禁止删除")] D5002, + + /// + /// 已存在同名或同编码应用 + /// + [ErrorCodeItemMetadata("已存在同名或同编码应用")] D5003, + + /// + /// 已存在同名或同编码职位 + /// + [ErrorCodeItemMetadata("已存在同名或同编码职位")] D6000, + + /// + /// 该职位下有员工禁止删除 + /// + [ErrorCodeItemMetadata("该职位下有员工禁止删除")] D6001, + + /// + /// 通知公告状态错误 + /// + [ErrorCodeItemMetadata("通知公告状态错误")] D7000, + + /// + /// 通知公告删除失败 + /// + [ErrorCodeItemMetadata("通知公告删除失败")] D7001, + + /// + /// 通知公告编辑失败 + /// + [ErrorCodeItemMetadata("通知公告编辑失败,类型必须为草稿")] + D7002, + + /// + /// 文件不存在 + /// + [ErrorCodeItemMetadata("文件不存在")] D8000, + + /// + /// 已存在同名或同编码参数配置 + /// + [ErrorCodeItemMetadata("已存在同名或同编码参数配置")] + D9000, + + /// + /// 禁止删除系统参数 + /// + [ErrorCodeItemMetadata("禁止删除系统参数")] D9001, + + /// + /// 已存在同名任务调度 + /// + [ErrorCodeItemMetadata("已存在同名任务调度")] D1100, + + /// + /// 任务调度不存在 + /// + [ErrorCodeItemMetadata("任务调度不存在")] D1101, + + /// + /// 演示环境禁止修改数据 + /// + [ErrorCodeItemMetadata("演示环境禁止修改数据")] D1200, + + /// + /// 已存在同名或同主机租户 + /// + [ErrorCodeItemMetadata("已存在同名或同主机租户")] D1300, + + /// + /// 该表代码模板已经生成过 + /// + [ErrorCodeItemMetadata("该表代码模板已经生成过")] D1400, + + /// + /// 该类型不存在 + /// + [ErrorCodeItemMetadata("该类型不存在")] D1501, + + /// + /// 该字段不存在 + /// + [ErrorCodeItemMetadata("该字段不存在")] D1502, + + /// + /// 该类型不是枚举类型 + /// + [ErrorCodeItemMetadata("该类型不是枚举类型")] D1503, + + /// + /// 该实体不存在 + /// + [ErrorCodeItemMetadata("该实体不存在")] D1504, + + /// + /// 父菜单不存在 + /// + [ErrorCodeItemMetadata("父菜单不存在")] D1505, + + /// + /// 已存在同名或同编码项目 + /// + [ErrorCodeItemMetadata("已存在同名或同编码项目")] xg1000, + + /// + /// 已存在相同证件号码人员 + /// + [ErrorCodeItemMetadata("已存在相同证件号码人员")] xg1001, + + /// + /// 检测数据不存在 + /// + [ErrorCodeItemMetadata("检测数据不存在")] xg1002, + + /// + /// JsCode不存在 + /// + [ErrorCodeItemMetadata("JsCode不存在")] D1506, + + /// + /// 账号已存在 + /// + [ErrorCodeItemMetadata("账号已存在")] xg1003 + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Enums/FileLocationEnum.cs b/src/Znyc.Dispatching.Core/Enums/FileLocationEnum.cs new file mode 100644 index 0000000..9faa223 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Enums/FileLocationEnum.cs @@ -0,0 +1,30 @@ +using System.ComponentModel; + +namespace Znyc.Dispatching.Core +{ + /// + /// 文件存储位置 + /// + public enum FileLocationEnum + { + /// + /// 阿里云 + /// + [Description("阿里云")] ALIYUN = 1, + + /// + /// 腾讯云 + /// + [Description("腾讯云")] TENCENT = 2, + + /// + /// minio服务器 + /// + [Description("minio服务器")] MINIO = 3, + + /// + /// 本地 + /// + [Description("本地")] LOCAL = 4 + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Enums/GpsStatusEnum.cs b/src/Znyc.Dispatching.Core/Enums/GpsStatusEnum.cs new file mode 100644 index 0000000..fa00d8b --- /dev/null +++ b/src/Znyc.Dispatching.Core/Enums/GpsStatusEnum.cs @@ -0,0 +1,25 @@ +using System.ComponentModel; + +namespace Znyc.Dispatching.Core +{ + /// + /// Gps状态 + /// + public enum GpsStatusEnum + { + /// + /// 离线 + /// + [Description("离线")] OFFLINE = 0, + + /// + /// 行驶 + /// + [Description("行驶")] DRIVING = 1, + + /// + /// 静止 + /// + [Description("静止")] STILL = 2 + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Enums/HangFireQueuesConfig.cs b/src/Znyc.Dispatching.Core/Enums/HangFireQueuesConfig.cs new file mode 100644 index 0000000..bf8168b --- /dev/null +++ b/src/Znyc.Dispatching.Core/Enums/HangFireQueuesConfig.cs @@ -0,0 +1,29 @@ +using System.ComponentModel; + +namespace Znyc.Dispatching.Core.Enums +{ + //HangFire定时任务相关 + public enum HangFireQueuesConfig + { + /// + /// 默认 + /// + [Description("默认")] + @default = 1, + /// + /// 接口 + /// + [Description("接口")] + apis = 2, + /// + /// 网站 + /// + [Description("网站")] + web = 3, + /// + /// 循环时间 + /// + [Description("循环时间")] + recurring = 4, + } +} diff --git a/src/Znyc.Dispatching.Core/Enums/HttpStatusCodeEnum.cs b/src/Znyc.Dispatching.Core/Enums/HttpStatusCodeEnum.cs new file mode 100644 index 0000000..856a76d --- /dev/null +++ b/src/Znyc.Dispatching.Core/Enums/HttpStatusCodeEnum.cs @@ -0,0 +1,212 @@ +using System.ComponentModel; + +namespace Znyc.Dispatching.Core +{ + /// + /// HTTP状态码 + /// + public enum HttpStatusCodeEnum + { + /// + /// 客户端可能继续其请求 + /// + [Description("继续")] Continue = 100, + + /// + /// 正在更改协议版本或协议 + /// + [Description("交换协议")] SwitchingProtocols = 101, + + /// + /// 请求成功,且请求的信息包含在响应中 + /// + [Description("OK")] OK = 200, + + /// + /// 请求导致在响应被发送前创建新资源 + /// + [Description("已创建")] Created = 201, + + /// + /// 请求已被接受做进一步处理 + /// + [Description("接收")] Accepted = 202, + + /// + /// 返回的元信息来自缓存副本而不是原始服务器,因此可能不正确 + /// + [Description("非认证信息")] NonAuthoritativeInformation = 203, + + /// + /// 已成功处理请求并且响应已被设定为无内容 + /// + [Description("无内容")] NoContent = 204, + + /// + /// 客户端应重置(或重新加载)当前资源 + /// + [Description("重置内容")] ResetContent = 205, + + /// + /// 响应是包括字节范围的 GET请求所请求的部分响应 + /// + [Description("部分内容")] PartialContent = 206, + + /// + /// 请求的信息有多种表示形式,默认操作是将此状态视为重定向 + /// + [Description("多路选择")] MultipleChoices = 300, + + /// + /// 请求的信息已移到 Location头中指定的 URI 处 + /// + [Description("永久转移")] MovedPermanently = 301, + + /// + /// 请求的信息位于 Location 头中指定的 URI 处 + /// + [Description("暂时转移")] Found = 302, + + /// + /// 将客户端自动重定向到 Location 头中指定的 URI + /// + [Description("参见其它")] SeeOther = 303, + + /// + /// 客户端的缓存副本是最新的 + /// + [Description("未修改")] NotModified = 304, + + /// + /// 请求应使用位于 Location 头中指定的 URI 的代理服务器 + /// + [Description("使用代理")] UseProxy = 305, + + /// + /// 服务器未能识别请求 + /// + [Description("错误请求")] BadRequest = 400, + + /// + /// 请求的资源要求身份验证 + /// + [Description("未认证")] Unauthorized = 401, + + /// + /// 需要付费 + /// + [Description("需要付费")] PaymentRequired = 402, + + /// + /// 服务器拒绝满足请求 + /// + [Description("禁止")] Forbidden = 403, + + /// + /// 请求的资源不在服务器上 + /// + [Description("未找到")] NotFound = 404, + + /// + /// 请求的资源上不允许请求方法(POST或 GET) + /// + [Description("请求方法不允许")] MethodNotAllowed = 405, + + /// + /// 客户端已用 Accept 头指示将不接受资源的任何可用表示形式 + /// + [Description("不接受")] NotAcceptable = 406, + + /// + /// 请求的代理要求身份验证 + /// Proxy-authenticate 头包含如何执行身份验证的详细信息 + /// + [Description("需要代理认证")] ProxyAuthenticationRequired = 407, + + /// + /// 客户端没有在服务器期望请求的时间内发送请求 + /// + [Description("请求超时")] RequestTimeout = 408, + + /// + /// 由于服务器上的冲突而未能执行请求 + /// + [Description("冲突")] Conflict = 409, + + /// + /// 请求的资源不再可用 + /// + [Description("失败")] Gone = 410, + + /// + /// 缺少必需的 Content-length + /// + [Description("缺少Content-length头")] LengthRequired = 411, + + /// + /// 为此请求设置的条件失败,且无法执行此请求 + /// 条件是用条件请求标头(如 If-Match、If-None-Match 或 If-Unmodified-Since)设置的。 + /// + [Description("条件失败")] PreconditionFailed = 412, + + /// + /// 请求太大,服务器无法处理 + /// + [Description("请求实体太大")] RequestEntityTooLarge = 413, + + /// + /// URI 太长 + /// + [Description("请求URI太长")] RequestUriTooLong = 414, + + /// + /// 请求是不支持的类型 + /// + [Description("不支持的媒体类型")] UnsupportedMediaType = 415, + + /// + /// 无法返回从资源请求的数据范围,因为范围的开头在资源的开头之前,或因为范围的结尾在资源的结尾之后 + /// + [Description("数据范围不匹配")] RequestedRangeNotSatisfiable = 416, + + /// + /// 服务器未能符合Expect头中给定的预期值 + /// + [Description("服务器与Expect头不匹配")] ExpectationFailed = 417, + + /// + /// 服务器拒绝处理客户端使用当前协议发送的请求,但是可以接受其使用升级后的协议发送的请求 + /// + [Description("当前协议不受支持")] UpgradeRequired = 426, + + /// + /// 服务器上发生了一般错误 + /// + [Description("服务器内部错误")] InternalServerError = 500, + + /// + /// 服务器不支持请求的函数 + /// + [Description("未实现")] NotImplemented = 501, + + /// + /// 中间代理服务器从另一代理或原始服务器接收到错误响应 + /// + [Description("网关失败")] BadGateway = 502, + + /// + /// 服务器暂时不可用,通常是由于过多加载或维护 + /// + [Description("服务器维护")] ServiceUnavailable = 503, + + /// + /// 中间代理服务器在等待来自另一个代理或原始服务器的响应时已超时 + /// + [Description("网关超时")] GatewayTimeout = 504, + + /// + /// 服务器不支持请求的HTTP版本 + /// + [Description("HTTP版本不支持")] HttpVersionNotSupported = 505 + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Enums/LoginTypeEnum.cs b/src/Znyc.Dispatching.Core/Enums/LoginTypeEnum.cs new file mode 100644 index 0000000..7d757a4 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Enums/LoginTypeEnum.cs @@ -0,0 +1,35 @@ +using System.ComponentModel; + +namespace Znyc.Dispatching.Core +{ + /// + /// 登陆类型 + /// + public enum LoginTypeEnum + { + /// + /// 登陆 + /// + [Description("登陆")] LOGIN = 0, + + /// + /// 登出 + /// + [Description("登出")] LOGOUT = 1, + + /// + /// 注册 + /// + [Description("注册")] REGISTER = 2, + + /// + /// 改密 + /// + [Description("改密")] CHANGEPASSWORD = 3, + + /// + /// 三方授权登陆 + /// + [Description("授权登陆")] AUTHORIZEDLOGIN = 4 + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Enums/MenuTypeEnum.cs b/src/Znyc.Dispatching.Core/Enums/MenuTypeEnum.cs new file mode 100644 index 0000000..bbd0cea --- /dev/null +++ b/src/Znyc.Dispatching.Core/Enums/MenuTypeEnum.cs @@ -0,0 +1,25 @@ +using System.ComponentModel; + +namespace Znyc.Dispatching.Core +{ + /// + /// 系统菜单类型 + /// + public enum MenuTypeEnum + { + /// + /// 目录 + /// + [Description("目录")] DIR = 0, + + /// + /// 菜单 + /// + [Description("菜单")] MENU = 1, + + /// + /// 按钮 + /// + [Description("按钮")] BTN = 2 + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Enums/NoticeUserStatusEnum.cs b/src/Znyc.Dispatching.Core/Enums/NoticeUserStatusEnum.cs new file mode 100644 index 0000000..53e9866 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Enums/NoticeUserStatusEnum.cs @@ -0,0 +1,20 @@ +using System.ComponentModel; + +namespace Znyc.Dispatching.Core +{ + /// + /// 通知公告用户状态 + /// + public enum NoticeUserStatusEnum + { + /// + /// 未读 + /// + [Description("未读")] UNREAD = 0, + + /// + /// 已读 + /// + [Description("已读")] READ = 1 + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Enums/OrderSourceEnum.cs b/src/Znyc.Dispatching.Core/Enums/OrderSourceEnum.cs new file mode 100644 index 0000000..a6003cd --- /dev/null +++ b/src/Znyc.Dispatching.Core/Enums/OrderSourceEnum.cs @@ -0,0 +1,24 @@ +using System.ComponentModel; + +namespace Znyc.Dispatching.Core +{ + /// + /// 派工订单来源 + /// + public enum OrderSourceEnum + { + /// + /// 录入 + /// + [Description("录入")] + Input = 10, + + /// + /// 报单 + /// + [Description("报单")] + Declaration = 20, + + + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Enums/OrderStatus.cs b/src/Znyc.Dispatching.Core/Enums/OrderStatus.cs new file mode 100644 index 0000000..6beb44f --- /dev/null +++ b/src/Znyc.Dispatching.Core/Enums/OrderStatus.cs @@ -0,0 +1,82 @@ +using System.ComponentModel; + +namespace Znyc.Dispatching.Core +{ + /// + /// 派工状态 + /// + public enum OrderStatus + { + + /// + /// 草稿 + /// + [Description("草稿")] + Draft = 0, + + /// + /// 待指派 + /// + [Description("待指派")] + StayAssign = 10, + + /// + /// 已指派(未接单) + /// + [Description("未接单")] + Assign = 20, + + /// + /// 已接单 + /// + [Description("已接单")] + Receive = 30, + + /// + /// 已出发 + /// + [Description("已出发")] + Travel = 40, + + /// + /// 已到达 + /// + [Description("已到达")] + Appear = 50, + + /// + /// 已完成 + /// + [Description("已完成")] + Complete = 60, + + /// + /// 已签单 + /// + [Description("已签单")] + Sign = 70, + /// + /// 已离开 + /// + [Description("已离开")] + Leave = 80, + + /// + /// 已评价 + /// + [Description("已评价")] + Evaluate = 90, + + /// + /// 撤销 + /// + [Description("撤销")] + Cancel = 101, + + /// + /// 已到家 + /// + [Description("已到家")] + ArriveHome = 85, + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Enums/PlatformTypeEnum.cs b/src/Znyc.Dispatching.Core/Enums/PlatformTypeEnum.cs new file mode 100644 index 0000000..bb0c038 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Enums/PlatformTypeEnum.cs @@ -0,0 +1,22 @@ +using System.ComponentModel; + +namespace Znyc.Dispatching.Core +{ + public enum PlatformTypeEnum + { + /// + /// 人才招聘 + /// + [Description("人才招聘")] Recruitment = 1, + + /// + /// 派工调度 + /// + [Description("派工调度")] Dispatching = 2, + + /// + /// 后台管理系统 + /// + [Description("后台管理系统")] Management = 3 + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Enums/QueryTypeEnum.cs b/src/Znyc.Dispatching.Core/Enums/QueryTypeEnum.cs new file mode 100644 index 0000000..826d18f --- /dev/null +++ b/src/Znyc.Dispatching.Core/Enums/QueryTypeEnum.cs @@ -0,0 +1,50 @@ +using System.ComponentModel; + +namespace Znyc.Dispatching.Core +{ + /// + /// 查询类型的枚举 + /// + public enum QueryTypeEnum + { + /// + /// 等于 + /// + [Description("等于")] eq = 0, + + /// + /// 模糊 + /// + [Description("模糊")] like = 1, + + /// + /// 大于 + /// + [Description("大于")] gt = 2, + + /// + /// 小于 + /// + [Description("小于")] lt = 3, + + /// + /// 不等于 + /// + [Description("不等于")] ne = 4, + + /// + /// 大于等于 + /// + [Description("大于等于")] ge = 5, + + /// + /// 小于等于 + /// + [Description("小于等于")] le = 6, + + /// + /// 不为空 + /// + [Description("不为空")] isNotNull = 7 + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Enums/RequestTypeEnum.cs b/src/Znyc.Dispatching.Core/Enums/RequestTypeEnum.cs new file mode 100644 index 0000000..d546835 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Enums/RequestTypeEnum.cs @@ -0,0 +1,33 @@ +namespace Znyc.Dispatching.Core +{ + /// + /// http请求类型 + /// + public enum RequestTypeEnum + { + /// + /// 执行内部方法 + /// + Run = 0, + + /// + /// GET请求 + /// + Get = 1, + + /// + /// POST请求 + /// + Post = 2, + + /// + /// PUT请求 + /// + Put = 3, + + /// + /// DELETE请求 + /// + Delete = 4 + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Enums/RoleStatusEnum.cs b/src/Znyc.Dispatching.Core/Enums/RoleStatusEnum.cs new file mode 100644 index 0000000..242fca9 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Enums/RoleStatusEnum.cs @@ -0,0 +1,65 @@ +using System.ComponentModel; + +namespace Znyc.Dispatching.Core +{ + /// + /// 角色类型 + /// + public enum RoleStatusEnum + { + /// + /// 车组人员 + /// + [Description("车组人员")] CrewMembers = 1001, + + /// + /// 调度 + /// + [Description("调度")] Scheduling = 1002, + + /// + /// 销售 + /// + [Description("销售")] Sales = 1003, + + /// + /// 财务 + /// + [Description("财务")] Financial = 1004, + + /// + /// 管理员 + /// + [Description("管理员")] Administrator = 1005, + + /// + /// 业务员 + /// + [Description("业务员")] Salesman = 1006, + + /// + /// 车队长 + /// + [Description("车队长")] CarCaptain = 1007, + + /// + /// 仓管 + /// + [Description("仓管")] WareHouse = 1008, + + /// + /// 工地施工员 + /// + [Description("工地施工员")] ProjectPerson = 1009, + + /// + /// 外租伙伴 + /// + [Description("外租伙伴")] Outside = 1010, + + /// + /// 兼职业务 + /// + [Description("兼职业务")] PartTimeSalesman = 1011, + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Enums/StopStatusEnum.cs b/src/Znyc.Dispatching.Core/Enums/StopStatusEnum.cs new file mode 100644 index 0000000..90cd5d2 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Enums/StopStatusEnum.cs @@ -0,0 +1,25 @@ +using System.ComponentModel; + +namespace Znyc.Dispatching.Core +{ + /// + /// 停留点状态 + /// + public enum StopStatusEnum + { + /// + /// 起点 + /// + [Description("起点")] STARTPOINT = 0, + + /// + /// 停留点 + /// + [Description("停留点")] STOPPOINT = 1, + + /// + /// 终点 + /// + [Description("终点")] ENDPOINT = 3 + } +} diff --git a/src/Znyc.Dispatching.Core/Enums/TerminalTypeEnum.cs b/src/Znyc.Dispatching.Core/Enums/TerminalTypeEnum.cs new file mode 100644 index 0000000..6a92eca --- /dev/null +++ b/src/Znyc.Dispatching.Core/Enums/TerminalTypeEnum.cs @@ -0,0 +1,15 @@ +using System.ComponentModel; + +namespace Znyc.Dispatching.Core +{ + /// + /// 产品类型 + /// + public enum TerminalTypeEnum + { + /// + /// 博实结KG_M08四线 + /// + [Description("博实结KG_M08四线")] BSJKGM_08 = 1 + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Enums/WorkStatusEnum.cs b/src/Znyc.Dispatching.Core/Enums/WorkStatusEnum.cs new file mode 100644 index 0000000..c701cac --- /dev/null +++ b/src/Znyc.Dispatching.Core/Enums/WorkStatusEnum.cs @@ -0,0 +1,20 @@ +using System.ComponentModel; + +namespace Znyc.Dispatching.Core +{ + /// + /// 工作状态 + /// + public enum WorkStatusEnum + { + /// + /// 关闭 + /// + [Description("关闭")] CLOSE = 0, + + /// + /// 正常工作 + /// + [Description("正常工作")] NORMAL = 1 + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Enums/YesOrNotEnum.cs b/src/Znyc.Dispatching.Core/Enums/YesOrNotEnum.cs new file mode 100644 index 0000000..56e0eab --- /dev/null +++ b/src/Znyc.Dispatching.Core/Enums/YesOrNotEnum.cs @@ -0,0 +1,20 @@ +using System.ComponentModel; + +namespace Znyc.Dispatching.Core +{ + /// + /// 菜单激活类型 + /// + public enum YesOrNotEnum + { + /// + /// 是 + /// + [Description("是")] Y = 0, + + /// + /// 否 + /// + [Description("否")] N = 1 + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Extension/DateTimeExtensions.cs b/src/Znyc.Dispatching.Core/Extension/DateTimeExtensions.cs new file mode 100644 index 0000000..8e9907b --- /dev/null +++ b/src/Znyc.Dispatching.Core/Extension/DateTimeExtensions.cs @@ -0,0 +1,36 @@ +using System; + +namespace Znyc.Dispatching.Core +{ + public static class DateTimeExtensions + { + /// + /// 时间戳起始日期 + /// + public static DateTime TimestampStart = new(1970, 1, 1, 0, 0, 0, 0); + + /// + /// 转换为时间戳 + /// + /// + /// 是否使用毫秒 + /// + public static int ToTimestamp(this DateTime dateTime, bool milliseconds = false) + { + TimeSpan timestamp = dateTime.ToUniversalTime() - TimestampStart; + return (int)(milliseconds ? timestamp.TotalMilliseconds : timestamp.TotalSeconds); + } + + /// + /// 获取周几 + /// + /// + /// + public static string GetWeekName(this DateTime datetime) + { + int day = (int)datetime.DayOfWeek; + string[] week = { "周日", "周一", "周二", "周三", "周四", "周五", "周六" }; + return week[day]; + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Extension/DictionaryExtensions.cs b/src/Znyc.Dispatching.Core/Extension/DictionaryExtensions.cs new file mode 100644 index 0000000..7f1b5c0 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Extension/DictionaryExtensions.cs @@ -0,0 +1,52 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Web; + +namespace Znyc.Dispatching.Core +{ + /// + /// 字典扩展 + /// + public static class DictionaryExtensions + { + /// + /// 将一个字典转化为 QueryString + /// + /// + /// + /// + public static string ToQueryString(this Dictionary dict, bool urlEncode = true) + { + return string.Join("&", + dict.Select(p => $"{(urlEncode ? p.Key?.UrlEncode() : "")}={(urlEncode ? p.Value?.UrlEncode() : "")}")); + } + + /// + /// 将一个字符串 URL 编码 + /// + /// + /// + public static string UrlEncode(this string str) + { + if (string.IsNullOrEmpty(str)) + { + return ""; + } + + return HttpUtility.UrlEncode(str, Encoding.UTF8); + } + + /// + /// 移除空值项 + /// + /// + public static void RemoveEmptyValueItems(this Dictionary dict) + { + dict.Where(item => string.IsNullOrEmpty(item.Value)).Select(item => item.Key).ToList().ForEach(key => + { + dict.Remove(key); + }); + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Extension/EnumExtensions.cs b/src/Znyc.Dispatching.Core/Extension/EnumExtensions.cs new file mode 100644 index 0000000..14d9867 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Extension/EnumExtensions.cs @@ -0,0 +1,317 @@ +using Furion.FriendlyException; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Reflection; +using Znyc.Dispatching.Core.Extension; + +namespace Znyc.Dispatching.Core +{ + /// + /// 枚举扩展 + /// + public static class EnumExtensions + { + // 枚举显示字典缓存 + private static readonly ConcurrentDictionary> EnumDisplayValueDict = new(); + + // 枚举值字典缓存 + private static readonly ConcurrentDictionary> EnumNameValueDict = new(); + + // 枚举类型缓存 + private static ConcurrentDictionary _enumTypeDict; + + /// + /// 获取枚举对象Key与名称的字典(缓存) + /// + /// + /// + public static Dictionary GetEnumDictionary(Type enumType) + { + if (!enumType.IsEnum) + { + throw Oops.Oh(ErrorCode.D1503); + } + + // 查询缓存 + Dictionary enumDic = EnumNameValueDict.ContainsKey(enumType) + ? EnumNameValueDict[enumType] + : new Dictionary(); + if (enumDic.Count == 0) + { + // 取枚举类型的Key/Value字典集合 + enumDic = GetEnumDictionaryItems(enumType); + + // 缓存 + EnumNameValueDict[enumType] = enumDic; + } + + return enumDic; + } + + /// + /// 获取枚举对象Key与名称的字典 + /// + /// + /// + private static Dictionary GetEnumDictionaryItems(Type enumType) + { + // 获取类型的字段,初始化一个有限长度的字典 + FieldInfo[] enumFields = enumType.GetFields(BindingFlags.Public | BindingFlags.Static); + Dictionary enumDic = new(enumFields.Length); + + // 遍历字段数组获取key和name + foreach (FieldInfo enumField in enumFields) + { + int intValue = (int)enumField.GetValue(enumType); + enumDic[intValue] = enumField.Name; + } + + return enumDic; + } + + /// + /// 获取枚举类型key与描述的字典(缓存) + /// + /// + /// + /// + public static Dictionary GetEnumDescDictionary(Type enumType) + { + if (!enumType.IsEnum) + { + throw Oops.Oh(ErrorCode.D1503); + } + + // 查询缓存 + Dictionary enumDic = EnumDisplayValueDict.ContainsKey(enumType) + ? EnumDisplayValueDict[enumType] + : new Dictionary(); + if (enumDic.Count == 0) + { + // 取枚举类型的Key/Value字典集合 + enumDic = GetEnumDescDictionaryItems(enumType); + + // 缓存 + EnumDisplayValueDict[enumType] = enumDic; + } + + return enumDic; + } + + /// + /// 获取枚举类型key与描述的字典(没有描述则获取name) + /// + /// + /// + /// + private static Dictionary GetEnumDescDictionaryItems(Type enumType) + { + // 获取类型的字段,初始化一个有限长度的字典 + FieldInfo[] enumFields = enumType.GetFields(BindingFlags.Public | BindingFlags.Static); + Dictionary enumDic = new(enumFields.Length); + + // 遍历字段数组获取key和name + foreach (FieldInfo enumField in enumFields) + { + int intValue = (int)enumField.GetValue(enumType); + DescriptionAttribute desc = enumField.GetDescriptionValue(); + enumDic[intValue] = desc != null && !string.IsNullOrEmpty(desc.Description) + ? desc.Description + : enumField.Name; + } + + return enumDic; + } + + /// + /// 从程序集中查找指定枚举类型 + /// + /// + /// + /// + public static Type TryToGetEnumType(Assembly assembly, string typeName) + { + // 枚举缓存为空则重新加载枚举类型字典 + _enumTypeDict ??= LoadEnumTypeDict(assembly); + + // 按名称查找 + if (_enumTypeDict.ContainsKey(typeName)) + { + return _enumTypeDict[typeName]; + } + + return null; + } + + /// + /// 从程序集中加载所有枚举类型 + /// + /// + /// + private static ConcurrentDictionary LoadEnumTypeDict(Assembly assembly) + { + // 取程序集中所有类型 + Type[] typeArray = assembly.GetTypes(); + + // 过滤非枚举类型,转成字典格式并返回 + Dictionary dict = typeArray.Where(o => o.IsEnum).ToDictionary(o => o.Name, o => o); + ConcurrentDictionary enumTypeDict = new(dict); + return enumTypeDict; + } + + /// + /// 从枚举中获取Description + /// + /// 需要获取枚举描述的枚举 + /// 描述内容 + public static string GetDescription(this Enum enumName) + { + string description = string.Empty; + FieldInfo fieldInfo = enumName.GetType().GetField(enumName.ToString()); + DescriptionAttribute[] attributes = GetDescriptAttr(fieldInfo); + if (attributes != null && attributes.Length > 0) + { + description = attributes[0].Description; + } + else + { + description = enumName.ToString(); + } + + return description; + } + + /// + /// 根据 value 值获取Description + /// + /// + /// + /// + public static string GetDescription(this Type enumType, int value) + { + object Key = GetNameAndValue(enumType).FirstOrDefault(p => p.Value.Equals(value)).Key; + if (Key.IsNull()) + { + return null; + } + + return Key.ToString(); + } + + + /// + /// 根据 value 值获取Description + /// + /// + /// + /// + public static string GetDescriptionByEnumType(Type enumType, int value) + { + object Key = GetNameAndValue(enumType).FirstOrDefault(p => p.Value.Equals(value)).Key; + if (Key.IsNull()) + { + return null; + } + + return Key.ToString(); + } + + + + + /// + /// 获取字段Description + /// + /// FieldInfo + /// DescriptionAttribute[] + private static DescriptionAttribute[] GetDescriptAttr(FieldInfo fieldInfo) + { + if (fieldInfo != null) + { + return (DescriptionAttribute[])fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false); + } + + return null; + } + + /// + /// 获取枚举所有名称 + /// + /// 枚举类型typeof(T) + /// 枚举名称列表 + public static List GetEnumNamesList(this Type enumType) + { + return Enum.GetNames(enumType).ToList(); + } + + /// + /// 获取所有枚举对应的值 + /// + /// 枚举类型typeof(T) + /// 枚举值列表 + public static List GetEnumValuesList(this Type enumType) + { + List list = new List(); + foreach (object value in Enum.GetValues(enumType)) + { + list.Add(Convert.ToInt32(value)); + } + + return list; + } + + /// + /// 获取枚举名以及对应的Description + /// + /// 枚举类型typeof(T) + /// 返回Dictionary ,Key为枚举名, Value为枚举对应的Description + public static Dictionary GetNameAndDescriptions(this Type type) + { + if (type.IsEnum) + { + Dictionary dic = new Dictionary(); + Array enumValues = Enum.GetValues(type); + foreach (Enum value in enumValues) + { + dic.Add(value, GetDescription(value)); + } + + return dic; + } + + return null; + } + + /// + /// 获取枚举名以及对应的Value + /// + /// 枚举类型typeof(T) + /// 返回Dictionary ,Key为描述名, Value为枚举对应的值 + public static Dictionary GetNameAndValue(this Type type) + { + if (type.IsEnum) + { + Dictionary dic = new Dictionary(); + Array enumValues = Enum.GetValues(type); + foreach (Enum value in enumValues) + { + dic.Add(GetDescription(value), value.GetHashCode()); + } + + return dic; + } + + return null; + } + + public static string ToDescription(this Enum item) + { + string name = item.ToString(); + DescriptionAttribute desc = item.GetType().GetField(name)?.GetCustomAttribute(); + return desc?.Description ?? name; + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Extension/InputBase.cs b/src/Znyc.Dispatching.Core/Extension/InputBase.cs new file mode 100644 index 0000000..24c229e --- /dev/null +++ b/src/Znyc.Dispatching.Core/Extension/InputBase.cs @@ -0,0 +1,71 @@ +using System.Collections.Generic; + +namespace Znyc.Dispatching.Core +{ + /// + /// 通用输入扩展参数(带权限) + /// + public class InputBase : PageInputBase + { + /// + /// 授权菜单 + /// + public List GrantMenuIdList { get; set; } = new(); + + /// + /// 授权角色 + /// + public virtual List GrantRoleIdList { get; set; } = new(); + + /// + /// 授权数据 + /// + public virtual List GrantOrgIdList { get; set; } = new(); + } + + /// + /// 通用分页输入参数 + /// + public class PageInputBase + { + /// + /// 搜索值 + /// + public virtual string SearchValue { get; set; } + + /// + /// 当前页码 + /// + public virtual int PageNo { get; set; } = 1; + + /// + /// 页码容量 + /// + public virtual int PageSize { get; set; } = 20; + + /// + /// 搜索开始时间 + /// + public virtual string SearchBeginTime { get; set; } + + /// + /// 搜索结束时间 + /// + public virtual string SearchEndTime { get; set; } + + /// + /// 排序字段 + /// + public virtual string SortField { get; set; } + + /// + /// 排序方法,默认升序,否则降序(配合antd前端,约定参数为 Ascend,Dscend) + /// + public virtual string SortOrder { get; set; } + + /// + /// 降序排序(不要问我为什么是descend不是desc,前端约定参数就是这样) + /// + public virtual string DescStr { get; set; } = "descend"; + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Extension/ObjectExtensions.cs b/src/Znyc.Dispatching.Core/Extension/ObjectExtensions.cs new file mode 100644 index 0000000..72bb73b --- /dev/null +++ b/src/Znyc.Dispatching.Core/Extension/ObjectExtensions.cs @@ -0,0 +1,233 @@ +using System; + +namespace Znyc.Dispatching.Core.Extension +{ + public static class ObjectExtensions + { + /// + /// 判断对象是否为空 + /// + /// + /// + public static bool IsNull(this object obj) + { + return obj == null; + } + + /// + /// 判断对象是否不为空 + /// + /// + /// + public static bool IsNotNull(this object obj) + { + return obj != null; + } + + /// + /// + /// + public static void ThrowIfNull(this object obj) + { + if (obj == null) + { + throw new ArgumentNullException(nameof(obj)); + } + } + + /// + /// + /// + /// + public static int ObjToInt(this object thisValue) + { + int reval = 0; + if (thisValue == null) + { + return 0; + } + + if (thisValue != DBNull.Value && int.TryParse(thisValue.ToString(), out reval)) + { + return reval; + } + + return reval; + } + + /// + /// + /// + /// + /// + public static int ObjToInt(this object thisValue, int errorValue) + { + if (thisValue != null && thisValue != DBNull.Value && int.TryParse(thisValue.ToString(), out int reval)) + { + return reval; + } + + return errorValue; + } + + /// + /// + /// + /// + public static double ObjToMoney(this object thisValue) + { + if (thisValue != null && thisValue != DBNull.Value && + double.TryParse(thisValue.ToString(), out double reval)) + { + return reval; + } + + return 0; + } + + /// + /// + /// + /// + /// + public static double ObjToMoney(this object thisValue, double errorValue) + { + if (thisValue != null && thisValue != DBNull.Value && + double.TryParse(thisValue.ToString(), out double reval)) + { + return reval; + } + + return errorValue; + } + + /// + /// + /// + /// + public static string ObjToString(this object thisValue) + { + if (thisValue != null) + { + return thisValue.ToString().Trim(); + } + + return ""; + } + + /// + /// + /// + /// + public static bool IsNotEmptyOrNull(this object thisValue) + { + return ObjToString(thisValue) != "" && ObjToString(thisValue) != "undefined" && + ObjToString(thisValue) != "null"; + } + + /// + /// + /// + /// + /// + public static string ObjToString(this object thisValue, string errorValue) + { + if (thisValue != null) + { + return thisValue.ToString().Trim(); + } + + return errorValue; + } + + /// + /// + /// + /// + public static decimal ObjToDecimal(this object thisValue) + { + if (thisValue != null && thisValue != DBNull.Value && + decimal.TryParse(thisValue.ToString(), out decimal reval)) + { + return reval; + } + + return 0; + } + + /// + /// + /// + /// + /// + public static decimal ObjToDecimal(this object thisValue, decimal errorValue) + { + if (thisValue != null && thisValue != DBNull.Value && + decimal.TryParse(thisValue.ToString(), out decimal reval)) + { + return reval; + } + + return errorValue; + } + + /// + /// + /// + /// + public static DateTime ObjToDate(this object thisValue) + { + DateTime reval = DateTime.MinValue; + if (thisValue != null && thisValue != DBNull.Value && DateTime.TryParse(thisValue.ToString(), out reval)) + { + reval = Convert.ToDateTime(thisValue); + } + + return reval; + } + + /// + /// + /// + /// + /// + public static DateTime ObjToDate(this object thisValue, DateTime errorValue) + { + DateTime reval = DateTime.MinValue; + if (thisValue != null && thisValue != DBNull.Value && + DateTime.TryParse(thisValue.ToString(), out reval)) + { + return reval; + } + + return errorValue; + } + + /// + /// + /// + /// + public static bool ObjToBool(this object thisValue) + { + bool reval = false; + if (thisValue != null && thisValue != DBNull.Value && + bool.TryParse(thisValue.ToString(), out reval)) + { + return reval; + } + + return reval; + } + + /// + /// 获取当前时间的时间戳 + /// + /// + /// + public static string DateToTimeStamp(this DateTime thisValue) + { + TimeSpan ts = thisValue - new DateTime(1970, 1, 1, 0, 0, 0, 0); + return Convert.ToInt64(ts.TotalSeconds).ToString(); + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Extension/PageInputOrder.cs b/src/Znyc.Dispatching.Core/Extension/PageInputOrder.cs new file mode 100644 index 0000000..b96c046 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Extension/PageInputOrder.cs @@ -0,0 +1,28 @@ +namespace Znyc.Dispatching.Core +{ + /// + /// 通用输入帮助类 + /// + public class PageInputOrder + { + /// + /// 排序方式(默认降序) + /// + /// + /// 是否降序 + /// + public static string OrderBuilder(PageInputBase pageInput, bool descSort = true) + { + // 约定默认每张表都有Id排序 + string orderStr = descSort ? "Id Desc" : "Id Asc"; + + // 排序是否可用-排序字段和排序顺序都为非空才启用排序 + if (!string.IsNullOrEmpty(pageInput.SortField) && !string.IsNullOrEmpty(pageInput.SortOrder)) + { + orderStr = $"{pageInput.SortField} {(pageInput.SortOrder == pageInput.DescStr ? "Desc" : "Asc")}"; + } + + return orderStr; + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Extension/RestfulResultProvider.cs b/src/Znyc.Dispatching.Core/Extension/RestfulResultProvider.cs new file mode 100644 index 0000000..213f814 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Extension/RestfulResultProvider.cs @@ -0,0 +1,159 @@ +using Furion.DataValidation; +using Furion.DependencyInjection; +using Furion.UnifyResult; +using Furion.UnifyResult.Internal; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using System; +using System.Linq; +using System.Threading.Tasks; + +namespace Znyc.Dispatching.Core +{ + /// + /// 规范化RESTful风格返回值 + /// + [SuppressSniffer] + [UnifyModel(typeof(XnRestfulResult<>))] + public class RestfulResultProvider : IUnifyResultProvider + { + /// + /// 异常返回值 + /// + /// + /// + /// + public IActionResult OnException(ExceptionContext context, ExceptionMetadata metadata) + { + return new JsonResult(new XnRestfulResult + { + Code = metadata.StatusCode, + Success = false, + Data = null, + Message = metadata.Errors, + Extras = UnifyContext.Take(), + Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() + }); + } + + /// + /// 成功返回值 + /// + /// + /// + /// + public IActionResult OnSucceeded(ActionExecutedContext context, object data) + { + return new JsonResult(new XnRestfulResult + { + Code = context.Result is EmptyResult + ? StatusCodes.Status204NoContent + : StatusCodes.Status200OK, // 处理没有返回值情况 204 + Success = true, + Data = data, + Message = "", + Extras = UnifyContext.Take(), + Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() + }); + } + + /// + /// 验证失败返回值 + /// + /// + /// + /// + public IActionResult OnValidateFailed(ActionExecutingContext context, ValidationMetadata metadata) + { + return new JsonResult(new XnRestfulResult + { + Code = StatusCodes.Status400BadRequest, + Success = false, + Data = null, + Message = metadata.ValidationResult.First().Value[0], + Extras = UnifyContext.Take(), + Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() + }); + } + + /// + /// 处理输出状态码 + /// + /// + /// + /// + /// + public async Task OnResponseStatusCodes(HttpContext context, int statusCode, UnifyResultSettingsOptions options) + { + // 设置响应状态码 + UnifyContext.SetResponseStatusCodes(context, statusCode, options); + + switch (statusCode) + { + // 处理 401 状态码 + case StatusCodes.Status401Unauthorized: + await context.Response.WriteAsJsonAsync(new XnRestfulResult + { + Code = StatusCodes.Status401Unauthorized, + Success = false, + Data = null, + Message = "401 未经授权", + Extras = UnifyContext.Take(), + Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() + }); + break; + // 处理 403 状态码 + case StatusCodes.Status403Forbidden: + await context.Response.WriteAsJsonAsync(new XnRestfulResult + { + Code = StatusCodes.Status403Forbidden, + Success = false, + Data = null, + Message = "403 禁止访问", + Extras = UnifyContext.Take(), + Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() + }); + break; + } + } + } + + /// + /// RESTful风格---XIAONUO返回格式 + /// + /// + [SuppressSniffer] + public class XnRestfulResult + { + /// + /// 执行成功 + /// + public bool Success { get; set; } + + /// + /// 状态码 + /// + public int? Code { get; set; } + + /// + /// 错误信息 + /// + public object Message { get; set; } + + /// + /// 数据 + /// + public T Data { get; set; } + + /// + /// 附加数据 + /// + public object Extras { get; set; } + + /// + /// 时间戳 + /// + public long Timestamp { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Extension/ReturnPageResult.cs b/src/Znyc.Dispatching.Core/Extension/ReturnPageResult.cs new file mode 100644 index 0000000..a025562 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Extension/ReturnPageResult.cs @@ -0,0 +1,45 @@ +using Mapster; +using System.Collections.Generic; + +namespace Znyc.Dispatching.Core +{ + public class PageResult + { + public int PageNo { get; set; } + public int PageSize { get; set; } + public int TotalPage { get; set; } + public int TotalRows { get; set; } + public ICollection Rows { get; set; } + } + + /// + /// 分页列表结果 + /// + /// + public class ReturnPageResult where T : new() + { + public static dynamic PageResult(PagedList page) + { + return new + { + PageNo = page.PageIndex, + page.PageSize, + TotalPage = page.TotalPages, + TotalRows = page.TotalCount, + Rows = page.Items //.Adapt>(), + }; + } + + public static PageResult PageResult(PagedList page) + { + return new() + { + PageNo = page.PageIndex, + PageSize = page.PageSize, + TotalPage = page.TotalPages, + TotalRows = page.TotalCount, + Rows = page.Items.Adapt>() + }; + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Extension/StringExtensions.cs b/src/Znyc.Dispatching.Core/Extension/StringExtensions.cs new file mode 100644 index 0000000..1f6f32b --- /dev/null +++ b/src/Znyc.Dispatching.Core/Extension/StringExtensions.cs @@ -0,0 +1,110 @@ +using System; +using System.Linq; +using System.Text; +using Znyc.Dispatching.Core.Helpers; + +namespace Znyc.Dispatching.Core.Extension +{ + public static class StringExtensions + { + /// + /// 判断字符串是否为Null、空 + /// + /// + /// + public static bool IsNull(this string s) + { + return string.IsNullOrWhiteSpace(s); + } + + /// + /// 判断字符串是否不为Null、空 + /// + /// + /// + public static bool NotNull(this string s) + { + return !string.IsNullOrWhiteSpace(s); + } + + /// + /// 与字符串进行比较,忽略大小写 + /// + /// + /// + /// + public static bool EqualsIgnoreCase(this string s, string value) + { + return s.Equals(value, StringComparison.OrdinalIgnoreCase); + } + + /// + /// 首字母转小写 + /// + /// + /// + public static string FirstCharToLower(this string s) + { + if (string.IsNullOrEmpty(s)) + { + return s; + } + + string str = s.First().ToString().ToLower() + s.Substring(1); + return str; + } + + /// + /// 首字母转大写 + /// + /// + /// + public static string FirstCharToUpper(this string s) + { + if (string.IsNullOrEmpty(s)) + { + return s; + } + + string str = s.First().ToString().ToUpper() + s.Substring(1); + return str; + } + + /// + /// 转为Base64,UTF-8格式 + /// + /// + /// + public static string ToBase64(this string s) + { + return s.ToBase64(Encoding.UTF8); + } + + /// + /// 转为Base64 + /// + /// + /// 编码 + /// + public static string ToBase64(this string s, Encoding encoding) + { + if (s.IsNull()) + { + return string.Empty; + } + + byte[] bytes = encoding.GetBytes(s); + return bytes.ToBase64(); + } + + public static string ToPath(this string s) + { + if (s.IsNull()) + { + return string.Empty; + } + + return s.Replace(@"\", "/"); + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Files/FileInfo.cs b/src/Znyc.Dispatching.Core/Files/FileInfo.cs new file mode 100644 index 0000000..8f407cf --- /dev/null +++ b/src/Znyc.Dispatching.Core/Files/FileInfo.cs @@ -0,0 +1,82 @@ +using System.IO; +using Znyc.Dispatching.Core.Extension; + +namespace Znyc.Dispatching.Core +{ + /// + /// 文件信息 + /// + public class FileInfo + { + public FileInfo() + { + } + + /// + /// 初始化文件信息 + /// + /// 文件名称 + /// 大小 + public FileInfo(string fileName, long size = 0L) + { + FileName = fileName; + Size = new FileSize(size); + Extension = Path.GetExtension(FileName)?.TrimStart('.'); + } + + /// + /// 上传路径 + /// + public string UploadPath { get; set; } + + /// + /// 请求路径 + /// + public string RequestPath { get; set; } + + /// + /// 相对路径 + /// + public string RelativePath { get; set; } + + /// + /// 文件名 + /// + public string FileName { get; set; } + + /// + /// 保存名 + /// + public string SaveName { get; set; } + + /// + /// 文件大小 + /// + public FileSize Size { get; set; } + + /// + /// 扩展名 + /// + public string Extension { get; set; } + + /// + /// 文件目录 + /// + public string FileDirectory => Path.Combine(UploadPath, RelativePath).ToPath(); + + /// + /// 文件请求路径 + /// + public string FileRequestPath => Path.Combine(RequestPath, RelativePath, SaveName).ToPath(); + + /// + /// 文件相对路径 + /// + public string FileRelativePath => Path.Combine(RelativePath, SaveName).ToPath(); + + /// + /// 文件路径 + /// + public string FilePath => Path.Combine(UploadPath, RelativePath, SaveName).ToPath(); + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Files/FileSize.cs b/src/Znyc.Dispatching.Core/Files/FileSize.cs new file mode 100644 index 0000000..a0ae9f7 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Files/FileSize.cs @@ -0,0 +1,97 @@ +using Znyc.Dispatching.Core.Helpers; + +namespace Znyc.Dispatching.Core +{ + /// + /// 文件大小 + /// + public struct FileSize + { + /// + /// 初始化文件大小 + /// + /// 文件大小 + /// 文件大小单位 + public FileSize(long size, FileSizeUnit unit = FileSizeUnit.Byte) + { + switch (unit) + { + case FileSizeUnit.K: + Size = size * 1024; + break; + + case FileSizeUnit.M: + Size = size * 1024 * 1024; + break; + + case FileSizeUnit.G: + Size = size * 1024 * 1024 * 1024; + break; + + default: + Size = size; + break; + } + } + + /// + /// 文件字节长度 + /// + public long Size { get; } + + /// + /// 获取文件大小,单位:字节 + /// + public long GetSize() + { + return Size; + } + + /// + /// 获取文件大小,单位:K + /// + public double GetSizeByK() + { + return (Size / 1024.0).ToDouble(2); + } + + /// + /// 获取文件大小,单位:M + /// + public double GetSizeByM() + { + return (Size / 1024.0 / 1024.0).ToDouble(2); + } + + /// + /// 获取文件大小,单位:G + /// + public double GetSizeByG() + { + return (Size / 1024.0 / 1024.0 / 1024.0).ToDouble(2); + } + + /// + /// 输出描述 + /// + public override string ToString() + { + if (Size >= 1024 * 1024 * 1024) + { + return $"{GetSizeByG()} {FileSizeUnit.G.ToDescription()}"; + } + + if (Size >= 1024 * 1024) + { + return $"{GetSizeByM()} {FileSizeUnit.M.ToDescription()}"; + } + + if (Size >= 1024) + { + return $"{GetSizeByK()} {FileSizeUnit.K.ToDescription()}"; + } + + return $"{Size} {FileSizeUnit.Byte.ToDescription()}"; + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Files/FileSizeUnit.cs b/src/Znyc.Dispatching.Core/Files/FileSizeUnit.cs new file mode 100644 index 0000000..60628d9 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Files/FileSizeUnit.cs @@ -0,0 +1,30 @@ +using System.ComponentModel; + +namespace Znyc.Dispatching.Core +{ + /// + /// 文件大小单位 + /// + public enum FileSizeUnit + { + /// + /// 字节 + /// + [Description("B")] Byte, + + /// + /// K字节 + /// + [Description("KB")] K, + + /// + /// M字节 + /// + [Description("MB")] M, + + /// + /// G字节 + /// + [Description("GB")] G + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Filter/LogExceptionHandler.cs b/src/Znyc.Dispatching.Core/Filter/LogExceptionHandler.cs new file mode 100644 index 0000000..d1ea1e5 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Filter/LogExceptionHandler.cs @@ -0,0 +1,56 @@ +using Furion; +using Furion.DependencyInjection; +using Furion.FriendlyException; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using System; +using System.Security.Claims; +using System.Threading.Tasks; +using Znyc.Dispatching.Core.Entitys; + +namespace Znyc.Dispatching.Core +{ + /// + /// 全局异常处理 + /// + public class LogExceptionHandler : IGlobalExceptionHandler, ISingleton + { + private readonly ILogger _logger; + + public LogExceptionHandler(ILogger logger) + { + _logger = logger; + } + + public Task OnExceptionAsync(ExceptionContext context) + { + ClaimsPrincipal userContext = App.User; + try + { + var logEX = new LogEx + { + Account = userContext?.FindFirstValue(ClaimConst.CLAINM_ACCOUNT), + Name = userContext?.FindFirstValue(ClaimConst.CLAINM_USERNAME), + ClassName = context.Exception.TargetSite.DeclaringType?.FullName, + MethodName = context.Exception.TargetSite.Name, + ExceptionName = context.Exception.Message, + ExceptionMsg = context.Exception.Message, + ExceptionSource = context.Exception.Source, + StackTrace = context.Exception.StackTrace, + ParamsObj = context.Exception.TargetSite.GetParameters().ToString(), + ExceptionTime = DateTime.Now + }; + _logger.LogError(JsonConvert.SerializeObject(logEX)); + Console.WriteLine($"logEX:{JsonConvert.SerializeObject(logEX)}"); + Console.WriteLine($"InnerException:{context.Exception.InnerException}"); + } + catch (Exception ex) + { + _logger.LogError("LogError", ex.Message); + + } + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Filter/RequestActionFilter.cs b/src/Znyc.Dispatching.Core/Filter/RequestActionFilter.cs new file mode 100644 index 0000000..5a80d08 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Filter/RequestActionFilter.cs @@ -0,0 +1,79 @@ +using Furion; +using Furion.JsonSerialization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.Filters; +using System; +using System.Diagnostics; +using System.Security.Claims; +using System.Threading.Tasks; +using UAParser; +using Znyc.Dispatching.Core.Entitys; +using Znyc.Dispatching.Core.SimpleQueue; + +namespace Znyc.Dispatching.Core +{ + /// + /// 请求日志拦截 + /// + public class RequestActionFilter : IAsyncActionFilter + { + public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) + { + HttpContext httpContext = context.HttpContext; + HttpRequest httpRequest = httpContext.Request; + + Stopwatch sw = new Stopwatch(); + sw.Start(); + ActionExecutedContext actionContext = await next(); + sw.Stop(); + + // 判断是否请求成功(没有异常就是请求成功) + bool isRequestSucceed = actionContext.Exception == null; + IHeaderDictionary headers = httpRequest.Headers; + ClientInfo clientInfo = headers.ContainsKey("User-Agent") + ? Parser.GetDefault().Parse(headers["User-Agent"]) + : null; + ControllerActionDescriptor actionDescriptor = context.ActionDescriptor as ControllerActionDescriptor; + + // 日志写入简单队列 + IConcurrentQueue _logOpQueue = App.GetService>(); + _logOpQueue.Add(new LogOp + { + Name = httpContext.User?.FindFirstValue(ClaimConst.CLAINM_USERNAME), + Success = isRequestSucceed ? YesOrNotEnum.Y : YesOrNotEnum.N, + Ip = httpContext.GetRemoteIpAddressToIPv4(), + Location = httpRequest.GetRequestUrlAddress(), + Browser = clientInfo?.UA.Family + clientInfo?.UA.Major, + Os = clientInfo?.OS.Family + clientInfo?.OS.Major, + Url = httpRequest.Path, + ClassName = context.Controller.ToString(), + MethodName = actionDescriptor.ActionName, + ReqMethod = httpRequest.Method, + Param = JSON.Serialize(context.ActionArguments.Count < 1 ? "" : context.ActionArguments), + //Result = JSON.Serialize(actionContext.Result), // 序列化异常,比如验证码 + ElapsedTime = sw.ElapsedMilliseconds, + OpTime = DateTime.Now, + Account = httpContext.User?.FindFirstValue(ClaimConst.CLAINM_ACCOUNT) + }); + + //MessageCenter.Send("create:oplog", new LogOp + //{ + // Name = httpContext.User?.FindFirstValue(ClaimConst.CLAINM_USERNAME), + // Success = isRequestSucceed ? YesOrNot.Y : YesOrNot.N, + // Ip = httpContext.GetRemoteIpAddressToIPv4(), + // Location = httpRequest.GetRequestUrlAddress(), + // Browser = clientInfo?.UA.Family + clientInfo?.UA.Major, + // Os = clientInfo?.OS.Family + clientInfo?.OS.Major, + // Url = httpRequest.Path, + // ClassName = context.Controller.ToString(), + // MethodName = actionDescriptor?.ActionName, + // ReqMethod = httpRequest.Method, + // Param = JSON.Serialize(context.ActionArguments.Count < 1 ? "" : context.ActionArguments), + // ElapsedTime = sw.ElapsedMilliseconds, + // OpTime = DateTime.Now, + // Account = httpContext.User?.FindFirstValue(ClaimConst.CLAINM_ACCOUNT) + //}); + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Filter/XSSFilterAttribute.cs b/src/Znyc.Dispatching.Core/Filter/XSSFilterAttribute.cs new file mode 100644 index 0000000..c50f60e --- /dev/null +++ b/src/Znyc.Dispatching.Core/Filter/XSSFilterAttribute.cs @@ -0,0 +1,85 @@ +using Microsoft.AspNetCore.Mvc.Filters; +using System; +using Znyc.Dispatching.Core.Helpers; + +namespace Znyc.Dispatching.Core.Filter +{ + public class FieldFilterAttribute : Attribute, IActionFilter + { + private XSSHelper _xSSHelper; + public FieldFilterAttribute() + { + _xSSHelper = new XSSHelper(); + } + + //在Action方法之回之后调用 + public void OnActionExecuted(ActionExecutedContext context) + { + + } + + //在调用Action方法之前调用 + public void OnActionExecuting(ActionExecutingContext context) + { + //获取Action参数集合 + var ps = context.ActionDescriptor.Parameters; + //遍历参数集合 + foreach (var p in ps) + { + if (context.ActionArguments[p.Name] != null) + { + //当参数等于字符串 + if (p.ParameterType.Equals(typeof(string))) + { + context.ActionArguments[p.Name] = _xSSHelper.Filter(context.ActionArguments[p.Name].ToString()); + } + else if (p.ParameterType.IsClass)//当参数等于类 + { + ModelFieldFilter(p.Name, p.ParameterType, context.ActionArguments[p.Name]); + } + } + + } + } + + /// + /// 遍历修改类的字符串属性 + /// + /// 类名 + /// 数据类型 + /// 对象 + /// + private object ModelFieldFilter(string key, Type t, object obj) + { + //获取类的属性集合 + //var ats = t.GetCustomAttributes(typeof(FieldFilterAttribute), false); + + + if (obj != null) + { + //获取类的属性集合 + var pps = t.GetProperties(); + + foreach (var pp in pps) + { + if (pp.GetValue(obj) != null) + { + //当属性等于字符串 + if (pp.PropertyType.Equals(typeof(string))) + { + string value = pp.GetValue(obj).ToString(); + pp.SetValue(obj, _xSSHelper.Filter(value)); + } + else if (pp.PropertyType.IsClass)//当属性等于类进行递归 + { + pp.SetValue(obj, ModelFieldFilter(pp.Name, pp.PropertyType, pp.GetValue(obj))); + } + } + + } + } + + return obj; + } + } +} diff --git a/src/Znyc.Dispatching.Core/Helpers/ByteConvertHelper.cs b/src/Znyc.Dispatching.Core/Helpers/ByteConvertHelper.cs new file mode 100644 index 0000000..5a23dc8 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Helpers/ByteConvertHelper.cs @@ -0,0 +1,45 @@ +using Newtonsoft.Json; +using System.Text; + +namespace Znyc.Dispatching.Core.Helpers +{ + /// + /// byte转换类 + /// + public class ByteConvertHelper + { + /// + /// 将对象转换为byte数组 + /// + /// 被转换对象 + /// 转换后byte数组 + public static byte[] Object2Bytes(object obj) + { + string json = JsonConvert.SerializeObject(obj); + byte[] serializedResult = Encoding.UTF8.GetBytes(json); + return serializedResult; + } + + /// + /// 将byte数组转换成对象 + /// + /// 被转换byte数组 + /// 转换完成后的对象 + public static object Bytes2Object(byte[] buff) + { + string json = Encoding.UTF8.GetString(buff); + return JsonConvert.DeserializeObject(json); + } + + /// + /// 将byte数组转换成对象 + /// + /// 被转换byte数组 + /// 转换完成后的对象 + public static T Bytes2Object(byte[] buff) + { + string json = Encoding.UTF8.GetString(buff); + return JsonConvert.DeserializeObject(json); + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Helpers/ConvertGps.cs b/src/Znyc.Dispatching.Core/Helpers/ConvertGps.cs new file mode 100644 index 0000000..7da30ef --- /dev/null +++ b/src/Znyc.Dispatching.Core/Helpers/ConvertGps.cs @@ -0,0 +1,154 @@ +using System; +using Znyc.Dispatching.Core.MapModel; + +namespace Znyc.Dispatching.Core.Helpers +{ + public class ConvertGps + { + private static readonly double pi = 3.1415926535897932384626; + private static readonly double a = 6378245.0; + private static readonly double ee = 0.00669342162296594323; + private static readonly double bd_pi = 3.14159265358979324 * 3000.0 / 180.0; + + public static bool OutOfChina(double lat, double lon) + { + if (lon < 72.004 || lon > 137.8347) + { + return true; + } + + if (lat < 0.8293 || lat > 55.8271) + { + return true; + } + + return false; + } + + private static double TransformLat(double x, double y) + { + double ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + + 0.2 * Math.Sqrt(Math.Abs(x)); + ret += (20.0 * Math.Sin(6.0 * x * pi) + 20.0 * Math.Sin(2.0 * x * pi)) * 2.0 / 3.0; + ret += (20.0 * Math.Sin(y * pi) + 40.0 * Math.Sin(y / 3.0 * pi)) * 2.0 / 3.0; + ret += (160.0 * Math.Sin(y / 12.0 * pi) + 320 * Math.Sin(y * pi / 30.0)) * 2.0 / 3.0; + return ret; + } + + private static double TransformLon(double x, double y) + { + double ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 + * Math.Sqrt(Math.Abs(x)); + ret += (20.0 * Math.Sin(6.0 * x * pi) + 20.0 * Math.Sin(2.0 * x * pi)) * 2.0 / 3.0; + ret += (20.0 * Math.Sin(x * pi) + 40.0 * Math.Sin(x / 3.0 * pi)) * 2.0 / 3.0; + ret += (150.0 * Math.Sin(x / 12.0 * pi) + 300.0 * Math.Sin(x / 30.0 + * pi)) * 2.0 / 3.0; + return ret; + } + + // 【84】-->【GCJ-02】 + public static PointLatLng Gps84ToGcj02(PointLatLng gpoint) + { + if (OutOfChina(gpoint.Lat, gpoint.Lng)) + { + return new PointLatLng(0, 0); + } + + double dLat = TransformLat(gpoint.Lng - 105.0, gpoint.Lat - 35.0); + double dLon = TransformLon(gpoint.Lng - 105.0, gpoint.Lat - 35.0); + double radLat = gpoint.Lat / 180.0 * pi; + double magic = Math.Sin(radLat); + magic = 1 - ee * magic * magic; + double sqrtMagic = Math.Sqrt(magic); + dLat = dLat * 180.0 / (a * (1 - ee) / (magic * sqrtMagic) * pi); + dLon = dLon * 180.0 / (a / sqrtMagic * Math.Cos(radLat) * pi); + double mgLat = gpoint.Lat + dLat; + double mgLon = gpoint.Lng + dLon; + return new PointLatLng(mgLat, mgLon); + } + + // 【GCJ-02】-->【84】 + public static PointLatLng Gcj02ToGps84(PointLatLng gpoint) + { + PointLatLng gps = Transform(gpoint); + double lontitude = gpoint.Lng * 2 - gps.Lng; + double latitude = gpoint.Lat * 2 - gps.Lat; + return new PointLatLng(latitude, lontitude); + } + + // 【GCJ-02】-->【BD-09】 + public static PointLatLng Gcj02ToBd09(PointLatLng gpoint) + { + double x = gpoint.Lng, y = gpoint.Lat; + double z = Math.Sqrt(x * x + y * y) + 0.00002 * Math.Sin(y * bd_pi); + double theta = Math.Atan2(y, x) + 0.000003 * Math.Cos(x * bd_pi); + double bd_lon = z * Math.Cos(theta) + 0.0065; + double bd_lat = z * Math.Sin(theta) + 0.006; + double resLng = Retain6(bd_lon); + double resLat = Retain6(bd_lat); + return new PointLatLng(resLat, resLng); + } + + // 【BD-09】-->【GCJ-02】 + public static PointLatLng Bd09ToGcj02(PointLatLng bdPoint) + { + double x = bdPoint.Lng - 0.0065, y = bdPoint.Lat - 0.006; + double z = Math.Sqrt(x * x + y * y) - 0.00002 * Math.Sin(y * bd_pi); + double theta = Math.Atan2(y, x) - 0.000003 * Math.Cos(x * bd_pi); + double gg_lon = z * Math.Cos(theta); + double gg_lat = z * Math.Sin(theta); + double resLng = Retain6(gg_lon); + double resLat = Retain6(gg_lat); + return new PointLatLng(resLat, resLng); + } + + // 【BD-09】-->【84】 + public static PointLatLng Bd09ToGps84(PointLatLng bdPoint) + { + PointLatLng gcj02 = Bd09ToGcj02(bdPoint); + PointLatLng map84 = Gcj02ToGps84(gcj02); + //保留小数点后六位 + //map84.Lat = Retain6(map84.Lat); + //map84.Lng = Retain6(map84.Lng); + return map84; + } + + //【84】-->【BD-09】 + public static PointLatLng Gps84ToBd09(PointLatLng gpsPoint) + { + PointLatLng gcj02 = Gps84ToGcj02(gpsPoint); + PointLatLng bd09 = Gcj02ToBd09(gcj02); + return bd09; + } + + private static PointLatLng Transform(PointLatLng gpoint) + { + if (OutOfChina(gpoint.Lat, gpoint.Lng)) + { + return new PointLatLng(gpoint.Lat, gpoint.Lng); + } + + double dLat = TransformLat(gpoint.Lng - 105.0, gpoint.Lat - 35.0); + double dLon = TransformLon(gpoint.Lng - 105.0, gpoint.Lat - 35.0); + double radLat = gpoint.Lat / 180.0 * pi; + double magic = Math.Sin(radLat); + magic = 1 - ee * magic * magic; + double sqrtMagic = Math.Sqrt(magic); + dLat = dLat * 180.0 / (a * (1 - ee) / (magic * sqrtMagic) * pi); + dLon = dLon * 180.0 / (a / sqrtMagic * Math.Cos(radLat) * pi); + double mgLat = gpoint.Lat + dLat; + double mgLon = gpoint.Lng + dLon; + return new PointLatLng(mgLat, mgLon); + } + + /// + /// 保留小数点后六位 + /// + /// + /// + private static double Retain6(double num) + { + return Math.Round(num, 6); + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Helpers/ConvertHelper.cs b/src/Znyc.Dispatching.Core/Helpers/ConvertHelper.cs new file mode 100644 index 0000000..e1d4cfd --- /dev/null +++ b/src/Znyc.Dispatching.Core/Helpers/ConvertHelper.cs @@ -0,0 +1,868 @@ +using System; +using System.Text; +using System.Threading; + +namespace Znyc.Dispatching.Core.Helpers +{ + /// + /// 处理数据类型转换,数制转换、编码转换相关的类 + /// + public sealed class ConvertHelper + { + #region 将byte[]转换成int + + /// + /// 将byte[]转换成int + /// + /// 需要转换成整数的byte数组 + public static int BytesToInt32(byte[] data) + { + //如果传入的字节数组长度小于4,则返回0 + if (data.Length < 4) + { + return 0; + } + + //定义要返回的整数 + int num = 0; + + //如果传入的字节数组长度大于4,需要进行处理 + if (data.Length >= 4) + { + //创建一个临时缓冲区 + byte[] tempBuffer = new byte[4]; + + //将传入的字节数组的前4个字节复制到临时缓冲区 + Buffer.BlockCopy(data, 0, tempBuffer, 0, 4); + + //将临时缓冲区的值转换成整数,并赋给num + num = BitConverter.ToInt32(tempBuffer, 0); + } + + //返回整数 + return num; + } + + #endregion 将byte[]转换成int + + #region 各进制数间转换 + + /// + /// 实现各进制数间的转换。ConvertBase("15",10,16)表示将十进制数15转换为16进制的数。 + /// + /// 要转换的值,即原值 + /// 原值的进制,只能是2,8,10,16四个值。 + /// 要转换到的目标进制,只能是2,8,10,16四个值。 + public static string ConvertBase(string value, int from, int to) + { + if (!isBaseNumber(from)) + { + throw new ArgumentException("参数from只能是2,8,10,16四个值。"); + } + + if (!isBaseNumber(to)) + { + throw new ArgumentException("参数to只能是2,8,10,16四个值。"); + } + + int intValue = Convert.ToInt32(value, from); //先转成10进制 + string result = Convert.ToString(intValue, to); //再转成目标进制 + if (to == 2) + { + int resultLength = result.Length; //获取二进制的长度 + switch (resultLength) + { + case 7: + result = "0" + result; + break; + + case 6: + result = "00" + result; + break; + + case 5: + result = "000" + result; + break; + + case 4: + result = "0000" + result; + break; + + case 3: + result = "00000" + result; + break; + } + } + + return result; + } + + /// + /// 判断是否是 2 8 10 16 + /// + /// + /// + private static bool isBaseNumber(int baseNumber) + { + if (baseNumber == 2 || baseNumber == 8 || baseNumber == 10 || baseNumber == 16) + { + return true; + } + + return false; + } + + #endregion 各进制数间转换 + + #region 使用指定字符集将string转换成byte[] + + /// + /// 将string转换成byte[] + /// + /// 要转换的字符串 + public static byte[] StringToBytes(string text) + { + return Encoding.Default.GetBytes(text); + } + + /// + /// 使用指定字符集将string转换成byte[] + /// + /// 要转换的字符串 + /// 字符编码 + public static byte[] StringToBytes(string text, Encoding encoding) + { + return encoding.GetBytes(text); + } + + #endregion 使用指定字符集将string转换成byte[] + + #region 使用指定字符集将byte[]转换成string + + /// + /// 将byte[]转换成string + /// + /// 要转换的字节数组 + public static string BytesToString(byte[] bytes) + { + return Encoding.Default.GetString(bytes); + } + + /// + /// 使用指定字符集将byte[]转换成string + /// + /// 要转换的字节数组 + /// 字符编码 + public static string BytesToString(byte[] bytes, Encoding encoding) + { + return encoding.GetString(bytes); + } + + #endregion 使用指定字符集将byte[]转换成string + + #region 将数据转换为整型 + + /// + /// 将数据转换为整型 转换失败返回默认值 + /// + /// 数据类型 + /// 数据 + /// 默认值 + /// + public static int ToInt32(T data, int defValue) + { + //如果为空则返回默认值 + if (data == null || Convert.IsDBNull(data)) + { + return defValue; + } + + try + { + return Convert.ToInt32(data); + } + catch + { + return defValue; + } + } + + /// + /// 将数据转换为整型 转换失败返回默认值 + /// + /// 数据 + /// 默认值 + /// + public static int ToInt32(string data, int defValue) + { + //如果为空则返回默认值 + if (string.IsNullOrEmpty(data)) + { + return defValue; + } + + if (int.TryParse(data, out int temp)) + { + return temp; + } + + return defValue; + } + + /// + /// 将数据转换为整型 转换失败返回默认值 + /// + /// 数据 + /// 默认值 + /// + public static int ToInt32(object data, int defValue) + { + //如果为空则返回默认值 + if (data == null || Convert.IsDBNull(data)) + { + return defValue; + } + + try + { + return Convert.ToInt32(data); + } + catch + { + return defValue; + } + } + + #endregion 将数据转换为整型 + + #region 将数据转换为布尔型 + + /// + /// 将数据转换为布尔类型 转换失败返回默认值 + /// + /// 数据类型 + /// 数据 + /// 默认值 + /// + public static bool ToBoolean(T data, bool defValue) + { + //如果为空则返回默认值 + if (data == null || Convert.IsDBNull(data)) + { + return defValue; + } + + try + { + return Convert.ToBoolean(data); + } + catch + { + return defValue; + } + } + + /// + /// 将数据转换为布尔类型 转换失败返回 默认值 + /// + /// 数据 + /// 默认值 + /// + public static bool ToBoolean(string data, bool defValue) + { + //如果为空则返回默认值 + if (string.IsNullOrEmpty(data)) + { + return defValue; + } + + if (bool.TryParse(data, out bool temp)) + { + return temp; + } + + return defValue; + } + + /// + /// 将数据转换为布尔类型 转换失败返回 默认值 + /// + /// 数据 + /// 默认值 + /// + public static bool ToBoolean(object data, bool defValue) + { + //如果为空则返回默认值 + if (data == null || Convert.IsDBNull(data)) + { + return defValue; + } + + try + { + return Convert.ToBoolean(data); + } + catch + { + return defValue; + } + } + + #endregion 将数据转换为布尔型 + + #region 将数据转换为单精度浮点型 + + /// + /// 将数据转换为单精度浮点型 转换失败 返回默认值 + /// + /// 数据类型 + /// 数据 + /// 默认值 + /// + public static float ToFloat(T data, float defValue) + { + //如果为空则返回默认值 + if (data == null || Convert.IsDBNull(data)) + { + return defValue; + } + + try + { + return Convert.ToSingle(data); + } + catch + { + return defValue; + } + } + + /// + /// 将数据转换为单精度浮点型 转换失败返回默认值 + /// + /// 数据 + /// 默认值 + /// + public static float ToFloat(object data, float defValue) + { + //如果为空则返回默认值 + if (data == null || Convert.IsDBNull(data)) + { + return defValue; + } + + try + { + return Convert.ToSingle(data); + } + catch + { + return defValue; + } + } + + /// + /// 将数据转换为单精度浮点型 转换失败返回默认值 + /// + /// 数据 + /// 默认值 + /// + public static float ToFloat(string data, float defValue) + { + //如果为空则返回默认值 + if (string.IsNullOrEmpty(data)) + { + return defValue; + } + + if (float.TryParse(data, out float temp)) + { + return temp; + } + + return defValue; + } + + #endregion 将数据转换为单精度浮点型 + + #region 将数据转换为双精度浮点型 + + /// + /// 将数据转换为双精度浮点型 转换失败返回默认值 + /// + /// 数据的类型 + /// 要转换的数据 + /// 默认值 + /// + public static double ToDouble(T data, double defValue) + { + //如果为空则返回默认值 + if (data == null || Convert.IsDBNull(data)) + { + return defValue; + } + + try + { + return Convert.ToDouble(data); + } + catch + { + return defValue; + } + } + + /// + /// 将数据转换为双精度浮点型,并设置小数位 转换失败返回默认值 + /// + /// 数据的类型 + /// 要转换的数据 + /// 小数的位数 + /// 默认值 + /// + public static double ToDouble(T data, int decimals, double defValue) + { + //如果为空则返回默认值 + if (data == null || Convert.IsDBNull(data)) + { + return defValue; + } + + try + { + return Math.Round(Convert.ToDouble(data), decimals); + } + catch + { + return defValue; + } + } + + /// + /// 将数据转换为双精度浮点型 转换失败返回默认值 + /// + /// 要转换的数据 + /// 默认值 + /// + public static double ToDouble(object data, double defValue) + { + //如果为空则返回默认值 + if (data == null || Convert.IsDBNull(data)) + { + return defValue; + } + + try + { + return Convert.ToDouble(data); + } + catch + { + return defValue; + } + } + + /// + /// 将数据转换为双精度浮点型 转换失败返回默认值 + /// + /// 要转换的数据 + /// 默认值 + /// + public static double ToDouble(string data, double defValue) + { + //如果为空则返回默认值 + if (string.IsNullOrEmpty(data)) + { + return defValue; + } + + if (double.TryParse(data, out double temp)) + { + return temp; + } + + return defValue; + } + + /// + /// 将数据转换为双精度浮点型,并设置小数位 转换失败返回默认值 + /// + /// 要转换的数据 + /// 小数的位数 + /// 默认值 + /// + public static double ToDouble(object data, int decimals, double defValue) + { + //如果为空则返回默认值 + if (data == null || Convert.IsDBNull(data)) + { + return defValue; + } + + try + { + return Math.Round(Convert.ToDouble(data), decimals); + } + catch + { + return defValue; + } + } + + /// + /// 将数据转换为双精度浮点型,并设置小数位 转换失败返回默认值 + /// + /// 要转换的数据 + /// 小数的位数 + /// 默认值 + /// + public static double ToDouble(string data, int decimals, double defValue) + { + //如果为空则返回默认值 + if (string.IsNullOrEmpty(data)) + { + return defValue; + } + + if (double.TryParse(data, out double temp)) + { + return Math.Round(temp, decimals); + } + + return defValue; + } + + #endregion 将数据转换为双精度浮点型 + + #region 将数据转换为指定类型 + + /// + /// 将数据转换为指定类型 + /// + /// 转换的数据 + /// 转换的目标类型 + public static object ConvertTo(object data, Type targetType) + { + if (data == null || Convert.IsDBNull(data)) + { + return null; + } + + Type type2 = data.GetType(); + if (targetType == type2) + { + return data; + } + + if ((targetType == typeof(Guid) || targetType == typeof(Guid?)) && type2 == typeof(string)) + { + if (string.IsNullOrEmpty(data.ToString())) + { + return null; + } + + return new Guid(data.ToString()); + } + + if (targetType.IsEnum) + { + try + { + return Enum.Parse(targetType, data.ToString(), true); + } + catch + { + return Enum.ToObject(targetType, data); + } + } + + if (targetType.IsGenericType) + { + targetType = targetType.GetGenericArguments()[0]; + } + + return Convert.ChangeType(data, targetType); + } + + /// + /// 将数据转换为指定类型 + /// + /// 转换的目标类型 + /// 转换的数据 + public static T ConvertTo(object data) + { + if (data == null || Convert.IsDBNull(data)) + { + return default; + } + + object obj = ConvertTo(data, typeof(T)); + if (obj == null) + { + return default; + } + + return (T)obj; + } + + #endregion 将数据转换为指定类型 + + #region = ChangeType = + + /// + /// + /// + /// + /// + public static object ChangeType(object obj, Type conversionType) + { + return ChangeType(obj, conversionType, Thread.CurrentThread.CurrentCulture); + } + + /// + /// + /// + /// + /// + /// + public static object ChangeType(object obj, Type conversionType, IFormatProvider provider) + { + #region Nullable + + Type nullableType = Nullable.GetUnderlyingType(conversionType); + if (nullableType != null) + { + if (obj == null) + { + return null; + } + + return Convert.ChangeType(obj, nullableType, provider); + } + + #endregion Nullable + + if (typeof(Enum).IsAssignableFrom(conversionType)) + { + return Enum.Parse(conversionType, obj.ToString()); + } + + return Convert.ChangeType(obj, conversionType, provider); + } + + #endregion = ChangeType = + + #region 将数据转换Decimal + + /// + /// 将数据转换为Decimal 转换失败返回默认值 + /// + /// 数据类型 + /// 数据 + /// 默认值 + /// + public static decimal ToDecimal(T data, decimal defValue) + { + //如果为空则返回默认值 + if (data == null || Convert.IsDBNull(data)) + { + return defValue; + } + + try + { + return Convert.ToDecimal(data); + } + catch + { + return defValue; + } + } + + /// + /// 将数据转换为Decimal 转换失败返回 默认值 + /// + /// 数据 + /// 默认值 + /// + public static decimal ToDecimal(object data, decimal defValue) + { + //如果为空则返回默认值 + if (data == null || Convert.IsDBNull(data)) + { + return defValue; + } + + try + { + return Convert.ToDecimal(data); + } + catch + { + return defValue; + } + } + + /// + /// 将数据转换为Decimal 转换失败返回 默认值 + /// + /// 数据 + /// 默认值 + /// + public static decimal ToDecimal(string data, decimal defValue) + { + //如果为空则返回默认值 + if (string.IsNullOrEmpty(data)) + { + return defValue; + } + + if (decimal.TryParse(data, out decimal temp)) + { + return temp; + } + + return defValue; + } + + #endregion 将数据转换Decimal + + #region 将数据转换为DateTime + + /// + /// 将数据转换为DateTime 转换失败返回默认值 + /// + /// 数据类型 + /// 数据 + /// 默认值 + /// + public static DateTime ToDateTime(T data, DateTime defValue) + { + //如果为空则返回默认值 + if (data == null || Convert.IsDBNull(data)) + { + return defValue; + } + + try + { + return Convert.ToDateTime(data); + } + catch + { + return defValue; + } + } + + /// + /// 将数据转换为DateTime 转换失败返回 默认值 + /// + /// 数据 + /// 默认值 + /// + public static DateTime ToDateTime(object data, DateTime defValue) + { + //如果为空则返回默认值 + if (data == null || Convert.IsDBNull(data)) + { + return defValue; + } + + try + { + return Convert.ToDateTime(data); + } + catch + { + return defValue; + } + } + + /// + /// 将数据转换为DateTime 转换失败返回 默认值 + /// + /// 数据 + /// 默认值 + /// + public static DateTime ToDateTime(string data, DateTime defValue) + { + //如果为空则返回默认值 + if (string.IsNullOrEmpty(data)) + { + return defValue; + } + + DateTime temp = DateTime.Now; + + if (DateTime.TryParse(data, out temp)) + { + return temp; + } + + return defValue; + } + + #endregion 将数据转换为DateTime + + #region 半角全角转换 + + /// + /// 转全角的函数(SBC case) + /// + /// 任意字符串 + /// 全角字符串 + /// + /// 全角空格为12288,半角空格为32 + /// 其他字符半角(33-126)与全角(65281-65374)的对应关系是:均相差65248 + /// + public static string ConvertToSBC(string input) + { + //半角转全角: + char[] c = input.ToCharArray(); + for (int i = 0; i < c.Length; i++) + { + if (c[i] == 32) + { + c[i] = (char)12288; + continue; + } + + if (c[i] < 127) + { + c[i] = (char)(c[i] + 65248); + } + } + + return new string(c); + } + + /// 转半角的函数(DBC case) + /// 任意字符串 + /// 半角字符串 + /// + /// 全角空格为12288,半角空格为32 + /// 其他字符半角(33-126)与全角(65281-65374)的对应关系是:均相差65248 + /// + public static string ConvertToDBC(string input) + { + char[] c = input.ToCharArray(); + for (int i = 0; i < c.Length; i++) + { + if (c[i] == 12288) + { + c[i] = (char)32; + continue; + } + + if (c[i] > 65280 && c[i] < 65375) + { + c[i] = (char)(c[i] - 65248); + } + } + + return new string(c); + } + + #endregion 半角全角转换 + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Helpers/DateTimeHelper.cs b/src/Znyc.Dispatching.Core/Helpers/DateTimeHelper.cs new file mode 100644 index 0000000..57c6c1e --- /dev/null +++ b/src/Znyc.Dispatching.Core/Helpers/DateTimeHelper.cs @@ -0,0 +1,47 @@ +using System; + +namespace Znyc.Dispatching.Core.Helpers +{ + /// + /// DateTime帮助类 + /// + public static class DateTimeHelper + { + /// + /// 判断当前时间是否为今天 + /// + /// + /// + public static bool IsToday(DateTime dt) + { + DateTime today = DateTime.Today; + DateTime tempToday = new DateTime(dt.Year, dt.Month, dt.Day); + return today.Equals(tempToday); + } + + + /// + /// 时间转换 + /// + /// + /// + public static string ToChineseDate(int second) + { + int hour = 0; + int minute = 0; + if (second > 60) + { + minute = second / 60; + second = second % 60; + } + if (minute > 60) + { + hour = minute / 60; + minute = minute % 60; + } + return (hour + "小时" + minute + "分钟" + + second + "秒"); + } + + } +} diff --git a/src/Znyc.Dispatching.Core/Helpers/DatetimeJsonConverter.cs b/src/Znyc.Dispatching.Core/Helpers/DatetimeJsonConverter.cs new file mode 100644 index 0000000..2997be5 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Helpers/DatetimeJsonConverter.cs @@ -0,0 +1,74 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Znyc.Dispatching.Core.Helpers +{ + /// + /// 时间类型格式数据处理 + /// + public class DateTimeJsonConverter : JsonConverter + { + /// + /// 时间格式 + /// + private readonly string _dateFormatString; + + /// + /// + public DateTimeJsonConverter() + { + _dateFormatString = "yyyy-MM-dd HH:mm:ss"; + } + + /// + /// + /// 时间格式 + public DateTimeJsonConverter(string dateFormatString) + { + _dateFormatString = dateFormatString; + } + + /// + /// + /// + /// + /// + /// + public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.String) + { + if (DateTime.TryParse(reader.GetString(), out DateTime date)) + { + return date; + } + } + + return reader.GetDateTime(); + } + + /// + /// + /// + /// + /// + public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString("yyyy-MM-dd HH:mm:ss")); + } + } + + public class DateTimeNullableConverter : JsonConverter + { + public override DateTime? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return string.IsNullOrEmpty(reader.GetString()) ? default(DateTime?) : DateTime.Parse(reader.GetString()); + } + + public override void Write(Utf8JsonWriter writer, DateTime? value, JsonSerializerOptions options) + { + writer.WriteStringValue(value?.ToString("yyyy-MM-dd HH:mm:ss")); + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Helpers/HtmlHelper.cs b/src/Znyc.Dispatching.Core/Helpers/HtmlHelper.cs new file mode 100644 index 0000000..9797e00 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Helpers/HtmlHelper.cs @@ -0,0 +1,70 @@ +using System; +using System.Text.RegularExpressions; + +namespace Znyc.Dispatching.Core.Helpers +{ + + /// + /// + /// + public static class HtmlHelper + { + public static string ReplaceHtmlTag(string html, int length = 0) + { + string strText = System.Text.RegularExpressions.Regex.Replace(html, "<[^>]+>", ""); + strText = System.Text.RegularExpressions.Regex.Replace(strText, "&[^;]+;", ""); + + if (length > 0 && strText.Length > length) + return strText.Substring(0, length); + + return strText; + } + + public static string ReplaceHtmlMark(object Contents) + { + string HtmlString = Convert.ToString(Contents); + string[] RegexString = { + @"style='.*?'", + @"class='.*?'", + @"()?", + @"()?", + @"()?", + @"()?", + @"()?", + @"(

    )?", + @"()?", + @"()?", + @"()?", + @"()?", + @"()?", + @"()?", + }; + + + foreach (String str in RegexString) + { + Regex regex = new Regex(str, RegexOptions.IgnoreCase); + HtmlString = regex.Replace(HtmlString, string.Empty); + } + string[] RegexString2 = { + @"", + @"", + @"", + @"

    ", + @"
    ", + @"", + @"", + @"", + @" ", + @"", + }; + foreach (String str2 in RegexString2) + { + Regex regex2 = new Regex(str2, RegexOptions.IgnoreCase); + HtmlString = regex2.Replace(HtmlString, string.Empty); + } + return HtmlString; + } + + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Helpers/HttpContextHelper.cs b/src/Znyc.Dispatching.Core/Helpers/HttpContextHelper.cs new file mode 100644 index 0000000..7a63d33 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Helpers/HttpContextHelper.cs @@ -0,0 +1,27 @@ +using Microsoft.AspNetCore.Http; + +namespace Znyc.Dispatching.Core.Helpers +{ + /// + /// HttpContext帮助类 + /// + public static class HttpContextHelper + { + /// + /// + private static IHttpContextAccessor httpContextAccessor; + + /// + /// + public static HttpContext HttpContext => httpContextAccessor.HttpContext; + + /// + /// 注入 + /// + /// + public static void Configure(IHttpContextAccessor _httpContextAccessor) + { + httpContextAccessor = _httpContextAccessor; + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Helpers/HttpRequestHelper.cs b/src/Znyc.Dispatching.Core/Helpers/HttpRequestHelper.cs new file mode 100644 index 0000000..1ac7258 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Helpers/HttpRequestHelper.cs @@ -0,0 +1,221 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Text; + +namespace Znyc.Dispatching.Core.Helpers +{ + /// + /// http请求类 + /// + public static class HttpRequestHelper + { + /// + /// + /// + /// + public static void FillFormDataStream(this Dictionary formData, Stream stream) + { + string dataString = GetQueryString(formData); + byte[] formDataBytes = formData == null ? new byte[0] : Encoding.UTF8.GetBytes(dataString); + stream.Write(formDataBytes, 0, formDataBytes.Length); + stream.Seek(0, SeekOrigin.Begin); //设置指针读取位置 + } + + /// + /// 组装QueryString的方法 + /// 参数之间用&连接,首位没有符号,如:a=1&b=2&c=3 + /// + /// + /// + public static string GetQueryString(this Dictionary formData) + { + if (formData == null || formData.Count == 0) + { + return ""; + } + + StringBuilder sb = new StringBuilder(); + int i = 0; + foreach (KeyValuePair kv in formData) + { + i++; + sb.AppendFormat("{0}={1}", kv.Key, kv.Value); + if (i < formData.Count) + { + sb.Append("&"); + } + } + + return sb.ToString(); + } + + #region 同步方法 + + /// + /// 使用Get方法获取字符串结果 + /// + /// + /// + /// + /// + public static string HttpGet(string url, Encoding encoding = null, int timeOut = 60000) + { + HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); + request.Method = "GET"; + request.Timeout = timeOut; + HttpWebResponse response = (HttpWebResponse)request.GetResponse(); + using (Stream responseStream = response.GetResponseStream()) + { + using (StreamReader myStreamReader = new StreamReader(responseStream, encoding ?? Encoding.GetEncoding("utf-8"))) + { + string retString = myStreamReader.ReadToEnd(); + return retString; + } + } + } + + /// + /// 使用Post方法获取字符串结果,常规提交 + /// + /// + public static string HttpPost(string url, Dictionary formData = null, Encoding encoding = null, + int timeOut = 60000, Dictionary headers = null) + { + MemoryStream ms = new MemoryStream(); + formData.FillFormDataStream(ms); //填充formData + return HttpPost(url, ms, "application/x-www-form-urlencoded", encoding, headers, timeOut); + } + + /// + /// 发送HttpPost请求,使用JSON格式传输数据 + /// + /// + /// + /// + /// + /// + public static string HttpPost(string url, string postData, Encoding encoding = null, + Dictionary headers = null) + { + if (encoding == null) + { + encoding = Encoding.UTF8; + } + + if (string.IsNullOrWhiteSpace(postData)) + { + throw new ArgumentNullException("postData"); + } + + byte[] data = encoding.GetBytes(postData); + MemoryStream stream = new MemoryStream(); + byte[] formDataBytes = postData == null ? new byte[0] : Encoding.UTF8.GetBytes(postData); + stream.Write(formDataBytes, 0, formDataBytes.Length); + stream.Seek(0, SeekOrigin.Begin); //设置指针读取位置 + return HttpPost(url, stream, "application/json", encoding, headers); + } + + /// + /// 使用POST请求数据,使用JSON传输数据 + /// + /// + /// 传输对象,转换为JSON传输 + /// + /// + /// + public static string HttpPost(string url, object dataObj, Encoding encoding = null, + Dictionary headers = null) + { + if (encoding == null) + { + encoding = Encoding.UTF8; + } + + if (dataObj == null) + { + throw new ArgumentNullException("dataObj"); + } + + string postData = JsonConvert.SerializeObject(dataObj, + new JsonSerializerSettings { DateFormatString = "yyyy-MM-dd HH:mm:ss" }); + byte[] data = encoding.GetBytes(postData); + MemoryStream stream = new MemoryStream(); + byte[] formDataBytes = postData == null ? new byte[0] : Encoding.UTF8.GetBytes(postData); + stream.Write(formDataBytes, 0, formDataBytes.Length); + stream.Seek(0, SeekOrigin.Begin); //设置指针读取位置 + return HttpPost(url, stream, "application/json", encoding, headers); + } + + /// + /// 使用Post方法获取字符串结果 + /// + /// + /// + /// + /// + /// + /// + /// + public static string HttpPost(string url, Stream postStream = null, + string contentType = "application/x-www-form-urlencoded", Encoding encoding = null, + Dictionary headers = null, int timeOut = 60000) + { + HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); + request.Method = "POST"; + request.Timeout = timeOut; + + request.ContentLength = postStream != null ? postStream.Length : 0; + request.Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"; + request.KeepAlive = false; + + request.UserAgent = + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.87 Safari/537.36 QQBrowser/9.2.5748.400"; + request.ContentType = contentType; + + if (headers != null) + { + foreach (KeyValuePair header in headers) + { + request.Headers.Add(header.Key, header.Value); + } + } + + #region 输入二进制流 + + if (postStream != null) + { + postStream.Position = 0; + + //直接写入流 + Stream requestStream = request.GetRequestStream(); + + byte[] buffer = new byte[1024]; + int bytesRead = 0; + while ((bytesRead = postStream.Read(buffer, 0, buffer.Length)) != 0) + { + requestStream.Write(buffer, 0, bytesRead); + } + + postStream.Close(); //关闭文件访问 + } + + #endregion 输入二进制流 + + HttpWebResponse response = (HttpWebResponse)request.GetResponse(); + + using (Stream responseStream = response.GetResponseStream()) + { + using (StreamReader myStreamReader = new StreamReader(responseStream, encoding ?? Encoding.GetEncoding("utf-8"))) + { + string retString = myStreamReader.ReadToEnd(); + return retString; + } + } + } + + #endregion 同步方法 + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Helpers/MapHelper/DistanceHelper.cs b/src/Znyc.Dispatching.Core/Helpers/MapHelper/DistanceHelper.cs new file mode 100644 index 0000000..a1fb8f0 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Helpers/MapHelper/DistanceHelper.cs @@ -0,0 +1,213 @@ +using Newtonsoft.Json; +using System; +using System.IO; +using System.Net; +using System.Text; + +namespace Znyc.Dispatching.Core.Helpers +{ + public class DistanceHelper + { + //根据百度api文档获取两点间最近的行车轨迹距离(非两点的直线距离) + public static decimal GetMileage(decimal originLat, decimal originLng, decimal destinationLat, + decimal destinationLng) + { + //origin_lat = 23.15232300M; + //origin_lng = 113.40876600M; + //destination_lat = 23.06921667M; + //destination_lng = 113.28197816M; + string apiUrl = "http://api.map.baidu.com/routematrix/v2/driving?"; + string apiKey = "tGOVThXXnqNAf0YLCegwSfTzuuYGy8WO"; + string output = "json"; + string origins = originLat + "," + originLng; + string destinations = destinationLat + "," + destinationLng; + string strUrl = string.Format(apiUrl + "output={0}&origins={1}&destinations={2}&tactics=13&&ak={3}", output, + origins, destinations, apiKey); + + HttpWebRequest req = (HttpWebRequest)WebRequest.Create(strUrl); + req.ServicePoint.Expect100Continue = false; + req.Method = "GET"; + req.KeepAlive = true; + req.UserAgent = "Test"; + req.ContentType = "application/x-www-form-urlencoded;charset=utf-8"; + + HttpWebResponse rsp = null; + try + { + rsp = (HttpWebResponse)req.GetResponse(); + + if (rsp.CharacterSet != null) + { + Encoding encoding = Encoding.GetEncoding(rsp.CharacterSet); + string response = GetResponseAsString(rsp, encoding); + //var aa = JsonHelper.FromJsonTo(response); + Mileage dataReust = JsonConvert.DeserializeObject(response); + if (dataReust.status.Equals("0")) //成功 + { + System.Collections.Generic.List items = dataReust.result; + double distance = 0; + foreach (array item in items) + { + if (item.distance.value > distance) + { + distance = item.distance.value; + } + } + + distance = Math.Round(distance * 1e4) / 1e4 / 1000; //将米转换成公里 + decimal result = Convert.ToDecimal(Math.Round(distance, 2)); //四舍五入两位小数 + return result; + } + + return 30; + } + } + catch (WebException) + { + return 30; + } + + return 30; + } + + /// + /// 把响应流转换为文本。 + /// + /// 响应流对象 + /// 编码方式 + /// 响应文本 + public static string GetResponseAsString(HttpWebResponse rsp, Encoding encoding) + { + StringBuilder result = new StringBuilder(); + Stream stream = null; + StreamReader reader = null; + + try + { + // 以字符流的方式读取HTTP响应 + stream = rsp.GetResponseStream(); + if (stream != null) + { + reader = new StreamReader(stream, encoding); + } + + // 每次读取不大于256个字符,并写入字符串 + char[] buffer = new char[256]; + int readBytes = 0; + while (reader != null && (readBytes = reader.Read(buffer, 0, buffer.Length)) > 0) + { + result.Append(buffer, 0, readBytes); + } + } + catch (WebException webEx) + { + if (webEx.Status == WebExceptionStatus.Timeout) + { + result = new StringBuilder(); + } + } + finally + { + // 释放资源 + if (reader != null) + { + reader.Close(); + } + + if (stream != null) + { + stream.Close(); + } + + if (rsp != null) + { + rsp.Close(); + } + } + + return result.ToString(); + } + + /// + /// 计算坐标点的距离,直线距离 + /// + /// 开始的纬度 + /// 开始的经度 + /// 结束的纬度 + /// 结束的经度 + /// 距离(公里) + public static decimal GetDistance(double lat1, double lng1, double lat2, double lng2) + { + //法3 + decimal result = 0; + if (!(lat2 < 0.00000001) && lat2 > -0.00000001 && !(lng2 < 0.00000001) && lng2 > -0.00000001) + { + // 地球的半径 + double earthRadius = 6378137.0; //地球半径(米) + Point begin = new Point(lat1, lng1); + Point end = new Point(lat2, lng2); + + double lat = begin.RadLat - end.RadLat; + double lng = begin.RadLng - end.RadLng; + + double dis = 2 * + Math.Asin( + Math.Sqrt(Math.Pow(Math.Sin(lat / 2), 2) + + Math.Cos(begin.RadLat) * Math.Cos(end.RadLat) * + Math.Pow(Math.Sin(lng / 2), 2))); + dis = dis * earthRadius; + dis = Math.Round(dis * 1e4) / 1e4 / 1000; //将米转换成公里 + result = Convert.ToDecimal(Math.Round(dis, 2)); //四舍五入两位小数 + } + + ////法1 + //double pk = 180 / 3.1415926; + //double a1 = lat1 / pk; + //double a2 = lng1 / pk; + //double b1 = lat2 / pk; + //double b2 = lng2 / pk; + //double t1 = Math.Cos(a1) * Math.Cos(a2) * Math.Cos(b1) * Math.Cos(b2); + //double t2 = Math.Cos(a1) * Math.Sin(a2) * Math.Cos(b1) * Math.Sin(b2); + //double t3 = Math.Sin(a1) * Math.Sin(b1); + //double tt = Math.Acos(t1 + t2 + t3); + //return 6366 * tt; + + //法2 + //double la1 = (Math.PI / 180) * lat1; + //double la2 = (Math.PI / 180) * lat2; + + //double lon1 = (Math.PI / 180) * lng1; + //double lon2 = (Math.PI / 180) * lng2; + + //地球半径 + //double R = 6371; + + //两点间距离 km,如果想要米的话,结果*1000就可以了 + //double d = Math.Acos(Math.Sin(la1) * Math.Sin(la2) + Math.Cos(la1) * Math.Cos(la2) * Math.Cos(lon2 - lon1)) * R; + + //return d * 1000; + + return result; + } + + /// + /// 计算两点之间角度 + /// + /// + /// + /// + /// + /// + public static double GetAngle(double lat1, double lng1, double lat2, double lng2) + { + double y = lng2 - lng1; + double x = lat2 - lat1; + + double angle = Math.Atan2(y, x) * 180.0F / Math.PI; + + angle += 180; + + return angle; + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Helpers/MapHelper/MapHelper.cs b/src/Znyc.Dispatching.Core/Helpers/MapHelper/MapHelper.cs new file mode 100644 index 0000000..68f7413 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Helpers/MapHelper/MapHelper.cs @@ -0,0 +1,353 @@ +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using Znyc.Dispatching.Core.MapModel; + +namespace Znyc.Dispatching.Core.Helpers +{ + public static class MapHelper + { + private static readonly double EARTH_RADIUS = 6378.137; + private const string key = "82c0ac5c67dc9b13a3bb5b8fd4813996"; //高德 + + /// + /// 传入经度纬度 + /// + /// 经度 + /// 纬度 + public static string GetLonLatUrl(string lat, string lon) + { + string LonLat = ""; + string url = "http://api.map.baidu.com/ag/coord/convert?from=0&to=4&x=" + lon + "&y=" + lat + ""; + + HttpWebRequest requst = (HttpWebRequest)WebRequest.Create(url); //http传输协议 + HttpWebResponse respone = (HttpWebResponse)requst.GetResponse(); //活的http的网络资源 + Stream stream = respone.GetResponseStream(); //转换成字节流 + StreamReader sr = new StreamReader(stream, Encoding.GetEncoding("utf-8")); //已utf-8模式读取数据 + string responestr = sr.ReadToEnd(); + sr.Close(); + sr.Dispose(); //释放资源 + string[] responeArr = responestr.Split('\"'); + if (responeArr.Length >= 11) + { + LonLat = GetbyBase64("utf-8", responeArr[5]) + "," + GetbyBase64("utf-8", responeArr[9]); + } + + return LonLat; + } + + /// + /// 解析base64信息数据 + /// + /// 解析编码格式 + /// 传入的base64位值 + /// + public static string GetbyBase64(string code_type, string code) + { + string decode = ""; + byte[] bytes = Convert.FromBase64String(code); + try + { + decode = Encoding.GetEncoding(code_type).GetString(bytes); + } + catch + { + decode = code; + } + + return decode; + } + + /// + /// 根据经纬度获取地图信息(高德) + /// + /// 超时时间默认10秒 + /// 经纬度字符串 + /// 失败返回"" + public static MapLocation GetMapLocation(string strLatLng, int timeout = 10000) + { + string url = + string.Format("http://restapi.amap.com/v3/geocode/regeo?key={0}&batch=true&location={1}", + key, strLatLng); + string apiText = HttpGet(url, timeout); + return JObject.Parse(apiText).ToObject(); + + } + + /// + /// 根据经纬度获取地址,多个 + /// + /// 超时时间默认10秒 + /// 经纬度数组 + /// 1、获取详细地址2、获取市跟区 + /// 失败返回"" + public static List GetLocationByLngLat(List lngLatList, int timeout = 10000, + int type = 1) + { + int idx = 0; + List lstAddress = new List(); + List listLatLng = new List(); + try + { + for (int i = 0; i < lngLatList.Count; i++) + { + LocationModel item = lngLatList[i]; + if (idx < 20 && i < lngLatList.Count - 1) + { + listLatLng.Add(item.LngLatStr); + idx++; + } + else + { + if (i == lngLatList.Count - 1) + { + listLatLng.Add(item.LngLatStr); + } + + if (i < lngLatList.Count - 1) + { + i--; //请求地址时,i递增的话会导致每次请求遗漏1个地址 + } + + idx = 0; + string strLatLng = string.Join("|", listLatLng); + MapLocation mapLocation = GetMapLocation(strLatLng); + if (Convert.ToInt32(mapLocation.Status) == 1) + { + switch (type) + { + case 2: + lstAddress.AddRange( + mapLocation.Regeocodes.Select( + x => + x.AddressComponent.City.ToString().Replace("[]", "").Replace("市", "") + + ( + !string.IsNullOrWhiteSpace( + x.AddressComponent.District.ToString().Replace("[]", "")) + ? x.AddressComponent.District.ToString().Replace("[]", "") + : x.AddressComponent.Township.ToString().Replace("[]", "")) + + "|" + x.AddressComponent.Adcode)); + break; + + default: + lstAddress.AddRange( + mapLocation.Regeocodes.Select( + x => x.FormattedAddress.ToString().Replace("[]", ""))); + break; + } + } + else + { + //TODO 当第一个经纬度错误的时候会失败,之后有空回来解决这个问题 + for (int j = 0; j < listLatLng.Count - 1; j++) + { + lstAddress.Add(""); + } + } + + listLatLng = new List(); + } + } + } + catch (Exception) + { + lstAddress = new List { "未知" }; + } + + return lstAddress; + } + + /// + /// 根据经纬度获取地址,一个 + /// + /// + /// + /// 超时时间默认10秒 + /// + /// 失败返回"" + public static string GetLocationByLngLat(decimal longitude = 0, decimal latitude = 0, int type = 1, + int timeout = 10000) + { + string address = ""; + + string strLatLng = longitude + "," + latitude; + + MapLocation mapLocation = GetMapLocation(strLatLng); + + if (Convert.ToInt32(mapLocation.Status) == 1) + { + Regeocode map = mapLocation.Regeocodes.First(); + switch (type) + { + case 2: + address = map.AddressComponent.City.ToString().Replace("[]", "").Replace("市", "") + + (!string.IsNullOrWhiteSpace(map.AddressComponent.District.ToString() + .Replace("[]", "")) + ? map.AddressComponent.District.ToString().Replace("[]", "") + : map.AddressComponent.Township.ToString().Replace("[]", "")) + + "|" + map.AddressComponent.Adcode; + + break; + + default: + address = map.FormattedAddress.ToString().Replace("[]", ""); + break; + } + } + + return address; + } + + + + /// + /// 获取路径规划路线 + /// + /// 起点经度 + /// 起点纬度 + /// 终点经度 + /// 终点纬度 + /// 超时时间默认10秒 + /// 失败返回"" + public static List GetRouteList(decimal startLongitude, decimal startLatitude, + decimal endLongitude, decimal endLatitude, int timeout = 10000) + { + try + { + string origin = startLongitude + "," + startLatitude; + + string destination = endLongitude + "," + endLatitude; + + PathPlanning pathPlanning = GetPathPlanning(origin, destination); + + List list = new List(); + + if (Convert.ToInt32(pathPlanning.Status) == 1) + { + MapModel.Path path = pathPlanning.Routes.Paths.FirstOrDefault(); + if (path != null) + { + string[] lineList = string.Join(";", path.Steps.Select(x => x.Polyline)).Split(';'); + + list.AddRange( + lineList.Select( + x => + new PointLatLng + (Convert.ToDouble(x.Split(',')[1]), Convert.ToDouble(x.Split(',')[0])))); + } + + return list; + } + } + catch (Exception) + { + return new List(); + } + + return new List(); + } + + /// + /// 根据起点终点经纬度获取路径规划(步行)(高德) + /// + /// 终点经纬度 + /// 超时时间默认10秒 + /// 起点经纬度 + /// 失败返回"" + public static PathPlanning GetPathPlanning(string origin, string destination, int timeout = 10000) + { + try + { + string url = + string.Format("https://restapi.amap.com/v3/direction/walking?key={0}&origin={1}&destination={2}", + key, origin, destination); + + string apiText = HttpGet(url, timeout); + return JObject.Parse(apiText).ToObject(); + } + catch (Exception) + { + return new PathPlanning(); + } + } + + public static string HttpGet(string url, int timeout) + { + HttpWebRequest req = WebRequest.Create(url) as HttpWebRequest; + req.ContentType = "multipart/form-data"; + req.Accept = "*/*"; + req.UserAgent = ""; + req.Timeout = timeout; + req.Method = "GET"; + req.KeepAlive = true; + HttpWebResponse response = req.GetResponse() as HttpWebResponse; + string apiText = ""; + using (StreamReader sr = new StreamReader(response.GetResponseStream(), Encoding.UTF8)) + { + apiText = sr.ReadToEnd(); + } + + return apiText; + } + + + /** + * 计算两个经纬度之间的距离 + * @param lat1 + * @param lng1 + * @param lat2 + * @param lng2 + * @return + */ + public static double GetDistance(double lat1, double lng1, double lat2, double lng2) + { + double radLat1 = rad(lat1); + double radLat2 = rad(lat2); + double a = radLat1 - radLat2; + double b = rad(lng1) - rad(lng2); + double s = 2 * Math.Asin(Math.Sqrt(Math.Pow(Math.Sin(a / 2), 2) + + Math.Cos(radLat1) * Math.Cos(radLat2) * Math.Pow(Math.Sin(b / 2), 2))); + s = s * EARTH_RADIUS; + s = Math.Round(s * 1000); + return s; + } + + + private static double rad(double d) + { + return d * Math.PI / 180.0; + } + + + #region v1.2.7获取路径规划 + /// + /// 根据起点终点经纬度获取路径规划(驾车)(高德) + /// + /// 终点经纬度 + /// 超时时间默认10秒 + /// 起点经纬度 + /// 失败返回"" + public static PathPlanningModel GetPathPlanningByDriving(string origin, string destination, int timeout = 10000) + { + try + { + string url = + string.Format("https://restapi.amap.com/v3/direction/driving?origin={1}&destination={2}&output=json&extensions=base&key={0}", + key, origin, destination); + + string apiText = HttpGet(url, timeout); + return JObject.Parse(apiText).ToObject(); + } + catch (Exception) + { + return new PathPlanningModel(); + } + } + #endregion + + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Helpers/MapHelper/MapModel/LocationModel.cs b/src/Znyc.Dispatching.Core/Helpers/MapHelper/MapModel/LocationModel.cs new file mode 100644 index 0000000..033d202 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Helpers/MapHelper/MapModel/LocationModel.cs @@ -0,0 +1,20 @@ +namespace Znyc.Dispatching.Core.MapModel +{ + public class LocationModel + { + public LocationModel() + { + } + + public LocationModel(decimal longitude, decimal latitude) + { + Latitude = latitude; + Longitude = longitude; + } + + public decimal Latitude { get; set; } + public decimal Longitude { get; set; } + + public string LngLatStr => Longitude + "," + Latitude; + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Helpers/MapHelper/MapModel/MapLocation.cs b/src/Znyc.Dispatching.Core/Helpers/MapHelper/MapModel/MapLocation.cs new file mode 100644 index 0000000..5471369 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Helpers/MapHelper/MapModel/MapLocation.cs @@ -0,0 +1,59 @@ +using Newtonsoft.Json; +using System.Collections.Generic; + +namespace Znyc.Dispatching.Core.MapModel +{ + public class MapLocation + { + [JsonProperty("status")] public object Status { get; set; } + + [JsonProperty("info")] public object Info { get; set; } + + [JsonProperty("infocode")] public object Infocode { get; set; } + + [JsonProperty("regeocodes")] public List Regeocodes { get; set; } + + [JsonProperty("regeocode")] public Regeocode Regeocode { get; set; } + } + + public class Regeocode + { + [JsonProperty("formatted_address")] public object FormattedAddress { get; set; } + + [JsonProperty("addressComponent")] public AddressComponent AddressComponent { get; set; } + } + + public class AddressComponent + { + [JsonProperty("country")] public object Country { get; set; } + + [JsonProperty("province")] public object Province { get; set; } + + [JsonProperty("city")] public object City { get; set; } + + [JsonProperty("citycode")] public object Citycode { get; set; } + + [JsonProperty("district")] public object District { get; set; } + + [JsonProperty("adcode")] public object Adcode { get; set; } + + [JsonProperty("township")] public object Township { get; set; } + + [JsonProperty("townCode")] public object TownCode { get; set; } + + [JsonProperty("streetNumber")] public StreetNumber StreetNumber { get; set; } + } + + public class StreetNumber + { + [JsonProperty("street")] public object Street { get; set; } + + [JsonProperty("number")] public object Number { get; set; } + + [JsonProperty("location")] public object Location { get; set; } + + [JsonProperty("direction")] public object Direction { get; set; } + + [JsonProperty("distance")] public object Distance { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Helpers/MapHelper/MapModel/Mileage.cs b/src/Znyc.Dispatching.Core/Helpers/MapHelper/MapModel/Mileage.cs new file mode 100644 index 0000000..e8d2fe6 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Helpers/MapHelper/MapModel/Mileage.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; + +namespace Znyc.Dispatching.Core.Helpers +{ + public class Mileage + { + //public Mileage(string status, string message) + //{ + // this.Status = status; + // this.Message = message; + //} + public List result { get; set; } + + /// + /// + public string status { get; set; } + + /// + /// + public string message { get; set; } + } + + public class array + { + public origin_destination distance { get; set; } + public origin_destination duration { get; set; } + } + + public class origin_destination + { + public string text { get; set; } + public double value { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Helpers/MapHelper/MapModel/PathPlanning.cs b/src/Znyc.Dispatching.Core/Helpers/MapHelper/MapModel/PathPlanning.cs new file mode 100644 index 0000000..2eae202 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Helpers/MapHelper/MapModel/PathPlanning.cs @@ -0,0 +1,120 @@ +using Newtonsoft.Json; +using System.Collections.Generic; + +namespace Znyc.Dispatching.Core.MapModel +{ + /// + /// 路径规划实体 + /// + public class PathPlanning + { + /// + /// 返回状态 + /// 值为0或1 1:成功;0:失败 + /// + [JsonProperty("status")] + public object Status { get; set; } + + /// + /// 返回的状态信息 + /// status为0时,info返回错误原;否则返回“OK”。详情参阅info状态表 + /// + [JsonProperty("info")] + public object Info { get; set; } + + /// + /// 返回结果总数目 + /// + [JsonProperty("count")] + public object Count { get; set; } + + /// + /// 路线信息列表 + /// + [JsonProperty("route")] + public Route Routes { get; set; } + } + + public class Route + { + /// + /// 起点坐标 + /// + [JsonProperty("origin")] + public object Origin { get; set; } + + /// + /// 终点坐标 + /// + [JsonProperty("destination")] + public object Destination { get; set; } + + /// + /// 步行方案 + /// + [JsonProperty("paths")] + public List Paths { get; set; } + } + + public class Path + { + /// + /// 起点和终点的步行距离 + /// 单位:米 + /// + [JsonProperty("distance")] + public object Distance { get; set; } + + /// + /// 步行时间预计 + /// 单位:秒 + /// + [JsonProperty("duration")] + public object Duration { get; set; } + + /// + /// 返回步行结果列表 + /// + [JsonProperty("steps")] + public List Steps { get; set; } + } + + public class Step + { + /// + /// 每段步行方案 + /// + [JsonProperty("instruction")] + public object Instruction { get; set; } + + /// + /// 方向 + /// + [JsonProperty("orientation")] + public object Orientation { get; set; } + + /// + /// 道路名称 + /// + [JsonProperty("road")] + public object Road { get; set; } + + /// + /// 此路段距离 + /// + [JsonProperty("distance")] + public object Distance { get; set; } + + /// + /// 此路段预计步行时间 + /// + [JsonProperty("duration")] + public object Duration { get; set; } + + /// + /// 此路段坐标点 + /// + [JsonProperty("polyline")] + public object Polyline { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Helpers/MapHelper/MapModel/PathPlanningModel.cs b/src/Znyc.Dispatching.Core/Helpers/MapHelper/MapModel/PathPlanningModel.cs new file mode 100644 index 0000000..00651c1 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Helpers/MapHelper/MapModel/PathPlanningModel.cs @@ -0,0 +1,86 @@ +using System.Collections.Generic; + +namespace Znyc.Dispatching.Core.Helpers +{ + + /// + /// 路径规划实体 + /// + public class PathPlanningModel + { + /// + /// 结果状态值,值为0或1 + /// + public string status { get; set; } + + /// + /// 返回状态说明 + /// + public string info { get; set; } + + + /// + /// + /// + public string infocode { get; set; } + /// + /// 驾车路径规划方案数目 + /// + public string count { get; set; } + + /// + /// 驾车路径规划信息列表 + /// + public Route route { get; set; } + + } + + /// + /// 驾车路径规划信息列表 + /// + public class Route + { + /// + /// 起点坐标 + /// + public string origin { get; set; } + /// + /// 终点坐标 + /// + public string destination { get; set; } + + /// + /// 驾车换乘方案 + /// + public List paths { get; set; } + } + + + /// + /// 驾车换乘方案 + /// + public class Paths + { + /// + /// 行驶距离 + /// + public string distance { get; set; } + + /// + /// 预计行驶时间 + /// + public string duration { get; set; } + + /// + /// 导航策略 + /// + public string strategy { get; set; } + + /// + /// 此导航方案道路收费 + /// + public string tolls { get; set; } + + } +} + diff --git a/src/Znyc.Dispatching.Core/Helpers/MapHelper/MapModel/Point.cs b/src/Znyc.Dispatching.Core/Helpers/MapHelper/MapModel/Point.cs new file mode 100644 index 0000000..9c712e6 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Helpers/MapHelper/MapModel/Point.cs @@ -0,0 +1,29 @@ +using System; + +namespace Znyc.Dispatching.Core.Helpers +{ + public class Point + { + /// 纬度 X + /// 经度 Y + public Point(double lat, double lng) + { + Lat = lat; + Lng = lng; + } + + /// + /// 代表纬度 X轴 + /// + public double Lat { set; get; } + + /// + /// 代表经度 Y轴 d + /// + public double Lng { get; set; } + + public double RadLat => Lat * Math.PI / 180; + + public double RadLng => Lng * Math.PI / 180; + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Helpers/MapHelper/MapModel/PointLatLng.cs b/src/Znyc.Dispatching.Core/Helpers/MapHelper/MapModel/PointLatLng.cs new file mode 100644 index 0000000..00bbb6e --- /dev/null +++ b/src/Znyc.Dispatching.Core/Helpers/MapHelper/MapModel/PointLatLng.cs @@ -0,0 +1,18 @@ +namespace Znyc.Dispatching.Core.MapModel +{ + public class PointLatLng + { + public PointLatLng() + { + } + + public PointLatLng(double lat, double lng) + { + Lat = lat; + Lng = lng; + } + + public double Lat { get; set; } + public double Lng { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Helpers/RegexHelper.cs b/src/Znyc.Dispatching.Core/Helpers/RegexHelper.cs new file mode 100644 index 0000000..ec6af20 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Helpers/RegexHelper.cs @@ -0,0 +1,257 @@ +using System; +using System.Globalization; +using System.Text.RegularExpressions; + +namespace Znyc.Dispatching.Core.Helpers +{ + + /// + /// 正则帮助类。含大量常用正则表达式。 + /// + public static class RegexHelper + { + /// + /// + /// + /// + /// + public static bool IsMobilePhone(string str) + { + return Regex.IsMatch(str, @"(^0?1[3|4|5|7|8][0-9]\d{8}$)"); + } + /// + /// + /// + /// + /// + public static bool IsBase64String(string str) + { + return Regex.IsMatch(str, @"[A-Za-z0-9\+\/\=]"); + } + + public static bool IsDate(string date) + { + if (string.IsNullOrEmpty(date)) + { + return false; + } + DateTime minValue = DateTime.MinValue; + return DateTime.TryParse(date, out minValue); + } + + public static bool IsDate(string date, string format) + { + return IsDate(date, format, null, DateTimeStyles.None); + } + + public static bool IsDate(string date, string format, IFormatProvider provider, DateTimeStyles styles) + { + if (string.IsNullOrEmpty(date)) + { + return false; + } + DateTime minValue = DateTime.MinValue; + return DateTime.TryParseExact(date, format, provider, styles, out minValue); + } + + public static bool IsEmail(string email) + { + if (string.IsNullOrEmpty(email)) + { + return false; + } + string pattern = @"^([0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*@([0-9a-zA-Z][-\w]*[0-9a-zA-Z]\.)+[a-zA-Z]{2,9})$"; + return Regex.IsMatch(email.Trim(), pattern); + } + /// + /// + /// + /// + /// + public static bool IsGuid(string guid) + { + if (string.IsNullOrEmpty(guid)) + { + return false; + } + return Regex.IsMatch(guid, "[A-F0-9]{8}(-[A-F0-9]{4}){3}-[A-F0-9]{12}|[A-F0-9]{32}", RegexOptions.IgnoreCase); + } + /// + /// + /// + /// + /// + public static bool IsIdCard(string idCard) + { + if (string.IsNullOrEmpty(idCard)) + { + return false; + } + if (idCard.Length == 15) + { + return Regex.IsMatch(idCard, @"^[1-9]\d{7}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}$"); + } + return ((idCard.Length == 0x12) && Regex.IsMatch(idCard, @"^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])((\d{4})|\d{3}[A-Z])$", RegexOptions.IgnoreCase)); + } + /// + /// + /// + /// + /// + public static bool IsInt(object number) + { + if (IsNullOrEmpty(number)) + { + return false; + } + return IsInt(number.ToString()); + } + /// + /// + /// + /// + /// + public static bool IsInt(string number) + { + if (string.IsNullOrEmpty(number)) + { + return false; + } + int result = 0; + return int.TryParse(number, out result); + } + + public static bool IsIP(string ip) + { + if (string.IsNullOrEmpty(ip)) + { + return false; + } + string pattern = @"^((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)$"; + return Regex.IsMatch(ip.Trim(), pattern); + } + /// + /// + /// + /// + /// + public static bool IsNullOrEmpty(object data) + { + return ((data == null) || (((data.GetType() == typeof(string)) && string.IsNullOrEmpty(data.ToString().Trim())) || (data.GetType() == typeof(DBNull)))); + } + /// + /// + /// + /// + /// + public static bool IsNumber(object number) + { + if (IsNullOrEmpty(number)) + { + return false; + } + return IsNumber(number.ToString()); + } + /// + /// + /// + /// + /// + public static bool IsNumber(string number) + { + if (string.IsNullOrEmpty(number)) + { + return false; + } + decimal result = 0M; + return decimal.TryParse(number, out result); + } + /// + /// + /// + /// + /// + public static bool IsUrl(string strUrl) + { + return Regex.IsMatch(strUrl, @"^(http|https)\://([a-zA-Z0-9\.\-]+(\:[a-zA-Z0-9\.&%\$\-]+)*@)*((25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])|localhost|([a-zA-Z0-9\-]+\.)*[a-zA-Z0-9\-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{1,10}))(\:[0-9]+)*(/($|[a-zA-Z0-9\.\,\?\'\\\+&%\$#\=~_\-]+))*$", RegexOptions.IgnoreCase); + } + /// + /// 判断是否是mac地址 + /// + /// mac地址字符串 + /// + public static bool IsMacAddress(string mac) + { + return Regex.IsMatch(mac, "^([0-9A-F]{2}-){5}[0-9A-F]{2}$") || Regex.IsMatch(mac, "^[0-9A-F]{12}$"); + } + + /// + /// 获取字节数 + /// str:需要获取的字符串 + /// + public static int Length(string str) + { + if (string.IsNullOrWhiteSpace(str)) + { + return 0; + } + int j = 0; + CharEnumerator ce = str.GetEnumerator(); + while (ce.MoveNext()) + { + j += (ce.Current > 0 && ce.Current < 255) ? 1 : 2; + } + return j; + } + + + /// + /// + /// + /// + public static string[] ReturnPhones(string value) + { + string pattern = @"(^|\s*\+?0?0?86|\D)(1\d{2})[-\s]{0,3}(\d{4})[-\s]{0,3}(\d{4})(?=\D|$)"; + MatchCollection matchCol = Regex.Matches(value.Trim(), pattern); + + string[] result = new string[matchCol.Count]; + if (matchCol.Count > 0) + { + for (int i = 0; i < matchCol.Count; i++) + { + string matchColValue = Regex.Replace(matchCol[i].Value, @"[^0-9]+", ""); + result[i] = matchColValue; + } + } + return result; + } + + /// + /// 判断是否字母 + /// + public static bool IsLetter(string value) + { + if (string.IsNullOrEmpty(value)) + { + return false; + } + string pattern = @"^[a-zA-Z]$"; + return Regex.IsMatch(value.Trim(), pattern); + } + + + /// + /// 判断是否汉字 + /// + public static bool IsChinese(string value) + { + if (string.IsNullOrEmpty(value)) + { + return false; + } + string pattern = @"^[\u4e00-\u9fa5]$"; + return Regex.IsMatch(value.Trim(), pattern); + + } + } +} diff --git a/src/Znyc.Dispatching.Core/Helpers/SmsHelper.cs b/src/Znyc.Dispatching.Core/Helpers/SmsHelper.cs new file mode 100644 index 0000000..4c49bca --- /dev/null +++ b/src/Znyc.Dispatching.Core/Helpers/SmsHelper.cs @@ -0,0 +1,68 @@ +using Furion; +using System; +using System.Threading.Tasks; +using TencentCloud.Common; +using TencentCloud.Common.Profile; +using TencentCloud.Sms.V20190711; +using TencentCloud.Sms.V20190711.Models; + +namespace Znyc.Dispatching.Core.Helpers +{ + /// + /// 短信服务 + /// + public static class SmsHelper + { + /// + /// 发送短信 + /// + /// +86格式 + /// + /// + /// + public static async Task SendSmsAsync(string[] phone, string[] param, string templateId) + { + clientProfile.HttpProfile = httpProfile; + SmsClient client = new SmsClient(cred, App.Configuration["SMSProvider:Region"], clientProfile); + SendSmsRequest req = new SendSmsRequest + { + SmsSdkAppid = App.Configuration["SMSProvider:SmsSdkAppid"], + Sign = App.Configuration["SMSProvider:Sign"], + SenderId = "", + PhoneNumberSet = phone, + TemplateID = templateId, + TemplateParamSet = param + }; + SendSmsResponse rep = await client.SendSms(req); + if (rep.SendStatusSet[0].Code.ToLower() != "ok") + { + return false; + } + + return true; + } + + #region 公共信息 + + private static readonly Credential cred = new() + { + SecretId = App.Configuration["SMSProvider:SecretId"], + SecretKey = App.Configuration["SMSProvider:SecretKey"] + }; + + private static readonly ClientProfile clientProfile = new() + { + SignMethod = ClientProfile.SIGN_TC3SHA256 + }; + + private static readonly HttpProfile httpProfile = new() + { + ReqMethod = "GET", + Timeout = 10, // 请求连接超时时间,单位为秒(默认60秒) + Endpoint = "sms.tencentcloudapi.com", + WebProxy = Environment.GetEnvironmentVariable("HTTPS_PROXY") + }; + + #endregion 公共信息 + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Helpers/Snowflake.cs b/src/Znyc.Dispatching.Core/Helpers/Snowflake.cs new file mode 100644 index 0000000..bff6360 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Helpers/Snowflake.cs @@ -0,0 +1,155 @@ +using System; + +namespace Znyc.Dispatching.Core.Helpers +{ + public class Snowflake + { + //private const long START_STMP = 1480166465631L; + /*每一部分占用的位数*/ + + //机器标识位数 + private const int MachineIdBits = 5; + + //数据标志位数 + private const int DatacenterIdBits = 5; + + //序列号识位数 + private const int SequenceBits = 12; + + /* 每一部分的最大值*/ + + //机器ID最大值 + private const long MaxMachineNum = -1L ^ (-1L << MachineIdBits); + + //数据标志ID最大值 + private const long MaxDatacenterNum = -1L ^ (-1L << DatacenterIdBits); + + //序列号ID最大值 + private const long MaxSequenceNum = -1L ^ (-1L << SequenceBits); + + /*每一部分向左的位移*/ + + //机器ID偏左移12位 + private const int MachineShift = SequenceBits; + + //数据ID偏左移17位 + private const int DatacenterIdShift = SequenceBits + MachineIdBits; + + //时间毫秒左移22位 + public const int TimestampLeftShift = SequenceBits + MachineIdBits + DatacenterIdBits; + + //基准时间 + private static readonly long StartStmp = 1288834974657L; + + private readonly object _lock = new(); + //public long Sequence = 0L;//序列号 + //{ + // get { return _sequence; } + // internal set { _sequence = value; } + //} + + private readonly DateTime Jan1st1970 = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + private long _lastTimestamp = -1L; //上一次时间戳 + + private long _sequence; //序列号 + + public Snowflake(long machineId, long datacenterId) + { + // 如果超出范围就抛出异常 + if (machineId > MaxMachineNum || machineId < 0) + { + throw new ArgumentException(string.Format("machineId 必须大于0,MaxMachineNum: {0}", MaxMachineNum)); + } + + if (datacenterId > MaxDatacenterNum || datacenterId < 0) + { + throw new ArgumentException(string.Format("datacenterId必须大于0,且不能大于MaxDatacenterNum: {0}", + MaxDatacenterNum)); + } + + //先检验再赋值 + MachineId = machineId; + DatacenterId = datacenterId; + //_sequence = sequence; + } + + public long MachineId { get; protected set; } //机器标识 + public long DatacenterId { get; protected set; } //数据中心 + + //public static Init(long machineId, long datacenterId) + //{ + //} + public long NextId() + { + lock (_lock) + { + long timestamp = TimeGen(); + if (timestamp < _lastTimestamp) + { + throw new Exception(string.Format("时间戳必须大于上一次生成ID的时间戳. 拒绝为{0}毫秒生成id", _lastTimestamp - timestamp)); + } + + //如果上次生成时间和当前时间相同,在同一毫秒内 + if (_lastTimestamp == timestamp) + { + //sequence自增,和sequenceMask相与一下,去掉高位 + _sequence = (_sequence + 1) & MaxSequenceNum; + //判断是否溢出,也就是每毫秒内超过1024,当为1024时,与sequenceMask相与,sequence就等于0 + if (_sequence == 0L) + { + //等待到下一毫秒 + timestamp = TilNextMillis(_lastTimestamp); + } + } + else + { + //如果和上次生成时间不同,重置sequence,就是下一毫秒开始,sequence计数重新从0开始累加, + //为了保证尾数随机性更大一些,最后一位可以设置一个随机数 + _sequence = 0L; //new Random().Next(10); + } + + _lastTimestamp = timestamp; + return ((timestamp - StartStmp) << TimestampLeftShift) | (DatacenterId << DatacenterIdShift) | + (MachineId << MachineShift) | _sequence; + } + } + + // 防止产生的时间比之前的时间还要小(由于NTP回拨等问题),保持增量的趋势. + protected virtual long TilNextMillis(long lastTimestamp) + { + long timestamp = TimeGen(); + while (timestamp <= lastTimestamp) + { + timestamp = TimeGen(); + } + + return timestamp; + } + + // 获取当前的时间戳 + protected virtual long TimeGen() + { + //return TimeExtensions.CurrentTimeMillis(); + return (long)(DateTime.UtcNow - Jan1st1970).TotalMilliseconds; + } + } + + public class IdWorkerHelper + { + private static readonly Snowflake _idWorker; + + static IdWorkerHelper() + { + _idWorker = new Snowflake(1, 1); + } + + private IdWorkerHelper() + { + } + + public static long GenId64() + { + return _idWorker.NextId(); + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Helpers/StringHelper.cs b/src/Znyc.Dispatching.Core/Helpers/StringHelper.cs new file mode 100644 index 0000000..120a603 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Helpers/StringHelper.cs @@ -0,0 +1,244 @@ +using hyjiacan.py4n; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; +using System.Text.RegularExpressions; +using Znyc.Dispatching.Core.Extension; + +namespace Znyc.Dispatching.Core.Helpers +{ + /// + /// 字符串帮助类 + /// + public static class StringHelper + { + private static readonly char[] _constant = + { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' + }; + + + /// + /// 生成随机字符串,默认32位 + /// + /// 随机数长度 + /// + public static string GenerateRandom(int length = 32) + { + StringBuilder newRandom = new StringBuilder(); + Random rd = new Random(); + for (int i = 0; i < length; i++) + { + newRandom.Append(_constant[rd.Next(_constant.Length)]); + } + + return newRandom.ToString(); + } + + /// + /// 生成随机字符串,只包含数字 + /// + /// + /// + public static string GenerateRandomNumber(int length = 6) + { + StringBuilder newRandom = new StringBuilder(); + Random rd = new Random(); + for (int i = 0; i < length; i++) + { + newRandom.Append(_constant[rd.Next(10)]); + } + + return newRandom.ToString(); + } + + public static string Format(string str, object obj) + { + if (str.IsNull()) + { + return str; + } + + string s = str; + if (obj.GetType().Name == "JObject") + { + foreach (KeyValuePair item in (JObject)obj) + { + string k = item.Key; + string v = item.Value.ToString(); + s = Regex.Replace(s, "\\{" + k + "\\}", v, RegexOptions.IgnoreCase); + } + } + else + { + foreach (PropertyInfo p in obj.GetType().GetProperties()) + { + string xx = p.Name; + string yy = p.GetValue(obj).ToString(); + s = Regex.Replace(s, "\\{" + xx + "\\}", yy, RegexOptions.IgnoreCase); + } + } + + return s; + } + + /// + /// 隐藏手机号 + /// + /// + /// + public static string ConvertPhoneNo(string Phone) + { + return Regex.Replace(Phone, "(\\d{3})\\d{4}(\\d{4})", "$1****$2"); + } + + /// + /// 隐藏身份证号 + /// + /// + /// + public static string ConvertIdCard(string idCard) + { + return Regex.Replace(idCard, "(\\d{6})\\d{8}(\\w{4})", "$1********$2"); + } + + public static string ToChineseMainlandForPhone(this string phone) + { + return string.Format("+86{0}", phone); + } + + /// + /// 计算时间间隔 + /// + /// + /// + public static string ShowTimeInterval(DateTime? startTime) + { + TimeSpan ts = DateTime.Now - startTime.ToDateTime(); + string time = ""; + if (ts.Days > 0) + { + time = string.Format(ts.Days + "天" + ts.Hours + "小时" + ts.Minutes + "分钟"); + } + + if (ts.Hours > 0 && ts.Days == 0) + { + time = string.Format(ts.Hours + "小时" + ts.Minutes + "分钟"); + } + + if (ts.Hours == 0 && ts.Days == 0) + { + time = string.Format(ts.Minutes + "分钟"); + } + + return time; + } + + /// + /// 将对象属性转换为key-value对 + /// + /// + /// + public static Dictionary ToMap(object o) + { + Dictionary map = new Dictionary(); + Type t = o.GetType(); + PropertyInfo[] pi = t.GetProperties(BindingFlags.Public | BindingFlags.Instance); + foreach (PropertyInfo p in pi) + { + MethodInfo mi = p.GetGetMethod(); + if (mi != null && mi.IsPublic) + { + map.Add(p.Name, mi.Invoke(o, new object[] { }).ToString()); + } + } + + return map; + } + + + + + + + + /// + /// 返回首字母 + /// + /// + /// + /// 获得字符串首字符字母(大写); + /// + /// + /// + public static string GetStringFirstSpell(string cnChar) + { + //除字母、数字、汉字以外的返回"*" + var result = "#"; + + if (string.IsNullOrEmpty(cnChar.Trim())) + return result; + + + cnChar = cnChar.Trim().Substring(0, 1); + var firstName = cnChar; + + byte[] arrCn = Encoding.Default.GetBytes(cnChar); + + //首字为字符,占一个字节 + if (arrCn.Length <= 1) + { + //大写英文字母 + if ((short)arrCn[0] >= 65 && (short)arrCn[0] <= 90) + return cnChar; + + //小写英文字母 + if (arrCn[0] >= 97 && arrCn[0] <= 122) + return Encoding.Default.GetString(new byte[] { (byte)((short)arrCn[0] - 32) }); + + //数字 + switch (cnChar) + { + case "1": + result = "Y"; + break; + case "2": + result = "E"; + break; + case "3": + case "4": + result = "S"; + break; + case "5": + result = "W"; + break; + case "0": + case "6": + result = "L"; + break; + case "7": + result = "Q"; + break; + case "8": + result = "B"; + break; + case "9": + result = "J"; + break; + } + return result; + } + else if (!RegexHelper.IsChinese(firstName)) + { + return "#"; + } + //首字为汉字,占两个字节 + else + { + return GetStringFirstSpell(Pinyin4Net.GetFirstPinyin(char.Parse(cnChar))); + } + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Helpers/TimeHelper.cs b/src/Znyc.Dispatching.Core/Helpers/TimeHelper.cs new file mode 100644 index 0000000..3abc582 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Helpers/TimeHelper.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Znyc.Dispatching.Core.Helpers +{ + public static class TimeHelper + { + /// + /// 比较两个时间相差的分钟 + /// + /// 开始时间 + /// 结束时间 + /// 返回(分钟) + public static int ExecDateDiff(DateTime dateBegin, DateTime dateEnd) + { + TimeSpan ts1 = new TimeSpan(dateBegin.Ticks); + TimeSpan ts2 = new TimeSpan(dateEnd.Ticks); + TimeSpan ts3 = ts1.Subtract(ts2).Duration(); + //你想转的格式 + return Convert.ToInt32(ts3.TotalMinutes); + } + + /// + /// 时间差格式转换 + /// + /// 时间差 + /// 精确度,默认为2 + /// + public static string TimeSpanFormat(TimeSpan ts, int accuracy = 2) + { + StringBuilder sb = new StringBuilder(); + int idx = 0; + if (ts.Days != 0 && idx < accuracy) + { + sb.Append(ts.Days + "天"); + idx++; + } + + if (ts.Hours != 0 && idx < accuracy) + { + sb.Append(ts.Hours + "小时"); + idx++; + } + + if (ts.Minutes != 0 && idx < accuracy) + { + sb.Append(ts.Minutes + "分"); + idx++; + } + + if (ts.Seconds != 0 && idx < accuracy) + { + sb.Append(ts.Seconds + "秒"); + } + + return sb.ToString(); + } + + /// + /// 获取时间格式(yyyy-MM-dd HH:mm:ss) + /// + /// + /// + public static string ToDateTimeFormat(this DateTime? dateTime) + { + return dateTime == null ? "" : Convert.ToDateTime(dateTime).ToString("yyyy-MM-dd HH:mm:ss"); + } + + /// + /// 获取时间格式(yyyy-MM-dd HH:mm:ss) + /// + /// + /// + public static string ToDateTimeFormat(this DateTime dateTime) + { + return Convert.ToDateTime(dateTime).ToString("yyyy-MM-dd HH:mm:ss"); + } + + /// + /// 得到时间戳 + /// + /// + public static long ConvertToTimestamp(this DateTime time) + { + DateTime startTime = TimeZoneInfo.ConvertTimeToUtc(new DateTime(1970, 1, 1)); + long times = (int)(time - startTime).TotalSeconds; + return times; + } + + /// + /// 得到时间戳 + /// + /// + public static long ConvertToTimestamp(this DateTime? time) + { + if (time == null) + { + return 0; + } + + DateTime startTime = TimeZoneInfo.ConvertTimeToUtc(new DateTime(1970, 1, 1)); + long times = (int)(Convert.ToDateTime(time) - startTime).TotalSeconds; + return times; + } + + /// + /// 获取某段日期范围内的所有日期 + /// + /// 开始日期 + /// 结束日期 + /// + public static List FindDates(DateTime startTime, DateTime endTime) + { + List listDays = new List(); + DateTime dtDay = new DateTime(); + for (dtDay = startTime; dtDay.CompareTo(endTime) <= 0; dtDay = dtDay.AddDays(1)) + { + listDays.Add(dtDay); + } + + return listDays; + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Helpers/UploadHelper.cs b/src/Znyc.Dispatching.Core/Helpers/UploadHelper.cs new file mode 100644 index 0000000..e0ddffc --- /dev/null +++ b/src/Znyc.Dispatching.Core/Helpers/UploadHelper.cs @@ -0,0 +1,132 @@ +using COSXML; +using COSXML.Auth; +using COSXML.Transfer; +using Furion.FriendlyException; +using Microsoft.AspNetCore.Http; +using NLog; +using System; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Znyc.Dispatching.Core.Extension; + +namespace Znyc.Dispatching.Core.Helpers +{ + public class UploadHelper + { + /// + /// 上传单文件 + /// + /// + /// + /// + /// + public async Task UploadAsync(IFormFile file, UploadOptions config, + CancellationToken cancellationToken = default) + { + FileInfo res = new FileInfo(); + + if (file == null || file.Length < 1) + { + throw Oops.Oh("请上传文件!"); + } + + //格式限制 + if (!config.Avatar.ContentType.Contains(file.ContentType)) + { + throw Oops.Oh("文件格式错误"); + } + + //大小限制 + if (!(file.Length <= config.Avatar.MaxSize)) + { + throw Oops.Oh("文件过大"); + } + + FileInfo fileInfo = new FileInfo(file.FileName, file.Length) + { + UploadPath = config.Avatar.UploadPath, + RequestPath = config.Avatar.RequestPath + }; + fileInfo.RelativePath = Path.Combine().ToPath(); + if (!Directory.Exists(fileInfo.FileDirectory)) + { + Directory.CreateDirectory(fileInfo.FileDirectory); + } + + fileInfo.SaveName = $"{IdWorkerHelper.GenId64()}.{fileInfo.Extension}"; + + bool result = await SaveAsync(file, fileInfo.FilePath, cancellationToken, config); + return fileInfo; + } + + /// + /// 保存文件 + /// + /// + /// + /// + /// + /// + public async Task SaveAsync(IFormFile file, string filePath, CancellationToken cancellationToken, + UploadOptions config) + { + using (FileStream stream = new FileStream(filePath, FileMode.Create)) + { + await file.CopyToAsync(stream, cancellationToken); + } + + bool result = await SaveCosAsync(filePath, config); + File.Delete(filePath); + return result; + } + + /// + /// 保存文件 + /// + /// + /// + /// + public async Task SaveCosAsync(string filePath, UploadOptions uploadConfig) + { + Logger logger = LogManager.GetCurrentClassLogger(); + + try + { + QCloudCredentialProvider cosCredentialProvider = + new DefaultQCloudCredentialProvider( + uploadConfig.SecretId, uploadConfig.SecretKey, 600); + CosXmlConfig config = new CosXmlConfig.Builder() + .IsHttps(true) + .SetRegion(uploadConfig.Region) + .SetDebugLog(true) + .Build(); + CosXml cosXml = new CosXmlServer(config, cosCredentialProvider); + TransferConfig transferConfig = new TransferConfig(); + TransferManager transferManager = new TransferManager(cosXml, transferConfig); + string bucket = uploadConfig.Bucket; //存储桶, + string cosPath = Path.GetFileName(filePath); + string srcPath = filePath; + COSXMLUploadTask uploadTask = new COSXMLUploadTask(bucket, cosPath); + uploadTask.SetSrcPath(srcPath); + COSXMLUploadTask.UploadTaskResult result = await transferManager.UploadAsync(uploadTask); + if (result.httpCode != 200) + { + return false; + } + } + catch (Exception ex) + { + logger.Error(ex.InnerException); + return false; + } + finally + { + LogManager.Shutdown(); + } + + return true; + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Helpers/UtilConvert.cs b/src/Znyc.Dispatching.Core/Helpers/UtilConvert.cs new file mode 100644 index 0000000..67ad89e --- /dev/null +++ b/src/Znyc.Dispatching.Core/Helpers/UtilConvert.cs @@ -0,0 +1,252 @@ +using System; +using System.Text; +using Znyc.Dispatching.Core.Extension; + +namespace Znyc.Dispatching.Core.Helpers +{ + /// + /// 数据类型转换 + /// + public static class UtilConvert + { + public static int ToInt(this object thisValue) + { + int reval = 0; + if (thisValue == null) + { + return 0; + } + + if (thisValue != null && thisValue != DBNull.Value && int.TryParse(thisValue.ToString(), out reval)) + { + return reval; + } + + return reval; + } + + public static int ToInt(this object thisValue, int errorValue) + { + if (thisValue != null && thisValue != DBNull.Value && + int.TryParse(thisValue.ToString(), out int reval)) + { + return reval; + } + + return errorValue; + } + + public static long ToLong(this object s) + { + if (s == null || s == DBNull.Value) + { + return 0L; + } + + long.TryParse(s.ToString(), out long result); + return result; + } + + public static double ToMoney(this object thisValue) + { + if (thisValue != null && thisValue != DBNull.Value && + double.TryParse(thisValue.ToString(), out double reval)) + { + return reval; + } + + return 0; + } + + public static double ToMoney(this object thisValue, double errorValue) + { + if (thisValue != null && thisValue != DBNull.Value && + double.TryParse(thisValue.ToString(), out double reval)) + { + return reval; + } + + return errorValue; + } + + public static string ToString(this object thisValue) + { + if (thisValue != null) + { + return thisValue.ToString().Trim(); + } + + return ""; + } + + public static string ToString(this object thisValue, string errorValue) + { + if (thisValue != null) + { + return thisValue.ToString().Trim(); + } + + return errorValue; + } + + /// + /// 转换成Double/Single + /// + /// + /// 小数位数 + /// + public static double ToDouble(this object s, int? digits = null) + { + if (s == null || s == DBNull.Value) + { + return 0d; + } + + double.TryParse(s.ToString(), out double result); + + if (digits == null) + { + return result; + } + + return Math.Round(result, digits.Value); + } + + public static decimal ToDecimal(this object thisValue) + { + if (thisValue != null && thisValue != DBNull.Value && + decimal.TryParse(thisValue.ToString(), out decimal reval)) + { + return reval; + } + + return 0; + } + + public static decimal ToDecimal(this object thisValue, decimal errorValue) + { + if (thisValue != null && thisValue != DBNull.Value && + decimal.TryParse(thisValue.ToString(), out decimal reval)) + { + return reval; + } + + return errorValue; + } + + public static DateTime ToDateTime(this object thisValue) + { + DateTime reval = DateTime.MinValue; + if (thisValue != null && thisValue != DBNull.Value && DateTime.TryParse(thisValue.ToString(), out reval)) + { + reval = Convert.ToDateTime(thisValue); + } + + return reval; + } + + public static DateTime ToDateTime(this object thisValue, DateTime errorValue) + { + if (thisValue != null && thisValue != DBNull.Value && + DateTime.TryParse(thisValue.ToString(), out DateTime reval)) + { + return reval; + } + + return errorValue; + } + + public static DateTime ToDateTime(this long milliseconds) + { + return DateTimeExtensions.TimestampStart.AddMilliseconds(milliseconds); + } + + public static bool ToBool(this object thisValue) + { + bool reval = false; + if (thisValue != null && thisValue != DBNull.Value && + bool.TryParse(thisValue.ToString().ToLower(), out reval)) + { + return reval; + } + + return reval; + } + + public static byte ToByte(this object s) + { + if (s == null || s == DBNull.Value) + { + return 0; + } + + byte.TryParse(s.ToString(), out byte result); + return result; + } + + #region ==字节转换== + + /// + /// 转换为16进制 + /// + /// + /// 是否小写 + /// + public static string ToHex(this byte[] bytes, bool lowerCase = true) + { + if (bytes == null) + { + return null; + } + + StringBuilder result = new StringBuilder(); + string format = lowerCase ? "x2" : "X2"; + for (int i = 0; i < bytes.Length; i++) + { + result.Append(bytes[i].ToString(format)); + } + + return result.ToString(); + } + + /// + /// 16进制转字节数组 + /// + /// + /// + public static byte[] HexToBytes(this string s) + { + if (s.IsNull()) + { + return null; + } + + byte[] bytes = new byte[s.Length / 2]; + + for (int x = 0; x < s.Length / 2; x++) + { + int i = Convert.ToInt32(s.Substring(x * 2, 2), 16); + bytes[x] = (byte)i; + } + + return bytes; + } + + /// + /// 转换为Base64 + /// + /// + /// + public static string ToBase64(this byte[] bytes) + { + if (bytes == null) + { + return null; + } + + return Convert.ToBase64String(bytes); + } + + #endregion ==字节转换== + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Helpers/XSSHelper.cs b/src/Znyc.Dispatching.Core/Helpers/XSSHelper.cs new file mode 100644 index 0000000..cf3048b --- /dev/null +++ b/src/Znyc.Dispatching.Core/Helpers/XSSHelper.cs @@ -0,0 +1,28 @@ +using Ganss.XSS; + +namespace Znyc.Dispatching.Core.Helpers +{ + public class XSSHelper + { + private HtmlSanitizer sanitizer; + public XSSHelper() + { + sanitizer = new HtmlSanitizer(); + //sanitizer.AllowedTags.Add("div");//标签白名单 + sanitizer.AllowedAttributes.Add("class");//标签属性白名单,默认没有class标签属性 + //sanitizer.AllowedCssProperties.Add("font-family");//CSS属性白名单 + } + + /// + /// XSS过滤 + /// + /// html代码 + /// 过滤结果 + public string Filter(string html) + { + string str = sanitizer.Sanitize(html); + return str; + } + } + +} diff --git a/src/Znyc.Dispatching.Core/HostedService/LogHostedService.cs b/src/Znyc.Dispatching.Core/HostedService/LogHostedService.cs new file mode 100644 index 0000000..d13a407 --- /dev/null +++ b/src/Znyc.Dispatching.Core/HostedService/LogHostedService.cs @@ -0,0 +1,130 @@ +using Furion.DatabaseAccessor; +using Microsoft.Extensions.Hosting; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Znyc.Dispatching.Core.Entitys; +using Znyc.Dispatching.Core.SimpleQueue; + +namespace Znyc.Dispatching.Core +{ + /// + /// 后台日志写入服务 + /// + public class LogHostedService : IHostedService + { + private readonly IConcurrentQueue _logExQueue; + + private readonly IRepository _LogExRepository; + private readonly IConcurrentQueue _logOpQueue; + private readonly IRepository _LogOpRepository; + private readonly IConcurrentQueue _logVisQueue; + private readonly IRepository _LogVisRepository; + + public LogHostedService( + IConcurrentQueue logExQueue, + IConcurrentQueue logOpQueue, + IConcurrentQueue logVisQueue) + { + _logExQueue = logExQueue; + _logOpQueue = logOpQueue; + _logVisQueue = logVisQueue; + _LogExRepository = Db.GetRepository(); + _LogOpRepository = Db.GetRepository(); + _LogVisRepository = Db.GetRepository(); + } + + /// + /// 任务开始 + /// + /// + /// + public Task StartAsync(CancellationToken cancellationToken) + { + Task.Run(DoWork, cancellationToken); + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + /// + /// 工作线程 + /// + private async Task DoWork() + { + // 日志暂存器,待写入 + List LogExs = new(); + List LogOps = new(); + List LogViss = new(); + while (true) + { + // 取系统配置,获得轮训间隔和单次写入容量 + int interval = 5000; + int quantity = 100; + + // 后台队列中产生了日志,取出写入暂存器 + int logExCount = _logExQueue.Count(); + if (logExCount > 0) + { + for (int i = 0; i < logExCount; i++) + { + if (_logExQueue.Try(out LogEx obj)) + { + LogExs.Add(obj); + } + } + } + + int logOpCount = _logOpQueue.Count(); + if (logOpCount > 0) + { + for (int i = 0; i < logOpCount; i++) + { + if (_logOpQueue.Try(out LogOp obj)) + { + LogOps.Add(obj); + } + } + } + + int logVisCount = _logVisQueue.Count(); + if (logVisCount > 0) + { + for (int i = 0; i < logVisCount; i++) + { + if (_logVisQueue.Try(out LogVis obj)) + { + LogViss.Add(obj); + } + } + } + + // 达到系统配置的容量则写入数据库 + if (LogExs.Count > quantity) + { + await _LogExRepository.InsertNowAsync(LogExs); + LogExs.Clear(); + } + + if (LogOps.Count > quantity) + { + await _LogOpRepository.InsertNowAsync(LogOps); + LogOps.Clear(); + } + + if (LogViss.Count > quantity) + { + await _LogVisRepository.InsertNowAsync(LogViss); + LogViss.Clear(); + } + + // 执行间隔 + await Task.Delay(interval); + } + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Manager/IUserManager.cs b/src/Znyc.Dispatching.Core/Manager/IUserManager.cs new file mode 100644 index 0000000..6afe38a --- /dev/null +++ b/src/Znyc.Dispatching.Core/Manager/IUserManager.cs @@ -0,0 +1,13 @@ +namespace Znyc.Dispatching.Core +{ + public interface IUserManager + { + string Account { get; } + long UserId { get; } + string UserName { get; } + long RoleId { get; } + long CompanyId { get; } + + bool SuperAdmin { get; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Manager/UserManager.cs b/src/Znyc.Dispatching.Core/Manager/UserManager.cs new file mode 100644 index 0000000..31d5df4 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Manager/UserManager.cs @@ -0,0 +1,129 @@ +using Furion.DependencyInjection; +using Microsoft.AspNetCore.Http; +using Znyc.Dispatching.Core.Extension; +using Znyc.Dispatching.Core.Helpers; + +namespace Znyc.Dispatching.Core +{ + /// + /// 用户管理 + /// + public class UserManager : IUserManager, IScoped + { + private readonly IHttpContextAccessor _httpContextAccessor; + + + public UserManager(IHttpContextAccessor httpContextAccessor) + { + _httpContextAccessor = httpContextAccessor; + } + + /// + /// 用户Id + /// + public virtual long UserId + { + get + { + System.Security.Claims.Claim id = _httpContextAccessor?.HttpContext?.User?.FindFirst(ClaimConst.CLAINM_USERID); + if (id != null && id.Value.NotNull()) + { + return id.Value.ToLong(); + } + + return 0; + } + } + + /// + /// 权限Id + /// + public long RoleId + { + get + { + System.Security.Claims.Claim roleId = _httpContextAccessor?.HttpContext?.User?.FindFirst(ClaimConst.CLAINM_ROLEID); + + if (roleId != null && roleId.Value.NotNull()) + { + return roleId.Value.ToLong(); + } + + return 0; + } + } + + /// + /// 昵称 + /// + public string UserName + { + get + { + System.Security.Claims.Claim userName = _httpContextAccessor?.HttpContext?.User?.FindFirst(ClaimConst.CLAINM_USERNAME); + + if (userName != null && userName.Value.NotNull()) + { + return userName.Value; + } + + return ""; + } + } + + + /// + /// CompanyId + /// + public long CompanyId + { + get + { + System.Security.Claims.Claim companyId = _httpContextAccessor?.HttpContext?.User?.FindFirst(ClaimConst.CLAINM_COMPANYID); + + if (companyId != null && companyId.Value.NotNull()) + { + return companyId.Value.ToLong(); + } + + return 0; + } + } + + /// + /// Account + /// + public string Account + { + get + { + System.Security.Claims.Claim name = _httpContextAccessor?.HttpContext?.User?.FindFirst(ClaimConst.CLAINM_ACCOUNT); + + if (name != null && name.Value.NotNull()) + { + return name.Value; + } + + return ""; + } + } + + /// + /// SessionKey + /// + public bool SuperAdmin + { + get + { + System.Security.Claims.Claim superAdmin = _httpContextAccessor?.HttpContext?.User?.FindFirst(ClaimConst.CLAINM_SUPERADMIN); + + if (superAdmin != null && superAdmin.Value.NotNull()) + { + return superAdmin.Value.ToBool(); + } + + return false; + } + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Options/RefreshTokenSettingOptions.cs b/src/Znyc.Dispatching.Core/Options/RefreshTokenSettingOptions.cs new file mode 100644 index 0000000..67fe7f5 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Options/RefreshTokenSettingOptions.cs @@ -0,0 +1,15 @@ +using Furion.ConfigurableOptions; + +namespace Znyc.Dispatching.Core.Options +{ + /// + /// 刷新令牌设置 + /// + public sealed class RefreshTokenSettingOptions : IConfigurableOptions + { + /// + /// 令牌过期时间(分钟) + /// + public int ExpiredTime { get; set; } = 43200; + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Rpc/IHttp.cs b/src/Znyc.Dispatching.Core/Rpc/IHttp.cs new file mode 100644 index 0000000..d7fc629 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Rpc/IHttp.cs @@ -0,0 +1,11 @@ +using Furion.RemoteRequest; +using System.Threading.Tasks; + +namespace Znyc.Dispatching.Core.Rpc +{ + public interface IHttp : IHttpDispatchProxy + { + [Get("https://api.apishop.net/common/weather/get15DaysWeatherByArea")] + Task GetXXXAsync(); + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/SimpleQueue/IConcurrentQueue.cs b/src/Znyc.Dispatching.Core/SimpleQueue/IConcurrentQueue.cs new file mode 100644 index 0000000..f040a5d --- /dev/null +++ b/src/Znyc.Dispatching.Core/SimpleQueue/IConcurrentQueue.cs @@ -0,0 +1,13 @@ +namespace Znyc.Dispatching.Core.SimpleQueue +{ + public interface IConcurrentQueue + { + void Add(T obj); + + bool Try(out T obj); + + int Count(); + + void Clear(); + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/SimpleQueue/SimpleQueue.cs b/src/Znyc.Dispatching.Core/SimpleQueue/SimpleQueue.cs new file mode 100644 index 0000000..6a77e46 --- /dev/null +++ b/src/Znyc.Dispatching.Core/SimpleQueue/SimpleQueue.cs @@ -0,0 +1,55 @@ +using System.Collections.Concurrent; +using Znyc.Dispatching.Core.SimpleQueue; + +namespace Znyc.Dispatching.Core +{ + /// + /// 简单泛型队列 + /// + /// + public class SimpleQueue : IConcurrentQueue + { + private static ConcurrentQueue _simpleQueue; + + public SimpleQueue() + { + _simpleQueue = new ConcurrentQueue(); + } + + /// + /// 新增 + /// + /// + public void Add(T obj) + { + _simpleQueue.Enqueue(obj); + } + + /// + /// 取出 + /// + /// + /// + public bool Try(out T obj) + { + return _simpleQueue.TryDequeue(out obj); + } + + /// + /// 总数 + /// + /// + public int Count() + { + return _simpleQueue.Count; + } + + /// + /// 清理 + /// + public void Clear() + { + _simpleQueue.Clear(); + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/SqlProxy/Company/ICompanySql .cs b/src/Znyc.Dispatching.Core/SqlProxy/Company/ICompanySql .cs new file mode 100644 index 0000000..a80104b --- /dev/null +++ b/src/Znyc.Dispatching.Core/SqlProxy/Company/ICompanySql .cs @@ -0,0 +1,25 @@ +using Furion.DatabaseAccessor; +using System.Collections.Generic; +using System.Threading.Tasks; +using Znyc.Dispatching.Core.Entitys; + +namespace Znyc.Dispatching.Core +{ + /// + /// sql高级代理 + /// + public interface ICompanySql : ISqlDispatchProxy + { + // 获取所有公司 + [SqlExecute("select * from dc_company where IsDeleted=0")] + Task> GetCompanyList(); + + // 带参数查询 + [SqlExecute("select * from dc_company where id >@id and name like @name")] + Company GetCompanyList(int id, string name); + + //单行单列 + [SqlExecute("select Name from dc_company where id = @id")] + Task GetValueAsync(int id); + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Util/OSSClientUtil.cs b/src/Znyc.Dispatching.Core/Util/OSSClientUtil.cs new file mode 100644 index 0000000..c041850 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Util/OSSClientUtil.cs @@ -0,0 +1,300 @@ +using Aliyun.OSS; +using Aliyun.OSS.Common; +using System; +using System.IO; +using System.Net; + +namespace Znyc.Dispatching.Core +{ + /// + /// 阿里云oss文件上传工具类 + /// + public class OSSClientUtil + { + private const string + _endpoint = + "oss-cn-beijing.aliyuncs.com"; //"oss-cn-beijing.aliyuncs.com";// "oss-cn-huhehaote-internal.aliyuncs.com";//"oss-cn-huhehaote.aliyuncs.com" ; + + private static readonly string _accessKeyId = "accessKeyId"; + private static readonly string _accessKeySecret = "accessKeySecret"; + + //const string endpoint = "oss-cn-huhehaote-internal.aliyuncs.com"; + private static readonly string _internalEndpoint = "internalEndpoint"; //内网传输连接 + private static readonly string _bucketName = "bucketName"; + + public static OssClient GetClient() + { + return new(_endpoint, _accessKeyId, _accessKeySecret); + } + + public static OssClient GetClient_CND() + { + ClientConfiguration conf = new ClientConfiguration + { + IsCname = true + }; + return new OssClient("cdnmedia.aliyuncs.com", _accessKeyId, _accessKeySecret, conf); + } + + public static OssClient GetClient_internal() + { + return new(_internalEndpoint, _accessKeyId, _accessKeySecret); + } + + /// + /// 上传本地文件(走阿里云内网传输) + /// + /// + /// + /// + public static bool PushMedia_internal(string objectName, string localFilename) + { + OssClient client = GetClient_internal(); + return client.PutObject(_bucketName, objectName, localFilename).HttpStatusCode == HttpStatusCode.OK; + } + + /// + /// 上传一个图片 + /// + /// 图片经过base64加密后的结果 + /// 文件名,例如:Emplyoee/dzzBack.jpg + public static bool PushImg(string base64Code, string fileName) + { + OssClient client = GetClient(); + MemoryStream stream = new MemoryStream(Convert.FromBase64String(base64Code)); + return client.PutObject(_bucketName, fileName, stream).HttpStatusCode == HttpStatusCode.OK; + } + + public static bool PushMedia(Stream stream, string fileName) + { + OssClient client = GetClient(); + return client.PutObject(_bucketName, fileName, stream).HttpStatusCode == HttpStatusCode.OK; + } + + /// + /// 上传本地文件 + /// + /// + /// + /// 返回参数说明 1.本地文件不存在 2.文件oss上已存在 3.上传失败 4.上传成功 + /// + public static int PushMedia(string objectName, string localFilename) + { + if (!File.Exists(localFilename)) + { + return 1; + } + + if (DoesObjectExist(objectName)) + { + return 2; // 存在文件 + } + + try + { + OssClient client = GetClient(); + //MemoryStream stream = new MemoryStream(Convert.FromBase64String(base64Code)); + //var metadata = new ObjectMetadata(); + //metadata.ContentType = TypeTo(contentType); + + if (localFilename.Contains("http")) + { + WebClient webClient = new WebClient { Credentials = CredentialCache.DefaultCredentials }; + byte[] stream = webClient.DownloadData(localFilename); + MemoryStream ms = new MemoryStream(stream); + PutObjectResult c = client.PutObject(_bucketName, objectName, ms); + if (c.HttpStatusCode == HttpStatusCode.OK) + { + return 4; + } + + return 3; + } + else + { + PutObjectResult c = client.PutObject(_bucketName, objectName, localFilename); + if (c.HttpStatusCode == HttpStatusCode.OK) + { + return 4; + } + + return 3; + } + } + catch + { + return 3; + } + } + + /// + /// 上传一个图片 + /// + /// 图片字节 + /// 文件名,例如:Emplyoee/dzzBack.jpg + /// + public static bool PushImg(byte[] filebyte, string fileName, out string md5) + { + OssClient client = GetClient(); + MemoryStream stream = new MemoryStream(filebyte, 0, filebyte.Length); + PutObjectResult result = client.PutObject(_bucketName, fileName, stream); + md5 = result.ResponseMetadata["Content-MD5"]; + return result.HttpStatusCode == HttpStatusCode.OK; + } + + /// + /// 获取鉴权后的URL,URL有效日期默认一小时 + /// + /// 文件名,例如:Emplyoee/dzzBack.jpg + /// + public static string GetImg(string fileName) + { + OssClient client = GetClient(); + string key = fileName; + GeneratePresignedUriRequest req = new GeneratePresignedUriRequest(_bucketName, key, SignHttpMethod.Get) + { + Expiration = DateTime.Now.AddHours(1) + }; + return client.GeneratePresignedUri(req).ToString(); + } + + /// + /// 获取鉴权后的URL + /// + /// 文件名,例如:Emplyoee/dzzBack.jpg + /// URL有效日期,例如:DateTime.Now.AddHours(1) + /// + public static string GetImg(string fileName, DateTime expiration) + { + OssClient client = GetClient(); + string key = fileName; + GeneratePresignedUriRequest req = new GeneratePresignedUriRequest(_bucketName, key, SignHttpMethod.Get) + { + Expiration = expiration + }; + return client.GeneratePresignedUri(req).ToString(); + } + + /// + /// 将文件转换成byte[] 数组 + /// + /// 文件路径文件名称 + /// byte[] + private byte[] AuthGetFileData(string fileUrl) + { + using (FileStream fs = new FileStream(fileUrl, FileMode.OpenOrCreate, FileAccess.ReadWrite)) + { + byte[] buffur = new byte[fs.Length]; + using (BinaryWriter bw = new BinaryWriter(fs)) + { + bw.Write(buffur); + bw.Close(); + } + + return buffur; + } + } + + /// + /// 删除文件 + /// + /// 文件id + /// 文件url + public static bool DeletefileCode(string fileCode) + { + if (string.IsNullOrEmpty(fileCode)) + { + return true; + } + + //检查fileCode磁盘中是否存在此文件 + if (File.Exists(fileCode)) + { + File.Delete(fileCode); + return true; + } + + OssClient client = GetClient(); + client.DeleteObject(_bucketName, fileCode); + return true; + } + + /// + /// 删除文件夹 + /// + /// 文件id + /// 文件url + public static bool DeleteFolder(string prefix) + { + if (string.IsNullOrEmpty(prefix)) + { + return true; + } + + OssClient client = GetClient(); + ListObjectsRequest listObjectsRequest = new ListObjectsRequest(_bucketName) + { + Prefix = prefix + }; + ObjectListing result = client.ListObjects(listObjectsRequest); + + foreach (OssObjectSummary summary in result.ObjectSummaries) + { + client.DeleteObject(_bucketName, summary.Key); + } + + return false; + } + + /// + /// 判断文件是否存在 + /// + /// + /// + public static bool DoesObjectExist(string fileName) + { + OssClient client = GetClient(); + // 判断文件是否存在。 + bool exist = client.DoesObjectExist(_bucketName, fileName); + return exist; + } + + /// + /// 类型转换 + /// + /// + public static string TypeTo(string type) + { + type = type switch + { + "mp4" => "video/mp4", + "mp3" => "audio/mp3", + _ => "image/" + type + }; + return type; + } + + public static OssObject DownLoad(string fileName) + { + //var client = GetClient(); + //DateTime expiration = new DateTime().AddHours(1); + //GeneratePresignedUriRequest request = new GeneratePresignedUriRequest(bucketName, fileName, SignHttpMethod.Get); + //// 设置过期时间。 + //request.Expiration=expiration; + //// 生成签名URL(HTTP GET请求)。 + //Uri signedUrl = client.GeneratePresignedUri(request); + + //// 添加GetObject请求头。 + ////customHeaders.put("Range", "bytes=100-1000"); + //OssObject result = client.GetObject(signedUrl); + + OssClient client = GetClient(); + string key = fileName; + _ = new GeneratePresignedUriRequest(_bucketName, key, SignHttpMethod.Get) + { + Expiration = DateTime.Now.AddHours(1) + }; + return client.GetObject(_bucketName, key); + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Util/ReflectionUtil.cs b/src/Znyc.Dispatching.Core/Util/ReflectionUtil.cs new file mode 100644 index 0000000..4179aef --- /dev/null +++ b/src/Znyc.Dispatching.Core/Util/ReflectionUtil.cs @@ -0,0 +1,26 @@ +using System; +using System.Reflection; + +namespace Znyc.Dispatching.Core +{ + /// + /// 反射工具 + /// + public static class ReflectionUtil + { + /// + /// 获取字段特性 + /// + /// + /// + /// + public static T GetDescriptionValue(this FieldInfo field) where T : Attribute + { + // 获取字段的指定特性,不包含继承中的特性 + object[] customAttributes = field.GetCustomAttributes(typeof(T), false); + + // 如果没有数据返回null + return customAttributes.Length > 0 ? (T)customAttributes[0] : null; + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Znyc.Dispatching.Core.csproj b/src/Znyc.Dispatching.Core/Znyc.Dispatching.Core.csproj new file mode 100644 index 0000000..e96f8ea --- /dev/null +++ b/src/Znyc.Dispatching.Core/Znyc.Dispatching.Core.csproj @@ -0,0 +1,46 @@ + + + + net6.0 + 1701;1702;1591 + Znyc.Dispatching.Core.xml + + + + + + + + + + + Always + + + Always + + + Always + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/Znyc.Dispatching.Core.xml b/src/Znyc.Dispatching.Core/Znyc.Dispatching.Core.xml new file mode 100644 index 0000000..39f06a9 --- /dev/null +++ b/src/Znyc.Dispatching.Core/Znyc.Dispatching.Core.xml @@ -0,0 +1,4995 @@ + + + + Znyc.Dispatching.Core + + + + + 缓存配置 + + + + + Redis配置 + + + + + 缓存接口 + + + + + 用于在 key 存在时删除 key + + 键 + + + + 用于在 key 存在时删除 key + + 键 + + + + + 用于在 key 模板存在时删除 + + key模板 + + + + + 检查给定 key 是否存在 + + 键 + + + + + 检查给定 key 是否存在 + + 键 + + + + + 获取指定 key 的值 + + 键 + + + + + 获取指定 key 的值 + + byte[] 或其他类型 + 键 + + + + + 获取指定 key 的值 + + 键 + + + + + 获取指定 key 的值 + + byte[] 或其他类型 + 键 + + + + + 设置指定 key 的值,所有写入参数object都支持string | byte[] | 数值 | 对象 + + 键 + 值 + + + + 设置指定 key 的值,所有写入参数object都支持string | byte[] | 数值 | 对象 + + 键 + 值 + 有效期 + + + + 设置指定 key 的值,所有写入参数object都支持string | byte[] | 数值 | 对象 + + 键 + 值 + + + + + 设置指定 key 的值,所有写入参数object都支持string | byte[] | 数值 | 对象 + + 键 + 值 + 有效期 + + + + + 获取所有缓存 + + + + + + 缓存壳 + + + + + + + + + + 缓存壳 + + + + + + + + + + 发布 + + + + + + + + 订阅 + + + + + + + Redis缓存 + + + + + 缓存壳 + + + + + + + + + + 缓存壳 + + + + + + + + + + 发布 + + + + + + + + 订阅 + + + + + + + 发布 + + + + + + + + 发送短信配置 + + + + + 上传配置 + + + + + 头像上传配置 + + + + + 文档图片上传配置 + + + + + 文件上传配置 + + + + + 上传路径 + + + + + 请求路径 + + + + + 读取路径 + + + + + 路径格式 + + + + + 路径日期格式 + + + + + 文件大小 10M = 10 * 1024 * 1024 + + + + + 最大允许上传个数 -1不限制 + + + + + 文件格式 + + + + + 微信配置 + + + + + 是否调试模式 + + + + + 小程序 + + + + + + + + + + + + + + + + + + + + + + + + + 公众号 + + + + + 公众号 + + + + + + + + + 用户 + + + + + 日志 + + + + + 系统中心 + + + + + 用户Id + + + + + 账号 + + + + + 名称 + + + + + 是否超级管理 + + + + + 角色Id + + + + + 公司Id + + + + + 默认密码 + + + + + 默认超速速度 + + + + + 默认停留标示Id + + + + + 默认角色Id + + + + + 默认角色名称 + + + + + 车主人员默认首页路径 + + + + + 外租伙伴默认首页路径 + + + + + 管理员默认首页路径 + + + + + 默认加密盐 + + + + + mongoDB超速报警表前缀 + + + + + 用户缓存 + + + + + 菜单缓存 + + + + + 权限缓存 + + + + + 数据范围缓存 + + + + + 验证码缓存 + + + + + 角色缓存 + + + + + 角色菜单缓存 + + + + + 车辆类型缓存 + + + + + 车辆分组缓存 + + + + + 字典缓存 + + + + + 默认logo地址 + + + + + 头像地址前缀 + + + + + 默认头像地址 + + + + + 默认头像全部地址 + + + + + 公司缓存 + + + + + JWT + + + + + token + + + + + 公司车辆缓存 + + + + + 车辆缓存 + + + + + 车辆GPS信息缓存 + + + + + 超速记录缓存 + + + + + 员工缓存 + + + + + 员工列表缓存 + + + + + 未读报警消息 + + + + + 轨迹回放 + + + + + 车场列表缓存 + + + + + 车场缓存 + + + + + 工程列表缓存 + + + + + 工程缓存 + + + + + 车组人员缓存 + + + + + + + + + + + + + + + 订单签证地址 + + + + + + + + + + 公司表 + + + + + 公司名称 + + + + + 公司Logo + + + + + 联系人 + + + + + 联系人电话 + + + + + 精度 + + + + + 纬度 + + + + + 地址 + + + + + 审核时间 + + + + + 状态(字典 0正常 1停用 2删除) + + + + + 停留标示Id + + + + + 一键派工是否显示工程名称 + + + + + 调度角色是否有添加工程名称的权限 + + + + + 是否启用任务车型选项,默认为关 + + + + + 施工单位表 + + + + + 所属公司 + + + + + 施工单位 + + + + + 联系人 + + + + + 联系电话 + + + + + 状态 + + + + + 自定义实体基类 + + + + + 自定义实体基类 + + + + + 主键Id + + + + + 创建时间 + + + + + 更新时间 + + + + + 创建者Id + + + + + 修改者Id + + + + + 软删除 + + + + + 数据字典表 + + + + + 字典父级 + + + + + 字典编码 + + + + + 字典值 + + + + + 字典描述 + + + + + 字典名称 + + + + + 是否启用 + + + + + 字典排序 + + + + + 员工表 + + + + + 用户Id + + + + + 员工头像 + + + + + 员工名 + + + + + 手机号 + + + + + 机构Id + + + + + CommonStatus + + + + + RoleStatus + + + + + RoleStatus + + + + + 是否默认登录 + + + + + 系统操作/审计日志表 + + + + + 表名 + + + + + 列名 + + + + + 新值 + + + + + 旧值 + + + + + 操作人Id + + + + + 操作人名称 + + + + + 操作方式:新增、更新、删除 + + + + + 异常日志 + + + + + 操作人 + + + + + 名称 + + + + + 类名 + + + + + 方法名 + + + + + 异常名称 + + + + + 异常信息 + + + + + 异常源 + + + + + 堆栈信息 + + + + + 参数对象 + + + + + 异常时间 + + + + + 操作日志表 + + + + + 名称 + + + + + 是否执行成功(Y-是,N-否) + + + + + 具体消息 + + + + + IP + + + + + 地址 + + + + + 浏览器 + + + + + 操作系统 + + + + + 请求地址 + + + + + 类名称 + + + + + 方法名称 + + + + + 请求方式(GET POST PUT DELETE) + + + + + 请求参数 + + + + + 返回结果 + + + + + 耗时(毫秒) + + + + + 操作时间 + + + + + 操作人 + + + + + 访问日志表 + + + + + 名称 + + + + + 是否执行成功(Y-是,N-否) + + + + + 具体消息 + + + + + IP + + + + + 地址 + + + + + 浏览器 + + + + + 操作系统 + + + + + 访问类型 + + + + + 访问时间 + + + + + 访问人 + + + + + 菜单表 + + + + + 父Id + + + + + 所属层级 + + + + + 名称 + + + + + 编码 + + + + + 菜单类型(字典 0目录 1菜单 2按钮) + + + + + 图标 + + + + + 路由地址 + + + + + 权限标识 + + + + + 是否外链 + + + + + 是否展开 + + + + + 是否显示 + + + + + 排序 + + + + + 详细描述 + + + + + CommonStatus + + + + + 多对多(角色) + + + + + 多对多中间表(用户角色) + + + + + 上级 + + + + + 子集 + + + + + 配置实体关系 + + + + + + + + 站内消息表 + + + + + 发送者Id + + + + + 消息标题 + + + + + 消息类型 + + + + + 发送类型 + + + + + 组Id + + + + + 消息内容 + + + + + 发送时间 + + + + + 消息记录表 + + + + + 接收者Id + + + + + 消息Id + + + + + 状态 + + + + + 站内消息 + + + + + 油耗表 + + + + + 公司Id + + + + + 加油日期 + + + + + 车辆Id + + + + + 车俩编号 + + + + + 加油单号 + + + + + 加油量 + + + + + 油单价 + + + + + 金额 + + + + + 派工订单表 + + + + + 公司Id + + + + + 车辆Id,,冗余查询车辆列表状态 + + + + + 车辆编号 + + + + + 到场时间 + + + + + 经度 + + + + + 纬度 + + + + + 地址 + + + + + 状态,10=待指派,20=已指派,未接单,30=已接单,40=已出发,50=已完成,60=已签单,70=已离开,80=已评价,101=撤销 + + + + + 工程Id + + + + + 工程名称 + + + + + 业务员Id + + + + + 订单来源,10=录入,20=报单 + + + + + 订单内容 + + + + + 是否外请车 + + + + + 指派时间 + + + + + 指派人 + + + + + 接单时间 + + + + + 出发时间 + + + + + 到达时间 + + + + + 签单时间 + + + + + 完成时间 + + + + + 评价时间 + + + + + 离开时间 + + + + + 到家时间 + + + + + 车组人员 + + + + + 任务车型 + + + + + 施工单位 + + + + + 派工订单车组人员表 + + + + + 订单Id + + + + + 指派任务人员Id + + + + + 姓名 + + + + + 电话 + + + + + 是否司机 + + + + + 订单签证表 + + + + + 订单Id + + + + + 图片路径 + + + + + 工程信息表 + + + + + 所属公司 + + + + + 工程名称 + + + + + 业务员Id + + + + + 经度 + + + + + 纬度 + + + + + 地址 + + + + + 是否启用 + + + + + 施工单位 + + + + + 施工单位名称 + + + + + 工程信息联系人表 + + + + + 工程Id + + + + + 联系人Id,链接员工表Id + + + + + 联系人姓名 + + + + + 联系人电话 + + + + + 意见反馈表 + + + + + 投诉人Id + + + + + 投诉内容 + + + + + 投诉时间 + + + + + 举报状态 + + + + + 处理状态 + + + + + 处理结果 + + + + + 角色表 + + + + + 名称 + + + + + 编码 + + + + + 角色说明 + + + + + 状态(字典 0正常 1停用 2删除) + + + + + 角色菜单表 + + + + + 角色Id + + + + + 菜单Id + + + + + 业务员 + + + + + 业务员姓名 + + + + + 业务员电话 + + + + + 是否启用 + + + + + 用户表 + + + + + OpenId + + + + + 头像 + + + + + 用户名 + + + + + 电话 + + + + + 状态 + + + + + 用户角色表 + + + + + 用户Id + + + + + 系统角色Id + + + + + 车场表 + + + + + 用户Id + + + + + 车场Id + + + + + 公司Id + + + + + 是否默认 + + + + + 车辆表 + + + + + 所属公司 + + + + + 车辆编号 + + + + + 车牌号 + + + + + 车辆类型 + + + + + 所属车组 + + + + + SIM卡号 + + + + + 设备类型 + + + + + 设备号 + + + + + GPS协议号 + + + + + 是否激活 + + + + + 是否开启GPS + + + + + 所属司机 + + + + + 司机电话 + + + + + 联系人 + + + + + 联系电话 + + + + + IEML号 + + + + + 状态 + + + + + 打火状态 + + + + + 工作状态 + + + + + Gps状态 + + + + + GPS时间 + + + + + 开通时间 + + + + + 到期时间 + + + + + 物联卡类型 + + + + + 是否开启超速报警 + + + + + 超速速度 + + + + + 排序 + + + + + 车辆表分组 + + + + + 公司Id + + + + + 名称 + + + + + 父级权限Id + + + + + 状态(字典 0正常 1停用 2删除) + + + + + 车组人员信息表 + + + + + 公司Id + + + + + 车组人员Id + + + + + 员工名 + + + + + 手机号 + + + + + 是否司机 + + + + + 车辆类型表 + + + + + 公司Id + + + + + 名称 + + + + + 父级权限Id + + + + + 状态(字典 0正常 1停用 2删除) + + + + + 微信中间表 + + + + + UnionId + + + + + OpenId + + + + + 用户Id + + + + + 公众号OpenId + + + + + 车场表 + + + + + 公司Id + + + + + 联系人 + + + + + 联系人电话 + + + + + 精度 + + + + + 纬度 + + + + + 地址 + + + + + 状态(字典 1正常 0停用 -1删除) + + + + + 打火状态 + + + + + 关闭 + + + + + 正常打火 + + + + + 公共状态 + + + + + 正常 + + + + + 停用 + + + + + 删除 + + + + + 审核中 + + + + + 数据操作类型 + + + + + 其它 + + + + + 增加 + + + + + 删除 + + + + + 编辑 + + + + + 更新 + + + + + 查询 + + + + + 详情 + + + + + 树 + + + + + 导入 + + + + + 导出 + + + + + 授权 + + + + + 强退 + + + + + 清空 + + + + + 修改状态 + + + + + 系统错误码 + + + + + 用户名或密码不正确 + + + + + 非法操作!禁止删除自己 + + + + + 记录不存在 + + + + + 账号已存在 + + + + + 旧密码不匹配 + + + + + 测试数据禁止更改admin密码 + + + + + 数据已存在 + + + + + 数据不存在或含有关联引用,禁止删除 + + + + + 禁止为管理员分配角色 + + + + + 重复数据或记录含有不存在数据 + + + + + 禁止为超级管理员角色分配权限 + + + + + 非法数据 + + + + + Id不能为空 + + + + + 所属机构不在自己的数据范围内 + + + + + 禁止删除超级管理员 + + + + + 禁止修改超级管理员状态 + + + + + 没有权限 + + + + + 账号已冻结 + + + + + 验证码错误 + + + + + 账号不存在 + + + + + 公司审核中 + + + + + 验证码失效 + + + + + 新密码不一致 + + + + + 父机构不存在 + + + + + 当前机构Id不能与父机构Id相同 + + + + + 已有相同组织机构,编码或名称相同 + + + + + 没有权限操作机构 + + + + + 该机构下有员工禁止删除 + + + + + 附属机构下有员工禁止删除 + + + + + 只能增加下级机构 + + + + + 字典类型不存在 + + + + + 字典类型已存在 + + + + + 字典类型下面有字典值禁止删除 + + + + + 字典值已存在 + + + + + 字典值不存在 + + + + + 字典状态错误 + + + + + 菜单已存在 + + + + + 路由地址为空 + + + + + 打开方式为空 + + + + + 权限标识格式为空 + + + + + 权限标识格式错误 + + + + + 权限不存在 + + + + + 父级菜单不能为当前节点,请重新选择父级菜单 + + + + + 不能移动根节点 + + + + + 已存在同名或同编码应用 + + + + + 默认激活系统只能有一个 + + + + + 该应用下有菜单禁止删除 + + + + + 已存在同名或同编码应用 + + + + + 已存在同名或同编码职位 + + + + + 该职位下有员工禁止删除 + + + + + 通知公告状态错误 + + + + + 通知公告删除失败 + + + + + 通知公告编辑失败 + + + + + 文件不存在 + + + + + 已存在同名或同编码参数配置 + + + + + 禁止删除系统参数 + + + + + 已存在同名任务调度 + + + + + 任务调度不存在 + + + + + 演示环境禁止修改数据 + + + + + 已存在同名或同主机租户 + + + + + 该表代码模板已经生成过 + + + + + 该类型不存在 + + + + + 该字段不存在 + + + + + 该类型不是枚举类型 + + + + + 该实体不存在 + + + + + 父菜单不存在 + + + + + 已存在同名或同编码项目 + + + + + 已存在相同证件号码人员 + + + + + 检测数据不存在 + + + + + JsCode不存在 + + + + + 账号已存在 + + + + + 文件存储位置 + + + + + 阿里云 + + + + + 腾讯云 + + + + + minio服务器 + + + + + 本地 + + + + + Gps状态 + + + + + 离线 + + + + + 行驶 + + + + + 静止 + + + + + 默认 + + + + + 接口 + + + + + 网站 + + + + + 循环时间 + + + + + HTTP状态码 + + + + + 客户端可能继续其请求 + + + + + 正在更改协议版本或协议 + + + + + 请求成功,且请求的信息包含在响应中 + + + + + 请求导致在响应被发送前创建新资源 + + + + + 请求已被接受做进一步处理 + + + + + 返回的元信息来自缓存副本而不是原始服务器,因此可能不正确 + + + + + 已成功处理请求并且响应已被设定为无内容 + + + + + 客户端应重置(或重新加载)当前资源 + + + + + 响应是包括字节范围的 GET请求所请求的部分响应 + + + + + 请求的信息有多种表示形式,默认操作是将此状态视为重定向 + + + + + 请求的信息已移到 Location头中指定的 URI 处 + + + + + 请求的信息位于 Location 头中指定的 URI 处 + + + + + 将客户端自动重定向到 Location 头中指定的 URI + + + + + 客户端的缓存副本是最新的 + + + + + 请求应使用位于 Location 头中指定的 URI 的代理服务器 + + + + + 服务器未能识别请求 + + + + + 请求的资源要求身份验证 + + + + + 需要付费 + + + + + 服务器拒绝满足请求 + + + + + 请求的资源不在服务器上 + + + + + 请求的资源上不允许请求方法(POST或 GET) + + + + + 客户端已用 Accept 头指示将不接受资源的任何可用表示形式 + + + + + 请求的代理要求身份验证 + Proxy-authenticate 头包含如何执行身份验证的详细信息 + + + + + 客户端没有在服务器期望请求的时间内发送请求 + + + + + 由于服务器上的冲突而未能执行请求 + + + + + 请求的资源不再可用 + + + + + 缺少必需的 Content-length + + + + + 为此请求设置的条件失败,且无法执行此请求 + 条件是用条件请求标头(如 If-Match、If-None-Match 或 If-Unmodified-Since)设置的。 + + + + + 请求太大,服务器无法处理 + + + + + URI 太长 + + + + + 请求是不支持的类型 + + + + + 无法返回从资源请求的数据范围,因为范围的开头在资源的开头之前,或因为范围的结尾在资源的结尾之后 + + + + + 服务器未能符合Expect头中给定的预期值 + + + + + 服务器拒绝处理客户端使用当前协议发送的请求,但是可以接受其使用升级后的协议发送的请求 + + + + + 服务器上发生了一般错误 + + + + + 服务器不支持请求的函数 + + + + + 中间代理服务器从另一代理或原始服务器接收到错误响应 + + + + + 服务器暂时不可用,通常是由于过多加载或维护 + + + + + 中间代理服务器在等待来自另一个代理或原始服务器的响应时已超时 + + + + + 服务器不支持请求的HTTP版本 + + + + + 登陆类型 + + + + + 登陆 + + + + + 登出 + + + + + 注册 + + + + + 改密 + + + + + 三方授权登陆 + + + + + 系统菜单类型 + + + + + 目录 + + + + + 菜单 + + + + + 按钮 + + + + + 通知公告用户状态 + + + + + 未读 + + + + + 已读 + + + + + 派工订单来源 + + + + + 录入 + + + + + 报单 + + + + + 派工状态 + + + + + 草稿 + + + + + 待指派 + + + + + 已指派(未接单) + + + + + 已接单 + + + + + 已出发 + + + + + 已到达 + + + + + 已完成 + + + + + 已签单 + + + + + 已离开 + + + + + 已评价 + + + + + 撤销 + + + + + 已到家 + + + + + 人才招聘 + + + + + 派工调度 + + + + + 后台管理系统 + + + + + 查询类型的枚举 + + + + + 等于 + + + + + 模糊 + + + + + 大于 + + + + + 小于 + + + + + 不等于 + + + + + 大于等于 + + + + + 小于等于 + + + + + 不为空 + + + + + http请求类型 + + + + + 执行内部方法 + + + + + GET请求 + + + + + POST请求 + + + + + PUT请求 + + + + + DELETE请求 + + + + + 角色类型 + + + + + 车组人员 + + + + + 调度 + + + + + 销售 + + + + + 财务 + + + + + 管理员 + + + + + 业务员 + + + + + 车队长 + + + + + 仓管 + + + + + 工地施工员 + + + + + 外租伙伴 + + + + + 兼职业务 + + + + + 停留点状态 + + + + + 起点 + + + + + 停留点 + + + + + 终点 + + + + + 产品类型 + + + + + 博实结KG_M08四线 + + + + + 工作状态 + + + + + 关闭 + + + + + 正常工作 + + + + + 菜单激活类型 + + + + + 是 + + + + + 否 + + + + + 时间戳起始日期 + + + + + 转换为时间戳 + + + 是否使用毫秒 + + + + + 获取周几 + + + + + + + 字典扩展 + + + + + 将一个字典转化为 QueryString + + + + + + + + 将一个字符串 URL 编码 + + + + + + + 移除空值项 + + + + + + 枚举扩展 + + + + + 获取枚举对象Key与名称的字典(缓存) + + + + + + + 获取枚举对象Key与名称的字典 + + + + + + + 获取枚举类型key与描述的字典(缓存) + + + + + + + + 获取枚举类型key与描述的字典(没有描述则获取name) + + + + + + + + 从程序集中查找指定枚举类型 + + + + + + + + 从程序集中加载所有枚举类型 + + + + + + + 从枚举中获取Description + + 需要获取枚举描述的枚举 + 描述内容 + + + + 根据 value 值获取Description + + + + + + + + 根据 value 值获取Description + + + + + + + + 获取字段Description + + FieldInfo + DescriptionAttribute[] + + + + 获取枚举所有名称 + + 枚举类型typeof(T) + 枚举名称列表 + + + + 获取所有枚举对应的值 + + 枚举类型typeof(T) + 枚举值列表 + + + + 获取枚举名以及对应的Description + + 枚举类型typeof(T) + 返回Dictionary ,Key为枚举名, Value为枚举对应的Description + + + + 获取枚举名以及对应的Value + + 枚举类型typeof(T) + 返回Dictionary ,Key为描述名, Value为枚举对应的值 + + + + 通用输入扩展参数(带权限) + + + + + 授权菜单 + + + + + 授权角色 + + + + + 授权数据 + + + + + 通用分页输入参数 + + + + + 搜索值 + + + + + 当前页码 + + + + + 页码容量 + + + + + 搜索开始时间 + + + + + 搜索结束时间 + + + + + 排序字段 + + + + + 排序方法,默认升序,否则降序(配合antd前端,约定参数为 Ascend,Dscend) + + + + + 降序排序(不要问我为什么是descend不是desc,前端约定参数就是这样) + + + + + 判断对象是否为空 + + + + + + + 判断对象是否不为空 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 获取当前时间的时间戳 + + + + + + + 判断字符串是否为Null、空 + + + + + + + 判断字符串是否不为Null、空 + + + + + + + 与字符串进行比较,忽略大小写 + + + + + + + + 首字母转小写 + + + + + + + 首字母转大写 + + + + + + + 转为Base64,UTF-8格式 + + + + + + + 转为Base64 + + + 编码 + + + + + 通用输入帮助类 + + + + + 排序方式(默认降序) + + + 是否降序 + + + + + 规范化RESTful风格返回值 + + + + + 异常返回值 + + + + + + + + 成功返回值 + + + + + + + + 验证失败返回值 + + + + + + + + 处理输出状态码 + + + + + + + + + RESTful风格---XIAONUO返回格式 + + + + + + 执行成功 + + + + + 状态码 + + + + + 错误信息 + + + + + 数据 + + + + + 附加数据 + + + + + 时间戳 + + + + + 分页列表结果 + + + + + + 文件信息 + + + + + 初始化文件信息 + + 文件名称 + 大小 + + + + 上传路径 + + + + + 请求路径 + + + + + 相对路径 + + + + + 文件名 + + + + + 保存名 + + + + + 文件大小 + + + + + 扩展名 + + + + + 文件目录 + + + + + 文件请求路径 + + + + + 文件相对路径 + + + + + 文件路径 + + + + + 文件大小 + + + + + 初始化文件大小 + + 文件大小 + 文件大小单位 + + + + 文件字节长度 + + + + + 获取文件大小,单位:字节 + + + + + 获取文件大小,单位:K + + + + + 获取文件大小,单位:M + + + + + 获取文件大小,单位:G + + + + + 输出描述 + + + + + 文件大小单位 + + + + + 字节 + + + + + K字节 + + + + + M字节 + + + + + G字节 + + + + + 全局异常处理 + + + + + 请求日志拦截 + + + + + 遍历修改类的字符串属性 + + 类名 + 数据类型 + 对象 + + + + + byte转换类 + + + + + 将对象转换为byte数组 + + 被转换对象 + 转换后byte数组 + + + + 将byte数组转换成对象 + + 被转换byte数组 + 转换完成后的对象 + + + + 将byte数组转换成对象 + + 被转换byte数组 + 转换完成后的对象 + + + + 保留小数点后六位 + + + + + + + 处理数据类型转换,数制转换、编码转换相关的类 + + + + + 将byte[]转换成int + + 需要转换成整数的byte数组 + + + + 实现各进制数间的转换。ConvertBase("15",10,16)表示将十进制数15转换为16进制的数。 + + 要转换的值,即原值 + 原值的进制,只能是2,8,10,16四个值。 + 要转换到的目标进制,只能是2,8,10,16四个值。 + + + + 判断是否是 2 8 10 16 + + + + + + + 将string转换成byte[] + + 要转换的字符串 + + + + 使用指定字符集将string转换成byte[] + + 要转换的字符串 + 字符编码 + + + + 将byte[]转换成string + + 要转换的字节数组 + + + + 使用指定字符集将byte[]转换成string + + 要转换的字节数组 + 字符编码 + + + + 将数据转换为整型 转换失败返回默认值 + + 数据类型 + 数据 + 默认值 + + + + + 将数据转换为整型 转换失败返回默认值 + + 数据 + 默认值 + + + + + 将数据转换为整型 转换失败返回默认值 + + 数据 + 默认值 + + + + + 将数据转换为布尔类型 转换失败返回默认值 + + 数据类型 + 数据 + 默认值 + + + + + 将数据转换为布尔类型 转换失败返回 默认值 + + 数据 + 默认值 + + + + + 将数据转换为布尔类型 转换失败返回 默认值 + + 数据 + 默认值 + + + + + 将数据转换为单精度浮点型 转换失败 返回默认值 + + 数据类型 + 数据 + 默认值 + + + + + 将数据转换为单精度浮点型 转换失败返回默认值 + + 数据 + 默认值 + + + + + 将数据转换为单精度浮点型 转换失败返回默认值 + + 数据 + 默认值 + + + + + 将数据转换为双精度浮点型 转换失败返回默认值 + + 数据的类型 + 要转换的数据 + 默认值 + + + + + 将数据转换为双精度浮点型,并设置小数位 转换失败返回默认值 + + 数据的类型 + 要转换的数据 + 小数的位数 + 默认值 + + + + + 将数据转换为双精度浮点型 转换失败返回默认值 + + 要转换的数据 + 默认值 + + + + + 将数据转换为双精度浮点型 转换失败返回默认值 + + 要转换的数据 + 默认值 + + + + + 将数据转换为双精度浮点型,并设置小数位 转换失败返回默认值 + + 要转换的数据 + 小数的位数 + 默认值 + + + + + 将数据转换为双精度浮点型,并设置小数位 转换失败返回默认值 + + 要转换的数据 + 小数的位数 + 默认值 + + + + + 将数据转换为指定类型 + + 转换的数据 + 转换的目标类型 + + + + 将数据转换为指定类型 + + 转换的目标类型 + 转换的数据 + + + + + + + + + + + + + + + + + + + 将数据转换为Decimal 转换失败返回默认值 + + 数据类型 + 数据 + 默认值 + + + + + 将数据转换为Decimal 转换失败返回 默认值 + + 数据 + 默认值 + + + + + 将数据转换为Decimal 转换失败返回 默认值 + + 数据 + 默认值 + + + + + 将数据转换为DateTime 转换失败返回默认值 + + 数据类型 + 数据 + 默认值 + + + + + 将数据转换为DateTime 转换失败返回 默认值 + + 数据 + 默认值 + + + + + 将数据转换为DateTime 转换失败返回 默认值 + + 数据 + 默认值 + + + + + 转全角的函数(SBC case) + + 任意字符串 + 全角字符串 + + 全角空格为12288,半角空格为32 + 其他字符半角(33-126)与全角(65281-65374)的对应关系是:均相差65248 + + + + 转半角的函数(DBC case) + 任意字符串 + 半角字符串 + + 全角空格为12288,半角空格为32 + 其他字符半角(33-126)与全角(65281-65374)的对应关系是:均相差65248 + + + + + DateTime帮助类 + + + + + 判断当前时间是否为今天 + + + + + + + 时间转换 + + + + + + + 时间类型格式数据处理 + + + + + 时间格式 + + + + + + + + + + 时间格式 + + + + + + + + + + + + + + + + + + + + + + + + HttpContext帮助类 + + + + + + + + + + + + + 注入 + + + + + + http请求类 + + + + + + + + + + + 组装QueryString的方法 + 参数之间用&连接,首位没有符号,如:a=1&b=2&c=3 + + + + + + + 使用Get方法获取字符串结果 + + + + + + + + + 使用Post方法获取字符串结果,常规提交 + + + + + + 发送HttpPost请求,使用JSON格式传输数据 + + + + + + + + + + 使用POST请求数据,使用JSON传输数据 + + + 传输对象,转换为JSON传输 + + + + + + + 使用Post方法获取字符串结果 + + + + + + + + + + + + 把响应流转换为文本。 + + 响应流对象 + 编码方式 + 响应文本 + + + + 计算坐标点的距离,直线距离 + + 开始的纬度 + 开始的经度 + 结束的纬度 + 结束的经度 + 距离(公里) + + + + 计算两点之间角度 + + + + + + + + + + 传入经度纬度 + + 经度 + 纬度 + + + + 解析base64信息数据 + + 解析编码格式 + 传入的base64位值 + + + + + 根据经纬度获取地图信息(高德) + + 超时时间默认10秒 + 经纬度字符串 + 失败返回"" + + + + 根据经纬度获取地址,多个 + + 超时时间默认10秒 + 经纬度数组 + 1、获取详细地址2、获取市跟区 + 失败返回"" + + + + 根据经纬度获取地址,一个 + + + + 超时时间默认10秒 + + 失败返回"" + + + + 获取路径规划路线 + + 起点经度 + 起点纬度 + 终点经度 + 终点纬度 + 超时时间默认10秒 + 失败返回"" + + + + 根据起点终点经纬度获取路径规划(步行)(高德) + + 终点经纬度 + 超时时间默认10秒 + 起点经纬度 + 失败返回"" + + + 计算两个经纬度之间的距离 + @param lat1 + @param lng1 + @param lat2 + @param lng2 + @return + + + + 根据起点终点经纬度获取路径规划(驾车)(高德) + + 终点经纬度 + 超时时间默认10秒 + 起点经纬度 + 失败返回"" + + + + + + + + + + + + 路径规划实体 + + + + + 结果状态值,值为0或1 + + + + + 返回状态说明 + + + + + + + + + + 驾车路径规划方案数目 + + + + + 驾车路径规划信息列表 + + + + + 驾车路径规划信息列表 + + + + + 起点坐标 + + + + + 终点坐标 + + + + + 驾车换乘方案 + + + + + 驾车换乘方案 + + + + + 行驶距离 + + + + + 预计行驶时间 + + + + + 导航策略 + + + + + 此导航方案道路收费 + + + + 纬度 X + 经度 Y + + + + 代表纬度 X轴 + + + + + 代表经度 Y轴 d + + + + + 正则帮助类。含大量常用正则表达式。 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 判断是否是mac地址 + + mac地址字符串 + + + + + 获取字节数 + str:需要获取的字符串 + + + + + + + + + + + 判断是否字母 + + + + + 判断是否汉字 + + + + + 短信服务 + + + + + 发送短信 + + +86格式 + + + + + + + 字符串帮助类 + + + + + 生成随机字符串,默认32位 + + 随机数长度 + + + + + 生成随机字符串,只包含数字 + + + + + + + 隐藏手机号 + + + + + + + 隐藏身份证号 + + + + + + + 计算时间间隔 + + + + + + + 将对象属性转换为key-value对 + + + + + + + 返回首字母 + + + + 获得字符串首字符字母(大写); + + + + + + + 比较两个时间相差的分钟 + + 开始时间 + 结束时间 + 返回(分钟) + + + + 时间差格式转换 + + 时间差 + 精确度,默认为2 + + + + + 获取时间格式(yyyy-MM-dd HH:mm:ss) + + + + + + + 获取时间格式(yyyy-MM-dd HH:mm:ss) + + + + + + + 得到时间戳 + + + + + + 得到时间戳 + + + + + + 获取某段日期范围内的所有日期 + + 开始日期 + 结束日期 + + + + + 上传单文件 + + + + + + + + + 保存文件 + + + + + + + + + + 保存文件 + + + + + + + + 数据类型转换 + + + + + 转换成Double/Single + + + 小数位数 + + + + + 转换为16进制 + + + 是否小写 + + + + + 16进制转字节数组 + + + + + + + 转换为Base64 + + + + + + + XSS过滤 + + html代码 + 过滤结果 + + + + 路径规划实体 + + + + + 返回状态 + 值为0或1 1:成功;0:失败 + + + + + 返回的状态信息 + status为0时,info返回错误原;否则返回“OK”。详情参阅info状态表 + + + + + 返回结果总数目 + + + + + 路线信息列表 + + + + + 起点坐标 + + + + + 终点坐标 + + + + + 步行方案 + + + + + 起点和终点的步行距离 + 单位:米 + + + + + 步行时间预计 + 单位:秒 + + + + + 返回步行结果列表 + + + + + 每段步行方案 + + + + + 方向 + + + + + 道路名称 + + + + + 此路段距离 + + + + + 此路段预计步行时间 + + + + + 此路段坐标点 + + + + + 后台日志写入服务 + + + + + 任务开始 + + + + + + + 工作线程 + + + + + 用户管理 + + + + + 用户Id + + + + + 权限Id + + + + + 昵称 + + + + + CompanyId + + + + + Account + + + + + SessionKey + + + + + 刷新令牌设置 + + + + + 令牌过期时间(分钟) + + + + + 简单泛型队列 + + + + + + 新增 + + + + + + 取出 + + + + + + + 总数 + + + + + + 清理 + + + + + sql高级代理 + + + + + 阿里云oss文件上传工具类 + + + + + 上传本地文件(走阿里云内网传输) + + + + + + + + 上传一个图片 + + 图片经过base64加密后的结果 + 文件名,例如:Emplyoee/dzzBack.jpg + + + + 上传本地文件 + + + + 返回参数说明 1.本地文件不存在 2.文件oss上已存在 3.上传失败 4.上传成功 + + + + + 上传一个图片 + + 图片字节 + 文件名,例如:Emplyoee/dzzBack.jpg + + + + + 获取鉴权后的URL,URL有效日期默认一小时 + + 文件名,例如:Emplyoee/dzzBack.jpg + + + + + 获取鉴权后的URL + + 文件名,例如:Emplyoee/dzzBack.jpg + URL有效日期,例如:DateTime.Now.AddHours(1) + + + + + 将文件转换成byte[] 数组 + + 文件路径文件名称 + byte[] + + + + 删除文件 + + 文件id + 文件url + + + + 删除文件夹 + + 文件id + 文件url + + + + 判断文件是否存在 + + + + + + + 类型转换 + + + + + + 反射工具 + + + + + 获取字段特性 + + + + + + + + 定时任务 + + + + + 任务名称 + + dilon + + + + 执行间隔时间(单位秒) + + 5 + + + + Cron表达式 + + + + + + 定时器类型 + + + + + 执行次数 + + + + + 请求url + + + + + 请求参数(Post,Put请求用) + + + + + Headers(可以包含如:Authorization授权认证) + 格式:{"Authorization":"userpassword.."} + + + + + 请求类型 + + 2 + + + + 备注 + + + + diff --git a/src/Znyc.Dispatching.Core/coreconfig.Development.json b/src/Znyc.Dispatching.Core/coreconfig.Development.json new file mode 100644 index 0000000..a3676b5 --- /dev/null +++ b/src/Znyc.Dispatching.Core/coreconfig.Development.json @@ -0,0 +1,36 @@ +{ + "SpecificationDocumentSettings": { + "DocumentTitle": "Znyc", + "DocExpansionState": "None", + "GroupOpenApiInfos": [ + { + "Group": "v1", + "Title": "Znyc.Dispatching.Web.Api", + "Description": "", + "Version": "1.0.0" + } + ] + }, + "Cache": { + //测试环境调试配置,非必要请在本地进行开发调试 + "RedisConnectionString": "42.194.147.251:6379,password=vtgA9HXFQQYA9g8Z,defaultDatabase=6" + //"RedisConnectionString": "127.0.0.1:6379" + }, + "SnowId": { + "WorkerId": "1" + }, + "SMSProvider": { + "SecretId": "AKIDGSN2VjJkZ7pIYzcdo0zjDCKCnQpEhXbW", + "SecretKey": "rQDI7fuoUyIEvLT5RWKqkUyGQJwBiU2P", + "SmsSdkAppid": "1400497500", + "Sign": "众能云车", + "Region": "ap-guangzhou", + "TemplateList": [ + { + "ChangePassWordTemplateId": "953233", + "RegisterTemplateId": "953232", + "AddEmployeeTemplateId": "970368" + } + ] + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/coreconfig.Production.json b/src/Znyc.Dispatching.Core/coreconfig.Production.json new file mode 100644 index 0000000..73a0341 --- /dev/null +++ b/src/Znyc.Dispatching.Core/coreconfig.Production.json @@ -0,0 +1,35 @@ +{ + "SpecificationDocumentSettings": { + "DocumentTitle": "Znyc", + "DocExpansionState": "None", + "GroupOpenApiInfos": [ + { + "Group": "v1", + "Title": "Znyc.Dispatching.Web.Api", + "Description": "", + "Version": "1.0.0" + } + ] + }, + "Cache": { + "CacheType": "RedisCache", + "RedisConnectionString": "172.16.0.17:6379,defaultDatabase=6" + }, + "SnowId": { + "WorkerId": "1" + }, + "SMSProvider": { + "SecretId": "AKIDGSN2VjJkZ7pIYzcdo0zjDCKCnQpEhXbW", + "SecretKey": "rQDI7fuoUyIEvLT5RWKqkUyGQJwBiU2P", + "SmsSdkAppid": "1400497500", + "Sign": "众能云车", + "Region": "ap-guangzhou", + "TemplateList": [ + { + "ChangePassWordTemplateId": "953233", + "RegisterTemplateId": "953232", + "AddEmployeeTemplateId": "970368" + } + ] + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Core/coreconfig.Staging.json b/src/Znyc.Dispatching.Core/coreconfig.Staging.json new file mode 100644 index 0000000..bd839a3 --- /dev/null +++ b/src/Znyc.Dispatching.Core/coreconfig.Staging.json @@ -0,0 +1,34 @@ +{ + "SpecificationDocumentSettings": { + "DocumentTitle": "Znyc", + "DocExpansionState": "None", + "GroupOpenApiInfos": [ + { + "Group": "v1", + "Title": "Znyc.Dispatching.Web.Api", + "Description": "", + "Version": "1.0.0" + } + ] + }, + "Cache": { + "RedisConnectionString": "10.0.8.5:6379,password=vtgA9HXFQQYA9g8Z,defaultDatabase=6" + }, + "SnowId": { + "WorkerId": "1" + }, + "SMSProvider": { + "SecretId": "AKIDGSN2VjJkZ7pIYzcdo0zjDCKCnQpEhXbW", + "SecretKey": "rQDI7fuoUyIEvLT5RWKqkUyGQJwBiU2P", + "SmsSdkAppid": "1400497500", + "Sign": "众能云车", + "Region": "ap-guangzhou", + "TemplateList": [ + { + "ChangePassWordTemplateId": "953233", + "RegisterTemplateId": "953232", + "AddEmployeeTemplateId": "970368" + } + ] + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Database.Migrations/MSBuild_Logs/MSBuild_pid-18752_4d6113493f7a4e73b20c145787e2b4ff.failure.txt b/src/Znyc.Dispatching.Database.Migrations/MSBuild_Logs/MSBuild_pid-18752_4d6113493f7a4e73b20c145787e2b4ff.failure.txt new file mode 100644 index 0000000..2e2d9e0 --- /dev/null +++ b/src/Znyc.Dispatching.Database.Migrations/MSBuild_Logs/MSBuild_pid-18752_4d6113493f7a4e73b20c145787e2b4ff.failure.txt @@ -0,0 +1,8 @@ +UNHANDLED EXCEPTIONS FROM PROCESS 18752: +===================== +2021/9/9 11:42:52 +System.IO.IOException: 管道已中断。 + 在 System.IO.Pipes.NamedPipeServerStream.CheckConnectOperationsServer() + 在 System.IO.Pipes.NamedPipeServerStream.BeginWaitForConnection(AsyncCallback callback, Object state) + 在 Microsoft.Build.BackEnd.NodeEndpointOutOfProcBase.PacketPumpProc() +=================== diff --git a/src/Znyc.Dispatching.Database.Migrations/Znyc.Dispatching.Database.Migrations.csproj b/src/Znyc.Dispatching.Database.Migrations/Znyc.Dispatching.Database.Migrations.csproj new file mode 100644 index 0000000..f9f7fe6 --- /dev/null +++ b/src/Znyc.Dispatching.Database.Migrations/Znyc.Dispatching.Database.Migrations.csproj @@ -0,0 +1,10 @@ + + + + net6.0 + + + + + + \ No newline at end of file diff --git a/src/Znyc.Dispatching.EntityFramework.Core/DbContexts/DefaultDbContext.cs b/src/Znyc.Dispatching.EntityFramework.Core/DbContexts/DefaultDbContext.cs new file mode 100644 index 0000000..2bcfd2a --- /dev/null +++ b/src/Znyc.Dispatching.EntityFramework.Core/DbContexts/DefaultDbContext.cs @@ -0,0 +1,177 @@ +using Furion; +using Furion.DatabaseAccessor; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using System; +using System.Linq; +using System.Linq.Expressions; +using Yitter.IdGenerator; +using Znyc.Dispatching.Core; +using Znyc.Dispatching.Core.Entitys; +using Znyc.Dispatching.Core.Extension; + +namespace Znyc.Dispatching.EntityFramework.Core +{ + [AppDbContext("DefaultConnection", DbProvider.MySql)] + public class DefaultDbContext : AppDbContext, IModelBuilderFilter + { + + public DefaultDbContext(DbContextOptions options) : + base(options) + { + //log + + // 启用实体数据更改监听 + // EnabledEntityChangedListener = true; + + // 忽略空值更新 + InsertOrUpdateIgnoreNullValues = true; + } + + /// + /// 配置假删除过滤器 + /// + /// + /// + /// + /// + public void OnCreating(ModelBuilder modelBuilder, EntityTypeBuilder entityBuilder, DbContext dbContext, + Type dbContextLocator) + { + // 配置假删除过滤器 + LambdaExpression expression = FakeDeleteQueryFilterExpression(entityBuilder, dbContext); + if (expression != null) + { + entityBuilder.HasQueryFilter(expression); + } + } + + /// + /// 重写保存之前事件 + /// + /// + /// + protected override void SavingChangesEvent(DbContextEventData eventData, InterceptionResult result) + { + // 获取当前事件对应上下文 + var dbContext = eventData.Context; + + // 强制重新检查一边实体更改信息 + dbContext.ChangeTracker.DetectChanges(); + + //// 获取所有更改,删除,新增的实体,但排除审计实体(避免死循环) + var entities = dbContext.ChangeTracker.Entries() + .Where(u => u.Entity.GetType() != typeof(LogAudit) && + (u.State == EntityState.Modified || u.State == EntityState.Deleted || + u.State == EntityState.Added)) + .ToList(); + + string userId = App.User.IsNull() ? "0" : App.User.FindFirst(ClaimConst.CLAINM_USERID)?.Value; + foreach (Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntry entity in entities) + { + if (entity.Entity.GetType().IsSubclassOf(typeof(DEntityBase))) + { + DEntityBase obj = entity.Entity as DEntityBase; + if (entity.State == EntityState.Added) + { + obj.Id = obj.Id == 0 ? YitIdHelper.NextId() : obj.Id; + obj.CreatedTime = DateTime.Now; + if (!string.IsNullOrEmpty(userId)) + { + obj.CreatedUserId = long.Parse(userId); + obj.CreatedTime = DateTime.Now; + ; + } + } + else if (entity.State == EntityState.Modified) + { + entity.Property(nameof(DEntityBase.CreatedUserId)).IsModified = false; + entity.Property(nameof(DEntityBase.CreatedTime)).IsModified = false; + obj.ModifiedTime = DateTime.Now; + obj.ModifiedUserId = userId.IsNotNull() ? long.Parse(userId) : 0; + } + } + } + + #region 数据库审计 + + //// 获取所有已更改的实体 + //foreach (var entity in entities) + //{ + // // 获取实体类型 + // var entityType = entity.Entity.GetType(); + + // // 获取所有实体有效属性,排除 [NotMapper] 属性 + // var props = entity.OriginalValues.Properties; + + // // 获取实体当前(现在)的值 + // var currentValues = entity.CurrentValues; + + // // 获取数据库中实体的值 + // var databaseValues = entity.GetDatabaseValues(); + + // // 遍历所有属性 + // foreach (var prop in props) + // { + // // 获取属性名 + // var propName = prop.Name; + + // // 获取现在的实体值 + // var newValue = currentValues[propName]; + + // object oldValue = null; + // // 如果是新增数据,则 databaseValues 为空,所以需要判断一下 + // if (databaseValues != null) + // { + // oldValue = databaseValues[propName]; + // } + + // _logger.LogInformation(JsonConvert.SerializeObject(new LogAudit + // { + // TableName = entityType.Name, // 表名 + // ColumnName = propName, // 更新的列 + // NewValue = newValue.ToString(), // 新值 + // OldValue = oldValue.ToString(), // 旧值 + // CreatedTime = DateTime.Now, // 操作时间 + // UserId = ConvertHelper.ConvertTo(userId), // 操作人 + // Operate = entity.State.ToString() // 操作方式:新增、更新、删除 + // })); + + // } + //} + + #endregion 数据库审计 + } + + /// + /// 构建假删除过滤器 + /// + /// + /// + /// + /// + /// + protected static LambdaExpression FakeDeleteQueryFilterExpression(EntityTypeBuilder entityBuilder, + DbContext dbContext, string isDeletedKey = null, object filterValue = null) + { + isDeletedKey ??= "IsDeleted"; + Microsoft.EntityFrameworkCore.Metadata.IMutableEntityType metadata = entityBuilder.Metadata; + Expression finialExpression = Expression.Constant(true); + ParameterExpression parameterExpression = Expression.Parameter(metadata.ClrType, "u"); + // 假删除过滤器 + if (metadata.FindProperty(isDeletedKey) != null) + { + ConstantExpression constantExpression = Expression.Constant(isDeletedKey); + ConstantExpression right = Expression.Constant(filterValue ?? false); + BinaryExpression fakeDeleteQueryExpression = Expression.Equal(Expression.Call(typeof(EF), "Property", new Type[1] + { + typeof(bool) + }, parameterExpression, constantExpression), right); + finialExpression = Expression.AndAlso(finialExpression, fakeDeleteQueryExpression); + } + + return Expression.Lambda(finialExpression, parameterExpression); + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.EntityFramework.Core/EntityFrameworkStartup.cs b/src/Znyc.Dispatching.EntityFramework.Core/EntityFrameworkStartup.cs new file mode 100644 index 0000000..009b42c --- /dev/null +++ b/src/Znyc.Dispatching.EntityFramework.Core/EntityFrameworkStartup.cs @@ -0,0 +1,24 @@ +using Furion; +using Furion.DatabaseAccessor; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; + +namespace Znyc.Dispatching.EntityFramework.Core +{ + [AppStartup(90)] + public class EntityFrameworkStartup : AppStartup + { + public void ConfigureServices(IServiceCollection services) + { + services.AddDatabaseAccessor(options => + { + //默认数据 + options.AddDb($"{DbProvider.MySql}", + opt => + { + opt.UseBatchEF_MySQLPomelo(); + }); + }); + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.EntityFramework.Core/Znyc.Dispatching.EntityFramework.Core.csproj b/src/Znyc.Dispatching.EntityFramework.Core/Znyc.Dispatching.EntityFramework.Core.csproj new file mode 100644 index 0000000..45a0cdb --- /dev/null +++ b/src/Znyc.Dispatching.EntityFramework.Core/Znyc.Dispatching.EntityFramework.Core.csproj @@ -0,0 +1,36 @@ + + + + net6.0 + + + + + + + + + + + + Always + + + Always + + + Always + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Znyc.Dispatching.EntityFramework.Core/dbsettings.Development.json b/src/Znyc.Dispatching.EntityFramework.Core/dbsettings.Development.json new file mode 100644 index 0000000..1707909 --- /dev/null +++ b/src/Znyc.Dispatching.EntityFramework.Core/dbsettings.Development.json @@ -0,0 +1,7 @@ +{ + "ConnectionStrings": { + //ԻãDZҪڱؽп + "DefaultConnection": "Server=42.194.147.251;Port=43306;Database=znyc_dispatching;Uid=guest;Pwd=4Y2e2WtekfDYWfT8;Charset=utf8mb4;AllowLoadLocalInfile=true" + //"DefaultConnection": "Server=127.0.0.1;Port=3306;Database=znyc_dispatching;Uid=root;Pwd=123456;Charset=utf8mb4" + } +} diff --git a/src/Znyc.Dispatching.EntityFramework.Core/dbsettings.Production.json b/src/Znyc.Dispatching.EntityFramework.Core/dbsettings.Production.json new file mode 100644 index 0000000..829eb6b --- /dev/null +++ b/src/Znyc.Dispatching.EntityFramework.Core/dbsettings.Production.json @@ -0,0 +1,6 @@ +{ + "ConnectionStrings": { + "DefaultConnection": "Server=172.16.0.6; Port=3306; Database=znyc_dispatching; Uid=znyc; Pwd=UhcAoRR5A3hnt^%U; Charset=utf8mb4" + + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.EntityFramework.Core/dbsettings.Staging.json b/src/Znyc.Dispatching.EntityFramework.Core/dbsettings.Staging.json new file mode 100644 index 0000000..c614f41 --- /dev/null +++ b/src/Znyc.Dispatching.EntityFramework.Core/dbsettings.Staging.json @@ -0,0 +1,5 @@ +{ + "ConnectionStrings": { + "DefaultConnection": "Server=10.0.8.5;Port=43306;Database=znyc_dispatching;Uid=guest;Pwd=4Y2e2WtekfDYWfT8; Charset=utf8mb4" + } +} diff --git a/src/Znyc.Dispatching.Evenbus/EventBusSubscriptions/InMemoryEventBusSubscriptionsManager.cs b/src/Znyc.Dispatching.Evenbus/EventBusSubscriptions/InMemoryEventBusSubscriptionsManager.cs new file mode 100644 index 0000000..5a0b709 --- /dev/null +++ b/src/Znyc.Dispatching.Evenbus/EventBusSubscriptions/InMemoryEventBusSubscriptionsManager.cs @@ -0,0 +1,174 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Znyc.Dispatching.Evenbus +{ + /// + /// 基于内存 + /// 事件总线订阅管理器 + /// 单例模式 + /// + public class InMemoryEventBusSubscriptionsManager : IEventBusSubscriptionsManager + { + private readonly List _eventTypes; + private readonly Dictionary> _handlers; + + public InMemoryEventBusSubscriptionsManager() + { + _handlers = new Dictionary>(); + _eventTypes = new List(); + } + + public event EventHandler OnEventRemoved; + + public bool IsEmpty => !_handlers.Keys.Any(); + + public void Clear() + { + _handlers.Clear(); + } + + /// + /// 添加动态订阅 + /// + /// 约束:动态事件处理器接口 + /// + public void AddDynamicSubscription(string eventName) + where TH : IDynamicIntegrationEventHandler + { + DoAddSubscription(typeof(TH), eventName, true); + } + + /// + /// 添加订阅 + /// + /// 约束:事件 + /// 约束:事件处理器接口<事件> + public void AddSubscription() + where T : IntegrationEvent + where TH : IIntegrationEventHandler + { + var eventName = GetEventKey(); + + DoAddSubscription(typeof(TH), eventName, false); + + if (!_eventTypes.Contains(typeof(T))) _eventTypes.Add(typeof(T)); + } + + /// + /// 移除动态订阅 + /// + /// + /// + public void RemoveDynamicSubscription(string eventName) + where TH : IDynamicIntegrationEventHandler + { + var handlerToRemove = FindDynamicSubscriptionToRemove(eventName); + DoRemoveHandler(eventName, handlerToRemove); + } + + public void RemoveSubscription() + where TH : IIntegrationEventHandler + where T : IntegrationEvent + { + var handlerToRemove = FindSubscriptionToRemove(); + var eventName = GetEventKey(); + DoRemoveHandler(eventName, handlerToRemove); + } + + public IEnumerable GetHandlersForEvent() where T : IntegrationEvent + { + var key = GetEventKey(); + return GetHandlersForEvent(key); + } + + public IEnumerable GetHandlersForEvent(string eventName) + { + return _handlers[eventName]; + } + + public bool HasSubscriptionsForEvent() where T : IntegrationEvent + { + var key = GetEventKey(); + return HasSubscriptionsForEvent(key); + } + + public bool HasSubscriptionsForEvent(string eventName) + { + return _handlers.ContainsKey(eventName); + } + + public Type GetEventTypeByName(string eventName) + { + return _eventTypes.SingleOrDefault(t => t.Name == eventName); + } + + public string GetEventKey() + { + return typeof(T).Name; + } + + private void DoAddSubscription(Type handlerType, string eventName, bool isDynamic) + { + if (!HasSubscriptionsForEvent(eventName)) _handlers.Add(eventName, new List()); + + if (_handlers[eventName].Any(s => s.HandlerType == handlerType)) + throw new ArgumentException( + $"Handler Type {handlerType.Name} already registered for '{eventName}'", nameof(handlerType)); + + if (isDynamic) + _handlers[eventName].Add(SubscriptionInfo.Dynamic(handlerType)); + else + _handlers[eventName].Add(SubscriptionInfo.Typed(handlerType)); + } + + private void DoRemoveHandler(string eventName, SubscriptionInfo subsToRemove) + { + if (subsToRemove != null) + { + _handlers[eventName].Remove(subsToRemove); + if (!_handlers[eventName].Any()) + { + _handlers.Remove(eventName); + var eventType = _eventTypes.SingleOrDefault(e => e.Name == eventName); + if (eventType != null) _eventTypes.Remove(eventType); + RaiseOnEventRemoved(eventName); + } + } + } + + private void RaiseOnEventRemoved(string eventName) + { + var handler = OnEventRemoved; + handler?.Invoke(this, eventName); + } + + private SubscriptionInfo FindDynamicSubscriptionToRemove(string eventName) + where TH : IDynamicIntegrationEventHandler + { + return DoFindSubscriptionToRemove(eventName, typeof(TH)); + } + + /// + /// 查询订阅并移除 + /// + /// + /// + /// + private SubscriptionInfo FindSubscriptionToRemove() + where T : IntegrationEvent + where TH : IIntegrationEventHandler + { + var eventName = GetEventKey(); + return DoFindSubscriptionToRemove(eventName, typeof(TH)); + } + + private SubscriptionInfo DoFindSubscriptionToRemove(string eventName, Type handlerType) + { + if (!HasSubscriptionsForEvent(eventName)) return null; + + return _handlers[eventName].SingleOrDefault(s => s.HandlerType == handlerType); + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Evenbus/EventBusSubscriptions/SubscriptionInfo.cs b/src/Znyc.Dispatching.Evenbus/EventBusSubscriptions/SubscriptionInfo.cs new file mode 100644 index 0000000..b059ac8 --- /dev/null +++ b/src/Znyc.Dispatching.Evenbus/EventBusSubscriptions/SubscriptionInfo.cs @@ -0,0 +1,29 @@ +using System; + +namespace Znyc.Dispatching.Evenbus +{ + /// + /// 订阅信息模型 + /// + public class SubscriptionInfo + { + private SubscriptionInfo(bool isDynamic, Type handlerType) + { + IsDynamic = isDynamic; + HandlerType = handlerType; + } + + public bool IsDynamic { get; } + public Type HandlerType { get; } + + public static SubscriptionInfo Dynamic(Type handlerType) + { + return new(true, handlerType); + } + + public static SubscriptionInfo Typed(Type handlerType) + { + return new(false, handlerType); + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Evenbus/Eventbus/IDynamicIntegrationEventHandler.cs b/src/Znyc.Dispatching.Evenbus/Eventbus/IDynamicIntegrationEventHandler.cs new file mode 100644 index 0000000..aacf97a --- /dev/null +++ b/src/Znyc.Dispatching.Evenbus/Eventbus/IDynamicIntegrationEventHandler.cs @@ -0,0 +1,13 @@ +using System.Threading.Tasks; + +namespace Znyc.Dispatching.Evenbus +{ + /// + /// 动态集成事件处理程序 + /// 接口 + /// + public interface IDynamicIntegrationEventHandler + { + Task Handle(dynamic eventData); + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Evenbus/Eventbus/IEventBus.cs b/src/Znyc.Dispatching.Evenbus/Eventbus/IEventBus.cs new file mode 100644 index 0000000..19642ae --- /dev/null +++ b/src/Znyc.Dispatching.Evenbus/Eventbus/IEventBus.cs @@ -0,0 +1,49 @@ +namespace Znyc.Dispatching.Evenbus +{ + /// + /// 事件总线 + /// 接口 + /// + public interface IEventBus + { + /// + /// 发布 + /// + /// 事件模型 + void Publish(IntegrationEvent @event); + + /// + /// 订阅 + /// + /// 约束:事件模型 + /// 约束:事件处理器<事件模型> + void Subscribe() + where T : IntegrationEvent + where TH : IIntegrationEventHandler; + + /// + /// 取消订阅 + /// + /// + /// + void Unsubscribe() + where TH : IIntegrationEventHandler + where T : IntegrationEvent; + + /// + /// 动态订阅 + /// + /// 约束:事件处理器 + /// + void SubscribeDynamic(string eventName) + where TH : IDynamicIntegrationEventHandler; + + /// + /// 动态取消订阅 + /// + /// + /// + void UnsubscribeDynamic(string eventName) + where TH : IDynamicIntegrationEventHandler; + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Evenbus/Eventbus/IEventBusSubscriptionsManager.cs b/src/Znyc.Dispatching.Evenbus/Eventbus/IEventBusSubscriptionsManager.cs new file mode 100644 index 0000000..7207d64 --- /dev/null +++ b/src/Znyc.Dispatching.Evenbus/Eventbus/IEventBusSubscriptionsManager.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; + +namespace Znyc.Dispatching.Evenbus +{ + /// + /// 事件总线订阅管理器 + /// 接口 + /// + public interface IEventBusSubscriptionsManager + { + bool IsEmpty { get; } + + event EventHandler OnEventRemoved; + + void AddDynamicSubscription(string eventName) + where TH : IDynamicIntegrationEventHandler; + + void AddSubscription() + where T : IntegrationEvent + where TH : IIntegrationEventHandler; + + void RemoveSubscription() + where TH : IIntegrationEventHandler + where T : IntegrationEvent; + + void RemoveDynamicSubscription(string eventName) + where TH : IDynamicIntegrationEventHandler; + + bool HasSubscriptionsForEvent() where T : IntegrationEvent; + + bool HasSubscriptionsForEvent(string eventName); + + Type GetEventTypeByName(string eventName); + + void Clear(); + + IEnumerable GetHandlersForEvent() where T : IntegrationEvent; + + IEnumerable GetHandlersForEvent(string eventName); + + string GetEventKey(); + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Evenbus/Eventbus/IIntegrationEventHandler.cs b/src/Znyc.Dispatching.Evenbus/Eventbus/IIntegrationEventHandler.cs new file mode 100644 index 0000000..e75e331 --- /dev/null +++ b/src/Znyc.Dispatching.Evenbus/Eventbus/IIntegrationEventHandler.cs @@ -0,0 +1,23 @@ +using System.Threading.Tasks; + +namespace Znyc.Dispatching.Evenbus +{ + /// + /// 集成事件处理程序 + /// 泛型接口 + /// + /// + public interface IIntegrationEventHandler : IIntegrationEventHandler + where TIntegrationEvent : IntegrationEvent + { + Task Handle(TIntegrationEvent @event); + } + + /// + /// 集成事件处理程序 + /// 基 接口 + /// + public interface IIntegrationEventHandler + { + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Evenbus/Eventbus/IntegrationEvent.cs b/src/Znyc.Dispatching.Evenbus/Eventbus/IntegrationEvent.cs new file mode 100644 index 0000000..7be3d83 --- /dev/null +++ b/src/Znyc.Dispatching.Evenbus/Eventbus/IntegrationEvent.cs @@ -0,0 +1,29 @@ +using System; +using Newtonsoft.Json; + +namespace Znyc.Dispatching.Evenbus +{ + /// + /// 事件模型 + /// 基类 + /// + public class IntegrationEvent + { + public IntegrationEvent() + { + Id = Guid.NewGuid(); + CreationDate = DateTime.UtcNow; + } + + [JsonConstructor] + public IntegrationEvent(Guid id, DateTime createDate) + { + Id = id; + CreationDate = createDate; + } + + [JsonProperty] public Guid Id { get; private set; } + + [JsonProperty] public DateTime CreationDate { get; private set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Evenbus/RabbitMQPersistent/EventBusRabbitMQ.cs b/src/Znyc.Dispatching.Evenbus/RabbitMQPersistent/EventBusRabbitMQ.cs new file mode 100644 index 0000000..63c9a67 --- /dev/null +++ b/src/Znyc.Dispatching.Evenbus/RabbitMQPersistent/EventBusRabbitMQ.cs @@ -0,0 +1,336 @@ +using System; +using System.Net.Sockets; +using System.Text; +using System.Threading.Tasks; +using Autofac; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Polly; +using RabbitMQ.Client; +using RabbitMQ.Client.Events; +using RabbitMQ.Client.Exceptions; +using Znyc.Dispatching.Common.Extensions; + +namespace Znyc.Dispatching.Evenbus +{ + /// + /// 基于RabbitMQ的事件总线 + /// + public class EventBusRabbitMQ : IEventBus, IDisposable + { + private const string BROKER_NAME = "blogcore_event_bus"; + private readonly ILifetimeScope _autofac; + private readonly ILogger _logger; + + private readonly IRabbitMQPersistentConnection _persistentConnection; + private readonly int _retryCount; + private readonly IEventBusSubscriptionsManager _subsManager; + private readonly string AUTOFAC_SCOPE_NAME = "dispatching_event_bus"; + + private IModel _consumerChannel; + private string _queueName; + + /// + /// RabbitMQ事件总线 + /// + /// RabbitMQ持久连接 + /// 日志 + /// autofac容器 + /// 事件总线订阅管理器 + /// 队列名称 + /// 重试次数 + public EventBusRabbitMQ(IRabbitMQPersistentConnection persistentConnection, ILogger logger, + ILifetimeScope autofac, + IEventBusSubscriptionsManager subsManager, + string? queueName = null, + int retryCount = 5) + { + _persistentConnection = + persistentConnection ?? throw new ArgumentNullException(nameof(persistentConnection)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _subsManager = subsManager ?? new InMemoryEventBusSubscriptionsManager(); + _queueName = queueName; + _consumerChannel = CreateConsumerChannel(); + _retryCount = retryCount; + _autofac = autofac; + _subsManager.OnEventRemoved += SubsManager_OnEventRemoved; + } + + public void Dispose() + { + if (_consumerChannel != null) _consumerChannel.Dispose(); + + _subsManager.Clear(); + } + + /// + /// 发布 + /// + /// 事件模型 + public void Publish(IntegrationEvent @event) + { + if (!_persistentConnection.IsConnected) _persistentConnection.TryConnect(); + + var policy = Policy.Handle() + .Or() + .WaitAndRetry(_retryCount, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), + (ex, time) => + { + _logger.LogWarning(ex, + "Could not publish event: {EventId} after {Timeout}s ({ExceptionMessage})", @event.Id, + $"{time.TotalSeconds:n1}", ex.Message); + }); + + var eventName = @event.GetType().Name; + + _logger.LogTrace("Creating RabbitMQ channel to publish event: {EventId} ({EventName})", @event.Id, + eventName); + + using (var channel = _persistentConnection.CreateModel()) + { + _logger.LogTrace("Declaring RabbitMQ exchange to publish event: {EventId}", @event.Id); + + channel.ExchangeDeclare(BROKER_NAME, "direct"); + + var message = JsonConvert.SerializeObject(@event); + var body = Encoding.UTF8.GetBytes(message); + + policy.Execute(() => + { + var properties = channel.CreateBasicProperties(); + properties.DeliveryMode = 2; // persistent + + _logger.LogTrace("Publishing event to RabbitMQ: {EventId}", @event.Id); + + channel.BasicPublish( + BROKER_NAME, + eventName, + true, + properties, + body); + }); + } + } + + /// + /// 订阅 + /// 动态 + /// + /// 事件处理器 + /// 事件名 + public void SubscribeDynamic(string eventName) + where TH : IDynamicIntegrationEventHandler + { + _logger.LogInformation("Subscribing to dynamic event {EventName} with {EventHandler}", eventName, + typeof(TH).GetGenericTypeName()); + + DoInternalSubscription(eventName); + _subsManager.AddDynamicSubscription(eventName); + StartBasicConsume(); + } + + /// + /// 订阅 + /// + /// 约束:事件模型 + /// 约束:事件处理器<事件模型> + public void Subscribe() + where T : IntegrationEvent + where TH : IIntegrationEventHandler + { + var eventName = _subsManager.GetEventKey(); + DoInternalSubscription(eventName); + + _logger.LogInformation("Subscribing to event {EventName} with {EventHandler}", eventName, + typeof(TH).GetGenericTypeName()); + + Console.WriteLine($"Subscribing to event {eventName} with {typeof(TH).GetGenericTypeName()}"); + + _subsManager.AddSubscription(); + StartBasicConsume(); + } + + /// + /// 取消订阅 + /// + /// + /// + public void Unsubscribe() + where T : IntegrationEvent + where TH : IIntegrationEventHandler + { + var eventName = _subsManager.GetEventKey(); + + _logger.LogInformation("Unsubscribing from event {EventName}", eventName); + + _subsManager.RemoveSubscription(); + } + + public void UnsubscribeDynamic(string eventName) + where TH : IDynamicIntegrationEventHandler + { + _subsManager.RemoveDynamicSubscription(eventName); + } + + /// + /// 订阅管理器事件 + /// + /// + /// + private void SubsManager_OnEventRemoved(object sender, string eventName) + { + if (!_persistentConnection.IsConnected) _persistentConnection.TryConnect(); + + using (var channel = _persistentConnection.CreateModel()) + { + channel.QueueUnbind(_queueName, + BROKER_NAME, + eventName); + + if (_subsManager.IsEmpty) + { + _queueName = string.Empty; + _consumerChannel.Close(); + } + } + } + + private void DoInternalSubscription(string eventName) + { + var containsKey = _subsManager.HasSubscriptionsForEvent(eventName); + if (!containsKey) + { + if (!_persistentConnection.IsConnected) _persistentConnection.TryConnect(); + + using (var channel = _persistentConnection.CreateModel()) + { + channel.QueueBind(_queueName, + BROKER_NAME, + eventName); + } + } + } + + /// + /// 开始基本消费 + /// + private void StartBasicConsume() + { + _logger.LogTrace("Starting RabbitMQ basic consume"); + + if (_consumerChannel != null) + { + var consumer = new AsyncEventingBasicConsumer(_consumerChannel); + + consumer.Received += Consumer_Received; + + _consumerChannel.BasicConsume( + _queueName, + false, + consumer); + } + else + { + _logger.LogError("StartBasicConsume can't call on _consumerChannel == null"); + } + } + + /// + /// 消费者接受到 + /// + /// + /// + /// + private async Task Consumer_Received(object sender, BasicDeliverEventArgs eventArgs) + { + var eventName = eventArgs.RoutingKey; + var message = Encoding.UTF8.GetString(eventArgs.Body.Span); + + try + { + if (message.ToLowerInvariant().Contains("throw-fake-exception")) + throw new InvalidOperationException($"Fake exception requested: \"{message}\""); + + await ProcessEvent(eventName, message); + } + catch (Exception ex) + { + _logger.LogWarning(ex, "----- ERROR Processing message \"{Message}\"", message); + } + + // Even on exception we take the message off the queue. + // in a REAL WORLD app this should be handled with a Dead Letter Exchange (DLX). + // For more information see: https://www.rabbitmq.com/dlx.html + _consumerChannel.BasicAck(eventArgs.DeliveryTag, false); + } + + /// + /// 创造消费通道 + /// + /// + private IModel CreateConsumerChannel() + { + if (!_persistentConnection.IsConnected) _persistentConnection.TryConnect(); + + _logger.LogTrace("Creating RabbitMQ consumer channel"); + + var channel = _persistentConnection.CreateModel(); + + channel.ExchangeDeclare(BROKER_NAME, + "direct"); + + channel.QueueDeclare(_queueName, + true, + false, + false, + null); + + channel.CallbackException += (sender, ea) => + { + _logger.LogWarning(ea.Exception, "Recreating RabbitMQ consumer channel"); + + _consumerChannel.Dispose(); + _consumerChannel = CreateConsumerChannel(); + StartBasicConsume(); + }; + + return channel; + } + + private async Task ProcessEvent(string eventName, string message) + { + _logger.LogTrace("Processing RabbitMQ event: {EventName}", eventName); + + if (_subsManager.HasSubscriptionsForEvent(eventName)) + using (var scope = _autofac.BeginLifetimeScope(AUTOFAC_SCOPE_NAME)) + { + var subscriptions = _subsManager.GetHandlersForEvent(eventName); + foreach (var subscription in subscriptions) + if (subscription.IsDynamic) + { + var handler = + scope.ResolveOptional(subscription.HandlerType) as IDynamicIntegrationEventHandler; + if (handler == null) continue; + dynamic eventData = JObject.Parse(message); + + await Task.Yield(); + await handler.Handle(eventData); + } + else + { + var handler = scope.ResolveOptional(subscription.HandlerType); + if (handler == null) continue; + var eventType = _subsManager.GetEventTypeByName(eventName); + var integrationEvent = JsonConvert.DeserializeObject(message, eventType); + var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType); + + await Task.Yield(); + await (Task) concreteType.GetMethod("Handle").Invoke(handler, new[] {integrationEvent}); + } + } + else + _logger.LogWarning("No subscription for RabbitMQ event: {EventName}", eventName); + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Evenbus/RabbitMQPersistent/IRabbitMQPersistentConnection.cs b/src/Znyc.Dispatching.Evenbus/RabbitMQPersistent/IRabbitMQPersistentConnection.cs new file mode 100644 index 0000000..2d948ef --- /dev/null +++ b/src/Znyc.Dispatching.Evenbus/RabbitMQPersistent/IRabbitMQPersistentConnection.cs @@ -0,0 +1,19 @@ +using System; +using RabbitMQ.Client; + +namespace Znyc.Dispatching.Evenbus +{ + /// + /// RabbitMQ持久连接 + /// 接口 + /// + public interface IRabbitMQPersistentConnection + : IDisposable + { + bool IsConnected { get; } + + bool TryConnect(); + + IModel CreateModel(); + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Evenbus/RabbitMQPersistent/RabbitMQPersistentConnection.cs b/src/Znyc.Dispatching.Evenbus/RabbitMQPersistent/RabbitMQPersistentConnection.cs new file mode 100644 index 0000000..b7ba470 --- /dev/null +++ b/src/Znyc.Dispatching.Evenbus/RabbitMQPersistent/RabbitMQPersistentConnection.cs @@ -0,0 +1,161 @@ +using System; +using System.IO; +using System.Net.Sockets; +using Microsoft.Extensions.Logging; +using Polly; +using RabbitMQ.Client; +using RabbitMQ.Client.Events; +using RabbitMQ.Client.Exceptions; + +namespace Znyc.Dispatching.Evenbus +{ + /// + /// RabbitMQ持久连接 + /// + public class RabbitMQPersistentConnection + : IRabbitMQPersistentConnection + { + private readonly IConnectionFactory _connectionFactory; + private readonly ILogger _logger; + private readonly int _retryCount; + private IConnection _connection; + private bool _disposed; + + private readonly object sync_root = new(); + + public RabbitMQPersistentConnection(IConnectionFactory connectionFactory, + ILogger logger, + int retryCount = 5) + { + _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _retryCount = retryCount; + } + + /// + /// 是否已连接 + /// + public bool IsConnected => _connection != null && _connection.IsOpen && !_disposed; + + /// + /// 创建Model + /// + /// + public IModel CreateModel() + { + if (!IsConnected) + throw new InvalidOperationException("No RabbitMQ connections are available to perform this action"); + + return _connection.CreateModel(); + } + + /// + /// 释放 + /// + public void Dispose() + { + if (_disposed) return; + + _disposed = true; + + try + { + _connection.Dispose(); + } + catch (IOException ex) + { + _logger.LogCritical(ex.ToString()); + } + } + + /// + /// 连接 + /// + /// + public bool TryConnect() + { + _logger.LogInformation("RabbitMQ Client is trying to connect"); + + lock (sync_root) + { + var policy = Policy.Handle() + .Or() + .WaitAndRetry(_retryCount, + retryAttempt => + TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), + (ex, time) => + { + _logger.LogWarning(ex, + "RabbitMQ Client could not connect after {TimeOut}s ({ExceptionMessage})", + $"{time.TotalSeconds:n1}", ex.Message); + } + ); + + policy.Execute(() => + { + _connection = _connectionFactory + .CreateConnection(); + }); + + if (IsConnected) + { + _connection.ConnectionShutdown += OnConnectionShutdown; + _connection.CallbackException += OnCallbackException; + _connection.ConnectionBlocked += OnConnectionBlocked; + + _logger.LogInformation( + "RabbitMQ Client acquired a persistent connection to '{HostName}' and is subscribed to failure events", + _connection.Endpoint.HostName); + + return true; + } + + _logger.LogCritical("FATAL ERROR: RabbitMQ connections could not be created and opened"); + + return false; + } + } + + /// + /// 连接被阻断 + /// + /// + /// + private void OnConnectionBlocked(object sender, ConnectionBlockedEventArgs e) + { + if (_disposed) return; + + _logger.LogWarning("A RabbitMQ connection is shutdown. Trying to re-connect..."); + + TryConnect(); + } + + /// + /// 连接出现异常 + /// + /// + /// + private void OnCallbackException(object sender, CallbackExceptionEventArgs e) + { + if (_disposed) return; + + _logger.LogWarning("A RabbitMQ connection throw exception. Trying to re-connect..."); + + TryConnect(); + } + + /// + /// 连接被关闭 + /// + /// + /// + private void OnConnectionShutdown(object sender, ShutdownEventArgs reason) + { + if (_disposed) return; + + _logger.LogWarning("A RabbitMQ connection is on shutdown. Trying to re-connect..."); + + TryConnect(); + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Evenbus/Znyc.Dispatching.Evenbus.csproj b/src/Znyc.Dispatching.Evenbus/Znyc.Dispatching.Evenbus.csproj new file mode 100644 index 0000000..39a3245 --- /dev/null +++ b/src/Znyc.Dispatching.Evenbus/Znyc.Dispatching.Evenbus.csproj @@ -0,0 +1,21 @@ + + + + net5.0 + enable + + + + + + + + + + + + + + + + diff --git a/src/Znyc.Dispatching.MongoDb.Repository/Collection/GpsCarAccDuration.cs b/src/Znyc.Dispatching.MongoDb.Repository/Collection/GpsCarAccDuration.cs new file mode 100644 index 0000000..167a414 --- /dev/null +++ b/src/Znyc.Dispatching.MongoDb.Repository/Collection/GpsCarAccDuration.cs @@ -0,0 +1,58 @@ +using Pursue.Extension.MongoDB; +using System; + +namespace Znyc.Dispatching.MongoDb.Repository.Collection +{ + public class GpsCarAccDuration : MongoEntityPrimaryKey + { + /// + /// 所属车辆 + /// + public long VehicleId { get; set; } + + /// + /// 所属公司 + /// + public long CompanyId { get; set; } + + /// + /// 车辆编号 + /// + public string VehicleCode { get; set; } + + /// + /// 车牌号 + /// + public string VehiclePlate { get; set; } + + /// + /// ACC状态 + /// + public int AccState { get; set; } + + /// + /// ACC持续时长 + /// + public string AccDurationTime { get; set; } + + /// + /// 开始时间 + /// + public DateTime TimeBegin { get; set; } + + /// + /// 结束时间 + /// + public DateTime TimeEnd { get; set; } + + /// + /// 更新时间 + /// + public DateTime ModifiedTime { get; set; } + + /// + /// 创建时间 + /// + public DateTime CreatedTime { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.MongoDb.Repository/Collection/GpsRealTime.cs b/src/Znyc.Dispatching.MongoDb.Repository/Collection/GpsRealTime.cs new file mode 100644 index 0000000..f545c23 --- /dev/null +++ b/src/Znyc.Dispatching.MongoDb.Repository/Collection/GpsRealTime.cs @@ -0,0 +1,149 @@ +using Pursue.Extension.MongoDB; +using System; + +namespace Znyc.Dispatching.MongoDb.Repository.Collection +{ + public class GpsRealTime : MongoEntityPrimaryKey + { + + private string _accDurationTime; + + /// + /// 车辆ID + /// + public long VehicleId { get; set; } + + /// + /// 车辆编号 + /// + public string VehicleCode { get; set; } + + /// + /// 车牌号 + /// + public string VehiclePlate { get; set; } + + /// + /// 司机 + /// + public long VehicleDriver { get; set; } + + /// + /// 司机联系电话 + /// + public string DriverPhone { get; set; } + + /// + /// 状态 + /// + public int Status { get; set; } + + /// + /// Gps状态 + /// + public int GpsState { get; set; } + + /// + /// Gps数据产生时间 + /// + public DateTime GpsTime { get; set; } + + /// + /// Gps服务器接收时间 + /// + public DateTime RecTime { get; set; } + + /// + /// 经度 + /// + public decimal Longitude { get; set; } + + /// + /// 纬度 + /// + public decimal Latitude { get; set; } + + /// + /// 方向 + /// + public int Direct { get; set; } + + /// + /// 速度 + /// + public int Speed { get; set; } + + /// + /// 静止时间 + /// + public string DurationTime { get; set; } + + /// + /// 打火状态 + /// + public int Acc { get; set; } + + /// + /// ACC持续时间 + /// + public string AccDurationTime + { + get => GpsState == 0 ? DurationTime : _accDurationTime; + set => _accDurationTime = value; + } + + /// + /// 地址 + /// + public string Address { get; set; } + + /// + /// SIM卡号 + /// + public string SimNo { get; set; } + + /// + /// 公司ID + /// + public long CompanyId { get; set; } + + /// + /// 修改时间 + /// + public DateTime ModifiedTime { get; set; } + + /// + /// 创建时间 + /// + public DateTime CreatedTime { get; set; } + + /// + /// 设备号 + /// + public string TerminalNo { get; set; } + + /// + /// 是否工作 + /// + public int Work { get; set; } + + /// + /// 工作时长 + /// + public string WorkDurationTime { get; set; } + + /// + /// Gps信号模式 1 GPS,2 基站 + /// + public int GpsMode { get; set; } + + /// + /// 位置基本信息报警标志位 --欠压 + /// + public int GpsAlert { get; set; } + /// + /// 掉电 + /// + public int Useing { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.MongoDb.Repository/Collection/PgyAlertStraightDtl.cs b/src/Znyc.Dispatching.MongoDb.Repository/Collection/PgyAlertStraightDtl.cs new file mode 100644 index 0000000..92b9845 --- /dev/null +++ b/src/Znyc.Dispatching.MongoDb.Repository/Collection/PgyAlertStraightDtl.cs @@ -0,0 +1,57 @@ +using Pursue.Extension.MongoDB; +using System; + +namespace Znyc.Dispatching.MongoDb.Repository.Collection +{ + /// + /// 警报 + /// + public class PgyAlertStraightDtl : MongoEntityPrimaryKey + { + /// + /// 所属公司 + /// + public long CompanyId { get; set; } + + /// + /// 车辆自编号 + /// + public string VehicleCode { get; set; } + + /// + /// 车牌号 + /// + public string VehiclePlate { get; set; } + + /// + /// 经度 + /// + public decimal Longitude { get; set; } + + /// + /// 纬度 + /// + public decimal Latitude { get; set; } + + /// + /// 方向 + /// + public int Direct { get; set; } + + /// + /// 速度 + /// + public int Speed { get; set; } + + /// + /// Gps数据产生时间 + /// + //[BsonDateTimeOptions(Kind = DateTimeKind.Unspecified)] + public DateTime GpsTime { get; set; } + + /// + /// 地址 + /// + public string Address { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.MongoDb.Repository/Collection/PgyAlertStraightLis.cs b/src/Znyc.Dispatching.MongoDb.Repository/Collection/PgyAlertStraightLis.cs new file mode 100644 index 0000000..96f99a1 --- /dev/null +++ b/src/Znyc.Dispatching.MongoDb.Repository/Collection/PgyAlertStraightLis.cs @@ -0,0 +1,72 @@ +using Pursue.Extension.MongoDB; +using System; + +namespace Znyc.Dispatching.MongoDb.Repository.Collection +{ + /// + /// 警报 + /// + public class PgyAlertStraightLis : MongoEntityPrimaryKey + { + // public object _id { get; set; } + /// + /// 车辆ID + /// + public long VehicleId { get; set; } + + /// + /// 公司id + /// + public long CompanyId { get; set; } + + /// + /// 车牌号 + /// + public string VehiclePlate { get; set; } + + /// + /// 车辆编号 + /// + public string VehicleCode { get; set; } + + /// + /// gps定位时间 + /// + public DateTime GpsTime { get; set; } + + /// + /// 设备名称(手机号) + /// + public string DeviceName { get; set; } + + /// + /// 报警类型 + /// + public string AlertTypeName { get; set; } + + /// + /// 连续超速第几个点,页面统计需要,轨迹连续点(超速的次数) + /// + public int ContinuteCount { get; set; } + + /// + /// 连续超速时长,页面统计需要,轨迹连续时长(超速的时长) + /// + public double ContinuteTimeLong { get; set; } + + /// + /// 报警最高速度 + /// + public int TopSpeed { get; set; } + + /// + /// 报警最低速度 + /// + public int LowSpeed { get; set; } + + /// + /// 主表的id,方便附表查询 + /// + public string AlertGuid { get; set; } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.MongoDb.Repository/Collection/StopPointCalculation.cs b/src/Znyc.Dispatching.MongoDb.Repository/Collection/StopPointCalculation.cs new file mode 100644 index 0000000..41603f0 --- /dev/null +++ b/src/Znyc.Dispatching.MongoDb.Repository/Collection/StopPointCalculation.cs @@ -0,0 +1,51 @@ +using System; + +namespace Znyc.Dispatching.MongoDb.Repository.Collection +{ + public class StopPointCalculation + { + public object _id { get; set; } + + /// + /// 经度 + /// + public decimal Longitude { get; set; } + + /// + /// 纬度 + /// + public decimal Latitude { get; set; } + + /// + /// 地址 + /// + public string Address { get; set; } + + /// + /// GPS时间 + /// + public DateTime StartTime { get; set; } + + /// + /// 状态 + /// + public int Status { get; set; } + + /// + /// 停留点描述 + /// + public string State { get; set; } + + /// + /// 停留时长描述 + /// + public string Behavior { get; set; } + + /// + /// 总里程 + /// + public decimal TotalDistance { get; set; } + + public DateTime EndTime { get; set; } + } +} diff --git a/src/Znyc.Dispatching.MongoDb.Repository/Collection/VehicleGps.cs b/src/Znyc.Dispatching.MongoDb.Repository/Collection/VehicleGps.cs new file mode 100644 index 0000000..f410cbc --- /dev/null +++ b/src/Znyc.Dispatching.MongoDb.Repository/Collection/VehicleGps.cs @@ -0,0 +1,95 @@ +using Pursue.Extension.MongoDB; +using System; + +namespace Znyc.Dispatching.MongoDb.Repository.Collection +{ + public class VehicleGps : MongoEntityPrimaryKey + { + + /// + /// 所属车辆 + /// + public long VehicleId { get; set; } + + /// + /// 所属公司 + /// + public long CompanyId { get; set; } + + /// + /// SIM卡号 + /// + public string SimNo { get; set; } + + /// + /// 车辆自编号 + /// + public string VehicleCode { get; set; } + + /// + /// 车牌号 + /// + public string VehiclePlate { get; set; } + + /// + /// 精度 + /// + public decimal Longitude { get; set; } + + /// + /// 纬度 + /// + public decimal Latitude { get; set; } + + /// + /// 速度 + /// + public int Speed { get; set; } + + /// + /// 方向 + /// + public int Direct { get; set; } + + /// + /// GPS状态 + /// + public int GpsState { get; set; } + + /// + /// Gps数据产生时间 + /// + //[BsonDateTimeOptions(Kind = DateTimeKind.Unspecified)] + public DateTime GpsTime { get; set; } + + /// + /// Gps服务器接收时间 + /// + //[BsonDateTimeOptions(Kind = DateTimeKind.Unspecified)] + public DateTime RecTime { get; set; } + + /// + /// ACC状态 + /// + public int Acc { get; set; } + + + /// + /// 是否工作 + /// + public int Work { get; set; } + + /// + /// 地址 + /// + public string Address { get; set; } + + /// + /// Gps信号模式 1 GPS,2 基站 + /// + public int GpsMode { get; set; } + + + + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.MongoDb.Repository/IRepositorys/IGpsRealTimeRepository.cs b/src/Znyc.Dispatching.MongoDb.Repository/IRepositorys/IGpsRealTimeRepository.cs new file mode 100644 index 0000000..3c5a7b2 --- /dev/null +++ b/src/Znyc.Dispatching.MongoDb.Repository/IRepositorys/IGpsRealTimeRepository.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Znyc.Dispatching.MongoDb.Repository.Collection; + +namespace Znyc.Dispatching.MongoDb.Repository.Repositorys +{ + public interface IGpsRealTimeRepository + { + Task InsertGpsRealTime(GpsRealTime gpsRealTime); + + Task GetGpsRealTimeByVehicleId(long vehicleId); + + Task> GetGpsRealTimeByCompanyId(long companyId); + + Task> GetOfflineGpsRealTime(); + + Task UpdateGpsRealTime(object id, GpsRealTime gpsRealTime); + + + Task> GetGpsRealTimeListByVehicleId(List vehicleIds); + + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.MongoDb.Repository/IRepositorys/IMongodbBaseRepository.cs b/src/Znyc.Dispatching.MongoDb.Repository/IRepositorys/IMongodbBaseRepository.cs new file mode 100644 index 0000000..a77360e --- /dev/null +++ b/src/Znyc.Dispatching.MongoDb.Repository/IRepositorys/IMongodbBaseRepository.cs @@ -0,0 +1,54 @@ +using MongoDB.Driver; +using System.Collections.Generic; +using System.Threading.Tasks; +using Znyc.Dispatching.MongoDb.Repository.Collection; + +namespace Znyc.Dispatching.MongoDb.Repository.Repositorys +{ + public interface IMongodbBaseRepository + { + /// + /// 查询车辆明细数据 + /// + /// + /// + /// + Task> GetVehicleGpsListAsync(string tbName, FilterDefinition filter); + + /// + /// 查询车辆停留点 + /// + /// + /// + Task> GetStopPointCalculationListAsync(string tbName); + + /// + /// 查询公司车辆超速数据最新100条 + /// + /// + /// + /// + Task> GetVehicleAlertListAsync(string tbName, + FilterDefinition filter); + + /// + /// 查询车辆超速分页数据 + /// + /// + /// + /// + /// + /// + Task> GetAlertPageAsync(string tbName, + FilterDefinition filter, int currentPage, int pageSize); + + /// + /// 查询车辆超速总条数 + /// + /// + /// + /// + Task GetAlertCountAsync(string tbName, + FilterDefinition filter); + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.MongoDb.Repository/IRepositorys/IPgyAlertStraightDtlRepository.cs b/src/Znyc.Dispatching.MongoDb.Repository/IRepositorys/IPgyAlertStraightDtlRepository.cs new file mode 100644 index 0000000..08143b0 --- /dev/null +++ b/src/Znyc.Dispatching.MongoDb.Repository/IRepositorys/IPgyAlertStraightDtlRepository.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Znyc.Dispatching.MongoDb.Repository.Collection; + +namespace Znyc.Dispatching.MongoDb.Repository.Repositorys +{ + public interface IPgyAlertStraightDtlRepository + { + /// + /// 超速报警记录 + /// + /// + /// + Task> FindAlarmListByTime(long companyId); + + /// + /// 超速报警记录分页列表 + /// + /// + /// + /// + /// + Task> FindAlarmPageAsync(long companyId, int currentPage, int pageSize); + + /// + /// 超速报警记录总条数 + /// + /// + /// + Task FindAlarmCountAsync(long companyId); + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.MongoDb.Repository/IRepositorys/IPgyAlertStraightLisRepository.cs b/src/Znyc.Dispatching.MongoDb.Repository/IRepositorys/IPgyAlertStraightLisRepository.cs new file mode 100644 index 0000000..0938f80 --- /dev/null +++ b/src/Znyc.Dispatching.MongoDb.Repository/IRepositorys/IPgyAlertStraightLisRepository.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Znyc.Dispatching.MongoDb.Repository.Collection; + +namespace Znyc.Dispatching.MongoDb.Repository.Repositorys +{ + public interface IPgyAlertStraightLisRepository + { + Task> FindAnalysisAlarmListByTime(string key, DateTime startDateTime, + DateTime endDateTime, long companyId, int lowSpeed, int topSpeed, int continuteCount); + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.MongoDb.Repository/MongoContext.cs b/src/Znyc.Dispatching.MongoDb.Repository/MongoContext.cs new file mode 100644 index 0000000..9a0dc68 --- /dev/null +++ b/src/Znyc.Dispatching.MongoDb.Repository/MongoContext.cs @@ -0,0 +1,23 @@ +using MongoDB.Driver; +using Pursue.Extension.MongoDB; +using Znyc.Dispatching.MongoDb.Repository.Collection; + +namespace Znyc.Dispatching.MongoDb.Repository +{ + public class MongoContext : MongoDbContext + { + public MongoContext() : base() + { + + } + + // 上下文模式可以注入需要操作的实体对象和EF一样 + public IMongoCollection GpsRealTime => DbSet(); + + public IMongoCollection GpsCarAccDuration => DbSet(); + + public IMongoCollection PgyAlertStraightDtl => DbSet(); + + public IMongoCollection VehicleGps => DbSet(); + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.MongoDb.Repository/MongoDbStartup.cs b/src/Znyc.Dispatching.MongoDb.Repository/MongoDbStartup.cs new file mode 100644 index 0000000..199327a --- /dev/null +++ b/src/Znyc.Dispatching.MongoDb.Repository/MongoDbStartup.cs @@ -0,0 +1,28 @@ +using Furion; +using Microsoft.Extensions.DependencyInjection; +using Pursue.Extension.MongoDB; +using Znyc.Dispatching.MongoDb.Repository.Repositorys; + +namespace Znyc.Dispatching.MongoDb.Repository +{ + [AppStartup(80)] + public class MongoDbStartup : AppStartup + { + public void ConfigureServices(IServiceCollection services) + { + services.AddMongoDBContext(options => + { + options.UseMongo(App.Configuration["MongoDbConnectionStrings:DefaultConnection"], + App.Configuration["MongoDbConnectionStrings:Database"]); + }); + + services.AddMongoDbService(); + + services.AddTransient(); + + services.AddTransient(); + + services.AddTransient(); + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.MongoDb.Repository/Repositorys/GpsRealTimeRepository.cs b/src/Znyc.Dispatching.MongoDb.Repository/Repositorys/GpsRealTimeRepository.cs new file mode 100644 index 0000000..5e4a6fe --- /dev/null +++ b/src/Znyc.Dispatching.MongoDb.Repository/Repositorys/GpsRealTimeRepository.cs @@ -0,0 +1,86 @@ +using Pursue.Extension.MongoDB; +using System.Collections.Generic; +using System.Threading.Tasks; +using Znyc.Dispatching.Core; +using Znyc.Dispatching.MongoDb.Repository.Collection; + +namespace Znyc.Dispatching.MongoDb.Repository.Repositorys +{ + /// + /// gps服务 + /// + public class GpsRealTimeRepository : IGpsRealTimeRepository + { + private readonly IMongoDbRepository _gpsRealTimemongoDbRepository; + + public GpsRealTimeRepository(IMongoDbService context) + { + _gpsRealTimemongoDbRepository = context.GetRepository(); + ; + } + + /// + /// 新增数据 + /// + /// + /// + public async Task InsertGpsRealTime(GpsRealTime gpsRealTime) + { + await _gpsRealTimemongoDbRepository.AddAsync(gpsRealTime); + return true; + } + + /// + /// 根据车辆Id查询信息 + /// + /// + /// + public async Task GetGpsRealTimeByVehicleId(long vehicleId) + { + return await _gpsRealTimemongoDbRepository.QueryOneAsync(x => x.VehicleId == vehicleId); + } + + /// + /// 根据公司Id查询所有车辆信息 + /// + /// + /// + public async Task> GetGpsRealTimeByCompanyId(long companyId) + { + return await _gpsRealTimemongoDbRepository.QueryListAsync(x => x.CompanyId == companyId); + } + + /// + /// 查询所有离线车辆信息 + /// + /// + public async Task> GetOfflineGpsRealTime() + { + return await _gpsRealTimemongoDbRepository.QueryListAsync(x => + x.GpsState == (int)GpsStatusEnum.OFFLINE && x.Status == (int)CommonStatusEnum.ENABLE); + } + + /// + /// 修改车辆信息 + /// + /// + /// + /// + public async Task UpdateGpsRealTime(object id, GpsRealTime gpsRealTime) + { + await _gpsRealTimemongoDbRepository.UpdateAsync(id.ToString(), gpsRealTime); + } + + + /// + /// 根据车辆Id查询所有信息信息 + /// + /// + /// + public async Task> GetGpsRealTimeListByVehicleId(List vehicleIds) + { + return await _gpsRealTimemongoDbRepository.QueryListAsync(x => vehicleIds.Contains(x.VehicleId)); + } + + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.MongoDb.Repository/Repositorys/MongodbBaseRepository.cs b/src/Znyc.Dispatching.MongoDb.Repository/Repositorys/MongodbBaseRepository.cs new file mode 100644 index 0000000..8e8a3d1 --- /dev/null +++ b/src/Znyc.Dispatching.MongoDb.Repository/Repositorys/MongodbBaseRepository.cs @@ -0,0 +1,97 @@ +using Furion; +using MongoDB.Driver; +using Pursue.Extension.MongoDB; +using System.Collections.Generic; +using System.Threading.Tasks; +using Znyc.Dispatching.MongoDb.Repository.Collection; + +namespace Znyc.Dispatching.MongoDb.Repository.Repositorys +{ + /// + /// gps服务 + /// + public class MongodbBaseRepository : IMongodbBaseRepository + { + private readonly IMongoDbContext _context; + + public MongodbBaseRepository(IMongoDbContext context) + { + _context = context; + } + + /// + /// 查询车辆明细数据 + /// + /// + /// + public async Task> GetVehicleGpsListAsync(string tbName, FilterDefinition filter) + { + return await _context.GetDataBase(App.Configuration["MongoDbConnectionStrings:Database"]) + .GetCollection(tbName).Find(filter).ToListAsync(); + } + + /// + /// 查询车辆停留点 + /// + /// + /// + public async Task> GetStopPointCalculationListAsync(string tbName) + { + FilterDefinition filter = Builders.Filter.Gte("Status", -1); + return await _context.GetDataBase(App.Configuration["MongoDbConnectionStrings:Database"]) + .GetCollection(tbName).Find(filter).ToListAsync(); + } + + /// + /// 查询公司车辆超速数据最新100条 + /// + /// + /// + /// + public async Task> GetVehicleAlertListAsync(string tbName, + FilterDefinition filter) + { + return await _context.GetDataBase(App.Configuration["MongoDbConnectionStrings:Database"]) + .GetCollection(tbName) + .Find(filter) + .SortByDescending(x => x.GpsTime) + .Limit(100) + .ToListAsync(); + } + + /// + /// 查询车辆超速分页数据 + /// + /// + /// + /// + /// + /// + public async Task> GetAlertPageAsync(string tbName, + FilterDefinition filter, int currentPage, int pageSize) + { + return await _context.GetDataBase(App.Configuration["MongoDbConnectionStrings:Database"]) + .GetCollection(tbName) + .Find(filter) + .SortByDescending(x => x.GpsTime) + .Limit(pageSize) + .Skip((currentPage - 1) * pageSize) + .ToListAsync(); + } + + /// + /// 查询车辆超速总条数 + /// + /// + /// + /// + public async Task GetAlertCountAsync(string tbName, + FilterDefinition filter) + { + return await _context.GetDataBase(App.Configuration["MongoDbConnectionStrings:Database"]) + .GetCollection(tbName).Find(filter).CountDocumentsAsync(); + } + + + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.MongoDb.Repository/Repositorys/PgyAlertStraightDtlRepository.cs b/src/Znyc.Dispatching.MongoDb.Repository/Repositorys/PgyAlertStraightDtlRepository.cs new file mode 100644 index 0000000..70511ca --- /dev/null +++ b/src/Znyc.Dispatching.MongoDb.Repository/Repositorys/PgyAlertStraightDtlRepository.cs @@ -0,0 +1,87 @@ +using Microsoft.AspNetCore.Mvc; +using MongoDB.Driver; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Znyc.Dispatching.Core; +using Znyc.Dispatching.MongoDb.Repository.Collection; + +namespace Znyc.Dispatching.MongoDb.Repository.Repositorys +{ + /// + /// gps警报服务 + /// + [ApiDescriptionSettings(ApiGroupConsts.LOG_CENTER, Name = "gps", Order = 100, IgnoreApi = true)] + public class PgyAlertStraightDtlRepository : IPgyAlertStraightDtlRepository + { + private readonly IMongodbBaseRepository _mongodbBaseRepository; + + public PgyAlertStraightDtlRepository(IMongodbBaseRepository mongodbBaseRepository) + { + _mongodbBaseRepository = mongodbBaseRepository; + } + + /// + /// 超速报警记录 + /// + /// + /// + public async Task> FindAlarmListByTime(long companyId) + { + string syear = DateTime.Now.ToString("yyyyMM"); + string tableName = CommonConst.DEFAULT_ALARMSTRAIGHT_PREFIX + syear; + FilterDefinition filter = null; + if (companyId > 0) + { + filter = Builders.Filter.Eq("CompanyId", companyId); + } + //if (vehicleId > 0) + // filter = filter & (Builders.Filter.Eq("VehicleId", vehicleId)); + //if (overSpeed > 0) + // filter = filter & (Builders.Filter.Gte("Speed", overSpeed)); + List list = await _mongodbBaseRepository.GetVehicleAlertListAsync(tableName, filter); + return list; + } + + /// + /// 超速报警记录分页列表 + /// + /// + /// + /// + /// + public async Task> FindAlarmPageAsync(long companyId, int currentPage, int pageSize) + { + string syear = DateTime.Now.ToString("yyyyMM"); + string tableName = CommonConst.DEFAULT_ALARMSTRAIGHT_PREFIX + syear; + FilterDefinition filter = null; + if (companyId > 0) + { + filter = Builders.Filter.Eq("CompanyId", companyId); + } + + List list = await _mongodbBaseRepository.GetAlertPageAsync(tableName, filter, currentPage, pageSize); + return list; + } + + /// + /// 超速报警记录总条数 + /// + /// + /// + public async Task FindAlarmCountAsync(long companyId) + { + string syear = DateTime.Now.ToString("yyyyMM"); + string tableName = CommonConst.DEFAULT_ALARMSTRAIGHT_PREFIX + syear; + FilterDefinition filter = null; + if (companyId > 0) + { + filter = Builders.Filter.Eq("CompanyId", companyId); + } + + long count = await _mongodbBaseRepository.GetAlertCountAsync(tableName, filter); + return count; + } + + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.MongoDb.Repository/Repositorys/PgyAlertStraightLisRepository.cs b/src/Znyc.Dispatching.MongoDb.Repository/Repositorys/PgyAlertStraightLisRepository.cs new file mode 100644 index 0000000..90e0bc3 --- /dev/null +++ b/src/Znyc.Dispatching.MongoDb.Repository/Repositorys/PgyAlertStraightLisRepository.cs @@ -0,0 +1,71 @@ +using MongoDB.Driver; +using Pursue.Extension.MongoDB; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Znyc.Dispatching.MongoDb.Repository.Collection; + +namespace Znyc.Dispatching.MongoDb.Repository.Repositorys +{ + /// + /// gps警报服务 + /// + public class PgyAlertStraightLisRepository : IPgyAlertStraightLisRepository + { + private readonly IMongoDbRepository _pgyAlertStraightLisRepository; + + public PgyAlertStraightLisRepository(IMongoDbService context) + { + _pgyAlertStraightLisRepository = context.GetRepository(); + ; + } + + /// + /// 直线/弯道报警,主表记录 + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public async Task> FindAnalysisAlarmListByTime(string key, DateTime startDateTime, + DateTime endDateTime, long companyId, int lowSpeed, int topSpeed, int continuteCount) + { + string syear = startDateTime.ToString("yyyyMM"); + FilterDefinition filter = Builders.Filter.Gte("GpsTime", startDateTime) + & Builders.Filter.Lt("GpsTime", endDateTime); + if (!string.IsNullOrEmpty(key)) + { + filter &= (Builders.Filter.Regex("VehiclePlate", key) | + Builders.Filter.Regex("VehicleCode", key)); + } + + if (companyId != 0) + { + filter &= Builders.Filter.Eq("CompanyId", companyId); + } + + if (lowSpeed != 0) + { + filter &= Builders.Filter.Gte("LowSpeed", lowSpeed); + } + + if (topSpeed != 0) + { + filter &= Builders.Filter.Gte("TopSpeed", topSpeed); + } + + if (continuteCount != 0) + { + filter &= Builders.Filter.Gte("ContinuteCount", continuteCount); + } + + List list = await _pgyAlertStraightLisRepository.QueryListAsync(filter); + return list; + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.MongoDb.Repository/Znyc.Dispatching.MongoDb.Repository.csproj b/src/Znyc.Dispatching.MongoDb.Repository/Znyc.Dispatching.MongoDb.Repository.csproj new file mode 100644 index 0000000..c585f7f --- /dev/null +++ b/src/Znyc.Dispatching.MongoDb.Repository/Znyc.Dispatching.MongoDb.Repository.csproj @@ -0,0 +1,34 @@ + + + + net6.0 + + + + + + + + + + + Always + + + Always + + + Always + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Znyc.Dispatching.MongoDb.Repository/mongodbsettings.Development.json b/src/Znyc.Dispatching.MongoDb.Repository/mongodbsettings.Development.json new file mode 100644 index 0000000..ed7ea68 --- /dev/null +++ b/src/Znyc.Dispatching.MongoDb.Repository/mongodbsettings.Development.json @@ -0,0 +1,7 @@ +{ + "MongoDbConnectionStrings": { + "DefaultConnection": "mongodb://42.194.147.251:27018", + //"DefaultConnection": "mongodb://127.0.0.1:27017", + "Database": "ZNYCGPS" + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.MongoDb.Repository/mongodbsettings.Production.json b/src/Znyc.Dispatching.MongoDb.Repository/mongodbsettings.Production.json new file mode 100644 index 0000000..cee2788 --- /dev/null +++ b/src/Znyc.Dispatching.MongoDb.Repository/mongodbsettings.Production.json @@ -0,0 +1,6 @@ +{ + "MongoDbConnectionStrings": { + "DefaultConnection": "mongodb://172.16.0.16:27017", + "Database": "ZNYCGPS" + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.MongoDb.Repository/mongodbsettings.Staging.json b/src/Znyc.Dispatching.MongoDb.Repository/mongodbsettings.Staging.json new file mode 100644 index 0000000..6ff716d --- /dev/null +++ b/src/Znyc.Dispatching.MongoDb.Repository/mongodbsettings.Staging.json @@ -0,0 +1,7 @@ +{ + "MongoDbConnectionStrings": { + //"DefaultConnection": "mongodb://root:D#Du17p$UYMNh@42.194.147.251:27017", + "DefaultConnection": "mongodb://10.0.8.5:27018", + "Database": "ZNYCGPS" + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Tasks/HangfireDispose.cs b/src/Znyc.Dispatching.Tasks/HangfireDispose.cs new file mode 100644 index 0000000..2ef950a --- /dev/null +++ b/src/Znyc.Dispatching.Tasks/HangfireDispose.cs @@ -0,0 +1,47 @@ +using Hangfire; +using System; +using Znyc.Dispatching.Tasks.TaskJobs; + +namespace Znyc.Dispatching.Tasks +{ + public class HangfireDispose + { + + public static void HangfireService() + { + //Fire - And - forget(发布 / 订阅) + //这是一个主要的后台任务类型,持久化消息队列会去处理这个任务。当你创建了一个发布 / 订阅任务,该任务会被保存到默认队列里面(默认队列是"Default",但是支持使用多队列)。多个专注的工作者(Worker)会监听这个队列,并且从中获取任务并且完成任务。 + //BackgroundJob.Enqueue(() => Console.WriteLine("Fire-and-forget")); + + //延迟 + //如果想要延迟某些任务的执行,可以是用以下任务。在给定延迟时间后,任务会被排入队列,并且和发布 / 订阅任务一样执行。 + //BackgroundJob.Schedule(() => Console.WriteLine("Delayed"), TimeSpan.FromDays(1)); + + //循环 + //按照周期性(小时,天等)来调用方法,请使用RecurringJob类。在复杂的场景,您可以使用CRON表达式指定计划时间来处理任务。 + //RecurringJob.AddOrUpdate(() => Console.WriteLine("Daily Job"), Cron.Daily); + + //连续 + //连续性允许您通过将多个后台任务链接在一起来定义复杂的工作流。 + //var id = BackgroundJob.Enqueue(() => Console.WriteLine("Hello, ")); + //BackgroundJob.ContinueWith(id, () => Console.WriteLine("world!")); + + //这里呢就是需要触发的方法 "0/10 * * * * ? " 可以自行搜索cron表达式 代表循环的规律很简单 + //CancelOrderJob代表你要触发的类 Execute代表你要触发的方法 + + //每十分钟预热今天的轨迹 + // RecurringJob.AddOrUpdate(s => s.Execute(), "0 0/10 * * * ? ", TimeZoneInfo.Utc); + //每晚预热昨天的轨迹 + //RecurringJob.AddOrUpdate(s => s.Execute(), "0 0 0 */1 * ? ", TimeZoneInfo.Utc); + + //每30天预热一次角色菜单缓存 + RecurringJob.AddOrUpdate(s => s.RoleMenuExecuteAsync(), "0 0 5 1 * ? ", TimeZoneInfo.Local); + + + RecurringJob.AddOrUpdate(s => s.SyncOrderStatusAsync(), "0 0/1 * * * ? ", TimeZoneInfo.Local); + + + + } + } +} diff --git a/src/Znyc.Dispatching.Tasks/TaskJobs/AutoEveryNightTrackPlaybackCorrectionJob.cs b/src/Znyc.Dispatching.Tasks/TaskJobs/AutoEveryNightTrackPlaybackCorrectionJob.cs new file mode 100644 index 0000000..ad99b85 --- /dev/null +++ b/src/Znyc.Dispatching.Tasks/TaskJobs/AutoEveryNightTrackPlaybackCorrectionJob.cs @@ -0,0 +1,23 @@ +using System; +using System.Threading.Tasks; +using Znyc.Dispatching.Application; + +namespace Znyc.Dispatching.Tasks.TaskJobs +{ + + public class AutoTrackPlaybackCorrectionJob + { + private readonly IVehicleService _vehicleService; + + + public AutoTrackPlaybackCorrectionJob(IVehicleService vehicleService) + { + _vehicleService = vehicleService; + } + + public async Task Execute() + { + await _vehicleService.SyncTrackPlaybackCorrectionAsync(DateTime.Now.Date, DateTime.Now, "Yesterday"); + } + } +} diff --git a/src/Znyc.Dispatching.Tasks/TaskJobs/AutoOrderJob.cs b/src/Znyc.Dispatching.Tasks/TaskJobs/AutoOrderJob.cs new file mode 100644 index 0000000..98deea2 --- /dev/null +++ b/src/Znyc.Dispatching.Tasks/TaskJobs/AutoOrderJob.cs @@ -0,0 +1,28 @@ +using Dilon.Core.Service; +using System.Threading.Tasks; + +namespace Znyc.Dispatching.Tasks.TaskJobs +{ + /// + /// + /// + public class AutoOrderJob + { + private readonly IJobService _jobService; + + public AutoOrderJob(IJobService JobService) + { + _jobService = JobService; + + } + + /// + /// + /// + /// + public async Task SyncOrderStatusAsync() + { + await _jobService.SyncOrderStatusAsync(); + } + } +} diff --git a/src/Znyc.Dispatching.Tasks/TaskJobs/AutoRoleMenuJob.cs b/src/Znyc.Dispatching.Tasks/TaskJobs/AutoRoleMenuJob.cs new file mode 100644 index 0000000..374ebcc --- /dev/null +++ b/src/Znyc.Dispatching.Tasks/TaskJobs/AutoRoleMenuJob.cs @@ -0,0 +1,38 @@ +using System.Threading.Tasks; +using Znyc.Dispatching.Application; + +namespace Znyc.Dispatching.Tasks.TaskJobs +{ + /// + /// 角色菜单定时任务 + /// + public class AutoRoleMenuJob + { + private readonly IRoleService _roleService; + private readonly IRoleMenuService _roleMenuService; + private readonly ICacheService _cacheService; + + public AutoRoleMenuJob(IRoleService roleService, + ICacheService cacheService, + IRoleMenuService roleMenuService) + { + _roleService = roleService; + _roleMenuService = roleMenuService; + _cacheService = cacheService; + } + + /// + /// 同步角色菜单缓存 + /// + /// + public async Task RoleMenuExecuteAsync() + { + //同步角色缓存 + var role = await _roleService.GetRoleAsync(); + foreach (var item in role) + { + await _roleMenuService.GetRoleMenuListByIdAsync(item.Id); + } + } + } +} diff --git a/src/Znyc.Dispatching.Tasks/TaskJobs/AutoTrackPlaybackCorrectionJob.cs b/src/Znyc.Dispatching.Tasks/TaskJobs/AutoTrackPlaybackCorrectionJob.cs new file mode 100644 index 0000000..f3cdbda --- /dev/null +++ b/src/Znyc.Dispatching.Tasks/TaskJobs/AutoTrackPlaybackCorrectionJob.cs @@ -0,0 +1,23 @@ +using System; +using System.Threading.Tasks; +using Znyc.Dispatching.Application; + +namespace Znyc.Dispatching.Tasks.TaskJobs +{ + + public class AutoEveryNightTrackPlaybackCorrectionJob + { + private readonly IVehicleService _vehicleService; + + + public AutoEveryNightTrackPlaybackCorrectionJob(IVehicleService vehicleService) + { + _vehicleService = vehicleService; + } + + public async Task Execute() + { + await _vehicleService.SyncTrackPlaybackCorrectionAsync(DateTime.Now.AddDays(-1).Date, DateTime.Now.Date.AddMinutes(-1), "Today"); + } + } +} diff --git a/src/Znyc.Dispatching.Tasks/TasksStartup.cs b/src/Znyc.Dispatching.Tasks/TasksStartup.cs new file mode 100644 index 0000000..97fbe2b --- /dev/null +++ b/src/Znyc.Dispatching.Tasks/TasksStartup.cs @@ -0,0 +1,69 @@ +using Furion; +using Hangfire; +using Hangfire.Dashboard.BasicAuthorization; +using Hangfire.Redis; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using System; +using Znyc.Dispatching.Core.Enums; +using Znyc.Dispatching.Tasks; + +namespace Znyc.Dispatching.MongoDb.Repository +{ + [AppStartup(95)] + public class TasksStartup : AppStartup + { + public void ConfigureServices(IServiceCollection services) + { + services.AddHangfire(x => x.UseRedisStorage(App.Configuration["Cache:RedisConnectionString"])); + services.AddHangfireServer(options => + { + options.Queues = new[] { HangFireQueuesConfig.@default.ToString(), HangFireQueuesConfig.apis.ToString(), HangFireQueuesConfig.web.ToString(), HangFireQueuesConfig.recurring.ToString() }; + options.ServerTimeout = TimeSpan.FromMinutes(4); + options.SchedulePollingInterval = TimeSpan.FromSeconds(15);//秒级任务需要配置短点,一般任务可以配置默认时间,默认15秒 + options.ShutdownTimeout = TimeSpan.FromMinutes(30); //超时时间 + options.WorkerCount = Math.Max(Environment.ProcessorCount, 20); //工作线程数,当前允许的最大线程,默认20 + }); + } + + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { //授权 + var filter = new BasicAuthAuthorizationFilter( + new BasicAuthAuthorizationFilterOptions + { + SslRedirect = false, + // Require secure connection for dashboard + RequireSsl = false, + // Case sensitive login checking + LoginCaseSensitive = false, + // Users + Users = new[] + { + new BasicAuthAuthorizationUser + { + Login = App.Configuration["Hangfire:Login"], + PasswordClear = App.Configuration["Hangfire:Password"] + } + } + }); + + var options = new DashboardOptions + { + AppPath = "/",//返回时跳转的地址 + DisplayStorageConnectionString = false,//是否显示数据库连接信息 + Authorization = new[] + { + filter + }, + IsReadOnlyFunc = Context => + { + return false;//是否只读面板 + } + }; + + app.UseHangfireDashboard("/dc_job", options); + HangfireDispose.HangfireService(); + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Tasks/Znyc.Dispatching.Tasks.csproj b/src/Znyc.Dispatching.Tasks/Znyc.Dispatching.Tasks.csproj new file mode 100644 index 0000000..389e8de --- /dev/null +++ b/src/Znyc.Dispatching.Tasks/Znyc.Dispatching.Tasks.csproj @@ -0,0 +1,19 @@ + + + + net6.0 + + + + + + + + + + + + + + + diff --git a/src/Znyc.Dispatching.WeChat.Core/CommonService/SubscribeMessage/WxApplet/WxAppletSubscribeMessage.cs b/src/Znyc.Dispatching.WeChat.Core/CommonService/SubscribeMessage/WxApplet/WxAppletSubscribeMessage.cs new file mode 100644 index 0000000..0550047 --- /dev/null +++ b/src/Znyc.Dispatching.WeChat.Core/CommonService/SubscribeMessage/WxApplet/WxAppletSubscribeMessage.cs @@ -0,0 +1,109 @@ +using Furion; +using Senparc.Weixin.Entities; +using Senparc.Weixin.Entities.TemplateMessage; +using Senparc.Weixin.WxOpen.AdvancedAPIs; + +namespace Znyc.Dispatching.WeChat.Core.CommonService.SubscribeMessage.WxApplet +{ + /// + /// 小程序订阅消息 + /// + public class WxAppletSubscribeMessage + { + private static readonly string wxOpenAppId = App.Configuration["SenparcWeixinSetting:WxOpenAppId"]; + + /// + /// 留言提醒,模板编号:1069 + /// + /// 接收者(用户)的 openid + /// 消息模板Id + /// 留言内容,20个以内字符 + /// 留言时间,4小时制时间格式(支持+年月日) 例如:15:01,或:2019年10月1日 15:01 + /// 点击模板卡片后的跳转页面,仅限本小程序内的页面。支持带参数,(示例index?foo=bar)。该字段不填则模板无跳转。 + public static WxJsonResult SendCommentNotice(string toUser, string templateId, string text, string date, + string page) + { + TemplateMessageData data = new TemplateMessageData + { + ["thing1"] = new(text), + ["time2"] = new(date) + }; + var submitData = new + { + touser = toUser, + template_id = templateId, + page, + data + }; + return MessageApi.SendSubscribe(wxOpenAppId, toUser, templateId, data, page); + } + + /// + /// 新的评论提醒 ,模板编号:484 + /// + /// 接收者(用户)的 openid + /// 消息模板Id + /// 文章标题,20个以内字符 + /// 评论内容,20个以内字符 + /// 评论时间,4小时制时间格式(支持+年月日) 例如:15:01,或:2019年10月1日 15:01 + /// 评论用户,20个以内字符 + /// + public static WxJsonResult SendRemarkNotice(string toUser, string templateId, string title, string desc, + string date, string userNick, string page) + { + TemplateMessageData data = new TemplateMessageData + { + {"thing1", new TemplateMessageDataValue(title)}, + {"thing2", new TemplateMessageDataValue(desc)}, + {"time3", new TemplateMessageDataValue(date)}, + {"thing5", new TemplateMessageDataValue(userNick)} + }; + return MessageApi.SendSubscribe(wxOpenAppId, toUser, templateId, data, page); + } + + /// + /// 动态点赞通知,模板编号:579 + /// + /// 接收者(用户)的 openid + /// 消息模板Id + /// 点赞用户,20个以内字符 + /// 点赞时间,4小时制时间格式(支持+年月日) 例如:15:01,或:2019年10月1日 15:01 + /// 点击模板卡片后的跳转页面,仅限本小程序内的页面。支持带参数,(示例index?foo=bar)。该字段不填则模板无跳转。 + public static WxJsonResult SendGoodNotice(string toUser, string templateId, string name, string date, + string page) + { + TemplateMessageData data = new TemplateMessageData + { + {"name1", new TemplateMessageDataValue(name)}, + {"date2", new TemplateMessageDataValue(date)} + }; + return MessageApi.SendSubscribe(wxOpenAppId, toUser, templateId, data, page); + } + + /// + /// 资讯早报通知,模板编号:269 + /// + /// 接收者(用户)的 openid + /// 消息模板Id + /// 更新内容,20个以内字符 + /// 备注,20个以内字符 + /// 点击模板卡片后的跳转页面,仅限本小程序内的页面。支持带参数,(示例index?foo=bar)。该字段不填则模板无跳转。 + public static WxJsonResult SendNewsMorningNotice(string toUser, string templateId, string title, string remark, + string page) + { + TemplateMessageData data = new TemplateMessageData + { + ["thing1"] = new(title), + ["thing2"] = new(remark) + }; + var submitData = new + { + touser = toUser, + template_id = templateId, + page, + data + }; + return MessageApi.SendSubscribe(wxOpenAppId, toUser, templateId, data, page); + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.WeChat.Core/CommonService/TemplateMessage/Weixin/WeixinTemplateApi.cs b/src/Znyc.Dispatching.WeChat.Core/CommonService/TemplateMessage/Weixin/WeixinTemplateApi.cs new file mode 100644 index 0000000..2b3df92 --- /dev/null +++ b/src/Znyc.Dispatching.WeChat.Core/CommonService/TemplateMessage/Weixin/WeixinTemplateApi.cs @@ -0,0 +1,8 @@ +namespace Znyc.Dispatching.WeChat.Core.CommonService.TemplateMessage.Weixin +{ + public static class WeixinTemplateApi + { + + + } +} diff --git a/src/Znyc.Dispatching.WeChat.Core/CommonService/TemplateMessage/Weixin/WeixinTemplate_AssignOrder.cs b/src/Znyc.Dispatching.WeChat.Core/CommonService/TemplateMessage/Weixin/WeixinTemplate_AssignOrder.cs new file mode 100644 index 0000000..edaa9d9 --- /dev/null +++ b/src/Znyc.Dispatching.WeChat.Core/CommonService/TemplateMessage/Weixin/WeixinTemplate_AssignOrder.cs @@ -0,0 +1,51 @@ + + +using Senparc.Weixin.Entities.TemplateMessage; +using Senparc.Weixin.MP.AdvancedAPIs.TemplateMessage; + +namespace Znyc.Dispatching.WeChat.Core.CommonService.TemplateMessage +{ + + /* + {{first.DATA}} + 项目名称:{{keyword1.DATA}} + 工单类型:{{keyword2.DATA}} + 工作内容:{{keyword3.DATA}} + 日期时间:{{keyword4.DATA}} + {{remark.DATA}} + */ + + /// + /// + /// + public class WeixinTemplate_AssignOrder : TemplateMessageBase + { + const string TEMPLATE_ID = "tOuu24WtROB03z3VQnwiyg1oEJB_Q-fa_-11Gno4FZc"; + + public TemplateDataItem first { get; set; } + + public TemplateDataItem keyword1 { get; set; } + + public TemplateDataItem keyword2 { get; set; } + + public TemplateDataItem keyword3 { get; set; } + + public TemplateDataItem keyword4 { get; set; } + + /// + /// + /// + public TemplateDataItem remark { get; set; } + + public WeixinTemplate_AssignOrder(string _first, string projectName, string orderType, string orderContent, string ArriveDate, string _remark, string url = null, string templateId = TEMPLATE_ID) + : base(templateId, url, "派车通知") + { + first = new TemplateDataItem(_first); + keyword1 = new TemplateDataItem(projectName); + keyword2 = new TemplateDataItem(orderType); + keyword3 = new TemplateDataItem(orderContent); + keyword4 = new TemplateDataItem(ArriveDate); + remark = new TemplateDataItem(_remark); + } + } +} diff --git a/src/Znyc.Dispatching.WeChat.Core/CommonService/TemplateMessage/WxOpen/CommonApi.cs b/src/Znyc.Dispatching.WeChat.Core/CommonService/TemplateMessage/WxOpen/CommonApi.cs new file mode 100644 index 0000000..3865c3b --- /dev/null +++ b/src/Znyc.Dispatching.WeChat.Core/CommonService/TemplateMessage/WxOpen/CommonApi.cs @@ -0,0 +1,230 @@ +using Senparc.CO2NET.Extensions; +using Senparc.CO2NET.HttpUtility; +using Senparc.Weixin; +using Senparc.Weixin.CommonAPIs; +using Senparc.Weixin.Entities; +using Senparc.Weixin.Exceptions; +using Senparc.Weixin.MP; +using Senparc.Weixin.MP.Containers; +using Senparc.Weixin.MP.Entities; +using System.Threading.Tasks; +using Znyc.Dispatching.Core.Extension; + +namespace Znyc.Dispatching.WeChat.Core.CommonService.TemplateMessage.WxOpen +{ + /// + /// 通用接口 + /// 通用接口用于和微信服务器通讯,一般不涉及自有网站服务器的通讯 + /// + public static class CommonApi + { + #region 同步方法 + + /// + /// 获取凭证接口 + /// + /// 获取access_token填写client_credential + /// 第三方用户唯一凭证 + /// 第三方用户唯一凭证密钥,既appsecret + /// + public static AccessTokenResult GetToken(string appid, string secret, string grant_type = "client_credential") + { + //注意:此方法不能再使用ApiHandlerWapper.TryCommonApi(),否则会循环 + string url = string.Format(Config.ApiMpHost + "/cgi-bin/token?grant_type={0}&appid={1}&secret={2}", + grant_type.AsUrlData(), appid.AsUrlData(), secret.AsUrlData()); + + AccessTokenResult result = Get.GetJson(CommonDI.CommonSP, url); //此处为最原始接口,不再使用重试获取的封装 + + if (Config.ThrownWhenJsonResultFaild && result.errcode != ReturnCode.请求成功) + { + throw new ErrorJsonResultException( + string.Format("微信请求发生错误(CommonApi.GetToken)!错误代码:{0},说明:{1}", + (int)result.errcode, result.errmsg), null, result); + } + + return result; + } + + + /// + /// 获取调用微信JS接口的临时票据 + /// + /// + /// + /// 默认为jsapi,当作为卡券接口使用时,应当为wx_card + /// + public static JsApiTicketResult GetTicket(string appId, string secret, string type = "jsapi") + { + string accessToken = AccessTokenContainer.TryGetAccessToken(appId, secret); + return GetTicketByAccessToken(accessToken, type); + } + + /// + /// 获取调用微信JS接口的临时票据 + /// + /// AccessToken或AppId(推荐使用AppId,需要先注册) + /// 默认为jsapi,当作为卡券接口使用时,应当为wx_card + /// + public static JsApiTicketResult GetTicketByAccessToken(string accessTokenOrAppId, string type = "jsapi") + { + return ApiHandlerWapper.TryCommonApi(accessToken => + { + string url = string.Format(Config.ApiMpHost + "/cgi-bin/ticket/getticket?access_token={0}&type={1}", + accessToken.AsUrlData(), type.AsUrlData()); + + JsApiTicketResult result = CommonJsonSend.Send(null, url, null, CommonJsonSendType.GET); + return result; + }, accessTokenOrAppId); + } + + /// + /// 获取微信服务器的ip段 + /// + /// AccessToken或AppId(推荐使用AppId,需要先注册) + /// + public static GetCallBackIpResult GetCallBackIp(string accessTokenOrAppId) + { + return ApiHandlerWapper.TryCommonApi(accessToken => + { + string url = string.Format(Config.ApiMpHost + "/cgi-bin/getcallbackip?access_token={0}", + accessToken.AsUrlData()); + + return CommonJsonSend.Send(null, url, null, CommonJsonSendType.GET); + }, accessTokenOrAppId); + } + + /// + /// 公众号调用或第三方平台帮公众号调用对公众号的所有api调用(包括第三方帮其调用)次数进行清零 + /// + /// AccessToken或AppId(推荐使用AppId,需要先注册) + /// + /// + /// + public static WxJsonResult Clear_quota(string accessTokenOrAppId, string appId, int timeOut = Config.TIME_OUT) + { + return ApiHandlerWapper.TryCommonApi(accessToken => + { + string urlFormat = string.Format(Config.ApiMpHost + "/cgi-bin/clear_quota?access_token={0}", + accessToken.AsUrlData()); + var data = new + { + appid = appId + }; + + return CommonJsonSend.Send(null, urlFormat, data, timeOut: timeOut); + }, accessTokenOrAppId); + } + + #endregion 同步方法 + + #region 异步方法 + + /// + /// 【异步方法】获取凭证接口 + /// + /// 获取access_token填写client_credential + /// 第三方用户唯一凭证 + /// 第三方用户唯一凭证密钥,既appsecret + /// + public static async Task GetTokenAsync(string appid, string secret, + string grant_type = "client_credential") + { + AccessTokenResult result; + result = await RedisHelper.GetAsync(appid); + if (result.IsNull()) + { + string url = string.Format(Config.ApiMpHost + "/cgi-bin/token?grant_type={0}&appid={1}&secret={2}", + grant_type.AsUrlData(), appid.AsUrlData(), secret.AsUrlData()); + + result = await Get.GetJsonAsync(CommonDI.CommonSP, url); //此处为最原始接口,不再使用重试获取的封装 + + if (Config.ThrownWhenJsonResultFaild && result.errcode != ReturnCode.请求成功) + { + throw new ErrorJsonResultException( + string.Format("微信请求发生错误(CommonApi.GetToken)!错误代码:{0},说明:{1}", + (int)result.errcode, result.errmsg), null, result); + } + + await RedisHelper.SetAsync(appid, result, 7200); + } + + return result; + } + + + /// + /// 【异步方法】获取调用微信JS接口的临时票据 + /// + /// + /// + /// 默认为jsapi,当作为卡券接口使用时,应当为wx_card + /// + public static async Task GetTicketAsync(string appId, string secret, string type = "jsapi") + { + string accessToken = await AccessTokenContainer.TryGetAccessTokenAsync(appId, secret).ConfigureAwait(false); + return GetTicketByAccessToken(accessToken, type); + } + + /// + /// 【异步方法】获取调用微信JS接口的临时票据 + /// + /// AccessToken或AppId(推荐使用AppId,需要先注册) + /// 默认为jsapi,当作为卡券接口使用时,应当为wx_card + /// + public static async Task GetTicketByAccessTokenAsync(string accessTokenOrAppId, + string type = "jsapi") + { + return await ApiHandlerWapper.TryCommonApiAsync(async accessToken => + { + string url = string.Format(Config.ApiMpHost + "/cgi-bin/ticket/getticket?access_token={0}&type={1}", + accessToken.AsUrlData(), type.AsUrlData()); + + Task result = CommonJsonSend.SendAsync(null, url, null, CommonJsonSendType.GET); + return await result.ConfigureAwait(false); + }, accessTokenOrAppId).ConfigureAwait(false); + } + + /// + /// 【异步方法】获取微信服务器的ip段 + /// + /// AccessToken或AppId(推荐使用AppId,需要先注册) + /// + public static async Task GetCallBackIpAsync(string accessTokenOrAppId) + { + return await ApiHandlerWapper.TryCommonApiAsync(async accessToken => + { + string url = string.Format(Config.ApiMpHost + "/cgi-bin/getcallbackip?access_token={0}", + accessToken.AsUrlData()); + + return await CommonJsonSend.SendAsync(null, url, null, CommonJsonSendType.GET) + .ConfigureAwait(false); + }, accessTokenOrAppId).ConfigureAwait(false); + } + + /// + /// 【异步方法】公众号调用或第三方平台帮公众号调用对公众号的所有api调用(包括第三方帮其调用)次数进行清零 + /// + /// AccessToken或AppId(推荐使用AppId,需要先注册) + /// + /// + /// + public static async Task Clear_quotaAsync(string accessTokenOrAppId, string appId, + int timeOut = Config.TIME_OUT) + { + return await ApiHandlerWapper.TryCommonApiAsync(async accessToken => + { + string urlFormat = string.Format(Config.ApiMpHost + "/cgi-bin/clear_quota?access_token={0}", + accessToken.AsUrlData()); + var data = new + { + appid = appId + }; + + return await CommonJsonSend.SendAsync(null, urlFormat, data, timeOut: timeOut) + .ConfigureAwait(false); + }, accessTokenOrAppId).ConfigureAwait(false); + } + + #endregion 异步方法 + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.WeChat.Core/CommonService/TemplateMessage/WxOpen/SnsApi.cs b/src/Znyc.Dispatching.WeChat.Core/CommonService/TemplateMessage/WxOpen/SnsApi.cs new file mode 100644 index 0000000..74f0eec --- /dev/null +++ b/src/Znyc.Dispatching.WeChat.Core/CommonService/TemplateMessage/WxOpen/SnsApi.cs @@ -0,0 +1,35 @@ +using Senparc.Weixin; +using Senparc.Weixin.CommonAPIs; +using Senparc.Weixin.WxOpen.AdvancedAPIs.Sns; +using System.Threading.Tasks; + +namespace Znyc.Dispatching.WeChat.Core.CommonService.TemplateMessage.WxOpen +{ + /// + /// WxApp接口 + /// + public static class SnsApi + { + /// + /// 【异步方法】code 换取 session_key + /// + /// + /// + /// + /// 保持默认:authorization_code + /// 请求超时时间 + /// + public static async Task JsCode2JsonAsync(string appId, string secret, string jsCode, + string grantType = "authorization_code", int timeOut = Config.TIME_OUT) + { + string urlFormat = + Config.ApiMpHost + "/sns/jscode2session?appid={0}&secret={1}&js_code={2}&grant_type={3}"; + + string url = string.Format(urlFormat, appId, secret, jsCode, grantType); + + JsCode2JsonResult result = await CommonJsonSend.SendAsync(null, url, null, CommonJsonSendType.GET) + .ConfigureAwait(false); + return result; + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.WeChat.Core/CommonService/TemplateMessage/WxOpen/WxOpenTemplateMessage_AssignOrder.cs b/src/Znyc.Dispatching.WeChat.Core/CommonService/TemplateMessage/WxOpen/WxOpenTemplateMessage_AssignOrder.cs new file mode 100644 index 0000000..4dfb5cb --- /dev/null +++ b/src/Znyc.Dispatching.WeChat.Core/CommonService/TemplateMessage/WxOpen/WxOpenTemplateMessage_AssignOrder.cs @@ -0,0 +1,43 @@ +//DPBMARK_FILE MiniProgram + +using Senparc.Weixin.Entities.TemplateMessage; +using Senparc.Weixin.MP.AdvancedAPIs.TemplateMessage; + +namespace Znyc.Dispatching.WeChat.Core.CommonService.TemplateMessage.WxOpen +{ + public class WxOpenTemplateMessage_AssignOrder : TemplateMessageBase + { + /// + /// “派车通知”模板消息数据 + /// + /// 出车时间 + /// 用车部门 + /// 目的地 + /// + /// + public WxOpenTemplateMessage_AssignOrder( + string ArriveDate, string address, + string url, + string templateId = "zSVi_wYuuK7E4YoU3axdDT2_oiw-JvXYXi-tRl4X1aU", + string department = "") + : base(templateId, url, "派车通知") + { + /* + 关键词 + 出车时间 {{keyword1.DATA}} + 用车部门 {{keyword2.DATA}} + 目的地 {{keyword3.DATA}} + {{remark.DATA}} + */ + + keyword1 = new TemplateDataItem(ArriveDate); + keyword2 = new TemplateDataItem(department); + keyword3 = new TemplateDataItem(address); + } + + public TemplateDataItem keyword1 { get; set; } + public TemplateDataItem keyword2 { get; set; } + public TemplateDataItem keyword3 { get; set; } + + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.WeChat.Core/Helper/SnsHelper.cs b/src/Znyc.Dispatching.WeChat.Core/Helper/SnsHelper.cs new file mode 100644 index 0000000..6b12228 --- /dev/null +++ b/src/Znyc.Dispatching.WeChat.Core/Helper/SnsHelper.cs @@ -0,0 +1,62 @@ +using Senparc.Weixin; +using Senparc.Weixin.CommonAPIs; +using Senparc.Weixin.WxOpen.AdvancedAPIs.Sns; +using System.Threading.Tasks; + +namespace Znyc.Dispatching.WeChat.Core.Helper +{ /// + /// WxApp接口 + /// + public static class SnsHelper + { + #region 同步方法 + + /// + /// code 换取 session_key + /// + /// + /// + /// + /// 保持默认:authorization_code + /// 请求超时时间 + /// + public static JsCode2JsonResult JsCode2Json(string appId, string secret, string jsCode, + string grantType = "authorization_code", int timeOut = Config.TIME_OUT) + { + string urlFormat = + Config.ApiMpHost + "/sns/jscode2session?appid={0}&secret={1}&js_code={2}&grant_type={3}"; + + string url = string.Format(urlFormat, appId, secret, jsCode, grantType); + JsCode2JsonResult result = CommonJsonSend.Send(null, url, null, CommonJsonSendType.GET); + return result; + } + + #endregion 同步方法 + + #region 异步方法 + + /// + /// 【异步方法】code 换取 session_key + /// + /// + /// + /// + /// 保持默认:authorization_code + /// 请求超时时间 + /// + public static async Task JsCode2JsonAsync(string appId, string secret, string jsCode, + string grantType = "authorization_code", int timeOut = Config.TIME_OUT) + { + string urlFormat = + Config.ApiMpHost + "/sns/jscode2session?appid={0}&secret={1}&js_code={2}&grant_type={3}"; + + string url = string.Format(urlFormat, appId, secret, jsCode, grantType); + + JsCode2JsonResult result = await CommonJsonSend.SendAsync(null, url, null, CommonJsonSendType.GET) + .ConfigureAwait(false); + return result; + } + + #endregion 异步方法 + } +} diff --git a/src/Znyc.Dispatching.WeChat.Core/WeChatStartup.cs b/src/Znyc.Dispatching.WeChat.Core/WeChatStartup.cs new file mode 100644 index 0000000..5cd22f7 --- /dev/null +++ b/src/Znyc.Dispatching.WeChat.Core/WeChatStartup.cs @@ -0,0 +1,33 @@ +using Furion; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Senparc.CO2NET; +using Senparc.CO2NET.RegisterServices; +using Senparc.Weixin.Entities; +using Senparc.Weixin.MP.Containers; +using Senparc.Weixin.RegisterServices; + +namespace Znyc.Dispatching.WeChat.Core +{ + [AppStartup(70)] + public class WeChatStartup : AppStartup + { + public void ConfigureServices(IServiceCollection services) + { + services.AddSenparcWeixinServices(App.Configuration); + + } + + public void Configure(IApplicationBuilder app, IOptions senparcSetting, + IOptions senparcWeixinSetting) + { + IRegisterService register = RegisterService.Start(senparcSetting.Value) + .UseSenparcGlobal(); + register.UseSenparcGlobal(true); + + AccessTokenContainer.RegisterAsync(App.Configuration["SenparcWeixinSetting:WeixinAppId"], App.Configuration["SenparcWeixinSetting:WeixinAppSecret"]); + + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.WeChat.Core/Znyc.Dispatching.WeChat.Core.csproj b/src/Znyc.Dispatching.WeChat.Core/Znyc.Dispatching.WeChat.Core.csproj new file mode 100644 index 0000000..91f545d --- /dev/null +++ b/src/Znyc.Dispatching.WeChat.Core/Znyc.Dispatching.WeChat.Core.csproj @@ -0,0 +1,36 @@ + + + + net6.0 + + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + + + + + + + + + + + + diff --git a/src/Znyc.Dispatching.WeChat.Core/wechatsettings.Development.json b/src/Znyc.Dispatching.WeChat.Core/wechatsettings.Development.json new file mode 100644 index 0000000..5660f9f --- /dev/null +++ b/src/Znyc.Dispatching.WeChat.Core/wechatsettings.Development.json @@ -0,0 +1,18 @@ +{ + "SenparcSetting": { + "IsDebug": false, + "DefaultCacheNamespace": "", + "SenparcUnionAgentKey": "" + }, + "SenparcWeixinSetting": { + "IsDebug": false, + "WxOpenAppId": "wxa883c42a8db02847", + "WxOpenAppSecret": "4b9bd0910aab6c7b5266b07d5c17dba6", + "WxOpenToken": "", + "WxOpenEncodingAESKey": "", + "Token": "", + "EncodingAESKey": "", + "WeixinAppId": "wx07c574aca93ae8d9", + "WeixinAppSecret": "2d202f91258131f63565e11bdb065f6b" + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.WeChat.Core/wechatsettings.Production.json b/src/Znyc.Dispatching.WeChat.Core/wechatsettings.Production.json new file mode 100644 index 0000000..ded8ef4 --- /dev/null +++ b/src/Znyc.Dispatching.WeChat.Core/wechatsettings.Production.json @@ -0,0 +1,19 @@ +{ + "SenparcSetting": { + "IsDebug": false, + "DefaultCacheNamespace": "DefaultCache", + "SenparcUnionAgentKey": "#{SenparcUnionAgentKey}#" + }, + "SenparcWeixinSetting": { + "IsDebug": false, + "WxOpenAppId": "wxa883c42a8db02847", + "WxOpenAppSecret": "4b9bd0910aab6c7b5266b07d5c17dba6", + "WxOpenToken": "#{WxOpenToken}#", + "WxOpenEncodingAESKey": "#{WxOpenEncodingAESKey}#", + + "Token": "#{Token}#", + "EncodingAESKey": "#{EncodingAESKey}#", + "WeixinAppId": "wx07c574aca93ae8d9", + "WeixinAppSecret": "2d202f91258131f63565e11bdb065f6b" + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.WeChat.Core/wechatsettings.Staging.json b/src/Znyc.Dispatching.WeChat.Core/wechatsettings.Staging.json new file mode 100644 index 0000000..5660f9f --- /dev/null +++ b/src/Znyc.Dispatching.WeChat.Core/wechatsettings.Staging.json @@ -0,0 +1,18 @@ +{ + "SenparcSetting": { + "IsDebug": false, + "DefaultCacheNamespace": "", + "SenparcUnionAgentKey": "" + }, + "SenparcWeixinSetting": { + "IsDebug": false, + "WxOpenAppId": "wxa883c42a8db02847", + "WxOpenAppSecret": "4b9bd0910aab6c7b5266b07d5c17dba6", + "WxOpenToken": "", + "WxOpenEncodingAESKey": "", + "Token": "", + "EncodingAESKey": "", + "WeixinAppId": "wx07c574aca93ae8d9", + "WeixinAppSecret": "2d202f91258131f63565e11bdb065f6b" + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Web.Core/Handlers/JwtHandler.cs b/src/Znyc.Dispatching.Web.Core/Handlers/JwtHandler.cs new file mode 100644 index 0000000..fd5d678 --- /dev/null +++ b/src/Znyc.Dispatching.Web.Core/Handlers/JwtHandler.cs @@ -0,0 +1,96 @@ +using Furion; +using Furion.Authorization; +using Furion.DataEncryption; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using System.Threading.Tasks; +using Znyc.Dispatching.Application; +using Znyc.Dispatching.Core; +using Znyc.Dispatching.Core.Extension; +using Znyc.Dispatching.Core.Options; + +namespace Znyc.Dispatching.Web.Core +{ + public class JwtHandler : AppAuthorizeHandler + { + /// + /// 重写 Handler 添加自动刷新 + /// + /// + /// + public override async Task HandleAsync(AuthorizationHandlerContext context) + { + // 自动刷新Token + if (JWTEncryption.AutoRefreshToken(context, context.GetCurrentHttpContext(), + App.GetOptions().ExpiredTime, + App.GetOptions().ExpiredTime)) + { + await AuthorizeHandleAsync(context); + } + else + { + context.Fail(); // 授权失败 + DefaultHttpContext currentHttpContext = context.GetCurrentHttpContext(); + if (currentHttpContext == null) + { + return; + } + + currentHttpContext.SignoutToSwagger(); + } + } + + /// + /// 授权判断逻辑,授权通过返回 true,否则返回 false + /// + /// + /// + /// + public override async Task PipelineAsync(AuthorizationHandlerContext context, DefaultHttpContext httpContext) + { + // 此处已经自动验证 Jwt Token的有效性了,无需手动验证 + return await CheckAuthorzieAsync(httpContext); + } + + + /// + /// 检查权限 + /// + /// + /// + private static async Task CheckAuthorzieAsync(DefaultHttpContext httpContext) + { + IUserManager _userManager = App.GetService(); + ICacheService _cacheService = App.GetService(); + //验证jwt有效期中是否被强制下线 + var token = await _cacheService.GetTokenAsync(_userManager.UserId); + if (token.IsNull()) + { + return false; + } + return true; + + #region 权限验证(暂时屏蔽) + + + // // 路由名称 + // string routeName = httpContext.Request.Path.Value.Substring(1).Replace("/", ":"); + // // 默认路由(获取登录用户信息) + // List defalutRoute = new List() + // { + // "Login" + // }; + + // if (defalutRoute.Contains(routeName)) + // { + // return true; + // } + // //获取用户权限集合 + // List permissionList = await App.GetService().GetLoginPermissionList(.UserId); + //// 检查授权 + // return permissionList.Contains(routeName); + + #endregion 权限验证(暂时屏蔽) + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Web.Core/Startup.cs b/src/Znyc.Dispatching.Web.Core/Startup.cs new file mode 100644 index 0000000..9fc6292 --- /dev/null +++ b/src/Znyc.Dispatching.Web.Core/Startup.cs @@ -0,0 +1,96 @@ +using Furion; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; +using Yitter.IdGenerator; +using Znyc.Dispatching.Core; +using Znyc.Dispatching.Core.Cache; +using Znyc.Dispatching.Core.Options; +using Znyc.Dispatching.Web.Core; + +namespace Znyc.Dispatching.WeChat.Core +{ + [AppStartup(100)] + public class Startup : AppStartup + { + public void ConfigureServices(IServiceCollection services) + { + services.AddConfigurableOptions(); + services.AddJwt(enableGlobalAuthorize: true); + services.AddCorsAccessor(); + services.AddRemoteRequest(); + services.AddConfigurableOptions(); + + + services.AddControllersWithViews() + .AddNewtonsoftJson(options => + { + // 首字母小写(驼峰样式) + options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); + // 时间格式化 + options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss"; + // 忽略循环引用 + options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; + // 忽略空值 + // options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore; + }) + //.AddMvcFilter() + .AddInjectWithUnifyResult(); + // .AddDataValidation(); + + services.AddRemoteRequest(); + + services.AddViewEngine(); + + services.AddSignalR(); + + services.AddConfigurableOptions(); + //配置微信 + services.AddConfigurableOptions(); + //配置短信 + services.AddConfigurableOptions(); + + } + + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseHttpsRedirection(); + + app.UseStaticFiles(); + + //app.UseSerilogRequestLogging(); + + app.UseRouting(); + + app.UseCorsAccessor(); + + app.UseAuthentication(); + + app.UseAuthorization(); + + app.UseInject(string.Empty); + + app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); + + // 设置雪花Id的workerId,确保每个实例workerId都应不同 + var workerId = ushort.Parse(App.Configuration["SnowId:WorkerId"] ?? "1"); + YitIdHelper.SetIdGenerator(new IdGeneratorOptions { WorkerId = workerId }); + + + + + // 事件总线,订阅服务 + //app.ConfigureEventBus(); + + + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Web.Core/Znyc.Dispatching.WeChat.Core.xml b/src/Znyc.Dispatching.Web.Core/Znyc.Dispatching.WeChat.Core.xml new file mode 100644 index 0000000..4cd86e0 --- /dev/null +++ b/src/Znyc.Dispatching.Web.Core/Znyc.Dispatching.WeChat.Core.xml @@ -0,0 +1,30 @@ + + + + Znyc.Dispatching.Web.Core + + + + + 重写 Handler 添加自动刷新 + + + + + + + 授权判断逻辑,授权通过返回 true,否则返回 false + + + + + + + + 检查权限 + + + + + + diff --git a/src/Znyc.Dispatching.Web.Core/Znyc.Dispatching.Web.Core.csproj b/src/Znyc.Dispatching.Web.Core/Znyc.Dispatching.Web.Core.csproj new file mode 100644 index 0000000..6c8627c --- /dev/null +++ b/src/Znyc.Dispatching.Web.Core/Znyc.Dispatching.Web.Core.csproj @@ -0,0 +1,19 @@ + + + + net6.0 + 1701;1702;1591 + Znyc.Dispatching.WeChat.Core.xml + + + + + + + + + + + + + diff --git a/src/Znyc.Dispatching.Web.Core/Znyc.Dispatching.Web.Core.xml b/src/Znyc.Dispatching.Web.Core/Znyc.Dispatching.Web.Core.xml new file mode 100644 index 0000000..461c5fc --- /dev/null +++ b/src/Znyc.Dispatching.Web.Core/Znyc.Dispatching.Web.Core.xml @@ -0,0 +1,34 @@ + + + + + Znyc.Dispatching.Web.Core + + + + + 重写 Handler 添加自动刷新 + + + + + + + 授权判断逻辑,授权通过返回 true,否则返回 false + + + + + + + + 检查权限 + + + + + + \ No newline at end of file diff --git a/src/Znyc.Dispatching.Web.Entry/Program.cs b/src/Znyc.Dispatching.Web.Entry/Program.cs new file mode 100644 index 0000000..4cc81d2 --- /dev/null +++ b/src/Znyc.Dispatching.Web.Entry/Program.cs @@ -0,0 +1,13 @@ +using NLog; +using NLog.Web; + +var logger = NLog.LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger(); + +var builder = WebApplication.CreateBuilder(args).Inject(); +builder.Logging.ClearProviders(); +builder.Logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Warning); +builder.Host.UseNLog(); +var app = builder.Build(); +app.Run(); + + diff --git a/src/Znyc.Dispatching.Web.Entry/Properties/PublishProfiles/FolderProfile.pubxml b/src/Znyc.Dispatching.Web.Entry/Properties/PublishProfiles/FolderProfile.pubxml new file mode 100644 index 0000000..040b013 --- /dev/null +++ b/src/Znyc.Dispatching.Web.Entry/Properties/PublishProfiles/FolderProfile.pubxml @@ -0,0 +1,16 @@ + + + + + False + False + True + Release + Any CPU + FileSystem + bin\Release\net5.0\publish\ + FileSystem + + \ No newline at end of file diff --git a/src/Znyc.Dispatching.Web.Entry/Properties/PublishProfiles/FolderProfile1.pubxml b/src/Znyc.Dispatching.Web.Entry/Properties/PublishProfiles/FolderProfile1.pubxml new file mode 100644 index 0000000..377dd20 --- /dev/null +++ b/src/Znyc.Dispatching.Web.Entry/Properties/PublishProfiles/FolderProfile1.pubxml @@ -0,0 +1,16 @@ + + + + + False + False + True + Release + Any CPU + FileSystem + bin\Release\net6.0\publish\ + FileSystem + + \ No newline at end of file diff --git a/src/Znyc.Dispatching.Web.Entry/Properties/launchSettings.json b/src/Znyc.Dispatching.Web.Entry/Properties/launchSettings.json new file mode 100644 index 0000000..577cd94 --- /dev/null +++ b/src/Znyc.Dispatching.Web.Entry/Properties/launchSettings.json @@ -0,0 +1,36 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:53785", + "sslPort": 44342 + } + }, + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Znyc.Dispatching.Web.Entry": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": "true", + "applicationUrl": "http://localhost:80" + }, + "Docker": { + "commandName": "Docker", + "launchBrowser": true, + "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}", + "publishAllPorts": true, + "useSSL": true + } + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Web.Entry/Znyc.Dispatching.Web.Entry.csproj b/src/Znyc.Dispatching.Web.Entry/Znyc.Dispatching.Web.Entry.csproj new file mode 100644 index 0000000..6073b6f --- /dev/null +++ b/src/Znyc.Dispatching.Web.Entry/Znyc.Dispatching.Web.Entry.csproj @@ -0,0 +1,52 @@ + + + + net6.0 + enable + Linux + + + + + + + + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + PreserveNewest + + + Always + + + + + cf45ded9-40d9-4078-a133-25f7f15d258e + + \ No newline at end of file diff --git a/src/Znyc.Dispatching.Web.Entry/appsettings.Development.json b/src/Znyc.Dispatching.Web.Entry/appsettings.Development.json new file mode 100644 index 0000000..18be695 --- /dev/null +++ b/src/Znyc.Dispatching.Web.Entry/appsettings.Development.json @@ -0,0 +1,89 @@ +{ + "AllowedHosts": "*", + "Upload": { + //ͷ + "avatar": { + //ϴ· D:/upload/admin/avatar + "uploadPath": "../upload/avatar", + //· + "requestPath": "/upload/avatar", + //ȡ· + "readPath": "https://znyc-images-1304677865.cos.ap-guangzhou.myqcloud.com/", + + //·ڸʽ yyyy/MM/dd + "dateTimeFormat": "", + //{û} + "format": "", + //ͼƬС 1M = 1 * 1024 * 1024 + "maxSize": 1048576, + //ϴ-1 + "limit": 1, + //ͼƬʽ + "contentType": [ "image/jpg", "image/png", "image/jpeg", "image/gif" ] + }, + //ĵͼƬ + "document": { + //ϴ· D:/upload/admin/document + "uploadPath": "../upload/document", + //· + "requestPath": "/images", + //·ڸʽ yyyy/MM/dd + "dateTimeFormat": "", + //{ĵ} + "format": "", + //ͼƬС 1M = 1 * 1024 * 1024 + "maxSize": 1048576, + //ϴ-1 + "limit": -1, + //ͼƬʽ + "contentType": [ "image/jpg", "image/png", "image/jpeg", "image/gif" ] + }, + + "secretId": "AKIDGSN2VjJkZ7pIYzcdo0zjDCKCnQpEhXbW", + "secretKey": "rQDI7fuoUyIEvLT5RWKqkUyGQJwBiU2P", + "bucket": "znyc-images-1304677865", + "region": "ap-guangzhou" + }, + "UploadInfo": { + "secretId": "AKIDGMBk7D3cuvf8KFtGMBj00iALFsEJ7dsq", + "secretKey": "i9IBn8Bzav0pMcbJkunPjmY3HsCF2Zom", + "bucket": "znyc-images-1304677865", + "region": "ap-guangzhou", + "allowPrefix": "a.jpg", + "durationSeconds": 1800, + "allowActions": [ + "name/cos:PutObject", + // ϴСϴ + "name/cos:PostObject", + // Ƭϴ + "name/cos:InitiateMultipartUpload", + "name/cos:ListMultipartUploads", + "name/cos:ListParts", + "name/cos:UploadPart", + "name/cos:CompleteMultipartUpload" + ] + }, + "RabbitMQ": { + "Enabled": false, + "Connection": "1.14.140.50", + "UserName": "znyc", + "Password": "eOmgADo3G", + "RetryCount": 3 + }, + "EventBus": { + "Enabled": false, + "SubscriptionClientName": "Dispatching" + }, + "Exceptionless": { + "ApiKey": "0V64pWS506bwhU73xqsXjznXfo23i8ClxLTDD0WE", + "ServerUrl": "http://42.194.147.251:5000" + }, + "Hangfire": { + "Login": "znyc", + "Password": "znyc2021" + + }, + "SnowId": { + "WorkerId": "1" // ȡֵΧ0~63,Ĭ1 + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Web.Entry/appsettings.Production.json b/src/Znyc.Dispatching.Web.Entry/appsettings.Production.json new file mode 100644 index 0000000..a133484 --- /dev/null +++ b/src/Znyc.Dispatching.Web.Entry/appsettings.Production.json @@ -0,0 +1,93 @@ +{ + "AppSettings": { + "InjectSpecificationDocument": true + }, + "AllowedHosts": "*", + + "Upload": { + //ͷ + "avatar": { + //ϴ· D:/upload/admin/avatar + "uploadPath": "../upload/avatar", + //· + "requestPath": "/upload/avatar", + //ȡ· + "readPath": "https://znyc-images-1304677865.cos.ap-guangzhou.myqcloud.com/", + + //·ڸʽ yyyy/MM/dd + "dateTimeFormat": "", + //{û} + "format": "", + //ͼƬС 1M = 1 * 1024 * 1024 + "maxSize": 1048576, + //ϴ-1 + "limit": 1, + //ͼƬʽ + "contentType": [ "image/jpg", "image/png", "image/jpeg", "image/gif" ] + }, + //ĵͼƬ + "document": { + //ϴ· D:/upload/admin/document + "uploadPath": "../upload/document", + //· + "requestPath": "/images", + //·ڸʽ yyyy/MM/dd + "dateTimeFormat": "", + //{ĵ} + "format": "", + //ͼƬС 1M = 1 * 1024 * 1024 + "maxSize": 1048576, + //ϴ-1 + "limit": -1, + //ͼƬʽ + "contentType": [ "image/jpg", "image/png", "image/jpeg", "image/gif" ] + }, + + "secretId": "AKIDGSN2VjJkZ7pIYzcdo0zjDCKCnQpEhXbW", + "secretKey": "rQDI7fuoUyIEvLT5RWKqkUyGQJwBiU2P", + "bucket": "znyc-images-1304677865", + "region": "ap-guangzhou" + }, + "UploadInfo": { + "secretId": "AKIDGMBk7D3cuvf8KFtGMBj00iALFsEJ7dsq", + "secretKey": "i9IBn8Bzav0pMcbJkunPjmY3HsCF2Zom", + "bucket": "znyc-images-1304677865", + "region": "ap-guangzhou", + "allowPrefix": "a.jpg", + "durationSeconds": 1800, + "allowActions": [ + "name/cos:PutObject", + // ϴСϴ + "name/cos:PostObject", + // Ƭϴ + "name/cos:InitiateMultipartUpload", + "name/cos:ListMultipartUploads", + "name/cos:ListParts", + "name/cos:UploadPart", + "name/cos:CompleteMultipartUpload" + ] + }, + "RabbitMQ": { + "Enabled": false, + "Connection": "1.14.140.50", + "UserName": "znyc", + "Password": "eOmgADo3G", + "RetryCount": 3 + }, + "EventBus": { + "Enabled": false, + "SubscriptionClientName": "Dispatching" + }, + "Exceptionless": { + "ApiKey": "Z74DxFmICy9ZLGmIBOtWkWxNvWbUlmZSS1PkH7hb", + "ServerUrl": "http://1.14.140.50:5000" + }, + "Hangfire": { + "Login": "root", + "Password": "jxdieJh/l" + + }, + "SnowId": { + "WorkerId": "1" // ȡֵΧ0~63,Ĭ1 + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Web.Entry/appsettings.Staging.json b/src/Znyc.Dispatching.Web.Entry/appsettings.Staging.json new file mode 100644 index 0000000..4737a10 --- /dev/null +++ b/src/Znyc.Dispatching.Web.Entry/appsettings.Staging.json @@ -0,0 +1,89 @@ +{ + "AllowedHosts": "*", + "Upload": { + //ͷ + "avatar": { + //ϴ· D:/upload/admin/avatar + "uploadPath": "../upload/avatar", + //· + "requestPath": "/upload/avatar", + //ȡ· + "readPath": "https://znyc-images-1304677865.cos.ap-guangzhou.myqcloud.com/", + + //·ڸʽ yyyy/MM/dd + "dateTimeFormat": "", + //{û} + "format": "", + //ͼƬС 1M = 1 * 1024 * 1024 + "maxSize": 1048576, + //ϴ-1 + "limit": 1, + //ͼƬʽ + "contentType": [ "image/jpg", "image/png", "image/jpeg", "image/gif" ] + }, + //ĵͼƬ + "document": { + //ϴ· D:/upload/admin/document + "uploadPath": "../upload/document", + //· + "requestPath": "/images", + //·ڸʽ yyyy/MM/dd + "dateTimeFormat": "", + //{ĵ} + "format": "", + //ͼƬС 1M = 1 * 1024 * 1024 + "maxSize": 1048576, + //ϴ-1 + "limit": -1, + //ͼƬʽ + "contentType": [ "image/jpg", "image/png", "image/jpeg", "image/gif" ] + }, + + "secretId": "AKIDGSN2VjJkZ7pIYzcdo0zjDCKCnQpEhXbW", + "secretKey": "rQDI7fuoUyIEvLT5RWKqkUyGQJwBiU2P", + "bucket": "znyc-images-1304677865", + "region": "ap-guangzhou" + }, + "UploadInfo": { + "secretId": "AKIDGMBk7D3cuvf8KFtGMBj00iALFsEJ7dsq", + "secretKey": "i9IBn8Bzav0pMcbJkunPjmY3HsCF2Zom", + "bucket": "znyc-images-1304677865", + "region": "ap-guangzhou", + "allowPrefix": "a.jpg", + "durationSeconds": 1800, + "allowActions": [ + "name/cos:PutObject", + // ϴСϴ + "name/cos:PostObject", + // Ƭϴ + "name/cos:InitiateMultipartUpload", + "name/cos:ListMultipartUploads", + "name/cos:ListParts", + "name/cos:UploadPart", + "name/cos:CompleteMultipartUpload" + ] + }, + "RabbitMQ": { + "Enabled": false, + "Connection": "1.14.140.50", + "UserName": "znyc", + "Password": "eOmgADo3G", + "RetryCount": 3 + }, + "EventBus": { + "Enabled": false, + "SubscriptionClientName": "Dispatching" + }, + "Exceptionless": { + "ApiKey": "0V64pWS506bwhU73xqsXjznXfo23i8ClxLTDD0WE", + "ServerUrl": "http://42.194.147.251:5000" + }, + "Hangfire": { + "Login": "faker", + "Password": "faker2021" + + }, + "SnowId": { + "WorkerId": "1" // ȡֵΧ0~63,Ĭ1 + } +} \ No newline at end of file diff --git a/src/Znyc.Dispatching.Web.Entry/nlog.config b/src/Znyc.Dispatching.Web.Entry/nlog.config new file mode 100644 index 0000000..e6acb50 --- /dev/null +++ b/src/Znyc.Dispatching.Web.Entry/nlog.config @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Znyc.Dispatching.sln b/src/Znyc.Dispatching.sln new file mode 100644 index 0000000..7ae6a32 --- /dev/null +++ b/src/Znyc.Dispatching.sln @@ -0,0 +1,85 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31825.309 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Znyc.Dispatching.Application", "Znyc.Dispatching.Application\Znyc.Dispatching.Application.csproj", "{AB699EE9-43A8-46F2-A855-04A26DE63372}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Znyc.Dispatching.EntityFramework.Core", "Znyc.Dispatching.EntityFramework.Core\Znyc.Dispatching.EntityFramework.Core.csproj", "{4BD77E5C-138D-4F2D-B709-F9020F306AF3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Znyc.Dispatching.Web.Core", "Znyc.Dispatching.Web.Core\Znyc.Dispatching.Web.Core.csproj", "{9D14BB78-DA2A-4040-B9DB-5A515B599181}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Znyc.Dispatching.Core", "Znyc.Dispatching.Core\Znyc.Dispatching.Core.csproj", "{4FB30091-15C7-4FD9-AB7D-266814F360F5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Znyc.Dispatching.Database.Migrations", "Znyc.Dispatching.Database.Migrations\Znyc.Dispatching.Database.Migrations.csproj", "{EA769D36-9D55-47A6-945F-1687EA95179F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Znyc.Dispatching.Web.Entry", "Znyc.Dispatching.Web.Entry\Znyc.Dispatching.Web.Entry.csproj", "{C8D99F52-EDC7-411F-8300-6DB14BF59E8C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Znyc.Dispatching.Tests", "Znyc.Dispatchimg.Test\Znyc.Dispatching.Tests.csproj", "{A712DF7E-1D6A-4594-BE81-360608861826}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Znyc.Dispatching.MongoDb.Repository", "Znyc.Dispatching.MongoDb.Repository\Znyc.Dispatching.MongoDb.Repository.csproj", "{61B8F8EB-3CA7-4B49-A952-538FE1FBBF9C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Znyc.Dispatching.WeChat.Core", "Znyc.Dispatching.WeChat.Core\Znyc.Dispatching.WeChat.Core.csproj", "{6807B4D4-1AF4-45C1-A070-51FF81F18BAD}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Znyc.Dispatching.Common", "Znyc.Dispatching.Common\Znyc.Dispatching.Common.csproj", "{869240E7-2C85-4EF6-A42E-78A7DA34351D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Znyc.Dispatching.Tasks", "Znyc.Dispatching.Tasks\Znyc.Dispatching.Tasks.csproj", "{F947FB16-40F3-42B3-921D-49B83AC82886}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {AB699EE9-43A8-46F2-A855-04A26DE63372}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AB699EE9-43A8-46F2-A855-04A26DE63372}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AB699EE9-43A8-46F2-A855-04A26DE63372}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AB699EE9-43A8-46F2-A855-04A26DE63372}.Release|Any CPU.Build.0 = Release|Any CPU + {4BD77E5C-138D-4F2D-B709-F9020F306AF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4BD77E5C-138D-4F2D-B709-F9020F306AF3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4BD77E5C-138D-4F2D-B709-F9020F306AF3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4BD77E5C-138D-4F2D-B709-F9020F306AF3}.Release|Any CPU.Build.0 = Release|Any CPU + {9D14BB78-DA2A-4040-B9DB-5A515B599181}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9D14BB78-DA2A-4040-B9DB-5A515B599181}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9D14BB78-DA2A-4040-B9DB-5A515B599181}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9D14BB78-DA2A-4040-B9DB-5A515B599181}.Release|Any CPU.Build.0 = Release|Any CPU + {4FB30091-15C7-4FD9-AB7D-266814F360F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4FB30091-15C7-4FD9-AB7D-266814F360F5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4FB30091-15C7-4FD9-AB7D-266814F360F5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4FB30091-15C7-4FD9-AB7D-266814F360F5}.Release|Any CPU.Build.0 = Release|Any CPU + {EA769D36-9D55-47A6-945F-1687EA95179F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EA769D36-9D55-47A6-945F-1687EA95179F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EA769D36-9D55-47A6-945F-1687EA95179F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EA769D36-9D55-47A6-945F-1687EA95179F}.Release|Any CPU.Build.0 = Release|Any CPU + {C8D99F52-EDC7-411F-8300-6DB14BF59E8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C8D99F52-EDC7-411F-8300-6DB14BF59E8C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C8D99F52-EDC7-411F-8300-6DB14BF59E8C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C8D99F52-EDC7-411F-8300-6DB14BF59E8C}.Release|Any CPU.Build.0 = Release|Any CPU + {A712DF7E-1D6A-4594-BE81-360608861826}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A712DF7E-1D6A-4594-BE81-360608861826}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A712DF7E-1D6A-4594-BE81-360608861826}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A712DF7E-1D6A-4594-BE81-360608861826}.Release|Any CPU.Build.0 = Release|Any CPU + {61B8F8EB-3CA7-4B49-A952-538FE1FBBF9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {61B8F8EB-3CA7-4B49-A952-538FE1FBBF9C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {61B8F8EB-3CA7-4B49-A952-538FE1FBBF9C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {61B8F8EB-3CA7-4B49-A952-538FE1FBBF9C}.Release|Any CPU.Build.0 = Release|Any CPU + {6807B4D4-1AF4-45C1-A070-51FF81F18BAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6807B4D4-1AF4-45C1-A070-51FF81F18BAD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6807B4D4-1AF4-45C1-A070-51FF81F18BAD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6807B4D4-1AF4-45C1-A070-51FF81F18BAD}.Release|Any CPU.Build.0 = Release|Any CPU + {869240E7-2C85-4EF6-A42E-78A7DA34351D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {869240E7-2C85-4EF6-A42E-78A7DA34351D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {869240E7-2C85-4EF6-A42E-78A7DA34351D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {869240E7-2C85-4EF6-A42E-78A7DA34351D}.Release|Any CPU.Build.0 = Release|Any CPU + {F947FB16-40F3-42B3-921D-49B83AC82886}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F947FB16-40F3-42B3-921D-49B83AC82886}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F947FB16-40F3-42B3-921D-49B83AC82886}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F947FB16-40F3-42B3-921D-49B83AC82886}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {B2073C2C-0FD3-452B-8047-8134D68E12CE} + EndGlobalSection +EndGlobal