Ver código fonte

first commit

Hai Lin 2 meses atrás
pai
commit
406b8f6b11
100 arquivos alterados com 7057 adições e 0 exclusões
  1. 38 0
      .gitignore
  2. BIN
      .idea/.cache/.Apifox_Helper/.api.cache.v1.1.db
  3. 8 0
      .idea/.gitignore
  4. 9 0
      .idea/ApifoxUploaderProjectSetting.xml
  5. 5 0
      .idea/MyBatisCodeHelperDatasource.xml
  6. 19 0
      .idea/dataSources.xml
  7. 11 0
      .idea/encodings.xml
  8. 7 0
      .idea/jpa-buddy.xml
  9. 19 0
      .idea/misc.xml
  10. 124 0
      .idea/uiDesigner.xml
  11. 6 0
      .idea/vcs.xml
  12. 33 0
      mirage-core/.gitignore
  13. 165 0
      mirage-core/pom.xml
  14. 22 0
      mirage-core/src/main/java/com/mirage/core/annotation/Auth.java
  15. 32 0
      mirage-core/src/main/java/com/mirage/core/annotation/PostJson.java
  16. 30 0
      mirage-core/src/main/java/com/mirage/core/annotation/RedisLock.java
  17. 15 0
      mirage-core/src/main/java/com/mirage/core/annotation/WithOriginalResponse.java
  18. 86 0
      mirage-core/src/main/java/com/mirage/core/aspect/LimitAspect.java
  19. 143 0
      mirage-core/src/main/java/com/mirage/core/aspect/RedisLockAround.java
  20. 25 0
      mirage-core/src/main/java/com/mirage/core/config/GsonConfig.java
  21. 10 0
      mirage-core/src/main/java/com/mirage/core/config/HttpConfig.java
  22. 55 0
      mirage-core/src/main/java/com/mirage/core/config/TaskConfig.java
  23. 58 0
      mirage-core/src/main/java/com/mirage/core/exception/AppRuntimeException.java
  24. 23 0
      mirage-core/src/main/java/com/mirage/core/gson/Base64TypeAdapter.java
  25. 22 0
      mirage-core/src/main/java/com/mirage/core/gson/ByteArrayTypeAdapter.java
  26. 71 0
      mirage-core/src/main/java/com/mirage/core/gson/GsonBox.java
  27. 18 0
      mirage-core/src/main/java/com/mirage/core/gson/GsonIgnore.java
  28. 43 0
      mirage-core/src/main/java/com/mirage/core/gson/GsonIgnoreStrategy.java
  29. 18 0
      mirage-core/src/main/java/com/mirage/core/gson/GsonProperty.java
  30. 27 0
      mirage-core/src/main/java/com/mirage/core/gson/GsonView.java
  31. 24 0
      mirage-core/src/main/java/com/mirage/core/gson/JsonFieldNamingPolicy.java
  32. 34 0
      mirage-core/src/main/java/com/mirage/core/gson/ParameterizedTypeImpl.java
  33. 18 0
      mirage-core/src/main/java/com/mirage/core/localcache/ExpireTimeBasedTag.java
  34. 51 0
      mirage-core/src/main/java/com/mirage/core/localcache/LocalCache.java
  35. 59 0
      mirage-core/src/main/java/com/mirage/core/meta/AppCode.java
  36. 8 0
      mirage-core/src/main/java/com/mirage/core/meta/AuthType.java
  37. 75 0
      mirage-core/src/main/java/com/mirage/core/meta/BusinessTypeEnum.java
  38. 46 0
      mirage-core/src/main/java/com/mirage/core/meta/Context.java
  39. 29 0
      mirage-core/src/main/java/com/mirage/core/meta/CourseTypeEnum.java
  40. 27 0
      mirage-core/src/main/java/com/mirage/core/meta/DownloadBean.java
  41. 21 0
      mirage-core/src/main/java/com/mirage/core/meta/HeaderKeys.java
  42. 12 0
      mirage-core/src/main/java/com/mirage/core/meta/HttpResponse.java
  43. 36 0
      mirage-core/src/main/java/com/mirage/core/meta/LearnPlatformTypeEnum.java
  44. 38 0
      mirage-core/src/main/java/com/mirage/core/meta/LogBean.java
  45. 31 0
      mirage-core/src/main/java/com/mirage/core/meta/PageQuery.java
  46. 32 0
      mirage-core/src/main/java/com/mirage/core/meta/PageResultVo.java
  47. 107 0
      mirage-core/src/main/java/com/mirage/core/meta/PageUtil.java
  48. 76 0
      mirage-core/src/main/java/com/mirage/core/meta/PageUtils.java
  49. 35 0
      mirage-core/src/main/java/com/mirage/core/meta/PlatformTypeEnum.java
  50. 26 0
      mirage-core/src/main/java/com/mirage/core/meta/RpcCodeEnum.java
  51. 175 0
      mirage-core/src/main/java/com/mirage/core/meta/RpcLogBean.java
  52. 78 0
      mirage-core/src/main/java/com/mirage/core/meta/ServiceStatus.java
  53. 202 0
      mirage-core/src/main/java/com/mirage/core/meta/WebLogBean.java
  54. 214 0
      mirage-core/src/main/java/com/mirage/core/utils/AppHttpClient.java
  55. 99 0
      mirage-core/src/main/java/com/mirage/core/utils/AppResult.java
  56. 104 0
      mirage-core/src/main/java/com/mirage/core/utils/AppUtil.java
  57. 51 0
      mirage-core/src/main/java/com/mirage/core/utils/BeanConvertUtil.java
  58. 44 0
      mirage-core/src/main/java/com/mirage/core/utils/CheckSumBuilder.java
  59. 16 0
      mirage-core/src/main/java/com/mirage/core/utils/Constants.java
  60. 34 0
      mirage-core/src/main/java/com/mirage/core/utils/DateUtil.java
  61. 16 0
      mirage-core/src/main/java/com/mirage/core/utils/ExcelRowData.java
  62. 60 0
      mirage-core/src/main/java/com/mirage/core/utils/ExcelUtil.java
  63. 350 0
      mirage-core/src/main/java/com/mirage/core/utils/GsonUtil.java
  64. 66 0
      mirage-core/src/main/java/com/mirage/core/utils/NosUtil.java
  65. 189 0
      mirage-core/src/main/java/com/mirage/core/utils/RegexUtil.java
  66. 123 0
      mirage-core/src/main/java/com/mirage/core/utils/RequestUtil.java
  67. 60 0
      mirage-core/src/main/java/com/mirage/core/utils/RpcResult.java
  68. 94 0
      mirage-core/src/main/java/com/mirage/core/utils/StringKit.java
  69. 63 0
      mirage-core/src/main/java/com/mirage/core/utils/VerifyUtil.java
  70. 33 0
      mirage-service/.gitignore
  71. 72 0
      mirage-service/pom.xml
  72. 56 0
      mirage-service/src/main/java/com/mirage/mirageservice/MirageServiceApplication.java
  73. 162 0
      mirage-service/src/main/java/com/mirage/mirageservice/aspect/ControllerAround.java
  74. 41 0
      mirage-service/src/main/java/com/mirage/mirageservice/aspect/LockAspect.java
  75. 101 0
      mirage-service/src/main/java/com/mirage/mirageservice/config/PrimaryDataSourceConfig.java
  76. 91 0
      mirage-service/src/main/java/com/mirage/mirageservice/config/SecondaryDataSourceConfig.java
  77. 113 0
      mirage-service/src/main/java/com/mirage/mirageservice/controller/WxController.java
  78. 155 0
      mirage-service/src/main/java/com/mirage/mirageservice/domain/CsMinWechatUser.java
  79. 188 0
      mirage-service/src/main/java/com/mirage/mirageservice/domain/ScanPage.java
  80. 247 0
      mirage-service/src/main/java/com/mirage/mirageservice/domain/Student.java
  81. 412 0
      mirage-service/src/main/java/com/mirage/mirageservice/domain/StudentDataReport.java
  82. 68 0
      mirage-service/src/main/java/com/mirage/mirageservice/domain/WechatBind.java
  83. 65 0
      mirage-service/src/main/java/com/mirage/mirageservice/enums/MinProgramConfigEnum.java
  84. 23 0
      mirage-service/src/main/java/com/mirage/mirageservice/mapper/mysql/CsMinWechatUserMapper.java
  85. 25 0
      mirage-service/src/main/java/com/mirage/mirageservice/mapper/mysql/StudentDataReportMapper.java
  86. 25 0
      mirage-service/src/main/java/com/mirage/mirageservice/mapper/mysql/WechatBindMapper.java
  87. 20 0
      mirage-service/src/main/java/com/mirage/mirageservice/mapper/sqlserver/ScanPageMapper.java
  88. 20 0
      mirage-service/src/main/java/com/mirage/mirageservice/mapper/sqlserver/StudentMapper.java
  89. 26 0
      mirage-service/src/main/java/com/mirage/mirageservice/meta/AppContext.java
  90. 20 0
      mirage-service/src/main/java/com/mirage/mirageservice/meta/BindRequest.java
  91. 18 0
      mirage-service/src/main/java/com/mirage/mirageservice/meta/StudentDataReportResponse.java
  92. 50 0
      mirage-service/src/main/java/com/mirage/mirageservice/meta/UserWechatLoginResponse.java
  93. 239 0
      mirage-service/src/main/java/com/mirage/mirageservice/service/UserService.java
  94. 79 0
      mirage-service/src/main/java/com/mirage/mirageservice/service/WechatService.java
  95. BIN
      mirage-service/src/main/resources/apiclient_cert.p12
  96. 25 0
      mirage-service/src/main/resources/apiclient_cert.pem
  97. 28 0
      mirage-service/src/main/resources/apiclient_key.pem
  98. 65 0
      mirage-service/src/main/resources/application.yml
  99. 200 0
      mirage-service/src/main/resources/com/mirage/mirageservice/mapper/mysql/CsMinWechatUserMapper.xml
  100. 425 0
      mirage-service/src/main/resources/com/mirage/mirageservice/mapper/mysql/StudentDataReportMapper.xml

+ 38 - 0
.gitignore

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

BIN
.idea/.cache/.Apifox_Helper/.api.cache.v1.1.db


+ 8 - 0
.idea/.gitignore

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

Diferenças do arquivo suprimidas por serem muito extensas
+ 9 - 0
.idea/ApifoxUploaderProjectSetting.xml


Diferenças do arquivo suprimidas por serem muito extensas
+ 5 - 0
.idea/MyBatisCodeHelperDatasource.xml


+ 19 - 0
.idea/dataSources.xml

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="DataSourceManagerImpl" format="xml" multifile-model="true">
+    <data-source source="LOCAL" name="chunsun" uuid="d2030384-e0f6-445c-8002-11b85b4ef4c2">
+      <driver-ref>mysql.8</driver-ref>
+      <synchronize>true</synchronize>
+      <jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
+      <jdbc-url>jdbc:mysql://8.155.61.172:3306/csqz-client</jdbc-url>
+      <working-dir>$ProjectFileDir$</working-dir>
+    </data-source>
+    <data-source source="LOCAL" name="sqlserver-test" uuid="be611318-2c59-46d6-ba8c-8783af1ac6c3">
+      <driver-ref>sqlserver.ms</driver-ref>
+      <synchronize>true</synchronize>
+      <jdbc-driver>com.microsoft.sqlserver.jdbc.SQLServerDriver</jdbc-driver>
+      <jdbc-url>jdbc:sqlserver://52.130.155.58:1433;database=dcjxb_db_test</jdbc-url>
+      <working-dir>$ProjectFileDir$</working-dir>
+    </data-source>
+  </component>
+</project>

+ 11 - 0
.idea/encodings.xml

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

+ 7 - 0
.idea/jpa-buddy.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="JpaBuddyIdeaProjectConfig">
+    <option name="defaultUnitInitialized" value="true" />
+    <option name="renamerInitialized" value="true" />
+  </component>
+</project>

+ 19 - 0
.idea/misc.xml

@@ -0,0 +1,19 @@
+<?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" />
+        <option value="$PROJECT_DIR$/mirage-service/pom.xml" />
+        <option value="$PROJECT_DIR$/mirage-core/pom.xml" />
+      </list>
+    </option>
+  </component>
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="temurin-11" project-jdk-type="JavaSDK">
+    <output url="file://$PROJECT_DIR$/out" />
+  </component>
+  <component name="ProjectType">
+    <option name="id" value="jpab" />
+  </component>
+</project>

+ 124 - 0
.idea/uiDesigner.xml

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

+ 6 - 0
.idea/vcs.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="$PROJECT_DIR$" vcs="Git" />
+  </component>
+</project>

+ 33 - 0
mirage-core/.gitignore

@@ -0,0 +1,33 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/

+ 165 - 0
mirage-core/pom.xml

@@ -0,0 +1,165 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.mirage</groupId>
+        <artifactId>mirage-server</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+    <artifactId>mirage-core</artifactId>
+    <packaging>jar</packaging>
+
+    <dependencies>
+<!--        <dependency>-->
+<!--            <groupId>org.springframework.boot</groupId>-->
+<!--            <artifactId>spring-boot-starter-web</artifactId>-->
+<!--        </dependency>-->
+        <dependency>
+            <groupId>redis.clients</groupId>
+            <artifactId>jedis</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-pool2</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.tomcat.embed</groupId>
+            <artifactId>tomcat-embed-core</artifactId>
+            <scope>compile</scope>
+            <exclusions>
+                <exclusion>
+                    <artifactId>tomcat-annotations-api</artifactId>
+                    <groupId>org.apache.tomcat</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-aop</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.google.code.gson</groupId>
+            <artifactId>gson</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>commons-codec</groupId>
+            <artifactId>commons-codec</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-classic</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>easyexcel</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi</artifactId>
+            <version>3.17</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-annotations</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-amqp</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpcore</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-dependencies-zookeeper</artifactId>
+            <type>pom</type>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+        <!-- 分页插件 -->
+        <dependency>
+            <groupId>com.github.pagehelper</groupId>
+            <artifactId>pagehelper-spring-boot-starter</artifactId>
+            <exclusions>
+                <exclusion>
+                    <artifactId>mybatis-spring-boot-starter</artifactId>
+                    <groupId>org.mybatis.spring.boot</groupId>
+                </exclusion>
+                <exclusion>
+                    <artifactId>spring-boot-starter</artifactId>
+                    <groupId>org.springframework.boot</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+    </dependencies>
+
+<!--    <build>-->
+<!--        <plugins>-->
+<!--            <plugin>-->
+<!--                &lt;!&ndash; The plugin rewrites your manifest &ndash;&gt;-->
+<!--                <groupId>org.springframework.boot</groupId>-->
+<!--                <artifactId>spring-boot-maven-plugin</artifactId>-->
+<!--                <executions>-->
+<!--                    <execution>-->
+<!--                        <goals>-->
+<!--                            &lt;!&ndash;可以把依赖的包都打包到生成的Jar包中&ndash;&gt;-->
+<!--                            <goal>repackage</goal>-->
+<!--                        </goals>-->
+<!--                        &lt;!&ndash;可以生成不含依赖包的不可执行Jar包&ndash;&gt;-->
+<!--                        &lt;!&ndash; configuration>-->
+<!--                          <classifier>exec</classifier>-->
+<!--                        </configuration> &ndash;&gt;-->
+<!--                    </execution>-->
+<!--                </executions>-->
+<!--            </plugin>-->
+<!--        </plugins>-->
+<!--    </build>-->
+
+</project>

+ 22 - 0
mirage-core/src/main/java/com/mirage/core/annotation/Auth.java

@@ -0,0 +1,22 @@
+package com.mirage.core.annotation;
+
+
+
+import com.mirage.core.meta.AuthType;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Created by hzlinahi on 2020/11/2.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.TYPE})
+public @interface Auth {
+    AuthType value() default AuthType.COOKIES;
+    int[] values() default {};
+    boolean uidRequired() default true;
+    boolean bindRequired() default true;
+}

+ 32 - 0
mirage-core/src/main/java/com/mirage/core/annotation/PostJson.java

@@ -0,0 +1,32 @@
+package com.mirage.core.annotation;
+
+import org.springframework.core.annotation.AliasFor;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+
+import java.lang.annotation.*;
+
+/**
+ * @author heyueling
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@RequestMapping(method = RequestMethod.POST,
+        consumes = MediaType.APPLICATION_JSON_UTF8_VALUE,
+        produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
+public @interface PostJson {
+
+    /**
+     * Alias for {@link RequestMapping#value}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] value() default {};
+
+    /**
+     * Alias for {@link RequestMapping#path}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] path() default {};
+}

+ 30 - 0
mirage-core/src/main/java/com/mirage/core/annotation/RedisLock.java

@@ -0,0 +1,30 @@
+package com.mirage.core.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * Created by hzlinhai on 2020/11/9.
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface RedisLock {
+
+    /**
+     * 锁时间,默认3s
+     * @return
+     */
+    int lockTime() default 3;
+
+    /**
+     * 重试次数,默认3次
+     * @return
+     */
+    int retries() default 3;
+
+    /**
+     * 锁定key,EL表达式
+     * @return
+     */
+    String lockField();
+}

+ 15 - 0
mirage-core/src/main/java/com/mirage/core/annotation/WithOriginalResponse.java

@@ -0,0 +1,15 @@
+package com.mirage.core.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Created by hzlinhai on 2025/1/2.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD})
+public @interface WithOriginalResponse {
+
+}

+ 86 - 0
mirage-core/src/main/java/com/mirage/core/aspect/LimitAspect.java

@@ -0,0 +1,86 @@
+package com.mirage.core.aspect;
+
+import com.google.common.collect.Maps;
+import com.google.gson.reflect.TypeToken;
+import com.mirage.core.localcache.ExpireTimeBasedTag;
+import com.mirage.core.localcache.LocalCache;
+import com.mirage.core.utils.GsonUtil;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.Map;
+import java.util.Random;
+import java.util.concurrent.Callable;
+
+/**
+ * @Description: 限流
+ * @Author: 万贤良
+ * @Email: wanxianliang@corp.netease.com
+ * @Date: 2020/11/19
+ */
+public class LimitAspect {
+
+    private static final Logger logger = LoggerFactory.getLogger(LimitAspect.class);
+
+    public  String limitUriKey = "limitUriKey";
+    public static final Integer limit_empyt = 10;  //不限流
+    public static final Integer limit_all = 0;    //全部限流
+
+    private RedisTemplate<String, String> limitRedisTemplate;
+
+    private LocalCache localCache;
+
+    public LimitAspect(RedisTemplate<String, String> limitRedisTemplate, LocalCache localCache, String limitKey) {
+        this.limitUriKey = limitKey;
+        this.limitRedisTemplate = limitRedisTemplate;
+        this.localCache = localCache;
+    }
+
+    public Boolean needDemotion(HttpServletRequest request) {
+        try {
+            // 本地缓存
+            Callable<Map<String, Integer>> callable = () -> {
+                String redisValue = limitRedisTemplate.opsForValue().get(this.limitUriKey);
+                if (StringUtils.isBlank(redisValue)) {
+                    return Maps.newHashMap();
+                }
+                return GsonUtil.gsonMapDouble.fromJson(redisValue, new TypeToken<Map<String, Integer>>() {
+                }.getType());
+            };
+            //本地没有缓存,生成一份缓存快照,缓存1min;
+            Map<String, Integer> localCacheUriMap = localCache.get(ExpireTimeBasedTag.ONE_MINUTES, this.limitUriKey, callable);
+            return needDemotionFromCache(localCacheUriMap, request);
+        } catch (Exception e) {
+            logger.error("urilimitException:", e);
+        }
+        return Boolean.FALSE;
+    }
+
+
+    public Boolean needDemotionFromCache(Map<String, Integer> localCacheUriMap, HttpServletRequest request) {
+        //如果redis中没有要降级的uri,则不需要降级
+        if (localCacheUriMap.isEmpty()) {
+            return false;
+        }
+        String uri = request.getRequestURI();
+        Integer limitProportion = localCacheUriMap.get(uri);
+        // 没有配限流级别,不限制
+        if (limitProportion == null) {
+            return false;
+        }
+        // 降级
+        if (limit_all.equals(limitProportion)) {
+            return true;
+        } else if (limit_empyt.equals(limitProportion)) {//不限制
+            return false;
+        }
+        // 随机放流算法
+        int randValue = new Random().nextInt(10) + 1;
+        return randValue > limitProportion;
+    }
+
+}
+

+ 143 - 0
mirage-core/src/main/java/com/mirage/core/aspect/RedisLockAround.java

