feat: 新增 sa-token 登录鉴权 + 映射配置管理 + 清理接口
- 新增 sa-token 登录鉴权(admin/mD8C-gR8UgRE8fNvJWgmjg) - 新增 BizFileMappingEntity/Mapper/Controller,映射配置改为数据库管理 - 新增 bizPath 字段,支持每张表单独配置 OSS 业务路径 - 新增文件日志/任务历史清理接口 - 新增全局异常过滤器 - 删除 BizFileMappingConfig(yml 配置方式废弃) - 前端新增映射配置 Tab、登录页、退出登录
This commit is contained in:
@@ -98,6 +98,13 @@
|
|||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Sa-Token 鉴权 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.dev33</groupId>
|
||||||
|
<artifactId>sa-token-solon-plugin</artifactId>
|
||||||
|
<version>1.44.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.noear</groupId>
|
<groupId>org.noear</groupId>
|
||||||
<artifactId>solon-test</artifactId>
|
<artifactId>solon-test</artifactId>
|
||||||
|
|||||||
@@ -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<BizFileMapping> mappings;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 单条业务表映射配置
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
public static class BizFileMapping {
|
|
||||||
/** 数据库名 */
|
|
||||||
private String dbName;
|
|
||||||
/** 业务表名 */
|
|
||||||
private String tableName;
|
|
||||||
/** 主键列名 */
|
|
||||||
private String idCol;
|
|
||||||
/** 文件路径列名 */
|
|
||||||
private String fileCol;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
+21
-14
@@ -49,20 +49,6 @@ biz-datasource:
|
|||||||
password: Aa135790123
|
password: Aa135790123
|
||||||
params: "characterEncoding=UTF-8&useUnicode=true&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai"
|
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
|
config-code: FILE-OSS-0a85c4dd40ca745a598b872c7974bcaca
|
||||||
directory: scyx/scfz/aygc/jkgl/
|
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
|
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"
|
||||||
|
|||||||
@@ -105,7 +105,10 @@
|
|||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="header">OSS 文件同步管理</div>
|
<div class="header" style="display:flex;align-items:center;justify-content:space-between;">
|
||||||
|
<span>OSS 文件同步管理</span>
|
||||||
|
<a href="/auth/logout" style="color:rgba(255,255,255,.8);font-size:13px;text-decoration:none;" onmouseover="this.style.color='#fff'" onmouseout="this.style.color='rgba(255,255,255,.8)'">退出登录</a>
|
||||||
|
</div>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
|
||||||
<!-- Tab 导航 -->
|
<!-- Tab 导航 -->
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>登录 - OSS 文件同步管理</title>
|
||||||
|
<style>
|
||||||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Microsoft YaHei", sans-serif;
|
||||||
|
background: #f0f2f5;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
.login-wrapper { width: 380px; }
|
||||||
|
.login-title {
|
||||||
|
text-align: center;
|
||||||
|
color: #1677ff;
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
.login-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
|
||||||
|
padding: 36px 40px;
|
||||||
|
}
|
||||||
|
.form-group { margin-bottom: 18px; }
|
||||||
|
.form-group label { display: block; font-size: 13px; color: #666; margin-bottom: 6px; }
|
||||||
|
.form-group input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 13px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 15px;
|
||||||
|
outline: none;
|
||||||
|
transition: border-color 0.2s;
|
||||||
|
}
|
||||||
|
.form-group input:focus { border-color: #1677ff; }
|
||||||
|
.btn-login {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
background: #1677ff;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 15px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-top: 6px;
|
||||||
|
transition: background 0.2s;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
}
|
||||||
|
.btn-login:hover { background: #4096ff; }
|
||||||
|
.error-msg {
|
||||||
|
background: #fff0f0;
|
||||||
|
border: 1px solid #ffcccc;
|
||||||
|
color: #d32f2f;
|
||||||
|
padding: 10px 14px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="login-wrapper">
|
||||||
|
<div class="login-title">OSS 文件同步管理</div>
|
||||||
|
<div class="login-card">
|
||||||
|
<#if errorMsg?has_content>
|
||||||
|
<div class="error-msg">${errorMsg}</div>
|
||||||
|
</#if>
|
||||||
|
<form method="post" action="${basePath}/auth/doLogin">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>用户名</label>
|
||||||
|
<input type="text" name="username" placeholder="请输入用户名" autofocus required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>密码</label>
|
||||||
|
<input type="password" name="password" placeholder="请输入密码" required>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn-login">登 录</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user