init: reinitialize from gin-template

This commit is contained in:
JustSong
2022-11-11 15:35:02 +08:00
parent 4063fe8c31
commit f1d5d9c8d1
129 changed files with 5629 additions and 2629 deletions
-25
View File
@@ -1,25 +0,0 @@
const WebSocket = require('ws');
const { tokenStore } = require('./token');
async function pushClientMessage(userPrefix, message) {
let user = tokenStore.get(userPrefix);
if (!user || !user.ws || user.ws.readyState !== WebSocket.OPEN) {
return {
success: false,
message: `客户端未连接`,
};
}
let data = {
title: message.title,
description: message.description,
};
user.ws.send(JSON.stringify(data));
return {
success: true,
message: '消息已发送至客户端',
};
}
module.exports = {
pushClientMessage,
};
+80
View File
@@ -0,0 +1,80 @@
package common
import (
"github.com/google/uuid"
"sync"
"time"
)
var StartTime = time.Now().Unix() // unit: second
var Version = "v0.0.0"
var SystemName = "消息推送服务"
var ServerAddress = "http://localhost:3000"
var Footer = ""
// Any options with "Secret", "Token" in its key won't be return by GetOptions
var SessionSecret = uuid.New().String()
var SQLitePath = ".message-pusher.db"
var OptionMap map[string]string
var OptionMapRWMutex sync.RWMutex
var ItemsPerPage = 10
var PasswordLoginEnabled = true
var PasswordRegisterEnabled = true
var EmailVerificationEnabled = false
var GitHubOAuthEnabled = false
var WeChatAuthEnabled = false
var SMTPServer = ""
var SMTPAccount = ""
var SMTPToken = ""
var GitHubClientId = ""
var GitHubClientSecret = ""
var WeChatServerAddress = ""
var WeChatServerToken = ""
var WeChatAccountQRCodeImageURL = ""
const (
RoleGuestUser = 0
RoleCommonUser = 1
RoleAdminUser = 10
RoleRootUser = 100
)
var (
FileUploadPermission = RoleGuestUser
FileDownloadPermission = RoleGuestUser
ImageUploadPermission = RoleGuestUser
ImageDownloadPermission = RoleGuestUser
)
// All duration's unit is seconds
// Shouldn't larger then RateLimitKeyExpirationDuration
var (
GlobalApiRateLimitNum = 60
GlobalApiRateLimitDuration int64 = 3 * 60
GlobalWebRateLimitNum = 60
GlobalWebRateLimitDuration int64 = 3 * 60
UploadRateLimitNum = 10
UploadRateLimitDuration int64 = 60
DownloadRateLimitNum = 10
DownloadRateLimitDuration int64 = 60
CriticalRateLimitNum = 20
CriticalRateLimitDuration int64 = 20 * 60
)
var RateLimitKeyExpirationDuration = 20 * time.Minute
const (
UserStatusEnabled = 1 // don't use 0, 0 is the default value!
UserStatusDisabled = 2 // also don't use 0
)
+14
View File
@@ -0,0 +1,14 @@
package common
import "golang.org/x/crypto/bcrypt"
func Password2Hash(password string) (string, error) {
passwordBytes := []byte(password)
hashedPassword, err := bcrypt.GenerateFromPassword(passwordBytes, bcrypt.DefaultCost)
return string(hashedPassword), err
}
func ValidatePasswordAndHash(password string, hash string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}
-9
View File
@@ -1,9 +0,0 @@
const { Sequelize } = require('sequelize');
const sequelize = new Sequelize({
dialect: 'sqlite',
storage: 'data.db',
logging: false,
});
module.exports = sequelize;
+14
View File
@@ -0,0 +1,14 @@
package common
import "gopkg.in/gomail.v2"
func SendEmail(subject string, receiver string, content string) error {
m := gomail.NewMessage()
m.SetHeader("From", SMTPAccount)
m.SetHeader("To", receiver)
m.SetHeader("Subject", subject)
m.SetBody("text/html", content)
d := gomail.NewDialer(SMTPServer, 587, SMTPAccount, SMTPToken)
err := d.DialAndSend(m)
return err
}
-51
View File
@@ -1,51 +0,0 @@
const nodemailer = require('nodemailer');
const { tokenStore } = require('./token');
const config = require('../config');
const { md2html } = require('./utils');
async function pushEmailMessage(userPrefix, message) {
let user = tokenStore.get(userPrefix);
if (!user) {
return {
success: false,
message: `不存在的前缀:${userPrefix},请注意大小写`,
};
}
let transporter = nodemailer.createTransport({
host: user.smtpServer,
secure: true,
auth: {
user: user.smtpUser,
pass: user.smtpPass,
},
});
let targetEmail = user.email;
if (message.email) {
targetEmail = message.email;
}
try {
await transporter.sendMail({
from: `"消息推送服务" <${user.smtpUser}>`,
to: targetEmail,
subject: message.description,
text: message.content,
html: md2html(message.content),
});
return {
success: true,
message: 'ok',
};
} catch (e) {
console.error(e);
return {
success: false,
message: e.message,
};
}
}
module.exports = {
pushEmailMessage,
};
+32
View File
@@ -0,0 +1,32 @@
package common
import (
"embed"
"github.com/gin-gonic/contrib/static"
"io/fs"
"net/http"
)
// Credit: https://github.com/gin-contrib/static/issues/19
type embedFileSystem struct {
http.FileSystem
}
func (e embedFileSystem) Exists(prefix string, path string) bool {
_, err := e.Open(path)
if err != nil {
return false
}
return true
}
func EmbedFolder(fsEmbed embed.FS, targetPath string) static.ServeFileSystem {
efs, err := fs.Sub(fsEmbed, targetPath)
if err != nil {
panic(err)
}
return embedFileSystem{
FileSystem: http.FS(efs),
}
}
+78
View File
@@ -0,0 +1,78 @@
package common
import (
"flag"
"fmt"
"log"
"os"
"path/filepath"
)
var (
Port = flag.Int("port", 3000, "the listening port")
PrintVersion = flag.Bool("version", false, "print version and exit")
LogDir = flag.String("log-dir", "", "specify the log directory")
//Host = flag.String("host", "localhost", "the server's ip address or domain")
//Path = flag.String("path", "", "specify a local path to public")
//VideoPath = flag.String("video", "", "specify a video folder to public")
//NoBrowser = flag.Bool("no-browser", false, "open browser or not")
)
// UploadPath Maybe override by ENV_VAR
var UploadPath = "upload"
//var ExplorerRootPath = UploadPath
//var ImageUploadPath = "upload/images"
//var VideoServePath = "upload"
func init() {
flag.Parse()
if *PrintVersion {
fmt.Println(Version)
os.Exit(0)
}
if os.Getenv("SESSION_SECRET") != "" {
SessionSecret = os.Getenv("SESSION_SECRET")
}
if os.Getenv("SQLITE_PATH") != "" {
SQLitePath = os.Getenv("SQLITE_PATH")
}
if os.Getenv("UPLOAD_PATH") != "" {
UploadPath = os.Getenv("UPLOAD_PATH")
//ExplorerRootPath = UploadPath
//ImageUploadPath = path.Join(UploadPath, "images")
//VideoServePath = UploadPath
}
if *LogDir != "" {
var err error
*LogDir, err = filepath.Abs(*LogDir)
if err != nil {
log.Fatal(err)
}
if _, err := os.Stat(*LogDir); os.IsNotExist(err) {
err = os.Mkdir(*LogDir, 0777)
if err != nil {
log.Fatal(err)
}
}
}
//if *Path != "" {
// ExplorerRootPath = *Path
//}
//if *VideoPath != "" {
// VideoServePath = *VideoPath
//}
//
//ExplorerRootPath, _ = filepath.Abs(ExplorerRootPath)
//VideoServePath, _ = filepath.Abs(VideoServePath)
//ImageUploadPath, _ = filepath.Abs(ImageUploadPath)
//
if _, err := os.Stat(UploadPath); os.IsNotExist(err) {
_ = os.Mkdir(UploadPath, 0777)
}
//if _, err := os.Stat(ImageUploadPath); os.IsNotExist(err) {
// _ = os.Mkdir(ImageUploadPath, 0777)
//}
}
+39
View File
@@ -0,0 +1,39 @@
package common
import (
"fmt"
"github.com/gin-gonic/gin"
"io"
"log"
"os"
"path/filepath"
"time"
)
func SetupGinLog() {
if *LogDir != "" {
commonLogPath := filepath.Join(*LogDir, "common.log")
errorLogPath := filepath.Join(*LogDir, "error.log")
commonFd, err := os.OpenFile(commonLogPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Fatal("failed to open log file")
}
errorFd, err := os.OpenFile(errorLogPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Fatal("failed to open log file")
}
gin.DefaultWriter = io.MultiWriter(os.Stdout, commonFd)
gin.DefaultErrorWriter = io.MultiWriter(os.Stderr, errorFd)
}
}
func SysLog(s string) {
t := time.Now()
_, _ = fmt.Fprintf(gin.DefaultWriter, "[SYS] %v | %s \n", t.Format("2006/01/02 - 15:04:05"), s)
}
func FatalLog(v ...any) {
t := time.Now()
_, _ = fmt.Fprintf(gin.DefaultErrorWriter, "[FATAL] %v | %v \n", t.Format("2006/01/02 - 15:04:05"), v)
os.Exit(1)
}
-62
View File
@@ -1,62 +0,0 @@
const {
getUserDefaultMethod,
checkAccessToken,
checkPrefix,
} = require('./token');
const { pushWeChatMessage } = require('./wechat');
const { pushWeChatCorpMessage } = require('./wechat-corp');
const { pushEmailMessage } = require('./email');
const { pushClientMessage } = require('./client');
const { Message } = require('../models');
async function processMessage(userPrefix, message) {
if (!checkPrefix(userPrefix)) {
return {
success: false,
message: `不存在的用户前缀:${userPrefix}`,
};
}
if (!checkAccessToken(userPrefix, message.token)) {
return {
success: false,
message: `无效的访问凭证,请检查 token 参数是否正确`,
};
}
if (message.email) {
// If message has the attribute "email", override its type.
message.type = 'email';
}
if (!message.type) {
message.type = getUserDefaultMethod(userPrefix);
}
if (message.content && message.type !== 'email') {
// If message is not email type, we should save it because we have to serve the page.
message = await Message.create(message, { raw: true });
}
let result;
switch (message.type) {
case 'test': // WeChat message
result = await pushWeChatMessage(userPrefix, message);
break;
case 'email': // Email message
result = await pushEmailMessage(userPrefix, message);
break;
case 'corp': // WeChat corp message
result = await pushWeChatCorpMessage(userPrefix, message);
break;
case 'client': // Client message
result = await pushClientMessage(userPrefix, message);
break;
default:
result = {
success: false,
message: `不支持的消息类型:${message.type}`,
};
break;
}
return result;
}
module.exports = {
processMessage,
};
+70
View File
@@ -0,0 +1,70 @@
package common
import (
"sync"
"time"
)
type InMemoryRateLimiter struct {
store map[string]*[]int64
mutex sync.Mutex
expirationDuration time.Duration
}
func (l *InMemoryRateLimiter) Init(expirationDuration time.Duration) {
if l.store == nil {
l.mutex.Lock()
if l.store == nil {
l.store = make(map[string]*[]int64)
l.expirationDuration = expirationDuration
if expirationDuration > 0 {
go l.clearExpiredItems()
}
}
l.mutex.Unlock()
}
}
func (l *InMemoryRateLimiter) clearExpiredItems() {
for {
time.Sleep(l.expirationDuration)
l.mutex.Lock()
now := time.Now().Unix()
for key := range l.store {
queue := l.store[key]
size := len(*queue)
if size == 0 || now-(*queue)[size-1] > int64(l.expirationDuration.Seconds()) {
delete(l.store, key)
}
}
l.mutex.Unlock()
}
}
// Request parameter duration's unit is seconds
func (l *InMemoryRateLimiter) Request(key string, maxRequestNum int, duration int64) bool {
l.mutex.Lock()
defer l.mutex.Unlock()
// [old <-- new]
queue, ok := l.store[key]
now := time.Now().Unix()
if ok {
if len(*queue) < maxRequestNum {
*queue = append(*queue, now)
return true
} else {
if now-(*queue)[0] >= duration {
*queue = (*queue)[1:]
*queue = append(*queue, now)
return true
} else {
return false
}
}
} else {
s := make([]int64, 0, maxRequestNum)
l.store[key] = &s
*(l.store[key]) = append(*(l.store[key]), now)
}
return true
}
+39
View File
@@ -0,0 +1,39 @@
package common
import (
"context"
"github.com/go-redis/redis/v8"
"os"
"time"
)
var RDB *redis.Client
var RedisEnabled = true
// InitRedisClient This function is called after init()
func InitRedisClient() (err error) {
if os.Getenv("REDIS_CONN_STRING") == "" {
RedisEnabled = false
SysLog("REDIS_CONN_STRING not set, Redis is not enabled")
return nil
}
opt, err := redis.ParseURL(os.Getenv("REDIS_CONN_STRING"))
if err != nil {
panic(err)
}
RDB = redis.NewClient(opt)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_, err = RDB.Ping(ctx).Result()
return err
}
func ParseRedisOption() *redis.Options {
opt, err := redis.ParseURL(os.Getenv("REDIS_CONN_STRING"))
if err != nil {
panic(err)
}
return opt
}
-109
View File
@@ -1,109 +0,0 @@
const { User } = require('../models');
const tokenStore = new Map();
async function initializeTokenStore() {
let users = [];
if (process.env.MODE === '1') {
console.log('Current mode is Heroku mode.');
let user = {
// Common
prefix: process.env.PREFIX,
accessToken: process.env.ACCESS_TOKEN,
defaultMethod: process.env.DEFAULT_METHOD,
// WeChat public account
wechatAppId: process.env.WECHAT_APP_ID,
wechatAppSecret: process.env.WECHAT_APP_SECRET,
wechatTemplateId: process.env.WECHAT_TEMPLATE_ID,
wechatOpenId: process.env.WECHAT_OPEN_ID,
wechatVerifyToken: process.env.WECHAT_VERIFY_TOKEN,
// Email
email: process.env.EMAIL,
smtpServer: process.env.SMTP_SERVER,
smtpUser: process.env.SMTP_USER,
smtpPass: process.env.SMTP_PASS,
// WeChat corporation account
corpId: process.env.CORP_ID,
corpAgentId: process.env.CORP_AGENT_ID,
corpAppSecret: process.env.CORP_APP_SECRET,
corpUserId: process.env.CORP_USER_ID,
};
users.push(user);
} else {
users = await User.findAll({
raw: true,
});
}
users.forEach((user) => {
if (user.prefix) {
tokenStore.set(user.prefix, {
// Common
accessToken: user.accessToken,
defaultMethod: user.defaultMethod,
// WeChat test account
wechatAppId: user.wechatAppId,
wechatAppSecret: user.wechatAppSecret,
wechatTemplateId: user.wechatTemplateId,
wechatOpenId: user.wechatOpenId,
wechatVerifyToken: user.wechatVerifyToken,
wechatToken: '',
// Email
email: user.email,
smtpServer: user.smtpServer,
smtpUser: user.smtpUser,
smtpPass: user.smtpPass,
// WeChat corporation account
corpId: user.corpId,
corpAgentId: user.corpAgentId,
corpAppSecret: user.corpAppSecret,
corpUserId: user.corpUserId,
corpToken: '',
});
}
});
console.log('Token store initialized.');
}
function updateTokenStore(prefix, key, value) {
let user = tokenStore.get(prefix);
user[key] = value;
tokenStore.set(prefix, user);
}
function getUserDefaultMethod(prefix) {
let user = tokenStore.get(prefix);
return user.defaultMethod;
}
function checkAccessToken(prefix, token) {
let user = tokenStore.get(prefix);
if (user.accessToken === '') {
return true;
} else {
return user.accessToken === token;
}
}
function checkPrefix(prefix) {
let user = tokenStore.get(prefix);
return user !== undefined;
}
function registerWebSocket(prefix, token, ws) {
let user = tokenStore.get(prefix);
if (user && user.accessToken === token) {
updateTokenStore(prefix, 'ws', ws);
} else {
ws.terminate();
}
}
module.exports = {
initializeTokenStore,
updateTokenStore,
getUserDefaultMethod,
tokenStore,
checkAccessToken,
checkPrefix,
registerWebSocket,
};
+133
View File
@@ -0,0 +1,133 @@
package common
import (
"fmt"
"github.com/google/uuid"
"html/template"
"log"
"net"
"os/exec"
"runtime"
"strconv"
"strings"
)
func OpenBrowser(url string) {
var err error
switch runtime.GOOS {
case "linux":
err = exec.Command("xdg-open", url).Start()
case "windows":
err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
case "darwin":
err = exec.Command("open", url).Start()
}
if err != nil {
log.Println(err)
}
}
func GetIp() (ip string) {
ips, err := net.InterfaceAddrs()
if err != nil {
log.Println(err)
return ip
}
for _, a := range ips {
if ipNet, ok := a.(*net.IPNet); ok && !ipNet.IP.IsLoopback() {
if ipNet.IP.To4() != nil {
ip = ipNet.IP.String()
if strings.HasPrefix(ip, "10") {
return
}
if strings.HasPrefix(ip, "172") {
return
}
if strings.HasPrefix(ip, "192.168") {
return
}
ip = ""
}
}
}
return
}
var sizeKB = 1024
var sizeMB = sizeKB * 1024
var sizeGB = sizeMB * 1024
func Bytes2Size(num int64) string {
numStr := ""
unit := "B"
if num/int64(sizeGB) > 1 {
numStr = fmt.Sprintf("%.2f", float64(num)/float64(sizeGB))
unit = "GB"
} else if num/int64(sizeMB) > 1 {
numStr = fmt.Sprintf("%d", int(float64(num)/float64(sizeMB)))
unit = "MB"
} else if num/int64(sizeKB) > 1 {
numStr = fmt.Sprintf("%d", int(float64(num)/float64(sizeKB)))
unit = "KB"
} else {
numStr = fmt.Sprintf("%d", num)
}
return numStr + " " + unit
}
func Seconds2Time(num int) (time string) {
if num/31104000 > 0 {
time += strconv.Itoa(num/31104000) + " 年 "
num %= 31104000
}
if num/2592000 > 0 {
time += strconv.Itoa(num/2592000) + " 个月 "
num %= 2592000
}
if num/86400 > 0 {
time += strconv.Itoa(num/86400) + " 天 "
num %= 86400
}
if num/3600 > 0 {
time += strconv.Itoa(num/3600) + " 小时 "
num %= 3600
}
if num/60 > 0 {
time += strconv.Itoa(num/60) + " 分钟 "
num %= 60
}
time += strconv.Itoa(num) + " 秒"
return
}
func Interface2String(inter interface{}) string {
switch inter.(type) {
case string:
return inter.(string)
case int:
return fmt.Sprintf("%d", inter.(int))
case float64:
return fmt.Sprintf("%f", inter.(float64))
}
return "Not Implemented"
}
func UnescapeHTML(x string) interface{} {
return template.HTML(x)
}
func IntMax(a int, b int) int {
if a >= b {
return a
} else {
return b
}
}
func GetUUID() string {
code := uuid.New().String()
code = strings.Replace(code, "-", "", -1)
return code
}
-13
View File
@@ -1,13 +0,0 @@
const lexer = require('marked').lexer;
const parser = require('marked').parser;
function md2html(markdown) {
if (markdown) {
return parser(lexer(markdown));
}
return markdown;
}
module.exports = {
md2html,
};
+9
View File
@@ -0,0 +1,9 @@
package common
import "github.com/go-playground/validator/v10"
var Validate *validator.Validate
func init() {
Validate = validator.New()
}
+77
View File
@@ -0,0 +1,77 @@
package common
import (
"github.com/google/uuid"
"strings"
"sync"
"time"
)
type verificationValue struct {
code string
time time.Time
}
const (
EmailVerificationPurpose = "v"
PasswordResetPurpose = "r"
)
var verificationMutex sync.Mutex
var verificationMap map[string]verificationValue
var verificationMapMaxSize = 10
var VerificationValidMinutes = 10
func GenerateVerificationCode(length int) string {
code := uuid.New().String()
code = strings.Replace(code, "-", "", -1)
if length == 0 {
return code
}
return code[:length]
}
func RegisterVerificationCodeWithKey(key string, code string, purpose string) {
verificationMutex.Lock()
defer verificationMutex.Unlock()
verificationMap[purpose+key] = verificationValue{
code: code,
time: time.Now(),
}
if len(verificationMap) > verificationMapMaxSize {
removeExpiredPairs()
}
}
func VerifyCodeWithKey(key string, code string, purpose string) bool {
verificationMutex.Lock()
defer verificationMutex.Unlock()
value, okay := verificationMap[purpose+key]
now := time.Now()
if !okay || int(now.Sub(value.time).Seconds()) >= VerificationValidMinutes*60 {
return false
}
return code == value.code
}
func DeleteKey(key string, purpose string) {
verificationMutex.Lock()
defer verificationMutex.Unlock()
delete(verificationMap, purpose+key)
}
// no lock inside!
func removeExpiredPairs() {
now := time.Now()
for key := range verificationMap {
if int(now.Sub(verificationMap[key].time).Seconds()) >= VerificationValidMinutes*60 {
delete(verificationMap, key)
}
}
}
func init() {
verificationMutex.Lock()
defer verificationMutex.Unlock()
verificationMap = make(map[string]verificationValue)
}
-100
View File
@@ -1,100 +0,0 @@
const axios = require('axios');
const { tokenStore, updateTokenStore } = require('./token');
const config = require('../config');
async function refreshToken() {
for (let [key, value] of tokenStore) {
if (value.corpId) {
value.corpToken = await requestToken(value.corpId, value.corpAppSecret);
tokenStore.set(key, value);
}
}
console.log('Token refreshed.');
}
async function requestToken(corpId, corpAppSecret) {
// Reference: https://work.weixin.qq.com/api/doc/90000/90135/91039
let token = '';
try {
let res = await axios.get(
`https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=${corpId}&corpsecret=${corpAppSecret}`
);
// console.debug(res);
if (res && res.data) {
if (res.data.access_token) {
token = res.data.access_token;
} else {
console.error(res.data);
}
}
} catch (e) {
console.error(e);
}
return token;
}
async function pushWeChatCorpMessage(userPrefix, message) {
// Reference: https://work.weixin.qq.com/api/doc/90000/90135/90236
let user = tokenStore.get(userPrefix);
if (!user) {
return {
success: false,
message: `不存在的前缀:${userPrefix},请注意大小写`,
};
}
let access_token = user.corpToken;
let request_data = {
msgtype: 'textcard',
touser: user.corpUserId,
agentid: user.corpAgentId,
textcard: {
title: message.title,
description: message.description,
},
};
if (message.content) {
request_data.textcard.url = `${config.href}message/${message.id}`;
} else {
request_data.textcard.url = `${config.href}`;
}
let requestUrl = `https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=${access_token}`;
try {
let response = await axios.post(requestUrl, request_data);
if (response && response.data && response.data.errcode !== 0) {
// Failed to push message, get a new token and try again.
access_token = await requestToken(user.corpId, user.corpAppSecret);
updateTokenStore(userPrefix, 'corpToken', access_token);
requestUrl = `https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=${access_token}`;
response = await axios.post(requestUrl, request_data);
}
if (response.data.errcode === 0) {
return {
success: true,
message: 'ok',
};
} else {
return {
success: false,
message: response.data.errmsg,
};
}
} catch (e) {
console.error(e);
let msg = e.message;
if (msg.startsWith('access_token missing')) {
msg = '请求微信服务器失败,请检查配置是否正确或重试!';
}
return {
success: false,
message: msg,
};
}
}
module.exports = {
refreshToken,
requestToken,
pushWeChatCorpMessage,
};
-90
View File
@@ -1,90 +0,0 @@
const axios = require('axios');
const { tokenStore, updateTokenStore } = require('./token');
const config = require('../config');
async function refreshToken() {
for (let [key, value] of tokenStore) {
if (value.wechatAppId) {
value.wechatToken = await requestToken(
value.wechatAppId,
value.wechatAppSecret
);
tokenStore.set(key, value);
}
}
console.log('Token refreshed.');
}
async function requestToken(appId, appSecret) {
let token = '';
try {
let res = await axios.get(
`https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appId}&secret=${appSecret}`
);
// console.debug(res);
if (res && res.data) {
if (res.data.access_token) {
token = res.data.access_token;
} else {
console.error(res.data);
}
}
} catch (e) {
console.error(e);
}
return token;
}
async function pushWeChatMessage(userPrefix, message) {
// Reference: https://mp.weixin.qq.com/debug/cgi-bin/readtmpl?t=tmplmsg/faq_tmpl
let user = tokenStore.get(userPrefix);
if (!user) {
return {
success: false,
message: `不存在的前缀:${userPrefix},请注意大小写`,
};
}
let access_token = user.wechatToken;
let request_data = {
touser: user.wechatOpenId,
template_id: user.wechatTemplateId,
};
if (message.content) {
request_data.url = `${config.href}message/${message.id}`;
}
request_data.data = { text: { value: message.description } };
let requestUrl = `https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=${access_token}`;
try {
let response = await axios.post(requestUrl, request_data);
if (response && response.data && response.data.errcode !== 0) {
// Failed to push message, get a new token and try again.
let token = await requestToken(user.wechatAppId, user.wechatAppSecret);
updateTokenStore(userPrefix, 'wechatToken', token);
requestUrl = `https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=${access_token}`;
response = await axios.post(requestUrl, request_data);
}
if (response.data.errcode === 0) {
return {
success: true,
message: 'ok',
};
} else {
return {
success: false,
message: response.data.errmsg,
};
}
} catch (e) {
console.error(e);
return {
success: false,
message: e.message,
};
}
}
module.exports = {
refreshToken,
requestToken,
pushWeChatMessage,
};