Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
X
xyqb-user2
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
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
head_group
xyqb-user2
Commits
d9b386b0
Commit
d9b386b0
authored
Apr 03, 2025
by
xuepeng.chang
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat:微信解绑功能
parent
9a37979b
Changes
12
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
232 additions
and
6 deletions
+232
-6
UserConstant.java
src/main/java/cn/quantgroup/xyqb/constant/UserConstant.java
+2
-0
AppletController.java
...xyqb/controller/middleoffice/applet/AppletController.java
+16
-0
WechatBindLog.java
src/main/java/cn/quantgroup/xyqb/entity/WechatBindLog.java
+57
-0
UnbindParam.java
...a/cn/quantgroup/xyqb/entity/middleoffice/UnbindParam.java
+48
-0
WechatPhoneNoUpdateEventListener.java
...antgroup/xyqb/event/WechatPhoneNoUpdateEventListener.java
+1
-0
BizExceptionEnum.java
...n/java/cn/quantgroup/xyqb/exception/BizExceptionEnum.java
+3
-1
IWeChatUserRepository.java
.../cn/quantgroup/xyqb/repository/IWeChatUserRepository.java
+7
-0
IWechatBindLogRepository.java
.../quantgroup/xyqb/repository/IWechatBindLogRepository.java
+8
-0
IAppletService.java
...roup/xyqb/service/middleoffice/applet/IAppletService.java
+3
-0
AppletServiceImpl.java
...b/service/middleoffice/applet/impl/AppletServiceImpl.java
+74
-4
ISessionService.java
...a/cn/quantgroup/xyqb/service/session/ISessionService.java
+3
-0
SessionServiceImpl.java
...ntgroup/xyqb/service/session/impl/SessionServiceImpl.java
+10
-1
No files found.
src/main/java/cn/quantgroup/xyqb/constant/UserConstant.java
View file @
d9b386b0
...
...
@@ -7,6 +7,8 @@ public class UserConstant {
public
static
final
String
USER_FREEZE_ERROR
=
"账号异常,已冻结。"
;
public
static
final
String
USER_WECHAT_BIND_ERROR
=
"该手机对应账户对应账户已绑定其他微信"
;
public
static
final
Integer
defaultTenantId
=
560761
;
public
static
final
String
defaultTenantIdString
=
"560761"
;
...
...
src/main/java/cn/quantgroup/xyqb/controller/middleoffice/applet/AppletController.java
View file @
d9b386b0
...
...
@@ -7,6 +7,7 @@ import cn.quantgroup.xyqb.controller.IBaseController;
import
cn.quantgroup.xyqb.controller.middleoffice.login.ILoginModule
;
import
cn.quantgroup.xyqb.controller.middleoffice.login.LoginVo
;
import
cn.quantgroup.xyqb.entity.middleoffice.AppletParamEntry
;
import
cn.quantgroup.xyqb.entity.middleoffice.UnbindParam
;
import
cn.quantgroup.xyqb.exception.DataException
;
import
cn.quantgroup.xyqb.model.JsonResult
;
import
cn.quantgroup.xyqb.model.WechatConfigBean
;
...
...
@@ -73,6 +74,21 @@ public class AppletController implements IBaseController {
.
buildSuccessResultGeneric
(
loginVo
);
}
@PostMapping
(
"/unbindWechat"
)
public
JsonResult
<
Boolean
>
unbindWechat
(
@Validated
@RequestBody
UnbindParam
unbindParam
)
{
log
.
info
(
"/middle_office/applet/unbindWechat:{}"
,
JSON
.
toJSONString
(
unbindParam
));
if
(!
containsAppName
(
unbindParam
.
getAppName
()))
{
throw
new
DataException
(
"appName不合法"
);
}
if
(
StringUtils
.
isEmpty
(
unbindParam
.
getTenantId
())){
unbindParam
.
setTenantId
(
getTenantId
());
}
boolean
unbind
=
iAppletService
.
unbind
(
unbindParam
);
return
JsonResult
.
buildSuccessResultGeneric
(
unbind
);
}
/**
* @return
*/
...
...
src/main/java/cn/quantgroup/xyqb/entity/WechatBindLog.java
0 → 100644
View file @
d9b386b0
package
cn
.
quantgroup
.
xyqb
.
entity
;
import
cn.quantgroup.xyqb.entity.converter.EncryptConverter
;
import
lombok.AllArgsConstructor
;
import
lombok.Data
;
import
lombok.NoArgsConstructor
;
import
javax.persistence.Column
;
import
javax.persistence.Convert
;
import
javax.persistence.Entity
;
import
javax.persistence.Table
;
import
java.io.Serializable
;
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table
(
name
=
"wechat_bind_log"
)
@Data
public
class
WechatBindLog
extends
BaseEntity
implements
Serializable
{
private
static
final
long
serialVersionUID
=
-
1L
;
/** 用户id*/
@Column
(
name
=
"user_id"
)
private
Long
userId
;
/** 微信用户唯一标识*/
@Column
(
name
=
"open_id"
)
private
String
openId
;
/** 应用名称*/
@Column
(
name
=
"app_name"
)
private
String
appName
;
/** 用户在微信平台的唯一标识*/
@Column
(
name
=
"union_id"
)
private
String
unionId
;
/** 加密手机号*/
@Column
(
name
=
"encrypted_phone_no"
)
@Convert
(
converter
=
EncryptConverter
.
class
)
private
String
encryptedPhoneNo
;
/** 绑定解绑类型 1:绑定 2:解绑*/
@Column
(
name
=
"bind_type"
)
private
Integer
bindType
;
/** 微信应用id*/
@Column
(
name
=
"app_id"
)
private
String
appId
;
/** 租户id*/
@Column
(
name
=
"tenant_id"
)
private
Integer
tenantId
;
public
WechatBindLog
(
Long
userId
,
String
openId
,
String
appName
,
String
appId
,
Integer
tenantId
,
String
unionId
,
Integer
bindType
)
{
this
.
userId
=
userId
;
this
.
openId
=
openId
;
this
.
appName
=
appName
;
this
.
appId
=
appId
;
this
.
tenantId
=
tenantId
;
this
.
unionId
=
unionId
;
this
.
bindType
=
bindType
;
}
}
src/main/java/cn/quantgroup/xyqb/entity/middleoffice/UnbindParam.java
0 → 100644
View file @
d9b386b0
package
cn
.
quantgroup
.
xyqb
.
entity
.
middleoffice
;
import
lombok.AllArgsConstructor
;
import
lombok.Data
;
import
lombok.NoArgsConstructor
;
import
javax.validation.constraints.NotNull
;
import
java.io.Serializable
;
@Data
@AllArgsConstructor
@NoArgsConstructor
public
class
UnbindParam
implements
Serializable
{
private
static
final
long
serialVersionUID
=
-
1L
;
/**
* 应用标识
*/
@NotNull
(
message
=
"appName 不能为空"
)
private
String
appName
;
/**
* 应用标识
*/
@NotNull
(
message
=
"appId 不能为空"
)
private
String
appId
;
/**
* 应用对应的渠道号
*/
@NotNull
(
message
=
"channelId 不能为空"
)
private
Long
channelId
;
@NotNull
(
message
=
"phone 不能为空"
)
private
String
phone
;
@NotNull
(
message
=
"userId 不能为空"
)
private
Long
userId
;
/**
* 租户ID
*/
private
Integer
tenantId
;
/** 微信小程序的渠道号*/
private
String
wechatChannelId
;
}
src/main/java/cn/quantgroup/xyqb/event/WechatPhoneNoUpdateEventListener.java
View file @
d9b386b0
...
...
@@ -30,6 +30,7 @@ public class WechatPhoneNoUpdateEventListener implements ApplicationListener<Pho
}
userInfo
.
setPhoneNo
(
user
.
getPhoneNo
());
userInfo
.
setEncryptedPhoneNo
(
user
.
getPhoneNo
());
wechatService
.
saveWechatUserInfo
(
userInfo
);
}
}
src/main/java/cn/quantgroup/xyqb/exception/BizExceptionEnum.java
View file @
d9b386b0
...
...
@@ -50,7 +50,9 @@ public enum BizExceptionEnum {
FAIL_JI_GUANG_VALIDATE
(
"2024"
,
"极光验证未通过"
),
//通用记录
ERROR_PARAM
(
"4000"
,
"参数错误"
),
INVALID_SMS_CODE
(
"4001"
,
"验证码失效,请重新获取"
);
INVALID_SMS_CODE
(
"4001"
,
"验证码失效,请重新获取"
),
USER_WECHAT_BIND_ERROR
(
"3001"
,
"该手机对应账户对应账户已绑定其他微信"
);
private
final
String
businessCode
;
private
final
String
msg
;
...
...
src/main/java/cn/quantgroup/xyqb/repository/IWeChatUserRepository.java
View file @
d9b386b0
...
...
@@ -18,6 +18,10 @@ import static org.springframework.transaction.annotation.Propagation.MANDATORY;
public
interface
IWeChatUserRepository
extends
JpaRepository
<
WechatUserInfo
,
Long
>
{
WechatUserInfo
findByOpenIdAndAppNameAndAppIdAndTenantId
(
String
openId
,
String
appName
,
String
appId
,
Integer
tenantId
);
List
<
WechatUserInfo
>
findByPhoneNoAndAppNameAndAppIdAndTenantId
(
String
phoneNo
,
String
appName
,
String
appId
,
Integer
tenantId
);
List
<
WechatUserInfo
>
findByUserIdAndAppNameAndAppIdAndTenantId
(
Long
userId
,
String
appName
,
String
appId
,
Integer
tenantId
);
/**
* 兼容历史逻辑,老的代码可能只通过appName进行支付宝、百度等登录
...
...
@@ -99,6 +103,9 @@ public interface IWeChatUserRepository extends JpaRepository<WechatUserInfo, Lon
@Transactional
void
deleteByUserIdAndTenantId
(
Long
userId
,
Integer
tenantId
);
@Transactional
void
deleteById
(
Long
id
);
/**
* 通过userId查询相关绑定微信记录
*/
...
...
src/main/java/cn/quantgroup/xyqb/repository/IWechatBindLogRepository.java
0 → 100644
View file @
d9b386b0
package
cn
.
quantgroup
.
xyqb
.
repository
;
import
cn.quantgroup.xyqb.entity.WechatBindLog
;
import
cn.quantgroup.xyqb.entity.WechatUserInfo
;
import
org.springframework.data.jpa.repository.JpaRepository
;
public
interface
IWechatBindLogRepository
extends
JpaRepository
<
WechatBindLog
,
Long
>
{
}
src/main/java/cn/quantgroup/xyqb/service/middleoffice/applet/IAppletService.java
View file @
d9b386b0
...
...
@@ -2,6 +2,7 @@ package cn.quantgroup.xyqb.service.middleoffice.applet;
import
cn.quantgroup.xyqb.controller.middleoffice.login.LoginVo
;
import
cn.quantgroup.xyqb.entity.middleoffice.AppletParamEntry
;
import
cn.quantgroup.xyqb.entity.middleoffice.UnbindParam
;
/**
* @author :dongjianhua
...
...
@@ -13,4 +14,6 @@ import cn.quantgroup.xyqb.entity.middleoffice.AppletParamEntry;
public
interface
IAppletService
{
Long
relevance
(
AppletParamEntry
appletParamEntry
);
LoginVo
login
(
String
appName
,
String
openId
,
Integer
tenantId
,
String
utmSource
,
String
unionId
,
String
appId
);
boolean
unbind
(
UnbindParam
unbindParam
);
}
src/main/java/cn/quantgroup/xyqb/service/middleoffice/applet/impl/AppletServiceImpl.java
View file @
d9b386b0
...
...
@@ -5,26 +5,39 @@ import cn.quantgroup.xyqb.constant.UserConstant;
import
cn.quantgroup.xyqb.controller.middleoffice.login.ILoginModule
;
import
cn.quantgroup.xyqb.controller.middleoffice.login.LoginVo
;
import
cn.quantgroup.xyqb.entity.User
;
import
cn.quantgroup.xyqb.entity.WechatBindLog
;
import
cn.quantgroup.xyqb.entity.WechatUserInfo
;
import
cn.quantgroup.xyqb.entity.middleoffice.AppletParamEntry
;
import
cn.quantgroup.xyqb.entity.middleoffice.UnbindParam
;
import
cn.quantgroup.xyqb.exception.AppletException
;
import
cn.quantgroup.xyqb.exception.BizException
;
import
cn.quantgroup.xyqb.exception.BizExceptionEnum
;
import
cn.quantgroup.xyqb.model.LoginProperties
;
import
cn.quantgroup.xyqb.repository.IWeChatUserRepository
;
import
cn.quantgroup.xyqb.repository.IWechatBindLogRepository
;
import
cn.quantgroup.xyqb.service.middleoffice.applet.IAppletService
;
import
cn.quantgroup.xyqb.service.register.IUserRegisterService
;
import
cn.quantgroup.xyqb.service.session.ISessionService
;
import
cn.quantgroup.xyqb.service.session.impl.SessionServiceImpl
;
import
cn.quantgroup.xyqb.service.user.IUserService
;
import
cn.quantgroup.xyqb.util.StringUtils
;
import
com.alibaba.fastjson.JSON
;
import
lombok.extern.slf4j.Slf4j
;
import
org.apache.commons.collections.CollectionUtils
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.stereotype.Service
;
import
org.springframework.transaction.annotation.Transactional
;
import
org.springframework.validation.BindException
;
import
java.util.ArrayList
;
import
java.util.Arrays
;
import
java.util.List
;
import
java.util.Objects
;
import
java.util.stream.Collectors
;
import
static
cn
.
quantgroup
.
xyqb
.
constant
.
UserConstant
.
USER_FREEZE_ERROR
;
/**
* @author :dongjianhua
* @date :Created in 2020/5/27 17:27
...
...
@@ -38,25 +51,28 @@ public class AppletServiceImpl implements IAppletService {
private
final
IWeChatUserRepository
iWeChatUserRepository
;
private
final
IUserRegisterService
iUserRegisterService
;
private
final
IUserService
userService
;
private
ILoginModule
loginModule
;
private
final
WechatConfiguration
wechatConfiguration
;
private
final
IWechatBindLogRepository
wechatBindLogRepository
;
private
final
ISessionService
sessionService
;
@Autowired
public
AppletServiceImpl
(
IWeChatUserRepository
iWeChatUserRepository
,
IUserRegisterService
iUserRegisterService
,
IUserService
userService
,
ILoginModule
loginModule
,
WechatConfiguration
wechatConfiguration
)
{
ILoginModule
loginModule
,
WechatConfiguration
wechatConfiguration
,
IWechatBindLogRepository
wechatBindLogRepository
,
ISessionService
sessionService
)
{
this
.
iWeChatUserRepository
=
iWeChatUserRepository
;
this
.
iUserRegisterService
=
iUserRegisterService
;
this
.
userService
=
userService
;
this
.
loginModule
=
loginModule
;
this
.
wechatConfiguration
=
wechatConfiguration
;
this
.
wechatBindLogRepository
=
wechatBindLogRepository
;
this
.
sessionService
=
sessionService
;
}
@Override
...
...
@@ -68,6 +84,25 @@ public class AppletServiceImpl implements IAppletService {
throw
new
BizException
(
BizExceptionEnum
.
ERROR_MATCHING_WECHAT_APP_ID
);
}
appletParamEntry
.
setAppId
(
appId
);
// 校验当前用户的手机号有没有绑定过openId
List
<
WechatUserInfo
>
wechatUserInfos
=
iWeChatUserRepository
.
findByPhoneNoAndAppNameAndAppIdAndTenantId
(
appletParamEntry
.
getMobile
(),
appletParamEntry
.
getAppName
(),
appletParamEntry
.
getAppId
(),
appletParamEntry
.
getTenantId
());
if
(
wechatUserInfos
.
size
()>
1
){
log
.
info
(
"微信关联失败[service]:phoneNo:{},openId:{},appName:{}"
,
appletParamEntry
.
getMobile
(),
appletParamEntry
.
getOpenId
(),
appletParamEntry
.
getAppName
());
throw
new
AppletException
(
BizExceptionEnum
.
USER_WECHAT_BIND_ERROR
.
getMsg
(),
BizExceptionEnum
.
USER_WECHAT_BIND_ERROR
.
getBusinessCode
());
}
if
(
wechatUserInfos
.
size
()
==
1
){
WechatUserInfo
wechatUserInfoOne
=
wechatUserInfos
.
get
(
0
);
if
(
Objects
.
equals
(
wechatUserInfoOne
.
getOpenId
(),
appletParamEntry
.
getOpenId
())){
log
.
info
(
"微信关联成功:重复关联:跳过:[service]:userId:{},phoneNo:{},openId:{}"
,
wechatUserInfoOne
.
getUserId
(),
wechatUserInfoOne
.
getPhoneNo
(),
wechatUserInfoOne
.
getOpenId
());
return
wechatUserInfoOne
.
getUserId
();
}
else
{
log
.
info
(
"微信关联失败[service]:phoneNo:{},openId:{},appName:{}"
,
appletParamEntry
.
getMobile
(),
appletParamEntry
.
getOpenId
(),
appletParamEntry
.
getAppName
());
throw
new
AppletException
(
BizExceptionEnum
.
USER_WECHAT_BIND_ERROR
.
getMsg
(),
BizExceptionEnum
.
USER_WECHAT_BIND_ERROR
.
getBusinessCode
());
}
}
WechatUserInfo
wechatUserInfo
=
iWeChatUserRepository
.
findByOpenIdAndAppNameAndAppIdAndTenantId
(
appletParamEntry
.
getOpenId
(),
appletParamEntry
.
getAppName
(),
appletParamEntry
.
getAppId
(),
appletParamEntry
.
getTenantId
());
//这个接口先不考虑更换手机号的情况
if
(
appletParamEntry
.
getTenantId
()
==
null
)
{
...
...
@@ -107,6 +142,10 @@ public class AppletServiceImpl implements IAppletService {
//如果存在就更新在微信表里
//iWeChatUserRepository.findByOpenIdAndAppNameAndTenantId()
iWeChatUserRepository
.
save
(
wechatUserInfo
);
// 记录微信绑定记录
WechatBindLog
wechatBindLog
=
new
WechatBindLog
(
wechatUserInfo
.
getUserId
(),
wechatUserInfo
.
getOpenId
(),
wechatUserInfo
.
getAppName
(),
appId
,
wechatUserInfo
.
getTenantId
(),
wechatUserInfo
.
getUnionId
(),
1
);
wechatBindLogRepository
.
save
(
wechatBindLog
);
return
wechatUserInfo
.
getUserId
();
}
...
...
@@ -147,5 +186,36 @@ public class AppletServiceImpl implements IAppletService {
utmSource
==
null
?
""
:
utmSource
,
user
.
getId
(),
tenantId
);
}
@Override
@Transactional
(
rollbackFor
=
Exception
.
class
)
public
boolean
unbind
(
UnbindParam
unbindParam
)
{
// 获取当前用户绑定了对应的微信小程序
List
<
WechatUserInfo
>
wechatUserInfoList
=
iWeChatUserRepository
.
findByUserIdAndAppNameAndAppIdAndTenantId
(
unbindParam
.
getUserId
(),
unbindParam
.
getAppName
(),
unbindParam
.
getAppId
(),
unbindParam
.
getTenantId
());
if
(
CollectionUtils
.
isEmpty
(
wechatUserInfoList
)){
log
.
info
(
"用户没有微信绑定记录,appName:{} ,phone:{},userId:{}"
,
unbindParam
.
getAppName
(),
unbindParam
.
getPhone
(),
unbindParam
.
getUserId
());
return
true
;
}
// 删除微信关联记录,并记录解绑记录
for
(
WechatUserInfo
wechatUserInfo
:
wechatUserInfoList
)
{
WechatBindLog
wechatBindLog
=
new
WechatBindLog
(
wechatUserInfo
.
getUserId
(),
wechatUserInfo
.
getOpenId
(),
wechatUserInfo
.
getAppName
(),
wechatUserInfo
.
getAppId
(),
wechatUserInfo
.
getTenantId
(),
wechatUserInfo
.
getUnionId
(),
2
);
log
.
info
(
"删除微信绑定记录,wechatUserInfo:{}"
,
JSON
.
toJSONString
(
wechatUserInfo
));
iWeChatUserRepository
.
deleteById
(
wechatUserInfo
.
getId
());
wechatBindLogRepository
.
save
(
wechatBindLog
);
}
// 删除微信关联记录后,清除微信渠道的登陆token
if
(
org
.
apache
.
commons
.
lang3
.
StringUtils
.
isNotEmpty
(
unbindParam
.
getWechatChannelId
())){
List
<
String
>
wechatChannelIds
=
Arrays
.
stream
(
unbindParam
.
getWechatChannelId
().
split
(
","
)).
collect
(
Collectors
.
toList
());
for
(
String
wechatChannelId
:
wechatChannelIds
)
{
LoginProperties
loginProperties
=
new
LoginProperties
(
1
,
Long
.
valueOf
(
wechatChannelId
),
unbindParam
.
getTenantId
());
sessionService
.
deleteWechatSession
(
unbindParam
.
getUserId
(),
loginProperties
,
unbindParam
.
getTenantId
());
log
.
info
(
"微信解绑成功,清除微信的登陆token,userId:{},wechatChannelId:{}"
,
unbindParam
.
getUserId
(),
wechatChannelId
);
}
}
log
.
info
(
"微信解绑成功,unbindParam:{} "
,
JSON
.
toJSONString
(
unbindParam
));
return
true
;
}
}
src/main/java/cn/quantgroup/xyqb/service/session/ISessionService.java
View file @
d9b386b0
...
...
@@ -36,6 +36,9 @@ public interface ISessionService {
void
kdspDeleteSession
(
Long
userId
,
LoginProperties
loginProperties
,
Integer
tenantId
);
void
deleteWechatSession
(
Long
userId
,
LoginProperties
loginProperties
,
Integer
tenantId
);
/**
* 更新session
* 用户信息存在,更新session中的最后访问时间,重新写入缓存.
...
...
src/main/java/cn/quantgroup/xyqb/service/session/impl/SessionServiceImpl.java
View file @
d9b386b0
...
...
@@ -147,7 +147,7 @@ public class SessionServiceImpl implements ISessionService {
* @param properties baitiao/xyqb/vcc ... + 用户注册来源
* @return redisKey. 用来标识这个渠道的用户 Session 是否存在
*/
p
rivate
String
generateLoginPropertiesKey
(
Long
userId
,
LoginProperties
properties
,
Integer
tenantId
)
{
p
ublic
static
String
generateLoginPropertiesKey
(
Long
userId
,
LoginProperties
properties
,
Integer
tenantId
)
{
if
(
ObjectUtils
.
isEmpty
(
properties
.
getTenantId
()))
{
return
Constants
.
Session
.
USER_SESSION_ID_CACHE
+
":"
+
userId
+
":"
+
properties
.
getMerchantName
()
+
":"
+
properties
.
getCreatedFrom
();
}
else
if
(
properties
.
getTenantId
().
equals
(
0
)
||
TenantUtil
.
TENANT_DEFAULT
.
equals
(
properties
.
getTenantId
()))
{
...
...
@@ -431,6 +431,15 @@ public class SessionServiceImpl implements ISessionService {
}
}
@Override
public
void
deleteWechatSession
(
Long
userId
,
LoginProperties
loginProperties
,
Integer
tenantId
)
{
String
key
=
generateLoginPropertiesKey
(
userId
,
loginProperties
,
tenantId
);
String
token
=
stringRedisTemplate
.
opsForValue
().
get
(
key
);
log
.
info
(
"deleteWechatSession,通过用户获取token结果,token:{} , userId: {},loginProperties:{},tenantId:{}"
,
token
,
userId
,
JSON
.
toJSONString
(
loginProperties
),
tenantId
);
deleteSession
(
token
,
tenantId
);
}
/**
* 获取用户的会话缓存Set的Redis-Key
...
...
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