Compare commits
27 Commits
master
...
feature/fo
| Author | SHA1 | Date |
|---|---|---|
|
|
cefb7ecf67 | 2 months ago |
|
|
bb30a3454a | 2 months ago |
|
|
1bae360e2a | 2 months ago |
|
|
f9ecb2aaef | 2 months ago |
|
|
bc20c40671 | 2 months ago |
|
|
7045cdea35 | 2 months ago |
|
|
9ec9758bd5 | 2 months ago |
|
|
88d393d1f7 | 2 months ago |
|
|
b2dc6af80f | 2 months ago |
|
|
b6e8b1914e | 2 months ago |
|
|
f3aa6ab636 | 2 months ago |
|
|
65f2f7938d | 2 months ago |
|
|
1f96a131a6 | 2 months ago |
|
|
b8a4b5727f | 2 months ago |
|
|
cb1b2dd9c3 | 2 months ago |
|
|
eadce13994 | 2 months ago |
|
|
35ee89c750 | 2 months ago |
|
|
9f5efe49e3 | 2 months ago |
|
|
3104f1b026 | 2 months ago |
|
|
6fc400ab77 | 2 months ago |
|
|
e9c3b36512 | 2 months ago |
|
|
7dcb3bb5c9 | 2 months ago |
|
|
f405113552 | 2 months ago |
|
|
15dd345744 | 2 months ago |
|
|
1d42dfc702 | 2 months ago |
|
|
c665cde835 | 2 months ago |
|
|
610a4d10ed | 2 months ago |
68 changed files with 3581 additions and 198 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,184 @@
@@ -0,0 +1,184 @@
|
||||
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); |
||||
} |
||||
return ResponseResult.success(schema); |
||||
} |
||||
} |
||||
@ -0,0 +1,117 @@
@@ -0,0 +1,117 @@
|
||||
package apelet.common.online.controller; |
||||
|
||||
import apelet.common.core.annotation.MyRequestBody; |
||||
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") |
||||
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,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,138 @@
@@ -0,0 +1,138 @@
|
||||
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; |
||||
|
||||
/** |
||||
* 权限数据规则方案表 实体对象。 |
||||
* |
||||
* @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_id") |
||||
@NotNull(message = "数据验证失败,form_id不能为空!") |
||||
private Long formId; |
||||
|
||||
/** |
||||
* 表单元数据编码。 |
||||
*/ |
||||
@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; |
||||
|
||||
// ========== 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,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,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.PermissionDataRuleSchema; |
||||
|
||||
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); |
||||
} |
||||
@ -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,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,93 @@
@@ -0,0 +1,93 @@
|
||||
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.PermissionDataRuleSchema; |
||||
import apelet.common.online.model.constant.DataRuleType; |
||||
import apelet.common.online.service.IPermissionDataRuleSchemaService; |
||||
import apelet.common.sequence.wrapper.IdGeneratorWrapper; |
||||
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.List; |
||||
|
||||
/** |
||||
* 权限数据规则方案表 服务实现类。 |
||||
* |
||||
* @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; |
||||
|
||||
// ========== BaseService 要求重写 ==========
|
||||
@Override |
||||
protected BaseDaoMapper<PermissionDataRuleSchema> mapper() { |
||||
return mapper; |
||||
} |
||||
|
||||
// ========== 分页查询 ==========
|
||||
@Override |
||||
public List<PermissionDataRuleSchema> getListByFilter(PermissionDataRuleSchema filter, String orderBy) { |
||||
return mapper.getListByFilter(filter, orderBy); |
||||
} |
||||
|
||||
// ========== 按 formId 查询启用状态的规则 ==========
|
||||
@Override |
||||
public List<PermissionDataRuleSchema> listByFormId(Long formId) { |
||||
LambdaQueryWrapper<PermissionDataRuleSchema> qw = new LambdaQueryWrapper<>(); |
||||
qw.eq(PermissionDataRuleSchema::getFormId, 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; |
||||
} |
||||
|
||||
// ========== 核心校验: formId → 主表/单表,自动填充 tableType ==========
|
||||
@Override |
||||
public CallResult verifyRelatedData(PermissionDataRuleSchema schema) { |
||||
// 1. 通过 tableName 查询 onl_form_head 获取 tableType
|
||||
OnlFormHead formHead = onlFormHeadService.getById(schema.getFormId()); |
||||
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,280 @@
@@ -0,0 +1,280 @@
|
||||
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) { |
||||
// 1. 获取当前登录用户的所有角色
|
||||
Set<Long> roleIds = getCurrentUserRoleIds(); |
||||
// Set<Long> roleIds = CollectionUtil.newHashSet(2080534691714174976L, 1694263314487447643L);
|
||||
if (CollectionUtil.isEmpty(roleIds)) { |
||||
return Collections.emptyMap(); |
||||
} |
||||
List<RoleFormButton> roleFormButtonList = mapper.selectList(new LambdaQueryWrapper<RoleFormButton>().in(RoleFormButton::getRoleId, roleIds)); |
||||
if (CollectionUtil.isEmpty(roleFormButtonList)) { |
||||
return Collections.emptyMap(); |
||||
} |
||||
// 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; |
||||
} |
||||
|
||||
/** |
||||
* 获取当前登录用户的所有角色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()); |
||||
} |
||||
|
||||
// ========== 核心:批量保存按钮配置(事务) ==========
|
||||
@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,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,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