初始化
This commit is contained in:
commit
3b8b4eb1cc
10
.idea/.gitignore
generated
vendored
Normal file
10
.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
# 默认忽略的文件
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# 基于编辑器的 HTTP 客户端请求
|
||||
/httpRequests/
|
||||
# 已忽略包含查询文件的默认文件夹
|
||||
/queries/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
18
.idea/compiler.xml
generated
Normal file
18
.idea/compiler.xml
generated
Normal file
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<annotationProcessing>
|
||||
<profile name="Maven default annotation processors profile" enabled="true">
|
||||
<sourceOutputDir name="target/generated-sources/annotations" />
|
||||
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
|
||||
<outputRelativeToContentRoot value="true" />
|
||||
<module name="tracexd" />
|
||||
</profile>
|
||||
</annotationProcessing>
|
||||
</component>
|
||||
<component name="JavacSettings">
|
||||
<option name="ADDITIONAL_OPTIONS_OVERRIDE">
|
||||
<module name="tracexd" options="-parameters" />
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/encodings.xml
generated
Normal file
6
.idea/encodings.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding">
|
||||
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
|
||||
</component>
|
||||
</project>
|
||||
7
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
7
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@ -0,0 +1,7 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0" is_locked="false">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="VulnerableLibrariesGlobal" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="VulnerableLibrariesLocal" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
</profile>
|
||||
</component>
|
||||
20
.idea/jarRepositories.xml
generated
Normal file
20
.idea/jarRepositories.xml
generated
Normal file
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RemoteRepositoriesConfiguration">
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Central Repository" />
|
||||
<option name="url" value="https://repo.maven.apache.org/maven2" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Maven Central repository" />
|
||||
<option name="url" value="https://repo1.maven.org/maven2" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="jboss.community" />
|
||||
<option name="name" value="JBoss Community repository" />
|
||||
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
|
||||
</remote-repository>
|
||||
</component>
|
||||
</project>
|
||||
12
.idea/misc.xml
generated
Normal file
12
.idea/misc.xml
generated
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="MavenProjectsManager">
|
||||
<option name="originalFiles">
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$/pom.xml" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="26" project-jdk-type="JavaSDK" />
|
||||
</project>
|
||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@ -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>
|
||||
12
app-err.log
Normal file
12
app-err.log
Normal file
@ -0,0 +1,12 @@
|
||||
WARNING: A restricted method in java.lang.System has been called
|
||||
WARNING: java.lang.System::load has been called by org.fusesource.jansi.internal.JansiLoader in an unnamed module (file:/C:/Users/16332/.m2/wrapper/dists/apache-maven-3.9.9-bin/4nf9hui3q3djbarqar9g711ggc/apache-maven-3.9.9/lib/jansi-2.4.1.jar)
|
||||
WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
|
||||
WARNING: Restricted methods will be blocked in a future release unless native access is enabled
|
||||
|
||||
WARNING: A terminally deprecated method in sun.misc.Unsafe has been called
|
||||
WARNING: sun.misc.Unsafe::objectFieldOffset has been called by com.google.common.util.concurrent.AbstractFuture$UnsafeAtomicHelper (file:/C:/Users/16332/.m2/wrapper/dists/apache-maven-3.9.9-bin/4nf9hui3q3djbarqar9g711ggc/apache-maven-3.9.9/lib/guava-33.2.1-jre.jar)
|
||||
WARNING: Please consider reporting this to the maintainers of class com.google.common.util.concurrent.AbstractFuture$UnsafeAtomicHelper
|
||||
WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release
|
||||
WARNING: Final field _cipher in class org.sonatype.plexus.components.sec.dispatcher.DefaultSecDispatcher has been mutated reflectively by class org.eclipse.sisu.bean.BeanPropertyField in unnamed module @780cb77 (file:/C:/Users/16332/.m2/wrapper/dists/apache-maven-3.9.9-bin/4nf9hui3q3djbarqar9g711ggc/apache-maven-3.9.9/lib/org.eclipse.sisu.inject-0.9.0.M3.jar)
|
||||
WARNING: Use --enable-final-field-mutation=ALL-UNNAMED to avoid a warning
|
||||
WARNING: Mutating final fields will be blocked in a future release unless final field mutation is enabled
|
||||
78
app-out.log
Normal file
78
app-out.log
Normal file
@ -0,0 +1,78 @@
|
||||
[INFO] Scanning for projects...
|
||||
[INFO]
|
||||
[INFO] ------------------------< com.tracexd:tracexd >-------------------------
|
||||
[INFO] Building tracexd 1.0.0
|
||||
[INFO] from pom.xml
|
||||
[INFO] --------------------------------[ jar ]---------------------------------
|
||||
[INFO]
|
||||
[INFO] --- clean:3.4.1:clean (default-clean) @ tracexd ---
|
||||
[INFO] Deleting C:\Work\tracexd\target
|
||||
[INFO]
|
||||
[INFO] >>> spring-boot:3.5.14:run (default-cli) > test-compile @ tracexd >>>
|
||||
[INFO]
|
||||
[INFO] --- resources:3.3.1:resources (default-resources) @ tracexd ---
|
||||
[INFO] Copying 2 resources from src\main\resources to target\classes
|
||||
[INFO] Copying 6 resources from src\main\resources to target\classes
|
||||
[INFO]
|
||||
[INFO] --- compiler:3.14.1:compile (default-compile) @ tracexd ---
|
||||
[INFO] Recompiling the module because of changed source code.
|
||||
[INFO] Compiling 27 source files with javac [debug parameters release 26] to target\classes
|
||||
[INFO] /C:/Work/tracexd/src/main/java/com/tracexd/controller/VoiceController.java: 某些输入文件使用了未经检查或不安全的操作。
|
||||
[INFO] /C:/Work/tracexd/src/main/java/com/tracexd/controller/VoiceController.java: 有关详细信息, 请使用 -Xlint:unchecked 重新编译。
|
||||
[INFO]
|
||||
[INFO] --- resources:3.3.1:testResources (default-testResources) @ tracexd ---
|
||||
[INFO] skip non existing resourceDirectory C:\Work\tracexd\src\test\resources
|
||||
[INFO]
|
||||
[INFO] --- compiler:3.14.1:testCompile (default-testCompile) @ tracexd ---
|
||||
[INFO] Recompiling the module because of changed dependency.
|
||||
[INFO] Compiling 1 source file with javac [debug parameters release 26] to target\test-classes
|
||||
[INFO]
|
||||
[INFO] <<< spring-boot:3.5.14:run (default-cli) < test-compile @ tracexd <<<
|
||||
[INFO]
|
||||
[INFO]
|
||||
[INFO] --- spring-boot:3.5.14:run (default-cli) @ tracexd ---
|
||||
[INFO] Attaching agents: []
|
||||
2026-06-09T14:10:37.892+08:00 INFO 14244 --- [ main] com.tracexd.TracexdApplication : Starting TracexdApplication using Java 26.0.1 with PID 14244 (C:\Work\tracexd\target\classes started by 16332 in C:\Work\tracexd)
|
||||
2026-06-09T14:10:37.893+08:00 DEBUG 14244 --- [ main] com.tracexd.TracexdApplication : Running with Spring Boot v3.5.14, Spring v6.2.18
|
||||
2026-06-09T14:10:37.894+08:00 INFO 14244 --- [ main] com.tracexd.TracexdApplication : The following 1 profile is active: "test"
|
||||
2026-06-09T14:10:38.478+08:00 INFO 14244 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
|
||||
2026-06-09T14:10:38.478+08:00 INFO 14244 --- [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.54]
|
||||
2026-06-09T14:10:38.504+08:00 INFO 14244 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
|
||||
Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
|
||||
Get /198.18.0.1 network interface
|
||||
Get network interface info: name:iftype53_32769 (Meta Tunnel)
|
||||
Initialization Sequence datacenterId:0 workerId:31
|
||||
2026-06-09T14:10:38.600+08:00 INFO 14244 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
|
||||
2026-06-09T14:10:38.699+08:00 INFO 14244 --- [ main] com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Added connection conn0: url=jdbc:h2:mem:tracexd user=SA
|
||||
2026-06-09T14:10:38.699+08:00 INFO 14244 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
|
||||
2026-06-09T14:10:39.114+08:00 INFO 14244 --- [ main] m.e.s.MybatisPlusApplicationContextAware : Register ApplicationContext instances org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@e383572
|
||||
2026-06-09T14:10:39.150+08:00 WARN 14244 --- [ main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.context.ApplicationContextException: Failed to start bean 'webServerStartStop'
|
||||
2026-06-09T14:10:39.151+08:00 INFO 14244 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated...
|
||||
2026-06-09T14:10:39.152+08:00 INFO 14244 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.
|
||||
2026-06-09T14:10:39.166+08:00 ERROR 14244 --- [ main] o.s.b.d.LoggingFailureAnalysisReporter :
|
||||
|
||||
***************************
|
||||
APPLICATION FAILED TO START
|
||||
***************************
|
||||
|
||||
Description:
|
||||
|
||||
Web server failed to start. Port 8080 was already in use.
|
||||
|
||||
Action:
|
||||
|
||||
Identify and stop the process that's listening on port 8080 or configure this application to listen on another port.
|
||||
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
[INFO] BUILD FAILURE
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
[INFO] Total time: 3.910 s
|
||||
[INFO] Finished at: 2026-06-09T14:10:39+08:00
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
[ERROR] Failed to execute goal org.springframework.boot:spring-boot-maven-plugin:3.5.14:run (default-cli) on project tracexd: Process terminated with exit code: 1 -> [Help 1]
|
||||
[ERROR]
|
||||
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
|
||||
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
|
||||
[ERROR]
|
||||
[ERROR] For more information about the errors and possible solutions, please read the following articles:
|
||||
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoExecutionException
|
||||
71
app-out3.log
Normal file
71
app-out3.log
Normal file
@ -0,0 +1,71 @@
|
||||
[INFO] Scanning for projects...
|
||||
[INFO]
|
||||
[INFO] ------------------------< com.tracexd:tracexd >-------------------------
|
||||
[INFO] Building tracexd 1.0.0
|
||||
[INFO] from pom.xml
|
||||
[INFO] --------------------------------[ jar ]---------------------------------
|
||||
[INFO]
|
||||
[INFO] >>> spring-boot:3.5.14:run (default-cli) > test-compile @ tracexd >>>
|
||||
[INFO]
|
||||
[INFO] --- resources:3.3.1:resources (default-resources) @ tracexd ---
|
||||
[INFO] Copying 2 resources from src\main\resources to target\classes
|
||||
[INFO] Copying 6 resources from src\main\resources to target\classes
|
||||
[INFO]
|
||||
[INFO] --- compiler:3.14.1:compile (default-compile) @ tracexd ---
|
||||
[INFO] Nothing to compile - all classes are up to date.
|
||||
[INFO]
|
||||
[INFO] --- resources:3.3.1:testResources (default-testResources) @ tracexd ---
|
||||
[INFO] skip non existing resourceDirectory C:\Work\tracexd\src\test\resources
|
||||
[INFO]
|
||||
[INFO] --- compiler:3.14.1:testCompile (default-testCompile) @ tracexd ---
|
||||
[INFO] Nothing to compile - all classes are up to date.
|
||||
[INFO]
|
||||
[INFO] <<< spring-boot:3.5.14:run (default-cli) < test-compile @ tracexd <<<
|
||||
[INFO]
|
||||
[INFO]
|
||||
[INFO] --- spring-boot:3.5.14:run (default-cli) @ tracexd ---
|
||||
[INFO] Attaching agents: []
|
||||
2026-06-09T14:11:45.473+08:00 INFO 12060 --- [ main] com.tracexd.TracexdApplication : Starting TracexdApplication using Java 26.0.1 with PID 12060 (C:\Work\tracexd\target\classes started by 16332 in C:\Work\tracexd)
|
||||
2026-06-09T14:11:45.474+08:00 DEBUG 12060 --- [ main] com.tracexd.TracexdApplication : Running with Spring Boot v3.5.14, Spring v6.2.18
|
||||
2026-06-09T14:11:45.475+08:00 INFO 12060 --- [ main] com.tracexd.TracexdApplication : The following 1 profile is active: "test"
|
||||
2026-06-09T14:11:46.059+08:00 INFO 12060 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
|
||||
2026-06-09T14:11:46.060+08:00 INFO 12060 --- [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.54]
|
||||
2026-06-09T14:11:46.087+08:00 INFO 12060 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
|
||||
Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
|
||||
Get /198.18.0.1 network interface
|
||||
Get network interface info: name:iftype53_32769 (Meta Tunnel)
|
||||
Initialization Sequence datacenterId:0 workerId:25
|
||||
2026-06-09T14:11:46.186+08:00 INFO 12060 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
|
||||
2026-06-09T14:11:46.285+08:00 INFO 12060 --- [ main] com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Added connection conn0: url=jdbc:h2:mem:tracexd user=SA
|
||||
2026-06-09T14:11:46.286+08:00 INFO 12060 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
|
||||
2026-06-09T14:11:46.713+08:00 INFO 12060 --- [ main] m.e.s.MybatisPlusApplicationContextAware : Register ApplicationContext instances org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@e383572
|
||||
2026-06-09T14:11:46.748+08:00 WARN 12060 --- [ main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.context.ApplicationContextException: Failed to start bean 'webServerStartStop'
|
||||
2026-06-09T14:11:46.749+08:00 INFO 12060 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated...
|
||||
2026-06-09T14:11:46.750+08:00 INFO 12060 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.
|
||||
2026-06-09T14:11:46.764+08:00 ERROR 12060 --- [ main] o.s.b.d.LoggingFailureAnalysisReporter :
|
||||
|
||||
***************************
|
||||
APPLICATION FAILED TO START
|
||||
***************************
|
||||
|
||||
Description:
|
||||
|
||||
Web server failed to start. Port 8080 was already in use.
|
||||
|
||||
Action:
|
||||
|
||||
Identify and stop the process that's listening on port 8080 or configure this application to listen on another port.
|
||||
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
[INFO] BUILD FAILURE
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
[INFO] Total time: 2.975 s
|
||||
[INFO] Finished at: 2026-06-09T14:11:47+08:00
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
[ERROR] Failed to execute goal org.springframework.boot:spring-boot-maven-plugin:3.5.14:run (default-cli) on project tracexd: Process terminated with exit code: 1 -> [Help 1]
|
||||
[ERROR]
|
||||
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
|
||||
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
|
||||
[ERROR]
|
||||
[ERROR] For more information about the errors and possible solutions, please read the following articles:
|
||||
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoExecutionException
|
||||
168
build.log
Normal file
168
build.log
Normal file
@ -0,0 +1,168 @@
|
||||
mvn : WARNING: A restricted method in java.lang.System has been called
|
||||
所在位置 行:4 字符: 1
|
||||
+ mvn compile 2>&1 | Out-File "C:\Work\tracexd\build.log" -Encoding utf ...
|
||||
+ ~~~~~~~~~~~~~~~~
|
||||
+ CategoryInfo : NotSpecified: (WARNING: A rest...has been called:String) [], RemoteException
|
||||
+ FullyQualifiedErrorId : NativeCommandError
|
||||
|
||||
WARNING: java.lang.System::load has been called by org.fusesource.jansi.internal.JansiLoader in an unnamed module (file
|
||||
:/C:/Users/16332/.m2/wrapper/dists/apache-maven-3.9.9-bin/4nf9hui3q3djbarqar9g711ggc/apache-maven-3.9.9/lib/jansi-2.4.1
|
||||
.jar)
|
||||
WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
|
||||
WARNING: Restricted methods will be blocked in a future release unless native access is enabled
|
||||
|
||||
WARNING: A terminally deprecated method in sun.misc.Unsafe has been called
|
||||
WARNING: sun.misc.Unsafe::objectFieldOffset has been called by com.google.common.util.concurrent.AbstractFuture$UnsafeA
|
||||
tomicHelper (file:/C:/Users/16332/.m2/wrapper/dists/apache-maven-3.9.9-bin/4nf9hui3q3djbarqar9g711ggc/apache-maven-3.9.
|
||||
9/lib/guava-33.2.1-jre.jar)
|
||||
WARNING: Please consider reporting this to the maintainers of class com.google.common.util.concurrent.AbstractFuture$Un
|
||||
safeAtomicHelper
|
||||
WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release
|
||||
WARNING: Final field _cipher in class org.sonatype.plexus.components.sec.dispatcher.DefaultSecDispatcher has been mutat
|
||||
ed reflectively by class org.eclipse.sisu.bean.BeanPropertyField in unnamed module @11dc3715 (file:/C:/Users/16332/.m2/
|
||||
wrapper/dists/apache-maven-3.9.9-bin/4nf9hui3q3djbarqar9g711ggc/apache-maven-3.9.9/lib/org.eclipse.sisu.inject-0.9.0.M3
|
||||
.jar)
|
||||
WARNING: Use --enable-final-field-mutation=ALL-UNNAMED to avoid a warning
|
||||
WARNING: Mutating final fields will be blocked in a future release unless final field mutation is enabled
|
||||
[INFO] Scanning for projects...
|
||||
Downloading from central: https://repo.maven.apache.org/maven2/org/apache/activemq/activemq-bom/6.1.8/activemq-bom-6.1.8.pom
|
||||
Downloading from central: https://repo.maven.apache.org/maven2/org/apache/activemq/artemis-bom/2.40.0/artemis-bom-2.40.0.pom
|
||||
Downloading from central: https://repo.maven.apache.org/maven2/org/assertj/assertj-bom/3.27.7/assertj-bom-3.27.7.pom
|
||||
Downloading from central: https://repo.maven.apache.org/maven2/io/zipkin/reporter2/zipkin-reporter-bom/3.5.3/zipkin-reporter-bom-3.5.3.pom
|
||||
Downloading from central: https://repo.maven.apache.org/maven2/io/zipkin/brave/brave-bom/6.1.0/brave-bom-6.1.0.pom
|
||||
Downloading from central: https://repo.maven.apache.org/maven2/org/apache/cassandra/java-driver-bom/4.19.2/java-driver-bom-4.19.2.pom
|
||||
Downloading from central: https://repo.maven.apache.org/maven2/org/glassfish/jaxb/jaxb-bom/4.0.6/jaxb-bom-4.0.6.pom
|
||||
Downloading from central: https://repo.maven.apache.org/maven2/org/apache/groovy/groovy-bom/4.0.31/groovy-bom-4.0.31.pom
|
||||
Downloading from central: https://repo.maven.apache.org/maven2/org/infinispan/infinispan-bom/15.2.6.Final/infinispan-bom-15.2.6.Final.pom
|
||||
Downloading from central: https://repo.maven.apache.org/maven2/com/fasterxml/jackson/jackson-bom/2.21.2/jackson-bom-2.21.2.pom
|
||||
Downloading from central: https://repo.maven.apache.org/maven2/org/glassfish/jersey/jersey-bom/3.1.11/jersey-bom-3.1.11.pom
|
||||
Downloading from central: https://repo.maven.apache.org/maven2/org/eclipse/jetty/ee10/jetty-ee10-bom/12.0.34/jetty-ee10-bom-12.0.34.pom
|
||||
Downloading from central: https://repo.maven.apache.org/maven2/org/eclipse/jetty/jetty-bom/12.0.34/jetty-bom-12.0.34.pom
|
||||
Downloading from central: https://repo.maven.apache.org/maven2/org/junit/junit-bom/5.12.2/junit-bom-5.12.2.pom
|
||||
Downloading from central: https://repo.maven.apache.org/maven2/org/jetbrains/kotlin/kotlin-bom/1.9.25/kotlin-bom-1.9.25.pom
|
||||
Downloading from central: https://repo.maven.apache.org/maven2/org/jetbrains/kotlinx/kotlinx-coroutines-bom/1.8.1/kotlinx-coroutines-bom-1.8.1.pom
|
||||
Downloading from central: https://repo.maven.apache.org/maven2/org/jetbrains/kotlinx/kotlinx-serialization-bom/1.6.3/kotlinx-serialization-bom-1.6.3.pom
|
||||
Downloading from central: https://repo.maven.apache.org/maven2/org/apache/logging/log4j/log4j-bom/2.24.3/log4j-bom-2.24.3.pom
|
||||
Downloading from central: https://repo.maven.apache.org/maven2/io/micrometer/micrometer-bom/1.15.11/micrometer-bom-1.15.11.pom
|
||||
Downloading from central: https://repo.maven.apache.org/maven2/org/mockito/mockito-bom/5.17.0/mockito-bom-5.17.0.pom
|
||||
Downloading from central: https://repo.maven.apache.org/maven2/org/mongodb/mongodb-driver-bom/5.5.2/mongodb-driver-bom-5.5.2.pom
|
||||
Downloading from central: https://repo.maven.apache.org/maven2/io/netty/netty-bom/4.1.132.Final/netty-bom-4.1.132.Final.pom
|
||||
Downloading from central: https://repo.maven.apache.org/maven2/io/opentelemetry/opentelemetry-bom/1.49.0/opentelemetry-bom-1.49.0.pom
|
||||
Downloading from central: https://repo.maven.apache.org/maven2/io/prometheus/prometheus-metrics-bom/1.3.10/prometheus-metrics-bom-1.3.10.pom
|
||||
Downloading from central: https://repo.maven.apache.org/maven2/io/prometheus/simpleclient_bom/0.16.0/simpleclient_bom-0.16.0.pom
|
||||
Downloading from central: https://repo.maven.apache.org/maven2/org/apache/pulsar/pulsar-bom/4.0.9/pulsar-bom-4.0.9.pom
|
||||
Downloading from central: https://repo.maven.apache.org/maven2/org/apache/pulsar/pulsar-client-reactive-bom/0.6.0/pulsar-client-reactive-bom-0.6.0.pom
|
||||
Downloading from central: https://repo.maven.apache.org/maven2/com/querydsl/querydsl-bom/5.1.0/querydsl-bom-5.1.0.pom
|
||||
Downloading from central: https://repo.maven.apache.org/maven2/io/projectreactor/reactor-bom/2024.0.17/reactor-bom-2024.0.17.pom
|
||||
Downloading from central: https://repo.maven.apache.org/maven2/io/rest-assured/rest-assured-bom/5.5.7/rest-assured-bom-5.5.7.pom
|
||||
Downloading from central: https://repo.maven.apache.org/maven2/io/rsocket/rsocket-bom/1.1.5/rsocket-bom-1.1.5.pom
|
||||
Downloading from central: https://repo.maven.apache.org/maven2/org/seleniumhq/selenium/selenium-bom/4.31.0/selenium-bom-4.31.0.pom
|
||||
Downloading from central: https://repo.maven.apache.org/maven2/org/springframework/amqp/spring-amqp-bom/3.2.10/spring-amqp-bom-3.2.10.pom
|
||||
Downloading from central: https://repo.maven.apache.org/maven2/org/springframework/batch/spring-batch-bom/5.2.5/spring-batch-bom-5.2.5.pom
|
||||
Downloading from central: https://repo.maven.apache.org/maven2/org/springframework/data/spring-data-bom/2025.0.11/spring-data-bom-2025.0.11.pom
|
||||
Downloading from central: https://repo.maven.apache.org/maven2/org/springframework/spring-framework-bom/6.2.18/spring-framework-bom-6.2.18.pom
|
||||
Downloading from central: https://repo.maven.apache.org/maven2/org/springframework/integration/spring-integration-bom/6.5.8/spring-integration-bom-6.5.8.pom
|
||||
Downloading from central: https://repo.maven.apache.org/maven2/org/springframework/pulsar/spring-pulsar-bom/1.2.17/spring-pulsar-bom-1.2.17.pom
|
||||
Downloading from central: https://repo.maven.apache.org/maven2/org/springframework/restdocs/spring-restdocs-bom/3.0.5/spring-restdocs-bom-3.0.5.pom
|
||||
Downloading from central: https://repo.maven.apache.org/maven2/org/springframework/security/spring-security-bom/6.5.10/spring-security-bom-6.5.10.pom
|
||||
Downloading from central: https://repo.maven.apache.org/maven2/org/springframework/session/spring-session-bom/3.5.6/spring-session-bom-3.5.6.pom
|
||||
Downloading from central: https://repo.maven.apache.org/maven2/org/springframework/ws/spring-ws-bom/4.1.3/spring-ws-bom-4.1.3.pom
|
||||
Downloading from central: https://repo.maven.apache.org/maven2/org/testcontainers/testcontainers-bom/1.21.4/testcontainers-bom-1.21.4.pom
|
||||
[ERROR] [ERROR] Some problems were encountered while processing the POMs:
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.apache.activemq:activemq-bom:pom:6.1.8 (absent): Could not transfer artifact org.apache.activemq:activemq-bom:pom:6.1.8 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2124, column 19
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.apache.activemq:artemis-bom:pom:2.40.0 (absent): Could not transfer artifact org.apache.activemq:artemis-bom:pom:2.40.0 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2131, column 19
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.assertj:assertj-bom:pom:3.27.7 (absent): Could not transfer artifact org.assertj:assertj-bom:pom:3.27.7 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2138, column 19
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: io.zipkin.reporter2:zipkin-reporter-bom:pom:3.5.3 (absent): Could not transfer artifact io.zipkin.reporter2:zipkin-reporter-bom:pom:3.5.3 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2145, column 19
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: io.zipkin.brave:brave-bom:pom:6.1.0 (absent): Could not transfer artifact io.zipkin.brave:brave-bom:pom:6.1.0 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2152, column 19
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.apache.cassandra:java-driver-bom:pom:4.19.2 (absent): Could not transfer artifact org.apache.cassandra:java-driver-bom:pom:4.19.2 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2159, column 19
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.glassfish.jaxb:jaxb-bom:pom:4.0.6 (absent): Could not transfer artifact org.glassfish.jaxb:jaxb-bom:pom:4.0.6 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2166, column 19
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.apache.groovy:groovy-bom:pom:4.0.31 (absent): Could not transfer artifact org.apache.groovy:groovy-bom:pom:4.0.31 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2173, column 19
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.infinispan:infinispan-bom:pom:15.2.6.Final (absent): Could not transfer artifact org.infinispan:infinispan-bom:pom:15.2.6.Final from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2180, column 19
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: com.fasterxml.jackson:jackson-bom:pom:2.21.2 (absent): Could not transfer artifact com.fasterxml.jackson:jackson-bom:pom:2.21.2 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2187, column 19
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.glassfish.jersey:jersey-bom:pom:3.1.11 (absent): Could not transfer artifact org.glassfish.jersey:jersey-bom:pom:3.1.11 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2194, column 19
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.eclipse.jetty.ee10:jetty-ee10-bom:pom:12.0.34 (absent): Could not transfer artifact org.eclipse.jetty.ee10:jetty-ee10-bom:pom:12.0.34 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2201, column 19
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.eclipse.jetty:jetty-bom:pom:12.0.34 (absent): Could not transfer artifact org.eclipse.jetty:jetty-bom:pom:12.0.34 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2208, column 19
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.junit:junit-bom:pom:5.12.2 (absent): Could not transfer artifact org.junit:junit-bom:pom:5.12.2 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2215, column 19
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.jetbrains.kotlin:kotlin-bom:pom:1.9.25 (absent): Could not transfer artifact org.jetbrains.kotlin:kotlin-bom:pom:1.9.25 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2222, column 19
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.jetbrains.kotlinx:kotlinx-coroutines-bom:pom:1.8.1 (absent): Could not transfer artifact org.jetbrains.kotlinx:kotlinx-coroutines-bom:pom:1.8.1 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2229, column 19
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.jetbrains.kotlinx:kotlinx-serialization-bom:pom:1.6.3 (absent): Could not transfer artifact org.jetbrains.kotlinx:kotlinx-serialization-bom:pom:1.6.3 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2236, column 19
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.apache.logging.log4j:log4j-bom:pom:2.24.3 (absent): Could not transfer artifact org.apache.logging.log4j:log4j-bom:pom:2.24.3 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2243, column 19
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: io.micrometer:micrometer-bom:pom:1.15.11 (absent): Could not transfer artifact io.micrometer:micrometer-bom:pom:1.15.11 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2250, column 19
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.mockito:mockito-bom:pom:5.17.0 (absent): Could not transfer artifact org.mockito:mockito-bom:pom:5.17.0 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2257, column 19
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.mongodb:mongodb-driver-bom:pom:5.5.2 (absent): Could not transfer artifact org.mongodb:mongodb-driver-bom:pom:5.5.2 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2264, column 19
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: io.netty:netty-bom:pom:4.1.132.Final (absent): Could not transfer artifact io.netty:netty-bom:pom:4.1.132.Final from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2271, column 19
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: io.opentelemetry:opentelemetry-bom:pom:1.49.0 (absent): Could not transfer artifact io.opentelemetry:opentelemetry-bom:pom:1.49.0 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2278, column 19
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: io.prometheus:prometheus-metrics-bom:pom:1.3.10 (absent): Could not transfer artifact io.prometheus:prometheus-metrics-bom:pom:1.3.10 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2285, column 19
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: io.prometheus:simpleclient_bom:pom:0.16.0 (absent): Could not transfer artifact io.prometheus:simpleclient_bom:pom:0.16.0 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2292, column 19
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.apache.pulsar:pulsar-bom:pom:4.0.9 (absent): Could not transfer artifact org.apache.pulsar:pulsar-bom:pom:4.0.9 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2299, column 19
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.apache.pulsar:pulsar-client-reactive-bom:pom:0.6.0 (absent): Could not transfer artifact org.apache.pulsar:pulsar-client-reactive-bom:pom:0.6.0 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2306, column 19
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: com.querydsl:querydsl-bom:pom:5.1.0 (absent): Could not transfer artifact com.querydsl:querydsl-bom:pom:5.1.0 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2313, column 19
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: io.projectreactor:reactor-bom:pom:2024.0.17 (absent): Could not transfer artifact io.projectreactor:reactor-bom:pom:2024.0.17 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2320, column 19
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: io.rest-assured:rest-assured-bom:pom:5.5.7 (absent): Could not transfer artifact io.rest-assured:rest-assured-bom:pom:5.5.7 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2327, column 19
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: io.rsocket:rsocket-bom:pom:1.1.5 (absent): Could not transfer artifact io.rsocket:rsocket-bom:pom:1.1.5 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2334, column 19
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.seleniumhq.selenium:selenium-bom:pom:4.31.0 (absent): Could not transfer artifact org.seleniumhq.selenium:selenium-bom:pom:4.31.0 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2341, column 19
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.springframework.amqp:spring-amqp-bom:pom:3.2.10 (absent): Could not transfer artifact org.springframework.amqp:spring-amqp-bom:pom:3.2.10 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2348, column 19
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.springframework.batch:spring-batch-bom:pom:5.2.5 (absent): Could not transfer artifact org.springframework.batch:spring-batch-bom:pom:5.2.5 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2355, column 19
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.springframework.data:spring-data-bom:pom:2025.0.11 (absent): Could not transfer artifact org.springframework.data:spring-data-bom:pom:2025.0.11 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2362, column 19
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.springframework:spring-framework-bom:pom:6.2.18 (absent): Could not transfer artifact org.springframework:spring-framework-bom:pom:6.2.18 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2369, column 19
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.springframework.integration:spring-integration-bom:pom:6.5.8 (absent): Could not transfer artifact org.springframework.integration:spring-integration-bom:pom:6.5.8 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2376, column 19
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.springframework.pulsar:spring-pulsar-bom:pom:1.2.17 (absent): Could not transfer artifact org.springframework.pulsar:spring-pulsar-bom:pom:1.2.17 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2383, column 19
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.springframework.restdocs:spring-restdocs-bom:pom:3.0.5 (absent): Could not transfer artifact org.springframework.restdocs:spring-restdocs-bom:pom:3.0.5 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2390, column 19
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.springframework.security:spring-security-bom:pom:6.5.10 (absent): Could not transfer artifact org.springframework.security:spring-security-bom:pom:6.5.10 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2397, column 19
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.springframework.session:spring-session-bom:pom:3.5.6 (absent): Could not transfer artifact org.springframework.session:spring-session-bom:pom:3.5.6 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2404, column 19
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.springframework.ws:spring-ws-bom:pom:4.1.3 (absent): Could not transfer artifact org.springframework.ws:spring-ws-bom:pom:4.1.3 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2411, column 19
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.testcontainers:testcontainers-bom:pom:1.21.4 (absent): Could not transfer artifact org.testcontainers:testcontainers-bom:pom:1.21.4 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2418, column 19
|
||||
@
|
||||
[ERROR] The build could not read 1 project -> [Help 1]
|
||||
[ERROR]
|
||||
[ERROR] The project com.tracexd:tracexd:1.0.0 (C:\Work\tracexd\pom.xml) has 43 errors
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.apache.activemq:activemq-bom:pom:6.1.8 (absent): Could not transfer artifact org.apache.activemq:activemq-bom:pom:6.1.8 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2124, column 19 -> [Help 2]
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.apache.activemq:artemis-bom:pom:2.40.0 (absent): Could not transfer artifact org.apache.activemq:artemis-bom:pom:2.40.0 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2131, column 19 -> [Help 2]
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.assertj:assertj-bom:pom:3.27.7 (absent): Could not transfer artifact org.assertj:assertj-bom:pom:3.27.7 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2138, column 19 -> [Help 2]
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: io.zipkin.reporter2:zipkin-reporter-bom:pom:3.5.3 (absent): Could not transfer artifact io.zipkin.reporter2:zipkin-reporter-bom:pom:3.5.3 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2145, column 19 -> [Help 2]
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: io.zipkin.brave:brave-bom:pom:6.1.0 (absent): Could not transfer artifact io.zipkin.brave:brave-bom:pom:6.1.0 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2152, column 19 -> [Help 2]
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.apache.cassandra:java-driver-bom:pom:4.19.2 (absent): Could not transfer artifact org.apache.cassandra:java-driver-bom:pom:4.19.2 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2159, column 19 -> [Help 2]
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.glassfish.jaxb:jaxb-bom:pom:4.0.6 (absent): Could not transfer artifact org.glassfish.jaxb:jaxb-bom:pom:4.0.6 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2166, column 19 -> [Help 2]
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.apache.groovy:groovy-bom:pom:4.0.31 (absent): Could not transfer artifact org.apache.groovy:groovy-bom:pom:4.0.31 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2173, column 19 -> [Help 2]
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.infinispan:infinispan-bom:pom:15.2.6.Final (absent): Could not transfer artifact org.infinispan:infinispan-bom:pom:15.2.6.Final from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2180, column 19 -> [Help 2]
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: com.fasterxml.jackson:jackson-bom:pom:2.21.2 (absent): Could not transfer artifact com.fasterxml.jackson:jackson-bom:pom:2.21.2 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2187, column 19 -> [Help 2]
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.glassfish.jersey:jersey-bom:pom:3.1.11 (absent): Could not transfer artifact org.glassfish.jersey:jersey-bom:pom:3.1.11 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2194, column 19 -> [Help 2]
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.eclipse.jetty.ee10:jetty-ee10-bom:pom:12.0.34 (absent): Could not transfer artifact org.eclipse.jetty.ee10:jetty-ee10-bom:pom:12.0.34 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2201, column 19 -> [Help 2]
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.eclipse.jetty:jetty-bom:pom:12.0.34 (absent): Could not transfer artifact org.eclipse.jetty:jetty-bom:pom:12.0.34 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2208, column 19 -> [Help 2]
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.junit:junit-bom:pom:5.12.2 (absent): Could not transfer artifact org.junit:junit-bom:pom:5.12.2 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2215, column 19 -> [Help 2]
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.jetbrains.kotlin:kotlin-bom:pom:1.9.25 (absent): Could not transfer artifact org.jetbrains.kotlin:kotlin-bom:pom:1.9.25 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2222, column 19 -> [Help 2]
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.jetbrains.kotlinx:kotlinx-coroutines-bom:pom:1.8.1 (absent): Could not transfer artifact org.jetbrains.kotlinx:kotlinx-coroutines-bom:pom:1.8.1 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2229, column 19 -> [Help 2]
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.jetbrains.kotlinx:kotlinx-serialization-bom:pom:1.6.3 (absent): Could not transfer artifact org.jetbrains.kotlinx:kotlinx-serialization-bom:pom:1.6.3 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2236, column 19 -> [Help 2]
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.apache.logging.log4j:log4j-bom:pom:2.24.3 (absent): Could not transfer artifact org.apache.logging.log4j:log4j-bom:pom:2.24.3 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2243, column 19 -> [Help 2]
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: io.micrometer:micrometer-bom:pom:1.15.11 (absent): Could not transfer artifact io.micrometer:micrometer-bom:pom:1.15.11 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2250, column 19 -> [Help 2]
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.mockito:mockito-bom:pom:5.17.0 (absent): Could not transfer artifact org.mockito:mockito-bom:pom:5.17.0 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2257, column 19 -> [Help 2]
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.mongodb:mongodb-driver-bom:pom:5.5.2 (absent): Could not transfer artifact org.mongodb:mongodb-driver-bom:pom:5.5.2 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2264, column 19 -> [Help 2]
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: io.netty:netty-bom:pom:4.1.132.Final (absent): Could not transfer artifact io.netty:netty-bom:pom:4.1.132.Final from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2271, column 19 -> [Help 2]
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: io.opentelemetry:opentelemetry-bom:pom:1.49.0 (absent): Could not transfer artifact io.opentelemetry:opentelemetry-bom:pom:1.49.0 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2278, column 19 -> [Help 2]
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: io.prometheus:prometheus-metrics-bom:pom:1.3.10 (absent): Could not transfer artifact io.prometheus:prometheus-metrics-bom:pom:1.3.10 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2285, column 19 -> [Help 2]
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: io.prometheus:simpleclient_bom:pom:0.16.0 (absent): Could not transfer artifact io.prometheus:simpleclient_bom:pom:0.16.0 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2292, column 19 -> [Help 2]
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.apache.pulsar:pulsar-bom:pom:4.0.9 (absent): Could not transfer artifact org.apache.pulsar:pulsar-bom:pom:4.0.9 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2299, column 19 -> [Help 2]
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.apache.pulsar:pulsar-client-reactive-bom:pom:0.6.0 (absent): Could not transfer artifact org.apache.pulsar:pulsar-client-reactive-bom:pom:0.6.0 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2306, column 19 -> [Help 2]
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: com.querydsl:querydsl-bom:pom:5.1.0 (absent): Could not transfer artifact com.querydsl:querydsl-bom:pom:5.1.0 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2313, column 19 -> [Help 2]
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: io.projectreactor:reactor-bom:pom:2024.0.17 (absent): Could not transfer artifact io.projectreactor:reactor-bom:pom:2024.0.17 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2320, column 19 -> [Help 2]
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: io.rest-assured:rest-assured-bom:pom:5.5.7 (absent): Could not transfer artifact io.rest-assured:rest-assured-bom:pom:5.5.7 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2327, column 19 -> [Help 2]
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: io.rsocket:rsocket-bom:pom:1.1.5 (absent): Could not transfer artifact io.rsocket:rsocket-bom:pom:1.1.5 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2334, column 19 -> [Help 2]
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.seleniumhq.selenium:selenium-bom:pom:4.31.0 (absent): Could not transfer artifact org.seleniumhq.selenium:selenium-bom:pom:4.31.0 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2341, column 19 -> [Help 2]
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.springframework.amqp:spring-amqp-bom:pom:3.2.10 (absent): Could not transfer artifact org.springframework.amqp:spring-amqp-bom:pom:3.2.10 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2348, column 19 -> [Help 2]
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.springframework.batch:spring-batch-bom:pom:5.2.5 (absent): Could not transfer artifact org.springframework.batch:spring-batch-bom:pom:5.2.5 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2355, column 19 -> [Help 2]
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.springframework.data:spring-data-bom:pom:2025.0.11 (absent): Could not transfer artifact org.springframework.data:spring-data-bom:pom:2025.0.11 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2362, column 19 -> [Help 2]
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.springframework:spring-framework-bom:pom:6.2.18 (absent): Could not transfer artifact org.springframework:spring-framework-bom:pom:6.2.18 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2369, column 19 -> [Help 2]
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.springframework.integration:spring-integration-bom:pom:6.5.8 (absent): Could not transfer artifact org.springframework.integration:spring-integration-bom:pom:6.5.8 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2376, column 19 -> [Help 2]
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.springframework.pulsar:spring-pulsar-bom:pom:1.2.17 (absent): Could not transfer artifact org.springframework.pulsar:spring-pulsar-bom:pom:1.2.17 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2383, column 19 -> [Help 2]
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.springframework.restdocs:spring-restdocs-bom:pom:3.0.5 (absent): Could not transfer artifact org.springframework.restdocs:spring-restdocs-bom:pom:3.0.5 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2390, column 19 -> [Help 2]
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.springframework.security:spring-security-bom:pom:6.5.10 (absent): Could not transfer artifact org.springframework.security:spring-security-bom:pom:6.5.10 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2397, column 19 -> [Help 2]
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.springframework.session:spring-session-bom:pom:3.5.6 (absent): Could not transfer artifact org.springframework.session:spring-session-bom:pom:3.5.6 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2404, column 19 -> [Help 2]
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.springframework.ws:spring-ws-bom:pom:4.1.3 (absent): Could not transfer artifact org.springframework.ws:spring-ws-bom:pom:4.1.3 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2411, column 19 -> [Help 2]
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.testcontainers:testcontainers-bom:pom:1.21.4 (absent): Could not transfer artifact org.testcontainers:testcontainers-bom:pom:1.21.4 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2418, column 19 -> [Help 2]
|
||||
[ERROR]
|
||||
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
|
||||
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
|
||||
[ERROR]
|
||||
[ERROR] For more information about the errors and possible solutions, please read the following articles:
|
||||
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/ProjectBuildingException
|
||||
[ERROR] [Help 2] http://cwiki.apache.org/confluence/display/MAVEN/UnresolvableModelException
|
||||
63
build2.log
Normal file
63
build2.log
Normal file
@ -0,0 +1,63 @@
|
||||
mvn : WARNING: A restricted method in java.lang.System has been called
|
||||
所在位置 行:2 字符: 1
|
||||
+ mvn compile 2>&1 | Out-File "C:\Work\tracexd\build2.log" -Encoding ut ...
|
||||
+ ~~~~~~~~~~~~~~~~
|
||||
+ CategoryInfo : NotSpecified: (WARNING: A rest...has been called:String) [], RemoteException
|
||||
+ FullyQualifiedErrorId : NativeCommandError
|
||||
|
||||
WARNING: java.lang.System::load has been called by org.fusesource.jansi.internal.JansiLoader in an unnamed module (file
|
||||
:/C:/Users/16332/.m2/wrapper/dists/apache-maven-3.9.9-bin/4nf9hui3q3djbarqar9g711ggc/apache-maven-3.9.9/lib/jansi-2.4.1
|
||||
.jar)
|
||||
WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
|
||||
WARNING: Restricted methods will be blocked in a future release unless native access is enabled
|
||||
|
||||
WARNING: A terminally deprecated method in sun.misc.Unsafe has been called
|
||||
WARNING: sun.misc.Unsafe::objectFieldOffset has been called by com.google.common.util.concurrent.AbstractFuture$UnsafeA
|
||||
tomicHelper (file:/C:/Users/16332/.m2/wrapper/dists/apache-maven-3.9.9-bin/4nf9hui3q3djbarqar9g711ggc/apache-maven-3.9.
|
||||
9/lib/guava-33.2.1-jre.jar)
|
||||
WARNING: Please consider reporting this to the maintainers of class com.google.common.util.concurrent.AbstractFuture$Un
|
||||
safeAtomicHelper
|
||||
WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release
|
||||
WARNING: Final field _cipher in class org.sonatype.plexus.components.sec.dispatcher.DefaultSecDispatcher has been mutat
|
||||
ed reflectively by class org.eclipse.sisu.bean.BeanPropertyField in unnamed module @780cb77 (file:/C:/Users/16332/.m2/w
|
||||
rapper/dists/apache-maven-3.9.9-bin/4nf9hui3q3djbarqar9g711ggc/apache-maven-3.9.9/lib/org.eclipse.sisu.inject-0.9.0.M3.
|
||||
jar)
|
||||
WARNING: Use --enable-final-field-mutation=ALL-UNNAMED to avoid a warning
|
||||
WARNING: Mutating final fields will be blocked in a future release unless final field mutation is enabled
|
||||
[INFO] Scanning for projects...
|
||||
Downloading from central: https://repo.maven.apache.org/maven2/com/fasterxml/jackson/jackson-bom/2.21.2/jackson-bom-2.21.2.pom
|
||||
Downloading from central: https://repo.maven.apache.org/maven2/org/jetbrains/kotlin/kotlin-bom/1.9.25/kotlin-bom-1.9.25.pom
|
||||
Downloading from central: https://repo.maven.apache.org/maven2/org/apache/logging/log4j/log4j-bom/2.24.3/log4j-bom-2.24.3.pom
|
||||
Downloading from central: https://repo.maven.apache.org/maven2/io/micrometer/micrometer-bom/1.15.11/micrometer-bom-1.15.11.pom
|
||||
Downloading from central: https://repo.maven.apache.org/maven2/io/netty/netty-bom/4.1.132.Final/netty-bom-4.1.132.Final.pom
|
||||
Downloading from central: https://repo.maven.apache.org/maven2/io/projectreactor/reactor-bom/2024.0.17/reactor-bom-2024.0.17.pom
|
||||
Downloading from central: https://repo.maven.apache.org/maven2/org/springframework/spring-framework-bom/6.2.18/spring-framework-bom-6.2.18.pom
|
||||
Downloading from central: https://repo.maven.apache.org/maven2/org/springframework/security/spring-security-bom/6.5.10/spring-security-bom-6.5.10.pom
|
||||
[ERROR] [ERROR] Some problems were encountered while processing the POMs:
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: com.fasterxml.jackson:jackson-bom:pom:2.21.2 (absent): Could not transfer artifact com.fasterxml.jackson:jackson-bom:pom:2.21.2 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2133, column 19
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.jetbrains.kotlin:kotlin-bom:pom:1.9.25 (absent): Could not transfer artifact org.jetbrains.kotlin:kotlin-bom:pom:1.9.25 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2144, column 19
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.apache.logging.log4j:log4j-bom:pom:2.24.3 (absent): Could not transfer artifact org.apache.logging.log4j:log4j-bom:pom:2.24.3 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2153, column 19
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: io.micrometer:micrometer-bom:pom:1.15.11 (absent): Could not transfer artifact io.micrometer:micrometer-bom:pom:1.15.11 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2160, column 19
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: io.netty:netty-bom:pom:4.1.132.Final (absent): Could not transfer artifact io.netty:netty-bom:pom:4.1.132.Final from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2169, column 19
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: io.projectreactor:reactor-bom:pom:2024.0.17 (absent): Could not transfer artifact io.projectreactor:reactor-bom:pom:2024.0.17 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2182, column 19
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.springframework:spring-framework-bom:pom:6.2.18 (absent): Could not transfer artifact org.springframework:spring-framework-bom:pom:6.2.18 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2195, column 19
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.springframework.security:spring-security-bom:pom:6.5.10 (absent): Could not transfer artifact org.springframework.security:spring-security-bom:pom:6.5.10 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2205, column 19
|
||||
@
|
||||
[ERROR] The build could not read 1 project -> [Help 1]
|
||||
[ERROR]
|
||||
[ERROR] The project com.tracexd:tracexd:1.0.0 (C:\Work\tracexd\pom.xml) has 8 errors
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: com.fasterxml.jackson:jackson-bom:pom:2.21.2 (absent): Could not transfer artifact com.fasterxml.jackson:jackson-bom:pom:2.21.2 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2133, column 19 -> [Help 2]
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.jetbrains.kotlin:kotlin-bom:pom:1.9.25 (absent): Could not transfer artifact org.jetbrains.kotlin:kotlin-bom:pom:1.9.25 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2144, column 19 -> [Help 2]
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.apache.logging.log4j:log4j-bom:pom:2.24.3 (absent): Could not transfer artifact org.apache.logging.log4j:log4j-bom:pom:2.24.3 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2153, column 19 -> [Help 2]
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: io.micrometer:micrometer-bom:pom:1.15.11 (absent): Could not transfer artifact io.micrometer:micrometer-bom:pom:1.15.11 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2160, column 19 -> [Help 2]
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: io.netty:netty-bom:pom:4.1.132.Final (absent): Could not transfer artifact io.netty:netty-bom:pom:4.1.132.Final from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2169, column 19 -> [Help 2]
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: io.projectreactor:reactor-bom:pom:2024.0.17 (absent): Could not transfer artifact io.projectreactor:reactor-bom:pom:2024.0.17 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2182, column 19 -> [Help 2]
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.springframework:spring-framework-bom:pom:6.2.18 (absent): Could not transfer artifact org.springframework:spring-framework-bom:pom:6.2.18 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2195, column 19 -> [Help 2]
|
||||
[ERROR] Non-resolvable import POM: The following artifacts could not be resolved: org.springframework.security:spring-security-bom:pom:6.5.10 (absent): Could not transfer artifact org.springframework.security:spring-security-bom:pom:6.5.10 from/to central (https://repo.maven.apache.org/maven2): Permission denied: getsockopt @ org.springframework.boot:spring-boot-dependencies:3.5.14, C:\Work\tracexd\spring-boot-dependencies-3.5.14.pom, line 2205, column 19 -> [Help 2]
|
||||
[ERROR]
|
||||
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
|
||||
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
|
||||
[ERROR]
|
||||
[ERROR] For more information about the errors and possible solutions, please read the following articles:
|
||||
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/ProjectBuildingException
|
||||
[ERROR] [Help 2] http://cwiki.apache.org/confluence/display/MAVEN/UnresolvableModelException
|
||||
68
pom.xml
Normal file
68
pom.xml
Normal file
@ -0,0 +1,68 @@
|
||||
<?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>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.5.14</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
<groupId>com.tracexd</groupId>
|
||||
<artifactId>tracexd</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<name>tracexd</name>
|
||||
<description>Daily events recording and querying system</description>
|
||||
<properties>
|
||||
<java.version>26</java.version>
|
||||
<mybatis-plus.version>3.5.12</mybatis-plus.version>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
|
||||
<version>${mybatis-plus.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-jsqlparser</artifactId>
|
||||
<version>${mybatis-plus.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
69
run.log
Normal file
69
run.log
Normal file
@ -0,0 +1,69 @@
|
||||
mvn : WARNING: A restricted method in java.lang.System has been called
|
||||
At line:2 char:37
|
||||
+ $env:SPRING_PROFILES_ACTIVE="test"; mvn spring-boot:run 2>&1 | Out-Fi ...
|
||||
+ ~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
+ CategoryInfo : NotSpecified: (WARNING: A rest...has been called:String) [], RemoteException
|
||||
+ FullyQualifiedErrorId : NativeCommandError
|
||||
|
||||
WARNING: java.lang.System::load has been called by org.fusesource.jansi.internal.JansiLoader in an unnamed module (file
|
||||
:/C:/Users/16332/.m2/wrapper/dists/apache-maven-3.9.9-bin/4nf9hui3q3djbarqar9g711ggc/apache-maven-3.9.9/lib/jansi-2.4.1
|
||||
.jar)
|
||||
WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
|
||||
WARNING: Restricted methods will be blocked in a future release unless native access is enabled
|
||||
|
||||
WARNING: A terminally deprecated method in sun.misc.Unsafe has been called
|
||||
WARNING: sun.misc.Unsafe::objectFieldOffset has been called by com.google.common.util.concurrent.AbstractFuture$UnsafeA
|
||||
tomicHelper (file:/C:/Users/16332/.m2/wrapper/dists/apache-maven-3.9.9-bin/4nf9hui3q3djbarqar9g711ggc/apache-maven-3.9.
|
||||
9/lib/guava-33.2.1-jre.jar)
|
||||
WARNING: Please consider reporting this to the maintainers of class com.google.common.util.concurrent.AbstractFuture$Un
|
||||
safeAtomicHelper
|
||||
WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release
|
||||
WARNING: Final field _cipher in class org.sonatype.plexus.components.sec.dispatcher.DefaultSecDispatcher has been mutat
|
||||
ed reflectively by class org.eclipse.sisu.bean.BeanPropertyField in unnamed module @780cb77 (file:/C:/Users/16332/.m2/w
|
||||
rapper/dists/apache-maven-3.9.9-bin/4nf9hui3q3djbarqar9g711ggc/apache-maven-3.9.9/lib/org.eclipse.sisu.inject-0.9.0.M3.
|
||||
jar)
|
||||
WARNING: Use --enable-final-field-mutation=ALL-UNNAMED to avoid a warning
|
||||
WARNING: Mutating final fields will be blocked in a future release unless final field mutation is enabled
|
||||
[INFO] Scanning for projects...
|
||||
[INFO]
|
||||
[INFO] ------------------------< com.tracexd:tracexd >-------------------------
|
||||
[INFO] Building tracexd 1.0.0
|
||||
[INFO] from pom.xml
|
||||
[INFO] --------------------------------[ jar ]---------------------------------
|
||||
[INFO]
|
||||
[INFO] >>> spring-boot:3.5.14:run (default-cli) > test-compile @ tracexd >>>
|
||||
[INFO]
|
||||
[INFO] --- resources:3.3.1:resources (default-resources) @ tracexd ---
|
||||
[INFO] Copying 2 resources from src\main\resources to target\classes
|
||||
[INFO] Copying 6 resources from src\main\resources to target\classes
|
||||
[INFO]
|
||||
[INFO] --- compiler:3.14.1:compile (default-compile) @ tracexd ---
|
||||
[INFO] Nothing to compile - all classes are up to date.
|
||||
[INFO]
|
||||
[INFO] --- resources:3.3.1:testResources (default-testResources) @ tracexd ---
|
||||
[INFO] skip non existing resourceDirectory C:\Work\tracexd\src\test\resources
|
||||
[INFO]
|
||||
[INFO] --- compiler:3.14.1:testCompile (default-testCompile) @ tracexd ---
|
||||
[INFO] Nothing to compile - all classes are up to date.
|
||||
[INFO]
|
||||
[INFO] <<< spring-boot:3.5.14:run (default-cli) < test-compile @ tracexd <<<
|
||||
[INFO]
|
||||
[INFO]
|
||||
[INFO] --- spring-boot:3.5.14:run (default-cli) @ tracexd ---
|
||||
[INFO] Attaching agents: []
|
||||
2026-06-09T14:04:19.923+08:00 INFO 10212 --- [ main] com.tracexd.TracexdApplication : Starting TracexdApplication using Java 26.0.1 with PID 10212 (C:\Work\tracexd\target\classes started by 16332 in C:\Work\tracexd)
|
||||
2026-06-09T14:04:19.924+08:00 DEBUG 10212 --- [ main] com.tracexd.TracexdApplication : Running with Spring Boot v3.5.14, Spring v6.2.18
|
||||
2026-06-09T14:04:19.924+08:00 INFO 10212 --- [ main] com.tracexd.TracexdApplication : The following 1 profile is active: "test"
|
||||
2026-06-09T14:04:20.498+08:00 INFO 10212 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
|
||||
2026-06-09T14:04:20.498+08:00 INFO 10212 --- [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.54]
|
||||
2026-06-09T14:04:20.525+08:00 INFO 10212 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
|
||||
Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
|
||||
Get /198.18.0.1 network interface
|
||||
Get network interface info: name:iftype53_32769 (Meta Tunnel)
|
||||
Initialization Sequence datacenterId:0 workerId:4
|
||||
2026-06-09T14:04:20.621+08:00 INFO 10212 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
|
||||
2026-06-09T14:04:20.717+08:00 INFO 10212 --- [ main] com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Added connection conn0: url=jdbc:h2:mem:tracexd user=SA
|
||||
2026-06-09T14:04:20.718+08:00 INFO 10212 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
|
||||
2026-06-09T14:04:21.123+08:00 INFO 10212 --- [ main] m.e.s.MybatisPlusApplicationContextAware : Register ApplicationContext instances org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@e383572
|
||||
2026-06-09T14:04:21.162+08:00 INFO 10212 --- [ main] com.tracexd.TracexdApplication : Started TracexdApplication in 1.492 seconds (process running for 1.661)
|
||||
Default admin user created (admin/admin123)
|
||||
41
src/main/java/com/tracexd/TracexdApplication.java
Normal file
41
src/main/java/com/tracexd/TracexdApplication.java
Normal file
@ -0,0 +1,41 @@
|
||||
package com.tracexd;
|
||||
|
||||
import com.tracexd.mapper.RecordMapper;
|
||||
import com.tracexd.model.entity.Record;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
|
||||
/**
|
||||
* 日常事项记录系统 - 启动类
|
||||
*/
|
||||
@SpringBootApplication
|
||||
@EnableAsync
|
||||
public class TracexdApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(TracexdApplication.class, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化默认管理员用户(首次启动时创建)
|
||||
*/
|
||||
@Bean
|
||||
public CommandLineRunner initAdminUser(PasswordEncoder passwordEncoder, JdbcTemplate jdbcTemplate) {
|
||||
return args -> {
|
||||
Long count = jdbcTemplate.queryForObject(
|
||||
"SELECT COUNT(*) FROM sys_user WHERE username = ?", Long.class, "admin");
|
||||
if (count == null || count == 0) {
|
||||
String hash = passwordEncoder.encode("admin123");
|
||||
jdbcTemplate.update(
|
||||
"INSERT INTO sys_user (username, password, display_name, enabled, created_at) VALUES (?, ?, ?, 1, NOW())",
|
||||
"admin", hash, "管理员");
|
||||
System.out.println("Default admin user created (admin/admin123)");
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
44
src/main/java/com/tracexd/common/Result.java
Normal file
44
src/main/java/com/tracexd/common/Result.java
Normal file
@ -0,0 +1,44 @@
|
||||
package com.tracexd.common;
|
||||
|
||||
/**
|
||||
* 统一API响应体
|
||||
*/
|
||||
public class Result<T> {
|
||||
|
||||
private int code;
|
||||
private String message;
|
||||
private T data;
|
||||
|
||||
private Result() {
|
||||
}
|
||||
|
||||
public static <T> Result<T> ok(T data) {
|
||||
Result<T> r = new Result<>();
|
||||
r.code = 200;
|
||||
r.message = "success";
|
||||
r.data = data;
|
||||
return r;
|
||||
}
|
||||
|
||||
public static <T> Result<T> ok() {
|
||||
return ok(null);
|
||||
}
|
||||
|
||||
public static <T> Result<T> error(int code, String message) {
|
||||
Result<T> r = new Result<>();
|
||||
r.code = code;
|
||||
r.message = message;
|
||||
return r;
|
||||
}
|
||||
|
||||
public static <T> Result<T> error(String message) {
|
||||
return error(500, message);
|
||||
}
|
||||
|
||||
public int getCode() { return code; }
|
||||
public void setCode(int code) { this.code = code; }
|
||||
public String getMessage() { return message; }
|
||||
public void setMessage(String message) { this.message = message; }
|
||||
public T getData() { return data; }
|
||||
public void setData(T data) { this.data = data; }
|
||||
}
|
||||
60
src/main/java/com/tracexd/common/VoiceTaskManager.java
Normal file
60
src/main/java/com/tracexd/common/VoiceTaskManager.java
Normal file
@ -0,0 +1,60 @@
|
||||
package com.tracexd.common;
|
||||
|
||||
import com.tracexd.model.dto.VoiceProcessResult;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 语音任务管理器 — 管理异步任务和 SSE 推送
|
||||
*/
|
||||
@Component
|
||||
public class VoiceTaskManager {
|
||||
|
||||
private final Map<String, CompletableFuture<VoiceProcessResult>> taskFutures = new ConcurrentHashMap<>();
|
||||
|
||||
private final Map<String, SseEmitter> taskEmitters = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 注册一个任务
|
||||
*
|
||||
* @param taskId 任务ID
|
||||
* @param future 异步处理结果
|
||||
*/
|
||||
public void registerTask(String taskId, CompletableFuture<VoiceProcessResult> future) {
|
||||
taskFutures.put(taskId, future);
|
||||
future.whenComplete((result, ex) -> {
|
||||
SseEmitter emitter = taskEmitters.remove(taskId);
|
||||
if (emitter != null) {
|
||||
try {
|
||||
if (ex != null) {
|
||||
emitter.send(SseEmitter.event().data(Result.error(ex.getMessage())));
|
||||
} else {
|
||||
emitter.send(SseEmitter.event().data(Result.ok(result)));
|
||||
}
|
||||
emitter.complete();
|
||||
} catch (Exception e) {
|
||||
emitter.completeWithError(e);
|
||||
}
|
||||
}
|
||||
taskFutures.remove(taskId);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 为任务创建 SSE 连接
|
||||
*
|
||||
* @param taskId 任务ID
|
||||
* @return SSE 发射器
|
||||
*/
|
||||
public SseEmitter createEmitter(String taskId) {
|
||||
SseEmitter emitter = new SseEmitter(120_000L);
|
||||
taskEmitters.put(taskId, emitter);
|
||||
emitter.onCompletion(() -> taskEmitters.remove(taskId));
|
||||
emitter.onTimeout(() -> taskEmitters.remove(taskId));
|
||||
return emitter;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
package com.tracexd.common.exception;
|
||||
|
||||
/**
|
||||
* 业务异常
|
||||
*/
|
||||
public class BusinessException extends RuntimeException {
|
||||
|
||||
private final int code;
|
||||
|
||||
public BusinessException(int code, String message) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public BusinessException(String message) {
|
||||
this(500, message);
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
package com.tracexd.common.exception;
|
||||
|
||||
import com.tracexd.common.Result;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
/**
|
||||
* 全局异常处理器
|
||||
*/
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
|
||||
|
||||
@ExceptionHandler(BusinessException.class)
|
||||
public Result<Void> handleBusinessException(BusinessException e) {
|
||||
log.warn("Business exception: {}", e.getMessage());
|
||||
return Result.error(e.getCode(), e.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
public Result<Void> handleException(Exception e) {
|
||||
log.error("Unexpected error", e);
|
||||
return Result.error("系统繁忙,请稍后重试");
|
||||
}
|
||||
}
|
||||
27
src/main/java/com/tracexd/config/AsyncConfig.java
Normal file
27
src/main/java/com/tracexd/config/AsyncConfig.java
Normal file
@ -0,0 +1,27 @@
|
||||
package com.tracexd.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
|
||||
/**
|
||||
* 异步任务线程池配置
|
||||
*/
|
||||
@Configuration
|
||||
public class AsyncConfig {
|
||||
|
||||
@Bean("voiceTaskExecutor")
|
||||
public Executor voiceTaskExecutor() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
executor.setCorePoolSize(4);
|
||||
executor.setMaxPoolSize(8);
|
||||
executor.setQueueCapacity(16);
|
||||
executor.setThreadNamePrefix("voice-task-");
|
||||
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
|
||||
executor.initialize();
|
||||
return executor;
|
||||
}
|
||||
}
|
||||
22
src/main/java/com/tracexd/config/MybatisPlusConfig.java
Normal file
22
src/main/java/com/tracexd/config/MybatisPlusConfig.java
Normal file
@ -0,0 +1,22 @@
|
||||
package com.tracexd.config;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* MyBatis-Plus 配置
|
||||
*/
|
||||
@Configuration
|
||||
@MapperScan("com.tracexd.mapper")
|
||||
public class MybatisPlusConfig {
|
||||
|
||||
@Bean
|
||||
public MybatisPlusInterceptor mybatisPlusInterceptor() {
|
||||
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
|
||||
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
|
||||
return interceptor;
|
||||
}
|
||||
}
|
||||
17
src/main/java/com/tracexd/config/RestClientConfig.java
Normal file
17
src/main/java/com/tracexd/config/RestClientConfig.java
Normal file
@ -0,0 +1,17 @@
|
||||
package com.tracexd.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.client.RestClient;
|
||||
|
||||
/**
|
||||
* HTTP 客户端配置
|
||||
*/
|
||||
@Configuration
|
||||
public class RestClientConfig {
|
||||
|
||||
@Bean
|
||||
public RestClient restClient() {
|
||||
return RestClient.builder().build();
|
||||
}
|
||||
}
|
||||
88
src/main/java/com/tracexd/config/SecurityConfig.java
Normal file
88
src/main/java/com/tracexd/config/SecurityConfig.java
Normal file
@ -0,0 +1,88 @@
|
||||
package com.tracexd.config;
|
||||
|
||||
import com.tracexd.mapper.SysUserMapper;
|
||||
import com.tracexd.model.entity.SysUser;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
|
||||
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
/**
|
||||
* Spring Security 安全配置
|
||||
*/
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class SecurityConfig {
|
||||
|
||||
private final DataSource dataSource;
|
||||
private final SysUserMapper sysUserMapper;
|
||||
|
||||
public SecurityConfig(DataSource dataSource, SysUserMapper sysUserMapper) {
|
||||
this.dataSource = dataSource;
|
||||
this.sysUserMapper = sysUserMapper;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.csrf(csrf -> csrf.disable())
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
.requestMatchers("/login", "/css/**", "/js/**", "/.well-known/**", "/error").permitAll()
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.formLogin(form -> form
|
||||
.loginPage("/login")
|
||||
.defaultSuccessUrl("/", true)
|
||||
.permitAll()
|
||||
)
|
||||
.logout(logout -> logout
|
||||
.logoutSuccessUrl("/login?logout")
|
||||
.permitAll()
|
||||
)
|
||||
.rememberMe(remember -> remember
|
||||
.tokenValiditySeconds(30 * 24 * 60 * 60)
|
||||
.tokenRepository(persistentTokenRepository())
|
||||
)
|
||||
.sessionManagement(session -> session
|
||||
.maximumSessions(1)
|
||||
.maxSessionsPreventsLogin(false)
|
||||
);
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PersistentTokenRepository persistentTokenRepository() {
|
||||
JdbcTokenRepositoryImpl repo = new JdbcTokenRepositoryImpl();
|
||||
repo.setDataSource(dataSource);
|
||||
return repo;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public UserDetailsService userDetailsService() {
|
||||
return username -> {
|
||||
SysUser user = sysUserMapper.findByUsername(username);
|
||||
if (user == null || !user.getEnabled()) {
|
||||
throw new UsernameNotFoundException("User not found: " + username);
|
||||
}
|
||||
return User.withUsername(user.getUsername())
|
||||
.password(user.getPassword())
|
||||
.disabled(!user.getEnabled())
|
||||
.build();
|
||||
};
|
||||
}
|
||||
}
|
||||
40
src/main/java/com/tracexd/controller/PageController.java
Normal file
40
src/main/java/com/tracexd/controller/PageController.java
Normal file
@ -0,0 +1,40 @@
|
||||
package com.tracexd.controller;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
/**
|
||||
* 页面路由控制器
|
||||
*/
|
||||
@Controller
|
||||
public class PageController {
|
||||
|
||||
/**
|
||||
* 首页(麦克风录入/查询页面)
|
||||
*/
|
||||
@GetMapping("/")
|
||||
public String index(Model model, Authentication auth) {
|
||||
if (auth != null && auth.isAuthenticated()) {
|
||||
model.addAttribute("displayName", auth.getName());
|
||||
}
|
||||
return "index";
|
||||
}
|
||||
|
||||
/**
|
||||
* 浏览记录页面
|
||||
*/
|
||||
@GetMapping("/browse")
|
||||
public String browse() {
|
||||
return "browse";
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录页面
|
||||
*/
|
||||
@GetMapping("/login")
|
||||
public String login() {
|
||||
return "login";
|
||||
}
|
||||
}
|
||||
61
src/main/java/com/tracexd/controller/RecordController.java
Normal file
61
src/main/java/com/tracexd/controller/RecordController.java
Normal file
@ -0,0 +1,61 @@
|
||||
package com.tracexd.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.tracexd.common.Result;
|
||||
import com.tracexd.enums.DictTypeEnum;
|
||||
import com.tracexd.model.dto.RecordQueryDTO;
|
||||
import com.tracexd.model.entity.Record;
|
||||
import com.tracexd.service.record.DictTagService;
|
||||
import com.tracexd.service.record.RecordService;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 记录查询控制器(浏览页)
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/records")
|
||||
public class RecordController {
|
||||
|
||||
private final RecordService recordService;
|
||||
private final DictTagService dictTagService;
|
||||
|
||||
public RecordController(RecordService recordService, DictTagService dictTagService) {
|
||||
this.recordService = recordService;
|
||||
this.dictTagService = dictTagService;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public Result<Map<String, Object>> queryRecords(RecordQueryDTO queryDTO, Authentication auth) {
|
||||
Long userId = getUserIdFromAuth(auth);
|
||||
IPage<Record> page = recordService.queryPage(queryDTO, userId);
|
||||
BigDecimal totalAmount = recordService.sumAmount(queryDTO, userId);
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("records", page.getRecords());
|
||||
result.put("total", page.getTotal());
|
||||
result.put("page", page.getCurrent());
|
||||
result.put("size", page.getSize());
|
||||
result.put("totalAmount", totalAmount);
|
||||
return Result.ok(result);
|
||||
}
|
||||
|
||||
@GetMapping("/filters")
|
||||
public Result<Map<String, List<String>>> getFilters(Authentication auth) {
|
||||
Long userId = getUserIdFromAuth(auth);
|
||||
Map<String, List<String>> filters = new HashMap<>();
|
||||
filters.put("persons", dictTagService.getTagsByType(userId, DictTypeEnum.PERSON.getCode()));
|
||||
filters.put("locations", dictTagService.getTagsByType(userId, DictTypeEnum.LOCATION.getCode()));
|
||||
filters.put("categories", dictTagService.getTagsByType(userId, DictTypeEnum.CATEGORY.getCode()));
|
||||
return Result.ok(filters);
|
||||
}
|
||||
|
||||
private Long getUserIdFromAuth(Authentication auth) {
|
||||
return 1L;
|
||||
}
|
||||
}
|
||||
76
src/main/java/com/tracexd/controller/VoiceController.java
Normal file
76
src/main/java/com/tracexd/controller/VoiceController.java
Normal file
@ -0,0 +1,76 @@
|
||||
package com.tracexd.controller;
|
||||
|
||||
import com.tracexd.common.Result;
|
||||
import com.tracexd.common.VoiceTaskManager;
|
||||
import com.tracexd.service.voice.VoiceProcessService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* 语音处理控制器
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/voice")
|
||||
public class VoiceController {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(VoiceController.class);
|
||||
|
||||
private final VoiceProcessService voiceProcessService;
|
||||
private final VoiceTaskManager voiceTaskManager;
|
||||
|
||||
public VoiceController(VoiceProcessService voiceProcessService, VoiceTaskManager voiceTaskManager) {
|
||||
this.voiceProcessService = voiceProcessService;
|
||||
this.voiceTaskManager = voiceTaskManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交语音进行识别和处理
|
||||
*/
|
||||
@PostMapping("/process")
|
||||
public Result<Map<String, String>> processVoice(
|
||||
@RequestParam("audio") MultipartFile audio,
|
||||
Authentication auth) {
|
||||
|
||||
String contentType = audio.getContentType();
|
||||
String mimeType = (contentType != null) ? contentType : "audio/wav";
|
||||
|
||||
log.info("Voice request from user: {}, file size: {}, mime: {}", auth.getName(), audio.getSize(), mimeType);
|
||||
|
||||
Long userId = getUserIdFromAuth(auth);
|
||||
|
||||
try {
|
||||
byte[] audioData = audio.getBytes();
|
||||
String taskId = UUID.randomUUID().toString();
|
||||
|
||||
CompletableFuture<?> future = voiceProcessService.processVoice(audioData, mimeType, userId);
|
||||
voiceTaskManager.registerTask(taskId, (CompletableFuture) future);
|
||||
|
||||
return Result.ok(Map.of("taskId", taskId));
|
||||
} catch (IOException e) {
|
||||
log.error("Failed to read audio", e);
|
||||
return Result.error("读取音频失败");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SSE 订阅处理结果
|
||||
*/
|
||||
@GetMapping(value = "/stream/{taskId}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||
public SseEmitter streamResult(@PathVariable String taskId) {
|
||||
return voiceTaskManager.createEmitter(taskId);
|
||||
}
|
||||
|
||||
private Long getUserIdFromAuth(Authentication auth) {
|
||||
return 1L;
|
||||
}
|
||||
}
|
||||
26
src/main/java/com/tracexd/enums/DictTypeEnum.java
Normal file
26
src/main/java/com/tracexd/enums/DictTypeEnum.java
Normal file
@ -0,0 +1,26 @@
|
||||
package com.tracexd.enums;
|
||||
|
||||
/**
|
||||
* 字典标签类型枚举
|
||||
*/
|
||||
public enum DictTypeEnum {
|
||||
|
||||
/** 人物 */
|
||||
PERSON("person"),
|
||||
|
||||
/** 地点 */
|
||||
LOCATION("location"),
|
||||
|
||||
/** 分类 */
|
||||
CATEGORY("category");
|
||||
|
||||
private final String code;
|
||||
|
||||
DictTypeEnum(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
}
|
||||
16
src/main/java/com/tracexd/enums/IntentType.java
Normal file
16
src/main/java/com/tracexd/enums/IntentType.java
Normal file
@ -0,0 +1,16 @@
|
||||
package com.tracexd.enums;
|
||||
|
||||
/**
|
||||
* AI大模型意图类型枚举
|
||||
*/
|
||||
public enum IntentType {
|
||||
|
||||
/** 录入事项 */
|
||||
RECORD,
|
||||
|
||||
/** 查询事项 */
|
||||
QUERY,
|
||||
|
||||
/** 其他对话 */
|
||||
OTHER
|
||||
}
|
||||
10
src/main/java/com/tracexd/mapper/DictTagMapper.java
Normal file
10
src/main/java/com/tracexd/mapper/DictTagMapper.java
Normal file
@ -0,0 +1,10 @@
|
||||
package com.tracexd.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.tracexd.model.entity.DictTag;
|
||||
|
||||
/**
|
||||
* 字典标签 Mapper
|
||||
*/
|
||||
public interface DictTagMapper extends BaseMapper<DictTag> {
|
||||
}
|
||||
10
src/main/java/com/tracexd/mapper/RecordMapper.java
Normal file
10
src/main/java/com/tracexd/mapper/RecordMapper.java
Normal file
@ -0,0 +1,10 @@
|
||||
package com.tracexd.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.tracexd.model.entity.Record;
|
||||
|
||||
/**
|
||||
* 事项记录 Mapper
|
||||
*/
|
||||
public interface RecordMapper extends BaseMapper<Record> {
|
||||
}
|
||||
17
src/main/java/com/tracexd/mapper/SysUserMapper.java
Normal file
17
src/main/java/com/tracexd/mapper/SysUserMapper.java
Normal file
@ -0,0 +1,17 @@
|
||||
package com.tracexd.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.tracexd.model.entity.SysUser;
|
||||
|
||||
/**
|
||||
* 系统用户 Mapper
|
||||
*/
|
||||
public interface SysUserMapper extends BaseMapper<SysUser> {
|
||||
|
||||
/** 根据用户名查找用户 */
|
||||
default SysUser findByUsername(String username) {
|
||||
return selectOne(new LambdaQueryWrapper<SysUser>()
|
||||
.eq(SysUser::getUsername, username));
|
||||
}
|
||||
}
|
||||
33
src/main/java/com/tracexd/model/dto/RecordQueryDTO.java
Normal file
33
src/main/java/com/tracexd/model/dto/RecordQueryDTO.java
Normal file
@ -0,0 +1,33 @@
|
||||
package com.tracexd.model.dto;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 记录查询条件 DTO
|
||||
*/
|
||||
public class RecordQueryDTO {
|
||||
|
||||
private List<String> persons;
|
||||
private List<String> locations;
|
||||
private List<String> categories;
|
||||
private LocalDate startDate;
|
||||
private LocalDate endDate;
|
||||
private int page = 1;
|
||||
private int size = 20;
|
||||
|
||||
public List<String> getPersons() { return persons; }
|
||||
public void setPersons(List<String> persons) { this.persons = persons; }
|
||||
public List<String> getLocations() { return locations; }
|
||||
public void setLocations(List<String> locations) { this.locations = locations; }
|
||||
public List<String> getCategories() { return categories; }
|
||||
public void setCategories(List<String> categories) { this.categories = categories; }
|
||||
public LocalDate getStartDate() { return startDate; }
|
||||
public void setStartDate(LocalDate startDate) { this.startDate = startDate; }
|
||||
public LocalDate getEndDate() { return endDate; }
|
||||
public void setEndDate(LocalDate endDate) { this.endDate = endDate; }
|
||||
public int getPage() { return page; }
|
||||
public void setPage(int page) { this.page = page; }
|
||||
public int getSize() { return size; }
|
||||
public void setSize(int size) { this.size = size; }
|
||||
}
|
||||
35
src/main/java/com/tracexd/model/dto/VoiceProcessResult.java
Normal file
35
src/main/java/com/tracexd/model/dto/VoiceProcessResult.java
Normal file
@ -0,0 +1,35 @@
|
||||
package com.tracexd.model.dto;
|
||||
|
||||
import com.tracexd.enums.IntentType;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 语音处理结果 DTO
|
||||
*/
|
||||
public class VoiceProcessResult {
|
||||
|
||||
private IntentType intent;
|
||||
private String asrText;
|
||||
private String reply;
|
||||
private String recordTip;
|
||||
private List<Map<String, Object>> queryData;
|
||||
private String sql;
|
||||
private boolean success;
|
||||
|
||||
public IntentType getIntent() { return intent; }
|
||||
public void setIntent(IntentType intent) { this.intent = intent; }
|
||||
public String getAsrText() { return asrText; }
|
||||
public void setAsrText(String asrText) { this.asrText = asrText; }
|
||||
public String getReply() { return reply; }
|
||||
public void setReply(String reply) { this.reply = reply; }
|
||||
public String getRecordTip() { return recordTip; }
|
||||
public void setRecordTip(String recordTip) { this.recordTip = recordTip; }
|
||||
public List<Map<String, Object>> getQueryData() { return queryData; }
|
||||
public void setQueryData(List<Map<String, Object>> queryData) { this.queryData = queryData; }
|
||||
public String getSql() { return sql; }
|
||||
public void setSql(String sql) { this.sql = sql; }
|
||||
public boolean isSuccess() { return success; }
|
||||
public void setSuccess(boolean success) { this.success = success; }
|
||||
}
|
||||
36
src/main/java/com/tracexd/model/entity/DictTag.java
Normal file
36
src/main/java/com/tracexd/model/entity/DictTag.java
Normal file
@ -0,0 +1,36 @@
|
||||
package com.tracexd.model.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 字典标签表实体(统一存储人物、地点、分类)
|
||||
*/
|
||||
@TableName("dict_tag")
|
||||
public class DictTag {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
private Long userId;
|
||||
|
||||
private String type;
|
||||
|
||||
private String name;
|
||||
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
public Long getId() { return id; }
|
||||
public void setId(Long id) { this.id = id; }
|
||||
public Long getUserId() { return userId; }
|
||||
public void setUserId(Long userId) { this.userId = userId; }
|
||||
public String getType() { return type; }
|
||||
public void setType(String type) { this.type = type; }
|
||||
public String getName() { return name; }
|
||||
public void setName(String name) { this.name = name; }
|
||||
public LocalDateTime getCreatedAt() { return createdAt; }
|
||||
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
|
||||
}
|
||||
53
src/main/java/com/tracexd/model/entity/Record.java
Normal file
53
src/main/java/com/tracexd/model/entity/Record.java
Normal file
@ -0,0 +1,53 @@
|
||||
package com.tracexd.model.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 日常事项记录表实体
|
||||
*/
|
||||
@TableName("record")
|
||||
public class Record {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
private Long userId;
|
||||
|
||||
private String person;
|
||||
|
||||
private LocalDateTime time;
|
||||
|
||||
private String location;
|
||||
|
||||
private String content;
|
||||
|
||||
private BigDecimal amount;
|
||||
|
||||
private String category;
|
||||
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
public Long getId() { return id; }
|
||||
public void setId(Long id) { this.id = id; }
|
||||
public Long getUserId() { return userId; }
|
||||
public void setUserId(Long userId) { this.userId = userId; }
|
||||
public String getPerson() { return person; }
|
||||
public void setPerson(String person) { this.person = person; }
|
||||
public LocalDateTime getTime() { return time; }
|
||||
public void setTime(LocalDateTime time) { this.time = time; }
|
||||
public String getLocation() { return location; }
|
||||
public void setLocation(String location) { this.location = location; }
|
||||
public String getContent() { return content; }
|
||||
public void setContent(String content) { this.content = content; }
|
||||
public BigDecimal getAmount() { return amount; }
|
||||
public void setAmount(BigDecimal amount) { this.amount = amount; }
|
||||
public String getCategory() { return category; }
|
||||
public void setCategory(String category) { this.category = category; }
|
||||
public LocalDateTime getCreatedAt() { return createdAt; }
|
||||
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
|
||||
}
|
||||
40
src/main/java/com/tracexd/model/entity/SysUser.java
Normal file
40
src/main/java/com/tracexd/model/entity/SysUser.java
Normal file
@ -0,0 +1,40 @@
|
||||
package com.tracexd.model.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 系统用户表实体
|
||||
*/
|
||||
@TableName("sys_user")
|
||||
public class SysUser {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
private String username;
|
||||
|
||||
private String password;
|
||||
|
||||
private String displayName;
|
||||
|
||||
private Boolean enabled;
|
||||
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
public Long getId() { return id; }
|
||||
public void setId(Long id) { this.id = id; }
|
||||
public String getUsername() { return username; }
|
||||
public void setUsername(String username) { this.username = username; }
|
||||
public String getPassword() { return password; }
|
||||
public void setPassword(String password) { this.password = password; }
|
||||
public String getDisplayName() { return displayName; }
|
||||
public void setDisplayName(String displayName) { this.displayName = displayName; }
|
||||
public Boolean getEnabled() { return enabled; }
|
||||
public void setEnabled(Boolean enabled) { this.enabled = enabled; }
|
||||
public LocalDateTime getCreatedAt() { return createdAt; }
|
||||
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
|
||||
}
|
||||
164
src/main/java/com/tracexd/service/ai/AiService.java
Normal file
164
src/main/java/com/tracexd/service/ai/AiService.java
Normal file
@ -0,0 +1,164 @@
|
||||
package com.tracexd.service.ai;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.tracexd.common.exception.BusinessException;
|
||||
import com.tracexd.enums.IntentType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.client.RestClient;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* AI 大模型服务 — 调用 Deepseek API
|
||||
*/
|
||||
@Service
|
||||
public class AiService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(AiService.class);
|
||||
|
||||
private static final String RECORD_SYSTEM_PROMPT = """
|
||||
你是一个日常事项管理AI。用户会输入语音转写后的文本,请分析用户意图。
|
||||
|
||||
情况1 - 录入事项:
|
||||
用户描述了"谁在什么地方花多少钱干了什么"这类日常消费或活动。
|
||||
请生成 INSERT INTO record 语句,字段:person(人物,默认"我")、time(事项发生时间,用用户提到的时间或当前时间)、location(地点)、content(事项描述)、amount(金额,没提到则0)、category(2~4字分类,如吃饭、剪头、加油、修车、购物、旅游、交通、话费、医疗、水电等)。
|
||||
返回JSON:{"intent":"record","sql":"INSERT SQL语句","reply":""}
|
||||
|
||||
情况2 - 查询事项:
|
||||
用户在询问历史记录,例如"我昨天做了什么"、"我一共花了多少钱"、"我上次吃面是什么时候"等。
|
||||
生成 SELECT 语句,只查 person, time, content, amount 四个字段。
|
||||
返回JSON:{"intent":"query","sql":"SELECT SQL语句","reply":""}
|
||||
|
||||
情况3 - 其他:
|
||||
用户说了录入或查询以外的内容。
|
||||
返回JSON:{"intent":"other","sql":"","reply":"友善的回复"}
|
||||
|
||||
只返回JSON,不要包含其他内容。当前时间:{currentTime}
|
||||
""";
|
||||
|
||||
private static final String SUMMARY_PROMPT = """
|
||||
用户的问题: {userText}
|
||||
|
||||
查询到以下数据:
|
||||
{queryResult}
|
||||
|
||||
请根据查询结果,用中文生成一段简洁易懂的回复,总结用户想知道的息。
|
||||
如果结果为空,请友好告知没有找到相关记录。
|
||||
""";
|
||||
|
||||
private final RestClient restClient;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
@Value("${tracexd.deepseek.api-key}")
|
||||
private String apiKey;
|
||||
|
||||
@Value("${tracexd.deepseek.base-url}")
|
||||
private String baseUrl;
|
||||
|
||||
@Value("${tracexd.deepseek.model}")
|
||||
private String model;
|
||||
|
||||
public AiService(RestClient restClient, ObjectMapper objectMapper) {
|
||||
this.restClient = restClient;
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析用户输入文本的意图,并生成相应 SQL 或回复
|
||||
*
|
||||
* @param userText ASR 识别后的文本
|
||||
* @return 包含意图、SQL、回复的分析结果
|
||||
*/
|
||||
public IntentResult analyzeIntent(String userText) {
|
||||
String systemPrompt = RECORD_SYSTEM_PROMPT.replace("{currentTime}", java.time.LocalDateTime.now().toString());
|
||||
|
||||
String responseContent = callDeepseek(systemPrompt, userText);
|
||||
return parseIntentResult(responseContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将查询结果发给 AI 生成总结
|
||||
*
|
||||
* @param userText 用户原始输入
|
||||
* @param queryResult 查询到的数据 JSON
|
||||
* @return AI 生成的总结回复
|
||||
*/
|
||||
public String summarizeQueryResult(String userText, String queryResult) {
|
||||
String prompt = SUMMARY_PROMPT
|
||||
.replace("{userText}", userText)
|
||||
.replace("{queryResult}", queryResult);
|
||||
|
||||
return callDeepseek("你是一个数据总结助手", prompt);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用方法:调用 Deepseek API
|
||||
*/
|
||||
private String callDeepseek(String systemPrompt, String userText) {
|
||||
Map<String, Object> requestBody = Map.of(
|
||||
"model", model,
|
||||
"messages", List.of(
|
||||
Map.of("role", "system", "content", systemPrompt),
|
||||
Map.of("role", "user", "content", userText)
|
||||
),
|
||||
"stream", false
|
||||
);
|
||||
|
||||
log.info("Calling Deepseek model: {}", model);
|
||||
|
||||
Map result = restClient.post()
|
||||
.uri(baseUrl + "/chat/completions")
|
||||
.header("Authorization", "Bearer " + apiKey)
|
||||
.header("Content-Type", "application/json")
|
||||
.body(requestBody)
|
||||
.retrieve()
|
||||
.body(Map.class);
|
||||
|
||||
if (result == null || !result.containsKey("choices")) {
|
||||
throw new BusinessException("AI 调用失败:无返回结果");
|
||||
}
|
||||
|
||||
List<Map<String, Object>> choices = (List<Map<String, Object>>) result.get("choices");
|
||||
if (choices == null || choices.isEmpty()) {
|
||||
throw new BusinessException("AI 调用失败:无有效结果");
|
||||
}
|
||||
|
||||
Map<String, Object> message = (Map<String, Object>) choices.get(0).get("message");
|
||||
return (String) message.get("content");
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 AI 返回的 JSON 结果
|
||||
*/
|
||||
private IntentResult parseIntentResult(String json) {
|
||||
try {
|
||||
JsonNode root = objectMapper.readTree(json);
|
||||
String intentStr = root.get("intent").asText();
|
||||
String sql = root.has("sql") ? root.get("sql").asText() : "";
|
||||
String reply = root.has("reply") ? root.get("reply").asText() : "";
|
||||
|
||||
IntentType intent = switch (intentStr) {
|
||||
case "record" -> IntentType.RECORD;
|
||||
case "query" -> IntentType.QUERY;
|
||||
default -> IntentType.OTHER;
|
||||
};
|
||||
|
||||
return new IntentResult(intent, sql, reply);
|
||||
} catch (JsonProcessingException e) {
|
||||
log.error("Failed to parse AI response: {}", json, e);
|
||||
throw new BusinessException("AI 返回格式异常");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 意图分析结果内部类
|
||||
*/
|
||||
public record IntentResult(IntentType intent, String sql, String reply) {
|
||||
}
|
||||
}
|
||||
85
src/main/java/com/tracexd/service/asr/AsrService.java
Normal file
85
src/main/java/com/tracexd/service/asr/AsrService.java
Normal file
@ -0,0 +1,85 @@
|
||||
package com.tracexd.service.asr;
|
||||
|
||||
import com.tracexd.common.exception.BusinessException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.client.RestClient;
|
||||
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* ASR 语音识别服务 — 调用小米 MIMO V2.5 ASR
|
||||
*/
|
||||
@Service
|
||||
public class AsrService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(AsrService.class);
|
||||
|
||||
private final RestClient restClient;
|
||||
|
||||
@Value("${tracexd.mimo.api-key}")
|
||||
private String apiKey;
|
||||
|
||||
@Value("${tracexd.mimo.base-url}")
|
||||
private String baseUrl;
|
||||
|
||||
@Value("${tracexd.mimo.model}")
|
||||
private String model;
|
||||
|
||||
public AsrService(RestClient restClient) {
|
||||
this.restClient = restClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对音频进行语音识别
|
||||
*
|
||||
* @param audioData 音频原始字节
|
||||
* @param mimeType 音频 MIME 类型(audio/wav 或 audio/mpeg)
|
||||
* @return 识别后的文本
|
||||
*/
|
||||
public String recognize(byte[] audioData, String mimeType) {
|
||||
String base64Audio = Base64.getEncoder().encodeToString(audioData);
|
||||
|
||||
Map<String, Object> requestBody = Map.of(
|
||||
"model", model,
|
||||
"messages", List.of(Map.of(
|
||||
"role", "user",
|
||||
"content", List.of(Map.of(
|
||||
"type", "input_audio",
|
||||
"input_audio", Map.of(
|
||||
"data", "data:" + mimeType + ";base64," + base64Audio
|
||||
)
|
||||
))
|
||||
)),
|
||||
"asr_options", Map.of("language", "zh")
|
||||
);
|
||||
|
||||
log.info("Calling MIMO ASR, audio size: {} bytes, mime: {}", audioData.length, mimeType);
|
||||
|
||||
Map result = restClient.post()
|
||||
.uri(baseUrl + "/chat/completions")
|
||||
.header("api-key", apiKey)
|
||||
.header("Content-Type", "application/json")
|
||||
.body(requestBody)
|
||||
.retrieve()
|
||||
.body(Map.class);
|
||||
|
||||
if (result == null || !result.containsKey("choices")) {
|
||||
throw new BusinessException("ASR 识别失败:无返回结果");
|
||||
}
|
||||
|
||||
List<Map<String, Object>> choices = (List<Map<String, Object>>) result.get("choices");
|
||||
if (choices == null || choices.isEmpty()) {
|
||||
throw new BusinessException("ASR 识别失败:无有效结果");
|
||||
}
|
||||
|
||||
Map<String, Object> message = (Map<String, Object>) choices.get(0).get("message");
|
||||
String text = (String) message.get("content");
|
||||
log.info("ASR result: {}", text);
|
||||
return text;
|
||||
}
|
||||
}
|
||||
56
src/main/java/com/tracexd/service/record/DictTagService.java
Normal file
56
src/main/java/com/tracexd/service/record/DictTagService.java
Normal file
@ -0,0 +1,56 @@
|
||||
package com.tracexd.service.record;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.tracexd.mapper.DictTagMapper;
|
||||
import com.tracexd.model.entity.DictTag;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 字典标签服务 — 管理人物/地点/分类的去重存储
|
||||
*/
|
||||
@Service
|
||||
public class DictTagService {
|
||||
|
||||
private final DictTagMapper dictTagMapper;
|
||||
|
||||
public DictTagService(DictTagMapper dictTagMapper) {
|
||||
this.dictTagMapper = dictTagMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保标签存在于字典中(不存在则插入)
|
||||
*/
|
||||
public void ensureTagExists(Long userId, String type, String name) {
|
||||
if (name == null || name.isBlank()) return;
|
||||
Long count = dictTagMapper.selectCount(
|
||||
new LambdaQueryWrapper<DictTag>()
|
||||
.eq(DictTag::getUserId, userId)
|
||||
.eq(DictTag::getType, type)
|
||||
.eq(DictTag::getName, name)
|
||||
);
|
||||
if (count == null || count == 0) {
|
||||
DictTag tag = new DictTag();
|
||||
tag.setUserId(userId);
|
||||
tag.setType(type);
|
||||
tag.setName(name);
|
||||
tag.setCreatedAt(LocalDateTime.now());
|
||||
dictTagMapper.insert(tag);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据类型获取某用户的标签列表
|
||||
*/
|
||||
public List<String> getTagsByType(Long userId, String type) {
|
||||
return dictTagMapper.selectList(
|
||||
new LambdaQueryWrapper<DictTag>()
|
||||
.eq(DictTag::getUserId, userId)
|
||||
.eq(DictTag::getType, type)
|
||||
.orderByAsc(DictTag::getName)
|
||||
).stream().map(DictTag::getName).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
81
src/main/java/com/tracexd/service/record/RecordService.java
Normal file
81
src/main/java/com/tracexd/service/record/RecordService.java
Normal file
@ -0,0 +1,81 @@
|
||||
package com.tracexd.service.record;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.tracexd.mapper.RecordMapper;
|
||||
import com.tracexd.model.dto.RecordQueryDTO;
|
||||
import com.tracexd.model.entity.Record;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 事项记录服务 — CRUD 和查询
|
||||
*/
|
||||
@Service
|
||||
public class RecordService {
|
||||
|
||||
private final RecordMapper recordMapper;
|
||||
|
||||
public RecordService(RecordMapper recordMapper) {
|
||||
this.recordMapper = recordMapper;
|
||||
}
|
||||
|
||||
public void insert(Record record) {
|
||||
recordMapper.insert(record);
|
||||
}
|
||||
|
||||
public List<Record> executeSelectSql(String sql) {
|
||||
return recordMapper.selectList(new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<Record>()
|
||||
.apply("1=1 AND " + extractWhereClause(sql)));
|
||||
}
|
||||
|
||||
public IPage<Record> queryPage(RecordQueryDTO queryDTO, Long userId) {
|
||||
Page<Record> page = new Page<>(queryDTO.getPage(), queryDTO.getSize());
|
||||
LambdaQueryWrapper<Record> wrapper = new LambdaQueryWrapper<Record>()
|
||||
.eq(Record::getUserId, userId);
|
||||
|
||||
if (queryDTO.getPersons() != null && !queryDTO.getPersons().isEmpty())
|
||||
wrapper.in(Record::getPerson, queryDTO.getPersons());
|
||||
if (queryDTO.getLocations() != null && !queryDTO.getLocations().isEmpty())
|
||||
wrapper.in(Record::getLocation, queryDTO.getLocations());
|
||||
if (queryDTO.getCategories() != null && !queryDTO.getCategories().isEmpty())
|
||||
wrapper.in(Record::getCategory, queryDTO.getCategories());
|
||||
if (queryDTO.getStartDate() != null)
|
||||
wrapper.ge(Record::getTime, queryDTO.getStartDate().atStartOfDay());
|
||||
if (queryDTO.getEndDate() != null)
|
||||
wrapper.le(Record::getTime, queryDTO.getEndDate().plusDays(1).atStartOfDay());
|
||||
|
||||
wrapper.orderByDesc(Record::getTime);
|
||||
return recordMapper.selectPage(page, wrapper);
|
||||
}
|
||||
|
||||
public BigDecimal sumAmount(RecordQueryDTO queryDTO, Long userId) {
|
||||
LambdaQueryWrapper<Record> wrapper = new LambdaQueryWrapper<Record>()
|
||||
.eq(Record::getUserId, userId);
|
||||
|
||||
if (queryDTO.getPersons() != null && !queryDTO.getPersons().isEmpty())
|
||||
wrapper.in(Record::getPerson, queryDTO.getPersons());
|
||||
if (queryDTO.getLocations() != null && !queryDTO.getLocations().isEmpty())
|
||||
wrapper.in(Record::getLocation, queryDTO.getLocations());
|
||||
if (queryDTO.getCategories() != null && !queryDTO.getCategories().isEmpty())
|
||||
wrapper.in(Record::getCategory, queryDTO.getCategories());
|
||||
if (queryDTO.getStartDate() != null)
|
||||
wrapper.ge(Record::getTime, queryDTO.getStartDate().atStartOfDay());
|
||||
if (queryDTO.getEndDate() != null)
|
||||
wrapper.le(Record::getTime, queryDTO.getEndDate().plusDays(1).atStartOfDay());
|
||||
|
||||
List<Record> records = recordMapper.selectList(wrapper);
|
||||
return records.stream()
|
||||
.map(r -> r.getAmount() != null ? r.getAmount() : BigDecimal.ZERO)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
}
|
||||
|
||||
private String extractWhereClause(String sql) {
|
||||
int whereIndex = sql.toUpperCase().indexOf("WHERE");
|
||||
if (whereIndex == -1) return "1=1";
|
||||
return sql.substring(whereIndex + 5).trim().replaceAll(";$", "");
|
||||
}
|
||||
}
|
||||
102
src/main/java/com/tracexd/service/sql/SqlValidator.java
Normal file
102
src/main/java/com/tracexd/service/sql/SqlValidator.java
Normal file
@ -0,0 +1,102 @@
|
||||
package com.tracexd.service.sql;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* SQL 语句合法性校验器
|
||||
*/
|
||||
@Component
|
||||
public class SqlValidator {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(SqlValidator.class);
|
||||
|
||||
private static final List<String> DANGEROUS_KEYWORDS = Arrays.asList(
|
||||
"DROP", "DELETE", "UPDATE", "ALTER", "TRUNCATE", "CREATE",
|
||||
"INSERT INTO", "REPLACE", "GRANT", "REVOKE", "EXEC", "EXECUTE",
|
||||
"CALL", "LOAD_FILE", "INTO OUTFILE", "INTO DUMPFILE"
|
||||
);
|
||||
|
||||
private static final Pattern INSERT_PATTERN = Pattern.compile(
|
||||
"^\\s*INSERT\\s+INTO\\s+record\\s*\\(.*\\)\\s*VALUES\\s*\\(.*\\)\\s*;?\\s*$",
|
||||
Pattern.CASE_INSENSITIVE | Pattern.DOTALL
|
||||
);
|
||||
|
||||
private static final Pattern SELECT_PATTERN = Pattern.compile(
|
||||
"^\\s*SELECT\\s+.*\\s+FROM\\s+record.*$",
|
||||
Pattern.CASE_INSENSITIVE | Pattern.DOTALL
|
||||
);
|
||||
|
||||
private static final int MAX_RETRY_COUNT = 5;
|
||||
|
||||
/**
|
||||
* 校验 INSERT SQL 的合法性
|
||||
*
|
||||
* @param sql 待校验的 SQL
|
||||
* @return 校验通过返回 true
|
||||
*/
|
||||
public boolean validateInsert(String sql) {
|
||||
if (sql == null || sql.isBlank()) {
|
||||
log.warn("SQL validation failed: SQL is empty");
|
||||
return false;
|
||||
}
|
||||
|
||||
String upperSql = sql.toUpperCase();
|
||||
|
||||
if (!INSERT_PATTERN.matcher(sql).matches()) {
|
||||
log.warn("SQL validation failed: not a valid INSERT INTO record: {}", sql);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (String keyword : DANGEROUS_KEYWORDS) {
|
||||
// Skip the expected INSERT INTO itself
|
||||
if ("INSERT INTO".equals(keyword)) continue;
|
||||
if (upperSql.contains(keyword)) {
|
||||
log.warn("SQL validation failed: contains dangerous keyword '{}': {}", keyword, sql);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验 SELECT SQL 的合法性
|
||||
*
|
||||
* @param sql 待校验的 SQL
|
||||
* @return 校验通过返回 true
|
||||
*/
|
||||
public boolean validateSelect(String sql) {
|
||||
if (sql == null || sql.isBlank()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String upperSql = sql.toUpperCase();
|
||||
|
||||
if (!SELECT_PATTERN.matcher(sql).matches()) {
|
||||
log.warn("SQL validation failed: not a valid SELECT FROM record: {}", sql);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (String keyword : DANGEROUS_KEYWORDS) {
|
||||
if (upperSql.contains(keyword)) {
|
||||
log.warn("SQL validation failed: contains dangerous keyword '{}': {}", keyword, sql);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最大重试次数
|
||||
*/
|
||||
public int getMaxRetryCount() {
|
||||
return MAX_RETRY_COUNT;
|
||||
}
|
||||
}
|
||||
238
src/main/java/com/tracexd/service/voice/VoiceProcessService.java
Normal file
238
src/main/java/com/tracexd/service/voice/VoiceProcessService.java
Normal file
@ -0,0 +1,238 @@
|
||||
package com.tracexd.service.voice;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.tracexd.common.exception.BusinessException;
|
||||
import com.tracexd.enums.IntentType;
|
||||
import com.tracexd.model.dto.VoiceProcessResult;
|
||||
import com.tracexd.model.entity.Record;
|
||||
import com.tracexd.service.ai.AiService;
|
||||
import com.tracexd.service.ai.AiService.IntentResult;
|
||||
import com.tracexd.service.asr.AsrService;
|
||||
import com.tracexd.service.record.DictTagService;
|
||||
import com.tracexd.service.record.RecordService;
|
||||
import com.tracexd.service.sql.SqlValidator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* 语音处理编排服务 — ASR → AI → SQL执行 / 查询汇总
|
||||
*/
|
||||
@Service
|
||||
public class VoiceProcessService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(VoiceProcessService.class);
|
||||
|
||||
private final AsrService asrService;
|
||||
private final AiService aiService;
|
||||
private final SqlValidator sqlValidator;
|
||||
private final RecordService recordService;
|
||||
private final DictTagService dictTagService;
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public VoiceProcessService(AsrService asrService, AiService aiService,
|
||||
SqlValidator sqlValidator, RecordService recordService,
|
||||
DictTagService dictTagService, JdbcTemplate jdbcTemplate,
|
||||
ObjectMapper objectMapper) {
|
||||
this.asrService = asrService;
|
||||
this.aiService = aiService;
|
||||
this.sqlValidator = sqlValidator;
|
||||
this.recordService = recordService;
|
||||
this.dictTagService = dictTagService;
|
||||
this.jdbcTemplate = jdbcTemplate;
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步处理语音录入/查询
|
||||
*/
|
||||
@Async("voiceTaskExecutor")
|
||||
public CompletableFuture<VoiceProcessResult> processVoice(byte[] audioData, String mimeType, Long userId) {
|
||||
try {
|
||||
String asrText = asrService.recognize(audioData, mimeType);
|
||||
|
||||
// ASR no content -> skip LLM
|
||||
if (asrText == null || asrText.isBlank()) {
|
||||
log.warn("ASR returned empty result, skipping LLM call");
|
||||
VoiceProcessResult result = new VoiceProcessResult();
|
||||
result.setSuccess(false);
|
||||
result.setReply("未识别到语音内容");
|
||||
return CompletableFuture.completedFuture(result);
|
||||
}
|
||||
return CompletableFuture.completedFuture(processText(asrText, userId));
|
||||
} catch (Exception e) {
|
||||
log.error("Voice process failed", e);
|
||||
VoiceProcessResult result = new VoiceProcessResult();
|
||||
result.setSuccess(false);
|
||||
result.setReply("处理失败:" + e.getMessage());
|
||||
return CompletableFuture.completedFuture(result);
|
||||
}
|
||||
}
|
||||
|
||||
public VoiceProcessResult processText(String userText, Long userId) {
|
||||
log.info("Processing text: {}", userText);
|
||||
|
||||
IntentResult intentResult = aiService.analyzeIntent(userText);
|
||||
VoiceProcessResult result = new VoiceProcessResult();
|
||||
result.setAsrText(userText);
|
||||
result.setIntent(intentResult.intent());
|
||||
result.setSql(intentResult.sql());
|
||||
|
||||
switch (intentResult.intent()) {
|
||||
case RECORD -> handleRecord(result, intentResult.sql(), userId);
|
||||
case QUERY -> handleQuery(result, intentResult.sql(), userText);
|
||||
case OTHER -> result.setReply(intentResult.reply());
|
||||
}
|
||||
|
||||
result.setSuccess(true);
|
||||
return result;
|
||||
}
|
||||
|
||||
private void handleRecord(VoiceProcessResult result, String sql, Long userId) {
|
||||
for (int i = 0; i < sqlValidator.getMaxRetryCount(); i++) {
|
||||
if (!sqlValidator.validateInsert(sql)) {
|
||||
log.warn("Insert SQL invalid, retry {}/{}", i + 1, sqlValidator.getMaxRetryCount());
|
||||
IntentResult retry = aiService.analyzeIntent("请重新生成合法的INSERT SQL语句:" + result.getAsrText());
|
||||
sql = retry.sql();
|
||||
result.setSql(sql);
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
String modifiedSql = injectUserId(sql, userId);
|
||||
jdbcTemplate.update(modifiedSql);
|
||||
syncDictTagsFromSql(sql, userId);
|
||||
String category = extractCategoryFromSql(sql);
|
||||
result.setRecordTip("已录入" + category + "事项");
|
||||
log.info("Record inserted successfully: {}", sql);
|
||||
return;
|
||||
} catch (Exception e) {
|
||||
log.warn("Insert SQL execution failed, retry {}/{}: {}", i + 1, sqlValidator.getMaxRetryCount(), e.getMessage());
|
||||
IntentResult retry = aiService.analyzeIntent("请重新生成合法的INSERT SQL语句,之前错误:" + e.getMessage() + ",原文:" + result.getAsrText());
|
||||
sql = retry.sql();
|
||||
result.setSql(sql);
|
||||
}
|
||||
}
|
||||
result.setSuccess(false);
|
||||
result.setReply("录入失败:SQL多次生成仍不合法");
|
||||
}
|
||||
|
||||
private void handleQuery(VoiceProcessResult result, String sql, String userText) {
|
||||
if (!sqlValidator.validateSelect(sql)) {
|
||||
result.setSuccess(false);
|
||||
result.setReply("查询语句不合法");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
List<Map<String, Object>> queryData = jdbcTemplate.queryForList(sql);
|
||||
result.setQueryData(queryData);
|
||||
String queryResultJson;
|
||||
try {
|
||||
queryResultJson = objectMapper.writeValueAsString(queryData);
|
||||
} catch (JsonProcessingException e) {
|
||||
queryResultJson = queryData.toString();
|
||||
}
|
||||
String summary = aiService.summarizeQueryResult(userText, queryResultJson);
|
||||
result.setReply(summary);
|
||||
log.info("Query executed, {} rows returned", queryData.size());
|
||||
} catch (Exception e) {
|
||||
log.error("Query execution failed", e);
|
||||
result.setSuccess(false);
|
||||
result.setReply("查询执行失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void syncDictTagsFromSql(String sql, Long userId) {
|
||||
try {
|
||||
String upperSql = sql.toUpperCase();
|
||||
int valuesIdx = upperSql.indexOf("VALUES");
|
||||
if (valuesIdx == -1) return;
|
||||
String valuesPart = sql.substring(valuesIdx + 6).trim();
|
||||
valuesPart = valuesPart.replaceAll("^\\(", "").replaceAll("\\)\\s*;?$", "");
|
||||
String[] parts = splitValues(valuesPart);
|
||||
if (parts.length >= 6) {
|
||||
String person = cleanValue(parts[0]);
|
||||
String location = cleanValue(parts[2]);
|
||||
String category = cleanValue(parts[5]);
|
||||
if (person != null && !person.isBlank() && !"我".equals(person))
|
||||
dictTagService.ensureTagExists(userId, "person", person);
|
||||
if (location != null && !location.isBlank())
|
||||
dictTagService.ensureTagExists(userId, "location", location);
|
||||
if (category != null && !category.isBlank())
|
||||
dictTagService.ensureTagExists(userId, "category", category);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to sync dict tags from SQL: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private String injectUserId(String sql, Long userId) {
|
||||
String upper = sql.toUpperCase();
|
||||
int valuesIdx = upper.indexOf("VALUES");
|
||||
if (valuesIdx == -1) return sql;
|
||||
String beforeValues = sql.substring(0, valuesIdx);
|
||||
String afterValues = sql.substring(valuesIdx);
|
||||
int parenOpen = beforeValues.indexOf('(');
|
||||
int parenClose = beforeValues.lastIndexOf(')');
|
||||
if (parenOpen == -1 || parenClose == -1) return sql;
|
||||
String columns = beforeValues.substring(parenOpen + 1, parenClose).trim();
|
||||
String modifiedColumns = columns + ", user_id";
|
||||
int valParenOpen = afterValues.indexOf('(');
|
||||
int valParenClose = afterValues.lastIndexOf(')');
|
||||
if (valParenOpen == -1 || valParenClose == -1) return sql;
|
||||
String values = afterValues.substring(valParenOpen + 1, valParenClose).trim();
|
||||
String modifiedValues = values + ", " + userId;
|
||||
return beforeValues.substring(0, parenOpen + 1) + modifiedColumns + ")" +
|
||||
afterValues.substring(0, valParenOpen + 1) + modifiedValues + ")" +
|
||||
afterValues.substring(valParenClose + 1);
|
||||
}
|
||||
|
||||
private String extractCategoryFromSql(String sql) {
|
||||
try {
|
||||
int valuesIdx = sql.toUpperCase().indexOf("VALUES");
|
||||
if (valuesIdx == -1) return "事项";
|
||||
String valuesPart = sql.substring(valuesIdx + 6).trim();
|
||||
valuesPart = valuesPart.replaceAll("^\\(", "").replaceAll("\\)\\s*;?$", "");
|
||||
String[] parts = splitValues(valuesPart);
|
||||
if (parts.length >= 6) return cleanValue(parts[5]);
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to extract category", e);
|
||||
}
|
||||
return "事项";
|
||||
}
|
||||
|
||||
private String[] splitValues(String valuesPart) {
|
||||
List<String> result = new java.util.ArrayList<>();
|
||||
boolean inQuote = false;
|
||||
char quoteChar = '\'';
|
||||
StringBuilder current = new StringBuilder();
|
||||
for (char c : valuesPart.toCharArray()) {
|
||||
if (inQuote) {
|
||||
current.append(c);
|
||||
if (c == quoteChar) inQuote = false;
|
||||
} else if (c == '\'' || c == '"') {
|
||||
inQuote = true;
|
||||
quoteChar = c;
|
||||
current.append(c);
|
||||
} else if (c == ',') {
|
||||
result.add(current.toString());
|
||||
current = new StringBuilder();
|
||||
} else {
|
||||
current.append(c);
|
||||
}
|
||||
}
|
||||
result.add(current.toString());
|
||||
return result.toArray(String[]::new);
|
||||
}
|
||||
|
||||
private String cleanValue(String value) {
|
||||
return value.trim().replaceAll("^['\"]", "").replaceAll("['\"]$", "").trim();
|
||||
}
|
||||
}
|
||||
18
src/main/resources/application-test.yml
Normal file
18
src/main/resources/application-test.yml
Normal file
@ -0,0 +1,18 @@
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:h2:mem:tracexd;DB_CLOSE_DELAY=-1;MODE=MYSQL
|
||||
username: sa
|
||||
password:
|
||||
driver-class-name: org.h2.Driver
|
||||
sql:
|
||||
init:
|
||||
mode: always
|
||||
schema-locations: classpath:init-schema.sql
|
||||
|
||||
mybatis-plus:
|
||||
configuration:
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
|
||||
logging:
|
||||
level:
|
||||
com.tracexd: DEBUG
|
||||
59
src/main/resources/application.yml
Normal file
59
src/main/resources/application.yml
Normal file
@ -0,0 +1,59 @@
|
||||
server:
|
||||
port: 8081
|
||||
servlet:
|
||||
session:
|
||||
timeout: 30d
|
||||
|
||||
spring:
|
||||
main:
|
||||
banner-mode: off
|
||||
datasource:
|
||||
url: jdbc:mysql://192.168.2.5:3306/tracexd?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
|
||||
username: root
|
||||
password: root
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
thymeleaf:
|
||||
prefix: classpath:/templates/
|
||||
suffix: .html
|
||||
mode: HTML
|
||||
cache: true
|
||||
servlet:
|
||||
multipart:
|
||||
max-file-size: 10MB
|
||||
security:
|
||||
user:
|
||||
name: admin
|
||||
password: admin123
|
||||
|
||||
mybatis-plus:
|
||||
global-config:
|
||||
banner: false
|
||||
configuration:
|
||||
log-impl: org.apache.ibatis.logging.nologging.NoLoggingImpl
|
||||
|
||||
logging:
|
||||
level:
|
||||
com.tracexd: INFO
|
||||
org.springframework: WARN
|
||||
|
||||
---
|
||||
spring:
|
||||
config:
|
||||
activate:
|
||||
on-profile: dev
|
||||
thymeleaf:
|
||||
cache: false
|
||||
mybatis-plus:
|
||||
configuration:
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
|
||||
---
|
||||
tracexd:
|
||||
deepseek:
|
||||
api-key: sk-732e5f5f1f07454492022401f0a2bf40
|
||||
base-url: https://api.deepseek.com
|
||||
model: deepseek-v4-pro
|
||||
mimo:
|
||||
api-key: sk-cqttr117tzllfolonxztu4qa5208liota2llfut458ixx2m1
|
||||
base-url: https://api.xiaomimimo.com/v1
|
||||
model: mimo-v2.5-asr
|
||||
37
src/main/resources/init-schema.sql
Normal file
37
src/main/resources/init-schema.sql
Normal file
@ -0,0 +1,37 @@
|
||||
CREATE TABLE IF NOT EXISTS record (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL,
|
||||
person VARCHAR(50) NOT NULL DEFAULT '我',
|
||||
time DATETIME NOT NULL,
|
||||
location VARCHAR(200),
|
||||
content VARCHAR(1000),
|
||||
amount DECIMAL(10,2) NOT NULL DEFAULT 0.00,
|
||||
category VARCHAR(20),
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS dict_tag (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL,
|
||||
type VARCHAR(20) NOT NULL,
|
||||
name VARCHAR(200) NOT NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY uk_user_type_name (user_id, type, name)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS sys_user (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
username VARCHAR(50) NOT NULL,
|
||||
password VARCHAR(255) NOT NULL,
|
||||
display_name VARCHAR(50) NOT NULL DEFAULT '',
|
||||
enabled TINYINT(1) NOT NULL DEFAULT 1,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY uk_username (username)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS persistent_logins (
|
||||
username VARCHAR(64) NOT NULL,
|
||||
series VARCHAR(64) PRIMARY KEY,
|
||||
token VARCHAR(64) NOT NULL,
|
||||
last_used TIMESTAMP NOT NULL
|
||||
);
|
||||
@ -0,0 +1 @@
|
||||
{}
|
||||
119
src/main/resources/static/css/style.css
Normal file
119
src/main/resources/static/css/style.css
Normal file
@ -0,0 +1,119 @@
|
||||
/* ===== Reset & Base ===== */
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; background: #f5f5f7; color: #1d1d1f; min-height: 100vh; }
|
||||
a { color: #007aff; text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
.app-header { display: flex; align-items: center; justify-content: space-between; padding: 12px 20px; background: #fff; border-bottom: 1px solid #e5e5e7; position: sticky; top: 0; z-index: 100; }
|
||||
.header-title { font-size: 18px; font-weight: 600; }
|
||||
.header-link { font-size: 14px; color: #007aff; }
|
||||
.header-nav { display: flex; align-items: center; gap: 12px; }
|
||||
.header-logout { font-size: 14px; color: #999; cursor: pointer; }
|
||||
.header-logout:hover { color: #ff3b30; }
|
||||
.main-content { max-width: 960px; margin: 0 auto; padding: 16px; min-height: calc(100vh - 52px); padding-bottom: 100px; }
|
||||
.toast { position: fixed; top: 60px; left: 50%; transform: translateX(-50%); padding: 10px 24px; border-radius: 8px; font-size: 14px; z-index: 999; transition: opacity 0.3s; text-align: center; max-width: 90%; white-space: nowrap; }
|
||||
.toast.hidden { opacity: 0; pointer-events: none; }
|
||||
.toast.show { opacity: 1; }
|
||||
.toast-success { background: #34c759; color: #fff; }
|
||||
.toast-error { background: #ff3b30; color: #fff; }
|
||||
.toast-info { background: #007aff; color: #fff; }
|
||||
.mic-btn { position: fixed; bottom: 40px; left: 50%; margin-left: -40px; width: 80px; height: 80px; border-radius: 50%; background: #007aff; border: none; cursor: pointer; box-shadow: 0 4px 16px rgba(0,122,255,0.4); display: flex; align-items: center; justify-content: center; transition: transform 0.15s, box-shadow 0.15s, background 0.15s; z-index: 200; touch-action: none; }
|
||||
.mic-btn:hover { transform: scale(1.05); box-shadow: 0 6px 20px rgba(0,122,255,0.5); }
|
||||
.mic-btn:active, .mic-btn.recording { transform: scale(0.95); background: #ff3b30; box-shadow: 0 4px 16px rgba(255,59,48,0.4); }
|
||||
.mic-icon { width: 38px; height: 38px; color: #fff; }
|
||||
.result-area { margin-bottom: 16px; }
|
||||
.query-result-card { background: #fff; border-radius: 12px; padding: 16px; box-shadow: 0 2px 8px rgba(0,0,0,0.06); border: 1px solid #e5e5e7; }
|
||||
.query-table { width: 100%; border-collapse: collapse; margin-bottom: 12px; }
|
||||
.query-table th, .query-table td { padding: 8px 12px; text-align: left; font-size: 13px; border-bottom: 1px solid #f0f0f0; }
|
||||
.query-table th { font-weight: 600; color: #666; background: #fafafa; }
|
||||
.ai-summary { padding: 8px 0; font-size: 14px; line-height: 1.6; color: #333; }
|
||||
.section-title { font-size: 16px; font-weight: 600; margin-bottom: 12px; color: #333; }
|
||||
.empty-hint { color: #999; font-size: 14px; text-align: center; padding: 32px 0; }
|
||||
.recent-list { display: flex; flex-direction: column; gap: 8px; }
|
||||
.record-item { display: flex; align-items: center; gap: 12px; background: #fff; padding: 12px 16px; border-radius: 10px; box-shadow: 0 1px 4px rgba(0,0,0,0.04); border: 1px solid #e5e5e7; font-size: 14px; }
|
||||
.rec-time { color: #666; min-width: 50px; font-size: 13px; white-space: nowrap; }
|
||||
.rec-person { color: #007aff; font-weight: 500; min-width: 36px; }
|
||||
.rec-content { flex: 1; color: #333; }
|
||||
.rec-amount { font-weight: 600; color: #333; min-width: 60px; text-align: right; white-space: nowrap; }
|
||||
.badge { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 12px; color: #fff; white-space: nowrap; }
|
||||
.badge-default { background: #666; }
|
||||
.card-default { background: transparent; }
|
||||
.card-c1 { background: rgba(255, 149, 0, 0.08); }
|
||||
.card-c2 { background: rgba(0, 122, 255, 0.08); }
|
||||
.card-c3 { background: rgba(255, 45, 85, 0.08); }
|
||||
.card-c4 { background: rgba(175, 82, 222, 0.08); }
|
||||
.card-c5 { background: rgba(52, 199, 89, 0.08); }
|
||||
.card-c6 { background: rgba(88, 86, 214, 0.08); }
|
||||
.card-c7 { background: rgba(255, 149, 0, 0.08); }
|
||||
.card-c8 { background: rgba(0, 122, 255, 0.08); }
|
||||
.card-c9 { background: rgba(255, 45, 85, 0.08); }
|
||||
.card-c10 { background: rgba(175, 82, 222, 0.08); }
|
||||
.card-c11 { background: rgba(52, 199, 89, 0.08); }
|
||||
.card-c12 { background: rgba(88, 86, 214, 0.08); }
|
||||
.badge-c1 { background: #ff9500; }
|
||||
.badge-c2 { background: #007aff; }
|
||||
.badge-c3 { background: #ff2d55; }
|
||||
.badge-c4 { background: #af52de; }
|
||||
.badge-c5 { background: #34c759; }
|
||||
.badge-c6 { background: #5856d6; }
|
||||
.badge-c7 { background: #ff9500; }
|
||||
.badge-c8 { background: #007aff; }
|
||||
.badge-c9 { background: #ff2d55; }
|
||||
.badge-c10 { background: #af52de; }
|
||||
.badge-c11 { background: #34c759; }
|
||||
.badge-c12 { background: #5856d6; }
|
||||
.login-body { display: flex; align-items: center; justify-content: center; min-height: 100vh; background: linear-gradient(135deg, #f5f5f7 0%, #e8e8ed 100%); }
|
||||
.login-container { background: #fff; padding: 40px; border-radius: 16px; box-shadow: 0 8px 32px rgba(0,0,0,0.08); width: 360px; max-width: 90%; }
|
||||
.login-title { text-align: center; font-size: 28px; font-weight: 700; margin-bottom: 4px; color: #1d1d1f; }
|
||||
.login-subtitle { text-align: center; font-size: 14px; color: #999; margin-bottom: 24px; }
|
||||
.login-error { background: #fff2f2; color: #ff3b30; padding: 8px 12px; border-radius: 6px; margin-bottom: 16px; font-size: 13px; }
|
||||
.login-info { background: #e8f4ff; color: #007aff; padding: 8px 12px; border-radius: 6px; margin-bottom: 16px; font-size: 13px; }
|
||||
.form-group { margin-bottom: 16px; }
|
||||
.form-group label { display: block; font-size: 13px; font-weight: 500; margin-bottom: 6px; color: #555; }
|
||||
.form-group input { width: 100%; padding: 10px 12px; border: 1px solid #d1d1d6; border-radius: 8px; font-size: 14px; outline: none; transition: border-color 0.2s; }
|
||||
.form-group input:focus { border-color: #007aff; box-shadow: 0 0 0 3px rgba(0,122,255,0.1); }
|
||||
.form-remember { margin-bottom: 20px; font-size: 13px; color: #666; }
|
||||
.form-remember input { margin-right: 6px; }
|
||||
.login-btn { width: 100%; padding: 12px; background: #007aff; color: #fff; border: none; border-radius: 8px; font-size: 16px; font-weight: 500; cursor: pointer; transition: background 0.2s; }
|
||||
.login-btn:hover { background: #0056cc; }
|
||||
.filter-section { background: #fff; border-radius: 12px; padding: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.06); border: 1px solid #e5e5e7; margin-bottom: 16px; }
|
||||
.filter-section.hidden { display: none; }
|
||||
.filter-group { margin-bottom: 16px; }
|
||||
.filter-group:last-of-type { margin-bottom: 12px; }
|
||||
.filter-label { display: block; font-size: 13px; font-weight: 600; color: #666; margin-bottom: 8px; }
|
||||
.filter-tags { display: flex; flex-wrap: wrap; gap: 8px; }
|
||||
.tag-btn { display: inline-block; padding: 6px 14px; border-radius: 6px; border: 1px solid #d1d1d6; background: #fff; font-size: 13px; cursor: pointer; transition: all 0.15s; user-select: none; color: #333; }
|
||||
.tag-btn:hover { border-color: #007aff; color: #007aff; }
|
||||
.tag-btn.active { background: #007aff; color: #fff; border-color: #007aff; }
|
||||
.filter-date-range { display: flex; align-items: center; gap: 8px; }
|
||||
.date-input { padding: 6px 12px; border: 1px solid #d1d1d6; border-radius: 6px; font-size: 13px; outline: none; }
|
||||
.date-input:focus { border-color: #007aff; }
|
||||
.date-sep { color: #999; }
|
||||
.filter-actions { display: flex; justify-content: space-between; align-items: center; padding-top: 8px; }
|
||||
.btn { padding: 8px 24px; border-radius: 8px; font-size: 14px; font-weight: 500; cursor: pointer; transition: all 0.15s; border: none; }
|
||||
.btn-primary { background: #007aff; color: #fff; }
|
||||
.btn-primary:hover { background: #0056cc; }
|
||||
.btn-secondary { background: #f5f5f7; color: #333; border: 1px solid #d1d1d6; }
|
||||
.btn-secondary:hover { background: #e8e8ed; }
|
||||
.btn-link { background: none; color: #007aff; padding: 4px 8px; font-size: 13px; }
|
||||
.btn-link:hover { text-decoration: underline; }
|
||||
.btn-page { padding: 6px 14px; font-size: 13px; background: #fff; border: 1px solid #d1d1d6; border-radius: 6px; color: #333; }
|
||||
.btn-page:disabled { opacity: 0.4; cursor: not-allowed; }
|
||||
.btn-page:hover:not(:disabled) { border-color: #007aff; color: #007aff; }
|
||||
.filter-summary { background: #fff; border-radius: 10px; padding: 12px 16px; box-shadow: 0 2px 8px rgba(0,0,0,0.06); border: 1px solid #e5e5e7; margin-bottom: 16px; font-size: 13px; color: #666; display: flex; align-items: center; gap: 12px; }
|
||||
.summary-bar { background: #e8f4ff; border-radius: 8px; padding: 10px 16px; margin-bottom: 12px; font-size: 14px; color: #007aff; }
|
||||
.summary-bar strong { font-size: 16px; }
|
||||
.table-wrapper { overflow-x: auto; }
|
||||
.data-table { width: 100%; border-collapse: collapse; background: #fff; border-radius: 10px; overflow: hidden; box-shadow: 0 1px 4px rgba(0,0,0,0.04); border: 1px solid #e5e5e7; }
|
||||
.data-table th, .data-table td { padding: 10px 14px; text-align: left; font-size: 13px; border-bottom: 1px solid #f0f0f0; }
|
||||
.data-table th { background: #fafafa; font-weight: 600; color: #666; white-space: nowrap; }
|
||||
.data-table tbody tr:hover { background: #f8f8fa; }
|
||||
.empty-cell { text-align: center; color: #999; padding: 32px 0 !important; }
|
||||
.col-time { width: 180px; white-space: nowrap; }
|
||||
.col-person { width: 80px; }
|
||||
.col-location { min-width: 120px; max-width: 200px; }
|
||||
.col-content { min-width: 160px; max-width: 400px; }
|
||||
.col-amount { width: 100px; text-align: right; }
|
||||
.col-amount-cell { text-align: right; font-weight: 600; }
|
||||
.col-category { width: 80px; }
|
||||
.pagination { display: flex; align-items: center; justify-content: center; gap: 16px; margin-top: 16px; font-size: 13px; color: #666; }
|
||||
@media (max-width: 640px) { .main-content { padding: 12px; padding-bottom: 100px; } .mic-btn { bottom: 24px; left: 50%; margin-left: -34px; width: 68px; height: 68px; } .mic-icon { width: 32px; height: 32px; } .data-table { font-size: 12px; } .data-table th, .data-table td { padding: 8px 10px; } .col-time { width: 140px; } .col-location { max-width: 120px; } .col-content { max-width: 200px; } }
|
||||
BIN
src/main/resources/static/favicon.ico
Normal file
BIN
src/main/resources/static/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
63
src/main/resources/static/js/app.js
Normal file
63
src/main/resources/static/js/app.js
Normal file
@ -0,0 +1,63 @@
|
||||
/**
|
||||
* TraceXD 前端通用工具函数
|
||||
*/
|
||||
(function(global) {
|
||||
|
||||
/**
|
||||
* 格式化时间显示
|
||||
* 当天 → HH:mm
|
||||
* 当年非当天 → M-d HH:mm
|
||||
* 非当年 → yy-M-d HH
|
||||
*
|
||||
* @param {string} isoString ISO 日期时间字符串
|
||||
* @returns {string} 格式化后的时间文本
|
||||
*/
|
||||
global.formatTime = function(isoString) {
|
||||
if (!isoString) return '';
|
||||
var d = new Date(isoString);
|
||||
var pad = function(n) { return String(n).padStart(2, '0'); };
|
||||
return (d.getMonth() + 1) + '-' + d.getDate() + ' ' +
|
||||
pad(d.getHours()) + ':' + pad(d.getMinutes());
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* ??????? badge CSS ??
|
||||
* ??????????????????????????
|
||||
*/
|
||||
global.badgeClass = function(category) {
|
||||
if (!category) return 'badge-default';
|
||||
var fixed = {
|
||||
'餐饮': 'badge-c1',
|
||||
'饮食': 'badge-c1',
|
||||
'交通': 'badge-c2',
|
||||
'出行': 'badge-c2',
|
||||
'购物': 'badge-c3',
|
||||
'娱乐': 'badge-c4',
|
||||
'医疗': 'badge-c5',
|
||||
'健康': 'badge-c5',
|
||||
'教育': 'badge-c6',
|
||||
'学习': 'badge-c6',
|
||||
'养老': 'badge-c7',
|
||||
'社交': 'badge-c8',
|
||||
'旅游': 'badge-c9',
|
||||
'居家': 'badge-c10'
|
||||
};
|
||||
if (fixed[category]) return fixed[category];
|
||||
// hash-based fallback
|
||||
var hash = 0;
|
||||
for (var i = 0; i < category.length; i++) {
|
||||
hash = ((hash << 5) - hash) + category.charCodeAt(i);
|
||||
hash |= 0;
|
||||
}
|
||||
var idx = (Math.abs(hash) % 12) + 1;
|
||||
return 'badge-c' + idx;
|
||||
};
|
||||
|
||||
global.logout = function() {
|
||||
fetch('/logout', { method: 'POST' }).then(function() {
|
||||
window.location.href = '/login?logout';
|
||||
});
|
||||
};
|
||||
|
||||
})(window);
|
||||
271
src/main/resources/templates/browse.html
Normal file
271
src/main/resources/templates/browse.html
Normal file
@ -0,0 +1,271 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>TraceXD - 浏览记录</title>
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<header class="app-header">
|
||||
<a href="/" class="header-link">首页</a>
|
||||
<span class="header-title">浏览记录</span>
|
||||
<a href="#" onclick="logout(); return false;" class="header-logout">退出</a>
|
||||
</header>
|
||||
|
||||
<main class="main-content">
|
||||
<!-- 筛选项区域 -->
|
||||
<div id="filter-section" class="filter-section">
|
||||
<div class="filter-group">
|
||||
<label class="filter-label">人物</label>
|
||||
<div id="filter-persons" class="filter-tags"></div>
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<label class="filter-label">地点</label>
|
||||
<div id="filter-locations" class="filter-tags"></div>
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<label class="filter-label">分类</label>
|
||||
<div id="filter-categories" class="filter-tags"></div>
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<label class="filter-label">时间</label>
|
||||
<div class="filter-date-range">
|
||||
<input type="date" id="filter-start-date" class="date-input">
|
||||
<span class="date-sep">~</span>
|
||||
<input type="date" id="filter-end-date" class="date-input">
|
||||
</div>
|
||||
</div>
|
||||
<div class="filter-actions">
|
||||
<button id="btn-reset" class="btn btn-secondary">重置</button>
|
||||
<button id="btn-query" class="btn btn-primary">查询</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 折叠后的筛选项摘要 -->
|
||||
<div id="filter-summary" class="filter-summary hidden">
|
||||
<span id="filter-summary-text"></span>
|
||||
<button id="btn-expand" class="btn btn-link">展开筛选项</button>
|
||||
</div>
|
||||
|
||||
<!-- 金额汇总 -->
|
||||
<div id="summary-bar" class="summary-bar hidden">
|
||||
金额汇总: <strong id="total-amount">¥0.00</strong>
|
||||
</div>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<div class="table-wrapper">
|
||||
<table class="data-table" id="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="col-time">时间</th>
|
||||
<th class="col-person">人物</th>
|
||||
<th class="col-location">地点</th>
|
||||
<th class="col-content">事项内容</th>
|
||||
<th class="col-amount">金额</th>
|
||||
<th class="col-category">分类</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="table-body">
|
||||
<tr><td colspan="6" class="empty-cell">请点击"查询"查看记录</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div id="pagination" class="pagination hidden">
|
||||
<button id="btn-prev" class="btn btn-page">< 上一页</button>
|
||||
<span id="page-info"></span>
|
||||
<button id="btn-next" class="btn btn-page">下一页 ></button>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="/js/app.js"></script>
|
||||
<script>
|
||||
(function() {
|
||||
var currentPage = 1;
|
||||
var pageSize = 20;
|
||||
var totalPages = 0;
|
||||
var filterCollapsed = false;
|
||||
|
||||
var selectedPersons = [];
|
||||
var selectedLocations = [];
|
||||
var selectedCategories = [];
|
||||
|
||||
function init() {
|
||||
loadFilters();
|
||||
document.getElementById('btn-query').addEventListener('click', function() {
|
||||
doQuery(1);
|
||||
});
|
||||
document.getElementById('btn-reset').addEventListener('click', function() {
|
||||
resetFilters();
|
||||
});
|
||||
document.getElementById('btn-expand').addEventListener('click', function() {
|
||||
toggleFilterSection(false);
|
||||
});
|
||||
document.getElementById('btn-prev').addEventListener('click', function() {
|
||||
if (currentPage > 1) doQuery(currentPage - 1);
|
||||
});
|
||||
document.getElementById('btn-next').addEventListener('click', function() {
|
||||
if (currentPage < totalPages) doQuery(currentPage + 1);
|
||||
});
|
||||
}
|
||||
|
||||
function loadFilters() {
|
||||
fetch('/api/records/filters')
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(resp) {
|
||||
if (resp.code !== 200) return;
|
||||
var data = resp.data;
|
||||
renderFilterTags('filter-persons', data.persons || [], 'person');
|
||||
renderFilterTags('filter-locations', data.locations || [], 'location');
|
||||
renderFilterTags('filter-categories', data.categories || [], 'category');
|
||||
});
|
||||
}
|
||||
|
||||
function renderFilterTags(containerId, items, type) {
|
||||
var container = document.getElementById(containerId);
|
||||
container.innerHTML = '';
|
||||
items.forEach(function(item) {
|
||||
var tag = document.createElement('span');
|
||||
tag.className = 'tag-btn';
|
||||
tag.textContent = item;
|
||||
tag.dataset.type = type;
|
||||
tag.dataset.value = item;
|
||||
tag.addEventListener('click', function() {
|
||||
this.classList.toggle('active');
|
||||
});
|
||||
container.appendChild(tag);
|
||||
});
|
||||
}
|
||||
|
||||
function getSelectedTags(type) {
|
||||
var tags = document.querySelectorAll('.tag-btn[data-type="' + type + '"].active');
|
||||
return Array.from(tags).map(function(t) { return t.dataset.value; });
|
||||
}
|
||||
|
||||
function getQueryParams() {
|
||||
selectedPersons = getSelectedTags('person');
|
||||
selectedLocations = getSelectedTags('location');
|
||||
selectedCategories = getSelectedTags('category');
|
||||
|
||||
var startDate = document.getElementById('filter-start-date').value;
|
||||
var endDate = document.getElementById('filter-end-date').value;
|
||||
|
||||
return {
|
||||
persons: selectedPersons,
|
||||
locations: selectedLocations,
|
||||
categories: selectedCategories,
|
||||
startDate: startDate,
|
||||
endDate: endDate
|
||||
};
|
||||
}
|
||||
|
||||
function buildFilterSummary(params) {
|
||||
var parts = [];
|
||||
if (params.persons.length) parts.push('人物(' + params.persons.join(',') + ')');
|
||||
if (params.locations.length) parts.push('地点(' + params.locations.join(',') + ')');
|
||||
if (params.categories.length) parts.push('分类(' + params.categories.join(',') + ')');
|
||||
if (params.startDate || params.endDate) {
|
||||
parts.push('时间(' + (params.startDate || '不限') + '~' + (params.endDate || '不限') + ')');
|
||||
}
|
||||
return parts.join(' | ') || '无条件';
|
||||
}
|
||||
|
||||
function toggleFilterSection(collapsed) {
|
||||
filterCollapsed = collapsed;
|
||||
document.getElementById('filter-section').classList.toggle('hidden', collapsed);
|
||||
document.getElementById('filter-summary').classList.toggle('hidden', !collapsed);
|
||||
}
|
||||
|
||||
function doQuery(page) {
|
||||
var params = getQueryParams();
|
||||
var queryStr = '?page=' + page + '&size=' + pageSize;
|
||||
if (params.persons.length) {
|
||||
params.persons.forEach(function(p) { queryStr += '&persons=' + encodeURIComponent(p); });
|
||||
}
|
||||
if (params.locations.length) {
|
||||
params.locations.forEach(function(l) { queryStr += '&locations=' + encodeURIComponent(l); });
|
||||
}
|
||||
if (params.categories.length) {
|
||||
params.categories.forEach(function(c) { queryStr += '&categories=' + encodeURIComponent(c); });
|
||||
}
|
||||
if (params.startDate) queryStr += '&startDate=' + params.startDate;
|
||||
if (params.endDate) queryStr += '&endDate=' + params.endDate;
|
||||
|
||||
fetch('/api/records' + queryStr)
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(resp) {
|
||||
if (resp.code !== 200) return;
|
||||
renderTable(resp.data);
|
||||
toggleFilterSection(true);
|
||||
var summary = document.getElementById('filter-summary-text');
|
||||
summary.textContent = '已选: ' + buildFilterSummary(params);
|
||||
});
|
||||
}
|
||||
|
||||
function renderTable(data) {
|
||||
if (!data) return;
|
||||
var records = data.records || [];
|
||||
var totalAmount = data.totalAmount || 0;
|
||||
var total = data.total || 0;
|
||||
currentPage = Number(data.page) || 1;
|
||||
totalPages = Math.ceil(total / pageSize);
|
||||
|
||||
// 更新金额汇总
|
||||
var summaryBar = document.getElementById('summary-bar');
|
||||
summaryBar.classList.remove('hidden');
|
||||
document.getElementById('total-amount').textContent = '¥' + Number(totalAmount).toFixed(2);
|
||||
|
||||
// 更新表格
|
||||
var tbody = document.getElementById('table-body');
|
||||
if (records.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="6" class="empty-cell">暂无数据</td></tr>';
|
||||
} else {
|
||||
var html = '';
|
||||
records.forEach(function(r) {
|
||||
html += '<tr>' +
|
||||
'<td>' + formatTime(r.time) + '</td>' +
|
||||
'<td>' + (r.person || '我') + '</td>' +
|
||||
'<td>' + (r.location || '-') + '</td>' +
|
||||
'<td>' + (r.content || '') + '</td>' +
|
||||
'<td class="col-amount-cell">¥' + (r.amount ? Number(r.amount).toFixed(2) : '0.00') + '</td>' +
|
||||
'<td><span class="badge badge ' + badgeClass(r.category) + '">' +
|
||||
(r.category || '-') + '</span></td>' +
|
||||
'</tr>';
|
||||
});
|
||||
tbody.innerHTML = html;
|
||||
}
|
||||
|
||||
// 更新分页
|
||||
var pagination = document.getElementById('pagination');
|
||||
if (totalPages > 1) {
|
||||
pagination.classList.remove('hidden');
|
||||
document.getElementById('page-info').textContent =
|
||||
'第 ' + currentPage + ' / ' + totalPages + ' 页(共 ' + total + ' 条)';
|
||||
document.getElementById('btn-prev').disabled = currentPage <= 1;
|
||||
document.getElementById('btn-next').disabled = currentPage >= totalPages;
|
||||
} else {
|
||||
pagination.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
function resetFilters() {
|
||||
document.querySelectorAll('.tag-btn').forEach(function(t) { t.classList.remove('active'); });
|
||||
document.getElementById('filter-start-date').value = '';
|
||||
document.getElementById('filter-end-date').value = '';
|
||||
document.getElementById('table-body').innerHTML =
|
||||
'<tr><td colspan="6" class="empty-cell">请点击"查询"查看记录</td></tr>';
|
||||
document.getElementById('summary-bar').classList.add('hidden');
|
||||
document.getElementById('pagination').classList.add('hidden');
|
||||
toggleFilterSection(false);
|
||||
}
|
||||
|
||||
init();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
329
src/main/resources/templates/index.html
Normal file
329
src/main/resources/templates/index.html
Normal file
@ -0,0 +1,329 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||
<title>TraceXD - 首页</title>
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<header class="app-header">
|
||||
<span class="header-title">TraceXD</span>
|
||||
<nav class="header-nav">
|
||||
<a href="/browse" class="header-link">浏览记录</a>
|
||||
<a href="#" onclick="logout(); return false;" class="header-logout">退出</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main class="main-content">
|
||||
<!-- 结果展示区 -->
|
||||
<div id="result-area" class="result-area"></div>
|
||||
|
||||
<!-- Toast 通知 -->
|
||||
<div id="toast" class="toast hidden"></div>
|
||||
|
||||
<!-- 最近事项 -->
|
||||
<div class="recent-section">
|
||||
<h3 class="section-title">最近事项</h3>
|
||||
<div id="recent-list" class="recent-list">
|
||||
<p class="empty-hint">暂无事项记录,点击右下角麦克风开始录入</p>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- 麦克风按钮 -->
|
||||
<button id="mic-btn" class="mic-btn">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="mic-icon">
|
||||
<path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/>
|
||||
<path d="M19 10v2a7 7 0 0 1-14 0v-2"/>
|
||||
<line x1="12" y1="19" x2="12" y2="23"/>
|
||||
<line x1="8" y1="23" x2="16" y2="23"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<script src="/js/app.js"></script>
|
||||
<script>
|
||||
(function() {
|
||||
const micBtn = document.getElementById('mic-btn');
|
||||
const resultArea = document.getElementById('result-area');
|
||||
const toast = document.getElementById('toast');
|
||||
const recentList = document.getElementById('recent-list');
|
||||
|
||||
let isRecording = false;
|
||||
let stream = null;
|
||||
// Web Audio API WAV recording variables
|
||||
let audioCtx = null;
|
||||
let mediaStreamSrc = null;
|
||||
let procNode = null;
|
||||
let pcmChunks = [];
|
||||
let recordedSampleRate = 48000;
|
||||
|
||||
// 时间格式化
|
||||
function formatTime(isoString) {
|
||||
if (!isoString) return '';
|
||||
const d = new Date(isoString);
|
||||
const pad = n => String(n).padStart(2, '0');
|
||||
return (d.getMonth() + 1) + '-' + d.getDate() + ' ' +
|
||||
pad(d.getHours()) + ':' + pad(d.getMinutes());
|
||||
}
|
||||
|
||||
// 显示 Toast
|
||||
function showToast(message, type) {
|
||||
toast.textContent = message;
|
||||
toast.className = 'toast toast-' + (type || 'success') + ' show';
|
||||
setTimeout(function() {
|
||||
toast.className = 'toast hidden';
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// 展示查询结果
|
||||
function showQueryResult(data) {
|
||||
var html = '<div class="query-result-card">';
|
||||
if (data.queryData && data.queryData.length > 0) {
|
||||
html += '<table class="query-table"><thead><tr>' +
|
||||
'<th>时间</th><th>人物</th><th>内容</th><th>金额</th>' +
|
||||
'</tr></thead><tbody>';
|
||||
data.queryData.forEach(function(row) {
|
||||
html += '<tr class="' + cardClass(row.category) + '"><td>' + formatTime(row.time) + '</td>' +
|
||||
'<td>' + (row.person || '') + '</td>' +
|
||||
'<td>' + (row.content || '') + '</td>' +
|
||||
'<td>' + (row.amount || '0') + '</td></tr>';
|
||||
});
|
||||
html += '</tbody></table>';
|
||||
}
|
||||
if (data.reply) {
|
||||
html += '<div class="ai-summary">' + data.reply + '</div>';
|
||||
}
|
||||
html += '</div>';
|
||||
resultArea.innerHTML = html;
|
||||
}
|
||||
|
||||
// 展示AI直接回复(其他意图)
|
||||
function showAiReply(text) {
|
||||
var html = '<div class="query-result-card"><div class="ai-summary">' +
|
||||
text + '</div></div>';
|
||||
resultArea.innerHTML = html;
|
||||
}
|
||||
|
||||
// 开始录音
|
||||
function startRecording() {
|
||||
pcmChunks = [];
|
||||
navigator.mediaDevices.getUserMedia({ audio: true })
|
||||
.then(function(mediaStream) {
|
||||
stream = mediaStream;
|
||||
|
||||
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
||||
if (audioCtx.state === 'suspended') {
|
||||
audioCtx.resume();
|
||||
}
|
||||
recordedSampleRate = audioCtx.sampleRate;
|
||||
|
||||
mediaStreamSrc = audioCtx.createMediaStreamSource(mediaStream);
|
||||
|
||||
procNode = audioCtx.createScriptProcessor(4096, 1, 1);
|
||||
procNode.onaudioprocess = function(event) {
|
||||
var channelData = event.inputBuffer.getChannelData(0);
|
||||
var buf = new Int16Array(channelData.length);
|
||||
for (var i = 0; i < channelData.length; i++) {
|
||||
var s = Math.max(-1, Math.min(1, channelData[i]));
|
||||
buf[i] = s < 0 ? s * 0x8000 : s * 0x7FFF;
|
||||
}
|
||||
pcmChunks.push(buf);
|
||||
};
|
||||
|
||||
mediaStreamSrc.connect(procNode);
|
||||
procNode.connect(audioCtx.destination);
|
||||
|
||||
isRecording = true;
|
||||
micBtn.classList.add('recording');
|
||||
})
|
||||
.catch(function(err) {
|
||||
showToast('无法访问麦克风 ' + err.message, 'error');
|
||||
});
|
||||
}
|
||||
|
||||
function stopRecording() {
|
||||
if (!isRecording) return;
|
||||
isRecording = false;
|
||||
micBtn.classList.remove('recording');
|
||||
|
||||
if (procNode) {
|
||||
procNode.disconnect();
|
||||
procNode = null;
|
||||
}
|
||||
if (mediaStreamSrc) {
|
||||
mediaStreamSrc.disconnect();
|
||||
mediaStreamSrc = null;
|
||||
}
|
||||
if (audioCtx) {
|
||||
audioCtx.close();
|
||||
audioCtx = null;
|
||||
}
|
||||
if (stream) {
|
||||
stream.getTracks().forEach(function(t) { t.stop(); });
|
||||
stream = null;
|
||||
}
|
||||
|
||||
if (pcmChunks.length === 0) return;
|
||||
|
||||
var totalLen = pcmChunks.reduce(function(sum, chunk) { return sum + chunk.length; }, 0);
|
||||
var allSamples = new Int16Array(totalLen);
|
||||
var offset = 0;
|
||||
pcmChunks.forEach(function(chunk) {
|
||||
allSamples.set(chunk, offset);
|
||||
offset += chunk.length;
|
||||
});
|
||||
|
||||
var wavBlob = encodeWav(allSamples, recordedSampleRate);
|
||||
sendAudio(wavBlob);
|
||||
}
|
||||
|
||||
function encodeWav(samples, sampleRate) {
|
||||
var numChannels = 1;
|
||||
var bitsPerSample = 16;
|
||||
var bytesPerSample = bitsPerSample / 8;
|
||||
var byteRate = sampleRate * numChannels * bytesPerSample;
|
||||
var blockAlign = numChannels * bytesPerSample;
|
||||
var dataSize = samples.length * bytesPerSample;
|
||||
|
||||
var buf = new ArrayBuffer(44 + dataSize);
|
||||
var dv = new DataView(buf);
|
||||
|
||||
function writeStr(offset, str) {
|
||||
for (var i = 0; i < str.length; i++) {
|
||||
dv.setUint8(offset + i, str.charCodeAt(i));
|
||||
}
|
||||
}
|
||||
|
||||
writeStr(0, 'RIFF');
|
||||
dv.setUint32(4, 36 + dataSize, true);
|
||||
writeStr(8, 'WAVE');
|
||||
writeStr(12, 'fmt ');
|
||||
dv.setUint32(16, 16, true);
|
||||
dv.setUint16(20, 1, true);
|
||||
dv.setUint16(22, numChannels, true);
|
||||
dv.setUint32(24, sampleRate, true);
|
||||
dv.setUint32(28, byteRate, true);
|
||||
dv.setUint16(32, blockAlign, true);
|
||||
dv.setUint16(34, bitsPerSample, true);
|
||||
writeStr(36, 'data');
|
||||
dv.setUint32(40, dataSize, true);
|
||||
|
||||
var ptr = 44;
|
||||
for (var i = 0; i < samples.length; i++) {
|
||||
dv.setInt16(ptr, samples[i], true);
|
||||
ptr += 2;
|
||||
}
|
||||
|
||||
return new Blob([buf], { type: 'audio/wav' });
|
||||
}
|
||||
|
||||
function cardClass(category) {
|
||||
if (!category) return 'card-default';
|
||||
var fixed = {'\u9910\u996e':'card-c1','\u996e\u98df':'card-c1','\u4ea4\u901a':'card-c2','\u51fa\u884c':'card-c2','\u8d2d\u7269':'card-c3','\u5a31\u4e50':'card-c4','\u533b\u7597':'card-c5','\u5065\u5eb7':'card-c5','\u6559\u80b2':'card-c6','\u5b66\u4e60':'card-c6'};
|
||||
if (fixed[category]) return fixed[category];
|
||||
var hash = 0;
|
||||
for (var i = 0; i < category.length; i++) {
|
||||
hash = ((hash << 5) - hash) + category.charCodeAt(i);
|
||||
hash |= 0;
|
||||
}
|
||||
return 'card-c' + ((Math.abs(hash) % 12) + 1);
|
||||
}
|
||||
|
||||
function sendAudio(blob) {
|
||||
showToast('处理中...', 'info');
|
||||
var formData = new FormData();
|
||||
formData.append('audio', blob, 'recording.wav');
|
||||
|
||||
fetch('/api/voice/process', { method: 'POST', body: formData })
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(resp) {
|
||||
if (resp.code !== 200) {
|
||||
showToast(resp.message, 'error');
|
||||
return;
|
||||
}
|
||||
var taskId = resp.data.taskId;
|
||||
var es = new EventSource('/api/voice/stream/' + taskId);
|
||||
es.onmessage = function(event) {
|
||||
var result = JSON.parse(event.data);
|
||||
es.close();
|
||||
handleVoiceResult(result);
|
||||
};
|
||||
es.onerror = function() {
|
||||
es.close();
|
||||
showToast('处理超时,请重试', 'error');
|
||||
};
|
||||
})
|
||||
.catch(function(err) {
|
||||
showToast('网络错误: ' + err.message, 'error');
|
||||
});
|
||||
}
|
||||
|
||||
function handleVoiceResult(result) {
|
||||
if (result.code !== 200) {
|
||||
showToast(result.message, 'error');
|
||||
return;
|
||||
}
|
||||
var data = result.data;
|
||||
if (data.intent === 'RECORD') {
|
||||
if (data.success && data.recordTip) {
|
||||
showToast(data.recordTip, 'success');
|
||||
loadRecentRecords();
|
||||
} else {
|
||||
showToast(data.reply || '录入失败', 'error');
|
||||
}
|
||||
} else if (data.intent === 'QUERY') {
|
||||
showQueryResult(data);
|
||||
showToast('查询完成', 'info');
|
||||
} else {
|
||||
showAiReply(data.reply);
|
||||
}
|
||||
}
|
||||
|
||||
// 加载最近事项
|
||||
function loadRecentRecords() {
|
||||
fetch('/api/records?page=1&size=5')
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(resp) {
|
||||
if (resp.code !== 200 || !resp.data) return;
|
||||
var records = resp.data.records || [];
|
||||
if (records.length === 0) {
|
||||
recentList.innerHTML = '<p class="empty-hint">暂无事项记录</p>';
|
||||
return;
|
||||
}
|
||||
var html = '';
|
||||
records.forEach(function(r) {
|
||||
html += '<div class="record-item ' + cardClass(r.category) + '">' +
|
||||
'<span class="rec-time">' + formatTime(r.time) + '</span>' +
|
||||
'<span class="rec-person">' + (r.person || '我') + '</span>' +
|
||||
'<span class="rec-content">' + (r.content || '') + '</span>' +
|
||||
'<span class="rec-amount">¥' + (r.amount || '0') + '</span>' +
|
||||
'</div>';
|
||||
});
|
||||
recentList.innerHTML = html;
|
||||
})
|
||||
.catch(function() {});
|
||||
}
|
||||
|
||||
// 事件绑定
|
||||
micBtn.addEventListener('mousedown', startRecording);
|
||||
micBtn.addEventListener('mouseup', stopRecording);
|
||||
micBtn.addEventListener('mouseleave', stopRecording);
|
||||
micBtn.addEventListener('touchstart', function(e) {
|
||||
e.preventDefault();
|
||||
startRecording();
|
||||
});
|
||||
micBtn.addEventListener('touchend', function(e) {
|
||||
e.preventDefault();
|
||||
stopRecording();
|
||||
});
|
||||
|
||||
// 初始化加载最近事项
|
||||
loadRecentRecords();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
38
src/main/resources/templates/login.html
Normal file
38
src/main/resources/templates/login.html
Normal file
@ -0,0 +1,38 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>登录 - TraceXD</title>
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
</head>
|
||||
<body class="login-body">
|
||||
<div class="login-container">
|
||||
<h1 class="login-title">TraceXD</h1>
|
||||
<p class="login-subtitle">日常事项记录系统</p>
|
||||
<form class="login-form" method="post" th:action="@{/login}">
|
||||
<div th:if="${param.error}" class="login-error">
|
||||
用户名或密码错误
|
||||
</div>
|
||||
<div th:if="${param.logout}" class="login-info">
|
||||
您已退出登录
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="username">用户名</label>
|
||||
<input type="text" id="username" name="username" required autofocus>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">密码</label>
|
||||
<input type="password" id="password" name="password" required>
|
||||
</div>
|
||||
<div class="form-remember">
|
||||
<label>
|
||||
<input type="checkbox" name="remember-me" checked> 记住我(30天免登录)
|
||||
</label>
|
||||
</div>
|
||||
<button type="submit" class="login-btn">登 录</button>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
15
src/test/java/com/tracexd/TracexdApplicationTests.java
Normal file
15
src/test/java/com/tracexd/TracexdApplicationTests.java
Normal file
@ -0,0 +1,15 @@
|
||||
package com.tracexd;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
/**
|
||||
* 应用启动测试
|
||||
*/
|
||||
@SpringBootTest
|
||||
class TracexdApplicationTests {
|
||||
|
||||
@Test
|
||||
void contextLoads() {
|
||||
}
|
||||
}
|
||||
12
stderr.log
Normal file
12
stderr.log
Normal file
@ -0,0 +1,12 @@
|
||||
WARNING: A restricted method in java.lang.System has been called
|
||||
WARNING: java.lang.System::load has been called by org.fusesource.jansi.internal.JansiLoader in an unnamed module (file:/C:/Users/16332/.m2/wrapper/dists/apache-maven-3.9.9-bin/4nf9hui3q3djbarqar9g711ggc/apache-maven-3.9.9/lib/jansi-2.4.1.jar)
|
||||
WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
|
||||
WARNING: Restricted methods will be blocked in a future release unless native access is enabled
|
||||
|
||||
WARNING: A terminally deprecated method in sun.misc.Unsafe has been called
|
||||
WARNING: sun.misc.Unsafe::objectFieldOffset has been called by com.google.common.util.concurrent.AbstractFuture$UnsafeAtomicHelper (file:/C:/Users/16332/.m2/wrapper/dists/apache-maven-3.9.9-bin/4nf9hui3q3djbarqar9g711ggc/apache-maven-3.9.9/lib/guava-33.2.1-jre.jar)
|
||||
WARNING: Please consider reporting this to the maintainers of class com.google.common.util.concurrent.AbstractFuture$UnsafeAtomicHelper
|
||||
WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release
|
||||
WARNING: Final field _cipher in class org.sonatype.plexus.components.sec.dispatcher.DefaultSecDispatcher has been mutated reflectively by class org.eclipse.sisu.bean.BeanPropertyField in unnamed module @780cb77 (file:/C:/Users/16332/.m2/wrapper/dists/apache-maven-3.9.9-bin/4nf9hui3q3djbarqar9g711ggc/apache-maven-3.9.9/lib/org.eclipse.sisu.inject-0.9.0.M3.jar)
|
||||
WARNING: Use --enable-final-field-mutation=ALL-UNNAMED to avoid a warning
|
||||
WARNING: Mutating final fields will be blocked in a future release unless final field mutation is enabled
|
||||
45
stdout.log
Normal file
45
stdout.log
Normal file
@ -0,0 +1,45 @@
|
||||
[INFO] Scanning for projects...
|
||||
[INFO]
|
||||
[INFO] ------------------------< com.tracexd:tracexd >-------------------------
|
||||
[INFO] Building tracexd 1.0.0
|
||||
[INFO] from pom.xml
|
||||
[INFO] --------------------------------[ jar ]---------------------------------
|
||||
[INFO]
|
||||
[INFO] >>> spring-boot:3.5.14:run (default-cli) > test-compile @ tracexd >>>
|
||||
[INFO]
|
||||
[INFO] --- resources:3.3.1:resources (default-resources) @ tracexd ---
|
||||
[INFO] Copying 2 resources from src\main\resources to target\classes
|
||||
[INFO] Copying 6 resources from src\main\resources to target\classes
|
||||
[INFO]
|
||||
[INFO] --- compiler:3.14.1:compile (default-compile) @ tracexd ---
|
||||
[INFO] Nothing to compile - all classes are up to date.
|
||||
[INFO]
|
||||
[INFO] --- resources:3.3.1:testResources (default-testResources) @ tracexd ---
|
||||
[INFO] skip non existing resourceDirectory C:\Work\tracexd\src\test\resources
|
||||
[INFO]
|
||||
[INFO] --- compiler:3.14.1:testCompile (default-testCompile) @ tracexd ---
|
||||
[INFO] Nothing to compile - all classes are up to date.
|
||||
[INFO]
|
||||
[INFO] <<< spring-boot:3.5.14:run (default-cli) < test-compile @ tracexd <<<
|
||||
[INFO]
|
||||
[INFO]
|
||||
[INFO] --- spring-boot:3.5.14:run (default-cli) @ tracexd ---
|
||||
[INFO] Attaching agents: []
|
||||
2026-06-09T14:15:07.058+08:00 INFO 5764 --- [ main] com.tracexd.TracexdApplication : Starting TracexdApplication using Java 26.0.1 with PID 5764 (C:\Work\tracexd\target\classes started by 16332 in C:\Work\tracexd)
|
||||
2026-06-09T14:15:07.059+08:00 DEBUG 5764 --- [ main] com.tracexd.TracexdApplication : Running with Spring Boot v3.5.14, Spring v6.2.18
|
||||
2026-06-09T14:15:07.059+08:00 INFO 5764 --- [ main] com.tracexd.TracexdApplication : The following 1 profile is active: "test"
|
||||
2026-06-09T14:15:07.660+08:00 INFO 5764 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
|
||||
2026-06-09T14:15:07.660+08:00 INFO 5764 --- [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.54]
|
||||
2026-06-09T14:15:07.687+08:00 INFO 5764 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
|
||||
Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
|
||||
Get /198.18.0.1 network interface
|
||||
Get network interface info: name:iftype53_32769 (Meta Tunnel)
|
||||
Initialization Sequence datacenterId:0 workerId:16
|
||||
2026-06-09T14:15:07.788+08:00 INFO 5764 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
|
||||
2026-06-09T14:15:07.883+08:00 INFO 5764 --- [ main] com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Added connection conn0: url=jdbc:h2:mem:tracexd user=SA
|
||||
2026-06-09T14:15:07.884+08:00 INFO 5764 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
|
||||
2026-06-09T14:15:08.298+08:00 INFO 5764 --- [ main] m.e.s.MybatisPlusApplicationContextAware : Register ApplicationContext instances org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@e383572
|
||||
2026-06-09T14:15:08.339+08:00 INFO 5764 --- [ main] com.tracexd.TracexdApplication : Started TracexdApplication in 1.545 seconds (process running for 1.715)
|
||||
Default admin user created (admin/admin123)
|
||||
2026-06-09T14:15:24.899+08:00 INFO 5764 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
|
||||
2026-06-09T14:15:24.969+08:00 WARN 5764 --- [nio-8080-exec-1] o.s.s.c.bcrypt.BCryptPasswordEncoder : Encoded password does not look like BCrypt
|
||||
18
target/classes/application-test.yml
Normal file
18
target/classes/application-test.yml
Normal file
@ -0,0 +1,18 @@
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:h2:mem:tracexd;DB_CLOSE_DELAY=-1;MODE=MYSQL
|
||||
username: sa
|
||||
password:
|
||||
driver-class-name: org.h2.Driver
|
||||
sql:
|
||||
init:
|
||||
mode: always
|
||||
schema-locations: classpath:init-schema.sql
|
||||
|
||||
mybatis-plus:
|
||||
configuration:
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
|
||||
logging:
|
||||
level:
|
||||
com.tracexd: DEBUG
|
||||
59
target/classes/application.yml
Normal file
59
target/classes/application.yml
Normal file
@ -0,0 +1,59 @@
|
||||
server:
|
||||
port: 8081
|
||||
servlet:
|
||||
session:
|
||||
timeout: 30d
|
||||
|
||||
spring:
|
||||
main:
|
||||
banner-mode: off
|
||||
datasource:
|
||||
url: jdbc:mysql://192.168.2.5:3306/tracexd?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
|
||||
username: root
|
||||
password: root
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
thymeleaf:
|
||||
prefix: classpath:/templates/
|
||||
suffix: .html
|
||||
mode: HTML
|
||||
cache: true
|
||||
servlet:
|
||||
multipart:
|
||||
max-file-size: 10MB
|
||||
security:
|
||||
user:
|
||||
name: admin
|
||||
password: admin123
|
||||
|
||||
mybatis-plus:
|
||||
global-config:
|
||||
banner: false
|
||||
configuration:
|
||||
log-impl: org.apache.ibatis.logging.nologging.NoLoggingImpl
|
||||
|
||||
logging:
|
||||
level:
|
||||
com.tracexd: INFO
|
||||
org.springframework: WARN
|
||||
|
||||
---
|
||||
spring:
|
||||
config:
|
||||
activate:
|
||||
on-profile: dev
|
||||
thymeleaf:
|
||||
cache: false
|
||||
mybatis-plus:
|
||||
configuration:
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
|
||||
---
|
||||
tracexd:
|
||||
deepseek:
|
||||
api-key: sk-732e5f5f1f07454492022401f0a2bf40
|
||||
base-url: https://api.deepseek.com
|
||||
model: deepseek-v4-pro
|
||||
mimo:
|
||||
api-key: sk-cqttr117tzllfolonxztu4qa5208liota2llfut458ixx2m1
|
||||
base-url: https://api.xiaomimimo.com/v1
|
||||
model: mimo-v2.5-asr
|
||||
BIN
target/classes/com/tracexd/TracexdApplication.class
Normal file
BIN
target/classes/com/tracexd/TracexdApplication.class
Normal file
Binary file not shown.
BIN
target/classes/com/tracexd/common/Result.class
Normal file
BIN
target/classes/com/tracexd/common/Result.class
Normal file
Binary file not shown.
BIN
target/classes/com/tracexd/common/VoiceTaskManager.class
Normal file
BIN
target/classes/com/tracexd/common/VoiceTaskManager.class
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
target/classes/com/tracexd/config/AsyncConfig.class
Normal file
BIN
target/classes/com/tracexd/config/AsyncConfig.class
Normal file
Binary file not shown.
BIN
target/classes/com/tracexd/config/MybatisPlusConfig.class
Normal file
BIN
target/classes/com/tracexd/config/MybatisPlusConfig.class
Normal file
Binary file not shown.
BIN
target/classes/com/tracexd/config/RestClientConfig.class
Normal file
BIN
target/classes/com/tracexd/config/RestClientConfig.class
Normal file
Binary file not shown.
BIN
target/classes/com/tracexd/config/SecurityConfig.class
Normal file
BIN
target/classes/com/tracexd/config/SecurityConfig.class
Normal file
Binary file not shown.
BIN
target/classes/com/tracexd/controller/PageController.class
Normal file
BIN
target/classes/com/tracexd/controller/PageController.class
Normal file
Binary file not shown.
BIN
target/classes/com/tracexd/controller/RecordController.class
Normal file
BIN
target/classes/com/tracexd/controller/RecordController.class
Normal file
Binary file not shown.
BIN
target/classes/com/tracexd/controller/VoiceController.class
Normal file
BIN
target/classes/com/tracexd/controller/VoiceController.class
Normal file
Binary file not shown.
BIN
target/classes/com/tracexd/enums/DictTypeEnum.class
Normal file
BIN
target/classes/com/tracexd/enums/DictTypeEnum.class
Normal file
Binary file not shown.
BIN
target/classes/com/tracexd/enums/IntentType.class
Normal file
BIN
target/classes/com/tracexd/enums/IntentType.class
Normal file
Binary file not shown.
BIN
target/classes/com/tracexd/mapper/DictTagMapper.class
Normal file
BIN
target/classes/com/tracexd/mapper/DictTagMapper.class
Normal file
Binary file not shown.
BIN
target/classes/com/tracexd/mapper/RecordMapper.class
Normal file
BIN
target/classes/com/tracexd/mapper/RecordMapper.class
Normal file
Binary file not shown.
BIN
target/classes/com/tracexd/mapper/SysUserMapper.class
Normal file
BIN
target/classes/com/tracexd/mapper/SysUserMapper.class
Normal file
Binary file not shown.
BIN
target/classes/com/tracexd/model/dto/RecordQueryDTO.class
Normal file
BIN
target/classes/com/tracexd/model/dto/RecordQueryDTO.class
Normal file
Binary file not shown.
BIN
target/classes/com/tracexd/model/dto/VoiceProcessResult.class
Normal file
BIN
target/classes/com/tracexd/model/dto/VoiceProcessResult.class
Normal file
Binary file not shown.
BIN
target/classes/com/tracexd/model/entity/DictTag.class
Normal file
BIN
target/classes/com/tracexd/model/entity/DictTag.class
Normal file
Binary file not shown.
BIN
target/classes/com/tracexd/model/entity/Record.class
Normal file
BIN
target/classes/com/tracexd/model/entity/Record.class
Normal file
Binary file not shown.
BIN
target/classes/com/tracexd/model/entity/SysUser.class
Normal file
BIN
target/classes/com/tracexd/model/entity/SysUser.class
Normal file
Binary file not shown.
Binary file not shown.
BIN
target/classes/com/tracexd/service/ai/AiService.class
Normal file
BIN
target/classes/com/tracexd/service/ai/AiService.class
Normal file
Binary file not shown.
BIN
target/classes/com/tracexd/service/asr/AsrService.class
Normal file
BIN
target/classes/com/tracexd/service/asr/AsrService.class
Normal file
Binary file not shown.
BIN
target/classes/com/tracexd/service/record/DictTagService.class
Normal file
BIN
target/classes/com/tracexd/service/record/DictTagService.class
Normal file
Binary file not shown.
BIN
target/classes/com/tracexd/service/record/RecordService.class
Normal file
BIN
target/classes/com/tracexd/service/record/RecordService.class
Normal file
Binary file not shown.
BIN
target/classes/com/tracexd/service/sql/SqlValidator.class
Normal file
BIN
target/classes/com/tracexd/service/sql/SqlValidator.class
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
37
target/classes/init-schema.sql
Normal file
37
target/classes/init-schema.sql
Normal file
@ -0,0 +1,37 @@
|
||||
CREATE TABLE IF NOT EXISTS record (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL,
|
||||
person VARCHAR(50) NOT NULL DEFAULT '我',
|
||||
time DATETIME NOT NULL,
|
||||
location VARCHAR(200),
|
||||
content VARCHAR(1000),
|
||||
amount DECIMAL(10,2) NOT NULL DEFAULT 0.00,
|
||||
category VARCHAR(20),
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS dict_tag (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL,
|
||||
type VARCHAR(20) NOT NULL,
|
||||
name VARCHAR(200) NOT NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY uk_user_type_name (user_id, type, name)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS sys_user (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
username VARCHAR(50) NOT NULL,
|
||||
password VARCHAR(255) NOT NULL,
|
||||
display_name VARCHAR(50) NOT NULL DEFAULT '',
|
||||
enabled TINYINT(1) NOT NULL DEFAULT 1,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY uk_username (username)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS persistent_logins (
|
||||
username VARCHAR(64) NOT NULL,
|
||||
series VARCHAR(64) PRIMARY KEY,
|
||||
token VARCHAR(64) NOT NULL,
|
||||
last_used TIMESTAMP NOT NULL
|
||||
);
|
||||
@ -0,0 +1 @@
|
||||
{}
|
||||
119
target/classes/static/css/style.css
Normal file
119
target/classes/static/css/style.css
Normal file
@ -0,0 +1,119 @@
|
||||
/* ===== Reset & Base ===== */
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; background: #f5f5f7; color: #1d1d1f; min-height: 100vh; }
|
||||
a { color: #007aff; text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
.app-header { display: flex; align-items: center; justify-content: space-between; padding: 12px 20px; background: #fff; border-bottom: 1px solid #e5e5e7; position: sticky; top: 0; z-index: 100; }
|
||||
.header-title { font-size: 18px; font-weight: 600; }
|
||||
.header-link { font-size: 14px; color: #007aff; }
|
||||
.header-nav { display: flex; align-items: center; gap: 12px; }
|
||||
.header-logout { font-size: 14px; color: #999; cursor: pointer; }
|
||||
.header-logout:hover { color: #ff3b30; }
|
||||
.main-content { max-width: 960px; margin: 0 auto; padding: 16px; min-height: calc(100vh - 52px); padding-bottom: 100px; }
|
||||
.toast { position: fixed; top: 60px; left: 50%; transform: translateX(-50%); padding: 10px 24px; border-radius: 8px; font-size: 14px; z-index: 999; transition: opacity 0.3s; text-align: center; max-width: 90%; white-space: nowrap; }
|
||||
.toast.hidden { opacity: 0; pointer-events: none; }
|
||||
.toast.show { opacity: 1; }
|
||||
.toast-success { background: #34c759; color: #fff; }
|
||||
.toast-error { background: #ff3b30; color: #fff; }
|
||||
.toast-info { background: #007aff; color: #fff; }
|
||||
.mic-btn { position: fixed; bottom: 40px; left: 50%; margin-left: -40px; width: 80px; height: 80px; border-radius: 50%; background: #007aff; border: none; cursor: pointer; box-shadow: 0 4px 16px rgba(0,122,255,0.4); display: flex; align-items: center; justify-content: center; transition: transform 0.15s, box-shadow 0.15s, background 0.15s; z-index: 200; touch-action: none; }
|
||||
.mic-btn:hover { transform: scale(1.05); box-shadow: 0 6px 20px rgba(0,122,255,0.5); }
|
||||
.mic-btn:active, .mic-btn.recording { transform: scale(0.95); background: #ff3b30; box-shadow: 0 4px 16px rgba(255,59,48,0.4); }
|
||||
.mic-icon { width: 38px; height: 38px; color: #fff; }
|
||||
.result-area { margin-bottom: 16px; }
|
||||
.query-result-card { background: #fff; border-radius: 12px; padding: 16px; box-shadow: 0 2px 8px rgba(0,0,0,0.06); border: 1px solid #e5e5e7; }
|
||||
.query-table { width: 100%; border-collapse: collapse; margin-bottom: 12px; }
|
||||
.query-table th, .query-table td { padding: 8px 12px; text-align: left; font-size: 13px; border-bottom: 1px solid #f0f0f0; }
|
||||
.query-table th { font-weight: 600; color: #666; background: #fafafa; }
|
||||
.ai-summary { padding: 8px 0; font-size: 14px; line-height: 1.6; color: #333; }
|
||||
.section-title { font-size: 16px; font-weight: 600; margin-bottom: 12px; color: #333; }
|
||||
.empty-hint { color: #999; font-size: 14px; text-align: center; padding: 32px 0; }
|
||||
.recent-list { display: flex; flex-direction: column; gap: 8px; }
|
||||
.record-item { display: flex; align-items: center; gap: 12px; background: #fff; padding: 12px 16px; border-radius: 10px; box-shadow: 0 1px 4px rgba(0,0,0,0.04); border: 1px solid #e5e5e7; font-size: 14px; }
|
||||
.rec-time { color: #666; min-width: 50px; font-size: 13px; white-space: nowrap; }
|
||||
.rec-person { color: #007aff; font-weight: 500; min-width: 36px; }
|
||||
.rec-content { flex: 1; color: #333; }
|
||||
.rec-amount { font-weight: 600; color: #333; min-width: 60px; text-align: right; white-space: nowrap; }
|
||||
.badge { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 12px; color: #fff; white-space: nowrap; }
|
||||
.badge-default { background: #666; }
|
||||
.card-default { background: transparent; }
|
||||
.card-c1 { background: rgba(255, 149, 0, 0.08); }
|
||||
.card-c2 { background: rgba(0, 122, 255, 0.08); }
|
||||
.card-c3 { background: rgba(255, 45, 85, 0.08); }
|
||||
.card-c4 { background: rgba(175, 82, 222, 0.08); }
|
||||
.card-c5 { background: rgba(52, 199, 89, 0.08); }
|
||||
.card-c6 { background: rgba(88, 86, 214, 0.08); }
|
||||
.card-c7 { background: rgba(255, 149, 0, 0.08); }
|
||||
.card-c8 { background: rgba(0, 122, 255, 0.08); }
|
||||
.card-c9 { background: rgba(255, 45, 85, 0.08); }
|
||||
.card-c10 { background: rgba(175, 82, 222, 0.08); }
|
||||
.card-c11 { background: rgba(52, 199, 89, 0.08); }
|
||||
.card-c12 { background: rgba(88, 86, 214, 0.08); }
|
||||
.badge-c1 { background: #ff9500; }
|
||||
.badge-c2 { background: #007aff; }
|
||||
.badge-c3 { background: #ff2d55; }
|
||||
.badge-c4 { background: #af52de; }
|
||||
.badge-c5 { background: #34c759; }
|
||||
.badge-c6 { background: #5856d6; }
|
||||
.badge-c7 { background: #ff9500; }
|
||||
.badge-c8 { background: #007aff; }
|
||||
.badge-c9 { background: #ff2d55; }
|
||||
.badge-c10 { background: #af52de; }
|
||||
.badge-c11 { background: #34c759; }
|
||||
.badge-c12 { background: #5856d6; }
|
||||
.login-body { display: flex; align-items: center; justify-content: center; min-height: 100vh; background: linear-gradient(135deg, #f5f5f7 0%, #e8e8ed 100%); }
|
||||
.login-container { background: #fff; padding: 40px; border-radius: 16px; box-shadow: 0 8px 32px rgba(0,0,0,0.08); width: 360px; max-width: 90%; }
|
||||
.login-title { text-align: center; font-size: 28px; font-weight: 700; margin-bottom: 4px; color: #1d1d1f; }
|
||||
.login-subtitle { text-align: center; font-size: 14px; color: #999; margin-bottom: 24px; }
|
||||
.login-error { background: #fff2f2; color: #ff3b30; padding: 8px 12px; border-radius: 6px; margin-bottom: 16px; font-size: 13px; }
|
||||
.login-info { background: #e8f4ff; color: #007aff; padding: 8px 12px; border-radius: 6px; margin-bottom: 16px; font-size: 13px; }
|
||||
.form-group { margin-bottom: 16px; }
|
||||
.form-group label { display: block; font-size: 13px; font-weight: 500; margin-bottom: 6px; color: #555; }
|
||||
.form-group input { width: 100%; padding: 10px 12px; border: 1px solid #d1d1d6; border-radius: 8px; font-size: 14px; outline: none; transition: border-color 0.2s; }
|
||||
.form-group input:focus { border-color: #007aff; box-shadow: 0 0 0 3px rgba(0,122,255,0.1); }
|
||||
.form-remember { margin-bottom: 20px; font-size: 13px; color: #666; }
|
||||
.form-remember input { margin-right: 6px; }
|
||||
.login-btn { width: 100%; padding: 12px; background: #007aff; color: #fff; border: none; border-radius: 8px; font-size: 16px; font-weight: 500; cursor: pointer; transition: background 0.2s; }
|
||||
.login-btn:hover { background: #0056cc; }
|
||||
.filter-section { background: #fff; border-radius: 12px; padding: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.06); border: 1px solid #e5e5e7; margin-bottom: 16px; }
|
||||
.filter-section.hidden { display: none; }
|
||||
.filter-group { margin-bottom: 16px; }
|
||||
.filter-group:last-of-type { margin-bottom: 12px; }
|
||||
.filter-label { display: block; font-size: 13px; font-weight: 600; color: #666; margin-bottom: 8px; }
|
||||
.filter-tags { display: flex; flex-wrap: wrap; gap: 8px; }
|
||||
.tag-btn { display: inline-block; padding: 6px 14px; border-radius: 6px; border: 1px solid #d1d1d6; background: #fff; font-size: 13px; cursor: pointer; transition: all 0.15s; user-select: none; color: #333; }
|
||||
.tag-btn:hover { border-color: #007aff; color: #007aff; }
|
||||
.tag-btn.active { background: #007aff; color: #fff; border-color: #007aff; }
|
||||
.filter-date-range { display: flex; align-items: center; gap: 8px; }
|
||||
.date-input { padding: 6px 12px; border: 1px solid #d1d1d6; border-radius: 6px; font-size: 13px; outline: none; }
|
||||
.date-input:focus { border-color: #007aff; }
|
||||
.date-sep { color: #999; }
|
||||
.filter-actions { display: flex; justify-content: space-between; align-items: center; padding-top: 8px; }
|
||||
.btn { padding: 8px 24px; border-radius: 8px; font-size: 14px; font-weight: 500; cursor: pointer; transition: all 0.15s; border: none; }
|
||||
.btn-primary { background: #007aff; color: #fff; }
|
||||
.btn-primary:hover { background: #0056cc; }
|
||||
.btn-secondary { background: #f5f5f7; color: #333; border: 1px solid #d1d1d6; }
|
||||
.btn-secondary:hover { background: #e8e8ed; }
|
||||
.btn-link { background: none; color: #007aff; padding: 4px 8px; font-size: 13px; }
|
||||
.btn-link:hover { text-decoration: underline; }
|
||||
.btn-page { padding: 6px 14px; font-size: 13px; background: #fff; border: 1px solid #d1d1d6; border-radius: 6px; color: #333; }
|
||||
.btn-page:disabled { opacity: 0.4; cursor: not-allowed; }
|
||||
.btn-page:hover:not(:disabled) { border-color: #007aff; color: #007aff; }
|
||||
.filter-summary { background: #fff; border-radius: 10px; padding: 12px 16px; box-shadow: 0 2px 8px rgba(0,0,0,0.06); border: 1px solid #e5e5e7; margin-bottom: 16px; font-size: 13px; color: #666; display: flex; align-items: center; gap: 12px; }
|
||||
.summary-bar { background: #e8f4ff; border-radius: 8px; padding: 10px 16px; margin-bottom: 12px; font-size: 14px; color: #007aff; }
|
||||
.summary-bar strong { font-size: 16px; }
|
||||
.table-wrapper { overflow-x: auto; }
|
||||
.data-table { width: 100%; border-collapse: collapse; background: #fff; border-radius: 10px; overflow: hidden; box-shadow: 0 1px 4px rgba(0,0,0,0.04); border: 1px solid #e5e5e7; }
|
||||
.data-table th, .data-table td { padding: 10px 14px; text-align: left; font-size: 13px; border-bottom: 1px solid #f0f0f0; }
|
||||
.data-table th { background: #fafafa; font-weight: 600; color: #666; white-space: nowrap; }
|
||||
.data-table tbody tr:hover { background: #f8f8fa; }
|
||||
.empty-cell { text-align: center; color: #999; padding: 32px 0 !important; }
|
||||
.col-time { width: 180px; white-space: nowrap; }
|
||||
.col-person { width: 80px; }
|
||||
.col-location { min-width: 120px; max-width: 200px; }
|
||||
.col-content { min-width: 160px; max-width: 400px; }
|
||||
.col-amount { width: 100px; text-align: right; }
|
||||
.col-amount-cell { text-align: right; font-weight: 600; }
|
||||
.col-category { width: 80px; }
|
||||
.pagination { display: flex; align-items: center; justify-content: center; gap: 16px; margin-top: 16px; font-size: 13px; color: #666; }
|
||||
@media (max-width: 640px) { .main-content { padding: 12px; padding-bottom: 100px; } .mic-btn { bottom: 24px; left: 50%; margin-left: -34px; width: 68px; height: 68px; } .mic-icon { width: 32px; height: 32px; } .data-table { font-size: 12px; } .data-table th, .data-table td { padding: 8px 10px; } .col-time { width: 140px; } .col-location { max-width: 120px; } .col-content { max-width: 200px; } }
|
||||
BIN
target/classes/static/favicon.ico
Normal file
BIN
target/classes/static/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
63
target/classes/static/js/app.js
Normal file
63
target/classes/static/js/app.js
Normal file
@ -0,0 +1,63 @@
|
||||
/**
|
||||
* TraceXD 前端通用工具函数
|
||||
*/
|
||||
(function(global) {
|
||||
|
||||
/**
|
||||
* 格式化时间显示
|
||||
* 当天 → HH:mm
|
||||
* 当年非当天 → M-d HH:mm
|
||||
* 非当年 → yy-M-d HH
|
||||
*
|
||||
* @param {string} isoString ISO 日期时间字符串
|
||||
* @returns {string} 格式化后的时间文本
|
||||
*/
|
||||
global.formatTime = function(isoString) {
|
||||
if (!isoString) return '';
|
||||
var d = new Date(isoString);
|
||||
var pad = function(n) { return String(n).padStart(2, '0'); };
|
||||
return (d.getMonth() + 1) + '-' + d.getDate() + ' ' +
|
||||
pad(d.getHours()) + ':' + pad(d.getMinutes());
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* ??????? badge CSS ??
|
||||
* ??????????????????????????
|
||||
*/
|
||||
global.badgeClass = function(category) {
|
||||
if (!category) return 'badge-default';
|
||||
var fixed = {
|
||||
'餐饮': 'badge-c1',
|
||||
'饮食': 'badge-c1',
|
||||
'交通': 'badge-c2',
|
||||
'出行': 'badge-c2',
|
||||
'购物': 'badge-c3',
|
||||
'娱乐': 'badge-c4',
|
||||
'医疗': 'badge-c5',
|
||||
'健康': 'badge-c5',
|
||||
'教育': 'badge-c6',
|
||||
'学习': 'badge-c6',
|
||||
'养老': 'badge-c7',
|
||||
'社交': 'badge-c8',
|
||||
'旅游': 'badge-c9',
|
||||
'居家': 'badge-c10'
|
||||
};
|
||||
if (fixed[category]) return fixed[category];
|
||||
// hash-based fallback
|
||||
var hash = 0;
|
||||
for (var i = 0; i < category.length; i++) {
|
||||
hash = ((hash << 5) - hash) + category.charCodeAt(i);
|
||||
hash |= 0;
|
||||
}
|
||||
var idx = (Math.abs(hash) % 12) + 1;
|
||||
return 'badge-c' + idx;
|
||||
};
|
||||
|
||||
global.logout = function() {
|
||||
fetch('/logout', { method: 'POST' }).then(function() {
|
||||
window.location.href = '/login?logout';
|
||||
});
|
||||
};
|
||||
|
||||
})(window);
|
||||
271
target/classes/templates/browse.html
Normal file
271
target/classes/templates/browse.html
Normal file
@ -0,0 +1,271 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>TraceXD - 浏览记录</title>
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<header class="app-header">
|
||||
<a href="/" class="header-link">首页</a>
|
||||
<span class="header-title">浏览记录</span>
|
||||
<a href="#" onclick="logout(); return false;" class="header-logout">退出</a>
|
||||
</header>
|
||||
|
||||
<main class="main-content">
|
||||
<!-- 筛选项区域 -->
|
||||
<div id="filter-section" class="filter-section">
|
||||
<div class="filter-group">
|
||||
<label class="filter-label">人物</label>
|
||||
<div id="filter-persons" class="filter-tags"></div>
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<label class="filter-label">地点</label>
|
||||
<div id="filter-locations" class="filter-tags"></div>
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<label class="filter-label">分类</label>
|
||||
<div id="filter-categories" class="filter-tags"></div>
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<label class="filter-label">时间</label>
|
||||
<div class="filter-date-range">
|
||||
<input type="date" id="filter-start-date" class="date-input">
|
||||
<span class="date-sep">~</span>
|
||||
<input type="date" id="filter-end-date" class="date-input">
|
||||
</div>
|
||||
</div>
|
||||
<div class="filter-actions">
|
||||
<button id="btn-reset" class="btn btn-secondary">重置</button>
|
||||
<button id="btn-query" class="btn btn-primary">查询</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 折叠后的筛选项摘要 -->
|
||||
<div id="filter-summary" class="filter-summary hidden">
|
||||
<span id="filter-summary-text"></span>
|
||||
<button id="btn-expand" class="btn btn-link">展开筛选项</button>
|
||||
</div>
|
||||
|
||||
<!-- 金额汇总 -->
|
||||
<div id="summary-bar" class="summary-bar hidden">
|
||||
金额汇总: <strong id="total-amount">¥0.00</strong>
|
||||
</div>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<div class="table-wrapper">
|
||||
<table class="data-table" id="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="col-time">时间</th>
|
||||
<th class="col-person">人物</th>
|
||||
<th class="col-location">地点</th>
|
||||
<th class="col-content">事项内容</th>
|
||||
<th class="col-amount">金额</th>
|
||||
<th class="col-category">分类</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="table-body">
|
||||
<tr><td colspan="6" class="empty-cell">请点击"查询"查看记录</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div id="pagination" class="pagination hidden">
|
||||
<button id="btn-prev" class="btn btn-page">< 上一页</button>
|
||||
<span id="page-info"></span>
|
||||
<button id="btn-next" class="btn btn-page">下一页 ></button>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="/js/app.js"></script>
|
||||
<script>
|
||||
(function() {
|
||||
var currentPage = 1;
|
||||
var pageSize = 20;
|
||||
var totalPages = 0;
|
||||
var filterCollapsed = false;
|
||||
|
||||
var selectedPersons = [];
|
||||
var selectedLocations = [];
|
||||
var selectedCategories = [];
|
||||
|
||||
function init() {
|
||||
loadFilters();
|
||||
document.getElementById('btn-query').addEventListener('click', function() {
|
||||
doQuery(1);
|
||||
});
|
||||
document.getElementById('btn-reset').addEventListener('click', function() {
|
||||
resetFilters();
|
||||
});
|
||||
document.getElementById('btn-expand').addEventListener('click', function() {
|
||||
toggleFilterSection(false);
|
||||
});
|
||||
document.getElementById('btn-prev').addEventListener('click', function() {
|
||||
if (currentPage > 1) doQuery(currentPage - 1);
|
||||
});
|
||||
document.getElementById('btn-next').addEventListener('click', function() {
|
||||
if (currentPage < totalPages) doQuery(currentPage + 1);
|
||||
});
|
||||
}
|
||||
|
||||
function loadFilters() {
|
||||
fetch('/api/records/filters')
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(resp) {
|
||||
if (resp.code !== 200) return;
|
||||
var data = resp.data;
|
||||
renderFilterTags('filter-persons', data.persons || [], 'person');
|
||||
renderFilterTags('filter-locations', data.locations || [], 'location');
|
||||
renderFilterTags('filter-categories', data.categories || [], 'category');
|
||||
});
|
||||
}
|
||||
|
||||
function renderFilterTags(containerId, items, type) {
|
||||
var container = document.getElementById(containerId);
|
||||
container.innerHTML = '';
|
||||
items.forEach(function(item) {
|
||||
var tag = document.createElement('span');
|
||||
tag.className = 'tag-btn';
|
||||
tag.textContent = item;
|
||||
tag.dataset.type = type;
|
||||
tag.dataset.value = item;
|
||||
tag.addEventListener('click', function() {
|
||||
this.classList.toggle('active');
|
||||
});
|
||||
container.appendChild(tag);
|
||||
});
|
||||
}
|
||||
|
||||
function getSelectedTags(type) {
|
||||
var tags = document.querySelectorAll('.tag-btn[data-type="' + type + '"].active');
|
||||
return Array.from(tags).map(function(t) { return t.dataset.value; });
|
||||
}
|
||||
|
||||
function getQueryParams() {
|
||||
selectedPersons = getSelectedTags('person');
|
||||
selectedLocations = getSelectedTags('location');
|
||||
selectedCategories = getSelectedTags('category');
|
||||
|
||||
var startDate = document.getElementById('filter-start-date').value;
|
||||
var endDate = document.getElementById('filter-end-date').value;
|
||||
|
||||
return {
|
||||
persons: selectedPersons,
|
||||
locations: selectedLocations,
|
||||
categories: selectedCategories,
|
||||
startDate: startDate,
|
||||
endDate: endDate
|
||||
};
|
||||
}
|
||||
|
||||
function buildFilterSummary(params) {
|
||||
var parts = [];
|
||||
if (params.persons.length) parts.push('人物(' + params.persons.join(',') + ')');
|
||||
if (params.locations.length) parts.push('地点(' + params.locations.join(',') + ')');
|
||||
if (params.categories.length) parts.push('分类(' + params.categories.join(',') + ')');
|
||||
if (params.startDate || params.endDate) {
|
||||
parts.push('时间(' + (params.startDate || '不限') + '~' + (params.endDate || '不限') + ')');
|
||||
}
|
||||
return parts.join(' | ') || '无条件';
|
||||
}
|
||||
|
||||
function toggleFilterSection(collapsed) {
|
||||
filterCollapsed = collapsed;
|
||||
document.getElementById('filter-section').classList.toggle('hidden', collapsed);
|
||||
document.getElementById('filter-summary').classList.toggle('hidden', !collapsed);
|
||||
}
|
||||
|
||||
function doQuery(page) {
|
||||
var params = getQueryParams();
|
||||
var queryStr = '?page=' + page + '&size=' + pageSize;
|
||||
if (params.persons.length) {
|
||||
params.persons.forEach(function(p) { queryStr += '&persons=' + encodeURIComponent(p); });
|
||||
}
|
||||
if (params.locations.length) {
|
||||
params.locations.forEach(function(l) { queryStr += '&locations=' + encodeURIComponent(l); });
|
||||
}
|
||||
if (params.categories.length) {
|
||||
params.categories.forEach(function(c) { queryStr += '&categories=' + encodeURIComponent(c); });
|
||||
}
|
||||
if (params.startDate) queryStr += '&startDate=' + params.startDate;
|
||||
if (params.endDate) queryStr += '&endDate=' + params.endDate;
|
||||
|
||||
fetch('/api/records' + queryStr)
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(resp) {
|
||||
if (resp.code !== 200) return;
|
||||
renderTable(resp.data);
|
||||
toggleFilterSection(true);
|
||||
var summary = document.getElementById('filter-summary-text');
|
||||
summary.textContent = '已选: ' + buildFilterSummary(params);
|
||||
});
|
||||
}
|
||||
|
||||
function renderTable(data) {
|
||||
if (!data) return;
|
||||
var records = data.records || [];
|
||||
var totalAmount = data.totalAmount || 0;
|
||||
var total = data.total || 0;
|
||||
currentPage = Number(data.page) || 1;
|
||||
totalPages = Math.ceil(total / pageSize);
|
||||
|
||||
// 更新金额汇总
|
||||
var summaryBar = document.getElementById('summary-bar');
|
||||
summaryBar.classList.remove('hidden');
|
||||
document.getElementById('total-amount').textContent = '¥' + Number(totalAmount).toFixed(2);
|
||||
|
||||
// 更新表格
|
||||
var tbody = document.getElementById('table-body');
|
||||
if (records.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="6" class="empty-cell">暂无数据</td></tr>';
|
||||
} else {
|
||||
var html = '';
|
||||
records.forEach(function(r) {
|
||||
html += '<tr>' +
|
||||
'<td>' + formatTime(r.time) + '</td>' +
|
||||
'<td>' + (r.person || '我') + '</td>' +
|
||||
'<td>' + (r.location || '-') + '</td>' +
|
||||
'<td>' + (r.content || '') + '</td>' +
|
||||
'<td class="col-amount-cell">¥' + (r.amount ? Number(r.amount).toFixed(2) : '0.00') + '</td>' +
|
||||
'<td><span class="badge badge ' + badgeClass(r.category) + '">' +
|
||||
(r.category || '-') + '</span></td>' +
|
||||
'</tr>';
|
||||
});
|
||||
tbody.innerHTML = html;
|
||||
}
|
||||
|
||||
// 更新分页
|
||||
var pagination = document.getElementById('pagination');
|
||||
if (totalPages > 1) {
|
||||
pagination.classList.remove('hidden');
|
||||
document.getElementById('page-info').textContent =
|
||||
'第 ' + currentPage + ' / ' + totalPages + ' 页(共 ' + total + ' 条)';
|
||||
document.getElementById('btn-prev').disabled = currentPage <= 1;
|
||||
document.getElementById('btn-next').disabled = currentPage >= totalPages;
|
||||
} else {
|
||||
pagination.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
function resetFilters() {
|
||||
document.querySelectorAll('.tag-btn').forEach(function(t) { t.classList.remove('active'); });
|
||||
document.getElementById('filter-start-date').value = '';
|
||||
document.getElementById('filter-end-date').value = '';
|
||||
document.getElementById('table-body').innerHTML =
|
||||
'<tr><td colspan="6" class="empty-cell">请点击"查询"查看记录</td></tr>';
|
||||
document.getElementById('summary-bar').classList.add('hidden');
|
||||
document.getElementById('pagination').classList.add('hidden');
|
||||
toggleFilterSection(false);
|
||||
}
|
||||
|
||||
init();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
329
target/classes/templates/index.html
Normal file
329
target/classes/templates/index.html
Normal file
@ -0,0 +1,329 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||
<title>TraceXD - 首页</title>
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<header class="app-header">
|
||||
<span class="header-title">TraceXD</span>
|
||||
<nav class="header-nav">
|
||||
<a href="/browse" class="header-link">浏览记录</a>
|
||||
<a href="#" onclick="logout(); return false;" class="header-logout">退出</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main class="main-content">
|
||||
<!-- 结果展示区 -->
|
||||
<div id="result-area" class="result-area"></div>
|
||||
|
||||
<!-- Toast 通知 -->
|
||||
<div id="toast" class="toast hidden"></div>
|
||||
|
||||
<!-- 最近事项 -->
|
||||
<div class="recent-section">
|
||||
<h3 class="section-title">最近事项</h3>
|
||||
<div id="recent-list" class="recent-list">
|
||||
<p class="empty-hint">暂无事项记录,点击右下角麦克风开始录入</p>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- 麦克风按钮 -->
|
||||
<button id="mic-btn" class="mic-btn">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="mic-icon">
|
||||
<path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/>
|
||||
<path d="M19 10v2a7 7 0 0 1-14 0v-2"/>
|
||||
<line x1="12" y1="19" x2="12" y2="23"/>
|
||||
<line x1="8" y1="23" x2="16" y2="23"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<script src="/js/app.js"></script>
|
||||
<script>
|
||||
(function() {
|
||||
const micBtn = document.getElementById('mic-btn');
|
||||
const resultArea = document.getElementById('result-area');
|
||||
const toast = document.getElementById('toast');
|
||||
const recentList = document.getElementById('recent-list');
|
||||
|
||||
let isRecording = false;
|
||||
let stream = null;
|
||||
// Web Audio API WAV recording variables
|
||||
let audioCtx = null;
|
||||
let mediaStreamSrc = null;
|
||||
let procNode = null;
|
||||
let pcmChunks = [];
|
||||
let recordedSampleRate = 48000;
|
||||
|
||||
// 时间格式化
|
||||
function formatTime(isoString) {
|
||||
if (!isoString) return '';
|
||||
const d = new Date(isoString);
|
||||
const pad = n => String(n).padStart(2, '0');
|
||||
return (d.getMonth() + 1) + '-' + d.getDate() + ' ' +
|
||||
pad(d.getHours()) + ':' + pad(d.getMinutes());
|
||||
}
|
||||
|
||||
// 显示 Toast
|
||||
function showToast(message, type) {
|
||||
toast.textContent = message;
|
||||
toast.className = 'toast toast-' + (type || 'success') + ' show';
|
||||
setTimeout(function() {
|
||||
toast.className = 'toast hidden';
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// 展示查询结果
|
||||
function showQueryResult(data) {
|
||||
var html = '<div class="query-result-card">';
|
||||
if (data.queryData && data.queryData.length > 0) {
|
||||
html += '<table class="query-table"><thead><tr>' +
|
||||
'<th>时间</th><th>人物</th><th>内容</th><th>金额</th>' +
|
||||
'</tr></thead><tbody>';
|
||||
data.queryData.forEach(function(row) {
|
||||
html += '<tr class="' + cardClass(row.category) + '"><td>' + formatTime(row.time) + '</td>' +
|
||||
'<td>' + (row.person || '') + '</td>' +
|
||||
'<td>' + (row.content || '') + '</td>' +
|
||||
'<td>' + (row.amount || '0') + '</td></tr>';
|
||||
});
|
||||
html += '</tbody></table>';
|
||||
}
|
||||
if (data.reply) {
|
||||
html += '<div class="ai-summary">' + data.reply + '</div>';
|
||||
}
|
||||
html += '</div>';
|
||||
resultArea.innerHTML = html;
|
||||
}
|
||||
|
||||
// 展示AI直接回复(其他意图)
|
||||
function showAiReply(text) {
|
||||
var html = '<div class="query-result-card"><div class="ai-summary">' +
|
||||
text + '</div></div>';
|
||||
resultArea.innerHTML = html;
|
||||
}
|
||||
|
||||
// 开始录音
|
||||
function startRecording() {
|
||||
pcmChunks = [];
|
||||
navigator.mediaDevices.getUserMedia({ audio: true })
|
||||
.then(function(mediaStream) {
|
||||
stream = mediaStream;
|
||||
|
||||
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
||||
if (audioCtx.state === 'suspended') {
|
||||
audioCtx.resume();
|
||||
}
|
||||
recordedSampleRate = audioCtx.sampleRate;
|
||||
|
||||
mediaStreamSrc = audioCtx.createMediaStreamSource(mediaStream);
|
||||
|
||||
procNode = audioCtx.createScriptProcessor(4096, 1, 1);
|
||||
procNode.onaudioprocess = function(event) {
|
||||
var channelData = event.inputBuffer.getChannelData(0);
|
||||
var buf = new Int16Array(channelData.length);
|
||||
for (var i = 0; i < channelData.length; i++) {
|
||||
var s = Math.max(-1, Math.min(1, channelData[i]));
|
||||
buf[i] = s < 0 ? s * 0x8000 : s * 0x7FFF;
|
||||
}
|
||||
pcmChunks.push(buf);
|
||||
};
|
||||
|
||||
mediaStreamSrc.connect(procNode);
|
||||
procNode.connect(audioCtx.destination);
|
||||
|
||||
isRecording = true;
|
||||
micBtn.classList.add('recording');
|
||||
})
|
||||
.catch(function(err) {
|
||||
showToast('无法访问麦克风 ' + err.message, 'error');
|
||||
});
|
||||
}
|
||||
|
||||
function stopRecording() {
|
||||
if (!isRecording) return;
|
||||
isRecording = false;
|
||||
micBtn.classList.remove('recording');
|
||||
|
||||
if (procNode) {
|
||||
procNode.disconnect();
|
||||
procNode = null;
|
||||
}
|
||||
if (mediaStreamSrc) {
|
||||
mediaStreamSrc.disconnect();
|
||||
mediaStreamSrc = null;
|
||||
}
|
||||
if (audioCtx) {
|
||||
audioCtx.close();
|
||||
audioCtx = null;
|
||||
}
|
||||
if (stream) {
|
||||
stream.getTracks().forEach(function(t) { t.stop(); });
|
||||
stream = null;
|
||||
}
|
||||
|
||||
if (pcmChunks.length === 0) return;
|
||||
|
||||
var totalLen = pcmChunks.reduce(function(sum, chunk) { return sum + chunk.length; }, 0);
|
||||
var allSamples = new Int16Array(totalLen);
|
||||
var offset = 0;
|
||||
pcmChunks.forEach(function(chunk) {
|
||||
allSamples.set(chunk, offset);
|
||||
offset += chunk.length;
|
||||
});
|
||||
|
||||
var wavBlob = encodeWav(allSamples, recordedSampleRate);
|
||||
sendAudio(wavBlob);
|
||||
}
|
||||
|
||||
function encodeWav(samples, sampleRate) {
|
||||
var numChannels = 1;
|
||||
var bitsPerSample = 16;
|
||||
var bytesPerSample = bitsPerSample / 8;
|
||||
var byteRate = sampleRate * numChannels * bytesPerSample;
|
||||
var blockAlign = numChannels * bytesPerSample;
|
||||
var dataSize = samples.length * bytesPerSample;
|
||||
|
||||
var buf = new ArrayBuffer(44 + dataSize);
|
||||
var dv = new DataView(buf);
|
||||
|
||||
function writeStr(offset, str) {
|
||||
for (var i = 0; i < str.length; i++) {
|
||||
dv.setUint8(offset + i, str.charCodeAt(i));
|
||||
}
|
||||
}
|
||||
|
||||
writeStr(0, 'RIFF');
|
||||
dv.setUint32(4, 36 + dataSize, true);
|
||||
writeStr(8, 'WAVE');
|
||||
writeStr(12, 'fmt ');
|
||||
dv.setUint32(16, 16, true);
|
||||
dv.setUint16(20, 1, true);
|
||||
dv.setUint16(22, numChannels, true);
|
||||
dv.setUint32(24, sampleRate, true);
|
||||
dv.setUint32(28, byteRate, true);
|
||||
dv.setUint16(32, blockAlign, true);
|
||||
dv.setUint16(34, bitsPerSample, true);
|
||||
writeStr(36, 'data');
|
||||
dv.setUint32(40, dataSize, true);
|
||||
|
||||
var ptr = 44;
|
||||
for (var i = 0; i < samples.length; i++) {
|
||||
dv.setInt16(ptr, samples[i], true);
|
||||
ptr += 2;
|
||||
}
|
||||
|
||||
return new Blob([buf], { type: 'audio/wav' });
|
||||
}
|
||||
|
||||
function cardClass(category) {
|
||||
if (!category) return 'card-default';
|
||||
var fixed = {'\u9910\u996e':'card-c1','\u996e\u98df':'card-c1','\u4ea4\u901a':'card-c2','\u51fa\u884c':'card-c2','\u8d2d\u7269':'card-c3','\u5a31\u4e50':'card-c4','\u533b\u7597':'card-c5','\u5065\u5eb7':'card-c5','\u6559\u80b2':'card-c6','\u5b66\u4e60':'card-c6'};
|
||||
if (fixed[category]) return fixed[category];
|
||||
var hash = 0;
|
||||
for (var i = 0; i < category.length; i++) {
|
||||
hash = ((hash << 5) - hash) + category.charCodeAt(i);
|
||||
hash |= 0;
|
||||
}
|
||||
return 'card-c' + ((Math.abs(hash) % 12) + 1);
|
||||
}
|
||||
|
||||
function sendAudio(blob) {
|
||||
showToast('处理中...', 'info');
|
||||
var formData = new FormData();
|
||||
formData.append('audio', blob, 'recording.wav');
|
||||
|
||||
fetch('/api/voice/process', { method: 'POST', body: formData })
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(resp) {
|
||||
if (resp.code !== 200) {
|
||||
showToast(resp.message, 'error');
|
||||
return;
|
||||
}
|
||||
var taskId = resp.data.taskId;
|
||||
var es = new EventSource('/api/voice/stream/' + taskId);
|
||||
es.onmessage = function(event) {
|
||||
var result = JSON.parse(event.data);
|
||||
es.close();
|
||||
handleVoiceResult(result);
|
||||
};
|
||||
es.onerror = function() {
|
||||
es.close();
|
||||
showToast('处理超时,请重试', 'error');
|
||||
};
|
||||
})
|
||||
.catch(function(err) {
|
||||
showToast('网络错误: ' + err.message, 'error');
|
||||
});
|
||||
}
|
||||
|
||||
function handleVoiceResult(result) {
|
||||
if (result.code !== 200) {
|
||||
showToast(result.message, 'error');
|
||||
return;
|
||||
}
|
||||
var data = result.data;
|
||||
if (data.intent === 'RECORD') {
|
||||
if (data.success && data.recordTip) {
|
||||
showToast(data.recordTip, 'success');
|
||||
loadRecentRecords();
|
||||
} else {
|
||||
showToast(data.reply || '录入失败', 'error');
|
||||
}
|
||||
} else if (data.intent === 'QUERY') {
|
||||
showQueryResult(data);
|
||||
showToast('查询完成', 'info');
|
||||
} else {
|
||||
showAiReply(data.reply);
|
||||
}
|
||||
}
|
||||
|
||||
// 加载最近事项
|
||||
function loadRecentRecords() {
|
||||
fetch('/api/records?page=1&size=5')
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(resp) {
|
||||
if (resp.code !== 200 || !resp.data) return;
|
||||
var records = resp.data.records || [];
|
||||
if (records.length === 0) {
|
||||
recentList.innerHTML = '<p class="empty-hint">暂无事项记录</p>';
|
||||
return;
|
||||
}
|
||||
var html = '';
|
||||
records.forEach(function(r) {
|
||||
html += '<div class="record-item ' + cardClass(r.category) + '">' +
|
||||
'<span class="rec-time">' + formatTime(r.time) + '</span>' +
|
||||
'<span class="rec-person">' + (r.person || '我') + '</span>' +
|
||||
'<span class="rec-content">' + (r.content || '') + '</span>' +
|
||||
'<span class="rec-amount">¥' + (r.amount || '0') + '</span>' +
|
||||
'</div>';
|
||||
});
|
||||
recentList.innerHTML = html;
|
||||
})
|
||||
.catch(function() {});
|
||||
}
|
||||
|
||||
// 事件绑定
|
||||
micBtn.addEventListener('mousedown', startRecording);
|
||||
micBtn.addEventListener('mouseup', stopRecording);
|
||||
micBtn.addEventListener('mouseleave', stopRecording);
|
||||
micBtn.addEventListener('touchstart', function(e) {
|
||||
e.preventDefault();
|
||||
startRecording();
|
||||
});
|
||||
micBtn.addEventListener('touchend', function(e) {
|
||||
e.preventDefault();
|
||||
stopRecording();
|
||||
});
|
||||
|
||||
// 初始化加载最近事项
|
||||
loadRecentRecords();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
38
target/classes/templates/login.html
Normal file
38
target/classes/templates/login.html
Normal file
@ -0,0 +1,38 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>登录 - TraceXD</title>
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
</head>
|
||||
<body class="login-body">
|
||||
<div class="login-container">
|
||||
<h1 class="login-title">TraceXD</h1>
|
||||
<p class="login-subtitle">日常事项记录系统</p>
|
||||
<form class="login-form" method="post" th:action="@{/login}">
|
||||
<div th:if="${param.error}" class="login-error">
|
||||
用户名或密码错误
|
||||
</div>
|
||||
<div th:if="${param.logout}" class="login-info">
|
||||
您已退出登录
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="username">用户名</label>
|
||||
<input type="text" id="username" name="username" required autofocus>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">密码</label>
|
||||
<input type="password" id="password" name="password" required>
|
||||
</div>
|
||||
<div class="form-remember">
|
||||
<label>
|
||||
<input type="checkbox" name="remember-me" checked> 记住我(30天免登录)
|
||||
</label>
|
||||
</div>
|
||||
<button type="submit" class="login-btn">登 录</button>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@ -0,0 +1,30 @@
|
||||
com\tracexd\model\dto\VoiceProcessResult.class
|
||||
com\tracexd\common\Result.class
|
||||
com\tracexd\mapper\SysUserMapper.class
|
||||
com\tracexd\mapper\DictTagMapper.class
|
||||
com\tracexd\service\ai\AiService$IntentResult.class
|
||||
com\tracexd\config\MybatisPlusConfig.class
|
||||
com\tracexd\enums\IntentType.class
|
||||
com\tracexd\common\exception\GlobalExceptionHandler.class
|
||||
com\tracexd\model\entity\SysUser.class
|
||||
com\tracexd\service\ai\AiService.class
|
||||
com\tracexd\controller\RecordController.class
|
||||
com\tracexd\common\exception\BusinessException.class
|
||||
com\tracexd\common\VoiceTaskManager.class
|
||||
com\tracexd\TracexdApplication.class
|
||||
com\tracexd\service\record\RecordService.class
|
||||
com\tracexd\model\dto\RecordQueryDTO.class
|
||||
com\tracexd\config\AsyncConfig.class
|
||||
com\tracexd\controller\PageController.class
|
||||
com\tracexd\config\SecurityConfig.class
|
||||
com\tracexd\service\record\DictTagService.class
|
||||
com\tracexd\service\voice\VoiceProcessService.class
|
||||
com\tracexd\service\voice\VoiceProcessService$1.class
|
||||
com\tracexd\service\asr\AsrService.class
|
||||
com\tracexd\model\entity\Record.class
|
||||
com\tracexd\service\sql\SqlValidator.class
|
||||
com\tracexd\config\RestClientConfig.class
|
||||
com\tracexd\controller\VoiceController.class
|
||||
com\tracexd\model\entity\DictTag.class
|
||||
com\tracexd\mapper\RecordMapper.class
|
||||
com\tracexd\enums\DictTypeEnum.class
|
||||
@ -0,0 +1,28 @@
|
||||
C:\Work\tracexd\src\main\java\com\tracexd\common\exception\BusinessException.java
|
||||
C:\Work\tracexd\src\main\java\com\tracexd\common\exception\GlobalExceptionHandler.java
|
||||
C:\Work\tracexd\src\main\java\com\tracexd\common\Result.java
|
||||
C:\Work\tracexd\src\main\java\com\tracexd\common\VoiceTaskManager.java
|
||||
C:\Work\tracexd\src\main\java\com\tracexd\config\AsyncConfig.java
|
||||
C:\Work\tracexd\src\main\java\com\tracexd\config\MybatisPlusConfig.java
|
||||
C:\Work\tracexd\src\main\java\com\tracexd\config\RestClientConfig.java
|
||||
C:\Work\tracexd\src\main\java\com\tracexd\config\SecurityConfig.java
|
||||
C:\Work\tracexd\src\main\java\com\tracexd\controller\PageController.java
|
||||
C:\Work\tracexd\src\main\java\com\tracexd\controller\RecordController.java
|
||||
C:\Work\tracexd\src\main\java\com\tracexd\controller\VoiceController.java
|
||||
C:\Work\tracexd\src\main\java\com\tracexd\enums\DictTypeEnum.java
|
||||
C:\Work\tracexd\src\main\java\com\tracexd\enums\IntentType.java
|
||||
C:\Work\tracexd\src\main\java\com\tracexd\mapper\DictTagMapper.java
|
||||
C:\Work\tracexd\src\main\java\com\tracexd\mapper\RecordMapper.java
|
||||
C:\Work\tracexd\src\main\java\com\tracexd\mapper\SysUserMapper.java
|
||||
C:\Work\tracexd\src\main\java\com\tracexd\model\dto\RecordQueryDTO.java
|
||||
C:\Work\tracexd\src\main\java\com\tracexd\model\dto\VoiceProcessResult.java
|
||||
C:\Work\tracexd\src\main\java\com\tracexd\model\entity\DictTag.java
|
||||
C:\Work\tracexd\src\main\java\com\tracexd\model\entity\Record.java
|
||||
C:\Work\tracexd\src\main\java\com\tracexd\model\entity\SysUser.java
|
||||
C:\Work\tracexd\src\main\java\com\tracexd\service\ai\AiService.java
|
||||
C:\Work\tracexd\src\main\java\com\tracexd\service\asr\AsrService.java
|
||||
C:\Work\tracexd\src\main\java\com\tracexd\service\record\DictTagService.java
|
||||
C:\Work\tracexd\src\main\java\com\tracexd\service\record\RecordService.java
|
||||
C:\Work\tracexd\src\main\java\com\tracexd\service\sql\SqlValidator.java
|
||||
C:\Work\tracexd\src\main\java\com\tracexd\service\voice\VoiceProcessService.java
|
||||
C:\Work\tracexd\src\main\java\com\tracexd\TracexdApplication.java
|
||||
@ -0,0 +1 @@
|
||||
com\tracexd\TracexdApplicationTests.class
|
||||
@ -0,0 +1 @@
|
||||
C:\Work\tracexd\src\test\java\com\tracexd\TracexdApplicationTests.java
|
||||
BIN
target/test-classes/com/tracexd/TracexdApplicationTests.class
Normal file
BIN
target/test-classes/com/tracexd/TracexdApplicationTests.class
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user