committed by
GitHub
619 changed files with 19602 additions and 1218 deletions
@ -0,0 +1,200 @@ |
|||||
|
<script lang="ts" setup> |
||||
|
import type { CSSProperties, PropType } from 'vue'; |
||||
|
|
||||
|
import type { Nullable } from '@vben/types'; |
||||
|
|
||||
|
import { |
||||
|
computed, |
||||
|
onMounted, |
||||
|
onUnmounted, |
||||
|
ref, |
||||
|
unref, |
||||
|
useAttrs, |
||||
|
useTemplateRef, |
||||
|
} from 'vue'; |
||||
|
|
||||
|
import { useNamespace } from '@vben/hooks'; |
||||
|
|
||||
|
import { useDebounceFn } from '@vueuse/core'; |
||||
|
import Cropper from 'cropperjs'; |
||||
|
|
||||
|
import 'cropperjs/dist/cropper.css'; |
||||
|
|
||||
|
type Options = Cropper.Options; |
||||
|
type ElRef<T extends HTMLElement = HTMLDivElement> = Nullable<T>; |
||||
|
|
||||
|
const props = defineProps({ |
||||
|
src: { type: String, required: true }, |
||||
|
alt: { type: String, default: '' }, |
||||
|
circled: { type: Boolean, default: false }, |
||||
|
realTimePreview: { type: Boolean, default: true }, |
||||
|
height: { type: [String, Number], default: '360px' }, |
||||
|
crossorigin: { |
||||
|
type: String as PropType<'' | 'anonymous' | 'use-credentials' | undefined>, |
||||
|
default: undefined, |
||||
|
}, |
||||
|
imageStyle: { type: Object as PropType<CSSProperties>, default: () => ({}) }, |
||||
|
options: { type: Object as PropType<Options>, default: () => ({}) }, |
||||
|
}); |
||||
|
const emits = defineEmits(['cropend', 'ready', 'cropendError']); |
||||
|
const defaultOptions: Options = { |
||||
|
aspectRatio: 1, |
||||
|
zoomable: true, |
||||
|
zoomOnTouch: true, |
||||
|
zoomOnWheel: true, |
||||
|
cropBoxMovable: true, |
||||
|
cropBoxResizable: true, |
||||
|
toggleDragModeOnDblclick: true, |
||||
|
autoCrop: true, |
||||
|
background: true, |
||||
|
highlight: true, |
||||
|
center: true, |
||||
|
responsive: true, |
||||
|
restore: true, |
||||
|
checkCrossOrigin: true, |
||||
|
checkOrientation: true, |
||||
|
scalable: true, |
||||
|
modal: true, |
||||
|
guides: true, |
||||
|
movable: true, |
||||
|
rotatable: true, |
||||
|
}; |
||||
|
|
||||
|
const attrs = useAttrs(); |
||||
|
|
||||
|
const imgElRef = useTemplateRef<ElRef<HTMLImageElement>>('imgElRef'); |
||||
|
const cropper = ref<Nullable<Cropper>>(); |
||||
|
const isReady = ref(false); |
||||
|
|
||||
|
const { b, is } = useNamespace('cropper-image'); |
||||
|
const debounceRealTimeCroppered = useDebounceFn(realTimeCroppered, 80); |
||||
|
|
||||
|
const getImageStyle = computed((): CSSProperties => { |
||||
|
return { |
||||
|
height: props.height, |
||||
|
maxWidth: '100%', |
||||
|
...props.imageStyle, |
||||
|
}; |
||||
|
}); |
||||
|
|
||||
|
const getClass = computed(() => { |
||||
|
return [b(), attrs.class, is('circled', props.circled)]; |
||||
|
}); |
||||
|
|
||||
|
const getWrapperStyle = computed((): CSSProperties => { |
||||
|
return { height: `${`${props.height}`.replace(/px/, '')}px` }; |
||||
|
}); |
||||
|
|
||||
|
onMounted(init); |
||||
|
|
||||
|
onUnmounted(() => { |
||||
|
cropper.value?.destroy(); |
||||
|
}); |
||||
|
|
||||
|
async function init() { |
||||
|
const imgEl = unref(imgElRef); |
||||
|
if (!imgEl) { |
||||
|
return; |
||||
|
} |
||||
|
cropper.value = new Cropper(imgEl, { |
||||
|
...defaultOptions, |
||||
|
ready: () => { |
||||
|
isReady.value = true; |
||||
|
realTimeCroppered(); |
||||
|
emits('ready', cropper.value); |
||||
|
}, |
||||
|
crop() { |
||||
|
debounceRealTimeCroppered(); |
||||
|
}, |
||||
|
zoom() { |
||||
|
debounceRealTimeCroppered(); |
||||
|
}, |
||||
|
cropmove() { |
||||
|
debounceRealTimeCroppered(); |
||||
|
}, |
||||
|
...props.options, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
// Real-time display preview |
||||
|
function realTimeCroppered() { |
||||
|
props.realTimePreview && croppered(); |
||||
|
} |
||||
|
|
||||
|
// event: return base64 and width and height information after cropping |
||||
|
function croppered() { |
||||
|
if (!cropper.value) { |
||||
|
return; |
||||
|
} |
||||
|
const imgInfo = cropper.value.getData(); |
||||
|
const canvas = props.circled |
||||
|
? getRoundedCanvas() |
||||
|
: cropper.value.getCroppedCanvas(); |
||||
|
canvas.toBlob((blob) => { |
||||
|
if (!blob) { |
||||
|
return; |
||||
|
} |
||||
|
const fileReader: FileReader = new FileReader(); |
||||
|
fileReader.readAsDataURL(blob); |
||||
|
fileReader.onloadend = (e) => { |
||||
|
emits('cropend', { |
||||
|
imgBase64: e.target?.result ?? '', |
||||
|
imgInfo, |
||||
|
}); |
||||
|
}; |
||||
|
// eslint-disable-next-line unicorn/prefer-add-event-listener |
||||
|
fileReader.onerror = () => { |
||||
|
emits('cropendError'); |
||||
|
}; |
||||
|
}, 'image/png'); |
||||
|
} |
||||
|
|
||||
|
// Get a circular picture canvas |
||||
|
function getRoundedCanvas() { |
||||
|
const sourceCanvas = cropper.value!.getCroppedCanvas(); |
||||
|
const canvas = document.createElement('canvas'); |
||||
|
const context = canvas.getContext('2d')!; |
||||
|
const width = sourceCanvas.width; |
||||
|
const height = sourceCanvas.height; |
||||
|
canvas.width = width; |
||||
|
canvas.height = height; |
||||
|
context.imageSmoothingEnabled = true; |
||||
|
context.drawImage(sourceCanvas, 0, 0, width, height); |
||||
|
context.globalCompositeOperation = 'destination-in'; |
||||
|
context.beginPath(); |
||||
|
context.arc( |
||||
|
width / 2, |
||||
|
height / 2, |
||||
|
Math.min(width, height) / 2, |
||||
|
0, |
||||
|
2 * Math.PI, |
||||
|
true, |
||||
|
); |
||||
|
context.fill(); |
||||
|
return canvas; |
||||
|
} |
||||
|
</script> |
||||
|
<template> |
||||
|
<div :class="getClass" :style="getWrapperStyle"> |
||||
|
<img |
||||
|
v-show="isReady" |
||||
|
ref="imgElRef" |
||||
|
:src="src" |
||||
|
:alt="alt" |
||||
|
:crossorigin="crossorigin" |
||||
|
:style="getImageStyle" |
||||
|
/> |
||||
|
</div> |
||||
|
</template> |
||||
|
<style scoped lang="scss"> |
||||
|
$namespace: vben; |
||||
|
|
||||
|
.#{$namespace}-cropper-image { |
||||
|
&.is-circled { |
||||
|
.cropper-view-box, |
||||
|
.cropper-face { |
||||
|
border-radius: 50%; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,159 @@ |
|||||
|
<script setup lang="ts"> |
||||
|
import type { ButtonProps } from 'ant-design-vue/es/button'; |
||||
|
|
||||
|
import type { CSSProperties, PropType } from 'vue'; |
||||
|
|
||||
|
import { computed, ref, unref, watch, watchEffect } from 'vue'; |
||||
|
|
||||
|
import { useVbenModal } from '@vben/common-ui'; |
||||
|
import { useNamespace } from '@vben/hooks'; |
||||
|
import { createIconifyIcon } from '@vben/icons'; |
||||
|
import { useI18n } from '@vben/locales'; |
||||
|
|
||||
|
import { Button } from 'ant-design-vue'; |
||||
|
|
||||
|
import CropperModal from './CropperModal.vue'; |
||||
|
|
||||
|
interface File { |
||||
|
file: Blob; |
||||
|
fileName?: string; |
||||
|
name: string; |
||||
|
} |
||||
|
|
||||
|
const props = defineProps({ |
||||
|
width: { type: [String, Number], default: '200px' }, |
||||
|
value: { type: String, default: '' }, |
||||
|
showBtn: { type: Boolean, default: true }, |
||||
|
btnProps: { type: Object as PropType<ButtonProps>, default: undefined }, |
||||
|
btnText: { type: String, default: '' }, |
||||
|
uploadApi: { |
||||
|
type: Function as PropType<(file: File) => Promise<void>>, |
||||
|
default: undefined, |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
const emits = defineEmits(['update:value', 'change']); |
||||
|
|
||||
|
const UploadIcon = createIconifyIcon('ant-design:cloud-upload-outlined'); |
||||
|
|
||||
|
const sourceValue = ref(props.value || ''); |
||||
|
const { b, e } = useNamespace('cropper-avatar'); |
||||
|
const [Modal, modalApi] = useVbenModal({ |
||||
|
connectedComponent: CropperModal, |
||||
|
}); |
||||
|
const { t } = useI18n(); |
||||
|
|
||||
|
const getWidth = computed(() => `${`${props.width}`.replace(/px/, '')}px`); |
||||
|
|
||||
|
const getIconWidth = computed( |
||||
|
() => `${Number.parseInt(`${props.width}`.replace(/px/, '')) / 2}px`, |
||||
|
); |
||||
|
|
||||
|
const getStyle = computed((): CSSProperties => ({ width: unref(getWidth) })); |
||||
|
|
||||
|
const getImageWrapperStyle = computed( |
||||
|
(): CSSProperties => ({ width: unref(getWidth), height: unref(getWidth) }), |
||||
|
); |
||||
|
|
||||
|
watchEffect(() => { |
||||
|
sourceValue.value = props.value || ''; |
||||
|
}); |
||||
|
|
||||
|
watch( |
||||
|
() => sourceValue.value, |
||||
|
(v: string) => { |
||||
|
emits('update:value', v); |
||||
|
}, |
||||
|
); |
||||
|
|
||||
|
function handleUploadSuccess(url: string) { |
||||
|
sourceValue.value = url; |
||||
|
emits('change', url); |
||||
|
} |
||||
|
function openModal() { |
||||
|
modalApi.open(); |
||||
|
} |
||||
|
function closeModal() { |
||||
|
modalApi.close(); |
||||
|
} |
||||
|
|
||||
|
defineExpose({ openModal, closeModal }); |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<div :class="b()" :style="getStyle"> |
||||
|
<div |
||||
|
:class="e(`image-wrapper`)" |
||||
|
:style="getImageWrapperStyle" |
||||
|
@click="openModal" |
||||
|
> |
||||
|
<div :class="e(`image-mask`)" :style="getImageWrapperStyle"> |
||||
|
<UploadIcon |
||||
|
:width="getIconWidth" |
||||
|
:style="getImageWrapperStyle" |
||||
|
color="#d6d6d6" |
||||
|
/> |
||||
|
</div> |
||||
|
<img :src="sourceValue" v-if="sourceValue" alt="avatar" /> |
||||
|
</div> |
||||
|
<Button |
||||
|
:class="e(`upload-btn`)" |
||||
|
@click="openModal" |
||||
|
v-if="showBtn" |
||||
|
v-bind="btnProps" |
||||
|
> |
||||
|
{{ btnText ? btnText : t('cropper.selectImage') }} |
||||
|
</Button> |
||||
|
|
||||
|
<Modal |
||||
|
@upload-success="handleUploadSuccess" |
||||
|
:upload-api="uploadApi" |
||||
|
:src="sourceValue" |
||||
|
/> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<style scoped lang="scss"> |
||||
|
$namespace: vben; |
||||
|
|
||||
|
.#{$namespace}-cropper-avatar { |
||||
|
display: inline-block; |
||||
|
text-align: center; |
||||
|
|
||||
|
&__image-wrapper { |
||||
|
overflow: hidden; |
||||
|
cursor: pointer; |
||||
|
border: var(--border); |
||||
|
border-radius: 50%; |
||||
|
|
||||
|
img { |
||||
|
width: 100%; |
||||
|
// height: 100%; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
&__image-mask { |
||||
|
position: absolute; |
||||
|
width: inherit; |
||||
|
height: inherit; |
||||
|
cursor: pointer; |
||||
|
background: rgb(0 0 0 / 40%); |
||||
|
border: inherit; |
||||
|
border-radius: inherit; |
||||
|
opacity: 0; |
||||
|
transition: opacity 0.4s; |
||||
|
|
||||
|
::v-deep(svg) { |
||||
|
margin: auto; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
&__image-mask:hover { |
||||
|
opacity: 40; |
||||
|
} |
||||
|
|
||||
|
&__upload-btn { |
||||
|
margin: 10px auto; |
||||
|
} |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,309 @@ |
|||||
|
<script setup lang="ts"> |
||||
|
import type { UploadProps } from 'ant-design-vue'; |
||||
|
|
||||
|
import type { PropType } from 'vue'; |
||||
|
|
||||
|
import type { CropendResult, Cropper } from './types'; |
||||
|
|
||||
|
import { h, ref } from 'vue'; |
||||
|
|
||||
|
import { useVbenModal } from '@vben/common-ui'; |
||||
|
import { useNamespace } from '@vben/hooks'; |
||||
|
import { createIconifyIcon } from '@vben/icons'; |
||||
|
import { $t } from '@vben/locales'; |
||||
|
import { isFunction } from '@vben/utils'; |
||||
|
|
||||
|
import { dataURLtoBlob } from '@abp/core'; |
||||
|
import { Avatar, Button, Space, Tooltip, Upload } from 'ant-design-vue'; |
||||
|
|
||||
|
import CropperImage from './Cropper.vue'; |
||||
|
|
||||
|
type ApiFunParams = { file: Blob; fileName: string; name: string }; |
||||
|
|
||||
|
const props = defineProps({ |
||||
|
circled: { type: Boolean, default: true }, |
||||
|
uploadApi: { |
||||
|
type: Function as PropType<(params: ApiFunParams) => Promise<any>>, |
||||
|
default: undefined, |
||||
|
}, |
||||
|
src: { type: String, default: '' }, |
||||
|
}); |
||||
|
|
||||
|
const emits = defineEmits<{ |
||||
|
(event: 'uploadSuccess', url: string): void; |
||||
|
}>(); |
||||
|
|
||||
|
const UploadIcon = createIconifyIcon('ant-design:upload-outlined'); |
||||
|
const ResetIcon = createIconifyIcon('ant-design:reload-outlined'); |
||||
|
const RotateLeftIcon = createIconifyIcon('ant-design:rotate-left-outlined'); |
||||
|
const RotateRightIcon = createIconifyIcon('ant-design:rotate-right-outlined'); |
||||
|
const ScaleXIcon = createIconifyIcon('vaadin:arrows-long-h'); |
||||
|
const ScaleYIcon = createIconifyIcon('vaadin:arrows-long-v'); |
||||
|
const ZoomInIcon = createIconifyIcon('ant-design:zoom-in-outlined'); |
||||
|
const ZoomOutIcon = createIconifyIcon('ant-design:zoom-out-outlined'); |
||||
|
|
||||
|
let fileName = ''; |
||||
|
const src = ref(props.src || ''); |
||||
|
const previewSource = ref(''); |
||||
|
const fileList = ref<UploadProps['fileList']>([]); |
||||
|
const cropper = ref<Cropper>(); |
||||
|
let scaleX = 1; |
||||
|
let scaleY = 1; |
||||
|
|
||||
|
const { b, e } = useNamespace('cropper-am'); |
||||
|
const [Modal, modalApi] = useVbenModal({ |
||||
|
class: 'w-[800px]', |
||||
|
fullscreen: false, |
||||
|
fullscreenButton: false, |
||||
|
confirmText: $t('cropper.confirmText'), |
||||
|
onConfirm: handleOk, |
||||
|
title: $t('cropper.title'), |
||||
|
}); |
||||
|
function handleBeforeUpload(file: File) { |
||||
|
const reader = new FileReader(); |
||||
|
reader.readAsDataURL(file); |
||||
|
src.value = ''; |
||||
|
previewSource.value = ''; |
||||
|
reader.addEventListener('load', (e) => { |
||||
|
src.value = (e.target?.result as string) ?? ''; |
||||
|
fileName = file.name; |
||||
|
}); |
||||
|
return false; |
||||
|
} |
||||
|
function handleCropend({ imgBase64 }: CropendResult) { |
||||
|
previewSource.value = imgBase64; |
||||
|
} |
||||
|
|
||||
|
function handleReady(cropperInstance: Cropper) { |
||||
|
cropper.value = cropperInstance; |
||||
|
} |
||||
|
function handlerToolbar(event: string, arg?: number) { |
||||
|
if (!cropper.value) { |
||||
|
return; |
||||
|
} |
||||
|
if (event === 'scaleX') { |
||||
|
scaleX = arg = scaleX === -1 ? 1 : -1; |
||||
|
} |
||||
|
if (event === 'scaleY') { |
||||
|
scaleY = arg = scaleY === -1 ? 1 : -1; |
||||
|
} |
||||
|
switch (event) { |
||||
|
case 'reset': { |
||||
|
return cropper.value.reset(); |
||||
|
} |
||||
|
case 'rotate': { |
||||
|
return cropper.value.rotate(arg!); |
||||
|
} |
||||
|
case 'scaleX': { |
||||
|
return cropper.value.scaleX(scaleX); |
||||
|
} |
||||
|
case 'scaleY': { |
||||
|
return cropper.value.scaleY(scaleY); |
||||
|
} |
||||
|
case 'zoom': { |
||||
|
return cropper.value.zoom(arg!); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
async function handleOk() { |
||||
|
const uploadApi = props.uploadApi; |
||||
|
if (uploadApi && isFunction(uploadApi)) { |
||||
|
const blob = dataURLtoBlob(previewSource.value); |
||||
|
try { |
||||
|
modalApi.setState({ submitting: true }); |
||||
|
await uploadApi({ name: 'file', file: blob, fileName }); |
||||
|
emits('uploadSuccess', previewSource.value); |
||||
|
modalApi.close(); |
||||
|
} finally { |
||||
|
modalApi.setState({ submitting: false }); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<Modal> |
||||
|
<div :class="b()"> |
||||
|
<div :class="e('left')"> |
||||
|
<div :class="e('cropper')"> |
||||
|
<CropperImage |
||||
|
v-if="src" |
||||
|
:src="src" |
||||
|
height="300px" |
||||
|
:circled="circled" |
||||
|
@cropend="handleCropend" |
||||
|
@ready="handleReady" |
||||
|
/> |
||||
|
</div> |
||||
|
<div :class="e('toolbar')"> |
||||
|
<Upload |
||||
|
:file-list="fileList" |
||||
|
accept="image/*" |
||||
|
:before-upload="handleBeforeUpload" |
||||
|
> |
||||
|
<Tooltip :title="$t('cropper.selectImage')" placement="bottom"> |
||||
|
<Button size="small" :icon="h(UploadIcon)" type="primary" /> |
||||
|
</Tooltip> |
||||
|
</Upload> |
||||
|
<Space> |
||||
|
<Tooltip :title="$t('cropper.btn_reset')" placement="bottom"> |
||||
|
<Button |
||||
|
type="primary" |
||||
|
:icon="h(ResetIcon)" |
||||
|
size="small" |
||||
|
:disabled="!src" |
||||
|
@click="handlerToolbar('reset')" |
||||
|
/> |
||||
|
</Tooltip> |
||||
|
<Tooltip :title="$t('cropper.btn_rotate_left')" placement="bottom"> |
||||
|
<Button |
||||
|
type="primary" |
||||
|
:icon="h(RotateLeftIcon)" |
||||
|
size="small" |
||||
|
:disabled="!src" |
||||
|
@click="handlerToolbar('rotate', -45)" |
||||
|
/> |
||||
|
</Tooltip> |
||||
|
<Tooltip :title="$t('cropper.btn_rotate_right')" placement="bottom"> |
||||
|
<Button |
||||
|
type="primary" |
||||
|
:icon="h(RotateRightIcon)" |
||||
|
size="small" |
||||
|
:disabled="!src" |
||||
|
@click="handlerToolbar('rotate', 45)" |
||||
|
/> |
||||
|
</Tooltip> |
||||
|
<Tooltip :title="$t('cropper.btn_scale_x')" placement="bottom"> |
||||
|
<Button |
||||
|
type="primary" |
||||
|
:icon="h(ScaleXIcon)" |
||||
|
size="small" |
||||
|
:disabled="!src" |
||||
|
@click="handlerToolbar('scaleX')" |
||||
|
/> |
||||
|
</Tooltip> |
||||
|
<Tooltip :title="$t('cropper.btn_scale_y')" placement="bottom"> |
||||
|
<Button |
||||
|
type="primary" |
||||
|
:icon="h(ScaleYIcon)" |
||||
|
size="small" |
||||
|
:disabled="!src" |
||||
|
@click="handlerToolbar('scaleY')" |
||||
|
/> |
||||
|
</Tooltip> |
||||
|
<Tooltip :title="$t('cropper.btn_zoom_in')" placement="bottom"> |
||||
|
<Button |
||||
|
type="primary" |
||||
|
:icon="h(ZoomInIcon)" |
||||
|
size="small" |
||||
|
:disabled="!src" |
||||
|
@click="handlerToolbar('zoom', 0.1)" |
||||
|
/> |
||||
|
</Tooltip> |
||||
|
<Tooltip :title="$t('cropper.btn_zoom_out')" placement="bottom"> |
||||
|
<Button |
||||
|
type="primary" |
||||
|
:icon="h(ZoomOutIcon)" |
||||
|
size="small" |
||||
|
:disabled="!src" |
||||
|
@click="handlerToolbar('zoom', -0.1)" |
||||
|
/> |
||||
|
</Tooltip> |
||||
|
</Space> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div :class="e('right')"> |
||||
|
<div :class="e(`preview`)"> |
||||
|
<img |
||||
|
:src="previewSource" |
||||
|
v-if="previewSource" |
||||
|
:alt="$t('cropper.preview')" |
||||
|
/> |
||||
|
</div> |
||||
|
<template v-if="previewSource"> |
||||
|
<div :class="e(`group`)"> |
||||
|
<Avatar :src="previewSource" size="large" /> |
||||
|
<Avatar :src="previewSource" :size="48" /> |
||||
|
<Avatar :src="previewSource" :size="64" /> |
||||
|
<Avatar :src="previewSource" :size="80" /> |
||||
|
</div> |
||||
|
</template> |
||||
|
</div> |
||||
|
</div> |
||||
|
</Modal> |
||||
|
</template> |
||||
|
|
||||
|
<style scoped lang="scss"> |
||||
|
$namespace: vben; |
||||
|
|
||||
|
.#{$namespace}-cropper-am { |
||||
|
display: flex; |
||||
|
|
||||
|
&__left, |
||||
|
&__right { |
||||
|
height: 340px; |
||||
|
} |
||||
|
|
||||
|
&__left { |
||||
|
width: 55%; |
||||
|
} |
||||
|
|
||||
|
&__right { |
||||
|
width: 45%; |
||||
|
} |
||||
|
|
||||
|
&__cropper { |
||||
|
height: 300px; |
||||
|
background: #eee; |
||||
|
background-image: |
||||
|
linear-gradient( |
||||
|
45deg, |
||||
|
rgb(0 0 0 / 25%) 25%, |
||||
|
transparent 0, |
||||
|
transparent 75%, |
||||
|
rgb(0 0 0 / 25%) 0 |
||||
|
), |
||||
|
linear-gradient( |
||||
|
45deg, |
||||
|
rgb(0 0 0 / 25%) 25%, |
||||
|
transparent 0, |
||||
|
transparent 75%, |
||||
|
rgb(0 0 0 / 25%) 0 |
||||
|
); |
||||
|
background-position: |
||||
|
0 0, |
||||
|
12px 12px; |
||||
|
background-size: 24px 24px; |
||||
|
} |
||||
|
|
||||
|
&__toolbar { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: space-between; |
||||
|
margin-top: 10px; |
||||
|
} |
||||
|
|
||||
|
&__preview { |
||||
|
width: 220px; |
||||
|
height: 220px; |
||||
|
margin: 0 auto; |
||||
|
overflow: hidden; |
||||
|
border: var(--border); |
||||
|
border-radius: 50%; |
||||
|
|
||||
|
img { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
&__group { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: space-around; |
||||
|
padding-top: 8px; |
||||
|
margin-top: 8px; |
||||
|
border-top: var(--border); |
||||
|
} |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,2 @@ |
|||||
|
export { default as CropperAvatar } from './CropperAvatar.vue'; |
||||
|
export { default as CropperModal } from './CropperModal.vue'; |
||||
@ -0,0 +1,8 @@ |
|||||
|
import type Cropper from 'cropperjs'; |
||||
|
|
||||
|
export interface CropendResult { |
||||
|
imgBase64: string; |
||||
|
imgInfo: Cropper.Data; |
||||
|
} |
||||
|
|
||||
|
export type { Cropper }; |
||||
@ -0,0 +1,20 @@ |
|||||
|
import type { SupportedLanguagesType } from '@vben/locales'; |
||||
|
|
||||
|
import { loadLocalesMapFromDir } from '@vben/locales'; |
||||
|
|
||||
|
const modules = import.meta.glob('./langs/**/*.json'); |
||||
|
|
||||
|
const localesMap = loadLocalesMapFromDir( |
||||
|
/\.\/langs\/([^/]+)\/(.*)\.json$/, |
||||
|
modules, |
||||
|
); |
||||
|
|
||||
|
/** |
||||
|
* 加载自定义组件本地化资源 |
||||
|
* @param lang 当前语言 |
||||
|
* @returns 资源集合 |
||||
|
*/ |
||||
|
export async function loadComponentMessages(lang: SupportedLanguagesType) { |
||||
|
const locales = localesMap[lang]?.(); |
||||
|
return locales; |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
{ |
||||
|
"confirmText": "Confirm and upload", |
||||
|
"title": "Avatar upload", |
||||
|
"selectImage": "Select Image", |
||||
|
"btn_rotate_left": "Counterclockwise rotation", |
||||
|
"btn_rotate_right": "Clockwise rotation", |
||||
|
"btn_scale_x": "Flip horizontal", |
||||
|
"btn_scale_y": "Flip vertical", |
||||
|
"btn_zoom_in": "Zoom in", |
||||
|
"btn_zoom_out": "Zoom out", |
||||
|
"btn_reset": "Reset", |
||||
|
"preview": "Preivew", |
||||
|
"uploadSuccess": "Uploaded success!" |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
{ |
||||
|
"confirmText": "确认并上传", |
||||
|
"title": "头像上传", |
||||
|
"selectImage": "选择图片", |
||||
|
"btn_rotate_left": "逆时针旋转", |
||||
|
"btn_rotate_right": "顺时针旋转", |
||||
|
"btn_scale_x": "水平翻转", |
||||
|
"btn_scale_y": "垂直翻转", |
||||
|
"btn_zoom_in": "放大", |
||||
|
"btn_zoom_out": "缩小", |
||||
|
"btn_reset": "重置", |
||||
|
"preview": "预览", |
||||
|
"uploadSuccess": "上传成功!" |
||||
|
} |
||||
@ -0,0 +1,42 @@ |
|||||
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */ |
||||
|
/** |
||||
|
* @description: base64 to blob |
||||
|
*/ |
||||
|
export function dataURLtoBlob(base64Buf: string): Blob { |
||||
|
const arr = base64Buf.split(','); |
||||
|
const typeItem = arr[0]; |
||||
|
const mime = typeItem?.match(/:(.*?);/)?.[1]; |
||||
|
const bstr = window.atob(arr[1]!); |
||||
|
let n = bstr.length; |
||||
|
const u8arr = new Uint8Array(n); |
||||
|
while (n--) { |
||||
|
u8arr[n] = bstr.codePointAt(n)!; |
||||
|
} |
||||
|
return new Blob([u8arr], { type: mime }); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* img url to base64 |
||||
|
* @param url |
||||
|
*/ |
||||
|
export function urlToBase64(url: string, mineType?: string): Promise<string> { |
||||
|
return new Promise((resolve, reject) => { |
||||
|
let canvas = document.createElement('CANVAS') as HTMLCanvasElement | null; |
||||
|
const ctx = canvas!.getContext('2d'); |
||||
|
|
||||
|
const img = new Image(); |
||||
|
img.crossOrigin = ''; |
||||
|
img.addEventListener('load', () => { |
||||
|
if (!canvas || !ctx) { |
||||
|
return reject(new Error('canvas or ctx is null!')); |
||||
|
} |
||||
|
canvas.height = img.height; |
||||
|
canvas.width = img.width; |
||||
|
ctx.drawImage(img, 0, 0); |
||||
|
const dataURL = canvas.toDataURL(mineType || 'image/png'); |
||||
|
canvas = null; |
||||
|
resolve(dataURL); |
||||
|
}); |
||||
|
img.src = url; |
||||
|
}); |
||||
|
} |
||||
@ -0,0 +1,5 @@ |
|||||
|
namespace LINGYUN.Abp.BlobStoring.Tencent; |
||||
|
internal static class BlobStoringTencentConsts |
||||
|
{ |
||||
|
public const string HttpClient = "BlobStoring.Tencent"; |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
using LINGYUN.Abp.BlobStoring.Tencent; |
||||
|
|
||||
|
namespace Microsoft.Extensions.DependencyInjection; |
||||
|
internal static class BlobStoringTencentHttpClientFactoryServiceCollectionExtensions |
||||
|
{ |
||||
|
public static IServiceCollection AddTenantOssClient(this IServiceCollection services) |
||||
|
{ |
||||
|
services.AddHttpClient(BlobStoringTencentConsts.HttpClient); |
||||
|
|
||||
|
return services; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
using LINGYUN.Abp.BlobStoring.Tencent; |
||||
|
|
||||
|
namespace System.Net.Http; |
||||
|
public static class BlobStoringTencentHttpClientFactoryExtenssions |
||||
|
{ |
||||
|
public static HttpClient CreateTenantOssClient( |
||||
|
this IHttpClientFactory httpClientFactory) |
||||
|
{ |
||||
|
return httpClientFactory.CreateClient(BlobStoringTencentConsts.HttpClient); ; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,156 @@ |
|||||
|
using DotNetCore.CAP; |
||||
|
using DotNetCore.CAP.Internal; |
||||
|
using DotNetCore.CAP.Persistence; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.EventBus.CAP; |
||||
|
|
||||
|
public class AbpCAPBootstrapper : IBootstrapper |
||||
|
{ |
||||
|
private readonly ILogger<IBootstrapper> _logger; |
||||
|
private readonly IServiceProvider _serviceProvider; |
||||
|
|
||||
|
private CancellationTokenSource _cts; |
||||
|
private bool _disposed; |
||||
|
private IEnumerable<IProcessingServer> _processors = default!; |
||||
|
|
||||
|
public bool IsStarted => !_cts?.IsCancellationRequested ?? false; |
||||
|
|
||||
|
public AbpCAPBootstrapper(IServiceProvider serviceProvider, ILogger<IBootstrapper> logger) |
||||
|
{ |
||||
|
_serviceProvider = serviceProvider; |
||||
|
_logger = logger; |
||||
|
} |
||||
|
|
||||
|
public async Task BootstrapAsync(CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
if (_cts != null) |
||||
|
{ |
||||
|
_logger.LogInformation("### CAP background task is already started!"); |
||||
|
|
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
_logger.LogDebug("### CAP background task is starting."); |
||||
|
|
||||
|
_cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); |
||||
|
|
||||
|
CheckRequirement(); |
||||
|
|
||||
|
_processors = _serviceProvider.GetServices<IProcessingServer>(); |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
await _serviceProvider.GetRequiredService<IStorageInitializer>().InitializeAsync(_cts.Token).ConfigureAwait(false); |
||||
|
} |
||||
|
catch (Exception e) |
||||
|
{ |
||||
|
if (e is InvalidOperationException) throw; |
||||
|
_logger.LogError(e, "Initializing the storage structure failed!"); |
||||
|
} |
||||
|
|
||||
|
_cts.Token.Register(() => |
||||
|
{ |
||||
|
_logger.LogDebug("### CAP background task is stopping."); |
||||
|
|
||||
|
|
||||
|
foreach (var item in _processors) |
||||
|
try |
||||
|
{ |
||||
|
item.Dispose(); |
||||
|
} |
||||
|
catch (OperationCanceledException ex) |
||||
|
{ |
||||
|
_logger.LogWarning(ex, $"Expected an OperationCanceledException, but found '{ex.Message}'."); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
await BootstrapCoreAsync().ConfigureAwait(false); |
||||
|
|
||||
|
_disposed = false; |
||||
|
_logger.LogInformation("### CAP started!"); |
||||
|
} |
||||
|
|
||||
|
protected virtual async Task BootstrapCoreAsync() |
||||
|
{ |
||||
|
foreach (var item in _processors) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
_cts!.Token.ThrowIfCancellationRequested(); |
||||
|
|
||||
|
await item.Start(_cts!.Token); |
||||
|
} |
||||
|
catch (OperationCanceledException) |
||||
|
{ |
||||
|
// ignore
|
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
_logger.LogError(ex, "Starting the processors throw an exception."); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public virtual void Dispose() |
||||
|
{ |
||||
|
if (_disposed) return; |
||||
|
|
||||
|
_cts?.Cancel(); |
||||
|
_cts?.Dispose(); |
||||
|
_cts = null; |
||||
|
_disposed = true; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task ExecuteAsync(CancellationToken stoppingToken) |
||||
|
{ |
||||
|
await BootstrapAsync(stoppingToken).ConfigureAwait(false); |
||||
|
} |
||||
|
|
||||
|
public virtual Task StopAsync(CancellationToken cancellationToken) |
||||
|
{ |
||||
|
_cts?.Cancel(); |
||||
|
|
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
|
||||
|
private void CheckRequirement() |
||||
|
{ |
||||
|
var marker = _serviceProvider.GetService<CapMarkerService>(); |
||||
|
if (marker == null) |
||||
|
throw new InvalidOperationException( |
||||
|
"AddCap() must be added on the service collection. eg: services.AddCap(...)"); |
||||
|
|
||||
|
var messageQueueMarker = _serviceProvider.GetService<CapMessageQueueMakerService>(); |
||||
|
if (messageQueueMarker == null) |
||||
|
throw new InvalidOperationException( |
||||
|
"You must be config transport provider for CAP!" + Environment.NewLine + |
||||
|
"==================================================================================" + |
||||
|
Environment.NewLine + |
||||
|
"======== eg: services.AddCap( options => { options.UseRabbitMQ(...) }); ========" + |
||||
|
Environment.NewLine + |
||||
|
"=================================================================================="); |
||||
|
|
||||
|
var databaseMarker = _serviceProvider.GetService<CapStorageMarkerService>(); |
||||
|
if (databaseMarker == null) |
||||
|
throw new InvalidOperationException( |
||||
|
"You must be config storage provider for CAP!" + Environment.NewLine + |
||||
|
"===================================================================================" + |
||||
|
Environment.NewLine + |
||||
|
"======== eg: services.AddCap( options => { options.UseSqlServer(...) }); ========" + |
||||
|
Environment.NewLine + |
||||
|
"==================================================================================="); |
||||
|
} |
||||
|
|
||||
|
public ValueTask DisposeAsync() |
||||
|
{ |
||||
|
Dispose(); |
||||
|
|
||||
|
return ValueTask.CompletedTask; |
||||
|
} |
||||
|
} |
||||
@ -1,397 +0,0 @@ |
|||||
<?xml version="1.0"?> |
|
||||
<doc> |
|
||||
<assembly> |
|
||||
<name>LINGYUN.Abp.EventBus.CAP</name> |
|
||||
</assembly> |
|
||||
<members> |
|
||||
<member name="T:LINGYUN.Abp.EventBus.CAP.AbpCAPConsumerServiceSelector"> |
|
||||
<summary> |
|
||||
消费者查找器 |
|
||||
</summary> |
|
||||
</member> |
|
||||
<member name="P:LINGYUN.Abp.EventBus.CAP.AbpCAPConsumerServiceSelector.CapOptions"> |
|
||||
<summary> |
|
||||
CAP配置 |
|
||||
</summary> |
|
||||
</member> |
|
||||
<member name="P:LINGYUN.Abp.EventBus.CAP.AbpCAPConsumerServiceSelector.AbpDistributedEventBusOptions"> |
|
||||
<summary> |
|
||||
Abp分布式事件配置 |
|
||||
</summary> |
|
||||
</member> |
|
||||
<member name="P:LINGYUN.Abp.EventBus.CAP.AbpCAPConsumerServiceSelector.ServiceProvider"> |
|
||||
<summary> |
|
||||
服务提供者 |
|
||||
</summary> |
|
||||
</member> |
|
||||
<member name="M:LINGYUN.Abp.EventBus.CAP.AbpCAPConsumerServiceSelector.#ctor(System.IServiceProvider,Microsoft.Extensions.Options.IOptions{DotNetCore.CAP.CapOptions},Microsoft.Extensions.Options.IOptions{Volo.Abp.EventBus.Distributed.AbpDistributedEventBusOptions})"> |
|
||||
<summary> |
|
||||
Creates a new <see cref="T:DotNetCore.CAP.Internal.ConsumerServiceSelector" />. |
|
||||
</summary> |
|
||||
</member> |
|
||||
<member name="M:LINGYUN.Abp.EventBus.CAP.AbpCAPConsumerServiceSelector.FindConsumersFromInterfaceTypes(System.IServiceProvider)"> |
|
||||
<summary> |
|
||||
查找消费者集合 |
|
||||
</summary> |
|
||||
<param name="provider"></param> |
|
||||
<returns></returns> |
|
||||
</member> |
|
||||
<member name="M:LINGYUN.Abp.EventBus.CAP.AbpCAPConsumerServiceSelector.GetHandlerDescription(System.Type,System.Type)"> |
|
||||
<summary> |
|
||||
获取事件处理器集合 |
|
||||
</summary> |
|
||||
<param name="eventType"></param> |
|
||||
<param name="typeInfo"></param> |
|
||||
<returns></returns> |
|
||||
</member> |
|
||||
<member name="T:LINGYUN.Abp.EventBus.CAP.AbpCAPEventBusModule"> |
|
||||
<summary> |
|
||||
AbpCAPEventBusModule |
|
||||
</summary> |
|
||||
</member> |
|
||||
<member name="M:LINGYUN.Abp.EventBus.CAP.AbpCAPEventBusModule.ConfigureServices(Volo.Abp.Modularity.ServiceConfigurationContext)"> |
|
||||
<summary> |
|
||||
ConfigureServices |
|
||||
</summary> |
|
||||
<param name="context"></param> |
|
||||
</member> |
|
||||
<member name="T:LINGYUN.Abp.EventBus.CAP.AbpCAPEventBusOptions"> |
|
||||
<summary> |
|
||||
过期消息清理配置项 |
|
||||
</summary> |
|
||||
</member> |
|
||||
<member name="P:LINGYUN.Abp.EventBus.CAP.AbpCAPEventBusOptions.NotifyFailedCallback"> |
|
||||
<summary> |
|
||||
发布消息处理失败通知 |
|
||||
default: false |
|
||||
</summary> |
|
||||
</member> |
|
||||
<member name="T:LINGYUN.Abp.EventBus.CAP.AbpCAPExecutionFailedException"> |
|
||||
<summary> |
|
||||
AbpECAPExecutionFailedException |
|
||||
</summary> |
|
||||
</member> |
|
||||
<member name="P:LINGYUN.Abp.EventBus.CAP.AbpCAPExecutionFailedException.MessageType"> |
|
||||
<summary> |
|
||||
MessageType |
|
||||
</summary> |
|
||||
</member> |
|
||||
<member name="P:LINGYUN.Abp.EventBus.CAP.AbpCAPExecutionFailedException.Origin"> |
|
||||
<summary> |
|
||||
Message |
|
||||
</summary> |
|
||||
</member> |
|
||||
<member name="M:LINGYUN.Abp.EventBus.CAP.AbpCAPExecutionFailedException.#ctor(DotNetCore.CAP.Messages.MessageType,DotNetCore.CAP.Messages.Message)"> |
|
||||
<summary> |
|
||||
constructor |
|
||||
</summary> |
|
||||
<param name="messageType"></param> |
|
||||
<param name="origin"></param> |
|
||||
</member> |
|
||||
<member name="M:LINGYUN.Abp.EventBus.CAP.AbpCAPExecutionFailedException.#ctor(DotNetCore.CAP.Messages.MessageType,DotNetCore.CAP.Messages.Message,System.String)"> |
|
||||
<summary> |
|
||||
constructor |
|
||||
</summary> |
|
||||
<param name="messageType"></param> |
|
||||
<param name="origin"></param> |
|
||||
<param name="message"></param> |
|
||||
</member> |
|
||||
<member name="M:LINGYUN.Abp.EventBus.CAP.AbpCAPExecutionFailedException.#ctor(DotNetCore.CAP.Messages.MessageType,DotNetCore.CAP.Messages.Message,System.String,System.Exception)"> |
|
||||
<summary> |
|
||||
constructor |
|
||||
</summary> |
|
||||
<param name="messageType"></param> |
|
||||
<param name="origin"></param> |
|
||||
<param name="message"></param> |
|
||||
<param name="innerException"></param> |
|
||||
</member> |
|
||||
<member name="T:LINGYUN.Abp.EventBus.CAP.AbpCAPMessageExtensions"> |
|
||||
<summary> |
|
||||
CAP消息扩展 |
|
||||
</summary> |
|
||||
</member> |
|
||||
<member name="M:LINGYUN.Abp.EventBus.CAP.AbpCAPMessageExtensions.TryGetTenantId(DotNetCore.CAP.Messages.Message,System.Nullable{System.Guid}@)"> |
|
||||
<summary> |
|
||||
尝试获取消息标头中的租户标识 |
|
||||
</summary> |
|
||||
<param name="message"></param> |
|
||||
<param name="tenantId"></param> |
|
||||
<returns></returns> |
|
||||
</member> |
|
||||
<member name="M:LINGYUN.Abp.EventBus.CAP.AbpCAPMessageExtensions.GetTenantIdOrNull(DotNetCore.CAP.Messages.Message)"> |
|
||||
<summary> |
|
||||
获取消息标头中的租户标识 |
|
||||
</summary> |
|
||||
<param name="message"></param> |
|
||||
<returns></returns> |
|
||||
</member> |
|
||||
<member name="M:LINGYUN.Abp.EventBus.CAP.AbpCAPMessageExtensions.TryGetCorrelationId(DotNetCore.CAP.Messages.Message,System.String@)"> |
|
||||
<summary> |
|
||||
尝试获取消息标头中的链路标识 |
|
||||
</summary> |
|
||||
<param name="message"></param> |
|
||||
<param name="correlationId"></param> |
|
||||
<returns></returns> |
|
||||
</member> |
|
||||
<member name="M:LINGYUN.Abp.EventBus.CAP.AbpCAPMessageExtensions.GetCorrelationIdOrNull(DotNetCore.CAP.Messages.Message)"> |
|
||||
<summary> |
|
||||
获取消息标头中的链路标识 |
|
||||
</summary> |
|
||||
<param name="message"></param> |
|
||||
<returns></returns> |
|
||||
</member> |
|
||||
<member name="T:LINGYUN.Abp.EventBus.CAP.AbpCAPSubscribeInvoker"> |
|
||||
<summary> |
|
||||
重写 ISubscribeInvoker 实现 Abp 租户集成 |
|
||||
</summary> |
|
||||
</member> |
|
||||
<member name="M:LINGYUN.Abp.EventBus.CAP.AbpCAPSubscribeInvoker.#ctor(Microsoft.Extensions.Logging.ILoggerFactory,System.IServiceProvider,Volo.Abp.Tracing.ICorrelationIdProvider,DotNetCore.CAP.Serialization.ISerializer,Volo.Abp.MultiTenancy.ICurrentTenant)"> |
|
||||
<summary> |
|
||||
AbpCAPSubscribeInvoker |
|
||||
</summary> |
|
||||
<param name="loggerFactory"></param> |
|
||||
<param name="serviceProvider"></param> |
|
||||
<param name="correlationIdProvider"></param> |
|
||||
<param name="serializer"></param> |
|
||||
<param name="currentTenant"></param> |
|
||||
</member> |
|
||||
<member name="M:LINGYUN.Abp.EventBus.CAP.AbpCAPSubscribeInvoker.InvokeAsync(DotNetCore.CAP.Internal.ConsumerContext,System.Threading.CancellationToken)"> |
|
||||
<summary> |
|
||||
调用订阅者方法 |
|
||||
</summary> |
|
||||
<param name="context"></param> |
|
||||
<param name="cancellationToken"></param> |
|
||||
<returns></returns> |
|
||||
</member> |
|
||||
<member name="M:LINGYUN.Abp.EventBus.CAP.AbpCAPSubscribeInvoker.GetCapProvidedParameter(DotNetCore.CAP.Internal.ParameterDescriptor,DotNetCore.CAP.Messages.Message,System.Threading.CancellationToken)"> |
|
||||
<summary> |
|
||||
|
|
||||
</summary> |
|
||||
<param name="parameterDescriptor"></param> |
|
||||
<param name="message"></param> |
|
||||
<param name="cancellationToken"></param> |
|
||||
<returns></returns> |
|
||||
<exception cref="T:System.ArgumentException"></exception> |
|
||||
</member> |
|
||||
<member name="M:LINGYUN.Abp.EventBus.CAP.AbpCAPSubscribeInvoker.GetInstance(System.IServiceProvider,DotNetCore.CAP.Internal.ConsumerContext)"> |
|
||||
<summary> |
|
||||
获取事件处理类实例 |
|
||||
</summary> |
|
||||
<param name="provider"></param> |
|
||||
<param name="context"></param> |
|
||||
<returns></returns> |
|
||||
</member> |
|
||||
<member name="M:LINGYUN.Abp.EventBus.CAP.AbpCAPSubscribeInvoker.ExecuteWithParameterAsync(LINGYUN.Abp.EventBus.CAP.Internal.ObjectMethodExecutor,System.Object,System.Object[])"> |
|
||||
<summary> |
|
||||
通过给定的类型实例与参数调用订阅者方法 |
|
||||
</summary> |
|
||||
<param name="executor"></param> |
|
||||
<param name="class"></param> |
|
||||
<param name="parameter"></param> |
|
||||
<returns></returns> |
|
||||
</member> |
|
||||
<member name="T:LINGYUN.Abp.EventBus.CAP.CAPDistributedEventBus"> |
|
||||
<summary> |
|
||||
CAP分布式事件总线 |
|
||||
</summary> |
|
||||
</member> |
|
||||
<member name="P:LINGYUN.Abp.EventBus.CAP.CAPDistributedEventBus.CapPublisher"> |
|
||||
<summary> |
|
||||
CAP消息发布接口 |
|
||||
</summary> |
|
||||
</member> |
|
||||
<member name="P:LINGYUN.Abp.EventBus.CAP.CAPDistributedEventBus.CustomDistributedEventSubscriber"> |
|
||||
<summary> |
|
||||
自定义事件注册接口 |
|
||||
</summary> |
|
||||
</member> |
|
||||
<member name="P:LINGYUN.Abp.EventBus.CAP.CAPDistributedEventBus.HandlerFactories"> |
|
||||
<summary> |
|
||||
本地事件处理器工厂对象集合 |
|
||||
</summary> |
|
||||
</member> |
|
||||
<member name="P:LINGYUN.Abp.EventBus.CAP.CAPDistributedEventBus.EventTypes"> |
|
||||
<summary> |
|
||||
本地事件集合 |
|
||||
</summary> |
|
||||
</member> |
|
||||
<member name="P:LINGYUN.Abp.EventBus.CAP.CAPDistributedEventBus.CurrentUser"> |
|
||||
<summary> |
|
||||
当前用户 |
|
||||
</summary> |
|
||||
</member> |
|
||||
<member name="P:LINGYUN.Abp.EventBus.CAP.CAPDistributedEventBus.CurrentClient"> |
|
||||
<summary> |
|
||||
当前客户端 |
|
||||
</summary> |
|
||||
</member> |
|
||||
<member name="P:LINGYUN.Abp.EventBus.CAP.CAPDistributedEventBus.JsonSerializer"> |
|
||||
<summary> |
|
||||
typeof <see cref="T:Volo.Abp.Json.IJsonSerializer"/> |
|
||||
</summary> |
|
||||
</member> |
|
||||
<member name="P:LINGYUN.Abp.EventBus.CAP.CAPDistributedEventBus.CancellationTokenProvider"> |
|
||||
<summary> |
|
||||
取消令牌 |
|
||||
</summary> |
|
||||
</member> |
|
||||
<member name="M:LINGYUN.Abp.EventBus.CAP.CAPDistributedEventBus.#ctor(Microsoft.Extensions.DependencyInjection.IServiceScopeFactory,Microsoft.Extensions.Options.IOptions{Volo.Abp.EventBus.Distributed.AbpDistributedEventBusOptions},DotNetCore.CAP.ICapPublisher,Volo.Abp.Users.ICurrentUser,Volo.Abp.Clients.ICurrentClient,Volo.Abp.MultiTenancy.ICurrentTenant,Volo.Abp.Json.IJsonSerializer,Volo.Abp.Uow.IUnitOfWorkManager,Volo.Abp.Guids.IGuidGenerator,Volo.Abp.Timing.IClock,Volo.Abp.Threading.ICancellationTokenProvider,LINGYUN.Abp.EventBus.CAP.ICustomDistributedEventSubscriber,Volo.Abp.EventBus.IEventHandlerInvoker,Volo.Abp.EventBus.Local.ILocalEventBus,Volo.Abp.Tracing.ICorrelationIdProvider)"> |
|
||||
<summary> |
|
||||
constructor |
|
||||
</summary> |
|
||||
<param name="serviceScopeFactory"></param> |
|
||||
<param name="distributedEventBusOptions"></param> |
|
||||
<param name="capPublisher"></param> |
|
||||
<param name="currentUser"></param> |
|
||||
<param name="currentClient"></param> |
|
||||
<param name="currentTenant"></param> |
|
||||
<param name="jsonSerializer"></param> |
|
||||
<param name="unitOfWorkManager"></param> |
|
||||
<param name="cancellationTokenProvider"></param> |
|
||||
<param name="guidGenerator"></param> |
|
||||
<param name="clock"></param> |
|
||||
<param name="customDistributedEventSubscriber"></param> |
|
||||
<param name="eventHandlerInvoker"></param> |
|
||||
<param name="localEventBus"></param> |
|
||||
<param name="correlationIdProvider"></param> |
|
||||
</member> |
|
||||
<member name="M:LINGYUN.Abp.EventBus.CAP.CAPDistributedEventBus.Subscribe(System.Type,Volo.Abp.EventBus.IEventHandlerFactory)"> |
|
||||
<summary> |
|
||||
订阅事件 |
|
||||
</summary> |
|
||||
<param name="eventType"></param> |
|
||||
<param name="factory"></param> |
|
||||
<returns></returns> |
|
||||
</member> |
|
||||
<member name="M:LINGYUN.Abp.EventBus.CAP.CAPDistributedEventBus.Unsubscribe``1(System.Func{``0,System.Threading.Tasks.Task})"> |
|
||||
<summary> |
|
||||
退订事件 |
|
||||
</summary> |
|
||||
<typeparam name="TEvent">事件类型</typeparam> |
|
||||
<param name="action"></param> |
|
||||
</member> |
|
||||
<member name="M:LINGYUN.Abp.EventBus.CAP.CAPDistributedEventBus.Unsubscribe(System.Type,Volo.Abp.EventBus.IEventHandler)"> |
|
||||
<summary> |
|
||||
退订事件 |
|
||||
</summary> |
|
||||
<param name="eventType">事件类型</param> |
|
||||
<param name="handler">事件处理器</param> |
|
||||
</member> |
|
||||
<member name="M:LINGYUN.Abp.EventBus.CAP.CAPDistributedEventBus.Unsubscribe(System.Type,Volo.Abp.EventBus.IEventHandlerFactory)"> |
|
||||
<summary> |
|
||||
退订事件 |
|
||||
</summary> |
|
||||
<param name="eventType">事件类型</param> |
|
||||
<param name="factory">事件处理器工厂</param> |
|
||||
</member> |
|
||||
<member name="M:LINGYUN.Abp.EventBus.CAP.CAPDistributedEventBus.UnsubscribeAll(System.Type)"> |
|
||||
<summary> |
|
||||
退订所有事件 |
|
||||
</summary> |
|
||||
<param name="eventType">事件类型</param> |
|
||||
</member> |
|
||||
<member name="M:LINGYUN.Abp.EventBus.CAP.CAPDistributedEventBus.PublishToEventBusAsync(System.Type,System.Object)"> |
|
||||
<summary> |
|
||||
发布事件 |
|
||||
</summary> |
|
||||
<param name="eventType">事件类型</param> |
|
||||
<param name="eventData">事件数据对象</param> |
|
||||
<returns></returns> |
|
||||
</member> |
|
||||
<member name="M:LINGYUN.Abp.EventBus.CAP.CAPDistributedEventBus.GetHandlerFactories(System.Type)"> |
|
||||
<summary> |
|
||||
获取事件处理器工厂列表 |
|
||||
</summary> |
|
||||
<param name="eventType"></param> |
|
||||
<returns></returns> |
|
||||
</member> |
|
||||
<member name="T:LINGYUN.Abp.EventBus.CAP.ICustomDistributedEventSubscriber"> |
|
||||
<summary> |
|
||||
自定义事件订阅者 |
|
||||
</summary> |
|
||||
</member> |
|
||||
<member name="M:LINGYUN.Abp.EventBus.CAP.ICustomDistributedEventSubscriber.Subscribe(System.Type,Volo.Abp.EventBus.IEventHandlerFactory)"> |
|
||||
<summary> |
|
||||
订阅事件 |
|
||||
</summary> |
|
||||
<param name="eventType"></param> |
|
||||
<param name="factory"></param> |
|
||||
</member> |
|
||||
<member name="M:LINGYUN.Abp.EventBus.CAP.ICustomDistributedEventSubscriber.UnSubscribe(System.Type,Volo.Abp.EventBus.IEventHandlerFactory)"> |
|
||||
<summary> |
|
||||
取消订阅 |
|
||||
</summary> |
|
||||
<param name="eventType"></param> |
|
||||
<param name="factory"></param> |
|
||||
</member> |
|
||||
<member name="M:LINGYUN.Abp.EventBus.CAP.Internal.ObjectMethodExecutor.Execute(System.Object,System.Object[])"> |
|
||||
<summary> |
|
||||
Executes the configured method on <paramref name="target" />. This can be used whether or not |
|
||||
the configured method is asynchronous. |
|
||||
</summary> |
|
||||
<remarks> |
|
||||
Even if the target method is asynchronous, it's desirable to invoke it using Execute rather than |
|
||||
ExecuteAsync if you know at compile time what the return type is, because then you can directly |
|
||||
"await" that value (via a cast), and then the generated code will be able to reference the |
|
||||
resulting awaitable as a value-typed variable. If you use ExecuteAsync instead, the generated |
|
||||
code will have to treat the resulting awaitable as a boxed object, because it doesn't know at |
|
||||
compile time what type it would be. |
|
||||
</remarks> |
|
||||
<param name="target">The object whose method is to be executed.</param> |
|
||||
<param name="parameters">Parameters to pass to the method.</param> |
|
||||
<returns>The method return value.</returns> |
|
||||
</member> |
|
||||
<member name="M:LINGYUN.Abp.EventBus.CAP.Internal.ObjectMethodExecutor.ExecuteAsync(System.Object,System.Object[])"> |
|
||||
<summary> |
|
||||
Executes the configured method on <paramref name="target" />. This can only be used if the configured |
|
||||
method is asynchronous. |
|
||||
</summary> |
|
||||
<remarks> |
|
||||
If you don't know at compile time the type of the method's returned awaitable, you can use ExecuteAsync, |
|
||||
which supplies an awaitable-of-object. This always works, but can incur several extra heap allocations |
|
||||
as compared with using Execute and then using "await" on the result value typecasted to the known |
|
||||
awaitable type. The possible extra heap allocations are for: |
|
||||
1. The custom awaitable (though usually there's a heap allocation for this anyway, since normally |
|
||||
it's a reference type, and you normally create a new instance per call). |
|
||||
2. The custom awaiter (whether or not it's a value type, since if it's not, you need a new instance |
|
||||
of it, and if it is, it will have to be boxed so the calling code can reference it as an object). |
|
||||
3. The async result value, if it's a value type (it has to be boxed as an object, since the calling |
|
||||
code doesn't know what type it's going to be). |
|
||||
</remarks> |
|
||||
<param name="target">The object whose method is to be executed.</param> |
|
||||
<param name="parameters">Parameters to pass to the method.</param> |
|
||||
<returns>An object that you can "await" to get the method return value.</returns> |
|
||||
</member> |
|
||||
<member name="T:LINGYUN.Abp.EventBus.CAP.Internal.ObjectMethodExecutorAwaitable"> |
|
||||
<summary> |
|
||||
Provides a common awaitable structure that <see cref="M:LINGYUN.Abp.EventBus.CAP.Internal.ObjectMethodExecutor.ExecuteAsync(System.Object,System.Object[])" /> can |
|
||||
return, regardless of whether the underlying value is a System.Task, an FSharpAsync, or an |
|
||||
application-defined custom awaitable. |
|
||||
</summary> |
|
||||
</member> |
|
||||
<member name="T:LINGYUN.Abp.EventBus.CAP.Internal.ObjectMethodExecutorFSharpSupport"> |
|
||||
<summary> |
|
||||
Helper for detecting whether a given type is FSharpAsync`1, and if so, supplying |
|
||||
an <see cref="T:System.Linq.Expressions.Expression" /> for mapping instances of that type to a C# awaitable. |
|
||||
</summary> |
|
||||
<remarks> |
|
||||
The main design goal here is to avoid taking a compile-time dependency on |
|
||||
FSharp.Core.dll, because non-F# applications wouldn't use it. So all the references |
|
||||
to FSharp types have to be constructed dynamically at runtime. |
|
||||
</remarks> |
|
||||
</member> |
|
||||
<member name="T:Microsoft.Extensions.DependencyInjection.ServiceCollectionExtensions"> |
|
||||
<summary> |
|
||||
CAP ServiceCollectionExtensions |
|
||||
</summary> |
|
||||
</member> |
|
||||
<member name="M:Microsoft.Extensions.DependencyInjection.ServiceCollectionExtensions.AddCAPEventBus(Microsoft.Extensions.DependencyInjection.IServiceCollection,System.Action{DotNetCore.CAP.CapOptions})"> |
|
||||
<summary> |
|
||||
Adds and configures the consistence services for the consistency. |
|
||||
</summary> |
|
||||
<param name="services"></param> |
|
||||
<param name="capAction"></param> |
|
||||
<returns></returns> |
|
||||
</member> |
|
||||
</members> |
|
||||
</doc> |
|
||||
@ -0,0 +1,40 @@ |
|||||
|
namespace LINGYUN.Abp.Sms.Aliyun; |
||||
|
public class AliyunSmsVerifyCodeResponse |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 请求状态码, OK代表请求成功
|
||||
|
/// </summary>
|
||||
|
public string Code { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 状态码的描述
|
||||
|
/// </summary>
|
||||
|
public string Message { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 请求是否成功
|
||||
|
/// </summary>
|
||||
|
public bool Success { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 请求结果数据
|
||||
|
/// </summary>
|
||||
|
public AliyunSmsVerifyCodeModel Model { get; set; } |
||||
|
} |
||||
|
|
||||
|
public class AliyunSmsVerifyCodeModel |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 请求Id
|
||||
|
/// </summary>
|
||||
|
public string RequestId { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 业务Id
|
||||
|
/// </summary>
|
||||
|
public string BizId { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 外部流水号
|
||||
|
/// </summary>
|
||||
|
public string OutId { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 验证码, 仅当使用阿里云短信验证服务生成验证码时携带
|
||||
|
/// </summary>
|
||||
|
public string VerifyCode { get; set; } |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Sms.Aliyun; |
||||
|
/// <summary>
|
||||
|
/// 阿里云发送短信验证码接口
|
||||
|
/// </summary>
|
||||
|
public interface IAliyunSmsVerifyCodeSender |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 发送短信验证码
|
||||
|
/// </summary>
|
||||
|
/// <param name="message"></param>
|
||||
|
/// <returns></returns>
|
||||
|
Task SendAsync(SmsVerifyCodeMessage message); |
||||
|
} |
||||
@ -0,0 +1,88 @@ |
|||||
|
namespace LINGYUN.Abp.Sms.Aliyun; |
||||
|
public class SmsVerifyCodeMessage |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 方案名称,如果不填则为“默认方案”。最多不超过 20 个字符。
|
||||
|
/// </summary>
|
||||
|
public string SchemeName { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 号码国家编码。默认为 86,目前也仅支持中国国内号码发送。
|
||||
|
/// </summary>
|
||||
|
public string CountryCode { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 上行短信扩展码。上行短信指发送给通信服务提供商的短信,用于定制某种服务、完成查询,或是办理某种业务等,需要收费,按运营商普通短信资费进行扣费。
|
||||
|
/// </summary>
|
||||
|
public string SmsUpExtendCode { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 外部流水号。
|
||||
|
/// </summary>
|
||||
|
public string OutId { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 验证码长度支持 4~8 位长度,默认是 4 位。
|
||||
|
/// </summary>
|
||||
|
public long? CodeLength { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 验证码有效时长,单位秒,默认为 300 秒。
|
||||
|
/// </summary>
|
||||
|
public long? ValidTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 核验规则,当有效时间内对同场景内的同号码重复发送验证码时,旧验证码如何处理。
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// 1 - 覆盖处理(默认),即旧验证码会失效掉。<br />
|
||||
|
/// 2 - 保留,即多个验证码都是在有效期内都可以校验通过。
|
||||
|
/// </remarks>
|
||||
|
public long? DuplicatePolicy { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 时间间隔,单位:秒。即多久间隔可以发送一次验证码,用于频控,默认 60 秒。
|
||||
|
/// </summary>
|
||||
|
public long? Interval { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 生成的验证码类型。当参数 TemplateParam 传入占位符时,此参数必填,将由系统根据指定的规则生成验证码。
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// 1 - 纯数字(默认)。
|
||||
|
/// 2 - 纯大写字母。
|
||||
|
/// 3 - 纯小写字母。
|
||||
|
/// 4 - 大小字母混合。
|
||||
|
/// 5 - 数字+大写字母混合。
|
||||
|
/// 6 - 数字+小写字母混合。
|
||||
|
/// 7 - 数字+大小写字母混合。
|
||||
|
/// </remarks>
|
||||
|
public long? CodeType { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 是否返回验证码。
|
||||
|
/// </summary>
|
||||
|
public bool? ReturnVerifyCode { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 是否自动替换签名重试(默认开启。
|
||||
|
/// </summary>
|
||||
|
public bool? AutoRetry { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 短信接收方手机号。
|
||||
|
/// </summary>
|
||||
|
public string PhoneNumber { get; } |
||||
|
/// <summary>
|
||||
|
/// 签名名称。暂不支持使用自定义签名,请使用系统赠送的签名。
|
||||
|
/// </summary>
|
||||
|
public string SignName { get; } |
||||
|
/// <summary>
|
||||
|
/// 短信模板 CODE。参数SignName选择赠送签名时,必须搭配赠送模板下发短信。您可在赠送模板配置页面选择适用您业务场景的模板。
|
||||
|
/// </summary>
|
||||
|
public string TemplateCode { get; } |
||||
|
/// <summary>
|
||||
|
/// 短信模板参数。
|
||||
|
/// </summary>
|
||||
|
public SmsVerifyCodeMessageParam TemplateParam { get; } |
||||
|
public SmsVerifyCodeMessage( |
||||
|
string phoneNumber, |
||||
|
SmsVerifyCodeMessageParam templateParam, |
||||
|
string signName = null, |
||||
|
string templateCode = null) |
||||
|
{ |
||||
|
PhoneNumber = phoneNumber; |
||||
|
TemplateParam = templateParam; |
||||
|
SignName = signName; |
||||
|
TemplateCode = templateCode; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
namespace LINGYUN.Abp.Sms.Aliyun; |
||||
|
public class SmsVerifyCodeMessageParam |
||||
|
{ |
||||
|
public string Code { get; } |
||||
|
public string Min { get; } |
||||
|
public SmsVerifyCodeMessageParam(string code, string min = "5") |
||||
|
{ |
||||
|
Code = code; |
||||
|
Min = min; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,3 @@ |
|||||
|
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
||||
|
<ConfigureAwait ContinueOnCapturedContext="false" /> |
||||
|
</Weavers> |
||||
@ -0,0 +1,30 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
||||
|
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
||||
|
<xs:element name="Weavers"> |
||||
|
<xs:complexType> |
||||
|
<xs:all> |
||||
|
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
||||
|
<xs:complexType> |
||||
|
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:all> |
||||
|
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:schema> |
||||
@ -0,0 +1,21 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk.Web"> |
||||
|
|
||||
|
<Import Project="..\..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net9.0</TargetFramework> |
||||
|
<AssemblyName>LINGYUN.Abp.AspNetCore.MultiTenancy</AssemblyName> |
||||
|
<PackageId>LINGYUN.Abp.AspNetCore.MultiTenancy</PackageId> |
||||
|
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
||||
|
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
||||
|
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
||||
|
<OutputType>Library</OutputType> |
||||
|
<Nullable>enable</Nullable> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Volo.Abp.AspNetCore.MultiTenancy" /> |
||||
|
</ItemGroup> |
||||
|
</Project> |
||||
@ -0,0 +1,10 @@ |
|||||
|
using Volo.Abp.Modularity; |
||||
|
using VoloAbpAspNetCoreMultiTenancyModule = Volo.Abp.AspNetCore.MultiTenancy.AbpAspNetCoreMultiTenancyModule; |
||||
|
|
||||
|
namespace LINGYUN.Abp.AspNetCore.MultiTenancy; |
||||
|
|
||||
|
[DependsOn(typeof(VoloAbpAspNetCoreMultiTenancyModule))] |
||||
|
public class AbpAspNetCoreMultiTenancyModule : AbpModule |
||||
|
{ |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
namespace LINGYUN.Abp.AspNetCore.MultiTenancy; |
||||
|
|
||||
|
public class AbpAspNetCoreMultiTenancyResolveOptions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 仅解析域名中的租户, 默认: true
|
||||
|
/// </summary>
|
||||
|
public bool OnlyResolveDomain { get; set; } |
||||
|
public AbpAspNetCoreMultiTenancyResolveOptions() |
||||
|
{ |
||||
|
OnlyResolveDomain = true; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
|
||||
|
namespace LINGYUN.Abp.AspNetCore.MultiTenancy; |
||||
|
|
||||
|
public static class AbpMultiTenancyOptionsExtensions |
||||
|
{ |
||||
|
public static void AddOnlyDomainTenantResolver(this AbpTenantResolveOptions options, string domainFormat) |
||||
|
{ |
||||
|
options.TenantResolvers.InsertAfter( |
||||
|
r => r is CurrentUserTenantResolveContributor, |
||||
|
new OnlyDomainTenantResolveContributor(domainFormat) |
||||
|
); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,52 @@ |
|||||
|
using Microsoft.AspNetCore.Http; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using System; |
||||
|
using System.Net; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.AspNetCore.MultiTenancy; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
using Volo.Abp.Text.Formatting; |
||||
|
|
||||
|
namespace LINGYUN.Abp.AspNetCore.MultiTenancy; |
||||
|
|
||||
|
public class OnlyDomainTenantResolveContributor : HttpTenantResolveContributorBase |
||||
|
{ |
||||
|
public const string ContributorName = "Domain"; |
||||
|
|
||||
|
public override string Name => ContributorName; |
||||
|
|
||||
|
private static readonly string[] ProtocolPrefixes = { "http://", "https://" }; |
||||
|
|
||||
|
private readonly string _domainFormat; |
||||
|
|
||||
|
public OnlyDomainTenantResolveContributor(string domainFormat) |
||||
|
{ |
||||
|
_domainFormat = domainFormat.RemovePreFix(ProtocolPrefixes); |
||||
|
} |
||||
|
|
||||
|
protected override Task<string?> GetTenantIdOrNameFromHttpContextOrNullAsync(ITenantResolveContext context, HttpContext httpContext) |
||||
|
{ |
||||
|
if (!httpContext.Request.Host.HasValue) |
||||
|
{ |
||||
|
return Task.FromResult<string?>(null); |
||||
|
} |
||||
|
|
||||
|
var options = httpContext.RequestServices.GetRequiredService<IOptions<AbpAspNetCoreMultiTenancyResolveOptions>>(); |
||||
|
if (options.Value.OnlyResolveDomain) |
||||
|
{ |
||||
|
// 仅仅解析域名, 如果请求的是IP地址, 则不使用这个解析贡献者
|
||||
|
if (IPAddress.TryParse(httpContext.Request.Host.Host, out var _)) |
||||
|
{ |
||||
|
return Task.FromResult<string?>(null); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
var hostName = httpContext.Request.Host.Value.RemovePreFix(ProtocolPrefixes); |
||||
|
var extractResult = FormattedStringValueExtracter.Extract(hostName, _domainFormat, ignoreCase: true); |
||||
|
|
||||
|
context.Handled = true; |
||||
|
|
||||
|
return Task.FromResult(extractResult.IsMatch ? extractResult.Matches[0].Value : null); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
{ |
||||
|
"profiles": { |
||||
|
"LINGYUN.Abp.AspNetCore.MultiTenancy": { |
||||
|
"commandName": "Project", |
||||
|
"launchBrowser": true, |
||||
|
"environmentVariables": { |
||||
|
"ASPNETCORE_ENVIRONMENT": "Development" |
||||
|
}, |
||||
|
"applicationUrl": "https://localhost:61811;http://localhost:61812" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,36 @@ |
|||||
|
using LINGYUN.Abp.WeChat.Work.Authorize; |
||||
|
using LINGYUN.Abp.WeChat.Work.Security.Claims; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using System.Security.Claims; |
||||
|
using System.Security.Principal; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Security.Claims; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Identity.WeChat.Work; |
||||
|
public class AbpWeChatWorkClaimsPrincipalContributor : IAbpClaimsPrincipalContributor, ITransientDependency |
||||
|
{ |
||||
|
public async virtual Task ContributeAsync(AbpClaimsPrincipalContributorContext context) |
||||
|
{ |
||||
|
var claimsIdentity = context.ClaimsPrincipal.Identities.First(); |
||||
|
if (claimsIdentity.HasClaim(x => x.Type == AbpWeChatWorkClaimTypes.UserId)) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
var userId = claimsIdentity.FindUserId(); |
||||
|
if (userId.HasValue) |
||||
|
{ |
||||
|
var userClaimProvider = context.ServiceProvider.GetService<IWeChatWorkUserClaimProvider>(); |
||||
|
|
||||
|
var weChatWorkUserId = await userClaimProvider?.FindUserIdentifierAsync(userId.Value); |
||||
|
if (!weChatWorkUserId.IsNullOrWhiteSpace()) |
||||
|
{ |
||||
|
claimsIdentity.AddOrReplace(new Claim(AbpWeChatWorkClaimTypes.UserId, weChatWorkUserId)); |
||||
|
|
||||
|
context.ClaimsPrincipal.AddIdentityIfNotContains(claimsIdentity); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,12 +0,0 @@ |
|||||
using System.Collections.Generic; |
|
||||
|
|
||||
namespace LINGYUN.Abp.WeChat.Common.Messages; |
|
||||
public class AbpWeChatMessageResolveOptions |
|
||||
{ |
|
||||
public List<IMessageResolveContributor> MessageResolvers { get; } |
|
||||
|
|
||||
public AbpWeChatMessageResolveOptions() |
|
||||
{ |
|
||||
MessageResolvers = new List<IMessageResolveContributor>(); |
|
||||
} |
|
||||
} |
|
||||
@ -1,7 +0,0 @@ |
|||||
using System.Threading.Tasks; |
|
||||
|
|
||||
namespace LINGYUN.Abp.WeChat.Common.Messages; |
|
||||
public interface IMessageResolver |
|
||||
{ |
|
||||
Task<MessageResolveResult> ResolveMessageAsync(MessageResolveData messageData); |
|
||||
} |
|
||||
@ -0,0 +1,16 @@ |
|||||
|
using LINGYUN.Abp.WeChat.Common.Messages; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WeChat.Official.Messages; |
||||
|
/// <summary>
|
||||
|
/// 微信公众号消息解析器
|
||||
|
/// </summary>
|
||||
|
public interface IWeChatOfficialMessageResolver |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 解析微信公众号消息
|
||||
|
/// </summary>
|
||||
|
/// <param name="messageData">公众号消息</param>
|
||||
|
/// <returns></returns>
|
||||
|
Task<MessageResolveResult> ResolveAsync(MessageResolveData messageData); |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
using LINGYUN.Abp.WeChat.Common.Crypto; |
||||
|
using LINGYUN.Abp.WeChat.Common.Messages; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WeChat.Official.Messages; |
||||
|
public class WeChatOfficialMessageResolver : MessageResolverBase, IWeChatOfficialMessageResolver |
||||
|
{ |
||||
|
private readonly AbpWeChatOfficialMessageResolveOptions _options; |
||||
|
public WeChatOfficialMessageResolver( |
||||
|
IWeChatCryptoService cryptoService, |
||||
|
IServiceProvider serviceProvider, |
||||
|
IOptions<AbpWeChatOfficialMessageResolveOptions> options) |
||||
|
: base(cryptoService, serviceProvider) |
||||
|
{ |
||||
|
_options = options.Value; |
||||
|
} |
||||
|
|
||||
|
protected async override Task<MessageResolveResult> ResolveMessageAsync(MessageResolveContext context) |
||||
|
{ |
||||
|
var result = new MessageResolveResult(context.Origin); |
||||
|
|
||||
|
foreach (var messageResolver in _options.MessageResolvers) |
||||
|
{ |
||||
|
await messageResolver.ResolveAsync(context); |
||||
|
|
||||
|
result.AppliedResolvers.Add(messageResolver.Name); |
||||
|
|
||||
|
if (context.HasResolvedMessage()) |
||||
|
{ |
||||
|
result.Message = context.Message; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
using LINGYUN.Abp.WeChat.Common.Messages; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WeChat.Work.Common.Messages; |
||||
|
/// <summary>
|
||||
|
/// 企业微信消息解析器
|
||||
|
/// </summary>
|
||||
|
public interface IWeChatWorkMessageResolver |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 解析企业微信消息
|
||||
|
/// </summary>
|
||||
|
/// <param name="messageData">企业微信消息</param>
|
||||
|
/// <returns></returns>
|
||||
|
Task<MessageResolveResult> ResolveAsync(MessageResolveData messageData); |
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
using LINGYUN.Abp.WeChat.Common.Messages; |
||||
|
using System.Xml.Serialization; |
||||
|
using Volo.Abp.EventBus; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WeChat.Work.Common.Messages.Models; |
||||
|
/// <summary>
|
||||
|
/// 会议室预定事件
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// 当用户在企业微信预定会议室时,会触发该事件回调给会议室系统应用。
|
||||
|
/// </remarks>
|
||||
|
[EventName("book_meeting_room")] |
||||
|
public class BookMeetingRoomEvent : WeChatWorkEventMessage |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 会议室id
|
||||
|
/// </summary>
|
||||
|
[XmlElement("MeetingRoomId")] |
||||
|
public int MeetingRoomId { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 预定id,可根据该ID查询具体的会议预定情况
|
||||
|
/// </summary>
|
||||
|
[XmlElement("BookingId")] |
||||
|
public string BookingId { get; set; } |
||||
|
|
||||
|
public override WeChatMessageEto ToEto() |
||||
|
{ |
||||
|
return new WeChatWorkEventMessageEto<BookMeetingRoomEvent>(this); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
using LINGYUN.Abp.WeChat.Common.Messages; |
||||
|
using System.Xml.Serialization; |
||||
|
using Volo.Abp.EventBus; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WeChat.Work.Common.Messages.Models; |
||||
|
/// <summary>
|
||||
|
/// 会议室取消事件
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// 当用户在企业微信取消预定会议室时,会触发该事件回调给会议室系统应用;如果该会议室由自建应用预定,除了会议室系统应用外,也会回调给对应的自建应用。
|
||||
|
/// </remarks>
|
||||
|
[EventName("cancel_meeting_room")] |
||||
|
public class CancelMeetingRoomEvent : WeChatWorkEventMessage |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 会议室id
|
||||
|
/// </summary>
|
||||
|
[XmlElement("MeetingRoomId")] |
||||
|
public int MeetingRoomId { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 预定id,可根据该ID查询具体的会议预定情况
|
||||
|
/// </summary>
|
||||
|
[XmlElement("BookingId")] |
||||
|
public string BookingId { get; set; } |
||||
|
|
||||
|
public override WeChatMessageEto ToEto() |
||||
|
{ |
||||
|
return new WeChatWorkEventMessageEto<CancelMeetingRoomEvent>(this); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,272 @@ |
|||||
|
using LINGYUN.Abp.WeChat.Common.Messages; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Xml.Serialization; |
||||
|
using Volo.Abp.EventBus; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WeChat.Work.Common.Messages.Models; |
||||
|
/// <summary>
|
||||
|
/// 企业微信“审批应用”审批状态通知事件
|
||||
|
/// </summary>
|
||||
|
[EventName("sys_approval_change")] |
||||
|
public class SysApprovalStatusChangeEvent : WeChatWorkEventMessage |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 审批信息
|
||||
|
/// </summary>
|
||||
|
[XmlElement("ApprovalInfo")] |
||||
|
public SysApprovalInfo ApprovalInfo { get; set; } |
||||
|
|
||||
|
public override WeChatMessageEto ToEto() |
||||
|
{ |
||||
|
return new WeChatWorkEventMessageEto<SysApprovalStatusChangeEvent>(this); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public class SysApprovalInfo |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 审批编号(字符串类型)
|
||||
|
/// </summary>
|
||||
|
[XmlElement("SpNoStr")] |
||||
|
public string SpNoStr { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 审批申请类型名称(审批模板名称)
|
||||
|
/// </summary>
|
||||
|
[XmlElement("SpName")] |
||||
|
public string SpName { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 申请单状态:1-审批中;2-已通过;3-已驳回;4-已撤销;6-通过后撤销;7-已删除;10-已支付
|
||||
|
/// </summary>
|
||||
|
[XmlElement("SpStatus")] |
||||
|
public byte SpStatus { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 审批模板id。可在“获取审批申请详情”、“审批状态变化回调通知”中获得,也可在审批模板的模板编辑页面链接中获得。
|
||||
|
/// </summary>
|
||||
|
[XmlElement("TemplateId")] |
||||
|
public string TemplateId { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 审批申请提交时间,Unix时间戳
|
||||
|
/// </summary>
|
||||
|
[XmlElement("ApplyTime")] |
||||
|
public int ApplyTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 申请人信息
|
||||
|
/// </summary>
|
||||
|
[XmlElement("Applyer")] |
||||
|
public SysApprovalApplyer Applyer { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 审批流程信息,可能有多个审批节点。
|
||||
|
/// </summary>
|
||||
|
[XmlElement("SpRecord")] |
||||
|
public List<SysApprovalRecord> SpRecord { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 抄送信息,可能有多个抄送节点
|
||||
|
/// </summary>
|
||||
|
[XmlElement("Notifyer")] |
||||
|
public List<SysApprovalNotifyer> Notifyer { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 审批申请备注信息,可能有多个备注节点
|
||||
|
/// </summary>
|
||||
|
[XmlElement("Comments")] |
||||
|
public List<SysApprovalComment> Comments { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 审批流程列表
|
||||
|
/// </summary>
|
||||
|
[XmlElement("ProcessList")] |
||||
|
public List<SysApprovalProcess> ProcessList { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 审批申请状态变化类型:1-提单;2-同意;3-驳回;4-转审;5-催办;6-撤销;8-通过后撤销;10-添加备注;11-回退给指定审批人;12-添加审批人;13-加签并同意; 14-已办理; 15-已转交
|
||||
|
/// </summary>
|
||||
|
[XmlElement("StatuChangeEvent")] |
||||
|
public byte StatuChangeEvent { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 审批编号
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// 局校审批单不返回此字段,其他类型审批单会返回此字段,不推荐使用此字段
|
||||
|
/// </remarks>
|
||||
|
[XmlElement("SpNo")] |
||||
|
[Obsolete("局校审批单不返回此字段,其他类型审批单会返回此字段,不推荐使用此字段")] |
||||
|
public string SpNo { get; set; } |
||||
|
} |
||||
|
|
||||
|
public class SysApprovalApplyer |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 申请人userid
|
||||
|
/// </summary>
|
||||
|
[XmlElement("UserId")] |
||||
|
public string UserId { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 申请人所在部门pid
|
||||
|
/// </summary>
|
||||
|
[XmlElement("Party")] |
||||
|
public string Party { get; set; } |
||||
|
} |
||||
|
|
||||
|
public class SysApprovalRecord |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 审批节点状态:1-审批中;2-已同意;3-已驳回;4-已转审
|
||||
|
/// </summary>
|
||||
|
[XmlElement("SpStatus")] |
||||
|
public byte SpStatus { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 节点审批方式:1-或签;2-会签
|
||||
|
/// </summary>
|
||||
|
[XmlElement("ApproverAttr")] |
||||
|
public byte ApproverAttr { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 节点审批方式:1-或签;2-会签
|
||||
|
/// </summary>
|
||||
|
[XmlElement("Details")] |
||||
|
public List<SysApprovalRecordDetail> Details { get; set; } |
||||
|
} |
||||
|
|
||||
|
public class SysApprovalRecordDetail |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 分支审批人
|
||||
|
/// </summary>
|
||||
|
[XmlElement("Approver")] |
||||
|
public SysApprovalApplyer Approver { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 审批意见字段
|
||||
|
/// </summary>
|
||||
|
[XmlElement("Speech")] |
||||
|
public string Speech { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 分支审批人审批状态:1-审批中;2-已同意;3-已驳回;4-已转审
|
||||
|
/// </summary>
|
||||
|
[XmlElement("SpStatus")] |
||||
|
public byte SpStatus { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 节点分支审批人审批操作时间,0为尚未操作
|
||||
|
/// </summary>
|
||||
|
[XmlElement("SpTime")] |
||||
|
public int SpTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 节点分支审批人审批意见附件,赋值为media_id具体使用请参考:文档-获取临时素材
|
||||
|
/// </summary>
|
||||
|
[XmlElement("Attach")] |
||||
|
public string Attach { get; set; } |
||||
|
} |
||||
|
|
||||
|
public class SysApprovalNotifyer |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 节点抄送人userid
|
||||
|
/// </summary>
|
||||
|
[XmlElement("UserId")] |
||||
|
public string UserId { get; set; } |
||||
|
} |
||||
|
|
||||
|
public class SysApprovalComment |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 备注人信息
|
||||
|
/// </summary>
|
||||
|
[XmlElement("CommentUserInfo")] |
||||
|
public SysApprovalCommenter CommentUserInfo { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 备注提交时间
|
||||
|
/// </summary>
|
||||
|
[XmlElement("CommentTime")] |
||||
|
public int CommentTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 备注文本内容
|
||||
|
/// </summary>
|
||||
|
[XmlElement("CommentContent")] |
||||
|
public string CommentContent { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 备注id
|
||||
|
/// </summary>
|
||||
|
[XmlElement("CommentId")] |
||||
|
public string CommentId { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 备注意见附件,值是附件media_id
|
||||
|
/// </summary>
|
||||
|
[XmlElement("Attach")] |
||||
|
public string Attach { get; set; } |
||||
|
} |
||||
|
|
||||
|
public class SysApprovalCommenter |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 节点抄送人userid
|
||||
|
/// </summary>
|
||||
|
[XmlElement("UserId")] |
||||
|
public string UserId { get; set; } |
||||
|
} |
||||
|
|
||||
|
public class SysApprovalProcess |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 流程节点
|
||||
|
/// </summary>
|
||||
|
[XmlElement("NodeList")] |
||||
|
public List<SysApprovalProcessNode> NodeList { get; set; } |
||||
|
} |
||||
|
|
||||
|
public class SysApprovalProcessNode |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 节点类型 1 审批人 2 抄送人 3办理人
|
||||
|
/// </summary>
|
||||
|
[XmlElement("NodeType")] |
||||
|
public byte NodeType { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 节点状态 1-审批中;2-同意;3-驳回;4-转审;11-退回给指定审批人;12-加签;13-同意并加签;14-办理;15-转交
|
||||
|
/// </summary>
|
||||
|
[XmlElement("SpStatus")] |
||||
|
public byte SpStatus { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 多人办理方式 1-会签;2-或签 3-依次审批
|
||||
|
/// </summary>
|
||||
|
[XmlElement("ApvRel")] |
||||
|
public byte ApvRel { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 子节点列表
|
||||
|
/// </summary>
|
||||
|
[XmlElement("SubNodeList")] |
||||
|
public List<SysApprovalProcessSubNode> SubNodeList { get; set; } |
||||
|
} |
||||
|
|
||||
|
public class SysApprovalProcessSubNode |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 处理人信息
|
||||
|
/// </summary>
|
||||
|
[XmlElement("UserInfo")] |
||||
|
public SysApprovalProcesser UserInfo { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 审批/办理意见
|
||||
|
/// </summary>
|
||||
|
[XmlElement("Speech")] |
||||
|
public string Speech { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 子节点状态 1-审批中;2-同意;3-驳回;4-转审;11-退回给指定审批人;12-加签;13-同意并加签;14-办理;15-转交
|
||||
|
/// </summary>
|
||||
|
[XmlElement("SpYj")] |
||||
|
public byte SpYj { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 操作时间
|
||||
|
/// </summary>
|
||||
|
[XmlElement("Sptime")] |
||||
|
public int Sptime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 备注意见附件,值是附件media_id
|
||||
|
/// </summary>
|
||||
|
[XmlElement("MediaIds")] |
||||
|
public string MediaIds { get; set; } |
||||
|
} |
||||
|
|
||||
|
public class SysApprovalProcesser |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 处理人userid
|
||||
|
/// </summary>
|
||||
|
[XmlElement("UserId")] |
||||
|
public string UserId { get; set; } |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
using LINGYUN.Abp.WeChat.Common.Crypto; |
||||
|
using LINGYUN.Abp.WeChat.Common.Messages; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WeChat.Work.Common.Messages; |
||||
|
public class WeChatWorkMessageResolver : MessageResolverBase, IWeChatWorkMessageResolver |
||||
|
{ |
||||
|
private readonly AbpWeChatWorkMessageResolveOptions _options; |
||||
|
public WeChatWorkMessageResolver( |
||||
|
IWeChatCryptoService cryptoService, |
||||
|
IServiceProvider serviceProvider, |
||||
|
IOptions<AbpWeChatWorkMessageResolveOptions> options) |
||||
|
: base(cryptoService, serviceProvider) |
||||
|
{ |
||||
|
_options = options.Value; |
||||
|
} |
||||
|
|
||||
|
protected async override Task<MessageResolveResult> ResolveMessageAsync(MessageResolveContext context) |
||||
|
{ |
||||
|
var result = new MessageResolveResult(context.Origin); |
||||
|
|
||||
|
foreach (var messageResolver in _options.MessageResolvers) |
||||
|
{ |
||||
|
await messageResolver.ResolveAsync(context); |
||||
|
|
||||
|
result.AppliedResolvers.Add(messageResolver.Name); |
||||
|
|
||||
|
if (context.HasResolvedMessage()) |
||||
|
{ |
||||
|
result.Message = context.Message; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,3 @@ |
|||||
|
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
||||
|
<ConfigureAwait ContinueOnCapturedContext="false" /> |
||||
|
</Weavers> |
||||
@ -0,0 +1,30 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
||||
|
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
||||
|
<xs:element name="Weavers"> |
||||
|
<xs:complexType> |
||||
|
<xs:all> |
||||
|
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
||||
|
<xs:complexType> |
||||
|
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:all> |
||||
|
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:schema> |
||||
@ -0,0 +1,27 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFrameworks>netstandard2.0;netstandard2.1;net8.0;net9.0</TargetFrameworks> |
||||
|
<AssemblyName>LINGYUN.Abp.WeChat.Work.ExternalContact</AssemblyName> |
||||
|
<PackageId>LINGYUN.Abp.WeChat.Work.ExternalContact</PackageId> |
||||
|
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
||||
|
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
||||
|
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
||||
|
<GenerateDocumentationFile>True</GenerateDocumentationFile> |
||||
|
<Nullable>enable</Nullable> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<None Remove="LINGYUN\Abp\WeChat\Work\ExternalContact\Localization\Resources\*.json" /> |
||||
|
<EmbeddedResource Include="LINGYUN\Abp\WeChat\Work\ExternalContact\Localization\Resources\*.json" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.WeChat.Work\LINGYUN.Abp.WeChat.Work.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,78 @@ |
|||||
|
using LINGYUN.Abp.WeChat.Common; |
||||
|
using LINGYUN.Abp.WeChat.Common.Messages; |
||||
|
using LINGYUN.Abp.WeChat.Work.Common.Messages; |
||||
|
using LINGYUN.Abp.WeChat.Work.ExternalContact.Messages.Models; |
||||
|
using LINGYUN.Abp.WeChat.Work.Localization; |
||||
|
using Volo.Abp.Localization; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WeChat.Work.ExternalContact; |
||||
|
|
||||
|
[DependsOn(typeof(AbpWeChatWorkModule))] |
||||
|
public class AbpWeChatWorkExternalContactModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
Configure<AbpWeChatWorkMessageResolveOptions>(options => |
||||
|
{ |
||||
|
// 企业客户变更事件
|
||||
|
options.MapEvent("change_external_contact", context => |
||||
|
{ |
||||
|
var changeType = context.GetMessageData("ChangeType"); |
||||
|
return changeType switch |
||||
|
{ |
||||
|
"add_external_contact" => context.GetWeChatMessage<ExternalContactCreateEvent>(), |
||||
|
"edit_external_contact" => context.GetWeChatMessage<ExternalContactUpdateEvent>(), |
||||
|
"add_half_external_contact" => context.GetWeChatMessage<ExternalContactCreateHalfEvent>(), |
||||
|
"del_external_contact" => context.GetWeChatMessage<ExternalContactDeleteEvent>(), |
||||
|
"del_follow_user" => context.GetWeChatMessage<ExternalContactDeleteFollowUserEvent>(), |
||||
|
"transfer_fail" => context.GetWeChatMessage<ExternalContactTransferFailEvent>(), |
||||
|
_ => throw new AbpWeChatException($"Contact change event change_external_contact:{changeType} is not mounted!"), |
||||
|
}; |
||||
|
}); |
||||
|
// 客户群变更事件
|
||||
|
options.MapEvent("change_external_chat", context => |
||||
|
{ |
||||
|
var changeType = context.GetMessageData("ChangeType"); |
||||
|
switch (changeType) |
||||
|
{ |
||||
|
case "create": return context.GetWeChatMessage<ExternalChatCreateEvent>(); |
||||
|
case "update": |
||||
|
// 客户群变更事件
|
||||
|
var updateDetail = context.GetMessageData("UpdateDetail"); |
||||
|
return updateDetail switch |
||||
|
{ |
||||
|
"add_member" => context.GetWeChatMessage<ExternalChatAddMemberEvent>(), |
||||
|
"del_member" => context.GetWeChatMessage<ExternalChaDelMemberEvent>(), |
||||
|
"change_owner" => context.GetWeChatMessage<ExternalChatChangeOwnerEvent>(), |
||||
|
"change_name" => context.GetWeChatMessage<ExternalChatChangeNameEvent>(), |
||||
|
"change_notice" => context.GetWeChatMessage<ExternalChatChangeNoticeEvent>(), |
||||
|
_ => throw new AbpWeChatException($"Contact change event change_external_chat:{changeType}:{updateDetail} is not mounted!"), |
||||
|
}; |
||||
|
case "dismiss": return context.GetWeChatMessage<ExternalChatDismissEvent>(); |
||||
|
default: throw new AbpWeChatException($"Contact change event change_external_chat:{changeType} is not mounted!"); |
||||
|
} |
||||
|
}); |
||||
|
// 企业客户标签事件
|
||||
|
options.MapEvent("change_external_tag", context => |
||||
|
{ |
||||
|
var changeType = context.GetMessageData("ChangeType"); |
||||
|
return changeType switch |
||||
|
{ |
||||
|
"create" => context.GetWeChatMessage<ExternalTagCreateEvent>(), |
||||
|
"update" => context.GetWeChatMessage<ExternalTagUpdateEvent>(), |
||||
|
"delete" => context.GetWeChatMessage<ExternalTagDeleteEvent>(), |
||||
|
"shuffle" => context.GetWeChatMessage<ExternalTagShuffleEvent>(), |
||||
|
_ => throw new AbpWeChatException($"Contact change event change_external_tag:{changeType} is not mounted!"), |
||||
|
}; |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
Configure<AbpLocalizationOptions>(options => |
||||
|
{ |
||||
|
options.Resources |
||||
|
.Get<WeChatWorkResource>() |
||||
|
.AddVirtualJson("/LINGYUN/Abp/WeChat/Work/ExternalContact/Localization/Resources"); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue