Saas平台接入商户代小程序开发解决方案

这篇具有很好参考价值的文章主要介绍了Saas平台接入商户代小程序开发解决方案。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

简介

需求背景:
基于Saas平台为商户提供小程序接入代理进行代开发(包含支付场景)、管理、发布等一系列实际业务场景的解决方案

以下摘要至微信官方文档

平台概述
微信开放平台 - 第三方平台(简称第三方平台),由微信团队面向所有通过开发者资质认证的第三方开发者提供提供的官方平台。
在得到公众号或小程序管理员授权后,基于该平台,第三方服务商可以通过调用官方接口能力,为商家提供公众号代运营、小程序代注册、代开发等服务以及提供公众号和小程序相关的行业方案、活动营销、插件能力等全方位服务。

资料参考

微信官方文档
WeChat SDK for Go: https://github.com/silenceper/wechat
SDK文档

配置

微信配置

微信配置文档

实战整理

项目情况

框架:kratos(微服务)
SDK:https://github.com/silenceper/wechat

应用场景

小程序开发使用第三方平台服务商方式代商户做小程序开发与管理

服务依赖

初始化开放平台openPlatform 服务


// newRedisClient 实例化Redis
func newRedisClient() *redis.Client {
	client := redis.NewClient(&redis.Options{
		Addr:     "conf.Redis.Addr",
		Username: "conf.Redis.Username",
		Password: "conf.Redis.Password",
		DB:       0,
	})
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
	defer cancel()
	err := client.Ping(ctx).Err()
	if err != nil {
		log.Fatalf("redis connect error: %v", err)
	}
	return client
}

func newRedisClient() *openplatform.OpenPlatform {
	wc := wechat.NewWechat()
	redisCache := cache.NewRedis(context.Background(), &cache.RedisOpts{})
	// SDK 的redis缓存方案连接配置不支持设置用户名,使用 SetConn 实现自定义连接设置
	redisClient := newRedisClient()
	redisCache.SetConn(redisClient)
	openPlatformConfig := &opConfig.Config{
		AppID:          "conf.Wechat.AppId",
		AppSecret:      "conf.Wechat.AppSecret",
		Token:          "conf.Wechat.Token",
		EncodingAESKey: "conf.Wechat.EncodingAesKey",
		Cache:          redisCache,
	}
	openPlatform := wc.GetOpenPlatform(openPlatformConfig)
	return openPlatform
}

用户故事一:小程序授权

商户作为Saas用户登录平台后通过扫描授权绑定商户小程序,后续开发流程由平台方支持

