您的位置:首页 > 编程语言 > Java开发

javaWEB SSM AOP+注解保存操作日志

2018-02-12 16:03 513 查看
本篇文章的诞生离不开这篇文章的作者:http://blog.csdn.net/czmchen/article/details/42392985。

前言

操作日志在javaWeb的业务系统中是在是太常见的功能了,主要记录用户再什么时间,什么位置进行了什么操作。如果每新增一个功能都要写一个插入代码的话,是非常不容易维护的。加一个字段就要在每个插入语句上加入这个字段。所以AOP+注解的优势就显现了出来,不仅如此,当我们有了这套代码以后,可以通用在该系统的wap端或者其他的系统中,不必修改太多的代码。针对日志这种实时性不是很高的功能,这里用了异步的方式进行,这样日志系统独立出来,不会影响业务。下面是我整理的代码,欢迎留下宝贵意见。这里再次感谢原作者提供良好的思路。

代码

一、核心类

1.自定义注解 拦截Controller  

/**
*
* 自定义注解 拦截Controller
* @see  [相关类/方法]
* @since  [产品/模块版本]
*/
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SystemControllerLog
{
String description() default "";//描述

String moduleType() default "";//模块代码

String operateValue() default "";//操作类型

boolean firstParamName() default false;
}

2.自定义注解 拦截service 

/**
*
* 自定义注解 拦截service
* @see  [相关类/方法]
* @since  [产品/模块版本]
*/
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SystemServiceLog
{
String description() default "";// 描述

String moduleType() default "";// 模块代码

String operateValue() default "";// 操作类型
}

3.AOP可以拦截到controller的配置

spring.xml中加入下面这句话<aop:aspectj-autoproxy proxy-target-class="true" />

4.切点类

