592 lines
15 KiB
Go
592 lines
15 KiB
Go
package jxutils
|
||
|
||
import (
|
||
"bytes"
|
||
"crypto/md5"
|
||
"encoding/base64"
|
||
"errors"
|
||
"fmt"
|
||
beego "github.com/astaxie/beego/server/web"
|
||
"image"
|
||
"image/png"
|
||
"io/ioutil"
|
||
"math"
|
||
"net/http"
|
||
"reflect"
|
||
"regexp"
|
||
"strings"
|
||
"sync"
|
||
"time"
|
||
|
||
"git.rosy.net.cn/baseapi/platformapi"
|
||
"git.rosy.net.cn/baseapi/utils"
|
||
"git.rosy.net.cn/jx-callback/business/model"
|
||
"git.rosy.net.cn/jx-callback/globals"
|
||
"github.com/boombuler/barcode"
|
||
"github.com/boombuler/barcode/code128"
|
||
"github.com/boombuler/barcode/qr"
|
||
)
|
||
|
||
const (
|
||
MaxCodeWidth = 400
|
||
MaxCodeHeight = 400
|
||
CodeTypeQr = "qr"
|
||
CodeTypeBar = "bar"
|
||
)
|
||
|
||
var (
|
||
storeNamePat = regexp.MustCompile(`([^(\[(【)\])】\-\s]*)[(\[(【\-\s]+([^(\[(【)\])】\-]*)[)\])】]*`)
|
||
mobilePat = regexp.MustCompile(`^1\d{10}$`)
|
||
)
|
||
|
||
// 合并得到最终的门店状态
|
||
func MergeStoreStatus(status int, vendorStatus int) int {
|
||
if status < vendorStatus {
|
||
return status
|
||
}
|
||
return vendorStatus
|
||
}
|
||
|
||
func MergeSkuStatus(skuStatus int, storeSkuStatus int) int {
|
||
if skuStatus < storeSkuStatus {
|
||
return skuStatus
|
||
}
|
||
return storeSkuStatus
|
||
}
|
||
|
||
func SplitSlice(list interface{}, batchCount int) (listInList [][]interface{}) {
|
||
typeInfo := reflect.TypeOf(list)
|
||
if typeInfo.Kind() != reflect.Slice {
|
||
panic("list must be slice")
|
||
}
|
||
valueInfo := reflect.ValueOf(list)
|
||
len := valueInfo.Len()
|
||
if len > 0 {
|
||
listInListLen := (len-1)/batchCount + 1
|
||
listInList = make([][]interface{}, listInListLen)
|
||
index := 0
|
||
for i := 0; i < len; i++ {
|
||
if i%batchCount == 0 {
|
||
index = i / batchCount
|
||
arrLen := len - i
|
||
if arrLen > batchCount {
|
||
arrLen = batchCount
|
||
}
|
||
listInList[index] = make([]interface{}, arrLen)
|
||
}
|
||
listInList[index][i%batchCount] = valueInfo.Index(i).Interface()
|
||
}
|
||
}
|
||
return listInList
|
||
}
|
||
|
||
func SplitStoreName(fullName, separator, defaultPrefix string) (prefix, bareName string) {
|
||
names := strings.Split(fullName, separator)
|
||
if len(names) == 2 {
|
||
prefix = names[0]
|
||
bareName = names[1]
|
||
} else {
|
||
searchResult := storeNamePat.FindStringSubmatch(fullName)
|
||
if searchResult != nil {
|
||
prefix = searchResult[1]
|
||
bareName = strings.Trim(strings.Trim(searchResult[2], defaultPrefix), separator)
|
||
} else {
|
||
prefix = defaultPrefix
|
||
bareName = strings.Trim(strings.Trim(fullName, defaultPrefix), separator)
|
||
}
|
||
}
|
||
return TrimDecorationChar(prefix), TrimDecorationChar(bareName)
|
||
}
|
||
|
||
func ComposeStoreName(bareName string, vendorID int) (fullName string) {
|
||
bareName = TrimDecorationChar(strings.Trim(bareName, "-"))
|
||
storeName := globals.StoreName
|
||
if vendorID == model.VendorIDJD {
|
||
fullName = storeName + "-" + bareName
|
||
} else {
|
||
if globals.IsMainProductEnv() && model.ShopChineseNames[vendorID] != "" {
|
||
storeName = model.ShopChineseNames[vendorID]
|
||
}
|
||
fullName = storeName + "(" + bareName + ")"
|
||
}
|
||
return fullName
|
||
}
|
||
|
||
func StrTime2JxOperationTime(strTime string, defValue int16) int16 {
|
||
if timeValue, err := time.Parse("15:04:05", strTime); err == nil {
|
||
return int16(timeValue.Hour()*100 + timeValue.Minute())
|
||
}
|
||
return defValue
|
||
}
|
||
|
||
func JxOperationTime2StrTime(value int16) string {
|
||
return fmt.Sprintf("%02d:%02d", value/100, value%100)
|
||
}
|
||
|
||
func JxOperationTime2TimeByDate(value int16, tm time.Time) (outTm time.Time) {
|
||
return utils.Str2TimeWithDefault(fmt.Sprintf("%s %02d:%02d:00", utils.Time2DateStr(tm), value/100, value%100), utils.DefaultTimeValue)
|
||
}
|
||
|
||
func GetPolygonFromCircle(lng, lat, distance float64, pointCount int) (points [][2]float64) {
|
||
points = make([][2]float64, pointCount)
|
||
for k := range points {
|
||
angle := float64(k) * 360 / float64(pointCount)
|
||
points[k][0], points[k][1] = ConvertDistanceToLogLat(lng, lat, float64(distance), angle)
|
||
}
|
||
return points
|
||
}
|
||
|
||
func GetPolygonFromCircleStr(lng, lat, distance float64, pointCount int) string {
|
||
return CoordinatePoints2Str(GetPolygonFromCircle(lng, lat, distance, pointCount))
|
||
}
|
||
|
||
func CoordinatePoints2Str(points [][2]float64) string {
|
||
points2 := make([]string, len(points))
|
||
for k, v := range points {
|
||
points2[k] = fmt.Sprintf("%.6f,%.6f", v[0], v[1])
|
||
}
|
||
return strings.Join(points2, ";")
|
||
}
|
||
|
||
func CoordinateStr2Points(pointsStr string) (points [][2]float64) {
|
||
strPoints := strings.Split(pointsStr, ";")
|
||
for _, v := range strPoints {
|
||
strPoint := strings.Split(v, ",")
|
||
if len(strPoint) >= 2 {
|
||
points = append(points, [2]float64{utils.Str2Float64(strPoint[0]), utils.Str2Float64(strPoint[1])})
|
||
}
|
||
}
|
||
return points
|
||
}
|
||
|
||
func IntMap2List(intMap map[int]int) []int {
|
||
retVal := make([]int, len(intMap))
|
||
index := 0
|
||
for k := range intMap {
|
||
retVal[index] = k
|
||
index++
|
||
}
|
||
return retVal
|
||
}
|
||
|
||
func IntList2Map(intList []int) map[int]int {
|
||
retVal := make(map[int]int)
|
||
for _, v := range intList {
|
||
retVal[v] = 1
|
||
}
|
||
return retVal
|
||
}
|
||
|
||
func Int64Map2List(int64Map map[int64]int) []int64 {
|
||
retVal := make([]int64, len(int64Map))
|
||
index := 0
|
||
for k := range int64Map {
|
||
retVal[index] = k
|
||
index++
|
||
}
|
||
return retVal
|
||
}
|
||
|
||
func StringMap2List(stringMap map[string]int) []string {
|
||
retVal := make([]string, len(stringMap))
|
||
index := 0
|
||
for k := range stringMap {
|
||
retVal[index] = k
|
||
index++
|
||
}
|
||
return retVal
|
||
}
|
||
|
||
func StringList2Map(strList []string) map[string]int {
|
||
retVal := make(map[string]int)
|
||
for _, v := range strList {
|
||
retVal[v] = 1
|
||
}
|
||
return retVal
|
||
}
|
||
|
||
func GetSliceLen(list interface{}) int {
|
||
return reflect.ValueOf(list).Len()
|
||
}
|
||
|
||
func RegularizeSkuQuality(specQuality float32, specUnit string) (g int) {
|
||
lowerSpecUnit := strings.ToLower(specUnit)
|
||
if lowerSpecUnit == "kg" || lowerSpecUnit == "l" {
|
||
specQuality *= 1000
|
||
}
|
||
return int(specQuality)
|
||
}
|
||
|
||
// 计算SKU价格,unitPrice为一斤的单价,specQuality为质量,单位为克
|
||
func CaculateSkuPrice(unitPrice int, specQuality float32, specUnit string, skuNameUnit string) int {
|
||
if skuNameUnit != model.SpecialUnit {
|
||
return unitPrice
|
||
}
|
||
specQuality2 := RegularizeSkuQuality(specQuality, specUnit)
|
||
floatPrice := float64(unitPrice) * float64(specQuality2) / float64(model.SpecialSpecQuality)
|
||
// if specQuality2 < 250 {
|
||
// floatPrice = floatPrice * 110 / 100
|
||
// } else if specQuality2 < 500 {
|
||
// floatPrice = floatPrice * 105 / 100
|
||
// }
|
||
if floatPrice <= 1 {
|
||
floatPrice = 1
|
||
}
|
||
return int(math.Round(floatPrice))
|
||
}
|
||
|
||
// 计算SKU标准价格,CaculateSkuPrice的逆过程
|
||
func CaculateUnitPrice(skuPrice int, specQuality float32, specUnit string, skuNameUnit string) (unitPrice int) {
|
||
if skuNameUnit != model.SpecialUnit {
|
||
return skuPrice
|
||
}
|
||
specQuality2 := RegularizeSkuQuality(specQuality, specUnit)
|
||
unitPrice = skuPrice * model.SpecialSpecQuality / specQuality2
|
||
// if specQuality2 < 250 {
|
||
// unitPrice = unitPrice * 100 / 110
|
||
// } else if specQuality2 < 500 {
|
||
// unitPrice = unitPrice * 100 / 105
|
||
// }
|
||
if unitPrice <= 1 {
|
||
unitPrice = 1
|
||
}
|
||
return unitPrice
|
||
}
|
||
|
||
func ConstrainPricePercentage(percentage int) int {
|
||
if percentage < model.MinVendorPricePercentage || percentage > model.MaxVendorPricePercentage {
|
||
percentage = model.DefVendorPricePercentage
|
||
}
|
||
return percentage
|
||
}
|
||
|
||
func CaculateSkuActVendorPrice(price, percentage, priceAdd int) (vendorPrice int) {
|
||
percentage = ConstrainPricePercentage(percentage)
|
||
vendorPrice = int(math.Round(float64(price*percentage)/100)) + priceAdd
|
||
if vendorPrice < 1 {
|
||
vendorPrice = 1
|
||
}
|
||
return vendorPrice
|
||
}
|
||
|
||
func CaculateSkuVendorPrice(price, percentage, priceAdd int) (vendorPrice int) {
|
||
percentage = ConstrainPricePercentage(percentage)
|
||
vendorPrice = int(math.Round(float64(price*percentage)/100)) + priceAdd
|
||
vendorPrice = int(math.Round(float64(vendorPrice)/10) * 10)
|
||
if vendorPrice < 1 {
|
||
vendorPrice = 1
|
||
}
|
||
return vendorPrice
|
||
}
|
||
|
||
func CaculateSkuPriceFromVendor(vendorPrice, percentage, priceAdd int) (price int) {
|
||
percentage = ConstrainPricePercentage(percentage)
|
||
price = int(math.Round(float64(vendorPrice-priceAdd) * 100 / float64(percentage)))
|
||
if price < 1 {
|
||
price = 1
|
||
}
|
||
return price
|
||
}
|
||
|
||
func GetPricePercentage(l model.PricePercentagePack, price int, defPricePercentage int) (pricePercentage, priceAdd int) {
|
||
pricePercentage = defPricePercentage
|
||
itemLen := len(l)
|
||
if itemLen > 0 {
|
||
low := 0
|
||
high := itemLen - 1
|
||
mid := 0
|
||
for low <= high {
|
||
mid = low + (high-low)/2
|
||
if mid < 0 || mid >= itemLen-1 {
|
||
break
|
||
}
|
||
if price >= l[mid].BeginPrice {
|
||
if price < l[mid+1].BeginPrice {
|
||
break
|
||
} else {
|
||
low = mid + 1
|
||
}
|
||
} else {
|
||
high = mid - 1
|
||
}
|
||
}
|
||
if mid >= 0 && mid <= itemLen-1 && low <= high {
|
||
pricePercentage = l[mid].PricePercentage
|
||
priceAdd = l[mid].PriceAdd
|
||
}
|
||
}
|
||
return pricePercentage, priceAdd
|
||
}
|
||
|
||
func GetPricePercentageByVendorPrice(l model.PricePercentagePack, vendorPrice int, defPricePercentage int) (pricePercentage, priceAdd int) {
|
||
pricePercentage = defPricePercentage
|
||
if len(l) > 0 {
|
||
var lastItem *model.PricePercentageItem
|
||
for _, v := range l {
|
||
if CaculateSkuVendorPrice(v.BeginPrice, v.PricePercentage, v.PriceAdd) > vendorPrice {
|
||
break
|
||
}
|
||
lastItem = v
|
||
}
|
||
if lastItem != nil {
|
||
pricePercentage = lastItem.PricePercentage
|
||
priceAdd = lastItem.PriceAdd
|
||
}
|
||
}
|
||
return pricePercentage, priceAdd
|
||
}
|
||
|
||
func CaculatePriceByPricePack(l model.PricePercentagePack, defPricePercentage, price int) (outPrice int) {
|
||
pricePercentage, priceAdd := GetPricePercentage(l, price, defPricePercentage)
|
||
return CaculateSkuVendorPrice(price, pricePercentage, priceAdd)
|
||
}
|
||
|
||
func CaculateJxPriceByPricePack(l model.PricePercentagePack, defPricePercentage, vendorPrice int) (jxPrice int) {
|
||
pricePercentage, priceAdd := GetPricePercentageByVendorPrice(l, vendorPrice, defPricePercentage)
|
||
jxPrice = CaculateSkuPriceFromVendor(vendorPrice, pricePercentage, priceAdd)
|
||
return jxPrice
|
||
}
|
||
|
||
func ConstrainPayPercentage(payPerCentage int) int {
|
||
if payPerCentage <= 50 {
|
||
payPerCentage = 70
|
||
}
|
||
return payPerCentage
|
||
}
|
||
|
||
func IsSkuSpecial(specQuality float32, specUnit string) bool {
|
||
return int(specQuality) == model.SpecialSpecQuality && (specUnit == model.SpecialSpecUnit || specUnit == model.SpecialSpecUnit2)
|
||
}
|
||
|
||
var lastFakeID int64
|
||
var lastFakeIDMutex sync.RWMutex
|
||
|
||
// 生成一个不重复的临时ID
|
||
func genFakeID1() int64 {
|
||
for {
|
||
fakeID := time.Now().UnixNano() / 1000
|
||
lastFakeIDMutex.RLock()
|
||
if fakeID == lastFakeID {
|
||
lastFakeIDMutex.RUnlock()
|
||
time.Sleep(1 * time.Microsecond)
|
||
} else {
|
||
lastFakeIDMutex.RUnlock()
|
||
lastFakeIDMutex.Lock()
|
||
defer lastFakeIDMutex.Unlock()
|
||
lastFakeID = fakeID
|
||
return fakeID
|
||
}
|
||
}
|
||
}
|
||
|
||
// 这个用于没有打开远程同步时的假同步,生成ID使用
|
||
func GenFakeID() int64 {
|
||
return genFakeID1() * 3
|
||
}
|
||
|
||
func IsFakeID(id int64) bool {
|
||
if IsEmptyID(id) {
|
||
return true
|
||
}
|
||
multiple := id / genFakeID1()
|
||
return multiple >= 2 && multiple <= 4
|
||
}
|
||
|
||
func IsEmptyID(id int64) bool {
|
||
return id == 0
|
||
}
|
||
|
||
func FormalizePageSize(pageSize int) int {
|
||
if pageSize == 0 {
|
||
return model.DefPageSize
|
||
} else if pageSize < 0 {
|
||
return model.UnlimitedPageSize
|
||
}
|
||
return pageSize
|
||
}
|
||
|
||
func FormalizePageOffset(offset int) int {
|
||
if offset < 0 {
|
||
offset = 0
|
||
}
|
||
return offset
|
||
}
|
||
|
||
func FormalizeName(name string) string {
|
||
return utils.TrimBlankChar(strings.Replace(strings.Replace(name, "\t", "", -1), "\"", "", -1))
|
||
}
|
||
|
||
func Int2OneZero(value int) int {
|
||
if value != 0 {
|
||
return 1
|
||
}
|
||
return 0
|
||
}
|
||
|
||
func FormalizeMobile(mobile string) string {
|
||
mobile = TrimDecorationChar(mobile)
|
||
return strings.Replace(strings.Replace(mobile, "-", ",", -1), "_", ",", -1)
|
||
}
|
||
|
||
func IsStringLikeMobile(mobile string) bool {
|
||
return mobilePat.FindStringIndex(mobile) != nil
|
||
}
|
||
|
||
func IsLegalStoreID(id int) bool {
|
||
return id >= 100000 && id < 900000
|
||
}
|
||
|
||
// 将规格转为重量
|
||
func FormatSkuWeight(specQuality float32, specUnit string) int {
|
||
return RegularizeSkuQuality(specQuality, specUnit)
|
||
}
|
||
|
||
type SkuList []*model.Sku
|
||
|
||
func (s SkuList) Len() int {
|
||
return len(s)
|
||
}
|
||
|
||
func (s SkuList) Less(i, j int) bool {
|
||
if s[i].NameID == s[j].NameID {
|
||
if s[i].SpecUnit == s[j].SpecUnit {
|
||
return s[i].SpecQuality < s[j].SpecQuality
|
||
}
|
||
return s[i].SpecUnit < s[j].SpecUnit
|
||
}
|
||
return s[i].NameID < s[j].NameID
|
||
}
|
||
|
||
func (s SkuList) Swap(i, j int) {
|
||
tmp := s[i]
|
||
s[i] = s[j]
|
||
s[j] = tmp
|
||
}
|
||
|
||
func DownloadFileByURL(fileURL string) (bodyData []byte, fileMD5 string, err error) {
|
||
response, err := http.Get(fileURL)
|
||
if err == nil {
|
||
defer response.Body.Close()
|
||
if response.StatusCode == http.StatusOK {
|
||
if bodyData, err = ioutil.ReadAll(response.Body); err == nil {
|
||
fileMD5 = fmt.Sprintf("%X", md5.Sum(bodyData))
|
||
}
|
||
} else {
|
||
err = platformapi.ErrHTTPCodeIsNot200
|
||
}
|
||
}
|
||
return bodyData, fileMD5, err
|
||
}
|
||
|
||
/////
|
||
func GenPicFileName(suffix string) string {
|
||
return fmt.Sprintf("image/%x%s", md5.Sum([]byte(utils.GetUUID()+suffix)), suffix)
|
||
}
|
||
|
||
func GuessVendorIDFromVendorStoreID(vendorStoreID int64) (vendorID int) {
|
||
vendorID = model.VendorIDUnknown
|
||
if vendorStoreID > 10040008 && vendorStoreID < 98765432 { // 京东11733065,8位
|
||
vendorID = model.VendorIDJD
|
||
} else if vendorStoreID > 1234567 && vendorStoreID < 9876543 { // 美团外卖 2461713,7位
|
||
vendorID = model.VendorIDMTWM
|
||
} else if vendorStoreID > 1234567890 && vendorStoreID < 998765432109 { // 饿百 2167002607,10位,12位
|
||
vendorID = model.VendorIDEBAI
|
||
} else if false { //vendorStoreID > 123456789 && vendorStoreID < 987654321 { // 微盟微商城 132091048,9位
|
||
// vendorID = model.VendorIDWSC
|
||
} else if vendorStoreID > 123456 && vendorStoreID < 654321 { // 京西门店ID,6位
|
||
vendorID = model.VendorIDJX
|
||
}
|
||
return vendorID
|
||
}
|
||
|
||
func GetVendorName(vendorID int) (vendorName string) {
|
||
return model.VendorChineseNames[vendorID]
|
||
}
|
||
|
||
func CaculateSkuEarningPrice(shopPrice, salePrice int64, storePayPercentage int) (earningPrice int64) {
|
||
//TODO 2021-07-05 16:50菜市和果园一样,取低的
|
||
//TODO 2021-07-05 18:48 还是改回来
|
||
if beego.BConfig.RunMode == "jxgy" {
|
||
earningPrice = salePrice
|
||
if salePrice != 0 {
|
||
if shopPrice > 0 && shopPrice < earningPrice {
|
||
earningPrice = shopPrice
|
||
}
|
||
}
|
||
} else {
|
||
earningPrice = shopPrice
|
||
}
|
||
storePayPercentage = ConstrainPayPercentage(storePayPercentage)
|
||
if storePayPercentage <= 0 {
|
||
storePayPercentage = model.DefaultEarningPricePercentage
|
||
}
|
||
earningPrice = int64(math.Round(float64(earningPrice) * float64(storePayPercentage) / 100))
|
||
return earningPrice
|
||
}
|
||
|
||
func GetImgBase64(img image.Image) (imgBase64 string, err error) {
|
||
bufWriter := bytes.NewBuffer(nil)
|
||
png.Encode(bufWriter, img)
|
||
imgBase64 = "data:image/png;base64," + base64.StdEncoding.EncodeToString(bufWriter.Bytes())
|
||
|
||
return imgBase64, err
|
||
}
|
||
|
||
func CreateQrOrBarCode(width, height int, codeType, srcData string) (imgBase64 string, err error) {
|
||
if width > MaxCodeWidth {
|
||
width = MaxCodeWidth
|
||
}
|
||
if height > MaxCodeHeight {
|
||
height = MaxCodeHeight
|
||
}
|
||
|
||
var code barcode.Barcode
|
||
if codeType == CodeTypeQr {
|
||
code, err = qr.Encode(srcData, qr.M, qr.Auto)
|
||
} else if codeType == CodeTypeBar {
|
||
code, err = code128.Encode(srcData)
|
||
} else {
|
||
err = errors.New(fmt.Sprintf("未知编码类型:%s", codeType))
|
||
}
|
||
if err == nil {
|
||
code, err = barcode.Scale(code, width, height)
|
||
if err == nil {
|
||
imgBase64, err = GetImgBase64(code)
|
||
}
|
||
}
|
||
|
||
return imgBase64, err
|
||
}
|
||
|
||
// 高德地图面积计算公式
|
||
// https://blog.csdn.net/zdb1314/article/details/80661602
|
||
func CalcPolygonAreaAutonavi(points [][2]float64) (area float64) {
|
||
sJ := float64(6378137)
|
||
Hq := float64(0.017453292519943295)
|
||
c := float64(sJ * Hq)
|
||
d := float64(0)
|
||
|
||
if 3 > len(points) {
|
||
return 0
|
||
}
|
||
|
||
for i := 0; i < len(points)-1; i++ {
|
||
h := points[i]
|
||
k := points[i+1]
|
||
u := h[0] * c * math.Cos(h[1]*Hq)
|
||
|
||
hhh := h[1] * c
|
||
v := k[0] * c * math.Cos(k[1]*Hq)
|
||
d = d + (u*k[1]*c - v*hhh)
|
||
}
|
||
|
||
g1 := points[len(points)-1]
|
||
point := points[0]
|
||
eee := g1[0] * c * math.Cos(g1[1]*Hq)
|
||
g2 := g1[1] * c
|
||
|
||
k := point[0] * c * math.Cos(point[1]*Hq)
|
||
d += eee*point[1]*c - k*g2
|
||
return 0.5 * math.Abs(d) / float64(1000000)
|
||
}
|