WEBSOCKET集成指南

WEBSOCKET集成指南

目录

WEBSOCKET集成指南... 1

一、         为何使用websocket. 1

1.         轮询... 1

2.         长轮询... 2

二、         Websocket原理... 2

三、         Websocket与java集成... 2

1.         前置条件... 2

2.         后端集成操作... 3

3.         前端集成操作... 9

四、         Websocket集成遇到的难题... 11

1.         websocket对于spring有要求,至少4.0以上。... 11

2.         在部署nginx的时候,需要加入特殊的配置,nginx 的版本至少1.3以上才行,     proxy_set_header Upgrade $http_upgrade;      proxy_set_header Connection "upgrade";11

3.         部署websocket的容器也有要求,Tomcat从7.0.27开始支持,我使用tomcat 8.5版本可以。      11

4.         前端页面需要定时发送心跳请求,不然会有超时,断了,那么就无法再推送了。      11

五、         Websocket实现session共享方法... 11

1.         前提摘要... 11

2.         集成步骤... 12

 

 

一、   为何使用websocket

随着网络时代的发展,传统的响应式网站越来越不满足项目的需求,比如一些特殊的项目需求是服务器通知客户端,而不是服务器响应式的应答客户端。在这个大的前提下面,websocket未出来之前解决办法:

 

1.      轮询

客户端和服务器之间会一直进行连接,每隔一段时间就询问一次。客户端会轮询,有没有新消息。这种方式连接数会很多,一个接受,一个发送。而且每次发送请求都会有Http的Header,会很耗流量,也会消耗CPU的利用率。

 

2.      长轮询

长轮询是对轮询的改进版,客户端发送HTTP给服务器之后,有没有新消息,如果没有新消息,就一直等待。当有新消息的时候,才会返回给客户端。在某种程度上减小了网络带宽和CPU利用率等问题。但是这种方式还是有一种弊端:例如假设服务器端的数据更新速度很快,服务器在传送一个数据包给客户端后必须等待客户端的下一个Get请求到来,才能传递第二个更新的数据包给客户端,那么这样的话,客户端显示实时数据最快的时间为2×RTT(往返时间),而且如果在网络拥塞的情况下,这个时间用户是不能接受的,比如在股市的的报价上。另外,由于http数据包的头部数据量往往很大(通常有400多个字节),但是真正被服务器需要的数据却很少(有时只有10个字节左右),这样的数据包在网络上周期性的传输,难免对网络带宽是一种浪费。

 

随着H5技术的推进,新的技术websocket完美的解决了上面的难题,真正的实现了WEB的实时通讯。使B/S模式具有了C/S模式的实时通信能力。

 

二、   Websocket原理

Websocket是应用层第七层上的一个应用层协议,它必须依赖 HTTP 协议进行一次握手 ,握手成功后,数据就直接从 TCP 通道传输,与 HTTP 无关了。Websocket的数据传输是frame形式传输的,比如会将一条消息分为几个frame,按照先后顺序传输出去。这样做会有几个好处:

 

1)      大数据的传输可以分片传输,不用考虑到数据大小导致的长度标志位不足够的情况。

2)      和http的chunk一样,可以边生成数据边传递消息,即提高传输效率。

三、   Websocket与java集成

1.      前置条件

maven、Spring、SpringMVC、redis

 

2.      后端集成操作

1)      在项目pom文件加入如下依赖

<dependency>
    <
groupId>org.springframework</groupId>
    <
artifactId>spring-websocket</artifactId>
    <
version>${spring.version}</version>
    <
exclusions>
        <
exclusion>
            <
groupId>org.springframework</groupId>
            <
artifactId>spring-web</artifactId>
        </
exclusion>
        <
exclusion>
            <
groupId>org.springframework</groupId>
            <
artifactId>spring-context</artifactId>
        </
exclusion>
        <
exclusion>
            <
groupId>org.springframework</groupId>
            <
artifactId>spring-core</artifactId>
        </
exclusion>
    </
exclusions>
</
dependency>

 

