文章目录
- 前言
- 正文
- 🚀 技术栈
- 🛠️ 功能模块
- 📁 项目结构
- 🌈 接口文档
- 🚀 项目启动
- 附录
- 项目功能代码示例
- 1、数据库拦截器-打印sql执行时间
- 2、数据记录变更拦截器
- 3、用户角色数据权限拦截器
- 4、实体转换器接口
- 5、触发器模版
- 6、satoken获取角色列表和权限列表
- 7、service实现举例,系统配置实现
前言
关于 pine-manage-system,是用于学习和使用一些java技术,和自己对某些业务的理解与落地的验证。
这是一个多模块项目,里边集成了不少后端常见的功能。
项目代码仓库:https://gitee.com/fengsoshuai/pine-manage-system.git
项目模块分层如下:
正文
🚀 技术栈
组件/框架/语言/插件 | 版本 | 备注 |
---|---|---|
Java | 17 | |
maven-compiler-plugin | 3.8.1 | 需要增加编译参数 -parameters |
spring-boot-dependencies | 3.3.4 | |
Druid | 1.2.22 | |
Mybatis-plus | 3.5.6 | 整合时需要使用高版本的mybatis-spring |
mybatis-spring | 3.0.3 | |
Hutool | 5.8.32 | |
Lombok | 1.18.32 | |
jackson-databind | 2.15.4 | |
Easyexcel | 3.3.4 | 整合需要使用高版本commons-compress |
commons-compress | 1.26.2 | |
knife4j-openapi3-jakarta | 4.5.0 | 接口文档采用openApi |
transmittable-thread-local | 2.14.5 | 升级ThreadLocal |
Satoken | 1.38.0 | 登录认证组件 |
Mapstruct | 1.5.5.Final | 对象转换,属性复制 |
lock4j-core | 2.2.7 | 分布式锁 |
Redisson-boot | 3.25.2 | |
swagger-annotations-jakarta | 2.2.19 | |
hibernate-validator | 8.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.htmlActuator
监控地址:http://localhost:8080/pine-manage/actuatorApi
导入地址:http://localhost:8080/pine-manage/v3/api-docs
🚀 项目启动
- 检查
java
环境,需要使用java17
- 检查
maven
是否正常安装,正常使用 - 数据库初始化,执行
pine-manage.sql
- 修改配置信息,比如数据库连接信息,
redis
信息等 - 启动项目
pine-manage-start
里的启动类PineManageApplication
如果使用 java -jar 的方式启动,可以采用项目中提供的脚本文件
start.bat
或start.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);}}
}