??????? 一: 計(jì)算機(jī)中的二進(jìn)制
????????計(jì)算機(jī)以二進(jìn)制表示數(shù)據(jù),以表示電路中的正反。在二進(jìn)制下,一個(gè)位只有 0 和 1 。逢二進(jìn)一 位。類似十進(jìn)制下,一個(gè)位只有 0~9 。逢十進(jìn)一位。????????二: 進(jìn)制常用運(yùn)算 (位運(yùn)算)
- 與運(yùn)算(&):將兩個(gè)二進(jìn)制數(shù)的對(duì)應(yīng)位進(jìn)行與操作,只有當(dāng)兩個(gè)位都為1時(shí),結(jié)果為1。
- 或運(yùn)算(|):將兩個(gè)二進(jìn)制數(shù)的對(duì)應(yīng)位進(jìn)行或操作,只要有一個(gè)位為1,結(jié)果就為1。
- 非運(yùn)算(~):對(duì)一個(gè)二進(jìn)制數(shù)的每個(gè)位取反,將1變?yōu)?,將0變?yōu)?。
- 異或運(yùn)算(^):將兩個(gè)二進(jìn)制數(shù)的對(duì)應(yīng)位進(jìn)行異或操作,只有當(dāng)兩個(gè)位不同時(shí),結(jié)果為1。
- 左移運(yùn)算(<<):將一個(gè)二進(jìn)制數(shù)的所有位向左移動(dòng)指定的位數(shù),右邊空出的位用0填充。
- 右移運(yùn)算(>>):將一個(gè)二進(jìn)制數(shù)的所有位向右移動(dòng)指定的位數(shù),左邊空出的位用原來的最高位填充。 這些二進(jìn)制的運(yùn)算在計(jì)算機(jī)的邏輯設(shè)計(jì)、編程和數(shù)據(jù)處理中經(jīng)常使用。
無符號(hào)右移( >>> , 無符號(hào)右移就是右移之后,無論該數(shù)為正還是為負(fù),右移之后左邊都是補(bǔ)上 0 )
??????? 三: 標(biāo)簽記錄的實(shí)現(xiàn)原理
??????? 基于(或)|,與+取反(&~) 去實(shí)現(xiàn):

????????
????????2.取消標(biāo)簽 與 + 取反(&~)(兩位同時(shí)為 1,結(jié)果才為 1,否則為 0) 取消16這個(gè)標(biāo)簽

