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

news2025/7/24 6:00:35

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

【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、定时任务等

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/35454.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【数据聚类】基于粒子群、遗传和差分算法实现数据聚类附matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步精进&#xff0c;matlab项目合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信条&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算法 …

【App自动化测试】(十)特殊控件Toast识别

目录1. toast介绍2. toast定位3. 实例演示前言&#xff1a; 本文为在霍格沃兹测试开发学社中学习到的一些技术写出来分享给大家&#xff0c;希望有志同道合的小伙伴可以一起交流技术&#xff0c;一起进步~ &#x1f618; 1. toast介绍 Toast&#xff0c;简易的消息提示框。为了…

CANdelaStudio-从入门到深入目录

前文介绍诊断协议那些事儿专栏,为大家深入介绍了ISO 14229各个服务的基础知识、请求与响应的报文格式,详情可查看:诊断协议那些事儿,从本专题开始,将由浅入深的展开诊断实际开发与测试的数据库编辑,包含大量实际开发过程中的步骤、使用技巧与少量对Autosar标准的解读。希…

HTML5学习笔记(四)

CSS3 颜色样式 在CSS3中&#xff0c;增加了大量定义颜色方面样式的属性&#xff0c;主要包括以下3种。 ▶ opacity透明度 ▶ RGBA颜色 ▶ CSS3渐变 opacity透明度 opacity属性取值是一个数值&#xff0c;取值范围为0.0~1.0。其中0.0表示完全透明&#xff0c;1.0表示完全不透…

I/O模型

网络IO的本质 网络IO的本质就是socket流的读取&#xff0c;通常一次IO读取会涉及两个阶段与两个对象&#xff0c;其中两个对象为&#xff1a;用户进程&#xff08;线程&#xff09;Process&#xff08;Thread&#xff09;、内核对象&#xff08;kernel&#xff09;,两个阶段为…

北方地区长乐市污水厂(150000m3d)工艺设计

目 录 1设计说明书 3 1.1概述 3 1.1.1设计题目 3 1.1.2设计任务 3 1.1.3设计阶段&#xff08;设计程度&#xff09; 3 1.1.4设计依据 3 1.1.5设计原始资料 3 1.1.6设计工作量 5 1.1.7设计要求 5 1.1.8 毕业设计日期 5 1.2 设计要求 6 1.2.1 设计原则 6 1.2.3 设计内容 6 1.3 水…

PLC中ST编程的比较运算

比较运算符&#xff1a; >大于、 <小于、 >大于等于、 <小于等于、等于、 <>不等于。 BOOL类型的比较是通过1&#xff08;TRUE&#xff09;和0&#xff08;FALSE&#xff09;来比较的&#xff1b; 只有xIn_1为真&#xff0c;xIn_2为假的时候&#xff0c;xRe…

《小猫猫大课堂》1——小喵是如何开启敲代码之路的?

更新不易&#xff0c;麻烦多多点赞&#xff0c;欢迎你的提问&#xff0c;感谢你的转发&#xff0c; 最后的最后&#xff0c;关注我&#xff0c;关注我&#xff0c;关注我&#xff0c;你会看到更多有趣的博客哦&#xff01;&#xff01;&#xff01; 喵喵喵&#xff0c;你对我真…

【Vue基础系列】vue-router 万字详解,一篇彻底搞懂

目录 一、路由的简介 二、路由基本使用 三、嵌套路由 四、路由的query参数 五、路由的params参数 六、路由的props配置 七、编程式路由导航 八、缓存路由组件 九、两个新的生命周期钩子 十、路由守卫 一、路由的简介 我们在生活中经常听到路由器&#xff0c;但关于路由…

将项目部署至云服务器的详细过程 以community项目为例

文章目录1.申请一个2核4G的云服务器&#xff0c;系统选择CentOS 7.62.使用终端连接云服务器3.使用 wget 命令下载以下安装文件4.安装jdk125.安装maven6.安装MySQL7.初始化mysql数据库8.安装Redis9.安装kafka10.安装elasticsearch及其分词工具11.安装Wkhtmltopdf12.安装tomcat13…

【Spring框架】一文带你吃透基于注解的DI技术详细教程

