Browse Source

init project

master
zhangyeguang 4 weeks ago
commit
8721d53c77
  1. 38
      .gitignore
  2. 8
      .idea/.gitignore
  3. 7
      .idea/encodings.xml
  4. 14
      .idea/misc.xml
  5. 124
      .idea/uiDesigner.xml
  6. 4
      .idea/vcs.xml
  7. 5207
      logs/solon-web-test.log
  8. 110
      logs/solon-web-test_2024-11-25_0.log
  9. 197
      pom.xml
  10. 12
      src/main/java/com/yeguang/AppMain.java
  11. 64
      src/main/java/com/yeguang/common/BizCode.java
  12. 53
      src/main/java/com/yeguang/common/BusinessException.java
  13. 143
      src/main/java/com/yeguang/common/UserConstants.java
  14. 105
      src/main/java/com/yeguang/common/thread/MonitorThreadPoolExecutor.java
  15. 40
      src/main/java/com/yeguang/common/thread/MonitoredRunnable.java
  16. 21
      src/main/java/com/yeguang/common/thread/NameThreadFactory.java
  17. 34
      src/main/java/com/yeguang/common/thread/ThreadExecutorConfig.java
  18. 30
      src/main/java/com/yeguang/common/thread/ThreadPoolConfig.java
  19. 118
      src/main/java/com/yeguang/common/thread/ThreadStatistics.java
  20. 36
      src/main/java/com/yeguang/config/AppFilter.java
  21. 36
      src/main/java/com/yeguang/config/AppRouterInterceptor.java
  22. 38
      src/main/java/com/yeguang/config/Knife4jConfig.java
  23. 32
      src/main/java/com/yeguang/config/ObsConfig.java
  24. 32
      src/main/java/com/yeguang/config/RedisConfig.java
  25. 70
      src/main/java/com/yeguang/config/SaInterceptorConfig.java
  26. 28
      src/main/java/com/yeguang/controller/InfoController.java
  27. 79
      src/main/java/com/yeguang/controller/RecordController.java
  28. 64
      src/main/java/com/yeguang/controller/UserController.java
  29. 27
      src/main/java/com/yeguang/handler/ModelPredictResponseListTypeHandler.java
  30. 14
      src/main/java/com/yeguang/mapper/RecordContentMapper.java
  31. 17
      src/main/java/com/yeguang/mapper/RecordMapper.java
  32. 13
      src/main/java/com/yeguang/mapper/UserMapper.java
  33. 38
      src/main/java/com/yeguang/model/CreateRecordRequest.java
  34. 21
      src/main/java/com/yeguang/model/LoginRequest.java
  35. 37
      src/main/java/com/yeguang/model/LoginResponse.java
  36. 85
      src/main/java/com/yeguang/model/LoginUser.java
  37. 12
      src/main/java/com/yeguang/model/ModelPredictResponse.java
  38. 23
      src/main/java/com/yeguang/model/PageRequest.java
  39. 33
      src/main/java/com/yeguang/model/PageResult.java
  40. 31
      src/main/java/com/yeguang/model/RecordContentExcel.java
  41. 27
      src/main/java/com/yeguang/model/RecordDetail.java
  42. 31
      src/main/java/com/yeguang/model/RecordExcel.java
  43. 29
      src/main/java/com/yeguang/model/RecordItem.java
  44. 26
      src/main/java/com/yeguang/model/RecordModel.java
  45. 10
      src/main/java/com/yeguang/model/Scale.java
  46. 19
      src/main/java/com/yeguang/model/UpdateScaleRequest.java
  47. 42
      src/main/java/com/yeguang/model/UserRequest.java
  48. 44
      src/main/java/com/yeguang/model/entity/RecordContentEntity.java
  49. 28
      src/main/java/com/yeguang/model/entity/RecordEntity.java
  50. 28
      src/main/java/com/yeguang/model/entity/UserEntity.java
  51. 50
      src/main/java/com/yeguang/remote/RecognitionModelClient.java
  52. 5
      src/main/java/com/yeguang/service/InfoService.java
  53. 13
      src/main/java/com/yeguang/service/InfoServiceImpl.java
  54. 26
      src/main/java/com/yeguang/service/RecordService.java
  55. 299
      src/main/java/com/yeguang/service/RecordServiceImpl.java
  56. 14
      src/main/java/com/yeguang/service/UserService.java
  57. 73
      src/main/java/com/yeguang/service/UserServiceImpl.java
  58. 94
      src/main/java/com/yeguang/service/handler/RecordTaskHandler.java
  59. 21
      src/main/java/com/yeguang/service/handler/RecordTaskManager.java
  60. 115
      src/main/java/com/yeguang/service/handler/RecordTaskProcessor.java
  61. 52
      src/main/java/com/yeguang/util/ExcelBigNumberConvert.java
  62. 303
      src/main/java/com/yeguang/util/HuaweiObs.java
  63. 14
      src/main/java/com/yeguang/util/LoginUtil.java
  64. 34
      src/main/java/com/yeguang/util/Md5Util.java
  65. 146
      src/main/resources/app.yml
  66. 27
      src/main/resources/mapper/RecordContentMapper.xml
  67. 30
      src/main/resources/mapper/RecordMapper.xml
  68. 20
      src/main/resources/mapper/UserMapper.xml

38
.gitignore

@ -0,0 +1,38 @@
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store

8
.idea/.gitignore

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

7
.idea/encodings.xml

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
</component>
</project>

14
.idea/misc.xml

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="MavenProjectsManager">
<option name="originalFiles">
<list>
<option value="$PROJECT_DIR$/pom.xml" />
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

124
.idea/uiDesigner.xml

@ -0,0 +1,124 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Palette2">
<group name="Swing">
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
</item>
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
</item>
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.svg" removable="false" auto-create-binding="false" can-attach-label="true">
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
</item>
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
<initial-values>
<property name="text" value="Button" />
</initial-values>
</item>
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="RadioButton" />
</initial-values>
</item>
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="CheckBox" />
</initial-values>
</item>
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
<initial-values>
<property name="text" value="Label" />
</initial-values>
</item>
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
</item>
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
</item>
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
<preferred-size width="-1" height="20" />
</default-constraints>
</item>
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
</item>
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
</item>
</group>
</component>
</project>

4
.idea/vcs.xml

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings" defaultProject="true" />
</project>

5207
logs/solon-web-test.log

File diff suppressed because it is too large

110
logs/solon-web-test_2024-11-25_0.log

