中间件自动化测试框架cmdlinker
背景
作为一个中间件的测试工程师,如何对于中间件提供的命令进行自动化的回归,这一直是一个难题,市面上好像缺乏了对于命令进行自动化回归的合理解决方案。
常见方式有下面两种:
- 直接写字符串的命令,然后使用各种编程语言的SSH库进行连接,然后执行命令字符串,获取执行结果,如果需要对传入的参数进行校验,还需要各种提取字符串,或者设置各种命令变量,比较繁琐
- 编写命令对象,通过操作命令对象set/get方式,生成命令字符串给SSH库进行命令执行,如果需要对传入的参数进行校验,只需要通过命令对象get的方式进行获取,当然缺点就是,每次都需要进行大量的工作去编写命令对象,重复性劳动
通过对比之后,会发现,使用第二种方式更加直观、简洁,提取响应数据也更加方便,但是第二种方式对于大量重复性的去编写命令对象的方式,也让人十分恶心。如果有一个工具能够帮助大家去生成命令对象,然后通过调用命令对象执行请求响应的方式就能让大家从这种困境中走出来。
所以引入了cmdlinker这个开源python库,cmdlinker能够帮助我们做什么?
- 通过编写命令对象yaml的方式,生成命令对象,无需手动进行命令对象代码的编写
- 命令对象自带runner,可以直接获取命令执行的请求响应
- 支持本地shell执行及远程ssh执行的方式
安装
pip3 install cmdlinker
文档地址
https://github.com/chineseluo/cmdlinker
案例演示:
使用docker命令举例,查询容器,获取容器配置信息,断言容器的ID是否包含请求的ID
编写yaml文件:
entry: "docker"
mapping_entry: "Docker"
module_name: "docker"
class_name: "Docker"
out_path: "./"
mode: "SSH"
parameters:- mapping_name: "ps"original_cmd: "ps"value: False # 是否需要值mutex: False # 是否互斥default: None- mapping_name: "inspect"original_cmd: "inspect"value: True # 是否需要值mutex: False # 是否互斥default: None- mapping_name: "f"original_cmd: "-f"value: True # 是否需要值mutex: False # 是否互斥default: None
执行生成命令:
CmdLinker init -f ./docker.yaml
生成python命令对象
from loguru import logger
from typing import Union, Text
from cmdlinker.builtin.exception import CmdLinkerMutexException, CmdLinkerMulMutexException
from cmdlinker.builtin.ssh_utils import SSHClient
from cmdlinker.builtin.shell_utils import ShellClientclass Cmds:def __init__(self):self.index = 0self.CMD_LIST = []class Runner:def __init__(self):self._mutexs = []self._gcs = []self._not_mutexs = []self.pre: Runner = objectself.root: Runner = objectself.next: Union[Runner] = objectself.main_cmd: Text = Noneself.cmds: Cmds = Noneself.ssh_client: SSHClient = Noneself.sudo: bool = Trueself.timeout: int = 20def _exclude(self, cmd_obj):if not cmd_obj.mark:logger.debug(f"命令对象:{cmd_obj.__class__.__name__}未被使用")return Falseelse:return Truedef _get_execute_cmd(self, cmd_obj):if cmd_obj.need_value:return f"{cmd_obj.meta_data['original_cmd']} {cmd_obj.value}"else:return f"{cmd_obj.meta_data['original_cmd']}"def _get_log_desc(self):if self.pre == self.root:log_desc = "子"elif self.pre is None and self.root == self:log_desc = "根"else:log_desc = "父"return log_descdef cmd_checker(self):log_desc = self._get_log_desc()logger.info("==" * 20 + f"开启{log_desc}命令{self.__class__.__name__}合法性检查" + "==" * 20)for cmd_obj_str in vars(self).keys():cmd_obj = getattr(self, cmd_obj_str)if isinstance(cmd_obj, BaseCmd):if cmd_obj_str == "next":continueif not self._exclude(cmd_obj):continueif cmd_obj.mutex:self._mutexs.append(cmd_obj)logger.debug(f"{self.main_cmd}添加互斥对象:{cmd_obj},最新互斥对象列表:")else:self._not_mutexs.append(cmd_obj)logger.debug(f"{self.main_cmd}添加非互斥对象:{cmd_obj},最新非互斥对象列表:{self._not_mutexs}")if len(self._mutexs) > 1:raise CmdLinkerMulMutexException(self.__class__.__name__, self._mutexs)if len(self._mutexs) != 0 and len(self._not_mutexs) != 0:raise CmdLinkerMutexException(self.__class__.__name__, self._mutexs, self._not_mutexs)if self.pre == self.root:logger.debug(f"启用命令对象{self.__class__.__name__} 根命令: {self.root.__class__.__name__} 检查")self.pre.cmd_checker()elif self.pre is None and self.root == self:passelse:logger.debug(f"启用命令对象{self.__class__.__name__}父命令: {self.pre.__class__.__name__} 检查")self.pre.cmd_checker()self._not_mutexs = []self._mutexs = []logger.info("==" * 20 + f"{log_desc}命令{self.__class__.__name__}合法性检查通过" + "==" * 20)def runner(self):self.cmd_checker()cmd = self.exec_cmd()self.cmds.CMD_LIST = []return self.ssh_client.run_cmd(cmd, timeout=self.timeout)def collector(self):return self.cmds.CMD_LIST.sort(key=lambda cmd_obj: cmd_obj.index)def exec_cmd(self):self.cmds.CMD_LIST.sort(key=lambda cmd_obj: cmd_obj.index)cmd_list = [self.main_cmd] + [self._get_execute_cmd(cmd) for cmd in self.cmds.CMD_LIST]if self.sudo:cmd_list.insert(0, "sudo")logger.info(f"执行命令列表:{cmd_list}")cmd = " ".join(cmd_list)return cmdclass BaseCmd(Runner):def __init__(self):super().__init__()self.cmds: Cmds = Noneself.meta_data = Noneself.mutex = Noneself.need_value = Noneself.has_child_cmd = Noneself.gc = Noneself.child_cmd = Noneself.default_value = Noneself.value = Noneself.mark = Noneself.index = Noneself.level = 0self._mutexs = []self._gcs = []self._not_mutexs = []self.main_cmd = "docker"self.pre: object = objectself.root: object = objectself.next: Union[object] = objectself.ssh_client: SSHClient = Noneclass Ps(BaseCmd):def __init__(self, root_obj, pre_obj):super().__init__()self.pre: Docker = pre_objself.root: Docker = root_objself.next: Union[Ps] = selfself.meta_data = {'mapping_name': 'ps', 'original_cmd': 'ps', 'value': False, 'mutex': False, 'default': 'None','has_child_cmd': False, 'child_cmds': [], 'parent_cmd': 'Docker', 'root_cmd': 'Docker'}self.level = 2self.mutex = Falseself.need_value = Falseself.has_child_cmd = Falseself.child_cmds = []self.gc = Falseself.default_value = "None"self.value = self.default_valueself.mark = Falseclass Inspect(BaseCmd):def __init__(self, root_obj, pre_obj):super().__init__()self.pre: Docker = pre_objself.root: Docker = root_objself.next: Union[Inspect] = selfself.meta_data = {'mapping_name': 'inspect', 'original_cmd': 'inspect', 'value': True, 'mutex': False,'default': 'None', 'has_child_cmd': False, 'child_cmds': [], 'parent_cmd': 'Docker','root_cmd': 'Docker'}self.level = 2self.mutex = Falseself.need_value = Trueself.has_child_cmd = Falseself.child_cmds = []self.gc = Falseself.default_value = "None"self.value = self.default_valueself.mark = Falseclass Format(BaseCmd):def __init__(self, root_obj, pre_obj):super().__init__()self.pre: Docker = pre_objself.root: Docker = root_objself.next: Union[Format] = selfself.meta_data = {'mapping_name': 'format', 'original_cmd': '--format', 'value': True, 'mutex': False,'default': 'None', 'has_child_cmd': False, 'child_cmds': [], 'parent_cmd': 'Docker','root_cmd': 'Docker'}self.level = 2self.mutex = Falseself.need_value = Trueself.has_child_cmd = Falseself.child_cmds = []self.gc = Falseself.default_value = "None"self.value = self.default_valueself.mark = Falseclass Docker(Runner):def __init__(self, host=None, name=None, pwd=None, port="22", timeout="60", sudo=False):super().__init__()self.cmds: Cmds = Cmds()self.pre: object = Noneself.root: Docker = selfself.next: Union[Ps, Inspect, Format,] = Noneself.main_cmd = "docker"self._mutexs = []self._gcs = []self._not_mutexs = []self.mode = "SSH"self.ssh_client = SSHClient(host, name, pwd, port)self._ps: Ps = Ps(self, self)self._inspect: Inspect = Inspect(self, self)self._format: Format = Format(self, self)def _set_ps(self):self._ps.mark = Trueself._ps.index = self.cmds.indexself.cmds.index += 1self.next = self._psself.cmds.CMD_LIST.append(self._ps)self._ps.cmds = self.cmdsself._ps.ssh_client = self.ssh_clientdef tset_ps(self):"""传递TRANSMIT模式,可以获取子命令对象,可通过,root/pre/next,控制命令对象的根/父/子级"""self._set_ps()return self._psdef hset_ps(self, ):"""保持HOLD模式,该方法返回该对象本身,不返回子对象"""self._set_ps()return selfdef _set_inspect(self, value=None):self._inspect.mark = Trueself._inspect.index = self.cmds.indexself.cmds.index += 1self.next = self._inspectself.cmds.CMD_LIST.append(self._inspect)self._inspect.cmds = self.cmdsself._inspect.ssh_client = self.ssh_clientif self._inspect.need_value:self._inspect.value = valuedef tset_inspect(self, value=None):"""传递TRANSMIT模式,可以获取子命令对象,可通过,root/pre/next,控制命令对象的根/父/子级"""self._set_inspect(value=value)return self._inspectdef hset_inspect(self, value=None):"""保持HOLD模式,该方法返回该对象本身,不返回子对象"""self._set_inspect(value=value)return selfdef _set_format(self, value=None):self._format.mark = Trueself._format.index = self.cmds.indexself.cmds.index += 1self.next = self._formatself.cmds.CMD_LIST.append(self._format)self._format.cmds = self.cmdsself._format.ssh_client = self.ssh_clientif self._format.need_value:self._format.value = valuedef tset_format(self, value=None):"""传递TRANSMIT模式,可以获取子命令对象,可通过,root/pre/next,控制命令对象的根/父/子级"""self._set_format(value=value)return self._formatdef hset_format(self, value=None):"""保持HOLD模式,该方法返回该对象本身,不返回子对象"""self._set_format(value=value)return selfdef ps(self) -> Ps:return self._psdef inspect(self) -> Inspect:return self._inspectdef format(self) -> Format:return self._format
执行测试
进行请求响应断言(PS:自行替换下面的host/name/pwd/port等参数验证)
if __name__ == '__main__':docker = Docker(host="192.168.1.2", name="root", pwd="xxxx")ps_res = docker.hset_ps().hset_format("{{.ID}}").runner()inspect_res = docker.hset_inspect(ps_res["stdout"]).runner()assert ps_res["stdout"].strip() in inspect_res["stdout"][0]["Id"]
检查日志
C:\Users\Dell\AppData\Local\Programs\Python\Python39\python.exe E:\开源项目\CmdLinker\tests\docker.py
2025-01-03 16:38:03.116 | INFO | __main__:cmd_checker:53 - ========================================开启根命令Docker合法性检查========================================
2025-01-03 16:38:03.116 | DEBUG | __main__:cmd_checker:66 - docker添加非互斥对象:<__main__.Ps object at 0x000001ADA171E9A0>,最新非互斥对象列表:[<__main__.Ps object at 0x000001ADA171E9A0>]
2025-01-03 16:38:03.116 | DEBUG | __main__:_exclude:31 - 命令对象:Inspect未被使用
2025-01-03 16:38:03.116 | DEBUG | __main__:cmd_checker:66 - docker添加非互斥对象:<__main__.Format object at 0x000001ADA171EAF0>,最新非互斥对象列表:[<__main__.Ps object at 0x000001ADA171E9A0>, <__main__.Format object at 0x000001ADA171EAF0>]
2025-01-03 16:38:03.116 | INFO | __main__:cmd_checker:81 - ========================================根命令Docker合法性检查通过========================================
2025-01-03 16:38:03.116 | INFO | __main__:exec_cmd:97 - 执行命令列表:['sudo', 'docker', 'ps', '--format {{.ID}}']
2025-01-03 16:38:03.693 | INFO | cmdlinker.builtin.logger_operation:console_output:18 -
================== cmd request details ==================
execute cmd on host : 192.168.1.2
execute cmd : sudo docker ps --format {{.ID}}2025-01-03 16:38:03.694 | WARNING | cmdlinker.builtin.ssh_utils:run_cmd:67 - 尝试对response stdout进行json转换失败,原样输出
2025-01-03 16:38:03.694 | INFO | cmdlinker.builtin.logger_operation:console_output:18 -
================== cmd response details ==================
status_code : 0
execute_time : 578ms
stdout : 7393aa6334e6stderr : 2025-01-03 16:38:03.694 | INFO | __main__:cmd_checker:53 - ========================================开启根命令Docker合法性检查========================================
2025-01-03 16:38:03.694 | DEBUG | __main__:cmd_checker:66 - docker添加非互斥对象:<__main__.Ps object at 0x000001ADA171E9A0>,最新非互斥对象列表:[<__main__.Ps object at 0x000001ADA171E9A0>]
2025-01-03 16:38:03.694 | DEBUG | __main__:cmd_checker:66 - docker添加非互斥对象:<__main__.Inspect object at 0x000001ADA171EA60>,最新非互斥对象列表:[<__main__.Ps object at 0x000001ADA171E9A0>, <__main__.Inspect object at 0x000001ADA171EA60>]
2025-01-03 16:38:03.694 | DEBUG | __main__:cmd_checker:66 - docker添加非互斥对象:<__main__.Format object at 0x000001ADA171EAF0>,最新非互斥对象列表:[<__main__.Ps object at 0x000001ADA171E9A0>, <__main__.Inspect object at 0x000001ADA171EA60>, <__main__.Format object at 0x000001ADA171EAF0>]
2025-01-03 16:38:03.694 | INFO | __main__:cmd_checker:81 - ========================================根命令Docker合法性检查通过========================================
2025-01-03 16:38:03.694 | INFO | __main__:exec_cmd:97 - 执行命令列表:['sudo', 'docker', 'inspect 7393aa6334e6\n']
2025-01-03 16:38:03.922 | INFO | cmdlinker.builtin.logger_operation:console_output:18 -
================== cmd request details ==================
execute cmd on host : 47.97.37.176
execute cmd : sudo docker inspect 7393aa6334e62025-01-03 16:38:03.922 | INFO | cmdlinker.builtin.logger_operation:console_output:18 -
================== cmd response details ==================
status_code : 0
execute_time : 228ms
stdout : [{"Id": "7393aa6334e6a091aae54790f5a6e3505f6dbab9515c5643978afbdc221975dd","Created": "2024-06-26T07:04:49.182101334Z","Path": "docker-entrypoint.sh","Args": ["--requirepass","yourpassword"],"State": {"Status": "running","Running": true,"Paused": false,"Restarting": false,"OOMKilled": false,"Dead": false,"Pid": 27544,"ExitCode": 0,"Error": "","StartedAt": "2024-06-26T07:04:49.54446658Z","FinishedAt": "0001-01-01T00:00:00Z"},"Image": "sha256:7614ae9453d1d87e740a2056257a6de7135c84037c367e1fffa92ae922784631","ResolvConfPath": "/var/lib/docker/containers/7393aa6334e6a091aae54790f5a6e3505f6dbab9515c5643978afbdc221975dd/resolv.conf","HostnamePath": "/var/lib/docker/containers/7393aa6334e6a091aae54790f5a6e3505f6dbab9515c5643978afbdc221975dd/hostname","HostsPath": "/var/lib/docker/containers/7393aa6334e6a091aae54790f5a6e3505f6dbab9515c5643978afbdc221975dd/hosts","LogPath": "/var/lib/docker/containers/7393aa6334e6a091aae54790f5a6e3505f6dbab9515c5643978afbdc221975dd/7393aa6334e6a091aae54790f5a6e3505f6dbab9515c5643978afbdc221975dd-json.log","Name": "/some-redis","RestartCount": 0,"Driver": "overlay2","Platform": "linux","MountLabel": "","ProcessLabel": "","AppArmorProfile": "","ExecIDs": null,"HostConfig": {"Binds": null,"ContainerIDFile": "","LogConfig": {"Type": "json-file","Config": {}},"NetworkMode": "default","PortBindings": {"6379/tcp": [{"HostIp": "","HostPort": "6379"}]},"RestartPolicy": {"Name": "no","MaximumRetryCount": 0},"AutoRemove": false,"VolumeDriver": "","VolumesFrom": null,"CapAdd": null,"CapDrop": null,"CgroupnsMode": "host","Dns": [],"DnsOptions": [],"DnsSearch": [],"ExtraHosts": null,"GroupAdd": null,"IpcMode": "private","Cgroup": "","Links": null,"OomScoreAdj": 0,"PidMode": "","Privileged": false,"PublishAllPorts": false,"ReadonlyRootfs": false,"SecurityOpt": null,"UTSMode": "","UsernsMode": "","ShmSize": 67108864,"Runtime": "runc","ConsoleSize": [0,0],"Isolation": "","CpuShares": 0,"Memory": 0,"NanoCpus": 0,"CgroupParent": "","BlkioWeight": 0,"BlkioWeightDevice": [],"BlkioDeviceReadBps": null,"BlkioDeviceWriteBps": null,"BlkioDeviceReadIOps": null,"BlkioDeviceWriteIOps": null,"CpuPeriod": 0,"CpuQuota": 0,"CpuRealtimePeriod": 0,"CpuRealtimeRuntime": 0,"CpusetCpus": "","CpusetMems": "","Devices": [],"DeviceCgroupRules": null,"DeviceRequests": null,"KernelMemory": 0,"KernelMemoryTCP": 0,"MemoryReservation": 0,"MemorySwap": 0,"MemorySwappiness": null,"OomKillDisable": false,"PidsLimit": null,"Ulimits": null,"CpuCount": 0,"CpuPercent": 0,"IOMaximumIOps": 0,"IOMaximumBandwidth": 0,"MaskedPaths": ["/proc/asound","/proc/acpi","/proc/kcore","/proc/keys","/proc/latency_stats","/proc/timer_list","/proc/timer_stats","/proc/sched_debug","/proc/scsi","/sys/firmware"],"ReadonlyPaths": ["/proc/bus","/proc/fs","/proc/irq","/proc/sys","/proc/sysrq-trigger"]},"GraphDriver": {"Data": {"LowerDir": "/var/lib/docker/overlay2/da926ce47de0e966f295ddf249be649da1f2c1ea162be28fb84a5c5d28db3d96-init/diff:/var/lib/docker/overlay2/cb066f8e71551530105d92d22aee4ac7d10beeff480389676e102be0d701e534/diff:/var/lib/docker/overlay2/b69aadb0a11079daa266a36401ba0ce147450516b500c645eff85176f8e7919b/diff:/var/lib/docker/overlay2/a57e79ac2a99e28c990210b22b831dcd8fad2c98d810de241fc8a3d99b64953a/diff:/var/lib/docker/overlay2/482ff82ce4a7763a75994358255c4e604abab9fcb77fd1f5f2d815a0a98a323b/diff:/var/lib/docker/overlay2/8e951ea76637a9804eb44f89eb8353a5a71da11d304056b22e2d1886e7da9c2d/diff:/var/lib/docker/overlay2/e88bc247f216bd19bd2329b2feeda9cc387df6241598b863b201cafe7ff0db3a/diff","MergedDir": "/var/lib/docker/overlay2/da926ce47de0e966f295ddf249be649da1f2c1ea162be28fb84a5c5d28db3d96/merged","UpperDir": "/var/lib/docker/overlay2/da926ce47de0e966f295ddf249be649da1f2c1ea162be28fb84a5c5d28db3d96/diff","WorkDir": "/var/lib/docker/overlay2/da926ce47de0e966f295ddf249be649da1f2c1ea162be28fb84a5c5d28db3d96/work"},"Name": "overlay2"},"Mounts": [{"Type": "volume","Name": "502c8ea157173ae65d24a2e0173c0e990e5ba1398013ad01a048e7a594a62608","Source": "/var/lib/docker/volumes/502c8ea157173ae65d24a2e0173c0e990e5ba1398013ad01a048e7a594a62608/_data","Destination": "/data","Driver": "local","Mode": "","RW": true,"Propagation": ""}],"Config": {"Hostname": "7393aa6334e6","Domainname": "","User": "","AttachStdin": false,"AttachStdout": false,"AttachStderr": false,"ExposedPorts": {"6379/tcp": {}},"Tty": false,"OpenStdin": false,"StdinOnce": false,"Env": ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","GOSU_VERSION=1.12","REDIS_VERSION=6.2.6","REDIS_DOWNLOAD_URL=http://download.redis.io/releases/redis-6.2.6.tar.gz","REDIS_DOWNLOAD_SHA=5b2b8b7a50111ef395bf1c1d5be11e6e167ac018125055daa8b5c2317ae131ab"],"Cmd": ["--requirepass","yourpassword"],"Image": "redis","Volumes": {"/data": {}},"WorkingDir": "/data","Entrypoint": ["docker-entrypoint.sh"],"OnBuild": null,"Labels": {}},"NetworkSettings": {"Bridge": "","SandboxID": "b98966d73f26b520343cc69867d8ac56dd3ac65655375c2edb047cabc9eec9c8","HairpinMode": false,"LinkLocalIPv6Address": "","LinkLocalIPv6PrefixLen": 0,"Ports": {"6379/tcp": [{"HostIp": "0.0.0.0","HostPort": "6379"}]},"SandboxKey": "/var/run/docker/netns/b98966d73f26","SecondaryIPAddresses": null,"SecondaryIPv6Addresses": null,"EndpointID": "63a20510e954d1b81181d319b2d004e68918a2926d3db1e71e846197b79c72e1","Gateway": "172.17.0.1","GlobalIPv6Address": "","GlobalIPv6PrefixLen": 0,"IPAddress": "172.17.0.2","IPPrefixLen": 16,"IPv6Gateway": "","MacAddress": "02:42:ac:11:00:02","Networks": {"bridge": {"IPAMConfig": null,"Links": null,"Aliases": null,"NetworkID": "7fdaf78d05fd8a3d1c3c2ae0151022ae8c326582524327e18403fdab4a26f16d","EndpointID": "63a20510e954d1b81181d319b2d004e68918a2926d3db1e71e846197b79c72e1","Gateway": "172.17.0.1","IPAddress": "172.17.0.2","IPPrefixLen": 16,"IPv6Gateway": "","GlobalIPv6Address": "","GlobalIPv6PrefixLen": 0,"MacAddress": "02:42:ac:11:00:02","DriverOpts": null}}}}
]
stderr :
Process finished with exit code 0
我们可以很方便的使用返回的status_code/stdout/stderr进行请求响应断言
OpenSourceTest cmdlinker 社区
欢迎小伙伴加群,讨论cmdlinker相关问题,或提出优化建议!
QQ群(自动化测试-夜行者):816489363
相关文章:
中间件自动化测试框架cmdlinker
背景 作为一个中间件的测试工程师,如何对于中间件提供的命令进行自动化的回归,这一直是一个难题,市面上好像缺乏了对于命令进行自动化回归的合理解决方案。 常见方式有下面两种: 直接写字符串的命令,然后使用各种编程…...
写一个类模板三个模板参数K,V,M,参数是函数(函数参数、lambda传参、函数指针)
cal是类的成员函数。cal的3个入参是func1(K),func2(K,V),func3(K,V,M),请写出cal,并在main函数中调用cal 在您给出的要求中,cal成员函数并不直接…...
STM32-笔记35-DMA(直接存储器访问)
一、什么叫DMA? DMA(Direct Memory Access,直接存储器访问)提供在外设与内存、存储器和存储器之间的高速数据传输使用。它允许不同速度的硬件装置来沟通,而不需要依赖于CPU,在这个时间中,CPU对于…...
数势科技:解锁数据分析 Agent 的智能密码(14/30)
一、数势科技引领数据分析变革 在当今数字化浪潮中,数据已然成为企业的核心资产,而数据分析则是挖掘这一资产价值的关键钥匙。数势科技,作为数据智能领域的领军者,以其前沿的技术与创新的产品,为企业开启了高效数据分析…...
ES6中定义私有属性详解
在ES6中,定义私有属性的方式相对传统的JavaScript有所不同。ES6并没有提供直接的语法来定义私有属性,但可以通过几种方法间接实现私有属性。 1. 使用Symbol来模拟私有属性 Symbol是一种新的数据类型,可以作为对象的键,并且它的值…...
@Cacheable 注解爆红(不兼容的类型。实际为 java. lang. String‘,需要 ‘boolean‘)
文章目录 1、org.springframework.cache.annotation.Cacheable2、javax.persistence.Cacheable Cacheable(value "findPAUserById", key "#id")public Optional<PAUser> findById(Integer id) {return paUserRepository.findById(id);}我真的要笑死…...
离散数学 期末笔记
命题符号化 使用等值演算法证明 求公式范式 在自然推理体系中构造下列推理的证明 在一阶逻辑中将下列命题符号化 设A、B、C、D是 Z 的子集 证明下列集合恒等式 二元关系 性质 没有空的 没有漏的 没有重复 函数...
物联网控制期末复习
第3章 物联网控制系统的过程通道设计 3.1 模拟量输出通道 3.1.1单模拟量输出通道的构成 计算机控制系统的模拟量输出通道将计算机产生的数字控制信号转换为模拟信号(电压或电流)作用于执行机构,以实现对被控对象的控制。 多D/A结构&#…...
Hypium纯血鸿蒙系统 HarmonyOS NEXT自动化测试框架
1、什么是Hypium Hypium是华为官方为鸿蒙操作系统开发的一款以python为语言的自动化测试框架。 引用华为官网介绍如下: DevEco Testing Hypium(以下简称Hypium)是HarmonyOS平台的UI自动化测试框架,支持开发者使用python语言为应用编写UI自动化测试脚本…...
李宏毅机器学习课程笔记01 | 1.Introduction of Machine/Deep Learning
笔记是在语雀上面做的,粘贴在CSND上可能存在格式错误 机器学习的本质就是借助机器寻找一个转换函数 根据函数的输出类型,可以将机器学习进行分类 regression 回归任务:函数输出时一个数值classification 分类任务:人类设定好选项…...
探索Docker Compose:轻松管理多容器应用
探索Docker Compose:轻松管理多容器应用 在现代软件开发中,容器化已经成为构建、部署和扩展应用的主流方式。而Docker Compose作为Docker生态系统的重要组成部分,可以简化多容器应用的管理。本文将深入探讨Docker Compose的核心功能及应用场…...
java中static和const和final的区别
static 关键字 static 关键字用于声明类的成员(方法或变量)为静态成员。静态成员属于类本身,而不是类的实例。换句话说,静态成员可以通过类名直接访问,而不需要实例化对象。 静态变量:属于类的所有对象共…...
[Win32/ATL]_[初级]_[处理WM_PAINT消息注意事项]
场景 在开发Win32/WTL程序时,遇到了使用CFolderDialog(atldlgs.h)打不开目录选择对话框的情况。具体表现是执行了窗口的DoModal,却没有窗口弹出来。 可以确定执行操作是在主线程,并不是工作线程。调试时暂停看堆栈,知道到DoModal方法里的SHB…...
【DevOps】Jenkins项目发布
Jenkins项目发布 文章目录 Jenkins项目发布前言资源列表基础环境一、Jenkins发布静态网站1.1、项目介绍1.2、部署Web1.3、准备gitlab1.4、配置gitlab1.5、创建项目1.6、推送代码 二、Jenkins中创建gitlab凭据2.1、创建凭据2.2、在Jenkins中添加远程主机2.3、获取gitlab项目的UR…...
Lua迭代器如何使用?
在Lua中,迭代器是一种用于遍历集合元素的重要工具。掌握迭代器的使用方法,对于提高Lua编程的效率和代码的可读性具有重要意义。 1.迭代器概述 12.1.1 迭代器介绍 迭代器是一种设计模式,它提供了一种访问集合元素的方法,而不需要…...
cesium小知识:3D tiles 概述、特点、示例
Cesium 的 3D Tiles 是一种高效的、流式传输的三维地理空间数据格式,专为在Web浏览器中快速渲染大规模三维场景而设计。3D Tiles 支持多种几何类型,包括点云、多边形、模型等,并且可以包含丰富的属性信息和层次细节(LOD, Level of Detail)结构,以确保不同设备和网络条件下…...
PTA DS 基础实验3-2.1 一元多项式求导
基础实验3-2.1 一元多项式求导 分数 20 全屏浏览 切换布局 作者 DS课程组 单位 浙江大学 设计函数求一元多项式的导数。 输入格式: 以指数递降方式输入多项式非零项系数和指数(绝对值均为不超过1000的整数)。数字间以空格分隔。 注意:…...
【玩转全栈】----用户管理案例
目录 案例需求: 成果显示: 源码展示: 部分源码解释及注意 1、info_list.html文件 2、info_add.html文件 3、models.py文件 4、views.py文件 经过前面的学习,相信您对Django、MySQL,以及他们之间的连接已经非常熟悉了&a…...
文件上传漏洞利用与绕过姿势总结
文章目录 攻击与绕过方式一、条件竞争二、二次渲染结合文件包含绕过1、gif2、png3、jpg 三、.htaccess解析绕过四、文件后缀名绕过1、文件特殊后缀名大小写绕过2、::$DATA绕过3、双后缀名绕过4、点绕过5、空格绕过 五、文件头绕过六、短标签绕过七、MIME(Content-Type)绕过八、…...
Java十六
2-9-1Request和Response介绍 Request继承体系 ServletRequest————Java提供的请求对象根接口 Http ServletRequest————Java提供的对Http协议封装的请求对象接口 RequestFacade————Tomcat定义的实现类 1.Tomcat需要解析请求数据,封装为request对象,并且创建requ…...
人工智能在事件管理中的应用
随着科技的不断发展,人工智能(AI)正在深入到各行各业,运维领域也不例外。在事件管理中,AI通过其强大的数据分析和自动化能力,帮助团队更高效地处理系统事件,提升业务的稳定性和用户体验。 什么是事件管理? 事件管理是IT运维的重要组成部分,其核心目标是快速响应和处…...
Launcher3版本确定
Launcher3有几个不同的版本,引入的代码和资源文件有所不同,我们需要确认当前设备中使用的是哪个 查看bp脚本可以看到会生成四个app /packages/apps/Launcher3$ grep -nr -E "android\_app\ \{" -A 1 ./Android.bp 184:android_app { 185- …...
算法题(25):只出现一次的数字(三)
审题: 该题中有两个元素只出现一次并且其他元素都出现两次,需要返回这两个只出现一次的数,并且不要求返回顺序 思路: 由于对空间复杂度有要求,我们这里不考虑哈希表。我们采用位运算的方法解题 方法:位运算 首先&#…...
atrust异常导致ERR_NETWORK_CHANGED
首先因为工作需要不断安装卸载不同版本深信服的atrust。那么可能遇到和我一样的问题。 深信服的这种东西有点毛病,以前只是偶尔导致我局域网无法访问,我停止atrust后,他还有后台程序在后台不断更改我的适配器,在我局域网需要固定…...
【Infineon AURIX】AURIX缓存(CACHE)变量访问指南
AURIX缓存变量访问指南 引言 本文分析Infineon AURIX控制器在调试过程中访问缓存内存变量的问题及解决方案重点探讨了变量缓存对调试的影响以及多种解决方法的优劣第1部分:问题描述与成因分析 主要症状 变量值发生变化,但实时内存访问显示初始值Watch窗口和Memory窗口中的变…...
轻量级通信协议 JSON-RPC 2.0 详解
目录 JSON-RPC 2.0 简介 请求对象 响应对象 通知 批量请求 错误码 使用场景 文档和版本控制 社区和支持 小结 参考资料 JSON-RPC 2.0 简介 JSON-RPC (JavaScript Object Notation - Remote Procedure Call) 是一种轻量级的远程过程调用协议,使用 JSON&am…...
[读书日志]从零开始学习Chisel 第一篇:书籍介绍,Scala与Chisel概述,Scala安装运行(敏捷硬件开发语言Chisel与数字系统设计)
简介:从20世纪90年代开始,利用硬件描述语言和综合技术设计实现复杂数字系统的方法已经在集成电路设计领域得到普及。随着集成电路集成度的不断提高,传统硬件描述语言和设计方法的开发效率低下的问题越来越明显。近年来逐渐崭露头角的敏捷化设…...
深入了解 StarRocks 表类型:解锁高效数据分析的密码
在当今数字化浪潮下,大数据分析成为企业决策、优化业务流程的关键利器。StarRocks 作为一款备受瞩目的高性能分析型数据库,其多样化的表类型为复杂的数据处理需求提供了精准解决方案。今天,就让我们一同深入探索 StarRocks 中的主键表、明细表…...
spring mvc源码学习笔记之四
pom.xml 内容如下 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/P…...
Spring Boot + Redis + Sa-Token
参考文献 Sa-Token实现分布式登录鉴权(Redis集成 前后端分离)-腾讯云开发者社区-腾讯云 介绍 StpInterface 是 Sa-Token 框架中的一个接口,属于 Sa-Token 身份认证与授权框架的一部分。该接口提供了一些方法来实现自定义的身份认证和授权管…...
【顶刊TPAMI 2025】多头编码(MHE)之Part 6:极限分类无需预处理
目录 1 标签分解方法的消融研究2 标签分解对泛化的影响3 讨论4 结论 论文:Multi-Head Encoding for Extreme Label Classification 作者:Daojun Liang, Haixia Zhang, Dongfeng Yuan and Minggao Zhang 单位:山东大学 代码:https:…...
Spring Certified Professional 2024 (2V0-72.22)
关于认证 Spring Certified Professional (2V0-72.22) 认证可证明您在 Spring Framework 方面的专业知识,Spring Framework 是构建企业级 Java 应用程序的领先平台。此认证在全球范围内得到认可,并证明您在 Spring 的各个方面都具有熟练程度,…...
asp.net core框架搭建4-部署IIS/Nginx/Docker
文章目录 系列文章一、Linux上部署Nginx1.1 Centos 安装配置环境1.2 使用Systemctl 控制Nginx 二、部署IIS三、部署Docker3.1 创建 Dockerfile 文件3.2 构建 Docker 镜像3.3 运行 Docker 容器3.4 检查容器运行情况 结束语 作者:xcLeigh 文章地址:https:/…...
改善 Kibana 中的 ES|QL 编辑器体验
作者:来自 Elastic Marco Liberati 随着新的 ES|QL 语言正式发布,Kibana 中开发了一种新的编辑器体验,以帮助用户编写更快、更好的查询。实时验证、改进的自动完成和快速修复等功能将简化 ES|QL 体验。 我们将介绍改进 Kibana 中 ES|QL 编辑器…...
webpack5基础(上篇)
一、基本配置 在开始使用 webpack 之前,我们需要对 webpack 的配置有一定的认识 1、5大核心概念 1)entry (入口) 指示 webpack 从哪个文件开始打包 2)output(输出) 制视 webpack 打包完的…...
C#设计模式(行为型模式):观察者模式
C#设计模式:观察者模式,让对象间通信更优雅 在软件开发中,我们经常会遇到一个对象的状态发生改变,其他对象需要自动更新或做出相应反应的场景。例如: GUI事件处理: 当用户点击按钮时,按钮需要…...
pg_wal 目录下 wal 日志文件异常累积过大
文章目录 背景当前配置分析解决过程1. 活动事务未完成2. 备份滞后或归档未完成3. 保留了过多的 WAL 文件4. 逻辑复制槽未释放5. 文件系统问题6. 强制触发 WAL 清理结果lsof D 是啥意思检查进程从名字来看, 该 wal 文件是最小的文件(一般也是最老的 wal 文件)pg_archivecleanup …...
【人工智能】用Python实现深度卷积生成对抗网络(DCGAN):原理、实现与优化
《Python OpenCV从菜鸟到高手》带你进入图像处理与计算机视觉的大门! 解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界 深度卷积生成对抗网络(DCGAN)是一种结合了卷积神经网络(CNN)和生成对抗网络(GAN)的深度学习模型,广泛应用于图像生成、图像增强、以…...
领域驱动设计(4)—绑定模型与实现
(4)—绑定模型与实现 模式:MODEL-DRIVEN DESIGN为什么模型对用户至关重要?模式:HANDS-ON MODELER 很多项目设计之初只考虑到模型如何设计,没有将模型如何实现、数据关系如何存储这些实现考虑在内,往往设计…...
logback日志框架源码分析
目录 (一)入口:slf4j选择日志框架 (二)日志框架初始化 (1)logback的3种配置方式 a、BasicConfigurator默认配置 b、SPI方式配置的Configurator实现类 c、通过配置文件初始化 (2)xml配置文件初始化 (三)Logger的创建 (四)打印日志 本文源码基于:logback版…...
Airflow:HttpSensor实现API驱动数据流程
数据管道工作流通常依赖于api来访问、获取和处理来自外部系统的数据。为了处理这些场景,Apache Airflow提供了HttpSensor,这是一个内置的Sensor,用于监视HTTP请求的状态,并在满足指定条件时触发后续任务。在这篇博文中,…...
在Mac电脑上搭建Gradle
1. 检查是否已安装Homebrew 打开终端,输入以下命令检查Homebrew是否已安装: brew -v如果显示版本号,则表示已安装。如果未安装,请运行以下命令安装Homebrew: /bin/bash -c "$(curl -fsSL https://raw.githubus…...
登录的几种方式
使用Session完成登录 1. 手机号发送验证码 逻辑步骤: 校验手机号格式是否正确。生成验证码(例如使用Hutool工具类)。将手机号和验证码存入Session。返回验证码发送成功的响应。 2. 用户登录逻辑 逻辑步骤: 从Session中获取存…...
Python学习(5):数据结构
1 列表 1.1 列表方法 列表数据类型支持很多方法,列表对象的所有方法所示如下: list.append(x):在列表末尾添加一项。 类似于 a[len(a):] [x]。list.extend(iterable):通过添加来自 iterable 的所有项来扩展列表。 类似于 a[len…...
第五届电网系统与绿色能源国际学术会议(PGSGE 2025)
2025年第五届电网系统与绿色能源国际学术会议(PGSGE 2025) 定于2025年01月10-12日在吉隆坡召开。 第五届电网系统与绿色能源国际学术会议(PGSGE 2025) 基本信息 会议官网:www.pgsge.org【点击投稿/了解会议详情】 会议时间:202…...
【顶刊TPAMI 2025】多头编码(MHE)之极限分类 Part 1:背景动机
目录 1 简单概括2 几个重要发现3 主要贡献4 背景知识5 方法简介 论文:Multi-Head Encoding for Extreme Label Classification 作者:Daojun Liang, Haixia Zhang, Dongfeng Yuan and Minggao Zhang 单位:山东大学 代码:https://gi…...
ruckus R510升级到Unleashe后不能访问
ruckus R510 是IPQ4019,升级到Unleashe,它弹窗提示 但是这个IP没办法用,访问不了AP。 必应了一下,官方提示用advance ip scanner扫描。 扫描持续好久,发现IP竟然是从主路由获得。 9090的端口不用填,甚至不…...
初学stm32 --- FSMC驱动LCD屏
目录 FSMC简介 FSMC框图介绍 FSMC通信引脚介绍 FSMC_NWE 的作用 FSMC_NWE 的时序关系 FSMC_NOE 的含义 FSMC_NOE 的典型用途 FSMC_NOE 的时序关系 使用FSMC驱动LCD FSMC时序介绍 时序特性中的 OE ILI9341重点时序: FSMC地址映射 HADDR与FSMC_A关系 LCD的…...
【2025最新计算机毕业设计】基于Spring Boot+Vue影院购票系统(高质量源码,提供文档,免费部署到本地)
作者简介:✌CSDN新星计划导师、Java领域优质创作者、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流。✌ 主要内容:🌟Java项目、Python项目、前端项目、PHP、ASP.NET、人工智能…...
Java 内存溢出(OOM)问题的排查与解决
在 Java 开发中,内存溢出(OutOfMemoryError,简称 OOM)是一个常见且棘手的问题。相比于数组越界、空指针等业务异常,OOM 问题通常更难定位和解决。本文将通过一次线上内存溢出问题的排查过程,分享从问题表现…...