这里写目录标题
美食社交APP
1、抢购大额代金券功能
1.1、数据库表
围绕着秒杀代金券这个功能,设计了三张表,分别是代金券表、抢购活动表、订单表
首先就是代金券表
字段名 | 字段类型 | 注释 |
---|---|---|
主键id | int(10) | |
代金券标题title | varchar(255) | |
缩略图thumbnail | varchar(255) | 缩略图一般存储在图片服务器中 |
代金券抵扣金额amount | int(11) | |
代金券售价price | decimal(10,2) | 这个字段的类型是decimal,浮点数是直接把小数转化为二进制,多数小数转化为二进制是有误差的,也就是说不能精确表示;decimal以字符串的形式保存精确的原始数值,精度高,而且没有误差 |
代金券状态status | int(10) | 代金券的状态,-1表示过期,0表示下架,1表示上架 |
开始使用时间start_use_time | datetime | 使用datetime类型来存储该字段 |
过期时间expire_time | datetime | |
验证餐厅redeem_restaurant_id | int(10) | |
库存stock | int(11) | |
剩余数量stock_left | int(11) | |
创建时间create_time | datetime | |
更新时间update_time | datetime |
其次是抢购活动表
字段名 | 字段类型 | 注释 |
---|---|---|
主键id | int(11) | |
关联代金券的外键fk_voucher_id | int(11) | |
库存amount | int(11) | |
开始时间start_time | datetime | |
结束时间end_time | datetime | |
是否有效is_valid | int(11) | |
创建时间create_time | datetime | |
更新时间update_time | datetime |
最后是订单表
字段名 | 字段类型 | 注释 |
---|---|---|
主键id | int(11) | |
通过MySQL完成:
- 对基本参数进行校验
- 判断此代金券是否加入抢购
- 判断是否开始、结束
- 判断是否卖完
- 获取登录用户信息
- 判断登录用户是否已抢到(一个用户针对这个活动只能买一次)
- 扣库存
- 下单
出现了超售和一个人买到了多个代金券这两个问题
2、好友功能
1、大体思路
关注、取关、共同关注、粉丝
类似于这样的功能我们如果采用数据库做的话只是单纯得到用户的一些粉丝或者关注列表的话是很简单也很容易实现, 但是如果我想要查出两个甚至多个用户共同关注了哪些人或者想要查询两个或者多个用户的共同粉丝的话就会很麻烦, 效率也不会很高
redis 自带了可以实现交集、并集、差集的数据类型,set;所以我们可以借助redis来实现
不是说把所有数据都放到redis中,我们把持久化的数据方法mysql中,把查询好友通过redis中,我们可以给每一个用户在redis中存储两个集合,一个是关注集合,一个是粉丝集合
总体思路我们采用MySQL + Redis的方式结合完成。MySQL主要是保存落地数据,而利用Redis的Sets进行集合操作。Sets拥有去重(我们不能多次关注同一用户)功能**。一个用户我们存贮两个集合:一个是保存用户关注的人 另一个是保存关注用户的人.**
用到的命令是:
- SADD 添加成员;命令格式: SADD key member [member …] ----- 关注
- SREM 移除某个成员;命令格式: SREM key member [member …] -------取关
- SCARD 统计集合内的成员数;命令格式: SCARD key -------关注/粉丝个数
- SISMEMBER 判断是否是集合成员;命令格式:SISMEMBER key member ---------判断是否关注(如果关注那么只可以点击取关)
- SMEMBERS 查询集合内的成员;命令格式: SMEMBERS key -------列表使用(关注列表和粉丝列表)
- SINTER 查询集合的交集;命令格式: SINTER key [key …] --------共同关注、我关注的人关注了他
2、表结构
CREATE TABLE `t_follow` (
`id` int(11) NOT NULL AUTO_INCREMENT , //主键id
`diner_id` int(11) NULL DEFAULT NULL COMMENT '用户外键' , //用户id
`follow_diner_id` int(11) NULL DEFAULT NULL COMMENT '用户食客外键' , //用户关注的人的id
`is_valid` tinyint(1) NULL DEFAULT NULL , //当前关注的状态,因为有可能会取关,然后再关注
`create_date` datetime NULL DEFAULT NULL , //记录创建时间
`update_date` datetime NULL DEFAULT NULL , //记录更新时间
PRIMARY KEY (`id`)
)
ENGINE=InnoDB
DEFAULT CHARACTER SET=utf8 COLLATE=utf8_general_ci
AUTO_INCREMENT=6
ROW_FORMAT=COMPACT;
3、关注取关业务逻辑
先拿到当前登录用户的信息,把需要关注或者取关的这个人的id传过来,然后再传一个标记,这个标记就是表明现在是关注还是取关操作;
传到后台之后,首先做健壮性判断,做一个非空校验,参数是否合法,被关注的人的主键id有没有,是不是登录状态;
做完校验之后,需要在Mysql中进行查询,查询曾经有没有关注过这个人,存在三种情况:
- 没有关注,需要关注
- 关注了,需要取关
- 关注过,取关了,需要重新关注
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n7RnO6F0-1652334914209)(C:\Users\86151\Desktop\学习图片\20220507184509.png)]
并且这涉及到了一条sql语句,这条语句使用是用来查询关注信息的,因为主要无论关注或者取关,都需要用到这条sql语句,所以对这条sql语句尝试加索引
select id, diner_id, follow_diner_id, is_valid from t_follow where diner_id = #{dinerId} and follow_diner_id = #{followDinerId}
关注、取关 具体的代码逻辑:
/**
* 关注/取关
*
* @param followDinerId 关注的食客ID
* @param isFollowed 是否关注 1=关注 0=取关
* @param accessToken 登录用户token
* @param path 访问地址
* @return
*/
@Transactional(rollbackFor = Exception.class)
public ResultInfo follow(Integer followDinerId, int isFollowed,
String accessToken, String path) {
//根据followDinerId判断是否选择了关注对象
//获取登录用户信息
//根据当前登录用户id和需要关注的人的id去数据库中去查
//根据查出来的这个结果进行判断
//第一种情况:如果查出来的结果为空,并且传进来的标记==1,关注操作
//这个时候需要添加关注信息,把当前登录用户id和需要关注的人的id插入数据库中
//如果插入成功,那么就就往redis中放,在redis中维护了两个set集合,一个是关注集合一个是粉丝集合,把两个id分别插进去
//如果是查到了信息,但是目前处于取关状态,我们需要重新关注
//我们需要去数据库中,把这一行数据的关注状态改一下
//如果更改成功,去redis中把这两个id插进去
//如果有关注信息,并且处于关注中的状态,需要进行取关操作
//那么先去数据库中把这一行数据的关注状态改一下
//然后去redis中,从维护的两个set集合中把这两个id移除
}
4、共同关注业务逻辑
- 从redis中读取当前用户的的关注列表和被查看用户的关注列表,然后进行交集操作,获取共同关注的用户id
- 然后通过用户id去获取用户基本信息(发一次请求,把多个id拼在一起,发过去一个id集合,通过in或者exist进行批量查询)
代码逻辑:
@Value("${service.name.ms-diners-server}")
private String dinersServerName;
/**
* 共同好友
*
* @param dinerId
* @param accessToken
* @param path
* @return
*/
public ResultInfo findCommonsFriends(Integer dinerId, String accessToken, String path) {
// 是否选择了关注对象,这个关注对象就是你选择查询
AssertUtil.isTrue(dinerId == null || dinerId < 1, "请选择要查看的人");
// 获取登录用户信息
SignInDinerInfo dinerInfo = loadSignInDinerInfo(accessToken);
// 登录用户的关注信息
String loginDinerKey = RedisKeyConstant.following.getKey() + dinerInfo.getId();
// 登录用户关注的对象的关注信息
String dinerKey = RedisKeyConstant.following.getKey() + dinerId;
// 计算交集
Set<Integer> followingDinerIds = redisTemplate.opsForSet().intersect(loginDinerKey, dinerKey);
// 没有
if (followingDinerIds == null || followingDinerIds.isEmpty()) {
return ResultInfoUtil.buildSuccess(path, new ArrayList<ShortDinerInfo>());
}
// 根据 ids 查询食客信息
ResultInfo resultInfo = restTemplate.getForObject(dinersServerName + "findByIds?access_token=${accessToken}&ids={ids}",
ResultInfo.class, accessToken, StrUtil.join(",", followingDinerIds));
if (resultInfo.getCode() != ApiConstant.SUCCESS_CODE) {
resultInfo.setPath(path);
return resultInfo;
}
// 处理结果集
List<LinkedHashMap> dinerInfoMaps = (ArrayList) resultInfo.getData();
List<ShortDinerInfo> dinerInfos = dinerInfoMaps.stream()
.map(diner -> fillBeanWithMap(diner, new