当前位置: 首页 > article >正文

写了一个SpringBoot的后端管理系统(仅后端)pine-manage-system

在这里插入图片描述

文章目录

  • 前言
  • 正文
    • 🚀 技术栈
    • 🛠️ 功能模块
    • 📁 项目结构
    • 🌈 接口文档
    • 🚀 项目启动
  • 附录
    • 项目功能代码示例
      • 1、数据库拦截器-打印sql执行时间
      • 2、数据记录变更拦截器
      • 3、用户角色数据权限拦截器
      • 4、实体转换器接口
      • 5、触发器模版
      • 6、satoken获取角色列表和权限列表
      • 7、service实现举例,系统配置实现

前言

关于 pine-manage-system,是用于学习和使用一些java技术,和自己对某些业务的理解与落地的验证。

这是一个多模块项目,里边集成了不少后端常见的功能。
项目代码仓库:https://gitee.com/fengsoshuai/pine-manage-system.git
项目模块分层如下:
在这里插入图片描述

正文

🚀 技术栈

组件/框架/语言/插件版本备注
Java17
maven-compiler-plugin3.8.1需要增加编译参数 -parameters
spring-boot-dependencies3.3.4
Druid1.2.22
Mybatis-plus3.5.6整合时需要使用高版本的mybatis-spring
mybatis-spring3.0.3
Hutool5.8.32
Lombok1.18.32
jackson-databind2.15.4
Easyexcel3.3.4整合需要使用高版本commons-compress
commons-compress1.26.2
knife4j-openapi3-jakarta4.5.0接口文档采用openApi
transmittable-thread-local2.14.5升级ThreadLocal
Satoken1.38.0登录认证组件
Mapstruct1.5.5.Final对象转换,属性复制
lock4j-core2.2.7分布式锁
Redisson-boot3.25.2
swagger-annotations-jakarta2.2.19
hibernate-validator8.0.1.Final校验器

🛠️ 功能模块

  • 系统认证:用户登录、用户登出、获取验证码、校验验证码
  • 系统配置:增删改查、刷新缓存、导入导出
  • 部门管理:查部门(树形结构)、删除、新增、修改
  • 字典管理:增删改查
  • 字典项管理:增删改查
  • 动态线程池管理:刷新线程池参数
  • 系统日志管理:查询
  • 菜单管理:查菜单(树形结构)、删除、新增、修改
  • 角色管理:增删改查、修改状态、分配菜单给角色、查角色对应的菜单ID
  • 加解密管理:加密、解密、解密并脱敏、获取敏感数据前缀
  • 用户管理:增删改查、获取当前用户信息、重置密码、修改状态、分配角色

📁 项目结构

