feat: 图片扫描和发布命令 #5
|
@ -7,4 +7,6 @@ public interface IEssayScanService
|
||||||
public Task<BlogContents> ScanContents();
|
public Task<BlogContents> ScanContents();
|
||||||
|
|
||||||
public Task SaveBlogContent(BlogContent content, bool isDraft = true);
|
public Task SaveBlogContent(BlogContent content, bool isDraft = true);
|
||||||
|
|
||||||
|
public Task<ImageScanResult> ScanImages();
|
||||||
}
|
}
|
||||||
|
|
3
YaeBlog.Core/Models/ImageScanResult.cs
Normal file
3
YaeBlog.Core/Models/ImageScanResult.cs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
namespace YaeBlog.Core.Models;
|
||||||
|
|
||||||
|
public record struct ImageScanResult(List<FileInfo> UnusedImages, List<FileInfo> NotFoundImages);
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using YaeBlog.Core.Abstractions;
|
using YaeBlog.Core.Abstractions;
|
||||||
|
@ -9,7 +10,7 @@ using YamlDotNet.Serialization;
|
||||||
|
|
||||||
namespace YaeBlog.Core.Services;
|
namespace YaeBlog.Core.Services;
|
||||||
|
|
||||||
public class EssayScanService(
|
public partial class EssayScanService(
|
||||||
ISerializer yamlSerializer,
|
ISerializer yamlSerializer,
|
||||||
IDeserializer yamlDeserializer,
|
IDeserializer yamlDeserializer,
|
||||||
IOptions<BlogOptions> blogOptions,
|
IOptions<BlogOptions> blogOptions,
|
||||||
|
@ -34,6 +35,11 @@ public class EssayScanService(
|
||||||
? new FileInfo(Path.Combine(drafts.FullName, content.FileName + ".md"))
|
? new FileInfo(Path.Combine(drafts.FullName, content.FileName + ".md"))
|
||||||
: new FileInfo(Path.Combine(posts.FullName, content.FileName + ".md"));
|
: new FileInfo(Path.Combine(posts.FullName, content.FileName + ".md"));
|
||||||
|
|
||||||
|
if (!isDraft)
|
||||||
|
{
|
||||||
|
content.Metadata.Date = DateTime.Now;
|
||||||
|
}
|
||||||
|
|
||||||
if (targetFile.Exists)
|
if (targetFile.Exists)
|
||||||
{
|
{
|
||||||
logger.LogWarning("Blog {} exists, overriding.", targetFile.Name);
|
logger.LogWarning("Blog {} exists, overriding.", targetFile.Name);
|
||||||
|
@ -44,7 +50,15 @@ public class EssayScanService(
|
||||||
await writer.WriteAsync("---\n");
|
await writer.WriteAsync("---\n");
|
||||||
await writer.WriteAsync(yamlSerializer.Serialize(content.Metadata));
|
await writer.WriteAsync(yamlSerializer.Serialize(content.Metadata));
|
||||||
await writer.WriteAsync("---\n");
|
await writer.WriteAsync("---\n");
|
||||||
await writer.WriteAsync("<!--more-->\n");
|
|
||||||
|
if (isDraft)
|
||||||
|
{
|
||||||
|
await writer.WriteLineAsync("<!--more-->");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await writer.WriteAsync(content.FileContent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<ConcurrentBag<BlogContent>> ScanContentsInternal(DirectoryInfo directory)
|
private async Task<ConcurrentBag<BlogContent>> ScanContentsInternal(DirectoryInfo directory)
|
||||||
|
@ -96,6 +110,79 @@ public class EssayScanService(
|
||||||
return contents;
|
return contents;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<ImageScanResult> ScanImages()
|
||||||
|
{
|
||||||
|
BlogContents contents = await ScanContents();
|
||||||
|
ValidateDirectory(_blogOptions.Root, out DirectoryInfo drafts, out DirectoryInfo posts);
|
||||||
|
|
||||||
|
List<FileInfo> unusedFiles = [];
|
||||||
|
List<FileInfo> notFoundFiles = [];
|
||||||
|
|
||||||
|
ImageScanResult draftResult = await ScanUnusedImagesInternal(contents.Drafts, drafts);
|
||||||
|
ImageScanResult postResult = await ScanUnusedImagesInternal(contents.Posts, posts);
|
||||||
|
|
||||||
|
unusedFiles.AddRange(draftResult.UnusedImages);
|
||||||
|
notFoundFiles.AddRange(draftResult.NotFoundImages);
|
||||||
|
unusedFiles.AddRange(postResult.UnusedImages);
|
||||||
|
notFoundFiles.AddRange(postResult.NotFoundImages);
|
||||||
|
|
||||||
|
return new ImageScanResult(unusedFiles, notFoundFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Task<ImageScanResult> ScanUnusedImagesInternal(IEnumerable<BlogContent> contents,
|
||||||
|
DirectoryInfo root)
|
||||||
|
{
|
||||||
|
Regex imageRegex = ImageRegex();
|
||||||
|
ConcurrentBag<FileInfo> unusedImage = [];
|
||||||
|
ConcurrentBag<FileInfo> notFoundImage = [];
|
||||||
|
|
||||||
|
Parallel.ForEach(contents, content =>
|
||||||
|
{
|
||||||
|
MatchCollection result = imageRegex.Matches(content.FileContent);
|
||||||
|
DirectoryInfo imageDirectory = new(Path.Combine(root.FullName, content.FileName));
|
||||||
|
|
||||||
|
Dictionary<string, bool> usedDictionary;
|
||||||
|
|
||||||
|
if (imageDirectory.Exists)
|
||||||
|
{
|
||||||
|
usedDictionary = (from file in imageDirectory.EnumerateFiles()
|
||||||
|
select new KeyValuePair<string, bool>(file.FullName, false)).ToDictionary();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
usedDictionary = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Match match in result)
|
||||||
|
{
|
||||||
|
string imageName = match.Groups[1].Value;
|
||||||
|
|
||||||
|
FileInfo usedFile = imageName.Contains(content.FileName)
|
||||||
|
? new FileInfo(Path.Combine(root.FullName, imageName))
|
||||||
|
: new FileInfo(Path.Combine(root.FullName, content.FileName, imageName));
|
||||||
|
|
||||||
|
if (usedDictionary.TryGetValue(usedFile.FullName, out _))
|
||||||
|
{
|
||||||
|
usedDictionary[usedFile.FullName] = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
notFoundImage.Add(usedFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (KeyValuePair<string, bool> pair in usedDictionary.Where(p => !p.Value))
|
||||||
|
{
|
||||||
|
unusedImage.Add(new FileInfo(pair.Key));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return Task.FromResult(new ImageScanResult(unusedImage.ToList(), notFoundImage.ToList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
[GeneratedRegex(@"\!\[.*?\]\((.*?)\)")]
|
||||||
|
private static partial Regex ImageRegex();
|
||||||
|
|
||||||
private void ValidateDirectory(string root, out DirectoryInfo drafts, out DirectoryInfo posts)
|
private void ValidateDirectory(string root, out DirectoryInfo drafts, out DirectoryInfo posts)
|
||||||
{
|
{
|
||||||
root = Path.Combine(Environment.CurrentDirectory, root);
|
root = Path.Combine(Environment.CurrentDirectory, root);
|
||||||
|
|
|
@ -81,6 +81,14 @@ public static class CommandExtensions
|
||||||
|
|
||||||
newCommand.SetHandler(async (file, _, _, essayScanService) =>
|
newCommand.SetHandler(async (file, _, _, essayScanService) =>
|
||||||
{
|
{
|
||||||
|
BlogContents contents = await essayScanService.ScanContents();
|
||||||
|
|
||||||
|
if (contents.Posts.Any(content => content.FileName == file))
|
||||||
|
{
|
||||||
|
Console.WriteLine("There exists the same title blog in posts.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await essayScanService.SaveBlogContent(new BlogContent
|
await essayScanService.SaveBlogContent(new BlogContent
|
||||||
{
|
{
|
||||||
FileName = file,
|
FileName = file,
|
||||||
|
@ -96,23 +104,113 @@ public static class CommandExtensions
|
||||||
public static void AddListCommand(this RootCommand rootCommand)
|
public static void AddListCommand(this RootCommand rootCommand)
|
||||||
{
|
{
|
||||||
Command command = new("list", "List all blogs");
|
Command command = new("list", "List all blogs");
|
||||||
rootCommand.Add(command);
|
rootCommand.AddCommand(command);
|
||||||
|
|
||||||
command.SetHandler(async (_, _, essyScanService) =>
|
command.SetHandler(async (_, _, essyScanService) =>
|
||||||
{
|
{
|
||||||
BlogContents contents = await essyScanService.ScanContents();
|
BlogContents contents = await essyScanService.ScanContents();
|
||||||
|
|
||||||
Console.WriteLine($"All {contents.Posts.Count} Posts:");
|
Console.WriteLine($"All {contents.Posts.Count} Posts:");
|
||||||
foreach (BlogContent content in contents.Posts)
|
foreach (BlogContent content in contents.Posts.OrderBy(x => x.FileName))
|
||||||
{
|
{
|
||||||
Console.WriteLine($" - {content.FileName}");
|
Console.WriteLine($" - {content.FileName}");
|
||||||
}
|
}
|
||||||
|
|
||||||
Console.WriteLine($"All {contents.Drafts.Count} Drafts:");
|
Console.WriteLine($"All {contents.Drafts.Count} Drafts:");
|
||||||
foreach (BlogContent content in contents.Drafts)
|
foreach (BlogContent content in contents.Drafts.OrderBy(x => x.FileName))
|
||||||
{
|
{
|
||||||
Console.WriteLine($" - {content.FileName}");
|
Console.WriteLine($" - {content.FileName}");
|
||||||
}
|
}
|
||||||
}, new BlogOptionsBinder(), new LoggerBinder<EssayScanService>(), new EssayScanServiceBinder());
|
}, new BlogOptionsBinder(), new LoggerBinder<EssayScanService>(), new EssayScanServiceBinder());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void AddScanCommand(this RootCommand rootCommand)
|
||||||
|
{
|
||||||
|
Command command = new("scan", "Scan unused and not found images.");
|
||||||
|
rootCommand.AddCommand(command);
|
||||||
|
|
||||||
|
Option<bool> removeOption =
|
||||||
|
new(name: "--rm", description: "Remove unused images.", getDefaultValue: () => false);
|
||||||
|
command.AddOption(removeOption);
|
||||||
|
|
||||||
|
command.SetHandler(async (_, _, essayScanService, removeOptionValue) =>
|
||||||
|
{
|
||||||
|
ImageScanResult result = await essayScanService.ScanImages();
|
||||||
|
|
||||||
|
if (result.UnusedImages.Count != 0)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Found unused images:");
|
||||||
|
Console.WriteLine("HINT: use '--rm' to remove unused images.");
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (FileInfo image in result.UnusedImages)
|
||||||
|
{
|
||||||
|
Console.WriteLine($" - {image.FullName}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (removeOptionValue)
|
||||||
|
{
|
||||||
|
foreach (FileInfo image in result.UnusedImages)
|
||||||
|
{
|
||||||
|
image.Delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine("Used not existed images:");
|
||||||
|
|
||||||
|
foreach (FileInfo image in result.NotFoundImages)
|
||||||
|
{
|
||||||
|
Console.WriteLine($" - {image.FullName}");
|
||||||
|
}
|
||||||
|
}, new BlogOptionsBinder(), new LoggerBinder<EssayScanService>(), new EssayScanServiceBinder(), removeOption);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddPublishCommand(this RootCommand rootCommand)
|
||||||
|
{
|
||||||
|
Command command = new("publish", "Publish a new blog file.");
|
||||||
|
rootCommand.AddCommand(command);
|
||||||
|
|
||||||
|
Argument<string> filenameArgument = new(name: "blog name", description: "The published blog filename.");
|
||||||
|
command.AddArgument(filenameArgument);
|
||||||
|
|
||||||
|
command.SetHandler(async (blogOptions, _, essayScanService, filename) =>
|
||||||
|
{
|
||||||
|
BlogContents contents = await essayScanService.ScanContents();
|
||||||
|
|
||||||
|
BlogContent? content = (from blog in contents.Drafts
|
||||||
|
where blog.FileName == filename
|
||||||
|
select blog).FirstOrDefault();
|
||||||
|
|
||||||
|
if (content is null)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Target blog does not exist.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将选中的博客文件复制到posts
|
||||||
|
await essayScanService.SaveBlogContent(content, isDraft: false);
|
||||||
|
|
||||||
|
// 复制图片文件夹
|
||||||
|
DirectoryInfo sourceImageDirectory =
|
||||||
|
new(Path.Combine(blogOptions.Value.Root, "drafts", content.FileName));
|
||||||
|
DirectoryInfo targetImageDirectory =
|
||||||
|
new(Path.Combine(blogOptions.Value.Root, "posts", content.FileName));
|
||||||
|
|
||||||
|
if (sourceImageDirectory.Exists)
|
||||||
|
{
|
||||||
|
targetImageDirectory.Create();
|
||||||
|
foreach (FileInfo file in sourceImageDirectory.EnumerateFiles())
|
||||||
|
{
|
||||||
|
file.CopyTo(Path.Combine(targetImageDirectory.FullName, file.Name), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceImageDirectory.Delete(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除原始的文件
|
||||||
|
FileInfo sourceBlogFile = new(Path.Combine(blogOptions.Value.Root, "drafts", content.FileName + ".md"));
|
||||||
|
sourceBlogFile.Delete();
|
||||||
|
}, new BlogOptionsBinder(),
|
||||||
|
new LoggerBinder<EssayScanService>(), new EssayScanServiceBinder(), filenameArgument);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,5 +7,7 @@ rootCommand.AddServeCommand();
|
||||||
rootCommand.AddNewCommand();
|
rootCommand.AddNewCommand();
|
||||||
rootCommand.AddListCommand();
|
rootCommand.AddListCommand();
|
||||||
rootCommand.AddWatchCommand();
|
rootCommand.AddWatchCommand();
|
||||||
|
rootCommand.AddScanCommand();
|
||||||
|
rootCommand.AddPublishCommand();
|
||||||
|
|
||||||
await rootCommand.InvokeAsync(args);
|
await rootCommand.InvokeAsync(args);
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
---
|
|
||||||
title: test-essay
|
|
||||||
date: 2024-08-22T22:31:34.3177253+08:00
|
|
||||||
tags:
|
|
||||||
---
|
|
||||||
<!--more-->
|
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
title: 日用Linux挑战第五篇 ArchLinux标准安装流程
|
title: 日用Linux挑战 第5篇 标准安装流程
|
||||||
date: 2024-7-16 20:08:37
|
date: 2024-7-16 20:08:37
|
||||||
tags:
|
tags:
|
||||||
- Linux
|
- Linux
|
||||||
|
|
|
@ -44,7 +44,7 @@ date: 2022-07-27 11:34:49
|
||||||
|
|
||||||
而且采用 `Git`还有一个好处,采用 `Github`的 `Insight`功能可以轻松的看出大家的贡献值()。
|
而且采用 `Git`还有一个好处,采用 `Github`的 `Insight`功能可以轻松的看出大家的贡献值()。
|
||||||
|
|
||||||
![img](1.png "贡献")
|
![img](1.png)
|
||||||
|
|
||||||
## 一些技术上的收获
|
## 一些技术上的收获
|
||||||
|
|
||||||
|
|
BIN
YaeBlog/source/posts/compile-mediapipe/2023-01-19-20-21-30-Screenshot_20230119_202008.png
(Stored with Git LFS)
BIN
YaeBlog/source/posts/compile-mediapipe/2023-01-19-20-21-30-Screenshot_20230119_202008.png
(Stored with Git LFS)
Binary file not shown.
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
title: 日用Linux挑战 第0篇
|
title: 日用Linux挑战 第0篇 初见Arch Linux
|
||||||
tags:
|
tags:
|
||||||
- Linux
|
- Linux
|
||||||
- 随笔
|
- 随笔
|
||||||
|
|
BIN
YaeBlog/source/posts/daily-linux-0/2023-01-12-13-28-11-Screenshot_20230112_132756.png
(Stored with Git LFS)
BIN
YaeBlog/source/posts/daily-linux-0/2023-01-12-13-28-11-Screenshot_20230112_132756.png
(Stored with Git LFS)
Binary file not shown.
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
title: 日用Linux挑战第1篇
|
title: 日用Linux挑战 第1篇 问题与挑战
|
||||||
tags:
|
tags:
|
||||||
- Linux
|
- Linux
|
||||||
- 随笔
|
- 随笔
|
||||||
|
@ -45,7 +45,7 @@ date: 2023-03-08 22:37:29
|
||||||
|
|
||||||
简单的说,我不认为现在`Linux`已经准备好切换到`Wayland`下了。
|
简单的说,我不认为现在`Linux`已经准备好切换到`Wayland`下了。
|
||||||
|
|
||||||
> 听说最精`Ubuntu 22.04`已经默认使用`Wayland`作为显示协议了,等我有了其他的电脑可以试一试,看看商业公司的加入能不能带来一点转机。
|
> 听说最新的`Ubuntu 22.04`已经默认使用`Wayland`作为显示协议了,等我有了其他的电脑可以试一试,看看商业公司的加入能不能带来一点转机。
|
||||||
|
|
||||||
## 使用中发现的问题
|
## 使用中发现的问题
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
title: 日用Linux挑战 第2篇
|
title: 日用Linux挑战 第2篇 Wayland
|
||||||
tags:
|
tags:
|
||||||
- 随笔
|
- 随笔
|
||||||
- Linux
|
- Linux
|
||||||
|
|
BIN
YaeBlog/source/posts/daily-linux-2/image-20230720202327220.png
(Stored with Git LFS)
BIN
YaeBlog/source/posts/daily-linux-2/image-20230720202327220.png
(Stored with Git LFS)
Binary file not shown.
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
title: 日用Linux挑战 第三篇
|
title: 日用Linux挑战 第3篇 放弃Wayland
|
||||||
tags:
|
tags:
|
||||||
- 随笔
|
- 随笔
|
||||||
- Linux
|
- Linux
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
title: 日用Linux挑战 第四篇
|
title: 日用Linux挑战 第4篇 新的开始
|
||||||
tags:
|
tags:
|
||||||
- Linux
|
- Linux
|
||||||
- 随笔
|
- 随笔
|
||||||
|
|
|
@ -44,7 +44,7 @@ bool ParserBoolValue(string input)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
这就是一个**解析器(Parser)**的原型实现:输入需要识别的字符串,输出识别的结果。在这个原型方法中,我们可以很容易发现两个问题:
|
这就是一个 **解析器(Parser)** 的原型实现:输入需要识别的字符串,输出识别的结果。在这个原型方法中,我们可以很容易发现两个问题:
|
||||||
|
|
||||||
1. 解析器并不会**消耗**输入的字符串,缺少一个读取字符串的游标。
|
1. 解析器并不会**消耗**输入的字符串,缺少一个读取字符串的游标。
|
||||||
2. 解析器输出结果的方式并不高明,输出的结果只能是解析成功情况的结果,解析失败的情况需要通过抛出一个自定义的异常`InvalidInputException`,但是考虑到一个解析器解析失败是非常常见的情况,在这里使用异常方式返回错误结果会导致非常严重的性能问题。
|
2. 解析器输出结果的方式并不高明,输出的结果只能是解析成功情况的结果,解析失败的情况需要通过抛出一个自定义的异常`InvalidInputException`,但是考虑到一个解析器解析失败是非常常见的情况,在这里使用异常方式返回错误结果会导致非常严重的性能问题。
|
||||||
|
@ -64,7 +64,7 @@ public abstract class Parser<T>
|
||||||
|
|
||||||
上面解析器基类中,类上的泛型参数`T`表示该解析器最终解析结果的类型,解析函数`Parse`的泛型参数`TState`是实现了输入状态`IReadState`的类型,返回的类型`ParseResult`就是上文中提到的解析结果基类。
|
上面解析器基类中,类上的泛型参数`T`表示该解析器最终解析结果的类型,解析函数`Parse`的泛型参数`TState`是实现了输入状态`IReadState`的类型,返回的类型`ParseResult`就是上文中提到的解析结果基类。
|
||||||
|
|
||||||
在设计完解析器之后,该谈一谈**组合子(Combinator)**了。实际上组合子就是将多个解析器组合到一起的一系列函数,输入一个或者多个解析器,输出一个合并之后的解析器。容易想到,各种解析器组合在一起的方式千千万万,但是实际上我们只需要实现一系列基本的组合子,就可以通过综合使用各种解析器和组合子将各种需要的解析器和组合子实现出现。实际上,这也是解析器组合子思想的集中体现,通过基础的“砖块”(解析器)和“水泥“(组合子)设计和实现各种构建,最终建造出宏伟的高楼。
|
在设计完解析器之后,该谈一谈 **组合子(Combinator)** 了。实际上组合子就是将多个解析器组合到一起的一系列函数,输入一个或者多个解析器,输出一个合并之后的解析器。容易想到,各种解析器组合在一起的方式千千万万,但是实际上我们只需要实现一系列基本的组合子,就可以通过综合使用各种解析器和组合子将各种需要的解析器和组合子实现出现。实际上,这也是解析器组合子思想的集中体现,通过基础的“砖块”(解析器)和“水泥“(组合子)设计和实现各种构建,最终建造出宏伟的高楼。
|
||||||
|
|
||||||
基础解析器和组合子的选择因人而异,但是一个常见的组合是:
|
基础解析器和组合子的选择因人而异,但是一个常见的组合是:
|
||||||
|
|
||||||
|
|
|
@ -55,7 +55,7 @@ cert: false
|
||||||
在进行了这些更改之后,我们再次输入code-server重启服务,如果一次顺利,我们可以看见以下的启动信息
|
在进行了这些更改之后,我们再次输入code-server重启服务,如果一次顺利,我们可以看见以下的启动信息
|
||||||
![启动信息](./vscode-in-browser/1.png)
|
![启动信息](./vscode-in-browser/1.png)
|
||||||
我们可以打开浏览器,在地址栏中输入你的服务器公网IP加上你自己设置的端口号,就可以打开自己的VSCode Online界面了。
|
我们可以打开浏览器,在地址栏中输入你的服务器公网IP加上你自己设置的端口号,就可以打开自己的VSCode Online界面了。
|
||||||
![主界面](./vscode-in-browser/1.png)
|
![主界面](./vscode-in-browser/2.png)
|
||||||
输入自己的设置密码,就可以开始把浏览器中的VSCode当作自己本地计算机上的VSCode使用了,不过其中的文件是位于自己的服务器上的。
|
输入自己的设置密码,就可以开始把浏览器中的VSCode当作自己本地计算机上的VSCode使用了,不过其中的文件是位于自己的服务器上的。
|
||||||
>如果你和我一样使用的阿里云的服务器,可能还需要到服务器的管理界面设置安全组放行相应的端口,具体参考[这篇文章](https://help.aliyun.com/document_detail/59086.html?spm=5176.10173289.help.dexternal.4ff02e77892BZP)
|
>如果你和我一样使用的阿里云的服务器,可能还需要到服务器的管理界面设置安全组放行相应的端口,具体参考[这篇文章](https://help.aliyun.com/document_detail/59086.html?spm=5176.10173289.help.dexternal.4ff02e77892BZP)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user