更新为postgre储存

This commit is contained in:
WuYuuuub
2026-04-28 20:26:52 +08:00
parent 8457cf1450
commit ac5b11b385
7 changed files with 557 additions and 234 deletions

View File

@@ -1,19 +1,20 @@
package main package main
import ( import (
"context"
"encoding/json"
"flag" "flag"
"fmt" "fmt"
"log" "log"
"net" "net"
"net/http"
"os" "os"
"os/signal" "os/signal"
"syscall" "syscall"
"github.com/cloudprint/cloudprint-backend/config" "github.com/cloudprint/cloudprint-backend/config"
"github.com/cloudprint/cloudprint-backend/internal/grpc"
"github.com/cloudprint/cloudprint-backend/internal/grpc/interceptor" "github.com/cloudprint/cloudprint-backend/internal/grpc/interceptor"
"github.com/cloudprint/cloudprint-backend/internal/grpc/service" grpcService "github.com/cloudprint/cloudprint-backend/internal/grpc/service"
"github.com/cloudprint/cloudprint-backend/internal/model"
"github.com/cloudprint/cloudprint-backend/internal/repository" "github.com/cloudprint/cloudprint-backend/internal/repository"
"github.com/cloudprint/cloudprint-backend/internal/service" "github.com/cloudprint/cloudprint-backend/internal/service"
"github.com/cloudprint/cloudprint-backend/pkg/jwt" "github.com/cloudprint/cloudprint-backend/pkg/jwt"
@@ -22,7 +23,10 @@ import (
pb "github.com/cloudprint/cloudprint-backend/proto/generated" pb "github.com/cloudprint/cloudprint-backend/proto/generated"
"go.uber.org/zap" "go.uber.org/zap"
"google.golang.org/grpc" "google.golang.org/grpc"
"gorm.io/driver/mysql" "google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"gorm.io/driver/postgres"
"gorm.io/gorm" "gorm.io/gorm"
"gorm.io/gorm/logger" "gorm.io/gorm/logger"
) )
@@ -47,23 +51,6 @@ func main() {
log.Fatalf("Failed to initialize database: %v", err) log.Fatalf("Failed to initialize database: %v", err)
} }
// Auto migrate
if err := db.AutoMigrate(
&model.User{},
&model.Admin{},
&model.Printer{},
&model.Order{},
&model.OrderItem{},
&model.File{},
&model.LibraryCategory{},
&model.LibraryFile{},
&model.PrintJob{},
&model.PriceList{},
&model.Payment{},
); err != nil {
log.Fatalf("Failed to migrate database: %v", err)
}
// Initialize repositories // Initialize repositories
userRepo := repository.NewUserRepository(db) userRepo := repository.NewUserRepository(db)
adminRepo := repository.NewAdminRepository(db) adminRepo := repository.NewAdminRepository(db)
@@ -105,14 +92,17 @@ func main() {
) )
// Register gRPC services // Register gRPC services
pb.RegisterAuthServiceServer(grpcServer, service.NewAuthServiceServer(authService)) pb.RegisterAuthServiceServer(grpcServer, grpcService.NewAuthServiceServer(authService))
pb.RegisterFileServiceServer(grpcServer, service.NewFileServiceServer(fileService)) pb.RegisterFileServiceServer(grpcServer, grpcService.NewFileServiceServer(fileService))
pb.RegisterOrderServiceServer(grpcServer, service.NewOrderServiceServer(orderService)) pb.RegisterOrderServiceServer(grpcServer, grpcService.NewOrderServiceServer(orderService))
pb.RegisterPrintServiceServer(grpcServer, service.NewPrintServiceServer(printService)) pb.RegisterPrintServiceServer(grpcServer, grpcService.NewPrintServiceServer(printService))
pb.RegisterLibraryServiceServer(grpcServer, service.NewLibraryServiceServer(libraryRepo, fileRepo)) pb.RegisterLibraryServiceServer(grpcServer, grpcService.NewLibraryServiceServer(libraryRepo, fileRepo))
pb.RegisterPriceServiceServer(grpcServer, service.NewPriceServiceServer(priceRepo)) pb.RegisterPriceServiceServer(grpcServer, grpcService.NewPriceServiceServer(priceRepo))
pb.RegisterPayServiceServer(grpcServer, service.NewPayServiceServer(orderService, paymentRepo, weChatPay, userRepo)) pb.RegisterPayServiceServer(grpcServer, grpcService.NewPayServiceServer(orderService, paymentRepo, weChatPay, userRepo))
pb.RegisterAdminServiceServer(grpcServer, service.NewAdminServiceServer(authService, orderRepo, printerRepo)) pb.RegisterAdminServiceServer(grpcServer, grpcService.NewAdminServiceServer(authService, orderRepo, printerRepo))
// Initialize HTTP gateway
gateway := newHTTPGateway(grpcServer, jwtManager)
// Start server // Start server
addr := fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.Port) addr := fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.Port)
@@ -125,14 +115,29 @@ func main() {
sigCh := make(chan os.Signal, 1) sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
// HTTP server for gateway
httpServer := &http.Server{
Handler: gateway,
}
// Start HTTP server in goroutine
go func() { go func() {
<-sigCh zapLogger.Info("Starting HTTP gateway", zap.String("address", addr))
grpcServer.GracefulStop() if err := httpServer.Serve(lis); err != nil && err != http.ErrServerClosed {
log.Fatalf("Failed to serve HTTP: %v", err)
}
}() }()
zapLogger.Info("Starting gRPC server", zap.String("address", addr)) // Wait for shutdown signal
if err := grpcServer.Serve(lis); err != nil { <-sigCh
log.Fatalf("Failed to serve: %v", err) zapLogger.Info("Shutting down servers...")
// Graceful stop gRPC server
grpcServer.GracefulStop()
// Shutdown HTTP server
if err := httpServer.Close(); err != nil {
zapLogger.Error("Failed to close HTTP server", zap.Error(err))
} }
} }
@@ -144,7 +149,7 @@ func initDB(cfg *config.Config) (*gorm.DB, error) {
}, },
) )
db, err := gorm.Open(mysql.Open(cfg.Database.DSN()), &gorm.Config{ db, err := gorm.Open(postgres.Open(cfg.Database.DSN()), &gorm.Config{
Logger: gormLogger, Logger: gormLogger,
}) })
if err != nil { if err != nil {
@@ -161,3 +166,301 @@ func initDB(cfg *config.Config) (*gorm.DB, error) {
return db, nil return db, nil
} }
// HTTP Gateway
type httpGateway struct {
grpcServer *grpc.Server
jwtManager *jwt.JWTManager
mux *http.ServeMux
}
func newHTTPGateway(grpcServer *grpc.Server, jwtManager *jwt.JWTManager) *httpGateway {
g := &httpGateway{
grpcServer: grpcServer,
jwtManager: jwtManager,
mux: http.NewServeMux(),
}
g.registerHandlers()
return g
}
func (g *httpGateway) ServeHTTP(w http.ResponseWriter, r *http.Request) {
g.mux.ServeHTTP(w, r)
}
func (g *httpGateway) registerHandlers() {
// Health check
g.mux.HandleFunc("/health", g.healthHandler)
// Auth endpoints
g.mux.HandleFunc("/api/v1/auth/login", g.handleLogin)
g.mux.HandleFunc("/api/v1/auth/refresh", g.handleRefreshToken)
g.mux.HandleFunc("/api/v1/auth/userinfo", g.handleGetUserInfo)
// File endpoints
g.mux.HandleFunc("/api/v1/file/upload", g.handleFileUpload)
g.mux.HandleFunc("/api/v1/file/", g.handleFileOps)
// Order endpoints
g.mux.HandleFunc("/api/v1/order/create", g.handleCreateOrder)
g.mux.HandleFunc("/api/v1/order/", g.handleOrderOps)
// Print endpoints
g.mux.HandleFunc("/api/v1/print/printers", g.handleGetPrinters)
g.mux.HandleFunc("/api/v1/print/submit", g.handleSubmitPrintJob)
// Library endpoints
g.mux.HandleFunc("/api/v1/library/categories", g.handleGetCategories)
g.mux.HandleFunc("/api/v1/library/files", g.handleGetLibraryFiles)
// Price endpoints
g.mux.HandleFunc("/api/v1/price/list", g.handleGetPriceList)
// Pay endpoints
g.mux.HandleFunc("/api/v1/pay/wxpay", g.handleCreateWxPayOrder)
g.mux.HandleFunc("/api/v1/pay/callback", g.handlePayCallback)
g.mux.HandleFunc("/api/v1/pay/balance", g.handlePayByBalance)
// Admin endpoints
g.mux.HandleFunc("/api/v1/admin/login", g.handleAdminLogin)
g.mux.HandleFunc("/api/v1/admin/stats", g.handleGetStatistics)
}
func (g *httpGateway) healthHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`{"status":"ok"}`))
}
func (g *httpGateway) writeJSON(w http.ResponseWriter, statusCode int, data interface{}) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(statusCode)
json.NewEncoder(w).Encode(data)
}
func (g *httpGateway) handleError(w http.ResponseWriter, err error) {
st, ok := status.FromError(err)
if !ok {
g.writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
return
}
code := http.StatusInternalServerError
switch st.Code() {
case codes.OK:
code = http.StatusOK
case codes.InvalidArgument:
code = http.StatusBadRequest
case codes.NotFound:
code = http.StatusNotFound
case codes.Unauthenticated:
code = http.StatusUnauthorized
case codes.PermissionDenied:
code = http.StatusForbidden
}
g.writeJSON(w, code, map[string]string{"error": st.Message()})
}
func (g *httpGateway) parseToken(r *http.Request) (context.Context, error) {
token := r.Header.Get("Authorization")
if token == "" {
return nil, status.Error(codes.Unauthenticated, "missing token")
}
if len(token) > 7 && token[:7] == "Bearer " {
token = token[7:]
}
claims, err := g.jwtManager.ValidateToken(token)
if err != nil {
return nil, status.Error(codes.Unauthenticated, "invalid token")
}
return metadata.NewOutgoingContext(r.Context(), metadata.Pairs(
"user_id", fmt.Sprintf("%d", claims.UserID),
"user_type", claims.UserType,
)), nil
}
func (g *httpGateway) handleLogin(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
var req pb.LoginRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// gRPC call would go here - returning placeholder for now
g.writeJSON(w, http.StatusOK, map[string]string{"message": "Use gRPC directly for login"})
}
func (g *httpGateway) handleRefreshToken(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
g.writeJSON(w, http.StatusOK, map[string]string{"message": "not implemented"})
}
func (g *httpGateway) handleGetUserInfo(w http.ResponseWriter, r *http.Request) {
_, err := g.parseToken(r)
if err != nil {
g.handleError(w, err)
return
}
g.writeJSON(w, http.StatusOK, map[string]string{"message": "not implemented"})
}
func (g *httpGateway) handleFileUpload(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
_, err := g.parseToken(r)
if err != nil {
g.handleError(w, err)
return
}
g.writeJSON(w, http.StatusOK, map[string]string{"message": "not implemented"})
}
func (g *httpGateway) handleFileOps(w http.ResponseWriter, r *http.Request) {
_, err := g.parseToken(r)
if err != nil {
g.handleError(w, err)
return
}
switch r.Method {
case http.MethodGet:
g.writeJSON(w, http.StatusOK, map[string]string{"message": "not implemented"})
case http.MethodDelete:
g.writeJSON(w, http.StatusOK, map[string]string{"message": "not implemented"})
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
}
func (g *httpGateway) handleCreateOrder(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
_, err := g.parseToken(r)
if err != nil {
g.handleError(w, err)
return
}
g.writeJSON(w, http.StatusOK, map[string]string{"message": "not implemented"})
}
func (g *httpGateway) handleOrderOps(w http.ResponseWriter, r *http.Request) {
_, err := g.parseToken(r)
if err != nil {
g.handleError(w, err)
return
}
switch r.Method {
case http.MethodGet:
g.writeJSON(w, http.StatusOK, map[string]string{"message": "not implemented"})
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
}
func (g *httpGateway) handleGetPrinters(w http.ResponseWriter, r *http.Request) {
_, err := g.parseToken(r)
if err != nil {
g.handleError(w, err)
return
}
g.writeJSON(w, http.StatusOK, map[string]string{"message": "not implemented"})
}
func (g *httpGateway) handleSubmitPrintJob(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
_, err := g.parseToken(r)
if err != nil {
g.handleError(w, err)
return
}
g.writeJSON(w, http.StatusOK, map[string]string{"message": "not implemented"})
}
func (g *httpGateway) handleGetCategories(w http.ResponseWriter, r *http.Request) {
_, err := g.parseToken(r)
if err != nil {
g.handleError(w, err)
return
}
g.writeJSON(w, http.StatusOK, map[string]string{"message": "not implemented"})
}
func (g *httpGateway) handleGetLibraryFiles(w http.ResponseWriter, r *http.Request) {
_, err := g.parseToken(r)
if err != nil {
g.handleError(w, err)
return
}
g.writeJSON(w, http.StatusOK, map[string]string{"message": "not implemented"})
}
func (g *httpGateway) handleGetPriceList(w http.ResponseWriter, r *http.Request) {
_, err := g.parseToken(r)
if err != nil {
g.handleError(w, err)
return
}
g.writeJSON(w, http.StatusOK, map[string]string{"message": "not implemented"})
}
func (g *httpGateway) handleCreateWxPayOrder(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
_, err := g.parseToken(r)
if err != nil {
g.handleError(w, err)
return
}
g.writeJSON(w, http.StatusOK, map[string]string{"message": "not implemented"})
}
func (g *httpGateway) handlePayCallback(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
g.writeJSON(w, http.StatusOK, map[string]string{"message": "not implemented"})
}
func (g *httpGateway) handlePayByBalance(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
_, err := g.parseToken(r)
if err != nil {
g.handleError(w, err)
return
}
g.writeJSON(w, http.StatusOK, map[string]string{"message": "not implemented"})
}
func (g *httpGateway) handleAdminLogin(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
g.writeJSON(w, http.StatusOK, map[string]string{"message": "not implemented"})
}
func (g *httpGateway) handleGetStatistics(w http.ResponseWriter, r *http.Request) {
_, err := g.parseToken(r)
if err != nil {
g.handleError(w, err)
return
}
g.writeJSON(w, http.StatusOK, map[string]string{"message": "not implemented"})
}

View File

@@ -32,8 +32,8 @@ type DatabaseConfig struct {
} }
func (d *DatabaseConfig) DSN() string { func (d *DatabaseConfig) DSN() string {
return fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local", return fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable TimeZone=Asia/Shanghai",
d.Username, d.Password, d.Host, d.Port, d.Database) d.Host, d.Port, d.Username, d.Password, d.Database)
} }
type RedisConfig struct { type RedisConfig struct {

View File

@@ -1,6 +1,6 @@
module github.com/cloudprint/cloudprint-backend module github.com/cloudprint/cloudprint-backend
go 1.24.0 go 1.25.0
require ( require (
github.com/golang-jwt/jwt/v5 v5.2.0 github.com/golang-jwt/jwt/v5 v5.2.0
@@ -8,18 +8,21 @@ require (
github.com/iGoogle-ink/gopay v1.5.14 github.com/iGoogle-ink/gopay v1.5.14
github.com/spf13/viper v1.18.2 github.com/spf13/viper v1.18.2
go.uber.org/zap v1.26.0 go.uber.org/zap v1.26.0
golang.org/x/crypto v0.46.0 golang.org/x/crypto v0.49.0
google.golang.org/grpc v1.79.3 google.golang.org/grpc v1.80.0
google.golang.org/protobuf v1.36.11 google.golang.org/protobuf v1.36.11
gorm.io/driver/mysql v1.5.2 gorm.io/driver/postgres v1.5.9
gorm.io/gorm v1.25.5 gorm.io/gorm v1.25.10
) )
require ( require (
github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/go-sql-driver/mysql v1.7.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect
github.com/iGoogle-ink/gotil v1.0.2 // indirect github.com/iGoogle-ink/gotil v1.0.2 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgx/v5 v5.5.5 // indirect
github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect github.com/jinzhu/now v1.1.5 // indirect
github.com/magiconair/properties v1.8.7 // indirect github.com/magiconair/properties v1.8.7 // indirect
@@ -31,13 +34,16 @@ require (
github.com/spf13/afero v1.11.0 // indirect github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/testify v1.11.1 // indirect
github.com/subosito/gotenv v1.6.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect
go.opentelemetry.io/otel v1.43.0 // indirect
go.uber.org/multierr v1.10.0 // indirect go.uber.org/multierr v1.10.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/net v0.48.0 // indirect golang.org/x/net v0.52.0 // indirect
golang.org/x/sys v0.39.0 // indirect golang.org/x/sync v0.20.0 // indirect
golang.org/x/text v0.32.0 // indirect golang.org/x/sys v0.42.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect golang.org/x/text v0.36.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

View File

@@ -56,8 +56,6 @@ github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GO
github.com/go-redis/redis/v8 v8.0.0-beta.5/go.mod h1:Mm9EH/5UMRx680UIryN6rd5XFn/L7zORPqLV+1D5thQ= github.com/go-redis/redis/v8 v8.0.0-beta.5/go.mod h1:Mm9EH/5UMRx680UIryN6rd5XFn/L7zORPqLV+1D5thQ=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@@ -95,6 +93,14 @@ github.com/iGoogle-ink/gopay v1.5.14 h1:vcwdqNeccx8FWrnqKTeZZSK5FcAHGUD07OFHwhMh
github.com/iGoogle-ink/gopay v1.5.14/go.mod h1:JuI4rWXHSTEgFHD5C5xfpy3Do3b0pIcu/wdsRyw/Ds4= github.com/iGoogle-ink/gopay v1.5.14/go.mod h1:JuI4rWXHSTEgFHD5C5xfpy3Do3b0pIcu/wdsRyw/Ds4=
github.com/iGoogle-ink/gotil v1.0.2 h1:z58bvmhq31twxecMkyWU5Mh8H7T9SGPOTvUuAw9vB7M= github.com/iGoogle-ink/gotil v1.0.2 h1:z58bvmhq31twxecMkyWU5Mh8H7T9SGPOTvUuAw9vB7M=
github.com/iGoogle-ink/gotil v1.0.2/go.mod h1:HOeCIcJkJLAsQ+cAPdozZS3huVDT4YUKN/F7tmvvrbM= github.com/iGoogle-ink/gotil v1.0.2/go.mod h1:HOeCIcJkJLAsQ+cAPdozZS3huVDT4YUKN/F7tmvvrbM=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jinzhu/gorm v1.9.14/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs= github.com/jinzhu/gorm v1.9.14/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
@@ -176,10 +182,12 @@ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpE
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
@@ -190,16 +198,16 @@ go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v0.6.0/go.mod h1:jzBIgIzK43Iu1BpDAXwqOd6UPsSAk+ewVZ5ofSXw4Ek= go.opentelemetry.io/otel v0.6.0/go.mod h1:jzBIgIzK43Iu1BpDAXwqOd6UPsSAk+ewVZ5ofSXw4Ek=
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo=
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
@@ -210,8 +218,8 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
@@ -233,8 +241,8 @@ golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -242,6 +250,8 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -252,13 +262,13 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -268,8 +278,8 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -279,23 +289,24 @@ google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRn
google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20191009194640-548a555dbc03/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191009194640-548a555dbc03/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 h1:RmoJA1ujG+/lRGNfUnOMfhCy5EipVMyvUE+KNbPbTlw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
@@ -307,11 +318,10 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/mysql v1.5.2 h1:QC2HRskSE75wBuOxe0+iCkyJZ+RqpudsQtqkp+IMuXs= gorm.io/driver/postgres v1.5.9 h1:DkegyItji119OlcaLjqN11kHoUgZ/j13E0jkJZgD6A8=
gorm.io/driver/mysql v1.5.2/go.mod h1:pQLhh1Ut/WUAySdTHwBpBv6+JKcj+ua4ZFx1QQTBzb8= gorm.io/driver/postgres v1.5.9/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s=
gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@@ -21,7 +21,7 @@ func NewFileServiceServer(fileService *service.FileService) *FileServiceServer {
} }
} }
func (s *FileServiceServer) Upload(stream pb.FileService_UploadServer) (*pb.UploadResponse, error) { func (s *FileServiceServer) Upload(stream pb.FileService_UploadServer) error {
var fileName, fileType, uploaderType string var fileName, fileType, uploaderType string
var uploaderID int32 var uploaderID int32
var chunks [][]byte var chunks [][]byte
@@ -32,7 +32,7 @@ func (s *FileServiceServer) Upload(stream pb.FileService_UploadServer) (*pb.Uplo
break break
} }
if err != nil { if err != nil {
return nil, status.Errorf(codes.Internal, "receive chunk failed: %v", err) return status.Errorf(codes.Internal, "receive chunk failed: %v", err)
} }
if fileName == "" { if fileName == "" {
@@ -53,14 +53,14 @@ func (s *FileServiceServer) Upload(stream pb.FileService_UploadServer) (*pb.Uplo
file, err := s.fileService.Upload(stream.Context(), fileName, fileType, uploaderType, uploaderID, data) file, err := s.fileService.Upload(stream.Context(), fileName, fileType, uploaderType, uploaderID, data)
if err != nil { if err != nil {
return nil, status.Errorf(codes.Internal, "upload failed: %v", err) return status.Errorf(codes.Internal, "upload failed: %v", err)
} }
return &pb.UploadResponse{ return stream.SendAndClose(&pb.UploadResponse{
FileId: file.ID, FileId: file.ID,
FilePath: file.FilePath, FilePath: file.FilePath,
FileUrl: file.FilePath, FileUrl: file.FilePath,
}, nil })
} }
func (s *FileServiceServer) GetFile(req *pb.GetFileRequest, stream pb.FileService_GetFileServer) error { func (s *FileServiceServer) GetFile(req *pb.GetFileRequest, stream pb.FileService_GetFileServer) error {

View File

@@ -116,14 +116,14 @@ func (r *OrderRepository) GetStatistics(ctx context.Context) (totalUsers, totalO
// 今日订单数 // 今日订单数
today := time.Now().Format("2006-01-02") today := time.Now().Format("2006-01-02")
if err = r.db.WithContext(ctx).Model(&model.Order{}). if err = r.db.WithContext(ctx).Model(&model.Order{}).
Where("DATE(created_at) = ? AND status = ?", today, model.OrderStatusPaid). Where("created_at::date = ? AND status = ?", today, model.OrderStatusPaid).
Count(&todayOrders).Error; err != nil { Count(&todayOrders).Error; err != nil {
return 0, 0, 0, 0, 0, fmt.Errorf("count today orders failed: %w", err) return 0, 0, 0, 0, 0, fmt.Errorf("count today orders failed: %w", err)
} }
// 今日收入 // 今日收入
if err = r.db.WithContext(ctx).Model(&model.Order{}). if err = r.db.WithContext(ctx).Model(&model.Order{}).
Select("COALESCE(SUM(total_amount), 0)"). Select("COALESCE(SUM(total_amount), 0)").
Where("DATE(created_at) = ? AND status = ?", today, model.OrderStatusPaid). Where("created_at::date = ? AND status = ?", today, model.OrderStatusPaid).
Scan(&todayRevenue).Error; err != nil { Scan(&todayRevenue).Error; err != nil {
return 0, 0, 0, 0, 0, fmt.Errorf("sum today revenue failed: %w", err) return 0, 0, 0, 0, 0, fmt.Errorf("sum today revenue failed: %w", err)
} }

View File

@@ -1,182 +1,186 @@
-- 云印享 - 付费打印服务系统数据库初始化脚本 -- 云印享 - 付费打印服务系统数据库初始化脚本 (PostgreSQL)
-- 数据库: cloudprint -- 数据库: cloudprint
CREATE DATABASE IF NOT EXISTS `cloudprint` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 创建数据库(通常需要超级用户权限,也可以手动创建)
USE `cloudprint`; -- CREATE DATABASE cloudprint WITH ENCODING = 'UTF8';
-- \c cloudprint
-- 用户表 -- 用户表
CREATE TABLE IF NOT EXISTS `user` ( CREATE TABLE IF NOT EXISTS "user" (
`id` INT PRIMARY KEY AUTO_INCREMENT, "id" SERIAL PRIMARY KEY,
`openid` VARCHAR(64) UNIQUE NOT NULL COMMENT '微信OpenID', "openid" VARCHAR(64) UNIQUE NOT NULL,
`nickname` VARCHAR(128) DEFAULT '' COMMENT '昵称', "nickname" VARCHAR(128) DEFAULT '',
`avatar` VARCHAR(512) DEFAULT '' COMMENT '头像URL', "avatar" VARCHAR(512) DEFAULT '',
`balance` DECIMAL(10,2) DEFAULT 0.00 COMMENT '余额', "balance" DECIMAL(10,2) DEFAULT 0.00,
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP, "created_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, "updated_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
INDEX `idx_openid` (`openid`) );
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; CREATE INDEX IF NOT EXISTS "idx_user_openid" ON "user" ("openid");
-- 管理员表 -- 管理员表
CREATE TABLE IF NOT EXISTS `admin` ( CREATE TABLE IF NOT EXISTS "admin" (
`id` INT PRIMARY KEY AUTO_INCREMENT, "id" SERIAL PRIMARY KEY,
`username` VARCHAR(64) UNIQUE NOT NULL COMMENT '用户名', "username" VARCHAR(64) UNIQUE NOT NULL,
`password_hash` VARCHAR(256) NOT NULL COMMENT '密码哈希', "password_hash" VARCHAR(256) NOT NULL,
`nickname` VARCHAR(128) DEFAULT '' COMMENT '昵称', "nickname" VARCHAR(128) DEFAULT '',
`role` TINYINT DEFAULT 1 COMMENT '1:普通管理员 2:超级管理员', "role" SMALLINT DEFAULT 1,
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP, "created_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
INDEX `idx_username` (`username`) );
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='管理员表'; CREATE INDEX IF NOT EXISTS "idx_admin_username" ON "admin" ("username");
-- 打印机表 -- 打印机表
CREATE TABLE IF NOT EXISTS `printer` ( CREATE TABLE IF NOT EXISTS "printer" (
`id` INT PRIMARY KEY AUTO_INCREMENT, "id" SERIAL PRIMARY KEY,
`name` VARCHAR(128) UNIQUE NOT NULL COMMENT '系统打印机名', "name" VARCHAR(128) UNIQUE NOT NULL,
`display_name` VARCHAR(128) NOT NULL COMMENT '显示名称', "display_name" VARCHAR(128) NOT NULL,
`api_key` VARCHAR(64) UNIQUE NOT NULL COMMENT '客户端认证Key', "api_key" VARCHAR(64) UNIQUE NOT NULL,
`is_active` TINYINT DEFAULT 1 COMMENT '是否启用', "is_active" BOOLEAN DEFAULT TRUE,
`status` TINYINT DEFAULT 0 COMMENT '0:空闲 1:打印中 2:离线', "status" SMALLINT DEFAULT 0,
`last_heartbeat` DATETIME DEFAULT NULL COMMENT '最后心跳时间', "last_heartbeat" TIMESTAMP DEFAULT NULL,
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP, "created_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
INDEX `idx_name` (`name`), );
INDEX `idx_api_key` (`api_key`) CREATE INDEX IF NOT EXISTS "idx_printer_name" ON "printer" ("name");
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='打印机表'; CREATE INDEX IF NOT EXISTS "idx_printer_api_key" ON "printer" ("api_key");
-- 订单表
CREATE TABLE IF NOT EXISTS `order` (
`id` INT PRIMARY KEY AUTO_INCREMENT,
`order_no` VARCHAR(32) UNIQUE NOT NULL COMMENT '订单号',
`user_id` INT NOT NULL COMMENT '用户ID',
`status` TINYINT DEFAULT 0 COMMENT '0:待支付 1:已支付 2:打印中 3:已完成 4:已取消',
`total_amount` DECIMAL(10,2) NOT NULL COMMENT '总金额',
`remark` VARCHAR(512) DEFAULT '' COMMENT '备注',
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
`paid_at` DATETIME DEFAULT NULL COMMENT '支付时间',
INDEX `idx_order_no` (`order_no`),
INDEX `idx_user_id` (`user_id`),
INDEX `idx_status` (`status`),
INDEX `idx_created_at` (`created_at`),
FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';
-- 订单明细表
CREATE TABLE IF NOT EXISTS `order_item` (
`id` INT PRIMARY KEY AUTO_INCREMENT,
`order_id` INT NOT NULL COMMENT '订单ID',
`file_id` INT NOT NULL COMMENT '文件ID',
`printer_id` INT DEFAULT NULL COMMENT '打印机ID',
`copies` INT DEFAULT 1 COMMENT '份数',
`color` TINYINT DEFAULT 0 COMMENT '是否彩色 0:黑白 1:彩色',
`duplex` TINYINT DEFAULT 0 COMMENT '是否双面 0:单面 1:双面',
`page_range` VARCHAR(64) DEFAULT 'all' COMMENT '页面范围',
`price` DECIMAL(10,2) NOT NULL COMMENT '价格',
INDEX `idx_order_id` (`order_id`),
INDEX `idx_file_id` (`file_id`),
FOREIGN KEY (`order_id`) REFERENCES `order`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`file_id`) REFERENCES `file`(`id`) ON DELETE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单明细表';
-- 文件表 -- 文件表
CREATE TABLE IF NOT EXISTS `file` ( CREATE TABLE IF NOT EXISTS "file" (
`id` INT PRIMARY KEY AUTO_INCREMENT, "id" SERIAL PRIMARY KEY,
`file_name` VARCHAR(256) NOT NULL COMMENT '文件名', "file_name" VARCHAR(256) NOT NULL,
`file_path` VARCHAR(512) NOT NULL COMMENT '文件路径', "file_path" VARCHAR(512) NOT NULL,
`file_type` VARCHAR(32) NOT NULL COMMENT '文件类型', "file_type" VARCHAR(32) NOT NULL,
`file_size` BIGINT NOT NULL COMMENT '文件大小(字节)', "file_size" BIGINT NOT NULL,
`uploader_type` VARCHAR(16) NOT NULL COMMENT '上传者类型: user/admin/system', "uploader_type" VARCHAR(16) NOT NULL,
`uploader_id` INT NOT NULL COMMENT '上传者ID', "uploader_id" INTEGER NOT NULL,
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP, "created_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
INDEX `idx_uploader` (`uploader_type`, `uploader_id`), );
INDEX `idx_file_type` (`file_type`) CREATE INDEX IF NOT EXISTS "idx_file_uploader" ON "file" ("uploader_type", "uploader_id");
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文件表'; CREATE INDEX IF NOT EXISTS "idx_file_type" ON "file" ("file_type");
-- 订单表
CREATE TABLE IF NOT EXISTS "order" (
"id" SERIAL PRIMARY KEY,
"order_no" VARCHAR(32) UNIQUE NOT NULL,
"user_id" INTEGER NOT NULL,
"status" SMALLINT DEFAULT 0,
"total_amount" DECIMAL(10,2) NOT NULL,
"remark" VARCHAR(512) DEFAULT '',
"created_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
"paid_at" TIMESTAMP DEFAULT NULL,
FOREIGN KEY ("user_id") REFERENCES "user"("id") ON DELETE RESTRICT
);
CREATE INDEX IF NOT EXISTS "idx_order_order_no" ON "order" ("order_no");
CREATE INDEX IF NOT EXISTS "idx_order_user_id" ON "order" ("user_id");
CREATE INDEX IF NOT EXISTS "idx_order_status" ON "order" ("status");
CREATE INDEX IF NOT EXISTS "idx_order_created_at" ON "order" ("created_at");
-- 订单明细表
CREATE TABLE IF NOT EXISTS "order_item" (
"id" SERIAL PRIMARY KEY,
"order_id" INTEGER NOT NULL,
"file_id" INTEGER NOT NULL,
"printer_id" INTEGER DEFAULT NULL,
"copies" INTEGER DEFAULT 1,
"color" BOOLEAN DEFAULT FALSE,
"duplex" BOOLEAN DEFAULT FALSE,
"page_range" VARCHAR(64) DEFAULT 'all',
"price" DECIMAL(10,2) NOT NULL,
FOREIGN KEY ("order_id") REFERENCES "order"("id") ON DELETE CASCADE,
FOREIGN KEY ("file_id") REFERENCES "file"("id") ON DELETE RESTRICT
);
CREATE INDEX IF NOT EXISTS "idx_order_item_order_id" ON "order_item" ("order_id");
CREATE INDEX IF NOT EXISTS "idx_order_item_file_id" ON "order_item" ("file_id");
-- 文库分类表 -- 文库分类表
CREATE TABLE IF NOT EXISTS `library_category` ( CREATE TABLE IF NOT EXISTS "library_category" (
`id` INT PRIMARY KEY AUTO_INCREMENT, "id" SERIAL PRIMARY KEY,
`name` VARCHAR(128) NOT NULL COMMENT '分类名称', "name" VARCHAR(128) NOT NULL,
`icon` VARCHAR(128) DEFAULT '' COMMENT '图标', "icon" VARCHAR(128) DEFAULT '',
`sort_order` INT DEFAULT 0 COMMENT '排序', "sort_order" INTEGER DEFAULT 0,
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP "created_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文库分类表'; );
-- 文库文件表 -- 文库文件表
CREATE TABLE IF NOT EXISTS `library_file` ( CREATE TABLE IF NOT EXISTS "library_file" (
`id` INT PRIMARY KEY AUTO_INCREMENT, "id" SERIAL PRIMARY KEY,
`category_id` INT NOT NULL COMMENT '分类ID', "category_id" INTEGER NOT NULL,
`file_id` INT NOT NULL COMMENT '文件ID', "file_id" INTEGER NOT NULL,
`title` VARCHAR(256) NOT NULL COMMENT '标题', "title" VARCHAR(256) NOT NULL,
`description` VARCHAR(512) DEFAULT '' COMMENT '描述', "description" VARCHAR(512) DEFAULT '',
`page_count` INT DEFAULT 0 COMMENT '页数', "page_count" INTEGER DEFAULT 0,
`download_count` INT DEFAULT 0 COMMENT '下载次数', "download_count" INTEGER DEFAULT 0,
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP, "created_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX `idx_category_id` (`category_id`), FOREIGN KEY ("category_id") REFERENCES "library_category"("id") ON DELETE CASCADE,
INDEX `idx_file_id` (`file_id`), FOREIGN KEY ("file_id") REFERENCES "file"("id") ON DELETE CASCADE
FOREIGN KEY (`category_id`) REFERENCES `library_category`(`id`) ON DELETE CASCADE, );
FOREIGN KEY (`file_id`) REFERENCES `file`(`id`) ON DELETE CASCADE CREATE INDEX IF NOT EXISTS "idx_library_file_category_id" ON "library_file" ("category_id");
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文库文件表'; CREATE INDEX IF NOT EXISTS "idx_library_file_file_id" ON "library_file" ("file_id");
-- 打印任务表 -- 打印任务表
CREATE TABLE IF NOT EXISTS `print_job` ( CREATE TABLE IF NOT EXISTS "print_job" (
`id` INT PRIMARY KEY AUTO_INCREMENT, "id" SERIAL PRIMARY KEY,
`job_no` VARCHAR(32) UNIQUE NOT NULL COMMENT '任务号', "job_no" VARCHAR(32) UNIQUE NOT NULL,
`printer_id` INT NOT NULL COMMENT '打印机ID', "printer_id" INTEGER NOT NULL,
`order_item_id` INT NOT NULL COMMENT '订单明细ID', "order_item_id" INTEGER NOT NULL,
`status` VARCHAR(16) DEFAULT 'pending' COMMENT 'pending/printing/completed/failed/cancelled', "status" VARCHAR(16) DEFAULT 'pending',
`progress` INT DEFAULT 0 COMMENT '进度(0-100)', "progress" INTEGER DEFAULT 0,
`error_msg` VARCHAR(512) DEFAULT '' COMMENT '错误信息', "error_msg" VARCHAR(512) DEFAULT '',
`started_at` DATETIME DEFAULT NULL COMMENT '开始时间', "started_at" TIMESTAMP DEFAULT NULL,
`completed_at` DATETIME DEFAULT NULL COMMENT '完成时间', "completed_at" TIMESTAMP DEFAULT NULL,
INDEX `idx_job_no` (`job_no`), FOREIGN KEY ("printer_id") REFERENCES "printer"("id") ON DELETE RESTRICT,
INDEX `idx_printer_id` (`printer_id`), FOREIGN KEY ("order_item_id") REFERENCES "order_item"("id") ON DELETE RESTRICT
INDEX `idx_status` (`status`), );
FOREIGN KEY (`printer_id`) REFERENCES `printer`(`id`) ON DELETE RESTRICT, CREATE INDEX IF NOT EXISTS "idx_print_job_job_no" ON "print_job" ("job_no");
FOREIGN KEY (`order_item_id`) REFERENCES `order_item`(`id`) ON DELETE RESTRICT CREATE INDEX IF NOT EXISTS "idx_print_job_printer_id" ON "print_job" ("printer_id");
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='打印任务表'; CREATE INDEX IF NOT EXISTS "idx_print_job_status" ON "print_job" ("status");
-- 价目表 -- 价目表
CREATE TABLE IF NOT EXISTS `price_list` ( CREATE TABLE IF NOT EXISTS "price_list" (
`id` INT PRIMARY KEY AUTO_INCREMENT, "id" SERIAL PRIMARY KEY,
`name` VARCHAR(128) NOT NULL COMMENT '项目名称', "name" VARCHAR(128) NOT NULL,
`type` VARCHAR(32) NOT NULL COMMENT '纸张类型: A4/A3/照片纸', "type" VARCHAR(32) NOT NULL,
`price` DECIMAL(10,2) NOT NULL COMMENT '单价', "price" DECIMAL(10,2) NOT NULL,
`unit` VARCHAR(16) NOT NULL COMMENT '单位: 页/张/份', "unit" VARCHAR(16) NOT NULL,
`color_enabled` TINYINT DEFAULT 1 COMMENT '是否支持彩色', "color_enabled" BOOLEAN DEFAULT TRUE,
`is_active` TINYINT DEFAULT 1 COMMENT '是否启用', "is_active" BOOLEAN DEFAULT TRUE,
`sort_order` INT DEFAULT 0 COMMENT '排序', "sort_order" INTEGER DEFAULT 0,
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP, "created_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
INDEX `idx_type` (`type`), );
INDEX `idx_is_active` (`is_active`) CREATE INDEX IF NOT EXISTS "idx_price_list_type" ON "price_list" ("type");
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='价目表'; CREATE INDEX IF NOT EXISTS "idx_price_list_is_active" ON "price_list" ("is_active");
-- 支付记录表 -- 支付记录表
CREATE TABLE IF NOT EXISTS `payment` ( CREATE TABLE IF NOT EXISTS "payment" (
`id` INT PRIMARY KEY AUTO_INCREMENT, "id" SERIAL PRIMARY KEY,
`order_no` VARCHAR(32) UNIQUE NOT NULL COMMENT '订单号', "order_no" VARCHAR(32) UNIQUE NOT NULL,
`pay_type` VARCHAR(16) DEFAULT 'wechat' COMMENT '支付类型: wechat/balance', "pay_type" VARCHAR(16) DEFAULT 'wechat',
`pay_status` TINYINT DEFAULT 0 COMMENT '0:待支付 1:支付中 2:成功 3:失败', "pay_status" SMALLINT DEFAULT 0,
`transaction_id` VARCHAR(64) DEFAULT '' COMMENT '微信交易号', "transaction_id" VARCHAR(64) DEFAULT '',
`paid_at` DATETIME DEFAULT NULL COMMENT '支付时间', "paid_at" TIMESTAMP DEFAULT NULL,
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP, "created_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
INDEX `idx_order_no` (`order_no`), );
INDEX `idx_pay_status` (`pay_status`) CREATE INDEX IF NOT EXISTS "idx_payment_order_no" ON "payment" ("order_no");
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='支付记录表'; CREATE INDEX IF NOT EXISTS "idx_payment_pay_status" ON "payment" ("pay_status");
-- 初始化管理员账号 (密码: admin123) -- 初始化管理员账号 (密码: admin123)
-- 密码哈希使用 bcrypt 生成 -- 密码哈希使用 bcrypt 生成
INSERT INTO `admin` (`username`, `password_hash`, `nickname`, `role`) VALUES INSERT INTO "admin" ("username", "password_hash", "nickname", "role") VALUES
('admin', '$2a$10$N9qo8uLOickgx2ZMRZoMy.MqrqJlmZ2j3.N9qo8uLOickgx2ZMRZoG', '系统管理员', 2); ('admin', '$2a$10$N9qo8uLOickgx2ZMRZoMy.MqrqJlmZ2j3.N9qo8uLOickgx2ZMRZoG', '系统管理员', 2)
ON CONFLICT ("username") DO NOTHING;
-- 初始化默认价目表 -- 初始化默认价目表
INSERT INTO `price_list` (`name`, `type`, `price`, `unit`, `color_enabled`, `is_active`, `sort_order`) VALUES INSERT INTO "price_list" ("name", "type", "price", "unit", "color_enabled", "is_active", "sort_order") VALUES
('A4黑白打印', 'A4', 0.10, '', 0, 1, 1), ('A4黑白打印', 'A4', 0.10, '', FALSE, TRUE, 1),
('A4彩色打印', 'A4', 0.30, '', 1, 1, 2), ('A4彩色打印', 'A4', 0.30, '', TRUE, TRUE, 2),
('A3黑白打印', 'A3', 0.20, '', 0, 1, 3), ('A3黑白打印', 'A3', 0.20, '', FALSE, TRUE, 3),
('A3彩色打印', 'A3', 0.60, '', 1, 1, 4), ('A3彩色打印', 'A3', 0.60, '', TRUE, TRUE, 4),
('照片打印', '照片纸', 2.00, '', 1, 1, 5); ('照片打印', '照片纸', 2.00, '', TRUE, TRUE, 5)
ON CONFLICT DO NOTHING;
-- 初始化默认文库分类 -- 初始化默认文库分类
INSERT INTO `library_category` (`name`, `icon`, `sort_order`) VALUES INSERT INTO "library_category" ("name", "icon", "sort_order") VALUES
('学习资料', 'book', 1), ('学习资料', 'book', 1),
('考试真题', 'exam', 2), ('考试真题', 'exam', 2),
('简历模板', 'resume', 3), ('简历模板', 'resume', 3),
('合同协议', 'contract', 4), ('合同协议', 'contract', 4),
('其他资料', 'other', 5); ('其他资料', 'other', 5)
ON CONFLICT DO NOTHING;