diff --git a/mms-admin/pom.xml b/mms-admin/pom.xml index 50bf947..866b61d 100644 --- a/mms-admin/pom.xml +++ b/mms-admin/pom.xml @@ -62,6 +62,16 @@ com.sxpcwlkj mms-ai + + com.sxpcwlkj + mms-doc + + + com.sxpcwlkj + ms-framework + + + diff --git a/mms-admin/src/main/resources/application.yml b/mms-admin/src/main/resources/application.yml index 10b0209..75dca87 100644 --- a/mms-admin/src/main/resources/application.yml +++ b/mms-admin/src/main/resources/application.yml @@ -79,6 +79,32 @@ spring: # 允许对象忽略json中不存在的属性 fail_on_unknown_properties: false + thymeleaf: + enabled: true # 是否启用 Thymeleaf(默认 true) + check-template: true # 是否检查模板是否存在(开发建议开启) + check-template-location: true # 是否检查模板路径有效性(默认 true) + template-resolver-order: 1 # 模板解析器优先级(数值越小优先级越高) + cache: false # 开发时关闭模板缓存 + encoding: UTF-8 # 模板文件编码 + mode: HTML # 模板模式(默认 HTML) + prefix: classpath:/template/html/ # 模板路径(默认值) + suffix: .html # 模板后缀(默认值) + servlet: + content-type: text/html # 设置响应的 Content-Type(默认 text/html) + static-resource-url: 'https://cdn.jsdelivr.net' # 注入静态资源 CDN 前缀(需在配置文件中定义) + + web: + # 排除路径 + excludes: + # 放行 + - /static/**, # 静态资源目录 + - /public/**, # 公共资源目录 + - /resources/**, # 默认资源路径 + - /favicon.ico, # 网站图标 + - /error # 错误页面 + - /.well-known/** + + --- # Sa-Token配置 sa-token: # token名称 (同时也是cookie名称) diff --git a/mms-admin/src/main/resources/static/index.css b/mms-admin/src/main/resources/static/css/ai.css similarity index 100% rename from mms-admin/src/main/resources/static/index.css rename to mms-admin/src/main/resources/static/css/ai.css diff --git a/mms-admin/src/main/resources/static/css/style.css b/mms-admin/src/main/resources/static/css/style.css new file mode 100644 index 0000000..e69de29 diff --git a/mms-admin/src/main/resources/static/js/app.js b/mms-admin/src/main/resources/static/js/app.js new file mode 100644 index 0000000..e69de29 diff --git a/mms-admin/src/main/resources/static/doc.html b/mms-admin/src/main/resources/template/html/ai/doc.html similarity index 100% rename from mms-admin/src/main/resources/static/doc.html rename to mms-admin/src/main/resources/template/html/ai/doc.html diff --git a/mms-admin/src/main/resources/template/html/ai/index.css b/mms-admin/src/main/resources/template/html/ai/index.css new file mode 100644 index 0000000..f64314f --- /dev/null +++ b/mms-admin/src/main/resources/template/html/ai/index.css @@ -0,0 +1,427 @@ +/* 基础变量 */ +:root { + --primary: #4a90e2; + --primary-dark: #357abd; + --secondary: #7c4dff; + --background: #f5f7fb; + --text: #2c3e50; + --border: #e0e0e0; + --shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + --radius: 12px; + --transition: all 0.3s ease; +} + +/* 全局基础样式 */ +* { + box-sizing: border-box; + margin: 0; + padding: 0; + font-family: 'Segoe UI', system-ui, sans-serif; +} + +body { + background: linear-gradient(135deg, #f5f7fb 0%, #e6e9ff 100%); + min-height: 100vh; + padding: 2rem; +} + +/* 容器布局 */ +.container { + display: flex; + max-width: 100vw; + margin: 0 auto; + background: rgba(255, 255, 255, 0.95); + border-radius: var(--radius); + box-shadow: var(--shadow); + backdrop-filter: blur(10px); + overflow: hidden; + height: calc(100vh - 4rem); +} + +.header { + padding: 0.5rem; + background: linear-gradient(135deg, var(--primary), var(--secondary)); + color: white; + text-align: center; + position: relative; + cursor: pointer; +} + +.header h1 { + font-size: 1.5rem; + margin-bottom: 1rem; + display: flex; + align-items: center; + justify-content: center; + gap: 1rem; +} + +/* 侧边栏模块 */ +.sidebar { + width: 300px; + background: white; + border-right: 1px solid var(--border); + transition: transform 0.3s ease; + height: calc(100vh - 4rem); + overflow-y: auto; + + &.collapsed { + transform: translateX(-100%); + position: absolute; + } +} + +.sidebar-header { + padding: 1.5rem; + border-bottom: 1px solid var(--border); + display: flex; + justify-content: space-between; + align-items: center; +} + +/* 主内容区域 */ +.main-content { + flex: 1; + display: flex; + flex-direction: column; +} +.chat-container { + overflow-y: auto; + padding: 2rem; + background: rgba(245, 247, 251, 0.5); + scroll-behavior: smooth; + height: calc(70vh); +} +/* 消息相关样式 */ +.message { + position: relative; + max-width: 80%; + padding: 1.5rem 2rem 1.5rem 1.5rem; + margin: 1rem 0; + border-radius: var(--radius); + animation: messageAppear 0.3s ease-out; + transition: var(--transition); + line-height: 1.6; + + &.bot-message { + background: white; + color: var(--text); + box-shadow: var(--shadow); + border: 1px solid var(--border); + padding: 15px 30px; + } + + &.user-message { + background: var(--primary); + color: white; + margin-left: auto; + } + + &:hover .copy-btn { + opacity: 1; + } +} + +/* 复制按钮模块 */ +.copy-btn { + position: absolute; + top: 12px; + right: 12px; + padding: 6px 10px; + background: rgba(255, 255, 255, 0.95); + border: 1px solid var(--border); + border-radius: 8px; + cursor: pointer; + opacity: 0; + transition: var(--transition); + display: flex; + align-items: center; + gap: 4px; + font-size: 0.8rem; + color: var(--text); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + + &:hover { + background: var(--primary); + color: white; + border-color: var(--primary); + } +} + +/* 输入区域公共样式 */ +.input-section, .upload-area, .file-card { + background: rgba(245, 247, 251, 0.5); + border-radius: var(--radius); + transition: var(--transition); +} + +.input-section { + padding: 2rem; + background: white; + border-top: 2px solid var(--border); +} + +.upload-area { + border: 2px dashed var(--border); + padding: 0 4px; + text-align: center; + cursor: pointer; + line-height: 45px; + + + &:hover, &.dragover { + border-color: var(--primary); + background: rgba(74, 144, 226, 0.05); + } + + &.dragover { + background: rgba(74, 144, 226, 0.1); + transform: scale(1.02); + } +} + +/* 文件预览相关 */ +.file-preview { + display: flex; + gap: 1rem; + flex-wrap: wrap; + margin-top: 1rem; + background: rgba(245, 247, 251, 0.5); + border-radius: var(--radius); + transition: var(--transition); +} + +.file-card { + padding: 1rem; + box-shadow: var(--shadow); + display: flex; + align-items: center; + gap: 1rem; + position: relative; + + &:hover { + transform: translateY(-2px); + } +} + +.remove-file { + position: absolute; + top: -8px; + right: -8px; + width: 24px; + height: 24px; + background: #ff4757; + color: white; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + font-size: 14px; +} + +/* 表单元素 */ +.input-group { + display: flex; + gap: 1rem; + width: 100%; +} +.update-tool{ + position: absolute; + display: flex; + gap: 1rem; + width: 100%; + bottom: 100px; +} + +#userInput { + flex: 1; + padding: 1rem; + border: 2px solid var(--border); + border-radius: var(--radius); + font-size: 1rem; + transition: var(--transition); + height: 50px; + &:focus { + border-color: var(--primary); + outline: none; + box-shadow: 0 0 0 3px rgba(74, 144, 226, 0.2); + } +} +.action-buttons { + display: flex; + gap: 0.5rem; +} +.tool-box{ + display: flex; + margin-top: 10px; + position: relative; +} +.tool-left{ + flex: 1; + display: flex; + flex-direction: column; + /*gap: 1.5rem;*/ + align-items: center; +} +/* 按钮公共样式 */ +button { + display: flex; + justify-content: center; + align-items: center; + padding: 0.8rem 1.5rem; + border: none; + border-radius: 8px; + background: var(--primary); + color: white; + cursor: pointer; + gap: 0.5rem; + transition: var(--transition); + + &:hover { + background: var(--primary-dark); + transform: translateY(-1px); + box-shadow: var(--shadow); + } +} + +#newChatBtn { + background: var(--secondary); + margin-left: 1rem; +} +#stopBtn { + background: #ff4757; + display: none; +} + +/* 加载动画 */ +.loading { + display: none; + text-align: center; + padding: 1rem; + position: fixed; + left: 50%; /* 水平定位到视口中间 */ + top: 50%; /* 垂直定位到视口中间 */ + transform: translate(-50%, -50%); /* 通过自身宽高回退50% */ +} + +.loading-wave { + display: flex; + justify-content: center; + gap: 4px; + height: 50px; +} + +.loading-wave div { + width: 8px; + height: 100%; + background: var(--primary); + animation: wave 1.2s ease-in-out infinite; +} + +@keyframes wave { + 0%, 100% { transform: scaleY(0.4) } + 50% { transform: scaleY(1) } +} + +/* 历史会话 */ +.history-list { + padding: 1rem; +} + +.session-item { + padding: 1rem; + margin: 0.5rem 0; + border-radius: 8px; + cursor: pointer; + display: flex; + flex-direction: column; + gap: 0.5rem; + transition: var(--transition); + background: rgba(245, 247, 251, 0.5); + + &:hover, &.active { + background: var(--primary); + color: white; + } +} + +/* 响应式设计 */ +@media (max-width: 768px) { + :root { + --radius: 12px; + } + + body { + padding: 0; + } + + .container { + height: calc(100vh - 2rem); + } + + .sidebar { + width: 85%; + position: fixed; + box-shadow: 4px 0 20px rgba(0, 0, 0, 0.1); + display: none; + } + + .message { + max-width: 90%; + padding: 1rem; + font-size: 0.95rem; + } + + .input-section { + padding: 1rem; + } + + .input-group { + gap: 0.5rem; + } + + button span { + display: none; + } + + #userInput { + padding: 0.8rem; + } + + .copy-btn { + opacity: 1; + padding: 5px; + .copy-text { display: none; } + } +} + +/* 动画定义 */ +@keyframes messageAppear { + from { opacity: 0; transform: translateY(20px); } + to { opacity: 1; transform: translateY(0); } +} + +@keyframes wave { + 0%, 100% { transform: scaleY(0.4); } + 50% { transform: scaleY(1); } +} + +/* 辅助类 */ +.copy-toast { + position: fixed; + bottom: 20px; + left: 50%; + transform: translateX(-50%); + background: var(--primary); + color: white; + padding: 8px 16px; + border-radius: var(--radius); + opacity: 0; + transition: opacity 0.3s; + pointer-events: none; + + &.show { opacity: 1; } +} + +.message-content { overflow-wrap: break-word; } diff --git a/mms-admin/src/main/resources/static/index.html b/mms-admin/src/main/resources/template/html/ai/index.html similarity index 96% rename from mms-admin/src/main/resources/static/index.html rename to mms-admin/src/main/resources/template/html/ai/index.html index 612a3bb..b989e51 100644 --- a/mms-admin/src/main/resources/static/index.html +++ b/mms-admin/src/main/resources/template/html/ai/index.html @@ -1,17 +1,17 @@ - + 智能对话助手 | AI Chat - - - - - - + + + + + +
diff --git a/mms-admin/src/main/resources/template/html/favicon.ico b/mms-admin/src/main/resources/template/html/favicon.ico new file mode 100644 index 0000000..3baea80 Binary files /dev/null and b/mms-admin/src/main/resources/template/html/favicon.ico differ diff --git a/mms-admin/src/main/resources/template/html/holle.html b/mms-admin/src/main/resources/template/html/holle.html new file mode 100644 index 0000000..04ac247 --- /dev/null +++ b/mms-admin/src/main/resources/template/html/holle.html @@ -0,0 +1,10 @@ + + + + + Thymeleaf Demo + + +

