Terminal 4.

temp

2025/11/28
loading

未整理的文件

问题

关于AOP

  1. 如果你在技术派的源码中搜 MdcUtil 的话,可以在 ReqRecordFilter 中找得到,顾名思义,该类是对请求的一个过滤器,会在每个请求中加上全链路的 traceid。

    项目中一整个MdcDot注解都被用来记录方法用时,那么reqrecord中的记录又是在干什么?他有一个.log文件的记录,同时还把这一次的请求时间输出了。

    我想一个适用于普通方法,而req的这个是在filter里专门记录请求的时间,可能和那个方法不兼容?

    还有一个问题,两个方法都用的mdcutil里的方法,这个traceid没有覆盖的可能吗?

    1. MdcDot 与 ReqRecord 的区别?traceId 会不会互相覆盖?

    你已经大致猜对了,但我帮你补充成一个完整逻辑:

    1)MdcDot 用来记录方法执行耗时

    • 这是一个 AOP 注解(作用在某个方法上)
    • AOP 拦截方法 → 进入前记录时间 → 退出后输出日志
    • 一般属于业务级别的方法耗时监控(精确到某个 service 方法)

    例如:
    UserService.getUserById() 花了 230ms
    OrderService.create() 花了 1.2s

    这些属于应用内部的性能监控。

    2)ReqRecord(你看到的 req 处理)是 Filter 层的请求级日志

    这段逻辑在 Filter 或 Interceptor 里:

    • 进入请求:建立 traceId、记录用户信息、记录路径、IP、UA
    • 完成请求:统一输出一次完整请求日志(写入 .log)

    这是每次 HTTP 请求的入口 / 出口监控
    也就是“这次 API 调用持续了多久”。

    例如:
    GET /api/user/info cost=156ms
    POST /order/create cost=1220ms

    该日志是更偏向运维监控、流量统计、接口质量监控

    3)两个都用 MdcUtil,会不会覆盖 traceId?

    不会。

    原因:

    • traceId 是在 filter 入口时统一设置的
    • AOP 方法执行时只会使用这个 traceId,不会重新生成
    • MdcUtil.addTraceId() 基本格式就是:“如果 MDC 中没有 traceId 才生成”

    所以一个请求有一个 traceId,所有方法耗时都共享它。

  2. 这个项目细分了好多类,他还有一个Dao,专门和service区分开来了。@Respository和@Service 的区别只是标记不一样吗

    2. Repository 与 Service 的区别?什么时候分层?

    不是标签不同而已。

    Repository(DAO)职责

    只负责与数据库交互

    • CRUD
    • 自定义 SQL
    • 查询分页
    • 统计 SQL

    Repository/mapper 层不包含任何业务逻辑。

    Service 的职责

    封装业务逻辑

    • 参数校验
    • 领域逻辑组合
    • 调用多个 DAO / RPC
    • 事务控制
    • 缓存控制

    什么时候需要分?

    当你有以下需求时必须分:

    • 业务需要组合多个数据库表的操作
    • 一个请求涉及多个操作(比如先查用户再查订单)
    • 需要保证事务(@Transactional)
    • 需要隐藏数据库细节(controller 不应该直接写 SQL)

    你之前的项目没有 DAO 层,是因为业务太简单

    所以说,之前那个项目有service层用其他service的情况,这种情况其实应该使用dao吗?

  3. 用service CRUD 和Mapper CRUD 的区别在?本来serviceimpl这个继承的类里就要求mapper继承了basemapper的,那用哪个有说法吗?我想了想可能是因为:用service的肯定是controller 层 ,controller层不可能去调用mapper层的东西的,即使调用的service其实本质上还是mapper,而dao(service)层就可以做基本CURD做不到的事情?

    3. 为什么 MyBatis-Plus 要提供 Service CRUD?和直接在 Service 调 Mapper 有什么区别?

    你问的这个问题非常关键:
    “既然 Controller 能调 Service,那为什么 Service 不自己写 CRUD,而要依赖 MP 提供的 ServiceImpl 的 CRUD?”

    我拆成 3 点,你看了就懂了。


    ① MyBatis-Plus 的 Service CRUD 是通用业务服务层的模板

    MP 的设计理念:

    • Mapper 层只负责数据库 CRUD(原子、低级别操作)
    • Service 层需要在 Mapper 的基础上补充业务流程逻辑
    • 绝大多数业务都会需要标准化的 CRUD(分页、按条件查询、批量删除……)

    因此 MP 提供了 IService + ServiceImpl,目的是:

    • 你不用每个项目都重复写:list、getById、removeById、save、update……
    • 这些方法自动配合 MP 的 QueryWrapper、分页插件、自动填充等
    • 代码统一规范、减少重复性工作

    也就是说,它做的是“中间层简化”和“业务层能力增强”。


    ② 你完全可以不继承 IService,但你会多写大量重复代码

    假设你不用 MyBatis-Plus 的 IService,你会写成这样:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    public class UserService {
    @Autowired
    private UserMapper mapper;

    public User getById(Long id) {
    return mapper.selectById(id);
    }

    public List<User> list() {
    return mapper.selectList(null);
    }

    public void save(User u) {
    mapper.insert(u);
    }

    public void update(User u) {
    mapper.updateById(u);
    }

    public void delete(Long id) {
    mapper.deleteById(id);
    }
    }

    这是一堆重复工作,而且不同开发者写出的接口风格可能完全不一致。

    MP 帮你统一成:

    1
    2
    @Service
    public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {}

    然后你可以直接用:

    1
    2
    3
    4
    userService.getById(id);
    userService.list();
    userService.save(entity);
    userService.removeById(id);

    你几乎不用写额外代码,即可保证所有项目统一风格。

  4. 为什么这里要用@Param注释?不加不可以吗?这是mybtis就有的还是plus才有的?这样的话在yml设置里开启驼峰命名的意义在?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public interface UserMapper extends BaseMapper<UserDO> {
    /**
    * 根据三方唯一id进行查询
    *
    * @param accountId
    * @return
    */
    @Select("select * from user where third_account_id = #{account_id} limit 1")
    UserDO getByThirdAccountId(@Param("account_id") String accountId);
    }

    情况1:编译器保留了参数名(默认情况自 JDK8 后大多数 IDE 会开启)

    MyBatis 可以直接识别:

    1
    UserDO getByThirdAccountId(String accountId);

    此时可以写:

    1
    #{accountId}

    无需 @Param。


    情况2:项目没有启用参数名保留功能(某些环境未开启 -parameters)

    MyBatis 拿到的参数名会变成:

    1
    arg0, arg1, arg2...

    你的方法:

    1
    UserDO getByThirdAccountId(String accountId)

    在运行期可能变成:

    1
    arg0

    这时你 SQL 写:

    1
    #{accountId}

    就会报错:找不到 accountId。

    ==> 因此为了防止环境不一致,团队规范通常建议:多参数和注解 SQL 要使用 @Param。

  5. TableId(type = IdType.AUTO),在数据库本来就设置了自增的情况下,还用这个注解让他在插入的时候自增其实没什么意义吧?哦,等等,它的数据库都没有设置约束,约束全在mybatis里,这样的好处在?

    5. @TableId(type = IdType.AUTO) 为何数据库不建约束?

    MP 默认通过注解控制主键策略,数据库里不建约束有两个好处:

    1)跨数据库兼容

    MySQL, PostgreSQL, Oracle 都有不同的自增写法
    MP 用注解帮你屏蔽差异。

    2)方便迁移(比如切换到雪花算法)

    你只改 Java 注解,不需要改表结构。

    项目越大越喜欢用 Java 控制,而非 DBA 控制。

  6. 为何这个是用的传统渲染页面?

  7. ```java
    @Controller
    public class IndexController extends BaseViewController {

     @Autowired
     private IndexRecommendHelper indexRecommendHelper;
    
     @GetMapping(path = {"/", "", "/index", "/login"})
     public String index(Model model, HttpServletRequest request) {
         String activeTab = request.getParameter("category");
         IndexVo vo = indexRecommendHelper.buildIndexVo(activeTab);
         model.addAttribute("vo", vo);
         return "views/home/index";
     }
    

    }

    1
    2
    3
    4
    5
    6
    7
    8
    9



    > ## 为什么不直接在 Controller 调 Service?
    >
    > 这是你问得最**专业**的点。
    >
    > ### ❌ 反模式(Controller 直接堆 Service)
    >

    ArticleService
    TagService
    UserService
    RecommendService

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    Controller 会变成:

    - 巨大
    - 难维护
    - 逻辑混乱
    - 改一处牵一堆

    ------

    ### ✅ 这里采用的是「Helper / Facade 层」

    Controller

    IndexRecommendHelper ← 聚合逻辑

    多个 Service

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33

    `IndexRecommendHelper` 的职责是:

    > **“为某一个页面 / 场景,组装完整的业务数据”**

    ------

    ## 五、这样设计的好处(非常重要)

    ### 1️⃣ Controller 极度干净

    Controller 只做 3 件事:

    1. 接收请求
    2. 调用场景服务
    3. 返回视图

    ------

    ### 2️⃣ Service 保持“单一业务职责”

    - ArticleService:文章
    - TagService:标签
    - UserService:用户

    不污染 Service 层

    ------

    ### 3️⃣ 页面级逻辑可以复用

    今天是:

    buildIndexVo()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44

    明天可能是:

    - PC 首页
    - 移动端首页
    - 小程序首页

    **Helper 可以复用,Controller 各自不同**

    ------

    ### 4️⃣ 非常适合「复杂页面」

    首页通常是:

    - 数据多
    - 来源多
    - 规则多
    - 经常改

    Helper / Facade 是最合理的位置。

    ------

    ## 六、你可以这样理解它的角色

    | 层级 | 角色 |
    | --------------- | ------------------ |
    | Controller | HTTP + 路由 + 视图 |
    | Helper / Facade | 页面级业务编排 |
    | Service | 单一领域业务 |
    | VO | 页面数据模型 |

    ------

    ## 七、一句话总结(面试级)

    > 这段代码是一个 **MVC 页面 Controller**,
    > 它不直接调用 Service,而是通过 Helper 聚合首页所需的业务数据,
    > 最终将 VO 注入 Model 并返回视图路径。
    > 这种设计降低了 Controller 复杂度,提高了页面逻辑的可维护性和复用性。

    Spring MVC 的流程是:

    Controller
    ↓ 返回 “views/home/index”
    ViewResolver
    ↓ 拼接前缀 + 后缀
    模板引擎(Thymeleaf / JSP)
    ↓ 渲染 HTML
    浏览器收到 HTML

    1
    2
    3

    例如(常见配置):

    spring.thymeleaf.prefix=classpath:/templates/
    spring.thymeleaf.suffix=.html

    1
    2
    3

    那么:

    return “views/home/index”;

    1
    2
    3

    最终渲染的是:

    classpath:/templates/views/home/index.html

    1
    2
    3
    4
    5



    ## 1️⃣ template.execute(...) —— 为什么要这么写?

    return template.execute(new RedisCallback>>() {

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48

    ### 这是在干什么?

    - `template` 是 `RedisTemplate`
    - `execute` 允许你:
    - **直接拿到底层 RedisConnection**
    - 使用 Redis 的原生命令(性能更好、功能更全)

    ### 为什么不用 `redisTemplate.opsForZSet()`?

    因为:

    - `zRangeWithScores` 在某些版本/泛型下不好用
    - 直接用 `RedisConnection` **最稳定、最贴近 Redis 原生命令**

    👉 **这是偏“底层工具类”的写法**





    | 数据结构 | 核心能力 | 最典型用途 |
    | -------- | ----------- | -------------- |
    | String | 单值 / 计数 | 缓存、计数、锁 |
    | List | 有序 | 队列、历史 |
    | Set | 去重 | 白名单、点赞 |
    | ZSet | 排序 | 排行榜 |
    | Hash | 结构化 | 对象缓存 |



    ```java
    @Test
    public void testExecute() {
    // 使用 execute 方法执行 Redis 命令
    redisTemplate.execute(new RedisCallback<Object>() {
    @Override
    public Object doInRedis(RedisConnection connection) throws DataAccessException {
    // 执行 Redis 命令,例如 set 和 get 命令
    connection.set("itwanger".getBytes(), "沉默王二".getBytes());
    byte[] value = connection.get("itwanger".getBytes());
    String strValue = new String(value);
    // 输出获取到的值
    System.out.println(strValue);
    return null;
    }
    });
    }

