refactor: 优化项目模块命名(简化、分类、统一)

This commit is contained in:
2024-10-30 23:01:54 +08:00
parent 9ecdeb52f6
commit c276e53a8e
346 changed files with 160 additions and 162 deletions

View File

@@ -0,0 +1,87 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.common.config.exception;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.NumberUtil;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.MultipartException;
import top.continew.starter.core.exception.BadRequestException;
import top.continew.starter.core.exception.BusinessException;
import top.continew.starter.web.model.R;
/**
* 全局异常处理器
*
* @author Charles7c
* @since 2024/8/7 20:21
*/
@Slf4j
@Order(99)
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 拦截业务异常
*/
@ExceptionHandler(BusinessException.class)
public R handleBusinessException(BusinessException e, HttpServletRequest request) {
log.error("[{}] {}", request.getMethod(), request.getRequestURI(), e);
return R.fail(String.valueOf(HttpStatus.INTERNAL_SERVER_ERROR.value()), e.getMessage());
}
/**
* 拦截自定义验证异常-错误请求
*/
@ExceptionHandler(BadRequestException.class)
public R handleBadRequestException(BadRequestException e, HttpServletRequest request) {
log.error("[{}] {}", request.getMethod(), request.getRequestURI(), e);
return R.fail(String.valueOf(HttpStatus.BAD_REQUEST.value()), e.getMessage());
}
/**
* 拦截文件上传异常-超过上传大小限制
*/
@ExceptionHandler(MultipartException.class)
public R handleRequestTooBigException(MultipartException e, HttpServletRequest request) {
log.error("[{}] {}", request.getMethod(), request.getRequestURI(), e);
String msg = e.getMessage();
R defaultFail = R.fail(String.valueOf(HttpStatus.BAD_REQUEST.value()), msg);
if (CharSequenceUtil.isBlank(msg)) {
return defaultFail;
}
String sizeLimit;
Throwable cause = e.getCause();
if (null != cause) {
msg = msg.concat(cause.getMessage().toLowerCase());
}
if (msg.contains("size") && msg.contains("exceed")) {
sizeLimit = CharSequenceUtil.subBetween(msg, "the maximum size ", " for");
} else if (msg.contains("larger than")) {
sizeLimit = CharSequenceUtil.subAfter(msg, "larger than ", true);
} else {
return defaultFail;
}
String errorMsg = "请上传小于 %sKB 的文件".formatted(NumberUtil.parseLong(sizeLimit) / 1024);
return R.fail(String.valueOf(HttpStatus.BAD_REQUEST.value()), errorMsg);
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.common.config.exception;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import top.continew.starter.web.model.R;
/**
* 全局 SaToken 异常处理器
*
* @author Charles7c
* @since 2024/8/7 20:21
*/
@Slf4j
@Order(99)
@RestControllerAdvice
public class GlobalSaTokenExceptionHandler {
/**
* 认证异常-登录认证
*/
@ExceptionHandler(NotLoginException.class)
public R handleNotLoginException(NotLoginException e, HttpServletRequest request) {
log.error("[{}] {}", request.getMethod(), request.getRequestURI(), e);
String errorMsg = switch (e.getType()) {
case NotLoginException.KICK_OUT -> "您已被踢下线";
case NotLoginException.BE_REPLACED_MESSAGE -> "您已被顶下线";
default -> "您的登录状态已过期,请重新登录";
};
return R.fail(String.valueOf(HttpStatus.UNAUTHORIZED.value()), errorMsg);
}
/**
* 认证异常-权限认证
*/
@ExceptionHandler(NotPermissionException.class)
public R handleNotPermissionException(NotPermissionException e, HttpServletRequest request) {
log.error("[{}] {}", request.getMethod(), request.getRequestURI(), e);
return R.fail(String.valueOf(HttpStatus.FORBIDDEN.value()), "没有访问权限,请联系管理员授权");
}
/**
* 认证异常-角色认证
*/
@ExceptionHandler(NotRoleException.class)
public R handleNotRoleException(NotRoleException e, HttpServletRequest request) {
log.error("[{}] {}", request.getMethod(), request.getRequestURI(), e);
return R.fail(String.valueOf(HttpStatus.FORBIDDEN.value()), "没有访问权限,请联系管理员授权");
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.common.config.mybatis;
import org.springframework.security.crypto.password.PasswordEncoder;
import top.continew.starter.security.crypto.encryptor.IEncryptor;
/**
* BCrypt 加/解密处理器(不可逆)
*
* @author Charles7c
* @since 2024/2/8 22:29
*/
public class BCryptEncryptor implements IEncryptor {
private final PasswordEncoder passwordEncoder;
public BCryptEncryptor(PasswordEncoder passwordEncoder) {
this.passwordEncoder = passwordEncoder;
}
@Override
public String encrypt(String plaintext, String password, String publicKey) throws Exception {
return passwordEncoder.encode(plaintext);
}
@Override
public String decrypt(String ciphertext, String password, String privateKey) throws Exception {
return ciphertext;
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.common.config.mybatis;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import org.apache.ibatis.annotations.Param;
import top.continew.starter.data.mp.base.BaseMapper;
import top.continew.starter.extension.datapermission.annotation.DataPermission;
import java.util.List;
/**
* 数据权限 Mapper 基类
*
* @param <T> 实体类
* @author Charles7c
* @since 2023/9/3 21:50
*/
public interface DataPermissionMapper<T> extends BaseMapper<T> {
/**
* 根据 entity 条件,查询全部记录
*
* @param queryWrapper 实体对象封装操作类(可以为 null
* @return 全部记录
*/
@Override
@DataPermission
List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/**
* 根据 entity 条件,查询全部记录(并翻页)
*
* @param page 分页查询条件
* @param queryWrapper 实体对象封装操作类(可以为 null
* @return 全部记录(并翻页)
*/
@Override
@DataPermission
List<T> selectList(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.common.config.mybatis;
import cn.hutool.core.convert.Convert;
import top.continew.admin.common.context.UserContextHolder;
import top.continew.starter.extension.datapermission.enums.DataScope;
import top.continew.starter.extension.datapermission.filter.DataPermissionUserContextProvider;
import top.continew.starter.extension.datapermission.model.RoleContext;
import top.continew.starter.extension.datapermission.model.UserContext;
import java.util.stream.Collectors;
/**
* 数据权限用户上下文提供者
*
* @author Charles7c
* @since 2023/12/21 21:19
*/
public class DefaultDataPermissionUserContextProvider implements DataPermissionUserContextProvider {
@Override
public boolean isFilter() {
return !UserContextHolder.isAdmin();
}
@Override
public UserContext getUserContext() {
top.continew.admin.common.context.UserContext context = UserContextHolder.getContext();
UserContext userContext = new UserContext();
userContext.setUserId(Convert.toStr(context.getId()));
userContext.setDeptId(Convert.toStr(context.getDeptId()));
userContext.setRoles(context.getRoles()
.stream()
.map(r -> new RoleContext(Convert.toStr(r.getId()), DataScope.valueOf(r.getDataScope().name())))
.collect(Collectors.toSet()));
return userContext;
}
}

View File

@@ -0,0 +1,121 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.common.config.mybatis;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import top.continew.admin.common.context.UserContextHolder;
import top.continew.starter.core.exception.BusinessException;
import top.continew.starter.extension.crud.model.entity.BaseDO;
import java.time.LocalDateTime;
/**
* MyBatis Plus 元对象处理器配置(插入或修改时自动填充)
*
* @author Charles7c
* @since 2022/12/22 19:52
*/
public class MyBatisPlusMetaObjectHandler implements MetaObjectHandler {
/**
* 创建人
*/
private static final String CREATE_USER = "createUser";
/**
* 创建时间
*/
private static final String CREATE_TIME = "createTime";
/**
* 修改人
*/
private static final String UPDATE_USER = "updateUser";
/**
* 修改时间
*/
private static final String UPDATE_TIME = "updateTime";
/**
* 插入数据时填充
*
* @param metaObject 元对象
*/
@Override
public void insertFill(MetaObject metaObject) {
try {
if (null == metaObject) {
return;
}
Long createUser = UserContextHolder.getUserId();
LocalDateTime createTime = LocalDateTime.now();
if (metaObject.getOriginalObject() instanceof BaseDO baseDO) {
// 继承了 BaseDO 的类,填充创建信息字段
baseDO.setCreateUser(ObjectUtil.defaultIfNull(baseDO.getCreateUser(), createUser));
baseDO.setCreateTime(ObjectUtil.defaultIfNull(baseDO.getCreateTime(), createTime));
} else {
// 未继承 BaseDO 的类,如存在创建信息字段则进行填充
this.fillFieldValue(metaObject, CREATE_USER, createUser, false);
this.fillFieldValue(metaObject, CREATE_TIME, createTime, false);
}
} catch (Exception e) {
throw new BusinessException("插入数据时自动填充异常:" + e.getMessage());
}
}
/**
* 修改数据时填充
*
* @param metaObject 元对象
*/
@Override
public void updateFill(MetaObject metaObject) {
try {
if (null == metaObject) {
return;
}
Long updateUser = UserContextHolder.getUserId();
LocalDateTime updateTime = LocalDateTime.now();
if (metaObject.getOriginalObject() instanceof BaseDO baseDO) {
// 继承了 BaseDO 的类,填充修改信息
baseDO.setUpdateUser(updateUser);
baseDO.setUpdateTime(updateTime);
} else {
// 未继承 BaseDO 的类,根据类中拥有的修改信息字段进行填充,不存在修改信息字段不进行填充
this.fillFieldValue(metaObject, UPDATE_USER, updateUser, true);
this.fillFieldValue(metaObject, UPDATE_TIME, updateTime, true);
}
} catch (Exception e) {
throw new BusinessException("修改数据时自动填充异常:" + e.getMessage());
}
}
/**
* 填充字段值
*
* @param metaObject 元数据对象
* @param fieldName 要填充的字段名
* @param fillFieldValue 要填充的字段值
* @param isOverride 如果字段值不为空是否覆盖true覆盖false不覆盖
*/
private void fillFieldValue(MetaObject metaObject, String fieldName, Object fillFieldValue, boolean isOverride) {
if (metaObject.hasSetter(fieldName)) {
Object fieldValue = metaObject.getValue(fieldName);
setFieldValByName(fieldName, null != fieldValue && !isOverride ? fieldValue : fillFieldValue, metaObject);
}
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.common.config.mybatis;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.password.PasswordEncoder;
import top.continew.starter.extension.datapermission.filter.DataPermissionUserContextProvider;
/**
* MyBatis Plus 配置
*
* @author Charles7c
* @since 2022/12/22 19:51
*/
@Configuration
public class MybatisPlusConfiguration {
/**
* 元对象处理器配置(插入或修改时自动填充)
*/
@Bean
public MetaObjectHandler metaObjectHandler() {
return new MyBatisPlusMetaObjectHandler();
}
/**
* 数据权限用户上下文提供者
*/
@Bean
public DataPermissionUserContextProvider dataPermissionUserContextProvider() {
return new DefaultDataPermissionUserContextProvider();
}
/**
* BCrypt 加/解密处理器
*/
@Bean
public BCryptEncryptor bCryptEncryptor(PasswordEncoder passwordEncoder) {
return new BCryptEncryptor(passwordEncoder);
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.common.config.properties;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 验证码配置属性
*
* @author Charles7c
* @since 2022/12/11 13:35
*/
@Data
@Component
@ConfigurationProperties(prefix = "captcha")
public class CaptchaProperties {
/**
* 图形验证码过期时间
*/
@Value("${continew-starter.captcha.graphic.expirationInMinutes}")
private long expirationInMinutes;
/**
* 邮箱验证码配置
*/
private CaptchaMail mail;
/**
* 短信验证码配置
*/
private CaptchaSms sms;
/**
* 邮箱验证码配置
*/
@Data
public static class CaptchaMail {
/**
* 内容长度
*/
private int length;
/**
* 过期时间
*/
private long expirationInMinutes;
/**
* 模板路径
*/
private String templatePath;
}
/**
* 短信验证码配置
*/
@Data
public static class CaptchaSms {
/**
* 内容长度
*/
private int length;
/**
* 过期时间
*/
private long expirationInMinutes;
/**
* 模板 ID
*/
private String templateId;
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.common.config.properties;
import cn.hutool.extra.spring.SpringUtil;
/**
* RSA 配置属性
*
* @author Zheng JieELADMIN
* @author Charles7c
* @since 2022/12/21 20:21
*/
public class RsaProperties {
/**
* 私钥
*/
public static final String PRIVATE_KEY;
public static final String PUBLIC_KEY;
static {
PRIVATE_KEY = SpringUtil.getProperty("continew-starter.security.crypto.private-key");
PUBLIC_KEY = SpringUtil.getProperty("continew-starter.security.crypto.public-key");
}
private RsaProperties() {
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.admin.common.config.websocket;
import cn.dev33.satoken.stp.StpUtil;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.stereotype.Component;
import top.continew.starter.core.exception.BusinessException;
import top.continew.starter.messaging.websocket.core.WebSocketClientService;
/**
* 当前登录用户 Provider
*
* @author Charles7c
* @since 2024/6/4 22:13
*/
@Component
public class WebSocketClientServiceImpl implements WebSocketClientService {
@Override
public String getClientId(ServletServerHttpRequest request) {
HttpServletRequest servletRequest = request.getServletRequest();
String token = servletRequest.getParameter("token");
if (null == StpUtil.getLoginIdByToken(token)) {
throw new BusinessException("登录已过期,请重新登录");
}
return token;
}
}