【Lilishop商城】No2-3.确定软件架构搭建二(本篇包括接口规范、日志处理)

 仅涉及后端,全部目录看顶部专栏,代码、文档、接口路径在:

【Lilishop商城】记录一下B2B2C商城系统学习笔记~_清晨敲代码的博客-CSDN博客


全篇只介绍重点架构逻辑,具体编写看源代码就行,读起来也不复杂~

谨慎:源代码中有一些注释是错误的,有的注释意思完全相反,有的注释对不上号,我在阅读过程中就顺手更新了,并且在我不会的地方添加了新的注释,所以在读源代码过程中一定要谨慎啊!

目录

A1.接口规范

A2.日志处理(使用的slf4j+logback+AOP+注解)

B1.日志基本搭建

B2.系统日志架构

B3.日志整合logstash(待更新)

剩余内容:ES检索、消息中间件AMQP、定时任务等


A1.接口规范

接口规范按照 RESTful API接口规范,但是这个项目里面的接口规范还不太严谨,比如新增类型接口,有的用"/add",有的不用,并没有统一,之后自己开发的话还是要注意的(统一的接口规范对于前后端都是非常方便的)。

RESTful API接口规范可以看这篇:RESTful API接口规范

这儿就不重点讲了RESTful,然后还有一个需要规定的就是接口返值类型,一般项目都会自定义一个返回类型,方便前端接收处理。

//1.后端返回数据类,详见:cn.lili.common.vo.ResultMessage
//里面就包括接口规范的 result/data、code、message

@Data
public class ResultMessage<T> implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 成功标志
     */
    private boolean success;

    /**
     * 消息
     */
    private String message;

    /**
     * 返回代码
     */
    private Integer code;

    /**
     * 时间戳
     */
    private long timestamp = System.currentTimeMillis();

    /**
     * 结果对象
     */
    private T result;
}

//2.返回结果工具类,详见:cn.lili.common.utils.ResultUtil
//该类专门处理返回信息的,直接是用他提供的静态方法就可以方便的返回数据。

public class ResultUtil<T> {

    /**
     * 抽象类,存放结果
     */
    private final ResultMessage<T> resultMessage;
    /**
     * 正常响应
     */
    private static final Integer SUCCESS = 200;

    /**
     * 构造话方法,给响应结果默认值
     */
    public ResultUtil() {
        resultMessage = new ResultMessage<>();
        resultMessage.setSuccess(true);
        resultMessage.setMessage("success");
        resultMessage.setCode(SUCCESS);
    }

    /**
     * 抽象静态方法,返回结果集
     * @param t 范型
     * @param <T>  范型
     * @return 消息
     */
    public static <T> ResultMessage<T> data(T t) {
        return new ResultUtil<T>().setData(t);
    }
...
}

//使用范例
@Slf4j
@RestController
@Api(tags = "管理端,菜单管理接口")
@RequestMapping("/manager/permission/menu")
public class MenuManagerController {

    @Autowired
    private MenuService menuService;

    @ApiOperation(value = "搜索菜单")
    @GetMapping
    public ResultMessage<List<Menu>> searchPermissionList(MenuSearchParams searchParams) {
        return ResultUtil.data(menuService.searchList(searchParams));
    }
...
}

 这里是用了两个类,一个返回数据类,一个返回数据工具类,其实用一个工具类来完成这个逻辑,也是可以的。

A2.日志处理(使用的slf4j+logback+AOP+注解)

系统里面写的日志处理是使用的是log4j,但是代码里面使用的是 logback ,可能文档没更新吧。

日志的基本搭建就挺简单的,添加依赖,添加配置文件,就能够通过Logger使用了。

之后重点就是日志处理,如果某些日志处理业务繁琐,就会涉及到大量代码,并且操作也不方便,所以这样我们就不能直接在业务中使用Logger,我们需要进行处理。

常见的日志业务就是使用AOP+注解,将同样的日志业务抽象成切面,然后在需要实现该日志业务的方法上添加切点就行啦,例如系统日志就是很多业务都需要的日志处理

B1.日志基本搭建

springboot本身就自带了slf4j+logback依赖包,我们就不用依赖了。

然后在业务包里的resource的配置文件application.xml中设置配置信息,设置日志级别,设置日志存储路径等信息,例如manager-api模块中,

# /lilishop-master/manager-api/pom.xml

