参数校验

This commit is contained in:
chen-xin-zhi 2025-05-03 10:20:50 +08:00
parent d84021f3db
commit bce338ee19
12 changed files with 688 additions and 7 deletions

View File

@ -187,6 +187,13 @@
<version>3.14.0</version>
</dependency>
<!--JSON 处理工具-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>

View File

@ -0,0 +1,20 @@
package com.greenorange.promotion.annotation;
import java.lang.annotation.*;
/**
* 自定义注解记录系统操作日志
*/
//Target注解决定 MyLog 注解可以加在哪些成分上如加在类身上或者属性身上或者方法身上等成分
@Target({ ElementType.PARAMETER, ElementType.METHOD })
//Retention注解括号中的"RetentionPolicy.RUNTIME"意思是让 MyLog 这个注解的生命周期一直程序运行时都存在
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLog {
/**
* 模块标题
*/
String title() default "";
/**
* 日志内容
*/
String content() default "";
}

View File

@ -0,0 +1,280 @@
package com.greenorange.promotion.aop;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.greenorange.promotion.annotation.MyLog;
import com.greenorange.promotion.model.entity.SysOperLog;
import com.greenorange.promotion.service.log.SysOperLogService;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* 切面处理类记录操作日志到数据库
*/
@Aspect
@Component
public class OperLogAspect {
@Resource
private SysOperLogService sysOperLogService;
// 为了记录方法的执行时间
ThreadLocal<Long> startTime = new ThreadLocal<>();
@Before("@annotation(myLog)")
public void beforeMethod(JoinPoint joinPoint, MyLog myLog){
startTime.set(System.currentTimeMillis());
}
/**
* 正常返回通知拦截用户操作日志连接点正常执行完成后执行 如果连接点抛出异常则不会执行
* @param joinPoint 切入点
* @param result 返回结果
*/
@AfterReturning(value = "@annotation(myLog)", returning = "result")
public void saveOperLog(JoinPoint joinPoint, MyLog myLog, Object result) {
// 获取RequestAttributes
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
// 从获取RequestAttributes中获取HttpServletRequest的信息
HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
try {
// 从切面织入点处通过反射机制获取织入点处的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 获取切入点所在的方法
Method method = signature.getMethod();
// 获取操作
SysOperLog sysOperLog = new SysOperLog();
sysOperLog.setTitle(myLog.title());//设置模块名称
sysOperLog.setContent(myLog.content());//设置日志内容
// 将入参转换成json
String params = argsArrayToString(joinPoint.getArgs());
// 获取请求的类名
String className = joinPoint.getTarget().getClass().getName();
// 获取请求的方法名
String methodName = method.getName();
methodName = className + "." + methodName + "()";
sysOperLog.setMethod(methodName); //设置请求方法
sysOperLog.setRequestMethod(request.getMethod());//设置请求方式
sysOperLog.setRequestParam(params); // 请求参数
sysOperLog.setResponseResult(JSON.toJSONString(result)); // 返回结果
// 从request中取出账号
String userAccount = (String) request.getAttribute("userAccount");
sysOperLog.setOperName(userAccount); // 获取用户名真实环境中肯定有工具类获取当前登录者的账号或ID的或者从token中解析而来
String ip = getIp(request);
sysOperLog.setIp(ip); // IP地址
String ipLocation = getIpLocation(ip);
sysOperLog.setIpLocation(ipLocation); // IP归属地真是环境中可以调用第三方API根据IP地址查询归属地
sysOperLog.setRequestUrl(request.getRequestURI()); // 请求URI
sysOperLog.setOperTime(new Date()); // 时间
sysOperLog.setStatus(0);//操作状态0正常 1异常
Long takeTime = System.currentTimeMillis() - startTime.get();//记录方法执行耗时时间单位毫秒
sysOperLog.setTakeTime(takeTime);
//插入数据库
sysOperLogService.save(sysOperLog);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 设置操作异常切入点记录异常日志 扫描所有controller包下操作
* 异常返回通知用于拦截异常日志信息 连接点抛出异常后执行
*/
@AfterThrowing(pointcut = "execution(* com.greenorange.promotion.controller..*.*(..))", throwing = "e")
public void saveExceptionLog(JoinPoint joinPoint, Throwable e) {
// 获取RequestAttributes
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
// 从获取RequestAttributes中获取HttpServletRequest的信息
HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
SysOperLog sysOperLog = new SysOperLog();
try {
// 从切面织入点处通过反射机制获取织入点处的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 获取切入点所在的方法
Method method = signature.getMethod();
// 获取请求的类名
String className = joinPoint.getTarget().getClass().getName();
// 获取请求的方法名
String methodName = method.getName();
methodName = className + "." + methodName + "()";
// 获取操作
MyLog myLog = method.getAnnotation(MyLog.class);
if (myLog != null) {
sysOperLog.setTitle(myLog.title());//设置模块名称
sysOperLog.setContent(myLog.content());//设置日志内容
}
// 将入参转换成json
String params = argsArrayToString(joinPoint.getArgs());
sysOperLog.setMethod(methodName); //设置请求方法
sysOperLog.setRequestMethod(request.getMethod());//设置请求方式
sysOperLog.setRequestParam(params); // 请求参数
// 从request中取出账号
String userAccount = (String) request.getAttribute("userAccount");
sysOperLog.setOperName(userAccount); // 获取用户名真实环境中肯定有工具类获取当前登录者的账号或ID的或者从token中解析而来
String ip = getIp(request);
sysOperLog.setIp(ip); // IP地址
String ipLocation = getIpLocation(ip);
sysOperLog.setIpLocation(ipLocation); // IP归属地真是环境中可以调用第三方API根据IP地址查询归属地
sysOperLog.setRequestUrl(request.getRequestURI()); // 请求URI
sysOperLog.setOperTime(new Date()); // 时间
sysOperLog.setStatus(1);//操作状态0正常 1异常
sysOperLog.setErrorMsg(stackTraceToString(e.getClass().getName(), e.getMessage(), e.getStackTrace()));//记录异常信息
//插入数据库
sysOperLogService.save(sysOperLog);
} catch (Exception e2) {
e2.printStackTrace();
}
}
/**
* 转换异常信息为字符串
*/
public String stackTraceToString(String exceptionName, String exceptionMessage, StackTraceElement[] elements) {
StringBuffer strbuff = new StringBuffer();
for (StackTraceElement stet : elements) {
strbuff.append(stet + "\n");
}
String message = exceptionName + ":" + exceptionMessage + "\n\t" + strbuff.toString();
message = substring(message,0 ,2000);
return message;
}
/**
* 参数拼装
*/
private String argsArrayToString(Object[] paramsArray)
{
String params = "";
if (paramsArray != null && paramsArray.length > 0) {
for (Object o : paramsArray) {
if (o != null) {
try {
Object jsonObj = JSON.toJSON(o);
params += jsonObj.toString() + " ";
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
return params.trim();
}
//字符串截取
public static String substring(String str, int start, int end) {
if (str == null) {
return null;
} else {
if (end < 0) {
end += str.length();
}
if (start < 0) {
start += str.length();
}
if (end > str.length()) {
end = str.length();
}
if (start > end) {
return "";
} else {
if (start < 0) {
start = 0;
}
if (end < 0) {
end = 0;
}
return str.substring(start, end);
}
}
}
/**
* 转换request 请求参数
* @param paramMap request获取的参数数组
*/
public Map<String, String> converMap(Map<String, String[]> paramMap) {
Map<String, String> returnMap = new HashMap<>();
for (String key : paramMap.keySet()) {
returnMap.put(key, paramMap.get(key)[0]);
}
return returnMap;
}
//根据HttpServletRequest获取访问者的IP地址
public String getIp(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;
}
public String getIpLocation(String ip) {
try {
// 发送请求到ip-api服务
String url = "http://ip-api.com/json/" + ip + "?lang=zh-CN"; // 获取中文结果
URL obj = new URL(url);
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
con.setRequestMethod("GET");
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
// 解析JSON返回结果
JSONObject jsonResponse = JSONObject.parseObject(response.toString());
String region = jsonResponse.getString("regionName"); //
String city = jsonResponse.getString("city"); // 城市
String country = jsonResponse.getString("country"); // 国家
return city + ", " + region + ", " + country; // 返回位置
} catch (Exception e) {
e.printStackTrace();
}
return "未知";
}
}

View File

@ -66,6 +66,8 @@ public class PermissionCheck {
DecodedJWT decodedJWT = jwtUtils.verify(token);
String userAccount = decodedJWT.getClaim("userAccount").asString();
String userPassword = decodedJWT.getClaim("userPassword").asString();
// 将账号存入request用于记录日志
request.setAttribute("userAccount", userAccount);
// 打印token的过期时间
Date expiresAt = decodedJWT.getExpiresAt();
String formatExpiresAt = DateUtil.format(expiresAt, "yyyy-MM-dd HH:mm:ss");

View File

@ -6,6 +6,8 @@ import com.greenorange.promotion.common.ResultUtils;
import io.swagger.v3.oas.annotations.Hidden;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.util.validation.ValidationError;
import org.springframework.dao.InvalidDataAccessResourceUsageException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindingResult;
@ -16,6 +18,7 @@ import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
@ -60,7 +63,6 @@ public class GlobalExceptionHandler {
}
// 处理业务异常
@ExceptionHandler(BusinessException.class)
public BaseResponse<?> businessExceptionHandler(BusinessException e) {
@ -69,11 +71,14 @@ public class GlobalExceptionHandler {
}
// // 处理运行时异常
// @ExceptionHandler(RuntimeException.class)
// public BaseResponse<?> runtimeExceptionHandler(RuntimeException e) {
// log.error("RuntimeException", e);
// return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "系统错误");
// }
// 处理 SQL 语法错误
@ExceptionHandler(SQLException.class)
public BaseResponse<?> handleSQLException(SQLException ex) {
// 记录详细的 SQL 错误信息
log.error("SQL 错误: " + ex.getMessage());
return ResultUtils.error(ErrorCode.DATABASE_ERROR, "SQL语法错误详细信息" + ex.getMessage());
}
}

View File

@ -0,0 +1,18 @@
package com.greenorange.promotion.mapper;
import com.greenorange.promotion.model.entity.SysOperLog;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @author 35880
* @description 针对表sys_oper_log(操作日志记录)的数据库操作Mapper
* @createDate 2025-05-02 20:24:46
* @Entity com.greenorange.promotion.model.entity.SysOperLog
*/
public interface SysOperLogMapper extends BaseMapper<SysOperLog> {
}

View File

@ -0,0 +1,96 @@
package com.greenorange.promotion.model.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.util.Date;
import lombok.Data;
/**
* 操作日志记录
* @TableName sys_oper_log
*/
@TableName(value ="sys_oper_log")
@Data
public class SysOperLog implements Serializable {
/**
* 日志主键
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 模块标题
*/
private String title;
/**
* 日志内容
*/
private String content;
/**
* 方法名称
*/
private String method;
/**
* 请求方式
*/
private String requestMethod;
/**
* 操作人员
*/
private String operName;
/**
* 请求URL
*/
private String requestUrl;
/**
* 请求IP地址
*/
private String ip;
/**
* IP归属地
*/
private String ipLocation;
/**
* 请求参数
*/
private String requestParam;
/**
* 方法响应参数
*/
private String responseResult;
/**
* 操作状态0正常 1异常
*/
private Integer status;
/**
* 错误消息
*/
private String errorMsg;
/**
* 操作时间
*/
private Date operTime;
/**
* 方法执行耗时单位毫秒
*/
private Long takeTime;
@TableField(exist = false)
private static final long serialVersionUID = 1L;
}

View File

@ -0,0 +1,13 @@
package com.greenorange.promotion.service.log;
import com.greenorange.promotion.model.entity.SysOperLog;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* @author 35880
* @description 针对表sys_oper_log(操作日志记录)的数据库操作Service
* @createDate 2025-05-02 20:24:46
*/
public interface SysOperLogService extends IService<SysOperLog> {
}

View File

@ -0,0 +1,22 @@
package com.greenorange.promotion.service.log.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.greenorange.promotion.model.entity.SysOperLog;
import com.greenorange.promotion.service.log.SysOperLogService;
import com.greenorange.promotion.mapper.SysOperLogMapper;
import org.springframework.stereotype.Service;
/**
* @author 35880
* @description 针对表sys_oper_log(操作日志记录)的数据库操作Service实现
* @createDate 2025-05-02 20:24:46
*/
@Service
public class SysOperLogServiceImpl extends ServiceImpl<SysOperLogMapper, SysOperLog>
implements SysOperLogService{
}

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.greenorange.promotion.mapper.SysOperLogMapper">
<resultMap id="BaseResultMap" type="com.greenorange.promotion.model.entity.SysOperLog">
<id property="id" column="id" jdbcType="BIGINT"/>
<result property="title" column="title" jdbcType="VARCHAR"/>
<result property="content" column="content" jdbcType="VARCHAR"/>
<result property="method" column="method" jdbcType="VARCHAR"/>
<result property="requestMethod" column="requestMethod" jdbcType="VARCHAR"/>
<result property="operName" column="operName" jdbcType="VARCHAR"/>
<result property="requestUrl" column="requestUrl" jdbcType="VARCHAR"/>
<result property="ip" column="ip" jdbcType="VARCHAR"/>
<result property="ipLocation" column="ipLocation" jdbcType="VARCHAR"/>
<result property="requestParam" column="requestParam" jdbcType="VARCHAR"/>
<result property="responseResult" column="responseResult" jdbcType="VARCHAR"/>
<result property="status" column="status" jdbcType="INTEGER"/>
<result property="errorMsg" column="errorMsg" jdbcType="VARCHAR"/>
<result property="operTime" column="operTime" jdbcType="TIMESTAMP"/>
<result property="takeTime" column="takeTime" jdbcType="BIGINT"/>
</resultMap>
<sql id="Base_Column_List">
id,title,content,
method,requestMethod,operName,
requestUrl,ip,ipLocation,
requestParam,responseResult,status,
errorMsg,operTime,takeTime
</sql>
</mapper>

View File

@ -0,0 +1,138 @@
package com.greenorange.promotion;
import java.util.Stack;
// Command 接口
interface Command {
void execute(); // 执行命令
void undo(); // 撤销命令
}
// Receiver 厨师执行具体的菜品制作命令
class Chef {
public void makeBurger() {
System.out.println("制作汉堡...");
}
public void makeFriedRice() {
System.out.println("制作炒饭...");
}
public void makeFriedChicken() {
System.out.println("制作炸鸡...");
}
}
// 具体命令类
class MakeBurgerCommand implements Command {
private Chef chef;
public MakeBurgerCommand(Chef chef) {
this.chef = chef;
}
@Override
public void execute() {
chef.makeBurger(); // 执行命令制作汉堡
}
@Override
public void undo() {
System.out.println("撤销汉堡制作命令...");
}
}
class MakeFriedRiceCommand implements Command {
private Chef chef;
public MakeFriedRiceCommand(Chef chef) {
this.chef = chef;
}
@Override
public void execute() {
chef.makeFriedRice(); // 执行命令制作炒饭
}
@Override
public void undo() {
System.out.println("撤销炒饭制作命令...");
}
}
class MakeFriedChickenCommand implements Command {
private Chef chef;
public MakeFriedChickenCommand(Chef chef) {
this.chef = chef;
}
@Override
public void execute() {
chef.makeFriedChicken(); // 执行命令制作炸鸡
}
@Override
public void undo() {
System.out.println("撤销炸鸡制作命令...");
}
}
// 服务员类接收并执行命令
class Waiter {
private Stack<Command> commandHistory = new Stack<>(); // 用栈来保存历史命令方便撤销
public void takeOrder(Command command) {
command.execute(); // 执行命令
commandHistory.push(command); // 将命令存入历史记录
}
public void undoLastOrder() {
if (!commandHistory.isEmpty()) {
Command lastCommand = commandHistory.pop(); // 获取并移除栈顶命令
lastCommand.undo(); // 执行撤销操作
} else {
System.out.println("没有可撤销的命令!");
}
}
// 宏命令一次性下多个菜品
public void placeMultipleOrders(Command[] commands) {
for (Command command : commands) {
takeOrder(command);
}
}
}
// 客户端模拟点餐过程
public class Client {
public static void main(String[] args) {
// 创建厨师
Chef chef = new Chef();
// 创建具体命令
Command makeBurger = new MakeBurgerCommand(chef);
Command makeFriedRice = new MakeFriedRiceCommand(chef);
Command makeFriedChicken = new MakeFriedChickenCommand(chef);
// 创建服务员
Waiter waiter = new Waiter();
// 用户下单
waiter.takeOrder(makeBurger);
waiter.takeOrder(makeFriedRice);
// 用户撤销上一条订单炒饭
waiter.undoLastOrder();
// 用户一次性下多个菜品宏命令
Command[] orders = {makeBurger, makeFriedChicken};
waiter.placeMultipleOrders(orders);
}
}

View File

@ -0,0 +1,48 @@
package com.greenorange.promotion;
import com.alibaba.fastjson.JSONObject;
import org.junit.jupiter.api.Test;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class IpDemo {
public static void main(String[] args) {
getIpLocation("123.167.57.119");
}
public static String getIpLocation(String ip) {
try {
// 发送请求到ip-api服务
String url = "http://ip-api.com/json/" + ip + "?lang=zh-CN"; // 获取中文结果
URL obj = new URL(url);
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
con.setRequestMethod("GET");
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
// 解析JSON返回结果
JSONObject jsonResponse = JSONObject.parseObject(response.toString());
String country = jsonResponse.getString("country"); // 国家
String region = jsonResponse.getString("regionName"); //
String city = jsonResponse.getString("city"); // 城市
System.out.println(jsonResponse);
System.out.println(country + "-" + region + "" + "-" + city);
return country + "-" + region + "" + "-" + city; // 返回位置
} catch (Exception e) {
e.printStackTrace();
}
return "未知";
}
}