@@ -0,0 +1,143 @@
+package com.mirage.core.aspect;
+
+import com.mirage.core.annotation.RedisLock;
+import com.mirage.core.exception.AppRuntimeException;
+import com.mirage.core.meta.AppCode;
+import org.apache.commons.lang3.StringUtils;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.expression.EvaluationContext;
+import org.springframework.expression.Expression;
+import org.springframework.expression.ExpressionParser;
+import org.springframework.expression.spel.standard.SpelExpressionParser;
+import org.springframework.expression.spel.support.StandardEvaluationContext;
+
+import java.lang.reflect.Method;
+
+/**
+ * Created by hzlinhai on 2020/11/9.
+ */
+public class RedisLockAround {
+
+    private static final Logger logger = LoggerFactory.getLogger(RedisLockAround.class);
+
+    private static final String SPLIT = "_";
+
+    private static final ThreadLocal<String> LOCK_TIME_THREAD_LOCAL = ThreadLocal.withInitial(String::new);
+
+    private StringRedisTemplate stringRedisTemplate;
+
+    public RedisLockAround(StringRedisTemplate stringRedisTemplate){
+        this.stringRedisTemplate = stringRedisTemplate;
+    }
+
+    public Object watchLockAspect(ProceedingJoinPoint jp) throws Throwable{
+        String targetName = jp.getTarget().getClass().getName();
+        String methodName = jp.getSignature().getName();
+        Object[] args = jp.getArgs();
+        Method method = ((MethodSignature) jp.getSignature()).getMethod();
+        LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
+        String[] paramNames = discoverer.getParameterNames(method);
+        RedisLock redisLock = method.getAnnotation(RedisLock.class);
+        String lockField = redisLock.lockField();
+        int lockTime = redisLock.lockTime();
+        int retries = redisLock.retries();
+        // 次数小于0,不尝试
+        if(retries < 0){
+            retries = 0;
+        }
+        // el表达式解析加锁
+        ExpressionParser parser = new SpelExpressionParser();
+        Expression expression = parser.parseExpression(lockField);
+        EvaluationContext context = new StandardEvaluationContext();
+        for(int i = 0; i < args.length; i++){
+            context.setVariable(paramNames[i], args[i]);
+        }
+        // key拼接
+        StringBuilder keyBuilder = new StringBuilder(targetName).append(SPLIT)
+                .append(methodName).append(SPLIT)
+                .append(lockField).append(SPLIT)
+                .append(expression.getValue(context, String.class));
+        // 加锁
+        boolean isLock = this.tryLock(keyBuilder.toString(), lockTime, retries);
+        if(!isLock){
+            throw new AppRuntimeException(AppCode.DUPLICATE);
+        }
+        Object result = null;
+        try {
+            result = jp.proceed();
+        }finally {
+            // 释放锁
+            this.releaseLock(keyBuilder.toString());
+        }
+        return result;
+    }
+
+    private boolean tryLock(String lockKey, int lockTime, int retries){
+        try{
+            long now = System.currentTimeMillis();
+            boolean result = false;
+            while (retries >= 0){
+                if((System.currentTimeMillis() - now) / 1000 > lockTime){
+                    logger.info("execute RedisLockAspect#tryLock timeout");
+                    break;
+                }else {
+                    result = innerTryLock(lockKey, lockTime);
+                    if(result){
+                        break;
+                    }else {
+                        Thread.sleep(200);
+                    }
+                }
+                retries--;
+            }
+            return result;
+        }catch (Exception e){
+            return false;
+        }
+    }
+
+    private boolean innerTryLock(String lockKey, int lockTime){
+        long currentTime = System.currentTimeMillis();
+        String lockTimeDuration = String.valueOf(currentTime + lockTime * 1000 + 1);
+        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, lockTimeDuration);
+        if(null == result){
+            return false;
+        }
+        if(result){
+            LOCK_TIME_THREAD_LOCAL.set(lockTimeDuration);
+            return true;
+        }else{
+            if(checkIfLockTimeout(currentTime, lockKey)){
+                String preLockTimeDuration = stringRedisTemplate.opsForValue().getAndSet(lockKey, lockTimeDuration);
+                if(StringUtils.isBlank(preLockTimeDuration) || currentTime > Long.parseLong(preLockTimeDuration)){
+                    LOCK_TIME_THREAD_LOCAL.set(lockTimeDuration);
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+
+    private boolean checkIfLockTimeout(long currentTime, String lockKey){
+        String value = stringRedisTemplate.opsForValue().get(lockKey);
+        if(StringUtils.isBlank(value) || currentTime > Long.parseLong(value)){
+            return true;
+        }else{
+            return false;
+        }
+    }
+
+    private void releaseLock(String lockKey){
+        // 仅是否自己的锁
+        if(StringUtils.isNotBlank(LOCK_TIME_THREAD_LOCAL.get())
+                && LOCK_TIME_THREAD_LOCAL.get().equals(stringRedisTemplate.opsForValue().get(lockKey))){
+            stringRedisTemplate.delete(lockKey);
+        }
+    }
+}

+ 25 - 0
mirage-core/src/main/java/com/mirage/core/config/GsonConfig.java

@@ -0,0 +1,25 @@
+package com.mirage.core.config;
+
+import com.google.gson.Gson;
+import com.mirage.core.gson.GsonBox;
+import com.mirage.core.gson.GsonView;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+
+/**
+ * Created by hzlinhai on 2020/1/9.
+ */
+@Configuration
+@ConditionalOnClass(Gson.class)
+public class GsonConfig {
+
+    @Bean
+    @Primary
+    @Qualifier(GsonView.APP)
+    public Gson gson() {
+        return GsonBox.APP.gson();
+    }
+}

+ 10 - 0
mirage-core/src/main/java/com/mirage/core/config/HttpConfig.java

@@ -0,0 +1,10 @@
+package com.mirage.core.config;
+
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * Created by hzlinhai on 2020/1/13.
+ */
+@Configuration
+public class HttpConfig {
+}

+ 55 - 0
mirage-core/src/main/java/com/mirage/core/config/TaskConfig.java

@@ -0,0 +1,55 @@
+package com.mirage.core.config;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.task.TaskExecutor;
+import org.springframework.scheduling.TaskScheduler;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+
+import java.util.concurrent.ThreadPoolExecutor;
+
+/**
+ * Created by hzlinhai on 2020/1/13.
+ */
+@Configuration
+@EnableAsync(proxyTargetClass = true)
+@EnableScheduling
+public class TaskConfig {
+
+    @Value("${app.executor.corePoolSize:4}")
+    Integer corePoolSize;
+    @Value("${app.executor.maxPoolSize:8}")
+    Integer maxPoolSize;
+
+    /**
+     * task executor
+     *
+     * @return
+     */
+    @Bean
+    public TaskExecutor taskExecutor() {
+        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
+        executor.setCorePoolSize(corePoolSize);
+        executor.setMaxPoolSize(maxPoolSize);
+        executor.setQueueCapacity(10000);
+        executor.setThreadNamePrefix("TaskExecutor-");
+        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
+        return executor;
+    }
+
+    /**
+     * task scheduler
+     *
+     * @return
+     */
+    @Bean
+    public TaskScheduler taskScheduler() {
+        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
+        scheduler.setThreadNamePrefix("TaskScheduler-");
+        return scheduler;
+    }
+}

+ 58 - 0
mirage-core/src/main/java/com/mirage/core/exception/AppRuntimeException.java

@@ -0,0 +1,58 @@
+package com.mirage.core.exception;
+
+
+import com.mirage.core.meta.AppCode;
+
+/**
+ *
+ */
+public class AppRuntimeException extends RuntimeException {
+
+    private int code;
+    private String message;
+    private Object result;
+
+    public AppRuntimeException(String message) {
+        this.code = AppCode.EXCEPTION_WINDOW_SHOW.getCode();
+        this.message = message;
+    }
+    public AppRuntimeException(int code, String message) {
+        super(message);
+        this.code = code;
+        this.message = message;
+    }
+
+    public AppRuntimeException(AppCode appCode) {
+        this(appCode.getCode(), appCode.getMessage());
+    }
+
+    public AppRuntimeException(AppCode appCode, Object result) {
+        this(appCode.getCode(), appCode.getMessage());
+        this.result = result;
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    public void setCode(int code) {
+        this.code = code;
+    }
+
+    @Override
+    public String getMessage() {
+        return message;
+    }
+
+    public void setMessage(String message) {
+        this.message = message;
+    }
+
+    public Object getResult() {
+        return this.result;
+    }
+
+    public void setResult(Object result) {
+        this.result = result;
+    }
+}

+ 23 - 0
mirage-core/src/main/java/com/mirage/core/gson/Base64TypeAdapter.java

@@ -0,0 +1,23 @@
+package com.mirage.core.gson;
+
+import com.google.gson.*;
+import org.apache.commons.codec.binary.Base64;
+
+import java.lang.reflect.Type;
+
+/**
+ * Created by hzlinhai on 2020/1/9.
+ */
+public class Base64TypeAdapter implements JsonSerializer<byte[]>, JsonDeserializer<byte[]> {
+
+    @Override
+    public JsonElement serialize(byte[] src, Type typeOfSrc, JsonSerializationContext context) {
+        return new JsonPrimitive(Base64.encodeBase64String(src));
+    }
+
+    @Override
+    public byte[] deserialize(JsonElement json, Type type, JsonDeserializationContext cxt) {
+        return Base64.decodeBase64(json.getAsString());
+    }
+}
+

+ 22 - 0
mirage-core/src/main/java/com/mirage/core/gson/ByteArrayTypeAdapter.java

@@ -0,0 +1,22 @@
+package com.mirage.core.gson;
+
+import com.google.gson.*;
+import com.mirage.core.utils.StringKit;
+
+import java.lang.reflect.Type;
+
+/**
+ * Created by hzlinhai on 2020/1/9.
+ */
+public class ByteArrayTypeAdapter implements JsonSerializer<byte[]>, JsonDeserializer<byte[]> {
+
+    @Override
+    public JsonElement serialize(byte[] src, Type typeOfSrc, JsonSerializationContext context) {
+        return new JsonPrimitive(StringKit.deserialize(src));
+    }
+
+    @Override
+    public byte[] deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) {
+        return StringKit.serialize(json.getAsString());
+    }
+}

+ 71 - 0
mirage-core/src/main/java/com/mirage/core/gson/GsonBox.java

@@ -0,0 +1,71 @@
+package com.mirage.core.gson;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+/**
+ * Created by hzlinhai on 2020/1/9.
+ */
+public enum GsonBox {
+
+    BASE(GsonView.BASE, new Gson()),
+
+    EXPOSE(GsonView.EXPOSE, new GsonBuilder()
+            .excludeFieldsWithoutExposeAnnotation()
+            .create()),
+
+    APP(GsonView.APP, new GsonBuilder()
+            .setExclusionStrategies(new GsonIgnoreStrategy(Collections.singletonList(GsonView.APP)))
+            .setFieldNamingStrategy(JsonFieldNamingPolicy.GsonProperty)
+            .registerTypeHierarchyAdapter(byte[].class, new ByteArrayTypeAdapter())
+            .create()),
+
+    CACHE(GsonView.CACHE, new GsonBuilder()
+            .setExclusionStrategies(new GsonIgnoreStrategy(Collections.singletonList(GsonView.CACHE)))
+            .setFieldNamingStrategy(JsonFieldNamingPolicy.GsonProperty)
+            .registerTypeHierarchyAdapter(byte[].class, new Base64TypeAdapter())
+            .create()),
+
+    SERVER(GsonView.SERVER, new GsonBuilder()
+            .setExclusionStrategies(new GsonIgnoreStrategy(Collections.singletonList(GsonView.SERVER)))
+            .setFieldNamingStrategy(JsonFieldNamingPolicy.GsonProperty)
+            .registerTypeHierarchyAdapter(byte[].class, new ByteArrayTypeAdapter())
+            .create()),
+
+    MINIMUM(GsonView.MINIMUM_IGNORE, new GsonBuilder()
+            .setExclusionStrategies(new GsonIgnoreStrategy(Collections.singletonList(GsonView.MINIMUM_IGNORE)))
+            .setFieldNamingStrategy(JsonFieldNamingPolicy.GsonProperty)
+            .registerTypeHierarchyAdapter(byte[].class, new ByteArrayTypeAdapter())
+            .create()),
+
+    SELF_INFO(GsonView.SELF_IGNORE, new GsonBuilder()
+            .setExclusionStrategies(new GsonIgnoreStrategy(Collections.singletonList(GsonView.SELF_IGNORE)))
+            .setFieldNamingStrategy(JsonFieldNamingPolicy.GsonProperty)
+            .registerTypeHierarchyAdapter(byte[].class, new ByteArrayTypeAdapter())
+            .create()),
+    LOG(GsonView.LOG, new GsonBuilder()
+            .setExclusionStrategies(new GsonIgnoreStrategy(Arrays.asList(GsonView.LOG, GsonView.APP)))
+            .setFieldNamingStrategy(JsonFieldNamingPolicy.GsonProperty)
+            .registerTypeHierarchyAdapter(byte[].class, new ByteArrayTypeAdapter())
+            .create()),;
+
+    private String view;
+    private Gson gson;
+
+    GsonBox(String view, Gson gson) {
+        this.view = view;
+        this.gson = gson;
+    }
+
+    public String view() {
+        return view;
+    }
+
+    public Gson gson() {
+        return gson;
+    }
+
+}

+ 18 - 0
mirage-core/src/main/java/com/mirage/core/gson/GsonIgnore.java

@@ -0,0 +1,18 @@
+package com.mirage.core.gson;
+
+import java.lang.annotation.*;
+
+/**
+ * Created by hzlinhai on 2020/1/9.
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+
+public @interface GsonIgnore {
+    /**
+     * json views
+     */
+    String[] value() default {};
+
+}

+ 43 - 0
mirage-core/src/main/java/com/mirage/core/gson/GsonIgnoreStrategy.java

@@ -0,0 +1,43 @@
+package com.mirage.core.gson;
+
+import com.google.gson.ExclusionStrategy;
+import com.google.gson.FieldAttributes;
+
+import java.util.Collection;
+
+/**
+ * Created by hzlinhai on 2020/1/9.
+ */
+public class GsonIgnoreStrategy implements ExclusionStrategy {
+
+    private Collection<String> views;
+
+    public GsonIgnoreStrategy(Collection<String> views) {
+        this.views = views;
+    }
+
+    @Override
+    public boolean shouldSkipField(FieldAttributes f) {
+        GsonIgnore annotation = f.getAnnotation(GsonIgnore.class);
+        if (annotation != null) {
+            if (views == null || views.isEmpty()) {
+                return true;
+            }
+
+            String[] value = annotation.value();
+            for (String v : value) {
+                if (views.contains(v)) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    @Override
+    public boolean shouldSkipClass(Class<?> clazz) {
+        return false;
+    }
+
+}

+ 18 - 0
mirage-core/src/main/java/com/mirage/core/gson/GsonProperty.java

@@ -0,0 +1,18 @@
+package com.mirage.core.gson;
+
+import java.lang.annotation.*;
+
+/**
+ * Created by hzlinhai on 2020/1/9.
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface GsonProperty {
+
+    /**
+     * the desired name of the field when it is serialized or deserialized
+     */
+    String value();
+}
+

+ 27 - 0
mirage-core/src/main/java/com/mirage/core/gson/GsonView.java

@@ -0,0 +1,27 @@
+package com.mirage.core.gson;
+
+/**
+ * Created by hzlinhai on 2020/1/9.
+ */
+public class GsonView {
+
+    public static final String BASE = "BASE";
+
+    public static final String EXPOSE = "EXPOSE";
+
+    public static final String APP = "APP";
+
+    public static final String CACHE = "CACHE";
+
+    public static final String SERVER = "SERVER";
+
+    public static final String MINIMUM_IGNORE = "MINIMUM_IGNORE";
+
+    public static final String SELF_IGNORE = "SELF_INFO";
+
+    public static final String LOG = "LOG";
+
+    private GsonView() {
+    }
+
+}

+ 24 - 0
mirage-core/src/main/java/com/mirage/core/gson/JsonFieldNamingPolicy.java

@@ -0,0 +1,24 @@
+package com.mirage.core.gson;
+
+import com.google.gson.FieldNamingStrategy;
+import org.apache.commons.lang3.StringUtils;
+
+import java.lang.reflect.Field;
+
+/**
+ * Created by hzlinhai on 2020/1/9.
+ */
+public enum JsonFieldNamingPolicy implements FieldNamingStrategy {
+
+    GsonProperty() {
+        @Override
+        public String translateName(Field f) {
+            GsonProperty annotation = f.getAnnotation(GsonProperty.class);
+            if (annotation != null && StringUtils.isNotBlank(annotation.value())) {
+                return annotation.value();
+            }
+            return f.getName();
+        }
+    }
+
+}

+ 34 - 0
mirage-core/src/main/java/com/mirage/core/gson/ParameterizedTypeImpl.java

@@ -0,0 +1,34 @@
+package com.mirage.core.gson;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.List;
+
+/**
+ * @Description gson type
+ * @Date 2021/4/22 20:41
+ * @Created by chenhao17
+ */
+public class ParameterizedTypeImpl implements ParameterizedType {
+
+    private Class clazz;
+
+    public ParameterizedTypeImpl(Class clz) {
+        this.clazz = clz;
+    }
+
+    @Override
+    public Type[] getActualTypeArguments() {
+        return new Type[]{clazz};
+    }
+
+    @Override
+    public Type getRawType() {
+        return List.class;
+    }
+
+    @Override
+    public Type getOwnerType() {
+        return null;
+    }
+}

+ 18 - 0
mirage-core/src/main/java/com/mirage/core/localcache/ExpireTimeBasedTag.java

@@ -0,0 +1,18 @@
+package com.mirage.core.localcache;
+
+public enum ExpireTimeBasedTag {
+    ONE_MINUTES(60), ONE_HOUR(3600), HALF_HOUR(1800), TEN_MINUTES(600), ONE_DAY(86400);
+    private int expireSeconds;
+    private int maxObjects = 100000;
+    ExpireTimeBasedTag(int expireSeconds) {
+        this.expireSeconds = expireSeconds;
+    }
+
+    public int getExpireSeconds() {
+        return expireSeconds;
+    }
+
+    public int getMaxObjects() {
+        return maxObjects;
+    }
+}

+ 51 - 0
mirage-core/src/main/java/com/mirage/core/localcache/LocalCache.java

@@ -0,0 +1,51 @@
+package com.mirage.core.localcache;
+
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+
+public class LocalCache {
+    private Map<ExpireTimeBasedTag, Cache<Object, Object>> map = Maps.newHashMap();
+
+    private Cache<Object, Object> getCache(ExpireTimeBasedTag tag){
+        Cache<Object, Object> cache = map.get(tag);
+        if(cache == null){
+            synchronized (LocalCache.this) {
+                cache = map.get(tag);
+                if(cache == null){
+                    cache = CacheBuilder
+                            .newBuilder().expireAfterWrite(tag.getExpireSeconds(), TimeUnit.SECONDS)
+                            .maximumSize(tag.getMaxObjects()).recordStats()
+                            .build();
+                    map.put(tag, cache);
+                }
+            }
+        }
+        return cache;
+    }
+
+    @SuppressWarnings("unchecked")
+    public <K,V> V get(ExpireTimeBasedTag tag, K key, Callable<V> loader) throws ExecutionException {
+        return (V) getCache(tag).get(key, loader);
+    }
+
+    public <K,V> List<V> get(ExpireTimeBasedTag tag, Set<K> keys, Callable<V> loader) throws ExecutionException {
+        List<V> accounts = Lists.newArrayList();
+        for (K k: keys) {
+            accounts.add(get(tag, k, loader));
+        }
+        return accounts;
+    }
+
+    public <K> void remove(ExpireTimeBasedTag tag, K key){
+        getCache(tag).invalidate(key);
+    }
+}

+ 59 - 0
mirage-core/src/main/java/com/mirage/core/meta/AppCode.java

@@ -0,0 +1,59 @@
+package com.mirage.core.meta;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author hzlinhai
+ */
+public enum AppCode {
+    /**
+     * wire [0,800)
+     */
+    OK(0, "OK"), // 成功
+    /**
+     * 异常 :弹窗显示message的内容
+     */
+    EXCEPTION_TOAST_SHOW(-1, "toast"),
+    EXCEPTION_WINDOW_SHOW(-2, "notify"),
+    UNAUTHORIZED(401, "Unauthorized"), // 没有权限
+    FORBIDDEN(403, "Forbidden"), // 禁止操作
+    UNKNOWN(500, "Unknown"), // 未知错误
+    /**
+     * 接口预处理错误码
+     */
+    BAD_REQUEST(810, "Bad Parameter"), // 非法参数
+    DUPLICATE(813, "DUPLICATE"), // 重复
+    BAD_NUMBER_FORMAT(817, "Bad Number Format"),
+
+    ;
+
+    private int code;
+    private String message;
+
+    AppCode(int code, String message) {
+        this.code = code;
+        this.message = message;
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    private static final Map<Integer, AppCode> lookup = new HashMap<>();
+
+    static {
+        for (AppCode o : AppCode.values()) {
+            lookup.put(o.getCode(), o);
+        }
+    }
+
+    public static AppCode lookup(int code) {
+        return lookup.get(code);
+    }
+
+}

+ 8 - 0
mirage-core/src/main/java/com/mirage/core/meta/AuthType.java

@@ -0,0 +1,8 @@
+package com.mirage.core.meta;
+
+/**
+ * Created by weiliang on 2017/12/8.
+ */
+public enum AuthType {
+    SIGN, OPEN, COOKIES, ADMIN
+}

+ 75 - 0
mirage-core/src/main/java/com/mirage/core/meta/BusinessTypeEnum.java

@@ -0,0 +1,75 @@
+package com.mirage.core.meta;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Created by hzlinhai on 2020/11/19.
+ */
+public enum BusinessTypeEnum {
+    ENGLISH(1, CourseTypeEnum.ENGLISH.getDescription(), LearnPlatformTypeEnum.PANDA.getCode(), LearnPlatformTypeEnum.PANDA.getDescription()),
+    WEIQI(2, CourseTypeEnum.WEIQI.getDescription(), LearnPlatformTypeEnum.PANDA.getCode(), LearnPlatformTypeEnum.PANDA.getDescription()),
+    KADA(3, CourseTypeEnum.KADA.getDescription(), LearnPlatformTypeEnum.KADA.getCode(), LearnPlatformTypeEnum.KADA.getDescription()),
+    LEDU(4, CourseTypeEnum.YUWEN.getDescription(), LearnPlatformTypeEnum.LEDU.getCode(), LearnPlatformTypeEnum.LEDU.getDescription()),
+    ART(5,CourseTypeEnum.ART.getDescription(), LearnPlatformTypeEnum.EXTERNAL_CHANNELS.getCode(), LearnPlatformTypeEnum.EXTERNAL_CHANNELS.getDescription()),
+    MATH(6, CourseTypeEnum.MATH.getDescription(), LearnPlatformTypeEnum.MATH.getCode(), LearnPlatformTypeEnum.MATH.getDescription());
+
+    private Integer businessType;
+
+    private String businessName;
+
+    private Integer learnPlatformType;
+
+    private String learnPlatformName;
+
+    private static final Map<Integer, BusinessTypeEnum> lookup = new HashMap<>();
+
+    static {
+        for (BusinessTypeEnum o : BusinessTypeEnum.values()) {
+            lookup.put(o.getBusinessType(), o);
+        }
+    }
+
+    public static BusinessTypeEnum lookup(int code) {
+        return lookup.get(code);
+    }
+
+    BusinessTypeEnum(Integer businessType, String businessName, Integer learnPlatformType, String learnPlatformName){
+        this.businessType = businessType;
+        this.businessName = businessName;
+        this.learnPlatformType = learnPlatformType;
+        this.learnPlatformName = learnPlatformName;
+    }
+
+    public Integer getBusinessType() {
+        return businessType;
+    }
+
+    public void setBusinessType(Integer businessType) {
+        this.businessType = businessType;
+    }
+
+    public String getBusinessName() {
+        return businessName;
+    }
+
+    public void setBusinessName(String businessName) {
+        this.businessName = businessName;
+    }
+
+    public Integer getLearnPlatformType() {
+        return learnPlatformType;
+    }
+
+    public void setLearnPlatformType(Integer learnPlatformType) {
+        this.learnPlatformType = learnPlatformType;
+    }
+
+    public String getLearnPlatformName() {
+        return learnPlatformName;
+    }
+
+    public void setLearnPlatformName(String learnPlatformName) {
+        this.learnPlatformName = learnPlatformName;
+    }
+}

+ 46 - 0
mirage-core/src/main/java/com/mirage/core/meta/Context.java

@@ -0,0 +1,46 @@
+package com.mirage.core.meta;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class Context {
+
+    private static final Logger logger = LoggerFactory.getLogger(Context.class);
+
+    private Map<String, Object> map;
+
+    public Map<String, Object> map() {
+        return this.map;
+    }
+
+    public Object put(String key, Object value) {
+        return this.map != null ? this.map.put(key, value) : null;
+    }
+
+    public Object remove(String key) {
+        return this.map != null ? this.map.remove(key) : null;
+    }
+
+    public Object get(String key) {
+        return this.map != null ? this.map.get(key) : null;
+    }
+
+    private static final ThreadLocal<Context> CONTEXT_THREAD_LOCAL = ThreadLocal.withInitial(Context::new);
+
+    public static Context current() {
+        return CONTEXT_THREAD_LOCAL.get();
+    }
+
+    public static void start() {
+       Context context = current();
+        context.map = new LinkedHashMap<>();
+    }
+
+    public static void end() {
+        Context context = current();
+        context.map = null;
+    }
+}

+ 29 - 0
mirage-core/src/main/java/com/mirage/core/meta/CourseTypeEnum.java

@@ -0,0 +1,29 @@
+package com.mirage.core.meta;
+
+public enum CourseTypeEnum {
+    /**
+     * 同步初始课程类型枚举
+     */
+    KADA(1, "编程"),
+    YUWEN(2, "语文"),
+    MATH(3, "数学"),
+    ENGLISH(4, "英语"),
+    WEIQI(5, "围棋"),
+    ART(6, "美术");
+
+    Integer code;
+    String description;
+
+    CourseTypeEnum(Integer code, String description) {
+        this.code = code;
+        this.description = description;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public Integer getCode() {
+        return code;
+    }
+}

+ 27 - 0
mirage-core/src/main/java/com/mirage/core/meta/DownloadBean.java

@@ -0,0 +1,27 @@
+package com.mirage.core.meta;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * @Description: 下载对象
+ * @Author: 万贤良
+ * @Email: wanxianliang@corp.netease.com
+ * @Date: 2021/1/4
+ */
+@Data
+public class DownloadBean implements Serializable {
+    /**
+     * 总页数
+     */
+    private Integer totalPage;
+    /**
+     * 当前页数
+     */
+    private Integer currentPage;
+    /**
+     * 下载链接
+     */
+    private String downloadUrl;
+}

+ 21 - 0
mirage-core/src/main/java/com/mirage/core/meta/HeaderKeys.java

@@ -0,0 +1,21 @@
+package com.mirage.core.meta;
+
+/**
+ * Created by hzlinhai on 2020/8/18.
+ */
+public class HeaderKeys {
+
+    public static final String KEY_X_B3_TraceId = "PANDA-X-B3-TraceId";
+    public static final String KEY_MQ_TOPIC = "Mq-Topic";
+
+    public static final String KEY_UID = "PANDA-UID"; // userInfo对象
+    public static final String KEY_DEBUG = "PANDA-DEBUG";
+    public static final String KEY_CLIENT_TYPE = "CLIENT-TYPE";
+    public static final String KEY_TOKEN = "PANDA-TOKEN";
+
+    public static final String KEY_APP_KEY = "AppKey"; // appKey
+    public static final String KEY_CUR_TIME = "CurTime"; // 当前时间
+    public static final String KEY_NONCE = "Nonce"; // 随机串
+    public static final String KEY_CHECKSUM = "CheckSum";
+
+}

+ 12 - 0
mirage-core/src/main/java/com/mirage/core/meta/HttpResponse.java

@@ -0,0 +1,12 @@
+package com.mirage.core.meta;
+
+import org.apache.http.Header;
+
+/**
+ * Created by hzlinhai on 2020/11/2.
+ */
+public class HttpResponse {
+    public int code;
+    public String body;
+    public Header[] headers;
+}

+ 36 - 0
mirage-core/src/main/java/com/mirage/core/meta/LearnPlatformTypeEnum.java

@@ -0,0 +1,36 @@
+package com.mirage.core.meta;
+
+/**
+ * @Description: 学习平台枚举
+ * @Author: 万贤良
+ * @Email: wanxianliang@corp.netease.com
+ * @Date: 2020/11/19
+ */
+public enum LearnPlatformTypeEnum {
+    /**
+     * 学习平台枚举
+     */
+    PANDA(1, "熊猫未来学院"),
+    LEDU(2, "乐读"),
+    MATH(3, "数学"),
+    ENGLISH(4, "少儿英语"),
+    KADA(5, "卡搭"),
+    YOUDAO(6, "精品课"),
+    EXTERNAL_CHANNELS(7, "外部渠道");
+
+    Integer code;
+    String description;
+
+    LearnPlatformTypeEnum(Integer code, String description) {
+        this.code = code;
+        this.description = description;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public Integer getCode() {
+        return code;
+    }
+}

+ 38 - 0
mirage-core/src/main/java/com/mirage/core/meta/LogBean.java

@@ -0,0 +1,38 @@
+package com.mirage.core.meta;
+
+/**
+ *
+ */
+public interface LogBean {
+
+    /**
+     * add prop
+     *
+     * @param key
+     * @param value
+     * @return
+     */
+    LogBean addProp(String key, Object value);
+
+    /**
+     * del prop
+     *
+     * @param key
+     * @return
+     */
+    Object delProp(String key);
+
+
+    /**
+     * get prop
+     *
+     * @param key
+     * @return
+     */
+    Object getProp(String key);
+
+    /**
+     * print logbean
+     */
+    void log();
+}

+ 31 - 0
mirage-core/src/main/java/com/mirage/core/meta/PageQuery.java

@@ -0,0 +1,31 @@
+package com.mirage.core.meta;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * @Description: 分页信息
+ * @Author: 万贤良
+ * @Email: wanxianliang@corp.netease.com
+ * @Date: 2020/8/21 3:53 下午
+ */
+@Data
+public class PageQuery implements Serializable {
+    /**
+     * 每页数量
+     */
+    int pageSize;
+    /**
+     * 总数量
+     */
+    long totalCount;
+    /**
+     * 当前页下标
+     */
+    int pageIndex;
+    /**
+     * 总页数
+     */
+    int pageCount;
+}

+ 32 - 0
mirage-core/src/main/java/com/mirage/core/meta/PageResultVo.java

@@ -0,0 +1,32 @@
+package com.mirage.core.meta;
+
+import lombok.Data;
+import lombok.ToString;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @Description: 分页结果
+ * @Author: 万贤良
+ * @Email: wanxianliang@corp.netease.com
+ * @Date: 2020/8/21 3:52 下午
+ */
+@Data
+@ToString
+public class PageResultVo<T> implements Serializable {
+    private static final long serialVersionUID = 5921979220309470281L;
+    private PageQuery query;
+    private List<T> list;
+
+    public PageResultVo(){
+        query= new PageQuery();
+        list= Collections.emptyList();
+    }
+
+    public PageResultVo(PageQuery pageQuery, List<T> list){
+        this.query = pageQuery;
+        this.list = list;
+    }
+}

+ 107 - 0
mirage-core/src/main/java/com/mirage/core/meta/PageUtil.java

@@ -0,0 +1,107 @@
+package com.mirage.core.meta;
+
+import com.github.pagehelper.Page;
+import com.github.pagehelper.PageInfo;
+import com.mirage.core.utils.BeanConvertUtil;
+import org.springframework.util.CollectionUtils;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @Description: 分页辅助工具类
+ * @Author: 万贤良
+ * @Email: wanxianliang@corp.netease.com
+ * @Date: 2020/8/21 3:59 下午
+ */
+public class PageUtil {
+    /**
+     * 更具totalCount 即分页信息获取分页对象
+     * @param pageSize
+     * @param pageIndex
+     * @param totalCount
+     */
+    public static PageQuery getPageQuery(Integer pageSize, Integer pageIndex, Long totalCount) {
+        if (pageSize == null || pageIndex == null || totalCount == null) {
+            return new PageQuery();
+        }
+        PageQuery pageQuery = new PageQuery();
+        pageQuery.setPageIndex(pageIndex);
+        pageQuery.setPageSize(pageSize);
+        pageQuery.setTotalCount(totalCount);
+        Integer pageCount = Math.toIntExact((totalCount % pageSize == 0) ? totalCount / pageSize : totalCount / pageSize + 1);
+        pageQuery.setPageCount(pageCount);
+        return pageQuery;
+    }
+
+    /**
+     * 装换Page对象
+     * @param pageInfo pageHelper对象
+     * @param <T>
+     */
+    public static <T> PageResultVo<T> convertPage(PageInfo<T> pageInfo) {
+        if (pageInfo == null) {
+            return null;
+        }
+        PageResultVo<T> pageResultVo = new PageResultVo<>();
+        PageQuery query = getPageQuery(pageInfo.getPageSize(), pageInfo.getPageNum(), pageInfo.getTotal());
+        pageResultVo.setQuery(query);
+        pageResultVo.setList(pageInfo.getList());
+        return pageResultVo;
+    }
+
+    /**
+     * 转换获得分页对象
+     * @param resultList
+     * @param <T>
+     */
+    public static <T> PageResultVo<T> convertPageResult(List<T> resultList) {
+        if (CollectionUtils.isEmpty(resultList)) {
+            PageQuery query = new PageQuery();
+            query.setPageCount(1);
+            query.setTotalCount(0L);
+            if (resultList instanceof Page) {
+                query.setPageIndex(((Page<T>) resultList).getPageNum());
+                query.setPageSize(((Page<T>) resultList).getPageSize());
+            } else {
+                query.setPageIndex(1);
+                query.setPageSize(20);
+            }
+            return new PageResultVo<>(query, Collections.emptyList());
+        }
+        PageInfo<T> pageInfo = new PageInfo<>(resultList);
+        PageResultVo<T> pageResultVo = new PageResultVo<>();
+        PageQuery query = getPageQuery(pageInfo.getPageSize(), pageInfo.getPageNum(), pageInfo.getTotal());
+        pageResultVo.setQuery(query);
+        pageResultVo.setList(pageInfo.getList());
+        return pageResultVo;
+    }
+
+    /**
+     * 转换获得分页对象
+     *
+     * @param resultList
+     * @param <T>
+     */
+    public static <T, K> PageResultVo<K> convertPageResult(List<T> resultList, Class<T> fromClass, Class<K> toClass) {
+        if (CollectionUtils.isEmpty(resultList)) {
+            PageQuery query = new PageQuery();
+            query.setPageCount(1);
+            query.setTotalCount(0L);
+            if (resultList instanceof Page) {
+                query.setPageIndex(((Page<T>) resultList).getPageNum());
+                query.setPageSize(((Page<T>) resultList).getPageSize());
+            } else {
+                query.setPageIndex(1);
+                query.setPageSize(20);
+            }
+            return new PageResultVo<>(query, Collections.emptyList());
+        }
+        PageInfo<T> pageInfo = new PageInfo<>(resultList);
+        PageResultVo<K> pageResultVo = new PageResultVo<>();
+        PageQuery query = getPageQuery(pageInfo.getPageSize(), pageInfo.getPageNum(), pageInfo.getTotal());
+        pageResultVo.setQuery(query);
+        pageResultVo.setList(BeanConvertUtil.safeConvert(pageInfo.getList(), fromClass, toClass));
+        return pageResultVo;
+    }
+}

+ 76 - 0
mirage-core/src/main/java/com/mirage/core/meta/PageUtils.java

@@ -0,0 +1,76 @@
+package com.mirage.core.meta;
+
+import com.mirage.core.utils.BeanConvertUtil;
+import org.springframework.util.CollectionUtils;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 分页工具
+ * @author zhao
+ * @version 1.0
+ * @date 2021/3/22 16:10
+ */
+public class PageUtils {
+
+    public static <T, K> PageResultVo<K> convertPageResult(PageResultVo<T> originPageResult, Class<T> fromClass, Class<K> toClass) {
+        if (originPageResult == null) {
+            return new PageResultVo<>();
+        }
+        PageResultVo<K> pageResultVo = new PageResultVo<>();
+        pageResultVo.setQuery(originPageResult.getQuery());
+        pageResultVo.setList(BeanConvertUtil.safeConvert(originPageResult.getList(), fromClass, toClass));
+        return pageResultVo;
+    }
+
+    public static <T> PageResultVo<T> paginationInMemory(PageQuery query, List<T> totalList) {
+        if (totalList == null) {
+            return emptyResult();
+        } else {
+            PageResultVo<T> result = new PageResultVo<>();
+            result.setQuery(query);
+            query.setTotalCount((long) totalList.size());
+            result.setList(pagination(totalList, query.getPageIndex(), query.getPageSize()));
+            return result;
+        }
+    }
+
+
+    public static <T> PageResultVo<T> emptyResult() {
+        PageResultVo<T> result = new PageResultVo<>();
+        PageQuery query = new PageQuery();
+        query.setTotalCount(0L);
+        result.setQuery(query);
+        result.setList(Collections.emptyList());
+        return result;
+    }
+
+    /**
+     * 分页
+     *
+     * @param records   待分页的数据
+     * @param pageIndex 当前页码
+     * @param pageSize  每页显示的条数
+     * @return 分页之后的数据
+     */
+    public static <T> List<T> pagination(Collection<T> records, int pageIndex, int pageSize) {
+        if (CollectionUtils.isEmpty(records)) {
+            return Collections.emptyList();
+        }
+        int totalCount = records.size();
+        int remainder = totalCount % pageSize;
+        int pageCount = (remainder > 0) ? totalCount / pageSize + 1 : totalCount / pageSize;
+        if (remainder == 0) {
+            return records.stream().skip((pageIndex - 1) * pageSize).limit(pageSize).collect(Collectors.toList());
+        } else {
+            if (pageIndex == pageCount) {
+                return records.stream().skip((pageIndex - 1) * pageSize).limit(totalCount).collect(Collectors.toList());
+            } else {
+                return records.stream().skip((pageIndex - 1) * pageSize).limit(pageSize).collect(Collectors.toList());
+            }
+        }
+    }
+}

+ 35 - 0
mirage-core/src/main/java/com/mirage/core/meta/PlatformTypeEnum.java

@@ -0,0 +1,35 @@
+package com.mirage.core.meta;
+
+/**
+ * @Description: 平台类型枚举
+ * @Author: 万贤良
+ * @Email: wanxianliang@corp.netease.com
+ * @Date: 2020/11/12
+ */
+public enum PlatformTypeEnum {
+    /**
+     * 平台类型枚举
+     */
+    PANDA(1, "熊猫未来学院"),
+    LEDU(2, "乐读"),
+    MATH(3, "数学"),
+    ENGLISH(4, "英语"),
+    KADA(5, "卡搭"),
+    YOUDAO(6, "精品课");
+
+    Integer code;
+    String description;
+
+    PlatformTypeEnum(Integer code, String description) {
+        this.code = code;
+        this.description = description;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public Integer getCode() {
+        return code;
+    }
+}

+ 26 - 0
mirage-core/src/main/java/com/mirage/core/meta/RpcCodeEnum.java

@@ -0,0 +1,26 @@
+package com.mirage.core.meta;
+
+/**
+ * dubbo统一错误码.
+ *
+ * Created by hzlinhai on 2020/8/20.
+ */
+public enum RpcCodeEnum {
+    SUCCESS(0),
+    PARAM_ERROR(1)
+    ;
+
+    private Integer code;
+
+    RpcCodeEnum(Integer code){
+        this.code = code;
+    }
+
+    public Integer getCode() {
+        return code;
+    }
+
+    public void setCode(Integer code) {
+        this.code = code;
+    }
+}

+ 175 - 0
mirage-core/src/main/java/com/mirage/core/meta/RpcLogBean.java

@@ -0,0 +1,175 @@
+package com.mirage.core.meta;
+
+import com.mirage.core.utils.GsonUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Created by hzlinhai on 2020/6/16.
+ */
+public class RpcLogBean implements LogBean {
+
+    private static final Logger logger = LoggerFactory.getLogger(RpcLogBean.class);
+
+    private String trace;
+
+    private String service;
+
+    private String method;
+
+    private long spend;
+
+    private String[] params;
+
+    private Map<String, Object> props = new LinkedHashMap<>();
+
+    private Object result;
+
+    private String throwableMessage;
+
+    private String remote;
+
+    private long beginTime;
+
+    public String getTrace() {
+        return trace;
+    }
+
+    public void setTrace(String trace) {
+        this.trace = trace;
+    }
+
+    public String getService() {
+        return service;
+    }
+
+    public void setService(String service) {
+        this.service = service;
+    }
+
+    public String getMethod() {
+        return method;
+    }
+
+    public void setMethod(String method) {
+        this.method = method;
+    }
+
+    public String getRemote() {
+        return remote;
+    }
+
+    public void setRemote(String remote) {
+        this.remote = remote;
+    }
+
+    public long getBeginTime() {
+        return beginTime;
+    }
+
+    public void setBeginTime(long beginTime) {
+        this.beginTime = beginTime;
+    }
+
+    public long getSpend() {
+        return spend;
+    }
+
+    public void setSpend(long spend) {
+        this.spend = spend;
+    }
+
+    public String[] getParams() {
+        return params;
+    }
+
+    public void setParams(String[] params) {
+        this.params = params;
+    }
+
+    public Map<String, Object> getProps() {
+        return props;
+    }
+
+    public void setProps(Map<String, Object> props) {
+        this.props = props;
+    }
+
+    public Object getResult() {
+        return result;
+    }
+
+    public void setResult(Object result) {
+        this.result = result;
+    }
+
+    public String getThrowableMessage() {
+        return throwableMessage;
+    }
+
+    public void setThrowableMessage(String message) {
+        this.throwableMessage = message;
+    }
+
+    @Override
+    public LogBean addProp(String key, Object value) {
+        if (this.getProps() != null) {
+            this.getProps().put(key, (value == null ? "null" : value));
+        }
+        return this;
+    }
+
+    @Override
+    public Object delProp(String key) {
+        if (this.getProps() != null) {
+            return this.getProps().remove(key);
+        }
+        return null;
+    }
+
+    @Override
+    public Object getProp(String key) {
+        if (this.getProps() != null) {
+            return this.getProps().get(key);
+        }
+        return null;
+    }
+
+    @Override
+    public void log() {
+        logger.info(GsonUtil.toJson(this));
+    }
+
+    public void print() {
+        this.print(System.currentTimeMillis());
+    }
+
+    public void print(long now) {
+        this.setSpend(now - this.getBeginTime());
+        this.log();
+    }
+
+    private static final ThreadLocal<RpcLogBean> LOG_BEAN_THREAD_LOCAL = ThreadLocal.withInitial(RpcLogBean::new);
+
+    public static RpcLogBean get() {
+        return LOG_BEAN_THREAD_LOCAL.get();
+    }
+
+    public static void remove() {
+        LOG_BEAN_THREAD_LOCAL.remove();
+    }
+
+    public static RpcLogBean start() {
+        RpcLogBean logBean = get();
+        logBean.setBeginTime(System.currentTimeMillis());
+        return logBean;
+    }
+
+    public static void end() {
+        get().print();
+        remove();
+    }
+}

+ 78 - 0
mirage-core/src/main/java/com/mirage/core/meta/ServiceStatus.java

@@ -0,0 +1,78 @@
+package com.mirage.core.meta;
+
+/**
+ * Created by hzlinhai on 2020/11/18.
+ */
+public class ServiceStatus {
+
+    private int status = 0;
+    private long lastAccessTime;
+    private int retries;
+    private int duration;
+    private int delta;
+
+    public ServiceStatus(int retries, int duration, int delta) {
+        this.retries = retries;
+        this.duration = duration;
+        this.delta = delta;
+    }
+
+    public long getLastAccessTime() {
+        return lastAccessTime;
+    }
+
+    public void setLastAccessTime(long lastAccessTime) {
+        this.lastAccessTime = lastAccessTime;
+    }
+
+    public int getRetries() {
+        return retries;
+    }
+
+    public void setRetries(int retries) {
+        this.retries = retries;
+    }
+
+    public int getDuration() {
+        return duration;
+    }
+
+    public void setDuration(int duration) {
+        this.duration = duration;
+    }
+
+    public int getDelta() {
+        return delta;
+    }
+
+    public void setDelta(int delta) {
+        this.delta = delta;
+    }
+
+    public void setOff(){
+        if(status >= 0 ){
+            status ++;
+        }else{
+            status = 1;
+        }
+    }
+
+    public void setOn(){
+        status = 0;
+    }
+
+    public boolean isOff(){
+        return status > 0;
+    }
+
+    public boolean check() throws InterruptedException {
+        int i = retries;
+        while(i-- > 0){
+            if(System.currentTimeMillis() - lastAccessTime > delta){
+                return true;
+            }
+            Thread.sleep(duration);
+        }
+        return false;
+    }
+}

+ 202 - 0
mirage-core/src/main/java/com/mirage/core/meta/WebLogBean.java

@@ -0,0 +1,202 @@
+package com.mirage.core.meta;
+
+import com.mirage.core.utils.GsonUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class WebLogBean implements LogBean {
+
+    private static final Logger logger = LoggerFactory.getLogger(WebLogBean.class);
+
+    private String path = "";
+
+    private Long uid = -1L;
+
+    private int code = 0;
+
+    private long spendTime = 0;
+
+    private long beginTime = 0;
+
+    private String trace;
+
+    private Object headers;
+
+    private Object params;
+
+    private Object payload;
+
+    private Map<String, Object> props = new LinkedHashMap<>();
+
+    private Object result;
+
+    private Object error;
+
+    private String ip = "";
+
+    public String getTrace() {
+        return trace;
+    }
+
+    public void setTrace(String trace) {
+        this.trace = trace;
+    }
+
+    public Long getUid() {
+        return uid;
+    }
+
+    public void setUid(Long uid) {
+        this.uid = uid;
+    }
+
+    public String getPath() {
+        return path;
+    }
+
+    public void setPath(String path) {
+        this.path = path;
+    }
+
+    public String getIp() {
+        return ip;
+    }
+
+    public void setIp(String ip) {
+        this.ip = ip;
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    public void setCode(int code) {
+        this.code = code;
+    }
+
+    public long getBeginTime() {
+        return beginTime;
+    }
+
+    public void setBeginTime(long beginTime) {
+        this.beginTime = beginTime;
+    }
+
+    public long getSpendTime() {
+        return spendTime;
+    }
+
+    public void setSpendTime(long spendTime) {
+        this.spendTime = spendTime;
+    }
+
+    public Object getHeaders() {
+        return headers;
+    }
+
+    public void setHeaders(Object headers) {
+        this.headers = headers;
+    }
+
+    public Object getParams() {
+        return params;
+    }
+
+    public void setParams(Object params) {
+        this.params = params;
+    }
+
+    public Object getPayload() {
+        return payload;
+    }
+
+    public void setPayload(Object payload) {
+        this.payload = payload;
+    }
+
+    public Map<String, Object> getProps() {
+        return props;
+    }
+
+    public void setProps(Map<String, Object> props) {
+        this.props = props;
+    }
+
+    public Object getResult() {
+        return result;
+    }
+
+    public void setResult(Object result) {
+        this.result = result;
+    }
+
+    public Object getError() {
+        return error;
+    }
+
+    public void setError(Object error) {
+        this.error = error;
+    }
+
+    @Override
+    public LogBean addProp(String key, Object value) {
+        if (this.getProps() != null) {
+            this.getProps().put(key, (value == null ? "null" : value));
+        }
+        return this;
+    }
+
+    @Override
+    public Object delProp(String key) {
+        if (this.getProps() != null) {
+            return this.getProps().remove(key);
+        }
+        return null;
+    }
+
+    @Override
+    public Object getProp(String key) {
+        if (this.getProps() != null) {
+            return this.getProps().get(key);
+        }
+        return null;
+    }
+
+    @Override
+    public void log() {
+        logger.info(GsonUtil.toJson(this));
+    }
+
+    public void print() {
+        this.print(System.currentTimeMillis());
+    }
+
+    public void print(long now) {
+        this.setSpendTime(now - this.getBeginTime());
+        this.log();
+    }
+
+    private static final ThreadLocal<WebLogBean> LOG_BEAN_THREAD_LOCAL = ThreadLocal.withInitial(WebLogBean::new);
+
+    public static WebLogBean get() {
+        return LOG_BEAN_THREAD_LOCAL.get();
+    }
+
+    public static void remove() {
+        LOG_BEAN_THREAD_LOCAL.remove();
+    }
+
+    public static WebLogBean start() {
+        WebLogBean logBean = get();
+        logBean.setBeginTime(System.currentTimeMillis());
+        return logBean;
+    }
+
+    public static void end() {
+        get().print();
+        remove();
+    }
+}

+ 214 - 0
mirage-core/src/main/java/com/mirage/core/utils/AppHttpClient.java

@@ -0,0 +1,214 @@
+package com.mirage.core.utils;
+
+import org.apache.commons.io.IOUtils;
+import com.mirage.core.meta.HttpResponse;
+import org.apache.http.*;
+import org.apache.http.client.config.CookieSpecs;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPut;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.client.methods.RequestBuilder;
+import org.apache.http.config.ConnectionConfig;
+import org.apache.http.config.MessageConstraints;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.message.BasicHeaderElementIterator;
+import org.apache.http.protocol.HTTP;
+import org.apache.http.util.EntityUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.nio.charset.CodingErrorAction;
+
+public class AppHttpClient {
+    private static Logger logger = LoggerFactory.getLogger(AppHttpClient.class);
+
+    private static class Holder {
+        static final AppHttpClient INSTANCE = new AppHttpClient();
+    }
+
+    private CloseableHttpClient client;
+
+    // default message constraints
+    MessageConstraints defaultMessageConstraints = MessageConstraints.custom()
+            .setMaxHeaderCount(200)
+            .setMaxLineLength(2000)
+            .build();
+
+    // default connection config
+    ConnectionConfig defaultConnectionConfig = ConnectionConfig.custom()
+            .setMalformedInputAction(CodingErrorAction.IGNORE)
+            .setUnmappableInputAction(CodingErrorAction.IGNORE)
+            .setCharset(Consts.UTF_8)
+            .setMessageConstraints(defaultMessageConstraints)
+            .build();
+
+    // default request config
+    RequestConfig defaultRequestConfig = RequestConfig.custom()
+            .setSocketTimeout(5000)
+            .setConnectTimeout(5000)
+            .setCookieSpec(CookieSpecs.IGNORE_COOKIES)
+            .setConnectionRequestTimeout(5000)
+            .build();
+
+    private AppHttpClient() {
+        client = HttpClients.custom()
+                .setDefaultConnectionConfig(defaultConnectionConfig)
+                .setDefaultRequestConfig(defaultRequestConfig)
+                .setMaxConnTotal(200)
+                .setMaxConnPerRoute(50)
+                .setKeepAliveStrategy((response, httpContext) -> {
+                    final HeaderElementIterator it = new BasicHeaderElementIterator(
+                            response.headerIterator(HTTP.CONN_KEEP_ALIVE));
+                    while (it.hasNext()) {
+                        final HeaderElement he = it.nextElement();
+                        final String param = he.getName();
+                        final String value = he.getValue();
+                        if (value != null && param.equalsIgnoreCase("timeout")) {
+                            try {
+                                return Long.parseLong(value) * 1000;
+                            } catch (final NumberFormatException ignore) {
+                            }
+                        }
+                    }
+                    return 1;
+                })
+                .build();
+
+    }
+
+    public static AppHttpClient getInstance() {
+        return Holder.INSTANCE;
+    }
+
+    public String execute(HttpUriRequest request) throws IOException {
+        CloseableHttpResponse response = client.execute(request);
+        try {
+            int statusCode = response.getStatusLine().getStatusCode();
+            if (statusCode == HttpStatus.SC_OK) {
+                return EntityUtils.toString(response.getEntity(), Consts.UTF_8);
+            }
+
+            logger.error("http execute failed, uri={}, statusCode={}", request.getURI(), statusCode);
+            EntityUtils.consumeQuietly(response.getEntity());
+            return null;
+        } finally {
+            IOUtils.closeQuietly(response);
+        }
+    }
+
+    public byte[] resultBytes(HttpUriRequest request) throws IOException {
+        CloseableHttpResponse response = client.execute(request);
+        try {
+            int statusCode = response.getStatusLine().getStatusCode();
+            logger.info("http execute, uri={}, statusCode={}, contentType:{}", request.getURI(), statusCode, response.getEntity().getContentType().getValue());
+            if (statusCode == HttpStatus.SC_OK
+                    && response.getEntity().getContentType().getValue().equals("image/jpeg")) {
+               return EntityUtils.toByteArray(response.getEntity());
+            }else if( response.getEntity().getContentType().getValue().equals("application/json")){
+                logger.error("http execute, uri={}, statusCode={} body:{}", request.getURI(), statusCode, EntityUtils.toString(response.getEntity(), Consts.UTF_8));
+            }
+            logger.error("http execute failed, uri={}, statusCode={}", request.getURI(), statusCode);
+            EntityUtils.consumeQuietly(response.getEntity());
+            return null;
+        } finally {
+            IOUtils.closeQuietly(response);
+        }
+    }
+
+    public String execute(HttpUriRequest request, int timeout) throws IOException {
+        return execute(RequestBuilder.copy(request).setConfig(
+                RequestConfig.copy(defaultRequestConfig).setSocketTimeout(timeout).build()).build());
+    }
+
+    public HttpResponse result(HttpUriRequest request, int timeout) throws IOException {
+        return result(RequestBuilder.copy(request).setConfig(
+                RequestConfig.copy(defaultRequestConfig).setSocketTimeout(timeout).build()).build());
+    }
+
+    public HttpResponse result(HttpUriRequest request) throws IOException {
+        CloseableHttpResponse response = client.execute(request);
+        try {
+            int statusCode = response.getStatusLine().getStatusCode();
+            HttpResponse httpResult = new HttpResponse();
+            httpResult.code = statusCode;
+            httpResult.body = EntityUtils.toString(response.getEntity(), Consts.UTF_8);
+            EntityUtils.consumeQuietly(response.getEntity());
+            return httpResult;
+        } finally {
+            IOUtils.closeQuietly(response);
+        }
+    }
+
+    public HttpResponse result(HttpUriRequest request, int timeout, boolean needHeader) throws IOException{
+        return result(RequestBuilder.copy(request).setConfig(
+                RequestConfig.copy(defaultRequestConfig).setSocketTimeout(timeout).build()).build(), needHeader);
+    }
+
+
+    public HttpResponse result(HttpUriRequest request, boolean needHeader) throws IOException{
+        CloseableHttpResponse response = client.execute(request);
+        try {
+            int statusCode = response.getStatusLine().getStatusCode();
+            HttpResponse httpResult = new HttpResponse();
+            httpResult.code = statusCode;
+            httpResult.body = EntityUtils.toString(response.getEntity(), Consts.UTF_8);
+            httpResult.headers = response.getAllHeaders();
+            EntityUtils.consumeQuietly(response.getEntity());
+            return httpResult;
+        } finally {
+            IOUtils.closeQuietly(response);
+        }
+    }
+    
+    /**
+     * 发送put请求
+     * @param
+     * @param
+     * @return null : 表示失败
+     * @throws IOException
+     */
+	public String putResult(String url, String paramsJson) {
+		logger.info("http execute put, uri={}, paramsJson={}", url, paramsJson);
+		CloseableHttpResponse response = null;
+		try {
+			HttpPut httpput = new HttpPut(url);
+	
+			// 设置header
+			httpput.setHeader("Content-type", "application/json");
+			// 组织请求参数
+			StringEntity stringEntity = new StringEntity(paramsJson,"UTF-8");
+			httpput.setEntity(stringEntity);
+	
+			response = client.execute(httpput);
+
+			int statusCode = response.getStatusLine().getStatusCode();
+			if (statusCode == HttpStatus.SC_OK) {
+				String result = EntityUtils.toString(response.getEntity(), Consts.UTF_8);
+				logger.info("http execute put, uri={}, paramsJson={}, result:{}", url, paramsJson, result);
+				return result;
+			} else {
+				HttpEntity responseEntity = response.getEntity();
+				if (responseEntity != null) {
+					String resultString = IOUtils.toString(responseEntity.getContent());
+					logger.info("http execute put, response status is not OK. uri={}, paramsJson={}", url, paramsJson);
+				}
+			}
+
+			logger.error("http execute put failed, uri={}, paramsJson={}, response status={}", url, paramsJson, statusCode);
+			EntityUtils.consumeQuietly(response.getEntity());
+			return null;
+		} catch (Exception ex) {
+			logger.error("http execute put exception, uri={}, paramsJson={}", url, paramsJson);
+			return null;
+		} finally {
+			if(response != null) {
+				IOUtils.closeQuietly(response);
+			}
+		}
+	}
+    
+}

+ 99 - 0
mirage-core/src/main/java/com/mirage/core/utils/AppResult.java

@@ -0,0 +1,99 @@
+package com.mirage.core.utils;
+
+import com.mirage.core.exception.AppRuntimeException;
+import com.mirage.core.meta.AppCode;
+import org.springframework.http.HttpStatus;
+
+/**
+ * Created by hzlinhai on 2024/5/15.
+ */
+public class AppResult {
+    private Object result;
+    private int code;
+    private String message;
+    private Long currentTime;
+
+    public AppResult(Object result) {
+        this.code = AppCode.OK.getCode();
+        this.message = AppCode.OK.getMessage();
+        this.currentTime = System.currentTimeMillis();
+        this.result = result;
+    }
+
+    public AppResult(int code, String message) {
+        this.code = AppCode.OK.getCode();
+        this.message = AppCode.OK.getMessage();
+        this.currentTime = System.currentTimeMillis();
+        this.code = code;
+        this.message = message;
+    }
+
+    public AppResult(Object result, int code, String message) {
+        this.code = AppCode.OK.getCode();
+        this.message = AppCode.OK.getMessage();
+        this.currentTime = System.currentTimeMillis();
+        this.result = result;
+        this.code = code;
+        this.message = message;
+    }
+
+    public AppResult(AppCode appCode) {
+        this.code = AppCode.OK.getCode();
+        this.message = AppCode.OK.getMessage();
+        this.currentTime = System.currentTimeMillis();
+        this.code = appCode.getCode();
+        this.message = appCode.getMessage();
+    }
+
+    public AppResult(AppRuntimeException e) {
+        this.code = AppCode.OK.getCode();
+        this.message = AppCode.OK.getMessage();
+        this.currentTime = System.currentTimeMillis();
+        this.code = e.getCode();
+        this.message = e.getMessage();
+        if (e.getResult() != null) {
+            this.result = e.getResult();
+        }
+
+    }
+
+    public AppResult(HttpStatus httpStatus) {
+        this.code = AppCode.OK.getCode();
+        this.message = AppCode.OK.getMessage();
+        this.currentTime = System.currentTimeMillis();
+        this.code = httpStatus.value();
+        this.message = httpStatus.getReasonPhrase();
+    }
+
+    public Object getResult() {
+        return this.result;
+    }
+
+    public void setResult(Object result) {
+        this.result = result;
+    }
+
+    public int getCode() {
+        return this.code;
+    }
+
+    public void setCode(int code) {
+        this.code = code;
+    }
+
+    public String getMessage() {
+        return this.message;
+    }
+
+    public void setMessage(String message) {
+        this.message = message;
+    }
+
+    public Long getCurrentTime() {
+        return this.currentTime;
+    }
+
+    public void setCurrentTime(Long currentTime) {
+        this.currentTime = currentTime;
+    }
+}

+ 104 - 0
mirage-core/src/main/java/com/mirage/core/utils/AppUtil.java

@@ -0,0 +1,104 @@
+package com.mirage.core.utils;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.Maps;
+import org.apache.commons.codec.binary.Hex;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.security.SecureRandom;
+import java.util.Map;
+import java.util.UUID;
+
+public class AppUtil {
+    private static final Logger logger = LoggerFactory.getLogger(AppUtil.class);
+
+    public static String generateAppSecret() {
+        int len = 6;
+        byte[] bytes = new byte[len];
+        for (int i = 0; i < len; i++) {
+            bytes[i] = (byte) (Math.random() * 256);
+        }
+        return Hex.encodeHexString(bytes);
+    }
+
+    public static String generateAppKey() {
+        return UUID.randomUUID().toString().replaceAll("-", "");
+    }
+    public static String generateAppId() {
+        return UUID.randomUUID().toString().replaceAll("-", "");
+    }
+    public static String generateAccid() {
+        return UUID.randomUUID().toString().replaceAll("-", "");
+    }
+    public static String generateVkeyForTeam(String tid) {
+        return "team|" + tid;
+    }
+    public static String generateTid() {
+        return UUID.randomUUID().toString().replaceAll("-", "");
+    }
+    public static String generateTraceId() {
+        return UUID.randomUUID().toString().replaceAll("-", "");
+    }
+
+
+    public static String generateVerifyCodeKey() {
+        return UUID.randomUUID().toString().replaceAll("-", "");
+    }
+
+
+    public static Map<String, String> parseUrsResult(String ursResult) {
+        String[] splits = ursResult.split("&");
+
+        Map<String, String> map = Maps.newHashMap();
+
+        for (String split : splits) {
+            if (Strings.isNullOrEmpty(split) || !split.contains("=")) {
+                continue;
+            }
+            String[] parts = split.split("=");
+            String key = parts[0].trim();
+            String value = parts[1].trim();
+            map.put(key, value);
+        }
+        return map;
+    }
+
+    public static String getCheckSum(String secret, Iterable<String> strings) {
+        StringBuilder data = new StringBuilder(secret);
+        for (String string : strings) {
+            data.append(string);
+        }
+        return getCheckSum(data.toString());
+    }
+
+    public static String getCheckSumOut(String secret, String appKey, String curTime, String nonce, String bodySum){
+        String data = secret + appKey + curTime + nonce + bodySum;
+        return getCheckSum(data);
+    }
+
+    public static String getCheckSum(String data) {
+        return DigestUtils.sha1Hex(data);
+    }
+
+    public static String generateUid(){
+        return UUID.randomUUID().toString().replaceAll("-", "");
+    }
+
+    public static String generateAppRegisterAuthCode() {
+        return UUID.randomUUID().toString().replaceAll("-", "");
+    }
+
+    public static String randomUniqueId(){
+        SecureRandom random = new SecureRandom();
+        StringBuilder sb = new StringBuilder();
+
+        for (int i = 0; i < 8; i++) {
+            // 生成一个大写字母(65-90)或小写字母(97-122)
+            int letter = random.nextInt(25) % 26 + (int)'a';
+            sb.append((char)letter);
+        }
+        return sb.toString();
+    }
+}

+ 51 - 0
mirage-core/src/main/java/com/mirage/core/utils/BeanConvertUtil.java

@@ -0,0 +1,51 @@
+package com.mirage.core.utils;
+
+import org.springframework.cglib.beans.BeanCopier;
+import org.springframework.util.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @Description: Bean操作工具类
+ * @Author: 万贤良
+ * @Email: wanxianliang@corp.netease.com
+ * @Date: 2020/8/21 3:30 下午
+ */
+public class BeanConvertUtil {
+
+    public static <FROM, TO> TO safeConvert(FROM dto, Class<FROM> fromClass, Class<TO> toClass) {
+        try {
+
+            if (dto == null) {
+                return null;
+            }
+
+            TO newDto = null;
+            newDto = toClass.newInstance();
+
+            BeanCopier copier = BeanCopier.create(fromClass, toClass, false);
+            copier.copy(dto, newDto, null);
+            return newDto;
+
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    public static <FROM, TO> List<TO> safeConvert(List<FROM> dtos, Class<FROM> fromClass, Class<TO> toClass) {
+        try {
+            if (CollectionUtils.isEmpty(dtos)) {
+                return new ArrayList<TO>(0);
+            }
+            List<TO> newDtos = new ArrayList<TO>();
+            for (FROM dto : dtos) {
+                newDtos.add(safeConvert(dto, fromClass, toClass));
+            }
+            return newDtos;
+        } catch (Exception e) {
+            return Collections.emptyList();
+        }
+    }
+}

+ 44 - 0
mirage-core/src/main/java/com/mirage/core/utils/CheckSumBuilder.java

@@ -0,0 +1,44 @@
+package com.mirage.core.utils;
+
+import java.security.MessageDigest;
+
+/**
+ * Created by hzlinhai on 2020/11/23.
+ */
+public class CheckSumBuilder {
+
+    // 计算并获取CheckSum
+    public static String getCheckSum(String appSecret, String nonce, String curTime) {
+        return encode("sha1", appSecret + nonce + curTime);
+    }
+
+    // 计算并获取md5值
+    public static String getMD5(String requestBody) {
+        return encode("md5", requestBody);
+    }
+
+    private static String encode(String algorithm, String value) {
+        if (value == null) {
+            return null;
+        }
+        try {
+            MessageDigest messageDigest
+                    = MessageDigest.getInstance(algorithm);
+            messageDigest.update(value.getBytes());
+            return getFormattedText(messageDigest.digest());
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+    private static String getFormattedText(byte[] bytes) {
+        int len = bytes.length;
+        StringBuilder buf = new StringBuilder(len * 2);
+        for (int j = 0; j < len; j++) {
+            buf.append(HEX_DIGITS[(bytes[j] >> 4) & 0x0f]);
+            buf.append(HEX_DIGITS[bytes[j] & 0x0f]);
+        }
+        return buf.toString();
+    }
+    private static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5',
+            '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+}

+ 16 - 0
mirage-core/src/main/java/com/mirage/core/utils/Constants.java

@@ -0,0 +1,16 @@
+package com.mirage.core.utils;
+
+/**
+ * Created by hzlinhai on 2025/1/2.
+ */
+public class Constants {
+
+    /**
+     * 小程序accessToken缓存key
+     */
+    public static final String REDIS_MIN_PROGRAM_ACCESS_TOKEN = "redis_min_program_account_token";
+
+    public static final String REDIS_MIRAGE_LOGIN_SESSION = "redis_mirage_login_session_";
+
+
+}

+ 34 - 0
mirage-core/src/main/java/com/mirage/core/utils/DateUtil.java

@@ -0,0 +1,34 @@
+package com.mirage.core.utils;
+
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * Created by hzlinhai on 2021/1/4.
+ */
+public class DateUtil {
+
+    public static String getDateTimeAsString(LocalDateTime localDateTime, String format) {
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format);
+        return localDateTime.format(formatter);
+    }
+
+    public static LocalDateTime getDateTimeOfTimestamp(long timestamp) {
+        Instant instant = Instant.ofEpochMilli(timestamp);
+        ZoneId zone = ZoneId.systemDefault();
+        return LocalDateTime.ofInstant(instant, zone);
+    }
+
+    public static long getTimestampOfDateTime(LocalDateTime localDateTime) {
+        ZoneId zone = ZoneId.systemDefault();
+        Instant instant = localDateTime.atZone(zone).toInstant();
+        return instant.toEpochMilli();
+    }
+
+    public static LocalDateTime parseStringToDateTime(String time, String format) {
+        DateTimeFormatter df = DateTimeFormatter.ofPattern(format);
+        return LocalDateTime.parse(time, df);
+    }
+}

+ 16 - 0
mirage-core/src/main/java/com/mirage/core/utils/ExcelRowData.java

@@ -0,0 +1,16 @@
+package com.mirage.core.utils;
+
+import java.util.List;
+
+/**
+ * excel行数据接口
+ * @author zhaojindong
+ *
+ */
+public interface ExcelRowData {
+	/**
+	 * 生成excel行数据
+	 * @return
+	 */
+	List<String> toExcelRowData();
+}

+ 60 - 0
mirage-core/src/main/java/com/mirage/core/utils/ExcelUtil.java

@@ -0,0 +1,60 @@
+package com.mirage.core.utils;
+
+import org.apache.poi.hssf.usermodel.HSSFRow;
+import org.apache.poi.hssf.usermodel.HSSFSheet;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.List;
+
+
+public class ExcelUtil {
+	private static final Logger logger = LoggerFactory.getLogger(ExcelUtil.class);
+	
+	/**
+	 * 写数据到本地excel文件
+	 * @param localFilePath
+	 * @param headDataList
+	 * @param rowDataList
+	 * @throws IOException
+	 */
+	public static void writeDate2Excel(String localFilePath, List<String> headDataList, List<ExcelRowData> rowDataList) throws IOException {
+    	logger.info("写excel文件到本地。localFilePath:{}",localFilePath);
+        //创建一个excel文件
+        HSSFWorkbook hssfWorkbook = new HSSFWorkbook();
+        //创建工作簿
+        HSSFSheet sheet = hssfWorkbook.createSheet();
+        
+        //创建标题行
+        HSSFRow headRow = sheet.createRow(0);
+        for(int i=0;i<headDataList.size();i++) {
+        	headRow.createCell(i).setCellValue(headDataList.get(i));
+        }
+
+        //遍历数据,创建数据行
+        for (int i = 1;i<(rowDataList.size()+1);i++) {
+        	ExcelRowData excelRowData = rowDataList.get(i-1);
+        	//创建一个行
+        	HSSFRow row = sheet.createRow(i);
+        	List<String> rowValueList = excelRowData.toExcelRowData();
+        	for(int j=0;j<rowValueList.size();j++) {
+        		row.createCell(j).setCellValue(rowValueList.get(j));
+        	}
+        }
+        
+        File file = new File(localFilePath);
+        if(!file.exists()) {
+        	logger.info("写excel文件到本地,创建文件。localFilePath:{}",localFilePath);
+        	file.createNewFile();
+        }
+
+        //写出文件,关闭流
+    	logger.info("写excel文件到本地,写数据到excel本地文件。localFilePath:{}",localFilePath);
+        hssfWorkbook.write(new FileOutputStream(file));
+        hssfWorkbook.close();
+    }
+}

+ 350 - 0
mirage-core/src/main/java/com/mirage/core/utils/GsonUtil.java

@@ -0,0 +1,350 @@
+package com.mirage.core.utils;
+
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.gson.*;
+import com.google.gson.reflect.TypeToken;
+import com.mirage.core.gson.GsonBox;
+import com.mirage.core.gson.ParameterizedTypeImpl;
+import org.apache.commons.lang3.StringUtils;
+
+import java.lang.reflect.Type;
+import java.util.*;
+
+/**
+ * Created by hzlinhai on 2020/1/9.
+ */
+public class GsonUtil {
+
+    private static final JsonParser PARSER = new JsonParser();
+
+    private GsonUtil() {
+    }
+
+    public static Gson gsonMapDouble = new GsonBuilder()
+            .registerTypeAdapter(
+                    new TypeToken<TreeMap<String, Integer>>(){}.getType(),
+                    new JsonDeserializer<TreeMap<String, Object>>() {
+                        @Override
+                        public TreeMap<String, Object> deserialize(
+                                JsonElement json, Type typeOfT,
+                                JsonDeserializationContext context) throws JsonParseException {
+
+                            TreeMap<String, Object> treeMap = new TreeMap<>();
+                            JsonObject jsonObject = json.getAsJsonObject();
+                            Set<Map.Entry<String, JsonElement>> entrySet = jsonObject.entrySet();
+                            for (Map.Entry<String, JsonElement> entry : entrySet) {
+                                treeMap.put(entry.getKey(), entry.getValue());
+                            }
+                            return treeMap;
+                        }
+                    }).create();
+
+    private static Gson gsonNoDouble = new GsonBuilder().registerTypeAdapter(Double.class, (JsonSerializer<Double>) (src, typeOfSrc, context) -> {
+        if(src == src.longValue()){
+            return new JsonPrimitive(src.longValue());
+        }else {
+            return new JsonPrimitive(src);
+        }
+    }).create();
+
+
+    public static JsonParser getParser() {
+        return PARSER;
+    }
+
+    public static String getString(JsonObject jo, String key) {
+        return getString(jo, key, null);
+    }
+
+    public static String getString(JsonObject jo, String key, String dv) {
+        return (jo.has(key) && !jo.get(key).isJsonNull()) ? jo.get(key).getAsString().trim() : dv;
+    }
+
+    public static Long getLong(JsonObject jo, String key) {
+        return getLong(jo, key, null);
+    }
+
+    public static Long getLong(JsonObject jo, String key, Long dv) {
+        return (jo.has(key) && !jo.get(key).isJsonNull()) ? (Long) jo.get(key).getAsLong() : dv;
+    }
+
+    public static Integer getInt(JsonObject jo, String key) {
+        return getInt(jo, key, null);
+    }
+
+    public static Integer getInt(JsonObject jo, String key, Integer dv) {
+        return (jo.has(key) && !jo.get(key).isJsonNull()) ? (Integer) jo.get(key).getAsInt() : dv;
+    }
+
+    public static Double getDouble(JsonObject jo, String key) {
+        return getDouble(jo, key, null);
+    }
+
+    public static Double getDouble(JsonObject jo, String key, Double dv) {
+        return (jo.has(key) && !jo.get(key).isJsonNull()) ? (Double) jo.get(key).getAsDouble() : dv;
+    }
+
+    public static Boolean getBoolean(JsonObject jo, String key) {
+        return getBoolean(jo, key, null);
+    }
+
+    public static Boolean getBoolean(JsonObject jo, String key, Boolean dv) {
+        return (jo.has(key) && !jo.get(key).isJsonNull()) ? (Boolean) jo.get(key).getAsBoolean() : dv;
+    }
+
+    public static Float getFloat(JsonObject jo, String key) {
+        return getFloat(jo, key, null);
+    }
+
+    public static Float getFloat(JsonObject jo, String key, Float dv) {
+        return (jo.has(key) && !jo.get(key).isJsonNull()) ? (Float) jo.get(key).getAsFloat() : dv;
+    }
+
+    public static <T> T getBean(JsonObject jo, String key, Class<T> clazz) {
+        if (!jo.has(key) || jo.get(key).isJsonNull()) {
+            return null;
+        }
+        return GsonUtil.fromJson(jo.get(key), clazz);
+    }
+
+    public static Boolean getBooleanCheck(JsonObject jo, String key) {
+        if(jo.has(key) && !jo.get(key).isJsonNull()){
+            JsonElement je = jo.get(key);
+            if(je.isJsonPrimitive()){
+                JsonPrimitive jp = je.getAsJsonPrimitive();
+                if(jp.isBoolean()){
+                    return jp.getAsBoolean();
+                }else {
+                    String str = jp.getAsString();
+                    if(str.equals("true")){
+                        return true;
+                    }else if(str.equals("false")){
+                        return false;
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    public static JsonObject getJsonObject(JsonObject jo, String key) {
+        JsonElement je = jo.get(key);
+        if (je != null && je.isJsonObject()) {
+            return je.getAsJsonObject();
+        }
+        return null;
+    }
+
+    public static JsonArray getJsonArray(JsonObject jo, String key) {
+        JsonElement je = jo.get(key);
+        if (je != null && je.isJsonArray()) {
+            return je.getAsJsonArray();
+        }
+        return null;
+    }
+
+    public static List<String> getStringList(JsonObject jo, String key) {
+        JsonElement je = jo.get(key);
+        if (je != null && je.isJsonArray()) {
+            JsonArray ja = je.getAsJsonArray();
+            List<String> list = new ArrayList<>();
+            for (JsonElement jsonElement : ja) {
+                list.add(jsonElement.getAsString());
+            }
+            return list;
+        }
+
+        return null;
+    }
+
+    public static List<Long> getLongList(JsonObject jo, String key) {
+        JsonElement je = jo.get(key);
+        if (je != null && je.isJsonArray()) {
+            JsonArray ja = je.getAsJsonArray();
+            List<Long> list = new ArrayList<>();
+            for (JsonElement jsonElement : ja) {
+                list.add(jsonElement.getAsLong());
+            }
+            return list;
+        }
+
+        return null;
+    }
+
+    public static <T> List<T> getList(JsonObject jo, String key, Class<T> clazz) {
+        JsonElement je = jo.get(key);
+        if (je != null && je.isJsonArray()) {
+            JsonArray ja = je.getAsJsonArray();
+            List<T> list = new ArrayList<>();
+            for (JsonElement jsonElement : ja) {
+                list.add(GsonUtil.fromJson(jsonElement, clazz));
+            }
+            return list;
+        }
+
+        return null;
+    }
+
+
+    public static <T> List<T> getList(String value, Class<T> clazz) {
+        Type type = new ParameterizedTypeImpl(clazz);
+        return  get().fromJson(value,type);
+    }
+
+
+    public static Set<Long> getLongSet(JsonObject jo, String key) {
+        JsonElement je = jo.get(key);
+        if (je != null && je.isJsonArray()) {
+            JsonArray ja = je.getAsJsonArray();
+            Set<Long> list = Sets.newHashSet();
+            for (JsonElement jsonElement : ja) {
+                list.add(jsonElement.getAsLong());
+            }
+            return list;
+        }
+
+        return null;
+    }
+
+    public static Map<Long,String> getLongStringMap(JsonObject jo, String key) {
+        JsonElement je = jo.get(key);
+        if (je != null && je.isJsonObject()) {
+            Map<Long, String> longStringMap = Maps.newHashMap();
+            JsonObject mapJo = je.getAsJsonObject();
+            for (String k : mapJo.keySet()) {
+                longStringMap.put(Long.valueOf(k), mapJo.get(k).getAsString());
+            }
+            return longStringMap;
+        }
+
+        return null;
+    }
+
+    public static Set<String> getStringSet(JsonObject jo, String key) {
+        JsonElement je = jo.get(key);
+        if (je != null && je.isJsonArray()) {
+            JsonArray ja = je.getAsJsonArray();
+            Set<String> set = Sets.newLinkedHashSet();
+            for (JsonElement jsonElement : ja) {
+                set.add(jsonElement.getAsString().trim());
+            }
+            return set;
+        }
+
+        return null;
+    }
+
+    public static Map<String, String> getStringStringMap(JsonObject jo, String key) {
+        JsonElement je = jo.get(key);
+        if (je != null && je.isJsonObject()) {
+            JsonObject jjo = je.getAsJsonObject();
+            Map<String, String> map = Maps.newHashMap();
+            for (String k : jjo.keySet()) {
+                map.put(k, jjo.get(k).getAsString());
+            }
+            return map;
+        }
+
+        return null;
+    }
+
+    public static JsonElement parseJsonElement(String str) {
+        return str == null ? null : getParser().parse(str);
+    }
+
+    public static JsonObject parseJsonObject(String src) {
+        return StringUtils.isBlank(src) ? new JsonObject() : getParser().parse(src).getAsJsonObject();
+    }
+
+    public static Map<String, String> parseStringStringMap(String s){
+        JsonObject jo = GsonUtil.parseJsonObject(s);
+        Map<String, String> map = Maps.newHashMap();
+        for (String k : jo.keySet()) {
+            map.put(k, jo.get(k).getAsString());
+        }
+        return map;
+    }
+
+    public static JsonObject toJsonObject(Object obj) {
+        return parseJsonObject(toJson(obj));
+    }
+
+    public static JsonArray parseJsonArray(String src) {
+        return StringUtils.isBlank(src) ? new JsonArray() : getParser().parse(src).getAsJsonArray();
+    }
+
+    public static JsonObject copyJsonObject(JsonObject src) {
+        JsonObject ret = new JsonObject();
+        Set<Map.Entry<String, JsonElement>> entries = src.entrySet();
+        for (Map.Entry<String, JsonElement> entry : entries) {
+            ret.add(entry.getKey(), entry.getValue());
+        }
+        return ret;
+    }
+
+    public static void copyJsonObject(JsonObject src, JsonObject ret) {
+        Set<Map.Entry<String, JsonElement>> entries = src.entrySet();
+        for (Map.Entry<String, JsonElement> entry : entries) {
+            ret.add(entry.getKey(), entry.getValue());
+        }
+    }
+
+    public static void cutJsonObject(JsonObject src, JsonObject ret) {
+        Set<Map.Entry<String, JsonElement>> entries = src.entrySet();
+        for (Map.Entry<String, JsonElement> entry : entries) {
+            ret.remove(entry.getKey());
+        }
+    }
+
+    public static JsonObject mergeJsonObject(JsonObject... jos) {
+        JsonObject ret = new JsonObject();
+        for (JsonObject jo : jos) {
+            Set<Map.Entry<String, JsonElement>> entries = jo.entrySet();
+            for (Map.Entry<String, JsonElement> entry : entries) {
+                ret.add(entry.getKey(), entry.getValue());
+            }
+        }
+        return ret;
+    }
+
+    // -------------------------
+    // GSON BASE DELEGATION
+    // -------------------------
+
+    public static Gson get() {
+        return GsonBox.BASE.gson();
+    }
+
+    public static <T> T fromJson(String str, Class<T> clazz) {
+        return get().fromJson(str, clazz);
+    }
+
+    public static <T> T fromJsonNoDouble(String str, Class<T> clazz){
+        return gsonNoDouble.fromJson(str, clazz);
+    }
+
+    public static <T> T fromJson(JsonElement json, Class<T> clazz) {
+        return get().fromJson(json, clazz);
+    }
+
+    public static <T> T fromJson(String str, Type type) {
+        return get().fromJson(str, type);
+    }
+
+    public static <T> T fromJson(JsonElement json, Type type) {
+        return get().fromJson(json, type);
+    }
+
+    public static String toJson(Object obj) {
+        return get().toJson(obj);
+    }
+    public static String toJsonNoDouble(Object obj) {
+        return gsonNoDouble.toJson(obj);
+    }
+
+    public static JsonElement toJsonTree(Object obj) {
+        return get().toJsonTree(obj);
+    }
+
+}

+ 66 - 0
mirage-core/src/main/java/com/mirage/core/utils/NosUtil.java

@@ -0,0 +1,66 @@
+package com.mirage.core.utils;
+
+import org.apache.commons.codec.binary.Base64;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * Created by hzlinhai on 2020/11/17.
+ */
+public class NosUtil {
+
+    private static Logger logger = LoggerFactory.getLogger(NosUtil.class);
+
+    private NosUtil() {
+    }
+
+    public static String base64edDigest(String src, byte[] salt, int keyLen) {
+        return base64Enc(getMD5Digest(simpleKeyXOR(src, salt, keyLen)));
+    }
+
+    public static String base64Enc(byte[] bsrc) {
+        String retVal = Base64.encodeBase64String(bsrc);
+        retVal = replaceSpecChars(retVal);
+        return retVal;
+    }
+
+    private static String replaceSpecChars(String in) {
+        char[] ca = in.toCharArray();
+        for (int i = 0; i < ca.length; i++) {
+            if (ca[i] == '/')
+                ca[i] = '_';
+            else if (ca[i] == '+')
+                ca[i] = '-';
+        }
+        return new String(ca);
+    }
+
+    private static byte[] simpleKeyXOR(String src, byte[] key, int keyLen) {
+        byte[] bsrc;
+        try {
+            bsrc = src.getBytes("utf-8");
+            for (int i = 0; i < bsrc.length; i++) {
+                bsrc[i] = (byte) (bsrc[i] ^ key[i % keyLen]);
+            }
+        } catch (UnsupportedEncodingException e) {
+            logger.error(e.getMessage(), e);
+            return null;
+        }
+        return bsrc;
+    }
+
+    private static byte[] getMD5Digest(byte[] bsrc) {
+        try {
+            MessageDigest alg = MessageDigest.getInstance("MD5");
+            alg.update(bsrc);
+            return alg.digest();
+        } catch (NoSuchAlgorithmException e) {
+            logger.error(e.getMessage(), e);
+            return null;
+        }
+    }
+}

+ 189 - 0
mirage-core/src/main/java/com/mirage/core/utils/RegexUtil.java

@@ -0,0 +1,189 @@
+package com.mirage.core.utils;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.Sets;
+
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class RegexUtil {
+
+    private static final Pattern fixedPhonePattern = Pattern
+            .compile("^(\\+?0*86-?)?(0+)?([0-9]{2,3})?([2-8][0-9]{6,7})([0-9]{1,4})?$");
+
+    private static final Pattern mobilePattern = Pattern
+            .compile("^\\+\\d{1,4}-\\d+|1[3,4,5,7,8]\\d{9}");
+
+    private static final Pattern domesticMobilePattern = Pattern
+            .compile("^1[3,4,5,7,8]\\d{9}");
+
+    // example:["18612345678", "186-1234-5678", "186 1234 5678", "186 - 1234 - 5678"]
+    private static final Pattern broadenDomesticMobilePattern = Pattern
+            .compile("1[3,4,5,7,8](\\s*-?\\s*\\d){9}");
+
+    private static final Pattern domesticMobileIllegalCharsPattern = Pattern.compile("\\s*-?\\s*");
+
+    private static final Pattern internationMobilePattern = Pattern
+            .compile("^\\+\\d{1,4}-\\d+");
+
+    private static final Pattern foreignerMobilePattern = Pattern
+            .compile("^\\d{5,}$");
+
+    private static final Pattern telecomMobilePattern = Pattern
+            .compile("^((133|149|153|173|177|180|181|189)\\d{8})|((1700|1701|1702)\\d{7})");
+
+    private static final Pattern unicomMobilePattern = Pattern
+            .compile("^((130|131|132|145|155|156|171|175|176|185|186)\\d{8})|((1707|1708|1709)\\d{7})");
+
+    private static final Pattern emailPattern = Pattern
+            .compile("^[_A-Za-z0-9-+]+(\\.[_A-Za-z0-9-]+)*@[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$");
+
+    private static final Pattern mobileEmailPattern = Pattern
+            .compile("^1[3,4,5,7,8]\\d{9}@([a-zA-Z0-9_-])+(\\.([a-zA-Z0-9_-])+)+");
+
+    private static final Pattern yidPattern = Pattern
+            .compile("[a-zA-Z][a-zA-Z0-9_]{5,19}"); // 易信号:以字母开头的字母+数字+下划线,6-20位
+
+    private static final Pattern bidPattern = Pattern.compile("^[0-9a-zA-Z_]{6,20}"); // 联盟号 字母+数字+下划线,6-20位
+
+    private static final Pattern yidPatternForFindUser = Pattern
+            .compile("[a-zA-Z][a-zA-Z0-9_]{4,19}"); // 易信号:以字母开头的字母+数字+下划线,5-20位
+
+    private static final Pattern ecpidPattern = Pattern
+            .compile(".+@ecplive\\.com");
+
+    private static final String ipDigitPattern = "(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)";
+
+    private static final Pattern ipPattern = Pattern.compile(String.format(
+            "%s(\\.%s){3}", ipDigitPattern, ipDigitPattern));
+
+    private static final Pattern internalIpPattern = Pattern
+            .compile(String.format("(10(\\.%s){3})|(172\\.(1[6-9]|2\\d|3[01])(\\.%s){2})|(192\\.168(\\.%s){2})|(127\\.0\\.0\\.%s)",
+                    ipDigitPattern, ipDigitPattern, ipDigitPattern, ipDigitPattern));
+
+    private static final Pattern numbersStringPattern = Pattern.compile("\\d+");
+
+    private static final Pattern uuidPattern = Pattern
+            .compile("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$");
+
+    public static Pattern iphoneOsPattern = Pattern
+            .compile("OSVersion: (.+?) Device: (.+)");
+
+    public static Pattern mentionPattern = Pattern.compile("@([\\u4e00-\\u9fa5A-Z0-9a-zéëêèàâäáùüûúìïîí._-]+)");
+
+    public static Pattern nickPattern = Pattern.compile("^[\\u4e00-\\u9fa5A-Z0-9a-zéëêèàâäáùüûúìïîí._-]+$");
+
+    private static final Pattern datePattern = Pattern.compile("^(19|20)\\d{2}-(1[0-2]|0[1-9])-(0[1-9]|[1-2][0-9]|3[0-1])$");
+
+    public static final String yearMonthRegEx = "^(19|20)\\d{2}-(1[0-2]|0[1-9])$";
+
+    private static final Pattern yearMonthPattern = Pattern.compile(yearMonthRegEx);
+
+    public static boolean isUUID(String str) {
+        return uuidPattern.matcher(str).matches();
+    }
+
+    public static boolean isNumbersString(String str) {
+        return numbersStringPattern.matcher(str).matches();
+    }
+
+    public static boolean isFixedPhone(String str) {
+        return fixedPhonePattern.matcher(str).matches();
+    }
+
+    public static boolean isMobile(String str) {
+        return mobilePattern.matcher(str).matches();
+    }
+
+    public static boolean isDomesticMobile(String str) {
+        return domesticMobilePattern.matcher(str).matches();
+    }
+
+    public static boolean isInternationMobile(String str) {
+        return internationMobilePattern.matcher(str).matches();
+    }
+
+    public static boolean isForeignerMobile(String str) {
+        return foreignerMobilePattern.matcher(str).matches();
+    }
+
+    public static boolean isTelecomMobile(String str) {
+        return telecomMobilePattern.matcher(str).matches();
+    }
+
+    public static boolean isUnicomMobile(String str) {
+        return unicomMobilePattern.matcher(str).matches();
+    }
+
+    public static boolean isEmail(String str) {
+        return emailPattern.matcher(str).matches();
+    }
+
+    public static boolean isMobileEmail(String str) {
+        return mobileEmailPattern.matcher(str).matches();
+    }
+
+    public static boolean isYid(String str) {
+        return yidPattern.matcher(str).matches();
+    }
+
+    public static boolean isYidForFindUser(String str) {
+        return yidPatternForFindUser.matcher(str).matches();
+    }
+
+    public static boolean isEcpid(String str) {
+        return ecpidPattern.matcher(str).matches();
+    }
+
+    public static boolean isInternalIp(String str) {
+        return internalIpPattern.matcher(str).matches();
+    }
+
+    public static boolean isIp(String str) {
+        return ipPattern.matcher(str).matches();
+    }
+
+    public static boolean isBid(String str){
+        return bidPattern.matcher(str).matches();
+    }
+
+    public static boolean isBroadenDomesticMobile(String str) {
+        return broadenDomesticMobilePattern.matcher(str).matches();
+    }
+
+    public static boolean isIphoneOs(String str) {
+        return iphoneOsPattern.matcher(str).matches();
+    }
+
+    public static String normalizeDomesticMobile(String str) {
+        return domesticMobileIllegalCharsPattern.matcher(str).replaceAll("");
+    }
+
+    public static boolean isValidNick(String nick){
+        if(Strings.isNullOrEmpty(nick)){
+            return false;
+        }
+        return nickPattern.matcher(nick).find();
+    }
+
+    public static final String validEndDateEx = "^now$|^(19|20)\\d{2}-(1[0-2]|0[1-9])$";
+
+    public static boolean isValidDate(String str) {
+        return datePattern.matcher(str).matches();
+    }
+
+    public static boolean isValidYearMonth(String str) {
+        return yearMonthPattern.matcher(str).matches();
+    }
+
+    public static Set<String> getMentionNicks(String text){
+        Matcher m = mentionPattern.matcher(text);
+        Set<String> list = Sets.newHashSet();
+        while(m.find()){
+           list.add(m.group(1));
+        }
+        return list;
+    }
+
+}

+ 123 - 0
mirage-core/src/main/java/com/mirage/core/utils/RequestUtil.java

@@ -0,0 +1,123 @@
+package com.mirage.core.utils;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.*;
+
+/**
+ * Created by hzlinhai on 2025/1/2.
+ */
+@Slf4j
+public class RequestUtil {
+
+    public static String getIp(HttpServletRequest request, boolean useInternalIp) {
+        try {
+            String header1 = request.getHeader("X-Forwarded-For");
+            String header2 = request.getHeader("Proxy-Client-IP");
+            String header3 = request.getHeader("WL-Proxy-Client-IP");
+
+            String header = null;
+            if (StringUtils.isNotBlank(header1) && !"unknown".equalsIgnoreCase(header1)) {
+                header = header1;
+            } else if (StringUtils.isNotBlank(header2) && !"unknown".equalsIgnoreCase(header2)) {
+                header = header2;
+            } else if (StringUtils.isNotBlank(header3) && !"unknown".equalsIgnoreCase(header3)) {
+                header = header3;
+            }
+
+            String realIp = null;
+            if (StringUtils.isNotBlank(header)) {
+                String[] ips = header.split(",");
+                for (String ip : ips) {
+                    // 过滤2g/3g网关添加的内网ip
+                    if (!RegexUtil.isInternalIp(ip)) {
+                        realIp = ip;
+                        break;
+                    }
+                }
+
+                // 只有内网ip并且应用允许的情况下才取内网ip
+                if (realIp == null && useInternalIp) {
+                    realIp = ips[0];
+                }
+            }
+
+            if (StringUtils.isNotBlank(realIp)) {
+                return realIp.trim();
+            }
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        }
+
+        return request.getRemoteAddr();
+    }
+
+    /**
+     * get ip (exclude internal ips)
+     *
+     * @param request
+     * @return
+     */
+    public static String getIp(HttpServletRequest request) {
+        return getIp(request, false);
+    }
+
+    /**
+     * get port
+     *
+     * @param request
+     * @return
+     */
+    public static int getPort(HttpServletRequest request) {
+        return request.getRemotePort();
+    }
+
+    /**
+     * get headers
+     *
+     * @param request
+     * @return
+     */
+    public static Map<String, List<String>> getHeaders(HttpServletRequest request) {
+        Map<String, List<String>> headers = new HashMap<String, List<String>>();
+        Enumeration<String> headerNames = request.getHeaderNames();
+        while (headerNames.hasMoreElements()) {
+            String key = headerNames.nextElement();
+            ArrayList<String> value = Collections.list(request.getHeaders(key));
+            headers.put(key, value);
+        }
+
+        return headers;
+    }
+
+    /**
+     * get body
+     *
+     * @param request
+     * @return
+     * @throws Exception
+     */
+    public static byte[] getBody(HttpServletRequest request) throws Exception {
+        int len = request.getContentLength();
+        InputStream is = request.getInputStream();
+        try {
+            if (len == -1) {
+                throw new IOException("read content length is -1!");
+            }
+
+            byte[] buffer = new byte[len];
+            if (is != null) {
+                IOUtils.readFully(is, buffer);
+            }
+
+            return buffer;
+        } finally {
+            IOUtils.closeQuietly(is);
+        }
+    }
+}

+ 60 - 0
mirage-core/src/main/java/com/mirage/core/utils/RpcResult.java

@@ -0,0 +1,60 @@
+package com.mirage.core.utils;
+
+
+import com.mirage.core.meta.RpcCodeEnum;
+
+import java.io.Serializable;
+
+/**
+ * dubbo结果封装.
+ * Created by hzlinhai on 2020/8/20.
+ */
+public class RpcResult<T> implements Serializable {
+
+    private static final long serialVersionUID = -3802081610037518451L;
+
+    private Integer code = 0;
+
+    private String message = "ok";
+
+    private T result;
+
+    public static <T> RpcResult<T> failed(RpcCodeEnum rpcCodeEnum, String message) {
+        RpcResult<T> rpcResult = new RpcResult<>();
+        rpcResult.setCode(rpcCodeEnum.getCode());
+        rpcResult.setMessage(message);
+        return rpcResult;
+    }
+
+    public static <T> RpcResult<T> success(T result) {
+        RpcResult<T> rpcResult = new RpcResult<>();
+        rpcResult.setCode(RpcCodeEnum.SUCCESS.getCode());
+        rpcResult.setMessage("success");
+        rpcResult.setResult(result);
+        return rpcResult;
+    }
+
+    public Integer getCode() {
+        return code;
+    }
+
+    public void setCode(Integer code) {
+        this.code = code;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public void setMessage(String message) {
+        this.message = message;
+    }
+
+    public T getResult() {
+        return result;
+    }
+
+    public void setResult(T result) {
+        this.result = result;
+    }
+}

+ 94 - 0
mirage-core/src/main/java/com/mirage/core/utils/StringKit.java

@@ -0,0 +1,94 @@
+package com.mirage.core.utils;
+
+import com.google.gson.JsonObject;
+import org.apache.commons.lang3.StringUtils;
+
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Created by hzlinhai on 2020/1/9.
+ */
+public class StringKit {
+
+    /**
+     * A String for null.
+     */
+    public static final String NULL = "null";
+
+    /**
+     * A String for a space character.
+     */
+    public static final String SPACE = " ";
+
+    /**
+     * The empty String {@code ""}.
+     */
+    public static final String EMPTY = "";
+
+    /**
+     * A String for linefeed LF ("\n").
+     *
+     * @see <a href="http://docs.oracle.com/javase/specs/jls/se7/html/jls-3.html#jls-3.10.6">JLF: Escape Sequences
+     * for Character and String Literals</a>
+     */
+    public static final String LF = "\n";
+
+    /**
+     * A String for carriage return CR ("\r").
+     *
+     * @see <a href="http://docs.oracle.com/javase/specs/jls/se7/html/jls-3.html#jls-3.10.6">JLF: Escape Sequences
+     * for Character and String Literals</a>
+     */
+    public static final String CR = "\r";
+
+    /**
+     * Represents a failed index search.
+     */
+    public static final int INDEX_NOT_FOUND = -1;
+
+    /**
+     * serialize
+     *
+     * @param string
+     * @return
+     */
+    public static byte[] serialize(String string) {
+        return (string == null ? null : string.getBytes(StandardCharsets.UTF_8));
+    }
+
+    /**
+     * deserialize
+     *
+     * @param bytes
+     * @return
+     */
+    public static String deserialize(byte[] bytes) {
+        return (bytes == null ? null : new String(bytes, StandardCharsets.UTF_8));
+    }
+
+    public static String trim(String str){
+        return str == null ? null : str.trim();
+    }
+
+    public static Long getLong(JsonObject jo, String key) {
+        Long id;
+        String idString = GsonUtil.getString(jo, key);
+        if (StringUtils.isBlank(idString)) {
+            id = null;
+        } else {
+            id = parseLong(idString);
+        }
+        return id;
+    }
+
+    public static Long parseLong(String s) {
+        Long id = null;
+        try {
+            id = Long.parseLong(s);
+        } catch (Exception ignore) {
+
+        }
+        return id;
+    }
+}
+

+ 63 - 0
mirage-core/src/main/java/com/mirage/core/utils/VerifyUtil.java

@@ -0,0 +1,63 @@
+package com.mirage.core.utils;
+
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * @Description: 参数校验工具
+ * @Author: 万贤良
+ * @Email: wanxianliang@corp.netease.com
+ * @Date: 2021/1/6
+ */
+public class VerifyUtil {
+    /**
+     * 验证参数是否可用
+     */
+    public static Boolean isNotNullOrEmpty(Object... args) {
+        for (Object arg : args) {
+            if (arg == null) {
+                return false;
+            }
+            if (arg instanceof String) {
+                String _arg = (String) arg;
+                if (StringUtils.isEmpty(_arg)) {
+                    return false;
+                }
+            } else if (arg instanceof List) {
+                List _arg = (List) arg;
+                if (CollectionUtils.isEmpty(_arg)) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    public static Boolean isNotEmpty(Object... args) {
+        for (Object arg : args) {
+            if (arg == null) {
+                return false;
+            }
+            if (arg instanceof String) {
+                String _arg = (String) arg;
+                if (StringUtils.isEmpty(_arg)) {
+                    return false;
+                }
+            } else if (arg instanceof Collection) {
+                Collection _arg = (Collection) arg;
+                if (CollectionUtils.isEmpty(_arg)) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+
+    public static Boolean isEmpty(Object... args) {
+        return !isNotNullOrEmpty(args);
+    }
+}

+ 33 - 0
mirage-service/.gitignore

@@ -0,0 +1,33 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/

+ 72 - 0
mirage-service/pom.xml

@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.mirage</groupId>
+        <artifactId>mirage-server</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+
+    <groupId>com.mirage</groupId>
+    <artifactId>mirage-service</artifactId>
+    <name>mirage-service</name>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.mirage</groupId>
+            <artifactId>mirage-core</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+
+<!--        <dependency>-->
+<!--            <groupId>mysql</groupId>-->
+<!--            <artifactId>mysql-connector-java</artifactId>-->
+<!--        </dependency>-->
+        <dependency>
+            <groupId>com.mysql</groupId>
+            <artifactId>mysql-connector-j</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.mybatis.spring.boot</groupId>
+            <artifactId>mybatis-spring-boot-starter</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-jpa</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>druid-spring-boot-starter</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.microsoft.sqlserver</groupId>
+            <artifactId>mssql-jdbc</artifactId>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <finalName>mirage-service</finalName>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.8.1</version> <!-- 使用最新版本 -->
+                <configuration>
+                    <source>${java.version}</source>
+                    <target>${java.version}</target>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 56 - 0
mirage-service/src/main/java/com/mirage/mirageservice/MirageServiceApplication.java

@@ -0,0 +1,56 @@
+package com.mirage.mirageservice;
+
+import com.mirage.core.aspect.RedisLockAround;
+import com.mirage.core.config.GsonConfig;
+import com.mirage.core.localcache.LocalCache;
+import org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Import;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
+import org.springframework.http.converter.StringHttpMessageConverter;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+import javax.annotation.Resource;
+import java.nio.charset.StandardCharsets;
+
+@EnableAsync
+@Import({GsonConfig.class})
+@EnableScheduling
+@SpringBootApplication(scanBasePackageClasses = {MirageServiceApplication.class}, exclude = {MybatisAutoConfiguration.class})
+@EnableTransactionManagement
+public class MirageServiceApplication implements WebMvcConfigurer {
+
+    @Resource
+    private StringRedisTemplate stringRedisTemplate;
+
+    @Bean
+    public LocalCache localCache() {
+        return new LocalCache();
+    }
+
+    @Bean
+    public RedisLockAround redisLockAround(){
+        return new RedisLockAround(stringRedisTemplate);
+    }
+
+    @Bean
+    public RestTemplate restTemplate(){
+        RestTemplate template = new RestTemplate();
+        template.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
+        template.setRequestFactory(new HttpComponentsClientHttpRequestFactory());
+        return template;
+    }
+
+
+    public static void main(String[] args) {
+        SpringApplication.run(MirageServiceApplication.class, args);
+    }
+
+}

+ 162 - 0
mirage-service/src/main/java/com/mirage/mirageservice/aspect/ControllerAround.java

@@ -0,0 +1,162 @@
+package com.mirage.mirageservice.aspect;
+
+
+import com.mirage.core.annotation.Auth;
+import com.mirage.core.annotation.WithOriginalResponse;
+import com.mirage.core.exception.AppRuntimeException;
+import com.mirage.core.meta.AppCode;
+import com.mirage.core.meta.AuthType;
+import com.mirage.core.meta.WebLogBean;
+import com.mirage.core.utils.AppResult;
+import com.mirage.core.utils.Constants;
+import com.mirage.core.utils.GsonUtil;
+import com.mirage.core.utils.RequestUtil;
+import com.mirage.mirageservice.domain.CsMinWechatUser;
+import com.mirage.mirageservice.meta.AppContext;
+import com.mirage.mirageservice.service.UserService;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.springframework.core.annotation.Order;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.stereotype.Component;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.lang.reflect.Parameter;
+
+/**
+ * Created by hzlinhai on 2024/5/15.
+ */
+@Component
+@Aspect
+@Order(value = 1)
+@Slf4j
+public class ControllerAround {
+
+    @Resource
+    private StringRedisTemplate stringRedisTemplate;
+    @Resource
+    private UserService userService;
+
+    @Pointcut("within(com.mirage.mirageservice.controller..*) " +
+            "&& (@annotation(org.springframework.web.bind.annotation.RequestMapping) " +
+            "|| @annotation(org.springframework.web.bind.annotation.PostMapping))" +
+            "|| @annotation(org.springframework.web.bind.annotation.GetMapping))")
+    private void requestMapping() {
+    }
+
+    private <T extends Annotation> T getAnnotation(Class<?> clazz, Method method, Class<T> annoClazz) {
+        T annotation = method.getAnnotation(annoClazz);
+        if (annotation == null) {
+            annotation = clazz.getAnnotation(annoClazz);
+        }
+        return annotation;
+    }
+
+    @Around("requestMapping()")
+    public Object watchRequestMapping(ProceedingJoinPoint jp) {
+        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
+        AppContext.start();
+        WebLogBean.start();
+        WebLogBean logBean = WebLogBean.get();
+        logBean.setPath(request.getRequestURI());
+        logBean.setIp(RequestUtil.getIp(request));
+        logBean.setHeaders(RequestUtil.getHeaders(request));
+        logBean.setParams(request.getParameterMap());
+
+        Object result = null;
+        try {
+            Class<?> clazz = jp.getTarget().getClass();
+            MethodSignature methodSignature = (MethodSignature) jp.getSignature();
+            Method method = methodSignature.getMethod();
+            Object[] args = jp.getArgs();
+            Parameter[] parameters = method.getParameters();
+            for (int i = 0; i < parameters.length; i++) {
+                if (parameters[i].getAnnotation(RequestBody.class) != null) {
+                    logBean.setPayload(args[i]);
+                    break;
+                }
+            }
+            long now = WebLogBean.get().getBeginTime();
+            Auth auth = getAnnotation(clazz, method, Auth.class);
+
+            WithOriginalResponse withOriginResponse = getAnnotation(clazz, method, WithOriginalResponse.class);
+            boolean returnOriginData = withOriginResponse != null;
+
+            String mirageAuth = request.getHeader("MIRAGE-AUTH");
+            //BussinessAuth 附加Auth(admin) 作用
+            AuthType authType = auth == null ? null : auth.value();
+            long startUserFilterTime = System.currentTimeMillis();
+            // 统一鉴权拦截
+            if (authType != null) {
+                CsMinWechatUser minWechatUser = null;
+                switch (authType) {
+                    case COOKIES: {
+                        String debugMod = request.getHeader("MIRAGE-X-DEBUG");
+                        if(StringUtils.isNotBlank(debugMod)){
+//                            userAccount = userService.getUserByAccount("18814867496");
+                        }else{
+                            if(StringUtils.isBlank(mirageAuth)){
+                                throw new AppRuntimeException(AppCode.UNAUTHORIZED);
+                            }
+                            String sessionValue = stringRedisTemplate.boundValueOps(Constants.REDIS_MIRAGE_LOGIN_SESSION + mirageAuth).get();
+                            if(StringUtils.isBlank(sessionValue)){
+                                throw new AppRuntimeException(AppCode.UNAUTHORIZED);
+                            }
+                            minWechatUser = GsonUtil.fromJson(sessionValue, CsMinWechatUser.class);
+                            if(null == minWechatUser){
+                                throw new AppRuntimeException(AppCode.UNAUTHORIZED);
+                            }
+                        }
+                        break;
+                    }
+                    case OPEN: {
+                        break;
+                    }
+                    default: {
+                        throw new AppRuntimeException(AppCode.FORBIDDEN, "Unsupported Api.");
+                    }
+                }
+                // 鉴权信息设置进上下文
+                AppContext.setUserInfo(minWechatUser);
+                if(null != minWechatUser) {
+                    AppContext.setUid(minWechatUser.getId());
+                    logBean.setUid(minWechatUser.getId());
+                }
+                logBean.addProp("filterCostTimeTotal", System.currentTimeMillis() - startUserFilterTime);
+            }
+            result = jp.proceed();
+            logBean.setResult(result);
+            if (returnOriginData) {
+                return result;
+            }
+            return new AppResult(result);
+        } catch (AppRuntimeException e) {
+            logBean.setCode(e.getCode());
+            logBean.setError(e.getMessage());
+            result = new AppResult(e.getCode(), e.getMessage());
+            return result;
+        } catch (Throwable e) {
+            log.error("@alert@P0@1min-1@:服务异常#");
+            log.error(e.getMessage(), e);
+            logBean.setError(e.getMessage());
+            logBean.setCode(AppCode.UNKNOWN.getCode());
+            result = new AppResult(AppCode.UNKNOWN.getCode(), AppCode.UNKNOWN.getMessage());
+            return result;
+        } finally {
+            logBean.setResult(result);
+            AppContext.end();
+            WebLogBean.end();
+        }
+    }
+}

+ 41 - 0
mirage-service/src/main/java/com/mirage/mirageservice/aspect/LockAspect.java

@@ -0,0 +1,41 @@
+package com.mirage.mirageservice.aspect;
+
+import com.mirage.core.aspect.RedisLockAround;
+import com.mirage.core.exception.AppRuntimeException;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+
+/**
+ * Created by hzlinhai on 2025/3/7.
+ */
+@Component
+@Aspect
+@Slf4j
+public class LockAspect {
+
+    @Resource
+    private RedisLockAround redisLockAround;
+
+    @Pointcut("@annotation(com.mirage.core.annotation.RedisLock)")
+    private void requestMapping() {
+    }
+
+    @Around("requestMapping()")
+    public Object watchLockAspect(ProceedingJoinPoint jp) throws Throwable{
+        try {
+            return redisLockAround.watchLockAspect(jp);
+        } catch (Throwable e) {
+            log.error(e.getMessage(), e);
+            if(e instanceof AppRuntimeException){
+                throw e;
+            }
+        }
+        return null;
+    }
+}

+ 101 - 0
mirage-service/src/main/java/com/mirage/mirageservice/config/PrimaryDataSourceConfig.java

@@ -0,0 +1,101 @@
+package com.mirage.mirageservice.config;
+
+import org.apache.ibatis.session.SqlSessionFactory;
+import org.mybatis.spring.SqlSessionFactoryBean;
+import org.mybatis.spring.SqlSessionTemplate;
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.jdbc.DataSourceBuilder;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+import org.springframework.jdbc.datasource.DataSourceTransactionManager;
+
+import javax.sql.DataSource;
+
+/**
+ * Created by hzlinhai on 2025/8/26.
+ */
+//@Configuration
+//@EnableTransactionManagement
+//@EnableJpaRepositories(
+//        entityManagerFactoryRef = "primaryEntityManagerFactory",
+//        transactionManagerRef = "primaryTransactionManager",
+//        basePackages = {"com.mirage.mirageservice.mapper.mysql"} // MySQL对应的Repository包路径
+//)
+@Configuration
+@MapperScan(
+        basePackages = "com.mirage.mirageservice.mapper.mysql",  // MySQL Mapper 包路径
+        sqlSessionFactoryRef = "primarySqlSessionFactory"
+)
+public class PrimaryDataSourceConfig {
+
+    // 配置主数据源
+    @Primary
+    @Bean(name = "primaryDataSource")
+    @ConfigurationProperties(prefix = "spring.datasource.primary")  // 对应配置文件中的前缀
+    public DataSource primaryDataSource() {
+        return DataSourceBuilder.create().build();
+    }
+
+    // 配置主数据源的 SqlSessionFactory
+    @Primary
+    @Bean(name = "primarySqlSessionFactory")
+    public SqlSessionFactory primarySqlSessionFactory(@Qualifier("primaryDataSource") DataSource dataSource) throws Exception {
+        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
+        bean.setDataSource(dataSource);
+        // 如果需要配置 MyBatis 全局配置文件
+        // bean.setConfigLocation(new PathMatchingResourcePatternResolver().getResource("classpath:mybatis-config.xml"));
+        return bean.getObject();
+    }
+
+    // 配置主数据源的事务管理器
+    @Primary
+    @Bean(name = "primaryTransactionManager")
+    public DataSourceTransactionManager primaryTransactionManager(@Qualifier("primaryDataSource") DataSource dataSource) {
+        return new DataSourceTransactionManager(dataSource);
+    }
+
+    // 配置主数据源的 SqlSessionTemplate
+    @Primary
+    @Bean(name = "primarySqlSessionTemplate")
+    public SqlSessionTemplate primarySqlSessionTemplate(@Qualifier("primarySqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
+        return new SqlSessionTemplate(sqlSessionFactory);
+    }
+
+//    @Primary
+//    @Bean(name = "primaryDataSource")
+//    @ConfigurationProperties(prefix = "spring.datasource.primary")
+//    public DataSource dataSource() {
+//        return DataSourceBuilder.create().build();
+//    }
+//
+//    @Primary
+//    @Bean(name = "primaryEntityManagerFactory")
+//    public LocalContainerEntityManagerFactoryBean entityManagerFactory(
+//            EntityManagerFactoryBuilder builder,
+//            @Qualifier("primaryDataSource") DataSource dataSource) {
+//        return builder
+//                .dataSource(dataSource)
+//                .packages("com.mirage.mirageservice.mapper.mysql") // MySQL对应的实体类包路径
+//                .persistenceUnit("primary")
+//                .properties(jpaProperties())
+//                .build();
+//    }
+//
+//    @Primary
+//    @Bean(name = "primaryTransactionManager")
+//    public PlatformTransactionManager transactionManager(
+//            @Qualifier("primaryEntityManagerFactory") EntityManagerFactory entityManagerFactory) {
+//        return new JpaTransactionManager(entityManagerFactory);
+//    }
+//
+//    // 配置JPA属性
+//    private Map<String, Object> jpaProperties() {
+//        Map<String, Object> properties = new HashMap<>();
+//        properties.put("hibernate.dialect", "org.hibernate.dialect.MySQL8Dialect");
+//        properties.put("hibernate.format_sql", true);
+//        return properties;
+//    }
+}

+ 91 - 0
mirage-service/src/main/java/com/mirage/mirageservice/config/SecondaryDataSourceConfig.java

@@ -0,0 +1,91 @@
+package com.mirage.mirageservice.config;
+
+import org.apache.ibatis.session.SqlSessionFactory;
+import org.mybatis.spring.SqlSessionFactoryBean;
+import org.mybatis.spring.SqlSessionTemplate;
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.jdbc.DataSourceBuilder;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.jdbc.datasource.DataSourceTransactionManager;
+
+import javax.sql.DataSource;
+
+/**
+ * Created by hzlinhai on 2025/8/26.
+ */
+//@Configuration
+//@EnableTransactionManagement
+//@EnableJpaRepositories(
+//        entityManagerFactoryRef = "secondaryEntityManagerFactory",
+//        transactionManagerRef = "secondaryTransactionManager",
+//        basePackages = {"com.mirage.mirageservice.mapper.sqlserver"} // SQL Server对应的Repository包路径
+//)
+@Configuration
+@MapperScan(
+        basePackages = "com.mirage.mirageservice.mapper.sqlserver",  // SQL Server Mapper 包路径
+        sqlSessionFactoryRef = "secondarySqlSessionFactory"
+)
+public class SecondaryDataSourceConfig {
+
+    // 配置从数据源
+    @Bean(name = "secondaryDataSource")
+    @ConfigurationProperties(prefix = "spring.datasource.secondary")  // 对应配置文件中的前缀
+    public DataSource secondaryDataSource() {
+        return DataSourceBuilder.create().build();
+    }
+
+    // 配置从数据源的 SqlSessionFactory
+    @Bean(name = "secondarySqlSessionFactory")
+    public SqlSessionFactory secondarySqlSessionFactory(@Qualifier("secondaryDataSource") DataSource dataSource) throws Exception {
+        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
+        bean.setDataSource(dataSource);
+        return bean.getObject();
+    }
+
+    // 配置从数据源的事务管理器
+    @Bean(name = "secondaryTransactionManager")
+    public DataSourceTransactionManager secondaryTransactionManager(@Qualifier("secondaryDataSource") DataSource dataSource) {
+        return new DataSourceTransactionManager(dataSource);
+    }
+
+    // 配置从数据源的 SqlSessionTemplate
+    @Bean(name = "secondarySqlSessionTemplate")
+    public SqlSessionTemplate secondarySqlSessionTemplate(@Qualifier("secondarySqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
+        return new SqlSessionTemplate(sqlSessionFactory);
+    }
+
+//    @Bean(name = "secondaryDataSource")
+//    @ConfigurationProperties(prefix = "spring.datasource.secondary")
+//    public DataSource dataSource() {
+//        return DataSourceBuilder.create().build();
+//    }
+//
+//    @Bean(name = "secondaryEntityManagerFactory")
+//    public LocalContainerEntityManagerFactoryBean entityManagerFactory(
+//            EntityManagerFactoryBuilder builder,
+//            @Qualifier("secondaryDataSource") DataSource dataSource) {
+//        return builder
+//                .dataSource(dataSource)
+//                .packages("com.mirage.mirageservice.mapper.sqlserver") // SQL Server对应的实体类包路径
+//                .persistenceUnit("secondary")
+//                .properties(jpaProperties())
+//                .build();
+//    }
+//
+//    @Bean(name = "secondaryTransactionManager")
+//    public PlatformTransactionManager transactionManager(
+//            @Qualifier("secondaryEntityManagerFactory") EntityManagerFactory entityManagerFactory) {
+//        return new JpaTransactionManager(entityManagerFactory);
+//    }
+//
+//    // 配置JPA属性
+//    private Map<String, Object> jpaProperties() {
+//        Map<String, Object> properties = new HashMap<>();
+//        properties.put("hibernate.dialect", "org.hibernate.dialect.SQLServer2012Dialect");
+//        properties.put("hibernate.format_sql", true);
+//        return properties;
+//    }
+}

+ 113 - 0
mirage-service/src/main/java/com/mirage/mirageservice/controller/WxController.java

@@ -0,0 +1,113 @@
+package com.mirage.mirageservice.controller;
+
+import com.mirage.core.annotation.Auth;
+import com.mirage.core.exception.AppRuntimeException;
+import com.mirage.core.meta.AppCode;
+import com.mirage.core.meta.AuthType;
+import com.mirage.mirageservice.enums.MinProgramConfigEnum;
+import com.mirage.mirageservice.meta.BindRequest;
+import com.mirage.mirageservice.service.UserService;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+
+/**
+ * 微信小程序交互、营销相关接口.
+ */
+@Slf4j
+@RestController
+@RequestMapping("/api/mp/wx")
+public class WxController {
+
+    @Resource
+    private UserService userService;
+
+    @Auth(value = AuthType.OPEN)
+    @RequestMapping(value = "/health", method = RequestMethod.GET)
+    public Object health(){
+        return null;
+    }
+
+    /**
+     * 小程序端-一键授权登陆
+     * 小程序一键授权应同步注册用户,生成登陆态
+     * @param code
+     * @param programName
+     * @return
+     */
+    @Auth(value = AuthType.OPEN)
+    @RequestMapping(value = "/user/exchange", method = RequestMethod.GET)
+    public Object wxLogin(@RequestParam String code,
+                          @RequestParam(required = false, defaultValue = "CHUNSUN_ENGLISH") String programName){
+        MinProgramConfigEnum configEnum = MinProgramConfigEnum.getByName(programName);
+        if(null == configEnum){
+            throw new AppRuntimeException("小程序标识不合法");
+        }
+        return userService.wxLogin(code, configEnum);
+    }
+
+    /**
+     * 校验登录状态.
+     * 登录过期,返回{"code":403, "message":""Forbidden}
+     * @return
+     */
+    @Auth(value = AuthType.COOKIES)
+    @RequestMapping(value = "/user/checkLoginSession", method = RequestMethod.GET)
+    public Object checkLoginSession(){
+        return null;
+    }
+
+    /**
+     * 小程序端-微信code获取微信用户信息
+     * 一键授权登陆以后调用,如首次登陆获取用户openId等信息存储
+     * @param code
+     * @return
+     */
+    @Auth(value = AuthType.COOKIES)
+    @RequestMapping(value = "/user/jscode2session", method = RequestMethod.GET)
+    public Object getUserWxInfo(@RequestParam String code, @RequestParam(required = false, defaultValue = "CHUNSUN_ENGLISH") String programName){
+        MinProgramConfigEnum configEnum = MinProgramConfigEnum.getByName(programName);
+        if(null == configEnum){
+            throw new AppRuntimeException("小程序标识不合法");
+        }
+        return userService.getUserWxInfo(code, configEnum);
+    }
+
+    @Auth(value = AuthType.OPEN)
+    @RequestMapping(value = "/user/bind", method = RequestMethod.POST)
+    public Object bind(@RequestBody BindRequest bindRequest){
+        if(null == bindRequest
+                || null == bindRequest.getScanPageId()
+                || StringUtils.isBlank(bindRequest.getOpenId())
+                || StringUtils.isBlank(bindRequest.getStudentName())){
+            throw new AppRuntimeException(AppCode.BAD_REQUEST);
+        }
+        return userService.bind(bindRequest);
+    }
+
+    @Auth(value = AuthType.OPEN)
+    @RequestMapping(value = "/user/unbind", method = RequestMethod.POST)
+    public Object unbind(@RequestBody BindRequest bindRequest){
+        if(null == bindRequest
+                || StringUtils.isBlank(bindRequest.getOpenId())){
+            throw new AppRuntimeException(AppCode.BAD_REQUEST);
+        }
+        return userService.unBind(bindRequest);
+    }
+
+    @Auth(value = AuthType.OPEN)
+    @RequestMapping(value = "/user/reports", method = RequestMethod.GET)
+    public Object reportList(@RequestParam String openId,
+                             @RequestParam(required = false, defaultValue = "1") Integer pageIndex,
+                             @RequestParam(required = false, defaultValue = "10") Integer pageSize){
+        return userService.reportList(openId, pageIndex, pageSize);
+    }
+
+    @Auth(value = AuthType.OPEN)
+    @RequestMapping(value = "/user/report/{id}", method = RequestMethod.GET)
+    public Object getReportDetail(@PathVariable Integer id){
+        return userService.getReportDetail(id);
+    }
+}

+ 155 - 0
mirage-service/src/main/java/com/mirage/mirageservice/domain/CsMinWechatUser.java

@@ -0,0 +1,155 @@
+package com.mirage.mirageservice.domain;
+
+/** 
+* Created by hzlinhai on 2025/8/28. 
+*/
+
+/**
+ * 春笋小程序公众号用户表
+ */
+public class CsMinWechatUser {
+    private Long id;
+
+    private String openId;
+
+    private String unionId;
+
+    /**
+     * 用户手机号
+     */
+    private String phone;
+
+    private String loginToken;
+
+    private Long lastLoginTime;
+
+    /**
+     * 0:小程序 1:公众号
+     */
+    private Integer wechatType;
+
+    /**
+     * 小程序、公众号appId
+     */
+    private String wechatAppId;
+
+    /**
+     * 微信昵称
+     */
+    private String nickName;
+
+    /**
+     * 微信头像
+     */
+    private String headImgUrl;
+
+    private Integer isDeleted;
+
+    private Long createTime;
+
+    private Long modifiedTime;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public String getOpenId() {
+        return openId;
+    }
+
+    public void setOpenId(String openId) {
+        this.openId = openId;
+    }
+
+    public String getUnionId() {
+        return unionId;
+    }
+
+    public void setUnionId(String unionId) {
+        this.unionId = unionId;
+    }
+
+    public String getPhone() {
+        return phone;
+    }
+
+    public void setPhone(String phone) {
+        this.phone = phone;
+    }
+
+    public String getLoginToken() {
+        return loginToken;
+    }
+
+    public void setLoginToken(String loginToken) {
+        this.loginToken = loginToken;
+    }
+
+    public Long getLastLoginTime() {
+        return lastLoginTime;
+    }
+
+    public void setLastLoginTime(Long lastLoginTime) {
+        this.lastLoginTime = lastLoginTime;
+    }
+
+    public Integer getWechatType() {
+        return wechatType;
+    }
+
+    public void setWechatType(Integer wechatType) {
+        this.wechatType = wechatType;
+    }
+
+    public String getWechatAppId() {
+        return wechatAppId;
+    }
+
+    public void setWechatAppId(String wechatAppId) {
+        this.wechatAppId = wechatAppId;
+    }
+
+    public String getNickName() {
+        return nickName;
+    }
+
+    public void setNickName(String nickName) {
+        this.nickName = nickName;
+    }
+
+    public String getHeadImgUrl() {
+        return headImgUrl;
+    }
+
+    public void setHeadImgUrl(String headImgUrl) {
+        this.headImgUrl = headImgUrl;
+    }
+
+    public Integer getIsDeleted() {
+        return isDeleted;
+    }
+
+    public void setIsDeleted(Integer isDeleted) {
+        this.isDeleted = isDeleted;
+    }
+
+    public Long getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Long createTime) {
+        this.createTime = createTime;
+    }
+
+    public Long getModifiedTime() {
+        return modifiedTime;
+    }
+
+    public void setModifiedTime(Long modifiedTime) {
+        this.modifiedTime = modifiedTime;
+    }
+}

+ 188 - 0
mirage-service/src/main/java/com/mirage/mirageservice/domain/ScanPage.java

@@ -0,0 +1,188 @@
+package com.mirage.mirageservice.domain;
+
+import java.util.Date;
+
+/**
+ * Created by hzlinhai on 2025/8/27.
+ */
+public class ScanPage {
+    private Integer id;
+
+    private Integer type;
+
+    private String title;
+
+    private Integer studentid;
+
+    private Integer exerciseid;
+
+    private Object groupid;
+
+    private Integer filtertableid;
+
+    private Boolean resolved;
+
+    private String recognize;
+
+    private Boolean haserror;
+
+    private String image;
+
+    private String debugimage;
+
+    private String device;
+
+    private String result;
+
+    private Date createtime;
+
+    private Date updatetime;
+
+    private String labelimage;
+
+    private String labeljson;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public Integer getType() {
+        return type;
+    }
+
+    public void setType(Integer type) {
+        this.type = type;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public Integer getStudentid() {
+        return studentid;
+    }
+
+    public void setStudentid(Integer studentid) {
+        this.studentid = studentid;
+    }
+
+    public Integer getExerciseid() {
+        return exerciseid;
+    }
+
+    public void setExerciseid(Integer exerciseid) {
+        this.exerciseid = exerciseid;
+    }
+
+    public Object getGroupid() {
+        return groupid;
+    }
+
+    public void setGroupid(Object groupid) {
+        this.groupid = groupid;
+    }
+
+    public Integer getFiltertableid() {
+        return filtertableid;
+    }
+
+    public void setFiltertableid(Integer filtertableid) {
+        this.filtertableid = filtertableid;
+    }
+
+    public Boolean getResolved() {
+        return resolved;
+    }
+
+    public void setResolved(Boolean resolved) {
+        this.resolved = resolved;
+    }
+
+    public String getRecognize() {
+        return recognize;
+    }
+
+    public void setRecognize(String recognize) {
+        this.recognize = recognize;
+    }
+
+    public Boolean getHaserror() {
+        return haserror;
+    }
+
+    public void setHaserror(Boolean haserror) {
+        this.haserror = haserror;
+    }
+
+    public String getImage() {
+        return image;
+    }
+
+    public void setImage(String image) {
+        this.image = image;
+    }
+
+    public String getDebugimage() {
+        return debugimage;
+    }
+
+    public void setDebugimage(String debugimage) {
+        this.debugimage = debugimage;
+    }
+
+    public String getDevice() {
+        return device;
+    }
+
+    public void setDevice(String device) {
+        this.device = device;
+    }
+
+    public String getResult() {
+        return result;
+    }
+
+    public void setResult(String result) {
+        this.result = result;
+    }
+
+    public Date getCreatetime() {
+        return createtime;
+    }
+
+    public void setCreatetime(Date createtime) {
+        this.createtime = createtime;
+    }
+
+    public Date getUpdatetime() {
+        return updatetime;
+    }
+
+    public void setUpdatetime(Date updatetime) {
+        this.updatetime = updatetime;
+    }
+
+    public String getLabelimage() {
+        return labelimage;
+    }
+
+    public void setLabelimage(String labelimage) {
+        this.labelimage = labelimage;
+    }
+
+    public String getLabeljson() {
+        return labeljson;
+    }
+
+    public void setLabeljson(String labeljson) {
+        this.labeljson = labeljson;
+    }
+}

+ 247 - 0
mirage-service/src/main/java/com/mirage/mirageservice/domain/Student.java

@@ -0,0 +1,247 @@
+package com.mirage.mirageservice.domain;
+
+import java.util.Date;
+
+/** 
+* Created by hzlinhai on 2025/8/26. 
+*/
+public class Student {
+    private Integer id;
+
+    private Object businessid;
+
+    private Integer classid;
+
+    private String name;
+
+    private Float score;
+
+    private String number;
+
+    private String remark;
+
+    private Short status;
+
+    private Date createtime;
+
+    private Integer settlementstatus;
+
+    private Date startdate;
+
+    private Date enddate;
+
+    private Short surplusprinttimes;
+
+    private Integer studentstage;
+
+    private Integer startyear;
+
+    private String school;
+
+    private Boolean isdelete;
+
+    private String exerciseparameter;
+
+    private Integer residuetimes;
+
+    private Integer totalscore;
+
+    /**
+    * 是否完成全量迁移:0-未完成,1-已完成
+    */
+    private Byte isfullmigrated;
+
+    /**
+    * 最后迁移时间(基于FilterTable.UpdateTime)
+    */
+    private Date lastmigrationtime;
+
+    /**
+    * V3算法迁移状态:0-未迁移,1-迁移完成,2-迁移中,3-迁移失败
+    */
+    private Byte ismigratedforv3;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public Object getBusinessid() {
+        return businessid;
+    }
+
+    public void setBusinessid(Object businessid) {
+        this.businessid = businessid;
+    }
+
+    public Integer getClassid() {
+        return classid;
+    }
+
+    public void setClassid(Integer classid) {
+        this.classid = classid;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public Float getScore() {
+        return score;
+    }
+
+    public void setScore(Float score) {
+        this.score = score;
+    }
+
+    public String getNumber() {
+        return number;
+    }
+
+    public void setNumber(String number) {
+        this.number = number;
+    }
+
+    public String getRemark() {
+        return remark;
+    }
+
+    public void setRemark(String remark) {
+        this.remark = remark;
+    }
+
+    public Short getStatus() {
+        return status;
+    }
+
+    public void setStatus(Short status) {
+        this.status = status;
+    }
+
+    public Date getCreatetime() {
+        return createtime;
+    }
+
+    public void setCreatetime(Date createtime) {
+        this.createtime = createtime;
+    }
+
+    public Integer getSettlementstatus() {
+        return settlementstatus;
+    }
+
+    public void setSettlementstatus(Integer settlementstatus) {
+        this.settlementstatus = settlementstatus;
+    }
+
+    public Date getStartdate() {
+        return startdate;
+    }
+
+    public void setStartdate(Date startdate) {
+        this.startdate = startdate;
+    }
+
+    public Date getEnddate() {
+        return enddate;
+    }
+
+    public void setEnddate(Date enddate) {
+        this.enddate = enddate;
+    }
+
+    public Short getSurplusprinttimes() {
+        return surplusprinttimes;
+    }
+
+    public void setSurplusprinttimes(Short surplusprinttimes) {
+        this.surplusprinttimes = surplusprinttimes;
+    }
+
+    public Integer getStudentstage() {
+        return studentstage;
+    }
+
+    public void setStudentstage(Integer studentstage) {
+        this.studentstage = studentstage;
+    }
+
+    public Integer getStartyear() {
+        return startyear;
+    }
+
+    public void setStartyear(Integer startyear) {
+        this.startyear = startyear;
+    }
+
+    public String getSchool() {
+        return school;
+    }
+
+    public void setSchool(String school) {
+        this.school = school;
+    }
+
+    public Boolean getIsdelete() {
+        return isdelete;
+    }
+
+    public void setIsdelete(Boolean isdelete) {
+        this.isdelete = isdelete;
+    }
+
+    public String getExerciseparameter() {
+        return exerciseparameter;
+    }
+
+    public void setExerciseparameter(String exerciseparameter) {
+        this.exerciseparameter = exerciseparameter;
+    }
+
+    public Integer getResiduetimes() {
+        return residuetimes;
+    }
+
+    public void setResiduetimes(Integer residuetimes) {
+        this.residuetimes = residuetimes;
+    }
+
+    public Integer getTotalscore() {
+        return totalscore;
+    }
+
+    public void setTotalscore(Integer totalscore) {
+        this.totalscore = totalscore;
+    }
+
+    public Byte getIsfullmigrated() {
+        return isfullmigrated;
+    }
+
+    public void setIsfullmigrated(Byte isfullmigrated) {
+        this.isfullmigrated = isfullmigrated;
+    }
+
+    public Date getLastmigrationtime() {
+        return lastmigrationtime;
+    }
+
+    public void setLastmigrationtime(Date lastmigrationtime) {
+        this.lastmigrationtime = lastmigrationtime;
+    }
+
+    public Byte getIsmigratedforv3() {
+        return ismigratedforv3;
+    }
+
+    public void setIsmigratedforv3(Byte ismigratedforv3) {
+        this.ismigratedforv3 = ismigratedforv3;
+    }
+}

+ 412 - 0
mirage-service/src/main/java/com/mirage/mirageservice/domain/StudentDataReport.java

@@ -0,0 +1,412 @@
+package com.mirage.mirageservice.domain;
+
+import java.util.Date;
+
+/** 
+* Created by hzlinhai on 2025/8/26. 
+*/
+/**
+ * 学生数据战报记录
+ */
+public class StudentDataReport {
+    /**
+    *  
+    */
+    private Integer id;
+
+    /**
+    * 战报名称
+    */
+    private String reportName;
+
+    private Date createTime;
+
+    private Date updateTime;
+
+    private Boolean deleted;
+
+    private String createBy;
+
+    private String updateBy;
+
+    /**
+    * 学生id
+    */
+    private Long studentId;
+
+    /**
+    * 学生姓名
+    */
+    private String studentName;
+
+    /**
+    * 教师姓名
+    */
+    private String teacherName;
+
+    /**
+    * 开始节点
+    */
+    private String startNode;
+
+    /**
+    * 结束节点
+    */
+    private String endNode;
+
+    /**
+    * 开始数据概况
+    */
+    private String startBaseData;
+
+    /**
+    * 结束数据概况
+    */
+    private String endBaseData;
+
+    /**
+    * 开始词汇
+    */
+    private String startWordsData;
+
+    /**
+    * 结束词汇
+    */
+    private String endWordsData;
+
+    /**
+    * 节点列表
+    */
+    private String processData;
+
+    /**
+    * 词汇变化
+    */
+    private String vocabularyChangeData;
+
+    /**
+    * 阅读难度
+    */
+    private String difficultiesData;
+
+    /**
+    * 词频对比
+    */
+    private String vocabularyContrastData;
+
+    /**
+    * 阅读速度
+    */
+    private String speedData;
+
+    /**
+    * 阅读强度
+    */
+    private String amountData;
+
+    /**
+    * 阅读正确率
+    */
+    private String accuracyData;
+
+    /**
+    * 得分能力
+    */
+    private String scoresData;
+
+    /**
+    * 名次变化
+    */
+    private String rankingData;
+
+    /**
+    * 商户姓名
+    */
+    private String businessName;
+
+    /**
+    * 商户手机号
+    */
+    private String businessPhone;
+
+    /**
+    * 学情建议
+    */
+    private String learnAdvice;
+
+    /**
+    * 海报预览图
+    */
+    private String viewUrl;
+
+    /**
+    * 班级名称
+    */
+    private String className;
+
+    /**
+    * 学号
+    */
+    private String studentNum;
+
+    /**
+    * 学段1小学,2初中,3高中
+    */
+    private Integer studentStage;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public String getReportName() {
+        return reportName;
+    }
+
+    public void setReportName(String reportName) {
+        this.reportName = reportName;
+    }
+
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    public Date getUpdateTime() {
+        return updateTime;
+    }
+
+    public void setUpdateTime(Date updateTime) {
+        this.updateTime = updateTime;
+    }
+
+    public Boolean getDeleted() {
+        return deleted;
+    }
+
+    public void setDeleted(Boolean deleted) {
+        this.deleted = deleted;
+    }
+
+    public String getCreateBy() {
+        return createBy;
+    }
+
+    public void setCreateBy(String createBy) {
+        this.createBy = createBy;
+    }
+
+    public String getUpdateBy() {
+        return updateBy;
+    }
+
+    public void setUpdateBy(String updateBy) {
+        this.updateBy = updateBy;
+    }
+
+    public Long getStudentId() {
+        return studentId;
+    }
+
+    public void setStudentId(Long studentId) {
+        this.studentId = studentId;
+    }
+
+    public String getStudentName() {
+        return studentName;
+    }
+
+    public void setStudentName(String studentName) {
+        this.studentName = studentName;
+    }
+
+    public String getTeacherName() {
+        return teacherName;
+    }
+
+    public void setTeacherName(String teacherName) {
+        this.teacherName = teacherName;
+    }
+
+    public String getStartNode() {
+        return startNode;
+    }
+
+    public void setStartNode(String startNode) {
+        this.startNode = startNode;
+    }
+
+    public String getEndNode() {
+        return endNode;
+    }
+
+    public void setEndNode(String endNode) {
+        this.endNode = endNode;
+    }
+
+    public String getStartBaseData() {
+        return startBaseData;
+    }
+
+    public void setStartBaseData(String startBaseData) {
+        this.startBaseData = startBaseData;
+    }
+
+    public String getEndBaseData() {
+        return endBaseData;
+    }
+
+    public void setEndBaseData(String endBaseData) {
+        this.endBaseData = endBaseData;
+    }
+
+    public String getStartWordsData() {
+        return startWordsData;
+    }
+
+    public void setStartWordsData(String startWordsData) {
+        this.startWordsData = startWordsData;
+    }
+
+    public String getEndWordsData() {
+        return endWordsData;
+    }
+
+    public void setEndWordsData(String endWordsData) {
+        this.endWordsData = endWordsData;
+    }
+
+    public String getProcessData() {
+        return processData;
+    }
+
+    public void setProcessData(String processData) {
+        this.processData = processData;
+    }
+
+    public String getVocabularyChangeData() {
+        return vocabularyChangeData;
+    }
+
+    public void setVocabularyChangeData(String vocabularyChangeData) {
+        this.vocabularyChangeData = vocabularyChangeData;
+    }
+
+    public String getDifficultiesData() {
+        return difficultiesData;
+    }
+
+    public void setDifficultiesData(String difficultiesData) {
+        this.difficultiesData = difficultiesData;
+    }
+
+    public String getVocabularyContrastData() {
+        return vocabularyContrastData;
+    }
+
+    public void setVocabularyContrastData(String vocabularyContrastData) {
+        this.vocabularyContrastData = vocabularyContrastData;
+    }
+
+    public String getSpeedData() {
+        return speedData;
+    }
+
+    public void setSpeedData(String speedData) {
+        this.speedData = speedData;
+    }
+
+    public String getAmountData() {
+        return amountData;
+    }
+
+    public void setAmountData(String amountData) {
+        this.amountData = amountData;
+    }
+
+    public String getAccuracyData() {
+        return accuracyData;
+    }
+
+    public void setAccuracyData(String accuracyData) {
+        this.accuracyData = accuracyData;
+    }
+
+    public String getScoresData() {
+        return scoresData;
+    }
+
+    public void setScoresData(String scoresData) {
+        this.scoresData = scoresData;
+    }
+
+    public String getRankingData() {
+        return rankingData;
+    }
+
+    public void setRankingData(String rankingData) {
+        this.rankingData = rankingData;
+    }
+
+    public String getBusinessName() {
+        return businessName;
+    }
+
+    public void setBusinessName(String businessName) {
+        this.businessName = businessName;
+    }
+
+    public String getBusinessPhone() {
+        return businessPhone;
+    }
+
+    public void setBusinessPhone(String businessPhone) {
+        this.businessPhone = businessPhone;
+    }
+
+    public String getLearnAdvice() {
+        return learnAdvice;
+    }
+
+    public void setLearnAdvice(String learnAdvice) {
+        this.learnAdvice = learnAdvice;
+    }
+
+    public String getViewUrl() {
+        return viewUrl;
+    }
+
+    public void setViewUrl(String viewUrl) {
+        this.viewUrl = viewUrl;
+    }
+
+    public String getClassName() {
+        return className;
+    }
+
+    public void setClassName(String className) {
+        this.className = className;
+    }
+
+    public String getStudentNum() {
+        return studentNum;
+    }
+
+    public void setStudentNum(String studentNum) {
+        this.studentNum = studentNum;
+    }
+
+    public Integer getStudentStage() {
+        return studentStage;
+    }
+
+    public void setStudentStage(Integer studentStage) {
+        this.studentStage = studentStage;
+    }
+}

+ 68 - 0
mirage-service/src/main/java/com/mirage/mirageservice/domain/WechatBind.java

@@ -0,0 +1,68 @@
+package com.mirage.mirageservice.domain;
+
+import java.util.Date;
+
+/** 
+* Created by hzlinhai on 2025/8/26. 
+*/
+public class WechatBind {
+    private Integer id;
+
+    private Integer studentid;
+
+    private String studentname;
+
+    private String phone;
+
+    private String openid;
+
+    private Date createtime;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public Integer getStudentid() {
+        return studentid;
+    }
+
+    public void setStudentid(Integer studentid) {
+        this.studentid = studentid;
+    }
+
+    public String getStudentname() {
+        return studentname;
+    }
+
+    public void setStudentname(String studentname) {
+        this.studentname = studentname;
+    }
+
+    public String getPhone() {
+        return phone;
+    }
+
+    public void setPhone(String phone) {
+        this.phone = phone;
+    }
+
+    public String getOpenid() {
+        return openid;
+    }
+
+    public void setOpenid(String openid) {
+        this.openid = openid;
+    }
+
+    public Date getCreatetime() {
+        return createtime;
+    }
+
+    public void setCreatetime(Date createtime) {
+        this.createtime = createtime;
+    }
+}

+ 65 - 0
mirage-service/src/main/java/com/mirage/mirageservice/enums/MinProgramConfigEnum.java

@@ -0,0 +1,65 @@
+package com.mirage.mirageservice.enums;
+
+/**
+ * Created by hzlinhai on 2025/8/26.
+ */
+public enum MinProgramConfigEnum {
+    CHUNSUN_ENGLISH_MIN_PROGRAM("CHUNSUN_ENGLISH", "wx7c38acf414870e06", "54d22d78cb553f0001a78753747ba150", "春笋英语学情小程序");
+
+    private String name;
+
+    private String appId;
+
+    private String secret;
+
+    private String remark;
+
+    MinProgramConfigEnum(String name, String appId, String secret, String remark){
+        this.name = name;
+        this.appId = appId;
+        this.secret = secret;
+        this.remark = remark;
+    }
+
+    public static MinProgramConfigEnum getByName(String name){
+        MinProgramConfigEnum[] minProgramConfigEnums = MinProgramConfigEnum.values();
+        for(MinProgramConfigEnum minProgramConfigEnum : minProgramConfigEnums){
+            if(minProgramConfigEnum.getName().equals(name)){
+                return minProgramConfigEnum;
+            }
+        }
+        return null;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getAppId() {
+        return appId;
+    }
+
+    public void setAppId(String appId) {
+        this.appId = appId;
+    }
+
+    public String getSecret() {
+        return secret;
+    }
+
+    public void setSecret(String secret) {
+        this.secret = secret;
+    }
+
+    public String getRemark() {
+        return remark;
+    }
+
+    public void setRemark(String remark) {
+        this.remark = remark;
+    }
+}

+ 23 - 0
mirage-service/src/main/java/com/mirage/mirageservice/mapper/mysql/CsMinWechatUserMapper.java

@@ -0,0 +1,23 @@
+package com.mirage.mirageservice.mapper.mysql;
+
+import com.mirage.mirageservice.domain.CsMinWechatUser;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * Created by hzlinhai on 2025/8/28.
+ */
+public interface CsMinWechatUserMapper {
+    int deleteByPrimaryKey(Long id);
+
+    int insert(CsMinWechatUser record);
+
+    int insertSelective(CsMinWechatUser record);
+
+    CsMinWechatUser selectByPrimaryKey(Long id);
+
+    int updateByPrimaryKeySelective(CsMinWechatUser record);
+
+    int updateByPrimaryKey(CsMinWechatUser record);
+
+    CsMinWechatUser selectOneByPhoneAndWechatAppIdAndIsDeleted(@Param("phone") String phone, @Param("wechatAppId") String wechatAppId, @Param("isDeleted") Integer isDeleted);
+}

+ 25 - 0
mirage-service/src/main/java/com/mirage/mirageservice/mapper/mysql/StudentDataReportMapper.java

@@ -0,0 +1,25 @@
+package com.mirage.mirageservice.mapper.mysql;
+import org.apache.ibatis.annotations.Param;
+import java.util.List;
+
+import com.mirage.mirageservice.domain.StudentDataReport;
+
+/** 
+* Created by hzlinhai on 2025/8/26. 
+*/
+public interface StudentDataReportMapper {
+    int deleteByPrimaryKey(Integer id);
+
+    int insert(StudentDataReport record);
+
+    int insertSelective(StudentDataReport record);
+
+    StudentDataReport selectByPrimaryKey(Integer id);
+
+    int updateByPrimaryKeySelective(StudentDataReport record);
+
+    int updateByPrimaryKey(StudentDataReport record);
+
+    List<StudentDataReport> selectAllByStudentIdOrderByCreateTimeDescAndIdDesc(@Param("studentId")Long studentId);
+
+}

+ 25 - 0
mirage-service/src/main/java/com/mirage/mirageservice/mapper/mysql/WechatBindMapper.java

@@ -0,0 +1,25 @@
+package com.mirage.mirageservice.mapper.mysql;
+import org.apache.ibatis.annotations.Param;
+
+import com.mirage.mirageservice.domain.WechatBind;
+
+/** 
+* Created by hzlinhai on 2025/8/26. 
+*/
+public interface WechatBindMapper {
+    int deleteByPrimaryKey(Integer id);
+
+    int insert(WechatBind record);
+
+    int insertSelective(WechatBind record);
+
+    WechatBind selectByPrimaryKey(Integer id);
+
+    int updateByPrimaryKeySelective(WechatBind record);
+
+    int updateByPrimaryKey(WechatBind record);
+
+    WechatBind selectOneByOpenid(@Param("openid")String openid);
+
+
+}

+ 20 - 0
mirage-service/src/main/java/com/mirage/mirageservice/mapper/sqlserver/ScanPageMapper.java

@@ -0,0 +1,20 @@
+package com.mirage.mirageservice.mapper.sqlserver;
+
+import com.mirage.mirageservice.domain.ScanPage;
+
+/**
+ * Created by hzlinhai on 2025/8/27.
+ */
+public interface ScanPageMapper {
+    int deleteByPrimaryKey(Integer id);
+
+    int insert(ScanPage record);
+
+    int insertSelective(ScanPage record);
+
+    ScanPage selectByPrimaryKey(Integer id);
+
+    int updateByPrimaryKeySelective(ScanPage record);
+
+    int updateByPrimaryKey(ScanPage record);
+}

+ 20 - 0
mirage-service/src/main/java/com/mirage/mirageservice/mapper/sqlserver/StudentMapper.java

@@ -0,0 +1,20 @@
+package com.mirage.mirageservice.mapper.sqlserver;
+
+import com.mirage.mirageservice.domain.Student;
+
+/** 
+* Created by hzlinhai on 2025/8/26. 
+*/
+public interface StudentMapper {
+    int deleteByPrimaryKey(Integer id);
+
+    int insert(Student record);
+
+    int insertSelective(Student record);
+
+    Student selectByPrimaryKey(Integer id);
+
+    int updateByPrimaryKeySelective(Student record);
+
+    int updateByPrimaryKey(Student record);
+}

+ 26 - 0
mirage-service/src/main/java/com/mirage/mirageservice/meta/AppContext.java

@@ -0,0 +1,26 @@
+package com.mirage.mirageservice.meta;
+
+import com.mirage.core.meta.Context;
+import com.mirage.core.meta.HeaderKeys;
+import com.mirage.mirageservice.domain.CsMinWechatUser;
+
+/**
+ * Created by hzlinhai on 2025/1/2.
+ */
+public class AppContext extends Context {
+
+    public static Long getUid() {
+        return (Long) current().get(HeaderKeys.KEY_UID);
+    }
+    public static void setUid(Long uid) {
+        current().put(HeaderKeys.KEY_UID, uid);
+    }
+    public static void setUserInfo(CsMinWechatUser userInfo){
+        Context.current().put("userInfo",userInfo);
+    }
+    public static CsMinWechatUser getUserInfo(){
+        return (CsMinWechatUser) Context.current().get("userInfo");
+    }
+
+
+}

+ 20 - 0
mirage-service/src/main/java/com/mirage/mirageservice/meta/BindRequest.java

@@ -0,0 +1,20 @@
+package com.mirage.mirageservice.meta;
+
+import lombok.Data;
+
+/**
+ * Created by hzlinhai on 2025/8/26.
+ */
+@Data
+public class BindRequest {
+
+    private String openId;
+
+    private Integer scanPageId;
+
+    private String studentName;
+
+    private String phone;
+
+    private Integer studentId;
+}

+ 18 - 0
mirage-service/src/main/java/com/mirage/mirageservice/meta/StudentDataReportResponse.java

@@ -0,0 +1,18 @@
+package com.mirage.mirageservice.meta;
+
+import lombok.Data;
+
+/**
+ * Created by hzlinhai on 2025/8/26.
+ */
+@Data
+public class StudentDataReportResponse {
+
+    private Integer id;
+
+    private Long studentId;
+
+    private String studentName;
+
+    private Integer studentStage;
+}

+ 50 - 0
mirage-service/src/main/java/com/mirage/mirageservice/meta/UserWechatLoginResponse.java

@@ -0,0 +1,50 @@
+package com.mirage.mirageservice.meta;
+
+import lombok.Data;
+
+/**
+ * Created by hzlinhai on 2025/8/28.
+ */
+@Data
+public class UserWechatLoginResponse {
+
+    private Long id;
+
+    private String openId;
+
+    private String unionId;
+
+    /**
+     * 用户手机号
+     */
+    private String phone;
+
+    /**
+     * 0:小程序 1:公众号
+     */
+    private Integer wechatType;
+
+    /**
+     * 小程序、公众号appId
+     */
+    private String wechatAppId;
+
+    /**
+     * 微信昵称
+     */
+    private String nickName;
+
+    /**
+     * 微信头像
+     */
+    private String headImgUrl;
+
+    private Integer isDeleted;
+
+    private Long createTime;
+
+    private Long modifiedTime;
+
+    private String loginToken;
+
+}

+ 239 - 0
mirage-service/src/main/java/com/mirage/mirageservice/service/UserService.java

@@ -0,0 +1,239 @@
+package com.mirage.mirageservice.service;
+
+import com.github.pagehelper.PageHelper;
+import com.google.common.collect.Maps;
+import com.google.gson.JsonObject;
+import com.mirage.core.exception.AppRuntimeException;
+import com.mirage.core.meta.PageResultVo;
+import com.mirage.core.meta.PageUtil;
+import com.mirage.core.utils.AppHttpClient;
+import com.mirage.core.utils.BeanConvertUtil;
+import com.mirage.core.utils.Constants;
+import com.mirage.core.utils.GsonUtil;
+import com.mirage.mirageservice.domain.*;
+import com.mirage.mirageservice.enums.MinProgramConfigEnum;
+import com.mirage.mirageservice.mapper.mysql.CsMinWechatUserMapper;
+import com.mirage.mirageservice.mapper.mysql.StudentDataReportMapper;
+import com.mirage.mirageservice.mapper.mysql.WechatBindMapper;
+import com.mirage.mirageservice.mapper.sqlserver.ScanPageMapper;
+import com.mirage.mirageservice.mapper.sqlserver.StudentMapper;
+import com.mirage.mirageservice.meta.AppContext;
+import com.mirage.mirageservice.meta.BindRequest;
+import com.mirage.mirageservice.meta.StudentDataReportResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.http.client.entity.EntityBuilder;
+import org.apache.http.client.methods.RequestBuilder;
+import org.apache.http.entity.ContentType;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Created by hzlinhai on 2025/1/2.
+ */
+
+@Service
+@Slf4j
+public class UserService {
+
+    @Resource
+    private StringRedisTemplate stringRedisTemplate;
+    @Resource
+    private WechatService wechatService;
+    @Resource
+    private ScanPageMapper scanPageMapper;
+    @Resource
+    private StudentMapper studentMapper;
+    @Resource
+    private WechatBindMapper wechatBindMapper;
+    @Resource
+    private StudentDataReportMapper studentDataReportMapper;
+    @Resource
+    private CsMinWechatUserMapper csMinWechatUserMapper;
+
+    public PageResultVo<StudentDataReportResponse> reportList(String openId, Integer pageIndex, Integer pageSize){
+        WechatBind wechatBind = wechatBindMapper.selectOneByOpenid(openId);
+        if(null == wechatBind){
+            return new PageResultVo<>();
+        }
+        Student student = studentMapper.selectByPrimaryKey(wechatBind.getStudentid());
+        if(null == student){
+            return new PageResultVo<>();
+        }
+        PageHelper.startPage(pageIndex, pageSize);
+        List<StudentDataReport> list = studentDataReportMapper.selectAllByStudentIdOrderByCreateTimeDescAndIdDesc(wechatBind.getStudentid().longValue());
+        PageResultVo<StudentDataReportResponse> resultVo = PageUtil.convertPageResult(list, StudentDataReport.class, StudentDataReportResponse.class);
+        for(StudentDataReportResponse reportResponse : resultVo.getList()){
+            reportResponse.setStudentStage(student.getStudentstage());
+        }
+        return resultVo;
+    }
+
+    public StudentDataReport getReportDetail(Integer id){
+        return studentDataReportMapper.selectByPrimaryKey(id);
+    }
+    public boolean unBind(BindRequest bindRequest){
+        WechatBind wechatBind = wechatBindMapper.selectOneByOpenid(bindRequest.getOpenId());
+        if(null == wechatBind){
+            return true;
+        }
+        wechatBindMapper.deleteByPrimaryKey(wechatBind.getId());
+        return true;
+    }
+
+    public BindRequest bind(BindRequest bindRequest){
+        ScanPage scanPage = scanPageMapper.selectByPrimaryKey(bindRequest.getScanPageId());
+        if(null == scanPage){
+            throw new AppRuntimeException("未找到该筛查表编号");
+        }
+        Student student = studentMapper.selectByPrimaryKey(scanPage.getStudentid());
+        if(null == student){
+            throw new AppRuntimeException("姓名与学籍库不一致");
+        }
+        WechatBind wechatBind = wechatBindMapper.selectOneByOpenid(bindRequest.getOpenId());
+        if(null == wechatBind){
+            wechatBind.setStudentid(student.getId());
+            wechatBind.setStudentname(student.getName());
+            wechatBind.setPhone(bindRequest.getPhone());
+            wechatBind.setCreatetime(new Date());
+            wechatBind.setOpenid(bindRequest.getOpenId());
+            wechatBindMapper.insertSelective(wechatBind);
+        }
+        BindRequest bindResponse = BeanConvertUtil.safeConvert(bindRequest, BindRequest.class, BindRequest.class);
+        bindResponse.setStudentId(student.getId());
+        bindResponse.setStudentName(wechatBind.getStudentname());
+        return bindResponse;
+    }
+
+    public CsMinWechatUser wxLogin(String code, MinProgramConfigEnum configEnum){
+        String phone = this.getPhoneNumberByCode(code, configEnum);
+        if(StringUtils.isBlank(phone)){
+            return null;
+        }
+        CsMinWechatUser wechatUser = csMinWechatUserMapper.selectOneByPhoneAndWechatAppIdAndIsDeleted(phone, configEnum.getAppId(), 0);
+        if(null != wechatUser){
+            String currentToken = wechatUser.getLoginToken();
+            if(StringUtils.isNotBlank(currentToken)){
+                String sessionValue = stringRedisTemplate.boundValueOps(Constants.REDIS_MIRAGE_LOGIN_SESSION + currentToken).get();
+                if(StringUtils.isNotBlank(sessionValue)){
+                    wechatUser.setLastLoginTime(System.currentTimeMillis());
+                    csMinWechatUserMapper.updateByPrimaryKeySelective(wechatUser);
+                    return wechatUser;
+                }
+                if(StringUtils.isBlank(sessionValue)){
+                    currentToken = UUID.randomUUID().toString().replaceAll("-", "");;
+                }
+            }else{
+                currentToken = UUID.randomUUID().toString().replaceAll("-", "");
+            }
+            wechatUser.setLoginToken(currentToken);
+            wechatUser.setLastLoginTime(System.currentTimeMillis());
+            csMinWechatUserMapper.updateByPrimaryKeySelective(wechatUser);
+            this.getLoginSession(currentToken, wechatUser);
+            return wechatUser;
+        }
+        wechatUser = new CsMinWechatUser();
+        wechatUser.setPhone(phone);
+        wechatUser.setWechatType(0);
+        wechatUser.setWechatAppId(configEnum.getAppId());
+        wechatUser.setIsDeleted(0);
+        wechatUser.setCreateTime(System.currentTimeMillis());
+        wechatUser.setModifiedTime(System.currentTimeMillis());
+        String loginToken = UUID.randomUUID().toString().replaceAll("-", "");
+        wechatUser.setLoginToken(loginToken);
+        csMinWechatUserMapper.insertSelective(wechatUser);
+        this.getLoginSession(loginToken, wechatUser);
+        return wechatUser;
+    }
+
+    private String getLoginSession(String sessionKey, CsMinWechatUser wechatUser){
+        if(StringUtils.isBlank(sessionKey) || null == wechatUser){
+            return null;
+        }
+        stringRedisTemplate.boundValueOps(Constants.REDIS_MIRAGE_LOGIN_SESSION+sessionKey).set(GsonUtil.toJson(wechatUser), 7*86400, TimeUnit.SECONDS);
+        return sessionKey;
+    }
+
+    public JsonObject getUserWxInfo(String code, MinProgramConfigEnum configEnum){
+        if(StringUtils.isBlank(code)){
+            return null;
+        }
+        CsMinWechatUser csMinWechatUser = AppContext.getUserInfo();
+        if(null == csMinWechatUser){
+            return null;
+        }
+        JsonObject weChatJo = wechatService.jscode2session(code, configEnum);
+        if (null == weChatJo) {
+            return null;
+        }
+        // 不返回Session_keys
+        if (weChatJo.get("session_key") != null) {
+            weChatJo.remove("session_key");
+        }
+        // 获取微信授权信息,回填用户表信息
+        if(StringUtils.isBlank(csMinWechatUser.getOpenId()) && null != weChatJo.get("openid")){
+            csMinWechatUser.setOpenId(weChatJo.get("openid").getAsString());
+        }
+        if(StringUtils.isBlank(csMinWechatUser.getUnionId()) && null != weChatJo.get("unionid")){
+            csMinWechatUser.setUnionId(weChatJo.get("unionid").getAsString());
+        }
+        csMinWechatUser.setModifiedTime(System.currentTimeMillis());
+        csMinWechatUserMapper.updateByPrimaryKeySelective(csMinWechatUser);
+        return weChatJo;
+    }
+
+    public String getPhoneNumberByCode(String code, MinProgramConfigEnum programConfigEnum){
+        if(StringUtils.isBlank(code)){
+            return null;
+        }
+        String accessToken = this.getOrRefreshAccountToken(programConfigEnum);
+        if(StringUtils.isBlank(accessToken)){
+            return null;
+        }
+        Map<String, Object> param = Maps.newHashMap();
+        param.put("code", code);
+        RequestBuilder requestBuilder = RequestBuilder.post("https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token="+accessToken)
+                .setEntity(EntityBuilder.create().setText(GsonUtil.toJson(param))
+                        .setContentType(ContentType.create("application/json", "UTF-8")).build());
+        try{
+            String response = AppHttpClient.getInstance().execute(requestBuilder.build(), 5000);
+            log.info("WechatService#getPhoneNumberByCode code:{}, response:{}", code, response);
+            JsonObject jsonObject = GsonUtil.parseJsonObject(response);
+            if(null == jsonObject
+                    || null == jsonObject.get("errcode")
+                    || jsonObject.get("errcode").getAsInt() != 0){
+                return null;
+            }
+            JsonObject dataJo = GsonUtil.getJsonObject(jsonObject, "phone_info");
+            if(null == dataJo || null == dataJo.get("phoneNumber")){
+                return null;
+            }
+            return dataJo.get("phoneNumber").getAsString();
+        }catch (Exception e){
+            log.error("WechatService#getPhoneNumberByCode code:{}, err:{}", code, e.getMessage(), e);
+        }
+        return null;
+    }
+
+    public void deleteCache(){
+        stringRedisTemplate.delete(Constants.REDIS_MIN_PROGRAM_ACCESS_TOKEN);
+    }
+
+    public String getOrRefreshAccountToken(MinProgramConfigEnum programConfigEnum){
+        String accessToken = stringRedisTemplate.boundValueOps(Constants.REDIS_MIN_PROGRAM_ACCESS_TOKEN).get();
+        if(StringUtils.isNotBlank(accessToken)){
+            return accessToken;
+        }
+        accessToken = wechatService.requestAccessToken(0, programConfigEnum);
+        stringRedisTemplate.boundValueOps(Constants.REDIS_MIN_PROGRAM_ACCESS_TOKEN).set(accessToken, 2, TimeUnit.HOURS);
+        return accessToken;
+    }
+
+}

+ 79 - 0
mirage-service/src/main/java/com/mirage/mirageservice/service/WechatService.java

@@ -0,0 +1,79 @@
+package com.mirage.mirageservice.service;
+
+import com.google.gson.JsonObject;
+import com.mirage.core.utils.AppHttpClient;
+import com.mirage.core.utils.Constants;
+import com.mirage.core.utils.GsonUtil;
+import com.mirage.mirageservice.enums.MinProgramConfigEnum;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.client.methods.RequestBuilder;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.io.IOException;
+
+/**
+ * Created by hzlinhai on 2025/1/2.
+ */
+@Slf4j
+@Service
+public class WechatService {
+    @Resource
+    private StringRedisTemplate stringRedisTemplate;
+
+    public String requestAccessToken(int retry, MinProgramConfigEnum programConfigEnum){
+        if(retry > 3){
+            return null;
+        }
+        Long flag = stringRedisTemplate.boundValueOps("REDIS-requestAccessToken-lock").increment(1L);
+        if(null != flag && flag == 1L){
+            try{
+                HttpUriRequest request = RequestBuilder.get("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + programConfigEnum.getAppId() + "&secret=" + programConfigEnum.getSecret()).build();
+                String response = null;
+                try {
+                    response = AppHttpClient.getInstance().execute(request, 2000);
+                } catch (IOException e) {
+                    log.error("WechatService#requestAccessToken response:{}", response);
+                    return null;
+                }
+                if (StringUtils.isBlank(response)) {
+                    return null;
+                }
+                JsonObject jsonObject = GsonUtil.parseJsonObject(response);
+                if (null == jsonObject
+                        || null == jsonObject.get("access_token")
+                        || StringUtils.isBlank(jsonObject.get("access_token").getAsString())) {
+                    return null;
+                }
+                return jsonObject.get("access_token").getAsString();
+            }finally {
+                stringRedisTemplate.delete("REDIS-requestAccessToken-lock");
+            }
+        }else{
+            return requestAccessToken(retry++, programConfigEnum);
+        }
+    }
+
+    public JsonObject jscode2session(String code, MinProgramConfigEnum configEnum){
+        String requestUrl = "https://api.weixin.qq.com/sns/jscode2session?appid="+configEnum.getAppId()+"&secret="+configEnum.getSecret()+"&js_code="+code+"&grant_type=authorization_code";
+        HttpUriRequest request = RequestBuilder.get(requestUrl).build();
+        try {
+            String response = AppHttpClient.getInstance().execute(request, 2000);
+            if(StringUtils.isBlank(response)){
+                return null;
+            }
+            log.info("WebChatService#jscode2session requestUrl:{}, response:{}", requestUrl, response);
+            JsonObject jsonObject = GsonUtil.parseJsonObject(response);
+            if(null == jsonObject){
+                return null;
+            }
+            return jsonObject;
+        } catch (IOException e) {
+            log.error("WebChatService#jscode2session requestUrl:{}, err:{}", requestUrl, e.getMessage(), e);
+            return null;
+        }
+    }
+}

BIN
mirage-service/src/main/resources/apiclient_cert.p12


+ 25 - 0
mirage-service/src/main/resources/apiclient_cert.pem

@@ -0,0 +1,25 @@
+-----BEGIN CERTIFICATE-----
+MIIEKDCCAxCgAwIBAgIUftn98CEPSDBSnm7qN7leL7hdGoYwDQYJKoZIhvcNAQEL
+BQAwXjELMAkGA1UEBhMCQ04xEzARBgNVBAoTClRlbnBheS5jb20xHTAbBgNVBAsT
+FFRlbnBheS5jb20gQ0EgQ2VudGVyMRswGQYDVQQDExJUZW5wYXkuY29tIFJvb3Qg
+Q0EwHhcNMjUwMzA3MDI1NTMyWhcNMzAwMzA2MDI1NTMyWjCBgTETMBEGA1UEAwwK
+MTcwNzc3MzIwMDEbMBkGA1UECgwS5b6u5L+h5ZWG5oi357O757ufMS0wKwYDVQQL
+DCTml6DplKHlpYfplKbnvZHnu5znp5HmioDmnInpmZDlhazlj7gxCzAJBgNVBAYT
+AkNOMREwDwYDVQQHDAhTaGVuWmhlbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBAL1rsrdP9PT86/RpU8844P4h0AEj2uDFlyppe3tL0ii5hA/oVgK2TJ2E
+lGI+GwiLkheBL+XCdzF8uhM6zCFqCa+kFEWX92vMRVN5NwNVEy1NV0ZeqOoLv/tC
+8KTQuL+poVf18mgDRQjNmF5oZMAe8vByVjiIh3ED6mBxfF1fQVqegPfZEe5OIVJt
+ceWb3smWl49ty4PHktsgtZ/ZRLt6LzAoLx0dJk/CgcGUiNWAo3HIhkSxund0mhJ2
+zGIrZjyeaYkZdf6RTw3rlINkwRIyDcLcq2kZ8KVdxT/D6QC6yNOf8ndRpnKvPEid
+UN1m7UcnZVNW4LR+Bddpe+wSGB+Lma0CAwEAAaOBuTCBtjAJBgNVHRMEAjAAMAsG
+A1UdDwQEAwID+DCBmwYDVR0fBIGTMIGQMIGNoIGKoIGHhoGEaHR0cDovL2V2Y2Eu
+aXRydXMuY29tLmNuL3B1YmxpYy9pdHJ1c2NybD9DQT0xQkQ0MjIwRTUwREJDMDRC
+MDZBRDM5NzU0OTg0NkMwMUMzRThFQkQyJnNnPUhBQ0M0NzFCNjU0MjJFMTJCMjdB
+OUQzM0E4N0FEMUNERjU5MjZFMTQwMzcxMA0GCSqGSIb3DQEBCwUAA4IBAQCOcxpY
+ECmVzsPU+Ksr3hiTF2XzMO9TnLgAIks+lNx6sfzSPQ0kapB1mBhnebBornO/1GWx
+Ibp8JrIRzfH6kqTRI6pQgP2TQ3ML7nXNH7C8Bwb56uB+TYIc3RYVYmkrQxcEOESB
+yJLjskNvPS4BGden1Mp99jDUvjqtVe0WKKSMcCXW3TC4y282w0RkyPP00ps4aPbA
+wRFe6PYNg95fNZGJtYVqwYt8BR4z/BdLrBEuDaObIsIf/WWjn0Ddw+QtlD2rxIxh
+d61Fkt1hyjbKIVjpvMKRfYhS62TJeuWcmQCIDi480whmAJAZs6lMpGYiDE0TGuPb
+gTuTCv8/B8tS6wV8
+-----END CERTIFICATE-----

+ 28 - 0
mirage-service/src/main/resources/apiclient_key.pem

@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC9a7K3T/T0/Ov0
+aVPPOOD+IdABI9rgxZcqaXt7S9IouYQP6FYCtkydhJRiPhsIi5IXgS/lwncxfLoT
+OswhagmvpBRFl/drzEVTeTcDVRMtTVdGXqjqC7/7QvCk0Li/qaFX9fJoA0UIzZhe
+aGTAHvLwclY4iIdxA+pgcXxdX0FanoD32RHuTiFSbXHlm97JlpePbcuDx5LbILWf
+2US7ei8wKC8dHSZPwoHBlIjVgKNxyIZEsbp3dJoSdsxiK2Y8nmmJGXX+kU8N65SD
+ZMESMg3C3KtpGfClXcU/w+kAusjTn/J3UaZyrzxInVDdZu1HJ2VTVuC0fgXXaXvs
+Ehgfi5mtAgMBAAECggEADGS/jL7uiBkEVzmNAkH2dqo/sdNmvs4u1deQ/MA5P/Dj
+cycAeazz1lqWWtc1bZm0NVV+jMnsgpBmnFt8f7Mwbw2NRgBrb+ItpwiHUPCAELa+
+vxvycV3Tcd0vYZlGfUPcCqVM9wE/wxOwpHywGuHrg0/31DsaoYpgC29hc7qHzCX1
+0+xipCWxcTZrbQu9WppARpgzEcwOQdqJkOyaXsDjpc/N+ADxS4yZAg/Sr2npbTHS
+2tW/Gfpz+C8dFxt1r+DE+/sjvsIiDARTkkxwT1svv2hMoU2iPhpozCjmSzFwzoIB
+g6VfrkMvKcHoin6qQNvWsabyWEbPilVQ4XH+YXEtAQKBgQDzgxUEt9vyhVPvwOri
+4hiXPj0tlJeoI70ihoW1cOyfWKLp0OwSi8yOYVddcveBDRqfPPyqd/WZklRcv3M7
+AUHtHkM9QNPK6aGFfz1mbEPqCRyAEXI7yOdLU1sMj7w0r8jHmHyqbwTrszcqs0Ge
+znPPan2j/MqKdRVQQAypMRvhewKBgQDHInwElXiJIpm1hL28exkNo8nUvZFYsSHw
+Bs8Qhg9UN8rGve2oCNzXwSUBZo9LCyv9MyJR7MbEKfr0/t2dLo7SlNv4JnbIErb6
+j52laBwLNmeR7uxFWsCSXfrdJvr/IM6Rsnr+qbQFTbF5e91hmrnSPQyUpQFX+PbY
+KfBtxWNk9wKBgBAYng8NyDAjuASlD+GnaFC3AHw+0jd/BUV+9t+jc0ClLz+dW71u
+GDthG9FFhC63mwbjlR//hfB8x67JyOghkSYHVhJ6coV7I/4bBwf6KBMUPxoIDTD+
+eqtz+xS9e4dQB0mKPtNVO1IJKqBb8P56TxVqyJtKXmkAjEQdt7iHUnx5AoGBAIBX
+K1LQ+rR3hlVEzfwx4f22eBgSbSCaWOMXoFBn9m0y4A+izQ6lV5/+LVlvvh6BfsJb
+OF1QmvJGlCVeNXpeTVJO+k4+jAN6fE+US+gU5y4azDlzoPc5/C7ZPxtVSiMwA7JH
+M5n5ybo5To6bVBzZ2bUz/221wt3t6Tl8HibBNsQVAoGAHMbDIlsTdMHHaOZ2YTVB
+qMIFXTZ4hT5S36byEhAr8hXrRw6BUfctL+FTLALEV+/7tnbFWpMwr7LJmrxqryt4
+EfWmgyYhZtJKaIVxrFsXi58a3VY9YJPxzPhRZck+8jviswXeQr0ECEM/fsnlsCLI
+dK7CBQ6veRYwx8Ful5jh2XY=
+-----END PRIVATE KEY-----

+ 65 - 0
mirage-service/src/main/resources/application.yml

@@ -0,0 +1,65 @@
+server:
+  port: 8880
+spring:
+  aop:
+    auto: true
+    proxy-target-class: true
+  jackson:
+    time-zone: GMT+8
+    default-property-inclusion: 'non_null'
+  http:
+    converters:
+      preferred-json-mapper: gson
+  redis:
+    host: 8.155.61.172
+    port: 6379
+    lettuce:
+      pool:
+        max-active: 20
+        max-idle: 20
+        min-idle: 0
+        max-wait: 3000
+    password: 'redis@2025'
+  datasource:
+    primary:
+      jdbc-url: jdbc:mysql://8.155.61.172:3306/csqz-client?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
+      username: 'root'
+      password: 'mysql@2025'
+      driver-class-name: com.mysql.cj.jdbc.Driver
+#      type: com.alibaba.druid.pool.DruidDataSource
+      druid:
+#        exception-sorter-class-name: com.alibaba.druid.pool.vendor.MySqlExceptionSorter
+#        validation-query: SELECT 1
+#        test-while-idle: false
+#        test-on-borrow: false
+#        test-on-return: true
+        # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+#        time-between-eviction-runs-millis: 30000
+        # 配置一个连接在池中最小生存的时间,单位是毫秒
+#        min-evictable-idle-time-millis: 180000
+        max-wait: 60000
+        initial-size: 2
+        max-active: 2
+        min-idle: 2
+    secondary:
+      jdbc-url: jdbc:sqlserver://52.130.155.58:1433;databaseName=dcjxb_db_test;encrypt=false;trustServerCertificate=true
+      username: 'sa'
+      password: 'yzx@2023!!'
+      driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
+      hikari:
+        connection-timeout: 30000
+        maximum-pool-size: 10
+  jpa:
+    primary:
+      properties:
+        hibernate:
+          dialect: org.hibernate.dialect.MySQL8Dialect
+          format_sql: true
+      show-sql: true
+    secondary:
+      properties:
+        hibernate:
+          dialect: org.hibernate.dialect.SQLServer2012Dialect
+          format_sql: true
+      show-sql: true
+

+ 200 - 0
mirage-service/src/main/resources/com/mirage/mirageservice/mapper/mysql/CsMinWechatUserMapper.xml

@@ -0,0 +1,200 @@
+<?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.mirage.mirageservice.mapper.mysql.CsMinWechatUserMapper">
+  <resultMap id="BaseResultMap" type="com.mirage.mirageservice.domain.CsMinWechatUser">
+    <!--@mbg.generated-->
+    <!--@Table cs_min_wechat_user-->
+    <id column="id" jdbcType="BIGINT" property="id" />
+    <result column="open_id" jdbcType="VARCHAR" property="openId" />
+    <result column="union_id" jdbcType="VARCHAR" property="unionId" />
+    <result column="phone" jdbcType="VARCHAR" property="phone" />
+    <result column="login_token" jdbcType="VARCHAR" property="loginToken" />
+    <result column="last_login_time" jdbcType="BIGINT" property="lastLoginTime" />
+    <result column="wechat_type" jdbcType="INTEGER" property="wechatType" />
+    <result column="wechat_app_id" jdbcType="VARCHAR" property="wechatAppId" />
+    <result column="nick_name" jdbcType="VARCHAR" property="nickName" />
+    <result column="head_img_url" jdbcType="VARCHAR" property="headImgUrl" />
+    <result column="is_deleted" jdbcType="INTEGER" property="isDeleted" />
+    <result column="create_time" jdbcType="BIGINT" property="createTime" />
+    <result column="modified_time" jdbcType="BIGINT" property="modifiedTime" />
+  </resultMap>
+  <sql id="Base_Column_List">
+    <!--@mbg.generated-->
+    id, open_id, union_id, phone, login_token, last_login_time, wechat_type, wechat_app_id, 
+    nick_name, head_img_url, is_deleted, create_time, modified_time
+  </sql>
+  <select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
+    <!--@mbg.generated-->
+    select 
+    <include refid="Base_Column_List" />
+    from cs_min_wechat_user
+    where id = #{id,jdbcType=BIGINT}
+  </select>
+  <delete id="deleteByPrimaryKey" parameterType="java.lang.Long">
+    <!--@mbg.generated-->
+    delete from cs_min_wechat_user
+    where id = #{id,jdbcType=BIGINT}
+  </delete>
+  <insert id="insert" keyColumn="id" keyProperty="id" parameterType="com.mirage.mirageservice.domain.CsMinWechatUser" useGeneratedKeys="true">
+    <!--@mbg.generated-->
+    insert into cs_min_wechat_user (open_id, union_id, phone, 
+      login_token, last_login_time, wechat_type, 
+      wechat_app_id, nick_name, head_img_url, 
+      is_deleted, create_time, modified_time
+      )
+    values (#{openId,jdbcType=VARCHAR}, #{unionId,jdbcType=VARCHAR}, #{phone,jdbcType=VARCHAR}, 
+      #{loginToken,jdbcType=VARCHAR}, #{lastLoginTime,jdbcType=BIGINT}, #{wechatType,jdbcType=INTEGER}, 
+      #{wechatAppId,jdbcType=VARCHAR}, #{nickName,jdbcType=VARCHAR}, #{headImgUrl,jdbcType=VARCHAR}, 
+      #{isDeleted,jdbcType=INTEGER}, #{createTime,jdbcType=BIGINT}, #{modifiedTime,jdbcType=BIGINT}
+      )
+  </insert>
+  <insert id="insertSelective" keyColumn="id" keyProperty="id" parameterType="com.mirage.mirageservice.domain.CsMinWechatUser" useGeneratedKeys="true">
+    <!--@mbg.generated-->
+    insert into cs_min_wechat_user
+    <trim prefix="(" suffix=")" suffixOverrides=",">
+      <if test="openId != null">
+        open_id,
+      </if>
+      <if test="unionId != null">
+        union_id,
+      </if>
+      <if test="phone != null">
+        phone,
+      </if>
+      <if test="loginToken != null">
+        login_token,
+      </if>
+      <if test="lastLoginTime != null">
+        last_login_time,
+      </if>
+      <if test="wechatType != null">
+        wechat_type,
+      </if>
+      <if test="wechatAppId != null">
+        wechat_app_id,
+      </if>
+      <if test="nickName != null">
+        nick_name,
+      </if>
+      <if test="headImgUrl != null">
+        head_img_url,
+      </if>
+      <if test="isDeleted != null">
+        is_deleted,
+      </if>
+      <if test="createTime != null">
+        create_time,
+      </if>
+      <if test="modifiedTime != null">
+        modified_time,
+      </if>
+    </trim>
+    <trim prefix="values (" suffix=")" suffixOverrides=",">
+      <if test="openId != null">
+        #{openId,jdbcType=VARCHAR},
+      </if>
+      <if test="unionId != null">
+        #{unionId,jdbcType=VARCHAR},
+      </if>
+      <if test="phone != null">
+        #{phone,jdbcType=VARCHAR},
+      </if>
+      <if test="loginToken != null">
+        #{loginToken,jdbcType=VARCHAR},
+      </if>
+      <if test="lastLoginTime != null">
+        #{lastLoginTime,jdbcType=BIGINT},
+      </if>
+      <if test="wechatType != null">
+        #{wechatType,jdbcType=INTEGER},
+      </if>
+      <if test="wechatAppId != null">
+        #{wechatAppId,jdbcType=VARCHAR},
+      </if>
+      <if test="nickName != null">
+        #{nickName,jdbcType=VARCHAR},
+      </if>
+      <if test="headImgUrl != null">
+        #{headImgUrl,jdbcType=VARCHAR},
+      </if>
+      <if test="isDeleted != null">
+        #{isDeleted,jdbcType=INTEGER},
+      </if>
+      <if test="createTime != null">
+        #{createTime,jdbcType=BIGINT},
+      </if>
+      <if test="modifiedTime != null">
+        #{modifiedTime,jdbcType=BIGINT},
+      </if>
+    </trim>
+  </insert>
+  <update id="updateByPrimaryKeySelective" parameterType="com.mirage.mirageservice.domain.CsMinWechatUser">
+    <!--@mbg.generated-->
+    update cs_min_wechat_user
+    <set>
+      <if test="openId != null">
+        open_id = #{openId,jdbcType=VARCHAR},
+      </if>
+      <if test="unionId != null">
+        union_id = #{unionId,jdbcType=VARCHAR},
+      </if>
+      <if test="phone != null">
+        phone = #{phone,jdbcType=VARCHAR},
+      </if>
+      <if test="loginToken != null">
+        login_token = #{loginToken,jdbcType=VARCHAR},
+      </if>
+      <if test="lastLoginTime != null">
+        last_login_time = #{lastLoginTime,jdbcType=BIGINT},
+      </if>
+      <if test="wechatType != null">
+        wechat_type = #{wechatType,jdbcType=INTEGER},
+      </if>
+      <if test="wechatAppId != null">
+        wechat_app_id = #{wechatAppId,jdbcType=VARCHAR},
+      </if>
+      <if test="nickName != null">
+        nick_name = #{nickName,jdbcType=VARCHAR},
+      </if>
+      <if test="headImgUrl != null">
+        head_img_url = #{headImgUrl,jdbcType=VARCHAR},
+      </if>
+      <if test="isDeleted != null">
+        is_deleted = #{isDeleted,jdbcType=INTEGER},
+      </if>
+      <if test="createTime != null">
+        create_time = #{createTime,jdbcType=BIGINT},
+      </if>
+      <if test="modifiedTime != null">
+        modified_time = #{modifiedTime,jdbcType=BIGINT},
+      </if>
+    </set>
+    where id = #{id,jdbcType=BIGINT}
+  </update>
+  <update id="updateByPrimaryKey" parameterType="com.mirage.mirageservice.domain.CsMinWechatUser">
+    <!--@mbg.generated-->
+    update cs_min_wechat_user
+    set open_id = #{openId,jdbcType=VARCHAR},
+      union_id = #{unionId,jdbcType=VARCHAR},
+      phone = #{phone,jdbcType=VARCHAR},
+      login_token = #{loginToken,jdbcType=VARCHAR},
+      last_login_time = #{lastLoginTime,jdbcType=BIGINT},
+      wechat_type = #{wechatType,jdbcType=INTEGER},
+      wechat_app_id = #{wechatAppId,jdbcType=VARCHAR},
+      nick_name = #{nickName,jdbcType=VARCHAR},
+      head_img_url = #{headImgUrl,jdbcType=VARCHAR},
+      is_deleted = #{isDeleted,jdbcType=INTEGER},
+      create_time = #{createTime,jdbcType=BIGINT},
+      modified_time = #{modifiedTime,jdbcType=BIGINT}
+    where id = #{id,jdbcType=BIGINT}
+  </update>
+
+<!--auto generated by MybatisCodeHelper on 2025-08-27-->
+  <select id="selectOneByPhoneAndWechatAppIdAndIsDeleted" resultMap="BaseResultMap">
+    select
+    <include refid="Base_Column_List" />
+    from cs_min_wechat_user
+    where phone=#{phone,jdbcType=VARCHAR} and wechat_app_id=#{wechatAppId,jdbcType=VARCHAR} and
+    is_deleted=#{isDeleted,jdbcType=INTEGER}
+  </select>
+</mapper>

+ 425 - 0
mirage-service/src/main/resources/com/mirage/mirageservice/mapper/mysql/StudentDataReportMapper.xml

@@ -0,0 +1,425 @@
+<?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.mirage.mirageservice.mapper.mysql.StudentDataReportMapper">
+  <resultMap id="BaseResultMap" type="com.mirage.mirageservice.domain.StudentDataReport">
+    <!--@mbg.generated-->
+    <!--@Table student_data_report-->
+    <id column="id" jdbcType="INTEGER" property="id" />
+    <result column="report_name" jdbcType="VARCHAR" property="reportName" />
+    <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
+    <result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
+    <result column="deleted" jdbcType="BOOLEAN" property="deleted" />
+    <result column="create_by" jdbcType="VARCHAR" property="createBy" />
+    <result column="update_by" jdbcType="VARCHAR" property="updateBy" />
+    <result column="student_id" jdbcType="BIGINT" property="studentId" />
+    <result column="student_name" jdbcType="VARCHAR" property="studentName" />
+    <result column="teacher_name" jdbcType="VARCHAR" property="teacherName" />
+    <result column="start_node" jdbcType="VARCHAR" property="startNode" />
+    <result column="end_node" jdbcType="VARCHAR" property="endNode" />
+    <result column="start_base_data" jdbcType="LONGVARCHAR" property="startBaseData" />
+    <result column="end_base_data" jdbcType="LONGVARCHAR" property="endBaseData" />
+    <result column="start_words_data" jdbcType="LONGVARCHAR" property="startWordsData" />
+    <result column="end_words_data" jdbcType="LONGVARCHAR" property="endWordsData" />
+    <result column="process_data" jdbcType="LONGVARCHAR" property="processData" />
+    <result column="vocabulary_change_data" jdbcType="LONGVARCHAR" property="vocabularyChangeData" />
+    <result column="difficulties_data" jdbcType="LONGVARCHAR" property="difficultiesData" />
+    <result column="vocabulary_contrast_data" jdbcType="LONGVARCHAR" property="vocabularyContrastData" />
+    <result column="speed_data" jdbcType="LONGVARCHAR" property="speedData" />
+    <result column="amount_data" jdbcType="LONGVARCHAR" property="amountData" />
+    <result column="accuracy_data" jdbcType="LONGVARCHAR" property="accuracyData" />
+    <result column="scores_data" jdbcType="LONGVARCHAR" property="scoresData" />
+    <result column="ranking_data" jdbcType="LONGVARCHAR" property="rankingData" />
+    <result column="business_name" jdbcType="VARCHAR" property="businessName" />
+    <result column="business_phone" jdbcType="VARCHAR" property="businessPhone" />
+    <result column="learn_advice" jdbcType="LONGVARCHAR" property="learnAdvice" />
+    <result column="view_url" jdbcType="VARCHAR" property="viewUrl" />
+    <result column="class_name" jdbcType="VARCHAR" property="className" />
+    <result column="student_num" jdbcType="VARCHAR" property="studentNum" />
+    <result column="student_stage" jdbcType="INTEGER" property="studentStage" />
+  </resultMap>
+  <sql id="Base_Column_List">
+    <!--@mbg.generated-->
+    id, report_name, create_time, update_time, deleted, create_by, update_by, student_id, 
+    student_name, teacher_name, start_node, end_node, start_base_data, end_base_data, 
+    start_words_data, end_words_data, process_data, vocabulary_change_data, difficulties_data, 
+    vocabulary_contrast_data, speed_data, amount_data, accuracy_data, scores_data, ranking_data, 
+    business_name, business_phone, learn_advice, view_url, class_name, student_num, student_stage
+  </sql>
+  <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
+    <!--@mbg.generated-->
+    select 
+    <include refid="Base_Column_List" />
+    from student_data_report
+    where id = #{id,jdbcType=INTEGER}
+  </select>
+  <delete id="deleteByPrimaryKey" parameterType="java.lang.Integer">
+    <!--@mbg.generated-->
+    delete from student_data_report
+    where id = #{id,jdbcType=INTEGER}
+  </delete>
+  <insert id="insert" keyColumn="id" keyProperty="id" parameterType="com.mirage.mirageservice.domain.StudentDataReport" useGeneratedKeys="true">
+    <!--@mbg.generated-->
+    insert into student_data_report (report_name, create_time, update_time, 
+      deleted, create_by, update_by, 
+      student_id, student_name, teacher_name, 
+      start_node, end_node, start_base_data, 
+      end_base_data, start_words_data, end_words_data, 
+      process_data, vocabulary_change_data, 
+      difficulties_data, vocabulary_contrast_data, 
+      speed_data, amount_data, accuracy_data, 
+      scores_data, ranking_data, business_name, 
+      business_phone, learn_advice, view_url, 
+      class_name, student_num, student_stage
+      )
+    values (#{reportName,jdbcType=VARCHAR}, #{createTime,jdbcType=TIMESTAMP}, #{updateTime,jdbcType=TIMESTAMP}, 
+      #{deleted,jdbcType=BOOLEAN}, #{createBy,jdbcType=VARCHAR}, #{updateBy,jdbcType=VARCHAR}, 
+      #{studentId,jdbcType=BIGINT}, #{studentName,jdbcType=VARCHAR}, #{teacherName,jdbcType=VARCHAR}, 
+      #{startNode,jdbcType=VARCHAR}, #{endNode,jdbcType=VARCHAR}, #{startBaseData,jdbcType=LONGVARCHAR}, 
+      #{endBaseData,jdbcType=LONGVARCHAR}, #{startWordsData,jdbcType=LONGVARCHAR}, #{endWordsData,jdbcType=LONGVARCHAR}, 
+      #{processData,jdbcType=LONGVARCHAR}, #{vocabularyChangeData,jdbcType=LONGVARCHAR}, 
+      #{difficultiesData,jdbcType=LONGVARCHAR}, #{vocabularyContrastData,jdbcType=LONGVARCHAR}, 
+      #{speedData,jdbcType=LONGVARCHAR}, #{amountData,jdbcType=LONGVARCHAR}, #{accuracyData,jdbcType=LONGVARCHAR}, 
+      #{scoresData,jdbcType=LONGVARCHAR}, #{rankingData,jdbcType=LONGVARCHAR}, #{businessName,jdbcType=VARCHAR}, 
+      #{businessPhone,jdbcType=VARCHAR}, #{learnAdvice,jdbcType=LONGVARCHAR}, #{viewUrl,jdbcType=VARCHAR}, 
+      #{className,jdbcType=VARCHAR}, #{studentNum,jdbcType=VARCHAR}, #{studentStage,jdbcType=INTEGER}
+      )
+  </insert>
+  <insert id="insertSelective" keyColumn="id" keyProperty="id" parameterType="com.mirage.mirageservice.domain.StudentDataReport" useGeneratedKeys="true">
+    <!--@mbg.generated-->
+    insert into student_data_report
+    <trim prefix="(" suffix=")" suffixOverrides=",">
+      <if test="reportName != null">
+        report_name,
+      </if>
+      <if test="createTime != null">
+        create_time,
+      </if>
+      <if test="updateTime != null">
+        update_time,
+      </if>
+      <if test="deleted != null">
+        deleted,
+      </if>
+      <if test="createBy != null">
+        create_by,
+      </if>
+      <if test="updateBy != null">
+        update_by,
+      </if>
+      <if test="studentId != null">
+        student_id,
+      </if>
+      <if test="studentName != null">
+        student_name,
+      </if>
+      <if test="teacherName != null">
+        teacher_name,
+      </if>
+      <if test="startNode != null">
+        start_node,
+      </if>
+      <if test="endNode != null">
+        end_node,
+      </if>
+      <if test="startBaseData != null">
+        start_base_data,
+      </if>
+      <if test="endBaseData != null">
+        end_base_data,
+      </if>
+      <if test="startWordsData != null">
+        start_words_data,
+      </if>
+      <if test="endWordsData != null">
+        end_words_data,
+      </if>
+      <if test="processData != null">
+        process_data,
+      </if>
+      <if test="vocabularyChangeData != null">
+        vocabulary_change_data,
+      </if>
+      <if test="difficultiesData != null">
+        difficulties_data,
+      </if>
+      <if test="vocabularyContrastData != null">
+        vocabulary_contrast_data,
+      </if>
+      <if test="speedData != null">
+        speed_data,
+      </if>
+      <if test="amountData != null">
+        amount_data,
+      </if>
+      <if test="accuracyData != null">
+        accuracy_data,
+      </if>
+      <if test="scoresData != null">
+        scores_data,
+      </if>
+      <if test="rankingData != null">
+        ranking_data,
+      </if>
+      <if test="businessName != null">
+        business_name,
+      </if>
+      <if test="businessPhone != null">
+        business_phone,
+      </if>
+      <if test="learnAdvice != null">
+        learn_advice,
+      </if>
+      <if test="viewUrl != null">
+        view_url,
+      </if>
+      <if test="className != null">
+        class_name,
+      </if>
+      <if test="studentNum != null">
+        student_num,
+      </if>
+      <if test="studentStage != null">
+        student_stage,
+      </if>
+    </trim>
+    <trim prefix="values (" suffix=")" suffixOverrides=",">
+      <if test="reportName != null">
+        #{reportName,jdbcType=VARCHAR},
+      </if>
+      <if test="createTime != null">
+        #{createTime,jdbcType=TIMESTAMP},
+      </if>
+      <if test="updateTime != null">
+        #{updateTime,jdbcType=TIMESTAMP},
+      </if>
+      <if test="deleted != null">
+        #{deleted,jdbcType=BOOLEAN},
+      </if>
+      <if test="createBy != null">
+        #{createBy,jdbcType=VARCHAR},
+      </if>
+      <if test="updateBy != null">
+        #{updateBy,jdbcType=VARCHAR},
+      </if>
+      <if test="studentId != null">
+        #{studentId,jdbcType=BIGINT},
+      </if>
+      <if test="studentName != null">
+        #{studentName,jdbcType=VARCHAR},
+      </if>
+      <if test="teacherName != null">
+        #{teacherName,jdbcType=VARCHAR},
+      </if>
+      <if test="startNode != null">
+        #{startNode,jdbcType=VARCHAR},
+      </if>
+      <if test="endNode != null">
+        #{endNode,jdbcType=VARCHAR},
+      </if>
+      <if test="startBaseData != null">
+        #{startBaseData,jdbcType=LONGVARCHAR},
+      </if>
+      <if test="endBaseData != null">
+        #{endBaseData,jdbcType=LONGVARCHAR},
+      </if>
+      <if test="startWordsData != null">
+        #{startWordsData,jdbcType=LONGVARCHAR},
+      </if>
+      <if test="endWordsData != null">
+        #{endWordsData,jdbcType=LONGVARCHAR},
+      </if>
+      <if test="processData != null">
+        #{processData,jdbcType=LONGVARCHAR},
+      </if>
+      <if test="vocabularyChangeData != null">
+        #{vocabularyChangeData,jdbcType=LONGVARCHAR},
+      </if>
+      <if test="difficultiesData != null">
+        #{difficultiesData,jdbcType=LONGVARCHAR},
+      </if>
+      <if test="vocabularyContrastData != null">
+        #{vocabularyContrastData,jdbcType=LONGVARCHAR},
+      </if>
+      <if test="speedData != null">
+        #{speedData,jdbcType=LONGVARCHAR},
+      </if>
+      <if test="amountData != null">
+        #{amountData,jdbcType=LONGVARCHAR},
+      </if>
+      <if test="accuracyData != null">
+        #{accuracyData,jdbcType=LONGVARCHAR},
+      </if>
+      <if test="scoresData != null">
+        #{scoresData,jdbcType=LONGVARCHAR},
+      </if>
+      <if test="rankingData != null">
+        #{rankingData,jdbcType=LONGVARCHAR},
+      </if>
+      <if test="businessName != null">
+        #{businessName,jdbcType=VARCHAR},
+      </if>
+      <if test="businessPhone != null">
+        #{businessPhone,jdbcType=VARCHAR},
+      </if>
+      <if test="learnAdvice != null">
+        #{learnAdvice,jdbcType=LONGVARCHAR},
+      </if>
+      <if test="viewUrl != null">
+        #{viewUrl,jdbcType=VARCHAR},
+      </if>
+      <if test="className != null">
+        #{className,jdbcType=VARCHAR},
+      </if>
+      <if test="studentNum != null">
+        #{studentNum,jdbcType=VARCHAR},
+      </if>
+      <if test="studentStage != null">
+        #{studentStage,jdbcType=INTEGER},
+      </if>
+    </trim>
+  </insert>
+  <update id="updateByPrimaryKeySelective" parameterType="com.mirage.mirageservice.domain.StudentDataReport">
+    <!--@mbg.generated-->
+    update student_data_report
+    <set>
+      <if test="reportName != null">
+        report_name = #{reportName,jdbcType=VARCHAR},
+      </if>
+      <if test="createTime != null">
+        create_time = #{createTime,jdbcType=TIMESTAMP},
+      </if>
+      <if test="updateTime != null">
+        update_time = #{updateTime,jdbcType=TIMESTAMP},
+      </if>
+      <if test="deleted != null">
+        deleted = #{deleted,jdbcType=BOOLEAN},
+      </if>
+      <if test="createBy != null">
+        create_by = #{createBy,jdbcType=VARCHAR},
+      </if>
+      <if test="updateBy != null">
+        update_by = #{updateBy,jdbcType=VARCHAR},
+      </if>
+      <if test="studentId != null">
+        student_id = #{studentId,jdbcType=BIGINT},
+      </if>
+      <if test="studentName != null">
+        student_name = #{studentName,jdbcType=VARCHAR},
+      </if>
+      <if test="teacherName != null">
+        teacher_name = #{teacherName,jdbcType=VARCHAR},
+      </if>
+      <if test="startNode != null">
+        start_node = #{startNode,jdbcType=VARCHAR},
+      </if>
+      <if test="endNode != null">
+        end_node = #{endNode,jdbcType=VARCHAR},
+      </if>
+      <if test="startBaseData != null">
+        start_base_data = #{startBaseData,jdbcType=LONGVARCHAR},
+      </if>
+      <if test="endBaseData != null">
+        end_base_data = #{endBaseData,jdbcType=LONGVARCHAR},
+      </if>
+      <if test="startWordsData != null">
+        start_words_data = #{startWordsData,jdbcType=LONGVARCHAR},
+      </if>
+      <if test="endWordsData != null">
+        end_words_data = #{endWordsData,jdbcType=LONGVARCHAR},
+      </if>
+      <if test="processData != null">
+        process_data = #{processData,jdbcType=LONGVARCHAR},
+      </if>
+      <if test="vocabularyChangeData != null">
+        vocabulary_change_data = #{vocabularyChangeData,jdbcType=LONGVARCHAR},
+      </if>
+      <if test="difficultiesData != null">
+        difficulties_data = #{difficultiesData,jdbcType=LONGVARCHAR},
+      </if>
+      <if test="vocabularyContrastData != null">
+        vocabulary_contrast_data = #{vocabularyContrastData,jdbcType=LONGVARCHAR},
+      </if>
+      <if test="speedData != null">
+        speed_data = #{speedData,jdbcType=LONGVARCHAR},
+      </if>
+      <if test="amountData != null">
+        amount_data = #{amountData,jdbcType=LONGVARCHAR},
+      </if>
+      <if test="accuracyData != null">
+        accuracy_data = #{accuracyData,jdbcType=LONGVARCHAR},
+      </if>
+      <if test="scoresData != null">
+        scores_data = #{scoresData,jdbcType=LONGVARCHAR},
+      </if>
+      <if test="rankingData != null">
+        ranking_data = #{rankingData,jdbcType=LONGVARCHAR},
+      </if>
+      <if test="businessName != null">
+        business_name = #{businessName,jdbcType=VARCHAR},
+      </if>
+      <if test="businessPhone != null">
+        business_phone = #{businessPhone,jdbcType=VARCHAR},
+      </if>
+      <if test="learnAdvice != null">
+        learn_advice = #{learnAdvice,jdbcType=LONGVARCHAR},
+      </if>
+      <if test="viewUrl != null">
+        view_url = #{viewUrl,jdbcType=VARCHAR},
+      </if>
+      <if test="className != null">
+        class_name = #{className,jdbcType=VARCHAR},
+      </if>
+      <if test="studentNum != null">
+        student_num = #{studentNum,jdbcType=VARCHAR},
+      </if>
+      <if test="studentStage != null">
+        student_stage = #{studentStage,jdbcType=INTEGER},
+      </if>
+    </set>
+    where id = #{id,jdbcType=INTEGER}
+  </update>
+  <update id="updateByPrimaryKey" parameterType="com.mirage.mirageservice.domain.StudentDataReport">
+    <!--@mbg.generated-->
+    update student_data_report
+    set report_name = #{reportName,jdbcType=VARCHAR},
+      create_time = #{createTime,jdbcType=TIMESTAMP},
+      update_time = #{updateTime,jdbcType=TIMESTAMP},
+      deleted = #{deleted,jdbcType=BOOLEAN},
+      create_by = #{createBy,jdbcType=VARCHAR},
+      update_by = #{updateBy,jdbcType=VARCHAR},
+      student_id = #{studentId,jdbcType=BIGINT},
+      student_name = #{studentName,jdbcType=VARCHAR},
+      teacher_name = #{teacherName,jdbcType=VARCHAR},
+      start_node = #{startNode,jdbcType=VARCHAR},
+      end_node = #{endNode,jdbcType=VARCHAR},
+      start_base_data = #{startBaseData,jdbcType=LONGVARCHAR},
+      end_base_data = #{endBaseData,jdbcType=LONGVARCHAR},
+      start_words_data = #{startWordsData,jdbcType=LONGVARCHAR},
+      end_words_data = #{endWordsData,jdbcType=LONGVARCHAR},
+      process_data = #{processData,jdbcType=LONGVARCHAR},
+      vocabulary_change_data = #{vocabularyChangeData,jdbcType=LONGVARCHAR},
+      difficulties_data = #{difficultiesData,jdbcType=LONGVARCHAR},
+      vocabulary_contrast_data = #{vocabularyContrastData,jdbcType=LONGVARCHAR},
+      speed_data = #{speedData,jdbcType=LONGVARCHAR},
+      amount_data = #{amountData,jdbcType=LONGVARCHAR},
+      accuracy_data = #{accuracyData,jdbcType=LONGVARCHAR},
+      scores_data = #{scoresData,jdbcType=LONGVARCHAR},
+      ranking_data = #{rankingData,jdbcType=LONGVARCHAR},
+      business_name = #{businessName,jdbcType=VARCHAR},
+      business_phone = #{businessPhone,jdbcType=VARCHAR},
+      learn_advice = #{learnAdvice,jdbcType=LONGVARCHAR},
+      view_url = #{viewUrl,jdbcType=VARCHAR},
+      class_name = #{className,jdbcType=VARCHAR},
+      student_num = #{studentNum,jdbcType=VARCHAR},
+      student_stage = #{studentStage,jdbcType=INTEGER}
+    where id = #{id,jdbcType=INTEGER}
+  </update>
+
+<!--auto generated by MybatisCodeHelper on 2025-08-26-->
+  <select id="selectAllByStudentIdOrderByCreateTimeDescAndIdDesc" resultMap="BaseResultMap">
+        select
+        <include refid="Base_Column_List"/>
+        from student_data_report
+        where student_id=#{studentId,jdbcType=BIGINT} order by create_time desc, id desc
+    </select>
+</mapper>

Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff