当前位置 : 主页 > 编程语言 > java >

018-管理后台操作日志功能开发

来源:互联网 收集:自由互联 发布时间:2023-09-03
1.功能分析 1.1. 查询列表 1.1.1. 页面效果 1.1.2. 功能要求 分页查询默认查询10条每页从第1页开始查询 日志只提供查询操作 搜索条件 日志来源:精准搜索 请求ip:精准搜索 点击搜索按钮是按

1.功能分析

1.1. 查询列表

1.1.1. 页面效果

018-管理后台操作日志功能开发_spring

1.1.2. 功能要求

  • 分页查询默认查询10条每页从第1页开始查询
  • 日志只提供查询操作
  • 搜索条件
  • 日志来源:精准搜索
  • 请求ip:精准搜索
  • 点击搜索按钮是按照录入的搜索条件进行查询数据并渲染
  • 点击重置按钮的时候清空搜索条件,并重新渲染数据

1.2.插入日志

1.2.1. 功能要求

  • 日志参数包含:请求ip,请求地址,浏览器信息(如果无则为空),请求时间,请求来源(管理后台/博客前台),请求方法,模块信息,请求参数,响应结果,响应时间,响应时长(s),方法描述,日志类型(新增/修改等等),创建时间,创建人编号(如果为客户端访问则填充ip信息),创建人名称(如果为客户端访问则填充ip信息)

1.3查看详情

1.3.1. 页面效果

018-管理后台操作日志功能开发_日志_02

1.3.2. 功能要求

  • 页面仅查看无法进行操作

2.功能实现

2.1. 初期准备

2.1.1. 创建数据库 zh_log_info

CREATE TABLE `zh_log_info` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',
  `request_ip` varchar(40) DEFAULT NULL COMMENT '请求ip',
  `request_url` varchar(255) DEFAULT NULL COMMENT '请求地址',
  `request_browser` varchar(255) DEFAULT NULL COMMENT '日志请求浏览器',
  `request_source` varchar(40)  DEFAULT NULL COMMENT '请求来源 manager管理后台 portal博客前台',
  `request_method` varchar(255) DEFAULT NULL COMMENT '请求方法',
  `request_params` text COMMENT '请求参数',
  `request_result` longtext COMMENT '请求响应结果',
  `request_start_time` datetime DEFAULT NULL COMMENT '请求开始时间',
  `request_end_time` datetime DEFAULT NULL COMMENT '响应时间/请求结束时间',
  `response_interval` bigint DEFAULT NULL COMMENT '响应时长(s)',
  `log_type` varchar(40) DEFAULT NULL COMMENT '日志类型(类型里面的value)',
  `log_identifying` varchar(40) DEFAULT NULL COMMENT '日志标识(类型里面的key)',
  `modual_info` varchar(100) DEFAULT NULL COMMENT '模块信息/请求模块',
  `method_desc` varchar(255) DEFAULT NULL COMMENT '方法描述',
  `method_type` varchar(255) DEFAULT NULL COMMENT '请求方法类别 GET/POST/PUT/DELETE',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `create_user_code` varchar(255) DEFAULT NULL COMMENT '创建人标识',
  `create_user_name` varchar(255) DEFAULT NULL COMMENT '创建人名称',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB COMMENT='日志记录表';

2.1.2. 创建控制层LogInfoController

package com.zhuhuo.modual.controller.manager;

import com.zhuhuo.modual.service.LogInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

@Controller
@RequestMapping(value = "/m/logInfo")
public class LogInfoController {

    @Autowired
    private LogInfoService logInfoService;

}

2.1.3. 创建实体映射LogInfo

package com.zhuhuo.modual.entity;

import lombok.Data;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import javax.persistence.Table;
import java.io.Serializable;
import java.util.Date;
import javax.persistence.Id;


@Data
@AllArgsConstructor
@NoArgsConstructor
@Table(name = "zh_log_info")
public class LogInfo implements Serializable {

  /**
   * id
   */
  @Id
  private Long id;
  /**
   * 请求ip
   */
  private String requestIp;
  /**
   * 请求地址
   */
  private String requestUrl;
  /**
   * 日志请求浏览器
   */
  private String requestBrowser;
  /**
   * 请求来源 1管理后台 2博客前台
   */
  private Byte requestSource;
  /**
   * 请求方法
   */
  private String requestMethod;
  /**
   * 请求参数
   */
  private String requestParams;
  /**
   * 请求响应结果
   */
  private String requestResult;
  /**
   * 请求开始时间
   */
  private Date requestStartTime;
  /**
   * 响应时间/请求结束时间
   */
  private Date requestEndTime;
  /**
   * 响应时长(s)
   */
  private Integer responseInterval;
  /**
   * 日志类型(类型里面的value)
   */
  private String logType;
  /**
   * 日志标识(类型里面的key)
   */
  private String logIdentifying;
  /**
   * 模块信息/请求模块
   */
  private String modualInfo;
  /**
   * 方法描述
   */
  private String methodDesc;
  /**
   * 请求方法类别 GET/POST/PUT/DELETE
   */
  private String methodType;
  /**
   * 创建时间
   */
  private Date createTime;
  /**
   * 创建人标识
   */
  private String createUserCode;
  /**
   * 创建人名称
   */
  private String createUserName;
    
}

