四:一起动手实现用户标签系统 - 底层标签读写组件的实现
4.1: 建立用户标签表SQL
``CREATE TABLE
t_user_tag(
user_idbigint NOT NULL DEFAULT -1 COMMENT '用户 id',
tag_info_01bigint NOT NULL DEFAULT '0' COMMENT '标签记录字段',
tag_info_02bigint NOT NULL DEFAULT '0' COMMENT '标签记录字段',
tag_info_03bigint NOT NULL DEFAULT '0' COMMENT '标签记录字段',
create_timedatetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时 间',
update_timedatetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (
user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_bin
COMMENT='用户标签记录';
###4.2:service层接口
```package com.laoyang.provider.service;
import com.laoyang.constants.UserTagsEnum;
/**
* @author:Kevin
* @create: 2023-08-01 09:53
* @Description:
*/
public interface IUserTagService {
/**
* 设置标签 只能设置成功一次
*
* @param userId
* @param userTagsEnum
* @return
*/
boolean setTag(Long userId, UserTagsEnum userTagsEnum);
/**
* 取消标签
*
* @param userId
* @param userTagsEnum
* @return
*/
boolean cancelTag(Long userId, UserTagsEnum userTagsEnum);
/**
* 是否包含某个标签
*
* @param userId
* @param userTagsEnum
* @return
*/
boolean containTag(Long userId,UserTagsEnum userTagsEnum);
}
```package com.laoyang.provider.service.impl;
import com.laoyang.common.utils.ConvertBeanUtils;
import com.laoyang.constants.UserTagFieldNameConstants;
import com.laoyang.constants.UserTagsEnum;
import com.laoyang.dto.UserTagDTO;
import com.laoyang.framework.redis.key.UserProviderCacheKeyBuilder;
import com.laoyang.provider.dao.mapper.IUserTagMapper;
import com.laoyang.provider.dao.po.UserTagPO;
import com.laoyang.provider.service.IUserTagService;
import com.laoyang.usils.TagInfoUtils;
import jakarta.annotation.Resource;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import java.nio.charset.StandardCharsets;
/**
- @author:Kevin
- @create: 2023-08-01 09:54
- @Description:
*/
public class UserTagServiceImpl implements IUserTagService {
@Resource
private IUserTagMapper userTagMapper;
@Resource
private RedisTemplate<String, String> redisTemplate;
private RedisTemplate<String, UserTagDTO> userTagDTORedisTemplate;
@Resource
private UserProviderCacheKeyBuilder cacheKeyBuilder;
@Override
public boolean setTag(Long userId, UserTagsEnum userTagsEnum) {
boolean updateStatus = userTagMapper.setTag(userId,userTagsEnum.getFieldName(),userTagsEnum.getTag()) > 0;
if (updateStatus){
String redisKey = cacheKeyBuilder.buildtagInfoKey(userId);
userTagDTORedisTemplate.delete(redisKey);
return true;
}
String key = cacheKeyBuilder.buildTagLockKey(userId);
//TODO 分布式锁 实现多个线程之间对同一个资源的互斥访问,保证同一时间只有一个线程能够获取到锁并执行相应的操作
String setNxResult = redisTemplate.execute(new RedisCallback<String>() {
@Override
public String doInRedis(RedisConnection connection) throws DataAccessException {
RedisSerializer keySerializer = redisTemplate.getKeySerializer();
RedisSerializer valueSerializer = redisTemplate.getValueSerializer();
String result = (String) connection.execute("set", keySerializer.serialize(key),
valueSerializer.serialize("-1"),
"NX".getBytes(StandardCharsets.UTF_8),
"EX".getBytes(StandardCharsets.UTF_8),
"3".getBytes(StandardCharsets.UTF_8));
return result;
}
});
if (!"OK".equals(setNxResult)){
return false;
}
UserTagPO userTagPO = userTagMapper.selectById(userId);
if (userTagPO!=null){
return false;
}
userTagPO = new UserTagPO();
userTagPO.setUserId(userId);
userTagMapper.insert(userTagPO);
updateStatus = userTagMapper.setTag(userId,userTagsEnum.getFieldName(),userTagsEnum.getTag()) > 0;
redisTemplate.delete(key);
return updateStatus;
}
@Override
public boolean cancelTag(Long userId, UserTagsEnum userTagsEnum) {
boolean cancleStatus = userTagMapper.cancelTag(userId,userTagsEnum.getFieldName(),userTagsEnum.getTag()) > 0;
if (cancleStatus){
return false;
}
String redisKey = cacheKeyBuilder.buildtagInfoKey(userId);
userTagDTORedisTemplate.delete(redisKey);
return true;
}
@Override
public boolean containTag(Long userId, UserTagsEnum userTagsEnum) {
UserTagDTO userTagDTO = this.queryByUserId(userId);
if (userTagDTO == null) {
return false;
}
String queryFieldName = userTagsEnum.getFieldName();
if
(UserTagFieldNameConstants.TAG_INFO_01.equals(queryFieldName)) {
return
TagInfoUtils.isContain(userTagDTO.getTagInfo01(),
userTagsEnum.getTag());
} else if
(UserTagFieldNameConstants.TAG_INFO_02.equals(queryFieldName)) {
return
TagInfoUtils.isContain(userTagDTO.getTagInfo02(),
userTagsEnum.getTag());
} else if
(UserTagFieldNameConstants.TAG_INFO_03.equals(queryFieldName)) {
return
TagInfoUtils.isContain(userTagDTO.getTagInfo03(),
userTagsEnum.getTag());
}
return false;
}
/**
* 从redis查询用户标签
* @param userId
* @return
*/
private UserTagDTO queryByUserId(Long userId){
String redisKey = cacheKeyBuilder.buildtagInfoKey(userId);
UserTagDTO userTagDTO = userTagDTORedisTemplate.opsForValue().get(redisKey);
if (userTagDTO != null){
return userTagDTO;
}
UserTagPO userTagPO = userTagMapper.selectById(userId);
if (userTagPO == null){
return null;
}
userTagDTO = ConvertBeanUtils.convert(userTagPO,UserTagDTO.class);
userTagDTORedisTemplate.opsForValue().set(redisKey, userTagDTO);
return userTagDTO;
}
}
```
说明:我们使用了redis作为缓存,mybatisplus, 并自行创建了redis业务主键生成工具类等等,会放在最后,先把核心代码呈现。这里说明下使用到了redis分布式实现
这段代码是使用RedisTemplate执行一个"set"命令,并设置了一些选项参数。下面对代码进行解释:
首先,通过redisTemplate.getKeySerializer()获取key的序列化器,通过redisTemplate.getValueSerializer()获取value的序列化器。
在RedisCallback的doInRedis方法中,通过RedisConnection的execute方法执行"set"命令。
参数中,keySerializer.serialize(key)将key序列化为字节数组,valueSerializer.serialize("-1")将value序列化为字节数组。
"NX".getBytes(StandardCharsets.UTF_8)表示设置NX选项,即只有在key不存在时才进行set操作。
"EX".getBytes(StandardCharsets.UTF_8)表示设置EX选项,即设置key的过期时间为3秒。
"3".getBytes(StandardCharsets.UTF_8)表示设置key的过期时间为3秒。
connection.execute方法返回的是一个Object类型的结果,需要将其转换为String类型并返回。 总体来说,这段代码的作用是在Redis中执行一个set命令,将key和value存储到Redis中,并设置了过期时间和NX选项,确保只有在key不存在时才进行set操作。
当多个节点同时尝试执行set操作来设置同一个key时,只有一个节点能够成功设置,因为Redis中的set命令默认具有原子性。如果设置了NX选项,即只有在key不存在时才进行set操作,那么只有第一个节点能够成功设置该key,其他节点将无法设置。 通过利用这个特性,可以将某个共享资源对应的key作为锁的名称,多个节点试图通过set操作来竞争该锁。只有一个节点能够成功设置该锁的key,即获得了分布式锁。其他节点则在设置失败后,可以选择等待或者进行其他处理。 同时,为了避免因为某个节点获得锁后发生故障而导致锁一直无法释放,还可以为锁设置过期时间。当锁的持有者在一定时间后未能释放锁,锁将自动过期并被其他节点获取。 综上所述,通过使用Redis的set操作和一些选项参数,可以实现简单的分布式锁。多个节点可以通过竞争设置同一个key来获得锁,并通过设置过期时间来避免因为锁的持有者发生故障而导致锁一直无法释放。
————————————————