许苑园 一一 寻找共同兴趣的伙伴项目笔记

许苑园 一一 寻找共同兴趣的伙伴项目笔记

需求分析

  1. 用户去添加标签,标签的分类(要有哪些标签、怎么把标签进行分类)学习方向 java / c++,工作 / 大学

  2. 主动搜索:允许用户根据标签去搜索其他用户

    • Redis 缓存
  3. 组队

    1. 创建队伍
    2. 加入队伍
    3. 根据标签查询队伍
    4. 队伍聊天
  4. 允许用户去修改标签

  5. 允许个人与个人进行聊天

  6. 公共聊天室

    • Websocket会话技术
  7. chagpt问答

  8. 博文发布

  9. 心动匹配

    • 相似度计算算法 + 本地分布式计算

技术栈

前端

  1. Vue 3 开发框架(提高页面开发的效率)
  2. Vant UI(基于 Vue 的移动端组件库)(React 版 Zent)
  3. Vite 2(打包工具,快!)
  4. Nginx 来单机部署

后端

  1. Java 编程语言 + SpringBoot 框架
  2. SpringMVC + MyBatis + MyBatis Plus(提高开发效率)
  3. MySQL 数据库
  4. Redis 缓存
  5. Websocket会话技术
  6. Chatgpt问答技术
  7. Swagger + Knife4j 接口文档

数据库表设计

标签的分类(要有哪些标签、怎么把标签进行分类)

新增标签表(分类表)

建议用标签,不要用分类,更灵活。

性别:男、女

方向:Java、C++、Go、前端

正在学:Spring

目标:考研、春招、秋招、社招、考公、竞赛(蓝桥杯)、转行、跳槽

段位:初级、中级、高级、王者

身份:小学、初中、高中、大一、大二、大三、大四、学生、待业、已就业、研一、研二、研三

状态:乐观、有点丧、一般、单身、已婚、有对象

字段:

id int 主键

标签名 varchar 非空(必须唯一,唯一索引)

上传标签的用户 userId int(如果要根据 userId 查已上传标签的话,最好加上,普通索引)

父标签 id ,parentId,int(分类)

是否为父标签 isParent, tinyint(0 不是父标签、1 - 父标签)

创建时间 createTime,datetime

更新时间 updateTime,datetime

是否删除 isDelete, tinyint(0、1)

怎么查询所有标签,并且把标签分好组?按父标签 id 分组,能实现 √

根据父标签查询子标签?根据 id 查询,能实现 √

SQL 语言分类:

DDL define 建表、操作表

DML manage 更新删除数据,影响实际表里的内容

DCL control 控制,权限

DQL query 查询,select

修改用户表

用户有哪些标签?

根据自己的实际需求来!!! 此处选择第一种

  1. 直接在用户表补充 tags 字段,[‘Java’, ‘男’] 存 json 字符串 优点:查询方便、不用新建关联表,标签是用户的固有属性(除了该系统、其他系统可能要用到,标签是用户的固有属性)节省开发成本**查询用户列表,查关系表拿到这 100 个用户有的所有标签 id,再根据标签 id 去查标签表。**哪怕性能低,可以用缓存。缺点:用户表多一列,会有点
  2. 加一个关联表,记录用户和标签的关系关联表的应用场景:查询灵活,可以正查反查缺点:要多建一个表、多维护一个表重点:企业大项目开发中尽量减少关联查询,很影响扩展性,而且会影响查询性能.

开发后端接口时间

搜索标签

\1. 允许用户传入多个标签,多个标签都存在才搜索出来 and。like ‘%Java%’ and like ‘%C++%’。

\2. 允许用户传入多个标签,有任何一个标签存在就能搜索出来 or。like ‘%Java%’ or like ‘%C++%’

两种方式:

\1. SQL 查询(实现简单,可以通过拆分查询进一步优化)

\2. 内存查询(灵活,可以通过并发进一步优化)

- 如果参数可以分析,根据用户的参数去选择查询方式,比如标签数

- 如果参数不可分析,并且数据库连接足够、内存空间足够,可以并发同时查询,谁先返回用谁。

