refactor:优化设备列表样式和更新 Redis 支持
- 在 `devices/index.less` 中注释掉不必要的 CSS 规则 - 更新 `AndroidAuthServiceImpl` 和 `AndroidDeviceRegistrationServiceImpl` 中的异常信息和方法简化 - 在 `MeetingCreateDrawer.tsx` 中启用文本精炼功能 - 在 `devices/index.tsx` 中使用通用成功消息 - 在 `DeviceOnlineManagementServiceImpl` 中添加对终端类型的映射 - 更新 `ClientManagement.tsx` 中的平台类型选项 - 在 `MeetingPointsManagement.tsx` 中注释掉当前可用额度显示 - 在 `scan-confirm/index.tsx` 中更新登录确认消息 - 更新 `RedisSupport` 以使用 Lettuce 库并调整相关方法dev_na
parent
f787f867bb
commit
7233f13598
|
|
@ -272,7 +272,7 @@ public class AndroidAuthServiceImpl implements AndroidAuthService {
|
|||
private DeviceInfoEntity requireRegisteredDevice(String deviceId) {
|
||||
DeviceInfoEntity device = findDevice(deviceId);
|
||||
if (device == null) {
|
||||
throw new RuntimeException("设备未注册,请先调用设备注册接口");
|
||||
throw new RuntimeException("设备未注册");
|
||||
}
|
||||
return device;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,7 +94,6 @@ public class AndroidDeviceRegistrationServiceImpl implements AndroidDeviceRegist
|
|||
}
|
||||
|
||||
private String normalizeTerminalType(String value) {
|
||||
String normalized = normalize(value);
|
||||
return normalized == null ? null : normalized.toLowerCase();
|
||||
return normalize(value);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package com.imeeting.service.biz.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.collection.ListUtil;
|
||||
import com.imeeting.dto.android.AndroidAuthContext;
|
||||
import com.imeeting.dto.android.AndroidDeviceSessionState;
|
||||
import com.imeeting.dto.biz.DeviceAdminUpdateCommand;
|
||||
|
|
@ -11,7 +13,9 @@ import com.imeeting.service.android.AndroidDeviceSessionService;
|
|||
import com.imeeting.service.android.AndroidGatewayPushService;
|
||||
import com.imeeting.service.biz.DeviceOnlineManagementService;
|
||||
import com.imeeting.service.biz.LicenseService;
|
||||
import com.unisbase.dto.SysDictItemDTO;
|
||||
import com.unisbase.security.LoginUser;
|
||||
import com.unisbase.service.SysDictItemService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
|
@ -20,7 +24,10 @@ import org.springframework.util.StringUtils;
|
|||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
|
|
@ -31,8 +38,9 @@ public class DeviceOnlineManagementServiceImpl implements DeviceOnlineManagement
|
|||
private final AndroidGatewayPushService androidGatewayPushService;
|
||||
private final AndroidDeviceBindingService androidDeviceBindingService;
|
||||
private final LicenseService licenseService;
|
||||
private final SysDictItemService sysDictItemService;
|
||||
|
||||
@Override
|
||||
@Override
|
||||
public void recordConnected(AndroidAuthContext authContext) {
|
||||
if (authContext == null || !StringUtils.hasText(authContext.getDeviceId())) {
|
||||
return;
|
||||
|
|
@ -82,8 +90,19 @@ public class DeviceOnlineManagementServiceImpl implements DeviceOnlineManagement
|
|||
@Override
|
||||
public List<DeviceOnlineAdminVO> listForAdmin(LoginUser loginUser) {
|
||||
List<DeviceOnlineAdminVO> devices = deviceInfoMapper.selectAdminList(loginUser == null ? null : loginUser.getTenantId(), isPlatformAdmin(loginUser));
|
||||
for (DeviceOnlineAdminVO device : devices) {
|
||||
List<SysDictItemDTO> clientPlatform = sysDictItemService.getItemsByTypeCode("client_platform");
|
||||
Map<String, String> typeMap = new HashMap<>();
|
||||
for (SysDictItemDTO sysDictItemDTO : clientPlatform) {
|
||||
List<SysDictItemDTO> itemsByTypeCode = sysDictItemService.getItemsByTypeCode(sysDictItemDTO.getItemValue());
|
||||
if (CollectionUtil.isNotEmpty(itemsByTypeCode)) {
|
||||
typeMap.putAll(itemsByTypeCode.stream().collect(Collectors.toMap(SysDictItemDTO::getItemValue, SysDictItemDTO::getItemLabel)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for (DeviceOnlineAdminVO device : devices) {
|
||||
AndroidDeviceSessionState state = androidDeviceSessionService.getByDeviceId(device.getDeviceCode());
|
||||
device.setTerminalType(typeMap.getOrDefault(device.getTerminalType(), device.getTerminalType()));
|
||||
if (state != null) {
|
||||
device.setOnline(true);
|
||||
device.setLastOnlineAt(toLocalDateTime(state.getLastSeenAt()));
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
package com.imeeting.support;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.lettuce.core.SetArgs;
|
||||
import io.lettuce.core.api.StatefulRedisConnection;
|
||||
import io.lettuce.core.api.sync.RedisCommands;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.Duration;
|
||||
|
|
@ -14,12 +16,12 @@ import java.util.Collection;
|
|||
@RequiredArgsConstructor
|
||||
public class RedisSupport {
|
||||
|
||||
private final StringRedisTemplate redisTemplate;
|
||||
private final StatefulRedisConnection<String, String> redisConnection;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public String getStringQuietly(String key) {
|
||||
try {
|
||||
return redisTemplate.opsForValue().get(key);
|
||||
return commands().get(key);
|
||||
} catch (Exception ex) {
|
||||
log.warn("读取 Redis 字符串失败, key={}", key, ex);
|
||||
return null;
|
||||
|
|
@ -41,7 +43,7 @@ public class RedisSupport {
|
|||
|
||||
public void setString(String key, String value) {
|
||||
try {
|
||||
redisTemplate.opsForValue().set(key, value);
|
||||
commands().set(key, value);
|
||||
} catch (Exception ex) {
|
||||
throw new RuntimeException("写入 Redis 字符串失败, key=" + key, ex);
|
||||
}
|
||||
|
|
@ -49,7 +51,7 @@ public class RedisSupport {
|
|||
|
||||
public void setString(String key, String value, Duration ttl) {
|
||||
try {
|
||||
redisTemplate.opsForValue().set(key, value, ttl);
|
||||
commands().psetex(key, ttl.toMillis(), value);
|
||||
} catch (Exception ex) {
|
||||
throw new RuntimeException("写入 Redis 字符串失败, key=" + key, ex);
|
||||
}
|
||||
|
|
@ -65,8 +67,8 @@ public class RedisSupport {
|
|||
|
||||
public boolean setIfAbsentQuietly(String key, String value, Duration ttl) {
|
||||
try {
|
||||
Boolean success = redisTemplate.opsForValue().setIfAbsent(key, value, ttl);
|
||||
return Boolean.TRUE.equals(success);
|
||||
String result = commands().set(key, value, buildNxPxArgs(ttl));
|
||||
return isOk(result);
|
||||
} catch (Exception ex) {
|
||||
log.warn("写入 Redis 锁失败, key={}", key, ex);
|
||||
return false;
|
||||
|
|
@ -75,8 +77,8 @@ public class RedisSupport {
|
|||
|
||||
public boolean setIfAbsentOrThrow(String key, String value, Duration ttl) {
|
||||
try {
|
||||
Boolean success = redisTemplate.opsForValue().setIfAbsent(key, value, ttl);
|
||||
return Boolean.TRUE.equals(success);
|
||||
String result = commands().set(key, value, buildNxPxArgs(ttl));
|
||||
return isOk(result);
|
||||
} catch (Exception ex) {
|
||||
throw new RuntimeException("写入 Redis 锁失败, key=" + key, ex);
|
||||
}
|
||||
|
|
@ -84,15 +86,18 @@ public class RedisSupport {
|
|||
|
||||
public void deleteQuietly(String key) {
|
||||
try {
|
||||
redisTemplate.delete(key);
|
||||
commands().del(key);
|
||||
} catch (Exception ex) {
|
||||
log.warn("删除 Redis Key 失败, key={}", key, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void deleteQuietly(Collection<String> keys) {
|
||||
if (keys == null || keys.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
redisTemplate.delete(keys);
|
||||
commands().del(keys.toArray(String[]::new));
|
||||
} catch (Exception ex) {
|
||||
log.warn("批量删除 Redis Key 失败, keys={}", keys, ex);
|
||||
}
|
||||
|
|
@ -103,7 +108,7 @@ public class RedisSupport {
|
|||
return;
|
||||
}
|
||||
try {
|
||||
redisTemplate.opsForSet().remove(key, (Object[]) members);
|
||||
commands().srem(key, members);
|
||||
} catch (Exception ex) {
|
||||
log.warn("从 Redis Set 删除成员失败, key={}", key, ex);
|
||||
}
|
||||
|
|
@ -114,8 +119,7 @@ public class RedisSupport {
|
|||
return false;
|
||||
}
|
||||
try {
|
||||
Long added = redisTemplate.opsForSet().add(key, member);
|
||||
return added != null && added > 0;
|
||||
return commands().sadd(key, member) > 0;
|
||||
} catch (Exception ex) {
|
||||
log.warn("add Redis set member failed, key={}", key, ex);
|
||||
return false;
|
||||
|
|
@ -127,8 +131,7 @@ public class RedisSupport {
|
|||
return false;
|
||||
}
|
||||
try {
|
||||
Boolean memberPresent = redisTemplate.opsForSet().isMember(key, member);
|
||||
return Boolean.TRUE.equals(memberPresent);
|
||||
return Boolean.TRUE.equals(commands().sismember(key, member));
|
||||
} catch (Exception ex) {
|
||||
log.warn("check Redis set member failed, key={}", key, ex);
|
||||
return false;
|
||||
|
|
@ -137,7 +140,7 @@ public class RedisSupport {
|
|||
|
||||
public long getSetSizeQuietly(String key) {
|
||||
try {
|
||||
Long size = redisTemplate.opsForSet().size(key);
|
||||
Long size = commands().scard(key);
|
||||
return size == null ? 0L : size;
|
||||
} catch (Exception ex) {
|
||||
log.warn("read Redis set size failed, key={}", key, ex);
|
||||
|
|
@ -145,6 +148,18 @@ public class RedisSupport {
|
|||
}
|
||||
}
|
||||
|
||||
private RedisCommands<String, String> commands() {
|
||||
return redisConnection.sync();
|
||||
}
|
||||
|
||||
private SetArgs buildNxPxArgs(Duration ttl) {
|
||||
return SetArgs.Builder.nx().px(ttl.toMillis());
|
||||
}
|
||||
|
||||
private boolean isOk(String result) {
|
||||
return "OK".equalsIgnoreCase(result);
|
||||
}
|
||||
|
||||
private String writeJson(Object value) {
|
||||
try {
|
||||
return objectMapper.writeValueAsString(value);
|
||||
|
|
|
|||
|
|
@ -217,7 +217,7 @@ export const MeetingCreateDrawer: React.FC<MeetingCreateDrawerProps> = ({
|
|||
hotWordGroupId: defaultPrompt?.hotWordGroupId ?? 0,
|
||||
summaryDetailLevel: "STANDARD",
|
||||
useSpkId: 1,
|
||||
enableTextRefine: false,
|
||||
enableTextRefine: true,
|
||||
mode: "2pass",
|
||||
language: "auto",
|
||||
enablePunctuation: true,
|
||||
|
|
@ -651,4 +651,4 @@ export const MeetingCreateDrawer: React.FC<MeetingCreateDrawerProps> = ({
|
|||
)}
|
||||
</Drawer>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,23 @@
|
|||
import { App, Button, Card, Col, Drawer, Empty, Form, Input, InputNumber, Popconfirm, Row, Select, Space, Switch, Table, Tabs, Tag, Typography, Upload } from "antd";
|
||||
import {
|
||||
App,
|
||||
Button,
|
||||
Card,
|
||||
Col,
|
||||
Drawer,
|
||||
Empty,
|
||||
Form,
|
||||
Input,
|
||||
InputNumber,
|
||||
Popconfirm,
|
||||
Row,
|
||||
Select,
|
||||
Space,
|
||||
Switch,
|
||||
Table,
|
||||
Tag,
|
||||
Typography,
|
||||
Upload
|
||||
} from "antd";
|
||||
import type { ColumnsType } from "antd/es/table";
|
||||
import { CheckCircleOutlined, CloudUploadOutlined, DeleteOutlined, DownloadOutlined, EditOutlined, LaptopOutlined, MobileOutlined, PlusOutlined, ReloadOutlined, RocketOutlined, SearchOutlined, UploadOutlined, WindowsOutlined } from "@ant-design/icons";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
|
|
@ -155,6 +174,13 @@ export default function ClientManagement() {
|
|||
() => Object.fromEntries(platformGroups.flatMap((group) => group.options.map((option) => [option.value, option]))) as Record<string, ClientPlatformOption>,
|
||||
[platformGroups]
|
||||
);
|
||||
const platformTypeOptions = useMemo(
|
||||
() => [{label: "全部类型", value: "all"}, ...platformGroups.map((group) => ({
|
||||
label: group.label,
|
||||
value: group.key
|
||||
}))],
|
||||
[platformGroups]
|
||||
);
|
||||
|
||||
const loadData = useCallback(async () => {
|
||||
setLoading(true);
|
||||
|
|
@ -447,12 +473,20 @@ export default function ClientManagement() {
|
|||
</Row>
|
||||
|
||||
<Card className="app-page__filter-card mb-4" styles={{ body: { padding: 16 } }}>
|
||||
<Space wrap style={{ width: "100%", justifyContent: "space-between" }}>
|
||||
<Space wrap>
|
||||
<Input placeholder="搜索平台、版本、系统要求或下载地址" prefix={<SearchOutlined />} allowClear style={{ width: 320 }} value={searchValue} onChange={(event) => setSearchValue(event.target.value)} />
|
||||
<Select style={{ width: 150 }} value={statusFilter} options={STATUS_FILTER_OPTIONS as unknown as { label: string; value: string }[]} onChange={(value) => setStatusFilter(value as typeof statusFilter)} />
|
||||
</Space>
|
||||
<Tabs activeKey={activeTab} onChange={setActiveTab} items={[{ key: "all", label: "全部" }, ...platformGroups.map((group) => ({ key: group.key, label: group.label }))]} />
|
||||
<Space wrap style={{width: "100%"}}>
|
||||
<Input placeholder="搜索平台、版本、系统要求或下载地址" prefix={<SearchOutlined/>} allowClear
|
||||
style={{width: 320}} value={searchValue} onChange={(event) => setSearchValue(event.target.value)}/>
|
||||
<Select style={{width: 150}} value={statusFilter}
|
||||
options={STATUS_FILTER_OPTIONS as unknown as { label: string; value: string }[]}
|
||||
onChange={(value) => setStatusFilter(value as typeof statusFilter)}/>
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
style={{width: 180}}
|
||||
value={activeTab}
|
||||
options={platformTypeOptions}
|
||||
onChange={setActiveTab}
|
||||
/>
|
||||
</Space>
|
||||
</Card>
|
||||
|
||||
|
|
|
|||
|
|
@ -101,12 +101,12 @@ function buildSummaryCards(overview: MeetingPointsOverviewVO | null) {
|
|||
value: number | string;
|
||||
note: string;
|
||||
}> = [
|
||||
{
|
||||
key: "available-balance",
|
||||
title: "当前可用额度",
|
||||
value: isUnlimitedBalanceMode ? "无限" : (overview.totalAvailableBalance ?? 0),
|
||||
note: isUnlimitedBalanceMode ? "关闭余额校验后只记录消耗和流水" : "按当前账户模式计算的可用额度",
|
||||
},
|
||||
// {
|
||||
// key: "available-balance",
|
||||
// title: "当前可用额度",
|
||||
// value: isUnlimitedBalanceMode ? "无限" : (overview.totalAvailableBalance ?? 0),
|
||||
// note: isUnlimitedBalanceMode ? "关闭余额校验后只记录消耗和流水" : "按当前账户模式计算的可用额度",
|
||||
// },
|
||||
{
|
||||
key: "charge-count",
|
||||
title: "累计消耗次数",
|
||||
|
|
@ -493,20 +493,20 @@ export default function MeetingPointsManagement() {
|
|||
<Text strong style={{ fontSize: 16 }}>
|
||||
账户概览
|
||||
</Text>
|
||||
<Space wrap>
|
||||
<Tag color="processing" bordered={false}>
|
||||
模式:{getAccountModeLabel(overview?.accountMode)}
|
||||
</Tag>
|
||||
<Tag color="blue" bordered={false}>
|
||||
优先级:{getChargePriorityLabel(overview?.chargePriority)}
|
||||
</Tag>
|
||||
<Tag color={isUnlimitedBalanceMode ? "volcano" : "green"} bordered={false}>
|
||||
{isUnlimitedBalanceMode ? "无限余额模式" : "校验余额模式"}
|
||||
</Tag>
|
||||
<Tag color={isAdmin ? "gold" : "default"} bordered={false}>
|
||||
{isAdmin ? "管理员视角" : "当前用户视角"}
|
||||
</Tag>
|
||||
</Space>
|
||||
{/*<Space wrap>*/}
|
||||
{/* <Tag color="processing" bordered={false}>*/}
|
||||
{/* 模式:{getAccountModeLabel(overview?.accountMode)}*/}
|
||||
{/* </Tag>*/}
|
||||
{/* <Tag color="blue" bordered={false}>*/}
|
||||
{/* 优先级:{getChargePriorityLabel(overview?.chargePriority)}*/}
|
||||
{/* </Tag>*/}
|
||||
{/* <Tag color={isUnlimitedBalanceMode ? "volcano" : "green"} bordered={false}>*/}
|
||||
{/* {isUnlimitedBalanceMode ? "无限余额模式" : "校验余额模式"}*/}
|
||||
{/* </Tag>*/}
|
||||
{/* <Tag color={isAdmin ? "gold" : "default"} bordered={false}>*/}
|
||||
{/* {isAdmin ? "管理员视角" : "当前用户视角"}*/}
|
||||
{/* </Tag>*/}
|
||||
{/*</Space>*/}
|
||||
</div>
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -175,10 +175,10 @@
|
|||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.app-page__table-wrap .ant-table-wrapper .ant-table-cell-fix-right-first,
|
||||
.app-page__table-wrap .ant-table-wrapper .ant-table-cell-fix-right-last {
|
||||
right: 0 !important;
|
||||
}
|
||||
//.app-page__table-wrap .ant-table-wrapper .ant-table-cell-fix-right-first,
|
||||
//.app-page__table-wrap .ant-table-wrapper .ant-table-cell-fix-right-last {
|
||||
// right: 0 !important;
|
||||
//}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.devices-page {
|
||||
|
|
|
|||
|
|
@ -117,13 +117,13 @@ export default function Devices() {
|
|||
|
||||
const remove = async (record: DeviceInfo) => {
|
||||
await deleteManagedDevice(record.deviceId);
|
||||
message.success(t("devicesExt.deleteSucceeded"));
|
||||
message.success(t("common.success"));
|
||||
await loadData();
|
||||
};
|
||||
|
||||
const resetStats = async (record: DeviceInfo) => {
|
||||
await resetManagedDeviceStats(record.deviceId);
|
||||
message.success(t("devicesExt.resetStatsSucceeded"));
|
||||
message.success(t("common.success"));
|
||||
await loadData();
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ export default function ScanConfirmPage() {
|
|||
<Result
|
||||
status="success"
|
||||
title="登录确认已发送"
|
||||
subTitle="安卓设备收到确认消息后,会继续按离线发会流程处理后续动作。"
|
||||
subTitle="设备收到确认消息后,会开启会议。"
|
||||
extra={
|
||||
<Button type="primary" onClick={() => navigate("/meetings")}>
|
||||
返回我的会议
|
||||
|
|
|
|||
Loading…
Reference in New Issue