feat: complete contract-driven business development and MySQL integration

This commit is contained in:
2026-09-24 21:56:04 +08:00
parent f9afb8f8dd
commit f5478cdc76
108 changed files with 6557 additions and 550 deletions

80
contract/file.go Normal file
View File

@@ -0,0 +1,80 @@
package contract
import (
"errors"
"io"
"os"
"sync"
)
// File is a request-scoped upload, staged on disk using a bounded copy buffer.
// Name and ContentType are untrusted client metadata. Open streams content;
// readers and the temporary file are closed when the generated handler returns.
// Persist wanted content before returning. Retaining File does not extend its lifetime.
type File struct {
Name string
ContentType string
Size int64
state *uploadState
}
type uploadState struct {
mu sync.Mutex
path string
closed bool
readers map[*uploadReader]bool
}
type uploadReader struct {
file *os.File
once sync.Once
err error
state *uploadState
}
// Present reports whether the optional upload was supplied.
func (f File) Present() bool { return f.state != nil }
func (f File) Open() (io.ReadCloser, error) {
if f.state == nil {
return nil, errors.New("upload file is absent")
}
s := f.state
s.mu.Lock()
defer s.mu.Unlock()
if s.closed {
return nil, errors.New("upload file is closed")
}
file, err := os.Open(s.path)
if err != nil {
return nil, err
}
r := &uploadReader{file: file, state: s}
s.readers[r] = true
return r, nil
}
func (r *uploadReader) Read(p []byte) (int, error) { return r.file.Read(p) }
func (r *uploadReader) Close() error {
r.once.Do(func() { r.err = r.file.Close(); r.state.mu.Lock(); delete(r.state.readers, r); r.state.mu.Unlock() })
return r.err
}
func (f File) cleanup() error {
if f.state == nil {
return nil
}
s := f.state
s.mu.Lock()
if s.closed {
s.mu.Unlock()
return nil
}
s.closed = true
readers := make([]*uploadReader, 0, len(s.readers))
for r := range s.readers {
readers = append(readers, r)
}
s.mu.Unlock()
var err error
for _, r := range readers {
err = errors.Join(err, r.Close())
}
return errors.Join(err, os.Remove(s.path))
}