汇客huikeCRM项目实战-初出茅庐

来自编程小趴菜的分享~~

希望对你有所帮助~~

你的小小的赞就是对我最大的鼓励~~

有不明白的地方可以私信解答~

文章目录

任务一:运行后端代码和前端代码

任务二:技术调研,接口权限,数据权限控制,自定义注解打印日志等


前言

该文章记录了汇客CRM项目实战第一天可能出现的问题,以及一些技术方案

一、运行后端代码和前端代码

        1、看代码阶段

注意:该阶段主要是理清楚项目的层次结构,各个模块的功能以及作用是什么,并且要明白数据库表与表之间的关联关系

汇客huikeCRM项目实战-初出茅庐_第1张图片汇客huikeCRM项目实战-初出茅庐_第2张图片汇客huikeCRM项目实战-初出茅庐_第3张图片

 汇客huikeCRM项目实战-初出茅庐_第4张图片

         2、运行后端代码

                a、打开项目首先修改一下maven的配置,然后设置一下jdk版本为1.8

                b、在huike-admin模块下的application.yml文件中修改一下当前使用的redis的IP和地址,记得一定要先启动redis,再启动后端项目,不然会报redis连接异常的问题

                c、在huike-admin模块下的application-druid.yml文件下修改一下数据库的连接信息,改为自己的数据库,然后启动后端项目

                这样后端代码就能跑起来了~~

        3、运行前端代码

                a、前置工作,记得先安装一下node.js,附上百度网盘链接

链接:https://pan.baidu.com/s/1xV4tWIEa5RArc5DdUGpWIw?pwd=kvwm 
提取码:kvwm 
                b、在前端代码的文件夹下打开命令行窗口,运行以下命令

//下载项目相关的依赖
npm install
//如果网不好的话,执行以下命令(记得在网好的时候下载,网不好的时候真的各种bug)
npm install --registry=https://registry.npm.taobao.org

# 启动服务
npm run dev

                这样前端代码就能跑起来了~~

汇客huikeCRM项目实战-初出茅庐_第5张图片

二、技术调研,自定义接口权限,自定义数据权限控制,自定义注解打印日志

        1、自定义接口权限控制

通过自定义注解来完成接口权限控制,有使用到jwt相关技术,也可以使用ThreadLocal来完成

        接口权限:顾名思义,配置不通角色调用接口的权限。有些敏感接口,是只能有固定的一些角色才能调用,普通角色是不能调用的。这种情况需要有一个明确的系统来控制对应的访问权限。

        接口权限系统,可以控制某些接口只能由固定的角色调用,可以动态控制不同的角色对不同接口的访问权限。

        通过接口配置实现,对接口的访问权限控制和数据权限控制。
        接口是REST接口,接口权限认证机制使用Json web token (JWT)。

        接口权限调用流程:

(1)通过接口用户的用户名密码,调用鉴权token接口获取接口用户的token,该token,2个小时内有效
(2)把获取的token作为参数,调用接口的时候,会根据token去鉴权
(3)鉴权通过,接口会根据接口定义的编码,检验是否有访问权限,有则可以继续访问,无则提示访问受限
(4)有访问权限,则获取接口的数据权限规则,根据授权的数据权限规则返回需要的数据
实现一个新的接口,无需关注token的鉴权机制,接口权限判断方式
使用AOP实现接口拦截:@PreAuth
鉴权配置注解名称为 @PreAuth ,在需要进行鉴权配置的方法加上 @PreAuth 注解,并在注解内写 入相关的鉴权方法。

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface PreAuth {
    String value();
}

         具体实现方法:

@Aspect
public class AuthAspect implements ApplicationContextAware {

	/**
	 * 表达式处理
	 */
	private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();

	/**
	 * 切 方法 和 类上的 @PreAuth 注解
	 *
	 * @param point 切点
	 * @return Object
	 * @throws Throwable 没有权限的异常
	 */
	@Around(
		"@annotation(com.dindo.core.secure.annotation.PreAuth) || " +
			"@within(com.dindo.core.secure.annotation.PreAuth)"
	)
	public Object preAuth(ProceedingJoinPoint point) throws Throwable {
		if (handleAuth(point)) {
			return point.proceed();
		}
		throw new SecureException(ResultCode.UN_AUTHORIZED);
	}

