Compare commits
49 Commits
master
...
feature/20
94 changed files with 4866 additions and 372 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,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); |
||||
} |
||||
} |
||||
@ -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,20 @@
@@ -0,0 +1,20 @@
|
||||
package apelet.common.online.service; |
||||
|
||||
/** |
||||
* 在线表单按钮权限检查器接口。 |
||||
* 由 tenant-admin 模块实现并注册为 Spring Bean。 |
||||
* |
||||
* @author chenchuchuan |
||||
* @date 2026-07-17 |
||||
*/ |
||||
public interface FormButtonPermissionChecker { |
||||
|
||||
/** |
||||
* 检查当前用户是否有指定表单上指定按钮的操作权限。 |
||||
* |
||||
* @param formId 在线表单ID (zz_online_form.form_id)。 |
||||
* @param buttonName 按钮名称(对应 operationList 中的 name 字段)。 |
||||
* @return true-有权限,false-无权限。 |
||||
*/ |
||||
boolean hasPermission(Long formId, String buttonName); |
||||
} |
||||
@ -0,0 +1,65 @@
@@ -0,0 +1,65 @@
|
||||
package apelet.common.online.service; |
||||
|
||||
import apelet.common.core.base.service.IBaseService; |
||||
import apelet.common.core.object.CallResult; |
||||
import apelet.common.online.model.PermissionDataRuleSchema; |
||||
|
||||
import java.util.Collection; |
||||
import java.util.List; |
||||
|
||||
/** |
||||
* 权限数据规则方案表 服务接口。 |
||||
* |
||||
* @author chenchuchuan |
||||
* @date 2026-07-15 |
||||
*/ |
||||
public interface IPermissionDataRuleSchemaService extends IBaseService<PermissionDataRuleSchema, Long> { |
||||
|
||||
/** |
||||
* 分页条件查询规则方案列表。 |
||||
* |
||||
* @param filter 过滤条件。 |
||||
* @param orderBy 排序语句。 |
||||
* @return 规则方案列表。 |
||||
*/ |
||||
List<PermissionDataRuleSchema> getListByFilter(PermissionDataRuleSchema filter, String orderBy); |
||||
|
||||
/** |
||||
* 根据 formId 查询启用状态的规则(运行时用)。 |
||||
* |
||||
* @param formId 在线表ID。 |
||||
* @return 启用状态的规则列表。 |
||||
*/ |
||||
List<PermissionDataRuleSchema> listByFormId(Long formId); |
||||
|
||||
/** |
||||
* 校验关联数据:验证 formId 对应的是主表或单表,并自动填充 tableType。 |
||||
* |
||||
* @param schema 待校验的规则方案对象。 |
||||
* @return 校验结果。 |
||||
*/ |
||||
CallResult verifyRelatedData(PermissionDataRuleSchema schema); |
||||
|
||||
/** |
||||
* 保存新增(含 ID生成 + 默认状态设置)。 |
||||
* |
||||
* @param schema 新增的规则方案对象。 |
||||
* @return 新增后的规则方案对象。 |
||||
*/ |
||||
PermissionDataRuleSchema saveNew(PermissionDataRuleSchema schema); |
||||
|
||||
/** |
||||
* 根据规则方案ID集合查询启用状态的规则(运行时用)。 |
||||
* |
||||
* @param idSet 规则方案ID集合。 |
||||
* @return 启用状态的规则列表。 |
||||
*/ |
||||
List<PermissionDataRuleSchema> listEnabledByIds(Collection<Long> idSet); |
||||
|
||||
/** |
||||
* 填充绑定的在线表单列表(formId + formName),用于详情接口回显。 |
||||
* |
||||
* @param schema 规则方案对象。 |
||||
*/ |
||||
void fillFormNameList(PermissionDataRuleSchema schema); |
||||
} |
||||
@ -0,0 +1,84 @@
@@ -0,0 +1,84 @@
|
||||
package apelet.common.online.service; |
||||
|
||||
import apelet.common.core.base.service.IBaseService; |
||||
import apelet.common.core.object.CallResult; |
||||
import apelet.common.core.object.MyOrderParam; |
||||
import apelet.common.core.object.MyPageData; |
||||
import apelet.common.core.object.MyPageParam; |
||||
import apelet.common.online.dto.RoleFormButtonDto; |
||||
import apelet.common.online.model.RoleFormButton; |
||||
|
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.Set; |
||||
|
||||
/** |
||||
* 角色-表单按钮权限配置服务接口。 |
||||
* |
||||
* @author chenchuchuan |
||||
* @date 2026-07-17 |
||||
*/ |
||||
public interface IRoleFormButtonService |
||||
extends IBaseService<RoleFormButton, Long> { |
||||
|
||||
/** |
||||
* 分页条件查询。 |
||||
*/ |
||||
List<RoleFormButton> getListByFilter(RoleFormButton filter, String orderBy); |
||||
|
||||
// ========== 核心业务接口 ==========
|
||||
|
||||
/** |
||||
* 查询当前登录用户对指定表单的按钮权限(合并所有角色的并集)。 |
||||
* |
||||
* @param formId 在线表单ID。 |
||||
* @return 包含 formId, buttonList(含name+checked的列表)。 |
||||
*/ |
||||
Map<String, Set<String>> getButtonConfigVo(Long formId); |
||||
|
||||
/** |
||||
* 批量保存按钮权限配置(同一事务内全部成功或全部回滚)。 |
||||
* |
||||
* @param dtoList 保存入参列表。 |
||||
* @return 保存结果。 |
||||
*/ |
||||
CallResult batchSaveButtonConfig(List<RoleFormButtonDto> dtoList); |
||||
|
||||
/** |
||||
* 保存单条按钮权限配置(仅供内部批量方法调用,不做事务注解)。 |
||||
* |
||||
* @param dto 保存入参。 |
||||
* @return 保存结果。 |
||||
*/ |
||||
CallResult saveButtonConfig(RoleFormButtonDto dto); |
||||
|
||||
/** |
||||
* 根据角色ID查询该角色下所有绑定的按钮权限配置。 |
||||
* |
||||
* @param roleId 角色ID。 |
||||
* @return 按钮权限配置列表。 |
||||
*/ |
||||
List<RoleFormButton> listByRoleId(Long roleId); |
||||
|
||||
/** |
||||
* 根据角色ID和表单ID查询唯一配置记录。 |
||||
*/ |
||||
RoleFormButton getByRoleIdAndFormId(Long roleId, Long formId); |
||||
|
||||
/** |
||||
* 删除角色下绑定的所有按钮权限配置(角色删除时调用)。 |
||||
*/ |
||||
void removeByRoleId(Long roleId); |
||||
|
||||
/** |
||||
* 分页查询所有在线表单下的所有按钮。 |
||||
* 遍历全部在线表单,从每个表单的 widgetJson 中提取已开启的按钮。 |
||||
* 如果传了 roleId,则只返回该角色已配置的表单,并合并已保存的勾选状态。 |
||||
* |
||||
* @param roleId 角色ID(可选,用于过滤已配置的表单)。 |
||||
* @param orderParam 排序参数。 |
||||
* @param pageParam 分页参数。 |
||||
* @return 分页结果,每个元素包含 formId, formName, buttonList[{name, checked}]。 |
||||
*/ |
||||
MyPageData<Map<String, Object>> listAllFormButtons(Long roleId, MyOrderParam orderParam, MyPageParam pageParam); |
||||
} |
||||
@ -0,0 +1,49 @@
@@ -0,0 +1,49 @@
|
||||
package apelet.common.online.service; |
||||
|
||||
import apelet.common.core.base.service.IBaseService; |
||||
import apelet.common.core.object.CallResult; |
||||
import apelet.common.online.model.RolePermissionSchema; |
||||
|
||||
import java.util.Collection; |
||||
import java.util.List; |
||||
|
||||
/** |
||||
* 角色-数据规则方案关联服务接口。 |
||||
* |
||||
* @author chenchuchuan |
||||
* @date 2026-08-05 |
||||
*/ |
||||
public interface IRolePermissionSchemaService extends IBaseService<RolePermissionSchema, Long> { |
||||
|
||||
/** |
||||
* 查询指定角色绑定的所有数据规则方案。 |
||||
* |
||||
* @param roleId 角色ID。 |
||||
* @return 关联关系列表。 |
||||
*/ |
||||
List<RolePermissionSchema> listByRoleId(Long roleId); |
||||
|
||||
/** |
||||
* 根据多个角色ID查询绑定的数据规则方案(运行时用)。 |
||||
* |
||||
* @param roleIds 角色ID集合。 |
||||
* @return 关联关系列表。 |
||||
*/ |
||||
List<RolePermissionSchema> listByRoleIds(Collection<Long> roleIds); |
||||
|
||||
/** |
||||
* 绑定角色的数据规则方案(先删后插)。schemaIdList 为空表示清空该角色的全部绑定。 |
||||
* |
||||
* @param roleId 角色ID。 |
||||
* @param schemaIdList 数据规则方案ID列表。 |
||||
* @return 绑定结果,校验失败时返回错误信息。 |
||||
*/ |
||||
CallResult bindRoleSchemas(Long roleId, List<Long> schemaIdList); |
||||
|
||||
/** |
||||
* 删除角色下绑定的所有数据规则方案(角色删除时调用)。 |
||||
* |
||||
* @param roleId 角色ID。 |
||||
*/ |
||||
void removeByRoleId(Long roleId); |
||||
} |
||||
@ -0,0 +1,69 @@
@@ -0,0 +1,69 @@
|
||||
package apelet.common.online.service.impl; |
||||
|
||||
import apelet.common.core.object.TokenData; |
||||
import apelet.common.online.model.RoleFormButton; |
||||
import apelet.common.online.service.FormButtonPermissionChecker; |
||||
import apelet.common.online.service.IRoleFormButtonService; |
||||
import apelet.common.online.util.WidgetJsonButtonParser; |
||||
import cn.hutool.core.util.StrUtil; |
||||
import com.alibaba.fastjson.JSON; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.stereotype.Component; |
||||
|
||||
import java.util.List; |
||||
import java.util.stream.Collectors; |
||||
|
||||
/** |
||||
* 按钮权限检查器实现。 |
||||
* 通过当前用户的所有角色,查询 xy_sys_role_form_button 表, |
||||
* 判断是否有指定表单-按钮的权限。 |
||||
* |
||||
* @author chenchuchuan |
||||
* @date 2026-07-17 |
||||
*/ |
||||
@Slf4j |
||||
@Component |
||||
public class FormButtonPermissionCheckerImpl implements FormButtonPermissionChecker { |
||||
|
||||
@Autowired |
||||
private IRoleFormButtonService roleFormButtonService; |
||||
|
||||
@Override |
||||
public boolean hasPermission(Long formId, String buttonName) { |
||||
try { |
||||
// 1. 获取当前登录用户信息
|
||||
TokenData tokenData = TokenData.takeFromRequest(); |
||||
if (tokenData == null) { |
||||
log.warn("TokenData 为空,无法校验按钮权限"); |
||||
return false; |
||||
} |
||||
Long userId = tokenData.getUserId(); |
||||
if (userId == null) { |
||||
return false; |
||||
} |
||||
|
||||
// 2. 从 TokenData 获取用户的所有角色ID
|
||||
if (StrUtil.isBlank(tokenData.getRoleIds())) { |
||||
return false; |
||||
} |
||||
List<Long> roleIds = StrUtil.split(tokenData.getRoleIds(), ',').stream().map(Long::valueOf).collect(Collectors.toList()); |
||||
|
||||
// 3. 遍历角色,检查是否有任一角色授权了该表单的该按钮
|
||||
for (Long roleId : roleIds) { |
||||
RoleFormButton config = roleFormButtonService.getByRoleIdAndFormId(roleId, formId); |
||||
if (config != null && config.getButtonConfig() != null) { |
||||
List<WidgetJsonButtonParser.ButtonInfo> buttons = JSON.parseArray(config.getButtonConfig(), WidgetJsonButtonParser.ButtonInfo.class); |
||||
for (WidgetJsonButtonParser.ButtonInfo btn : buttons) { |
||||
if (buttonName.equals(btn.getName()) && Boolean.TRUE.equals(btn.getEnabled())) { |
||||
return true; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} catch (Exception e) { |
||||
log.error("校验按钮权限时发生异常", e); |
||||
} |
||||
return false; |
||||
} |
||||
} |
||||
@ -0,0 +1,140 @@
@@ -0,0 +1,140 @@
|
||||
package apelet.common.online.service.impl; |
||||
|
||||
import apelet.common.core.base.dao.BaseDaoMapper; |
||||
import apelet.common.core.base.service.BaseService; |
||||
import apelet.common.core.object.CallResult; |
||||
import apelet.common.generator.model.OnlFormHead; |
||||
import apelet.common.generator.service.IOnlFormHeadService; |
||||
import apelet.common.online.dao.PermissionDataRuleSchemaMapper; |
||||
import apelet.common.online.model.OnlineForm; |
||||
import apelet.common.online.model.PermissionDataRuleSchema; |
||||
import apelet.common.online.model.constant.DataRuleType; |
||||
import apelet.common.online.service.IPermissionDataRuleSchemaService; |
||||
import apelet.common.online.service.OnlineFormService; |
||||
import apelet.common.sequence.wrapper.IdGeneratorWrapper; |
||||
import cn.hutool.core.collection.CollUtil; |
||||
import cn.hutool.core.util.StrUtil; |
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.stereotype.Service; |
||||
|
||||
import java.util.*; |
||||
import java.util.stream.Collectors; |
||||
|
||||
/** |
||||
* 权限数据规则方案表 服务实现类。 |
||||
* |
||||
* @author chenchuchuan |
||||
* @date 2026-07-15 |
||||
*/ |
||||
@Service("permissionDataRuleSchemaService") |
||||
@Slf4j |
||||
public class PermissionDataRuleSchemaServiceImpl extends BaseService<PermissionDataRuleSchema, Long> implements IPermissionDataRuleSchemaService { |
||||
|
||||
@Autowired |
||||
PermissionDataRuleSchemaMapper mapper; |
||||
|
||||
@Autowired |
||||
private IdGeneratorWrapper idGenerator; |
||||
|
||||
@Autowired |
||||
private IOnlFormHeadService onlFormHeadService; |
||||
|
||||
@Autowired |
||||
private OnlineFormService onlineFormService; |
||||
|
||||
// ========== BaseService 要求重写 ==========
|
||||
@Override |
||||
protected BaseDaoMapper<PermissionDataRuleSchema> mapper() { |
||||
return mapper; |
||||
} |
||||
|
||||
// ========== 分页查询 ==========
|
||||
@Override |
||||
public List<PermissionDataRuleSchema> getListByFilter(PermissionDataRuleSchema filter, String orderBy) { |
||||
return mapper.getListByFilter(filter, orderBy); |
||||
} |
||||
|
||||
// ========== 按 formHeadId 查询启用状态的规则 ==========
|
||||
@Override |
||||
public List<PermissionDataRuleSchema> listByFormId(Long formId) { |
||||
LambdaQueryWrapper<PermissionDataRuleSchema> qw = new LambdaQueryWrapper<>(); |
||||
qw.eq(PermissionDataRuleSchema::getFormHeadId, formId) |
||||
.eq(PermissionDataRuleSchema::getStatus, 1); |
||||
return this.list(qw); |
||||
} |
||||
|
||||
// ========== 新增(含 ID生成 + 默认状态) ==========
|
||||
@Override |
||||
public PermissionDataRuleSchema saveNew(PermissionDataRuleSchema schema) { |
||||
if (schema.getStatus() == null) { |
||||
schema.setStatus(1); |
||||
} |
||||
if (schema.getRuleType() == null) { |
||||
schema.setRuleType(DataRuleType.LIST); |
||||
} |
||||
this.save(schema); |
||||
return schema; |
||||
} |
||||
|
||||
// ========== 根据规则方案ID集合查询启用状态的规则(运行时用) ==========
|
||||
@Override |
||||
public List<PermissionDataRuleSchema> listEnabledByIds(Collection<Long> idSet) { |
||||
if (CollUtil.isEmpty(idSet)) { |
||||
return Collections.emptyList(); |
||||
} |
||||
LambdaQueryWrapper<PermissionDataRuleSchema> qw = new LambdaQueryWrapper<>(); |
||||
qw.in(PermissionDataRuleSchema::getId, idSet) |
||||
.eq(PermissionDataRuleSchema::getStatus, 1); |
||||
return this.list(qw); |
||||
} |
||||
|
||||
// ========== 填充绑定的在线表单列表(formId + formName),详情接口回显用 ==========
|
||||
@Override |
||||
public void fillFormNameList(PermissionDataRuleSchema schema) { |
||||
if (schema == null ){ |
||||
return; |
||||
} |
||||
List<Map<String, Object>> formList = new ArrayList<>(); |
||||
if (StrUtil.isNotBlank(schema.getFormIdList())) { |
||||
List<Long> formIds = StrUtil.split(schema.getFormIdList(), ',') |
||||
.stream().filter(StrUtil::isNotBlank).map(Long::valueOf).collect(Collectors.toList()); |
||||
if (CollUtil.isNotEmpty(formIds)) { |
||||
OnlineForm filter = new OnlineForm(); |
||||
filter.setFormIdSet(new HashSet<>(formIds)); |
||||
List<OnlineForm> formListData = onlineFormService.getOnlineFormList(filter, null); |
||||
if (CollUtil.isNotEmpty(formListData)) { |
||||
for (OnlineForm form : formListData) { |
||||
Map<String, Object> item = new HashMap<>(); |
||||
item.put("formId", form.getFormId()); |
||||
item.put("formName", form.getFormName()); |
||||
formList.add(item); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
schema.setFormList(formList); |
||||
} |
||||
|
||||
// ========== 核心校验: formHeadId → 主表/单表,自动填充 tableType ==========
|
||||
@Override |
||||
public CallResult verifyRelatedData(PermissionDataRuleSchema schema) { |
||||
// 1. 通过 formHeadId 查询 onl_form_head 获取 tableType
|
||||
OnlFormHead formHead = onlFormHeadService.getById(schema.getFormHeadId()); |
||||
if (formHead == null) { |
||||
return CallResult.error("数据验证失败,无法获取表类型信息,请检查表单配置!"); |
||||
} |
||||
|
||||
// 2. tableType 校验: 0=附表拒绝, 1=主表, 2=单表
|
||||
String tableType = formHead.getTableType(); |
||||
if (!"1".equals(tableType) && !"2".equals(tableType)) { |
||||
return CallResult.error("数据验证失败,仅主表和单表支持配置数据规则,当前为附表!"); |
||||
} |
||||
|
||||
// 3. 自动填充 tableType
|
||||
schema.setTableType(Integer.valueOf(tableType)); |
||||
|
||||
return CallResult.ok(); |
||||
} |
||||
} |
||||
@ -0,0 +1,305 @@
@@ -0,0 +1,305 @@
|
||||
package apelet.common.online.service.impl; |
||||
|
||||
import apelet.common.core.base.dao.BaseDaoMapper; |
||||
import apelet.common.core.base.service.BaseService; |
||||
import apelet.common.core.object.*; |
||||
import apelet.common.online.dao.RoleFormButtonMapper; |
||||
import apelet.common.online.dto.RoleFormButtonDto; |
||||
import apelet.common.online.model.OnlineForm; |
||||
import apelet.common.online.model.OnlinePage; |
||||
import apelet.common.online.model.RoleFormButton; |
||||
import apelet.common.online.service.IRoleFormButtonService; |
||||
import apelet.common.online.service.OnlineFormService; |
||||
import apelet.common.online.service.OnlinePageService; |
||||
import apelet.common.online.util.WidgetJsonButtonParser; |
||||
import apelet.common.sequence.wrapper.IdGeneratorWrapper; |
||||
import cn.hutool.core.collection.CollectionUtil; |
||||
import cn.hutool.core.util.StrUtil; |
||||
import com.alibaba.fastjson.JSON; |
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
||||
import com.github.pagehelper.Page; |
||||
import com.github.pagehelper.page.PageMethod; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.apache.commons.lang3.StringUtils; |
||||
import org.jetbrains.annotations.NotNull; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.stereotype.Service; |
||||
import org.springframework.transaction.annotation.Transactional; |
||||
import org.springframework.util.CollectionUtils; |
||||
|
||||
import java.util.*; |
||||
import java.util.stream.Collectors; |
||||
|
||||
/** |
||||
* 角色-表单按钮权限配置服务实现。 |
||||
* |
||||
* @author chenchuchuan |
||||
* @date 2026-07-17 |
||||
*/ |
||||
@Service |
||||
@Slf4j |
||||
public class RoleFormButtonServiceImpl extends BaseService<RoleFormButton, Long> implements IRoleFormButtonService { |
||||
|
||||
@Autowired |
||||
private RoleFormButtonMapper mapper; |
||||
|
||||
@Autowired |
||||
private IdGeneratorWrapper idGenerator; |
||||
|
||||
@Autowired |
||||
private OnlineFormService onlineFormService; |
||||
|
||||
@Autowired |
||||
private OnlinePageService onlinePageService; |
||||
|
||||
@Override |
||||
protected BaseDaoMapper<RoleFormButton> mapper() { |
||||
return mapper; |
||||
} |
||||
|
||||
@Override |
||||
public List<RoleFormButton> getListByFilter( |
||||
RoleFormButton filter, String orderBy) { |
||||
return mapper.getListByFilter(filter, orderBy); |
||||
} |
||||
|
||||
// ========== 核心:查询按钮配置VO(合并当前用户所有角色) ==========
|
||||
@Override |
||||
public Map<String, Set<String>> getButtonConfigVo(Long formId) { |
||||
try { |
||||
// 1. 获取当前登录用户的所有角色
|
||||
Set<Long> roleIds = getCurrentUserRoleIds(); |
||||
// Set<Long> roleIds = CollectionUtil.newHashSet(2080534691714174976L, 1694263314487447643L);
|
||||
if (CollectionUtil.isEmpty(roleIds)) { |
||||
return buildEmptyResultForAllForms(); |
||||
} |
||||
List<RoleFormButton> roleFormButtonList = mapper.selectList(new LambdaQueryWrapper<RoleFormButton>().in(RoleFormButton::getRoleId, roleIds)); |
||||
if (CollectionUtil.isEmpty(roleFormButtonList)) { |
||||
return buildEmptyResultForAllForms(); |
||||
} |
||||
// 2. 遍历所有角色,合并各角色在该表单的勾选按钮(取并集)
|
||||
Map<String, Set<String>> roleCheckedNamesMap = new HashMap<>(); |
||||
for (RoleFormButton roleFormButton : roleFormButtonList) { |
||||
if (StringUtils.isBlank(roleFormButton.getButtonConfig())) { |
||||
continue; |
||||
} |
||||
List<WidgetJsonButtonParser.ButtonInfo> buttonInfoList = JSON.parseArray(roleFormButton.getButtonConfig(), WidgetJsonButtonParser.ButtonInfo.class); |
||||
Set<String> checkedNames = buttonInfoList.stream().filter(btn -> btn.getEnabled() != null && btn.getEnabled()).map(WidgetJsonButtonParser.ButtonInfo::getName).collect(Collectors.toSet()); |
||||
roleCheckedNamesMap.computeIfAbsent(Long.toString(roleFormButton.getFormId()), k -> new HashSet<>()).addAll(checkedNames); |
||||
} |
||||
return roleCheckedNamesMap; |
||||
} catch (Exception e) { |
||||
log.error("获取按钮配置失败", e); |
||||
} |
||||
return buildEmptyResultForAllForms(); |
||||
} |
||||
|
||||
/** |
||||
* 获取当前登录用户的所有角色ID集合(从 TokenData 中解析)。 |
||||
*/ |
||||
private Set<Long> getCurrentUserRoleIds() { |
||||
TokenData tokenData = TokenData.takeFromRequest(); |
||||
if (tokenData == null || StrUtil.isBlank(tokenData.getRoleIds())) { |
||||
return Collections.emptySet(); |
||||
} |
||||
return StrUtil.split(tokenData.getRoleIds(), ',').stream().map(Long::valueOf).collect(Collectors.toSet()); |
||||
} |
||||
|
||||
/** |
||||
* 构建空结果 Map:查询所有已发布在线表单中有按钮的 formId,value 均为空 Set。 |
||||
* 确保前端在没有角色按钮权限配置时仍能拿到完整的 formId key 列表。 |
||||
*/ |
||||
private Map<String, Set<String>> buildEmptyResultForAllForms() { |
||||
Map<String, Set<String>> result = new HashMap<>(); |
||||
List<OnlinePage> pageList = onlinePageService.queryByEnableList(); |
||||
if (CollectionUtil.isEmpty(pageList)) { |
||||
return result; |
||||
} |
||||
Set<Long> pageIdSet = pageList.stream().map(OnlinePage::getPageId).collect(Collectors.toSet()); |
||||
OnlineForm filter = new OnlineForm(); |
||||
filter.setPageIdSet(pageIdSet); |
||||
List<OnlineForm> formList = onlineFormService.getOnlineFormList(filter, null); |
||||
for (OnlineForm form : formList) { |
||||
result.put(form.getFormId().toString(), Collections.emptySet()); |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
// ========== 核心:批量保存按钮配置(事务) ==========
|
||||
@Override |
||||
@Transactional(rollbackFor = Exception.class) |
||||
public CallResult batchSaveButtonConfig(List<RoleFormButtonDto> dtoList) { |
||||
for (RoleFormButtonDto dto : dtoList) { |
||||
CallResult result = this.saveButtonConfig(dto); |
||||
if (!result.isSuccess()) { |
||||
return result; |
||||
} |
||||
} |
||||
return CallResult.ok(); |
||||
} |
||||
|
||||
// ========== 核心:保存单条按钮配置(无独立事务,由外层统一管理) ==========
|
||||
@Override |
||||
public CallResult saveButtonConfig(RoleFormButtonDto dto) { |
||||
// 1. 必填校验
|
||||
if (dto.getRoleId() == null) { |
||||
return CallResult.error("数据验证失败,角色ID不能为空!"); |
||||
} |
||||
if (dto.getFormId() == null) { |
||||
return CallResult.error("数据验证失败,表单ID不能为空!"); |
||||
} |
||||
|
||||
// 2. 构建 button_config JSON
|
||||
List<Map<String, Object>> configList = new ArrayList<>(); |
||||
if (dto.getButtonList() != null) { |
||||
for (RoleFormButtonDto.ButtonItem item : dto.getButtonList()) { |
||||
Map<String, Object> configItem = new HashMap<>(); |
||||
configItem.put("name", item.getName()); |
||||
configItem.put("enabled", Objects.nonNull(item.getChecked()) && item.getChecked()); |
||||
configList.add(configItem); |
||||
} |
||||
} |
||||
String buttonConfigJson = JSON.toJSONString(configList); |
||||
|
||||
// 3. 查询是否已存在配置(role_id + form_id 唯一)
|
||||
RoleFormButton existing = |
||||
getByRoleIdAndFormId(dto.getRoleId(), dto.getFormId()); |
||||
|
||||
if (existing != null) { |
||||
// 更新
|
||||
existing.setButtonConfig(buttonConfigJson); |
||||
existing.setUpdateTime(new Date()); |
||||
this.updateById(existing); |
||||
} else { |
||||
// 新增
|
||||
RoleFormButton entity = new RoleFormButton(); |
||||
entity.setRoleId(dto.getRoleId()); |
||||
entity.setFormId(dto.getFormId()); |
||||
entity.setButtonConfig(buttonConfigJson); |
||||
entity.setId(idGenerator.nextLongId()); |
||||
this.save(entity); |
||||
} |
||||
|
||||
return CallResult.ok(); |
||||
} |
||||
|
||||
@Override |
||||
public List<RoleFormButton> listByRoleId(Long roleId) { |
||||
LambdaQueryWrapper<RoleFormButton> qw = new LambdaQueryWrapper<>(); |
||||
qw.eq(RoleFormButton::getRoleId, roleId); |
||||
return this.list(qw); |
||||
} |
||||
|
||||
@Override |
||||
public RoleFormButton getByRoleIdAndFormId(Long roleId, Long formId) { |
||||
LambdaQueryWrapper<RoleFormButton> qw = new LambdaQueryWrapper<>(); |
||||
qw.eq(RoleFormButton::getRoleId, roleId) |
||||
.eq(RoleFormButton::getFormId, formId); |
||||
return this.getOne(qw); |
||||
} |
||||
|
||||
@Override |
||||
@Transactional(rollbackFor = Exception.class) |
||||
public void removeByRoleId(Long roleId) { |
||||
LambdaQueryWrapper<RoleFormButton> qw = new LambdaQueryWrapper<>(); |
||||
qw.eq(RoleFormButton::getRoleId, roleId); |
||||
this.remove(qw); |
||||
} |
||||
|
||||
private static @NotNull Map<String, Object> getButtonResult(OnlineForm form, List<WidgetJsonButtonParser.ButtonInfo> buttons) { |
||||
Map<String, Object> item = new HashMap<>(); |
||||
item.put("formId", form.getFormId().toString()); |
||||
item.put("formName", form.getFormName()); |
||||
List<Map<String, Object>> buttonList = new ArrayList<>(); |
||||
if (!CollectionUtils.isEmpty(buttons)) { |
||||
for (WidgetJsonButtonParser.ButtonInfo btn : buttons) { |
||||
Map<String, Object> btnMap = new HashMap<>(); |
||||
btnMap.put("name", btn.getName()); |
||||
btnMap.put("checked", false); |
||||
buttonList.add(btnMap); |
||||
} |
||||
} |
||||
item.put("buttonList", buttonList); |
||||
return item; |
||||
} |
||||
|
||||
@Override |
||||
public MyPageData<Map<String, Object>> listAllFormButtons(Long roleId, MyOrderParam orderParam, MyPageParam pageParam) { |
||||
// 1. 查询已发布的 page(不分页,获取全量)
|
||||
List<OnlinePage> pageList = onlinePageService.queryByEnableList(); |
||||
if (CollectionUtil.isEmpty(pageList)) { |
||||
return new MyPageData<>(Collections.emptyList(), 0L); |
||||
} |
||||
Set<Long> pageIdSet = pageList.stream().map(OnlinePage::getPageId).collect(Collectors.toSet()); |
||||
|
||||
// 2. 分页查询 OnlineForm
|
||||
if (pageParam != null) { |
||||
PageMethod.startPage(pageParam.getPageNum(), pageParam.getPageSize()); |
||||
} |
||||
String orderBy = MyOrderParam.buildOrderBy(orderParam, OnlineForm.class); |
||||
OnlineForm onlineForm = new OnlineForm(); |
||||
onlineForm.setPageIdSet(pageIdSet); |
||||
List<OnlineForm> formList = onlineFormService.getOnlineFormList(onlineForm, orderBy); |
||||
long total = formList instanceof Page ? ((Page) formList).getTotal() : formList.size(); |
||||
|
||||
// 3. 如果传了 roleId,查询 RoleFormButton 获取勾选状态
|
||||
Map<Long, Set<String>> formCheckedMap = Collections.emptyMap(); |
||||
if (roleId != null) { |
||||
List<RoleFormButton> configList = this.listByRoleId(roleId); |
||||
if (CollectionUtil.isNotEmpty(configList)) { |
||||
formCheckedMap = new HashMap<>(configList.size()); |
||||
for (RoleFormButton config : configList) { |
||||
formCheckedMap.put(config.getFormId(), extractCheckedNames(config)); |
||||
} |
||||
} |
||||
} |
||||
|
||||
// 4. 构建结果
|
||||
List<Map<String, Object>> result = new ArrayList<>(); |
||||
for (OnlineForm form : formList) { |
||||
List<WidgetJsonButtonParser.ButtonInfo> buttons = |
||||
WidgetJsonButtonParser.extractEnabledButtons(form.getWidgetJson()); |
||||
Map<String, Object> item = getButtonResult(form, buttons); |
||||
if (roleId != null) { |
||||
Set<String> checkedNames = formCheckedMap.get(form.getFormId()); |
||||
if (CollectionUtil.isNotEmpty(checkedNames)) { |
||||
applyCheckedStatus(item, checkedNames); |
||||
} |
||||
} |
||||
result.add(item); |
||||
} |
||||
return new MyPageData<>(result, total); |
||||
} |
||||
|
||||
/** |
||||
* 从 RoleFormButton.buttonConfig 中提取已勾选的按钮名称集合。 |
||||
*/ |
||||
private Set<String> extractCheckedNames(RoleFormButton config) { |
||||
if (config.getButtonConfig() == null) { |
||||
return Collections.emptySet(); |
||||
} |
||||
List<WidgetJsonButtonParser.ButtonInfo> savedButtons = |
||||
JSON.parseArray(config.getButtonConfig(), WidgetJsonButtonParser.ButtonInfo.class); |
||||
if (CollectionUtils.isEmpty(savedButtons)) { |
||||
return Collections.emptySet(); |
||||
} |
||||
return savedButtons.stream() |
||||
.filter(b -> Boolean.TRUE.equals(b.getEnabled())) |
||||
.map(WidgetJsonButtonParser.ButtonInfo::getName) |
||||
.collect(Collectors.toSet()); |
||||
} |
||||
|
||||
/** |
||||
* 将已勾选的按钮名称集合应用到返回结果中。 |
||||
*/ |
||||
private void applyCheckedStatus(Map<String, Object> item, Set<String> checkedNames) { |
||||
@SuppressWarnings("unchecked") |
||||
List<Map<String, Object>> buttonList = |
||||
(List<Map<String, Object>>) item.get("buttonList"); |
||||
if (buttonList != null) { |
||||
for (Map<String, Object> btn : buttonList) { |
||||
btn.put("checked", checkedNames.contains(btn.get("name"))); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,89 @@
@@ -0,0 +1,89 @@
|
||||
package apelet.common.online.service.impl; |
||||
|
||||
import apelet.common.core.base.dao.BaseDaoMapper; |
||||
import apelet.common.core.base.service.BaseService; |
||||
import apelet.common.core.object.CallResult; |
||||
import apelet.common.online.dao.RolePermissionSchemaMapper; |
||||
import apelet.common.online.model.RolePermissionSchema; |
||||
import apelet.common.online.service.IPermissionDataRuleSchemaService; |
||||
import apelet.common.online.service.IRolePermissionSchemaService; |
||||
import cn.hutool.core.collection.CollUtil; |
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.stereotype.Service; |
||||
import org.springframework.transaction.annotation.Transactional; |
||||
|
||||
import java.util.Collection; |
||||
import java.util.Collections; |
||||
import java.util.HashSet; |
||||
import java.util.List; |
||||
|
||||
/** |
||||
* 角色-数据规则方案关联服务实现类。 |
||||
* |
||||
* @author chenchuchuan |
||||
* @date 2026-08-05 |
||||
*/ |
||||
@Service("rolePermissionSchemaService") |
||||
@Slf4j |
||||
public class RolePermissionSchemaServiceImpl extends BaseService<RolePermissionSchema, Long> implements IRolePermissionSchemaService { |
||||
|
||||
@Autowired |
||||
private RolePermissionSchemaMapper mapper; |
||||
|
||||
@Autowired |
||||
private IPermissionDataRuleSchemaService permissionDataRuleSchemaService; |
||||
|
||||
@Override |
||||
protected BaseDaoMapper<RolePermissionSchema> mapper() { |
||||
return mapper; |
||||
} |
||||
|
||||
@Override |
||||
public List<RolePermissionSchema> listByRoleId(Long roleId) { |
||||
LambdaQueryWrapper<RolePermissionSchema> qw = new LambdaQueryWrapper<>(); |
||||
qw.eq(RolePermissionSchema::getRoleId, roleId); |
||||
return this.list(qw); |
||||
} |
||||
|
||||
@Override |
||||
public List<RolePermissionSchema> listByRoleIds(Collection<Long> roleIds) { |
||||
if (CollUtil.isEmpty(roleIds)) { |
||||
return Collections.emptyList(); |
||||
} |
||||
LambdaQueryWrapper<RolePermissionSchema> qw = new LambdaQueryWrapper<>(); |
||||
qw.in(RolePermissionSchema::getRoleId, roleIds); |
||||
return this.list(qw); |
||||
} |
||||
|
||||
@Transactional(rollbackFor = Exception.class) |
||||
@Override |
||||
public CallResult bindRoleSchemas(Long roleId, List<Long> schemaIdList) { |
||||
// 1. 校验数据规则方案是否存在
|
||||
if (CollUtil.isNotEmpty(schemaIdList)) { |
||||
if (!permissionDataRuleSchemaService.existAllPrimaryKeys(new HashSet<>(schemaIdList))) { |
||||
return CallResult.error("数据验证失败,存在不合法的数据规则方案,请刷新后重试!"); |
||||
} |
||||
} |
||||
// 2. 先删除该角色原有的绑定
|
||||
this.removeByRoleId(roleId); |
||||
// 3. 再插入新的绑定
|
||||
if (CollUtil.isNotEmpty(schemaIdList)) { |
||||
for (Long schemaId : schemaIdList) { |
||||
RolePermissionSchema roleSchema = new RolePermissionSchema(); |
||||
roleSchema.setRoleId(roleId); |
||||
roleSchema.setSchemaId(schemaId); |
||||
this.save(roleSchema); |
||||
} |
||||
} |
||||
return CallResult.ok(); |
||||
} |
||||
|
||||
@Override |
||||
public void removeByRoleId(Long roleId) { |
||||
LambdaQueryWrapper<RolePermissionSchema> qw = new LambdaQueryWrapper<>(); |
||||
qw.eq(RolePermissionSchema::getRoleId, roleId); |
||||
this.remove(qw); |
||||
} |
||||
} |
||||
@ -0,0 +1,87 @@
@@ -0,0 +1,87 @@
|
||||
package apelet.common.online.util; |
||||
|
||||
import java.util.HashMap; |
||||
import java.util.Map; |
||||
|
||||
public class WidgetFieldTypeMapping { |
||||
|
||||
private WidgetFieldTypeMapping() { |
||||
} |
||||
|
||||
private static final Map<Integer, String> WIDGET_TO_FIELD_TYPE = new HashMap<>(); |
||||
private static final Map<Integer, Integer> DEFAULT_LENGTH = new HashMap<>(); |
||||
|
||||
static { |
||||
// 文本输入框 → varchar
|
||||
WIDGET_TO_FIELD_TYPE.put(1, "0"); |
||||
DEFAULT_LENGTH.put(1, 255); |
||||
// 数字输入框 → int
|
||||
WIDGET_TO_FIELD_TYPE.put(3, "1"); |
||||
// 开关组件 → int
|
||||
WIDGET_TO_FIELD_TYPE.put(5, "1"); |
||||
// 滑块组件 → decimal
|
||||
WIDGET_TO_FIELD_TYPE.put(6, "4"); |
||||
// 单选组件 → varchar
|
||||
WIDGET_TO_FIELD_TYPE.put(7, "0"); |
||||
DEFAULT_LENGTH.put(7, 255); |
||||
// 复选框 → varchar
|
||||
WIDGET_TO_FIELD_TYPE.put(8, "0"); |
||||
DEFAULT_LENGTH.put(8, 255); |
||||
// 下拉选择框 → varchar
|
||||
WIDGET_TO_FIELD_TYPE.put(10, "0"); |
||||
DEFAULT_LENGTH.put(10, 255); |
||||
// 级联选择框 → varchar
|
||||
WIDGET_TO_FIELD_TYPE.put(12, "0"); |
||||
DEFAULT_LENGTH.put(12, 255); |
||||
// 树形选择 → varchar
|
||||
WIDGET_TO_FIELD_TYPE.put(13, "0"); |
||||
DEFAULT_LENGTH.put(13, 255); |
||||
// 评分组件 → int
|
||||
WIDGET_TO_FIELD_TYPE.put(14, "1"); |
||||
DEFAULT_LENGTH.put(14, 5); |
||||
// 日期选择框 → datetime
|
||||
WIDGET_TO_FIELD_TYPE.put(20, "3"); |
||||
// 颜色选择 → varchar
|
||||
WIDGET_TO_FIELD_TYPE.put(30, "0"); |
||||
DEFAULT_LENGTH.put(30, 50); |
||||
// 附件上传 → varchar
|
||||
WIDGET_TO_FIELD_TYPE.put(31, "0"); |
||||
DEFAULT_LENGTH.put(31, 255); |
||||
// 图片上传 → varchar
|
||||
WIDGET_TO_FIELD_TYPE.put(653, "0"); |
||||
DEFAULT_LENGTH.put(653, 255); |
||||
// 富文本编辑 → varchar(2000)
|
||||
WIDGET_TO_FIELD_TYPE.put(32, "0"); |
||||
DEFAULT_LENGTH.put(32, 2000); |
||||
// MD编辑器 → varchar(2000)
|
||||
WIDGET_TO_FIELD_TYPE.put(760, "0"); |
||||
DEFAULT_LENGTH.put(760, 2000); |
||||
// 用户选择 → bigint
|
||||
WIDGET_TO_FIELD_TYPE.put(400, "2"); |
||||
// 部门选择 → bigint
|
||||
WIDGET_TO_FIELD_TYPE.put(401, "2"); |
||||
// 关联选择 → bigint
|
||||
WIDGET_TO_FIELD_TYPE.put(402, "2"); |
||||
// 时间选择器 → datetime
|
||||
WIDGET_TO_FIELD_TYPE.put(652, "3"); |
||||
// 文件上传 → varchar
|
||||
WIDGET_TO_FIELD_TYPE.put(750, "0"); |
||||
DEFAULT_LENGTH.put(750, 255); |
||||
} |
||||
|
||||
public static String getFieldTypeId(Integer widgetType) { |
||||
String type = WIDGET_TO_FIELD_TYPE.get(widgetType); |
||||
if (type == null) { |
||||
throw new IllegalArgumentException("不支持的控件类型: " + widgetType); |
||||
} |
||||
return type; |
||||
} |
||||
|
||||
public static Integer getDefaultLength(Integer widgetType) { |
||||
return DEFAULT_LENGTH.get(widgetType); |
||||
} |
||||
|
||||
public static boolean isDataWidget(Integer widgetType) { |
||||
return WIDGET_TO_FIELD_TYPE.containsKey(widgetType); |
||||
} |
||||
} |
||||
@ -0,0 +1,93 @@
@@ -0,0 +1,93 @@
|
||||
package apelet.common.online.util; |
||||
|
||||
import com.alibaba.fastjson.JSON; |
||||
import com.alibaba.fastjson.JSONArray; |
||||
import com.alibaba.fastjson.JSONObject; |
||||
import lombok.AllArgsConstructor; |
||||
import lombok.Data; |
||||
import lombok.NoArgsConstructor; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.HashSet; |
||||
import java.util.List; |
||||
import java.util.Set; |
||||
|
||||
/** |
||||
* widgetJson 按钮解析工具类。 |
||||
* 从在线表单的 widgetJson 中提取所有 operationList 下的按钮。 |
||||
* |
||||
* @author chenchuchuan |
||||
* @date 2026-07-17 |
||||
*/ |
||||
@Slf4j |
||||
public class WidgetJsonButtonParser { |
||||
|
||||
/** |
||||
* 按钮信息DTO(内部类,轻量)。 |
||||
*/ |
||||
@Data |
||||
@AllArgsConstructor |
||||
@NoArgsConstructor |
||||
public static class ButtonInfo { |
||||
/** 按钮名称 */ |
||||
private String name; |
||||
/** 是否在设计器中开启 */ |
||||
private Boolean enabled; |
||||
} |
||||
|
||||
/** |
||||
* 从 widgetJson 中提取所有开启的按钮(去重,按name)。 |
||||
* |
||||
* @param widgetJson 在线表单的 widget_json 字段值。 |
||||
* @return 按钮列表(只包含 enabled=true 的按钮,按 name 去重)。 |
||||
*/ |
||||
public static List<ButtonInfo> extractEnabledButtons(String widgetJson) { |
||||
List<ButtonInfo> allButtons = extractAllButtons(widgetJson); |
||||
List<ButtonInfo> result = new ArrayList<>(); |
||||
Set<String> seen = new HashSet<>(); |
||||
for (ButtonInfo btn : allButtons) { |
||||
if (Boolean.TRUE.equals(btn.getEnabled()) && seen.add(btn.getName())) { |
||||
result.add(btn); |
||||
} |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
/** |
||||
* 从 widgetJson 中提取所有按钮(不去重,不过滤)。 |
||||
* 仅提取 pc.operationList 和 mobile.operationList 两个顶层数组, |
||||
* 不递归 tableWidget、widgetList、childWidgetList 等子节点下的 operationList。 |
||||
* |
||||
* @param widgetJson 在线表单的 widget_json 字段值。 |
||||
* @return 全部按钮列表。 |
||||
*/ |
||||
public static List<ButtonInfo> extractAllButtons(String widgetJson) { |
||||
List<ButtonInfo> result = new ArrayList<>(); |
||||
if (widgetJson == null || widgetJson.isEmpty()) { |
||||
return result; |
||||
} |
||||
try { |
||||
JSONObject root = JSON.parseObject(widgetJson); |
||||
// 仅取 pc 和 mobile 两端的顶层 operationList
|
||||
for (String device : new String[]{"pc", "mobile"}) { |
||||
JSONObject deviceJson = root.getJSONObject(device); |
||||
if (deviceJson == null) continue; |
||||
JSONArray operationList = deviceJson.getJSONArray("operationList"); |
||||
if (operationList != null) { |
||||
for (int i = 0; i < operationList.size(); i++) { |
||||
JSONObject btn = operationList.getJSONObject(i); |
||||
String name = btn.getString("name"); |
||||
Boolean enabled = btn.getBoolean("enabled"); |
||||
if (name != null) { |
||||
result.add(new ButtonInfo(name, enabled != null ? enabled : false)); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} catch (Exception e) { |
||||
log.error("解析 widgetJson 失败", e); |
||||
} |
||||
return result; |
||||
} |
||||
} |
||||
@ -0,0 +1,410 @@
@@ -0,0 +1,410 @@
|
||||
package apelet.common.online.util; |
||||
|
||||
import apelet.common.core.constant.AppDeviceType; |
||||
import cn.hutool.core.collection.CollUtil; |
||||
import cn.hutool.core.map.MapUtil; |
||||
import cn.hutool.core.util.StrUtil; |
||||
import com.alibaba.fastjson.JSON; |
||||
import com.alibaba.fastjson.JSONArray; |
||||
import com.alibaba.fastjson.JSONObject; |
||||
|
||||
import java.util.HashMap; |
||||
import java.util.HashSet; |
||||
import java.util.Map; |
||||
import java.util.Set; |
||||
import java.util.function.Consumer; |
||||
|
||||
/** |
||||
* widgetJson 数据操作工具类。 |
||||
* 提供从在线表单 widgetJson 中提取字典、绑定字段、子表控件等信息的纯数据操作方法。 |
||||
* |
||||
* @author chenchuchuan |
||||
* @date 2026-08-06 |
||||
*/ |
||||
public class WidgetJsonUtil { |
||||
|
||||
private WidgetJsonUtil() { |
||||
} |
||||
|
||||
/** |
||||
* 从 widgetJson 中提取所有控件绑定的字典Id。 |
||||
* 仅遍历当前设备端(widgetList 及 childWidgetList)控件树中 props.dictInfo.dictId。 |
||||
* |
||||
* @param widgetJson 在线表单的 widgetJson。 |
||||
* @return 字典Id集合。 |
||||
*/ |
||||
public static Set<Long> extractDictIdSet(String widgetJson) { |
||||
Set<Long> dictIdSet = new HashSet<>(); |
||||
if (StrUtil.isBlank(widgetJson)) { |
||||
return dictIdSet; |
||||
} |
||||
JSONObject deviceData = JSON.parseObject(widgetJson).getJSONObject(AppDeviceType.getDeviceType()); |
||||
if (MapUtil.isEmpty(deviceData)) { |
||||
return dictIdSet; |
||||
} |
||||
JSONArray widgetListArray = deviceData.getJSONArray("widgetList"); |
||||
if (CollUtil.isEmpty(widgetListArray)) { |
||||
return dictIdSet; |
||||
} |
||||
for (int i = 0; i < widgetListArray.size(); i++) { |
||||
extractDictIdRecursively(widgetListArray.getJSONObject(i), dictIdSet); |
||||
} |
||||
return dictIdSet; |
||||
} |
||||
|
||||
/** |
||||
* 递归提取单个控件及其子控件中的字典Id。 |
||||
*/ |
||||
private static void extractDictIdRecursively(JSONObject widgetData, Set<Long> dictIdSet) { |
||||
JSONObject propsData = widgetData.getJSONObject("props"); |
||||
if (MapUtil.isNotEmpty(propsData)) { |
||||
JSONObject dictInfoData = propsData.getJSONObject("dictInfo"); |
||||
if (MapUtil.isNotEmpty(dictInfoData)) { |
||||
Long dictId = dictInfoData.getLong("dictId"); |
||||
if (dictId != null) { |
||||
dictIdSet.add(dictId); |
||||
} |
||||
} |
||||
} |
||||
JSONArray childWidgetArray = widgetData.getJSONArray("childWidgetList"); |
||||
if (CollUtil.isNotEmpty(childWidgetArray)) { |
||||
for (int i = 0; i < childWidgetArray.size(); i++) { |
||||
extractDictIdRecursively(childWidgetArray.getJSONObject(i), dictIdSet); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 收集子表控件(widgetType=100) bindData.tableName 的名称集合。 |
||||
* |
||||
* @param widgetJson 在线表单的 widgetJson。 |
||||
* @return 子表名称集合。 |
||||
*/ |
||||
public static Set<String> collectSubTableNames(JSONObject widgetJson) { |
||||
Set<String> tableNameSet = new HashSet<>(); |
||||
walkAllModes(widgetJson, widget -> { |
||||
JSONArray widgetList = widget.getJSONArray("widgetList"); |
||||
if (widgetList == null) { |
||||
return; |
||||
} |
||||
for (int i = 0; i < widgetList.size(); i++) { |
||||
JSONObject childWidget = widgetList.getJSONObject(i); |
||||
// 只处理 widgetType=100 的子表控件
|
||||
Integer widgetType = childWidget.getInteger("widgetType"); |
||||
if (widgetType == null || widgetType != 100) { |
||||
continue; |
||||
} |
||||
JSONObject bindData = childWidget.getJSONObject("bindData"); |
||||
if (bindData == null) { |
||||
continue; |
||||
} |
||||
if (bindData.containsKey("tableName")) { |
||||
tableNameSet.add(bindData.getString("tableName")); |
||||
} |
||||
} |
||||
}); |
||||
return tableNameSet; |
||||
} |
||||
|
||||
/** |
||||
* 收集需要新增的子表名称:widgetType=100 且 bindData 只有 tableName(无 tableId)的表。 |
||||
* 新增表下面通常不存在需要处理的字段,故不注册进字段 map(见 collectTableColumns), |
||||
* 仅在此单独收集用于建表。递归遍历 childWidgetList,与 buildTableFieldMap 取值保持一致。 |
||||
* |
||||
* @param widgetJson 在线表单的 widgetJson。 |
||||
* @return 新增子表名称集合。 |
||||
*/ |
||||
public static Set<String> collectNewSubTableNames(JSONObject widgetJson) { |
||||
Set<String> tableNameSet = new HashSet<>(); |
||||
if (widgetJson == null) return tableNameSet; |
||||
for (String mode : new String[]{"pc", "mobile"}) { |
||||
JSONObject modeObj = widgetJson.getJSONObject(mode); |
||||
if (modeObj == null) continue; |
||||
collectNewSubTableNames(modeObj.getJSONArray("widgetList"), tableNameSet); |
||||
JSONObject tableWidget = modeObj.getJSONObject("tableWidget"); |
||||
if (tableWidget != null) collectNewSubTableNames(tableWidget.getJSONArray("childWidgetList"), tableNameSet); |
||||
} |
||||
return tableNameSet; |
||||
} |
||||
|
||||
private static void collectNewSubTableNames(JSONArray widgetList, Set<String> tableNameSet) { |
||||
if (CollUtil.isEmpty(widgetList)) return; |
||||
for (int i = 0; i < widgetList.size(); i++) { |
||||
JSONObject widget = widgetList.getJSONObject(i); |
||||
if (widget == null) continue; |
||||
Integer widgetType = widget.getInteger("widgetType"); |
||||
if (widgetType != null && widgetType == 100) { |
||||
JSONObject bindData = widget.getJSONObject("bindData"); |
||||
if (bindData != null && StrUtil.isEmpty(bindData.getString("tableId"))) { |
||||
String tableName = bindData.getString("tableName"); |
||||
if (StrUtil.isNotEmpty(tableName)) tableNameSet.add(tableName); |
||||
} |
||||
continue; |
||||
} |
||||
JSONArray childWidgetList = widget.getJSONArray("childWidgetList"); |
||||
if (CollUtil.isNotEmpty(childWidgetList)) collectNewSubTableNames(childWidgetList, tableNameSet); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 统一收集 pc/mobile 控件树中所有"表-字段"绑定关系。 |
||||
* <p> |
||||
* 返回嵌套 Map:外层 key = 表标识(bindData.tableId;缺省用 bindData.tableName,用于尚未创建的新表), |
||||
* 内层 key = 字段名,value = 对应的控件对象(叶子字段控件 或 子表列对象)。 |
||||
* <p> |
||||
* 收集规则: |
||||
* <ul> |
||||
* <li>widgetType=100(子表组件):取 props.tableColumnList 各列,字段名取 showFieldName(兜底 showName/columnId),跳过 fieldType=1 的序号/合计列;</li> |
||||
* <li>widgetType=102(移动端子表组件):跳过,由移动端子表专用逻辑处理,避免重复;</li> |
||||
* <li>容器组件(childWidgetList 非空):递归收集;</li> |
||||
* <li>叶子控件:取 bindData.columnName 与 bindData.tableId/tableName。</li> |
||||
* </ul> |
||||
* mobile 兼容两种结构:widgetList(新)与 老 tableWidget → childWidgetList。 |
||||
* |
||||
* @param widgetJson 在线表单的 widgetJson。 |
||||
* @return 表标识 → (字段名 → 控件对象) 的嵌套映射。 |
||||
*/ |
||||
public static Map<String, Map<String, JSONObject>> buildTableFieldMap(JSONObject widgetJson) { |
||||
Map<String, Map<String, JSONObject>> tableFieldMap = new HashMap<>(); |
||||
if (widgetJson == null) return tableFieldMap; |
||||
for (String mode : new String[]{"pc", "mobile"}) { |
||||
JSONObject modeObj = widgetJson.getJSONObject(mode); |
||||
if (modeObj == null) continue; |
||||
// 新结构:widgetList(递归收集)
|
||||
collectTableFields(modeObj.getJSONArray("widgetList"), tableFieldMap); |
||||
// mobile 老结构兜底:tableWidget -> childWidgetList -> childWidgetList
|
||||
JSONObject tableWidget = modeObj.getJSONObject("tableWidget"); |
||||
if (tableWidget != null) collectTableFields(tableWidget.getJSONArray("childWidgetList"), tableFieldMap); |
||||
} |
||||
return tableFieldMap; |
||||
} |
||||
|
||||
/** |
||||
* 递归收集控件树中的"表-字段"绑定关系。 |
||||
*/ |
||||
private static void collectTableFields(JSONArray widgetList, Map<String, Map<String, JSONObject>> tableFieldMap) { |
||||
if (CollUtil.isEmpty(widgetList)) return; |
||||
for (int i = 0; i < widgetList.size(); i++) { |
||||
JSONObject widget = widgetList.getJSONObject(i); |
||||
if (widget == null) continue; |
||||
Integer widgetType = widget.getInteger("widgetType"); |
||||
if (widgetType != null && widgetType == 100) { |
||||
collectTableColumns(widget, tableFieldMap); |
||||
continue; |
||||
} |
||||
if (widgetType != null && widgetType == 102) continue; |
||||
JSONArray childWidgetList = widget.getJSONArray("childWidgetList"); |
||||
if (CollUtil.isNotEmpty(childWidgetList)) { |
||||
collectTableFields(childWidgetList, tableFieldMap); |
||||
continue; |
||||
} |
||||
JSONObject bindData = widget.getJSONObject("bindData"); |
||||
if (bindData == null) continue; |
||||
// 字段名优先取 bindData.columnName,兼容仅绑定 columnFieldName 的移动端字段
|
||||
String columnName = bindData.getString("columnName"); |
||||
if (StrUtil.isEmpty(columnName)) columnName = bindData.getString("columnFieldName"); |
||||
String tableKey = resolveTableKey(bindData); |
||||
if (StrUtil.isEmpty(columnName) || StrUtil.isEmpty(tableKey)) continue; |
||||
tableFieldMap.computeIfAbsent(tableKey, k -> new HashMap<>()).put(columnName, widget); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 提取子表组件(widgetType=100) props.tableColumnList 中各列。 |
||||
*/ |
||||
private static void collectTableColumns(JSONObject tableWidget, Map<String, Map<String, JSONObject>> tableFieldMap) { |
||||
JSONObject bindData = tableWidget.getJSONObject("bindData"); |
||||
if (bindData == null) { |
||||
return; |
||||
} |
||||
String tableKey = resolveTableKey(bindData); |
||||
// 新增表(无 tableId、只有 tableName)下面不会有需要处理的字段,不注册进字段 map,由建表逻辑单独收集
|
||||
if (StrUtil.isEmpty(tableKey) || !tableKey.matches("\\d+")) { |
||||
return; |
||||
} |
||||
Map<String, JSONObject> columnMap = tableFieldMap.computeIfAbsent(tableKey, k -> new HashMap<>()); |
||||
JSONObject props = tableWidget.getJSONObject("props"); |
||||
if (props == null) { |
||||
return; |
||||
} |
||||
JSONArray tableColumnList = props.getJSONArray("tableColumnList"); |
||||
if (CollUtil.isEmpty(tableColumnList)) { |
||||
return; |
||||
} |
||||
for (int i = 0; i < tableColumnList.size(); i++) { |
||||
JSONObject tableColumn = tableColumnList.getJSONObject(i); |
||||
Integer fieldType = tableColumn.getInteger("fieldType"); |
||||
if (fieldType != null && fieldType == 1) continue; |
||||
String columnIdStr = tableColumn.getString("columnId"); |
||||
String columnName; |
||||
if (StrUtil.isNotEmpty(columnIdStr) && !columnIdStr.matches("\\d+")) { |
||||
// 新增列:字段名存在 columnId 里(非数字),描述在 showName
|
||||
columnName = columnIdStr; |
||||
} else { |
||||
// 已有列:字段名在 showFieldName,兜底 showName
|
||||
columnName = tableColumn.getString("showFieldName"); |
||||
if (StrUtil.isEmpty(columnName)) columnName = tableColumn.getString("showName"); |
||||
} |
||||
if (StrUtil.isEmpty(columnName)) continue; |
||||
columnMap.put(columnName, tableColumn); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 解析控件所属表标识:优先 bindData.tableId,缺省用 bindData.tableName。 |
||||
*/ |
||||
private static String resolveTableKey(JSONObject bindData) { |
||||
String tableId = bindData.getString("tableId"); |
||||
if (StrUtil.isNotEmpty(tableId)) { |
||||
return tableId; |
||||
} |
||||
return bindData.getString("tableName"); |
||||
} |
||||
|
||||
/** |
||||
* 回填新列 columnId 到 widgetJson 中所有归属该表且字段名匹配的控件。 |
||||
* <p> |
||||
* 覆盖两类控件:叶子字段(bindData.columnName/columnFieldName == columnName)与 |
||||
* 子表列(widgetType=100 组件 props.tableColumnList 中 showFieldName/showName == columnName)。 |
||||
* 归属表用 tableId / tableName 双重校验,避免同名列误回填。 |
||||
* |
||||
* @param widgetJson widgetJson 根对象。 |
||||
* @param columnName 字段名。 |
||||
* @param columnId 新列 id。 |
||||
* @param tableId 所属表 id。 |
||||
* @param tableName 所属表名。 |
||||
*/ |
||||
public static void backfillColumnId(JSONObject widgetJson, String columnName, long columnId, Long tableId, String tableName) { |
||||
if (widgetJson == null) return; |
||||
for (String mode : new String[]{"pc", "mobile"}) { |
||||
JSONObject modeObj = widgetJson.getJSONObject(mode); |
||||
if (modeObj == null) continue; |
||||
backfillInWidgets(modeObj.getJSONArray("widgetList"), columnName, columnId, tableId, tableName); |
||||
JSONObject tableWidget = modeObj.getJSONObject("tableWidget"); |
||||
if (tableWidget != null) backfillInWidgets(tableWidget.getJSONArray("childWidgetList"), columnName, columnId, tableId, tableName); |
||||
} |
||||
} |
||||
|
||||
private static void backfillInWidgets(JSONArray widgetList, String columnName, long columnId, Long tableId, String tableName) { |
||||
if (CollUtil.isEmpty(widgetList)) return; |
||||
for (int i = 0; i < widgetList.size(); i++) { |
||||
JSONObject widget = widgetList.getJSONObject(i); |
||||
if (widget == null) continue; |
||||
Integer widgetType = widget.getInteger("widgetType"); |
||||
if (widgetType != null && widgetType == 100) { |
||||
backfillTableColumns(widget, columnName, columnId, tableId, tableName); |
||||
continue; |
||||
} |
||||
JSONArray childWidgetList = widget.getJSONArray("childWidgetList"); |
||||
if (CollUtil.isNotEmpty(childWidgetList)) backfillInWidgets(childWidgetList, columnName, columnId, tableId, tableName); |
||||
JSONObject bindData = widget.getJSONObject("bindData"); |
||||
if (bindData == null) continue; |
||||
String boundColumnName = bindData.getString("columnName"); |
||||
if (StrUtil.isEmpty(boundColumnName)) boundColumnName = bindData.getString("columnFieldName"); |
||||
if (!columnName.equals(boundColumnName)) continue; |
||||
String widgetTableKey = resolveTableKey(bindData); |
||||
if (!tableMatched(widgetTableKey, tableId, tableName)) continue; |
||||
bindData.put("columnId", columnId); |
||||
bindData.put("tableId", tableId); |
||||
bindData.put("columnFieldName", columnName); |
||||
} |
||||
} |
||||
|
||||
private static void backfillTableColumns(JSONObject tableWidget, String columnName, long columnId, Long tableId, String tableName) { |
||||
JSONObject bindData = tableWidget.getJSONObject("bindData"); |
||||
JSONObject props = tableWidget.getJSONObject("props"); |
||||
if (bindData == null || props == null) return; |
||||
String widgetTableKey = resolveTableKey(bindData); |
||||
if (!tableMatched(widgetTableKey, tableId, tableName)) return; |
||||
JSONArray tableColumnList = props.getJSONArray("tableColumnList"); |
||||
if (CollUtil.isEmpty(tableColumnList)) return; |
||||
for (int i = 0; i < tableColumnList.size(); i++) { |
||||
JSONObject tableColumn = tableColumnList.getJSONObject(i); |
||||
String colIdStr = tableColumn.getString("columnId"); |
||||
String boundColumnName; |
||||
if (StrUtil.isNotEmpty(colIdStr) && !colIdStr.matches("\\d+")) { |
||||
// 新增列:字段名存在 columnId 里(非数字)
|
||||
boundColumnName = colIdStr; |
||||
} else { |
||||
boundColumnName = tableColumn.getString("showFieldName"); |
||||
if (StrUtil.isEmpty(boundColumnName)) boundColumnName = tableColumn.getString("showName"); |
||||
} |
||||
if (columnName.equals(boundColumnName)) { |
||||
// 回写:columnId 写真实 id,原 columnId 值(字段名)移到 showFieldName
|
||||
tableColumn.put("columnId", columnId); |
||||
tableColumn.put("showFieldName", columnName); |
||||
} |
||||
} |
||||
} |
||||
|
||||
private static boolean tableMatched(String widgetTableKey, Long tableId, String tableName) { |
||||
if (StrUtil.isEmpty(widgetTableKey)) return false; |
||||
if (widgetTableKey.equals(String.valueOf(tableId))) return true; |
||||
return StrUtil.isNotEmpty(tableName) && widgetTableKey.equalsIgnoreCase(tableName); |
||||
} |
||||
|
||||
/** |
||||
* 在 widgetJson 中查找匹配 tableName 的子表控件(widgetType=100),回填 bindData.tableId。 |
||||
* |
||||
* @param widgetJson 在线表单的 widgetJson。 |
||||
* @param tableName 子表名称。 |
||||
* @param tableId 子表Id。 |
||||
*/ |
||||
public static void backfillTableId(JSONObject widgetJson, String tableName, Long tableId) { |
||||
if (widgetJson == null) return; |
||||
for (String mode : new String[]{"pc", "mobile"}) { |
||||
JSONObject modeObj = widgetJson.getJSONObject(mode); |
||||
if (modeObj == null) continue; |
||||
backfillTableIdInWidgets(modeObj.getJSONArray("widgetList"), tableName, tableId); |
||||
JSONObject tableWidget = modeObj.getJSONObject("tableWidget"); |
||||
if (tableWidget != null) { |
||||
backfillTableIdInWidgets(tableWidget.getJSONArray("childWidgetList"), tableName, tableId); |
||||
} |
||||
} |
||||
} |
||||
|
||||
private static void backfillTableIdInWidgets(JSONArray widgetList, String tableName, Long tableId) { |
||||
if (CollUtil.isEmpty(widgetList)) return; |
||||
for (int i = 0; i < widgetList.size(); i++) { |
||||
JSONObject widget = widgetList.getJSONObject(i); |
||||
if (widget == null) continue; |
||||
if (widget.getInteger("widgetType") != null && 100 == widget.getInteger("widgetType")) { |
||||
JSONObject bindData = widget.getJSONObject("bindData"); |
||||
if (bindData != null && tableName.equals(bindData.getString("tableName"))) { |
||||
bindData.put("tableId", tableId); |
||||
} |
||||
continue; |
||||
} |
||||
JSONArray childWidgetList = widget.getJSONArray("childWidgetList"); |
||||
if (CollUtil.isNotEmpty(childWidgetList)) backfillTableIdInWidgets(childWidgetList, tableName, tableId); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 遍历 "pc" 和 "mobile" 两个模式的控件树。 |
||||
*/ |
||||
private static void walkAllModes(JSONObject widgetJson, Consumer<JSONObject> visitor) { |
||||
for (String mode : new String[]{"pc", "mobile"}) { |
||||
JSONObject modeObj = widgetJson.getJSONObject(mode); |
||||
if (modeObj != null) { |
||||
walkWidgets(modeObj, visitor); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 递归遍历控件树,访问每个控件及其子控件(widgetList)。 |
||||
*/ |
||||
private static void walkWidgets(JSONObject node, Consumer<JSONObject> visitor) { |
||||
if (node == null) { |
||||
return; |
||||
} |
||||
visitor.accept(node); |
||||
JSONArray children = node.getJSONArray("widgetList"); |
||||
if (children != null) { |
||||
for (int i = 0; i < children.size(); i++) { |
||||
walkWidgets(children.getJSONObject(i), visitor); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,19 @@
@@ -0,0 +1,19 @@
|
||||
package apelet.common.online.vo; |
||||
|
||||
import apelet.common.online.model.PermissionDataRuleSchema; |
||||
import lombok.Data; |
||||
import lombok.EqualsAndHashCode; |
||||
|
||||
import java.io.Serializable; |
||||
|
||||
/** |
||||
* 权限数据规则方案表 VO。 |
||||
* |
||||
* @author chenchuchuan |
||||
* @date 2026-07-15 |
||||
*/ |
||||
@Data |
||||
@EqualsAndHashCode(callSuper = true) |
||||
public class PermissionDataRuleSchemaVo extends PermissionDataRuleSchema implements Serializable { |
||||
private static final long serialVersionUID = 1L; |
||||
} |
||||
@ -0,0 +1,17 @@
@@ -0,0 +1,17 @@
|
||||
package apelet.common.online.vo; |
||||
|
||||
import apelet.common.online.model.RoleFormButton; |
||||
import lombok.Data; |
||||
import lombok.EqualsAndHashCode; |
||||
|
||||
/** |
||||
* 角色-表单按钮权限配置VO对象。 |
||||
* |
||||
* @author chenchuchuan |
||||
* @date 2026-07-17 |
||||
*/ |
||||
@Data |
||||
@EqualsAndHashCode(callSuper = true) |
||||
public class RoleFormButtonVo extends RoleFormButton { |
||||
private static final long serialVersionUID = 1L; |
||||
} |
||||
Loading…
Reference in new issue