Java 项目实战:交友项目 - 保存访客记录

概述

交友项目的「我的访客」功能要落地,第一步就是把访客数据真正存进 MongoDB。这里只讲一件事:在查看用户详情的接口里,顺手把访客记录写下来,触发点在哪、Visitors 对象怎么构建、缘分值 score 从哪来、一天只记一次的去重怎么实现。

纲要

  • 需求回顾与设计特点:为什么访客记录挂在「读接口」上
  • 文件清单:涉及的 5 个文件(表格 + 目录树)
  • 完整代码链路:
  • 触发点:mfController.personalInfo → mfService.personalInfo
  • 实体:mf-model 的 Visitors
  • Dubbo 接口与实现:VisitorsApi / VisitorsApiImpl.save
  • score 缘分值的传递:冗余换查询效率
  • 去重策略:同一用户一天只记一次的三种工程选择
  • 时序图:查看主页 → 记录访客 → 被访者列表查询
  • 踩坑记录与验证步骤

需求回顾与设计特点「我的访客」的需求是:其他用户访问我的动态、视频、个人信息时,记录访客数据;首页展示最新 5 条访客,「我的」页面展示全部历史记录。

需求描述里有一句容易被忽略的关键约束:

注意:记录访客数据,在查询用户详情的接口中记录即可,不需要额外的接口提供。

也就是说,记录访客这个「写」动作,寄生在「查看用户详情」这个「读」接口里 。用户在前端点开一个推荐用户的详情页,后端返回详情的同时,悄悄写一条访客记录。没有独立的「上报访客」接口。

这个设计有两个直接后果:

  1. 前端零改造:APP 不需要新增埋点或上报逻辑,访客统计天然覆盖所有「点开详情」的行为。
  2. 读路径上多了一次同步写:详情接口的响应时间里混入了 Dubbo 调用 + MongoDB 写入的耗时。生产环境上应该把这段保存逻辑异步化(丢线程池或发 MQ),主流程只管查详情返回。教学项目里为了链路清晰是同步写的,这个性能影响先点到为止,后面的坑表里还会提。

文件清单

模块 文件 职责
mf-model model/mongo/Visitors.java 访客记录 Mongo 实体,映射 visitors 集合
mf-app-server server/controller/mfController.java 查看用户详情的 HTTP 入口
mf-app-server server/service/mfService.java 构建 Visitors 对象,调用 Dubbo API 保存
mf-dubbo-interface dubbo/api/VisitorsApi.java 定义保存访客数据的 Dubbo 接口
mf-dubbo-mongo dubbo/api/VisitorsApiImpl.java 保存实现:查重 + 落库
目录树:
tree 复制代码
mf
├── mf-model
│   └── src/main/java/com/mf/model/mongo
│       └── Visitors.java                # 访客记录实体
├── mf-app-server
│   └── src/main/java/com/mf/server
│       ├── controller
│       │   └── mfController.java    # GET /{id}/personalInfo 触发点
│       └── service
│           └── mfService.java       # 构建 Visitors 并调用保存
└── mf-dubbo
    ├── mf-dubbo-interface
    │   └── src/main/java/com/mf/dubbo/api
    │       └── VisitorsApi.java         # Dubbo 接口定义
    └── mf-dubbo-mongo
        └── src/main/java/com/mf/dubbo/api
            └── VisitorsApiImpl.java     # 去重 + 落库实现

触发点:查看用户详情接口

先看 HTTP 入口。访问他人主页走的是这个接口:

java 复制代码
package com.mf.server.controller;

import com.mf.model.vo.TodayBest;
import com.mf.server.service.mfService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/mf")
public class mfController {

    @Autowired
    private mfService mfService;

    /**
     * 查看用户详情
     */
    @GetMapping("/{id}/personalInfo")
    public ResponseEntity personalInfo(@PathVariable("id") Long userId) {
        TodayBest best = mfService.personalInfo(userId);
        return ResponseEntity.ok(best);
    }
}

Controller 本身不含任何访客逻辑,一行改动都没有,所有工作都在 mfService.personalInfo 里。这正是「不需要额外接口」的落点:改造发生在已有方法的内部。

实体:Visitors

visitors 集合对应的实体放在 mf-model,字段与集合结构一一对应:

java 复制代码
package com.mf.model.mongo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.bson.types.ObjectId;
import org.springframework.data.mongodb.core.mapping.Document;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Document(collection = "visitors")
public class Visitors implements java.io.Serializable {