pine-manage-system
├── documents # 脚本文档
│   ├── bin
│   └── sql
├── logs # 日志
│   └── pine-manage
├── pine-manage-client # 客户端接口,对外接口-暂未使用
│   └── src
│       └── main
│           └── java
│               └── com
│                   └── pine
│                       └── client
├── pine-manage-common # 公共组件
│   ├── pine-manage-common-beans # bean对象(请求+响应)、异常类、枚举
│   │   └── src
│   │       └── main
│   │           └── java
│   │               └── com
│   │                   └── pine
│   │                       └── common
│   │                           └── beans
│   │                               ├── enums # 枚举
│   │                               ├── exception # 异常
│   │                               ├── pineframework # 系统的请求+响应
│   │                               │   ├── request
│   │                               │   └── response
│   │                               ├── request # 公共请求
│   │                               └── response # 公共响应
│   ├── pine-manage-common-captcha # 验证码组件
│   │   └── src
│   │       └── main
│   │           └── java
│   │               └── com
│   │                   └── pine
│   │                       └── common
│   │                           └── captcha
│   │                               ├── config
│   │                               └── core
│   ├── pine-manage-common-convertor # 转换器组件,集成mapstruct
│   │   └── src
│   │       └── main
│   │           └── java
│   │               └── com
│   │                   └── pine
│   │                       └── common
│   │                           └── convertor
│   ├── pine-manage-common-database # 数据库组件
│   │   └── src
│   │       └── main
│   │           └── java
│   │               └── com
│   │                   └── pine
│   │                       └── common
│   │                           └── database
│   │                               ├── annotations # 自定义注解
│   │                               ├── batch # 批量操作
│   │                               ├── config # 配置信息
│   │                               ├── constant # 常量,枚举
│   │                               ├── handler # 处理器
│   │                               ├── interceptor # 拦截器
│   │                               └── listener # 监听器
│   ├── pine-manage-common-doc # 接口文档组件
│   │   └── src
│   │       └── main
│   │           └── java
│   │               └── com
│   │                   └── pine
│   │                       └── common
│   │                           └── doc
│   │                               └── config # swagger+openapi的文档配置
│   ├── pine-manage-common-dynamic-threadpool # 动态线程池组件
│   │   └── src
│   │       └── main
│   │           └── java
│   │               └── com
│   │                   └── pine
│   │                       └── common
│   │                           └── threadpool
│   │                               ├── config
│   │                               └── core
│   ├── pine-manage-common-excel # excel组件
│   │   └── src
│   │       └── main
│   │           └── java
│   │               └── com
│   │                   └── pine
│   │                       └── common
│   │                           └── excel
│   ├── pine-manage-common-redis # redis组件,缓存+分布式锁
│   │   └── src
│   │       └── main
│   │           ├── java
│   │           │   └── com
│   │           │       └── pine
│   │           │           └── common
│   │           │               └── redis
│   │           │                   ├── cache
│   │           │                   ├── config
│   │           │                   └── lock
│   │           └── resources
│   ├── pine-manage-common-system-log # 系统日志组件
│   │   └── src
│   │       └── main
│   │           ├── java
│   │           │   └── com
│   │           │       └── pine
│   │           │           └── common
│   │           │               └── systemlog
│   │           │                   ├── annotation
│   │           │                   ├── aspect
│   │           │                   ├── config
│   │           │                   └── event
│   │           └── resources
│   │               └── META-INF
│   │                   └── spring
│   ├── pine-manage-common-trigger # 触发器组件
│   │   └── src
│   │       └── main
│   │           └── java
│   │               └── com
│   │                   └── pine
│   │                       └── common
│   │                           └── trigger
│   └── pine-manage-common-util # 工具类
│       └── src
│           └── main
│               └── java
│                   └── com
│                       └── pine
│                           └── common
│                               └── util
│                                   ├── async
│                                   ├── sensitive
│                                   ├── time
│                                   ├── tree
│                                   └── user
├── pine-manage-dao # DAO,数据访问层
│   └── src
│       └── main
│           ├── java
│           │   └── com
│           │       └── pine
│           │           └── dao
│           │               └── pineframework # pine框架模块
│           │                   ├── constants # 枚举
│           │                   ├── entity # 实体
│           │                   └── mapper # mapper接口
│           └── resources
│               └── mapper
│                   └── pineframework # mapper的xml文件
├── pine-manage-manager # 管理层,依赖DAO
│   └── src
│       └── main
│           └── java
│               └── com
│                   └── pine
│                       └── manager
│                           ├── config
│                           ├── core
│                           └── pineframework # pine框架模块
│                               ├── bo # 业务对象
│                               ├── convertors # 转换器
│                               ├── dto # 数据传输对象
│                               ├── query # 查询对象
│                               └── trigger # 触发器
│                                   └── impl
├── pine-manage-service # service层
│   └── src
│       └── main
│           └── java
│               └── com
│                   └── pine
│                       └── service # service业务接口+业务实现层,依赖Manager层
│                           ├── core
│                           ├── pineframework # pine框架模块
│                           │   └── impl
│                           └── util
├── pine-manage-start # 启动层,Application的启动,配置文件
│   └── src
│       └── main
│           ├── java
│           │   └── com
│           │       └── pine
│           │           └── start # 启动类
│           └── resources # 配置文件
│               └── environments
│                   ├── dev # 开发环境配置
│                   └── local # 本地环境配置
└── pine-manage-web # web层,包含全局拦截器,控制器等(也可以叫controller层)
    └── src
        └── main
            └── java
                └── com
                    └── pine
                        └── web
                            ├── config
                            ├── core
                            └── pineframework # pine框架模块
                                └── controller # 控制器

