diff --git a/pom.xml b/pom.xml index b9aa60d..c34b79d 100644 --- a/pom.xml +++ b/pom.xml @@ -98,6 +98,13 @@ provided + + + cn.dev33 + sa-token-solon-plugin + 1.44.0 + + org.noear solon-test diff --git a/src/main/java/com/yxtech/ossyn/config/BizFileMappingConfig.java b/src/main/java/com/yxtech/ossyn/config/BizFileMappingConfig.java deleted file mode 100644 index ca3e099..0000000 --- a/src/main/java/com/yxtech/ossyn/config/BizFileMappingConfig.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.yxtech.ossyn.config; - -import lombok.Data; -import org.noear.solon.annotation.Configuration; -import org.noear.solon.annotation.Inject; - -import java.util.List; - -/** - * 业务文件映射配置 - * 对应 app.yml 中的 biz-file-mappings 节点 - */ -@Data -@Configuration -public class BizFileMappingConfig { - - /** 业务表映射列表 */ - @Inject("${biz-file-mappings}") - private List mappings; - - /** - * 单条业务表映射配置 - */ - @Data - public static class BizFileMapping { - /** 数据库名 */ - private String dbName; - /** 业务表名 */ - private String tableName; - /** 主键列名 */ - private String idCol; - /** 文件路径列名 */ - private String fileCol; - } -} diff --git a/src/main/java/com/yxtech/ossyn/config/SaTokenConfig.java b/src/main/java/com/yxtech/ossyn/config/SaTokenConfig.java new file mode 100644 index 0000000..91dcb55 --- /dev/null +++ b/src/main/java/com/yxtech/ossyn/config/SaTokenConfig.java @@ -0,0 +1,72 @@ +package com.yxtech.ossyn.config; + +import cn.dev33.satoken.context.SaHolder; +import cn.dev33.satoken.router.SaHttpMethod; +import cn.dev33.satoken.router.SaRouter; +import cn.dev33.satoken.solon.integration.SaTokenInterceptor; +import cn.dev33.satoken.stp.StpUtil; +import lombok.extern.slf4j.Slf4j; +import org.noear.solon.annotation.Bean; +import org.noear.solon.annotation.Configuration; +import org.noear.solon.annotation.Inject; +import org.noear.solon.core.handle.Filter; + +/** + * Sa-Token 鉴权配置 + * 拦截所有请求,放行登录页、登录接口、静态资源 + * 未登录时重定向到 /auth/login + */ +@Slf4j +@Configuration +public class SaTokenConfig { + + @Inject("${server.contextPath:}") + private String basePath; + + @Bean(index = -100) + public SaTokenInterceptor saTokenInterceptor() { + return new SaTokenInterceptor() + .addInclude("/**") + .addExclude("/favicon.ico") + .setAuth(obj -> { + // 放行登录相关路由,其余均需登录 + SaRouter.match("/**") + .notMatch(basePath + "/auth/login") + .notMatch(basePath + "/auth/doLogin") + .check(r -> StpUtil.checkLogin()); + }) + .setBeforeAuth(obj -> { + SaHolder.getResponse() + .setHeader("Access-Control-Allow-Origin", "*") + .setHeader("Access-Control-Allow-Methods", "*") + .setHeader("Access-Control-Allow-Headers", "*") + .setHeader("Access-Control-Max-Age", "3600"); + SaRouter.match(SaHttpMethod.OPTIONS).back(); + }) + .setError(e -> { + // 拦截器抛出异常由下方 Filter 处理重定向,此处仅记录日志 + log.warn("鉴权失败 [{}]: {}", SaHolder.getRequest().getUrl(), e.getMessage()); + return null; + }); + } + + /** + * 未登录时重定向到登录页(order=-90,在 SaTokenInterceptor 之后执行) + */ + @Bean(index = -90) + public Filter authRedirectFilter() { + return (ctx, chain) -> { + String path = ctx.path(); + // 放行登录相关路由和静态资源 + if (path.startsWith(basePath + "/auth/") || path.startsWith("/static/")) { + chain.doFilter(ctx); + return; + } + if (!StpUtil.isLogin()) { + ctx.redirect(basePath + "/auth/login"); + return; + } + chain.doFilter(ctx); + }; + } +} diff --git a/src/main/java/com/yxtech/ossyn/controller/AuthController.java b/src/main/java/com/yxtech/ossyn/controller/AuthController.java new file mode 100644 index 0000000..fffdf7b --- /dev/null +++ b/src/main/java/com/yxtech/ossyn/controller/AuthController.java @@ -0,0 +1,75 @@ +package com.yxtech.ossyn.controller; + +import cn.dev33.satoken.stp.StpUtil; +import lombok.extern.slf4j.Slf4j; +import org.noear.solon.annotation.*; +import org.noear.solon.core.handle.Context; +import org.noear.solon.core.handle.ModelAndView; + +import java.io.IOException; + +/** + * 登录/登出控制器 + * 凭证来源:app.yml sa-token.http-basic(格式:用户名:密码) + */ +@Slf4j +@Controller +@Mapping("/auth") +public class AuthController { + + /** HTTP Basic 凭证,格式:用户名:密码 */ + @Inject("${sa-token.http-basic}") + private String httpBasic; + + @Inject("${server.contextPath:}") + private String basePath; + + /** + * 显示登录页 + */ + @Get + @Mapping("/login") + public Object loginPage(@Param(defaultValue = "") String error, Context ctx) throws IOException { + if (StpUtil.isLogin()) { + ctx.redirect(basePath + "/oss-sync/"); + return null; + } + ModelAndView mv = new ModelAndView("login.ftl"); + mv.put("basePath", basePath); + mv.put("errorMsg", "1".equals(error) ? "用户名或密码错误,请重试" : ""); + return mv; + } + + /** + * 处理登录表单提交 + */ + @Post + @Mapping("/doLogin") + public void doLogin( + @Param(defaultValue = "") String username, + @Param(defaultValue = "") String password, + Context ctx) throws IOException { + String[] parts = httpBasic.split(":", 2); + String cfgUser = parts.length > 0 ? parts[0] : ""; + String cfgPass = parts.length > 1 ? parts[1] : ""; + + if (cfgUser.equals(username.trim()) && cfgPass.equals(password)) { + StpUtil.login(1); + log.info("[Auth] 登录成功,用户: {}", username); + ctx.redirect(basePath + "/oss-sync/"); + } else { + log.warn("[Auth] 登录失败,用户名: {}", username); + ctx.redirect(basePath + "/auth/login?error=1"); + } + } + + /** + * 退出登录 + */ + @Get + @Mapping("/logout") + public void logout(Context ctx) throws IOException { + StpUtil.logout(); + ctx.redirect(basePath + "/auth/login"); + } +} diff --git a/src/main/resources/app.yml b/src/main/resources/app.yml index 3d692f4..b8e1320 100644 --- a/src/main/resources/app.yml +++ b/src/main/resources/app.yml @@ -49,20 +49,6 @@ biz-datasource: password: Aa135790123 params: "characterEncoding=UTF-8&useUnicode=true&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai" -# ===================================== -# 业务表映射配置 -# 格式:数据库名 业务表名 业务表ID列名 业务文件路径列名 -# ===================================== -biz-file-mappings: -# - dbName: dmz_health_system -# tableName: sys_user -# idCol: id -# fileCol: avatar - - dbName: dmz_health_consult - tableName: con_resource - idCol: id - fileCol: img - # ===================================== # 本地文件根路径(业务表中存储的是相对路径,拼接此前缀查找文件) # ===================================== @@ -80,3 +66,24 @@ xj-oss: config-code: FILE-OSS-0a85c4dd40ca745a598b872c7974bcaca directory: scyx/scfz/aygc/jkgl/ file-type-white-list: dat,svg,dwg,pr,pptm,ai,bak,pptx,lic,db,caj,wps,xmind,eps,exe,doc,docx,xls,xlsx,ppt,pps,pdf,xml,webp,MOV,MP4,HEVC,wmv,rmvb,rm,odt,3gp,swf,gz,tgz,log,bz,bz2,tbz,zip,rar,tar,7z,mp3,mid,ogg,mpga,mp4a,wav,wma,avi,dv,mp4,bmp,xlsm,dps,bak,cdr,et,kdh,nh,,nh,mpeg,mpg,mov,wm,flv,mkv,bmp,jpg,jpeg,gif,png,tif,tiff,tga,psd,txt,php,html,htm,js,css,rtf,rtfd,py,java,rb,sh,pl,sql,xml,gdb,gdbx + +# ===================================== +# Sa-Token 鉴权配置 +# ===================================== +sa-token: + # token 名称(同时也是 cookie 名称) + token-name: oss-token + # token 有效期(秒),-1 代表永不过期 + timeout: 86400 + # 无操作超时时间(秒),-1 代表不限制 + activity-timeout: -1 + # 允许同一账号并发登录 + allow-concurrent-login: true + # 多人登录共用同一 token + is-share: true + # token 风格 + token-style: uuid + # 关闭操作日志 + is-log: false + # 登录凭证(格式:用户名:密码) + http-basic: "admin:mD8C-gR8UgRE8fNvJWgmjg" diff --git a/src/main/resources/templates/index.ftl b/src/main/resources/templates/index.ftl index 104b3fe..39f8e13 100644 --- a/src/main/resources/templates/index.ftl +++ b/src/main/resources/templates/index.ftl @@ -105,7 +105,10 @@ -
OSS 文件同步管理
+
+ OSS 文件同步管理 + 退出登录 +
diff --git a/src/main/resources/templates/login.ftl b/src/main/resources/templates/login.ftl new file mode 100644 index 0000000..791874b --- /dev/null +++ b/src/main/resources/templates/login.ftl @@ -0,0 +1,89 @@ + + + + + + 登录 - OSS 文件同步管理 + + + + + +