Skip to content

Commit d02aa6c

Browse files
committed
fix: resolve product icon rendering issue and add missing port display in domain URLs
Change-Id: I0d40aacee67059cefcd2c89fbf4f9946370715d3
1 parent 7f693ec commit d02aa6c

11 files changed

Lines changed: 173 additions & 137 deletions

File tree

himarket-server/src/main/java/com/alibaba/himarket/service/impl/PortalServiceImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ public class PortalServiceImpl implements PortalService {
8787

8888
private final IdpService idpService;
8989

90-
private final String domainFormat = "%s.api.portal.local";
90+
private final String domainFormat = "%s.himarket.local";
9191

9292
private final ProductPublicationRepository publicationRepository;
9393

himarket-web/himarket-admin/src/components/api-product/ApiProductLinkApi.tsx

Lines changed: 56 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import type { ApiProduct, LinkedService, RestAPIItem, NacosMCPItem, APIGAIMCPIte
55
import type { Gateway, NacosInstance } from '@/types/gateway'
66
import { apiProductApi, gatewayApi, nacosApi } from '@/lib/api'
77
import { getGatewayTypeLabel } from '@/lib/constant'
8-
import { copyToClipboard } from '@/lib/utils'
8+
import { copyToClipboard, formatDomainWithPort } from '@/lib/utils'
99
import * as yaml from 'js-yaml'
1010
import { SwaggerUIWrapper } from './SwaggerUIWrapper'
1111

@@ -109,11 +109,12 @@ export function ApiProductLinkApi({ apiProduct, linkedService, onLinkedServiceUp
109109
}, [apiProduct, linkedService, selectedDomainIndex])
110110

111111
// 生成域名选项的函数
112-
const getDomainOptions = (domains: Array<{ domain: string; protocol: string; networkType?: string }>) => {
112+
const getDomainOptions = (domains: Array<{ domain: string; port?: number; protocol: string; networkType?: string }>) => {
113113
return domains.map((domain, index) => {
114+
const formattedDomain = formatDomainWithPort(domain.domain, domain.port, domain.protocol);
114115
return {
115116
value: index,
116-
label: `${domain.protocol}://${domain.domain}`,
117+
label: `${domain.protocol}://${formattedDomain}`,
117118
domain: domain
118119
}
119120
})
@@ -160,7 +161,7 @@ export function ApiProductLinkApi({ apiProduct, linkedService, onLinkedServiceUp
160161

161162
// 生成连接配置
162163
const generateConnectionConfig = (
163-
domains: Array<{ domain: string; protocol: string }> | null | undefined,
164+
domains: Array<{ domain: string; port?: number; protocol: string }> | null | undefined,
164165
path: string | null | undefined,
165166
serverName: string,
166167
localConfig?: unknown,
@@ -179,22 +180,7 @@ export function ApiProductLinkApi({ apiProduct, linkedService, onLinkedServiceUp
179180
// HTTP/SSE 模式
180181
if (domains && domains.length > 0 && path && domainIndex < domains.length) {
181182
const domain = domains[domainIndex]
182-
// 处理域名和端口,隐藏默认端口(80/443)
183-
const formatDomainWithPort = (domainStr: string, protocol: string) => {
184-
const [host, port] = domainStr.split(':');
185-
// 如果没有端口,直接返回域名
186-
if (!port) return domainStr;
187-
188-
// 隐藏 HTTP 默认端口 80
189-
if (protocol === 'http' && port === '80') return host;
190-
// 隐藏 HTTPS 默认端口 443
191-
if (protocol === 'https' && port === '443') return host;
192-
193-
// 其他情况保留端口
194-
return domainStr;
195-
};
196-
197-
const formattedDomain = formatDomainWithPort(domain.domain, domain.protocol);
183+
const formattedDomain = formatDomainWithPort(domain.domain, domain.port, domain.protocol);
198184
const baseUrl = `${domain.protocol}://${formattedDomain}`;
199185
let fullUrl = `${baseUrl}${path || '/'}`;
200186

@@ -988,12 +974,12 @@ export function ApiProductLinkApi({ apiProduct, linkedService, onLinkedServiceUp
988974

989975
// 获取所有唯一域名的简化版本
990976
const getAllUniqueDomains = () => {
991-
const domainsMap = new Map<string, { domain: string; protocol: string }>()
977+
const domainsMap = new Map<string, { domain: string; port?: number; protocol: string }>()
992978

993979
routes.forEach(route => {
994980
if (route.domains && route.domains.length > 0) {
995981
route.domains.forEach((domain: any) => {
996-
const key = `${domain.protocol}://${domain.domain}`
982+
const key = `${domain.protocol}://${domain.domain}${domain.port ? `:${domain.port}` : ''}`
997983
domainsMap.set(key, domain)
998984
})
999985
}
@@ -1005,10 +991,13 @@ export function ApiProductLinkApi({ apiProduct, linkedService, onLinkedServiceUp
1005991
const allUniqueDomains = getAllUniqueDomains()
1006992

1007993
// 生成域名选择器选项
1008-
const agentDomainOptions = allUniqueDomains.map((domain, index) => ({
1009-
value: index,
1010-
label: `${domain.protocol.toLowerCase()}://${domain.domain}`
1011-
}))
994+
const agentDomainOptions = allUniqueDomains.map((domain, index) => {
995+
const formattedDomain = formatDomainWithPort(domain.domain, domain.port, domain.protocol);
996+
return {
997+
value: index,
998+
label: `${domain.protocol.toLowerCase()}://${formattedDomain}`
999+
}
1000+
})
10121001

10131002
// 生成路由显示文本(优化方法显示)
10141003
const getRouteDisplayText = (route: any, domainIndex: number = 0) => {
@@ -1021,11 +1010,13 @@ export function ApiProductLinkApi({ apiProduct, linkedService, onLinkedServiceUp
10211010
let domainInfo = ''
10221011
if (allUniqueDomains.length > 0 && allUniqueDomains.length > domainIndex) {
10231012
const selectedDomain = allUniqueDomains[domainIndex]
1024-
domainInfo = `${selectedDomain.protocol.toLowerCase()}://${selectedDomain.domain}`
1013+
const formattedDomain = formatDomainWithPort(selectedDomain.domain, selectedDomain.port, selectedDomain.protocol);
1014+
domainInfo = `${selectedDomain.protocol.toLowerCase()}://${formattedDomain}`
10251015
} else if (route.domains && route.domains.length > 0) {
10261016
// 回退到路由的第一个域名
10271017
const domain = route.domains[0]
1028-
domainInfo = `${domain.protocol.toLowerCase()}://${domain.domain}`
1018+
const formattedDomain = formatDomainWithPort(domain.domain, domain.port, domain.protocol);
1019+
domainInfo = `${domain.protocol.toLowerCase()}://${formattedDomain}`
10291020
}
10301021

10311022
// 构建基本路由信息(匹配符号直接加到path后面)
@@ -1051,12 +1042,14 @@ export function ApiProductLinkApi({ apiProduct, linkedService, onLinkedServiceUp
10511042
const getFullUrl = (route: any, domainIndex: number = 0) => {
10521043
if (allUniqueDomains.length > 0 && allUniqueDomains.length > domainIndex) {
10531044
const selectedDomain = allUniqueDomains[domainIndex]
1045+
const formattedDomain = formatDomainWithPort(selectedDomain.domain, selectedDomain.port, selectedDomain.protocol);
10541046
const path = route.match?.path?.value || '/'
1055-
return `${selectedDomain.protocol.toLowerCase()}://${selectedDomain.domain}${path}`
1047+
return `${selectedDomain.protocol.toLowerCase()}://${formattedDomain}${path}`
10561048
} else if (route.domains && route.domains.length > 0) {
10571049
const domain = route.domains[0]
1050+
const formattedDomain = formatDomainWithPort(domain.domain, domain.port, domain.protocol);
10581051
const path = route.match?.path?.value || '/'
1059-
return `${domain.protocol.toLowerCase()}://${domain.domain}${path}`
1052+
return `${domain.protocol.toLowerCase()}://${formattedDomain}${path}`
10601053
}
10611054
return ''
10621055
}
@@ -1270,11 +1263,14 @@ export function ApiProductLinkApi({ apiProduct, linkedService, onLinkedServiceUp
12701263
{/* 域名信息 */}
12711264
<div>
12721265
<div className="text-xs text-gray-500 mb-1">域名:</div>
1273-
{route.domains?.map((domain: any, domainIndex: number) => (
1274-
<div key={domainIndex} className="text-sm">
1275-
<span className="font-mono">{domain.protocol.toLowerCase()}://{domain.domain}</span>
1276-
</div>
1277-
))}
1266+
{route.domains?.map((domain: any, domainIndex: number) => {
1267+
const formattedDomain = formatDomainWithPort(domain.domain, domain.port, domain.protocol);
1268+
return (
1269+
<div key={domainIndex} className="text-sm">
1270+
<span className="font-mono">{domain.protocol.toLowerCase()}://{formattedDomain}</span>
1271+
</div>
1272+
)
1273+
})}
12781274
</div>
12791275

12801276
{/* 匹配规则 */}
@@ -1346,12 +1342,12 @@ export function ApiProductLinkApi({ apiProduct, linkedService, onLinkedServiceUp
13461342

13471343
// 获取所有唯一域名的简化版本
13481344
const getAllModelUniqueDomains = () => {
1349-
const domainsMap = new Map<string, { domain: string; protocol: string }>()
1345+
const domainsMap = new Map<string, { domain: string; port?: number; protocol: string }>()
13501346

13511347
routes.forEach(route => {
13521348
if (route.domains && route.domains.length > 0) {
13531349
route.domains.forEach((domain: any) => {
1354-
const key = `${domain.protocol}://${domain.domain}`
1350+
const key = `${domain.protocol}://${domain.domain}${domain.port ? `:${domain.port}` : ''}`
13551351
domainsMap.set(key, domain)
13561352
})
13571353
}
@@ -1363,10 +1359,13 @@ export function ApiProductLinkApi({ apiProduct, linkedService, onLinkedServiceUp
13631359
const allModelUniqueDomains = getAllModelUniqueDomains()
13641360

13651361
// 生成域名选择器选项
1366-
const modelDomainOptions = allModelUniqueDomains.map((domain, index) => ({
1367-
value: index,
1368-
label: `${domain.protocol.toLowerCase()}://${domain.domain}`
1369-
}))
1362+
const modelDomainOptions = allModelUniqueDomains.map((domain, index) => {
1363+
const formattedDomain = formatDomainWithPort(domain.domain, domain.port, domain.protocol);
1364+
return {
1365+
value: index,
1366+
label: `${domain.protocol.toLowerCase()}://${formattedDomain}`
1367+
}
1368+
})
13701369

13711370
// 生成匹配类型前缀文字
13721371
const getMatchTypePrefix = (matchType: string) => {
@@ -1393,11 +1392,13 @@ export function ApiProductLinkApi({ apiProduct, linkedService, onLinkedServiceUp
13931392
let domainInfo = ''
13941393
if (allModelUniqueDomains.length > 0 && allModelUniqueDomains.length > domainIndex) {
13951394
const selectedDomain = allModelUniqueDomains[domainIndex]
1396-
domainInfo = `${selectedDomain.protocol.toLowerCase()}://${selectedDomain.domain}`
1395+
const formattedDomain = formatDomainWithPort(selectedDomain.domain, selectedDomain.port, selectedDomain.protocol);
1396+
domainInfo = `${selectedDomain.protocol.toLowerCase()}://${formattedDomain}`
13971397
} else if (route.domains && route.domains.length > 0) {
13981398
// 回退到路由的第一个域名
13991399
const domain = route.domains[0]
1400-
domainInfo = `${domain.protocol.toLowerCase()}://${domain.domain}`
1400+
const formattedDomain = formatDomainWithPort(domain.domain, domain.port, domain.protocol);
1401+
domainInfo = `${domain.protocol.toLowerCase()}://${formattedDomain}`
14011402
}
14021403

14031404
// 构建基本路由信息(匹配符号直接加到path后面)
@@ -1431,12 +1432,14 @@ export function ApiProductLinkApi({ apiProduct, linkedService, onLinkedServiceUp
14311432
const getFullUrl = (route: any, domainIndex: number = 0) => {
14321433
if (allModelUniqueDomains.length > 0 && allModelUniqueDomains.length > domainIndex) {
14331434
const selectedDomain = allModelUniqueDomains[domainIndex]
1435+
const formattedDomain = formatDomainWithPort(selectedDomain.domain, selectedDomain.port, selectedDomain.protocol);
14341436
const path = route.match?.path?.value || '/'
1435-
return `${selectedDomain.protocol.toLowerCase()}://${selectedDomain.domain}${path}`
1437+
return `${selectedDomain.protocol.toLowerCase()}://${formattedDomain}${path}`
14361438
} else if (route.domains && route.domains.length > 0) {
14371439
const domain = route.domains[0]
1440+
const formattedDomain = formatDomainWithPort(domain.domain, domain.port, domain.protocol);
14381441
const path = route.match?.path?.value || '/'
1439-
return `${domain.protocol.toLowerCase()}://${domain.domain}${path}`
1442+
return `${domain.protocol.toLowerCase()}://${formattedDomain}${path}`
14401443
}
14411444
return null
14421445
}
@@ -1564,11 +1567,14 @@ export function ApiProductLinkApi({ apiProduct, linkedService, onLinkedServiceUp
15641567
{/* 域名信息 */}
15651568
<div>
15661569
<div className="text-xs text-gray-500 mb-1">域名:</div>
1567-
{route.domains?.map((domain: any, domainIndex: number) => (
1568-
<div key={domainIndex} className="text-sm">
1569-
<span className="font-mono">{domain.protocol.toLowerCase()}://{domain.domain}</span>
1570-
</div>
1571-
))}
1570+
{route.domains?.map((domain: any, domainIndex: number) => {
1571+
const formattedDomain = formatDomainWithPort(domain.domain, domain.port, domain.protocol);
1572+
return (
1573+
<div key={domainIndex} className="text-sm">
1574+
<span className="font-mono">{domain.protocol.toLowerCase()}://{formattedDomain}</span>
1575+
</div>
1576+
)
1577+
})}
15721578
</div>
15731579

15741580
{/* 匹配规则 */}

himarket-web/himarket-admin/src/lib/apis/typing.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export interface IAgentConfig {
99
routes?: {
1010
domains: {
1111
domain: string;
12+
port?: number;
1213
protocol: string;
1314
networkType: string;
1415
}[],
@@ -116,6 +117,7 @@ export interface IRoute {
116117

117118
export interface IDomain {
118119
domain: string;
120+
port?: number;
119121
protocol: string;
120122
networkType: string;
121123
}

himarket-web/himarket-admin/src/lib/iconUtils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export function getIconString(icon?: IProductIcon): string {
1515
}
1616

1717
if (icon.type === "BASE64") {
18-
return `data:image/png;base64,${icon.value}`;
18+
return icon.value.startsWith('data:') ? icon.value : `data:image/png;base64,${icon.value}`;
1919
}
2020

2121
return "default";

himarket-web/himarket-admin/src/lib/utils.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,3 +259,26 @@ export const copyToClipboard = async (text: string): Promise<void> => {
259259
throw error;
260260
}
261261
};
262+
263+
/**
264+
* 格式化域名和端口为完整的 host 字符串
265+
* @param domain - 域名
266+
* @param port - 端口号(可选)
267+
* @param protocol - 协议(http/https)
268+
* @returns 格式化后的 host 字符串
269+
*
270+
* 规则:
271+
* - 如果 port 为 null/undefined,只返回 domain
272+
* - 如果 port 是默认端口(http:80, https:443),只返回 domain
273+
* - 其他情况返回 domain:port
274+
*/
275+
export function formatDomainWithPort(
276+
domain: string,
277+
port: number | null | undefined,
278+
protocol: string
279+
): string {
280+
if (!port) return domain;
281+
if (protocol === 'http' && port === 80) return domain;
282+
if (protocol === 'https' && port === 443) return domain;
283+
return `${domain}:${port}`;
284+
}

himarket-web/himarket-frontend/src/lib/apis/typing.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export interface IAgentConfig {
99
routes?: {
1010
domains: {
1111
domain: string;
12+
port?: number;
1213
protocol: string;
1314
networkType: string;
1415
}[],
@@ -85,6 +86,7 @@ export interface IMCPConfig {
8586
path: string;
8687
domains: {
8788
domain: string;
89+
port?: number;
8890
protocol: string;
8991
networkType: string;
9092
}[];
@@ -116,6 +118,7 @@ export interface IRoute {
116118

117119
export interface IDomain {
118120
domain: string;
121+
port?: number;
119122
protocol: string;
120123
networkType: string;
121124
}

himarket-web/himarket-frontend/src/lib/iconUtils.tsx

Lines changed: 2 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,7 @@
1-
import { DefaultModel } from "../components/icon";
21
import type { IProductIcon } from "./apis/typing";
32

43
/**
5-
* 渲染产品图标
6-
* @param icon - 产品图标对象(可能为 null/undefined)
7-
* @param alt - 图片的 alt 文本
8-
* @param className - 额外的 CSS 类名
9-
* @returns React 元素
10-
*/
11-
export function renderProductIcon(
12-
icon?: IProductIcon,
13-
alt: string = "icon",
14-
className: string = "w-full h-full object-cover"
15-
): JSX.Element {
16-
// 如果没有 icon 或 icon 为空,使用默认图标
17-
if (!icon || !icon.value) {
18-
return <DefaultModel />;
19-
}
20-
21-
// 如果是 URL 类型
22-
if (icon.type === "URL") {
23-
return <img src={icon.value} alt={alt} className={className} />;
24-
}
25-
26-
// 如果是 BASE64 类型
27-
if (icon.type === "BASE64") {
28-
return <img src={`data:image/png;base64,${icon.value}`} alt={alt} className={className} />;
29-
}
30-
31-
// 其他情况返回默认图标
32-
return <DefaultModel />;
33-
}
34-
35-
/**
36-
* 获取图标的字符串表示(用于向后兼容)
4+
* 获取图标的字符串表示
375
* @param icon - 产品图标对象
386
* @returns 图标的字符串表示
397
*/
@@ -47,7 +15,7 @@ export function getIconString(icon?: IProductIcon): string {
4715
}
4816

4917
if (icon.type === "BASE64") {
50-
return `data:image/png;base64,${icon.value}`;
18+
return icon.value.startsWith('data:') ? icon.value : `data:image/png;base64,${icon.value}`;
5119
}
5220

5321
return "default";

himarket-web/himarket-frontend/src/lib/utils.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,4 +133,27 @@ export function copyToClipboard(text: string) {
133133
document.body.removeChild(textArea);
134134
}
135135
});
136+
}
137+
138+
/**
139+
* 格式化域名和端口为完整的 host 字符串
140+
* @param domain - 域名
141+
* @param port - 端口号(可选)
142+
* @param protocol - 协议(http/https)
143+
* @returns 格式化后的 host 字符串
144+
*
145+
* 规则:
146+
* - 如果 port 为 null/undefined,只返回 domain
147+
* - 如果 port 是默认端口(http:80, https:443),只返回 domain
148+
* - 其他情况返回 domain:port
149+
*/
150+
export function formatDomainWithPort(
151+
domain: string,
152+
port: number | null | undefined,
153+
protocol: string
154+
): string {
155+
if (!port) return domain;
156+
if (protocol === 'http' && port === 80) return domain;
157+
if (protocol === 'https' && port === 443) return domain;
158+
return `${domain}:${port}`;
136159
}

0 commit comments

Comments
 (0)