feat: add wxshare

This commit is contained in:
Rogee
2025-04-30 17:06:10 +08:00
parent af0507d0c1
commit 42c1c17c0a
24 changed files with 313 additions and 147 deletions

View File

@@ -21,6 +21,7 @@ type TokenResponse struct {
} }
// Login // Login
//
// @Router /admin/auth [post] // @Router /admin/auth [post]
// @Bind body body // @Bind body body
func (ctl *auth) Login(ctx fiber.Ctx, body *AuthBody) (*TokenResponse, error) { func (ctl *auth) Login(ctx fiber.Ctx, body *AuthBody) (*TokenResponse, error) {

View File

@@ -14,6 +14,7 @@ type medias struct {
} }
// List medias // List medias
//
// @Router /admin/medias [get] // @Router /admin/medias [get]
// @Bind pagination query // @Bind pagination query
// @Bind query query // @Bind query query
@@ -23,6 +24,7 @@ func (ctl *medias) List(ctx fiber.Ctx, pagination *requests.Pagination, query *L
} }
// Show media // Show media
//
// @Router /admin/medias/:id [get] // @Router /admin/medias/:id [get]
// @Bind id path // @Bind id path
func (ctl *medias) Show(ctx fiber.Ctx, id int64) error { func (ctl *medias) Show(ctx fiber.Ctx, id int64) error {
@@ -40,6 +42,7 @@ func (ctl *medias) Show(ctx fiber.Ctx, id int64) error {
} }
// Delete // Delete
//
// @Router /admin/medias/:id [delete] // @Router /admin/medias/:id [delete]
// @Bind id path // @Bind id path
func (ctl *medias) Delete(ctx fiber.Ctx, id int64) error { func (ctl *medias) Delete(ctx fiber.Ctx, id int64) error {

View File

@@ -16,6 +16,7 @@ type OrderListQuery struct {
type orders struct{} type orders struct{}
// List users // List users
//
// @Router /admin/orders [get] // @Router /admin/orders [get]
// @Bind pagination query // @Bind pagination query
// @Bind query query // @Bind query query

View File

@@ -18,6 +18,7 @@ type ListQuery struct {
type posts struct{} type posts struct{}
// List posts // List posts
//
// @Router /admin/posts [get] // @Router /admin/posts [get]
// @Bind pagination query // @Bind pagination query
// @Bind query query // @Bind query query
@@ -63,6 +64,7 @@ type PostForm struct {
} }
// Create // Create
//
// @Router /admin/posts [post] // @Router /admin/posts [post]
// @Bind form body // @Bind form body
func (ctl *posts) Create(ctx fiber.Ctx, form *PostForm) error { func (ctl *posts) Create(ctx fiber.Ctx, form *PostForm) error {
@@ -100,6 +102,7 @@ func (ctl *posts) Create(ctx fiber.Ctx, form *PostForm) error {
} }
// Update posts // Update posts
//
// @Router /admin/posts/:id [put] // @Router /admin/posts/:id [put]
// @Bind id path // @Bind id path
// @Bind form body // @Bind form body
@@ -148,6 +151,7 @@ func (ctl *posts) Update(ctx fiber.Ctx, id int64, form *PostForm) error {
} }
// Delete posts // Delete posts
//
// @Router /admin/posts/:id [delete] // @Router /admin/posts/:id [delete]
// @Bind id path // @Bind id path
func (ctl *posts) Delete(ctx fiber.Ctx, id int64) error { func (ctl *posts) Delete(ctx fiber.Ctx, id int64) error {
@@ -172,6 +176,7 @@ type PostItem struct {
} }
// Show posts by id // Show posts by id
//
// @Router /admin/posts/:id [get] // @Router /admin/posts/:id [get]
// @Bind id path // @Bind id path
func (ctl *posts) Show(ctx fiber.Ctx, id int64) (*PostItem, error) { func (ctl *posts) Show(ctx fiber.Ctx, id int64) (*PostItem, error) {
@@ -193,6 +198,7 @@ func (ctl *posts) Show(ctx fiber.Ctx, id int64) (*PostItem, error) {
} }
// SendTo // SendTo
//
// @Router /admin/posts/:id/send-to/:userId [post] // @Router /admin/posts/:id/send-to/:userId [post]
// @Bind id path // @Bind id path
// @Bind userId path // @Bind userId path

View File

@@ -22,6 +22,7 @@ type StatisticsResponse struct {
} }
// dashboard statistics // dashboard statistics
//
// @Router /admin/statistics [get] // @Router /admin/statistics [get]
func (s *statistics) statistics(ctx fiber.Ctx) (*StatisticsResponse, error) { func (s *statistics) statistics(ctx fiber.Ctx) (*StatisticsResponse, error) {
statistics := &StatisticsResponse{} statistics := &StatisticsResponse{}

View File

@@ -33,6 +33,7 @@ type PreCheckResp struct {
} }
// PreUploadCheck // PreUploadCheck
//
// @Router /admin/uploads/pre-uploaded-check/:md5.:ext [get] // @Router /admin/uploads/pre-uploaded-check/:md5.:ext [get]
// @Bind md5 path // @Bind md5 path
// @Bind ext path // @Bind ext path
@@ -59,6 +60,7 @@ type PostUploadedForm struct {
} }
// PostUploadedAction // PostUploadedAction
//
// @Router /admin/uploads/post-uploaded-action [post] // @Router /admin/uploads/post-uploaded-action [post]
// @Bind body body // @Bind body body
func (up *uploads) PostUploadedAction(ctx fiber.Ctx, body *PostUploadedForm) error { func (up *uploads) PostUploadedAction(ctx fiber.Ctx, body *PostUploadedForm) error {

View File

@@ -16,6 +16,7 @@ type UserListQuery struct {
type users struct{} type users struct{}
// List users // List users
//
// @Router /admin/users [get] // @Router /admin/users [get]
// @Bind pagination query // @Bind pagination query
// @Bind query query // @Bind query query
@@ -25,6 +26,7 @@ func (ctl *users) List(ctx fiber.Ctx, pagination *requests.Pagination, query *Us
} }
// Show user // Show user
//
// @Router /admin/users/:id [get] // @Router /admin/users/:id [get]
// @Bind id path // @Bind id path
func (ctl *users) Show(ctx fiber.Ctx, id int64) (*model.Users, error) { func (ctl *users) Show(ctx fiber.Ctx, id int64) (*model.Users, error) {
@@ -32,6 +34,7 @@ func (ctl *users) Show(ctx fiber.Ctx, id int64) (*model.Users, error) {
} }
// Articles show user bought articles // Articles show user bought articles
//
// @Router /admin/users/:id/articles [get] // @Router /admin/users/:id/articles [get]
// @Bind id path // @Bind id path
// @Bind pagination query // @Bind pagination query

View File

@@ -22,6 +22,7 @@ type pays struct {
} }
// Callback // Callback
//
// @Router /pay/callback/:channel [get] // @Router /pay/callback/:channel [get]
// @Bind channel path // @Bind channel path
func (ctl *pays) Callback(ctx fiber.Ctx, channel string) error { func (ctl *pays) Callback(ctx fiber.Ctx, channel string) error {

View File

@@ -29,6 +29,7 @@ type posts struct {
} }
// List posts // List posts
//
// @Router /posts [get] // @Router /posts [get]
// @Bind pagination query // @Bind pagination query
// @Bind query query // @Bind query query
@@ -99,6 +100,7 @@ type PostItem struct {
} }
// Show // Show
//
// @Router /posts/:id/show [get] // @Router /posts/:id/show [get]
// @Bind id path // @Bind id path
// @Bind user local // @Bind user local
@@ -148,6 +150,7 @@ type PlayUrl struct {
} }
// Play // Play
//
// @Router /posts/:id/play [get] // @Router /posts/:id/play [get]
// @Bind id path // @Bind id path
// @Bind user local // @Bind user local
@@ -181,6 +184,7 @@ func (ctl *posts) Play(ctx fiber.Ctx, id int64, user *model.Users) (*PlayUrl, er
} }
// Mine posts // Mine posts
//
// @Router /posts/mine [get] // @Router /posts/mine [get]
// @Bind pagination query // @Bind pagination query
// @Bind query query // @Bind query query
@@ -236,6 +240,7 @@ func (ctl *posts) Mine(ctx fiber.Ctx, pagination *requests.Pagination, query *Li
} }
// Buy // Buy
//
// @Router /posts/:id/buy [get] // @Router /posts/:id/buy [get]
// @Bind id path // @Bind id path
// @Bind user local // @Bind user local

View File

@@ -58,12 +58,14 @@ func Provide(opts ...opt.Option) error {
pays *pays, pays *pays,
posts *posts, posts *posts,
users *users, users *users,
wechats *wechats,
) (contracts.HttpRoute, error) { ) (contracts.HttpRoute, error) {
obj := &Routes{ obj := &Routes{
auth: auth, auth: auth,
pays: pays, pays: pays,
posts: posts, posts: posts,
users: users, users: users,
wechats: wechats,
} }
if err := obj.Prepare(); err != nil { if err := obj.Prepare(); err != nil {
return nil, err return nil, err

View File

@@ -19,6 +19,7 @@ type Routes struct {
pays *pays pays *pays
posts *posts posts *posts
users *users users *users
wechats *wechats
} }
func (r *Routes) Prepare() error { func (r *Routes) Prepare() error {
@@ -95,4 +96,11 @@ func (r *Routes) Register(router fiber.Router) {
Body[ProfileForm]("form"), Body[ProfileForm]("form"),
)) ))
// 注册路由组: wechats
router.Get("/wechats/js-sdk", DataFunc2(
r.wechats.GetJsSDK,
QueryParam[string]("url"),
Local[*model.Users]("user"),
))
} }

View File

@@ -34,6 +34,7 @@ type ProfileForm struct {
} }
// Update // Update
//
// @Router /users/username [put] // @Router /users/username [put]
// @Bind user local // @Bind user local
// @Bind form body // @Bind form body

View File

@@ -1,19 +0,0 @@
package http
import (
"quyun/database/schemas/public/model"
"quyun/providers/wechat"
"github.com/gofiber/fiber/v3"
)
// @provider
type wechats struct {
wechat *wechat.Client
}
// @Router /wechat/js-ticket [get]
// @Bind user local
func (ctl *wechats) GetTicket(ctx fiber.Ctx, user *model.Users) (string, error) {
return ctl.wechat.GetJSTicket(user.AuthToken.Data.AccessToken)
}

View File

@@ -0,0 +1,21 @@
package http
import (
"quyun/database/schemas/public/model"
"quyun/providers/wechat"
"github.com/gofiber/fiber/v3"
)
// @provider
type wechats struct {
wechat *wechat.Client
}
// GetJsSDK
// @Router /wechats/js-sdk [get]
// @Bind url query
// @Bind user local
func (ctl *wechats) GetJsSDK(ctx fiber.Ctx, url string, user *model.Users) (*wechat.JsSDK, error) {
return ctl.wechat.GetJsSDK(user.AuthToken.Data.StableAccessToken, url)
}

View File

@@ -1,14 +1,24 @@
package wechat package wechat
import "math/rand" import (
"crypto/sha1"
"encoding/hex"
"math/rand"
)
// RandomString generate random size string // RandomString generate random size string
func randomString(size int) (string, error) { func randomString(size int) string {
// generate size string [0-9a-zA-Z] // generate size string [0-9a-zA-Z]
const chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" const chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
b := make([]byte, size) b := make([]byte, size)
for i := range b { for i := range b {
b[i] = chars[rand.Intn(len(chars))] b[i] = chars[rand.Intn(len(chars))]
} }
return string(b), nil return string(b)
}
func hashSha1(input string) string {
h := sha1.New()
h.Write([]byte(input))
return hex.EncodeToString(h.Sum(nil))
} }

View File

@@ -3,6 +3,7 @@ package wechat
import ( import (
"crypto/sha1" "crypto/sha1"
"encoding/hex" "encoding/hex"
"fmt"
"net/url" "net/url"
"sort" "sort"
"strings" "strings"
@@ -287,3 +288,32 @@ func (we *Client) GetJSTicket(token string) (string, error) {
return data.Ticket, nil return data.Ticket, nil
} }
type JsSDK struct {
Debug bool `json:"debug"`
AppID string `json:"appId"`
Timestamp int64 `json:"timestamp"`
NonceStr string `json:"nonceStr"`
Signature string `json:"signature"`
}
// GetJSTicket
func (we *Client) GetJsSDK(token, url string) (*JsSDK, error) {
sdk := &JsSDK{
Debug: false,
AppID: we.appID,
Timestamp: time.Now().Unix(),
NonceStr: randomString(16),
Signature: "",
}
// get ticket
ticket, err := we.GetJSTicket(token)
if err != nil {
return nil, errors.Wrap(err, "get wechat ticket failed")
}
input := fmt.Sprintf("jsapi_ticket=%s&noncestr=%s&timestamp=%d&url=%s", ticket, sdk.NonceStr, sdk.Timestamp, url)
sdk.Signature = hashSha1(input)
return sdk, nil
}

Binary file not shown.

View File

@@ -15,10 +15,13 @@
"dplayer": "^1.27.1", "dplayer": "^1.27.1",
"pinia": "^3.0.2", "pinia": "^3.0.2",
"plyr": "^3.7.8", "plyr": "^3.7.8",
"sha1": "^1.1.1",
"tailwindcss": "^4.1.4", "tailwindcss": "^4.1.4",
"vue": "^3.5.13", "vue": "^3.5.13",
"vue-icons-plus": "^0.1.8", "vue-icons-plus": "^0.1.8",
"vue-router": "^4.5.0" "vue-router": "^4.5.0",
"wechat-js-sdk": "^1.3.3",
"weixin-js-sdk": "^1.6.5"
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "^5.2.2", "@vitejs/plugin-vue": "^5.2.2",

View File

@@ -0,0 +1,11 @@
import client from './client';
export const wechatApi = {
jsSdk() {
return client.get('/wechats/js-sdk', {
params: {
url: window.location.href.split('#')[0],
},
});
},
}

View File

@@ -0,0 +1,14 @@
export function useRandom() {
function string(size) {
let result = '';
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
const charactersLength = characters.length;
for (let i = 0; i < size; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
return {
string,
};
}

View File

@@ -0,0 +1,80 @@
import wx from "weixin-js-sdk";
export function useWxSDK() {
/**
* 初始化设置
*/
function initConfig(config) {
return new Promise((resolve) => {
wx.config({
debug: config.debug,
appId: config.appId,
timestamp: config.timestamp,
nonceStr: config.nonceStr,
signature: config.signature,
jsApiList: [
"chooseImage",
"uploadImage",
"previewImage",
"onMenuShareTimeline",
"onMenuShareAppMessage",
"chooseWXPay",
],
openTagList: [],
});
wx.ready(() => {
console.log("wx.ready called");
resolve(true);
});
});
}
/** 设置微信分享 */
function setShareInfo(
shareInfo,
onSuccess = () => { },
onCancel = () => { }
) {
wx.onMenuShareTimeline({
title: shareInfo.title, // 分享标题
link: shareInfo.link, // 分享链接可以不是当前页面该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
imgUrl: shareInfo.imgUrl,
success: function () {
// 用户确认分享后执行的回调函数
onSuccess();
},
cancel: function () {
onCancel();
// 用户取消分享后执行的回调函数
},
});
wx.onMenuShareAppMessage({
title: shareInfo.title, // 分享标题
desc: shareInfo.desc,
link: shareInfo.link, // 分享链接可以不是当前页面该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
imgUrl: shareInfo.imgUrl,
type: "link", // 分享类型,music、video或link不填默认为link
success: function (e) {
// 用户确认分享后执行的回调函数
onSuccess();
console.log("分享成功", e);
},
cancel: function (e) {
// 用户取消分享后执行的回调函数
onCancel();
console.log("分享取消", e);
},
});
}
/** 是否是ios微信 */
function isiOSWechat() {
return window.__wxjs_is_wkwebview;
}
return {
initConfig,
setShareInfo,
isiOSWechat,
};
}

View File

@@ -5,7 +5,10 @@ import { onMounted, onUnmounted, ref } from 'vue'
import { BsChevronLeft } from 'vue-icons-plus/bs' import { BsChevronLeft } from 'vue-icons-plus/bs'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import { postApi } from '../api/postApi' import { postApi } from '../api/postApi'
import { wechatApi } from '../api/wechatApi'
import { useWxSDK } from '../hooks/useWxSDK'
const wx = useWxSDK()
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const article = ref(null) const article = ref(null)
@@ -90,40 +93,13 @@ const fetchArticle = async () => {
article.value = data article.value = data
document.title = article.value.title document.title = article.value.title
// 定义“分享给朋友”及“分享到QQ”按钮的分享内容 // 调用微信 JS SDK 分享接口
const shareContent = { wx.setShareInfo({
title: data.title, title: data.title,
desc: data.content, desc: data.content,
link: window.location.href, link: window.location.href,
imgUrl: data.head_images[0] imgUrl: data.head_images[0]
}
// 调用微信 JS SDK 分享接口
if (window.wx) {
wx.updateTimelineShareData({
title: shareContent.title,
link: shareContent.link,
imgUrl: shareContent.imgUrl,
success: function () {
console.log('分享成功')
},
cancel: function () {
console.log('分享取消')
}
}) })
wx.updateAppMessageShareData({
title: shareContent.title,
desc: shareContent.desc,
link: shareContent.link,
imgUrl: shareContent.imgUrl,
success: function () {
console.log('分享成功')
},
cancel: function () {
console.log('分享取消')
}
})
}
} catch (error) { } catch (error) {
console.error('Failed to fetch article:', error) console.error('Failed to fetch article:', error)
alert("加载失败!") alert("加载失败!")
@@ -137,6 +113,12 @@ const handleBack = () => {
onMounted(async () => { onMounted(async () => {
await fetchArticle() await fetchArticle()
initializePlayer() initializePlayer()
wechatApi.jsSdk().then(resp => {
wx.initConfig(resp.data)
}).catch(error => {
console.error('Failed to initialize WeChat SDK:', error)
})
}) })
onUnmounted(() => { onUnmounted(() => {