	/**
	 * 处理权限
	 *
	 * @param point 切点
	 */
	private boolean handleAuth(ProceedingJoinPoint point) {
		MethodSignature ms = (MethodSignature) point.getSignature();
		Method method = ms.getMethod();
		// 读取权限注解,优先方法上,没有则读取类
		PreAuth preAuth = ClassUtil.getAnnotation(method, PreAuth.class);
		// 判断表达式
		String condition = preAuth.value();
		if (StringUtil.isNotBlank(condition)) {
			Expression expression = EXPRESSION_PARSER.parseExpression(condition);
			// 方法参数值
			Object[] args = point.getArgs();
			StandardEvaluationContext context = getEvaluationContext(method, args);
			return expression.getValue(context, Boolean.class);
		}
		return false;
	}

	/**
	 * 获取方法上的参数
	 *
	 * @param method 方法
	 * @param args   变量
	 * @return {SimpleEvaluationContext}
	 */
	private StandardEvaluationContext getEvaluationContext(Method method, Object[] args) {
		// 初始化Sp el表达式上下文,并设置 AuthFun
		StandardEvaluationContext context = new StandardEvaluationContext(new AuthFun());
		// 设置表达式支持spring bean
		context.setBeanResolver(new BeanFactoryResolver(applicationContext));
		for (int i = 0; i < args.length; i++) {
			// 读取方法参数
			MethodParameter methodParam = ClassUtil.getMethodParameter(method, i);
			// 设置方法 参数名和值 为sp el变量
			context.setVariable(methodParam.getParameterName(), args[i]);
		}
		return context;
	}

	private ApplicationContext applicationContext;

	@Override
	public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = applicationContext;
	}

}
public class AuthFun {

	/**
	 * 权限校验处理器
	 */
	private static IPermissionHandler permissionHandler;

	private static IPermissionHandler getPermissionHandler() {
		if (permissionHandler == null) {
			permissionHandler = SpringUtil.getBean(IPermissionHandler.class);
		}
		return permissionHandler;
	}

	/**
	 * 判断角色是否具有接口权限
	 *
	 * @return {boolean}
	 */
	public boolean permissionAll() {
		return getPermissionHandler().permissionAll();
	}

	/**
	 * 判断角色是否具有接口权限
	 *
	 * @param permission 权限编号
	 * @return {boolean}
	 */
	public boolean hasPermission(String permission) {
		return getPermissionHandler().hasPermission(permission);
	}

	/**
	 * 放行所有请求
	 *
	 * @return {boolean}
	 */
	public boolean permitAll() {
		return true;
	}

	/**
	 * 只有超管角色才可访问
	 *
	 * @return {boolean}
	 */
	public boolean denyAll() {
		return hasRole(RoleConstant.ADMIN);
	}

	/**
	 * 是否已授权
	 *
	 * @return {boolean}
	 */
	public boolean hasAuth() {
		return Func.isNotEmpty(AuthUtil.getUser());
	}

	/**
	 * 是否有时间授权
	 *
	 * @param start 开始时间
	 * @param end   结束时间
	 * @return {boolean}
	 */
	public boolean hasTimeAuth(Integer start, Integer end) {
		Integer hour = DateUtil.hour();
		return hour >= start && hour <= end;
	}

	/**
	 * 判断是否有该角色权限
	 *
	 * @param role 单角色
	 * @return {boolean}
	 */
	public boolean hasRole(String role) {
		return hasAnyRole(role);
	}

	/**
	 * 判断是否具有所有角色权限
	 *
	 * @param role 角色集合
	 * @return {boolean}
	 */
	public boolean hasAllRole(String... role) {
		for (String r : role) {
			if (!hasRole(r)) {
				return false;
			}
		}
		return true;
	}

	/**
	 * 判断是否有该角色权限
	 *
	 * @param role 角色集合
	 * @return {boolean}
	 */
	public boolean hasAnyRole(String... role) {
		BladeUser user = AuthUtil.getUser();
		if (user == null) {
			return false;
		}
		String userRole = user.getRoleName();
		if (StringUtil.isBlank(userRole)) {
			return false;
		}
		String[] roles = Func.toStrArray(userRole);
		for (String r : role) {
			if (CollectionUtil.contains(roles, r)) {
				return true;
			}
		}
		return false;
	}

}

        使用方法:可以注释到类上、方法上  

