Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
徽霖
Vscode
提交
92dafb39
V
Vscode
项目概览
徽霖
/
Vscode
通知
9
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
DevOps
流水线
流水线任务
计划
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
V
Vscode
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
提交
92dafb39
编写于
8月 15, 2019
作者:
B
Benjamin Pasero
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
web - implement credentials provider and add API
上级
2b22c0a0
变更
12
显示空白变更内容
内联
并排
Showing
12 changed file
with
179 addition
and
51 deletion
+179
-51
src/vs/workbench/api/browser/mainThreadKeytar.ts
src/vs/workbench/api/browser/mainThreadKeytar.ts
+11
-27
src/vs/workbench/browser/web.main.ts
src/vs/workbench/browser/web.main.ts
+1
-6
src/vs/workbench/services/credentials/browser/credentialsService.ts
...kbench/services/credentials/browser/credentialsService.ts
+132
-0
src/vs/workbench/services/credentials/common/credentials.ts
src/vs/workbench/services/credentials/common/credentials.ts
+4
-2
src/vs/workbench/services/credentials/node/credentialsService.ts
...workbench/services/credentials/node/credentialsService.ts
+6
-2
src/vs/workbench/services/environment/browser/environmentService.ts
...kbench/services/environment/browser/environmentService.ts
+10
-8
src/vs/workbench/services/environment/common/environmentService.ts
...rkbench/services/environment/common/environmentService.ts
+3
-0
src/vs/workbench/services/userData/test/electron-browser/fileUserDataProvider.test.ts
...erData/test/electron-browser/fileUserDataProvider.test.ts
+3
-3
src/vs/workbench/workbench.desktop.main.ts
src/vs/workbench/workbench.desktop.main.ts
+1
-3
src/vs/workbench/workbench.web.api.ts
src/vs/workbench/workbench.web.api.ts
+6
-0
src/vs/workbench/workbench.web.main.ts
src/vs/workbench/workbench.web.main.ts
+1
-0
tslint.json
tslint.json
+1
-0
未找到文件。
src/vs/workbench/api/browser/mainThreadKeytar.ts
浏览文件 @
92dafb39
...
...
@@ -5,49 +5,33 @@
import
{
extHostNamedCustomer
}
from
'
vs/workbench/api/common/extHostCustomers
'
;
import
{
MainContext
,
MainThreadKeytarShape
,
IExtHostContext
}
from
'
vs/workbench/api/common/extHost.protocol
'
;
import
{
ICredentialsService
}
from
'
vs/platform/credentials/common/credentials
'
;
import
{
optional
}
from
'
vs/platform/instantiation/common/instantiation
'
;
import
{
ICredentialsService
}
from
'
vs/workbench/services/credentials/common/credentials
'
;
@
extHostNamedCustomer
(
MainContext
.
MainThreadKeytar
)
export
class
MainThreadKeytar
implements
MainThreadKeytarShape
{
private
readonly
_credentialsService
?:
ICredentialsService
;
constructor
(
_extHostContext
:
IExtHostContext
,
@
optional
(
ICredentialsService
)
credentialsService
:
ICredentialsService
,
)
{
this
.
_credentialsService
=
credentialsService
;
}
dispose
():
void
{
//
}
@
ICredentialsService
private
readonly
_credentialsService
:
ICredentialsService
,
)
{
}
async
$getPassword
(
service
:
string
,
account
:
string
):
Promise
<
string
|
null
>
{
if
(
this
.
_credentialsService
)
{
return
this
.
_credentialsService
.
getPassword
(
service
,
account
);
}
return
null
;
}
async
$setPassword
(
service
:
string
,
account
:
string
,
password
:
string
):
Promise
<
void
>
{
if
(
this
.
_credentialsService
)
{
return
this
.
_credentialsService
.
setPassword
(
service
,
account
,
password
);
}
}
async
$deletePassword
(
service
:
string
,
account
:
string
):
Promise
<
boolean
>
{
if
(
this
.
_credentialsService
)
{
return
this
.
_credentialsService
.
deletePassword
(
service
,
account
);
}
return
false
;
}
async
$findPassword
(
service
:
string
):
Promise
<
string
|
null
>
{
if
(
this
.
_credentialsService
)
{
return
this
.
_credentialsService
.
findPassword
(
service
);
}
return
null
;
dispose
():
void
{
//
}
}
src/vs/workbench/browser/web.main.ts
浏览文件 @
92dafb39
...
...
@@ -117,12 +117,7 @@ class CodeRendererMain extends Disposable {
const
payload
=
await
this
.
resolveWorkspaceInitializationPayload
();
// Environment
const
environmentService
=
new
BrowserWorkbenchEnvironmentService
({
workspaceId
:
payload
.
id
,
remoteAuthority
:
this
.
configuration
.
remoteAuthority
,
webviewEndpoint
:
this
.
configuration
.
webviewEndpoint
,
connectionToken
:
this
.
configuration
.
connectionToken
});
const
environmentService
=
new
BrowserWorkbenchEnvironmentService
(
payload
.
id
,
this
.
configuration
);
serviceCollection
.
set
(
IWorkbenchEnvironmentService
,
environmentService
);
// Product
...
...
src/vs/workbench/services/credentials/browser/credentialsService.ts
0 → 100644
浏览文件 @
92dafb39
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import
{
ICredentialsService
}
from
'
vs/workbench/services/credentials/common/credentials
'
;
import
{
registerSingleton
}
from
'
vs/platform/instantiation/common/extensions
'
;
import
{
ServiceIdentifier
}
from
'
vs/platform/instantiation/common/instantiation
'
;
import
{
IWorkbenchEnvironmentService
}
from
'
vs/workbench/services/environment/common/environmentService
'
;
export
interface
ICredentialsProvider
{
getPassword
(
service
:
string
,
account
:
string
):
Promise
<
string
|
null
>
;
setPassword
(
service
:
string
,
account
:
string
,
password
:
string
):
Promise
<
void
>
;
deletePassword
(
service
:
string
,
account
:
string
):
Promise
<
boolean
>
;
findPassword
(
service
:
string
):
Promise
<
string
|
null
>
;
}
export
class
BrowserCredentialsService
implements
ICredentialsService
{
_serviceBrand
!
:
ServiceIdentifier
<
any
>
;
private
credentialsProvider
:
ICredentialsProvider
;
constructor
(@
IWorkbenchEnvironmentService
environmentService
:
IWorkbenchEnvironmentService
)
{
if
(
environmentService
.
options
&&
environmentService
.
options
.
credentialsProvider
)
{
this
.
credentialsProvider
=
environmentService
.
options
.
credentialsProvider
;
}
else
{
this
.
credentialsProvider
=
new
LocalStorageCredentialsProvider
();
}
}
async
getPassword
(
service
:
string
,
account
:
string
):
Promise
<
string
|
null
>
{
return
this
.
credentialsProvider
.
getPassword
(
service
,
account
);
}
async
setPassword
(
service
:
string
,
account
:
string
,
password
:
string
):
Promise
<
void
>
{
return
this
.
credentialsProvider
.
setPassword
(
service
,
account
,
password
);
}
async
deletePassword
(
service
:
string
,
account
:
string
):
Promise
<
boolean
>
{
return
this
.
credentialsProvider
.
deletePassword
(
service
,
account
);
}
async
findPassword
(
service
:
string
):
Promise
<
string
|
null
>
{
return
this
.
credentialsProvider
.
findPassword
(
service
);
}
}
interface
ICredential
{
service
:
string
;
account
:
string
;
password
:
string
;
}
class
LocalStorageCredentialsProvider
implements
ICredentialsProvider
{
static
readonly
CREDENTIALS_OPENED_KEY
=
'
credentials.provider
'
;
private
_credentials
:
ICredential
[];
private
get
credentials
():
ICredential
[]
{
if
(
!
this
.
_credentials
)
{
try
{
const
serializedCredentials
=
window
.
localStorage
.
getItem
(
LocalStorageCredentialsProvider
.
CREDENTIALS_OPENED_KEY
);
if
(
serializedCredentials
)
{
this
.
_credentials
=
JSON
.
parse
(
serializedCredentials
);
}
}
catch
(
error
)
{
// ignore
}
if
(
!
Array
.
isArray
(
this
.
_credentials
))
{
this
.
_credentials
=
[];
}
}
return
this
.
_credentials
;
}
private
save
():
void
{
window
.
localStorage
.
setItem
(
LocalStorageCredentialsProvider
.
CREDENTIALS_OPENED_KEY
,
JSON
.
stringify
(
this
.
credentials
));
}
async
getPassword
(
service
:
string
,
account
:
string
):
Promise
<
string
|
null
>
{
return
this
.
doGetPassword
(
service
,
account
);
}
private
async
doGetPassword
(
service
:
string
,
account
?:
string
):
Promise
<
string
|
null
>
{
for
(
const
credential
of
this
.
credentials
)
{
if
(
credential
.
service
===
service
)
{
if
(
typeof
account
!==
'
string
'
||
account
===
credential
.
account
)
{
return
credential
.
password
;
}
}
}
return
null
;
}
async
setPassword
(
service
:
string
,
account
:
string
,
password
:
string
):
Promise
<
void
>
{
this
.
deletePassword
(
service
,
account
);
this
.
credentials
.
push
({
service
,
account
,
password
});
this
.
save
();
}
async
deletePassword
(
service
:
string
,
account
:
string
):
Promise
<
boolean
>
{
let
found
=
false
;
this
.
_credentials
=
this
.
credentials
.
filter
(
credential
=>
{
if
(
credential
.
service
===
service
&&
credential
.
account
===
account
)
{
found
=
true
;
return
false
;
}
return
true
;
});
if
(
found
)
{
this
.
save
();
}
return
found
;
}
async
findPassword
(
service
:
string
):
Promise
<
string
|
null
>
{
return
this
.
doGetPassword
(
service
);
}
}
registerSingleton
(
ICredentialsService
,
BrowserCredentialsService
,
true
);
src/vs/
platform
/credentials/common/credentials.ts
→
src/vs/
workbench/services
/credentials/common/credentials.ts
浏览文件 @
92dafb39
...
...
@@ -3,12 +3,14 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import
{
createDecorator
}
from
'
vs/platform/instantiation/common/instantiation
'
;
import
{
createDecorator
,
ServiceIdentifier
}
from
'
vs/platform/instantiation/common/instantiation
'
;
export
const
ICredentialsService
=
createDecorator
<
ICredentialsService
>
(
'
ICredentialsService
'
);
export
interface
ICredentialsService
{
_serviceBrand
:
any
;
_serviceBrand
:
ServiceIdentifier
<
any
>
;
getPassword
(
service
:
string
,
account
:
string
):
Promise
<
string
|
null
>
;
setPassword
(
service
:
string
,
account
:
string
,
password
:
string
):
Promise
<
void
>
;
deletePassword
(
service
:
string
,
account
:
string
):
Promise
<
boolean
>
;
...
...
src/vs/
platform
/credentials/node/credentialsService.ts
→
src/vs/
workbench/services
/credentials/node/credentialsService.ts
浏览文件 @
92dafb39
...
...
@@ -3,8 +3,10 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import
{
ICredentialsService
}
from
'
vs/
platform
/credentials/common/credentials
'
;
import
{
ICredentialsService
}
from
'
vs/
workbench/services
/credentials/common/credentials
'
;
import
{
IdleValue
}
from
'
vs/base/common/async
'
;
import
{
registerSingleton
}
from
'
vs/platform/instantiation/common/extensions
'
;
import
{
ServiceIdentifier
}
from
'
vs/platform/instantiation/common/instantiation
'
;
type
KeytarModule
=
{
getPassword
(
service
:
string
,
account
:
string
):
Promise
<
string
|
null
>
;
...
...
@@ -15,7 +17,7 @@ type KeytarModule = {
export
class
KeytarCredentialsService
implements
ICredentialsService
{
_serviceBrand
:
any
;
_serviceBrand
!
:
ServiceIdentifier
<
any
>
;
private
readonly
_keytar
=
new
IdleValue
<
Promise
<
KeytarModule
>>
(()
=>
import
(
'
keytar
'
));
...
...
@@ -39,3 +41,5 @@ export class KeytarCredentialsService implements ICredentialsService {
return
keytar
.
findPassword
(
service
);
}
}
registerSingleton
(
ICredentialsService
,
KeytarCredentialsService
,
true
);
src/vs/workbench/services/environment/browser/environmentService.ts
浏览文件 @
92dafb39
...
...
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import
{
IWindowConfiguration
,
IPath
,
IPathsToWaitFor
}
from
'
vs/platform/windows/common/windows
'
;
import
{
IE
nvironmentService
,
IE
xtensionHostDebugParams
,
IDebugParams
,
BACKUPS
}
from
'
vs/platform/environment/common/environment
'
;
import
{
IExtensionHostDebugParams
,
IDebugParams
,
BACKUPS
}
from
'
vs/platform/environment/common/environment
'
;
import
{
ServiceIdentifier
}
from
'
vs/platform/instantiation/common/instantiation
'
;
import
{
URI
}
from
'
vs/base/common/uri
'
;
import
{
IProcessEnvironment
}
from
'
vs/base/common/platform
'
;
...
...
@@ -13,6 +13,8 @@ import { ExportData } from 'vs/base/common/performance';
import
{
LogLevel
}
from
'
vs/platform/log/common/log
'
;
import
{
joinPath
}
from
'
vs/base/common/resources
'
;
import
{
Schemas
}
from
'
vs/base/common/network
'
;
import
{
IWorkbenchEnvironmentService
}
from
'
vs/workbench/services/environment/common/environmentService
'
;
import
{
IWorkbenchConstructionOptions
}
from
'
vs/workbench/workbench.web.api
'
;
export
class
BrowserWindowConfiguration
implements
IWindowConfiguration
{
...
...
@@ -66,26 +68,26 @@ export interface IBrowserWindowConfiguration {
connectionToken
?:
string
;
}
export
class
BrowserWorkbenchEnvironmentService
implements
IEnvironmentService
{
export
class
BrowserWorkbenchEnvironmentService
implements
I
Workbench
EnvironmentService
{
_serviceBrand
!
:
ServiceIdentifier
<
IEnvironmentService
>
;
_serviceBrand
!
:
ServiceIdentifier
<
I
Workbench
EnvironmentService
>
;
readonly
configuration
:
IWindowConfiguration
=
new
BrowserWindowConfiguration
();
constructor
(
configuration
:
IBrowserWindowConfiguration
)
{
constructor
(
workspaceId
:
string
,
public
readonly
options
:
IWorkbenchConstructionOptions
)
{
this
.
args
=
{
_
:
[]
};
this
.
appRoot
=
'
/web/
'
;
this
.
appNameLong
=
'
Visual Studio Code - Web
'
;
this
.
configuration
.
remoteAuthority
=
configuration
.
remoteAuthority
;
this
.
configuration
.
remoteAuthority
=
options
.
remoteAuthority
;
this
.
userRoamingDataHome
=
URI
.
file
(
'
/User
'
).
with
({
scheme
:
Schemas
.
userData
});
this
.
settingsResource
=
joinPath
(
this
.
userRoamingDataHome
,
'
settings.json
'
);
this
.
keybindingsResource
=
joinPath
(
this
.
userRoamingDataHome
,
'
keybindings.json
'
);
this
.
keyboardLayoutResource
=
joinPath
(
this
.
userRoamingDataHome
,
'
keyboardLayout.json
'
);
this
.
localeResource
=
joinPath
(
this
.
userRoamingDataHome
,
'
locale.json
'
);
this
.
backupHome
=
joinPath
(
this
.
userRoamingDataHome
,
BACKUPS
);
this
.
configuration
.
backupWorkspaceResource
=
joinPath
(
this
.
backupHome
,
configuration
.
workspaceId
);
this
.
configuration
.
connectionToken
=
configuration
.
connectionToken
||
this
.
getConnectionTokenFromLocation
();
this
.
configuration
.
backupWorkspaceResource
=
joinPath
(
this
.
backupHome
,
workspaceId
);
this
.
configuration
.
connectionToken
=
options
.
connectionToken
||
this
.
getConnectionTokenFromLocation
();
this
.
logsPath
=
'
/web/logs
'
;
...
...
@@ -94,7 +96,7 @@ export class BrowserWorkbenchEnvironmentService implements IEnvironmentService {
break
:
false
};
this
.
webviewEndpoint
=
configuration
.
webviewEndpoint
;
this
.
webviewEndpoint
=
options
.
webviewEndpoint
;
this
.
untitledWorkspacesHome
=
URI
.
from
({
scheme
:
Schemas
.
untitled
,
path
:
'
Workspaces
'
});
if
(
document
&&
document
.
location
&&
document
.
location
.
search
)
{
...
...
src/vs/workbench/services/environment/common/environmentService.ts
浏览文件 @
92dafb39
...
...
@@ -6,6 +6,7 @@
import
{
createDecorator
,
ServiceIdentifier
}
from
'
vs/platform/instantiation/common/instantiation
'
;
import
{
IWindowConfiguration
}
from
'
vs/platform/windows/common/windows
'
;
import
{
IEnvironmentService
}
from
'
vs/platform/environment/common/environment
'
;
import
{
IWorkbenchConstructionOptions
}
from
'
vs/workbench/workbench.web.api
'
;
export
const
IWorkbenchEnvironmentService
=
createDecorator
<
IWorkbenchEnvironmentService
>
(
'
environmentService
'
);
...
...
@@ -14,4 +15,6 @@ export interface IWorkbenchEnvironmentService extends IEnvironmentService {
_serviceBrand
:
ServiceIdentifier
<
IEnvironmentService
>
;
readonly
configuration
:
IWindowConfiguration
;
readonly
options
?:
IWorkbenchConstructionOptions
;
}
src/vs/workbench/services/userData/test/electron-browser/fileUserDataProvider.test.ts
浏览文件 @
92dafb39
...
...
@@ -47,7 +47,7 @@ suite('FileUserDataProvider', () => {
userDataResource
=
URI
.
file
(
userDataPath
).
with
({
scheme
:
Schemas
.
userData
});
await
Promise
.
all
([
pfs
.
mkdirp
(
userDataPath
),
pfs
.
mkdirp
(
backupsPath
)]);
const
environmentService
=
new
BrowserWorkbenchEnvironmentService
(
{
workspaceId
:
'
workspaceId
'
});
const
environmentService
=
new
BrowserWorkbenchEnvironmentService
(
'
workspaceId
'
,
{
remoteAuthority
:
'
remote
'
});
environmentService
.
userRoamingDataHome
=
userDataResource
;
const
userDataFileSystemProvider
=
new
FileUserDataProvider
(
URI
.
file
(
userDataPath
),
URI
.
file
(
backupsPath
),
diskFileSystemProvider
,
environmentService
);
...
...
@@ -321,7 +321,7 @@ suite('FileUserDataProvider - Watching', () => {
localUserDataResource
=
URI
.
file
(
userDataPath
);
userDataResource
=
localUserDataResource
.
with
({
scheme
:
Schemas
.
userData
});
const
environmentService
=
new
BrowserWorkbenchEnvironmentService
(
{
workspaceId
:
'
workspaceId
'
});
const
environmentService
=
new
BrowserWorkbenchEnvironmentService
(
'
workspaceId
'
,
{
remoteAuthority
:
'
remote
'
});
environmentService
.
userRoamingDataHome
=
userDataResource
;
const
userDataFileSystemProvider
=
new
FileUserDataProvider
(
localUserDataResource
,
localBackupsResource
,
new
TestFileSystemProvider
(
fileEventEmitter
.
event
),
environmentService
);
...
...
src/vs/workbench/workbench.desktop.main.ts
浏览文件 @
92dafb39
...
...
@@ -49,6 +49,7 @@ import 'vs/workbench/services/accessibility/node/accessibilityService';
import
'
vs/workbench/services/remote/node/tunnelService
'
;
import
'
vs/workbench/services/backup/node/backupFileService
'
;
import
'
vs/workbench/services/opener/electron-browser/openerService
'
;
import
'
vs/workbench/services/credentials/node/credentialsService
'
;
import
{
registerSingleton
}
from
'
vs/platform/instantiation/common/extensions
'
;
import
{
IClipboardService
}
from
'
vs/platform/clipboard/common/clipboardService
'
;
...
...
@@ -74,8 +75,6 @@ import { IMenubarService } from 'vs/platform/menubar/common/menubar';
import
{
MenubarService
}
from
'
vs/platform/menubar/electron-browser/menubarService
'
;
import
{
IURLService
}
from
'
vs/platform/url/common/url
'
;
import
{
RelayURLService
}
from
'
vs/platform/url/electron-browser/urlService
'
;
import
{
ICredentialsService
}
from
'
vs/platform/credentials/common/credentials
'
;
import
{
KeytarCredentialsService
}
from
'
vs/platform/credentials/node/credentialsService
'
;
registerSingleton
(
IClipboardService
,
ClipboardService
,
true
);
registerSingleton
(
IRequestService
,
RequestService
,
true
);
...
...
@@ -89,7 +88,6 @@ registerSingleton(IIssueService, IssueService);
registerSingleton
(
IWorkspacesService
,
WorkspacesService
);
registerSingleton
(
IMenubarService
,
MenubarService
);
registerSingleton
(
IURLService
,
RelayURLService
);
registerSingleton
(
ICredentialsService
,
KeytarCredentialsService
,
true
);
//#endregion
...
...
src/vs/workbench/workbench.web.api.ts
浏览文件 @
92dafb39
...
...
@@ -8,6 +8,7 @@ import { main } from 'vs/workbench/browser/web.main';
import
{
UriComponents
}
from
'
vs/base/common/uri
'
;
import
{
IFileSystemProvider
}
from
'
vs/platform/files/common/files
'
;
import
{
IWebSocketFactory
}
from
'
vs/platform/remote/browser/browserSocketFactory
'
;
import
{
ICredentialsProvider
}
from
'
vs/workbench/services/credentials/browser/credentialsService
'
;
export
interface
IWorkbenchConstructionOptions
{
...
...
@@ -53,6 +54,11 @@ export interface IWorkbenchConstructionOptions {
* Experimental: Whether to enable the smoke test driver.
*/
driver
?:
boolean
;
/**
* Experimental: The credentials provider to store and retrieve secrets.
*/
credentialsProvider
?:
ICredentialsProvider
;
}
/**
...
...
src/vs/workbench/workbench.web.main.ts
浏览文件 @
92dafb39
...
...
@@ -36,6 +36,7 @@ import 'vs/workbench/services/extensions/browser/extensionService';
import
'
vs/workbench/services/extensionManagement/common/extensionManagementServerService
'
;
import
'
vs/workbench/services/telemetry/browser/telemetryService
'
;
import
'
vs/workbench/services/configurationResolver/browser/configurationResolverService
'
;
import
'
vs/workbench/services/credentials/browser/credentialsService
'
;
import
'
vs/workbench/browser/web.simpleservices
'
;
import
{
registerSingleton
}
from
'
vs/platform/instantiation/common/extensions
'
;
...
...
tslint.json
浏览文件 @
92dafb39
...
...
@@ -436,6 +436,7 @@
"**/vs/base/**/common/**"
,
"**/vs/platform/**/common/**"
,
"**/vs/editor/common/**"
,
"**/vs/workbench/workbench.web.api"
,
"**/vs/workbench/common/**"
,
"**/vs/workbench/services/**/common/**"
,
"**/vs/workbench/api/**/common/**"
,
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录