weijianghai 1 year ago
commit
87a2871398
43 changed files with 3515 additions and 0 deletions
  1. 1037 0
      .gitignore
  2. 130 0
      pom.xml
  3. 17 0
      readme.md
  4. 11 0
      scripts/copy.sh
  5. 6 0
      scripts/rollback.sh
  6. 3 0
      scripts/run.sh
  7. 5 0
      scripts/stop.sh
  8. 7 0
      scripts/update.sh
  9. 15 0
      src/main/java/com/nokia/finance/tasks/FinanceTasksApplication.java
  10. 85 0
      src/main/java/com/nokia/finance/tasks/common/R.java
  11. 10 0
      src/main/java/com/nokia/finance/tasks/common/exception/BizException.java
  12. 22 0
      src/main/java/com/nokia/finance/tasks/common/exception/MyRuntimeException.java
  13. 91 0
      src/main/java/com/nokia/finance/tasks/common/utils/psql/PsqlUtil.java
  14. 51 0
      src/main/java/com/nokia/finance/tasks/config/JobConfig.java
  15. 21 0
      src/main/java/com/nokia/finance/tasks/config/SchedulingConfig.java
  16. 25 0
      src/main/java/com/nokia/finance/tasks/config/ValidatorConfig.java
  17. 86 0
      src/main/java/com/nokia/finance/tasks/config/web/ControllerExceptionHandler.java
  18. 19 0
      src/main/java/com/nokia/finance/tasks/config/web/MyDispatcherServlet.java
  19. 58 0
      src/main/java/com/nokia/finance/tasks/config/web/MyHttpServletRequestWrapper.java
  20. 47 0
      src/main/java/com/nokia/finance/tasks/config/web/MyHttpServletResponseWrapper.java
  21. 53 0
      src/main/java/com/nokia/finance/tasks/config/web/MyWebMvcConfigurer.java
  22. 74 0
      src/main/java/com/nokia/finance/tasks/config/web/RequestLogHandlerInterceptor.java
  23. 26 0
      src/main/java/com/nokia/finance/tasks/controller/JobController.java
  24. 20 0
      src/main/java/com/nokia/finance/tasks/controller/TestController.java
  25. 26 0
      src/main/java/com/nokia/finance/tasks/dao/common/AreaDao.java
  26. 26 0
      src/main/java/com/nokia/finance/tasks/dao/common/OrganizationDao.java
  27. 13 0
      src/main/java/com/nokia/finance/tasks/enums/JobEnum.java
  28. 333 0
      src/main/java/com/nokia/finance/tasks/jobs/car/ruixing/CljbxxJob.java
  29. 305 0
      src/main/java/com/nokia/finance/tasks/jobs/car/ruixing/YjbjrtjJob.java
  30. 12 0
      src/main/java/com/nokia/finance/tasks/pojo/RunJobDto.java
  31. 14 0
      src/main/java/com/nokia/finance/tasks/pojo/po/common/AreaPo.java
  32. 15 0
      src/main/java/com/nokia/finance/tasks/pojo/po/common/OrganizationPo.java
  33. 32 0
      src/main/java/com/nokia/finance/tasks/service/JobService.java
  34. 501 0
      src/main/java/com/nokia/finance/tasks/service/car/CarService.java
  35. 67 0
      src/main/java/com/nokia/finance/tasks/service/common/AreaService.java
  36. 68 0
      src/main/java/com/nokia/finance/tasks/service/common/OrganizationService.java
  37. 29 0
      src/main/resources/application-dev.yml
  38. 29 0
      src/main/resources/application-prod.yml
  39. 0 0
      src/main/resources/application.yml
  40. 51 0
      src/main/resources/logback-spring.xml
  41. 31 0
      src/test/java/com/nokia/finance/tasks/FinanceTasksApplicationTests.java
  42. 22 0
      src/test/java/com/nokia/finance/tasks/car/ruixing/CljbxxJobTests.java
  43. 22 0
      src/test/java/com/nokia/finance/tasks/car/ruixing/YjbjrtjJobTests.java

+ 1037 - 0
.gitignore

@@ -0,0 +1,1037 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
+### Intellij template
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# AWS User-specific
+.idea/**/aws.xml
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn.  Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# SonarLint plugin
+.idea/sonarlint/
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+### Java template
+# Compiled class file
+*.class
+
+# Log file
+*.log
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+replay_pid*
+
+### Maven template
+target/
+pom.xml.tag
+pom.xml.releaseBackup
+pom.xml.versionsBackup
+pom.xml.next
+release.properties
+dependency-reduced-pom.xml
+buildNumber.properties
+.mvn/timing.properties
+# https://github.com/takari/maven-wrapper#usage-without-binary-jar
+.mvn/wrapper/maven-wrapper.jar
+
+# Eclipse m2e generated files
+# Eclipse Core
+.project
+# JDT-specific (Eclipse Java Development Tools)
+.classpath
+
+### VisualStudioCode template
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+!.vscode/*.code-snippets
+
+# Local History for Visual Studio Code
+.history/
+
+# Built Visual Studio Code Extensions
+*.vsix
+
+### JetBrains+all template
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# AWS User-specific
+.idea/**/aws.xml
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn.  Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# SonarLint plugin
+.idea/sonarlint/
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+### JetBrains template
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# AWS User-specific
+.idea/**/aws.xml
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn.  Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# SonarLint plugin
+.idea/sonarlint/
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+### Eclipse template
+.metadata
+bin/
+tmp/
+*.tmp
+*.bak
+*.swp
+*~.nib
+local.properties
+.settings/
+.loadpath
+.recommenders
+
+# External tool builders
+.externalToolBuilders/
+
+# Locally stored "Eclipse launch configurations"
+*.launch
+
+# PyDev specific (Python IDE for Eclipse)
+*.pydevproject
+
+# CDT-specific (C/C++ Development Tooling)
+.cproject
+
+# CDT- autotools
+.autotools
+
+# Java annotation processor (APT)
+.factorypath
+
+# PDT-specific (PHP Development Tools)
+.buildpath
+
+# sbteclipse plugin
+.target
+
+# Tern plugin
+.tern-project
+
+# TeXlipse plugin
+.texlipse
+
+# STS (Spring Tool Suite)
+.springBeans
+
+# Code Recommenders
+.recommenders/
+
+# Annotation Processing
+.apt_generated/
+.apt_generated_test/
+
+# Scala IDE specific (Scala & Java development for Eclipse)
+.cache-main
+.scala_dependencies
+.worksheet
+
+# Uncomment this line if you wish to ignore the project description file.
+# Typically, this file would be tracked if it contains build/dependency configurations:
+#.project
+
+### Intellij+all template
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# AWS User-specific
+.idea/**/aws.xml
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn.  Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# SonarLint plugin
+.idea/sonarlint/
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+### Example user template template
+### Example user template
+
+# IntelliJ project files
+.idea
+*.iml
+out
+gen
+### VisualStudio template
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.tlog
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*.json
+coverage*.xml
+coverage*.info
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio 6 auto-generated project file (contains which files were open etc.)
+*.vbp
+
+# Visual Studio 6 workspace and project file (working project files containing files to include in project)
+*.dsw
+*.dsp
+
+# Visual Studio 6 technical files
+*.ncb
+*.aps
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# Visual Studio History (VSHistory) files
+.vshistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
+
+# VS Code files for those working on multiple tools
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+*.code-workspace
+
+# Local History for Visual Studio Code
+.history/
+
+# Windows Installer files from build outputs
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# JetBrains Rider
+*.sln.iml
+
+### JetBrains+iml template
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# AWS User-specific
+.idea/**/aws.xml
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn.  Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# SonarLint plugin
+.idea/sonarlint/
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+### Intellij+iml template
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# AWS User-specific
+.idea/**/aws.xml
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn.  Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# SonarLint plugin
+.idea/sonarlint/
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+/data/

+ 130 - 0
pom.xml

@@ -0,0 +1,130 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>com.nokia</groupId>
+    <artifactId>finance-tasks</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <name>finance-tasks</name>
+    <description>finance-tasks</description>
+    <properties>
+        <java.version>17</java.version>
+        <maven.compiler.source>${java.version}</maven.compiler.source>
+        <maven.compiler.target>${java.version}</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>${project.build.sourceEncoding}</project.reporting.outputEncoding>
+        <spring-boot.version>2.7.13</spring-boot.version>
+    </properties>
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-configuration-processor</artifactId>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all -->
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+            <version>5.8.23</version>
+        </dependency>
+        <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-csv -->
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-csv</artifactId>
+            <version>1.10.0</version>
+        </dependency>
+        <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-exec -->
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-exec</artifactId>
+            <version>1.3</version>
+        </dependency>
+        <!-- https://mvnrepository.com/artifact/org.apache.poi/poi -->
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi</artifactId>
+            <version>5.2.5</version>
+        </dependency>
+        <!-- https://mvnrepository.com/artifact/org.apache.poi/poi-ooxml -->
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi-ooxml</artifactId>
+            <version>5.2.5</version>
+        </dependency>
+        <!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
+        <dependency>
+            <groupId>com.google.code.gson</groupId>
+            <artifactId>gson</artifactId>
+            <version>2.10.1</version>
+        </dependency>
+        <!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter -->
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+            <version>3.5.4.1</version>
+        </dependency>
+        <dependency>
+            <groupId>org.postgresql</groupId>
+            <artifactId>postgresql</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+    </dependencies>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-dependencies</artifactId>
+                <version>${spring-boot.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <build>
+        <finalName>finance-tasks</finalName>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>repackage</id>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <excludes>
+                        <exclude>
+                            <groupId>org.projectlombok</groupId>
+                            <artifactId>lombok</artifactId>
+                        </exclude>
+                    </excludes>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 17 - 0
readme.md

@@ -0,0 +1,17 @@
+# 财务后端定时任务
+
+## 正式环境
+
+133.96.95.30/app/finance-tasks
+
+## 定时任务api
+
+### 睿行车辆基本信息日数据入库定时任务
+
+```shell
+curl --location 'http://localhost:39110/jobs/runJob' \
+--header 'Content-Type: application/json' \
+--data '{
+    "jobName": "CLJBXX_JOB"
+}'
+```

+ 11 - 0
scripts/copy.sh

@@ -0,0 +1,11 @@
+#!/bin/bash
+
+export PGPASSWORD=$4
+host=$1
+port=$2
+username=$3
+dbname=$5
+table=$6
+filename=$7
+columns=$8
+psql -h "${host}" -p "${port}" -U "${username}" -d "${dbname}" -c "\\copy ${table} ${columns} from ${filename} with csv header;"

+ 6 - 0
scripts/rollback.sh

@@ -0,0 +1,6 @@
+#!/bin/bash
+
+sh stop.sh
+rm -rf finance-tasks.jar
+mv finance-tasks.jar.bak finance-tasks.jar
+sh run.sh

+ 3 - 0
scripts/run.sh

@@ -0,0 +1,3 @@
+#!/bin/bash
+
+nohup /app/jdks/jdk17/bin/java -Dspring.profiles.active=prod -jar finance-tasks.jar >/dev/null 2>&1 &

+ 5 - 0
scripts/stop.sh

@@ -0,0 +1,5 @@
+#!/bin/bash
+
+for i in $(pgrep -f finance-tasks.jar); do
+  kill -9 "$i"
+done

+ 7 - 0
scripts/update.sh

@@ -0,0 +1,7 @@
+#!/bin/bash
+
+rm -rf finance-tasks.jar.bak
+sh stop.sh
+mv finance-tasks.jar finance-tasks.jar.bak
+mv finance-tasks.jar.new finance-tasks.jar
+sh run.sh

+ 15 - 0
src/main/java/com/nokia/finance/tasks/FinanceTasksApplication.java

@@ -0,0 +1,15 @@
+package com.nokia.finance.tasks;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.scheduling.annotation.EnableScheduling;
+
+@EnableScheduling
+@SpringBootApplication
+public class FinanceTasksApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(FinanceTasksApplication.class, args);
+    }
+
+}