/**
* 新增
*/
@PostMapping("/save")
@PreAuth("hasPermission(#test)")
public R save(@RequestBody Blog blof){
    return R.status(service.save(blog)); 
}

        2、自定义数据权限控制

其基本原理同接口 权限控制,主要是判断访问该数据的用户的权限是否足够,依次来拒绝访问,或者自定义sql查询语句,返回部分数据

         所谓数据权限,就是有或者没有对某些数据的访问权限,具体表现形式就是当某用户有操作权限的时候,但不代表其对所有的数据都有查看或者管理权限。

        数据权限有两种表现形式:一种是行权限、另外一种是列权限。

        所谓行权限,就是限制用户对某些行的访问权限,比如:只能对本人、本部门、本组织的数据进行访问;也可以是根据数据的范围进行限制,比如:合同额大小来限制用户对数据的访问。

        所谓列权限,就是限制用户对某些列的访问权限,比如:某些内容的摘要可以被查阅,但是详细内容就只有VIP用户才能查看。通过数据权限,可以从物理层级限制用户对数据的行或列进行获取。

        再比如:同样一个部门经理的角色,看到的数据是不一样的,所以,牵扯到数据二字,就应该不和操作二字等同起来。所以我们是通过职位来解决数据权限的,职位也可以叫岗位,是和数据查看范围有关系的,也就是组织结构里的关系。 所以在设计数据结构的时候,每个有数据范围的数据实体,都需要具备数据拥有人的字段,比如A部门的小b同学,他创建的数据,只能由A部门的部门经理看到,而同样在角色里具有部门经理的B部门的部门经理是不能看到的。所以延伸出来了一个设计思路,根据数据拥有人,圈定查看范围。数据范围的维度有:全部、本集团、本公司、本部门、自己,五个维度,可以满足大部分业务场景。 还有一个维度是自定义维度,可以自定义机构进行设置。这样的设计就达到了数据权限的操作灵活性。

        核心注解:

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface DataAuth {
	/**
	 * 资源编号
	 */
	String code() default "";

	/**
	 * 数据权限对应字段
	 */
	String column() default DataScopeConstant.DEFAULT_COLUMN;

	/**
	 * 数据权限规则
	 */
	DataScopeEnum type() default DataScopeEnum.ALL;

	/**
	 * 可见字段
	 */
	String field() default "*";

	/**
	 * 数据权限规则值域
	 */
	String value() default "";
}

        枚举类: 

@Getter
@AllArgsConStructor
public enum DataScopeEnum{
    /**
    * 全部数据
    */
    ALL(1,"全部");
    
    /**
    * 本人可见
    */
    OWN(2,"本人可见");
    
    /**
    * 所在机构可见
    */
    OWN_DEPT(3,"所在机构可见");
    
    /**
    * 所在机构及其子级可见
    */
    OWN_DEPT_CHILD(4,"所在机构及其子级可见");
    
    /**
    * 自定义
    */
    CUSTOM(5,"自定义");
        
    private final int type;
    private final String description;
}

        目前的数据权限类型一共有五种,前面四种都是不需要自定义写sql的,只有选择了CUSTOM类型,才需要定义注解的value属性。

        注解默认过滤的字段名为create_dept,如果有修改,则需要定义对应的字段名。  

         数据权限拦截器配置:


/**
 * mybatis 数据权限拦截器
 * @author L.cm, Chill
 */
@Slf4j
@RequiredArgsConstructor
@SuppressWarnings({"rawtypes"})
public class DataScopeInterceptor implements QueryInterceptor {

	private final ConcurrentMap dataAuthMap = new ConcurrentHashMap<>(8);

	private final DataScopeHandler dataScopeHandler;
	private final DataScopeProperties dataScopeProperties;

