diff --git a/doc/api/miniprogram.md b/doc/api/miniprogram.md
index 5f3c1f3..75d2a24 100644
--- a/doc/api/miniprogram.md
+++ b/doc/api/miniprogram.md
@@ -1,3 +1,50 @@
# 小程序
-TODO
\ No newline at end of file
+## 基础接口
+
+TODO
+
+## 内容安全
+
+[官方文档](https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/sec-check/security.mediaCheckAsync.html)
+
+| 名称 | 请求方式 | URL | 是否已实现 | 使用方法 |
+| :-------------------------------: | -------- | :------------------------------- | ---------- | -------------------------------------- |
+| 异步校验图片/音频 v1.0 | POST | /wxa/media_check_async | YES | (security *Security) MediaCheckAsyncV1 |
+| 同步校验一张图片 v1.0 | POST | /wxa/img_sec_check | YES | (security *Security) ImageCheckV1 |
+| 异步校验图片/音频 | POST | /wxa/media_check_async?version=2 | YES | (security *Security) MediaCheckAsync |
+| 同步检查一段文本 v1.0 | POST | /wxa/msg_sec_check | YES | (security *Security) MsgCheckV1 |
+| 同步检查一段文本 | POST | /wxa/msg_sec_check?version=2 | YES | (security *Security) MsgCheck |
+
+
+## OCR
+
+[官方文档](https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/ocr/ocr.bankcard.html)
+
+| 名称 | 请求方式 | URL | 是否已实现 | 使用方法 |
+| :------------: | -------- | :--------------------- | ---------- | -------- |
+| 银行卡识别 | POST | /cv/ocr/bankcard | | |
+| 营业执照识别 | POST | /cv/ocr/bizlicense | | |
+| 驾驶证识别 | POST | /cv/ocr/drivinglicense | | |
+| 身份证识别 | POST | /cv/ocr/idcard | | |
+| 通用印刷体识别 | POST | /cv/ocr/comm | | |
+| 行驶证识别 | POST | /cv/ocr/driving | | |
+
+
+## 手机号
+
+[官方文档](https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/phonenumber/phonenumber.getPhoneNumber.html)
+
+| 名称 | 请求方式 | URL | 是否已实现 | 使用方法 |
+| :----------------: | -------- | :------------------------------- | ---------- | ----------------------------------- |
+| code换取用户手机号 | POST | /wxa/business/getuserphonenumber | YES | (business *Business) GetPhoneNumber |
+
+
+## 安全风控
+
+[官方文档](https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/safety-control-capability/riskControl.getUserRiskRank.html)
+
+| 名称 | 请求方式 | URL | 是否已实现 | 使用方法 |
+| :----------------: | -------- | :------------------- | ---------- | ------------------------------------------ |
+| 获取用户的安全等级 | POST | /wxa/getuserriskrank | YES | (riskControl *RiskControl) GetUserRiskRank |
+
diff --git a/miniprogram/business/business.go b/miniprogram/business/business.go
new file mode 100644
index 0000000..1b0d826
--- /dev/null
+++ b/miniprogram/business/business.go
@@ -0,0 +1,13 @@
+package business
+
+import "github.com/silenceper/wechat/v2/miniprogram/context"
+
+// Business 业务
+type Business struct {
+ *context.Context
+}
+
+// NewBusiness init
+func NewBusiness(ctx *context.Context) *Business {
+ return &Business{ctx}
+}
diff --git a/miniprogram/business/phone_number.go b/miniprogram/business/phone_number.go
new file mode 100644
index 0000000..bf99057
--- /dev/null
+++ b/miniprogram/business/phone_number.go
@@ -0,0 +1,54 @@
+package business
+
+import (
+ "fmt"
+
+ "github.com/silenceper/wechat/v2/util"
+)
+
+const (
+ getPhoneNumberURL = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=%s"
+)
+
+// GetPhoneNumberRequest 获取手机号请求
+type GetPhoneNumberRequest struct {
+ Code string `json:"code"` // 手机号获取凭证
+}
+
+// PhoneInfo 手机号信息
+type PhoneInfo struct {
+ PhoneNumber string `json:"phoneNumber"` // 用户绑定的手机号(国外手机号会有区号)
+ PurePhoneNumber string `json:"purePhoneNumber"` // 没有区号的手机号
+ CountryCode string `json:"countryCode"` // 区号
+ Watermark struct {
+ AppID string `json:"appid"` // 小程序appid
+ Timestamp int64 `json:"timestamp"` // 用户获取手机号操作的时间戳
+ } `json:"watermark"`
+}
+
+// GetPhoneNumber code换取用户手机号。 每个code只能使用一次,code的有效期为5min
+func (business *Business) GetPhoneNumber(in *GetPhoneNumberRequest) (info PhoneInfo, err error) {
+ accessToken, err := business.GetAccessToken()
+ if err != nil {
+ return
+ }
+
+ uri := fmt.Sprintf(getPhoneNumberURL, accessToken)
+ response, err := util.PostJSON(uri, in)
+ if err != nil {
+ return
+ }
+
+ // 使用通用方法返回错误
+ var resp struct {
+ util.CommonError
+ PhoneInfo PhoneInfo `json:"phone_info"`
+ }
+ err = util.DecodeWithError(response, &resp, "business.GetPhoneNumber")
+ if nil != err {
+ return
+ }
+
+ info = resp.PhoneInfo
+ return
+}
diff --git a/miniprogram/content/content.go b/miniprogram/content/content.go
index 91f6921..d53f2db 100644
--- a/miniprogram/content/content.go
+++ b/miniprogram/content/content.go
@@ -24,6 +24,8 @@ func NewContent(ctx *context.Context) *Content {
// CheckText 检测文字
// @text 需要检测的文字
+// Deprecated
+// 采用 security.MsgCheckV1 替代,返回值更加丰富
func (content *Content) CheckText(text string) error {
accessToken, err := content.GetAccessToken()
if err != nil {
@@ -44,6 +46,8 @@ func (content *Content) CheckText(text string) error {
// CheckImage 检测图片
// 所传参数为要检测的图片文件的绝对路径,图片格式支持PNG、JPEG、JPG、GIF, 像素不超过 750 x 1334,同时文件大小以不超过 300K 为宜,否则可能报错
// @media 图片文件的绝对路径
+// Deprecated
+// 采用 security.ImageCheckV1 替代,返回值更加丰富
func (content *Content) CheckImage(media string) error {
accessToken, err := content.GetAccessToken()
if err != nil {
diff --git a/miniprogram/miniprogram.go b/miniprogram/miniprogram.go
index 6741574..d76d7c2 100644
--- a/miniprogram/miniprogram.go
+++ b/miniprogram/miniprogram.go
@@ -4,6 +4,7 @@ import (
"github.com/silenceper/wechat/v2/credential"
"github.com/silenceper/wechat/v2/miniprogram/analysis"
"github.com/silenceper/wechat/v2/miniprogram/auth"
+ "github.com/silenceper/wechat/v2/miniprogram/business"
"github.com/silenceper/wechat/v2/miniprogram/config"
"github.com/silenceper/wechat/v2/miniprogram/content"
"github.com/silenceper/wechat/v2/miniprogram/context"
@@ -11,6 +12,8 @@ import (
"github.com/silenceper/wechat/v2/miniprogram/message"
"github.com/silenceper/wechat/v2/miniprogram/privacy"
"github.com/silenceper/wechat/v2/miniprogram/qrcode"
+ "github.com/silenceper/wechat/v2/miniprogram/riskcontrol"
+ "github.com/silenceper/wechat/v2/miniprogram/security"
"github.com/silenceper/wechat/v2/miniprogram/shortlink"
"github.com/silenceper/wechat/v2/miniprogram/subscribe"
"github.com/silenceper/wechat/v2/miniprogram/tcb"
@@ -59,6 +62,11 @@ func (miniProgram *MiniProgram) GetAnalysis() *analysis.Analysis {
return analysis.NewAnalysis(miniProgram.ctx)
}
+// GetBusiness 业务接口
+func (miniProgram *MiniProgram) GetBusiness() *business.Business {
+ return business.NewBusiness(miniProgram.ctx)
+}
+
// GetPrivacy 小程序隐私协议相关API
func (miniProgram *MiniProgram) GetPrivacy() *privacy.Privacy {
return privacy.NewPrivacy(miniProgram.ctx)
@@ -99,6 +107,16 @@ func (miniProgram *MiniProgram) GetURLLink() *urllink.URLLink {
return urllink.NewURLLink(miniProgram.ctx)
}
+// GetRiskControl 安全风控接口
+func (miniProgram *MiniProgram) GetRiskControl() *riskcontrol.RiskControl {
+ return riskcontrol.NewRiskControl(miniProgram.ctx)
+}
+
+// GetSecurity 内容安全接口
+func (miniProgram *MiniProgram) GetSecurity() *security.Security {
+ return security.NewSecurity(miniProgram.ctx)
+}
+
// GetShortLink 小程序短链接口
func (miniProgram *MiniProgram) GetShortLink() *shortlink.ShortLink {
return shortlink.NewShortLink(miniProgram.ctx)
diff --git a/miniprogram/riskcontrol/riskcontrol.go b/miniprogram/riskcontrol/riskcontrol.go
new file mode 100644
index 0000000..2618772
--- /dev/null
+++ b/miniprogram/riskcontrol/riskcontrol.go
@@ -0,0 +1,60 @@
+package riskcontrol
+
+import (
+ "fmt"
+
+ "github.com/silenceper/wechat/v2/miniprogram/context"
+ "github.com/silenceper/wechat/v2/util"
+)
+
+const (
+ getUserRiskRankURL = "https://api.weixin.qq.com/wxa/getuserriskrank?access_token=%s"
+)
+
+// RiskControl 安全风控
+type RiskControl struct {
+ *context.Context
+}
+
+// NewRiskControl init
+func NewRiskControl(ctx *context.Context) *RiskControl {
+ return &RiskControl{ctx}
+}
+
+// UserRiskRankRequest 获取用户安全等级请求
+type UserRiskRankRequest struct {
+ AppID string `json:"appid"` // 小程序 app id
+ OpenID string `json:"openid"` // 用户的 openid
+ Scene uint8 `json:"scene"` // 场景值,0:注册,1:营销作弊
+ ClientIP string `json:"client_ip"` // 用户访问源ip
+
+ Mobile string `json:"mobile_no"` // 用户手机号
+ Email string `json:"email_address"` // 用户邮箱地址
+ ExtendedInfo string `json:"extended_info"` // 额外补充信息
+ IsTest bool `json:"is_test"` // false:正式调用,true:测试调用
+}
+
+// UserRiskRank 用户安全等级
+type UserRiskRank struct {
+ util.CommonError
+ UnionID int64 `json:"union_id"` // 唯一请求标识
+ RiskRank uint8 `json:"risk_rank"` // 用户风险等级
+}
+
+// GetUserRiskRank 根据提交的用户信息数据获取用户的安全等级 risk_rank,无需用户授权。
+func (riskControl *RiskControl) GetUserRiskRank(in *UserRiskRankRequest) (res UserRiskRank, err error) {
+ accessToken, err := riskControl.GetAccessToken()
+ if err != nil {
+ return
+ }
+
+ uri := fmt.Sprintf(getUserRiskRankURL, accessToken)
+ response, err := util.PostJSON(uri, in)
+ if err != nil {
+ return
+ }
+
+ // 使用通用方法返回错误
+ err = util.DecodeWithError(response, &res, "GetUserRiskRank")
+ return
+}
diff --git a/miniprogram/security/security.go b/miniprogram/security/security.go
new file mode 100644
index 0000000..7e4d5dc
--- /dev/null
+++ b/miniprogram/security/security.go
@@ -0,0 +1,256 @@
+package security
+
+import (
+ "fmt"
+ "strconv"
+
+ "github.com/silenceper/wechat/v2/miniprogram/context"
+ "github.com/silenceper/wechat/v2/util"
+)
+
+const (
+ mediaCheckAsyncURL = "https://api.weixin.qq.com/wxa/media_check_async?access_token=%s"
+ imageCheckURL = "https://api.weixin.qq.com/wxa/img_sec_check?access_token=%s"
+ msgCheckURL = "https://api.weixin.qq.com/wxa/msg_sec_check?access_token=%s"
+)
+
+// Security 内容安全
+type Security struct {
+ *context.Context
+}
+
+// NewSecurity init
+func NewSecurity(ctx *context.Context) *Security {
+ return &Security{ctx}
+}
+
+// MediaCheckAsyncV1Request 图片/音频异步校验请求参数
+type MediaCheckAsyncV1Request struct {
+ MediaURL string `json:"media_url"` // 要检测的图片或音频的url,支持图片格式包括jpg, jepg, png, bmp, gif(取首帧),支持的音频格式包括mp3, aac, ac3, wma, flac, vorbis, opus, wav
+ MediaType uint8 `json:"media_type"` // 1:音频;2:图片
+}
+
+// MediaCheckAsyncV1 异步校验图片/音频是否含有违法违规内容
+// Deprecated
+// 在2021年9月1日停止更新,请尽快更新至 2.0 接口。建议使用 MediaCheckAsync
+func (security *Security) MediaCheckAsyncV1(in *MediaCheckAsyncV1Request) (traceID string, err error) {
+ accessToken, err := security.GetAccessToken()
+ if err != nil {
+ return
+ }
+
+ uri := fmt.Sprintf(mediaCheckAsyncURL, accessToken)
+ response, err := util.PostJSON(uri, in)
+ if err != nil {
+ return
+ }
+
+ // 使用通用方法返回错误
+ var res struct {
+ util.CommonError
+ TraceID string `json:"trace_id"`
+ }
+ err = util.DecodeWithError(response, &res, "MediaCheckAsyncV1")
+ if err != nil {
+ return
+ }
+
+ traceID = res.TraceID
+ return
+}
+
+// MediaCheckAsyncRequest 图片/音频异步校验请求参数
+type MediaCheckAsyncRequest struct {
+ MediaURL string `json:"media_url"` // 要检测的图片或音频的url,支持图片格式包括jpg, jepg, png, bmp, gif(取首帧),支持的音频格式包括mp3, aac, ac3, wma, flac, vorbis, opus, wav
+ MediaType uint8 `json:"media_type"` // 1:音频;2:图片
+ OpenID string `json:"openid"` // 用户的openid(用户需在近两小时访问过小程序)
+ Scene uint8 `json:"scene"` // 场景枚举值(1 资料;2 评论;3 论坛;4 社交日志)
+}
+
+// MediaCheckAsync 异步校验图片/音频是否含有违法违规内容
+func (security *Security) MediaCheckAsync(in *MediaCheckAsyncRequest) (traceID string, err error) {
+ accessToken, err := security.GetAccessToken()
+ if err != nil {
+ return
+ }
+
+ var req struct {
+ MediaCheckAsyncRequest
+ Version uint `json:"version"` // 接口版本号,2.0版本为固定值2
+ }
+ req.MediaCheckAsyncRequest = *in
+ req.Version = 2
+
+ uri := fmt.Sprintf(mediaCheckAsyncURL, accessToken)
+ response, err := util.PostJSON(uri, req)
+ if err != nil {
+ return
+ }
+
+ // 使用通用方法返回错误
+ var res struct {
+ util.CommonError
+ TraceID string `json:"trace_id"`
+ }
+ err = util.DecodeWithError(response, &res, "MediaCheckAsync")
+ if err != nil {
+ return
+ }
+
+ traceID = res.TraceID
+ return
+}
+
+// ImageCheckV1 校验一张图片是否含有违法违规内容(同步)
+// https://developers.weixin.qq.com/miniprogram/dev/framework/security.imgSecCheck.html
+// Deprecated
+// 在2021年9月1日停止更新。建议使用 MediaCheckAsync
+func (security *Security) ImageCheckV1(filename string) (err error) {
+ accessToken, err := security.GetAccessToken()
+ if err != nil {
+ return
+ }
+
+ uri := fmt.Sprintf(imageCheckURL, accessToken)
+ response, err := util.PostFile("media", filename, uri)
+ if err != nil {
+ return
+ }
+
+ // 使用通用方法返回错误
+ return util.DecodeWithCommonError(response, "ImageCheckV1")
+}
+
+// CheckSuggest 检查建议
+type CheckSuggest string
+
+const (
+ // CheckSuggestRisky 违规风险建议
+ CheckSuggestRisky CheckSuggest = "risky"
+ // CheckSuggestPass 安全
+ CheckSuggestPass CheckSuggest = "pass"
+ // CheckSuggestReview 需要审查
+ CheckSuggestReview CheckSuggest = "review"
+)
+
+// MsgScene 文本场景
+type MsgScene uint8
+
+const (
+ // MsgSceneMaterial 资料文件检查场景
+ MsgSceneMaterial MsgScene = iota + 1
+ // MsgSceneComment 评论
+ MsgSceneComment
+ // MsgSceneForum 论坛
+ MsgSceneForum
+ // MsgSceneSocialLog 社交日志
+ MsgSceneSocialLog
+)
+
+// CheckLabel 检查命中标签
+type CheckLabel int
+
+func (cl CheckLabel) String() string {
+ switch cl {
+ case 100:
+ return "正常"
+ case 10001:
+ return "广告"
+ case 20001:
+ return "时政"
+ case 20002:
+ return "色情"
+ case 20003:
+ return "辱骂"
+ case 20006:
+ return "违法犯罪"
+ case 20008:
+ return "欺诈"
+ case 20012:
+ return "低俗"
+ case 20013:
+ return "版权"
+ case 21000:
+ return "其他"
+ default:
+ return strconv.Itoa(int(cl))
+ }
+}
+
+// MsgCheckRequest 文本检查请求
+type MsgCheckRequest struct {
+ OpenID string `json:"openid"` // 用户的openid(用户需在近两小时访问过小程序)
+ Scene MsgScene `json:"scene"` // 场景枚举值(1 资料;2 评论;3 论坛;4 社交日志)
+ Content string `json:"content"` // 需检测的文本内容,文本字数的上限为 2500 字,需使用 UTF-8 编码
+ Nickname string `json:"nickname"` // (非必填)用户昵称,需使用UTF-8编码
+ Title string `json:"title"` // (非必填)文本标题,需使用UTF-8编码
+ Signature string `json:"signature"` // (非必填)个性签名,该参数仅在资料类场景有效(scene=1),需使用UTF-8编码
+}
+
+// MsgCheckResponse 文本检查响应
+type MsgCheckResponse struct {
+ util.CommonError
+ TraceID string `json:"trace_id"` // 唯一请求标识
+ Result struct {
+ Suggest CheckSuggest `json:"suggest"` // 建议
+ Label CheckLabel `json:"label"` // 命中标签
+ } `json:"result"` // 综合结果
+ Detail []struct {
+ ErrCode int64 `json:"errcode"` // 错误码,仅当该值为0时,该项结果有效
+ Strategy string `json:"strategy"` // 策略类型
+ Suggest string `json:"suggest"` // 建议
+ Label CheckLabel `json:"label"` // 命中标签
+ Prob uint `json:"prob"` // 置信度。0-100,越高代表越有可能属于当前返回的标签(label)
+ Keyword string `json:"keyword"` // 命中的自定义关键词
+ } `json:"detail"` // 详细检测结果
+}
+
+// MsgCheckV1 检查一段文本是否含有违法违规内容
+// Deprecated
+// 在2021年9月1日停止更新,请尽快更新至 2.0 接口。建议使用 MsgCheck
+func (security *Security) MsgCheckV1(content string) (res MsgCheckResponse, err error) {
+ accessToken, err := security.GetAccessToken()
+ if err != nil {
+ return
+ }
+
+ var req struct {
+ Content string `json:"content"`
+ }
+ req.Content = content
+
+ uri := fmt.Sprintf(msgCheckURL, accessToken)
+ response, err := util.PostJSON(uri, req)
+ if err != nil {
+ return
+ }
+
+ // 使用通用方法返回错误
+ err = util.DecodeWithError(response, &res, "security.MsgCheckV1")
+ return
+}
+
+// MsgCheck 检查一段文本是否含有违法违规内容
+func (security *Security) MsgCheck(in *MsgCheckRequest) (res MsgCheckResponse, err error) {
+ accessToken, err := security.GetAccessToken()
+ if err != nil {
+ return
+ }
+
+ var req struct {
+ MsgCheckRequest
+ Version uint `json:"version"`
+ }
+ req.MsgCheckRequest = *in
+ req.Version = 2
+
+ uri := fmt.Sprintf(msgCheckURL, accessToken)
+ response, err := util.PostJSON(uri, req)
+ if err != nil {
+ return
+ }
+
+ // 使用通用方法返回错误
+ err = util.DecodeWithError(response, &res, "security.MsgCheck")
+ return
+}