    private static final long serialVersionUID = 2811682148052386573L;

    private ObjectId id;
    private Long userId;         // 我的id(被访者)
    private Long visitorUserId;  // 来访用户id(访客,当前操作人)
    private String from;         // 来源,如首页、圈子等
    private Long date;           // 来访时间(毫秒数)
    private String visitDate;    // 来访日期,格式 yyyyMMdd
    private Double score;        // 得分(缘分值)
}

字段语义要盯紧,这是最容易搞反的地方:

字段 含义 本例取值
userId 被访者(主页的主人) 路径参数 userId
visitorUserId 访客(谁来看的) UserHolder.getUserId(),当前登录人
date 精确时间戳,用于列表按时间倒序 System.currentTimeMillis()
visitDate 日期字符串 yyyyMMdd,用于按天去重 new SimpleDateFormat("yyyyMMdd").format(new Date())
score 两人之间的缘分值快照 从 RecommendUser 查来

date 和 visitDate 看似重复,其实各司其职:前者服务「展示最新访客」的排序,后者服务「一天只记一次」的去重判断。用一个毫秒数去判断「是否同一天」很别扭,冗余一列日期字符串是最省事的做法。

核心:mfService.personalInfo 的保存逻辑

mfService 里注入了 VisitorsApi(Dubbo 引用),personalInfo 方法在查完详情后构建访客数据并保存。以下是与工程实读一致的完整代码:

java 复制代码
package com.mf.server.service;

import com.mf.dubbo.api.UserInfoApi;
import com.mf.dubbo.api.VisitorsApi;
import com.mf.model.domain.UserInfo;
import com.mf.model.mongo.RecommendUser;
import com.mf.model.mongo.Visitors;
import com.mf.model.vo.TodayBest;
import com.mf.server.interceptor.UserHolder;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.stereotype.Service;

import java.text.SimpleDateFormat;
import java.util.Date;

@Service
public class mfService {

    @DubboReference
    private UserInfoApi userInfoApi;

    @DubboReference
    private RecommendUserApi recommendUserApi;

    @DubboReference
    private VisitorsApi visitorsApi;

    //查看佳人详情
    public TodayBest personalInfo(Long userId) {
        //1、根据用户id查询用户详情
        UserInfo userInfo = userInfoApi.findById(userId);
        //2、根据操作人id和查看的用户id,查询两者的推荐数据
        RecommendUser user = recommendUserApi.queryByUserId(userId, UserHolder.getUserId());

        //构造访客数据,调用API保存
        Visitors visitors = new Visitors();
        visitors.setUserId(userId);
        visitors.setVisitorUserId(UserHolder.getUserId());
        visitors.setFrom("首页");
        visitors.setDate(System.currentTimeMillis());
        visitors.setVisitDate(new SimpleDateFormat("yyyyMMdd").format(new Date()));
        visitors.setScore(user.getScore());
        visitorsApi.save(visitors);

        //3、构造返回值
        return TodayBest.init(userInfo, user);
    }
}

逐行拆开看三个关键点:

① 两个 id 的方向 。setUserId(userId) 存的是路径参数,即被看的人;setVisitorUserId(UserHolder.getUserId()) 存的是当前登录人。逻辑很直白:「谁来看了谁」,看的人在 visitorUserId,被看的人在 userId。后面「首页谁看过我」的查询就是拿 userId = 我 去捞谁来看过我,方向反了整个功能就废了。

② from 来源 。当前只有从首页访问这一条路径,所以写死 "首页"。字段预留了扩展空间,后续从圈子、动态页进来的访问可以带上不同来源。

③ score 的来源 。user.getScore() 里的 user 是第 2 步查出来的 RecommendUser,也就是说缘分值不是实时计算,而是直接复用推荐系统已经算好的结果。这一步是本节设计的精髓,单独展开。

Dubbo 接口与实现:VisitorsApi / VisitorsApiImpl

跨模块保存走 Dubbo。先在 mf-dubbo-interface 定义接口:

java 复制代码
package com.mf.dubbo.api;

import com.mf.model.mongo.Visitors;

public interface VisitorsApi {

    //保存访客数据
    void save(Visitors visitors);
}

实现放在 mf-dubbo-mongo,操作 MongoDB:

java 复制代码
package com.mf.dubbo.api;

