https://www.cnblogs.com/zjdxr-up/p/10573936.html
本文為博主原創,未經允許不得轉載:
在項目開發已經完成多半的情況下,需要開發進行操作日志功能的開發,由於操作的重要性,需要記錄下操作前的參數和請求時的參數,
在網上找了很多,沒找到可行的方法.由於操作日志用注解方式的AOP記錄操作日志比較便捷,所以想到了在注解中定義操作前查詢數據
詳情的bean,查詢方法及參數,參數類型,在aop進行方法執行前,對指定的bean,方法,參數進行調用,獲得修改前的參數,並進行保存.
此處需要注意:
1.在前面中調用指定bean的方法時,不可用反射進行調用,反射不能加載spring容器,無法獲取指定的spring bean,下面方法中封裝的獲取spring bean的
工具類也需要配置為bean,而且被spring加載,才可以;
2.@Aspect注解的類一定要配置成bean,而且被spring加載,才可以,即同時配置@Component和@Aspect,或在spring的配置文件進行bean的配置
3.如果配置了bean,要檢索component-scan掃描范圍是否包括Aspect類;
一.定義切面執行的注解(該注解可根據自己實現的內容進行自定義)
1 import java.lang.annotation.Documented;
2 import java.lang.annotation.Retention;
3 import java.lang.annotation.Target;
4
5 import java.lang.annotation.ElementType;
6 import java.lang.annotation.RetentionPolicy;
7
8 @Target({ElementType.PARAMETER, ElementType.METHOD})
9 @Retention(RetentionPolicy.RUNTIME)
10 @Documented
11 public @interface SystemControllerLog {
12
13 /**查詢模塊*/
14 String module() default "";
15
16 /**查詢模塊名稱*/
17 String methods() default "";
18
19 /**查詢的bean名稱*/
20 String serviceClass() default "";
21
22 /**查詢單個詳情的bean的方法*/
23 String queryMethod() default "";
24
25 /**查詢詳情的參數類型*/
26 String parameterType() default "";
27
28 /**從頁面參數中解析出要查詢的id,
29 * 如域名修改中要從參數中獲取customerDomainId的值進行查詢
30 */
31 String parameterKey() default "";
32
33 /**是否為批量類型操作*/
34 boolean paramIsArray() default false;
35
36 }
二.切面執行的方法
1
2 import java.lang.reflect.Method;
3 import java.text.SimpleDateFormat;
4 import java.util.Date;
5
6 import javax.servlet.http.HttpServletRequest;
7 import javax.servlet.http.HttpSession;
8
9 import org.apache.commons.lang3.StringUtils;
10 import org.aspectj.lang.ProceedingJoinPoint;
11 import org.aspectj.lang.Signature;
12 import org.aspectj.lang.annotation.Around;
13 import org.aspectj.lang.annotation.Aspect;
14 import org.aspectj.lang.annotation.Pointcut;
15 import org.aspectj.lang.reflect.MethodSignature;
16 import org.slf4j.Logger;
17 import org.slf4j.LoggerFactory;
18 import org.springframework.beans.factory.annotation.Autowired;
19 import org.springframework.stereotype.Component;
20 import org.springframework.util.ReflectionUtils;
21 import org.springframework.web.context.request.RequestContextHolder;
22 import org.springframework.web.context.request.ServletRequestAttributes;
23
24 import com.alibaba.fastjson.JSON;
25 import com.alibaba.fastjson.JSONArray;
26 import com.alibaba.fastjson.JSONObject;
27 import com.suning.fucdn.common.RequestResult;
28 import com.suning.fucdn.common.enums.FucdnStrConstant;
29 import com.suning.fucdn.entity.log.SystemControllerLogInfo;
30 import com.suning.fucdn.impl.service.log.LogServiceImpl;
31 import com.suning.fucdn.vo.AdminUserVO;
32
33 /**
34 *
35 * 〈一句話功能簡述:操作日志切面記錄操作〉<br>
36 * 〈功能詳細描述〉
37 *
38 * @author xiang
39 * @see [相關類/方法](可選)
40 * @since [產品/模塊版本] (可選)
41 */
42 @Component
43 @Aspect
44 public class ControllerLogAopAspect {
45
46 private static final Logger LOGGER = LoggerFactory.getLogger(ControllerLogAopAspect.class);
47
48 //注入service,用來將日志信息保存在數據庫
49 @Autowired
50 private LogServiceImpl logservice;
51
52 //配置接入點,如果不知道怎么配置,可以百度一下規則
//指定controller的類進行切面 @Pointcut("execution(* com.controller..CustomerController.*(..))||execution(* com.controller.ManageController.*(..))")
53 @Pointcut("execution(* com.controller..*.*(..))")
54 private void controllerAspect(){
55 System.out.println("point cut start");
56 }//定義一個切入點
57
58 @SuppressWarnings({ "rawtypes", "unused" })
59 @Around("controllerAspect()")
60 public Object around(ProceedingJoinPoint pjp) throws Throwable {
61 //常見日志實體對象
62 SystemControllerLogInfo log = new SystemControllerLogInfo();
63 //獲取登錄用戶賬戶
64 HttpServletRequest httpRequest = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
65
66 //方法通知前獲取時間,為什么要記錄這個時間呢?當然是用來計算模塊執行時間的
67 //獲取系統時間
68 String time = new SimpleDateFormat(FucdnStrConstant.YEAR_MONTH_DAY_HOUR_MINUTE_SECOND.getConstant()).format(new Date());
69 log.setStartTime(time);
70
71 //獲取系統ip,這里用的是我自己的工具類,可自行網上查詢獲取ip方法
72 //String ip = GetLocalIp.localIp();
73 //log.setIP(ip);
74
75 // 攔截的實體類,就是當前正在執行的controller
76 Object target = pjp.getTarget();
77 // 攔截的方法名稱。當前正在執行的方法
78 String methodName = pjp.getSignature().getName();
79 // 攔截的方法參數
80 Object[] args = pjp.getArgs();
81 //String params = Arrays.toString(pjp.getArgs());
82 JSONArray operateParamArray = new JSONArray();
83 for (int i = 0; i < args.length; i++) {
84 Object paramsObj = args[i];
85 //通過該方法可查詢對應的object屬於什么類型:String type = paramsObj.getClass().getName();
86 if(paramsObj instanceof String || paramsObj instanceof JSONObject){
87 String str = (String) paramsObj;
88 //將其轉為jsonobject
89 JSONObject dataJson = JSONObject.parseObject(str);
90 if(dataJson == null || dataJson.isEmpty() || "null".equals(dataJson)){
91 break;
92 }else{
93 operateParamArray.add(dataJson);
94 }
95 }else if(paramsObj instanceof Map){
96 //get請求,以map類型傳參
97 //1.將object的map類型轉為jsonobject類型
98 Map<String, Object> map = (Map<String, Object>) paramsObj;
99 JSONObject json =new JSONObject(map);
100 operateParamArray.add(json);
101 }
102 }
103 //設置請求參數
104 log.setOperateParams(operateParamArray.toJSONString());
105 // 攔截的放參數類型
106 Signature sig = pjp.getSignature();
107 MethodSignature msig = null;
108 if (!(sig instanceof MethodSignature)) {
109 throw new IllegalArgumentException("該注解只能用於方法");
110 }
111 msig = (MethodSignature) sig;
112
113 Class[] parameterTypes = msig.getMethod().getParameterTypes();
114 Object object = null;
115 // 獲得被攔截的方法
116 Method method = null;
117 try {
118 method = target.getClass().getMethod(methodName, parameterTypes);
119 } catch (NoSuchMethodException e1) {
120 LOGGER.error("ControllerLogAopAspect around error",e1);
121 } catch (SecurityException e1) {
122 LOGGER.error("ControllerLogAopAspect around error",e1);
123 }
124 if (null != method) {
125 // 判斷是否包含自定義的注解,說明一下這里的SystemLog就是我自己自定義的注解
126 if (method.isAnnotationPresent(SystemControllerLog.class)) {
127
128 //此處需要對用戶進行區分:1為admin user 2為customer user
129 // get session
130 HttpSession httpSession = httpRequest.getSession(true);
131 // 從session獲取登錄用戶
132 AdminUserVO adminUserVO = (AdminUserVO) httpSession
133 .getAttribute(FucdnStrConstant.SESSION_KEY_ADMIN.getConstant());
134 long adminUserId = adminUserVO.getAdminUserId();
135 log.setUserId(String.valueOf(adminUserId));
136
137 SystemControllerLog systemlog = method.getAnnotation(SystemControllerLog.class);
138
139 log.setModule(systemlog.module());
140 log.setMethod(systemlog.methods());
141 //請求查詢操作前數據的spring bean
142 String serviceClass = systemlog.serviceClass();
143 //請求查詢數據的方法
144 String queryMethod = systemlog.queryMethod();
145 //判斷是否需要進行操作前的對象參數查詢
146 if(StringUtils.isNotBlank(systemlog.parameterKey())
147 &&StringUtils.isNotBlank(systemlog.parameterType())
148 &&StringUtils.isNotBlank(systemlog.queryMethod())
149 &&StringUtils.isNotBlank(systemlog.serviceClass())){
150 boolean isArrayResult = systemlog.paramIsArray();
151 //參數類型
152 String paramType = systemlog.parameterType();
153 String key = systemlog.parameterKey();
154
155 if(isArrayResult){//批量操作
156 //JSONArray jsonarray = (JSONArray) object.get(key);
157 //從請求的參數中解析出查詢key對應的value值
158 String value = "";
159 JSONArray beforeParamArray = new JSONArray();
160 for (int i = 0; i < operateParamArray.size(); i++) {
161 JSONObject params = operateParamArray.getJSONObject(i);
162 JSONArray paramArray = (JSONArray) params.get(key);
163 if (paramArray != null) {
164 for (int j = 0; j < paramArray.size(); j++) {
165 String paramId = paramArray.getString(j);
166 //在此處判斷spring bean查詢的方法參數類型
167 Object data = getOperateBeforeData(paramType, serviceClass, queryMethod, paramId);
168 JSONObject json = (JSONObject) JSON.toJSON(data);
169 beforeParamArray.add(json);
170 }
171 }
172 }
173 log.setBeforeParams(beforeParamArray.toJSONString());
174
175 }else{//單量操作
176
177 //從請求的參數中解析出查詢key對應的value值
178 String value = "";
179 for (int i = 0; i < operateParamArray.size(); i++) {
180 JSONObject params = operateParamArray.getJSONObject(i);
181 value = params.getString(key);
182 if(StringUtils.isNotBlank(value)){
183 break;
184 }
185 }
186 //在此處獲取操作前的spring bean的查詢方法
187 Object data = getOperateBeforeData(paramType, serviceClass, queryMethod, value);
188 JSONObject beforeParam = (JSONObject) JSON.toJSON(data);
189 log.setBeforeParams(beforeParam.toJSONString());
190 }
191 }
192
193 try {
194 //執行頁面請求模塊方法,並返回
195 object = pjp.proceed();
196 //獲取系統時間
197 String endTime = new SimpleDateFormat(FucdnStrConstant.YEAR_MONTH_DAY_HOUR_MINUTE_SECOND.getConstant()).format(new Date());
198 log.setEndTime(endTime);
199 //將object 轉化為controller封裝返回的實體類:RequestResult
200 RequestResult requestResult = (RequestResult) object;
201 if(requestResult.isResult()){
202 //操作流程成功
203 if(StringUtils.isNotBlank(requestResult.getErrMsg())){
204 log.setResultMsg(requestResult.getErrMsg());
205 }else if(requestResult.getData() instanceof String){
206 log.setResultMsg((String) requestResult.getData());
207 }else{
208 log.setResultMsg("執行成功");
209 }
210 }else{
211 log.setResultMsg("失敗");
212 }
213 //保存進數據庫
214 logservice.saveLog(log);
215 } catch (Throwable e) {
216 String endTime = new SimpleDateFormat(FucdnStrConstant.YEAR_MONTH_DAY_HOUR_MINUTE_SECOND.getConstant()).format(new Date());
217 log.setEndTime(endTime);
218
219 log.setResultMsg(e.getMessage());
220 logservice.saveLog(log);
221 }
222 } else {
223 //沒有包含注解
224 object = pjp.proceed();
225 }
226 } else {
227 //不需要攔截直接執行
228 object = pjp.proceed();
229 }
230 return object;
231 }
232
233 /**
234 *
235 * 功能描述: <br>
236 * 〈功能詳細描述〉
237 *
238 * @param paramType:參數類型
239 * @param serviceClass:bean名稱
240 * @param queryMethod:查詢method
241 * @param value:查詢id的value
242 * @return
243 * @see [相關類/方法](可選)
244 * @since [產品/模塊版本](可選)
245 */
246 public Object getOperateBeforeData(String paramType,String serviceClass,String queryMethod,String value){
247 Object obj = new Object();
248 //在此處解析請求的參數類型,根據id查詢數據,id類型有四種:int,Integer,long,Long
249 if(paramType.equals("int")){
250 int id = Integer.parseInt(value);
251 Method mh = ReflectionUtils.findMethod(SpringContextUtil.getBean(serviceClass).getClass(), queryMethod,Long.class );
252 //用spring bean獲取操作前的參數,此處需要注意:傳入的id類型與bean里面的參數類型需要保持一致
253 obj = ReflectionUtils.invokeMethod(mh, SpringContextUtil.getBean(serviceClass),id);
254
255 }else if(paramType.equals("Integer")){
256 Integer id = Integer.valueOf(value);
257 Method mh = ReflectionUtils.findMethod(SpringContextUtil.getBean(serviceClass).getClass(), queryMethod,Long.class );
258 //用spring bean獲取操作前的參數,此處需要注意:傳入的id類型與bean里面的參數類型需要保持一致
259 obj = ReflectionUtils.invokeMethod(mh, SpringContextUtil.getBean(serviceClass),id);
260
261 }else if(paramType.equals("long")){
262 long id = Long.parseLong(value);
263 Method mh = ReflectionUtils.findMethod(SpringContextUtil.getBean(serviceClass).getClass(), queryMethod,Long.class );
264 //用spring bean獲取操作前的參數,此處需要注意:傳入的id類型與bean里面的參數類型需要保持一致
265 obj = ReflectionUtils.invokeMethod(mh, SpringContextUtil.getBean(serviceClass),id);
266
267 }else if(paramType.equals("Long")){
268 Long id = Long.valueOf(value);
269 Method mh = ReflectionUtils.findMethod(SpringContextUtil.getBean(serviceClass).getClass(), queryMethod,Long.class );
270 //用spring bean獲取操作前的參數,此處需要注意:傳入的id類型與bean里面的參數類型需要保持一致
271 obj = ReflectionUtils.invokeMethod(mh, SpringContextUtil.getBean(serviceClass),id);
272 }
273 return obj;
274 }
275 }
三.獲取spring bean的工具類
1 import org.springframework.beans.BeansException;
2 import org.springframework.context.ApplicationContext;
3 import org.springframework.context.ApplicationContextAware;
4 import org.springframework.stereotype.Component;
5
6
7 /**
8 * 獲取spring容器,以訪問容器中定義的其他bean
9 * xiang
10 * MOSTsView 3.0 2009-11-16
11 */
12 @Component
13 public class SpringContextUtil implements ApplicationContextAware{
14
15 private static ApplicationContext applicationContext;
16
17 /**
18 * 實現ApplicationContextAware接口的回調方法,設置上下文環境
19 */
20 public void setApplicationContext(ApplicationContext applicationContext){
21 SpringContextUtil.applicationContext = applicationContext;
22 }
23
24 public static ApplicationContext getApplicationContext(){
25 return applicationContext;
26 }
27
28 /**
29 * 獲取對象
30 * @return Object 一個以所給名字注冊的bean的實例 (service注解方式,自動生成以首字母小寫的類名為bean name)
31 */
32 public static Object getBean(String name) throws BeansException{
33 return applicationContext.getBean(name);
34 }
35 }
四.操作日志對應的實體類
1 public class SystemControllerLogInfo{
2
3 private long id;
4
5 /**用戶id*/
6 private String userId;
7
8 /**用戶類型*/
9 private int userType;
10
11 /**操作模塊*/
12 private String module;
13
14 /**操作類型*/
15 private String method;
16
17 /**操作前參數*/
18 private String beforeParams;
19
20 /**操作時請求參數*/
21 private String operateParams;
22
23 /**開始時間*/
24 private String startTime;
25
26 /**結束時間*/
27 private String endTime;
28
29 /**操作狀態描述*/
30 private int resultStatus;
31
32 /**操作結果描述*/
33 private String resultMsg;
五.進行注解切面調用
1 @ResponseBody
2 @RequestMapping(value = "/delete", method = { RequestMethod.POST })
3 @SystemControllerLog(module="域名管理",methods="域名刪除",serviceClass="domainConfService",queryMethod="queryDomain",parameterType="Long",parameterKey="customerDomainId")
4 public RequestResult delete(@RequestBody String param) {
5 // 定義請求數據
6 RequestResult result = new RequestResult();
7 // 接收數據
8 CustomerDomain customerDomain = JSONObject.parseObject(param, CustomerDomain.class);
9 // 更新客戶域名
10 try {
11 String data = domainConfService.deleteDomain(customerDomain.getId());
12 // 設置true
13 if (StringUtils.isBlank(data)) {
14 result.setData("刪除成功");
15 } else {
16 result.setData(data);
17 }
18 result.setResult(true);
19 } catch (Exception e) {
20 // 記錄錯誤信息,並返回
21 LOGGER.error("delete failed", e);
22 result.setErrMsg(e.getMessage());
23 }
24 // 返回
25 return result;
26 }
六.數據實例

補充:get請求參數類型解析和記錄
1 JSONArray operateParamArray = new JSONArray();
2 for (int i = 0; i < args.length; i++) {
3 Object paramsObj = args[i];
4 //通過該方法可查詢對應的object屬於什么類型:String type = paramsObj.getClass().getName();
5 if(paramsObj instanceof String || paramsObj instanceof JSONObject){
6 String str = (String) paramsObj;
7 //將其轉為jsonobject
8 JSONObject dataJson = JSONObject.parseObject(str);
9 if(dataJson == null || dataJson.isEmpty() || "null".equals(dataJson)){
10 break;
11 }else{
12 operateParamArray.add(dataJson);
13 }
14 }else if(paramsObj instanceof Map){
15 //get請求,以map類型傳參
16 //1.將object的map類型轉為jsonobject類型
17 Map<String, Object> map = (Map<String, Object>) paramsObj;
18 JSONObject json =new JSONObject(map);
19 operateParamArray.add(json);
20 }
21 }
get請求的controller示例:
@ResponseBody
@RequestMapping(value = "/add", method = { RequestMethod.GET })
@SystemControllerLog(module="域名管理",methods="域名新增")
public RequestResult addDomain(@RequestParam Map<String, String> paramMap) {
標簽:
spring


