Spring之Aop切面---日志收集(环绕处理、前置处理方式)--使用/教程/实例
Spring之Aop切面---日志收集(环绕处理、前置处理方式)--使用/教程/实例
- 简介
- 系统登录日志类LoginLogEntity .java
- 一、环绕处理方式
- 1、自定义注解类LoginLogAop.class
- 2、切面处理类LogoutLogAspect.java
- 二、前置处理方式:
- 1、自定义注解类LogoutLogAop.class
- 2、切面处理类LogoutLogAspect.java
- 三、Proceedingjoinpoint简述
简介
本文章介绍采用两种不同方式处理----系统登录、系统退出登录两种场景日志。
- 环绕处理系统登录日志
- 前置处理系统退出登录日志
系统登录日志类LoginLogEntity .java
package com.fy.test.entity;import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;import java.io.Serializable;
import java.time.LocalDateTime;import com.baomidou.mybatisplus.annotation.TableId;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;/*** @ClassName: LoginLogEntity * @Description: * @Author fy* @Date 2023/07/10 9:00*/
@Data
@Accessors(chain = true)
@TableName("t_login_log")
public class LoginLogEntity implements Serializable {private static final long serialVersionUID = 1L;/*** 主键*/@TableId(value = "id", type = IdType.ASSIGN_ID)private String id;/*** 操作系统*/private String opOs;/*** 浏览器类型*/private String opBrowser;/*** 登录IP地址*/private String opIp;/*** 登录时间*/private LocalDateTime opDate;/*** 登录用户ID*/private String userId;/*** 登录用户名称*/private String userName;/*** 错误类型*/private String exCode;/*** 错误信息*/private String exMsg;/*** 登录状态*/private boolean status;/*** 描述*/private String desc;
}
一、环绕处理方式
1、自定义注解类LoginLogAop.class
package com.fy.test.log.annotation;import java.lang.annotation.*;/*** @ClassName: LoginLogAop* @Description: * @Author fy* @Date 2023/07/10 9:05*/
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LoginLogAop {/*** 描述*/String desc() default "";}
2、切面处理类LogoutLogAspect.java
package com.fy.test.log.aspect;import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import com.fy.test.log.annotation.LoginLogAop;
import com.fy.test.utils.RequestHolder;
import com.fy.test.utils.SecurityUtil;
import com.fy.test.service.dto.LoginLogDto;
import com.fy.test.service.feign.LogServiceFeign;
import com.fy.test.service.vo.UserVo;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.multipart.MultipartFile;import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;/*** @ClassName: LoginLogAspect* @Description:* @Author fy* @Date 2023/07/10 9:05*/
@Slf4j
@Aspect
public class LoginLogAspect {@Autowiredprivate LogServiceFeign logServiceFeign;/*** 配置织入点*/@Pointcut("@annotation(com.fy.test.common.log.annotation.LoginLogAop)")public void logPointCut() {}/*** 通知方法会将目标方法封装起来* 注意:环绕方式选择ProceedingJoinPoint* Proceedingjoinpoint 继承了JoinPoint,在JoinPoint的基础上暴露出 proceed(), 这个方法是AOP代理链执行的方法。* JoinPoint仅能获取相关参数,无法执行连接点。* 暴露出proceed()这个方法,就能支持 aop:around 这种切面(而其他的几种切面只需要用到JoinPoint,这跟切面类型有关),* 就能控制走代理链还是走自己拦截的其他逻辑。 * * @param joinPoint 切点*/@Around(value = "logPointCut()")public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {Object result = joinPoint.proceed();LoginLogDto logDto = getLog();logDto.setStatus(true);handleLog(joinPoint, logDto);return result;}/*** 通知方法会在目标方法抛出异常后执行** @param joinPoint* @param e*/@AfterThrowing(value = "logPointCut()", throwing = "e")public void doAfterThrowing(JoinPoint joinPoint, Exception e) {LoginLogDto logDto = getLog();logDto.setExCode(e.getClass().getSimpleName()).setExMsg(e.getMessage());logDto.setStatus(false);handleLog(joinPoint, logDto);}private LoginLogDto getLog() {HttpServletRequest request = RequestHolder.getHttpServletRequest();UserAgent userAgent = UserAgentUtil.parse(request.getHeader("User-Agent"));LoginLogDto loginLog = new LoginLogDto();loginLog.setOpIp(ServletUtil.getClientIP(request)).setOpOs(userAgent.getOs().getName()).setOpBrowser(userAgent.getBrowser().getName()).setUserId(SecurityUtil.getUserId()).setUserName(SecurityUtil.getUserName()).setOpDate(LocalDateTime.now());return loginLog;}protected void handleLog(final JoinPoint joinPoint, LoginLogDto loginLogDto) {// 获得注解LoginLogAop logAop = getAnnotationLog(joinPoint);if (null == logAop) {return;}loginLogDto.setDescription(logAop.description());Map<String, Object> requestParams = getRequestParams(joinPoint);if (requestParams.containsKey("userVo")) {UserVo userVo = JSONObject.parseObject(JSON.toJSONString(requestParams.get("userVo")), UserVo.class);if (null != userVo && StringUtils.isBlank(loginLogDto.getUserName())) {loginLogDto.setUserName(userVo.getUsername());}}// 保存数据库logServiceFeign.saveLoginLog(loginLogDto);}/*** 是否存在注解,如果存在就获取*/private LoginLogAop getAnnotationLog(JoinPoint joinPoint) {Signature signature = joinPoint.getSignature();MethodSignature methodSignature = (MethodSignature) signature;Method method = methodSignature.getMethod();if (method != null) {return method.getAnnotation(LoginLogAop.class);}return null;}/*** 获取入参*/private Map<String, Object> getRequestParams(JoinPoint joinPoint) {Map<String, Object> requestParams = new HashMap<>();// 参数名String[] paramNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();// 参数值Object[] paramValues = joinPoint.getArgs();for (int i = 0; i < paramNames.length; i++) {Object value = paramValues[i];// 如果是文件对象if (value instanceof MultipartFile) {MultipartFile file = (MultipartFile) value;// 获取文件名value = file.getOriginalFilename();}requestParams.put(paramNames[i], value);}return requestParams;}
}
二、前置处理方式:
1、自定义注解类LogoutLogAop.class
package com.fy.test.log.annotation;import java.lang.annotation.*;/*** @ClassName: LogoutLogAop* @Description: * @Author fy* @Date 2023/07/10 9:10*/
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogoutLogAop {/*** 描述*/String desc() default "";}
2、切面处理类LogoutLogAspect.java
package com.fy.test.log.aspect;import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import com.fy.test.log.annotation.LoginLogAop;
import com.fy.test.log.annotation.LogoutLogAop;
import com.fy.test.utils.RequestHolder;
import com.fy.test.utils.SecurityUtil;
import com.fy.test.service.dto.LoginLogDto;
import com.fy.test.service.feign.LogServiceFeign;
import com.fy.test.service.vo.UserVo;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;/*** @ClassName: LogoutLogAspect* @Description:* @Author fy* @Date 2023/07/10 9:10*/
@Slf4j
@Aspect
public class LogoutLogAspect {@Autowiredprivate LogServiceFeign logServiceFeign;/*** 配置织入点*/@Pointcut("@annotation(com.fy.test.log.annotation.LogoutLogAop)")public void logPointCut() {}/*** 通知方法会将目标方法封装起来** @param joinPoint 切点*/@Before("logPointCut()")public void doBefore(JoinPoint joinPoint) throws Throwable {try {System.out.println("==============前置处理开始==============");LoginLogDto logDto = getLog();logDto.setStatus(true);handleLog(joinPoint, logDto);} catch (Exception e) {//记录本地异常日志log.error("==前置通知异常==");log.error("异常信息:{}", e.getMessage());}}/*** 通知方法会在目标方法抛出异常后执行** @param joinPoint* @param e*/@AfterThrowing(value = "logPointCut()", throwing = "e")public void doAfterThrowing(JoinPoint joinPoint, Exception e) {LoginLogDto logDto = getLog();logDto.setExCode(e.getClass().getSimpleName()).setExMsg(e.getMessage());logDto.setStatus(false);handleLog(joinPoint, logDto);}private LoginLogDto getLog() {HttpServletRequest request = RequestHolder.getHttpServletRequest();UserAgent userAgent = UserAgentUtil.parse(request.getHeader("User-Agent"));LoginLogDto loginLogDto = new LoginLogDto();loginLogDto.setOpIp(ServletUtil.getClientIP(request)).setOpOs(userAgent.getOs().getName()).setOpBrowser(userAgent.getBrowser().getName()).setUserId(SecurityUtil.getUserId()).setUserName(SecurityUtil.getUserName()).setOpDate(LocalDateTime.now());return loginLogDto;}protected void handleLog(final JoinPoint joinPoint, LoginLogDto loginLogDto) {// 获得注解LogoutLogAop logAop = getAnnotationLog(joinPoint);if (null == logAop) {return;}loginLogDto.setDesc(logAop.desc());// 保存数据库logServiceFeign.saveLoginLog(loginLogDto);}/*** 是否存在注解,如果存在就获取*/private LogoutLogAop getAnnotationLog(JoinPoint joinPoint) {Signature signature = joinPoint.getSignature();MethodSignature methodSignature = (MethodSignature) signature;Method method = methodSignature.getMethod();if (method != null) {return method.getAnnotation(LogoutLogAop.class);}return null;}/*** 获取入参*/private Map<String, Object> getRequestParams(JoinPoint joinPoint) {Map<String, Object> requestParams = new HashMap<>();// 参数名String[] paramNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();// 参数值Object[] paramValues = joinPoint.getArgs();for (int i = 0; i < paramNames.length; i++) {Object value = paramValues[i];// 如果是文件对象if (value instanceof MultipartFile) {MultipartFile file = (MultipartFile) value;// 获取文件名value = file.getOriginalFilename();}requestParams.put(paramNames[i], value);}return requestParams;}
}
三、Proceedingjoinpoint简述
Proceedingjoinpoint 继承了JoinPoint,在JoinPoint的基础上暴露出 proceed(), 这个方法是AOP代理链执行的方法。
JoinPoint仅能获取相关参数,无法执行连接点。暴露出proceed()这个方法,就能支持 aop:around 这种切面(而其他的几种切面只需要用到JoinPoint,这跟切面类型有关),就能控制走代理链还是走自己拦截的其他逻辑。
import org.aspectj.lang.reflect.SourceLocation;
public interface JoinPoint { String toString(); //连接点所在位置的相关信息 String toShortString(); //连接点所在位置的简短相关信息 String toLongString(); //连接点所在位置的全部相关信息 Object getThis(); //返回AOP代理对象,也就是com.sun.proxy.$Proxy18Object getTarget(); //返回目标对象,一般我们都需要它或者(也就是定义方法的接口或类,为什么会是接口呢?//这主要是在目标对象本身是动态代理的情况下,例如Mapper。所以返回的是定义方法的对象如//aoptest.daoimpl.GoodDaoImpl或com.b.base.BaseMapper<T, E, PK>)Object[] getArgs(); //返回被通知方法参数列表 Signature getSignature(); //返回当前连接点签名。其getName()方法返回方法的FQN,如void aoptest.dao.GoodDao.delete()//或com.b.base.BaseMapper.insert(T)(需要注意的是,很多时候我们定义了子类继承父类的时候,//我们希望拿到基于子类的FQN,无法直接拿到,要依赖于//AopUtils.getTargetClass(point.getTarget())获取原始代理对象,下面会详细讲解)SourceLocation getSourceLocation();//返回连接点方法所在类文件中的位置 String getKind(); //连接点类型 StaticPart getStaticPart(); //返回连接点静态部分 } public interface ProceedingJoinPoint extends JoinPoint { public Object proceed() throws Throwable; public Object proceed(Object[] args) throws Throwable; }
JoinPoint.StaticPart:提供访问连接点的静态部分,如被通知方法签名、连接点类型等等。
public interface StaticPart { Signature getSignature(); //返回当前连接点签名 String getKind(); //连接点类型 int getId(); //唯一标识 String toString(); //连接点所在位置的相关信息 String toShortString(); //连接点所在位置的简短相关信息 String toLongString(); //连接点所在位置的全部相关信息
}
相关文章:
Spring之Aop切面---日志收集(环绕处理、前置处理方式)--使用/教程/实例
Spring之Aop切面---日志收集(环绕处理、前置处理方式)--使用/教程/实例 简介系统登录日志类LoginLogEntity .java 一、环绕处理方式1、自定义注解类LoginLogAop.class2、切面处理类LogoutLogAspect.java 二、前置处理方式:1、自定义注解类Log…...
UE4/UE5 照明构建失败 “Lightmass crashed”解决“数组索引越界”
在构建全局光照时,经常会出现“Lightmass crashed”的错误,导致光照构建失败。本文将分析这一问题的原因,并给出解决建议。 UE4 版本4.26 报错如下: <None> Lightmass crashed: Assertion failed: (Index > 0) & (Index < ArrayNum) [File:d:\bu…...
并发编程系列-Semaphore
Semaphore,如今通常被翻译为"信号量",过去也曾被翻译为"信号灯",因为类似于现实生活中的红绿灯,车辆是否能通行取决于是否是绿灯。同样,在编程世界中,线程是否能执行取决于信号量是否允…...
3年 Android 开发的面试心经(后悔当初没有拿 N+1)
作者:勇闯天涯 当某人顺利通过大厂面试时,总会有人认为这是运气比较好罢了,但他们不曾得知对方之前受过多少苦和委屈,又付出了多少努力一步步去突破这些困境。正是因为他们的努力付出,在合适的时间与地点,用…...
【c语言】 -- 指针进阶
📕博主介绍:目前大一正在学习c语言,数据结构,计算机网络。 c语言学习,是为了更好的学习其他的编程语言,C语言是母体语言,是人机交互接近底层的桥梁。 本章来学习指针进阶。 让我们开启c语言学习…...
软件压力测试对软件产品起到什么作用?
一、软件压力测试是什么? 软件压力测试是一种通过模拟正常使用环境中可能出现的大量用户和大数据量的情况,来评估软件系统在压力下的稳定性和性能表现的测试方法。在软件开发过程中,经常会遇到一些性能瓶颈和稳定性问题,而软件压力测试的作…...
Stephen Wolfram:那么…ChatGPT 在做什么,为什么它有效呢?
So … What Is ChatGPT Doing, and Why Does It Work? 那么…ChatGPT在做什么,为什么它有效呢? The basic concept of ChatGPT is at some level rather simple. Start from a huge sample of human-created text from the web, books, etc. Then train…...
机器学习基础(五)
决策树 决策树是一种预测模型,它代表着对象属属性与对象值之间的一种映射关系。树中的每个节点代表一个对象,分叉路径(或者叫树枝)则代表一个属性值。 决策树常用方法: 分类树分析,是一种监督学习,用于预计结果可能为离散类型。 回归树分析,用于预计结果为实数。 CART,…...
阿里云服务器安装WordPress网站教程基于CentOS系统
阿里云百科分享使用阿里云服务器安装WordPress博客网站教程,WordPress是使用PHP语言开发的博客平台,在支持PHP和MySQL数据库的服务器上,您可以用WordPress架设自己的网站,也可以用作内容管理系统(CMS)。本教…...
【100天精通python】Day37:GUI界面编程_PyQT从入门到实战(上)
目录 专栏导读 1 PyQt6 简介: 1.1 安装 PyQt6 和相关工具: 1.2 PyQt6 基础知识: 1.2.1 Qt 的基本概念和组件: 1.2.2 创建和使用 Qt 窗口、标签、按钮等基本组件 1.2.3 布局管理器:垂直布局、水平布局、网格布局…...
数据结构—散列表的查找
7.4散列表的查找 7.4.1散列表的基本概念 基本思想:记录的存储位置域关键字之间存在对应关系 对应关系——hash函数 Loc(i) H(keyi) 如何查找: 根据散列函数 H(key) k 查找key9,则访…...
Expo项目 使用Native base UI库
装包: yarn add native-base expo install react-native-svg12.1.1 Index.js: import React from react import { View, Text } from react-native import useList from ./useList import { NativeBaseProvider, Button, Box } from native-base import styles f…...
74、75、76——tomcat项目实战
tomcat项目实战 tomcat 依赖 java运行环境,必须要有jre , 选择 jdk1.8 JvmPertest 千万不能用 kyj易捷支付 项目机器 选择 一台机器 ,安装jdk1.8的机器下载tomcat的包 上传到机器,解压tomcattomcat文件 bin文件夹: 启动文件 堆栈配置文件 catalina.sh JAVA_OPTS="-Xm…...
jmeter errstr :“unsupported field type for multipart.FileHeader“
在使用jmeter测试接口的时候,提示errstr :"unsupported field type for multipart.FileHeader"如图所示 这是因为我们 在HTTP信息头管理加content-type参数有问题 直接在HTTP请求中,勾选: use multipart/form-data for POST【中文…...
C#调用C++ DLL传参byte[]数组字节值大于127时会变为0x3f的问题解决
最近做了一个网络编程的DLL给C#调用,DLL中封装了一个TCP Client的函数接口,如下所示 //C TCP报文发送接口 int TcpClient_send(unsigned char* buffSend, unsigned int nLen) {unsigned char buff[1024];int len StringToHex(buffSend, buff);int nRet…...
【vue3+xlxs+xlsx-style-vite】vue3项目中使用xlsx插件实现Excel表格的导出和解析,已实现
在vue3项目中使用xlsx插件实现Excel表格的导出和解析 1、xlsx插件包官方 xlsx插件包官方 2、FileReader官方文档:FileReader官方文档 安装xlsx和xlsx-style-vite、file-saver npm install xlsx npm install xlsx-style-vite npm install file-saverpackage.json中查…...
Doris2.0时代的一些机遇和挑战!
300万字!全网最全大数据学习面试社区等你来! 上个周五的时候,Doris官宣了2.0版本,除了在性能上的大幅提升,还有一些特性需要大家特别关注。 根据官网的描述,Doris在下面领域都有了长足进步: 日志…...
Leetcode-每日一题【剑指 Offer 32 - I. 从上到下打印二叉树】
题目 从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。 例如: 给定二叉树: [3,9,20,null,null,15,7], 3 / \ 9 20 / \ 15 7 返回: [3,9,20,15,7] 提示: 节点总数 < 1000 解题思路 1.题目要求我们从…...
网神 SecGate 3600 防火墙任意文件上传漏洞复现
0x01 产品简介 网神SecGate3600下一代极速防火墙(NSG系列)是基于完全自主研发、经受市场检验的成熟稳定网神第三代SecOS操作系统 并且在专业防火墙、VPN、IPS的多年产品经验积累基础上精心研发的高性能下一代防火墙 专门为运营商、政府、军队、教育、大型…...
把独显塞回CPU,新核显能够媲美RTX 30、40系显卡了
上个月,AMD 发布了 Zen4 架构 R5 7600X 的无核显版 - 7500F 。 各种数据评测和玩家实际体验大家也已经看过了,说是变相降价一点不错。 原因也很简单,感谢 Intel 。 Jon Peddie Research 刚出炉报告显示,2023 第二季度 AMD 客户端…...
高效管理惠普OMEN游戏本:OmenSuperHub全面解析与实战指南
高效管理惠普OMEN游戏本:OmenSuperHub全面解析与实战指南 【免费下载链接】OmenSuperHub 项目地址: https://gitcode.com/gh_mirrors/om/OmenSuperHub OmenSuperHub是一款专为惠普OMEN系列游戏本设计的轻量级系统管理工具,它通过替代原厂Omen Ga…...
华为MateBook D14安装Ubuntu16避坑指南:WiFi/蓝牙/触控板驱动一键搞定
华为MateBook D14安装Ubuntu 16.04驱动优化全攻略 华为MateBook D14作为一款高性价比轻薄本,在安装Ubuntu 16.04时可能会遇到WiFi、蓝牙和触控板驱动不兼容的问题。这主要源于硬件迭代速度远超Linux内核更新周期——你的笔记本搭载了新一代无线网卡和输入设备&#…...
2026年3月27日NSSCTF之[SWPUCTF 2021 新生赛]ez_unserialize
[SWPUCTF 2021 新生赛]ez_unserialize 开启环境,进入并查看,可以看到一个动图,选择查看网页源码,得到 看到有隐藏信息,根据隐藏信息可以猜测,可以利用robots协议查看相关信息,访问得到 可以得…...
嵌入式软件架构设计:硬件抽象层实践
嵌入式软件架构设计:建立硬件抽象层的工程实践 1. 嵌入式软件架构概述 1.1 架构设计的必要性 在嵌入式系统开发中,软件架构设计直接影响产品的可维护性、可扩展性和可移植性。良好的架构设计能够: 减少不必要的返工 建立宏观层面的开发规…...
SQLite.Interop.DLL加载失败的3种修复方案 - 从运行库到项目配置全搞定
SQLite.Interop.DLL加载失败的终极解决方案:从运行环境到项目配置深度解析 当你正在开发一个依赖SQLite数据库的C#项目时,突然遇到"无法加载DLLSQLite.Interop.DLL"的错误提示,这绝对是一个令人头疼的问题。作为一名有多年.NET开发…...
WebPlotDigitizer图表数据提取工具:科研工作者的终极数字化解决方案
WebPlotDigitizer图表数据提取工具:科研工作者的终极数字化解决方案 【免费下载链接】WebPlotDigitizer WebPlotDigitizer: 一个基于 Web 的工具,用于从图形图像中提取数值数据,支持 XY、极地、三角图和地图。 项目地址: https://gitcode.c…...
解锁TikTok电商API:PHP开发者的零门槛接入方案
解锁TikTok电商API:PHP开发者的零门槛接入方案 【免费下载链接】tiktokshop-php Unofficial Tiktok Shop API Client in PHP. Use API version 202309 and later 项目地址: https://gitcode.com/gh_mirrors/ti/tiktokshop-php 跨境电商API对接新选择…...
Mac 版 SSH 登录脚本
Mac 版 SSH 登录脚本 整合原有编码机器人 + 新增飞书运营机器人,分区域展示、带完整名称/备注/专线IP,一键登录,Mac 专属、直接可用! 前置准备(仅执行1次) brew install sshpass完整脚本(复制保存为 robot_ssh.sh) #!/bin/bash # Mac 专用 - 编码机器人 + 飞书机器…...
用快马AI快速原型设计:9·1免费素材库管理界面十分钟搭建指南
最近在帮朋友设计一个免费素材库的管理界面,需求是要快速搭建一个能展示"91免费素材"的网页应用。作为一个经常需要验证设计想法的开发者,我发现用InsCode(快马)平台可以大大缩短原型开发时间。下面分享下我是如何在十分钟内完成这个素材库管理…...
[DRAM Test]从入门到精通:全面解析DRAM内存测试工具与实战故障排查
1. DRAM测试工具全景解析 内存作为计算机系统的核心组件,其稳定性直接影响整机性能。我经手过的蓝屏案例中,超过60%最终都指向内存问题。目前市面上的DRAM测试工具主要分为三大类: 应用层工具以HCI MemTest为代表,这类工具运行在操…...