🌈 接口文档

  • Doc 接口文档:http://localhost:8080/pine-manage/doc.html
  • Actuator监控地址:http://localhost:8080/pine-manage/actuator
  • Api导入地址:http://localhost:8080/pine-manage/v3/api-docs

🚀 项目启动

  1. 检查java环境,需要使用java17
  2. 检查maven是否正常安装,正常使用
  3. 数据库初始化,执行 pine-manage.sql
  4. 修改配置信息,比如数据库连接信息,redis信息等
  5. 启动项目 pine-manage-start 里的启动类 PineManageApplication

如果使用 java -jar 的方式启动,可以采用项目中提供的脚本文件start.batstart.sh;注意在启动时,需要将jar包和脚本文件放在同一目录下。

附录

项目功能代码示例

1、数据库拦截器-打印sql执行时间

package com.pine.common.database.interceptor;

import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

/**
 * mybatis拦截器拦截处理查询、更新的方法,mybatis-plus拦截器见:{@link MybatisPlusInterceptor}<br>
 * <a href="https://blog.csdn.net/FBB360JAVA/article/details/132513180">https://blog.csdn.net/FBB360JAVA/article/details/132513180</a>
 * </br>
 * 注意这里的拦截,只打印MappedStatement 和执行时间,不拦截sql
 *
 * @author pine manage
 * @since 2024-08-09
 */

@Intercepts({
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
})
@Slf4j
public class MybatisPrintSqlInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 获取语句映射对象
        Object[] invocationArgs = invocation.getArgs();
        MappedStatement mappedStatement = (MappedStatement) invocationArgs[0];
        String mappedStatementId = mappedStatement.getId();
        // 开始执行时间
        long start = System.currentTimeMillis();
        // 执行方法
        Object returnValue = invocation.proceed();
        // 执行耗时
        long executeTime = System.currentTimeMillis() - start;
        // 打印
        log.info("数据库SQL打印拦截-执行方法:{} 执行耗时:{}ms", mappedStatementId, executeTime);
        return returnValue;
    }


    @Override
    public Object plugin(Object target) {
        // 如果是Executor(执行增删改查操作),则拦截下来
        if (target instanceof Executor) {
            return Plugin.wrap(target, this);
        }
        return target;
    }
}


2、数据记录变更拦截器

package com.pine.common.database.interceptor;

import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.extension.parser.JsqlParserGlobal;
import com.baomidou.mybatisplus.extension.plugins.inner.DataChangeRecorderInnerInterceptor;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.update.Update;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;

import java.sql.Connection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * 自定义数据变更记录拦截器
 *
 * @author pine manage
 * @since 2024-08-09
 */
@Slf4j
public class CustomDataChangeRecorderInnerInterceptor extends DataChangeRecorderInnerInterceptor {

    private static final Set<String> SKIP_MAPPED_STATEMENT_ID = new HashSet<>();

    /**
     * 表名和列名,列名用英文逗号分隔
     */
    private static final Set<String> SKIP_TABLE_NAMES = new HashSet<>();