+ 85 - 0
src/main/java/com/nokia/finance/tasks/common/R.java

@@ -0,0 +1,85 @@
+package com.nokia.finance.tasks.common;
+
+import lombok.Data;
+import org.slf4j.MDC;
+
+/**
+ * 返回值的统一包装
+ */
+@Data
+public class R<T> {
+    private Boolean success;
+    private Integer code;
+    private String message;
+    private T data = null;
+
+    /**
+     * 私有化构造方法,不允许在外部实例化
+     */
+    private R() {
+    }
+
+    /**
+     * 成功的静态方法
+     *
+     * @return R实例
+     */
+    public static <T> R<T> ok() {
+        R<T> r = new R<>();
+        r.setSuccess(true);
+        r.setCode(1);
+        r.setMessage("成功");
+        return r;
+    }
+
+    public static <T> R<T> ok(T data) {
+        R<T> r = new R<>();
+        r.setSuccess(true);
+        r.setCode(1);
+        r.setMessage("成功");
+        r.setData(data);
+        return r;
+    }
+
+    /**
+     * 失败的静态方法
+     *
+     * @return R实例
+     */
+    public static <T> R<T> error() {
+        R<T> r = new R<>();
+        r.setSuccess(false);
+        r.setCode(0);
+        r.setMessage("失败" + MDC.get("traceId"));
+        return r;
+    }
+
+    public static <T> R<T> error(String message) {
+        R<T> r = new R<>();
+        r.setSuccess(false);
+        r.setCode(0);
+        r.setMessage(message);
+        return r;
+    }
+
+    public R<T> success(Boolean success) {
+        this.setSuccess(success);
+        return this;
+    }
+
+
+    public R<T> code(Integer code) {
+        this.setCode(code);
+        return this;
+    }
+
+    public R<T> data(T object) {
+        this.setData(object);
+        return this;
+    }
+
+    public R<T> message(String message) {
+        this.setMessage(message);
+        return this;
+    }
+}

+ 10 - 0
src/main/java/com/nokia/finance/tasks/common/exception/BizException.java

@@ -0,0 +1,10 @@
+package com.nokia.finance.tasks.common.exception;
+
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor
+public class BizException extends RuntimeException{
+    public BizException(String message) {
+        super(message);
+    }
+}

+ 22 - 0
src/main/java/com/nokia/finance/tasks/common/exception/MyRuntimeException.java

@@ -0,0 +1,22 @@
+package com.nokia.finance.tasks.common.exception;
+
+public class MyRuntimeException extends RuntimeException{
+    public MyRuntimeException() {
+    }
+
+    public MyRuntimeException(String message) {
+        super(message);
+    }
+
+    public MyRuntimeException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public MyRuntimeException(Throwable cause) {
+        super(cause);
+    }
+
+    public MyRuntimeException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+        super(message, cause, enableSuppression, writableStackTrace);
+    }
+}

+ 91 - 0
src/main/java/com/nokia/finance/tasks/common/utils/psql/PsqlUtil.java

@@ -0,0 +1,91 @@
+package com.nokia.finance.tasks.common.utils.psql;
+
+import com.nokia.finance.tasks.common.exception.MyRuntimeException;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.exec.CommandLine;
+import org.apache.commons.exec.DefaultExecutor;
+import org.apache.commons.exec.ExecuteWatchdog;
+import org.apache.commons.exec.PumpStreamHandler;
+import org.springframework.util.StringUtils;
+
+import java.io.ByteArrayOutputStream;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * psql命令工具
+ */
+@Slf4j
+public class PsqlUtil {
+    /**
+     * 匹配psql copy成功结果
+     */
+    private static final Pattern PATTERN = Pattern.compile("^(COPY) (\\d+)$");
+
+    /**
+     * 导入csv
+     *
+     * @param script         脚本
+     * @param dbHost         数据库主机
+     * @param dbPort         数据库端口
+     * @param dbUsername     数据库用户名
+     * @param dbPassword     数据库密码
+     * @param dbName         数据库名字
+     * @param dbTable        数据库表
+     * @param csv            csv
+     * @param columns        字段
+     * @param timeout        超时ms
+     * @param minInsertCount 最小值插入数
+     */
+    public static void copyCsv(String script, String dbHost, String dbPort, String dbUsername, String dbPassword,
+                               String dbName, String dbTable, String csv, String columns, Long timeout,
+                               Long minInsertCount) {
+        String command = "sh " + script;
+        CommandLine commandLine = CommandLine.parse(command);
+        commandLine.addArgument(dbHost);
+        commandLine.addArgument(dbPort);
+        commandLine.addArgument(dbUsername);
+        commandLine.addArgument(dbPassword);
+        commandLine.addArgument(dbName);
+        commandLine.addArgument(dbTable);
+        commandLine.addArgument(csv);
+        commandLine.addArgument(columns);
+        log.info("command: {}", commandLine);
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        ByteArrayOutputStream err = new ByteArrayOutputStream();
+        DefaultExecutor executor = new DefaultExecutor();
+        ExecuteWatchdog watchdog = new ExecuteWatchdog(timeout);
+        executor.setWatchdog(watchdog);
+        PumpStreamHandler streamHandler = new PumpStreamHandler(out, err);
+        executor.setStreamHandler(streamHandler);
+        try {
+            int exitValue = executor.execute(commandLine);
+            log.info("exitValue: {}", exitValue);
+            String outString = out.toString();
+            Long count = null;
+            Matcher matcher = PATTERN.matcher(outString);
+            if (matcher.find()) {
+                count = Long.parseLong(matcher.group(2));
+            }
+            if (count == null) {
+                throw new MyRuntimeException("导入数据失败");
+            }
+            log.info("插入 {} 条数据", count);
+            if (minInsertCount != null && count < minInsertCount) {
+                throw new MyRuntimeException(csv + " 数据异常,少于 " + minInsertCount);
+            }
+        } catch (Exception e) {
+            if (watchdog.killedProcess()) {
+                throw new MyRuntimeException("执行超时", e);
+            }
+            throw new MyRuntimeException(e);
+        } finally {
+            String outString = out.toString();
+            String errString = err.toString();
+            log.info("out: {}", outString);
+            if (StringUtils.hasText(errString)) {
+                log.error("err: {}", errString);
+            }
+        }
+    }
+}

+ 51 - 0
src/main/java/com/nokia/finance/tasks/config/JobConfig.java

@@ -0,0 +1,51 @@
+package com.nokia.finance.tasks.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@ConfigurationProperties("job.config")
+@Data
+public class JobConfig {
+    /**
+     * copy脚本路径
+     */
+    private String copyScriptPath;
+    /**
+     * 导入的数据库ip
+     */
+    private String dbHost;
+    /**
+     * 数据库端口
+     */
+    private String dbPort;
+    /**
+     * 数据库账号
+     */
+    private String dbUsername;
+    /**
+     * 数据库密码
+     */
+    private String dbPassword;
+    /**
+     * 数据库名称
+     */
+    private String dbName;
+    /**
+     * 睿行车辆基本信息日数据路径
+     */
+    private String cljbxxSourcePath;
+    /**
+     * 睿行车辆基本信息日数据归档路径
+     */
+    private String cljbxxHistoryPath;
+    /**
+     * 睿行车辆越界报警日数据路径
+     */
+    private String yjbjrtjSourcePath;
+    /**
+     * 睿行车辆越界报警日数据归档路径
+     */
+    private String yjbjrtjHistoryPath;
+}

+ 21 - 0
src/main/java/com/nokia/finance/tasks/config/SchedulingConfig.java

@@ -0,0 +1,21 @@
+package com.nokia.finance.tasks.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.SchedulingConfigurer;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+import org.springframework.scheduling.config.ScheduledTaskRegistrar;
+
+@Configuration
+public class SchedulingConfig implements SchedulingConfigurer {
+    /**
+     * 定时任务线程池配置
+     */
+    @Override
+    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
+        ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
+        threadPoolTaskScheduler.setPoolSize(100);
+        threadPoolTaskScheduler.setThreadNamePrefix("job-");
+        threadPoolTaskScheduler.initialize();
+        taskRegistrar.setTaskScheduler(threadPoolTaskScheduler);
+    }
+}

+ 25 - 0
src/main/java/com/nokia/finance/tasks/config/ValidatorConfig.java

@@ -0,0 +1,25 @@
+package com.nokia.finance.tasks.config;
+
+import org.hibernate.validator.HibernateValidator;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.validation.Validation;
+import javax.validation.Validator;
+import javax.validation.ValidatorFactory;
+
+/**
+ * 校验配置
+ */
+@Configuration
+public class ValidatorConfig
+{
+    @Bean
+    public Validator validator()
+    {
+        try (ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class).configure()
+                .failFast(true).buildValidatorFactory()) {
+            return validatorFactory.getValidator();
+        }
+    }
+}

+ 86 - 0
src/main/java/com/nokia/finance/tasks/config/web/ControllerExceptionHandler.java

@@ -0,0 +1,86 @@
+package com.nokia.finance.tasks.config.web;
+
+import com.nokia.finance.tasks.common.R;
+import com.nokia.finance.tasks.common.exception.BizException;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.TypeMismatchException;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.http.converter.HttpMessageConversionException;
+import org.springframework.validation.BindException;
+import org.springframework.validation.FieldError;
+import org.springframework.web.HttpRequestMethodNotSupportedException;
+import org.springframework.web.bind.MissingRequestValueException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+import javax.validation.ValidationException;
+
+/**
+ * 请求异常处理
+ */
+@Slf4j
+@RestControllerAdvice
+public class ControllerExceptionHandler
+{
+    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
+    public ResponseEntity<Object> httpRequestMethodNotSupportedExceptionHandler(HttpRequestMethodNotSupportedException e)
+    {
+        log.warn(e.getMessage());
+        return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED).body(R.error().message(e.getMessage()));
+    }
+
+    @ExceptionHandler(MissingRequestValueException.class)
+    public ResponseEntity<Object> missingRequestValueExceptionHandler(MissingRequestValueException e)
+    {
+        log.warn(e.getMessage());
+        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(R.error().message(e.getMessage()));
+    }
+
+    @ExceptionHandler(HttpMessageConversionException.class)
+    public ResponseEntity<Object> httpMessageConversionExceptionHandler(HttpMessageConversionException e)
+    {
+        log.warn(e.getMessage());
+        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(R.error().message(e.getMessage()));
+    }
+
+    @ExceptionHandler(TypeMismatchException.class)
+    public ResponseEntity<Object> typeMismatchExceptionHandler(TypeMismatchException e)
+    {
+        log.warn(e.getMessage());
+        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(R.error().message(e.getMessage()));
+    }
+
+    @ExceptionHandler(BindException.class)
+    public ResponseEntity<Object> bindExceptionHandler(BindException e)
+    {
+        FieldError fieldError = e.getBindingResult().getFieldError();
+        String message = "";
+        if (fieldError != null)
+        {
+            message = fieldError.getDefaultMessage();
+        }
+        log.warn(message);
+        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(R.error().message(message));
+    }
+
+    @ExceptionHandler(ValidationException.class)
+    public ResponseEntity<Object> validationExceptionHandler(ValidationException e)
+    {
+        log.warn(e.getMessage());
+        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(R.error().message(e.getMessage()));
+    }
+
+    @ExceptionHandler(BizException.class)
+    public ResponseEntity<Object> bizExceptionHandler(BizException e)
+    {
+        return ResponseEntity.ok().body(R.error(e.getMessage()));
+    }
+
+    @ExceptionHandler(Exception.class)
+    public ResponseEntity<Object> exceptionHandler(Exception e)
+    {
+        log.error("╭( ′• o •′ )╭☞ 发生错误了 {}", e.getMessage(), e);
+        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(R.error());
+    }
+}

