伙伴匹配介绍
介绍:帮助大家找到志同道合的伙伴(h5项目)
1 需求分析
- 用户去添加标签,标签的分类(要有哪些标签、怎么把标签进行分类)学习方向java/c++,工作/大学
- 主动搜索:允许用户根据标签去搜索其他用户
- Redis 缓存技术 + 本地
- 组队(允许超过人数、替补)
- 创建房间
- 加入队伍
- 根据标签查询队伍
- 邀请其他人
- 允许用户去修改标签
- 推荐
- 相似度计算 + 本地分布式
2 技术选型
2.1 前端
- Vue 3 开发框架(提高页面开发的效率)
- Vant UI (基于Vue的移动端组件库)(React版本Zent)
- Vite(打包工具,非常快)
- Nginx
2.2 后端
- Java编程语言+SpringBoot框架
- SpringMVC+MyBatis+MyBatisPlus
- Swagger+Knife4i接口文档
2.3 数据库
- Mysql 数据库
- Redis 缓存
3 项目开发(数据库设计)
标签的分类(要有哪些标签、怎么把标签进行分类)
3.1 设计标签表(分类表)
建议用标签,不要用分类、更灵活
性别 | 男、女 |
---|---|
方向 | Java、C++、Go、前端 |
正在学 | Spring |
目标 | 考研、春招、秋招、社招、考公、竞赛(蓝桥杯)、转行、跳槽 |
段位 | 初级、中级、高级、王者 |
身份 | 大一、大二、大三、大四、学生、待业、已就业、研一、研二、研三 |
状态 | 乐观、有点丧、一般、单身、已婚、有对象 |
用户自己定义标签
3.1.1 字段
字段名 | 类型 | 备注 |
---|---|---|
id | int | 主键 |
标签名 | varchar | 标签名 非空(必须唯一,唯一索引) |
userld | int | 上传标签的用户(如果要根据userId查已上传标签的话,最好加上,普通索引) |
parentId | int | 父标签id |
isParent | tinyint(0,1) | 是否为父标签(分类)0-不是父标签 1- 是父标签 |
createTime | datatime | 创建时间 |
updateTime | datatime | 更新时间 |
isDelete | tinyint(0,1) | 是否删除 |
怎么查询所有标签,并且把标签分好组?按父标签id分组,能实现 √
根据父标签查询子标签?根据id查询,能实现 √
3.2 修改用户表
用户有哪些标签?
根据自己的实际需求来!!!此处选择第一种
- 直接在用户表补充tags字段,['Java',男] 存json字符串
- 优点:查询方便、不用新建关联表,标签是用户的固有属性(除了该系统、其他系统可能要用到,标签是用户 的固有属性)节省开发成本
- 查询用户列表,查关系表拿到这100个用户有的所有标签id,再根据标签id去查标签表。
- 哪怕性能低,可以用缓存。
- 缺点:用户表多一列,会有点
- 加一个关联表,记录用户和标签的关系
- 关联表的应用场景:查询灵活,可以正查反查
- 缺点:要多建一个表、多维护一个表
- 重点:企业大项目开发中尽量减少关联查询,很影响扩展性,而且会影响查询性能
4 项目开发(前端)
4. 1 项目初始化
4.1.1 用脚手架初始项目
yarn create vite
输入项目名称 yupao-frontend
选着vue
框架和ts
语言风格
4.1.2 添加组件库
bash
# yarn (如果安装失败 用npm yarn 可能权限不足)
yarn add vite-plugin-style-import@1.4.1 -D
# 或者 npm
npm i vite-plugin-style-import@1.4.1 -D
4.1.3 安装vant
npm i vant
4.1.3.1 引入组件-按需引入组件样式
npm install -D unplugin-vue-components @vant/auto-import-resolver
4.1.3.2 测试配置
ts
// vite.config.ts
import vue from '@vitejs/plugin-vue';
import AutoImport from 'unplugin-auto-import/vite';
import Components from 'unplugin-vue-components/vite';
import { VantResolver } from '@vant/auto-import-resolver';
export default {
plugins: [
vue(),
AutoImport({
resolvers: [VantResolver()],
}),
Components({
resolvers: [VantResolver()],
}),
],
};
//main.ts
import {createApp} from 'vue'
import App from './App.vue'
// 1. 引入你需要的组件
import { Button } from 'vant';
// 2. 引入组件样式
import 'vant/lib/index.css';
const app = createApp(App);
app.use(Button);
app.mount('#app')
//App.vue
<template>
<HelloWorld msg="Hello Vue 3 + TypeScript + Vite" />
<van-button type="primary">主要按钮</van-button>
<van-button type="success">成功按钮</van-button>
<van-button type="default">默认按钮</van-button>
<van-button type="danger">危险按钮</van-button>
<van-button type="warning">警告按钮</van-button>
</template>
4.2 前端主页 + 组件概览
4.2.1 设计
导航条:展示当前页面名称
主页搜索框 => 搜索页 => 搜索结果页面(标签筛选页)
内容
tab栏:
- 主页(推荐页+广告)
- 搜索框
- banner
- 推荐信息流
- 队伍页
- 用户页(消息 - 暂时考虑发邮件 )
4.2.2 开发
很多页面要复用组件/样式,重复写很麻烦、不利于维护,所以抽象一个通用的布局
4.3 前端页面开发
4.3.1 路由整合
有些组件库可能自带了和Vue-Router的整合,所以尽量先看文档。
vue
<!--内容-->
<div id="content">
<router-view></router-view>
</div>
<!--底部tabbar-->
<van-tabbar route @change="onChange">
<van-tabbar-item to="/" icon="home-o" name="index">主页</van-tabbar-item>
<van-tabbar-item to="/team" icon="search" name="team">队伍</van-tabbar-item>
<van-tabbar-item to="/user" icon="friends-o" name="user">个人</van-tabbar-item>
</van-tabbar>
5 项目开发(后端)
5.1 项目初始化
idea 初始化
5.2 接口开发(搜索标签)
5.2.1 SQL查询(现简单,可以通过拆分查询进一步优化)
- 允许用户传入多个标签,多个标签都存在才搜索出来and。like‘%Java%' and like '%C++%'
- 允许用户传入多个标签,有任何一个标签存在就能搜索出来or。like '%Java%' or like '%C++%'
5.2.2 内存查询(灵活,可以通过并发进一步优化)
- 如果参数可以分析,根据用户的参数去选择查询方式,比如标签数
- 如果参数不可分析,并且数据库连接足够、内存空间足够,可以并发同时查询,谁先返回用谁。
- 还可以SQL查询与内存计算相结合,比如先用SQL过滤掉部分tag
建议通过实际测试来分析哪种查询比较快,数据量大的时候验证效果更明显!
6 项目开发(部署上线)
7 补补知识
7.1 开发页面经验
- 多参考
- 从整体到局部
- 先想清楚页面要做成什么样子,再写代码
7.2 SQL语言分类
- DDL define 建表、操作表
- DML manage 更新删除数据,影响实际表里的内容
- DCL control 控制,权限
- DQL query 查询 select
7.3 解析ISON字符串
序列化:java对象转成json
反序列化:把json转为java对象
java json 序列化库
- gson (Google出品)
- fastjson(阿里出品,快,但是有漏洞)
- jackson
- kryo
7.4 java8特性
- stream / parallelStream 流失处理
- Optional可选类
8 代码板子
内存查询与SQL查询比较
java
/**
* 根据标签搜索用户 SQL查询
* <p>
* 根据标签搜索用户 SQL查询
*/
@Override
public List<User> searchUsersByTags(List<String> tagNameList) {
if (CollectionUtils.isEmpty(tagNameList)) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
for (String tagName : tagNameList) {
queryWrapper = queryWrapper.like("tags", tagName);
}
List<User> userList = userMapper.selectList(queryWrapper);
return userList.stream().map(this::getSafetyUser).collect(Collectors.toList());
}
/**
* 根据标签搜索用户 内存查询
*/
@Override
public List<User> searchUsersByTags(List<String> tagNameList) {
if (CollectionUtils.isEmpty(tagNameList)) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
//1.先查询所有的用户
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
List<User> userList = userMapper.selectList(queryWrapper);
Gson gson = new Gson();
//2.遍历用户列表,查询标签列表 内存中判断是否包含要求的标签
return userList.stream().filter(user -> {
String tagsStr = user.getTags();
if (StringUtils.isBlank(tagsStr)) {
return false;
}
Set<String> temptagNameList = gson.fromJson(tagsStr, new TypeToken<Set<String>>() {
}.getType());
for (String tagName : tagNameList) {
if (!temptagNameList.contains(tagName)) {
return false;
}
}
return true;
}).map(this::getSafetyUser).collect(Collectors.toList());
}
// 测试
@Test
public void testsearchUsersByTags() {
List<String> tags = Arrays.asList("java");
List<User> userList = userService.searchUsersByTags(tags);
Assertions.assertNotNull(userList);
}