- 管理端大文件分片上传与 sessionStorage 续传;Nginx 大请求体/超时 - .chunk-uploads 定期清扫;system_config 后台配置保留时长与扫描间隔 - 宇恒云 POST /register 对接与 yuheng_cloud_register_records 留痕;yuheng_cloud:manage 权限 Made-with: Cursor
183 lines
5.3 KiB
Go
183 lines
5.3 KiB
Go
package handlers
|
||
|
||
import (
|
||
"bytes"
|
||
"context"
|
||
"encoding/json"
|
||
"fmt"
|
||
"io"
|
||
"net/http"
|
||
"os"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
|
||
"yh_web/server/config"
|
||
"yh_web/server/models"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
"go.mongodb.org/mongo-driver/v2/bson"
|
||
"go.mongodb.org/mongo-driver/v2/mongo/options"
|
||
)
|
||
|
||
const yuhengCloudRegisterColl = "yuheng_cloud_register_records"
|
||
|
||
func cloudRegisterURL() string {
|
||
u := strings.TrimSpace(os.Getenv("YH_CLOUD_REGISTER_URL"))
|
||
if u != "" {
|
||
return strings.TrimSuffix(u, "/")
|
||
}
|
||
return "http://www.cloud.yuxindazhineng.com:3001/register"
|
||
}
|
||
|
||
// YuhengCloudRegisterInput 与云端 POST /register 一致;email 仅用于调用云端,不写入 Mongo
|
||
type YuhengCloudRegisterInput struct {
|
||
Username string `json:"username" binding:"required"`
|
||
Password string `json:"password" binding:"required"`
|
||
Email string `json:"email" binding:"required"`
|
||
}
|
||
|
||
type cloudRegisterPayload struct {
|
||
Username string `json:"username"`
|
||
Password string `json:"password"`
|
||
Email string `json:"email"`
|
||
}
|
||
|
||
func postCloudRegister(ctx context.Context, payload cloudRegisterPayload) (int, string, error) {
|
||
body, err := json.Marshal(payload)
|
||
if err != nil {
|
||
return 0, "", err
|
||
}
|
||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, cloudRegisterURL(), bytes.NewReader(body))
|
||
if err != nil {
|
||
return 0, "", err
|
||
}
|
||
req.Header.Set("Content-Type", "application/json")
|
||
client := &http.Client{Timeout: 45 * time.Second}
|
||
resp, err := client.Do(req)
|
||
if err != nil {
|
||
return 0, "", err
|
||
}
|
||
defer resp.Body.Close()
|
||
b, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))
|
||
return resp.StatusCode, strings.TrimSpace(string(b)), nil
|
||
}
|
||
|
||
// CreateYuhengCloudRegister 调用云端注册接口,成功后在 Mongo 写入一条记录(仅 username、password)
|
||
func CreateYuhengCloudRegister(c *gin.Context) {
|
||
var in YuhengCloudRegisterInput
|
||
if err := c.ShouldBindJSON(&in); err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "请填写用户名、密码与邮箱(邮箱仅提交云端)"})
|
||
return
|
||
}
|
||
in.Username = strings.TrimSpace(in.Username)
|
||
in.Password = strings.TrimSpace(in.Password)
|
||
in.Email = strings.TrimSpace(in.Email)
|
||
if in.Username == "" || in.Password == "" || in.Email == "" {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "用户名、密码、邮箱不能为空"})
|
||
return
|
||
}
|
||
|
||
ctx, cancel := context.WithTimeout(c.Request.Context(), 50*time.Second)
|
||
defer cancel()
|
||
|
||
status, bodySnippet, err := postCloudRegister(ctx, cloudRegisterPayload{
|
||
Username: in.Username,
|
||
Password: in.Password,
|
||
Email: in.Email,
|
||
})
|
||
if err != nil {
|
||
c.JSON(http.StatusBadGateway, gin.H{"error": "调用云端注册失败: " + err.Error()})
|
||
return
|
||
}
|
||
if status < 200 || status >= 300 {
|
||
msg := bodySnippet
|
||
if len(msg) > 500 {
|
||
msg = msg[:500] + "…"
|
||
}
|
||
if msg == "" {
|
||
msg = http.StatusText(status)
|
||
}
|
||
c.JSON(http.StatusBadGateway, gin.H{"error": fmt.Sprintf("云端返回 %d: %s", status, msg)})
|
||
return
|
||
}
|
||
|
||
db := config.GetDB(config.DBName)
|
||
if db == nil {
|
||
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "数据库未连接,未写入本地记录"})
|
||
return
|
||
}
|
||
insCtx, insCancel := context.WithTimeout(context.Background(), 8*time.Second)
|
||
defer insCancel()
|
||
doc := bson.M{
|
||
"username": in.Username,
|
||
"password": in.Password,
|
||
"created_at": time.Now().UTC().Format(time.RFC3339),
|
||
}
|
||
res, err := db.Collection(yuhengCloudRegisterColl).InsertOne(insCtx, doc)
|
||
if err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "云端已成功但本地记录失败: " + err.Error()})
|
||
return
|
||
}
|
||
idHex := ""
|
||
switch v := res.InsertedID.(type) {
|
||
case bson.ObjectID:
|
||
idHex = v.Hex()
|
||
}
|
||
c.JSON(http.StatusOK, gin.H{"id": idHex, "message": "已提交云端注册并写入本地记录"})
|
||
}
|
||
|
||
// ListYuhengCloudRegisterRecords 分页列出本地留痕(便于管理页展示)
|
||
func ListYuhengCloudRegisterRecords(c *gin.Context) {
|
||
db := config.GetDB(config.DBName)
|
||
if db == nil {
|
||
c.JSON(http.StatusOK, gin.H{"list": []any{}, "total": 0})
|
||
return
|
||
}
|
||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||
if page < 1 {
|
||
page = 1
|
||
}
|
||
if pageSize < 1 || pageSize > 100 {
|
||
pageSize = 20
|
||
}
|
||
skip := int64((page - 1) * pageSize)
|
||
limit := int64(pageSize)
|
||
|
||
ctx, cancel := context.WithTimeout(c.Request.Context(), 15*time.Second)
|
||
defer cancel()
|
||
coll := db.Collection(yuhengCloudRegisterColl)
|
||
total, err := coll.CountDocuments(ctx, bson.M{})
|
||
if err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
opts := options.Find().SetSort(bson.D{{Key: "created_at", Value: -1}}).SetSkip(skip).SetLimit(limit)
|
||
cur, err := coll.Find(ctx, bson.M{}, opts)
|
||
if err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
defer cur.Close(ctx)
|
||
var list []models.YuhengCloudRegisterRecord
|
||
for cur.Next(ctx) {
|
||
var row struct {
|
||
ID bson.ObjectID `bson:"_id"`
|
||
Username string `bson:"username"`
|
||
Password string `bson:"password"`
|
||
CreatedAt string `bson:"created_at"`
|
||
}
|
||
if err := cur.Decode(&row); err != nil {
|
||
continue
|
||
}
|
||
list = append(list, models.YuhengCloudRegisterRecord{
|
||
ID: row.ID.Hex(),
|
||
Username: row.Username,
|
||
Password: row.Password,
|
||
CreatedAt: row.CreatedAt,
|
||
})
|
||
}
|
||
c.JSON(http.StatusOK, gin.H{"list": list, "total": total})
|
||
}
|