分布式锁Lock4J 使用总结

news2025/6/2 10:21:33

Lok4j 简介

lock4j是一个分布式锁组件,其提供了多种不同的支持以满足不同性能和环境的需求。

立志打造一个简单但富有内涵的分布式锁组件。

特点

  • 简单易用,功能强大,扩展性强。
  • 支持redission,redisTemplate,zookeeper。可混用,支持扩展

Docker 安装Redis

请参考文章:Docker 安装Redis

Docker 安装ZooKeeper

请参考文章:Docker 安装Zookeeper

SpringBoot 集成Lock4j 

Lock4j 之Reids 版本

pom.xml 

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>SpringBootCase</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>SpringBoot-Lock4J</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <mybatisplus-spring-boot-starter.version>3.4.3</mybatisplus-spring-boot-starter.version>
        <mysql-spring-boot-starter.version>8.0.19</mysql-spring-boot-starter.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatisplus-spring-boot-starter.version}</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql-spring-boot-starter.version}</version>
        </dependency>
        <!--添加开源分布式锁Lock4j-->
        <!--若使用redisTemplate作为分布式锁底层,则需要引入-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>lock4j-redis-template-spring-boot-starter</artifactId>
            <version>2.2.4</version>
        </dependency>
    </dependencies>

</project>

application.yml 之Redis 配置

server:
  port: 8086
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.43.10:3306/bill?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&autoReconnect=true
    username: root
    password: 123456
    hikari:
      minimum-idle: 10
      maximum-pool-size: 20
      idle-timeout: 500000
      max-lifetime: 540000
      connection-timeout: 60000
      connection-test-query: select 1
  redis:
    database: 0
    host: 192.168.43.10
    port: 6379
    jedis:
      pool:
        max-active: 200
        max-wait: -1
        max-idle: 10
        min-idle: 0
    timeout: 6000

mybatis-plus:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: cn.zzg.mybatisplus.entity

controller 使用Lock4j 注解,实现分布式锁功能

package cn.zzg.lock.controller;

import com.baomidou.lock.annotation.Lock4j;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/lock4j")
public class Lock4JController {

    @GetMapping("/lockMethod")
    @Lock4j(keys = {"#key"}, acquireTimeout = 1000, expire = 10000)
    public String lockMethod(@RequestParam String key){
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return new String(key);
    }

}

Lock4j 之Redission版本

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>SpringBootCase</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>SpringBoot-Lock4JRedission</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <!--若使用redisson作为分布式锁底层,则需要引入-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>lock4j-redisson-spring-boot-starter</artifactId>
            <version>2.2.4</version>
        </dependency>
    </dependencies>


</project>

application.yml 配置文件

server:
  port: 8089
spring:
  redis:
    database: 0
    host: 192.168.43.10
    port: 6379
    jedis:
      pool:
        max-active: 200
        max-wait: -1
        max-idle: 10
        min-idle: 0
    timeout: 6000

 controller 使用Lock4j 注解,实现分布式锁功能

package cn.zzg.lock4j.controller;

import com.baomidou.lock.annotation.Lock4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/lock4j")
public class Lock4JController {

    @GetMapping("/lockMethod")
    @Lock4j(keys = {"#key"}, acquireTimeout = 1000, expire = 10000)
    public String lockMethod(@RequestParam String key){
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return new String(key);
    }

}

Lock4j 之ZooKeeper 版本

pom.xml 

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>SpringBootCase</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>SpringBoot-Lock4jZookeeper</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <!--添加开源分布式锁Lock4j-->
        <!--若使用Zookeeper作为分布式锁底层,则需要引入-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>lock4j-zookeeper-spring-boot-starter</artifactId>
            <version>2.2.4</version>
        </dependency>
    </dependencies>

</project>

application.yml 之zookeeper配置

server:
  port: 8087
spring:
  coordinate:
    zookeeper:
      zkServers: 192.168.43.10:2181

controller 使用Lock4j 注解,实现分布式锁功能

package cn.zzg.lock.controller;