默认文本(当Thymeleaf未渲染时显示)

+ + diff --git a/mms-modules/mms-common/src/main/java/com/sxpcwlkj/common/properties/WebThymeleafProperties.java b/mms-modules/mms-common/src/main/java/com/sxpcwlkj/common/properties/WebThymeleafProperties.java new file mode 100644 index 0000000..1f4b866 --- /dev/null +++ b/mms-modules/mms-common/src/main/java/com/sxpcwlkj/common/properties/WebThymeleafProperties.java @@ -0,0 +1,20 @@ +package com.sxpcwlkj.common.properties; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * @author shanpengnian + */ +@Data +@Component +@ConfigurationProperties(prefix = "spring.web") +public class WebThymeleafProperties { + + private Boolean isOpen; + /** + * 排除路径 + */ + private String[] excludes; +} diff --git a/mms-modules/mms-framework/pom.xml b/mms-modules/mms-framework/pom.xml index 1a009e7..c54f946 100644 --- a/mms-modules/mms-framework/pom.xml +++ b/mms-modules/mms-framework/pom.xml @@ -106,6 +106,12 @@ com.sxpcwlkj mms-common + + + + org.springframework.boot + spring-boot-starter-thymeleaf + diff --git a/mms-modules/mms-framework/src/main/java/com/sxpcwlkj/framework/controller/FrameworkController.java b/mms-modules/mms-framework/src/main/java/com/sxpcwlkj/framework/controller/FrameworkController.java index 610bfaa..2371b8a 100644 --- a/mms-modules/mms-framework/src/main/java/com/sxpcwlkj/framework/controller/FrameworkController.java +++ b/mms-modules/mms-framework/src/main/java/com/sxpcwlkj/framework/controller/FrameworkController.java @@ -1,11 +1,14 @@ package com.sxpcwlkj.framework.controller; import cn.dev33.satoken.annotation.SaIgnore; +import cn.hutool.core.lang.Console; import com.sxpcwlkj.common.annotation.RateLimit; import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; /** @@ -13,7 +16,7 @@ import org.springframework.web.bind.annotation.RestController; * * @author shanpengnian */ -@RestController +@Controller @RequiredArgsConstructor public class FrameworkController { @@ -35,15 +38,23 @@ public class FrameworkController { * * @return 系统信息 */ -// @RateLimit(permitsPerSecond = 1.0) // 每秒允许 1 个请求 -// @SaIgnore -// @GetMapping("/") -// public String index() { -// return "Hello " + name + "! V" + version; -// } + @RateLimit(permitsPerSecond = 1.0) // 每秒允许 1 个请求 + @SaIgnore + @GetMapping("/") + @ResponseBody + public String index() { + return "Hello " + name + "! V" + version; + } + + @RateLimit(permitsPerSecond = 1.0) // 每秒允许 1 个请求 + @SaIgnore + @GetMapping("/ai") + public String ai() { + return "ai/index"; + } @PostConstruct public void init() { - + Console.log("========== Hello" + name + "! V" + version+" =========="); } } diff --git a/mms-modules/mms-framework/src/main/java/com/sxpcwlkj/framework/controller/GlobalModelAttributes.java b/mms-modules/mms-framework/src/main/java/com/sxpcwlkj/framework/controller/GlobalModelAttributes.java new file mode 100644 index 0000000..3c4a484 --- /dev/null +++ b/mms-modules/mms-framework/src/main/java/com/sxpcwlkj/framework/controller/GlobalModelAttributes.java @@ -0,0 +1,30 @@ +package com.sxpcwlkj.framework.controller; + +import jakarta.servlet.ServletContext; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ModelAttribute; + +@ControllerAdvice +@RequiredArgsConstructor +public class GlobalModelAttributes { + + + private final ServletContext servletContext; + + // 注入上下文路径(如 /myapp) + @ModelAttribute("ctx") + public String contextPath() { + return servletContext.getContextPath(); + } + + // 可选:注入静态资源 CDN 前缀(需在配置文件中定义) + @Value("${spring.thymeleaf.static-resource-url:}") + private String staticResourceUrl; + + @ModelAttribute("staticCdn") + public String staticCdn() { + return staticResourceUrl; + } +} diff --git a/mms-modules/mms-framework/src/main/java/com/sxpcwlkj/framework/handler/WebMvcConfigurerHandler.java b/mms-modules/mms-framework/src/main/java/com/sxpcwlkj/framework/handler/WebMvcConfigurerHandler.java index 63c8474..621eeeb 100644 --- a/mms-modules/mms-framework/src/main/java/com/sxpcwlkj/framework/handler/WebMvcConfigurerHandler.java +++ b/mms-modules/mms-framework/src/main/java/com/sxpcwlkj/framework/handler/WebMvcConfigurerHandler.java @@ -1,7 +1,10 @@ package com.sxpcwlkj.framework.handler; +import com.sxpcwlkj.common.properties.WebThymeleafProperties; import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.autoconfigure.web.WebProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.cors.CorsConfiguration; @@ -16,12 +19,19 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; * 跨域请求配置 */ @Configuration +@RequiredArgsConstructor public class WebMvcConfigurerHandler implements WebMvcConfigurer { + + private final WebThymeleafProperties webThymeleafProperties; @Override public void addInterceptors(InterceptorRegistry registry) { // 全局访问性能拦截 - registry.addInterceptor(new WebHandlerInterceptorHandler()); + registry.addInterceptor(new WebHandlerInterceptorHandler()) + // 拦截所有路径 + .addPathPatterns("/**") + .excludePathPatterns(webThymeleafProperties.getExcludes()); + ; } @Override