80分钟100分,83行代码决赛优秀选手如何解题? -阿里云开发者社区

开发者社区> 云效运营官> 正文

80分钟100分,83行代码决赛优秀选手如何解题?

简介: 由阿里云云效主办的2021年第3届83行代码挑战赛已经收官。超2万人围观,近4000人参赛,85个团队组团来战。大赛采用游戏闯关玩儿法,融合元宇宙科幻和剧本杀元素,让一众开发者玩得不亦乐乎。
+关注继续查看

本次大赛最后一道题考验的是参赛者的Debug能力,最好对基于springboot的spring webflux(至少是spring mvc),spring security有一定了解,可以省去很多在比赛过程中查找资料的时间。


下面站在一个对上述架构不那么熟悉的角度, 按步骤讲解debug思路. 对于以后了解spring全家桶还是很有帮助的。


第一步


Bug1


ReactiveWebSocketHandlerTest单元测试调试,首先执行单元测试,查看执行结果。


image


调用栈从上向下分析,看到是一个EOF错误,即流在读取完毕后仍然尝试读取,找到错误栈中最近的com.aliyun.code83开头的源代码上下文。

private static CheckedFunction<DataInputStream, String> charsetNameDecoder = (DataInputStream input) -> {

        byte[] charsetNameBytes = input.readNBytes(input.readByte()); //源代码第100行

        if (charsetName.get() == null) {
            charsetName.set(new String(charsetNameBytes, ISO_8859_1));
        }

        return charsetName.get();
    };

可以看到错误出在第100行的input.readByte()中,但是在上文中并没有其他的input操作,说明这个流在传入时就已经被读光了,顺着错误栈继续向上找,Utils的89行上下文。

public static String decodeMessage(byte[] rawMessage) {
        ByteArrayInputStream in = new ByteArrayInputStream(rawMessage);
        DataInputStream dis = new DataInputStream(in);

        try {
            return new String(dis.readAllBytes(), charsetNameDecoder.apply(dis)); //源代码第89行
        } catch (IOException e) {
            e.printStackTrace();
            return String.format("%s<_>-<_>.", e.getClass().getSimpleName()); // 此行勿动,影响评分
        }
    }

从89行可以看到:


charsetNameDecoder.apply(dis) 报了错,原因是new String的第一个参数dis.readAllBytes()已经把数据读完了,这里我们需要大概分析一下这部分代码的功能。


new String如果传入2个参数的话,第一个参数bytes[]是对应内容的byte数组,而第二个参数是字符集。这里看起来无论是字符串内容还是字符集都来自于input输入流,通过charsetNameDecoder逻辑来看,先通过input.readByte读出一个长度N来,在通过readNBytes读出长度N的部分解析出字符集,再把剩余的部分读出来按照字符集进行字符串构建。


如果完整读过所有代码的话,也可以从ReactiveWebSocketHandler的javadoc上看到包的格式,这里也体现出javadoc的重要性,做题前也许代码不用读完,但是尽量把javadoc都过一下。

/**
 * 二进制包格式
 * byte 字符集长度; n1
 * byte[n1] 字符集数据; n1 = 字符集长度
 * byte[n2] 有效数据;n2 = 包总长度 - n1 - 1
 */
@Component("ReactiveWebSocketHandler")
public class ReactiveWebSocketHandler implements WebSocketHandler {
...

这里问题明显出89行上, readAllBytes提前把所有数据都读完了, 所以代码调整如下

