心链12-----队伍页业务完善+匹配算法实现随机匹配(最短距离算法)

心链 — 伙伴匹配系统

搜索队伍

我们选择vant组件库里的基础搜索框,复制到TeamPage页面,同时还有查询为空时,显示的无结果页面(用户页面以写过)
image.png
因为,我们一次性挂载本质性也是搜索队伍,所以我们把代码提取出来

/*** 搜索队伍* @param val* @returns {Promise<void>}*/
const listTeam = async (val = '') => {const res = await myAxios.get("/team/list", {params: {searchText: val,pageNum: 1,},});if (res?.code === 0) {teamList.value = res.data;} else {Toast.fail('加载队伍失败,请刷新重试');}
}

挂载和搜索框修改为下图所示:(PS:搜索直接回车就可行)
image.png

更新队伍

分析:我们的更新页面和新建队伍页面类似,所以我们直接复制TeamAddPage,创建TeamUpdateTeam页面
完善TeamCardList
我们首先在队伍页面,创建一个按钮来跳转到更新页面,但是只有当前用户是队伍创建者才可以看到次按钮,我们可以直接写在TeamCardList组件里
按钮添加
image.png
由于需要判断当前用户是否为队伍创建者,我们要获取当前用户(调用以前写的方法)
image.png
写跳转按钮的逻辑
image.png
PS:别忘了引入useRouter
完整代码如下:

<template><divid="teamCardList"><van-cardv-for="team in props.teamList":thumb="mouse":desc="team.description":title="`${team.name}`"><template #tags><van-tag plain type="danger" style="margin-right: 8px; margin-top: 8px">{{teamStatusEnum[team.status]}}</van-tag></template><template #bottom><div>{{ '最大人数: ' + team.maxNum }}</div><div v-if="team.expireTime">{{ '过期时间: ' + team.expireTime }}</div><div>{{ '创建时间: ' + team.createTime }}</div></template><template #footer><van-button size="small" type="primary"  plain @click="doJoinTeam(team.id)">加入队伍</van-button><van-button v-if="team.userId === currentUser?.id" size="small" plain@click="doUpdateTeam(team.id)">更新队伍</van-button></template></van-card></div></template><script setup lang="ts">
import {TeamType} from "../models/team";
import {teamStatusEnum} from "../constants/team";
import mouse from '../assets/mouse.jpg';
import myAxios from "../plugins/myAxios";
import {Toast} from "vant";import {useRouter} from "vue-router";
import {onMounted, ref} from "vue";
import {getCurrentUser} from "../services/user";interface TeamCardListProps {teamList: TeamType[];
}const props = withDefaults(defineProps<TeamCardListProps>(), {// @ts-ignoreteamList: [] as TeamType[],
});const router = useRouter();/*** 加入队伍*/
const doJoinTeam = async (id:number) => {const res = await myAxios.post('/team/join', {teamId: id,});if (res?.code === 0) {Toast.success('加入成功');} else {Toast.fail('加入失败' + (res.description ? `${res.description}` : ''));}
}/*** 跳转至更新队伍页* @param id*/
const doUpdateTeam = (id: number) => {router.push({path: '/team/update',query: {id,}})
}const currentUser = ref();onMounted(async () =>{currentUser.value = await getCurrentUser();
})</script><style scoped>
#teamCardList :deep(.van-image__img) {height: 128px;object-fit: unset;
}
</style>

修改TeamUpdateTeam
删除不能修改的组件(最大人数)和固定显示的参数(initFormData),修改提交逻辑(由于是复制得来的,千万别忘了,不然就是增加队伍了)
关键是获取之前队伍的信息。引入Route,来获取上个页面传来的参数
定义变量id
image.png

挂载获取之前队伍的信息

image.png

完整代码如下:

