实习日志day4——理解业务逻辑
日期2026.7.6今日任务研读项目业务拆解手册逐一分析其作用、接口、数据表、分层结构和核心流程阅读Log.java注释定义理解个字段含义阅读BusinessTypeEnum.java了解所有业务类型枚举阅读 SerialPortAlarmBindingConfigController.java这是一个 Log 注解使用规范的参考示例7 个接口全部有 Logtitle 统一businessType 准确阅读 LogAspect.java理解日志切面如何处理 Log 注解刷数组和链表学到的新知识Log 操作日志注解 — 全面理解指南第一周任务产出阅读 4 个核心源文件后的结构化学习笔记阅读顺序建议Log.java → BusinessTypeEnum.java → SerialPortAlarmBindingConfigController.java → LogAspectj.java一、Log 注解定义 — Log.java文件位置XYIoT-common/src/main/java/com/xinye/iot/common/annotaion/Log.java注意目录名annotaion是拼写错误应为 annotation但项目中已固定使用此路径不要随意改名。Log 是一个自定义方法级注解用于标记需要记录操作日志的 Controller 方法。它有 4 个属性属性类型默认值作用填写规范titleString模块名称会写入sys_log.sys_model字段用中文短词描述该接口所属的业务模块例如 “串口报警绑定查询”businessTypeBusinessTypeEnumOTHER操作类型枚举写入sys_log.oper_type字段根据 HTTP 语义精确匹配如 GET→SELECT、POST→INSERT、PUT→UPDATE、DELETE→DELETEisSaveRequestDatabooleantrue是否把请求参数序列化后存入日志默认 true如果参数体量大如文件上传可设为 falseisSaveResponseDatabooleantrue是否把响应结果序列化后存入日志默认 true如果响应体过大如导出接口可设为 false注解元信息Target({PARAMETER, METHOD})— 可以放在方法或参数上实际项目里只用在方法上。Retention(RUNTIME)— 运行时保留这样 AOP 切面才能通过反射读取到它。Documented— 会出现在 Javadoc 中。关键理解Log 本身不做任何事它只是一个标记。真正的日志记录逻辑全部由 LogAspectj 切面完成。这是典型的 AOP面向切面编程设计思路——声明与执行分离。二、BusinessTypeEnum 业务类型枚举文件位置XYIoT-common/src/main/java/com/xinye/iot/common/enums/BusinessTypeEnum.java这是 Log 注解的businessType属性的可选值集合共定义了 26 种操作类型Code枚举值中文名典型使用场景0OTHER其他无法归类的操作也是默认值1INSERT插入POST 新增接口2DELETE删除DELETE 删除接口3UPDATE更新PUT 修改接口4SELECT查询GET/POST 查询接口5EXPORT导出Excel 导出等6AUTH授权角色授权、菜单权限分配7OPERATING操作设备远程控制设备开关等 IoT 特有操作8FORCE强制退出踢出在线用户9UPLOAD上传文件上传10DOWNLOAD下载文件下载11LOGIN登录用户登录12LOGOUT登出用户登出13CONFIG配置修改系统配置变更14SYNC数据同步定时任务同步、手工触发同步15REGISTER设备注册IoT 设备注册16UNBIND设备解绑IoT 设备与网关解绑17CONNECT设备连接建立设备连接18DISCONNECT设备断开断开设备连接19ALARM告警告警配置、告警确认操作20COLLECT数据采集触发数据采集任务21SEND_DATA发送数据向设备下发数据22RECEIVE_DATA接收数据接收设备上报数据23FORWARD_DATA数据转发数据路由转发24PARSE_DATA数据解析原始报文解析25STORE_DATA数据存储存储操作触发设计观察0-12 是通用后台管理系统的常见操作类型增删改查导出授权等13-25 是 XYIoT 项目为物联网业务扩展的专用类型。这说明日志系统是在通用框架基础上为 IoT 场景做了定制。辅助方法getByCode(int code)— 根据 code 反查枚举值找不到返回 null。SysLog 实体的getOperTypeName()就用这个方法把整数 code 转成中文名。contains(int code)— 判断某个 code 是否存在。三、Log 规范用法参考 — SerialPortAlarmBindingConfigController.java文件位置XYIoT-admin/src/main/java/com/xinye/iot/admin/controller/communication/serialport/SerialPortAlarmBindingConfigController.java这是 mentor 指定的标杆示例7 个接口全部正确使用 Log 注解。从中可以提炼出以下规范规范 1每个对外接口都必须加 Log该 Controller 的 7 个方法list / page / getById / save / updateById / remove / saveOrUpdate每一个都有 Log 注解没有遗漏。规范 2title 格式统一——“模块名 动作”所有 title 都遵循串口报警绑定 动作描述的模式接口titlebusinessType列表查询串口报警绑定查询SELECT分页查询串口报警绑定分页查询SELECT详情查询串口报警绑定详情查询SELECT新增串口报警绑定新增INSERT修改串口报警绑定修改UPDATE删除串口报警绑定删除DELETE新增或修改串口报警绑定新增或修改UPDATE注意前缀统一为串口报警绑定后面跟具体的动作词查询/新增/修改/删除让人一眼就能在日志列表里看出是哪个模块的什么操作。规范 3businessType 与 HTTP 方法语义一致GET 查询 →BusinessTypeEnum.SELECTPOST 新增 →BusinessTypeEnum.INSERTPUT 修改 →BusinessTypeEnum.UPDATEDELETE 删除 →BusinessTypeEnum.DELETEsaveOrUpdate新增或修改→BusinessTypeEnum.UPDATE因为 UPDATE 涵盖改的语义规范 4注解顺序固定Log(title...,businessTypeBusinessTypeEnum.XXX)// 第一行日志注解ApiOperation(...)// 第二行Swagger文档注解RequestMapping(...)// 第三行路由映射注解四、LogAspectj 日志切面 — 完整执行流程文件位置XYIoT-framework/src/main/java/com/xinye/iot/framework/aspectj/LogAspectj.java这是整个操作日志机制的引擎。理解了它就知道 Log 注解是如何变成 sys_log 表里的一行记录的。4.1 切面入口Pointcut(annotation(com.xinye.iot.common.annotaion.Log)) public void logPointCut() {}切点定义拦截所有标注了 Log 的方法。Spring AOP 会在运行时自动生成代理当被 Log 标记的方法被调用时切面逻辑就会介入。4.2 两种通知Advice通知类型触发时机方法签名AfterReturning方法正常返回后doAfterReturning(JoinPoint, Object jsonResult)AfterThrowing方法抛出异常后doAfterThrowing(JoinPoint, Exception e)两者都调用同一个handleLog方法区别在于传入的参数——正常返回时enull异常时jsonResultnull。4.3 handleLog 核心流程6 步方法被调用 │ ▼ Step 1: 通过反射获取 Log 注解 → getAnnotationLog() │ 如果注解不存在直接 return ▼ Step 2: 创建 SysLog 对象设置默认值 │ - operStatus SUCCESS乐观预设 │ - operIp ShiroUtils.getIp() ▼ Step 3: setOperator() — 设置操作人 │ - FileApiController → 公共API │ - 已登录用户 → loginName │ - 其他情况 → 匿名用户 ▼ Step 4: determineOperationStatus() — 判定操作状态 │ - 有异常 → FAIL │ - 返回 ResultUtil 且 code ! 200 → FAIL │ - 返回 AjaxResult 且 code ! 0 → FAIL │ - 其余保持 SUCCESS ▼ Step 5: 填充日志详情 │ - sysModel ← Log.title如 串口报警绑定查询 │ - operType ← Log.businessType.code如 SELECT4 │ - setRequestInfo: requestUrl, requestMethod, requestParams │ - setResponseInfo: 响应JSON受 isSaveResponseData 控制 │ - setExceptionInfo: 异常消息如果有 ▼ Step 6: 异步写库 │ AsyncManager.me().execute(AsyncFactory.recordOper(sysLog)) │ → 创建 TimerTask → 解析 IP 地址为地理位置 → ISysLogService.addSysLog() ▼ 日志记录完成写入 sys_log 表4.4 关键设计细节**敏感参数过滤 — **filterSensitiveParams()请求参数在序列化时会对 Map 类型参数做 key 检测如果 key 包含 password / passwd / secret / token / apikey / accesskey / authcode / pwd 中的任一个值会被替换为******。这防止了密码等敏感信息明文写入日志表。长度截断保护请求参数和响应结果都有 2000 字符的上限MAX_PARAM_LENGTH/MAX_RESULT_LENGTH超出后截断并追加...[truncated]。这防止了大响应体撑爆 sys_log 表的 text 字段。异步写库日志写入不是同步执行的而是通过AsyncManager内部用 ScheduledExecutorService提交一个 TimerTask 异步完成。好处是不影响接口的响应时间代价是如果 JVM 在异步任务执行前崩溃日志会丢失。异常安全整个 handleLog 方法被 try-catch 包裹日志记录失败只会打 error 日志不会影响业务接口的正常响应。这遵循了日志是旁路功能不能拖垮主流程的原则。五、数据流全景从注解到数据库Controller 方法上的 Log 注解 │ ▼ (Spring AOP 代理拦截) LogAspectj.logPointCut() │ ▼ (AfterReturning / AfterThrowing) LogAspectj.handleLog() │ 构建 SysLog 对象 │ - title → sys_model │ - businessType.code → oper_type │ - 操作人 / IP / URL / 参数 / 响应 / 异常 ▼ AsyncManager.me().execute() │ (异步线程池) ▼ AsyncFactory.recordOper() → TimerTask │ - IP 地址 → 地理位置 (AddressUtils) ▼ ISysLogService.addSysLog(sysLog) │ (MyBatis-Plus INSERT) ▼ sys_log 表 (MySQL)六、关联实体SysLog 字段映射SysLog 字段数据库列来源说明logIdlog_id自动生成主键sysModelsys_modelLog.title模块名称operTypeoper_typeLog.businessType.code操作类型整数码operNameoper_nameShiroUtils / “公共API” / “匿名用户”操作人operIpoper_ipShiroUtils.getIp()客户端IPoperLocationoper_locationAddressUtils 异步解析IP归属地operStatusoper_statusdetermineOperationStatus() 判定0成功, 1失败operDateoper_dateBaseParamEntity 自动填充操作时间requestUrlrequest_urlHttpServletRequest请求URIrequestMethodrequest_methodHttpServletRequestGET/POST/PUT/DELETErequestParamsrequest_paramsjoinpoint.getArgs() 序列化请求参数≤2000字符responseResultresponse_resultjsonResult JSON序列化响应结果≤2000字符costTimecost_timeSystem.currentTimeMillis 差值执行耗时(ms)errorMsgerror_msgException.getMessage()异常信息≤2000字符一、AOP到底是啥餐馆等位叫号类比想象你去一家网红餐厅吃饭。业务逻辑核心功能厨师炒菜相当于你的insertUser()、deleteOrder()等业务方法。非业务逻辑杂事门口迎宾、记录排队时间、收拾桌子、给好评返现。没有AOP之前OOP厨师炒菜前必须先自己跑到门口去记录排队人数炒完菜自己还得去擦桌子。代码到处都是重复的杂事核心炒菜逻辑被污染了。用了AOP之后老板雇了一个大堂经理也就是这个LogAspectj类。经理站在后厨门口盯着只要看到厨师开始做菜Log注解经理就自动记录厨师几点开始的耗时。记录厨师用了什么食材请求参数。如果菜炒糊了抛异常经理立刻记录失败原因。菜端出去后经理顺便把数据记在账本上异步存数据库。结论AOP 就是这位大堂经理他偷懒不用改每个厨师的做菜步骤就能统一把所有杂事干了。二、手把手逐行拆解这份代码从外到内现在拿着这份“大堂经理”的工牌一步步看他是怎么干活的。第一步立规矩——你要看谁Pointcut 切点Pointcut(annotation(com.xinye.iot.common.annotaion.Log))publicvoidlogPointCut(){}人话经理说“我只管那些厨师帽上贴了Log标签的人。没贴标签的爱咋咋地我不记录。”作用如果你在 Controller 的某个方法上写了Log经理就会盯上你。第二步定动作——什么时候出手通知 Advice// 情况1菜正常端出去了方法执行成功AfterReturning(pointcutlogPointCut(),returningjsonResult)publicvoiddoAfterReturning(JoinPointjoinpoint,ObjectjsonResult){handleLog(joinpoint,null,jsonResult);}// 情况2菜炒糊了方法抛异常AfterThrowing(pointcutlogPointCut(),throwinge)publicvoiddoAfterThrowing(JoinPointjoinpoint,Exceptione){handleLog(joinpoint,e,null);}人话经理只关注两个时刻——菜做好端出去时成功或者后厨起火时异常。这两个时刻一到经理就冲进去干活。第三步核心干活流程handleLog 方法这是经理的工作手册他按照这个清单逐项打钩。1. 先看帽子没标签直接走人LogcontrollerLoggetAnnotationLog(joinpoint);if(controllerLognull){return;}防止经理误闯没戴标签的厨房。2. 开一张新账单SysLogSysLogsysLognewSysLog();longstartTimeSystem.currentTimeMillis();// 开始计时sysLog.setOperIp(ShiroUtils.getIp());// 记下客人的IP地址3. 到底是谁点的菜setOperatorSysUsercurrentUserShiroUtils.getSysUser();if(currentUser!null){sysLog.setOperName(currentUser.getLoginName());// 记录是谁操作的}else{sysLog.setOperName(匿名用户);// 没登录就写匿名}含金量代码里还判断了是不是FileApiController文件上传接口如果是直接写死为“公共API”。因为文件上传常常是系统内部调用没有登录用户。4. 这道菜成没成功determineOperationStatusif(e!null){sysLog.setOperStatus(BusinessStatusEnum.FAIL.getCode());// 异常 - 失败return;}// 看看返回的JSON里的code是不是200成功if(jsonResultinstanceofAjaxResult){ObjectcodeObjajaxResult.get(AjaxResult.CODE_TAG);if(codeObj!null!Integer.valueOf(0).equals(codeObj)){sysLog.setOperStatus(BusinessStatusEnum.FAIL.getCode());// code!0 - 失败}}关键点即使没抛异常如果业务返回的code不是 0比如code500经理依然会把这条日志标红为“失败”。这比只记录异常更精准5. 记录地址、参数、返回结果setRequestInfo / setResponseInfosysLog.setRequestUrl(request.getRequestURI());// 例如 /api/user/loginsysLog.setRequestMethod(request.getMethod());// POST 或 GET// 把入参变成JSON字符串存起来StringparamsfilterSensitiveParams(args);sysLog.setRequestParams(params);// 把返回结果也变JSON存起来StringresultJSON.toJSONString(jsonResult);sysLog.setResponseResult(result);注意那两个长度判断if (params.length() 2000)就截断。这是好习惯防止有人传了 10MB 的大图片base64把日志表撑爆了。6. 最骚的操作脱敏处理filterSensitiveParamsif(isSensitiveKey(key)){filtered.put(key,******);// 把密码、token替换成星号}人话经理很机灵看到参数里有password、token、secret这些词不会傻乎乎地写进日志直接打成******。防止日志泄露密码这是安全红线7. 算总时长 异步存盘sysLog.setCostTime(System.currentTimeMillis()-startTime);// 这道菜做了几毫秒// 把账单交给小弟异步线程去数据库存起来经理立刻回去继续盯着下一道菜AsyncManager.me().execute(AsyncFactory.recordOper(sysLog));为什么要异步存数据库要耗费时间比如 20ms。如果同步存每个接口都要等这 20ms接口响应就变慢了。扔给后台线程干主线程立刻给前端返回结果这叫异步解耦。三、灵魂总结一张图看懂 AOP代码位置角色对应 AOP 概念Pointcutannotation(Log)经理的“盯人名单”切点PointcutAfterReturning/AfterThrowing经理行动的“时机”通知AdviceLogAspectj整个类经理本人切面Aspect业务方法如Log下的login()正在炒菜的厨师目标对象Target今日Bug暂无写代码今日技术总结今天通过系统阅读 4 个核心源文件Log.java → BusinessTypeEnum.java → SerialPortAlarmBindingConfigController.java → LogAspectj.java彻底攻克了上周五看 LogAspectj 时留下的 AOP 理解缺口。现在不仅看懂了切面代码还能画出从 Log 注解到 sys_log 表的完整数据流。日志模块看似简单但其背后的设计思考——声明与执行分离、状态双保险、安全脱敏、异步解耦——处处体现着工业级代码的严谨性。下周将进入 mentor 布置的第二周任务梳理设备告警规则匹配流程。可写进简历● 深入研读企业级 Spring Boot AOP 操作日志系统源码理解切点定义、通知类型、异步落库等核心机制并输出结构化学习笔记与数据流图● 掌握自定义注解在业务系统中的设计模式能够基于 Log 注解规范为 CRUD 接口配置准确的操作类型与模块名称● 具备敏锐的代码质量感知能力能从枚举设计通用类型 vs IoT 扩展、脱敏处理、长度截断等细节中提炼可复用的生产级实践● 能够参考标杆 Controller 为新增接口正确添加操作日志注解确保系统操作记录完整可追溯STAR素材维度内容Situation入职第一周mentor 要求在熟悉项目核心代码的基础上聚焦操作日志模块完成从注解定义到切面执行全链路的深度学习并产出可沉淀的学习笔记。Task系统研读 4 个核心源文件Log.java / BusinessTypeEnum.java / SerialPortAlarmBindingConfigController.java / LogAspectj.java理解 Log 注解的工作机制、枚举设计意图、Controller 使用规范以及 AOP 切面的执行流程最终输出结构化学习笔记并完成自检清单。Action采用 mentor 建议的「文档阅读顺序」注解定义 → 枚举 → 标杆示例 → 切面源码逐层深入先用 IDE 跟踪代码调用链观察JoinPoin如何获取运行时上下文再结合AsyncManager理解异步写库的线程模型最后参考SerialPortAlarmBindingConfigController的 7 个接口提炼 title 命名规范和 businessType 选择规律整理成可复用 checklist。Result产出了一份完整的《Log 操作日志注解理解指南》学习笔记覆盖注解属性、枚举分类、规范示例、切面流程、数据流全景、自检清单对日志模块的掌握程度可通过自检清单全部确认为后续在业务开发中正确使用操作日志打下扎实基础。

相关新闻