- 还可以 SQL 查询与内存计算相结合,比如先用 SQL 过滤掉部分 tag

建议通过实际测试来分析哪种查询比较快,数据量大的时候验证效果更明显!

解析 JSON 字符串:

序列化:java对象转成 json

反序列化:把 json 转为 java 对象

java json 序列化库有很多:

\1. gson(google 的)

\2. fastjson alibaba(ali 出品,快,但是漏洞太多)

\3. jackson

\4. kryo

Swagger和Knife4j的区别和联系

Swagger

Swagger 是一个非常流行的 API 开发框架,它包括了一系列工具和服务,旨在帮助开发者设计、构建、文档化和使用 RESTful API。Swagger 提供了一个规范(OpenAPI Specification,OAS)来定义 RESTful API,使得开发者可以通过 YAML 或 JSON 文件来描述他们的 API,从而自动生成文档。Swagger 生态系统包含了许多工具,如 Swagger UI(用于展示 API 文档)、Swagger Codegen(用于生成客户端 SDK、服务器 stubs 和 API 文档)等。

特点:

  • 广泛支持:Swagger 支持多种语言和技术栈。
  • 标准化:遵循 OpenAPI 规范。
  • 丰富的生态系统:有许多工具可以集成,比如编辑器插件、IDE 插件等。
Knife4j

Knife4j 是一个基于 Swagger2 的增强版解决方案,专门为使用 Spring Boot 和 Spring Cloud 的项目而设计。它提供了比原生 Swagger 更多的功能和更友好的界面体验。Knife4j 在国内开发者社区中有较好的口碑,特别是在中文社区中,因为它提供了很好的本地化支持,并且有一些针对国内开发者习惯的优化。

特点:

  • 针对 Spring Boot/Spring Cloud:更加贴近 Spring 生态系统。
  • 中文友好:对于中文开发者来说有更好的文档和支持。
  • 额外功能:例如 API 分组、自定义页面样式等。
  • 增强的 UI:提供了更加美观和易用的 UI 界面。
区别
  1. 适用范围
    • Swagger 可以应用于任何遵循 OpenAPI 规范的项目。
    • Knife4j 主要面向的是使用 Spring Boot 和 Spring Cloud 的 Java 开发者。
  2. 社区支持
    • Swagger 有着广泛的国际社区支持。
    • Knife4j 在国内有着更好的支持,特别是中文社区。
  3. 功能特性
    • Swagger 提供基本的 API 文档生成功能。
    • Knife4j 在此基础上增加了一些实用的特性,比如 API 分组、自定义 UI 等。

Knife4j使用

  1. 引入配置依赖
   <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
            <version>4.4.0</version>
        </dependency>
  1. 配置文件配置
knife4j:
  enable: true
  openapi:
    title: Knife4j官方文档
    description: 我是许苑向上
    # aaa"
    email: 2517115657@qq.com
    concat: 一心向上
    url: https://docs.xiaominfo.com
    version: v4.0
    license: Apache 2.0
    license-url: https://stackoverflow.com/
    terms-of-service-url: https://stackoverflow.com/
    group:
      test1:
        group-name: 分组名称
        api-rule: package
        api-rule-resources:
          - xu.yuan.controller
  1. 注解信息的标注

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

分布式 session

种 session 的时候注意范围,cookie.domain

比如两个域名:

aaa.yupi.com

bbb.yupi.com

如果要共享 cookie,可以种一个更高层的公共域名,比如 yupi.com

为什么服务器 A 登录后,请求发到服务器 B,不认识该用户?

用户在 A 登录,所以 session(用户登录信息)存在了 A 上

结果请求 B 时,B 没有用户信息,所以不认识。

img

解决方案:共享存储 ,而不是把数据放到单台服务器的内存中

img

Session 共享实现Redis

如何共享存储?

  1. Redis(基于内存的 K / V 数据库)此处选择 Redis,因为用户信息读取 / 是否登录的判断极其频繁 ,Redis 基于内存,读写性能很高,简单的数据单机 qps 5w - 10w
  2. MySQL
  3. 文件服务器 ceph