@ -0,0 +1,110 @@
INFO 2024-11-25 18:06:48.905 [-main][*][o.noear.solon.Solon]:
App: Start loading
INFO 2024-11-25 18:06:48.937 [-main][*][o.noear.solon.Solon]:
App: Plugin starting
INFO 2024-11-25 18:06:49.008 [-main][*][o.noear.solon.Solon]:
Render mapping: @json=StringSerializerRender#snack3-json
INFO 2024-11-25 18:06:49.009 [-main][*][o.noear.solon.Solon]:
Render mapping: @type_json=StringSerializerRender#snack3-json
INFO 2024-11-25 18:06:49.012 [-main][*][o.noear.solon.Solon]:
Session: Local session state plugin is loaded
INFO 2024-11-25 18:06:49.016 [-main][*][o.noear.solon.Solon]:
App: Bean scanning
INFO 2024-11-25 18:06:49.125 [-main][*][o.noear.solon.Solon]:
solon.connector:main: smarthttp: Started ServerConnector@{HTTP/1.1,[http/1.1]}{http://localhost:9198}
INFO 2024-11-25 18:06:49.126 [-main][*][o.noear.solon.Solon]:
Server:main: smarthttp: Started (smart http 2.0/3.0.3) @66ms
INFO 2024-11-25 18:06:49.128 [-main][*][o.noear.solon.Solon]:
App: End loading elapsed=543ms pid=58141 v=3.0.3
INFO 2024-11-25 18:06:53.368 [-Thread-1][*][o.noear.solon.Solon]:
Server:main: smarthttp: Has Stopped (smart http 2.0/3.0.3)
INFO 2024-11-25 18:06:53.368 [-Thread-1][*][o.noear.solon.Solon]:
App: Stopped
INFO 2024-11-25 18:07:06.300 [-main][*][o.noear.solon.Solon]:
App: Start loading
INFO 2024-11-25 18:07:06.334 [-main][*][o.noear.solon.Solon]:
App: Plugin starting
INFO 2024-11-25 18:07:06.400 [-main][*][o.noear.solon.Solon]:
Render mapping: @json=StringSerializerRender#snack3-json
INFO 2024-11-25 18:07:06.401 [-main][*][o.noear.solon.Solon]:
Render mapping: @type_json=StringSerializerRender#snack3-json
INFO 2024-11-25 18:07:06.403 [-main][*][o.noear.solon.Solon]:
Session: Local session state plugin is loaded
INFO 2024-11-25 18:07:06.407 [-main][*][o.noear.solon.Solon]:
App: Bean scanning
INFO 2024-11-25 18:07:06.499 [-main][*][o.noear.solon.Solon]:
solon.connector:main: smarthttp: Started ServerConnector@{HTTP/1.1,[http/1.1]}{http://localhost:9198}
INFO 2024-11-25 18:07:06.499 [-main][*][o.noear.solon.Solon]:
Server:main: smarthttp: Started (smart http 2.0/3.0.3) @55ms
INFO 2024-11-25 18:07:06.501 [-main][*][o.noear.solon.Solon]:
App: End loading elapsed=521ms pid=58150 v=3.0.3
INFO 2024-11-25 18:09:44.098 [-Thread-1][*][o.noear.solon.Solon]:
Server:main: smarthttp: Has Stopped (smart http 2.0/3.0.3)
INFO 2024-11-25 18:09:44.100 [-Thread-1][*][o.noear.solon.Solon]:
App: Stopped
INFO 2024-11-25 18:09:47.177 [-main][*][o.noear.solon.Solon]:
App: Start loading
INFO 2024-11-25 18:09:47.218 [-main][*][o.noear.solon.Solon]:
App: Plugin starting
INFO 2024-11-25 18:09:47.287 [-main][*][o.noear.solon.Solon]:
Render mapping: @json=StringSerializerRender#snack3-json
INFO 2024-11-25 18:09:47.288 [-main][*][o.noear.solon.Solon]:
Render mapping: @type_json=StringSerializerRender#snack3-json
INFO 2024-11-25 18:09:47.291 [-main][*][o.noear.solon.Solon]:
Session: Local session state plugin is loaded
INFO 2024-11-25 18:09:47.295 [-main][*][o.noear.solon.Solon]:
App: Bean scanning
INFO 2024-11-25 18:09:47.404 [-main][*][o.noear.solon.Solon]:
solon.connector:main: smarthttp: Started ServerConnector@{HTTP/1.1,[http/1.1]}{http://localhost:9198}
INFO 2024-11-25 18:09:47.404 [-main][*][o.noear.solon.Solon]:
Server:main: smarthttp: Started (smart http 2.0/3.0.3) @59ms
INFO 2024-11-25 18:09:47.406 [-main][*][o.noear.solon.Solon]:
App: End loading elapsed=560ms pid=58210 v=3.0.3
INFO 2024-11-25 18:11:27.711 [-Thread-1][*][o.noear.solon.Solon]:
Server:main: smarthttp: Has Stopped (smart http 2.0/3.0.3)
INFO 2024-11-25 18:11:27.712 [-Thread-1][*][o.noear.solon.Solon]:
App: Stopped
INFO 2024-11-25 18:12:31.162 [-main][*][o.noear.solon.Solon]:
App: Start loading
INFO 2024-11-25 18:12:31.193 [-main][*][o.noear.solon.Solon]:
App: Plugin starting
INFO 2024-11-25 18:12:31.295 [-main][*][o.noear.solon.Solon]:
Render mapping: @json=StringSerializerRender#snack3-json
INFO 2024-11-25 18:12:31.296 [-main][*][o.noear.solon.Solon]:
Render mapping: @type_json=StringSerializerRender#snack3-json
INFO 2024-11-25 18:12:31.297 [-main][*][o.noear.solon.Solon]:
Session: Local session state plugin is loaded
INFO 2024-11-25 18:12:31.301 [-main][*][o.noear.solon.Solon]:
App: Bean scanning
INFO 2024-11-25 18:12:31.408 [-main][*][o.noear.solon.Solon]:
solon.connector:main: smarthttp: Started ServerConnector@{HTTP/1.1,[http/1.1]}{http://localhost:9198}
INFO 2024-11-25 18:12:31.410 [-main][*][o.noear.solon.Solon]:
Server:main: smarthttp: Started (smart http 2.0/3.0.3) @69ms
INFO 2024-11-25 18:12:31.417 [-main][*][o.noear.solon.Solon]:
App: End loading elapsed=622ms pid=58274 v=3.0.3
INFO 2024-11-25 18:13:51.719 [-Thread-1][*][o.noear.solon.Solon]:
Server:main: smarthttp: Has Stopped (smart http 2.0/3.0.3)
INFO 2024-11-25 18:13:51.721 [-Thread-1][*][o.noear.solon.Solon]:
App: Stopped
INFO 2024-11-25 18:13:53.668 [-main][*][o.noear.solon.Solon]:
App: Start loading
INFO 2024-11-25 18:13:53.700 [-main][*][o.noear.solon.Solon]:
App: Plugin starting
INFO 2024-11-25 18:13:53.793 [-main][*][o.noear.solon.Solon]:
Render mapping: @json=StringSerializerRender#snack3-json
INFO 2024-11-25 18:13:53.794 [-main][*][o.noear.solon.Solon]:
Render mapping: @type_json=StringSerializerRender#snack3-json
INFO 2024-11-25 18:13:53.795 [-main][*][o.noear.solon.Solon]:
Session: Local session state plugin is loaded
INFO 2024-11-25 18:13:53.800 [-main][*][o.noear.solon.Solon]:
App: Bean scanning
INFO 2024-11-25 18:13:53.894 [-main][*][o.noear.solon.Solon]:
solon.connector:main: smarthttp: Started ServerConnector@{HTTP/1.1,[http/1.1]}{http://localhost:9198}
INFO 2024-11-25 18:13:53.894 [-main][*][o.noear.solon.Solon]:
Server:main: smarthttp: Started (smart http 2.0/3.0.3) @59ms
INFO 2024-11-25 18:13:53.896 [-main][*][o.noear.solon.Solon]:
App: End loading elapsed=555ms pid=58312 v=3.0.3
INFO 2024-11-25 18:14:20.671 [-Thread-1][*][o.noear.solon.Solon]:
Server:main: smarthttp: Has Stopped (smart http 2.0/3.0.3)
INFO 2024-11-25 18:14:20.672 [-Thread-1][*][o.noear.solon.Solon]:
App: Stopped

197
pom.xml

@ -0,0 +1,197 @@
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.yeguang</groupId>
<artifactId>siliqua-recognition-solon</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.noear</groupId>
<artifactId>solon-parent</artifactId>
<version>3.0.3</version>
</parent>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<mybatis-plus.version>3.5.9</mybatis-plus.version>
<mybatis-flex.version>1.9.8</mybatis-flex.version>
<mapstruct.version>1.6.1</mapstruct.version>
<hutool.version>5.8.22</hutool.version>
</properties>
<dependencies>
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon-web</artifactId>
</dependency>
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon.web.cors</artifactId>
</dependency>
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon-logging-logback</artifactId>
</dependency>
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon-net-httputils</artifactId>
</dependency>
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon-openapi2-knife4j</artifactId>
</dependency>
<!-- 数据库相关依赖-->
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon-data-sqlutils</artifactId>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>
<dependency>
<groupId>org.noear</groupId>
<artifactId>mybatis-solon-plugin</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>com.mybatis-flex</groupId>-->
<!-- <artifactId>mybatis-flex-solon-plugin</artifactId>-->
<!-- <version>${mybatis-flex.version}</version>-->
<!-- <exclusions>-->
<!-- <exclusion>-->
<!-- <groupId>org.noear</groupId>-->
<!-- <artifactId>mybatis-solon-plugin</artifactId>-->
<!-- </exclusion>-->
<!-- </exclusions>-->
<!-- </dependency>-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-solon-plugin</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-jsqlparser-4.9</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.34</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon-cache-redisson</artifactId>
<!-- 或者用 (如果不需要 CacheService,直接用它)
<artifactId>redisson-solon-plugin</artifactId>
-->
</dependency>
<dependency>
<groupId>org.noear</groupId>
<artifactId>sa-token-solon-plugin</artifactId>
</dependency>
<dependency>
<groupId>org.noear</groupId>
<artifactId>sa-token-dao-redisson-jackson</artifactId>
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-jwt</artifactId>
<version>1.39.0</version>
</dependency>
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon-cloud</artifactId>
</dependency>
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon-config-plus</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.43</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.16.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-email</artifactId>
<version>1.5</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${mapstruct.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.4</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.17.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>4.0.2</version>
</dependency>
<dependency>
<groupId>com.huaweicloud</groupId>
<artifactId>esdk-obs-java</artifactId>
<version>3.21.11</version>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.noear</groupId>
<artifactId>solon-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

12
src/main/java/com/yeguang/AppMain.java

@ -0,0 +1,12 @@
package com.yeguang;
import org.noear.solon.Solon;
import org.noear.solon.annotation.SolonMain;
@SolonMain
public class AppMain {
public static void main(String[] args) {
Solon.start(AppMain.class, args);
}
}

64
src/main/java/com/yeguang/common/BizCode.java

@ -0,0 +1,64 @@
package com.yeguang.common;
import lombok.Getter;
@Getter
public enum BizCode {
General_Success(200, "接口调用成功"),
ServerError(10001, "服务器异常"),
General_Failure(10004, "接口调用失败"),
General_DBError(10005, "DB错误"),
General_ParameterInvalid(12001, "参数校验失败"),
/**
* 通用类
*/
USER_UNREGISTERED(12001, "用户未注册"),
NOT_FOUND(12002, "数据不存在"),
LOGIN_TYPE_ERROR(12005, "登录类型错误"),
/**
* 文件服务
*/
FILE_DOWNLOAD_ERROR(90001, "文件下载错误"),
FILE_UPLOAD_ERROR(90002, "文件上传失败"),
USER_NOT_FOUND(40004, "用户不存在"),
CAPTCHA_ERROR(40003, "验证码错误"),
PASSWORD_ERROR(40002, "密码错误"),
TOKEN_TIMEOUT_ERROR(40006, "token已过期"),
USER_STOP_FOUND(40008, "用户被禁用"),
USER_STATUS_ERROR(40009,"用户未审核通过"),
USER_NOT_LOGIN(40011, "用户未登录,请登录后再操作"),
ACCESS_TOKEN_INVALID(40040, "用户token无效,请重新登录"),
ACCESS_NOT_ALLOWABLE(50001, "服务不允许直接访问"),
PERMISSION_NOT_FOUND(40001,"该用户无操作权限"),
USER_PHONE_EXIST(40012, "用户手机号已存在"),
;
private final Integer code;
private final String msg;
BizCode(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
}

53
src/main/java/com/yeguang/common/BusinessException.java

@ -0,0 +1,53 @@
package com.yeguang.common;
import lombok.Data;
@Data
public class BusinessException extends RuntimeException {
private int code;
private String message;
public BusinessException(int code, String message) {
super(message);
this.code = code;
this.message = message;
}
public BusinessException(String message, Throwable cause, int code, String message1) {
super(message, cause);
this.code = code;
this.message = message1;
}
public BusinessException(Throwable cause, int code, String message) {
super(cause);
this.code = code;
this.message = message;
}
public BusinessException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, int code, String message1) {
super(message, cause, enableSuppression, writableStackTrace);
this.code = code;
this.message = message1;
}
public BusinessException(BizCode biz) {
super();
this.code = biz.getCode();
this.message = biz.getMsg();
}
public BusinessException(String message) {
super(message);
this.code= BizCode.General_ParameterInvalid.getCode();
this.message =message;
}
public BusinessException(BizCode biz, String msg) {
super();
this.code = biz.getCode();
this.message = msg;
}
}

143
src/main/java/com/yeguang/common/UserConstants.java

@ -0,0 +1,143 @@
package com.yeguang.common;
/**
* 用户常量信息
*
* @author ruoyi
*/
public interface UserConstants {
/**
* 平台内系统用户的唯一标志
*/
String SYS_SESSION = "CURRENT_USER";
/**
* 正常状态
*/
String NORMAL = "0";
/**
* 异常状态
*/
String EXCEPTION = "1";
/**
* 用户正常状态
*/
String USER_NORMAL = "0";
/**
* 用户封禁状态
*/
String USER_DISABLE = "1";
/**
* 角色正常状态
*/
String ROLE_NORMAL = "0";
/**
* 角色封禁状态
*/
String ROLE_DISABLE = "1";
/**
* 部门正常状态
*/
String DEPT_NORMAL = "0";
/**
* 部门停用状态
*/
String DEPT_DISABLE = "1";
/**
* 岗位正常状态
*/
String POST_NORMAL = "0";
/**
* 岗位停用状态
*/
String POST_DISABLE = "1";
/**
* 字典正常状态
*/
String DICT_NORMAL = "0";
/**
* 是否为系统默认
*/
String YES = "Y";
/**
* 是否菜单外链
*/
String YES_FRAME = "0";
/**
* 是否菜单外链
*/
String NO_FRAME = "1";
/**
* 菜单正常状态
*/
String MENU_NORMAL = "0";
/**
* 菜单停用状态
*/
String MENU_DISABLE = "1";
/**
* 菜单类型目录
*/
String TYPE_DIR = "M";
/**
* 菜单类型菜单
*/
String TYPE_MENU = "C";
/**
* 菜单类型按钮
*/
String TYPE_BUTTON = "F";
/**
* Layout组件标识
*/
String LAYOUT = "Layout";
/**
* ParentView组件标识
*/
String PARENT_VIEW = "ParentView";
/**
* InnerLink组件标识
*/
String INNER_LINK = "InnerLink";
/**
* 用户名长度限制
*/
int USERNAME_MIN_LENGTH = 2;
int USERNAME_MAX_LENGTH = 20;
/**
* 密码长度限制
*/
int PASSWORD_MIN_LENGTH = 5;
int PASSWORD_MAX_LENGTH = 20;
/**
* 超级管理员ID
*/
Long SUPER_ADMIN_ID = 1L;
}

105
src/main/java/com/yeguang/common/thread/MonitorThreadPoolExecutor.java

@ -0,0 +1,105 @@
package com.yeguang.common.thread;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.*;
@Slf4j
public class MonitorThreadPoolExecutor extends ThreadPoolExecutor {
private final ThreadLocal<Long> executeStartTime;
protected String poolName;
private final int slowTaskThreshold;
private final int queueTimeThreshold;
private final int queueSizeThreshold;
private static final int DEFAULT_SLOW_TASK_TIME = 1000;
private static final int DEFAULT_QUEUE_TIME = 100;
private static final int DEFAULT_QUEUE_SIZE = 450;
public MonitorThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, String poolName) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, new NameThreadFactory(poolName), new AbortPolicy(), poolName);
}
public MonitorThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, String poolName) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, new AbortPolicy(), poolName);
}
public MonitorThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler, String poolName) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, new NameThreadFactory(poolName), handler, poolName);
}
public MonitorThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler, String poolName) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
this.poolName = poolName;
executeStartTime = new ThreadLocal<>();
slowTaskThreshold = 0;
queueTimeThreshold = 0;
queueSizeThreshold = 0;
}
public MonitorThreadPoolExecutor(ThreadPoolConfig poolConfig) {
super(poolConfig.getCorePoolSize(),
poolConfig.getMaxPoolSize(),
poolConfig.getKeepAliveSeconds(),
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(poolConfig.getQueueCapacity()), new NameThreadFactory(poolConfig.getThreadName()),
(r, executor1) -> log.info("任务被中断"));
this.poolName = poolConfig.getThreadName();
executeStartTime = new ThreadLocal<>();
this.slowTaskThreshold = poolConfig.getTaskThreshold() > 0 ? poolConfig.getTaskThreshold() : DEFAULT_SLOW_TASK_TIME;
this.queueTimeThreshold = poolConfig.getQueueTimeThreshold() > 0 ? poolConfig.getQueueTimeThreshold() : DEFAULT_QUEUE_TIME;
this.queueSizeThreshold = poolConfig.getQueueSizeThreshold() > 0 ? poolConfig.getQueueSizeThreshold() : DEFAULT_QUEUE_SIZE;
}
@Override
public void execute(Runnable command) {
super.execute(command);
}
@Override
protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
executeStartTime.set(System.nanoTime());
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
MonitoredRunnable monitored = null;
try {
long executeEndNano = System.nanoTime();
Long executeStartTime = this.executeStartTime.get();
monitored = (MonitoredRunnable) r;
long queueNanoTime = monitored.getInQueueNanoTime();
int queueTime = (int) ((executeStartTime - queueNanoTime) / 1000000L);
int executeTime = (int) ((executeEndNano - executeStartTime) / 1000000L);
ThreadStatistics packStatistics = null;
if (monitored.getThreadLocal() != null) {
packStatistics = monitored.getThreadLocal().get();
}
if (executeTime > this.slowTaskThreshold || queueTime > this.queueTimeThreshold || getQueue().size() >= queueSizeThreshold) {
log.warn("线程池({}),触发告警,当前任务执行时间={},当前任务排队时间={},队列线程总数={} ", Thread.currentThread().getName(), executeTime, queueTime, getQueue().size());
if (packStatistics != null) {
log.warn("当前任务各阶段耗时统计:{}", packStatistics);
}
}
if (t != null) {
log.error("线程池名称 = {}, 执行异常的任务数+1", poolName);
}
} catch (Exception ignore) {
} finally {
executeStartTime.remove();
if (monitored != null && monitored.getThreadLocal() != null) {
monitored.getThreadLocal().remove();
}
}
}
}