我草吓哭了

  • 构建工具:后端(Maven、Gradle)、前端(Webpack、Vite)
  • 单元测试:Junit
  • 开发框架:SpringMVC、Spring、Spring Boot
  • Web 服务器:Tomcat、Caddy、Nginx
  • 微服务:Spring Cloud
  • 数据层:JPA、MyBatis、MyBatis-Plus
  • 模板引擎:thymeleaf
  • 容器:Docker(镜像仓库服务 Harbor、图形化工具 Portainer)、k8s、Podman
  • 分布式 RPC 框架:Dubbo
  • 消息队列:Kafka(图形化工具 Eagle)、RocketMQ、RabbitMQ、Pulsar
  • 持续集成:Jenkins、Drone
  • 压力测试:Jmeter
  • 数据库:MySQL(数据库中间件 Gaea、同步数据 canal、数据库迁移工具 Flyway)
  • 缓存:Redis(增强模块 RedisMod、ORM 框架 RedisOM)
  • nosql:MongoDB
  • 对象存储服务:minio
  • 日志:Log4jLogbackSF4JLog4j2
  • 搜索引擎:ES
  • 日志收集:ELK(日志采集器 Filebeat)、EFK(Fluentd)、LPG(Loki+Promtail+Grafana)
  • 大数据:Spark、Hadoop、HBase、Hive、Storm、Flink
  • 分布式应用程序协调:Zookeeper
  • token 管理:jwt(nimbus-jose-jwt)
  • 诊断工具:arthas
  • 安全框架:Shiro、SpringSecurity
  • 权限框架:Keycloak、Sa-Token
  • JSON 处理:fastjson2、JacksonGson
  • office 文档操作:EasyPoi、EasyExcel
  • 文件预览:kkFileView
  • 属性映射:mapStruct
  • Java 硬件信息库:oshi
  • Java 连接 SSH 服务器:ganymed
  • 接口文档:Swagger-ui、Knife4j、Spring Doc、Torna、YApi
  • 任务调度框架:Spring Task、Quartz、PowerJob、XXL-Job
  • Git 服务:Gogs
  • 低代码:LowCodeEngine、Yao、Erupt、magic-api
  • API 网关:Gateway、Zuul、apisix
  • 数据可视化(Business Intelligence,也就是 BI):DataEase、Metabase
  • 项目文档:Hexo、VuePress
  • 应用监控:SpringBoot Admin、Grafana、SkyWalking、Elastic APM
  • 注解:lombok
  • jdbc 连接池:Druid
  • Java 工具包:hutool、Guava
  • 数据检查:hibernate validator
  • 代码生成器:Mybatis generator
  • Web 自动化测试:selenium
  • HTTP 客户端工具:Retrofit
  • 脚手架:sa-plus