数据结构
CREATE TABLE `tenant_application` (
  `tenant_id` char(21) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '商户ID',
  `app_type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '应用类型(WECHAT_MINIPROGRAM:微信小程序)',
  `app_id` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '应用ID',
  `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '应用名称',
  `headimg_url` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '应用头像链接',
  `auth_refresh_token` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '应用刷新令牌',
  `is_authed` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否授权',
  `auth_at` datetime NOT NULL COMMENT '最近授权时间',
  `unauth_at` datetime NOT NULL COMMENT '最近取消授权时间',
  `meta_info` json DEFAULT NULL COMMENT '元数据(保留数据)',
  `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updated_at` datetime NOT NULL DEFAULT '0000-00-00 00:00:00' ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`tenant_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='商户应用'
第三方平台配置

Saas平台接入商户代小程序开发解决方案
微信开放平台登录入口

  1. 配置授权事件接收接口
  2. 处理ticket获取并缓存平台token

demo:

package main

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"os"
	"time"

	"github.com/go-redis/redis/v8"
	"github.com/silenceper/wechat/v2"
	"github.com/silenceper/wechat/v2/cache"
	"github.com/silenceper/wechat/v2/officialaccount/message"
	"github.com/silenceper/wechat/v2/openplatform"
	opConfig "github.com/silenceper/wechat/v2/openplatform/config"
)

// logx 微信回调接口调试日志
func logx(format string, v ...any) {
	file, err := os.OpenFile("/tmp/wechat.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, os.ModePerm)
	if err != nil {
		return
	}
	defer file.Close()
	log.SetOutput(file)
	log.SetFlags(log.Llongfile)
	log.Printf(format+"\n", v)
}

// handleWechatAuthorMessage 处理微信授权消息
func handleWechatAuthorMessage(openPlatform *openplatform.OpenPlatform, msg *message.MixMessage) error {
	switch msg.InfoType {
	case message.InfoTypeVerifyTicket:
		// TODO: 判断 ComponentAccessToken 未过期时直接返回
		// TODO: 判断 ComponentVerifyTicket 缓存存在时直接调用 SetComponentAccessToken
		// TODO: 缓存 ComponentVerifyTicket
		logx("ComponentVerifyTicket: %s", msg.ComponentVerifyTicket)
		// 接收授权信息:通过 ComponentVerifyTicket 获取并缓存 ComponentAccessToken
		openPlatform.SetComponentAccessToken(msg.ComponentVerifyTicket)
		token, err := openPlatform.GetComponentAccessToken()
		if err != nil {
			logx("GetComponentAccessToken err: %v", err)
		} else {
			logx("GetComponentAccessToken token: %s;", token)
		}
	case message.InfoTypeAuthorized:
		logx("AuthCode: %s", msg.AuthCode)
		// 授权通知 获取权限令牌,并保存刷新令牌等信息
		authBaseInfo, err := openPlatform.QueryAuthCode(msg.AuthCode)
		if err != nil {
			logx("QueryAuthCode err: %v", err)
		} else {
			logx("QueryAuthCode authBaseInfo: %v;", authBaseInfo)
		}
		// TODO:收集授权待保存数据
		// TODO:缓存权限令牌
		// 获取授权账号详情
		//authorizerInfo, authorizationInfo, err := u.OpenPlatform.GetAuthrInfo(authBaseInfo.Appid)
		// TODO:收集账号详情待保存数据
		// TODO:保存应用信息
	case message.InfoTypeUpdateAuthorized:
		// 更新授权
		// TODO:更新应用信息
	case message.InfoTypeUnauthorized:
		// 取消授权
		// TODO:更新应用状态为未授权
	}
	return nil
}

func main() {
	// 授权事件回调接口
	http.HandleFunc("/callback/auth", func(writer http.ResponseWriter, request *http.Request) {
		openPlatform := getOpenPlatform()
		// 传入request和responseWriter
		server := openPlatform.GetServer(request, writer)
		//设置接收消息的处理方法
		server.SetMessageHandler(func(msg *message.MixMessage) *message.Reply {
			err := handleWechatAuthorMessage(openPlatform., msg)
			if err != nil {
				log.Fatalf("handleWechatAuthorMessage failed err: %s", err)
			}
			return nil
		})
		// 调试阶段:关闭校验
		server.SkipValidate(true)
		//处理消息接收以及回复
		err := server.Serve()
		if err != nil {
			log.Fatalf("server.Serve() err: %s", err)
			return
		}
		server.String("success")
		return
	})
	//建立监听
	err := http.ListenAndServe("127.0.0.1:8080", nil)
	if err != nil {
		fmt.Println("网络错误")
		return
	}
}
商户授权小程序
  1. 使用平台token获取授权页面链接 文档说明
  2. 商户小程序管理员扫描授权页面二维码进行授权
  3. 回调后台页面提交授权数据服务端继续获取授权信息进行授权绑定
    1. 获取刷新令牌与APPID等授权数据
    2. 禁止小程序直接重复授权绑定(否则导致旧账号的小程序刷新令牌过期会导致一系列问题)
    3. 缓存商户小程序权限令牌(定时过期,可用刷新令牌重新获取)
    4. 持久化保存商户小程序刷新令牌(重新授权前长期有效)

demo:

package main

import (
	"encoding/json"
	"fmt"
	"github.com/silenceper/wechat/v2/util"
	"net/url"
)

// getAuthorizePageUrl 获取小程序授权页面链接
// 重新封装:
// SDK没有做异常捕获,配置出现问题时没有错误提示,比如component_access_token过期。导致的结果是预授权码为空字符串
func getAuthorizePageUrl(returnUrl string, authType int) (string, error) {
	openPlatform := getOpenPlatform()
	cat, err := openPlatform.GetComponentAccessToken()
	if err != nil {
		return "", err
	}
	req := map[string]string{
		"component_appid": openPlatform.AppID,
	}
	uri := fmt.Sprintf(getPreCodeURL, cat)
	body, err := util.PostJSON(uri, req)
	if err != nil {
		return "", err
	}
	var ret struct {
		PreCode string `json:"pre_auth_code"`
	}
	if err := json.Unmarshal(body, &ret); err != nil {
		return "", err
	}
	if err != nil {
		return "", err
	}
	code := ret.PreCode
	// 预授权码获取失败
	if code == "" {
		fmt.Printf("预授权码获取失败 body: %s\n", body)
		return "", fmt.Errorf("预授权码获取失败")
	}
	return fmt.Sprintf(componentLoginURL, openPlatform.AppID, code, url.QueryEscape(returnUrl), authType, ""), nil
}

// 其余操作由SDK提供
商户更换小程序
  1. 与商户授权小程序一致流程直接进行覆盖授权绑定
切换商户使用同一小程序
  1. 【优先级高】(不做限制容易引发连带bug,如出现过期的刷新令牌)授权已授权的小程序时提示小程序已绑定其他账号
  2. 【优先级低】提供平台单方面解除小程序授权功能(直接清除授权数据)

用户故事二:代小程序开发(暂以命令行工具形式开发)

接口详情见文首微信官方文档

数据结构(是否做版本管理)
草稿箱管理

【优先级低】(可在微信开放平台后台操作)使用平台token提交草稿箱代码至模板库

代码管理
  1. 使用小程序token指定代码模板上传代码
  2. 使用小程序token提交代码审核
  3. 【优先级低】(可主动查询审核状态)接收处理审核通知回调数据
  4. 使用小程序版本查看审核状态
  5. 发布小程序
更新代码模板

解决方案:(待分析)

  • 手动触发小程序批量上传(更新)代码并/提交审核/发布

  • 定期(如每天)自动检查代码模板更新时自动触发批量上传(更新)代码并/提交审核/发布

用户故事三:代小程序登录

获取小程序端提交参数
  1. app_id 需要在为小程序上传代码时设置ext_json(里指定extappid),小程序从ext_json获取自身appid并在登录时提交
  2. js_code
  3. phone_code
获取小程序信息

使用app_id通过tenant服务grpc获取刷新令牌与tenant_id

获取开放账号信息

通过js_code获取session_key,open_id,union_id

获取用户手机号
  1. 获取小程序实例
  2. 设置小程序刷新令牌
  3. 获取访问令牌(令牌失效时通过刷新令牌更新)
  4. 通过令牌与phone_code获取手机号
注册用户
  1. 获取fan粉丝数据
  2. 获取用户数据
获取登录态

使用fan_id和session_key生成jwt_token

前端登录鉴权
  1. 创建前端authzx
  2. server包中的http添加一个前端鉴权中间件(与后端鉴权区分开)
  3. 鉴权中间件白名单区分前后端路由

用户故事四:代小程序支付(用于账单支付)

方案选定:
直连模式

产品介绍-小程序支付 | 微信支付商户平台文档中心

信息、资金流:微信支付—>直连商户

服务商模式(初定)

产品介绍-小程序支付 | 微信支付服务商平台文档中心

Saas平台接入商户代小程序开发解决方案—— 信息流 —— 资金流

目前服务商的社交载体只能是公众号,服务商可通过公众平台完成公众号注册申请。

前期准备

  1. 平台商户号申请成为服务商商户号
  2. 注册微信公众号绑定商户ID

可解决的问题:

  1. 后端支付相关接口无需子商户每一个的key与证书(只需要服务商平台商户的相关配置,直连模式需要每一个子商户的相关配置)
  2. 子商户只需要创建账号,服务商平台商户添加子商户,商户再做审核通过即可(无需做开发配置)

需手动操作步骤:

  1. 商家注册子商户号
  2. 商家小程序绑定商家商户号
  3. 服务商新增子商户号
支付功能服务划分与数据结构
服务划分方案选定:
  • 以包形式封装通过import调用支付能力在各自服务处理
    • 简单维护
  • 独立支付服务(或者直接使用财务服务)通过grpc通知相关服务处理(初定)
    • 统一支付相关接口
    • 与业务解耦
数据结构设计

子商户

tenant_application表添加商户信息

`mch_id` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '商户ID',

支付订单

CREATE TABLE `pay_order` (
  `id` char(21) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '唯一ID',
  `pay_type` enum("WECHAT") CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '支付方式(WECHAT:微信)',
  `pay_mode` enum("WECHAT_PARTNER","WECHAT") CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '支付对接模式(WECHAT_PARTNER:微信服务商模式;WECHAT:微信直通模式)',
  `trade_type` enum("JSAPI","NATIVE","APP","MICROPAY","MWEB","FACEPAY") CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '交易类型(JSAPI:公众号支付;NATIVE:扫码支付;APP:APP支付;MICROPAY:付款码支付;MWEB:H5支付;FACEPAY:刷脸支付)',
  `tenant_id` char(21) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '租户ID',
  `app_id` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '租户应用ID(小程序APPID)',
  `mch_id` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '租户商户ID',
  `out_trade_no` char(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '交易订单号',
  `open_id` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '支付者open_id',
  `subject_type` enum("BILL") CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '交易主体类型(BILL:租房月账单)',
  `subject_id` char(21) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '交易主体ID(BILL:填收款单ID?)',
  `amount` decimal(18,2) NOT NULL COMMENT '金额',
  `state` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '订单交易状态(SUCCESS:支付成功;REFUND:转入退款;NOTPAY:未支付;CLOSED:已关闭;REVOKED:已撤销【仅付款码支付会返回】;USERPAYING:用户支付中【仅付款码支付会返回】;PAYERROR:支付失败【仅付款码支付会返回】)',
  `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '交易主体描述',
  `meta_info` json DEFAULT NULL COMMENT '元数据(保留数据)',
  `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updated_at` datetime NOT NULL DEFAULT '0000-00-00 00:00:00' ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE KEY `out_trade_no` (`mch_id`,`out_trade_no`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='支付订单'
接入微信SDK

GO_官方SDK

支付客户端服务注入

预下单
  1. 实例化服务商
  2. 获取商户appID和mchID
  3. 调用接口获取prepay_id(前端小程序下单使用)

demo

package main
import (
    "context"
    "log"
    "time"
    "github.com/wechatpay-apiv3/wechatpay-go/core"
    "github.com/wechatpay-apiv3/wechatpay-go/core/option"
    partnerJsapi "github.com/wechatpay-apiv3/wechatpay-go/services/partnerpayments/jsapi"
    "github.com/wechatpay-apiv3/wechatpay-go/services/payments/jsapi"
    "github.com/wechatpay-apiv3/wechatpay-go/utils"
)
func getClient(ctx context.Context) (client *core.Client, err error) {
    var (
        mchID                      string = "190000****"                               // 商户号
        mchCertificateSerialNumber string = "3775************************************" // 商户证书序列号
        mchAPIv3Key                string = "2ab9****************************"         // 商户APIv3密钥
    )
    // 使用 utils 提供的函数从本地文件中加载商户私钥,商户私钥会用来生成请求的签名
    mchPrivateKey, err := utils.LoadPrivateKeyWithPath("./apiclient_key.pem")
    if err != nil {
        log.Print("load merchant private key error")
        return
    }
    // 使用商户私钥等初始化 client,并使它具有自动定时获取微信支付平台证书的能力
    opts := []core.ClientOption{
        option.WithWechatPayAutoAuthCipher(mchID, mchCertificateSerialNumber, mchPrivateKey, mchAPIv3Key),
    }
    client, err = core.NewClient(ctx, opts...)
    if err != nil {
        log.Printf("new wechat pay client err:%s", err)
    }
    return
}
func partnerPrepay(ctx context.Context) {
    client, err := getClient(ctx)
    if err != nil {
        log.Print(err.Error())
        return
    }
    svc := partnerJsapi.JsapiApiService{Client: client}
    resp, result, err := svc.Prepay(ctx,
        partnerJsapi.PrepayRequest{
            SpAppid:       core.String("wxd678efh567hg6787"),
            SpMchid:       core.String("1230000109"),
            SubAppid:      core.String("wxd678efh567hg6787"),
            SubMchid:      core.String("1230000109"),
            Description:   core.String("Image形象店-深圳腾大-QQ公仔"),
            OutTradeNo:    core.String("1217752501201407033233368018"),
            TimeExpire:    core.Time(time.Now()),
            Attach:        core.String("自定义数据说明"),
            NotifyUrl:     core.String("https://www.weixin.qq.com/wxpay/pay.php"),
            GoodsTag:      core.String("WXG"),
            LimitPay:      []string{"LimitPay_example"},
            SupportFapiao: core.Bool(false),
            Amount: &partnerJsapi.Amount{
                Currency: core.String("CNY"),
                Total:    core.Int64(100),
            },
            Payer: &partnerJsapi.Payer{
                SpOpenid:  core.String("oUpF8uMuAJO_M2pxb1Q9zNjWeS6o"),
                SubOpenid: core.String("oUpF8uMuAJO_M2pxb1Q9zNjWeS6o"),
            },
            Detail: &partnerJsapi.Detail{
                CostPrice: core.Int64(608800),
                GoodsDetail: []partnerJsapi.GoodsDetail{partnerJsapi.GoodsDetail{
                    GoodsName:        core.String("iPhoneX 256G"),
                    MerchantGoodsId:  core.String("ABC"),
                    Quantity:         core.Int64(1),
                    UnitPrice:        core.Int64(828800),
                    WechatpayGoodsId: core.String("1001"),
                }},
                InvoiceId: core.String("wx123"),
            },
            SceneInfo: &partnerJsapi.SceneInfo{
                DeviceId:      core.String("013467007045764"),
                PayerClientIp: core.String("14.23.150.211"),
                StoreInfo: &partnerJsapi.StoreInfo{
                    Address:  core.String("广东省深圳市南山区科技中一道10000号"),
                    AreaCode: core.String("440305"),
                    Id:       core.String("0001"),
                    Name:     core.String("腾讯大厦分店"),
                },
            },
            SettleInfo: &partnerJsapi.SettleInfo{
                ProfitSharing: core.Bool(false),
            },
        },
    )
    if err != nil {
        // 处理错误
        log.Printf("call Prepay err:%s", err)
    } else {
        // 处理返回结果
        log.Printf("status=%d resp=%s", result.Response.StatusCode, resp)
    }
}
func prepay(ctx context.Context) {
    client, err := getClient(ctx)
    if err != nil {
        log.Print(err.Error())
        return
    }
    svc := jsapi.JsapiApiService{Client: client}
    // 得到prepay_id,以及调起支付所需的参数和签名
    resp, result, err := svc.PrepayWithRequestPayment(ctx,
        jsapi.PrepayRequest{
            Appid:       core.String("wxd678efh567hg6787"),
            Mchid:       core.String("1900009191"),
            Description: core.String("Image形象店-深圳腾大-QQ公仔"),
            OutTradeNo:  core.String("1217752501201407033233368018"),
            Attach:      core.String("自定义数据说明"),
            NotifyUrl:   core.String("https://www.weixin.qq.com/wxpay/pay.php"),
            Amount: &jsapi.Amount{
                Total: core.Int64(100),
            },
            Payer: &jsapi.Payer{
                Openid: core.String("oUpF8uMuAJO_M2pxb1Q9zNjWeS6o"),
            },
        },
    )
    if err == nil {
        log.Println(resp)
        log.Println(result)
    } else {
        log.Println(err)
    }
}
func main() {
    ctx := context.Background()
    partnerPrepay(ctx)
    prepay(ctx)
}
支付结果异步通知处理
  1. 参数解密
  2. 验签
  3. 数据处理
支付结果主动查询
  1. 通过订单标识查询接口
  2. 数据处理
支付结果业务逻辑处理
  1. 事务(幂等)
  2. 根据支付结构处理订单状态
【优先级低】【待预研】特约商户进件

官方需求背景
● 人工录入大量商户资料,耗时耗力。
● 商户对标准费率不满意,无法说服商户先签约再帮其调整费率。

用户故事五:代小程序消息推送(用于账单推送)

待预研文章来源地址https://www.toymoban.com/news/detail-484572.html

用户故事六:【优先级低】代小程序注册

待预研

到了这里,关于Saas平台接入商户代小程序开发解决方案的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • likeshop单商户SaaS商城系统—无限多开,搭建多个商城

    likeshop单商户SaaS商城系统:适用于多开(SaaS)、B2C、单商户、自营商城场景,完美契合私域流量变现闭环交易使用,系统拥有丰富的营销玩法,强大的分销能力,支持DIY多模板,前后端分离。 基于ThinkPHP6.0,Vue,uni-app,PHP8.0,MySQL5.7,element-ui等主流通用技术,真正做到好懂

    2024年02月09日
    浏览(55)
  • 基于ThinkPHP6.0+Vue+MySQL的单商户SaaS商城系统

    单商户SaaS商城系统是一种面向单个商户的软件即服务(SaaS)解决方案,用于构建和管理商户的网上商城。它提供一个完整的商城平台,可以帮助商户构建、管理和优化在线商城,并通过强大的数据分析功能,使商户可以更好地了解客户行为,从而提高销售效率。单商户SaaS商

    2024年02月13日
    浏览(34)
  • 一站式解决方案:Qt 跨平台开发灵活可靠

    一站式解决方案:Qt 跨平台开发灵活可靠 Qt 是一种跨平台开发工具,为开发者提供了一站式解决方案。无论您的项目目标是 Windows、Linux、macOS、嵌入式系统还是移动平台,Qt 都能胜任。这种跨平台的特性不仅节省开支,还推动了战略的快速落地。 适用范围广泛:Qt 可在多种

    2024年02月07日
    浏览(63)
  • {return_msg=输入源“/body/xml/mch_id”映射到值字段“商户号”字符串规则校验失 解决方案

    测试环境OK 我检查了下代码,造数据本地跑了下,貌似也没啥问题   进了生产直接报错,报错信息如下:  看了下特意看了下生产库的配置,,一个一个的查,发现都配置了,没有问题,但是调用微信下单接口的时候报错了 然后看了下调用的接口那边收银台返回的日志如下

    2024年02月11日
    浏览(39)
  • 低代码开发中的Nacos配置:跨平台跳转的解决方案

    在当今低代码开发的时代,平台的易用性和灵活性非常重要。右上角平台跳转作为用户界面中常见的交互元素,对于提高用户体验具有举足轻重的地位。然而,有时候我们会遇到跳转失效的情况,这无疑给用户带来了困扰。本文以JVS低代码平台为例,探讨如何通过后台配置来

    2024年01月25日
    浏览(41)
  • Flutter:引领移动开发新潮流,跨平台应用程序的终极解决方案

    Flutter是由Google开发的一款开源移动应用开发框架,它可以帮助开发者快速高效地构建跨平台的移动应用程序。Flutter基于Dart编程语言,具有简单易学、高效灵活的特点,并且可以与现有的Android和iOS应用进行集成。本文将介绍Flutter的基本概念、开发环境搭建、基础组件、生命

    2024年02月04日
    浏览(86)
  • 多租户的低代码平台,Saas开发平台:MateCloud

    简介 MateCloud是一款基于Spring Cloud Alibaba的微服务架构。目前已经整合Spring Boot 2.7.0、 Spring Cloud 2021、Spring Cloud Alibaba 2021、Spring Security Oauth2、Feign、Dubbo、JetCache、RocketMQ等,支持多租户的低代码平台,Saas平台开发套件 技术架构 功能特点 主体框架 :采用最新的Spring Cloud 2021.0

    2024年02月15日
    浏览(46)
  • 智慧园区SaaS管理系统解决方案:赋能园区实现信息化、数字化管理

    智慧园区的概念来源于智慧城市,是指应用云计算、互联网等新一代信息通信技术,改变园区中各机构的交互方式,提高交互效率的新型科技园区。近年来,我国智慧园区发展迅速,随着大数据、人工智能等技术的发展不断取得新突破,以科技赋能产业园区数字化升级是必然

    2024年02月02日
    浏览(63)
  • 基于5G+物联网+SaaS+AI的农业大数据综合解决方案:PPT全文44页,附下载

    : 智慧农业大数据,5G智慧农业,物联网智慧农业,SaaS智慧农业,AI智慧农业,智慧农业大数据平台 1、应对全球人口快速增长带来的粮食生产压力,未来的粮食生产力必须提高60%。面对可种植耕地的不断缩小,以及越来越严峻的天气,传统农业面临着巨大挑战。 2、信

    2024年02月04日
    浏览(44)
  • 关于微信小程序列表懒加载的解决方案

      我们在渲染一个展示数组,不能一次性的把所有的元素都展示出来,这样可能会导致用户性能体验下降,因此我们需要把所有的元素先保存在一个容器里,等用户需要(上滑)的时候再拿出来显示,为了更加高效的开发,我封装了方法,为了让封装方法得到的数据能够持久

    2024年02月12日
    浏览(41)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包