diff --git a/core/core-backend/src/main/java/io/dataease/share/manage/XpackShareManage.java b/core/core-backend/src/main/java/io/dataease/share/manage/XpackShareManage.java index fabaf8359a..1a8974802c 100644 --- a/core/core-backend/src/main/java/io/dataease/share/manage/XpackShareManage.java +++ b/core/core-backend/src/main/java/io/dataease/share/manage/XpackShareManage.java @@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import io.dataease.api.visualization.request.VisualizationWorkbranchQueryRequest; import io.dataease.api.xpack.share.request.XpackShareProxyRequest; import io.dataease.api.xpack.share.request.XpackSharePwdValidator; +import io.dataease.api.xpack.share.request.XpackShareUuidEditor; import io.dataease.api.xpack.share.vo.XpackShareGridVO; import io.dataease.api.xpack.share.vo.XpackShareProxyVO; import io.dataease.auth.bo.TokenUserBO; @@ -31,6 +32,8 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import java.util.stream.Collectors; @Component("xpackShareManage") @@ -70,6 +73,35 @@ public class XpackShareManage { xpackShareMapper.insert(xpackShare); } + public String editUuid(XpackShareUuidEditor editor) { + Long resourceId = editor.getResourceId(); + String uuid = editor.getUuid(); + XpackShare originData = queryByResource(resourceId); + if (ObjectUtils.isEmpty(originData)) { + return "公共链接不存在,请先创建!"; + } + if (StringUtils.isBlank(uuid)) { + return "不能为空!"; + } + if (StringUtils.equals(uuid, originData.getUuid())) { + return ""; + } + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("uuid", uuid); + if (xpackShareMapper.selectCount(queryWrapper) > 0) { + return "已存在相同的链接,请重新输入!"; + } + String regex = "^[a-zA-Z0-9]{8,16}$"; + Pattern pattern = Pattern.compile(regex); + Matcher matcher = pattern.matcher(uuid); + if (!matcher.matches()) { + return "仅支持8-16位(字母数字),请重新输入!"; + } + originData.setUuid(uuid); + xpackShareMapper.updateById(originData); + return ""; + } + public void editExp(Long resourceId, Long exp) { XpackShare originData = queryByResource(resourceId); if (ObjectUtils.isEmpty(originData)) { @@ -92,6 +124,8 @@ public class XpackShareManage { xpackShareMapper.updateById(originData); } + + public IPage querySharePage(int goPage, int pageSize, VisualizationWorkbranchQueryRequest request) { Long uid = AuthUtils.getUser().getUserId(); QueryWrapper queryWrapper = new QueryWrapper<>(); @@ -170,16 +204,16 @@ public class XpackShareManage { if (StringUtils.isBlank(xpackShare.getPwd())) return true; if (StringUtils.isBlank(ciphertext)) return false; String text = RsaUtils.decryptStr(ciphertext); - int splitIndex = 8; - String pwd = text.substring(splitIndex); + int splitIndex = text.indexOf(","); + String pwd = text.substring(splitIndex + 1); String uuid = text.substring(0, splitIndex); return StringUtils.equals(xpackShare.getUuid(), uuid) && StringUtils.equals(xpackShare.getPwd(), pwd); } public boolean validatePwd(XpackSharePwdValidator validator) { String ciphertext = RsaUtils.decryptStr(validator.getCiphertext()); - int splitIndex = 8; - String pwd = ciphertext.substring(splitIndex); + int splitIndex = ciphertext.indexOf(","); + String pwd = ciphertext.substring(splitIndex + 1); String uuid = ciphertext.substring(0, splitIndex); QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("uuid", uuid); diff --git a/core/core-backend/src/main/java/io/dataease/share/server/XpackShareServer.java b/core/core-backend/src/main/java/io/dataease/share/server/XpackShareServer.java index 11d2b412e6..12267140b9 100644 --- a/core/core-backend/src/main/java/io/dataease/share/server/XpackShareServer.java +++ b/core/core-backend/src/main/java/io/dataease/share/server/XpackShareServer.java @@ -2,10 +2,7 @@ package io.dataease.share.server; import io.dataease.api.visualization.request.VisualizationWorkbranchQueryRequest; import io.dataease.api.xpack.share.XpackShareApi; -import io.dataease.api.xpack.share.request.XpackShareExpRequest; -import io.dataease.api.xpack.share.request.XpackShareProxyRequest; -import io.dataease.api.xpack.share.request.XpackSharePwdRequest; -import io.dataease.api.xpack.share.request.XpackSharePwdValidator; +import io.dataease.api.xpack.share.request.*; import io.dataease.api.xpack.share.vo.XpackShareGridVO; import io.dataease.api.xpack.share.vo.XpackShareProxyVO; import io.dataease.api.xpack.share.vo.XpackShareVO; @@ -70,7 +67,12 @@ public class XpackShareServer implements XpackShareApi { } @Override - public Map queryRelationByUserId(@PathVariable("uid") Long uid) { + public Map queryRelationByUserId(Long uid) { return xpackShareManage.queryRelationByUserId(uid); } + + @Override + public String editUuid(XpackShareUuidEditor editor) { + return xpackShareManage.editUuid(editor); + } } diff --git a/core/core-frontend/src/views/share/link/pwd.vue b/core/core-frontend/src/views/share/link/pwd.vue index f43a30047e..c45764989a 100644 --- a/core/core-frontend/src/views/share/link/pwd.vue +++ b/core/core-frontend/src/views/share/link/pwd.vue @@ -50,6 +50,8 @@ import { rsaEncryp } from '@/utils/encryption' import { useCache } from '@/hooks/web/useCache' import { queryDekey } from '@/api/login' import { CustomPassword } from '@/components/custom-password' +import { useRoute } from 'vue-router' +const route = useRoute() const { wsCache } = useCache() const appStore = useAppStoreWithOut() @@ -76,15 +78,9 @@ const refresh = async (formEl: FormInstance | undefined) => { if (!formEl) return await formEl.validate((valid, fields) => { if (valid) { - const curLocation = window.location.href - const paramIndex = curLocation.indexOf('?') - const uuidIndex = curLocation.indexOf('de-link/') + 8 - const uuid = curLocation.substring( - uuidIndex, - paramIndex !== -1 ? paramIndex : curLocation.length - ) + const uuid = route.params.uuid const pwd = form.value.password - const text = uuid + pwd + const text = `${uuid},${pwd}` const ciphertext = rsaEncryp(text) request.post({ url: '/share/validate', data: { ciphertext } }).then(res => { if (res.data) { @@ -100,6 +96,7 @@ const refresh = async (formEl: FormInstance | undefined) => { }) } onMounted(() => { + debugger if (!wsCache.get(appStore.getDekey)) { queryDekey() .then(res => { diff --git a/core/core-frontend/src/views/share/share/ShareHandler.vue b/core/core-frontend/src/views/share/share/ShareHandler.vue index 8eb69653eb..0a2b87bfa2 100644 --- a/core/core-frontend/src/views/share/share/ShareHandler.vue +++ b/core/core-frontend/src/views/share/share/ShareHandler.vue @@ -31,7 +31,26 @@ {{ shareTips }} -
{{ linkAddr }}
+ +
`开启后,用户可以通过该链接访问${props.resourceType === 'dashboard' ? '仪表板' : '数据大屏'}` ) +const editUuid = () => { + linkCustom.value = true + nextTick(() => { + if (linkUuidRef?.value) { + linkUuidRef.value.input.focus() + } + }) +} +const validateUuid = async () => { + const val = state.detailInfo.uuid + const className = 'link-uuid-error-msg' + if (!val) { + showPageError('不能为空!', linkUuidRef, className) + return false + } + const regex = /^[a-zA-Z0-9]{8,16}$/ + const result = regex.test(val) + if (!result) { + showPageError('仅支持8-16位(字母数字),请重新输入!', linkUuidRef, className) + } else { + const msg = await uuidValidateApi(val) + showPageError(msg, linkUuidRef, className) + return !msg + } + return result +} + +const uuidValidateApi = async val => { + const url = '/share/editUuid' + const data = { resourceId: props.resourceId, uuid: val } + const res = await request.post({ url, data }) + return res.data +} +const finishEditUuid = async () => { + const uuidValid = await validateUuid() + linkCustom.value = !uuidValid +} const copyPwd = async () => { if (shareEnable.value && passwdEnable.value) { - if (!state.detailInfo.autoPwd && existErrorMsg()) { + if (!state.detailInfo.autoPwd && existErrorMsg('link-pwd-error-msg')) { ElMessage.warning('密码格式错误,请重新填写!') return } @@ -166,6 +224,10 @@ const copyPwd = async () => { const copyInfo = async () => { if (shareEnable.value) { try { + if (existErrorMsg('link-uuid-error-msg')) { + ElMessage.warning('链接格式错误,请重新填写!') + return + } await toClipboard(linkAddr.value) ElMessage.success(t('common.copy_success')) } catch (e) { @@ -226,6 +288,9 @@ const enableSwitcher = () => { } const formatLinkAddr = () => { + linkAddr.value = formatLinkBase() + state.detailInfo.uuid +} +const formatLinkBase = () => { let prefix = '/' if (window.DataEaseBi?.baseUrl) { prefix = window.DataEaseBi.baseUrl + '#' @@ -233,7 +298,7 @@ const formatLinkAddr = () => { const href = window.location.href prefix = href.substring(0, href.indexOf('#') + 1) } - linkAddr.value = prefix + SHARE_BASE + state.detailInfo.uuid + return prefix + SHARE_BASE } const expEnableSwitcher = val => { @@ -260,32 +325,36 @@ const expChangeHandler = exp => { loadShareInfo() }) } -const beforeClose = done => { - if (validatePwdFormat()) { +const beforeClose = async done => { + const pwdValid = validatePwdFormat() + const uuidValid = await validateUuid() + if (pwdValid && uuidValid) { done() } } const validatePwdFormat = () => { if (!shareEnable.value || state.detailInfo.autoPwd) { - showPageError(null) + showPageError(null, pwdRef) return true } const val = state.detailInfo.pwd if (!val) { - showPageError('密码不能为空,请重新输入!') + showPageError('密码不能为空,请重新输入!', pwdRef) return false } const regex = /^(?=.*[A-Za-z])(?=.*\d)(?=.*[!@#$%^&*()_+])[A-Za-z\d!@#$%^&*()_+]{4,10}$/ if (!regex.test(val)) { - showPageError('密码必须是包含数字、字母、特殊字符[!@#$%^&*()_+]的4-10位字符串') + showPageError('密码必须是包含数字、字母、特殊字符[!@#$%^&*()_+]的4-10位字符串', pwdRef) return false } - showPageError(null) + showPageError(null, pwdRef) resetPwdHandler(val, false) return true } -const showPageError = msg => { - const domRef = pwdRef +const showPageError = (msg, target, className?: string) => { + className = className || 'link-pwd-error-msg' + const fullClassName = `.${className}` + const domRef = target || pwdRef if (!domRef.value) { return } @@ -293,7 +362,7 @@ const showPageError = msg => { if (!msg) { e.style = null e.style.borderColor = null - const child = e.parentElement.querySelector('.link-pwd-error-msg') + const child = e.parentElement.querySelector(fullClassName) if (child) { e.parentElement['style'] = null e.parentElement.removeChild(child) @@ -302,10 +371,10 @@ const showPageError = msg => { e.style.color = 'red' e.style.borderColor = 'red' e.parentElement['style']['box-shadow'] = '0 0 0 1px red inset' - const child = e.parentElement.querySelector('.link-pwd-error-msg') + const child = e.parentElement.querySelector(fullClassName) if (!child) { const errorDom = document.createElement('div') - errorDom.className = 'link-pwd-error-msg' + errorDom.className = className errorDom.innerText = msg e.parentElement.appendChild(errorDom) } else { @@ -313,12 +382,12 @@ const showPageError = msg => { } } } -const existErrorMsg = () => { - return document.getElementsByClassName('link-pwd-error-msg')?.length +const existErrorMsg = (className: string) => { + return document.getElementsByClassName(className)?.length } const autoEnableSwitcher = val => { if (val) { - showPageError(null) + showPageError(null, pwdRef) resetPwd() } else { state.detailInfo.pwd = '' @@ -348,11 +417,29 @@ const resetPwdHandler = (pwd?: string, autoPwd?: boolean) => { } const getUuid = () => { - return 'xyxy'.replace(/[xy]/g, function (c) { - var r = (Math.random() * 16) | 0, - v = c == 'x' ? r : (r & 0x3) | 0x8 - return v.toString(16) - }) + const length = 10 + const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+' + let result = '' + const specialChars = '!@#$%^&*()_+' + let hasSpecialChar = false + + for (let i = 0; i < length; i++) { + if (i === 0) { + result += characters.charAt(Math.floor(Math.random() * characters.length)) + } else { + if (!hasSpecialChar && i < length - 2) { + result += specialChars.charAt(Math.floor(Math.random() * specialChars.length)) + hasSpecialChar = true + } else { + result += characters.charAt(Math.floor(Math.random() * characters.length)) + } + } + } + result = result + .split('') + .sort(() => 0.5 - Math.random()) + .join('') + return result } const execute = () => { @@ -483,18 +570,26 @@ onMounted(() => { margin-right: 8px; } } - - .text { - border-radius: 4px; - border: 1px solid #bbbfc4; - background: #eff0f1; + .custom-link-line { + display: flex; margin-bottom: 16px; - height: 32px; - padding: 5px 12px; - color: #8f959e; - font-size: 14px; - font-style: normal; - line-height: 22px; + align-items: center; + button { + width: 40px; + min-width: 40px; + margin-left: 8px; + height: 100%; + } + :deep(.link-uuid-error-msg) { + color: red; + position: absolute; + z-index: 9; + font-size: 10px; + height: 10px; + top: 25px; + width: 350px; + left: 0px; + } } } } diff --git a/core/core-frontend/src/views/share/share/ShareVisualHead.vue b/core/core-frontend/src/views/share/share/ShareVisualHead.vue index 2f5cdb4d19..a87c8ae1c5 100644 --- a/core/core-frontend/src/views/share/share/ShareVisualHead.vue +++ b/core/core-frontend/src/views/share/share/ShareVisualHead.vue @@ -27,8 +27,27 @@ {{ shareTips }}
-