package jxutils import ( "fmt" "math" "math/rand" "regexp" "strings" "sync" "time" "unicode/utf8" "github.com/fatih/structs" "git.rosy.net.cn/baseapi/platformapi/autonavi" "git.rosy.net.cn/baseapi/utils" "git.rosy.net.cn/baseapi/utils/routinepool" "git.rosy.net.cn/jx-callback/business/model" "git.rosy.net.cn/jx-callback/globals/api" ) var ( routinePool *routinepool.Pool skuNamePat *regexp.Regexp ) type SyncMapWithTimeout struct { sync.Map timers sync.Map } func init() { rand.Seed(time.Now().Unix()) routinePool = routinepool.New(1000, 1000) // Go regex does not support lookarounds. // https://stackoverflow.com/questions/38933898/error-parsing-regexp-invalid-or-unsupported-perl-syntax skuNamePat = regexp.MustCompile(`([\((\[【][^\((\[【\))\]】]*[\))\]】])?(.*?)([((].*[))])?\s*约?([1-9][\d\.]*)(g|G|kg|kG|Kg|KG|l|L|ml|mL|Ml|ML|克)\s*([((].*[))])?\s*(?:\/|/|)\s*([^\s()()]{0,2})\s*([((].*[))])?$`) } func (m *SyncMapWithTimeout) StoreWithTimeout(key, value interface{}, timeout time.Duration) { m.Map.Store(key, value) m.timers.Store(key, time.AfterFunc(timeout, func() { m.Delete(key) })) } func (m *SyncMapWithTimeout) Delete(key interface{}) { m.Map.Delete(key) if value, ok := m.timers.Load(key); ok { timer := value.(*time.Timer) timer.Stop() } m.timers.Delete(key) } func GetJxStoreIDFromOrder(order *model.GoodsOrder) (retVal int) { if order.JxStoreID != 0 { return order.JxStoreID } return order.StoreID } func SplitUniversalOrderID(universalOrderID string) (orderID string, vendorID int) { index := strings.Index(universalOrderID, "|") if index != -1 { orderID = universalOrderID[:index] vendorID = int(utils.Str2Int64(universalOrderID[index:])) } else { // 800402581000221 jd order // 3022716176275221584 elm order orderIDLen := len(universalOrderID) if orderIDLen == len("800402581000221") { vendorID = model.VendorIDJD } else if orderIDLen == len("3022716176275221584") { vendorID = model.VendorIDELM } else if orderIDLen == len("15380342248732") { vendorID = model.VendorIDEBAI } else if orderIDLen == len("33437032333978492") { vendorID = model.VendorIDMTWM } else { // globals.SugarLogger.Errorf("unkown order type:%v", universalOrderID) panic(fmt.Sprintf("unkown order type:%v, orderIDLen:%d", universalOrderID, orderIDLen)) vendorID = model.VendorIDUnknown } orderID = universalOrderID } return orderID, vendorID } func ComposeUniversalOrderID(orderID string, vendorID int) string { // return fmt.Sprintf("%s|%d", orderID, vendorID) return orderID // 当前用长度就能区分,先不加上vendorID } func GetUniversalOrderIDFromWaybill(bill *model.Waybill) string { return ComposeUniversalOrderID(bill.VendorOrderID, bill.OrderVendorID) } func GetUniversalOrderIDFromOrder(order *model.GoodsOrder) string { return ComposeUniversalOrderID(order.VendorOrderID, order.VendorID) } func GetUniversalOrderIDFromOrderStatus(status *model.OrderStatus) string { return ComposeUniversalOrderID(status.VendorOrderID, status.VendorID) } func EarthDistance(lat1, lng1, lat2, lng2 float64) float64 { radius := 6378.137 rad := math.Pi / 180.0 lat1 = lat1 * rad lng1 = lng1 * rad lat2 = lat2 * rad lng2 = lng2 * rad theta := lng2 - lng1 dist := math.Acos(math.Sin(lat1)*math.Sin(lat2) + math.Cos(lat1)*math.Cos(lat2)*math.Cos(theta)) return dist * radius } func StandardCoordinate2Int(value float64) int { return int(math.Round(value * 1000000)) } func IntCoordinate2Standard(value int) float64 { return float64(value) / 1000000 } func IntCoordinate2MarsStandard(gpsLng, gpsLat int, coordinateType int) (marsLng, marsLat float64, err error) { marsLng = IntCoordinate2Standard(gpsLng) marsLat = IntCoordinate2Standard(gpsLat) coordSys := "" switch coordinateType { case model.CoordinateTypeGPS: coordSys = autonavi.CoordSysGPS case model.CoordinateTypeMars: coordSys = autonavi.CoordSysAutonavi case model.CoordinateTypeBaiDu: coordSys = autonavi.CoordSysBaidu case model.CoordinateTypeMapbar: coordSys = autonavi.CoordSysMapbar default: panic(fmt.Sprintf("known coordinate type:%d", coordinateType)) } return api.AutonaviAPI.CoordinateConvert(marsLng, marsLat, coordSys) } func IntPrice2Standard(value int64) float64 { return float64(value) / 100 } func StandardPrice2Int(value float64) int64 { return int64(math.Round(value * 100)) } func IntPrice2StandardString(value int64) string { return fmt.Sprintf("%.2f", IntPrice2Standard(value)) } func CallMsgHandler(handler func(), primaryID string) { routinePool.CallFun(func() { handler() }, primaryID) } func CallMsgHandlerAsync(handler func(), primaryID string) { routinePool.CallFunAsync(func() { handler() }, primaryID) } func GetNameAndUnitFromSkuName(fullName string) (name string, unit string) { unit = "份" index := strings.Index(fullName, "/") if index >= 0 { name = fullName[:index] unitTmp := []rune(fullName[index+1:]) if len(unitTmp) >= 1 { unit = string(unitTmp[:1]) } } else { name = fullName } return name, unit } func MapValue2Scope(value, fromMin, fromMax, toMin, toMax int64) int64 { if value < fromMin { value = fromMin } if value > fromMax { value = fromMax } return int64(math.Round(float64(value-fromMin)/float64(fromMax-fromMin)*float64(toMax-toMin) + float64(toMin))) } func Errs2Str(sep string, errs ...error) (retVal string) { if sep == "" { sep = "\n" } for _, err := range errs { if err != nil { retVal += err.Error() + sep } } return retVal } func IntWeight2Float(weight int) float32 { return float32(weight) / 1000.0 } func FloatWeight2Int(weight float32) int { return int(math.Round(float64(weight * 1000))) } func ComposeSkuName(prefix, name, comment, unit string, spec_quality float32, spec_unit string, maxLen int) (skuName string) { if prefix != "" { skuName = "[" + prefix + "]" } skuName += name if unit == "份" { skuName += "约" } skuName += ComposeSkuSpec(spec_quality, spec_unit) skuName += "/" + unit if comment != "" { skuName += "(" + comment + ")" } if maxLen > 0 { runeList := []rune(skuName) if len(runeList) > maxLen { skuName = string(runeList[:maxLen]) } } return skuName } func ComposeSpuName(prefix, name string, maxLen int) (spuName string) { if prefix != "" { spuName = "[" + prefix + "]" } spuName += name if maxLen > 0 { runeList := []rune(spuName) if len(runeList) > maxLen { spuName = string(runeList[:maxLen]) } } return spuName } func ComposeSkuSpec(spec_quality float32, spec_unit string) (spec string) { if math.Round(float64(spec_quality)) == float64(spec_quality) || (spec_unit != "L" && spec_unit != "kg") { spec = fmt.Sprintf("%d", int(spec_quality)) } else { spec = strings.TrimRight(fmt.Sprintf("%.2f", spec_quality), "0.") } spec += spec_unit return spec } // 1:商品特殊前缀 // 2:商品名字 // 3:商品说明1(可缺失) // 4:质量数字 // 5:质量单位 // 6:商品说明2(可缺失) // 7:商品单位 // 8:商品说明3(可缺失) func SplitSkuName(skuName string) (prefix, name, comment, specUnit, unit string, specQuality float32) { searchResult := skuNamePat.FindStringSubmatch(skuName) if searchResult != nil { if searchResult[3] != "" { comment = searchResult[3] } else if searchResult[6] != "" { comment = searchResult[6] } else if searchResult[8] != "" { comment = searchResult[8] } comment = TrimDecorationChar(comment) name = TrimDecorationChar(searchResult[2]) if comment != "" { if utf8.RuneCountInString(comment) <= 5 { name += "-" + comment comment = "" } } specUnit = strings.ToLower(strings.Replace(searchResult[5], "克", "g", -1)) if specUnit == "l" { specUnit = "L" } if searchResult[7] == "" { unit = "份" } else { unit = searchResult[7] } specQuality = float32(utils.Str2Float64(searchResult[4])) prefix = TrimDecorationChar(searchResult[1]) } return prefix, name, comment, specUnit, unit, specQuality } func FlatMap(in map[string]interface{}) map[string]interface{} { keys := []string{} maps := []map[string]interface{}{} for k, v := range in { if vMap, ok := v.(map[string]interface{}); ok { vMap = FlatMap(vMap) maps = append(maps, vMap) keys = append(keys, k) } } if len(maps) > 0 { retVal := utils.MergeMaps(in, maps...) for _, v := range keys { delete(retVal, v) } return retVal } return in } func Struct2FlatMap(obj interface{}) map[string]interface{} { m := structs.Map(obj) return FlatMap(m) } // todo 这里看是否需要将key值转换成标准格式(即字母大写),因为beego orm不区分,不转换也可以 func FilterMapByStructObject(mapData map[string]interface{}, obj interface{}, excludedFields []string, isCheckValue bool) (valid map[string]interface{}, invalid map[string]interface{}) { excludedMap := make(map[string]int) for _, v := range excludedFields { excludedMap[v] = 1 } m := Struct2FlatMap(obj) valid = make(map[string]interface{}) invalid = make(map[string]interface{}) for k, v := range mapData { if m[k] != nil && excludedMap[k] == 0 && v != nil && (!isCheckValue || m[k] != v) { valid[k] = v } else { invalid[k] = v } } return valid, invalid } func FilterMapByFieldList(mapData map[string]interface{}, fields []string) (valid map[string]interface{}, invalid map[string]interface{}) { valid = make(map[string]interface{}) invalid = make(map[string]interface{}) for _, field := range fields { if mapData[field] != nil { valid[field] = mapData[field] } else { invalid[field] = mapData[field] } } return valid, invalid } func MakeValidationMapFromSlice(validValues []string, flag int) map[string]int { retVal := make(map[string]int) for _, v := range validValues { retVal[v] = flag } return retVal } func ComposeQiniuResURL(key string) string { return "http://image.jxc4.com/" + key } func IsLegalMobileNumber(num int64) bool { return num >= 13000000000 && num <= 19999999999 } func TrimDecorationChar(value string) string { return strings.Trim(value, " \t\n[]()【】()-_——") } func BatchStr2Time(strTime ...string) (timeList []time.Time, err error) { timeList = make([]time.Time, len(strTime)) for k, v := range strTime { if v == "" { timeList[k] = utils.DefaultTimeValue } else { if timeList[k], err = utils.TryStr2Time(v); err != nil { return nil, err } } } return timeList, nil } func IsTimeEmpty(timeValue time.Time) bool { return (timeValue == utils.DefaultTimeValue || timeValue == utils.ZeroTimeValue) } // strAndObjAddPairs必须按字符串1,转换地址1,字符串2,转换地址2这样的格式传递 func Strings2Objs(strAndObjAddPairs ...interface{}) (err error) { str := "" for k, v := range strAndObjAddPairs { if k%2 == 0 { str, _ = v.(string) } else if str != "" { if err = utils.UnmarshalUseNumber([]byte(str), v); err != nil { return err } } } return nil }