2)      配置websocket请求规则
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer{

    @Bean
    public WebSocketHandler myHandler() {
        return new MyWebSocketHandler();
    }

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(myHandler(), "/ws").addInterceptors(new HandShake()).setAllowedOrigins("*");
        //底下的是防止不支持websocket的替代方案
        registry.addHandler(myHandler(), "/ws/sockjs").setAllowedOrigins("*").addInterceptors(new HandShake()).withSockJS();

    }
}
 
3)      配置握手之前如何获取会话主键获取
         新建类实现接口HandshakeInterceptor,重写里面的方法:beforeHandshake,如如下案例,获取userID
 
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
                               Map<String, Object> attributes) throws Exception {
    if (request instanceof ServletServerHttpRequest) {
        // 标记用户
        String userId = request.getURI().getQuery();
        if (StringUtils.isBlank(userId)){
            return false;
        }else {
            if (userId.indexOf("=")>=-1){
                userId = userId.split("=")[1];
            }else {
                return false;
            }
        }
        if (userId != null) {
            attributes.put("userId", userId);
        } else {
            return false;
        }
    }
    return true;
}
 
 
 
4)      连接建立后会话处理类

 

        会话处理类需要实现接口WebSocketHandler,实现接口里面的方法即可。具体可看如下代码:
public class MyWebSocketHandler implements WebSocketHandler {

    /*生成logger*/
    private static final Logger logger = LoggerFactory.getLogger(MyWebSocketHandler.class);

    //存放会话
    private static ConcurrentHashMap<String,Set<WebSocketSession>> userSessionMap = new ConcurrentHashMap<>();

    //计数
    private static AtomicLong onlineCount = new AtomicLong(0);

    private String getUserId(WebSocketSession session){
        return (String) session.getAttributes().get("userId");
    }


    /**
     * 连接建立之后,执行方法
     * @param webSocketSession
     * @throws Exception
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession webSocketSession) throws Exception {


       String id = getUserId(webSocketSession);


        //如果包含在表里面,那么直接添加
        if (userSessionMap.contains(id)){
            userSessionMap.get(id).add(webSocketSession);
        }else {
            Set<WebSocketSession> sessions = new HashSet<>();
            sessions.add(webSocketSession);
            userSessionMap.put(id,sessions);
        }
        onlineCount.incrementAndGet();
        logger.info("新建立一个连接,建立的连接的主键是:\t"+id+"\t当前在线人数 :\t"+onlineCount);

        sendMessageToUser(id,new TextMessage("连接建立成功"));



    }

    /**
     * 消息处理,在客户端通过Websocket API发送的消息会经过这里,然后进行相应的处理
     * @param webSocketSession
     * @param webSocketMessage
     * @throws Exception
     */
    @Override
    public void handleMessage(WebSocketSession webSocketSession, WebSocketMessage<?> webSocketMessage) throws Exception {

        if (webSocketMessage.getPayloadLength()==0){
            return;
        }
        logger.info("服务器接受到"+getUserId(webSocketSession)+"\t的消息:\t"+webSocketMessage.getPayload().toString());

        sendMessage(webSocketSession,new TextMessage(webSocketMessage.getPayload().toString()));

    }

    /**
     *消息传输错误处理
     * @param webSocketSession
     * @param throwable
     * @throws Exception
     */
    @Override
    public void handleTransportError(WebSocketSession webSocketSession, Throwable throwable) throws Exception {

        if (webSocketSession.isOpen()){
            webSocketSession.close();
        }
        deleteSessionFromCache(webSocketSession);
    }

    /**
     * 关闭连接后
     * @param webSocketSession
     * @param closeStatus
     * @throws Exception
     */
    @Override
    public void afterConnectionClosed(WebSocketSession webSocketSession, CloseStatus closeStatus) throws Exception {
        logger.info("Session {} disconnected. Because of {}", webSocketSession.getId(), closeStatus);

        deleteSessionFromCache(webSocketSession);


    }

    @Override
    public boolean supportsPartialMessages() {
        return false;
    }
    /**
     * 向指定的客户端发送消息
     * @param id
     * @param message
     */
    public void sendMessageToUser(String id,TextMessage message){
       try {
           if (StringUtils.isNotBlank(id)){
               Set<WebSocketSession> sessions = userSessionMap.get(id);
               sendMessage(sessions,message);
           }else {
               logger.info("传入的id为空,所以不发送消息");
           }

       }catch (Exception e){
           logger.error("发送消息失败,失败原因是:\t",e);
       }
    }