<template><div id="teamAddPage"><van-form @submit="onSubmit"><van-cell-group inset><van-fieldv-model="addTeamData.name"name="name"label="队伍名"placeholder="请输入队伍名":rules="[{ required: true, message: '请输入队伍名' }]"/><van-fieldv-model="addTeamData.description"rows="4"autosizelabel="队伍描述"type="textarea"placeholder="请输入队伍描述"/><van-fieldis-linkreadonlyname="datetimePicker"label="过期时间":placeholder="addTeamData.expireTime ?? '点击选择过期时间'"@click="showPicker = true"/><van-popup v-model:show="showPicker" position="bottom"><van-datetime-pickerv-model="addTeamData.expireTime"@confirm="showPicker = false"type="datetime"title="请选择过期时间":min-date="minDate"/></van-popup><van-field name="radio" label="队伍状态"><template #input><van-radio-group v-model="addTeamData.status" direction="horizontal"><van-radio name="0">公开</van-radio><van-radio name="1">私有</van-radio><van-radio name="2">加密</van-radio></van-radio-group></template></van-field><van-fieldv-if="Number(addTeamData.status) === 2"v-model="addTeamData.password"type="password"name="password"label="密码"placeholder="请输入队伍密码":rules="[{ required: true, message: '请填写密码' }]"/></van-cell-group><div style="margin: 16px;"><van-button round block type="primary" native-type="submit">提交</van-button></div></van-form></div>
</template><script setup lang="ts">import {useRoute, useRouter} from "vue-router";
import myAxios from "../plugins/myAxios";
import {Toast} from "vant";
import {onMounted, ref} from "vue";
import {TeamType} from "../models/team";const router = useRouter();
const route = useRoute();
// 展示日期选择器
const showPicker = ref(false);const minDate = new Date();// 需要用户填写的表单数据
const addTeamData = ref({})const id = route.query.id;//获取之前队伍的信息
onMounted(async () => {if (id <= 0) {Toast.fail("队伍加载失败");return;}const res = await myAxios.get("/team/get", {params: {id: id,}});if (res?.code === 0) {addTeamData.value = res.data;} else {Toast.fail("队伍加载失败,请刷新重试");}
})// 提交
const onSubmit = async () => {const postData = {...addTeamData.value,status: Number(addTeamData.value.status)}const res = await myAxios.post("/team/update", postData);if (res?.code === 0 && res.data) {Toast.success('更新成功');router.push({path: '/team',replace: true,});} else {Toast.success('更新失败');}
}
</script><style scoped>
#teamPage {
}
</style>

image.png

获取当前用户已加入的队伍