	@Override
	public void intercept(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
		//未启用则放行
		if (!dataScopeProperties.getEnabled()) {
			return;
		}

		//未取到用户则放行
		BladeUser bladeUser = AuthUtil.getUser();
		if (bladeUser == null) {
			return;
		}

		if (SqlCommandType.SELECT != ms.getSqlCommandType() || StatementType.CALLABLE == ms.getStatementType()) {
			return;
		}

		String originalSql = boundSql.getSql();

		//查找注解中包含DataAuth类型的参数
		DataAuth dataAuth = findDataAuthAnnotation(ms);

		//注解为空并且数据权限方法名未匹配到,则放行
		String mapperId = ms.getId();
		String className = mapperId.substring(0, mapperId.lastIndexOf(StringPool.DOT));
		String mapperName = ClassUtil.getShortName(className);
		String methodName = mapperId.substring(mapperId.lastIndexOf(StringPool.DOT) + 1);
		boolean mapperSkip = dataScopeProperties.getMapperKey().stream().noneMatch(methodName::contains)
			|| dataScopeProperties.getMapperExclude().stream().anyMatch(mapperName::contains);
		if (dataAuth == null && mapperSkip) {
			return;
		}

		//创建数据权限模型
		DataScopeModel dataScope = new DataScopeModel();

		//若注解不为空,则配置注解项
		if (dataAuth != null) {
			dataScope.setResourceCode(dataAuth.code());
			dataScope.setScopeColumn(dataAuth.column());
			dataScope.setScopeType(dataAuth.type().getType());
			dataScope.setScopeField(dataAuth.field());
			dataScope.setScopeValue(dataAuth.value());
		}

		//获取数据权限规则对应的筛选Sql
		String sqlCondition = dataScopeHandler.sqlCondition(mapperId, dataScope, bladeUser, originalSql);
		if (!StringUtil.isBlank(sqlCondition)) {
			PluginUtils.MPBoundSql mpBoundSql = PluginUtils.mpBoundSql(boundSql);
			mpBoundSql.sql(sqlCondition);
		}
	}

	/**
	 * 获取数据权限注解信息
	 *
	 * @param mappedStatement mappedStatement
	 * @return DataAuth
	 */
	private DataAuth findDataAuthAnnotation(MappedStatement mappedStatement) {
		String id = mappedStatement.getId();
		return dataAuthMap.computeIfAbsent(id, (key) -> {
			String className = key.substring(0, key.lastIndexOf(StringPool.DOT));
			String mapperBean = StringUtil.firstCharToLower(ClassUtil.getShortName(className));
			Object mapper = SpringUtil.getBean(mapperBean);
			String methodName = key.substring(key.lastIndexOf(StringPool.DOT) + 1);
			Class[] interfaces = ClassUtil.getAllInterfaces(mapper);
			for (Class mapperInterface : interfaces) {
				for (Method method : mapperInterface.getDeclaredMethods()) {
					if (methodName.equals(method.getName()) && method.isAnnotationPresent(DataAuth.class)) {
						return method.getAnnotation(DataAuth.class);
					}
				}
			}
			return null;
		});
	}

}

         数据权限处规则:


/**
 * 默认数据权限规则
 * 获取过滤sql
 * @param mapperId    数据查询类
 * @param dataScope   数据权限类
 * @param bladeUser   当前用户信息
 * @param originalSql 原始Sql
 * @author Chill
 */
@RequiredArgsConstructor
public class BladeDataScopeHandler implements DataScopeHandler {

	private final ScopeModelHandler scopeModelHandler;

