├── 多模块版请移步至helio-boot-modular项目.md ├── attachments ├── db │ ├── MySQL │ │ └── upgrade │ │ │ ├── 2.4.0_to_2.4.1.sql │ │ │ ├── 1.10.0_to_1.11.0.sql │ │ │ ├── 1.7.3_to_1.8.0.sql │ │ │ ├── 2.0.0_to_2.1.0.sql │ │ │ └── 1.7.1_to_1.7.2.sql │ └── PostgreSQL │ │ └── upgrade │ │ ├── 2.4.0_to_2.4.1.sql │ │ ├── 1.9.0_to_1.10.0.sql │ │ ├── 1.10.0_to_1.11.0.sql │ │ ├── 1.7.3_to_1.8.0.sql │ │ └── 2.0.0_to_2.1.0.sql └── README.md ├── entrypoint.sh ├── Dockerfile ├── src ├── main │ ├── java │ │ └── cc │ │ │ └── uncarbon │ │ │ ├── module │ │ │ ├── oss │ │ │ │ ├── constant │ │ │ │ │ └── OssConstant.java │ │ │ │ ├── config │ │ │ │ │ └── FileStorageConfiguration.java │ │ │ │ ├── mapper │ │ │ │ │ └── OssFileInfoMapper.java │ │ │ │ ├── enums │ │ │ │ │ ├── OssErrorEnum.java │ │ │ │ │ └── UploadFileCheckResultEnum.java │ │ │ │ ├── model │ │ │ │ │ ├── response │ │ │ │ │ │ ├── OssFileUploadResultVO.java │ │ │ │ │ │ ├── OssFileDownloadReplyBO.java │ │ │ │ │ │ └── OssFileInfoBO.java │ │ │ │ │ └── request │ │ │ │ │ │ ├── UploadFileAttributeDTO.java │ │ │ │ │ │ └── AdminListOssFileInfoDTO.java │ │ │ │ ├── facade │ │ │ │ │ └── OssUploadDownloadFacade.java │ │ │ │ ├── entity │ │ │ │ │ └── OssFileInfoEntity.java │ │ │ │ └── util │ │ │ │ │ └── UploadFileChecker.java │ │ │ ├── appapi │ │ │ │ ├── constant │ │ │ │ │ └── AppApiConstant.java │ │ │ │ └── web │ │ │ │ │ └── AppAuthController.java │ │ │ ├── adminapi │ │ │ │ ├── constant │ │ │ │ │ └── AdminApiConstant.java │ │ │ │ ├── model │ │ │ │ │ ├── interior │ │ │ │ │ │ └── AdminCaptchaContainer.java │ │ │ │ │ └── response │ │ │ │ │ │ └── AdminCaptchaVO.java │ │ │ │ ├── enums │ │ │ │ │ └── AdminApiErrorEnum.java │ │ │ │ ├── event │ │ │ │ │ └── KickOutSysUsersEvent.java │ │ │ │ ├── listener │ │ │ │ │ └── AdminApiEventListener.java │ │ │ │ └── web │ │ │ │ │ ├── sys │ │ │ │ │ ├── AdminSysLogController.java │ │ │ │ │ ├── AdminCurrentSysUserController.java │ │ │ │ │ ├── AdminSysDeptController.java │ │ │ │ │ ├── AdminSysParamController.java │ │ │ │ │ └── AdminSysMenuController.java │ │ │ │ │ ├── common │ │ │ │ │ └── AdminSelectOptionsController.java │ │ │ │ │ └── oss │ │ │ │ │ └── AdminOssFileInfoController.java │ │ │ ├── sys │ │ │ │ ├── mapper │ │ │ │ │ ├── SysLogMapper.java │ │ │ │ │ ├── SysMenuMapper.java │ │ │ │ │ ├── SysRoleMapper.java │ │ │ │ │ ├── SysParamMapper.java │ │ │ │ │ ├── SysTenantMapper.java │ │ │ │ │ ├── SysDataDictItemMapper.java │ │ │ │ │ ├── SysRoleMenuRelationMapper.java │ │ │ │ │ ├── SysUserDeptRelationMapper.java │ │ │ │ │ ├── SysUserRoleRelationMapper.java │ │ │ │ │ ├── SysDeptMapper.java │ │ │ │ │ ├── xml │ │ │ │ │ │ └── SysUserMapper.xml │ │ │ │ │ ├── SysDataDictClassifiedMapper.java │ │ │ │ │ └── SysUserMapper.java │ │ │ │ ├── enums │ │ │ │ │ ├── SysUserStatusEnum.java │ │ │ │ │ ├── SysLogStatusEnum.java │ │ │ │ │ ├── SysMenuTypeEnum.java │ │ │ │ │ └── SysErrorEnum.java │ │ │ │ ├── model │ │ │ │ │ ├── response │ │ │ │ │ │ ├── VbenAdminMenuMetaVO.java │ │ │ │ │ │ ├── SysTenantKickOutUsersBO.java │ │ │ │ │ │ ├── SysUserLoginVO.java │ │ │ │ │ │ ├── SysUserBaseInfoBO.java │ │ │ │ │ │ ├── VbenAdminUserInfoVO.java │ │ │ │ │ │ ├── IPLocationBO.java │ │ │ │ │ │ ├── SysUserLoginBO.java │ │ │ │ │ │ ├── SysParamBO.java │ │ │ │ │ │ ├── SysRoleBO.java │ │ │ │ │ │ ├── SysDeptBO.java │ │ │ │ │ │ ├── SysDataDictClassifiedBO.java │ │ │ │ │ │ ├── SysTenantBO.java │ │ │ │ │ │ ├── SysLogBO.java │ │ │ │ │ │ ├── SysDataDictItemBO.java │ │ │ │ │ │ ├── SysUserBO.java │ │ │ │ │ │ └── SysMenuBO.java │ │ │ │ │ ├── request │ │ │ │ │ │ ├── AdminSysDataDictClassifiedListDTO.java │ │ │ │ │ │ ├── AdminSysDataDictItemListDTO.java │ │ │ │ │ │ ├── AdminListSysParamDTO.java │ │ │ │ │ │ ├── AdminListSysRoleDTO.java │ │ │ │ │ │ ├── AdminBindRoleMenuRelationDTO.java │ │ │ │ │ │ ├── AdminBindUserRoleRelationDTO.java │ │ │ │ │ │ ├── AdminListSysTenantDTO.java │ │ │ │ │ │ ├── AdminResetSysUserPasswordDTO.java │ │ │ │ │ │ ├── AdminListSysUserDTO.java │ │ │ │ │ │ ├── AdminUpdateCurrentSysUserAvatarDTO.java │ │ │ │ │ │ ├── AdminInsertOrUpdateSysDeptDTO.java │ │ │ │ │ │ ├── AdminUpdateCurrentSysUserPasswordDTO.java │ │ │ │ │ │ ├── AdminUpdateSysTenantDTO.java │ │ │ │ │ │ ├── AdminInsertOrUpdateSysParamDTO.java │ │ │ │ │ │ ├── SysUserLoginDTO.java │ │ │ │ │ │ ├── AdminListSysLogDTO.java │ │ │ │ │ │ ├── AdminInsertOrUpdateSysRoleDTO.java │ │ │ │ │ │ ├── AdminInsertSysLogDTO.java │ │ │ │ │ │ ├── AdminSysDataDictClassifiedInsertOrUpdateDTO.java │ │ │ │ │ │ ├── AdminInsertSysTenantDTO.java │ │ │ │ │ │ ├── AdminUpdateCurrentSysUserInfoDTO.java │ │ │ │ │ │ ├── AdminInsertOrUpdateSysMenuDTO.java │ │ │ │ │ │ ├── AdminSysDataDictItemInsertOrUpdateDTO.java │ │ │ │ │ │ └── AdminInsertOrUpdateSysUserDTO.java │ │ │ │ │ └── interior │ │ │ │ │ │ ├── UserRoleContainer.java │ │ │ │ │ │ └── UserDeptContainer.java │ │ │ │ ├── util │ │ │ │ │ └── PwdUtil.java │ │ │ │ ├── facade │ │ │ │ │ └── SysTenantFacade.java │ │ │ │ ├── constant │ │ │ │ │ └── SysConstant.java │ │ │ │ ├── extension │ │ │ │ │ ├── SysLogAspectExtension.java │ │ │ │ │ └── impl │ │ │ │ │ │ └── DefaultSysLogAspectExtension.java │ │ │ │ ├── entity │ │ │ │ │ ├── SysUserDeptRelationEntity.java │ │ │ │ │ ├── SysUserRoleRelationEntity.java │ │ │ │ │ ├── SysRoleMenuRelationEntity.java │ │ │ │ │ ├── SysParamEntity.java │ │ │ │ │ ├── SysTenantEntity.java │ │ │ │ │ ├── SysRoleEntity.java │ │ │ │ │ ├── SysDataDictClassifiedEntity.java │ │ │ │ │ ├── SysDeptEntity.java │ │ │ │ │ ├── SysDataDictItemEntity.java │ │ │ │ │ ├── SysMenuEntity.java │ │ │ │ │ ├── SysLogEntity.java │ │ │ │ │ └── SysUserEntity.java │ │ │ │ ├── annotation │ │ │ │ │ └── SysLog.java │ │ │ │ └── service │ │ │ │ │ ├── SysUserDeptRelationService.java │ │ │ │ │ ├── SysUserRoleRelationService.java │ │ │ │ │ └── SysRoleMenuRelationService.java │ │ │ └── bizcommon │ │ │ │ ├── model │ │ │ │ └── request │ │ │ │ │ └── AdminBatchUpdateStatusDTO.java │ │ │ │ └── context │ │ │ │ └── PrivilegedTenantContext.java │ │ │ ├── HelioBootApplication.java │ │ │ ├── aspect │ │ │ └── extension │ │ │ │ └── SysLogAspectExtensionForSysUserLogin.java │ │ │ ├── config │ │ │ ├── CorsConfiguration.java │ │ │ ├── NotFoundConfiguration.java │ │ │ ├── AdminSaTokenExtendConfiguration.java │ │ │ ├── CustomInterceptorConfiguration.java │ │ │ └── AsyncConfiguration.java │ │ │ ├── interceptor │ │ │ ├── DefaultSaTokenParseInterceptor.java │ │ │ └── AdminSaTokenParseInterceptor.java │ │ │ └── helper │ │ │ ├── RolePermissionCacheHelper.java │ │ │ └── CaptchaHelper.java │ └── resources │ │ ├── i18n │ │ ├── messages.properties │ │ ├── messages_zh_CN.properties │ │ └── messages_en_US.properties │ │ ├── banner.txt │ │ ├── application-prod.yml │ │ ├── application-dev.yml │ │ ├── application-test.yml │ │ └── application.yml └── test │ └── java │ └── cc │ └── uncarbon │ └── test │ └── ExampleUnitTest.java ├── .gitignore └── LICENSE /多模块版请移步至helio-boot-modular项目.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /attachments/db/MySQL/upgrade/2.4.0_to_2.4.1.sql: -------------------------------------------------------------------------------- 1 | -- 新增'头像URL'字段 2 | ALTER TABLE sys_user ADD COLUMN avatar_url varchar(255) NULL COMMENT '头像URL'; 3 | -------------------------------------------------------------------------------- /attachments/db/PostgreSQL/upgrade/2.4.0_to_2.4.1.sql: -------------------------------------------------------------------------------- 1 | -- 新增'头像URL'字段 2 | ALTER TABLE sys_user ADD COLUMN avatar_url varchar(255); 3 | 4 | COMMENT ON COLUMN sys_user.avatar_url IS '头像URL'; 5 | -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo "The application will start ..." 3 | exec java ${JAVA_OPTS} ${APP_OPTS} -noverify -XX:+AlwaysPreTouch -Djava.security.egd=file:/dev/./urandom -jar app.jar "$@" 4 | -------------------------------------------------------------------------------- /attachments/db/PostgreSQL/upgrade/1.9.0_to_1.10.0.sql: -------------------------------------------------------------------------------- 1 | -- v1.10.0 - 补充遗漏的数据表默认值 2 | ALTER TABLE "oss_file_info" 3 | ALTER COLUMN "revision" SET DEFAULT 1, 4 | ALTER COLUMN "del_flag" SET DEFAULT 0; 5 | -------------------------------------------------------------------------------- /attachments/README.md: -------------------------------------------------------------------------------- 1 | > ## [快速启动步骤](https://helio.uncarbon.cc/#/i18n/zh-CN/helio-boot/quick-start) 2 | 3 | 1. 本目录存放的是 DB 预装数据表 schema,请根据自己的 DB 架构选择导入 4 | 2. `upgrade`目录存放脚手架版本升级时的 DB 变更 5 | 3. `helio_boot.sql`: 基础数据表,使用脚手架则必须导入 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # https://hub.docker.com/_/eclipse-temurin/tags 2 | FROM eclipse-temurin:17-jre 3 | COPY ./target/*.jar ./app.jar 4 | COPY entrypoint.sh / 5 | RUN chmod +x ./entrypoint.sh && \ 6 | ln -snf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo Asia/Shanghai > /etc/timezone 7 | ENTRYPOINT ["./entrypoint.sh"] 8 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/oss/constant/OssConstant.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.oss.constant; 2 | 3 | public final class OssConstant { 4 | private OssConstant() { 5 | } 6 | 7 | /** 8 | * 存储平台-本地存储-前缀 9 | */ 10 | public static final String PLATFORM_PREFIX_LOCAL = "local"; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/oss/config/FileStorageConfiguration.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.oss.config; 2 | 3 | import org.dromara.x.file.storage.spring.EnableFileStorage; 4 | import org.springframework.context.annotation.Configuration; 5 | 6 | 7 | @EnableFileStorage 8 | @Configuration 9 | public class FileStorageConfiguration { 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/appapi/constant/AppApiConstant.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.appapi.constant; 2 | 3 | 4 | /** 5 | * C端接口常量 6 | */ 7 | public final class AppApiConstant { 8 | private AppApiConstant() { 9 | } 10 | 11 | /** 12 | * HTTP-API路由前缀 13 | */ 14 | public static final String HTTP_API_URL_PREFIX = "/app"; 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/adminapi/constant/AdminApiConstant.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.adminapi.constant; 2 | 3 | 4 | /** 5 | * 后台管理接口常量 6 | */ 7 | public final class AdminApiConstant { 8 | private AdminApiConstant() { 9 | } 10 | 11 | /** 12 | * HTTP-API路由前缀 13 | */ 14 | public static final String HTTP_API_URL_PREFIX = "/admin"; 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/mapper/SysLogMapper.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.mapper; 2 | 3 | import cc.uncarbon.module.sys.entity.SysLogEntity; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | import org.apache.ibatis.annotations.Mapper; 6 | 7 | /** 8 | * 系统日志 9 | */ 10 | @Mapper 11 | public interface SysLogMapper extends BaseMapper { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/mapper/SysMenuMapper.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.mapper; 2 | 3 | import cc.uncarbon.module.sys.entity.SysMenuEntity; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | import org.apache.ibatis.annotations.Mapper; 6 | 7 | /** 8 | * 后台菜单 9 | */ 10 | @Mapper 11 | public interface SysMenuMapper extends BaseMapper { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/mapper/SysRoleMapper.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.mapper; 2 | 3 | import cc.uncarbon.module.sys.entity.SysRoleEntity; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | import org.apache.ibatis.annotations.Mapper; 6 | 7 | /** 8 | * 后台角色 9 | */ 10 | @Mapper 11 | public interface SysRoleMapper extends BaseMapper { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/mapper/SysParamMapper.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.mapper; 2 | 3 | import cc.uncarbon.module.sys.entity.SysParamEntity; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | import org.apache.ibatis.annotations.Mapper; 6 | 7 | /** 8 | * 系统参数 9 | */ 10 | @Mapper 11 | public interface SysParamMapper extends BaseMapper { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/mapper/SysTenantMapper.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.mapper; 2 | 3 | import cc.uncarbon.module.sys.entity.SysTenantEntity; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | import org.apache.ibatis.annotations.Mapper; 6 | 7 | /** 8 | * 系统租户 9 | */ 10 | @Mapper 11 | public interface SysTenantMapper extends BaseMapper { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/resources/i18n/messages.properties: -------------------------------------------------------------------------------- 1 | # 具体请参阅文档:进阶使用-国际化 2 | #-- Web 全局异常处理国际化消息 - 默认值 3 | GLOBAL__NO_LOGIN=请您先登录 4 | GLOBAL__PERMISSION_NOT_MATCH=您权限不足 5 | GLOBAL__ROLE_NOT_MATCH=您与要求角色不符 6 | GLOBAL__NOT_FOUND=你迷路啦 7 | GLOBAL__UNACCEPTABLE_PARAMETERS=错误参数格式或值 8 | GLOBAL__METHOD_NOT_ALLOWED=错误的请求方式 9 | GLOBAL__INTERNAL_ERROR=请稍后再试 10 | 11 | #-- SysErrorEnum 不指定则使用原有定义的label作为提示消息 12 | 13 | #-- OssErrorEnum 14 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/oss/mapper/OssFileInfoMapper.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.oss.mapper; 2 | 3 | import cc.uncarbon.module.oss.entity.OssFileInfoEntity; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | import org.apache.ibatis.annotations.Mapper; 6 | 7 | 8 | /** 9 | * 上传文件信息 10 | */ 11 | @Mapper 12 | public interface OssFileInfoMapper extends BaseMapper { 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/mapper/SysDataDictItemMapper.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.mapper; 2 | 3 | import cc.uncarbon.module.sys.entity.SysDataDictItemEntity; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | import org.apache.ibatis.annotations.Mapper; 6 | 7 | 8 | /** 9 | * 数据字典项 10 | */ 11 | @Mapper 12 | public interface SysDataDictItemMapper extends BaseMapper { 13 | 14 | } 15 | -------------------------------------------------------------------------------- /attachments/db/PostgreSQL/upgrade/1.10.0_to_1.11.0.sql: -------------------------------------------------------------------------------- 1 | -- v1.11.0 - 「后台角色」中,`title`字段更名为「角色名」,`value`字段更名为「角色编码」 2 | COMMENT ON COLUMN "sys_role"."title" IS '角色名'; 3 | COMMENT ON COLUMN "sys_role"."value" IS '角色编码'; 4 | 5 | -- v1.11.0 - 「数据字典」中,`value`字段更名为「数据值」 6 | COMMENT ON COLUMN "sys_data_dict"."value" IS '数据值'; 7 | 8 | -- v1.11.0 - 扩充「后台用户」数据表,「昵称」字段的长度上限为255 9 | ALTER TABLE "sys_user" 10 | ALTER COLUMN "nickname" TYPE varchar(255); 11 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/mapper/SysRoleMenuRelationMapper.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.mapper; 2 | 3 | import cc.uncarbon.module.sys.entity.SysRoleMenuRelationEntity; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | import org.apache.ibatis.annotations.Mapper; 6 | 7 | /** 8 | * 后台角色-可见菜单关联 9 | */ 10 | @Mapper 11 | public interface SysRoleMenuRelationMapper extends BaseMapper { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/mapper/SysUserDeptRelationMapper.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.mapper; 2 | 3 | import cc.uncarbon.module.sys.entity.SysUserDeptRelationEntity; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | import org.apache.ibatis.annotations.Mapper; 6 | 7 | /** 8 | * 后台用户-部门关联 9 | */ 10 | @Mapper 11 | public interface SysUserDeptRelationMapper extends BaseMapper { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/mapper/SysUserRoleRelationMapper.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.mapper; 2 | 3 | import cc.uncarbon.module.sys.entity.SysUserRoleRelationEntity; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | import org.apache.ibatis.annotations.Mapper; 6 | 7 | /** 8 | * 后台用户-角色关联 9 | */ 10 | @Mapper 11 | public interface SysUserRoleRelationMapper extends BaseMapper { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/resources/i18n/messages_zh_CN.properties: -------------------------------------------------------------------------------- 1 | #-- Web 全局异常处理国际化消息 - 中文简体 2 | GLOBAL__NO_LOGIN=请您先登录 3 | GLOBAL__PERMISSION_NOT_MATCH=您权限不足 4 | GLOBAL__ROLE_NOT_MATCH=您与要求角色不符 5 | GLOBAL__NOT_FOUND=你迷路啦 6 | GLOBAL__UNACCEPTABLE_PARAMETERS=错误参数格式或值 7 | GLOBAL__METHOD_NOT_ALLOWED=错误的请求方式 8 | GLOBAL__INTERNAL_ERROR=请稍后再试 9 | 10 | #-- SysErrorEnum 不指定则使用原有定义的label作为提示消息 11 | # SysErrorEnum.INCORRECT_PIN_OR_PWD=你账号或者密码是不是有啥子输错了噢? 12 | 13 | #-- OssErrorEnum 14 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/adminapi/model/interior/AdminCaptchaContainer.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.adminapi.model.interior; 2 | 3 | import cn.hutool.captcha.AbstractCaptcha; 4 | 5 | import java.time.LocalDateTime; 6 | 7 | /** 8 | * 后台管理-验证码容器 9 | * 10 | * @param image 验证码图片对象 11 | * @param uuid 验证码唯一标识(UUID) 12 | * @param expiredAt 验证码失效时刻 13 | */ 14 | public record AdminCaptchaContainer(AbstractCaptcha image, String uuid, LocalDateTime expiredAt) { 15 | } 16 | -------------------------------------------------------------------------------- /src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | ${AnsiColor.BLUE} 2 | _ _ _ _ ______ _ 3 | | | | | | |(_) | ___ \ | | 4 | | |_| | ___ | | _ ___ ______ | |_/ / ___ ___ | |_ 5 | | _ | / _ \| || | / _ \ |______|| ___ \ / _ \ / _ \ | __| 6 | | | | || __/| || || (_) | | |_/ /| (_) || (_) || |_ 7 | \_| |_/ \___||_||_| \___/ \____/ \___/ \___/ \__| 8 | 9 | 10 | ${AnsiColor.GREEN} 11 | Spring Boot Version: ${spring-boot.version} 12 | 13 | ${AnsiColor.WHITE} 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | 25 | # MacOS 26 | .DS_Store 27 | 28 | # IntelliJ IDEA 29 | .idea 30 | *.iws 31 | *.iml 32 | *.ipr 33 | 34 | # VSCode 35 | .vscode 36 | 37 | # VSCode 不会自动忽略 **/target 目录 38 | target/ 39 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/HelioBootApplication.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon; 2 | 3 | import cc.uncarbon.framework.crud.annotation.EnableInitHikariPoolAtStartup; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | 7 | 8 | /** 9 | * @author Uncarbon 10 | */ 11 | @EnableInitHikariPoolAtStartup 12 | @SpringBootApplication 13 | public class HelioBootApplication { 14 | public static void main(String[] args) { 15 | SpringApplication.run(HelioBootApplication.class, args); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/enums/SysUserStatusEnum.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.enums; 2 | 3 | import cc.uncarbon.framework.core.enums.HelioBaseEnum; 4 | import com.baomidou.mybatisplus.annotation.EnumValue; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Getter; 7 | 8 | 9 | /** 10 | * 预置系统后台用户状态枚举类 11 | */ 12 | @AllArgsConstructor 13 | @Getter 14 | public enum SysUserStatusEnum implements HelioBaseEnum { 15 | 16 | BANNED(0, "封禁"), 17 | ENABLED(1, "正常"),; 18 | 19 | @EnumValue 20 | private final Integer value; 21 | private final String label; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/enums/SysLogStatusEnum.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.enums; 2 | 3 | import cc.uncarbon.framework.core.enums.HelioBaseEnum; 4 | import com.baomidou.mybatisplus.annotation.EnumValue; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Getter; 7 | 8 | 9 | /** 10 | * 预置系统日志状态枚举类 11 | */ 12 | @AllArgsConstructor 13 | @Getter 14 | public enum SysLogStatusEnum implements HelioBaseEnum { 15 | 16 | NON_EXECUTION(0, "未执行"), 17 | SUCCESS(1, "成功"), 18 | FAILED(2, "失败"),; 19 | 20 | @EnumValue 21 | private final Integer value; 22 | private final String label; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/model/response/VbenAdminMenuMetaVO.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.model.response; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | 7 | import java.io.Serializable; 8 | 9 | 10 | /** 11 | * 后台菜单子项详情 for VbenAdmin 12 | */ 13 | @AllArgsConstructor 14 | @Data 15 | public class VbenAdminMenuMetaVO implements Serializable { 16 | 17 | @Schema(description = "标题") 18 | private String title; 19 | 20 | @Schema(description = "是否不可关闭") 21 | private Boolean affix; 22 | 23 | @Schema(description = "图标") 24 | private String icon; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/oss/enums/OssErrorEnum.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.oss.enums; 2 | 3 | import cc.uncarbon.framework.core.enums.HelioBaseEnum; 4 | import com.baomidou.mybatisplus.annotation.EnumValue; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Getter; 7 | 8 | 9 | /** 10 | * oss模块错误枚举类 11 | */ 12 | @AllArgsConstructor 13 | @Getter 14 | public enum OssErrorEnum implements HelioBaseEnum { 15 | 16 | INVALID_ID(400, "无效ID"), 17 | FILE_UPLOAD_FAILED(500, "文件上传失败,请联系管理员"), 18 | FILE_DOWNLOAD_FAILED(500, "文件下载失败,请联系管理员"),; 19 | 20 | @EnumValue 21 | private final Integer value; 22 | private final String label; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/adminapi/enums/AdminApiErrorEnum.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.adminapi.enums; 2 | 3 | import cc.uncarbon.framework.core.enums.HelioBaseEnum; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | 7 | 8 | /** 9 | * admin-api模块错误枚举类 10 | */ 11 | @AllArgsConstructor 12 | @Getter 13 | public enum AdminApiErrorEnum implements HelioBaseEnum { 14 | 15 | CAPTCHA_GENERATE_FAILED(500, "验证码生成失败,请稍后再试"), 16 | CAPTCHA_VALIDATE_FAILED(400, "验证码不正确,请重新输入"), 17 | // 很少遇到;但是如果出现了只会提示默认的「请稍后再试」,不方便排查,还是整个文案比较好 18 | UPLOAD_FILE_NOT_EXIST(400, "欲上传的文件可能已被删除,请重新选择"),; 19 | 20 | private final Integer value; 21 | private final String label; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/mapper/SysDeptMapper.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.mapper; 2 | 3 | import cc.uncarbon.module.sys.entity.SysDeptEntity; 4 | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; 5 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 6 | import org.apache.ibatis.annotations.Mapper; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * 部门 12 | */ 13 | @Mapper 14 | public interface SysDeptMapper extends BaseMapper { 15 | 16 | /** 17 | * 列举已排序好的所有部门列表 18 | */ 19 | default List sortedList() { 20 | return selectList(new LambdaQueryWrapper().orderByAsc(SysDeptEntity::getSort)); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/model/request/AdminSysDataDictClassifiedListDTO.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.model.request; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | import lombok.experimental.Accessors; 8 | import lombok.experimental.SuperBuilder; 9 | 10 | import java.io.Serializable; 11 | 12 | 13 | /** 14 | * 后台管理-分页列表数据字典分类 DTO 15 | */ 16 | @Accessors(chain = true) 17 | @SuperBuilder 18 | @AllArgsConstructor 19 | @NoArgsConstructor 20 | @Data 21 | public class AdminSysDataDictClassifiedListDTO implements Serializable { 22 | 23 | @Schema(description = "分类编码") 24 | private String code; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/model/request/AdminSysDataDictItemListDTO.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.model.request; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | import lombok.experimental.Accessors; 8 | import lombok.experimental.SuperBuilder; 9 | 10 | import java.io.Serializable; 11 | 12 | 13 | /** 14 | * 后台管理-分页列表数据字典项 DTO 15 | */ 16 | @Accessors(chain = true) 17 | @SuperBuilder 18 | @AllArgsConstructor 19 | @NoArgsConstructor 20 | @Data 21 | public class AdminSysDataDictItemListDTO implements Serializable { 22 | 23 | @Schema(description = "所属分类ID", hidden = true) 24 | private Long classifiedId; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/model/response/SysTenantKickOutUsersBO.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.model.response; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.Getter; 5 | 6 | import java.util.Collections; 7 | import java.util.List; 8 | 9 | /** 10 | * 系统租户-需强制登出用户 BO 11 | * 同一时间大量登出,会操作大量Redis键,可能存在缓存雪崩的风险 12 | */ 13 | @Getter 14 | public class SysTenantKickOutUsersBO { 15 | 16 | @Schema(description = "后台用户IDs") 17 | private final List sysUserIds; 18 | 19 | public SysTenantKickOutUsersBO() { 20 | this.sysUserIds = Collections.emptyList(); 21 | } 22 | 23 | public SysTenantKickOutUsersBO(List sysUserIds) { 24 | this.sysUserIds = sysUserIds; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /attachments/db/MySQL/upgrade/1.10.0_to_1.11.0.sql: -------------------------------------------------------------------------------- 1 | -- v1.11.0 - 「后台角色」中,`title`字段更名为「角色名」,`value`字段更名为「角色编码」 2 | ALTER TABLE sys_role 3 | MODIFY COLUMN title varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色名', 4 | MODIFY COLUMN `value` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色编码'; 5 | 6 | -- v1.11.0 - 「数据字典」中,`value`字段更名为「数据值」 7 | ALTER TABLE sys_data_dict 8 | MODIFY COLUMN `value` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '数据值'; 9 | 10 | -- v1.11.0 - 扩充「后台用户」数据表,「昵称」字段的长度上限为255 11 | ALTER TABLE sys_user 12 | MODIFY COLUMN `nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '昵称'; 13 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/model/request/AdminListSysParamDTO.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.model.request; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | import lombok.experimental.Accessors; 9 | 10 | import java.io.Serializable; 11 | 12 | 13 | /** 14 | * 后台管理-分页列表系统参数 15 | */ 16 | @Accessors(chain = true) 17 | @Builder 18 | @AllArgsConstructor 19 | @NoArgsConstructor 20 | @Data 21 | public class AdminListSysParamDTO implements Serializable { 22 | 23 | @Schema(description = "键名") 24 | private String name; 25 | 26 | @Schema(description = "描述") 27 | private String description; 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/model/request/AdminListSysRoleDTO.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.model.request; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | import lombok.experimental.Accessors; 9 | 10 | import java.io.Serializable; 11 | 12 | 13 | /** 14 | * 后台管理-分页列表后台角色 15 | */ 16 | @Accessors(chain = true) 17 | @Builder 18 | @AllArgsConstructor 19 | @NoArgsConstructor 20 | @Data 21 | public class AdminListSysRoleDTO implements Serializable { 22 | 23 | @Schema(description = "角色名(关键词)") 24 | private String title; 25 | 26 | @Schema(description = "角色编码(关键词)") 27 | private String value; 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/util/PwdUtil.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.util; 2 | 3 | import cn.hutool.core.text.CharSequenceUtil; 4 | import cn.hutool.crypto.SecureUtil; 5 | import lombok.experimental.UtilityClass; 6 | 7 | /** 8 | * 密码加密工具类 9 | * 可以根据自己的业务需要, 修改加密算法(如加盐等) 10 | */ 11 | @UtilityClass 12 | public class PwdUtil { 13 | public static String encrypt(String str, String salt) { 14 | if (CharSequenceUtil.isEmpty(str)) { 15 | return ""; 16 | } 17 | 18 | // 第一步: 2次MD5, 这一步可以放前端完成 19 | String step1 = SecureUtil.md5(SecureUtil.md5(str)); 20 | 21 | // 第二步: 拼接salt 22 | String step2 = salt + step1 + salt; 23 | 24 | // 第三步: SHA256 25 | return SecureUtil.sha256(step2); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/facade/SysTenantFacade.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.facade; 2 | 3 | import cc.uncarbon.module.sys.model.request.AdminInsertSysTenantDTO; 4 | import cc.uncarbon.module.sys.model.request.AdminUpdateSysTenantDTO; 5 | import cc.uncarbon.module.sys.model.response.SysTenantKickOutUsersBO; 6 | 7 | import java.util.Collection; 8 | 9 | /** 10 | * 系统租户解耦层,用于解决循环依赖 11 | */ 12 | public interface SysTenantFacade { 13 | 14 | /** 15 | * 后台管理-新增 16 | */ 17 | Long adminInsert(AdminInsertSysTenantDTO dto); 18 | 19 | /** 20 | * 后台管理-编辑 21 | */ 22 | SysTenantKickOutUsersBO adminUpdate(AdminUpdateSysTenantDTO dto); 23 | 24 | /** 25 | * 后台管理-删除 26 | */ 27 | SysTenantKickOutUsersBO adminDelete(Collection ids); 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/adminapi/event/KickOutSysUsersEvent.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.adminapi.event; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | import org.springframework.context.ApplicationEvent; 6 | 7 | import java.util.Collection; 8 | 9 | /** 10 | * 强制登出后台用户事件 11 | */ 12 | @Getter 13 | public final class KickOutSysUsersEvent extends ApplicationEvent { 14 | 15 | private final transient EventData data; 16 | 17 | public KickOutSysUsersEvent(EventData data) { 18 | super(data); 19 | this.data = data; 20 | } 21 | 22 | @Getter 23 | @RequiredArgsConstructor 24 | public static final class EventData { 25 | 26 | /** 27 | * 需要被强制登出的后台用户IDs 28 | */ 29 | private final Collection sysUserIds; 30 | 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/model/request/AdminBindRoleMenuRelationDTO.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.model.request; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | import lombok.experimental.Accessors; 9 | 10 | import java.io.Serializable; 11 | import java.util.Set; 12 | 13 | 14 | /** 15 | * 后台管理-绑定角色与菜单关联关系 16 | */ 17 | @Accessors(chain = true) 18 | @Builder 19 | @AllArgsConstructor 20 | @NoArgsConstructor 21 | @Data 22 | public class AdminBindRoleMenuRelationDTO implements Serializable { 23 | 24 | @Schema(description = "菜单Ids(空=清理关联关系后不再绑定任何菜单)") 25 | private Set menuIds; 26 | 27 | @Schema(description = "角色ID", hidden = true) 28 | private Long roleId; 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/model/request/AdminBindUserRoleRelationDTO.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.model.request; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | import lombok.experimental.Accessors; 9 | 10 | import java.io.Serializable; 11 | import java.util.Set; 12 | 13 | 14 | /** 15 | * 后台管理-绑定用户与角色关联关系 16 | */ 17 | @Accessors(chain = true) 18 | @Builder 19 | @AllArgsConstructor 20 | @NoArgsConstructor 21 | @Data 22 | public class AdminBindUserRoleRelationDTO implements Serializable { 23 | 24 | @Schema(description = "角色Ids(空=清理关联关系后不再绑定任何角色)") 25 | private Set roleIds; 26 | 27 | @Schema(description = "用户ID", hidden = true) 28 | private Long userId; 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/constant/SysConstant.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.constant; 2 | 3 | 4 | /** 5 | * 系统管理常量 6 | */ 7 | public final class SysConstant { 8 | private SysConstant() { 9 | } 10 | 11 | /** 12 | * 无上级节点的父级ID 13 | */ 14 | public static final Long ROOT_PARENT_ID = 0L; 15 | 16 | /** 17 | * Vben Admin后台管理-空页面 18 | */ 19 | public static final String VBEN_ADMIN_BLANK_VIEW = "LAYOUT"; 20 | 21 | /** 22 | * 超级管理员角色ID 23 | */ 24 | public static final Long SUPER_ADMIN_ROLE_ID = 1L; 25 | 26 | /** 27 | * 超级管理员角色值(固定) 28 | */ 29 | public static final String SUPER_ADMIN_ROLE_VALUE = "SuperAdmin"; 30 | 31 | /** 32 | * 租户管理员角色值 33 | * 为了外显美观没有在前面增加Tenant字样 34 | */ 35 | public static final String TENANT_ADMIN_ROLE_VALUE = "Admin"; 36 | 37 | } 38 | -------------------------------------------------------------------------------- /attachments/db/MySQL/upgrade/1.7.3_to_1.8.0.sql: -------------------------------------------------------------------------------- 1 | -- v1.8.0 - Uncarbon - 新增错误原因堆栈、用户UA、IP属地字段;表注释更新 2 | ALTER TABLE sys_log 3 | ADD COLUMN error_stacktrace varchar(3000) NOT NULL DEFAULT '' COMMENT '错误原因堆栈' AFTER status, 4 | ADD COLUMN user_agent varchar(255) NOT NULL DEFAULT '' COMMENT '用户UA' AFTER error_stacktrace, 5 | ADD COLUMN ip_location_region_name varchar(100) NOT NULL DEFAULT '' COMMENT 'IP地址属地-国家或地区名' AFTER user_agent, 6 | ADD COLUMN ip_location_province_name varchar(100) NOT NULL DEFAULT '' COMMENT 'IP地址属地-省级行政区名' AFTER ip_location_region_name, 7 | ADD COLUMN ip_location_city_name varchar(100) NOT NULL DEFAULT '' COMMENT 'IP地址属地-市级行政区名' AFTER ip_location_province_name, 8 | ADD COLUMN ip_location_district_name varchar(100) NOT NULL DEFAULT '' COMMENT 'IP地址属地-县级行政区名' AFTER ip_location_city_name; 9 | ALTER TABLE sys_log COMMENT = '系统日志'; 10 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/oss/model/response/OssFileUploadResultVO.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.oss.model.response; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | import lombok.experimental.Accessors; 8 | 9 | import java.io.Serializable; 10 | 11 | 12 | /** 13 | * 上传文件结果 VO 14 | */ 15 | @Accessors(chain = true) 16 | @AllArgsConstructor 17 | @NoArgsConstructor 18 | @Data 19 | public class OssFileUploadResultVO implements Serializable { 20 | 21 | @Schema(description = "文件ID") 22 | private Long fileId; 23 | 24 | @Schema(description = "存储文件名") 25 | private String filename; 26 | 27 | @Schema(description = "完整外链") 28 | private String url; 29 | 30 | @Schema(description = "原始文件名") 31 | private String originalFilename; 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/model/request/AdminListSysTenantDTO.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.model.request; 2 | 3 | import cc.uncarbon.framework.core.enums.EnabledStatusEnum; 4 | import io.swagger.v3.oas.annotations.media.Schema; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | import lombok.experimental.Accessors; 10 | 11 | import java.io.Serializable; 12 | 13 | 14 | /** 15 | * 后台管理-分页列表系统租户 16 | */ 17 | @Accessors(chain = true) 18 | @Builder 19 | @AllArgsConstructor 20 | @NoArgsConstructor 21 | @Data 22 | public class AdminListSysTenantDTO implements Serializable { 23 | 24 | @Schema(description = "租户名(关键词)") 25 | private String tenantName; 26 | 27 | @Schema(description = "租户ID(纯数字)") 28 | private Long tenantId; 29 | 30 | @Schema(description = "状态") 31 | private EnabledStatusEnum status; 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/adminapi/model/response/AdminCaptchaVO.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.adminapi.model.response; 2 | 3 | import cc.uncarbon.module.adminapi.model.interior.AdminCaptchaContainer; 4 | import io.swagger.v3.oas.annotations.media.Schema; 5 | import lombok.Getter; 6 | 7 | import java.time.LocalDateTime; 8 | 9 | /** 10 | * 后台管理-验证码 VO 11 | */ 12 | @Getter 13 | public class AdminCaptchaVO { 14 | 15 | @Schema(description = "验证码图片Base64") 16 | private final String captchaImage; 17 | 18 | @Schema(description = "验证码唯一标识") 19 | private final String captchaId; 20 | 21 | @Schema(description = "验证码失效时刻") 22 | private final LocalDateTime expiredAt; 23 | 24 | 25 | public AdminCaptchaVO(AdminCaptchaContainer source) { 26 | this.captchaImage = source.image().getImageBase64Data(); 27 | this.captchaId = source.uuid(); 28 | this.expiredAt = source.expiredAt(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/oss/enums/UploadFileCheckResultEnum.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.oss.enums; 2 | 3 | import cc.uncarbon.framework.core.enums.HelioBaseEnum; 4 | import com.baomidou.mybatisplus.annotation.EnumValue; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Getter; 7 | 8 | /** 9 | * 前端上传文件检查结果枚举类 10 | */ 11 | @AllArgsConstructor 12 | @Getter 13 | public enum UploadFileCheckResultEnum implements HelioBaseEnum { 14 | 15 | OK(200, "正常"), 16 | NO_FILE(401, "缺少欲上传的文件"), 17 | TOO_MANY_FILES(402, "欲上传的文件数量超出限制"), 18 | TOO_LARGE_FILE_SIZE(403, "欲上传的文件大小超出限制"), 19 | EMPTY_FILE(404, "欲上传的文件为空"), 20 | ILLEGAL_FILE_SUFFIX(405, "欲上传的文件类型超出限制"),; 21 | 22 | @EnumValue 23 | private final Integer value; 24 | private final String label; 25 | 26 | public boolean isOK() { 27 | return this == OK; 28 | } 29 | 30 | public boolean isNotOK() { 31 | return !isOK(); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/mapper/xml/SysUserMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 13 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/model/response/SysUserLoginVO.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.model.response; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | import lombok.experimental.Accessors; 8 | import lombok.experimental.SuperBuilder; 9 | 10 | import java.io.Serializable; 11 | import java.util.Collection; 12 | 13 | 14 | /** 15 | * 登录后返回的字段 16 | * 用于返回给前端 17 | */ 18 | @Accessors(chain = true) 19 | @SuperBuilder 20 | @AllArgsConstructor 21 | @NoArgsConstructor 22 | @Data 23 | public class SysUserLoginVO implements Serializable { 24 | 25 | @Schema(description = "token名称") 26 | private String tokenName; 27 | 28 | @Schema(description = "token值") 29 | private String tokenValue; 30 | 31 | @Schema(description = "对应角色") 32 | private Collection roles; 33 | 34 | @Schema(description = "拥有权限") 35 | private Collection permissions; 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/aspect/extension/SysLogAspectExtensionForSysUserLogin.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.aspect.extension; 2 | 3 | import cc.uncarbon.module.sys.annotation.SysLog; 4 | import cc.uncarbon.module.sys.extension.impl.DefaultSysLogAspectExtension; 5 | import cc.uncarbon.module.sys.model.request.AdminInsertSysLogDTO; 6 | import cc.uncarbon.module.sys.model.request.SysUserLoginDTO; 7 | import lombok.NoArgsConstructor; 8 | import org.aspectj.lang.JoinPoint; 9 | 10 | /** 11 | * SysLog 切面实现类扩展 for 登录后台用户 12 | */ 13 | @NoArgsConstructor 14 | public class SysLogAspectExtensionForSysUserLogin extends DefaultSysLogAspectExtension { 15 | 16 | @Override 17 | public void beforeSaving(AdminInsertSysLogDTO insertSysLogDTO, JoinPoint joinPoint, SysLog annotation, Throwable e, Object ret) { 18 | for (Object arg : joinPoint.getArgs()) { 19 | if (arg instanceof SysUserLoginDTO dto) { 20 | insertSysLogDTO.setUsername(dto.getUsername()); 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/model/request/AdminResetSysUserPasswordDTO.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.model.request; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import jakarta.validation.constraints.NotBlank; 5 | import jakarta.validation.constraints.Size; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Builder; 8 | import lombok.Data; 9 | import lombok.NoArgsConstructor; 10 | import lombok.experimental.Accessors; 11 | 12 | import java.io.Serializable; 13 | 14 | 15 | /** 16 | * 后台管理-重置某用户密码 17 | */ 18 | @Accessors(chain = true) 19 | @Builder 20 | @AllArgsConstructor 21 | @NoArgsConstructor 22 | @Data 23 | public class AdminResetSysUserPasswordDTO implements Serializable { 24 | 25 | @Schema(description = "随机新密码", requiredMode = Schema.RequiredMode.REQUIRED) 26 | @Size(min = 16, max = 64, message = "【随机新密码】最短16位,最长64位") 27 | @NotBlank(message = "随机新密码不能为空") 28 | private String randomPassword; 29 | 30 | @Schema(description = "用户ID", hidden = true) 31 | private Long userId; 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/extension/SysLogAspectExtension.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.extension; 2 | 3 | import cc.uncarbon.module.sys.annotation.SysLog; 4 | import cc.uncarbon.module.sys.model.request.AdminInsertSysLogDTO; 5 | import cc.uncarbon.module.sys.model.response.IPLocationBO; 6 | import org.aspectj.lang.JoinPoint; 7 | 8 | /** 9 | * SysLog 切面实现类扩展 10 | * 11 | */ 12 | public interface SysLogAspectExtension { 13 | 14 | /** 15 | * 查询IP地址属地 16 | * @param ip IPv4或IPv6地址 17 | */ 18 | default IPLocationBO queryIPLocation(String ip) { 19 | return IPLocationBO.unknown(); 20 | } 21 | 22 | /** 23 | * 保存到 DB 前 24 | * @param insertSysLogDTO 新系统日志DTO 25 | * @param joinPoint 切点 26 | * @param annotation 注解实例 27 | * @param e 异常实例;如果没有发生异常则值为null 28 | * @param ret 原始方法过程,于顺利运行时的返回值 29 | */ 30 | default void beforeSaving(final AdminInsertSysLogDTO insertSysLogDTO, final JoinPoint joinPoint, SysLog annotation, final Throwable e, Object ret) { 31 | 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/oss/model/response/OssFileDownloadReplyBO.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.oss.model.response; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | import lombok.experimental.Accessors; 8 | import lombok.experimental.SuperBuilder; 9 | 10 | import java.io.Serializable; 11 | 12 | 13 | /** 14 | * 下载文件结果 BO 15 | */ 16 | @Accessors(chain = true) 17 | @SuperBuilder 18 | @AllArgsConstructor 19 | @NoArgsConstructor 20 | @Data 21 | public class OssFileDownloadReplyBO implements Serializable { 22 | 23 | @Schema(description = "是否直接重定向到对象存储直链", title = "如果允许客户端直接从“对象存储直链”下载,则本字段可以置 true") 24 | private boolean redirect2DirectUrl; 25 | 26 | @Schema(description = "文件数据", title = "如果允许客户端直接从“对象存储直链”下载,则本字段可以置空") 27 | private byte[] fileBytes; 28 | 29 | @Schema(description = "对象存储直链") 30 | private String directUrl; 31 | 32 | @Schema(description = "存储文件名") 33 | private String storageFilename; 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/config/CorsConfiguration.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.config; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.web.servlet.config.annotation.CorsRegistry; 5 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 6 | 7 | /** 8 | * Spring Boot 2.4+ 跨域设置需要变化 9 | * 参考https://blog.csdn.net/jxysgzs/article/details/110818712 10 | * 11 | * 因可能个别项目需要特别的跨域设置,单拎出来放在这 12 | * 13 | * @author Uncarbon 14 | */ 15 | @Configuration 16 | public class CorsConfiguration implements WebMvcConfigurer { 17 | 18 | @Override 19 | public void addCorsMappings(CorsRegistry registry) { 20 | registry 21 | // 设置允许跨域的路由规则 22 | .addMapping("/**") 23 | // 设置允许跨域请求的域名 24 | .allowedOriginPatterns("*") 25 | // 是否允许证书(cookies) 26 | .allowCredentials(true) 27 | // 设置允许的方法 28 | .allowedMethods("*") 29 | // 跨域允许时间 30 | .maxAge(3600); 31 | } 32 | } -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/config/NotFoundConfiguration.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.config; 2 | 3 | import io.swagger.v3.oas.annotations.Operation; 4 | import org.springframework.boot.web.servlet.error.ErrorController; 5 | import org.springframework.http.HttpHeaders; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.bind.annotation.RestController; 8 | import org.springframework.web.servlet.NoHandlerFoundException; 9 | 10 | import jakarta.servlet.http.HttpServletRequest; 11 | 12 | /** 13 | * 由于除 404 以外的异常都会被全局异常处理掉,所以走到这里的请求都是 404 了 14 | * 15 | * @author Uncarbon 16 | */ 17 | @RestController 18 | public class NotFoundConfiguration implements ErrorController { 19 | 20 | /** 21 | * 不生成接口文档 22 | * 主动抛出异常,由全局异常处理接管 23 | */ 24 | @Operation(hidden = true) 25 | @RequestMapping(value = "/error") 26 | public void error(HttpServletRequest request) throws NoHandlerFoundException { 27 | throw new NoHandlerFoundException(request.getMethod(), request.getRequestURI(), new HttpHeaders()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/model/request/AdminListSysUserDTO.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.model.request; 2 | 3 | import cc.uncarbon.module.sys.constant.SysConstant; 4 | import io.swagger.v3.oas.annotations.media.Schema; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | import lombok.experimental.Accessors; 10 | 11 | import java.io.Serializable; 12 | import java.util.Objects; 13 | 14 | 15 | /** 16 | * 后台管理-分页列表后台用户 17 | */ 18 | @Accessors(chain = true) 19 | @Builder 20 | @AllArgsConstructor 21 | @NoArgsConstructor 22 | @Data 23 | public class AdminListSysUserDTO implements Serializable { 24 | 25 | @Schema(description = "手机号(关键词)") 26 | private String phoneNo; 27 | 28 | @Schema(description = "手动选择的部门ID") 29 | private Long selectedDeptId; 30 | 31 | /** 32 | * 是否需要根据【手动选择的部门】筛选用户 33 | */ 34 | public boolean needFilterBySelectedDeptId() { 35 | return Objects.nonNull(selectedDeptId) && selectedDeptId > SysConstant.ROOT_PARENT_ID; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/bizcommon/model/request/AdminBatchUpdateStatusDTO.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.bizcommon.model.request; 2 | 3 | import cc.uncarbon.framework.core.enums.HelioBaseEnum; 4 | import io.swagger.v3.oas.annotations.media.Schema; 5 | import jakarta.validation.constraints.NotEmpty; 6 | import jakarta.validation.constraints.NotNull; 7 | import lombok.AllArgsConstructor; 8 | import lombok.Data; 9 | import lombok.NoArgsConstructor; 10 | import lombok.experimental.Accessors; 11 | 12 | import java.io.Serializable; 13 | import java.util.Collection; 14 | 15 | /** 16 | * 批量修改状态公共类 17 | */ 18 | @Accessors(chain = true) 19 | @AllArgsConstructor 20 | @NoArgsConstructor 21 | @Data 22 | public class AdminBatchUpdateStatusDTO> implements Serializable { 23 | 24 | @Schema(description = "主键IDs") 25 | @NotEmpty(message = "ID不能为空") 26 | private Collection ids; 27 | 28 | @Schema(description = "新状态") 29 | @NotNull(message = "新状态不能为空") 30 | private E newStatus; 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/entity/SysUserDeptRelationEntity.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.entity; 2 | 3 | import cc.uncarbon.framework.crud.entity.HelioNoTenantBaseEntity; 4 | import com.baomidou.mybatisplus.annotation.TableField; 5 | import com.baomidou.mybatisplus.annotation.TableName; 6 | import io.swagger.v3.oas.annotations.media.Schema; 7 | import lombok.AllArgsConstructor; 8 | import lombok.Data; 9 | import lombok.EqualsAndHashCode; 10 | import lombok.NoArgsConstructor; 11 | import lombok.experimental.Accessors; 12 | import lombok.experimental.SuperBuilder; 13 | 14 | 15 | /** 16 | * 后台用户-部门关联 17 | */ 18 | @EqualsAndHashCode(callSuper = true) 19 | @Accessors(chain = true) 20 | @SuperBuilder 21 | @AllArgsConstructor 22 | @NoArgsConstructor 23 | @Data 24 | @TableName(value = "sys_user_dept_relation") 25 | public class SysUserDeptRelationEntity extends HelioNoTenantBaseEntity { 26 | 27 | @Schema(description = "用户ID") 28 | @TableField(value = "user_id") 29 | private Long userId; 30 | 31 | @Schema(description = "部门ID") 32 | @TableField(value = "dept_id") 33 | private Long deptId; 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/entity/SysUserRoleRelationEntity.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.entity; 2 | 3 | import cc.uncarbon.framework.crud.entity.HelioNoTenantBaseEntity; 4 | import com.baomidou.mybatisplus.annotation.TableField; 5 | import com.baomidou.mybatisplus.annotation.TableName; 6 | import io.swagger.v3.oas.annotations.media.Schema; 7 | import lombok.AllArgsConstructor; 8 | import lombok.Data; 9 | import lombok.EqualsAndHashCode; 10 | import lombok.NoArgsConstructor; 11 | import lombok.experimental.Accessors; 12 | import lombok.experimental.SuperBuilder; 13 | 14 | 15 | /** 16 | * 后台用户-角色关联 17 | */ 18 | @EqualsAndHashCode(callSuper = true) 19 | @Accessors(chain = true) 20 | @SuperBuilder 21 | @AllArgsConstructor 22 | @NoArgsConstructor 23 | @Data 24 | @TableName(value = "sys_user_role_relation") 25 | public class SysUserRoleRelationEntity extends HelioNoTenantBaseEntity { 26 | 27 | @Schema(description = "用户ID") 28 | @TableField(value = "user_id") 29 | private Long userId; 30 | 31 | @Schema(description = "角色ID") 32 | @TableField(value = "role_id") 33 | private Long roleId; 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/entity/SysRoleMenuRelationEntity.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.entity; 2 | 3 | import cc.uncarbon.framework.crud.entity.HelioNoTenantBaseEntity; 4 | import com.baomidou.mybatisplus.annotation.TableField; 5 | import com.baomidou.mybatisplus.annotation.TableName; 6 | import io.swagger.v3.oas.annotations.media.Schema; 7 | import lombok.AllArgsConstructor; 8 | import lombok.Data; 9 | import lombok.EqualsAndHashCode; 10 | import lombok.NoArgsConstructor; 11 | import lombok.experimental.Accessors; 12 | import lombok.experimental.SuperBuilder; 13 | 14 | 15 | /** 16 | * 后台角色-可见菜单关联 17 | */ 18 | @EqualsAndHashCode(callSuper = true) 19 | @Accessors(chain = true) 20 | @SuperBuilder 21 | @AllArgsConstructor 22 | @NoArgsConstructor 23 | @Data 24 | @TableName(value = "sys_role_menu_relation") 25 | public class SysRoleMenuRelationEntity extends HelioNoTenantBaseEntity { 26 | 27 | @Schema(description = "角色ID") 28 | @TableField(value = "role_id") 29 | private Long roleId; 30 | 31 | @Schema(description = "菜单ID") 32 | @TableField(value = "menu_id") 33 | private Long menuId; 34 | 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 uncarbon97 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /attachments/db/PostgreSQL/upgrade/1.7.3_to_1.8.0.sql: -------------------------------------------------------------------------------- 1 | -- v1.8.0 - Uncarbon - 新增错误原因堆栈、用户UA、IP属地字段;表注释更新 2 | ALTER TABLE sys_log 3 | ADD COLUMN error_stacktrace varchar(3000) NOT NULL DEFAULT '', 4 | ADD COLUMN user_agent varchar(255) NOT NULL DEFAULT '', 5 | ADD COLUMN ip_location_region_name varchar(100) NOT NULL DEFAULT '', 6 | ADD COLUMN ip_location_province_name varchar(100) NOT NULL DEFAULT '', 7 | ADD COLUMN ip_location_city_name varchar(100) NOT NULL DEFAULT '', 8 | ADD COLUMN ip_location_district_name varchar(100) NOT NULL DEFAULT ''; 9 | 10 | COMMENT ON COLUMN sys_log.error_stacktrace IS '错误原因堆栈'; 11 | COMMENT ON COLUMN sys_log.user_agent IS '用户UA'; 12 | COMMENT ON COLUMN sys_log.ip_location_region_name IS 'IP地址属地-国家或地区名'; 13 | COMMENT ON COLUMN sys_log.ip_location_province_name IS 'IP地址属地-省级行政区名'; 14 | COMMENT ON COLUMN sys_log.ip_location_city_name IS 'IP地址属地-市级行政区名'; 15 | COMMENT ON COLUMN sys_log.ip_location_district_name IS 'IP地址属地-县级行政区名'; 16 | 17 | COMMENT ON TABLE sys_log IS '系统日志'; 18 | 19 | -- v1.8.0 - Uncarbon - 订正系统菜单-权限串缺少默认值问题 20 | ALTER TABLE "sys_menu" 21 | ALTER COLUMN "permission" SET DEFAULT ''; 22 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/config/AdminSaTokenExtendConfiguration.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.config; 2 | 3 | import cc.uncarbon.framework.core.context.UserContextHolder; 4 | import cc.uncarbon.helper.RolePermissionCacheHelper; 5 | import cn.dev33.satoken.stp.StpInterface; 6 | import java.util.List; 7 | import lombok.RequiredArgsConstructor; 8 | import org.springframework.stereotype.Component; 9 | 10 | 11 | /** 12 | * 自定义权限验证接口扩展 13 | * 用于后台管理 14 | * 15 | * @author Uncarbon 16 | */ 17 | @Component 18 | @RequiredArgsConstructor 19 | public class AdminSaTokenExtendConfiguration implements StpInterface { 20 | 21 | private final RolePermissionCacheHelper rolePermissionCacheHelper; 22 | 23 | 24 | /** 25 | * 返回一个账号所拥有的权限码集合 26 | */ 27 | @Override 28 | public List getPermissionList(Object loginId, String loginType) { 29 | return rolePermissionCacheHelper.getUserPermissions(); 30 | } 31 | 32 | /** 33 | * 返回一个账号所拥有的角色标识集合 (权限与角色可分开校验) 34 | */ 35 | @Override 36 | public List getRoleList(Object loginId, String loginType) { 37 | return UserContextHolder.getUserContext().getRoles(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/model/response/SysUserBaseInfoBO.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.model.response; 2 | 3 | import cc.uncarbon.framework.core.enums.GenderEnum; 4 | import cc.uncarbon.module.sys.enums.SysUserStatusEnum; 5 | import io.swagger.v3.oas.annotations.media.Schema; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Builder; 8 | import lombok.Data; 9 | import lombok.NoArgsConstructor; 10 | import lombok.experimental.Accessors; 11 | 12 | import java.io.Serializable; 13 | 14 | 15 | /** 16 | * 后台用户BO 17 | * 仅保留基本信息 18 | */ 19 | @Accessors(chain = true) 20 | @Builder 21 | @AllArgsConstructor 22 | @NoArgsConstructor 23 | @Data 24 | public class SysUserBaseInfoBO implements Serializable { 25 | 26 | @Schema(description = "账号") 27 | private String username; 28 | 29 | @Schema(description = "昵称") 30 | private String nickname; 31 | 32 | @Schema(description = "状态") 33 | private SysUserStatusEnum status; 34 | 35 | @Schema(description = "性别") 36 | private GenderEnum gender; 37 | 38 | @Schema(description = "邮箱") 39 | private String email; 40 | 41 | @Schema(description = "手机号") 42 | private String phoneNo; 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/entity/SysParamEntity.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.entity; 2 | 3 | import cc.uncarbon.framework.crud.entity.HelioBaseEntity; 4 | import com.baomidou.mybatisplus.annotation.TableField; 5 | import com.baomidou.mybatisplus.annotation.TableName; 6 | import io.swagger.v3.oas.annotations.media.Schema; 7 | import lombok.AllArgsConstructor; 8 | import lombok.Data; 9 | import lombok.EqualsAndHashCode; 10 | import lombok.NoArgsConstructor; 11 | import lombok.experimental.Accessors; 12 | import lombok.experimental.SuperBuilder; 13 | 14 | 15 | /** 16 | * 系统参数 17 | */ 18 | @EqualsAndHashCode(callSuper = true) 19 | @Accessors(chain = true) 20 | @SuperBuilder 21 | @AllArgsConstructor 22 | @NoArgsConstructor 23 | @Data 24 | @TableName(value = "sys_param") 25 | public class SysParamEntity extends HelioBaseEntity { 26 | 27 | @Schema(description = "键名") 28 | @TableField(value = "name") 29 | private String name; 30 | 31 | @Schema(description = "键值") 32 | @TableField(value = "value") 33 | private String value; 34 | 35 | @Schema(description = "描述") 36 | @TableField(value = "description") 37 | private String description; 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/resources/i18n/messages_en_US.properties: -------------------------------------------------------------------------------- 1 | #-- Web 全局异常处理国际化消息 - 美式英语 2 | GLOBAL__NO_LOGIN=No login 3 | GLOBAL__PERMISSION_NOT_MATCH=Permission not match 4 | GLOBAL__ROLE_NOT_MATCH=Role not match 5 | GLOBAL__NOT_FOUND=Page not found 6 | GLOBAL__UNACCEPTABLE_PARAMETERS=Unacceptable parameters 7 | GLOBAL__METHOD_NOT_ALLOWED=Method not allowed 8 | GLOBAL__INTERNAL_ERROR=Please try again later 9 | 10 | #-- SysErrorEnum 11 | SysErrorEnum.INVALID_ID=Invalid id 12 | SysErrorEnum.INCORRECT_PIN_OR_PWD=Incorrect username or password 13 | SysErrorEnum.BANNED_USER=Banned user 14 | SysErrorEnum.INVALID_TENANT=Invalid tenant 15 | SysErrorEnum.DISABLED_TENANT=Disabled tenant 16 | SysErrorEnum.INCORRECT_OLD_PASSWORD=Old password is incorrect 17 | SysErrorEnum.NO_ROLE_AVAILABLE_FOR_CURRENT_USER=No role available for current user 18 | SysErrorEnum.NO_MENU_AVAILABLE_FOR_CURRENT_ROLE=No menu available for current role 19 | SysErrorEnum.UUID_CANNOT_BE_BLANK=UUID cannot be blank 20 | 21 | #-- OssErrorEnum 22 | OssErrorEnum.INVALID_ID=Invalid id 23 | OssErrorEnum.FILE_UPLOAD_FAILED=File upload failed, please contact administrator 24 | OssErrorEnum.FILE_DOWNLOAD_FAILED=File download failed, please contact administrator 25 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/adminapi/listener/AdminApiEventListener.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.adminapi.listener; 2 | 3 | import cc.uncarbon.module.adminapi.event.KickOutSysUsersEvent; 4 | import cc.uncarbon.module.adminapi.util.AdminStpUtil; 5 | import cn.hutool.core.collection.CollUtil; 6 | import lombok.RequiredArgsConstructor; 7 | import org.springframework.context.event.EventListener; 8 | import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; 9 | import org.springframework.stereotype.Component; 10 | 11 | import java.util.Collection; 12 | 13 | /** 14 | * admin-api模块事件监听器 15 | */ 16 | @Component 17 | @RequiredArgsConstructor 18 | public class AdminApiEventListener { 19 | 20 | private final ThreadPoolTaskExecutor taskExecutor; 21 | 22 | 23 | @EventListener(value = KickOutSysUsersEvent.class) 24 | public void handleKickOutSysUsersEvent(KickOutSysUsersEvent event) { 25 | Collection sysUserIds = event.getData().getSysUserIds(); 26 | if (CollUtil.isNotEmpty(sysUserIds)) { 27 | // 异步强制登出;同一时间大量登出,会操作大量Redis键,可能存在缓存雪崩的风险 28 | taskExecutor.submit(() -> sysUserIds.forEach(AdminStpUtil::kickout)); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/oss/facade/OssUploadDownloadFacade.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.oss.facade; 2 | 3 | import cc.uncarbon.framework.core.exception.BusinessException; 4 | import cc.uncarbon.module.oss.model.request.UploadFileAttributeDTO; 5 | import cc.uncarbon.module.oss.model.response.OssFileDownloadReplyBO; 6 | import cc.uncarbon.module.oss.model.response.OssFileInfoBO; 7 | import lombok.NonNull; 8 | 9 | /** 10 | * 文件上传下载门面 11 | */ 12 | public interface OssUploadDownloadFacade { 13 | 14 | /** 15 | * 根据哈希值,查找是否已有文件 16 | */ 17 | OssFileInfoBO findByHash(String md5); 18 | 19 | /** 20 | * 正常上传文件到服务端 21 | * 22 | * @param fileBytes 文件数据 23 | * @param attr 附加属性 24 | */ 25 | OssFileInfoBO upload(byte[] fileBytes, @NonNull UploadFileAttributeDTO attr) throws BusinessException; 26 | 27 | /** 28 | * 根据文件ID下载 29 | * 30 | * @throws BusinessException 业务异常,如:文件ID无效;原始文件不存在 31 | */ 32 | OssFileDownloadReplyBO downloadById(Long fileInfoId) throws BusinessException; 33 | 34 | /** 35 | * 是否为本地存储平台 36 | * @param storagePlatform 存储平台名 37 | */ 38 | boolean isLocalPlatform(String storagePlatform); 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/appapi/web/AppAuthController.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.appapi.web; 2 | 3 | 4 | import cc.uncarbon.framework.web.model.response.ApiResult; 5 | import cc.uncarbon.module.appapi.constant.AppApiConstant; 6 | import io.swagger.v3.oas.annotations.tags.Tag; 7 | import io.swagger.v3.oas.annotations.Operation; 8 | import lombok.RequiredArgsConstructor; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.springframework.web.bind.annotation.PostMapping; 11 | import org.springframework.web.bind.annotation.RequestMapping; 12 | import org.springframework.web.bind.annotation.RestController; 13 | 14 | 15 | @RequiredArgsConstructor 16 | @Slf4j 17 | @Tag(name = "APP鉴权接口") 18 | @RequestMapping(AppApiConstant.HTTP_API_URL_PREFIX + "/api/v1") 19 | @RestController 20 | public class AppAuthController { 21 | 22 | /* 23 | /app/** 开头的C端接口默认为都需要登录,放行接口请在配置文件的 helio.security.exclude-routes 中设置 24 | 相关拦截器代码请见 CustomInterceptorConfiguration.java 25 | */ 26 | 27 | @Operation(summary = "登录") 28 | @PostMapping("/auth/login") 29 | public ApiResult login() { 30 | // 可参考 admin-api 的 AdminAuthController#login 31 | return ApiResult.success(); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/model/response/VbenAdminUserInfoVO.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.model.response; 2 | 3 | import cc.uncarbon.framework.core.enums.GenderEnum; 4 | import io.swagger.v3.oas.annotations.media.Schema; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | import lombok.experimental.Accessors; 10 | 11 | import java.io.Serializable; 12 | import java.time.LocalDateTime; 13 | 14 | 15 | /** 16 | * 后台用户 for VbenAdmin 17 | */ 18 | @Accessors(chain = true) 19 | @Builder 20 | @AllArgsConstructor 21 | @NoArgsConstructor 22 | @Data 23 | public class VbenAdminUserInfoVO implements Serializable { 24 | 25 | @Schema(description = "账号") 26 | private String username; 27 | 28 | @Schema(description = "昵称") 29 | private String nickname; 30 | 31 | @Schema(description = "最后登录时刻") 32 | private LocalDateTime lastLoginAt; 33 | 34 | @Schema(description = "性别") 35 | private GenderEnum gender; 36 | 37 | @Schema(description = "邮箱") 38 | private String email; 39 | 40 | @Schema(description = "手机号") 41 | private String phoneNo; 42 | 43 | @Schema(description = "头像URL") 44 | private String avatar; 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/model/request/AdminUpdateCurrentSysUserAvatarDTO.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.model.request; 2 | 3 | import cc.uncarbon.framework.core.exception.BusinessException; 4 | import cn.hutool.core.text.CharSequenceUtil; 5 | import io.swagger.v3.oas.annotations.media.Schema; 6 | import jakarta.validation.constraints.Size; 7 | import lombok.AllArgsConstructor; 8 | import lombok.Data; 9 | import lombok.NoArgsConstructor; 10 | import lombok.experimental.Accessors; 11 | 12 | import java.io.Serializable; 13 | 14 | 15 | /** 16 | * 后台管理-更新当前后台用户头像 17 | */ 18 | @Accessors(chain = true) 19 | @AllArgsConstructor 20 | @NoArgsConstructor 21 | @Data 22 | public class AdminUpdateCurrentSysUserAvatarDTO implements Serializable { 23 | 24 | 25 | @Schema(description = "头像URL", requiredMode = Schema.RequiredMode.REQUIRED) 26 | @Size(max = 255, message = "头像输入长度超限") 27 | private String avatarUrl; 28 | 29 | /** 30 | * 实际使用时,请手动补充安全检查机制(如检查头像URL所属的CDN域名) 31 | */ 32 | public void securityCheck() { 33 | if (!CharSequenceUtil.startWithIgnoreCase(this.avatarUrl, "http")) { 34 | throw new BusinessException(400, "头像输入格式不正确"); 35 | } 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/oss/model/request/UploadFileAttributeDTO.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.oss.model.request; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | import lombok.experimental.Accessors; 8 | 9 | import java.io.Serializable; 10 | 11 | 12 | /** 13 | * 上传文件属性 DTO 14 | */ 15 | @Accessors(chain = true) 16 | @AllArgsConstructor 17 | @NoArgsConstructor 18 | @Data 19 | public class UploadFileAttributeDTO implements Serializable { 20 | 21 | @Schema(description = "文件类别", example = "id_card=身份证 driver_license=驾驶证") 22 | private String classified; 23 | 24 | /* 25 | 以下字段为内部使用 26 | */ 27 | @Schema(description = "原始文件名", hidden = true) 28 | private String originalFilename; 29 | 30 | @Schema(description = "MIME类型", hidden = true) 31 | private String contentType; 32 | 33 | @Schema(description = "MD5", hidden = true) 34 | private String md5; 35 | 36 | @Schema(description = "指定要上传到的平台名(null则取值默认平台)", hidden = true) 37 | private String platform; 38 | 39 | @Schema(description = "从对象存储服务器中下载时,以原始文件名命名(通过指定 metadata 实现)", hidden = true) 40 | private boolean useOriginalFilenameAsDownloadFileName; 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/mapper/SysDataDictClassifiedMapper.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.mapper; 2 | 3 | import cc.uncarbon.framework.core.constant.HelioConstant; 4 | import cc.uncarbon.framework.core.enums.EnabledStatusEnum; 5 | import cc.uncarbon.module.sys.entity.SysDataDictClassifiedEntity; 6 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 7 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 8 | import jakarta.annotation.Nonnull; 9 | import jakarta.annotation.Nullable; 10 | import org.apache.ibatis.annotations.Mapper; 11 | 12 | import java.util.Objects; 13 | 14 | 15 | /** 16 | * 数据字典分类 17 | */ 18 | @Mapper 19 | public interface SysDataDictClassifiedMapper extends BaseMapper { 20 | 21 | default SysDataDictClassifiedEntity selectByCode(@Nonnull String code, @Nullable EnabledStatusEnum status) { 22 | return selectOne( 23 | new QueryWrapper() 24 | .lambda() 25 | .eq(SysDataDictClassifiedEntity::getCode, code) 26 | .eq(Objects.nonNull(status), SysDataDictClassifiedEntity::getStatus, status) 27 | .last(HelioConstant.CRUD.SQL_LIMIT_1) 28 | ); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/model/interior/UserRoleContainer.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.model.interior; 2 | 3 | import cc.uncarbon.module.sys.entity.SysRoleEntity; 4 | import lombok.Getter; 5 | 6 | import java.util.List; 7 | import java.util.Set; 8 | 9 | /** 10 | * 用户关联角色容器 11 | */ 12 | @Getter 13 | public class UserRoleContainer { 14 | 15 | /** 16 | * 直接关联的角色IDs 17 | */ 18 | private final Set relatedRoleIds; 19 | 20 | /** 21 | * 直接关联的角色实例集合 22 | */ 23 | private final List relatedRoles; 24 | 25 | /** 26 | * 为超级管理员 27 | */ 28 | private final boolean superAdmin; 29 | 30 | /** 31 | * 为租户管理员 32 | */ 33 | private final boolean tenantAdmin; 34 | 35 | /** 36 | * 非级管理员or租户管理员 37 | */ 38 | private final boolean notAnyAdmin; 39 | 40 | 41 | public UserRoleContainer(Set relatedRoleIds, List relatedRoles) { 42 | this.relatedRoleIds = relatedRoleIds; 43 | this.relatedRoles = relatedRoles; 44 | this.superAdmin = relatedRoles.stream().anyMatch(SysRoleEntity::isSuperAdmin); 45 | this.tenantAdmin = relatedRoles.stream().anyMatch(SysRoleEntity::isTenantAdmin); 46 | this.notAnyAdmin = !this.superAdmin && !this.tenantAdmin; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/model/request/AdminInsertOrUpdateSysDeptDTO.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.model.request; 2 | 3 | import cc.uncarbon.framework.core.enums.EnabledStatusEnum; 4 | import io.swagger.v3.oas.annotations.media.Schema; 5 | import jakarta.validation.constraints.NotBlank; 6 | import jakarta.validation.constraints.Size; 7 | import lombok.AllArgsConstructor; 8 | import lombok.Builder; 9 | import lombok.Data; 10 | import lombok.NoArgsConstructor; 11 | import lombok.experimental.Accessors; 12 | 13 | import java.io.Serializable; 14 | 15 | 16 | /** 17 | * 后台管理-新增/编辑部门 18 | */ 19 | @Accessors(chain = true) 20 | @Builder 21 | @AllArgsConstructor 22 | @NoArgsConstructor 23 | @Data 24 | public class AdminInsertOrUpdateSysDeptDTO implements Serializable { 25 | 26 | @Schema(description = "主键ID", hidden = true, title = "仅更新时使用") 27 | private Long id; 28 | 29 | @Schema(description = "部门名称", requiredMode = Schema.RequiredMode.REQUIRED) 30 | @Size(max = 50, message = "【部门名称】最长50位") 31 | @NotBlank(message = "部门名称不能为空") 32 | private String title; 33 | 34 | @Schema(description = "上级ID(无上级节点设置为0)") 35 | private Long parentId; 36 | 37 | @Schema(description = "排序") 38 | private Integer sort; 39 | 40 | @Schema(description = "状态") 41 | private EnabledStatusEnum status; 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/model/request/AdminUpdateCurrentSysUserPasswordDTO.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.model.request; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import jakarta.validation.constraints.NotBlank; 5 | import jakarta.validation.constraints.Size; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Builder; 8 | import lombok.Data; 9 | import lombok.NoArgsConstructor; 10 | import lombok.experimental.Accessors; 11 | 12 | import java.io.Serializable; 13 | 14 | 15 | /** 16 | * 后台管理-修改当前用户密码 17 | */ 18 | @Accessors(chain = true) 19 | @Builder 20 | @AllArgsConstructor 21 | @NoArgsConstructor 22 | @Data 23 | public class AdminUpdateCurrentSysUserPasswordDTO implements Serializable { 24 | 25 | @Schema(description = "原密码", requiredMode = Schema.RequiredMode.REQUIRED) 26 | @NotBlank(message = "原密码不能为空") 27 | private String oldPassword; 28 | 29 | @Schema(description = "新密码", requiredMode = Schema.RequiredMode.REQUIRED) 30 | @Size(min = 8, max = 20, message = "【密码】长度须在 8 至 20 位之间") 31 | @NotBlank(message = "密码不能为空") 32 | private String newPassword; 33 | 34 | @Schema(description = "确认新密码", requiredMode = Schema.RequiredMode.REQUIRED) 35 | @Size(min = 8, max = 20, message = "【确认密码】长度须在 8 至 20 位之间") 36 | @NotBlank(message = "确认密码不能为空") 37 | private String confirmNewPassword; 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/model/response/IPLocationBO.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.model.response; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | import lombok.experimental.Accessors; 8 | 9 | import java.io.Serializable; 10 | 11 | /** 12 | * IP地址属地 13 | * 首用于系统日志 14 | */ 15 | @Accessors(chain = true) 16 | @AllArgsConstructor 17 | @NoArgsConstructor 18 | @Data 19 | public class IPLocationBO implements Serializable { 20 | 21 | @Schema(description = "国家或地区名") 22 | private String regionName; 23 | 24 | @Schema(description = "省级行政区名") 25 | private String provinceName; 26 | 27 | @Schema(description = "市级行政区名") 28 | private String cityName; 29 | 30 | @Schema(description = "县级行政区名") 31 | private String districtName; 32 | 33 | /** 34 | * 未知属地 35 | */ 36 | public static IPLocationBO unknown() { 37 | return new IPLocationBO("未知", null, null, null); 38 | } 39 | 40 | /** 41 | * 内网地址 42 | */ 43 | public static IPLocationBO intranet() { 44 | return new IPLocationBO("内网", null, null, null); 45 | } 46 | 47 | /** 48 | * 位于中国内地 49 | */ 50 | public static IPLocationBO inChina() { 51 | return new IPLocationBO("中国", null, null, null); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/bizcommon/context/PrivilegedTenantContext.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.bizcommon.context; 2 | 3 | import cc.uncarbon.framework.core.constant.HelioConstant; 4 | import cc.uncarbon.framework.core.context.TenantContext; 5 | 6 | /** 7 | * 快速构造一个超级租户上下文,用于临时绕过租户拦截器;同时提供一个单例对象 8 | * 使用示例 9 | * TenantContext originContext = TenantContextHolder.getTenantContext(); 10 | * try { 11 | * if (originContext == null) { 12 | * // 用户可能尚未登录,但需要查库(如获取APP首页轮播图等),得绕过租户限制;但如果已经登录了,则不必切换租户态 13 | * TenantContextHolder.setTenantContext(PrivilegedTenantContext.SINGLETON); 14 | * } 15 | * // 业务代码... 16 | * } finally { 17 | * TenantContextHolder.setTenantContext(originContext); 18 | * } 19 | */ 20 | public class PrivilegedTenantContext extends TenantContext { 21 | 22 | public PrivilegedTenantContext() { 23 | super(HelioConstant.Tenant.DEFAULT_PRIVILEGED_TENANT_ID, "超级租户"); 24 | } 25 | 26 | public static final PrivilegedTenantContext SINGLETON = new PrivilegedTenantContext(); 27 | 28 | @Override 29 | public TenantContext setTenantId(Long tenantId) { 30 | // 不允许修改 31 | return this; 32 | } 33 | 34 | @Override 35 | public TenantContext setTenantName(String tenantName) { 36 | // 不允许修改 37 | return this; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/entity/SysTenantEntity.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.entity; 2 | 3 | import cc.uncarbon.framework.core.enums.EnabledStatusEnum; 4 | import cc.uncarbon.framework.crud.entity.HelioBaseEntity; 5 | import com.baomidou.mybatisplus.annotation.TableField; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | import io.swagger.v3.oas.annotations.media.Schema; 8 | import lombok.AllArgsConstructor; 9 | import lombok.Data; 10 | import lombok.EqualsAndHashCode; 11 | import lombok.NoArgsConstructor; 12 | import lombok.experimental.Accessors; 13 | import lombok.experimental.SuperBuilder; 14 | 15 | 16 | /** 17 | * 系统租户 18 | */ 19 | @EqualsAndHashCode(callSuper = true) 20 | @Accessors(chain = true) 21 | @SuperBuilder 22 | @AllArgsConstructor 23 | @NoArgsConstructor 24 | @Data 25 | @TableName(value = "sys_tenant") 26 | public class SysTenantEntity extends HelioBaseEntity { 27 | 28 | @Schema(description = "租户名") 29 | @TableField(value = "tenant_name") 30 | private String tenantName; 31 | 32 | @Schema(description = "状态") 33 | @TableField(value = "status") 34 | private EnabledStatusEnum status; 35 | 36 | @Schema(description = "租户管理员用户ID") 37 | @TableField(value = "tenant_admin_user_id") 38 | private Long tenantAdminUserId; 39 | 40 | @Schema(description = "备注") 41 | @TableField(value = "remark") 42 | private String remark; 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/model/request/AdminUpdateSysTenantDTO.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.model.request; 2 | 3 | import cc.uncarbon.framework.core.enums.EnabledStatusEnum; 4 | import io.swagger.v3.oas.annotations.media.Schema; 5 | import jakarta.validation.constraints.NotBlank; 6 | import jakarta.validation.constraints.NotNull; 7 | import jakarta.validation.constraints.Size; 8 | import lombok.AllArgsConstructor; 9 | import lombok.Builder; 10 | import lombok.Data; 11 | import lombok.NoArgsConstructor; 12 | import lombok.experimental.Accessors; 13 | 14 | import java.io.Serializable; 15 | 16 | 17 | /** 18 | * 后台管理-编辑系统租户 19 | */ 20 | @Accessors(chain = true) 21 | @Builder 22 | @AllArgsConstructor 23 | @NoArgsConstructor 24 | @Data 25 | public class AdminUpdateSysTenantDTO implements Serializable { 26 | 27 | @Schema(description = "主键ID", hidden = true, title = "仅更新时使用") 28 | private Long id; 29 | 30 | @Schema(description = "租户名", requiredMode = Schema.RequiredMode.REQUIRED) 31 | @Size(max = 50, message = "【租户名】最长50位") 32 | @NotBlank(message = "租户名不能为空") 33 | private String tenantName; 34 | 35 | @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED) 36 | @NotNull(message = "状态不能为空") 37 | private EnabledStatusEnum status; 38 | 39 | @Schema(description = "备注") 40 | @Size(max = 255, message = "【备注】最长255位") 41 | private String remark; 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/model/request/AdminInsertOrUpdateSysParamDTO.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.model.request; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import jakarta.validation.constraints.NotBlank; 5 | import jakarta.validation.constraints.Size; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Builder; 8 | import lombok.Data; 9 | import lombok.NoArgsConstructor; 10 | import lombok.experimental.Accessors; 11 | 12 | import java.io.Serializable; 13 | 14 | 15 | /** 16 | * 后台管理-新增/编辑系统参数 17 | */ 18 | @Accessors(chain = true) 19 | @Builder 20 | @AllArgsConstructor 21 | @NoArgsConstructor 22 | @Data 23 | public class AdminInsertOrUpdateSysParamDTO implements Serializable { 24 | 25 | @Schema(description = "主键ID", hidden = true, title = "仅更新时使用") 26 | private Long id; 27 | 28 | @Schema(description = "键名", requiredMode = Schema.RequiredMode.REQUIRED) 29 | @Size(max = 50, message = "【键名】最长50位") 30 | @NotBlank(message = "键名不能为空") 31 | private String name; 32 | 33 | @Schema(description = "键值", requiredMode = Schema.RequiredMode.REQUIRED) 34 | @Size(max = 255, message = "【键值】最长255位") 35 | @NotBlank(message = "键值不能为空") 36 | private String value; 37 | 38 | @Schema(description = "描述", requiredMode = Schema.RequiredMode.REQUIRED) 39 | @Size(max = 255, message = "【描述】最长255位") 40 | @NotBlank(message = "描述不能为空") 41 | private String description; 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/enums/SysMenuTypeEnum.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.enums; 2 | 3 | import cc.uncarbon.framework.core.enums.HelioBaseEnum; 4 | import com.baomidou.mybatisplus.annotation.EnumValue; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Getter; 7 | 8 | import java.util.Arrays; 9 | import java.util.List; 10 | 11 | 12 | /** 13 | * 预置系统菜单类型枚举类 14 | */ 15 | @AllArgsConstructor 16 | @Getter 17 | public enum SysMenuTypeEnum implements HelioBaseEnum { 18 | 19 | /** 20 | * 可以认为是父级菜单 21 | */ 22 | DIR(0, "目录"), 23 | 24 | /** 25 | * 可以认为是子菜单 26 | */ 27 | MENU(1, "菜单"), 28 | 29 | /** 30 | * 可以认为是页内按钮 31 | */ 32 | BUTTON(2, "按钮"), 33 | 34 | /** 35 | * 外链 36 | */ 37 | EXTERNAL_LINK(3, "外链"),; 38 | 39 | @EnumValue 40 | private final Integer value; 41 | private final String label; 42 | 43 | /** 44 | * 所有菜单类型 45 | */ 46 | public static List all() { 47 | return Arrays.asList(DIR, MENU, BUTTON, EXTERNAL_LINK); 48 | } 49 | 50 | /** 51 | * 用于后台管理-侧边菜单的几种菜单类型 52 | */ 53 | public static List forAdminSide() { 54 | return Arrays.asList(DIR, MENU, EXTERNAL_LINK); 55 | } 56 | 57 | /** 58 | * 用于后台管理-绑定角色与菜单关联关系 59 | */ 60 | public static List forAdminBindMenus() { 61 | return Arrays.asList(DIR, MENU, BUTTON, EXTERNAL_LINK); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/entity/SysRoleEntity.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.entity; 2 | 3 | import cc.uncarbon.framework.crud.entity.HelioBaseEntity; 4 | import cc.uncarbon.module.sys.constant.SysConstant; 5 | import com.baomidou.mybatisplus.annotation.TableField; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | import io.swagger.v3.oas.annotations.media.Schema; 8 | import lombok.AllArgsConstructor; 9 | import lombok.Data; 10 | import lombok.EqualsAndHashCode; 11 | import lombok.NoArgsConstructor; 12 | import lombok.experimental.Accessors; 13 | import lombok.experimental.SuperBuilder; 14 | 15 | 16 | /** 17 | * 后台角色 18 | */ 19 | @EqualsAndHashCode(callSuper = true) 20 | @Accessors(chain = true) 21 | @SuperBuilder 22 | @AllArgsConstructor 23 | @NoArgsConstructor 24 | @Data 25 | @TableName(value = "sys_role") 26 | public class SysRoleEntity extends HelioBaseEntity { 27 | 28 | @Schema(description = "角色名") 29 | @TableField(value = "title") 30 | private String title; 31 | 32 | @Schema(description = "角色编码") 33 | @TableField(value = "value") 34 | private String value; 35 | 36 | /** 37 | * 角色实例可被视为超级管理员 38 | */ 39 | public boolean isSuperAdmin() { 40 | return SysConstant.SUPER_ADMIN_ROLE_ID.equals(getId()) || SysConstant.SUPER_ADMIN_ROLE_VALUE.equalsIgnoreCase(getValue()); 41 | } 42 | 43 | /** 44 | * 角色实例可被视为租户管理员 45 | */ 46 | public boolean isTenantAdmin() { 47 | return SysConstant.TENANT_ADMIN_ROLE_VALUE.equalsIgnoreCase(getValue()); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/model/response/SysUserLoginBO.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.model.response; 2 | 3 | import cc.uncarbon.framework.core.context.TenantContext; 4 | import io.swagger.v3.oas.annotations.media.Schema; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | import lombok.experimental.Accessors; 9 | import lombok.experimental.SuperBuilder; 10 | 11 | import java.io.Serializable; 12 | import java.util.List; 13 | import java.util.Map; 14 | import java.util.Set; 15 | 16 | 17 | /** 18 | * 登录后返回的字段 19 | * 用于内部 RPC 调用 20 | */ 21 | @Accessors(chain = true) 22 | @SuperBuilder 23 | @AllArgsConstructor 24 | @NoArgsConstructor 25 | @Data 26 | public class SysUserLoginBO implements Serializable { 27 | 28 | @Schema(description = "用户ID") 29 | private Long id; 30 | 31 | @Schema(description = "账号") 32 | private String username; 33 | 34 | @Schema(description = "昵称") 35 | private String nickname; 36 | 37 | @Schema(description = "手机号") 38 | private String phoneNo; 39 | 40 | @Schema(description = "对应角色ID") 41 | private Set roleIds; 42 | 43 | @Schema(description = "对应角色名") 44 | private List roles; 45 | 46 | @Schema(description = "所有拥有权限名") 47 | private Set permissions; 48 | 49 | @Schema(description = "角色ID-对应权限名 Map") 50 | private Map> roleIdPermissionMap; 51 | 52 | @Schema(description = "关联租户上下文") 53 | private TenantContext tenantContext; 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/entity/SysDataDictClassifiedEntity.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.entity; 2 | 3 | import cc.uncarbon.framework.core.enums.EnabledStatusEnum; 4 | import cc.uncarbon.framework.crud.entity.HelioBaseEntity; 5 | import com.baomidou.mybatisplus.annotation.TableField; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | import io.swagger.v3.oas.annotations.media.Schema; 8 | import lombok.AllArgsConstructor; 9 | import lombok.Data; 10 | import lombok.EqualsAndHashCode; 11 | import lombok.NoArgsConstructor; 12 | import lombok.experimental.Accessors; 13 | import lombok.experimental.SuperBuilder; 14 | 15 | import java.io.Serial; 16 | 17 | 18 | /** 19 | * 数据字典分类 20 | */ 21 | @EqualsAndHashCode(callSuper = true) 22 | @Accessors(chain = true) 23 | @SuperBuilder 24 | @AllArgsConstructor 25 | @NoArgsConstructor 26 | @Data 27 | @TableName(value = "sys_data_dict_classified") 28 | public class SysDataDictClassifiedEntity extends HelioBaseEntity { 29 | 30 | @Serial 31 | private static final long serialVersionUID = 1L; 32 | 33 | 34 | @Schema(description = "分类编码") 35 | @TableField(value = "code") 36 | private String code; 37 | 38 | @Schema(description = "分类名称") 39 | @TableField(value = "name") 40 | private String name; 41 | 42 | @Schema(description = "状态") 43 | @TableField(value = "status") 44 | private EnabledStatusEnum status; 45 | 46 | @Schema(description = "分类描述") 47 | @TableField(value = "description") 48 | private String description; 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/annotation/SysLog.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.annotation; 2 | 3 | import cc.uncarbon.module.sys.extension.SysLogAspectExtension; 4 | import cc.uncarbon.module.sys.extension.impl.DefaultSysLogAspectExtension; 5 | 6 | import java.lang.annotation.*; 7 | 8 | 9 | /** 10 | * 放在Controller方法上,可将操作记录至系统日志中 11 | * 需同JVM中,存在对应的SysLogAspect切面类 12 | */ 13 | @Target(ElementType.METHOD) 14 | @Retention(RetentionPolicy.RUNTIME) 15 | @Documented 16 | @Inherited 17 | public @interface SysLog { 18 | 19 | /** 20 | * 操作内容(如:新增部门) 21 | */ 22 | String value(); 23 | 24 | /** 25 | * 是否同步保存至系统日志数据表中 26 | * true = 同步保存:如果开启了事务/事务注解,若系统日志保存失败,则会抛出异常触发回滚,使得本次操作也失败 27 | * false = 异步保存:若系统日志保存失败,不影响本次操作 28 | */ 29 | boolean syncSave() default false; 30 | 31 | /** 32 | * 保存系统日志至数据表的时机 33 | * 默认为仅“成功时” 34 | * 多个输入值间默认为“或”关系 35 | */ 36 | When[] when() default When.SUCCESS; 37 | enum When { 38 | /** 39 | * 成功时 40 | */ 41 | SUCCESS, 42 | 43 | /** 44 | * 失败时 45 | */ 46 | FAILED, 47 | } 48 | 49 | /** 50 | * 扩展点,可以增强系统日志,在保存时的动作 51 | * 示例见:SysLogAspectExtensionForSysUserLogin 52 | */ 53 | Class extension() default DefaultSysLogAspectExtension.class; 54 | 55 | /** 56 | * 是否查询IP地址属地 57 | * 本功能可能存在网络开销,建议仅在关键处使用,并视情况决定是否搭配异步保存(即:syncSave = false)使用 58 | */ 59 | boolean queryIPLocation() default false; 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/entity/SysDeptEntity.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.entity; 2 | 3 | import cc.uncarbon.framework.core.enums.EnabledStatusEnum; 4 | import cc.uncarbon.framework.crud.entity.HelioBaseEntity; 5 | import com.baomidou.mybatisplus.annotation.TableField; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | import io.swagger.v3.oas.annotations.media.Schema; 8 | import lombok.AllArgsConstructor; 9 | import lombok.Data; 10 | import lombok.EqualsAndHashCode; 11 | import lombok.NoArgsConstructor; 12 | import lombok.experimental.Accessors; 13 | import lombok.experimental.SuperBuilder; 14 | 15 | 16 | /** 17 | * 部门 18 | */ 19 | @EqualsAndHashCode(callSuper = true) 20 | @Accessors(chain = true) 21 | @SuperBuilder 22 | @AllArgsConstructor 23 | @NoArgsConstructor 24 | @Data 25 | @TableName(value = "sys_dept") 26 | public class SysDeptEntity extends HelioBaseEntity { 27 | 28 | /** 29 | * 乐观锁 30 | * 需自行加@Version注解才有效 31 | */ 32 | @Schema(description = "乐观锁", title = "需再次复制本字段,并自行加 @Version 注解才有效") 33 | @TableField(value = "revision", exist = false) 34 | private Long revision; 35 | 36 | @Schema(description = "名称") 37 | @TableField(value = "title") 38 | private String title; 39 | 40 | @Schema(description = "上级ID(无上级节点设置为0)") 41 | @TableField(value = "parent_id") 42 | private Long parentId; 43 | 44 | @Schema(description = "排序") 45 | @TableField(value = "sort") 46 | private Integer sort; 47 | 48 | @Schema(description = "状态") 49 | @TableField(value = "status") 50 | private EnabledStatusEnum status; 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/model/request/SysUserLoginDTO.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.model.request; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import jakarta.validation.constraints.NotBlank; 5 | import jakarta.validation.constraints.NotNull; 6 | import jakarta.validation.constraints.Size; 7 | import lombok.AllArgsConstructor; 8 | import lombok.Builder; 9 | import lombok.Data; 10 | import lombok.NoArgsConstructor; 11 | import lombok.experimental.Accessors; 12 | 13 | import java.io.Serializable; 14 | 15 | 16 | /** 17 | * 后台管理-后台用户登录 18 | */ 19 | @Accessors(chain = true) 20 | @Builder 21 | @AllArgsConstructor 22 | @NoArgsConstructor 23 | @Data 24 | public class SysUserLoginDTO implements Serializable { 25 | 26 | @Schema(description = "账号", requiredMode = Schema.RequiredMode.REQUIRED) 27 | @Size(min = 5, max = 16, message = "【账号】最短5位,最长16位") 28 | @NotBlank(message = "账号不能为空") 29 | private String username; 30 | 31 | @Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED) 32 | @Size(min = 5, max = 20, message = "【密码】最短5位,最长20位") 33 | @NotBlank(message = "密码不能为空") 34 | private String password; 35 | 36 | @Schema(description = "记住我", requiredMode = Schema.RequiredMode.REQUIRED) 37 | @NotNull(message = "记住我不能为空") 38 | private Boolean rememberMe; 39 | 40 | @Schema(description = "租户ID(可选,启用多租户后有效)") 41 | private Long tenantId; 42 | 43 | @Schema(description = "验证码唯一标识(可选)") 44 | private String captchaId; 45 | 46 | @Schema(description = "验证码答案(可选)") 47 | private String captchaAnswer; 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/model/response/SysParamBO.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.model.response; 2 | 3 | import cc.uncarbon.framework.core.constant.HelioConstant; 4 | import com.fasterxml.jackson.annotation.JsonFormat; 5 | import io.swagger.v3.oas.annotations.media.Schema; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Builder; 8 | import lombok.Data; 9 | import lombok.NoArgsConstructor; 10 | import lombok.experimental.Accessors; 11 | import org.springframework.format.annotation.DateTimeFormat; 12 | 13 | import java.io.Serializable; 14 | import java.time.LocalDateTime; 15 | 16 | 17 | /** 18 | * 系统参数BO 19 | */ 20 | @Accessors(chain = true) 21 | @Builder 22 | @AllArgsConstructor 23 | @NoArgsConstructor 24 | @Data 25 | public class SysParamBO implements Serializable { 26 | 27 | @Schema(description = "主键ID") 28 | private Long id; 29 | 30 | @Schema(description = "创建时刻") 31 | @DateTimeFormat(pattern = HelioConstant.Jackson.DATE_TIME_FORMAT) 32 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = HelioConstant.Jackson.DATE_TIME_FORMAT) 33 | private LocalDateTime createdAt; 34 | 35 | @Schema(description = "更新时刻") 36 | @DateTimeFormat(pattern = HelioConstant.Jackson.DATE_TIME_FORMAT) 37 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = HelioConstant.Jackson.DATE_TIME_FORMAT) 38 | private LocalDateTime updatedAt; 39 | 40 | @Schema(description = "键名") 41 | private String name; 42 | 43 | @Schema(description = "键值") 44 | private String value; 45 | 46 | @Schema(description = "描述") 47 | private String description; 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/oss/model/request/AdminListOssFileInfoDTO.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.oss.model.request; 2 | 3 | import cc.uncarbon.framework.core.constant.HelioConstant; 4 | import com.fasterxml.jackson.annotation.JsonFormat; 5 | import io.swagger.v3.oas.annotations.media.Schema; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | import lombok.experimental.Accessors; 10 | import lombok.experimental.SuperBuilder; 11 | import org.springframework.format.annotation.DateTimeFormat; 12 | 13 | import java.io.Serializable; 14 | import java.time.LocalDateTime; 15 | 16 | 17 | /** 18 | * 后台管理-分页列表上传文件信息 DTO 19 | */ 20 | @Accessors(chain = true) 21 | @SuperBuilder 22 | @AllArgsConstructor 23 | @NoArgsConstructor 24 | @Data 25 | public class AdminListOssFileInfoDTO implements Serializable { 26 | 27 | @Schema(description = "原始文件名(关键词)") 28 | private String originalFilename; 29 | 30 | @Schema(description = "扩展名") 31 | private String extendName; 32 | 33 | @Schema(description = "文件类别") 34 | private String classified; 35 | 36 | @Schema(description = "时间区间起") 37 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = HelioConstant.Jackson.DATE_TIME_FORMAT) 38 | @DateTimeFormat(pattern = HelioConstant.Jackson.DATE_TIME_FORMAT) 39 | private LocalDateTime beginAt; 40 | 41 | @Schema(description = "时间区间止") 42 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = HelioConstant.Jackson.DATE_TIME_FORMAT) 43 | @DateTimeFormat(pattern = HelioConstant.Jackson.DATE_TIME_FORMAT) 44 | private LocalDateTime endAt; 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/model/request/AdminListSysLogDTO.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.model.request; 2 | 3 | import cc.uncarbon.framework.core.constant.HelioConstant; 4 | import cc.uncarbon.module.sys.enums.SysLogStatusEnum; 5 | import com.fasterxml.jackson.annotation.JsonFormat; 6 | import io.swagger.v3.oas.annotations.media.Schema; 7 | import lombok.AllArgsConstructor; 8 | import lombok.Builder; 9 | import lombok.Data; 10 | import lombok.NoArgsConstructor; 11 | import lombok.experimental.Accessors; 12 | import org.springframework.format.annotation.DateTimeFormat; 13 | 14 | import java.io.Serializable; 15 | import java.time.LocalDateTime; 16 | 17 | 18 | /** 19 | * 后台管理-分页列表系统日志 20 | */ 21 | @Accessors(chain = true) 22 | @Builder 23 | @AllArgsConstructor 24 | @NoArgsConstructor 25 | @Data 26 | public class AdminListSysLogDTO implements Serializable { 27 | 28 | @Schema(description = "用户账号") 29 | private String username; 30 | 31 | @Schema(description = "操作内容") 32 | private String operation; 33 | 34 | @Schema(description = "状态") 35 | private SysLogStatusEnum status; 36 | 37 | @Schema(description = "时间区间起") 38 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = HelioConstant.Jackson.DATE_TIME_FORMAT) 39 | @DateTimeFormat(pattern = HelioConstant.Jackson.DATE_TIME_FORMAT) 40 | private LocalDateTime beginAt; 41 | 42 | @Schema(description = "时间区间止") 43 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = HelioConstant.Jackson.DATE_TIME_FORMAT) 44 | @DateTimeFormat(pattern = HelioConstant.Jackson.DATE_TIME_FORMAT) 45 | private LocalDateTime endAt; 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/model/response/SysRoleBO.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.model.response; 2 | 3 | import cc.uncarbon.framework.core.constant.HelioConstant; 4 | import com.fasterxml.jackson.annotation.JsonFormat; 5 | import io.swagger.v3.oas.annotations.media.Schema; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Builder; 8 | import lombok.Data; 9 | import lombok.NoArgsConstructor; 10 | import lombok.experimental.Accessors; 11 | import org.springframework.format.annotation.DateTimeFormat; 12 | 13 | import java.io.Serializable; 14 | import java.time.LocalDateTime; 15 | import java.util.Collection; 16 | 17 | 18 | /** 19 | * 后台角色BO 20 | */ 21 | @Accessors(chain = true) 22 | @Builder 23 | @AllArgsConstructor 24 | @NoArgsConstructor 25 | @Data 26 | public class SysRoleBO implements Serializable { 27 | 28 | @Schema(description = "主键ID") 29 | private Long id; 30 | 31 | @Schema(description = "创建时刻") 32 | @DateTimeFormat(pattern = HelioConstant.Jackson.DATE_TIME_FORMAT) 33 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = HelioConstant.Jackson.DATE_TIME_FORMAT) 34 | private LocalDateTime createdAt; 35 | 36 | @Schema(description = "更新时刻") 37 | @DateTimeFormat(pattern = HelioConstant.Jackson.DATE_TIME_FORMAT) 38 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = HelioConstant.Jackson.DATE_TIME_FORMAT) 39 | private LocalDateTime updatedAt; 40 | 41 | @Schema(description = "角色名") 42 | private String title; 43 | 44 | @Schema(description = "角色编码") 45 | private String value; 46 | 47 | @Schema(description = "可见菜单Ids") 48 | private Collection menuIds; 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/model/request/AdminInsertOrUpdateSysRoleDTO.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.model.request; 2 | 3 | import cc.uncarbon.module.sys.constant.SysConstant; 4 | import io.swagger.v3.oas.annotations.media.Schema; 5 | import jakarta.validation.constraints.NotBlank; 6 | import jakarta.validation.constraints.Size; 7 | import lombok.AllArgsConstructor; 8 | import lombok.Builder; 9 | import lombok.Data; 10 | import lombok.NoArgsConstructor; 11 | import lombok.experimental.Accessors; 12 | 13 | import java.io.Serializable; 14 | import java.util.Objects; 15 | 16 | 17 | /** 18 | * 后台管理-新增/编辑后台角色 19 | */ 20 | @Accessors(chain = true) 21 | @Builder 22 | @AllArgsConstructor 23 | @NoArgsConstructor 24 | @Data 25 | public class AdminInsertOrUpdateSysRoleDTO implements Serializable { 26 | 27 | @Schema(description = "主键ID", hidden = true, title = "仅更新时使用") 28 | private Long id; 29 | 30 | @Schema(description = "所属租户ID", hidden = true, title = "仅新增时使用") 31 | private Long tenantId; 32 | 33 | @Schema(description = "角色名", requiredMode = Schema.RequiredMode.REQUIRED) 34 | @Size(max = 50, message = "【角色名】最长50位") 35 | @NotBlank(message = "角色名不能为空") 36 | private String title; 37 | 38 | @Schema(description = "角色编码", requiredMode = Schema.RequiredMode.REQUIRED) 39 | @Size(max = 100, message = "【角色编码】最长100位") 40 | @NotBlank(message = "角色编码不能为空") 41 | private String value; 42 | 43 | /** 44 | * 是否用于创建新租户管理员角色 45 | */ 46 | public boolean creatingNewTenantAdmin() { 47 | return Objects.nonNull(tenantId) && SysConstant.TENANT_ADMIN_ROLE_VALUE.equalsIgnoreCase(value); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/model/request/AdminInsertSysLogDTO.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.model.request; 2 | 3 | import cc.uncarbon.module.sys.enums.SysLogStatusEnum; 4 | import io.swagger.v3.oas.annotations.media.Schema; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | import lombok.experimental.Accessors; 9 | 10 | import java.io.Serializable; 11 | 12 | 13 | /** 14 | * 后台管理-新增系统日志 15 | */ 16 | @Accessors(chain = true) 17 | @AllArgsConstructor 18 | @NoArgsConstructor 19 | @Data 20 | public class AdminInsertSysLogDTO implements Serializable { 21 | 22 | @Schema(description = "用户ID") 23 | private Long userId; 24 | 25 | @Schema(description = "用户账号") 26 | private String username; 27 | 28 | @Schema(description = "操作内容") 29 | private String operation; 30 | 31 | @Schema(description = "请求方法") 32 | private String method; 33 | 34 | @Schema(description = "请求参数") 35 | private String params; 36 | 37 | @Schema(description = "IP地址") 38 | private String ip; 39 | 40 | @Schema(description = "状态") 41 | private SysLogStatusEnum status; 42 | 43 | @Schema(description = "错误原因堆栈") 44 | private String errorStacktrace; 45 | 46 | @Schema(description = "用户UA") 47 | private String userAgent; 48 | 49 | @Schema(description = "IP地址属地-国家或地区名") 50 | private String ipLocationRegionName; 51 | 52 | @Schema(description = "IP地址属地-省级行政区名") 53 | private String ipLocationProvinceName; 54 | 55 | @Schema(description = "IP地址属地-市级行政区名") 56 | private String ipLocationCityName; 57 | 58 | @Schema(description = "IP地址属地-县级行政区名") 59 | private String ipLocationDistrictName; 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/model/request/AdminSysDataDictClassifiedInsertOrUpdateDTO.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.model.request; 2 | 3 | import cc.uncarbon.framework.core.enums.EnabledStatusEnum; 4 | import io.swagger.v3.oas.annotations.media.Schema; 5 | import jakarta.validation.constraints.NotBlank; 6 | import jakarta.validation.constraints.NotNull; 7 | import jakarta.validation.constraints.Size; 8 | import lombok.AllArgsConstructor; 9 | import lombok.Data; 10 | import lombok.NoArgsConstructor; 11 | import lombok.experimental.Accessors; 12 | import lombok.experimental.SuperBuilder; 13 | 14 | import java.io.Serializable; 15 | 16 | 17 | /** 18 | * 后台管理-新增/编辑数据字典分类 DTO 19 | */ 20 | @Accessors(chain = true) 21 | @SuperBuilder 22 | @AllArgsConstructor 23 | @NoArgsConstructor 24 | @Data 25 | public class AdminSysDataDictClassifiedInsertOrUpdateDTO implements Serializable { 26 | 27 | @Schema(description = "主键ID", hidden = true, title = "仅更新时使用") 28 | private Long id; 29 | 30 | @Schema(description = "分类编码", requiredMode = Schema.RequiredMode.REQUIRED) 31 | @Size(max = 255, message = "【分类编码】最长255位") 32 | @NotBlank(message = "分类编码不能为空") 33 | private String code; 34 | 35 | @Schema(description = "分类名称", requiredMode = Schema.RequiredMode.REQUIRED) 36 | @Size(max = 255, message = "【分类名称】最长255位") 37 | @NotBlank(message = "分类名称不能为空") 38 | private String name; 39 | 40 | @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED) 41 | @NotNull(message = "状态不能为空") 42 | private EnabledStatusEnum status; 43 | 44 | @Schema(description = "分类描述") 45 | @Size(max = 255, message = "【分类描述】最长255位") 46 | private String description; 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/model/response/SysDeptBO.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.model.response; 2 | 3 | import cc.uncarbon.framework.core.constant.HelioConstant; 4 | import cc.uncarbon.framework.core.enums.EnabledStatusEnum; 5 | import com.fasterxml.jackson.annotation.JsonFormat; 6 | import io.swagger.v3.oas.annotations.media.Schema; 7 | import lombok.AllArgsConstructor; 8 | import lombok.Builder; 9 | import lombok.Data; 10 | import lombok.NoArgsConstructor; 11 | import lombok.experimental.Accessors; 12 | import org.springframework.format.annotation.DateTimeFormat; 13 | 14 | import java.io.Serializable; 15 | import java.time.LocalDateTime; 16 | 17 | 18 | /** 19 | * 部门BO 20 | */ 21 | @Accessors(chain = true) 22 | @Builder 23 | @AllArgsConstructor 24 | @NoArgsConstructor 25 | @Data 26 | public class SysDeptBO implements Serializable { 27 | 28 | @Schema(description = "主键ID") 29 | private Long id; 30 | 31 | @Schema(description = "创建时刻") 32 | @DateTimeFormat(pattern = HelioConstant.Jackson.DATE_TIME_FORMAT) 33 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = HelioConstant.Jackson.DATE_TIME_FORMAT) 34 | private LocalDateTime createdAt; 35 | 36 | @Schema(description = "更新时刻") 37 | @DateTimeFormat(pattern = HelioConstant.Jackson.DATE_TIME_FORMAT) 38 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = HelioConstant.Jackson.DATE_TIME_FORMAT) 39 | private LocalDateTime updatedAt; 40 | 41 | @Schema(description = "名称") 42 | private String title; 43 | 44 | @Schema(description = "上级ID(无上级节点设置为0)") 45 | private Long parentId; 46 | 47 | @Schema(description = "排序") 48 | private Integer sort; 49 | 50 | @Schema(description = "状态") 51 | private EnabledStatusEnum status; 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/enums/SysErrorEnum.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.enums; 2 | 3 | import cc.uncarbon.framework.core.enums.HelioBaseEnum; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | 7 | 8 | /** 9 | * sys模块错误枚举类 10 | */ 11 | @AllArgsConstructor 12 | @Getter 13 | public enum SysErrorEnum implements HelioBaseEnum { 14 | 15 | INVALID_ID(400, "无效ID"), 16 | INCORRECT_PIN_OR_PWD(400, "账号或密码不正确"), 17 | BANNED_USER(400, "用户被封禁"), 18 | INVALID_TENANT(400, "所属租户无效"), 19 | DISABLED_TENANT(400, "所属租户已禁用"), 20 | INCORRECT_OLD_PASSWORD(400, "原密码有误"), 21 | NO_ROLE_AVAILABLE_FOR_CURRENT_USER(400, "当前用户没有可用角色"), 22 | NO_MENU_AVAILABLE_FOR_CURRENT_ROLE(400, "当前角色没有可用菜单"), 23 | 24 | // 以下8个枚举用于后台角色管理的越权检查 25 | ROLE_VALUE_CANNOT_BE(403, "角色值 {} 不能用于新增或编辑,请选用其他值"), 26 | CANNOT_DELETE_SUPER_ADMIN_ROLE(403, "不能删除超级管理员角色"), 27 | CANNOT_DELETE_TENANT_ADMIN_ROLE(403, "为减少脏数据,不建议直接删除租户管理员角色,需通过【删除租户】关联删除"), 28 | CANNOT_DELETE_SELF_ROLE(403, "不能删除自身角色"), 29 | CANNOT_BIND_MENUS_FOR_SUPER_ADMIN_ROLE(403, "不能为超级管理员角色绑定菜单"), 30 | CANNOT_BIND_MENUS_FOR_SELF(403, "不能为自身角色绑定菜单"), 31 | BEYOND_AUTHORITY_BIND_MENUS(401, "不得超越自身菜单权限"), 32 | CANNOT_BIND_MENUS_FOR_TENANT_ADMIN_ROLE(403, "无权为租户管理员绑定菜单"), 33 | 34 | // 以下4个枚举用于后台用户管理的越权检查 35 | CANNOT_OPERATE_SELF_USER(403, "不能对自身进行此操作"), 36 | CANNOT_OPERATE_THIS_USER(403, "不能该用户进行此操作"), 37 | CANNOT_UNBIND_SELF_TENANT_ADMIN_ROLE(403, "自身的管理员角色不能被取消"), 38 | BEYOND_AUTHORITY_BIND_ROLES(401, "不得超越自身角色权限"), 39 | 40 | CANNOT_DELETE_PRIVILEGED_TENANT(403, "不能删除超级租户"), 41 | NEED_DELETE_EXISTING_TENANT_ADMIN_ROLE(500, "租户ID {} 对应的租户管理员角色已存在,请使用超级管理员账号删除"),; 42 | 43 | private final Integer value; 44 | private final String label; 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/model/response/SysDataDictClassifiedBO.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.model.response; 2 | 3 | import cc.uncarbon.framework.core.constant.HelioConstant; 4 | import cc.uncarbon.framework.core.enums.EnabledStatusEnum; 5 | import com.fasterxml.jackson.annotation.JsonFormat; 6 | import io.swagger.v3.oas.annotations.media.Schema; 7 | import lombok.AllArgsConstructor; 8 | import lombok.Data; 9 | import lombok.NoArgsConstructor; 10 | import lombok.experimental.Accessors; 11 | import lombok.experimental.SuperBuilder; 12 | import org.springframework.format.annotation.DateTimeFormat; 13 | 14 | import java.io.Serializable; 15 | import java.time.LocalDateTime; 16 | 17 | 18 | /** 19 | * 数据字典分类 BO 20 | */ 21 | @Accessors(chain = true) 22 | @SuperBuilder 23 | @AllArgsConstructor 24 | @NoArgsConstructor 25 | @Data 26 | public class SysDataDictClassifiedBO implements Serializable { 27 | 28 | @Schema(description = "主键ID", hidden = true, title = "仅更新时使用") 29 | private Long id; 30 | 31 | @Schema(description = "创建时刻") 32 | @DateTimeFormat(pattern = HelioConstant.Jackson.DATE_TIME_FORMAT) 33 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = HelioConstant.Jackson.DATE_TIME_FORMAT) 34 | private LocalDateTime createdAt; 35 | 36 | @Schema(description = "更新时刻") 37 | @DateTimeFormat(pattern = HelioConstant.Jackson.DATE_TIME_FORMAT) 38 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = HelioConstant.Jackson.DATE_TIME_FORMAT) 39 | private LocalDateTime updatedAt; 40 | 41 | @Schema(description = "分类编码") 42 | private String code; 43 | 44 | @Schema(description = "分类名称") 45 | private String name; 46 | 47 | @Schema(description = "状态") 48 | private EnabledStatusEnum status; 49 | 50 | @Schema(description = "分类描述") 51 | private String description; 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/model/response/SysTenantBO.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.model.response; 2 | 3 | import cc.uncarbon.framework.core.constant.HelioConstant; 4 | import cc.uncarbon.framework.core.enums.EnabledStatusEnum; 5 | import com.fasterxml.jackson.annotation.JsonFormat; 6 | import io.swagger.v3.oas.annotations.media.Schema; 7 | import lombok.AllArgsConstructor; 8 | import lombok.Builder; 9 | import lombok.Data; 10 | import lombok.NoArgsConstructor; 11 | import lombok.experimental.Accessors; 12 | import org.springframework.format.annotation.DateTimeFormat; 13 | 14 | import java.io.Serializable; 15 | import java.time.LocalDateTime; 16 | 17 | 18 | /** 19 | * 系统租户BO 20 | */ 21 | @Accessors(chain = true) 22 | @Builder 23 | @AllArgsConstructor 24 | @NoArgsConstructor 25 | @Data 26 | public class SysTenantBO implements Serializable { 27 | 28 | @Schema(description = "主键ID") 29 | private Long id; 30 | 31 | @Schema(description = "创建时刻") 32 | @DateTimeFormat(pattern = HelioConstant.Jackson.DATE_TIME_FORMAT) 33 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = HelioConstant.Jackson.DATE_TIME_FORMAT) 34 | private LocalDateTime createdAt; 35 | 36 | @Schema(description = "更新时刻") 37 | @DateTimeFormat(pattern = HelioConstant.Jackson.DATE_TIME_FORMAT) 38 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = HelioConstant.Jackson.DATE_TIME_FORMAT) 39 | private LocalDateTime updatedAt; 40 | 41 | @Schema(description = "备注") 42 | private String remark; 43 | 44 | @Schema(description = "租户名") 45 | private String tenantName; 46 | 47 | @Schema(description = "租户ID") 48 | private Long tenantId; 49 | 50 | @Schema(description = "状态") 51 | private EnabledStatusEnum status; 52 | 53 | @Schema(description = "租户管理员用户基本信息") 54 | private SysUserBaseInfoBO tenantAdminUser; 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/entity/SysDataDictItemEntity.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.entity; 2 | 3 | import cc.uncarbon.framework.core.enums.EnabledStatusEnum; 4 | import cc.uncarbon.framework.crud.entity.HelioBaseEntity; 5 | import com.baomidou.mybatisplus.annotation.TableField; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | import io.swagger.v3.oas.annotations.media.Schema; 8 | import lombok.AllArgsConstructor; 9 | import lombok.Data; 10 | import lombok.EqualsAndHashCode; 11 | import lombok.NoArgsConstructor; 12 | import lombok.experimental.Accessors; 13 | import lombok.experimental.SuperBuilder; 14 | 15 | import java.io.Serial; 16 | 17 | 18 | /** 19 | * 数据字典项 20 | */ 21 | @EqualsAndHashCode(callSuper = true) 22 | @Accessors(chain = true) 23 | @SuperBuilder 24 | @AllArgsConstructor 25 | @NoArgsConstructor 26 | @Data 27 | @TableName(value = "sys_data_dict_item") 28 | public class SysDataDictItemEntity extends HelioBaseEntity { 29 | 30 | @Serial 31 | private static final long serialVersionUID = 1L; 32 | 33 | 34 | @Schema(description = "所属分类ID") 35 | @TableField(value = "classified_id") 36 | private Long classifiedId; 37 | 38 | @Schema(description = "字典项编码") 39 | @TableField(value = "code") 40 | private String code; 41 | 42 | @Schema(description = "字典项标签") 43 | @TableField(value = "label") 44 | private String label; 45 | 46 | @Schema(description = "字典项值") 47 | @TableField(value = "value") 48 | private String value; 49 | 50 | @Schema(description = "状态") 51 | @TableField(value = "status") 52 | private EnabledStatusEnum status; 53 | 54 | @Schema(description = "排序") 55 | @TableField(value = "sort") 56 | private Integer sort; 57 | 58 | @Schema(description = "描述") 59 | @TableField(value = "description") 60 | private String description; 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/model/response/SysLogBO.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.model.response; 2 | 3 | import cc.uncarbon.framework.core.constant.HelioConstant; 4 | import cc.uncarbon.module.sys.enums.SysLogStatusEnum; 5 | import com.fasterxml.jackson.annotation.JsonFormat; 6 | import io.swagger.v3.oas.annotations.media.Schema; 7 | import lombok.AllArgsConstructor; 8 | import lombok.Builder; 9 | import lombok.Data; 10 | import lombok.NoArgsConstructor; 11 | import lombok.experimental.Accessors; 12 | import org.springframework.format.annotation.DateTimeFormat; 13 | 14 | import java.io.Serializable; 15 | import java.time.LocalDateTime; 16 | 17 | 18 | /** 19 | * 系统日志BO 20 | */ 21 | @Accessors(chain = true) 22 | @Builder 23 | @AllArgsConstructor 24 | @NoArgsConstructor 25 | @Data 26 | public class SysLogBO implements Serializable { 27 | 28 | @Schema(description = "创建时刻") 29 | @DateTimeFormat(pattern = HelioConstant.Jackson.DATE_TIME_FORMAT) 30 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = HelioConstant.Jackson.DATE_TIME_FORMAT) 31 | private LocalDateTime createdAt; 32 | 33 | @Schema(description = "用户账号") 34 | private String username; 35 | 36 | @Schema(description = "操作内容") 37 | private String operation; 38 | 39 | @Schema(description = "IP地址") 40 | private String ip; 41 | 42 | @Schema(description = "状态") 43 | private SysLogStatusEnum status; 44 | 45 | @Schema(description = "用户UA") 46 | private String userAgent; 47 | 48 | @Schema(description = "IP地址属地-国家或地区名") 49 | private String ipLocationRegionName; 50 | 51 | @Schema(description = "IP地址属地-省级行政区名") 52 | private String ipLocationProvinceName; 53 | 54 | @Schema(description = "IP地址属地-市级行政区名") 55 | private String ipLocationCityName; 56 | 57 | @Schema(description = "IP地址属地-县级行政区名") 58 | private String ipLocationDistrictName; 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/mapper/SysUserMapper.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.mapper; 2 | 3 | import cc.uncarbon.framework.core.enums.EnabledStatusEnum; 4 | import cc.uncarbon.module.sys.entity.SysUserEntity; 5 | import cc.uncarbon.module.sys.model.response.SysUserBaseInfoBO; 6 | import cn.hutool.core.collection.CollUtil; 7 | import com.baomidou.mybatisplus.annotation.InterceptorIgnore; 8 | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; 9 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 10 | import org.apache.ibatis.annotations.Mapper; 11 | import org.apache.ibatis.annotations.Param; 12 | 13 | import java.util.Collection; 14 | import java.util.List; 15 | import java.util.stream.Collectors; 16 | 17 | /** 18 | * 后台用户 19 | */ 20 | @Mapper 21 | public interface SysUserMapper extends BaseMapper { 22 | 23 | /** 24 | * 取用户实体,忽略行级租户拦截器 25 | * @param pin 账号 26 | * @return SysUserEntity 27 | */ 28 | @InterceptorIgnore(tenantLine = "true") 29 | SysUserEntity getUserByPin(@Param(value = "pin") String pin); 30 | 31 | /** 32 | * 取用户基本信息,忽略行级租户拦截器 33 | * @param userId 用户ID 34 | * @return SysUserBaseInfoBO 35 | */ 36 | @InterceptorIgnore(tenantLine = "true") 37 | SysUserBaseInfoBO getBaseInfoByUserId(@Param(value = "userId") Long userId); 38 | 39 | /** 40 | * 查询所有用户IDs 41 | * @param statusEnums 仅保留符合指定状态的,可以为null 42 | */ 43 | default List selectIds(Collection statusEnums) { 44 | return selectList( 45 | new LambdaQueryWrapper() 46 | // 只取主键ID 47 | .select(SysUserEntity::getId) 48 | // 状态 49 | .in(CollUtil.isNotEmpty(statusEnums), SysUserEntity::getStatus, statusEnums) 50 | ).stream().map(SysUserEntity::getId).collect(Collectors.toList()); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/entity/SysMenuEntity.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.entity; 2 | 3 | import cc.uncarbon.framework.core.enums.EnabledStatusEnum; 4 | import cc.uncarbon.framework.crud.entity.HelioBaseEntity; 5 | import cc.uncarbon.module.sys.enums.SysMenuTypeEnum; 6 | import com.baomidou.mybatisplus.annotation.TableField; 7 | import com.baomidou.mybatisplus.annotation.TableName; 8 | import io.swagger.v3.oas.annotations.media.Schema; 9 | import lombok.AllArgsConstructor; 10 | import lombok.Data; 11 | import lombok.EqualsAndHashCode; 12 | import lombok.NoArgsConstructor; 13 | import lombok.experimental.Accessors; 14 | import lombok.experimental.SuperBuilder; 15 | 16 | 17 | /** 18 | * 后台菜单 19 | */ 20 | @EqualsAndHashCode(callSuper = true) 21 | @Accessors(chain = true) 22 | @SuperBuilder 23 | @AllArgsConstructor 24 | @NoArgsConstructor 25 | @Data 26 | @TableName(value = "sys_menu") 27 | public class SysMenuEntity extends HelioBaseEntity { 28 | 29 | @Schema(description = "名称") 30 | @TableField(value = "title") 31 | private String title; 32 | 33 | @Schema(description = "上级菜单ID") 34 | @TableField(value = "parent_id") 35 | private Long parentId; 36 | 37 | @Schema(description = "菜单类型") 38 | @TableField(value = "type") 39 | private SysMenuTypeEnum type; 40 | 41 | @Schema(description = "权限标识") 42 | @TableField(value = "permission") 43 | private String permission; 44 | 45 | @Schema(description = "图标") 46 | @TableField(value = "icon") 47 | private String icon; 48 | 49 | @Schema(description = "排序") 50 | @TableField(value = "sort") 51 | private Integer sort; 52 | 53 | @Schema(description = "状态") 54 | @TableField(value = "status") 55 | private EnabledStatusEnum status; 56 | 57 | @Schema(description = "组件(Vue项目中`/@/views/`后的路径部分; 填`LAYOUT`为空页面)") 58 | @TableField(value = "component") 59 | private String component; 60 | 61 | @Schema(description = "外链地址") 62 | @TableField(value = "external_link") 63 | private String externalLink; 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/main/resources/application-prod.yml: -------------------------------------------------------------------------------- 1 | helio: 2 | crud: 3 | # JDBC驱动类名,根据数据库类型自行修改 4 | db-driver-class: com.mysql.cj.jdbc.Driver 5 | # 数据库类型 6 | db-type: mysql 7 | # 数据库连接主机 8 | db-host: 127.0.0.1 9 | # 数据库端口 10 | db-port: 3306 11 | # 数据库登录账号 12 | db-username: root 13 | # 数据库登录密码 14 | db-password: root 15 | # 数据库名 16 | db-name: helio_boot 17 | web: 18 | logging: 19 | # 是否启用 Web 访问日志切面;开发、测试环境可以开启,生产环境不开启 20 | enabled: false 21 | 22 | spring: 23 | data: 24 | redis: 25 | # Redis连接主机 26 | host: 127.0.0.1 27 | # Redis连接端口 28 | port: 6379 29 | # Redis登录密码;如果没有设置密码,请注释掉下面这行,避免向 Redis-server 发送 AUTH 鉴权请求,引发程序启动失败 30 | password: 31 | # Redis数据库序号 32 | database: 1 33 | 34 | knife4j: 35 | # 生产环境务必设为true 36 | production: true 37 | 38 | dromara: 39 | x-file-storage: 40 | # 文档:https://x-file-storage.xuyanwu.cn/ 41 | # 默认使用 local-plus-1 存储平台 42 | default-platform: local-plus-1 43 | # 缩略图后缀 44 | thumbnail-suffix: ".min.jpg" 45 | local-plus: # 本地存储升级版,不使用的情况下可以不写 46 | - platform: local-plus-1 # 存储平台标识 47 | enable-storage: true #启用存储 48 | enable-access: false #启用访问(线上请使用 Nginx 配置,效率更高) 49 | domain: "" # 访问域名,例如:“http://127.0.0.1:8030/”,注意后面要和 path-patterns 保持一致,“/”结尾,本地存储建议使用相对路径,方便后期更换域名 50 | base-path: / # 基础路径 51 | path-patterns: /** # 访问路径 52 | storage-path: (实际本地存储路径,如 D:/local-plus-1) # 实际本地存储路径 53 | minio: # MinIO,由于 MinIO SDK 支持 AWS S3,其它兼容 AWS S3 协议的存储平台也都可配置在这里 54 | - platform: minio-1 # 存储平台标识 55 | enable-storage: true # 启用存储 56 | access-key: (MinIO AK) 57 | secret-key: (MinIO SK) 58 | end-point: http://127.0.0.1:9000 59 | bucket-name: helio 60 | domain: http://127.0.0.1:9000/helio/ # 访问域名,注意“/”结尾,例如:http://minio.abc.com/abc/ 61 | base-path: / # 基础路径 62 | 63 | logging: 64 | level: 65 | # 指定日志级别,开发、测试环境建议为 DEBUG,生产环境建议为 INFO 66 | cc.uncarbon: INFO 67 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/model/request/AdminInsertSysTenantDTO.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.model.request; 2 | 3 | import cc.uncarbon.framework.core.constant.HelioConstant; 4 | import io.swagger.v3.oas.annotations.media.Schema; 5 | import jakarta.validation.constraints.*; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Data; 8 | import lombok.EqualsAndHashCode; 9 | import lombok.NoArgsConstructor; 10 | import lombok.experimental.Accessors; 11 | 12 | 13 | /** 14 | * 后台管理-新增系统租户 15 | */ 16 | @EqualsAndHashCode(callSuper = true) 17 | @Accessors(chain = true) 18 | @AllArgsConstructor 19 | @NoArgsConstructor 20 | @Data 21 | public class AdminInsertSysTenantDTO extends AdminUpdateSysTenantDTO { 22 | 23 | @Schema(description = "租户ID(纯数字)", requiredMode = Schema.RequiredMode.REQUIRED) 24 | @Positive(message = "租户ID须为正整数") 25 | @NotNull(message = "租户ID不能为空") 26 | private Long tenantId; 27 | 28 | @Schema(description = "管理员账号", requiredMode = Schema.RequiredMode.REQUIRED) 29 | @Size(min = 6, max = 16, message = "【管理员账号】最短6位,最长16位") 30 | @NotBlank(message = "管理员账号不能为空") 31 | private String tenantAdminUsername; 32 | 33 | @Schema(description = "管理员密码", requiredMode = Schema.RequiredMode.REQUIRED) 34 | @Size(min = 8, max = 20, message = "【管理员密码】最短8位,最长20位") 35 | @NotBlank(message = "管理员密码不能为空") 36 | private String tenantAdminPassword; 37 | 38 | @Schema(description = "管理员邮箱", requiredMode = Schema.RequiredMode.REQUIRED) 39 | @Pattern(message = "管理员邮箱格式不正确", regexp = HelioConstant.Regex.EMAIL) 40 | @Size(max = 255, message = "【管理员邮箱】最长255位") 41 | @NotBlank(message = "管理员邮箱不能为空") 42 | private String tenantAdminEmail; 43 | 44 | @Schema(description = "管理员手机号", requiredMode = Schema.RequiredMode.REQUIRED) 45 | @Pattern(message = "管理员手机号格式不正确", regexp = HelioConstant.Regex.CHINA_MAINLAND_PHONE_NO) 46 | @Size(max = 20, message = "【管理员手机号】最长20位") 47 | @NotBlank(message = "管理员手机号不能为空") 48 | private String tenantAdminPhoneNo; 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/test/java/cc/uncarbon/test/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.test; 2 | 3 | import cc.uncarbon.HelioBootApplication; 4 | import cc.uncarbon.framework.core.constant.HelioConstant; 5 | import cc.uncarbon.framework.core.context.TenantContext; 6 | import cc.uncarbon.framework.core.context.TenantContextHolder; 7 | import cc.uncarbon.framework.core.context.UserContext; 8 | import cc.uncarbon.framework.core.context.UserContextHolder; 9 | import cc.uncarbon.module.sys.model.response.SysRoleBO; 10 | import cc.uncarbon.module.sys.service.SysRoleService; 11 | import cn.hutool.core.collection.CollUtil; 12 | import jakarta.annotation.Resource; 13 | import org.junit.jupiter.api.Assertions; 14 | import org.junit.jupiter.api.BeforeAll; 15 | import org.junit.jupiter.api.Test; 16 | import org.springframework.boot.test.context.SpringBootTest; 17 | 18 | import java.util.List; 19 | 20 | /** 21 | * 一个仅用于示例的单元测试 22 | * 23 | * @author Uncarbon 24 | */ 25 | @SpringBootTest(classes = HelioBootApplication.class) 26 | class ExampleUnitTest { 27 | 28 | @Resource 29 | private SysRoleService sysRoleService; 30 | 31 | 32 | @BeforeAll 33 | public static void init() { 34 | // 设置用户上下文 35 | UserContext userContext = new UserContext(); 36 | userContext 37 | .setUserId(1L) 38 | .setUserName("超级管理员") 39 | // 用户类型, 根据单元测试需要修改 40 | .setUserTypeStr("ADMIN_USER"); 41 | UserContextHolder.setUserContext(userContext); 42 | 43 | // 设置租户上下文 44 | TenantContext tenantContext = new TenantContext(); 45 | tenantContext 46 | .setTenantId(HelioConstant.Tenant.DEFAULT_PRIVILEGED_TENANT_ID) 47 | .setTenantName("超级租户"); 48 | TenantContextHolder.setTenantContext(tenantContext); 49 | } 50 | 51 | @Test 52 | void exampleTest() { 53 | List selectOptions = sysRoleService.adminSelectOptions(); 54 | Assertions.assertTrue(CollUtil.isNotEmpty(selectOptions)); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/resources/application-dev.yml: -------------------------------------------------------------------------------- 1 | helio: 2 | crud: 3 | # JDBC驱动类名,根据数据库类型自行修改 4 | db-driver-class: com.mysql.cj.jdbc.Driver 5 | # 数据库类型 6 | db-type: mysql 7 | # 数据库连接主机 8 | db-host: 127.0.0.1 9 | # 数据库端口 10 | db-port: 3306 11 | # 数据库登录账号 12 | db-username: root 13 | # 数据库登录密码 14 | db-password: root 15 | # 数据库名 16 | db-name: helio_boot 17 | web: 18 | logging: 19 | # 是否启用 Web 访问日志切面;开发、测试环境可以开启,生产环境不开启 20 | enabled: false 21 | 22 | spring: 23 | data: 24 | redis: 25 | # Redis连接主机 26 | host: 127.0.0.1 27 | # Redis连接端口 28 | port: 6379 29 | # Redis登录密码;如果没有设置密码,请注释掉下面这行,避免向 Redis-server 发送 AUTH 鉴权请求,引发程序启动失败 30 | password: 31 | # Redis数据库序号 32 | database: 1 33 | 34 | knife4j: 35 | # 生产环境务必设为true 36 | production: false 37 | 38 | mybatis-plus: 39 | configuration: 40 | # 生产环境下建议去除该配置项,不在控制台打印SQL 41 | log-impl: org.apache.ibatis.logging.stdout.StdOutImpl 42 | 43 | dromara: 44 | x-file-storage: 45 | # 文档:https://x-file-storage.xuyanwu.cn/ 46 | # 默认使用 local-plus-1 存储平台 47 | default-platform: local-plus-1 48 | # 缩略图后缀 49 | thumbnail-suffix: ".min.jpg" 50 | local-plus: # 本地存储升级版,不使用的情况下可以不写 51 | - platform: local-plus-1 # 存储平台标识 52 | enable-storage: true #启用存储 53 | enable-access: false #启用访问(线上请使用 Nginx 配置,效率更高) 54 | domain: "" # 访问域名,例如:“http://127.0.0.1:8030/”,注意后面要和 path-patterns 保持一致,“/”结尾,本地存储建议使用相对路径,方便后期更换域名 55 | base-path: / # 基础路径 56 | path-patterns: /** # 访问路径 57 | storage-path: (实际本地存储路径,如 D:/local-plus-1) # 实际本地存储路径 58 | minio: # MinIO,由于 MinIO SDK 支持 AWS S3,其它兼容 AWS S3 协议的存储平台也都可配置在这里 59 | - platform: minio-1 # 存储平台标识 60 | enable-storage: true # 启用存储 61 | access-key: (MinIO AK) 62 | secret-key: (MinIO SK) 63 | end-point: http://127.0.0.1:9000 64 | bucket-name: helio 65 | domain: http://127.0.0.1:9000/helio/ # 访问域名,注意“/”结尾,例如:http://minio.abc.com/abc/ 66 | base-path: / # 基础路径 67 | -------------------------------------------------------------------------------- /src/main/resources/application-test.yml: -------------------------------------------------------------------------------- 1 | helio: 2 | crud: 3 | # JDBC驱动类名,根据数据库类型自行修改 4 | db-driver-class: com.mysql.cj.jdbc.Driver 5 | # 数据库类型 6 | db-type: mysql 7 | # 数据库连接主机 8 | db-host: 127.0.0.1 9 | # 数据库端口 10 | db-port: 3306 11 | # 数据库登录账号 12 | db-username: root 13 | # 数据库登录密码 14 | db-password: root 15 | # 数据库名 16 | db-name: helio_boot 17 | web: 18 | logging: 19 | # 是否启用 Web 访问日志切面;开发、测试环境可以开启,生产环境不开启 20 | enabled: true 21 | 22 | spring: 23 | data: 24 | redis: 25 | # Redis连接主机 26 | host: 127.0.0.1 27 | # Redis连接端口 28 | port: 6379 29 | # Redis登录密码;如果没有设置密码,请注释掉下面这行,避免向 Redis-server 发送 AUTH 鉴权请求,引发程序启动失败 30 | password: 31 | # Redis数据库序号 32 | database: 1 33 | 34 | knife4j: 35 | # 生产环境务必设为true 36 | production: false 37 | 38 | mybatis-plus: 39 | configuration: 40 | # 生产环境下建议去除该配置项,不在控制台打印SQL 41 | log-impl: org.apache.ibatis.logging.stdout.StdOutImpl 42 | 43 | dromara: 44 | x-file-storage: 45 | # 文档:https://x-file-storage.xuyanwu.cn/ 46 | # 默认使用 local-plus-1 存储平台 47 | default-platform: local-plus-1 48 | # 缩略图后缀 49 | thumbnail-suffix: ".min.jpg" 50 | local-plus: # 本地存储升级版,不使用的情况下可以不写 51 | - platform: local-plus-1 # 存储平台标识 52 | enable-storage: true #启用存储 53 | enable-access: false #启用访问(线上请使用 Nginx 配置,效率更高) 54 | domain: "" # 访问域名,例如:“http://127.0.0.1:8030/”,注意后面要和 path-patterns 保持一致,“/”结尾,本地存储建议使用相对路径,方便后期更换域名 55 | base-path: / # 基础路径 56 | path-patterns: /** # 访问路径 57 | storage-path: (实际本地存储路径,如 D:/local-plus-1) # 实际本地存储路径 58 | minio: # MinIO,由于 MinIO SDK 支持 AWS S3,其它兼容 AWS S3 协议的存储平台也都可配置在这里 59 | - platform: minio-1 # 存储平台标识 60 | enable-storage: true # 启用存储 61 | access-key: (MinIO AK) 62 | secret-key: (MinIO SK) 63 | end-point: http://127.0.0.1:9000 64 | bucket-name: helio 65 | domain: http://127.0.0.1:9000/helio/ # 访问域名,注意“/”结尾,例如:http://minio.abc.com/abc/ 66 | base-path: / # 基础路径 67 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/model/response/SysDataDictItemBO.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.model.response; 2 | 3 | import cc.uncarbon.framework.core.constant.HelioConstant; 4 | import cc.uncarbon.framework.core.enums.EnabledStatusEnum; 5 | import com.fasterxml.jackson.annotation.JsonFormat; 6 | import io.swagger.v3.oas.annotations.media.Schema; 7 | import lombok.AllArgsConstructor; 8 | import lombok.Data; 9 | import lombok.NoArgsConstructor; 10 | import lombok.experimental.Accessors; 11 | import lombok.experimental.SuperBuilder; 12 | import org.springframework.format.annotation.DateTimeFormat; 13 | 14 | import java.io.Serializable; 15 | import java.time.LocalDateTime; 16 | 17 | 18 | /** 19 | * 数据字典项 BO 20 | */ 21 | @Accessors(chain = true) 22 | @SuperBuilder 23 | @AllArgsConstructor 24 | @NoArgsConstructor 25 | @Data 26 | public class SysDataDictItemBO implements Serializable { 27 | 28 | @Schema(description = "主键ID", hidden = true, title = "仅更新时使用") 29 | private Long id; 30 | 31 | @Schema(description = "创建时刻") 32 | @DateTimeFormat(pattern = HelioConstant.Jackson.DATE_TIME_FORMAT) 33 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = HelioConstant.Jackson.DATE_TIME_FORMAT) 34 | private LocalDateTime createdAt; 35 | 36 | @Schema(description = "更新时刻") 37 | @DateTimeFormat(pattern = HelioConstant.Jackson.DATE_TIME_FORMAT) 38 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = HelioConstant.Jackson.DATE_TIME_FORMAT) 39 | private LocalDateTime updatedAt; 40 | 41 | @Schema(description = "所属分类ID") 42 | private Long classifiedId; 43 | 44 | @Schema(description = "字典项编码") 45 | private String code; 46 | 47 | @Schema(description = "字典项标签") 48 | private String label; 49 | 50 | @Schema(description = "字典项值") 51 | private String value; 52 | 53 | @Schema(description = "状态") 54 | private EnabledStatusEnum status; 55 | 56 | @Schema(description = "排序") 57 | private Integer sort; 58 | 59 | @Schema(description = "描述") 60 | private String description; 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/model/request/AdminUpdateCurrentSysUserInfoDTO.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.model.request; 2 | 3 | import cc.uncarbon.framework.core.constant.HelioConstant; 4 | import cc.uncarbon.framework.core.enums.GenderEnum; 5 | import io.swagger.v3.oas.annotations.media.Schema; 6 | import jakarta.validation.constraints.NotBlank; 7 | import jakarta.validation.constraints.NotNull; 8 | import jakarta.validation.constraints.Pattern; 9 | import jakarta.validation.constraints.Size; 10 | import lombok.AllArgsConstructor; 11 | import lombok.Data; 12 | import lombok.NoArgsConstructor; 13 | import lombok.experimental.Accessors; 14 | 15 | import java.io.Serializable; 16 | 17 | 18 | /** 19 | * 后台管理-更新当前后台用户信息资料 20 | */ 21 | @Accessors(chain = true) 22 | @AllArgsConstructor 23 | @NoArgsConstructor 24 | @Data 25 | public class AdminUpdateCurrentSysUserInfoDTO implements Serializable { 26 | 27 | 28 | @Schema(description = "昵称", requiredMode = Schema.RequiredMode.REQUIRED) 29 | @Size(max = 100, message = "【昵称】最长100位") 30 | @NotBlank(message = "昵称不能为空") 31 | private String nickname; 32 | 33 | @Schema(description = "性别", requiredMode = Schema.RequiredMode.REQUIRED) 34 | @NotNull(message = "性别不能为空") 35 | private GenderEnum gender; 36 | 37 | @Schema(description = "邮箱", requiredMode = Schema.RequiredMode.REQUIRED) 38 | @Pattern(message = "邮箱格式有误", regexp = HelioConstant.Regex.EMAIL) 39 | @Size(max = 255, message = "【邮箱】最长255位") 40 | @NotBlank(message = "邮箱不能为空") 41 | private String email; 42 | 43 | @Schema(description = "手机号", requiredMode = Schema.RequiredMode.REQUIRED) 44 | @Pattern(message = "手机号格式有误", regexp = HelioConstant.Regex.CHINA_MAINLAND_PHONE_NO) 45 | @Size(max = 20, message = "【手机号】最长20位") 46 | @NotBlank(message = "手机号不能为空") 47 | private String phoneNo; 48 | 49 | @Schema(description = "头像URL", requiredMode = Schema.RequiredMode.REQUIRED) 50 | @Size(max = 255, message = "头像格式有误") 51 | private String avatar; 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/model/request/AdminInsertOrUpdateSysMenuDTO.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.model.request; 2 | 3 | import cc.uncarbon.framework.core.enums.EnabledStatusEnum; 4 | import cc.uncarbon.module.sys.enums.SysMenuTypeEnum; 5 | import io.swagger.v3.oas.annotations.media.Schema; 6 | import jakarta.validation.constraints.NotBlank; 7 | import jakarta.validation.constraints.NotNull; 8 | import jakarta.validation.constraints.Size; 9 | import lombok.AllArgsConstructor; 10 | import lombok.Builder; 11 | import lombok.Data; 12 | import lombok.NoArgsConstructor; 13 | import lombok.experimental.Accessors; 14 | 15 | import java.io.Serializable; 16 | 17 | 18 | /** 19 | * 后台管理-新增/编辑后台菜单 20 | */ 21 | @Accessors(chain = true) 22 | @Builder 23 | @AllArgsConstructor 24 | @NoArgsConstructor 25 | @Data 26 | public class AdminInsertOrUpdateSysMenuDTO implements Serializable { 27 | 28 | @Schema(description = "主键ID", hidden = true, title = "仅更新时使用") 29 | private Long id; 30 | 31 | @Schema(description = "菜单名称", requiredMode = Schema.RequiredMode.REQUIRED) 32 | @Size(max = 50, message = "【菜单名称】最长50位") 33 | @NotBlank(message = "菜单名称不能为空") 34 | private String title; 35 | 36 | @Schema(description = "上级菜单ID(无上级节点设置为0)") 37 | private Long parentId; 38 | 39 | @Schema(description = "菜单类型", requiredMode = Schema.RequiredMode.REQUIRED) 40 | @NotNull(message = "菜单类型不能为空") 41 | private SysMenuTypeEnum type; 42 | 43 | @Schema(description = "组件") 44 | @Size(max = 50, message = "【组件】最长50位") 45 | private String component; 46 | 47 | @Schema(description = "权限标识") 48 | @Size(max = 255, message = "【权限标识】最长255位") 49 | private String permission; 50 | 51 | @Schema(description = "图标") 52 | @Size(max = 255, message = "【图标】最长255位") 53 | private String icon; 54 | 55 | @Schema(description = "排序") 56 | private Integer sort; 57 | 58 | @Schema(description = "状态") 59 | private EnabledStatusEnum status; 60 | 61 | @Schema(description = "外链地址") 62 | @Size(max = 255, message = "【外链地址】最长255位") 63 | private String externalLink; 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/model/request/AdminSysDataDictItemInsertOrUpdateDTO.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.model.request; 2 | 3 | import cc.uncarbon.framework.core.enums.EnabledStatusEnum; 4 | import io.swagger.v3.oas.annotations.media.Schema; 5 | import jakarta.validation.constraints.NotBlank; 6 | import jakarta.validation.constraints.NotNull; 7 | import jakarta.validation.constraints.Size; 8 | import lombok.AllArgsConstructor; 9 | import lombok.Data; 10 | import lombok.NoArgsConstructor; 11 | import lombok.experimental.Accessors; 12 | import lombok.experimental.SuperBuilder; 13 | 14 | import java.io.Serializable; 15 | 16 | 17 | /** 18 | * 后台管理-新增/编辑数据字典项 DTO 19 | */ 20 | @Accessors(chain = true) 21 | @SuperBuilder 22 | @AllArgsConstructor 23 | @NoArgsConstructor 24 | @Data 25 | public class AdminSysDataDictItemInsertOrUpdateDTO implements Serializable { 26 | 27 | @Schema(description = "主键ID", hidden = true, title = "仅更新时使用") 28 | private Long id; 29 | 30 | @Schema(description = "所属分类ID", hidden = true) 31 | private Long classifiedId; 32 | 33 | @Schema(description = "字典项编码", requiredMode = Schema.RequiredMode.REQUIRED) 34 | @Size(max = 255, message = "【字典项编码】最长255位") 35 | @NotBlank(message = "字典项编码不能为空") 36 | private String code; 37 | 38 | @Schema(description = "字典项标签", requiredMode = Schema.RequiredMode.REQUIRED) 39 | @Size(max = 255, message = "【字典项标签】最长255位") 40 | @NotBlank(message = "字典项标签不能为空") 41 | private String label; 42 | 43 | @Schema(description = "字典项值", requiredMode = Schema.RequiredMode.REQUIRED) 44 | @Size(max = 4096, message = "【字典项值】最长4096位") 45 | @NotBlank(message = "字典项值不能为空") 46 | private String value; 47 | 48 | @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED) 49 | @NotNull(message = "状态不能为空") 50 | private EnabledStatusEnum status; 51 | 52 | @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED) 53 | @NotNull(message = "排序不能为空") 54 | private Integer sort; 55 | 56 | @Schema(description = "描述") 57 | @Size(max = 255, message = "【描述】最长255位") 58 | private String description; 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/model/response/SysUserBO.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.model.response; 2 | 3 | import cc.uncarbon.framework.core.constant.HelioConstant; 4 | import cc.uncarbon.framework.core.enums.GenderEnum; 5 | import cc.uncarbon.module.sys.enums.SysUserStatusEnum; 6 | import com.fasterxml.jackson.annotation.JsonFormat; 7 | import io.swagger.v3.oas.annotations.media.Schema; 8 | import lombok.AllArgsConstructor; 9 | import lombok.Builder; 10 | import lombok.Data; 11 | import lombok.NoArgsConstructor; 12 | import lombok.experimental.Accessors; 13 | import org.springframework.format.annotation.DateTimeFormat; 14 | 15 | import java.io.Serializable; 16 | import java.time.LocalDateTime; 17 | 18 | 19 | /** 20 | * 后台用户BO 21 | */ 22 | @Accessors(chain = true) 23 | @Builder 24 | @AllArgsConstructor 25 | @NoArgsConstructor 26 | @Data 27 | public class SysUserBO implements Serializable { 28 | 29 | @Schema(description = "主键ID") 30 | private Long id; 31 | 32 | @Schema(description = "创建时刻") 33 | @DateTimeFormat(pattern = HelioConstant.Jackson.DATE_TIME_FORMAT) 34 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = HelioConstant.Jackson.DATE_TIME_FORMAT) 35 | private LocalDateTime createdAt; 36 | 37 | @Schema(description = "更新时刻") 38 | @DateTimeFormat(pattern = HelioConstant.Jackson.DATE_TIME_FORMAT) 39 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = HelioConstant.Jackson.DATE_TIME_FORMAT) 40 | private LocalDateTime updatedAt; 41 | 42 | @Schema(description = "账号") 43 | private String username; 44 | 45 | @Schema(description = "昵称") 46 | private String nickname; 47 | 48 | @Schema(description = "状态") 49 | private SysUserStatusEnum status; 50 | 51 | @Schema(description = "性别") 52 | private GenderEnum gender; 53 | 54 | @Schema(description = "邮箱") 55 | private String email; 56 | 57 | @Schema(description = "手机号") 58 | private String phoneNo; 59 | 60 | @Schema(description = "最后登录时刻") 61 | private LocalDateTime lastLoginAt; 62 | 63 | @Schema(description = "所属部门ID") 64 | private Long deptId; 65 | 66 | @Schema(description = "所属部门名称") 67 | private String deptTitle; 68 | 69 | @Schema(description = "头像URL") 70 | private String avatarUrl; 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/config/CustomInterceptorConfiguration.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.config; 2 | 3 | import cc.uncarbon.framework.core.props.HelioProperties; 4 | import cc.uncarbon.interceptor.AdminSaTokenParseInterceptor; 5 | import cc.uncarbon.interceptor.DefaultSaTokenParseInterceptor; 6 | import cc.uncarbon.module.adminapi.constant.AdminApiConstant; 7 | import cc.uncarbon.module.appapi.constant.AppApiConstant; 8 | import cn.dev33.satoken.interceptor.SaInterceptor; 9 | import cn.dev33.satoken.stp.StpUtil; 10 | import lombok.RequiredArgsConstructor; 11 | import org.springframework.context.annotation.Configuration; 12 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 13 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 14 | 15 | 16 | /** 17 | * 将自定义拦截器加入到拦截器队列中 18 | * 19 | * @author Uncarbon 20 | */ 21 | @Configuration 22 | @RequiredArgsConstructor 23 | public class CustomInterceptorConfiguration implements WebMvcConfigurer { 24 | 25 | private final HelioProperties helioProperties; 26 | 27 | 28 | @Override 29 | public void addInterceptors(InterceptorRegistry registry) { 30 | /* 31 | 1. 通用请求头解析,设定用户、租户上下文 32 | */ 33 | registry 34 | .addInterceptor(new DefaultSaTokenParseInterceptor()) 35 | // @since 1.7.3 fix: 访问除 /app/** 以外的路由时,不会清除复用线程中残留的用户态 36 | .addPathPatterns("/**"); 37 | 38 | registry 39 | .addInterceptor(new AdminSaTokenParseInterceptor()) 40 | .addPathPatterns(AdminApiConstant.HTTP_API_URL_PREFIX + "/**"); 41 | 42 | /* 43 | 2. 注解拦截器,启用注解功能 44 | */ 45 | registry 46 | .addInterceptor(new SaInterceptor()) 47 | .addPathPatterns("/**"); 48 | 49 | /* 50 | 3. /app/** 路由拦截器, 使几乎所有接口都需要登录 51 | 放行接口请在配置文件的 helio.security.exclude-routes 中设置 52 | 53 | @see https://sa-token.cc/doc.html#/use/route-check 54 | */ 55 | registry 56 | .addInterceptor(new SaInterceptor( 57 | handler -> StpUtil.checkLogin() 58 | )) 59 | .addPathPatterns(AppApiConstant.HTTP_API_URL_PREFIX + "/**") 60 | .excludePathPatterns(helioProperties.getSecurity().getExcludeRoutes()); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/adminapi/web/sys/AdminSysLogController.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.adminapi.web.sys; 2 | 3 | import cc.uncarbon.framework.core.constant.HelioConstant; 4 | import cc.uncarbon.framework.core.page.PageParam; 5 | import cc.uncarbon.framework.core.page.PageResult; 6 | import cc.uncarbon.framework.web.model.response.ApiResult; 7 | import cc.uncarbon.module.adminapi.constant.AdminApiConstant; 8 | import cc.uncarbon.module.adminapi.util.AdminStpUtil; 9 | import cc.uncarbon.module.sys.model.request.AdminListSysLogDTO; 10 | import cc.uncarbon.module.sys.model.response.SysLogBO; 11 | import cc.uncarbon.module.sys.service.SysLogService; 12 | import cn.dev33.satoken.annotation.SaCheckLogin; 13 | import cn.dev33.satoken.annotation.SaCheckPermission; 14 | import io.swagger.v3.oas.annotations.Operation; 15 | import io.swagger.v3.oas.annotations.tags.Tag; 16 | import lombok.RequiredArgsConstructor; 17 | import lombok.extern.slf4j.Slf4j; 18 | import org.springframework.web.bind.annotation.GetMapping; 19 | import org.springframework.web.bind.annotation.PathVariable; 20 | import org.springframework.web.bind.annotation.RequestMapping; 21 | import org.springframework.web.bind.annotation.RestController; 22 | 23 | 24 | @SaCheckLogin(type = AdminStpUtil.TYPE) 25 | @Tag(name = "系统日志管理接口") 26 | @RequestMapping(value = AdminApiConstant.HTTP_API_URL_PREFIX + "/api/v1") 27 | @RequiredArgsConstructor 28 | @RestController 29 | @Slf4j 30 | public class AdminSysLogController { 31 | 32 | private static final String PERMISSION_PREFIX = "SysLog:"; 33 | 34 | private final SysLogService sysLogService; 35 | 36 | 37 | @SaCheckPermission(type = AdminStpUtil.TYPE, value = PERMISSION_PREFIX + HelioConstant.Permission.RETRIEVE) 38 | @Operation(summary = "分页列表") 39 | @GetMapping(value = "/sys/logs") 40 | public ApiResult> list(PageParam pageParam, AdminListSysLogDTO dto) { 41 | return ApiResult.data(sysLogService.adminList(pageParam, dto)); 42 | } 43 | 44 | @SaCheckPermission(type = AdminStpUtil.TYPE, value = PERMISSION_PREFIX + HelioConstant.Permission.RETRIEVE) 45 | @Operation(summary = "详情") 46 | @GetMapping(value = "/sys/logs/{id}") 47 | public ApiResult getById(@PathVariable Long id) { 48 | return ApiResult.data(sysLogService.getOneById(id, true)); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/model/interior/UserDeptContainer.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.model.interior; 2 | 3 | import cc.uncarbon.module.sys.entity.SysDeptEntity; 4 | import cn.hutool.core.collection.CollUtil; 5 | import lombok.Getter; 6 | 7 | import java.util.Collections; 8 | import java.util.List; 9 | import java.util.stream.Collectors; 10 | 11 | /** 12 | * 用户关联部门容器 13 | */ 14 | @Getter 15 | public class UserDeptContainer { 16 | 17 | /** 18 | * 直接关联的部门IDs 19 | * 一般只有0或1个元素 20 | */ 21 | private final List relatedDeptIds; 22 | 23 | /** 24 | * 直接关联的部门实例集合 25 | * 一般只有0或1个元素 26 | */ 27 | private final List relatedDepts; 28 | 29 | /** 30 | * 可见的部门IDs 31 | */ 32 | private List visibleDeptIds; 33 | 34 | /** 35 | * 可见的部门实例集合 36 | */ 37 | private List visibleDepts; 38 | 39 | 40 | public UserDeptContainer(List relatedDeptIds, List relatedDepts) { 41 | this.relatedDeptIds = relatedDeptIds; 42 | this.relatedDepts = relatedDepts; 43 | this.visibleDeptIds = relatedDeptIds; 44 | this.visibleDepts = relatedDepts; 45 | } 46 | 47 | /** 48 | * 用户是否有实际关联的部门 49 | */ 50 | public boolean hasRelatedDepts() { 51 | return CollUtil.isNotEmpty(relatedDeptIds) && CollUtil.isNotEmpty(relatedDepts); 52 | } 53 | 54 | /** 55 | * 用户主要关联的部门,默认取第一个元素 56 | * @return null or 部门实例 57 | */ 58 | public SysDeptEntity primaryRelatedDept() { 59 | return CollUtil.getFirst(relatedDepts); 60 | } 61 | 62 | /** 63 | * 用户是否有实际可见的部门 64 | * 也可视为部门的数据权限范围 65 | */ 66 | public boolean hasVisibleDepts() { 67 | return CollUtil.isNotEmpty(visibleDeptIds) && CollUtil.isNotEmpty(visibleDepts); 68 | } 69 | 70 | /** 71 | * 更新可见的部门 72 | */ 73 | public void updateVisibleDepts(List visibleDepts) { 74 | if (CollUtil.isEmpty(visibleDepts)) { 75 | this.visibleDeptIds = Collections.emptyList(); 76 | this.visibleDepts = Collections.emptyList(); 77 | } else { 78 | this.visibleDeptIds = visibleDepts.stream().map(SysDeptEntity::getId).collect(Collectors.toList()); 79 | this.visibleDepts = visibleDepts; 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/adminapi/web/common/AdminSelectOptionsController.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.adminapi.web.common; 2 | 3 | 4 | import cc.uncarbon.framework.web.model.response.ApiResult; 5 | import cc.uncarbon.module.adminapi.constant.AdminApiConstant; 6 | import cc.uncarbon.module.adminapi.model.response.AdminSelectOptionItemVO; 7 | import cc.uncarbon.module.adminapi.util.AdminStpUtil; 8 | import cc.uncarbon.module.sys.model.response.SysDeptBO; 9 | import cc.uncarbon.module.sys.model.response.SysRoleBO; 10 | import cc.uncarbon.module.sys.service.SysDeptService; 11 | import cc.uncarbon.module.sys.service.SysRoleService; 12 | import cn.dev33.satoken.annotation.SaCheckLogin; 13 | import io.swagger.v3.oas.annotations.tags.Tag; 14 | import io.swagger.v3.oas.annotations.Operation; 15 | import lombok.RequiredArgsConstructor; 16 | import lombok.extern.slf4j.Slf4j; 17 | import org.springframework.web.bind.annotation.GetMapping; 18 | import org.springframework.web.bind.annotation.RequestMapping; 19 | import org.springframework.web.bind.annotation.RestController; 20 | 21 | import java.util.List; 22 | 23 | // 约束:登录后才能使用 👇 后台管理对应的鉴权工具类 24 | @SaCheckLogin(type = AdminStpUtil.TYPE) 25 | @Tag(name = "后台管理-下拉框数据源接口") 26 | @RequestMapping(AdminApiConstant.HTTP_API_URL_PREFIX + "/api/v1") 27 | @RequiredArgsConstructor 28 | @RestController 29 | @Slf4j 30 | public class AdminSelectOptionsController { 31 | 32 | private final SysRoleService sysRoleService; 33 | private final SysDeptService sysDeptService; 34 | 35 | 36 | /* 37 | 这里统一存放所有用于后台管理的下拉框数据源接口 38 | 避免多人协作时,不知道原来是否已经有了,或者写在某个边边角角里,造成重复开发 39 | */ 40 | 41 | @Operation(summary = "后台角色下拉框") 42 | @GetMapping(value = "/select-options/roles") 43 | public ApiResult> roles() { 44 | return ApiResult.data( 45 | AdminSelectOptionItemVO.listOf(sysRoleService.adminSelectOptions(), SysRoleBO::getId, SysRoleBO::getTitle) 46 | ); 47 | } 48 | 49 | @Operation(summary = "部门下拉框(前端负责转为树状数据)") 50 | @GetMapping(value = "/select-options/depts") 51 | public ApiResult> depts() { 52 | return ApiResult.data( 53 | AdminSelectOptionItemVO.listOf(sysDeptService.adminSelectOptions(true), SysDeptBO::getId, SysDeptBO::getTitle, SysDeptBO::getParentId) 54 | ); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/entity/SysLogEntity.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.entity; 2 | 3 | import cc.uncarbon.framework.crud.entity.HelioBaseEntity; 4 | import cc.uncarbon.module.sys.enums.SysLogStatusEnum; 5 | import com.baomidou.mybatisplus.annotation.TableField; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | import io.swagger.v3.oas.annotations.media.Schema; 8 | import lombok.AllArgsConstructor; 9 | import lombok.Data; 10 | import lombok.EqualsAndHashCode; 11 | import lombok.NoArgsConstructor; 12 | import lombok.experimental.Accessors; 13 | import lombok.experimental.SuperBuilder; 14 | 15 | 16 | /** 17 | * 系统日志 18 | */ 19 | @EqualsAndHashCode(callSuper = true) 20 | @Accessors(chain = true) 21 | @SuperBuilder 22 | @AllArgsConstructor 23 | @NoArgsConstructor 24 | @Data 25 | @TableName(value = "sys_log") 26 | public class SysLogEntity extends HelioBaseEntity { 27 | 28 | @Schema(description = "用户ID") 29 | @TableField(value = "user_id") 30 | private Long userId; 31 | 32 | @Schema(description = "用户账号") 33 | @TableField(value = "username") 34 | private String username; 35 | 36 | @Schema(description = "操作内容") 37 | @TableField(value = "operation") 38 | private String operation; 39 | 40 | @Schema(description = "请求方法") 41 | @TableField(value = "method") 42 | private String method; 43 | 44 | @Schema(description = "请求参数") 45 | @TableField(value = "params") 46 | private String params; 47 | 48 | @Schema(description = "IP地址") 49 | @TableField(value = "ip") 50 | private String ip; 51 | 52 | @Schema(description = "状态") 53 | @TableField(value = "status") 54 | private SysLogStatusEnum status; 55 | 56 | @Schema(description = "错误原因堆栈") 57 | @TableField(value = "error_stacktrace") 58 | private String errorStacktrace; 59 | 60 | @Schema(description = "用户UA") 61 | @TableField(value = "user_agent") 62 | private String userAgent; 63 | 64 | @Schema(description = "IP地址属地-国家或地区名") 65 | @TableField(value = "ip_location_region_name") 66 | private String ipLocationRegionName; 67 | 68 | @Schema(description = "IP地址属地-省级行政区名") 69 | @TableField(value = "ip_location_province_name") 70 | private String ipLocationProvinceName; 71 | 72 | @Schema(description = "IP地址属地-市级行政区名") 73 | @TableField(value = "ip_location_city_name") 74 | private String ipLocationCityName; 75 | 76 | @Schema(description = "IP地址属地-县级行政区名") 77 | @TableField(value = "ip_location_district_name") 78 | private String ipLocationDistrictName; 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/model/response/SysMenuBO.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.model.response; 2 | 3 | import cc.uncarbon.framework.core.constant.HelioConstant; 4 | import cc.uncarbon.framework.core.enums.EnabledStatusEnum; 5 | import cc.uncarbon.module.sys.enums.SysMenuTypeEnum; 6 | import com.fasterxml.jackson.annotation.JsonFormat; 7 | import io.swagger.v3.oas.annotations.media.Schema; 8 | import lombok.AllArgsConstructor; 9 | import lombok.Builder; 10 | import lombok.Data; 11 | import lombok.NoArgsConstructor; 12 | import lombok.experimental.Accessors; 13 | import org.springframework.format.annotation.DateTimeFormat; 14 | 15 | import java.io.Serializable; 16 | import java.time.LocalDateTime; 17 | 18 | 19 | /** 20 | * 后台菜单BO 21 | */ 22 | @Accessors(chain = true) 23 | @Builder 24 | @AllArgsConstructor 25 | @NoArgsConstructor 26 | @Data 27 | public class SysMenuBO implements Serializable { 28 | 29 | @Schema(description = "主键ID") 30 | private Long id; 31 | 32 | @Schema(description = "创建时刻") 33 | @DateTimeFormat(pattern = HelioConstant.Jackson.DATE_TIME_FORMAT) 34 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = HelioConstant.Jackson.DATE_TIME_FORMAT) 35 | private LocalDateTime createdAt; 36 | 37 | @Schema(description = "更新时刻") 38 | @DateTimeFormat(pattern = HelioConstant.Jackson.DATE_TIME_FORMAT) 39 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = HelioConstant.Jackson.DATE_TIME_FORMAT) 40 | private LocalDateTime updatedAt; 41 | 42 | @Schema(description = "名称") 43 | private String title; 44 | 45 | @Schema(description = "上级菜单ID") 46 | private Long parentId; 47 | 48 | @Schema(description = "菜单类型") 49 | private SysMenuTypeEnum type; 50 | 51 | @Schema(description = "权限标识") 52 | private String permission; 53 | 54 | @Schema(description = "图标") 55 | private String icon; 56 | 57 | @Schema(description = "排序") 58 | private Integer sort; 59 | 60 | @Schema(description = "状态") 61 | private EnabledStatusEnum status; 62 | 63 | @Schema(description = "组件") 64 | private String component; 65 | 66 | @Schema(description = "外链地址") 67 | private String externalLink; 68 | 69 | @Schema(description = "【用于Vben Admin】路由地址", hidden = true) 70 | private String path; 71 | 72 | @Schema(description = "【用于Vben Admin】菜单名(全局唯一, 不能重复)", hidden = true) 73 | private String name; 74 | 75 | @Schema(description = "【用于Vben Admin】菜单详情", hidden = true) 76 | private VbenAdminMenuMetaVO meta; 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/oss/entity/OssFileInfoEntity.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.oss.entity; 2 | 3 | import cc.uncarbon.framework.crud.entity.HelioBaseEntity; 4 | import com.baomidou.mybatisplus.annotation.TableField; 5 | import com.baomidou.mybatisplus.annotation.TableName; 6 | import io.swagger.v3.oas.annotations.media.Schema; 7 | import lombok.AllArgsConstructor; 8 | import lombok.Data; 9 | import lombok.EqualsAndHashCode; 10 | import lombok.NoArgsConstructor; 11 | import lombok.experimental.Accessors; 12 | import lombok.experimental.SuperBuilder; 13 | 14 | 15 | /** 16 | * 上传文件信息 17 | */ 18 | @EqualsAndHashCode(callSuper = true) 19 | @Accessors(chain = true) 20 | @SuperBuilder 21 | @AllArgsConstructor 22 | @NoArgsConstructor 23 | @Data 24 | @TableName(value = "oss_file_info") 25 | public class OssFileInfoEntity extends HelioBaseEntity { 26 | 27 | private static final long serialVersionUID = 1L; 28 | 29 | 30 | @Schema(description = "存储平台") 31 | @TableField(value = "storage_platform") 32 | private String storagePlatform; 33 | 34 | @Schema(description = "基础存储路径") 35 | @TableField(value = "storage_base_path") 36 | private String storageBasePath; 37 | 38 | @Schema(description = "存储路径") 39 | @TableField(value = "storage_path") 40 | private String storagePath; 41 | 42 | @Schema(description = "存储文件名") 43 | @TableField(value = "storage_filename") 44 | private String storageFilename; 45 | 46 | @Schema(description = "原始文件名") 47 | @TableField(value = "original_filename") 48 | private String originalFilename; 49 | 50 | @Schema(description = "扩展名") 51 | @TableField(value = "extend_name") 52 | private String extendName; 53 | 54 | @Schema(description = "文件大小") 55 | @TableField(value = "file_size") 56 | private Long fileSize; 57 | 58 | @Schema(description = "MD5") 59 | @TableField(value = "md5") 60 | private String md5; 61 | 62 | @Schema(description = "类别名") 63 | @TableField(value = "classified") 64 | private String classified; 65 | 66 | @Schema(description = "对象存储直链") 67 | @TableField(value = "direct_url") 68 | private String directUrl; 69 | 70 | 71 | /** 72 | * 取完整的存储文件名(带扩展名) 73 | */ 74 | public String getStorageFilenameFull() { 75 | return String.format("%s.%s", this.getStorageFilename(), this.getExtendName()); 76 | } 77 | 78 | /** 79 | * 取完整的原始文件名(带扩展名) 80 | */ 81 | public String getOriginalFilenameFull() { 82 | return String.format("%s.%s", this.getOriginalFilename(), this.getExtendName()); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/interceptor/DefaultSaTokenParseInterceptor.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.interceptor; 2 | 3 | import cc.uncarbon.framework.core.context.TenantContext; 4 | import cc.uncarbon.framework.core.context.TenantContextHolder; 5 | import cc.uncarbon.framework.core.context.UserContext; 6 | import cc.uncarbon.framework.core.context.UserContextHolder; 7 | import cc.uncarbon.framework.web.util.IPUtil; 8 | import cn.dev33.satoken.session.SaSession; 9 | import cn.dev33.satoken.stp.StpUtil; 10 | import lombok.NonNull; 11 | import lombok.extern.slf4j.Slf4j; 12 | import org.springframework.web.servlet.AsyncHandlerInterceptor; 13 | import org.springframework.web.servlet.resource.ResourceHttpRequestHandler; 14 | 15 | import jakarta.servlet.http.HttpServletRequest; 16 | import jakarta.servlet.http.HttpServletResponse; 17 | 18 | /** 19 | * 从请求头解析并赋值到用户上下文,默认用于C端用户的鉴权 20 | * @author Uncarbon 21 | */ 22 | @Slf4j 23 | public class DefaultSaTokenParseInterceptor implements AsyncHandlerInterceptor { 24 | 25 | @Override 26 | public boolean preHandle(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) { 27 | if (handler instanceof ResourceHttpRequestHandler) { 28 | // 直接放行静态资源 29 | return true; 30 | } 31 | 32 | // SA-Token 会自动从请求头中解析 token,所以这里可以直接拿到对应 session,从而取出业务字段 33 | if (StpUtil.isLogin()) { 34 | setContextsFromSaSession(StpUtil.getSession(), request); 35 | if (log.isDebugEnabled()) { 36 | log.debug("[SA-Token] 从请求头解析出用户上下文 >> {}", UserContextHolder.getUserContext()); 37 | } 38 | } else { 39 | UserContextHolder.clear(); 40 | TenantContextHolder.clear(); 41 | } 42 | 43 | return true; 44 | } 45 | 46 | /** 47 | * 自 SaSession 更新当前线程用户、租户上下文 48 | * 49 | * @param session 会话对象 50 | * @param request ServletRequest 对象 51 | */ 52 | public static void setContextsFromSaSession(SaSession session, HttpServletRequest request) { 53 | // 赋值用户上下文 54 | UserContext userContext = (UserContext) session.get(UserContext.CAMEL_NAME); 55 | 56 | // 获取用户公网IP 57 | // 先按逗号分隔后,再取第index个IP地址(从0开始);兼容启用了云防护盾CDN的服务器(可能获取到的IP会带上中间代理节点的IP地址) 58 | userContext.setClientIP(IPUtil.getClientIPAddress(request, 0)); 59 | UserContextHolder.setUserContext(userContext); 60 | 61 | // 赋值租户上下文 62 | TenantContext tenantContext = (TenantContext) session.get(TenantContext.CAMEL_NAME); 63 | TenantContextHolder.setTenantContext(tenantContext); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /attachments/db/MySQL/upgrade/2.0.0_to_2.1.0.sql: -------------------------------------------------------------------------------- 1 | -- 重构数据字典,拆分为`sys_data_dict_classified`(数据字典分类)和`sys_data_dict_item`(数据字典项),并重新设计字段 2 | CREATE TABLE `sys_data_dict_classified` ( 3 | `id` bigint(20) NOT NULL COMMENT '主键ID', 4 | `tenant_id` bigint(20) NULL DEFAULT NULL COMMENT '租户ID', 5 | `revision` bigint(20) NOT NULL DEFAULT 1 COMMENT '乐观锁', 6 | `del_flag` tinyint(4) UNSIGNED NOT NULL DEFAULT 0 COMMENT '逻辑删除标识', 7 | `created_at` datetime NOT NULL COMMENT '创建时刻', 8 | `created_by` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建者', 9 | `updated_at` datetime NOT NULL COMMENT '更新时刻', 10 | `updated_by` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '更新者', 11 | `code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '分类编码', 12 | `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '分类名称', 13 | `status` tinyint(4) NOT NULL DEFAULT 1 COMMENT '状态', 14 | `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '分类描述', 15 | PRIMARY KEY (`id`) USING BTREE 16 | ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '数据字典分类' ROW_FORMAT = Dynamic; 17 | 18 | CREATE TABLE `sys_data_dict_item` ( 19 | `id` bigint(20) NOT NULL COMMENT '主键ID', 20 | `tenant_id` bigint(20) NULL DEFAULT NULL COMMENT '租户ID', 21 | `revision` bigint(20) NOT NULL DEFAULT 1 COMMENT '乐观锁', 22 | `del_flag` tinyint(4) UNSIGNED NOT NULL DEFAULT 0 COMMENT '逻辑删除标识', 23 | `created_at` datetime NOT NULL COMMENT '创建时刻', 24 | `created_by` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建者', 25 | `updated_at` datetime NOT NULL COMMENT '更新时刻', 26 | `updated_by` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '更新者', 27 | `classified_id` bigint(20) NOT NULL COMMENT '所属分类ID', 28 | `code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '字典项编码', 29 | `label` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '字典项标签', 30 | `value` varchar(4096) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '字典项值', 31 | `status` tinyint(4) NOT NULL DEFAULT 1 COMMENT '状态', 32 | `sort` int(11) NOT NULL DEFAULT 1 COMMENT '排序', 33 | `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '描述', 34 | PRIMARY KEY (`id`) USING BTREE 35 | ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '数据字典项' ROW_FORMAT = Dynamic; 36 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/oss/model/response/OssFileInfoBO.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.oss.model.response; 2 | 3 | import cc.uncarbon.framework.core.constant.HelioConstant; 4 | import com.fasterxml.jackson.annotation.JsonFormat; 5 | import io.swagger.v3.oas.annotations.media.Schema; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | import lombok.experimental.Accessors; 10 | import lombok.experimental.SuperBuilder; 11 | import org.springframework.format.annotation.DateTimeFormat; 12 | 13 | import java.io.Serializable; 14 | import java.time.LocalDateTime; 15 | 16 | 17 | /** 18 | * 上传文件信息 BO 19 | */ 20 | @Accessors(chain = true) 21 | @SuperBuilder 22 | @AllArgsConstructor 23 | @NoArgsConstructor 24 | @Data 25 | public class OssFileInfoBO implements Serializable { 26 | 27 | @Schema(description = "主键ID") 28 | private Long id; 29 | 30 | @Schema(description = "创建时刻") 31 | @DateTimeFormat(pattern = HelioConstant.Jackson.DATE_TIME_FORMAT) 32 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = HelioConstant.Jackson.DATE_TIME_FORMAT) 33 | private LocalDateTime createdAt; 34 | 35 | @Schema(description = "更新时刻") 36 | @DateTimeFormat(pattern = HelioConstant.Jackson.DATE_TIME_FORMAT) 37 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = HelioConstant.Jackson.DATE_TIME_FORMAT) 38 | private LocalDateTime updatedAt; 39 | 40 | @Schema(description = "存储平台") 41 | private String storagePlatform; 42 | 43 | @Schema(description = "基础存储路径") 44 | private String storageBasePath; 45 | 46 | @Schema(description = "存储路径") 47 | private String storagePath; 48 | 49 | @Schema(description = "存储文件名") 50 | private String storageFilename; 51 | 52 | @Schema(description = "原始文件名") 53 | private String originalFilename; 54 | 55 | @Schema(description = "扩展名") 56 | private String extendName; 57 | 58 | @Schema(description = "文件大小") 59 | private Long fileSize; 60 | 61 | @Schema(description = "MD5") 62 | private String md5; 63 | 64 | @Schema(description = "类别编号") 65 | private String classified; 66 | 67 | @Schema(description = "对象存储直链") 68 | private String directUrl; 69 | 70 | /** 71 | * 取完整的存储文件名(带扩展名) 72 | */ 73 | public String getStorageFilenameFull() { 74 | return String.format("%s.%s", this.getStorageFilename(), this.getExtendName()); 75 | } 76 | 77 | /** 78 | * 取完整的原始文件名(带扩展名) 79 | */ 80 | public String getOriginalFilenameFull() { 81 | return String.format("%s.%s", this.getOriginalFilename(), this.getExtendName()); 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/interceptor/AdminSaTokenParseInterceptor.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.interceptor; 2 | 3 | import cc.uncarbon.framework.core.context.TenantContext; 4 | import cc.uncarbon.framework.core.context.TenantContextHolder; 5 | import cc.uncarbon.framework.core.context.UserContext; 6 | import cc.uncarbon.framework.core.context.UserContextHolder; 7 | import cc.uncarbon.framework.web.util.IPUtil; 8 | import cc.uncarbon.module.adminapi.util.AdminStpUtil; 9 | import cn.dev33.satoken.session.SaSession; 10 | import lombok.NonNull; 11 | import lombok.extern.slf4j.Slf4j; 12 | import org.springframework.web.servlet.AsyncHandlerInterceptor; 13 | import org.springframework.web.servlet.resource.ResourceHttpRequestHandler; 14 | 15 | import jakarta.servlet.http.HttpServletRequest; 16 | import jakarta.servlet.http.HttpServletResponse; 17 | 18 | 19 | /** 20 | * 从请求头解析并赋值到用户上下文,用于后台管理用户的鉴权 21 | * 其实就是"DefaultSaTokenParseInterceptor"改个名, 工具类换成"AdminStpUtil" 22 | * @author Uncarbon 23 | */ 24 | @Slf4j 25 | public class AdminSaTokenParseInterceptor implements AsyncHandlerInterceptor { 26 | 27 | @Override 28 | public boolean preHandle(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) { 29 | 30 | if (handler instanceof ResourceHttpRequestHandler) { 31 | // 直接放行静态资源 32 | return true; 33 | } 34 | 35 | // SA-Token 会自动从请求头中解析 token,所以这里可以直接拿到对应 session,从而取出业务字段 36 | if (AdminStpUtil.isLogin()) { 37 | setContextsFromSaSession(AdminStpUtil.getSession(), request); 38 | if (log.isDebugEnabled()) { 39 | log.debug("[SA-Token][Admin] 从请求头解析出用户上下文 >> {}", UserContextHolder.getUserContext()); 40 | } 41 | } else { 42 | UserContextHolder.clear(); 43 | TenantContextHolder.clear(); 44 | } 45 | 46 | return true; 47 | } 48 | 49 | /** 50 | * 自 SaSession 更新当前线程用户、租户上下文 51 | * 52 | * @param session 会话对象 53 | * @param request ServletRequest 对象 54 | */ 55 | public static void setContextsFromSaSession(SaSession session, HttpServletRequest request) { 56 | // 赋值用户上下文 57 | UserContext userContext = (UserContext) session.get(UserContext.CAMEL_NAME); 58 | 59 | // 获取用户公网IP 60 | // 先按逗号分隔后,再取第index个IP地址(从0开始);兼容启用了云防护盾CDN的服务器(可能获取到的IP会带上中间代理节点的IP地址) 61 | userContext.setClientIP(IPUtil.getClientIPAddress(request, 0)); 62 | UserContextHolder.setUserContext(userContext); 63 | 64 | // 赋值租户上下文 65 | TenantContext tenantContext = (TenantContext) session.get(TenantContext.CAMEL_NAME); 66 | TenantContextHolder.setTenantContext(tenantContext); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/adminapi/web/oss/AdminOssFileInfoController.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.adminapi.web.oss; 2 | 3 | import cc.uncarbon.framework.core.constant.HelioConstant; 4 | import cc.uncarbon.framework.core.page.PageParam; 5 | import cc.uncarbon.framework.core.page.PageResult; 6 | import cc.uncarbon.framework.web.model.request.IdsDTO; 7 | import cc.uncarbon.framework.web.model.response.ApiResult; 8 | import cc.uncarbon.module.adminapi.constant.AdminApiConstant; 9 | import cc.uncarbon.module.oss.model.request.AdminListOssFileInfoDTO; 10 | import cc.uncarbon.module.oss.model.response.OssFileInfoBO; 11 | import cc.uncarbon.module.oss.service.OssFileInfoService; 12 | import cc.uncarbon.module.adminapi.util.AdminStpUtil; 13 | import cn.dev33.satoken.annotation.SaCheckLogin; 14 | import cn.dev33.satoken.annotation.SaCheckPermission; 15 | import io.swagger.v3.oas.annotations.tags.Tag; 16 | import io.swagger.v3.oas.annotations.Operation; 17 | import lombok.RequiredArgsConstructor; 18 | import lombok.extern.slf4j.Slf4j; 19 | import org.springframework.web.bind.annotation.*; 20 | 21 | import jakarta.validation.Valid; 22 | 23 | 24 | @SaCheckLogin(type = AdminStpUtil.TYPE) 25 | @Tag(name = "后台管理-上传文件信息管理接口") 26 | @RequestMapping(value = AdminApiConstant.HTTP_API_URL_PREFIX + "/api/v1") 27 | @RequiredArgsConstructor 28 | @RestController 29 | @Slf4j 30 | public class AdminOssFileInfoController { 31 | 32 | // 功能权限串前缀 33 | private static final String PERMISSION_PREFIX = "OssFileInfo:"; 34 | 35 | private final OssFileInfoService ossFileInfoService; 36 | 37 | 38 | @SaCheckPermission(type = AdminStpUtil.TYPE, value = PERMISSION_PREFIX + HelioConstant.Permission.RETRIEVE) 39 | @Operation(summary = "分页列表") 40 | @GetMapping(value = "/oss/file/infos") 41 | public ApiResult> list(PageParam pageParam, AdminListOssFileInfoDTO dto) { 42 | return ApiResult.data(ossFileInfoService.adminList(pageParam, dto)); 43 | } 44 | 45 | @SaCheckPermission(type = AdminStpUtil.TYPE, value = PERMISSION_PREFIX + HelioConstant.Permission.RETRIEVE) 46 | @Operation(summary = "详情") 47 | @GetMapping(value = "/oss/file/infos/{id}") 48 | public ApiResult getById(@PathVariable Long id) { 49 | return ApiResult.data(ossFileInfoService.getOneById(id, true)); 50 | } 51 | 52 | @SaCheckPermission(type = AdminStpUtil.TYPE, value = PERMISSION_PREFIX + HelioConstant.Permission.DELETE) 53 | @Operation(summary = "删除") 54 | @DeleteMapping(value = "/oss/file/infos") 55 | public ApiResult delete(@RequestBody @Valid IdsDTO dto) { 56 | ossFileInfoService.adminDelete(dto.getIds()); 57 | 58 | return ApiResult.success(); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/entity/SysUserEntity.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.entity; 2 | 3 | import cc.uncarbon.framework.core.enums.GenderEnum; 4 | import cc.uncarbon.framework.crud.entity.HelioBaseEntity; 5 | import cc.uncarbon.module.sys.enums.SysUserStatusEnum; 6 | import cc.uncarbon.module.sys.model.request.AdminUpdateCurrentSysUserAvatarDTO; 7 | import cc.uncarbon.module.sys.model.request.AdminUpdateCurrentSysUserInfoDTO; 8 | import cn.hutool.core.annotation.Alias; 9 | import com.baomidou.mybatisplus.annotation.TableField; 10 | import com.baomidou.mybatisplus.annotation.TableName; 11 | import io.swagger.v3.oas.annotations.media.Schema; 12 | import lombok.AllArgsConstructor; 13 | import lombok.Data; 14 | import lombok.EqualsAndHashCode; 15 | import lombok.NoArgsConstructor; 16 | import lombok.experimental.Accessors; 17 | import lombok.experimental.SuperBuilder; 18 | 19 | import java.time.LocalDateTime; 20 | 21 | 22 | /** 23 | * 后台用户 24 | */ 25 | @EqualsAndHashCode(callSuper = true) 26 | @Accessors(chain = true) 27 | @SuperBuilder 28 | @AllArgsConstructor 29 | @NoArgsConstructor 30 | @Data 31 | @TableName(value = "sys_user") 32 | public class SysUserEntity extends HelioBaseEntity { 33 | 34 | @Schema(description = "账号") 35 | @Alias(value = "username") 36 | @TableField(value = "pin") 37 | private String pin; 38 | 39 | @Schema(description = "密码") 40 | @TableField(value = "pwd") 41 | private String pwd; 42 | 43 | @Schema(description = "盐") 44 | @TableField(value = "salt") 45 | private String salt; 46 | 47 | @Schema(description = "昵称") 48 | @TableField(value = "nickname") 49 | private String nickname; 50 | 51 | @Schema(description = "状态") 52 | @TableField(value = "status") 53 | private SysUserStatusEnum status; 54 | 55 | @Schema(description = "性别") 56 | @TableField(value = "gender") 57 | private GenderEnum gender; 58 | 59 | @Schema(description = "邮箱") 60 | @TableField(value = "email") 61 | private String email; 62 | 63 | @Schema(description = "手机号") 64 | @TableField(value = "phone_no") 65 | private String phoneNo; 66 | 67 | @Schema(description = "最后登录时刻") 68 | @TableField(value = "last_login_at") 69 | private LocalDateTime lastLoginAt; 70 | 71 | @Schema(description = "头像URL") 72 | @TableField(value = "avatar_url") 73 | private String avatarUrl; 74 | 75 | 76 | public static SysUserEntity of(AdminUpdateCurrentSysUserInfoDTO dto) { 77 | SysUserEntity ret = new SysUserEntity(); 78 | ret.setNickname(dto.getNickname()) 79 | .setGender(dto.getGender()) 80 | .setEmail(dto.getEmail()) 81 | .setPhoneNo(dto.getPhoneNo()); 82 | return ret; 83 | } 84 | 85 | public static SysUserEntity of(AdminUpdateCurrentSysUserAvatarDTO dto) { 86 | SysUserEntity ret = new SysUserEntity(); 87 | ret.setAvatarUrl(dto.getAvatarUrl()); 88 | return ret; 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/helper/RolePermissionCacheHelper.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.helper; 2 | 3 | import cc.uncarbon.framework.core.context.UserContextHolder; 4 | import cn.hutool.core.collection.CollUtil; 5 | import lombok.RequiredArgsConstructor; 6 | import org.springframework.data.redis.core.RedisTemplate; 7 | import org.springframework.stereotype.Component; 8 | 9 | import java.util.*; 10 | 11 | /** 12 | * 将角色对应权限,缓存至 Redis 13 | * 参考文章: https://sa-token.cc/doc.html#/fun/jur-cache 14 | * 15 | * @author Uncarbon 16 | */ 17 | @Component 18 | @RequiredArgsConstructor 19 | public class RolePermissionCacheHelper { 20 | 21 | private final RedisTemplate> stringSetRedisTemplate; 22 | 23 | private static final String CACHE_KEY_ROLE_PERMISSIONS = "Authorization:rolePermissions:roleId_%s"; 24 | 25 | 26 | /** 27 | * 从缓存中取得当前用户拥有的所有权限名集合 28 | * 29 | * @return List 30 | */ 31 | public List getUserPermissions() { 32 | Set rolesIds = UserContextHolder.getUserContext().getRolesIds(); 33 | if (CollUtil.isEmpty(rolesIds)) { 34 | return Collections.emptyList(); 35 | } 36 | 37 | // 批量查询缓存 38 | List cacheKeys = rolesIds.stream() 39 | .map(roleId -> String.format(CACHE_KEY_ROLE_PERMISSIONS, roleId)) 40 | .toList(); 41 | List> cacheValues = stringSetRedisTemplate.opsForValue().multiGet(cacheKeys); 42 | if (CollUtil.isEmpty(cacheValues)) { 43 | return Collections.emptyList(); 44 | } 45 | 46 | return cacheValues.stream().flatMap(Collection::stream).toList(); 47 | } 48 | 49 | /** 50 | * 覆盖更新角色对应权限至 Redis 51 | * 52 | * @param map key=角色ID value=权限集合 53 | */ 54 | public void putCache(Map> map) { 55 | Set>> entries = map.entrySet(); 56 | entries.forEach( 57 | entry -> this.putCache(entry.getKey(), entry.getValue()) 58 | ); 59 | } 60 | 61 | /** 62 | * 覆盖更新角色对应权限至 Redis 63 | * 64 | * @param roleId 角色ID 65 | * @param newPermissions 新权限名集合 66 | */ 67 | public void putCache(Long roleId, Collection newPermissions) { 68 | String cacheKey = String.format(CACHE_KEY_ROLE_PERMISSIONS, roleId); 69 | stringSetRedisTemplate.opsForValue().set(cacheKey, newPermissions); 70 | } 71 | 72 | /** 73 | * 删除角色ID对应的权限缓存 74 | * 75 | * @param roleIds 角色ID集合 76 | */ 77 | public void deleteCache(Collection roleIds) { 78 | if (CollUtil.isNotEmpty(roleIds)) { 79 | // 批量删除缓存 80 | List cacheKeys = roleIds.stream().map(roleId -> String.format(CACHE_KEY_ROLE_PERMISSIONS, roleId)).toList(); 81 | stringSetRedisTemplate.delete(cacheKeys); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/adminapi/web/sys/AdminCurrentSysUserController.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.adminapi.web.sys; 2 | 3 | import cc.uncarbon.framework.core.exception.BusinessException; 4 | import cc.uncarbon.framework.web.model.response.ApiResult; 5 | import cc.uncarbon.module.adminapi.constant.AdminApiConstant; 6 | import cc.uncarbon.module.adminapi.util.AdminStpUtil; 7 | import cc.uncarbon.module.sys.annotation.SysLog; 8 | import cc.uncarbon.module.sys.model.request.AdminUpdateCurrentSysUserAvatarDTO; 9 | import cc.uncarbon.module.sys.model.request.AdminUpdateCurrentSysUserInfoDTO; 10 | import cc.uncarbon.module.sys.model.request.AdminUpdateCurrentSysUserPasswordDTO; 11 | import cc.uncarbon.module.sys.model.response.VbenAdminUserInfoVO; 12 | import cc.uncarbon.module.sys.service.SysUserService; 13 | import cn.dev33.satoken.annotation.SaCheckLogin; 14 | import io.swagger.v3.oas.annotations.Operation; 15 | import io.swagger.v3.oas.annotations.tags.Tag; 16 | import jakarta.validation.Valid; 17 | import lombok.RequiredArgsConstructor; 18 | import lombok.extern.slf4j.Slf4j; 19 | import org.springframework.web.bind.annotation.*; 20 | 21 | 22 | @SaCheckLogin(type = AdminStpUtil.TYPE) 23 | @Tag(name = "当前后台用户信息接口") 24 | @RequestMapping(value = AdminApiConstant.HTTP_API_URL_PREFIX + "/api/v1") 25 | @RequiredArgsConstructor 26 | @RestController 27 | @Slf4j 28 | public class AdminCurrentSysUserController { 29 | 30 | private final SysUserService sysUserService; 31 | 32 | 33 | @Operation(summary = "取当前用户信息资料") 34 | // 新路由与旧路由并行 35 | @GetMapping(value = {"/sys/users/me/info", "/sys/users/info"}) 36 | public ApiResult getMyInfo() { 37 | return ApiResult.data(sysUserService.adminGetCurrentUserInfo()); 38 | } 39 | 40 | @SysLog(value = "修改当前用户密码") 41 | @Operation(summary = "修改当前用户密码") 42 | @PostMapping(value = "/sys/users/me/password:update") 43 | public ApiResult updatePassword(@RequestBody @Valid AdminUpdateCurrentSysUserPasswordDTO dto) { 44 | if (!dto.getConfirmNewPassword().equals(dto.getNewPassword())) { 45 | throw new BusinessException(400, "密码与确认密码不同,请检查"); 46 | } 47 | sysUserService.adminUpdateCurrentUserPassword(dto); 48 | 49 | // 用户更改密码后使其当前会话直接过期 50 | AdminStpUtil.logout(); 51 | 52 | return ApiResult.success(); 53 | } 54 | 55 | @Operation(summary = "更新当前用户信息资料") 56 | @PutMapping(value = "/sys/users/me/info") 57 | public ApiResult updateMyInfo(@RequestBody @Valid AdminUpdateCurrentSysUserInfoDTO dto) { 58 | sysUserService.adminUpdateCurrentUserInfo(dto); 59 | return ApiResult.success(); 60 | } 61 | 62 | @Operation(summary = "更新当前用户头像") 63 | @PutMapping(value = "/sys/users/me/avatar") 64 | public ApiResult updateMyAvatar(@RequestBody @Valid AdminUpdateCurrentSysUserAvatarDTO dto) { 65 | sysUserService.adminUpdateCurrentUserAvatar(dto); 66 | return ApiResult.success(); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/config/AsyncConfiguration.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.config; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; 6 | import org.springframework.boot.autoconfigure.task.TaskExecutionProperties; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.context.annotation.Primary; 10 | import org.springframework.scheduling.annotation.AsyncConfigurer; 11 | import org.springframework.scheduling.annotation.EnableAsync; 12 | import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; 13 | 14 | import java.util.concurrent.Executor; 15 | import java.util.concurrent.ThreadPoolExecutor; 16 | 17 | 18 | /** 19 | * @author Uncarbon 20 | */ 21 | @Slf4j 22 | @EnableAsync 23 | @Configuration 24 | @RequiredArgsConstructor 25 | public class AsyncConfiguration implements AsyncConfigurer { 26 | 27 | private final TaskExecutionProperties taskExecutionProperties; 28 | 29 | 30 | /** 31 | * 创建默认线程池 32 | * 加 @Primary 注解以确保依赖注入时,获取到的是这个Bean 33 | */ 34 | @Primary 35 | @Bean(name = "taskExecutor") 36 | public ThreadPoolTaskExecutor taskExecutor() { 37 | final String threadNamePrefix = "taskExecutor-"; 38 | 39 | if (log.isDebugEnabled()) { 40 | log.debug("[异步任务线程池] 创建默认线程池【taskExecutor】,该线程池参数可通过 spring.task.execution 调节 >> " 41 | + "corePoolSize核心线程池大小={}, maxPoolSize最大线程数={}, queueCapacity队列容量={}" 42 | + ", rejectedExecutionHandler拒绝策略={}", 43 | taskExecutionProperties.getPool().getCoreSize(), 44 | taskExecutionProperties.getPool().getMaxSize(), 45 | taskExecutionProperties.getPool().getQueueCapacity(), 46 | "CallerRunsPolicy" 47 | ); 48 | } 49 | 50 | ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); 51 | // 核心线程池大小,默认 8 52 | executor.setCorePoolSize(taskExecutionProperties.getPool().getCoreSize()); 53 | // 最大线程数,默认 Integer.MAX_VALUE 54 | executor.setMaxPoolSize(taskExecutionProperties.getPool().getMaxSize()); 55 | // 队列容量,默认 Integer.MAX_VALUE 56 | executor.setQueueCapacity(taskExecutionProperties.getPool().getQueueCapacity()); 57 | // 拒绝策略 58 | executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); 59 | // 线程名前缀 60 | executor.setThreadNamePrefix(threadNamePrefix); 61 | // 这个配置是为了graceful shutdown? 62 | executor.setWaitForTasksToCompleteOnShutdown(true); 63 | 64 | executor.initialize(); 65 | return executor; 66 | } 67 | 68 | @Override 69 | public Executor getAsyncExecutor() { 70 | return this.taskExecutor(); 71 | } 72 | 73 | @Override 74 | public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { 75 | return (ex, method, params) -> 76 | log.error("[异步任务线程池] 执行异步任务【{}】时出错 >> 堆栈\t\n", method, ex); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/model/request/AdminInsertOrUpdateSysUserDTO.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.model.request; 2 | 3 | import cc.uncarbon.framework.core.constant.HelioConstant; 4 | import cc.uncarbon.framework.core.enums.GenderEnum; 5 | import cc.uncarbon.framework.core.exception.BusinessException; 6 | import cc.uncarbon.module.sys.enums.SysUserStatusEnum; 7 | import cn.hutool.core.text.CharSequenceUtil; 8 | import io.swagger.v3.oas.annotations.media.Schema; 9 | import jakarta.validation.constraints.NotBlank; 10 | import jakarta.validation.constraints.NotNull; 11 | import jakarta.validation.constraints.Pattern; 12 | import jakarta.validation.constraints.Size; 13 | import lombok.AllArgsConstructor; 14 | import lombok.Builder; 15 | import lombok.Data; 16 | import lombok.NoArgsConstructor; 17 | import lombok.experimental.Accessors; 18 | 19 | import java.io.Serializable; 20 | import java.util.Objects; 21 | 22 | 23 | /** 24 | * 后台管理-新增/编辑后台用户 25 | */ 26 | @Accessors(chain = true) 27 | @Builder 28 | @AllArgsConstructor 29 | @NoArgsConstructor 30 | @Data 31 | public class AdminInsertOrUpdateSysUserDTO implements Serializable { 32 | 33 | @Schema(description = "主键ID", hidden = true, title = "仅更新时使用") 34 | private Long id; 35 | 36 | @Schema(description = "所属租户ID", hidden = true, title = "仅新增时使用") 37 | private Long tenantId; 38 | 39 | @Schema(description = "账号", requiredMode = Schema.RequiredMode.REQUIRED) 40 | @Size(min = 6, max = 16, message = "【账号】最短6位,最长16位") 41 | @NotBlank(message = "账号不能为空") 42 | private String username; 43 | 44 | @Schema(description = "密码字符串(仅注册时有效)") 45 | private String passwordOfNewUser; 46 | 47 | @Schema(description = "昵称", requiredMode = Schema.RequiredMode.REQUIRED) 48 | @Size(max = 100, message = "【昵称】最长100位") 49 | @NotBlank(message = "昵称不能为空") 50 | private String nickname; 51 | 52 | @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED) 53 | @NotNull(message = "状态不能为空") 54 | private SysUserStatusEnum status; 55 | 56 | @Schema(description = "性别", requiredMode = Schema.RequiredMode.REQUIRED) 57 | @NotNull(message = "性别不能为空") 58 | private GenderEnum gender; 59 | 60 | @Schema(description = "邮箱", requiredMode = Schema.RequiredMode.REQUIRED) 61 | @Pattern(message = "邮箱格式有误", regexp = HelioConstant.Regex.EMAIL) 62 | @Size(max = 255, message = "【邮箱】最长255位") 63 | @NotBlank(message = "邮箱不能为空") 64 | private String email; 65 | 66 | @Schema(description = "手机号", requiredMode = Schema.RequiredMode.REQUIRED) 67 | @Pattern(message = "手机号格式有误", regexp = HelioConstant.Regex.CHINA_MAINLAND_PHONE_NO) 68 | @Size(max = 20, message = "【手机号】最长20位") 69 | @NotBlank(message = "手机号不能为空") 70 | private String phoneNo; 71 | 72 | @Schema(description = "所属部门ID") 73 | private Long deptId; 74 | 75 | 76 | public void validate() { 77 | boolean isUpdate = Objects.nonNull(id); 78 | if (!isUpdate) { 79 | // 新增 80 | int passwordOfNewUserLen = CharSequenceUtil.length(passwordOfNewUser); 81 | if (passwordOfNewUserLen < 8 || passwordOfNewUserLen > 20) { 82 | throw new BusinessException("【密码】最短8位,最长20位"); 83 | } 84 | } 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/service/SysUserDeptRelationService.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.service; 2 | 3 | import cc.uncarbon.framework.core.constant.HelioConstant; 4 | import cc.uncarbon.module.sys.entity.SysUserDeptRelationEntity; 5 | import cc.uncarbon.module.sys.mapper.SysUserDeptRelationMapper; 6 | import cn.hutool.core.collection.CollUtil; 7 | import cn.hutool.core.util.ObjectUtil; 8 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 9 | import lombok.RequiredArgsConstructor; 10 | import lombok.extern.slf4j.Slf4j; 11 | import org.springframework.stereotype.Service; 12 | import org.springframework.transaction.annotation.Transactional; 13 | 14 | import java.util.Collection; 15 | import java.util.Collections; 16 | import java.util.List; 17 | import java.util.Set; 18 | import java.util.stream.Collectors; 19 | 20 | 21 | /** 22 | * 后台用户-部门关联 23 | */ 24 | @RequiredArgsConstructor 25 | @Service 26 | @Slf4j 27 | public class SysUserDeptRelationService { 28 | 29 | private final SysUserDeptRelationMapper sysUserDeptRelationMapper; 30 | 31 | 32 | /** 33 | * 列举用户ID关联的部门IDs 34 | * 35 | * @return 关联的部门IDs;目前最多只有1个元素 36 | */ 37 | public List getUserDeptIds(Long userId) { 38 | SysUserDeptRelationEntity entity = sysUserDeptRelationMapper.selectOne( 39 | new QueryWrapper() 40 | .lambda() 41 | .eq(SysUserDeptRelationEntity::getUserId, userId) 42 | .last(HelioConstant.CRUD.SQL_LIMIT_1) 43 | ); 44 | 45 | if (entity == null) { 46 | return Collections.emptyList(); 47 | } 48 | return Collections.singletonList(entity.getDeptId()); 49 | } 50 | 51 | /** 52 | * 先清理用户ID所有关联关系, 再绑定用户ID与部门ID 53 | */ 54 | @Transactional(rollbackFor = Exception.class) 55 | public void cleanAndBind(Long userId, Long deptId) { 56 | sysUserDeptRelationMapper.delete( 57 | new QueryWrapper() 58 | .lambda() 59 | .eq(SysUserDeptRelationEntity::getUserId, userId) 60 | ); 61 | 62 | if (ObjectUtil.isNotNull(deptId)) { 63 | // 需要绑定部门 64 | sysUserDeptRelationMapper.insert( 65 | SysUserDeptRelationEntity.builder() 66 | .userId(userId) 67 | .deptId(deptId) 68 | .build() 69 | ); 70 | } 71 | 72 | } 73 | 74 | /** 75 | * 列举部门IDs关联的用户IDs 76 | */ 77 | public Set listUserIdsByDeptIds(Collection deptIds) { 78 | if (CollUtil.isEmpty(deptIds)) { 79 | return Collections.emptySet(); 80 | } 81 | 82 | return sysUserDeptRelationMapper.selectList( 83 | new QueryWrapper() 84 | .lambda() 85 | // 只要用户ID 86 | .select(SysUserDeptRelationEntity::getUserId) 87 | .in(SysUserDeptRelationEntity::getDeptId, deptIds) 88 | ).stream().map(SysUserDeptRelationEntity::getUserId).collect(Collectors.toSet()); 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/extension/impl/DefaultSysLogAspectExtension.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.extension.impl; 2 | 3 | import cc.uncarbon.module.sys.extension.SysLogAspectExtension; 4 | import cc.uncarbon.module.sys.model.response.IPLocationBO; 5 | import cn.hutool.core.net.NetUtil; 6 | import cn.hutool.core.text.CharSequenceUtil; 7 | import cn.hutool.core.text.StrPool; 8 | import cn.hutool.core.util.CharsetUtil; 9 | import cn.hutool.http.Header; 10 | import cn.hutool.http.HttpRequest; 11 | import cn.hutool.http.HttpResponse; 12 | import cn.hutool.json.JSONObject; 13 | import cn.hutool.json.JSONUtil; 14 | import lombok.extern.slf4j.Slf4j; 15 | 16 | @Slf4j 17 | public class DefaultSysLogAspectExtension implements SysLogAspectExtension { 18 | 19 | @Override 20 | public IPLocationBO queryIPLocation(String ip) { 21 | if (CharSequenceUtil.contains(ip, StrPool.COLON)) { 22 | // 仅根据冒号简易判断;暂不支持IPv6地址 23 | return IPLocationBO.unknown(); 24 | } 25 | 26 | if (NetUtil.isInnerIP(ip)) { 27 | return IPLocationBO.intranet(); 28 | } 29 | 30 | // 该API主要支持中国内地 31 | HttpRequest httpRequest = HttpRequest.get("http://whois.pconline.com.cn/ipJson.jsp") 32 | .form("ip", ip) 33 | .form("json", Boolean.TRUE.toString()) 34 | .charset(CharsetUtil.CHARSET_GBK) 35 | // since 1.11.0,加个UA避免被当成恶意请求,造成查IP失败 36 | .header(Header.USER_AGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36") 37 | .timeout(5000); 38 | try (HttpResponse httpResponse = httpRequest.execute()) { 39 | String repStr = httpResponse.body(); 40 | if (JSONUtil.isTypeJSONObject(repStr)) { 41 | /* 42 | 返回文本是JSON对象 43 | example1: 44 | {"ip":"39.156.66.10","pro":"北京市","proCode":"110000","city":"北京市","cityCode":"110000","region":"","regionCode":"0","addr":"北京市 移通","regionNames":"","err":""} 45 | 46 | example2: 47 | {"ip":"1.1.1.1","pro":"","proCode":"999999","city":"","cityCode":"0","region":"","regionCode":"0","addr":" 美国APNIC&CloudFlare公共DNS服务器","regionNames":"","err":"noprovince"} 48 | 49 | example3: 50 | {"ip":"8.8.8.8","pro":"","proCode":"999999","city":"","cityCode":"0","region":"","regionCode":"0","addr":" 美国","regionNames":"","err":"noprovince"} 51 | */ 52 | JSONObject repJson = JSONUtil.parseObj(repStr); 53 | String pro = repJson.getStr("pro"); 54 | String err = repJson.getStr("err"); 55 | if (CharSequenceUtil.isEmpty(pro) || "noprovince".equals(err)) { 56 | // 可能是非中国内地IP 57 | String regionName = repJson.getStr("addr"); 58 | return IPLocationBO.unknown().setRegionName(regionName); 59 | } 60 | return IPLocationBO.inChina() 61 | .setProvinceName(pro) 62 | .setCityName(repJson.getStr("city")); 63 | } 64 | } catch (Exception e) { 65 | log.error("[SysLog切面][查询IP地址属地异常] >> ip={} \n", ip, e); 66 | } 67 | 68 | return IPLocationBO.unknown(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: helio-boot 4 | profiles: 5 | # 可通过命令行参数实现多环境切换配置文件 6 | active: dev 7 | datasource: 8 | driver-class-name: ${helio.crud.db-driver-class} 9 | username: ${helio.crud.db-username} 10 | password: ${helio.crud.db-password} 11 | url: jdbc:${helio.crud.db-type}://${helio.crud.db-host}:${helio.crud.db-port}/${helio.crud.db-name}?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai 12 | cache: 13 | type: redis 14 | redis: 15 | cache-null-values: true 16 | messages: 17 | # 国际化资源目录,一般不做修改 18 | basename: i18n/messages 19 | task: 20 | # spring-schedule 线程池容量 21 | scheduling: 22 | pool: 23 | size: 8 24 | # spring-async 线程池容量 25 | execution: 26 | pool: 27 | core-size: 8 28 | servlet: 29 | multipart: 30 | # 文件上传大小限制(两个一起配) 31 | # 如果还用了 Nginx 等反向代理,需要在反向代理的配置中也放宽请求体大小限制 32 | max-file-size: 1024MB 33 | max-request-size: 1024MB 34 | # 不为工程中的资源文件建立映射 35 | web: 36 | resources: 37 | add-mappings: false 38 | 39 | helio: 40 | crud: 41 | id-generator: 42 | # 使用雪花算法作为主键ID生成策略 43 | strategy: SNOWFLAKE 44 | # 雪花ID-数据中心ID 45 | datacenter-id: ${DATACENTER_ID:0} 46 | # 雪花ID-起始日期-可以选用项目开工的日期-这个一旦确定就不要改动了 47 | epoch-date: '2021-07-17' 48 | optimistic-lock: 49 | # 是否开启乐观锁 50 | enabled: true 51 | security: 52 | exclude-routes: 53 | # 放行路由, 不进行登录校验 54 | # 图标 55 | - /favicon.ico 56 | # SpringDoc 接口 57 | - /v3/api-docs 58 | # SpringMVC 404 59 | - /error 60 | # C端登录接口 61 | - /app/api/v1/auth/login 62 | tenant: 63 | # 是否开启多租户,详情请见文档【进阶使用-多租户】 64 | enabled: false 65 | # 多租户隔离级别 66 | isolate-level: LINE 67 | ignored-tables: 68 | # 以下表不参与【行级】多租户 SQL 拦截器条件拼接 69 | - sys_menu 70 | - sys_tenant 71 | - sys_role_menu_relation 72 | - sys_user_dept_relation 73 | - sys_user_role_relation 74 | i18n: 75 | # 是否对返回消息启用国际化翻译 76 | enabled: false 77 | 78 | knife4j: 79 | # 保持true 80 | enable: true 81 | basic: 82 | enable: true 83 | username: helio 84 | password: helio 85 | springdoc: 86 | # 平铺显示复合入参对象 87 | default-flat-param-object: true 88 | 89 | server: 90 | port: 7003 91 | 92 | mybatis-plus: 93 | # XML扫描,支持统配符 * 或者 , 分割 94 | mapper-locations: classpath*:/mapper/**/*.xml, classpath*:/**/mapper/xml/*.xml 95 | global-config: 96 | # 启动时不打印字符画 97 | banner: false 98 | 99 | logging: 100 | level: 101 | # 指定日志级别,开发、测试环境建议为 DEBUG,生产环境建议为 INFO 102 | cc.uncarbon.framework: DEBUG 103 | cc.uncarbon.module: DEBUG 104 | # 单元测试日志级别 105 | cc.uncarbon.test: INFO 106 | 107 | # 详细配置文档:https://sa-token.cc/doc.html#/use/config 108 | sa-token: 109 | # token名称 110 | token-name: Authorization 111 | # token风格 112 | token-style: tik 113 | # 是否允许同一账号并发登录 114 | is-concurrent: false 115 | # 打印banner 116 | is-print: false 117 | # 是否尝试从请求体里读取token 118 | is-read-body: false 119 | # 是否尝试从cookie里读取token 120 | is-read-cookie: false 121 | # 有效期解释:https://sa-token.cc/doc.html#/fun/token-timeout 122 | # token 有效期,单位=秒;默认30天,-1代表永不过期 123 | timeout: 2592000 124 | # token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结 125 | active-timeout: -1 126 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/adminapi/web/sys/AdminSysDeptController.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.adminapi.web.sys; 2 | 3 | 4 | import cc.uncarbon.framework.core.constant.HelioConstant; 5 | import cc.uncarbon.framework.web.model.request.IdsDTO; 6 | import cc.uncarbon.framework.web.model.response.ApiResult; 7 | import cc.uncarbon.module.adminapi.constant.AdminApiConstant; 8 | import cc.uncarbon.module.sys.annotation.SysLog; 9 | import cc.uncarbon.module.sys.model.request.AdminInsertOrUpdateSysDeptDTO; 10 | import cc.uncarbon.module.sys.model.response.SysDeptBO; 11 | import cc.uncarbon.module.sys.service.SysDeptService; 12 | import cc.uncarbon.module.adminapi.util.AdminStpUtil; 13 | import cn.dev33.satoken.annotation.SaCheckLogin; 14 | import cn.dev33.satoken.annotation.SaCheckPermission; 15 | import io.swagger.v3.oas.annotations.tags.Tag; 16 | import io.swagger.v3.oas.annotations.Operation; 17 | import lombok.RequiredArgsConstructor; 18 | import lombok.extern.slf4j.Slf4j; 19 | import org.springframework.web.bind.annotation.*; 20 | 21 | import jakarta.validation.Valid; 22 | import java.util.List; 23 | 24 | 25 | @SaCheckLogin(type = AdminStpUtil.TYPE) 26 | @Tag(name = "部门管理接口") 27 | @RequestMapping(value = AdminApiConstant.HTTP_API_URL_PREFIX + "/api/v1") 28 | @RequiredArgsConstructor 29 | @RestController 30 | @Slf4j 31 | public class AdminSysDeptController { 32 | 33 | private static final String PERMISSION_PREFIX = "SysDept:"; 34 | 35 | private final SysDeptService sysDeptService; 36 | 37 | 38 | @SaCheckPermission(type = AdminStpUtil.TYPE, value = PERMISSION_PREFIX + HelioConstant.Permission.RETRIEVE) 39 | @Operation(summary = "列表") 40 | @GetMapping(value = "/sys/depts") 41 | public ApiResult> list() { 42 | return ApiResult.data(sysDeptService.adminList()); 43 | } 44 | 45 | @SaCheckPermission(type = AdminStpUtil.TYPE, value = PERMISSION_PREFIX + HelioConstant.Permission.RETRIEVE) 46 | @Operation(summary = "详情") 47 | @GetMapping(value = "/sys/depts/{id}") 48 | public ApiResult getById(@PathVariable Long id) { 49 | return ApiResult.data(sysDeptService.getOneById(id, true)); 50 | } 51 | 52 | @SysLog(value = "新增部门") 53 | @SaCheckPermission(type = AdminStpUtil.TYPE, value = PERMISSION_PREFIX + HelioConstant.Permission.CREATE) 54 | @Operation(summary = "新增") 55 | @PostMapping(value = "/sys/depts") 56 | public ApiResult insert(@RequestBody @Valid AdminInsertOrUpdateSysDeptDTO dto) { 57 | sysDeptService.adminInsert(dto); 58 | 59 | return ApiResult.success(); 60 | } 61 | 62 | @SysLog(value = "编辑部门") 63 | @SaCheckPermission(type = AdminStpUtil.TYPE, value = PERMISSION_PREFIX + HelioConstant.Permission.UPDATE) 64 | @Operation(summary = "编辑") 65 | @PutMapping(value = "/sys/depts/{id}") 66 | public ApiResult update(@PathVariable Long id, @RequestBody @Valid AdminInsertOrUpdateSysDeptDTO dto) { 67 | dto.setId(id); 68 | sysDeptService.adminUpdate(dto); 69 | 70 | return ApiResult.success(); 71 | } 72 | 73 | @SysLog(value = "删除部门") 74 | @SaCheckPermission(type = AdminStpUtil.TYPE, value = PERMISSION_PREFIX + HelioConstant.Permission.DELETE) 75 | @Operation(summary = "删除") 76 | @DeleteMapping(value = "/sys/depts") 77 | public ApiResult delete(@RequestBody @Valid IdsDTO dto) { 78 | sysDeptService.adminDelete(dto.getIds()); 79 | 80 | return ApiResult.success(); 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/helper/CaptchaHelper.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.helper; 2 | 3 | import cc.uncarbon.framework.core.exception.BusinessException; 4 | import cc.uncarbon.module.adminapi.enums.AdminApiErrorEnum; 5 | import cc.uncarbon.module.adminapi.model.interior.AdminCaptchaContainer; 6 | import cn.hutool.captcha.CaptchaUtil; 7 | import cn.hutool.captcha.ShearCaptcha; 8 | import cn.hutool.core.date.LocalDateTimeUtil; 9 | import cn.hutool.core.lang.UUID; 10 | import cn.hutool.core.text.CharSequenceUtil; 11 | import lombok.RequiredArgsConstructor; 12 | import org.springframework.data.redis.core.RedisTemplate; 13 | import org.springframework.stereotype.Component; 14 | 15 | import java.time.LocalDateTime; 16 | import java.time.temporal.ChronoUnit; 17 | import java.util.concurrent.TimeUnit; 18 | 19 | /** 20 | * 验证码助手类;可将验证码答案缓存至 Redis 21 | * 22 | * @author Uncarbon 23 | */ 24 | @Component 25 | @RequiredArgsConstructor 26 | public class CaptchaHelper { 27 | 28 | private final RedisTemplate stringRedisTemplate; 29 | 30 | private static final String CACHE_KEY_CAPTCHA_ANSWER = "Authorization:captcha:uuid_%s"; 31 | 32 | /** 33 | * 验证码答案长度 34 | */ 35 | private static final int CAPTCHA_ANSWER_LENGTH = 4; 36 | 37 | /** 38 | * 生成一个验证码 39 | */ 40 | public AdminCaptchaContainer generate() { 41 | // redis预占位;随机10个UUID,应该有个能成的吧…… 42 | UUID uuid = UUID.randomUUID(); 43 | String captchaCacheKey = null; 44 | Boolean stubFlag = Boolean.FALSE; 45 | for (int count = 0; count < 10; count++) { 46 | captchaCacheKey = String.format(CACHE_KEY_CAPTCHA_ANSWER, uuid.toString(true)); 47 | stubFlag = stringRedisTemplate.opsForValue().setIfAbsent(captchaCacheKey, CharSequenceUtil.EMPTY); 48 | if (Boolean.TRUE.equals(stubFlag)) { 49 | break; 50 | } 51 | uuid = UUID.randomUUID(); 52 | } 53 | if (!Boolean.TRUE.equals(stubFlag)) { 54 | throw new BusinessException(AdminApiErrorEnum.CAPTCHA_GENERATE_FAILED); 55 | } 56 | 57 | // 定义图形验证码的长、宽、验证码字符数、干扰线宽度 58 | ShearCaptcha captcha = CaptchaUtil.createShearCaptcha(196, 50, CAPTCHA_ANSWER_LENGTH, 4); 59 | 60 | // 将验证码答案保存至 redis, 有效期5分钟 61 | stringRedisTemplate.opsForValue().set(captchaCacheKey, captcha.getCode(), 300, TimeUnit.SECONDS); 62 | LocalDateTime expiredAt = LocalDateTimeUtil.offset(LocalDateTimeUtil.now(), 300, ChronoUnit.SECONDS); 63 | 64 | return new AdminCaptchaContainer(captcha, uuid.toString(true), expiredAt); 65 | } 66 | 67 | /** 68 | * 核验验证码是否输入正确 69 | * 70 | * @param uuid 验证码唯一标识(UUID) 71 | * @param captchaAnswer 验证码答案 72 | * @return 是否正确 73 | */ 74 | public boolean validate(String uuid, String captchaAnswer) { 75 | if (CharSequenceUtil.hasBlank(uuid, captchaAnswer)) { 76 | return false; 77 | } 78 | 79 | String cacheKey = String.format(CACHE_KEY_CAPTCHA_ANSWER, uuid); 80 | boolean equals; 81 | try { 82 | if (CharSequenceUtil.length(captchaAnswer) != CAPTCHA_ANSWER_LENGTH) { 83 | // 长度不同 84 | return false; 85 | } 86 | 87 | String answerInRedis = stringRedisTemplate.opsForValue().get(cacheKey); 88 | equals = CharSequenceUtil.equalsIgnoreCase(answerInRedis, captchaAnswer); 89 | } finally { 90 | stringRedisTemplate.delete(cacheKey); 91 | } 92 | return equals; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/oss/util/UploadFileChecker.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.oss.util; 2 | 3 | import cc.uncarbon.module.oss.enums.UploadFileCheckResultEnum; 4 | import cn.hutool.core.collection.CollUtil; 5 | import cn.hutool.core.io.file.FileNameUtil; 6 | import cn.hutool.core.text.CharSequenceUtil; 7 | import cn.hutool.core.util.ArrayUtil; 8 | import jakarta.annotation.Nonnull; 9 | import lombok.experimental.UtilityClass; 10 | import org.springframework.web.multipart.MultipartFile; 11 | 12 | import java.util.Collection; 13 | import java.util.Locale; 14 | import java.util.Objects; 15 | 16 | /** 17 | * 前端上传文件检查器 18 | * 该检查器较为简易,请按照实际网络安全需要扩展 19 | */ 20 | @UtilityClass 21 | public class UploadFileChecker { 22 | 23 | // 最小文件数量 24 | private static final int FILE_QTY_MIN = 1; 25 | // 最小文件尺寸 26 | private static final int FILE_SIZE_MIN = 1; 27 | 28 | // 文件尺寸 1MB 29 | public static final long FILE_SIZE_1MB = 1024 * 1024L; 30 | 31 | 32 | /** 33 | * 批量检查 34 | * 35 | * @param fileQtyMax 最大允许的文件数量 36 | * @param singleFileSizeMax 最大单文件尺寸,单位=KB 37 | * @param allFileSizeMax 最大多文件累计尺寸,单位=KB 38 | * @param allowedSuffixes 允许上传的文件后缀 39 | */ 40 | public UploadFileCheckResultEnum check(Collection multipartFiles, int fileQtyMax, 41 | long singleFileSizeMax, long allFileSizeMax, String[] allowedSuffixes) { 42 | // 限制文件数量 43 | int fileQty = CollUtil.size(multipartFiles); 44 | if (fileQty < FILE_QTY_MIN) { 45 | return UploadFileCheckResultEnum.NO_FILE; 46 | } else if (fileQty > fileQtyMax) { 47 | return UploadFileCheckResultEnum.TOO_MANY_FILES; 48 | } 49 | // 限制所有文件总大小 50 | long allFileSize = multipartFiles.stream().mapToLong(MultipartFile::getSize).sum(); 51 | // 除以1024 52 | allFileSize = allFileSize >> 10; 53 | if (allFileSize > allFileSizeMax) { 54 | return UploadFileCheckResultEnum.TOO_LARGE_FILE_SIZE; 55 | } 56 | 57 | // 每个文件单独检查 58 | for (MultipartFile item : multipartFiles) { 59 | UploadFileCheckResultEnum singleCheckRet = check(item, singleFileSizeMax, allowedSuffixes); 60 | if (singleCheckRet.isNotOK()) { 61 | return singleCheckRet; 62 | } 63 | } 64 | return UploadFileCheckResultEnum.OK; 65 | } 66 | 67 | /** 68 | * 单个检查 69 | * @param singleFileSizeMax 最大单文件尺寸,单位=KB 70 | * @param allowedSuffixes 允许上传的文件后缀 71 | */ 72 | public UploadFileCheckResultEnum check(@Nonnull MultipartFile multipartFile, long singleFileSizeMax, String[] allowedSuffixes) { 73 | long fileSize = multipartFile.getSize(); 74 | 75 | // 确定文件大小区间 76 | if (fileSize < FILE_SIZE_MIN) { 77 | return UploadFileCheckResultEnum.EMPTY_FILE; 78 | } 79 | // 除以1024 80 | fileSize = fileSize >> 10; 81 | if (fileSize > singleFileSizeMax) { 82 | return UploadFileCheckResultEnum.TOO_LARGE_FILE_SIZE; 83 | } 84 | 85 | // 确定后缀名 86 | String suffix = FileNameUtil.getSuffix(multipartFile.getOriginalFilename()); 87 | if (CharSequenceUtil.isNotBlank(suffix)) { 88 | suffix = suffix.toLowerCase(Locale.ROOT); 89 | } 90 | // 检查后缀名 91 | if (Objects.isNull(suffix) || !ArrayUtil.contains(allowedSuffixes, suffix)) { 92 | return UploadFileCheckResultEnum.ILLEGAL_FILE_SUFFIX; 93 | } 94 | return UploadFileCheckResultEnum.OK; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /attachments/db/PostgreSQL/upgrade/2.0.0_to_2.1.0.sql: -------------------------------------------------------------------------------- 1 | -- 重构数据字典,拆分为`sys_data_dict_classified`(数据字典分类)和`sys_data_dict_item`(数据字典项),并重新设计字段 2 | CREATE TABLE "sys_data_dict_classified" ( 3 | "id" int8 NOT NULL, 4 | "tenant_id" int8, 5 | "revision" int8 NOT NULL DEFAULT 1, 6 | "del_flag" int2 NOT NULL DEFAULT 0, 7 | "created_at" timestamp(6) NOT NULL, 8 | "created_by" varchar(255) COLLATE "pg_catalog"."default", 9 | "updated_at" timestamp(6) NOT NULL, 10 | "updated_by" varchar(255) COLLATE "pg_catalog"."default", 11 | "code" varchar(255) COLLATE "pg_catalog"."default" NOT NULL, 12 | "name" varchar(255) COLLATE "pg_catalog"."default" NOT NULL, 13 | "status" int2 NOT NULL DEFAULT 1, 14 | "description" varchar(255) COLLATE "pg_catalog"."default" NOT NULL DEFAULT ''::character varying 15 | ); 16 | COMMENT ON COLUMN "sys_data_dict_classified"."id" IS '主键ID'; 17 | COMMENT ON COLUMN "sys_data_dict_classified"."tenant_id" IS '租户ID'; 18 | COMMENT ON COLUMN "sys_data_dict_classified"."revision" IS '乐观锁'; 19 | COMMENT ON COLUMN "sys_data_dict_classified"."del_flag" IS '逻辑删除标识'; 20 | COMMENT ON COLUMN "sys_data_dict_classified"."created_at" IS '创建时刻'; 21 | COMMENT ON COLUMN "sys_data_dict_classified"."created_by" IS '创建者'; 22 | COMMENT ON COLUMN "sys_data_dict_classified"."updated_at" IS '更新时刻'; 23 | COMMENT ON COLUMN "sys_data_dict_classified"."updated_by" IS '更新者'; 24 | COMMENT ON COLUMN "sys_data_dict_classified"."code" IS '分类编码'; 25 | COMMENT ON COLUMN "sys_data_dict_classified"."name" IS '分类名称'; 26 | COMMENT ON COLUMN "sys_data_dict_classified"."status" IS '状态'; 27 | COMMENT ON COLUMN "sys_data_dict_classified"."description" IS '分类描述'; 28 | COMMENT ON TABLE "sys_data_dict_classified" IS '数据字典分类'; 29 | ALTER TABLE "sys_data_dict_classified" ADD PRIMARY KEY ("id"); 30 | 31 | CREATE TABLE "sys_data_dict_item" ( 32 | "id" int8 NOT NULL, 33 | "tenant_id" int8, 34 | "revision" int8 NOT NULL DEFAULT 1, 35 | "del_flag" int2 NOT NULL DEFAULT 0, 36 | "created_at" timestamp(6) NOT NULL, 37 | "created_by" varchar(255) COLLATE "pg_catalog"."default", 38 | "updated_at" timestamp(6) NOT NULL, 39 | "updated_by" varchar(255) COLLATE "pg_catalog"."default", 40 | "classified_id" int8 NOT NULL, 41 | "code" varchar(255) COLLATE "pg_catalog"."default" NOT NULL, 42 | "label" varchar(255) COLLATE "pg_catalog"."default" NOT NULL, 43 | "value" varchar(4096) COLLATE "pg_catalog"."default" NOT NULL, 44 | "status" int2 NOT NULL DEFAULT 1, 45 | "sort" int4 NOT NULL DEFAULT 1, 46 | "description" varchar(255) COLLATE "pg_catalog"."default" NOT NULL DEFAULT ''::character varying 47 | ); 48 | COMMENT ON COLUMN "sys_data_dict_item"."id" IS '主键ID'; 49 | COMMENT ON COLUMN "sys_data_dict_item"."tenant_id" IS '租户ID'; 50 | COMMENT ON COLUMN "sys_data_dict_item"."revision" IS '乐观锁'; 51 | COMMENT ON COLUMN "sys_data_dict_item"."del_flag" IS '逻辑删除标识'; 52 | COMMENT ON COLUMN "sys_data_dict_item"."created_at" IS '创建时刻'; 53 | COMMENT ON COLUMN "sys_data_dict_item"."created_by" IS '创建者'; 54 | COMMENT ON COLUMN "sys_data_dict_item"."updated_at" IS '更新时刻'; 55 | COMMENT ON COLUMN "sys_data_dict_item"."updated_by" IS '更新者'; 56 | COMMENT ON COLUMN "sys_data_dict_item"."classified_id" IS '所属分类ID'; 57 | COMMENT ON COLUMN "sys_data_dict_item"."code" IS '字典项编码'; 58 | COMMENT ON COLUMN "sys_data_dict_item"."label" IS '字典项标签'; 59 | COMMENT ON COLUMN "sys_data_dict_item"."value" IS '字典项值'; 60 | COMMENT ON COLUMN "sys_data_dict_item"."status" IS '状态'; 61 | COMMENT ON COLUMN "sys_data_dict_item"."sort" IS '排序'; 62 | COMMENT ON COLUMN "sys_data_dict_item"."description" IS '描述'; 63 | COMMENT ON TABLE "sys_data_dict_item" IS '数据字典项'; 64 | ALTER TABLE "sys_data_dict_item" ADD PRIMARY KEY ("id"); 65 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/adminapi/web/sys/AdminSysParamController.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.adminapi.web.sys; 2 | 3 | import cc.uncarbon.framework.core.constant.HelioConstant; 4 | import cc.uncarbon.framework.core.page.PageParam; 5 | import cc.uncarbon.framework.core.page.PageResult; 6 | import cc.uncarbon.framework.web.model.request.IdsDTO; 7 | import cc.uncarbon.framework.web.model.response.ApiResult; 8 | import cc.uncarbon.module.adminapi.constant.AdminApiConstant; 9 | import cc.uncarbon.module.sys.annotation.SysLog; 10 | import cc.uncarbon.module.sys.model.request.AdminInsertOrUpdateSysParamDTO; 11 | import cc.uncarbon.module.sys.model.request.AdminListSysParamDTO; 12 | import cc.uncarbon.module.sys.model.response.SysParamBO; 13 | import cc.uncarbon.module.sys.service.SysParamService; 14 | import cc.uncarbon.module.adminapi.util.AdminStpUtil; 15 | import cn.dev33.satoken.annotation.SaCheckLogin; 16 | import cn.dev33.satoken.annotation.SaCheckPermission; 17 | import io.swagger.v3.oas.annotations.tags.Tag; 18 | import io.swagger.v3.oas.annotations.Operation; 19 | import lombok.RequiredArgsConstructor; 20 | import lombok.extern.slf4j.Slf4j; 21 | import org.springframework.web.bind.annotation.*; 22 | 23 | import jakarta.validation.Valid; 24 | 25 | 26 | @SaCheckLogin(type = AdminStpUtil.TYPE) 27 | @Tag(name = "系统参数管理接口") 28 | @RequestMapping(value = AdminApiConstant.HTTP_API_URL_PREFIX + "/api/v1") 29 | @RequiredArgsConstructor 30 | @RestController 31 | @Slf4j 32 | public class AdminSysParamController { 33 | 34 | private static final String PERMISSION_PREFIX = "SysParam:"; 35 | 36 | private final SysParamService sysParamService; 37 | 38 | 39 | @SaCheckPermission(type = AdminStpUtil.TYPE, value = PERMISSION_PREFIX + HelioConstant.Permission.RETRIEVE) 40 | @Operation(summary = "分页列表") 41 | @GetMapping(value = "/sys/params") 42 | public ApiResult> list(PageParam pageParam, AdminListSysParamDTO dto) { 43 | return ApiResult.data(sysParamService.adminList(pageParam, dto)); 44 | } 45 | 46 | @SaCheckPermission(type = AdminStpUtil.TYPE, value = PERMISSION_PREFIX + HelioConstant.Permission.RETRIEVE) 47 | @Operation(summary = "详情") 48 | @GetMapping(value = "/sys/params/{id}") 49 | public ApiResult getById(@PathVariable Long id) { 50 | return ApiResult.data(sysParamService.getOneById(id, true)); 51 | } 52 | 53 | @SysLog(value = "新增系统参数") 54 | @SaCheckPermission(type = AdminStpUtil.TYPE, value = PERMISSION_PREFIX + HelioConstant.Permission.CREATE) 55 | @Operation(summary = "新增") 56 | @PostMapping(value = "/sys/params") 57 | public ApiResult insert(@RequestBody @Valid AdminInsertOrUpdateSysParamDTO dto) { 58 | sysParamService.adminInsert(dto); 59 | 60 | return ApiResult.success(); 61 | } 62 | 63 | @SysLog(value = "编辑系统参数") 64 | @SaCheckPermission(type = AdminStpUtil.TYPE, value = PERMISSION_PREFIX + HelioConstant.Permission.UPDATE) 65 | @Operation(summary = "编辑") 66 | @PutMapping(value = "/sys/params/{id}") 67 | public ApiResult update(@PathVariable Long id, @RequestBody @Valid AdminInsertOrUpdateSysParamDTO dto) { 68 | dto.setId(id); 69 | sysParamService.adminUpdate(dto); 70 | 71 | return ApiResult.success(); 72 | } 73 | 74 | @SysLog(value = "删除系统参数") 75 | @SaCheckPermission(type = AdminStpUtil.TYPE, value = PERMISSION_PREFIX + HelioConstant.Permission.DELETE) 76 | @Operation(summary = "删除") 77 | @DeleteMapping(value = "/sys/params") 78 | public ApiResult delete(@RequestBody @Valid IdsDTO dto) { 79 | sysParamService.adminDelete(dto.getIds()); 80 | 81 | return ApiResult.success(); 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/adminapi/web/sys/AdminSysMenuController.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.adminapi.web.sys; 2 | 3 | import cc.uncarbon.framework.core.constant.HelioConstant; 4 | import cc.uncarbon.framework.web.model.request.IdsDTO; 5 | import cc.uncarbon.framework.web.model.response.ApiResult; 6 | import cc.uncarbon.module.adminapi.constant.AdminApiConstant; 7 | import cc.uncarbon.module.sys.annotation.SysLog; 8 | import cc.uncarbon.module.sys.model.request.AdminInsertOrUpdateSysMenuDTO; 9 | import cc.uncarbon.module.sys.model.response.SysMenuBO; 10 | import cc.uncarbon.module.sys.service.SysMenuService; 11 | import cc.uncarbon.module.adminapi.util.AdminStpUtil; 12 | import cn.dev33.satoken.annotation.SaCheckLogin; 13 | import cn.dev33.satoken.annotation.SaCheckPermission; 14 | import io.swagger.v3.oas.annotations.tags.Tag; 15 | import io.swagger.v3.oas.annotations.Operation; 16 | import lombok.RequiredArgsConstructor; 17 | import lombok.extern.slf4j.Slf4j; 18 | import org.springframework.web.bind.annotation.*; 19 | 20 | import jakarta.validation.Valid; 21 | import java.util.List; 22 | 23 | 24 | @SaCheckLogin(type = AdminStpUtil.TYPE) 25 | @Tag(name = "后台菜单管理接口") 26 | @RequestMapping(value = AdminApiConstant.HTTP_API_URL_PREFIX + "/api/v1") 27 | @RequiredArgsConstructor 28 | @RestController 29 | @Slf4j 30 | public class AdminSysMenuController { 31 | 32 | private static final String PERMISSION_PREFIX = "SysMenu:"; 33 | 34 | private final SysMenuService sysMenuService; 35 | 36 | 37 | @SaCheckPermission(type = AdminStpUtil.TYPE, value = PERMISSION_PREFIX + HelioConstant.Permission.RETRIEVE) 38 | @Operation(summary = "列表") 39 | @GetMapping(value = "/sys/menus") 40 | public ApiResult> list() { 41 | return ApiResult.data(sysMenuService.adminList()); 42 | } 43 | 44 | @SaCheckPermission(type = AdminStpUtil.TYPE, value = PERMISSION_PREFIX + HelioConstant.Permission.RETRIEVE) 45 | @Operation(summary = "详情") 46 | @GetMapping(value = "/sys/menus/{id}") 47 | public ApiResult getById(@PathVariable Long id) { 48 | return ApiResult.data(sysMenuService.getOneById(id, true)); 49 | } 50 | 51 | @SysLog(value = "新增后台菜单") 52 | @SaCheckPermission(type = AdminStpUtil.TYPE, value = PERMISSION_PREFIX + HelioConstant.Permission.CREATE) 53 | @Operation(summary = "新增") 54 | @PostMapping(value = "/sys/menus") 55 | public ApiResult insert(@RequestBody @Valid AdminInsertOrUpdateSysMenuDTO dto) { 56 | sysMenuService.adminInsert(dto); 57 | 58 | return ApiResult.success(); 59 | } 60 | 61 | @SysLog(value = "编辑后台菜单") 62 | @SaCheckPermission(type = AdminStpUtil.TYPE, value = PERMISSION_PREFIX + HelioConstant.Permission.UPDATE) 63 | @Operation(summary = "编辑") 64 | @PutMapping(value = "/sys/menus/{id}") 65 | public ApiResult update(@PathVariable Long id, @RequestBody @Valid AdminInsertOrUpdateSysMenuDTO dto) { 66 | dto.setId(id); 67 | sysMenuService.adminUpdate(dto); 68 | 69 | return ApiResult.success(); 70 | } 71 | 72 | @SysLog(value = "删除后台菜单") 73 | @SaCheckPermission(type = AdminStpUtil.TYPE, value = PERMISSION_PREFIX + HelioConstant.Permission.DELETE) 74 | @Operation(summary = "删除") 75 | @DeleteMapping(value = "/sys/menus") 76 | public ApiResult delete(@RequestBody @Valid IdsDTO dto) { 77 | sysMenuService.adminDelete(dto.getIds()); 78 | 79 | return ApiResult.success(); 80 | } 81 | 82 | @Operation(summary = "取侧边菜单") 83 | @GetMapping("/sys/menus/side") 84 | public ApiResult> adminListSideMenu() { 85 | return ApiResult.data(sysMenuService.adminListSideMenu()); 86 | } 87 | 88 | @Operation(summary = "取所有可见菜单") 89 | @GetMapping("/sys/menus/all") 90 | public ApiResult> adminListVisibleMenu() { 91 | return ApiResult.data(sysMenuService.adminListVisibleMenu()); 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/service/SysUserRoleRelationService.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.service; 2 | 3 | import cc.uncarbon.module.sys.entity.SysUserRoleRelationEntity; 4 | import cc.uncarbon.module.sys.mapper.SysUserRoleRelationMapper; 5 | import cn.hutool.core.collection.CollUtil; 6 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 7 | import lombok.RequiredArgsConstructor; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.springframework.stereotype.Service; 10 | import org.springframework.transaction.annotation.Transactional; 11 | 12 | import java.util.Collection; 13 | import java.util.Collections; 14 | import java.util.Set; 15 | import java.util.stream.Collectors; 16 | 17 | 18 | /** 19 | * 后台用户-角色关联 20 | */ 21 | @RequiredArgsConstructor 22 | @Service 23 | @Slf4j 24 | public class SysUserRoleRelationService { 25 | 26 | private final SysUserRoleRelationMapper sysUserRoleRelationMapper; 27 | 28 | 29 | /** 30 | * 后台管理-新增 31 | * 注:本方法较为特殊,仅供SysTenantFacadeImpl调用 32 | */ 33 | @Transactional(rollbackFor = Exception.class) 34 | public Long adminInsert(Long tenantId, Long userId, Long roleId) { 35 | SysUserRoleRelationEntity entity = new SysUserRoleRelationEntity() 36 | .setUserId(userId).setRoleId(roleId); 37 | entity.setTenantId(tenantId); 38 | 39 | sysUserRoleRelationMapper.insert(entity); 40 | 41 | return entity.getId(); 42 | } 43 | 44 | 45 | /** 46 | * 先清理用户ID所有关联关系, 再绑定用户ID与角色ID 47 | */ 48 | @Transactional(rollbackFor = Exception.class) 49 | public void cleanAndBind(Long userId, Collection roleIds) { 50 | sysUserRoleRelationMapper.delete( 51 | new QueryWrapper() 52 | .lambda() 53 | .eq(SysUserRoleRelationEntity::getUserId, userId) 54 | ); 55 | 56 | if (CollUtil.isNotEmpty(roleIds)) { 57 | // 需要绑定角色 58 | roleIds.forEach( 59 | roleId -> sysUserRoleRelationMapper.insert( 60 | SysUserRoleRelationEntity.builder() 61 | .userId(userId) 62 | .roleId(roleId) 63 | .build() 64 | ) 65 | ); 66 | } 67 | } 68 | 69 | /** 70 | * 取拥有角色Ids 71 | * 72 | * @param userId 用户ID 73 | * @return 失败返回空列表 74 | */ 75 | public Set listRoleIdsByUserId(Long userId) throws IllegalArgumentException { 76 | if (userId == null) { 77 | throw new IllegalArgumentException("userId不能为空"); 78 | } 79 | 80 | return sysUserRoleRelationMapper.selectList( 81 | new QueryWrapper() 82 | .lambda() 83 | .select(SysUserRoleRelationEntity::getRoleId) 84 | .eq(SysUserRoleRelationEntity::getUserId, userId) 85 | ).stream().map(SysUserRoleRelationEntity::getRoleId).collect(Collectors.toSet()); 86 | } 87 | 88 | /** 89 | * 取角色IDs关联的用户IDs 90 | * 91 | * @param roleIds 角色IDs 92 | * @return 空集合or用户IDs 93 | */ 94 | public Set listUserIdsByRoleIds(Collection roleIds) { 95 | if (CollUtil.isEmpty(roleIds)) { 96 | return Collections.emptySet(); 97 | } 98 | 99 | return sysUserRoleRelationMapper.selectList( 100 | new QueryWrapper() 101 | .lambda() 102 | .select(SysUserRoleRelationEntity::getUserId) 103 | .in(SysUserRoleRelationEntity::getRoleId, roleIds) 104 | ).stream().map(SysUserRoleRelationEntity::getUserId).collect(Collectors.toSet()); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /attachments/db/MySQL/upgrade/1.7.1_to_1.7.2.sql: -------------------------------------------------------------------------------- 1 | -- v1.7.2 - Uncarbon - 默认内置文件上传功能 2 | -- 建表 oss_file_info 3 | DROP TABLE IF EXISTS `oss_file_info`; 4 | CREATE TABLE `oss_file_info` 5 | ( 6 | `id` bigint(20) NOT NULL COMMENT '主键ID', 7 | `tenant_id` bigint(20) NULL DEFAULT NULL COMMENT '租户ID', 8 | `revision` bigint(20) NOT NULL DEFAULT 1 COMMENT '乐观锁', 9 | `del_flag` tinyint(4) UNSIGNED NOT NULL DEFAULT 0 COMMENT '逻辑删除标识', 10 | `created_at` datetime(0) NOT NULL COMMENT '创建时刻', 11 | `created_by` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建者', 12 | `updated_at` datetime(0) NOT NULL COMMENT '更新时刻', 13 | `updated_by` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '更新者', 14 | `storage_platform` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '存储平台', 15 | `storage_base_path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '基础存储路径', 16 | `storage_path` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '存储路径', 17 | `storage_filename` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '存储文件名', 18 | `original_filename` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '原始文件名', 19 | `extend_name` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '扩展名', 20 | `file_size` bigint(20) NOT NULL COMMENT '文件大小', 21 | `md5` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'MD5', 22 | `classified` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '文件类别', 23 | `direct_url` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '对象存储直链', 24 | PRIMARY KEY (`id`) USING BTREE 25 | ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '上传文件信息' ROW_FORMAT = Dynamic; 26 | 27 | -- v1.7.2 - Uncarbon - 新增上传文件信息后台管理菜单 28 | INSERT INTO `sys_menu` (`id`, `tenant_id`, `revision`, `del_flag`, `created_at`, `created_by`, `updated_at`, 29 | `updated_by`, `title`, `parent_id`, `type`, `permission`, `icon`, `sort`, `status`, `component`, 30 | `external_link`) 31 | VALUES (20220922152710, NULL, 1, 0, '2022-12-25 23:09:30', 'admin', '2022-12-26 22:02:31', 'admin', '文件管理', 0, 0, 32 | 'Oss', 'ant-design:file-outlined', 4, 1, NULL, ''); 33 | INSERT INTO `sys_menu` (`id`, `tenant_id`, `revision`, `del_flag`, `created_at`, `created_by`, `updated_at`, 34 | `updated_by`, `title`, `parent_id`, `type`, `permission`, `icon`, `sort`, `status`, `component`, 35 | `external_link`) 36 | VALUES (20220922152714, NULL, 1, 0, '2022-09-22 15:27:14', 'helio-generator', '2022-12-26 22:03:14', 'admin', 37 | '上传文件信息管理', 20220922152710, 1, 'OssFileInfo', 'ant-design:save-twotone', 1, 1, '/oss/OssFileInfo/index', 38 | ''); 39 | INSERT INTO `sys_menu` (`id`, `tenant_id`, `revision`, `del_flag`, `created_at`, `created_by`, `updated_at`, 40 | `updated_by`, `title`, `parent_id`, `type`, `permission`, `icon`, `sort`, `status`, `component`, 41 | `external_link`) 42 | VALUES (20220922152715, NULL, 1, 0, '2022-09-22 15:27:14', 'helio-generator', '2022-12-26 22:01:58', 'admin', '查询', 43 | 20220922152714, 2, 'OssFileInfo:retrieve', NULL, 1, 1, NULL, ''); 44 | INSERT INTO `sys_menu` (`id`, `tenant_id`, `revision`, `del_flag`, `created_at`, `created_by`, `updated_at`, 45 | `updated_by`, `title`, `parent_id`, `type`, `permission`, `icon`, `sort`, `status`, `component`, 46 | `external_link`) 47 | VALUES (20220922152718, NULL, 1, 0, '2022-09-22 15:27:14', 'helio-generator', '2022-12-26 22:02:09', 'admin', '删除', 48 | 20220922152714, 2, 'OssFileInfo:delete', NULL, 2, 1, NULL, ''); 49 | -------------------------------------------------------------------------------- /src/main/java/cc/uncarbon/module/sys/service/SysRoleMenuRelationService.java: -------------------------------------------------------------------------------- 1 | package cc.uncarbon.module.sys.service; 2 | 3 | import cc.uncarbon.module.sys.entity.SysRoleMenuRelationEntity; 4 | import cc.uncarbon.module.sys.mapper.SysRoleMenuRelationMapper; 5 | import cn.hutool.core.collection.CollUtil; 6 | import cn.hutool.core.lang.Assert; 7 | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; 8 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 9 | import lombok.RequiredArgsConstructor; 10 | import lombok.extern.slf4j.Slf4j; 11 | import org.springframework.stereotype.Service; 12 | import org.springframework.transaction.annotation.Transactional; 13 | 14 | import java.util.*; 15 | import java.util.stream.Collectors; 16 | 17 | 18 | /** 19 | * 后台角色-可见菜单关联 20 | */ 21 | @RequiredArgsConstructor 22 | @Service 23 | @Slf4j 24 | public class SysRoleMenuRelationService { 25 | 26 | private final SysRoleMenuRelationMapper sysRoleMenuRelationMapper; 27 | 28 | 29 | /** 30 | * 根据角色Ids取菜单Ids 31 | * 因为多种角色容易出现交集,所以干脆用 Set 32 | * 33 | * @param roleIds 角色Ids 34 | * @return 菜单Ids 35 | */ 36 | public Set listMenuIdsByRoleIds(Collection roleIds) throws IllegalArgumentException { 37 | Assert.notEmpty(roleIds); 38 | 39 | // aka * 16 40 | Set ret = new HashSet<>(roleIds.size() << 4); 41 | for (Long roleId : roleIds) { 42 | ret.addAll( 43 | sysRoleMenuRelationMapper.selectList( 44 | new QueryWrapper() 45 | .lambda() 46 | .select(SysRoleMenuRelationEntity::getMenuId) 47 | .eq(SysRoleMenuRelationEntity::getRoleId, roleId) 48 | ).stream().map(SysRoleMenuRelationEntity::getMenuId).collect(Collectors.toSet())); 49 | } 50 | 51 | return ret; 52 | } 53 | 54 | /** 55 | * 绑定角色ID与菜单ID关联关系,增量更新 56 | * 57 | * @param roleId 角色ID 58 | * @param menuIds 新菜单ID集合 59 | */ 60 | @Transactional(rollbackFor = Exception.class) 61 | public void cleanAndBind(Long roleId, Collection menuIds) { 62 | LambdaQueryWrapper menuIdsQuery = 63 | new QueryWrapper() 64 | .lambda() 65 | .select(SysRoleMenuRelationEntity::getMenuId) 66 | .eq(SysRoleMenuRelationEntity::getRoleId, roleId); 67 | 68 | if (CollUtil.isEmpty(menuIds)) { 69 | // 清除绑定,直接删除所有关联关系就行 70 | sysRoleMenuRelationMapper.delete(menuIdsQuery); 71 | return; 72 | } 73 | 74 | // 先删除不再需要的关联关系 75 | sysRoleMenuRelationMapper.delete( 76 | new QueryWrapper() 77 | .lambda() 78 | .eq(SysRoleMenuRelationEntity::getRoleId, roleId) 79 | .notIn(SysRoleMenuRelationEntity::getMenuId, menuIds) 80 | ); 81 | 82 | // 取出需要增量更新的部分 83 | Set existingMenuIds = sysRoleMenuRelationMapper.selectList(menuIdsQuery) 84 | .stream().map(SysRoleMenuRelationEntity::getMenuId) 85 | .collect(Collectors.toSet()); 86 | menuIds.removeAll(existingMenuIds); 87 | 88 | if (CollUtil.isNotEmpty(menuIds)) { 89 | // 批量插入需要增量更新的部分 90 | List entityList = new ArrayList<>(menuIds.size()); 91 | for (Long menuId : menuIds) { 92 | entityList.add( 93 | SysRoleMenuRelationEntity.builder() 94 | .roleId(roleId) 95 | .menuId(menuId) 96 | .build() 97 | ); 98 | } 99 | entityList.forEach(sysRoleMenuRelationMapper::insert); 100 | } 101 | } 102 | } 103 | --------------------------------------------------------------------------------