这里用的是返回通知,用来接收成功或失败。/**
*
* AOP记录操作&异常日志-切点类
* @see [相关类/方法]
* @since [产品/模块版本]
*/
@Aspect
@Component
public class SystemLogAspect
{

private static final Logger logger = LoggerFactory.getLogger(SystemLogAspect.class);

// 队列
private static BlockingQueue<Log> queue = new LinkedBlockingQueue<Log>();

// 缓存线程池
private static ExecutorService threadPool = Executors.newFixedThreadPool(3);

// 任务数
private static int taskSize = 6;

// 线程是否已启动
boolean isStartThread = false;

// 用来启动或停止线程
static boolean run = true;

@Autowired
private LogService logService;

@Autowired
private UserService userService;

// Service层切点
@Pointcut("@annotation(com.rzzl.wap.log.annotation.SystemServiceLog)")
public void serviceAspect()
{
}

// Controller层切点
@Pointcut("@annotation(com.rzzl.wap.log.annotation.SystemControllerLog)")
public void controllerAspect()
{
}

public static BlockingQueue<Log> getQueue()
{
return queue;
}

public static void setQueue(BlockingQueue<Log> queue)
{
SystemLogAspect.queue = queue;
}

public static boolean isRun()
{
return run;
}

public static void setRun(boolean run)
{
SystemLogAspect.run = run;
}

/**
*
* 返回通知 用于拦截Controller层记录用户的操作
* @param joinPoint 切点
* @param result 返回值
* @see [类、类#方法、类#成员]
*/
@AfterReturning(value = "controllerAspect()", returning = "result")
public void afterReturn(JoinPoint joinPoint, Object result)
{
// 请求的IP
User user = WebUtils.getSessionValue(LoginContact.SESSION_USER);
String params = "";
WebResult webResult = new WebResult();
webResult.setCode(FlagContact.BACK_SUCCESS);
try
{
if (WebResult.class.isInstance(result))
{
webResult = (WebResult)result;
}

String loginName = "";
InnnerBean innnerBean = getControllerMethodDescription(joinPoint);
Object[] arguments = innnerBean.getArguments();
String remark = innnerBean.getDescription();

Log log = new Log.Builder().type(LogTypes.type.operate)
.moduleType(innnerBean.getModuleType())
.operateCode(joinPoint.getSignature().getName())
.operateValue(innnerBean.getOperateValue())
.remark(remark)
.operateStatus(webResult.getCode().equals(FlagContact.BACK_SUCCESS) ? LogTypes.operateStatus.Y
: LogTypes.operateStatus.N)// 返回值1操作成功,否则失败
.method((joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + "()"))
.param(params)
.loginName(user.getAccountNo())
.fullName(user.getUserName())
.build();
// 放入队列
queue.put(log);
if (!isStartThread)
{
for (int i = 0; i < taskSize; i++)
{
threadPool.execute(new saveLogThread());
}
isStartThread = true;
}
}
catch (Exception e)
{
logger.error("异常信息:{}", e.toString());
}
}

/**
* 异常通知 用于拦截service层记录异常日志
* @param joinPoint
* @param e
* @see [类、类#方法、类#成员]
*/
@AfterThrowing(pointcut = "serviceAspect()", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Throwable e)
{
// 读取session中的用户
User user = WebUtils.getSessionValue(LoginContact.SESSION_USER);
String params = "";

try
{
if (joinPoint.getArgs() != null && joinPoint.getArgs().length > 0)
{
for (int i = 0; i < joinPoint.getArgs().length; i++)
{
params += JSONUtils.valueToString(joinPoint.getArgs()[i].toString()) + ";";
}
}

InnnerBean innnerBean = getServiceMthodDescription(joinPoint);
String loginName = "";

Log log =
new Log.Builder().type(LogTypes.type.exception)
.moduleType(innnerBean.getModuleType())
.operateCode(joinPoint.getSignature().getName())
.operateValue(innnerBean.getOperateValue())
.remark(innnerBean.getDescription())
.operateStatus(LogTypes.operateStatus.N)
.method(
(joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + "()"))
.param(params)
.exceptionDetail(e.toString())
.build();
// 放入队列
queue.put(log);
if (!isStartThread)
{
new Thread(new saveLogThread()).start();
isStartThread = true;
}
}
catch (Exception ex)
{
logger.error("异常信息:{}", ex.toString());
}
finally
{
logger.error("异常方法:{" + joinPoint.getTarget().getClass().getName() + "}异常代码:{"
+ joinPoint.getSignature().getName() + "}异常信息:{" + e.toString() + "}参数:{" + params + "}");
}

}

/**
* 获取注解中对方法的描述信息 用于service层注解
* @param joinPoint 切点
* @return 方法描述
* @throws Exception
* @see [类、类#方法、类#成员]
*/
@SuppressWarnings("rawtypes")
public static InnnerBean getServiceMthodDescription(JoinPoint joinPoint)
throws Exception
{
String targetName = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
Object[] arguments = joinPoint.getArgs();
Class targetClass = Class.forName(targetName);
Method[] methods = targetClass.getMethods();
String moduleType = "";
String operateValue = "";
String description = "";
InnnerBean innnerBean = new InnnerBean(moduleType, operateValue, description);
for (Method method : methods)
{
if (method.getName().equals(methodName))
{
Class[] clazzs = method.getParameterTypes();
if (clazzs.length == arguments.length)
{
SystemServiceLog annotation = method.getAnnotation(SystemServiceLog.class);
moduleType = annotation.moduleType();
operateValue = annotation.operateValue();
description = annotation.description();
innnerBean = new InnnerBean(moduleType, operateValue, description);
break;
}
}
}
innnerBean.setArguments(arguments);
return innnerBean;
}

/**
* 获取注解中对方法的描述信息 用于Controller层注解
* @param joinPoint 切点
* @return 方法描述
* @throws Exception
* @see [类、类#方法、类#成员]
*/
@SuppressWarnings("rawtypes")
public static InnnerBean getControllerMethodDescription(JoinPoint joinPoint)
throws Exception
{
String targetName = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
Object[] arguments = joinPoint.getArgs();
Class targetClass = Class.forName(targetName);
Method[] methods = targetClass.getMethods();
String moduleType = "";
String operateValue = "";
String description = "";
boolean firstParamName = false;
InnnerBean innnerBean = new InnnerBean(moduleType, operateValue, description);
for (Method method : methods)
{
if (method.getName().equals(methodName))
{
Class[] clazzs = method.getParameterTypes();
if (clazzs.length == arguments.length)
{
SystemControllerLog annotation = method.getAnnotation(SystemControllerLog.class);
moduleType = annotation.moduleType();
operateValue = annotation.operateValue();
description = annotation.description();
firstParamName = annotation.firstParamName();
innnerBean = new InnnerBean(moduleType, operateValue, description);
innnerBean.setFirstParamName(firstParamName);
break;
}
}
}
innnerBean.setArguments(arguments);
return innnerBean;
}

/**
*
* 内部类封装注入信息
* @see [相关类/方法]
* @since [产品/模块版本]
*/
static class InnnerBean
{
private String moduleType;// 模块代码

private String description;// 描述

private String operateValue;// 操作类型

private boolean firstParamName;

private Object[] arguments;

public InnnerBean(String moduleType, String operateValue, String description)
{
super();
this.moduleType = moduleType;
this.description = description;
this.operateValue = operateValue;
}

public String getOperateValue()
{
return operateValue;
}

public void setOperateValue(String operateValue)
{
this.operateValue = operateValue;
}

public String getModuleType()
{
return moduleType;
}

public void setModuleType(String moduleType)
{
this.moduleType = moduleType;
}

public String getDescription()
{
return description;
}

public void setDescription(String description)
{
this.description = description;
}

public Object[] getArguments()
{
return arguments;
}

public void setArguments(Object[] arguments)
{
this.arguments = arguments;
}

public boolean isFirstParamName()
{
return firstParamName;
}

public void setFirstParamName(boolean firstParamName)
{
this.firstParamName = firstParamName;
}
}

/**
*
* 异步保存日志
* @see [相关类/方法]
* @since [产品/模块版本]
*/
class saveLogThread implements Runnable
{
Lock lock = new ReentrantLock();

@Override
public void run()
{
try
{
while (run)
{
while (queue.size() != 0)
{
// 如果对插入顺序无要求,此处不需要同步可提升效率
lock.lock();
Log log = queue.take();
logService.insert(log);
lock.unlock();
}
Thread.sleep(3000);
}
}
catch (InterruptedException e)
{
logger.error("saveLogThread被唤醒:" + e.toString());
}
catch (Exception e)
{
logger.error("saveLogThread异常:" + e.toString());
}
}
}
}