官网:https://redis.io/

windows 下载:

Redis 5.0.14 下载:

链接:https://pan.baidu.com/s/1XcsAIrdeesQAyQU2lE3cOg

提取码:vkoi

redis 管理工具 quick redis:https://quick123.net/

  1. 引入 redis,能够操作 redis:
  <!-- redis -->
        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <version>2.6.4</version>
        </dependency>
  1. 引入 spring-session 和 redis 的整合,使得自动将 session 存储到 redis 中:

spring-session-data-redis 依赖用于将 HTTP 会话(HttpSession)数据存储到 Redis 中

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

其中request.getSession().setAttribute(LOGIN_USER_KEY, safetyUser);虽然存放的都是同一个键,但是其实redis真正存放数据的结构是,键是会话ID,值是Httpsession对象,其中Httpsession中有个Map用来存放数据,键是LOGIN_USER_KEY,值是存放的对象。

 <!-- session-data-redis -->
        <!-- https://mvnrepository.com/artifact/org.springframework.session/spring-session-data-redis -->
        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-data-redis</artifactId>
            <version>2.6.3</version>
        </dependency>

img

  1. 修改 spring-session 存储配置 spring.session.store-type

默认是 none,表示存储在单台服务器

store-type: redis,表示从 redis 读写 session

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

打包并分开运行。

img

img

img

再去他们对应的swagger-ui接口测试。

在8080端口登录了,可以在8081端口获取到用户信息。

img

img

img

JWT 的优缺点:https://zhuanlan.zhihu.com/p/108999941

(整体来说就跨域时出现了一点点小的问题,不知道是直播时漏掉了,还是用户中心源码没有更新。其他一切都还好。)

定时任务

  1. 开启定时任务;注解。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 新建InsertUser.java(鱼皮是在once文件夹,我这个是之前命名是起的,都可以无所谓的,自己记得就好。)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

插件(idea里搜的)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 编写定时任务代码并进行测试(这里的定时取巧,尽量别用,注释掉。)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 编写定时任务测试
package com.yupi.usercenter.easyExcel;
import java.util.Date;

import com.yupi.usercenter.mapper.UserMapper;
import com.yupi.usercenter.model.domain.User;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;

import javax.annotation.Resource;

/**
 * @author: shayu
 * @date:
 * @ClassName: yupao-backend01
 * @Description:
 */
@Component
public class InsertUsers {

    @Resource
    private UserMapper userMapper;

    /**
     * 循环插入用户
     */
//    @Scheduled(initialDelay = 5000,fixedRate = Long.MAX_VALUE )
    public void doInsertUser() {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        final int INSERT_NUM = 1000;
        for (int i = 0; i < INSERT_NUM; i++) {
            User user = new User();
            user.setUsername("假沙鱼");
            user.setUserAccount("yusha");
            user.setAvatarUrl("shanghai.myqcloud.com/shayu931/shayu.png");
            user.setProfile("一条咸鱼");
            user.setGender(0);
            user.setUserPassword("12345678");
            user.setPhone("123456789108");
            user.setEmail("shayu-yusha@qq.com");
            user.setUserStatus(0);
            user.setUserRole(0);
            user.setPlanetCode("931");
            user.setTags("[]");
            userMapper.insert(user);
        }
        stopWatch.stop();
        System.out.println( stopWatch.getLastTaskTimeMillis());

    }
}

分页查询配置

重启项目,前端主页用户信息会刷不出来,因为数据量过大。

(注意一下这里page的依赖是mybatis的)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

添加mybatis,注意扫描的包要修改(如果启动类已经添加就不需要添加了)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

首页查询时候会进行redis分页查询配置

定时任务/数据预热(Redis版本)

问题:第一个用户访问还是很慢(加入第一个老板),也能一定程度上保护数据库

缓存预热的优点:

  1. 解决上面的问题,可以让用户始终访问很快

缺点:

  1. 增加开发成本(你要额外的开发、设计)
  2. 预热的时机和时间如果错了,有可能你缓存的数据不对或者太老
  3. 需要占用额外空间