    @Override
    public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {
        PluginUtils.MPStatementHandler mpSh = PluginUtils.mpStatementHandler(sh);
        MappedStatement ms = mpSh.mappedStatement();
        String mappedStatementId = ms.getId();
        boolean containsMappedId = SKIP_MAPPED_STATEMENT_ID.contains(mappedStatementId);
        if (containsMappedId) {
            log.info("数据变更拦截跳过mappedStatementId:{}", mappedStatementId);
            return;
        }

        final BoundSql boundSql = mpSh.boundSql();
        SqlCommandType sct = ms.getSqlCommandType();
        if (sct == SqlCommandType.INSERT || sct == SqlCommandType.UPDATE || sct == SqlCommandType.DELETE) {
            PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql();
            OperationResult operationResult;
            long startTs = System.currentTimeMillis();
            try {
                Statement statement = JsqlParserGlobal.parse(mpBs.sql());
                if (statement instanceof Insert insert) {
                    Table table = insert.getTable();
                    String name = table.getName();
                    if (SKIP_TABLE_NAMES.contains(name)) {
                        log.info("数据变更拦截跳过tableName:{}", name);
                        return;
                    }
                    operationResult = processInsert(insert, mpSh.boundSql());
                } else if (statement instanceof Update update) {
                    Table table = update.getTable();
                    String name = table.getName();
                    if (SKIP_TABLE_NAMES.contains(name)) {
                        log.info("数据变更拦截跳过tableName:{}", name);
                        return;
                    }
                    operationResult = processUpdate((Update) statement, ms, boundSql, connection);
                } else if (statement instanceof Delete delete) {
                    Table table = delete.getTable();
                    String name = table.getName();
                    if (SKIP_TABLE_NAMES.contains(name)) {
                        log.info("数据变更拦截跳过tableName:{}", name);
                        return;
                    }
                    operationResult = processDelete((Delete) statement, ms, boundSql, connection);
                } else {
                    logger.info("other operation sql={}", mpBs.sql());
                    return;
                }
            } catch (Exception e) {
                if (e instanceof DataUpdateLimitationException) {
                    throw (DataUpdateLimitationException) e;
                }
                logger.error("Unexpected error for mappedStatement={}, sql={}", ms.getId(), mpBs.sql(), e);
                return;
            }
            long costThis = System.currentTimeMillis() - startTs;
            if (operationResult != null) {
                operationResult.setCost(costThis);
                dealOperationResult(operationResult);
            }
        }
    }

    @Override
    protected void dealOperationResult(OperationResult operationResult) {
        log.info("数据变更:{}", operationResult);
    }

    @Override
    protected boolean allowProcess(String sql) {
        return super.allowProcess(sql);
    }

    @Override
    protected Map<String, Object> getUpdatedColumnDatas(String tableName, BoundSql updateSql, Statement statement) {
        return super.getUpdatedColumnDatas(tableName, updateSql, statement);
    }

    public static void appendSkipMappedStatementId(String mappedStatementId) {
        SKIP_MAPPED_STATEMENT_ID.add(mappedStatementId);
    }

    public static void appendSkipMappedStatementIdSet(Set<String> mappedStatementIdSet) {
        SKIP_MAPPED_STATEMENT_ID.addAll(mappedStatementIdSet);
    }

    public static void appendSkipTableName(String tableName) {
        SKIP_TABLE_NAMES.add(tableName);
    }

    public static void appendSkipTableNameSet(Set<String> tableNameSet) {
        SKIP_TABLE_NAMES.addAll(tableNameSet);
    }

}

3、用户角色数据权限拦截器

package com.pine.common.database.interceptor;

import com.baomidou.mybatisplus.extension.plugins.handler.MultiDataPermissionHandler;
import com.baomidou.mybatisplus.extension.plugins.inner.DataPermissionInterceptor;
import com.pine.common.database.handler.CustomDataPermissionHandler;
import com.pine.common.database.handler.RoleCustomDataPermissionHandler;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.statement.select.PlainSelect;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.sql.SQLException;

/**
 * 数据权限拦截器</br>
 * 如果需要使用该拦截器,需要给RoleCustomDataPermissionHandler配置监听器
 *
 * @author pine manage
 * @since 2024-09-26
 */
@Slf4j
public class MybatisPlusPermissionInterceptor extends DataPermissionInterceptor {

    public MybatisPlusPermissionInterceptor() {
        super(new RoleCustomDataPermissionHandler());
    }

    public MybatisPlusPermissionInterceptor(CustomDataPermissionHandler dataPermissionHandler) {
        super(dataPermissionHandler);
    }