二、定值类

日志系统中的定值

/**
*
* 日志系统中的定值
*
* @see [相关类/方法]
* @since [产品/模块版本]
*/
public interface LogTypes
{

/**
*
* 操作状态(成功与否Y\\N)
* @see [相关类/方法]
* @since [产品/模块版本]
*/
static interface operateStatus
{
// 成功
final String Y = "Y";

// 失败
final String N = "N";
}

/**
*
* 日志类型
* @see [相关类/方法]
* @since [产品/模块版本]
*/
static interface type
{
// 操作日志
final String operate = "operate";

// 异常日志
final String exception = "exception";
}

/**
*
* 模块类型
* @see [相关类/方法]
* @since [产品/模块版本]
*/
static interface moduleType
{
// 登录模块
final String LOGIN = "LOGIN";

// 项目模块
final String PROJECT = "PROJECT";

// 客户模块
final String CUSTOMER = "CUSTOMER";

// 用户模块
final String SYS_USER = "SYS_USER";
}

/**
*
* 操作类型
* @see [相关类/方法]
* @since [产品/模块版本]
*/
static interface operateValue
{
// 查询
final String select = "select";

// 登录
final String login = "login";

// 保存
final String save = "save";

// 新增
final String add = "add";

// 修改
final String edit = "edit";

// 删除
final String delete = "delete";

// 查看
final String view = "view";

// 修改密码
final String editPassword = "editPassword";

// 上传
final String upload = "upload";

// 下载
final String down = "down";

// 下载
final String packagedown = "packagedown";

}

/**
*
* 保存描述的前缀
*          方便于批量修改该备注
* @see [相关类/方法]
* @since [产品/模块版本]
*/
static interface Prefix
{
// 查询
final String savePrefix = "新增/编辑";

}
}

