当前位置: 首页 > news >正文

网站开发 不好 怎么说嵌入式软件开发工具有哪些

网站开发 不好 怎么说,嵌入式软件开发工具有哪些,兰州专业网站建设公司哪家好,免费下载简历模板网站目录 前言 方法一、将接口的参数和结果打印在日志文件中   1、使用aop监控接口   2、增加requestId   3、效果如下图   4、接口监控遇到的一些坑 方法二、将风险高的操作保存到数据库中   1、新建一张log表#xff0c;存储风险操作   2、新建Log注解和切面处理类L… 目录 前言 方法一、将接口的参数和结果打印在日志文件中   1、使用aop监控接口   2、增加requestId   3、效果如下图   4、接口监控遇到的一些坑 方法二、将风险高的操作保存到数据库中   1、新建一张log表存储风险操作   2、新建Log注解和切面处理类LogAspect 方法三、记录每一行数据的创建者和修改者   1、统一字段名和类型   2、将这些字段集成到一个抽象类中   3、使用mybatis-plus的MetaObjectHandler全局拦截insert和update操作 前言 接口设计是整个系统设计中非常重要的一环其中包括限流、权限、入参出参、切面等方面。设计一个好的接口可以帮助我们省去很多不必要的麻烦从而提升整个系统的稳定性和可扩展性。作为接口设计经验分享的第三篇我想分享一下如何在用户使用过程中留下操作痕迹。在实际开发中我会采取一些手段来记录用户操作例如使用日志记录用户行为或者在数据库中保存用户操作记录。这些痕迹可以帮助我们快速定位和解决问题同时也可以为后续数据分析和优化提供有价值的参考。 本文参考项目源码地址summo-springboot-interface-demo 方法一、将接口的参数和结果打印在日志文件中 日志文件是我们记录用户使用痕迹的第一个地方我之前写过一篇SpringBoot项目如何配置logback.xml的文章来实现系统日志输出有兴趣的同学可以去看看。 这里我主要讲一下怎么方便将所有接口的出入参打印出来。 1、使用aop监控接口 依赖如下 !-- aspectj -- dependencygroupIdorg.aspectj/groupIdartifactIdaspectjweaver/artifactIdversion1.9.5/version /dependency如果有同学不知道aspectj是啥的可以看我这篇文章SpringBoot整合aspectj实现面向切面编程(即AOP) 关键代码如下 package com.summo.aspect;import java.util.Objects;import javax.servlet.http.HttpServletRequest;import com.alibaba.druid.util.StringUtils;import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.slf4j.MDC; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes;Aspect Component Slf4j public class ControllerLoggingAspect {/*** 拦截所有controller包下的方法*/Pointcut(execution(* com.summo.controller..*.*(..)))private void controllerMethod() {}Around(controllerMethod())public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {long startTime System.currentTimeMillis();//获取本次接口的唯一码String token java.util.UUID.randomUUID().toString().replaceAll(-, ).toUpperCase();MDC.put(requestId, token);//获取HttpServletRequestRequestAttributes ra RequestContextHolder.getRequestAttributes();ServletRequestAttributes sra (ServletRequestAttributes)ra;HttpServletRequest request sra.getRequest();// 获取请求相关信息String url request.getRequestURL().toString();String method request.getMethod();String uri request.getRequestURI();String params request.getQueryString();if (StringUtils.isEmpty(params) StringUtils.equals(POST, method)) {if (Objects.nonNull(joinPoint.getArgs())) {for (Object arg : joinPoint.getArgs()) {params arg;}}}// 获取调用方法相信Signature signature joinPoint.getSignature();String className signature.getDeclaringTypeName();String methodName signature.getName();log.info(http请求开始, {}#{}() URI: {}, method: {}, URL: {}, params: {},className, methodName, uri, method, url, params);//result的值就是被拦截方法的返回值try {//proceed方法是调用实际所拦截的controller中的方法这里的result为调用方法后的返回值Object result joinPoint.proceed();long endTime System.currentTimeMillis();//定义请求结束时的返回数据包括调用时间、返回值结果等log.info(http请求结束, {}#{}(), URI: {}, method: {}, URL: {}, time: {}ms ,className, methodName, uri, method, url, (endTime - startTime));return result;} catch (Exception e) {long endTime System.currentTimeMillis();log.error(http请求出错, {}#{}(), URI: {}, method: {}, URL: {}, time: {}ms,className, methodName, uri, method, url, (endTime - startTime), e);throw e;} finally {MDC.remove(requestId);}} }2、增加requestId 由于接口的调用都是异步的所以一旦QPS上来那么接口的调用就会很混乱不加一个标识的话就不知道哪个返回值属于那个请求的了。 这个时候我们则需要加一个requestId(或者叫traceId)用来标识一个请求。 也即这段代码 //获取本次接口的唯一码 String token java.util.UUID.randomUUID().toString().replaceAll(-, ).toUpperCase(); MDC.put(requestId, token);... ... MDC.remove(requestId);同时logback.xml中也需要加一下requestId的打印在logback.xml中可以使用%X{requestId}获取到MDC中添加的遍历。 完整的logback.xml配置文件如下 configuration!-- 默认的一些配置 --include resourceorg/springframework/boot/logging/logback/defaults.xml/!-- 定义应用名称区分应用 --property nameAPP_NAME valuemonitor-test/!-- 定义日志文件的输出路径 --property nameLOG_PATH value${user.home}/logs/${APP_NAME}/!-- 定义日志文件名称和路径 --property nameLOG_FILE value${LOG_PATH}/application.log/!-- 定义警告级别日志文件名称和路径 --property nameWARN_LOG_FILE value${LOG_PATH}/warn.log/!-- 定义错误级别日志文件名称和路径 --property nameERROR_LOG_FILE value${LOG_PATH}/error.log/!-- 自定义控制台打印格式 --property nameFILE_LOG_PATTERN value%green(%d{yyyy-MM-dd HH:mm:ss.SSS}) [%blue(requestId: %X{requestId})] [%highlight(%thread)] ${PID:- } %logger{36} %-5level - %msg%n/!-- 将日志滚动输出到application.log文件中 --appender nameAPPLICATIONclassch.qos.logback.core.rolling.RollingFileAppender!-- 输出文件目的地 --file${LOG_FILE}/fileencoderpattern${FILE_LOG_PATTERN}/patterncharsetutf8/charset/encoder!-- 设置 RollingPolicy 属性用于配置文件大小限制保留天数、文件名格式 --rollingPolicy classch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy!-- 文件命名格式 --fileNamePattern${LOG_FILE}.%d{yyyy-MM-dd}.%i.log/fileNamePattern!-- 文件保留最大天数 --maxHistory7/maxHistory!-- 文件大小限制 --maxFileSize50MB/maxFileSize!-- 文件总大小 --totalSizeCap500MB/totalSizeCap/rollingPolicy/appender!-- 摘取出WARN级别日志输出到warn.log中 --appender nameWARN classch.qos.logback.core.rolling.RollingFileAppenderfile${WARN_LOG_FILE}/fileencoder!-- 使用默认的输出格式打印 --pattern${CONSOLE_LOG_PATTERN}/patterncharsetutf8/charset/encoder!-- 设置 RollingPolicy 属性用于配置文件大小限制保留天数、文件名格式 --rollingPolicy classch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy!-- 文件命名格式 --fileNamePattern${LOG_PATH}/warn.%d{yyyy-MM-dd}.%i.log/fileNamePattern!-- 文件保留最大天数 --maxHistory7/maxHistory!-- 文件大小限制 --maxFileSize50MB/maxFileSize!-- 文件总大小 --totalSizeCap500MB/totalSizeCap/rollingPolicy!-- 日志过滤器将WARN相关日志过滤出来 --filter classch.qos.logback.classic.filter.ThresholdFilterlevelWARN/level/filter/appender!-- 摘取出ERROR级别日志输出到error.log中 --appender nameERROR classch.qos.logback.core.rolling.RollingFileAppenderfile${ERROR_LOG_FILE}/fileencoder!-- 使用默认的输出格式打印 --pattern${CONSOLE_LOG_PATTERN}/patterncharsetutf8/charset/encoder!-- 设置 RollingPolicy 属性用于配置文件大小限制保留天数、文件名格式 --rollingPolicy classch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy!-- 文件命名格式 --fileNamePattern${LOG_PATH}/error.%d{yyyy-MM-dd}.%i.log/fileNamePattern!-- 文件保留最大天数 --maxHistory7/maxHistory!-- 文件大小限制 --maxFileSize50MB/maxFileSize!-- 文件总大小 --totalSizeCap500MB/totalSizeCap/rollingPolicy!-- 日志过滤器将ERROR相关日志过滤出来 --filter classch.qos.logback.classic.filter.ThresholdFilterlevelERROR/level/filter/appender!-- 配置控制台输出 --appender nameCONSOLE classch.qos.logback.core.ConsoleAppenderencoderpattern${FILE_LOG_PATTERN}/patterncharsetutf8/charset/encoder/appender!-- 配置输出级别 --root levelINFO!-- 加入控制台输出 --appender-ref refCONSOLE/!-- 加入APPLICATION输出 --appender-ref refAPPLICATION/!-- 加入WARN日志输出 --appender-ref refWARN/!-- 加入ERROR日志输出 --appender-ref refERROR//root /configuration 4、接口监控遇到的一些坑 返回值数据量很大会刷屏尽量不要打印返回值。文件上传接口会直接挂掉所以上传的接口一般不会加入监控。 方法二、将风险高的操作保存到数据库中 虽然方法一能够记录每个接口的日志但这些日志只存在于服务器上并且有大小和时间限制到期后就会消失。这种做法对所有请求或操作都一视同仁不会对风险较高的请求进行特殊处理。为了解决危险操作带来的风险我们需要将其持久化以便在出现问题时能够快速找到原因。最常见的做法是将风险高的操作保存到数据库中。 实现原理还是使用方法一种的切面不过这里使用的是注解切面具体做法请见下文。 1、新建一张log表存储风险操作 建表语句我也贴出来 SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS 0;-- ---------------------------- -- Table structure for user_oper_log -- ---------------------------- DROP TABLE IF EXISTS user_oper_log; CREATE TABLE user_oper_log (id bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 物理主键,operation varchar(64) DEFAULT NULL COMMENT 操作内容,time bigint DEFAULT NULL COMMENT 耗时,method text COMMENT 操作方法,params text COMMENT 参数内容,ip varchar(64) DEFAULT NULL COMMENT IP,location varchar(64) DEFAULT NULL COMMENT 操作地点,response_code varchar(32) DEFAULT NULL COMMENT 应答码,response_text text COMMENT 应答内容,gmt_create datetime DEFAULT NULL COMMENT 创建时间,gmt_modified datetime DEFAULT NULL COMMENT 更新时间,creator_id bigint DEFAULT NULL COMMENT 创建人,modifier_id bigint DEFAULT NULL COMMENT 更新人,PRIMARY KEY (id) ) ENGINEInnoDB AUTO_INCREMENT1 DEFAULT CHARSETutf8mb4 COMMENT用户操作日志表;SET FOREIGN_KEY_CHECKS 1;核心字段为操作方法、参数内容、IP、操作地点、应答码、应答内容、创建人这些其中IP和操作地址这两个是推算的不一定很准。这些字段也不是非常全面如果大家还有自己想记录的字段信息也可以加进来。 2、新建Log注解和切面处理类LogAspect 注解类 package com.summo.log;import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) public interface Log {/*** 接口功能描述** return*/String methodDesc() default ; }切面处理类 package com.summo.log;import java.io.Serializable; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.List; import java.util.Map; import java.util.Set;import javax.servlet.http.HttpServletRequest;import com.alibaba.fastjson.JSONObject;import com.summo.entity.UserOperInfoDO; import com.summo.repository.UserOperInfoRepository; import com.summo.util.HttpContextUtil; import com.summo.util.IPUtil; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; 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.core.LocalVariableTableParameterNameDiscoverer; import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile;Slf4j Aspect Component public class LogAspect {Autowiredprivate UserOperInfoRepository userOperInfoRepository;Pointcut(annotation(com.summo.log.Log))public void pointcut() {// do nothing}Around(pointcut())public Object around(ProceedingJoinPoint joinPoint) throws Throwable {Object result null;//默认操作对象为-1LMethodSignature signature (MethodSignature)joinPoint.getSignature();Method method signature.getMethod();Log logAnnotation method.getAnnotation(Log.class);UserOperInfoDO log new UserOperInfoDO();if (logAnnotation ! null) {// 注解上的描述log.setOperation(logAnnotation.methodDesc());}// 请求的类名String className joinPoint.getTarget().getClass().getName();// 请求的方法名String methodName signature.getName();log.setMethod(className . methodName ());// 请求的方法参数值Object[] args joinPoint.getArgs();// 请求的方法参数名称LocalVariableTableParameterNameDiscoverer u new LocalVariableTableParameterNameDiscoverer();String[] paramNames u.getParameterNames(method);if (args ! null paramNames ! null) {StringBuilder params new StringBuilder();params handleParams(params, args, Arrays.asList(paramNames));log.setParams(params.toString());}log.setGmtCreate(Calendar.getInstance().getTime());long beginTime System.currentTimeMillis();// 执行方法result joinPoint.proceed();// 执行时长(毫秒)long time System.currentTimeMillis() - beginTime;HttpServletRequest request HttpContextUtil.getHttpServletRequest();// 设置 IP 地址String ip IPUtil.getIpAddr(request);log.setIp(ip);log.setTime(time);//保存操作记录到数据库中userOperInfoRepository.save(log);return result;}/*** 参数打印合理化** param params 参数字符串* param args 参数列表* param paramNames 参数名* return*/private StringBuilder handleParams(StringBuilder params, Object[] args, List paramNames) {for (int i 0; i args.length; i) {if (args[i] instanceof Map) {Set set ((Map)args[i]).keySet();ListObject list new ArrayList();ListObject paramList new ArrayList();for (Object key : set) {list.add(((Map)args[i]).get(key));paramList.add(key);}return handleParams(params, list.toArray(), paramList);} else {if (args[i] instanceof Serializable) {Class? aClass args[i].getClass();try {aClass.getDeclaredMethod(toString, new Class[] {null});// 如果不抛出 NoSuchMethodException 异常则存在 toString 方法 安全的 writeValueAsString 否则 走 Object的// toString方法params.append( ).append(paramNames.get(i)).append(: ).append(JSONObject.toJSONString(args[i]));} catch (NoSuchMethodException e) {params.append( ).append(paramNames.get(i)).append(: ).append(JSONObject.toJSONString(args[i].toString()));}} else if (args[i] instanceof MultipartFile) {MultipartFile file (MultipartFile)args[i];params.append( ).append(paramNames.get(i)).append(: ).append(file.getName());} else {params.append( ).append(paramNames.get(i)).append(: ).append(args[i]);}}}return params;} }3、使用方法 在需要监控的接口方法上加上Log注解 PostMapping(/saveRel) Log(methodDesc 添加记录) public Boolean saveRel(RequestBody SaveRelReq saveRelReq) {return userRoleRelService.saveRel(saveRelReq); }DeleteMapping(/delRel) Log(methodDesc 删除记录) public Boolean delRel(Long relId) {return userRoleRelService.delRel(relId); }这里可以看到已经有记录保存在数据库中了包括两次添加操作、一次删除操作并且记录了操作人的IP地址(这里我使用的是localhost所以IP是127.0.0.1)和操作时间。但是这里有一个问题没有记录操作人的ID也即creator_id字段为空如果不知道这条记录是谁的那这个功能就没有意义了所以在方法三我将会说一下如何记录每一行数据的创建者和修改者。 方法三、记录每一行数据的创建者和修改者 这个功能的实现需要用到一个非常关键的东西用户上下文。如何实现请看《优化接口设计的思路》系列第二篇—接口用户上下文的设计与实现。 那么现在假设我已经有了GlobalUserContext.getUserContext()方法可以获取到用户上下文信息如何使用呢 方法二没有记录操作人的ID现在可以可以通过下面这种方法获取当前操作人的ID log.setCreatorId(GlobalUserContext.getUserContext().getUserId());但是我这里的标题是记录每一行数据的创建者和修改者可不仅仅是只操作user_oper_log的每一行数据而是系统中的每一张表的每一行数据那现在问题来了如何实现这个需求 最笨的办法就是在每个新增、更新的代码下都加上setCreatorId和setModifierId这些代码实现是可以实现但是感觉太low了所以我这里提供一个思路和一个例子来优化这些代码。 1、统一字段名和类型 在每张表中都加入gmt_create(datetime 创建时间)、gmt_modified(datetime 更新时间)、creator_id(bigint 创建人ID)、modifier_id(bigint 更新人ID)我们将所有表中的这些辅助字段统一命名、统一类型这样给我们统一处理提供了基础。 2、将这些字段集成到一个抽象类中 这样做的好处有两个 其他表的DO类继承这个抽象类那么DO中就不需要再定义以上4个字段统一处理的类只有抽象类一个了 tips非常建议使用mybatis-plus来实现这个功能maven依赖如下 !-- mybatis-plus -- dependencygroupIdcom.baomidou/groupIdartifactIdmybatis-plus-boot-starter/artifactIdversion3.3.2/version /dependency dependencygroupIdcom.baomidou/groupIdartifactIdmybatis-plus-extension/artifactIdversion3.3.2/version /dependency类名定义和代码如下 AbstractBaseDO.java package com.summo.entity;import java.io.Serializable; import java.util.Date;import com.baomidou.mybatisplus.annotation.FieldFill; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.extension.activerecord.Model; import lombok.Getter; import lombok.Setter;Getter Setter public class AbstractBaseDOT extends ModelT extends ModelT implements Serializable {/*** 创建时间*/TableField(fill FieldFill.INSERT)private Date gmtCreate;/*** 修改时间*/TableField(fill FieldFill.INSERT_UPDATE)private Date gmtModified;/*** 创建人ID*/TableField(fill FieldFill.INSERT)private Long creatorId;/*** 修改人ID*/TableField(fill FieldFill.INSERT_UPDATE)private Long modifierId;}3、使用mybatis-plus的MetaObjectHandler全局拦截insert和update操作 自定义MetaObjectHandlerConfig继承MetaObjectHandler代码如下 MetaObjectHandlerConfig.java package com.summo.entity;import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import org.apache.ibatis.reflection.MetaObject;Configuration public class MetaObjectHandlerConfig implements MetaObjectHandler {Overridepublic void insertFill(MetaObject metaObject) {}Overridepublic void updateFill(MetaObject metaObject) {} }逻辑补全的代码如下 package com.summo.entity;import java.util.Calendar; import java.util.Date;import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import com.summo.context.GlobalUserContext; import com.summo.context.UserContext; import org.apache.ibatis.reflection.MetaObject;Configuration public class MetaObjectHandlerConfig implements MetaObjectHandler {Overridepublic void insertFill(MetaObject metaObject) {//获取用户上下文UserContext userContext GlobalUserContext.getUserContext();//获取创建时间Date date Calendar.getInstance().getTime();//设置gmtCreatethis.fillStrategy(metaObject, gmtCreate, date);//设置gmtModifiedthis.fillStrategy(metaObject, gmtModified, date);//设置creatorIdthis.fillStrategy(metaObject, creatorId, userContext.getUserId());//设置modifierIdthis.fillStrategy(metaObject, modifierId, userContext.getUserId());}Overridepublic void updateFill(MetaObject metaObject) {//获取用户上下文UserContext userContext GlobalUserContext.getUserContext();//获取更新时间Date date Calendar.getInstance().getTime();//更新操作修改gmtModifiedthis.setFieldValByName(gmtModified, date, metaObject);//更新操作修改modifierIdthis.setFieldValByName(modifierId, userContext.getUserId(), metaObject);} }
http://www.huolong8.cn/news/108929/

