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

news2024/10/24 14:41:27

在这里插入图片描述

文章目录

  • 前言
  • 正文
    • 🚀 技术栈
    • 🛠️ 功能模块
    • 📁 项目结构
    • 🌈 接口文档
    • 🚀 项目启动
  • 附录
    • 项目功能代码示例
      • 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.coloradmin.cn/o/2222467.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

青少年编程能力等级测评CPA C++ 四级试卷(2)

青少年编程能力等级测评CPA C 四级试卷&#xff08;2&#xff09; 一、单项选择题&#xff08;共15题&#xff0c;每题3分&#xff0c;共45分&#xff09; CP4_2_1. 下列有关面向对象程序设计的叙述中&#xff0c;不正确的是&#xff08; &#xff09;。 A&#xff0e;面向对…

__桥接模式

在C#中 初始版本 namespace _013_桥接模式 {/// <summary>/// 玩家抽象类/// </summary>public abstract class AbstractPlayer{protected IWeapon _weapon;//武器引用public void SetWeapon(IWeapon weapon){_weapon weapon;}//抽象方法使用武器public abstract…

Windows下Python3.8环境快速安装部署

为了帮助非python计算机软件开发人员快速上手使用python环境&#xff0c;我准备好了的Python 3.8免安装环境&#xff0c;并安装Jupyter Notebook以进行Python学习&#xff0c;以下是一个简单易懂的教程。请按照以下步骤操作&#xff1a; 步骤 1: 解压Python环境 下载并解压&a…

药智网数据库和摩熵医药数据库哪个好?

药智和摩熵医药(原药融云)作为医药行业内专业的医药数据库平台&#xff0c;它们各自在行业内占据着重要的地位&#xff0c;并且都在不断地创新和完善自己的数据库服务&#xff0c;以满足行业日益增长的需求为医药行业的数据服务、决策支持、研发分析等方面提供支持。. 但让大家…

java拆分地址中的省市县区,全国地址通用

package com.sure;import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * description: 拆分地址工具类 * fileName: AddressResolutionUtil.ja…

QExcel 保存数据 (QtXlsxWriter库 编译)

QtXlsxWriter 是一个用于在 Qt 应用程序中创建和操作 Excel XLSX 文件的库。它提供了一个简单的 API&#xff0c;使开发者能够轻松地生成和修改 Excel 文件&#xff0c;而无需依赖 Microsoft Excel 或其他外部应用程序。支持初始化、写文件、读文件、格式设置、合并单元格、加粗…

运维管理软件:如何引领一体化自动运维

在数字化转型的洪流中&#xff0c;运维团队正面临着前所未有的挑战与机遇。随着企业业务规模的不断扩大&#xff0c;IT架构的日益复杂&#xff0c;如何确保服务的连续性、稳定性和高效性&#xff0c;成为了运维团队亟待解决的核心问题。在此背景下&#xff0c;监控易运维管理软…

使用皮尔逊相关系数矩阵进行特征筛选

皮尔逊相关系数矩阵是一个用于量化多个变量之间线性关系的统计工具。它的每个元素表示两个变量之间的皮尔逊相关系数&#xff0c;取值范围从 -1 到 1&#xff1a; 1 表示完全正相关&#xff1a;当一个变量增加时&#xff0c;另一个变量也会增加。-1 表示完全负相关&#xff1a…

#每日一题#自动化 2024年10月

#每日一题#自动化 2024年10月 1、深拷贝和浅拷贝的区别是什么&#xff1f; 参考答案&#xff1a; 深拷贝是将对象本身复制给另一个对象。这意味着如果对对象的副本进行更改时不会影响原对象。在 Python 中&#xff0c;我们使用 deepcopy&#xff08;&#xff09;函数进行深拷贝…

Debezium和SeaTunnel实现MySQL到Hadoop的实时数据流和全量同步(基于尚硅谷的集群环境)

1、hadoop集群连接本地MySQL 1.1 首先测试集群是否可以ping通本地 虚拟机可以ping通网关&#xff08;192.168.10.2&#xff09;&#xff0c;但不能ping通192.168.10.1&#xff0c;这表明问题可能出在Windows主机的防火墙设置或VMware的网络配置上。 1.1.1 检查Windows防火墙…

