428 lines
11 KiB
Go
428 lines
11 KiB
Go
package app
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"verification-platform-backend/internal/database"
|
|
"verification-platform-backend/internal/model"
|
|
"verification-platform-backend/internal/service"
|
|
"verification-platform-backend/pkg/jwt"
|
|
"verification-platform-backend/pkg/response"
|
|
|
|
"github.com/dop251/goja"
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
func SetupDynamicRoutes(r *gin.RouterGroup) {
|
|
dynamicCode := r.Group("/dynamic-code")
|
|
{
|
|
dynamicCode.POST("/:key/execute", handleExecuteDynamicCode)
|
|
}
|
|
}
|
|
|
|
func handleExecuteDynamicCode(c *gin.Context) {
|
|
var token string
|
|
authHeader := c.GetHeader("Authorization")
|
|
if authHeader != "" {
|
|
parts := strings.SplitN(authHeader, " ", 2)
|
|
if len(parts) == 2 && parts[0] == "Bearer" {
|
|
token = parts[1]
|
|
}
|
|
}
|
|
|
|
if token == "" {
|
|
token = c.Query("token")
|
|
}
|
|
|
|
if token == "" {
|
|
response.Error(c, http.StatusUnauthorized, "Authorization header is required")
|
|
return
|
|
}
|
|
|
|
claims, err := jwt.ParseToken(token)
|
|
if err != nil {
|
|
response.Error(c, http.StatusUnauthorized, "Invalid token")
|
|
return
|
|
}
|
|
|
|
if time.Now().Unix() > claims.ExpiresAt.Unix() {
|
|
response.Error(c, http.StatusUnauthorized, "Token expired")
|
|
return
|
|
}
|
|
|
|
c.Set("user_id", claims.UserID)
|
|
c.Set("username", claims.Username)
|
|
c.Set("role", claims.Role)
|
|
|
|
appKey := c.Param("appKey")
|
|
key := c.Param("key")
|
|
|
|
var app model.Application
|
|
if err := database.DB.Where("app_key = ?", appKey).First(&app).Error; err != nil {
|
|
response.Error(c, 404, "应用不存在")
|
|
return
|
|
}
|
|
|
|
if service.GetApplicationDisabledStatus(app.ID) {
|
|
response.Error(c, 403, "该应用已被禁用")
|
|
return
|
|
}
|
|
|
|
var appUser model.AppUser
|
|
if err := database.DB.Where("id = ? AND application_id = ?", claims.UserID, app.ID).First(&appUser).Error; err != nil {
|
|
response.Error(c, http.StatusForbidden, "无权访问该应用的动态代码")
|
|
return
|
|
}
|
|
|
|
var dynamicCode model.DynamicCode
|
|
if err := database.DB.Where("application_id = ? AND key = ?", app.ID, key).First(&dynamicCode).Error; err != nil {
|
|
response.Error(c, 404, "动态代码不存在")
|
|
return
|
|
}
|
|
|
|
if dynamicCode.Status != "active" {
|
|
response.Error(c, 400, "动态代码未启用")
|
|
return
|
|
}
|
|
|
|
var req struct {
|
|
Params map[string]interface{} `json:"params"`
|
|
UserID *uint `json:"user_id,omitempty"`
|
|
}
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
response.Error(c, 400, "参数错误")
|
|
return
|
|
}
|
|
|
|
startTime := time.Now()
|
|
|
|
vm := goja.New()
|
|
|
|
appData := map[string]interface{}{
|
|
"id": app.ID,
|
|
"name": app.Name,
|
|
"description": app.Description,
|
|
"app_key": app.AppKey,
|
|
"status": app.Status,
|
|
"billing_type": app.BillingType,
|
|
"deduction_mode": app.DeductionMode,
|
|
"deduction_type": app.DeductionType,
|
|
"deduction_interval": app.DeductionInterval,
|
|
"deduction_unit": app.DeductionUnit,
|
|
"deduction_amount": app.DeductionAmount,
|
|
"enable_trial": app.EnableTrial,
|
|
"trial_balance": app.TrialBalance,
|
|
"trial_days": app.TrialDays,
|
|
"enable_free_period": app.EnableFreePeriod,
|
|
"free_period_type": app.FreePeriodType,
|
|
"free_period_start": app.FreePeriodStart,
|
|
"free_period_end": app.FreePeriodEnd,
|
|
"free_period_weekdays": app.FreePeriodWeekdays,
|
|
"free_period_start_time": app.FreePeriodStartTime,
|
|
"free_period_end_time": app.FreePeriodEndTime,
|
|
"max_devices": app.MaxDevices,
|
|
"bind_type": app.BindType,
|
|
"multi_open": app.MultiOpen,
|
|
"multi_open_mode": app.MultiOpenMode,
|
|
"max_instances": app.MaxInstances,
|
|
"login_policy": app.LoginPolicy,
|
|
"max_attempts": app.MaxAttempts,
|
|
"lock_duration": app.LockDuration,
|
|
"heartbeat_interval": app.HeartbeatInterval,
|
|
"heartbeat_timeout": app.HeartbeatTimeout,
|
|
"change_limit": app.ChangeLimit,
|
|
"change_interval": app.ChangeInterval,
|
|
"change_exceed_action": app.ChangeExceedAction,
|
|
"change_deduct_amount": app.ChangeDeductAmount,
|
|
}
|
|
if err := vm.Set("app", appData); err != nil {
|
|
response.Error(c, 500, "应用数据设置失败")
|
|
return
|
|
}
|
|
|
|
var constants []model.CloudConstant
|
|
database.DB.Where("app_id = ? AND status = ?", app.ID, "active").Find(&constants)
|
|
constantsData := make(map[string]interface{})
|
|
for _, c := range constants {
|
|
constantsData[c.Key] = c.Value
|
|
}
|
|
if err := vm.Set("constants", constantsData); err != nil {
|
|
response.Error(c, 500, "云端常量设置失败")
|
|
return
|
|
}
|
|
|
|
var cloudVariables []model.CloudVariable
|
|
database.DB.Where("app_id = ? AND status = ?", app.ID, "active").Find(&cloudVariables)
|
|
appVariables := make(map[string]interface{})
|
|
for _, v := range cloudVariables {
|
|
appVariables[v.Key] = v.DefaultValue
|
|
}
|
|
if err := vm.Set("appVariables", appVariables); err != nil {
|
|
response.Error(c, 500, "云端变量设置失败")
|
|
return
|
|
}
|
|
|
|
userData := map[string]interface{}{}
|
|
subscription := map[string]interface{}{}
|
|
userVariables := map[string]interface{}{}
|
|
devices := []map[string]interface{}{}
|
|
|
|
if req.UserID != nil {
|
|
var user model.AppUser
|
|
if err := database.DB.Where("id = ? AND application_id = ?", *req.UserID, app.ID).First(&user).Error; err == nil {
|
|
userData = map[string]interface{}{
|
|
"id": user.ID,
|
|
"username": user.Username,
|
|
"email": user.Email,
|
|
"status": user.Status,
|
|
"device_id": user.DeviceID,
|
|
"avatar": user.Avatar,
|
|
"created_at": user.CreatedAt,
|
|
"last_login_at": user.LastLoginAt,
|
|
}
|
|
|
|
subscription = map[string]interface{}{
|
|
"balance": user.Balance,
|
|
"is_trial_user": user.IsTrialUser,
|
|
"trial_start_at": user.TrialStartAt,
|
|
"trial_end_at": user.TrialEndAt,
|
|
"expiry_at": user.ExpiryAt,
|
|
"is_expired": user.ExpiryAt != nil && user.ExpiryAt.Before(time.Now()),
|
|
"is_lifetime": user.Balance == -1,
|
|
"days_remaining": calculateDaysRemaining(user.ExpiryAt, user.Balance),
|
|
}
|
|
|
|
var userDevices []model.UserDevice
|
|
database.DB.Where("user_id = ? AND application_id = ?", user.ID, app.ID).Find(&userDevices)
|
|
for _, d := range userDevices {
|
|
devices = append(devices, map[string]interface{}{
|
|
"id": d.ID,
|
|
"device_id": d.DeviceID,
|
|
"device_name": d.DeviceName,
|
|
"device_type": d.DeviceType,
|
|
"status": d.Status,
|
|
"created_at": d.CreatedAt,
|
|
})
|
|
}
|
|
|
|
var userVars []model.UserVariable
|
|
database.DB.Where("user_id = ? AND app_id = ?", user.ID, app.ID).Find(&userVars)
|
|
for _, v := range userVars {
|
|
userVariables[v.VarName] = v.VarValue
|
|
}
|
|
}
|
|
}
|
|
|
|
if err := vm.Set("user", userData); err != nil {
|
|
response.Error(c, 500, "用户数据设置失败")
|
|
return
|
|
}
|
|
if err := vm.Set("subscription", subscription); err != nil {
|
|
response.Error(c, 500, "订阅数据设置失败")
|
|
return
|
|
}
|
|
if err := vm.Set("userVariables", userVariables); err != nil {
|
|
response.Error(c, 500, "用户变量设置失败")
|
|
return
|
|
}
|
|
if err := vm.Set("devices", devices); err != nil {
|
|
response.Error(c, 500, "设备数据设置失败")
|
|
return
|
|
}
|
|
|
|
for k, v := range req.Params {
|
|
if err := vm.Set(k, v); err != nil {
|
|
response.Error(c, 500, "参数设置失败")
|
|
return
|
|
}
|
|
}
|
|
|
|
if err := vm.Set("params", req.Params); err != nil {
|
|
response.Error(c, 500, "参数设置失败")
|
|
return
|
|
}
|
|
|
|
value, err := vm.RunString("(function() { " + dynamicCode.Code + " })()")
|
|
if err != nil {
|
|
response.Error(c, 400, "代码执行错误: "+err.Error())
|
|
return
|
|
}
|
|
|
|
executionTime := time.Since(startTime).Milliseconds()
|
|
|
|
result := value.Export()
|
|
|
|
if actionMap, ok := result.(map[string]interface{}); ok {
|
|
if action, hasAction := actionMap["action"]; hasAction {
|
|
switch action {
|
|
case "extend_time":
|
|
if err := handleExtendTime(app.ID, actionMap); err != nil {
|
|
response.Error(c, 500, "执行加时操作失败: "+err.Error())
|
|
return
|
|
}
|
|
case "deduct_points":
|
|
if err := handleDeductPoints(app.ID, actionMap); err != nil {
|
|
response.Error(c, 500, "执行扣点操作失败: "+err.Error())
|
|
return
|
|
}
|
|
case "update_user_variable":
|
|
appID := app.ID
|
|
if err := handleUpdateUserVariable(&appID, req.UserID, actionMap); err != nil {
|
|
response.Error(c, 500, "更新用户变量失败: "+err.Error())
|
|
return
|
|
}
|
|
case "update_app_variable":
|
|
if err := handleUpdateAppVariable(app.ID, actionMap); err != nil {
|
|
response.Error(c, 500, "更新应用变量失败: "+err.Error())
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
response.Success(c, gin.H{
|
|
"result": result,
|
|
"execution_time": executionTime,
|
|
})
|
|
}
|
|
|
|
func calculateDaysRemaining(expiryAt *time.Time, balance float64) int {
|
|
if balance == -1 {
|
|
return -1
|
|
}
|
|
if expiryAt == nil {
|
|
return 0
|
|
}
|
|
remaining := int(time.Until(*expiryAt).Hours() / 24)
|
|
if remaining < 0 {
|
|
return 0
|
|
}
|
|
return remaining
|
|
}
|
|
|
|
func handleExtendTime(appID uint, actionMap map[string]interface{}) error {
|
|
userID, ok := actionMap["user_id"].(float64)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
days, ok := actionMap["days"].(float64)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
var user model.AppUser
|
|
if err := database.DB.Where("id = ? AND application_id = ?", uint(userID), appID).First(&user).Error; err != nil {
|
|
return err
|
|
}
|
|
|
|
if user.Balance == -1 {
|
|
return nil
|
|
}
|
|
|
|
user.Balance += float64(days)
|
|
|
|
return database.DB.Save(&user).Error
|
|
}
|
|
|
|
func handleDeductPoints(appID uint, actionMap map[string]interface{}) error {
|
|
userID, ok := actionMap["user_id"].(float64)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
points, ok := actionMap["points"].(float64)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
var user model.AppUser
|
|
if err := database.DB.Where("id = ? AND application_id = ?", uint(userID), appID).First(&user).Error; err != nil {
|
|
return err
|
|
}
|
|
|
|
if user.Balance == -1 {
|
|
return nil
|
|
}
|
|
|
|
user.Balance -= points
|
|
if user.Balance < 0 {
|
|
user.Balance = 0
|
|
}
|
|
|
|
return database.DB.Save(&user).Error
|
|
}
|
|
|
|
func handleUpdateUserVariable(appID *uint, userID *uint, actionMap map[string]interface{}) error {
|
|
if userID == nil {
|
|
return nil
|
|
}
|
|
|
|
varName, ok := actionMap["name"].(string)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
varValue, ok := actionMap["value"].(string)
|
|
if !ok {
|
|
if v, ok := actionMap["value"]; ok {
|
|
varValue = toString(v)
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
var userVar model.UserVariable
|
|
if err := database.DB.Where("user_id = ? AND app_id = ? AND var_name = ?", *userID, appID, varName).First(&userVar).Error; err != nil {
|
|
userVar = model.UserVariable{
|
|
UserID: *userID,
|
|
AppID: *appID,
|
|
VarName: varName,
|
|
VarValue: varValue,
|
|
}
|
|
return database.DB.Create(&userVar).Error
|
|
}
|
|
|
|
userVar.VarValue = varValue
|
|
return database.DB.Save(&userVar).Error
|
|
}
|
|
|
|
func handleUpdateAppVariable(appID uint, actionMap map[string]interface{}) error {
|
|
varName, ok := actionMap["name"].(string)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
varValue, ok := actionMap["value"].(string)
|
|
if !ok {
|
|
if v, ok := actionMap["value"]; ok {
|
|
varValue = toString(v)
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
var appVar model.CloudVariable
|
|
if err := database.DB.Where("app_id = ? AND key = ?", appID, varName).First(&appVar).Error; err != nil {
|
|
return err
|
|
}
|
|
|
|
appVar.DefaultValue = varValue
|
|
return database.DB.Save(&appVar).Error
|
|
}
|
|
|
|
func toString(v interface{}) string {
|
|
switch val := v.(type) {
|
|
case string:
|
|
return val
|
|
case float64:
|
|
return string(rune(int(val)))
|
|
case int:
|
|
return string(rune(val))
|
|
default:
|
|
return ""
|
|
}
|
|
}
|