2.1.4. 创建LogInfoMapper, LogInfoMapper.xml

package com.zhuhuo.modual.mapper;

import com.zhuhuo.core.frame.mapper.BasicsMapper;
import com.zhuhuo.modual.entity.LogInfo;

public interface LogInfoMapper extends BasicsMapper<LogInfo>{

}
<resultMap id="BaseResultMap" type="com.zhuhuo.modual.entity.LogInfo">
        <id column="id" property="id" jdbcType="BIGINT"/>
        <result column="request_ip" property="requestIp" jdbcType="VARCHAR"/>
        <result column="request_url" property="requestUrl" jdbcType="VARCHAR"/>
        <result column="request_browser" property="requestBrowser" jdbcType="VARCHAR"/>
        <result column="request_source" property="requestSource" jdbcType="VARCHAR"/>
        <result column="request_method" property="requestMethod" jdbcType="VARCHAR"/>
        <result column="request_params" property="requestParams" jdbcType="LONGVARCHAR"/>
        <result column="request_result" property="requestResult" jdbcType="VARCHAR"/>
        <result column="request_start_time" property="requestStartTime" jdbcType="TIMESTAMP"/>
        <result column="request_end_time" property="requestEndTime" jdbcType="TIMESTAMP"/>
        <result column="response_interval" property="responseInterval" jdbcType="BIGINT"/>
        <result column="log_type" property="logType" jdbcType="VARCHAR"/>
        <result column="log_identifying" property="logIdentifying" jdbcType="VARCHAR"/>
        <result column="modual_info" property="modualInfo" jdbcType="VARCHAR"/>
        <result column="method_desc" property="methodDesc" jdbcType="VARCHAR"/>
        <result column="method_type" property="methodType" jdbcType="VARCHAR"/>
        <result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
        <result column="create_user_code" property="createUserCode" jdbcType="VARCHAR"/>
        <result column="create_user_name" property="createUserName" jdbcType="VARCHAR"/>
    </resultMap>

    <sql id="base_column_list">
        id, request_ip, request_url, request_browser, request_source, request_method, request_params, 
        request_result,request_start_time, request_end_time, response_interval, log_type, log_identifying,
        modual_info, method_desc,method_type, create_time, create_user_code, create_user_name
    </sql>

2.1.5. 创建LogInfoService ,LogInfoServiceImpl

package com.zhuhuo.modual.service;


public interface LogInfoService {

}
package com.zhuhuo.modual.service.impl;

import com.zhuhuo.modual.service.LogInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service("logInfoService")
public class LogInfoServiceImpl implements LogInfoService {

    @Autowired
    private LogInfoMapper logInfoMapper;

}

2.1. 添加日志

2.1.1 引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

<dependency>
    <groupId>eu.bitwalker</groupId>
    <artifactId>UserAgentUtils</artifactId>
    <version>1.21</version>
</dependency>

2.1.2 创建日志枚举LogType

@Getter
@AllArgsConstructor
public enum LogType {

    /**
     * 新增
     */
    INSERT("insert","新增"),

    /**
     * 修改
     */
    UPDATE("update","修改"),

    /**
     * 删除
     */
    DELETE("delete","删除"),

    /**
     * 查看
     */
    VIEW("view","查看"),

    /**
     * 默认的
     */
    DEFAULTS("default","默认"),
    ;

    /**
     * 枚举标识
     */
    private String code;


    /**
     * 枚举描述
     */
    private String desc;


}

2.1.3 创建枚举LogSource

@Getter
@AllArgsConstructor
public enum LogSource {

    /**
     * 管理后台枚举
     */
    MANAGER("manager","管理后台"),

    /**
     * 博客前台枚举
     */
    PORTAL("portal","博客前台");

    /**
     * 枚举标识
     */
    private String code;


    /**
     * 枚举描述
     */
    private String desc;


}

2.1.4 创建注解ActionLog

package com.zhuhuo.core.logs.annotation;

import com.zhuhuo.core.logs.enums.LogSource;
import com.zhuhuo.core.logs.enums.LogType;

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ActionLog {

    /**
     * 模块相关
     * @return
     */
    String modual() default "";

    /**
     * 方法描述
     * @return
     */
    String methodDesc() default "";

    /**
     * 日志请求来源
     * @return
     */
    LogSource source() default LogSource.MANAGER;


    /**
     * 日志类别
     * @return
     */
    LogType logtype() default LogType.DEFAULTS;

}

2.1.5 创建切面LogAspect

package com.zhuhuo.core.logs.aspect;

import cn.hutool.http.ContentType;
import com.zhuhuo.common.JacksonUtil;
import com.zhuhuo.common.RequestUtil;
import com.zhuhuo.core.logs.annotation.ActionLog;
import com.zhuhuo.modual.entity.LogInfo;
import com.zhuhuo.modual.mapper.LogInfoMapper;
import eu.bitwalker.useragentutils.UserAgent;
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.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

@Slf4j
@Aspect
@Component
public class LogAspect {

    @Autowired
    private LogInfoMapper logInfoMapper;

    /**
     * 配置日志的切入点
     */
    @Pointcut("@annotation(com.zhuhuo.core.logs.annotation.ActionLog)")
    public void logPointCut(){

    }


    @Around("logPointCut()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        //做一些事情
        //1.获取日志的注解 拿到模块信息,方法描述,日志类别,日志来源
        ActionLog actionLog = ((MethodSignature)joinPoint.getSignature()).getMethod().getAnnotation(ActionLog.class);
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        //2.通过HttpServletRequest 获取请求相关信息 例如 请求地址 请求的ip信息,请求参数
        HttpServletRequest request = RequestUtil.getHttpServletRequest();
        //3.计算我们程序整体的请求响应时间
        long startTimeMillis = System.currentTimeMillis();
        Object result = joinPoint.proceed();
        long endTimeMillis = System.currentTimeMillis();
        long diffTimeMillis = endTimeMillis - startTimeMillis;
        //4.获取浏览器相关信息
        final UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
        //5.拼接对应的参数到日志表里面进行存储
        LogInfo logInfo = new LogInfo();
        logInfo.setRequestIp(RequestUtil.getHttpServletRequestIpAddress());
        logInfo.setRequestUrl(request.getRequestURI());
        logInfo.setRequestStartTime(new Date(startTimeMillis));
        logInfo.setRequestEndTime(new Date(endTimeMillis));
        logInfo.setResponseInterval(diffTimeMillis);
        logInfo.setModualInfo(actionLog.modual());
        logInfo.setMethodDesc(actionLog.methodDesc());
        logInfo.setRequestSource(actionLog.source().getDesc());
        logInfo.setLogType(actionLog.logtype().getDesc());
        logInfo.setLogIdentifying(actionLog.logtype().getCode());
        logInfo.setMethodType(request.getMethod());
        logInfo.setRequestMethod(className+methodName);
        logInfo.setRequestResult(JacksonUtil.transformJSONCompact(result));
        logInfo.setRequestBrowser(userAgent.getBrowser().getName());

        logInfo.setCreateTime(new Date());
        //设置创建人信息
        if("manager".equals(actionLog.source().getDesc())){
            // 在后面做完用户模块的时候进行完善
            // 解析用户信息 并获取用户编号和用户名称存放到对应的地方
        }else {
            //1.用户编号存储ip地址
            logInfo.setCreateUserCode(RequestUtil.getHttpServletRequestIpAddress());
            //2.用户名称存储ip地址,可以通过ip库反查询到ip信息 获取详细的省市区  在烛火项目中我们存储ip
            logInfo.setCreateUserName(RequestUtil.getHttpServletRequestIpAddress());
        }
        logInfo.setRequestParams(spliceingParameters(request));
        logInfoMapper.insertSelective(logInfo);
        //返回
        return result;
    }

    /**
     * 获取请求参数信息
     * @param request
     * @return
     */
    private String spliceingParameters(HttpServletRequest request) throws IOException {
        String requestParams = null;
        String contentType = request.getContentType();
        if(ContentType.JSON.getValue().equals(contentType)){
            //通过流的方式去获取参数信息
            StringBuilder sb = new StringBuilder();
            BufferedReader bufferedReader = request.getReader();
            String line;
            while ((line = bufferedReader.readLine()) != null){
                sb.append(line);
            }
            requestParams = sb.toString();

        } else if (ContentType.FORM_URLENCODED.getValue().equals(contentType)) {
            Map<String,Object> requestParmsMap = new HashMap<>();
            Enumeration<String> enumerations = request.getParameterNames();
            while (enumerations.hasMoreElements()){
                String key = (String)enumerations.nextElement();
                String values[] = request.getParameterValues(key);
                requestParmsMap.put(key,values.length == 1 ? request.getParameter(key):values);
            }

            requestParams = JacksonUtil.transformJSONCompact(requestParmsMap);
        }
        return requestParams;
    }
}