	@Override
	public String sqlCondition(String mapperId, DataScopeModel dataScope, BladeUser bladeUser, String originalSql) {

		//数据权限资源编号
		String code = dataScope.getResourceCode();

		//根据mapperId从数据库中获取对应模型
		DataScopeModel dataScopeDb = scopeModelHandler.getDataScopeByMapper(mapperId, bladeUser.getRoleId());

		//mapperId配置未取到则从数据库中根据资源编号获取
		if (dataScopeDb == null && StringUtil.isNotBlank(code)) {
			dataScopeDb = scopeModelHandler.getDataScopeByCode(code);
		}

		//未从数据库找到对应配置则采用默认
		dataScope = (dataScopeDb != null) ? dataScopeDb : dataScope;

		//判断数据权限类型并组装对应Sql
		Integer scopeRule = Objects.requireNonNull(dataScope).getScopeType();
		DataScopeEnum scopeTypeEnum = DataScopeEnum.of(scopeRule);
		List ids = new ArrayList<>();
		String whereSql = "where scope.{} in ({})";
        //需要注意的是,下面的这个判断,如果角色是ADMINISTRATOR的话也是不执行直接返回null的,我就是在这里掉坑
		if (DataScopeEnum.ALL == scopeTypeEnum || StringUtil.containsAny(bladeUser.getRoleName(), RoleConstant.ADMINISTRATOR)) {
			return null;
		} else if (DataScopeEnum.CUSTOM == scopeTypeEnum) {
			whereSql = PlaceholderUtil.getDefaultResolver().resolveByMap(dataScope.getScopeValue(), BeanUtil.toMap(bladeUser));
		} else if (DataScopeEnum.OWN == scopeTypeEnum) {
			ids.add(bladeUser.getUserId());
		} else if (DataScopeEnum.OWN_DEPT == scopeTypeEnum) {
			ids.addAll(Func.toLongList(bladeUser.getDeptId()));
		} else if (DataScopeEnum.OWN_DEPT_CHILD == scopeTypeEnum) {
			List deptIds = Func.toLongList(bladeUser.getDeptId());
			ids.addAll(deptIds);
			deptIds.forEach(deptId -> {
				List deptIdList = scopeModelHandler.getDeptAncestors(deptId);
				ids.addAll(deptIdList);
			});
		}
		return StringUtil.format(" select {} from ({}) scope " + whereSql, Func.toStr(dataScope.getScopeField(), "*"), originalSql, dataScope.getScopeColumn(), StringUtil.join(ids));
	}

}

        3、自定义注解打印日志

完成思路:主要是使用自定义注解,以及aop的思想来完成方法调用的日志打印

引入依赖:


	org.springframework.boot
	spring-boot-starter-aop

创建注解:

/**
 * 自定义注解练习
 * 声明在方法上的注解
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LogAnnotation {

    /**
     * 日志内容
     * @return
     */
    String value() default "";

    /**
     * 操作方类型
     * 0-未知来源,1-pc端,2-小程序端,3-其他
     */
    int type() default 0;
}

实现注解环绕:

@Slf4j
@Component
@Aspect
public class OpenLogUtil {

    /**
     * 配置切入点:注释中引号的部分为自己创建的注解的路径,可以通过该注解请求到切入点中去。
     */
    @Pointcut("@annotation(com.example.BootDemo.Annotation.LogAnnotation)")
    public void logPointcut() {
        // 该方法无方法体,主要为了让同类中其他方法使用此切入点
    }

    /**
     * 配置环绕通知,使用自定义方法上注册的切入点。
     */
    @Around("logPointcut()")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        //前置
        long startTime= System.currentTimeMillis();
        /**
         * 环绕通知=前置+目标方法执行+后置,proceed方法就是用于启动目标方法执行的
         * Proceedingjoinpoint 继承了 JoinPoint。是在JoinPoint的基础上暴露出 proceed 这个方法。proceed很重要,这个是aop代理链执行的方法。
         * 暴露出这个方法,就能支持 aop:around 这种切面(而其他的几种切面只需要用到JoinPoint,,这也是环绕通知和前置、后置通知方法的一个最大区别。这跟切面类型有关)
         * */
        Object result=joinPoint.proceed();
        //后置
        long time=System.currentTimeMillis()-startTime;
        recordLog(joinPoint,time);
        return result;
    }
     /**
     * 记录日志
     */
    public void recordLog(ProceedingJoinPoint proceedingJoinPoint,long time){
        //getSignature());是获取到这样的信息 :修饰符+ 包名+组件名(类名) +方法名
        MethodSignature methodSignature= (MethodSignature) proceedingJoinPoint.getSignature();
        Method method=methodSignature.getMethod();
        //getAnnotation:方法如果存在这样的注释,则返回指定类型的元素的注释,否则为null
        LogAnnotation logAnnotation=method.getAnnotation(LogAnnotation.class);
        log.info("==============================开始记录日志===============================");
        log.info("value:{}",logAnnotation.value());
        log.info("type:{}",logAnnotation.type());
        //proceedingJoinPoint.getTarget():获取切入点所在目标对象
        String className=proceedingJoinPoint.getTarget().getClass().getName();
        String methodName=methodSignature.getName();
        log.info("请求的方法是:{}",className+"."+methodName+"()");
        //这里返回的是切入点方法的参数列表
        Object[] args=proceedingJoinPoint.getArgs();
        String params= JSON.toJSONString(args.length==0?"":args[0]);
        log.info("请求的参数是:{}",params);
        log.info("执行时间总共为:{}",time);
        log.info("=================================end===================================");
    }
}

