1.功能分析
1.1. 查询列表
1.1.1. 页面效果
1.1.2. 功能要求
- 分页查询默认查询10条每页从第1页开始查询
- 日志只提供查询操作
- 搜索条件
- 日志来源:精准搜索
- 请求ip:精准搜索
- 点击搜索按钮是按照录入的搜索条件进行查询数据并渲染
- 点击重置按钮的时候清空搜索条件,并重新渲染数据
1.2.插入日志
1.2.1. 功能要求
- 日志参数包含:请求ip,请求地址,浏览器信息(如果无则为空),请求时间,请求来源(管理后台/博客前台),请求方法,模块信息,请求参数,响应结果,响应时间,响应时长(s),方法描述,日志类型(新增/修改等等),创建时间,创建人编号(如果为客户端访问则填充ip信息),创建人名称(如果为客户端访问则填充ip信息)
1.3查看详情
1.3.1. 页面效果
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. 测试查询列表
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. 测试查询明细
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;
}
}