??????? 四:一起動(dòng)手實(shí)現(xiàn)用戶標(biāo)簽系統(tǒng) - 底層標(biāo)簽讀寫組件的實(shí)現(xiàn)
??????? ????????4.1: 建立用戶標(biāo)簽表SQL
CREATE TABLE `t_user_tag` (
`user_id` bigint NOT NULL DEFAULT -1 COMMENT '用戶 id',
`tag_info_01` bigint NOT NULL DEFAULT '0' COMMENT '標(biāo)簽記錄字段',
`tag_info_02` bigint NOT NULL DEFAULT '0' COMMENT '標(biāo)簽記錄字段',
`tag_info_03` bigint NOT NULL DEFAULT '0' COMMENT '標(biāo)簽記錄字段',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '創(chuàng)建時(shí)
間',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE
CURRENT_TIMESTAMP COMMENT '更新時(shí)間',
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_bin
COMMENT='用戶標(biāo)簽記錄';
??????????????? 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 {
/**
* 設(shè)置標(biāo)簽 只能設(shè)置成功一次
*
* @param userId
* @param userTagsEnum
* @return
*/
boolean setTag(Long userId, UserTagsEnum userTagsEnum);
/**
* 取消標(biāo)簽
*
* @param userId
* @param userTagsEnum
* @return
*/
boolean cancelTag(Long userId, UserTagsEnum userTagsEnum);
/**
* 是否包含某個(gè)標(biāo)簽
*
* @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 分布式鎖 實(shí)現(xiàn)多個(gè)線程之間對(duì)同一個(gè)資源的互斥訪問,保證同一時(shí)間只有一個(gè)線程能夠獲取到鎖并執(zhí)行相應(yīng)的操作
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查詢用戶標(biāo)簽
* @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, 并自行創(chuàng)建了redis業(yè)務(wù)主鍵生成工具類等等,會(huì)放在最后,先把核心代碼呈現(xiàn)。這里說明下使用到了redis分布式實(shí)現(xiàn)
這段代碼是使用RedisTemplate執(zhí)行一個(gè)"set"命令,并設(shè)置了一些選項(xiàng)參數(shù)。下面對(duì)代碼進(jìn)行解釋:
- 首先,通過redisTemplate.getKeySerializer()獲取key的序列化器,通過redisTemplate.getValueSerializer()獲取value的序列化器。
- 在RedisCallback的doInRedis方法中,通過RedisConnection的execute方法執(zhí)行"set"命令。
- 參數(shù)中,keySerializer.serialize(key)將key序列化為字節(jié)數(shù)組,valueSerializer.serialize("-1")將value序列化為字節(jié)數(shù)組。
- "NX".getBytes(StandardCharsets.UTF_8)表示設(shè)置NX選項(xiàng),即只有在key不存在時(shí)才進(jìn)行set操作。
- "EX".getBytes(StandardCharsets.UTF_8)表示設(shè)置EX選項(xiàng),即設(shè)置key的過期時(shí)間為3秒。
- "3".getBytes(StandardCharsets.UTF_8)表示設(shè)置key的過期時(shí)間為3秒。
- connection.execute方法返回的是一個(gè)Object類型的結(jié)果,需要將其轉(zhuǎn)換為String類型并返回。 總體來說,這段代碼的作用是在Redis中執(zhí)行一個(gè)set命令,將key和value存儲(chǔ)到Redis中,并設(shè)置了過期時(shí)間和NX選項(xiàng),確保只有在key不存在時(shí)才進(jìn)行set操作。
????????
????????當(dāng)多個(gè)節(jié)點(diǎn)同時(shí)嘗試執(zhí)行
set
操作來設(shè)置同一個(gè)key時(shí),只有一個(gè)節(jié)點(diǎn)能夠成功設(shè)置,因?yàn)镽edis中的set
命令默認(rèn)具有原子性。如果設(shè)置了NX
選項(xiàng),即只有在key不存在時(shí)才進(jìn)行set操作,那么只有第一個(gè)節(jié)點(diǎn)能夠成功設(shè)置該key,其他節(jié)點(diǎn)將無法設(shè)置。 通過利用這個(gè)特性,可以將某個(gè)共享資源對(duì)應(yīng)的key作為鎖的名稱,多個(gè)節(jié)點(diǎn)試圖通過set
操作來競(jìng)爭(zhēng)該鎖。只有一個(gè)節(jié)點(diǎn)能夠成功設(shè)置該鎖的key,即獲得了分布式鎖。其他節(jié)點(diǎn)則在設(shè)置失敗后,可以選擇等待或者進(jìn)行其他處理。 同時(shí),為了避免因?yàn)槟硞€(gè)節(jié)點(diǎn)獲得鎖后發(fā)生故障而導(dǎo)致鎖一直無法釋放,還可以為鎖設(shè)置過期時(shí)間。當(dāng)鎖的持有者在一定時(shí)間后未能釋放鎖,鎖將自動(dòng)過期并被其他節(jié)點(diǎn)獲取。 綜上所述,通過使用Redis的set
操作和一些選項(xiàng)參數(shù),可以實(shí)現(xiàn)簡(jiǎn)單的分布式鎖。多個(gè)節(jié)點(diǎn)可以通過競(jìng)爭(zhēng)設(shè)置同一個(gè)key來獲得鎖,并通過設(shè)置過期時(shí)間來避免因?yàn)殒i的持有者發(fā)生故障而導(dǎo)致鎖一直無法釋放。
package com.laoyang.provider.dao.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.laoyang.provider.dao.po.UserTagPO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Update;
/**
* @author:Kevin
* @create: 2023-08-01 09:54
* @Description:
*/
@Mapper
public interface IUserTagMapper extends BaseMapper<UserTagPO> {
/**
* 使用或的思路來設(shè)置標(biāo)簽,只能允許第一次設(shè)置成功
* @param userId
* @param fieldName
* @param tag
* @return
*/
@Update("update t_user_tag set ${fieldName}=${fieldName} | #{tag} where user_id=#{userId} and ${fieldName} & #{tag}=0")
int setTag(Long userId, String fieldName, long tag);
/**
* 使用先取反在與的思路來取消標(biāo)簽,只能允許第一次刪除成功
* @param userId
* @param fieldName
* @param tag
* @return
*/
@Update("update t_user_tag set ${fieldName}=${fieldName} &~ #{tag} where user_id=#{userId} and ${fieldName} & #{tag}=#{tag}")
int cancelTag(Long userId, String fieldName, long tag);
}
??????? 說明:這里的sql可以參考開頭看到的實(shí)現(xiàn)原理
??????? 4.5 工具類
??????? 4.5.1:對(duì)象轉(zhuǎn)換類
package com.laoyang.common.utils;
import org.springframework.beans.BeanInstantiationException;
import org.springframework.beans.BeanUtils;
import java.util.ArrayList;
import java.util.List;
/**
* @author:Kevin
* @create: 2023-07-29 15:03
* @Description:
*/
public class ConvertBeanUtils {
/**
* 將一個(gè)對(duì)象轉(zhuǎn)成目標(biāo)對(duì)象
*/
public static <T> T convert(Object source,Class<T> targetClass){
if (source == null){
return null;
}
T t = newInstance(targetClass);
BeanUtils.copyProperties(source,t);
return t;
}
/**
* 將List對(duì)象轉(zhuǎn)換成目標(biāo)對(duì)象
*/
public static <K,T> List<T> convertList(List<K> sourceList, Class<T> targetClass){
if (sourceList == null){
return null;
}
List targetlist = new ArrayList((int) (sourceList.size() / 0.75) + 1);
for (K source : sourceList) {
targetlist.add(source);
}
return targetlist;
}
private static <T> T newInstance(Class<T> targetClass){
try {
return targetClass.newInstance();
}catch (Exception e){
throw new BeanInstantiationException(targetClass,"instantiation error",e);
}
}
}
????????調(diào)用實(shí)例:第一個(gè)參數(shù):要轉(zhuǎn)的對(duì)象? 第二個(gè)參數(shù):最終轉(zhuǎn)換成的對(duì)象類
userTagDTO = ConvertBeanUtils.convert(userTagPO,UserTagDTO.class);
??????? 4.5.2 redis業(yè)務(wù)封裝key的工具類(繼承實(shí)現(xiàn))
??????????????? 父類
package com.laoyang.framework.redis.key;
import org.springframework.beans.factory.annotation.Value;
/**
* @author:Kevin
* @create: 2023-07-30 16:37
* @Description:
*/
public class RedisKeyBuilder {
#獲取到對(duì)應(yīng)業(yè)務(wù)主題的名稱
@Value("${spring.application.name}")
private String applicationName;
private static final String SPLIT_ITEM = ":";
public String getSplitItem() {
return SPLIT_ITEM;
}
public String getRrefix(){
return applicationName + SPLIT_ITEM;
}
}
??????? ?????? 子類文章來源:http://www.zghlxwxcb.cn/news/detail-652187.html
package com.laoyang.framework.redis.key;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
/**
* @author:Kevin
* @create: 2023-07-30 16:41
* @Description: 用戶中臺(tái)的redis的key的封裝工具類,生成這個(gè)業(yè)務(wù)字段的key
*/
@Configuration
@Conditional(RedisKeyLoadMatch.class)
public class UserProviderCacheKeyBuilder extends RedisKeyBuilder{
private static String USER_INFO_KEY = "userInfo";
private static String USER_TAG_LOCK_KEY = "userTagLock";
private static String USER_TAG_KEY = "userTag";
private static String USER_PHONE_LIST_KEY = "userPhoneList";
private static String USER_PHONE_OBJ_KEY = "userPhoneObj";
private static String USER_LOGIN_TOKEN_KEY = "userLoginToken";
public String buildUserInfoKey(Long userId) {
return super.getRrefix() + USER_INFO_KEY + super.getSplitItem() + userId;
}
public String buildTagLockKey(Long userId){
return super.getRrefix() + USER_TAG_LOCK_KEY + super.getSplitItem() + userId;
}
public String buildtagInfoKey(Long userId){
return super.getRrefix() + USER_TAG_KEY + super.getSplitItem() + userId;
}
public String buildUserPhoneListKey(Long userId) {
return super.getRrefix() + USER_PHONE_LIST_KEY + super.getSplitItem() + userId;
}
public String buildUserPhoneObjKey(String phone) {
return super.getRrefix() + USER_PHONE_OBJ_KEY + super.getSplitItem() + phone;
}
public String buildUserLoginTokenKey(String tokenKey) {
return super.getRrefix() + USER_LOGIN_TOKEN_KEY + super.getSplitItem() + tokenKey;
}
}
??????????????? 4.5.3 po類文章來源地址http://www.zghlxwxcb.cn/news/detail-652187.html
package com.laoyang.provider.dao.po;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.util.Date;
/**
* @author:Kevin
* @create: 2023-08-01 09:56
* @Description:
*/
@Data
@TableName("t_user_tag")
public class UserTagPO {
@TableId(type = IdType.INPUT)
private Long userId;
@TableField(value = "tag_info_01")
private Long tagInfo01;
@TableField(value = "tag_info_02")
private Long tagInfo02;
@TableField(value = "tag_info_03")
private Long tagInfo03;
private Date createTime;
private Date updateTime;
}
到了這里,關(guān)于實(shí)戰(zhàn)篇之基于二進(jìn)制思想的用戶標(biāo)簽系統(tǒng)(Mysql+SpringBoot)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!