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,8 +21,9 @@ type TokenResponse struct {
}
// Login
// @Router /admin/auth [post]
// @Bind body body
//
// @Router /admin/auth [post]
// @Bind body body
func (ctl *auth) Login(ctx fiber.Ctx, body *AuthBody) (*TokenResponse, error) {
if body.Username == "pl.yang" && body.Password == "Xixi@0202" {
claim := ctl.jwt.CreateClaims(jwt.BaseClaims{

View File

@@ -14,17 +14,19 @@ type medias struct {
}
// List medias
// @Router /admin/medias [get]
// @Bind pagination query
// @Bind query query
//
// @Router /admin/medias [get]
// @Bind pagination query
// @Bind query query
func (ctl *medias) List(ctx fiber.Ctx, pagination *requests.Pagination, query *ListQuery) (*requests.Pager, error) {
cond := models.Medias.BuildConditionWithKey(query.Keyword)
return models.Medias.List(ctx.Context(), pagination, cond)
}
// Show media
// @Router /admin/medias/:id [get]
// @Bind id path
//
// @Router /admin/medias/:id [get]
// @Bind id path
func (ctl *medias) Show(ctx fiber.Ctx, id int64) error {
media, err := models.Medias.GetByID(ctx.Context(), id)
if err != nil {
@@ -40,8 +42,9 @@ func (ctl *medias) Show(ctx fiber.Ctx, id int64) error {
}
// Delete
// @Router /admin/medias/:id [delete]
// @Bind id path
//
// @Router /admin/medias/:id [delete]
// @Bind id path
func (ctl *medias) Delete(ctx fiber.Ctx, id int64) error {
media, err := models.Medias.GetByID(ctx.Context(), id)
if err != nil {

View File

@@ -16,9 +16,10 @@ type OrderListQuery struct {
type orders struct{}
// List users
// @Router /admin/orders [get]
// @Bind pagination query
// @Bind query query
//
// @Router /admin/orders [get]
// @Bind pagination query
// @Bind query query
func (ctl *orders) List(ctx fiber.Ctx, pagination *requests.Pagination, query *OrderListQuery) (*requests.Pager, error) {
cond := models.Orders.BuildConditionWithKey(query.OrderNumber, query.UserID)
return models.Orders.List(ctx.Context(), pagination, cond)

View File

@@ -18,9 +18,10 @@ type ListQuery struct {
type posts struct{}
// List posts
// @Router /admin/posts [get]
// @Bind pagination query
// @Bind query query
//
// @Router /admin/posts [get]
// @Bind pagination query
// @Bind query query
func (ctl *posts) List(ctx fiber.Ctx, pagination *requests.Pagination, query *ListQuery) (*requests.Pager, error) {
cond := models.Posts.BuildConditionWithKey(query.Keyword)
pager, err := models.Posts.List(ctx.Context(), pagination, cond)
@@ -63,8 +64,9 @@ type PostForm struct {
}
// Create
// @Router /admin/posts [post]
// @Bind form body
//
// @Router /admin/posts [post]
// @Bind form body
func (ctl *posts) Create(ctx fiber.Ctx, form *PostForm) error {
post := model.Posts{
Title: form.Title,
@@ -100,9 +102,10 @@ func (ctl *posts) Create(ctx fiber.Ctx, form *PostForm) error {
}
// Update posts
// @Router /admin/posts/:id [put]
// @Bind id path
// @Bind form body
//
// @Router /admin/posts/:id [put]
// @Bind id path
// @Bind form body
func (ctl *posts) Update(ctx fiber.Ctx, id int64, form *PostForm) error {
oldPost, err := models.Posts.GetByID(ctx.Context(), id)
if err != nil {
@@ -148,8 +151,9 @@ func (ctl *posts) Update(ctx fiber.Ctx, id int64, form *PostForm) error {
}
// Delete posts
// @Router /admin/posts/:id [delete]
// @Bind id path
//
// @Router /admin/posts/:id [delete]
// @Bind id path
func (ctl *posts) Delete(ctx fiber.Ctx, id int64) error {
post, err := models.Posts.GetByID(ctx.Context(), id)
if err != nil {
@@ -172,8 +176,9 @@ type PostItem struct {
}
// Show posts by id
// @Router /admin/posts/:id [get]
// @Bind id path
//
// @Router /admin/posts/:id [get]
// @Bind id path
func (ctl *posts) Show(ctx fiber.Ctx, id int64) (*PostItem, error) {
post, err := models.Posts.GetByID(ctx.Context(), id)
if err != nil {
@@ -193,9 +198,10 @@ func (ctl *posts) Show(ctx fiber.Ctx, id int64) (*PostItem, error) {
}
// SendTo
// @Router /admin/posts/:id/send-to/:userId [post]
// @Bind id path
// @Bind userId path
//
// @Router /admin/posts/:id/send-to/:userId [post]
// @Bind id path
// @Bind userId path
func (ctl *posts) SendTo(ctx fiber.Ctx, id, userId int64) error {
if _, err := models.Posts.GetByID(ctx.Context(), id); err != nil {
return err

View File

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

View File

@@ -33,10 +33,11 @@ type PreCheckResp struct {
}
// PreUploadCheck
// @Router /admin/uploads/pre-uploaded-check/:md5.:ext [get]
// @Bind md5 path
// @Bind ext path
// @Bind mime query
//
// @Router /admin/uploads/pre-uploaded-check/:md5.:ext [get]
// @Bind md5 path
// @Bind ext path
// @Bind mime query
func (up *uploads) PreUploadCheck(ctx fiber.Ctx, md5, ext, mime string) (*PreCheckResp, error) {
_, err := models.Medias.GetByHash(ctx.Context(), md5)
if err != nil && errors.Is(err, qrm.ErrNoRows) {
@@ -59,8 +60,9 @@ type PostUploadedForm struct {
}
// PostUploadedAction
// @Router /admin/uploads/post-uploaded-action [post]
// @Bind body body
//
// @Router /admin/uploads/post-uploaded-action [post]
// @Bind body body
func (up *uploads) PostUploadedAction(ctx fiber.Ctx, body *PostUploadedForm) error {
m, err := models.Medias.GetByHash(ctx.Context(), body.Md5)
if err != nil && !errors.Is(err, qrm.ErrNoRows) {

View File

@@ -16,25 +16,28 @@ type UserListQuery struct {
type users struct{}
// List users
// @Router /admin/users [get]
// @Bind pagination query
// @Bind query query
//
// @Router /admin/users [get]
// @Bind pagination query
// @Bind query query
func (ctl *users) List(ctx fiber.Ctx, pagination *requests.Pagination, query *UserListQuery) (*requests.Pager, error) {
cond := models.Users.BuildConditionWithKey(query.Keyword)
return models.Users.List(ctx.Context(), pagination, cond)
}
// Show user
// @Router /admin/users/:id [get]
// @Bind id path
//
// @Router /admin/users/:id [get]
// @Bind id path
func (ctl *users) Show(ctx fiber.Ctx, id int64) (*model.Users, error) {
return models.Users.GetByID(ctx.Context(), id)
}
// Articles show user bought articles
// @Router /admin/users/:id/articles [get]
// @Bind id path
// @Bind pagination query
//
// @Router /admin/users/:id/articles [get]
// @Bind id path
// @Bind pagination query
func (ctl *users) Articles(ctx fiber.Ctx, id int64, pagination *requests.Pagination) (*requests.Pager, error) {
return models.Posts.Bought(ctx.Context(), id, pagination)
}

View File

@@ -27,10 +27,10 @@ type auth struct {
jwt *jwt.JWT
}
// @Router /auth/login [get]
// @Bind code query
// @Bind state query
// @Bind redirect query
// @Router /auth/login [get]
// @Bind code query
// @Bind state query
// @Bind redirect query
func (ctl *auth) Login(ctx fiber.Ctx, code, state, redirect string) error {
log.Debugf("code: %s, state: %s", code, state)
@@ -100,7 +100,7 @@ func (ctl *auth) Login(ctx fiber.Ctx, code, state, redirect string) error {
}
// @Router /auth/wechat [get]
// @Bind redirect query
// @Bind redirect query
func (ctl *auth) Wechat(ctx fiber.Ctx, redirect string) error {
log.Debugf("%s, query: %v", ctx.OriginalURL(), ctx.Queries())

View File

@@ -22,8 +22,9 @@ type pays struct {
}
// Callback
// @Router /pay/callback/:channel [get]
// @Bind channel path
//
// @Router /pay/callback/:channel [get]
// @Bind channel path
func (ctl *pays) Callback(ctx fiber.Ctx, channel string) error {
log := log.WithField("method", "pays.Callback")

View File

@@ -29,10 +29,11 @@ type posts struct {
}
// List posts
// @Router /posts [get]
// @Bind pagination query
// @Bind query query
// @Bind user local
//
// @Router /posts [get]
// @Bind pagination query
// @Bind query query
// @Bind user local
func (ctl *posts) List(ctx fiber.Ctx, pagination *requests.Pagination, query *ListQuery, user *model.Users) (*requests.Pager, error) {
log.Infof("ok", pagination, query.Keyword, user.ID)
@@ -99,9 +100,10 @@ type PostItem struct {
}
// Show
// @Router /posts/:id/show [get]
// @Bind id path
// @Bind user local
//
// @Router /posts/:id/show [get]
// @Bind id path
// @Bind user local
func (ctl *posts) Show(ctx fiber.Ctx, id int64, user *model.Users) (*PostItem, error) {
log.Infof("Fetching post with ID: %d", id)
post, err := models.Posts.GetByID(ctx.Context(), id)
@@ -148,9 +150,10 @@ type PlayUrl struct {
}
// Play
// @Router /posts/:id/play [get]
// @Bind id path
// @Bind user local
//
// @Router /posts/:id/play [get]
// @Bind id path
// @Bind user local
func (ctl *posts) Play(ctx fiber.Ctx, id int64, user *model.Users) (*PlayUrl, error) {
log.Infof("Fetching play URL for post ID: %d", id)
post, err := models.Posts.GetByID(ctx.Context(), id)
@@ -181,10 +184,11 @@ func (ctl *posts) Play(ctx fiber.Ctx, id int64, user *model.Users) (*PlayUrl, er
}
// Mine posts
// @Router /posts/mine [get]
// @Bind pagination query
// @Bind query query
// @Bind user local
//
// @Router /posts/mine [get]
// @Bind pagination query
// @Bind query query
// @Bind user local
func (ctl *posts) Mine(ctx fiber.Ctx, pagination *requests.Pagination, query *ListQuery, user *model.Users) (*requests.Pager, error) {
log.Infof("Fetching posts for user with pagination: %+v and keyword: %v", pagination, query.Keyword)
@@ -236,9 +240,10 @@ func (ctl *posts) Mine(ctx fiber.Ctx, pagination *requests.Pagination, query *Li
}
// Buy
// @Router /posts/:id/buy [get]
// @Bind id path
// @Bind user local
//
// @Router /posts/:id/buy [get]
// @Bind id path
// @Bind user local
func (ctl *posts) Buy(ctx fiber.Ctx, id int64, user *model.Users) (*wechat.JSAPIPayParams, error) {
post, err := models.Posts.GetByID(ctx.Context(), id)
if err != nil {

View File

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

View File

@@ -14,11 +14,12 @@ import (
// @provider contracts.HttpRoute atom.GroupRoutes
type Routes struct {
log *log.Entry `inject:"false"`
auth *auth
pays *pays
posts *posts
users *users
log *log.Entry `inject:"false"`
auth *auth
pays *pays
posts *posts
users *users
wechats *wechats
}
func (r *Routes) Prepare() error {
@@ -95,4 +96,11 @@ func (r *Routes) Register(router fiber.Router) {
Body[ProfileForm]("form"),
))
// 注册路由组: wechats
router.Get("/wechats/js-sdk", DataFunc2(
r.wechats.GetJsSDK,
QueryParam[string]("url"),
Local[*model.Users]("user"),
))
}

View File

@@ -19,8 +19,8 @@ type UserInfo struct {
Username string `json:"username,omitempty"`
}
// @Router /users/profile [get]
// @Bind user local
// @Router /users/profile [get]
// @Bind user local
func (ctl *users) Profile(ctx fiber.Ctx, user *model.Users) (*UserInfo, error) {
return &UserInfo{
ID: user.ID,
@@ -34,9 +34,10 @@ type ProfileForm struct {
}
// Update
// @Router /users/username [put]
// @Bind user local
// @Bind form body
//
// @Router /users/username [put]
// @Bind user local
// @Bind form body
func (ctl *users) Update(ctx fiber.Ctx, user *model.Users, form *ProfileForm) error {
username := strings.TrimSpace(form.Username)
if len([]rune(username)) > 12 {

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

@@ -9,20 +9,20 @@ import (
"go.ipao.vip/atom"
)
// @title ApiDoc
// @version 1.0
// @description This is a sample server celler server.
// @termsOfService http://swagger.io/terms/
// @contact.name UserName
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @host localhost:8080
// @BasePath /api/v1
// @securityDefinitions.basic BasicAuth
// @externalDocs.description OpenAPI
// @externalDocs.url https://swagger.io/resources/open-api/
// @title ApiDoc
// @version 1.0
// @description This is a sample server celler server.
// @termsOfService http://swagger.io/terms/
// @contact.name UserName
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @host localhost:8080
// @BasePath /api/v1
// @securityDefinitions.basic BasicAuth
// @externalDocs.description OpenAPI
// @externalDocs.url https://swagger.io/resources/open-api/
func main() {
opts := []atom.Option{
atom.Name("quyun"),

View File

@@ -1,14 +1,24 @@
package wechat
import "math/rand"
import (
"crypto/sha1"
"encoding/hex"
"math/rand"
)
// RandomString generate random size string
func randomString(size int) (string, error) {
func randomString(size int) string {
// generate size string [0-9a-zA-Z]
const chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
b := make([]byte, size)
for i := range b {
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 (
"crypto/sha1"
"encoding/hex"
"fmt"
"net/url"
"sort"
"strings"
@@ -287,3 +288,32 @@ func (we *Client) GetJSTicket(token string) (string, error) {
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",
"pinia": "^3.0.2",
"plyr": "^3.7.8",
"sha1": "^1.1.1",
"tailwindcss": "^4.1.4",
"vue": "^3.5.13",
"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": {
"@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 { useRoute, useRouter } from 'vue-router'
import { postApi } from '../api/postApi'
import { wechatApi } from '../api/wechatApi'
import { useWxSDK } from '../hooks/useWxSDK'
const wx = useWxSDK()
const route = useRoute()
const router = useRouter()
const article = ref(null)
@@ -90,40 +93,13 @@ const fetchArticle = async () => {
article.value = data
document.title = article.value.title
// 定义“分享给朋友”及“分享到QQ”按钮的分享内容
const shareContent = {
// 调用微信 JS SDK 分享接口
wx.setShareInfo({
title: data.title,
desc: data.content,
link: window.location.href,
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) {
console.error('Failed to fetch article:', error)
alert("加载失败!")
@@ -137,6 +113,12 @@ const handleBack = () => {
onMounted(async () => {
await fetchArticle()
initializePlayer()
wechatApi.jsSdk().then(resp => {
wx.initConfig(resp.data)
}).catch(error => {
console.error('Failed to initialize WeChat SDK:', error)
})
})
onUnmounted(() => {