    public static String decodeMessage(byte[] rawMessage) {
        ByteArrayInputStream in = new ByteArrayInputStream(rawMessage);
        DataInputStream dis = new DataInputStream(in);

        try {
            //先从流中前部分读出字符集, 剩下的再通过readAllBytes读出
            final String charset = charsetNameDecoder.apply(dis);
            
            return new String(dis.readAllBytes(), charset);
        } catch (IOException e) {
            e.printStackTrace();
            return String.format("%s<_>-<_>.", e.getClass().getSimpleName()); // 此行勿动,影响评分
        }
    }

重新运行单元测试,发现ReactiveWebSocketHandlerTest已经没有错误了 (至少满足的unit test的判断期望, 业务上是否有错误未必)。


行第二个单元测试Round4ApplicationTests,看起来是空的,直接成功下一个。


Bug2


执行第三个单元测试UtilsTest。


image


执行失败,看起来是一个字符串处理的逻辑,而处理的结果不太对。


下面观察一下测试用例:

Triplet.with(
        "提取普通文本",
        "Welcome to <pre>DevStudio</pre>",
        "Welcome to DevStudio"
),
Triplet.with(
        "提取CJK文本",
        "有<i>对象</i>了么? 别慌, 送你一个! 领取请加钉钉群: <quote>35991139</quote>",
        "有对象了么? 别慌, 送你一个! 领取请加钉钉群: 35991139"
),
Triplet.with(
        "提取Tag文本",
        "<p>Cosy 提效补全用过没, 还能搜搜搜 https://developer.aliyun.com/tool/cosy</p>",
        "Cosy 提效补全用过没, 还能搜搜搜 https://developer.aliyun.com/tool/cosy"
),
Triplet.with(
        "提取嵌套tag文本",
        "<blockquote><p>401?!! 不要慌,不要急,App Observer 帮助您~ https://help.aliyun.com/document_detail/326231.html 了解一下</p></blockquote>",
        "401?!! 不要慌,不要急,App Observer 帮助您~ https://help.aliyun.com/document_detail/326231.html 了解一下"
),
Triplet.with(
        "万圣节惊喜小剧场",
        "<happy>碧油鸡全部退散, 颈腰椎早日康复! </happy>贼真诚",
        "碧油鸡全部退散, 颈腰椎早日康复! 贼真诚"
)

从每个用例可以看出,似乎对于处理逻辑的期望是把所有中括号里的元素去掉,就像消除html节点定义,只保留文本内容一样。下面观察一下实际被测试的方法:

private static final Pattern REGULAR_HTML_TAG = Pattern.compile("<(?<tag>.*)>");

public static String stripHtmlTag(String html) {

    if (ObjectUtils.isEmpty(html)) {
        return null;
    }

    StringBuilder builder = new StringBuilder();
    final Matcher matcher = REGULAR_HTML_TAG.matcher(html);
    while (matcher.find()) {
        matcher.appendReplacement(builder, Strings.EMPTY);
        if (log.isDebugEnabled()) {
            log.debug("remove tag {}", matcher.group("tag"));
        }
    }
    return builder.toString();
}

的确, 从整体逻辑看起来是通过正则匹配一对<>, 并替换成空的逻辑. 首先分析最上面定义的正则表达式, 乍一看是OK的, 匹配两端为<>的任意字符.* , ?是给匹配分组命名用的, 对于匹配无直接作用, 是replace时作为group的key对待, 这个具体可以查正则相关文档. 但是UT执行明显有错, 我们把一个用例字符串用这个正则匹配看看


image


可以看到,正则匹配从第一个<直接到了最后结尾的>,所以执行结果就是整句话替换还剩一个"有"字。这里涉及到正则的贪婪匹配问题,默认为贪婪的,尽可能匹配更多内容,而取消贪婪的做法是在匹配规则后面加一个问号?变成

private static final Pattern REGULAR_HTML_TAG = Pattern.compile("<(?<tag>.*?)>");

bug3


改掉后再执行一次UT


image


看起来好多了! 只有一个错误了,现在来分析一下为什么错。

public static String stripHtmlTag(String html) {

    if (ObjectUtils.isEmpty(html)) {
        return null;
    }

    StringBuilder builder = new StringBuilder();
    final Matcher matcher = REGULAR_HTML_TAG.matcher(html);
    while (matcher.find()) {
        matcher.appendReplacement(builder, Strings.EMPTY);
        if (log.isDebugEnabled()) {
            log.debug("remove tag {}", matcher.group("tag"));
        }
    }
    return builder.toString();
}

从循环上看,对于html进行tag匹配,找到的话向builder里写入新字符串,而新字符串的内容是截止到匹配部分位置的文本,并且把<(?.*?)>替换为空。针对 "碧油鸡全部退散, 颈腰椎早日康复! 贼真诚" 这个用例。