    /**
     * 设置 where 条件
     *
     * @param plainSelect    查询对象
     * @param whereStatement 查询条件片段
     */
    protected void setWhere(PlainSelect plainSelect, String whereStatement) {
        if (this.getDataPermissionHandler() instanceof MultiDataPermissionHandler) {
            super.processPlainSelect(plainSelect, whereStatement);
            return;
        }

        if (this.getDataPermissionHandler() instanceof CustomDataPermissionHandler handler) {
            Expression sqlSegment = handler.getSqlSegmentWithPermission(plainSelect, whereStatement);
            if (null != sqlSegment) {
                plainSelect.setWhere(sqlSegment);
            }
        }
    }

    @Override
    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        super.beforeQuery(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        PrintSqlUtil.printSql("Query", ms, parameter, boundSql);
    }

    @Override
    public void beforeUpdate(Executor executor, MappedStatement ms, Object parameter) throws SQLException {
        super.beforeUpdate(executor, ms, parameter);
        PrintSqlUtil.printSql("Update", ms, parameter, null);
    }
}

4、实体转换器接口

package com.pine.common.convertor;

import java.util.List;

/**
 * 实体转换器
 *
 * @author pine manage
 * @since 2024-08-09
 */
public interface EntityConvertor<Entity, EntityBo> {

    Entity entityBoToEntity(EntityBo entityBo);

    EntityBo entityToEntityBo(Entity entity);

    List<Entity> entityBoToEntity(List<EntityBo> entityBo);

    List<EntityBo> entityToEntityBo(List<Entity> entity);
}

5、触发器模版

package com.pine.common.trigger;

import com.pine.common.redis.config.SpringBeanUtil;
import jakarta.annotation.PostConstruct;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.lang.NonNull;

import java.util.Objects;

/**
 * 抽象触发器模版
 *
 * @author pine manage
 * @since 2024-08-09
 */
@Getter
@Slf4j
public abstract class AbstractTriggerTemplate<TriggerRequest, TriggerResponse> implements Trigger<TriggerRequest, TriggerResponse> {

    /**
     * 触发器类型
     */
    private final TriggerType triggerType;

    public AbstractTriggerTemplate(@NonNull TriggerType triggerType) {
        Objects.requireNonNull(triggerType);
        this.triggerType = triggerType;
    }

    /**
     * 前置操作
     *
     * @param triggerContext 请求上下文
     */
    protected void before(TriggerContext<TriggerRequest> triggerContext) {
    }

    /**
     * 后置操作
     *
     * @param triggerContext  请求上下文
     * @param triggerResponse 触发器响应
     */
    protected void after(TriggerContext<TriggerRequest> triggerContext, TriggerResponse triggerResponse) {
    }

    /**
     * 执行
     *
     * @param triggerContext 请求上下文
     * @return 触发器响应
     */
    @SuppressWarnings("unchecked")
    public final TriggerResponse execute(TriggerContext<TriggerRequest> triggerContext) {
        AbstractTriggerTemplate<TriggerRequest, TriggerResponse> triggerTemplate = SpringBeanUtil.getByClass(this.getClass());

        // 执行前置操作
        triggerTemplate.before(triggerContext);
        // 触发器执行
        TriggerResponse triggerResponse = triggerTemplate.trigger(triggerContext);
        // 执行后置操作
        triggerTemplate.after(triggerContext, triggerResponse);

        // 返回响应
        return triggerResponse;
    }

    @PostConstruct
    private void registerTrigger() {
        log.info("注册触发器{}:triggerType.code={},triggerType.desc={}", this.getClass(), triggerType.getCode(), triggerType.getDesc());
        TriggerFactory.register(triggerType, this);
    }
}


6、satoken获取角色列表和权限列表

package com.pine.service.core;

import cn.dev33.satoken.stp.StpInterface;
import cn.hutool.core.collection.CollUtil;
import com.pine.manager.pineframework.SysMenuManager;
import com.pine.manager.pineframework.SysRoleManager;
import com.pine.manager.pineframework.SysRoleMenuManager;
import com.pine.manager.pineframework.SysRoleUserManager;
import com.pine.manager.pineframework.bo.SysMenuBo;
import com.pine.manager.pineframework.bo.SysRoleBo;
import com.pine.manager.pineframework.bo.SysRoleMenuBo;
import com.pine.manager.pineframework.bo.SysRoleUserBo;
import com.pine.manager.pineframework.query.SysRoleMenuQuery;
import com.pine.manager.pineframework.query.SysRoleUserQuery;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * <p>
 * 获取角色列表&权限列表 服务实现类
 * </p>
 *
 * @author pine manage
 * @since 2024-08-15
 */