# logback日志
logging:
  config: classpath:logback-spring.xml
  # 输出级别
  level:
    cn.lili: info
  #    org.hibernate: debug
  #    org.springframework: debug
  file:
    # 指定路径
    path: lili-logs
  logback:
    rollingpolicy:
      # 最大保存天数
      max-history: 7
      # 每个文件最大大小
      max-file-size: 5MB

然后添加logback配置文件,具体的我有时也记不住,可以见这篇文章:springboot使用logback日志框架超详细教程

//详见 :/lilishop-master/manager-api/src/main/resources/logback-spring.xml

 然后启动程序时会根据配置的日志信息存储日志

B2.系统日志架构

springboot本身就自带了spring-boot-starter-aop依赖包,我们就不用依赖了。

系统本身就有日志业务,一般系统都有日志业务,那么就要结合系统日志业务来编写框架。

首先先确定产生的日志都包含操作名称、日志内容、操作人、操作时间等重要信息,并且某些日志内容还会拿取请求操作入参、出参中的内容,例如:日志内容=管理员登录请求:admin,admin就是获取的当前登录帐号的用户名。这儿的逻辑就靠 Spel 实现~

步骤

1.创建系统日志的注解类,里面参数包含操作名称、日志内容(操作内容是不会修改的、日志内容里面是有需要切换字段的);

2.创建系统日志切面类,这里是要有前置通知和后置通知都有,前置通知记录操作开始时间,然后在后置通知里面得到操作时间(操作开始时间-当前时间),并且通过 Spel 将日志内容的字段内容进行解析转化。日志数据准备完后,调用一个新线程来进行日志存储工作(开新线程不会影响当前线程操作)

注意:该系统将系统日志存储到了ES里面,并没有存到mysql数据库中,所以这里会使用到ES,我们在开发过程中可以暂时不写日志存储业务,在开发ES检索时添加~

//1.创建系统日志的注解类,里面参数包含操作名称、日志内容(操作内容是不会修改的、日志内容里面是有需要切换字段的);

//详见:cn.lili.modules.system.aspect.annotation.SystemLogPoint
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface SystemLogPoint {

    /**
     * 日志名称
     *
     * @return
     */
    String description() default "";

    /**
     * 自定义日志内容
     *
     * @return
     */
    String customerLog() default "";
}

//使用例子,我们开发时可以直接加在管理员登录接口上测试
@Slf4j
@Service
public class AdminUserServiceImpl extends ServiceImpl<AdminUserMapper, AdminUser> implements AdminUserService {
    @Override
    @SystemLogPoint(description = "管理员登录", customerLog = "'管理员登录请求:'+#username")
    public Token login(String username, String password) {
        ...
    }
...
}
//2.创建系统日志切面类,这里是要有前置通知和后置通知都有,前置通知记录操作开始时间(时间要存到ThreadLocal里面,并且在后置通知里买呢要记得清除),然后在后置通知里面得到操作时间(操作开始时间-当前时间),并且通过 Spel 将日志内容的字段内容进行解析转化。日志数据准备完后,调用一个新线程来进行日志存储工作(开新线程不会影响当前线程操作)

//系统日志切面类,详见:cn.lili.modules.system.aspect.interceptor.SystemLogAspect
//Spel工具类,详见:cn.lili.common.utils.SpelUtil
//保存系统日志线程类,详见:cn.lili.modules.system.aspect.interceptor.SystemLogAspect.SaveSystemLogThread
//系统日志存储业务,不是一个类哦,是涉及到service和ElasticsearchRepository,我们可以先把service完成,ES留到开发ES时再写也可以,详见:cn.lili.modules.permission.serviceimpl.SystemLogServiceImpl、cn.lili.modules.permission.repository.SystemLogRepository


//切面类
@Aspect
@Component
@Slf4j
public class SystemLogAspect {

    /**
     * 启动线程异步记录日志
     */
    private static final ThreadLocal<Date> BEGIN_TIME_THREAD_LOCAL = new NamedThreadLocal<>("SYSTEM-LOG");

    @Autowired
    private SystemLogService systemLogService;

    @Autowired
    private HttpServletRequest request;

    @Autowired
    private IpHelper ipHelper;

    /**
     * Controller层切点,注解方式
     */
    @Pointcut("@annotation(cn.lili.modules.system.aspect.annotation.SystemLogPoint)")
    public void controllerAspect() {

    }