import com.mf.model.mongo.Visitors;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;

@DubboService
public class VisitorsApiImpl implements VisitorsApi {

    @Autowired
    private MongoTemplate mongoTemplate;

    /**
     * 保存访客数据
     *  对于同一个用户,一天之内只能保存一次访客数据
     */
    public void save(Visitors visitors) {
        //1、查询访客数据
        Query query = Query.query(Criteria.where("userId").is(visitors.getUserId())
                .and("visitorUserId").is(visitors.getVisitorUserId())
                .and("visitDate").is(visitors.getVisitDate()));
        //2、不存在,保存
        if (!mongoTemplate.exists(query, Visitors.class)) {
            mongoTemplate.save(visitors);
        }
    }
}

实现很短,但三个条件一个都不能少:

  • userId:被访者
  • visitorUserId:访客
  • visitDate:访问日期(yyyyMMdd 字符串)

「同一天同一人对同一人只记一次」这个语义,就是靠 userId + visitorUserId + visitDate 三元组唯一来保证的。mongoTemplate.exists() 只判断存在性,不拉数据,比 findOne 省一次反序列化。

score 缘分值的传递:冗余换查询效率

为什么访客记录里要存 score?查询时再去 recommend_user 集合现算不行吗?

不行,也不划算。看这两个集合的数据面:

对比项 recommend_user visitors
数据规模 每个用户对每个候选人都有一条,量大 只有点开详情才产生,量小
score 含义 实时推荐计算的缘分值 访问发生那一刻的缘分值快照
查询场景 推荐列表(按 toUserId 查) 访客列表(按 userId 查)

「首页谁看过我」的列表页要给每个访客展示缘分值,如果查询时再拿 userId + visitorUserId 回 recommend_user 联查,列表 5 条就是 5 次额外查询,而且两次查询分属两个集合,联查在 MongoDB 里意味着应用层手动组装(这就是前面章节说的跨存储组装问题)。

把 score 随访客记录一起存下来,是典型的冗余换查询效率:写入时多存一个字段,读取时免掉一整轮跨集合查询。访客记录本身是快照性质的数据(记录的是「访问那一刻」的缘分值),这个冗余在语义上也站得住。

这里顺带回答一个实读中确认的细节,recommendUserApi.queryByUserId 查不到推荐数据时怎么办:

java 复制代码
@Override
public RecommendUser queryByUserId(Long userId, Long toUserId) {
    Criteria criteria = Criteria.where("toUserId").is(toUserId).and("userId").is(userId);
    Query query = Query.query(criteria);
    RecommendUser user = mongoTemplate.findOne(query, RecommendUser.class);
    if (user == null) {
        user = new RecommendUser();
        user.setUserId(userId);
        user.setToUserId(toUserId);
        //构建缘分值
        user.setScore(95d);
    }
    return user;
}

推荐数据缺失时兜底给 95d。这样保证了 personalInfo 里的 user.getScore() 永远不会拿到 null,下游的访客记录也永远有 score 值。代价是数据库里会出现一批 score=95 的「假缘分值」,教学项目可接受,生产环境更稳的做法是 personalInfo 里对 user 判空后走默认值,而不是在查询方法里悄悄造数据。

去重策略:三种工程选择

VisitorsApiImpl.save 的策略是当天去重:同一天内同一访客对同一被访者只记一条,第二天再访问则新增一条。这是课程给定的业务要求。实际工程里还有另外两种常见选择:

策略 行为 实现 适用场景
每次都记 每次访问新增一条,数据量随访问次数线性增长 直接 save,无查重 需要精确统计访问次数/PV
当天去重(本项目) 一天一条,多条只留首条 exists(userId + visitorUserId + visitDate) 后再 save 「谁看过我」列表,只关心谁哪天来过
覆盖更新 一人一条,重复访问只刷新时间 先 findOne,存在则 update date,否则 insert 关心「最近一次访问时间」,控数据量

三者的取舍点在数据量 和信息量之间:

  • 每次都记信息最全,但一个热门用户一天被刷几百次详情页,visitors 集合会被灌爆,列表查询还得再去重。
  • 覆盖更新数据量最省(每人一条),但丢失了「每天都来过」的历史信息,且存在先查后写的并发窗口,高并发下可能插入两条。
  • 当天去重是折中:既保留了「每天来过」的维度,又把单日重复访问压成一条。