创建controller:

@Slf4j
@RestController
public class MyController {

	@LogAnnotation(value = "记录日志",type=1)
    @GetMapping("/SourceB")
    public String SourceB(){
        log.info("正在执行数据源B");
        return "Source B, All Right!";
    }
}

打印结果:

汇客huikeCRM项目实战-初出茅庐_第6张图片


 简简单单的自定义注解打印日志就完成了,是不是很简单啊~~后续可以通过IO流的方式来把日志打印到本地指定文件夹,如下:

package com.huike.common.advice;

import com.huike.common.utils.LogPrint;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

@Slf4j
@Aspect
@Component
public class LogAdvice {

    /**
     * 配置切入点
     */
    @Pointcut("@annotation(com.huike.common.annotation.Log)")
    public void log() {
    }

    /**
     * 环绕通知
     *
     * @param pjp
     * @return
     * @throws Throwable
     */
    @Around("log()")
    public Object logAround(ProceedingJoinPoint pjp) throws Throwable {
        Long startTime = System.currentTimeMillis();

        // 执行原始方法
        Object proceed = pjp.proceed();

        //记录日志
        Long endTime = System.currentTimeMillis();
        Long time = endTime - startTime;
        logOut(pjp, time,proceed);

        return proceed;
    }

    /**
     * 记录日志的方法
     *
     * @param pjp
     * @param time
     */
    private void logOut(ProceedingJoinPoint pjp, Long time,Object proceed) {
        Signature signature = pjp.getSignature();
        String typeName = signature.getDeclaringTypeName();//获取包名
        String methondName = signature.getName();//获取执行的方法名

        Date date = new Date();
        String format = new SimpleDateFormat("yyyy-HH-dd HH:mm:ss").format(date);
        Object[] args = pjp.getArgs();//获取参数

        RequestAttributes requestAttribute = RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = ((ServletRequestAttributes) requestAttribute).getRequest();
        String requestURL = request.getRequestURL().toString();
        String contextPath = request.getContextPath();

        if (proceed == null){
            proceed = "";
        }else {
            proceed = proceed.toString();
        }

        log.info("=======================" + format + "=========================");
        log.info("请求路径是:{}", requestURL);
        log.info("请求包结构是:{}", typeName);
        log.info("请求访问的方法是:{}", methondName);
        log.info("携带的参数是:{}", Arrays.toString(args));
        log.info("返回的结果是:{}", proceed);
        log.info("执行耗时:{} 毫秒", time);
        log.info("=============================================================");

        List list = new ArrayList<>();
        list.add("=======================" + format + "=========================");
        list.add("请求路径是:" + requestURL);
        list.add("请求包结构是:" + typeName);
        list.add("请求访问的方法是:" + methondName);
        list.add("携带的参数是:" + Arrays.toString(args));
        list.add("返回的结果是:" + proceed);
        list.add("执行耗时:" + time + "毫秒");
        list.add("=============================================================");
        LogPrint.print(list,"D:\\huike.log");

    }
}

打印日志输出的方法的工具类:

package com.huike.common.utils;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.List;

public class LogPrint {

    public static void print(List list,String path){
        try {
            BufferedWriter bw = new BufferedWriter(new FileWriter(path,true));
            for (String s : list) {
                bw.write(s);//输出日志
                bw.newLine();//换行
            }
            bw.newLine();//换行
            bw.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

 

总结

以上就是CRM第一天的重点内容啦,是不是很简单,有什么不动的可以私信答复。

你可能感兴趣的:(汇客CRM,java,spring,boot,spring,maven)