    /**
     * 前置通知 (在方法执行之前返回)用于拦截Controller层记录用户的操作的开始时间
     */
    @Before("controllerAspect()")
    public void doBefore() {
        BEGIN_TIME_THREAD_LOCAL.set(new Date());
    }


    /**
     * 后置通知(在方法执行之后并返回数据) 用于拦截Controller层无异常的操作
     * JoinPoint 连接点,可以获取被代理方法的各种信息,如方法参数,方法所在类的class对象
     *
     * @param joinPoint 切点
     */
    @AfterReturning(returning = "rvt", pointcut = "controllerAspect()")
    public void after(JoinPoint joinPoint, Object rvt) {
        try {
            //获取注解中对方法的描述信息,并将内容通过 SPEL 进行解析转化
            Map map = this.spelFormat(joinPoint, rvt);
            String description = map.get("description").toString();
            String customerLog = map.get("customerLog").toString();

            Map<String, String[]> logParams = request.getParameterMap();
            AuthUser authUser = UserContext.getCurrentUser();
            SystemLogVO systemLogVO = new SystemLogVO();

            if (authUser == null) {
                // -2 代表游客
                systemLogVO.setStoreId(-2L);
                //请求用户
                systemLogVO.setUsername("游客");
            } else {
                //如果是商家/买家则记录商家/买家id,否则记录-1,代表平台id
                systemLogVO.setStoreId(authUser.getRole().equals(UserEnums.STORE) ? Long.parseLong(authUser.getStoreId()) : -1);
                //请求用户
                systemLogVO.setUsername(authUser.getUsername());
            }

            //日志标题
            systemLogVO.setName(description);
            //日志请求url
            systemLogVO.setRequestUrl(request.getRequestURI());
            //请求方式
            systemLogVO.setRequestType(request.getMethod());
            //请求参数
            systemLogVO.setMapToParams(logParams);
            //响应参数 此处数据太大了,所以先注释掉
//           systemLogVO.setResponseBody(JSONUtil.toJsonStr(rvt));
            //请求IP
            systemLogVO.setIp(IpUtils.getIpAddress(request));
            //IP地址
            systemLogVO.setIpInfo(ipHelper.getIpCity(request));
            //写入自定义日志内容
            systemLogVO.setCustomerLog(customerLog);
            //请求开始时间
            long beginTime = BEGIN_TIME_THREAD_LOCAL.get().getTime();
            long endTime = System.currentTimeMillis();
            //请求耗时
            Long usedTime = endTime - beginTime;
            systemLogVO.setCostTime(usedTime.intValue());
            //调用线程保存
            ThreadPoolUtil.getPool().execute(new SaveSystemLogThread(systemLogVO, systemLogService));


        } catch (Exception e) {
            log.error("系统日志保存异常", e);
        }finally {
            BEGIN_TIME_THREAD_LOCAL.remove();
        }
    }

    /**
     * 保存日志的线程类
     */
    private static class SaveSystemLogThread implements Runnable {
        @Autowired
        private SystemLogVO systemLogVO;
        @Autowired
        private SystemLogService systemLogService;

        public SaveSystemLogThread(SystemLogVO systemLogVO, SystemLogService systemLogService) {
            this.systemLogVO = systemLogVO;
            this.systemLogService = systemLogService;
        }

        @Override
        public void run() {
            try {
                systemLogService.saveLog(systemLogVO);
            } catch (Exception e) {
                log.error("系统日志保存异常,内容{}:", systemLogVO, e);
            }
        }
    }


    /**
     * 获取注解中对方法的描述信息,并将内容通过 SPEL 进行解析转化
     *
     * @param joinPoint 切点
     * @return 方法描述
     * @throws Exception
     */
    private static Map<String, String> spelFormat(JoinPoint joinPoint, Object rvt) {

        Map<String, String> result = new HashMap<>(2);

        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        SystemLogPoint systemLogPoint = signature.getMethod().getAnnotation(SystemLogPoint.class);

        String description = systemLogPoint.description();
        //调用 sqel 工具,解析转化日志内容
        String customerLog = SpelUtil.compileParams(joinPoint, rvt, systemLogPoint.customerLog());

        result.put("description", description);
        result.put("customerLog", customerLog);
        return result;
    }

}

我们现在进行管理员登录操作时,就会在执行接口之后进去切面,执行系统日志操作~~~但要记得现在还没有存储日志哦

B3.日志整合logstash(待更新)

logstash待学习 ~

剩余内容:ES检索、消息中间件AMQP、定时任务等

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值