Files
YMhut-box-C-/server/unified-management/internal/db/scanner_util.go
T
QWQLwToo 2513eb2903
build-winui / winui (push) Has been cancelled
继续更新 update 门户站点界面和功能
2026-06-26 20:17:48 +08:00

277 lines
7.7 KiB
Go

package db
import (
"crypto/rand"
"database/sql"
"encoding/hex"
"encoding/json"
"sort"
"strings"
"time"
)
func (s *Store) scanFeedbackRow(scanner feedbackScanner) (Feedback, error) {
return scanFeedback(scanner)
}
func feedbackSelectSQL() string {
return `SELECT code, title, type, severity, category, priority, contact, body, status, status_detail, public_reply,
note, assignee, handled_by, due_at, resolved_at, archived_at, sla_level, source_channel, risk_score, resolution,
attachment, package_path, encrypted_package_path, package_sha256, plain_package_sha256, summary_text, included_files,
mail_sent, remote_addr, tags, created_at, updated_at, last_activity_at FROM feedback_tickets`
}
func releaseNoticeSelectSQL() string {
return `SELECT id, version, build, channel, title, message, release_notes, message_md, release_notes_md,
download_url, notice_file, raw_json, published_at, created_at, updated_at FROM release_notices`
}
type feedbackScanner interface {
Scan(dest ...any) error
}
func scanReleaseNotice(scanner interface{ Scan(dest ...any) error }) (ReleaseNotice, error) {
var item ReleaseNotice
err := scanner.Scan(&item.ID, &item.Version, &item.Build, &item.Channel, &item.Title, &item.Message,
&item.ReleaseNotes, &item.MessageMD, &item.ReleaseNotesMD, &item.DownloadURL, &item.NoticeFile,
&item.RawJSON, &item.PublishedAt, &item.CreatedAt, &item.UpdatedAt)
return item, err
}
func scanFeedback(scanner feedbackScanner) (Feedback, error) {
var item Feedback
var mailSent int
var tags string
err := scanner.Scan(&item.Code, &item.Title, &item.Type, &item.Severity, &item.Category, &item.Priority, &item.Contact,
&item.Body, &item.Status, &item.StatusDetail, &item.PublicReply, &item.Note, &item.Assignee, &item.HandledBy,
&item.DueAt, &item.ResolvedAt, &item.ArchivedAt, &item.SLALevel, &item.SourceChannel, &item.RiskScore,
&item.Resolution, &item.Attachment, &item.PackagePath, &item.EncryptedPackagePath, &item.PackageSha256,
&item.PlainPackageSha256, &item.SummaryText, &item.IncludedFiles, &mailSent, &item.RemoteAddr, &tags,
&item.CreatedAt, &item.UpdatedAt, &item.LastActivityAt)
item.MailSent = mailSent == 1
_ = json.Unmarshal([]byte(tags), &item.Tags)
return item, err
}
func scanFeedbackRows(rows *sql.Rows) ([]Feedback, error) {
items := []Feedback{}
for rows.Next() {
item, err := scanFeedback(rows)
if err != nil {
return nil, err
}
items = append(items, item)
}
return items, rows.Err()
}
func sourceSelectSQL() string {
return `SELECT id, category_id, category_name, source_id, name, description, method, api_url, url_template, thumbnail_url,
proxy_mode, timeout_ms, retry_count, cache_seconds, check_interval_sec, enabled, client_visible, supported_formats,
last_status, last_latency_ms, last_checked_at, last_error, consecutive_failure, created_at, updated_at FROM source_endpoints`
}
func scanSourceRow(scanner sourceScanner) (Source, error) {
return scanSource(scanner)
}
type sourceScanner interface {
Scan(dest ...any) error
}
func scanSourceRowsCurrent(scanner sourceScanner) (Source, error) {
return scanSource(scanner)
}
func scanSource(scanner sourceScanner) (Source, error) {
var item Source
var enabled, visible int
err := scanner.Scan(&item.ID, &item.CategoryID, &item.CategoryName, &item.SourceID, &item.Name, &item.Description,
&item.Method, &item.APIURL, &item.URLTemplate, &item.ThumbnailURL, &item.ProxyMode, &item.TimeoutMS, &item.RetryCount,
&item.CacheSeconds, &item.CheckIntervalSec, &enabled, &visible, &item.SupportedFormats, &item.LastStatus,
&item.LastLatencyMS, &item.LastCheckedAt, &item.LastError, &item.ConsecutiveFailure, &item.CreatedAt, &item.UpdatedAt)
item.Enabled = enabled == 1
item.ClientVisible = visible == 1
return item, err
}
func scanAuditRows(rows *sql.Rows) ([]AuditLog, error) {
items := []AuditLog{}
for rows.Next() {
var item AuditLog
if err := rows.Scan(&item.ID, &item.Actor, &item.Type, &item.Target, &item.Message, &item.IP, &item.UserAgent, &item.CreatedAt); err != nil {
return nil, err
}
items = append(items, item)
}
return items, rows.Err()
}
func feedbackWhere(filters FeedbackFilters) (string, []any) {
clauses := []string{}
args := []any{}
if filters.Status != "" {
clauses = append(clauses, "status = ?")
args = append(args, filters.Status)
}
if filters.Category != "" {
clauses = append(clauses, "category = ?")
args = append(args, filters.Category)
}
if filters.Priority != "" {
clauses = append(clauses, "priority = ?")
args = append(args, filters.Priority)
}
if filters.Assignee != "" {
clauses = append(clauses, "assignee = ?")
args = append(args, filters.Assignee)
}
if filters.Query != "" {
like := "%" + filters.Query + "%"
clauses = append(clauses, "(code LIKE ? OR title LIKE ? OR contact LIKE ? OR body LIKE ?)")
args = append(args, like, like, like, like)
}
if len(clauses) == 0 {
return "", args
}
return " WHERE " + strings.Join(clauses, " AND "), args
}
func normalizePage(page, perPage int) (int, int) {
if page < 1 {
page = 1
}
if perPage <= 0 || perPage > 100 {
perPage = 20
}
return page, perPage
}
func NewFeedbackCode() string {
var data [3]byte
if _, err := rand.Read(data[:]); err != nil {
return "FB-" + time.Now().UTC().Format("20060102-150405")
}
return "FB-" + time.Now().UTC().Format("20060102") + "-" + strings.ToUpper(hex.EncodeToString(data[:]))
}
func Now() string {
return time.Now().UTC().Format(time.RFC3339)
}
func sanitize(value string) string {
return sanitizeLong(value, 1000)
}
func sanitizeLong(value string, max int) string {
value = strings.TrimSpace(strings.ReplaceAll(value, "\x00", ""))
value = strings.Map(func(r rune) rune {
if r == '\n' || r == '\r' || r == '\t' {
return r
}
if r < 32 {
return -1
}
return r
}, value)
runes := []rune(value)
if max > 0 && len(runes) > max {
return string(runes[:max])
}
return value
}
func boolInt(value bool) int {
if value {
return 1
}
return 0
}
func normalizeCategory(value string) string {
switch strings.ToLower(strings.TrimSpace(value)) {
case "suggestion", "ui", "other":
return strings.ToLower(strings.TrimSpace(value))
default:
return "issue"
}
}
func normalizePriority(value string) string {
switch strings.ToLower(strings.TrimSpace(value)) {
case "major", "blocking":
return strings.ToLower(strings.TrimSpace(value))
default:
return "normal"
}
}
func defaultSLA(priority string) string {
switch normalizePriority(priority) {
case "blocking":
return "urgent"
case "major":
return "elevated"
default:
return "standard"
}
}
func defaultRisk(priority string) int {
switch normalizePriority(priority) {
case "blocking":
return 90
case "major":
return 65
default:
return 30
}
}
func normalizeTags(tags []string) []string {
seen := map[string]bool{}
out := []string{}
for _, tag := range tags {
tag = strings.ToLower(strings.Trim(strings.TrimSpace(tag), ",;#"))
if tag == "" || seen[tag] {
continue
}
runes := []rune(tag)
if len(runes) > 32 {
tag = string(runes[:32])
}
seen[tag] = true
out = append(out, tag)
}
sort.Strings(out)
return out
}
func normalizeProxyMode(value, category, name, url string) string {
value = strings.ToLower(strings.TrimSpace(value))
switch value {
case "server_proxy", "proxy":
return "server_proxy"
case "disabled":
return "disabled"
case "client_direct", "direct":
return "client_direct"
}
haystack := strings.ToLower(category + " " + name + " " + url)
for _, token := range []string{"ip", "weather", "location", "定位", "天气"} {
if strings.Contains(haystack, token) {
return "client_direct"
}
}
return "client_direct"
}
func firstNonEmpty(values ...string) string {
for _, value := range values {
if strings.TrimSpace(value) != "" {
return strings.TrimSpace(value)
}
}
return ""
}