个人信息窗口(三)

个人信息窗口&#xff08;三&#xff09; 前言 在上一集我们就可以显示我们就可以全部显示所有的未隐藏的组件了&#xff0c;但是隐藏的组件我们还没有蛆完成&#xff0c;所以我们这一集就需要去做隐藏组件的显示&#xff0c;以及如何切换到隐藏的组件的功能。 需求分析并实…

centos7 nginx优化

优化nginx进程个数的策略 在高并发、高访问量的web服务场景&#xff0c;需要事先启动好更多的nginx进程&#xff0c;以保证快速响应并处理大量并发用户的请求。worker_processes 1;一般调整到与CPU的颗数相同查看LInux可查看CPU个数及总核数grep processor /proc/cpuinfo|wc …

开放式耳机排行榜前十名,开放式耳机全价位段盘点

关于开放式蓝牙耳机的品牌选择&#xff0c;这是一个常见的问题。因为市面上的蓝牙耳机种类繁多&#xff0c;各种样式和类型层出不穷&#xff0c;让许多消费者感到困惑&#xff0c;不知道如何选择一款适合自己的蓝牙耳机。一款好的蓝牙耳机不仅需要音质好、配置高&#xff0c;还…

计算机毕业设计hadoop+spark知识图谱中药推荐系统 中药材推荐系统 中药可视化 中药数据分析 中药爬虫 机器学习 深度学习 人工智能 大数据

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 摘 要 本文所探讨的领域是…

深度学习-2:数据向量化

向量化 在逻辑回归中&#xff0c;x,w,b都是列向量&#xff0c;若要计算w.T*xb&#xff0c;不向量化的话就需要for循环计算起来很麻烦 向量化后&#xff0c;使用numpy.dot(w,x)函数即可快速计算 逻辑回归向量化

接口测试(六)jmeter——参数化(配置元件 --> 用户定义的变量)

一、jmeter——参数化&#xff08;配置元件 --> 用户定义的变量&#xff09; 注&#xff1a;示例仅供参考 1. 参数化格式&#xff1a;${变量名} 2. 配置元件&#xff1a;用户定义的变量 3. 添加【用户定义的变量】&#xff0c;【线程组】–>【添加】–>【配置元件】–…

手持无人机飞手执照,会组装调试入伍当兵有多香!

手持无人机飞手执照&#xff0c;并具备组装调试技能&#xff0c;在入伍当兵时确实会具有显著的优势和吸引力。以下是对这一情况的详细分析&#xff1a; 一、无人机飞手执照的优势 1. 法规遵从与安全保障&#xff1a; 根据《民用无人驾驶航空器系统驾驶员管理暂行规定》等相关…

深入浅出 Vue3 nextTick

程序员节日快乐~ #1024程序员节 | 征文# nextTick 概念 当你在 Vue 的响应式数据模型中对数据进行修改时&#xff0c;这些变化并不会立即同步到 DOM 上_&#xff0c;而是会在当前的微任务队列&#xff08;microtask queue&#xff09;执行完毕后进行批量更新。这种机制被称为…

【宝塔面板】轻松使用docker搭建lobe-chat项目(AI对话)

我们的目的&#xff1a;就是下面的这个玩意&#xff1a; 主要也就三步 1、创建容器编排模版 2、创建容器 3、配置 第一步&#xff1a;创建编排模版 代码我放下面了&#xff1a;&#xff08;你要记住这里开放的是3210端口&#xff09; # https://github.com/lobehub/lobe-ch…

Spring Boot驱动的植物健康监测革命

1系统概述 1.1 研究背景 随着计算机技术的发展以及计算机网络的逐渐普及&#xff0c;互联网成为人们查找信息的重要场所&#xff0c;二十一世纪是信息的时代&#xff0c;所以信息的管理显得特别重要。因此&#xff0c;使用计算机来管理植物健康系统的相关信息成为必然。开发合适…