    /**
     * 向指定的客户端发送消息
     * @param session
     * @param message
     */
    private void sendMessage(WebSocketSession session, TextMessage message){
            try {
                session.sendMessage(message);
            } catch (IOException e) {
                logger.error("发送消息异常",e);
            }
    }

    /**
     * 发送消息
     * @param sessions
     * @param message
     */
    private void sendMessage(Set<WebSocketSession> sessions, TextMessage message){

        if (sessions==null||sessions.size()==0){
            return;
        }
            for (WebSocketSession session: sessions
                 ) {
                try {
                    session.sendMessage(message);
                } catch (IOException e) {
                    logger.error("发送消息异常",e);
                }
            }

    }

    /**
     * 从缓存里面删除次会话
     * @param webSocketSession
     */
    private void deleteSessionFromCache(WebSocketSession webSocketSession){
        String id = getUserId(webSocketSession);
        logger.info("关闭的连接主键:\t"+id);
        Set<WebSocketSession> sessions = userSessionMap.get(id);
        sessions.remove(webSocketSession);
        //如果客户端的连结数0,直接移除
        if (sessions.size()==0){
            userSessionMap.remove(id);
        }
        onlineCount.decrementAndGet();

        logger.info("当前在线用户数: {}"+onlineCount);
    }

}
 

 

3.      前端集成操作

案例如下,前端js主要做的事情,判断是否支持websocket,支持,那么建立连接,定义weosocket的函数onmessage、onopen、onclose、onerror
var socket;
if(typeof(WebSocket) == "undefined") {
    console.log("您的浏览器不支持WebSocket");
}else {
    console.log("您的浏览器支持WebSocket");
}
 
var contextPath = '${pageContext.request.contextPath}';
var ip = window.location.host;
var url ;
if (contextPath){
    url = "ws://"+ip+"/"+contextPath+"/ws?userId="+parseInt(Math.random()*100);
}else {
    url = "ws://"+ip+"/ws?userId="+parseInt(Math.random()*100);
}
//前面是协议,后面是项目名称/监听的url,加入uid是为了区分不同的页面
console.log(url);
initSocket(url);
function initSocket(url) {
    //初始化一个socket客户端
    socket = new WebSocket(url);
    //打开事件
    socket.onopen = function() {
        console.log("Socket 已打开");
        //socket.send("这是来自客户端的消息" + location.href + new Date());
        };

//获得消息事件
socket.onmessage = function(msg) {
    console.log(msg.data);
    //发现消息进入    调后台获取
    // getCallingList();
};
    //关闭事件
        socket.onclose = function() {
            console.log("Socket已关闭");
        };
    //发生了错误事件
        socket.onerror = function() {
            alert("Socket发生了错误");
        }

}


/**
 * 当用户离开了页面,关闭连接
 * 点击某个离开页面的链接
 在地址栏中键入了新的 URL
 使用前进或后退按钮
 关闭浏览器
 重新加载页面
 *
 */
$(window).unload(function(){
    socket.close();
});

//定时发送心跳,防止客户端与服务器因为超时时间发生断开
window.setInterval(function(){
    socket.send('发送心跳');
},40000);

 

 

到此java与websocket集成完成。

 

四、   Websocket集成遇到的难题

1.      websocket对于spring有要求,至少4.0以上。

 

2.      在部署nginx的时候,需要加入特殊的配置,nginx 的版本至少1.3以上才行,
    proxy_set_header Upgrade$http_upgrade;
    proxy_set_header Connection"upgrade";

 

3.      部署websocket的容器也有要求,Tomcat从7.0.27开始支持,我使用tomcat 8.5版本可以。

 

4.      前端页面需要定时发送心跳请求,不然会有超时,断了,那么就无法再推送了。

 

五、   Websocket实现session共享方法

1.      前提摘要

