项目地址:
@
前端:
1.Vue 3 开发框架 (提高页面开发的效率)
2. Vant UI (基于 Vue 的移动端组件库)(React 版 Zent)
3.Vite 2(打包工具,快!)181862491
4.Nginx来单机部署
后端:
脚手架初始化项目:
$ yarn create vite
npm install # 安装相关依赖
Vant : https://vant-ui.github.io/vant/v3/#/zh-CN
npm init vite@latest
npm install # 安装相关依赖,注意需要在一个我们创建生成的一个目录位置下,就是我们当前这个项目名下。
执行该代码。
npm run dev # 启动该项目
打开浏览器访问:测试
整合组件库:Vant:
npm i vite-plugin-style-import@1.4.1 -D
npm i vite-plugin-style-import@1.4.1 -D # 配置按需引入
安装 Vant 插件
npm i vant # 未安装 vant 是无法识别到 vant 的样式,组件内容的。
开发页面经验:
如果存在广告的话,就一定要预留广告位置。
设计:
从上到下依次为:
开发:
很多页面复用组件/样式,重复写很麻烦,不利于维护,所以抽象一个通用的布局。可以定义在一个目录当中(layouts)
组件化
建议用标签,不要用分类,更灵活
标签分类:这里暂定为 8 个标签
表设计:
id int 主键
标签名 varchar 非空 ( 必须唯一,唯一索引)
上传标签的用户 userId int (如果要根据 userId 查已上传的标签的话,最好加上,普通索引)
父标签 id,parentId,int(分类)
是否为父标签 isParent tinyint(0 不是父标签,1- 父标签),布尔类型,只有两个,可能不太够,这里用 tinyint 0-255 来替换。
创建时间 createTime,datetime
更新时间 updateTime datetime
是否删除:isDelete tinyint(0,1)
create table tag
(
id bigint auto_increment comment 'id'
primary key,
tagName varchar(256) null comment '标签名称',
userId bigint null comment '用户id',
parenId bigint null comment '父标签 id',
isParent tinyint null comment '0 - 不是, 1- 父标签',
createTime datetime default CURRENT_TIMESTAMP null comment '创建时间',
updateTime datetime default CURRENT_TIMESTAMP null comment '更新时间',
isDelete tinyint default 0 not null comment '是否删除 0 1(逻辑删除)'
)
comment '标签';
-- 为 user 表,添加上一个新的 tags 字段
alter table user add column tags varchar(1024) null comment '标签列表';
验证表设计,是否合理
对于自己设计的数据表,是否合理,可以通过,反向验证,反向思考。
主要思考该设计能不能满足业务操作诉求。
比如:这里:
按父标签 Id 进行分组,能实现
根据 id 查询,能实现
思考: 怎么给用户表补充标签。
一定要根据自己的实际需求来
常见方案有 2 种:
json 字符串
,比如:["java","男"]。注意:json 当中字符串,是使用 双引号
的。优点: 查询方便,不用新建关联表,标签是用户的固有属性(除了该系统,其他系统可能要用到,标签是用户的固有属性) 节省开发成本。
缺点: 根据标签查用户时,只能用模糊查询,或者遍历用户列表,性能不高。
关联表的优点(适用场景):查询灵活,可以正查,反查
提示:企业大项目开发中尽量减少关联查询,因为关联查询很是影响扩展性(比如:新建一张表,记录关系),而且会影响查询性能。
第一种方式:如果慢了,可以用缓存。
优化:这里我们会经常对标签名。
-- 用户
-- auto-generated definition
create table user
(
username varchar(256) null comment '用户昵称',
id bigint auto_increment comment 'id'
primary key,
userAccount varchar(256) null comment '账号',
avatarUrl varchar(1024) null comment '用户头像',
gender tinyint null comment '性别',
userPassword varchar(512) not null comment '密码',
phone varchar(128) null comment '电话',
email varchar(512) null comment '邮箱',
userStatus int default 0 not null comment '状态-0-正常',
createTime datetime default CURRENT_TIMESTAMP null comment '创建时间',
updateTime datetime default CURRENT_TIMESTAMP null comment '更新时间',
isDelete tinyint default 0 not null comment '是否删除 0 1(逻辑删除)',
userRole int default 0 not null comment '用户角色 0- 普通用户 1 - 管理员 2 - vip',
planetCode varchar(512) null comment '用户的编号',
tags varchar(1024) null comment '标签列表 json'
)
comment '用户';
面试点:
按标签搜索用户:
2 种实现方式:
方案选择:
建议通过实际测试来分析,那种查询比较快,数据量大的时候验证效果更明显。
判断点:
方式一:SQL 查询:实现简单,可以通过拆分查询进一步优化。
/**
* 根据标签搜索用户
*
* @param tagNameList 用户要拥有的标签
* @return List
*/
@Override
public List searchUserByTags(List tagNameList) {
// 方式1: SQL 查询:实现简单,可以通过拆分查询进一步优化。
if (CollectionUtils.isEmpty(tagNameList)) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
QueryWrapper queryWrapper = new QueryWrapper();
//// 拼接 and 查询
//// like '%Java%' and like '%C++%'
for (String tagName : tagNameList) {
// // column 是数据表当中的字段名,不可以随便写
queryWrapper = queryWrapper.like("tags", tagName);
}
//
List userList = userMapper.selectList(queryWrapper);
//
//// 使用 stream 进行过滤显示
return userList.stream().map(this::getSafetyUser).collect(Collectors.toList());
}
方式二:内存查询:灵活,可以通过并发进一步优化。就是查询出来后,通过 steam 进行处理,过滤。
package com.rainbowsea.yupao.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.rainbowsea.yupao.common.ErrorCode;
import com.rainbowsea.yupao.exception.BusinessException;
import com.rainbowsea.yupao.model.User;
import com.rainbowsea.yupao.service.UserService;
import com.rainbowsea.yupao.mapper.UserMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.DigestUtils;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import static com.rainbowsea.yupao.contant.UserConstant.USER_LOGIN_STATE;
/**
* @author huo
* @description 针对表【user(用户)】的数据库操作Service实现
* @createDate 2025-04-14 16:03:21
*/
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl
implements UserService {
@Resource
private UserMapper userMapper;
/**
* 根据标签搜索用户
*
* @param tagNameList 用户要拥有的标签
* @return List
*/
@Override
public List searchUserByTags(List tagNameList) {
// 方式1: SQL 查询:实现简单,可以通过拆分查询进一步优化。
if (CollectionUtils.isEmpty(tagNameList)) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
// 方式2: 内存查询:灵活,可以通过并发进一步优化。就是查询出来后,通过 steam 进行处理,过滤。
// 1. 先查询所有用户
QueryWrapper queryWrapper = new QueryWrapper();
List 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 集合对象转换为 JSON 字符串
Set tempTagNameSet = gson.fromJson(tagsStr,new TypeToken>(){}.getType());
for (String tagName : tagNameList) {
// 判断 set 集合当中是否含有该 tagName 标签,不含有该标签就返回 false 过滤掉
if(!tempTagNameSet.contains(tagName)) {
return false;
}
}
return true;
}).map(this::getSafetyUser).collect(Collectors.toList());
}
}
package com.rainbowsea.yupao.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.rainbowsea.yupao.common.ErrorCode;
import com.rainbowsea.yupao.exception.BusinessException;
import com.rainbowsea.yupao.model.User;
import com.rainbowsea.yupao.service.UserService;
import com.rainbowsea.yupao.mapper.UserMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.DigestUtils;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import static com.rainbowsea.yupao.contant.UserConstant.USER_LOGIN_STATE;
/**
* @author huo
* @description 针对表【user(用户)】的数据库操作Service实现
* @createDate 2025-04-14 16:03:21
*/
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl
implements UserService {
@Resource
private UserMapper userMapper;
/**
* 根据标签搜索用户
*
* @param tagNameList 用户要拥有的标签
* @return List
*/
@Override
public List searchUserByTags(List tagNameList) {
// 方式1: SQL 查询:实现简单,可以通过拆分查询进一步优化。
if (CollectionUtils.isEmpty(tagNameList)) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
//QueryWrapper queryWrapper = new QueryWrapper();
//// 拼接 and 查询
//// like '%Java%' and like '%C++%'
//for (String tagName : tagNameList) {
// // column 是数据表当中的字段名,不可以随便写
// queryWrapper = queryWrapper.like("tags", tagName);
//
//}
//
//List userList = userMapper.selectList(queryWrapper);
//
//// 使用 stream 进行过滤显示
//return userList.stream().map(this::getSafetyUser).collect(Collectors.toList());
// 方式2: 内存查询:灵活,可以通过并发进一步优化。就是查询出来后,通过 steam 进行处理,过滤。
// 1. 先查询所有用户
QueryWrapper queryWrapper = new QueryWrapper();
List 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 集合对象转换为 JSON 字符串
Set tempTagNameSet = gson.fromJson(tagsStr,new TypeToken>(){}.getType());
for (String tagName : tagNameList) {
// 判断 set 集合当中是否含有该 tagName 标签,不含有该标签就返回 false 过滤掉
if(!tempTagNameSet.contains(tagName)) {
return false;
}
}
return false;
}).map(this::getSafetyUser).collect(Collectors.toList());
}
}
序列化:Java 对象转成 JSON
反序列化:把 JSON 转为 Java 对象
Java json 序列化库有很多:
com.google.code.gson
gson
2.8.5
公共线程池,可能会存在问题,就是:当如果你的一个查询数据库的时候,都是耗光了这个公共线程池的话,那么就会存在问题:eg:Java 8 中的 ParalleStream 的坑
Java 8 特性:
Vue-Router: https://router.vuejs.org/zh/guide/#html
Vue-Router: 其实就是帮助你根据不同的 url 来展示不同的页面(组件),不用自己写 if / else
路由配置影响整个项目,所以建议单独用 config 目录,单独的配置文件去集中定义和管理。
有些组件库可能自带了和 Vue-Router 的整合,所以尽量先看组件文档,节省时间。
“在这个最后的篇章中,我要表达我对每一位读者的感激之情。你们的关注和回复是我创作的动力源泉,我从你们身上吸取了无尽的灵感与勇气。我会将你们的鼓励留在心底,继续在其他的领域奋斗。感谢你们,我们总会在某个时刻再次相遇。”
参与评论
手机查看
返回顶部