  • 第一次匹配内容是<happy>,向builder里写入替换文本为空;
  • 第二次匹配的部分是碧油鸡全部退散,颈腰椎早日康复! </happy>,向builder里追加后的内容是"碧油鸡全部退散,颈腰椎早日康复!"
  • 第三次while过来,因为没有新的<.*>内容, 直接结束循环,return了!所以问题出在这里,需要把剩下的部分"贼真诚"补进来。


于是代码调整如下:

public static String stripHtmlTag(String html) {
    if (ObjectUtils.isEmpty(html)) {
        return null;
    }

    StringBuilder builder = new StringBuilder();
    final Matcher matcher = REGULAR_HTML_TAG.matcher(html);
    while (matcher.find()) {
        matcher.appendReplacement(builder, Strings.EMPTY);
        if (log.isDebugEnabled()) {
            log.debug("remove tag {}", matcher.group("tag"));
        }
    }

    matcher.appendTail(builder);
    
    return builder.toString();
}

增加appendTrail,把剩下的部分补进来。运行单元测试, 一切OK!


其实,这里还有个简单写法:

public static String stripHtmlTag(String html) {
    if (ObjectUtils.isEmpty(html)) {
        return null;
    }

    return html.replaceAll("<.*?>", "");
}

不过, 既然改bug,那尽量保留原有逻辑为好。


第二步


下面我们开始进行业务调试,按照README提示运行。/round4. 开场就挂了,看到提醒需要启动服务才行。


找到带@SpringBootApplication注解的main方法,这是spring boot程序标准的启动入口,启动, run/debug都可,如果想要断点调试的话, 用debug。


Bug4


再次执行./round4


image


Step1看起来没啥错,Step2出现错误,看起来是期望动态添加一个用户reporter,失败了,错误消息是缺少CSRF请求头,如果用过spring security (无论在spring MVC还是spring Webflux)的话, 在安全配置里面可能会留下印像,就是对csrf的配置。这里看到round4客户端似乎请求中不带有csrf的token,那我们只能改服务了。


注: CSRF百度一下可以了解它的作用,目的和基本机制,spring security有原生实现,只要通过配置处理就好,csrf功能默认是开启的。


找到安全配置的类WebSecurityConfig,调整配置如下:

@Bean
public SecurityWebFilterChain securityFilterChain(ServerHttpSecurity http) {
    return http
            .headers().disable()
            .authorizeExchange()
            .pathMatchers("/endpoints").hasAnyRole("USER")
            .pathMatchers("/users").hasAnyRole("admin")
            .pathMatchers("/ws/test").hasAnyRole("TEST") // 该行勿改动,否则影响评分
            .pathMatchers("/ws/**").hasAnyRole("admin")
            .anyExchange().authenticated()
            .and()
            .httpBasic()
            .and()
            .formLogin().disable()
            //加入这一行
            .csrf().disable()
            .build();
}

bug5


重启服务, 再次执行./round4


image


第二步又挂了,但是错误内容变了,变成了401,看描述是身份凭证不对,错误日志很贴心的打出了错误凭证内容,是basic auth方式,后面有一串字符。


一看=结尾的乱码字符,会比较容易联想到base64。随便找个base64解密工具,把这串文字放进去。


image


来一看,很典型的账号:密码格式,也就是尝试用admin / admin123 作为账号密码处理失败了。回到WebSecurityConfig类检查配置

@Bean
public MapReactiveUserDetailsService userDetailsService() {
    UserDetails user = User.builder()
            .username("user")
            .password("{noop}user")
            .roles("USER")
            .build();
    UserDetails admin = User.builder()
            .username("admin")
            .password("{noop}admin")
            .roles("ADMIN")
            .build();
    return new MapReactiveUserDetailsService(user, admin);
}

看到admin的配置password似乎是admin,至于{noop}是什么意思,如果有精力调试的话,可以跟进去看下,UserDetailService对于密码管理是使用一个PasswordEncoder接口来处理的,因为输入密码时虽然时明文,但是安全起见密码在数据库中要混淆过才可以存储,否则数据库数据泄露的话后果是灾难性的。而PasswordEncoder有很多的实现类,UserDetailService默认使用的是一个叫做DelegatingPasswordEncoder的类,它会根据情况把明文交给不同的PasswordEncoder处理成密文匹配,而这个"情况"就是前面大括号的内容。下面是DelegatePasswordEncoder注册的各种混淆算法。

public final class PasswordEncoderFactories {
    private PasswordEncoderFactories() {
    }

