Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
J
jiedao-api-boot-master
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
lanbaoming
jiedao-api-boot-master
Commits
b5d2fcc1
Commit
b5d2fcc1
authored
Nov 29, 2024
by
honghy
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
新增短信日志穿梭、节点国家多选、模板预览
parent
471a32e1
Changes
11
Show whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
130 additions
and
77 deletions
+130
-77
20241120hhy.sql
sql/v2.3/20241120hhy.sql
+2
-2
SmsNodeController.java
...module/system/controller/admin/sms/SmsNodeController.java
+19
-3
SmsNodeBackVO.java
...system/controller/admin/sms/vo/smsNode/SmsNodeBackVO.java
+11
-7
SmsNodeBaseVO.java
...system/controller/admin/sms/vo/smsNode/SmsNodeBaseVO.java
+5
-6
SmsNodeQueryVO.java
...ystem/controller/admin/sms/vo/smsNode/SmsNodeQueryVO.java
+1
-1
SmsNodeTestVO.java
...system/controller/admin/sms/vo/smsNode/SmsNodeTestVO.java
+3
-0
SmsNodeDO.java
...der/yudao/module/system/dal/dataobject/sms/SmsNodeDO.java
+6
-4
SmsCodeServiceImpl.java
...r/yudao/module/system/service/sms/SmsCodeServiceImpl.java
+1
-1
SmsNodeServiceImpl.java
...r/yudao/module/system/service/sms/SmsNodeServiceImpl.java
+45
-21
SmsSendService.java
...coder/yudao/module/system/service/sms/SmsSendService.java
+4
-2
SmsSendServiceImpl.java
...r/yudao/module/system/service/sms/SmsSendServiceImpl.java
+33
-30
No files found.
sql/v2.3/20241120hhy.sql
View file @
b5d2fcc1
...
...
@@ -29,8 +29,8 @@ create table system_sms_node
is_transport
tinyint
not
null
comment
'是否匹配运输方式(0:否,1:是)'
,
transport_id
tinyint
not
null
comment
'运输方式ID'
,
is_orders
tinyint
not
null
comment
'多订单(0:否,1:是)'
,
country_id
bigint
not
null
comment
'国家区号id'
,
country_code
varchar
(
3
0
)
not
null
comment
'国家区号,和订单中国家区号保持一致'
,
country_id
varchar
(
600
)
not
null
comment
'国家区号id'
,
country_code
varchar
(
60
0
)
not
null
comment
'国家区号,和订单中国家区号保持一致'
,
status
tinyint
not
null
comment
'启用状态(0:开启,1:关闭)'
,
template_id_one
bigint
not
null
comment
'模板1'
,
template_id_two
bigint
comment
'模板2'
,
...
...
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sms/SmsNodeController.java
View file @
b5d2fcc1
...
...
@@ -70,7 +70,23 @@ public class SmsNodeController {
@PreAuthorize
(
"@ss.hasPermission('system:sms-node:query')"
)
public
CommonResult
<
SmsNodeBackVO
>
getSmsNode
(
@RequestParam
(
"id"
)
Long
id
)
{
SmsNodeDO
smsNode
=
smsNodeService
.
getSmsNode
(
id
);
return
success
(
SmsNodeConvert
.
INSTANCE
.
convert
(
smsNode
));
SmsNodeBackVO
convert
=
SmsNodeConvert
.
INSTANCE
.
convert
(
smsNode
);
String
countryId
=
convert
.
getCountryId
();
if
(
countryId
==
null
||
countryId
.
isEmpty
())
{
convert
.
setCountryIds
(
new
int
[
0
]);
}
else
{
String
[]
countryIdsStr
=
countryId
.
split
(
","
);
int
[]
countryIdsInt
=
new
int
[
countryIdsStr
.
length
];
for
(
int
i
=
0
;
i
<
countryIdsStr
.
length
;
i
++)
{
try
{
countryIdsInt
[
i
]
=
Integer
.
parseInt
(
countryIdsStr
[
i
].
trim
());
}
catch
(
NumberFormatException
e
)
{
throw
new
IllegalArgumentException
(
"Invalid country ID: "
+
countryIdsStr
[
i
],
e
);
}
}
convert
.
setCountryIds
(
countryIdsInt
);
}
return
success
(
convert
);
}
@GetMapping
(
"/list"
)
...
...
@@ -107,7 +123,7 @@ public class SmsNodeController {
public
CommonResult
<
Boolean
>
test
(
@Valid
@RequestBody
SmsNodeTestVO
smsNodeTestVO
)
{
smsSendService
.
sendSingleSmsV2
(
smsNodeTestVO
.
getMobile
(),
SecurityFrameworkUtils
.
getLoginUserId
(),
UserTypeEnum
.
ADMIN
.
getValue
(),
smsNodeTestVO
.
getNodeValue
(),
smsNodeTestVO
.
getCountryCode
(),
smsNodeTestVO
.
getIsOrders
(),
smsNodeTestVO
.
getIsTransport
(),
smsNodeTestVO
.
getTransportId
(),
smsNodeTestVO
.
getMessageType
(),
smsNodeTestVO
.
getTemplateParams
(),
null
,
smsNodeTestVO
.
getNodeTemplateSn
());
smsNodeTestVO
.
getMessageType
(),
smsNodeTestVO
.
getTemplateParams
(),
null
,
smsNodeTestVO
.
getNodeTemplateSn
()
,
null
);
return
success
(
true
);
}
}
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sms/vo/smsNode/SmsNodeBackVO.java
View file @
b5d2fcc1
package
cn
.
iocoder
.
yudao
.
module
.
system
.
controller
.
admin
.
sms
.
vo
.
smsNode
;
import
lombok.*
;
import
java.util.*
;
import
io.swagger.annotations.*
;
import
com.alibaba.excel.annotation.ExcelProperty
;
import
cn.iocoder.yudao.framework.excel.annotations.DictFormat
;
import
cn.iocoder.yudao.framework.excel.convert.DictConvert
;
import
com.alibaba.excel.annotation.ExcelProperty
;
import
io.swagger.annotations.ApiModel
;
import
io.swagger.annotations.ApiModelProperty
;
import
lombok.Data
;
import
org.springframework.format.annotation.DateTimeFormat
;
import
java.util.Date
;
import
static
cn
.
iocoder
.
yudao
.
framework
.
common
.
util
.
date
.
DateUtils
.
FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND
;
/**
...
...
@@ -36,12 +36,16 @@ public class SmsNodeBackVO {
@ExcelProperty
(
"国家区号"
)
@ApiModelProperty
(
value
=
"国家区号"
,
required
=
true
)
private
Lo
ng
countryId
;
private
Stri
ng
countryId
;
@ExcelProperty
(
"国家区号"
)
@ApiModelProperty
(
value
=
"国家区号,和订单中国家区号保持一致"
,
required
=
true
)
private
String
countryCode
;
@ExcelProperty
(
"国家区号"
)
@ApiModelProperty
(
value
=
"国家区号,和订单中国家区号保持一致"
,
required
=
true
)
private
int
[]
countryIds
;
@ExcelProperty
(
"启用状态"
)
@ApiModelProperty
(
value
=
"启用状态(0:启用,1:关闭)"
,
required
=
true
)
private
Integer
status
;
...
...
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sms/vo/smsNode/SmsNodeBaseVO.java
View file @
b5d2fcc1
package
cn
.
iocoder
.
yudao
.
module
.
system
.
controller
.
admin
.
sms
.
vo
.
smsNode
;
import
com.alibaba.excel.annotation.ExcelProperty
;
import
lombok.*
;
import
java.util.*
;
import
io.swagger.annotations.*
;
import
javax.validation.constraints.*
;
import
io.swagger.annotations.ApiModelProperty
;
import
lombok.Data
;
import
javax.validation.constraints.NotNull
;
/**
* 短信节点 Base VO,提供给添加、修改、详细的子 VO 使用
...
...
@@ -23,7 +22,7 @@ public class SmsNodeBaseVO {
@ApiModelProperty
(
value
=
"国家区号id"
,
required
=
true
)
@NotNull
(
message
=
"国家区号id不能为空"
)
private
Lo
ng
countryId
;
private
Stri
ng
countryId
;
@ApiModelProperty
(
value
=
"国家区号,和订单中国家区号保持一致"
,
required
=
true
)
@NotNull
(
message
=
"国家区号,和订单中国家区号保持一致不能为空"
)
...
...
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sms/vo/smsNode/SmsNodeQueryVO.java
View file @
b5d2fcc1
...
...
@@ -21,7 +21,7 @@ public class SmsNodeQueryVO extends PageParam{
private
Long
transportId
;
@ApiModelProperty
(
value
=
"国家区号id"
)
private
Lo
ng
countryId
;
private
Stri
ng
countryId
;
@ApiModelProperty
(
value
=
"国家区号,和订单中国家区号保持一致"
)
private
String
countryCode
;
...
...
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sms/vo/smsNode/SmsNodeTestVO.java
View file @
b5d2fcc1
...
...
@@ -15,6 +15,9 @@ import java.util.Map;
@Data
public
class
SmsNodeTestVO
{
@ApiModelProperty
(
value
=
"id"
)
private
Long
id
;
@ApiModelProperty
(
value
=
"节点"
,
required
=
true
)
@NotNull
(
message
=
"节点不能为空"
)
private
String
nodeValue
;
...
...
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/sms/SmsNodeDO.java
View file @
b5d2fcc1
package
cn
.
iocoder
.
yudao
.
module
.
system
.
dal
.
dataobject
.
sms
;
import
lombok.*
;
import
java.util.*
;
import
com.baomidou.mybatisplus.annotation.*
;
import
cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO
;
import
com.baomidou.mybatisplus.annotation.FieldStrategy
;
import
com.baomidou.mybatisplus.annotation.TableField
;
import
com.baomidou.mybatisplus.annotation.TableId
;
import
com.baomidou.mybatisplus.annotation.TableName
;
import
lombok.*
;
/**
* 短信节点 DO
...
...
@@ -36,7 +38,7 @@ public class SmsNodeDO extends BaseDO {
/**
* 国家区号id
*/
private
Lo
ng
countryId
;
private
Stri
ng
countryId
;
/**
* 国家区号,和订单中国家区号保持一致
*/
...
...
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsCodeServiceImpl.java
View file @
b5d2fcc1
...
...
@@ -41,7 +41,7 @@ public class SmsCodeServiceImpl implements SmsCodeService {
String
code
=
createSmsCode
(
reqDTO
.
getMobile
(),
reqDTO
.
getNodeValue
(),
reqDTO
.
getCreateIp
());
// 发送验证码
smsSendService
.
sendSingleSmsV2
(
reqDTO
.
getAreaCode
()
+
reqDTO
.
getMobile
(),
null
,
null
,
reqDTO
.
getNodeValue
(),
reqDTO
.
getAreaCode
(),
reqDTO
.
getIsOrders
(),
reqDTO
.
getIsTransport
(),
reqDTO
.
getTransportId
(),
reqDTO
.
getMessageType
(),
MapUtil
.
of
(
"code"
,
code
),
null
,
1
);
reqDTO
.
getTransportId
(),
reqDTO
.
getMessageType
(),
MapUtil
.
of
(
"code"
,
code
),
null
,
1
,
null
);
}
private
String
createSmsCode
(
String
mobile
,
String
nodeValue
,
String
ip
)
{
...
...
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsNodeServiceImpl.java
View file @
b5d2fcc1
...
...
@@ -24,8 +24,7 @@ import org.springframework.validation.annotation.Validated;
import
javax.annotation.PostConstruct
;
import
javax.annotation.Resource
;
import
java.util.Collection
;
import
java.util.List
;
import
java.util.*
;
import
static
cn
.
iocoder
.
yudao
.
framework
.
apollo
.
core
.
constants
.
CacheConstants
.
SYSTEM_SMS_NODE_KEY
;
import
static
cn
.
iocoder
.
yudao
.
framework
.
common
.
exception
.
util
.
ServiceExceptionUtil
.
exception
;
...
...
@@ -74,15 +73,7 @@ public class SmsNodeServiceImpl extends AbstractService<SmsNodeMapper, SmsNodeDO
// 从数据库中选择所有短信节点数据对象
List
<
SmsNodeDO
>
smsNodeDOList
=
smsNodeMapper
.
selectList
();
for
(
SmsNodeDO
smsNodeDO
:
smsNodeDOList
)
{
// 构建缓存键
String
key
=
buildCacheKey
(
smsNodeDO
);
// 将对象转换为JSON字符串作为缓存值
String
value
=
JSON
.
toJSONString
(
smsNodeDO
);
// 将键值对存入Redis缓存
redisHelper
.
set
(
key
,
value
);
redisHelper
.
set
(
SYSTEM_SMS_NODE_KEY
+
smsNodeDO
.
getId
(),
value
);
// 记录日志信息
// log.info("Cache key: {}, value: {}", key, value);
createCache
(
smsNodeDO
);
}
}
catch
(
Exception
e
)
{
// 记录错误日志信息
...
...
@@ -164,7 +155,7 @@ public class SmsNodeServiceImpl extends AbstractService<SmsNodeMapper, SmsNodeDO
// 调用短信发送服务重新发送短信
smsSendService
.
sendSingleSmsV2
(
smsLog
.
getMobile
(),
smsLog
.
getUserId
(),
smsLog
.
getUserType
(),
smsNodeDO
.
getNodeValue
(),
smsNodeDO
.
getCountryCode
(),
smsNodeDO
.
getIsOrders
(),
smsNodeDO
.
getIsTransport
(),
smsNodeDO
.
getTransportId
(),
smsLog
.
getMessageType
(),
smsLog
.
getTemplateParams
(),
smsLog
.
getId
(),
smsLog
.
getNodeTemplateSn
()
+
1
);
smsLog
.
getMessageType
(),
smsLog
.
getTemplateParams
(),
smsLog
.
getId
(),
smsLog
.
getNodeTemplateSn
()
+
1
,
smsNodeDO
);
}
}
...
...
@@ -192,13 +183,48 @@ public class SmsNodeServiceImpl extends AbstractService<SmsNodeMapper, SmsNodeDO
// 插入新的短信节点到数据库
smsNodeMapper
.
insert
(
smsNode
);
// 设置新的缓存
redisHelper
.
set
(
buildCacheKey
(
smsNode
),
JSON
.
toJSONString
(
smsNode
));
redisHelper
.
set
(
SYSTEM_SMS_NODE_KEY
+
smsNode
.
getId
(),
JSON
.
toJSONString
(
smsNode
));
createCache
(
smsNode
);
// 返回新创建的短信节点的ID
return
smsNode
.
getId
();
}
/**
* 创建缓存
*/
public
void
createCache
(
SmsNodeDO
smsNode
)
{
String
[]
countryCodesStr
=
smsNode
.
getCountryCode
().
split
(
","
);
Map
<
String
,
String
>
maps
=
new
HashMap
<>();
for
(
String
s
:
countryCodesStr
)
{
smsNode
.
setCountryCode
(
s
);
// 构建缓存键
String
key
=
buildCacheKey
(
smsNode
);
// 将对象转换为JSON字符串作为缓存值
String
value
=
JSON
.
toJSONString
(
smsNode
);
maps
.
put
(
key
,
value
);
}
// 将键值对存入Redis缓存
redisHelper
.
multiSet
(
maps
);
redisHelper
.
set
(
SYSTEM_SMS_NODE_KEY
+
smsNode
.
getId
(),
JSON
.
toJSONString
(
smsNode
));
}
/**
* 删除缓存
*/
public
void
deleteCache
(
SmsNodeDO
smsNode
)
{
String
[]
countryCodesStr
=
smsNode
.
getCountryCode
().
split
(
","
);
Collection
<
String
>
keys
=
new
ArrayList
<>();
for
(
String
s
:
countryCodesStr
)
{
smsNode
.
setCountryCode
(
s
);
// 构建缓存键
String
key
=
buildCacheKey
(
smsNode
);
keys
.
add
(
key
);
}
// 将键值对存入Redis缓存
redisHelper
.
delete
(
keys
);
redisHelper
.
delete
(
SYSTEM_SMS_NODE_KEY
+
smsNode
.
getId
());
}
/**
* 更新短信节点信息
*
...
...
@@ -220,10 +246,9 @@ public class SmsNodeServiceImpl extends AbstractService<SmsNodeMapper, SmsNodeDO
// 更新数据库中的短信节点信息
smsNodeMapper
.
updateById
(
updateObj
);
// 删除旧的缓存
redisHelper
.
delete
(
buildCacheKey
(
smsNodeDO
));
// 设置新的缓存
redisHelper
.
set
(
buildCacheKey
(
updateObj
),
JSON
.
toJSONString
(
updateObj
));
redisHelper
.
set
(
SYSTEM_SMS_NODE_KEY
+
updateObj
.
getId
(),
JSON
.
toJSONString
(
updateObj
));
deleteCache
(
smsNodeDO
);
// 创建新的缓存
createCache
(
updateObj
);
}
@Override
...
...
@@ -233,8 +258,7 @@ public class SmsNodeServiceImpl extends AbstractService<SmsNodeMapper, SmsNodeDO
// 删除
smsNodeMapper
.
deleteById
(
id
);
// 删除缓存中的短信节点信息
redisHelper
.
delete
(
buildCacheKey
(
smsNodeDO
));
redisHelper
.
delete
(
SYSTEM_SMS_NODE_KEY
+
id
);
deleteCache
(
smsNodeDO
);
}
private
SmsNodeDO
validateSmsNodeExists
(
Long
id
)
{
...
...
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsSendService.java
View file @
b5d2fcc1
package
cn
.
iocoder
.
yudao
.
module
.
system
.
service
.
sms
;
import
cn.iocoder.yudao.framework.common.validation.Mobile
;
import
cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsNodeDO
;
import
cn.iocoder.yudao.module.system.mq.message.sms.SmsSendMessage
;
import
cn.iocoder.yudao.module.system.mq.message.sms.SmsSendMessageV2
;
...
...
@@ -113,10 +114,11 @@ public interface SmsSendService {
* @param templateParams 短信模板参数
* @param smsLogId 重发短信时的原短信日志id
* @param nodeTemplateSn 模板序号
* @param smsNode 节点对象
*/
Long
sendSingleSmsV2
(
@Mobile
@NotEmpty
(
message
=
"手机号不能为空"
)
String
mobile
,
Long
userId
,
Integer
userType
,
@NotNull
(
message
=
"业务节点不能为空"
)
String
nodeValue
,
@NotNull
(
message
=
"国家区号不能为空"
)
String
areaCode
,
@NotNull
(
message
=
"是否多订单不能为空"
)
Integer
isOrders
,
@NotNull
(
message
=
"是否匹配运输方式不能为空"
)
Integer
isTransport
,
@NotNull
(
message
=
"运输方式不能空"
)
Integer
transportId
,
@NotNull
(
message
=
"发送类型不能为空"
)
Integer
messageType
,
Map
<
String
,
Object
>
templateParams
,
Long
smsLogId
,
Integer
nodeTemplateSn
);
@NotNull
(
message
=
"运输方式不能空"
)
Integer
transportId
,
@NotNull
(
message
=
"发送类型不能为空"
)
Integer
messageType
,
Map
<
String
,
Object
>
templateParams
,
Long
smsLogId
,
Integer
nodeTemplateSn
,
SmsNodeDO
smsNode
);
void
resendSingleSmsBySmsLogIds
(
Collection
<
Long
>
smsLogIds
);
...
...
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsSendServiceImpl.java
View file @
b5d2fcc1
...
...
@@ -102,7 +102,7 @@ public class SmsSendServiceImpl implements SmsSendService {
}
}
// 执行发送
return
this
.
sendSingleSmsV2
(
areaCode
+
mobile
,
userId
,
UserTypeEnum
.
ADMIN
.
getValue
(),
nodeValue
,
areaCode
,
isOrders
,
isTransport
,
transportId
,
messageType
,
templateParams
,
null
,
1
);
return
this
.
sendSingleSmsV2
(
areaCode
+
mobile
,
userId
,
UserTypeEnum
.
ADMIN
.
getValue
(),
nodeValue
,
areaCode
,
isOrders
,
isTransport
,
transportId
,
messageType
,
templateParams
,
null
,
1
,
null
);
}
@Override
...
...
@@ -131,7 +131,7 @@ public class SmsSendServiceImpl implements SmsSendService {
}
}
// 执行发送
return
this
.
sendSingleSmsV2
(
areaCode
+
mobile
,
userId
,
UserTypeEnum
.
MEMBER
.
getValue
(),
nodeValue
,
areaCode
,
isOrders
,
isTransport
,
transportId
,
messageType
,
templateParams
,
null
,
1
);
return
this
.
sendSingleSmsV2
(
areaCode
+
mobile
,
userId
,
UserTypeEnum
.
MEMBER
.
getValue
(),
nodeValue
,
areaCode
,
isOrders
,
isTransport
,
transportId
,
messageType
,
templateParams
,
null
,
1
,
null
);
}
@Override
...
...
@@ -314,10 +314,13 @@ public class SmsSendServiceImpl implements SmsSendService {
* @param templateParams 短信模板参数
* @param smsLogId 重发短信时的原短信日志id
* @param nodeTemplateSn 模板序号
* @param smsNode 节点对象
*/
@Override
public
Long
sendSingleSmsV2
(
String
mobile
,
Long
userId
,
Integer
userType
,
String
nodeValue
,
String
areaCode
,
Integer
isOrders
,
Integer
isTransport
,
Integer
transportId
,
Integer
messageType
,
Map
<
String
,
Object
>
templateParams
,
Long
smsLogId
,
Integer
nodeTemplateSn
)
{
Integer
isOrders
,
Integer
isTransport
,
Integer
transportId
,
Integer
messageType
,
Map
<
String
,
Object
>
templateParams
,
Long
smsLogId
,
Integer
nodeTemplateSn
,
SmsNodeDO
smsNode
)
{
//首次发送
if
(
smsNode
==
null
)
{
// 创建SmsNodeDO对象并设置相关属性
SmsNodeDO
smsNodeDO
=
new
SmsNodeDO
()
.
setNodeValue
(
nodeValue
)
...
...
@@ -348,9 +351,9 @@ public class SmsSendServiceImpl implements SmsSendService {
if
(
smsNodeCache
==
null
)
{
throw
exception
(
SMS_SEND_TEMPLATE_NOT_EXISTS
);
}
// 解析缓存获取短信节点信息
SmsNodeDO
smsNode
=
JSON
.
parseObject
(
smsNodeCache
,
SmsNodeDO
.
class
);
smsNode
=
JSON
.
parseObject
(
smsNodeCache
,
SmsNodeDO
.
class
);
}
String
smsTemplateDO
=
null
;
// 从缓存中获取短信模板信息
...
...
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