记得上下班打卡 | git大法好,push需谨慎
Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
L
liquidnet-bus-v1
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
董敬伟
liquidnet-bus-v1
Commits
a7321f5f
Commit
a7321f5f
authored
Jun 04, 2021
by
张国柄
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
登录TOKEN REDIS存储校验调整;
parent
854b0a3c
Changes
4
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
73 additions
and
84 deletions
+73
-84
AdamLoginController.java
...iquidnet/service/adam/controller/AdamLoginController.java
+29
-46
AdamRdmServiceImpl.java
...quidnet/service/adam/service/impl/AdamRdmServiceImpl.java
+1
-1
KylinStationController.java
...dnet/service/kylin/controller/KylinStationController.java
+10
-15
GlobalAuthFilter.java
...a/com/liquidnet/support/zuul/filter/GlobalAuthFilter.java
+33
-22
No files found.
liquidnet-bus-service/liquidnet-service-adam/liquidnet-service-adam-impl/src/main/java/com/liquidnet/service/adam/controller/AdamLoginController.java
View file @
a7321f5f
...
@@ -29,7 +29,6 @@ import org.springframework.util.DigestUtils;
...
@@ -29,7 +29,6 @@ import org.springframework.util.DigestUtils;
import
org.springframework.util.LinkedMultiValueMap
;
import
org.springframework.util.LinkedMultiValueMap
;
import
org.springframework.web.bind.annotation.*
;
import
org.springframework.web.bind.annotation.*
;
import
javax.servlet.http.HttpServletRequest
;
import
java.nio.charset.StandardCharsets
;
import
java.nio.charset.StandardCharsets
;
import
java.util.Arrays
;
import
java.util.Arrays
;
import
java.util.HashMap
;
import
java.util.HashMap
;
...
@@ -43,12 +42,12 @@ import java.util.Objects;
...
@@ -43,12 +42,12 @@ import java.util.Objects;
@RequestMapping
(
""
)
@RequestMapping
(
""
)
public
class
AdamLoginController
{
public
class
AdamLoginController
{
@Autowired
@Autowired
Environment
environment
;
Environment
env
;
@Autowired
JwtValidator
jwtValidator
;
@Autowired
@Autowired
RedisUtil
redisUtil
;
RedisUtil
redisUtil
;
@Autowired
@Autowired
JwtValidator
jwtValidator
;
@Autowired
DefaultAcsClient
defaultAcsClient
;
DefaultAcsClient
defaultAcsClient
;
@Autowired
@Autowired
IAdamUserService
adamUserService
;
IAdamUserService
adamUserService
;
...
@@ -130,13 +129,7 @@ public class AdamLoginController {
...
@@ -130,13 +129,7 @@ public class AdamLoginController {
loginInfoVo
.
setMemberSimpleVo
(
adamRdmService
.
getMemberSimpleVo
());
loginInfoVo
.
setMemberSimpleVo
(
adamRdmService
.
getMemberSimpleVo
());
// }
// }
loginInfoVo
.
setUserInfo
(
userInfoVo
);
loginInfoVo
.
setUserInfo
(
userInfoVo
);
loginInfoVo
.
setToken
(
this
.
ssoProcess
(
userInfoVo
));
Map
<
String
,
Object
>
claimsMap
=
new
HashMap
<>();
claimsMap
.
put
(
"sub"
,
userInfoVo
.
getUid
());
// TODO: 2021/5/25 修改手机号更新TOKEN
claimsMap
.
put
(
"mobile"
,
userInfoVo
.
getMobile
());
claimsMap
.
put
(
"nickname"
,
userInfoVo
.
getNickname
());
loginInfoVo
.
setToken
(
this
.
ssoProcess
(
claimsMap
));
return
ResponseDto
.
success
(
loginInfoVo
);
return
ResponseDto
.
success
(
loginInfoVo
);
}
}
...
@@ -164,12 +157,7 @@ public class AdamLoginController {
...
@@ -164,12 +157,7 @@ public class AdamLoginController {
loginInfoVo
.
setMemberSimpleVo
(
adamRdmService
.
getMemberSimpleVo
());
loginInfoVo
.
setMemberSimpleVo
(
adamRdmService
.
getMemberSimpleVo
());
// }
// }
loginInfoVo
.
setUserInfo
(
userInfoVo
);
loginInfoVo
.
setUserInfo
(
userInfoVo
);
loginInfoVo
.
setToken
(
this
.
ssoProcess
(
userInfoVo
));
Map
<
String
,
Object
>
claimsMap
=
new
HashMap
<>();
claimsMap
.
put
(
"sub"
,
userInfoVo
.
getUid
());
claimsMap
.
put
(
"mobile"
,
userInfoVo
.
getMobile
());
claimsMap
.
put
(
"nickname"
,
userInfoVo
.
getNickname
());
loginInfoVo
.
setToken
(
this
.
ssoProcess
(
claimsMap
));
return
ResponseDto
.
success
(
AdamLoginInfoVo
.
getNew
());
return
ResponseDto
.
success
(
AdamLoginInfoVo
.
getNew
());
}
}
...
@@ -191,51 +179,44 @@ public class AdamLoginController {
...
@@ -191,51 +179,44 @@ public class AdamLoginController {
loginInfoVo
.
setUserMemberVo
(
adamRdmService
.
getUserMemberVoByUid
(
uid
));
loginInfoVo
.
setUserMemberVo
(
adamRdmService
.
getUserMemberVoByUid
(
uid
));
loginInfoVo
.
setMemberSimpleVo
(
adamRdmService
.
getMemberSimpleVo
());
loginInfoVo
.
setMemberSimpleVo
(
adamRdmService
.
getMemberSimpleVo
());
}
else
{
// 新账号注册
}
else
{
// 新账号注册
if
(!
this
.
checkSmsCode
(
parameter
.
getMobile
(),
parameter
.
getCode
()))
return
ResponseDto
.
failure
(
ErrorMapping
.
get
(
"10002"
));
if
(!
this
.
checkSmsCode
(
parameter
.
getMobile
(),
parameter
.
getCode
()))
return
ResponseDto
.
failure
(
ErrorMapping
.
get
(
"10002"
));
AdamUserInfoVo
registerUserInfo
=
adamUserService
.
register
(
parameter
);
AdamUserInfoVo
registerUserInfo
=
adamUserService
.
register
(
parameter
);
loginInfoVo
.
setUserInfo
(
registerUserInfo
);
loginInfoVo
.
setUserInfo
(
registerUserInfo
);
loginInfoVo
.
setThirdPartInfo
(
adamRdmService
.
getThirdPartVoListByUid
(
registerUserInfo
.
getUid
()));
loginInfoVo
.
setThirdPartInfo
(
adamRdmService
.
getThirdPartVoListByUid
(
registerUserInfo
.
getUid
()));
loginInfoVo
.
setMemberSimpleVo
(
adamRdmService
.
getMemberSimpleVo
());
loginInfoVo
.
setMemberSimpleVo
(
adamRdmService
.
getMemberSimpleVo
());
}
}
loginInfoVo
.
setToken
(
this
.
ssoProcess
(
loginInfoVo
.
getUserInfo
()));
Map
<
String
,
Object
>
claimsMap
=
new
HashMap
<>();
claimsMap
.
put
(
"sub"
,
loginInfoVo
.
getUserInfo
().
getUid
());
claimsMap
.
put
(
"mobile"
,
loginInfoVo
.
getUserInfo
().
getMobile
());
claimsMap
.
put
(
"nickname"
,
loginInfoVo
.
getUserInfo
().
getNickname
());
loginInfoVo
.
setToken
(
this
.
ssoProcess
(
claimsMap
));
return
ResponseDto
.
success
(
loginInfoVo
);
return
ResponseDto
.
success
(
loginInfoVo
);
}
}
@ApiOperationSupport
(
order
=
6
)
@ApiOperationSupport
(
order
=
6
)
@ApiOperation
(
value
=
"登出"
)
@ApiOperation
(
value
=
"登出"
)
@PostMapping
(
value
=
{
"out"
})
@PostMapping
(
value
=
{
"out"
})
public
ResponseDto
<
Object
>
logout
(
HttpServletRequest
request
)
{
public
void
logout
()
{
String
uToken
=
request
.
getHeader
(
CurrentUtil
.
uToken
);
log
.
info
(
"###logout:uid:{}\ntoken:{}"
,
CurrentUtil
.
getCurrentUid
(),
CurrentUtil
.
getToken
());
log
.
info
(
"###logout:uid:{}\ntoken:{}\nuToken:{}"
,
CurrentUtil
.
getCurrentUid
(),
CurrentUtil
.
getToken
(),
uToken
);
String
ssoKeyUidM5Token
=
jwtValidator
.
getSsoRedisKey
().
concat
(
CurrentUtil
.
getCurrentUid
()).
concat
(
redisUtil
.
del
(
jwtValidator
.
getSsoRedisKey
().
concat
(
CurrentUtil
.
getCurrentUid
()));
DigestUtils
.
md5DigestAsHex
(
uToken
.
getBytes
(
StandardCharsets
.
UTF_8
))
);
redisUtil
.
set
(
ssoKeyUidM5Token
,
false
);
return
ResponseDto
.
success
();
}
}
@ApiOperationSupport
(
order
=
7
)
@ApiOperationSupport
(
order
=
7
)
@ApiOperation
(
value
=
"注销"
)
@ApiOperation
(
value
=
"注销"
)
@PostMapping
(
value
=
{
"close"
})
@PostMapping
(
value
=
{
"close"
})
public
ResponseDto
<
Object
>
close
(
HttpServletRequest
request
)
{
public
ResponseDto
<
Object
>
close
()
{
log
.
info
(
"###close:uid:{}"
,
CurrentUtil
.
getCurrentUid
());
log
.
info
(
"###close:uid:{}"
,
CurrentUtil
.
getCurrentUid
());
adamUserService
.
close
(
CurrentUtil
.
getCurrentUid
());
adamUserService
.
close
(
CurrentUtil
.
getCurrentUid
());
return
this
.
logout
(
request
);
this
.
logout
();
return
ResponseDto
.
success
();
}
}
/* ---------------------------- Internal Method ---------------------------- */
/* ---------------------------- Internal Method ---------------------------- */
private
boolean
checkSmsCode
(
String
mobile
,
String
code
)
{
private
boolean
checkSmsCode
(
String
mobile
,
String
code
)
{
if
(
Arrays
.
asList
(
"dev"
,
"test"
).
contains
(
env
ironment
.
getProperty
(
"spring.profiles.active"
)))
{
if
(
Arrays
.
asList
(
"dev"
,
"test"
).
contains
(
env
.
getProperty
(
"spring.profiles.active"
)))
{
return
"111111"
.
equals
(
code
);
return
"111111"
.
equals
(
code
);
}
}
...
@@ -275,18 +256,20 @@ public class AdamLoginController {
...
@@ -275,18 +256,20 @@ public class AdamLoginController {
return
null
;
return
null
;
}
}
private
String
ssoProcess
(
Map
<
String
,
Object
>
claimsMap
)
{
private
String
ssoProcess
(
AdamUserInfoVo
userInfoVo
)
{
String
uid
=
(
String
)
claimsMap
.
get
(
"sub"
);
Map
<
String
,
Object
>
claimsMap
=
new
HashMap
<>(
);
claimsMap
.
put
(
"sub"
,
userInfoVo
.
getUid
());
String
uidSso
=
jwtValidator
.
getSsoRedisKey
().
concat
(
uid
);
// TODO: 2021/5/25 修改手机号更新TOKEN
claimsMap
.
put
(
"mobile"
,
userInfoVo
.
getMobile
());
// redisUtil.delKeysByPrefix(uidSso
);
claimsMap
.
put
(
"nickname"
,
userInfoVo
.
getNickname
()
);
String
token
=
jwtValidator
.
create
(
claimsMap
);
String
token
=
jwtValidator
.
create
(
claimsMap
);
String
ssoKey
=
uidSso
.
concat
(
DigestUtils
.
md5DigestAsHex
(
token
.
getBytes
(
StandardCharsets
.
UTF_8
)));
redisUtil
.
set
(
jwtValidator
.
getSsoRedisKey
().
concat
(
userInfoVo
.
getUid
()),
redisUtil
.
set
(
ssoKey
,
true
,
jwtValidator
.
getExpireTtl
()
*
60
);
DigestUtils
.
md5DigestAsHex
(
token
.
getBytes
(
StandardCharsets
.
UTF_8
)),
jwtValidator
.
getExpireTtl
()
*
60
);
return
token
;
return
token
;
}
}
...
...
liquidnet-bus-service/liquidnet-service-adam/liquidnet-service-adam-impl/src/main/java/com/liquidnet/service/adam/service/impl/AdamRdmServiceImpl.java
View file @
a7321f5f
...
@@ -212,7 +212,7 @@ public class AdamRdmServiceImpl implements IAdamRdmService {
...
@@ -212,7 +212,7 @@ public class AdamRdmServiceImpl implements IAdamRdmService {
@Override
@Override
public
void
delUserMemberVoByUid
(
String
uid
)
{
public
void
delUserMemberVoByUid
(
String
uid
)
{
redisUtil
.
del
(
AdamRedisConst
.
INFO_USER_MEMBER
.
concat
(
uid
));
;
redisUtil
.
del
(
AdamRedisConst
.
INFO_USER_MEMBER
.
concat
(
uid
));
}
}
@Override
@Override
...
...
liquidnet-bus-service/liquidnet-service-kylin/liquidnet-service-kylin-impl/src/main/java/com/liquidnet/service/kylin/controller/KylinStationController.java
View file @
a7321f5f
...
@@ -85,20 +85,17 @@ public class KylinStationController {
...
@@ -85,20 +85,17 @@ public class KylinStationController {
@ApiOperation
(
value
=
"登出"
)
@ApiOperation
(
value
=
"登出"
)
@PostMapping
(
"out"
)
@PostMapping
(
"out"
)
public
ResponseDto
<
String
>
out
()
{
public
void
out
()
{
String
checkUserId
=
CurrentUtil
.
getCurrentUid
();
String
checkUserId
=
CurrentUtil
.
getCurrentUid
();
String
token
=
CurrentUtil
.
getToken
();
String
token
=
CurrentUtil
.
getToken
();
String
ssoKeyUidM5Token
=
jwtValidator
.
getSsoRedisKey
().
concat
(
CurrentUtil
.
getCurrentUid
()).
concat
(
String
ssoUidM5TokenKey
=
jwtValidator
.
getSsoRedisKey
()
DigestUtils
.
md5DigestAsHex
(
token
.
getBytes
(
StandardCharsets
.
UTF_8
))
.
concat
(
checkUserId
).
concat
(
":"
).
concat
(
DigestUtils
.
md5DigestAsHex
(
token
.
getBytes
(
StandardCharsets
.
UTF_8
)));
);
log
.
info
(
"###logout:checkUserId:{}\nssoKey:{}\ntoken:{}"
,
checkUserId
,
ssoKeyUidM5Token
,
token
);
redisUtil
.
set
(
ssoKeyUidM5Token
,
false
);
log
.
info
(
"###logout:checkUserId:{}\nssoKey:{}\ntoken:{}"
,
checkUserId
,
ssoUidM5TokenKey
,
token
);
re
turn
ResponseDto
.
success
(
);
re
disUtil
.
set
(
ssoUidM5TokenKey
,
0
);
}
}
/* ------------------------------------------------------------------ */
/* ------------------------------------------------------------------ */
...
@@ -400,20 +397,18 @@ public class KylinStationController {
...
@@ -400,20 +397,18 @@ public class KylinStationController {
}
}
private
ResponseDto
<
KylinStationLoginVo
>
loginAuthentication
(
KylinCheckUserVo
checkUserVo
)
{
private
ResponseDto
<
KylinStationLoginVo
>
loginAuthentication
(
KylinCheckUserVo
checkUserVo
)
{
String
uid
=
checkUserVo
.
getCheckUserId
();
String
ssoKeyUid
=
jwtValidator
.
getSsoRedisKey
().
concat
(
uid
);
Map
<
String
,
Object
>
claimsMap
=
new
HashMap
<>();
Map
<
String
,
Object
>
claimsMap
=
new
HashMap
<>();
claimsMap
.
put
(
"sub"
,
uid
);
claimsMap
.
put
(
"sub"
,
checkUserVo
.
getCheckUserId
()
);
claimsMap
.
put
(
"mobile"
,
checkUserVo
.
getMobile
());
claimsMap
.
put
(
"mobile"
,
checkUserVo
.
getMobile
());
claimsMap
.
put
(
"nickname"
,
checkUserVo
.
getName
());
claimsMap
.
put
(
"nickname"
,
checkUserVo
.
getName
());
String
token
=
jwtValidator
.
create
(
claimsMap
);
String
token
=
jwtValidator
.
create
(
claimsMap
);
String
ssoKeyUidM5Token
=
ssoKeyUid
.
concat
(
DigestUtils
.
md5DigestAsHex
(
token
.
getBytes
(
StandardCharsets
.
UTF_8
)));
String
ssoUidM5TokenKey
=
jwtValidator
.
getSsoRedisKey
()
.
concat
(
checkUserVo
.
getCheckUserId
())
.
concat
(
":"
).
concat
(
DigestUtils
.
md5DigestAsHex
(
token
.
getBytes
(
StandardCharsets
.
UTF_8
)));
redisUtil
.
set
(
sso
KeyUidM5Token
,
true
,
jwtValidator
.
getExpireTtl
()
*
60
);
redisUtil
.
set
(
sso
UidM5TokenKey
,
1
,
jwtValidator
.
getExpireTtl
()
*
60
);
KylinStationLoginVo
stationLoginVo
=
KylinStationLoginVo
.
getNew
();
KylinStationLoginVo
stationLoginVo
=
KylinStationLoginVo
.
getNew
();
stationLoginVo
.
setUid
(
checkUserVo
.
getCheckUserId
());
stationLoginVo
.
setUid
(
checkUserVo
.
getCheckUserId
());
...
...
liquidnet-bus-support/liquidnet-support-zuul/src/main/java/com/liquidnet/support/zuul/filter/GlobalAuthFilter.java
View file @
a7321f5f
...
@@ -35,6 +35,8 @@ public class GlobalAuthFilter extends ZuulFilter {
...
@@ -35,6 +35,8 @@ public class GlobalAuthFilter extends ZuulFilter {
private
List
<
String
>
excludeUrl
;
private
List
<
String
>
excludeUrl
;
private
List
<
String
>
excludeUrlPattern
;
private
List
<
String
>
excludeUrlPattern
;
private
static
final
String
KYLIN_STATION_JWT_VALID
=
"/kylin/station/**"
;
private
static
final
String
CONTENT_TYPE
=
"application/json;charset=utf-8"
;
private
static
final
String
CONTENT_TYPE
=
"application/json;charset=utf-8"
;
private
static
final
String
AUTHORIZATION
=
"authorization"
;
private
static
final
String
AUTHORIZATION
=
"authorization"
;
private
static
final
String
TOKEN_STATUS
=
"token_status"
;
private
static
final
String
TOKEN_STATUS
=
"token_status"
;
...
@@ -80,6 +82,8 @@ public class GlobalAuthFilter extends ZuulFilter {
...
@@ -80,6 +82,8 @@ public class GlobalAuthFilter extends ZuulFilter {
ctx
.
addZuulRequestHeader
(
TOKEN_STATUS
,
TOKEN_ILLEGAL
);
ctx
.
addZuulRequestHeader
(
TOKEN_STATUS
,
TOKEN_ILLEGAL
);
}
}
ctx
.
addZuulRequestHeader
(
CurrentUtil
.
uToken
,
token
);
ctx
.
addZuulRequestHeader
(
CurrentUtil
.
uToken
,
token
);
}
else
{
ctx
.
addZuulRequestHeader
(
TOKEN_STATUS
,
TOKEN_ILLEGAL
);
}
}
String
requestURI
=
ctxRequest
.
getRequestURI
();
String
requestURI
=
ctxRequest
.
getRequestURI
();
...
@@ -106,36 +110,43 @@ public class GlobalAuthFilter extends ZuulFilter {
...
@@ -106,36 +110,43 @@ public class GlobalAuthFilter extends ZuulFilter {
@Override
@Override
public
Object
run
()
{
public
Object
run
()
{
RequestContext
ctx
=
RequestContext
.
getCurrentContext
();
RequestContext
ctx
=
RequestContext
.
getCurrentContext
();
Map
<
String
,
String
>
zuulRequestHeaders
=
ctx
.
getZuulRequestHeaders
();
Map
<
String
,
String
>
zuulRequestHeaders
=
ctx
.
getZuulRequestHeaders
();
String
uToken
=
zuulRequestHeaders
.
get
(
CurrentUtil
.
uToken
);
log
.
debug
(
"lns.headers:{}"
,
zuulRequestHeaders
);
log
.
debug
(
"headers:{}"
,
zuulRequestHeaders
);
String
uToken
=
zuulRequestHeaders
.
get
(
CurrentUtil
.
uToken
),
uid
;
if
(
StringUtils
.
isEmpty
(
uToken
)
||
StringUtils
.
isEmpty
(
uid
=
zuulRequestHeaders
.
get
(
CurrentUtil
.
uID
)))
{
this
.
respHandler
(
ctx
,
zuulRequestHeaders
.
get
(
TOKEN_STATUS
));
if
(
StringUtils
.
isEmpty
(
uToken
))
{
return
null
;
respHandler
(
ctx
,
TOKEN_ILLEGAL
);
}
if
(
PathMatchUtil
.
isPathMatch
(
KYLIN_STATION_JWT_VALID
,
ctx
.
getRequest
().
getRequestURI
()))
{
// 专业版APP
// adam:identity:sso:${uid}:MD5(${token})=${1-在线|0-离线}
String
ssoUidM5TokenKey
=
jwtValidator
.
getSsoRedisKey
()
.
concat
(
uid
).
concat
(
":"
).
concat
(
DigestUtils
.
md5DigestAsHex
(
uToken
.
getBytes
(
StandardCharsets
.
UTF_8
)));
Integer
online
=
(
Integer
)
redisUtil
.
get
(
ssoUidM5TokenKey
);
if
(
null
==
online
||
online
!=
1
)
{
this
.
respHandler
(
ctx
,
TOKEN_INVALID
);
}
else
{
}
else
{
String
uid
=
zuulRequestHeaders
.
get
(
CurrentUtil
.
uID
);
ctx
.
setSendZuulResponse
(
true
);
}
if
(
StringUtils
.
isEmpty
(
uid
))
{
respHandler
(
ctx
,
zuulRequestHeaders
.
get
(
TOKEN_STATUS
));
}
else
{
}
else
{
String
ssoKeyUidM5Token
=
jwtValidator
.
getSsoRedisKey
().
concat
(
uid
).
concat
(
// adam:identity:sso:${uid}=MD5(${token})
DigestUtils
.
md5DigestAsHex
(
uToken
.
getBytes
(
StandardCharsets
.
UTF_8
))
String
ssoKey
=
jwtValidator
.
getSsoRedisKey
().
concat
(
uid
);
);
if
(
redisUtil
.
hasKey
(
ssoKeyUidM5Token
))
{
// 是否存在SSO
String
md5Token
=
(
String
)
redisUtil
.
get
(
ssoKey
);
if
((
boolean
)
redisUtil
.
get
(
ssoKeyUidM5Token
))
{
// 是否在线
if
(
StringUtils
.
isEmpty
(
md5Token
))
{
// 已离线
ctx
.
setSendZuulResponse
(
true
);
this
.
respHandler
(
ctx
,
TOKEN_INVALID
);
}
else
{
}
else
{
// 与在线TOKEN比对
respHandler
(
ctx
,
TOKEN_INVALID
);
if
(
md5Token
.
equals
(
DigestUtils
.
md5DigestAsHex
(
uToken
.
getBytes
(
StandardCharsets
.
UTF_8
))))
{
}
// 一致则放行
ctx
.
setSendZuulResponse
(
true
);
}
else
{
}
else
{
respHandler
(
ctx
,
TOKEN_KICK
);
// 不一致则被踢下线
this
.
respHandler
(
ctx
,
TOKEN_KICK
);
}
}
}
}
}
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment