需求: 要求對系統的所有操作進行日誌記錄
分表規則 僅供參考:
採取的是基於業務的模式:迫使用戶無法進行跳頁查詢,什麼意思呢,就是用戶只能點擊下一頁或者上一頁的方式瀏覽,具體的做法在於查詢得到記錄數的同時記錄下當前唯一id值的最大值,然後再次查詢的時候添加where 條件…讓我們從頭開始捋: 第一次查詢pageNum=1,pageSize=10 ,maxId=0->sql:select * from db_x where id>0 limit 10; 然後分發到對應的庫的表中,將得到的4*10條數據合併,再在內存中進行解析排序,取前10條數據,同時將第10條數據的id=maxId單獨取出渲染到前端頁面上保存,這樣當點擊下一頁的時候,這個maxId=10也提交上去了,sql 變成了select * from db_x where id>10 limit 10,然後繼續解析,繼續保存…這種方式返回的數據都是穩定的並且數據是連貫的(排序)
mysql表
CREATE TABLE `tb_logging` (
`id` int(64) NOT NULL COMMENT 'id',
`class_name` varchar(64) DEFAULT NULL COMMENT '類名',
`method_name` varchar(64) DEFAULT NULL COMMENT '方法名',
`param` varchar(3000) DEFAULT NULL COMMENT '參數',
`url` varchar(512) DEFAULT NULL COMMENT '訪問的url',
`ip` varchar(64) DEFAULT NULL COMMENT '操作人ip',
`create_user_id` varchar(64) DEFAULT NULL COMMENT '創建人id',
`create_time` datetime DEFAULT NULL COMMENT '創建時間',
`state_time` datetime DEFAULT NULL COMMENT '執行開始時間',
`end_time` datetime DEFAULT NULL COMMENT '執行結束時間',
`long_time` varchar(64) DEFAULT NULL COMMENT '耗時',
`method` varchar(32) DEFAULT NULL COMMENT '請求方式GET、POST...',
`module_name` varchar(512) DEFAULT NULL COMMENT '操作模塊描述',
`return_value` varchar(521) DEFAULT NULL COMMENT '返回值',
`browser` varchar(128) DEFAULT NULL COMMENT '當前操作的瀏覽器',
`is_delete` tinyint(1) DEFAULT '0' COMMENT '是否刪除 0、未刪除 1、刪除',
`state` varchar(32) DEFAULT NULL COMMENT '返回狀態碼',
PRIMARY KEY (`id`),
KEY `log_Joint_idx` (`is_delete`,`class_name`,`method_name`,`create_time`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='日誌記錄 用於新增修改刪除等操作記錄';
CREATE TABLE `tb_logging_select` (
`id` int(64) NOT NULL COMMENT 'id',
`class_name` varchar(64) DEFAULT NULL COMMENT '類名',
`method_name` varchar(64) DEFAULT NULL COMMENT '方法名',
`param` varchar(3000) DEFAULT NULL COMMENT '參數',
`url` varchar(512) DEFAULT NULL COMMENT '訪問的url',
`ip` varchar(64) DEFAULT NULL COMMENT '操作人ip',
`create_user_id` varchar(64) DEFAULT NULL COMMENT '創建人id',
`create_time` datetime DEFAULT NULL COMMENT '創建時間',
`state_time` datetime DEFAULT NULL COMMENT '執行開始時間',
`end_time` datetime DEFAULT NULL COMMENT '執行結束時間',
`long_time` varchar(64) DEFAULT NULL COMMENT '耗時',
`method` varchar(32) DEFAULT NULL COMMENT '請求方式GET、POST...',
`module_name` varchar(512) DEFAULT NULL COMMENT '操作模塊描述',
`return_value` varchar(521) DEFAULT NULL COMMENT '返回值',
`browser` varchar(128) DEFAULT NULL COMMENT '當前操作的瀏覽器',
`is_delete` tinyint(1) DEFAULT '0' COMMENT '是否刪除 0、未刪除 1、刪除',
`state` varchar(32) DEFAULT NULL COMMENT '返回狀態碼',
PRIMARY KEY (`id`),
KEY `log_Joint_idx` (`is_delete`,`class_name`,`method_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='日誌記錄用於查詢操作記錄';
實體類:
import lombok.Data;
import java.util.Date;
/**
* 項目名稱:
* 類 名 稱:TbLogging
* 類 描 述:TODO
* 創建時間:2020/6/11 19:30
* 創 建 人:heng
*/
@Data
public class TbLogging {
//id
private java.lang.Integer id;//id
//類名
private java.lang.String class_name;//類名
//方法名
private java.lang.String method_name;//方法名
//參數
private java.lang.String param;//參數
//訪問的url
private java.lang.String url;//訪問的url
//操作人ip
private java.lang.String ip;//操作人ip
//創建人id
private java.lang.String create_user_id;//創建人id
//創建時間
private Date create_time;//創建時間
//執行開始時間
private Date state_time;//執行開始時間
//執行結束時間
private Date end_time;//執行結束時間
//耗時
private java.lang.String long_time;//耗時
//請求方式GET、POST...
private java.lang.String method;//請求方式GET、POST...
//操作模塊描述
private java.lang.String module_name;//操作模塊描述 //操作模塊描述
//返回值
private java.lang.String return_value;//返回值
//當前操作的瀏覽器
private java.lang.String browser;//當前操作的瀏覽器
//是否刪除 0、未刪除 1、刪除
private java.lang.Integer is_delete;//是否刪除 0、未刪除 1、刪除
private java.lang.String state;//操作模塊描述
public Date getCreate_time() {
return create_time;
}
public void setCreate_time(Date create_time) {
this.create_time = create_time;
}
public Date getState_time() {
return state_time;
}
public void setState_time(Date state_time) {
this.state_time = state_time;
}
public Date getEnd_time() {
return end_time;
}
public void setEnd_time(Date end_time) {
this.end_time = end_time;
}
}
import lombok.Data;
import java.util.Date;
/**
* 項目名稱:
* 類 名 稱:TbLoggingSelect
* 類 描 述:TODO
* 創建時間:2020/6/11 19:30
* 創 建 人:heng
*/
@Data
public class TbLoggingSelect {
//id
private Integer id;//id
//類名
private String class_name;//類名
//方法名
private String method_name;//方法名
//參數
private String param;//參數
//訪問的url
private String url;//訪問的url
//操作人ip
private String ip;//操作人ip
//創建人id
private String create_user_id;//創建人id
//創建時間
private Date create_time;//創建時間
//執行開始時間
private Date state_time;//執行開始時間
//執行結束時間
private Date end_time;//執行結束時間
//耗時
private String long_time;//耗時
//請求方式GET、POST...
private String method;//請求方式GET、POST...
//操作模塊描述
private String module_name;//操作模塊描述 //操作模塊描述
//返回值
private String return_value;//返回值
//當前操作的瀏覽器
private String browser;//當前操作的瀏覽器
//是否刪除 0、未刪除 1、刪除
private Integer is_delete;//是否刪除 0、未刪除 1、刪除
private String state;//操作模塊描述
public Date getCreate_time() {
return create_time;
}
public void setCreate_time(Date create_time) {
this.create_time = create_time;
}
public Date getState_time() {
return state_time;
}
public void setState_time(Date state_time) {
this.state_time = state_time;
}
public Date getEnd_time() {
return end_time;
}
public void setEnd_time(Date end_time) {
this.end_time = end_time;
}
}
攔截器採集日誌
id是用redis生成的小夥伴可以用別的
新增語句這裏就不貼出來了
以下操作查詢的放一個表
其他放一張表
package com.web.common.intercept;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.web.common.controller.BaseController;
import com.web.entity.TbLogging;
import com.web.entity.TbLoggingSelect;
import nl.bitwalker.useragentutils.UserAgent;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 redis.clients.jedis.Jedis;
import web.dao.hsdao.TbLoggingMapper;
import web.dao.hsdao.TbLoggingSelectMapper;
import web.util.JedisUtil;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
/**
* @ClassName SginAop
* @Description
* @Author heng
* @Date 2020/4/26 10:41
* @Version 1.0
*/
public class LogInterceptor extends HandlerInterceptorAdapter {
private final String redisKey = "LOG:LOGGING_KEY";
private final static Logger LOGGER = LoggerFactory.getLogger(LogInterceptor.class);
//請求開始時間標識
private static final String LOGGER_SEND_TIME = "_send_time";
//請求日誌實體標識
private static final String LOGGER_ENTITY = "_logger_entity";
@Autowired
private TbLoggingSelectMapper tbLoggingSelectMapper;
@Autowired
private TbLoggingMapper tbLoggingMapper;
public Long incr(String key) {
Jedis jedis =JedisUtil.getJedis();
try {
return jedis.incr(key);
} finally {
if (null != jedis) {
jedis.close();
}
}
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler.getClass().isAssignableFrom(HandlerMethod.class)) {
try {
TbLogging tbLogging = new TbLogging();
Long start_long_time = System.currentTimeMillis();
tbLogging.setState_time(DateUtil.date(start_long_time));
//獲取ip
tbLogging.setIp(getRemoteHost(request));
//獲取控制器的名字
tbLogging.setClass_name(((HandlerMethod) handler).getBean().getClass().getName());
//獲取方法名
tbLogging.setMethod_name(((HandlerMethod) handler).getMethod().getName());
//獲取請求參數信息
String param = JSON.toJSONString(request.getParameterMap(),
SerializerFeature.DisableCircularReferenceDetect,
SerializerFeature.WriteMapNullValue);
tbLogging.setParam(param);
try {
tbLogging.setCreate_user_id(new BaseController().obtainLoginUserId(request));
}catch (Exception ex){
LOGGER.info("拿到當前登錄人失敗");
}
//請求方法
tbLogging.setMethod(request.getMethod());
//請求url到後端的url
tbLogging.setUrl(request.getRequestURI());
//瀏覽器
//獲取瀏覽器信息
String ua = request.getHeader("User-Agent");
//轉成UserAgent對象
UserAgent userAgent = UserAgent.parseUserAgentString(ua);
tbLogging.setBrowser(userAgent.toString());
//獲取返回值
//tbLogging.setReturn_value(response.getWriter().toString());
//設置請求開始時間
request.setAttribute(LOGGER_SEND_TIME, start_long_time);
//設置請求實體到request內,方便afterCompletion方法調用
request.setAttribute(LOGGER_ENTITY, tbLogging);
} catch (Exception e) {
LOGGER.info("日誌添加失敗");
}
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
try {
String uri = request.getRequestURI();
String contextPath = request.getContextPath();
if (StringUtils.length(contextPath) > 0) {
contextPath = StringUtils.substring(uri, contextPath.length());
}
TbLogging tbLogging = (TbLogging) request.getAttribute(LOGGER_ENTITY);
//獲取請求錯誤碼
int status = response.getStatus();
//當前時間
long currentTime = System.currentTimeMillis();
//請求開始時間
long time = Long.valueOf(request.getAttribute(LOGGER_SEND_TIME).toString());
//獲取本次請求日誌實體
tbLogging.setState(status+"");
//設置請求時間差
tbLogging.setLong_time((currentTime - time)+"");
tbLogging.setEnd_time(DateUtil.date(currentTime));
tbLogging.setCreate_time(DateUtil.date(currentTime));
// Long longId = IdUtil.createSnowflake(1, 1).nextId();
Long longId = incr(redisKey);
tbLogging.setId(longId.intValue());
if(tbLogging.getClass_name().indexOf("TbLoggingController") == -1){
if (contextPath.indexOf("search_") == -1
&& !tbLogging.getMethod_name().startsWith("search")
&& !tbLogging.getMethod_name().startsWith("get")
&& !tbLogging.getMethod_name().startsWith("query")
&& !tbLogging.getMethod_name().startsWith("find")
&& !tbLogging.getMethod_name().startsWith("select")
&& !tbLogging.getMethod_name().equals("index")) {
//執行將日誌寫入數據庫,可以根據實際需求進行保存
// sysLogRepo.save(sysLog);
tbLoggingMapper.insertTbLogging(tbLogging);
}else {
TbLoggingSelect select = new TbLoggingSelect();
//把tbLogging的值給select
BeanUtils.copyProperties(select,tbLogging);
tbLoggingSelectMapper.insertTbLoggingSelect(select);
}
}
}catch (Exception e){
LOGGER.info("日誌添加失敗");
}
}
public int difference(Date nowDate, String decrypted){
return Math.abs((int)(nowDate.getTime()-Long.valueOf(decrypted))/1000);
}
/**
* @Title: getRemoteHost
* @Description: 獲取Ip地址
* @return: String
* @version V1.0
*/
public String getRemoteHost(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip.equals("0:0:0:0:0:0:0:1") ? "127.0.0.1" : ip;
}
}
以下操作用於查詢
mapper.xml
/**
* 用於分表分頁查詢
* @param map
* @return
*/
List<TbLogging> pageListTbLoggingSelectByObj(Map<String,Object> map);
int pageListTbLoggingSelectByObjCount(Map<String,Object> map);
<select id="pageListTbLoggingSelectByObj" parameterType="map" resultMap="BaseResultMap2">
SELECT <include refid="Base_Column_List" />
FROM tb_logging_select
<where>
is_delete = 0
<if test ='null != create_time and create_time != ""'>
AND date_format(create_time,'%Y-%m-%d') = #{create_time}
</if>
<if test ='null != maxId and maxId != "" '>
AND id > #{maxId}
</if>
<if test ='null != minId and minId != ""'>
AND id < #{minId}
</if>
</where>
<if test ='null != maxId and maxId != "" '>
order by id asc
</if>
<if test ='null != minId and minId != ""'>
order by id desc
</if>
<if test ='(minId == null or minId == "") and (maxId == "" or maxId ==null) '>
order by id desc
</if>
LIMIT #{pageSize}
</select>
<select id="pageListTbLoggingSelectByObjCount" parameterType="map" resultType="int">
SELECT count(1)
FROM tb_logging_select
<where>
is_delete = 0
<if test ='null != create_time and create_time != ""'>
AND date_format(create_time,'%Y-%m-%d') = #{create_time}
</if>
</where>
</select>
/**
* 用於分表分頁查詢
* @param map
* @return
*/
List<TbLogging> pageListTbLoggingByObj(Map<String,Object> map);
int pageListTbLoggingByObjCount(Map<String,Object> map);
<select id="pageListTbLoggingByObj" parameterType="map" resultMap="BaseResultMap2">
SELECT <include refid="Base_Column_List" />
FROM tb_logging
<where>
is_delete = 0
<if test ='null != create_time and create_time != "" '>
AND date_format(create_time,'%Y-%m-%d') = #{create_time}
</if>
<if test ='null != maxId and maxId != "" '>
AND id > #{maxId}
</if>
<if test ='null != minId and minId != ""'>
AND id < #{minId}
</if>
</where>
<if test ='null != maxId and maxId != "" '>
order by id asc
</if>
<if test ='null != minId and minId != ""'>
order by id desc
</if>
<if test ='minId == "" and maxId == ""'>
order by id desc
</if>
LIMIT #{pageSize}
</select>
<select id="pageListTbLoggingByObjCount" parameterType="map" resultType="int">
SELECT count(1)
FROM tb_logging
<where>
is_delete = 0
<if test ='null != create_time and create_time != "" '>
AND date_format(create_time,'%Y-%m-%d') = #{create_time}
</if>
</where>
</select>
service
package web.service.logging;
import com.web.common.util.PropertyValueChangeUtil;
import com.web.common.util.web.BeanRefUtil;
import com.web.entity.TbLogging;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import web.dao.hsdao.TbLoggingMapper;
import web.dao.hsdao.TbLoggingSelectMapper;
import web.util.ResultUtils;
import java.util.*;
/**
* 項目名稱:
* 類 名 稱:TbLoggingService
* 類 描 述:TODO
* 創建時間:2020/6/12 17:25
* 創 建 人:heng
*/
@Service
public class TbLoggingService {
@Autowired
private TbLoggingMapper tbLoggingMapper;
@Autowired
private TbLoggingSelectMapper tbLoggingSelectMapper;
public ResultUtils findLoging(Map<String,Object> map){
Integer pageSize = Integer.valueOf( map.get("pageSize").toString());
List<TbLogging> tbLoggings = tbLoggingMapper.pageListTbLoggingByObj(map);
List<TbLogging> tbLoggingSelects = tbLoggingSelectMapper.pageListTbLoggingSelectByObj(map);
int count1 = tbLoggingMapper.pageListTbLoggingByObjCount(map);
int count2 = tbLoggingSelectMapper.pageListTbLoggingSelectByObjCount(map);
tbLoggings.addAll(tbLoggingSelects);
List<TbLogging> list = new ArrayList<>();
if (map.get("maxId") != null){
Collections.sort(tbLoggings, new Comparator<TbLogging>() {
@Override
public int compare(TbLogging o1, TbLogging o2) {
if (o1.getId()> o2.getId()){
return 1;
}
if(o1.getId()< o2.getId()){
return -1;
}
return 0;
}
});
if (tbLoggings.size() >= pageSize){
list = tbLoggings.subList(0,pageSize);
}else {
list = tbLoggings;
}
Collections.sort(list, new Comparator<TbLogging>() {
@Override
public int compare(TbLogging o1, TbLogging o2) {
if (o1.getId()< o2.getId()){
return 1;
}
if(o1.getId()> o2.getId()){
return -1;
}
return 0;
}
});
}else{
Collections.sort(tbLoggings, new Comparator<TbLogging>() {
@Override
public int compare(TbLogging o1, TbLogging o2) {
if (o1.getId()< o2.getId()){
return 1;
}
if(o1.getId()> o2.getId()){
return -1;
}
return 0;
}
});
if (tbLoggings.size() >= pageSize){
list = tbLoggings.subList(0,pageSize);
}else {
list = tbLoggings;
}
}
List<Map> mapRows = new ArrayList<Map>();
for (TbLogging d : list) {
BeanRefUtil beanRefUtil = new BeanRefUtil();
Map map1 = beanRefUtil.transBean2Map(d);
// 2.自定義按鈕設置在此處
map1.put("maxId",list.get(0).getId());
map1.put("minId",list.get(list.size()-1).getId());
//下面的方法是將對象中的枚舉值改爲枚舉描述。如stat爲0時表示無效。則將map中的stat的值從0改爲0-無效,方便前端顯示,但是該方法需要完善Dto的PropertyEnum方法
PropertyValueChangeUtil.dateValue2Desc(map1 );
mapRows.add(map1);
}
return new ResultUtils(mapRows,count1+count2);
}
}
controller 這裏的maxId和minId是存前端傳來的第一次查詢爲空會查詢全部的
然後排序各取10條進行合併分頁 第一次查詢後會把合併後的最大id和最小id存到前端等第二次點上一頁或者下一頁的時候傳入
點下一頁的時候傳入 minId 點上一頁的時候傳入maxId因爲是desc排序原因 只能傳入一個 點上一頁或者下一頁傳入
package com.web.controller;
import com.web.common.controller.BaseController;
import com.web.common.exception.BusinessException;
import com.web.common.util.ConstantValue;
import com.web.common.util.web.PagingObject;
import com.web.common.util.web.PangingUtils;
import org.apache.commons.collections.map.HashedMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import web.service.logging.TbLoggingService;
import web.util.ResultUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
/**
* 項目名稱:
* 類 名 稱:TbLoggingController
* 類 描 述:TODO
* 創建時間:2020/6/12 17:15
* 創 建 人:heng
*/
@Controller
@RequestMapping("logging")
public class TbLoggingController {
@Autowired
TbLoggingService tbLoggingService;
/***
* 查詢實體TbLoggingDto的分頁列表
* @return
* @throws Exception
*/
@ResponseBody
@RequestMapping(value="/search_TbLogging.do")
public ResultUtils getListData(HttpServletRequest request , HttpServletResponse response)throws Exception {
try {
PagingObject init_pg = PangingUtils.getPagingObjectFormRequest(request);
Map<String,Object> map = new HashedMap();
map.put("create_time",request.getParameter("create_time"));
map.put("maxId",request.getParameter("maxId"));
map.put("minId",request.getParameter("minId"));
map.put("pageSize",10);
return tbLoggingService.findLoging(map);
} catch (Exception ex) {
logger.error("操作錯誤",ex);
ex.printStackTrace();
throw new BusinessException(ConstantValue.SYSTEM_ERROR_CODE,ConstantValue.SYSTEM_EROR_MESSAGE);
}
}
}