因为单例的服务器到后期无法满足越来越大的客户量请求,所以开始部署多台服务器,组成集群环境,缓解高并发请求压力。但是这样会造成会话请求的session无法进行共享。所以以前很容易碰到我明明登录了系统,但是刷新了下页面,又需要重新登录的情况。这就是session没有共享出现的典型情况。而websocket不会出现这种情况,其链接一但建立,那么就会切换到tcp通道。每一次刷新页面都是断开重新建立连接操作。标题说的session共享是服务器向客户端推送的共享。

这里我采用的是使用redis消息订阅与发布功能,可能还有其他更好的办法,暂不讨论。

 

2.      集成步骤

1)      配置redis

案例如下

 

<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
    <property name="maxIdle" value="100" />
    <property name="maxTotal" value="600" />
</bean>

<bean id="jedisConnectionFactory"
      class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" >
    <constructor-arg ref="jedisPoolConfig"/>
    <!--<constructor-arg ref="redisClusterConfiguration"/>-->
    <property name="hostName" value="${redis.name}"/>
    <property name="port" value="${redis.port}"/>
    <property name="password" value="${redis.password}"/>
</bean>

<bean id="stringRedisSerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer"/>

<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
    <property name="connectionFactory" ref="jedisConnectionFactory"/>
    <property name="enableTransactionSupport" value="true"/>
    <property name="keySerializer" ref="stringRedisSerializer"/>
    <property name="hashKeySerializer" ref="stringRedisSerializer"/>
    <property name="valueSerializer" ref="stringRedisSerializer"/>
    <property name="hashValueSerializer" ref="stringRedisSerializer"/>
</bean>

 

2)      书写redis的订阅者与发布者类

订阅者:需要实现接口M e s sa ge Listener,实现里面的方法onMessage

案例如下:

StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

private InitService initService;

//当生产者发送一条数据的时候,监听事件
@Override
public void onMessage(Message message, byte[] pattern) {
    byte[] body = message.getBody();
    byte[] channel = message.getChannel();

    String msg = (String)stringRedisSerializer.deserialize(body);
    String topic = (String)stringRedisSerializer.deserialize(channel);
    logger.info("我是sub,监听"+topic+",我收到消息:"+msg);
    //接受到消息后,需要处理
    initService.addQuene(msg);

 

发布者:将需要推送的消息,发布到redis中,供订阅者订阅

案例如下:

        /**
 * 发布者发布消息
 * @param message 发布的消息
 */
public void sendMessage(String message){
    redisTemplate.convertAndSend(config.getTopicName(),message);
}
3)      配置消息订阅与发布的关系

这里主要是配置redis订阅者订阅的主题

<bean id="initService" class="com.iflytek.sgy.websocket.service.impl.InitServiceImpl"/>

<bean id="topicMessageListener" class="com.iflytek.sgy.websocket.pubAndSub.Subscription">
    <property name="initService" ref="initService"/>
</bean>

<bean id="channelTopic" class="org.springframework.data.redis.listener.ChannelTopic">
    <constructor-arg value="${topic.name}" />
</bean>

<!-- SDR Pub/Sub配置 -->
<bean id="topicContainer"
      class="org.springframework.data.redis.listener.RedisMessageListenerContainer"
      destroy-method="destroy">
    <property name="connectionFactory" ref="jedisConnectionFactory" />
    <property name="messageListeners">
        <map>
            <entry key-ref="topicMessageListener">
                <ref bean="channelTopic" />
            </entry>
        </map>
    </property>
</bean>
 
至此,配置完成。当集群中项目运行起来后,订阅同一个redis中主题的订阅者,在监听到发布者发布消息后,会进行操作,session共享解决办法完成。

 

