主题
内置功能说明
后端
账号与登录
内置账号
FgxAdmin内置有一个账号admin,初始密码是:Asdf123!
通过以下配置项控制是否可以在账号管理里编辑内置账号:
yaml
# 是否能编辑系统内置账号,默认:false(不可编辑)
setting:
system:
builtInAccountEditable: false
登录方式
FgxAdmin支持账号密码、手机验证码、邮箱验证码三种方式进行登录,可通过以下配置项启用或禁用:
yaml
setting:
system:
# 是否启用账号密码登录,默认:true(启用)
enablePswLogin: true
# 是否启用短信验证码,默认:true(启用)
enableSmsCode: true
# 是否启用邮箱验证码,默认:true(启用)
enableEmailCode: true
图片验证码可通过以下配置项启用或禁用:
yaml
setting:
system:
# 发送短信验证码时是否启用图片验证码,默认:true(启用)
enableSmsCaptcha: true
# 发送邮件验证码时是否启用图片验证码,默认:true(启用)
enableEmailCaptcha: true
# 使用账号密码登录时是否启用图片验证码,默认:true(启用)
enableLoginCaptcha: true
找回密码
FgxAdmin支持手机验证码、邮箱验证码两种方式进行找回密码,这两种方式在启用SMS短信和Email邮箱验证码的情况下可用,可通过以下配置项启用或禁用找回密码功能:
yaml
# 是否启用找回密码功能,默认:true(启用)
setting:
system:
enableRetrievePsw: true
重置密码
FgxAdmin支持重置密码功能,有重置权限的平台管理员可在账号管理(单租户模式:在用户管理)重置用户密码,重置后密码安全级别被重置为“初始”级别,因为管理员需要把重置后的密码发送给用户,这过程中可能会存在密码漏洞问题,当密码为“初始”级别,登录时会提醒用户修改密码。
可通过以下配置项启用或禁用重置密码功能,禁用后仅本人可修改密码:
yaml
# 是否启用重置密码功能,当安全性要求较高时禁用此功能,禁用后仅本人可修改密码;默认:true(启用)
setting:
system:
enableResetPassword: true
当密码的安全级别为“初始”级别时可通过以下配置项控制是否必须修改密码:
yaml
# 提醒设置密码方式,当密码为空、初始密码或密码被重置时,提示设置/修改密码;0表示不提醒,1表示建议设置密码,2表示强制设置密码;默认:1
setting:
system:
setPasswordRemindType: 1
密码安全设置
FgxAdmin永远把安全放在第一位,对密码安全进行了加固:
- 支持国际算法和国密算法多重算法组全加密,如:SM3+SHA256+PBKDF2
- 支持动态盐值
- 支持传输加密(SM2+RSA)
- 支持密码组合校验,至少包含大写字母、小写字母、数字和特殊字符其中三种
- 支持密码长度校验
- 支持密码安全等级归属
- 支持N天后提醒修改密码(建议修改或强制修改)
- 支持密码不能与前N次相同的校验
- 支持连续登录错误次数限制
- 支持连续登录错误后锁定账号N分钟
可通过以下配置项对安全配置进行修改:
yaml
setting:
system:
# 密码不能与前N次相同,默认6;最大不能超过30次,超过则取30;负数或0表示不校验
passwordCantBeSameAsPrevNTimes: 6
# 提醒设置密码方式,当密码为空、初始密码或密码被重置时,提示设置/修改密码;0表示不提醒,1表示建议设置密码,2表示强制设置密码;默认:1
setPasswordRemindType: 1
# N天后提醒修改密码,与changePasswordRemindType配合使用。默认:90; 负数或0表示不提醒
changePasswordDays: 90
# 提醒修改密码的方式,0表示不提醒,1表示建议修改,2表示强制修改;默认:1
changePasswordRemindType: 1
# 登录连续错误次数限制(密码错误、短信或邮箱验证码错误),默认5次
loginErrorTimes: 5
# 超过登录错误次数限制锁定时长,单位:分钟;默认120分钟
accountLockMinutes: 120
绑定手机号
可通过以下配置项控制登录账号是否必须绑定手机号
yaml
# 账号是否必须绑定手机号,默认:true(启用)
setting:
system:
accountMobileRequired: true
SMS短信配置
FgxAdmin已对接阿里云、七牛云和腾讯云的SMS短信通知产品。支持发送以下场景的验证码:
- 修改密码 - templateId1
- 找回密码 - templateId2
- 登录 - templateId3
- 绑定手机 - templateId4
- 注册 - templateId5
- 通用 - templateId (通用验证码模板,可选)
需要在云厂商平台申请签名和这5种验证码的短信模板,短信模板中的验证码参数默认使用code。如果不想区分场景,也可只申请一个通用的验证码模板,以上5种场景的模板ID(teamplateId)填写通用模板ID的值,也可留空,当具体场景模板ID为空时会使用通用模板ID发送。(建议区分场景)
SMS配置项以下,根据setting.system.sms.type填写对应提供商的信息:
yaml
setting:
system:
# SMS短信配置
sms:
# SMS短信服务提供商, 选项:aliyun/qiniu/tencent
type: aliyun
# 是否模拟发送(模拟发送的验证码可在redis或日志中获取), 默认false
mock: false
# 短信验证码有效时间,单位:秒;默认300秒
smsCodeTtlSeconds: 300
# aliyun阿里云
aliyun:
accessKey:
secretKey:
endpoint: dysmsapi.aliyuncs.com
region:
signName:
codeParam: code
templateId:
templateId1:
templateId2:
templateId3:
templateId4:
templateId5:
qiniu:
accessKey:
secretKey:
codeParam: code
templateId:
templateId1:
templateId2:
templateId3:
templateId4:
templateId5:
tencent:
accessKey:
secretKey:
region:
appid:
signName:
codeParam: code
templateId:
templateId1:
templateId2:
templateId3:
templateId4:
templateId5:
Email邮箱配置
FgxAdmin支持发送邮箱验证码,支持以下发送场景验证码:
- 修改密码
- 找回密码
- 登录
- 通用 (绑定邮箱或其它场景等)
需要配置邮箱服务器信息才能正常发送邮件:
yaml
# 邮箱服务器信息:
spring:
mail:
host: smtp.163.com
port:
username:
password:
properties:
mail:
smtp:
auth: true
ssl:
enable: true
starttls:
enable: true
required: true
setting:
system:
email:
# 邮件验证码有效时间,单位:秒;默认300秒
mailCodeTtlSeconds: 300
# 是否模拟发送(模拟发送的验证码可在redis或日志中获取), 默认false
mock: false
以下是邮箱验证码邮件模板的配置信息,一般不需要修改:
yaml
# freemarker default path is classpath:/templates/
#spring:
#freemarker:
#template-loader-path: classpath:/templates/
setting:
system:
email:
# 邮件验证码有效时间,单位:秒;默认300秒
mailCodeTtlSeconds: 300
# 是否模拟发送(模拟发送的验证码可在redis或日志中获取), 默认false
mock: false
# 修改密码验证码的邮件标题
changePasswordMailSubject: 修改密码验证码
# 修改密码邮件模板
changePasswordMailTemplate: mail/changePasswordTemplate.html
# 找回密码验证码的邮件标题
retrievePasswordMailSubject: 找回密码验证码
# 修改密码邮件模板
retrievePasswordTemplate: mail/retrievePasswordTemplate.html
# 登录验证码的邮件标题
loginMailSubject: 登录验证码
# 登录验证码邮件模板
loginTemplate: mail/loginTemplate.html
# 通用邮箱验证码的邮件标题
simpleMailSubject: 邮箱验证码
# 通用邮箱验证码邮件模板
simpleTemplate: mail/simpleTemplate.html
默认模板文件目录:fgx-admin-java/fgx-admin-web/resources/templates/mail
权限控制
FgxAdmin采用基于RBAC权限管理系统,支持菜单、按钮权限的配置。所有权限资源在菜单管理模块维护。
角色说明
角色分为三大类:平台角色、企业角色和单租户角色
- 平台角色是平台级别使用的角色,面向平台用户,如:平台的运营人员;内置的企业级别角色也在平台角色里维护,因为这几个内置角色是跨企业共用的,其权限需由平台统一维护。除内置的企业角色,平台角色只能分配给账号(tbl_account)。
- 企业角色是企业级别使用的角色,面向企业成员,角色只可分配给企业成员(tbl_user)
- 单租户角色是多租户模式下的一个特例,角色只可分配给单租户企业成员(tbl_user)
系统内置角色:
- 超级管理员 - 多租户模式下的平台角色
- 企业拥有者 - 多租户模式下的企业角色
- 管理员 - 多租户模式下的企业角色
- 成员 - 多租户模式下的企业角色
- 系统管理员 - 单租户模式下管理员角色
一个用户可拥有多个角色,其权限是多个角色的并集
内置角色的权限列表可通过以下配置项控制是否可编辑:
yaml
# 是否能编辑系统内置角色,默认:false(不可编辑)
setting:
system:
builtInRoleEditable: false
默认不可编辑,内置角色的权限在需求和开发阶段就确定了,一般在开发环境启用即可。生产环境通过SQL的方式修改。在有Nacos配置中心的情况下,可在生产环境启用,修改完内置角色的权限后再设置为false。该配置项主要是防止权限范围过大导致误操作。
可匿名访问的资源
在某些场景下,有部分接口需要要开放匿名访问,可通过以下两种方式配置:
编码方式
把可匿名访问的接口URL添加到SystemProperties.java类的builtInAnonPatterns配置列表里:
java
/**
* 系统内置的不需校验登录态的路径(优先级比authPaths高), Ant风格的路径匹配,如:/system/account/login 或 /cms/**
*/
private List<String> builtInAnonPatterns = Lists.newArrayList(
"/system/account/login",
"/system/account/logout",
"/system/account/open/**",
"/system/user/signup",
"/system/invitation/detail",
"/favicon.ico",
"/swagger-ui/**",
"/v3/api-docs/**",
"/v3/api-docs.yaml",
"/webjars/**",
"/*.html"
);
配置方式
在application.yml文件里找到以下配置项,按格式添加可匿名访问的URL
yaml
setting:
system:
# 不需要校验登录态的路径(优先级比authPaths高), Ant风格的路径匹配,如:/system/account/login 或 /cms/**
# 配置样例如下
#anonPatterns:
#- "/system/account/login"
#- "/system/account/open/**"
#- "/system/user/signup"
#- "/system/invitation/detail"
#- "/favicon.ico"
#- "/favicon.png"
#- "/v3/api-docs/**"
#- "/v3/api-docs.yaml"
#- "/webjars/**"
#- "/*.html"
@RequiresPermissions注解
每个菜单或按钮权限都有一个唯一的权限编码,在需要进行权限控制的Controller方法使用注解@RequiresPermissions,支持配置多个权限编码,支持AND、OR逻辑关系,如:
java
@RequiresPermissions(value = {"system:account", "system:account:view"}, logical = Logical.OR)
@RequiresPermissions("system:account:add")
前端控制权限控制
用户登录后按钮权限列表会缓存到store里,可通过以下方式控制表格的新增、编辑、删除和查看操作权限:
:permission属性也可是一个Function,用法如下:
自定义按钮的权限控制:
多租户/单租户模式
实现多租户的方式主要有两种:一是独立数据库方式,二是共享数据库的列隔离方式。FgxAdmin的多租户方式采用列隔离方式实现。
开启多租户模式
FgxAdmin的企业管理模块即租户管理,每一个企业即一个租户,企业ID即租户ID。
涉及配置项:
yaml
setting:
system:
# 启动多租户,默认:true(启用)
enableMultiTenant: true
# 单租户模式的企业ID,默认:1
singleTenantCorpId: 1
mybatis-flex:
global-config:
# 多租户字段
tenant-column: corp_id
- 多租户模式可通过setting.system.enableMultiTenant配置开启
- 当setting.system.enableMultiTenant=false时为单租户模式
- 多租户字段为corp_id,当数据库表有corp_id字段时mybatis-flex会自动加上corpId条件,mapper.java或mapper.xml里自定义SQL需要手动加上corpId条件
已对租户数据进行隔离的功能包括:成员、角色、部门、岗位、操作日志、文件管理等
忽略租户条件
在某些场景下,在增删改查等操作,我们可能需要忽略租户条件, 此时可以使用TenantManager的withoutTenantCondition方法,如:
java
TenantManager.withoutTenantCondition(() -> this.getMapper().deleteByQuery(qw));
多数据源
多数据源配置
FgxAdmin默认已经支持多数据源,多个数据源可通过以下方式配置,如添加第二个数据源ds2。默认第一个是默认数据源
yaml
mybatis-flex:
# 数据源配置
datasource:
# default datasource, 默认取第一个
default:
type: com.zaxxer.hikari.HikariDataSource
#driver-class-name: com.mysql.cj.jdbc.Driver
#url: jdbc:mysql://127.0.0.1:3306/fgxadmin?rewriteBatchedStatements=true&autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
url: jdbc:p6spy:mysql://127.0.0.1:3306/fgxadmin?rewriteBatchedStatements=true&autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: root
password: Root123456!
hikari:
maximum-pool-size: 10
minimum-idle: 5
# 示例数据源2
ds2:
type: com.zaxxer.hikari.HikariDataSource
#driver-class-name: com.mysql.cj.jdbc.Driver
#url: jdbc:mysql://127.0.0.1:3306/fgxadmin2?rewriteBatchedStatements=true&autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
url: jdbc:p6spy:mysql://127.0.0.1:3306/fgxadmin2?rewriteBatchedStatements=true&autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: root
password: Root123456!
hikari:
maximum-pool-size: 10
minimum-idle: 5
数据源切换
4 种方式来配置数据源:
- 1、编码,使用DataSourceKey.use 方法。
- 2、@UseDataSource("dataSourceName") 在 Mapper 类上,添加注解,用于指定使用哪个数据源。
- 3、@UseDataSource("dataSourceName") 在 Mapper 方法上,添加注解,用于指定使用哪个数据源。
- 4、@Table(dataSource="dataSourceName") 在 Entity 类上添加注解,该 Entity 的增删改查请求默认使用该数据源。
在 SpringBoot 项目上,@UseDataSource("dataSourceName") 也可用于在 Controller 或者 Service 上。
当不指定数据源名称时会在默认数据源上执行。
分布式事务
Redis缓存
Redis配置
FgxAdmin默认支持对接Redis,支持集群配置,同时支持Redisson和RedisTemplate/StringRedisTemplate两种方式操作缓存数据,默认使用Jackson�序列化方式。推荐使用Redisson,因为Redisson提供了很多开箱即用的功能。
Redis单机配置:
yaml
spring:
data:
redis:
host: 127.0.0.1
port: 6379
password:
database: 0
connect-timeout: 3000
timeout: 3000
Redis Cluster集群配置:
yaml
spring:
data:
redis:
password:
database: 0
connect-timeout: 3000
timeout: 3000
cluster:
max-redirects:
nodes:
- '10.1.1.101:6379'
- '10.1.1.102:6379'
- '10.1.1.103:6379'
Redis Sentinel集群配置:
yaml
spring:
data:
redis:
password:
database: 0
connect-timeout: 3000
timeout: 3000
sentinel:
master: '10.1.1.101:6379'
nodes:
- '10.1.1.101:6379'
- '10.1.1.102:6379'
- '10.1.1.103:6379'
Redis客户端注入
根据个人喜好注入想要的客户端
java
@Resource
private RedissonClient redisson;
@Resource
private RedisTemplate redisTemplate;
@Resource
private StringRedisTemplate stringRedisTemplate;
Spring Cache注解缓存
系统默认支持Spring Cache模块,Service类默认继承CacheableServiceImpl类,使用Spring Cache 的相关注解实现数据缓存,如:
java
@Service
@CacheConfig(cacheNames = "account")
public class AccountServiceImpl extends CacheableServiceImpl<MyAccountMapper, Account> {
@Override
@CacheEvict(allEntries = true)
public boolean remove(QueryWrapper query) {
return super.remove(query);
}
@Override
@CacheEvict(key = "#id")
public boolean removeById(Serializable id) {
return super.removeById(id);
}
@Override
@CacheEvict(allEntries = true)
public boolean removeByIds(Collection<? extends Serializable> ids) {
return super.removeByIds(ids);
}
// 根据查询条件更新时,实体类主键可能为 null。
@Override
@CacheEvict(allEntries = true)
public boolean update(Account entity, QueryWrapper query) {
return super.update(entity, query);
}
@Override
@CacheEvict(key = "#entity.id")
public boolean updateById(Account entity, boolean ignoreNulls) {
return super.updateById(entity, ignoreNulls);
}
@Override
@CacheEvict(allEntries = true)
public boolean updateBatch(Collection<Account> entities, int batchSize) {
return super.updateBatch(entities, batchSize);
}
@Override
@Cacheable(key = "#id")
public Account getById(Serializable id) {
return super.getById(id);
}
@Override
@Cacheable(key = "#root.methodName + ':' + #query.toSQL()")
public Account getOne(QueryWrapper query) {
return super.getOne(query);
}
@Override
@Cacheable(key = "#root.methodName + ':' + #query.toSQL()")
public <R> R getOneAs(QueryWrapper query, Class<R> asType) {
return super.getOneAs(query, asType);
}
@Override
@Cacheable(key = "#root.methodName + ':' + #query.toSQL()")
public List<Account> list(QueryWrapper query) {
return super.list(query);
}
@Override
@Cacheable(key = "#root.methodName + ':' + #query.toSQL()")
public <R> List<R> listAs(QueryWrapper query, Class<R> asType) {
return super.listAs(query, asType);
}
@Override
@Cacheable(key = "#root.methodName + ':' + #query.toSQL()")
public long count(QueryWrapper query) {
return super.count(query);
}
@Override
@Cacheable(key = "#root.methodName + ':' + #page.getPageSize() + ':' + #page.getPageNumber() + ':' + #query.toSQL()")
public <R> Page<R> pageAs(Page<R> page, QueryWrapper query, Class<R> asType) {
return super.pageAs(page, query, asType);
}
}
分布式锁和防重复提交
@RedisLock
基于Redis实现的分布式锁,用法如下,一般配置value和param即可:
java
public class RoleServiceImpl extends BaseServiceImpl<RoleMapper, Role> implements RoleService {
@Override
@Transactional
@RedisLock(value = "updateRole", param = "#req.id")
public Role update(RoleUpdateReq req, boolean sysRole) {
// ....
}
}
RedisRock支持的参数如下:
java
public @interface RedisLock {
/**
* 分布式锁的 key,必须:保持key+param唯一。默认前缀为: "lock:"
*
* @return key
*/
String value();
/**
* 分布式锁参数,可选,支持 spring el # 读取方法参数和 @ 读取 spring bean
*
* @return param
*/
String param() default "";
/**
* 等待锁超时时间,默认30
*
* @return int
*/
long waitTime() default 30;
/**
* 自动解锁时间,自动解锁时间一定得大于方法执行时间,否则会导致锁提前释放,默认100
*
* @return int
*/
long leaseTime() default 100;
/**
* 时间单温,默认为秒
*
* @return 时间单位
*/
TimeUnit timeUnit() default TimeUnit.SECONDS;
/**
* 默认公平锁
*
* @return LockType
*/
LockType type() default LockType.FAIR;
/**
* 获锁失败提示语,默认为:请求过多,请稍后重试
* @return ResponseType
*/
ResponseType responseType() default ResponseType.TOO_MANY_REQUESTS;
}
/**
* 获锁失败的提示语
*
*/
public enum ResponseType {
/**
* 重复提交
*/
REPEATED_REQUEST,
/**
* 请求过多
*/
TOO_MANY_REQUESTS,
/**
* 空
*/
NONE
}
@NoRepeatSubmit
防重复提交,用于Controller类的变更类型的方法上,一般不需求设置参数,系统会根据请求的类方法和用户标识生成Redis Key ,用法如下:
java
public class AccountController extends BaseController {
/**
* 新增账号
*/
@Operation(summary = "新增账号", description = "新增账号")
@OperationLog
@RequiresPermissions("system:account:add")
@NoRepeatSubmit()
@PostMapping("/add")
public Result<Account> add(@Valid @RequestBody AccountAddReq req) {
// ...
}
}
NoRepeatSubmit注解支持以下参数:
java
/**
* 防止重复提交
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface NoRepeatSubmit {
/**
* 分布式锁参数,可选,支持 spring el # 读取方法参数和 @ 读取 spring bean
*
* @return param
*/
String param() default "";
/**
* 自动解锁时间,自动解锁时间一定得大于方法执行时间,否则会导致锁提前释放,默认10
*
* @return int
*/
long leaseTime() default 10;
/**
* 时间单温,默认为秒
*
* @return 时间单位
*/
TimeUnit timeUnit() default TimeUnit.SECONDS;
/**
* 重复提交的提示语,默认为:请勿重复提交
* @return ResponseType
*/
ResponseType responseType() default ResponseType.REPEATED_REQUEST;
}
/**
* 获锁失败的提示语
*
*/
public enum ResponseType {
/**
* 重复提交
*/
REPEATED_REQUEST,
/**
* 请求过多
*/
TOO_MANY_REQUESTS,
/**
* 空
*/
NONE
}
OSS对象存储
FgxAdmin已对接以下对象存储产品:
- MinIO
- 阿里云OSS
- 七牛云OSS
- 腾讯云COS
- 华为云OBS
默认使用MinIO,可通过setting.oss.type配置项进行切换,设置type后须填写对应类型的服务器信息、桶信息、APPID和AK/SK等,具体配置项如下:
yaml
setting:
# OSS对象存储配置
oss:
# 对象存储类型,默认:minio
type: minio
minio:
accessKey:
secretKey:
endpoint:
bucketName:
# 阿里云
aliyun:
accessKey:
endpoint:
secretKey:
bucketName:
# 七牛云
qiniu:
accessKey:
secretKey:
endpoint:
bucketName:
# 腾讯云
tencent:
appid:
accessKey:
secretKey:
# bucket的命名规则为 BucketName-APPID ,此处填写的存储桶名称必须为此格式
bucketName: BucketName-${setting.oss.tencent.appid}
region:
# 华为云
huawei:
accessKey:
secretKey:
endpoint:
bucketName:
location:
访问日志
启用禁用
系统默认启用访问日志的打印,访问日志为INFO级别,会打印到本地日志文件。
- 开始请求时会打印:请求方法Method、请求URI、类方法路径、客户端IP、当前登录用户信息(accountId、corpId和userId)、请求报文
- 请求完成时会打印:耗时、当前总内存、已用内存、剩余内存、响应报文
可通过以下配置启用或禁用访问日志:
yaml
# 是否启用访问日志(打印到日志文件),默认true(启用)
setting:
monitor:
enableAccessLog: true
忽略报文
当某些接口报文过长或涉及敏感数据不方便打印请求报文或响应报文时,可在对应的Controller方法上通过以下注解来忽略请求报文、响应报文或同时忽略两者
java
public class AccountController extends BaseController {
/**
* 获取图片验证码
*/
@Operation(summary = "获取图片验证码", description = "获取图片验证码")
@IgnoreLogParam(IgnoreLogParam.IgnoreParamType.RESPONSE)
@NoRepeatSubmit(responseType = ResponseType.NONE)
@RequestMapping("/open/captcha")
public Result captcha(HttpServletRequest request, HttpServletResponse response) {
// ...
}
}
/**
* 访问日志忽略参数注解;当不需要打印出入参时使用,如:报文超大、包含敏感数据时<br>
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface IgnoreLogParam {
/**
* 参数日志类型
*/
enum IgnoreParamType {
/**
* 忽略请求参数和响应结果
*/
ALL,
/**
* 忽略请求参数
*/
REQUEST,
/**
* 忽略响应结果
*/
RESPONSE
}
/**
* 忽略参数日志类型
* @return
*/
IgnoreParamType value() default IgnoreParamType.ALL;
}
日志跟踪
FgxAdmin内置了日志跟踪traceId功能,日志跟踪通过log4j2的Context(MDC)来实现,在每行日志里会添加一个traceId,一个traceId对应一个HTTP请求,通过traceId可搜索出当次请求的所有日志。
在以下三个地方可以获取到请求的TraceId:
- 请求头X-TRACE-ID : 如果调用方不传则随机生成一个
- 响应头X-TRACE-ID
- 响应报文的traceId属性
该traceId仅用于服务本身,如果要跨服务传输,需在调用外部接口时手动传输,如:加到请求头里。
跨服务的链接跟踪建议使用skywalking、pinpoint和zipkin等专业的链路跟踪产品。
操作日志(审计日志)
日志配置
操作日志主要记录系统用户的操作内容,用于业务上审计。操作日志分为登录、登出、浏览和变更四种。操作日志会记录以下内容:
操作日志支持以下配置项:
yaml
setting:
# 监控模块
monitor:
# 是否启用操作日志(记录到数据库,在日志管理模块查看),默认true(启用)
enableOperationLog: true
# 是否记录接口入参到操作日志(记录到数据库,在日志管理模块查看),默认true(启用)
logReqJsonToOprLog: true
# 是否记录接口出参到操作日志(记录到数据库,在日志管理模块查看),默认true(启用)
logRespJsonToOprLog: true
# 是否记录浏览类型的日志,默认true(启用); 当不想审计浏览类型日志或日志量过多时可设置为false;
logViewOprLog: true
# 操作日志-请求参数最大长度限制,须与tbl_opr_log.request_param字段长度一起修改,默认4096
queryStringMaxLength: 4096
# 操作日志-接口入参最大长度限制,须与tbl_opr_log.req_json字段长度一起修改,默认4096
reqJsonMaxLength: 4096
# 操作日志保留时长,单位天;默认:0(0或负数表示永久保留)
operationLogRetentionDays: -1
# 操作日志清理任务每次删除日志条数;默认:2000;清理任务每次分钟执行一次,每次会执行若干次删除SQL,执行一次SQL限制operationLogHouseKeepLimit条日志,避免事务过长或DB资源占用过高
operationLogHouseKeepLimit: 2000
注解@OperationLog
在需要记录操作日志的Controller方法上加上@OperationLog注解,当方法上有权限注解@RequiresPermissions时, 操作日志会取菜单权title和logType参数。 用法如下:
java
public class AccountController extends BaseController {
/**
* 修改账号
*/
@Operation(summary = "修改账号", description = "修改账号")
@OperationLog
@RequiresPermissions("system:account:update")
@NoRepeatSubmit
@PostMapping("/update")
public Result<Account> update(@Valid @RequestBody AccountUpdateReq req) {
// ...
}
/**
* 登录
*/
@Operation(summary = "登录", description = "登录")
@OperationLog(title = "登录", logType = OperationLog.LOGIN, ignoreReqJson = true)
@NoRepeatSubmit
@PostMapping("/login")
public Result<UserInfo> login(@Valid @RequestBody LoginReq req, HttpServletRequest request) {
// ...
}
}
忽略响应报文
在某些场景下,我们想忽略请求或响应报文,如:浏览类的接口或含有敏感信息的接口。可通用ignoreReqJson和ignoreRespJson参数控制,如:
java
@OperationLog(title = "登录", logType = OperationLog.LOGIN, ignoreReqJson = true)
系统内置模块默认不记录数据列表接口的响应报文。
如果想全局忽略或记录报文可通过以下配置项设置,默认true
yaml
setting:
monitor:
# 是否记录接口入参到操作日志(记录到数据库,在日志管理模块查看),默认true(启用)
logReqJsonToOprLog: true
# 是否记录接口出参到操作日志(记录到数据库,在日志管理模块查看),默认true(启用)
logRespJsonToOprLog: true
启用调试模式
可通过以下配置项启用或禁用:
yaml
# 是否启用调试模式,调试模式下会打印堆栈信息或调试日志, 默认: false
setting:
base:
debug: false
传输加密
FgxAdmin支持前后端传输加密,支持国际算法RSA和国标算法SM2加密,也支持SM2+RSA双重加密。目前修改密码、重置密码、注册等场景对密码进行了加密传输。其它有敏感数据场景也可使用加密传输。
前端可通过以下函数对敏感数据进行加密, 函数位于src/utils/util:
javascript
{
/**
* RSA加密
*/
export const RSAEncrypt = (publicKey, data) => {
// ...
return encryptedData;
}
/**
* SM2加密
*/
export const SM2Encrypt = (publicKey, data) => {
// ...
return encryptedData;
}
/**
* SM2+RAS加密 SM2(RSA(数据))
*/
export const SM2RSAEncrypt = (rsaPublicKey, sm2PublicKey, data) => {
// ...
return encryptedData;
}
}
公钥rsaPublicKey, sm2PublicKey可通过以下接口获取:
javascript
import { getSecurityConfig } from '@/api/system/account';
// ...
initConfig() {
getSecurityConfig().then((res) => {
this.securityConfig = res.data.data;
// 获取公钥:
// this.securityConfig.rsaPublicKey
// this.securityConfig.sm2PublicKey
});
}
// ...
后端接收到加密数据后可通过以下方法解密:
com.fuginx.admin.core.common.util.CryptoUtil
javascript
public class CryptoUtil {
/**
* SM2解密,解密时会自动加上04前缀
*
* @param redisson redisson客户端实例,用于获取缓存密钥
* @param encodedData SM2密文(不含04前缀,如果有04前缀去掉后再输入)
* @return
*/
public static String SM2Decrypt(RedissonClient redisson, String encodedData) {
// ...
return decryptedData;
}
/**
* RSA解密
*
* @param redisson redisson客户端实例,用于获取缓存密钥
* @param encodedData RSA密文
* @return
*/
public static String RSADecrypt(RedissonClient redisson, String encodedData) {
// ...
return decryptedData;
}
/**
* SM2解密+RSA解密,即RSADecrypt(SM2Decrypt())
*
* @param redisson redisson客户端实例,用于获取缓存密钥
* @param encodedData SM2密文
* @return
*/
public static String SM2AndRSADecrypt(RedissonClient redisson, String encodedData) {
// ...
return decryptedData;
}
}
加密存储与脱敏展示
FgxAdmin支持敏感数据加密存储到数据库和支持页面脱敏展示。为了满足安全合规要求,除数据库加密存储外日志中也不能含有敏感数据的明文。FgxAdmin采用在Controller接口入参和出参对敏感数据进行加密和解密处理,这样就能保证敏感数据在后端的逻辑处理过程中和数据库存储都是加密的。
开启加解密
- Controller接口方法上使用@EncryptDecrypt 注解,仅对被注解的接口进行加解密处理
- 接口入参对象(必须继承BaseReq入参基类)中需要加密的属性使用 @Encrypt 注解
- 接口出参对象中需要解密的属性使用@Decrypt 注解
java
/**
* 新增账号
*/
@Operation(summary = "新增账号", description = "新增账号")
@OperationLog
@RequiresPermissions("system:account:add")
@NoRepeatSubmit()
@EncryptDecrypt
@PostMapping("/add")
public Result<Account> add(@Valid @RequestBody AccountAddReq req) {
// ...
return Result.ok(accountService.add(req));
}
@Data
@EqualsAndHashCode(callSuper = false)
public class AccountAddReq extends BaseReq {
// ...
/**
* 手机号
*/
@Schema(title = "手机号")
@Length(max = 16, message = "手机号的长度不能超过16个字符")
@Encrypt(group = Groups.ACCOUNT)
private String mobile;
/**
* 邮箱
*/
@Schema(title = "邮箱")
@Length(max = 128, message = "邮箱的长度不能超过128个字符")
@Encrypt(group = Groups.ACCOUNT)
private String email;
// ...
}
@Data
public class Account extends SimpleEntity {
// ...
/**
* 手机号
*/
@Schema(title = "手机号")
@Decrypt(type = Types.MOBILE, group = Groups.ACCOUNT, permissions = {"system:account:unmask"})
private String mobile;
/**
* 邮箱
*/
@Schema(title = "邮箱")
@Decrypt(type = Types.EMAIL, group = Groups.ACCOUNT, permissions = {"system:account:unmask"})
private String email;
// ...
}
@Decrypt 注解支持配置脱敏方式:
- 展示密文 (即不解密)
- 展示明文 (即不脱敏)
- 手机号脱敏
- 固定电话脱敏
- 身份证号脱敏
- 中文名脱敏
- 地址脱敏
- 电子邮箱脱敏
- 密码脱敏
- 车牌号脱敏
- 银行卡号脱敏
加密分组
@Encrypt 和 @Decrypt 注解中都支持配置group分组,用于按分组关闭加密或脱敏功能,可通过配置项来关闭:
关闭加密功能:setting.base.encrypt.encryptDisabledGroups=account,corpContact #多个逗号分隔
关闭脱敏功能:setting.base.encrypt.maskDisabledGroups=account,corpContact #多个逗号分隔
yaml
# FgxAdmin配置
setting:
base:
# 数据库敏感数据加解密设置@Encrypt@Decrypt
encrypt:
# 关闭加密功能的分组,多个用逗号分隔; 默认空
encryptDisabledGroups:
# 关闭脱敏功能的分组,多个用逗号分隔; 默认空
maskDisabledGroups:
敏感数据的查看权限
页面一般仅展示脱敏后的打码数据,但在某些场景下需要查看敏感数据的明文,可通过“查看敏感数据”权限来控制,代码生成器中可以指定哪些字段是敏感数据并配置脱敏感方式和分组,生成代码时会同步生成“查看敏感数据”权限SQL,权限码格式为:模块名:子模块名:unmask
当用户有该模块的敏感数据查看权限时页面搜索栏下面会有一个展示敏感数据的选项,勾选后可查看敏感数据的明文。
加密和解密工具类
在某些场景下需要对敏感数据进手动加密或手动解密,可使用DbCryptoUtil工具类:
java
public class DbCryptoUtil {
/**
* 加密数据库字段内容
*
* @param data 需加密的数据
* @return
*/
public static String encrypt(String data) {}
/**
* 解密数据库字段内容
*
* @param encryptedData 密文
* @return
*/
public static String decrypt(String encryptedData) {}
/**
* 加密数据库字段内容
*
* @param dataList 需加密的数据列表
* @return
*/
public static List<String> encrypt(List<String> dataList) {}
/**
* 解密数据库字段内容
*
* @param encryptedDataList 密文列表
* @return
*/
public static List<String> decrypt(List<String> encryptedDataList) {}
}
加密算法和密钥配置
FgxAdmin支持AES和SM4算法,其中SM4是国密算法。默认使用AES(CBC/PKCS7Padding)算法。系统默认使用授权证书里的随机生成的128位密钥。配置授权码即可正常使用。也可通过以下配置更换密钥和算法,需要在系统投入使用前配置,如果在系统投入使用后且产生了加密数据,需要在更换前手动对解密数据再使用新算法和密钥对数据进行加密。
yaml
# FgxAdmin配置
setting:
# 授权
license:
# 授权码
code:
# 数据库字段内容加密算法(AES/SM4),默认AES (CBC/PKCS7Padding)
#algorithm: AES
# SM4算法的密钥Base64,默认使用授权证书里的key
#sm4Key:
# AES算法的密钥Base64,默认使用授权证书里的key
#aesKey:
# AES算法的iv值Base64, 仅PKCS7Padding需要, 默认使用授权证书里的Iv
#aesIv:
# AES算法的加密模式: CBC、ECB、CTR、OCF、CFB,默认:CBC
#aesMode: CBC
# AES算法的补码方式: NoPadding、PKCS5Padding、PKCS7Padding,默认:PKCS7Padding
#aesPadding: PKCS7Padding
SM4国密算法配置样例:
yaml
# FgxAdmin配置
setting:
# 授权
license:
# 授权码
code: 你的授权码
# 数据库字段内容加密算法(AES/SM4),默认AES (CBC/PKCS7Padding)
algorithm: AES
# SM4算法的密钥Base64,默认使用授权证书里的key
sm4Key: SI9xmy2Vao2HzDHLnTgcGQ==
AES(ECB/PKCS5Padding)算法配置样例:
yaml
# FgxAdmin配置
setting:
# 授权
license:
# 授权码
code: 你的授权码
# 数据库字段内容加密算法(AES/SM4),默认AES (CBC/PKCS7Padding)
algorithm: AES
# AES算法的密钥Base64,默认使用授权证书里的key
aesKey: SI9xmy2Vao2HzDHLnTgcGQ==
# AES算法的加密模式: CBC、ECB、CTR、OCF、CFB,默认:CBC
aesMode: ECB
# AES算法的补码方式: NoPadding、PKCS5Padding、PKCS7Padding,默认:PKCS7Padding
aesPadding: PKCS5Padding
CORS跨域访问
FgxAdmin支持开启CORS跨域访问,可配置全局开放跨域访问,也可开放部分接口或目录。如有Nacos配置中心可实现动态配置实时生效,不需重启。
全局开放
yaml
setting:
base:
# Cors跨域访问配置
cors:
# 是否启用cors, 默认false
enabled: true
# cors规则,仅启用后有效,支持配置多个规则(urlPattern须唯一)
rules:
- urlPattern: /**
# 是否携带认证信息,默认true
#allowCredentials: true
# 允许的来源,多个用逗号分隔,默认:*
#allowedOrigins: "*"
# 允许的来源格式,多个用逗号分隔
#allowedOriginPatterns:
# 允许的请求方法,多个用逗号分隔,默认:GET, HEAD and POST
#allowedMethods: "GET,POST,HEAD"
# 允许的请求头,多个用逗号分隔,默认:*
#allowedHeaders: "*"
# 暴露的响应头,多个用逗号分隔
#exposedHeaders:
# pre-flight的客户端缓存时长,单位秒, 默认1800
#maxAge: 1800
- urlPattern为/** 表示所有接口都开放,pattern支持AntPath格式。
- allowedOrigins 允许的来源默认开放所有,可配置具体的来源,如:http://abc.com 或 http://2.2.4.76:9090
- 其它配置项一般使用默认值即可
部分开放
yaml
setting:
base:
# Cors跨域访问配置
cors:
# 是否启用cors, 默认false
enabled: true
# cors规则,仅启用后有效,支持配置多个规则(urlPattern须唯一)
rules:
- urlPattern: /system/position/**
allowCredentials: false
- urlPattern: /mall/product/**
allowedMethods: "*"
- urlPattern: /mall/shop/info
以上配置了三条CORS规则
- 允许跨域访问position的所有接口,但不允许携带认证信息
- 允许跨域访问product的所有接口,并且允许所有请求方法
- 允许跨域访问/mall/shop/info单个接口,其它配置使用默认值
前端
通用CRUD
FgxAdmin的通用CRUD功能是基于Avue实现的,核心代码是src/mixins/crud.js , 其它vue页面都是扩展自crud.js, crud.js已经定义好通用模板,业务扩展时只需简单定义列表字段即可。
crud定义了各操作的回调函数供扩展使用:
javascript
{
/**
* 获取表格数据前执行
*/
beforeList: () => {},
/**
* 数据加载到表格前执行
* @param data 表格数据
*/
beforeLoad: (data) => {},
/**
* 数据加载到表格后执行
* @param data 表格数据
*/
afterList: (data) => {},
/**
* 打开表单对话窗口-打开前执行
* @param boxType 窗口类型:add/edit/view (新增/编辑/查看)
*/
beforeOpen: (boxType) => {},
/**
* 打开表单对话窗口-打开后执行
* @param boxType 窗口类型:add/edit/view (新增/编辑/查看)
*/
afterOpen: (boxType) => {},
/**
* 关闭表单对话窗口-关闭前执行
* @param boxType 窗口类型:add/edit/view (新增/编辑/查看)
*/
beforeClose: (boxType) => {},
/**
* 新增操作-调用新增接口前执行
* @param form 表单数据
*/
beforeAdd: (form) => {},
/**
* 新增操作-调用新增接口后执行
* @param data 接口返回的新增成功的数据
*/
afterAdd: (data) => {},
/**
* 更新操作-调用更新接口前执行
* @param form 表单数据
*/
beforeUpdate: (form) => {},
/**
* 更新操作-调用更新接口后执行
* @param data 接口返回的更新成功的数据
*/
afterUpdate: (data) => {},
/**
* 删除操作-调用删除接口前执行
* @param form 表单数据
*/
beforeDel: (form) => {},
/**
* 删除操作-调用删除接口后执行
* @param data 接口返回的已删除成功的数据
* @param row 当前操作行的数据
* @param index 当前操作行的下标(序号)
*/
afterDel: (data, row, index) => {},
}
crud.js的其它函数也可在vue里重写覆盖。