From ebcedbb02c676e82bf30ea5c5a46de442ee70004 Mon Sep 17 00:00:00 2001 From: DengBiao <2319963317@qq.com> Date: Thu, 4 Jan 2024 22:54:38 +0800 Subject: [PATCH] =?UTF-8?q?update=20=E5=AF=BC=E8=B4=AD=E5=90=8E=E5=8F=B0?= =?UTF-8?q?=E8=87=AA=E8=90=A5=E5=95=86=E5=9F=8E-=E5=8A=A0=E5=85=A5?= =?UTF-8?q?=E5=95=86=E5=93=81=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/db/dbs_map.go | 2 +- consume/init.go | 95 +-- consume/mall_add_supply_goods.go | 88 +++ consume/md/consume_key.go | 14 +- go.mod | 2 + mall/curl_supply/md/cfg_key.go | 84 +++ mall/curl_supply/md/md.b2c_setting.go | 11 + .../md/md_platform_official_goods.go | 64 ++ mall/curl_supply/md/supplier_goods.go | 115 ++++ mall/curl_supply/svc/svc_supply.go | 93 +++ mall/db/db_mall_sku_add_price_rule.go | 30 + mall/db/dbs_sys_cfg.go | 72 ++ mall/db/model/mall_goods.go | 92 +++ mall/db/model/mall_official_goods.go | 62 ++ mall/db/model/mall_official_sku.go | 31 + mall/db/model/mall_sku.go | 37 ++ mall/db/model/mall_sku_add_price_rule.go | 13 + mall/db/model/supply_user_app_domain.go | 8 + mall/db/model/sys_cfg.go | 7 + mall/md/mall_goods_list.go | 69 ++ mall/svc/svc_mall_official_goods.go | 192 ++++++ mall/svc/svc_sys_cfg_get.go | 107 +++ mall/tool/json.go | 131 ++++ mall/tool/time2s.go | 21 + mall/utils/aes.go | 163 +++++ mall/utils/base64.go | 108 +++ mall/utils/boolean.go | 26 + mall/utils/cache/base.go | 421 ++++++++++++ mall/utils/cache/cache/cache.go | 107 +++ mall/utils/cache/cache/conv.go | 86 +++ mall/utils/cache/cache/file.go | 241 +++++++ mall/utils/cache/cache/memory.go | 239 +++++++ mall/utils/cache/redis.go | 403 ++++++++++++ mall/utils/cache/redis_cluster.go | 622 ++++++++++++++++++ mall/utils/cache/redis_pool.go | 324 +++++++++ mall/utils/cache/redis_pool_cluster.go | 617 +++++++++++++++++ mall/utils/convert.go | 372 +++++++++++ mall/utils/crypto.go | 19 + mall/utils/curl.go | 170 +++++ mall/utils/debug.go | 25 + mall/utils/duplicate.go | 37 ++ mall/utils/file.go | 22 + mall/utils/file_and_dir.go | 29 + mall/utils/format.go | 59 ++ mall/utils/json.go | 161 +++++ mall/utils/json_time_parse.go | 31 + mall/utils/logx/log.go | 245 +++++++ mall/utils/logx/output.go | 105 +++ mall/utils/logx/sugar.go | 192 ++++++ mall/utils/map.go | 55 ++ mall/utils/map_and_struct.go | 341 ++++++++++ mall/utils/md5.go | 12 + mall/utils/qrcode/decodeFile.go | 33 + mall/utils/qrcode/getBase64.go | 43 ++ mall/utils/qrcode/saveFile.go | 85 +++ mall/utils/qrcode/writeWeb.go | 39 ++ mall/utils/rand.go | 41 ++ mall/utils/redis.go | 36 + mall/utils/rsa.go | 170 +++++ mall/utils/serialize.go | 23 + mall/utils/shuffle.go | 48 ++ mall/utils/slice.go | 70 ++ mall/utils/slice_and_string.go | 47 ++ mall/utils/string.go | 96 +++ mall/utils/time.go | 181 +++++ mall/utils/uuid.go | 61 ++ mall/utils/validator_err_trans.go | 55 ++ 67 files changed, 7651 insertions(+), 49 deletions(-) create mode 100644 consume/mall_add_supply_goods.go create mode 100644 mall/curl_supply/md/cfg_key.go create mode 100644 mall/curl_supply/md/md.b2c_setting.go create mode 100644 mall/curl_supply/md/md_platform_official_goods.go create mode 100644 mall/curl_supply/md/supplier_goods.go create mode 100644 mall/curl_supply/svc/svc_supply.go create mode 100644 mall/db/db_mall_sku_add_price_rule.go create mode 100644 mall/db/dbs_sys_cfg.go create mode 100644 mall/db/model/mall_goods.go create mode 100644 mall/db/model/mall_official_goods.go create mode 100644 mall/db/model/mall_official_sku.go create mode 100644 mall/db/model/mall_sku.go create mode 100644 mall/db/model/mall_sku_add_price_rule.go create mode 100644 mall/db/model/supply_user_app_domain.go create mode 100644 mall/db/model/sys_cfg.go create mode 100644 mall/md/mall_goods_list.go create mode 100644 mall/svc/svc_mall_official_goods.go create mode 100644 mall/svc/svc_sys_cfg_get.go create mode 100644 mall/tool/json.go create mode 100644 mall/tool/time2s.go create mode 100644 mall/utils/aes.go create mode 100644 mall/utils/base64.go create mode 100644 mall/utils/boolean.go create mode 100644 mall/utils/cache/base.go create mode 100644 mall/utils/cache/cache/cache.go create mode 100644 mall/utils/cache/cache/conv.go create mode 100644 mall/utils/cache/cache/file.go create mode 100644 mall/utils/cache/cache/memory.go create mode 100644 mall/utils/cache/redis.go create mode 100644 mall/utils/cache/redis_cluster.go create mode 100644 mall/utils/cache/redis_pool.go create mode 100644 mall/utils/cache/redis_pool_cluster.go create mode 100644 mall/utils/convert.go create mode 100644 mall/utils/crypto.go create mode 100644 mall/utils/curl.go create mode 100644 mall/utils/debug.go create mode 100644 mall/utils/duplicate.go create mode 100644 mall/utils/file.go create mode 100644 mall/utils/file_and_dir.go create mode 100644 mall/utils/format.go create mode 100644 mall/utils/json.go create mode 100644 mall/utils/json_time_parse.go create mode 100644 mall/utils/logx/log.go create mode 100644 mall/utils/logx/output.go create mode 100644 mall/utils/logx/sugar.go create mode 100644 mall/utils/map.go create mode 100644 mall/utils/map_and_struct.go create mode 100644 mall/utils/md5.go create mode 100644 mall/utils/qrcode/decodeFile.go create mode 100644 mall/utils/qrcode/getBase64.go create mode 100644 mall/utils/qrcode/saveFile.go create mode 100644 mall/utils/qrcode/writeWeb.go create mode 100644 mall/utils/rand.go create mode 100644 mall/utils/redis.go create mode 100644 mall/utils/rsa.go create mode 100644 mall/utils/serialize.go create mode 100644 mall/utils/shuffle.go create mode 100644 mall/utils/slice.go create mode 100644 mall/utils/slice_and_string.go create mode 100644 mall/utils/string.go create mode 100644 mall/utils/time.go create mode 100644 mall/utils/uuid.go create mode 100644 mall/utils/validator_err_trans.go diff --git a/app/db/dbs_map.go b/app/db/dbs_map.go index a6f60e1..1d5edcd 100644 --- a/app/db/dbs_map.go +++ b/app/db/dbs_map.go @@ -21,7 +21,7 @@ func InitMapDbs(c *cfg.DBCfg, prd bool) { logx.Fatalf("db_mapping not exists : %v", err) } // tables := MapAllDatabases(debug) - if prd { + if true { tables = GetAllDatabasePrd() //debug 获取生产 } else { tables = GetAllDatabaseDev() //debug 获取开发 diff --git a/consume/init.go b/consume/init.go index 2c7e665..d73c021 100644 --- a/consume/init.go +++ b/consume/init.go @@ -17,54 +17,54 @@ func Init() { // 增加消费任务队列 func initConsumes() { - jobs[consumeMd.ZhiosUserUpLvFunName] = ZhiosUserUpLv - jobs[consumeMd.CanalGuideOrderByUserUpLvConsume] = CanalGuideOrderByUserUpLvConsume - jobs[consumeMd.ZhiosOrderFreeFunName] = ZhiosOrderFree - jobs[consumeMd.ZhiosOrderTotalFunName] = ZhiosOrderTotal - jobs[consumeMd.ZhiosOrderTotalSecondFunName] = ZhiosOrderTotalSecond + //jobs[consumeMd.ZhiosUserUpLvFunName] = ZhiosUserUpLv + //jobs[consumeMd.CanalGuideOrderByUserUpLvConsume] = CanalGuideOrderByUserUpLvConsume + //jobs[consumeMd.ZhiosOrderFreeFunName] = ZhiosOrderFree + //jobs[consumeMd.ZhiosOrderTotalFunName] = ZhiosOrderTotal + //jobs[consumeMd.ZhiosOrderTotalSecondFunName] = ZhiosOrderTotalSecond + //// + //jobs[consumeMd.ZhiosOrderSettleTotalFunName] = ZhiosSettleTotal + //jobs[consumeMd.ZhiosOrderHjyFunName] = ZhiosOrderHjy + //jobs[consumeMd.ZhiosOrderBuckleFunName] = ZhiosOrderBuckle + //// + //jobs[consumeMd.ZhiosSupplierAfterOrderFunName] = ZhiosSupplierAfterOrder + //jobs[consumeMd.ZhiosGuideStoreOrderFunName] = ZhiosGuideStoreOrder // - jobs[consumeMd.ZhiosOrderSettleTotalFunName] = ZhiosSettleTotal - jobs[consumeMd.ZhiosOrderHjyFunName] = ZhiosOrderHjy - jobs[consumeMd.ZhiosOrderBuckleFunName] = ZhiosOrderBuckle + ////jobs[consumeMd.ZhiosAppreciationDevFunName] = ZhiosAppreciation // - jobs[consumeMd.ZhiosSupplierAfterOrderFunName] = ZhiosSupplierAfterOrder - jobs[consumeMd.ZhiosGuideStoreOrderFunName] = ZhiosGuideStoreOrder - - //jobs[consumeMd.ZhiosAppreciationDevFunName] = ZhiosAppreciation - - jobs[consumeMd.ZhiosAppreciationFunName] = ZhiosAppreciation - jobs[consumeMd.ZhiosValidUserFunName] = ZhiosValidUser - - //jobs[consumeMd.ZhiosAcquisitionConditionDevFunName] = ZhiosAcquisitionCondition - - jobs[consumeMd.ZhiosAcquisitionConditionFunName] = ZhiosAcquisitionCondition - jobs[consumeMd.CanalOrderConsumeFunName] = CanalOrderConsume - jobs[consumeMd.CanalGuideOrderConsumeFunName] = CanalGuideOrderConsume - jobs[consumeMd.ZhiOsUserVisitIpAddressConsumeFunName] = ZhiOsUserVisitIpAddressConsume - - jobs[consumeMd.DouShenUserRegisterConsumeForOfficialFunName] = DouShenUserRegisterConsumeForOfficial - jobs[consumeMd.DouShenUserRegisterConsumeForOperationCenterFunName] = DouShenUserRegisterConsumeForOperationCenter - jobs[consumeMd.DouShenUserRegisterConsumeForMyRecommenderFunName] = DouShenUserRegisterConsumeForMyRecommender - jobs[consumeMd.DouShenUserRegisterConsumeForMyFansFunName] = DouShenUserRegisterConsumeForMyFans - jobs[consumeMd.DouShenUserRegisterConsumeForUserRegisterUpLvFunName] = DouShenUserRegisterConsumeForUserRegisterUpLv - - jobs[consumeMd.ZhiosFastReturnOrderPayFunName] = ZhiosFastReturnOrderPay - jobs[consumeMd.ZhiosFastReturnOrderSuccessFunName] = ZhiosFastReturnOrderSuccess - jobs[consumeMd.ZhiosFastReturnOrderRefundFunName] = ZhiosFastReturnOrderRefund - jobs[consumeMd.ZhiosFastReturnOrderRefundSecondFunName] = ZhiosFastReturnOrderRefundSecond - - //jobs[consumeMd.CanalMallOrdForYouMiShangFunName] = CanalMallOrdForYouMiShang - jobs[consumeMd.YoumishangExchangeStoreFunName] = YoumishangExchangeStore - - jobs[consumeMd.ZhiosRechargeOrderFailFunName] = ZhiosRechargeOrderFail - - jobs[consumeMd.CloudIssuanceAsyncMLoginFunName] = CloudIssuanceAsyncMLoginConsume - jobs[consumeMd.ZhiosTikTokUpdateFunName] = ZhiosTikTokUpdate - jobs[consumeMd.ZhiosTikTokAllUpdateFunName] = ZhiosTikTokAllUpdate - - jobs[consumeMd.ZhiosCapitalPoolOrderTotalFunName] = ZhiosCapitalPoolOrderTotal - jobs[consumeMd.ZhiosExpressOrderFail] = ZhiosExpressOrderFail - jobs[consumeMd.ZhiosWithdrawReward] = ZhiosWithdrawReward + //jobs[consumeMd.ZhiosAppreciationFunName] = ZhiosAppreciation + //jobs[consumeMd.ZhiosValidUserFunName] = ZhiosValidUser + // + ////jobs[consumeMd.ZhiosAcquisitionConditionDevFunName] = ZhiosAcquisitionCondition + // + //jobs[consumeMd.ZhiosAcquisitionConditionFunName] = ZhiosAcquisitionCondition + //jobs[consumeMd.CanalOrderConsumeFunName] = CanalOrderConsume + //jobs[consumeMd.CanalGuideOrderConsumeFunName] = CanalGuideOrderConsume + //jobs[consumeMd.ZhiOsUserVisitIpAddressConsumeFunName] = ZhiOsUserVisitIpAddressConsume + // + //jobs[consumeMd.DouShenUserRegisterConsumeForOfficialFunName] = DouShenUserRegisterConsumeForOfficial + //jobs[consumeMd.DouShenUserRegisterConsumeForOperationCenterFunName] = DouShenUserRegisterConsumeForOperationCenter + //jobs[consumeMd.DouShenUserRegisterConsumeForMyRecommenderFunName] = DouShenUserRegisterConsumeForMyRecommender + //jobs[consumeMd.DouShenUserRegisterConsumeForMyFansFunName] = DouShenUserRegisterConsumeForMyFans + //jobs[consumeMd.DouShenUserRegisterConsumeForUserRegisterUpLvFunName] = DouShenUserRegisterConsumeForUserRegisterUpLv + // + //jobs[consumeMd.ZhiosFastReturnOrderPayFunName] = ZhiosFastReturnOrderPay + //jobs[consumeMd.ZhiosFastReturnOrderSuccessFunName] = ZhiosFastReturnOrderSuccess + //jobs[consumeMd.ZhiosFastReturnOrderRefundFunName] = ZhiosFastReturnOrderRefund + //jobs[consumeMd.ZhiosFastReturnOrderRefundSecondFunName] = ZhiosFastReturnOrderRefundSecond + // + ////jobs[consumeMd.CanalMallOrdForYouMiShangFunName] = CanalMallOrdForYouMiShang + //jobs[consumeMd.YoumishangExchangeStoreFunName] = YoumishangExchangeStore + // + //jobs[consumeMd.ZhiosRechargeOrderFailFunName] = ZhiosRechargeOrderFail + // + //jobs[consumeMd.CloudIssuanceAsyncMLoginFunName] = CloudIssuanceAsyncMLoginConsume + //jobs[consumeMd.ZhiosTikTokUpdateFunName] = ZhiosTikTokUpdate + //jobs[consumeMd.ZhiosTikTokAllUpdateFunName] = ZhiosTikTokAllUpdate + // + //jobs[consumeMd.ZhiosCapitalPoolOrderTotalFunName] = ZhiosCapitalPoolOrderTotal + //jobs[consumeMd.ZhiosExpressOrderFail] = ZhiosExpressOrderFail + //jobs[consumeMd.ZhiosWithdrawReward] = ZhiosWithdrawReward //jobs[consumeMd.ZhiosRechargeOrderFailDevFunName] = ZhiosRechargeOrderFailDev @@ -74,6 +74,9 @@ func initConsumes() { //////////////////////////////////////// V2 ///////////////////////////////////////////////////// //jobs[consumeMd.SupplyCloudChainFenxiaoNewChangeFunName] = SupplyCloudChainFenxiaoNewChangeConsume + //////////////////////////////////////// V3 ///////////////////////////////////////////////////// + jobs[consumeMd.MallAddSupplyGoodsFunName] = MallAddSupplyGoodsConsume + } func Run() { diff --git a/consume/mall_add_supply_goods.go b/consume/mall_add_supply_goods.go new file mode 100644 index 0000000..cd97377 --- /dev/null +++ b/consume/mall_add_supply_goods.go @@ -0,0 +1,88 @@ +package consume + +import ( + svc2 "applet/app/svc" + "applet/app/utils/logx" + "applet/consume/md" + md3 "applet/mall/md" + "applet/mall/svc" + "code.fnuoos.com/go_rely_warehouse/zyos_go_mq.git/rabbit" + "encoding/json" + "errors" + "fmt" + "github.com/gin-gonic/gin" + "github.com/jinzhu/copier" + "github.com/streadway/amqp" + "strings" +) + +func MallAddSupplyGoodsConsume(queue md.MqQueue) { + fmt.Println(">>>>>>>>>>>>>>>>>>>>>>>>") + ch, err := rabbit.Cfg.Pool.GetChannel() + if err != nil { + logx.Error(err) + return + } + defer ch.Release() + //2、取出数据进行消费 + ch.Qos(1) + delivery := ch.Consume(queue.Name, false) + + var res amqp.Delivery + var ok bool + for { + res, ok = <-delivery + if ok == true { + fmt.Println(">>>>>>>>>>>>>>>>MallAddSupplyGoodsConsume<<<<<<<<<<<<<<<<<<<<<<<<<") + err = handleMallAddSupplyGoodsConsume(res.Body) + if err != nil { + fmt.Println("*****************err*************************", err) + } + _ = res.Ack(true) + } else { + panic(errors.New("error getting message")) + } + } + fmt.Println("get msg done") +} + +func handleMallAddSupplyGoodsConsume(msgData []byte) error { + //解析mq中queue的数据结构体 + var pushStuct struct { + Args struct { + GoodsInfo []*md3.OfficialGoods `json:"goods_info"` + md3.OfficialGoodsConditions + } `json:"args"` + Mid string `json:"mid"` + } + err := json.Unmarshal(msgData, &pushStuct) + if err != nil { + panic(err) + } + //设置masterId + var c = &gin.Context{} + c.Set("mid", pushStuct.Mid) + var t struct { + GoodsInfo []*md3.OfficialGoods `json:"goods_info"` + md3.OfficialGoodsConditions + } + t = pushStuct.Args + var conditions md3.OfficialGoodsConditions + conditions.CategoryId = t.CategoryId + conditions.AddPriceBase = t.AddPriceBase + conditions.AddPriceNum = t.AddPriceNum + conditions.AddPriceType = t.AddPriceType + conditions.NumType = t.NumType + engine := svc2.MasterDb(c) + for _, goods := range t.GoodsInfo { + var b2cGoods md3.OfficialGoods + copier.Copy(&b2cGoods, goods) + ex := strings.Split(b2cGoods.Price, "-") + b2cGoods.Price = ex[0] + err1 := svc.AddOfficialGoods(c, engine, &b2cGoods, &conditions) + if err1 != nil { + return err1 + } + } + return nil +} diff --git a/consume/md/consume_key.go b/consume/md/consume_key.go index edcf582..1d16924 100644 --- a/consume/md/consume_key.go +++ b/consume/md/consume_key.go @@ -21,7 +21,7 @@ var RabbitMqQueueKeyList = []*MqQueue{ ConsumeFunName: "CloudIssuanceAsyncMLoginConsume", }, { - ExchangeName: "", + ExchangeName: "zhios.cloud.issuance.msg.callback.exchange", Name: "cloud_issuance_msg_call_back", Type: DirectQueueType, IsPersistent: false, @@ -30,7 +30,7 @@ var RabbitMqQueueKeyList = []*MqQueue{ ConsumeFunName: "CloudIssuanceMsgCallBackConsume", }, { - ExchangeName: "zhios.cloud.issuance.msg.callback.exchange", + ExchangeName: "zhios.cloud_chain.fenxiao.newChange.exchange", Name: "cloud_chain_fenxiao_newChange", Type: FanOutQueueType, IsPersistent: false, @@ -38,6 +38,15 @@ var RabbitMqQueueKeyList = []*MqQueue{ BindKey: "", ConsumeFunName: "SupplyCloudChainFenxiaoNewChangeConsume", }, + { + ExchangeName: "zhios.addSupplyGoods.exchange", + Name: "mall_add_supply_goods", + Type: FanOutQueueType, + IsPersistent: false, + RoutKey: "", + BindKey: "", + ConsumeFunName: "MallAddSupplyGoodsConsume", + }, { ExchangeName: "canal.topic", Name: "canal_order", @@ -413,4 +422,5 @@ const ( ZhiosGuideStoreOrderFunName = "ZhiosGuideStoreOrder" ZhiosAcquisitionConditionDevFunName = "ZhiosAcquisitionConditionDev" SupplyCloudChainFenxiaoNewChangeFunName = "SupplyCloudChainFenxiaoNewChangeConsume" + MallAddSupplyGoodsFunName = "MallAddSupplyGoodsConsume" ) diff --git a/go.mod b/go.mod index ee5acbd..d25acf7 100644 --- a/go.mod +++ b/go.mod @@ -44,6 +44,7 @@ require ( ) require ( + github.com/360EntSecGroup-Skylar/excelize v1.4.1 // indirect github.com/KyleBanks/depth v1.2.1 // indirect github.com/PuerkitoBio/purell v1.1.1 // indirect github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect @@ -66,6 +67,7 @@ require ( github.com/mingrammer/commonregex v1.0.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/mvdan/xurls v1.1.0 // indirect github.com/nilorg/sdk v0.0.0-20221104025912-4b6ccb7004d8 // indirect github.com/olivere/elastic/v7 v7.0.32 // indirect diff --git a/mall/curl_supply/md/cfg_key.go b/mall/curl_supply/md/cfg_key.go new file mode 100644 index 0000000..5b89ebb --- /dev/null +++ b/mall/curl_supply/md/cfg_key.go @@ -0,0 +1,84 @@ +package md + +// 获取用户的缓存key +const ( + KEY_SYS_CFG_CACHE = "sys_cfg_cache" + KEY_CFG_CACHE = "cfg_cache" + + // 文件缓存的key + KEY_CFG_FILE_PVD = "file_provider" // 文件供应商 + KEY_CFG_FILE_BUCKET = "file_bucket" + KEY_CFG_FILE_REGION = "file_bucket_region" + KEY_CFG_FILE_HOST = "file_bucket_host" + KEY_CFG_FILE_SCHEME = "file_bucket_scheme" + KEY_CFG_FILE_AK = "file_access_key" + KEY_CFG_FILE_SK = "file_secret_key" + KEY_CFG_FILE_MAX_SIZE = "file_user_upload_max_size" + KEY_CFG_FILE_EXT = "file_ext" + KEY_CFG_FILE_AVATAR_THUMBNAIL = "file_avatar_thumbnail" // 默认头像缩略图参数,宽高120px,格式webp. + // 智盟 + KEY_CFG_ZM_JD_SITE_ID = "third_zm_jd_site_id" // 智盟京东联盟id + KEY_CFG_ZM_WEB_ID = "third_zm_web_id" // 智盟网站ID + KEY_CFG_ZM_AK = "third_zm_app_key" + KEY_CFG_ZM_SK = "third_zm_app_secret" + KEY_CFG_ZM_SMS_AK = "third_zm_sms_ak" + KEY_CFG_ZM_SMS_SK = "third_zm_sms_sk" + KEY_CFG_APP_NAME = "app_name" + + KEY_CFG_WHITELIST = "api_cfg_whitelist" // API允许的访问的设置白名单 + + // 淘宝 + KEY_CFG_TB_AUTH_AK = "third_taobao_auth_ak" + KEY_CFG_TB_AUTH_SK = "third_taobao_auth_sk" + KEY_CFG_TB_INVITER_CODE = "third_taobao_auth_inviter_code" + KEY_CFG_TB_AK = "third_taobao_ak" + KEY_CFG_TB_SK = "third_taobao_sk" + KEY_CFG_TB_PID = "third_taobao_pid" // 淘宝推广ID,如:mm_123_456_789,123是联盟ID,456是site_id,789是adzone_id + KEY_CFG_TB_SID = "third_taobao_sid" // 淘宝session id ,又称access_token + + // 苏宁 + KEY_CFG_SN_AK = "third_suning_ak" + KEY_CFG_SN_SK = "third_suning_sk" + + KEY_CFG_JD_AK = "" + KEY_CFG_JD_SK = "" + + KEY_CFG_KL_AK = "third_kaola_ak" + KEY_CFG_KL_SK = "third_kaola_sk" + + KEY_CFG_VIP_AK = "" + KEY_CFG_VIP_SK = "" + + // 自动任务配置 + KEY_CFG_CRON_TB = "cron_order_taobao" + KEY_CFG_CRON_JD = "cron_order_jd" + KEY_CFG_CRON_PDD = "cron_order_pdd" + KEY_CFG_CRON_SN = "cron_order_suning" + KEY_CFG_CRON_VIP = "cron_order_vip" + KEY_CFG_CRON_KL = "cron_order_kaola" + KEY_CFG_CRON_HIS = "cron_order_his" // 迁移到历史订单 + KEY_CFG_CRON_SETTLE = "cron_order_settle" // 迁移到历史订单 + KEY_CFG_CRON_PUBLISHER = "cron_taobao_publisher" // 跟踪淘宝备案信息绑定会员运营id 针对小程序 + KEY_CFG_CRON_MEITUAN = "cron_order_meituan" //美团 + KEY_CFG_CRON_OILSTATION = "cron_order_oilstation" //加油 + KEY_CFG_CRON_KFC = "cron_order_kfc" //肯德基 + KEY_CFG_CRON_CINEMA = "cron_order_cinema" //电影票 + KEY_CFG_CRON_OilRequest = "cron_order_oilrequest" //加入主动请求抓单 + KEY_CFG_CRON_AGOTB = "cron_order_agotaobao" //n天前的淘宝订单 + KEY_CFG_CRON_CREDIT_CARD = "cron_order_credit_card" + KEY_CFG_CRON_ORDER_STAT = "cron_order_stat" // 订单统计任务 + + // 自动任务运行时设置 + KEY_CFG_CRON_TIME_TB = "crontab_order_time_taobao" + KEY_CFG_CRON_TIME_JD = "crontab_order_time_jd" + KEY_CFG_CRON_TIME_PDD = "crontab_order_time_pdd" + KEY_CFG_CRON_TIME_SN = "crontab_order_time_suning" + KEY_CFG_CRON_TIME_VIP = "crontab_order_time_vip" + KEY_CFG_CRON_TIME_KL = "crontab_order_time_kaola" + KEY_CFG_CRON_TIME_PUBLISHER = "crontab_taobao_time_publisher" // 跟踪淘宝备案信息绑定会员运营id 针对小程序 + KEY_CFG_CRON_TIME_MEITUAN = "crontab_order_time_meituan" //美团 + KEY_CFG_CRON_TIME_OILSTATION = "crontab_order_time_oilstation" //加油 + KEY_CFG_CRON_TIME_KFC = "crontab_order_time_kfc" //肯德基 + KEY_CFG_CRON_TIME_CINEMA = "crontab_order_time_cinema" //电影票 + +) diff --git a/mall/curl_supply/md/md.b2c_setting.go b/mall/curl_supply/md/md.b2c_setting.go new file mode 100644 index 0000000..77ce817 --- /dev/null +++ b/mall/curl_supply/md/md.b2c_setting.go @@ -0,0 +1,11 @@ +package md + +const ( + //http://supply-chain-admin.izhyin.com + SupplierDomain = "supplier_domain" + //eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6OCwiYWNjb3VudCI6IjE4MjI5Nzc1MzE2IiwicGhvbmUiOiIxODIyOTc3NTMxNiIsIm1pZCI6IjEyMzQ1NiIsImlzcyI6Inp5b3Nfc3VwcGx5X2NoYWluX3VuaXF1ZV9rZXkifQ.CuiPv1vmdPXJlieJOa4ykCoBuWWA9TT8n8qgSP9ZJ5o + SupplierUniqueKey = "supplier_unique_key" + OrderFailureByNotPaying = "b2c_order_failure_by_not_paying" + OrderFinish = "b2c_order_finish" + BizB2cSmartyCustomerService = "biz_b2c_smarty_customer_service" +) diff --git a/mall/curl_supply/md/md_platform_official_goods.go b/mall/curl_supply/md/md_platform_official_goods.go new file mode 100644 index 0000000..208118e --- /dev/null +++ b/mall/curl_supply/md/md_platform_official_goods.go @@ -0,0 +1,64 @@ +package md + +import "time" + +type OfficialGoods struct { + ID string `json:"id"` + Price string `json:"price"` + LinePrice string `json:"line_price"` + Weight string `json:"weight"` + Profit string `json:"profit"` + CreateTime string `json:"create_time"` + UpdateTime string `json:"update_time"` + Stock int `json:"stock"` + SkuList []*OfficialSku `json:"skuList"` + SaleCount int `json:"sale_count"` + Title string `json:"title"` + Image string `json:"image"` + StateText string `json:"state_text"` + SaleState int `json:"sale_state"` + SaleStartTime string `json:"sale_start_time"` + StateCode int `json:"state_code"` + MerchantID int `json:"merchant_id"` + MerchantName string `json:"merchant_name"` + IsGoodsWarehouse string `json:"is_goods_warehouse"` + AuditReason string `json:"audit_reason"` + CategoryID string `json:"category_id"` + GoodsCode string `json:"goods_code"` +} + +type OfficialSku struct { + SkuID int `json:"sku_id"` + GoodsID int `json:"goods_id"` + Price string `json:"price"` + CreateTime time.Time `json:"create_time"` + UpdateTime time.Time `json:"update_time"` + Weight string `json:"weight"` + Stock int `json:"stock"` + Indexes string `json:"indexes"` + Sku string `json:"sku"` + SaleCount int `json:"sale_count"` + LinePrice string `json:"line_price"` + SkuCode string `json:"sku_code"` + CloudChainSkuImgUrl string `json:"cloud_chain_sku_img_url"` +} + +type OfficialGoodsConditions struct { + PlatformType string `json:"platform_type"` //加入平台 + CategoryId string `json:"category_id"` + AddPriceType string `json:"add_price_type"` //1按金额增加2按比例增加 + AddPriceBase string `json:"add_price_base"` //加价基数 1按进货价2按利润空间 + AddPriceNum string `json:"add_price_num"` //类型是1就是直接加值,如果是2则是百分比 + NumType string `json:"num_type"` //价格数值设置 1小数点后两位2整数 + Ids []string `json:"ids"` //编辑商品id组 + IsSkuAddPrice string `json:"is_sku_add_price"` + SkusConditions []*OfficialSkuConditions `json:"skus_conditions"` +} + +type OfficialSkuConditions struct { + AddPriceType string `json:"add_price_type"` //1按金额增加2按比例增加 + AddPriceBase string `json:"add_price_base"` //加价基数 1按进货价2按利润空间 + AddPriceNum string `json:"add_price_num"` //类型是1就是直接加值,如果是2则是百分比 + NumType string `json:"num_type"` //价格数值设置 1小数点后两位2整数 + SkuCode string `json:"sku_code"` +} diff --git a/mall/curl_supply/md/supplier_goods.go b/mall/curl_supply/md/supplier_goods.go new file mode 100644 index 0000000..dd329db --- /dev/null +++ b/mall/curl_supply/md/supplier_goods.go @@ -0,0 +1,115 @@ +package md + +import ( + "applet/supply/md" +) + +type SupplierGoodsDetail struct { + BaseGoods BaseGoods `json:"base_goods"` + //SelectData SelectData `json:"select_data"`\ + MallLogo string `json:"mall_logo"` //店铺Logo + MallName string `json:"mall_name"` //店铺名称 + Skus []*Sku `json:"sku"` + ShippingData MallShippingTemplateListRespForCamel `json:"shipping_data"` +} + +type MallShippingTemplateDataForCamel struct { + Regions []string `json:"region" label:"省/市/区(县)" label:"省市区的id"` + AdditionalAmount string `json:"additionalAmount" label:"续件/续重"` + AdditionalFee string `json:"additionalFee" label:"续费"` + FirstAmount string `json:"firstAmount" label:"首件/首重"` + FirstFee string `json:"firstFee" label:"运费"` +} + +// +type MallShippingTemplateListRespForCamel struct { + Id int `json:"id" label:"not null pk autoincr INT(11)"` + Name string `json:"name" label:"模板名称" binding:"required"` + RegionRules md.RegionRule `json:"regionRules"` + Data []MallShippingTemplateDataForCamel `json:"data" label:"运费数据" binding:"required"` + CalculateType int `json:"calculateType" label:"计费方式:1按重量 2按件数" binding:"required"` + CreateTime string `json:"createTime"` +} + +type BaseGoods struct { + AuditReason string `json:"audit_reason"` + CategoryID int `json:"category_id"` + CategoryName string `json:"category_name"` + CustomProperty []interface{} `json:"custom_property"` + Detail []string `json:"detail"` + DetailURL []string `json:"detail_url"` + GoodsID int64 `json:"goods_id"` + GoodsType int `json:"goods_type"` + ImageList []string `json:"image_list"` + ImageListURL []string `json:"image_list_url"` + IsSpeImageInDetail int `json:"is_spe_image_in_detail"` + IsSpeImageOn int `json:"is_spe_image_on"` + MerchantID int `json:"merchant_id"` + Price string `json:"price"` + LinePrice string `json:"line_price"` + ProfitRate string `json:"profit_rate"` + SaleCount int `json:"sale_count"` + SaleStartTime string `json:"sale_start_time"` + SaleState int `json:"sale_state"` + SaleStateZh string `json:"sale_state_zh"` + Service []int `json:"service"` + ShippingFee string `json:"shipping_fee"` + ShippingFeeType int `json:"shipping_fee_type"` + ShippingTimeCustom string `json:"shipping_time_custom"` + ShippingTimeType int `json:"shipping_time_type"` + ShippingTplID int `json:"shipping_tpl_id"` + ShippingType int `json:"shipping_type"` + Spe []*Spe `json:"spe"` + SpeImages []string `json:"spe_images"` + SpeImagesURL []string `json:"spe_images_url"` + Stock int `json:"stock"` + Title string `json:"title"` + UpdateTime string `json:"update_time"` + GoodsCode string `json:"goods_code"` +} +type Spe struct { + Name string `json:"name"` + Values []string `json:"values"` +} + +type SelectData struct { + CustomCategory []interface{} `json:"custom_category"` + GoodsTypeList []*SelectDataInsideGoodsTypeList `json:"goods_type_list"` + ServiceList []*SelectDataInsideServiceList `json:"service_list"` + ShippingTemplateList []*SelectDataInsideShippingTemplateList `json:"shipping_template_list"` +} + +type SelectDataInsideGoodsTypeList struct { + Desc string `json:"desc"` + Name string `json:"name"` + Type int `json:"type"` +} + +type SelectDataInsideServiceList struct { + Label string `json:"label"` + Value int `json:"value"` +} + +type SelectDataInsideShippingTemplateList struct { + ShippingTemplateID int `json:"shipping_template_id"` + Name string `json:"name"` +} + +type Sku struct { + SkuId int64 `json:"sku_id"` + GoodsID int64 `json:"goods_id"` + LinePrice string `json:"line_price"` + Price string `json:"price"` + Sku []*SkuInsideSku `json:"sku"` + Stock int `json:"stock"` + Weight string `json:"weight"` + SkuCode string `json:"sku_code"` + Indexes string `json:"indexes"` + SaleCount int `json:"sale_count"` + CloudChainSkuImgUrl string `json:"cloud_chain_sku_img_url"` +} + +type SkuInsideSku struct { + Name string `json:"name"` + Value string `json:"value"` +} diff --git a/mall/curl_supply/svc/svc_supply.go b/mall/curl_supply/svc/svc_supply.go new file mode 100644 index 0000000..232a477 --- /dev/null +++ b/mall/curl_supply/svc/svc_supply.go @@ -0,0 +1,93 @@ +package svc + +import ( + "applet/app/db" + "applet/app/utils" + supplyMd "applet/mall/curl_supply/md" + "applet/mall/db/model" + "applet/supply/svc" + "bytes" + "encoding/json" + "errors" + "fmt" + "github.com/gin-gonic/gin" + "strconv" + "strings" +) + +func CommGet(doMain, uniqueKey string) (map[string]string, error) { + if find := strings.Contains(doMain, "http://"); find { + doMain = strings.Replace(doMain, "http://", "", 1) + } + if find := strings.Contains(doMain, "https://"); find { + doMain = strings.Replace(doMain, "https://", "", 1) + } + var dm model.SupplyUserAppDomain + get, err := db.Db.Where("domain=?", doMain).Get(&dm) + if err != nil || !get { + utils.FilePutContents("CurlSupplyDomain", doMain) + return nil, errors.New("该域名未授权") + } + header := map[string]string{ + "Content-Type": "application/json", + "Authorization": uniqueKey, + "master_id": strconv.Itoa(dm.Uuid), + } + return header, nil +} +func CurlSupplyGoodsDetails(c *gin.Context, supplierGoodsId string) (*supplyMd.SupplierGoodsDetail, error) { + var ( + goodsDetail supplyMd.SupplierGoodsDetail + err error + ) + doMain := svc.SysCfgGet(c, supplyMd.SupplierDomain) + url := doMain + "/api/open/goods/detail?id=%s" + url = fmt.Sprintf(url, supplierGoodsId) + uniqueKey := svc.SysCfgGet(c, supplyMd.SupplierUniqueKey) + if doMain == "" || uniqueKey == "" { + err = errors.New("请前往供应链平台获取domain和uniqueKey,完成供应链设置") + return nil, err + } + header, err := CommGet(doMain, uniqueKey) + if err != nil { + return nil, err + } + bytes1, err := utils.CurlGet(url, header) + if err != nil { + err = errors.New("请前往供应链平台获取domain和uniqueKey,完成供应链设置") + return nil, err + } + //resp := make(map[string]interface{}) + //utils.Unserialize(bytes1, &resp) + //fmt.Println("$$$$$$$$$$$$$$->:" + utils.SerializeStr(resp) + ":<-$$$$$$$$$$$$$$") + respJson := make(map[string]interface{}) + d := json.NewDecoder(bytes.NewReader(bytes1)) + d.UseNumber() + err = d.Decode(&respJson) + if err != nil { + fmt.Println(err) + } + fmt.Println("222222222222->:" + utils.SerializeStr(respJson) + ":<-22222222") + _, ok := respJson["data"] + if ok { + utils.Unserialize([]byte(utils.SerializeStr(respJson["data"])), &goodsDetail) + } else { + msg, ok1 := respJson["msg"] + if ok1 { + err = errors.New(utils.AnyToString(msg)) + } else { + err = errors.New("获取供应链商品信息失败,请前往供应链检查商品信息1!") + } + return nil, err + } + if goodsDetail.BaseGoods.GoodsID > 0 { + return &goodsDetail, nil + } + msg, ok1 := respJson["msg"] + if ok1 { + err = errors.New(utils.AnyToString(msg)) + } else { + err = errors.New("获取供应链商品信息失败,请前往供应链检查商品信息2!") + } + return nil, err +} diff --git a/mall/db/db_mall_sku_add_price_rule.go b/mall/db/db_mall_sku_add_price_rule.go new file mode 100644 index 0000000..5fa374f --- /dev/null +++ b/mall/db/db_mall_sku_add_price_rule.go @@ -0,0 +1,30 @@ +package db + +import ( + "applet/mall/db/model" + "xorm.io/xorm" +) + +func AddOrUpdateMallSkuAddPriceRule(session *xorm.Session, addPriceRule, supplyGoodsSkuCode string) error { + var rule model.MallSkuAddPriceRule + get, err := session.Where("supply_goods_sku_code = ?", supplyGoodsSkuCode).Get(&rule) + if err != nil { + return err + } + if get { + rule.AddPriceRule = addPriceRule + _, err := session.ID(rule.Id).Update(&rule) + if err != nil { + return err + } + return nil + } else { + rule.AddPriceRule = addPriceRule + rule.SupplyGoodsSkuCode = supplyGoodsSkuCode + one, err := session.InsertOne(&rule) + if err != nil || one == 0 { + return err + } + return nil + } +} diff --git a/mall/db/dbs_sys_cfg.go b/mall/db/dbs_sys_cfg.go new file mode 100644 index 0000000..cae0740 --- /dev/null +++ b/mall/db/dbs_sys_cfg.go @@ -0,0 +1,72 @@ +package db + +import ( + "applet/mall/db/model" + "xorm.io/xorm" + + "applet/app/utils/logx" +) + +// 系统配置get +func DbsSysCfgGetAll(eg *xorm.Engine) (*[]model.SysCfg, error) { + var cfgList []model.SysCfg + if err := eg.Cols("`key`,`val`").Find(&cfgList); err != nil { + return nil, logx.Error(err) + } + return &cfgList, nil +} + +// 获取一条记录 +func DbsSysCfgGet(eg *xorm.Engine, key string) (*model.SysCfg, error) { + var cfgList model.SysCfg + if has, err := eg.Where("`key`=?", key).Get(&cfgList); err != nil || has == false { + return nil, logx.Error(err) + } + return &cfgList, nil +} + +func NewDbsSysCfgGet(eg *xorm.Engine, key string) (*model.SysCfg, error) { + var cfgList model.SysCfg + if _, err := eg.Where("`key`=?", key).Get(&cfgList); err != nil { + return nil, logx.Error(err) + } + return &cfgList, nil +} + +//写入或更新 +func DbsSysCfgInsertANDUPDATE(eg *xorm.Engine, key, val string) bool { + latest, err := DbsSysCfgGet(eg, key) + if err != nil { + return false + } + if latest == nil { + cfg := model.SysCfg{Key: key, Val: val} + has, _ := eg.Insert(cfg) + if has > 0 { + return true + } + return false + } + bools := DbsSysCfgUpdate(eg, key, val) + return bools + +} +func DbsSysCfgInsert(eg *xorm.Engine, key, val string) bool { + cfg := model.SysCfg{Key: key, Val: val} + _, err := eg.Where("`key`=?", key).Cols("val,memo").Update(&cfg) + if err != nil { + logx.Error(err) + return false + } + return true +} + +func DbsSysCfgUpdate(eg *xorm.Engine, key, val string) bool { + cfg := model.SysCfg{Key: key, Val: val} + _, err := eg.Where("`key`=?", key).Cols("val").Update(&cfg) + if err != nil { + logx.Error(err) + return false + } + return true +} diff --git a/mall/db/model/mall_goods.go b/mall/db/model/mall_goods.go new file mode 100644 index 0000000..5d2c9f3 --- /dev/null +++ b/mall/db/model/mall_goods.go @@ -0,0 +1,92 @@ +package model + +import ( + "time" +) + +type MallGoods struct { + GoodsId int `json:"goods_id" xorm:"not null pk autoincr comment('商品id') INT(11)"` + GoodsType int `json:"goods_type" xorm:"not null default 1 comment('商品类型:1实物商品') TINYINT(1)"` + Title string `json:"title" xorm:"not null default '' comment('商品标题') VARCHAR(1024)"` + CategoryId int `json:"category_id" xorm:"not null default 0 comment('类目ID') INT(11)"` + ShareText string `json:"share_text" xorm:"not null default '' comment('分享描述') VARCHAR(2048)"` + ImageList string `json:"image_list" xorm:"comment('商品图json') TEXT"` + CommissionRate string `json:"commission_rate" xorm:"not null default 0.0000 comment('佣金比例') DECIMAL(6,4)"` + IcbcCommissionRate string `json:"icbc_commission_rate" xorm:"not null default 0.0000 comment('佣金比例') DECIMAL(6,4)"` + IntegralRate string `json:"integral_rate" xorm:"not null default 0.0000 comment('积分分佣比例') DECIMAL(6,4)"` + Price string `json:"price" xorm:"not null default 0.00 comment('价格') DECIMAL(12,2)"` + BuyNeedDeductBili string `json:"buy_need_deduct_bili" xorm:"not null default 0.00 comment('抵扣券最大比例') DECIMAL(12,2)"` + LinePrice string `json:"line_price" xorm:"not null default 0.00 comment('划线价') DECIMAL(12,2)"` + VipDiscountType int `json:"vip_discount_type" xorm:"not null default 1 comment('会员优惠方式:1打折 2减价 3指定价格') TINYINT(3)"` + VipDiscountPrice string `json:"vip_discount_price" xorm:"comment('会员优惠价信息') TEXT"` + Stock int `json:"stock" xorm:"not null default 0 comment('库存') INT(255)"` + Weight string `json:"weight" xorm:"not null default 0.0000 comment('重量') DECIMAL(12,4)"` + ShippingType int `json:"shipping_type" xorm:"not null default 1 comment('配送方式:1快递') TINYINT(1)"` + ShippingFeeType int `json:"shipping_fee_type" xorm:"not null default 1 comment('运费计费方式:1统一运费 2运费模板') TINYINT(1)"` + ShippingTplId int `json:"shipping_tpl_id" xorm:"not null default 0 comment('运费模板id') INT(11)"` + ShippingFee string `json:"shipping_fee" xorm:"not null default 0.00 comment('统一运费的值') DECIMAL(12,2)"` + SaleState int `json:"sale_state" xorm:"not null default 1 comment('销售状态:1上架(立即销售)2定时销售 3放入仓库') TINYINT(1)"` + SaleStartTime time.Time `json:"sale_start_time" xorm:"comment('定时开售时间') DATETIME"` + IsLimitBuy int `json:"is_limit_buy" xorm:"not null default 0 comment('是否限购:0否 1是') TINYINT(1)"` + LimitBuyData string `json:"limit_buy_data" xorm:"not null default '' comment('限购数据:type:1:终身限购n件 2:按周期x限购y件') VARCHAR(5012)"` + CreateTime time.Time `json:"create_time" xorm:"created not null default 'CURRENT_TIMESTAMP' comment('创建时间') DATETIME"` + UpdateTime time.Time `json:"update_time" xorm:"updated not null default 'CURRENT_TIMESTAMP' comment('更新时间') DATETIME"` + OfficialProperty string `json:"official_property" xorm:"not null comment('官方参数json') TEXT"` + CustomProperty string `json:"custom_property" xorm:"not null comment('自定义参数json') TEXT"` + Spe string `json:"spe" xorm:"not null default '' comment('所有规格属性json') VARCHAR(5012)"` + SpeImages string `json:"spe_images" xorm:"not null default '' comment('第一组规格值对应的图片json') VARCHAR(1024)"` + Detail string `json:"detail" xorm:"comment('商品详情') TEXT"` + VipDiscountSwitch int `json:"vip_discount_switch" xorm:"not null default 0 comment('是否开启会员价:0关闭 1开启') TINYINT(1)"` + Sort int `json:"sort" xorm:"not null default 0 comment('序号') INT(11)"` + ActivityType int `json:"activity_type" xorm:"not null default 0 comment('参与的活动:0未参加活动 1拼团活动 2秒杀活动 3砍价活动 4超级拼团 5会员升级礼包') TINYINT(3)"` + SaleCount int `json:"sale_count" xorm:"not null default 0 comment('销量') INT(11)"` + IsSpeImageOn int `json:"is_spe_image_on" xorm:"not null default 0 comment('是否开启规格图片:0否 1是') TINYINT(1)"` + IsSpeImageInDetail int `json:"is_spe_image_in_detail" xorm:"not null default 0 comment('规格图是否显示在详情页') TINYINT(1)"` + DeleteAt time.Time `json:"delete_at" xorm:"comment('商品删除字段,不为null时,表示删除') DATETIME"` + Service string `json:"service" xorm:"comment('支持的服务json,id数组') VARCHAR(512)"` + IsVirtualCoin int `json:"is_virtual_coin" xorm:"comment('是否支持虚拟币') TINYINT(1)"` + VirtualCoinId int `json:"virtual_coin_id" xorm:"comment('支持支付的虚拟币id') INT(11)"` + RepurchaseDiscount string `json:"repurchase_discount" xorm:"default 0.00 comment('复购折扣') DECIMAL(12,2)"` + UserLevel int `json:"user_level" xorm:"default 0 comment('升级等级') INT(11)"` + UserLevelDay int `json:"user_level_day" xorm:"default 0 comment('升级等级天数 0为永久') INT(11)"` + IsHasRepurchase int `json:"is_has_repurchase" xorm:"default 0 comment('是否有复购') INT(1)"` + RepurchaseType int `json:"repurchase_type" xorm:"default 0 comment('复购类型 0购买相同商品为复购 1购买商品赠送相同会员等级为复购') INT(11)"` + IsHasUserLevel int `json:"is_has_user_level" xorm:"default 0 comment('是否有会员权益') INT(1)"` + UserLevelGivenStatus int `json:"user_level_given_status" xorm:"default 0 comment('会员礼包赠送权益订单状态') INT(11)"` + UserLevelUpTimeType int `json:"user_level_up_time_type" xorm:"default 0 comment('升级礼包升级方式 0过期时间延长 1当前时间延长') INT(11)"` + UserLevelCanRefund int `json:"user_level_can_refund" xorm:"default 0 comment('会员礼包是否能退货') INT(1)"` + SeckillStock int `json:"seckill_stock" xorm:"default 0 comment('秒杀库存(总)') INT(11)"` + VideoUrl string `json:"video_url" xorm:"not null default '' comment('视频链接') VARCHAR(255)"` + VideoHeight string `json:"video_height" xorm:"not null default '' comment('视频高度') VARCHAR(100)"` + GoodsSourceUrl string `json:"goods_source_url" xorm:"not null default '' comment('商品来源链接') TEXT"` + SalesStoreName string `json:"sales_store_name" xorm:"not null default '' comment('经销商名称') VARCHAR(100)"` + Pvd int `json:"pvd" xorm:"default 1 comment('来源:1:商家自己发布,2:来自于供应商') TINYINT(1)"` + SupplierGoodsId int64 `json:"supplier_goods_id" xorm:"comment('供应商的商品id') INT(11)"` + SupplierMerchantId int `json:"supplier_merchant_id" xorm:"comment('供应商id') INT(11)"` + SupplierMerchantName string `json:"supplier_merchant_name" xorm:"comment('供应商名称') VARCHAR(255)"` + SupplierCategoryId int `json:"supplier_category_id" xorm:"comment('供应商分类id') INT(11)"` + SupplierGoodsUpdateTime string `json:"supplier_goods_update_time" xorm:"default '' VARCHAR(100)"` + OriginalPrice string `json:"original_price" xorm:"comment('原价(指站长库的价格)') DECIMAL(12,4)"` + AddPrice string `json:"add_price" xorm:"comment('加价(指在原价上加多少钱)') DECIMAL(10,2)"` + SupplierMerchantAvatar string `json:"supplier_merchant_avatar" xorm:"comment('供应链商家头像') VARCHAR(255)"` + AddPriceRule string `json:"add_price_rule" xorm:"comment('加价规则json') TEXT"` + SupplierGoodsCode string `json:"supplier_goods_code" xorm:"comment('供应商的商品编号') VARCHAR(255)"` + Msg string `json:"msg" xorm:"comment('商品的错误信息或其他内容') TEXT"` + RewardGuessCardNum int `json:"reward_guess_card_num" xorm:"default 0 comment('奖励拆猜卡数量') INT(11)"` + LimitJoinType string `json:"limit_join_type" xorm:"not null default '' comment('条件限制json') VARCHAR(1024)"` + LevelType string `json:"level_type" xorm:"not null default '' comment('等级限制json') VARCHAR(1024)"` + AgentRule string `json:"agent_rule" xorm:"not null default '' comment('等级限制json') VARCHAR(5012)"` + IntegralRule string `json:"integral_rule" xorm:"not null default '' comment('等级限制json') VARCHAR(500)"` + IntegralPayType int `json:"integral_pay_type" xorm:"default 0 comment('积分支付方式 0固定积分金额 1积分不够余额付') INT(1)"` + IsVirtualGoods int `json:"is_virtual_goods" xorm:"default 0 comment('') INT(1)"` + RewardVirtualCoinId int `json:"reward_virtual_coin_id" xorm:"default 0 comment('') INT(11)"` + RewardVirtualUserLv string `json:"reward_virtual_user_lv" xorm:"not null comment('自定义参数json') TEXT"` + BaseCommission string `json:"base_commission" xorm:"comment('积分兑换模式基数') DECIMAL(12,4)"` + PlatformCostPrice string `json:"platform_cost_price" xorm:"comment('平台成本价') DECIMAL(12,4)"` + GroupRuleData string `json:"group_rule_data" xorm:" comment('拼团数据 json') TEXT"` + BelongCity string `json:"belong_city" xorm:"not null default '' comment('所属城市 大陆mainland 澳门 macao') VARCHAR(255)"` + VirtualGoodsInfo string `json:"virtual_goods_info" xorm:"not null default '' comment('') VARCHAR(1024)"` + ConsignForSaleInfo string `json:"consign_for_sale_info" xorm:"not null default '' comment('') VARCHAR(1024)"` + ConsignForSaleAddCart int `json:"consign_for_sale_add_cart" xorm:"default 0 comment('') INT(11)"` + IsJuShuiTan int `json:"is_ju_shui_tan" xorm:"default 0 comment('') INT(11)"` +} diff --git a/mall/db/model/mall_official_goods.go b/mall/db/model/mall_official_goods.go new file mode 100644 index 0000000..42103df --- /dev/null +++ b/mall/db/model/mall_official_goods.go @@ -0,0 +1,62 @@ +package model + +import ( + "time" +) + +type MallOfficialGoods struct { + GoodsId int `json:"goods_id" xorm:"not null pk autoincr comment('商品id') INT(11)"` + GoodsType int `json:"goods_type" xorm:"not null default 1 comment('商品类型:1实物商品') TINYINT(1)"` + Title string `json:"title" xorm:"not null default '' comment('商品标题') VARCHAR(1024)"` + CategoryId int `json:"category_id" xorm:"not null default 0 comment('类目ID') INT(11)"` + ShareText string `json:"share_text" xorm:"not null default '' comment('分享描述') VARCHAR(2048)"` + ImageList string `json:"image_list" xorm:"comment('商品图json') TEXT"` + Price string `json:"price" xorm:"not null default 0.00 comment('价格(original_price+add_price)站长实收数值') DECIMAL(12,2)"` + Discount string `json:"discount" xorm:"default 10.00 comment('折扣,为10无折扣') DECIMAL(6,2)"` + DiscountPrice string `json:"discount_price" xorm:"not null default 0.00 comment('折扣价') DECIMAL(12,2)"` + VipDiscountType int `json:"vip_discount_type" xorm:"not null default 1 comment('会员优惠方式:0原价 1会员 2折扣') TINYINT(3)"` + VipPrice string `json:"vip_price" xorm:"comment('会员价,会员等级对应的价格值json:格式为:[{"level_id":1,"value":10}]') TEXT"` + Stock int `json:"stock" xorm:"not null default 0 comment('库存') INT(255)"` + Weight string `json:"weight" xorm:"not null default 0.0000 comment('重量') DECIMAL(12,4)"` + ShippingType int `json:"shipping_type" xorm:"not null default 1 comment('配送方式:1快递') TINYINT(1)"` + ShippingFeeType int `json:"shipping_fee_type" xorm:"not null default 1 comment('运费计费方式:1统一运费 2运费模板') TINYINT(1)"` + ShippingTplId int `json:"shipping_tpl_id" xorm:"not null default 0 comment('运费模板id') INT(11)"` + ShippingFee string `json:"shipping_fee" xorm:"not null default 0.00 comment('统一运费的值') DECIMAL(12,2)"` + SaleState int `json:"sale_state" xorm:"not null default 1 comment('销售状态:1上架(立即销售)2定时销售 3放入仓库') TINYINT(1)"` + SaleStartTime time.Time `json:"sale_start_time" xorm:"comment('定时开售时间') DATETIME"` + IsLimitBuy int `json:"is_limit_buy" xorm:"not null default 0 comment('是否限购:0否 1是') TINYINT(1)"` + LimitBuyData string `json:"limit_buy_data" xorm:"not null default '' comment('限购数据:type:1:终身限购n件 2:按周期x限购y件') VARCHAR(5012)"` + CreateTime time.Time `json:"create_time" xorm:"not null default 'CURRENT_TIMESTAMP' comment('创建时间') DATETIME"` + UpdateTime time.Time `json:"update_time" xorm:"not null default 'CURRENT_TIMESTAMP' comment('更新时间') DATETIME"` + OfficialProperty string `json:"official_property" xorm:"not null comment('官方参数json') TEXT"` + CustomProperty string `json:"custom_property" xorm:"not null comment('自定义参数json') TEXT"` + Spe string `json:"spe" xorm:"not null default '' comment('所有规格属性json') VARCHAR(5012)"` + SpeImages string `json:"spe_images" xorm:"not null default '' comment('第一组规格值对应的图片json') VARCHAR(1024)"` + Detail string `json:"detail" xorm:"comment('商品详情') TEXT"` + Sort int `json:"sort" xorm:"not null default 0 comment('序号') INT(11)"` + ActivityType int `json:"activity_type" xorm:"not null default 0 comment('参与的活动:0未参加活动 1拼团活动 2秒杀活动 3砍价活动 4超级拼团') TINYINT(3)"` + SaleCount int `json:"sale_count" xorm:"not null default 0 comment('销量') INT(11)"` + IsSpeImageOn int `json:"is_spe_image_on" xorm:"not null default 0 comment('是否开启规格图片:0否 1是') TINYINT(1)"` + IsSpeImageInDetail int `json:"is_spe_image_in_detail" xorm:"not null default 0 comment('规格图是否显示在详情页') TINYINT(1)"` + DeleteAt time.Time `json:"delete_at" xorm:"comment('商品删除字段,不为null时,表示删除') DATETIME"` + Service string `json:"service" xorm:"comment('支持的服务json,id数组') VARCHAR(512)"` + IsVirtualCoin int `json:"is_virtual_coin" xorm:"comment('是否支持虚拟币') TINYINT(1)"` + VirtualCoinId int `json:"virtual_coin_id" xorm:"comment('支持支付的虚拟币id') INT(11)"` + StoreId int `json:"store_id" xorm:"not null comment('店铺id') INT(11)"` + IsSingleSku int `json:"is_single_sku" xorm:"not null default 1 comment('是否单规格,0:否,1是') TINYINT(1)"` + CommissionRate string `json:"commission_rate" xorm:"not null default 0.0000 comment('佣金比例') DECIMAL(6,4)"` + AuditState int `json:"audit_state" xorm:"default 1 comment('审核状态:1未审核 2通过 3失败') TINYINT(1)"` + AuditRemark string `json:"audit_remark" xorm:"comment('审核备注') VARCHAR(255)"` + StockDeductionWay int `json:"stock_deduction_way" xorm:"default 1 comment('库存减扣方式 1:拍下减库存,') TINYINT(1)"` + IsReturnWithoutReason int `json:"is_return_without_reason" xorm:"default 0 comment('是否7天无理由退货(0否,1是)') TINYINT(1)"` + IsManufactorDelivery int `json:"is_manufactor_delivery" xorm:"default 0 comment('是否厂家配送(0:否,1是)') TINYINT(1)"` + IsOfficialFidelity int `json:"is_official_fidelity" xorm:"default 0 comment('是官方保真(0:否,1:是)') TINYINT(1)"` + Pvd int `json:"pvd" xorm:"comment('来源:1:商家自己发布,2:来自于供应商') TINYINT(1)"` + SupplierGoodsId int `json:"supplier_goods_id" xorm:"comment('供应商的商品id') INT(11)"` + SupplierMerchantId int `json:"supplier_merchant_id" xorm:"comment('供应商id') INT(11)"` + SupplierMerchantName string `json:"supplier_merchant_name" xorm:"comment('供应商名称') VARCHAR(255)"` + SupplierCategoryId int `json:"supplier_category_id" xorm:"comment('供应商分类id') INT(11)"` + OriginalPrice string `json:"original_price" xorm:"comment('原价(指供应链的价格)') DECIMAL(12,4)"` + AddPrice string `json:"add_price" xorm:"comment('加价(指在原价上加多少钱)') DECIMAL(10,2)"` + LinePrice string `json:"line_price" xorm:"comment('指导价(指供应链的价格)') DECIMAL(12,4)"` +} diff --git a/mall/db/model/mall_official_sku.go b/mall/db/model/mall_official_sku.go new file mode 100644 index 0000000..34fc316 --- /dev/null +++ b/mall/db/model/mall_official_sku.go @@ -0,0 +1,31 @@ +package model + +import ( + "time" +) + +type MallOfficialSku struct { + SkuId int64 `json:"sku_id" xorm:"not null pk autoincr BIGINT(20)"` + GoodsId int `json:"goods_id" xorm:"not null comment('商品id') INT(11)"` + Price string `json:"price" xorm:"not null default 0.00 comment('价格(original_price+add_price)站长实收数值') DECIMAL(12,2)"` + CreateTime time.Time `json:"create_time" xorm:"default 'CURRENT_TIMESTAMP' comment('创建时间') DATETIME"` + UpdateTime time.Time `json:"update_time" xorm:"default 'CURRENT_TIMESTAMP' comment('更新时间') DATETIME"` + VipPrice string `json:"vip_price" xorm:"comment('会员价,会员等级对应的价格值json:格式为:') VARCHAR(5012)"` + Weight string `json:"weight" xorm:"not null default 0.0000 comment('重量') DECIMAL(12,4)"` + Stock int `json:"stock" xorm:"not null default 0 comment('库存') INT(11)"` + Indexes string `json:"indexes" xorm:"not null default '' comment('规格值组合的下标') VARCHAR(100)"` + Sku string `json:"sku" xorm:"not null comment('规格组合json') VARCHAR(2048)"` + SaleCount int `json:"sale_count" xorm:"not null default 0 comment('销量') INT(11)"` + Sort int `json:"sort" xorm:"not null default 0 comment('排序') INT(11)"` + ActivityPrice string `json:"activity_price" xorm:"not null default 0.00 comment('活动价格') DECIMAL(12,2)"` + VirtualCoinPrice string `json:"virtual_coin_price" xorm:"comment('虚拟币购买价(剩余部分按比例折算成虚拟币)') DECIMAL(12,2)"` + VirtualCoinVipPrice string `json:"virtual_coin_vip_price" xorm:"comment('虚拟币支付会员折扣设置') VARCHAR(5012)"` + Discount string `json:"discount" xorm:"default 10.00 comment('折扣,为10无折扣') DECIMAL(6,2)"` + DiscountPrice string `json:"discount_price" xorm:"default 0.00 comment('折扣价') DECIMAL(12,2)"` + VipDiscountType int `json:"vip_discount_type" xorm:"default 0 comment('会员优惠方式:0原价 1会员 2折扣') INT(11)"` + OriginalPrice string `json:"original_price" xorm:"comment('原价(指供应链的价格)') DECIMAL(12,4)"` + AddPrice string `json:"add_price" xorm:"comment('加价(指在原价上加多少钱)') DECIMAL(10,2)"` + SupplierGoodsId int `json:"supplier_goods_id" xorm:"comment('供应商的商品id') INT(11)"` + SupplierSkuId int `json:"supplier_sku_id" xorm:"comment('供应商的skuid') INT(11)"` + LinePrice string `json:"line_price" xorm:"comment('指导价(指供应链的价格)') DECIMAL(12,4)"` +} diff --git a/mall/db/model/mall_sku.go b/mall/db/model/mall_sku.go new file mode 100644 index 0000000..f7e64b5 --- /dev/null +++ b/mall/db/model/mall_sku.go @@ -0,0 +1,37 @@ +package model + +import ( + "time" +) + +type MallSku struct { + SkuId int64 `json:"sku_id" xorm:"not null pk autoincr BIGINT(20)"` + GoodsId int `json:"goods_id" xorm:"not null comment('商品id') INT(11)"` + Price string `json:"price" xorm:"not null default 0.00 comment('价格') DECIMAL(12,2)"` + CreateTime time.Time `json:"create_time" xorm:"created default 'CURRENT_TIMESTAMP' comment('创建时间') DATETIME"` + UpdateTime time.Time `json:"update_time" xorm:"updated default 'CURRENT_TIMESTAMP' comment('更新时间') DATETIME"` + VipPrice string `json:"vip_price" xorm:"comment('会员等级对应的价格值json') VARCHAR(5012)"` + Weight string `json:"weight" xorm:"not null default 0.0000 comment('重量') DECIMAL(12,4)"` + Stock int `json:"stock" xorm:"not null default 0 comment('库存') INT(11)"` + SeckillStock int `json:"seckill_stock" xorm:"default 0 comment('秒杀库存') INT(11)"` + SkuCode string `json:"sku_code" xorm:"not null default '' comment('规格值组合的下标') VARCHAR(100)"` + Indexes string `json:"indexes" xorm:"not null default '' comment('规格值组合的下标') VARCHAR(100)"` + Sku string `json:"sku" xorm:"not null comment('规格组合json') VARCHAR(2048)"` + SaleCount int `json:"sale_count" xorm:"not null default 0 comment('销量') INT(11)"` + Sort int `json:"sort" xorm:"not null default 0 comment('排序') INT(11)"` + ActivityPrice string `json:"activity_price" xorm:"not null default 0.00 comment('活动价格') DECIMAL(12,2)"` + VirtualCoinPrice string `json:"virtual_coin_price" xorm:"comment('虚拟币购买价(剩余部分按比例折算成虚拟币)') DECIMAL(12,2)"` + VirtualCoinVipPrice string `json:"virtual_coin_vip_price" xorm:"comment('虚拟币支付会员折扣设置') VARCHAR(5012)"` + UserLevel int `json:"user_level" xorm:"not null default 0 comment('升级等级') INT(11)"` + UserLevelDay int `json:"user_level_day" xorm:"not null default 0 comment('升级等级天数 0为永久') INT(11)"` + IsHasRepurchase int `json:"is_has_repurchase" xorm:"not null default 0 comment('是否有复购') INT(1)"` + RepurchaseType int `json:"repurchase_type" xorm:"not null default 0 comment('复购类型 0购买相同商品为复购 1购买商品赠送相同会员等级为复购') INT(11)"` + RepurchaseDiscount string `json:"repurchase_discount" xorm:"comment('虚拟币购买价(复购折扣)') DECIMAL(12,2)"` + SupplierGoodsId int64 `json:"supplier_goods_id" xorm:"comment('供应商的商品id') INT(11)"` + SupplierSkuId int64 `json:"supplier_sku_id" xorm:"comment('供应商的skuid') INT(11)"` + OriginalPrice string `json:"original_price" xorm:"comment('原价(指站长库的价格)') DECIMAL(12,4)"` + AddPrice string `json:"add_price" xorm:"comment('加价(指在原价上加多少钱)') DECIMAL(10,2)"` + SupplierSkuCode string `json:"supplier_sku_code" xorm:"comment('供应商的SKU编号') VARCHAR(255)"` + CloudChainSkuImgUrl string `json:"cloud_chain_sku_img_url" xorm:"comment('云链的产品SKU图片url') VARCHAR(255)"` + RewardCoinAmount string `json:"reward_coin_amount" xorm:"comment('') DECIMAL(12,4)"` +} diff --git a/mall/db/model/mall_sku_add_price_rule.go b/mall/db/model/mall_sku_add_price_rule.go new file mode 100644 index 0000000..57cecbd --- /dev/null +++ b/mall/db/model/mall_sku_add_price_rule.go @@ -0,0 +1,13 @@ +package model + +import ( + "time" +) + +type MallSkuAddPriceRule struct { + Id int64 `json:"id" xorm:"pk autoincr BIGINT(22)"` + SupplyGoodsSkuCode string `json:"supply_goods_sku_code" xorm:"not null comment('供应链good-sku编码(唯一)') unique VARCHAR(600)"` + AddPriceRule string `json:"add_price_rule" xorm:"not null default '' comment('加价规则') VARCHAR(600)"` + CreateTime time.Time `json:"create_time" xorm:"created not null default 'CURRENT_TIMESTAMP' comment('创建时间') DATETIME"` + UpdateTime time.Time `json:"update_time" xorm:"updated not null default 'CURRENT_TIMESTAMP' comment('更新时间') DATETIME"` +} diff --git a/mall/db/model/supply_user_app_domain.go b/mall/db/model/supply_user_app_domain.go new file mode 100644 index 0000000..dbca71b --- /dev/null +++ b/mall/db/model/supply_user_app_domain.go @@ -0,0 +1,8 @@ +package model + +type SupplyUserAppDomain struct { + Domain string `json:"domain" xorm:"not null pk comment('绑定域名') VARCHAR(100)"` + Uuid int `json:"uuid" xorm:"not null comment('对应APP ID编号') index unique(IDX_UUID_TYPE) INT(10)"` + Type string `json:"type" xorm:"not null comment('api接口域名,wap.h5域名,admin管理后台') unique(IDX_UUID_TYPE) ENUM('admin','api','pc','wap')"` + IsSsl int `json:"is_ssl" xorm:"not null default 0 comment('是否开启ssl:0否;1是') TINYINT(255)"` +} diff --git a/mall/db/model/sys_cfg.go b/mall/db/model/sys_cfg.go new file mode 100644 index 0000000..22d906b --- /dev/null +++ b/mall/db/model/sys_cfg.go @@ -0,0 +1,7 @@ +package model + +type SysCfg struct { + Key string `json:"key" xorm:"not null pk comment('键') VARCHAR(127)"` + Val string `json:"val" xorm:"comment('值') TEXT"` + Memo string `json:"memo" xorm:"not null default '' comment('备注') VARCHAR(255)"` +} diff --git a/mall/md/mall_goods_list.go b/mall/md/mall_goods_list.go new file mode 100644 index 0000000..0b00080 --- /dev/null +++ b/mall/md/mall_goods_list.go @@ -0,0 +1,69 @@ +package md + +import "time" + +type OfficialGoods struct { + ID string `json:"id"` + Price string `json:"price"` + LinePrice string `json:"line_price"` + Weight string `json:"weight"` + Profit string `json:"profit"` + CreateTime string `json:"create_time"` + UpdateTime string `json:"update_time"` + Stock int `json:"stock"` + SkuList []*OfficialSku `json:"skuList"` + SaleCount int `json:"sale_count"` + Title string `json:"title"` + Image string `json:"image"` + StateText string `json:"state_text"` + SaleState int `json:"sale_state"` + SaleStartTime string `json:"sale_start_time"` + StateCode int `json:"state_code"` + MerchantID int `json:"merchant_id"` + MerchantName string `json:"merchant_name"` + IsGoodsWarehouse string `json:"is_goods_warehouse"` + AuditReason string `json:"audit_reason"` + CategoryID string `json:"category_id"` + GoodsCode string `json:"goods_code"` +} + +type OfficialSku struct { + SkuID int64 `json:"sku_id"` + GoodsID int64 `json:"goods_id"` + Price string `json:"price"` + CreateTime time.Time `json:"create_time"` + UpdateTime time.Time `json:"update_time"` + Weight string `json:"weight"` + Stock int `json:"stock"` + Indexes string `json:"indexes"` + Sku string `json:"sku"` + SaleCount int `json:"sale_count"` + LinePrice string `json:"line_price"` + SkuCode string `json:"sku_code"` + CloudChainSkuImgUrl string `json:"cloud_chain_sku_img_url"` +} + +type OfficialGoodsConditions struct { //自营加价结构体 + Title string `json:"title"` + CategoryId string `json:"category_id"` + AddPriceType string `json:"add_price_type"` //1按金额增加2按比例增加 + AddPriceBase string `json:"add_price_base"` //加价基数 1按进货价2按利润空间 + AddPriceNum string `json:"add_price_num"` //类型是1就是直接加值,如果是2则是百分比 + NumType string `json:"num_type"` //价格数值设置 1小数点后两位2整数 + Ids []string `json:"ids"` //编辑商品id组 + IsSkuAddPrice string `json:"is_sku_add_price"` + SkusConditions []*OfficialSkuConditions `json:"skus_conditions"` +} +type OfficialSkuConditions struct { + AddPriceType string `json:"add_price_type"` //1按金额增加2按比例增加 + AddPriceBase string `json:"add_price_base"` //加价基数 1按进货价2按利润空间 + AddPriceNum string `json:"add_price_num"` //类型是1就是直接加值,如果是2则是百分比 + NumType string `json:"num_type"` //价格数值设置 1小数点后两位2整数 + SkuCode string `json:"sku_code"` +} +type Conditions struct { + AddPriceType string `json:"add_price_type"` //1按金额增加2按比例增加 + AddPriceBase string `json:"add_price_base"` //加价基数 1按进货价2按利润空间 + AddPriceNum string `json:"add_price_num"` //类型是1就是直接加值,如果是2则是百分比 + NumType string `json:"num_type"` //价格数值设置 1小数点后两位2整数 +} diff --git a/mall/svc/svc_mall_official_goods.go b/mall/svc/svc_mall_official_goods.go new file mode 100644 index 0000000..d4d1b39 --- /dev/null +++ b/mall/svc/svc_mall_official_goods.go @@ -0,0 +1,192 @@ +package svc + +import ( + "applet/app/utils" + "applet/app/utils/logx" + supplySvc "applet/mall/curl_supply/svc" + "applet/mall/db" + "applet/mall/db/model" + "applet/mall/md" + utils2 "applet/mall/utils" + "errors" + "github.com/gin-gonic/gin" + "xorm.io/xorm" +) + +func AddOfficialGoods(c *gin.Context, engine *xorm.Engine, goods *md.OfficialGoods, conditions *md.OfficialGoodsConditions) error { + exist, err2 := engine.Where("supplier_goods_id = ?", goods.ID).Exist(new(model.MallGoods)) + if exist { + return errors.New("该商品已存在无需重复拉取") + } + details, err2 := supplySvc.CurlSupplyGoodsDetails(c, goods.ID) + if err2 != nil { + return err2 + } + if details.BaseGoods.GoodsID <= 0 { + return errors.New("错误的供应链商品id") + } + if details.BaseGoods.MerchantID <= 0 { + return errors.New("错误的供应链站长id") + } + for _, sku := range details.Skus { + if sku.GoodsID <= 0 || sku.SkuId <= 0 { + return errors.New("错误的供应链商品SkuId") + } + } + if details.BaseGoods.SaleState != 1 { + return errors.New("该商品不是销售中状态!") + } + session := engine.NewSession() + defer session.Close() + session.Begin() + var officialGoods model.MallGoods + officialGoods.CategoryId = utils.StrToInt(conditions.CategoryId) + officialGoods.Title = details.BaseGoods.Title + //officialGoods.AuditState = 2 + //imgStringSlice := make([]string, 0) + //imgStringSlice = append(imgStringSlice, goods.Image) + officialGoods.AddPriceRule = utils.SerializeStr(conditions) + var goodsConditions md.Conditions + goodsConditions.AddPriceBase = conditions.AddPriceBase + goodsConditions.AddPriceNum = conditions.AddPriceNum + goodsConditions.AddPriceType = conditions.AddPriceType + goodsConditions.NumType = conditions.NumType + + officialGoods.ImageList = utils.SerializeStr(details.BaseGoods.ImageListURL) + officialGoods.GoodsType = details.BaseGoods.GoodsType + if details.BaseGoods.DetailURL == nil { + details.BaseGoods.DetailURL = make([]string, 0) + } + officialGoods.Detail = utils.SerializeStr(details.BaseGoods.DetailURL) + officialGoods.LinePrice = goods.LinePrice + officialGoods.SupplierMerchantId = details.BaseGoods.MerchantID + officialGoods.SupplierGoodsId = details.BaseGoods.GoodsID + officialGoods.SupplierCategoryId = details.BaseGoods.CategoryID + officialGoods.SupplierMerchantName = details.MallName + officialGoods.AddPrice = calculateAddPriceNum(goods.Price, goods.LinePrice, &goodsConditions) + officialGoods.SaleState = details.BaseGoods.SaleState + officialGoods.Stock = details.BaseGoods.Stock + officialGoods.Pvd = 2 + officialGoods.Weight = goods.Weight + officialGoods.SaleCount = details.BaseGoods.SaleCount + officialGoods.OriginalPrice = details.BaseGoods.Price + officialGoods.Price = utils.AnyToString(utils.StrToFloat64(officialGoods.AddPrice) + utils.StrToFloat64(officialGoods.OriginalPrice)) + officialGoods.Spe = utils.SerializeStr(details.BaseGoods.Spe) + officialGoods.SpeImages = utils.SerializeStr(details.BaseGoods.SpeImagesURL) + officialGoods.IsSpeImageInDetail = details.BaseGoods.IsSpeImageInDetail + officialGoods.IsSpeImageOn = details.BaseGoods.IsSpeImageOn + officialGoods.SaleStartTime = utils2.String2Time(details.BaseGoods.SaleStartTime) + officialGoods.VipDiscountType = 1 + officialGoods.VipDiscountPrice = "0" + officialGoods.SupplierGoodsCode = details.BaseGoods.GoodsCode + officialGoods.CustomProperty = utils.SerializeStr(details.BaseGoods.CustomProperty) + + //todo 是否需要同步运费模板!! + officialGoods.ShippingFee = details.BaseGoods.ShippingFee + officialGoods.ShippingFeeType = details.BaseGoods.ShippingFeeType + officialGoods.ShippingTplId = details.BaseGoods.ShippingTplID + officialGoods.ShippingType = details.BaseGoods.ShippingType + + officialGoods.SupplierMerchantAvatar = details.MallLogo + _, err := session.InsertOne(&officialGoods) + if err != nil { + session.Rollback() + return err + } + if officialGoods.GoodsId == 0 { + session.Rollback() + return errors.New("数据库操作失败!") + } + skuConditionsMap := make(map[string]*md.OfficialSkuConditions) + for _, condition := range conditions.SkusConditions { + skuConditionsMap[condition.SkuCode] = condition + } + for _, sku := range goods.SkuList { + var officialSku model.MallSku + officialSku.GoodsId = officialGoods.GoodsId + officialSku.SaleCount = sku.SaleCount + officialSku.Sku = sku.Sku + officialSku.SupplierGoodsId = sku.GoodsID + officialSku.SupplierSkuId = sku.SkuID + officialSku.Indexes = sku.Indexes + officialSku.Weight = sku.Weight + officialSku.Stock = sku.Stock + + var skuConditions md.Conditions + skuConditions.AddPriceBase = conditions.AddPriceBase + skuConditions.AddPriceNum = conditions.AddPriceNum + skuConditions.AddPriceType = conditions.AddPriceType + skuConditions.NumType = conditions.NumType + if conditions.IsSkuAddPrice == "1" { + officialSkuConditions, ok := skuConditionsMap[sku.SkuCode] + if !ok { + session.Rollback() + return errors.New("SKU编码对应的加价信息获取失败或SKU编码不一致,无法加价!") + } + skuConditions.AddPriceBase = officialSkuConditions.AddPriceBase + skuConditions.AddPriceNum = officialSkuConditions.AddPriceNum + skuConditions.AddPriceType = officialSkuConditions.AddPriceType + skuConditions.NumType = officialSkuConditions.NumType + } + + officialSku.OriginalPrice = sku.Price + officialSku.AddPrice = calculateAddPriceNum(sku.Price, sku.LinePrice, &skuConditions) + officialSku.Price = utils.AnyToString(utils.StrToFloat64(officialSku.AddPrice) + utils.StrToFloat64(officialSku.OriginalPrice)) + + officialSku.SupplierSkuId = sku.SkuID + officialSku.SupplierGoodsId = sku.GoodsID + officialSku.SupplierSkuCode = sku.SkuCode + officialSku.CloudChainSkuImgUrl = sku.CloudChainSkuImgUrl + one, err := session.InsertOne(&officialSku) + if err != nil || one == 0 { + session.Rollback() + return err + } + err = db.AddOrUpdateMallSkuAddPriceRule(session, utils.SerializeStr(&skuConditions), details.BaseGoods.GoodsCode+"-"+sku.SkuCode) + if err != nil { + logx.Error(err) + session.Rollback() + return err + } + } + err = session.Commit() + if err != nil { + session.Rollback() + return err + } + return nil +} + +func calculateAddPriceNum(originalPrice string, linePrice string, conditions *md.Conditions) string { + addPrice := "0.0" + if conditions.AddPriceType == "1" { + if conditions.AddPriceBase == "1" { + addPrice = utils.AnyToString(utils.StrToFloat64(conditions.AddPriceNum)) + } else if conditions.AddPriceBase == "2" { + profits := utils.StrToFloat64(linePrice) - utils.StrToFloat64(originalPrice) + if profits < 0 { + profits = utils.StrToFloat64(originalPrice) - utils.StrToFloat64(linePrice) + } + if profits < 0 { + profits = 0 + } + addPrice = utils.AnyToString(profits + utils.StrToFloat64(conditions.AddPriceNum)) + } + } else if conditions.AddPriceType == "2" { + if conditions.AddPriceBase == "1" { + addPriceRate := utils.StrToFloat64(conditions.AddPriceNum) / 100 + addPrice = utils.AnyToString(utils.StrToFloat64(originalPrice) * addPriceRate) + } else if conditions.AddPriceBase == "2" { + addPriceRate := utils.StrToFloat64(conditions.AddPriceNum) / 100 + profits := utils.StrToFloat64(linePrice) - utils.StrToFloat64(originalPrice) + if profits < 0 { + profits = utils.StrToFloat64(originalPrice) - utils.StrToFloat64(linePrice) + } + if profits < 0 { + profits = 0 + } + addPrice = utils.AnyToString(profits * addPriceRate) + } + } + return addPrice +} diff --git a/mall/svc/svc_sys_cfg_get.go b/mall/svc/svc_sys_cfg_get.go new file mode 100644 index 0000000..d328450 --- /dev/null +++ b/mall/svc/svc_sys_cfg_get.go @@ -0,0 +1,107 @@ +package svc + +import ( + "applet/mall/curl_supply/md" + db2 "applet/supply/db" + "github.com/gin-gonic/gin" + "xorm.io/xorm" + + "applet/app/cfg" + "applet/app/db" + + "applet/app/utils" + "applet/app/utils/cache" +) + +// 单挑记录获取 +func SysCfgGet(c *gin.Context, key string) string { + res := SysCfgFind(c, key) + if _, ok := res[key]; !ok { + return "" + } + return res[key] +} + +// 多条记录获取 +func SysCfgFind(c *gin.Context, keys ...string) map[string]string { + var e *xorm.Engine + if c == nil { + e = db.Db + } else { + e = db.DBs[c.GetString("mid")] + } + res := map[string]string{} + err := cache.GetJson(md.KEY_SYS_CFG_CACHE, &res) + if err != nil || len(res) == 0 { + cfgList, _ := db.SysCfgGetAll(e) + if cfgList == nil { + return nil + } + for _, v := range *cfgList { + res[v.Key] = v.Val + } + // 先不设置缓存 + // cache.SetJson(md.KEY_SYS_CFG_CACHE, res, 60) + } + if len(keys) == 0 { + return res + } + tmp := map[string]string{} + for _, v := range keys { + if val, ok := res[v]; ok { + tmp[v] = val + } else { + tmp[v] = "" + } + } + return tmp +} + +// 清理系统配置信息 +func SysCfgCleanCache() { + cache.Del(md.KEY_SYS_CFG_CACHE) +} + +// 写入系统设置 +func SysCfgSet(c *gin.Context, key, val, memo string) bool { + cfg, err := db.SysCfgGetOne(db.DBs[c.GetString("mid")], key) + if err != nil || cfg == nil { + return db.SysCfgInsert(db.DBs[c.GetString("mid")], key, val, memo) + } + if memo != "" && cfg.Memo != memo { + cfg.Memo = memo + } + SysCfgCleanCache() + return db.SysCfgUpdate(db.DBs[c.GetString("mid")], key, val, cfg.Memo) +} + +// 多条记录获取 +func SysCfgFindByIds(eg *xorm.Engine, keys ...string) map[string]string { + key := utils.Md5(eg.DataSourceName() + md.KEY_SYS_CFG_CACHE) + res := map[string]string{} + c, ok := cfg.MemCache.Get(key).(map[string]string) + if !ok || len(c) == 0 { + cfgList, _ := db2.DbsSysCfgGetAll(eg) + if cfgList == nil { + return nil + } + for _, v := range *cfgList { + res[v.Key] = v.Val + } + cfg.MemCache.Put(key, res, 10) + } else { + res = c + } + if len(keys) == 0 { + return res + } + tmp := map[string]string{} + for _, v := range keys { + if val, ok := res[v]; ok { + tmp[v] = val + } else { + tmp[v] = "" + } + } + return tmp +} diff --git a/mall/tool/json.go b/mall/tool/json.go new file mode 100644 index 0000000..2986f73 --- /dev/null +++ b/mall/tool/json.go @@ -0,0 +1,131 @@ +package tool + +import ( + "bytes" + "encoding/json" + "log" + "regexp" + "strconv" + "strings" + "unicode" +) + +/*************************************** 下划线json ***************************************/ +type JsonSnakeCase struct { + Value interface{} +} + +func MarshalJSON(marshalJson []byte) []byte { + // Regexp definitions + var keyMatchRegex = regexp.MustCompile(`\"(\w+)\":`) + var wordBarrierRegex = regexp.MustCompile(`(\w)([A-Z])`) + converted := keyMatchRegex.ReplaceAllFunc( + marshalJson, + func(match []byte) []byte { + return bytes.ToLower(wordBarrierRegex.ReplaceAll( + match, + []byte(`${1}_${2}`), + )) + }, + ) + return converted +} + +/*************************************** 驼峰json ***************************************/ +type JsonCamelCase struct { + Value interface{} +} + +func (c JsonCamelCase) MarshalJSON() ([]byte, error) { + var keyMatchRegex = regexp.MustCompile(`\"(\w+)\":`) + marshalled, err := json.Marshal(c.Value) + converted := keyMatchRegex.ReplaceAllFunc( + marshalled, + func(match []byte) []byte { + matchStr := string(match) + key := matchStr[1 : len(matchStr)-2] + resKey := Lcfirst(Case2Camel(key)) + return []byte(`"` + resKey + `":`) + }, + ) + return converted, err +} + +/*************************************** 其他方法 ***************************************/ +// 驼峰式写法转为下划线写法 +func Camel2Case(name string) string { + buffer := NewBuffer() + for i, r := range name { + if unicode.IsUpper(r) { + if i != 0 { + buffer.Append('_') + } + buffer.Append(unicode.ToLower(r)) + } else { + buffer.Append(r) + } + } + return buffer.String() +} + +// 下划线写法转为驼峰写法 +func Case2Camel(name string) string { + name = strings.Replace(name, "_", " ", -1) + name = strings.Title(name) + return strings.Replace(name, " ", "", -1) +} + +// 首字母大写 +func Ucfirst(str string) string { + for i, v := range str { + return string(unicode.ToUpper(v)) + str[i+1:] + } + return "" +} + +// 首字母小写 +func Lcfirst(str string) string { + for i, v := range str { + return string(unicode.ToLower(v)) + str[i+1:] + } + return "" +} + +// 内嵌bytes.Buffer,支持连写 +type Buffer struct { + *bytes.Buffer +} + +func NewBuffer() *Buffer { + return &Buffer{Buffer: new(bytes.Buffer)} +} + +func (b *Buffer) Append(i interface{}) *Buffer { + switch val := i.(type) { + case int: + b.append(strconv.Itoa(val)) + case int64: + b.append(strconv.FormatInt(val, 10)) + case uint: + b.append(strconv.FormatUint(uint64(val), 10)) + case uint64: + b.append(strconv.FormatUint(val, 10)) + case string: + b.append(val) + case []byte: + b.Write(val) + case rune: + b.WriteRune(val) + } + return b +} + +func (b *Buffer) append(s string) *Buffer { + defer func() { + if err := recover(); err != nil { + log.Println("*****内存不够了!******") + } + }() + b.WriteString(s) + return b +} diff --git a/mall/tool/time2s.go b/mall/tool/time2s.go new file mode 100644 index 0000000..58dc021 --- /dev/null +++ b/mall/tool/time2s.go @@ -0,0 +1,21 @@ +package tool + +import "time" + +func Time2String(date time.Time, format string) string { + if format == "" { + format = "2006-01-02 15:04:05" + } + timeS := date.Format(format) + if timeS == "0001-01-01 00:00:00" { + return "" + } + return timeS +} +func String2Time(timeStr string) time.Time { + toTime, err := time.ParseInLocation("2006-01-02 15:04:05", timeStr, time.Local) + if err != nil { + return time.Now() + } + return toTime +} diff --git a/mall/utils/aes.go b/mall/utils/aes.go new file mode 100644 index 0000000..90dd506 --- /dev/null +++ b/mall/utils/aes.go @@ -0,0 +1,163 @@ +package utils + +import ( + "applet/app/cfg" + "bytes" + "crypto/aes" + "crypto/cipher" + "encoding/base64" + "encoding/json" + "fmt" +) + +func AesEncrypt(rawData, key []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + blockSize := block.BlockSize() + rawData = PKCS5Padding(rawData, blockSize) + // rawData = ZeroPadding(rawData, block.BlockSize()) + blockMode := cipher.NewCBCEncrypter(block, key[:blockSize]) + encrypted := make([]byte, len(rawData)) + // 根据CryptBlocks方法的说明,如下方式初始化encrypted也可以 + // encrypted := rawData + blockMode.CryptBlocks(encrypted, rawData) + return encrypted, nil +} + +func AesDecrypt(encrypted, key []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + blockSize := block.BlockSize() + blockMode := cipher.NewCBCDecrypter(block, key[:blockSize]) + rawData := make([]byte, len(encrypted)) + // rawData := encrypted + blockMode.CryptBlocks(rawData, encrypted) + rawData = PKCS5UnPadding(rawData) + // rawData = ZeroUnPadding(rawData) + return rawData, nil +} + +func ZeroPadding(cipherText []byte, blockSize int) []byte { + padding := blockSize - len(cipherText)%blockSize + padText := bytes.Repeat([]byte{0}, padding) + return append(cipherText, padText...) +} + +func ZeroUnPadding(rawData []byte) []byte { + length := len(rawData) + unPadding := int(rawData[length-1]) + return rawData[:(length - unPadding)] +} + +func PKCS5Padding(cipherText []byte, blockSize int) []byte { + padding := blockSize - len(cipherText)%blockSize + padText := bytes.Repeat([]byte{byte(padding)}, padding) + return append(cipherText, padText...) +} + +func PKCS5UnPadding(rawData []byte) []byte { + length := len(rawData) + // 去掉最后一个字节 unPadding 次 + unPadding := int(rawData[length-1]) + return rawData[:(length - unPadding)] +} + +// 填充0 +func zeroFill(key *string) { + l := len(*key) + if l != 16 && l != 24 && l != 32 { + if l < 16 { + *key = *key + fmt.Sprintf("%0*d", 16-l, 0) + } else if l < 24 { + *key = *key + fmt.Sprintf("%0*d", 24-l, 0) + } else if l < 32 { + *key = *key + fmt.Sprintf("%0*d", 32-l, 0) + } else { + *key = string([]byte(*key)[:32]) + } + } +} + +type AesCrypt struct { + Key []byte + Iv []byte +} + +func (a *AesCrypt) Encrypt(data []byte) ([]byte, error) { + aesBlockEncrypt, err := aes.NewCipher(a.Key) + if err != nil { + println(err.Error()) + return nil, err + } + + content := pKCS5Padding(data, aesBlockEncrypt.BlockSize()) + cipherBytes := make([]byte, len(content)) + aesEncrypt := cipher.NewCBCEncrypter(aesBlockEncrypt, a.Iv) + aesEncrypt.CryptBlocks(cipherBytes, content) + return cipherBytes, nil +} + +func (a *AesCrypt) Decrypt(src []byte) (data []byte, err error) { + decrypted := make([]byte, len(src)) + var aesBlockDecrypt cipher.Block + aesBlockDecrypt, err = aes.NewCipher(a.Key) + if err != nil { + println(err.Error()) + return nil, err + } + aesDecrypt := cipher.NewCBCDecrypter(aesBlockDecrypt, a.Iv) + aesDecrypt.CryptBlocks(decrypted, src) + return pKCS5Trimming(decrypted), nil +} + +func pKCS5Padding(cipherText []byte, blockSize int) []byte { + padding := blockSize - len(cipherText)%blockSize + padText := bytes.Repeat([]byte{byte(padding)}, padding) + return append(cipherText, padText...) +} + +func pKCS5Trimming(encrypt []byte) []byte { + padding := encrypt[len(encrypt)-1] + return encrypt[:len(encrypt)-int(padding)] +} + +// AesAdminCurlPOST is 与后台接口加密交互 +func AesAdminCurlPOST(aesData string, url string) ([]byte, error) { + adminKey := cfg.Admin.AesKey + adminVI := cfg.Admin.AesIV + crypto := AesCrypt{ + Key: []byte(adminKey), + Iv: []byte(adminVI), + } + + encrypt, err := crypto.Encrypt([]byte(aesData)) + if err != nil { + return nil, err + } + + // 发送请求到后台 + postData := map[string]string{ + "postData": base64.StdEncoding.EncodeToString(encrypt), + } + postDataByte, _ := json.Marshal(postData) + rdata, err := CurlPost(url, postDataByte, nil) + if err != nil { + return nil, err + } + + pass, err := base64.StdEncoding.DecodeString(string(rdata)) + if err != nil { + return nil, err + } + + decrypt, err := crypto.Decrypt(pass) + + if err != nil { + return nil, err + } + return decrypt, nil +} diff --git a/mall/utils/base64.go b/mall/utils/base64.go new file mode 100644 index 0000000..2948d52 --- /dev/null +++ b/mall/utils/base64.go @@ -0,0 +1,108 @@ +package utils + +import ( + "encoding/base64" + "fmt" + "io/ioutil" + "net/http" +) + +const ( + Base64Std = iota + Base64Url + Base64RawStd + Base64RawUrl +) + +func Base64StdEncode(str interface{}) string { + return Base64Encode(str, Base64Std) +} + +func Base64StdDecode(str interface{}) string { + return Base64Decode(str, Base64Std) +} + +func Base64UrlEncode(str interface{}) string { + return Base64Encode(str, Base64Url) +} + +func Base64UrlDecode(str interface{}) string { + return Base64Decode(str, Base64Url) +} + +func Base64RawStdEncode(str interface{}) string { + return Base64Encode(str, Base64RawStd) +} + +func Base64RawStdDecode(str interface{}) string { + return Base64Decode(str, Base64RawStd) +} + +func Base64RawUrlEncode(str interface{}) string { + return Base64Encode(str, Base64RawUrl) +} + +func Base64RawUrlDecode(str interface{}) string { + return Base64Decode(str, Base64RawUrl) +} + +func Base64Encode(str interface{}, encode int) string { + newEncode := base64Encode(encode) + if newEncode == nil { + return "" + } + switch v := str.(type) { + case string: + return newEncode.EncodeToString([]byte(v)) + case []byte: + return newEncode.EncodeToString(v) + } + return newEncode.EncodeToString([]byte(fmt.Sprint(str))) +} + +func Base64Decode(str interface{}, encode int) string { + var err error + var b []byte + newEncode := base64Encode(encode) + if newEncode == nil { + return "" + } + switch v := str.(type) { + case string: + b, err = newEncode.DecodeString(v) + case []byte: + b, err = newEncode.DecodeString(string(v)) + default: + return "" + } + if err != nil { + return "" + } + return string(b) +} + +func base64Encode(encode int) *base64.Encoding { + switch encode { + case Base64Std: + return base64.StdEncoding + case Base64Url: + return base64.URLEncoding + case Base64RawStd: + return base64.RawStdEncoding + case Base64RawUrl: + return base64.RawURLEncoding + default: + return nil + } +} + +func GetBase64FromUrlForImage(url string) (string, error) { + res, err := http.Get(url) + if err != nil { + return "", err + } + defer res.Body.Close() + data, _ := ioutil.ReadAll(res.Body) + imageBase64 := base64.StdEncoding.EncodeToString(data) + return imageBase64, nil +} diff --git a/mall/utils/boolean.go b/mall/utils/boolean.go new file mode 100644 index 0000000..fdfd986 --- /dev/null +++ b/mall/utils/boolean.go @@ -0,0 +1,26 @@ +package utils + +import "reflect" + +// 检验一个值是否为空 +func Empty(val interface{}) bool { + v := reflect.ValueOf(val) + switch v.Kind() { + case reflect.String, reflect.Array: + return v.Len() == 0 + case reflect.Map, reflect.Slice: + return v.Len() == 0 || v.IsNil() + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + } + + return reflect.DeepEqual(val, reflect.Zero(v.Type()).Interface()) +} diff --git a/mall/utils/cache/base.go b/mall/utils/cache/base.go new file mode 100644 index 0000000..64648dd --- /dev/null +++ b/mall/utils/cache/base.go @@ -0,0 +1,421 @@ +package cache + +import ( + "errors" + "fmt" + "strconv" + "time" +) + +const ( + redisDialTTL = 10 * time.Second + redisReadTTL = 3 * time.Second + redisWriteTTL = 3 * time.Second + redisIdleTTL = 10 * time.Second + redisPoolTTL = 10 * time.Second + redisPoolSize int = 512 + redisMaxIdleConn int = 64 + redisMaxActive int = 512 +) + +var ( + ErrNil = errors.New("nil return") + ErrWrongArgsNum = errors.New("args num error") + ErrNegativeInt = errors.New("redis cluster: unexpected value for Uint64") +) + +// 以下为提供类型转换 + +func Int(reply interface{}, err error) (int, error) { + if err != nil { + return 0, err + } + switch reply := reply.(type) { + case int: + return reply, nil + case int8: + return int(reply), nil + case int16: + return int(reply), nil + case int32: + return int(reply), nil + case int64: + x := int(reply) + if int64(x) != reply { + return 0, strconv.ErrRange + } + return x, nil + case uint: + n := int(reply) + if n < 0 { + return 0, strconv.ErrRange + } + return n, nil + case uint8: + return int(reply), nil + case uint16: + return int(reply), nil + case uint32: + n := int(reply) + if n < 0 { + return 0, strconv.ErrRange + } + return n, nil + case uint64: + n := int(reply) + if n < 0 { + return 0, strconv.ErrRange + } + return n, nil + case []byte: + data := string(reply) + if len(data) == 0 { + return 0, ErrNil + } + + n, err := strconv.ParseInt(data, 10, 0) + return int(n), err + case string: + if len(reply) == 0 { + return 0, ErrNil + } + + n, err := strconv.ParseInt(reply, 10, 0) + return int(n), err + case nil: + return 0, ErrNil + case error: + return 0, reply + } + return 0, fmt.Errorf("redis cluster: unexpected type for Int, got type %T", reply) +} + +func Int64(reply interface{}, err error) (int64, error) { + if err != nil { + return 0, err + } + switch reply := reply.(type) { + case int: + return int64(reply), nil + case int8: + return int64(reply), nil + case int16: + return int64(reply), nil + case int32: + return int64(reply), nil + case int64: + return reply, nil + case uint: + n := int64(reply) + if n < 0 { + return 0, strconv.ErrRange + } + return n, nil + case uint8: + return int64(reply), nil + case uint16: + return int64(reply), nil + case uint32: + return int64(reply), nil + case uint64: + n := int64(reply) + if n < 0 { + return 0, strconv.ErrRange + } + return n, nil + case []byte: + data := string(reply) + if len(data) == 0 { + return 0, ErrNil + } + + n, err := strconv.ParseInt(data, 10, 64) + return n, err + case string: + if len(reply) == 0 { + return 0, ErrNil + } + + n, err := strconv.ParseInt(reply, 10, 64) + return n, err + case nil: + return 0, ErrNil + case error: + return 0, reply + } + return 0, fmt.Errorf("redis cluster: unexpected type for Int64, got type %T", reply) +} + +func Uint64(reply interface{}, err error) (uint64, error) { + if err != nil { + return 0, err + } + switch reply := reply.(type) { + case uint: + return uint64(reply), nil + case uint8: + return uint64(reply), nil + case uint16: + return uint64(reply), nil + case uint32: + return uint64(reply), nil + case uint64: + return reply, nil + case int: + if reply < 0 { + return 0, ErrNegativeInt + } + return uint64(reply), nil + case int8: + if reply < 0 { + return 0, ErrNegativeInt + } + return uint64(reply), nil + case int16: + if reply < 0 { + return 0, ErrNegativeInt + } + return uint64(reply), nil + case int32: + if reply < 0 { + return 0, ErrNegativeInt + } + return uint64(reply), nil + case int64: + if reply < 0 { + return 0, ErrNegativeInt + } + return uint64(reply), nil + case []byte: + data := string(reply) + if len(data) == 0 { + return 0, ErrNil + } + + n, err := strconv.ParseUint(data, 10, 64) + return n, err + case string: + if len(reply) == 0 { + return 0, ErrNil + } + + n, err := strconv.ParseUint(reply, 10, 64) + return n, err + case nil: + return 0, ErrNil + case error: + return 0, reply + } + return 0, fmt.Errorf("redis cluster: unexpected type for Uint64, got type %T", reply) +} + +func Float64(reply interface{}, err error) (float64, error) { + if err != nil { + return 0, err + } + + var value float64 + err = nil + switch v := reply.(type) { + case float32: + value = float64(v) + case float64: + value = v + case int: + value = float64(v) + case int8: + value = float64(v) + case int16: + value = float64(v) + case int32: + value = float64(v) + case int64: + value = float64(v) + case uint: + value = float64(v) + case uint8: + value = float64(v) + case uint16: + value = float64(v) + case uint32: + value = float64(v) + case uint64: + value = float64(v) + case []byte: + data := string(v) + if len(data) == 0 { + return 0, ErrNil + } + value, err = strconv.ParseFloat(string(v), 64) + case string: + if len(v) == 0 { + return 0, ErrNil + } + value, err = strconv.ParseFloat(v, 64) + case nil: + err = ErrNil + case error: + err = v + default: + err = fmt.Errorf("redis cluster: unexpected type for Float64, got type %T", v) + } + + return value, err +} + +func Bool(reply interface{}, err error) (bool, error) { + if err != nil { + return false, err + } + switch reply := reply.(type) { + case bool: + return reply, nil + case int64: + return reply != 0, nil + case []byte: + data := string(reply) + if len(data) == 0 { + return false, ErrNil + } + + return strconv.ParseBool(data) + case string: + if len(reply) == 0 { + return false, ErrNil + } + + return strconv.ParseBool(reply) + case nil: + return false, ErrNil + case error: + return false, reply + } + return false, fmt.Errorf("redis cluster: unexpected type for Bool, got type %T", reply) +} + +func Bytes(reply interface{}, err error) ([]byte, error) { + if err != nil { + return nil, err + } + switch reply := reply.(type) { + case []byte: + if len(reply) == 0 { + return nil, ErrNil + } + return reply, nil + case string: + data := []byte(reply) + if len(data) == 0 { + return nil, ErrNil + } + return data, nil + case nil: + return nil, ErrNil + case error: + return nil, reply + } + return nil, fmt.Errorf("redis cluster: unexpected type for Bytes, got type %T", reply) +} + +func String(reply interface{}, err error) (string, error) { + if err != nil { + return "", err + } + + value := "" + err = nil + switch v := reply.(type) { + case string: + if len(v) == 0 { + return "", ErrNil + } + + value = v + case []byte: + if len(v) == 0 { + return "", ErrNil + } + + value = string(v) + case int: + value = strconv.FormatInt(int64(v), 10) + case int8: + value = strconv.FormatInt(int64(v), 10) + case int16: + value = strconv.FormatInt(int64(v), 10) + case int32: + value = strconv.FormatInt(int64(v), 10) + case int64: + value = strconv.FormatInt(v, 10) + case uint: + value = strconv.FormatUint(uint64(v), 10) + case uint8: + value = strconv.FormatUint(uint64(v), 10) + case uint16: + value = strconv.FormatUint(uint64(v), 10) + case uint32: + value = strconv.FormatUint(uint64(v), 10) + case uint64: + value = strconv.FormatUint(v, 10) + case float32: + value = strconv.FormatFloat(float64(v), 'f', -1, 32) + case float64: + value = strconv.FormatFloat(v, 'f', -1, 64) + case bool: + value = strconv.FormatBool(v) + case nil: + err = ErrNil + case error: + err = v + default: + err = fmt.Errorf("redis cluster: unexpected type for String, got type %T", v) + } + + return value, err +} + +func Strings(reply interface{}, err error) ([]string, error) { + if err != nil { + return nil, err + } + switch reply := reply.(type) { + case []interface{}: + result := make([]string, len(reply)) + for i := range reply { + if reply[i] == nil { + continue + } + switch subReply := reply[i].(type) { + case string: + result[i] = subReply + case []byte: + result[i] = string(subReply) + default: + return nil, fmt.Errorf("redis cluster: unexpected element type for String, got type %T", reply[i]) + } + } + return result, nil + case []string: + return reply, nil + case nil: + return nil, ErrNil + case error: + return nil, reply + } + return nil, fmt.Errorf("redis cluster: unexpected type for Strings, got type %T", reply) +} + +func Values(reply interface{}, err error) ([]interface{}, error) { + if err != nil { + return nil, err + } + switch reply := reply.(type) { + case []interface{}: + return reply, nil + case nil: + return nil, ErrNil + case error: + return nil, reply + } + return nil, fmt.Errorf("redis cluster: unexpected type for Values, got type %T", reply) +} diff --git a/mall/utils/cache/cache/cache.go b/mall/utils/cache/cache/cache.go new file mode 100644 index 0000000..e43c5f0 --- /dev/null +++ b/mall/utils/cache/cache/cache.go @@ -0,0 +1,107 @@ +package cache + +import ( + "fmt" + "time" +) + +var c Cache + +type Cache interface { + // get cached value by key. + Get(key string) interface{} + // GetMulti is a batch version of Get. + GetMulti(keys []string) []interface{} + // set cached value with key and expire time. + Put(key string, val interface{}, timeout time.Duration) error + // delete cached value by key. + Delete(key string) error + // increase cached int value by key, as a counter. + Incr(key string) error + // decrease cached int value by key, as a counter. + Decr(key string) error + // check if cached value exists or not. + IsExist(key string) bool + // clear all cache. + ClearAll() error + // start gc routine based on config string settings. + StartAndGC(config string) error +} + +// Instance is a function create a new Cache Instance +type Instance func() Cache + +var adapters = make(map[string]Instance) + +// Register makes a cache adapter available by the adapter name. +// If Register is called twice with the same name or if driver is nil, +// it panics. +func Register(name string, adapter Instance) { + if adapter == nil { + panic("cache: Register adapter is nil") + } + if _, ok := adapters[name]; ok { + panic("cache: Register called twice for adapter " + name) + } + adapters[name] = adapter +} + +// NewCache Create a new cache driver by adapter name and config string. +// config need to be correct JSON as string: {"interval":360}. +// it will start gc automatically. +func NewCache(adapterName, config string) (adapter Cache, err error) { + instanceFunc, ok := adapters[adapterName] + if !ok { + err = fmt.Errorf("cache: unknown adapter name %q (forgot to import?)", adapterName) + return + } + adapter = instanceFunc() + err = adapter.StartAndGC(config) + if err != nil { + adapter = nil + } + return +} + +func InitCache(adapterName, config string) (err error) { + instanceFunc, ok := adapters[adapterName] + if !ok { + err = fmt.Errorf("cache: unknown adapter name %q (forgot to import?)", adapterName) + return + } + c = instanceFunc() + err = c.StartAndGC(config) + if err != nil { + c = nil + } + return +} + +func Get(key string) interface{} { + return c.Get(key) +} + +func GetMulti(keys []string) []interface{} { + return c.GetMulti(keys) +} +func Put(key string, val interface{}, ttl time.Duration) error { + return c.Put(key, val, ttl) +} +func Delete(key string) error { + return c.Delete(key) +} +func Incr(key string) error { + return c.Incr(key) +} +func Decr(key string) error { + return c.Decr(key) +} +func IsExist(key string) bool { + return c.IsExist(key) +} +func ClearAll() error { + return c.ClearAll() +} +func StartAndGC(cfg string) error { + return c.StartAndGC(cfg) +} diff --git a/mall/utils/cache/cache/conv.go b/mall/utils/cache/cache/conv.go new file mode 100644 index 0000000..6b700ae --- /dev/null +++ b/mall/utils/cache/cache/conv.go @@ -0,0 +1,86 @@ +package cache + +import ( + "fmt" + "strconv" +) + +// GetString convert interface to string. +func GetString(v interface{}) string { + switch result := v.(type) { + case string: + return result + case []byte: + return string(result) + default: + if v != nil { + return fmt.Sprint(result) + } + } + return "" +} + +// GetInt convert interface to int. +func GetInt(v interface{}) int { + switch result := v.(type) { + case int: + return result + case int32: + return int(result) + case int64: + return int(result) + default: + if d := GetString(v); d != "" { + value, _ := strconv.Atoi(d) + return value + } + } + return 0 +} + +// GetInt64 convert interface to int64. +func GetInt64(v interface{}) int64 { + switch result := v.(type) { + case int: + return int64(result) + case int32: + return int64(result) + case int64: + return result + default: + + if d := GetString(v); d != "" { + value, _ := strconv.ParseInt(d, 10, 64) + return value + } + } + return 0 +} + +// GetFloat64 convert interface to float64. +func GetFloat64(v interface{}) float64 { + switch result := v.(type) { + case float64: + return result + default: + if d := GetString(v); d != "" { + value, _ := strconv.ParseFloat(d, 64) + return value + } + } + return 0 +} + +// GetBool convert interface to bool. +func GetBool(v interface{}) bool { + switch result := v.(type) { + case bool: + return result + default: + if d := GetString(v); d != "" { + value, _ := strconv.ParseBool(d) + return value + } + } + return false +} diff --git a/mall/utils/cache/cache/file.go b/mall/utils/cache/cache/file.go new file mode 100644 index 0000000..5c4e366 --- /dev/null +++ b/mall/utils/cache/cache/file.go @@ -0,0 +1,241 @@ +package cache + +import ( + "bytes" + "crypto/md5" + "encoding/gob" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "reflect" + "strconv" + "time" +) + +// FileCacheItem is basic unit of file cache adapter. +// it contains data and expire time. +type FileCacheItem struct { + Data interface{} + LastAccess time.Time + Expired time.Time +} + +// FileCache Config +var ( + FileCachePath = "cache" // cache directory + FileCacheFileSuffix = ".bin" // cache file suffix + FileCacheDirectoryLevel = 2 // cache file deep level if auto generated cache files. + FileCacheEmbedExpiry time.Duration // cache expire time, default is no expire forever. +) + +// FileCache is cache adapter for file storage. +type FileCache struct { + CachePath string + FileSuffix string + DirectoryLevel int + EmbedExpiry int +} + +// NewFileCache Create new file cache with no config. +// the level and expiry need set in method StartAndGC as config string. +func NewFileCache() Cache { + // return &FileCache{CachePath:FileCachePath, FileSuffix:FileCacheFileSuffix} + return &FileCache{} +} + +// StartAndGC will start and begin gc for file cache. +// the config need to be like {CachePath:"/cache","FileSuffix":".bin","DirectoryLevel":2,"EmbedExpiry":0} +func (fc *FileCache) StartAndGC(config string) error { + + var cfg map[string]string + json.Unmarshal([]byte(config), &cfg) + if _, ok := cfg["CachePath"]; !ok { + cfg["CachePath"] = FileCachePath + } + if _, ok := cfg["FileSuffix"]; !ok { + cfg["FileSuffix"] = FileCacheFileSuffix + } + if _, ok := cfg["DirectoryLevel"]; !ok { + cfg["DirectoryLevel"] = strconv.Itoa(FileCacheDirectoryLevel) + } + if _, ok := cfg["EmbedExpiry"]; !ok { + cfg["EmbedExpiry"] = strconv.FormatInt(int64(FileCacheEmbedExpiry.Seconds()), 10) + } + fc.CachePath = cfg["CachePath"] + fc.FileSuffix = cfg["FileSuffix"] + fc.DirectoryLevel, _ = strconv.Atoi(cfg["DirectoryLevel"]) + fc.EmbedExpiry, _ = strconv.Atoi(cfg["EmbedExpiry"]) + + fc.Init() + return nil +} + +// Init will make new dir for file cache if not exist. +func (fc *FileCache) Init() { + if ok, _ := exists(fc.CachePath); !ok { // todo : error handle + _ = os.MkdirAll(fc.CachePath, os.ModePerm) // todo : error handle + } +} + +// get cached file name. it's md5 encoded. +func (fc *FileCache) getCacheFileName(key string) string { + m := md5.New() + io.WriteString(m, key) + keyMd5 := hex.EncodeToString(m.Sum(nil)) + cachePath := fc.CachePath + switch fc.DirectoryLevel { + case 2: + cachePath = filepath.Join(cachePath, keyMd5[0:2], keyMd5[2:4]) + case 1: + cachePath = filepath.Join(cachePath, keyMd5[0:2]) + } + + if ok, _ := exists(cachePath); !ok { // todo : error handle + _ = os.MkdirAll(cachePath, os.ModePerm) // todo : error handle + } + + return filepath.Join(cachePath, fmt.Sprintf("%s%s", keyMd5, fc.FileSuffix)) +} + +// Get value from file cache. +// if non-exist or expired, return empty string. +func (fc *FileCache) Get(key string) interface{} { + fileData, err := FileGetContents(fc.getCacheFileName(key)) + if err != nil { + return "" + } + var to FileCacheItem + GobDecode(fileData, &to) + if to.Expired.Before(time.Now()) { + return "" + } + return to.Data +} + +// GetMulti gets values from file cache. +// if non-exist or expired, return empty string. +func (fc *FileCache) GetMulti(keys []string) []interface{} { + var rc []interface{} + for _, key := range keys { + rc = append(rc, fc.Get(key)) + } + return rc +} + +// Put value into file cache. +// timeout means how long to keep this file, unit of ms. +// if timeout equals FileCacheEmbedExpiry(default is 0), cache this item forever. +func (fc *FileCache) Put(key string, val interface{}, timeout time.Duration) error { + gob.Register(val) + + item := FileCacheItem{Data: val} + if timeout == FileCacheEmbedExpiry { + item.Expired = time.Now().Add((86400 * 365 * 10) * time.Second) // ten years + } else { + item.Expired = time.Now().Add(timeout) + } + item.LastAccess = time.Now() + data, err := GobEncode(item) + if err != nil { + return err + } + return FilePutContents(fc.getCacheFileName(key), data) +} + +// Delete file cache value. +func (fc *FileCache) Delete(key string) error { + filename := fc.getCacheFileName(key) + if ok, _ := exists(filename); ok { + return os.Remove(filename) + } + return nil +} + +// Incr will increase cached int value. +// fc value is saving forever unless Delete. +func (fc *FileCache) Incr(key string) error { + data := fc.Get(key) + var incr int + if reflect.TypeOf(data).Name() != "int" { + incr = 0 + } else { + incr = data.(int) + 1 + } + fc.Put(key, incr, FileCacheEmbedExpiry) + return nil +} + +// Decr will decrease cached int value. +func (fc *FileCache) Decr(key string) error { + data := fc.Get(key) + var decr int + if reflect.TypeOf(data).Name() != "int" || data.(int)-1 <= 0 { + decr = 0 + } else { + decr = data.(int) - 1 + } + fc.Put(key, decr, FileCacheEmbedExpiry) + return nil +} + +// IsExist check value is exist. +func (fc *FileCache) IsExist(key string) bool { + ret, _ := exists(fc.getCacheFileName(key)) + return ret +} + +// ClearAll will clean cached files. +// not implemented. +func (fc *FileCache) ClearAll() error { + return nil +} + +// check file exist. +func exists(path string) (bool, error) { + _, err := os.Stat(path) + if err == nil { + return true, nil + } + if os.IsNotExist(err) { + return false, nil + } + return false, err +} + +// FileGetContents Get bytes to file. +// if non-exist, create this file. +func FileGetContents(filename string) (data []byte, e error) { + return ioutil.ReadFile(filename) +} + +// FilePutContents Put bytes to file. +// if non-exist, create this file. +func FilePutContents(filename string, content []byte) error { + return ioutil.WriteFile(filename, content, os.ModePerm) +} + +// GobEncode Gob encodes file cache item. +func GobEncode(data interface{}) ([]byte, error) { + buf := bytes.NewBuffer(nil) + enc := gob.NewEncoder(buf) + err := enc.Encode(data) + if err != nil { + return nil, err + } + return buf.Bytes(), err +} + +// GobDecode Gob decodes file cache item. +func GobDecode(data []byte, to *FileCacheItem) error { + buf := bytes.NewBuffer(data) + dec := gob.NewDecoder(buf) + return dec.Decode(&to) +} + +func init() { + Register("file", NewFileCache) +} diff --git a/mall/utils/cache/cache/memory.go b/mall/utils/cache/cache/memory.go new file mode 100644 index 0000000..0cc5015 --- /dev/null +++ b/mall/utils/cache/cache/memory.go @@ -0,0 +1,239 @@ +package cache + +import ( + "encoding/json" + "errors" + "sync" + "time" +) + +var ( + // DefaultEvery means the clock time of recycling the expired cache items in memory. + DefaultEvery = 60 // 1 minute +) + +// MemoryItem store memory cache item. +type MemoryItem struct { + val interface{} + createdTime time.Time + lifespan time.Duration +} + +func (mi *MemoryItem) isExpire() bool { + // 0 means forever + if mi.lifespan == 0 { + return false + } + return time.Now().Sub(mi.createdTime) > mi.lifespan +} + +// MemoryCache is Memory cache adapter. +// it contains a RW locker for safe map storage. +type MemoryCache struct { + sync.RWMutex + dur time.Duration + items map[string]*MemoryItem + Every int // run an expiration check Every clock time +} + +// NewMemoryCache returns a new MemoryCache. +func NewMemoryCache() Cache { + cache := MemoryCache{items: make(map[string]*MemoryItem)} + return &cache +} + +// Get cache from memory. +// if non-existed or expired, return nil. +func (bc *MemoryCache) Get(name string) interface{} { + bc.RLock() + defer bc.RUnlock() + if itm, ok := bc.items[name]; ok { + if itm.isExpire() { + return nil + } + return itm.val + } + return nil +} + +// GetMulti gets caches from memory. +// if non-existed or expired, return nil. +func (bc *MemoryCache) GetMulti(names []string) []interface{} { + var rc []interface{} + for _, name := range names { + rc = append(rc, bc.Get(name)) + } + return rc +} + +// Put cache to memory. +// if lifespan is 0, it will be forever till restart. +func (bc *MemoryCache) Put(name string, value interface{}, lifespan time.Duration) error { + bc.Lock() + defer bc.Unlock() + bc.items[name] = &MemoryItem{ + val: value, + createdTime: time.Now(), + lifespan: lifespan, + } + return nil +} + +// Delete cache in memory. +func (bc *MemoryCache) Delete(name string) error { + bc.Lock() + defer bc.Unlock() + if _, ok := bc.items[name]; !ok { + return errors.New("key not exist") + } + delete(bc.items, name) + if _, ok := bc.items[name]; ok { + return errors.New("delete key error") + } + return nil +} + +// Incr increase cache counter in memory. +// it supports int,int32,int64,uint,uint32,uint64. +func (bc *MemoryCache) Incr(key string) error { + bc.RLock() + defer bc.RUnlock() + itm, ok := bc.items[key] + if !ok { + return errors.New("key not exist") + } + switch itm.val.(type) { + case int: + itm.val = itm.val.(int) + 1 + case int32: + itm.val = itm.val.(int32) + 1 + case int64: + itm.val = itm.val.(int64) + 1 + case uint: + itm.val = itm.val.(uint) + 1 + case uint32: + itm.val = itm.val.(uint32) + 1 + case uint64: + itm.val = itm.val.(uint64) + 1 + default: + return errors.New("item val is not (u)int (u)int32 (u)int64") + } + return nil +} + +// Decr decrease counter in memory. +func (bc *MemoryCache) Decr(key string) error { + bc.RLock() + defer bc.RUnlock() + itm, ok := bc.items[key] + if !ok { + return errors.New("key not exist") + } + switch itm.val.(type) { + case int: + itm.val = itm.val.(int) - 1 + case int64: + itm.val = itm.val.(int64) - 1 + case int32: + itm.val = itm.val.(int32) - 1 + case uint: + if itm.val.(uint) > 0 { + itm.val = itm.val.(uint) - 1 + } else { + return errors.New("item val is less than 0") + } + case uint32: + if itm.val.(uint32) > 0 { + itm.val = itm.val.(uint32) - 1 + } else { + return errors.New("item val is less than 0") + } + case uint64: + if itm.val.(uint64) > 0 { + itm.val = itm.val.(uint64) - 1 + } else { + return errors.New("item val is less than 0") + } + default: + return errors.New("item val is not int int64 int32") + } + return nil +} + +// IsExist check cache exist in memory. +func (bc *MemoryCache) IsExist(name string) bool { + bc.RLock() + defer bc.RUnlock() + if v, ok := bc.items[name]; ok { + return !v.isExpire() + } + return false +} + +// ClearAll will delete all cache in memory. +func (bc *MemoryCache) ClearAll() error { + bc.Lock() + defer bc.Unlock() + bc.items = make(map[string]*MemoryItem) + return nil +} + +// StartAndGC start memory cache. it will check expiration in every clock time. +func (bc *MemoryCache) StartAndGC(config string) error { + var cf map[string]int + json.Unmarshal([]byte(config), &cf) + if _, ok := cf["interval"]; !ok { + cf = make(map[string]int) + cf["interval"] = DefaultEvery + } + dur := time.Duration(cf["interval"]) * time.Second + bc.Every = cf["interval"] + bc.dur = dur + go bc.vacuum() + return nil +} + +// check expiration. +func (bc *MemoryCache) vacuum() { + bc.RLock() + every := bc.Every + bc.RUnlock() + + if every < 1 { + return + } + for { + <-time.After(bc.dur) + if bc.items == nil { + return + } + if keys := bc.expiredKeys(); len(keys) != 0 { + bc.clearItems(keys) + } + } +} + +// expiredKeys returns key list which are expired. +func (bc *MemoryCache) expiredKeys() (keys []string) { + bc.RLock() + defer bc.RUnlock() + for key, itm := range bc.items { + if itm.isExpire() { + keys = append(keys, key) + } + } + return +} + +// clearItems removes all the items which key in keys. +func (bc *MemoryCache) clearItems(keys []string) { + bc.Lock() + defer bc.Unlock() + for _, key := range keys { + delete(bc.items, key) + } +} + +func init() { + Register("memory", NewMemoryCache) +} diff --git a/mall/utils/cache/redis.go b/mall/utils/cache/redis.go new file mode 100644 index 0000000..4e5f047 --- /dev/null +++ b/mall/utils/cache/redis.go @@ -0,0 +1,403 @@ +package cache + +import ( + "encoding/json" + "errors" + "log" + "strings" + "time" + + redigo "github.com/gomodule/redigo/redis" +) + +// configuration +type Config struct { + Server string + Password string + MaxIdle int // Maximum number of idle connections in the pool. + + // Maximum number of connections allocated by the pool at a given time. + // When zero, there is no limit on the number of connections in the pool. + MaxActive int + + // Close connections after remaining idle for this duration. If the value + // is zero, then idle connections are not closed. Applications should set + // the timeout to a value less than the server's timeout. + IdleTimeout time.Duration + + // If Wait is true and the pool is at the MaxActive limit, then Get() waits + // for a connection to be returned to the pool before returning. + Wait bool + KeyPrefix string // prefix to all keys; example is "dev environment name" + KeyDelimiter string // delimiter to be used while appending keys; example is ":" + KeyPlaceholder string // placeholder to be parsed using given arguments to obtain a final key; example is "?" +} + +var pool *redigo.Pool +var conf *Config + +func NewRedis(addr string) { + if addr == "" { + panic("\nredis connect string cannot be empty\n") + } + pool = &redigo.Pool{ + MaxIdle: redisMaxIdleConn, + IdleTimeout: redisIdleTTL, + MaxActive: redisMaxActive, + // MaxConnLifetime: redisDialTTL, + Wait: true, + Dial: func() (redigo.Conn, error) { + c, err := redigo.Dial("tcp", addr, + redigo.DialConnectTimeout(redisDialTTL), + redigo.DialReadTimeout(redisReadTTL), + redigo.DialWriteTimeout(redisWriteTTL), + ) + if err != nil { + log.Println("Redis Dial failed: ", err) + return nil, err + } + return c, err + }, + TestOnBorrow: func(c redigo.Conn, t time.Time) error { + _, err := c.Do("PING") + if err != nil { + log.Println("Unable to ping to redis server:", err) + } + return err + }, + } + conn := pool.Get() + defer conn.Close() + if conn.Err() != nil { + println("\nredis connect " + addr + " error: " + conn.Err().Error()) + } else { + println("\nredis connect " + addr + " success!\n") + } +} + +func Do(cmd string, args ...interface{}) (reply interface{}, err error) { + conn := pool.Get() + defer conn.Close() + return conn.Do(cmd, args...) +} + +func GetPool() *redigo.Pool { + return pool +} + +func ParseKey(key string, vars []string) (string, error) { + arr := strings.Split(key, conf.KeyPlaceholder) + actualKey := "" + if len(arr) != len(vars)+1 { + return "", errors.New("redis/connection.go: Insufficient arguments to parse key") + } else { + for index, val := range arr { + if index == 0 { + actualKey = arr[index] + } else { + actualKey += vars[index-1] + val + } + } + } + return getPrefixedKey(actualKey), nil +} + +func getPrefixedKey(key string) string { + return conf.KeyPrefix + conf.KeyDelimiter + key +} +func StripEnvKey(key string) string { + return strings.TrimLeft(key, conf.KeyPrefix+conf.KeyDelimiter) +} +func SplitKey(key string) []string { + return strings.Split(key, conf.KeyDelimiter) +} +func Expire(key string, ttl int) (interface{}, error) { + return Do("EXPIRE", key, ttl) +} +func Persist(key string) (interface{}, error) { + return Do("PERSIST", key) +} + +func Del(key string) (interface{}, error) { + return Do("DEL", key) +} +func Set(key string, data interface{}) (interface{}, error) { + // set + return Do("SET", key, data) +} +func SetNX(key string, data interface{}) (interface{}, error) { + return Do("SETNX", key, data) +} +func SetEx(key string, data interface{}, ttl int) (interface{}, error) { + return Do("SETEX", key, ttl, data) +} + +func SetJson(key string, data interface{}, ttl int) bool { + c, err := json.Marshal(data) + if err != nil { + return false + } + if ttl < 1 { + _, err = Set(key, c) + } else { + _, err = SetEx(key, c, ttl) + } + if err != nil { + return false + } + return true +} + +func GetJson(key string, dst interface{}) error { + b, err := GetBytes(key) + if err != nil { + return err + } + if err = json.Unmarshal(b, dst); err != nil { + return err + } + return nil +} + +func Get(key string) (interface{}, error) { + // get + return Do("GET", key) +} +func GetTTL(key string) (time.Duration, error) { + ttl, err := redigo.Int64(Do("TTL", key)) + return time.Duration(ttl) * time.Second, err +} +func GetBytes(key string) ([]byte, error) { + return redigo.Bytes(Do("GET", key)) +} +func GetString(key string) (string, error) { + return redigo.String(Do("GET", key)) +} +func GetStringMap(key string) (map[string]string, error) { + return redigo.StringMap(Do("GET", key)) +} +func GetInt(key string) (int, error) { + return redigo.Int(Do("GET", key)) +} +func GetInt64(key string) (int64, error) { + return redigo.Int64(Do("GET", key)) +} +func GetStringLength(key string) (int, error) { + return redigo.Int(Do("STRLEN", key)) +} +func ZAdd(key string, score float64, data interface{}) (interface{}, error) { + return Do("ZADD", key, score, data) +} +func ZAddNX(key string, score float64, data interface{}) (interface{}, error) { + return Do("ZADD", key, "NX", score, data) +} +func ZRem(key string, data interface{}) (interface{}, error) { + return Do("ZREM", key, data) +} +func ZRange(key string, start int, end int, withScores bool) ([]interface{}, error) { + if withScores { + return redigo.Values(Do("ZRANGE", key, start, end, "WITHSCORES")) + } + return redigo.Values(Do("ZRANGE", key, start, end)) +} +func ZRemRangeByScore(key string, start int64, end int64) ([]interface{}, error) { + return redigo.Values(Do("ZREMRANGEBYSCORE", key, start, end)) +} +func ZCard(setName string) (int64, error) { + return redigo.Int64(Do("ZCARD", setName)) +} +func ZScan(setName string) (int64, error) { + return redigo.Int64(Do("ZCARD", setName)) +} +func SAdd(setName string, data interface{}) (interface{}, error) { + return Do("SADD", setName, data) +} +func SCard(setName string) (int64, error) { + return redigo.Int64(Do("SCARD", setName)) +} +func SIsMember(setName string, data interface{}) (bool, error) { + return redigo.Bool(Do("SISMEMBER", setName, data)) +} +func SMembers(setName string) ([]string, error) { + return redigo.Strings(Do("SMEMBERS", setName)) +} +func SRem(setName string, data interface{}) (interface{}, error) { + return Do("SREM", setName, data) +} +func HSet(key string, HKey string, data interface{}) (interface{}, error) { + return Do("HSET", key, HKey, data) +} + +func HGet(key string, HKey string) (interface{}, error) { + return Do("HGET", key, HKey) +} + +func HMGet(key string, hashKeys ...string) ([]interface{}, error) { + ret, err := Do("HMGET", key, hashKeys) + if err != nil { + return nil, err + } + reta, ok := ret.([]interface{}) + if !ok { + return nil, errors.New("result not an array") + } + return reta, nil +} + +func HMSet(key string, hashKeys []string, vals []interface{}) (interface{}, error) { + if len(hashKeys) == 0 || len(hashKeys) != len(vals) { + var ret interface{} + return ret, errors.New("bad length") + } + input := []interface{}{key} + for i, v := range hashKeys { + input = append(input, v, vals[i]) + } + return Do("HMSET", input...) +} + +func HGetString(key string, HKey string) (string, error) { + return redigo.String(Do("HGET", key, HKey)) +} +func HGetFloat(key string, HKey string) (float64, error) { + f, err := redigo.Float64(Do("HGET", key, HKey)) + return f, err +} +func HGetInt(key string, HKey string) (int, error) { + return redigo.Int(Do("HGET", key, HKey)) +} +func HGetInt64(key string, HKey string) (int64, error) { + return redigo.Int64(Do("HGET", key, HKey)) +} +func HGetBool(key string, HKey string) (bool, error) { + return redigo.Bool(Do("HGET", key, HKey)) +} +func HDel(key string, HKey string) (interface{}, error) { + return Do("HDEL", key, HKey) +} + +func HGetAll(key string) (map[string]interface{}, error) { + vals, err := redigo.Values(Do("HGETALL", key)) + if err != nil { + return nil, err + } + num := len(vals) / 2 + result := make(map[string]interface{}, num) + for i := 0; i < num; i++ { + key, _ := redigo.String(vals[2*i], nil) + result[key] = vals[2*i+1] + } + return result, nil +} + +func FlushAll() bool { + res, _ := redigo.String(Do("FLUSHALL")) + if res == "" { + return false + } + return true +} + +// NOTE: Use this in production environment with extreme care. +// Read more here:https://redigo.io/commands/keys +func Keys(pattern string) ([]string, error) { + return redigo.Strings(Do("KEYS", pattern)) +} + +func HKeys(key string) ([]string, error) { + return redigo.Strings(Do("HKEYS", key)) +} + +func Exists(key string) bool { + count, err := redigo.Int(Do("EXISTS", key)) + if count == 0 || err != nil { + return false + } + return true +} + +func Incr(key string) (int64, error) { + return redigo.Int64(Do("INCR", key)) +} + +func Decr(key string) (int64, error) { + return redigo.Int64(Do("DECR", key)) +} + +func IncrBy(key string, incBy int64) (int64, error) { + return redigo.Int64(Do("INCRBY", key, incBy)) +} + +func DecrBy(key string, decrBy int64) (int64, error) { + return redigo.Int64(Do("DECRBY", key)) +} + +func IncrByFloat(key string, incBy float64) (float64, error) { + return redigo.Float64(Do("INCRBYFLOAT", key, incBy)) +} + +func DecrByFloat(key string, decrBy float64) (float64, error) { + return redigo.Float64(Do("DECRBYFLOAT", key, decrBy)) +} + +// use for message queue +func LPush(key string, data interface{}) (interface{}, error) { + // set + return Do("LPUSH", key, data) +} + +func LPop(key string) (interface{}, error) { + return Do("LPOP", key) +} + +func LPopString(key string) (string, error) { + return redigo.String(Do("LPOP", key)) +} +func LPopFloat(key string) (float64, error) { + f, err := redigo.Float64(Do("LPOP", key)) + return f, err +} +func LPopInt(key string) (int, error) { + return redigo.Int(Do("LPOP", key)) +} +func LPopInt64(key string) (int64, error) { + return redigo.Int64(Do("LPOP", key)) +} + +func RPush(key string, data interface{}) (interface{}, error) { + // set + return Do("RPUSH", key, data) +} + +func RPop(key string) (interface{}, error) { + return Do("RPOP", key) +} + +func RPopString(key string) (string, error) { + return redigo.String(Do("RPOP", key)) +} +func RPopFloat(key string) (float64, error) { + f, err := redigo.Float64(Do("RPOP", key)) + return f, err +} +func RPopInt(key string) (int, error) { + return redigo.Int(Do("RPOP", key)) +} +func RPopInt64(key string) (int64, error) { + return redigo.Int64(Do("RPOP", key)) +} + +func Scan(cursor int64, pattern string, count int64) (int64, []string, error) { + var items []string + var newCursor int64 + + values, err := redigo.Values(Do("SCAN", cursor, "MATCH", pattern, "COUNT", count)) + if err != nil { + return 0, nil, err + } + values, err = redigo.Scan(values, &newCursor, &items) + if err != nil { + return 0, nil, err + } + return newCursor, items, nil +} diff --git a/mall/utils/cache/redis_cluster.go b/mall/utils/cache/redis_cluster.go new file mode 100644 index 0000000..901f30c --- /dev/null +++ b/mall/utils/cache/redis_cluster.go @@ -0,0 +1,622 @@ +package cache + +import ( + "strconv" + "time" + + "github.com/go-redis/redis" +) + +var pools *redis.ClusterClient + +func NewRedisCluster(addrs []string) error { + opt := &redis.ClusterOptions{ + Addrs: addrs, + PoolSize: redisPoolSize, + PoolTimeout: redisPoolTTL, + IdleTimeout: redisIdleTTL, + DialTimeout: redisDialTTL, + ReadTimeout: redisReadTTL, + WriteTimeout: redisWriteTTL, + } + pools = redis.NewClusterClient(opt) + if err := pools.Ping().Err(); err != nil { + return err + } + return nil +} + +func RCGet(key string) (interface{}, error) { + res, err := pools.Get(key).Result() + if err != nil { + return nil, convertError(err) + } + return []byte(res), nil +} +func RCSet(key string, value interface{}) error { + err := pools.Set(key, value, 0).Err() + return convertError(err) +} +func RCGetSet(key string, value interface{}) (interface{}, error) { + res, err := pools.GetSet(key, value).Result() + if err != nil { + return nil, convertError(err) + } + return []byte(res), nil +} +func RCSetNx(key string, value interface{}) (int64, error) { + res, err := pools.SetNX(key, value, 0).Result() + if err != nil { + return 0, convertError(err) + } + if res { + return 1, nil + } + return 0, nil +} +func RCSetEx(key string, value interface{}, timeout int64) error { + _, err := pools.Set(key, value, time.Duration(timeout)*time.Second).Result() + if err != nil { + return convertError(err) + } + return nil +} + +// nil表示成功,ErrNil表示数据库内已经存在这个key,其他表示数据库发生错误 +func RCSetNxEx(key string, value interface{}, timeout int64) error { + res, err := pools.SetNX(key, value, time.Duration(timeout)*time.Second).Result() + if err != nil { + return convertError(err) + } + if res { + return nil + } + return ErrNil +} +func RCMGet(keys ...string) ([]interface{}, error) { + res, err := pools.MGet(keys...).Result() + return res, convertError(err) +} + +// 为确保多个key映射到同一个slot,每个key最好加上hash tag,如:{test} +func RCMSet(kvs map[string]interface{}) error { + pairs := make([]string, 0, len(kvs)*2) + for k, v := range kvs { + val, err := String(v, nil) + if err != nil { + return err + } + pairs = append(pairs, k, val) + } + return convertError(pools.MSet(pairs).Err()) +} + +// 为确保多个key映射到同一个slot,每个key最好加上hash tag,如:{test} +func RCMSetNX(kvs map[string]interface{}) (bool, error) { + pairs := make([]string, 0, len(kvs)*2) + for k, v := range kvs { + val, err := String(v, nil) + if err != nil { + return false, err + } + pairs = append(pairs, k, val) + } + res, err := pools.MSetNX(pairs).Result() + return res, convertError(err) +} +func RCExpireAt(key string, timestamp int64) (int64, error) { + res, err := pools.ExpireAt(key, time.Unix(timestamp, 0)).Result() + if err != nil { + return 0, convertError(err) + } + if res { + return 1, nil + } + return 0, nil +} +func RCDel(keys ...string) (int64, error) { + args := make([]interface{}, 0, len(keys)) + for _, key := range keys { + args = append(args, key) + } + res, err := pools.Del(keys...).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func RCIncr(key string) (int64, error) { + res, err := pools.Incr(key).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func RCIncrBy(key string, delta int64) (int64, error) { + res, err := pools.IncrBy(key, delta).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func RCExpire(key string, duration int64) (int64, error) { + res, err := pools.Expire(key, time.Duration(duration)*time.Second).Result() + if err != nil { + return 0, convertError(err) + } + if res { + return 1, nil + } + return 0, nil +} +func RCExists(key string) (bool, error) { + res, err := pools.Exists(key).Result() + if err != nil { + return false, convertError(err) + } + if res > 0 { + return true, nil + } + return false, nil +} +func RCHGet(key string, field string) (interface{}, error) { + res, err := pools.HGet(key, field).Result() + if err != nil { + return nil, convertError(err) + } + return []byte(res), nil +} +func RCHLen(key string) (int64, error) { + res, err := pools.HLen(key).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func RCHSet(key string, field string, val interface{}) error { + value, err := String(val, nil) + if err != nil && err != ErrNil { + return err + } + _, err = pools.HSet(key, field, value).Result() + if err != nil { + return convertError(err) + } + return nil +} +func RCHDel(key string, fields ...string) (int64, error) { + args := make([]interface{}, 0, len(fields)+1) + args = append(args, key) + for _, field := range fields { + args = append(args, field) + } + res, err := pools.HDel(key, fields...).Result() + if err != nil { + return 0, convertError(err) + } + return res, nil +} + +func RCHMGet(key string, fields ...string) (interface{}, error) { + args := make([]interface{}, 0, len(fields)+1) + args = append(args, key) + for _, field := range fields { + args = append(args, field) + } + if len(fields) == 0 { + return nil, ErrNil + } + res, err := pools.HMGet(key, fields...).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func RCHMSet(key string, kvs ...interface{}) error { + if len(kvs) == 0 { + return nil + } + if len(kvs)%2 != 0 { + return ErrWrongArgsNum + } + var err error + v := map[string]interface{}{} // todo change + v["field"], err = String(kvs[0], nil) + if err != nil && err != ErrNil { + return err + } + v["value"], err = String(kvs[1], nil) + if err != nil && err != ErrNil { + return err + } + pairs := make([]string, 0, len(kvs)-2) + if len(kvs) > 2 { + for _, kv := range kvs[2:] { + kvString, err := String(kv, nil) + if err != nil && err != ErrNil { + return err + } + pairs = append(pairs, kvString) + } + } + v["paris"] = pairs + _, err = pools.HMSet(key, v).Result() + if err != nil { + return convertError(err) + } + return nil +} + +func RCHKeys(key string) ([]string, error) { + res, err := pools.HKeys(key).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func RCHVals(key string) ([]interface{}, error) { + res, err := pools.HVals(key).Result() + if err != nil { + return nil, convertError(err) + } + rs := make([]interface{}, 0, len(res)) + for _, res := range res { + rs = append(rs, res) + } + return rs, nil +} +func RCHGetAll(key string) (map[string]string, error) { + vals, err := pools.HGetAll(key).Result() + if err != nil { + return nil, convertError(err) + } + return vals, nil +} +func RCHIncrBy(key, field string, delta int64) (int64, error) { + res, err := pools.HIncrBy(key, field, delta).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func RCZAdd(key string, kvs ...interface{}) (int64, error) { + args := make([]interface{}, 0, len(kvs)+1) + args = append(args, key) + args = append(args, kvs...) + if len(kvs) == 0 { + return 0, nil + } + if len(kvs)%2 != 0 { + return 0, ErrWrongArgsNum + } + zs := make([]redis.Z, len(kvs)/2) + for i := 0; i < len(kvs); i += 2 { + idx := i / 2 + score, err := Float64(kvs[i], nil) + if err != nil && err != ErrNil { + return 0, err + } + zs[idx].Score = score + zs[idx].Member = kvs[i+1] + } + res, err := pools.ZAdd(key, zs...).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func RCZRem(key string, members ...string) (int64, error) { + args := make([]interface{}, 0, len(members)) + args = append(args, key) + for _, member := range members { + args = append(args, member) + } + res, err := pools.ZRem(key, members).Result() + if err != nil { + return res, convertError(err) + } + return res, err +} + +func RCZRange(key string, min, max int64, withScores bool) (interface{}, error) { + res := make([]interface{}, 0) + if withScores { + zs, err := pools.ZRangeWithScores(key, min, max).Result() + if err != nil { + return nil, convertError(err) + } + for _, z := range zs { + res = append(res, z.Member, strconv.FormatFloat(z.Score, 'f', -1, 64)) + } + } else { + ms, err := pools.ZRange(key, min, max).Result() + if err != nil { + return nil, convertError(err) + } + for _, m := range ms { + res = append(res, m) + } + } + return res, nil +} +func RCZRangeByScoreWithScore(key string, min, max int64) (map[string]int64, error) { + opt := new(redis.ZRangeBy) + opt.Min = strconv.FormatInt(int64(min), 10) + opt.Max = strconv.FormatInt(int64(max), 10) + opt.Count = -1 + opt.Offset = 0 + vals, err := pools.ZRangeByScoreWithScores(key, *opt).Result() + if err != nil { + return nil, convertError(err) + } + res := make(map[string]int64, len(vals)) + for _, val := range vals { + key, err := String(val.Member, nil) + if err != nil && err != ErrNil { + return nil, err + } + res[key] = int64(val.Score) + } + return res, nil +} +func RCLRange(key string, start, stop int64) (interface{}, error) { + res, err := pools.LRange(key, start, stop).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func RCLSet(key string, index int, value interface{}) error { + err := pools.LSet(key, int64(index), value).Err() + return convertError(err) +} +func RCLLen(key string) (int64, error) { + res, err := pools.LLen(key).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func RCLRem(key string, count int, value interface{}) (int, error) { + val, _ := value.(string) + res, err := pools.LRem(key, int64(count), val).Result() + if err != nil { + return int(res), convertError(err) + } + return int(res), nil +} +func RCTTl(key string) (int64, error) { + duration, err := pools.TTL(key).Result() + if err != nil { + return int64(duration.Seconds()), convertError(err) + } + return int64(duration.Seconds()), nil +} +func RCLPop(key string) (interface{}, error) { + res, err := pools.LPop(key).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func RCRPop(key string) (interface{}, error) { + res, err := pools.RPop(key).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func RCBLPop(key string, timeout int) (interface{}, error) { + res, err := pools.BLPop(time.Duration(timeout)*time.Second, key).Result() + if err != nil { + // 兼容redis 2.x + if err == redis.Nil { + return nil, ErrNil + } + return nil, err + } + return res[1], nil +} +func RCBRPop(key string, timeout int) (interface{}, error) { + res, err := pools.BRPop(time.Duration(timeout)*time.Second, key).Result() + if err != nil { + // 兼容redis 2.x + if err == redis.Nil { + return nil, ErrNil + } + return nil, convertError(err) + } + return res[1], nil +} +func RCLPush(key string, value ...interface{}) error { + args := make([]interface{}, 0, len(value)+1) + args = append(args, key) + args = append(args, value...) + vals := make([]string, 0, len(value)) + for _, v := range value { + val, err := String(v, nil) + if err != nil && err != ErrNil { + return err + } + vals = append(vals, val) + } + _, err := pools.LPush(key, vals).Result() // todo ... + if err != nil { + return convertError(err) + } + return nil +} +func RCRPush(key string, value ...interface{}) error { + args := make([]interface{}, 0, len(value)+1) + args = append(args, key) + args = append(args, value...) + vals := make([]string, 0, len(value)) + for _, v := range value { + val, err := String(v, nil) + if err != nil && err != ErrNil { + if err == ErrNil { + continue + } + return err + } + if val == "" { + continue + } + vals = append(vals, val) + } + _, err := pools.RPush(key, vals).Result() // todo ... + if err != nil { + return convertError(err) + } + return nil +} + +// 为确保srcKey跟destKey映射到同一个slot,srcKey和destKey需要加上hash tag,如:{test} +func RCBRPopLPush(srcKey string, destKey string, timeout int) (interface{}, error) { + res, err := pools.BRPopLPush(srcKey, destKey, time.Duration(timeout)*time.Second).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} + +// 为确保srcKey跟destKey映射到同一个slot,srcKey和destKey需要加上hash tag,如:{test} +func RCRPopLPush(srcKey string, destKey string) (interface{}, error) { + res, err := pools.RPopLPush(srcKey, destKey).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func RCSAdd(key string, members ...interface{}) (int64, error) { + args := make([]interface{}, 0, len(members)+1) + args = append(args, key) + args = append(args, members...) + ms := make([]string, 0, len(members)) + for _, member := range members { + m, err := String(member, nil) + if err != nil && err != ErrNil { + return 0, err + } + ms = append(ms, m) + } + res, err := pools.SAdd(key, ms).Result() // todo ... + if err != nil { + return res, convertError(err) + } + return res, nil +} +func RCSPop(key string) ([]byte, error) { + res, err := pools.SPop(key).Result() + if err != nil { + return nil, convertError(err) + } + return []byte(res), nil +} +func RCSIsMember(key string, member interface{}) (bool, error) { + m, _ := member.(string) + res, err := pools.SIsMember(key, m).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func RCSRem(key string, members ...interface{}) (int64, error) { + args := make([]interface{}, 0, len(members)+1) + args = append(args, key) + args = append(args, members...) + ms := make([]string, 0, len(members)) + for _, member := range members { + m, err := String(member, nil) + if err != nil && err != ErrNil { + return 0, err + } + ms = append(ms, m) + } + res, err := pools.SRem(key, ms).Result() // todo ... + if err != nil { + return res, convertError(err) + } + return res, nil +} +func RCSMembers(key string) ([]string, error) { + res, err := pools.SMembers(key).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func RCScriptLoad(luaScript string) (interface{}, error) { + res, err := pools.ScriptLoad(luaScript).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func RCEvalSha(sha1 string, numberKeys int, keysArgs ...interface{}) (interface{}, error) { + vals := make([]interface{}, 0, len(keysArgs)+2) + vals = append(vals, sha1, numberKeys) + vals = append(vals, keysArgs...) + keys := make([]string, 0, numberKeys) + args := make([]string, 0, len(keysArgs)-numberKeys) + for i, value := range keysArgs { + val, err := String(value, nil) + if err != nil && err != ErrNil { + return nil, err + } + if i < numberKeys { + keys = append(keys, val) + } else { + args = append(args, val) + } + } + res, err := pools.EvalSha(sha1, keys, args).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func RCEval(luaScript string, numberKeys int, keysArgs ...interface{}) (interface{}, error) { + vals := make([]interface{}, 0, len(keysArgs)+2) + vals = append(vals, luaScript, numberKeys) + vals = append(vals, keysArgs...) + keys := make([]string, 0, numberKeys) + args := make([]string, 0, len(keysArgs)-numberKeys) + for i, value := range keysArgs { + val, err := String(value, nil) + if err != nil && err != ErrNil { + return nil, err + } + if i < numberKeys { + keys = append(keys, val) + } else { + args = append(args, val) + } + } + res, err := pools.Eval(luaScript, keys, args).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func RCGetBit(key string, offset int64) (int64, error) { + res, err := pools.GetBit(key, offset).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func RCSetBit(key string, offset uint32, value int) (int, error) { + res, err := pools.SetBit(key, int64(offset), value).Result() + return int(res), convertError(err) +} +func RCGetClient() *redis.ClusterClient { + return pools +} +func convertError(err error) error { + if err == redis.Nil { + // 为了兼容redis 2.x,这里不返回 ErrNil,ErrNil在调用redis_cluster_reply函数时才返回 + return nil + } + return err +} diff --git a/mall/utils/cache/redis_pool.go b/mall/utils/cache/redis_pool.go new file mode 100644 index 0000000..ca38b3f --- /dev/null +++ b/mall/utils/cache/redis_pool.go @@ -0,0 +1,324 @@ +package cache + +import ( + "errors" + "log" + "strings" + "time" + + redigo "github.com/gomodule/redigo/redis" +) + +type RedisPool struct { + *redigo.Pool +} + +func NewRedisPool(cfg *Config) *RedisPool { + return &RedisPool{&redigo.Pool{ + MaxIdle: cfg.MaxIdle, + IdleTimeout: cfg.IdleTimeout, + MaxActive: cfg.MaxActive, + Wait: cfg.Wait, + Dial: func() (redigo.Conn, error) { + c, err := redigo.Dial("tcp", cfg.Server) + if err != nil { + log.Println("Redis Dial failed: ", err) + return nil, err + } + if cfg.Password != "" { + if _, err := c.Do("AUTH", cfg.Password); err != nil { + c.Close() + log.Println("Redis AUTH failed: ", err) + return nil, err + } + } + return c, err + }, + TestOnBorrow: func(c redigo.Conn, t time.Time) error { + _, err := c.Do("PING") + if err != nil { + log.Println("Unable to ping to redis server:", err) + } + return err + }, + }} +} + +func (p *RedisPool) Do(cmd string, args ...interface{}) (reply interface{}, err error) { + conn := pool.Get() + defer conn.Close() + return conn.Do(cmd, args...) +} + +func (p *RedisPool) GetPool() *redigo.Pool { + return pool +} + +func (p *RedisPool) ParseKey(key string, vars []string) (string, error) { + arr := strings.Split(key, conf.KeyPlaceholder) + actualKey := "" + if len(arr) != len(vars)+1 { + return "", errors.New("redis/connection.go: Insufficient arguments to parse key") + } else { + for index, val := range arr { + if index == 0 { + actualKey = arr[index] + } else { + actualKey += vars[index-1] + val + } + } + } + return getPrefixedKey(actualKey), nil +} + +func (p *RedisPool) getPrefixedKey(key string) string { + return conf.KeyPrefix + conf.KeyDelimiter + key +} +func (p *RedisPool) StripEnvKey(key string) string { + return strings.TrimLeft(key, conf.KeyPrefix+conf.KeyDelimiter) +} +func (p *RedisPool) SplitKey(key string) []string { + return strings.Split(key, conf.KeyDelimiter) +} +func (p *RedisPool) Expire(key string, ttl int) (interface{}, error) { + return Do("EXPIRE", key, ttl) +} +func (p *RedisPool) Persist(key string) (interface{}, error) { + return Do("PERSIST", key) +} + +func (p *RedisPool) Del(key string) (interface{}, error) { + return Do("DEL", key) +} +func (p *RedisPool) Set(key string, data interface{}) (interface{}, error) { + // set + return Do("SET", key, data) +} +func (p *RedisPool) SetNX(key string, data interface{}) (interface{}, error) { + return Do("SETNX", key, data) +} +func (p *RedisPool) SetEx(key string, data interface{}, ttl int) (interface{}, error) { + return Do("SETEX", key, ttl, data) +} +func (p *RedisPool) Get(key string) (interface{}, error) { + // get + return Do("GET", key) +} +func (p *RedisPool) GetStringMap(key string) (map[string]string, error) { + // get + return redigo.StringMap(Do("GET", key)) +} + +func (p *RedisPool) GetTTL(key string) (time.Duration, error) { + ttl, err := redigo.Int64(Do("TTL", key)) + return time.Duration(ttl) * time.Second, err +} +func (p *RedisPool) GetBytes(key string) ([]byte, error) { + return redigo.Bytes(Do("GET", key)) +} +func (p *RedisPool) GetString(key string) (string, error) { + return redigo.String(Do("GET", key)) +} +func (p *RedisPool) GetInt(key string) (int, error) { + return redigo.Int(Do("GET", key)) +} +func (p *RedisPool) GetStringLength(key string) (int, error) { + return redigo.Int(Do("STRLEN", key)) +} +func (p *RedisPool) ZAdd(key string, score float64, data interface{}) (interface{}, error) { + return Do("ZADD", key, score, data) +} +func (p *RedisPool) ZRem(key string, data interface{}) (interface{}, error) { + return Do("ZREM", key, data) +} +func (p *RedisPool) ZRange(key string, start int, end int, withScores bool) ([]interface{}, error) { + if withScores { + return redigo.Values(Do("ZRANGE", key, start, end, "WITHSCORES")) + } + return redigo.Values(Do("ZRANGE", key, start, end)) +} +func (p *RedisPool) SAdd(setName string, data interface{}) (interface{}, error) { + return Do("SADD", setName, data) +} +func (p *RedisPool) SCard(setName string) (int64, error) { + return redigo.Int64(Do("SCARD", setName)) +} +func (p *RedisPool) SIsMember(setName string, data interface{}) (bool, error) { + return redigo.Bool(Do("SISMEMBER", setName, data)) +} +func (p *RedisPool) SMembers(setName string) ([]string, error) { + return redigo.Strings(Do("SMEMBERS", setName)) +} +func (p *RedisPool) SRem(setName string, data interface{}) (interface{}, error) { + return Do("SREM", setName, data) +} +func (p *RedisPool) HSet(key string, HKey string, data interface{}) (interface{}, error) { + return Do("HSET", key, HKey, data) +} + +func (p *RedisPool) HGet(key string, HKey string) (interface{}, error) { + return Do("HGET", key, HKey) +} + +func (p *RedisPool) HMGet(key string, hashKeys ...string) ([]interface{}, error) { + ret, err := Do("HMGET", key, hashKeys) + if err != nil { + return nil, err + } + reta, ok := ret.([]interface{}) + if !ok { + return nil, errors.New("result not an array") + } + return reta, nil +} + +func (p *RedisPool) HMSet(key string, hashKeys []string, vals []interface{}) (interface{}, error) { + if len(hashKeys) == 0 || len(hashKeys) != len(vals) { + var ret interface{} + return ret, errors.New("bad length") + } + input := []interface{}{key} + for i, v := range hashKeys { + input = append(input, v, vals[i]) + } + return Do("HMSET", input...) +} + +func (p *RedisPool) HGetString(key string, HKey string) (string, error) { + return redigo.String(Do("HGET", key, HKey)) +} +func (p *RedisPool) HGetFloat(key string, HKey string) (float64, error) { + f, err := redigo.Float64(Do("HGET", key, HKey)) + return float64(f), err +} +func (p *RedisPool) HGetInt(key string, HKey string) (int, error) { + return redigo.Int(Do("HGET", key, HKey)) +} +func (p *RedisPool) HGetInt64(key string, HKey string) (int64, error) { + return redigo.Int64(Do("HGET", key, HKey)) +} +func (p *RedisPool) HGetBool(key string, HKey string) (bool, error) { + return redigo.Bool(Do("HGET", key, HKey)) +} +func (p *RedisPool) HDel(key string, HKey string) (interface{}, error) { + return Do("HDEL", key, HKey) +} +func (p *RedisPool) HGetAll(key string) (map[string]interface{}, error) { + vals, err := redigo.Values(Do("HGETALL", key)) + if err != nil { + return nil, err + } + num := len(vals) / 2 + result := make(map[string]interface{}, num) + for i := 0; i < num; i++ { + key, _ := redigo.String(vals[2*i], nil) + result[key] = vals[2*i+1] + } + return result, nil +} + +// NOTE: Use this in production environment with extreme care. +// Read more here:https://redigo.io/commands/keys +func (p *RedisPool) Keys(pattern string) ([]string, error) { + return redigo.Strings(Do("KEYS", pattern)) +} + +func (p *RedisPool) HKeys(key string) ([]string, error) { + return redigo.Strings(Do("HKEYS", key)) +} + +func (p *RedisPool) Exists(key string) (bool, error) { + count, err := redigo.Int(Do("EXISTS", key)) + if count == 0 { + return false, err + } else { + return true, err + } +} + +func (p *RedisPool) Incr(key string) (int64, error) { + return redigo.Int64(Do("INCR", key)) +} + +func (p *RedisPool) Decr(key string) (int64, error) { + return redigo.Int64(Do("DECR", key)) +} + +func (p *RedisPool) IncrBy(key string, incBy int64) (int64, error) { + return redigo.Int64(Do("INCRBY", key, incBy)) +} + +func (p *RedisPool) DecrBy(key string, decrBy int64) (int64, error) { + return redigo.Int64(Do("DECRBY", key)) +} + +func (p *RedisPool) IncrByFloat(key string, incBy float64) (float64, error) { + return redigo.Float64(Do("INCRBYFLOAT", key, incBy)) +} + +func (p *RedisPool) DecrByFloat(key string, decrBy float64) (float64, error) { + return redigo.Float64(Do("DECRBYFLOAT", key, decrBy)) +} + +// use for message queue +func (p *RedisPool) LPush(key string, data interface{}) (interface{}, error) { + // set + return Do("LPUSH", key, data) +} + +func (p *RedisPool) LPop(key string) (interface{}, error) { + return Do("LPOP", key) +} + +func (p *RedisPool) LPopString(key string) (string, error) { + return redigo.String(Do("LPOP", key)) +} +func (p *RedisPool) LPopFloat(key string) (float64, error) { + f, err := redigo.Float64(Do("LPOP", key)) + return float64(f), err +} +func (p *RedisPool) LPopInt(key string) (int, error) { + return redigo.Int(Do("LPOP", key)) +} +func (p *RedisPool) LPopInt64(key string) (int64, error) { + return redigo.Int64(Do("LPOP", key)) +} + +func (p *RedisPool) RPush(key string, data interface{}) (interface{}, error) { + // set + return Do("RPUSH", key, data) +} + +func (p *RedisPool) RPop(key string) (interface{}, error) { + return Do("RPOP", key) +} + +func (p *RedisPool) RPopString(key string) (string, error) { + return redigo.String(Do("RPOP", key)) +} +func (p *RedisPool) RPopFloat(key string) (float64, error) { + f, err := redigo.Float64(Do("RPOP", key)) + return float64(f), err +} +func (p *RedisPool) RPopInt(key string) (int, error) { + return redigo.Int(Do("RPOP", key)) +} +func (p *RedisPool) RPopInt64(key string) (int64, error) { + return redigo.Int64(Do("RPOP", key)) +} + +func (p *RedisPool) Scan(cursor int64, pattern string, count int64) (int64, []string, error) { + var items []string + var newCursor int64 + + values, err := redigo.Values(Do("SCAN", cursor, "MATCH", pattern, "COUNT", count)) + if err != nil { + return 0, nil, err + } + values, err = redigo.Scan(values, &newCursor, &items) + if err != nil { + return 0, nil, err + } + + return newCursor, items, nil +} diff --git a/mall/utils/cache/redis_pool_cluster.go b/mall/utils/cache/redis_pool_cluster.go new file mode 100644 index 0000000..cd1911b --- /dev/null +++ b/mall/utils/cache/redis_pool_cluster.go @@ -0,0 +1,617 @@ +package cache + +import ( + "strconv" + "time" + + "github.com/go-redis/redis" +) + +type RedisClusterPool struct { + client *redis.ClusterClient +} + +func NewRedisClusterPool(addrs []string) (*RedisClusterPool, error) { + opt := &redis.ClusterOptions{ + Addrs: addrs, + PoolSize: 512, + PoolTimeout: 10 * time.Second, + IdleTimeout: 10 * time.Second, + DialTimeout: 10 * time.Second, + ReadTimeout: 3 * time.Second, + WriteTimeout: 3 * time.Second, + } + c := redis.NewClusterClient(opt) + if err := c.Ping().Err(); err != nil { + return nil, err + } + return &RedisClusterPool{client: c}, nil +} + +func (p *RedisClusterPool) Get(key string) (interface{}, error) { + res, err := p.client.Get(key).Result() + if err != nil { + return nil, convertError(err) + } + return []byte(res), nil +} +func (p *RedisClusterPool) Set(key string, value interface{}) error { + err := p.client.Set(key, value, 0).Err() + return convertError(err) +} +func (p *RedisClusterPool) GetSet(key string, value interface{}) (interface{}, error) { + res, err := p.client.GetSet(key, value).Result() + if err != nil { + return nil, convertError(err) + } + return []byte(res), nil +} +func (p *RedisClusterPool) SetNx(key string, value interface{}) (int64, error) { + res, err := p.client.SetNX(key, value, 0).Result() + if err != nil { + return 0, convertError(err) + } + if res { + return 1, nil + } + return 0, nil +} +func (p *RedisClusterPool) SetEx(key string, value interface{}, timeout int64) error { + _, err := p.client.Set(key, value, time.Duration(timeout)*time.Second).Result() + if err != nil { + return convertError(err) + } + return nil +} + +// nil表示成功,ErrNil表示数据库内已经存在这个key,其他表示数据库发生错误 +func (p *RedisClusterPool) SetNxEx(key string, value interface{}, timeout int64) error { + res, err := p.client.SetNX(key, value, time.Duration(timeout)*time.Second).Result() + if err != nil { + return convertError(err) + } + if res { + return nil + } + return ErrNil +} +func (p *RedisClusterPool) MGet(keys ...string) ([]interface{}, error) { + res, err := p.client.MGet(keys...).Result() + return res, convertError(err) +} + +// 为确保多个key映射到同一个slot,每个key最好加上hash tag,如:{test} +func (p *RedisClusterPool) MSet(kvs map[string]interface{}) error { + pairs := make([]string, 0, len(kvs)*2) + for k, v := range kvs { + val, err := String(v, nil) + if err != nil { + return err + } + pairs = append(pairs, k, val) + } + return convertError(p.client.MSet(pairs).Err()) +} + +// 为确保多个key映射到同一个slot,每个key最好加上hash tag,如:{test} +func (p *RedisClusterPool) MSetNX(kvs map[string]interface{}) (bool, error) { + pairs := make([]string, 0, len(kvs)*2) + for k, v := range kvs { + val, err := String(v, nil) + if err != nil { + return false, err + } + pairs = append(pairs, k, val) + } + res, err := p.client.MSetNX(pairs).Result() + return res, convertError(err) +} +func (p *RedisClusterPool) ExpireAt(key string, timestamp int64) (int64, error) { + res, err := p.client.ExpireAt(key, time.Unix(timestamp, 0)).Result() + if err != nil { + return 0, convertError(err) + } + if res { + return 1, nil + } + return 0, nil +} +func (p *RedisClusterPool) Del(keys ...string) (int64, error) { + args := make([]interface{}, 0, len(keys)) + for _, key := range keys { + args = append(args, key) + } + res, err := p.client.Del(keys...).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) Incr(key string) (int64, error) { + res, err := p.client.Incr(key).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) IncrBy(key string, delta int64) (int64, error) { + res, err := p.client.IncrBy(key, delta).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) Expire(key string, duration int64) (int64, error) { + res, err := p.client.Expire(key, time.Duration(duration)*time.Second).Result() + if err != nil { + return 0, convertError(err) + } + if res { + return 1, nil + } + return 0, nil +} +func (p *RedisClusterPool) Exists(key string) (bool, error) { // todo (bool, error) + res, err := p.client.Exists(key).Result() + if err != nil { + return false, convertError(err) + } + if res > 0 { + return true, nil + } + return false, nil +} +func (p *RedisClusterPool) HGet(key string, field string) (interface{}, error) { + res, err := p.client.HGet(key, field).Result() + if err != nil { + return nil, convertError(err) + } + return []byte(res), nil +} +func (p *RedisClusterPool) HLen(key string) (int64, error) { + res, err := p.client.HLen(key).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) HSet(key string, field string, val interface{}) error { + value, err := String(val, nil) + if err != nil && err != ErrNil { + return err + } + _, err = p.client.HSet(key, field, value).Result() + if err != nil { + return convertError(err) + } + return nil +} +func (p *RedisClusterPool) HDel(key string, fields ...string) (int64, error) { + args := make([]interface{}, 0, len(fields)+1) + args = append(args, key) + for _, field := range fields { + args = append(args, field) + } + res, err := p.client.HDel(key, fields...).Result() + if err != nil { + return 0, convertError(err) + } + return res, nil +} + +func (p *RedisClusterPool) HMGet(key string, fields ...string) (interface{}, error) { + args := make([]interface{}, 0, len(fields)+1) + args = append(args, key) + for _, field := range fields { + args = append(args, field) + } + if len(fields) == 0 { + return nil, ErrNil + } + res, err := p.client.HMGet(key, fields...).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) HMSet(key string, kvs ...interface{}) error { + if len(kvs) == 0 { + return nil + } + if len(kvs)%2 != 0 { + return ErrWrongArgsNum + } + var err error + v := map[string]interface{}{} // todo change + v["field"], err = String(kvs[0], nil) + if err != nil && err != ErrNil { + return err + } + v["value"], err = String(kvs[1], nil) + if err != nil && err != ErrNil { + return err + } + pairs := make([]string, 0, len(kvs)-2) + if len(kvs) > 2 { + for _, kv := range kvs[2:] { + kvString, err := String(kv, nil) + if err != nil && err != ErrNil { + return err + } + pairs = append(pairs, kvString) + } + } + v["paris"] = pairs + _, err = p.client.HMSet(key, v).Result() + if err != nil { + return convertError(err) + } + return nil +} + +func (p *RedisClusterPool) HKeys(key string) ([]string, error) { + res, err := p.client.HKeys(key).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) HVals(key string) ([]interface{}, error) { + res, err := p.client.HVals(key).Result() + if err != nil { + return nil, convertError(err) + } + rs := make([]interface{}, 0, len(res)) + for _, res := range res { + rs = append(rs, res) + } + return rs, nil +} +func (p *RedisClusterPool) HGetAll(key string) (map[string]string, error) { + vals, err := p.client.HGetAll(key).Result() + if err != nil { + return nil, convertError(err) + } + return vals, nil +} +func (p *RedisClusterPool) HIncrBy(key, field string, delta int64) (int64, error) { + res, err := p.client.HIncrBy(key, field, delta).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) ZAdd(key string, kvs ...interface{}) (int64, error) { + args := make([]interface{}, 0, len(kvs)+1) + args = append(args, key) + args = append(args, kvs...) + if len(kvs) == 0 { + return 0, nil + } + if len(kvs)%2 != 0 { + return 0, ErrWrongArgsNum + } + zs := make([]redis.Z, len(kvs)/2) + for i := 0; i < len(kvs); i += 2 { + idx := i / 2 + score, err := Float64(kvs[i], nil) + if err != nil && err != ErrNil { + return 0, err + } + zs[idx].Score = score + zs[idx].Member = kvs[i+1] + } + res, err := p.client.ZAdd(key, zs...).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) ZRem(key string, members ...string) (int64, error) { + args := make([]interface{}, 0, len(members)) + args = append(args, key) + for _, member := range members { + args = append(args, member) + } + res, err := p.client.ZRem(key, members).Result() + if err != nil { + return res, convertError(err) + } + return res, err +} + +func (p *RedisClusterPool) ZRange(key string, min, max int64, withScores bool) (interface{}, error) { + res := make([]interface{}, 0) + if withScores { + zs, err := p.client.ZRangeWithScores(key, min, max).Result() + if err != nil { + return nil, convertError(err) + } + for _, z := range zs { + res = append(res, z.Member, strconv.FormatFloat(z.Score, 'f', -1, 64)) + } + } else { + ms, err := p.client.ZRange(key, min, max).Result() + if err != nil { + return nil, convertError(err) + } + for _, m := range ms { + res = append(res, m) + } + } + return res, nil +} +func (p *RedisClusterPool) ZRangeByScoreWithScore(key string, min, max int64) (map[string]int64, error) { + opt := new(redis.ZRangeBy) + opt.Min = strconv.FormatInt(int64(min), 10) + opt.Max = strconv.FormatInt(int64(max), 10) + opt.Count = -1 + opt.Offset = 0 + vals, err := p.client.ZRangeByScoreWithScores(key, *opt).Result() + if err != nil { + return nil, convertError(err) + } + res := make(map[string]int64, len(vals)) + for _, val := range vals { + key, err := String(val.Member, nil) + if err != nil && err != ErrNil { + return nil, err + } + res[key] = int64(val.Score) + } + return res, nil +} +func (p *RedisClusterPool) LRange(key string, start, stop int64) (interface{}, error) { + res, err := p.client.LRange(key, start, stop).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) LSet(key string, index int, value interface{}) error { + err := p.client.LSet(key, int64(index), value).Err() + return convertError(err) +} +func (p *RedisClusterPool) LLen(key string) (int64, error) { + res, err := p.client.LLen(key).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) LRem(key string, count int, value interface{}) (int, error) { + val, _ := value.(string) + res, err := p.client.LRem(key, int64(count), val).Result() + if err != nil { + return int(res), convertError(err) + } + return int(res), nil +} +func (p *RedisClusterPool) TTl(key string) (int64, error) { + duration, err := p.client.TTL(key).Result() + if err != nil { + return int64(duration.Seconds()), convertError(err) + } + return int64(duration.Seconds()), nil +} +func (p *RedisClusterPool) LPop(key string) (interface{}, error) { + res, err := p.client.LPop(key).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) RPop(key string) (interface{}, error) { + res, err := p.client.RPop(key).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) BLPop(key string, timeout int) (interface{}, error) { + res, err := p.client.BLPop(time.Duration(timeout)*time.Second, key).Result() + if err != nil { + // 兼容redis 2.x + if err == redis.Nil { + return nil, ErrNil + } + return nil, err + } + return res[1], nil +} +func (p *RedisClusterPool) BRPop(key string, timeout int) (interface{}, error) { + res, err := p.client.BRPop(time.Duration(timeout)*time.Second, key).Result() + if err != nil { + // 兼容redis 2.x + if err == redis.Nil { + return nil, ErrNil + } + return nil, convertError(err) + } + return res[1], nil +} +func (p *RedisClusterPool) LPush(key string, value ...interface{}) error { + args := make([]interface{}, 0, len(value)+1) + args = append(args, key) + args = append(args, value...) + vals := make([]string, 0, len(value)) + for _, v := range value { + val, err := String(v, nil) + if err != nil && err != ErrNil { + return err + } + vals = append(vals, val) + } + _, err := p.client.LPush(key, vals).Result() // todo ... + if err != nil { + return convertError(err) + } + return nil +} +func (p *RedisClusterPool) RPush(key string, value ...interface{}) error { + args := make([]interface{}, 0, len(value)+1) + args = append(args, key) + args = append(args, value...) + vals := make([]string, 0, len(value)) + for _, v := range value { + val, err := String(v, nil) + if err != nil && err != ErrNil { + if err == ErrNil { + continue + } + return err + } + if val == "" { + continue + } + vals = append(vals, val) + } + _, err := p.client.RPush(key, vals).Result() // todo ... + if err != nil { + return convertError(err) + } + return nil +} + +// 为确保srcKey跟destKey映射到同一个slot,srcKey和destKey需要加上hash tag,如:{test} +func (p *RedisClusterPool) BRPopLPush(srcKey string, destKey string, timeout int) (interface{}, error) { + res, err := p.client.BRPopLPush(srcKey, destKey, time.Duration(timeout)*time.Second).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} + +// 为确保srcKey跟destKey映射到同一个slot,srcKey和destKey需要加上hash tag,如:{test} +func (p *RedisClusterPool) RPopLPush(srcKey string, destKey string) (interface{}, error) { + res, err := p.client.RPopLPush(srcKey, destKey).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) SAdd(key string, members ...interface{}) (int64, error) { + args := make([]interface{}, 0, len(members)+1) + args = append(args, key) + args = append(args, members...) + ms := make([]string, 0, len(members)) + for _, member := range members { + m, err := String(member, nil) + if err != nil && err != ErrNil { + return 0, err + } + ms = append(ms, m) + } + res, err := p.client.SAdd(key, ms).Result() // todo ... + if err != nil { + return res, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) SPop(key string) ([]byte, error) { + res, err := p.client.SPop(key).Result() + if err != nil { + return nil, convertError(err) + } + return []byte(res), nil +} +func (p *RedisClusterPool) SIsMember(key string, member interface{}) (bool, error) { + m, _ := member.(string) + res, err := p.client.SIsMember(key, m).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) SRem(key string, members ...interface{}) (int64, error) { + args := make([]interface{}, 0, len(members)+1) + args = append(args, key) + args = append(args, members...) + ms := make([]string, 0, len(members)) + for _, member := range members { + m, err := String(member, nil) + if err != nil && err != ErrNil { + return 0, err + } + ms = append(ms, m) + } + res, err := p.client.SRem(key, ms).Result() // todo ... + if err != nil { + return res, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) SMembers(key string) ([]string, error) { + res, err := p.client.SMembers(key).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) ScriptLoad(luaScript string) (interface{}, error) { + res, err := p.client.ScriptLoad(luaScript).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) EvalSha(sha1 string, numberKeys int, keysArgs ...interface{}) (interface{}, error) { + vals := make([]interface{}, 0, len(keysArgs)+2) + vals = append(vals, sha1, numberKeys) + vals = append(vals, keysArgs...) + keys := make([]string, 0, numberKeys) + args := make([]string, 0, len(keysArgs)-numberKeys) + for i, value := range keysArgs { + val, err := String(value, nil) + if err != nil && err != ErrNil { + return nil, err + } + if i < numberKeys { + keys = append(keys, val) + } else { + args = append(args, val) + } + } + res, err := p.client.EvalSha(sha1, keys, args).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) Eval(luaScript string, numberKeys int, keysArgs ...interface{}) (interface{}, error) { + vals := make([]interface{}, 0, len(keysArgs)+2) + vals = append(vals, luaScript, numberKeys) + vals = append(vals, keysArgs...) + keys := make([]string, 0, numberKeys) + args := make([]string, 0, len(keysArgs)-numberKeys) + for i, value := range keysArgs { + val, err := String(value, nil) + if err != nil && err != ErrNil { + return nil, err + } + if i < numberKeys { + keys = append(keys, val) + } else { + args = append(args, val) + } + } + res, err := p.client.Eval(luaScript, keys, args).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) GetBit(key string, offset int64) (int64, error) { + res, err := p.client.GetBit(key, offset).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) SetBit(key string, offset uint32, value int) (int, error) { + res, err := p.client.SetBit(key, int64(offset), value).Result() + return int(res), convertError(err) +} +func (p *RedisClusterPool) GetClient() *redis.ClusterClient { + return pools +} diff --git a/mall/utils/convert.go b/mall/utils/convert.go new file mode 100644 index 0000000..cf21bf6 --- /dev/null +++ b/mall/utils/convert.go @@ -0,0 +1,372 @@ +package utils + +import ( + "encoding/binary" + "encoding/json" + "fmt" + "github.com/gin-gonic/gin" + "math" + "strconv" + "strings" +) + +func ToString(raw interface{}, e error) (res string) { + if e != nil { + return "" + } + return AnyToString(raw) +} + +func ToInt64(raw interface{}, e error) int64 { + if e != nil { + return 0 + } + return AnyToInt64(raw) +} +func Float64ToStrByPrec(f float64, prec int) string { + return strconv.FormatFloat(f, 'f', prec, 64) +} +func Float64ToStrPrec4(f float64) string { + return strconv.FormatFloat(f, 'f', 4, 64) +} + +func StrToFormat(c *gin.Context, s string, prec int) string { + ex := strings.Split(s, ".") + if len(ex) == 2 { + if StrToFloat64(ex[1]) == 0 && c.GetString("is_show_point") != "1" { //小数点后面为空就是不要小数点了 + return ex[0] + } + //看取多少位 + str := ex[1] + str1 := str + if prec < len(str) { + str1 = str[0:prec] + } else { + for i := 0; i < prec-len(str); i++ { + str1 += "0" + } + } + if prec > 0 { + return ex[0] + "." + str1 + } else { + return ex[0] + } + } + return s +} + +// 强制舍弃尾数 +func Truncate(num string, prec int) string { + if num == "" { + return "" + } + if prec >= len(num) { + return num + } + newn := strings.Split(num, ".") + if len(newn) < 2 || prec >= len(newn[1]) { + return num + } + return newn[0] + "." + newn[1][:prec] +} +func AnyToBool(raw interface{}) bool { + switch i := raw.(type) { + case float32, float64, int, int64, uint, uint8, uint16, uint32, uint64, int8, int16, int32: + return i != 0 + case []byte: + return i != nil + case string: + if i == "false" { + return false + } + return i != "" + case error: + return false + case nil: + return true + } + val := fmt.Sprint(raw) + val = strings.TrimLeft(val, "&") + if strings.TrimLeft(val, "{}") == "" { + return false + } + if strings.TrimLeft(val, "[]") == "" { + return false + } + // ptr type + b, err := json.Marshal(raw) + if err != nil { + return false + } + if strings.TrimLeft(string(b), "\"\"") == "" { + return false + } + if strings.TrimLeft(string(b), "{}") == "" { + return false + } + return true +} + +func AnyToInt64(raw interface{}) int64 { + switch i := raw.(type) { + case string: + res, _ := strconv.ParseInt(i, 10, 64) + return res + case []byte: + return BytesToInt64(i) + case int: + return int64(i) + case int64: + return i + case uint: + return int64(i) + case uint8: + return int64(i) + case uint16: + return int64(i) + case uint32: + return int64(i) + case uint64: + return int64(i) + case int8: + return int64(i) + case int16: + return int64(i) + case int32: + return int64(i) + case float32: + return int64(i) + case float64: + return int64(i) + case error: + return 0 + case bool: + if i { + return 1 + } + return 0 + } + return 0 +} + +func AnyToString(raw interface{}) string { + switch i := raw.(type) { + case []byte: + return string(i) + case int: + return strconv.FormatInt(int64(i), 10) + case int64: + return strconv.FormatInt(i, 10) + case float32: + return Float64ToStr(float64(i)) + case float64: + return Float64ToStr(i) + case uint: + return strconv.FormatInt(int64(i), 10) + case uint8: + return strconv.FormatInt(int64(i), 10) + case uint16: + return strconv.FormatInt(int64(i), 10) + case uint32: + return strconv.FormatInt(int64(i), 10) + case uint64: + return strconv.FormatInt(int64(i), 10) + case int8: + return strconv.FormatInt(int64(i), 10) + case int16: + return strconv.FormatInt(int64(i), 10) + case int32: + return strconv.FormatInt(int64(i), 10) + case string: + return i + case error: + return i.Error() + case bool: + return strconv.FormatBool(i) + } + return fmt.Sprintf("%#v", raw) +} + +func AnyToFloat64(raw interface{}) float64 { + switch i := raw.(type) { + case []byte: + f, _ := strconv.ParseFloat(string(i), 64) + return f + case int: + return float64(i) + case int64: + return float64(i) + case float32: + return float64(i) + case float64: + return i + case uint: + return float64(i) + case uint8: + return float64(i) + case uint16: + return float64(i) + case uint32: + return float64(i) + case uint64: + return float64(i) + case int8: + return float64(i) + case int16: + return float64(i) + case int32: + return float64(i) + case string: + f, _ := strconv.ParseFloat(i, 64) + return f + case bool: + if i { + return 1 + } + } + return 0 +} + +func ToByte(raw interface{}, e error) []byte { + if e != nil { + return []byte{} + } + switch i := raw.(type) { + case string: + return []byte(i) + case int: + return Int64ToBytes(int64(i)) + case int64: + return Int64ToBytes(i) + case float32: + return Float32ToByte(i) + case float64: + return Float64ToByte(i) + case uint: + return Int64ToBytes(int64(i)) + case uint8: + return Int64ToBytes(int64(i)) + case uint16: + return Int64ToBytes(int64(i)) + case uint32: + return Int64ToBytes(int64(i)) + case uint64: + return Int64ToBytes(int64(i)) + case int8: + return Int64ToBytes(int64(i)) + case int16: + return Int64ToBytes(int64(i)) + case int32: + return Int64ToBytes(int64(i)) + case []byte: + return i + case error: + return []byte(i.Error()) + case bool: + if i { + return []byte("true") + } + return []byte("false") + } + return []byte(fmt.Sprintf("%#v", raw)) +} + +func Int64ToBytes(i int64) []byte { + var buf = make([]byte, 8) + binary.BigEndian.PutUint64(buf, uint64(i)) + return buf +} + +func BytesToInt64(buf []byte) int64 { + return int64(binary.BigEndian.Uint64(buf)) +} + +func StrToInt(s string) int { + res, _ := strconv.Atoi(s) + return res +} + +func StrToInt64(s string) int64 { + res, _ := strconv.ParseInt(s, 10, 64) + return res +} + +func Float32ToByte(float float32) []byte { + bits := math.Float32bits(float) + bytes := make([]byte, 4) + binary.LittleEndian.PutUint32(bytes, bits) + + return bytes +} + +func ByteToFloat32(bytes []byte) float32 { + bits := binary.LittleEndian.Uint32(bytes) + return math.Float32frombits(bits) +} + +func Float64ToByte(float float64) []byte { + bits := math.Float64bits(float) + bytes := make([]byte, 8) + binary.LittleEndian.PutUint64(bytes, bits) + return bytes +} + +func ByteToFloat64(bytes []byte) float64 { + bits := binary.LittleEndian.Uint64(bytes) + return math.Float64frombits(bits) +} + +func Float64ToStr(f float64) string { + return strconv.FormatFloat(f, 'f', 2, 64) +} +func Float64ToStrPrec1(f float64) string { + return strconv.FormatFloat(f, 'f', 1, 64) +} + +func Float64ToStrPrec6(f float64) string { + return strconv.FormatFloat(f, 'f', 6, 64) +} + +func Float32ToStr(f float32) string { + return Float64ToStr(float64(f)) +} + +func StrToFloat64(s string) float64 { + res, err := strconv.ParseFloat(s, 64) + if err != nil { + return 0 + } + return res +} + +func StrToFloat32(s string) float32 { + res, err := strconv.ParseFloat(s, 32) + if err != nil { + return 0 + } + return float32(res) +} + +func StrToBool(s string) bool { + b, _ := strconv.ParseBool(s) + return b +} + +func BoolToStr(b bool) string { + if b { + return "true" + } + return "false" +} + +func FloatToInt64(f float64) int64 { + return int64(f) +} + +func IntToStr(i int) string { + return strconv.Itoa(i) +} + +func Int64ToStr(i int64) string { + return strconv.FormatInt(i, 10) +} diff --git a/mall/utils/crypto.go b/mall/utils/crypto.go new file mode 100644 index 0000000..56289c5 --- /dev/null +++ b/mall/utils/crypto.go @@ -0,0 +1,19 @@ +package utils + +import ( + "crypto/md5" + "encoding/base64" + "fmt" +) + +func GetMd5(raw []byte) string { + h := md5.New() + h.Write(raw) + return fmt.Sprintf("%x", h.Sum(nil)) +} + +func GetBase64Md5(raw []byte) string { + h := md5.New() + h.Write(raw) + return base64.StdEncoding.EncodeToString(h.Sum(nil)) +} diff --git a/mall/utils/curl.go b/mall/utils/curl.go new file mode 100644 index 0000000..04e2d10 --- /dev/null +++ b/mall/utils/curl.go @@ -0,0 +1,170 @@ +package utils + +import ( + "bytes" + "crypto/tls" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "sort" + "strings" + "time" +) + +var CurlDebug bool + +func CurlGet(router string, header map[string]string) ([]byte, error) { + return curl(http.MethodGet, router, nil, header) +} + +// 只支持form 与json 提交, 请留意body的类型, 支持string, []byte, map[string]string +func CurlPost(router string, body interface{}, header map[string]string) ([]byte, error) { + return curl(http.MethodPost, router, body, header) +} + +func CurlPut(router string, body interface{}, header map[string]string) ([]byte, error) { + return curl(http.MethodPut, router, body, header) +} + +// 只支持form 与json 提交, 请留意body的类型, 支持string, []byte, map[string]string +func CurlPatch(router string, body interface{}, header map[string]string) ([]byte, error) { + return curl(http.MethodPatch, router, body, header) +} + +// CurlDelete is curl delete +func CurlDelete(router string, body interface{}, header map[string]string) ([]byte, error) { + return curl(http.MethodDelete, router, body, header) +} + +func curl(method, router string, body interface{}, header map[string]string) ([]byte, error) { + var reqBody io.Reader + contentType := "application/json" + switch v := body.(type) { + case string: + reqBody = strings.NewReader(v) + case []byte: + reqBody = bytes.NewReader(v) + case map[string]string: + val := url.Values{} + for k, v := range v { + val.Set(k, v) + } + reqBody = strings.NewReader(val.Encode()) + contentType = "application/x-www-form-urlencoded" + case map[string]interface{}: + val := url.Values{} + for k, v := range v { + val.Set(k, v.(string)) + } + reqBody = strings.NewReader(val.Encode()) + contentType = "application/x-www-form-urlencoded" + } + if header == nil { + header = map[string]string{"Content-Type": contentType} + } + if _, ok := header["Content-Type"]; !ok { + header["Content-Type"] = contentType + } + resp, er := CurlReq(method, router, reqBody, header) + if er != nil { + return nil, er + } + res, err := ioutil.ReadAll(resp.Body) + if CurlDebug { + blob := SerializeStr(body) + if contentType != "application/json" { + blob = HttpBuild(body) + } + fmt.Printf("\n\n=====================\n[url]: %s\n[time]: %s\n[method]: %s\n[content-type]: %v\n[req_header]: %s\n[req_body]: %#v\n[resp_err]: %v\n[resp_header]: %v\n[resp_body]: %v\n=====================\n\n", + router, + time.Now().Format("2006-01-02 15:04:05.000"), + method, + contentType, + HttpBuildQuery(header), + blob, + err, + SerializeStr(resp.Header), + string(res), + ) + } + resp.Body.Close() + return res, err +} + +func CurlReq(method, router string, reqBody io.Reader, header map[string]string) (*http.Response, error) { + req, _ := http.NewRequest(method, router, reqBody) + if header != nil { + for k, v := range header { + req.Header.Set(k, v) + } + } + // 绕过github等可能因为特征码返回503问题 + // https://www.imwzk.com/posts/2021-03-14-why-i-always-get-503-with-golang/ + defaultCipherSuites := []uint16{0xc02f, 0xc030, 0xc02b, 0xc02c, 0xcca8, 0xcca9, 0xc013, 0xc009, + 0xc014, 0xc00a, 0x009c, 0x009d, 0x002f, 0x0035, 0xc012, 0x000a} + client := &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + CipherSuites: append(defaultCipherSuites[8:], defaultCipherSuites[:8]...), + }, + }, + // 获取301重定向 + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }, + } + return client.Do(req) +} + +// 组建get请求参数,sortAsc true为小到大,false为大到小,nil不排序 a=123&b=321 +func HttpBuildQuery(args map[string]string, sortAsc ...bool) string { + str := "" + if len(args) == 0 { + return str + } + if len(sortAsc) > 0 { + keys := make([]string, 0, len(args)) + for k := range args { + keys = append(keys, k) + } + if sortAsc[0] { + sort.Strings(keys) + } else { + sort.Sort(sort.Reverse(sort.StringSlice(keys))) + } + for _, k := range keys { + str += "&" + k + "=" + args[k] + } + } else { + for k, v := range args { + str += "&" + k + "=" + v + } + } + return str[1:] +} + +func HttpBuild(body interface{}, sortAsc ...bool) string { + params := map[string]string{} + if args, ok := body.(map[string]interface{}); ok { + for k, v := range args { + params[k] = AnyToString(v) + } + return HttpBuildQuery(params, sortAsc...) + } + if args, ok := body.(map[string]string); ok { + for k, v := range args { + params[k] = AnyToString(v) + } + return HttpBuildQuery(params, sortAsc...) + } + if args, ok := body.(map[string]int); ok { + for k, v := range args { + params[k] = AnyToString(v) + } + return HttpBuildQuery(params, sortAsc...) + } + return AnyToString(body) +} diff --git a/mall/utils/debug.go b/mall/utils/debug.go new file mode 100644 index 0000000..bb2e9d3 --- /dev/null +++ b/mall/utils/debug.go @@ -0,0 +1,25 @@ +package utils + +import ( + "fmt" + "os" + "strconv" + "time" +) + +func Debug(args ...interface{}) { + s := "" + l := len(args) + if l < 1 { + fmt.Println("please input some data") + os.Exit(0) + } + i := 1 + for _, v := range args { + s += fmt.Sprintf("【"+strconv.Itoa(i)+"】: %#v\n", v) + i++ + } + s = "******************** 【DEBUG - " + time.Now().Format("2006-01-02 15:04:05") + "】 ********************\n" + s + "******************** 【DEBUG - END】 ********************\n" + fmt.Println(s) + os.Exit(0) +} diff --git a/mall/utils/duplicate.go b/mall/utils/duplicate.go new file mode 100644 index 0000000..17cea88 --- /dev/null +++ b/mall/utils/duplicate.go @@ -0,0 +1,37 @@ +package utils + +func RemoveDuplicateString(elms []string) []string { + res := make([]string, 0, len(elms)) + temp := map[string]struct{}{} + for _, item := range elms { + if _, ok := temp[item]; !ok { + temp[item] = struct{}{} + res = append(res, item) + } + } + return res +} + +func RemoveDuplicateInt(elms []int) []int { + res := make([]int, 0, len(elms)) + temp := map[int]struct{}{} + for _, item := range elms { + if _, ok := temp[item]; !ok { + temp[item] = struct{}{} + res = append(res, item) + } + } + return res +} + +func RemoveDuplicateInt64(elms []int64) []int64 { + res := make([]int64, 0, len(elms)) + temp := map[int64]struct{}{} + for _, item := range elms { + if _, ok := temp[item]; !ok { + temp[item] = struct{}{} + res = append(res, item) + } + } + return res +} diff --git a/mall/utils/file.go b/mall/utils/file.go new file mode 100644 index 0000000..93ed08f --- /dev/null +++ b/mall/utils/file.go @@ -0,0 +1,22 @@ +package utils + +import ( + "os" + "path" + "strings" + "time" +) + +// 获取文件后缀 +func FileExt(fname string) string { + return strings.ToLower(strings.TrimLeft(path.Ext(fname), ".")) +} + +func FilePutContents(fileName string, content string) { + fd, _ := os.OpenFile("./tmp/"+fileName+".log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644) + fd_time := time.Now().Format("2006-01-02 15:04:05") + fd_content := strings.Join([]string{"[", fd_time, "] ", content, "\n"}, "") + buf := []byte(fd_content) + fd.Write(buf) + fd.Close() +} diff --git a/mall/utils/file_and_dir.go b/mall/utils/file_and_dir.go new file mode 100644 index 0000000..93141f9 --- /dev/null +++ b/mall/utils/file_and_dir.go @@ -0,0 +1,29 @@ +package utils + +import "os" + +// 判断所给路径文件、文件夹是否存在 +func Exists(path string) bool { + _, err := os.Stat(path) //os.Stat获取文件信息 + if err != nil { + if os.IsExist(err) { + return true + } + return false + } + return true +} + +// 判断所给路径是否为文件夹 +func IsDir(path string) bool { + s, err := os.Stat(path) + if err != nil { + return false + } + return s.IsDir() +} + +// 判断所给路径是否为文件 +func IsFile(path string) bool { + return !IsDir(path) +} diff --git a/mall/utils/format.go b/mall/utils/format.go new file mode 100644 index 0000000..0ed998d --- /dev/null +++ b/mall/utils/format.go @@ -0,0 +1,59 @@ +package utils + +import ( + "math" +) + +func CouponFormat(data string) string { + switch data { + case "0.00", "0", "": + return "" + default: + return Int64ToStr(FloatToInt64(StrToFloat64(data))) + } +} +func CommissionFormat(data string) string { + if data != "" && data != "0" { + return data + } + + return "" +} + +func HideString(src string, hLen int) string { + str := []rune(src) + if hLen == 0 { + hLen = 4 + } + hideStr := "" + for i := 0; i < hLen; i++ { + hideStr += "*" + } + hideLen := len(str) / 2 + showLen := len(str) - hideLen + if hideLen == 0 || showLen == 0 { + return hideStr + } + subLen := showLen / 2 + if subLen == 0 { + return string(str[:showLen]) + hideStr + } + s := string(str[:subLen]) + s += hideStr + s += string(str[len(str)-subLen:]) + return s +} + +//SaleCountFormat is 格式化销量 +func SaleCountFormat(s string) string { + return s + "已售" +} + +// 小数格式化 +func FloatFormat(f float64, i int) float64 { + if i > 14 { + return f + } + p := math.Pow10(i) + return float64(int64((f+0.000000000000009)*p)) / p +} diff --git a/mall/utils/json.go b/mall/utils/json.go new file mode 100644 index 0000000..f5c0fd0 --- /dev/null +++ b/mall/utils/json.go @@ -0,0 +1,161 @@ +package utils + +import ( + "bytes" + "encoding/json" + "log" + "regexp" + "strconv" + "strings" + "unicode" +) + +func JsonMarshal(interface{}) { + +} + +// 不科学计数法 +func JsonDecode(data []byte, v interface{}) error { + d := json.NewDecoder(bytes.NewReader(data)) + d.UseNumber() + return d.Decode(v) +} + +/*************************************** 下划线json ***************************************/ +type JsonSnakeCase struct { + Value interface{} +} + +func (c JsonSnakeCase) MarshalJSON() ([]byte, error) { + // Regexp definitions + var keyMatchRegex = regexp.MustCompile(`\"(\w+)\":`) + var wordBarrierRegex = regexp.MustCompile(`(\w)([A-Z])`) + marshalled, err := json.Marshal(c.Value) + converted := keyMatchRegex.ReplaceAllFunc( + marshalled, + func(match []byte) []byte { + return bytes.ToLower(wordBarrierRegex.ReplaceAll( + match, + []byte(`${1}_${2}`), + )) + }, + ) + return converted, err +} + +/*************************************** 驼峰json ***************************************/ +type JsonCamelCase struct { + Value interface{} +} + +func (c JsonCamelCase) MarshalJSON() ([]byte, error) { + var keyMatchRegex = regexp.MustCompile(`\"(\w+)\":`) + marshalled, err := json.Marshal(c.Value) + converted := keyMatchRegex.ReplaceAllFunc( + marshalled, + func(match []byte) []byte { + matchStr := string(match) + key := matchStr[1 : len(matchStr)-2] + resKey := Lcfirst(Case2Camel(key)) + return []byte(`"` + resKey + `":`) + }, + ) + return converted, err +} + +// 驼峰式写法转为下划线写法 +func Camel2Case(name string) string { + buffer := NewBuffer() + for i, r := range name { + if unicode.IsUpper(r) { + if i != 0 { + buffer.Append('_') + } + buffer.Append(unicode.ToLower(r)) + } else { + buffer.Append(r) + } + } + return buffer.String() +} + +// 下划线写法转为驼峰写法 +func Case2Camel(name string) string { + name = strings.Replace(name, "_", " ", -1) + name = strings.Title(name) + return strings.Replace(name, " ", "", -1) +} + +// 首字母大写 +func Ucfirst(str string) string { + for i, v := range str { + return string(unicode.ToUpper(v)) + str[i+1:] + } + return "" +} + +// 首字母小写 +func Lcfirst(str string) string { + for i, v := range str { + return string(unicode.ToLower(v)) + str[i+1:] + } + return "" +} + +// 内嵌bytes.Buffer,支持连写 +type Buffer struct { + *bytes.Buffer +} + +func NewBuffer() *Buffer { + return &Buffer{Buffer: new(bytes.Buffer)} +} + +func (b *Buffer) Append(i interface{}) *Buffer { + switch val := i.(type) { + case int: + b.append(strconv.Itoa(val)) + case int64: + b.append(strconv.FormatInt(val, 10)) + case uint: + b.append(strconv.FormatUint(uint64(val), 10)) + case uint64: + b.append(strconv.FormatUint(val, 10)) + case string: + b.append(val) + case []byte: + b.Write(val) + case rune: + b.WriteRune(val) + } + return b +} + +func (b *Buffer) append(s string) *Buffer { + defer func() { + if err := recover(); err != nil { + log.Println("*****内存不够了!******") + } + }() + b.WriteString(s) + return b +} + +// json字符串驼峰命名格式 转为 下划线命名格式 +// c :json字符串 +func MarshalJSONCamelCase2JsonSnakeCase(c string) []byte { + // Regexp definitions + var keyMatchRegex = regexp.MustCompile(`\"(\w+)\":`) + var wordBarrierRegex = regexp.MustCompile(`(\w)([A-Z])`) + marshalled := []byte(c) + converted := keyMatchRegex.ReplaceAllFunc( + marshalled, + func(match []byte) []byte { + return bytes.ToLower(wordBarrierRegex.ReplaceAll( + match, + []byte(`${1}_${2}`), + )) + }, + ) + return converted +} diff --git a/mall/utils/json_time_parse.go b/mall/utils/json_time_parse.go new file mode 100644 index 0000000..7c2cb37 --- /dev/null +++ b/mall/utils/json_time_parse.go @@ -0,0 +1,31 @@ +package utils + +import "time" + +// 自定义时间类型 用于解决json解析错误问题 +type LocalTime time.Time + +const TimeFormat = "2006-01-02 15:04:05" + +// 实现 json解析(反序列化)接口,调用c.ShouldBindJSON()方法时,ShouldBindJSON()方法会调用 +func (t *LocalTime) UnmarshalJSON(data []byte) (err error) { + // 空值不进行解析 + if len(data) == 2 { + *t = LocalTime(time.Time{}) + return + } + + // 指定解析的格式 + now, err := time.Parse(`"`+TimeFormat+`"`, string(data)) + *t = LocalTime(now) + return +} + +// 实现 json(序列化)接口 +func (t LocalTime) MarshalJSON() ([]byte, error) { + b := make([]byte, 0, len(TimeFormat)+2) + b = append(b, '"') + b = time.Time(t).AppendFormat(b, TimeFormat) + b = append(b, '"') + return b, nil +} diff --git a/mall/utils/logx/log.go b/mall/utils/logx/log.go new file mode 100644 index 0000000..ca11223 --- /dev/null +++ b/mall/utils/logx/log.go @@ -0,0 +1,245 @@ +package logx + +import ( + "os" + "strings" + "time" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +type LogConfig struct { + AppName string `yaml:"app_name" json:"app_name" toml:"app_name"` + Level string `yaml:"level" json:"level" toml:"level"` + StacktraceLevel string `yaml:"stacktrace_level" json:"stacktrace_level" toml:"stacktrace_level"` + IsStdOut bool `yaml:"is_stdout" json:"is_stdout" toml:"is_stdout"` + TimeFormat string `yaml:"time_format" json:"time_format" toml:"time_format"` // second, milli, nano, standard, iso, + Encoding string `yaml:"encoding" json:"encoding" toml:"encoding"` // console, json + Skip int `yaml:"skip" json:"skip" toml:"skip"` + + IsFileOut bool `yaml:"is_file_out" json:"is_file_out" toml:"is_file_out"` + FileDir string `yaml:"file_dir" json:"file_dir" toml:"file_dir"` + FileName string `yaml:"file_name" json:"file_name" toml:"file_name"` + FileMaxSize int `yaml:"file_max_size" json:"file_max_size" toml:"file_max_size"` + FileMaxAge int `yaml:"file_max_age" json:"file_max_age" toml:"file_max_age"` +} + +var ( + l *LogX = defaultLogger() + conf *LogConfig +) + +// default logger setting +func defaultLogger() *LogX { + conf = &LogConfig{ + Level: "debug", + StacktraceLevel: "error", + IsStdOut: true, + TimeFormat: "standard", + Encoding: "console", + Skip: 2, + } + writers := []zapcore.WriteSyncer{os.Stdout} + lg, lv := newZapLogger(setLogLevel(conf.Level), setLogLevel(conf.StacktraceLevel), conf.Encoding, conf.TimeFormat, conf.Skip, zapcore.NewMultiWriteSyncer(writers...)) + zap.RedirectStdLog(lg) + return &LogX{logger: lg, atomLevel: lv} +} + +// initial standard log, if you don't init, it will use default logger setting +func InitDefaultLogger(cfg *LogConfig) { + var writers []zapcore.WriteSyncer + if cfg.IsStdOut || (!cfg.IsStdOut && !cfg.IsFileOut) { + writers = append(writers, os.Stdout) + } + if cfg.IsFileOut { + writers = append(writers, NewRollingFile(cfg.FileDir, cfg.FileName, cfg.FileMaxSize, cfg.FileMaxAge)) + } + + lg, lv := newZapLogger(setLogLevel(cfg.Level), setLogLevel(cfg.StacktraceLevel), cfg.Encoding, cfg.TimeFormat, cfg.Skip, zapcore.NewMultiWriteSyncer(writers...)) + zap.RedirectStdLog(lg) + if cfg.AppName != "" { + lg = lg.With(zap.String("app", cfg.AppName)) // 加上应用名称 + } + l = &LogX{logger: lg, atomLevel: lv} +} + +// create a new logger +func NewLogger(cfg *LogConfig) *LogX { + var writers []zapcore.WriteSyncer + if cfg.IsStdOut || (!cfg.IsStdOut && !cfg.IsFileOut) { + writers = append(writers, os.Stdout) + } + if cfg.IsFileOut { + writers = append(writers, NewRollingFile(cfg.FileDir, cfg.FileName, cfg.FileMaxSize, cfg.FileMaxAge)) + } + + lg, lv := newZapLogger(setLogLevel(cfg.Level), setLogLevel(cfg.StacktraceLevel), cfg.Encoding, cfg.TimeFormat, cfg.Skip, zapcore.NewMultiWriteSyncer(writers...)) + zap.RedirectStdLog(lg) + if cfg.AppName != "" { + lg = lg.With(zap.String("app", cfg.AppName)) // 加上应用名称 + } + return &LogX{logger: lg, atomLevel: lv} +} + +// create a new zaplog logger +func newZapLogger(level, stacktrace zapcore.Level, encoding, timeType string, skip int, output zapcore.WriteSyncer) (*zap.Logger, *zap.AtomicLevel) { + encCfg := zapcore.EncoderConfig{ + TimeKey: "T", + LevelKey: "L", + NameKey: "N", + CallerKey: "C", + MessageKey: "M", + StacktraceKey: "S", + LineEnding: zapcore.DefaultLineEnding, + EncodeCaller: zapcore.ShortCallerEncoder, + EncodeDuration: zapcore.NanosDurationEncoder, + EncodeLevel: zapcore.LowercaseLevelEncoder, + } + setTimeFormat(timeType, &encCfg) // set time type + atmLvl := zap.NewAtomicLevel() // set level + atmLvl.SetLevel(level) + encoder := zapcore.NewJSONEncoder(encCfg) // 确定encoder格式 + if encoding == "console" { + encoder = zapcore.NewConsoleEncoder(encCfg) + } + return zap.New(zapcore.NewCore(encoder, output, atmLvl), zap.AddCaller(), zap.AddStacktrace(stacktrace), zap.AddCallerSkip(skip)), &atmLvl +} + +// set log level +func setLogLevel(lvl string) zapcore.Level { + switch strings.ToLower(lvl) { + case "panic": + return zapcore.PanicLevel + case "fatal": + return zapcore.FatalLevel + case "error": + return zapcore.ErrorLevel + case "warn", "warning": + return zapcore.WarnLevel + case "info": + return zapcore.InfoLevel + default: + return zapcore.DebugLevel + } +} + +// set time format +func setTimeFormat(timeType string, z *zapcore.EncoderConfig) { + switch strings.ToLower(timeType) { + case "iso": // iso8601 standard + z.EncodeTime = zapcore.ISO8601TimeEncoder + case "sec": // only for unix second, without millisecond + z.EncodeTime = func(t time.Time, enc zapcore.PrimitiveArrayEncoder) { + enc.AppendInt64(t.Unix()) + } + case "second": // unix second, with millisecond + z.EncodeTime = zapcore.EpochTimeEncoder + case "milli", "millisecond": // millisecond + z.EncodeTime = zapcore.EpochMillisTimeEncoder + case "nano", "nanosecond": // nanosecond + z.EncodeTime = zapcore.EpochNanosTimeEncoder + default: // standard format + z.EncodeTime = func(t time.Time, enc zapcore.PrimitiveArrayEncoder) { + enc.AppendString(t.Format("2006-01-02 15:04:05.000")) + } + } +} + +func GetLevel() string { + switch l.atomLevel.Level() { + case zapcore.PanicLevel: + return "panic" + case zapcore.FatalLevel: + return "fatal" + case zapcore.ErrorLevel: + return "error" + case zapcore.WarnLevel: + return "warn" + case zapcore.InfoLevel: + return "info" + default: + return "debug" + } +} + +func SetLevel(lvl string) { + l.atomLevel.SetLevel(setLogLevel(lvl)) +} + +// temporary add call skip +func AddCallerSkip(skip int) *LogX { + l.logger.WithOptions(zap.AddCallerSkip(skip)) + return l +} + +// permanent add call skip +func AddDepth(skip int) *LogX { + l.logger = l.logger.WithOptions(zap.AddCallerSkip(skip)) + return l +} + +// permanent add options +func AddOptions(opts ...zap.Option) *LogX { + l.logger = l.logger.WithOptions(opts...) + return l +} + +func AddField(k string, v interface{}) { + l.logger.With(zap.Any(k, v)) +} + +func AddFields(fields map[string]interface{}) *LogX { + for k, v := range fields { + l.logger.With(zap.Any(k, v)) + } + return l +} + +// Normal log +func Debug(e interface{}, args ...interface{}) error { + return l.Debug(e, args...) +} +func Info(e interface{}, args ...interface{}) error { + return l.Info(e, args...) +} +func Warn(e interface{}, args ...interface{}) error { + return l.Warn(e, args...) +} +func Error(e interface{}, args ...interface{}) error { + return l.Error(e, args...) +} +func Panic(e interface{}, args ...interface{}) error { + return l.Panic(e, args...) +} +func Fatal(e interface{}, args ...interface{}) error { + return l.Fatal(e, args...) +} + +// Format logs +func Debugf(format string, args ...interface{}) error { + return l.Debugf(format, args...) +} +func Infof(format string, args ...interface{}) error { + return l.Infof(format, args...) +} +func Warnf(format string, args ...interface{}) error { + return l.Warnf(format, args...) +} +func Errorf(format string, args ...interface{}) error { + return l.Errorf(format, args...) +} +func Panicf(format string, args ...interface{}) error { + return l.Panicf(format, args...) +} +func Fatalf(format string, args ...interface{}) error { + return l.Fatalf(format, args...) +} + +func formatFieldMap(m FieldMap) []Field { + var res []Field + for k, v := range m { + res = append(res, zap.Any(k, v)) + } + return res +} diff --git a/mall/utils/logx/output.go b/mall/utils/logx/output.go new file mode 100644 index 0000000..ef33f0b --- /dev/null +++ b/mall/utils/logx/output.go @@ -0,0 +1,105 @@ +package logx + +import ( + "bytes" + "io" + "os" + "path/filepath" + "time" + + "gopkg.in/natefinch/lumberjack.v2" +) + +// output interface +type WriteSyncer interface { + io.Writer + Sync() error +} + +// split writer +func NewRollingFile(dir, filename string, maxSize, MaxAge int) WriteSyncer { + s, err := os.Stat(dir) + if err != nil || !s.IsDir() { + os.RemoveAll(dir) + if err := os.MkdirAll(dir, 0766); err != nil { + panic(err) + } + } + return newLumberjackWriteSyncer(&lumberjack.Logger{ + Filename: filepath.Join(dir, filename), + MaxSize: maxSize, // megabytes, MB + MaxAge: MaxAge, // days + LocalTime: true, + Compress: false, + }) +} + +type lumberjackWriteSyncer struct { + *lumberjack.Logger + buf *bytes.Buffer + logChan chan []byte + closeChan chan interface{} + maxSize int +} + +func newLumberjackWriteSyncer(l *lumberjack.Logger) *lumberjackWriteSyncer { + ws := &lumberjackWriteSyncer{ + Logger: l, + buf: bytes.NewBuffer([]byte{}), + logChan: make(chan []byte, 5000), + closeChan: make(chan interface{}), + maxSize: 1024, + } + go ws.run() + return ws +} + +func (l *lumberjackWriteSyncer) run() { + ticker := time.NewTicker(1 * time.Second) + + for { + select { + case <-ticker.C: + if l.buf.Len() > 0 { + l.sync() + } + case bs := <-l.logChan: + _, err := l.buf.Write(bs) + if err != nil { + continue + } + if l.buf.Len() > l.maxSize { + l.sync() + } + case <-l.closeChan: + l.sync() + return + } + } +} + +func (l *lumberjackWriteSyncer) Stop() { + close(l.closeChan) +} + +func (l *lumberjackWriteSyncer) Write(bs []byte) (int, error) { + b := make([]byte, len(bs)) + for i, c := range bs { + b[i] = c + } + l.logChan <- b + return 0, nil +} + +func (l *lumberjackWriteSyncer) Sync() error { + return nil +} + +func (l *lumberjackWriteSyncer) sync() error { + defer l.buf.Reset() + _, err := l.Logger.Write(l.buf.Bytes()) + if err != nil { + return err + } + return nil +} diff --git a/mall/utils/logx/sugar.go b/mall/utils/logx/sugar.go new file mode 100644 index 0000000..ab380fc --- /dev/null +++ b/mall/utils/logx/sugar.go @@ -0,0 +1,192 @@ +package logx + +import ( + "errors" + "fmt" + "strconv" + + "go.uber.org/zap" +) + +type LogX struct { + logger *zap.Logger + atomLevel *zap.AtomicLevel +} + +type Field = zap.Field +type FieldMap map[string]interface{} + +// 判断其他类型--start +func getFields(msg string, format bool, args ...interface{}) (string, []Field) { + var str []interface{} + var fields []zap.Field + if len(args) > 0 { + for _, v := range args { + if f, ok := v.(Field); ok { + fields = append(fields, f) + } else if f, ok := v.(FieldMap); ok { + fields = append(fields, formatFieldMap(f)...) + } else { + str = append(str, AnyToString(v)) + } + } + if format { + return fmt.Sprintf(msg, str...), fields + } + str = append([]interface{}{msg}, str...) + return fmt.Sprintln(str...), fields + } + return msg, []Field{} +} + +func (l *LogX) Debug(s interface{}, args ...interface{}) error { + es, e := checkErr(s) + if es != "" { + msg, field := getFields(es, false, args...) + l.logger.Debug(msg, field...) + } + return e +} +func (l *LogX) Info(s interface{}, args ...interface{}) error { + es, e := checkErr(s) + if es != "" { + msg, field := getFields(es, false, args...) + l.logger.Info(msg, field...) + } + return e +} +func (l *LogX) Warn(s interface{}, args ...interface{}) error { + es, e := checkErr(s) + if es != "" { + msg, field := getFields(es, false, args...) + l.logger.Warn(msg, field...) + } + return e +} +func (l *LogX) Error(s interface{}, args ...interface{}) error { + es, e := checkErr(s) + if es != "" { + msg, field := getFields(es, false, args...) + l.logger.Error(msg, field...) + } + return e +} +func (l *LogX) DPanic(s interface{}, args ...interface{}) error { + es, e := checkErr(s) + if es != "" { + msg, field := getFields(es, false, args...) + l.logger.DPanic(msg, field...) + } + return e +} +func (l *LogX) Panic(s interface{}, args ...interface{}) error { + es, e := checkErr(s) + if es != "" { + msg, field := getFields(es, false, args...) + l.logger.Panic(msg, field...) + } + return e +} +func (l *LogX) Fatal(s interface{}, args ...interface{}) error { + es, e := checkErr(s) + if es != "" { + msg, field := getFields(es, false, args...) + l.logger.Fatal(msg, field...) + } + return e +} + +func checkErr(s interface{}) (string, error) { + switch e := s.(type) { + case error: + return e.Error(), e + case string: + return e, errors.New(e) + case []byte: + return string(e), nil + default: + return "", nil + } +} + +func (l *LogX) LogError(err error) error { + return l.Error(err.Error()) +} + +func (l *LogX) Debugf(msg string, args ...interface{}) error { + s, f := getFields(msg, true, args...) + l.logger.Debug(s, f...) + return errors.New(s) +} + +func (l *LogX) Infof(msg string, args ...interface{}) error { + s, f := getFields(msg, true, args...) + l.logger.Info(s, f...) + return errors.New(s) +} + +func (l *LogX) Warnf(msg string, args ...interface{}) error { + s, f := getFields(msg, true, args...) + l.logger.Warn(s, f...) + return errors.New(s) +} + +func (l *LogX) Errorf(msg string, args ...interface{}) error { + s, f := getFields(msg, true, args...) + l.logger.Error(s, f...) + return errors.New(s) +} + +func (l *LogX) DPanicf(msg string, args ...interface{}) error { + s, f := getFields(msg, true, args...) + l.logger.DPanic(s, f...) + return errors.New(s) +} + +func (l *LogX) Panicf(msg string, args ...interface{}) error { + s, f := getFields(msg, true, args...) + l.logger.Panic(s, f...) + return errors.New(s) +} + +func (l *LogX) Fatalf(msg string, args ...interface{}) error { + s, f := getFields(msg, true, args...) + l.logger.Fatal(s, f...) + return errors.New(s) +} + +func AnyToString(raw interface{}) string { + switch i := raw.(type) { + case []byte: + return string(i) + case int: + return strconv.FormatInt(int64(i), 10) + case int64: + return strconv.FormatInt(i, 10) + case float32: + return strconv.FormatFloat(float64(i), 'f', 2, 64) + case float64: + return strconv.FormatFloat(i, 'f', 2, 64) + case uint: + return strconv.FormatInt(int64(i), 10) + case uint8: + return strconv.FormatInt(int64(i), 10) + case uint16: + return strconv.FormatInt(int64(i), 10) + case uint32: + return strconv.FormatInt(int64(i), 10) + case uint64: + return strconv.FormatInt(int64(i), 10) + case int8: + return strconv.FormatInt(int64(i), 10) + case int16: + return strconv.FormatInt(int64(i), 10) + case int32: + return strconv.FormatInt(int64(i), 10) + case string: + return i + case error: + return i.Error() + } + return fmt.Sprintf("%#v", raw) +} diff --git a/mall/utils/map.go b/mall/utils/map.go new file mode 100644 index 0000000..501ad96 --- /dev/null +++ b/mall/utils/map.go @@ -0,0 +1,55 @@ +package utils + +// MapStringKeys 取出map的key +func MapStringKeys(collection *map[string]struct{}) []interface{} { + result := make([]interface{}, 0, len(*collection)) + for key := range *collection { + result = append(result, key) + } + + return result +} + +// GetOneKeyOfMapString 取出Map的一个key +func GetOneKeyOfMapString(collection map[string]string) string { + for k := range collection { + return k + } + return "" +} + +// sources源数组,num拆分份数,size每份的大小 +func SplitArray(sources []string, num, pageSize int64) [][]string { + max := int64(len(sources)) + if max < num { + return nil + } + var segmens = make([][]string, 0) + quantity := pageSize + end := int64(0) + for i := int64(1); i <= num; i++ { + qu := i * quantity + if i != num { + segmens = append(segmens, sources[i-1+end:qu]) + } else { + segmens = append(segmens, sources[i-1+end:]) + } + end = qu - i + } + return segmens +} + +// sourceslen源数组长度,pageSize页数据量 +// 获取拆分份数 +func SplitArrayCnt(sourceslen, pageSize int) int { + if sourceslen < pageSize { + return 1 + } + s := sourceslen / pageSize + y := sourceslen % pageSize + if y > 0 { + return s + 1 + } else { + return s + } +} diff --git a/mall/utils/map_and_struct.go b/mall/utils/map_and_struct.go new file mode 100644 index 0000000..34904ce --- /dev/null +++ b/mall/utils/map_and_struct.go @@ -0,0 +1,341 @@ +package utils + +import ( + "encoding/json" + "fmt" + "reflect" + "strconv" + "strings" +) + +func Map2Struct(vals map[string]interface{}, dst interface{}) (err error) { + return Map2StructByTag(vals, dst, "json") +} + +func Map2StructByTag(vals map[string]interface{}, dst interface{}, structTag string) (err error) { + defer func() { + e := recover() + if e != nil { + if v, ok := e.(error); ok { + err = fmt.Errorf("Panic: %v", v.Error()) + } else { + err = fmt.Errorf("Panic: %v", e) + } + } + }() + + pt := reflect.TypeOf(dst) + pv := reflect.ValueOf(dst) + + if pv.Kind() != reflect.Ptr || pv.Elem().Kind() != reflect.Struct { + return fmt.Errorf("not a pointer of struct") + } + + var f reflect.StructField + var ft reflect.Type + var fv reflect.Value + + for i := 0; i < pt.Elem().NumField(); i++ { + f = pt.Elem().Field(i) + fv = pv.Elem().Field(i) + ft = f.Type + + if f.Anonymous || !fv.CanSet() { + continue + } + + tag := f.Tag.Get(structTag) + + name, option := parseTag(tag) + + if name == "-" { + continue + } + + if name == "" { + name = strings.ToLower(f.Name) + } + val, ok := vals[name] + + if !ok { + if option == "required" { + return fmt.Errorf("'%v' not found", name) + } + if len(option) != 0 { + val = option // default value + } else { + //fv.Set(reflect.Zero(ft)) // TODO set zero value or just ignore it? + continue + } + } + + // convert or set value to field + vv := reflect.ValueOf(val) + vt := reflect.TypeOf(val) + + if vt.Kind() != reflect.String { + // try to assign and convert + if vt.AssignableTo(ft) { + fv.Set(vv) + continue + } + + if vt.ConvertibleTo(ft) { + fv.Set(vv.Convert(ft)) + continue + } + + return fmt.Errorf("value type not match: field=%v(%v) value=%v(%v)", f.Name, ft.Kind(), val, vt.Kind()) + } + s := strings.TrimSpace(vv.String()) + if len(s) == 0 && option == "required" { + return fmt.Errorf("value of required argument can't not be empty") + } + fk := ft.Kind() + + // convert string to value + if fk == reflect.Ptr && ft.Elem().Kind() == reflect.String { + fv.Set(reflect.ValueOf(&s)) + continue + } + if fk == reflect.Ptr || fk == reflect.Struct { + err = convertJsonValue(s, name, fv) + } else if fk == reflect.Slice { + err = convertSlice(s, f.Name, ft, fv) + } else { + err = convertValue(fk, s, f.Name, fv) + } + + if err != nil { + return err + } + continue + } + + return nil +} + +func Struct2Map(s interface{}) map[string]interface{} { + return Struct2MapByTag(s, "json") +} +func Struct2MapByTag(s interface{}, tagName string) map[string]interface{} { + t := reflect.TypeOf(s) + v := reflect.ValueOf(s) + + if v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct { + t = t.Elem() + v = v.Elem() + } + + if v.Kind() != reflect.Struct { + return nil + } + + m := make(map[string]interface{}) + + for i := 0; i < t.NumField(); i++ { + fv := v.Field(i) + ft := t.Field(i) + + if !fv.CanInterface() { + continue + } + + if ft.PkgPath != "" { // unexported + continue + } + + var name string + var option string + tag := ft.Tag.Get(tagName) + if tag != "" { + ts := strings.Split(tag, ",") + if len(ts) == 1 { + name = ts[0] + } else if len(ts) > 1 { + name = ts[0] + option = ts[1] + } + if name == "-" { + continue // skip this field + } + if name == "" { + name = strings.ToLower(ft.Name) + } + if option == "omitempty" { + if isEmpty(&fv) { + continue // skip empty field + } + } + } else { + name = strings.ToLower(ft.Name) + } + + if ft.Anonymous && fv.Kind() == reflect.Ptr && fv.IsNil() { + continue + } + if (ft.Anonymous && fv.Kind() == reflect.Struct) || + (ft.Anonymous && fv.Kind() == reflect.Ptr && fv.Elem().Kind() == reflect.Struct) { + + // embedded struct + embedded := Struct2MapByTag(fv.Interface(), tagName) + for embName, embValue := range embedded { + m[embName] = embValue + } + } else if option == "string" { + kind := fv.Kind() + if kind == reflect.Int || kind == reflect.Int8 || kind == reflect.Int16 || kind == reflect.Int32 || kind == reflect.Int64 { + m[name] = strconv.FormatInt(fv.Int(), 10) + } else if kind == reflect.Uint || kind == reflect.Uint8 || kind == reflect.Uint16 || kind == reflect.Uint32 || kind == reflect.Uint64 { + m[name] = strconv.FormatUint(fv.Uint(), 10) + } else if kind == reflect.Float32 || kind == reflect.Float64 { + m[name] = strconv.FormatFloat(fv.Float(), 'f', 2, 64) + } else { + m[name] = fv.Interface() + } + } else { + m[name] = fv.Interface() + } + } + + return m +} + +func isEmpty(v *reflect.Value) bool { + k := v.Kind() + if k == reflect.Bool { + return v.Bool() == false + } else if reflect.Int < k && k < reflect.Int64 { + return v.Int() == 0 + } else if reflect.Uint < k && k < reflect.Uintptr { + return v.Uint() == 0 + } else if k == reflect.Float32 || k == reflect.Float64 { + return v.Float() == 0 + } else if k == reflect.Array || k == reflect.Map || k == reflect.Slice || k == reflect.String { + return v.Len() == 0 + } else if k == reflect.Interface || k == reflect.Ptr { + return v.IsNil() + } + return false +} + +func convertSlice(s string, name string, ft reflect.Type, fv reflect.Value) error { + var err error + et := ft.Elem() + + if et.Kind() == reflect.Ptr || et.Kind() == reflect.Struct { + return convertJsonValue(s, name, fv) + } + + ss := strings.Split(s, ",") + + if len(s) == 0 || len(ss) == 0 { + return nil + } + + fs := reflect.MakeSlice(ft, 0, len(ss)) + + for _, si := range ss { + ev := reflect.New(et).Elem() + + err = convertValue(et.Kind(), si, name, ev) + if err != nil { + return err + } + fs = reflect.Append(fs, ev) + } + + fv.Set(fs) + + return nil +} + +func convertJsonValue(s string, name string, fv reflect.Value) error { + var err error + d := StringToSlice(s) + + if fv.Kind() == reflect.Ptr { + if fv.IsNil() { + fv.Set(reflect.New(fv.Type().Elem())) + } + } else { + fv = fv.Addr() + } + + err = json.Unmarshal(d, fv.Interface()) + + if err != nil { + return fmt.Errorf("invalid json '%v': %v, %v", name, err.Error(), s) + } + + return nil +} + +func convertValue(kind reflect.Kind, s string, name string, fv reflect.Value) error { + if !fv.CanAddr() { + return fmt.Errorf("can not addr: %v", name) + } + + if kind == reflect.String { + fv.SetString(s) + return nil + } + + if kind == reflect.Bool { + switch s { + case "true": + fv.SetBool(true) + case "false": + fv.SetBool(false) + case "1": + fv.SetBool(true) + case "0": + fv.SetBool(false) + default: + return fmt.Errorf("invalid bool: %v value=%v", name, s) + } + return nil + } + + if reflect.Int <= kind && kind <= reflect.Int64 { + i, err := strconv.ParseInt(s, 10, 64) + if err != nil { + return fmt.Errorf("invalid int: %v value=%v", name, s) + } + fv.SetInt(i) + + } else if reflect.Uint <= kind && kind <= reflect.Uint64 { + i, err := strconv.ParseUint(s, 10, 64) + if err != nil { + return fmt.Errorf("invalid int: %v value=%v", name, s) + } + fv.SetUint(i) + + } else if reflect.Float32 == kind || kind == reflect.Float64 { + i, err := strconv.ParseFloat(s, 64) + + if err != nil { + return fmt.Errorf("invalid float: %v value=%v", name, s) + } + + fv.SetFloat(i) + } else { + // not support or just ignore it? + // return fmt.Errorf("type not support: field=%v(%v) value=%v(%v)", name, ft.Kind(), val, vt.Kind()) + } + return nil +} + +func parseTag(tag string) (string, string) { + tags := strings.Split(tag, ",") + + if len(tags) <= 0 { + return "", "" + } + + if len(tags) == 1 { + return tags[0], "" + } + + return tags[0], tags[1] +} diff --git a/mall/utils/md5.go b/mall/utils/md5.go new file mode 100644 index 0000000..52c108d --- /dev/null +++ b/mall/utils/md5.go @@ -0,0 +1,12 @@ +package utils + +import ( + "crypto/md5" + "encoding/hex" +) + +func Md5(str string) string { + h := md5.New() + h.Write([]byte(str)) + return hex.EncodeToString(h.Sum(nil)) +} diff --git a/mall/utils/qrcode/decodeFile.go b/mall/utils/qrcode/decodeFile.go new file mode 100644 index 0000000..f50fb28 --- /dev/null +++ b/mall/utils/qrcode/decodeFile.go @@ -0,0 +1,33 @@ +package qrcode + +import ( + "image" + _ "image/jpeg" + _ "image/png" + "os" + + "github.com/makiuchi-d/gozxing" + "github.com/makiuchi-d/gozxing/qrcode" +) + +func DecodeFile(fi string) (string, error) { + file, err := os.Open(fi) + if err != nil { + return "", err + } + img, _, err := image.Decode(file) + if err != nil { + return "", err + } + // prepare BinaryBitmap + bmp, err := gozxing.NewBinaryBitmapFromImage(img) + if err != nil { + return "", err + } + // decode image + result, err := qrcode.NewQRCodeReader().Decode(bmp, nil) + if err != nil { + return "", err + } + return result.String(), nil +} diff --git a/mall/utils/qrcode/getBase64.go b/mall/utils/qrcode/getBase64.go new file mode 100644 index 0000000..11d149c --- /dev/null +++ b/mall/utils/qrcode/getBase64.go @@ -0,0 +1,43 @@ +package qrcode + +// 生成登录二维码图片, 方便在网页上显示 + +import ( + "bytes" + "encoding/base64" + "image/jpeg" + "image/png" + + "github.com/boombuler/barcode" + "github.com/boombuler/barcode/qr" +) + +func GetJPGBase64(content string, edges ...int) string { + edgeLen := 300 + if len(edges) > 0 && edges[0] > 100 && edges[0] < 2000 { + edgeLen = edges[0] + } + img, _ := qr.Encode(content, qr.L, qr.Unicode) + img, _ = barcode.Scale(img, edgeLen, edgeLen) + + emptyBuff := bytes.NewBuffer(nil) // 开辟一个新的空buff缓冲区 + jpeg.Encode(emptyBuff, img, nil) + dist := make([]byte, 50000) // 开辟存储空间 + base64.StdEncoding.Encode(dist, emptyBuff.Bytes()) // buff转成base64 + return "data:image/png;base64," + string(dist) // 输出图片base64(type = []byte) +} + +func GetPNGBase64(content string, edges ...int) string { + edgeLen := 300 + if len(edges) > 0 && edges[0] > 100 && edges[0] < 2000 { + edgeLen = edges[0] + } + img, _ := qr.Encode(content, qr.L, qr.Unicode) + img, _ = barcode.Scale(img, edgeLen, edgeLen) + + emptyBuff := bytes.NewBuffer(nil) // 开辟一个新的空buff缓冲区 + png.Encode(emptyBuff, img) + dist := make([]byte, 50000) // 开辟存储空间 + base64.StdEncoding.Encode(dist, emptyBuff.Bytes()) // buff转成base64 + return string(dist) // 输出图片base64(type = []byte) +} diff --git a/mall/utils/qrcode/saveFile.go b/mall/utils/qrcode/saveFile.go new file mode 100644 index 0000000..4854783 --- /dev/null +++ b/mall/utils/qrcode/saveFile.go @@ -0,0 +1,85 @@ +package qrcode + +// 生成登录二维码图片 + +import ( + "errors" + "image" + "image/jpeg" + "image/png" + "os" + "path/filepath" + "strings" + + "github.com/boombuler/barcode" + "github.com/boombuler/barcode/qr" +) + +func SaveJpegFile(filePath, content string, edges ...int) error { + edgeLen := 300 + if len(edges) > 0 && edges[0] > 100 && edges[0] < 2000 { + edgeLen = edges[0] + } + img, _ := qr.Encode(content, qr.L, qr.Unicode) + img, _ = barcode.Scale(img, edgeLen, edgeLen) + + return writeFile(filePath, img, "jpg") +} + +func SavePngFile(filePath, content string, edges ...int) error { + edgeLen := 300 + if len(edges) > 0 && edges[0] > 100 && edges[0] < 2000 { + edgeLen = edges[0] + } + img, _ := qr.Encode(content, qr.L, qr.Unicode) + img, _ = barcode.Scale(img, edgeLen, edgeLen) + + return writeFile(filePath, img, "png") +} + +func writeFile(filePath string, img image.Image, format string) error { + if err := createDir(filePath); err != nil { + return err + } + file, err := os.Create(filePath) + defer file.Close() + if err != nil { + return err + } + switch strings.ToLower(format) { + case "png": + err = png.Encode(file, img) + break + case "jpg": + err = jpeg.Encode(file, img, nil) + default: + return errors.New("format not accept") + } + if err != nil { + return err + } + return nil +} + +func createDir(filePath string) error { + var err error + // filePath, _ = filepath.Abs(filePath) + dirPath := filepath.Dir(filePath) + dirInfo, err := os.Stat(dirPath) + if err != nil { + if !os.IsExist(err) { + err = os.MkdirAll(dirPath, 0777) + if err != nil { + return err + } + } else { + return err + } + } else { + if dirInfo.IsDir() { + return nil + } + return errors.New("directory is a file") + } + return nil +} diff --git a/mall/utils/qrcode/writeWeb.go b/mall/utils/qrcode/writeWeb.go new file mode 100644 index 0000000..57e1e92 --- /dev/null +++ b/mall/utils/qrcode/writeWeb.go @@ -0,0 +1,39 @@ +package qrcode + +import ( + "bytes" + "image/jpeg" + "image/png" + "net/http" + + "github.com/boombuler/barcode" + "github.com/boombuler/barcode/qr" +) + +func WritePng(w http.ResponseWriter, content string, edges ...int) error { + edgeLen := 300 + if len(edges) > 0 && edges[0] > 100 && edges[0] < 2000 { + edgeLen = edges[0] + } + img, _ := qr.Encode(content, qr.L, qr.Unicode) + img, _ = barcode.Scale(img, edgeLen, edgeLen) + buff := bytes.NewBuffer(nil) + png.Encode(buff, img) + w.Header().Set("Content-Type", "image/png") + _, err := w.Write(buff.Bytes()) + return err +} + +func WriteJpg(w http.ResponseWriter, content string, edges ...int) error { + edgeLen := 300 + if len(edges) > 0 && edges[0] > 100 && edges[0] < 2000 { + edgeLen = edges[0] + } + img, _ := qr.Encode(content, qr.L, qr.Unicode) + img, _ = barcode.Scale(img, edgeLen, edgeLen) + buff := bytes.NewBuffer(nil) + jpeg.Encode(buff, img, nil) + w.Header().Set("Content-Type", "image/jpg") + _, err := w.Write(buff.Bytes()) + return err +} diff --git a/mall/utils/rand.go b/mall/utils/rand.go new file mode 100644 index 0000000..a7495d3 --- /dev/null +++ b/mall/utils/rand.go @@ -0,0 +1,41 @@ +package utils + +import ( + crand "crypto/rand" + "fmt" + "github.com/syyongx/php2go" + "math" + "math/big" + "math/rand" + "time" +) + +func RandString(l int, c ...string) string { + var ( + chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + str string + num *big.Int + ) + if len(c) > 0 { + chars = c[0] + } + chrLen := int64(len(chars)) + for len(str) < l { + num, _ = crand.Int(crand.Reader, big.NewInt(chrLen)) + str += string(chars[num.Int64()]) + } + return str +} + +//x的y次方 +func RandPow(l int) int { + + min := int(math.Pow10(l - 1)) + max := int(math.Pow10(l) - 1) + return php2go.Rand(min, max) +} + +func RandNum() string { + seed := time.Now().UnixNano() + rand.Int63() + return fmt.Sprintf("%05v", rand.New(rand.NewSource(seed)).Int31n(1000000)) +} diff --git a/mall/utils/redis.go b/mall/utils/redis.go new file mode 100644 index 0000000..8e99309 --- /dev/null +++ b/mall/utils/redis.go @@ -0,0 +1,36 @@ +package utils + +import ( + "applet/app/cfg" + "applet/app/utils/cache" + "fmt" + "github.com/gin-gonic/gin" +) + +func ClearRedis(c *gin.Context) { + var str = []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"} + + key := fmt.Sprintf("%s:cfg_cache", c.GetString("mid")) + cache.Del(key) + key2 := fmt.Sprintf("%s:virtual_coin_cfg", c.GetString("mid")) + cache.Del(key2) + key3 := fmt.Sprintf("%s:virtual_coin_cfg_comm", c.GetString("mid")) + cache.Del(key3) + for _, v := range str { + key1 := fmt.Sprintf("%s:cfg_cache:%s", c.GetString("mid"), v) + cache.Del(key1) + } + if cfg.Prd == false { + key := fmt.Sprintf("%s:cfg_cache", "22255132") + cache.Del(key) + key2 := fmt.Sprintf("%s:virtual_coin_cfg", "22255132") + cache.Del(key2) + key3 := fmt.Sprintf("%s:virtual_coin_cfg_comm", "22255132") + cache.Del(key3) + for _, v := range str { + key1 := fmt.Sprintf("%s:cfg_cache:%s", "22255132", v) + cache.Del(key1) + } + } + +} diff --git a/mall/utils/rsa.go b/mall/utils/rsa.go new file mode 100644 index 0000000..fb8274a --- /dev/null +++ b/mall/utils/rsa.go @@ -0,0 +1,170 @@ +package utils + +import ( + "bytes" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "errors" + "fmt" + "io/ioutil" + "log" + "os" +) + +// 生成私钥文件 TODO 未指定路径 +func RsaKeyGen(bits int) error { + privateKey, err := rsa.GenerateKey(rand.Reader, bits) + if err != nil { + return err + } + derStream := x509.MarshalPKCS1PrivateKey(privateKey) + block := &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: derStream, + } + priFile, err := os.Create("private.pem") + if err != nil { + return err + } + err = pem.Encode(priFile, block) + priFile.Close() + if err != nil { + return err + } + // 生成公钥文件 + publicKey := &privateKey.PublicKey + derPkix, err := x509.MarshalPKIXPublicKey(publicKey) + if err != nil { + return err + } + block = &pem.Block{ + Type: "PUBLIC KEY", + Bytes: derPkix, + } + pubFile, err := os.Create("public.pem") + if err != nil { + return err + } + err = pem.Encode(pubFile, block) + pubFile.Close() + if err != nil { + return err + } + return nil +} + +// 生成私钥文件, 返回 privateKey , publicKey, error +func RsaKeyGenText(bits int) (string, string, error) { // bits 字节位 1024/2048 + privateKey, err := rsa.GenerateKey(rand.Reader, bits) + if err != nil { + return "", "", err + } + derStream := x509.MarshalPKCS1PrivateKey(privateKey) + block := &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: derStream, + } + priBuff := bytes.NewBuffer(nil) + err = pem.Encode(priBuff, block) + if err != nil { + return "", "", err + } + // 生成公钥文件 + publicKey := &privateKey.PublicKey + derPkix, err := x509.MarshalPKIXPublicKey(publicKey) + if err != nil { + return "", "", err + } + block = &pem.Block{ + Type: "PUBLIC KEY", + Bytes: derPkix, + } + pubBuff := bytes.NewBuffer(nil) + err = pem.Encode(pubBuff, block) + if err != nil { + return "", "", err + } + return priBuff.String(), pubBuff.String(), nil +} + +// 加密 +func RsaEncrypt(rawData, publicKey []byte) ([]byte, error) { + block, _ := pem.Decode(publicKey) + if block == nil { + return nil, errors.New("public key error") + } + pubInterface, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + return nil, err + } + pub := pubInterface.(*rsa.PublicKey) + return rsa.EncryptPKCS1v15(rand.Reader, pub, rawData) +} + +// 公钥加密 +func RsaEncrypts(data, keyBytes []byte) []byte { + //解密pem格式的公钥 + block, _ := pem.Decode(keyBytes) + if block == nil { + panic(errors.New("public key error")) + } + // 解析公钥 + pubInterface, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + panic(err) + } + // 类型断言 + pub := pubInterface.(*rsa.PublicKey) + //加密 + ciphertext, err := rsa.EncryptPKCS1v15(rand.Reader, pub, data) + if err != nil { + panic(err) + } + return ciphertext +} + +// 解密 +func RsaDecrypt(cipherText, privateKey []byte) ([]byte, error) { + block, _ := pem.Decode(privateKey) + if block == nil { + return nil, errors.New("private key error") + } + priv, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return nil, err + } + return rsa.DecryptPKCS1v15(rand.Reader, priv, cipherText) +} + +// 从证书获取公钥 +func OpensslPemGetPublic(pathOrString string) (interface{}, error) { + var certPem []byte + var err error + if IsFile(pathOrString) && Exists(pathOrString) { + certPem, err = ioutil.ReadFile(pathOrString) + if err != nil { + return nil, err + } + if string(certPem) == "" { + return nil, errors.New("empty pem file") + } + } else { + if pathOrString == "" { + return nil, errors.New("empty pem string") + } + certPem = StringToSlice(pathOrString) + } + block, rest := pem.Decode(certPem) + if block == nil || block.Type != "PUBLIC KEY" { + //log.Fatal("failed to decode PEM block containing public key") + return nil, errors.New("failed to decode PEM block containing public key") + } + pub, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + log.Fatal(err) + } + fmt.Printf("Got a %T, with remaining data: %q", pub, rest) + return pub, nil +} diff --git a/mall/utils/serialize.go b/mall/utils/serialize.go new file mode 100644 index 0000000..1ac4d80 --- /dev/null +++ b/mall/utils/serialize.go @@ -0,0 +1,23 @@ +package utils + +import ( + "encoding/json" +) + +func Serialize(data interface{}) []byte { + res, err := json.Marshal(data) + if err != nil { + return []byte{} + } + return res +} + +func Unserialize(b []byte, dst interface{}) { + if err := json.Unmarshal(b, dst); err != nil { + dst = nil + } +} + +func SerializeStr(data interface{}, arg ...interface{}) string { + return string(Serialize(data)) +} diff --git a/mall/utils/shuffle.go b/mall/utils/shuffle.go new file mode 100644 index 0000000..2c845a8 --- /dev/null +++ b/mall/utils/shuffle.go @@ -0,0 +1,48 @@ +package utils + +import ( + "math/rand" + "time" +) + +// 打乱随机字符串 +func ShuffleString(s *string) { + if len(*s) > 1 { + b := []byte(*s) + rand.Seed(time.Now().UnixNano()) + rand.Shuffle(len(b), func(x, y int) { + b[x], b[y] = b[y], b[x] + }) + *s = string(b) + } +} + +// 打乱随机slice +func ShuffleSliceBytes(b []byte) { + if len(b) > 1 { + rand.Seed(time.Now().UnixNano()) + rand.Shuffle(len(b), func(x, y int) { + b[x], b[y] = b[y], b[x] + }) + } +} + +// 打乱slice int +func ShuffleSliceInt(i []int) { + if len(i) > 1 { + rand.Seed(time.Now().UnixNano()) + rand.Shuffle(len(i), func(x, y int) { + i[x], i[y] = i[y], i[x] + }) + } +} + +// 打乱slice interface +func ShuffleSliceInterface(i []interface{}) { + if len(i) > 1 { + rand.Seed(time.Now().UnixNano()) + rand.Shuffle(len(i), func(x, y int) { + i[x], i[y] = i[y], i[x] + }) + } +} diff --git a/mall/utils/slice.go b/mall/utils/slice.go new file mode 100644 index 0000000..7b594e9 --- /dev/null +++ b/mall/utils/slice.go @@ -0,0 +1,70 @@ +package utils + +// ContainsString is 字符串是否包含在字符串切片里 +func ContainsString(array []string, val string) (index int) { + index = -1 + for i := 0; i < len(array); i++ { + if array[i] == val { + index = i + return + } + } + return +} + +//ArrayDiff 模拟PHP array_diff函数 +func ArrayDiff(array1 []interface{}, othersParams ...[]interface{}) ([]interface{}, error) { + if len(array1) == 0 { + return []interface{}{}, nil + } + if len(array1) > 0 && len(othersParams) == 0 { + return array1, nil + } + var tmp = make(map[interface{}]int, len(array1)) + for _, v := range array1 { + tmp[v] = 1 + } + for _, param := range othersParams { + for _, arg := range param { + if tmp[arg] != 0 { + tmp[arg]++ + } + } + } + var res = make([]interface{}, 0, len(tmp)) + for k, v := range tmp { + if v == 1 { + res = append(res, k) + } + } + return res, nil +} + +//ArrayIntersect 模拟PHP array_intersect函数 +func ArrayIntersect(array1 []interface{}, othersParams ...[]interface{}) ([]interface{}, error) { + if len(array1) == 0 { + return []interface{}{}, nil + } + if len(array1) > 0 && len(othersParams) == 0 { + return array1, nil + } + var tmp = make(map[interface{}]int, len(array1)) + for _, v := range array1 { + tmp[v] = 1 + } + for _, param := range othersParams { + for _, arg := range param { + if tmp[arg] != 0 { + tmp[arg]++ + } + } + } + var res = make([]interface{}, 0, len(tmp)) + for k, v := range tmp { + if v > 1 { + res = append(res, k) + } + } + return res, nil + +} diff --git a/mall/utils/slice_and_string.go b/mall/utils/slice_and_string.go new file mode 100644 index 0000000..3ae6946 --- /dev/null +++ b/mall/utils/slice_and_string.go @@ -0,0 +1,47 @@ +package utils + +import ( + "fmt" + "reflect" + "strings" + "unsafe" +) + +// string与slice互转,零copy省内存 + +// zero copy to change slice to string +func Slice2String(b []byte) (s string) { + pBytes := (*reflect.SliceHeader)(unsafe.Pointer(&b)) + pString := (*reflect.StringHeader)(unsafe.Pointer(&s)) + pString.Data = pBytes.Data + pString.Len = pBytes.Len + return +} + +// no copy to change string to slice +func StringToSlice(s string) (b []byte) { + pBytes := (*reflect.SliceHeader)(unsafe.Pointer(&b)) + pString := (*reflect.StringHeader)(unsafe.Pointer(&s)) + pBytes.Data = pString.Data + pBytes.Len = pString.Len + pBytes.Cap = pString.Len + return +} + +// 任意slice合并 +func SliceJoin(sep string, elems ...interface{}) string { + l := len(elems) + if l == 0 { + return "" + } + if l == 1 { + s := fmt.Sprint(elems[0]) + sLen := len(s) - 1 + if s[0] == '[' && s[sLen] == ']' { + return strings.Replace(s[1:sLen], " ", sep, -1) + } + return s + } + sep = strings.Replace(fmt.Sprint(elems), " ", sep, -1) + return sep[1 : len(sep)-1] +} diff --git a/mall/utils/string.go b/mall/utils/string.go new file mode 100644 index 0000000..f04d686 --- /dev/null +++ b/mall/utils/string.go @@ -0,0 +1,96 @@ +package utils + +import ( + "fmt" + "reflect" + "strings" +) + +func Implode(glue string, args ...interface{}) string { + data := make([]string, len(args)) + for i, s := range args { + data[i] = fmt.Sprint(s) + } + return strings.Join(data, glue) +} +func ImplodeByKey(glue string, args ...interface{}) string { + data := make([]string, len(args)) + for i, _ := range args { + data[i] = fmt.Sprint(i) + } + return strings.Join(data, glue) +} + +//字符串是否在数组里 +func InArr(target string, str_array []string) bool { + for _, element := range str_array { + if target == element { + return true + } + } + return false +} + +//把数组的值放到key里 +func ArrayColumn(array interface{}, key string) (result map[string]interface{}, err error) { + result = make(map[string]interface{}) + t := reflect.TypeOf(array) + v := reflect.ValueOf(array) + if t.Kind() != reflect.Slice { + return nil, nil + } + if v.Len() == 0 { + return nil, nil + } + for i := 0; i < v.Len(); i++ { + indexv := v.Index(i) + if indexv.Type().Kind() != reflect.Struct { + return nil, nil + } + mapKeyInterface := indexv.FieldByName(key) + if mapKeyInterface.Kind() == reflect.Invalid { + return nil, nil + } + mapKeyString, err := InterfaceToString(mapKeyInterface.Interface()) + if err != nil { + return nil, err + } + result[mapKeyString] = indexv.Interface() + } + return result, err +} + +//转string +func InterfaceToString(v interface{}) (result string, err error) { + switch reflect.TypeOf(v).Kind() { + case reflect.Int64, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32: + result = fmt.Sprintf("%v", v) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + result = fmt.Sprintf("%v", v) + case reflect.String: + result = v.(string) + default: + err = nil + } + return result, err +} + +func HideTrueName(name string) string { + res := "**" + if name != "" { + runs := []rune(name) + leng := len(runs) + if leng <= 3 { + res = string(runs[0:1]) + res + } else if leng < 5 { + res = string(runs[0:2]) + res + } else if leng < 10 { + res = string(runs[0:2]) + "***" + string(runs[leng-2:leng]) + } else if leng < 16 { + res = string(runs[0:3]) + "****" + string(runs[leng-3:leng]) + } else { + res = string(runs[0:4]) + "*****" + string(runs[leng-4:leng]) + } + } + return res +} diff --git a/mall/utils/time.go b/mall/utils/time.go new file mode 100644 index 0000000..8479905 --- /dev/null +++ b/mall/utils/time.go @@ -0,0 +1,181 @@ +package utils + +import ( + "errors" + "fmt" + "strconv" + "strings" + "time" +) + +func StrToTime(s string) (int64, error) { + // delete all not int characters + if s == "" { + return time.Now().Unix(), nil + } + r := make([]rune, 14) + l := 0 + // 过滤除数字以外的字符 + for _, v := range s { + if '0' <= v && v <= '9' { + r[l] = v + l++ + if l == 14 { + break + } + } + } + for l < 14 { + r[l] = '0' // 补0 + l++ + } + t, err := time.Parse("20060102150405", string(r)) + if err != nil { + return 0, err + } + return t.Unix(), nil +} +func Time2String(date time.Time, format string) string { + if format == "" { + format = "2006-01-02 15:04:05" + } + timeS := date.Format(format) + if timeS == "0001-01-01 00:00:00" || strings.Contains(timeS, "0001-01-01") { + return "" + } + return timeS +} +func String2Time(timeStr string) time.Time { + toTime, err := time.ParseInLocation("2006-01-02 15:04:05", timeStr, time.Local) + if err != nil { + return time.Now() + } + return toTime +} + +func TimeToStr(unixSecTime interface{}, layout ...string) string { + i := AnyToInt64(unixSecTime) + if i == 0 { + return "" + } + f := "2006-01-02 15:04:05" + if len(layout) > 0 { + f = layout[0] + } + return time.Unix(i, 0).Format(f) +} + +func FormatNanoUnix() string { + return strings.Replace(time.Now().Format("20060102150405.0000000"), ".", "", 1) +} + +func TimeParse(format, src string) (time.Time, error) { + return time.ParseInLocation(format, src, time.Local) +} + +func TimeParseStd(src string) time.Time { + t, _ := TimeParse("2006-01-02 15:04:05", src) + return t +} + +func TimeStdParseUnix(src string) int64 { + t, err := TimeParse("2006-01-02 15:04:05", src) + if err != nil { + return 0 + } + return t.Unix() +} + +// 获取一个当前时间 时间间隔 时间戳 +func GetTimeInterval(unit string, amount int) (startTime, endTime int64) { + t := time.Now() + nowTime := t.Unix() + tmpTime := int64(0) + switch unit { + case "years": + tmpTime = time.Date(t.Year()+amount, t.Month(), t.Day(), t.Hour(), 0, 0, 0, t.Location()).Unix() + case "months": + tmpTime = time.Date(t.Year(), t.Month()+time.Month(amount), t.Day(), t.Hour(), 0, 0, 0, t.Location()).Unix() + case "days": + tmpTime = time.Date(t.Year(), t.Month(), t.Day()+amount, t.Hour(), 0, 0, 0, t.Location()).Unix() + case "hours": + tmpTime = time.Date(t.Year(), t.Month(), t.Day(), t.Hour()+amount, 0, 0, 0, t.Location()).Unix() + } + if amount > 0 { + startTime = nowTime + endTime = tmpTime + } else { + startTime = tmpTime + endTime = nowTime + } + return +} + +// 时分秒字符串转时间戳,传入示例:8:40 or 8:40:10 +func HmsToUnix(str string) (int64, error) { + t := time.Now() + arr := strings.Split(str, ":") + if len(arr) < 2 { + return 0, errors.New("Time format error") + } + h, _ := strconv.Atoi(arr[0]) + m, _ := strconv.Atoi(arr[1]) + s := 0 + if len(arr) == 3 { + s, _ = strconv.Atoi(arr[3]) + } + formatted1 := fmt.Sprintf("%d%02d%02d%02d%02d%02d", t.Year(), t.Month(), t.Day(), h, m, s) + res, err := time.ParseInLocation("20060102150405", formatted1, time.Local) + if err != nil { + return 0, err + } else { + return res.Unix(), nil + } +} + +// 获取特定时间范围 +func GetTimeRange(s string) map[string]int64 { + t := time.Now() + var stime, etime time.Time + + switch s { + case "today": + stime = time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location()) + // 明天 0点 + etime = time.Date(t.Year(), t.Month(), t.Day()+1, 0, 0, 0, 0, t.Location()) + case "this_month": + stime = time.Date(t.Year(), t.Month(), 0, 0, 0, 0, 0, t.Location()) + // 这个月的最后一天 + etime = time.Date(t.Year(), t.Month()+1, 0, 0, 0, 0, 0, t.Location()) + case "last_month": + stime = time.Date(t.Year(), t.Month()-1, 0, 0, 0, 0, 0, t.Location()) + // 上月的最后一天 + etime = time.Date(t.Year(), t.Month(), 0, 0, 0, 0, 0, t.Location()) + } + + return map[string]int64{ + "start": stime.Unix(), + "end": etime.Unix(), + } +} + +// GetFirstDateOfMonth 获取传入的时间所在月份的第一天,即某月第一天的0点。如传入time.Now(), 返回当前月份的第一天0点时间。 +func GetFirstDateOfMonth(d time.Time) time.Time { + d = d.AddDate(0, 0, -d.Day()+1) + return GetZeroTime(d) +} + +// GetLastDateOfMonth 获取传入的时间所在月份的最后一天,即某月最后一天的0点。如传入time.Now(), 返回当前月份的最后一天0点时间。 +func GetLastDateOfMonth(d time.Time) time.Time { + return GetFirstDateOfMonth(d).AddDate(0, 1, -1) +} + +// GetZeroTime 获取某一天的0点时间 +func GetZeroTime(d time.Time) time.Time { + return time.Date(d.Year(), d.Month(), d.Day(), 0, 0, 0, 0, d.Location()) +} + +// GetZeroTime 获取某一天的0点时间的时间戳 +func GetZeroTimeUnix(d time.Time) int64 { + return time.Date(d.Year(), d.Month(), d.Day(), 0, 0, 0, 0, d.Location()).Unix() +} diff --git a/mall/utils/uuid.go b/mall/utils/uuid.go new file mode 100644 index 0000000..346b7b2 --- /dev/null +++ b/mall/utils/uuid.go @@ -0,0 +1,61 @@ +package utils + +import ( + "fmt" + "math/rand" + "time" +) + +const ( + KC_RAND_KIND_NUM = 0 // 纯数字 + KC_RAND_KIND_LOWER = 1 // 小写字母 + KC_RAND_KIND_UPPER = 2 // 大写字母 + KC_RAND_KIND_ALL = 3 // 数字、大小写字母 +) + +func newUUID() *[16]byte { + u := &[16]byte{} + rand.Read(u[:16]) + u[8] = (u[8] | 0x80) & 0xBf + u[6] = (u[6] | 0x40) & 0x4f + return u +} + +func UUIDString() string { + u := newUUID() + return fmt.Sprintf("%x-%x-%x-%x-%x", u[:4], u[4:6], u[6:8], u[8:10], u[10:]) +} + +func UUIDHexString() string { + u := newUUID() + return fmt.Sprintf("%x%x%x%x%x", u[:4], u[4:6], u[6:8], u[8:10], u[10:]) +} +func UUIDBinString() string { + u := newUUID() + return fmt.Sprintf("%s", [16]byte(*u)) +} + +func Krand(size int, kind int) []byte { + ikind, kinds, result := kind, [][]int{[]int{10, 48}, []int{26, 97}, []int{26, 65}}, make([]byte, size) + isAll := kind > 2 || kind < 0 + rand.Seed(time.Now().UnixNano()) + for i := 0; i < size; i++ { + if isAll { // random ikind + ikind = rand.Intn(3) + } + scope, base := kinds[ikind][0], kinds[ikind][1] + result[i] = uint8(base + rand.Intn(scope)) + } + return result +} + +// OrderUUID is only num for uuid +func OrderUUID(uid int) string { + ustr := IntToStr(uid) + tstr := Int64ToStr(time.Now().Unix()) + ulen := len(ustr) + tlen := len(tstr) + rlen := 18 - ulen - tlen + krb := Krand(rlen, KC_RAND_KIND_NUM) + return ustr + tstr + string(krb) +} diff --git a/mall/utils/validator_err_trans.go b/mall/utils/validator_err_trans.go new file mode 100644 index 0000000..29d97bf --- /dev/null +++ b/mall/utils/validator_err_trans.go @@ -0,0 +1,55 @@ +package utils + +import ( + "fmt" + "github.com/gin-gonic/gin/binding" + "github.com/go-playground/locales/en" + "github.com/go-playground/locales/zh" + ut "github.com/go-playground/universal-translator" + "github.com/go-playground/validator/v10" + enTranslations "github.com/go-playground/validator/v10/translations/en" + chTranslations "github.com/go-playground/validator/v10/translations/zh" + "reflect" +) + +var ValidatorTrans ut.Translator + +// ValidatorTransInit 验证器错误信息翻译初始化 +// local 通常取决于 http 请求头的 'Accept-Language' +func ValidatorTransInit(local string) (err error) { + if v, ok := binding.Validator.Engine().(*validator.Validate); ok { + zhT := zh.New() //chinese + enT := en.New() //english + uni := ut.New(enT, zhT, enT) + + var o bool + ValidatorTrans, o = uni.GetTranslator(local) + if !o { + return fmt.Errorf("uni.GetTranslator(%s) failed", local) + } + // 注册一个方法,从自定义标签label中获取值(用在把字段名映射为中文) + v.RegisterTagNameFunc(func(field reflect.StructField) string { + label := field.Tag.Get("label") + if label == "" { + return field.Name + } + return label + }) + // 注册翻译器 + switch local { + case "en": + err = enTranslations.RegisterDefaultTranslations(v, ValidatorTrans) + case "zh": + err = chTranslations.RegisterDefaultTranslations(v, ValidatorTrans) + default: + err = enTranslations.RegisterDefaultTranslations(v, ValidatorTrans) + } + return + } + return +} + +// ValidatorTransInitZh 验证器错误信息翻译为中文初始化 +func ValidatorTransInitZh() (err error) { + return ValidatorTransInit("zh") +}