相关文章:

  • 如何建设成为营销网站网站建站建设上海黔文信息科技有限公司30
  • 江苏有哪些网站建设的公司微信小程序开发案例教程
  • 公司网站建设的作用与意义做一个公司官网怎么做
  • 做超链接的网站制作网站的公司怎么样
  • 建设网站的 成本珠海网约车
  • 福州营销型网站建设如何查看网站的建设者
  • dw手机网站建设上海平台网站建设
  • 建网站提供下载深圳外贸公司招聘信息
  • 建设网站联盟网页制作模板在哪买
  • 学科网站建设方案莆田哪里有做网站的
  • 邵阳市建设投资经营集团网站做网站wamp和xamp
  • 网站制作哪家好手机域名免费注册
  • 成都科技网站建设联山西省城乡建设厅网站
  • 河北省城乡建设厅网站广州佛山建设信息网站
  • 学校网站建设报告comodo ssl wordpress
  • 用来做收录的网站id文件直接导入wordpress
  • 做钓鱼网站教程视频教程中国建设银行网站类型
  • 佛山企业网站建设公司推荐网站关键词排名优化
  • 网站建设小说开发公司空置房物业费会计科目
  • 国内最好的网站服务器网页编辑软件 破解版
  • 盐城哪家做网站的正规嘉兴微信网站
  • discuz做的网站网页版梦幻西游五色石攻略
  • 自学网站开发软件开发wordpress 插件 原理
  • 网站建设培训 店wordpress购买会员资格
  • 佛山网站制作建设平台网站 备案吗
  • asp网站作业下载做电商与做网站的区别
  • 小地方网站建设公司好新手seo要学多久
  • 网站建设需求分析直接登录的网站
  • 四川通江县住房和建设局网站大学生项目app策划书
  • 长春市住房城乡建设厅网站石家庄免费建站