From bce338ee1982a96070bcce57f75156ffc920b642 Mon Sep 17 00:00:00 2001
From: chen-xin-zhi <3588068430@qq.com>
Date: Sat, 3 May 2025 10:20:50 +0800
Subject: [PATCH] =?UTF-8?q?=E5=8F=82=E6=95=B0=E6=A0=A1=E9=AA=8C?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
pom.xml | 7 +
.../promotion/annotation/MyLog.java | 20 ++
.../promotion/aop/OperLogAspect.java | 280 ++++++++++++++++++
.../promotion/aop/PermissionCheck.java | 2 +
.../exception/GlobalExceptionHandler.java | 19 +-
.../promotion/mapper/SysOperLogMapper.java | 18 ++
.../promotion/model/entity/SysOperLog.java | 96 ++++++
.../service/log/SysOperLogService.java | 13 +
.../log/impl/SysOperLogServiceImpl.java | 22 ++
.../resources/mapper/SysOperLogMapper.xml | 32 ++
.../com/greenorange/promotion/Client.java | 138 +++++++++
.../com/greenorange/promotion/IpDemo.java | 48 +++
12 files changed, 688 insertions(+), 7 deletions(-)
create mode 100644 src/main/java/com/greenorange/promotion/annotation/MyLog.java
create mode 100644 src/main/java/com/greenorange/promotion/aop/OperLogAspect.java
create mode 100644 src/main/java/com/greenorange/promotion/mapper/SysOperLogMapper.java
create mode 100644 src/main/java/com/greenorange/promotion/model/entity/SysOperLog.java
create mode 100644 src/main/java/com/greenorange/promotion/service/log/SysOperLogService.java
create mode 100644 src/main/java/com/greenorange/promotion/service/log/impl/SysOperLogServiceImpl.java
create mode 100644 src/main/resources/mapper/SysOperLogMapper.xml
create mode 100644 src/test/java/com/greenorange/promotion/Client.java
create mode 100644 src/test/java/com/greenorange/promotion/IpDemo.java
diff --git a/pom.xml b/pom.xml
index 2ecd2c2..0af57de 100644
--- a/pom.xml
+++ b/pom.xml
@@ -187,6 +187,13 @@
3.14.0
+
+
+ com.alibaba
+ fastjson
+ 1.2.62
+
+
diff --git a/src/main/java/com/greenorange/promotion/annotation/MyLog.java b/src/main/java/com/greenorange/promotion/annotation/MyLog.java
new file mode 100644
index 0000000..0a28fc6
--- /dev/null
+++ b/src/main/java/com/greenorange/promotion/annotation/MyLog.java
@@ -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 "";
+}
\ No newline at end of file
diff --git a/src/main/java/com/greenorange/promotion/aop/OperLogAspect.java b/src/main/java/com/greenorange/promotion/aop/OperLogAspect.java
new file mode 100644
index 0000000..af6f8a6
--- /dev/null
+++ b/src/main/java/com/greenorange/promotion/aop/OperLogAspect.java
@@ -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 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 converMap(Map paramMap) {
+ Map 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 "未知";
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/greenorange/promotion/aop/PermissionCheck.java b/src/main/java/com/greenorange/promotion/aop/PermissionCheck.java
index 66ff732..3675e3e 100644
--- a/src/main/java/com/greenorange/promotion/aop/PermissionCheck.java
+++ b/src/main/java/com/greenorange/promotion/aop/PermissionCheck.java
@@ -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");
diff --git a/src/main/java/com/greenorange/promotion/exception/GlobalExceptionHandler.java b/src/main/java/com/greenorange/promotion/exception/GlobalExceptionHandler.java
index 051d6c0..a5c1491 100644
--- a/src/main/java/com/greenorange/promotion/exception/GlobalExceptionHandler.java
+++ b/src/main/java/com/greenorange/promotion/exception/GlobalExceptionHandler.java
@@ -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());
+ }
+
+
}
diff --git a/src/main/java/com/greenorange/promotion/mapper/SysOperLogMapper.java b/src/main/java/com/greenorange/promotion/mapper/SysOperLogMapper.java
new file mode 100644
index 0000000..14e8402
--- /dev/null
+++ b/src/main/java/com/greenorange/promotion/mapper/SysOperLogMapper.java
@@ -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 {
+
+}
+
+
+
+
diff --git a/src/main/java/com/greenorange/promotion/model/entity/SysOperLog.java b/src/main/java/com/greenorange/promotion/model/entity/SysOperLog.java
new file mode 100644
index 0000000..b7657a6
--- /dev/null
+++ b/src/main/java/com/greenorange/promotion/model/entity/SysOperLog.java
@@ -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;
+}
\ No newline at end of file
diff --git a/src/main/java/com/greenorange/promotion/service/log/SysOperLogService.java b/src/main/java/com/greenorange/promotion/service/log/SysOperLogService.java
new file mode 100644
index 0000000..898aa74
--- /dev/null
+++ b/src/main/java/com/greenorange/promotion/service/log/SysOperLogService.java
@@ -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 {
+
+}
diff --git a/src/main/java/com/greenorange/promotion/service/log/impl/SysOperLogServiceImpl.java b/src/main/java/com/greenorange/promotion/service/log/impl/SysOperLogServiceImpl.java
new file mode 100644
index 0000000..eab9c4a
--- /dev/null
+++ b/src/main/java/com/greenorange/promotion/service/log/impl/SysOperLogServiceImpl.java
@@ -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
+ implements SysOperLogService{
+
+}
+
+
+
+
diff --git a/src/main/resources/mapper/SysOperLogMapper.xml b/src/main/resources/mapper/SysOperLogMapper.xml
new file mode 100644
index 0000000..1fe1c91
--- /dev/null
+++ b/src/main/resources/mapper/SysOperLogMapper.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ id,title,content,
+ method,requestMethod,operName,
+ requestUrl,ip,ipLocation,
+ requestParam,responseResult,status,
+ errorMsg,operTime,takeTime
+
+
diff --git a/src/test/java/com/greenorange/promotion/Client.java b/src/test/java/com/greenorange/promotion/Client.java
new file mode 100644
index 0000000..057a4c6
--- /dev/null
+++ b/src/test/java/com/greenorange/promotion/Client.java
@@ -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 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);
+ }
+}
+
diff --git a/src/test/java/com/greenorange/promotion/IpDemo.java b/src/test/java/com/greenorange/promotion/IpDemo.java
new file mode 100644
index 0000000..1939e8c
--- /dev/null
+++ b/src/test/java/com/greenorange/promotion/IpDemo.java
@@ -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 "未知";
+ }
+
+
+}