import com.baomidou.lock.annotation.Lock4j;
import com.baomidou.lock.executor.ZookeeperLockExecutor;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/lock4j")
public class Lock4JController {

    @GetMapping("/lockMethod")
    @Lock4j(keys = {"#key"}, acquireTimeout = 1000, expire = 10000, executor = ZookeeperLockExecutor.class)
    public String lockMethod(@RequestParam String key){
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return new String(key);
    }

}

Lock4J 注解详解

 Lock4J 高级使用

1、配置获取锁超时时间和锁过期时间。

lock4j:
  acquire-timeout: 3000 #默认值3s,可不设置
  expire: 30000 #默认值30s,可不设置
  primary-executor: com.baomidou.lock.executor.RedisTemplateLockExecutor #默认redisson>redisTemplate>zookeeper,可不设置
  lock-key-prefix: lock4j #锁key前缀, 默认值lock4j,可不设置

acquire-timeout /排队时长,超出这个时间退出队列,并抛出超时异常。
expire 锁过期时间 。 主要是防止死锁。默认30秒是为了兼容绝大部分场景。 

2、自定义执行器。

前提条件必须继承抽象类:com.baomidou.lock.executor.AbstractLockExecutor<T> 

ZooKeeper 版本执行器之ZookeeperLockExecutor

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.baomidou.lock.executor;

import java.util.concurrent.TimeUnit;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.imps.CuratorFrameworkState;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ZookeeperLockExecutor extends AbstractLockExecutor<InterProcessMutex> {
    private static final Logger log = LoggerFactory.getLogger(ZookeeperLockExecutor.class);
    private final CuratorFramework curatorFramework;

    public InterProcessMutex acquire(String lockKey, String lockValue, long expire, long acquireTimeout) {
        if (!CuratorFrameworkState.STARTED.equals(this.curatorFramework.getState())) {
            log.warn("instance must be started before calling this method");
            return null;
        } else {
            String nodePath = "/curator/lock4j/%s";

            try {
                InterProcessMutex mutex = new InterProcessMutex(this.curatorFramework, String.format(nodePath, lockKey));
                boolean locked = mutex.acquire(acquireTimeout, TimeUnit.MILLISECONDS);
                return (InterProcessMutex)this.obtainLockInstance(locked, mutex);
            } catch (Exception var10) {
                return null;
            }
        }
    }

    public boolean releaseLock(String key, String value, InterProcessMutex lockInstance) {
        try {
            lockInstance.release();
            return true;
        } catch (Exception var5) {
            log.warn("zookeeper lock release error", var5);
            return false;
        }
    }

    public ZookeeperLockExecutor(final CuratorFramework curatorFramework) {
        this.curatorFramework = curatorFramework;
    }
}

Redis 版本执行器之RedisTemplateLockExecutor

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.baomidou.lock.executor;

import java.util.Collections;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;

public class RedisTemplateLockExecutor extends AbstractLockExecutor<String> {
    private static final Logger log = LoggerFactory.getLogger(RedisTemplateLockExecutor.class);
    private static final RedisScript<String> SCRIPT_LOCK = new DefaultRedisScript("return redis.call('set',KEYS[1],ARGV[1],'NX','PX',ARGV[2])", String.class);
    private static final RedisScript<String> SCRIPT_UNLOCK = new DefaultRedisScript("if redis.call('get',KEYS[1]) == ARGV[1] then return tostring(redis.call('del', KEYS[1])==1) else return 'false' end", String.class);
    private static final String LOCK_SUCCESS = "OK";
    private final StringRedisTemplate redisTemplate;

    public String acquire(String lockKey, String lockValue, long expire, long acquireTimeout) {
        String lock = (String)this.redisTemplate.execute(SCRIPT_LOCK, this.redisTemplate.getStringSerializer(), this.redisTemplate.getStringSerializer(), Collections.singletonList(lockKey), new Object[]{lockValue, String.valueOf(expire)});
        boolean locked = "OK".equals(lock);
        return (String)this.obtainLockInstance(locked, lock);
    }