@Slf4j
@Component
public class StpInterfaceImpl implements StpInterface {

    @Resource
    private SysRoleManager sysRoleManager;

    @Resource
    private SysRoleUserManager sysRoleUserManager;

    @Resource
    private SysRoleMenuManager sysRoleMenuManager;

    @Resource
    private SysMenuManager sysMenuManager;

    @Override
    public List<String> getPermissionList(Object loginId, String loginType) {
        // 通过用户ID查角色ID
        List<Long> roleIds = getRoleIds(loginId);
        if (CollUtil.isEmpty(roleIds)) {
            return new ArrayList<>();
        }

        // 查角色对应的菜单信息
        SysRoleMenuQuery sysRoleMenuQuery = new SysRoleMenuQuery();
        sysRoleMenuQuery.setRoleIds(roleIds);
        List<SysRoleMenuBo> sysRoleMenuBos = sysRoleMenuManager.list(sysRoleMenuQuery);
        if (CollUtil.isEmpty(sysRoleMenuBos)) {
            return new ArrayList<>();
        }

        List<Long> menuIds = sysRoleMenuBos.stream().map(SysRoleMenuBo::getMenuId).distinct().toList();
        if (CollUtil.isEmpty(menuIds)) {
            return new ArrayList<>();
        }

        // 根据菜单列表查菜单信息
        List<SysMenuBo> sysMenuBos = sysMenuManager.listByPrimaryKeys(menuIds);
        if (CollUtil.isEmpty(sysMenuBos)) {
            return new ArrayList<>();
        }

        // 获取菜单对应的权限
        return sysMenuBos.stream().map(SysMenuBo::getPermission).collect(Collectors.toList());
    }

    @Override
    public List<String> getRoleList(Object loginId, String loginType) {
        // 通过用户ID查角色ID
        List<Long> roleIds = getRoleIds(loginId);
        if (CollUtil.isEmpty(roleIds)) {
            return new ArrayList<>();
        }

        List<SysRoleBo> sysRoleBos = sysRoleManager.listByPrimaryKeys(roleIds);
        if (CollUtil.isEmpty(sysRoleBos)) {
            return new ArrayList<>();
        }

        // 返回角色编码
        return sysRoleBos.stream().map(SysRoleBo::getCode).collect(Collectors.toList());
    }

    /**
     * 获取角色ID列表
     *
     * @param loginId 登录ID
     * @return 角色ID列表
     */
    private List<Long> getRoleIds(Object loginId) {
        // 查用户角色关联关系
        SysRoleUserQuery sysRoleUserQuery = new SysRoleUserQuery();
        sysRoleUserQuery.setUserId(Long.valueOf(loginId.toString()));
        List<SysRoleUserBo> sysRoleUserBos = sysRoleUserManager.list(sysRoleUserQuery);
        if (CollUtil.isEmpty(sysRoleUserBos)) {
            return new ArrayList<>();
        }

        return sysRoleUserBos.stream().map(SysRoleUserBo::getRoleId).toList();
    }
}

7、service实现举例,系统配置实现

package com.pine.service.pineframework.impl;