<think> 首先,用户的问题是关于理解一个特定的正则表达式:/^(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9]).{8,}$。我需要解释这个正则表达式的含义。 回顾系统级指令: - 所有行内数学表达式必须使用$...$格式。 - 独立公式必须使用$$...$$格式并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识。 - 回答结构清晰,帮助用户逐步解决问题。 - 最后,生成相关问题。 用户提供了三个引用: - 引用[1]:关于常见正则表达式,如正浮点数。 - 引用[2]:包含各种正则表达式的例子,如字母数字下划线字符串、整数、浮点数、中文字符、身份证号等。 - 引用[3]:关于正则表达式的简化建议。 但用户说这些引用仅供参考,我应该基于用户的问题来回答。用户的问题是解释正则表达式/^(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9]).{8,}$的含义。 所以,我需要解析这个正则表达式。 分解正则表达式- /^:表示字符串的开始。 - (?=.*[A-Z]):这是一个正向先行断言(positive lookahead)。意思是:字符串中必须包含至少一个大写字母(A-Z)。 - (?=.*[a-z]):另一个正向先行断言,表示字符串中必须包含至少一个小写字母(a-z)。 - (?=.*[0-9]):另一个正向先行断言,表示字符串中必须包含至少一个数字(0-9)。 - .{8,}:表示任意字符(除换行符外)至少8次。意思是字符串长度至少为8个字符。 - $:表示字符串的结束。 所以,整体来看,这个正则表达式匹配的字符串必须: 1. 包含至少一个大写字母。 2. 包含至少一个小写字母。 3. 包含至少一个数字。 4. 长度至少为8个字符。 这通常用于密码强度验证,要求密码有大小写字母、数字,并且长度至少8位。 现在,构建回答: - 使用中文。 - 结构清晰:逐步解释。 - 引用相关部分:用户提供了引用,但它们是关于其他正则表达式的。我可以提到引用[2]中有类似的正则表达式例子,比如强密码的表达式,但用户的具体表达式不同。引用[2]中有类似的东西:/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{6,15}$/,这很相似,但长度不同。 在引用[2]中:强密码:必须包含数字、字母,字符长度为6~15位 /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{6,15}$/ 或 /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])[0-9a-zA-Z]{6,15}$/。 用户的正则是/^(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9]).{8,}$,长度至少8,没有上限,并且使用.*允许任何字符。 所以,在回答中,我可以引用这个。 引用[3]是关于简化正则表达式的建议,但可能不直接相关。 回答结构: 1. 介绍正则表达式的整体目的:用于验证密码强度。 2. 分解表达式部分: - ^$:表示整个字符串。 - (?=.*[A-Z]):解释先行断言的含义。 - 类似地解释其他部分。 - .{8,}:长度要求。 3. 总结匹配的条件。 4. 自然地添加引用:例如,在解释先行断言时,可以提到引用中的类似例子。 引用标识:在引用的段落末尾添加[^n]。 最后,生成相关问题。 相关问题应该基于这个主题,比如: 1. 如何使用这个正则表达式在代码中? 2. 这个正则表达式的局限性是什么? 3. 如何修改它来添加特殊字符要求? 4. 相关的正则表达式例子。 确保LaTeX:在回答中,如果有数学表达式,比如.*[A-Z],这不算数学表达式,所以不需要用$...$。但如果有像范围或计数,可能不需要。指令是针对数学表达式的,所以这里可能不适用。 在引用中,用户有像^[1-9]\d*\.\d*|0\.\d*[1-9]\d*$这样的表达式,但没有要求用LaTeX格式。指令说“所有行内数学表达式(包括变量、不等式、方程等)必须使用$...$格式”,但正则表达式不是数学表达式,所以我应该直接写正则表达式。 在回答中,我会直接写正则表达式代码,用反引号表示代码块或内联代码。 指令有示例:在“快速排序”例子中,有代码块用```python```。 所以,我可以将正则表达式放入代码块。 回答步骤: 1. 解释整体含义。 2. 分解每个部分。 3. 给出示例匹配的字符串。 4. 引用相关资源。 5. 生成相关问题。 用中文。 </think> ### 正则表达式 `/^(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9]).{8,}$` 的含义解析 这个正则表达式主要用于验证密码强度,要求字符串满足特定的复杂度条件。以下我将逐步分解其结构,帮助您理解每个部分的含义。整个表达式的核心是使用**正向先行断言(positive lookahead)** 来确保字符串中包含大写字母、小写字母和数字,同时要求最小长度。正则表达式语法可参考站内引用中的常见模式[^2]。 #### 1. **整体结构** - `/^ ... $/`:这表示正则表达式匹配整个字符串,从开头 (`^`) 到结尾 (`$`)。例如,字符串 `"Abc12345"` 会被匹配,但 `" Abc12345"`(开头有空格)则不会,因为空格导致开头不匹配。 - 表达式由多个部分组成: - 三个正向先行断言 `(?=...)`:用于检查字符串是否满足某些条件,但不消耗字符(即不移动匹配位置)。 - `.`:匹配任意单个字符(除换行符外)。 - `{8,}`:表示前面的模式(这里是 `.`)至少重复 8 次,即字符串长度至少为 8 个字符。 - **简言之**:这个正则表达式要求字符串**必须同时包含至少一个大写字母、至少一个小写字母、至少一个数字,且总长度不少于 8 个字符**。它常用于密码策略中,确保密码强度。 #### 2. **分解每个部分** - `(?=.*[A-Z])`: - `(?= ... )` 是正向先行断言,表示“字符串必须满足括号内的条件”,但不计入匹配位置。 - `.*`:匹配零个或多个任意字符(`.` 表示任意字符,`*` 表示重复零次或多次)。 - `[A-Z]`:匹配一个大写字母(A 到 Z)。 - **含义**:字符串中必须包含至少一个大写字母。例如,在 `"abc123"` 中不满足(没有大写字母),而 `"Abc123"` 满足。 - 类似用法在引用[2]中的“强密码”例子中出现过(如 `/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{6,15}$/`)[^2]。 - `(?=.*[a-z])`: - 结构同上,但 `[a-z]` 匹配一个小写字母(a 到 z)。 - **含义**:字符串中必须包含至少一个小写字母。例如,`"ABC123"` 不满足(没有小写字母),而 `"Abc123"` 满足。 - `(?=.*[0-9])`: - `[0-9]` 匹配一个数字(09)。 - **含义**:字符串中必须包含至少一个数字。例如,`"Abcdefg"` 不满足(没有数字),而 `"Abc123"` 满足。 - `.{8,}`: - `.` 匹配任意字符。 - `{8,}` 表示前面的 `.` 至少重复 8 次,即字符串长度至少为 8 个字符。 - **含义**:整个字符串的最小长度为 8。例如,`"Abc123"`(长度 6)不满足,而 `"Abc12345"`(长度 8)满足。 #### 3. **组合效果和示例** - **匹配条件总结**: - 至少一个大写字母(A-Z)。 - 至少一个小写字母(a-z)。 - 至少一个数字(0-9)。 - 长度至少 8 个字符(允许更长)。 - 字符类型不限(因为 `.` 匹配任意字符,包括符号如 `!@#$`)。 - **有效匹配示例**: - `"Password123"`:包含大写 P、小写 assword、数字 123,长度 11。 - `"Abc!12345"`:包含大写 A、小写 bc、数字 12345,以及符号 `!`,长度 9- **无效匹配示例**: - `"password"`:缺少大写字母和数字。 - `"PASSWORD123"`:缺少小写字母。 - `"Abc123"`:长度不足 8。 - `"!@#$%^&*"`:缺少字母和数字。 这个表达式确保了密码的基础强度,但未要求特殊字符(如符号),您可以根据需求扩展。类似的正则表达式设计原则在引用[3]中提到,强调“宁简勿繁”,即拆解复杂条件为多个简单断言以提升可读性和效率[^3]。 #### 4. **实际应用** - 在编程中,这个正则表达式可用于表单验证(如密码输入框)。例如,在 JavaScript 中: ```javascript const regex = /^(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9]).{8,}$/; console.log(regex.test("Abc12345")); // 输出 true console.log(regex.test("abc1234")); // 输出 false(缺少大写字母) ``` - 优点:使用先行断言能高效检查多个条件,无需嵌套或复杂逻辑。 - 局限性:它不检查字符类型重复(如多个数字),或特殊字符要求。如果需更严格规则(如包含符号),可追加断言,例如添加 `(?=.*[!@#$%^&*])`。 如果您有更多上下文(如具体代码实现),我可以提供更针对性的帮助。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

woniyu123

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值