怎么缓存预热?
  1. 定时
  2. 模拟触发(手动触发)
实现

用定时任务,每天刷新所有用户的推荐列表

注意点:

  1. 缓存预热的意义(新增少、总用户多)
  2. 缓存的空间不能太大,要预留给其他缓存空间
  3. 缓存数据的周期(此处每天一次)

定时任务实现

  1. Spring Scheduler(spring boot 默认整合了)
  2. Quartz(独立于 Spring 存在的定时任务框架)
  3. XXL-Job 之类的分布式任务调度平台(界面 + sdk)

第一种方式:

  1. 主类开启 @EnableScheduling
  2. 给要定时执行的方法添加 @Scheduling 注解,指定 cron 表达式或者执行频率

不要去背 cron 表达式!!!!!去查找!!!!

  • https://cron.qqe2.com/
  • https://www.matools.com/crontab/

新建文件夹新建文件。之前就写个的,就不具体写出来了。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

定时任务会遇见的问题

控制定时任务的执行(为啥?)

  1. 浪费资源,想象 10000 台服务器同时 “打鸣”
  2. 脏数据,比如重复插入

**要控制定时任务在同一时间只有 1 个服务器能执行。(**怎么做?)

  1. 分离定时任务程序和主程序,只在 1 个服务器运行定时任务。成本太大
  2. 写死配置,每个服务器都执行定时任务,但是只有 ip 符合配置的服务器才真实执行业务逻辑,其他的直接返回。成本最低;但是我们的 IP 可能是不固定的,把 IP 写的太死了
  3. 动态配置,配置是可以轻松的、很方便地更新的(代码无需重启),但是只有 ip 符合配置的服务器才真实执行业务逻辑。问题:服务器多了、IP 不可控还是很麻烦,还是要人工修改
    • 数据库
    • Redis
    • 配置中心(Nacos、Apollo、Spring Cloud Config)
  1. 分布式锁,只有抢到锁的服务器才能执行业务逻辑。坏处:增加成本;好处:不用手动配置,多少个服务器都一样。

单机就会存在单点故障。

有限资源的情况下,控制同一时间(段)只有某些线程(用户 / 服务器)能访问到资源。

Java 实现锁:synchronized 关键字、并发包的类

问题:只对单个 JVM 有效

分布式锁

为啥需要分布式锁?

  1. 有限资源的情况下,控制同一时间(段)只有某些线程(用户 / 服务器)能访问到资源。

  2. 单个锁只对单个 JVM 有效。

分布式锁实现的关键

抢锁机制

怎么保证同一时间只有 1 个服务器能抢到锁?

核心思想 就是:先来的人先把数据改成自己的标识(服务器 ip),后来的人发现标识已存在,就抢锁失败,继续等待。

等先来的人执行方法结束,把标识清空,其他的人继续抢锁。

MySQL 数据库:select for update 行级锁(最简单)

(乐观锁)

Redis 实现:内存数据库,读写速度快 。支持 setnx、lua 脚本,比较方便我们实现分布式锁。

setnx:set if not exists 如果不存在,则设置;只有设置成功才会返回 true,否则返回 false。

注意事项

  1. 用完锁要释放(腾地方)√
  2. **锁一定要加过期时间 √**
  3. 如果方法执行时间过长,锁提前过期了?问题:

  4. 1. 连锁效应:释放掉别人的锁
     2. 这样还是会存在多个方法同时执行的情况

​ 解决方案:续期

boolean end = false;

//线程
new Thread(() -> {
 //判断状态
 if (!end)}{
     续期
 })

 end = true;
  1. 释放锁的时候,有可能先判断出是自己的锁,但这时锁过期了,最后还是释放了别人的锁
// 原子操作
if(get lock == A) {
 // set lock B
 del lock
}
  1. Redis 如果是集群(而不是只有一个 Redis),如果分布式锁的数据不同步怎么办?

Redis + lua 脚本实现

https://blog.youkuaiyun.com/feiying0canglang/article/details/113258494