+ 19 - 0
src/main/java/com/nokia/finance/tasks/config/web/MyDispatcherServlet.java

@@ -0,0 +1,19 @@
+package com.nokia.finance.tasks.config.web;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.servlet.DispatcherServlet;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@Slf4j
+public class MyDispatcherServlet extends DispatcherServlet {
+
+    @Override
+    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
+        String uri = request.getRequestURI();
+        // 替换request和response
+        log.info("替换 {} 的request和response", uri);
+        super.doDispatch(new MyHttpServletRequestWrapper(request), new MyHttpServletResponseWrapper(response));
+    }
+}

+ 58 - 0
src/main/java/com/nokia/finance/tasks/config/web/MyHttpServletRequestWrapper.java

@@ -0,0 +1,58 @@
+package com.nokia.finance.tasks.config.web;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.util.StreamUtils;
+
+import javax.servlet.ReadListener;
+import javax.servlet.ServletInputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+
+/**
+ * 解决流只能读取一次问题
+ */
+@Slf4j
+public class MyHttpServletRequestWrapper extends HttpServletRequestWrapper {
+    private byte[] bytes;
+
+    public MyHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
+        super(request);
+        bytes = StreamUtils.copyToByteArray(request.getInputStream());
+    }
+
+    @Override
+    public ServletInputStream getInputStream() {
+        ByteArrayInputStream stream = new ByteArrayInputStream(bytes);
+        return new ServletInputStream() {
+            @Override
+            public boolean isFinished() {
+                return false;
+            }
+
+            @Override
+            public boolean isReady() {
+                return false;
+            }
+
+            @Override
+            public void setReadListener(ReadListener readListener) {
+                //
+            }
+
+            @Override
+            public int read() {
+                return stream.read();
+            }
+        };
+    }
+
+    @Override
+    public BufferedReader getReader() throws UnsupportedEncodingException {
+        return new BufferedReader(new InputStreamReader(getInputStream(), super.getCharacterEncoding()));
+    }
+}

+ 47 - 0
src/main/java/com/nokia/finance/tasks/config/web/MyHttpServletResponseWrapper.java

@@ -0,0 +1,47 @@
+package com.nokia.finance.tasks.config.web;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.WriteListener;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+/**
+ * 解决流只能读取一次问题
+ */
+public class MyHttpServletResponseWrapper extends HttpServletResponseWrapper {
+    private ByteArrayOutputStream byteArrayOutputStream;
+    private ServletOutputStream servletOutputStream;
+
+    public MyHttpServletResponseWrapper(HttpServletResponse response) {
+        super(response);
+        byteArrayOutputStream = new ByteArrayOutputStream();
+        servletOutputStream = new ServletOutputStream() {
+            @Override
+            public boolean isReady() {
+                return false;
+            }
+
+            @Override
+            public void setWriteListener(WriteListener writeListener) {
+                //
+            }
+
+            @Override
+            public void write(int b) throws IOException {
+                response.getOutputStream().write(b);
+                byteArrayOutputStream.write(b);
+            }
+        };
+    }
+
+    @Override
+    public ServletOutputStream getOutputStream() {
+        return servletOutputStream;
+    }
+
+    public byte[] toByteArray() {
+        return byteArrayOutputStream.toByteArray();
+    }
+}

+ 53 - 0
src/main/java/com/nokia/finance/tasks/config/web/MyWebMvcConfigurer.java

@@ -0,0 +1,53 @@
+package com.nokia.finance.tasks.config.web;
+
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.DispatcherServlet;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+public class MyWebMvcConfigurer implements WebMvcConfigurer {
+
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        // 添加请求日志拦截
+        registry.addInterceptor(new RequestLogHandlerInterceptor()).addPathPatterns("/**").excludePathPatterns("/webjars/**", "/doc**", "/v3/**");
+    }
+
+    /**
+     * 使用自定义DispatcherServlet
+     */
+    @Bean
+    @Qualifier(DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
+    public DispatcherServlet dispatcherServlet() {
+        return new MyDispatcherServlet();
+    }
+
+//    /**
+//     * 配置消息转换器
+//     *
+//     * @param converters 转换器
+//     */
+//    @Override
+//    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
+//        converters.add(mappingJackson2HttpMessageConverter());
+//    }
+//
+//    /**
+//     * 配置映射jackson2 http消息转换器
+//     *
+//     * @return {@link MappingJackson2HttpMessageConverter}
+//     */
+//    @Bean
+//    public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
+//        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
+//        ObjectMapper mapper = new ObjectMapper();
+//        mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
+//        mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
+//        converter.setObjectMapper(mapper);
+//        return converter;
+//    }
+}

+ 74 - 0
src/main/java/com/nokia/finance/tasks/config/web/RequestLogHandlerInterceptor.java

@@ -0,0 +1,74 @@
+package com.nokia.finance.tasks.config.web;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.slf4j.MDC;
+import org.springframework.lang.Nullable;
+import org.springframework.util.StopWatch;
+import org.springframework.util.StreamUtils;
+import org.springframework.util.StringUtils;
+import org.springframework.web.servlet.HandlerInterceptor;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.nio.charset.Charset;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * 请求日志拦截器
+ */
+@Slf4j
+public class RequestLogHandlerInterceptor implements HandlerInterceptor {
+    /**
+     * 计时器线程变量
+     */
+    private static final ThreadLocal<StopWatch> STOP_WATCH_THREAD_LOCAL = new ThreadLocal<>();
+
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
+        StopWatch stopWatch = new StopWatch();
+        stopWatch.start();
+        // 计时器放入线程变量
+        STOP_WATCH_THREAD_LOCAL.set(stopWatch);
+        // 日志添加跟踪id
+        MDC.put("traceId", UUID.randomUUID().toString().replace("-", ""));
+        log.info("请求地址: {} {}", request.getRequestURL().toString(), request.getMethod());
+        // 请求头参数
+        Map<String, String> headers = new HashMap<>();
+        Enumeration<String> headerNames = request.getHeaderNames();
+        while (headerNames.hasMoreElements()) {
+            String k	= headerNames.nextElement();
+            String v = request.getHeader(k);
+            headers.put(k, v);
+        }
+        log.info("请求头参数: {}", new ObjectMapper().writeValueAsString(headers));
+        // 查询参数
+        Map<String, String> parameters = new HashMap<>();
+        Enumeration<String> parameterNames = request.getParameterNames();
+        while (parameterNames.hasMoreElements()) {
+            String k	= parameterNames.nextElement();
+            String v = request.getParameter(k);
+            parameters.put(k, v);
+        }
+        log.info("查询参数: {}", new ObjectMapper().writeValueAsString(parameters));
+        // 请求体参数
+        String body = StreamUtils.copyToString(request.getInputStream(), Charset.forName(request.getCharacterEncoding()));
+        log.info("请求体参数: {}", StringUtils.trimAllWhitespace(body));
+        return true;
+    }
+
+    @Override
+    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
+                                @Nullable Exception ex) throws Exception {
+        MyHttpServletResponseWrapper wrapper = (MyHttpServletResponseWrapper) response;
+        String responseString = new String(wrapper.toByteArray());
+        log.info("响应 {}: {}", wrapper.getStatus(), responseString);
+        StopWatch stopWatch = STOP_WATCH_THREAD_LOCAL.get();
+        stopWatch.stop();
+        log.info("耗时 {} ms", stopWatch.getTotalTimeMillis());
+        STOP_WATCH_THREAD_LOCAL.remove();
+    }
+}

+ 26 - 0
src/main/java/com/nokia/finance/tasks/controller/JobController.java

@@ -0,0 +1,26 @@
+package com.nokia.finance.tasks.controller;
+
+import com.nokia.finance.tasks.common.R;
+import com.nokia.finance.tasks.pojo.RunJobDto;
+import com.nokia.finance.tasks.service.JobService;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.validation.Valid;
+
+@RestController
+@RequestMapping("/jobs")
+public class JobController {
+    private final JobService jobService;
+
+    public JobController(JobService jobService) {
+        this.jobService = jobService;
+    }
+
+    @PostMapping("/runJob")
+    public R<Object> runJob(@Valid @RequestBody RunJobDto dto) {
+        return jobService.runJob(dto);
+    }
+}

+ 20 - 0
src/main/java/com/nokia/finance/tasks/controller/TestController.java

@@ -0,0 +1,20 @@
+package com.nokia.finance.tasks.controller;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@Slf4j
+@RequestMapping("/test")
+@RestController
+public class TestController {
+    /**
+     * 短信告警测试
+     */
+    @GetMapping("/alert")
+    public Object alert() {
+        log.error("告警测试");
+        return "ok";
+    }
+}

+ 26 - 0
src/main/java/com/nokia/finance/tasks/dao/common/AreaDao.java

@@ -0,0 +1,26 @@
+package com.nokia.finance.tasks.dao.common;
+
+import com.nokia.finance.tasks.pojo.po.common.AreaPo;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.List;
+
+@Mapper
+public interface AreaDao {
+    /**
+     * 查询地市
+     */
+    @Select("""
+select * from common.area where area_grade = 1 order by area_id
+""")
+    List<AreaPo> getCities();
+
+    /**
+     * 查询区县
+     */
+    @Select("""
+select * from common.area where area_grade = 2 order by parent_id, area_id
+""")
+    List<AreaPo> getDistricts();
+}

+ 26 - 0
src/main/java/com/nokia/finance/tasks/dao/common/OrganizationDao.java

@@ -0,0 +1,26 @@
+package com.nokia.finance.tasks.dao.common;
+
+import com.nokia.finance.tasks.pojo.po.common.OrganizationPo;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.List;
+
+@Mapper
+public interface OrganizationDao {
+    /**
+     * 查询二级组织机构
+     */
+    @Select("""
+select * from common.organization where grade = 1 order by order_num
+""")
+    List<OrganizationPo> getSecondOrgs();
+
+    /**
+     * 查询三级组织机构
+     */
+    @Select("""
+select * from common.organization where grade = 2 order by parent_id, order_num
+""")
+    List<OrganizationPo> getThirdOrgs();
+}

+ 13 - 0
src/main/java/com/nokia/finance/tasks/enums/JobEnum.java

@@ -0,0 +1,13 @@
+package com.nokia.finance.tasks.enums;
+
+public enum JobEnum {
+    /**
+     * 睿行车辆基本信息日数据入库定时任务
+     */
+    CLJBXX_JOB,
+    /**
+     * 睿行车辆越界报警日数据入库定时任务
+     */
+    YJBJRTJ_JOB,
+    ;
+}

+ 333 - 0
src/main/java/com/nokia/finance/tasks/jobs/car/ruixing/CljbxxJob.java