40
src/main/java/com/yeguang/common/thread/MonitoredRunnable.java

@ -0,0 +1,40 @@
package com.yeguang.common.thread;
public class MonitoredRunnable implements Runnable {
private final Runnable runnable;
private ThreadLocal<ThreadStatistics> threadLocal;
private final long inQueueNanoTime;
public MonitoredRunnable(Runnable runnable, ThreadLocal<ThreadStatistics> threadLocal) {
this.runnable = runnable;
this.threadLocal = threadLocal;
this.inQueueNanoTime = System.nanoTime();
}
public ThreadLocal<ThreadStatistics> getThreadLocal() {
return threadLocal;
}
public void setThreadLocal(ThreadLocal<ThreadStatistics> threadLocal) {
this.threadLocal = threadLocal;
}
public long getInQueueNanoTime() {
return inQueueNanoTime;
}
public MonitoredRunnable(Runnable runnable) {
this.runnable = runnable;
this.inQueueNanoTime = System.nanoTime();
}
@Override
public void run() {
this.runnable.run();
}
}

21
src/main/java/com/yeguang/common/thread/NameThreadFactory.java

@ -0,0 +1,21 @@
package com.yeguang.common.thread;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
public class NameThreadFactory implements ThreadFactory {
private String namePrefix;
private AtomicInteger nextId = new AtomicInteger(1);
public NameThreadFactory(String namePrefix) {
this.namePrefix = namePrefix;
}
@Override
public Thread newThread(Runnable r) {
return new Thread(r, namePrefix + nextId.getAndIncrement());
}
}

34
src/main/java/com/yeguang/common/thread/ThreadExecutorConfig.java

@ -0,0 +1,34 @@
package com.yeguang.common.thread;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.noear.solon.annotation.Bean;
import org.noear.solon.annotation.Configuration;
import org.noear.solon.annotation.Inject;
import java.util.concurrent.ThreadPoolExecutor;
@Slf4j
@Configuration
@RequiredArgsConstructor
public class ThreadExecutorConfig {
private final ThreadPoolConfig threadPoolConfig;
// @Primary
@Bean(name = "customThreadPool")
public ThreadPoolExecutor getCustomExecutor(@Inject("${thread.enabled}") boolean enabled) {
if (enabled) {
return generateExecutor(threadPoolConfig);
}
return null;
}
private ThreadPoolExecutor generateExecutor(ThreadPoolConfig threadPoolConfig) {
return new MonitorThreadPoolExecutor(threadPoolConfig);
}
}

30
src/main/java/com/yeguang/common/thread/ThreadPoolConfig.java

@ -0,0 +1,30 @@
package com.yeguang.common.thread;
import lombok.Data;
import org.noear.solon.annotation.Configuration;
import org.noear.solon.annotation.Inject;
@Data
@Configuration
@Inject("${thread.pool}")
public class ThreadPoolConfig {
private int corePoolSize;
private int maxPoolSize;
private int keepAliveSeconds;
private int queueCapacity;
private String threadName;
private int taskThreshold;
private int queueTimeThreshold;
private int queueSizeThreshold;
}

118
src/main/java/com/yeguang/common/thread/ThreadStatistics.java

@ -0,0 +1,118 @@
package com.yeguang.common.thread;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Data
@Accessors(chain = true)
public class ThreadStatistics {
private String imei;
private long totalCost;
private long start;
private long finish;
private Map<StatisticsEnum, Item> phases = new ConcurrentHashMap<>();
/**
* 指定阶段处理统计
*
* @param statisticsEnum
*/
public void startPhase(StatisticsEnum statisticsEnum) {
phases.computeIfAbsent(statisticsEnum, key -> new Item());
}
/**
* 完成指定部分的处理统计
*
* @param statisticsEnum 指定部分
*/
public void finishPhase(StatisticsEnum statisticsEnum) {
Item item = phases.getOrDefault(statisticsEnum, null);
if (item != null) {
item.finish();
}
}
public void finishStatistics() {
this.finish = System.currentTimeMillis();
this.totalCost = this.finish - this.start;
}
public ThreadStatistics(long start) {
this.start = start;
}
@Data
public static class Item {
public Item() {
start = System.currentTimeMillis();
}
long start;
volatile long finish;
volatile long cost;
/**
* 完成处理统计
*/
public void finish() {
if (finish != 0) {
return;
}
this.finish = System.currentTimeMillis();
this.cost = this.finish - start;
}
@Override
public String toString() {
return "Item{" +
"cost=" + cost +
'}';
}
}
public enum StatisticsEnum {
/**
* 基础信息解析
*/
base_info,
/**
* 解密
*/
decrypt,
/**
* 数据包解析
*/
deserialization,
/**
* 数据保存到ck
*/
save;
}
@Override
public String toString() {
return "Statistics{" +
"imei='" + imei + '\'' +
", totalCost=" + totalCost +
", start=" + start +
", finish=" + finish +
", phases=" + phases +
'}';
}
}

36
src/main/java/com/yeguang/config/AppFilter.java

@ -0,0 +1,36 @@
package com.yeguang.config;
import cn.dev33.satoken.exception.NotLoginException;
import org.noear.solon.annotation.Component;
import org.noear.solon.cloud.CloudClient;
import org.noear.solon.core.exception.StatusException;
import org.noear.solon.core.handle.Context;
import org.noear.solon.core.handle.Filter;
import org.noear.solon.core.handle.FilterChain;
import org.noear.solon.core.handle.Result;
import org.noear.solon.validation.ValidatorException;
import org.slf4j.MDC;
@Component(index = -100) //index 为顺序位(不加,则默认为0)
public class AppFilter implements Filter {
@Override
public void doFilter(Context ctx, FilterChain chain) throws Throwable {
try {
String traceId = CloudClient.trace().getTraceId();
MDC.put("X-TraceId", traceId);
chain.doFilter(ctx);
} catch (NotLoginException e) {
ctx.render(Result.failure(e.getCode(), e.getMessage()));
} catch (ValidatorException e) {
ctx.render(Result.failure(e.getCode(), e.getMessage())); //e.getResult().getDescription()
} catch (StatusException e) {
if (e.getCode() == 404) {
ctx.status(e.getCode());
} else {
ctx.render(Result.failure(e.getCode(), e.getMessage()));
}
} catch (Throwable e) {
ctx.render(Result.failure(500, "服务端运行出错"));
}
}
}

36
src/main/java/com/yeguang/config/AppRouterInterceptor.java

@ -0,0 +1,36 @@
package com.yeguang.config;
import cn.dev33.satoken.exception.NotLoginException;
import lombok.extern.slf4j.Slf4j;
import org.noear.solon.annotation.Component;
import org.noear.solon.core.exception.StatusException;
import org.noear.solon.core.handle.Context;
import org.noear.solon.core.handle.Handler;
import org.noear.solon.core.handle.Result;
import org.noear.solon.core.route.RouterInterceptor;
import org.noear.solon.core.route.RouterInterceptorChain;
import org.noear.solon.validation.ValidatorException;
@Slf4j
@Component(index = 0) //index 为顺序位(不加,则默认为0)
public class AppRouterInterceptor implements RouterInterceptor {
@Override
public void doIntercept(Context ctx, Handler mainHandler, RouterInterceptorChain chain) throws Throwable {
try {
chain.doIntercept(ctx, mainHandler);
} catch (NotLoginException e) {
ctx.render(Result.failure(e.getCode(), e.getMessage()));
} catch (ValidatorException e) {
ctx.render(Result.failure(e.getCode(), e.getMessage())); //e.getResult().getDescription()
} catch (StatusException e) {
if (e.getCode() == 404) {
ctx.status(e.getCode());
} else {
ctx.render(Result.failure(e.getCode(), e.getMessage()));
}
} catch (Throwable e) {
log.error("服务端运行出错", e);
ctx.render(Result.failure(500, "服务端运行出错"));
}
}
}

38
src/main/java/com/yeguang/config/Knife4jConfig.java

@ -0,0 +1,38 @@
package com.yeguang.config;
import com.github.xiaoymin.knife4j.solon.extension.OpenApiExtensionResolver;
import io.swagger.models.Scheme;
import org.noear.solon.annotation.Bean;
import org.noear.solon.annotation.Configuration;
import org.noear.solon.annotation.Inject;
import org.noear.solon.docs.DocDocket;
import org.noear.solon.docs.models.ApiContact;
import org.noear.solon.docs.models.ApiInfo;
/**
* 自定义 Knife4j 接口文档的配置
*/
@Configuration
public class Knife4jConfig {
@Inject
OpenApiExtensionResolver openApiExtensionResolver;
@Bean(value = "defaultApi2")
public DocDocket defaultApi2() {
//根据情况增加 "knife4j.setting" (可选)
return new DocDocket()
.basicAuth(openApiExtensionResolver.getSetting().getBasic())
.vendorExtensions(openApiExtensionResolver.buildExtensions())
.groupName("demo接口文档")
.info(new ApiInfo().title("在线文档")
.description("demo在线API文档")
.termsOfService("https://gitee.com/noear/solon")
.contact(new ApiContact().name("yeguangzhang")
.url("https://gitee.com/noear/solon")
.email("yeguangzhang@126.com"))
.version("1.0"))
.schemes(String.valueOf(Scheme.HTTP))
.apis("com.yeguang.controller");
}
}

32
src/main/java/com/yeguang/config/ObsConfig.java

@ -0,0 +1,32 @@
package com.yeguang.config;
import com.obs.services.ObsClient;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.noear.solon.annotation.Bean;
import org.noear.solon.annotation.Configuration;
import org.noear.solon.annotation.Inject;
@Data
@Configuration
@NoArgsConstructor
@Inject("${huawei.obs}")
public class ObsConfig {
private String endPoint;
private String ak;
private String sk;
private String bucketName;
private String pathPrefix;
@Bean
public ObsClient obsClient() {
ObsClient obsClient = new ObsClient(ak, sk, endPoint);
return obsClient;
}
}

