Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
杨树贤
/
search_server
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Settings
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
66d3adc7
authored
Jul 21, 2020
by
mushishixian
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
商品固定顺序
parent
4714c1e0
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
206 additions
and
49 deletions
conf/search.ini
controller/quote_controller.go
go.mod
model/goods.go
pkg/common/function.go
pkg/common/response.go
pkg/log_report/log_report.go
pkg/message/dingding.go
pkg/mysql/mysql.go
service/goods_service.go
service/transformer/goods_transformer.go
test/test.go
conf/search.ini
View file @
66d3adc7
...
...
@@ -150,5 +150,19 @@ Index = liexin_ziying
1678
=
https://estore.heilindasia.com/search.asp?p=liexin
1680
=
http://shop.wpgam.com/WPGAStore/index.php?route=product/search&filter_description=true&filter_name=liexin
[AUTH]
SUPER_AUTH_KEY
=
fh6y5t4rr351d2c3bryi
SEARCH_TOKEN_EXPIRE_TIME
=
30
\ No newline at end of file
SEARCH_TOKEN_EXPIRE_TIME
=
30
[UNIT_SUPPLIER_LOG_CODE]
333333
=
arrow
444444
=
master
555555
=
mouser
666666
=
verical
777777
=
tme
888888
=
buerklin
999999
=
zhuanmai
[SEARCH_API_LOG]
SEARCH_API_ERROR_PRE
=
search_api_overtime_
\ No newline at end of file
controller/quote_controller.go
View file @
66d3adc7
package
controller
import
(
"fmt"
"github.com/gin-gonic/gin"
"github.com/gomodule/redigo/redis"
"github.com/ichunt2019/logger"
"github.com/syyongx/php2go"
"search_server/dao"
"search_server/pkg/common"
...
...
@@ -21,13 +21,13 @@ func QuoteIndex(c *gin.Context) {
var
r
requests
.
QuoteIndexRequest
//绑定参数到结构体
if
err
:=
c
.
ShouldBind
(
&
r
);
err
!=
nil
{
fmt
.
Println
(
err
)
logger
.
Error
(
"%s"
,
err
)
}
quoteService
:=
service
.
QuoteService
{}
//检查参数,有错误就返回错误
errCode
,
errMsg
:=
requests
.
CheckQuoteRequest
(
r
)
if
errCode
!=
0
{
c
.
JSON
(
200
,
common
.
ErrorResponse
(
errCode
,
errMsg
)
)
c
ommon
.
Output
(
errCode
,
errMsg
,
nil
)
return
}
flag
:=
1
...
...
@@ -37,26 +37,27 @@ func QuoteIndex(c *gin.Context) {
if
len
(
res
)
>
0
{
flag
=
0
}
c
.
JSON
(
200
,
common
.
SuccessResponse
(
flag
,
""
,
res
)
)
c
ommon
.
Output
(
flag
,
""
,
res
)
return
}
//获取供应商名称
supplierNameMap
:=
config
.
Cfg
.
Section
(
"SUPPLIER_ALL"
)
.
KeysHash
()
supplierName
,
exist
:=
supplierNameMap
[
r
.
SupplierId
]
if
!
exist
{
c
.
JSON
(
200
,
common
.
ErrorResponse
(
1
,
"供应商有误"
)
)
c
ommon
.
Output
(
1
,
"供应商有误"
,
nil
)
return
}
if
supplierName
==
"mouser"
{
//mouser同步请求搜索
res
,
_
=
mouserOrg
(
r
.
Keyword
,
r
.
SupplierId
)
}
else
{
r
.
GetGoodsBySupplierRequest
.
SupplierName
=
supplierName
res
,
_
=
quoteService
.
GetGoodsBySupplierName
(
r
.
GetGoodsBySupplierRequest
)
}
if
len
(
res
)
>
0
{
flag
=
0
}
c
.
JSON
(
200
,
common
.
SuccessResponse
(
flag
,
""
,
res
)
)
c
ommon
.
Output
(
flag
,
""
,
res
)
return
}
...
...
@@ -91,22 +92,11 @@ func mouserOrg(keyword, suppliedId string) (result map[string]interface{}, err e
return
}
//获取内部编码对应关系
func
GetDataPur
(
c
*
gin
.
Context
)
{
supplierData
,
err
:=
dao
.
GetAllSupplierCodeAndUid
()
if
err
!=
nil
{
c
.
String
(
200
,
err
.
Error
())
return
}
intracode
,
err
:=
dao
.
GetAllIntraCode
()
if
err
!=
nil
{
c
.
String
(
200
,
err
.
Error
())
return
}
userInfo
,
err
:=
dao
.
GetAllUserBaseInfo
()
if
err
!=
nil
{
c
.
String
(
200
,
err
.
Error
())
return
}
supplierData
,
_
:=
dao
.
GetAllSupplierCodeAndUid
()
intracode
,
_
:=
dao
.
GetAllIntraCode
()
userInfo
,
_
:=
dao
.
GetAllUserBaseInfo
()
temp
:=
make
(
map
[
int
]
string
)
for
_
,
user
:=
range
userInfo
{
if
user
.
Name
==
""
&&
user
.
Email
!=
""
{
...
...
@@ -129,7 +119,7 @@ func GetDataPur(c *gin.Context) {
for
_
,
supplier
:=
range
supplierData
{
if
value
,
exist
:=
temp2
[
supplier
.
ChannelUid
];
exist
{
res
,
err
:=
redis
.
Bool
(
redisCon
.
Do
(
"HSET"
,
"search_supplier_canaltopurchase
_test
"
,
supplier
.
SupplierCode
,
value
))
res
,
err
:=
redis
.
Bool
(
redisCon
.
Do
(
"HSET"
,
"search_supplier_canaltopurchase"
,
supplier
.
SupplierCode
,
value
))
if
err
!=
nil
||
!
res
{
c
.
String
(
200
,
"失败"
)
return
...
...
go.mod
View file @
66d3adc7
...
...
@@ -25,7 +25,7 @@ require (
github.com/prometheus/client_golang v1.5.1 // indirect
github.com/prometheus/common v0.10.0
github.com/prometheus/procfs v0.0.11 // indirect
github.com/sirupsen/logrus v1.5.0
// indirect
github.com/sirupsen/logrus v1.5.0
github.com/smartystreets/goconvey v1.6.4 // indirect
github.com/stretchr/testify v1.5.1 // indirect
github.com/syyongx/php2go v0.9.4
...
...
model/goods.go
View file @
66d3adc7
...
...
@@ -7,6 +7,7 @@ type ApiGoods struct {
GoodsName
string
`json:"goods_name"`
GoodsSn
string
`json:"goods_sn"`
GoodsType
int
`json:"goods_type"`
GoodsStatus
int
`json:"goods_status"`
SupplierId
int
`json:"supplier_id"`
Moq
int
`json:"-"`
MoqStr
interface
{}
`json:"moq"`
...
...
@@ -58,14 +59,15 @@ type ApiGoods struct {
Coefficient
Coefficient
`json:"coefficient,omitempty"`
OriginalPrice
[]
OriginalPrice
`json:"original_price,omitempty"`
SuppExtendFee
SuppExtendFee
`json:"supp_extend_fee,omitempty"`
ClassId3
int
`json:"class_id3
,omitempty
"`
ClassId3
int
`json:"class_id3"`
SpuName
string
`json:"spu_name,omitempty"`
ImagesL
string
`json:"images_l,omitempty"`
SpuBrief
string
`json:"spu_brief"`
SpuDetail
string
`json:"spu_detail
,omitempty
"`
ClassName3
string
`json:"class_name3
,omitempty
"`
SpuDetail
string
`json:"spu_detail"`
ClassName3
string
`json:"class_name3"`
ErpTax
bool
`json:"erp_tax"`
ScmBrand
ScmBrand
`json:"scm_brand,omitempty"`
ActivityEndTime
int64
`json:"activity_end_time,omitempty"`
}
//经过处理后的商品数据
...
...
pkg/common/function.go
View file @
66d3adc7
...
...
@@ -254,7 +254,7 @@ func auth() bool {
k1
:=
request
[
"k1"
]
k1Num
,
_
:=
strconv
.
Atoi
(
k1
)
k2
:=
request
[
"k2"
]
key
:=
config
.
Get
(
"SUPER_AUTH_KEY"
)
.
String
()
key
:=
config
.
Get
(
"
AUTH.
SUPER_AUTH_KEY"
)
.
String
()
if
k1
!=
""
&&
int64
(
k1Num
)
<
(
time
.
Now
()
.
Unix
()
-
600
)
{
return
false
}
...
...
pkg/common/response.go
View file @
66d3adc7
package
common
import
(
"regexp"
"runtime"
"search_server/middleware"
"search_server/pkg/log_report"
"strconv"
)
type
Response
struct
{
ErrCode
int
`json:"error_code"`
ErrMsg
string
`json:"err_msg"`
...
...
@@ -28,4 +36,44 @@ func ErrorResponse(errCode int, errMsg string) Response {
ErrMsg
:
errMsg
,
Data
:
[]
string
{},
}
}
\ No newline at end of file
}
//统一输出,里面还要去处理jsonp
func
Output
(
errCode
int
,
errMsg
string
,
data
interface
{})
{
if
data
==
nil
{
data
=
[]
string
{}
}
response
:=
Response
{
ErrCode
:
errCode
,
ErrMsg
:
errMsg
,
Data
:
data
,
}
ctx
:=
middleware
.
Context
if
ctx
.
DefaultQuery
(
"callback"
,
""
)
!=
""
{
ctx
.
JSONP
(
200
,
response
)
}
else
{
referer
:=
ctx
.
Request
.
Referer
()
r
,
_
:=
regexp
.
Compile
(
`/^(\w+:\/\/)?([^\/]+)/i`
)
var
match
string
matches
:=
r
.
FindStringSubmatch
(
referer
)
if
len
(
matches
)
>
0
{
match
=
matches
[
0
]
}
ctx
.
Header
(
"Access-Control-Allow-Origin"
,
match
)
ctx
.
Header
(
"Access-Control-Allow-Credentials"
,
"true"
)
//允许跨站访问的站点域名
//跨域请求头设置
ctx
.
JSONP
(
200
,
response
)
}
}
//错误的搜索日志记录
func
SearchApiLog
(
code
int
,
msg
string
)
{
pc
,
file
,
line
,
_
:=
runtime
.
Caller
(
0
)
f
:=
runtime
.
FuncForPC
(
pc
)
ctx
:=
middleware
.
Context
errMsg
:=
"提示信息:"
+
msg
+
",请求url:"
+
ctx
.
Request
.
URL
.
String
()
lineNo
:=
strconv
.
Itoa
(
line
)
log_report
.
AnalyzeSearchError
(
code
,
ctx
.
ClientIP
(),
errMsg
,
file
,
lineNo
,
f
.
Name
())
}
pkg/log_report/log_report.go
0 → 100644
View file @
66d3adc7
package
log_report
import
(
"encoding/json"
log
"github.com/sirupsen/logrus"
"os"
)
type
SearchApiLog
struct
{
Msg
string
MsgCode
int
Ts
int64
DateStr
string
App
string
ServerIp
string
FileName
string
LineNo
string
Method
string
}
func
AnalyzeSearchError
(
code
int
,
ip
,
errMsg
,
file
,
line
,
method
string
)
(
log
SearchApiLog
)
{
//log.Msg = "" + errMsg
//log.MsgCode = 1
//log.Ts = time.Now().Unix()
//log.DateStr = time.Now().Format("2006-01-02 15:04:05")
//log.App = "search_api"
//log.ServerIp = ip
//log.FileName = file
//log.LineNo = line
//log.Method = method
//
////还要判断是否要推送消息
//unitSupplierLogCode := config.Cfg.Section("UNIT_SUPPLIER_LOG_CODE").KeysHash()
//codeStr := strconv.Itoa(code)
//if unitSupplierLogCode[codeStr] == "" {
// if !strings.Contains(strings.ToUpper(errMsg), "SOAP-ERROR") {
// //推送到钉钉
// result, err := message.DingDingPush(errMsg)
// if err != nil || result.Errcode != 0 {
// //logger.Errorf("%s", err, result)
// }
// }
//} else {
// redisCon := gredis.Conn("search_w")
// defer redisCon.Close()
// key := config.Get("SEARCH_API_LOG.SEARCH_API_ERROR_PRE").String() + unitSupplierLogCode[codeStr]
// redisCon.Do("INCR", key)
//}
return
}
type
LogFormatter
struct
{}
//格式详情
func
(
s
*
LogFormatter
)
Format
(
entry
*
log
.
Entry
)
([]
byte
,
error
)
{
msg
:=
entry
.
Message
return
[]
byte
(
msg
),
nil
}
//写入到特定的文件夹给ELK使用的日志
func
WriteSearchErrorLog
(
searchLog
SearchApiLog
)
{
logJson
,
_
:=
json
.
Marshal
(
searchLog
)
// 设置日志格式为json格式
log
.
SetFormatter
(
new
(
LogFormatter
))
//设置output,默认为stderr,可以为任何io.Writer,比如文件*os.File
file
,
_
:=
os
.
OpenFile
(
"1.log"
,
os
.
O_CREATE
|
os
.
O_WRONLY
,
0666
)
log
.
SetOutput
(
file
)
log
.
Error
(
logJson
)
}
func
WriteSearchErrorLogTest
(
searchLog
string
)
{
// 设置日志格式为json格式
log
.
SetFormatter
(
new
(
LogFormatter
))
//设置output,默认为stderr,可以为任何io.Writer,比如文件*os.File
file
,
_
:=
os
.
OpenFile
(
"1.log"
,
os
.
O_CREATE
|
os
.
O_WRONLY
,
0666
)
log
.
SetOutput
(
file
)
log
.
Error
(
searchLog
)
}
pkg/message/dingding.go
View file @
66d3adc7
...
...
@@ -2,7 +2,6 @@ package message
import
(
"encoding/json"
"fmt"
"github.com/imroc/req"
"github.com/tidwall/gjson"
"search_server/pkg/config"
...
...
@@ -32,7 +31,6 @@ func DingDingPush(content string) (result DingDingResponse, err error) {
req
.
Debug
=
false
dataStrByte
,
_
:=
json
.
Marshal
(
data
)
dataStr
:=
string
(
dataStrByte
)
fmt
.
Println
(
dataStr
)
dataStr
=
strings
.
Replace
(
dataStr
,
"
\\
"
,
"
\\\\
"
,
-
1
)
params
:=
req
.
BodyJSON
(
dataStr
)
resp
,
err
:=
req
.
Post
(
webhook
,
params
,
req
.
Header
{
...
...
pkg/mysql/mysql.go
View file @
66d3adc7
...
...
@@ -29,7 +29,7 @@ func Setup() error {
}
//日志打印SQL
DatabaseConMap
[
conName
]
.
ShowSQL
(
tru
e
)
DatabaseConMap
[
conName
]
.
ShowSQL
(
fals
e
)
//设置连接池的空闲数大小
DatabaseConMap
[
conName
]
.
SetMaxIdleConns
(
db
.
MaxIdleCons
)
...
...
service/goods_service.go
View file @
66d3adc7
...
...
@@ -13,7 +13,7 @@ import (
)
//获取商品信息,需要传入userId用于判断是否登陆
func
GetGoodsInfo
(
goodsIds
[]
string
)
(
goodsList
Map
map
[
string
]
model
.
ApiGoods
,
err
error
)
{
func
GetGoodsInfo
(
goodsIds
[]
string
)
(
goodsList
[
]
model
.
ApiGoods
,
err
error
)
{
var
userIdStr
string
userIdStr
,
_
=
middleware
.
Context
.
Cookie
(
"Yo4teW_uid"
)
userId
,
_
:=
strconv
.
Atoi
(
userIdStr
)
...
...
@@ -24,7 +24,7 @@ func GetGoodsInfo(goodsIds []string) (goodsListMap map[string]model.ApiGoods, er
"power[newCustomer]"
:
isNewCustomer
,
"power[member]"
:
isMember
,
}
_
,
goodsListMap
,
err
=
CurlGoodsInfo
(
goodsIdsStr
,
params
)
goodsList
,
_
,
err
=
CurlGoodsInfo
(
goodsIdsStr
,
params
)
return
}
...
...
@@ -63,6 +63,7 @@ func CurlGoodsInfo(goodsIdsStr string, params req.Param) (goodsList []model.ApiG
goods
.
GoodsName
=
data
.
Get
(
"goods_name"
)
.
String
()
goods
.
GoodsSn
=
data
.
Get
(
"goods_sn"
)
.
String
()
goods
.
GoodsType
=
int
(
data
.
Get
(
"goods_type"
)
.
Int
())
goods
.
GoodsStatus
=
int
(
data
.
Get
(
"goods_status"
)
.
Int
())
goods
.
SupplierId
=
int
(
data
.
Get
(
"supplier_id"
)
.
Int
())
goods
.
Mpq
=
int
(
data
.
Get
(
"mpq"
)
.
Int
())
goods
.
MpqStr
=
int
(
data
.
Get
(
"mpq"
)
.
Int
())
...
...
@@ -154,7 +155,7 @@ func CurlGoodsInfo(goodsIdsStr string, params req.Param) (goodsList []model.ApiG
//original_price
originalPrice
:=
make
([]
model
.
OriginalPrice
,
0
)
for
_
,
price
:=
range
data
.
Get
(
"
ladder
_price"
)
.
Array
()
{
for
_
,
price
:=
range
data
.
Get
(
"
original
_price"
)
.
Array
()
{
price
:=
model
.
OriginalPrice
{
Purchases
:
int
(
price
.
Get
(
"purchases"
)
.
Int
()),
PriceUs
:
price
.
Get
(
"price_us"
)
.
Float
(),
...
...
@@ -174,6 +175,7 @@ func CurlGoodsInfo(goodsIdsStr string, params req.Param) (goodsList []model.ApiG
goods
.
ScmBrand
.
ErpBrandName
=
data
.
Get
(
"scm_brand.erp_brand_name"
)
.
String
()
goods
.
ScmBrand
.
ErpBrandId
=
data
.
Get
(
"scm_brand.erp_brand_id"
)
.
String
()
goods
.
ScmBrand
.
ScmBrandId
=
data
.
Get
(
"scm_brand.scm_brand_id"
)
.
String
()
goods
.
ActivityEndTime
=
data
.
Get
(
"activity_end_time"
)
.
Int
()
goodsList
=
append
(
goodsList
,
goods
)
goodsListMap
[
goodsId
]
=
goods
...
...
service/transformer/goods_transformer.go
View file @
66d3adc7
...
...
@@ -2,7 +2,6 @@ package transformer
import
(
"encoding/json"
"fmt"
"github.com/gomodule/redigo/redis"
"github.com/tidwall/gjson"
"regexp"
...
...
@@ -12,16 +11,17 @@ import (
"search_server/pkg/common"
"search_server/pkg/config"
"search_server/pkg/gredis"
"search_server/pkg/mq"
"strings"
"time"
)
//处理商品信息数据
func
DullDataInfo
(
keyword
string
,
goodsList
Map
map
[
string
]
model
.
ApiGoods
)
(
result
[]
model
.
DullGoodsData
,
err
error
)
{
func
DullDataInfo
(
keyword
string
,
goodsList
[
]
model
.
ApiGoods
)
(
result
[]
model
.
DullGoodsData
,
err
error
)
{
//获取redis链接
redisCon
:=
gredis
.
Conn
(
"search_r"
)
defer
redisCon
.
Close
()
for
_
,
item
:=
range
goodsList
Map
{
for
_
,
item
:=
range
goodsList
{
var
goods
model
.
DullGoodsData
goods
.
ApiGoods
=
item
if
goods
.
GoodsName
==
""
{
...
...
@@ -40,7 +40,18 @@ func DullDataInfo(keyword string, goodsListMap map[string]model.ApiGoods) (resul
goods
=
spliceUrlByEnvironment
(
goods
)
//获取汇率相关
goods
=
getRatio
(
goods
)
//todo:提供给客服报价系统专用(因为只是给快手用的,所以抽离出这个方法)
//提供给客服报价系统专用
if
strings
.
Contains
(
middleware
.
Context
.
Request
.
URL
.
String
(),
"quote"
)
{
for
key
,
price
:=
range
goods
.
LadderPrice
{
if
price
.
PriceUs
!=
0
{
if
goods
.
SupplierId
==
6
{
goods
.
LadderPrice
[
key
]
.
PriceUs
=
goods
.
OriginalPrice
[
key
]
.
PriceUs
*
goods
.
PriceHkxs
}
else
{
goods
.
LadderPrice
[
key
]
.
PriceUs
=
goods
.
OriginalPrice
[
key
]
.
PriceUs
*
goods
.
Coefficient
.
ExtraRatio
}
}
}
}
//整理数据
goods
=
arrangeData
(
goods
)
...
...
@@ -50,7 +61,7 @@ func DullDataInfo(keyword string, goodsListMap map[string]model.ApiGoods) (resul
goods
.
LastSearchTime
=
common
.
GetLastSearchTime
(
goods
.
GoodsId
)
//关税信息
goods
.
CustomsTax
=
common
.
GetCustomsTax
(
goods
.
GoodsName
,
goods
.
BrandName
)
ShowSku
(
goods
)
go
ShowSku
(
goods
)
//价格等信息混淆
if
keyword
!=
"---"
{
goods
,
err
=
MetGoodsInfo
(
goods
)
...
...
@@ -71,7 +82,7 @@ func spliceUrlByEnvironment(goods model.DullGoodsData) model.DullGoodsData {
refererTemp
:=
strings
.
Split
(
referer
,
".com"
)
if
len
(
refererTemp
)
>
0
{
goods
.
BrandUrl
=
refererTemp
[
0
]
+
".com/v3/brand/list/"
+
common
.
ToString
(
goods
.
BrandId
)
+
".html"
goods
.
GoodsUrl
=
refererTemp
[
0
]
+
".com/goods_"
+
common
.
ToString
(
goods
.
GoodsId
)
+
".html"
goods
.
GoodsUrl
=
refererTemp
[
0
]
+
".com/goods_"
+
goods
.
GoodsId
+
".html"
}
return
goods
}
...
...
@@ -358,7 +369,6 @@ func ShowSku(goods model.DullGoodsData) {
}
}
res
,
_
:=
json
.
Marshal
(
result
)
fmt
.
Println
(
string
(
res
))
//mq.PushMsg("search_show_sku_list", string(res))
mq
.
PushMsg
(
"search_show_sku_list"
,
string
(
res
))
}
}
test/test.go
View file @
66d3adc7
...
...
@@ -3,10 +3,11 @@ package main
import
(
"flag"
"fmt"
log
"github.com/sirupsen/logrus"
"github.com/syyongx/php2go"
"os"
"reflect"
"search_server/boot"
"search_server/pkg/common"
)
func
StrRandom
(
lenNum
int
)
string
{
...
...
@@ -23,6 +24,14 @@ func StrRandom(lenNum int) string {
return
result
}
type
LogFormatter
struct
{}
//格式详情
func
(
s
*
LogFormatter
)
Format
(
entry
*
log
.
Entry
)
([]
byte
,
error
)
{
msg
:=
entry
.
Message
+
"
\n
"
return
[]
byte
(
msg
),
nil
}
func
main
()
{
var
path
string
flag
.
StringVar
(
&
path
,
"configPath"
,
"conf"
,
"配置文件"
)
...
...
@@ -30,9 +39,16 @@ func main() {
if
err
:=
boot
.
Boot
(
path
);
err
!=
nil
{
fmt
.
Println
(
err
)
}
b
:=
[]
string
{
"b"
,
"z"
,
"a"
}
common
.
SortSlice
(
b
)
fmt
.
Println
(
b
)
log
.
SetFormatter
(
new
(
LogFormatter
))
//设置output,默认为stderr,可以为任何io.Writer,比如文件*os.File
str
,
_
:=
os
.
Getwd
()
file
,
_
:=
os
.
OpenFile
(
str
+
"/logs/search/1.log"
,
os
.
O_CREATE
|
os
.
O_WRONLY
,
0666
)
log
.
SetOutput
(
file
)
fmt
.
Println
(
str
)
log
.
Error
(
"sadsadsadsa"
)
log
.
Error
(
"sadsadsadsa"
)
log
.
Error
(
"sadsadsadsa"
)
}
func
InterfaceSlice
(
slice
interface
{})
[]
interface
{}
{
...
...
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