我们查询加入的队伍需要用到id的列表,所以在Teamquery里增加idList字段
image.png
获取我加入的队伍
编写后端接口
复用 listTeam 方法,只新增查询条件,不做修改(开闭原则)
获取当前用户已加入的队伍

    /*** 获取我加入的队伍** @param teamQuery* @param request* @return*/@GetMapping("/list/my/join")public BaseResponse<List<TeamUserVO>> listMyJoinTeams(TeamQuery teamQuery, HttpServletRequest request) {if (teamQuery == null) {throw new BusinessException(ErrorCode.PARAMS_ERROR);}User loginUser = userService.getLoginUser(request);QueryWrapper<UserTeam> queryWrapper = new QueryWrapper<>();queryWrapper.eq("userId", loginUser.getId());List<UserTeam> userTeamList = userTeamService.list(queryWrapper);// 取出不重复的队伍 id// teamId userIdMap<Long, List<UserTeam>> listMap = userTeamList.stream().collect(Collectors.groupingBy(UserTeam::getTeamId));List<Long> idList = new ArrayList<>(listMap.keySet());teamQuery.setIdList(idList);List<TeamUserVO> teamList = teamService.listTeams(teamQuery, true);return ResultUtils.success(teamList);}

修改下listTeam方法,加一层校验
image.png
image.png
加入队伍列表是经过去重的
image.png
image.png

获取当前用户创建的队伍

/***  获取我加入的队伍* @param teamQuery* @param request* @return*/@GetMapping("/list/my/join")public BaseResponse<List<TeamUserVO>> listMyJoinTeams(TeamQuery teamQuery, HttpServletRequest request) {if (teamQuery == null) {throw new BusinessException(ErrorCode.PARAMS_ERROR);}User logininUser = userService.getLoginUser(request);QueryWrapper<UserTeam> queryWrapper = new QueryWrapper<>();queryWrapper.eq("userId",logininUser.getId());List<UserTeam> userTeamlist = userTeamService.list(queryWrapper);// 取出不重复的队伍 id//teamId userId//1,2//1,3//2,3//result//1=> 2,3//2=> 3Map<Long, List<UserTeam>> listMap = userTeamlist.stream().collect(Collectors.groupingBy(UserTeam::getUserId));ArrayList<Long> idList = new ArrayList<>(listMap.keySet());teamQuery.setIdList(idList);List<TeamUserVO> teamList = teamService.listTeams(teamQuery,true);return ResultUtils.success(teamList);}
创建前端页面

我们复制一份UserPage,命名为UserUpdatePage,修改UserPage(我们只需要当前用户,修改信息,我创建的队伍,我加入的队伍)
修改UserPage如下:

<template><template v-if="user"><van-cell title="当前用户" :value="user?.username" /><van-cell title="修改信息" is-link to="/user/update" /><van-cell title="我创建的队伍" is-link to="/user/team/create" /><van-cell title="我加入的队伍" is-link to="/user/team/join" /></template>
</template><script setup lang="ts">
import {useRouter} from "vue-router";
import {onMounted, ref} from "vue";
import myAxios from "../plugins/myAxios.js";
import {Toast} from "vant";
import {getCurrentUser} from "../services/user";const user = ref();onMounted(async ()=>{user.value=await getCurrentUser();
})const router = useRouter();const toEdit = (editKey: string, editName: string, currentValue: string) => {router.push({path: '/user/edit',query: {editKey,editName,currentValue,}})
}
</script><style scoped></style>

创建页面:查询加入队伍页面和查询创建队伍页面(复制TeamPage页面,形式相同) PS:别忘了在路由里面添加这两个页面 因为我们把原来的用户页面改为用户更新页面,路由里也要修改
image.png

查询加入队伍页面

<template><div id="teamPage"><van-search v-model="searchText" placeholder="搜索队伍" @search="onSearch" /><team-card-list :teamList="teamList" /><van-empty v-if="teamList?.length < 1" description="数据为空"/></div>
</template><script setup lang="ts">import {useRouter} from "vue-router";
import TeamCardList from "../components/TeamCardList.vue";
import {onMounted, ref} from "vue";
import myAxios from "../plugins/myAxios";
import {Toast} from "vant";const router = useRouter();
const searchText = ref('');const teamList = ref([]);/*** 搜索队伍* @param val* @returns {Promise<void>}*/
const listTeam = async (val = '') => {const res = await myAxios.get("/team/list/my/join", {params: {searchText: val,pageNum: 1,},});if (res?.code === 0) {teamList.value = res.data;} else {Toast.fail('加载队伍失败,请刷新重试');}
}// 页面加载时只触发一次
onMounted( () => {listTeam();
})const onSearch = (val) => {listTeam(val);
};</script><style scoped>
#teamPage {}
</style>

查询创建队伍页面

<template><div id="teamPage"><van-search v-model="searchText" placeholder="搜索队伍" @search="onSearch" /><van-button type="primary" @click="doJoinTeam">创建队伍</van-button><team-card-list :teamList="teamList" /><van-empty v-if="teamList?.length < 1" description="数据为空"/></div>
</template><script setup lang="ts">import {useRouter} from "vue-router";
import TeamCardList from "../components/TeamCardList.vue";
import {onMounted, ref} from "vue";
import myAxios from "../plugins/myAxios";
import {Toast} from "vant";const router = useRouter();
const searchText = ref('');// 跳转到加入队伍页
const doJoinTeam = () => {router.push({path: "/team/add"})
}const teamList = ref([]);/*** 搜索队伍* @param val* @returns {Promise<void>}*/
const listTeam = async (val = '') => {const res = await myAxios.get("/team/list/my/create", {params: {searchText: val,pageNum: 1,},});if (res?.code === 0) {teamList.value = res.data;} else {Toast.fail('加载队伍失败,请刷新重试');}
}// 页面加载时只触发一次
onMounted( () => {listTeam();
})const onSearch = (val) => {listTeam(val);
};</script><style scoped>
#teamPage {}
</style>

image.png
image.png
退出和解散队伍

1.在TeamCardList添加两个按钮来实现这两个功能

image.png

2.在js里写入按钮的方法

/*** 退出队伍* @param id*/
const doQuitTeam = async (id: number) => {const res = await myAxios.post('/team/quit', {teamId: id});if (res?.code === 0) {Toast.success('操作成功');} else {Toast.fail('操作失败' + (res.description ? `${res.description}` : ''));}
}/*** 解散队伍* @param id*/
const doDeleteTeam = async (id: number) => {const res = await myAxios.post('/team/delete', {id,});if (res?.code === 0) {Toast.success('操作成功');} else {Toast.fail('操作失败' + (res.description ? `${res.description}` : ''));}
}

封装一个删除请求DeleteRequest

/*** 通用删除请求** @author yupi*/
@Data 
public class DeleteRequest implements Serializable {private static final long serialVersionUID = 1787902631969457554L;private long id;
}

修改删除接口

    @PostMapping("/delete")public BaseResponse<Boolean> deleteTeam(@RequestBody DeleteRequest deleteRequest, HttpServletRequest request) {if (deleteRequest == null || deleteRequest.getId() <= 0) {throw new BusinessException(ErrorCode.PARAMS_ERROR);}long id = deleteRequest.getId();User loginUser = userService.getLoginUser(request);boolean result = teamService.deleteTeam(id, loginUser);if (!result) {throw new BusinessException(ErrorCode.SYSTEM_ERROR, "删除失败");}return ResultUtils.success(true);}

解散队伍成功

image.png

随机匹配

为了帮大家更快地发现和自己兴趣相同的朋友
匹配 1 个还是匹配多个?
答:匹配多个,并且按照匹配的相似度从高到低排序

怎么匹配?(根据什么匹配)
答:标签 tags
还可以根据 user_team 匹配加入相同队伍的用户
本质:找到有相似标签的用户
举例:
用户 A:[Java, 大一, 男]
用户 B:[Java, 大二, 男]
用户 C:[Python, 大二, 女]
用户 D:[Java, 大一, 女]

1. 怎么匹配

:::danger

  1. 找到有共同标签最多的用户(TopN)
  2. 共同标签越多,分数越高,越排在前面
  3. 如果没有匹配的用户,随机推荐几个(降级方案)

编辑距离算法:https://blog.csdn.net/DBC_121/article/details/104198838
最小编辑距离:字符串 1 通过最少多少次增删改字符的操作可以变成字符串 2
余弦相似度算法:https://blog.csdn.net/m0_55613022/article/details/125683937(如果需要带权重计算,比如学什么方向最重要,性别相对次要)
:::

2. 怎么对所有用户匹配,取 TOP(下一期的内容)

:::danger
直接取出所有用户,依次和当前用户计算分数,取 TOP N(54 秒)
优化方法:

  1. 切忌不要在数据量大的时候循环输出日志(取消掉日志后 20 秒)
  2. Map 存了所有的分数信息,占用内存解决:维护一个固定长度的有序集合(sortedSet),只保留分数最高的几个用户(时间换空间)e.g.【3, 4, 5, 6, 7】取 TOP 5,id 为 1 的用户就不用放进去了
  3. 细节:剔除自己 √
  4. 尽量只查需要的数据:
    1. 过滤掉标签为空的用户 √
    2. 根据部分标签取用户(前提是能区分出来哪个标签比较重要)
    3. 只查需要的数据(比如 id 和 tags) √(7.0s)
  5. 提前查?(定时任务)
    1. 提前把所有用户给缓存(不适用于经常更新的数据)
    2. 提前运算出来结果,缓存(针对一些重点用户,提前缓存)
      :::
      :::danger
      大数据推荐,比如说有几亿个商品,难道要查出来所有的商品?
      难道要对所有的数据计算一遍相似度?
匹配通用

检索 => 召回 => 粗排 => 精排 => 重排序等等
检索:尽可能多地查符合要求的数据(比如按记录查)
召回:查询可能要用到的数据(不做运算)
粗排:粗略排序,简单地运算(运算相对轻量)
精排:精细排序,确定固定排位
:::

匹配算法

:::danger

最短距离算法
  • 编辑距离算法(用于计算最相似的两组标签)
  • 原理:https://blog.csdn.net/DBC_121/article/details/104198838
    最小编辑距离:字符串 str1 通过最少多少次增删改字符的操作可以变成字符串str2
    **余弦相似度算法:**https://blog.csdn.net/m0_55613022/article/details/125683937(如果需要带权重计算,比如学什么方向最重要,性别相对次要)

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

/**
* 算法工具类
*
* @author yupi
*/
public class AlgorithmUtils {/**
* 编辑距离算法(用于计算最相似的两组标签)
*
* @param tagList1
* @param tagList2
* @return
*/public static int minDistance(List<String> tagList1, List<String> tagList2) {int n = tagList1.size();int m = tagList2.size();if (n * m == 0) {return n + m;}int[][] d = new int[n + 1][m + 1];for (int i = 0; i < n + 1; i++) {d[i][0] = i;}for (int j = 0; j < m + 1; j++) {d[0][j] = j;}for (int i = 1; i < n + 1; i++) {for (int j = 1; j < m + 1; j++) {int left = d[i - 1][j] + 1;int down = d[i][j - 1] + 1;int left_down = d[i - 1][j - 1];if (!Objects.equals(tagList1.get(i - 1), tagList2.get(j - 1))) {left_down += 1;}d[i][j] = Math.min(left, Math.min(down, left_down));}}return d[n][m];}/**
* 编辑距离算法(用于计算最相似的两个字符串)
* 原理:https://blog.csdn.net/DBC_121/article/details/104198838
*
* @param word1
* @param word2
* @return
*/public static int minDistance(String word1, String word2) {int n = word1.length();int m = word2.length();if (n * m == 0) {return n + m;}int[][] d = new int[n + 1][m + 1];for (int i = 0; i < n + 1; i++) {d[i][0] = i;}for (int j = 0; j < m + 1; j++) {d[0][j] = j;}for (int i = 1; i < n + 1; i++) {for (int j = 1; j < m + 1; j++) {int left = d[i - 1][j] + 1;int down = d[i][j - 1] + 1;int left_down = d[i - 1][j - 1];if (word1.charAt(i - 1) != word2.charAt(j - 1)) {left_down += 1;}d[i][j] = Math.min(left, Math.min(down, left_down));}}return d[n][m];}
}

测试方法

/**
* 算法工具类测试
*/
public class AlgorithmUtilsTest {@Testvoid test() {String str1 = "阿尼是狗";String str2 = "阿尼不是狗";String str3 = "阿尼是鱼不是狗";//        String str4 = "阿尼是猫";// 1int score1 = AlgorithmUtils.minDistance(str1, str2);// 3int score2 = AlgorithmUtils.minDistance(str1, str3);System.out.println(score1);System.out.println(score2);}@Testvoid testCompareTags() {List<String> tagList1 = Arrays.asList("Java", "大一", "男");List<String> tagList2 = Arrays.asList("Java", "大一", "女");List<String> tagList3 = Arrays.asList("Python", "大二", "女");// 1int score1 = AlgorithmUtils.minDistance(tagList1, tagList2);// 3int score2 = AlgorithmUtils.minDistance(tagList1, tagList3);System.out.println(score1);System.out.println(score2);}
}

在UserService里写入matchUsers方法并实现
image.png
在UserController里写入获取最匹配的用户的接口

    /*** 获取最匹配的用户** @param num* @param request* @return*/@GetMapping("/match")public BaseResponse<List<User>> matchUsers(long num, HttpServletRequest request) {if (num <= 0 || num > 20) {throw new BusinessException(ErrorCode.PARAMS_ERROR);}User user = userService.getLoginUser(request);return ResultUtils.success(userService.matchUsers(num, user));}

测试

在数据库里插入标签假数据
image.png
根据最小编辑距离算法,结果排序(排除自己)应该是顺序:46235
启动后端,在knife4j接口文档里测试matchUsers,正确用户排序也是46235
image.png

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

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

相关文章

@Validated 前端表单数据校验

1. 整合 1.1 依赖引入 <dependency><groupId>org.hibernate.validator</groupId><artifactId>hibernate-validator</artifactId></dependency>1.2 控制层 /*** 新增胎架计划** param subsectionPlanVo* return*/PostMapping("/sched…

数据结构复习指导之外部排序

目录 外部排序 复习提示 1.外部排序的基本概念 2.外部排序的方法 2.1对大文件排序时使用的排序算法&#xff08;2016&#xff09; 3.多路平衡归并与败者树 4.置换-选择排序&#xff08;生成初始归并段&#xff09; 4.1置换-选择排序生成初始归并段的实例(2023) 5.最佳…

【经验】Ubuntu上离线安装VsCode插件浏览Linux kernel源码

1、下载VsCode离线安装包 1.1 下载 下载地址:https://marketplace.visualstudio.com/vscode 本人安装的插件: C/C++ checkpatch Chinese clangd kconfig Makefile Tools Perl Perl Toolbox注意:C/C++插件要安装Linux 64版本 1.2 安装 将离线安装包拷贝到Ubuntu中,执…

SpringCloud整合OpenFeign实现微服务间的通信

1. 前言 1.1 为什么要使用OpenFeign&#xff1f; 虽说RestTemplate 对HTTP封装后, 已经⽐直接使⽤HTTPClient简单⽅便很多, 但是还存在⼀些问题. 需要拼接URL, 灵活性⾼, 但是封装臃肿, URL复杂时, 容易出错. 代码可读性差, ⻛格不统⼀。 1.2 介绍一下微服务之间的通信方式 微…

使用 stress 命令进行Linux CPU 压力测试

大家好&#xff0c;在现代计算机系统中&#xff0c;对系统性能和稳定性的评估是至关重要的。特别是在服务器环境中&#xff0c;我们需要确保系统能够在高负载情况下稳定运行&#xff0c;以满足用户的需求。而 CPU 是系统中最关键的组件之一&#xff0c;其性能直接影响着整个系统…

Python 快速查找并替换Excel中的数据

Excel中的查找替换是一个非常实用的功能&#xff0c;能够帮助用户快速完成大量数据的整理和处理工作&#xff0c;避免手动逐一修改数据的麻烦&#xff0c;提高工作效率。要使用Python实现这一功能&#xff0c; 我们可以借助Spire.XLS for Python 库&#xff0c;具体操作如下&am…

GAN网络理论和实验(二)

文章目录 一、说明二、什么是生成对抗网络&#xff1f;三、判别模型与生成模型四、生成对抗网络的架构五、你的第一个 GAN六、准备训练数据七、实现鉴别器八、实现生成器九、训练模型十、检查 GAN 生成的样本十一、使用 GAN 生成手写数字十二、准备训练数据十三、实现鉴别器和生…

笔记-2024视频会议软件技术选型方案

一、背景 视频会议系统是一种现代化的办公系统&#xff0c;它可以使不同会场的实时现场场景和语音互连起来&#xff0c;同时向与会者提供分享听觉和视觉的空间&#xff0c;使各与会方有“面对面”交谈的感觉。随着社会的发展&#xff0c;视频会议的应用越来越广泛&#xff0c;…

BC6 小飞机

BC6 小飞机 废话不多说先上题目&#xff1a; 代码如下&#xff1a; #include<stdio.h> int main() {printf(" ## \n############\n############\n # # \n # # \n");return 0; }这是用一个printf打印我们还可以用多个printf发打印代码如下…

Django框架中级

Django框架中级 – 潘登同学的WEB框架 文章目录 Django框架中级 -- 潘登同学的WEB框架 中间件自定义中间件常用中间件process_view() 使用中间件进行URL过滤 Django生命周期生命周期分析 Django日志日志配置filter过滤器自定义filter 日志格式化formatter Django信号内置信号定…

类和对象(二)(C++)

初始化列表 class Date{public:Date(int year, int month, int day){_year year;_month month;_day day;}private:int _year;int _month;int _day;}; 虽然上述构造函数调用之后&#xff0c;对象中已经有了一个初始值&#xff0c;但是不能将其称为对对象中成员变量的初始化…

【纯血鸿蒙】——响应式布局如何实现?

前面介绍了自适应布局&#xff0c;但是将窗口尺寸变化较大时&#xff0c;仅仅依靠自适应布局可能出现图片异常放大或页面内容稀疏、留白过多等问题。此时就需要借助响应式布局能力调整页面结构。 响应式布局 响应式布局是指页面内的元素可以根据特定的特征&#xff08;如窗口…

docker部署使用本地文件的fastapi项目

项目背景&#xff1a;项目使用python开发&#xff0c;需要使用ubutun系统部署后端api接口&#xff0c;对外使用8901端口。 1:项目结构&#xff1a; 2&#xff1a;项目需要使用的pyhton版本为3.9&#xff0c;dockerfile内容如下&#xff1a; # FROM python:3.9# WORKDIR /co…

自制植物大战僵尸:HTML5与JavaScript实现的简单游戏

引言 在本文中&#xff0c;我们将一起探索如何使用HTML5和JavaScript来创建一个简单的植物大战僵尸游戏。这不仅是一项有趣的编程挑战&#xff0c;也是学习游戏开发基础的绝佳机会。 什么是植物大战僵尸&#xff1f; 植物大战僵尸是一款流行的策略塔防游戏&#xff0c;玩家需…

如何提高网站排名?

提高网站排名是一个复杂的过程&#xff0c;涉及到多个方面的优化&#xff0c;包括但不限于内容质量、网站结构、用户体验、外部链接建设等&#xff0c;GSR这个系统&#xff0c;它是一种快速提升关键词排名的方案&#xff0c;不过它有个前提&#xff0c;就是你的站点在目标关键词…

超详解——深入详解Python基础语法——小白篇

目录 1 .语句和变量 变量赋值示例&#xff1a; 打印变量的值&#xff1a; 2. 语句折行 反斜杠折行示例&#xff1a; 使用括号自动折行&#xff1a; 3. 缩进规范 缩进示例&#xff1a; 4. 多重赋值&#xff08;链式赋值&#xff09; 多重赋值的应用&#xff1a; 5 .多…

FonesGo Location Changer 用Mac修改iPhone定位的工具

搜索Mac软件之家下载FonesGo Location Changer 用Mac修改iPhone定位的工具 FonesGo Location Changer 7.0.0 可以自定义修改iPhone和Android手机的GPS定位。FonesGo Location Changer 是玩 Pokemon Go 时的最佳搭档。您可以以自定义速度模拟 GPS 运动&#xff0c;例如步行、骑…

【设计模式】JAVA Design Patterns——State(状态模式)

&#x1f50d;目的 允许对象在内部状态改变时改变它的行为。对象看起来好像修改了它的类。 &#x1f50d;解释 真实世界例子 当在长毛象的自然栖息地观察长毛象时&#xff0c;似乎它会根据情况来改变自己的行为。它开始可能很平静但是随着时间推移当它检测到威胁时它会对周围的…

element-plus 的icon 图标的使用

element-plus的icon 已经独立出来了&#xff0c;需要单独安装 1. npm安装 icon包 npm install element-plus/icons-vue2.注册到全局组件中 同时注册到全局组件中&#xff0c;或者按需单独引入&#xff0c;这里只介绍全局引入。 import { createApp } from vue import { cre…

Python易错点总结

目录 多分支选择结构 嵌套选择 用match模式识别 match与if的对比 案例&#xff1a;闰年判断 三角形的判断 用whlie循环 高斯求和 死循环 用for循环 ​编辑continue​编辑 whlie与else结合 pass 序列 列表&#xff08;有序&#xff09; 元组&#xff08;有序&…