【广告投放系统】头条可视化投放平台vue3+element-plus+vite落地历程和心得体会
前言
hallo,又是许久未见,昨天也是正式把公司内部的广告投放平台暂时落地,我也即将离开待了两年多的地方。言归正传,由于头条广告后台的升级改版,因此为了满足内部投放需求,做了一个可视化的投放平台,时间大概是1个星期多,然后我会阐述我是如何在最短的时间内完成这个平台的。
需求梳理
-
时间尽可能的短
为了实现这一点,可能暂时不考虑非常华丽的UI设计和变化,对于后台系统来说,快速上手和简单易用是最重要的 -
技术选型
实际上在头条后台没有改版之前,公司内部使用的是React18那一套玩法,我也亲身参与和维护过,那么之所以选择vue3。
一方面是:vue3发展到现在跟React的差距并不是特别大,特别在复杂的计算场景上面。
二方面是:兼顾公司内部前端的技术栈考虑,公司主要以vue为主,那么为了之后的维护和更新,vue技术栈最合适不过
三方面是:旧版广告系统迭代了非常多版本,既不满足新版需求,其次是数据维护极其困难,数据污染严重,规范和可靠性不强,因此二次开发比重新创作难度更大。 -
调试简单
-
易于维护
设计思路
借鉴于上一代广告投放系统的痛点,其实最难的就是数据的回显和数据的汇总,导致出现的各种问题。因此我给自己提出以下几点原则:
-
不违背单向数据源原则
数据最终的处理,应该由顶层决定,数据可以获取,但是修改必须经过顶层同意。
-
数据驱动
为了解决回显问题,实际上迭代到后期,组件和组件之间嵌套的层级可能会比较深,那么就需要一个公共数据管理库去统一管理数据,所有的数据在初始化的时候,可以通过接口获取数据之后,存储到数据管理库去统一处理,再在各个组件中做监听,数据变化驱动视图变化。
方案选型
- 公共状态管理库(pinia)官方支持,社区活跃
- 请求库(axios),请求响应拦截,二次封装,自定义参数
- 打包构建工具(vite),本地代理,插件丰富,官方支持
- 组件库(element-plus),支持企业级系统搭建,适配vue3
- 路由管理(vue-router)
- 调试工具(code-inspector-plugin)支持多种前端技术框架和构建工具,ide支持,当组件嵌套深层次的时候,能通过快捷键快速定位代码位置
项目搭建
那么初次创建项目的时候,所有的资源都会在src下面去创作,结构大致如下,我也借鉴了在Flutter端和在React端的经验。
目录解析
- 所有的请求都在api目录下面,不同的请求api类型区分不同的文件,比如账户相关单独一个文件,资源包相关单独一个文件
- 静态资源都在assets下面
- 所有的组件都放在components下面,不同的组件类型单独一个文件夹命名,子组件也是如此
- 路由文件放在router下面,统一由index.js去对外暴露
- 公共管理库放在store下面,统一由index.js去对外暴露
- 所有的工具类放在utils,不同种类的工具单独文件夹分开处理
- 主视图文件放在views下面,充当为顶层决策者
- App.vue文件为最高决策者,如果未来规划页面布局应当在此规划
本地代理
vite本身就支持本地代理操作,所有的配置都在vite.config.js文件中去配置,假如是公司内部使用,那么公司内部系统会在请求上带上特殊参数,那么可以通过代理的手段去设置。
server: {// port: 8080, // 设置端口号// host: '192.168.11.5',proxy: {'/static': {target: 'http://xxxxxx.com',ws: false,changeOrigin: true,rewrite: (path) => path.replace(/^\/static/, ''),configure: (proxy) => {proxy.on('proxyReq', function (proxyReq) {proxyReq.setHeader('Cookie', 'my_dev=a221cc9410bae508aa3c1106af467db5; ly_my_user=Ikft5xHzizNCSKdfddTxTVyj9wZ3pdhh; ly_my_refer=1380.8.74.2; PHPSESSID=fufvppacre3nor48aos834dtei; SERVERID=6c7b09313256c7aae1d2b6b27bf60e38|1732675765|1732675765');});}},
请求配置
本地代理是需要和请求配置相对应的,当处理本地环境时,请求链接为配置好的代理头,触发本地代理,完成本地请求。
const service = axios.create({timeout: 5000,timeoutRetry: true,// 请求头信息headers: {'Content-Type': 'application/x-www-form-urlencoded'},// 携带凭证withCredentials: true,// 返回数据类型// responseType: 'json'
})service.interceptors.request.use(config => {// 根据custom参数中配置的是否需要baseURL,添加对应的请求头if (config?.custom?.baseUrlType) {if (process.env.NODE_ENV === 'development') {config.baseURL = `/${config.custom.baseUrlType}`} else {config.baseURL = `//${config.custom.baseUrlType}.${window.location.host.split('.')[1]}.com`}}
我在请求拦截中就是这么做的。
打包配置
打包注意要在vite.config.js中进行配置,否则可能出现找不到资源的问题
base: process.env.NODE_ENV === 'production' ? './' : '/',
资源访问配置
这里我为了方便资源的获取,将src目录设置别名
resolve: {alias: {'@': path.resolve(__dirname, 'src')}},
难点和解析
数据传递
前面说过了,我希望做到的就是不违背单一数据源的原则,因此在数据的处理上,我希望最终都交给顶层去决策,那么是这么处理的:
对于子级组件,我在父级中给予一个ref,为了能够操作子组件
<CustomTemplate name="选择账户"><Account ref="AccountRef" /></CustomTemplate>const AccountRef = ref();
子组件对外暴露一个handleSubmit方法,用于对外抛出数据
const account = await AccountRef.value.handleSubmit()
这样有几个好处:
- 组件和组件之前是解耦的。
不会说一个组件影响到了其他组件,组件的事情组件自己处理,我要的只是处理完之后的数据,在多人开发中,可以把页面组件化,这一块组件只做自己的事情,最终要的只是通过你暴露出来的方法拿到最终处理好的数据即可。 - 单一数据源原则
地域数据树级处理
从接口拿到中国地域数据的时候,首先要考虑以下问题:
- 数据量大
地域数据通常数据量都会比较大,反复的调用接口去拿显然是不合适的,因为地域数据通常是不会改变的,因此在首次调用接口成功拿到数据之后,就应该存储本地了,之后都看本地有就本地拿,没有就调用接口,接口需要更新再重新调用重新执行流程 - 怎么存
对于浏览器来说,存储的方式有很多种:
例如cookie、LocalStorage、SessionStorage、浏览器数据库。
但是我们的数据量比较大,可能需要反复读写,那么就要求:
一是能够存储比较大的数据
二是读写得快,不能阻塞渲染进程
那么数据库IndexedDB就非常合适了,存储容量大,异步操作不阻塞线程,数据结构也足够灵活 - 怎么展示
展示当然就选中了element-plus的树级选择器了,但是需要注意的是,由于地域在嵌套方面比较深的时候,渲染会慢,因此我们需要做懒加载(render-after-expand),并且选中的时候,不能选中父子级联(check-strictly),否则严重阻塞渲染。
<el-tree-select v-model="ACForm.city" :data="cityOptions" multiple :render-after-expand="true"show-checkbox style="width: 240px" placeholder="请选择区域" node-key="id" check-strictly />
并且大概率从头条获取的数据并不是一个标准的树级结构,因此需要修改为标准的树级结构
const addAdminSelectorsCity = (data) => {let arr = [];data.map(item => {let newData = {};//这一块根据选用的组件来newData.label = item.name; newData.id = item.geoname_id;newData.value = item.code;if (item.name === "台湾省" ||item.name === "香港特别行政区" ||item.name === "澳门特别行政区") {newData.value = item.geoname_id;}if (item.sub_districts) {newData.children = addAdminSelectorsCity(item.sub_districts);if (newData.label === newData.children[0].label) {newData = newData.children[0];}}return arr.push(newData);});return arr;
};
可动态增删数据的数据校验
本身其实el-form 配合el-form-item是有一套标准的校验规范的,在非动态的数据校验不会有任何问题。
<el-form :model="AdBudgetForm" :rules="rules" style="max-width: 600px" label-width="auto" label-position="left"size="default" ref="AdBudgetRef"><div class="form-container"><el-form-item label="广告预算" prop="budget"><el-input v-model="AdBudgetForm.budget" style="width: 240px" placeholder="请输入广告预算"><template #suffix>元</template></el-input></el-form-item></div></el-form>
表单定义一个AdBudgetForm负责获取所有数据,定义一个rules
负责处理规则,每一项都使用el-form-item进行包裹,通过设置prop去匹配rules
中的校验,去完成校验。
但是有一种场景:
点击能够不断添加,删除又可以批量删除和添加,当条数比较多的时候,肯定不能去手动定义非常多的rules去处理,因为他们实际上校验规则都是同一个。
只需要这么处理即可:
<div style="margin-bottom: 10px;" v-for="(item, index) in textSourceForm.textSourceList" :key="index"><el-form-item :prop="'textSourceList.' + index + '.title'" :rules="rules.title">const rules = reactive({title: [{validator: textRules,trigger: 'blur'}]
});
头条自定义日期选择组件
这个组件是在这一位开源的基础上进行二次改造成vue3版本的,完整实例如下,开箱即用:
开源作者地址:https://github.com/xiejunping/andt-components
<TimeRangePicker ref="TimeRangePickerRef" :value="mult_timeRange" :data="weektimeDataArr"@clearSelection="clearSelection" />const weektimeDataArr = ref(weektimeData)
const mult_timeRange = computed(() => { //时间范围return weektimeDataArr.value.map((item) => {return {id: item.row,week: item.value,value: splicing(item.child),};});
});const clearSelection = () => {weektimeDataArr.value.forEach((item) => {item.child.forEach((t) => {t.check = false; // 直接修改属性即可});});
}
weektimeData
const formatDate = (date, fmt) => {const o = {'M+': date.getMonth() + 1,'d+': date.getDate(),'h+': date.getHours(),'m+': date.getMinutes(),'s+': date.getSeconds(),'q+': Math.floor((date.getMonth() + 3) / 3),S: date.getMilliseconds()}if (/(y+)/.test(fmt)) {fmt = fmt.replace(RegExp.$1,(date.getFullYear() + '').substr(4 - RegExp.$1.length))}for (const k in o) {if (new RegExp('(' + k + ')').test(fmt)) {fmt = fmt.replace(RegExp.$1,RegExp.$1.length === 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length))}}return fmt
}const createArr = (len) => {return Array.from(Array(len)).map((ret, id) => id)
}const formatWeektime = (col) => {const timestamp = 1542384000000 // '2018-11-17 00:00:00'const beginstamp = timestamp + col * 1800000 // col * 30 * 60 * 1000const endstamp = beginstamp + 1800000const begin = formatDate(new Date(beginstamp), 'hh:mm')const end = formatDate(new Date(endstamp), 'hh:mm')return `${begin}~${end}`
}const data = ['星期一','星期二','星期三','星期四','星期五','星期六','星期日'
].map((ret, index) => {const children = (ret, row, max) => {return createArr(max).map((t, col) => {return {week: ret,value: formatWeektime(col),begin: formatWeektime(col).split('~')[0],end: formatWeektime(col).split('~')[1],row,col}})}return {value: ret,row: index,child: children(ret, index, 48)}
})export default data
<template><div class="c-weektime"><div class="c-schedue"></div><div :class="{ 'c-schedue': true, 'c-schedue-notransi': mode }" :style="styleValue"></div><table :class="{ 'c-min-table': colspan < 2 }" class="c-weektime-table"><thead class="c-weektime-head"><tr><th rowspan="8" class="week-td">星期/时间</th><th :colspan="12 * colspan">00:00 - 12:00</th><th :colspan="12 * colspan">12:00 - 24:00</th></tr><tr><td v-for="t in theadArr" :key="t" :colspan="colspan">{{ t }}</td></tr></thead><tbody class="c-weektime-body"><tr v-for="t in data" :key="t.row"><td>{{ t.value }}</td><td v-for="n in t.child" :key="`${n.row}-${n.col}`" :data-week="n.row" :data-time="n.col":class="selectClasses(n)" @mouseenter="cellEnter(n)" @mousedown="cellDown(n)"@mouseup="cellUp(n)" class="weektime-atom-item"></td></tr><tr><td colspan="49" class="c-weektime-preview"><div class="g-clearfix c-weektime-con"><span class="g-pull-left">{{selectState ? '已选择时间段' : '可拖动鼠标选择时间段'}}</span><a @click.prevent="clearSelection" class="g-pull-right">清空选择</a></div><div v-if="selectState" class="c-weektime-time"><div v-for="t in selectValue" :key="t.id"><p v-if="t.value"><span class="g-tip-text">{{ t.week }}:</span><span>{{ t.value }}</span></p></div></div></td></tr></tbody></table></div>
</template><script setup>
import { computed, defineEmits, defineExpose, defineProps, ref } from "vue";const emit = defineEmits(['clearSelection']);// Props
const props = defineProps({value: { type: Array, required: true },data: { type: Array, required: true },colspan: { type: Number, default: 2 },
});// Reactive State
const width = ref(0);
const height = ref(0);
const left = ref(0);
const top = ref(0);
const mode = ref(0);
const row = ref(0);
const col = ref(0);
const theadArr = ref([]);// Helper Function
const createArr = (len) => Array.from({ length: len }, (_, id) => id);// Computed Properties
const styleValue = computed(() => ({width: `${width.value}px`,height: `${height.value}px`,left: `${left.value}px`,top: `${top.value}px`,
}));const selectValue = computed(() => props.value);const selectState = computed(() => props.value.some((ret) => ret.value));const selectClasses = (n) => (n.check ? "ui-selected" : "");// Initialization (created hook)
theadArr.value = createArr(24);// Methods
const cellEnter = (item) => {const ele = document.querySelector(`td[data-week='${item.row}'][data-time='${item.col}']`);if (!ele) return;if (!mode.value) {left.value = ele.offsetLeft;top.value = ele.offsetTop;} else if (item.col <= col.value && item.row <= row.value) {width.value = (col.value - item.col + 1) * ele.offsetWidth;height.value = (row.value - item.row + 1) * ele.offsetHeight;left.value = ele.offsetLeft;top.value = ele.offsetTop;} else if (item.col >= col.value && item.row >= row.value) {width.value = (item.col - col.value + 1) * ele.offsetWidth;height.value = (item.row - row.value + 1) * ele.offsetHeight;if (item.col > col.value && item.row === row.value) top.value = ele.offsetTop;if (item.col === col.value && item.row > row.value) left.value = ele.offsetLeft;} else if (item.col > col.value && item.row < row.value) {width.value = (item.col - col.value + 1) * ele.offsetWidth;height.value = (row.value - item.row + 1) * ele.offsetHeight;top.value = ele.offsetTop;} else if (item.col < col.value && item.row > row.value) {width.value = (col.value - item.col + 1) * ele.offsetWidth;height.value = (item.row - row.value + 1) * ele.offsetHeight;left.value = ele.offsetLeft;}
};const cellDown = (item) => {const ele = document.querySelector(`td[data-week='${item.row}'][data-time='${item.col}']`);if (!ele) return;mode.value = 1;width.value = ele.offsetWidth;height.value = ele.offsetHeight;row.value = item.row;col.value = item.col;
};const cellUp = (item) => {if (item.col <= col.value && item.row <= row.value) {selectWeek([item.row, row.value], [item.col, col.value], !item.check);} else if (item.col >= col.value && item.row >= row.value) {selectWeek([row.value, item.row], [col.value, item.col], !item.check);} else if (item.col > col.value && item.row < row.value) {selectWeek([item.row, row.value], [col.value, item.col], !item.check);} else if (item.col < col.value && item.row > row.value) {selectWeek([row.value, item.row], [item.col, col.value], !item.check);}width.value = 0;height.value = 0;mode.value = 0;
};const selectWeek = (rowRange, colRange, check) => {const [minRow, maxRow] = rowRange;const [minCol, maxCol] = colRange;props.data.forEach((item) => {item.child.forEach((t) => {if (t.row >= minRow &&t.row <= maxRow &&t.col >= minCol &&t.col <= maxCol) {t.check = check;}});});
};const clearSelection = () => {emit('clearSelection')
};defineExpose({selectValue
})
</script><style lang="scss" scoped>
.c-weektime {min-width: 640px;position: relative;display: inline-block;
}.c-schedue {background: #598fe6;position: absolute;width: 0;height: 0;opacity: 0.6;pointer-events: none;&-notransi {transition: width 0.12s ease, height 0.12s ease, top 0.12s ease,left 0.12s ease;}
}.c-weektime-table {border-collapse: collapse;th {vertical-align: inherit;font-weight: bold;}tr {height: 30px;}tr,td,th {user-select: none;border: 1px solid #dee4f5;text-align: center;line-height: 1.0em;transition: background 0.2s ease;}.c-weektime-head {font-size: 12px;.week-td {width: 9.2em !important;}}.c-weektime-body {font-size: 12px;td {&.weektime-atom-item {user-select: unset;background-color: #f5f5f5;width: 1.4em;}&.ui-selected {background-color: #598fe6;}}}.c-weektime-preview {line-height: 2.4em;padding: 0 10px;font-size: 14px;.c-weektime-con {line-height: 46px;user-select: none;}.c-weektime-time {text-align: left;line-height: 2.4em;p {max-width: 625px;line-height: 1.4em;word-break: break-all;margin-bottom: 8px;}}}
}.c-min-table {tr,td,th {min-width: 24px;}
}.g-clearfix {&:after,&:before {clear: both;content: ' ';display: table;}
}.g-pull-left {float: left;
}.g-pull-right {float: right;
}.g-tip-text {color: #999;
}
</style>
性能优化和迭代
- 在点击不同操作的时候,注意浏览器的性能指标,及时发现究竟哪些操作会阻塞到渲染进程
- 目前仅是单账户和单广告,实际上做成多账户和多广告只需要补充一些匹配规则,然后做不同的分配即可。
总结
本文简单阐述使用vue3去实现简易版的广告投放系统中遇到的问题和解决思路,希望能给到一些设计思路,同时如果有更好的建议,欢迎提出!
相关文章:

【广告投放系统】头条可视化投放平台vue3+element-plus+vite落地历程和心得体会
前言 hallo,又是许久未见,昨天也是正式把公司内部的广告投放平台暂时落地,我也即将离开待了两年多的地方。言归正传,由于头条广告后台的升级改版,因此为了满足内部投放需求,做了一个可视化的投放平台&…...
Gazebo插件相机传感器(可订阅/camera/image_raw话题)
在仿真环境中使用相机传感器,通常需要结合Gazebo插件来实现。Gazebo是一个功能强大的机器人仿真工具,支持多种传感器模型,包括相机。下面是如何在Gazebo中使用相机传感器的详细步骤。 1. 修改Xacro文件以包含Gazebo插件 首先,修…...

华三(HCL)和华为(eNSP)模拟器共存安装手册
接上章叙述,解决同一台PC上同时部署华三(HCL)和华为(eNSP)模拟器。原因就是华三HCL 的老版本如v2及以下使用VirtualBox v5版本,可以直接和eNSP兼容Oracle VirtualBox,而其他版本均使用Oracle VirtualBox v6以上的版本,…...

信息学奥赛一本通 1448:【例题1】电路维修 | 洛谷 P4667 [BalticOI 2011 Day1] Switch the Lamp On 电路维修
【题目链接】 ybt 1448:【例题1】电路维修 洛谷 P4667 [BalticOI 2011 Day1] Switch the Lamp On 电路维修 【题目考点】 1. 双端队列广搜(0-1BFS) 【解题思路】 整个电路是由一个个的正方形的电路元件组成,每个正方形有四个…...

k8s删除网络组件错误
k8s集群删除calico网络组件重新部署flannel网络组件,再部署pod后出现报错不能分配ip地址 plugin type"calico" failed (add): error getting ClusterInformation: connection is unauthorized: Unauthorized 出现该问题是因为删除网络组件后,网…...

MySQL之JDBC
我们在学习完了数据库的基本操作后,希望和我们的Java程序建立连接,那么我们今天就来一探究竟JDBC是如何让Java程序与数据库建立连接的 1. 什么是JDBC JDBC(Java Data Base Connectivity, Java数据库连接) 是Java程序和数据库之间…...

音视频入门基础:MPEG2-TS专题(10)——PAT简介
一、引言 当某个transport packet的TS Header中的PID属性的值为0x0000时,该transport packet的payload为Program association table ,即 PAT表。PAT表包含所有PMT表的目录列表,将program_number和PMT表的PID相关联,获取数据的起始…...
ElementUI:el-drawer实现在父组件区域内打开抽屉组件非全屏
我们在开发ElementUI的时候遇到抽屉组件全屏的问题,但是我们需要在指定div中展示出来,上代码: 1、在el-drawer中增加属性 el-drawerstyle"position: absolute"z-index"-1":append-to-body"false">// do s…...

Vue教程|搭建vue项目|Vue-CLI2.x 模板脚手架
一、项目构建环境准备 在构建Vue项目之前,需要搭建Node环境以及Vue-CLI脚手架,由于本篇文章为上一篇文章的补充,也是为了给大家分享更为完整的搭建vue项目方式,所以环境准备部分采用Vue教程|搭建vue项目|V…...

jmeter学习(7)命令行控制
jmeter -n -t E:\IOT\test2.jmx -l E:\IOT\output\output.jtl -j E:\IOT\output\jmeter.log -e -o E:\IOT\output\report IOT下创建output 文件夹,jmx文件名避免中文,再次执行output.jtl不能有数据要删除...

BGP协议路由黑洞
一、实验环境 1、分公司与运营商AS自治系统内运行IGP路由协议OSPF、RIP或静态路由,AS自治系统内通过IBGP路由协议建立BGP邻居关系。 2、公司AS自治系统与运营商AS自治系统间运行EBGP路由协议。 3、通过loopback建立IBGP与EBGP邻居关系,发挥loopback建立…...

存储结构及关系(一)
学习目标 描述数据库的逻辑结构列出段类型及其用途列出控制块空间使用的关键字获取存储结构信息 段的类型 段是数据库中占用空间的对象。它们使用数据库数据文件中的空间。介绍不同类型的段。 表 表是在数据库中存储数据的最常用方法。表段用于存储既没有集群也没有分区的表…...

玄机应急:linux入侵排查webshell查杀日志分析
目录 第一章linux:入侵排查 1.web目录存在木马,请找到木马的密码提交 2.服务器疑似存在不死马,请找到不死马的密码提交 3.不死马是通过哪个文件生成的,请提交文件名 4.黑客留下了木马文件,请找出黑客的服务器ip提交 5.黑客留…...
python爬虫安装教程
Python爬虫是用于从网站上自动抓取信息的程序。在开始之前,请确保您了解并遵守目标网站的服务条款,尊重版权法,并且在合理合法的范围内使用爬虫技术。 安装环境 安装Python:首先确保您的计算机上已经安装了Python。推荐版本为3.…...
田忌赛马五局三胜问题matlab代码
问题描述:在可以随机选择出场顺序的情况下,如果把比赛规则从三局两胜制改为五局三胜制,齐王胜出的概率是上升了还是下降了?五局三胜的赛制下,大家的马重新分为5个等级。前提条件仍然是齐王每种等级的马都优于田忌同等级…...
Spring循环依赖问题的解决
项目启动提示如下异常: The dependencies of some of the beans in the application context form a cycle 这表明在我们的应用中存在了循环依赖,示例: Bean A 中注入了Bean B依赖,然后 Bean B 中注入了Bean A依赖。也就是说&…...

KAN-Transfomer——基于新型神经网络KAN的时间序列预测
1.数据集介绍 ETT(电变压器温度):由两个小时级数据集(ETTh)和两个 15 分钟级数据集(ETTm)组成。它们中的每一个都包含 2016 年 7 月至 2018 年 7 月的七种石油和电力变压器的负载特征。 traffic(交通) :描…...

鸿蒙学习自由流转与分布式运行环境-价值与架构定义(1)
文章目录 价值与架构定义1、价值2、架构定义 随着个人设备数量越来越多,跨多个设备间的交互将成为常态。基于传统 OS 开发跨设备交互的应用程序时,需要解决设备发现、设备认证、设备连接、数据同步等技术难题,不但开发成本高,还存…...
【k8s深入理解之 Scheme 补充-2】理解 register.go 暴露的 AddToScheme 函数
AddToScheme 函数 AddToScheme 就是为了对外暴露,方便别人调用,将当前Group组的信息注册到其 Scheme 中,以便了解该 Group 组的数据结构,用于后续处理 项目版本用途使用场景k8s.io/apiV1注册资源某一外部版本数据结构࿰…...
uni-app写的微信小程序每次换账号登录时出现缓存上一个账号数据的问题
uni-app写的微信小程序每次更换另外账号登录时出现缓存上一个账号数据的问题? 清除缓存数据:在 onShow 钩子中,我们将 powerStations、list 和 responseRoles 的值重置为初始状态,以清除之前的缓存数据。重新获取数据:…...

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)
题目:3442. 奇偶频次间的最大差值 I 思路 :哈希,时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况,哈希表这里用数组即可实现。 C版本: class Solution { public:int maxDifference(string s) {int a[26]…...

基于FPGA的PID算法学习———实现PID比例控制算法
基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容:参考网站: PID算法控制 PID即:Proportional(比例)、Integral(积分&…...

Docker 运行 Kafka 带 SASL 认证教程
Docker 运行 Kafka 带 SASL 认证教程 Docker 运行 Kafka 带 SASL 认证教程一、说明二、环境准备三、编写 Docker Compose 和 jaas文件docker-compose.yml代码说明:server_jaas.conf 四、启动服务五、验证服务六、连接kafka服务七、总结 Docker 运行 Kafka 带 SASL 认…...

渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止
<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet: https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...
JVM垃圾回收机制全解析
Java虚拟机(JVM)中的垃圾收集器(Garbage Collector,简称GC)是用于自动管理内存的机制。它负责识别和清除不再被程序使用的对象,从而释放内存空间,避免内存泄漏和内存溢出等问题。垃圾收集器在Ja…...

华硕a豆14 Air香氛版,美学与科技的馨香融合
在快节奏的现代生活中,我们渴望一个能激发创想、愉悦感官的工作与生活伙伴,它不仅是冰冷的科技工具,更能触动我们内心深处的细腻情感。正是在这样的期许下,华硕a豆14 Air香氛版翩然而至,它以一种前所未有的方式&#x…...
【Java学习笔记】BigInteger 和 BigDecimal 类
BigInteger 和 BigDecimal 类 二者共有的常见方法 方法功能add加subtract减multiply乘divide除 注意点:传参类型必须是类对象 一、BigInteger 1. 作用:适合保存比较大的整型数 2. 使用说明 创建BigInteger对象 传入字符串 3. 代码示例 import j…...
LRU 缓存机制详解与实现(Java版) + 力扣解决
📌 LRU 缓存机制详解与实现(Java版) 一、📖 问题背景 在日常开发中,我们经常会使用 缓存(Cache) 来提升性能。但由于内存有限,缓存不可能无限增长,于是需要策略决定&am…...

MySQL:分区的基本使用
目录 一、什么是分区二、有什么作用三、分类四、创建分区五、删除分区 一、什么是分区 MySQL 分区(Partitioning)是一种将单张表的数据逻辑上拆分成多个物理部分的技术。这些物理部分(分区)可以独立存储、管理和优化,…...

从物理机到云原生:全面解析计算虚拟化技术的演进与应用
前言:我的虚拟化技术探索之旅 我最早接触"虚拟机"的概念是从Java开始的——JVM(Java Virtual Machine)让"一次编写,到处运行"成为可能。这个软件层面的虚拟化让我着迷,但直到后来接触VMware和Doc…...