Compare commits
61 Commits
master
...
feature/20
135 changed files with 7157 additions and 833 deletions
@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
|
||||
package apelet.tenantadmin.upms.dto; |
||||
|
||||
import lombok.Data; |
||||
|
||||
/** |
||||
* 用户-角色-组织关联参数对象,用于 assignRoleDept 接口接收前端传值。 |
||||
* |
||||
* @author chenchuchuan |
||||
* @date 2026-07-28 |
||||
*/ |
||||
@Data |
||||
public class RoleDeptItemDto { |
||||
|
||||
/** |
||||
* 角色Id(可为空,为空时跳过该条记录)。 |
||||
*/ |
||||
private String roleId; |
||||
|
||||
/** |
||||
* 组织Id(可为空,表示不限定组织)。 |
||||
*/ |
||||
private String deptId; |
||||
} |
||||
@ -0,0 +1,161 @@
@@ -0,0 +1,161 @@
|
||||
server: |
||||
tomcat: |
||||
uri-encoding: UTF-8 |
||||
threads: |
||||
max: 100 |
||||
min-spare: 10 |
||||
servlet: |
||||
encoding: |
||||
force: true |
||||
charset: UTF-8 |
||||
enabled: true |
||||
|
||||
logging: |
||||
config: classpath:logback-spring.xml |
||||
|
||||
spring: |
||||
servlet: |
||||
multipart: |
||||
max-file-size: 50MB |
||||
max-request-size: 50MB |
||||
mvc: |
||||
converters: |
||||
preferred-json-mapper: fastjson |
||||
freemarker: |
||||
template-loader-path: classpath:/template/ |
||||
cache: false |
||||
charset: UTF-8 |
||||
check-template-location: true |
||||
content-type: text/html |
||||
expose-request-attributes: false |
||||
expose-session-attributes: false |
||||
request-context-attribute: request |
||||
suffix: .ftl |
||||
cloud: |
||||
sentinel: |
||||
transport: |
||||
# sentinel控制台地址。 |
||||
# 由于8080端口容易与其他服务端口冲突,可以在启动sentinel控制台时动态修改,如: |
||||
# java -Dserver.port=8858 -jar sentinel-dashboard-$VERSION.jar |
||||
# 改为8858后,下面的配置端口也需要改为:localhost:8858。 |
||||
dashboard: localhost:8858 |
||||
|
||||
# feign 配置 |
||||
feign: |
||||
sentinel: |
||||
enabled: false |
||||
httpclient: |
||||
enabled: true |
||||
max-connections: 200 |
||||
max-connections-per-route: 50 |
||||
client: |
||||
config: |
||||
default: |
||||
# 输出feignclient的日志,缺省值为none,可选值为none/basic/headers/full |
||||
# 注意:需要把feignclient类所在包的日志级别设置为debug时才生效。如: |
||||
# logging: |
||||
# level: |
||||
# com.demo.multi: info |
||||
# com.demo.multi.coursepaperinterface.client: debug |
||||
loggerLevel: full |
||||
connectTimeout: 5000 |
||||
readTimeout: 10000 |
||||
compression: |
||||
request: |
||||
enabled: true |
||||
response: |
||||
enabled: true |
||||
|
||||
common-core: |
||||
# 可选值为 mysql / postgresql / oracle / dm8 / kingbase |
||||
databaseType: mysql |
||||
|
||||
swagger: |
||||
# 当enabled为false的时候,则可禁用swagger。 |
||||
enabled: true |
||||
# 工程的基础包名。 |
||||
basePackage: apelet |
||||
# 工程服务的基础包名。 |
||||
serviceBasePackage: |
||||
title: 小猿微服务工程 |
||||
description: 小猿微服务工程详情 |
||||
version: 1.0 |
||||
knife4j: |
||||
# 打开knife4j增强模式。 |
||||
enable: true |
||||
|
||||
aliyun: |
||||
oss: |
||||
enabled: false |
||||
expireSeconds: 1000 |
||||
# 下面几项均需在申请阿里云OSS后,根据自己的实际情况进行配置。 |
||||
endpoint: |
||||
accessKey: |
||||
secretKey: |
||||
bucketName: |
||||
|
||||
minio: |
||||
enabled: false |
||||
endpoint: |
||||
accessKey: |
||||
secretKey: |
||||
bucketName: |
||||
|
||||
# 暴露监控端点 |
||||
management: |
||||
metrics: |
||||
tags: |
||||
application: ${spring.application.name} |
||||
endpoints: |
||||
web: |
||||
exposure: |
||||
include: '*' |
||||
jmx: |
||||
exposure: |
||||
include: '*' |
||||
endpoint: |
||||
# 与中间件相关的健康详情也会被展示 |
||||
health: |
||||
show-details: always |
||||
configprops: |
||||
# 在/actuator/configprops中,所有包含password的配置,将用 * 隐藏。 |
||||
# 如果不想隐藏任何配置项的值,可以直接使用如下被注释的空值。 |
||||
# keys-to-sanitize: |
||||
keys-to-sanitize: password |
||||
server: |
||||
base-path: "/" |
||||
|
||||
# 存储session数据的Redis,所有服务均需要,因此放到公共配置中。 |
||||
# 根据实际情况,该Redis也可以用于存储其他数据。 |
||||
common-redis: |
||||
# redisson的配置。每个服务可以自己的配置文件中覆盖此选项。 |
||||
redisson: |
||||
# 如果该值为false,系统将不会创建RedissionClient的bean。 |
||||
enabled: true |
||||
# mode的可用值为,single/cluster/sentinel/master-slave |
||||
mode: single |
||||
# single: 单机模式 |
||||
# address: redis://localhost:6379 |
||||
# cluster: 集群模式 |
||||
# 每个节点逗号分隔,同时每个节点前必须以redis://开头。 |
||||
# address: redis://localhost:6379,redis://localhost:6378,... |
||||
# sentinel: |
||||
# 每个节点逗号分隔,同时每个节点前必须以redis://开头。 |
||||
# address: redis://localhost:6379,redis://localhost:6378,... |
||||
# master-slave: |
||||
# 每个节点逗号分隔,第一个为主节点,其余为从节点。同时每个节点前必须以redis://开头。 |
||||
# address: redis://localhost:6379,redis://localhost:6378,... |
||||
address: redis://192.168.100.25:6379 |
||||
# 链接超时,单位毫秒。 |
||||
timeout: 6000 |
||||
# 单位毫秒。分布式锁的超时检测时长。 |
||||
# 如果一次锁内操作超该毫秒数,或在释放锁之前异常退出,Redis会在该时长之后主动删除该锁使用的key。 |
||||
lockWatchdogTimeout: 60000 |
||||
# redis 密码,空可以不填。 |
||||
password: |
||||
database: 0 |
||||
pool: |
||||
# 连接池数量。 |
||||
poolSize: 20 |
||||
# 连接池中最小空闲数量。 |
||||
minIdle: 5 |
||||
@ -0,0 +1,355 @@
@@ -0,0 +1,355 @@
|
||||
logging: |
||||
level: |
||||
# 这里设置的日志级别优先于logback-spring.xml文件Loggers中的日志级别。 |
||||
apelet: info |
||||
config: classpath:logback-spring.xml |
||||
|
||||
server: |
||||
port: 8075 |
||||
|
||||
application: |
||||
# Jwt令牌加密的签名值。该值的长度要超过10个字符(过短会报错)。 |
||||
tokenSigningKey: tenantadmin-signing-key-signing-key |
||||
# Jwt令牌在Http Header中的键名称。 |
||||
tokenHeaderKey: Authorization |
||||
# Jwt令牌刷新后在Http Header中的键名称。 |
||||
refreshedTokenHeaderKey: RefreshedToken |
||||
# Jwt令牌过期时间(毫秒)。 |
||||
expiration: 72000000 |
||||
# 初始化密码。 |
||||
defaultUserPassword: 123456 |
||||
# 缺省的文件上传根目录。 |
||||
uploadFileBaseDir: ./zz-resource/upload-files/tenant-admin |
||||
# 跨域的IP(http://192.168.10.10:8086)白名单列表,多个IP之间逗号分隔(* 表示全部信任,空白表示禁用跨域信任)。 |
||||
credentialIpList: "*" |
||||
# 同步租户公共管理数据到消息队列的主题,再由同步消费者服务将数据同步到目标数据库中。 |
||||
tenantSyncTopic: TenantSync |
||||
# Session会话和用户和权限在Redis中的过期时间(秒)。 |
||||
sessionExpiredSeconds: 86400 |
||||
# Session的数据权限信息在Redis中的过期时间(秒)。 |
||||
dataPermExpiredSeconds: 86460 |
||||
# 是否排他登录。 |
||||
excludeLogin: false |
||||
common-eas: |
||||
urlPrefix: /tenantadmin/eas |
||||
common-ext: |
||||
urlPrefix: /tenantadmin/commonext |
||||
common-mobile: |
||||
# 注意不要以反斜杠(/)结尾。 |
||||
urlPrefix: /tenantadmin/mobile |
||||
# 在线表单业务数据上传资源路径 |
||||
uploadFileBaseDir: ./zz-resource/upload-files/mobile |
||||
common-online: |
||||
# 注意不要以反斜杠(/)结尾。 |
||||
urlPrefix: /tenantadmin/online |
||||
# 打印接口的路径,不要以反斜杠(/)结尾。 |
||||
printUrlPath: /tenantadmin/report/reportPrint/print |
||||
# 在线表单业务数据上传资源路径 |
||||
uploadFileBaseDir: ./zz-resource/upload-files/online |
||||
# 如果为false,在线表单模块中所有Controller接口将不能使用。 |
||||
operationEnabled: true |
||||
# 1: minio 2: aliyun-oss 3: qcloud-cos。 |
||||
distributeStoreType: 2 |
||||
# 调用render接口时候,是否打开一级缓存加速。 |
||||
enableRenderCache: false |
||||
# 脱敏字段的掩码字符,只能为单个字符。 |
||||
maskChar: '*' |
||||
# 下面的url列表,请保持反斜杠(/)结尾。 |
||||
viewUrlList: |
||||
- ${common-online.urlPrefix}/onlineOperation/viewByDatasourceId/ |
||||
- ${common-online.urlPrefix}/onlineOperation/viewByOneToManyRelationId/ |
||||
- ${common-online.urlPrefix}/onlineOperation/listByDatasourceId/ |
||||
- ${common-online.urlPrefix}/onlineOperation/listByOneToManyRelationId/ |
||||
- ${common-online.urlPrefix}/onlineOperation/exportByDatasourceId/ |
||||
- ${common-online.urlPrefix}/onlineOperation/exportByOneToManyRelationId/ |
||||
- ${common-online.urlPrefix}/onlineOperation/downloadDatasource/ |
||||
- ${common-online.urlPrefix}/onlineOperation/downloadOneToManyRelation/ |
||||
- ${common-online.urlPrefix}/onlineOperation/print/ |
||||
editUrlList: |
||||
- ${common-online.urlPrefix}/onlineOperation/addDatasource/ |
||||
- ${common-online.urlPrefix}/onlineOperation/addOneToManyRelation/ |
||||
- ${common-online.urlPrefix}/onlineOperation/updateDatasource/ |
||||
- ${common-online.urlPrefix}/onlineOperation/updateOneToManyRelation/ |
||||
- ${common-online.urlPrefix}/onlineOperation/deleteDatasource/ |
||||
- ${common-online.urlPrefix}/onlineOperation/deleteOneToManyRelation/ |
||||
- ${common-online.urlPrefix}/onlineOperation/deleteBatchDatasource/ |
||||
- ${common-online.urlPrefix}/onlineOperation/deleteBatchOneToManyRelation/ |
||||
- ${common-online.urlPrefix}/onlineOperation/uploadDatasource/ |
||||
- ${common-online.urlPrefix}/onlineOperation/uploadOneToManyRelation/ |
||||
|
||||
common-flow: |
||||
# 请慎重修改urlPrefix的缺省配置,注意不要以反斜杠(/)结尾。如必须修改其他路径,请同步修改数据库脚本。 |
||||
urlPrefix: /tenantadmin/flow |
||||
# 如果为false,流程模块的所有Controller中的接口将不能使用。 |
||||
operationEnabled: true |
||||
|
||||
common-report: |
||||
# 注意不要以反斜杠(/)结尾。 |
||||
urlPrefix: /tenantadmin/report |
||||
# 如果为false,报表模块的所有Controller中的接口将不能使用。 |
||||
operationEnabled: true |
||||
# 该配置项仅当打印模板中,打印图片字段时,才会需要。 |
||||
# 这里的url配置只是一个示例,并不能保证开箱即用,代码示例和说明可参考common-report模块 |
||||
# example包内的ReportExampleController中的代码和详细说明。 |
||||
imageDownloadUrl: "http://localhost:8083/tenantadmin/report/example/downloadDirectly" |
||||
# 该配置用于报表部分的数据权限过滤功能。 |
||||
# 当前数据权限需要获取指定部门Ids的所有下级子部门Ids的时候,会调用该接口。 |
||||
dataPermAllChildrenDeptIdUrl: "http://localhost:8083/tenantadmin/upms/sysDept/listAllChildDeptIdByParentIds" |
||||
# 当前服务是否为可视化后台服务。 |
||||
isVisualization: false |
||||
# 下面的url列表,请保持反斜杠(/)结尾。 |
||||
viewUrlList: |
||||
- ${common-report.urlPrefix}/reportOperation/listData/ |
||||
|
||||
datasync: |
||||
producer: |
||||
# upms服务作为生产者,需要打开该选项。 |
||||
enabled: true |
||||
# Redisson 分布式锁的名称。 |
||||
# 当存在多个发送者实例时,需要分布式锁控制只有一个实例可以执行消息流水读取并发送到MQ的操作。 |
||||
distributeLockName: tenant-datasync |
||||
# 每次读取生产者消息流水的等待时间间隔,单位分钟。 |
||||
fetchTransIntervalMins: 2 |
||||
# 每次读取生产者消息流水的记录数量。 |
||||
fetchCount: 10 |
||||
|
||||
rocketmq: |
||||
name-server: 127.0.0.1:9876 |
||||
producer: |
||||
group: apelet_saas |
||||
|
||||
datafilter: |
||||
tenant: |
||||
enabled: false |
||||
dataperm: |
||||
enabled: true |
||||
# 在拼接数据权限过滤的SQL时,我们会用到sys_dept_relation表,该表的前缀由此配置项指定。 |
||||
# 如果没有前缀,请使用 "" 。 |
||||
deptRelationTablePrefix: xy_ |
||||
# 是否在每次执行数据权限查询过滤时,都要进行菜单Id和URL之间的越权验证。 |
||||
enableMenuPermVerify: true |
||||
|
||||
sequence: |
||||
# 是否使用基于美团Leaf的分布式Id生成器。 |
||||
advanceIdGenerator: true |
||||
# 多个zk服务之间逗号分隔。 |
||||
zkAddress: localhost:2181 |
||||
# 与本机的ip一起构成zk中标识不同服务实例的key值。 |
||||
idPort: 18083 |
||||
# zk中生成WorkNode的路径。不同的业务可以使用不同的路径,以免冲突。 |
||||
zkPath: apelet |
||||
|
||||
common-log: |
||||
# 操作日志配置,对应配置文件common-log/OperationLogProperties.java |
||||
operation-log: |
||||
enabled: false |
||||
kafkaTopic: SysOperationLog-TenantAdmin |
||||
|
||||
mybatis-plus: |
||||
mapper-locations: classpath:apelet/tenantadmin/tenant/dao/mapper/*Mapper.xml,apelet/tenantadmin/upms/dao/mapper/*Mapper.xml,apelet/common/report/dao/mapper/*Mapper.xml,apelet/common/datasync/dao/mapper/*Mapper.xml,apelet/common/log/dao/mapper/*Mapper.xml,apelet/common/flow/dao/mapper/*Mapper.xml,apelet/common/online/dao/mapper/*Mapper.xml,apelet/common/generator/dao/mapper/*Mapper.xml,apelet/common/botp/dao/mapper/*Mapper.xml,apelet/tenantadmin/app/dao/mapper/*Mapper.xml,apelet/common/mobile/dao/mapper/*Mapper.xml,apelet/common/eas/dao/mapper/*Mapper.xml,classpath:mapper/*Mapper.xml |
||||
type-aliases-package: apelet.tenantadmin.*.model,apelet.common.report.model,apelet.common.datasync.model,apelet.common.log.model,apelet.common.flow.model,apelet.common.online.model,apelet.common.mobile.model,apelet.common.eas.model |
||||
global-config: |
||||
db-config: |
||||
logic-delete-value: -1 |
||||
logic-not-delete-value: 1 |
||||
|
||||
# 自动分页的配置 |
||||
pagehelper: |
||||
helperDialect: mysql |
||||
reasonable: true |
||||
supportMethodsArguments: false |
||||
params: count=countSql |
||||
|
||||
aj: |
||||
captcha: |
||||
# 缓存local/redis.. |
||||
cache-type: redis |
||||
# 验证码类型default两种都实例化。 |
||||
type: default |
||||
# 右下角水印文字(我的水印) |
||||
water-mark: 我的水印 |
||||
#点选字体样式 默认Font.BOLD |
||||
font-style: 1 |
||||
#点选字体字体大小 |
||||
font-size: 25 |
||||
# 支持项目路径,以classpath:开头,取resource目录下路径,例:classpath:images/jigsaw |
||||
jigsaw: classpath:images/jigsaw |
||||
# 支持项目路径,以classpath:开头,取resource目录下路径,例:classpath:images/pic-click |
||||
pic-click: classpath:images/pic-click |
||||
history-data-clear-enable: false |
||||
# 接口请求次数一分钟限制是否开启 true|false |
||||
req-frequency-limit-enable: false |
||||
# 验证失败5次,get接口锁定 |
||||
req-get-lock-limit: 5 |
||||
# 验证失败后,锁定时间间隔,s |
||||
req-get-lock-seconds: 360 |
||||
# get接口一分钟内请求数限制 |
||||
req-get-minute-limit: 30 |
||||
# check接口一分钟内请求数限制 |
||||
req-check-minute-limit: 30 |
||||
# verify接口一分钟内请求数限制 |
||||
req-verify-minute-limit: 60 |
||||
# 校验滑动拼图允许误差偏移量(默认5像素) |
||||
slip-offset: 5 |
||||
# aes加密坐标开启或者禁用(true|false) |
||||
aes-status: true |
||||
# 滑动干扰项(0/1/2) |
||||
interference-options: 2 |
||||
|
||||
# 开发数据库相关配置 |
||||
spring: |
||||
datasource: |
||||
type: com.alibaba.druid.pool.DruidDataSource |
||||
druid: |
||||
# 租户管理数据源。 |
||||
tenant-admin: |
||||
url: jdbc:mysql://192.168.100.35:3306/apelet_admin_xk?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai |
||||
username: root |
||||
password: X7&9p8L2@6z4K7!8 |
||||
# 租户业务平台的通用业务数据,如字典、在线表单、流程、报表的定义等数据。 |
||||
tenant-common: |
||||
url: jdbc:mysql://192.168.100.35:3306/apelet_admin_xk?characterEncoding=utf8&useSSL=true&serverTimezone=Asia/Shanghai |
||||
username: root |
||||
password: X7&9p8L2@6z4K7!8 |
||||
# 默认生成的操作日志数据源配置。 |
||||
operation-log: |
||||
url: jdbc:mysql://192.168.100.35:3306/apelet_admin_xk?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai |
||||
username: root |
||||
password: X7&9p8L2@6z4K7!8 |
||||
driverClassName: com.mysql.cj.jdbc.Driver |
||||
name: tenant-admin |
||||
initialSize: 10 |
||||
minIdle: 10 |
||||
maxActive: 50 |
||||
maxWait: 60000 |
||||
timeBetweenEvictionRunsMillis: 60000 |
||||
minEvictableIdleTimeMillis: 300000 |
||||
poolPreparedStatements: true |
||||
maxPoolPreparedStatementPerConnectionSize: 20 |
||||
maxOpenPreparedStatements: 20 |
||||
validationQuery: SELECT 'x' |
||||
testWhileIdle: true |
||||
testOnBorrow: false |
||||
testOnReturn: false |
||||
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 |
||||
filters: stat,wall |
||||
useGlobalDataSourceStat: true |
||||
web-stat-filter: |
||||
enabled: true |
||||
url-pattern: "/*" |
||||
exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*,/actuator/*,/ai/*" |
||||
stat-view-servlet: |
||||
enabled: true |
||||
urlPattern: /druid/* |
||||
resetEnable: true |
||||
eas: |
||||
config: |
||||
easurl: |
||||
userName: |
||||
password: 1 |
||||
dcName: |
||||
dbType: 2 |
||||
ltpaToken: C:\work\company\project\plat\orm\apelet_saas\common\common-eas\src\main\resources\LtpaToken.properties |
||||
#easurl: https://ehr.jztweb.com |
||||
#userName: SSQadmin |
||||
#password: SSQadmin |
||||
#dcName: EHR_001 |
||||
#dbType: 2 |
||||
#ltpaToken: C:\work\company\project\plat\orm\apelet_saas\common\common-eas\src\main\resources\LtpaToken_zs.properties |
||||
hubCloud: |
||||
#测试 |
||||
#secret: RXmdRE0lf5ngGfLJSSj4 |
||||
#appId: 501019058 |
||||
secret: |
||||
appId: |
||||
dingTalk: |
||||
secret: 95gAMy_7nWGqRwFbt_kLzRpI9BqKhB7LM3SPgpcvV8Q7s0Az_PMei_yRxWE6LmhV |
||||
project: |
||||
server: |
||||
url: https://jgzy-api.nbjgcloud.com |
||||
sync: |
||||
url: http://10.22.98.37:9999 |
||||
loginUrl: /sys/login |
||||
loginName: nbjg |
||||
loginPassword: NBjj123456 |
||||
userUrl: |
||||
projectUrl: |
||||
ai: |
||||
dify: |
||||
baseUrl: |
||||
datasetKey: |
||||
appKey: |
||||
appUrl: |
||||
pandoc: |
||||
panurl: pandoc |
||||
genurl: |
||||
templateFileName: sample-orgin-change.docx |
||||
luaFileName: parse-html.lua |
||||
preUrl: |
||||
mineru: |
||||
workspace: |
||||
batchfile: |
||||
timeout: 10000 |
||||
baseurl: http://192.168.77.6:8000 |
||||
pdfurl: /file_parse |
||||
mdSaveDir: /app/work/pdf |
||||
cqurl: |
||||
config: |
||||
url: |
||||
gys: |
||||
#client_id: xy_mobile |
||||
#client_secret: asdasdnsandlkadnlkas!@#@!3WWW |
||||
#username: sunquan |
||||
client_id: |
||||
client_secret: |
||||
username: |
||||
accountId: |
||||
|
||||
msg: |
||||
notice: |
||||
# 是否启用定时消息提醒(true启用,false禁用) |
||||
enabled: true |
||||
|
||||
# 定时任务cron表达式,控制提醒执行频率 |
||||
# 默认值:0 0 * * * ? (每小时整点执行) |
||||
# 常用示例: |
||||
# 0 0 * * * ? - 每小时执行一次 |
||||
# 0 0 */2 * * ? - 每2小时执行一次 |
||||
# 0 0 9 * * ? - 每天上午9点执行 |
||||
# 0 0 9,18 * * ? - 每天上午9点和下午6点执行 |
||||
# 0 */30 * * * ? - 每30分钟执行一次 |
||||
cron: "0 0 * * * ?" |
||||
|
||||
# 默认提醒消息内容 |
||||
default-message: hello |
||||
|
||||
# ==================================== |
||||
# 短信通知配置 |
||||
# ==================================== |
||||
sms: |
||||
# 是否启用短信通知 |
||||
enabled: false |
||||
# 短信API地址(根据实际短信服务商配置) |
||||
api-url: https://sms.example.com/send |
||||
# 短信API密钥 |
||||
api-key: your-sms-api-key |
||||
# 短信签名 |
||||
sign: "【您的签名】" |
||||
|
||||
# ==================================== |
||||
# 邮箱通知配置 |
||||
# ==================================== |
||||
email: |
||||
# 是否启用邮箱通知 |
||||
enabled: true |
||||
# SMTP服务器地址 |
||||
smtp-host: smtp.qq.com |
||||
# SMTP端口(25或465或587) |
||||
smtp-port: 465 |
||||
# 发件人邮箱地址 |
||||
from-email: 287962685@qq.com |
||||
# 发件人密码或授权码 |
||||
password: evccxezdmdnpbhbh |
||||
# 是否启用SSL(如果使用465端口,建议启用SSL) |
||||
ssl-enabled: true |
||||
|
||||
@ -0,0 +1,55 @@
@@ -0,0 +1,55 @@
|
||||
package apelet.common.flow.dto; |
||||
|
||||
import com.alibaba.fastjson.JSONObject; |
||||
import io.swagger.v3.oas.annotations.media.Schema; |
||||
import lombok.Data; |
||||
|
||||
import javax.validation.constraints.NotBlank; |
||||
import javax.validation.constraints.NotNull; |
||||
|
||||
/** |
||||
* 批量提交流程任务 |
||||
* |
||||
* @author guifc |
||||
* @date 2023-08-04 |
||||
*/ |
||||
@Schema(description = "批量提交流程任务") |
||||
@Data |
||||
public class FlowTaskDto { |
||||
|
||||
/** |
||||
* 流程标识 |
||||
*/ |
||||
@Schema(description = "流程标识") |
||||
@NotNull(message = "数据验证失败,任务的流程标识不能为空!") |
||||
private String processInstanceId; |
||||
|
||||
/** |
||||
* 流程任务ID |
||||
*/ |
||||
@Schema(description = "流程任务ID") |
||||
@NotBlank(message = "数据验证失败,任务ID不能为空!") |
||||
private String taskId; |
||||
|
||||
/** |
||||
* 流程任务公共变量数据 |
||||
*/ |
||||
@Schema(description = "流程任务公共变量数据") |
||||
private JSONObject taskVariableData; |
||||
/** |
||||
* 流程审批相关公共主表数据 |
||||
*/ |
||||
@Schema(description = "流程审批相关公共主表数据") |
||||
private JSONObject masterData; |
||||
/** |
||||
* 流程审批相关公共从表数据 |
||||
*/ |
||||
@Schema(description = "流程审批相关公共从表数据") |
||||
private JSONObject slaveData; |
||||
/** |
||||
* 传阅公共数据,格式为type和id,type的值参考FlowConstant中的常量值 |
||||
*/ |
||||
@Schema(description = "传阅公共数据,格式为type和id,type的值参考FlowConstant中的常量值") |
||||
private JSONObject copyData; |
||||
|
||||
} |
||||
@ -0,0 +1,76 @@
@@ -0,0 +1,76 @@
|
||||
package apelet.common.flow.listener; |
||||
|
||||
import apelet.common.core.util.ApplicationContextHolder; |
||||
import apelet.common.flow.model.FlowTaskExt; |
||||
import apelet.common.flow.service.FlowTaskExtService; |
||||
import apelet.common.flow.util.BaseFlowIdentityExtHelper; |
||||
import apelet.common.flow.util.FlowCustomExtFactory; |
||||
import cn.hutool.core.collection.CollUtil; |
||||
import cn.hutool.core.util.StrUtil; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.flowable.engine.delegate.DelegateExecution; |
||||
import org.flowable.engine.delegate.ExecutionListener; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
import java.util.Set; |
||||
|
||||
/** |
||||
* 多实例会签节点审批人监听器。 |
||||
* 当多实例节点没有通过前置"会签"按钮写入 assigneeList 时, |
||||
* 自动从 zz_flow_task_ext.candidate_usernames 中读取审批人列表。 |
||||
* |
||||
* @author guifc |
||||
* @date 2025-07-29 |
||||
*/ |
||||
@Slf4j |
||||
public class MultiInstanceAssigneeListener implements ExecutionListener { |
||||
|
||||
private final transient FlowTaskExtService flowTaskExtService = ApplicationContextHolder.getBean(FlowTaskExtService.class); |
||||
|
||||
private final transient FlowCustomExtFactory flowCustomExtFactory = ApplicationContextHolder.getBean(FlowCustomExtFactory.class); |
||||
|
||||
|
||||
@Override |
||||
public void notify(DelegateExecution execution) { |
||||
// 如果 assigneeList 已经存在(由前置节点的会签按钮写入),则不覆盖
|
||||
if (execution.hasVariable("assigneeList")) { |
||||
return; |
||||
} |
||||
FlowTaskExt flowTaskExt = flowTaskExtService.getByProcessDefinitionIdAndTaskId( |
||||
execution.getProcessDefinitionId(), execution.getCurrentActivityId()); |
||||
if (flowTaskExt == null) { |
||||
return; |
||||
} |
||||
List<String> candidateUsernames = new ArrayList<>(); |
||||
if (StrUtil.isNotBlank(flowTaskExt.getCandidateUsernames())) { |
||||
candidateUsernames.addAll(StrUtil.split(flowTaskExt.getCandidateUsernames(), ",")); |
||||
} else { |
||||
BaseFlowIdentityExtHelper extHelper = flowCustomExtFactory.getFlowIdentityExtHelper(); |
||||
if (extHelper == null) { |
||||
return; |
||||
} |
||||
// 先看部门是否存在,通过部门id 去取用户登录名
|
||||
if (StrUtil.isNotBlank(flowTaskExt.getDeptIds())) { |
||||
Set<String> deptUsernameSet = extHelper.getUsernameListByDeptIds(CollUtil.newHashSet(StrUtil.split(flowTaskExt.getDeptIds(), ","))); |
||||
if (CollUtil.isNotEmpty(deptUsernameSet)) { |
||||
candidateUsernames.addAll(deptUsernameSet); |
||||
} |
||||
} |
||||
// 再看角色是否存在,通过角色id 去取用户登录名
|
||||
if (StrUtil.isNotBlank(flowTaskExt.getRoleIds())) { |
||||
Set<String> roleUsernameSet = extHelper.getUsernameListByRoleIds(CollUtil.newHashSet(StrUtil.split(flowTaskExt.getRoleIds(), ","))); |
||||
if (CollUtil.isNotEmpty(roleUsernameSet)) { |
||||
candidateUsernames.addAll(roleUsernameSet); |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
if (CollUtil.isEmpty(candidateUsernames)) { |
||||
return; |
||||
} |
||||
execution.setVariable("assigneeList", candidateUsernames); |
||||
log.info("MultiInstanceAssigneeListener: set assigneeList from candidate_usernames = {}", candidateUsernames); |
||||
} |
||||
} |
||||
@ -1,282 +0,0 @@
@@ -1,282 +0,0 @@
|
||||
package apelet.common.online.abstractplugin; |
||||
|
||||
import apelet.common.core.constant.AppDeviceType; |
||||
import apelet.common.core.object.ObjectCollection; |
||||
import apelet.common.core.object.ObjectValue; |
||||
import apelet.common.core.util.ApplicationContextHolder; |
||||
import apelet.common.online.model.constant.AttributeEnum; |
||||
import apelet.common.online.service.impl.OnlineFormServiceImpl; |
||||
import apelet.common.online.util.Jexl3Util; |
||||
import cn.hutool.core.util.StrUtil; |
||||
import com.alibaba.fastjson.JSONArray; |
||||
import com.alibaba.fastjson.JSONObject; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.apache.commons.lang3.StringUtils; |
||||
|
||||
import java.util.*; |
||||
import java.util.stream.Collectors; |
||||
|
||||
@Slf4j |
||||
public class MyInterfaceRulesExecute extends ExecutePluginParent { |
||||
|
||||
private OnlineFormServiceImpl onlineFormService; |
||||
private Jexl3Util jexl3Util; |
||||
|
||||
public MyInterfaceRulesExecute() { |
||||
onlineFormService = ApplicationContextHolder.getBean("onlineFormService"); |
||||
jexl3Util = ApplicationContextHolder.getBean("jexl3Util"); |
||||
} |
||||
|
||||
@Override |
||||
public void formCreated(String widgetVariableName, ObjectValue objectValue) { |
||||
Map values = objectValue.getValues(); |
||||
List<Map<String, Object>> conditional = (List) values.get("conditional"); |
||||
conditional("加载", widgetVariableName, conditional, objectValue); |
||||
List<Map<String, Object>> business = (List) values.get("business"); |
||||
conditional("加载", widgetVariableName, business, objectValue); |
||||
} |
||||
|
||||
@Override |
||||
public void change(String widgetVariableName, ObjectValue objectValue) { |
||||
Map values = objectValue.getValues(); |
||||
List<Map<String, Object>> conditional = (List) values.get("conditional"); |
||||
conditional("值改变", widgetVariableName, conditional, objectValue); |
||||
List<Map<String, Object>> business = (List) values.get("business"); |
||||
conditional("值改变", widgetVariableName, business, objectValue); |
||||
} |
||||
|
||||
void conditional(String type, String widgetVariableName, List<Map<String, Object>> isEstablish, ObjectValue objectValue) { |
||||
if (isEstablish == null || objectValue == null) { |
||||
return; |
||||
} |
||||
for (Map<String, Object> obj : isEstablish) { |
||||
List<JSONObject> allList = (List<JSONObject>) obj.get("allList"); |
||||
List<JSONObject> condition; |
||||
if ((boolean) obj.get("establish")) { |
||||
condition = allList.stream().filter(f -> "1".equals(f.get("condition").toString())).collect(Collectors.toList()); |
||||
} else { |
||||
condition = allList.stream().filter(f -> "2".equals(f.get("condition").toString())).collect(Collectors.toList()); |
||||
} |
||||
//业务规则
|
||||
d(type, widgetVariableName, condition, objectValue); |
||||
|
||||
// 条件规则
|
||||
b(type, widgetVariableName, condition); |
||||
} |
||||
} |
||||
|
||||
void d(String pluginType, String widgetVariableName, List<JSONObject> jsonArray, ObjectValue objectValue) { |
||||
for (JSONObject obj : jsonArray) { |
||||
String checkList = obj.getJSONArray("checkList").toString(); |
||||
if (checkList.contains(pluginType)) { |
||||
String type = obj.get("type").toString(); |
||||
JSONArray treeList = obj.getJSONArray("treeList"); |
||||
switch (type) { |
||||
case "1": { //计算定义公式的值并填写到指定列
|
||||
String expressionTransl = obj.getString("expressionTranslation"); |
||||
List<String> split = Arrays.asList(expressionTransl.split("=")); |
||||
String fun = ""; |
||||
for (String s : split) { |
||||
s = s.trim(); |
||||
if (s.contains("$")) { |
||||
fun = s; |
||||
} |
||||
} |
||||
if (StringUtils.isEmpty(fun)) { |
||||
String tag = split.get(0); |
||||
String src = split.get(1); |
||||
if (src.contains(objectValue.getTableName())) { |
||||
List<String> list = Arrays.asList(tag.split(" ")); |
||||
list.forEach(f -> { |
||||
|
||||
}); |
||||
} else { |
||||
List<String> list = Arrays.asList(tag.split("\\.")); |
||||
super.setWidgetAttribute(list.get(list.size() - 1), AttributeEnum.VALUE_CHANGE, src); |
||||
} |
||||
} else { |
||||
fun = jexl3Util.getMySplit("\\$(.*?)\\$", fun); |
||||
List<String> funParam = Arrays.asList(jexl3Util.getMySplit("\\((.*?)\\)", fun).split(",")); |
||||
Object[] objects = jexl3Util.getFunParam(funParam, objectValue); |
||||
Object o = onlineFormService.executeFunction(obj, fun, objects); |
||||
List<String> list = Arrays.asList(split.get(0).split("\\.")); |
||||
super.setWidgetAttribute(list.get(list.size() - 1).trim(), AttributeEnum.VALUE_CHANGE, o); |
||||
} |
||||
break; |
||||
} |
||||
case "2": { //携带基础资料属性到指定列
|
||||
JSONArray array = obj.getJSONArray("specifyJiList"); |
||||
JSONObject keyNameObj = (JSONObject) objectValue.get("keyNameObj"); |
||||
for (Object o : array) { |
||||
JSONObject o2 = (JSONObject) o; |
||||
String[] sourceFields = o2.get("sourceFields").toString().split("\\."); |
||||
String[] targetFields = o2.get("targetFields").toString().split("\\."); |
||||
if (sourceFields[0].equals(sourceFields[1])) { |
||||
ObjectValue o1 = (ObjectValue) objectValue.get(sourceFields[2]); |
||||
Object o11 = o1.get("id"); |
||||
if (o11 != null) { |
||||
ObjectValue next1 = jexl3Util.getObjectValue(o1.get("id"), o1.getTableName()); |
||||
Object values = next1.get(sourceFields[3]); |
||||
super.setWidgetAttribute(StrUtil.toCamelCase(targetFields[1]), AttributeEnum.VALUE_CHANGE, values); |
||||
} |
||||
} else { |
||||
ObjectCollection o3 = (ObjectCollection) objectValue.get(sourceFields[1]); |
||||
Iterator iterator = o3.iterator(); |
||||
String rowKeySubset = getDto().getRowKeySubset(); |
||||
Set<String> targetSet = new HashSet<String>(); |
||||
if (rowKeySubset != null && !rowKeySubset.equals("")) { |
||||
String[] rowKeySubsetAry = rowKeySubset.split(","); |
||||
Collections.addAll(targetSet, rowKeySubsetAry); |
||||
|
||||
} |
||||
while (iterator.hasNext()) { |
||||
ObjectValue next = (ObjectValue) iterator.next(); |
||||
String deviceType = AppDeviceType.getDeviceType(); |
||||
if ( |
||||
(StringUtils.isEmpty(rowKeySubset) && next.get("rowkey") != null) || //分录弹出界面新增时,rowkey 为空,所以为空时,为当前编辑数据
|
||||
(next.get("rowkey") != null && !targetSet.contains(next.get("rowkey"))) //rowKey相等时,为当前编辑数据
|
||||
) { |
||||
continue; |
||||
} |
||||
ObjectValue o4 = (ObjectValue) next.get(sourceFields[2]); |
||||
if (o4 == null) { |
||||
return; |
||||
} |
||||
o4 = jexl3Util.getObjectValue(o4.get("id"), o4.getTableName()); |
||||
Object values = o4.get(sourceFields[3]); |
||||
Map rowChangeMap = (Map) objectValue.get("rowChange"); |
||||
if (rowChangeMap == null) {//为空是弹出界面编辑,不为空行内编辑
|
||||
super.setWidgetAttribute(StrUtil.toCamelCase(targetFields[1]), AttributeEnum.VALUE_CHANGE, values); |
||||
} else { |
||||
super.setWidgetAttributeByEntry(widgetVariableName, targetFields[1], AttributeEnum.VALUE_CHANGE, values, |
||||
next.getString("rowkey")); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
break; |
||||
} |
||||
case "3": { //设置当前编辑字段值到指定字段
|
||||
JSONArray array = obj.getJSONArray("specifyList"); |
||||
Object v = ""; |
||||
for (Object o : array) { |
||||
JSONObject o2 = (JSONObject) o; |
||||
String sourceFields = (String) o2.get("sourceFields"); |
||||
List<String> list = Arrays.asList(sourceFields.split("\\.")); |
||||
String filed = list.get(list.size() - 1); |
||||
String table = list.get(list.size() - 2); |
||||
if (table.equals(objectValue.getTableName())) { |
||||
v = objectValue.get(filed); |
||||
} else { |
||||
ObjectCollection o1 = (ObjectCollection) objectValue.get(table); |
||||
Iterator iterator = o1.iterator(); |
||||
while (iterator.hasNext()) { |
||||
ObjectValue next = (ObjectValue) iterator.next(); |
||||
v = next.get(String.valueOf(filed)); |
||||
} |
||||
} |
||||
String targetFields = (String) o2.get("targetFields"); |
||||
List<String> list1 = Arrays.asList(targetFields.split("\\.")); |
||||
super.setWidgetAttribute(list1.get(list1.size() - 1), AttributeEnum.VALUE_CHANGE, v); |
||||
} |
||||
break; |
||||
} |
||||
case "4": { //清除指定字段值
|
||||
for (Object o : treeList) { |
||||
JSONObject jsonObject = (JSONObject) o; |
||||
if (jsonObject.containsKey("zxName")) { |
||||
String[] split = jsonObject.getString("zxName").split("\\."); |
||||
if (split[0].equals(split[1])) { |
||||
String name = jsonObject.get("label").toString(); |
||||
super.setWidgetAttribute(name, AttributeEnum.VALUE_CHANGE, ""); |
||||
} else { |
||||
String tab = jexl3Util.getMySplit("\\((.*?)\\)", split[1]); |
||||
JSONObject keyNameObj = (JSONObject) objectValue.get("keyNameObj"); |
||||
String tabId = (String) keyNameObj.getJSONArray(tab).get(0); |
||||
ObjectCollection collection = (ObjectCollection) objectValue.get(tab); |
||||
Iterator iterator = collection.iterator(); |
||||
while (iterator.hasNext()) { |
||||
ObjectValue next = (ObjectValue) iterator.next(); |
||||
super.setWidgetAttributeByEntry(tabId, jsonObject.get("label").toString(), AttributeEnum.VALUE_CHANGE, "", next.getString("rowkey")); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
void b(String pluginType, String widgetVariableName, List<JSONObject> jsonArray) { |
||||
for (JSONObject obj : jsonArray) { |
||||
String checkList = obj.getJSONArray("checkList").toString(); |
||||
if (checkList.contains(pluginType)) { |
||||
String type = obj.get("type").toString(); |
||||
JSONArray treeList = obj.getJSONArray("treeList"); |
||||
JSONArray objects = new JSONArray(); |
||||
switch (type) { |
||||
case "5": { //锁定控件
|
||||
for (Object o : treeList) { |
||||
JSONObject jsonObject = (JSONObject) o; |
||||
String name = jsonObject.get("variableName").toString(); |
||||
if (jsonObject.containsKey("btnId")) { |
||||
String showName = jsonObject.get("showName").toString(); |
||||
String parentVariableName = jsonObject.get("parentVariableName").toString(); |
||||
JSONObject json = new JSONObject(); |
||||
json.put("name", showName); |
||||
json.put("disable", true); |
||||
objects.add(json); |
||||
super.setWidgetAttribute(parentVariableName, AttributeEnum.CHANGEOPERATE, objects); |
||||
} else { |
||||
super.setWidgetAttribute(name, AttributeEnum.DISABLED, true); |
||||
} |
||||
} |
||||
break; |
||||
} |
||||
case "6": { //解锁控件
|
||||
for (Object o : treeList) { |
||||
JSONObject jsonObject = (JSONObject) o; |
||||
String name = jsonObject.get("variableName").toString(); |
||||
super.setWidgetAttribute(name, AttributeEnum.DISABLED, false); |
||||
} |
||||
break; |
||||
} |
||||
case "7": { //显示控件
|
||||
for (Object o : treeList) { |
||||
JSONObject jsonObject = (JSONObject) o; |
||||
String name = jsonObject.get("variableName").toString(); |
||||
super.setWidgetAttribute(name, AttributeEnum.SHOW, true); |
||||
} |
||||
break; |
||||
} |
||||
case "8": { //隐藏控件
|
||||
for (Object o : treeList) { |
||||
JSONObject jsonObject = (JSONObject) o; |
||||
String name = jsonObject.get("variableName").toString(); |
||||
super.setWidgetAttribute(name, AttributeEnum.SHOW, false); |
||||
} |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void beforeSelector(String widgetVariableName, ObjectValue objectValue) { |
||||
super.beforeSelector(widgetVariableName, objectValue); |
||||
} |
||||
|
||||
@Override |
||||
public void entryEditComplete(String widgetVariableName, ObjectValue objectValue) { |
||||
super.entryEditComplete(widgetVariableName, objectValue); |
||||
} |
||||
|
||||
public String removeSymbols(String input) { |
||||
// 使用正则表达式替换所有非字母数字字符为空字符串
|
||||
return input.replaceAll("_", "").toLowerCase(); |
||||
} |
||||
} |
||||
@ -0,0 +1,186 @@
@@ -0,0 +1,186 @@
|
||||
package apelet.common.online.controller; |
||||
|
||||
import apelet.common.core.annotation.MyRequestBody; |
||||
import apelet.common.core.constant.ErrorCodeEnum; |
||||
import apelet.common.core.object.CallResult; |
||||
import apelet.common.core.object.MyOrderParam; |
||||
import apelet.common.core.object.MyPageParam; |
||||
import apelet.common.core.object.ResponseResult; |
||||
import apelet.common.core.util.MyCommonUtil; |
||||
import apelet.common.core.util.MyPageUtil; |
||||
import apelet.common.core.validator.UpdateGroup; |
||||
import apelet.common.log.annotation.OperationLog; |
||||
import apelet.common.log.model.constant.SysOperationLogType; |
||||
import apelet.common.online.model.PermissionDataRuleSchema; |
||||
import apelet.common.online.service.IPermissionDataRuleSchemaService; |
||||
import com.github.pagehelper.page.PageMethod; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.web.bind.annotation.*; |
||||
|
||||
import javax.validation.groups.Default; |
||||
import java.util.Date; |
||||
import java.util.List; |
||||
import java.util.Objects; |
||||
|
||||
/** |
||||
* 权限数据规则方案表 控制器。 |
||||
* |
||||
* @author chenchuchuan |
||||
* @date 2026-07-15 |
||||
*/ |
||||
@RestController |
||||
@RequestMapping("${common-online.urlPrefix}/permissionDataRuleSchema") |
||||
public class PermissionDataRuleSchemaController { |
||||
|
||||
@Autowired |
||||
private IPermissionDataRuleSchemaService service; |
||||
|
||||
// ========== 新增 ==========
|
||||
/** |
||||
* 新增数据规则方案。 |
||||
* |
||||
* @param schema 新增的规则方案对象。 |
||||
* @return 新增后的规则方案对象。 |
||||
*/ |
||||
@OperationLog(type = SysOperationLogType.ADD) |
||||
@PostMapping("/add") |
||||
public ResponseResult<?> add(@RequestBody PermissionDataRuleSchema schema) { |
||||
schema.setCreateTime(new Date()); |
||||
// 1. 基础字段校验(@NotBlank / @NotNull)
|
||||
String errorMessage = MyCommonUtil.getModelValidationError(schema); |
||||
if (errorMessage != null) { |
||||
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); |
||||
} |
||||
// 2. 业务校验:formId → 主表/单表,并填充 tableType
|
||||
CallResult result = service.verifyRelatedData(schema); |
||||
if (!result.isSuccess()) { |
||||
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, result.getErrorMessage()); |
||||
} |
||||
// 3. 保存
|
||||
service.saveNew(schema); |
||||
return ResponseResult.success(schema); |
||||
} |
||||
|
||||
// ========== 编辑(含启用/禁用) ==========
|
||||
/** |
||||
* 更新数据规则方案(含启用/禁用)。 |
||||
* |
||||
* @param schema 更新的规则方案对象。 |
||||
* @return 应答结果。 |
||||
*/ |
||||
@OperationLog(type = SysOperationLogType.UPDATE) |
||||
@PostMapping("/update") |
||||
public ResponseResult<?> update(@RequestBody PermissionDataRuleSchema schema) { |
||||
// 1. 基础校验(需校验 id 非空 + 其他必填字段)
|
||||
String errorMessage = MyCommonUtil.getModelValidationError(schema, Default.class, UpdateGroup.class); |
||||
if (errorMessage != null) { |
||||
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); |
||||
} |
||||
// 2. 检查数据是否存在
|
||||
PermissionDataRuleSchema original = service.getById(schema.getId()); |
||||
if (original == null) { |
||||
return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, |
||||
"数据验证失败,当前规则方案不存在,请刷新后重试!"); |
||||
} |
||||
// 3. 业务校验:formId → 主表/单表,填充 tableType
|
||||
CallResult result = service.verifyRelatedData(schema); |
||||
if (!result.isSuccess()) { |
||||
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, result.getErrorMessage()); |
||||
} |
||||
// 4. 更新
|
||||
schema.setUpdateTime(new Date()); |
||||
if (!service.updateById(schema)) { |
||||
return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST); |
||||
} |
||||
return ResponseResult.success(); |
||||
} |
||||
|
||||
// ========== 删除(启用状态禁止删) ==========
|
||||
/** |
||||
* 删除数据规则方案(已启用的规则不允许删除)。 |
||||
* |
||||
* @param id 主键ID。 |
||||
* @return 应答结果。 |
||||
*/ |
||||
@OperationLog(type = SysOperationLogType.DELETE) |
||||
@PostMapping("/delete") |
||||
public ResponseResult<?> delete(@MyRequestBody Long id) { |
||||
if (MyCommonUtil.existBlankArgument(id)) { |
||||
return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST); |
||||
} |
||||
PermissionDataRuleSchema schema = service.getById(id); |
||||
if (schema == null) { |
||||
return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST); |
||||
} |
||||
// 启用状态不允许删除
|
||||
if (Objects.equals(schema.getStatus(), 1)) { |
||||
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, |
||||
"该规则方案已启用,请先禁用后再删除!"); |
||||
} |
||||
if (!service.removeById(id)) { |
||||
return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST); |
||||
} |
||||
return ResponseResult.success(); |
||||
} |
||||
|
||||
// ========== 分页列表 ==========
|
||||
/** |
||||
* 分页查询数据规则方案列表。 |
||||
* |
||||
* @param filter 过滤条件(支持 ruleCode/ruleName/status)。 |
||||
* @param orderParam 排序参数。 |
||||
* @param pageParam 分页参数。 |
||||
* @return 分页结果。 |
||||
*/ |
||||
@PostMapping("/list") |
||||
public ResponseResult<?> list( |
||||
@MyRequestBody PermissionDataRuleSchema filter, |
||||
@MyRequestBody MyOrderParam orderParam, |
||||
@MyRequestBody MyPageParam pageParam) { |
||||
if (pageParam != null) { |
||||
PageMethod.startPage(pageParam.getPageNum(), pageParam.getPageSize()); |
||||
} |
||||
if (filter == null){ |
||||
filter = new PermissionDataRuleSchema(); |
||||
} |
||||
List<PermissionDataRuleSchema> list = service.getListByFilter(filter, MyOrderParam.buildOrderBy(orderParam, PermissionDataRuleSchema.class)); |
||||
return ResponseResult.success(MyPageUtil.makeResponseData(list, PermissionDataRuleSchema.INSTANCE)); |
||||
} |
||||
|
||||
// ========== 根据 formId 查询启用的规则(运行时用) ==========
|
||||
/** |
||||
* 根据 formId 查询启用状态的数据规则方案(运行时用)。 |
||||
* |
||||
* @param formId 在线表ID。 |
||||
* @return 启用状态的规则方案列表。 |
||||
*/ |
||||
@GetMapping("/listByFormId") |
||||
public ResponseResult<List<PermissionDataRuleSchema>> listByFormId(@RequestParam Long formId) { |
||||
if (MyCommonUtil.existBlankArgument(formId)) { |
||||
return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST); |
||||
} |
||||
List<PermissionDataRuleSchema> list = service.listByFormId(formId); |
||||
return ResponseResult.success(list); |
||||
} |
||||
|
||||
// ========== 查看详情 ==========
|
||||
/** |
||||
* 查看数据规则方案详情。 |
||||
* |
||||
* @param id 主键ID。 |
||||
* @return 规则方案详情。 |
||||
*/ |
||||
@GetMapping("/view") |
||||
public ResponseResult<PermissionDataRuleSchema> view(@RequestParam Long id) { |
||||
if (MyCommonUtil.existBlankArgument(id)) { |
||||
return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST); |
||||
} |
||||
PermissionDataRuleSchema schema = service.getById(id); |
||||
if (schema == null) { |
||||
return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST); |
||||
} |
||||
// 根据配置的多个 formId 查询 zz_online_form,返回 formName
|
||||
service.fillFormNameList(schema); |
||||
return ResponseResult.success(schema); |
||||
} |
||||
} |
||||
@ -0,0 +1,119 @@
@@ -0,0 +1,119 @@
|
||||
package apelet.common.online.controller; |
||||
|
||||
import apelet.common.core.annotation.MyRequestBody; |
||||
import apelet.common.core.annotation.NoAuthInterface; |
||||
import apelet.common.core.constant.ErrorCodeEnum; |
||||
import apelet.common.core.object.*; |
||||
import apelet.common.core.util.MyCommonUtil; |
||||
import apelet.common.core.util.MyPageUtil; |
||||
import apelet.common.log.annotation.OperationLog; |
||||
import apelet.common.log.model.constant.SysOperationLogType; |
||||
import apelet.common.online.dto.RoleFormButtonDto; |
||||
import apelet.common.online.model.RoleFormButton; |
||||
import apelet.common.online.service.IRoleFormButtonService; |
||||
import com.github.pagehelper.page.PageMethod; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.web.bind.annotation.*; |
||||
|
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.Set; |
||||
|
||||
/** |
||||
* 角色-表单按钮权限控制器。 |
||||
* |
||||
* @author chenchuchuan |
||||
* @date 2026-07-17 |
||||
*/ |
||||
@RestController |
||||
@RequestMapping("${common-online.urlPrefix}/roleFormButton") |
||||
public class RoleFormButtonController { |
||||
|
||||
@Autowired |
||||
private IRoleFormButtonService service; |
||||
|
||||
/** |
||||
* 查询当前登录用户对指定表单的按钮权限(合并所有角色)。 |
||||
* 根据当前登录用户的所有角色,查询各角色在该表单的按钮配置,取并集返回。 |
||||
* |
||||
* @param formId 在线表单ID(必填)。 |
||||
* @return 包含 formId, buttonList[{name, checked}]。 |
||||
*/ |
||||
@GetMapping("/getButtonConfig") |
||||
@NoAuthInterface |
||||
public ResponseResult<Map<String, Set<String>>> getButtonConfig(@RequestParam(required = false) Long formId) { |
||||
return ResponseResult.success(service.getButtonConfigVo(formId)); |
||||
} |
||||
|
||||
/** |
||||
* 批量保存角色+表单的按钮权限配置(新增或更新)。 |
||||
* |
||||
* @param dtoList 列表,每个元素包含 roleId(必填), formId(必填), id(编辑时传), buttonList。 |
||||
* @return 应答结果。 |
||||
*/ |
||||
@OperationLog(type = SysOperationLogType.ADD) |
||||
@PostMapping("/saveButtonConfig") |
||||
public ResponseResult<Void> saveButtonConfig( |
||||
@MyRequestBody List<RoleFormButtonDto> dtoList) { |
||||
if (dtoList == null || dtoList.isEmpty()) { |
||||
return ResponseResult.error( |
||||
ErrorCodeEnum.ARGUMENT_NULL_EXIST, "数据验证失败,按钮配置列表不能为空!"); |
||||
} |
||||
CallResult result = service.batchSaveButtonConfig(dtoList); |
||||
if (!result.isSuccess()) { |
||||
return ResponseResult.error( |
||||
ErrorCodeEnum.DATA_VALIDATED_FAILED, result.getErrorMessage()); |
||||
} |
||||
return ResponseResult.success(); |
||||
} |
||||
|
||||
/** |
||||
* 根据角色ID查询该角色下所有绑定的按钮权限配置列表。 |
||||
* |
||||
* @param roleId 角色ID。 |
||||
* @return 按钮权限配置列表。 |
||||
*/ |
||||
@GetMapping("/listByRoleId") |
||||
public ResponseResult<List<RoleFormButton>> listByRoleId( |
||||
@RequestParam Long roleId) { |
||||
if (MyCommonUtil.existBlankArgument(roleId)) { |
||||
return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST); |
||||
} |
||||
return ResponseResult.success(service.listByRoleId(roleId)); |
||||
} |
||||
|
||||
/** |
||||
* 分页查询所有在线表单下的所有按钮。 |
||||
* 遍历全部在线表单,从每个表单的 widgetJson 中提取已开启的按钮。 |
||||
* 如果传了 roleId,则只返回该角色已配置的表单,并合并已保存的勾选状态。 |
||||
* |
||||
* @param roleId 角色ID(可选,用于过滤已配置的表单)。 |
||||
* @param orderParam 排序参数。 |
||||
* @param pageParam 分页参数。 |
||||
* @return 分页结果,每个元素包含 formId, formName, buttonList[{name, checked}]。 |
||||
*/ |
||||
@PostMapping("/listAllFormButtons") |
||||
public ResponseResult<MyPageData<Map<String, Object>>> listAllFormButtons( |
||||
@MyRequestBody Long roleId, |
||||
@MyRequestBody MyOrderParam orderParam, |
||||
@MyRequestBody MyPageParam pageParam) { |
||||
return ResponseResult.success(service.listAllFormButtons(roleId, orderParam, pageParam)); |
||||
} |
||||
|
||||
/** |
||||
* 分页查询按钮权限配置列表。 |
||||
*/ |
||||
@PostMapping("/list") |
||||
public ResponseResult<MyPageData<?>> list( |
||||
@MyRequestBody RoleFormButton filter, |
||||
@MyRequestBody MyOrderParam orderParam, |
||||
@MyRequestBody MyPageParam pageParam) { |
||||
if (pageParam != null) { |
||||
PageMethod.startPage(pageParam.getPageNum(), pageParam.getPageSize()); |
||||
} |
||||
List<RoleFormButton> list = service.getListByFilter( |
||||
filter, MyOrderParam.buildOrderBy(orderParam, RoleFormButton.class)); |
||||
return ResponseResult.success( |
||||
MyPageUtil.makeResponseData(list, RoleFormButton.INSTANCE)); |
||||
} |
||||
} |
||||
@ -0,0 +1,27 @@
@@ -0,0 +1,27 @@
|
||||
package apelet.common.online.dao; |
||||
|
||||
import apelet.common.core.base.dao.BaseDaoMapper; |
||||
import apelet.common.online.model.PermissionDataRuleSchema; |
||||
import org.apache.ibatis.annotations.Param; |
||||
|
||||
import java.util.List; |
||||
|
||||
/** |
||||
* 权限数据规则方案表 Mapper 接口。 |
||||
* |
||||
* @author chenchuchuan |
||||
* @date 2026-07-15 |
||||
*/ |
||||
public interface PermissionDataRuleSchemaMapper extends BaseDaoMapper<PermissionDataRuleSchema> { |
||||
|
||||
/** |
||||
* 分页条件查询(支持 rule_code/rule_name/status 过滤 + 排序)。 |
||||
* |
||||
* @param filter 过滤条件。 |
||||
* @param orderBy 排序语句。 |
||||
* @return 规则方案列表。 |
||||
*/ |
||||
List<PermissionDataRuleSchema> getListByFilter( |
||||
@Param("filter") PermissionDataRuleSchema filter, |
||||
@Param("orderBy") String orderBy); |
||||
} |
||||
@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
|
||||
package apelet.common.online.dao; |
||||
|
||||
import apelet.common.core.base.dao.BaseDaoMapper; |
||||
import apelet.common.online.model.RoleFormButton; |
||||
import org.apache.ibatis.annotations.Param; |
||||
|
||||
import java.util.List; |
||||
|
||||
/** |
||||
* 角色-表单按钮权限配置数据访问操作接口。 |
||||
* |
||||
* @author chenchuchuan |
||||
* @date 2026-07-17 |
||||
*/ |
||||
public interface RoleFormButtonMapper extends BaseDaoMapper<RoleFormButton> { |
||||
|
||||
/** |
||||
* 分页条件查询。 |
||||
*/ |
||||
List<RoleFormButton> getListByFilter( |
||||
@Param("filter") RoleFormButton filter, |
||||
@Param("orderBy") String orderBy); |
||||
} |
||||
@ -0,0 +1,13 @@
@@ -0,0 +1,13 @@
|
||||
package apelet.common.online.dao; |
||||
|
||||
import apelet.common.core.base.dao.BaseDaoMapper; |
||||
import apelet.common.online.model.RolePermissionSchema; |
||||
|
||||
/** |
||||
* 角色-数据规则方案关联表 Mapper 接口。 |
||||
* |
||||
* @author chenchuchuan |
||||
* @date 2026-08-05 |
||||
*/ |
||||
public interface RolePermissionSchemaMapper extends BaseDaoMapper<RolePermissionSchema> { |
||||
} |
||||
@ -0,0 +1,27 @@
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" |
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
||||
<mapper namespace="apelet.common.online.dao.PermissionDataRuleSchemaMapper"> |
||||
|
||||
<select id="getListByFilter" |
||||
resultType="apelet.common.online.model.PermissionDataRuleSchema"> |
||||
SELECT * FROM zz_permission_data_rule_schema |
||||
WHERE 1=1 |
||||
<if test="filter.ruleCode != null and filter.ruleCode != ''"> |
||||
AND rule_code LIKE CONCAT('%', #{filter.ruleCode}, '%') |
||||
</if> |
||||
<if test="filter.ruleName != null and filter.ruleName != ''"> |
||||
AND rule_name LIKE CONCAT('%', #{filter.ruleName}, '%') |
||||
</if> |
||||
<if test="filter.status != null"> |
||||
AND status = #{filter.status} |
||||
</if> |
||||
<if test="filter.ruleType != null"> |
||||
AND rule_type = #{filter.ruleType} |
||||
</if> |
||||
<if test="orderBy != null and orderBy != ''"> |
||||
ORDER BY ${orderBy} |
||||
</if> |
||||
</select> |
||||
|
||||
</mapper> |
||||
@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" |
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
||||
<mapper namespace="apelet.common.online.dao.RoleFormButtonMapper"> |
||||
|
||||
<select id="getListByFilter" |
||||
resultType="apelet.common.online.model.RoleFormButton"> |
||||
SELECT * FROM xy_sys_role_form_button |
||||
WHERE 1=1 |
||||
<if test="filter.roleId != null"> |
||||
AND role_id = #{filter.roleId} |
||||
</if> |
||||
<if test="filter.formId != null"> |
||||
AND form_id = #{filter.formId} |
||||
</if> |
||||
<if test="orderBy != null and orderBy != ''"> |
||||
ORDER BY ${orderBy} |
||||
</if> |
||||
</select> |
||||
|
||||
</mapper> |
||||
@ -0,0 +1,35 @@
@@ -0,0 +1,35 @@
|
||||
package apelet.common.online.dto; |
||||
|
||||
import lombok.Data; |
||||
|
||||
import java.util.List; |
||||
|
||||
/** |
||||
* 角色表单按钮权限保存入参。 |
||||
* |
||||
* @author chenchuchuan |
||||
* @date 2026-07-17 |
||||
*/ |
||||
@Data |
||||
public class RoleFormButtonDto { |
||||
|
||||
/** 角色ID(必填) */ |
||||
private Long roleId; |
||||
|
||||
/** 表单ID(必填) */ |
||||
private Long formId; |
||||
|
||||
/** 主键ID(编辑时传,新增时不传) */ |
||||
private Long id; |
||||
|
||||
/** 按钮勾选列表 */ |
||||
private List<ButtonItem> buttonList; |
||||
|
||||
@Data |
||||
public static class ButtonItem { |
||||
/** 按钮名称 */ |
||||
private String name; |
||||
/** 是否勾选 */ |
||||
private Boolean checked; |
||||
} |
||||
} |
||||
@ -0,0 +1,152 @@
@@ -0,0 +1,152 @@
|
||||
package apelet.common.online.model; |
||||
|
||||
import apelet.common.core.base.mapper.BaseModelMapper; |
||||
import apelet.common.core.base.model.BaseModel; |
||||
import apelet.common.core.validator.UpdateGroup; |
||||
import apelet.common.online.vo.PermissionDataRuleSchemaVo; |
||||
import com.baomidou.mybatisplus.annotation.TableField; |
||||
import com.baomidou.mybatisplus.annotation.TableId; |
||||
import com.baomidou.mybatisplus.annotation.TableName; |
||||
import lombok.Data; |
||||
import lombok.EqualsAndHashCode; |
||||
import org.mapstruct.Mapper; |
||||
import org.mapstruct.factory.Mappers; |
||||
|
||||
import javax.validation.constraints.NotBlank; |
||||
import javax.validation.constraints.NotNull; |
||||
import java.io.Serializable; |
||||
import java.util.Date; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* 权限数据规则方案表 实体对象。 |
||||
* |
||||
* @author chenchuchuan |
||||
* @date 2026-07-15 |
||||
*/ |
||||
@EqualsAndHashCode(callSuper = true) |
||||
@Data |
||||
@TableName("zz_permission_data_rule_schema") |
||||
public class PermissionDataRuleSchema extends BaseModel implements Serializable { |
||||
|
||||
private static final long serialVersionUID = 1L; |
||||
|
||||
/** |
||||
* 主键ID。 |
||||
*/ |
||||
@TableId(value = "id") |
||||
@NotNull(message = "数据验证失败,id不能为空!", groups = {UpdateGroup.class}) |
||||
private Long id; |
||||
|
||||
/** |
||||
* onl_form_head 表 Id(数据规则所属主表)。 |
||||
*/ |
||||
@TableField(value = "form_head_id") |
||||
@NotNull(message = "数据验证失败,form_head_id不能为空!") |
||||
private Long formHeadId; |
||||
|
||||
/** |
||||
* 在线表单id集合(zz_online_form.form_id),多个逗号分隔,为空表示对绑定的全部在线表单生效。 |
||||
*/ |
||||
@TableField(value = "form_id_list") |
||||
private String formIdList; |
||||
|
||||
/** |
||||
* 表单元数据编码。 |
||||
*/ |
||||
@TableField(value = "meta_code") |
||||
// @NotBlank(message = "数据验证失败,表单元数据编码不能为空!")
|
||||
private String metaCode; |
||||
|
||||
/** |
||||
* 主表类型:1-主表,2-单表(由后端自动填充,前端无需传入)。 |
||||
*/ |
||||
@TableField(value = "table_type") |
||||
private Integer tableType; |
||||
|
||||
/** |
||||
* 规则编码。 |
||||
*/ |
||||
@TableField(value = "rule_code") |
||||
@NotBlank(message = "数据验证失败,规则编码不能为空!") |
||||
private String ruleCode; |
||||
|
||||
/** |
||||
* 规则方案名称。 |
||||
*/ |
||||
@TableField(value = "rule_name") |
||||
@NotBlank(message = "数据验证失败,规则方案名称不能为空!") |
||||
private String ruleName; |
||||
|
||||
/** |
||||
* 条件表达式(JSON)。 |
||||
*/ |
||||
@TableField(value = "condition_expression") |
||||
@NotBlank(message = "数据验证失败,条件表达式不能为空!") |
||||
private String conditionExpression; |
||||
|
||||
/** |
||||
* 元数据展示名称 |
||||
*/ |
||||
@TableField(value = "meta_show_name") |
||||
private String metaShowName; |
||||
|
||||
/** |
||||
* 规则类型:1-列表权限,2-基础数据权限。 |
||||
* @see apelet.common.online.model.constant.DataRuleType |
||||
*/ |
||||
@TableField(value = "rule_type") |
||||
private Integer ruleType; |
||||
|
||||
/** |
||||
* 状态:1-启用,0-禁用。 |
||||
*/ |
||||
@TableField(value = "status") |
||||
private Integer status; |
||||
|
||||
/** |
||||
* 备注。 |
||||
*/ |
||||
@TableField(value = "remark") |
||||
private String remark; |
||||
|
||||
/** |
||||
* 创建时间。 |
||||
*/ |
||||
@TableField(value = "create_time") |
||||
private Date createTime; |
||||
|
||||
/** |
||||
* 创建者。 |
||||
*/ |
||||
@TableField(value = "create_user_id") |
||||
private Long createUserId; |
||||
|
||||
/** |
||||
* 更新时间。 |
||||
*/ |
||||
@TableField(value = "update_time") |
||||
private Date updateTime; |
||||
|
||||
/** |
||||
* 更新者。 |
||||
*/ |
||||
@TableField(value = "update_user_id") |
||||
private Long updateUserId; |
||||
|
||||
/** |
||||
* 绑定的在线表单列表(formId + formName),详情接口返回用,非持久化。 |
||||
*/ |
||||
@TableField(exist = false) |
||||
private List<Map<String, Object>> formList; |
||||
|
||||
// ========== MapStruct ==========
|
||||
@Mapper |
||||
public interface PermissionDataRuleSchemaModelMapper |
||||
extends BaseModelMapper<PermissionDataRuleSchemaVo, PermissionDataRuleSchema> { |
||||
} |
||||
|
||||
public static final PermissionDataRuleSchemaModelMapper INSTANCE = |
||||
Mappers.getMapper(PermissionDataRuleSchemaModelMapper.class); |
||||
} |
||||
@ -0,0 +1,69 @@
@@ -0,0 +1,69 @@
|
||||
package apelet.common.online.model; |
||||
|
||||
import apelet.common.core.base.mapper.BaseModelMapper; |
||||
import apelet.common.core.base.model.BaseModel; |
||||
import apelet.common.core.validator.UpdateGroup; |
||||
import apelet.common.online.vo.RoleFormButtonVo; |
||||
import com.baomidou.mybatisplus.annotation.TableField; |
||||
import com.baomidou.mybatisplus.annotation.TableId; |
||||
import com.baomidou.mybatisplus.annotation.TableName; |
||||
import lombok.Data; |
||||
import lombok.EqualsAndHashCode; |
||||
import org.mapstruct.Mapper; |
||||
import org.mapstruct.factory.Mappers; |
||||
|
||||
import javax.validation.constraints.NotNull; |
||||
import java.io.Serializable; |
||||
import java.util.Date; |
||||
|
||||
/** |
||||
* 角色-表单按钮权限配置实体对象。 |
||||
* |
||||
* @author chenchuchuan |
||||
* @date 2026-07-17 |
||||
*/ |
||||
@Data |
||||
@EqualsAndHashCode(callSuper = true) |
||||
@TableName("xy_sys_role_form_button") |
||||
public class RoleFormButton extends BaseModel implements Serializable { |
||||
|
||||
private static final long serialVersionUID = 1L; |
||||
|
||||
@TableId(value = "id") |
||||
@NotNull(message = "数据验证失败,id不能为空!", groups = {UpdateGroup.class}) |
||||
private Long id; |
||||
|
||||
/** 角色ID */ |
||||
@TableField(value = "role_id") |
||||
@NotNull(message = "数据验证失败,角色ID不能为空!") |
||||
private Long roleId; |
||||
|
||||
/** 在线表单ID */ |
||||
@TableField(value = "form_id") |
||||
@NotNull(message = "数据验证失败,表单ID不能为空!") |
||||
private Long formId; |
||||
|
||||
/** 按钮配置JSON */ |
||||
@TableField(value = "button_config") |
||||
private String buttonConfig; |
||||
|
||||
@TableField(value = "create_time") |
||||
private Date createTime; |
||||
|
||||
@TableField(value = "create_user_id") |
||||
private Long createUserId; |
||||
|
||||
@TableField(value = "update_time") |
||||
private Date updateTime; |
||||
|
||||
@TableField(value = "update_user_id") |
||||
private Long updateUserId; |
||||
|
||||
@Mapper |
||||
public interface RoleFormButtonModelMapper |
||||
extends BaseModelMapper<RoleFormButtonVo, RoleFormButton> { |
||||
} |
||||
|
||||
public static final RoleFormButtonModelMapper INSTANCE = |
||||
Mappers.getMapper(RoleFormButtonModelMapper.class); |
||||
} |
||||
@ -0,0 +1,28 @@
@@ -0,0 +1,28 @@
|
||||
package apelet.common.online.model; |
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField; |
||||
import com.baomidou.mybatisplus.annotation.TableName; |
||||
import lombok.Data; |
||||
|
||||
/** |
||||
* 角色-数据规则方案关联实体对象(role_id + schema_id 联合主键)。 |
||||
* |
||||
* @author chenchuchuan |
||||
* @date 2026-08-05 |
||||
*/ |
||||
@Data |
||||
@TableName(value = "zz_role_permission_schema") |
||||
public class RolePermissionSchema { |
||||
|
||||
/** |
||||
* 角色ID。 |
||||
*/ |
||||
@TableField(value = "role_id") |
||||
private Long roleId; |
||||
|
||||
/** |
||||
* 规则方案ID(关联 zz_permission_data_rule_schema.id)。 |
||||
*/ |
||||
@TableField(value = "schema_id") |
||||
private Long schemaId; |
||||
} |
||||
@ -0,0 +1,35 @@
@@ -0,0 +1,35 @@
|
||||
package apelet.common.online.model.constant; |
||||
|
||||
import java.util.HashMap; |
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* 数据权限规则类型常量。 |
||||
* |
||||
* @author chenchuchuan |
||||
* @date 2026-07-21 |
||||
*/ |
||||
public final class DataRuleType { |
||||
|
||||
/** |
||||
* 列表权限。 |
||||
*/ |
||||
public static final int LIST = 1; |
||||
/** |
||||
* 基础数据权限。 |
||||
*/ |
||||
public static final int BASIC_DATA = 2; |
||||
|
||||
private static final Map<Object, String> DICT_MAP = new HashMap<>(2); |
||||
static { |
||||
DICT_MAP.put(LIST, "列表权限"); |
||||
DICT_MAP.put(BASIC_DATA, "基础数据权限"); |
||||
} |
||||
|
||||
public static boolean isValid(Integer value) { |
||||
return value != null && DICT_MAP.containsKey(value); |
||||
} |
||||
|
||||
private DataRuleType() { |
||||
} |
||||
} |
||||
@ -0,0 +1,143 @@
@@ -0,0 +1,143 @@
|
||||
package apelet.common.online.rule; |
||||
|
||||
import apelet.common.core.object.ObjectValue; |
||||
import apelet.common.online.dto.OnlineEventPluginExecuteDto; |
||||
import apelet.common.online.model.OnlineForm; |
||||
import apelet.common.online.rule.action.RuleActionExecutor; |
||||
import apelet.common.online.rule.condition.ConditionEvaluator; |
||||
import apelet.common.online.rule.enums.RuleTypeEnum; |
||||
import apelet.common.online.rule.model.FormRule; |
||||
import apelet.common.online.rule.model.RuleAction; |
||||
import apelet.common.online.rule.model.RuleActionContext; |
||||
import apelet.common.online.rule.model.RuleEvalContext; |
||||
import apelet.common.online.rule.parser.FormRuleParser; |
||||
import com.alibaba.fastjson.JSONObject; |
||||
import org.apache.commons.lang3.StringUtils; |
||||
import org.springframework.stereotype.Component; |
||||
|
||||
import javax.annotation.Resource; |
||||
import java.util.ArrayList; |
||||
import java.util.Comparator; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* 规则引擎入口:解析 → 规则级一次求值 → 按 condition 选动作 → 事件匹配 → 路由执行器。 |
||||
*/ |
||||
@Component |
||||
public class FormRuleEngine { |
||||
|
||||
@Resource |
||||
private FormRuleParser parser; |
||||
@Resource |
||||
private ConditionEvaluator conditionEvaluator; |
||||
@Resource |
||||
private RuleActionExecutorRegistry registry; |
||||
|
||||
public List<Object> execute(OnlineForm form, ObjectValue ov, String eventCode, OnlineEventPluginExecuteDto dto) { |
||||
List<Object> result = new ArrayList<>(); |
||||
if (form == null || form.getWidgetJson() == null) { |
||||
return result; |
||||
} |
||||
for (FormRule rule : parser.parse(form.getWidgetJson())) { |
||||
executeRule(rule, form, ov, eventCode, dto, result); |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
/** |
||||
* 通用动作执行入口:给定单条规则的 JSON 串、字段值、规则来源类型, |
||||
* 走 解析 → 条件求值 → 按 condition 选动作 → 路由执行器 → 产出 Vo 全流程。 |
||||
* 通用入口不按事件过滤(eventCode 为 null,checkList 只作展示,不拦截执行)。 |
||||
* |
||||
* @param ruleJson 单条规则 JSON 串(businessRulesList / conditionTypeList 数组元素:expressionTranslation、allList、Enable 等) |
||||
* @param values 字段取值(key 为字段名或完整 表.表.字段 / 表.关联对象.字段) |
||||
* @param ruleType 规则来源类型(界面规则 / 业务规则) |
||||
*/ |
||||
public List<Object> executeAction(String ruleJson, Map<String, Object> values, RuleTypeEnum ruleType) { |
||||
if (StringUtils.isBlank(ruleJson)) { |
||||
return null; |
||||
} |
||||
FormRule rule = parser.parseSingle(JSONObject.parseObject(ruleJson), ruleType); |
||||
if (rule == null) { |
||||
return null; |
||||
} |
||||
return executeAction(rule, buildObjectValue(rule.getCondition().getExpression(), values)); |
||||
} |
||||
|
||||
public List<Object> executeAction(String ruleJson, ObjectValue values, RuleTypeEnum ruleType) { |
||||
if (StringUtils.isBlank(ruleJson)) { |
||||
return null; |
||||
} |
||||
FormRule rule = parser.parseSingle(JSONObject.parseObject(ruleJson), ruleType); |
||||
if (rule == null) { |
||||
return null; |
||||
} |
||||
return executeAction(rule, values); |
||||
} |
||||
|
||||
public List<Object> executeAction(FormRule rule, ObjectValue values) { |
||||
if (rule == null) { |
||||
return null; |
||||
} |
||||
List<Object> result = new ArrayList<>(); |
||||
executeRule(rule, null, values, null, new OnlineEventPluginExecuteDto(), result); |
||||
return result; |
||||
} |
||||
|
||||
|
||||
private void executeRule(FormRule rule, OnlineForm form, ObjectValue ov, String eventCode, |
||||
OnlineEventPluginExecuteDto dto, List<Object> result) { |
||||
if (!rule.isEnable()) { |
||||
return; |
||||
} |
||||
// 规则级:表达式只求值一次
|
||||
boolean establish = conditionEvaluator.evaluate(rule.getCondition(), new RuleEvalContext(ov, dto)); |
||||
// 规则内动作按 sort 升序执行(稳定排序,缺失/相同的保持数组顺序)
|
||||
rule.getActions().sort(Comparator.comparingInt(RuleAction::getSort)); |
||||
for (RuleAction action : rule.getActions()) { |
||||
// 成立 → condition=1;不成立 → condition=2
|
||||
if ((establish && action.getCondition() != 1) || (!establish && action.getCondition() != 2)) { |
||||
continue; |
||||
} |
||||
// executeAction 通用入口 eventCode 为 null:不按事件过滤,全部执行
|
||||
if (eventCode != null && !action.matchEvent(eventCode)) { |
||||
continue; |
||||
} |
||||
RuleActionExecutor executor = registry.get(action.getActionType()); |
||||
RuleActionContext ctx = new RuleActionContext(form, ov, dto, action, eventCode, |
||||
dto == null ? null : dto.getEventWidgetVariableName(), rule.getCondition().getFunctionForm()); |
||||
Object value = executor.resolveValue(ctx); |
||||
result.addAll(executor.buildResult(ctx, value)); |
||||
} |
||||
} |
||||
|
||||
private ObjectValue buildObjectValue(String expression, Map<String, Object> values) { |
||||
ObjectValue objectValue = new ObjectValue(extractTableName(expression)); |
||||
if (values != null) { |
||||
for (Map.Entry<String, Object> entry : values.entrySet()) { |
||||
objectValue.put(lastSegment(entry.getKey()), entry.getValue()); |
||||
} |
||||
} |
||||
return objectValue; |
||||
} |
||||
|
||||
/** |
||||
* 从表达式里取主表名:如 "t_flowtest001.t_flowtest001.amount = ..." → "t_flowtest001" |
||||
*/ |
||||
private String extractTableName(String expressionTranslation) { |
||||
if (StringUtils.isBlank(expressionTranslation)) return ""; |
||||
int eq = expressionTranslation.indexOf('='); |
||||
String tag = (eq < 0 ? expressionTranslation : expressionTranslation.substring(0, eq)).trim(); |
||||
int dot = tag.indexOf('.'); |
||||
return dot < 0 ? tag : tag.substring(0, dot); |
||||
} |
||||
|
||||
/** |
||||
* 取 key 末段:完整 表.表.字段 或字段名 → 字段名 |
||||
*/ |
||||
private String lastSegment(String key) { |
||||
int dot = key.lastIndexOf('.'); |
||||
return dot < 0 ? key : key.substring(dot + 1); |
||||
} |
||||
} |
||||
@ -0,0 +1,39 @@
@@ -0,0 +1,39 @@
|
||||
package apelet.common.online.rule; |
||||
|
||||
import apelet.common.online.rule.action.RuleActionExecutor; |
||||
import apelet.common.online.rule.enums.RuleActionTypeEnum; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.springframework.stereotype.Component; |
||||
|
||||
import java.util.HashMap; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* 动作类型 → 执行器 路由选择器。 |
||||
* 常量 map 在构造器注入时一次性初始化,运行期只读,无需反复构建。 |
||||
* 加新动作类型 = 加一个执行器,Spring 注入后自动注册,无需改这里。 |
||||
*/ |
||||
@Slf4j |
||||
@Component |
||||
public class RuleActionExecutorRegistry { |
||||
|
||||
/** 动作类型 → 执行器 常量映射,构造器初始化,运行期只读 */ |
||||
private final Map<RuleActionTypeEnum, RuleActionExecutor> executors = new HashMap<>(); |
||||
|
||||
public RuleActionExecutorRegistry(List<RuleActionExecutor> executorList) { |
||||
for (RuleActionExecutor executor : executorList) { |
||||
executors.put(executor.getActionType(), executor); |
||||
} |
||||
log.info("规则执行器初始化完成,共注册 {} 个动作类型:{}", executors.size(), executors.keySet()); |
||||
} |
||||
|
||||
/** 按动作类型路由执行器 */ |
||||
public RuleActionExecutor get(RuleActionTypeEnum actionType) { |
||||
RuleActionExecutor executor = executors.get(actionType); |
||||
if (executor == null) { |
||||
throw new IllegalArgumentException("未注册的动作类型: " + actionType); |
||||
} |
||||
return executor; |
||||
} |
||||
} |
||||
@ -0,0 +1,105 @@
@@ -0,0 +1,105 @@
|
||||
package apelet.common.online.rule.action; |
||||
|
||||
import apelet.common.core.object.ObjectCollection; |
||||
import apelet.common.core.object.ObjectValue; |
||||
import apelet.common.online.model.OnlineForm; |
||||
import apelet.common.online.model.constant.AttributeEnum; |
||||
import apelet.common.online.rule.model.RuleActionContext; |
||||
import apelet.common.online.util.WidgetJsonUtil; |
||||
import apelet.common.online.vo.OnlineEventPluginExecuteEntryVo; |
||||
import apelet.common.online.vo.OnlineEventPluginExecuteVo; |
||||
import com.alibaba.fastjson.JSONArray; |
||||
import com.alibaba.fastjson.JSONObject; |
||||
import org.apache.commons.lang3.StringUtils; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* 执行器基类:自带 Vo 产出助手(不复用 ExecutePluginParent,插件机制独立)。 |
||||
*/ |
||||
public abstract class AbstractRuleActionExecutor implements RuleActionExecutor { |
||||
|
||||
/** 默认取值:整表单(type5~8 无重活,直接用默认) */ |
||||
@Override |
||||
public Object resolveValue(RuleActionContext ctx) { |
||||
return ctx.getObjectValue(); |
||||
} |
||||
|
||||
protected OnlineEventPluginExecuteVo buildVo(String widgetVariableName, AttributeEnum type, Object value) { |
||||
OnlineEventPluginExecuteVo vo = new OnlineEventPluginExecuteVo(); |
||||
vo.setWidgetVariableName(widgetVariableName); |
||||
vo.setWidgetOperateType(type.getCode()); |
||||
vo.setWidgetOperateContent(value instanceof ObjectValue ? ((ObjectValue) value).getValues() : value); |
||||
return vo; |
||||
} |
||||
|
||||
protected OnlineEventPluginExecuteEntryVo buildEntryVo(String name, String col, AttributeEnum type, Object value, String rowNum) { |
||||
OnlineEventPluginExecuteEntryVo vo = new OnlineEventPluginExecuteEntryVo(); |
||||
vo.setWidgetVariableName(name); |
||||
vo.setEntriesTableColumnName(col); |
||||
vo.setWidgetOperateType(type.getCode()); |
||||
vo.setWidgetOperateContent(value); |
||||
vo.setRowNum(rowNum); |
||||
return vo; |
||||
} |
||||
|
||||
/** |
||||
* 列名 → 控件 variableName 列表:从表单 widgetJson 的 pc/mobile.widgetList 按 bindData.columnFieldName 反查。 |
||||
* 未配置映射的列名原样返回自身;一个列名对应多个控件(多个控件绑定同一列)时返回多个 variableName。 |
||||
*/ |
||||
protected List<String> resolveWidgetVariableNames(RuleActionContext ctx, String column) { |
||||
OnlineForm form = ctx.getForm(); |
||||
if (form != null && StringUtils.isNotBlank(form.getWidgetJson())) { |
||||
Map<String, List<String>> map = WidgetJsonUtil.collectColumnVariableNames(JSONObject.parseObject(form.getWidgetJson())); |
||||
List<String> names = map.get(column); |
||||
if (names != null && !names.isEmpty()) { |
||||
return names; |
||||
} |
||||
} |
||||
return Collections.singletonList(column); |
||||
} |
||||
|
||||
/** treeList 控件目标 → 一组普通指令(type 6/7/8 用) */ |
||||
protected List<OnlineEventPluginExecuteVo> buildFromTreeList(RuleActionContext ctx, AttributeEnum type, Object attrValue) { |
||||
List<OnlineEventPluginExecuteVo> out = new ArrayList<>(); |
||||
JSONArray treeList = ctx.getAction().getSource().getJSONArray("treeList"); |
||||
if (treeList == null) return out; |
||||
for (Object o : treeList) { |
||||
JSONObject json = (JSONObject) o; |
||||
out.add(buildVo(json.get("variableName").toString(), type, attrValue)); |
||||
} |
||||
return out; |
||||
} |
||||
|
||||
/** 分录 tabId:keyNameObj 反查(主表/已登记分录),缺失时兜底用事件触发控件名(分录内触发即分录表控件),均无则返回 null 不产出分录 Vo */ |
||||
protected String resolveEntryTabId(ObjectValue objectValue, String tableName, String eventWidgetVariableName) { |
||||
JSONObject keyNameObj = (JSONObject) objectValue.get("keyNameObj"); |
||||
if (keyNameObj != null) { |
||||
JSONArray tabIds = keyNameObj.getJSONArray(tableName); |
||||
if (tabIds != null && !tabIds.isEmpty()) { |
||||
return tabIds.get(0).toString(); |
||||
} |
||||
} |
||||
return eventWidgetVariableName; |
||||
} |
||||
|
||||
/** |
||||
* 从目标/源字段路径识别附表名:目标 主表.分录表.字段 / 分录表.字段 优先,源 主表.分录表.字段 兜底(附表内字段影响附表字段时目标随源); |
||||
* 主表目标(表.字段 / 表.表.字段)返回 null。 |
||||
*/ |
||||
protected String resolveEntryTable(ObjectValue objectValue, String[] targetParts, String[] sourceParts) { |
||||
if (targetParts.length >= 3 && !targetParts[0].equals(targetParts[1]) && objectValue.get(targetParts[1]) instanceof ObjectCollection) { |
||||
return targetParts[1]; |
||||
} |
||||
if (targetParts.length == 2 && objectValue.get(targetParts[0]) instanceof ObjectCollection) { |
||||
return targetParts[0]; |
||||
} |
||||
if (sourceParts.length >= 3 && !sourceParts[0].equals(sourceParts[1]) && objectValue.get(sourceParts[1]) instanceof ObjectCollection) { |
||||
return sourceParts[1]; |
||||
} |
||||
return null; |
||||
} |
||||
} |
||||
@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
|
||||
package apelet.common.online.rule.action; |
||||
|
||||
import apelet.common.online.rule.enums.RuleActionTypeEnum; |
||||
import apelet.common.online.rule.model.RuleActionContext; |
||||
import apelet.common.online.vo.OnlineEventPluginExecuteVo; |
||||
|
||||
import java.util.List; |
||||
|
||||
/** |
||||
* 规则动作执行器:一个动作类型一个执行器,三方法契约。 |
||||
*/ |
||||
public interface RuleActionExecutor { |
||||
/** ① 路由标识:本执行器负责的动作类型,Registry 靠它路由 */ |
||||
RuleActionTypeEnum getActionType(); |
||||
|
||||
/** ② 取值:读取执行所需的字段/数据(各动作类型差异最大) */ |
||||
Object resolveValue(RuleActionContext ctx); |
||||
|
||||
/** ③ 返回结果:产出前端指令 */ |
||||
List<OnlineEventPluginExecuteVo> buildResult(RuleActionContext ctx, Object value); |
||||
} |
||||
@ -0,0 +1,105 @@
@@ -0,0 +1,105 @@
|
||||
package apelet.common.online.rule.action.impl; |
||||
|
||||
import apelet.common.core.object.ObjectCollection; |
||||
import apelet.common.core.object.ObjectValue; |
||||
import apelet.common.online.model.constant.AttributeEnum; |
||||
import apelet.common.online.rule.action.AbstractRuleActionExecutor; |
||||
import apelet.common.online.rule.enums.RuleActionTypeEnum; |
||||
import apelet.common.online.rule.model.RuleActionContext; |
||||
import apelet.common.online.util.Jexl3Util; |
||||
import apelet.common.online.vo.OnlineEventPluginExecuteVo; |
||||
import com.alibaba.fastjson.JSONArray; |
||||
import com.alibaba.fastjson.JSONObject; |
||||
import org.apache.commons.lang3.StringUtils; |
||||
import org.springframework.stereotype.Component; |
||||
|
||||
import javax.annotation.Resource; |
||||
import java.util.*; |
||||
|
||||
/** |
||||
* type 2 携带基础资料:主表直接带出,分录按 rowKeySubset 只带当前编辑行(行内编辑带 rowNum)。 |
||||
*/ |
||||
@Component |
||||
public class BaseDataCarryExecutor extends AbstractRuleActionExecutor { |
||||
|
||||
@Resource |
||||
private Jexl3Util jexl3Util; |
||||
|
||||
@Override |
||||
public RuleActionTypeEnum getActionType() { |
||||
return RuleActionTypeEnum.BASE_DATA_CARRY; |
||||
} |
||||
|
||||
@Override |
||||
public Object resolveValue(RuleActionContext ctx) { |
||||
List<CarryValue> values = new ArrayList<>(); |
||||
ObjectValue objectValue = ctx.getObjectValue(); |
||||
JSONArray array = ctx.getAction().getSource().getJSONArray("specifyJiList"); |
||||
if (array == null) { |
||||
return values; |
||||
} |
||||
String rowKeySubset = ctx.getDto().getRowKeySubset(); |
||||
String rowKey = ctx.getDto().getRowKey(); |
||||
Set<String> targetSet = new HashSet<>(); |
||||
if (StringUtils.isNotEmpty(rowKeySubset)) { |
||||
Collections.addAll(targetSet, rowKeySubset.split(",")); |
||||
} |
||||
Map rowChangeMap = (Map) objectValue.get("rowChange"); |
||||
for (Object o : array) { |
||||
JSONObject o2 = (JSONObject) o; |
||||
String[] parts = o2.get("sourceFields").toString().split("\\."); |
||||
String[] targetParts = o2.get("targetFields").toString().split("\\."); |
||||
String targetField = targetParts[targetParts.length - 1]; |
||||
Object first = objectValue.get(parts[1]); |
||||
if (!parts[0].equals(parts[1]) && first instanceof ObjectCollection) { // 分录:主表.分录表.关联对象.字段
|
||||
Iterator iterator = ((ObjectCollection) first).iterator(); |
||||
while (iterator.hasNext()) { |
||||
ObjectValue next = (ObjectValue) iterator.next(); |
||||
String k = next.getString("rowkey"); |
||||
if (StringUtils.isNotEmpty(rowKeySubset)) { // 行内编辑:命中 subset 的行
|
||||
if (k == null || !targetSet.contains(k)) continue; |
||||
} else if (StringUtils.isNotEmpty(rowKey)) { // 分录编辑完成:命中当前行 rowKey
|
||||
if (!rowKey.equals(k)) continue; |
||||
} else { // 无过滤:跳过已保存行,只处理新增行
|
||||
if (k != null) continue; |
||||
} |
||||
String rowNum = rowChangeMap == null ? null : next.getString("rowkey"); // 行内编辑才带 rowNum
|
||||
Object value = jexl3Util.resolveFieldChain(next, parts, 2); |
||||
next.put(targetField, value); // 写回分录行,供后续规则条件求值
|
||||
values.add(new CarryValue(targetField, value, rowNum)); |
||||
} |
||||
} else { // 主表:表.关联对象.子字段 / 表.表.字段
|
||||
Object value = jexl3Util.resolveFieldChain(objectValue, parts, parts[0].equals(parts[1]) ? 2 : 1); |
||||
objectValue.put(targetField, value); // 写回主表,供后续规则条件求值
|
||||
values.add(new CarryValue(targetField, value, null)); |
||||
} |
||||
} |
||||
return values; |
||||
} |
||||
|
||||
@Override |
||||
public List<OnlineEventPluginExecuteVo> buildResult(RuleActionContext ctx, Object value) { |
||||
List<OnlineEventPluginExecuteVo> out = new ArrayList<>(); |
||||
for (CarryValue v : (List<CarryValue>) value) { |
||||
if (v.rowNum == null) { |
||||
for (String name : resolveWidgetVariableNames(ctx, v.targetField)) { |
||||
out.add(buildVo(name, AttributeEnum.VALUE_CHANGE, v.value)); |
||||
} |
||||
} else { |
||||
out.add(buildEntryVo(ctx.getEventWidgetVariableName(), v.targetField, AttributeEnum.VALUE_CHANGE, v.value, v.rowNum)); |
||||
} |
||||
} |
||||
return out; |
||||
} |
||||
|
||||
private static class CarryValue { |
||||
final String targetField; |
||||
final Object value; |
||||
final String rowNum; |
||||
CarryValue(String targetField, Object value, String rowNum) { |
||||
this.targetField = targetField; |
||||
this.value = value; |
||||
this.rowNum = rowNum; |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,97 @@
@@ -0,0 +1,97 @@
|
||||
package apelet.common.online.rule.action.impl; |
||||
|
||||
import apelet.common.core.object.ObjectCollection; |
||||
import apelet.common.core.object.ObjectValue; |
||||
import apelet.common.online.model.constant.AttributeEnum; |
||||
import apelet.common.online.rule.action.AbstractRuleActionExecutor; |
||||
import apelet.common.online.rule.enums.RuleActionTypeEnum; |
||||
import apelet.common.online.rule.model.RuleActionContext; |
||||
import apelet.common.online.util.Jexl3Util; |
||||
import apelet.common.online.vo.OnlineEventPluginExecuteVo; |
||||
import com.alibaba.fastjson.JSONArray; |
||||
import com.alibaba.fastjson.JSONObject; |
||||
import org.springframework.stereotype.Component; |
||||
|
||||
import javax.annotation.Resource; |
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
/** |
||||
* type 3 设置字段值:sourceFields 灵活解析(主表 表.字段 / 表.关联对象.子字段,分录取当前编辑行); |
||||
* 目标字段归属分录表时写当前编辑行并产分录 Vo,主表目标写主表对象。 |
||||
*/ |
||||
@Component |
||||
public class FieldAssignExecutor extends AbstractRuleActionExecutor { |
||||
|
||||
@Resource |
||||
private Jexl3Util jexl3Util; |
||||
|
||||
@Override |
||||
public RuleActionTypeEnum getActionType() { |
||||
return RuleActionTypeEnum.FIELD_ASSIGN; |
||||
} |
||||
|
||||
@Override |
||||
public Object resolveValue(RuleActionContext ctx) { |
||||
List<AssignValue> values = new ArrayList<>(); |
||||
ObjectValue objectValue = ctx.getObjectValue(); |
||||
JSONArray array = ctx.getAction().getSource().getJSONArray("specifyList"); |
||||
if (array == null) { |
||||
return values; |
||||
} |
||||
String rowKeySubset = ctx.getDto().getRowKeySubset(); |
||||
for (Object o : array) { |
||||
JSONObject o2 = (JSONObject) o; |
||||
String sourceFields = (String) o2.get("sourceFields"); |
||||
String targetFields = (String) o2.get("targetFields"); |
||||
String[] sourceParts = sourceFields.split("\\."); |
||||
String[] targetParts = targetFields.split("\\."); |
||||
String targetField = targetParts[targetParts.length - 1]; |
||||
Object v = jexl3Util.resolveRefValue(sourceFields, objectValue, rowKeySubset); |
||||
String entryTable = resolveEntryTable(objectValue, targetParts, sourceParts); |
||||
ObjectValue row = entryTable == null ? null : jexl3Util.currentRow((ObjectCollection) objectValue.get(entryTable), rowKeySubset, ctx.getDto().getRowKey()); |
||||
if (entryTable != null && row == null) { |
||||
continue; // 附表目标但表为空,跳过,避免误写主表
|
||||
} |
||||
if (row != null) { |
||||
row.put(targetField, v); // 附表目标:写回当前编辑行
|
||||
String tabId = resolveEntryTabId(objectValue, entryTable, ctx.getEventWidgetVariableName()); |
||||
if (tabId != null) { |
||||
values.add(new AssignValue(targetField, v, tabId, row.getString("rowkey"))); |
||||
} |
||||
} else { |
||||
objectValue.put(targetField, v); // 主表目标:写回表单对象,供后续规则条件求值
|
||||
values.add(new AssignValue(targetField, v, null, null)); |
||||
} |
||||
} |
||||
return values; |
||||
} |
||||
|
||||
@Override |
||||
public List<OnlineEventPluginExecuteVo> buildResult(RuleActionContext ctx, Object value) { |
||||
List<OnlineEventPluginExecuteVo> out = new ArrayList<>(); |
||||
for (AssignValue v : (List<AssignValue>) value) { |
||||
if (v.rowNum == null) { |
||||
for (String name : resolveWidgetVariableNames(ctx, v.targetField)) { |
||||
out.add(buildVo(name, AttributeEnum.VALUE_CHANGE, v.value)); |
||||
} |
||||
} else { |
||||
out.add(buildEntryVo(v.tabId, v.targetField, AttributeEnum.VALUE_CHANGE, v.value, v.rowNum)); |
||||
} |
||||
} |
||||
return out; |
||||
} |
||||
|
||||
private static class AssignValue { |
||||
final String targetField; |
||||
final Object value; |
||||
final String tabId; |
||||
final String rowNum; |
||||
AssignValue(String targetField, Object value, String tabId, String rowNum) { |
||||
this.targetField = targetField; |
||||
this.value = value; |
||||
this.tabId = tabId; |
||||
this.rowNum = rowNum; |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,97 @@
@@ -0,0 +1,97 @@
|
||||
package apelet.common.online.rule.action.impl; |
||||
|
||||
import apelet.common.core.object.ObjectCollection; |
||||
import apelet.common.core.object.ObjectValue; |
||||
import apelet.common.online.model.constant.AttributeEnum; |
||||
import apelet.common.online.rule.action.AbstractRuleActionExecutor; |
||||
import apelet.common.online.rule.enums.RuleActionTypeEnum; |
||||
import apelet.common.online.rule.model.RuleActionContext; |
||||
import apelet.common.online.util.Jexl3Util; |
||||
import apelet.common.online.vo.OnlineEventPluginExecuteVo; |
||||
import com.alibaba.fastjson.JSONArray; |
||||
import com.alibaba.fastjson.JSONObject; |
||||
import org.springframework.stereotype.Component; |
||||
|
||||
import javax.annotation.Resource; |
||||
import java.util.ArrayList; |
||||
import java.util.Iterator; |
||||
import java.util.List; |
||||
|
||||
/** |
||||
* type 4 清除字段值:主表直接清,分录从主表对象取集合后逐行清(空表跳过,tabId 经 keyNameObj 反查、缺失兜底事件控件)。 |
||||
*/ |
||||
@Component |
||||
public class FieldClearExecutor extends AbstractRuleActionExecutor { |
||||
|
||||
@Resource |
||||
private Jexl3Util jexl3Util; |
||||
|
||||
@Override |
||||
public RuleActionTypeEnum getActionType() { |
||||
return RuleActionTypeEnum.FIELD_CLEAR; |
||||
} |
||||
|
||||
@Override |
||||
public Object resolveValue(RuleActionContext ctx) { |
||||
List<ClearValue> values = new ArrayList<>(); |
||||
ObjectValue objectValue = ctx.getObjectValue(); |
||||
JSONArray treeList = ctx.getAction().getSource().getJSONArray("treeList"); |
||||
if (treeList == null) { |
||||
return values; |
||||
} |
||||
for (Object o : treeList) { |
||||
JSONObject jsonObject = (JSONObject) o; |
||||
if (jsonObject.containsKey("zxName")) { |
||||
String[] split = jsonObject.getString("zxName").split("\\."); |
||||
if (split[0].equals(split[1])) { // 主表字段
|
||||
String label = jsonObject.get("label").toString(); |
||||
objectValue.put(label, ""); // 写回清空,供后续规则条件求值
|
||||
values.add(new ClearValue(label, null, null)); |
||||
} else { // 分录字段:主表对象里取分录集合,清当前编辑行
|
||||
String tab = jexl3Util.getMySplit("\\((.*?)\\)", split[1]); |
||||
ObjectCollection collection = (ObjectCollection) objectValue.get(tab); // 从主表对象取分录集合(新增空分录表时为空)
|
||||
if (collection == null || collection.isEmpty()) { |
||||
continue; // 附表为空,无行可清
|
||||
} |
||||
ObjectValue current = jexl3Util.currentRow(collection, ctx.getDto().getRowKeySubset(), ctx.getDto().getRowKey()); |
||||
if (current == null) { |
||||
continue; // 无当前编辑行(表单加载等),跳过不清,避免误清其他行
|
||||
} |
||||
String label = jsonObject.get("label").toString(); |
||||
String tabId = resolveEntryTabId(objectValue, tab, ctx.getEventWidgetVariableName()); |
||||
current.put(label, ""); // 只清当前编辑行,避免连带清空其他行
|
||||
if (tabId != null) { |
||||
values.add(new ClearValue(label, tabId, current.getString("rowkey"))); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
return values; |
||||
} |
||||
|
||||
@Override |
||||
public List<OnlineEventPluginExecuteVo> buildResult(RuleActionContext ctx, Object value) { |
||||
List<OnlineEventPluginExecuteVo> out = new ArrayList<>(); |
||||
for (ClearValue v : (List<ClearValue>) value) { |
||||
if (v.rowNum == null) { |
||||
for (String name : resolveWidgetVariableNames(ctx, v.field)) { |
||||
out.add(buildVo(name, AttributeEnum.VALUE_CHANGE, "")); |
||||
} |
||||
} else { |
||||
out.add(buildEntryVo(v.tabId, v.field, AttributeEnum.VALUE_CHANGE, "", v.rowNum)); |
||||
} |
||||
} |
||||
return out; |
||||
} |
||||
|
||||
private static class ClearValue { |
||||
final String field; |
||||
final String tabId; |
||||
final String rowNum; |
||||
ClearValue(String field, String tabId, String rowNum) { |
||||
this.field = field; |
||||
this.tabId = tabId; |
||||
this.rowNum = rowNum; |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,162 @@
@@ -0,0 +1,162 @@
|
||||
package apelet.common.online.rule.action.impl; |
||||
|
||||
import apelet.common.core.object.ObjectCollection; |
||||
import apelet.common.core.object.ObjectValue; |
||||
import apelet.common.online.model.constant.AttributeEnum; |
||||
import apelet.common.online.rule.action.AbstractRuleActionExecutor; |
||||
import apelet.common.online.rule.condition.FunctionExecutor; |
||||
import apelet.common.online.rule.enums.RuleActionTypeEnum; |
||||
import apelet.common.online.rule.model.RuleActionContext; |
||||
import apelet.common.online.util.Jexl3Util; |
||||
import apelet.common.online.vo.OnlineEventPluginExecuteVo; |
||||
import org.springframework.stereotype.Component; |
||||
|
||||
import javax.annotation.Resource; |
||||
import java.math.BigDecimal; |
||||
import java.util.*; |
||||
|
||||
/** |
||||
* type 1 计算并赋值:拆 expressionTranslation,公式引用字段→算值、字面量直接赋、$fun$→函数; |
||||
* 目标字段归属分录表时写当前编辑行并产分录 Vo,主表目标写主表对象。 |
||||
*/ |
||||
@Component |
||||
public class FormulaAssignExecutor extends AbstractRuleActionExecutor { |
||||
|
||||
@Resource |
||||
private Jexl3Util jexl3Util; |
||||
@Resource |
||||
private FunctionExecutor functionExecutor; |
||||
|
||||
@Override |
||||
public RuleActionTypeEnum getActionType() { |
||||
return RuleActionTypeEnum.FORMULA_ASSIGN; |
||||
} |
||||
|
||||
@Override |
||||
public Object resolveValue(RuleActionContext ctx) { |
||||
String expressionTranslation = ctx.getAction().getSource().getString("expressionTranslation"); |
||||
if (expressionTranslation == null || !expressionTranslation.contains("=")) { |
||||
return null; |
||||
} |
||||
String[] split = expressionTranslation.split("="); |
||||
String tag = split[0].trim(); |
||||
String src = split[1].trim(); |
||||
ObjectValue objectValue = ctx.getObjectValue(); |
||||
String targetField = lastSegment(tag); |
||||
String rowKeySubset = ctx.getDto().getRowKeySubset(); |
||||
Object result; |
||||
if (src.contains("$")) { // 分支 C:$fun$ 函数赋值
|
||||
String fun = jexl3Util.getMySplit("\\$(.*?)\\$", src); |
||||
result = functionExecutor.execute(ctx.getFunctionForm(), fun, objectValue); |
||||
} else if (!src.contains(objectValue.getTableName())) { // 分支 B:字面量直接赋值
|
||||
result = parseLiteral(src); |
||||
} else { // 分支 A:公式引用字段,替换为实际值后求值(空值补 0)
|
||||
Map<String, Object> context = new HashMap<>(); |
||||
for (String ref : extractFieldRefs(src)) { |
||||
if (ref.contains(objectValue.getTableName()) && !context.containsKey(ref)) { |
||||
context.put(ref, resolveFormulaField(ref, objectValue, rowKeySubset)); |
||||
} |
||||
} |
||||
result = jexl3Util.expression(src, context); |
||||
} |
||||
String[] tagParts = tag.split("\\."); |
||||
List<String> srcRefs = extractFieldRefs(src); |
||||
String[] srcParts = srcRefs.isEmpty() ? new String[0] : srcRefs.get(0).split("\\."); |
||||
String entryTable = resolveEntryTable(objectValue, tagParts, srcParts); |
||||
ObjectValue row = entryTable == null ? null : jexl3Util.currentRow((ObjectCollection) objectValue.get(entryTable), rowKeySubset, ctx.getDto().getRowKey()); |
||||
if (entryTable != null && row == null) { |
||||
return null; // 附表目标但表为空,跳过,避免误写主表
|
||||
} |
||||
if (row != null) { |
||||
row.put(targetField, result); // 附表目标:写回当前编辑行
|
||||
String tabId = resolveEntryTabId(objectValue, entryTable, ctx.getEventWidgetVariableName()); |
||||
return tabId == null ? null : new FormulaValue(targetField, result, tabId, row.getString("rowkey")); |
||||
} |
||||
objectValue.put(targetField, result); // 主表目标:写回表单对象,供后续规则条件求值
|
||||
return new FormulaValue(targetField, result, null, null); |
||||
} |
||||
|
||||
@Override |
||||
public List<OnlineEventPluginExecuteVo> buildResult(RuleActionContext ctx, Object value) { |
||||
FormulaValue fv = (FormulaValue) value; |
||||
if (fv == null) { |
||||
return Collections.emptyList(); |
||||
} |
||||
List<OnlineEventPluginExecuteVo> out = new ArrayList<>(); |
||||
if (fv.rowNum == null) { |
||||
for (String name : resolveWidgetVariableNames(ctx, fv.targetField)) { |
||||
out.add(buildVo(name, AttributeEnum.VALUE_CHANGE, fv.value)); |
||||
} |
||||
} else { |
||||
out.add(buildEntryVo(fv.tabId, fv.targetField, AttributeEnum.VALUE_CHANGE, fv.value, fv.rowNum)); |
||||
} |
||||
return out; |
||||
} |
||||
|
||||
private String lastSegment(String tag) { |
||||
String[] parts = tag.split("\\."); |
||||
return parts[parts.length - 1].trim(); |
||||
} |
||||
|
||||
/** 解析公式中 表.表.字段 / 表.字段 引用的实际值:主表直接取/下钻关联对象,分录取当前编辑行;空值补 0,数字字符串转数值(避免字符串相加) */ |
||||
private Object resolveFormulaField(String ref, ObjectValue objectValue, String rowKeySubset) { |
||||
String[] parts = ref.split("\\."); |
||||
Object value; |
||||
Object first = objectValue.get(parts[1]); |
||||
if (!parts[0].equals(parts[1]) && first instanceof ObjectCollection) { // 分录:表.分录表.字段
|
||||
ObjectValue row = jexl3Util.currentRow((ObjectCollection) first, rowKeySubset, (String) objectValue.get("rowKey")); |
||||
value = row == null ? null : row.get(parts[2]); |
||||
} else { // 主表:表.字段 / 表.表.字段 / 表.关联对象.子字段
|
||||
value = jexl3Util.resolveFieldChain(objectValue, parts, parts[0].equals(parts[1]) ? 2 : 1); |
||||
} |
||||
if (value == null || value.toString().isEmpty()) { |
||||
return 0; |
||||
} |
||||
return value instanceof String ? toNumber((String) value) : value; |
||||
} |
||||
|
||||
/** 解析字面量赋值:去首尾引号,'' → 空串,数字字符串转数值,其余原样返回 */ |
||||
private Object parseLiteral(String src) { |
||||
String trim = src.trim(); |
||||
if (trim.length() >= 2 |
||||
&& ((trim.startsWith("'") && trim.endsWith("'")) || (trim.startsWith("\"") && trim.endsWith("\"")))) { |
||||
trim = trim.substring(1, trim.length() - 1); |
||||
} |
||||
return toNumber(trim); |
||||
} |
||||
|
||||
/** 数字字符串转数值:正则判数字 → 整数转 Long,小数转 BigDecimal;非数字原样返回 */ |
||||
private Object toNumber(String s) { |
||||
String trim = s.trim(); |
||||
if (!trim.matches("-?\\d+(\\.\\d+)?")) { |
||||
return s; |
||||
} |
||||
if (trim.contains(".")) { |
||||
return new BigDecimal(trim); |
||||
} |
||||
return Long.valueOf(trim); |
||||
} |
||||
|
||||
private List<String> extractFieldRefs(String src) { |
||||
List<String> refs = new ArrayList<>(); |
||||
for (String token : src.split("[^a-zA-Z0-9_.]+")) { |
||||
if (!token.isEmpty() && token.split("\\.").length >= 2 && !refs.contains(token)) { |
||||
refs.add(token); |
||||
} |
||||
} |
||||
return refs; |
||||
} |
||||
|
||||
private static class FormulaValue { |
||||
final String targetField; |
||||
final Object value; |
||||
final String tabId; |
||||
final String rowNum; |
||||
FormulaValue(String targetField, Object value, String tabId, String rowNum) { |
||||
this.targetField = targetField; |
||||
this.value = value; |
||||
this.tabId = tabId; |
||||
this.rowNum = rowNum; |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,27 @@
@@ -0,0 +1,27 @@
|
||||
package apelet.common.online.rule.action.impl; |
||||
|
||||
import apelet.common.online.model.constant.AttributeEnum; |
||||
import apelet.common.online.rule.action.AbstractRuleActionExecutor; |
||||
import apelet.common.online.rule.enums.RuleActionTypeEnum; |
||||
import apelet.common.online.rule.model.RuleActionContext; |
||||
import apelet.common.online.vo.OnlineEventPluginExecuteVo; |
||||
import org.springframework.stereotype.Component; |
||||
|
||||
import java.util.List; |
||||
|
||||
/** |
||||
* type 8 隐藏控件 → SHOW=false。 |
||||
*/ |
||||
@Component |
||||
public class WidgetHideExecutor extends AbstractRuleActionExecutor { |
||||
|
||||
@Override |
||||
public RuleActionTypeEnum getActionType() { |
||||
return RuleActionTypeEnum.WIDGET_HIDE; |
||||
} |
||||
|
||||
@Override |
||||
public List<OnlineEventPluginExecuteVo> buildResult(RuleActionContext ctx, Object value) { |
||||
return buildFromTreeList(ctx, AttributeEnum.SHOW, false); |
||||
} |
||||
} |
||||
@ -0,0 +1,51 @@
@@ -0,0 +1,51 @@
|
||||
package apelet.common.online.rule.action.impl; |
||||
|
||||
import apelet.common.online.model.constant.AttributeEnum; |
||||
import apelet.common.online.rule.action.AbstractRuleActionExecutor; |
||||
import apelet.common.online.rule.enums.RuleActionTypeEnum; |
||||
import apelet.common.online.rule.model.RuleActionContext; |
||||
import apelet.common.online.vo.OnlineEventPluginExecuteVo; |
||||
import com.alibaba.fastjson.JSONArray; |
||||
import com.alibaba.fastjson.JSONObject; |
||||
import org.springframework.stereotype.Component; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
/** |
||||
* type 5 锁定控件:普通控件 → DISABLED;btnId 特例(锁父容器内按钮)→ CHANGEOPERATE 数组。 |
||||
*/ |
||||
@Component |
||||
public class WidgetLockExecutor extends AbstractRuleActionExecutor { |
||||
|
||||
@Override |
||||
public RuleActionTypeEnum getActionType() { |
||||
return RuleActionTypeEnum.WIDGET_LOCK; |
||||
} |
||||
|
||||
@Override |
||||
public List<OnlineEventPluginExecuteVo> buildResult(RuleActionContext ctx, Object value) { |
||||
List<OnlineEventPluginExecuteVo> out = new ArrayList<>(); |
||||
JSONArray treeList = ctx.getAction().getSource().getJSONArray("treeList"); |
||||
if (treeList == null) { |
||||
return out; |
||||
} |
||||
for (Object o : treeList) { |
||||
JSONObject jsonObject = (JSONObject) o; |
||||
String name = jsonObject.get("variableName").toString(); |
||||
if (jsonObject.containsKey("btnId")) { // 特例:锁定父容器内按钮
|
||||
String showName = jsonObject.get("showName").toString(); |
||||
String parentVariableName = jsonObject.get("parentVariableName").toString(); |
||||
JSONArray objects = new JSONArray(); |
||||
JSONObject json = new JSONObject(); |
||||
json.put("name", showName); |
||||
json.put("disable", true); |
||||
objects.add(json); |
||||
out.add(buildVo(parentVariableName, AttributeEnum.CHANGEOPERATE, objects)); |
||||
} else { // 普通控件锁定
|
||||
out.add(buildVo(name, AttributeEnum.DISABLED, true)); |
||||
} |
||||
} |
||||
return out; |
||||
} |
||||
} |
||||
@ -0,0 +1,27 @@
@@ -0,0 +1,27 @@
|
||||
package apelet.common.online.rule.action.impl; |
||||
|
||||
import apelet.common.online.model.constant.AttributeEnum; |
||||
import apelet.common.online.rule.action.AbstractRuleActionExecutor; |
||||
import apelet.common.online.rule.enums.RuleActionTypeEnum; |
||||
import apelet.common.online.rule.model.RuleActionContext; |
||||
import apelet.common.online.vo.OnlineEventPluginExecuteVo; |
||||
import org.springframework.stereotype.Component; |
||||
|
||||
import java.util.List; |
||||
|
||||
/** |
||||
* type 7 显示控件 → SHOW=true。 |
||||
*/ |
||||
@Component |
||||
public class WidgetShowExecutor extends AbstractRuleActionExecutor { |
||||
|
||||
@Override |
||||
public RuleActionTypeEnum getActionType() { |
||||
return RuleActionTypeEnum.WIDGET_SHOW; |
||||
} |
||||
|
||||
@Override |
||||
public List<OnlineEventPluginExecuteVo> buildResult(RuleActionContext ctx, Object value) { |
||||
return buildFromTreeList(ctx, AttributeEnum.SHOW, true); |
||||
} |
||||
} |
||||
@ -0,0 +1,27 @@
@@ -0,0 +1,27 @@
|
||||
package apelet.common.online.rule.action.impl; |
||||
|
||||
import apelet.common.online.model.constant.AttributeEnum; |
||||
import apelet.common.online.rule.action.AbstractRuleActionExecutor; |
||||
import apelet.common.online.rule.enums.RuleActionTypeEnum; |
||||
import apelet.common.online.rule.model.RuleActionContext; |
||||
import apelet.common.online.vo.OnlineEventPluginExecuteVo; |
||||
import org.springframework.stereotype.Component; |
||||
|
||||
import java.util.List; |
||||
|
||||
/** |
||||
* type 6 解锁控件 → DISABLED=false。 |
||||
*/ |
||||
@Component |
||||
public class WidgetUnlockExecutor extends AbstractRuleActionExecutor { |
||||
|
||||
@Override |
||||
public RuleActionTypeEnum getActionType() { |
||||
return RuleActionTypeEnum.WIDGET_UNLOCK; |
||||
} |
||||
|
||||
@Override |
||||
public List<OnlineEventPluginExecuteVo> buildResult(RuleActionContext ctx, Object value) { |
||||
return buildFromTreeList(ctx, AttributeEnum.DISABLED, false); |
||||
} |
||||
} |
||||
@ -0,0 +1,121 @@
@@ -0,0 +1,121 @@
|
||||
package apelet.common.online.rule.condition; |
||||
|
||||
import apelet.common.core.object.ObjectCollection; |
||||
import apelet.common.core.object.ObjectValue; |
||||
import apelet.common.online.rule.model.RuleCondition; |
||||
import apelet.common.online.rule.model.RuleEvalContext; |
||||
import apelet.common.online.util.Jexl3Util; |
||||
import org.apache.commons.lang3.StringUtils; |
||||
import org.springframework.stereotype.Component; |
||||
|
||||
import javax.annotation.Resource; |
||||
import java.util.*; |
||||
import java.util.regex.Matcher; |
||||
import java.util.regex.Pattern; |
||||
|
||||
/** |
||||
* 规则级条件求值:一条规则只求值一次,用结果选 condition=1/2 的动作。 |
||||
* 加载表达式里【所有】表.表.字段 / 表.字段 token 进 context(修复:旧 getExpressionValue 只取第一个字段)。 |
||||
*/ |
||||
@Component |
||||
public class ConditionEvaluator { |
||||
|
||||
private static final Pattern FIELD_REF = Pattern.compile("[a-zA-Z0-9_]+(\\.[a-zA-Z0-9_]+)+"); |
||||
|
||||
@Resource |
||||
private Jexl3Util jexl3Util; |
||||
@Resource |
||||
private FunctionExecutor functionExecutor; |
||||
|
||||
public boolean evaluate(RuleCondition condition, RuleEvalContext ctx) { |
||||
String expression = condition.getExpression(); |
||||
if (StringUtils.isEmpty(expression)) return false; |
||||
boolean result = true; |
||||
for (String expr : expression.split("&&")) { |
||||
expr = expr.trim(); |
||||
if (expr.isEmpty()) continue; |
||||
if (expr.startsWith("$")) { |
||||
String fun = jexl3Util.getMySplit("\\$(.*?)\\$", expr); |
||||
Object value = functionExecutor.execute(condition.getFunctionForm(), fun, ctx.getObjectValue()); |
||||
result = jexl3Util.judgmentFunc(expr, value) && result; |
||||
} else { |
||||
result = evaluateSimple(expr, ctx.getObjectValue()) && result; |
||||
} |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
private boolean evaluateSimple(String expr, ObjectValue objectValue) { |
||||
List<String> refs = extractFieldRefs(expr); |
||||
Map<String, Object> context = new HashMap<>(); |
||||
String entryTable = null; |
||||
for (String ref : refs) { |
||||
String[] parts = ref.split("\\."); |
||||
if (parts.length < 2) continue; |
||||
// 分录:起始段在 objectValue 里是分录集合;其余都是主表字段(兼容 表.表.字段 / 表.字段 两种格式)
|
||||
if (!parts[0].equals(parts[1]) && objectValue.get(parts[1]) instanceof ObjectCollection) { |
||||
if (entryTable == null) entryTable = parts[1]; |
||||
} else { |
||||
context.put(ref, resolveMasterField(ref, objectValue)); |
||||
} |
||||
} |
||||
if (entryTable == null) { |
||||
Object val = jexl3Util.expression(expr, context); |
||||
return val instanceof Boolean && (Boolean) val; |
||||
} |
||||
return evaluateEntryRows(expr, refs, entryTable, context, objectValue); |
||||
} |
||||
|
||||
/** 分录表达式:命中当前编辑行逐行求值(rowKeySubset 行内编辑 / rowKey 分录编辑完成 / 无过滤取新增行),任一行为 true 即成立 */ |
||||
private boolean evaluateEntryRows(String expr, List<String> refs, String entryTable, |
||||
Map<String, Object> baseContext, ObjectValue objectValue) { |
||||
ObjectCollection rows = (ObjectCollection) objectValue.get(entryTable); |
||||
if (rows == null || rows.isEmpty()) return false; |
||||
String rowKeySubset = (String) objectValue.get("rowKeySubset"); |
||||
String rowKey = (String) objectValue.get("rowKey"); |
||||
Set<String> targetSet = new HashSet<>(); |
||||
if (StringUtils.isNotEmpty(rowKeySubset)) Collections.addAll(targetSet, rowKeySubset.split(",")); |
||||
Iterator iterator = rows.iterator(); |
||||
while (iterator.hasNext()) { |
||||
ObjectValue row = (ObjectValue) iterator.next(); |
||||
if (row.getValues() == null || row.getValues().isEmpty()) continue; |
||||
String k = row.getString("rowkey"); |
||||
if (StringUtils.isNotEmpty(rowKeySubset)) { // 行内编辑:命中 subset 的行
|
||||
if (k == null || !targetSet.contains(k)) continue; |
||||
} else if (StringUtils.isNotEmpty(rowKey)) { // 分录编辑完成:命中当前行 rowKey
|
||||
if (!rowKey.equals(k)) continue; |
||||
} else { // 无过滤:跳过已保存行,只评估新增行
|
||||
if (k != null) continue; |
||||
} |
||||
Map<String, Object> context = new HashMap<>(baseContext); |
||||
for (String ref : refs) { |
||||
String[] parts = ref.split("\\."); |
||||
if (parts.length >= 2 && entryTable.equals(parts[1])) { |
||||
context.put(ref, resolveRowField(row, parts)); |
||||
} |
||||
} |
||||
Object val = jexl3Util.expression(expr, context); |
||||
if (val instanceof Boolean && (Boolean) val) return true; |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
private Object resolveMasterField(String ref, ObjectValue objectValue) { |
||||
String[] parts = ref.split("\\."); |
||||
return jexl3Util.resolveFieldChain(objectValue, parts, parts[0].equals(parts[1]) ? 2 : 1); |
||||
} |
||||
|
||||
private Object resolveRowField(ObjectValue row, String[] parts) { |
||||
return jexl3Util.resolveFieldChain(row, parts, 2); |
||||
} |
||||
|
||||
private List<String> extractFieldRefs(String expr) { |
||||
List<String> refs = new ArrayList<>(); |
||||
Matcher matcher = FIELD_REF.matcher(expr); |
||||
while (matcher.find()) { |
||||
String ref = matcher.group(); |
||||
if (!refs.contains(ref)) refs.add(ref); |
||||
} |
||||
return refs; |
||||
} |
||||
} |
||||
@ -0,0 +1,67 @@
@@ -0,0 +1,67 @@
|
||||
package apelet.common.online.rule.condition; |
||||
|
||||
import apelet.common.core.exception.MyRuntimeException; |
||||
import apelet.common.core.object.ObjectValue; |
||||
import apelet.common.online.model.SysFunction; |
||||
import apelet.common.online.service.ISysFunctionService; |
||||
import apelet.common.online.util.Jexl3Util; |
||||
import apelet.common.online.vo.SysFunctionVo; |
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
||||
import org.springframework.beans.BeanUtils; |
||||
import org.springframework.stereotype.Component; |
||||
|
||||
import javax.annotation.Resource; |
||||
import java.util.Arrays; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* 封装 $fun$ 动态编译函数调用(javassist),条件求值与 type1 动作赋值共用。 |
||||
*/ |
||||
@Component |
||||
public class FunctionExecutor { |
||||
|
||||
@Resource |
||||
private ISysFunctionService sysFunctionService; |
||||
@Resource |
||||
private Jexl3Util jexl3Util; |
||||
|
||||
/** 规则引擎路径:提取函数参数后执行 */ |
||||
public Object execute(Map<String, Long> functionForm, String fun, ObjectValue objectValue) { |
||||
List<String> funParam = Arrays.asList(jexl3Util.getMySplit("\\((.*?)\\)", fun).split(",")); |
||||
Object[] objects = jexl3Util.getFunParam(funParam, objectValue); |
||||
return executeWithParams(functionForm, fun, objects); |
||||
} |
||||
|
||||
/** 核心调用:给定已提取的参数,查函数并动态编译执行(Save/Audit 插件经 OnlineFormServiceImpl.executeFunction 也走这里) */ |
||||
public Object executeWithParams(Map<String, Long> functionForm, String fun, Object[] objects) { |
||||
if (fun == null) { |
||||
return null; |
||||
} |
||||
String funName = fun.substring(0, fun.indexOf('(')).trim(); |
||||
SysFunction sysFunction = resolveSysFunction(functionForm, funName); |
||||
SysFunctionVo sysFunctionVo = new SysFunctionVo(); |
||||
BeanUtils.copyProperties(sysFunction, sysFunctionVo); |
||||
sysFunctionVo.setParameter(objects); |
||||
return sysFunctionService.onLineFunction(sysFunctionVo); |
||||
} |
||||
|
||||
/** |
||||
* 解析 $fun$ 引用的函数:functionForm 映射的 ID 优先 getById;ID 缺失/查不到时按函数名或编码(function_name/number)查询 |
||||
* (兼容 functionName 字段后续改为 number 字段);名称或编码匹配到 0 个或多个视为配置错误,直接失效。 |
||||
*/ |
||||
private SysFunction resolveSysFunction(Map<String, Long> functionForm, String funName) { |
||||
Long funId = functionForm == null ? null : functionForm.get(funName); |
||||
if (funId != null) { |
||||
SysFunction f = sysFunctionService.getById(funId); |
||||
if (f != null) return f; |
||||
} |
||||
List<SysFunction> list = sysFunctionService.list(new LambdaQueryWrapper<SysFunction>() |
||||
.eq(SysFunction::getFunctionName, funName) |
||||
.or().eq(SysFunction::getNumber, funName)); |
||||
if (list == null || list.size() != 1) { |
||||
throw new MyRuntimeException("函数 [" + funName + "] 不存在或匹配到多个,无法执行 $fun$"); |
||||
} |
||||
return list.get(0); |
||||
} |
||||
} |
||||
@ -0,0 +1,26 @@
@@ -0,0 +1,26 @@
|
||||
package apelet.common.online.rule.enums; |
||||
|
||||
import lombok.Getter; |
||||
|
||||
/** |
||||
* 归一化后的事件类型,由动作 checkList 归一化而来: |
||||
* "加载" → FORM_CREATED;"值更新"/"值改变" → CHANGE;空数组 → ALL(监听全部事件)。 |
||||
* |
||||
* 触发匹配:eventCode = formCreated 命中 FORM_CREATED,其余命中 CHANGE;ALL 恒命中。 |
||||
*/ |
||||
public enum EventTypeEnum { |
||||
|
||||
/** 全部事件:checkList 为空数组,动作在所有事件下都触发(修复空数组「永不执行」行为反转点) */ |
||||
ALL("全部事件(checkList 为空)"), |
||||
/** 页面加载:checkList 含「加载」,对应 eventCode = formCreated */ |
||||
FORM_CREATED("页面加载(checkList 含「加载」,formCreated)"), |
||||
/** 值改变:checkList 含「值更新」/「值改变」,对应 eventCode = change */ |
||||
CHANGE("值改变(checkList 含「值更新」/「值改变」,change)"); |
||||
|
||||
@Getter |
||||
private final String description; |
||||
|
||||
EventTypeEnum(String description) { |
||||
this.description = description; |
||||
} |
||||
} |
||||
@ -0,0 +1,44 @@
@@ -0,0 +1,44 @@
|
||||
package apelet.common.online.rule.enums; |
||||
|
||||
import lombok.Getter; |
||||
|
||||
import java.util.Arrays; |
||||
|
||||
/** |
||||
* 规则动作类型(1~8),对应规则 JSON allList[].type,Registry 按它路由到 RuleActionExecutor 实现。 |
||||
* |
||||
* 1~4 为数据类动作(业务规则 businessRulesList),5~8 为控件状态类动作(界面规则 conditionTypeList): |
||||
* <pre> |
||||
* 1 计算并赋值 → FormulaAssignExecutor 金额 = 数量 * 单价 |
||||
* 2 携带基础资料 → BaseDataCarryExecutor 创建人.fname → 提交人 |
||||
* 3 设置字段值 → FieldAssignExecutor 源字段值 → 目标字段 |
||||
* 4 清除字段值 → FieldClearExecutor 清空字段 |
||||
* 5~8 锁定/解锁/显示/隐藏控件(WidgetLock/Unlock/Show/HideExecutor) |
||||
* </pre> |
||||
*/ |
||||
@Getter |
||||
public enum RuleActionTypeEnum { |
||||
|
||||
FORMULA_ASSIGN(1, "计算并赋值"), |
||||
BASE_DATA_CARRY(2, "携带基础资料"), |
||||
FIELD_ASSIGN(3, "设置字段值"), |
||||
FIELD_CLEAR(4, "清除字段值"), |
||||
WIDGET_LOCK(5, "锁定控件"), |
||||
WIDGET_UNLOCK(6, "解锁控件"), |
||||
WIDGET_SHOW(7, "显示控件"), |
||||
WIDGET_HIDE(8, "隐藏控件"); |
||||
|
||||
private final int code; |
||||
private final String description; |
||||
|
||||
RuleActionTypeEnum(int code, String description) { |
||||
this.code = code; |
||||
this.description = description; |
||||
} |
||||
|
||||
/** 由 widgetJson 动作里的 type 值(1~8)转枚举,未识别抛异常 */ |
||||
public static RuleActionTypeEnum of(int code) { |
||||
return Arrays.stream(values()).filter(t -> t.code == code).findFirst() |
||||
.orElseThrow(() -> new IllegalArgumentException("未识别的动作类型: " + code)); |
||||
} |
||||
} |
||||
@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
|
||||
package apelet.common.online.rule.enums; |
||||
|
||||
import lombok.Getter; |
||||
|
||||
/** |
||||
* 规则来源类型:界面规则 / 业务规则。 |
||||
* 对应 widgetJson 两个数组:conditionTypeList(界面规则) / businessRulesList(业务规则)。 |
||||
* 两个数组在代码里一视同仁处理(同一管线),ruleType 仅作来源标记,供前端差异化展示或排查。 |
||||
*/ |
||||
public enum RuleTypeEnum { |
||||
|
||||
/** 界面规则:widgetJson 的 conditionTypeList 数组(锁定/解锁/显隐等控件状态类动作) */ |
||||
INTERFACE("界面规则(conditionTypeList)"), |
||||
/** 业务规则:widgetJson 的 businessRulesList 数组(赋值/携带基础资料等数据类动作) */ |
||||
BUSINESS("业务规则(businessRulesList)"); |
||||
|
||||
@Getter |
||||
private final String description; |
||||
|
||||
RuleTypeEnum(String description) { |
||||
this.description = description; |
||||
} |
||||
} |
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue