Merge pull request '🍒 功能优化' (#412) from jasondom/Admin.NET.Pro:v2-1 into v2
Reviewed-on: https://code.adminnet.top/Admin.NET/Admin.NET.Pro/pulls/412
This commit is contained in:
commit
57db1b7c65
@ -36,12 +36,14 @@ public static class HttpRemotesExtension {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 设置Http远程服务选项
|
/// 携带接口描述相关属性
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static HttpRequestBuilder SetHttpOptions(this HttpRequestBuilder builder, HttpRemoteItem option, string apiDesc = "")
|
/// <param name="builder"></param>
|
||||||
|
/// <param name="httpName"></param>
|
||||||
|
/// <param name="desc"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static HttpRequestBuilder SetRemoteApiAttr(this HttpRequestBuilder builder, string httpName, string desc = "")
|
||||||
{
|
{
|
||||||
builder.SetHttpClientName(option.HttpName);
|
return HttpLoggingHandler.SetRemoteApiAttr(builder, httpName, desc);
|
||||||
builder.WithHeader(CommonConst.HttpRemoteApiDescHeaderName, apiDesc, replace:true);
|
|
||||||
return builder;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -38,6 +38,7 @@ public class HttpLoggingHandler : DelegatingHandler, ITransient
|
|||||||
if (!string.IsNullOrWhiteSpace(clientName)) enabledLog = _enabledLogMap.GetOrDefault(clientName);
|
if (!string.IsNullOrWhiteSpace(clientName)) enabledLog = _enabledLogMap.GetOrDefault(clientName);
|
||||||
if (!enabledLog) return await base.SendAsync(request, cancellationToken);
|
if (!enabledLog) return await base.SendAsync(request, cancellationToken);
|
||||||
|
|
||||||
|
var stopWatch = Stopwatch.StartNew();
|
||||||
var sysLogHttp = new SysLogHttp();
|
var sysLogHttp = new SysLogHttp();
|
||||||
sysLogHttp.HttpClientName = clientName;
|
sysLogHttp.HttpClientName = clientName;
|
||||||
|
|
||||||
@ -48,20 +49,17 @@ public class HttpLoggingHandler : DelegatingHandler, ITransient
|
|||||||
sysLogHttp.HttpMethod = request.Method.Method;
|
sysLogHttp.HttpMethod = request.Method.Method;
|
||||||
sysLogHttp.RequestUrl = request.RequestUri?.ToString();
|
sysLogHttp.RequestUrl = request.RequestUri?.ToString();
|
||||||
sysLogHttp.RequestHeaders = request.Headers.ToDictionary(u => u.Key, u => u.Value.Join(";")).ToJson();
|
sysLogHttp.RequestHeaders = request.Headers.ToDictionary(u => u.Key, u => u.Value.Join(";")).ToJson();
|
||||||
|
|
||||||
if (request.Content != null) sysLogHttp.RequestBody = await request.Content.ReadAsStringAsync(cancellationToken);
|
if (request.Content != null) sysLogHttp.RequestBody = await request.Content.ReadAsStringAsync(cancellationToken);
|
||||||
|
|
||||||
sysLogHttp.StartTime = DateTime.Now;
|
sysLogHttp.StartTime = DateTime.Now;
|
||||||
var stopWatch = Stopwatch.StartNew();
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var response = await base.SendAsync(request, cancellationToken);
|
var response = await base.SendAsync(request, cancellationToken);
|
||||||
stopWatch.Stop();
|
stopWatch.Stop();
|
||||||
sysLogHttp.EndTime = DateTime.Now;
|
sysLogHttp.EndTime = DateTime.Now;
|
||||||
|
sysLogHttp.StatusCode = response.StatusCode;
|
||||||
sysLogHttp.ResponseHeaders = response.Headers.ToDictionary(u => u.Key, u => u.Value.Join(";")).ToJson();
|
sysLogHttp.ResponseHeaders = response.Headers.ToDictionary(u => u.Key, u => u.Value.Join(";")).ToJson();
|
||||||
sysLogHttp.IsSuccessStatusCode = response.IsSuccessStatusCode ? YesNoEnum.Y : YesNoEnum.N;
|
sysLogHttp.IsSuccessStatusCode = response.IsSuccessStatusCode ? YesNoEnum.Y : YesNoEnum.N;
|
||||||
sysLogHttp.StatusCode = response.StatusCode;
|
|
||||||
|
|
||||||
sysLogHttp.ResponseBody = await response.Content.ReadAsStringAsync(cancellationToken);
|
sysLogHttp.ResponseBody = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
@ -79,4 +77,18 @@ public class HttpLoggingHandler : DelegatingHandler, ITransient
|
|||||||
await _eventPublisher.PublishAsync(nameof(AppEventSubscriber.CreateHttpLog), sysLogHttp, cancellationToken);
|
await _eventPublisher.PublishAsync(nameof(AppEventSubscriber.CreateHttpLog), sysLogHttp, cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置接口描述相关属性
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder"></param>
|
||||||
|
/// <param name="httpName"></param>
|
||||||
|
/// <param name="desc"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static HttpRequestBuilder SetRemoteApiAttr(HttpRequestBuilder builder, string httpName, string desc = "")
|
||||||
|
{
|
||||||
|
builder.WithHeader(CommonConst.HttpRemoteApiDescHeaderName, desc, replace:true);
|
||||||
|
builder.SetHttpClientName(httpName);
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -24,6 +24,22 @@ public class WorkWxBaseService(
|
|||||||
IHttpRemoteService httpRemoteService,
|
IHttpRemoteService httpRemoteService,
|
||||||
IOptions<HttpRemotesOptions> options) : ITransient
|
IOptions<HttpRemotesOptions> options) : ITransient
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 获取企业微信接口凭证
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
private async Task<string> GetTokenAsync()
|
||||||
|
{
|
||||||
|
using var disposable = sysCacheService.BeginCacheLock(WorkWeixinConst.KeyLockWorkWeixin);
|
||||||
|
var result = await SendAsync<TokenWorkWxInput, TokenWorkWxOutput>(new()
|
||||||
|
{
|
||||||
|
CorpId = await sysConfigService.GetConfigValueByCode(WorkWeixinConst.WorkWeixinCorpId),
|
||||||
|
CorpSecret = await sysConfigService.GetConfigValueByCode(WorkWeixinConst.WorkWeixinCorpSecret)
|
||||||
|
});
|
||||||
|
sysCacheService.Set(WorkWeixinConst.KeyWorkWeixinToken, result.AccessToken, TimeSpan.FromSeconds(result.ExpiresIn));
|
||||||
|
return result.AccessToken;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 发起请求
|
/// 发起请求
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -33,12 +49,13 @@ public class WorkWxBaseService(
|
|||||||
/// <returns>返回结果</returns>
|
/// <returns>返回结果</returns>
|
||||||
public async Task<R> SendAsync<T, R>(T input) where R : BaseWorkWxOutput
|
public async Task<R> SendAsync<T, R>(T input) where R : BaseWorkWxOutput
|
||||||
{
|
{
|
||||||
|
var opt = options.Value.WorkWeixin;
|
||||||
var attr = typeof(T).GetCustomAttribute<HttpRemoteApiAttribute>();
|
var attr = typeof(T).GetCustomAttribute<HttpRemoteApiAttribute>();
|
||||||
if (attr == null || string.IsNullOrWhiteSpace(attr.Action) || string.IsNullOrWhiteSpace(attr.Desc))
|
if (attr == null || string.IsNullOrWhiteSpace(attr.Action) || string.IsNullOrWhiteSpace(attr.Desc))
|
||||||
throw Oops.Oh($"接口入参类型({typeof(T).FullName})未正确配置[HttpRemoteApi]特性");
|
throw Oops.Oh($"接口入参类型({typeof(T).FullName})未正确配置[HttpRemoteApi]特性");
|
||||||
|
|
||||||
// 拼接请求地址,并设置token
|
// 拼接请求地址,并设置token
|
||||||
var url = options.Value.WorkWeixin.BaseAddress + $"/cgi-bin/{attr.Action}?";
|
var url = opt.BaseAddress + $"/cgi-bin/{attr.Action}?";
|
||||||
if (input is AuthWorkWxInput)
|
if (input is AuthWorkWxInput)
|
||||||
{
|
{
|
||||||
var token = sysCacheService.Get<string>(WorkWeixinConst.KeyWorkWeixinToken) ?? await GetTokenAsync();
|
var token = sysCacheService.Get<string>(WorkWeixinConst.KeyWorkWeixinToken) ?? await GetTokenAsync();
|
||||||
@ -52,9 +69,9 @@ public class WorkWxBaseService(
|
|||||||
{
|
{
|
||||||
HttpMethodEnum.Get => await httpRemoteService.GetAsync(
|
HttpMethodEnum.Get => await httpRemoteService.GetAsync(
|
||||||
url + input.ToCustomJsonPropertyQueryString(),
|
url + input.ToCustomJsonPropertyQueryString(),
|
||||||
builder => builder.SetHttpOptions(options.Value.WorkWeixin, attr.Desc)),
|
builder => builder.SetRemoteApiAttr(opt.HttpName, attr.Desc)),
|
||||||
HttpMethodEnum.Post => await httpRemoteService.PostAsync(url,
|
HttpMethodEnum.Post => await httpRemoteService.PostAsync(url,
|
||||||
builder => builder.SetHttpOptions(options.Value.WorkWeixin, attr.Desc)
|
builder => builder.SetRemoteApiAttr(opt.HttpName, attr.Desc)
|
||||||
.SetContent(new StringContent(JSON.Serialize(input, CustomJsonPropertyConverter.Options), Encoding.UTF8, "application/json"))),
|
.SetContent(new StringContent(JSON.Serialize(input, CustomJsonPropertyConverter.Options), Encoding.UTF8, "application/json"))),
|
||||||
_ => throw Oops.Oh($"[企业微信] 不支持的请求方式{attr.HttpMethod.ToString()}:({typeof(T).FullName})"),
|
_ => throw Oops.Oh($"[企业微信] 不支持的请求方式{attr.HttpMethod.ToString()}:({typeof(T).FullName})"),
|
||||||
};
|
};
|
||||||
@ -84,7 +101,7 @@ public class WorkWxBaseService(
|
|||||||
{
|
{
|
||||||
var resp = JSON.Deserialize<R>(responseContent, CustomJsonPropertyConverter.Options);
|
var resp = JSON.Deserialize<R>(responseContent, CustomJsonPropertyConverter.Options);
|
||||||
if (resp?.ErrCode == 0) return resp;
|
if (resp?.ErrCode == 0) return resp;
|
||||||
throw Oops.Oh("[企业微信] 请求失败:" + resp?.ErrMsg);
|
throw Oops.Oh("[企业微信] " + resp?.ErrMsg);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -92,20 +109,4 @@ public class WorkWxBaseService(
|
|||||||
throw Oops.Oh((ex is AppFriendlyException ? "" : "[企业微信] 序列化失败:") + ex.Message);
|
throw Oops.Oh((ex is AppFriendlyException ? "" : "[企业微信] 序列化失败:") + ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取企业微信接口凭证
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
private async Task<string> GetTokenAsync()
|
|
||||||
{
|
|
||||||
using var disposable = sysCacheService.BeginCacheLock(WorkWeixinConst.KeyLockWorkWeixin);
|
|
||||||
var result = await SendAsync<TokenWorkWxInput, TokenWorkWxOutput>(new()
|
|
||||||
{
|
|
||||||
CorpId = await sysConfigService.GetConfigValueByCode(WorkWeixinConst.WorkWeixinCorpId),
|
|
||||||
CorpSecret = await sysConfigService.GetConfigValueByCode(WorkWeixinConst.WorkWeixinCorpSecret)
|
|
||||||
});
|
|
||||||
sysCacheService.Set(WorkWeixinConst.KeyWorkWeixinToken, result.AccessToken, TimeSpan.FromSeconds(result.ExpiresIn));
|
|
||||||
return result.AccessToken;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -1,13 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { PropType, reactive, watch } from 'vue';
|
import { PropType, reactive, watch } from 'vue';
|
||||||
import { number } from 'echarts';
|
|
||||||
import { auth } from '/@/utils/authFunction';
|
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue', 'change']);
|
const emit = defineEmits(['update:modelValue', 'change']);
|
||||||
|
|
||||||
type BadgeTabsItem = {
|
type BadgeTabsItem = {
|
||||||
label: string; // 标签
|
label: NullableString | NullableNumber; // 标签
|
||||||
value: string; // 值
|
value: NullableString | NullableNumber; // 值
|
||||||
count?: number; // 数量
|
count?: number; // 数量
|
||||||
visible?: boolean; // 是否可见
|
visible?: boolean; // 是否可见
|
||||||
disabled?: boolean; // 是否禁用
|
disabled?: boolean; // 是否禁用
|
||||||
@ -25,7 +23,7 @@ const props = defineProps({
|
|||||||
* <BadgeTabs v-model="state.active" :data="[
|
* <BadgeTabs v-model="state.active" :data="[
|
||||||
* { label: '履约中', value: 10, count: state.total?.ly ?? 0 },
|
* { label: '履约中', value: 10, count: state.total?.ly ?? 0 },
|
||||||
* { label: '解约申请', value: 20, count: state.total?.jysq ?? 0 },
|
* { label: '解约申请', value: 20, count: state.total?.jysq ?? 0 },
|
||||||
* { label: '解约审核', value: 21, count: state.total?.jysh ?? 0, visible: auth('jtysqyQyd/approve') },
|
* { label: '解约审核', value: 21, count: state.total?.jysh ?? 0, visible: auth('xxx') },
|
||||||
* { label: '已解约', value: 22, count: state.total?.yjy ?? 0 },
|
* { label: '已解约', value: 22, count: state.total?.yjy ?? 0 },
|
||||||
* { label: '已超期', value: 11, count: state.total?.ycq ?? 0 },
|
* { label: '已超期', value: 11, count: state.total?.ycq ?? 0 },
|
||||||
* { label: '已失效', value: 199, count: state.total?.ysx ?? 0 },
|
* { label: '已失效', value: 199, count: state.total?.ysx ?? 0 },
|
||||||
@ -69,8 +67,8 @@ const props = defineProps({
|
|||||||
* @example [5, -2]
|
* @example [5, -2]
|
||||||
*/
|
*/
|
||||||
offset: {
|
offset: {
|
||||||
type: Array as PropType<[int, int]>,
|
type: (Array as unknown) as PropType<[number, number]>,
|
||||||
default: [5, -2],
|
default: [5, -2] as const,
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* 隐藏小于等于0的badge
|
* 隐藏小于等于0的badge
|
||||||
|
|||||||
@ -114,7 +114,7 @@ const props = defineProps({
|
|||||||
* <g-sys-dict v-model="selectedValues" code="roles" renderAs="select" multiple multiple-model="comma" />
|
* <g-sys-dict v-model="selectedValues" code="roles" renderAs="select" multiple multiple-model="comma" />
|
||||||
*/
|
*/
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: [String, Number, Boolean, Array, null] as PropType<string | number | boolean | any[] | null>,
|
type: [String, Number, Boolean, Array, null] as PropType<string | number | boolean | any[] | Nullable>,
|
||||||
default: null,
|
default: null,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
@ -270,7 +270,7 @@ const currentDictItems = computed(() => {
|
|||||||
if (!state.value) return null;
|
if (!state.value) return null;
|
||||||
|
|
||||||
if (Array.isArray(state.value)) {
|
if (Array.isArray(state.value)) {
|
||||||
// 确保回显时也去重
|
// 去重
|
||||||
const uniqueValues = [...new Set(state.value)];
|
const uniqueValues = [...new Set(state.value)];
|
||||||
return formattedDictData.value.filter((item) => uniqueValues.includes(item.value));
|
return formattedDictData.value.filter((item) => uniqueValues.includes(item.value));
|
||||||
}
|
}
|
||||||
@ -296,7 +296,7 @@ const getDataList = (): DictItem[] => {
|
|||||||
|
|
||||||
return data.map((item: any) => ({
|
return data.map((item: any) => ({
|
||||||
...item,
|
...item,
|
||||||
label: item[props.propLabel] ?? item.name,
|
label: item[props.propLabel] ?? [item.name, item.desc].filter(x => x).join("-"),
|
||||||
value: item[props.propValue] ?? item.code,
|
value: item[props.propValue] ?? item.code,
|
||||||
}));
|
}));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -445,7 +445,7 @@ const updateValue = (newValue: any) => {
|
|||||||
|
|
||||||
state.value = processedValue;
|
state.value = processedValue;
|
||||||
emit('update:modelValue', emitValue === '' ? undefined : emitValue);
|
emit('update:modelValue', emitValue === '' ? undefined : emitValue);
|
||||||
emit('change', state.value, state.dictData);
|
emit('change', state.value, currentDictItems, state.dictData);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -539,7 +539,7 @@ watch(() => [userStore.dictList, userStore.constList, state], initData, { immedi
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 渲染选择器 -->
|
<!-- 渲染选择器 -->
|
||||||
<el-select v-else-if="props.renderAs === 'select'" v-model="state.value" v-bind="$attrs" :multiple="props.multiple" @change="updateValue" clearable>
|
<el-select v-else-if="props.renderAs === 'select'" v-model="state.value" v-bind="$attrs" :multiple="props.multiple" @change="updateValue">
|
||||||
<el-option v-for="(item, index) in formattedDictData" :key="index" :label="getDisplayText(item)" :value="item.value" :disabled="item.disabled" />
|
<el-option v-for="(item, index) in formattedDictData" :key="index" :label="getDisplayText(item)" :value="item.value" :disabled="item.disabled" />
|
||||||
</el-select>
|
</el-select>
|
||||||
|
|
||||||
|
|||||||
6
Web/src/types/global.d.ts
vendored
6
Web/src/types/global.d.ts
vendored
@ -77,6 +77,12 @@ declare interface RouteToFrom<T = any> extends RouteItem {
|
|||||||
children?: T[];
|
children?: T[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 声明 Nullable 可空类型
|
||||||
|
declare type Nullable = null | undefined;
|
||||||
|
declare type NullableString = Nullable | String;
|
||||||
|
declare type NullableNumber = Nullable | Number;
|
||||||
|
declare type NullableBoolean = Nullable | Boolean;
|
||||||
|
|
||||||
// 声明路由当前项类型集合
|
// 声明路由当前项类型集合
|
||||||
declare type RouteItems<T extends RouteItem = any> = T[];
|
declare type RouteItems<T extends RouteItem = any> = T[];
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user