修改积分商城商品显示问题1 #35
@ -3,7 +3,6 @@
|
||||
"baseUrl": "http://127.0.0.1:48080/admin-api",
|
||||
"token": "test1",
|
||||
"adminTenentId": "1",
|
||||
|
||||
"appApi": "http://127.0.0.1:48080/app-api",
|
||||
"appToken": "test247",
|
||||
"appTenentId": "1"
|
||||
@ -12,7 +11,6 @@
|
||||
"baseUrl": "http://127.0.0.1:8888/admin-api",
|
||||
"token": "test1",
|
||||
"adminTenentId": "1",
|
||||
|
||||
"appApi": "http://127.0.0.1:8888/app-api",
|
||||
"appToken": "test1",
|
||||
"appTenantId": "1"
|
||||
|
@ -15,6 +15,7 @@ import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import liquibase.CatalogAndSchema;
|
||||
import liquibase.Scope;
|
||||
import liquibase.database.AbstractJdbcDatabase;
|
||||
|
@ -133,7 +133,9 @@ public abstract class AbstractEngineConfiguration {
|
||||
|
||||
protected final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
/** The tenant id indicating 'no tenant' */
|
||||
/**
|
||||
* The tenant id indicating 'no tenant'
|
||||
*/
|
||||
public static final String NO_TENANT_ID = "";
|
||||
|
||||
/**
|
||||
@ -261,9 +263,9 @@ public abstract class AbstractEngineConfiguration {
|
||||
|
||||
/**
|
||||
* Flag that can be set to configure or not a relational database is used. This is useful for custom implementations that do not use relational databases at all.
|
||||
*
|
||||
* <p>
|
||||
* If true (default), the {@link AbstractEngineConfiguration#getDatabaseSchemaUpdate()} value will be used to determine what needs to happen wrt the database schema.
|
||||
*
|
||||
* <p>
|
||||
* If false, no validation or schema creation will be done. That means that the database schema must have been created 'manually' before but the engine does not validate whether the schema is
|
||||
* correct. The {@link AbstractEngineConfiguration#getDatabaseSchemaUpdate()} value will not be used.
|
||||
*/
|
||||
@ -287,7 +289,7 @@ public abstract class AbstractEngineConfiguration {
|
||||
|
||||
/**
|
||||
* Escape character for doing wildcard searches.
|
||||
*
|
||||
* <p>
|
||||
* This will be added at then end of queries that include for example a LIKE clause. For example: SELECT * FROM table WHERE column LIKE '%\%%' ESCAPE '\';
|
||||
*/
|
||||
protected String databaseWildcardEscapeCharacter;
|
||||
|
@ -11,6 +11,7 @@ export interface DiscountActivityVO {
|
||||
endTime?: Date
|
||||
products?: DiscountProductVO[]
|
||||
}
|
||||
|
||||
// 限时折扣相关 属性
|
||||
export interface DiscountProductVO {
|
||||
spuId: number
|
||||
|
@ -4,6 +4,7 @@ import request from '@/config/axios'
|
||||
export interface PayWalletUserReqVO {
|
||||
userId: number
|
||||
}
|
||||
|
||||
/** 钱包 VO */
|
||||
export interface WalletVO {
|
||||
id: number
|
||||
|
@ -1,5 +1,6 @@
|
||||
import {ElMessage, ElMessageBox, ElNotification} from 'element-plus'
|
||||
import {useI18n} from './useI18n'
|
||||
|
||||
export const useMessage = () => {
|
||||
const {t} = useI18n()
|
||||
return {
|
||||
|
@ -7,6 +7,7 @@ import type { TableProps } from '@/components/Table/src/types'
|
||||
import {TableSetPropsType} from '@/types/table'
|
||||
|
||||
const {t} = useI18n()
|
||||
|
||||
interface ResponseType<T = any> {
|
||||
list: T[]
|
||||
total?: number
|
||||
|
@ -1,7 +1,8 @@
|
||||
const domSymbol = Symbol('watermark-dom')
|
||||
|
||||
export function useWatermark(appendEl: HTMLElement | null = document.body) {
|
||||
let func: Fn = () => {}
|
||||
let func: Fn = () => {
|
||||
}
|
||||
const id = domSymbol.toString()
|
||||
const clear = () => {
|
||||
const domId = document.getElementById(id)
|
||||
|
@ -3,6 +3,7 @@ import { store } from '../index'
|
||||
// @ts-ignore
|
||||
import {DictDataVO} from '@/api/system/dict/types'
|
||||
import {CACHE_KEY, useCache} from '@/hooks/web/useCache'
|
||||
|
||||
const {wsCache} = useCache('sessionStorage')
|
||||
import {getSimpleDictDataList} from '@/api/system/dict/dict.data'
|
||||
|
||||
@ -12,10 +13,12 @@ export interface DictValueType {
|
||||
clorType?: string
|
||||
cssClass?: string
|
||||
}
|
||||
|
||||
export interface DictTypeType {
|
||||
dictType: string
|
||||
dictValue: DictValueType[]
|
||||
}
|
||||
|
||||
export interface DictState {
|
||||
dictMap: Map<string, any>
|
||||
isSetDict: boolean
|
||||
|
@ -11,6 +11,7 @@ const elLocaleMap = {
|
||||
'zh-CN': zhCn,
|
||||
en: en
|
||||
}
|
||||
|
||||
interface LocaleState {
|
||||
currentLocale: LocaleDropdownType
|
||||
localeMap: LocaleDropdownType[]
|
||||
|
1
yudao-admin-vue3/src/types/configGlobal.d.ts
vendored
1
yudao-admin-vue3/src/types/configGlobal.d.ts
vendored
@ -1,4 +1,5 @@
|
||||
import {ElementPlusSize} from './elementPlus'
|
||||
|
||||
export interface ConfigGlobalTypes {
|
||||
size?: ElementPlusSize
|
||||
}
|
||||
|
@ -2,7 +2,8 @@ const isArray = function (obj: any): boolean {
|
||||
return Object.prototype.toString.call(obj) === '[object Array]'
|
||||
}
|
||||
|
||||
const Logger = () => {}
|
||||
const Logger = () => {
|
||||
}
|
||||
|
||||
Logger.typeColor = function (type: string) {
|
||||
let color = ''
|
||||
|
@ -1,4 +1,5 @@
|
||||
import {isServer} from './is'
|
||||
|
||||
const ieVersion = isServer ? 0 : Number((document as any).documentMode)
|
||||
const SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g
|
||||
const MOZ_HACK_REGEXP = /^moz([A-Z])/
|
||||
|
@ -52,8 +52,12 @@
|
||||
</div>
|
||||
<div class="button-wrapper" v-show="hoverConversationId === conversation.id">
|
||||
<el-button class="btn" link @click.stop="handleTop(conversation)">
|
||||
<el-icon title="置顶" v-if="!conversation.pinned"><Top /></el-icon>
|
||||
<el-icon title="置顶" v-if="conversation.pinned"><Bottom /></el-icon>
|
||||
<el-icon title="置顶" v-if="!conversation.pinned">
|
||||
<Top/>
|
||||
</el-icon>
|
||||
<el-icon title="置顶" v-if="conversation.pinned">
|
||||
<Bottom/>
|
||||
</el-icon>
|
||||
</el-button>
|
||||
<el-button class="btn" link @click.stop="updateConversationTitle(conversation)">
|
||||
<el-icon title="编辑">
|
||||
@ -285,7 +289,8 @@ const deleteChatConversation = async (conversation: ChatConversationVO) => {
|
||||
await getChatConversationList()
|
||||
// 回调
|
||||
emits('onConversationDelete', conversation)
|
||||
} catch {}
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
|
||||
/** 清空对话 */
|
||||
@ -303,7 +308,8 @@ const handleClearConversation = async () => {
|
||||
await getChatConversationList()
|
||||
// 回调 方法
|
||||
emits('onConversationClear')
|
||||
} catch {}
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
|
||||
/** 对话置顶 */
|
||||
|
@ -43,10 +43,14 @@
|
||||
<img class="btn-image h-17px mr-12px" src="@/assets/ai/delete.svg"/>
|
||||
</el-button>
|
||||
<el-button class="btn-cus" link @click="onRefresh(item)">
|
||||
<el-icon size="17"><RefreshRight /></el-icon>
|
||||
<el-icon size="17">
|
||||
<RefreshRight/>
|
||||
</el-icon>
|
||||
</el-button>
|
||||
<el-button class="btn-cus" link @click="onEdit(item)">
|
||||
<el-icon size="17"><Edit /></el-icon>
|
||||
<el-icon size="17">
|
||||
<Edit/>
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
@ -182,6 +186,7 @@ onMounted(async () => {
|
||||
flex-direction: column;
|
||||
overflow-y: hidden;
|
||||
padding: 0 20px;
|
||||
|
||||
.message-item {
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
@ -13,10 +13,12 @@
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item :command="['edit', role]">
|
||||
<Icon icon="ep:edit" color="#787878" />编辑
|
||||
<Icon icon="ep:edit" color="#787878"/>
|
||||
编辑
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item :command="['delete', role]" style="color: red">
|
||||
<Icon icon="ep:delete" color="red" />删除
|
||||
<Icon icon="ep:delete" color="red"/>
|
||||
删除
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
|
@ -227,6 +227,7 @@ onMounted(async () => {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.el-tabs__nav-scroll {
|
||||
margin: 10px 20px;
|
||||
}
|
||||
|
@ -307,7 +307,8 @@ const handlerMessageClear = async () => {
|
||||
await ChatMessageApi.deleteByConversationId(activeConversationId.value)
|
||||
// 刷新 message 列表
|
||||
activeMessageList.value = []
|
||||
} catch {}
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
|
||||
/** 回到 message 列表的顶部 */
|
||||
@ -468,7 +469,8 @@ const doSendMessageStream = async (userMessage: ChatMessageVO) => {
|
||||
stopStream()
|
||||
}
|
||||
)
|
||||
} catch {}
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
|
||||
/** 停止 stream 流式调用 */
|
||||
@ -553,7 +555,8 @@ const textRoll = async () => {
|
||||
}
|
||||
}
|
||||
let timer = setTimeout(task, textSpeed.value)
|
||||
} catch {}
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
|
@ -44,8 +44,14 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||
<el-button @click="handleQuery">
|
||||
<Icon icon="ep:search" class="mr-5px"/>
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">
|
||||
<Icon icon="ep:refresh" class="mr-5px"/>
|
||||
重置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
@ -151,7 +157,8 @@ const handleDelete = async (id: number) => {
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
|
@ -44,8 +44,14 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||
<el-button @click="handleQuery">
|
||||
<Icon icon="ep:search" class="mr-5px"/>
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">
|
||||
<Icon icon="ep:refresh" class="mr-5px"/>
|
||||
重置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
@ -163,7 +169,8 @@ const handleDelete = async (id: number) => {
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
|
@ -190,6 +190,7 @@ onUnmounted(async () => {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.task-card {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
@ -212,6 +213,7 @@ onUnmounted(async () => {
|
||||
margin-right: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
> div:last-of-type {
|
||||
//margin-bottom: 100px;
|
||||
}
|
||||
|
@ -68,7 +68,8 @@ const platformOptions = [
|
||||
]
|
||||
|
||||
/** 绘画 start */
|
||||
const handleDrawStart = async (platform: string) => {}
|
||||
const handleDrawStart = async (platform: string) => {
|
||||
}
|
||||
|
||||
/** 绘画 complete */
|
||||
const handleDrawComplete = async (platform: string) => {
|
||||
|
@ -75,8 +75,14 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||
<el-button @click="handleQuery">
|
||||
<Icon icon="ep:search" class="mr-5px"/>
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">
|
||||
<Icon icon="ep:refresh" class="mr-5px"/>
|
||||
重置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
@ -222,7 +228,8 @@ const handleDelete = async (id: number) => {
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
|
||||
/** 修改是否发布 */
|
||||
|
@ -43,15 +43,22 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||
<el-button @click="handleQuery">
|
||||
<Icon icon="ep:search" class="mr-5px"/>
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">
|
||||
<Icon icon="ep:refresh" class="mr-5px"/>
|
||||
重置
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
@click="openForm('create')"
|
||||
v-hasPermi="['ai:api-key:create']"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||
<Icon icon="ep:plus" class="mr-5px"/>
|
||||
新增
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
@ -170,7 +177,8 @@ const handleDelete = async (id: number) => {
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
|
@ -36,15 +36,22 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||
<el-button @click="handleQuery">
|
||||
<Icon icon="ep:search" class="mr-5px"/>
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">
|
||||
<Icon icon="ep:refresh" class="mr-5px"/>
|
||||
重置
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
@click="openForm('create')"
|
||||
v-hasPermi="['ai:chat-model:create']"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||
<Icon icon="ep:plus" class="mr-5px"/>
|
||||
新增
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
@ -173,7 +180,8 @@ const handleDelete = async (id: number) => {
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
|
@ -42,15 +42,22 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||
<el-button @click="handleQuery">
|
||||
<Icon icon="ep:search" class="mr-5px"/>
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">
|
||||
<Icon icon="ep:refresh" class="mr-5px"/>
|
||||
重置
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
@click="openForm('create')"
|
||||
v-hasPermi="['ai:chat-role:create']"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||
<Icon icon="ep:plus" class="mr-5px"/>
|
||||
新增
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
@ -177,7 +184,8 @@ const handleDelete = async (id: number) => {
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
|
@ -1,8 +1,10 @@
|
||||
<template>
|
||||
<div class="flex items-center justify-between px-2 h-72px bg-[var(--el-bg-color-overlay)] b-solid b-1 b-[var(--el-border-color)] b-l-none">
|
||||
<div
|
||||
class="flex items-center justify-between px-2 h-72px bg-[var(--el-bg-color-overlay)] b-solid b-1 b-[var(--el-border-color)] b-l-none">
|
||||
<!-- 歌曲信息 -->
|
||||
<div class="flex gap-[10px]">
|
||||
<el-image src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png" class="w-[45px]"/>
|
||||
<el-image src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png"
|
||||
class="w-[45px]"/>
|
||||
<div>
|
||||
<div>{{currentSong.name}}</div>
|
||||
<div class="text-[12px] text-gray-400">{{currentSong.singer}}</div>
|
||||
@ -12,7 +14,8 @@
|
||||
<!-- 音频controls -->
|
||||
<div class="flex gap-[12px] items-center">
|
||||
<Icon icon="majesticons:back-circle" :size="20" class="text-gray-300 cursor-pointer"/>
|
||||
<Icon :icon="audioProps.paused ? 'mdi:arrow-right-drop-circle' : 'solar:pause-circle-bold'" :size="30" class=" cursor-pointer" @click="toggleStatus('paused')"/>
|
||||
<Icon :icon="audioProps.paused ? 'mdi:arrow-right-drop-circle' : 'solar:pause-circle-bold'"
|
||||
:size="30" class=" cursor-pointer" @click="toggleStatus('paused')"/>
|
||||
<Icon icon="majesticons:next-circle" :size="20" class="text-gray-300 cursor-pointer"/>
|
||||
<div class="flex gap-[16px] items-center">
|
||||
<span>{{audioProps.currentTime}}</span>
|
||||
@ -20,14 +23,16 @@
|
||||
<span>{{ audioProps.duration }}</span>
|
||||
</div>
|
||||
<!-- 音频 -->
|
||||
<audio v-bind="audioProps" ref="audioRef" controls v-show="!audioProps" @timeupdate="audioTimeUpdate">
|
||||
<audio v-bind="audioProps" ref="audioRef" controls v-show="!audioProps"
|
||||
@timeupdate="audioTimeUpdate">
|
||||
<source :src="audioUrl"/>
|
||||
</audio>
|
||||
</div>
|
||||
|
||||
<!-- 音量控制器 -->
|
||||
<div class="flex gap-[16px] items-center">
|
||||
<Icon :icon="audioProps.muted ? 'tabler:volume-off' : 'tabler:volume'" :size="20" class="cursor-pointer" @click="toggleStatus('muted')"/>
|
||||
<Icon :icon="audioProps.muted ? 'tabler:volume-off' : 'tabler:volume'" :size="20"
|
||||
class="cursor-pointer" @click="toggleStatus('muted')"/>
|
||||
<el-slider v-model="audioProps.volume" color="#409eff" class="w-[160px!important] "/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -100,6 +100,7 @@ defineExpose({
|
||||
:deep(.el-tabs) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.el-tabs__content {
|
||||
padding: 0 7px;
|
||||
overflow: auto;
|
||||
|
@ -2,8 +2,11 @@
|
||||
<div class="flex bg-[var(--el-bg-color-overlay)] p-12px mb-12px rounded-1">
|
||||
<div class="relative" @click="playSong">
|
||||
<el-image :src="songInfo.imageUrl" class="flex-none w-80px"/>
|
||||
<div class="bg-black bg-op-40 absolute top-0 left-0 w-full h-full flex items-center justify-center cursor-pointer">
|
||||
<Icon :icon="currentSong.id === songInfo.id ? 'solar:pause-circle-bold':'mdi:arrow-right-drop-circle'" :size="30" />
|
||||
<div
|
||||
class="bg-black bg-op-40 absolute top-0 left-0 w-full h-full flex items-center justify-center cursor-pointer">
|
||||
<Icon
|
||||
:icon="currentSong.id === songInfo.id ? 'solar:pause-circle-bold':'mdi:arrow-right-drop-circle'"
|
||||
:size="30"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-8px">
|
||||
|
@ -64,6 +64,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Title from '../title/index.vue'
|
||||
|
||||
defineOptions({name: 'Lyric'})
|
||||
|
||||
const tags = ['rock', 'punk', 'jazz', 'soul', 'country', 'kidsmusic', 'pop']
|
||||
|
@ -89,8 +89,14 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||
<el-button @click="handleQuery">
|
||||
<Icon icon="ep:search" class="mr-5px"/>
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">
|
||||
<Icon icon="ep:refresh" class="mr-5px"/>
|
||||
重置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
@ -263,7 +269,8 @@ const handleDelete = async (id: number) => {
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
|
||||
/** 修改是否发布 */
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { isEmpty } from '@/utils/is'
|
||||
|
||||
const emojiList = [
|
||||
{ name: '[狗子]', file: 'gouzi.png' },
|
||||
{ name: '[笑掉牙]', file: 'xiaodiaoya.png' },
|
||||
{ name: '[可爱]', file: 'keai.png' },
|
||||
{ name: '[冷酷]', file: 'lengku.png' },
|
||||
|
@ -20,8 +20,7 @@ public enum DateIntervalEnum implements IntArrayValuable {
|
||||
WEEK(2, "周"),
|
||||
MONTH(3, "月"),
|
||||
QUARTER(4, "季度"),
|
||||
YEAR(5, "年")
|
||||
;
|
||||
YEAR(5, "年");
|
||||
|
||||
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(DateIntervalEnum::getInterval).toArray();
|
||||
|
||||
|
@ -2,7 +2,7 @@ package cn.iocoder.yudao.framework.common.enums;
|
||||
|
||||
/**
|
||||
* Web 过滤器顺序的枚举类,保证过滤器按照符合我们的预期
|
||||
*
|
||||
* <p>
|
||||
* 考虑到每个 starter 都需要用到该工具类,所以放到 common 模块下的 enums 包下
|
||||
*
|
||||
* @author 芋道源码
|
||||
|
@ -6,10 +6,10 @@ import lombok.Data;
|
||||
|
||||
/**
|
||||
* 错误码对象
|
||||
*
|
||||
* <p>
|
||||
* 全局错误码,占用 [0, 999], 参见 {@link GlobalErrorCodeConstants}
|
||||
* 业务异常错误码,占用 [1 000 000 000, +∞),参见 {@link ServiceErrorCodeRange}
|
||||
*
|
||||
* <p>
|
||||
* TODO 错误码设计成对象的原因,为未来的 i18 国际化做准备
|
||||
*/
|
||||
@Data
|
||||
|
@ -5,7 +5,7 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode;
|
||||
/**
|
||||
* 全局错误码枚举
|
||||
* 0-999 系统异常编码保留
|
||||
*
|
||||
* <p>
|
||||
* 一般情况下,使用 HTTP 响应状态码 https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status
|
||||
* 虽然说,HTTP 响应状态码作为业务使用表达能力偏弱,但是使用在系统层面还是非常不错的
|
||||
* 比较特殊的是,因为之前一直使用 0 作为成功,就不使用 200 啦。
|
||||
|
@ -2,9 +2,9 @@ package cn.iocoder.yudao.framework.common.exception.enums;
|
||||
|
||||
/**
|
||||
* 业务异常的错误码区间,解决:解决各模块错误码定义,避免重复,在此只声明不做实际使用
|
||||
*
|
||||
* <p>
|
||||
* 一共 10 位,分成四段
|
||||
*
|
||||
* <p>
|
||||
* 第一段,1 位,类型
|
||||
* 1 - 业务级别异常
|
||||
* x - 预留
|
||||
|
@ -8,10 +8,9 @@ import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* {@link ServiceException} 工具类
|
||||
*
|
||||
* <p>
|
||||
* 目的在于,格式化异常信息提示。
|
||||
* 考虑到 String.format 在参数不正确时会报错,因此使用 {} 作为占位符,并使用 {@link #doFormat(int, String, Object...)} 方法来格式化
|
||||
*
|
||||
*/
|
||||
@Slf4j
|
||||
public class ServiceExceptionUtil {
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* 基础的通用类,和框架无关
|
||||
*
|
||||
* <p>
|
||||
* 例如说,CommonResult 为通用返回
|
||||
*/
|
||||
package cn.iocoder.yudao.framework.common;
|
||||
|
@ -37,7 +37,7 @@ public class CommonResult<T> implements Serializable {
|
||||
|
||||
/**
|
||||
* 将传入的 result 对象,转换成另外一个泛型结果的对象
|
||||
*
|
||||
* <p>
|
||||
* 因为 A 方法返回的 CommonResult 对象,不满足调用其的 B 方法的返回,所以需要进行转换。
|
||||
*
|
||||
* @param result 传入的 result 对象
|
||||
|
@ -17,7 +17,7 @@ public class PageParam implements Serializable {
|
||||
|
||||
/**
|
||||
* 每页条数 - 不分页
|
||||
*
|
||||
* <p>
|
||||
* 例如说,导出接口,可以设置 {@link #pageSize} 为 -1 不分页,查询所有数据。
|
||||
*/
|
||||
public static final Integer PAGE_SIZE_NONE = -1;
|
||||
|
@ -8,7 +8,7 @@ import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 排序字段 DTO
|
||||
*
|
||||
* <p>
|
||||
* 类名加了 ing 的原因是,避免和 ES SortField 重名。
|
||||
*/
|
||||
@Data
|
||||
|
@ -16,9 +16,9 @@ public class CacheUtils {
|
||||
|
||||
/**
|
||||
* 构建异步刷新的 LoadingCache 对象
|
||||
*
|
||||
* <p>
|
||||
* 注意:如果你的缓存和 ThreadLocal 有关系,要么自己处理 ThreadLocal 的传递,要么使用 {@link #buildCache(Duration, CacheLoader)} 方法
|
||||
*
|
||||
* <p>
|
||||
* 或者简单理解:
|
||||
* 1、和“人”相关的,使用 {@link #buildCache(Duration, CacheLoader)} 方法
|
||||
* 2、和“全局”、“系统”相关的,使用当前缓存方法
|
||||
|
@ -28,7 +28,7 @@ public class LocalDateTimeUtils {
|
||||
|
||||
/**
|
||||
* 解析时间
|
||||
*
|
||||
* <p>
|
||||
* 相比 {@link LocalDateTimeUtil#parse(CharSequence)} 方法来说,会尽量去解析,直到成功
|
||||
*
|
||||
* @param time 时间
|
||||
|
@ -39,7 +39,7 @@ public class HttpUtils {
|
||||
|
||||
/**
|
||||
* 拼接 URL
|
||||
*
|
||||
* <p>
|
||||
* copy from Spring Security OAuth2 的 AuthorizationEndpoint 类的 append 方法
|
||||
*
|
||||
* @param base 基础 URL
|
||||
|
@ -8,7 +8,7 @@ import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Long 序列化规则
|
||||
*
|
||||
* <p>
|
||||
* 会将超长 long 值转换为 string,解决前端 JavaScript 最大安全整数是 2^53-1 的问题
|
||||
*
|
||||
* @author 星语
|
||||
|
@ -4,7 +4,7 @@ import org.apache.skywalking.apm.toolkit.trace.TraceContext;
|
||||
|
||||
/**
|
||||
* 链路追踪工具类
|
||||
*
|
||||
* <p>
|
||||
* 考虑到每个 starter 都需要用到该工具类,所以放到 common 模块下的 util 包下
|
||||
*
|
||||
* @author 芋道源码
|
||||
|
@ -86,7 +86,7 @@ public class MoneyUtils {
|
||||
|
||||
/**
|
||||
* 分转元(字符串)
|
||||
*
|
||||
* <p>
|
||||
* 例如说 fen 为 1 时,则结果为 0.01
|
||||
*
|
||||
* @param fen 分
|
||||
@ -98,7 +98,7 @@ public class MoneyUtils {
|
||||
|
||||
/**
|
||||
* 金额相乘,默认进行四舍五入
|
||||
*
|
||||
* <p>
|
||||
* 位数:{@link #PRICE_SCALE}
|
||||
*
|
||||
* @param price 金额
|
||||
@ -114,7 +114,7 @@ public class MoneyUtils {
|
||||
|
||||
/**
|
||||
* 金额相乘(百分比),默认进行四舍五入
|
||||
*
|
||||
* <p>
|
||||
* 位数:{@link #PRICE_SCALE}
|
||||
*
|
||||
* @param price 金额
|
||||
|
@ -22,7 +22,7 @@ public class NumberUtils {
|
||||
|
||||
/**
|
||||
* 通过经纬度获取地球上两点之间的距离
|
||||
*
|
||||
* <p>
|
||||
* 参考 <<a href="https://gitee.com/dromara/hutool/blob/1caabb586b1f95aec66a21d039c5695df5e0f4c1/hutool-core/src/main/java/cn/hutool/core/util/DistanceUtil.java">DistanceUtil</a>> 实现,目前它已经被 hutool 删除
|
||||
*
|
||||
* @param lat1 经度1
|
||||
@ -46,7 +46,7 @@ public class NumberUtils {
|
||||
|
||||
/**
|
||||
* 提供精确的乘法运算
|
||||
*
|
||||
* <p>
|
||||
* 和 hutool {@link NumberUtil#mul(BigDecimal...)} 的差别是,如果存在 null,则返回 null
|
||||
*
|
||||
* @param values 多个被乘值
|
||||
|
@ -9,7 +9,7 @@ import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Bean 工具类
|
||||
*
|
||||
* <p>
|
||||
* 1. 默认使用 {@link cn.hutool.core.bean.BeanUtil} 作为实现类,虽然不同 bean 工具的性能有差别,但是对绝大多数同学的项目,不用在意这点性能
|
||||
* 2. 针对复杂的对象转换,可以搜参考 AuthConvert 实现,通过 mapstruct + default 配合实现
|
||||
*
|
||||
|
@ -1,7 +1,7 @@
|
||||
/**
|
||||
* 对于工具类的选择,优先查找 Hutool 中有没对应的方法
|
||||
* 如果没有,则自己封装对应的工具类,以 Utils 结尾,用于区分
|
||||
*
|
||||
* <p>
|
||||
* ps:如果担心 Hutool 存在坑的问题,可以阅读 Hutool 的实现源码,以确保可靠性。并且,可以补充相关的单元测试。
|
||||
*/
|
||||
package cn.iocoder.yudao.framework.common.util;
|
||||
|
@ -62,7 +62,7 @@ public class DataPermissionContextHolder {
|
||||
|
||||
/**
|
||||
* 清空上下文
|
||||
*
|
||||
* <p>
|
||||
* 目前仅仅用于单测
|
||||
*/
|
||||
public static void clear() {
|
||||
|
@ -14,7 +14,7 @@ import java.util.List;
|
||||
|
||||
/**
|
||||
* 基于 {@link DataPermissionRule} 的数据权限处理器
|
||||
*
|
||||
* <p>
|
||||
* 它的底层,是基于 MyBatis Plus 的 <a href="https://baomidou.com/plugins/data-permission/">数据权限插件</a>
|
||||
* 核心原理:它会在 SQL 执行前拦截 SQL 语句,并根据用户权限动态添加权限相关的 SQL 片段。这样,只有用户有权限访问的数据才会被查询出来
|
||||
*
|
||||
|
@ -17,7 +17,7 @@ public interface DataPermissionRule {
|
||||
/**
|
||||
* 返回需要生效的表名数组
|
||||
* 为什么需要该方法?Data Permission 数组基于 SQL 重写,通过 Where 返回只有权限的数据
|
||||
*
|
||||
* <p>
|
||||
* 如果需要基于实体名获得表名,可调用 {@link TableInfoHelper#getTableInfo(Class)} 获得
|
||||
*
|
||||
* @return 表名数组
|
||||
|
@ -29,9 +29,9 @@ import java.util.Set;
|
||||
|
||||
/**
|
||||
* 基于部门的 {@link DataPermissionRule} 数据权限规则实现
|
||||
*
|
||||
* <p>
|
||||
* 注意,使用 DeptDataPermissionRule 时,需要保证表中有 dept_id 部门编号的字段,可自定义。
|
||||
*
|
||||
* <p>
|
||||
* 实际业务场景下,会存在一个经典的问题?当用户修改部门时,冗余的 dept_id 是否需要修改?
|
||||
* 1. 一般情况下,dept_id 不进行修改,则会导致用户看不到之前的数据。【yudao-server 采用该方案】
|
||||
* 2. 部分情况下,希望该用户还是能看到之前的数据,则有两种方式解决:【需要你改造该 DeptDataPermissionRule 的实现代码】
|
||||
@ -63,7 +63,7 @@ public class DeptDataPermissionRule implements DataPermissionRule {
|
||||
/**
|
||||
* 基于部门的表字段配置
|
||||
* 一般情况下,每个表的部门编号字段是 dept_id,通过该配置自定义。
|
||||
*
|
||||
* <p>
|
||||
* key:表名
|
||||
* value:字段名
|
||||
*/
|
||||
@ -71,7 +71,7 @@ public class DeptDataPermissionRule implements DataPermissionRule {
|
||||
/**
|
||||
* 基于用户的表字段配置
|
||||
* 一般情况下,每个表的部门编号字段是 dept_id,通过该配置自定义。
|
||||
*
|
||||
* <p>
|
||||
* key:表名
|
||||
* value:字段名
|
||||
*/
|
||||
|
@ -103,16 +103,20 @@ class DataPermissionRuleFactoryImplTest extends BaseMockitoUnitTest {
|
||||
}
|
||||
|
||||
@DataPermission(enable = false)
|
||||
static class TestClass03 {}
|
||||
static class TestClass03 {
|
||||
}
|
||||
|
||||
@DataPermission(includeRules = DataPermissionRule01.class)
|
||||
static class TestClass04 {}
|
||||
static class TestClass04 {
|
||||
}
|
||||
|
||||
@DataPermission(excludeRules = DataPermissionRule01.class)
|
||||
static class TestClass05 {}
|
||||
static class TestClass05 {
|
||||
}
|
||||
|
||||
@DataPermission
|
||||
static class TestClass06 {}
|
||||
static class TestClass06 {
|
||||
}
|
||||
|
||||
static class DataPermissionRule01 implements DataPermissionRule {
|
||||
|
||||
|
@ -9,7 +9,7 @@ import java.util.List;
|
||||
|
||||
/**
|
||||
* 区域节点,包括国家、省份、城市、地区等信息
|
||||
*
|
||||
* <p>
|
||||
* 数据可见 resources/area.csv 文件
|
||||
*
|
||||
* @author 芋道源码
|
||||
@ -38,7 +38,7 @@ public class Area {
|
||||
private String name;
|
||||
/**
|
||||
* 类型
|
||||
*
|
||||
* <p>
|
||||
* 枚举 {@link AreaTypeEnum}
|
||||
*/
|
||||
private Integer type;
|
||||
|
@ -138,7 +138,7 @@ public class AreaUtils {
|
||||
|
||||
/**
|
||||
* 格式化区域
|
||||
*
|
||||
* <p>
|
||||
* 例如说:
|
||||
* 1. id = “静安区”时:上海 上海市 静安区
|
||||
* 2. id = “上海市”时:上海 上海市
|
||||
|
@ -10,7 +10,7 @@ import java.io.IOException;
|
||||
|
||||
/**
|
||||
* IP 工具类
|
||||
*
|
||||
* <p>
|
||||
* IP 数据源来自 ip2region.xdb 精简版,基于 <a href="https://gitee.com/zhijiantianya/ip2region"/> 项目
|
||||
*
|
||||
* @author wanglhup
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* IP 拓展,支持如下功能:
|
||||
*
|
||||
* <p>
|
||||
* 1. IP 功能:查询 IP 对应的城市信息
|
||||
* 基于 https://gitee.com/lionsoul/ip2region 实现
|
||||
* 2. 城市功能:查询城市编码对应的城市信息
|
||||
|
@ -27,14 +27,14 @@ public class TenantProperties {
|
||||
|
||||
/**
|
||||
* 需要忽略多租户的请求
|
||||
*
|
||||
* <p>
|
||||
* 默认情况下,每个请求需要带上 tenant-id 的请求头。但是,部分请求是无需带上的,例如说短信回调、支付回调等 Open API!
|
||||
*/
|
||||
private Set<String> ignoreUrls = Collections.emptySet();
|
||||
|
||||
/**
|
||||
* 需要忽略多租户的表
|
||||
*
|
||||
* <p>
|
||||
* 即默认所有表都开启多租户的功能,所以记得添加对应的 tenant_id 字段哟
|
||||
*/
|
||||
private Set<String> ignoreTables = Collections.emptySet();
|
||||
|
@ -36,7 +36,8 @@ import org.springframework.data.redis.core.RedisTemplate;
|
||||
import java.util.Objects;
|
||||
|
||||
@AutoConfiguration
|
||||
@ConditionalOnProperty(prefix = "yudao.tenant", value = "enable", matchIfMissing = true) // 允许使用 yudao.tenant.enable=false 禁用多租户
|
||||
@ConditionalOnProperty(prefix = "yudao.tenant", value = "enable", matchIfMissing = true)
|
||||
// 允许使用 yudao.tenant.enable=false 禁用多租户
|
||||
@EnableConfigurationProperties(TenantProperties.class)
|
||||
public class YudaoTenantAutoConfiguration {
|
||||
|
||||
|
@ -4,7 +4,7 @@ import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 忽略租户,标记指定方法不进行租户的自动过滤
|
||||
*
|
||||
* <p>
|
||||
* 注意,只有 DB 的场景会过滤,其它场景暂时不过滤:
|
||||
* 1、Redis 场景:因为是基于 Key 实现多租户的能力,所以忽略没有意义,不像 DB 是一个 column 实现的
|
||||
* 2、MQ 场景:有点难以抉择,目前可以通过 Consumer 手动在消费的方法上,添加 @TenantIgnore 进行忽略
|
||||
|
@ -11,7 +11,7 @@ import org.aspectj.lang.annotation.Aspect;
|
||||
* 忽略多租户的 Aspect,基于 {@link TenantIgnore} 注解实现,用于一些全局的逻辑。
|
||||
* 例如说,一个定时任务,读取所有数据,进行处理。
|
||||
* 又例如说,读取所有数据,进行缓存。
|
||||
*
|
||||
* <p>
|
||||
* 整体逻辑的实现,和 {@link TenantUtils#executeIgnore(Runnable)} 需要保持一致
|
||||
*
|
||||
* @author 芋道源码
|
||||
|
@ -18,7 +18,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
/**
|
||||
* 多租户 JobHandler AOP
|
||||
* 任务执行时,会按照租户逐个执行 Job 的逻辑
|
||||
*
|
||||
* <p>
|
||||
* 注意,需要保证 JobHandler 的幂等性。因为 Job 因为某个租户执行失败重试时,之前执行成功的租户也会再次执行。
|
||||
*
|
||||
* @author 芋道源码
|
||||
|
@ -8,7 +8,7 @@ import org.springframework.core.env.ConfigurableEnvironment;
|
||||
|
||||
/**
|
||||
* 多租户的 Kafka 的 {@link EnvironmentPostProcessor} 实现类
|
||||
*
|
||||
* <p>
|
||||
* Kafka Producer 发送消息时,增加 {@link TenantKafkaProducerInterceptor} 拦截器
|
||||
*
|
||||
* @author 芋道源码
|
||||
|
@ -14,7 +14,7 @@ import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.HEADER_
|
||||
|
||||
/**
|
||||
* Kafka 消息队列的多租户 {@link ProducerInterceptor} 实现类
|
||||
*
|
||||
* <p>
|
||||
* 1. Producer 发送消息时,将 {@link TenantContextHolder} 租户编号,添加到消息的 Header 中
|
||||
* 2. Consumer 消费消息时,将消息的 Header 的租户编号,添加到 {@link TenantContextHolder} 中,通过 {@link InvocableHandlerMethod} 实现
|
||||
*
|
||||
|
@ -11,7 +11,7 @@ import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.HEADER_
|
||||
|
||||
/**
|
||||
* RabbitMQ 消息队列的多租户 {@link ProducerInterceptor} 实现类
|
||||
*
|
||||
* <p>
|
||||
* 1. Producer 发送消息时,将 {@link TenantContextHolder} 租户编号,添加到消息的 Header 中
|
||||
* 2. Consumer 消费消息时,将消息的 Header 的租户编号,添加到 {@link TenantContextHolder} 中,通过 {@link InvocableHandlerMethod} 实现
|
||||
*
|
||||
|
@ -9,7 +9,7 @@ import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.HEADER_
|
||||
|
||||
/**
|
||||
* 多租户 {@link AbstractRedisMessage} 拦截器
|
||||
*
|
||||
* <p>
|
||||
* 1. Producer 发送消息时,将 {@link TenantContextHolder} 租户编号,添加到消息的 Header 中
|
||||
* 2. Consumer 消费消息时,将消息的 Header 的租户编号,添加到 {@link TenantContextHolder} 中
|
||||
*
|
||||
|
@ -14,7 +14,7 @@ import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.HEADER_
|
||||
|
||||
/**
|
||||
* RocketMQ 消息队列的多租户 {@link ConsumeMessageHook} 实现类
|
||||
*
|
||||
* <p>
|
||||
* Consumer 消费消息时,将消息的 Header 的租户编号,添加到 {@link TenantContextHolder} 中,通过 {@link InvocableHandlerMethod} 实现
|
||||
*
|
||||
* @author 芋道源码
|
||||
|
@ -8,7 +8,7 @@ import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.HEADER_
|
||||
|
||||
/**
|
||||
* RocketMQ 消息队列的多租户 {@link SendMessageHook} 实现类
|
||||
*
|
||||
* <p>
|
||||
* Producer 发送消息时,将 {@link TenantContextHolder} 租户编号,添加到消息的 Header 中
|
||||
*
|
||||
* @author 芋道源码
|
||||
|
@ -10,7 +10,7 @@ import org.springframework.data.redis.cache.RedisCacheWriter;
|
||||
|
||||
/**
|
||||
* 多租户的 {@link RedisCacheManager} 实现类
|
||||
*
|
||||
* <p>
|
||||
* 操作指定 name 的 {@link Cache} 时,自动拼接租户后缀,格式为 name + ":" + tenantId + 后缀
|
||||
*
|
||||
* @author airhead
|
||||
|
@ -16,7 +16,7 @@ public class TenantUtils {
|
||||
|
||||
/**
|
||||
* 使用指定租户,执行对应的逻辑
|
||||
*
|
||||
* <p>
|
||||
* 注意,如果当前是忽略租户的情况下,会被强制设置成不忽略租户
|
||||
* 当然,执行完成后,还是会恢复回去
|
||||
*
|
||||
@ -39,7 +39,7 @@ public class TenantUtils {
|
||||
|
||||
/**
|
||||
* 使用指定租户,执行对应的逻辑
|
||||
*
|
||||
* <p>
|
||||
* 注意,如果当前是忽略租户的情况下,会被强制设置成不忽略租户
|
||||
* 当然,执行完成后,还是会恢复回去
|
||||
*
|
||||
|
@ -12,6 +12,5 @@
|
||||
* 2)Spring Security:
|
||||
* TransmittableThreadLocalSecurityContextHolderStrategy
|
||||
* 和 YudaoSecurityAutoConfiguration#securityContextHolderMethodInvokingFactoryBean() 方法
|
||||
*
|
||||
*/
|
||||
package cn.iocoder.yudao.framework.tenant;
|
||||
|
@ -38,7 +38,7 @@ import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.HEADER_
|
||||
* Extension of {@link HandlerMethod} that invokes the underlying method with
|
||||
* argument values resolved from the current HTTP request through a list of
|
||||
* {@link HandlerMethodArgumentResolver}.
|
||||
*
|
||||
* <p>
|
||||
* 针对 rabbitmq-spring 和 kafka-spring,不存在合适的拓展点,可以实现 Consumer 消费前,读取 Header 中的 tenant-id 设置到 {@link TenantContextHolder} 中
|
||||
* TODO 芋艿:持续跟进,看看有没新的拓展点
|
||||
*
|
||||
@ -70,6 +70,7 @@ public class InvocableHandlerMethod extends HandlerMethod {
|
||||
|
||||
/**
|
||||
* Construct a new handler method with the given bean instance, method name and parameters.
|
||||
*
|
||||
* @param bean the object bean
|
||||
* @param methodName the method name
|
||||
* @param parameterTypes the method parameter types
|
||||
@ -105,6 +106,7 @@ public class InvocableHandlerMethod extends HandlerMethod {
|
||||
* i.e. without argument resolution.
|
||||
* <p>Delegates to {@link #getMethodArgumentValues} and calls {@link #doInvoke} with the
|
||||
* resolved arguments.
|
||||
*
|
||||
* @param message the current message being processed
|
||||
* @param providedArgs "given" arguments matched by type, not resolved
|
||||
* @return the raw value returned by the invoked method
|
||||
@ -153,6 +155,7 @@ public class InvocableHandlerMethod extends HandlerMethod {
|
||||
* Get the method argument values for the current message, checking the provided
|
||||
* argument values and falling back to the configured argument resolvers.
|
||||
* <p>The resulting array will be passed into {@link #doInvoke}.
|
||||
*
|
||||
* @since 5.1.2
|
||||
*/
|
||||
protected Object[] getMethodArgumentValues(Message<?> message, Object... providedArgs) throws Exception {
|
||||
@ -175,8 +178,7 @@ public class InvocableHandlerMethod extends HandlerMethod {
|
||||
}
|
||||
try {
|
||||
args[i] = this.resolvers.resolveArgument(parameter, message);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
} catch (Exception ex) {
|
||||
// Leave stack trace for later, exception may actually be resolved and handled...
|
||||
if (logger.isDebugEnabled()) {
|
||||
String exMsg = ex.getMessage();
|
||||
@ -197,25 +199,20 @@ public class InvocableHandlerMethod extends HandlerMethod {
|
||||
protected Object doInvoke(Object... args) throws Exception {
|
||||
try {
|
||||
return getBridgedMethod().invoke(getBean(), args);
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
} catch (IllegalArgumentException ex) {
|
||||
assertTargetBean(getBridgedMethod(), getBean(), args);
|
||||
String text = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument");
|
||||
throw new IllegalStateException(formatInvokeError(text, args), ex);
|
||||
}
|
||||
catch (InvocationTargetException ex) {
|
||||
} catch (InvocationTargetException ex) {
|
||||
// Unwrap for HandlerExceptionResolvers ...
|
||||
Throwable targetException = ex.getTargetException();
|
||||
if (targetException instanceof RuntimeException) {
|
||||
throw (RuntimeException) targetException;
|
||||
}
|
||||
else if (targetException instanceof Error) {
|
||||
} else if (targetException instanceof Error) {
|
||||
throw (Error) targetException;
|
||||
}
|
||||
else if (targetException instanceof Exception) {
|
||||
} else if (targetException instanceof Exception) {
|
||||
throw (Exception) targetException;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
throw new IllegalStateException(formatInvokeError("Invocation failure", args), targetException);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* 字典数据模块,提供 {@link cn.iocoder.yudao.framework.dict.core.DictFrameworkUtils} 工具类
|
||||
*
|
||||
* <p>
|
||||
* 通过将字典缓存在内存中,保证性能
|
||||
*/
|
||||
package cn.iocoder.yudao.framework.dict;
|
||||
|
@ -4,7 +4,7 @@ import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 字典格式化
|
||||
*
|
||||
* <p>
|
||||
* 实现将字典数据的值,格式化成字典数据的标签
|
||||
*/
|
||||
@Target({ElementType.FIELD})
|
||||
|
@ -4,7 +4,7 @@ import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 给 Excel 列添加下拉选择数据
|
||||
*
|
||||
* <p>
|
||||
* 其中 {@link #dictType()} 和 {@link #functionName()} 二选一
|
||||
*
|
||||
* @author HUIHUI
|
||||
|
@ -11,7 +11,7 @@ import java.math.RoundingMode;
|
||||
|
||||
/**
|
||||
* 金额转换器
|
||||
*
|
||||
* <p>
|
||||
* 金额单位:分
|
||||
*
|
||||
* @author 芋道源码
|
||||
|
@ -4,9 +4,9 @@ import java.util.List;
|
||||
|
||||
/**
|
||||
* Excel 列下拉数据源获取接口
|
||||
*
|
||||
* <p>
|
||||
* 为什么不直接解析字典还搞个接口?考虑到有的下拉数据不是从字典中获取的所有需要做一个兼容
|
||||
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
public interface ExcelColumnSelectFunction {
|
||||
|
@ -37,7 +37,7 @@ public class SelectSheetWriteHandler implements SheetWriteHandler {
|
||||
|
||||
/**
|
||||
* 数据起始行从 0 开始
|
||||
*
|
||||
* <p>
|
||||
* 约定:本项目第一行有标题所以从 1 开始如果您的 Excel 有多行标题请自行更改
|
||||
*/
|
||||
public static final int FIRST_ROW = 1;
|
||||
|
@ -9,11 +9,11 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU
|
||||
|
||||
/**
|
||||
* {@link org.quartz.Scheduler} 的管理器,负责创建任务
|
||||
*
|
||||
* <p>
|
||||
* 考虑到实现的简洁性,我们使用 jobHandlerName 作为唯一标识,即:
|
||||
* 1. Job 的 {@link JobDetail#getKey()}
|
||||
* 2. Trigger 的 {@link Trigger#getKey()}
|
||||
*
|
||||
* <p>
|
||||
* 另外,jobHandlerName 对应到 Spring Bean 的名字,直接调用
|
||||
*
|
||||
* @author 芋道源码
|
||||
|
@ -1,7 +1,7 @@
|
||||
/**
|
||||
* 1. 定时任务,采用 Quartz 实现进程内的任务执行。
|
||||
* 考虑到高可用,使用 Quartz 自带的 MySQL 集群方案。
|
||||
*
|
||||
* <p>
|
||||
* 2. 异步任务,采用 Spring Async 异步执行。
|
||||
*/
|
||||
package cn.iocoder.yudao.framework.quartz;
|
||||
|
@ -15,7 +15,8 @@ import org.springframework.context.annotation.Bean;
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@ConditionalOnClass({MeterRegistryCustomizer.class})
|
||||
@ConditionalOnProperty(prefix = "yudao.metrics", value = "enable", matchIfMissing = true) // 允许使用 yudao.metrics.enable=false 禁用 Metrics
|
||||
@ConditionalOnProperty(prefix = "yudao.metrics", value = "enable", matchIfMissing = true)
|
||||
// 允许使用 yudao.metrics.enable=false 禁用 Metrics
|
||||
public class YudaoMetricsAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
|
@ -4,7 +4,7 @@ import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 打印业务编号 / 业务类型注解
|
||||
*
|
||||
* <p>
|
||||
* 使用时,需要设置 SkyWalking OAP Server 的 application.yaml 配置文件,修改 SW_SEARCHABLE_TAG_KEYS 配置项,
|
||||
* 增加 biz.type 和 biz.id 两值,然后重启 SkyWalking OAP Server 服务器。
|
||||
*
|
||||
|
@ -44,7 +44,8 @@ public class YudaoRedisMQConsumerAutoConfiguration {
|
||||
* 创建 Redis Pub/Sub 广播消费的容器
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnBean(AbstractRedisChannelMessageListener.class) // 只有 AbstractChannelMessageListener 存在的时候,才需要注册 Redis pubsub 监听
|
||||
@ConditionalOnBean(AbstractRedisChannelMessageListener.class)
|
||||
// 只有 AbstractChannelMessageListener 存在的时候,才需要注册 Redis pubsub 监听
|
||||
public RedisMessageListenerContainer redisMessageListenerContainer(
|
||||
RedisMQTemplate redisMQTemplate, List<AbstractRedisChannelMessageListener<?>> listeners) {
|
||||
// 创建 RedisMessageListenerContainer 对象
|
||||
@ -65,7 +66,8 @@ public class YudaoRedisMQConsumerAutoConfiguration {
|
||||
* 创建 Redis Stream 重新消费的任务
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnBean(AbstractRedisStreamMessageListener.class) // 只有 AbstractStreamMessageListener 存在的时候,才需要注册 Redis pubsub 监听
|
||||
@ConditionalOnBean(AbstractRedisStreamMessageListener.class)
|
||||
// 只有 AbstractStreamMessageListener 存在的时候,才需要注册 Redis pubsub 监听
|
||||
public RedisPendingMessageResendJob redisPendingMessageResendJob(List<AbstractRedisStreamMessageListener<?>> listeners,
|
||||
RedisMQTemplate redisTemplate,
|
||||
@Value("${spring.application.name}") String groupName,
|
||||
@ -75,11 +77,12 @@ public class YudaoRedisMQConsumerAutoConfiguration {
|
||||
|
||||
/**
|
||||
* 创建 Redis Stream 集群消费的容器
|
||||
*
|
||||
* <p>
|
||||
* 基础知识:<a href="https://www.geek-book.com/src/docs/redis/redis/redis.io/commands/xreadgroup.html">Redis Stream 的 xreadgroup 命令</a>
|
||||
*/
|
||||
@Bean(initMethod = "start", destroyMethod = "stop")
|
||||
@ConditionalOnBean(AbstractRedisStreamMessageListener.class) // 只有 AbstractStreamMessageListener 存在的时候,才需要注册 Redis pubsub 监听
|
||||
@ConditionalOnBean(AbstractRedisStreamMessageListener.class)
|
||||
// 只有 AbstractStreamMessageListener 存在的时候,才需要注册 Redis pubsub 监听
|
||||
public StreamMessageListenerContainer<String, ObjectRecord<String, String>> redisStreamMessageListenerContainer(
|
||||
RedisMQTemplate redisMQTemplate, List<AbstractRedisStreamMessageListener<?>> listeners) {
|
||||
RedisTemplate<String, ?> redisTemplate = redisMQTemplate.getRedisTemplate();
|
||||
|
@ -27,7 +27,7 @@ public class RedisPendingMessageResendJob {
|
||||
|
||||
/**
|
||||
* 消息超时时间,默认 5 分钟
|
||||
*
|
||||
* <p>
|
||||
* 1. 超时的消息才会被重新投递
|
||||
* 2. 由于定时任务 1 分钟一次,消息超时后不会被立即重投,极端情况下消息5分钟过期后,再等 1 分钟才会被扫瞄到
|
||||
*/
|
||||
|
@ -17,7 +17,6 @@ import java.util.List;
|
||||
* Redis Pub/Sub 监听器抽象类,用于实现广播消费
|
||||
*
|
||||
* @param <T> 消息类型。一定要填写噢,不然会报错
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public abstract class AbstractRedisChannelMessageListener<T extends AbstractRedisChannelMessage> implements MessageListener {
|
||||
|
@ -19,7 +19,6 @@ import java.util.List;
|
||||
* Redis Stream 监听器抽象类,用于实现集群消费
|
||||
*
|
||||
* @param <T> 消息类型。一定要填写噢,不然会报错
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public abstract class AbstractRedisStreamMessageListener<T extends AbstractRedisStreamMessage>
|
||||
|
@ -2,10 +2,10 @@ package cn.iocoder.yudao.framework.datasource.core.enums;
|
||||
|
||||
/**
|
||||
* 对应于多数据源中不同数据源配置
|
||||
*
|
||||
* <p>
|
||||
* 通过在方法上,使用 {@link com.baomidou.dynamic.datasource.annotation.DS} 注解,设置使用的数据源。
|
||||
* 注意,默认是 {@link #MASTER} 数据源
|
||||
*
|
||||
* <p>
|
||||
* 对应官方文档为 http://dynamic-datasource.com/guide/customize/Annotation.html
|
||||
*/
|
||||
public interface DataSourceEnum {
|
||||
|
@ -21,7 +21,8 @@ import org.springframework.core.env.ConfigurableEnvironment;
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@AutoConfiguration(before = MybatisPlusAutoConfiguration.class) // 目的:先于 MyBatis Plus 自动配置,避免 @MapperScan 可能扫描不到 Mapper 打印 warn 日志
|
||||
@AutoConfiguration(before = MybatisPlusAutoConfiguration.class)
|
||||
// 目的:先于 MyBatis Plus 自动配置,避免 @MapperScan 可能扫描不到 Mapper 打印 warn 日志
|
||||
@MapperScan(value = "${yudao.info.base-package}", annotationClass = Mapper.class,
|
||||
lazyInitialization = "${mybatis.lazy-initialization:false}") // Mapper 懒加载,目前仅用于单元测试
|
||||
public class YudaoMybatisAutoConfiguration {
|
||||
|
@ -13,7 +13,7 @@ import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 基础实体对象
|
||||
*
|
||||
* <p>
|
||||
* 为什么实现 {@link TransPojo} 接口?
|
||||
* 因为使用 Easy-Trans TransType.SIMPLE 模式,集成 MyBatis Plus 查询
|
||||
*
|
||||
@ -35,14 +35,14 @@ public abstract class BaseDO implements Serializable, TransPojo {
|
||||
private LocalDateTime updateTime;
|
||||
/**
|
||||
* 创建者,目前使用 SysUser 的 id 编号
|
||||
*
|
||||
* <p>
|
||||
* 使用 String 类型的原因是,未来可能会存在非数值的情况,留好拓展性。
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT, jdbcType = JdbcType.VARCHAR)
|
||||
private String creator;
|
||||
/**
|
||||
* 更新者,目前使用 SysUser 的 id 编号
|
||||
*
|
||||
* <p>
|
||||
* 使用 String 类型的原因是,未来可能会存在非数值的情况,留好拓展性。
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE, jdbcType = JdbcType.VARCHAR)
|
||||
|
@ -30,7 +30,7 @@ public enum DbTypeEnum {
|
||||
|
||||
/**
|
||||
* PostgreSQL
|
||||
*
|
||||
* <p>
|
||||
* 华为 openGauss 使用 ProductName 与 PostgreSQL 相同
|
||||
*/
|
||||
POSTGRE_SQL(DbType.POSTGRE_SQL, "PostgreSQL", "POSITION('#{value}' IN #{column}) <> 0"),
|
||||
|
@ -10,7 +10,7 @@ import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 通用参数填充实现类
|
||||
*
|
||||
* <p>
|
||||
* 如果没有显式的对通用参数进行赋值,这里会对通用参数进行填充、赋值
|
||||
*
|
||||
* @author hexiaowu
|
||||
|
@ -26,7 +26,7 @@ import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 在 MyBatis Plus 的 BaseMapper 的基础上拓展,提供更多的能力
|
||||
*
|
||||
* <p>
|
||||
* 1. {@link BaseMapper} 为 MyBatis Plus 的基础接口,提供基础的 CRUD 能力
|
||||
* 2. {@link MPJBaseMapper} 为 MyBatis Plus Join 的基础接口,提供连表 Join 能力
|
||||
*/
|
||||
|
@ -11,7 +11,7 @@ import java.util.Collection;
|
||||
|
||||
/**
|
||||
* 拓展 MyBatis Plus QueryWrapper 类,主要增加如下功能:
|
||||
*
|
||||
* <p>
|
||||
* 1. 拼接条件的方法,增加 xxxIfPresent 方法,用于判断值不存在的时候,不要拼接到条件中。
|
||||
*
|
||||
* @param <T> 数据类型
|
||||
@ -141,7 +141,7 @@ public class QueryWrapperX<T> extends QueryWrapper<T> {
|
||||
|
||||
/**
|
||||
* 设置只返回最后一条
|
||||
*
|
||||
* <p>
|
||||
* TODO 芋艿:不是完美解,需要在思考下。如果使用多数据源,并且数据源是多种类型时,可能会存在问题:实现之返回一条的语法不同
|
||||
*
|
||||
* @return this
|
||||
|
@ -21,7 +21,7 @@ public class TranslateUtils {
|
||||
|
||||
/**
|
||||
* 数据翻译
|
||||
*
|
||||
* <p>
|
||||
* 使用场景:无法使用 @TransMethodResult 注解的场景,只能通过手动触发翻译
|
||||
*
|
||||
* @param data 数据
|
||||
|
@ -22,10 +22,11 @@ public @interface Idempotent {
|
||||
|
||||
/**
|
||||
* 幂等的超时时间,默认为 1 秒
|
||||
*
|
||||
* <p>
|
||||
* 注意,如果执行时间超过它,请求还是会进来
|
||||
*/
|
||||
int timeout() default 1;
|
||||
|
||||
/**
|
||||
* 时间单位,默认为 SECONDS 秒
|
||||
*/
|
||||
@ -44,6 +45,7 @@ public @interface Idempotent {
|
||||
* @see ExpressionIdempotentKeyResolver 自定义表达式,通过 {@link #keyArg()} 计算
|
||||
*/
|
||||
Class<? extends IdempotentKeyResolver> keyResolver() default DefaultIdempotentKeyResolver.class;
|
||||
|
||||
/**
|
||||
* 使用的 Key 参数
|
||||
*/
|
||||
@ -51,10 +53,10 @@ public @interface Idempotent {
|
||||
|
||||
/**
|
||||
* 删除 Key,当发生异常时候
|
||||
*
|
||||
* <p>
|
||||
* 问题:为什么发生异常时,需要删除 Key 呢?
|
||||
* 回答:发生异常时,说明业务发生错误,此时需要删除 Key,避免下次请求无法正常执行。
|
||||
*
|
||||
* <p>
|
||||
* 问题:为什么不搞 deleteWhenSuccess 执行成功时,需要删除 Key 呢?
|
||||
* 回答:这种情况下,本质上是分布式锁,推荐使用 @Lock4j 注解
|
||||
*/
|
||||
|
@ -8,7 +8,7 @@ import org.aspectj.lang.JoinPoint;
|
||||
|
||||
/**
|
||||
* 默认(全局级别)幂等 Key 解析器,使用方法名 + 方法参数,组装成一个 Key
|
||||
*
|
||||
* <p>
|
||||
* 为了避免 Key 过长,使用 MD5 进行“压缩”
|
||||
*
|
||||
* @author 芋道源码
|
||||
|
@ -9,7 +9,7 @@ import org.aspectj.lang.JoinPoint;
|
||||
|
||||
/**
|
||||
* 用户级别的幂等 Key 解析器,使用方法名 + 方法参数 + userId + userType,组装成一个 Key
|
||||
*
|
||||
* <p>
|
||||
* 为了避免 Key 过长,使用 MD5 进行“压缩”
|
||||
*
|
||||
* @author 芋道源码
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user