移动端网站
用户可以快速找到志同道合(比如目标相同,发展阶段相同,当前状态相同)的朋友组队学习
需求分析
用户之间怎么寻找(匹配)?
通过标签。用户要有标签,标签要有分类,比如,学习方向:Java / c++。年级:大一 / 大二
有了标签,用户通过标签主动搜索
找到了伙伴,可以一起组队
- 创建队伍
- 邀请伙伴
- 解散队伍
- 根据标签查询队伍
- 加入队伍
允许用户修改标签
首页推荐标签相似的伙伴
技术栈
前端:
- Vue3
- Vant3,移动端组件库
- Vite2,打包工具
- Axios,请求
后端
-
Spring Boot、Mybatis-Plus
-
Redis
-
MySQL
-
Swagger + Knife4j
项目计划(1)
前端初始化
设计和开发前端基础布局
数据库表设计
后端初始化
搭建增删改查
开发后端:通过标签搜索用户
前端初始化
用脚手架初始化项目
- Vue CLI:https://cli.vuejs.org/zh/
- Vite:https://cn.vitejs.dev/guide/(此项目选择vite)
整合移动端组件库 Vant。Vant 提供了按需引入,可以减少代码体积
https://vant-contrib.gitee.io/vant/v3/#/zh-CN/quickstart#fang-fa-er.-an-xu-yin-ru-zu-jian-yang-shi
所有命令
npm init vite@latest
npm install
npm i vant
npm i unplugin-vue-components -D
前端的基础布局
设计
NavBar 导航栏:
- 当前页面名称
- 搜索
内容展示
TabBar 标签栏:
-
主页
-
队伍
-
我的
开发
很多页面要复用组件/样式,重复写很麻烦,不利于维护,所以抽象一个通用的布局(Layout)
在src目录下新建一个layouts目录,新建一个BasicLayout.vue文件,将文档中的代码复制到BasicLayout中,稍作修改
NavBar导航栏:https://vant-contrib.gitee.io/vant/v3/#/zh-CN/nav-bar#slots
Tabbar 标签栏:https://vant-contrib.gitee.io/vant/v3/#/zh-CN/tabbar
在src目录下新建一个pages目录,新建三个文件Index.vue、Team.vue、User.vue。
我们点击不同的 tab栏,active 会发生变化,v-model是双向绑定的, 通过判断 active 的值来加载相应的页面
<script setup lang="ts">
import {ref} from "vue";
import Index from "../pages/Index.vue"
import Team from "../pages/Team.vue";
import User from "../pages/User.vue";
const onClickLeft = () => alert('左');
const onClickRight = () => alert('右');
const active = ref('index');
const onChange = (active: string) => console.log(active);
</script>
<template>
<van-nav-bar
title="标题"
left-arrow
@click-left="onClickLeft"
@click-right="onClickRight"
>
<template #right>
<van-icon name="search" size="18"/>
</template>
</van-nav-bar>
<div id="content">
<template v-if="active === 'index'">
<Index />
</template>
<template v-if="active === 'team'">
<Team />
</template>
<template v-if="active === 'user'">
<User />
</template>
</div>
<van-tabbar v-model="active" @change="onChange">
<van-tabbar-item icon="home-o" name="index">主页</van-tabbar-item>
<van-tabbar-item icon="search" name="team">队伍</van-tabbar-item>
<van-tabbar-item icon="friends-o" name="user">我的</van-tabbar-item>
</van-tabbar>
</template>
<style scoped>
</style>
数据库设计
标签表
分析标签有哪些,分类
性别:男、女
方向:Java、C++
正在学:Spring
阶段:小学、初中、高中、大一、大二、大三、大四、待业、已就业、研一、研二、研三
个人特质:乐观、焦虑、平静、单身、有对象
当前目标:考研、春招、秋招、社招、考公、转行、跳槽
id,bigint,主键
标签名,varchar,(非空,唯一索引)
上传标签的用户,userId,bigint(如果要根据 userId 查已上传标签的话,最好加上,普通索引)
父标签id,parentId,bigint(这里有点像emp表,员工的上司)
是否为父标签,isParent,tinyint(布尔不够灵活)
创建时间,createTime,datetime
更新时间,updateTime,datetime
是否删除,isDelete,tinyint(0,1)
我们的需求中,需要查询所有标签,及其父标签,也就是标签分组
查询所有标签,能得到父标签id,根据这个id进行分组,可以查出所有子标签
标签不应该写死,如果是固定写死的,直接在后端用枚举就可以了,没必要建表
用户的标签是会改变的,比如用户正在学spring,之后可能学redis。让用户可以自定义标签
CREATE TABLE `tag` (
`id` tinyint UNSIGNED NOT NULL AUTO_INCREMENT,
`tag_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '标签名称',
`user_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '用户id',
`parent_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '父标签id',
`is_parent` tinyint UNSIGNED NULL DEFAULT NULL COMMENT '0 - 不是,1 - 是父标签',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`is_deleted` tinyint UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否删除(0否,1是)',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uniIdx_tag_name`(`tag_name` ASC) USING BTREE,
INDEX `idx_user_id`(`user_id` ASC) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
用户表
用户要有标签,两种方案
-
方案一:直接在用户表加 tags 字段,[“java”,“男”],存json字符串
优点:查询方便,不用新建关联表
比如查询 id 为111用户的所有标签
select tags from user where id = 111
比如查询有”java“标签的用户,可以用like
-- 查询"java"标签下的用户 SELECT * FROM user WHERE tags LIKE '%java%'
标签放在用户表也很合理,是用户的固有属性(用户画像)
比如要查用户列表,要先查关系表,拿到这些用户的标签id,用标签id再去标签表里查询(很麻烦)
-
方案二:加一个关联表,记录用户和标签的关系
关联表的应用场景:正查,反查。比如查询某个用户有多少标签,查询某个标签下有多少用户
缺点:要多建一个表,多维护一个表
根据标签查用户方便,但是如果查询某个用户的所用标签,就要关联查询
用户量不多的时候,方案一的 like 查询不会比方案二的关联查询慢多少
如果用户量增多,可以用缓存,标签不是经常变化的数据
企业大项目开发中,尽量减少关联查询,因为影响拓展性
最终选择方案一
CREATE TABLE `user` (
`id` tinyint UNSIGNED NOT NULL AUTO_INCREMENT,
`user_account` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '账号',
`user_password` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '密码',
`nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '昵称',
`avatar_url` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '头像',
`gender` tinyint UNSIGNED NULL DEFAULT NULL COMMENT '性别',
`profile` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '个人简介',
`email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '邮箱',
`phone` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '电话号码',
`is_normal` tinyint UNSIGNED NOT NULL DEFAULT 1 COMMENT '用户状态(0不正常,1正常)',
`user_role` tinyint UNSIGNED NOT NULL DEFAULT 0 COMMENT '用户角色(user/admin)',
`tags` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '标签json列表',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`is_deleted` tinyint UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否删除(0否,1是)',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 9 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
后端初始化
创建springboot项目,引入一些依赖。关于Java版本,boot3.0以上的必须用17。以下的用8
spring-boot-starter-web
spring-boot-starter-test
lombok
commons-lang3
gson
redis
mybatis-plus-spring-boot3-starter
mysql
搭建增删改查
application.yml 配置数据源,idea连接数据源,MybatisX生成代码,写增删改查接口
封装通用返回类
自定义错误码
封装返回工具类
自定义异常
封装全局异常处理
…
需求:注册
- 接收数据,UserRegisterRequest:用户名,密码,确认密码(重复输入密码)
- 校验数据
- dto对象非空,里面的参数非空
- 账号要大于4位,小于12位
- 密码要大于8位,小于20位
- 账号不包含特殊字符
- 密码和确认密码相同
- 账号在数据库中不存在
- 存储数据,返回注册结果
- 密码加密
- 插入数据库,返回用户id
需求:登录
- 接收数据,UserLoginRequest:用户名,密码
- 校验数据
- dto对象非空,里面的参数非空
- 账号要大于4位,小于12位
- 密码要大于8位,小于20位
- 账号不包含特殊字符
- 密码和确认密码相同
- 账号在数据库中不存在
- 查询数据,返回登录结果
- 密码加密
- 根据账号和加密之后的密码查询用户是否存在
- 用户信息脱敏,隐藏敏感信息,防止数据库字段泄露
- 保存用户登录态(session)
- 返回脱敏后的用户信息
需求:获取当前登录用户和退出登录
getAttribute
removeAttribute
需求:通过标签搜索用户
方式一:SQL查询
-
接收数据,用户选择多个标签进行搜索,List,标签列表
-
校验数据,List集合是否为null,或者是一个空的集合
-
查询数据
-- 比如查询标签有 java 和 男 的用户 select * from user where tags like ‘%”java“%’ and tags like ‘%男%’;
使用mybatis-plus条件构造器,
循环标签列表,构建查询条件,.like()
-
返回数据,把查询到的多个用户,进行脱敏返回,List
方式二:内存查询
-
方法的参数、返回值不变
-
校验数据,List集合是否为null,或者是一个空的集合
-
查询数据。先把所有用户查出来。在内存中判断是否有包含要求的标签
-
遍历所有用户。拿到每个用户的tags的值(json 字符串),
-
判断该字符串是否null,排除标签为null的用户
-
然后 json 格式字符串转成 Set,一个集合临时存一个用户的所有标签
这里用List还是Set?选择Set。[“java”,“男”,“大一”]—>Set
序列化库选择:
gson,谷歌,√
fastjson,阿里
jackson
kryo
-
遍历标签名列表,如果该用户的所有标签,包含,用户传过来的所有标签,就符合条件
比如:A{1,2,3,4} B{1,3} B是A的子集,就符合条件
-
-
返回数据。对于符合条件的用户,通过
map
方法将其映射为脱敏用户,然后通过collect
方法收集到一个新的列表中,返回。
测试哪种方式更快
有bug,要找java标签的用户,但java和javascript标签的用户都被找出来了。在模糊查询的时候,标签外面加双引号
userQueryWrapper.like("tags", "\"" + tagName + "\"");
利用 System.currentTimeMillis() 测试时间,发现差不多,注意数据库第一次连接是很耗时的。目前因为数据量太少,测试结果没有参考性,之后再测试。
两种方式如何选择?
-
可以测试查询时间和参数的关系,比如用户选择的标签数量达到多少的时候,两种方式谁更快
-
内存查询可以通过并发进一步优化,所以如果能保证数据库连接和内存足够,可以并发同时查询,谁先返回用谁
-
SQL查询和内存查询相结合,比如先用SQL过滤掉部分tag