import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.read.listener.PageReadListener;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.pine.common.beans.exception.BusinessException;
import com.pine.common.beans.exception.ExceptionCodeEnum;
import com.pine.common.beans.pineframework.request.SysConfigQueryRequest;
import com.pine.common.beans.pineframework.response.SysConfigQueryResponse;
import com.pine.common.beans.request.PageRequest;
import com.pine.common.beans.response.PageResponse;
import com.pine.common.util.valid.ValidUtil;
import com.pine.dao.pineframework.entity.SysConfig;
import com.pine.manager.pineframework.SysConfigManager;
import com.pine.manager.pineframework.bo.SysConfigBo;
import com.pine.manager.pineframework.convertors.SysConfigConvertor;
import com.pine.manager.pineframework.dto.SysConfigImportDto;
import com.pine.manager.pineframework.query.SysConfigQuery;
import com.pine.service.pineframework.SysConfigService;
import com.pine.service.util.PageUtil;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * <p>
 * 系统配置表 服务实现类
 * </p>
 *
 * @author pine manage
 * @since 2024-08-12
 */
@Slf4j
@Service
public class SysConfigServiceImpl implements SysConfigService {

    @Resource
    private SysConfigManager sysConfigManager;

    @Resource
    private SysConfigConvertor sysConfigConvertor;

    private static final Map<String, SFunction<SysConfig, ?>> COLUMNS_FUNCTION_MAP;

    static {
        COLUMNS_FUNCTION_MAP = Map.of(
                "id", SysConfig::getId,
                "configKey", SysConfig::getConfigKey
        );
    }


    /**
     * 通过唯一key查询单个数据
     *
     * @param uniqueKey 唯一键
     * @param column    列名
     * @return 单个数据查询响应结果
     */
    @Override
    public SysConfigQueryResponse getOneByUniqueKey(Object uniqueKey, String column) {
        // 根据业务ID查询
        SysConfigBo sysConfigBo = sysConfigManager.getOneByUniqueKey(uniqueKey, COLUMNS_FUNCTION_MAP.getOrDefault(column, SysConfig::getId));
        return sysConfigConvertor.entityBoToQueryResponse(sysConfigBo);
    }

    /**
     * 查询信息(不分页)
     *
     * @param request 请求
     * @return 响应
     */
    @Override
    public List<SysConfigQueryResponse> list(SysConfigQueryRequest request) {
        SysConfigQuery sysConfigQuery = sysConfigConvertor.queryRequestToQuery(request);
        if (ObjectUtils.isNotEmpty(sysConfigQuery)) {
            List<SysConfigBo> sysConfigList = sysConfigManager.list(sysConfigQuery);
            if (ObjectUtils.isNotEmpty(sysConfigList)) {
                return sysConfigList.stream()
                        .map(sysConfigConvertor::entityBoToQueryResponse)
                        .filter(ObjectUtils::isNotEmpty)
                        .collect(Collectors.toList());
            }
        }
        return Collections.emptyList();
    }


    /**
     * 查询信息(分页)
     *
     * @param request 请求
     * @return PageResponse 响应
     */
    @Override
    public PageResponse<SysConfigQueryResponse> listPages(PageRequest<SysConfigQueryRequest> request) {
        try {
            // 分页查询
            IPage<SysConfigBo> sysConfigPage = sysConfigManager.page(transformToQuery(request));

            // 安全地处理分页转换逻辑
            return safelyConvertPage(sysConfigPage);
        } catch (Exception e) {
            log.error("查询失败", e);
            throw new BusinessException(ExceptionCodeEnum.SELECT_ERROR);
        }
    }


    /**
     * 安全地将分页数据转换为响应对象
     *
     * @param sysConfigPage 分页查询结果
     * @return 分页响应对象
     */
    private PageResponse<SysConfigQueryResponse> safelyConvertPage(IPage<SysConfigBo> sysConfigPage) {
        if (sysConfigPage == null || sysConfigPage.getRecords() == null) {
            return new PageResponse<>();
        }

        // 使用并行流进行转换以提高效率,但需确保线程安全
        List<SysConfigQueryResponse> responses = sysConfigPage.getRecords().parallelStream()
                .map(sysConfigBo -> sysConfigConvertor.entityBoToQueryResponse(sysConfigBo))
                .collect(Collectors.toList());

        return PageUtil.convertPage(sysConfigPage, responses);
    }