本项目的实现还有个小坑:exists + save 是两步操作,不是原子的。同一用户极短时间内连点两次详情,理论上可能两个请求都通过了 exists 检查、各写一条。教学场景无所谓,生产上可以给三元组建唯一索引兜底(db.visitors.createIndex({userId:1, visitorUserId:1, visitDate:1}, {unique:true})),重复写入直接被索引拦下。

时序图

把整条链路串起来:
MongoDB VisitorsApiImpl RecommendUserApi mfService mfController APP(访客) MongoDB VisitorsApiImpl RecommendUserApi mfService mfController APP(访客) #mermaid-svg-NE3NemUdGZdBZMin{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-NE3NemUdGZdBZMin .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-NE3NemUdGZdBZMin .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-NE3NemUdGZdBZMin .error-icon{fill:#552222;}#mermaid-svg-NE3NemUdGZdBZMin .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-NE3NemUdGZdBZMin .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-NE3NemUdGZdBZMin .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-NE3NemUdGZdBZMin .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-NE3NemUdGZdBZMin .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-NE3NemUdGZdBZMin .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-NE3NemUdGZdBZMin .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-NE3NemUdGZdBZMin .marker{fill:#333333;stroke:#333333;}#mermaid-svg-NE3NemUdGZdBZMin .marker.cross{stroke:#333333;}#mermaid-svg-NE3NemUdGZdBZMin svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-NE3NemUdGZdBZMin p{margin:0;}#mermaid-svg-NE3NemUdGZdBZMin .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-NE3NemUdGZdBZMin text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-NE3NemUdGZdBZMin .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-NE3NemUdGZdBZMin .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-NE3NemUdGZdBZMin .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-NE3NemUdGZdBZMin .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-NE3NemUdGZdBZMin #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-NE3NemUdGZdBZMin .sequenceNumber{fill:white;}#mermaid-svg-NE3NemUdGZdBZMin #sequencenumber{fill:#333;}#mermaid-svg-NE3NemUdGZdBZMin #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-NE3NemUdGZdBZMin .messageText{fill:#333;stroke:none;}#mermaid-svg-NE3NemUdGZdBZMin .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-NE3NemUdGZdBZMin .labelText,#mermaid-svg-NE3NemUdGZdBZMin .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-NE3NemUdGZdBZMin .loopText,#mermaid-svg-NE3NemUdGZdBZMin .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-NE3NemUdGZdBZMin .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-NE3NemUdGZdBZMin .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-NE3NemUdGZdBZMin .noteText,#mermaid-svg-NE3NemUdGZdBZMin .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-NE3NemUdGZdBZMin .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-NE3NemUdGZdBZMin .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-NE3NemUdGZdBZMin .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-NE3NemUdGZdBZMin .actorPopupMenu{position:absolute;}#mermaid-svg-NE3NemUdGZdBZMin .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-NE3NemUdGZdBZMin .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-NE3NemUdGZdBZMin .actor-man circle,#mermaid-svg-NE3NemUdGZdBZMin line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-NE3NemUdGZdBZMin :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} alt当日记录不存在当日记录已存在 之后:被访者打开"谁看过我"按 userId 查 visitors,score 直接展示无需联查 GET /mf/{id}/personalInfopersonalInfo(userId)queryByUserId(userId, 当前用户id)查 recommend_user 集合RecommendUser(含 score,无则兜底 95d)RecommendUser构建 Visitors(userId/visitorUserId/from/date/visitDate/score)save(visitors)exists(userId + visitorUserId + visitDate)?save(visitors) 写入 visitors 集合跳过保存TodayBest佳人详情 JSON

踩坑记录

坑 现象 处理
读路径上的同步写 详情接口响应时间混入 Dubbo + Mongo 写入耗时,热点用户主页被频繁刷新时放大 生产上把 visitorsApi.save 异步化(线程池 / MQ),这里教学代码保持同步便于调试
两个 id 写反 userId 存成了访客自己,「谁看过我」查出来说「我看过谁」 记牢:userId=被访者(主页主人),visitorUserId=当前操作人
score 取不到 null 新注册用户互无推荐数据时,queryByUserId 返回 null,getScore() NPE 课程方案:queryByUserId 内部兜底 score=95d;更稳妥做法是调用方判空
自己看自己 用户点开自己主页也记一条访客,「谁看过我」里出现自己 可在 personalInfo 开头判断 userId.equals(UserHolder.getUserId()) 则跳过保存,课程代码未做此处理
exists + save 非原子 极端并发下同一天可能写入两条重复记录 给 userId + visitorUserId + visitDate 建唯一索引兜底
日期格式不统一 visitDate 一处用 yyyyMMdd 一处用 yyyy-MM-dd,去重永远失效 格式收口到常量或工具类,全项目统一 yyyyMMdd

