写了一个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 {@Overridepublic 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;}@Overridepublic 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<>();@Overridepublic 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);}}}@Overrideprotected void dealOperationResult(OperationResult operationResult) {log.info("数据变更:{}", operationResult);}@Overrideprotected boolean allowProcess(String sql) {return super.allowProcess(sql);}@Overrideprotected 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);}}}@Overridepublic 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);}@Overridepublic 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;}@PostConstructprivate 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 {@Resourceprivate SysRoleManager sysRoleManager;@Resourceprivate SysRoleUserManager sysRoleUserManager;@Resourceprivate SysRoleMenuManager sysRoleMenuManager;@Resourceprivate SysMenuManager sysMenuManager;@Overridepublic List<String> getPermissionList(Object loginId, String loginType) {// 通过用户ID查角色IDList<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());}@Overridepublic List<String> getRoleList(Object loginId, String loginType) {// 通过用户ID查角色IDList<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 {@Resourceprivate SysConfigManager sysConfigManager;@Resourceprivate 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 单个数据查询响应结果*/@Overridepublic 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 响应*/@Overridepublic 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 响应*/@Overridepublic 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);}@Overridepublic void syncExport(HttpServletResponse response, SysConfigQueryRequest request) {sysConfigManager.syncExport(response, sysConfigConvertor.queryRequestToQuery(request));}@Overridepublic 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.mzph.cn/web/56415.shtml

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

相关文章

自动驾驶合集2

我自己的原文哦~ https://blog.51cto.com/whaosoft/12304421 #NeRF与自动驾驶 神经辐射场&#xff08;Neural Radiance Fields&#xff09;自2020年被提出以来&#xff0c;相关论文数量呈指数增长&#xff0c;不但成为了三维重建的重要分支方向&#xff0c;也逐渐作为自动驾驶…

C++学习笔记----9、发现继承的技巧(五)---- 多重继承(1)

我们前面提到过&#xff0c;多重继承常被认为是面向对象编程中复杂且没有必要的部分。这就仁者见仁&#xff0c;智者见智了&#xff0c;留给大家去评判。本节解释c中的多重继承。 1、多个类继承 从语法角度来说&#xff0c;定义一个有多个父类的类是很简单的。需要做的就是当声…

DASCTF 2024金秋十月赛RE题wp

目录 RE1&#xff1a;ezRERE2&#xff1a;ezelfRE3&#xff1a;ezAndroid 3题RE&#xff0c;差一点就AK了&#xff0c;可能好久没打比赛了&#xff0c;技能有所下降&#xff0c;还是需要经常摸一摸工具。 RE1&#xff1a;ezRE 执行的时候dump出来&#xff0c;然后静态分析 发…

Java项目-基于springboot框架的游戏分享系统项目实战(附源码+文档)

作者&#xff1a;计算机学长阿伟 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、ElementUI等&#xff0c;“文末源码”。 开发运行环境 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBoot、Vue、Mybaits Plus、ELementUI工具&#xff1a;IDEA/…

[ACTF2020] 新生赛]Exec1

目录 0x01命令执行 [ACTF2020 新生赛]Exec1 1、解法1 2、解法2 3、总结 3.1php命令注入函数 3.2java命令注入函数 3.3常见管道符 0x02SQL注入 [极客大挑战 2019]EasySQL1 0x01命令执行 [ACTF2020 新生赛]Exec1 1、解法1 ping本地&#xff0c;有回显&#xff0c;TTL…

红队-安全见闻篇(上)

声明 学习视频来自B站UP主 泷羽sec的个人空间-泷羽sec个人主页-哔哩哔哩视频,如涉及侵权马上删除文章 笔记的只是方便各位师傅学习知识,以下网站只涉及学习内容,其他的都与本人无关,切莫逾越法律红线,否则后果自负 一.编程与开发 1.后端语言学习 C语⾔&#xff1a;⼀种通⽤的…

Pytest-Bdd-Playwright 系列教程(1):从零开始教你写自动化测试框架「喂饭教程」

Pytest-Bdd-Playwright 系列教程&#xff08;1&#xff09;&#xff1a;从零开始教你写自动化测试框架「喂饭教程」 前言一、项目结构二、安装依赖三、BDD特性文件四、页面对象五、步骤定义六、测试脚本七、Pytest配置八、运行测试 前言 最近收到一些小伙伴在后台的留言&#x…

生成式AI时代的内容安全与系统构建:合合信息文档图像篡改检测创新方案

目录 一、生成式AI时代的内容安全与图像识别1.图像内容安全的重要性2.伪造文档与证件检测的应用场景3.人脸伪造检测技术 二、系统构建加速与文档解析1.TextIn文档解析平台2.TextIn文档解析输出的示例 三、合合信息的行业影响力总结 一、生成式AI时代的内容安全与图像识别 随着…

python-----函数详解(一)

一、概念及作用&#xff1a; 概念&#xff1a;由若干条语句组成语句块&#xff0c;其中包括函数名称、参数列表&#xff0c;它是组织代码的最小单元&#xff0c;完成一定的功能 作用&#xff1a;把一个代码封装成一个函数&#xff0c;一般按功能组织一段代码 目的就是为了重…

autMan奥特曼机器人-安装或更新golang依赖

autMan2.3.4及以上需要更新中间件或安装golang依赖&#xff0c;参照下列步骤&#xff1a; 一、直装版本 ssh下进入autMan文件夹下plugin/scripts下面输入以下指令&#xff1a; go get -u github.com/hdbjlizhe/middleware二、docker版本 从后台进入web终端&#xff0c;依次输入…

Ubuntu 上安装 Redmine 5.1 指南

文章目录 官网安装文档&#xff1a;命令步骤相关介绍GemRubyRailsBundler 安装 Redmine更新系统包列表和软件包&#xff1a;安装必要的依赖&#xff1a;安装 Ruby&#xff1a;安装 bundler下载 Redmine 源代码&#xff1a;安装 MySQL配置 Redmine 的数据库配置文件&#xff1a;…

Node.js:深入探秘 CommonJS 模块化的奥秘

在Node.js出现之前&#xff0c;服务端JavaScript基本上处于一片荒芜的境况&#xff0c;而当时也没有出现ES6的模块化规范。因此&#xff0c;Node.js采用了当时比较先进的一种模块化规范来实现服务端JavaScript的模块化机制&#xff0c;它就是CommonJS&#xff0c;有时也简称为C…

2024ideaUI切换和svn与git的切换,svn的安装和配置,idea集成svn ,2024-10-18日

2024-10-18日 2024的UI实在很不舒服&#xff0c;隐藏了很多按键&#xff1b; 第一步&#xff1a; 视图 -》 外观 -》 工具栏选出来&#xff1b; 结果出来&#xff1a; 运行的按键和设置的按钮 第二步 点击设置的按钮&#xff0c;选择最后一个&#xff0c;重启就行 结果 舒服&…

论文阅读(二十四):SA-Net: Shuffle Attention for Deep Convolutional Neural Networks

文章目录 Abstract1.Introduction2.Shuffle Attention3.Code 论文&#xff1a;SA-Net&#xff1a;Shuffle Attention for Deep Convolutional Neural Networks(SA-Net&#xff1a;置换注意力机制)   论文链接&#xff1a;SA-Net&#xff1a;Shuffle Attention for Deep Convo…

九州未来亓绚亮相丽台Solution Day 2024,共建AI赋能教育新时代

在数字化浪潮席卷全球的当下&#xff0c;生成式人工智能正迅速渗透至数字世界的每一个角落&#xff0c;而AI技术的物理化应用也正成为新的趋势。10月22日&#xff0c;丽台解决方案日Solution Day 2024&#xff1a;物理AI推动行业数字变革在上海绿地外滩中心顺利举行。 大会聚焦…

报表工具怎么选?山海鲸VS帆软,哪个更适合你?

概述 在国产报表软件市场中&#xff0c;山海鲸报表和帆软这两款工具都占有一席之地&#xff0c;许多企业在选择报表工具时常常在它们之间徘徊。然而&#xff0c;随着企业对数据分析需求的不断增长和复杂化&#xff0c;如何选取一款高效、易用且性价比高的报表工具&#xff0c;…

“摄像机”跟随及攻击抖动实现

学习Unity的摄像机功能&#xff0c;可以帮助我们实现摄像机对人物的跟随移动&#xff0c;还可以使用这个工具自带的插件&#xff0c;摄像机震动&#xff0c;颤动&#xff0c;增强打击感&#xff1b; 首先来安装一下这个插件&#xff0c;window菜单--packageManage--左上角Unit…

vcpkg 从清单文件安装依赖项

vcpkg 有两种运行模式&#xff1a;经典模式和清单模式。清单文件有自己的 vcpkg_installed 目录&#xff0c;可在其中安装依赖项&#xff0c;与所有包都安装在通用 %VCPKG_ROOT%/installed 目录中的经典模式不同。 因此&#xff0c;每个项目都可以有自己的清单和自己的一组依赖…

R语言机器学习算法实战系列(十)自适应提升分类算法 (Adaptive Boosting)

禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍原理步骤教程下载数据加载R包导入数据数据预处理数据描述数据切割调节参数构建模型预测测试数据评估模型模型准确性混淆矩阵模型评估指标ROC CurvePRC Curve特征的重要性保存模型总…

生发产品哪个效果最好?油秃头秋冬季养发搭子

如果你是大额头 或者 M型发际线&#xff0c;无论是天生的 亦或者是后天造成的&#xff0c;养发防脱一定要重视起来&#xff0c;因为防脱育发是需要循序渐进坚持的&#xff0c;今天就给大家分享一下几个特别有效的育发液&#xff0c;选对产品养发那真是稳了~ 1、露卡菲娅防脱育发…