🚀 注重版权,转载请注明原作者和原文链接
🌐 Open开袁 的官方网站已全面升级!探索更多精彩内容,尽在:https://open-yuan.com。
在这里,你将发现丰富的编程资源、深度的技术文章,以及开源项目的地址,一起加入我们的技术交流社区吧!
📱 想要随时随地获取最新动态?微信搜索公众号“全栈小袁”,一手掌握最新项目动态和技术分享。让我们一起,开启技术的无限可能!🌈


CREATE TABLE `access_log` ( `access_log_id` bigint NOT NULL AUTO_INCREMENT, `access_time` datetime NOT NULL COMMENT '访问时间', `access_ip` varchar(30) NOT NULL COMMENT '访问IP', `api_group` varchar(50) NOT NULL DEFAULT '默认' COMMENT '接口分组', `req_url` varchar(100) NOT NULL COMMENT '请求URL', `req_method` varchar(10) NOT NULL COMMENT '请求方式', `os` varchar(100) NULL DEFAULT NULL COMMENT '操作系统', `browser` varchar(50) NULL DEFAULT NULL COMMENT '浏览器', `lsp` varchar(15) NULL DEFAULT NULL COMMENT '运营商', `country` varchar(15) NULL DEFAULT NULL COMMENT '国家', `province` varchar(15) NULL DEFAULT NULL COMMENT '省', `city` varchar(15) NULL DEFAULT NULL COMMENT '城市', PRIMARY KEY (`access_log_id`) ) COMMENT='访问日志表';
@Data
@TableName(value = "access_log")
public class AccessLog implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 访问日志ID
*/
@TableId(type = IdType.AUTO)
private Long accessLogId;
/**
* 访问时间
*/
private Date accessTime;
/**
* 访问IP
*/
private String accessIp;
/**
* 接口分组
*/
private String apiGroup;
/**
* 请求URL
*/
private String reqUrl;
/**
* 请求方式
*/
private String reqMethod;
/**
* 操作系统
*/
private String os;
/**
* 浏览器
*/
private String browser;
/**
* 运营商
*/
private String lsp;
/**
* 国家
*/
private String country;
/**
* 省
*/
private String province;
/**
* 城市
*/
private String city;
}
@Repository public interface AccessLogMapper extends BaseMapper{ }
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
/**
* 给接口分组
*/
String apiGroup() default "默认";
}
案例:
@Log(apiGroup = "文章模块")
@RestController
@RequestMapping("/article")
public class ArticleController {
......
}
ps:https://api.vvhan.com/api/getIpInfo?ip=[你的IP],这个网址是一个免费获取国家、省、市、运营商的地址
当然这种对IP地址的解析应该是放在定时任务中,每天晚上定时解析日志IP,如果解析IP的API挂了,接口会受到影响,我这里只是为了方便写在这里
@Slf4j
@Component
public class AccessLogInterceptor implements HandlerInterceptor {
@Autowired
private AccessLogMapper accessLogMapper;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (!(handler instanceof HandlerMethod)) {
return true;
}
try {
// 获取客户端真是IP地址,这种网上很多现成代码
String accessIp = NetUtil.getRemoteHost(request);
// 获取User-Agent
String requestUserAgent = request.getHeader("User-Agent");
// 获取浏览器用户标识
UserAgent userAgent = UserAgentUtil.parse(requestUserAgent);
HandlerMethod handlerMethod = (HandlerMethod) handler;
Log logAnnotation = handlerMethod.getMethod().getDeclaringClass().getAnnotation(Log.class);
AccessLog accessLog = new AccessLog();
accessLog.setAccessIp(accessIp);
accessLog.setAccessTime(new Date());
if (logAnnotation != null) {
accessLog.setApiGroup(logAnnotation.apiGroup());
}
accessLog.setReqUrl(request.getRequestURI());
accessLog.setReqMethod(request.getMethod());
accessLog.setOs(userAgent.getOs().getName());
accessLog.setBrowser(userAgent.getBrowser().getName());
// 解析IP
try {
String ipParseStr = HttpUtil.get("https://api.vvhan.com/api/getIpInfo?ip=" + accessIp);
JSONObject ipParseJson = JSONUtil.parseObj(ipParseStr);
if (ipParseJson.getBool("success")) {
JSONObject infoJson = ipParseJson.getJSONObject("info");
accessLog.setLsp(infoJson.getStr("lsp"));
accessLog.setCountry(infoJson.getStr("country"));
accessLog.setProvince(infoJson.getStr("prov"));
accessLog.setCity(infoJson.getStr("city"));
}
} catch (Exception e) {
accessLog.setLsp("未知");
accessLog.setCountry("未知");
accessLog.setProvince("未知");
accessLog.setCity("未知");
}
accessLogMapper.insert(accessLog);
} catch (Exception e) {
log.error("", e);
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
@Configuration
public class WebMVCConfig implements WebMvcConfigurer {
@Autowired
private AccessLogInterceptor accessLogInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册全局日志拦截器
registry.addInterceptor(accessLogInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/static/**")
.excludePathPatterns("/error");
// 其他拦截器......
}
}
到这里就完成一个简单的全局日志拦截器了,随便发几个请求测试一下,成功记录入库!

@Data
public class AccessBrowserGroupVo {
private String browser;
private Integer count;
}
@Data
public class AccessLspGroupVo {
private String lsp;
private Integer count;
}
@Data
public class AccessProvinceGroupVo {
private String province;
private Integer count;
}
@Data
public class AccessTimeGroupVo {
private String accessTime;
private Integer count;
}
/** * FileName: R * Author: 小袁 * Date: 2022/3/12 12:23 * Description: 统一结果返回的类 */ @Data public class R{ private Boolean success; private Integer code; private String message; private T data; // 成功静态方法 public static R success() { R r = new R<>(); r.setSuccess(true); r.setCode(HttpStatusEnum.SUCCESS.getCode()); r.setMessage(HttpStatusEnum.SUCCESS.getName()); return r; } public static R success(String message) { R r = new R<>(); r.setSuccess(true); r.setCode(HttpStatusEnum.SUCCESS.getCode()); r.message(message); return r; } public static R success(T object) { R r = new R<>(); r.setData(object); r.setSuccess(true); r.setCode(HttpStatusEnum.SUCCESS.getCode()); r.setMessage(HttpStatusEnum.SUCCESS.getName()); return r; } public static R success(String msg, T object) { R r = new R<>(); r.setData(object); r.setCode(HttpStatusEnum.SUCCESS.getCode()); r.setMessage(msg); return r; } // 失败静态方法 public static R fail() { R r = new R<>(); r.setSuccess(false); r.setCode(HttpStatusEnum.FAIL.getCode()); r.setMessage(HttpStatusEnum.FAIL.getName()); return r; } public static R fail(String msg) { R r = new R<>(); r.setSuccess(false); r.setCode(HttpStatusEnum.FAIL.getCode()); r.setMessage(msg); return r; } public static R fail(HttpStatusEnum httpStatusEnum) { R r = new R<>(); r.setSuccess(false); r.setCode(httpStatusEnum.getCode()); r.setMessage(httpStatusEnum.getName()); return r; } public R message(String message){ this.setMessage(message); return this; } public R code(Integer code){ this.setCode(code); return this; } public R data(T data){ this.setData(data); return this; } }
/**
* FileName: Code
* Author: 小袁
* Date: 2022/5/1 23:29
* Description: 客户端响应状态码
*/
public enum HttpStatusEnum implements BaseCodeEnum {
SUCCESS(200, "成功"),
FAIL(20001, "失败"),
INTERNAL_SERVER_ERROR(500, "服务器异常"),
private final Integer code;
private final String name;
HttpStatusEnum(int code, String msg) {
this.code = code;
this.name = msg;
}
@Override
public Integer getCode() {
return this.code;
}
@Override
public String getName() {
return this.name;
}
}
@Repository public interface AccessLogMapper extends BaseMapper{ List countLspGroupAccess(); List countBrowserGroupAccess(); List countProvinceGroupAccess(); List countTimeGroupAccess(); }
public interface AccessLogService extends IService{ List countLspGroupAccess(); List countBrowserGroupAccess(); List countProvinceGroupAccess(); List countTimeGroupAccess(); }
@Slf4j @Service public class AccessLogServiceImpl extends ServiceImplimplements AccessLogService { @Override public List countLspGroupAccess() { return this.baseMapper.countLspGroupAccess(); } @Override public List countBrowserGroupAccess() { return this.baseMapper.countBrowserGroupAccess(); } @Override public List countProvinceGroupAccess() { return this.baseMapper.countProvinceGroupAccess(); } @Override public List countTimeGroupAccess() { return this.baseMapper.countTimeGroupAccess(); } }
@RestController
@RequestMapping("/stat/access")
public class AccessStatController {
@Autowired
private AccessLogService accessLogService;
/**
* 查询15天内的访问次数情况-折线图
*/
@GetMapping("/query_line_by_day")
public R> queryAccessLogByTimeGroup() {
return R.success(accessLogService.countTimeGroupAccess());
}
/**
* 查询省份访问占比-柱形图
*/
@GetMapping("/query_col_by_province")
public R> queryAccessLogByProvinceGroup() {
return R.success(accessLogService.countProvinceGroupAccess());
}
/**
* 查询运营商访问占比-饼图
*/
@GetMapping("/query_pie_by_lsp")
public R> queryAccessLogByLspGroup() {
return R.success(accessLogService.countLspGroupAccess());
}
/**
* 查询浏览器访问占比-饼图
*/
@GetMapping("/query_pie_by_browser")
public R> queryAccessLogByBrowserGroup() {
return R.success(accessLogService.countBrowserGroupAccess());
}
}
npm install axios npm install echarts
import axios from 'axios'
import { Message, MessageBox,} from 'element-ui'
import store from '../store'
import { getToken } from '@/utils/auth'
import router from '@/router'
// 创建axios实例
const service = axios.create({
baseURL: process.env.BASE_API, // api 的 base_url
// timeout: 5000 // 请求超时时间
})
// request拦截器
service.interceptors.request.use(
config => {
if (store.getters.token) {
config.headers['token'] = getToken()
}
return config
},
error => {
// Do something with request error
console.log(error) // for debug
Promise.reject(error)
}
)
// response 拦截器
service.interceptors.response.use(
response => {
/**
* code为非200是抛错 可结合自己业务进行修改
*/
const res = response.data
const url = response.config.url
if (res.code !== 200) {
if (url.indexOf("/login") < 0 && res.code === 40005) {
store.dispatch('FedLogOut').then(() => {
router.push(`/login`)
})
Message({
message: res.message,
type: 'warning',
duration: 2 * 1000,
})
return Promise.resolve(res)
}else if (res.code >= 40000) {
Message({
message: res.message,
type: 'error',
duration: 3 * 1000
})
return Promise.resolve(res)
}else {
Message({
message: res.message,
type: 'error',
duration: 5 * 1000
})
return Promise.reject(new Error(res.message || 'Error'))
}
} else {
return res
}
},
error => {
Message({
message: error.message,
type: 'error',
duration: 5 * 1000
})
return Promise.reject(error)
}
)
export default service
import request from "../../utils/request";
export default {
getAccessStatByTime() {
return request({
url: '/stat/access/query_line_by_day',
method: 'get'
})
},
getAccessStatByProvince() {
return request({
url: '/stat/access/query_col_by_province',
method: 'get'
})
},
getAccessStatByLsp() {
return request({
url: '/stat/access/query_pie_by_lsp',
method: 'get'
})
},
getAccessStatByBrowser() {
return request({
url: '/stat/access/query_pie_by_browser',
method: 'get'
})
},
}
直接引入
到这里整篇文章就结束了,我们重新捋一下整个流程
🚀 注重版权,转载请注明原作者和原文链接
🌐 Open开袁 的官方网站已全面升级!探索更多精彩内容,尽在:https://open-yuan.com。
在这里,你将发现丰富的编程资源、深度的技术文章,以及开源项目的地址,一起加入我们的技术交流社区吧!
📱 想要随时随地获取最新动态?微信搜索公众号“全栈小袁”,一手掌握最新项目动态和技术分享。让我们一起,开启技术的无限可能!🌈