Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Y
yichengstreet-be
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
yichengstreet
yichengstreet-be
Commits
f6a66148
Commit
f6a66148
authored
Apr 20, 2026
by
lixuan
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: 需求
parent
6147cdb2
Pipeline
#147205
failed with stages
in 0 seconds
Changes
7
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
392 additions
and
0 deletions
+392
-0
ResourcesConfig.java
...main/java/com/ruoyi/framework/config/ResourcesConfig.java
+16
-0
ApiCallStatsInterceptor.java
...i/framework/interceptor/impl/ApiCallStatsInterceptor.java
+91
-0
AsyncFactory.java
...ava/com/ruoyi/framework/manager/factory/AsyncFactory.java
+36
-0
ApiCallStats.java
...in/java/com/ruoyi/system/domain/monitor/ApiCallStats.java
+181
-0
ApiCallStatsMapper.java
...a/com/ruoyi/system/mapper/monitor/ApiCallStatsMapper.java
+19
-0
ApiCallStatsMapper.xml
.../src/main/resources/mapper/monitor/ApiCallStatsMapper.xml
+29
-0
api_call_stats.sql
sql/api_call_stats.sql
+20
-0
No files found.
ruoyi-framework/src/main/java/com/ruoyi/framework/config/ResourcesConfig.java
View file @
f6a66148
...
...
@@ -14,6 +14,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import
com.ruoyi.common.config.RuoYiConfig
;
import
com.ruoyi.common.constant.Constants
;
import
com.ruoyi.framework.interceptor.RepeatSubmitInterceptor
;
import
com.ruoyi.framework.interceptor.impl.ApiCallStatsInterceptor
;
/**
* 通用配置
...
...
@@ -26,6 +27,9 @@ public class ResourcesConfig implements WebMvcConfigurer
@Autowired
private
RepeatSubmitInterceptor
repeatSubmitInterceptor
;
@Autowired
private
ApiCallStatsInterceptor
apiCallStatsInterceptor
;
@Override
public
void
addResourceHandlers
(
ResourceHandlerRegistry
registry
)
{
...
...
@@ -46,6 +50,18 @@ public class ResourcesConfig implements WebMvcConfigurer
public
void
addInterceptors
(
InterceptorRegistry
registry
)
{
registry
.
addInterceptor
(
repeatSubmitInterceptor
).
addPathPatterns
(
"/**"
);
// 接口调用统计(记录哪些接口被调用、次数、耗时,用于后续重构分析)
registry
.
addInterceptor
(
apiCallStatsInterceptor
)
.
addPathPatterns
(
"/**"
)
.
excludePathPatterns
(
"/swagger-ui/**"
,
"/swagger-resources/**"
,
"/v2/api-docs"
,
"/v3/api-docs"
,
"/webjars/**"
,
"/profile/**"
,
"/favicon.ico"
,
"/error"
);
}
/**
...
...
ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/impl/ApiCallStatsInterceptor.java
0 → 100644
View file @
f6a66148
package
com
.
ruoyi
.
framework
.
interceptor
.
impl
;
import
java.lang.reflect.Method
;
import
javax.servlet.http.HttpServletRequest
;
import
javax.servlet.http.HttpServletResponse
;
import
org.slf4j.Logger
;
import
org.slf4j.LoggerFactory
;
import
org.springframework.stereotype.Component
;
import
org.springframework.web.method.HandlerMethod
;
import
org.springframework.web.servlet.HandlerInterceptor
;
import
org.springframework.web.servlet.HandlerMapping
;
import
com.ruoyi.framework.manager.AsyncManager
;
import
com.ruoyi.framework.manager.factory.AsyncFactory
;
/**
* 接口调用统计拦截器。
* <p>
* 目标:记录"哪些接口被调用 + 调用次数 + 耗时",用于后续重构分析。
* <p>
* - 只统计能匹配到 {@link HandlerMethod} 的请求(自动跳过 404、静态资源、CORS 预检等)。
* - 在 {@code preHandle} 记录开始时间到 request attribute。
* - 在 {@code afterCompletion} 计算耗时,异步写库。
* - 所有异常吞掉仅记 log,保证绝不影响主业务请求。
*
* @author ruoyi
*/
@Component
public
class
ApiCallStatsInterceptor
implements
HandlerInterceptor
{
private
static
final
Logger
log
=
LoggerFactory
.
getLogger
(
ApiCallStatsInterceptor
.
class
);
/** 请求开始时间的 request attribute key */
private
static
final
String
START_TIME_ATTR
=
"__api_call_stats_start__"
;
@Override
public
boolean
preHandle
(
HttpServletRequest
request
,
HttpServletResponse
response
,
Object
handler
)
{
if
(
handler
instanceof
HandlerMethod
)
{
request
.
setAttribute
(
START_TIME_ATTR
,
System
.
currentTimeMillis
());
}
return
true
;
}
@Override
public
void
afterCompletion
(
HttpServletRequest
request
,
HttpServletResponse
response
,
Object
handler
,
Exception
ex
)
{
if
(!(
handler
instanceof
HandlerMethod
))
{
return
;
}
try
{
Object
startAttr
=
request
.
getAttribute
(
START_TIME_ATTR
);
long
start
=
startAttr
instanceof
Long
?
(
Long
)
startAttr
:
System
.
currentTimeMillis
();
long
cost
=
System
.
currentTimeMillis
()
-
start
;
HandlerMethod
handlerMethod
=
(
HandlerMethod
)
handler
;
Method
method
=
handlerMethod
.
getMethod
();
String
handlerClass
=
method
.
getDeclaringClass
().
getName
();
String
handlerMethodName
=
method
.
getName
();
String
httpMethod
=
request
.
getMethod
();
String
urlPattern
=
resolveUrlPattern
(
request
);
boolean
isError
=
ex
!=
null
||
response
.
getStatus
()
>=
500
;
AsyncManager
.
me
().
execute
(
AsyncFactory
.
recordApiCallStats
(
httpMethod
,
urlPattern
,
handlerClass
,
handlerMethodName
,
cost
,
isError
));
}
catch
(
Exception
e
)
{
log
.
warn
(
"[ApiCallStats] record failed: {}"
,
e
.
getMessage
());
}
}
/**
* 优先使用 Spring 匹配到的 URL pattern(如 /xxx/{id}),
* 匹配不到则回退到原始 requestURI。
*/
private
String
resolveUrlPattern
(
HttpServletRequest
request
)
{
Object
best
=
request
.
getAttribute
(
HandlerMapping
.
BEST_MATCHING_PATTERN_ATTRIBUTE
);
if
(
best
instanceof
String
&&
!((
String
)
best
).
isEmpty
())
{
return
(
String
)
best
;
}
return
request
.
getRequestURI
();
}
}
ruoyi-framework/src/main/java/com/ruoyi/framework/manager/factory/AsyncFactory.java
View file @
f6a66148
...
...
@@ -10,8 +10,10 @@ import com.ruoyi.common.utils.StringUtils;
import
com.ruoyi.common.utils.ip.AddressUtils
;
import
com.ruoyi.common.utils.ip.IpUtils
;
import
com.ruoyi.common.utils.spring.SpringUtils
;
import
com.ruoyi.system.domain.monitor.ApiCallStats
;
import
com.ruoyi.system.domain.system.SysLogininfor
;
import
com.ruoyi.system.domain.system.SysOperLog
;
import
com.ruoyi.system.mapper.monitor.ApiCallStatsMapper
;
import
com.ruoyi.system.service.system.ISysLogininforService
;
import
com.ruoyi.system.service.system.ISysOperLogService
;
import
eu.bitwalker.useragentutils.UserAgent
;
...
...
@@ -99,4 +101,38 @@ public class AsyncFactory
}
};
}
/**
* 接口调用统计(按 http_method + url_pattern 聚合 upsert)。
* <p>仅用于记录"哪些接口被调用 + 调用次数 + 耗时",服务后续重构分析。
*/
public
static
TimerTask
recordApiCallStats
(
final
String
httpMethod
,
final
String
urlPattern
,
final
String
handlerClass
,
final
String
handlerMethod
,
final
long
costMs
,
final
boolean
isError
)
{
return
new
TimerTask
()
{
@Override
public
void
run
()
{
try
{
ApiCallStats
stats
=
new
ApiCallStats
();
stats
.
setHttpMethod
(
httpMethod
);
stats
.
setUrlPattern
(
urlPattern
);
stats
.
setHandlerClass
(
handlerClass
);
stats
.
setHandlerMethod
(
handlerMethod
);
stats
.
setLastCostMs
(
costMs
);
stats
.
setErrorCount
(
isError
?
1L
:
0L
);
SpringUtils
.
getBean
(
ApiCallStatsMapper
.
class
).
upsert
(
stats
);
}
catch
(
Exception
e
)
{
// 统计失败不影响主流程
LoggerFactory
.
getLogger
(
AsyncFactory
.
class
)
.
warn
(
"[ApiCallStats] async upsert failed: {}"
,
e
.
getMessage
());
}
}
};
}
}
ruoyi-system/src/main/java/com/ruoyi/system/domain/monitor/ApiCallStats.java
0 → 100644
View file @
f6a66148
package
com
.
ruoyi
.
system
.
domain
.
monitor
;
import
java.util.Date
;
/**
* 接口调用统计(按 http_method + url_pattern 聚合)
*
* 用于后续重构分析:哪些接口还在用、调用频率、耗时分布。
*
* @author ruoyi
*/
public
class
ApiCallStats
{
private
Long
id
;
/** 请求方式 GET/POST/PUT/DELETE */
private
String
httpMethod
;
/** 接口路径模式(带占位符,如 /xxx/{id}) */
private
String
urlPattern
;
/** 处理器类全限定名 */
private
String
handlerClass
;
/** 处理器方法名 */
private
String
handlerMethod
;
/** 累计调用次数 */
private
Long
callCount
;
/** 累计耗时(ms) */
private
Long
totalCostMs
;
/** 最大耗时(ms) */
private
Long
maxCostMs
;
/** 最小耗时(ms) */
private
Long
minCostMs
;
/** 最近一次耗时(ms) */
private
Long
lastCostMs
;
/** 异常次数 */
private
Long
errorCount
;
/** 首次调用时间 */
private
Date
firstCallTime
;
/** 最近调用时间 */
private
Date
lastCallTime
;
public
Long
getId
()
{
return
id
;
}
public
void
setId
(
Long
id
)
{
this
.
id
=
id
;
}
public
String
getHttpMethod
()
{
return
httpMethod
;
}
public
void
setHttpMethod
(
String
httpMethod
)
{
this
.
httpMethod
=
httpMethod
;
}
public
String
getUrlPattern
()
{
return
urlPattern
;
}
public
void
setUrlPattern
(
String
urlPattern
)
{
this
.
urlPattern
=
urlPattern
;
}
public
String
getHandlerClass
()
{
return
handlerClass
;
}
public
void
setHandlerClass
(
String
handlerClass
)
{
this
.
handlerClass
=
handlerClass
;
}
public
String
getHandlerMethod
()
{
return
handlerMethod
;
}
public
void
setHandlerMethod
(
String
handlerMethod
)
{
this
.
handlerMethod
=
handlerMethod
;
}
public
Long
getCallCount
()
{
return
callCount
;
}
public
void
setCallCount
(
Long
callCount
)
{
this
.
callCount
=
callCount
;
}
public
Long
getTotalCostMs
()
{
return
totalCostMs
;
}
public
void
setTotalCostMs
(
Long
totalCostMs
)
{
this
.
totalCostMs
=
totalCostMs
;
}
public
Long
getMaxCostMs
()
{
return
maxCostMs
;
}
public
void
setMaxCostMs
(
Long
maxCostMs
)
{
this
.
maxCostMs
=
maxCostMs
;
}
public
Long
getMinCostMs
()
{
return
minCostMs
;
}
public
void
setMinCostMs
(
Long
minCostMs
)
{
this
.
minCostMs
=
minCostMs
;
}
public
Long
getLastCostMs
()
{
return
lastCostMs
;
}
public
void
setLastCostMs
(
Long
lastCostMs
)
{
this
.
lastCostMs
=
lastCostMs
;
}
public
Long
getErrorCount
()
{
return
errorCount
;
}
public
void
setErrorCount
(
Long
errorCount
)
{
this
.
errorCount
=
errorCount
;
}
public
Date
getFirstCallTime
()
{
return
firstCallTime
;
}
public
void
setFirstCallTime
(
Date
firstCallTime
)
{
this
.
firstCallTime
=
firstCallTime
;
}
public
Date
getLastCallTime
()
{
return
lastCallTime
;
}
public
void
setLastCallTime
(
Date
lastCallTime
)
{
this
.
lastCallTime
=
lastCallTime
;
}
}
ruoyi-system/src/main/java/com/ruoyi/system/mapper/monitor/ApiCallStatsMapper.java
0 → 100644
View file @
f6a66148
package
com
.
ruoyi
.
system
.
mapper
.
monitor
;
import
com.ruoyi.system.domain.monitor.ApiCallStats
;
/**
* 接口调用统计 数据层
*
* @author ruoyi
*/
public
interface
ApiCallStatsMapper
{
/**
* 新增或更新一条接口调用统计。
* <p>通过 (http_method, url_pattern) 唯一键做 upsert:
* 首次调用则插入,后续调用累加 call_count / total_cost_ms,
* 更新 max/min/last_cost_ms、error_count、last_call_time。
*/
void
upsert
(
ApiCallStats
stats
);
}
ruoyi-system/src/main/resources/mapper/monitor/ApiCallStatsMapper.xml
0 → 100644
View file @
f6a66148
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper
namespace=
"com.ruoyi.system.mapper.monitor.ApiCallStatsMapper"
>
<insert
id=
"upsert"
parameterType=
"ApiCallStats"
>
INSERT INTO sys_api_call_stats (
http_method, url_pattern, handler_class, handler_method,
call_count, total_cost_ms, max_cost_ms, min_cost_ms, last_cost_ms,
error_count, first_call_time, last_call_time
) VALUES (
#{httpMethod}, #{urlPattern}, #{handlerClass}, #{handlerMethod},
1, #{lastCostMs}, #{lastCostMs}, #{lastCostMs}, #{lastCostMs},
#{errorCount}, NOW(), NOW()
)
ON DUPLICATE KEY UPDATE
call_count = call_count + 1,
total_cost_ms = total_cost_ms + VALUES(last_cost_ms),
max_cost_ms = GREATEST(max_cost_ms, VALUES(last_cost_ms)),
min_cost_ms = LEAST(min_cost_ms, VALUES(last_cost_ms)),
last_cost_ms = VALUES(last_cost_ms),
error_count = error_count + VALUES(error_count),
handler_class = VALUES(handler_class),
handler_method = VALUES(handler_method),
last_call_time = NOW()
</insert>
</mapper>
sql/api_call_stats.sql
0 → 100644
View file @
f6a66148
-- 接口调用统计表(用于后续重构分析:哪些接口在用、调用频率、耗时)
-- 按 (http_method, url_pattern) 聚合,一个接口一行,upsert 更新
DROP
TABLE
IF
EXISTS
`sys_api_call_stats`
;
CREATE
TABLE
`sys_api_call_stats`
(
`id`
bigint
(
20
)
NOT
NULL
AUTO_INCREMENT
,
`http_method`
varchar
(
10
)
NOT
NULL
COMMENT
'请求方式 GET/POST/PUT/DELETE/...'
,
`url_pattern`
varchar
(
255
)
NOT
NULL
COMMENT
'接口路径模式(带占位符,如 /xxx/{id})'
,
`handler_class`
varchar
(
255
)
DEFAULT
NULL
COMMENT
'处理器类全限定名'
,
`handler_method`
varchar
(
128
)
DEFAULT
NULL
COMMENT
'处理器方法名'
,
`call_count`
bigint
(
20
)
NOT
NULL
DEFAULT
'0'
COMMENT
'累计调用次数'
,
`total_cost_ms`
bigint
(
20
)
NOT
NULL
DEFAULT
'0'
COMMENT
'累计耗时(ms),用于算平均'
,
`max_cost_ms`
bigint
(
20
)
NOT
NULL
DEFAULT
'0'
COMMENT
'最大耗时(ms)'
,
`min_cost_ms`
bigint
(
20
)
NOT
NULL
DEFAULT
'0'
COMMENT
'最小耗时(ms)'
,
`last_cost_ms`
bigint
(
20
)
NOT
NULL
DEFAULT
'0'
COMMENT
'最近一次耗时(ms)'
,
`error_count`
bigint
(
20
)
NOT
NULL
DEFAULT
'0'
COMMENT
'异常调用次数'
,
`first_call_time`
datetime
DEFAULT
NULL
COMMENT
'首次调用时间'
,
`last_call_time`
datetime
DEFAULT
NULL
COMMENT
'最近调用时间'
,
PRIMARY
KEY
(
`id`
),
UNIQUE
KEY
`uk_method_url`
(
`http_method`
,
`url_pattern`
)
)
ENGINE
=
InnoDB
DEFAULT
CHARSET
=
utf8mb4
COMMENT
=
'接口调用统计'
;
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment