继续更新 update 门户站点界面和功能
build-winui / winui (push) Has been cancelled

This commit is contained in:
QWQLwToo
2026-06-26 20:17:34 +08:00
parent f525e5f3ba
commit 2513eb2903
68 changed files with 5586 additions and 3195 deletions
@@ -0,0 +1,345 @@
package db
import (
"database/sql"
"encoding/json"
"errors"
"os"
"path/filepath"
)
func (s *Store) InsertFeedback(item Feedback) error {
now := Now()
if item.Code == "" {
item.Code = NewFeedbackCode()
}
if item.Status == "" {
item.Status = "new"
}
if item.Category == "" {
item.Category = normalizeCategory(item.Type)
}
if item.Priority == "" {
item.Priority = normalizePriority(item.Severity)
}
if item.SLALevel == "" {
item.SLALevel = defaultSLA(item.Priority)
}
if item.SourceChannel == "" {
item.SourceChannel = "winui"
}
if item.RiskScore == 0 {
item.RiskScore = defaultRisk(item.Priority)
}
if item.StatusDetail == "" {
item.StatusDetail = "反馈已接收,等待后台处理。"
}
if item.CreatedAt == "" {
item.CreatedAt = now
}
item.UpdatedAt = now
item.LastActivityAt = now
tagsJSON, _ := json.Marshal(normalizeTags(item.Tags))
_, err := s.exec(`INSERT INTO feedback_tickets (
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
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
item.Code, sanitize(item.Title), sanitize(item.Type), sanitize(item.Severity), item.Category, item.Priority,
sanitize(item.Contact), sanitizeLong(item.Body, 5000), item.Status, sanitize(item.StatusDetail),
sanitizeLong(item.PublicReply, 3000), sanitizeLong(item.Note, 3000), sanitize(item.Assignee), sanitize(item.HandledBy),
item.DueAt, item.ResolvedAt, item.ArchivedAt, item.SLALevel, item.SourceChannel, item.RiskScore,
sanitizeLong(item.Resolution, 3000), item.Attachment, item.PackagePath, item.EncryptedPackagePath,
item.PackageSha256, item.PlainPackageSha256, sanitizeLong(item.SummaryText, 6000), item.IncludedFiles,
boolInt(item.MailSent), sanitize(item.RemoteAddr), string(tagsJSON), item.CreatedAt, item.UpdatedAt, item.LastActivityAt)
if err != nil {
return err
}
if item.PackagePath != "" {
_ = s.InsertFeedbackAttachment(FeedbackAttachment{FeedbackCode: item.Code, Kind: "package", Path: item.PackagePath, FileName: filepath.Base(item.PackagePath), SHA256: item.PlainPackageSha256})
}
if item.EncryptedPackagePath != "" {
_ = s.InsertFeedbackAttachment(FeedbackAttachment{FeedbackCode: item.Code, Kind: "encrypted_package", Path: item.EncryptedPackagePath, FileName: filepath.Base(item.EncryptedPackagePath), SHA256: item.PackageSha256})
}
return nil
}
func (s *Store) GetFeedback(code string) (Feedback, error) {
item, err := s.scanFeedbackRow(s.queryRow(feedbackSelectSQL()+` WHERE code = ?`, code))
if errors.Is(err, sql.ErrNoRows) {
return Feedback{}, errors.New("feedback not found")
}
return item, err
}
func (s *Store) GetFeedbackDetail(code string) (*FeedbackDetail, error) {
item, err := s.GetFeedback(code)
if err != nil {
return nil, err
}
comments, err := s.ListFeedbackComments(code)
if err != nil {
return nil, err
}
attachments, err := s.ListFeedbackAttachments(code)
if err != nil {
return nil, err
}
events, _ := s.ListAuditLogsForTarget(code, 100)
legacyEvents, _ := s.ListFeedbackEvents(code, 100)
mailRecords, _ := s.ListMailRecords(code, 100)
return &FeedbackDetail{Feedback: item, Comments: comments, Attachments: attachments, Events: events, LegacyEvents: legacyEvents, MailRecords: mailRecords}, nil
}
func (s *Store) ListFeedbacks(limit int) ([]Feedback, error) {
if limit <= 0 || limit > 200 {
limit = 50
}
rows, err := s.query(feedbackSelectSQL()+` ORDER BY last_activity_at DESC, created_at DESC LIMIT ?`, limit)
if err != nil {
return nil, err
}
defer rows.Close()
return scanFeedbackRows(rows)
}
func (s *Store) ListFeedbacksFiltered(page, perPage int, filters FeedbackFilters) ([]Feedback, int, error) {
page, perPage = normalizePage(page, perPage)
where, args := feedbackWhere(filters)
var total int
if err := s.queryRow(`SELECT COUNT(*) FROM feedback_tickets`+where, args...).Scan(&total); err != nil {
return nil, 0, err
}
order := ` ORDER BY last_activity_at DESC, created_at DESC`
if filters.Sort == "oldest" {
order = ` ORDER BY created_at ASC`
}
args = append(args, perPage, (page-1)*perPage)
rows, err := s.query(feedbackSelectSQL()+where+order+` LIMIT ? OFFSET ?`, args...)
if err != nil {
return nil, 0, err
}
defer rows.Close()
items, err := scanFeedbackRows(rows)
return items, total, err
}
func (s *Store) UpdateFeedback(code, status, detail, reply string) error {
update := FeedbackUpdate{Status: status, StatusDetail: detail, PublicReply: reply, Actor: "admin"}
return s.UpdateFeedbackTicket(code, update)
}
func (s *Store) UpdateFeedbackTicket(code string, update FeedbackUpdate) error {
current, err := s.GetFeedback(code)
if err != nil {
return err
}
if update.Status == "" {
update.Status = current.Status
}
if update.Category == "" {
update.Category = current.Category
}
if update.Priority == "" {
update.Priority = current.Priority
}
if update.SLALevel == "" {
update.SLALevel = current.SLALevel
}
if update.StatusDetail == "" {
update.StatusDetail = current.StatusDetail
}
if update.PublicReply == "" {
update.PublicReply = current.PublicReply
}
if update.Note == "" {
update.Note = current.Note
}
if update.Assignee == "" {
update.Assignee = current.Assignee
}
if update.HandledBy == "" {
update.HandledBy = current.HandledBy
}
if update.DueAt == "" {
update.DueAt = current.DueAt
}
if update.Resolution == "" {
update.Resolution = current.Resolution
}
tags := current.Tags
if len(update.Tags) > 0 {
tags = update.Tags
}
tagsJSON, _ := json.Marshal(normalizeTags(tags))
now := Now()
_, err = s.exec(`UPDATE feedback_tickets SET status = ?, category = ?, priority = ?, status_detail = ?, public_reply = ?,
note = ?, assignee = ?, handled_by = ?, due_at = ?, sla_level = ?, resolution = ?, tags = ?, updated_at = ?, last_activity_at = ?
WHERE code = ?`,
update.Status, update.Category, update.Priority, sanitize(update.StatusDetail), sanitizeLong(update.PublicReply, 3000),
sanitizeLong(update.Note, 3000), sanitize(update.Assignee), sanitize(update.HandledBy), update.DueAt, update.SLALevel,
sanitizeLong(update.Resolution, 3000), string(tagsJSON), now, now, code)
if err == nil {
_ = s.InsertAudit(AuditLog{Actor: firstNonEmpty(update.Actor, "admin"), Type: "feedback.updated", Target: code, Message: "反馈工单已更新"})
}
return err
}
func (s *Store) BulkUpdateFeedback(codes []string, update FeedbackUpdate) error {
for _, code := range codes {
if err := s.UpdateFeedbackTicket(code, update); err != nil {
return err
}
}
return nil
}
func (s *Store) InsertFeedbackComment(comment FeedbackComment) (FeedbackComment, error) {
if comment.CreatedAt == "" {
comment.CreatedAt = Now()
}
id, err := s.insertID(`INSERT INTO feedback_comments (feedback_code, author, body, internal, created_at) VALUES (?, ?, ?, ?, ?)`,
comment.Code, sanitize(comment.Author), sanitizeLong(comment.Body, 3000), boolInt(comment.Internal), comment.CreatedAt)
if err != nil {
return FeedbackComment{}, err
}
comment.ID = id
return comment, nil
}
func (s *Store) ListFeedbackComments(code string) ([]FeedbackComment, error) {
rows, err := s.query(`SELECT id, feedback_code, author, body, internal, created_at FROM feedback_comments WHERE feedback_code = ? ORDER BY id ASC`, code)
if err != nil {
return nil, err
}
defer rows.Close()
items := []FeedbackComment{}
for rows.Next() {
var item FeedbackComment
var internal int
if err := rows.Scan(&item.ID, &item.Code, &item.Author, &item.Body, &internal, &item.CreatedAt); err != nil {
return nil, err
}
item.Internal = internal == 1
items = append(items, item)
}
return items, rows.Err()
}
func (s *Store) InsertFeedbackAttachment(item FeedbackAttachment) error {
if item.CreatedAt == "" {
item.CreatedAt = Now()
}
if item.FileName == "" {
item.FileName = filepath.Base(item.Path)
}
if item.SizeBytes == 0 {
if info, err := os.Stat(item.Path); err == nil {
item.SizeBytes = info.Size()
}
}
_, err := s.exec(`INSERT INTO feedback_attachments (feedback_code, kind, path, file_name, sha256, size_bytes, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)`,
item.FeedbackCode, item.Kind, item.Path, item.FileName, item.SHA256, item.SizeBytes, item.CreatedAt)
return err
}
func (s *Store) ListFeedbackAttachments(code string) ([]FeedbackAttachment, error) {
rows, err := s.query(`SELECT id, feedback_code, kind, path, file_name, sha256, size_bytes, created_at FROM feedback_attachments WHERE feedback_code = ? ORDER BY id ASC`, code)
if err != nil {
return nil, err
}
defer rows.Close()
items := []FeedbackAttachment{}
for rows.Next() {
var item FeedbackAttachment
if err := rows.Scan(&item.ID, &item.FeedbackCode, &item.Kind, &item.Path, &item.FileName, &item.SHA256, &item.SizeBytes, &item.CreatedAt); err != nil {
return nil, err
}
items = append(items, item)
}
return items, rows.Err()
}
func (s *Store) UpsertFeedbackEvent(item LegacyFeedbackEvent) error {
if item.CreatedAt == "" {
item.CreatedAt = Now()
}
conn, d := s.active()
columns := []string{"id", "feedback_code", "event_type", "actor", "from_value", "to_value", "message", "created_at"}
_, err := conn.Exec(d.rebind(d.upsert("feedback_events", columns, []string{"id"})),
item.ID, sanitize(item.FeedbackCode), sanitize(item.EventType), sanitize(item.Actor), sanitize(item.FromValue), sanitize(item.ToValue), sanitizeLong(item.Message, 1000), item.CreatedAt)
if err != nil {
s.markFailover(err)
}
return err
}
func (s *Store) UpsertFeedbackTag(code, tag, createdAt string) error {
if createdAt == "" {
createdAt = Now()
}
conn, d := s.active()
columns := []string{"feedback_code", "tag", "created_at"}
_, err := conn.Exec(d.rebind(d.upsert("feedback_tags", columns, []string{"feedback_code", "tag"})), sanitize(code), sanitize(tag), createdAt)
if err != nil {
s.markFailover(err)
}
return err
}
func (s *Store) UpsertMailRecord(item LegacyMailRecord) error {
if item.CreatedAt == "" {
item.CreatedAt = Now()
}
conn, d := s.active()
columns := []string{"id", "feedback_code", "kind", "status", "to_address", "subject", "plain_body", "html_body", "attachment_path", "attachment_name", "error_message", "created_at", "sent_at"}
_, err := conn.Exec(d.rebind(d.upsert("mail_records", columns, []string{"id"})),
item.ID, sanitize(item.FeedbackCode), sanitize(item.Kind), sanitize(item.Status), sanitize(item.ToAddress), sanitizeLong(item.Subject, 1000),
"", "", item.AttachmentPath, item.AttachmentName, sanitizeLong(item.ErrorMessage, 1000), item.CreatedAt, item.SentAt)
if err != nil {
s.markFailover(err)
}
return err
}
func (s *Store) ListFeedbackEvents(code string, limit int) ([]LegacyFeedbackEvent, error) {
if limit <= 0 || limit > 200 {
limit = 100
}
rows, err := s.query(`SELECT id, feedback_code, event_type, actor, from_value, to_value, message, created_at FROM feedback_events WHERE feedback_code = ? ORDER BY created_at DESC, id DESC LIMIT ?`, code, limit)
if err != nil {
return nil, err
}
defer rows.Close()
items := []LegacyFeedbackEvent{}
for rows.Next() {
var item LegacyFeedbackEvent
if err := rows.Scan(&item.ID, &item.FeedbackCode, &item.EventType, &item.Actor, &item.FromValue, &item.ToValue, &item.Message, &item.CreatedAt); err != nil {
return nil, err
}
items = append(items, item)
}
return items, rows.Err()
}
func (s *Store) ListMailRecords(code string, limit int) ([]LegacyMailRecord, error) {
if limit <= 0 || limit > 200 {
limit = 100
}
rows, err := s.query(`SELECT id, feedback_code, kind, status, to_address, subject, attachment_path, attachment_name, error_message, created_at, sent_at FROM mail_records WHERE feedback_code = ? ORDER BY created_at DESC, id DESC LIMIT ?`, code, limit)
if err != nil {
return nil, err
}
defer rows.Close()
items := []LegacyMailRecord{}
for rows.Next() {
var item LegacyMailRecord
if err := rows.Scan(&item.ID, &item.FeedbackCode, &item.Kind, &item.Status, &item.ToAddress, &item.Subject, &item.AttachmentPath, &item.AttachmentName, &item.ErrorMessage, &item.CreatedAt, &item.SentAt); err != nil {
return nil, err
}
items = append(items, item)
}
return items, rows.Err()
}