@@ -0,0 +1,333 @@
+package com.nokia.finance.tasks.jobs.car.ruixing;
+
+import com.nokia.finance.tasks.common.exception.MyRuntimeException;
+import com.nokia.finance.tasks.common.utils.psql.PsqlUtil;
+import com.nokia.finance.tasks.config.JobConfig;
+import com.nokia.finance.tasks.pojo.po.common.AreaPo;
+import com.nokia.finance.tasks.pojo.po.common.OrganizationPo;
+import com.nokia.finance.tasks.service.car.CarService;
+import com.nokia.finance.tasks.service.common.AreaService;
+import com.nokia.finance.tasks.service.common.OrganizationService;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.csv.CSVFormat;
+import org.apache.commons.csv.CSVPrinter;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.DateUtil;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
+
+import java.io.InputStream;
+import java.io.OutputStreamWriter;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Stream;
+
+/**
+ * 睿行车辆基本信息日数据入库定时任务
+ */
+@Slf4j
+@Service
+public class CljbxxJob {
+    private final JobConfig jobConfig;
+    private final CarService carService;
+    private final OrganizationService organizationService;
+    private final AreaService areaService;
+
+    public CljbxxJob(JobConfig jobConfig, CarService carService, OrganizationService organizationService,
+                     AreaService areaService) {
+        this.jobConfig = jobConfig;
+        this.carService = carService;
+        this.organizationService = organizationService;
+        this.areaService = areaService;
+    }
+
+    /**
+     * 执行任务
+     */
+    @Scheduled(cron = "0 30 23 * * ?")
+    public void runJob() {
+        // 数据目录
+        Path dir = Paths.get(jobConfig.getCljbxxSourcePath());
+        try (Stream<Path> stream = Files.list(dir)) {
+            // 获取数据目录下的文件列表
+            List<Path> pathList = stream.filter(t -> t.toString().endsWith(".xlsx")).sorted().toList();
+            log.info("睿行车辆基本信息日数据文件列表: {}", pathList);
+            if (CollectionUtils.isEmpty(pathList)) {
+                throw new MyRuntimeException("睿行车辆基本信息日数据没有文件");
+            }
+            for (Path path : pathList) {
+                singleJob(path);
+            }
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 处理单个文件
+     *
+     * @param path 文件路径
+     */
+    public void singleJob(Path path) throws Exception {
+        List<Map<String, String>> list = readFile(path);
+        List<Map<String, String>> distinctList = dataProcessing(path, list);
+        Path csvPath = toCsv(path, distinctList);
+        copyCsv(csvPath);
+        move(path);
+    }
+
+    /**
+     * 读取文件
+     *
+     * @param path 文件路径
+     */
+    public List<Map<String, String>> readFile(Path path) throws Exception {
+        log.info("读取: {}", path);
+        List<String> rawHeaders = Stream.of("车牌号", "车辆品牌", "排量", "排量类型", "燃油类型", "燃油标号",
+                "登记日期", "车辆来源", "车辆类型", "车辆使用性质", "环保等级", "用车模式", "发动机号", "车架号", "初始里程",
+                "车辆所属单位", "行驶证号", "备注", "审核状态", "车辆状态", "资产编号", "绑定驾驶员姓名", "购置价格(万元)",
+                "车龄(年)", "标准百公里油耗", "设备工作模式").toList();
+        List<String> headers = Stream.of("che_pai_hao", "che_liang_pin_pai", "pai_liang", "pai_liang_lei_xing",
+                "ran_you_lei_xing", "ran_you_biao_hao", "deng_ji_ri_qi", "che_liang_lai_yuan", "che_liang_lei_xing",
+                "che_liang_shi_yong_xing_zhi", "huan_bao_deng_ji", "yong_che_mo_shi", "fa_dong_ji_hao", "che_jia_hao",
+                "chu_shi_li_cheng", "che_liang_suo_shu_dan_wei", "xing_shi_zheng_hao", "bei_zhu", "shen_he_zhuang_tai",
+                "che_liang_zhuang_tai", "zi_chan_bian_hao", "bang_ding_jia_shi_yuan_xing_ming", "gou_zhi_jia_ge",
+                "che_ling_nian", "biao_zhun_bai_gong_li_you_hao", "she_bei_gong_zuo_mo_shi").toList();
+        try (InputStream inputStream = Files.newInputStream(path);
+             Workbook workbook = new XSSFWorkbook(inputStream)
+        ) {
+            List<Map<String, String>> resultList = new ArrayList<>();
+            // 读取第一个工作表
+            Sheet sheet = workbook.getSheetAt(0);
+            // 表头行
+            Row headerRow = sheet.getRow(0);
+            // 列数
+            int columnCount = headerRow.getPhysicalNumberOfCells();
+            log.info("columnCount: {}", columnCount);
+            // 检查表头
+            if (headers.size() != columnCount) {
+                throw new MyRuntimeException(path.getFileName() + "列数错误");
+            }
+            for (int i = 0; i < columnCount; i++) {
+                Cell cell = headerRow.getCell(i);
+                if (cell == null || !rawHeaders.get(i).equals(cell.getStringCellValue())) {
+                    throw new MyRuntimeException(path.getFileName() + " 表头错误");
+                }
+            }
+            // 最后行数
+            int lastRowNum = sheet.getLastRowNum();
+            log.info("lastRowNum: {}", lastRowNum);
+            if (lastRowNum == 0) {
+                throw new MyRuntimeException(path.getFileName() + " 为空");
+            }
+            // 遍历行
+            for (int i = 1; i <= lastRowNum; i++) {
+                Row row = sheet.getRow(i);
+                if (row == null) {
+                    continue;
+                }
+                Map<String, String> rowMap = new LinkedHashMap<>();
+                // 遍历列
+                for (int j = 0; j < columnCount; j++) {
+                    String cellValue = "";
+                    rowMap.put(headers.get(j), cellValue);
+                    Cell cell = row.getCell(j);
+                    if (cell == null) {
+                        continue;
+                    }
+                    switch (cell.getCellType()) {
+                        case STRING:
+                            // 删除字符串空白字符
+                            cellValue = StringUtils.trimAllWhitespace(cell.getStringCellValue());
+                            break;
+                        case NUMERIC:
+                            if (DateUtil.isCellDateFormatted(cell)) {
+                                cellValue = DateUtil.getLocalDateTime(cell.getNumericCellValue())
+                                        .format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss"));
+                                break;
+                            }
+                            cellValue = String.valueOf(cell.getNumericCellValue());
+                            break;
+                        case BOOLEAN:
+                            cellValue = String.valueOf(cell.getBooleanCellValue());
+                            break;
+                        default:
+                            break;
+                    }
+                    rowMap.put(headers.get(j), cellValue);
+                }
+                resultList.add(rowMap);
+            }
+            return resultList;
+        }
+    }
+
+    /**
+     * 数据加工
+     *
+     * @param path 文件路径
+     * @param list 数据
+     */
+    public List<Map<String, String>> dataProcessing(Path path, List<Map<String, String>> list) {
+        // 从文件名提取日期
+        String regex = "\\d{8}";
+        Pattern pattern = Pattern.compile(regex);
+        Matcher matcher = pattern.matcher(path.getFileName().toString());
+        String dateString;
+        if (matcher.find()) {
+            dateString = matcher.group();
+        } else {
+            throw new MyRuntimeException(path.getFileName() + " 提取日期失败");
+        }
+        List<OrganizationPo> secondOrgs = organizationService.getSecondOrgs();
+        List<OrganizationPo> thirdOrgs = organizationService.getThirdOrgs();
+        Map<String, OrganizationPo> orgMap = organizationService.getOrgMap(secondOrgs, thirdOrgs);
+        Map<String, List<OrganizationPo>> thirdOrganizationListMap =
+                organizationService.getThirdOrganizationListMap(secondOrgs, thirdOrgs);
+        List<AreaPo> cities = areaService.getCities();
+        List<AreaPo> districts = areaService.getDistricts();
+        Map<String, AreaPo> areaMap = areaService.getAreaMap(cities, districts);
+        Map<String, List<AreaPo>> districtListMap = areaService.getDistrictListMap(cities, districts);
+        LocalDate localDate = LocalDate.parse(dateString, DateTimeFormatter.ofPattern("yyyyMMdd"));
+        LocalDate lastMonthDate = localDate.minusMonths(1);
+        String dataDate = localDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
+        String yearMonth = localDate.format(DateTimeFormatter.ofPattern("yyyyMM"));
+        String year = String.valueOf(localDate.getYear());
+        String month = String.valueOf(localDate.getMonthValue());
+        String lastMonth = lastMonthDate.format(DateTimeFormatter.ofPattern("yyyyMM"));
+        for (Map<String, String> map : list) {
+            map.put("data_date", dataDate);
+            map.put("year_month", yearMonth);
+            map.put("year_no", year);
+            map.put("month_no", month);
+            map.put("last_month", lastMonth);
+            String rawChePaiHao = map.get("che_pai_hao");
+            map.put("raw_che_pai_hao", rawChePaiHao);
+            String chePaiHao = carService.getChePai(rawChePaiHao);
+            map.put("che_pai_hao", chePaiHao);
+            String chePaiFail = carService.chePaiFail(rawChePaiHao);
+            map.put("che_pai_fail", chePaiFail);
+            String cheLiangSuoShuDanWei = map.get("che_liang_suo_shu_dan_wei");
+            String firstUnit = carService.getFirstUnit(cheLiangSuoShuDanWei);
+            map.put("first_unit", firstUnit);
+            String secondUnit = carService.getSecondUnit(cheLiangSuoShuDanWei, firstUnit);
+            map.put("second_unit", secondUnit);
+            String thirdUnit = carService.getThirdUnit(cheLiangSuoShuDanWei, secondUnit);
+            map.put("third_unit", thirdUnit);
+            String areaNo = carService.getAreaNo(secondOrgs, cheLiangSuoShuDanWei);
+            map.put("area_no", areaNo);
+            String areaName = carService.getOrgName(orgMap, areaNo);
+            map.put("area_name", areaName);
+            String cityNo = carService.getCityNo(thirdOrganizationListMap, areaNo, areaName, cheLiangSuoShuDanWei);
+            map.put("city_no", cityNo);
+            String cityName = carService.getOrgName(orgMap, cityNo);
+            map.put("city_name", cityName);
+            String areaNo2 = carService.getAreaNo2(areaName, cityName);
+            map.put("area_no2", areaNo2);
+            String areaName2 = carService.getOrgName(orgMap, areaNo2);
+            map.put("area_name2", areaName2);
+            String cityId = carService.getCityId(cities, cheLiangSuoShuDanWei);
+            map.put("city_id", cityId);
+            String city = carService.getAreaName(areaMap, cityId);
+            map.put("city", city);
+            String districtId = carService.getDistrictId(districtListMap, cityId, cityName, cheLiangSuoShuDanWei);
+            map.put("district_id", districtId);
+            String district = carService.getAreaName(areaMap, districtId);
+            map.put("district", district);
+            String cheLing = "0";
+            String dengJiRiQi = map.get("deng_ji_ri_qi");
+            if (StringUtils.hasText(dengJiRiQi)) {
+                LocalDate dengJiRiQiDate = LocalDate.parse(dengJiRiQi, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
+                long days = ChronoUnit.DAYS.between(dengJiRiQiDate, localDate);
+                if (days > 0) {
+                    BigDecimal n = BigDecimal.valueOf(days).divide(BigDecimal.valueOf(365), 1, RoundingMode.HALF_UP);
+                    cheLing = n.toString();
+                }
+            }
+            map.put("che_ling", cheLing);
+            map.put("source", path.getFileName().toString());
+        }
+        // 去重
+        return list.stream().filter(distinctByKey(map -> map.get("che_pai_hao"))).toList();
+    }
+
+    /**
+     * 去重
+     */
+    private static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
+        Set<Object> set = ConcurrentHashMap.newKeySet();
+        return t -> set.add(keyExtractor.apply(t));
+    }
+
+    /**
+     * 生成csv
+     *
+     * @param path 源文件路径
+     * @param list 数据
+     */
+    public Path toCsv(Path path, List<Map<String, String>> list) throws Exception {
+        log.info("去重后条数:{}", list.size());
+        Files.createDirectories(Paths.get(jobConfig.getCljbxxHistoryPath()));
+        Path csvPath = Paths.get(jobConfig.getCljbxxHistoryPath() + path.getFileName() + ".csv");
+        try (OutputStreamWriter osw = new OutputStreamWriter(Files.newOutputStream(csvPath),
+                StandardCharsets.UTF_8);
+             CSVPrinter printer = new CSVPrinter(osw, CSVFormat.DEFAULT)) {
+            // 添加bom头避免excel乱码
+            osw.write('\ufeff');
+            Map<String, String> header = list.get(0);
+            // 表头
+            printer.printRecord(header.keySet());
+            for (Map<String, String> map : list) {
+                printer.printRecord(map.values());
+            }
+        }
+        return csvPath;
+    }
+
+    /**
+     * 导入数据库
+     * @param path 文件路径
+     */
+    public void copyCsv(Path path) {
+        String dbTable = "car.car_base_data_day";
+        String csv = path.toString();
+        String columns = "(che_pai_hao,che_liang_pin_pai,pai_liang,pai_liang_lei_xing,ran_you_lei_xing,ran_you_biao_hao,deng_ji_ri_qi,che_liang_lai_yuan,che_liang_lei_xing,che_liang_shi_yong_xing_zhi,huan_bao_deng_ji,yong_che_mo_shi,fa_dong_ji_hao,che_jia_hao,chu_shi_li_cheng,che_liang_suo_shu_dan_wei,xing_shi_zheng_hao,bei_zhu,shen_he_zhuang_tai,che_liang_zhuang_tai,zi_chan_bian_hao,bang_ding_jia_shi_yuan_xing_ming,gou_zhi_jia_ge,che_ling_nian,biao_zhun_bai_gong_li_you_hao,she_bei_gong_zuo_mo_shi,data_date,year_month,year_no,month_no,last_month,raw_che_pai_hao,che_pai_fail,first_unit,second_unit,third_unit,area_no,area_name,city_no,city_name,area_no2,area_name2,city_id,city,district_id,district,che_ling,source)";
+        Long timeout = 60000L;
+        PsqlUtil.copyCsv(jobConfig.getCopyScriptPath(), jobConfig.getDbHost(), jobConfig.getDbPort(),
+                jobConfig.getDbUsername(), jobConfig.getDbPassword(), jobConfig.getDbName(), dbTable, csv, columns,
+                timeout, null);
+    }
+
+    /**
+     * 移动源文件到历史文件夹
+     * @param path 源文件路径
+     */
+    public void move(Path path) throws Exception {
+        Path targetPath = Paths.get(jobConfig.getCljbxxHistoryPath(), path.getFileName().toString());
+        Files.move(path, targetPath, StandardCopyOption.REPLACE_EXISTING);
+    }
+}

+ 305 - 0
src/main/java/com/nokia/finance/tasks/jobs/car/ruixing/YjbjrtjJob.java

@@ -0,0 +1,305 @@
+package com.nokia.finance.tasks.jobs.car.ruixing;
+
+import com.nokia.finance.tasks.common.exception.MyRuntimeException;
+import com.nokia.finance.tasks.common.utils.psql.PsqlUtil;
+import com.nokia.finance.tasks.config.JobConfig;
+import com.nokia.finance.tasks.pojo.po.common.AreaPo;
+import com.nokia.finance.tasks.pojo.po.common.OrganizationPo;
+import com.nokia.finance.tasks.service.car.CarService;
+import com.nokia.finance.tasks.service.common.AreaService;
+import com.nokia.finance.tasks.service.common.OrganizationService;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.csv.CSVFormat;
+import org.apache.commons.csv.CSVPrinter;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.DateUtil;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
+
+import java.io.InputStream;
+import java.io.OutputStreamWriter;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
+
+/**
+ * 睿行车辆越界报警日数据入库定时任务
+ */
+@Slf4j
+@Service
+public class YjbjrtjJob {
+    private final JobConfig jobConfig;
+    private final CarService carService;
+    private final OrganizationService organizationService;
+    private final AreaService areaService;
+
+    public YjbjrtjJob(JobConfig jobConfig, CarService carService, OrganizationService organizationService,
+                      AreaService areaService) {
+        this.jobConfig = jobConfig;
+        this.carService = carService;
+        this.organizationService = organizationService;
+        this.areaService = areaService;
+    }
+
+    /**
+     * 执行任务
+     */
+    @Scheduled(cron = "0 55 23 * * ?")
+    public void runJob() {
+        // 数据目录
+        Path dir = Paths.get(jobConfig.getYjbjrtjSourcePath());
+        try (Stream<Path> stream = Files.list(dir)) {
+            // 获取数据目录下的文件列表
+            List<Path> pathList = stream.filter(t -> t.toString().endsWith(".xlsx")).sorted().toList();
+            log.info("睿行车辆越界报警日数据文件列表: {}", pathList);
+            if (CollectionUtils.isEmpty(pathList)) {
+                throw new MyRuntimeException("睿行车辆越界报警日数据没有文件");
+            }
+            for (Path path : pathList) {
+                singleJob(path);
+            }
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 处理单个文件
+     *
+     * @param path 文件路径
+     */
+    public void singleJob(Path path) throws Exception {
+        List<Map<String, String>> list = readFile(path);
+        List<Map<String, String>> distinctList = dataProcessing(path, list);
+        Path csvPath = toCsv(path, distinctList);
+        copyCsv(csvPath);
+        move(path);
+    }
+
+    /**
+     * 读取文件
+     *
+     * @param path 文件路径
+     */
+    public List<Map<String, String>> readFile(Path path) throws Exception {
+        log.info("读取: {}", path);
+        List<String> rawHeaders = Stream.of("车辆所属单位", "车牌号码", "车辆类型", "车辆使用性质", "报警区域描述",
+                "报警规则描述", "越界时间", "是否执行中越界", "详情").toList();
+        List<String> headers = Stream.of("che_liang_suo_shu_dan_wei", "che_pai_hao", "che_liang_lei_xing",
+                "che_liang_shi_yong_xing_zhi", "bao_jing_qu_yu_miao_shu", "bao_jing_gui_ze_miao_shu",
+                "yue_jie_shi_jian", "shi_fou_zhi_xing_zhong_yue_jie", "xiang_qing").toList();
+        try (InputStream inputStream = Files.newInputStream(path);
+             Workbook workbook = new XSSFWorkbook(inputStream)
+        ) {
+            List<Map<String, String>> resultList = new ArrayList<>();
+            // 读取第一个工作表
+            Sheet sheet = workbook.getSheetAt(0);
+            // 表头行
+            Row headerRow = sheet.getRow(0);
+            // 列数
+            int columnCount = headerRow.getPhysicalNumberOfCells();
+            log.info("columnCount: {}", columnCount);
+            // 检查表头
+            if (headers.size() != columnCount) {
+                throw new MyRuntimeException(path.getFileName() + "列数错误");
+            }
+            for (int i = 0; i < columnCount; i++) {
+                Cell cell = headerRow.getCell(i);
+                if (cell == null || !rawHeaders.get(i).equals(cell.getStringCellValue())) {
+                    throw new MyRuntimeException(path.getFileName() + " 表头错误");
+                }
+            }
+            // 最后行数
+            int lastRowNum = sheet.getLastRowNum();
+            log.info("lastRowNum: {}", lastRowNum);
+            if (lastRowNum == 0) {
+                throw new MyRuntimeException(path.getFileName() + " 为空");
+            }
+            // 遍历行
+            for (int i = 1; i <= lastRowNum; i++) {
+                Row row = sheet.getRow(i);
+                if (row == null) {
+                    continue;
+                }
+                Map<String, String> rowMap = new LinkedHashMap<>();
+                // 遍历列
+                for (int j = 0; j < columnCount; j++) {
+                    String cellValue = "";
+                    rowMap.put(headers.get(j), cellValue);
+                    Cell cell = row.getCell(j);
+                    if (cell == null) {
+                        continue;
+                    }
+                    switch (cell.getCellType()) {
+                        case STRING:
+                            // 删除字符串空白字符
+                            cellValue = StringUtils.trimAllWhitespace(cell.getStringCellValue());
+                            break;
+                        case NUMERIC:
+                            if (DateUtil.isCellDateFormatted(cell)) {
+                                cellValue = DateUtil.getLocalDateTime(cell.getNumericCellValue())
+                                        .format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss"));
+                                break;
+                            }
+                            cellValue = String.valueOf(cell.getNumericCellValue());
+                            break;
+                        case BOOLEAN:
+                            cellValue = String.valueOf(cell.getBooleanCellValue());
+                            break;
+                        default:
+                            break;
+                    }
+                    rowMap.put(headers.get(j), cellValue);
+                }
+                resultList.add(rowMap);
+            }
+            return resultList;
+        }
+    }
+
+    /**
+     * 数据加工
+     *
+     * @param path 文件路径
+     * @param list 数据
+     */
+    public List<Map<String, String>> dataProcessing(Path path, List<Map<String, String>> list) {
+        List<OrganizationPo> secondOrgs = organizationService.getSecondOrgs();
+        List<OrganizationPo> thirdOrgs = organizationService.getThirdOrgs();
+        Map<String, OrganizationPo> orgMap = organizationService.getOrgMap(secondOrgs, thirdOrgs);
+        Map<String, List<OrganizationPo>> thirdOrganizationListMap =
+                organizationService.getThirdOrganizationListMap(secondOrgs, thirdOrgs);
+        List<AreaPo> cities = areaService.getCities();
+        List<AreaPo> districts = areaService.getDistricts();
+        Map<String, AreaPo> areaMap = areaService.getAreaMap(cities, districts);
+        Map<String, List<AreaPo>> districtListMap = areaService.getDistrictListMap(cities, districts);
+        for (Map<String, String> map : list) {
+            String yueJieShiJian = map.get("yue_jie_shi_jian");
+            LocalDateTime localDateTime = LocalDateTime.parse(yueJieShiJian,
+                    DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
+            String dataDate = localDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
+            String yearMonth = localDateTime.format(DateTimeFormatter.ofPattern("yyyyMM"));
+            String year = String.valueOf(localDateTime.getYear());
+            String month = String.valueOf(localDateTime.getMonthValue());
+            map.put("data_date", dataDate);
+            map.put("year_month", yearMonth);
+            map.put("year_no", year);
+            map.put("month_no", month);
+            String rawChePaiHao = map.get("che_pai_hao");
+            map.put("raw_che_pai_hao", rawChePaiHao);
+            String chePaiHao = carService.getChePai(rawChePaiHao);
+            map.put("che_pai_hao", chePaiHao);
+            String chePaiFail = carService.chePaiFail(rawChePaiHao);
+            map.put("che_pai_fail", chePaiFail);
+            String cheLiangSuoShuDanWei = map.get("che_liang_suo_shu_dan_wei");
+            String firstUnit = carService.getFirstUnit(cheLiangSuoShuDanWei);
+            map.put("first_unit", firstUnit);
+            String secondUnit = carService.getSecondUnit(cheLiangSuoShuDanWei, firstUnit);
+            map.put("second_unit", secondUnit);
+            String thirdUnit = carService.getThirdUnit(cheLiangSuoShuDanWei, secondUnit);
+            map.put("third_unit", thirdUnit);
+            String areaNo = carService.getAreaNo(secondOrgs, cheLiangSuoShuDanWei);
+            map.put("area_no", areaNo);
+            String areaName = carService.getOrgName(orgMap, areaNo);
+            map.put("area_name", areaName);
+            String cityNo = carService.getCityNo(thirdOrganizationListMap, areaNo, areaName, cheLiangSuoShuDanWei);
+            map.put("city_no", cityNo);
+            String cityName = carService.getOrgName(orgMap, cityNo);
+            map.put("city_name", cityName);
+            String areaNo2 = carService.getAreaNo2(areaName, cityName);
+            map.put("area_no2", areaNo2);
+            String areaName2 = carService.getOrgName(orgMap, areaNo2);
+            map.put("area_name2", areaName2);
+            String cityId = carService.getCityId(cities, cheLiangSuoShuDanWei);
+            map.put("city_id", cityId);
+            String city = carService.getAreaName(areaMap, cityId);
+            map.put("city", city);
+            String districtId = carService.getDistrictId(districtListMap, cityId, cityName, cheLiangSuoShuDanWei);
+            map.put("district_id", districtId);
+            String district = carService.getAreaName(areaMap, districtId);
+            map.put("district", district);
+            String baoFei = carService.baoFei(rawChePaiHao);
+            map.put("bao_fei", baoFei);
+            map.put("source", path.getFileName().toString());
+        }
+        // 去重
+        return list.stream().filter(distinctByKey(map -> map.get("che_pai_hao") + map.get("yue_jie_shi_jian"))).toList();
+    }
+
+    /**
+     * 去重
+     */
+    private static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
+        Set<Object> set = ConcurrentHashMap.newKeySet();
+        return t -> set.add(keyExtractor.apply(t));
+    }
+
+    /**
+     * 生成csv
+     *
+     * @param path 源文件路径
+     * @param list 数据
+     */
+    public Path toCsv(Path path, List<Map<String, String>> list) throws Exception {
+        log.info("去重后条数:{}", list.size());
+        Files.createDirectories(Paths.get(jobConfig.getYjbjrtjHistoryPath()));
+        Path csvPath = Paths.get(jobConfig.getYjbjrtjHistoryPath() + path.getFileName() + ".csv");
+        try (OutputStreamWriter osw = new OutputStreamWriter(Files.newOutputStream(csvPath),
+                StandardCharsets.UTF_8);
+             CSVPrinter printer = new CSVPrinter(osw, CSVFormat.DEFAULT)) {
+            // 添加bom头避免excel乱码
+            osw.write('\ufeff');
+            Map<String, String> header = list.get(0);
+            // 表头
+            printer.printRecord(header.keySet());
+            for (Map<String, String> map : list) {
+                printer.printRecord(map.values());
+            }
+        }
+        return csvPath;
+    }
+
+    /**
+     * 导入数据库
+     *
+     * @param path 文件路径
+     */
+    public void copyCsv(Path path) {
+        String dbTable = "car.car_yue_jie";
+        String csv = path.toString();
+        String columns = "(che_liang_suo_shu_dan_wei,che_pai_hao,che_liang_lei_xing,che_liang_shi_yong_xing_zhi,bao_jing_qu_yu_miao_shu,bao_jing_gui_ze_miao_shu,yue_jie_shi_jian,shi_fou_zhi_xing_zhong_yue_jie,xiang_qing,data_date,year_month,year_no,month_no,raw_che_pai_hao,che_pai_fail,first_unit,second_unit,third_unit,area_no,area_name,city_no,city_name,area_no2,area_name2,city_id,city,district_id,district,bao_fei,source)";
+        Long timeout = 60000L;
+        PsqlUtil.copyCsv(jobConfig.getCopyScriptPath(), jobConfig.getDbHost(), jobConfig.getDbPort(),
+                jobConfig.getDbUsername(), jobConfig.getDbPassword(), jobConfig.getDbName(), dbTable, csv, columns,
+                timeout, null);
+    }
+
+    /**
+     * 移动源文件到历史文件夹
+     *
+     * @param path 源文件路径
+     */
+    public void move(Path path) throws Exception {
+        Path targetPath = Paths.get(jobConfig.getYjbjrtjHistoryPath(), path.getFileName().toString());
+        Files.move(path, targetPath, StandardCopyOption.REPLACE_EXISTING);
+    }
+}

+ 12 - 0
src/main/java/com/nokia/finance/tasks/pojo/RunJobDto.java

@@ -0,0 +1,12 @@
+package com.nokia.finance.tasks.pojo;
+
+import com.nokia.finance.tasks.enums.JobEnum;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+@Data
+public class RunJobDto {
+    @NotNull
+    private JobEnum jobName;
+}

+ 14 - 0
src/main/java/com/nokia/finance/tasks/pojo/po/common/AreaPo.java

@@ -0,0 +1,14 @@
+package com.nokia.finance.tasks.pojo.po.common;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor
+@Data
+public class AreaPo {
+    private Integer areaId;
+    private String areaName;
+    private Integer parentId;
+    private Integer areaGrade;
+    private String shortName;
+}

+ 15 - 0
src/main/java/com/nokia/finance/tasks/pojo/po/common/OrganizationPo.java

@@ -0,0 +1,15 @@
+package com.nokia.finance.tasks.pojo.po.common;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor
+@Data
+public class OrganizationPo {
+    private String id;
+    private String name;
+    private String parentId;
+    private Integer orderNum;
+    private Integer status;
+    private Integer grade;
+}

+ 32 - 0
src/main/java/com/nokia/finance/tasks/service/JobService.java

@@ -0,0 +1,32 @@
+package com.nokia.finance.tasks.service;
+
+import com.nokia.finance.tasks.common.R;
+import com.nokia.finance.tasks.jobs.car.ruixing.CljbxxJob;
+import com.nokia.finance.tasks.jobs.car.ruixing.YjbjrtjJob;
+import com.nokia.finance.tasks.pojo.RunJobDto;
+import org.springframework.stereotype.Service;
+
+@Service
+public class JobService {
+    private final CljbxxJob cljbxxJob;
+    private final YjbjrtjJob yjbjrtjJob;
+
+    public JobService(CljbxxJob cljbxxJob, YjbjrtjJob yjbjrtjJob) {
+        this.cljbxxJob = cljbxxJob;
+        this.yjbjrtjJob = yjbjrtjJob;
+    }
+
+    public R<Object> runJob(RunJobDto dto) {
+        switch (dto.getJobName()) {
+            case CLJBXX_JOB:
+                cljbxxJob.runJob();
+                break;
+            case YJBJRTJ_JOB:
+                yjbjrtjJob.runJob();
+                break;
+            default:
+                break;
+        }
+        return R.ok();
+    }
+}

+ 501 - 0
src/main/java/com/nokia/finance/tasks/service/car/CarService.java

@@ -0,0 +1,501 @@
+package com.nokia.finance.tasks.service.car;
+
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+import com.nokia.finance.tasks.pojo.po.common.AreaPo;
+import com.nokia.finance.tasks.pojo.po.common.OrganizationPo;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+@Slf4j
+@Service
+public class CarService {
+    /**
+     * 匹配非车牌字符
+     */
+    static final Pattern NOT_CHE_PAI_PATTERN = Pattern.compile("[^京津晋冀蒙辽吉黑沪苏浙皖闽赣鲁豫鄂湘粤桂琼渝川贵云藏陕甘青宁新港澳学挂领试超练警A-Z\\d]");
+    /**
+     * 车牌正则
+     */
+    static final Pattern CHE_PAI_PATTERN = Pattern.compile(
+            "([京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z][A-Z](([DF]((?![IO])[A-Z0-9](?![IO]))\\d{4})|(\\d{5}[DF]))|[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z][A-Z][A-Z0-9]{4}[A-Z0-9挂学警港澳])"
+    );
+
+    /**
+     * 一级单位的二级单位字典
+     */
+    static final Map<String, List<String>> ER_JI_MAP = new Gson().fromJson("""
+{
+    "石家庄": ["鹿泉", "藁城", "栾城", "井陉矿区", "井陉", "无极", "正定", "元氏", "新乐", "晋州", "平山", "灵寿", "赞皇", "赵县", "行唐", "高邑", "辛集", "深泽"],
+    "唐山": ["唐山高开区", "迁西", "海港", "开平", "丰南", "滦县", "乐亭", "丰润", "玉田", "古冶", "曹妃甸", "遵化", "滦南", "迁安"],
+    "秦皇岛": ["北戴河新区", "北戴河", "山海关", "昌黎", "卢龙", "青龙", "抚宁"],
+    "邯郸": ["曲周", "魏县", "馆陶", "磁县", "大名", "鸡泽", "成安", "涉县", "永年", "武安", "峰峰", "广平", "临漳", "邱县", "肥乡"],
+    "邢台": ["新河", "南宫", "隆尧", "内邱", "平乡", "宁晋", "广宗", "清河", "临西", "任县", "巨鹿", "沙河", "威县", "临城", "柏乡", "南和"],
+    "保定": ["涞水", "蠡县", "顺平", "博野", "安国", "涞源", "唐县", "定州", "高阳", "曲阳", "阜平", "清苑", "高碑店", "满城", "涿州", "易县", "望都", "徐水", "定兴", "白沟"],
+    "张家口": ["张北", "崇礼", "康保", "赤城", "阳原", "万全", "下花园", "尚义", "怀安", "怀来", "蔚县", "涿鹿", "沽源", "宣化"],
+    "承德": ["承德县", "兴隆", "宽城", "平泉", "营子", "隆化", "滦平", "围场", "丰宁", "双滦"],
+    "廊坊": ["文安", "霸州", "大城", "廊坊开发区", "三河", "香河", "永清", "胜芳", "燕郊", "固安", "大厂"],
+    "沧州": ["东光", "吴桥", "黄骅", "盐山", "孟村", "泊头", "献县", "南皮", "渤海新区", "海兴", "沧县", "河间", "青县", "任丘", "肃宁"],
+    "衡水": ["景县", "阜城", "枣强", "深州", "饶阳", "故城", "武强", "武邑", "冀州", "安平"],
+    "雄安": ["容城", "雄县", "安新"]
+}
+                    """,
+            new TypeToken<Map<String, List<String>>>() {}.getType());
+
+    /**
+     * 匹配车牌
+     * @param chePai 车牌
+     */
+    public String getChePai(String chePai) {
+        if (!StringUtils.hasText(chePai)) {
+            return "";
+        }
+        // 字母转大写删除非车牌字符
+        String s = NOT_CHE_PAI_PATTERN.matcher(chePai.toUpperCase()).replaceAll("");
+        Matcher m = CHE_PAI_PATTERN.matcher(s);
+        if (m.find()) {
+            return m.group(0);
+        }
+        log.warn("车牌匹配失败: {} -> {}", chePai, s);
+        return s;
+    }
+
+    /**
+     * 车牌是否匹配失败
+     * @param chePai 车牌
+     */
+    public String chePaiFail(String chePai) {
+        if (!StringUtils.hasText(chePai)) {
+            return "1";
+        }
+        // 字母转大写删除非车牌字符
+        String s = NOT_CHE_PAI_PATTERN.matcher(chePai.toUpperCase()).replaceAll("");
+        Matcher m = CHE_PAI_PATTERN.matcher(s);
+        if (m.find()) {
+            return "0";
+        }
+        return "1";
+    }
+
+    /**
+     * 车牌是否包含报废
+     * @param chePai 车牌
+     */
+    public String baoFei(String chePai) {
+        if (chePai.contains("报废")) {
+            return "1";
+        }
+        return "0";
+    }
+
+    /**
+     * 获取一级单位
+     * @param unit 单位
+     */
+    public String getFirstUnit(String unit) {
+        if (!StringUtils.hasText(unit)) {
+            return "";
+        }
+        if (unit.contains("机动通信局") || unit.contains("机动局")) {
+            return "机动局";
+        }
+        if (unit.contains("雄安基地建设部")) {
+            return "雄安基地建设部";
+        }
+        if (unit.contains("华北基地建设部")) {
+            return "华北基地建设部";
+        }
+        for (String yj : ER_JI_MAP.keySet()) {
+            if (unit.contains(yj)) {
+                return yj;
+            }
+        }
+        return "省本部";
+    }
+
+    /**
+     * 获取而二级单位
+     * @param unit 单位
+     * @param firstUnit 一级单位
+     */
+    public String getSecondUnit(String unit, String firstUnit) {
+        if (!StringUtils.hasText(unit)) {
+            return "";
+        }
+        if ("省公司本部".equals(firstUnit)) {
+            return firstUnit;
+        }
+        if ("机动局".equals(firstUnit)) {
+            for (String yj : ER_JI_MAP.keySet()) {
+                if (unit.contains(yj)) {
+                    return "机动局" + yj;
+                }
+            }
+            return "机动局本部";
+        }
+        if ("石家庄".equals(firstUnit)) {
+            if (unit.contains("开发区")) {
+                return "石家庄开发区";
+            }
+        }
+        if ("廊坊".equals(firstUnit)) {
+            if (unit.contains("开发区")) {
+                return "廊坊开发区";
+            }
+        }
+        if ("唐山".equals(firstUnit)) {
+            if (unit.contains("高开区")) {
+                return "唐山高开区";
+            }
+        }
+        List<String> ejs = ER_JI_MAP.get(firstUnit);
+        if (CollectionUtils.isEmpty(ejs)) {
+            return firstUnit;
+        }
+        if ("雄安".equals(firstUnit)) {
+            unit = unit.replace("雄安新区", "");
+        }
+        for (String ej : ejs) {
+            if (unit.contains(ej)) {
+                return ej;
+            }
+        }
+        return firstUnit + "本部";
+    }
+
+    /**
+     * 获取三级单位
+     * @param unit 单位
+     * @param secondUnit 二级单位
+     */
+    public String getThirdUnit(String unit, String secondUnit) {
+        if (!StringUtils.hasText(unit)) {
+            return "";
+        }
+        if (secondUnit == null) {
+            return unit;
+        }
+        String[] a = unit.split("_");
+        if (a.length < 4) {
+            return secondUnit;
+        }
+        return a[3];
+    }
+
+    /**
+     * 获取二级组织机构编码
+     * @param secondOrgs 二级组织机构列表
+     * @param unit 单位
+     */
+    public String getAreaNo(List<OrganizationPo> secondOrgs, String unit) {
+        if (!StringUtils.hasText(unit)) {
+            return "";
+        }
+        if (unit.contains("机动通信局") || unit.contains("机动局")) {
+            return "-11";
+        }
+        if (unit.contains("省公司本部") || unit.contains("雄安基地建设部") || unit.contains("华北基地建设部")) {
+            return "-12";
+        }
+        for (OrganizationPo secondOrg : secondOrgs) {
+            if (unit.contains(secondOrg.getName())) {
+                return secondOrg.getId();
+            }
+        }
+        return "-12";
+    }
+
+    /**
+     * 获取组织机构名称
+     * @param orgMap 组织机构字典
+     * @param orgNo 组织机构编码
+     */
+    public String getOrgName(Map<String, OrganizationPo> orgMap, String orgNo) {
+        if (!StringUtils.hasText(orgNo)) {
+            return "";
+        }
+        OrganizationPo po = orgMap.get(orgNo);
+        if (po != null) {
+            return po.getName();
+        }
+        return "";
+    }
+
+    /**
+     * 获取三级组织机构编码
+     * @param thirdOrgListMap 三级组织机构列表字典
+     * @param areaNo 二级组织机构编码
+     * @param areaName 二级组织机构名称
+     * @param unit 单位
+     */
+    public String getCityNo(Map<String, List<OrganizationPo>> thirdOrgListMap, String areaNo, String areaName,
+                            String unit) {
+        if (!StringUtils.hasText(areaNo) || !StringUtils.hasText(areaName)) {
+            return "";
+        }
+        if ("石家庄".equals(areaName)) {
+            if (unit.contains("井陉矿区")) {
+                return "D0130185";
+            }
+            if (unit.contains("井陉")) {
+                return "D0130121";
+            }
+        }
+        if ("秦皇岛".equals(areaName)) {
+            if (unit.contains("北戴河新区")) {
+                return "D0130185";
+            }
+            if (unit.contains("北戴河")) {
+                return "D0130304";
+            }
+        }
+        if ("省机动局".equals(areaName)) {
+            if (unit.contains("沧州")) {
+                return "HECS180";
+            }
+            if (unit.contains("唐山")) {
+                return "HECS181";
+            }
+            if (unit.contains("秦皇岛")) {
+                return "HECS182";
+            }
+            if (unit.contains("廊坊")) {
+                return "HECS183";
+            }
+            if (unit.contains("张家口")) {
+                return "HECS184";
+            }
+            if (unit.contains("邢台")) {
+                return "HECS185";
+            }
+            if (unit.contains("邯郸")) {
+                return "HECS186";
+            }
+            if (unit.contains("保定")) {
+                return "HECS187";
+            }
+            if (unit.contains("石家庄")) {
+                return "HECS188";
+            }
+            if (unit.contains("承德")) {
+                return "HECS189";
+            }
+            if (unit.contains("衡水")) {
+                return "HECS720";
+            }
+            if (unit.contains("雄安")) {
+                return "HECS728";
+            }
+            return "HECS018";
+        }
+        if ("雄安".equals(areaName)) {
+            unit = unit.replace("雄安新区", "");
+        }
+        List<OrganizationPo> l3 = thirdOrgListMap.getOrDefault(areaNo, new ArrayList<>());
+        for (OrganizationPo organizationPo : l3) {
+            if (unit.contains(organizationPo.getName())) {
+                return organizationPo.getId();
+            }
+        }
+        // 市区
+        if ("沧州".equals(areaName)) {
+            return "D0130911";
+        }
+        if ("唐山".equals(areaName)) {
+            return "D0130202";
+        }
+        if ("秦皇岛".equals(areaName)) {
+            return "D0130302";
+        }
+        if ("廊坊".equals(areaName)) {
+            return "D0131000";
+        }
+        if ("张家口".equals(areaName)) {
+            return "D0130701";
+        }
+        if ("邢台".equals(areaName)) {
+            return "D0130502";
+        }
+        if ("邯郸".equals(areaName)) {
+            return "D0130402";
+        }
+        if ("保定".equals(areaName)) {
+            return "D0130601";
+        }
+        if ("石家庄".equals(areaName)) {
+            return "D0130186";
+        }
+        if ("承德".equals(areaName)) {
+            return "D0130801";
+        }
+        if ("衡水".equals(areaName)) {
+            return "D0133001";
+        }
+        if ("雄安".equals(areaName)) {
+            return "D0130830";
+        }
+        // 河北省分公司
+        return "HE001";
+    }
+
+    /**
+     * 获取地图匹配的二级组织机构编码,车效大屏
+     * @param areaName 二级组织机构名称
+     * @param cityName 三级组织机构名称
+     */
+    public String getAreaNo2(String areaName, String cityName) {
+        if (!StringUtils.hasText(areaName)) {
+            return "";
+        }
+        if ("省机动局".equals(areaName) && StringUtils.hasText(cityName)) {
+            if (cityName.contains("沧州")) {
+                return "180";
+            }
+            if (cityName.contains("唐山")) {
+                return "181";
+            }
+            if (cityName.contains("秦皇岛")) {
+                return "182";
+            }
+            if (cityName.contains("廊坊")) {
+                return "183";
+            }
+            if (cityName.contains("张家口")) {
+                return "184";
+            }
+            if (cityName.contains("邢台")) {
+                return "185";
+            }
+            if (cityName.contains("邯郸")) {
+                return "186";
+            }
+            if (cityName.contains("保定")) {
+                return "187";
+            }
+            if (cityName.contains("石家庄")) {
+                return "188";
+            }
+            if (cityName.contains("承德")) {
+                return "189";
+            }
+            if (cityName.contains("衡水")) {
+                return "720";
+            }
+            if (cityName.contains("雄安")) {
+                return "782";
+            }
+        }
+        if (areaName.contains("沧州")) {
+            return "180";
+        }
+        if (areaName.contains("唐山")) {
+            return "181";
+        }
+        if (areaName.contains("秦皇岛")) {
+            return "182";
+        }
+        if (areaName.contains("廊坊")) {
+            return "183";
+        }
+        if (areaName.contains("张家口")) {
+            return "184";
+        }
+        if (areaName.contains("邢台")) {
+            return "185";
+        }
+        if (areaName.contains("邯郸")) {
+            return "186";
+        }
+        if (areaName.contains("保定")) {
+            return "187";
+        }
+        if (areaName.contains("石家庄")) {
+            return "188";
+        }
+        if (areaName.contains("承德")) {
+            return "189";
+        }
+        if (areaName.contains("衡水")) {
+            return "720";
+        }
+        if (areaName.contains("雄安")) {
+            return "782";
+        }
+        return "";
+    }
+
+    /**
+     * 获取地市id
+     * @param cities 地市列表
+     * @param unit 单位
+     */
+    public String getCityId(List<AreaPo> cities, String unit) {
+        if (!StringUtils.hasText(unit)) {
+            return "";
+        }
+        for (AreaPo city : cities) {
+            if (unit.contains(city.getShortName())) {
+                return String.valueOf(city.getAreaId());
+            }
+        }
+        return "";
+    }
+
+    /**
+     * 获取地区名称
+     * @param areaMap 地区map
+     * @param id 地区id
+     */
+    public String getAreaName(Map<String, AreaPo> areaMap, String id) {
+        if (!StringUtils.hasText(id)) {
+            return "";
+        }
+        AreaPo areaPo = areaMap.get(id);
+        if (null != areaPo) {
+            return areaPo.getAreaName();
+        }
+        return "";
+    }
+
+    /**
+     * 获取区县id
+     * @param districtListMap 区县列表map
+     * @param cityId 地市id
+     * @param cityName 地市名称
+     * @param unit 单位
+     */
+    public String getDistrictId(Map<String, List<AreaPo>> districtListMap, String cityId, String cityName, String unit) {
+        if (!StringUtils.hasText(cityId) || !StringUtils.hasText(cityName) || !StringUtils.hasText(unit)) {
+            return "";
+        }
+        if ("石家庄".equals(cityName)) {
+            if (unit.contains("井陉矿区")) {
+                return "130107";
+            }
+            if (unit.contains("井陉")) {
+                return "130121";
+            }
+        }
+        if ("雄安".equals(cityName)) {
+            unit = unit.replace("雄安新区", "");
+        }
+        List<AreaPo> districts = districtListMap.get(cityId);
+        if (CollectionUtils.isEmpty(districts)) {
+            return "";
+        }
+        for (AreaPo district : districts) {
+            if (unit.contains(district.getShortName())) {
+                return String.valueOf(district.getAreaId());
+            }
+        }
+        return "";
+    }
+}

+ 67 - 0
src/main/java/com/nokia/finance/tasks/service/common/AreaService.java

@@ -0,0 +1,67 @@
+package com.nokia.finance.tasks.service.common;
+
+import com.nokia.finance.tasks.dao.common.AreaDao;
+import com.nokia.finance.tasks.pojo.po.common.AreaPo;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Slf4j
+@Service
+public class AreaService {
+    private final AreaDao areaDao;
+
+    public AreaService(AreaDao areaDao) {
+        this.areaDao = areaDao;
+    }
+
+    /**
+     * 查询地市
+     * @return {@link List}<{@link AreaPo}>
+     */
+    public List<AreaPo> getCities() {
+        return areaDao.getCities();
+    }
+
+    /**
+     * 查询区县
+     * @return {@link List}<{@link AreaPo}>
+     */
+    public List<AreaPo> getDistricts() {
+        return areaDao.getDistricts();
+    }
+
+    /**
+     * 获取地区字典,key:areaId,value:地区
+     * @param cities 地市列表
+     * @param districts 区县列表
+     */
+    public Map<String, AreaPo> getAreaMap(List<AreaPo> cities, List<AreaPo> districts) {
+        Map<String, AreaPo> map = new HashMap<>();
+        cities.forEach(t -> map.put(String.valueOf(t.getAreaId()), t));
+        districts.forEach(t -> map.put(String.valueOf(t.getAreaId()), t));
+        return map;
+    }
+
+    /**
+     * 获取区县列表字典,key:地市id,value:区县列表
+     * @param cities 地市列表
+     * @param districts 区县列表
+     */
+    public Map<String, List<AreaPo>> getDistrictListMap(List<AreaPo> cities, List<AreaPo> districts) {
+        Map<String, List<AreaPo>> map = new HashMap<>();
+        for (AreaPo city : cities) {
+            map.put(String.valueOf(city.getAreaId()), new ArrayList<>());
+            for (AreaPo district : districts) {
+                if (city.getAreaId().equals(district.getParentId())) {
+                    map.get(String.valueOf(city.getAreaId())).add(district);
+                }
+            }
+        }
+        return map;
+    }
+}

+ 68 - 0
src/main/java/com/nokia/finance/tasks/service/common/OrganizationService.java

@@ -0,0 +1,68 @@
+package com.nokia.finance.tasks.service.common;
+
+import com.nokia.finance.tasks.dao.common.OrganizationDao;
+import com.nokia.finance.tasks.pojo.po.common.OrganizationPo;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Slf4j
+@Service
+public class OrganizationService {
+    private final OrganizationDao organizationDao;
+
+    public OrganizationService(OrganizationDao organizationDao) {
+        this.organizationDao = organizationDao;
+    }
+
+    /**
+     * 查询二级组织机构
+     */
+    public List<OrganizationPo> getSecondOrgs() {
+        return organizationDao.getSecondOrgs();
+    }
+
+    /**
+     * 查询三级组织机构
+     */
+    public List<OrganizationPo> getThirdOrgs() {
+        return organizationDao.getThirdOrgs();
+    }
+
+    /**
+     * 获取组织机构字典,key:id,value:组织机构
+     * @param secondOrgs 二级组织机构列表
+     * @param thirdOrgs 三级组织机构列表
+     * @return {@link Map}<{@link String}, {@link OrganizationPo}>
+     */
+    public Map<String, OrganizationPo> getOrgMap(List<OrganizationPo> secondOrgs,
+                                                 List<OrganizationPo> thirdOrgs) {
+        Map<String, OrganizationPo> map = new HashMap<>();
+        secondOrgs.forEach(t -> map.put(t.getId(), t));
+        thirdOrgs.forEach(t -> map.put(t.getId(), t));
+        return map;
+    }
+
+    /**
+     * 获取三级组织机构列表字典,key:二级组织机构id,value:三级组织机构列表
+     * @param secondOrgs 二级组织机构列表
+     * @param thirdOrgs 三级组织机构列表
+     */
+    public Map<String, List<OrganizationPo>> getThirdOrganizationListMap(List<OrganizationPo> secondOrgs,
+                                                                         List<OrganizationPo> thirdOrgs) {
+        Map<String, List<OrganizationPo>> map = new HashMap<>();
+        for (OrganizationPo secondOrg : secondOrgs) {
+            map.put(secondOrg.getId(), new ArrayList<>());
+            for (OrganizationPo thirdOrg : thirdOrgs) {
+                if (secondOrg.getId().equals(thirdOrg.getParentId())) {
+                    map.get(secondOrg.getId()).add(thirdOrg);
+                }
+            }
+        }
+        return map;
+    }
+}

+ 29 - 0
src/main/resources/application-dev.yml

@@ -0,0 +1,29 @@
+server:
+  port: 39110
+logging:
+  level:
+    com:
+      nokia: debug
+spring:
+  datasource:
+    driver-class-name: org.postgresql.Driver
+    username: postgres
+    password: Test!234
+    url: jdbc:postgresql://127.0.0.1:5432/financialdb
+job:
+  config:
+    # copy脚本路径
+    copy-script-path: scripts/copy.sh
+    db-host: 127.0.0.1
+    db-port: 5432
+    db-username: postgres
+    db-password: Test!234
+    db-name: financialdb
+    # 睿行车辆基本信息日数据路径
+    cljbxx-source-path: data/rxftp/cljbxx/
+    # 睿行车辆基本信息日数据归档路径
+    cljbxx-history-path: data/history/rxftp/cljbxx/
+    # 睿行车辆越界报警日数据路径
+    yjbjrtj-source-path: data/rxftp/yjbjrtj/
+    # 睿行车辆越界报警日数据归档路径
+    yjbjrtj-history-path: data/history/rxftp/yjbjrtj/

+ 29 - 0
src/main/resources/application-prod.yml

@@ -0,0 +1,29 @@
+server:
+  port: 39110
+logging:
+  level:
+    com:
+      nokia: debug
+spring:
+  datasource:
+    driver-class-name: org.postgresql.Driver
+    username: finance
+    password: Finance@unicom23
+    url: jdbc:postgresql://172.16.107.5:5432/financialdb
+job:
+  config:
+    # copy脚本路径
+    copy-script-path: copy.sh
+    db-host: 172.16.107.5
+    db-port: 5432
+    db-username: finance
+    db-password: Finance@unicom23
+    db-name: financialdb
+    # 睿行车辆基本信息日数据路径
+    cljbxx-source-path: /data/rxftp/cljbxx/
+    # 睿行车辆基本信息日数据归档路径
+    cljbxx-history-path: /data/history/rxftp/cljbxx/
+    # 睿行车辆越界报警日数据路径
+    yjbjrtj-source-path: /data/rxftp/yjbjrtj/
+    # 睿行车辆越界报警日数据归档路径
+    yjbjrtj-history-path: /data/history/rxftp/yjbjrtj/

+ 0 - 0
src/main/resources/application.yml


+ 51 - 0
src/main/resources/logback-spring.xml

@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+    <property name="PATH" value="./log"/>
+    <springProperty scope="context" name="appName" source="spring.application.name"/>
+    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <Pattern>%d{HH:mm:ss.SSS} %highlight(%-5level) %yellow(%X{traceId}) %magenta([%thread]) %cyan(%logger:%line) %msg%n</Pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+    </appender>
+    <appender name="TRACE_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${PATH}/trace.log</file>
+        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+            <!-- rollover daily -->
+            <fileNamePattern>${PATH}/trace.%d{yyyy-MM-dd_HH}.%i.log</fileNamePattern>
+            <!-- each file should be at most 100MB, keep 60 days worth of history, but at most 20GB -->
+            <maxFileSize>50MB</maxFileSize>
+            <maxHistory>60</maxHistory>
+            <totalSizeCap>20GB</totalSizeCap>
+        </rollingPolicy>
+        <encoder>
+            <Pattern>%d %highlight(%-5level) %yellow(%X{traceId}) %magenta([%thread]) %cyan(%logger:%line) %msg%n</Pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+    </appender>
+    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${PATH}/error.log</file>
+        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+            <!-- rollover daily -->
+            <fileNamePattern>${PATH}/error.%d{yyyy-MM-dd_HH}.%i.log</fileNamePattern>
+            <!-- each file should be at most 100MB, keep 60 days worth of history, but at most 20GB -->
+            <maxFileSize>50MB</maxFileSize>
+            <maxHistory>60</maxHistory>
+            <totalSizeCap>20GB</totalSizeCap>
+        </rollingPolicy>
+        <encoder>
+            <Pattern>%d %-5level %X{traceId} [%thread] %logger:%line %msg%n</Pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <level>ERROR</level>
+            <onMatch>ACCEPT</onMatch>
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+    <root level="INFO">
+        <appender-ref ref="STDOUT"/>
+        <appender-ref ref="TRACE_FILE"/>
+        <appender-ref ref="ERROR_FILE"/>
+    </root>
+</configuration>

+ 31 - 0
src/test/java/com/nokia/finance/tasks/FinanceTasksApplicationTests.java

@@ -0,0 +1,31 @@
+package com.nokia.finance.tasks;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+@SpringBootTest
+class FinanceTasksApplicationTests {
+
+    @Test
+    void contextLoads() {
+    }
+
+    public static void main(String[] args) {
+        String input = "出车事由:开会,乘车人:张三、李四、王五";
+        String patternStr = "出车事由(.*?)乘车人(.*?)$";
+        Pattern pattern = Pattern.compile(patternStr);
+        Matcher matcher = pattern.matcher(input);
+
+        if (matcher.find()) {
+            String reason = matcher.group(1);
+            String passengers = matcher.group(2);
+            System.out.println("出车事由:" + reason);
+            System.out.println("乘车人:" + passengers);
+        } else {
+            System.out.println("未找到匹配项");
+        }
+    }
+}

+ 22 - 0
src/test/java/com/nokia/finance/tasks/car/ruixing/CljbxxJobTests.java

@@ -0,0 +1,22 @@
+package com.nokia.finance.tasks.car.ruixing;
+
+import com.nokia.finance.tasks.jobs.car.ruixing.CljbxxJob;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.ActiveProfiles;
+
+@Slf4j
+@SpringBootTest
+@ActiveProfiles("dev")
+class CljbxxJobTests {
+    @Autowired
+    CljbxxJob cljbxxJob;
+
+    @Test
+    void runJobTest() {
+        cljbxxJob.runJob();
+    }
+
+}

+ 22 - 0
src/test/java/com/nokia/finance/tasks/car/ruixing/YjbjrtjJobTests.java

@@ -0,0 +1,22 @@
+package com.nokia.finance.tasks.car.ruixing;
+
+import com.nokia.finance.tasks.jobs.car.ruixing.YjbjrtjJob;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.ActiveProfiles;
+
+@Slf4j
+@SpringBootTest
+@ActiveProfiles("dev")
+class YjbjrtjJobTests {
+    @Autowired
+    YjbjrtjJob yjbjrtjJob;
+
+    @Test
+    void runJobTest() {
+        yjbjrtjJob.runJob();
+    }
+
+}