    public boolean releaseLock(String key, String value, String lockInstance) {
        String releaseResult = (String)this.redisTemplate.execute(SCRIPT_UNLOCK, this.redisTemplate.getStringSerializer(), this.redisTemplate.getStringSerializer(), Collections.singletonList(key), new Object[]{value});
        return Boolean.parseBoolean(releaseResult);
    }

    public RedisTemplateLockExecutor(final StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
}

3、自定义Key生成器

前提条件必须实现:com.baomidou.lock.LockKeyBuilder 接口

默认Key生成器:DefaultLockKeyBuilder

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.baomidou.lock;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.BeanResolver;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.StringUtils;

public class DefaultLockKeyBuilder implements LockKeyBuilder {
    private static final ParameterNameDiscoverer NAME_DISCOVERER = new DefaultParameterNameDiscoverer();
    private static final ExpressionParser PARSER = new SpelExpressionParser();
    private BeanResolver beanResolver;

    public DefaultLockKeyBuilder(BeanFactory beanFactory) {
        this.beanResolver = new BeanFactoryResolver(beanFactory);
    }

    public String buildKey(MethodInvocation invocation, String[] definitionKeys) {
        Method method = invocation.getMethod();
        return definitionKeys.length <= 1 && "".equals(definitionKeys[0]) ? "" : this.getSpelDefinitionKey(definitionKeys, method, invocation.getArguments());
    }

    protected String getSpelDefinitionKey(String[] definitionKeys, Method method, Object[] parameterValues) {
        StandardEvaluationContext context = new MethodBasedEvaluationContext((Object)null, method, parameterValues, NAME_DISCOVERER);
        context.setBeanResolver(this.beanResolver);
        List<String> definitionKeyList = new ArrayList(definitionKeys.length);
        String[] var6 = definitionKeys;
        int var7 = definitionKeys.length;

        for(int var8 = 0; var8 < var7; ++var8) {
            String definitionKey = var6[var8];
            if (definitionKey != null && !definitionKey.isEmpty()) {
                String key = (String)PARSER.parseExpression(definitionKey).getValue(context, String.class);
                definitionKeyList.add(key);
            }
        }

        return StringUtils.collectionToDelimitedString(definitionKeyList, ".", "", "");
    }
}

4、自定义锁获取失败策略

前提条件必须实现:com.baomidou.lock.LockFailureStrategy 接口

默认失败策略:DefaultLockFailureStrategy

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.baomidou.lock;

import com.baomidou.lock.exception.LockFailureException;
import java.lang.reflect.Method;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class DefaultLockFailureStrategy implements LockFailureStrategy {
    private static final Logger log = LoggerFactory.getLogger(DefaultLockFailureStrategy.class);
    protected static String DEFAULT_MESSAGE = "request failed,please retry it.";

    public DefaultLockFailureStrategy() {
    }

    public void onLockFailure(String key, Method method, Object[] arguments) {
        throw new LockFailureException(DEFAULT_MESSAGE);
    }
}

5、手动获取/释放锁

通用方法:

@Service
public class CommonService {
    @Autowired
    private LockTemplate lockTemplate;