CATALOG
  1. 1. 问题
    1. 1.1. 关于AOP
  2. 2. 1. MdcDot 与 ReqRecord 的区别?traceId 会不会互相覆盖?
    1. 2.1. 1)MdcDot 用来记录方法执行耗时
    2. 2.2. 2)ReqRecord(你看到的 req 处理)是 Filter 层的请求级日志
    3. 2.3. 3)两个都用 MdcUtil,会不会覆盖 traceId?
  3. 3. 2. Repository 与 Service 的区别?什么时候分层?
    1. 3.1. Repository(DAO)职责
    2. 3.2. Service 的职责
    3. 3.3. 什么时候需要分?
  4. 4. 3. 为什么 MyBatis-Plus 要提供 Service CRUD?和直接在 Service 调 Mapper 有什么区别?
    1. 4.1. ① MyBatis-Plus 的 Service CRUD 是通用业务服务层的模板
    2. 4.2. ② 你完全可以不继承 IService,但你会多写大量重复代码
      1. 4.2.1. 情况1:编译器保留了参数名(默认情况自 JDK8 后大多数 IDE 会开启)
      2. 4.2.2. 情况2:项目没有启用参数名保留功能(某些环境未开启 -parameters)
  5. 5. 5. @TableId(type = IdType.AUTO) 为何数据库不建约束?
    1. 5.1. 1)跨数据库兼容
    2. 5.2. 2)方便迁移(比如切换到雪花算法)
  6. 6. 我草吓哭了