    public static PasswordEncoder createDelegatingPasswordEncoder() {
        String encodingId = "bcrypt";
        Map<String, PasswordEncoder> encoders = new HashMap();
        encoders.put(encodingId, new BCryptPasswordEncoder());
        encoders.put("ldap", new LdapShaPasswordEncoder());
        encoders.put("MD4", new Md4PasswordEncoder());
        encoders.put("MD5", new MessageDigestPasswordEncoder("MD5"));
        encoders.put("noop", NoOpPasswordEncoder.getInstance());
        encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
        encoders.put("scrypt", new SCryptPasswordEncoder());
        encoders.put("SHA-1", new MessageDigestPasswordEncoder("SHA-1"));
        encoders.put("SHA-256", new MessageDigestPasswordEncoder("SHA-256"));
        encoders.put("sha256", new StandardPasswordEncoder());
        encoders.put("argon2", new Argon2PasswordEncoder());
        return new DelegatingPasswordEncoder(encodingId, encoders);
    }
}

可以看到,noop对应的是一个叫NoOpPasswordEncoder的实例,也就是no operation,明文拿来什么都不干直接明文存储或比较,所以这样也方便了我们修改。


总之{noop}可以看不明白是怎么回事,但是看不明白的东西不要碰,只碰明白的,admin还是认识的改成{noop}admin123。


Bug6


重启服务,再次执行./round4


image


Step2又换个错误继续挂....


这次看到错误是权限错误了,但是到底需要什么权限,从这里看不出来。这时候我们回到Web服务的控制台找线索


image


看到服务日志中,尝试调用POST /users后,报出了一个403错误,应该跟round4的错误对的上的,也就是说可能是/users接口权限有问题,回到代码查看

public class WebSecurityConfig {

    @Bean
    public SecurityWebFilterChain securityFilterChain(ServerHttpSecurity http) {
        return http
                .headers().disable()
                .authorizeExchange()
                .pathMatchers("/endpoints").hasAnyRole("USER")
                .pathMatchers("/users").hasAnyRole("admin")
                .pathMatchers("/ws/test").hasAnyRole("TEST") // 该行勿改动,否则影响评分
                .pathMatchers("/ws/**").hasAnyRole("admin")
                .anyExchange().authenticated()
                .and()
                .httpBasic()
                .and()
                .formLogin().disable()
                .csrf().disable()
                .build();
    }