本文目录 文章目录本文目录&#x1f496;基于注解的DI✨概念✨[Component](https://so.csdn.net/so/search?qComponent&spm1001.2101.3001.7020)注解创建对象✨声明组件扫描器✨创建对象的四个注解✨扫描多个包的三种方式✨Value简单类型属性赋值✨Value使用外部属性配置文…

外卖项目07---git

git&#xff1a;企业、公司等 目录 一、Git概述 105 1.1Git简介 105 1.2Git下载与安装 105 二、Git代码托管服务 106 2.1常用的Git代码托管服务 106 三、Git常用命令 107 3.1Git全局配置 3.2获取Git仓库 ​编辑 ​编辑 3.3工作区、暂存区、版本库概念 3.4Git工作…

ASPICE系列:顺利通过ASPICE流程软件单元验证(SWE.4)

上次的ASPICE评估是否出了问题而您不知道原因? 或者您马上要进行第一次评估&#xff1f; 本系列文章是关于如何准备ASPICE流程软件单元验证(SWE.4)评估的。我们探究这个过程&#xff0c;预期交付以及评估人员的观点。永远记住一个想法:怎样做才能成功地通过评估? 想要成功通…

【PdgCntEditor】利用PDF目录书签编辑软件PdgCntEditor为PDF型图书快速添加书签的方法

一、给PDF加书签的两种情况 1.1 文字版PDF添加书签的理想情形 假设我们弄到了一本PDF&#xff0c;这个PDF如果是由Word或WPS转化而来&#xff0c;其中的标题也就代表了目录&#xff0c;我们可以用acrobat PDF中的AutuBookmark插件实现自动识别标题为目录的方法来添加书签。 …

『Java安全』利用反射调用MimeLauncher.run()触发RCE

文章目录前言MimeLauncherrun()MimeLauncher()反射调用MimeLauncher.run()触发RCE条件PoC完前言 rt.jar内的sun.net.www.MimeLauncher类的run方法调用了exec 据说可以有效绕过某些免杀&#xff0c;下面分析一下调用过程 MimeLauncher run() 首先&#xff1a;调用了this.m.ge…

古人的名与字、号、讳、谥有什么区别

古人复杂的名字 这个世界上想来是不存在没有名字的人&#xff0c;即便真的有人没名字&#xff0c;也会被外人赠予姓名&#xff0c;比如说一些古人典籍里的“无名氏”&#xff0c;就是专门用来形容那些没有名字也不清楚根脚的人&#xff0c;即便是现如今一些作品不知道作者是谁…

信号与线性时不变系统的傅里叶描述

1、复正弦信号和线性时不变系统的频率相应 卷积积分和卷积和傅里叶变换冲激表示信号正弦表示信号输入信号表示为延迟冲激的加权叠加输入信号为复正弦信号的加权叠加输出可以用卷积的形式来表示输出可以用傅里叶的形式来表示 (1)频率响应Frequency response 线性时不变系统对正…

Java中mybatis的Mpper代理开发的详细使用步骤

目录 前言&#xff1a; 一、全图预览 二、使用步骤 1.pom.xml里面添加依赖包 2.新建统一配置文件&#xff08;俗称数据库连接文件&#xff09; 3.新建项目 4.新建映射文件&#xff08;俗称数据库对应表xml&#xff09; 5.测试 三、文中的全部代码&#xff08;去复制可…

MySQL如何保证主备一致?

1. MySQL主备的基本原理 如下图展示的是基本的主备切换流程&#xff1a; 在状态1中&#xff0c;主库是A&#xff0c;备库是B&#xff0c;所以客户端的读写都直接方法节点A。由于节点B是节点A的备库&#xff0c;所以备库B只是将A的更新都同步过来&#xff0c;本地执行&#x…

皕杰报表使用字体和部署后添加字体

Windows系统 1、打开Win10系统的字体安装文件夹&#xff0c;可以双击打开此电脑-->打开C盘-->打开Windows-->打开Fonts&#xff1b;也可先打开计算机&#xff0c;在计算机地址栏上直接拷贝“C:WindowsFonts”路径。回车打开Win10字体文件夹。 2.下载自己需要的字体。…