Compare commits

...

6 Commits
dev-v3 ... v3.0

Author SHA1 Message Date
xuwei-fit2cloud
5ce2c3e5d2 fix(分享): 修复社区版公共链接状态与访客权限控制 2026-09-23 23:01:01 +08:00
xuwei-fit2cloud
f7910e986b fix(installer): 升级时替换同类旧 JPA 数据库驱动 2026-09-23 20:49:27 +08:00
fit2cloud-chenyw
20c088992e chore: 同步索引 2026-09-23 19:22:09 +08:00
fit2cloud-chenyw
0f77ddc022 chore: 同步索引 2026-09-23 19:20:12 +08:00
wisonic-s
37a2e547d3 Merge branch 'dev-v3' into v3.0 2026-09-23 18:24:37 +08:00
tjlygdx
ebaa8425d6 fix: 增加repo信息 2026-09-01 12:57:20 +08:00
6 changed files with 113 additions and 42 deletions

View File

@@ -4,6 +4,7 @@ import com.auth0.jwt.JWT;
import io.dataease.constant.AuthConstant;
import io.dataease.exception.DEException;
import io.dataease.i18n.Translator;
import io.dataease.license.utils.LicenseUtil;
import io.dataease.share.dao.auto.entity.XpackShare;
import io.dataease.share.dao.auto.mapper.XpackShareRepository;
import io.dataease.utils.ServletUtils;
@@ -25,8 +26,9 @@ public class ShareVisitorPermissionManage {
}
public int creatorPermissions(Long resourceId) {
if (!LicenseUtil.licenseValid()) return 7;
var auth = io.dataease.utils.CommonBeanFactory.getBean(io.dataease.api.permissions.auth.api.InteractiveAuthApi.class);
if (auth == null) return 7;
if (auth == null) return 0;
var permission = auth.queryAuth(resourceId);
if (permission == null || permission.getWeight() <= 1) return 0;
if (permission.getWeight() == 9) return 7;
@@ -48,6 +50,8 @@ public class ShareVisitorPermissionManage {
var shares = repository.findAll((root, query, cb) -> cb.and(
cb.equal(root.get("creator"), uid), cb.equal(root.get("resourceId"), resourceId)));
if (shares.isEmpty()) DEException.throwException(Translator.get("i18n_share_operation_denied"));
// Community sharing retains its original operations, but still requires an existing link.
if (!LicenseUtil.licenseValid()) return 7;
// Be conservative if legacy duplicate shares exist.
return shares.stream().mapToInt(ShareVisitorPermissionManage::permissions).reduce(7, (a, b) -> a & b)
& creatorPermissions(resourceId);

View File

@@ -42,6 +42,9 @@ public class XpackShareServer implements XpackShareApi {
@PostMapping("/visitorPermissions")
public void saveVisitorPermissions(@RequestBody VisitorPermissionsRequest request) {
if (!io.dataease.license.utils.LicenseUtil.licenseValid()) {
DEException.throwException(io.dataease.i18n.Translator.get("i18n_share_operation_denied"));
}
if (request.resourceId() == null || request.visitorPermissions() == null
|| request.visitorPermissions() < 0 || request.visitorPermissions() > 7) {
DEException.throwException(io.dataease.i18n.Translator.get("i18n_share_operation_denied"));

View File

@@ -31,7 +31,12 @@
<div ref="shareDialogRef" class="share-dialog-container">
<div class="copy-link">
<div class="open-share flex-align-center">
<el-switch size="small" v-model="shareEnable" @change="enableSwitcher" />
<el-switch
size="small"
v-model="shareEnable"
:disabled="!shareInfoReady"
@change="enableSwitcher"
/>
{{ shareTips }}
</div>
<div v-if="shareEnable" class="custom-link-line">
@@ -189,7 +194,7 @@
</div>
</div>
<ShareVisitorPermissions
v-if="shareEnable"
v-if="shareEnable && !appearanceStore.getCommunity"
v-model="visitorChoices"
:allowed="state.detailInfo.allowedVisitorPermissions ?? 7"
/>
@@ -228,6 +233,7 @@
<script lang="ts" setup>
import ShareVisitorPermissions from './ShareVisitorPermissions.vue'
import { useAppearanceStoreWithOut } from '@/store/modules/appearance'
import dvShare from '@/assets/svg/dv-share.svg'
import icon_shareLabel_outlined from '@/assets/svg/icon_share-label_outlined.svg'
import deCopy from '@/assets/svg/de-copy.svg'
@@ -260,6 +266,8 @@ const props = defineProps({
weight: propTypes.number.def(0),
isButton: propTypes.bool.def(false)
})
const appearanceStore = useAppearanceStoreWithOut()
const shareInfoReady = ref(false)
const visitorChoices = ref<number[]>([])
const originUuid = ref('')
const customPwdRef = ref()
@@ -376,13 +384,15 @@ const copyInfo = async () => {
}
}
savingPermissions.value = true
await request.post({
url: '/share/visitorPermissions',
data: {
resourceId: props.resourceId,
visitorPermissions: visitorChoices.value.reduce((a, b) => a | b, 0)
}
})
if (!appearanceStore.getCommunity) {
await request.post({
url: '/share/visitorPermissions',
data: {
resourceId: props.resourceId,
visitorPermissions: visitorChoices.value.reduce((a, b) => a | b, 0)
}
})
}
formatLinkAddr()
let info = linkAddr.value
if (passwdEnable.value) {
@@ -429,6 +439,7 @@ const share = () => {
}
const loadShareInfo = (cb, resetPermissions = false) => {
shareInfoReady.value = false
showLoading()
const resourceId = props.resourceId
const url = `/share/detail/${resourceId}`
@@ -452,6 +463,10 @@ const loadShareInfo = (cb, resetPermissions = false) => {
originUuid.value = res.data.uuid
}
setPageInfo()
shareInfoReady.value = true
})
.catch(() => {
// The request interceptor reports the error; do not allow toggling an unknown state.
})
.finally(() => {
closeLoading()
@@ -460,8 +475,8 @@ const loadShareInfo = (cb, resetPermissions = false) => {
}
const setPageInfo = () => {
if (state.detailInfo.id && state.detailInfo.uuid) {
shareEnable.value = true
shareEnable.value = !!(state.detailInfo.id && state.detailInfo.uuid)
if (shareEnable.value) {
formatLinkAddr()
}
passwdEnable.value = !!state.detailInfo.pwd
@@ -469,6 +484,8 @@ const setPageInfo = () => {
}
const enableSwitcher = () => {
if (!shareInfoReady.value) return
shareInfoReady.value = false
showLoading()
const resourceId = props.resourceId
const url = `/share/switcher/${resourceId}`
@@ -477,6 +494,9 @@ const enableSwitcher = () => {
.then(() => {
loadShareInfo(null)
})
.catch(() => {
loadShareInfo(null)
})
.finally(() => {
closeLoading()
})

View File

@@ -27,7 +27,12 @@
<div v-if="!shareDisable" ref="shareContainerRef" class="share-container">
<div class="share-title share-padding">{{ t('work_branch.public_link_share') }}</div>
<div class="open-share flex-align-center share-padding">
<el-switch size="small" v-model="shareEnable" @change="enableSwitcher" />
<el-switch
size="small"
v-model="shareEnable"
:disabled="!shareInfoReady"
@change="enableSwitcher"
/>
{{ shareTips }}
</div>
<div v-if="shareEnable" class="custom-link-line share-padding">
@@ -189,7 +194,7 @@
</div>
<ShareVisitorPermissions
v-if="shareEnable"
v-if="shareEnable && !appearanceStore.getCommunity"
v-model="visitorChoices"
:allowed="state.detailInfo.allowedVisitorPermissions ?? 7"
/>
@@ -229,6 +234,7 @@
<script lang="ts" setup>
import ShareVisitorPermissions from './ShareVisitorPermissions.vue'
import { useAppearanceStoreWithOut } from '@/store/modules/appearance'
import icon_shareLabel_outlined from '@/assets/svg/icon_share-label_outlined.svg'
import icon_edit_outlined from '@/assets/svg/icon_edit_outlined.svg'
import icon_close_outlined from '@/assets/svg/icon_close_outlined.svg'
@@ -345,6 +351,8 @@ const shareTips = computed(
const shareDisable = computed(() => shareStore.getShareDisable)
const sharePeRequire = computed(() => shareStore.getSharePeRequire)
const appearanceStore = useAppearanceStoreWithOut()
const shareInfoReady = ref(false)
const visitorChoices = ref<number[]>([])
const savingPermissions = ref(false)
const copyInfo = async () => {
@@ -366,13 +374,15 @@ const copyInfo = async () => {
}
}
savingPermissions.value = true
await request.post({
url: '/share/visitorPermissions',
data: {
resourceId: props.resourceId,
visitorPermissions: visitorChoices.value.reduce((a, b) => a | b, 0)
}
})
if (!appearanceStore.getCommunity) {
await request.post({
url: '/share/visitorPermissions',
data: {
resourceId: props.resourceId,
visitorPermissions: visitorChoices.value.reduce((a, b) => a | b, 0)
}
})
}
formatLinkAddr()
let info = linkAddr.value
if (passwdEnable.value) {
@@ -416,6 +426,7 @@ const share = () => {
}
const loadShareInfo = (cb, resetPermissions = false) => {
shareInfoReady.value = false
showLoading()
const resourceId = props.resourceId
const url = `/share/detail/${resourceId}`
@@ -439,6 +450,10 @@ const loadShareInfo = (cb, resetPermissions = false) => {
originUuid.value = res.data.uuid
}
setPageInfo()
shareInfoReady.value = true
})
.catch(() => {
// The request interceptor reports the error; do not allow toggling an unknown state.
})
.finally(() => {
closeLoading()
@@ -460,6 +475,8 @@ const setPageInfo = () => {
}
const enableSwitcher = () => {
if (!shareInfoReady.value) return
shareInfoReady.value = false
showLoading()
const resourceId = props.resourceId
const url = `/share/switcher/${resourceId}`
@@ -468,6 +485,9 @@ const enableSwitcher = () => {
.then(() => {
loadShareInfo(null)
})
.catch(() => {
loadShareInfo(null)
})
.finally(() => {
closeLoading()
})

View File

@@ -32,42 +32,42 @@ function db_defaults() {
mysql)
DB_DEFAULT_PORT=3306
DB_DEFAULT_PARAMS="autoReconnect=false&useUnicode=true&characterEncoding=UTF-8&characterSetResults=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true"
DB_DRIVER_JAR="mariadb-java-client-3.3.6.jar"
DB_DRIVER_PATTERN="mariadb-java-client-*.jar"
;;
pg)
DB_DEFAULT_PORT=5432
DB_DEFAULT_PARAMS="connectTimeout=10"
DB_DRIVER_JAR="postgresql-42.7.11.jar"
DB_DRIVER_PATTERN="postgresql-*.jar"
;;
oracle)
DB_DEFAULT_PORT=1521
DB_DEFAULT_PARAMS="useUnicode=true&characterEncoding=UTF-8"
DB_DRIVER_JAR="ojdbc10-19.19.0.0.jar"
DB_DRIVER_PATTERN="ojdbc[0-9]*.jar"
;;
dm)
DB_DEFAULT_PORT=5236
DB_DEFAULT_PARAMS="charset=UTF-8&loginTimeout=10&connectTimeout=10000"
DB_DRIVER_JAR="DmJdbcDriver18.jar"
DB_DRIVER_PATTERN="DmJdbcDriver*.jar"
;;
kingbase)
DB_DEFAULT_PORT=54321
DB_DEFAULT_PARAMS="connectTimeout=10"
DB_DRIVER_JAR="kingbase8-9.0.1.jar"
DB_DRIVER_PATTERN="kingbase[0-9]*.jar"
;;
sqlserver)
DB_DEFAULT_PORT=1433
DB_DEFAULT_PARAMS="encrypt=false"
DB_DRIVER_JAR="mssql-jdbc-13.4.0.jre11.jar"
DB_DRIVER_PATTERN="mssql-jdbc-*.jar"
;;
greatsql)
DB_DEFAULT_PORT=3306
DB_DEFAULT_PARAMS="autoReconnect=false&useUnicode=true&characterEncoding=UTF-8&characterSetResults=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true"
DB_DRIVER_JAR="mariadb-java-client-3.3.6.jar"
DB_DRIVER_PATTERN="mariadb-java-client-*.jar"
;;
*)
DB_DEFAULT_PORT=3306
DB_DEFAULT_PARAMS=""
DB_DRIVER_JAR=""
DB_DRIVER_PATTERN=""
;;
esac
}
@@ -138,23 +138,47 @@ function prompt_database_config() {
log_content "数据库类型: ${DE_DB_TYPE}, 使用外置数据库: ${DE_EXTERNAL_DB}"
}
# 复制数据库驱动到 data/driver
# 同类 JPA 驱动只保留安装包版本,避免新旧 jar 同时被加载。
function copy_db_driver() {
db_defaults "$DE_DB_TYPE"
if [[ -z "$DB_DRIVER_JAR" ]]; then
log_content "[警告] ${DE_DB_TYPE} 未配置驱动 jar,请确认 drivers 目录包含对应驱动"
return
if [[ -z "$DB_DRIVER_PATTERN" ]]; then
log_content "[错误] ${DE_DB_TYPE} 未配置驱动类型"
return 1
fi
local drivers_folder="${CURRENT_DIR}/drivers"
[[ -d "$drivers_folder" ]] || drivers_folder="${CURRENT_DIR}/../drivers"
local driver_source="${drivers_folder}/${DB_DRIVER_JAR}"
if [[ -f "$driver_source" ]]; then
mkdir -p ${DE_RUN_BASE}/data/driver
cp "$driver_source" ${DE_RUN_BASE}/data/driver/
log_content "已复制数据库驱动: ${DB_DRIVER_JAR}"
else
log_content "[警告] 未找到驱动 ${driver_source},请检查安装包是否包含该驱动"
local driver_source driver_file
local driver_sources=()
# 模式刻意不加引号,以匹配版本变化;目录仍保留引号以支持空格。
for driver_file in "$drivers_folder"/$DB_DRIVER_PATTERN; do
[[ -f "$driver_file" ]] && driver_sources+=("$driver_file")
done
if [[ ${#driver_sources[@]} -ne 1 ]]; then
log_content "[错误] 安装包必须包含唯一的 ${DE_DB_TYPE} 驱动(${DB_DRIVER_PATTERN}),实际找到 ${#driver_sources[@]} 个"
return 1
fi
driver_source="${driver_sources[0]}"
local driver_name="${driver_source##*/}"
local driver_target="${DE_RUN_BASE}/data/driver"
local staged_driver
mkdir -p "$driver_target" || return 1
staged_driver=$(mktemp "$driver_target/.jpa-driver.XXXXXX") || return 1
# 完整复制后再替换,复制失败时保留原有驱动。
if ! cp "$driver_source" "$staged_driver" || ! chmod 644 "$staged_driver" ||
! mv -f "$staged_driver" "$driver_target/$driver_name"; then
rm -f "$staged_driver"
log_content "[错误] 复制数据库驱动失败: ${driver_name}"
return 1
fi
for driver_file in "$driver_target"/$DB_DRIVER_PATTERN; do
[[ -e "$driver_file" || -L "$driver_file" ]] || continue
[[ "$driver_file" == "$driver_target/$driver_name" ]] && continue
if ! rm -f "$driver_file"; then
log_content "[错误] 清理旧数据库驱动失败: ${driver_file}"
return 1
fi
done
log_content "已更新数据库驱动: ${driver_name}"
}
function check_and_prepare_env_params() {
@@ -277,7 +301,7 @@ function prepare_de_run_base() {
fi
# 复制数据库驱动到 data/driver
copy_db_driver
copy_db_driver || exit 1
# 内置地图由镜像 map-origin 提供,持久化 map 仅保存用户覆盖文件
}