RedisTemplate和RedissonClient的区别?

  • RedisTemplate:用于基本的 Redis 数据操作,提供类型安全和序列化支持。
  • RedissonClient:用于实现高级的分布式功能,如分布式锁、队列等,适用于需要分布式协调的应用场景。

Redisson 如何实现分布式锁

Java 客户端,数据网格

实现了很多 Java 里支持的接口和数据结构

Redisson 是一个 java 操作 Redis 的客户端,提供了大量的分布式数据集来简化对 Redis 的操作和使用,可以让开发者像使用本地集合一样使用 Redis,完全感知不到 Redis 的存在。

spring boot starter 引入(不推荐,版本迭代太快,容易冲突)https://github.com/redisson/redisson/tree/master/redisson-spring-boot-starter

实现如下:

  1. 直接引入:    Quick start(快速开始)
  2. 添加依赖,编写RedissonConfig文件。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

/**
 * Redisson 配置  => 作用是,用java作为客户端连接redis
 */
@Configuration
public class RedissionConfig {
    @Value("${spring.redis.host}")
    private String host;
    @Value("${spring.redis.port}")
    private String port;
    @Value("${spring.redis.password}")
    private String password ;

    @Bean
    public RedissonClient redissonClient(){
        // 1. Create config object
        Config config = new Config();
        String address = "redis://" + host + ":" + port;
        // 没有密码设置这个
        if (StringUtils.isBlank(password)){
            config.useSingleServer().setAddress(address);
        }else{
            config.useSingleServer().setAddress(address).setPassword(password);
        }
        // 2. Create Redisson instance(创建实例)
        // 返回redisson对象
        RedissonClient redisson = Redisson.create(config);
        return redisson;
    }


}

分布式锁的实现

定时任务 + 锁
  1. waitTime 设置为 0,只抢一次,抢不到就放弃
  2. 注意释放锁要写在 finally 中
@Component
@Slf4j
/**
 * 实现数据定期缓存
 */

// 定时任务,每天在 23:59:59 执行
@Scheduled(cron = "59 59 23 * * *")
public void preCacheJob() {
    // 获取 RedisTemplate 的 ValueOperations 实例,用于执行基本的 CRUD 操作
    ValueOperations<String, Object> redis = redisTemplate.opsForValue();
    
    // 从 RedissonClient 获取一个名为 "lock:pre:xuyuan" 的分布式锁实例
    RLock lock = redissonClient.getLock("lock:pre:xuyuan");
    
    try {
        // 尝试获取锁,等待时间为 0 ms(即立即返回),并且锁的超时时间为 30 秒
        if ( lock.tryLock(0, 30000L, TimeUnit.MILLISECONDS)) {
            // 遍历所有用户的 ID 列表
            for (Long userId : mainUserList) {
                // 构建 Redis 中键的名称,格式为 "yupao:user:recommend:$s"
                // 注意这里应该使用 userId 而不是 "$s"
                String key = String.format("yupao:user:recommend:%s", userId);
                
                // 如果 Redis 中不存在该键,则从数据库中查询数据
                LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
                Page<User> page = new Page<>(1, 20); // 设置分页参数,当前页为 1,每页记录数为 20
                Page<User> pageList = userService.page(page, lqw); // 查询数据库
                
                // 尝试将查询到的数据存入 Redis
                try {
                    // 将查询结果存入 Redis,过期时间为 1 分钟
                    redis.set(key, pageList, 1, TimeUnit.MINUTES);
                } catch (Exception e) {
                    // 记录错误日志
                    log.info("redis set key: error {}", e);
                }
            }
        }
    } catch (InterruptedException e) {
        // 记录获取锁过程中出现的异常
        log.info("获取锁,出现异常");
    } finally {
        // 在 finally 块中释放锁
        // 判断当前线程是否持有锁,如果是则解锁
        if (lock.isHeldByCurrentThread()) {
            lock.unlock();
        }
    }
}

匹配算法

最短距离算法

* 编辑距离算法(用于计算最相似的两组标签)