    public void commonLock(String bizId) {
        // 各种查询操作 不上锁
        // ...
        // 获取锁
        final LockInfo lockInfo = lockTemplate.lock(bizId, 30000L, 5000L, RedissonLockExecutor.class);
        if (null == lockInfo) {
            throw new RuntimeException("业务处理中,请稍后再试");
        }
        // 获取锁成功,处理业务
        try {
            System.out.println("执行普通方法 , 当前线程:" + Thread.currentThread().getName());
        } finally {
            //释放锁
            lockTemplate.releaseLock(lockInfo);
        }
        //结束
    }
}

参考资料

官网地址:Lock4J

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1355488.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

羊大师讲解,羊奶为什么更适合高血压人群?

羊大师讲解&#xff0c;羊奶为什么更适合高血压人群&#xff1f; 高血压是一种常见的健康问题&#xff0c;它会引起诸多并发症并增加心脑血管疾病的风险。与此同时&#xff0c;人们越来越关注饮食对健康的影响。作为一种营养丰富且适合高血压人群的饮品&#xff0c;羊奶备受关…

Java版直播商城免 费 搭 建:平台规划与常见营销模式,电商源码、小程序、三级分销及详解

【saas云平台】打造全行业全渠道全场景的saas产品&#xff0c;为经营场景提供一体化解决方案&#xff1b;门店经营区域化、网店经营一体化&#xff0c;本地化、全方位、一站式服务&#xff0c;为多门店提供统一运营解决方案&#xff1b;提供丰富多样的营销玩法覆盖所有经营场景…

玩转JNPF可视化平台,软件开发如此简单!

以 Vue 为技术栈的低代码 JNPF &#xff0c;开发力量还可以。这款低代码和市面上的其他低代码区别很大的&#xff0c;相较于轻流、简道云、轻宜搭、微搭、帆软、活字格等等&#xff0c;这类面向业务人群使用的低代码&#xff08;不需要研发人员操作的编写的&#xff0c;我更愿意…

spring boot dispatcherServlet注册到tomcat及tomcat请求如何找到dispatcherServlet

spring boot dispatcherServlet如何注册到tomcat容器中&#xff0c;及request请求如何在tomcat的servlet容器找到dispatcherServlet ##spring boot 注册dispatcherServlet javax.servlet.ServletContext.addServlet添加tomcat容器 ##spring boot 注册dispatcherServlet ##req…

H5C3练习心得 2024.01.04(鼠标悬停缩放效果)--box-shadow,font字体,文本样式

&#xff08;一&#xff09;box-shadow 1.作用 为盒子添加阴影 2.属性语法 box-shadow&#xff1a;h-shadow v-shadow blur spread color position h-shadow:水平阴影的的位置&#xff0c;允许负值&#xff08;必填&#xff09;v-shadow&#xff1a;垂直阴影的位置&#x…

Python 自学(四) 之元组字典与集合

目录 1. 列表&#xff0c;元组&#xff0c;字典与集合的区别 2. 元组的创建和删除 tuple() del P101 3. 单个元素的元组 P102 4. 元组元素的修改 P106 5. 元组的使用场景 6. 字典的创建和删除 dict() zip() : del clear() P1…

草图大师 sketchup pro2023

SketchUp Pro是一款功能强大的三维建模软件&#xff0c;适用于建筑、机械、室内设计等领域。它提供了丰富的绘图工具和灵活的建模选项&#xff0c;支持实时预览和多种设备适配&#xff0c;让用户能够快速高效地创建出逼真的三维模型。SketchUp Pro还具备强大的插件生态和团队协…

ArrayList集合综合练习

文章目录 题目1训练目标训练提示训练步骤参考答案 题目2训练目标训练提示参考方案训练步骤参考答案 题目3训练目标训练提示参考方案训练步骤参考答案 题目4&#xff08;综合&#xff09;训练目标训练提示参考方案训练步骤参考答案 题目1 现有如下字符串元素&#xff1a;[“aaa…

Go语言基本数据类型

Go语言基本数据类型 1.整型2.浮点型3.复数4.布尔型5.字符串窥探字符串类型字符串内建函数UTF-8编码字符串处理相关的四个包字符串和数字的转换 6.常量 1.整型 Go语言同时提供了有符号和无符号类型的整数运算。这里有int8、int16、int32和int64四种截然不同大小的有符号整数类型…

2024,这将是量子计算的真正挑战

2023年&#xff0c;一项项量子计算纪录被打破。 谷歌量子AI团队证明了将多个量子比特分组合成为一个逻辑量子比特的纠错方法可以提供更低的容错率。以往的纠错研究随着比特数的增加&#xff0c;错误率会提高&#xff0c;都是“越纠越错”&#xff0c;而这次谷歌首次实现了“越纠…

小兔鲜儿 - 订单模块

目录 填写订单页​ 静态结构 购物车结算​ 立即购买​ 页面传参​ 选择收货地址​ 提交订单​ 订单详情页​ 静态结构​ 自定义导航栏交互​ 获取订单详情​ 订单状态​ 再次购买​ 支付倒计时​ 订单支付​ 微信支付说明​ 参考代码​ 支付成功页​ 模拟发…

网络类型之GRE和MGRE和NHRP

GRE-通用路由封装 是一种简单的三层VPN封装技术&#xff0c;属于虚拟的点到点网络类型 优点&#xff1a;支持IP 网络作为承载网络、支持多种协议、支持IP 组播&#xff0c;配置简单&#xff0c;容易布署。 缺点&#xff1a;缺少保护功能&#xff0c;不能执行如认证、加密、以…

基于SSM实现的社区论坛系统(附PPT、设计文档)

基于SSM实现的社区论坛系统&#xff08;附PPT、设计文档&#xff09; 文章目录 基于SSM实现的社区论坛系统&#xff08;附PPT、设计文档&#xff09;系统介绍技术选型成果展示设计文档用户端管理员端 源码获取账号地址及其他说明 系统介绍 基于SSM实现的社区论坛系统是一款前后…

Stable Diffusion汉化插件

今天为大家介绍Stable Diffusion的两种UI汉化包&#xff0c;一种是汉化包&#xff0c;就中文界面&#xff0c;方便大家对于繁杂的参数的模型的操作&#xff0c;一种是中英文对照界面&#xff0c;在中文提示下&#xff0c;同时显示英文&#xff0c;不但方便设置也同时学习了英文…

五大安全须知,享受酒精壁炉温馨每一年

酒精壁炉作为一种独特的取暖方式&#xff0c;受到了越来越多家庭的喜爱。然而&#xff0c;为了确保使用过程中的安全性&#xff0c;需要熟悉并遵循一些关键的事项。 选择合适的酒精燃料 在使用酒精壁炉之前&#xff0c;最好是确保使用纯度为95%至97%的工业酒精。避免使用含有…

开源知识库对比,到底哪个才最好用?

在现代信息社会&#xff0c;知识的获取和共享变得越来越重要。为了更好地管理和传播知识&#xff0c;越来越多的人开始关注开源知识库的使用。但是&#xff0c;在众多开源知识库中&#xff0c;到底哪个才最好用呢&#xff1f;本文将对比三款开源知识库&#xff0c;以帮助读者做…

openmediavault(OMV) (27)网络(2)adguardhome

简介 AdGuard Home 是一个开源的网络广告和隐私保护解决方案,它充当局域网中的 DNS 服务器,提供广告拦截、跟踪器阻止和家长控制等功能。它可以在个人电脑、树莓派或其他支持的硬件设备上运行。 AdGuard Home 的主要功能包括: 广告拦截:AdGuard Home 使用广泛维护的广告…

点击出现视频弹框

<VideoPlayer ref"video":size"{ width: 88%, height: 100% }" :videoSrc"currentVideo.url"></VideoPlayer>import VideoPlayer from /components/video-player.vue

2023最大技术潮:大模型冲击下的智能汽车

作者 |德新 编辑 |王博 过去这年最大的技术潮&#xff0c;非大模型莫属。 2023年初&#xff0c;由ChatGPT掀起的浪花&#xff0c;迅速地演变成了席卷全球的AI科技浪潮。汽车行业在其中也不可避免。各大车企纷纷投入与大模型相关的布局。 长城官宣成立了AI Lab&#xff0c;到…

共享企业文件数据信息:实用方法与技巧分享

在当下快节奏的企业办公生活中&#xff0c;如何有效且高效的进行企业文件数据信息共享&#xff0c;保持企业竞争力&#xff0c;是许多企业团队面临的问题。 诚然&#xff0c;社交媒体工具的出现可以缓解企业信息共享协作的痛点。然而&#xff0c;多平台工具的交叉使用又使企业…