2.1.6 解决getInputStream() has already been called for this request

2.1.6.1. CustomerRequestWrapper

public class CustomerRequestWrapper extends HttpServletRequestWrapper {

    /**
     * 存储流信息
     */
    private final byte[] body;

    /**
     * 创建一个新的request请求
     * @param request
     */
    public CustomerRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        body = IOUtils.toByteArray(request.getInputStream());
    }


    /**
     * 重写getReader 主要是重写inputStream信息 在读取的时候读取我们自定义的body的
     * @return
     * @throws IOException
     */
    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        final ByteArrayInputStream bais = new ByteArrayInputStream(body);
        return new ServletInputStream() {
            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setReadListener(ReadListener readListener) {

            }

            @Override
            public int read() throws IOException {
                return bais.read();
            }
        };
    }
}

2.1.6.2. CustomerHttpServletRequestFilter

@Component
public class CustomerHttpServletRequestFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        ServletRequest servletWrapper = null;
        if(servletRequest instanceof HttpServletRequest){
            servletWrapper = new CustomerRequestWrapper((HttpServletRequest) servletRequest);
        }
        if(servletRequest == null){
            filterChain.doFilter(servletRequest,servletResponse);
        }else {
            try {
                filterChain.doFilter(servletWrapper,servletResponse);
            }catch (IOException e){
                e.printStackTrace();
            }catch (ServletException e){
                e.printStackTrace();
            }
        }
    }
}

2.3. 查询日志列表

2.3.1. 静态页面

2.3.1.1. 页面布局

<!DOCTYPE html>
<html lang="en">
<head>
    <div th:replace = "/manager/common/common :: core-head('日志','日志','')"></div>
    <div th:replace = "/manager/common/common :: core-css"></div>
    <div th:replace = "/manager/common/common :: lib-bootstrap-table-css"></div>
</head>
<body class="gray-bg">
     <div class="wrapper wrapper-content">
         <!-- 搜索区域-->
         <div class="panel">
             <div class="panel-body">
                 <form role="search-form" class="form-inline" id="search-form">
                     <div class="form-group">
                         <label class="control-label">请求IP</label>
                         <input type="text" placeholder="请输入请求IP" id="requestIp" name="requestIp" class="form-control">
                     </div>

                     <div class="form-group">
                         <label class="control-label">请求来源</label>
                         <select class="form-control" name="requestSource" id="requestSource">
                             <option value="">全部来源</option>
                             <option value="manager" >管理后台</option>
                             <option value="portal">博客前台</option>
                         </select>
                     </div>

                     <a class="btn btn-primary" id="searchBtn"  notallow="$.bstable.search()">
                         <i class="fa fa-search"></i> 搜索
                     </a>
                     <a class="btn btn-warning" id="resetBtn" notallow="$.bstable.reset()">
                         <i class="fa fa-refresh"></i> 重置
                     </a>
                 </form>
             </div>
         </div>
         <!-- 表格区域-->
         <div class="panel">
             <div class="panel-body">
                 <!-- 内容区域-->
                 <div class="select-table table-striped">
                     <table id="bootstrap-table-list" ></table>
                 </div>
             </div>
         </div>
     </div>
    <div th:replace = "/manager/common/common :: core-js"></div>
    <div th:replace = "/manager/common/common :: lib-bootstrap-table-js"></div>
    <script src="../../../static/local/js/zhuhuo.js" th:src="@{/local/js/zhuhuo.js}" ></script>
</body>
</html>

2.3.1.2. 初始化表格js

