Skip to content
项目
群组
代码片段
帮助
正在加载...
登录
切换导航
F
freeswitch
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
分枝图
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
分枝图
统计图
创建新议题
作业
提交
议题看板
打开侧边栏
张华
freeswitch
Commits
d2fb03ed
提交
d2fb03ed
authored
8月 20, 2013
作者:
Chris Rienzo
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
mod_rayo: use XMPP URI instead of FS UUID for join/unjoin
上级
c695f232
隐藏空白字符变更
内嵌
并排
正在显示
1 个修改的文件
包含
67 行增加
和
47 行删除
+67
-47
mod_rayo.c
src/mod/event_handlers/mod_rayo/mod_rayo.c
+67
-47
没有找到文件。
src/mod/event_handlers/mod_rayo/mod_rayo.c
浏览文件 @
d2fb03ed
...
...
@@ -718,6 +718,9 @@ struct rayo_actor *rayo_actor_locate(const char *jid, const char *file, int line
{
struct
rayo_actor
*
actor
=
NULL
;
switch_mutex_lock
(
globals
.
actors_mutex
);
if
(
!
strncmp
(
"xmpp:"
,
jid
,
5
))
{
jid
=
jid
+
5
;
}
actor
=
(
struct
rayo_actor
*
)
switch_core_hash_find
(
globals
.
actors
,
jid
);
if
(
actor
)
{
if
(
!
actor
->
destroy
)
{
...
...
@@ -824,13 +827,30 @@ int rayo_actor_seq_next(struct rayo_actor *actor)
return
seq
;
}
#define RAYO_CALL_LOCATE(call_uuid) rayo_call_locate(call_uuid, __FILE__, __LINE__)
#define RAYO_CALL_LOCATE(call_uri) rayo_call_locate(call_uri, __FILE__, __LINE__)
/**
* Get access to Rayo call data. Use to access call data outside channel thread.
* @param call_uri the Rayo XMPP URI
* @return the call or NULL.
*/
static
struct
rayo_call
*
rayo_call_locate
(
const
char
*
call_uri
,
const
char
*
file
,
int
line
)
{
struct
rayo_actor
*
actor
=
rayo_actor_locate
(
call_uri
,
file
,
line
);
if
(
actor
&&
is_call_actor
(
actor
))
{
return
RAYO_CALL
(
actor
);
}
else
if
(
actor
)
{
RAYO_UNLOCK
(
actor
);
}
return
NULL
;
}
#define RAYO_CALL_LOCATE_BY_ID(call_uuid) rayo_call_locate_by_id(call_uuid, __FILE__, __LINE__)
/**
* Get
exclusive
access to Rayo call data. Use to access call data outside channel thread.
* Get access to Rayo call data. Use to access call data outside channel thread.
* @param call_uuid the FreeSWITCH call UUID
* @return the call or NULL.
*/
static
struct
rayo_call
*
rayo_call_locate
(
const
char
*
call_uuid
,
const
char
*
file
,
int
line
)
static
struct
rayo_call
*
rayo_call_locate
_by_id
(
const
char
*
call_uuid
,
const
char
*
file
,
int
line
)
{
struct
rayo_actor
*
actor
=
rayo_actor_locate_by_id
(
call_uuid
,
file
,
line
);
if
(
actor
&&
is_call_actor
(
actor
))
{
...
...
@@ -1692,18 +1712,18 @@ static iks *on_rayo_hangup(struct rayo_actor *call, struct rayo_message *msg, vo
* @param call the call that joins
* @param session the session
* @param node the join request
* @param call_
id
to join
* @param call_
uri
to join
* @param media mode (direct/bridge)
* @return the response
*/
static
iks
*
join_call
(
struct
rayo_call
*
call
,
switch_core_session_t
*
session
,
iks
*
node
,
const
char
*
call_
id
,
const
char
*
media
)
static
iks
*
join_call
(
struct
rayo_call
*
call
,
switch_core_session_t
*
session
,
iks
*
node
,
const
char
*
call_
uri
,
const
char
*
media
)
{
iks
*
response
=
NULL
;
/* take call out of media path if media = "direct" */
const
char
*
bypass
=
!
strcmp
(
"direct"
,
media
)
?
"true"
:
"false"
;
/* check if joining to rayo call */
struct
rayo_call
*
b_call
=
RAYO_CALL_LOCATE
(
call_
id
);
struct
rayo_call
*
b_call
=
RAYO_CALL_LOCATE
(
call_
uri
);
if
(
!
b_call
)
{
/* not a rayo call */
response
=
iks_new_error_detailed
(
node
,
STANZA_ERROR_SERVICE_UNAVAILABLE
,
"b-leg is not a rayo call"
);
...
...
@@ -1712,18 +1732,17 @@ static iks *join_call(struct rayo_call *call, switch_core_session_t *session, ik
response
=
iks_new_error_detailed
(
node
,
STANZA_ERROR_CONFLICT
,
"multiple joined calls not supported"
);
RAYO_UNLOCK
(
b_call
);
}
else
{
RAYO_UNLOCK
(
b_call
);
/* bridge this call to call-uri */
switch_channel_set_variable
(
switch_core_session_get_channel
(
session
),
"bypass_media"
,
bypass
);
if
(
switch_false
(
bypass
))
{
switch_channel_pre_answer
(
switch_core_session_get_channel
(
session
));
}
if
(
switch_ivr_uuid_bridge
(
rayo_call_get_uuid
(
call
),
call_id
)
==
SWITCH_STATUS_SUCCESS
)
{
if
(
switch_ivr_uuid_bridge
(
rayo_call_get_uuid
(
call
),
rayo_call_get_uuid
(
b_call
)
)
==
SWITCH_STATUS_SUCCESS
)
{
response
=
iks_new_iq_result
(
node
);
}
else
{
response
=
iks_new_error_detailed
(
node
,
STANZA_ERROR_INTERNAL_SERVER_ERROR
,
"failed to bridge call"
);
}
RAYO_UNLOCK
(
b_call
);
}
return
response
;
}
...
...
@@ -1797,7 +1816,7 @@ static iks *on_rayo_join(struct rayo_actor *call, struct rayo_message *msg, void
iks
*
join
=
iks_find
(
node
,
"join"
);
const
char
*
join_id
;
const
char
*
mixer_name
;
const
char
*
call_
id
;
const
char
*
call_
uri
;
/* validate input attributes */
if
(
!
VALIDATE_RAYO_JOIN
(
join
))
{
...
...
@@ -1806,22 +1825,22 @@ static iks *on_rayo_join(struct rayo_actor *call, struct rayo_message *msg, void
goto
done
;
}
mixer_name
=
iks_find_attrib
(
join
,
"mixer-name"
);
call_
id
=
iks_find_attrib
(
join
,
"call-uri"
);
call_
uri
=
iks_find_attrib
(
join
,
"call-uri"
);
if
(
!
zstr
(
mixer_name
))
{
join_id
=
mixer_name
;
}
else
{
join_id
=
call_
id
;
join_id
=
call_
uri
;
}
/* can't join both mixer and call */
if
(
!
zstr
(
mixer_name
)
&&
!
zstr
(
call_
id
))
{
if
(
!
zstr
(
mixer_name
)
&&
!
zstr
(
call_
uri
))
{
response
=
iks_new_error_detailed
(
node
,
STANZA_ERROR_BAD_REQUEST
,
"mixer-name and call-uri are mutually exclusive"
);
goto
done
;
}
/* need to join *something* */
if
(
zstr
(
mixer_name
)
&&
zstr
(
call_
id
))
{
if
(
zstr
(
mixer_name
)
&&
zstr
(
call_
uri
))
{
response
=
iks_new_error_detailed
(
node
,
STANZA_ERROR_BAD_REQUEST
,
"mixer-name or call-uri is required"
);
goto
done
;
}
...
...
@@ -1838,7 +1857,7 @@ static iks *on_rayo_join(struct rayo_actor *call, struct rayo_message *msg, void
response
=
join_mixer
(
RAYO_CALL
(
call
),
session
,
node
,
mixer_name
,
iks_find_attrib
(
join
,
"direction"
));
}
else
{
/* bridge calls */
response
=
join_call
(
RAYO_CALL
(
call
),
session
,
node
,
call_
id
,
iks_find_attrib
(
join
,
"media"
));
response
=
join_call
(
RAYO_CALL
(
call
),
session
,
node
,
call_
uri
,
iks_find_attrib
(
join
,
"media"
));
}
done:
...
...
@@ -1850,21 +1869,22 @@ done:
* @param call the call that unjoined
* @param session the session
* @param node the unjoin request
* @param call_
id the b-leg uuid
* @param call_
uri the b-leg xmpp URI
* @return the response
*/
static
iks
*
unjoin_call
(
struct
rayo_call
*
call
,
switch_core_session_t
*
session
,
iks
*
node
,
const
char
*
call_
id
)
static
iks
*
unjoin_call
(
struct
rayo_call
*
call
,
switch_core_session_t
*
session
,
iks
*
node
,
const
char
*
call_
uri
)
{
iks
*
response
=
NULL
;
const
char
*
bleg
=
switch_channel_get_variable
(
switch_core_session_get_channel
(
session
),
SWITCH_BRIDGE_UUID_VARIABLE
);
const
char
*
bleg_uuid
=
switch_channel_get_variable
(
switch_core_session_get_channel
(
session
),
SWITCH_BRIDGE_UUID_VARIABLE
);
const
char
*
bleg_uri
=
switch_core_session_sprintf
(
session
,
"xmpp:%s@%s"
,
bleg_uuid
?
bleg_uuid
:
""
,
RAYO_JID
(
globals
.
server
));
/* bleg must match call_
id
*/
if
(
!
zstr
(
bleg
)
&&
!
strcmp
(
bleg
,
call_id
))
{
/* bleg must match call_
uri
*/
if
(
!
zstr
(
bleg
_uri
)
&&
!
strcmp
(
bleg_uri
,
call_uri
))
{
/* unbridge call */
response
=
iks_new_iq_result
(
node
);
switch_ivr_park_session
(
session
);
}
else
{
/* not bridged or wrong b-leg U
UID
*/
/* not bridged or wrong b-leg U
RI
*/
response
=
iks_new_error
(
node
,
STANZA_ERROR_SERVICE_UNAVAILABLE
);
}
...
...
@@ -1919,16 +1939,16 @@ static iks *on_rayo_unjoin(struct rayo_actor *call, struct rayo_message *msg, vo
switch_core_session_t
*
session
=
(
switch_core_session_t
*
)
session_data
;
iks
*
response
=
NULL
;
iks
*
unjoin
=
iks_find
(
node
,
"unjoin"
);
const
char
*
call_
id
=
iks_find_attrib
(
unjoin
,
"call-uri"
);
const
char
*
call_
uri
=
iks_find_attrib
(
unjoin
,
"call-uri"
);
const
char
*
mixer_name
=
iks_find_attrib
(
unjoin
,
"mixer-name"
);
if
(
!
zstr
(
call_
id
)
&&
!
zstr
(
mixer_name
))
{
if
(
!
zstr
(
call_
uri
)
&&
!
zstr
(
mixer_name
))
{
response
=
iks_new_error
(
node
,
STANZA_ERROR_BAD_REQUEST
);
}
else
if
(
!
RAYO_CALL
(
call
)
->
joined
)
{
/* not joined to anything */
response
=
iks_new_error
(
node
,
STANZA_ERROR_SERVICE_UNAVAILABLE
);
}
else
if
(
!
zstr
(
call_
id
))
{
response
=
unjoin_call
(
RAYO_CALL
(
call
),
session
,
node
,
call_
id
);
}
else
if
(
!
zstr
(
call_
uri
))
{
response
=
unjoin_call
(
RAYO_CALL
(
call
),
session
,
node
,
call_
uri
);
}
else
if
(
!
zstr
(
mixer_name
))
{
response
=
unjoin_mixer
(
RAYO_CALL
(
call
),
session
,
node
,
mixer_name
);
}
else
{
...
...
@@ -2043,20 +2063,20 @@ static void *SWITCH_THREAD_FUNC rayo_dial_thread(switch_thread_t *thread, void *
if
(
join
)
{
/* check join args */
const
char
*
call_
id
=
iks_find_attrib
(
join
,
"call-uri"
);
const
char
*
call_
uri
=
iks_find_attrib
(
join
,
"call-uri"
);
const
char
*
mixer_name
=
iks_find_attrib
(
join
,
"mixer-name"
);
if
(
!
zstr
(
call_
id
)
&&
!
zstr
(
mixer_name
))
{
if
(
!
zstr
(
call_
uri
)
&&
!
zstr
(
mixer_name
))
{
/* can't join both */
response
=
iks_new_error
(
iq
,
STANZA_ERROR_BAD_REQUEST
);
goto
done
;
}
else
if
(
zstr
(
call_
id
)
&&
zstr
(
mixer_name
))
{
}
else
if
(
zstr
(
call_
uri
)
&&
zstr
(
mixer_name
))
{
/* nobody to join to? */
response
=
iks_new_error
(
iq
,
STANZA_ERROR_BAD_REQUEST
);
goto
done
;
}
else
if
(
!
zstr
(
call_
id
))
{
}
else
if
(
!
zstr
(
call_
uri
))
{
/* bridge */
struct
rayo_call
*
b_call
=
RAYO_CALL_LOCATE
(
call_
id
);
struct
rayo_call
*
b_call
=
RAYO_CALL_LOCATE
(
call_
uri
);
/* is b-leg available? */
if
(
!
b_call
)
{
response
=
iks_new_error_detailed
(
iq
,
STANZA_ERROR_SERVICE_UNAVAILABLE
,
"b-leg not found"
);
...
...
@@ -2066,8 +2086,8 @@ static void *SWITCH_THREAD_FUNC rayo_dial_thread(switch_thread_t *thread, void *
RAYO_UNLOCK
(
b_call
);
goto
done
;
}
stream
.
write_function
(
&
stream
,
"%s%s &rayo(bridge %s)"
,
gateway
->
dial_prefix
,
dial_to_stripped
,
rayo_call_get_uuid
(
b_call
));
RAYO_UNLOCK
(
b_call
);
stream
.
write_function
(
&
stream
,
"%s%s &rayo(bridge %s)"
,
gateway
->
dial_prefix
,
dial_to_stripped
,
call_id
);
}
else
{
/* conference */
stream
.
write_function
(
&
stream
,
"%s%s &rayo(conference %s@%s)"
,
gateway
->
dial_prefix
,
dial_to_stripped
,
mixer_name
,
globals
.
mixer_conf_profile
);
...
...
@@ -2399,7 +2419,7 @@ static void on_mixer_delete_member_event(struct rayo_mixer *mixer, switch_event_
switch_core_hash_delete
(
mixer
->
members
,
uuid
);
/* flag call as available to join another mixer */
call
=
RAYO_CALL_LOCATE
(
uuid
);
call
=
RAYO_CALL_LOCATE
_BY_ID
(
uuid
);
if
(
call
)
{
call
->
joined
=
0
;
call
->
joined_id
=
NULL
;
...
...
@@ -2415,7 +2435,7 @@ static void on_mixer_delete_member_event(struct rayo_mixer *mixer, switch_event_
/* broadcast member unjoined event to subscribers */
delete_member_event
=
iks_new_presence
(
"unjoined"
,
RAYO_NS
,
RAYO_JID
(
mixer
),
""
);
x
=
iks_find
(
delete_member_event
,
"unjoined"
);
iks_insert_attrib
(
x
,
"call-uri"
,
uuid
);
iks_insert_attrib
_printf
(
x
,
"call-uri"
,
"xmpp:%s@%s"
,
uuid
,
RAYO_JID
(
globals
.
server
)
);
broadcast_mixer_event
(
mixer
,
delete_member_event
);
iks_delete
(
delete_member_event
);
...
...
@@ -2451,7 +2471,7 @@ static void on_mixer_add_member_event(struct rayo_mixer *mixer, switch_event_t *
{
iks
*
add_member_event
=
NULL
,
*
x
;
const
char
*
uuid
=
switch_event_get_header
(
event
,
"Unique-ID"
);
struct
rayo_call
*
call
=
RAYO_CALL_LOCATE
(
uuid
);
struct
rayo_call
*
call
=
RAYO_CALL_LOCATE
_BY_ID
(
uuid
);
if
(
!
mixer
)
{
/* new mixer */
...
...
@@ -2493,7 +2513,7 @@ static void on_mixer_add_member_event(struct rayo_mixer *mixer, switch_event_t *
/* broadcast member joined event to subscribers */
add_member_event
=
iks_new_presence
(
"joined"
,
RAYO_NS
,
RAYO_JID
(
mixer
),
""
);
x
=
iks_find
(
add_member_event
,
"joined"
);
iks_insert_attrib
(
x
,
"call-uri"
,
uuid
);
iks_insert_attrib
_printf
(
x
,
"call-uri"
,
"xmpp:%s@%s"
,
uuid
,
RAYO_JID
(
globals
.
server
)
);
broadcast_mixer_event
(
mixer
,
add_member_event
);
iks_delete
(
add_member_event
);
}
...
...
@@ -2539,7 +2559,7 @@ static void on_call_originate_event(struct rayo_client *rclient, switch_event_t
{
switch_core_session_t
*
session
=
NULL
;
const
char
*
uuid
=
switch_event_get_header
(
event
,
"Unique-ID"
);
struct
rayo_call
*
call
=
RAYO_CALL_LOCATE
(
uuid
);
struct
rayo_call
*
call
=
RAYO_CALL_LOCATE
_BY_ID
(
uuid
);
if
(
call
&&
(
session
=
switch_core_session_locate
(
uuid
)))
{
iks
*
response
,
*
ref
;
...
...
@@ -2569,7 +2589,7 @@ static void on_call_originate_event(struct rayo_client *rclient, switch_event_t
*/
static
void
on_call_end_event
(
switch_event_t
*
event
)
{
struct
rayo_call
*
call
=
RAYO_CALL_LOCATE
(
switch_event_get_header
(
event
,
"Unique-ID"
));
struct
rayo_call
*
call
=
RAYO_CALL_LOCATE
_BY_ID
(
switch_event_get_header
(
event
,
"Unique-ID"
));
if
(
call
)
{
#if 0
...
...
@@ -2593,7 +2613,7 @@ static void on_call_end_event(switch_event_t *event)
*/
static
void
on_call_answer_event
(
struct
rayo_client
*
rclient
,
switch_event_t
*
event
)
{
struct
rayo_call
*
call
=
RAYO_CALL_LOCATE
(
switch_event_get_header
(
event
,
"Unique-ID"
));
struct
rayo_call
*
call
=
RAYO_CALL_LOCATE
_BY_ID
(
switch_event_get_header
(
event
,
"Unique-ID"
));
if
(
call
)
{
iks
*
revent
=
iks_new_presence
(
"answered"
,
RAYO_NS
,
switch_event_get_header
(
event
,
"variable_rayo_call_jid"
),
...
...
@@ -2612,7 +2632,7 @@ static void on_call_ringing_event(struct rayo_client *rclient, switch_event_t *e
{
const
char
*
call_direction
=
switch_event_get_header
(
event
,
"Call-Direction"
);
if
(
call_direction
&&
!
strcmp
(
call_direction
,
"outbound"
))
{
struct
rayo_call
*
call
=
RAYO_CALL_LOCATE
(
switch_event_get_header
(
event
,
"Unique-ID"
));
struct
rayo_call
*
call
=
RAYO_CALL_LOCATE
_BY_ID
(
switch_event_get_header
(
event
,
"Unique-ID"
));
if
(
call
)
{
switch_mutex_lock
(
RAYO_ACTOR
(
call
)
->
mutex
);
if
(
!
call
->
ringing_sent
)
{
...
...
@@ -2637,7 +2657,7 @@ static void on_call_bridge_event(struct rayo_client *rclient, switch_event_t *ev
{
const
char
*
a_uuid
=
switch_event_get_header
(
event
,
"Unique-ID"
);
const
char
*
b_uuid
=
switch_event_get_header
(
event
,
"Bridge-B-Unique-ID"
);
struct
rayo_call
*
call
=
RAYO_CALL_LOCATE
(
a_uuid
);
struct
rayo_call
*
call
=
RAYO_CALL_LOCATE
_BY_ID
(
a_uuid
);
struct
rayo_call
*
b_call
;
if
(
call
)
{
...
...
@@ -2646,7 +2666,7 @@ static void on_call_bridge_event(struct rayo_client *rclient, switch_event_t *ev
switch_event_get_header
(
event
,
"variable_rayo_call_jid"
),
switch_event_get_header
(
event
,
"variable_rayo_dcp_jid"
));
iks
*
joined
=
iks_find
(
revent
,
"joined"
);
iks_insert_attrib
(
joined
,
"call-uri"
,
b_uuid
);
iks_insert_attrib
_printf
(
joined
,
"call-uri"
,
"xmpp:%s@%s"
,
b_uuid
,
RAYO_JID
(
globals
.
server
)
);
call
->
joined
=
JOINED_CALL
;
call
->
joined_id
=
switch_core_strdup
(
RAYO_POOL
(
call
),
b_uuid
);
...
...
@@ -2654,11 +2674,11 @@ static void on_call_bridge_event(struct rayo_client *rclient, switch_event_t *ev
RAYO_SEND_MESSAGE
(
call
,
RAYO_JID
(
rclient
),
revent
);
/* send B-leg event */
b_call
=
RAYO_CALL_LOCATE
(
b_uuid
);
b_call
=
RAYO_CALL_LOCATE
_BY_ID
(
b_uuid
);
if
(
b_call
)
{
revent
=
iks_new_presence
(
"joined"
,
RAYO_NS
,
RAYO_JID
(
b_call
),
rayo_call_get_dcp_jid
(
b_call
));
joined
=
iks_find
(
revent
,
"joined"
);
iks_insert_attrib
(
joined
,
"call-uri"
,
a_uuid
);
iks_insert_attrib
_printf
(
joined
,
"call-uri"
,
"xmpp:%s@%s"
,
a_uuid
,
RAYO_JID
(
globals
.
server
)
);
b_call
->
joined
=
JOINED_CALL
;
b_call
->
joined_id
=
switch_core_strdup
(
RAYO_POOL
(
b_call
),
a_uuid
);
...
...
@@ -2679,7 +2699,7 @@ static void on_call_unbridge_event(struct rayo_client *rclient, switch_event_t *
{
const
char
*
a_uuid
=
switch_event_get_header
(
event
,
"Unique-ID"
);
const
char
*
b_uuid
=
switch_event_get_header
(
event
,
"Bridge-B-Unique-ID"
);
struct
rayo_call
*
call
=
RAYO_CALL_LOCATE
(
a_uuid
);
struct
rayo_call
*
call
=
RAYO_CALL_LOCATE
_BY_ID
(
a_uuid
);
struct
rayo_call
*
b_call
;
if
(
call
)
{
...
...
@@ -2688,18 +2708,18 @@ static void on_call_unbridge_event(struct rayo_client *rclient, switch_event_t *
switch_event_get_header
(
event
,
"variable_rayo_call_jid"
),
switch_event_get_header
(
event
,
"variable_rayo_dcp_jid"
));
iks
*
joined
=
iks_find
(
revent
,
"unjoined"
);
iks_insert_attrib
(
joined
,
"call-uri"
,
b_uuid
);
iks_insert_attrib
_printf
(
joined
,
"call-uri"
,
"xmpp:%s@%s"
,
b_uuid
,
RAYO_JID
(
globals
.
server
)
);
RAYO_SEND_MESSAGE
(
call
,
RAYO_JID
(
rclient
),
revent
);
call
->
joined
=
0
;
call
->
joined_id
=
NULL
;
/* send B-leg event */
b_call
=
RAYO_CALL_LOCATE
(
b_uuid
);
b_call
=
RAYO_CALL_LOCATE
_BY_ID
(
b_uuid
);
if
(
b_call
)
{
revent
=
iks_new_presence
(
"unjoined"
,
RAYO_NS
,
RAYO_JID
(
b_call
),
rayo_call_get_dcp_jid
(
b_call
));
joined
=
iks_find
(
revent
,
"unjoined"
);
iks_insert_attrib
(
joined
,
"call-uri"
,
a_uuid
);
iks_insert_attrib
_printf
(
joined
,
"call-uri"
,
"xmpp:%s@%s"
,
a_uuid
,
RAYO_JID
(
globals
.
server
)
);
RAYO_SEND_MESSAGE
(
b_call
,
rayo_call_get_dcp_jid
(
b_call
),
revent
);
b_call
->
joined
=
0
;
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论