32
src/main/java/com/yeguang/config/RedisConfig.java

@ -0,0 +1,32 @@
package com.yeguang.config;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.solon.dao.SaTokenDaoOfRedissonJackson;
import org.noear.solon.annotation.Bean;
import org.noear.solon.annotation.Configuration;
import org.noear.solon.annotation.Inject;
import org.noear.solon.cache.redisson.RedissonCacheService;
import org.noear.solon.cache.redisson.RedissonClientOriginalSupplier;
import org.noear.solon.data.cache.CacheService;
import org.redisson.api.RedissonClient;
@Configuration
public class RedisConfig {
// 构建 redis client(如直接用);RedissonClientOriginalSupplier 支持 Redisson 的原始风格配置
@Bean
public RedissonClient redisClient(@Inject("${app.redis}") RedissonClientOriginalSupplier supplier) {
return supplier.get();
}
//构建 Cache Service(给 @Cache 用)
@Bean
public CacheService cacheService(@Inject RedissonClient client){
return new RedissonCacheService(client, 30);
}
//构建 SaToken Dao //v2.4.3 后支持
@Bean
public SaTokenDao saTokenDao(@Inject RedissonClient client){
return new SaTokenDaoOfRedissonJackson(client);
}
}

70
src/main/java/com/yeguang/config/SaInterceptorConfig.java

@ -0,0 +1,70 @@
package com.yeguang.config;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.jwt.StpLogicJwtForSimple;
import cn.dev33.satoken.solon.integration.SaTokenInterceptor;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.stp.StpUtil;
import lombok.extern.slf4j.Slf4j;
import org.noear.solon.annotation.Bean;
import org.noear.solon.annotation.Configuration;
import org.noear.solon.core.mvc.ActionDefault;
@Slf4j
@Configuration
public class SaInterceptorConfig {
private final String[] EXCLUDE_PATH = {
"/favicon.ico",
"/doc.html",
"/swagger-ui/**",
// "/swagger-resources",
"/swagger-resources**",
"/swagger/v2",
"/v2/api-docs/**",
"/webjars/**",
"/static/**",
"/error",
"/user/login",
"/user/register",
"/user/logout",
"/record/export/**"
};
@Bean(index = -200) //-100,是顺序位(低值优先)
public SaTokenInterceptor saTokenInterceptor() {
return new SaTokenInterceptor()
// 指定 [拦截路由] 与 [放行路由]
.addInclude("/**").addExclude(EXCLUDE_PATH)
// 认证函数: 每次请求执行
.setAuth(req -> {
StpUtil.checkLogin();
})
// 异常处理函数:每次认证函数发生异常时执行此函数 //包括注解异常
// .setError(e -> {
// log.error("sa interceptor", e);
// })
// 前置函数:在每次认证函数之前执行
.setBeforeAuth(req -> {
// ---------- 设置一些安全响应头 ----------
SaHolder.getResponse()
// 服务器名称
.setServer("sa-server")
// 是否可以在iframe显示视图: DENY=不可以 | SAMEORIGIN=同域下可以 | ALLOW-FROM uri=指定域名下可以
.setHeader("X-Frame-Options", "SAMEORIGIN")
// 是否启用浏览器默认XSS防护: 0=禁用 | 1=启用 | 1; mode=block 启用, 并在检查到XSS攻击时,停止渲染页面
.setHeader("X-XSS-Protection", "1; mode=block")
// 禁用浏览器内容嗅探
.setHeader("X-Content-Type-Options", "nosniff");
});
}
@Bean
public StpLogic getStpLogicJwt() {
// Sa-Token 整合 jwt (简单模式)
return new StpLogicJwtForSimple();
}
}

28
src/main/java/com/yeguang/controller/InfoController.java

@ -0,0 +1,28 @@
package com.yeguang.controller;
import com.yeguang.service.InfoService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.noear.solon.annotation.Controller;
import org.noear.solon.annotation.Get;
import org.noear.solon.annotation.Mapping;
@Slf4j
@Controller
@Mapping("/info")
@RequiredArgsConstructor
@Api(tags = "信息")
public class InfoController {
private final InfoService infoService;
@ApiOperation("获取信息")
@Get
@Mapping("/hello")
public String hello() {
log.info("hello api has been called");
return infoService.hello();
}
}

79
src/main/java/com/yeguang/controller/RecordController.java

@ -0,0 +1,79 @@
package com.yeguang.controller;
import cn.hutool.core.map.MapUtil;
import com.yeguang.model.*;
import com.yeguang.model.entity.RecordContentEntity;
import com.yeguang.service.RecordService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.noear.solon.annotation.Controller;
import org.noear.solon.annotation.Mapping;
import org.noear.solon.core.handle.Context;
import org.noear.solon.core.handle.MethodType;
import org.noear.solon.core.handle.Result;
import org.noear.solon.validation.annotation.Validated;
@Api(tags = "识别记录")
@Slf4j
@Controller
@RequiredArgsConstructor
@Mapping("/record")
public class RecordController {
private final RecordService recordService;
@ApiOperation(value = "创建识别记录")
@Mapping(value = "/createRecord", multipart = true, produces = "application/json", method = MethodType.POST)
public Result createRecord(Context context) {
CreateRecordRequest request = context.paramAsBean(CreateRecordRequest.class);
request.setFiles(context.fileValues("files"));
String taskId = recordService.createRecordAsync(request);
return Result.succeed(MapUtil.of("taskId", taskId));
}
@ApiOperation(value = "识别记录处理进度")
@Mapping(value = "/taskProgress/{taskId}", method = MethodType.GET)
public Float getTaskProgress(String taskId) {
return recordService.getTaskProgress(taskId);
}
@ApiOperation(value = "获取识别记录详情")
@Mapping(value = "/getRecordDetail/{recordId}", method = MethodType.GET)
public Result<RecordDetail> getRecordDetail(Long recordId) {
return Result.succeed(recordService.getRecordDetail(recordId));
}
@ApiOperation(value = "导出识别记录", notes = "下载文件(响应类型为文件流)")
@Mapping(value = "/export/{recordId}", produces = "application/octet-stream", method = MethodType.GET)
public void exportRecord(Long recordId, Context context) {
recordService.exportRecord(recordId, context);
}
@ApiOperation(value = "分页获取识别记录")
@Mapping(value = "/page", method = MethodType.POST)
public Result<PageResult<RecordItem>> getPages(PageRequest pageRequest) {
return Result.succeed(recordService.getPages(pageRequest));
}
@ApiOperation(value = "更新识别记录内容")
@Mapping(value = "/updateRecordContent", method = MethodType.PUT)
public void updateRecord(RecordContentEntity recordContentEntity) {
recordService.updateRecordContent(recordContentEntity);
}
@ApiOperation(value = "获取模型列表")
@Mapping(value = "/models", method = MethodType.GET)
public Result<RecordModel> getModels() {
return Result.succeed(recordService.getModels());
}
@ApiOperation(value = "比例尺设置")
@Mapping(value = "/scale", method = MethodType.PUT)
public void updateScale(UpdateScaleRequest request) {
recordService.updateScale(request);
}
}

64
src/main/java/com/yeguang/controller/UserController.java

@ -0,0 +1,64 @@
package com.yeguang.controller;
import cn.dev33.satoken.stp.StpUtil;
import com.yeguang.model.LoginRequest;
import com.yeguang.model.LoginResponse;
import com.yeguang.model.UserRequest;
import com.yeguang.service.UserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.noear.solon.annotation.Controller;
import org.noear.solon.annotation.Get;
import org.noear.solon.annotation.Mapping;
import org.noear.solon.core.handle.Result;
import org.noear.solon.validation.annotation.Validated;
@Api(tags = "用户")
@Slf4j
@Controller
@Mapping("/user")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@Get
@Mapping("/info")
public String info() {
log.info("info");
return "info";
}
@ApiOperation(value = "登录")
@Mapping("/login")
public Result<LoginResponse> login(@Validated LoginRequest loginBody) {
LoginResponse loginVo = userService.login(loginBody);
return Result.succeed(loginVo);
}
@ApiOperation(value = "登出")
@Mapping("/logout")
public Result<String> logout() {
StpUtil.logout();
return Result.succeed("logout");
}
@ApiOperation(value = "注册")
@Mapping(value = "/register", consumes = "application/json")
public Result<Void> register(@Validated UserRequest user) {
userService.register(user);
return Result.succeed();
}
@ApiOperation(value = "审批")
@Mapping(value = "/approval/{userId}")
public Result<Void> approval(Long userId) {
userService.approval(userId);
return Result.succeed();
}
}

27
src/main/java/com/yeguang/handler/ModelPredictResponseListTypeHandler.java

@ -0,0 +1,27 @@
package com.yeguang.handler;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yeguang.model.ModelPredictResponse;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
public class ModelPredictResponseListTypeHandler extends JacksonTypeHandler {
public ModelPredictResponseListTypeHandler(Class<?> type) {
super(type);
}
@Override
public Object getResult(ResultSet rs, String columnName) throws SQLException {
String json = rs.getString(columnName);
try {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(json, new TypeReference<List<ModelPredictResponse>>(){});
} catch (Exception e) {
throw new SQLException("Error deserializing recognition_data", e);
}
}
}

14
src/main/java/com/yeguang/mapper/RecordContentMapper.java

@ -0,0 +1,14 @@
package com.yeguang.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yeguang.model.entity.RecordContentEntity;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface RecordContentMapper extends BaseMapper<RecordContentEntity> {
List<RecordContentEntity> selectListByRecordId(@Param("recordId") Long recordId);
}

17
src/main/java/com/yeguang/mapper/RecordMapper.java

@ -0,0 +1,17 @@
package com.yeguang.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yeguang.model.RecordDetail;
import com.yeguang.model.RecordItem;
import com.yeguang.model.entity.RecordEntity;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface RecordMapper extends BaseMapper<RecordEntity> {
RecordDetail getRecordDetail(long id);
Page<RecordItem> pageList(Page<RecordItem> page, Object o);
}

13
src/main/java/com/yeguang/mapper/UserMapper.java

@ -0,0 +1,13 @@
package com.yeguang.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yeguang.model.LoginUser;
import com.yeguang.model.entity.UserEntity;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface UserMapper extends BaseMapper<UserEntity> {
LoginUser selectLoginUserByPhone(@Param("phone") String phone);
}

38
src/main/java/com/yeguang/model/CreateRecordRequest.java

@ -0,0 +1,38 @@
package com.yeguang.model;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.noear.solon.core.handle.UploadedFile;
import org.noear.solon.validation.annotation.*;
@Data
@AllArgsConstructor
@NoArgsConstructor
@ApiModel("创建记录请求")
public class CreateRecordRequest {
@ApiModelProperty(value = "图片列表", required = true)
@NotEmpty(message = "文件不能为空")
private UploadedFile[] files;
@ApiModelProperty(value = "模型名称", required = true)
@NotBlank(message = "模型名称不能为空")
private String modelName;
@ApiModelProperty(value = "重叠度", required = true)
@NotNull(message = "IOU 不能为空")
@Min(value = 0, message = "IOU 不能小于 0")
@Max(value = 1, message = "IOU 不能大于 1")
private Float iou;
@ApiModelProperty(value = "置信度", required = true)
@NotNull(message = "Conf 不能为空")
@Min(value = 0, message = "Conf 不能小于 0")
@Max(value = 1, message = "Conf 不能大于 1")
private Float conf;
@ApiModelProperty(value = "任务名称", required = true)
@NotBlank(message = "任务名称不能为空")
private String taskName;
}

21
src/main/java/com/yeguang/model/LoginRequest.java

@ -0,0 +1,21 @@
package com.yeguang.model;
import com.yeguang.common.UserConstants;
import lombok.Data;
import org.noear.solon.validation.annotation.Length;
import org.noear.solon.validation.annotation.NotBlank;
import java.io.Serializable;
@Data
public class LoginRequest implements Serializable {
@NotBlank(message = "{user.phone.not.blank}")
private String phone;
@NotBlank(message = "{user.password.not.blank}")
@Length(min = UserConstants.PASSWORD_MIN_LENGTH, max = UserConstants.PASSWORD_MAX_LENGTH, message = "{user.password.length.valid}")
private String password;
private String grantType = "password";
}