<script>
    let options = {
        url:"/m/logInfo/findLogInfoList",
        viewPageUrl: "/m/logInfo/viewLogInfoPage/{id}",
        modualName: "日志",
        columns: [
            {
                checkbox: true
            },
            {
                field: 'id',
                title: 'id'
            },
            {
                field: 'modualInfo',
                title: '模块信息'
            },
            {
                field: 'requestUrl',
                title: '请求地址'
            },
            {
                field: 'requestIp',
                title: '请求IP'
            },
            {
                field: 'requestBrowser',
                title: '浏览器'
            },

            {
                field: 'requestSource',
                title: '请求来源',
                formatter: function (value, item, index) {
                    if (item.requestSource == 'manager') {
                        return '管理后台';
                    } else  {
                        return '博客前台';
                    }
                }
            },
            {
                field: 'createTime',
                title: '创建时间'
            },
            {
                title: '操作',
                align: 'center',
                formatter: function actionFormatter(value, item) {
                        let btnArr = [];
                        btnArr.push('<button type="button" class="btn btn-sm btn-secondary" 
                                    data-toggle="tooltip" title="查看明细" data-width="720" data-height="450"
                                    notallow="$.action.viewPage('+item.id+')">
                                    <i class="fa fa-search"></i></button>');
                        return btnArr.join(" ");

                },
            }
        ],
    }
    $.bstable.init(options);
</script>

2.3.1.3. sidebar修改

<li>
  <a class="zh-menu-item" th:href="@{/m/logInfo/findLogInfoPage}">
    <i class="fa fa-navicon"></i>日志管理
  </a>
<li>  

2.3.2. 列表功能

2.3.2.1. 创建查询列表页面方法

@ActionLog(methodDesc = "查询日志列表页面",source = LogSource.MANAGER,modual = "日志管理" ,logtype = LogType.VIEW)
@GetMapping(value = "/findLogInfoPage")
public String findLogInfoPage(){
    return "/manager/loginfo/list";
}

2.3.2.2.创建请求对象和响应对象

2.3.2.2.1. 请求对象 NavListBO
@Data
public class LogInfoListBO {


    /**
     * 请求ip
     */
    private String requestIp;

    /**
     * 请求来源
     */
    private String reuqestSource;

    /**
     * 每页展示的数量
     */
    private Integer pageSize;


    /**
     * 分页的索引 当前是第几页
     */
    private Integer pageNum;
}
2.3.2.2.2. 响应对象 NavListDTO
@Data
public class LogInfoListDTO {
    /**
     * id
     */
    @Id
    private Long id;
    /**
     * 请求ip
     */
    private String requestIp;
    /**
     * 请求地址
     */
    private String requestUrl;
    /**
     * 日志请求浏览器
     */
    private String requestBrowser;
    /**
     * 请求来源  manager 管理后台 portal 博客前台
     */
    private String requestSource;
    /**
     * 请求方法
     */
    private String requestMethod;
    /**
     * 日志类型(类型里面的value)
     */
    private String logType;
    /**
     * 模块信息/请求模块
     */
    private String modualInfo;

    /**
     * 创建时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss" ,shape = JsonFormat.Shape.STRING,timezone = "GTM+8")
    private Date createTime;

}

2.3.2.3. 创建查询列表明细方法

2.3.2.3.1. controller
@ActionLog(methodDesc = "查询日志列表明细",source = LogSource.MANAGER,modual = "日志管理" ,logtype = LogType.VIEW)
@ResponseBody
@GetMapping(value = "/findLogInfoList")
public RespJsonPageData<LogInfoListDTO> findLogInfoList(LogInfoListBO logInfoListBO){
    return logInfoService.findLogInfoList(logInfoListBO);
}
2.3.2.3.2. service
RespJsonPageData<LogInfoListDTO> findLogInfoList(LogInfoListBO logInfoListBO);
2.3.2.3.3. serviceImpl
@Override
public RespJsonPageData<LogInfoListDTO> findLogInfoList(LogInfoListBO logInfoListBO) {
    PageQuery pageQuery = new PageQuery(logInfoListBO);
    Page<Object> result = PageHelper.startPage(pageQuery.getPageNum(),pageQuery.getPageSize());
    List<LogInfo> logInfoList = logInfoMapper.findLogInfoList(MapUtils.objToMap(logInfoListBO));
    List<LogInfoListDTO> logInfoListDTOList = 
    JacksonUtil.transformList(JacksonUtil.transformJSONCompact(logInfoList),LogInfoListDTO.class);
    return RespJsonPageData.success(logInfoListDTOList,result.getTotal());
}
2.3.2.3.4. mapper
List<LogInfo> findLogInfoList(Map<String, Object> params);
2.3.2.3.5. xml
<select id="findLogInfoList" parameterType="java.util.Map" resultMap="BaseResultMap">
    select
    <include refid="base_column_list"/>
    from zh_log_info
    <include refid="search_list_condition"/>
    order by create_time desc ,id desc
</select>


<!-- 搜索参数 -->
<sql id="search_list_condition">
    <where>
        <if test="requestIp != null and requestIp != ''">
            and request_ip =#{requestIp}
        </if>

        <if test="requestSource != null and requestSource != ''">
            and request_source =#{requestSource}
        </if>

    </where>
</sql>

2.3.2.3. 测试查询列表

018-管理后台操作日志功能开发_日志_03

2.4. 查询日志明细

2.4.1. 静态页面

2.4.1.1. 页面布局

<!DOCTYPE html>
<html lang="en">
<head>
    <div th:replace = "/manager/common/common :: core-head('查询导航明细','zhuhuo-blog,烛火博客,blog','')"></div>
    <div th:replace = "/manager/common/common :: core-css"></div>
</head>
<body class="gray-bg">

<div class="wrapper wrapper-content">
    <div class="panel">
        <div class="panel-body">
            <form  class="form-horizontal m" id="view-form" style="padding-left: 20px;padding-right: 20px" th:object="${viewLogInfo}">

                <input type="hidden" name="id" th:field="*{id}">

                <div class="form-group">
                    <div class="col-md-4">
                        <div class="input-group m-b">
                            <span class="input-group-addon">请求ip</span>
                            <input type="text" class="form-control" id="requestIp" name="requestIp" th:field="*{requestIp}" disabled>
                        </div>
                    </div>

                    <div class="col-md-4">
                        <div class="input-group m-b">
                            <span class="input-group-addon">请求地址</span>
                            <input type="text" class="form-control" id="requestUrl" name="requestUrl" th:field="*{requestUrl}" disabled>
                        </div>
                    </div>

                    <div class="col-md-4">
                        <div class="input-group m-b">
                            <span class="input-group-addon">请求来源</span>

                            <select class="form-control m-b" name="requestSource" id="requestSource" th:field="*{requestSource}" disabled>
                                <option value="manager">管理后台</option>
                                <option value="portal">博客前台</option>
                            </select>

                        </div>
                    </div>
                </div>

                <div class="form-group">
                    <div class="col-md-4">
                        <div class="input-group m-b">
                            <span class="input-group-addon">模块信息</span>
                            <input type="text" class="form-control" id="modualInfo" name="modualInfo" th:field="*{modualInfo}" disabled>
                        </div>
                    </div>

                    <div class="col-md-4">
                        <div class="input-group m-b">
                            <span class="input-group-addon">方法描述</span>
                            <input type="text" class="form-control" id="methodDesc" name="methodDesc" th:field="*{methodDesc}" disabled>
                        </div>
                    </div>

                    <div class="col-md-4">
                        <div class="input-group m-b">
                            <span class="input-group-addon">请求方式</span>
                            <input type="text" class="form-control" id="methodType" name="methodType" th:field="*{methodType}" disabled>
                        </div>
                    </div>
                </div>


                <div class="form-group">
                    <div class="col-md-4">
                        <div class="input-group m-b">
                            <span class="input-group-addon">浏览器</span>
                            <input type="text" class="form-control" id="requestBrowser" name="requestBrowser" th:field="*{requestBrowser}" disabled>
                        </div>
                    </div>

                    <div class="col-md-4">
                        <div class="input-group m-b">
                            <span class="input-group-addon">日志标识</span>
                            <input type="text" class="form-control" id="logIdentifying" name="logIdentifying" th:field="*{logIdentifying}" disabled>
                        </div>
                    </div>

                    <div class="col-md-4">
                        <div class="input-group m-b">
                            <span class="input-group-addon">日志类型</span>
                            <input type="text" class="form-control" id="logType" name="logType" th:field="*{logType}" disabled>
                        </div>
                    </div>
                </div>

                <div class="form-group">
                    <div class="col-md-4">
                        <div class="input-group m-b">
                            <span class="input-group-addon">开始时间</span>
                            <input type="text" class="form-control" id="requestStartTime" name="requestStartTime" th:field="*{requestStartTime}" disabled>
                        </div>
                    </div>

                    <div class="col-md-4">
                        <div class="input-group m-b">
                            <span class="input-group-addon">结束时间</span>
                            <input type="text" class="form-control" id="requestEndTime" name="requestEndTime" th:field="*{requestEndTime}" disabled>
                        </div>
                    </div>

                    <div class="col-md-4">
                        <div class="input-group m-b">
                            <span class="input-group-addon">响应时长(ms)</span>
                            <input type="text" class="form-control" id="responseInterval" name="responseInterval" th:field="*{responseInterval}" disabled>
                        </div>
                    </div>
                </div>

                <div class="form-group">
                    <div class="col-md-12">
                        <div class="input-group m-b">
                            <span class="input-group-addon">请求方法</span>
                            <input type="text" class="form-control" id="requestMethod" name="requestMethod" th:field="*{requestMethod}" disabled>
                        </div>
                    </div>
                </div>


                <div class="form-group">
                    <div class="col-md-12">
                        <div class="input-group m-b">
                            <span class="input-group-addon">请求参数</span>
                            <textarea class="form-control" id="requestParams" name="requestParams" th:field="*{requestParams}" disabled></textarea>
                        </div>
                    </div>
                </div>

                <div class="form-group">
                    <div class="col-md-12">
                        <div class="input-group m-b">
                            <span class="input-group-addon">响应结果</span>
                            <textarea class="form-control" id="requestResult" name="requestResult" th:field="*{requestResult}" disabled></textarea>
                        </div>
                    </div>
                </div>


                <div class="form-group">
                    <div class="col-md-4">
                        <div class="input-group m-b">
                            <span class="input-group-addon">创建时间</span>
                            <input type="text" class="form-control" id="createTime" name="createTime" th:field="*{createTime}" disabled>
                        </div>
                    </div>

                    <div class="col-md-4">
                        <div class="input-group m-b">
                            <span class="input-group-addon">创建人标识</span>
                            <input type="text" class="form-control" id="createUserCode" name="createUserCode" th:field="*{createUserCode}" disabled>
                        </div>
                    </div>

                    <div class="col-md-4">
                        <div class="input-group m-b">
                            <span class="input-group-addon">创建人名称</span>
                            <input type="text" class="form-control" id="createUserName" name="createUserName" th:field="*{createUserName}" disabled>
                        </div>
                    </div>
                </div>
            </form>
        </div>
    </div>
</div>
<div th:replace = "/manager/common/common :: core-js"></div>
</body>
</html>

2.4.2.明细功能

2.4.2.1.创建响应对象

2.4.2.1.1. 响应对象 LogInfoDetailDTO
@Data
public class LogInfoDetailDTO {

    /**
     * id
     */
    private Long id;
    /**
     * 请求ip
     */
    private String requestIp;
    /**
     * 请求地址
     */
    private String requestUrl;
    /**
     * 日志请求浏览器
     */
    private String requestBrowser;
    /**
     * 请求来源  manager 管理后台 portal 博客前台
     */
    private String requestSource;
    /**
     * 请求方法
     */
    private String requestMethod;
    /**
     * 请求参数
     */
    private String requestParams;
    /**
     * 请求响应结果
     */
    private String requestResult;
    /**
     * 请求开始时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss" ,shape = JsonFormat.Shape.STRING,timezone = "GTM+8")
    private Date requestStartTime;
    /**
     * 响应时间/请求结束时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss" ,shape = JsonFormat.Shape.STRING,timezone = "GTM+8")
    private Date requestEndTime;
    /**
     * 响应时长(ms)
     */
    private Long responseInterval;
    /**
     * 日志类型(类型里面的value)
     */
    private String logType;
    /**
     * 日志标识(类型里面的key)
     */
    private String logIdentifying;
    /**
     * 模块信息/请求模块
     */
    private String modualInfo;
    /**
     * 方法描述
     */
    private String methodDesc;
    /**
     * 请求方法类别 GET/POST/PUT/DELETE
     */
    private String methodType;
    /**
     * 创建时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss" ,shape = JsonFormat.Shape.STRING,timezone = "GTM+8")
    private Date createTime;
    /**
     * 创建人标识
     */
    private String createUserCode;
    /**
     * 创建人名称
     */
    private String createUserName;
}

2.4.2.2. 创建查询导航明细方法

2.3.2.2.1. controller
@ActionLog(methodDesc = "查询日志详情",source = LogSource.MANAGER,modual = "日志管理" ,logtype = LogType.VIEW)
@GetMapping(value = "/viewLogInfoPage/{id}")
public String viewLogInfoPage(@PathVariable("id") Long id, ModelMap modelMap){
    return logInfoService.viewLogInfoPage(id,modelMap);
}
2.3.2.2.2. service
String viewLogInfoPage(Long id, ModelMap modelMap);
2.3.2.2.3. serviceImpl
@Override
public String viewLogInfoPage(Long id, ModelMap modelMap) {
    LogInfo logInfo = logInfoMapper.selectByPrimaryKey(id);
    LogInfoDetailDTO logInfoDetailDTO = 
    JacksonUtil.transformObject(JacksonUtil.transformJSONCompact(logInfo),LogInfoDetailDTO.class);
    modelMap.put("viewLogInfo",logInfoDetailDTO);
    return "/manager/loginfo/view";
}

2.4.2.3. 测试查询明细

018-管理后台操作日志功能开发_spring_04

2.5. 拓展拦截器方式实现日志插入

package com.zhuhuo.core.interceptor;

import cn.hutool.http.ContentType;
import com.zhuhuo.common.JacksonUtil;
import com.zhuhuo.common.RequestUtil;
import com.zhuhuo.core.logs.annotation.ActionLog;
import com.zhuhuo.modual.entity.LogInfo;
import com.zhuhuo.modual.mapper.LogInfoMapper;
import eu.bitwalker.useragentutils.UserAgent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

/**
 * @author 玖零
 * @version v0.0.1
 * @project zhuhuo-blog
 * @package com.zhuhuo.core.interceptor
 * @date 2023-08-22 03:13
 * @desc TODO
 */
public class UserLogInterceptor extends HandlerInterceptorAdapter {

    @Autowired
    private LogInfoMapper logInfoMapper;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        long startTimeMillis = System.currentTimeMillis();
        request.setAttribute("startTimeMillis",startTimeMillis);
        return super.preHandle(request, response, handler);
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        long endTimeMillis = System.currentTimeMillis();
        request.setAttribute("endTimeMillis",endTimeMillis);
        super.postHandle(request, response, handler, modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        //做一些事情
        //1.获取日志的注解 拿到模块信息,方法描述,日志类别,日志来源
        ActionLog actionLog = handlerMethod.getBeanType().getAnnotation(ActionLog.class);
        String className = handlerMethod.getClass().getName();
        String methodName = handlerMethod.getMethod().getName();
        //3.计算我们程序整体的请求响应时间
        long startTimeMillis = (long)request.getAttribute("startTimeMillis");
        long endTimeMillis = (long)request.getAttribute("endTimeMillis");
        long diffTimeMillis = endTimeMillis - startTimeMillis;
        //4.获取浏览器相关信息
        final UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
        //5.拼接对应的参数到日志表里面进行存储
        LogInfo logInfo = new LogInfo();
        logInfo.setRequestIp(RequestUtil.getHttpServletRequestIpAddress());
        logInfo.setRequestUrl(request.getRequestURI());
        logInfo.setRequestStartTime(new Date(startTimeMillis));
        logInfo.setRequestEndTime(new Date(endTimeMillis));
        logInfo.setResponseInterval(diffTimeMillis);
        logInfo.setModualInfo(actionLog.modual());
        logInfo.setMethodDesc(actionLog.methodDesc());
        logInfo.setRequestSource(actionLog.source().getCode());
        logInfo.setLogType(actionLog.logtype().getDesc());
        logInfo.setLogIdentifying(actionLog.logtype().getCode());
        logInfo.setMethodType(request.getMethod());
        logInfo.setRequestMethod(className+methodName);
        logInfo.setRequestResult(JacksonUtil.transformJSONCompact());
        logInfo.setRequestBrowser(userAgent.getBrowser().getName());

        logInfo.setCreateTime(new Date());
        //设置创建人信息
        if("manager".equals(actionLog.source().getDesc())){
            // 在后面做完用户模块的时候进行完善
            // 解析用户信息 并获取用户编号和用户名称存放到对应的地方
        }else {
            //1.用户编号存储ip地址
            logInfo.setCreateUserCode(RequestUtil.getHttpServletRequestIpAddress());
            //2.用户名称存储ip地址,可以通过ip库反查询到ip信息 获取详细的省市区  在烛火项目中我们存储ip
            logInfo.setCreateUserName(RequestUtil.getHttpServletRequestIpAddress());
        }
        logInfo.setRequestParams(spliceingParameters(request));
        logInfoMapper.insertSelective(logInfo);
        //返回
        super.afterCompletion(request, response, handler, ex);
    }


    /**
     * 获取请求参数信息
     * @param request
     * @return
     */
    private String spliceingParameters(HttpServletRequest request) throws IOException {
        String requestParams = null;
        String contentType = request.getContentType();
        if(ContentType.JSON.getValue().equals(contentType)){
            //通过流的方式去获取参数信息
            StringBuilder sb = new StringBuilder();
            BufferedReader bufferedReader = request.getReader();
            String line;
            while ((line = bufferedReader.readLine()) != null){
                sb.append(line);
            }
            requestParams = sb.toString();

        } else if (ContentType.FORM_URLENCODED.getValue().equals(contentType)) {
            Map<String,Object> requestParmsMap = new HashMap<>();
            Enumeration<String> enumerations = request.getParameterNames();
            while (enumerations.hasMoreElements()){
                String key = (String)enumerations.nextElement();
                String values[] = request.getParameterValues(key);
                requestParmsMap.put(key,values.length == 1 ? request.getParameter(key):values);
            }

            requestParams = JacksonUtil.transformJSONCompact(requestParmsMap);
        }
        return requestParams;
    }
}



【文章原创作者:滨海网站开发公司 http://www.1234xp.com/binhai.html 欢迎留下您的宝贵建议】
上一篇:生产环境 kafka 平滑迁移之旅
下一篇:没有了
网友评论