更新为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
import (
"context"
"encoding/json"
"flag"
"fmt"
"log"
"net"
"net/http"
"os"
"os/signal"
"syscall"
"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/service"
"github.com/cloudprint/cloudprint-backend/internal/model"
grpcService "github.com/cloudprint/cloudprint-backend/internal/grpc/service"
"github.com/cloudprint/cloudprint-backend/internal/repository"
"github.com/cloudprint/cloudprint-backend/internal/service"
"github.com/cloudprint/cloudprint-backend/pkg/jwt"
@@ -22,7 +23,10 @@ import (
pb "github.com/cloudprint/cloudprint-backend/proto/generated"
"go.uber.org/zap"
"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/logger"
)
@@ -47,23 +51,6 @@ func main() {
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
userRepo := repository.NewUserRepository(db)
adminRepo := repository.NewAdminRepository(db)
@@ -105,14 +92,17 @@ func main() {
)
// Register gRPC services
pb.RegisterAuthServiceServer(grpcServer, service.NewAuthServiceServer(authService))
pb.RegisterFileServiceServer(grpcServer, service.NewFileServiceServer(fileService))
pb.RegisterOrderServiceServer(grpcServer, service.NewOrderServiceServer(orderService))
pb.RegisterPrintServiceServer(grpcServer, service.NewPrintServiceServer(printService))
pb.RegisterLibraryServiceServer(grpcServer, service.NewLibraryServiceServer(libraryRepo, fileRepo))
pb.RegisterPriceServiceServer(grpcServer, service.NewPriceServiceServer(priceRepo))
pb.RegisterPayServiceServer(grpcServer, service.NewPayServiceServer(orderService, paymentRepo, weChatPay, userRepo))
pb.RegisterAdminServiceServer(grpcServer, service.NewAdminServiceServer(authService, orderRepo, printerRepo))
pb.RegisterAuthServiceServer(grpcServer, grpcService.NewAuthServiceServer(authService))
pb.RegisterFileServiceServer(grpcServer, grpcService.NewFileServiceServer(fileService))
pb.RegisterOrderServiceServer(grpcServer, grpcService.NewOrderServiceServer(orderService))
pb.RegisterPrintServiceServer(grpcServer, grpcService.NewPrintServiceServer(printService))
pb.RegisterLibraryServiceServer(grpcServer, grpcService.NewLibraryServiceServer(libraryRepo, fileRepo))
pb.RegisterPriceServiceServer(grpcServer, grpcService.NewPriceServiceServer(priceRepo))
pb.RegisterPayServiceServer(grpcServer, grpcService.NewPayServiceServer(orderService, paymentRepo, weChatPay, userRepo))
pb.RegisterAdminServiceServer(grpcServer, grpcService.NewAdminServiceServer(authService, orderRepo, printerRepo))
// Initialize HTTP gateway
gateway := newHTTPGateway(grpcServer, jwtManager)
// Start server
addr := fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.Port)
@@ -125,14 +115,29 @@ func main() {
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
// HTTP server for gateway
httpServer := &http.Server{
Handler: gateway,
}
// Start HTTP server in goroutine
go func() {
<-sigCh
grpcServer.GracefulStop()
zapLogger.Info("Starting HTTP gateway", zap.String("address", addr))
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))
if err := grpcServer.Serve(lis); err != nil {
log.Fatalf("Failed to serve: %v", err)
// Wait for shutdown signal
<-sigCh
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,
})
if err != nil {
@@ -161,3 +166,301 @@ func initDB(cfg *config.Config) (*gorm.DB, error) {
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 {
return fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
d.Username, d.Password, d.Host, d.Port, d.Database)
return fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable TimeZone=Asia/Shanghai",
d.Host, d.Port, d.Username, d.Password, d.Database)
}
type RedisConfig struct {

View File

@@ -1,6 +1,6 @@
module github.com/cloudprint/cloudprint-backend
go 1.24.0
go 1.25.0
require (
github.com/golang-jwt/jwt/v5 v5.2.0
@@ -8,18 +8,21 @@ require (
github.com/iGoogle-ink/gopay v1.5.14
github.com/spf13/viper v1.18.2
go.uber.org/zap v1.26.0
golang.org/x/crypto v0.46.0
google.golang.org/grpc v1.79.3
golang.org/x/crypto v0.49.0
google.golang.org/grpc v1.80.0
google.golang.org/protobuf v1.36.11
gorm.io/driver/mysql v1.5.2
gorm.io/gorm v1.25.5
gorm.io/driver/postgres v1.5.9
gorm.io/gorm v1.25.10
)
require (
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/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/now v1.1.5 // 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/cast v1.6.0 // 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
go.opentelemetry.io/otel v1.43.0 // indirect
go.uber.org/multierr v1.10.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/net v0.48.0 // indirect
golang.org/x/sys v0.39.0 // indirect
golang.org/x/text v0.32.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
golang.org/x/net v0.52.0 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.42.0 // 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/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-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.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/gogo/protobuf v1.1.1/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/gotil v1.0.2 h1:z58bvmhq31twxecMkyWU5Mh8H7T9SGPOTvUuAw9vB7M=
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/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
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.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.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/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
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/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
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.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
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/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/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
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/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo=
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-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.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
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-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
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-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.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
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-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
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-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.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-20180905080454-ebe1bf3edb33/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-20200116001909-b77594299b42/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.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
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.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.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
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/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=
@@ -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-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
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.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
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/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
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-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
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-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 h1:RmoJA1ujG+/lRGNfUnOMfhCy5EipVMyvUE+KNbPbTlw=
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.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
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.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
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.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
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/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
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 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-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/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
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/mysql v1.5.2/go.mod h1:pQLhh1Ut/WUAySdTHwBpBv6+JKcj+ua4ZFx1QQTBzb8=
gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls=
gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
gorm.io/driver/postgres v1.5.9 h1:DkegyItji119OlcaLjqN11kHoUgZ/j13E0jkJZgD6A8=
gorm.io/driver/postgres v1.5.9/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s=
gorm.io/gorm v1.25.10/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-20190102054323-c2f93a96b099/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 uploaderID int32
var chunks [][]byte
@@ -32,7 +32,7 @@ func (s *FileServiceServer) Upload(stream pb.FileService_UploadServer) (*pb.Uplo
break
}
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 == "" {
@@ -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)
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,
FilePath: file.FilePath,
FileUrl: file.FilePath,
}, nil
})
}
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")
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 {
return 0, 0, 0, 0, 0, fmt.Errorf("count today orders failed: %w", err)
}
// 今日收入
if err = r.db.WithContext(ctx).Model(&model.Order{}).
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 {
return 0, 0, 0, 0, 0, fmt.Errorf("sum today revenue failed: %w", err)
}

View File

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