验证

复现课程测试步骤:

  1. 启动 MongoDB、Dubbo(dubbo-mongo 工程)、mf-app-server。
  2. 用 106 号用户登录 APP,从首页点开 2 号用户的详情页。
  3. mongosh 查看访客记录:
javascript 复制代码
// 切换到 mf 库
use mf

// 查看刚写入的访客记录,确认 userId=2、visitorUserId=106、score 有值
db.visitors.find().pretty()
// 预期输出(节选):
// {
//   "_id": ObjectId("..."),
//   "userId": NumberLong("2"),
//   "visitorUserId": NumberLong("106"),
//   "from": "首页",
//   "date": NumberLong("1635..."),
//   "visitDate": "20260930",
//   "score": 86.0
// }
  1. 验证去重 :同一账号当天内再次访问同一详情页,重复第 3 步,记录条数不变;把其中一条的 visitDate 手动改成昨天再访问,会新增一条:
javascript 复制代码
// 构造"昨天"条件,确认第二天可以再次记录
db.visitors.countDocuments({ userId: NumberLong("2"), visitorUserId: NumberLong("106"), visitDate: "20260930" })
// 连续访问多次后仍应为 1
  1. 被访者(2 号)登录,调「谁看过我」相关接口,确认访客列表里出现 106,且缘分值直接来自 visitors.score,没有回查 recommend_user。

常见误解澄清

误解 事实
记录访客需要前端单独上报 不需要,写在查看详情接口内部,前端零改动
score 是保存访客时实时算出来的 不是,直接取推荐系统已算好的 recommend_user.score,缺失时兜底 95
userId 是「我」(当前登录人) 反了。userId 是被看的人,visitorUserId 才是当前操作人
同一访客的记录全局只有一条 只有「同一天」内唯一,跨天会新增记录(visitDate 变了)
去重判断用了 date 时间戳 用的是 visitDate 日期字符串,date 只负责列表排序

总结

  • 访客记录挂在 GET /mf/{id}/personalInfo 读接口内部完成,无需独立接口,代价是读路径上多一次同步写,生产环境应异步化。
  • 保存链路:mfService.personalInfo 构建 Visitors → Dubbo 调用 VisitorsApi.save → VisitorsApiImpl 按 userId + visitorUserId + visitDate 查重后落 MongoDB。
  • score 随记录冗余存储,让「谁看过我」列表免于跨集合联查 recommend_user,是冗余换查询效率的典型应用。
  • 去重采用「当天去重」策略,在数据量与信息量之间取平衡;并发严格场景用唯一索引兜底。

下一篇同章内容是首页谁看过我:思路分析,基于这里写入的 visitors 数据实现访客列表查询(含「上次查看之后的新访客」的增量逻辑)。

相关推荐
liangbo71 小时前
Spring系列01:IoC 与 DI 到底解决了什么
java·spring
江湖中的阿龙1 小时前
Oracle 紧急安全警报:Java 9.1 分漏洞 CVE-2026-91403,撤销对 eMudhra 风险证书的信任
java·安全·oracle
挖掘狂人1 小时前
缓存和数据库的一致性,别停在 “延时双删”
java·后端·程序员
codigger2 小时前
缓存和数据库的一致性,别停在 “延时双删”
java·后端·程序员·后端开发
Java后端的Ai之路2 小时前
Java并发编程07-等待通知机制优化循环等待
java·数据库·python
Raas1002 小时前
AI网关如何接入?MAI Gateway部署实操教程
java·开发语言·人工智能·gateway·ai网关·mai gateway·企业级产品
小小龙学IT2 小时前
Go 语言 pprof 性能剖析与调优深度解析:把“卡在哪“变成一张可执行的数据图
开发语言·后端·golang
前端双越老师2 小时前
程序员 1v1 访谈系列第十一期:应届生别做开发
java·agent·ai编程
Captain1314_李祖团2 小时前
5. MLIR相关语法
java·前端·javascript