    /**
     * 将请求 request 转换成 manager 的 query 对象
     *
     * @param request 请求参数
     * @return query 对象
     */
    private PageRequest<SysConfigQuery> transformToQuery(PageRequest<SysConfigQueryRequest> request) {
        SysConfigQuery sysConfigQuery = sysConfigConvertor.queryRequestToQuery(request.getData());
        return new PageRequest<>(request.getPageNum(), request.getPageSize(), sysConfigQuery);
    }

    @Override
    public void syncExport(HttpServletResponse response, SysConfigQueryRequest request) {
        sysConfigManager.syncExport(response, sysConfigConvertor.queryRequestToQuery(request));
    }

    @Override
    public void getImportTemplate(HttpServletResponse response) {
        try {
            // 设置响应信息,文件名
            sysConfigManager.setResponseAndHeaderInfo(response, "系统配置_导入模版_");
            try (ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream(), SysConfigImportDto.class).autoCloseStream(false).build()) {
                WriteSheet writeSheet = EasyExcel.writerSheet("sheet").build();
                excelWriter.write(Collections.emptyList(), writeSheet);
            }
        } catch (Exception e) {
            log.warn("导出模版异常 {}", e.getLocalizedMessage(), e);
            throw new BusinessException(ExceptionCodeEnum.IMPORT_TEMPLATE_ERROR);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void importExcel(MultipartFile file) {
        try {
            // 默认每次会读取100条数据
            EasyExcel.read(file.getInputStream(), SysConfigImportDto.class, new PageReadListener<SysConfigImportDto>(importDtos -> {
                // 校验导入数据
                String validateResult = ValidUtil.validate(importDtos);
                if (StrUtil.isNotBlank(validateResult)) {
                    log.error("导入数据校验失败 {}", validateResult);
                    throw new BusinessException(String.valueOf(ExceptionCodeEnum.IMPORT_ERROR), validateResult);
                }

                // 数据转换
                List<SysConfigBo> sysConfigBos = sysConfigConvertor.importDtoToEntityBo(importDtos);
                // 保存到数据库
                sysConfigManager.saveBatch(sysConfigConvertor.entityBoToEntity(sysConfigBos));
            })).sheet().doRead();
        } catch (Exception e) {
            log.warn("上传文件异常 {}", e.getLocalizedMessage(), e);
            throw new BusinessException(ExceptionCodeEnum.IMPORT_ERROR);
        }
    }
}


http://www.kler.cn/news/362784.html

相关文章:

  • 基于STM32设计的养殖场环境监测系统(华为云IOT)
  • 【MySQL】表的增删改查(CRUD)
  • konvajs -基础图形-标签-箭头,动画,学习笔记
  • 陆金所控股第三季度财报分析:增长困局与逆势突围
  • excel将文本型数字转变为数值型数字
  • 算法1—八大常用排序算法(上)
  • 【NodeJS】NodeJS+mongoDB在线版开发简单RestfulAPI (四):状态码的使用
  • 软件测试与软件缺陷的基础知识
  • Triton语言:机器学习领域的新星,能否挑战CUDA的霸主地位? ​​​​​​​
  • Zookeeper面试整理-分布式系统知识
  • Oracle OCP认证考试考点详解082系列01
  • perl统一修改文件前缀并排序
  • Embedding实现GPT回答和知识库内容相关的内容
  • LabVIEW继电器视觉检测系统
  • CSS3文本阴影、文本换行、文本溢出、文本修饰、文本描边的使用
  • 项目打包不同环境
  • 【D3.js in Action 3 精译_036】4.1 DIY 实战:在 Observable 平台实现 D3折线图坐标轴的绘制
  • AudioSegment 提高音频音量 - python 实现
  • 消息队列(仿RabbitMQ)—— 生产消费模型
  • 钉钉消息推送工具类
  • 使用皮尔逊相关系数矩阵进行特征筛选
  • Windows系统启动MongoDB报错无法连接服务器
  • 码支付源码2024又一款全新码支付源码
  • 国产自主可控新征程:华为原生鸿蒙系统与鲲鹏认证
  • vue中选项式 API(Options API) 和 组合式 API(Composition API)区别
  • Python 实现的风控系统(使用了kafka、Faust、模拟drools、redis、分布式数据库)