zhangyeguang
4 weeks ago
commit
8721d53c77
68 changed files with 8715 additions and 0 deletions
@ -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 |
@ -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 |
@ -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> |
@ -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> |
@ -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> |
@ -0,0 +1,4 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<project version="4"> |
|||
<component name="VcsDirectoryMappings" defaultProject="true" /> |
|||
</project> |
File diff suppressed because it is too large
@ -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 |
@ -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> |
@ -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); |
|||
} |
|||
} |
@ -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; |
|||
} |
|||
|
|||
} |
@ -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; |
|||
} |
|||
} |
@ -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; |
|||
|
|||
} |
@ -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(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
} |
@ -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(); |
|||
} |
|||
} |
@ -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()); |
|||
} |
|||
|
|||
} |
@ -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); |
|||
} |
|||
|
|||
|
|||
} |
@ -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; |
|||
|
|||
|
|||
} |
@ -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 + |
|||
'}'; |
|||
} |
|||
} |
@ -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, "服务端运行出错")); |
|||
} |
|||
} |
|||
} |
@ -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, "服务端运行出错")); |
|||
} |
|||
} |
|||
} |
@ -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"); |
|||
} |
|||
|
|||
} |
|||
|
@ -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; |
|||
} |
|||
} |
@ -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); |
|||
} |
|||
} |
@ -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(); |
|||
} |
|||
} |
@ -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(); |
|||
} |
|||
} |
@ -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); |
|||
} |
|||
|
|||
} |
@ -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(); |
|||
} |
|||
|
|||
} |
@ -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); |
|||
} |
|||
} |
|||
} |
@ -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); |
|||
} |
@ -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); |
|||
} |
@ -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); |
|||
} |
@ -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; |
|||
|
|||
|
|||
} |
@ -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"; |
|||
} |
@ -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; |
|||
|
|||
|
|||
} |
@ -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; |
|||
|
|||
} |
@ -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; |
|||
} |
|||
|
@ -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; |
|||
|
|||
|
|||
} |
@ -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; |
|||
} |
|||
} |
@ -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; |
|||
} |
@ -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; |
|||
} |
@ -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; |
|||
} |
@ -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; |
|||
} |
@ -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; |
|||
} |
|||
} |
@ -0,0 +1,10 @@ |
|||
package com.yeguang.model; |
|||
|
|||
import lombok.Data; |
|||
|
|||
@Data |
|||
public class Scale { |
|||
private int scale; |
|||
private float size; |
|||
private int[][] points; |
|||
} |
@ -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; |
|||
|
|||
} |
@ -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; |
|||
|
|||
|
|||
} |
@ -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; |
|||
} |
@ -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; |
|||
|
|||
} |
@ -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; |
|||
|
|||
|
|||
} |
@ -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); |
|||
} |
|||
} |
|||
|
|||
|
|||
} |
@ -0,0 +1,5 @@ |
|||
package com.yeguang.service; |
|||
|
|||
public interface InfoService { |
|||
String hello(); |
|||
} |
@ -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"; |
|||
} |
|||
|
|||
} |
@ -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); |
|||
} |
@ -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); |
|||
} |
|||
|
|||
} |
|||
} |
@ -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); |
|||
} |
@ -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); |
|||
} |
|||
} |
@ -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; |
|||
|
|||
} |
|||
|
|||
|
|||
} |
@ -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); |
|||
} |
|||
|
|||
} |
@ -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(); |
|||
|
|||
|
|||
} |
|||
|
|||
} |
@ -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; |
|||
} |
|||
|
|||
} |
@ -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; |
|||
} |
|||
} |
|||
} |
|||
|
@ -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); |
|||
} |
|||
|
|||
} |
@ -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(); |
|||
} |
|||
} |
@ -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 |
@ -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> |
@ -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> |
@ -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…
Reference in new issue