三、实体类

1、日志实体

应用了Builder设计模式/**
*
* 操作日志&异常日志
* @see [相关类/方法]
* @since [产品/模块版本]
*/
public class Log implements Serializable
{

/**
* serialVersionUID
*/
private static final long serialVersionUID = 1L;

private static final String reqSource;// 请求来源,pc:pc端,wap:wap端 默认来源为pc

private static final String localAddr;// 服务器IP

private String ip;// 操作电脑ip

private String fullName;// 操作人员名字

private String loginName;// 操作人员登录账号

private Date operateDateTime;// 操作时间

private Date createDateTime;// 创建时间

private Long id;

private String type;// 日志类型,‘operate’:操作日志,‘exception’:异常日志

private String moduleType;// 模块代码

private String operateCode;// 操作代码

private String operateValue;// 操作类型

private String remark;// 操作备注(记录参数)

private String operateStatus;// 操作状态(成功与否Y\\N)

private String method;// 调用方法

private String param;// 方法的请求参数

private String exceptionDetail;// 异常信息

static{
reqSource = "wap";
localAddr = WebUtils.getLocalAddr();
}

public void init()
{
User user = WebUtils.getSessionValue(LoginContact.SESSION_USER);
ip = WebUtils.getRemoteIP();
loginName = user != null ? user.getAccountNo() : getLoginName();
fullName = user != null ? user.getUserName() : getFullName();
operateDateTime = new Date();
createDateTime = new Date();
}

public Log()
{
init();
}

public Log(Log origin)
{
init();
this.id = origin.id;
this.type = origin.type;
this.moduleType = origin.moduleType;
this.operateCode = origin.operateCode;
this.operateValue = origin.operateValue;
this.remark = origin.remark;
this.operateStatus = origin.operateStatus;
this.method = origin.method;
this.param = origin.param;
this.exceptionDetail = origin.exceptionDetail;
this.fullName = origin.fullName;
this.loginName = origin.loginName;
}

public String getIp()
{
return ip;
}

public void setIp(String ip)
{
this.ip = ip;
}

public String getFullName()
{
return fullName;
}

public void setFullName(String fullName)
{
this.fullName = fullName;
}

public String getLoginName()
{
return loginName;
}

public void setLoginName(String loginName)
{
this.loginName = loginName;
}

public Date getOperateDateTime()
{
return operateDateTime;
}

public void setOperateDateTime(Date operateDateTime)
{
this.operateDateTime = operateDateTime;
}

public Date getCreateDateTime()
{
return createDateTime;
}

public void setCreateDateTime(Date createDateTime)
{
this.createDateTime = createDateTime;
}

public Long getId()
{
return id;
}

public void setId(Long id)
{
this.id = id;
}

public String getType()
{
return type;
}

public void setType(String type)
{
this.type = type;
}

public String getModuleType()
{
return moduleType;
}

public void setModuleType(String moduleType)
{
this.moduleType = moduleType;
}

public String getOperateCode()
{
return operateCode;
}

public void setOperateCode(String operateCode)
{
this.operateCode = operateCode;
}

public String getOperateValue()
{
return operateValue;
}

public void setOperateValue(String operateValue)
{
this.operateValue = operateValue;
}

public String getRemark()
{
return remark;
}

public void setRemark(String remark)
{
this.remark = remark;
}

public String getOperateStatus()
{
return operateStatus;
}

public void setOperateStatus(String operateStatus)
{
this.operateStatus = operateStatus;
}

public String getMethod()
{
return method;
}

public void setMethod(String method)
{
this.method = method;
}

public String getParam()
{
return param;
}

public void setParam(String param)
{
this.param = param;
}

public String getExceptionDetail()
{
return exceptionDetail;
}

public void setExceptionDetail(String exceptionDetail)
{
this.exceptionDetail = exceptionDetail;
}

public String getLocalAddr()
{
return localAddr;
}

public static class Builder
{

private Log target;

public Builder()
{
target = new Log();
}

public Builder id(Long id)
{
target.id = id;
return this;
}

public Builder type(String type)
{
target.type = type;
return this;
}

public Builder moduleType(String moduleType)
{
target.moduleType = moduleType;
return this;
}

public Builder operateCode(String operateCode)
{
target.operateCode = operateCode;
return this;
}

public Builder operateValue(String operateValue)
{
target.operateValue = operateValue;
return this;
}

public Builder remark(String remark)
{
target.remark = remark;
return this;
}

public Builder operateStatus(String operateStatus)
{
target.operateStatus = operateStatus;
return this;
}

public Builder method(String method)
{
target.method = method;
return this;
}

public Builder param(String param)
{
target.param = param;
return this;
}

public Builder exceptionDetail(String exceptionDetail)
{
target.exceptionDetail = exceptionDetail;
return this;
}

public Builder loginName(String loginName)
{
target.loginName = loginName;
return this;
}

public Builder fullName(String fullName)
{
target.fullName = fullName;
return this;
}

public Log build()
{
return new Log(target);
}

}
}