37
src/main/java/com/yeguang/model/LoginResponse.java

@ -0,0 +1,37 @@
package com.yeguang.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
/**
* 登录验证信息
*/
@Data
public class LoginResponse {
/**
* 授权令牌
*/
@JsonProperty("access_token")
private String accessToken;
/**
* 刷新令牌
*/
@JsonProperty("refresh_token")
private String refreshToken;
/**
* 授权令牌 access_token 的有效期
*/
@JsonProperty("expire_in")
private Long expireIn;
/**
* 刷新令牌 refresh_token 的有效期
*/
@JsonProperty("refresh_expire_in")
private Long refreshExpireIn;
}

85
src/main/java/com/yeguang/model/LoginUser.java

@ -0,0 +1,85 @@
package com.yeguang.model;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* @ClassName LoginUser
* @author: zhangyeguang
* @create: 2024-09-01 09:28
* @Version 1.0
* @description:
**/
@Data
@NoArgsConstructor
public class LoginUser implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 用户ID
*/
@JsonSerialize(using = ToStringSerializer.class)
private Long userId;
/**
* 部门
*/
private String unitName;
/**
* 用户唯一标识
*/
private String token;
/**
* 登录时间
*/
private Long loginTime;
/**
* 过期时间
*/
private Long expireTime;
/**
* 登录IP地址
*/
private String ipaddr;
/**
* 用户名
*/
private String username;
/**
* 用户昵称
*/
private String realName;
/**
* 手机号
*/
private String phone;
@JsonIgnore
private String password;
private String email;
/**
* 头像
*/
private String avatar;
private LocalDateTime createTime;
private Integer status;
}

12
src/main/java/com/yeguang/model/ModelPredictResponse.java

@ -0,0 +1,12 @@
package com.yeguang.model;
import lombok.Data;
@Data
public class ModelPredictResponse {
private Integer classify;
private String name;
private int[][] points;
private Float length;
}

23
src/main/java/com/yeguang/model/PageRequest.java

@ -0,0 +1,23 @@
package com.yeguang.model;
import lombok.Data;
/**
* @ClassName PageInfo
* @author: zhangyeguang
* @create: 2024-09-02 15:21
* @Version 1.0
* @description:
**/
@Data
public class PageRequest<T> implements java.io.Serializable {
private static final long serialVersionUID = 1L;
private int pageNum;
private int pageSize;
private String oderByColumn;
private T request;
}

33
src/main/java/com/yeguang/model/PageResult.java

@ -0,0 +1,33 @@
package com.yeguang.model;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.List;
/**
* @ClassName PageResult
* @author: zhangyeguang
* @create: 2024-09-02 15:22
* @Version 1.0
* @description:
**/
@Data
@Accessors(chain = true)
public class PageResult<T> {
private int total;
private int pageSize;
private int pageNum;
private int pages;
private List<T> records;
public static <T> PageResult<T> of(int total, int pageSize, int pageNum, List<T> records) {
PageResult<T> pageResult = new PageResult<>();
pageResult.setTotal(total);
pageResult.setPageSize(pageSize);
pageResult.setPageNum(pageNum);
pageResult.setPages(total==0||pageSize==0?0:total % pageSize == 0 ? total / pageSize : total / pageSize + 1);
pageResult.setRecords(records);
return pageResult;
}
}

31
src/main/java/com/yeguang/model/RecordContentExcel.java

@ -0,0 +1,31 @@
package com.yeguang.model;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
@Data
public class RecordContentExcel {
@ExcelProperty("文件名")
private String name;
@ExcelProperty("总喙数")
private int beakNum;
@ExcelProperty("总柄数")
private int handleNum;
@ExcelProperty("总角果数")
private int rapeNum;
@ExcelProperty("置信度")
private float conf;
@ExcelProperty("重叠度")
private float iou;
@ExcelProperty("比例系数")
private int scale;
@ExcelProperty("角果长")
private float rapeLength;
@ExcelProperty("喙长")
private float beakLength;
@ExcelProperty("柄长")
private float handleLength;
}

27
src/main/java/com/yeguang/model/RecordDetail.java

@ -0,0 +1,27 @@
package com.yeguang.model;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.yeguang.model.entity.RecordContentEntity;
import lombok.Data;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
import java.util.List;
@Data
@Accessors(chain = true)
public class RecordDetail {
private Long id;
private String name;
private int imageNum;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;
private List<RecordContentEntity> content;
}

31
src/main/java/com/yeguang/model/RecordExcel.java

@ -0,0 +1,31 @@
package com.yeguang.model;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
@Data
public class RecordExcel {
@ExcelProperty("总图片数")
private int imageNum;
@ExcelProperty("总喙数")
private int beakNum;
@ExcelProperty("总柄数")
private int handleNum;
@ExcelProperty("总角果数")
private int rapeNum;
@ExcelProperty("平均角果数")
private float avgRapeNum;
@ExcelProperty("最大角果数")
private int maxRapeNum;
@ExcelProperty("最少角果数")
private int minRapeNum;
@ExcelProperty("平均角果长度")
private float avgRapeLength;
@ExcelProperty("平均喙长度")
private float avgBeakLength;
@ExcelProperty("平均柄长度")
private float avgHandleLength;
}

29
src/main/java/com/yeguang/model/RecordItem.java

@ -0,0 +1,29 @@
package com.yeguang.model;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
@Data
@Accessors(chain = true)
public class RecordItem {
private String recordName;
private Long recordId;
private int imageNum;
private String userName;
private Long userId;
private String unitName;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
private String phone;
}

26
src/main/java/com/yeguang/model/RecordModel.java

@ -0,0 +1,26 @@
package com.yeguang.model;
import lombok.Data;
import java.util.List;
@Data
public class RecordModel {
private List<String> loaded_models;
private List<ModelDetail> models;
@Data
public static class ModelDetail {
private float conf;
private float iou;
private String name;
private String desc;
private String file;
}
}

10
src/main/java/com/yeguang/model/Scale.java

@ -0,0 +1,10 @@
package com.yeguang.model;
import lombok.Data;
@Data
public class Scale {
private int scale;
private float size;
private int[][] points;
}

19
src/main/java/com/yeguang/model/UpdateScaleRequest.java

@ -0,0 +1,19 @@
package com.yeguang.model;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel("更新比例尺")
public class UpdateScaleRequest {
@ApiModelProperty("记录id")
private long recordId;
@ApiModelProperty("记录内容id")
private long recordContentId;
@ApiModelProperty("比例尺类型,1=统一、2=自用、3=延用")
private int scaleType;
@ApiModelProperty("比例尺")
private Scale scale;
}

42
src/main/java/com/yeguang/model/UserRequest.java

@ -0,0 +1,42 @@
package com.yeguang.model;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.noear.solon.validation.annotation.Pattern;
import org.noear.solon.validation.annotation.Size;
/**
* @ClassName UserRequest
* @author: zhangyeguang
* @create: 2024-08-31 10:00
* @Version 1.0
* @description:
**/
@Data
@NoArgsConstructor
public class UserRequest {
/**
* 用户昵称
*/
@Size(min = 2, max = 30, message = "用户姓名长度不能超过{max}个字符")
private String realName;
/**
* 手机号码
*/
@Pattern(value = "^1[34578]\\d{9}$", message = "手机号码格式不正确")
private String phone;
/**
* 密码
*/
private String password;
private String unitName;
}

44
src/main/java/com/yeguang/model/entity/RecordContentEntity.java

@ -0,0 +1,44 @@
package com.yeguang.model.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import com.yeguang.model.ModelPredictResponse;
import com.yeguang.model.Scale;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.List;
@Data
@Accessors(chain = true)
@TableName(value = "record_content", autoResultMap = true)
public class RecordContentEntity {
@TableId(type = IdType.AUTO)
private Long id;
private Long recordId;
private String imageName;
private String imageUrl;
@TableField(typeHandler = JacksonTypeHandler.class)
private List<ModelPredictResponse> recognitionData;
private int siliquaNum;
private int handleNum;
private int beakNum;
//比例尺类型 ,1=统一/2=自用/3=延用/0=无
private int scaleType;
//比例
@TableField(typeHandler = JacksonTypeHandler.class)
private Scale scale;
private float conf;
private float iou;
}

28
src/main/java/com/yeguang/model/entity/RecordEntity.java

@ -0,0 +1,28 @@
package com.yeguang.model.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
@Data
@Accessors(chain = true)
@TableName("record_info")
public class RecordEntity {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private Long userId;
private int imageNum;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}

28
src/main/java/com/yeguang/model/entity/UserEntity.java

@ -0,0 +1,28 @@
package com.yeguang.model.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
@Data
@TableName("siliqua_user")
@Accessors(chain = true)
public class UserEntity {
@TableId
private long id;
private String phone;
private String password;
private String realName;
private String unitName;
private LocalDateTime createTime;
private int status;
}

50
src/main/java/com/yeguang/remote/RecognitionModelClient.java

@ -0,0 +1,50 @@
package com.yeguang.remote;
import com.alibaba.fastjson2.JSON;
import com.yeguang.model.ModelPredictResponse;
import com.yeguang.model.RecordModel;
import lombok.extern.slf4j.Slf4j;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
import org.noear.solon.net.http.HttpUtils;
import java.io.File;
import java.io.IOException;
import java.util.List;
@Slf4j
@Component
public class RecognitionModelClient {
@Inject("${recognition.model.url}")
private String modelUrl;
public List<ModelPredictResponse> predict(File file, String modeName, float lou, float conf) {
try {
if (!file.exists()) {
throw new IllegalArgumentException("文件不存在: " + file.getAbsolutePath());
}
String response = HttpUtils.http(modelUrl + "/api/predict")
.multipart(true) // 开启 multipart/form-data 模式
.data("image", file)
.data("model_name", modeName)
.data("lou", String.valueOf(lou))
.data("conf", String.valueOf(conf))
.post();
return JSON.parseArray(response, ModelPredictResponse.class);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public RecordModel models() {
try {
return HttpUtils.http(modelUrl + "/api/models").getAs(RecordModel.class);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

5
src/main/java/com/yeguang/service/InfoService.java

@ -0,0 +1,5 @@
package com.yeguang.service;
public interface InfoService {
String hello();
}

13
src/main/java/com/yeguang/service/InfoServiceImpl.java

@ -0,0 +1,13 @@
package com.yeguang.service;
import org.noear.solon.annotation.Component;
@Component
public class InfoServiceImpl implements InfoService {
@Override
public String hello() {
return "hello solon";
}
}

26
src/main/java/com/yeguang/service/RecordService.java

@ -0,0 +1,26 @@
package com.yeguang.service;
import com.yeguang.model.*;
import com.yeguang.model.entity.RecordContentEntity;
import org.noear.solon.core.handle.Context;
public interface RecordService {
String createRecordAsync(CreateRecordRequest request);
Float getTaskProgress(String taskId);
RecordDetail getRecordDetail(Long recordId);
void exportRecord(Long recordId, Context context);
PageResult<RecordItem> getPages(PageRequest pageRequest);
void updateRecordContent(RecordContentEntity recordContentEntity);
RecordModel getModels();
void updateScale(UpdateScaleRequest request);
}

299
src/main/java/com/yeguang/service/RecordServiceImpl.java

@ -0,0 +1,299 @@
package com.yeguang.service;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.builder.ExcelWriterSheetBuilder;
import com.alibaba.excel.write.merge.OnceAbsoluteMergeStrategy;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yeguang.mapper.RecordContentMapper;
import com.yeguang.mapper.RecordMapper;
import com.yeguang.model.*;
import com.yeguang.model.entity.RecordContentEntity;
import com.yeguang.remote.RecognitionModelClient;
import com.yeguang.service.handler.RecordTaskHandler;
import com.yeguang.service.handler.RecordTaskManager;
import com.yeguang.service.handler.RecordTaskProcessor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.VerticalAlignment;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
import org.noear.solon.core.handle.Context;
import org.noear.solon.data.annotation.Tran;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.URLEncoder;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ThreadPoolExecutor;
@Slf4j
@Component
public class RecordServiceImpl implements RecordService {
@Inject("customThreadPool")
private ThreadPoolExecutor customThreadPool;
@Inject
private RecordTaskProcessor taskProcessor;
@Inject
private RecordMapper recordMapper;
@Inject
private RecordContentMapper recordContentMapper;
@Inject
private RecognitionModelClient recognitionModelClient;
@Override
public String createRecordAsync(CreateRecordRequest request) {
String taskId = UUID.randomUUID().toString();
RecordTaskHandler recordTaskHandler = new RecordTaskHandler(customThreadPool, taskProcessor);
recordTaskHandler.createTask(request, taskId);
RecordTaskManager.addRecordTask(taskId, recordTaskHandler);
return taskId;
}
@Override
public Float getTaskProgress(String taskId) {
RecordTaskHandler recordTask = RecordTaskManager.getRecordTask(taskId);
if (recordTask == null) {
return 100f;
}
if (recordTask.getRecordTask() == null) {
return 100f;
}
return recordTask.getRecordTask().getProgress();
}
@Override
public RecordDetail getRecordDetail(Long recordId) {
return recordMapper.getRecordDetail(recordId);
}
@Override
public void exportRecord(Long recordId, Context context) {
RecordDetail recordDetail = this.getRecordDetail(recordId);
List<RecordContentEntity> content = recordDetail.getContent();
if (CollectionUtils.isNotEmpty(content)) {
RecordExcel recordExcel = new RecordExcel();
recordExcel.setImageNum(recordDetail.getImageNum());
int beakNum = 0;
int rapeNum = 0;
int handleNum = 0;
int rapeMax = 0;
int rapeMin = 10000;
List<Double> avgBeakLen = new ArrayList<>();
List<Double> avgRapeLen = new ArrayList<>();
List<Double> avgHandleLen = new ArrayList<>();
List<RecordContentExcel> recordContentExcelList = new ArrayList<>();
List<OnceAbsoluteMergeStrategy> mergeStrategies = new ArrayList<>();
int startRowIndex = 1;
int endRowIndex = 0;
//处理记录详情数据
for (RecordContentEntity record : content) {
//统计各分类总数
beakNum += record.getBeakNum();
rapeNum += record.getSiliquaNum();
handleNum += record.getHandleNum();
if (record.getSiliquaNum() > rapeMax) {
rapeMax = record.getSiliquaNum();
}
if (record.getSiliquaNum() < rapeMin) {
rapeMin = record.getSiliquaNum();
}
List<Float> rapeLenList = new ArrayList<>();
List<Float> beakLenList = new ArrayList<>();
List<Float> handleLenList = new ArrayList<>();
List<ModelPredictResponse> recognitionData = record.getRecognitionData();
Scale scaleDto = record.getScale();
//把识别数据进行分类
float size = scaleDto.getSize();
int scale = scaleDto.getScale();
for (ModelPredictResponse item : recognitionData) {
float len;
if (size > 0 && scale > 0) {
BigDecimal b = new BigDecimal(item.getLength() * size / scale);
len = b.setScale(2, RoundingMode.HALF_UP).floatValue();
} else {
len = item.getLength();
}
switch (item.getClassify()) {
case 0:
rapeLenList.add(len);
break;
case 1:
beakLenList.add(len);
break;
default:
handleLenList.add(len);
}
}
int len = Math.max(rapeLenList.size(), Math.max(handleLenList.size(), beakLenList.size()));
//创建表格行记录
for (int i = 0; i < len; i++) {
endRowIndex++;
RecordContentExcel contentExcel = new RecordContentExcel();
//需要合并的列开始
contentExcel.setName(record.getImageName());
contentExcel.setBeakNum(record.getBeakNum());
contentExcel.setRapeNum(record.getSiliquaNum());
contentExcel.setHandleNum(record.getHandleNum());
contentExcel.setScale(scale);
contentExcel.setConf(record.getConf());
contentExcel.setIou(record.getIou());
//需要合并的列结束
recordContentExcelList.add(contentExcel);
if (i <= rapeLenList.size() - 1) {
contentExcel.setRapeLength(rapeLenList.get(i));
}
if (i <= beakLenList.size() - 1) {
contentExcel.setBeakLength(beakLenList.get(i));
}
if (i <= handleLenList.size() - 1) {
contentExcel.setHandleLength(handleLenList.get(i));
}
}
//创建单元格的合并策略, 前7列数据需要合并
for (int i = 0; i <= 6; i++) {
OnceAbsoluteMergeStrategy mergeStrategy = new OnceAbsoluteMergeStrategy(
startRowIndex,
endRowIndex,
i, i);
mergeStrategies.add(mergeStrategy);
}
startRowIndex += len;
avgHandleLen.add(handleLenList.stream().mapToDouble(Float::doubleValue).average().orElse(0.0));
avgRapeLen.add(rapeLenList.stream().mapToDouble(Float::doubleValue).average().orElse(0.0));
avgBeakLen.add(beakLenList.stream().mapToDouble(Float::doubleValue).average().orElse(0.0));
}
recordExcel.setBeakNum(beakNum);
recordExcel.setRapeNum(rapeNum);
recordExcel.setHandleNum(handleNum);
recordExcel.setMaxRapeNum(rapeMax);
recordExcel.setMinRapeNum(rapeMin);
DecimalFormat dfnum = new DecimalFormat("##0.00");
float avgRapeNum = Float.parseFloat(dfnum.format((float) rapeNum / content.size()));
recordExcel.setAvgRapeNum(avgRapeNum);
double v = avgBeakLen.stream().mapToDouble(Double::doubleValue).average().orElse(0.0);
BigDecimal b = new BigDecimal(v);
recordExcel.setAvgBeakLength(b.setScale(2, RoundingMode.HALF_UP).floatValue());
double r = avgRapeLen.stream().mapToDouble(Double::doubleValue).average().orElse(0.0);
BigDecimal b1 = new BigDecimal(r);
recordExcel.setAvgRapeLength(b1.setScale(2, RoundingMode.HALF_UP).floatValue());
double h = avgHandleLen.stream().mapToDouble(Double::doubleValue).average().orElse(0.0);
BigDecimal b2 = new BigDecimal(h);
recordExcel.setAvgHandleLength(b2.setScale(2, RoundingMode.HALF_UP).floatValue());
try {
context.contentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
String fileName = URLEncoder.encode(recordDetail.getName(), "UTF-8").replaceAll("\\+", "%20");
context.headerSet("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
//创建 Cell 样式
WriteCellStyle writeCellStyle = new WriteCellStyle();
//设置垂直对齐方式
writeCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
//设置水平对齐方式
writeCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
ExcelWriter writer = EasyExcel.write(context.outputStream()).autoCloseStream(false).build();
//总览表格的sheet
WriteSheet recordSheet = EasyExcel.writerSheet("总览").head(RecordExcel.class).build();
//详情表格的sheet
ExcelWriterSheetBuilder sheetBuilder = EasyExcel.writerSheet("详情")
.registerWriteHandler(new HorizontalCellStyleStrategy(null, writeCellStyle))
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
.head(RecordContentExcel.class);
//添加单元格合并策略
for (OnceAbsoluteMergeStrategy mergeStrategy : mergeStrategies) {
sheetBuilder.registerWriteHandler(mergeStrategy);
}
WriteSheet contentSheet = sheetBuilder.build();
writer.write(Collections.singletonList(recordExcel), recordSheet);
writer.write(recordContentExcelList, contentSheet);
writer.finish();
} catch (Exception e) {
log.error("下载文件失败", e);
}
}
}
@Override
public PageResult<RecordItem> getPages(PageRequest pageRequest) {
Page<RecordItem> page = new Page(pageRequest.getPageNum(), pageRequest.getPageSize());
Page<RecordItem> result = recordMapper.pageList(page, null);
return PageResult.of((int) result.getTotal(), (int) result.getSize(), (int) result.getCurrent(), result.getRecords());
}
@Override
public void updateRecordContent(RecordContentEntity recordContentEntity) {
recordContentMapper.updateById(recordContentEntity);
}
@Override
public RecordModel getModels() {
return recognitionModelClient.models();
}
@Tran
@Override
public void updateScale(UpdateScaleRequest request) {
QueryWrapper<RecordContentEntity> queryWrapper = Wrappers.query();
RecordContentEntity own = null;
RecordContentEntity other = null;
switch (request.getScaleType()) {
case 1:
own = new RecordContentEntity();
own.setScale(request.getScale());
own.setScaleType(request.getScaleType());
own.setId(request.getRecordContentId());
other = new RecordContentEntity().setScale(request.getScale());
queryWrapper.eq("record_id", request.getRecordId());
queryWrapper.eq("scale_type", 3);
break;
case 2:
own = recordContentMapper.selectById(request.getRecordContentId());
if (own.getScaleType() == 1) {
queryWrapper.eq("record_id", own.getRecordId());
queryWrapper.eq("scale_type", 3);
other = new RecordContentEntity().setScaleType(0).setScale(null);
}
own.setScale(request.getScale());
own.setScaleType(request.getScaleType());
break;
case 3:
default:
own = new RecordContentEntity().setScaleType(3);
queryWrapper.eq("record_id", request.getRecordId());
queryWrapper.eq("scale_type", 1);
RecordContentEntity recordContentEntity = recordContentMapper.selectOne(queryWrapper);
if (recordContentEntity != null) {
own.setScale(recordContentEntity.getScale());
}
own.setId(request.getRecordContentId());
}
recordContentMapper.updateById(own);
if (other != null) {
recordContentMapper.update(other, queryWrapper);
}
}
}

14
src/main/java/com/yeguang/service/UserService.java

@ -0,0 +1,14 @@
package com.yeguang.service;
import com.yeguang.model.LoginRequest;
import com.yeguang.model.LoginResponse;
import com.yeguang.model.UserRequest;
public interface UserService {
LoginResponse login(LoginRequest loginBody);
void register(UserRequest user);
void approval(Long userId);
}

73
src/main/java/com/yeguang/service/UserServiceImpl.java

@ -0,0 +1,73 @@
package com.yeguang.service;
import cn.dev33.satoken.secure.BCrypt;
import cn.dev33.satoken.stp.StpUtil;
import com.yeguang.common.BizCode;
import com.yeguang.common.BusinessException;
import com.yeguang.common.UserConstants;
import com.yeguang.mapper.UserMapper;
import com.yeguang.model.LoginRequest;
import com.yeguang.model.LoginResponse;
import com.yeguang.model.LoginUser;
import com.yeguang.model.UserRequest;
import com.yeguang.model.entity.UserEntity;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.solon.annotation.Db;
import org.noear.solon.annotation.Component;
import java.time.LocalDateTime;
@Slf4j
@Component
public class UserServiceImpl implements UserService {
@Db
private UserMapper userMapper;
// @Inject
// private EmailUtil emailUtil;
@Override
public LoginResponse login(LoginRequest loginBody) {
String phone = loginBody.getPhone();
String password = loginBody.getPassword();
LoginUser user = userMapper.selectLoginUserByPhone(phone);
if (user == null) {
throw new BusinessException(BizCode.USER_NOT_FOUND);
}
if (user.getStatus() != 1) {
throw new BusinessException(BizCode.USER_STATUS_ERROR);
}
boolean checkPw = BCrypt.checkpw(password, user.getPassword());
if (!checkPw) {
throw new BusinessException(BizCode.PASSWORD_ERROR);
}
StpUtil.login(user.getUserId());
user.setLoginTime(System.currentTimeMillis());
StpUtil.getSession().set(UserConstants.SYS_SESSION, user);
LoginResponse loginVO = new LoginResponse();
loginVO.setAccessToken(StpUtil.getTokenValue());
loginVO.setExpireIn(StpUtil.getTokenTimeout());
return loginVO;
}
@Override
public void register(UserRequest user) {
UserEntity userEntity = new UserEntity()
.setCreateTime(LocalDateTime.now())
.setStatus(2)
.setRealName(user.getRealName())
.setUnitName(user.getUnitName())
.setPhone(user.getPhone())
.setPassword(BCrypt.hashpw(user.getPassword()));
userMapper.insert(userEntity);
// emailUtil.sendEmail(null, user.getPhone(), user.getUnitName(), user.getRealName());
}
@Override
public void approval(Long userId) {
UserEntity userEntity = userMapper.selectById(userId);
userEntity.setStatus(1);
userMapper.updateById(userEntity);
}
}

94
src/main/java/com/yeguang/service/handler/RecordTaskHandler.java

@ -0,0 +1,94 @@
package com.yeguang.service.handler;
import com.yeguang.common.thread.MonitoredRunnable;
import com.yeguang.model.CreateRecordRequest;
import com.yeguang.model.LoginUser;
import com.yeguang.util.LoginUtil;
import lombok.Data;
import lombok.SneakyThrows;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.noear.solon.core.handle.UploadedFile;
import java.io.File;
import java.net.URL;
import java.util.concurrent.ThreadPoolExecutor;
@Data
@Accessors(chain = true)
@Slf4j
public class RecordTaskHandler {
private RecordTask recordTask;
private ThreadPoolExecutor theadPoolExecutor;
private RecordTaskProcessor processor;
public RecordTaskHandler(ThreadPoolExecutor threadPoolExecutor, RecordTaskProcessor processor) {
this.theadPoolExecutor = threadPoolExecutor;
this.processor = processor;
}
protected void processTask() {
theadPoolExecutor.execute(new MonitoredRunnable(() -> {
try {
processor.handle(recordTask);
log.info("任务完成,任务ID:{}", recordTask.getTaskId());
} finally {
for (File file : recordTask.getFiles()) {
log.info("删除临时文件:{}", file.getAbsolutePath());
if (file.exists()) {
if (!file.delete())
log.error("删除临时文件失败:{}", file.getAbsolutePath());
}
}
RecordTaskManager.removeRecordTask(recordTask.getTaskId());
recordTask = null;
}
}));
}
@SneakyThrows
public void createTask(CreateRecordRequest request, String taskId) {
URL resource = RecordTaskHandler.class.getClassLoader().getResource("");
assert resource != null;
String path = resource.getPath();
File[] files = new File[request.getFiles().length];
for (int i = 0; i < request.getFiles().length; i++) {
UploadedFile file = request.getFiles()[i];
File file1 = new File(path + file.getName());
file.transferTo(file1);
files[i] = file1;
}
this.recordTask = new RecordTask()
.setFiles(files)
.setModelName(request.getModelName())
.setIou(request.getIou())
.setConf(request.getConf())
.setProgress(5f)
.setTaskName(request.getTaskName())
.setTaskId(taskId)
.setLoginUser(LoginUtil.getLoginUser());
processTask();
}
@Data
@Accessors(chain = true)
public static class RecordTask {
private String taskId;
private File[] files;
private String modelName;
private Float iou;
private Float conf;
private Float progress;
private String taskName;
private LoginUser loginUser;
}
}

21
src/main/java/com/yeguang/service/handler/RecordTaskManager.java

@ -0,0 +1,21 @@
package com.yeguang.service.handler;
import java.util.concurrent.ConcurrentHashMap;
public class RecordTaskManager {
private static final ConcurrentHashMap<String, RecordTaskHandler> recordTasks = new ConcurrentHashMap<>();
public static void addRecordTask(String taskId, RecordTaskHandler handler) {
recordTasks.put(taskId, handler);
}
public static RecordTaskHandler getRecordTask(String taskId) {
return recordTasks.get(taskId);
}
public static void removeRecordTask(String taskId) {
recordTasks.remove(taskId);
}
}

115
src/main/java/com/yeguang/service/handler/RecordTaskProcessor.java

@ -0,0 +1,115 @@
package com.yeguang.service.handler;
import com.yeguang.mapper.RecordContentMapper;
import com.yeguang.mapper.RecordMapper;
import com.yeguang.model.ModelPredictResponse;
import com.yeguang.model.entity.RecordContentEntity;
import com.yeguang.model.entity.RecordEntity;
import com.yeguang.remote.RecognitionModelClient;
import com.yeguang.util.HuaweiObs;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.ibatis.solon.annotation.Db;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
import org.noear.solon.data.annotation.Tran;
import java.io.File;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.List;
@Slf4j
@Component
public class RecordTaskProcessor {
@Db
private RecordMapper recordMapper;
@Db
private RecordContentMapper recordContentMapper;
@Inject
private RecognitionModelClient modelClient;
@Inject
private HuaweiObs huaweiObs;
@Tran
public void handle(@NonNull RecordTaskHandler.RecordTask recordTask) {
log.info("开始处理任务:{}", recordTask.getTaskId());
float progress = 5f;
RecordEntity recordEntity = new RecordEntity().setName(recordTask.getTaskName())
.setImageNum(recordTask.getFiles().length)
.setUserId(recordTask.getLoginUser().getUserId())
.setCreateTime(LocalDateTime.now())
.setUpdateTime(LocalDateTime.now());
recordMapper.insert(recordEntity);
recordTask.setProgress(progress);
for (File file : recordTask.getFiles()) {
List<ModelPredictResponse> predict = modelClient.predict(file, recordTask.getModelName(), recordTask.getIou(), recordTask.getConf());
int handleNum = 0;
int rapeNum = 0;
int beakNum = 0;
for (ModelPredictResponse item : predict) {
item.setLength(getMaxLength(item.getPoints()));
switch (item.getClassify()) {
case 0:
rapeNum++;
break;
case 1:
beakNum++;
break;
default:
handleNum++;
}
}
progress += (float) (100 / recordTask.getFiles().length - 10);
recordTask.setProgress(progress);
if (!CollectionUtils.isEmpty(predict)) {
String imageUrl;
try {
imageUrl = huaweiObs.uploadFile(file);
progress += 2f;
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
file.deleteOnExit();
}
RecordContentEntity recordContentEntity = new RecordContentEntity()
.setRecordId(recordEntity.getId()).setImageUrl(imageUrl)
.setRecognitionData(predict)
.setImageName(file.getName())
.setConf(recordTask.getConf())
.setIou(recordTask.getIou())
.setHandleNum(handleNum)
.setBeakNum(beakNum)
.setSiliquaNum(rapeNum);
recordContentMapper.insert(recordContentEntity);
progress += 3f;
}
}
}
private static double calculateDistance(int x1, int y1, int x2, int y2) {
return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
}
private float getMaxLength(int[][] points) {
// 计算四条边的长度
double ab = calculateDistance(points[0][0], points[0][1], points[1][0], points[1][1]); // AB
double bc = calculateDistance(points[1][0], points[1][1], points[2][0], points[2][1]); // BC
double cd = calculateDistance(points[2][0], points[2][1], points[3][0], points[3][1]); // CD
double da = calculateDistance(points[3][0], points[3][1], points[0][0], points[0][1]); // DA
double max = Math.max(Math.max(ab, bc), Math.max(cd, da));
BigDecimal b = new BigDecimal(max);
// 找出最大边长
return b.setScale(2, RoundingMode.HALF_UP).floatValue();
}
}

52
src/main/java/com/yeguang/util/ExcelBigNumberConvert.java

@ -0,0 +1,52 @@
package com.yeguang.util;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
import lombok.extern.slf4j.Slf4j;
import java.math.BigDecimal;
/**
* 大数值转换
* Excel 数值长度位15位 大于15位的数值转换位字符串
*
* @author Lion Li
*/
@Slf4j
public class ExcelBigNumberConvert implements Converter<Long> {
@Override
public Class<Long> supportJavaTypeKey() {
return Long.class;
}
@Override
public CellDataTypeEnum supportExcelTypeKey() {
return CellDataTypeEnum.STRING;
}
@Override
public Long convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
return Convert.toLong(cellData.getData());
}
@Override
public WriteCellData<Object> convertToExcelData(Long object, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
if (ObjectUtil.isNotNull(object)) {
String str = Convert.toStr(object);
if (str.length() > 15) {
return new WriteCellData<>(str);
}
}
WriteCellData<Object> cellData = new WriteCellData<>(new BigDecimal(object));
cellData.setType(CellDataTypeEnum.NUMBER);
return cellData;
}
}

303
src/main/java/com/yeguang/util/HuaweiObs.java

@ -0,0 +1,303 @@
package com.yeguang.util;
import com.alibaba.fastjson2.JSON;
import com.obs.services.ObsClient;
import com.obs.services.exception.ObsException;
import com.obs.services.model.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
import org.noear.solon.core.handle.UploadedFile;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* @author Lenovo
*/
@Slf4j
@Component
public class HuaweiObs {
//Access Key Id
//private String ak = "Access Key Id";
//Secret Access Key
//private String sk = "Secret Access Key";
//桶名称
private String bucketName = "jg-iot";
// 终端节点访问Endpoint
//private String endpoint = "Endpoint";
// 文件目录
private String prifix = "/recognition";
// 访问域名 在域名后面或文件目录前加“/”
private String path = "/recognition/";
@Inject
private ObsClient obsClient;
/**
* 文件上传
*
* @param file
* @return
* @throws IOException
*/
public String upload(UploadedFile file) throws Exception {
//ObsClient obsClient = null;
Calendar cal = Calendar.getInstance();
int year = cal.get(Calendar.YEAR);
int month = cal.get(Calendar.MONTH);
int day = cal.get(Calendar.DATE);
String fileName = Md5Util.calculateMD5(file.getContent());
String objectName = prifix + "/" + year + "/" + month + "/" + day + "/" + fileName;
//obsClient = new ObsClient(ak, sk, endpoint);
PutObjectResult response = obsClient.putObject(bucketName, objectName, file.getContent());
log.info(JSON.toJSONString(response));
// 可选:调用成功后,记录调用成功的HTTP状态码和服务端请求ID
int statusCode = response.getStatusCode();
if (200 == statusCode) {
return response.getObjectUrl();
}
return null;
}
/**
* 上传文件--流式
*
* @param fileName 上传文件名称
* @param is 文件流
* @return
*/
public String uploadFile(String fileName, InputStream is) {
try {
if (is.available() == 0) {
log.error("InputStream is empty.");
return null;
}
} catch (IOException e) {
throw new RuntimeException(e);
}
Calendar cal = Calendar.getInstance();
int year = cal.get(Calendar.YEAR);
int month = cal.get(Calendar.MONTH);
int day = cal.get(Calendar.DATE);
String objectName = prifix + "/" + year + "/" + month + "/" + day + "/" + fileName;
PutObjectResult response = obsClient.putObject(bucketName, objectName, is);
log.info(JSON.toJSONString(response));
// 可选:调用成功后,记录调用成功的HTTP状态码和服务端请求ID
int statusCode = response.getStatusCode();
if (200 == statusCode) {
return response.getObjectUrl();
}
return null;
}
public String uploadFile(File file) {
try {
String subbfix = file.getName().substring(file.getName().indexOf("."));
String fileName = UUID.randomUUID().toString().replaceAll("-", "") + subbfix;
Calendar cal = Calendar.getInstance();
int year = cal.get(Calendar.YEAR);
int month = cal.get(Calendar.MONTH);
int day = cal.get(Calendar.DATE);
String objectName = prifix + "/" + year + "/" + month + "/" + fileName;
PutObjectRequest putObjectRequest = new PutObjectRequest();
putObjectRequest.setFile(file);
putObjectRequest.setBucketName(bucketName);
putObjectRequest.setObjectKey(objectName);
PutObjectResult response = obsClient.putObject(putObjectRequest);
log.info(JSON.toJSONString(response));
// 可选:调用成功后,记录调用成功的HTTP状态码和服务端请求ID
int statusCode = response.getStatusCode();
if (200 == statusCode) {
return response.getObjectUrl();
}
} catch (ObsException e) {
log.error("文件上传失败:http code={},error code={},error message={}", e.getResponseCode(), e.getErrorCode(), e.getErrorMessage(), e);
} catch (Exception e) {
throw new RuntimeException(e);
}
return null;
}
/**
* 上传文件--字节数组
*
* @param fileName 上传文件名称
* @param fileType 文件路径
* @param is 文件流
* @return
*/
public boolean uploadFileByte(String fileName, FileType fileType, byte[] is) {
//ObsClient obsClient = null;
try {
String objectName = fileType.getType().concat("/").concat(fileName);
//obsClient = new ObsClient(ak, sk, endpoint);
HeaderResponse response = obsClient.putObject(bucketName, objectName, new ByteArrayInputStream(is));
// 可选:调用成功后,记录调用成功的HTTP状态码和服务端请求ID
int statusCode = response.getStatusCode();
if (200 == statusCode) {
return true;
}
obsClient.close();
} catch (IOException e) {
log.info("文件上传失败:{}", e.getMessage(), e);
}
return false;
}
/**
* 下载文件
*
* @param fileName 文件名称
* @param fileType 文件路径
* @return
*/
public String getDownloadUrl(String fileName, FileType fileType) {
//ObsClient obsClient = null;
//obsClient = new ObsClient(ak, sk, endpoint);
// URL有效期,3600秒.5分钟
long expireSeconds = 3600L;
String objectName = fileType.getType().concat("/").concat(fileName);
TemporarySignatureRequest request = new TemporarySignatureRequest(HttpMethodEnum.GET, expireSeconds);
request.setBucketName(bucketName);
request.setObjectKey(objectName);
TemporarySignatureResponse response = obsClient.createTemporarySignature(request);
return response.getSignedUrl();
}
/**
* 获取上传地址
*
* @param fileName 文件名称
* @param fileType 文件路径
* @return
*/
public String getUploadUrl(String fileName, FileType fileType) {
try {
// 创建ObsClient实例
//ObsClient obsClient = new ObsClient(ak, sk, endpoint);
// URL有效期,3600秒
long expireSeconds = 3600L;
Map<String, String> headers = new HashMap<String, String>();
headers.put("Content-Type", "application/octet-stream");
String objectName = fileType.getType().concat("/").concat(fileName);
TemporarySignatureRequest request = new TemporarySignatureRequest(HttpMethodEnum.PUT, expireSeconds);
request.setBucketName(bucketName);
request.setObjectKey(objectName);
request.setHeaders(headers);
TemporarySignatureResponse response = obsClient.createTemporarySignature(request);
return response.getSignedUrl();
} catch (Exception e) {
log.error("获取上传地址异常:{}", e.getMessage(), e);
}
return null;
}
/**
* 下载文件返回字节数组
*
* @param fileName 文件名称
* @param fileType 文件路径
* @return
*/
public byte[] downloadFile(String fileName, FileType fileType) {
try {
InputStream inputStream = downloadFileInputStream(fileName, fileType);
byte[] bytes = IOUtils.toByteArray(inputStream);
return bytes;
} catch (Exception e) {
log.error("下载文件异常:{}", e.getMessage(), e);
}
return null;
}
/**
* 上传视频
*
* @param fileName 文件名称
* @param fileType 文件路径
* @return
*/
public boolean uploadFileVideo(String fileName, FileType fileType, InputStream is) {
try {
String objectName = fileType.getType().concat("/").concat(fileName);
//ObsClient obsClient = new ObsClient(ak, sk, endpoint);
// 添加 ContentType (添加后可在浏览器中直接浏览,而非下载链接)
obsClient.putObject(bucketName, objectName, is);
obsClient.close();
return true;
} catch (Exception e) {
log.error("上传视频文件异常:{}", e.getMessage(), e);
}
return false;
}
/**
* 下载文件返回流式
*
* @param fileName 文件名称
* @param fileType 文件路径
* @return
*/
public InputStream downloadFileInputStream(String fileName, FileType fileType) {
try {
String objectName = fileType.getType().concat("/").concat(fileName);
// 用户拿到STS临时凭证后,通过其中的安全令牌(SecurityToken)和临时访问密钥(AccessKeyId和AccessKeySecret)生成OSSClient。
//ObsClient obsClient = new ObsClient(ak, sk, endpoint);
ObsObject obsObject = obsClient.getObject(bucketName, objectName);
obsClient.close();
return obsObject.getObjectContent();
} catch (Exception e) {
log.error("下载文件异常:{}", e.getMessage(), e);
}
return null;
}
public enum FileType {
TEST("TEST", "测试");
private String type;
private String desc;
FileType(String type, String desc) {
this.type = type;
this.desc = desc;
}
public String getType() {
return type;
}
public String getDesc() {
return desc;
}
}
}

14
src/main/java/com/yeguang/util/LoginUtil.java

@ -0,0 +1,14 @@
package com.yeguang.util;
import cn.dev33.satoken.stp.StpUtil;
import com.yeguang.common.UserConstants;
import com.yeguang.model.LoginUser;
public class LoginUtil {
public static LoginUser getLoginUser() {
return (LoginUser) StpUtil.getSession().get(UserConstants.SYS_SESSION);
}
}

34
src/main/java/com/yeguang/util/Md5Util.java

@ -0,0 +1,34 @@
package com.yeguang.util;
import java.io.InputStream;
import java.security.MessageDigest;
public class Md5Util {
public static String calculateMD5(InputStream inputStream) throws Exception {
// 创建 MD5 消息摘要实例
MessageDigest digest = MessageDigest.getInstance("MD5");
byte[] buffer = new byte[1024]; // 缓冲区大小
int bytesRead;
// 读取 InputStream 并更新 MD5 摘要
while ((bytesRead = inputStream.read(buffer)) != -1) {
digest.update(buffer, 0, bytesRead);
}
// 获取 MD5 摘要结果
byte[] md5Bytes = digest.digest();
// 转换为 16 进制字符串
StringBuilder hexString = new StringBuilder();
for (byte b : md5Bytes) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
}
}

146
src/main/resources/app.yml

@ -0,0 +1,146 @@
server:
port: 9198
request:
max-body-size: 10MB
max-file-size: 10MB
max-header-size: 8kb
solon.app:
name: solon-web-test
solon.dataSources:
db1!:
class: com.zaxxer.hikari.HikariDataSource
jdbc-url: jdbc:mysql://101.34.243.138:7306/siliqua_recognition?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
driver-class-name: com.mysql.cj.jdbc.Driver
username: ams
password: ams@1234
maximum-pool-size: 10
minimum-idle: 5
idle-timeout: 30000
connection-timeout: 30000
max-lifetime: 1800000
pool-name: siliqua-recognition
#app:
# redis:
# server: 192.168.10.111
# port: 6379
# db: 9
# password: ams@1234
app.redis:
config: |
singleServerConfig:
address: "redis://192.168.10.111:6379"
database: 9
mybatis.db1:
typeAliases:
- "com.yeguang.model.entity"
mappers:
- "com.yeguang.mapper.*Mapper"
- "classpath:mapper/*.xml"
configuration:
mapperVerifyEnabled: false
cacheEnabled: false
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
global-config:
db-config:
id-type: auto
field-strategy: not_null
update-strategy: not_null
banner: false
#分页组件的配置
pagehelper:
offsetAsPageNum: true
rowBoundsWithCount: true
pageSizeZero: true
reasonable: false
params: pageNum=pageHelperStart;pageSize=pageHelperRows;
supportMethodsArguments: false
# sa-token配置
sa-token:
# token名称 (同时也是cookie名称)
token-name: Authorization
token-prefix: Bearer
# token有效期,单位s 默认30天, -1代表永不过期
timeout: 2592000
# token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
active-timeout: -1
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
is-concurrent: true #旧版曾用名(allow-concurrent-login)
# 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
is-share: true
# token风格
token-style: uuid
# 是否输出操作日志
is-log: false
is-print: false
jwt-secret-key: asdfghjklzxcvbnmqwertyuiop1234567890
knife4j:
enable: true
basic:
enable: true
username: admin
password: 123456
setting:
enableOpenApi: false
enableSwaggerModels: false
enableFooter: false
solon:
logging:
appender:
console:
level: TRACE
enable: true #是否启用
pattern: "%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(%-5level) %magenta(${PID:-}) - %X{X-TraceId:-} - %-15([%15.15thread]) %-56(%cyan(%-40.40logger{39}[%L])) : %msg%n"
file:
name: "logs/${solon.app.name}"
rolling: "logs/${solon.app.name}_%d{yyyy-MM-dd}_%i.log"
level: INFO
enable: true #是否启用
extension: ".log" #v2.2.18 后支持(例:.log, .log.gz, .log.zip)
maxFileSize: "10 MB"
maxHistory: "7"
pattern: "%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level ${PID:-} - %X{X-TraceId:-} - %-15([%15.15thread]) %-56(%-40.40logger{39}[%L]) : %msg%n"
cloud:
level: INFO
enable: true #是否启用
logger:
"root": #默认记录器配置
level: INFO
"com.zaxxer.hikari":
level: WARN
"com.yeguang.mapper":
level: DEBUG
recognition:
model:
url: http://api.camacloud.org.cn/recognition
thread:
enabled: true
pool:
core-pool-size: 5
max-pool-size: 8
queue-capacity: 500
thread-name: siliqua-recognition
task-threshold: 300000
queue-size-threshold: 200
queue-time-threshold: 1000
huawei:
obs:
end-point: "obs.cn-east-2.myhuaweicloud.com"
ak: "NMUAWG3TN50OSJESCIRV"
sk: "qG0VFwhiTAtiCuUaMAxJL3zsusp3W4GHs7THR9pC"
bucket-name: iot
path-prefix: recognition

27
src/main/resources/mapper/RecordContentMapper.xml

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yeguang.mapper.RecordContentMapper">
<resultMap id="RecordContentMap" type="com.yeguang.model.entity.RecordContentEntity">
<id column="id" property="id"/>
<result column="record_id" property="recordId"/>
<result column="image_name" property="imageName"/>
<result column="image_url" property="imageUrl"/>
<result column="recognition_data" property="recognitionData"
typeHandler="com.yeguang.handler.ModelPredictResponseListTypeHandler"
javaType="java.util.List"/>
<result column="siliqua_num" property="siliquaNum"/>
<result column="handle_num" property="handleNum"/>
<result column="beak_num" property="beakNum"/>
<result column="scale_type" property="scaleType"/>
<result column="scale" property="scale" javaType="com.yeguang.model.Scale"
typeHandler="com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler"/>
<result column="conf" property="conf"/>
<result column="iou" property="iou"/>
</resultMap>
<select id="selectListByRecordId" resultMap="RecordContentMap">
select *
from record_content
where record_id = #{recordId}
order by id asc
</select>
</mapper>

30
src/main/resources/mapper/RecordMapper.xml

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yeguang.mapper.RecordMapper">
<resultMap id="RecordDetailMap" type="com.yeguang.model.RecordDetail">
<id property="id" column="id"/>
<collection property="content" javaType="java.util.List" column="id"
select="com.yeguang.mapper.RecordContentMapper.selectListByRecordId"/>
</resultMap>
<select id="getRecordDetail" resultMap="RecordDetailMap">
select *
from record_info
where id = #{id}
</select>
<select id="pageList" resultType="com.yeguang.model.RecordItem">
select r.id as recordId,
r.name as recordName,
r.user_id,
r.image_num as imageNum,
r.create_time as createTime,
u.real_name as userName,
u.unit_name as unitName,
u.phone
from record_info r
left join siliqua_user u on r.user_id = u.id
order by r.create_time desc
</select>
</mapper>

20
src/main/resources/mapper/UserMapper.xml

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yeguang.mapper.UserMapper">
<select id="selectLoginUserByPhone" resultType="com.yeguang.model.LoginUser">
select id as user_id,
real_name,
phone,
create_time,
password,
unit_name,
status
from siliqua_user
where phone = #{phone}
and status != 0
</select>
</mapper>
Loading…
Cancel
Save