SpringBoot整合Echarts實現(xiàn)數(shù)據(jù)大屏
效果展示
MySQL
建表
CREATE TABLE `access_log` ( `access_log_id` bigint NOT NULL AUTO_INCREMENT, `access_time` datetime NOT NULL COMMENT '訪問時間', `access_ip` varchar(30) NOT NULL COMMENT '訪問IP', `api_group` varchar(50) NOT NULL DEFAULT '默認' COMMENT '接口分組', `req_url` varchar(100) NOT NULL COMMENT '請求URL', `req_method` varchar(10) NOT NULL COMMENT '請求方式', `os` varchar(100) NULL DEFAULT NULL COMMENT '操作系統(tǒng)', `browser` varchar(50) NULL DEFAULT NULL COMMENT '瀏覽器', `lsp` varchar(15) NULL DEFAULT NULL COMMENT '運營商', `country` varchar(15) NULL DEFAULT NULL COMMENT '國家', `province` varchar(15) NULL DEFAULT NULL COMMENT '省', `city` varchar(15) NULL DEFAULT NULL COMMENT '城市', PRIMARY KEY (`access_log_id`) ) COMMENT='訪問日志表';
后端
POJO實體
@Data @TableName(value = "access_log") public class AccessLog implements Serializable { private static final long serialVersionUID = 1L; /** * 訪問日志ID */ @TableId(type = IdType.AUTO) private Long accessLogId; /** * 訪問時間 */ private Date accessTime; /** * 訪問IP */ private String accessIp; /** * 接口分組 */ private String apiGroup; /** * 請求URL */ private String reqUrl; /** * 請求方式 */ private String reqMethod; /** * 操作系統(tǒng) */ private String os; /** * 瀏覽器 */ private String browser; /** * 運營商 */ private String lsp; /** * 國家 */ private String country; /** * 省 */ private String province; /** * 城市 */ private String city; }
Mapper接口
@Repository public interface AccessLogMapper extends BaseMapper<AccessLog> { }
自定義注解
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Log { /** * 給接口分組 */ String apiGroup() default "默認"; }
案例:
@Log(apiGroup = "文章模塊") @RestController @RequestMapping("/article") public class ArticleController { ...... }
攔截器
ps:https://api.vvhan.com/api/getIpInfo?ip=[你的IP],這個網(wǎng)址是一個免費獲取國家、省、市、運營商的地址
當然這種對IP地址的解析應(yīng)該是放在定時任務(wù)中,每天晚上定時解析日志IP,如果解析IP的API掛了,接口會受到影響,我這里只是為了方便寫在這里
@Slf4j @Component public class AccessLogInterceptor implements HandlerInterceptor { @Autowired private AccessLogMapper accessLogMapper; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (!(handler instanceof HandlerMethod)) { return true; } try { // 獲取客戶端真是IP地址,這種網(wǎng)上很多現(xiàn)成代碼 String accessIp = NetUtil.getRemoteHost(request); // 獲取User-Agent String requestUserAgent = request.getHeader("User-Agent"); // 獲取瀏覽器用戶標識 UserAgent userAgent = UserAgentUtil.parse(requestUserAgent); HandlerMethod handlerMethod = (HandlerMethod) handler; Log logAnnotation = handlerMethod.getMethod().getDeclaringClass().getAnnotation(Log.class); AccessLog accessLog = new AccessLog(); accessLog.setAccessIp(accessIp); accessLog.setAccessTime(new Date()); if (logAnnotation != null) { accessLog.setApiGroup(logAnnotation.apiGroup()); } accessLog.setReqUrl(request.getRequestURI()); accessLog.setReqMethod(request.getMethod()); accessLog.setOs(userAgent.getOs().getName()); accessLog.setBrowser(userAgent.getBrowser().getName()); // 解析IP try { String ipParseStr = HttpUtil.get("https://api.vvhan.com/api/getIpInfo?ip=" + accessIp); JSONObject ipParseJson = JSONUtil.parseObj(ipParseStr); if (ipParseJson.getBool("success")) { JSONObject infoJson = ipParseJson.getJSONObject("info"); accessLog.setLsp(infoJson.getStr("lsp")); accessLog.setCountry(infoJson.getStr("country")); accessLog.setProvince(infoJson.getStr("prov")); accessLog.setCity(infoJson.getStr("city")); } } catch (Exception e) { accessLog.setLsp("未知"); accessLog.setCountry("未知"); accessLog.setProvince("未知"); accessLog.setCity("未知"); } accessLogMapper.insert(accessLog); } catch (Exception e) { log.error("", e); } return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { HandlerInterceptor.super.postHandle(request, response, handler, modelAndView); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { HandlerInterceptor.super.afterCompletion(request, response, handler, ex); } }
注冊攔截器
@Configuration public class WebMVCConfig implements WebMvcConfigurer { @Autowired private AccessLogInterceptor accessLogInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { // 注冊全局日志攔截器 registry.addInterceptor(accessLogInterceptor) .addPathPatterns("/**") .excludePathPatterns("/static/**") .excludePathPatterns("/error"); // 其他攔截器...... } }
測試
到這里就完成一個簡單的全局日志攔截器了,隨便發(fā)幾個請求測試一下,成功記錄入庫!
進階——整合Echarts實現(xiàn)數(shù)據(jù)大屏
數(shù)據(jù)VO實體
瀏覽器訪問占比情況VO
@Data public class AccessBrowserGroupVo { private String browser; private Integer count; }
運營商訪問占比情況VO
@Data public class AccessLspGroupVo { private String lsp; private Integer count; }
各省份訪問情況VO
@Data public class AccessProvinceGroupVo { private String province; private Integer count; }
每天訪問情況VO
@Data public class AccessTimeGroupVo { private String accessTime; private Integer count; }
查詢SQL
瀏覽器訪問統(tǒng)計
<select id="countBrowserGroupAccess" resultType="com.xiaoyuan.common.vo.logs.AccessBrowserGroupVo"> select browser, count(*) as count from access_log group by browser </select>
運營商訪問統(tǒng)計
<select id="countLspGroupAccess" resultType="com.xiaoyuan.common.vo.logs.AccessLspGroupVo"> select lsp, count(*) as count from access_log group by lsp </select>
各省份訪問統(tǒng)計
<select id="countProvinceGroupAccess" resultType="com.xiaoyuan.common.vo.logs.AccessProvinceGroupVo"> select province, count(*) as count from access_log group by province order by count desc limit 15 </select>
近15天內(nèi)訪問統(tǒng)計
<select id="countTimeGroupAccess" resultType="com.xiaoyuan.common.vo.logs.AccessTimeGroupVo"> SELECT date_match.date_c as access_time, IFNULL( count, 0 ) as count FROM ( SELECT DATE_FORMAT( @now := date_sub( @now, INTERVAL 1 DAY ), '%Y-%m-%d' ) AS date_c FROM ( SELECT @now := date_add( CURDATE(), INTERVAL 1 DAY ) FROM access_log LIMIT 15 ) date_match ORDER BY date_c ) date_match LEFT JOIN ( SELECT DATE_FORMAT( access_time, '%Y-%m-%d' ) AS access_time, count(*) AS count FROM access_log WHERE access_time >= ( SELECT date_sub( curdate(), INTERVAL 15 DAY )) GROUP BY DATE_FORMAT( access_time, '%Y-%m-%d' ) ) acc ON acc.access_time = date_match.date_c </select>
后端業(yè)務(wù)
封裝統(tǒng)一接口返回
/** * FileName: R * Author: 小袁 * Date: 2022/3/12 12:23 * Description: 統(tǒng)一結(jié)果返回的類 */ @Data public class R<T> { private Boolean success; private Integer code; private String message; private T data; // 成功靜態(tài)方法 public static <T> R<T> success() { R<T> r = new R<>(); r.setSuccess(true); r.setCode(HttpStatusEnum.SUCCESS.getCode()); r.setMessage(HttpStatusEnum.SUCCESS.getName()); return r; } public static <T> R<T> success(String message) { R<T> r = new R<>(); r.setSuccess(true); r.setCode(HttpStatusEnum.SUCCESS.getCode()); r.message(message); return r; } public static <T> R<T> success(T object) { R<T> r = new R<>(); r.setData(object); r.setSuccess(true); r.setCode(HttpStatusEnum.SUCCESS.getCode()); r.setMessage(HttpStatusEnum.SUCCESS.getName()); return r; } public static <T> R<T> success(String msg, T object) { R<T> r = new R<>(); r.setData(object); r.setCode(HttpStatusEnum.SUCCESS.getCode()); r.setMessage(msg); return r; } // 失敗靜態(tài)方法 public static <T> R<T> fail() { R<T> r = new R<>(); r.setSuccess(false); r.setCode(HttpStatusEnum.FAIL.getCode()); r.setMessage(HttpStatusEnum.FAIL.getName()); return r; } public static <T> R<T> fail(String msg) { R<T> r = new R<>(); r.setSuccess(false); r.setCode(HttpStatusEnum.FAIL.getCode()); r.setMessage(msg); return r; } public static <T> R<T> fail(HttpStatusEnum httpStatusEnum) { R<T> r = new R<>(); r.setSuccess(false); r.setCode(httpStatusEnum.getCode()); r.setMessage(httpStatusEnum.getName()); return r; } public R<T> message(String message){ this.setMessage(message); return this; } public R<T> code(Integer code){ this.setCode(code); return this; } public R<T> data(T data){ this.setData(data); return this; } }
封裝客戶端響應(yīng)碼
/** * FileName: Code * Author: 小袁 * Date: 2022/5/1 23:29 * Description: 客戶端響應(yīng)狀態(tài)碼 */ public enum HttpStatusEnum implements BaseCodeEnum { SUCCESS(200, "成功"), FAIL(20001, "失敗"), INTERNAL_SERVER_ERROR(500, "服務(wù)器異常"), private final Integer code; private final String name; HttpStatusEnum(int code, String msg) { this.code = code; this.name = msg; } @Override public Integer getCode() { return this.code; } @Override public String getName() { return this.name; } }
Mapper接口
@Repository public interface AccessLogMapper extends BaseMapper<AccessLog> { List<AccessLspGroupVo> countLspGroupAccess(); List<AccessBrowserGroupVo> countBrowserGroupAccess(); List<AccessProvinceGroupVo> countProvinceGroupAccess(); List<AccessTimeGroupVo> countTimeGroupAccess(); }
Service接口
public interface AccessLogService extends IService<AccessLog> { List<AccessLspGroupVo> countLspGroupAccess(); List<AccessBrowserGroupVo> countBrowserGroupAccess(); List<AccessProvinceGroupVo> countProvinceGroupAccess(); List<AccessTimeGroupVo> countTimeGroupAccess(); }
Service實現(xiàn)類
@Slf4j @Service public class AccessLogServiceImpl extends ServiceImpl<AccessLogMapper, AccessLog> implements AccessLogService { @Override public List<AccessLspGroupVo> countLspGroupAccess() { return this.baseMapper.countLspGroupAccess(); } @Override public List<AccessBrowserGroupVo> countBrowserGroupAccess() { return this.baseMapper.countBrowserGroupAccess(); } @Override public List<AccessProvinceGroupVo> countProvinceGroupAccess() { return this.baseMapper.countProvinceGroupAccess(); } @Override public List<AccessTimeGroupVo> countTimeGroupAccess() { return this.baseMapper.countTimeGroupAccess(); } }
Controller接口
@RestController @RequestMapping("/stat/access") public class AccessStatController { @Autowired private AccessLogService accessLogService; /** * 查詢15天內(nèi)的訪問次數(shù)情況-折線圖 */ @GetMapping("/query_line_by_day") public R<List<AccessTimeGroupVo>> queryAccessLogByTimeGroup() { return R.success(accessLogService.countTimeGroupAccess()); } /** * 查詢省份訪問占比-柱形圖 */ @GetMapping("/query_col_by_province") public R<List<AccessProvinceGroupVo>> queryAccessLogByProvinceGroup() { return R.success(accessLogService.countProvinceGroupAccess()); } /** * 查詢運營商訪問占比-餅圖 */ @GetMapping("/query_pie_by_lsp") public R<List<AccessLspGroupVo>> queryAccessLogByLspGroup() { return R.success(accessLogService.countLspGroupAccess()); } /** * 查詢?yōu)g覽器訪問占比-餅圖 */ @GetMapping("/query_pie_by_browser") public R<List<AccessBrowserGroupVo>> queryAccessLogByBrowserGroup() { return R.success(accessLogService.countBrowserGroupAccess()); } }
前端配置
安裝axios、echarts
npm install axios npm install echarts
封裝request
import axios from 'axios' import { Message, MessageBox,} from 'element-ui' import store from '../store' import { getToken } from '@/utils/auth' import router from '@/router' // 創(chuàng)建axios實例 const service = axios.create({ baseURL: process.env.BASE_API, // api 的 base_url // timeout: 5000 // 請求超時時間 }) // request攔截器 service.interceptors.request.use( config => { if (store.getters.token) { config.headers['token'] = getToken() } return config }, error => { // Do something with request error console.log(error) // for debug Promise.reject(error) } ) // response 攔截器 service.interceptors.response.use( response => { /** * code為非200是拋錯 可結(jié)合自己業(yè)務(wù)進行修改 */ const res = response.data const url = response.config.url if (res.code !== 200) { if (url.indexOf("/login") < 0 && res.code === 40005) { store.dispatch('FedLogOut').then(() => { router.push(`/login`) }) Message({ message: res.message, type: 'warning', duration: 2 * 1000, }) return Promise.resolve(res) }else if (res.code >= 40000) { Message({ message: res.message, type: 'error', duration: 3 * 1000 }) return Promise.resolve(res) }else { Message({ message: res.message, type: 'error', duration: 5 * 1000 }) return Promise.reject(new Error(res.message || 'Error')) } } else { return res } }, error => { Message({ message: error.message, type: 'error', duration: 5 * 1000 }) return Promise.reject(error) } ) export default service
定義API
import request from "../../utils/request"; export default { getAccessStatByTime() { return request({ url: '/stat/access/query_line_by_day', method: 'get' }) }, getAccessStatByProvince() { return request({ url: '/stat/access/query_col_by_province', method: 'get' }) }, getAccessStatByLsp() { return request({ url: '/stat/access/query_pie_by_lsp', method: 'get' }) }, getAccessStatByBrowser() { return request({ url: '/stat/access/query_pie_by_browser', method: 'get' }) }, }
封裝echarts
<template> <div :id="id" :class="className" :style="{ height: height, width: width }" /> </template> <script> import * as echarts from 'echarts' export default { name: 'echart', props: { className: { type: String, default: 'chart' }, id: { type: String, default: 'chart' }, width: { type: String, default: '100%' }, height: { type: String, default: '2.5rem' }, options: { type: Object, default: ()=>({}) } }, data () { return { chart: null } }, watch: { options: { handler (options) { // 設(shè)置true清空echart緩存 this.chart.setOption(options, true) }, deep: true } }, mounted () { // echarts.registerTheme('tdTheme', tdTheme); // 覆蓋默認主題 this.initChart(); }, beforeDestroy () { this.chart.dispose() this.chart = null }, methods: { initChart () { // 初始化echart this.chart = echarts.init(this.$el) this.chart.setOption(this.options, true) } } } </script> <style> body { margin: 0; padding: 0; } </style>
餅圖-瀏覽器訪問占比
<template> <div> <Echart :options="options" id="lspEcharts" height="300px" width="100%"/> </div> </template> <script> import statAccess from "@/api/stat/statAccess"; import Echart from "@/components/Echart/index.vue"; export default { components: { Echart }, data() { return { options: {}, lspData: [] } }, methods: { initData() { statAccess.getAccessStatByBrowser().then(res => { this.lspData = res.data.map(obj => { return { name: obj.browser, value: obj.count } }) this.executeDraw() }) }, executeDraw() { this.options = { title: { text: '瀏覽器訪問占比', left: 'center', textStyle: { color: '#FDF5E6' } }, tooltip: { trigger: 'item' }, legend: { orient: 'vertical', left: 'left', top: '20%', textStyle: { color: '#FDF5E6' } }, series: [ { name: '訪問次數(shù)', type: 'pie', radius: '90%', top: '20%', data: this.lspData, label: { color: '#FDF5E6' }, emphasis: { itemStyle: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0, 0, 0, 0.5)' } } } ] } } }, mounted() { this.initData() } } </script>
餅圖-運營商訪問占比
<template> <div> <Echart :options="options" id="lspEcharts" height="300px" width="100%"/> </div> </template> <script> import statAccess from "@/api/stat/statAccess"; import Echart from "@/components/Echart/index.vue"; export default { components: { Echart }, data() { return { options: {}, lspData: [] } }, methods: { initData() { statAccess.getAccessStatByLsp().then(res => { this.lspData = res.data.map(obj => { return { name: obj.lsp, value: obj.count } }) this.executeDraw() }) }, executeDraw() { this.options = { title: { text: '運營商訪問占比', left: 'center', textStyle: { color: '#FDF5E6' } }, tooltip: { trigger: 'item' }, legend: { orient: 'vertical', left: 'left', top: '20%', textStyle: { color: '#FDF5E6' } }, series: [ { name: '訪問次數(shù)', type: 'pie', radius: '90%', top: '20%', data: this.lspData, label: { color: '#FDF5E6' }, emphasis: { itemStyle: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0, 0, 0, 0.5)' } } } ] } } }, mounted() { this.initData() } } </script>
折線圖-每天訪問量情況
<template> <div> <Echart :options="options" id="timeEcharts" height="400px" width="100%"/> </div> </template> <script> import statAccess from "@/api/stat/statAccess"; import Echart from "@/components/Echart/index.vue"; export default { components: { Echart }, data() { return { options: {}, xAxis: [], yAxis: [] } }, methods: { initData() { statAccess.getAccessStatByTime().then(res => { let x = [] let y = [] for (let i = 0; i < res.data.length; i++) { x.push(res.data[i].accessTime) y.push(res.data[i].count) } this.xAxis = x this.yAxis = y this.executeDraw() }) }, executeDraw() { this.options = { title: { show: true, text: '小袁博客15天內(nèi)訪問情況統(tǒng)計', left: 'center', textStyle: { color: '#FDF5E6' } }, legend: { show: true, left: '1%', textStyle: { color: '#FDF5E6' } }, tooltip: { trigger: 'axis', axisPointer: { type: 'line' } }, grid:{ left:"1%", right:"1%", bottom:"1%", containLabel:true, }, xAxis: { type: 'category', data: this.xAxis, boundaryGap: ['5%', '5%',],//坐標軸兩邊留白 axisLabel: { color: '#FDF5E6' }, axisLine: {//坐標軸 lineStyle:{ opacity: 0.01,//設(shè)置透明度就可以控制顯示不顯示 }, }, splitLine: {//網(wǎng)格線 lineStyle:{ color: '#eeeeee', }, }, axisTick: {//刻度線 show: false,//去掉刻度線 }, }, yAxis: { type: 'value', name:'次 ',//是基于Y軸線對齊,用空格站位讓坐標軸名稱與刻度名稱對齊 axisLabel: { color: '#eee' }, nameTextStyle: { color:'#444e65', align:'left',//文字水平對齊方式 verticalAlign:'middle',//文字垂直對齊方式 }, axisTick: {//刻度線 show: false,//去掉刻度線 }, axisLine: {//坐標軸線 lineStyle:{ opacity: 0,//透明度為0 }, }, splitLine: {//網(wǎng)格線 show: true,//網(wǎng)格線 lineStyle:{ color: 'rgba(211, 211, 211, 0.5)', }, }, }, series: [ { data: this.yAxis, smooth: true, type: 'line', name: '訪問次數(shù)', itemStyle: {//折線拐點標志的樣式。 normal: { color: '#98F5FF', }, }, } ] } } }, mounted() { this.initData() } } </script>
柱狀圖-各省份訪問情況
<template> <div> <Echart :options="options" id="lspEcharts" height="300px" width="100%"/> </div> </template> <script> import statAccess from "@/api/stat/statAccess"; import Echart from "@/components/Echart/index.vue"; export default { components: { Echart }, data() { return { options: {}, axis: [], series: [] } }, methods: { initData() { statAccess.getAccessStatByProvince().then(res => { let x = [] let y = [] for (let i = 0; i < res.data.length; i++) { x.push(res.data[i].province) y.push(res.data[i].count) } this.axis = x this.series = y this.executeDraw() }) }, executeDraw() { this.options = { tooltip: {}, title: { text: '各城市訪問情況占比', left: 'center', textStyle: { color: '#FDF5E6' } }, grid:{ left: "1%", right: "1%", bottom: "1%", containLabel: true, }, legend: { show: true, left: '1%', textStyle: { color: '#FDF5E6' } }, xAxis: { data: this.axis, axisLine: { show: false, }, axisLabel: { color: '#FDF5E6' } }, yAxis: { // 網(wǎng)格樣式 splitLine: { show: false, }, axisLabel: { color: '#FDF5E6' } }, series: [{ name: '訪問量', type: 'bar', data: this.series, barWidth: 15, itemStyle: { color: { type:'linear', x: 0, y: 0, x2: 0, y2: 1, colorStops: [ { offset: 0, color: 'rgba(255, 130, 71, 1)', }, { offset: 1, color: 'rgba(255, 130, 71, 0.5)', }, ], globaCoord: false, }, barBorderRadius: [5, 5, 0, 0], // (順時針左上,右上,右下,左下) }, }], } } }, mounted() { this.initData() } } </script>
首頁
直接引入
<template> <div class="dashboard-container"> <div class="dashboard-bg"></div> <div class="echart-div"> <el-row :gutter="1" class="item"> <el-col :span="8"> <BrowserPie></BrowserPie> </el-col> <el-col :span="16"> <TimeLine></TimeLine> </el-col> </el-row> <el-row style="margin-top: 35px" class="item"> <el-col span="8"> <LspPie></LspPie> </el-col> <el-col span="16"> <ProvinceCol></ProvinceCol> </el-col> </el-row> </div> </div> </template> <script> import LspPie from "./components/LspPie.vue"; import ProvinceCol from "./components/ProvinceCol.vue"; import BrowserPie from "./components/BrowserPie.vue"; import TimeLine from "./components/TimeLine.vue"; export default { components: { LspPie, ProvinceCol, BrowserPie, TimeLine }, name: 'home', data() { return { } }, mounted() { }, methods: { } } </script> <style rel="stylesheet/scss" lang="scss" scoped> .dashboard { &-container { .echart-div { padding: 30px; } } &-text { font-size: 22px; line-height: 46px; } .personal { .box-card-header { position: relative; height: 220px; img { width: 100%; height: 100%; transition: all 0.2s linear; &:hover { transform: scale(1.1, 1.1); filter: contrast(130%); } } } } } .dashboard { &-bg { background-image: url('../../assets/img/home_bg.png'); background-size: cover; background-repeat: no-repeat; background-attachment: fixed; /* 可選,固定背景圖片 */ background-position: center; /* 可選,設(shè)置背景圖片位置 */ position: absolute; top: 0; left: 0; height: 100%; width: 100%; filter: blur(1px); } } </style>
結(jié)束
到這里整篇文章就結(jié)束了,我們重新捋一下整個流程
- 全局過濾器攔截請求
- 對請求信息進行解析入庫
- 定義API接口
- 前端引入axios、echarts
- 編寫圖形Vue組件
- 前后端數(shù)據(jù)交互
以上就是SpringBoot整合Echarts實現(xiàn)數(shù)據(jù)大屏的詳細內(nèi)容,更多關(guān)于SpringBoot Echarts數(shù)據(jù)大屏的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
springcloud下hibernate本地化方言配置方式
這篇文章主要介紹了springcloud下hibernate本地化方言配置方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-09-09springcloud?feign服務(wù)之間調(diào)用,date類型轉(zhuǎn)換錯誤的問題
這篇文章主要介紹了springcloud?feign服務(wù)之間調(diào)用,date類型轉(zhuǎn)換錯誤的問題及解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03Spring?Boot中KafkaListener的介紹、原理和使用方法案例詳解
本文介紹了Spring Boot中 @KafkaListener 注解的介紹、原理和使用方法,通過本文的介紹,我們希望讀者能夠更好地理解Spring Boot中 @KafkaListener 注解的使用方法,并在項目中更加靈活地應(yīng)用2023-09-09