这里给一个题目的描述:给定两个单词 word1 和 word2,计算出将 word1 转换成 word2 所使用的最少操作数 。你可以对一个单词进行如下三种操作。

  • 插入一个字符
  • 删除一个字符
  • 替换一个字符

* 原理:https://blog.youkuaiyun.com/DBC_121/article/details/104198838

新建文件夹utils 编写距离算法AlgorithmUtils

模块分析

许苑模块主要分为7个模块:

  • 用户模块

  • 队伍模块

  • 聊天模块

  • 上传文件模块

  • 粉丝关注模块

  • 博客模块

  • AI模块

聊天室(Websocket技术)

  1. 导入依赖
        <!--websocket-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>
  1. 配置类WebSocketConfig,并将ServerEndpointExporter类注册成bean对象

  2. /**
     * WebSocket配置
     * 其实是创建这个配置类去扫描含有@ServerEndpoint注解
     * 通过在配置类中声明一个ServerEndpointExporter bean,Spring Boot
     * 应用将在启动时自动扫描所有的@ServerEndpoint注解,
     * 并将它们注册为有效的WebSocket端点。
     * 这意味着你无需在Servlet容器中手动配置WebSocket端点,
     * Spring会帮你完成这项工作。
     */
    

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 创建WebSocket对象,WebSocket 类通过 @ServerEndpoint 注解指定了 HttpSessionConfig 作为配置器,从而确保每次 WebSocket 连接建立时都能正确地配置 HTTP 会话信息。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 创建配置类,重写modifyHandshake方法,在这个方法中,通过 request.getHttpSession() 获取了当前 HTTP 会话,并将其保存到 ServerEndpointConfiggetUserProperties() 方法Map中。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 这样做的目的是将 HTTP 会话信息关联到 WebSocket 连接上,使得 WebSocket 连接可以访问 HTTP 会话中的信息。
/**
 * 编写http会话配置类,用于获取HttpSession对象
 *
 * @author xuyuan
 */
@Component
public class HttpSessionConfig extends ServerEndpointConfig.Configurator implements ServletRequestListener {

    /**
     * 请求初始化
     *
     * @param sre 行为
     */
    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        //获取HttpSession,将所有的请求都获得HttpSession
        HttpSession httpSession = ((HttpServletRequest) sre.getServletRequest()).getSession();
    }

    /**
     * 修改握手
     *
     * @param sec
     * @param request  请求
     * @param response 响应
     */
    @Override
    public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
        // 获取HttpSession
        HttpSession httpSession = (HttpSession) request.getHttpSession();
        if (httpSession != null) {
            // session放入serverEndpointConfig
            sec.getUserProperties().put(HttpSession.class.getName(), httpSession);
        }
        super.modifyHandshake(sec, request, response);
    }
}

总结

websocket技术就是实现了客户端发送消息给服务端,然后经过服务端转发给对应会话的客户端。

一些疑惑性问题

Request URL: http://mate.xuyuan.asia:8082/api/user/recommend?currentPage=2&username=

Request Method: GET

其中后端方法参数@GetMapping(“/recommend”) public Result<Page> recommendUser(long currentPage, String username ,HttpServletRequest httpServletRequest),为什么参数是不需要加@RequestParam而且@GetMapping(“/recommend”)也不需要加路径符,这是为什么?

在 Spring MVC 中,当你定义了一个 GET 映射的方法来处理 HTTP GET 请求时,可以通过方法参数直接接收请求参数而不需要显式地使用 @RequestParam 注解。这是因为 Spring MVC 默认会尝试将请求中的查询参数映射到控制器方法的相应参数上只要控制器方法的参数名称与请求 URL 中的查询参数名称匹配,Spring MVC 就能自动绑定这些值。

如果不匹配:

@GetMapping("/user")
public String getUserInfo(@RequestParam("currentPage") String currentPage,
                          @RequestParam("username") int username) {
    // 处理逻辑
    return "userInfo";
}

同时可以添加注解@RequestParam进行映射。

Request URL: http://mate.xuyuan.asia:8082/api/user/24

Request Method: POST

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

对于URL的路径符号则需要对应的注解**@PathVariable**进行参数的映射。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值