2.返回结果实体

/**
* @ClassName: WebResult
* @version 1.0
* @Desc: WEB返回JSON结果
* @history v1.0
*/
public class WebResult implements Serializable
{
private static final long serialVersionUID = -4776437900752507269L;

/**
* 返回消息
*/
private String msg;

/**
* 返回码
*/
private String code;

/**
* 返回数据
*/
private Object data;

private Map<?,?> map;

private List<Map<String, Object>> list;

public WebResult()
{
}

public WebResult(String msg, String code)
{
super();
this.msg = msg;
this.code = code;
}

public WebResult(String msg, String code, Object data)
{
super();
this.msg = msg;
this.code = code;
this.data = data;
}

public String getMsg()
{
return msg;
}

public void setMsg(String msg)
{
this.msg = msg;
}

public String getCode()
{
return code;
}

public void setCode(String code)
{
this.code = code;
}

public Object getData()
{
return data;
}

public void setData(Object data)
{
this.data = data;
}

public Map<?, ?> getMap()
{
return map;
}

public void setMap(Map<?, ?> map)
{
this.map = map;
}

public List<Map<String, Object>> getList()
{
return list;
}

public void setList(List<Map<String, Object>> list)
{
this.list = list;
}

@Override
public String toString()
{
return "WebResult [msg=" + msg + ", code=" + code + ", data=" + data + "]";
}

/**
* 初始失败方法
*
* @author cc HSSD0473
* @see [类、类#方法、类#成员]
*/
public void invokeFail(){
this.data = null;
this.code = FlagContact.BACK_FAIL;
this.msg = "操作失败";
}

public void invokeFail(String msg){
this.data = null;
this.code = FlagContact.BACK_FAIL;
if(msg != null && !msg.equals(""))
{
this.msg = msg;
}
}

public void invokeSuccess()
{
this.code = FlagContact.BACK_SUCCESS;
this.msg = "操作成功";
}

public void invokeSuccess(String msg)
{
if(msg != null && !msg.equals(""))
{
this.msg = msg;
}
this.code = FlagContact.BACK_SUCCESS;
}
}

三、工具类

