Compare commits
No commits in common. "fastapi" and "master" have entirely different histories.
38
.gitignore
vendored
Normal file
38
.gitignore
vendored
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
target/
|
||||||
|
!.mvn/wrapper/maven-wrapper.jar
|
||||||
|
!**/src/main/**/target/
|
||||||
|
!**/src/test/**/target/
|
||||||
|
|
||||||
|
### IntelliJ IDEA ###
|
||||||
|
.idea/modules.xml
|
||||||
|
.idea/jarRepositories.xml
|
||||||
|
.idea/compiler.xml
|
||||||
|
.idea/libraries/
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
|
||||||
|
### Eclipse ###
|
||||||
|
.apt_generated
|
||||||
|
.classpath
|
||||||
|
.factorypath
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
.springBeans
|
||||||
|
.sts4-cache
|
||||||
|
|
||||||
|
### NetBeans ###
|
||||||
|
/nbproject/private/
|
||||||
|
/nbbuild/
|
||||||
|
/dist/
|
||||||
|
/nbdist/
|
||||||
|
/.nb-gradle/
|
||||||
|
build/
|
||||||
|
!**/src/main/**/build/
|
||||||
|
!**/src/test/**/build/
|
||||||
|
|
||||||
|
### VS Code ###
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
### Mac OS ###
|
||||||
|
.DS_Store
|
@ -1,24 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
from dataclasses import dataclass, field
|
|
||||||
from typing import List, Dict
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class Upload:
|
|
||||||
img_quality: float = 1.0 # 默认值为 1.0f
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class Config:
|
|
||||||
url: str = "http://www.linxyun.com" # 默认值
|
|
||||||
entCode: str = field(default=None, metadata={"required": True, "message": "The 'entCode' property is mandatory"})
|
|
||||||
project: str = field(default=None, metadata={"required": True, "message": "The 'project' property is mandatory"})
|
|
||||||
role: Dict[str, List[str]] = field(default_factory=dict)
|
|
||||||
white_list: List[str] = field(default_factory=list)
|
|
||||||
upload: Upload = field(default_factory=Upload)
|
|
||||||
|
|
||||||
def __post_init__(self):
|
|
||||||
# 进行字段校验,例如检查必填字段
|
|
||||||
if self.entCode is None:
|
|
||||||
raise ValueError("The 'entCode' property is mandatory")
|
|
||||||
if self.project is None:
|
|
||||||
raise ValueError("The 'project' property is mandatory")
|
|
@ -1,66 +0,0 @@
|
|||||||
import requests
|
|
||||||
from fastapi import FastAPI
|
|
||||||
from linxyun.utils.result import Result, SysCodes
|
|
||||||
from linxyun.utils.logger import get_logger
|
|
||||||
from linxyun.config import Config
|
|
||||||
from linxyun.sso.middleware.security import SecurityMiddleware
|
|
||||||
from expiringdict import ExpiringDict
|
|
||||||
import json
|
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
|
||||||
|
|
||||||
class Linxyun:
|
|
||||||
def __init__(self, config: Config):
|
|
||||||
logger.info(f"Linxyun init:{config}")
|
|
||||||
self.user_auth_dict = ExpiringDict(max_len=10000, max_age_seconds=60*60*24) # 24小时
|
|
||||||
self.config = config
|
|
||||||
|
|
||||||
def add_security_middleware(self, app: FastAPI):
|
|
||||||
app.add_middleware(SecurityMiddleware, linxyun=self)
|
|
||||||
|
|
||||||
def get_api_url(self, path: str) -> str:
|
|
||||||
# 基础 URL 和项目名称
|
|
||||||
base_url = f"{self.config.url}/{self.config.project}"
|
|
||||||
|
|
||||||
# 如果 path 为空,返回 base_url
|
|
||||||
if not path:
|
|
||||||
return base_url
|
|
||||||
|
|
||||||
# 如果 path 不以 / 开头,添加 /
|
|
||||||
if not path.startswith("/"):
|
|
||||||
path = "/" + path
|
|
||||||
|
|
||||||
# 如果 path 不以 .action 结尾,添加 .action
|
|
||||||
if not path.endswith(".action"):
|
|
||||||
path += ".action"
|
|
||||||
|
|
||||||
# 返回完整的 URL
|
|
||||||
return base_url + path
|
|
||||||
|
|
||||||
def user_login_auth(self, token: str) -> Result:
|
|
||||||
if not token:
|
|
||||||
return Result.error(SysCodes.USER_NOT_LOGIN)
|
|
||||||
url = self.get_api_url("userLoginAuth")
|
|
||||||
resp = requests.post(url, json={"LoginID": token})
|
|
||||||
if resp.status_code != 200:
|
|
||||||
return Result.error(SysCodes.REQUEST_FAILED)
|
|
||||||
json_resp = resp.json()
|
|
||||||
if json_resp.get("success") is False:
|
|
||||||
return Result.error(SysCodes.LOGIN_FAIL)
|
|
||||||
data = json_resp.get("data")
|
|
||||||
if not data:
|
|
||||||
return Result.error(SysCodes.USER_NOT_LOGIN)
|
|
||||||
data = json.loads(data)
|
|
||||||
logger.info(f"登录成功:{data}")
|
|
||||||
self.user_auth_dict[token] = data
|
|
||||||
return Result.ok(data)
|
|
||||||
|
|
||||||
|
|
||||||
def get_user_auth(self, token: str) -> Result:
|
|
||||||
if token in self.user_auth_dict:
|
|
||||||
return Result.ok(self.user_auth_dict[token])
|
|
||||||
return self.user_login_auth(token)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,77 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
from starlette.middleware.base import BaseHTTPMiddleware
|
|
||||||
import fnmatch
|
|
||||||
from starlette.responses import JSONResponse
|
|
||||||
|
|
||||||
from linxyun.utils.logger import get_logger
|
|
||||||
from linxyun.utils.result import Result, SysCodes
|
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
|
||||||
|
|
||||||
# 鉴权中间件
|
|
||||||
class SecurityMiddleware(BaseHTTPMiddleware):
|
|
||||||
def __init__(self, app, linxyun):
|
|
||||||
logger.info(f"添加鉴权中间件 SecurityMiddleware")
|
|
||||||
super().__init__(app)
|
|
||||||
self.linxyun = linxyun
|
|
||||||
self.pattern = re.compile("LoginID_\\d{14}_\\d{6}")
|
|
||||||
|
|
||||||
|
|
||||||
async def dispatch(self, request, call_next):
|
|
||||||
# 获取请求路径
|
|
||||||
path = request.url.path
|
|
||||||
method = request.method
|
|
||||||
logger.info(f"鉴权拦截: {method} {path}")
|
|
||||||
# 获取请求头 Token
|
|
||||||
token = request.headers.get("token")
|
|
||||||
if not token:
|
|
||||||
return JSONResponse(content=Result.error(SysCodes.USER_NOT_LOGIN).to_dict())
|
|
||||||
if token.startswith("Session"):
|
|
||||||
search_resp = self.pattern.search(token)
|
|
||||||
if search_resp:
|
|
||||||
token = search_resp.group()
|
|
||||||
user_auth_result:dict = self.linxyun.get_user_auth(token).to_dict()
|
|
||||||
if user_auth_result.get("success") is False:
|
|
||||||
logger.info(f"用户登录信息失效: {user_auth_result}")
|
|
||||||
return JSONResponse(content=user_auth_result)
|
|
||||||
user_auth = user_auth_result.get("data")
|
|
||||||
user_role = user_auth.get("UserRoles")
|
|
||||||
if user_role is None:
|
|
||||||
logger.info(f"用户角色为空: {user_auth}")
|
|
||||||
return JSONResponse(content=Result.error(SysCodes.USER_NO_AUTHORITY).to_dict())
|
|
||||||
if self.linxyun.config.entCode != user_auth.get("EntCode"):
|
|
||||||
logger.info(f"用户企业编码错误: {user_auth.get('EntCode')}")
|
|
||||||
return JSONResponse(content=Result.error(SysCodes.LOGIN_ERROR).to_dict())
|
|
||||||
role_map = self.linxyun.config.role
|
|
||||||
if user_role not in role_map:
|
|
||||||
logger.info(f"用户权限未在系统权限中: {user_role}")
|
|
||||||
return JSONResponse(content=Result.error(SysCodes.LOGIN_ERROR).to_dict())
|
|
||||||
# 是否有权限访问该路径
|
|
||||||
path_list = role_map.get(user_role)
|
|
||||||
if self.is_uri_authorized(path, path_list) is False:
|
|
||||||
logger.info(f"用户没有权限访问该路径: {path}")
|
|
||||||
return JSONResponse(content=Result.error(SysCodes.USER_NO_AUTHORITY).to_dict())
|
|
||||||
logger.info(f"鉴权通过: {path} {token}")
|
|
||||||
return await call_next(request)
|
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def is_uri_authorized(path: str, path_list: list[str]) -> bool:
|
|
||||||
# 遍历所有授权路径
|
|
||||||
for authorized_path in path_list:
|
|
||||||
# 如果授权路径包含 **,使用 fnmatch 模块进行通配符匹配
|
|
||||||
if authorized_path.endswith("/**"):
|
|
||||||
# 将 ** 转换为通配符 *,然后进行匹配
|
|
||||||
pattern = authorized_path.replace("/**", "/*")
|
|
||||||
if fnmatch.fnmatch(path, pattern):
|
|
||||||
return True
|
|
||||||
elif path == authorized_path:
|
|
||||||
# 完全匹配授权路径
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
|||||||
import logging
|
|
||||||
import os
|
|
||||||
from logging.handlers import RotatingFileHandler
|
|
||||||
|
|
||||||
# 创建日志目录(如果不存在)
|
|
||||||
LOG_DIR = "logs"
|
|
||||||
os.makedirs(LOG_DIR, exist_ok=True)
|
|
||||||
|
|
||||||
# 日志文件路径
|
|
||||||
LOG_FILE = os.path.join(LOG_DIR, "app.log")
|
|
||||||
|
|
||||||
# 日志格式
|
|
||||||
LOG_FORMAT = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
||||||
|
|
||||||
# 配置日志
|
|
||||||
logging.basicConfig(
|
|
||||||
level=logging.INFO, # 设置日志级别
|
|
||||||
format=LOG_FORMAT,
|
|
||||||
handlers=[
|
|
||||||
# 控制台输出
|
|
||||||
logging.StreamHandler(),
|
|
||||||
# 文件输出,支持日志文件轮转
|
|
||||||
RotatingFileHandler(LOG_FILE, maxBytes=5 * 1024 * 1024, backupCount=3, encoding="utf-8"),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
# 获取日志实例
|
|
||||||
def get_logger(name: str):
|
|
||||||
"""获取带有模块名称的日志记录器"""
|
|
||||||
return logging.getLogger(name)
|
|
@ -1,83 +0,0 @@
|
|||||||
from enum import Enum
|
|
||||||
|
|
||||||
class SysCodes(Enum):
|
|
||||||
SUCCESS = ("0000", "操作成功")
|
|
||||||
PARAM_ERROR = ("1006", "参数错误")
|
|
||||||
URI_NOT_FOUND = ("240", "URI不存在")
|
|
||||||
URI_EXISTS = ("241", "URI已经存在")
|
|
||||||
SERVICE_ERROR = ("320", "服务的路由异常,请联系管理员")
|
|
||||||
TIMEOUT = ("998", "处理超时,服务配置出错,请联系管理员")
|
|
||||||
LOGIN_TIMEOUT = ("9998", "登录超时,请重新登录")
|
|
||||||
LOGIN_FAIL = ("401", "登录超时,请重新登录")
|
|
||||||
OPERATE_FAIL = ("1005", "操作失败")
|
|
||||||
USER_EXISTS = ("1007", "用户已经存在")
|
|
||||||
USER_ID_ERROR = ("1009", "用户ID参数错误")
|
|
||||||
USER_NOT_EXISTS = ("1010", "用户不存在或密码不匹配")
|
|
||||||
USER_EMAIL_ERROR = ("1011", "用户邮箱地址错误")
|
|
||||||
USER_EMAIL_EXISTS = ("1012", "邮箱地址已经存在")
|
|
||||||
USER_PHONE_ERROR = ("1013", "手机号码格式不正确")
|
|
||||||
USER_PHONE_EXISTS = ("1014", "手机号码已经存在")
|
|
||||||
LOGIN_ERROR = ("1020", "登录验证出错")
|
|
||||||
USER_NOT_LOGIN = ("1021", "用户没有登录")
|
|
||||||
USER_STATUS_ERROR = ("1022", "用户状态不正常")
|
|
||||||
SEND_CODE_FAIL = ("1023", "发送验证码失败")
|
|
||||||
CHECK_CODE_FAIL = ("1024", "验证码验证失败")
|
|
||||||
USER_NO_AUTHORITY = ("1025", "用户没有权限")
|
|
||||||
USER_GROUP_EXISTS = ("1030", "用户组已经存在")
|
|
||||||
USER_GROUP_NOT_EXISTS = ("1031", "用户组不经存在")
|
|
||||||
OPERATE_ERROR = ("1999", "操作出错")
|
|
||||||
FRONT_ERROR = ("886000", "前端处理验证数据出错")
|
|
||||||
DELETE_CONTENT = ("2012", "请先删除内容")
|
|
||||||
FILE_NOT_FOUND = ("3000", "文件不存在")
|
|
||||||
FILE_SIZE_EXCEEDED = ("3001", "文件大小超出限制")
|
|
||||||
FILE_UPLOAD_FAILED = ("3002", "文件上传失败")
|
|
||||||
REQUEST_FAILED = ("3003", "请求失败")
|
|
||||||
NONE = ("9999", "异常错误")
|
|
||||||
|
|
||||||
def __init__(self, code, msg):
|
|
||||||
self._value_ = code
|
|
||||||
self._msg = msg
|
|
||||||
|
|
||||||
@property
|
|
||||||
def code(self):
|
|
||||||
return self._value_
|
|
||||||
|
|
||||||
@property
|
|
||||||
def msg(self):
|
|
||||||
return self._msg
|
|
||||||
|
|
||||||
class Result:
|
|
||||||
code:str = ""
|
|
||||||
msg:str = ""
|
|
||||||
data:dict = None
|
|
||||||
success:bool = False
|
|
||||||
def __init__(self, code:str, msg:str, data:object, success:bool, sys_codes: SysCodes = None) -> None:
|
|
||||||
if sys_codes:
|
|
||||||
self.code = sys_codes.code
|
|
||||||
self.msg = sys_codes.msg
|
|
||||||
else:
|
|
||||||
self.code = code
|
|
||||||
self.msg = msg
|
|
||||||
self.data = data
|
|
||||||
self.success = success
|
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
return {
|
|
||||||
"code": self.code,
|
|
||||||
"msg": self.msg,
|
|
||||||
"data": self.data,
|
|
||||||
"success": self.success
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def ok(data):
|
|
||||||
return Result("0000", "操作成功", data, True)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def error(sys_codes: SysCodes = None):
|
|
||||||
if sys_codes:
|
|
||||||
return Result(sys_codes.code, sys_codes.msg, None, False)
|
|
||||||
return Result.error(SysCodes.NONE)
|
|
||||||
|
|
||||||
|
|
82
pom.xml
Normal file
82
pom.xml
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>com.linxyun</groupId>
|
||||||
|
<artifactId>spring-boot-starter-linxyun</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
<parent>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starters</artifactId>
|
||||||
|
<version>2.1.0.RELEASE</version>
|
||||||
|
</parent>
|
||||||
|
<name>Spring Boot Linxyun Starter</name>
|
||||||
|
<description>Starter for using Linxyun</description>
|
||||||
|
|
||||||
|
<developers>
|
||||||
|
<developer>
|
||||||
|
<name>Wen Xin</name>
|
||||||
|
<email>1731551615@qq.com</email>
|
||||||
|
</developer>
|
||||||
|
</developers>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<!--Spring Validation –>-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-validation</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<!-- Spring Web -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<!-- Lombok -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
<version>1.18.34</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- Thumbnailator -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.coobird</groupId>
|
||||||
|
<artifactId>thumbnailator</artifactId>
|
||||||
|
<version>0.4.20</version>
|
||||||
|
</dependency>
|
||||||
|
<!--OkHttp-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.hutool</groupId>
|
||||||
|
<artifactId>hutool-http</artifactId>
|
||||||
|
<version>5.8.34</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- FastJSON2 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.fastjson2</groupId>
|
||||||
|
<artifactId>fastjson2</artifactId>
|
||||||
|
<version>2.0.53</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- ExpiringMap -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.jodah</groupId>
|
||||||
|
<artifactId>expiringmap</artifactId>
|
||||||
|
<version>0.5.11</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- Spring Configuration Processor -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>8</maven.compiler.source>
|
||||||
|
<maven.compiler.target>8</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
</project>
|
57
src/main/java/com/linxyun/core/common/entity/Result.java
Normal file
57
src/main/java/com/linxyun/core/common/entity/Result.java
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
package com.linxyun.core.common.entity;
|
||||||
|
|
||||||
|
import com.linxyun.core.common.enums.ErrorCode;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@ToString
|
||||||
|
public class Result<E> {
|
||||||
|
public E data;
|
||||||
|
public String code;
|
||||||
|
public String msg;
|
||||||
|
public boolean success = true;
|
||||||
|
|
||||||
|
public Result(E data, String code, String msg, boolean success) {
|
||||||
|
this.data = data;
|
||||||
|
this.code = code;
|
||||||
|
this.msg = msg;
|
||||||
|
this.success = success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> Result<T> error(ErrorCode codeEnum) {
|
||||||
|
return new Result<>(null, codeEnum.getCode(), codeEnum.getMessage(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> Result<T> error(ErrorCode codeEnum, String message) {
|
||||||
|
return new Result<>(null, codeEnum.getCode(), message, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> Result<T> error(String code, String msg) {
|
||||||
|
return new Result<>(null, code, msg, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> Result<T> ok() {
|
||||||
|
return new Result<>(null, "0000", "操作成功", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> Result<T> ok(String msg) {
|
||||||
|
return new Result<>(null, "0000", msg, true);
|
||||||
|
|
||||||
|
}
|
||||||
|
public static <T> Result<T> ok(T data) {
|
||||||
|
return new Result<>(data, "0000", "操作成功", true);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> Result<T> ok(T data, String msg) {
|
||||||
|
return new Result<>(data, "0000", msg, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Result() {
|
||||||
|
this.success = false;
|
||||||
|
this.code = "9999";
|
||||||
|
this.msg = "操作失败";
|
||||||
|
this.data = null;
|
||||||
|
}
|
||||||
|
}
|
16
src/main/java/com/linxyun/core/common/entity/UserAuth.java
Normal file
16
src/main/java/com/linxyun/core/common/entity/UserAuth.java
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package com.linxyun.core.common.entity;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class UserAuth {
|
||||||
|
private String ContainerID;
|
||||||
|
private String EntCode;
|
||||||
|
private String LoginEntCode;
|
||||||
|
private String LoginIP;
|
||||||
|
private String LoginNodeID;
|
||||||
|
private String LoginTime;
|
||||||
|
private String UserID;
|
||||||
|
private String UserName;
|
||||||
|
private String UserRoles;
|
||||||
|
}
|
126
src/main/java/com/linxyun/core/common/enums/ErrorCode.java
Normal file
126
src/main/java/com/linxyun/core/common/enums/ErrorCode.java
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
package com.linxyun.core.common.enums;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public enum ErrorCode {
|
||||||
|
|
||||||
|
// 用户不存在或密码不匹配
|
||||||
|
USER_NOT_FOUND_OR_PASSWORD_MISMATCH("1006", "用户不存在或密码不匹配"),
|
||||||
|
|
||||||
|
// 参数错误
|
||||||
|
PARAMETER_ERROR("204", "参数错误"),
|
||||||
|
|
||||||
|
// URI 不存在
|
||||||
|
URI_NOT_FOUND("240", "URI不存在"),
|
||||||
|
|
||||||
|
// URI 已经存在
|
||||||
|
URI_ALREADY_EXISTS("241", "URI已经存在"),
|
||||||
|
|
||||||
|
// 服务的路由异常,请联系管理员
|
||||||
|
ROUTING_EXCEPTION("320", "服务的路由异常,请联系管理员"),
|
||||||
|
|
||||||
|
// 处理超时,服务配置出错,请联系管理员
|
||||||
|
TIMEOUT_ERROR("998", "处理超时,服务配置出错,请联系管理员"),
|
||||||
|
|
||||||
|
// 登录超时,请重新登录
|
||||||
|
LOGIN_TIMEOUT("9998", "登录超时,请重新登录"),
|
||||||
|
|
||||||
|
// 登录超时,请重新登录
|
||||||
|
LOGIN_TIMEOUT_2("401", "登录超时,请重新登录"),
|
||||||
|
|
||||||
|
// 操作失败
|
||||||
|
OPERATION_FAIL("1005", "操作失败"),
|
||||||
|
|
||||||
|
// 用户已经存在
|
||||||
|
USER_ALREADY_EXISTS("1007", "用户已经存在"),
|
||||||
|
|
||||||
|
// 操作ID配置错误,请联系管理员
|
||||||
|
OPERATION_ID_ERROR("1008", "操作ID配置错误,请联系管理员"),
|
||||||
|
|
||||||
|
// 用户ID参数错误
|
||||||
|
USER_ID_PARAMETER_ERROR("1009", "用户ID参数错误"),
|
||||||
|
|
||||||
|
// 用户邮箱地址错误
|
||||||
|
USER_EMAIL_ERROR("1011", "用户邮箱地址错误"),
|
||||||
|
|
||||||
|
// 邮箱地址已经存在
|
||||||
|
EMAIL_ALREADY_EXISTS("1012", "邮箱地址已经存在"),
|
||||||
|
|
||||||
|
// 手机号码格式不正确
|
||||||
|
PHONE_NUMBER_FORMAT_ERROR("1013", "手机号码格式不正确"),
|
||||||
|
|
||||||
|
// 手机号码已经存在
|
||||||
|
PHONE_NUMBER_ALREADY_EXISTS("1014", "手机号码已经存在"),
|
||||||
|
|
||||||
|
// 登录验证出错
|
||||||
|
LOGIN_VALIDATION_ERROR("1020", "登录验证出错"),
|
||||||
|
|
||||||
|
// 用户没有登录
|
||||||
|
USER_NOT_LOGGED_IN("1021", "用户没有登录"),
|
||||||
|
|
||||||
|
// 用户状态不正常
|
||||||
|
USER_STATUS_ERROR("1022", "用户状态不正常"),
|
||||||
|
|
||||||
|
// 发送验证码失败
|
||||||
|
SEND_VERIFICATION_CODE_FAIL("1023", "发送验证码失败"),
|
||||||
|
|
||||||
|
// 验证码验证失败
|
||||||
|
VERIFICATION_CODE_FAIL("1024", "验证码验证失败"),
|
||||||
|
|
||||||
|
USER_NO_AUTHORITY("1025", "用户没有权限"),
|
||||||
|
|
||||||
|
// 用户组已经存在
|
||||||
|
USER_GROUP_ALREADY_EXISTS("1030", "用户组已经存在"),
|
||||||
|
|
||||||
|
// 用户组不存在
|
||||||
|
USER_GROUP_NOT_EXISTS("1031", "用户组不经存在"),
|
||||||
|
|
||||||
|
// 操作出错
|
||||||
|
OPERATION_ERROR("1999", "操作出错"),
|
||||||
|
|
||||||
|
// 前端处理验证数据出错
|
||||||
|
FRONTEND_VALIDATION_ERROR("886000", "前端处理验证数据出错"),
|
||||||
|
|
||||||
|
// 请先删除内容
|
||||||
|
DELETE_CONTENT_FIRST("2012", "请先删除内容"),
|
||||||
|
// 文件不存在
|
||||||
|
FILE_NOT_FOUND("3000", "文件不存在"),
|
||||||
|
// 文件大小超出限制
|
||||||
|
FILE_SIZE_EXCEEDED("3001", "文件大小超出限制"),
|
||||||
|
// 文件上传失败
|
||||||
|
FILE_UPLOAD_FAILED("3002", "文件上传失败"),
|
||||||
|
// 请求失败
|
||||||
|
REQUEST_FAILED("3003", "请求失败"),
|
||||||
|
NONE("9999", "异常错误");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private final String code;
|
||||||
|
private final String message;
|
||||||
|
|
||||||
|
ErrorCode(String code, String message) {
|
||||||
|
this.code = code;
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据错误代码获取对应的错误信息
|
||||||
|
public static String getMessageByCode(String code) {
|
||||||
|
for (ErrorCode errorCode : values()) {
|
||||||
|
if (errorCode.getCode().equals(code)) {
|
||||||
|
return errorCode.getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "未知错误";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ErrorCode getErrorCodeByCode(String code) {
|
||||||
|
for (ErrorCode errorCode : values()) {
|
||||||
|
if (errorCode.getCode().equals(code)) {
|
||||||
|
return errorCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ErrorCode.NONE;
|
||||||
|
}
|
||||||
|
}
|
27
src/main/java/com/linxyun/core/common/enums/FileType.java
Normal file
27
src/main/java/com/linxyun/core/common/enums/FileType.java
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package com.linxyun.core.common.enums;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public enum FileType {
|
||||||
|
IMAGE(Arrays.asList(".jpg", ".jpeg", ".png", ".gif", ".bmp")),
|
||||||
|
VIDEO(Arrays.asList(".mp4", ".mkv", ".mov", ".avi")),
|
||||||
|
AUDIO(Arrays.asList(".mp3", ".wav", ".ogg")),
|
||||||
|
DOCUMENT(Arrays.asList(".pdf", ".doc", ".docx", ".xls", ".xlsx")),
|
||||||
|
OTHER(new ArrayList<>());
|
||||||
|
|
||||||
|
private final List<String> extensions;
|
||||||
|
|
||||||
|
FileType(List<String> extensions) {
|
||||||
|
this.extensions = extensions;
|
||||||
|
}
|
||||||
|
public static FileType fromExtension(String fileName) {
|
||||||
|
String extension = fileName.substring(fileName.lastIndexOf(".")).toLowerCase();
|
||||||
|
return Arrays.stream(values())
|
||||||
|
.filter(type -> type.extensions.contains(extension))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(OTHER);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
|||||||
|
package com.linxyun.core.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.ComponentScan;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@ComponentScan(basePackages = "com.linxyun.core")
|
||||||
|
public class AutoConfiguration {
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
package com.linxyun.core.config;
|
||||||
|
|
||||||
|
import com.linxyun.core.interceptor.SecurityInterceptor;
|
||||||
|
import com.linxyun.core.properties.LinxyunProperties;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||||
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Configuration
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class WebInterceptorConfig implements WebMvcConfigurer {
|
||||||
|
|
||||||
|
private final LinxyunProperties linxyunProperties;
|
||||||
|
|
||||||
|
private final SecurityInterceptor securityInterceptor;
|
||||||
|
|
||||||
|
// 添加拦截器
|
||||||
|
@Override
|
||||||
|
public void addInterceptors(@NotNull InterceptorRegistry registry) {
|
||||||
|
log.info("添加鉴权拦截器:Linxyun Security Interceptor");
|
||||||
|
List<String> whiteList = linxyunProperties.getWhiteList();
|
||||||
|
// 添加默认的静态资源路径排除
|
||||||
|
whiteList.add("/static/**");
|
||||||
|
whiteList.add("/public/**");
|
||||||
|
whiteList.add("/resources/**");
|
||||||
|
whiteList.add("/META-INF/resources/**");
|
||||||
|
whiteList.add("/webjars/**");
|
||||||
|
log.info("白名单:{}", whiteList);
|
||||||
|
registry.addInterceptor(securityInterceptor)
|
||||||
|
.excludePathPatterns(whiteList)
|
||||||
|
.addPathPatterns("/**");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,117 @@
|
|||||||
|
package com.linxyun.core.interceptor;
|
||||||
|
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
|
import com.linxyun.core.common.entity.Result;
|
||||||
|
import com.linxyun.core.common.entity.UserAuth;
|
||||||
|
import com.linxyun.core.common.enums.ErrorCode;
|
||||||
|
import com.linxyun.core.properties.LinxyunProperties;
|
||||||
|
import com.linxyun.core.utils.ApiUtils;
|
||||||
|
import com.linxyun.core.utils.URLUtils;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.web.servlet.HandlerInterceptor;
|
||||||
|
import org.springframework.web.servlet.ModelAndView;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SecurityInterceptor implements HandlerInterceptor {
|
||||||
|
|
||||||
|
private final LinxyunProperties linxyunProperties;
|
||||||
|
|
||||||
|
private Pattern pattern = Pattern.compile("LoginID_\\d{14}_\\d{6}");
|
||||||
|
|
||||||
|
|
||||||
|
// 生命周期: 拦截器在请求处理之前调用,只有返回true才会继续调用下一个拦截器或者处理器,否则不会调用
|
||||||
|
@Override
|
||||||
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||||
|
// 跨域请求会首先发送一个OPTIONS请求,这里我们给OPTIONS请求直接返回正常状态
|
||||||
|
if (request.getMethod().equals("OPTIONS")) return true;
|
||||||
|
log.info("鉴权拦截:{} {}", request.getMethod(), request.getRequestURI());
|
||||||
|
|
||||||
|
// 获取请求头上的Token
|
||||||
|
String token = request.getHeader("Token");
|
||||||
|
if (StringUtils.isEmpty(token)) {
|
||||||
|
token = request.getParameter("Token");
|
||||||
|
}
|
||||||
|
log.info("请求头中Token:{}", token);
|
||||||
|
response.setCharacterEncoding("UTF-8");
|
||||||
|
response.setContentType("application/json; charset=utf-8");
|
||||||
|
Result<JSONObject> result;
|
||||||
|
if (StringUtils.isEmpty(token)) {
|
||||||
|
log.info("请求头中无 Token 信息");
|
||||||
|
result = Result.error(ErrorCode.USER_NOT_LOGGED_IN);
|
||||||
|
response.getWriter().write(JSON.toJSONString(result));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (token.startsWith("Session")) {
|
||||||
|
Matcher matcher = pattern.matcher(token);
|
||||||
|
if (matcher.find()) {
|
||||||
|
token = matcher.group();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Result<UserAuth> authRt = ApiUtils.getUserAuth(token);
|
||||||
|
assert authRt != null; // 断言
|
||||||
|
if (!authRt.isSuccess()) {
|
||||||
|
response.getWriter().write(JSON.toJSONString(authRt));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
UserAuth userAuth = authRt.getData();
|
||||||
|
String userRole = userAuth.getUserRoles();
|
||||||
|
if (StringUtils.isEmpty(userRole)) {
|
||||||
|
log.info("用户权限为空:{}", userAuth.getUserRoles());
|
||||||
|
result = Result.error(ErrorCode.USER_NO_AUTHORITY);
|
||||||
|
response.getWriter().write(JSON.toJSONString(result));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!linxyunProperties.getEntCode().equals(userAuth.getEntCode())) {
|
||||||
|
log.info("用户企业编码错误:{}", userAuth.getEntCode());
|
||||||
|
result = Result.error(ErrorCode.LOGIN_VALIDATION_ERROR);
|
||||||
|
response.getWriter().write(JSON.toJSONString(result));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 验证用户是否拥有权限
|
||||||
|
Map<String, List<String>> roleMap = linxyunProperties.getRole();
|
||||||
|
if (!roleMap.containsKey(userAuth.getUserRoles())) {
|
||||||
|
log.info("用户权限未在系统权限中:{}", userAuth.getUserRoles());
|
||||||
|
result = Result.error(ErrorCode.USER_NO_AUTHORITY);
|
||||||
|
response.getWriter().write(JSON.toJSONString(result));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
List<String> pathList = roleMap.get(userRole);
|
||||||
|
// 是否有权限访问该路径
|
||||||
|
if (!URLUtils.isUriAuthorized(request.getRequestURI(), pathList)) {
|
||||||
|
log.info("用户无权访问该路径:{}", request.getRequestURI());
|
||||||
|
result = Result.error(ErrorCode.USER_NO_AUTHORITY);
|
||||||
|
response.getWriter().write(JSON.toJSONString(result));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
log.info("鉴权通过:{} {}", token, request.getRequestURI());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生命周期: 拦截器在请求处理之后调用,但是此时还没有返回视图,只有返回true才会继续调用下一个拦截器或者处理器,否则不会调用
|
||||||
|
@Override
|
||||||
|
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生命周期: 拦截器在视图渲染之后调用,即在视图渲染完成之后,页面响应给客户端之前,只有返回true才会继续调用下一个拦截器或者处理器,否则不会调用
|
||||||
|
@Override
|
||||||
|
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,88 @@
|
|||||||
|
package com.linxyun.core.properties;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.beans.BeansException;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.context.ApplicationContextAware;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Component
|
||||||
|
@ConfigurationProperties(prefix = "linxyun")
|
||||||
|
@Validated
|
||||||
|
public class LinxyunProperties implements ApplicationContextAware {
|
||||||
|
|
||||||
|
private static ApplicationContext context;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* url
|
||||||
|
*/
|
||||||
|
private String url = "http://www.linxyun.com";
|
||||||
|
/**
|
||||||
|
* 企业编码
|
||||||
|
*/
|
||||||
|
@NotNull(message = "The 'entCode' property is mandatory")
|
||||||
|
private String entCode;
|
||||||
|
/**
|
||||||
|
* 项目名称
|
||||||
|
*/
|
||||||
|
@NotNull(message = "The 'project' property is mandatory")
|
||||||
|
private String project;
|
||||||
|
/**
|
||||||
|
* 角色权限
|
||||||
|
*/
|
||||||
|
private Map<String, List<String>> role = new HashMap<>();
|
||||||
|
/**
|
||||||
|
* 白名单
|
||||||
|
*/
|
||||||
|
private List<String> whiteList = new ArrayList<>();
|
||||||
|
/**
|
||||||
|
* 上传配置
|
||||||
|
*/
|
||||||
|
private Upload upload = new Upload();
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class Upload {
|
||||||
|
private Float imgQuality = 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Value("${server.servlet.context-path}")
|
||||||
|
private String contextPath;
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
// 解决 springboot 设置 context-path,导致权限路由验证失效
|
||||||
|
if (contextPath != null && !contextPath.isEmpty()) {
|
||||||
|
for (String key : role.keySet()) {
|
||||||
|
List<String> paths = role.get(key);
|
||||||
|
for (int i = 0; i < paths.size(); i++) {
|
||||||
|
String path = paths.get(i);
|
||||||
|
if (!path.startsWith(contextPath)) {
|
||||||
|
paths.set(i, contextPath + path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 静态方法来访问实例
|
||||||
|
public static LinxyunProperties getInstance() {
|
||||||
|
return context.getBean(LinxyunProperties.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setApplicationContext(ApplicationContext applicationContext) {
|
||||||
|
context = applicationContext;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
142
src/main/java/com/linxyun/core/utils/ApiUtils.java
Normal file
142
src/main/java/com/linxyun/core/utils/ApiUtils.java
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
package com.linxyun.core.utils;
|
||||||
|
|
||||||
|
import cn.hutool.http.HttpRequest;
|
||||||
|
import cn.hutool.http.HttpResponse;
|
||||||
|
import cn.hutool.http.HttpUtil;
|
||||||
|
import cn.hutool.http.body.RequestBody;
|
||||||
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
|
import com.linxyun.core.common.entity.Result;
|
||||||
|
import com.linxyun.core.common.entity.UserAuth;
|
||||||
|
import com.linxyun.core.common.enums.ErrorCode;
|
||||||
|
import com.linxyun.core.properties.LinxyunProperties;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import net.jodah.expiringmap.ExpirationPolicy;
|
||||||
|
import net.jodah.expiringmap.ExpiringMap;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class ApiUtils {
|
||||||
|
|
||||||
|
private final static ExpiringMap<String, UserAuth> USER_AUTH_MAP = ExpiringMap.builder()
|
||||||
|
// 设置最大值,添加第11个entry时,会导致第1个立马过期(即使没到过期时间)。默认 Integer.MAX_VALUE
|
||||||
|
.maxSize(10)
|
||||||
|
// 允许 Map 元素具有各自的到期时间,并允许更改到期时间。
|
||||||
|
.variableExpiration()
|
||||||
|
// 设置过期时间,如果key不设置过期时间,key永久有效。
|
||||||
|
.expiration(1, TimeUnit.DAYS)
|
||||||
|
.asyncExpirationListener((key, value) -> log.info("USER_AUTH_MAP key数据被删除了 -> key={}, value={}", key, value))
|
||||||
|
//设置 Map 的过期策略
|
||||||
|
.expirationPolicy(ExpirationPolicy.CREATED)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
private static final LinxyunProperties linxyunProperties = LinxyunProperties.getInstance();
|
||||||
|
private static final String url = linxyunProperties.getUrl();
|
||||||
|
private static final String projectName = linxyunProperties.getProject();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单点登录
|
||||||
|
*/
|
||||||
|
public static Result<UserAuth> userLoginAuth(String token) {
|
||||||
|
try {
|
||||||
|
if (StringUtils.isEmpty(token)) {
|
||||||
|
return Result.error(ErrorCode.USER_NOT_LOGGED_IN);
|
||||||
|
}
|
||||||
|
String url = getApiUrl("userLoginAuth");
|
||||||
|
JSONObject body = new JSONObject();
|
||||||
|
body.put("LoginID", token);
|
||||||
|
JSONObject result = HttpUtils.post(url, body);
|
||||||
|
if (result == null) {
|
||||||
|
log.error("LinxyunUtils-userLoginAuth result is null");
|
||||||
|
return Result.error(ErrorCode.REQUEST_FAILED);
|
||||||
|
}
|
||||||
|
log.info("LinxyunUtils-userLoginAuth result: {}", result);
|
||||||
|
if (!result.getBoolean("success")) {
|
||||||
|
String code = result.getString("code");
|
||||||
|
String msg = result.getString("msg");
|
||||||
|
log.error("LinxyunUtils-userLoginAuth result is not success: {}", msg);
|
||||||
|
return Result.error(ErrorCode.getErrorCodeByCode(code));
|
||||||
|
}
|
||||||
|
String data = result.getString("data");
|
||||||
|
if (StringUtils.isEmpty(data)) {
|
||||||
|
log.error("LinxyunUtils-userLoginAuth result.data is null");
|
||||||
|
return Result.error(ErrorCode.OPERATION_ERROR);
|
||||||
|
}
|
||||||
|
UserAuth userAuth = JSONObject.parseObject(data, UserAuth.class);
|
||||||
|
USER_AUTH_MAP.put(token, userAuth);
|
||||||
|
return Result.ok(userAuth);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("linxyunUtils.userLoginAuth error: {}", e.getMessage());
|
||||||
|
return Result.error(ErrorCode.TIMEOUT_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Result<UserAuth> getUserAuth(String token) {
|
||||||
|
if (StringUtils.isEmpty(token)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
boolean isExist = USER_AUTH_MAP.containsKey(token);
|
||||||
|
if (isExist) {
|
||||||
|
// 存在,直接获取缓存的数据
|
||||||
|
UserAuth userAuth = USER_AUTH_MAP.get(token);
|
||||||
|
return Result.ok(userAuth);
|
||||||
|
}
|
||||||
|
return userLoginAuth(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getApiUrl(String path) {
|
||||||
|
String baseUrl = url + "/" + projectName;
|
||||||
|
if (StringUtils.isEmpty(path)) {
|
||||||
|
return baseUrl;
|
||||||
|
}
|
||||||
|
if (!path.startsWith("/")) {
|
||||||
|
path = "/" + path;
|
||||||
|
}
|
||||||
|
if (!path.endsWith(".action")) {
|
||||||
|
path += ".action";
|
||||||
|
}
|
||||||
|
return baseUrl + path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用 OkHttp 上传文件
|
||||||
|
*
|
||||||
|
* @param fileBytes 文件字节数组
|
||||||
|
* @param fileName 文件名
|
||||||
|
* @return 上传成功时返回服务器响应的 "data" 字段;否则返回 null
|
||||||
|
*/
|
||||||
|
public static String uploadFile(byte[] fileBytes, String fileName) {
|
||||||
|
String uploadUrl = url + "/eslithe/uploadFile.action";
|
||||||
|
|
||||||
|
// 创建请求
|
||||||
|
HttpRequest request = HttpUtil.createPost(uploadUrl);
|
||||||
|
request.form("file", fileBytes, fileName);
|
||||||
|
// 执行请求
|
||||||
|
try (HttpResponse response = request.execute()) {
|
||||||
|
if (response.isOk() && response.body() != null) {
|
||||||
|
String responseBody = response.body();
|
||||||
|
log.info("FileUploadTool uploadFile result: {}", responseBody);
|
||||||
|
|
||||||
|
// 解析响应
|
||||||
|
JSONObject jsonObject = JSONObject.parseObject(responseBody);
|
||||||
|
if (jsonObject != null
|
||||||
|
&& jsonObject.getBoolean("success") != null
|
||||||
|
&& jsonObject.getBoolean("success")
|
||||||
|
&& jsonObject.containsKey("data")) {
|
||||||
|
return jsonObject.getString("data");
|
||||||
|
} else {
|
||||||
|
log.error("FileUploadTool uploadFile failed: {}", responseBody);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.error("FileUploadTool uploadFile failed, HTTP code: {}", response.getStatus());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
94
src/main/java/com/linxyun/core/utils/FileUtils.java
Normal file
94
src/main/java/com/linxyun/core/utils/FileUtils.java
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
package com.linxyun.core.utils;
|
||||||
|
|
||||||
|
import cn.hutool.http.HttpResponse;
|
||||||
|
import com.linxyun.core.common.enums.FileType;
|
||||||
|
import com.linxyun.core.properties.LinxyunProperties;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import net.coobird.thumbnailator.Thumbnails;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class FileUtils {
|
||||||
|
|
||||||
|
private static final LinxyunProperties linxyunProperties = LinxyunProperties.getInstance();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载文件到指定目录
|
||||||
|
* @param fileUrl 文件 URL
|
||||||
|
* @param targetDir 本地目标目录
|
||||||
|
* @param fileName 保存的文件名
|
||||||
|
* @throws IOException 如果发生 IO 异常
|
||||||
|
*/
|
||||||
|
public static void downloadFile(String fileUrl, String targetDir, String fileName) {
|
||||||
|
// 执行请求
|
||||||
|
try (HttpResponse response = HttpUtils.getResp(fileUrl)) {
|
||||||
|
if (!response.isOk()) {
|
||||||
|
log.info("文件下载失败, HTTP 响应码: {}", response.getStatus());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 确保目标目录存在
|
||||||
|
File directory = new File(targetDir);
|
||||||
|
if (!directory.exists() && !directory.mkdirs()) {
|
||||||
|
log.error("无法创建目标目录: {}", targetDir);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 获取输入流并保存文件
|
||||||
|
byte[] bytes = response.body().getBytes();
|
||||||
|
FileOutputStream outputStream = new FileOutputStream(new File(directory, fileName));
|
||||||
|
outputStream.write(bytes);
|
||||||
|
log.info("文件下载完成: {}", targetDir + File.separator + fileName);
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
log.error("文件不存在", e);
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("文件下载异常", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载指定 URL 的文件,并将内容转化为字节数组。
|
||||||
|
*
|
||||||
|
* @param fileUrl 文件的 URL
|
||||||
|
* @param fileName 文件名(可以用来关联下载到本地的文件)
|
||||||
|
* @return 文件内容的字节数组
|
||||||
|
*/
|
||||||
|
|
||||||
|
public static String uploadFileByUrl(String fileUrl, String fileName) {
|
||||||
|
FileType fileType = FileType.fromExtension(fileName);
|
||||||
|
// 执行请求
|
||||||
|
try (HttpResponse response = HttpUtils.getResp(fileUrl)) {
|
||||||
|
if (!response.isOk()) {
|
||||||
|
log.error("Failed to download file: {}", response.body());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// 获取文件内容作为字节数组
|
||||||
|
try (InputStream inputStream = response.bodyStream()) {
|
||||||
|
// 如果是图片,进行压缩
|
||||||
|
if (fileType == FileType.IMAGE) {
|
||||||
|
try (ByteArrayOutputStream compressedOutputStream = new ByteArrayOutputStream()) {
|
||||||
|
log.info("压缩图片:{} 图片质量: {}", fileUrl, linxyunProperties.getUpload().getImgQuality());
|
||||||
|
// 压缩图片
|
||||||
|
Thumbnails.of(inputStream)
|
||||||
|
.outputQuality(linxyunProperties.getUpload().getImgQuality()) // 设置输出质量 (0~1)
|
||||||
|
.scale(1.0) // 保持原尺寸(如需缩放可调整 scale 值或使用 size 方法)
|
||||||
|
.toOutputStream(compressedOutputStream);
|
||||||
|
|
||||||
|
// 日志输出原始大小和压缩后大小
|
||||||
|
log.info("图片原始大小: {} 字节", response.contentLength());
|
||||||
|
log.info("图片压缩后大小: {} 字节", compressedOutputStream.size());
|
||||||
|
// 上传压缩后的图片
|
||||||
|
return ApiUtils.uploadFile(compressedOutputStream.toByteArray(), fileName);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 非图片直接上传
|
||||||
|
log.info("上传其它文件:{}", fileUrl);
|
||||||
|
byte[] fileBytes = StreamUtils.readAllBytes(inputStream);
|
||||||
|
return ApiUtils.uploadFile(fileBytes, fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("FileUploadTool uploadFileByUrl exception: ", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
84
src/main/java/com/linxyun/core/utils/HttpUtils.java
Normal file
84
src/main/java/com/linxyun/core/utils/HttpUtils.java
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
package com.linxyun.core.utils;
|
||||||
|
|
||||||
|
import cn.hutool.http.HttpRequest;
|
||||||
|
import cn.hutool.http.HttpResponse;
|
||||||
|
import cn.hutool.http.HttpUtil;
|
||||||
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class HttpUtils {
|
||||||
|
|
||||||
|
// 通用请求方法,处理所有的 GET 和 POST 请求
|
||||||
|
private static JSONObject executeRequest(HttpRequest request) {
|
||||||
|
try (HttpResponse response = request.execute()) {
|
||||||
|
log.info("HttpRequest:{} {}", request.getUrl(), request.getMethod());
|
||||||
|
if (!response.isOk()) {
|
||||||
|
log.error("HttpRequest failed: {}", response.getStatus());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (response.body() == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return JSONObject.parseObject(response.body());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET 请求
|
||||||
|
public static JSONObject get(String url) {
|
||||||
|
return executeRequest(HttpUtil.createGet(url));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HttpResponse getResp(String url) throws IOException {
|
||||||
|
return HttpUtil.createGet(url).execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HttpResponse postResp(String url, JSONObject body) throws IOException {
|
||||||
|
return HttpUtil.createPost(url).body(body.toJSONString()).execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JSONObject get(String url, Map<String, String> params) {
|
||||||
|
String urlWithParams = buildUrlWithParams(url, params);
|
||||||
|
return get(urlWithParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JSONObject get(String url, Map<String, String> params, Map<String, String> headers) throws IOException {
|
||||||
|
String urlWithParams = buildUrlWithParams(url, params);
|
||||||
|
return get(urlWithParams, headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// POST 请求
|
||||||
|
public static JSONObject post(String url, JSONObject body) {
|
||||||
|
HttpRequest request = HttpUtil.createPost(url).body(body.toJSONString());
|
||||||
|
return executeRequest(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JSONObject post(String url, JSONObject body, Map<String, String> params) {
|
||||||
|
String urlWithParams = buildUrlWithParams(url, params);
|
||||||
|
return post(urlWithParams, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static JSONObject post(String url, Map<String, String> params, JSONObject body, Map<String, String> headers) {
|
||||||
|
String urlWithParams = buildUrlWithParams(url, params);
|
||||||
|
HttpRequest request = HttpUtil.createPost(urlWithParams).body(body.toJSONString()).addHeaders(headers);
|
||||||
|
return executeRequest(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建带参数的 URL
|
||||||
|
private static String buildUrlWithParams(String url, Map<String, String> params) {
|
||||||
|
if (params == null || params.isEmpty()) {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
StringBuilder sb = new StringBuilder(url);
|
||||||
|
sb.append("?");
|
||||||
|
for (Map.Entry<String, String> entry : params.entrySet()) {
|
||||||
|
sb.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
|
||||||
|
}
|
||||||
|
return sb.substring(0, sb.length() - 1);
|
||||||
|
}
|
||||||
|
}
|
19
src/main/java/com/linxyun/core/utils/StreamUtils.java
Normal file
19
src/main/java/com/linxyun/core/utils/StreamUtils.java
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package com.linxyun.core.utils;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
public class StreamUtils {
|
||||||
|
|
||||||
|
public static byte[] readAllBytes(InputStream inputStream) throws IOException {
|
||||||
|
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
|
||||||
|
byte[] buffer = new byte[1024];
|
||||||
|
int bytesRead;
|
||||||
|
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||||
|
byteArrayOutputStream.write(buffer, 0, bytesRead);
|
||||||
|
}
|
||||||
|
return byteArrayOutputStream.toByteArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
25
src/main/java/com/linxyun/core/utils/TimeUtils.java
Normal file
25
src/main/java/com/linxyun/core/utils/TimeUtils.java
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package com.linxyun.core.utils;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class TimeUtils {
|
||||||
|
static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss"); // 定义日期格式
|
||||||
|
public static Date toDate(String dateStr) {
|
||||||
|
try {
|
||||||
|
return dateFormat.parse(dateStr);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("时间转换异常", e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getCurrentTime() {
|
||||||
|
Date now = new Date();
|
||||||
|
return dateFormat.format(now);
|
||||||
|
}
|
||||||
|
}
|
35
src/main/java/com/linxyun/core/utils/URLUtils.java
Normal file
35
src/main/java/com/linxyun/core/utils/URLUtils.java
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package com.linxyun.core.utils;
|
||||||
|
|
||||||
|
import org.springframework.util.AntPathMatcher;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class URLUtils {
|
||||||
|
|
||||||
|
private static final AntPathMatcher pathMatcher = new AntPathMatcher();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查请求的 URI 是否符合角色对应的路径列表
|
||||||
|
*
|
||||||
|
* @param requestUri 需要匹配的 URI
|
||||||
|
* @param pathList 角色对应的路径列表
|
||||||
|
* @return true 如果匹配;否则 false
|
||||||
|
*/
|
||||||
|
public static boolean isUriAuthorized(String requestUri, List<String> pathList) {
|
||||||
|
// 获取请求的 URI
|
||||||
|
if (requestUri == null || requestUri.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (pathList == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 遍历路径列表,检查是否有匹配项
|
||||||
|
for (String pathPattern : pathList) {
|
||||||
|
if (pathMatcher.match(pathPattern, requestUri)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 如果没有匹配到,返回 false
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
1
src/main/resources/META-INF/spring.factories
Normal file
1
src/main/resources/META-INF/spring.factories
Normal file
@ -0,0 +1 @@
|
|||||||
|
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.linxyun.core.config.AutoConfiguration
|
Loading…
Reference in New Issue
Block a user