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

智慧社区(十)——声明式日志记录与小区地图功能实现

在社区管理系统中,日志记录和数据可视化是两个核心需求。日志记录用于追踪系统操作,保障安全性;地图展示则能直观呈现小区分布。本文将详细介绍如何通过自定义注解 + AOP 实现声明式日志记录,并结合百度地图 API 实现小区地图功能。

一、声明式日志记录:注解 + AOP 的优雅实现

传统的日志记录方式需要在每个方法中手动编写日志代码,存在代码冗余、维护困难等问题。而通过自定义注解结合 AOP(面向切面编程),可以实现日志记录与业务逻辑的解耦,达到 "声明式" 记录日志的效果。

1. 自定义日志注解:@LogAnnotation

首先,我们需要定义一个用于标记需要记录日志的方法的注解。这个注解将作为 AOP 的切入点标识。

package com.qcby.community.annotation;import java.lang.annotation.*;@Target(ElementType.METHOD)       // 仅用于方法上
@Retention(RetentionPolicy.RUNTIME)    // 运行时保留,可通过反射获取
@Documented    // 生成API文档时包含该注解
public @interface LogAnnotation {String value() default "";  // 用于描述操作名称,如"人脸采集"、"添加小区"
}

注解设计解析

  • @Target(ElementType.METHOD):限制注解仅能作用于方法,符合日志记录的场景(通常记录方法级别的操作)。
  • @Retention(RetentionPolicy.RUNTIME):指定注解在运行时可见,因为 AOP 需要在程序运行时通过反射获取注解信息。
  • value()属性:用于存储操作的描述信息,如 "人脸采集"、"删除小区" 等,使日志更具可读性。

2. AOP 切面实现:LogAspect

有了注解后,我们需要通过 AOP 切面捕获被@LogAnnotation标记的方法,在方法执行前后自动记录日志。核心逻辑是:在方法执行前记录开始时间,执行后收集操作信息并保存日志

package com.qcby.community.aspect;import com.google.gson.Gson;
import com.qcby.community.annotation.LogAnnotation;
import com.qcby.community.entity.Log;
import com.qcby.community.entity.User;
import com.qcby.community.service.LogService;
import com.qcby.community.util.HttpContextUtil;
import com.qcby.community.util.IPUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import java.lang.reflect.Method;
import java.util.Date;@Aspect
@Component
public class LogAspect {private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);@Autowiredprivate LogService logService;  // 日志保存服务// 切入点:所有被@LogAnnotation标记的方法@Pointcut("@annotation(com.qcby.community.annotation.LogAnnotation)")public void logPointCut() {}// 环绕通知:在方法执行前后执行日志记录逻辑@Around("logPointCut()")public Object around(ProceedingJoinPoint point) throws Throwable {long beginTime = System.currentTimeMillis();  // 记录开始时间Object result = point.proceed();  // 执行目标方法long time = System.currentTimeMillis() - beginTime;  // 计算方法执行耗时saveLog(point, (int) time);  // 保存日志return result;}// 日志保存逻辑private void saveLog(ProceedingJoinPoint joinPoint, int time) {try {MethodSignature signature = (MethodSignature) joinPoint.getSignature();Method method = signature.getMethod();Log log = new Log();  // 日志实体类// 1. 获取注解中的操作描述LogAnnotation logAnnotation = method.getAnnotation(LogAnnotation.class);if (logAnnotation != null) {log.setOperation(logAnnotation.value());  // 如"人脸采集"}// 2. 记录调用的类和方法名String className = joinPoint.getTarget().getClass().getName();String methodName = signature.getName();log.setMethod(className + "." + methodName + "()");  // 如"com.qcby.controller.PersonController.addPerson()"// 3. 记录请求参数Object[] args = joinPoint.getArgs();try {if (args != null && args.length > 0) {log.setParams(new Gson().toJson(args[0]));  // 使用Gson将参数转为JSON}} catch (Exception e) {logger.error("参数解析失败", e);log.setParams("参数解析失败");  // 容错处理}// 4. 获取请求信息(IP、用户)HttpServletRequest request = HttpContextUtil.getHttpServletRequest();if (request != null) {log.setIp(IPUtil.getIpAddr(request));  // 获取客户端IP// 从Session获取当前登录用户HttpSession session = request.getSession();if (session != null) {User currentUser = (User) session.getAttribute("user");log.setUsername(currentUser != null ? currentUser.getUsername() : "匿名用户");}}// 5. 记录执行时间和操作时间log.setTime(time);  // 方法执行耗时(毫秒)log.setCreateTime(new Date());  // 操作发生的时间// 6. 保存日志到数据库logService.save(log);logger.info("日志保存成功: {}", log);} catch (Exception e) {logger.error("日志保存失败", e);  // 日志记录失败不影响主业务}}
}

切面核心逻辑解析

  1. 切入点定义
    @Pointcut("@annotation(com.qcby.community.annotation.LogAnnotation)") 表示所有被@LogAnnotation标记的方法都会被该切面拦截。

  2. 环绕通知(@Around)
    环绕通知是 AOP 中功能最强大的通知类型,它可以在方法执行前后插入逻辑。这里我们用它来:

    • 记录方法执行开始时间
    • 调用point.proceed()执行目标方法(业务逻辑)
    • 计算执行耗时并触发日志保存
  3. 日志信息收集
    保存日志时需要收集的关键信息包括:

    • 操作描述(从注解value中获取)
    • 调用的类和方法名(通过ProceedingJoinPoint获取)
    • 请求参数(通过 Gson 转为 JSON 字符串,便于存储和查看)
    • 客户端 IP(通过工具类从请求中获取)
    • 操作用户(从 Session 中获取当前登录用户)
    • 执行耗时和操作时间
  4. 容错设计

    • 日志记录失败时(如数据库异常),通过try-catch捕获并仅记录错误日志,不影响主业务流程。
    • 参数解析失败时,设置默认提示信息,避免日志记录中断。