获取登录用户,客户ip主机ip等方法**
* @ClassName: WebUtils
* @version 1.0
* @Desc: WebUtils
* @history v1.0
*/
public class WebUtils
{
/**
* 描述:获取request对象
* @return
*/
public static HttpServletRequest getRequest()
{
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
}

/**
* 描述:获取responce对象
* @return
*/
public static HttpServletResponse getResponse()
{
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();

}

/**
* 描述:获取session
* @return
*/
public static HttpSession getSession()
{
return getRequest().getSession();
}

/**
* 描述:设置session值
* @param key
* @param val
*/
public static <T> void setSessionValue(String key, T val)
{
getSession().setAttribute(key, val);
}

/**
* 描述:获取session值
* @param key
* @return
*/
@SuppressWarnings("unchecked")
public static <T> T getSessionValue(String key)
{
return (T) getSession().getAttribute(key);
}

/**
* 描述:移除session
* @param key
* @return
*/
@SuppressWarnings("unchecked")
public static <T> T removeSessionValue(String key)
{
Object obj = getSession().getAttribute(key);
getSession().removeAttribute(key);
return (T) obj;
}

/**
* 描述:获取客户端ip
* @param request
* @return
*/
public static String getRemoteIP(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.getRemoteAddr();
}
return ip.equals("0:0:0:0:0:0:0:1") ? "127.0.0.1" : ip;
}

/**
*
* 获得本机IP
* @return
* @throws Exception
* @see [类、类#方法、类#成员]
*/
public final static String getLocalAddr()
{
String hostAddress = "";
try
{
hostAddress = InetAddress.getLocalHost().getHostAddress();
}
catch (Exception e)
{
}
return hostAddress;
}

/**
* 描述:获取客户端ip
* @return
*/
public static String getRemoteIP()
{
HttpServletRequest request = getRequest();
return getRemoteIP(request);
}
}

四、清理工作

tomcat停止前,异步日志的清理动作

/**
*
* tomcat停止前,异步日志的清理动作
* @see  [相关类/方法]
* @since  [产品/模块版本]
*/
@WebListener()
public class BeforeDestoryListener implements ServletContextListener
{

@Override
public void contextInitialized(ServletContextEvent sce)
{
}

@Override
public void contextDestroyed(ServletContextEvent sce)
{
while (SystemLogAspect.getQueue().size() == 0)
{
SystemLogAspect.setRun(false);
break;
}
}

}

数据库

用的是mysql,其他数据库请自行更改语句CREATE TABLE `t_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`reqSource` varchar(10) DEFAULT 'pc' COMMENT '请求来源,pc:pc端,wap:wap端 默认来源为pc',
`type` varchar(10) DEFAULT NULL COMMENT '日志类型,‘operate’:操作日志,‘exception’:异常日志',
`ip` varchar(20) NOT NULL COMMENT '操作电脑ip',
`fullName` varchar(50) NOT NULL COMMENT '操作人员名字',
`loginName` varchar(50) NOT NULL COMMENT '操作人员登录账号',
`moduleType` varchar(50) NOT NULL COMMENT '模块代码',
`operateCode` varchar(50) NOT NULL COMMENT '操作代码',
`operateValue` varchar(50) DEFAULT NULL COMMENT '操作类型',
`operateDateTime` datetime NOT NULL COMMENT '操作时间',
`createDateTime` datetime NOT NULL COMMENT '创建时间',
`remark` varchar(100) DEFAULT NULL COMMENT '操作备注(记录参数)',
`operateStatus` varchar(20) DEFAULT NULL COMMENT '操作状态(成功与否Y\\N)',
`localAddr` varchar(20) DEFAULT NULL COMMENT '服务器IP',
`method` varchar(100) DEFAULT NULL COMMENT '调用方法',
`param` varchar(2000) DEFAULT NULL COMMENT '方法的请求参数',
`exceptionDetail` varchar(1000) DEFAULT NULL COMMENT '异常信息',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3430 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;

测试及结果

控制层代码

/**
* 登录请求
*
* @param userName	用户名
* @param password	密码
* @return WebResult msg:系统反馈消息 code:登录标识码
* @see [类、类#方法、类#成员]
*/
@ResponseBody
@RequestMapping("login")
@SystemControllerLog(moduleType=LogTypes.moduleType.LOGIN,operateValue=LogTypes.operateValue.login,description = "登录动作")
public WebResult userLogin(String accoutnNo, String password) {
WebResult wt = new WebResult();
try
{
// 登录逻辑

}
catch(Exception e)
{
log.error("登录异常:" + e.toString());
wt.invokeFail();
}

return wt;
}

数据库结果

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息