Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
Z
ZLMediaKit
概览
Overview
Details
Activity
Cycle Analytics
版本库
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
问题
0
Issues
0
列表
Board
标记
里程碑
合并请求
0
Merge Requests
0
CI / CD
CI / CD
流水线
作业
日程表
图表
维基
Wiki
代码片段
Snippets
成员
Collapse sidebar
Close sidebar
活动
图像
聊天
创建新问题
作业
提交
Issue Boards
Open sidebar
张翔宇
ZLMediaKit
Commits
f84981dc
Commit
f84981dc
authored
Sep 20, 2020
by
xiongziliang
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
合并pr(添加录制ts完成通知): #479
parent
0fce108d
隐藏空白字符变更
内嵌
并排
正在显示
11 个修改的文件
包含
94 行增加
和
145 行删除
+94
-145
api/include/mk_events_objects.h
+11
-11
api/source/mk_events_objects.cpp
+11
-11
server/WebHook.cpp
+18
-26
src/Record/HlsMaker.cpp
+1
-5
src/Record/HlsMaker.h
+6
-20
src/Record/HlsMakerImp.cpp
+17
-20
src/Record/HlsMakerImp.h
+2
-3
src/Record/MP4Recorder.cpp
+14
-14
src/Record/MP4Recorder.h
+0
-1
src/Record/RecordInfo.h
+0
-34
src/Record/Recorder.h
+14
-0
没有找到文件。
api/include/mk_events_objects.h
查看文件 @
f84981dc
...
...
@@ -19,25 +19,25 @@ extern "C" {
///////////////////////////////////////////MP4Info/////////////////////////////////////////////
//MP4Info对象的C映射
typedef
void
*
mk_mp4_info
;
//
MP4Info::ui64StartedTime
//
GMT 标准时间,单位秒
API_EXPORT
uint64_t
API_CALL
mk_mp4_info_get_start_time
(
const
mk_mp4_info
ctx
);
//
MP4Info::ui64TimeLen
API_EXPORT
uint64_
t
API_CALL
mk_mp4_info_get_time_len
(
const
mk_mp4_info
ctx
);
//
MP4Info::ui64FileSize
//
录像长度,单位秒
API_EXPORT
floa
t
API_CALL
mk_mp4_info_get_time_len
(
const
mk_mp4_info
ctx
);
//
文件大小,单位 BYTE
API_EXPORT
uint64_t
API_CALL
mk_mp4_info_get_file_size
(
const
mk_mp4_info
ctx
);
//
MP4Info::strFilePath
//
文件路径
API_EXPORT
const
char
*
API_CALL
mk_mp4_info_get_file_path
(
const
mk_mp4_info
ctx
);
//
MP4Info::strFileName
//
文件名称
API_EXPORT
const
char
*
API_CALL
mk_mp4_info_get_file_name
(
const
mk_mp4_info
ctx
);
//
MP4Info::strFolder
//
文件夹路径
API_EXPORT
const
char
*
API_CALL
mk_mp4_info_get_folder
(
const
mk_mp4_info
ctx
);
//
MP4Info::strUrl
//
播放路径
API_EXPORT
const
char
*
API_CALL
mk_mp4_info_get_url
(
const
mk_mp4_info
ctx
);
//
MP4Info::strVhost
//
应用名称
API_EXPORT
const
char
*
API_CALL
mk_mp4_info_get_vhost
(
const
mk_mp4_info
ctx
);
//
MP4Info::strAppName
//
流 ID
API_EXPORT
const
char
*
API_CALL
mk_mp4_info_get_app
(
const
mk_mp4_info
ctx
);
//
MP4Info::strStreamId
//
虚拟主机
API_EXPORT
const
char
*
API_CALL
mk_mp4_info_get_stream
(
const
mk_mp4_info
ctx
);
///////////////////////////////////////////Parser/////////////////////////////////////////////
...
...
api/source/mk_events_objects.cpp
查看文件 @
f84981dc
...
...
@@ -22,61 +22,61 @@ using namespace mediakit;
API_EXPORT
uint64_t
API_CALL
mk_mp4_info_get_start_time
(
const
mk_mp4_info
ctx
){
assert
(
ctx
);
RecordInfo
*
info
=
(
RecordInfo
*
)
ctx
;
return
info
->
ui64StartedT
ime
;
return
info
->
start_t
ime
;
}
API_EXPORT
uint64_
t
API_CALL
mk_mp4_info_get_time_len
(
const
mk_mp4_info
ctx
){
API_EXPORT
floa
t
API_CALL
mk_mp4_info_get_time_len
(
const
mk_mp4_info
ctx
){
assert
(
ctx
);
RecordInfo
*
info
=
(
RecordInfo
*
)
ctx
;
return
info
->
ui64TimeL
en
;
return
info
->
time_l
en
;
}
API_EXPORT
uint64_t
API_CALL
mk_mp4_info_get_file_size
(
const
mk_mp4_info
ctx
){
assert
(
ctx
);
RecordInfo
*
info
=
(
RecordInfo
*
)
ctx
;
return
info
->
ui64FileS
ize
;
return
info
->
file_s
ize
;
}
API_EXPORT
const
char
*
API_CALL
mk_mp4_info_get_file_path
(
const
mk_mp4_info
ctx
){
assert
(
ctx
);
RecordInfo
*
info
=
(
RecordInfo
*
)
ctx
;
return
info
->
strFileP
ath
.
c_str
();
return
info
->
file_p
ath
.
c_str
();
}
API_EXPORT
const
char
*
API_CALL
mk_mp4_info_get_file_name
(
const
mk_mp4_info
ctx
){
assert
(
ctx
);
RecordInfo
*
info
=
(
RecordInfo
*
)
ctx
;
return
info
->
strFileN
ame
.
c_str
();
return
info
->
file_n
ame
.
c_str
();
}
API_EXPORT
const
char
*
API_CALL
mk_mp4_info_get_folder
(
const
mk_mp4_info
ctx
){
assert
(
ctx
);
RecordInfo
*
info
=
(
RecordInfo
*
)
ctx
;
return
info
->
strF
older
.
c_str
();
return
info
->
f
older
.
c_str
();
}
API_EXPORT
const
char
*
API_CALL
mk_mp4_info_get_url
(
const
mk_mp4_info
ctx
){
assert
(
ctx
);
RecordInfo
*
info
=
(
RecordInfo
*
)
ctx
;
return
info
->
strU
rl
.
c_str
();
return
info
->
u
rl
.
c_str
();
}
API_EXPORT
const
char
*
API_CALL
mk_mp4_info_get_vhost
(
const
mk_mp4_info
ctx
){
assert
(
ctx
);
RecordInfo
*
info
=
(
RecordInfo
*
)
ctx
;
return
info
->
strV
host
.
c_str
();
return
info
->
v
host
.
c_str
();
}
API_EXPORT
const
char
*
API_CALL
mk_mp4_info_get_app
(
const
mk_mp4_info
ctx
){
assert
(
ctx
);
RecordInfo
*
info
=
(
RecordInfo
*
)
ctx
;
return
info
->
strAppName
.
c_str
();
return
info
->
app
.
c_str
();
}
API_EXPORT
const
char
*
API_CALL
mk_mp4_info_get_stream
(
const
mk_mp4_info
ctx
){
assert
(
ctx
);
RecordInfo
*
info
=
(
RecordInfo
*
)
ctx
;
return
info
->
str
StreamId
.
c_str
();
return
info
->
str
eam
.
c_str
();
}
///////////////////////////////////////////Parser/////////////////////////////////////////////
...
...
server/WebHook.cpp
查看文件 @
f84981dc
...
...
@@ -342,25 +342,29 @@ void installWebHook(){
do_http_hook
(
hook_stream_not_found
,
body
,
nullptr
);
});
static
auto
getRecordInfo
=
[](
const
RecordInfo
&
info
)
{
ArgsType
body
;
body
[
"start_time"
]
=
(
Json
::
UInt64
)
info
.
start_time
;
body
[
"file_size"
]
=
(
Json
::
UInt64
)
info
.
file_size
;
body
[
"time_len"
]
=
info
.
time_len
;
body
[
"file_path"
]
=
info
.
file_path
;
body
[
"file_name"
]
=
info
.
file_name
;
body
[
"folder"
]
=
info
.
folder
;
body
[
"url"
]
=
info
.
url
;
body
[
"app"
]
=
info
.
app
;
body
[
"stream"
]
=
info
.
stream
;
body
[
"vhost"
]
=
info
.
vhost
;
return
body
;
};
#ifdef ENABLE_MP4
//录制mp4文件成功后广播
NoticeCenter
::
Instance
().
addListener
(
nullptr
,
Broadcast
::
kBroadcastRecordMP4
,[](
BroadcastRecordMP4Args
){
if
(
!
hook_enable
||
hook_record_mp4
.
empty
())
{
if
(
!
hook_enable
||
hook_record_mp4
.
empty
())
{
return
;
}
ArgsType
body
;
body
[
"start_time"
]
=
(
Json
::
UInt64
)
info
.
ui64StartedTime
;
body
[
"time_len"
]
=
(
Json
::
UInt64
)
info
.
ui64TimeLen
;
body
[
"file_size"
]
=
(
Json
::
UInt64
)
info
.
ui64FileSize
;
body
[
"file_path"
]
=
info
.
strFilePath
;
body
[
"file_name"
]
=
info
.
strFileName
;
body
[
"folder"
]
=
info
.
strFolder
;
body
[
"url"
]
=
info
.
strUrl
;
body
[
"app"
]
=
info
.
strAppName
;
body
[
"stream"
]
=
info
.
strStreamId
;
body
[
"vhost"
]
=
info
.
strVhost
;
//执行hook
do_http_hook
(
hook_record_mp4
,
body
,
nullptr
);
do_http_hook
(
hook_record_mp4
,
getRecordInfo
(
info
)
,
nullptr
);
});
#endif //ENABLE_MP4
...
...
@@ -368,19 +372,8 @@ void installWebHook(){
if
(
!
hook_enable
||
hook_record_ts
.
empty
())
{
return
;
}
ArgsType
body
;
body
[
"start_time"
]
=
(
Json
::
UInt64
)
info
.
ui64StartedTime
;
body
[
"time_len"
]
=
(
Json
::
UInt64
)
info
.
ui64TimeLen
;
body
[
"file_size"
]
=
(
Json
::
UInt64
)
info
.
ui64FileSize
;
body
[
"file_path"
]
=
info
.
strFilePath
;
body
[
"file_name"
]
=
info
.
strFileName
;
body
[
"folder"
]
=
info
.
strFolder
;
body
[
"url"
]
=
info
.
strUrl
;
body
[
"app"
]
=
info
.
strAppName
;
body
[
"stream"
]
=
info
.
strStreamId
;
body
[
"vhost"
]
=
info
.
strVhost
;
// 执行 hook
do_http_hook
(
hook_record_ts
,
body
,
nullptr
);
do_http_hook
(
hook_record_ts
,
getRecordInfo
(
info
)
,
nullptr
);
});
NoticeCenter
::
Instance
().
addListener
(
nullptr
,
Broadcast
::
kBroadcastShellLogin
,[](
BroadcastShellLoginArgs
){
...
...
@@ -421,7 +414,6 @@ void installWebHook(){
}
strongSrc
->
close
(
false
);
});
});
/**
...
...
src/Record/HlsMaker.cpp
查看文件 @
f84981dc
...
...
@@ -118,16 +118,12 @@ void HlsMaker::flushLastSegment(bool eof){
seg_dur
=
100
;
}
_seg_dur_list
.
push_back
(
std
::
make_tuple
(
seg_dur
,
std
::
move
(
_last_file_name
)));
_last_file_name
.
clear
();
delOldSegment
();
makeIndexFile
(
eof
);
_last_file_name
.
clear
();
onFlushLastSegment
(
seg_dur
);
}
void
HlsMaker
::
onFlushLastSegment
(
uint32_t
)
{
}
bool
HlsMaker
::
isLive
()
{
return
_seg_number
!=
0
;
}
...
...
src/Record/HlsMaker.h
查看文件 @
f84981dc
...
...
@@ -22,20 +22,6 @@ using namespace toolkit;
namespace
mediakit
{
class
TsInfo
{
public
:
time_t
ui64StartedTime
;
// GMT 标准时间,单位秒
time_t
ui64TimeLen
;
// 录像长度,单位毫秒
off_t
ui64FileSize
;
// 文件大小,单位 BYTE
string
strFilePath
;
// 文件路径
string
strFileName
;
// 文件名称
string
strFolder
;
// 文件夹路径
string
strUrl
;
// 播放路径
string
strAppName
;
// 应用名称
string
strStreamId
;
// 流 ID
string
strVhost
;
// vhost
};
class
HlsMaker
{
public
:
/**
...
...
@@ -93,16 +79,16 @@ protected:
virtual
void
onWriteHls
(
const
char
*
data
,
int
len
)
=
0
;
/**
*
关闭上个ts切片并且写入m3u8索引
* @param
eof HLS直播是否已结束
*
上一个 ts 切片写入完成, 可在这里进行通知处理
* @param
duration_ms 上一个 ts 切片的时长, 单位为毫秒
*/
v
oid
flushLastSegment
(
bool
eof
)
;
v
irtual
void
onFlushLastSegment
(
uint32_t
duration_ms
)
{}
;
/**
*
上一个 ts 切片写入完成, 可在这里进行通知处理
* @param
duration 上一个 ts 切片的时长, 单位为毫秒
*
关闭上个ts切片并且写入m3u8索引
* @param
eof HLS直播是否已结束
*/
v
irtual
void
onFlushLastSegment
(
uint32_t
duration
);
v
oid
flushLastSegment
(
bool
eof
);
private
:
/**
...
...
src/Record/HlsMakerImp.cpp
查看文件 @
f84981dc
...
...
@@ -11,7 +11,6 @@
#include <ctime>
#include <sys/stat.h>
#include "HlsMakerImp.h"
#include "Thread/WorkThreadPool.h"
#include "Util/util.h"
#include "Util/uv_errno.h"
...
...
@@ -32,7 +31,7 @@ HlsMakerImp::HlsMakerImp(const string &m3u8_file,
delete
[]
ptr
;
});
_info
.
strF
older
=
_path_prefix
;
_info
.
f
older
=
_path_prefix
;
}
HlsMakerImp
::~
HlsMakerImp
()
{
...
...
@@ -65,10 +64,11 @@ string HlsMakerImp::onOpenSegment(int index) {
}
_file
=
makeFile
(
segment_path
,
true
);
_info
.
ui64StartedTime
=
::
time
(
NULL
);
_info
.
strFileName
=
segment_name
;
_info
.
strFilePath
=
segment_path
;
_info
.
strUrl
=
_info
.
strAppName
+
"/"
+
_info
.
strStreamId
+
"/"
+
segment_name
;
//保存本切片的元数据
_info
.
start_time
=
::
time
(
NULL
);
_info
.
file_name
=
segment_name
;
_info
.
file_path
=
segment_path
;
_info
.
url
=
_info
.
app
+
"/"
+
_info
.
stream
+
"/"
+
segment_name
;
if
(
!
_file
)
{
WarnL
<<
"create file failed,"
<<
segment_path
<<
" "
<<
get_uv_errmsg
();
...
...
@@ -108,22 +108,19 @@ void HlsMakerImp::onWriteHls(const char *data, int len) {
//DebugL << "\r\n" << string(data,len);
}
void
HlsMakerImp
::
onFlushLastSegment
(
uint32_t
duration
)
{
void
HlsMakerImp
::
onFlushLastSegment
(
uint32_t
duration
_ms
)
{
GET_CONFIG
(
bool
,
broadcastRecordTs
,
Hls
::
kBroadcastRecordTs
);
if
(
broadcastRecordTs
)
{
auto
info
=
_info
;
info
.
ui64TimeLen
=
duration
;
WorkThreadPool
::
Instance
().
getExecutor
()
->
async
([
info
]()
{
struct
stat
fileData
;
stat
(
info
.
strFilePath
.
data
(),
&
fileData
);
const_cast
<
RecordInfo
&>
(
info
).
ui64FileSize
=
fileData
.
st_size
;
NoticeCenter
::
Instance
().
emitEvent
(
Broadcast
::
kBroadcastRecordTs
,
info
);
});
//关闭ts文件以便获取正确的文件大小
_file
=
nullptr
;
_info
.
time_len
=
duration_ms
/
1000.0
;
struct
stat
fileData
;
stat
(
_info
.
file_path
.
data
(),
&
fileData
);
_info
.
file_size
=
fileData
.
st_size
;
NoticeCenter
::
Instance
().
emitEvent
(
Broadcast
::
kBroadcastRecordTs
,
_info
);
}
}
std
::
shared_ptr
<
FILE
>
HlsMakerImp
::
makeFile
(
const
string
&
file
,
bool
setbuf
)
{
auto
file_buf
=
_file_buf
;
auto
ret
=
shared_ptr
<
FILE
>
(
File
::
create_file
(
file
.
data
(),
"wb"
),
[
file_buf
](
FILE
*
fp
)
{
...
...
@@ -139,9 +136,9 @@ std::shared_ptr<FILE> HlsMakerImp::makeFile(const string &file, bool setbuf) {
void
HlsMakerImp
::
setMediaSource
(
const
string
&
vhost
,
const
string
&
app
,
const
string
&
stream_id
)
{
_media_src
=
std
::
make_shared
<
HlsMediaSource
>
(
vhost
,
app
,
stream_id
);
_info
.
strAppName
=
app
;
_info
.
str
StreamId
=
stream_id
;
_info
.
strV
host
=
vhost
;
_info
.
app
=
app
;
_info
.
str
eam
=
stream_id
;
_info
.
v
host
=
vhost
;
}
HlsMediaSource
::
Ptr
HlsMakerImp
::
getMediaSource
()
const
{
...
...
src/Record/HlsMakerImp.h
查看文件 @
f84981dc
...
...
@@ -16,7 +16,6 @@
#include <stdlib.h>
#include "HlsMaker.h"
#include "HlsMediaSource.h"
#include "RecordInfo.h"
using
namespace
std
;
...
...
@@ -56,7 +55,7 @@ protected:
void
onDelSegment
(
int
index
)
override
;
void
onWriteSegment
(
const
char
*
data
,
int
len
)
override
;
void
onWriteHls
(
const
char
*
data
,
int
len
)
override
;
void
onFlushLastSegment
(
uint32_t
duration
)
override
;
void
onFlushLastSegment
(
uint32_t
duration
_ms
)
override
;
private
:
std
::
shared_ptr
<
FILE
>
makeFile
(
const
string
&
file
,
bool
setbuf
=
false
);
...
...
@@ -66,10 +65,10 @@ private:
string
_params
;
string
_path_hls
;
string
_path_prefix
;
RecordInfo
_info
;
std
::
shared_ptr
<
FILE
>
_file
;
std
::
shared_ptr
<
char
>
_file_buf
;
HlsMediaSource
::
Ptr
_media_src
;
RecordInfo
_info
;
map
<
int
/*index*/
,
string
/*file_path*/
>
_segment_file_paths
;
};
...
...
src/Record/MP4Recorder.cpp
查看文件 @
f84981dc
...
...
@@ -25,10 +25,10 @@ MP4Recorder::MP4Recorder(const string& strPath,
const
string
&
strStreamId
)
{
_strPath
=
strPath
;
/////record 业务逻辑//////
_info
.
strAppName
=
strApp
;
_info
.
str
StreamId
=
strStreamId
;
_info
.
strV
host
=
strVhost
;
_info
.
strF
older
=
strPath
;
_info
.
app
=
strApp
;
_info
.
str
eam
=
strStreamId
;
_info
.
v
host
=
strVhost
;
_info
.
f
older
=
strPath
;
}
MP4Recorder
::~
MP4Recorder
()
{
closeFile
();
...
...
@@ -42,15 +42,15 @@ void MP4Recorder::createFile() {
auto
strFile
=
_strPath
+
strDate
+
"/"
+
strTime
+
".mp4"
;
/////record 业务逻辑//////
_info
.
ui64StartedT
ime
=
::
time
(
NULL
);
_info
.
strFileN
ame
=
strTime
+
".mp4"
;
_info
.
strFileP
ath
=
strFile
;
_info
.
start_t
ime
=
::
time
(
NULL
);
_info
.
file_n
ame
=
strTime
+
".mp4"
;
_info
.
file_p
ath
=
strFile
;
GET_CONFIG
(
string
,
appName
,
Record
::
kAppName
);
_info
.
strU
rl
=
appName
+
"/"
+
_info
.
strAppName
+
"/"
+
_info
.
strStreamId
+
"/"
+
strDate
+
"/"
+
strTime
+
".mp4"
;
_info
.
u
rl
=
appName
+
"/"
+
_info
.
app
+
"/"
+
_info
.
stream
+
"/"
+
strDate
+
"/"
+
strTime
+
".mp4"
;
try
{
_muxer
=
std
::
make_shared
<
MP4Muxer
>
(
strFileTmp
.
data
());
...
...
@@ -73,7 +73,7 @@ void MP4Recorder::asyncClose() {
auto
info
=
_info
;
WorkThreadPool
::
Instance
().
getExecutor
()
->
async
([
muxer
,
strFileTmp
,
strFile
,
info
]()
{
//获取文件录制时间,放在关闭mp4之前是为了忽略关闭mp4执行时间
const_cast
<
RecordInfo
&>
(
info
).
ui64TimeLen
=
::
time
(
NULL
)
-
info
.
ui64StartedT
ime
;
const_cast
<
RecordInfo
&>
(
info
).
time_len
=
::
time
(
NULL
)
-
info
.
start_t
ime
;
//关闭mp4非常耗时,所以要放在后台线程执行
muxer
->
closeMP4
();
//临时文件名改成正式文件名,防止mp4未完成时被访问
...
...
@@ -81,7 +81,7 @@ void MP4Recorder::asyncClose() {
//获取文件大小
struct
stat
fileData
;
stat
(
strFile
.
data
(),
&
fileData
);
const_cast
<
RecordInfo
&>
(
info
).
ui64FileS
ize
=
fileData
.
st_size
;
const_cast
<
RecordInfo
&>
(
info
).
file_s
ize
=
fileData
.
st_size
;
/////record 业务逻辑//////
NoticeCenter
::
Instance
().
emitEvent
(
Broadcast
::
kBroadcastRecordMP4
,
info
);
});
...
...
src/Record/MP4Recorder.h
查看文件 @
f84981dc
...
...
@@ -20,7 +20,6 @@
#include "Util/TimeTicker.h"
#include "Common/MediaSink.h"
#include "MP4Muxer.h"
#include "RecordInfo.h"
using
namespace
toolkit
;
...
...
src/Record/RecordInfo.h
deleted
100644 → 0
查看文件 @
0fce108d
/*
* Copyright (c) 2020 The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit).
*
* Use of this source code is governed by MIT license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef RECORDINFO_H_
#define RECORDINFO_H_
#include "Common/config.h"
namespace
mediakit
{
class
RecordInfo
{
public
:
time_t
ui64StartedTime
;
// GMT 标准时间,单位秒
time_t
ui64TimeLen
;
// 录像长度,需要注意 mp4 单位是秒,而 hls ts 单位是毫秒
off_t
ui64FileSize
;
// 文件大小,单位 BYTE
string
strFilePath
;
// 文件路径
string
strFileName
;
// 文件名称
string
strFolder
;
// 文件夹路径
string
strUrl
;
// 播放路径
string
strAppName
;
// 应用名称
string
strStreamId
;
// 流 ID
string
strVhost
;
// vhost
};
}
// namespace mediakit
#endif // RECORDINFO_H_
src/Record/Recorder.h
查看文件 @
f84981dc
...
...
@@ -16,6 +16,20 @@ using namespace std;
namespace
mediakit
{
class
MediaSinkInterface
;
class
RecordInfo
{
public
:
time_t
start_time
;
// GMT 标准时间,单位秒
float
time_len
;
// 录像长度,单位秒
off_t
file_size
;
// 文件大小,单位 BYTE
string
file_path
;
// 文件路径
string
file_name
;
// 文件名称
string
folder
;
// 文件夹路径
string
url
;
// 播放路径
string
app
;
// 应用名称
string
stream
;
// 流 ID
string
vhost
;
// 虚拟主机
};
class
Recorder
{
public
:
typedef
enum
{
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论