3. 注解的使用:在业务方法中标记日志

定义好注解和切面后,使用方式非常简单:只需在需要记录日志的方法上添加@LogAnnotation并指定操作描述即可。

// 人脸采集方法示例
@LogAnnotation("人脸采集")
@PostMapping("/addPerson")
public Result addPerson(@RequestBody PersonFaceForm personFaceForm) {// 业务逻辑:处理人脸采集...return Result.ok();
}// 添加小区方法示例
@LogAnnotation("添加小区")
@PostMapping("/add")
public Result add(@RequestBody Community community, HttpSession session) {// 业务逻辑:添加小区...return Result.ok();
}

效果:当这些方法被调用时,AOP 切面会自动拦截并记录日志,无需在业务代码中编写任何日志相关逻辑。

二、小区地图功能实现:后端数据接口与前端集成

小区地图功能需要后端提供小区的地理位置数据(经纬度),前端通过百度地图 API 将小区标记在地图上。

1. 后端接口设计:提供小区地理数据

后端需要实现一个接口,查询所有小区的基本信息(包括经度lng和纬度lat),返回给前端用于地图标记。

/*** 获取小区地图数据* @return Result 包含小区列表的响应对象*/
@GetMapping("/getCommunityMap")
public Result getCommunityMap() {List<Community> data = communityService.list();  // 查询所有小区if (data == null) {return Result.error("没有小区数据");}return Result.ok().put("data", data);  // 返回小区列表
}

返回数据格式

{"msg": "操作成功","code": 200,"data": [{"communityId": 2,"communityName": "栖海澐颂","termCount": 0,"seq": 0,"creater": "","createTime": "","lng": 116.2524,  // 经度"lat": 40.0961    // 纬度}// 更多小区...]
}

实现说明

  • 接口通过communityService.list()查询所有小区数据,包含lng(经度)和lat(纬度)字段。
  • 若查询结果为空,返回错误提示;否则将数据放入响应对象中返回。

2. 前端集成百度地图

前端使用 Vue 框架结合vue-baidu-map组件库实现地图展示,步骤如下:

(1)安装依赖
cnpm i --save vue-baidu-map  # 安装百度地图Vue组件
(2)配置百度地图 AK

在 Vue 项目入口文件(如main.js)中配置百度地图开发者密钥(AK):

import Vue from 'vue'
import BaiduMap from 'vue-baidu-map'Vue.use(BaiduMap, {// AK需在百度地图开放平台申请ak: '7eTaUxl9NY8RCMxCPm3oc8m2snTBOgbt'
})
(3)地图组件实现

在 Vue 组件中使用baidu-map组件加载地图,并根据后端接口返回的小区数据添加标记:

<template><baidu-map class="map" center="北京"  // 初始中心点zoom="12"      // 初始缩放级别><!-- 遍历小区数据,添加标记 --><bm-marker v-for="community in communityList" :key="community.communityId":position="{lng: community.lng, lat: community.lat}"  // 经纬度位置:title="community.communityName"  // 鼠标悬停提示><!-- 信息窗口:点击标记显示小区名称 --><bm-info-window :show="false">{{ community.communityName }}</bm-info-window></bm-marker></baidu-map>
</template><script>
export default {data() {return {communityList: []  // 小区数据列表}},mounted() {// 调用后端接口获取小区数据this.axios.get('/sys/community/getCommunityMap').then(res => {if (res.code === 200) {this.communityList = res.data;}})}
}
</script><style>
.map {width: 100%;height: 600px;
}
</style>

效果:地图加载后,会根据小区的经纬度在对应位置显示标记,点击标记可查看小区名称,实现小区分布的可视化展示。

http://www.lryc.cn/news/614114.html

相关文章:

  • Python实现点云PCA配准——粗配准
  • Ubuntu安装 L20显卡驱动
  • Linux网络--2、Socket编程
  • 中国电信清华:大模型驱动的具身智能发展与挑战综述
  • 动漫软件集合分享
  • Pytest项目_day08(setup、teardown前置后置操作)
  • 144.二叉树的前序遍历
  • 鲸签云解决互联网行业合同管理难题​
  • 【Rust】多级目录模块化集成测试——以Cucumber为例
  • 线程组和线程池的基本用法
  • 【Spring Boot 快速入门】八、登录认证
  • duxapp 2025-05-29 更新 兼容鸿蒙C-API方案,现在鸿蒙端可以用于生产
  • React SSR 水合问题
  • 《告别Bug!GDB/CGDB调试实战指南》
  • TF 上架全流程实战,从构建到 TestFlight 分发
  • UniApp 跳转外部链接实现
  • Elasticsearch LTR(Learning To Rank)从训练到检索与重排
  • Elasticsearch:在向量搜索中使用 Direct IO
  • 力扣-438.找到字符串中所有字母异位词
  • ctfshow_萌新web9-web13-----rce
  • python学智能算法(三十五)|SVM-软边界拉格朗日方程乘子非负性理解
  • LeetCode 刷题【34. 在排序数组中查找元素的第一个和最后一个位置、35. 搜索插入位置】
  • 文件管理从基础到高级:文件描述符、超大文件切片重组与快速删除实战
  • 五、CV_ResNet
  • 腾讯iOA:数据安全的港湾
  • wordpress的wp-config.php文件的详解
  • proteus实现简易DS18B20温度计(stm32)
  • Linux软硬链接与动静态库
  • SQL的多表连接查询(难点)
  • 冷冻食材,鲜美生活的新选择