文章目录
- 前言
- 正文
- 🚀 技术栈
- 🛠️ 功能模块
- 📁 项目结构
- 🌈 接口文档
- 🚀 项目启动
- 附录
- 项目功能代码示例
- 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 {
@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);
}
}
}