    @Bean
    public MapReactiveUserDetailsService userDetailsService() {
        UserDetails user = User.builder()
                .username("user")
                .password("{noop}user")
                .roles("USER")
                .build();
        UserDetails admin = User.builder()
                .username("admin")
                .password("{noop}admin123")
                .roles("ADMIN")
                .build();
        return new MapReactiveUserDetailsService(user, admin);
    }

}

从配置中看,admin账号具备一个角色叫做"ADMIN",而上面/users接口的配置是需要权限"admin"。这个不像数据库对大小写不敏感,身份权限这么严谨的东西,一个字母都不    能差,把上面的admin全都改成大写ADMIN。


Bug7


重启服务再试


image


可喜可贺! Step2 跑通了,不管它干了啥,总之是跑通了! 然后处理Step3,又是权限问题,但是我们不知道到底是什么问题,如果账号密码错误的话是401,而且reporter是Step2动态加进去的,要是密码对不上那也没办法调整。


从刚才Step2调试经验来看,403 Forbidden的原因很可能出在权限上面,但是我们也不知道reporter的权限是啥,这时候就要给/users打个断点了,看看Step2到底放进来了个啥。


找到Round4Controller,addUser方法打个断点,执行./round4


image


看到Step2调用接口是参数username是reporter,密码是reporter,还有一个特别的字段,叫做authorities,内容是ROLE_REPORTER,看起来很可疑,似乎是给这个用户赋予权限。


而Step3调用的接口是/ws/DevStudio、/ws/Cosy、WebSecurityConfig中匹配的规则

.pathMatchers("/ws/**").hasAnyRole("ADMIN")

目前服务器的配置来看,只有ADMIN角色可以访问,那就需要给它开后门了,看看hasAnyRole是个数组类型参数,追加一个值"ROLE_REPORTER"

.pathMatchers("/ws/**").hasAnyRole("ADMIN","ROLE_REPORTER")

再试,这次应该成.....又挂了! 错误还是一样?


那就是说刚才添加的角色不对? 


这时候观察一下配置,其他的角色都叫ADMIN、USER、TEST就是这个前面加了个ROLE_很奇怪,而且在POST /users中,这个值放在一个叫做authorities的数组里名字很宽泛,没有特指Role,会不会像刚才的{noop}ADMIN123一样,前面的ROLE_是一个潜规则?如果是个PERMISSION_之类的话就是权限了?那这么说这个角色可能就是叫做REPORTER,重新调整配置

.pathMatchers("/ws/**").hasAnyRole("ADMIN", "REPORTER")

再试! 可喜可贺, 有变化了! 而且提示可以打分了,调用./round4 --submit,无视掉三个灵魂问题后,总算有分数了,但是明显问题多多。看来,只是走通了,但是结果不尽如人意。


Bug8


重新执行./round4 观察输出,可以看到满里面乱码,而且几乎没有一个正常的中文,结合前面Unit Test调试,可以猜测这可能跟字符集处理有关,也就是Utils那个类应该还是有bug。


这时候从输出也大概能看出来些这个程序的目的了, 似乎是接受round4的请求, 输出一些文本。而这些业务接口入口就是/ws/**。


开始寻找/ws/**到底是怎么映射到这些方法的,首先在WebConfig找到可疑方法

    @Autowired
    @Qualifier("ReactiveWebSocketHandler")
    private WebSocketHandler webSocketHandler;

    @Bean
    public Map<String, WebSocketHandler> webSocketUrlMap() {
        return Utils.randomWords(3)
                .stream()
                .map(w -> "/ws/" + w)
                .collect(Collectors.toMap(Function.identity(), w -> webSocketHandler));
    }

似乎注册了一个Map类型的Bean,key是/ws开头的地址,value是WebSocketHandler,而 WebSocketHandler 的定义中,声明了在 spring 容器中,它的名字是ReactiveWebSocketHandler。接着全文寻ReactiveWebSocketHandler 文本,发现另一个可疑的类。

@Component("ReactiveWebSocketHandler")
public class ReactiveWebSocketHandler implements WebSocketHandler {
    @Override
    public Mono<Void> handle(WebSocketSession session) {
        return session.send(
                session.receive()
                        .map(WebSocketMessage::getPayload)
                        .map(getBufferConverter())
                        .map(Utils::decodeMessage)
                        .map(Utils::stripHtmlTag)
                        .log()
                        .map(session::textMessage));
    }

    private Function<DataBuffer, byte[]> getBufferConverter() {
        final byte[] buffer = new byte[1024];
        return (DataBuffer dataBuffer) -> {
            int length = dataBuffer.readableByteCount();
            dataBuffer.read(buffer, 0, length);
            return buffer;
        };
    }
}

看起来很有关系,handle方法看起来是处理文本的,至于Mono是什么,baidu一下和WebFlux有关,也许不是太懂,但是看着一连串的map方法,如果java8的特性熟悉的话,很像Collections.stream()后面或者Optional类中。map的使用方法,是一连串的映射逻辑,从方法名大概猜想各自的功能:


  1. WebSocketMessage::getPayload 获得请求体;
  2. getBufferConverter() 转换成个buffer;
  3. Utils::decodeMessage 解码;
  4. Utils::stripHtmlTag 去掉tag;
  5. log() 打印日志;
  6. session::textMessage 向会话输出文本;


其中1,6都是spring的方法,出bug的可能性微乎其微,log一般也出不了啥错,问题可能就在2,3,4上面。


首先乱码一大片,感觉少不了decodeMessage的干系,分析源代码:

    public static String decodeMessage(byte[] rawMessage) {
        ByteArrayInputStream in = new ByteArrayInputStream(rawMessage);
        DataInputStream dis = new DataInputStream(in);

        try {
            final String charset = charsetNameDecoder.apply(dis);

            return new String(dis.readAllBytes(), charset);
        } catch (IOException e) {
            e.printStackTrace();
            return String.format("%s<_>-<_>.", e.getClass().getSimpleName()); // 此行勿动,影响评分
        }
    }

    private static final ThreadLocal<String> charsetName = new ThreadLocal<>();

    private static final CheckedFunction<DataInputStream, String> charsetNameDecoder = (DataInputStream input) -> {

        byte[] charsetNameBytes = input.readNBytes(input.readByte());

        if (charsetName.get() == null) {
            charsetName.set(new String(charsetNameBytes, ISO_8859_1));
        }

        return charsetName.get();
    };

这两个类做过Unit Test,说明硬伤不大,但是代码行数不多,慢慢分析。


decodeMessage逻辑相对清晰,看起来没大问题,那继续看charsetNameDecoder。


charsetNameDecoder很特别的是用了一个ThreadLocal来存储信息,而WebSocket是不是每次请求都用新的线程来处理也许不清楚,会不会因为线程池重用导致ThreadLocal被污染也不那么清楚,但是至少我费劲解析出了charset,仅因为threadlocal存在数据就不用我解析的了就不太对啊。


从这代码上的意思看起来像,如果能解析出charset最好,如果解析不出来的话,用之前解析出来的,所以按照这个思路调整代码:

   private static final CheckedFunction<DataInputStream, String> charsetNameDecoder = (DataInputStream input) -> {

        byte[] charsetNameBytes = input.readNBytes(input.readByte());

        if (charsetNameBytes != null && charsetNameBytes.length > 0) {
            charsetName.set(new String(charsetNameBytes, ISO_8859_1));
        }

        return charsetName.get();
    };

再试!


image


Bug9


可喜可贺,可以看得出来结果中的乱码明显减少,而且出现了正常的中文,说明字符集的修改有了效果,而且从输出看,似乎在尝试输出一些java代码。


再仔细观察乱码的规律,似乎都是在每一行的最后出现,乱码前面的部分看起来都还好。会不会是流太长的原因?


转向前一个步骤ReactiveWebSocketHandler::getBufferConverter


   private Function<DataBuffer, byte[]> getBufferConverter() {
        final byte[] buffer = new byte[1024];
        return (DataBuffer dataBuffer) -> {
            int length = dataBuffer.readableByteCount();
            dataBuffer.read(buffer, 0, length);
            return buffer;
        };
    }

看起来是构建一个长度为1024的数组,然后把length长度的内容填充进去... 等等?为啥知道length了还固定搞个1024?10月24号很吉利还是咋的?


改了再跑!


   private Function<DataBuffer, byte[]> getBufferConverter() {
        return (DataBuffer dataBuffer) -> {
            int length = dataBuffer.readableByteCount();
            final byte[] buffer = new byte[length];
            dataBuffer.read(buffer, 0, length);
            return buffer;
        };
    }

Bug10


乱码都消失了! 似乎都OK了! 这时候扫一眼web服务的控制台,咋输出了一堆错误?


image


经典错误NPE,看这一排的onNext就感觉跟那一排的map有关系,难道说传下来的数据不能有null?每个步骤检查一下,在Utils::stripHtmlTag找到可疑代码。

       if (ObjectUtils.isEmpty(html)) {
            return null;
        }

        StringBuilder builder = new StringBuilder();
        final Matcher matcher = REGULAR_HTML_TAG.matcher(html);
        while (matcher.find()) {
            matcher.appendReplacement(builder, Strings.EMPTY);
            if (log.isDebugEnabled()) {
                log.debug("remove tag {}", matcher.group("tag"));
            }
        }

        matcher.appendTail(builder);

        return builder.toString();
    }

如果传入html是空的,返回一个null,何必呢,把null改成 ""再试。


终于没有乱码和错误了,提交评分! 90分!


继续找bug! 找了有10分钟也没看出哪里错来,然后无奈再看了下README,发现最后10分是那3个问题的分数...


不看文档害死人啊...


至于最后三个问题,我是慢慢试出来的,正确答案请看主办方的解读吧...


最后


在实际工作当中,是很忌讳在没有阅读代码搞清功能的前提下debug的,因为平时没有round4给我们打分,很容易改掉一个bug,又带来一群新的。


所以,以上的攻略是基于比赛环境,在有明确的评分系统存在时追求速度的一种做法思路,并不推荐使用在日常工作中,当然debug中寻找错误的思路是共通的。


如果有充足的时间了解业务背景(比赛也不会给详细的prd...)和技术思路的前提下,这些程度的bug应该大部分都可以肉眼直接发现排除,当然日常工作中不会有这么多底层的低级bug出现,如果开发天天面对这种bug,架构师就可以拿来祭旗了...


大赛目前全部关卡开放体验,域名地址:https://code83.ide.aliyun.com/,欢迎你来。


推荐阅读


1、用代码玩剧本杀?第3届83行代码大赛剧情官方解析

2、无算法不Java,这道算法题很难?


欢迎大家使用云效,云原生时代新DevOps平台,通过云原生新技术和研发新模式,大幅提升研发效率。现云效公共云基础版不限人数0元使用。


版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,大概有三种登录方式:
2503 0
阿里云服务器ECS远程登录用户名密码查询方法
阿里云服务器ECS远程连接登录输入用户名和密码,阿里云没有默认密码,如果购买时没设置需要先重置实例密码,Windows用户名是administrator,Linux账号是root,阿小云来详细说下阿里云服务器远程登录连接用户名和密码查询方法
9501 0
阿里云服务器端口号设置
阿里云服务器初级使用者可能面临的问题之一. 使用tomcat或者其他服务器软件设置端口号后,比如 一些不是默认的, mysql的 3306, mssql的1433,有时候打不开网页, 原因是没有在ecs安全组去设置这个端口号. 解决: 点击ecs下网络和安全下的安全组 在弹出的安全组中,如果没有就新建安全组,然后点击配置规则 最后如上图点击添加...或快速创建.   have fun!  将编程看作是一门艺术,而不单单是个技术。
8920 0
使用SSH远程登录阿里云ECS服务器
远程连接服务器以及配置环境
2117 0
使用OpenApi弹性释放和设置云服务器ECS释放
云服务器ECS的一个重要特性就是按需创建资源。您可以在业务高峰期按需弹性的自定义规则进行资源创建,在完成业务计算的时候释放资源。本篇将提供几个Tips帮助您更加容易和自动化的完成云服务器的释放和弹性设置。
11017 0
windows server 2008阿里云ECS服务器安全设置
最近我们Sinesafe安全公司在为客户使用阿里云ecs服务器做安全的过程中,发现服务器基础安全性都没有做。为了为站长们提供更加有效的安全基础解决方案,我们Sinesafe将对阿里云服务器win2008 系统进行基础安全部署实战过程! 比较重要的几部分 1.
8120 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,云吞铺子总结大概有三种登录方式: 登录到ECS云服务器控制台 在ECS云服务器控制台用户可以更改密码、更换系统盘、创建快照、配置安全组等操作如何登录ECS云服务器控制台? 1、先登录到阿里云ECS服务器控制台 2、点击顶部的“控制台” 3、通过左侧栏,切换到“云服务器ECS”即可,如下图所示 通过ECS控制台的远程连接来登录到云服务器 阿里云ECS云服务器自带远程连接功能,使用该功能可以登录到云服务器,简单且方便,如下图:点击“远程连接”,第一次连接会自动生成6位数字密码,输入密码即可登录到云服务器上。
20957 0
+关注
云效运营官
阿里云云效,云原生时代新DevOps平台
31
文章
0
问答
来源圈子
更多
+ 订阅
文章排行榜
最热
最新
相关电子书
更多
《Nacos架构&原理》
立即下载
《看见新力量:二》电子书
立即下载
云上自动化运维(CloudOps)白皮书
立即下载