wanghongzhi 1 ay önce
ebeveyn
işleme
8c07f12655
64 değiştirilmiş dosya ile 11969 ekleme ve 0 silme
  1. 32 0
      config/config-dev.yaml
  2. 32 0
      config/config-prod.yaml
  3. 63 0
      config/config.go
  4. 59 0
      go.mod
  5. 276 0
      go.sum
  6. 146 0
      handlers/auth.go
  7. 21 0
      handlers/captcha.go
  8. 229 0
      handlers/customer.go
  9. 88 0
      handlers/dept.go
  10. 104 0
      handlers/device.go
  11. 152 0
      handlers/menu.go
  12. 93 0
      handlers/minio.go
  13. 305 0
      handlers/oursourcing_plan.go
  14. 515 0
      handlers/process_route.go
  15. 246 0
      handlers/process_stage.go
  16. 356 0
      handlers/product_bom.go
  17. 202 0
      handlers/product_material.go
  18. 479 0
      handlers/product_plan.go
  19. 555 0
      handlers/product_preplan.go
  20. 357 0
      handlers/purchase_order.go
  21. 314 0
      handlers/purchase_plan.go
  22. 232 0
      handlers/quality_inspect_program.go
  23. 68 0
      handlers/role_menu.go
  24. 113 0
      handlers/roles.go
  25. 300 0
      handlers/sale_order.go
  26. 259 0
      handlers/sale_performance.go
  27. 221 0
      handlers/sale_plan.go
  28. 161 0
      handlers/tenant.go
  29. 325 0
      handlers/user.go
  30. 155 0
      handlers/warehouse.go
  31. 213 0
      handlers/warehouse_material.go
  32. BIN
      mes_win7.exe
  33. 53 0
      middleware/auth.go
  34. 107 0
      models/basic_data.go
  35. 92 0
      models/enum.go
  36. 41 0
      models/extend_vo.go
  37. 10 0
      models/flow_no.go
  38. 12 0
      models/minio_file.go
  39. 30 0
      models/outsourcing.go
  40. 64 0
      models/process.go
  41. 62 0
      models/product.go
  42. 66 0
      models/purchase.go
  43. 80 0
      models/sale.go
  44. 63 0
      models/system.go
  45. 47 0
      models/user.go
  46. 53 0
      models/warehouse.go
  47. 63 0
      services/captcha.go
  48. 77 0
      services/flow_no.go
  49. 165 0
      services/init_service.go
  50. 1184 0
      services/jdbc_client.go
  51. 142 0
      services/minio_service.go
  52. 126 0
      services/pool.go
  53. 94 0
      services/user.go
  54. 169 0
      utils/a_test.go
  55. 99 0
      utils/b_test.go
  56. 89 0
      utils/c_test.go
  57. 75 0
      utils/common.go
  58. 19 0
      utils/log.go
  59. 11 0
      utils/network_devices.txt
  60. 2 0
      utils/postgres_test.go
  61. 46 0
      utils/response.go
  62. 1631 0
      utils/result_set.go
  63. 69 0
      utils/rsa.go
  64. 457 0
      utils/scan_test.go

+ 32 - 0
config/config-dev.yaml

@@ -0,0 +1,32 @@
+# 服务器配置
+server:
+  port: "0.0.0.0:10160"
+  web_log: true
+
+# JWT配置
+jwt:
+  secret_key: "your-very-long-and-secure-256-bit-secret-key-here"
+  access_token_exp: 3600s  # 1小时,可以使用1h、60m、3600s等格式
+  refresh_token_exp: 604800s  # 7天
+
+# Redis配置
+redis:
+  addr: "localhost:6379"
+  password: ""
+  db: 0
+
+# 数据库配置 
+database:
+  host: "119.167.167.11"
+  port: 43307
+  user: "root"
+  password: "_Developer0532"
+  name: "mes"
+  log: false
+
+# minio 配置
+minio:
+  addr: "minio.qdeasydo.com"
+  user: "minioadm"
+  password: "minio@123"
+  usessl: false

+ 32 - 0
config/config-prod.yaml

@@ -0,0 +1,32 @@
+# 服务器配置
+server:
+  port: "0.0.0.0:10160"
+  web_log: true
+
+# JWT配置
+jwt:
+  secret_key: "your-very-long-and-secure-256-bit-secret-key-here"
+  access_token_exp: 3600s  # 1小时,可以使用1h、60m、3600s等格式
+  refresh_token_exp: 604800s  # 7天
+
+# Redis配置
+redis:
+  addr: "127.0.0.1:6379"
+  password: "_Developer0532"
+  db: 0
+
+# 数据库配置 (可选)
+database:
+  host: "172.16.100.151"
+  port: 3306
+  user: "root"
+  password: "_Developer0532"
+  name: "mes"
+  log: false
+
+# minio 配置
+minio:
+  addr: "minio.qdeasydo.com"
+  user: "minioadm"
+  password: "minio@123"
+  usessl: false

+ 63 - 0
config/config.go

@@ -0,0 +1,63 @@
+package config
+
+import (
+    "fmt"
+    "os"
+    "time"
+    
+    "gopkg.in/yaml.v3"
+)
+
+type Config struct {
+    Server struct {
+        Port string `yaml:"port"`
+        WebLog bool `yaml:"web_log"`
+
+    } `yaml:"server"`
+    
+    JWT struct {
+        SecretKey        string        `yaml:"secret_key"`
+        AccessTokenExp   time.Duration `yaml:"access_token_exp"`   // 纳秒为单位
+        RefreshTokenExp  time.Duration `yaml:"refresh_token_exp"`  // 纳秒为单位
+    } `yaml:"jwt"`
+    
+    Redis struct {
+        Addr     string `yaml:"addr"`
+        Password string `yaml:"password"`
+        DB       int    `yaml:"db"`
+    } `yaml:"redis"`
+    
+    Database struct {
+        Host     string `yaml:"host"`
+        Port     int    `yaml:"port"`
+        User     string `yaml:"user"`
+        Password string `yaml:"password"`
+        Name     string `yaml:"name"`
+        Log      bool `yaml:"log"`
+    } `yaml:"database"`
+    
+    Minio struct {
+        Addr     string `yaml:"addr"`
+        User     string `yaml:"user"`
+        Password string `yaml:"password"`
+        UseSSL   bool   `yaml:"usessl"`
+    } `yaml:"minio"`
+}
+
+// Load 从配置文件和环境变量加载配置
+func Load(configPath string) (*Config, error) {
+    cfg := &Config{}
+    // 如果提供了配置文件路径,则从文件读取
+    if configPath != "" {
+        data, err := os.ReadFile(configPath)
+        if err != nil {
+            return nil, fmt.Errorf("读取配置文件失败: %w", err)
+        }
+        
+        if err := yaml.Unmarshal(data, cfg); err != nil {
+            return nil, fmt.Errorf("解析配置文件失败: %w", err)
+        }
+    }
+    
+    return cfg, nil
+}

+ 59 - 0
go.mod

@@ -0,0 +1,59 @@
+module easydo-echo_win7
+
+go 1.20
+
+require (
+	github.com/apache/rocketmq-client-go/v2 v2.1.2
+	github.com/go-redis/redis/v8 v8.11.5
+	github.com/go-sql-driver/mysql v1.7.1
+	github.com/google/uuid v1.3.0
+	github.com/gorilla/sessions v1.2.1
+	github.com/jmoiron/sqlx v1.3.5
+	github.com/labstack/echo-contrib v0.13.1
+	github.com/labstack/echo/v4 v4.9.1
+	github.com/minio/minio-go/v7 v7.0.50
+	github.com/mojocn/base64Captcha v1.3.6
+	golang.org/x/crypto v0.11.0
+	gopkg.in/yaml.v3 v3.0.1
+)
+
+require (
+	github.com/cespare/xxhash/v2 v2.2.0 // indirect
+	github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
+	github.com/dustin/go-humanize v1.0.1 // indirect
+	github.com/emirpasic/gods v1.12.0 // indirect
+	github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
+	github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
+	github.com/golang/mock v1.3.1 // indirect
+	github.com/gorilla/context v1.1.1 // indirect
+	github.com/gorilla/securecookie v1.1.1 // indirect
+	github.com/json-iterator/go v1.1.12 // indirect
+	github.com/klauspost/compress v1.16.0 // indirect
+	github.com/klauspost/cpuid/v2 v2.2.4 // indirect
+	github.com/labstack/gommon v0.4.0 // indirect
+	github.com/mattn/go-colorable v0.1.13 // indirect
+	github.com/mattn/go-isatty v0.0.19 // indirect
+	github.com/minio/md5-simd v1.1.2 // indirect
+	github.com/minio/sha256-simd v1.0.0 // indirect
+	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+	github.com/modern-go/reflect2 v1.0.2 // indirect
+	github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
+	github.com/pkg/errors v0.9.1 // indirect
+	github.com/rs/xid v1.4.0 // indirect
+	github.com/sirupsen/logrus v1.9.0 // indirect
+	github.com/stretchr/testify v1.8.0 // indirect
+	github.com/tidwall/gjson v1.13.0 // indirect
+	github.com/tidwall/match v1.1.1 // indirect
+	github.com/tidwall/pretty v1.2.0 // indirect
+	github.com/valyala/bytebufferpool v1.0.0 // indirect
+	github.com/valyala/fasttemplate v1.2.2 // indirect
+	go.uber.org/atomic v1.9.0 // indirect
+	golang.org/x/image v0.13.0 // indirect
+	golang.org/x/net v0.12.0 // indirect
+	golang.org/x/sys v0.10.0 // indirect
+	golang.org/x/text v0.13.0 // indirect
+	golang.org/x/time v0.3.0 // indirect
+	gopkg.in/ini.v1 v1.67.0 // indirect
+	gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
+	stathat.com/c/consistent v1.0.0 // indirect
+)

+ 276 - 0
go.sum

@@ -0,0 +1,276 @@
+github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
+github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
+github.com/apache/rocketmq-client-go/v2 v2.1.2 h1:yt73olKe5N6894Dbm+ojRf/JPiP0cxfDNNffKwhpJVg=
+github.com/apache/rocketmq-client-go/v2 v2.1.2/go.mod h1:6I6vgxHR3hzrvn+6n/4mrhS+UTulzK/X9LB2Vk1U5gE=
+github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
+github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
+github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
+github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
+github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
+github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
+github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
+github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
+github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
+github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
+github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
+github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
+github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
+github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
+github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
+github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
+github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
+github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
+github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s=
+github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
+github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
+github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
+github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
+github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
+github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
+github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
+github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4=
+github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
+github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
+github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
+github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
+github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/labstack/echo-contrib v0.13.1 h1:9TktDom9FJKhkKO45YvV4klW8IedtSUp/k85gZVdZ28=
+github.com/labstack/echo-contrib v0.13.1/go.mod h1:LdM7aOHAYLOPmAAGXXG9TuN4h5sh6dPEu4pb6W2HKuU=
+github.com/labstack/echo/v4 v4.9.1 h1:GliPYSpzGKlyOhqIbG8nmHBo3i1saKWFOgh41AN3b+Y=
+github.com/labstack/echo/v4 v4.9.1/go.mod h1:Pop5HLc+xoc4qhTZ1ip6C0RtP7Z+4VzRLWZZFKqbbjo=
+github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
+github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
+github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
+github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
+github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
+github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
+github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
+github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
+github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
+github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg=
+github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
+github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
+github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
+github.com/minio/minio-go/v7 v7.0.50 h1:4IL4V8m/kI90ZL6GupCARZVrBv8/XrcKcJhaJ3iz68k=
+github.com/minio/minio-go/v7 v7.0.50/go.mod h1:IbbodHyjUAguneyucUaahv+VMNs/EOTV9du7A7/Z3HU=
+github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
+github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/mojocn/base64Captcha v1.3.6 h1:gZEKu1nsKpttuIAQgWHO+4Mhhls8cAKyiV2Ew03H+Tw=
+github.com/mojocn/base64Captcha v1.3.6/go.mod h1:i5CtHvm+oMbj1UzEPXaA8IH/xHFZ3DGY3Wh3dBpZ28E=
+github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
+github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
+github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
+github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
+github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
+github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
+github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
+github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
+github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
+github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
+github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
+github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
+github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
+github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY=
+github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
+github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
+github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
+github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
+github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
+github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
+github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+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.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+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 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/tidwall/gjson v1.13.0 h1:3TFY9yxOQShrvmjdM76K+jc66zJeT6D3/VFFYCGQf7M=
+github.com/tidwall/gjson v1.13.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
+github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
+github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
+github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
+github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
+github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
+github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
+github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
+github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
+github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+go.uber.org/atomic v1.5.1/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
+go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
+go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
+golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
+golang.org/x/image v0.13.0 h1:3cge/F/QTkNLauhf2QoE9zp+7sr+ZcL4HnoZmdwg9sg=
+golang.org/x/image v0.13.0/go.mod h1:6mmbMOeV28HuMTgA6OSRkdXKYw/t5W9Uwn2Yv1r3Yxk=
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
+golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/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-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
+golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
+golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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=
+gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
+gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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=
+stathat.com/c/consistent v1.0.0 h1:ezyc51EGcRPJUxfHGSgJjWzJdj3NiMU9pNfLNGiXV0c=
+stathat.com/c/consistent v1.0.0/go.mod h1:QkzMWzcbB+yQBL2AttO6sgsQS/JSTapcDISJalmCDS0=

+ 146 - 0
handlers/auth.go

@@ -0,0 +1,146 @@
+package handlers
+
+import (
+	"net/http"
+	"time"
+	
+	"easydo-echo_win7/models"
+	"easydo-echo_win7/services"
+	"easydo-echo_win7/utils"
+	
+	"github.com/labstack/echo-contrib/session"
+	"github.com/labstack/echo/v4"
+)
+
+func Add_auth_to_routes(e *echo.Echo) {
+	// 公共API路由(不需要认证)
+	authGroup := e.Group("/auth")
+	authGroup.GET("/health", healthCheck)
+	authGroup.GET("/code", generateCaptcha)
+	authGroup.POST("/login", login)
+	authGroup.POST("/logout", logout)
+	
+}
+
+// HealthCheck 健康检查
+func healthCheck(c echo.Context) error {
+	return c.JSON(http.StatusOK, utils.SuccessResponse("服务运行正常"))
+}
+
+// Login 用户登录
+func login(c echo.Context) error {
+	req := new(models.UserLoginRequest)
+	
+	// 验证请求参数
+	if err := c.Bind(req); err != nil {
+		return c.JSON(http.StatusBadRequest, utils.ErrorResponse("请求参数错误", err.Error()))
+	}
+	username := req.Username
+	password := req.Password
+	captchaID := req.CaptchaID
+	captchaCode := req.CaptchaCode
+	
+	// 验证必填字段
+	if username == "" || password == "" || captchaID == "" || captchaCode == "" {
+		return c.JSON(http.StatusBadRequest, utils.ErrorResponse("请填写完整信息", ""))
+	}
+	
+	// 检查登录尝试
+	if ok, msg := services.CheckLoginAttempts(username); !ok {
+		return c.JSON(http.StatusBadRequest, utils.ErrorResponse(msg, ""))
+	}
+
+	// 验证验证码
+	if !services.VerifyCaptcha(captchaID, captchaCode) {
+		services.RecordLoginAttempt(username, false)
+		return c.JSON(http.StatusBadRequest, utils.ErrorResponse("验证码错误或已失效", ""))
+	}
+
+	// 验证用户凭据
+	user := new(models.SysUser) 
+
+	user.Username = &username
+	err := services.JdbcClient.GetJdbcModel(user)
+
+	if err != nil || *user.ID == 0  || user.TenantId == nil {
+		services.RecordLoginAttempt(username, false)
+		return c.JSON(http.StatusBadRequest, utils.ErrorResponse("用户不存在", ""))
+	}
+	tenant := new(models.Tenant)
+	tenant.ID = user.TenantId
+
+	err = services.JdbcClient.GetJdbcModelById(tenant)
+	if err != nil {
+		return c.JSON(http.StatusBadRequest, utils.ErrorResponse("企业不存在", ""))
+	}
+	if tenant.Status == nil || *tenant.Status != models.Status_Enable {
+		return c.JSON(http.StatusBadRequest, utils.ErrorResponse("企业已被禁用", ""))
+	}
+	user.Tenant = tenant
+
+	// 验证密码
+	if !user.CheckPassword(password) {
+		services.RecordLoginAttempt(username, false)
+		return c.JSON(http.StatusBadRequest, utils.ErrorResponse("用户名或密码错误", ""))
+	}
+	// 创建会话
+	sess, _ := session.Get("auth_session", c)
+	sess.Values["user_id"] = user.ID
+	sess.Values["username"] = user.Username
+	sess.Values["is_authenticated"] = true
+	sess.Values["login_time"] = time.Now().Unix()
+	
+	if err := sess.Save(c.Request(), c.Response()); err != nil {
+		return c.JSON(http.StatusBadRequest, utils.ErrorResponse("会话创建失败", err.Error()))
+	}
+	dept := new(models.SysDept)
+	dept.ID = user.DeptId
+
+	err = services.JdbcClient.GetJdbcModelById(dept)
+	if err != nil {
+		return c.JSON(http.StatusBadRequest, utils.ErrorResponse("用户所属部门不存在", ""))
+	}
+	user.Dept = dept
+
+	paramMap :=  map[string]interface{}{
+		"userId": user.ID,
+	}
+	p_result,_ := services.JdbcClient.GetJdbcList(paramMap,models.SysUsersRoles{})
+	p_list := utils.ConvertInterface[[]models.SysUsersRoles](p_result)
+	role_id_list :=utils.Map(p_list, func(user_role models.SysUsersRoles) int64 {
+		return *user_role.RoleID
+	})
+
+	for k := range paramMap {
+		delete(paramMap, k)
+	}
+	paramMap["idIn"] = role_id_list
+	r_result,_ := services.JdbcClient.GetJdbcList(paramMap,models.SysRole{})
+	role_list := utils.ConvertInterface[[]models.SysRole](r_result)
+	user.RoleList = &role_list
+	// 记录成功登录
+	services.RecordLoginAttempt(username, true)
+
+	// 返回成功响应
+	return c.JSON(http.StatusOK, map[string]interface{}{
+		"token":"-",
+		"user": user,
+		"session_expires": time.Now().Add(7 * 24 * time.Hour).Unix(),
+	})
+	
+}
+
+// Logout 用户登出
+func logout(c echo.Context) error {
+	sess, _ := session.Get("auth_session", c)
+	
+	// 清除会话
+	sess.Options.MaxAge = -1
+	sess.Values = make(map[interface{}]interface{})
+	
+	if err := sess.Save(c.Request(), c.Response()); err != nil {
+		return c.JSON(http.StatusBadRequest, utils.ErrorResponse("登出失败", err.Error()))
+	}
+	
+	return c.JSON(http.StatusOK, utils.SuccessResponse("登出成功"))
+}

+ 21 - 0
handlers/captcha.go

@@ -0,0 +1,21 @@
+package handlers
+
+import (
+	"net/http"
+	
+	"easydo-echo_win7/services"
+	"easydo-echo_win7/utils"
+	
+	"github.com/labstack/echo/v4"
+)
+
+// GenerateCaptcha 生成验证码
+func generateCaptcha(c echo.Context) error {
+	// 生成验证码
+	captcha, err := services.GenerateCaptcha()
+	if err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("生成验证码失败", err.Error()))
+	}
+	
+	return c.JSON(http.StatusOK, captcha)
+}

+ 229 - 0
handlers/customer.go

@@ -0,0 +1,229 @@
+package handlers
+
+import (
+	"net/http"
+
+	"easydo-echo_win7/middleware"
+	"easydo-echo_win7/models"
+	"easydo-echo_win7/services"
+	"easydo-echo_win7/utils"
+
+	"github.com/jmoiron/sqlx"
+	"github.com/labstack/echo-contrib/session"
+	"github.com/labstack/echo/v4"
+)
+
+func Add_customer_to_routes(e *echo.Echo) {
+	group := e.Group("/customer")
+	group.Use(middleware.AuthMiddleware)
+	group.POST("/getPage", customerGetPage)
+	group.POST("/getList", customerGetList)
+	group.POST("/save", customerSave)
+	group.POST("/update", customerUpdate)
+	group.POST("/remove", customerRemove)
+}
+
+func customerGetPage(c echo.Context) error {
+	var paramMap map[string]interface{}
+	if err := c.Bind(&paramMap); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	result, err := services.JdbcClient.GetJdbcPage(paramMap, models.Customer{})
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	list := utils.ConvertInterface[[]models.Customer](result.Records)
+	if len(list) == 0 {
+		list = []models.Customer{}
+	}
+	for i := range list {
+		model := list[i]
+		minioList, err := services.JdbcClient.GetMinioFile(model)
+		if err != nil {
+			utils.PrintSearchFileErr(err)
+		}
+		model.FileList = &minioList
+
+		list[i] = model
+	}
+	result.Records = list
+	return c.JSON(http.StatusOK, result)
+
+}
+
+func customerGetList(c echo.Context) error {
+	var paramMap map[string]interface{}
+	if err := c.Bind(&paramMap); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	result, err := services.JdbcClient.GetJdbcList(paramMap, models.Customer{})
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	list := utils.ConvertInterface[[]models.Customer](result)
+	if len(list) == 0 {
+		return c.JSON(http.StatusOK, []models.Customer{})
+	}
+	return c.JSON(http.StatusOK, list)
+}
+
+func customerSave(c echo.Context) error {
+	tx, _ := services.MYSQL_DB.Beginx()
+
+	customer := new(models.Customer)
+	if err := c.Bind(customer); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	status_enable := models.Status_Enable
+	customer.Status = &status_enable
+	sess, _ := session.Get("auth_session", c)
+	err_msg := customer_generateCode(customer, tx)
+	if err_msg != "" {
+		tx.Rollback()
+		return c.JSON(http.StatusBadRequest, utils.ErrorResponse(err_msg, ""))
+	}
+	user_id, ok := sess.Values["user_id"].(int64)
+	if !ok {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("用户请先登录", ""))
+	}
+	customer.CreateId = &user_id
+	err := services.JdbcClient.JdbcInsert(customer, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	if customer.FileList == nil {
+		tx.Commit()
+		return c.JSON(http.StatusOK, customer)
+	}
+	fileList := *customer.FileList
+	for _, file := range fileList {
+		file.RefId = customer.ID
+		refType := models.File_Ref_Type_Customer
+		file.RefType = &refType
+
+		err := services.JdbcClient.JdbcInsert(&file, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+	}
+	tx.Commit()
+	return c.JSON(http.StatusOK, customer)
+}
+
+func customerUpdate(c echo.Context) error {
+	tx, _ := services.MYSQL_DB.Beginx()
+
+	customer := new(models.Customer)
+	if err := c.Bind(customer); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	sess, _ := session.Get("auth_session", c)
+	user_id, ok := sess.Values["user_id"].(int64)
+	if !ok {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("用户请先登录", ""))
+	}
+	customer.UpdateId = &user_id
+	err := services.JdbcClient.JdbcUpdateById(customer, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	if customer.FileList == nil {
+		tx.Commit()
+		return c.JSON(http.StatusOK, customer)
+	}
+
+	fileList := *customer.FileList
+	for _, file := range fileList {
+		file.RefId = customer.ID
+		refType := models.File_Ref_Type_Customer
+		file.RefType = &refType
+
+		err := services.JdbcClient.JdbcInsert(&file, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+	}
+	tx.Commit()
+	return c.JSON(http.StatusOK, customer)
+}
+
+func customerRemove(c echo.Context) error {
+	tx, _ := services.MYSQL_DB.Beginx()
+
+	customer := new(models.Customer)
+	if err := c.Bind(customer); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+
+	err := services.JdbcClient.JdbcRemoveById(customer, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	minio_file := new(models.MinioFile)
+	minio_file.RefId = customer.ID
+	refType := models.File_Ref_Type_Customer
+	minio_file.RefType = &refType
+	err = services.JdbcClient.JdbcRemove(minio_file, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	tx.Commit()
+	return c.JSON(http.StatusOK, customer)
+}
+
+func customer_generateCode(model *models.Customer, tx *sqlx.Tx) string {
+	sync := new(models.Synchronized)
+	sync.Sync.Lock()
+	defer sync.Sync.Unlock()
+
+	if model.Code != nil && len(*model.Code) > 0 {
+		modelParam := new(models.Customer)
+		modelParam.Code = model.Code
+		modelParam.TenantId = model.TenantId
+		count, err := services.JdbcClient.GetJdbcCount(modelParam, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return "SQL执行失败"
+		}
+		if count > 0 {
+			return "编号已存在,请重新填写"
+		}
+	} else {
+		for {
+			code, err := services.GetFlowNo(models.Flow_No_Type_Customer, *model.TenantId, tx)
+			if err != nil {
+				return "自动生成编码失败"
+			}
+			modelParam := new(models.Customer)
+			modelParam.Code = &code
+			modelParam.TenantId = model.TenantId
+			count, err := services.JdbcClient.GetJdbcCount(modelParam, tx)
+			if err != nil {
+				utils.PrintSqlErr(err)
+				return "SQL执行失败"
+			}
+			if count == 0 {
+				model.Code = &code
+				break
+			}
+		}
+
+	}
+	return ""
+}

+ 88 - 0
handlers/dept.go

@@ -0,0 +1,88 @@
+package handlers
+
+import (
+	"net/http"
+
+	"easydo-echo_win7/middleware"
+	"easydo-echo_win7/models"
+	"easydo-echo_win7/services"
+	"easydo-echo_win7/utils"
+
+	"github.com/labstack/echo/v4"
+)
+
+func Add_dept_to_routes(e *echo.Echo) {
+	group := e.Group("/sysDept")
+	group.Use(middleware.AuthMiddleware)
+	group.POST("/getList", deptGetList)
+	group.POST("/save", deptSave)
+	group.POST("/update", deptUpdate)
+	group.POST("/remove", deptRemove)
+}
+
+func deptGetList(c echo.Context) error {
+	var paramMap map[string]interface{}
+	if err := c.Bind(&paramMap); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	result, err := services.JdbcClient.GetJdbcList(paramMap, models.SysDept{})
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+	}
+	list := utils.ConvertInterface[[]models.SysDept](result)
+	if len(list) == 0 {
+		return c.JSON(http.StatusOK, []models.SysDept{})
+	}
+	return c.JSON(http.StatusOK, list)
+}
+
+func deptSave(c echo.Context) error {
+	dept := new(models.SysDept)
+	if err := c.Bind(dept); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	err := services.JdbcClient.JdbcInsert(dept)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+	}
+	return c.JSON(http.StatusOK, dept)
+}
+
+func deptUpdate(c echo.Context) error {
+	dept := new(models.SysDept)
+	if err := c.Bind(dept); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	err := services.JdbcClient.JdbcUpdateById(dept)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+	}
+	return c.JSON(http.StatusOK, dept)
+}
+
+func deptRemove(c echo.Context) error {
+	dept := new(models.SysDept)
+	if err := c.Bind(dept); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	deptParam := new(models.SysDept)
+	deptParam.Pid = dept.ID
+	count, err := services.JdbcClient.GetJdbcCount(deptParam)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+	}
+	if count > 0 {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("存在下级部门,无法删除", ""))
+	}
+
+	err = services.JdbcClient.JdbcRemoveById(dept)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+	}
+	return c.JSON(http.StatusOK, dept)
+}

+ 104 - 0
handlers/device.go

@@ -0,0 +1,104 @@
+package handlers
+
+import (
+	"net/http"
+
+	"easydo-echo_win7/models"
+	"easydo-echo_win7/services"
+	"easydo-echo_win7/utils"
+
+	// "github.com/labstack/echo-contrib/session"
+	"easydo-echo_win7/middleware"
+
+	"github.com/labstack/echo/v4"
+)
+
+func Add_device_to_routes(e *echo.Echo) {
+	group := e.Group("/device")
+	group.Use(middleware.AuthMiddleware)
+	group.POST("/getPage", deviceGetPage)
+	group.POST("/getList", deviceGetList)
+	group.POST("/save", deviceSave)
+	group.POST("/update", deviceUpdate)
+	group.POST("/remove", deviceRemove)
+}
+
+func deviceGetPage(c echo.Context) error {
+	var paramMap map[string]interface{}
+	if err := c.Bind(&paramMap); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	result, err := services.JdbcClient.GetJdbcPage(paramMap, models.Device{})
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	list := utils.ConvertInterface[[]models.Device](result.Records)
+	if len(list) == 0 {
+		list = []models.Device{}
+	}
+	result.Records = list
+	return c.JSON(http.StatusOK, result)
+
+}
+
+func deviceGetList(c echo.Context) error {
+	var paramMap map[string]interface{}
+	if err := c.Bind(&paramMap); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	result, err := services.JdbcClient.GetJdbcList(paramMap, models.Device{})
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	list := utils.ConvertInterface[[]models.Device](result)
+	if len(list) == 0 {
+		return c.JSON(http.StatusOK, []models.Device{})
+	}
+	return c.JSON(http.StatusOK, list)
+}
+
+func deviceSave(c echo.Context) error {
+	device := new(models.Device)
+	if err := c.Bind(device); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	err := services.JdbcClient.JdbcInsert(device)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	return c.JSON(http.StatusOK, device)
+}
+
+func deviceUpdate(c echo.Context) error {
+	device := new(models.Device)
+	if err := c.Bind(device); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	err := services.JdbcClient.JdbcUpdateById(device)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	return c.JSON(http.StatusOK, device)
+}
+
+func deviceRemove(c echo.Context) error {
+	tx, _ := services.MYSQL_DB.Beginx()
+
+	role := new(models.Device)
+	if err := c.Bind(role); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+
+	err := services.JdbcClient.JdbcRemoveById(role, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	tx.Commit()
+	return c.JSON(http.StatusOK, role)
+}

+ 152 - 0
handlers/menu.go

@@ -0,0 +1,152 @@
+package handlers
+
+import (
+	"net/http"
+
+	"easydo-echo_win7/models"
+	"easydo-echo_win7/services"
+	"easydo-echo_win7/utils"
+
+	"easydo-echo_win7/middleware"
+
+	"github.com/labstack/echo-contrib/session"
+	"github.com/labstack/echo/v4"
+)
+
+func Add_menu_to_routes(e *echo.Echo) {
+	group := e.Group("/sysMenu")
+	group.Use(middleware.AuthMiddleware)
+	group.POST("/build", menuBuild)
+	group.POST("/getList", menuGetList)
+	group.POST("/save", menuSave)
+	group.POST("/update", menuUpdate)
+	group.POST("/remove", menuRemove)
+}
+
+func menuBuild(c echo.Context) error {
+	sess, err := session.Get("auth_session", c)
+	if err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("用户请先登录", err.Error()))
+	}
+
+	user_role := new(models.SysUsersRoles)
+	user_id, ok := sess.Values["user_id"].(int64)
+	if !ok {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("用户请先登录", err.Error()))
+	}
+	user_role.UserID = &user_id
+	result, err := services.JdbcClient.GetJdbcListByObject(user_role)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+	}
+	user_role_list := utils.ConvertInterface[[]models.SysUsersRoles](result)
+	role_id_list := utils.Map(user_role_list, func(role models.SysUsersRoles) int64 {
+		return *role.RoleID
+	})
+
+	paramMap := map[string]interface{}{
+		"roleIdIn": role_id_list,
+	}
+	result, err = services.JdbcClient.GetJdbcList(paramMap, models.SysRolesMenus{})
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+	}
+	role_menu_list := utils.ConvertInterface[[]models.SysRolesMenus](result)
+	menu_id_list := utils.Map(role_menu_list, func(role models.SysRolesMenus) int64 {
+		return *role.MenuID
+	})
+	paramMap = map[string]interface{}{
+		"idIn": menu_id_list,
+	}
+	result, err = services.JdbcClient.GetJdbcList(paramMap, models.SysMenu{})
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+	}
+	menu_list := utils.ConvertInterface[[]models.SysMenu](result)
+	if menu_list == nil {
+		return c.JSON(http.StatusOK, []string{})
+	}
+	return c.JSON(http.StatusOK, menu_list)
+}
+
+func menuGetList(c echo.Context) error {
+	var paramMap map[string]interface{}
+	if err := c.Bind(&paramMap); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析错误", err.Error()))
+	}
+	result, err := services.JdbcClient.GetJdbcList(paramMap, models.SysMenu{})
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+	}
+	menu_list := utils.ConvertInterface[[]models.SysMenu](result)
+	if len(menu_list) == 0 {
+		return c.JSON(http.StatusOK, []string{})
+	}
+	return c.JSON(http.StatusOK, menu_list)
+}
+
+func menuSave(c echo.Context) error {
+	menu := new(models.SysMenu)
+	if err := c.Bind(menu); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析错误", err.Error()))
+	}
+	err := services.JdbcClient.JdbcInsert(menu)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+	}
+	return c.JSON(http.StatusOK, menu)
+}
+
+func menuUpdate(c echo.Context) error {
+	menu := new(models.SysMenu)
+	if err := c.Bind(menu); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析错误", err.Error()))
+	}
+	err := services.JdbcClient.JdbcUpdateById(menu)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+	}
+	return c.JSON(http.StatusOK, menu)
+}
+
+func menuRemove(c echo.Context) error {
+	tx, _ := services.MYSQL_DB.Beginx()
+
+	menu := new(models.SysMenu)
+	if err := c.Bind(menu); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析错误", err.Error()))
+	}
+	menuParam := new(models.SysMenu)
+	menuParam.Pid = menu.ID
+	count, err := services.JdbcClient.GetJdbcCount(menuParam, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+	}
+	if count > 0 {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("存在下级子菜单,无法删除", ""))
+	}
+	roleMenuParam := new(models.SysRolesMenus)
+	roleMenuParam.MenuID = menu.ID
+	err = services.JdbcClient.JdbcRemove(roleMenuParam, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+	}
+
+	err = services.JdbcClient.JdbcRemoveById(menu, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	tx.Commit()
+	return c.JSON(http.StatusOK, menu)
+}

+ 93 - 0
handlers/minio.go

@@ -0,0 +1,93 @@
+package handlers
+
+import (
+	"easydo-echo_win7/models"
+	"easydo-echo_win7/services"
+	"easydo-echo_win7/utils"
+	"fmt"
+	"net/http"
+	"path/filepath"
+
+	// "strings"
+
+	"easydo-echo_win7/middleware"
+
+	"github.com/google/uuid"
+	"github.com/labstack/echo/v4"
+)
+
+func Add_minio_to_routes(e *echo.Echo) {
+	group := e.Group("/file")
+	group.Use(middleware.AuthMiddleware)
+	group.POST("/upload", fileUpload)
+	group.POST("/remove", fileRemove)
+
+}
+
+// 响应结构体
+type UploadResponse struct {
+	Code    int                    `json:"code"`
+	Message string                 `json:"message"`
+	Expands map[string]interface{} `json:"expands,omitempty"`
+}
+
+// UploadHandler 是您的主要处理函数
+func fileUpload(c echo.Context) error {
+	// 1. 从form-data中获取文件
+	file, err := c.FormFile("file")
+	if err != nil {
+		return c.JSON(http.StatusBadRequest, utils.ErrorResponse("无法获取上传的文件,请检查字段名是否为'file'", ""))
+	}
+
+	// 2. 打开上传的文件
+	src, err := file.Open()
+	if err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("无法打开上传的文件", ""))
+	}
+	defer src.Close()
+
+	// 3. 生成唯一对象名并上传到MinIO
+	objectName := generateUUIDFileName(file.Filename)
+
+	// 上传到MinIO[citation:2]
+	filePath, err := services.MinioClient.UploadFile(services.BucketName, src, objectName)
+	if err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse(fmt.Sprintf("上传到MinIO失败: %v", err), ""))
+	}
+
+	// 构建成功响应
+	return c.JSON(http.StatusOK, UploadResponse{
+		Code:    200,
+		Message: "success",
+		Expands: map[string]interface{}{
+			"file": filePath,
+		},
+	})
+}
+
+// 生成UUID的辅助函数
+func generateUUIDFileName(filename string) string {
+	fullExt := filepath.Ext(filename)
+	uuid := uuid.New()
+	return uuid.String() + fullExt
+}
+
+func fileRemove(c echo.Context) error {
+	file := new(models.MinioFile)
+	if err := c.Bind(file); err != nil {
+		return c.JSON(http.StatusOK, nil)
+	}
+	// file_path := *file.Path
+	// bucketName := strings.Split(file_path[1:], "/")[0]
+	// objectName := file_path[1+len(bucketName)+1:]
+	// services.MinioClient.RemoveFile(bucketName,objectName)
+
+	if file.ID != nil {
+		err := services.JdbcClient.JdbcRemoveById(file)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+	}
+	return c.JSON(http.StatusOK, file)
+}

+ 305 - 0
handlers/oursourcing_plan.go

@@ -0,0 +1,305 @@
+package handlers
+
+import (
+	"net/http"
+
+	"easydo-echo_win7/models"
+	"easydo-echo_win7/services"
+	"easydo-echo_win7/utils"
+
+	"easydo-echo_win7/middleware"
+
+	"github.com/jmoiron/sqlx"
+	"github.com/labstack/echo-contrib/session"
+	"github.com/labstack/echo/v4"
+)
+
+func Add_outsourcing_plan_to_routes(e *echo.Echo) {
+	group := e.Group("/outsourcingPlan")
+	group.Use(middleware.AuthMiddleware)
+	group.POST("/getPage", outsourcingPlanGetPage)
+	group.POST("/getList", outsourcingPlanGetList)
+	group.POST("/save", outsourcingPlanSave)
+	group.POST("/update", outsourcingPlanUpdate)
+	group.POST("/remove", outsourcingPlanRemove)
+
+}
+
+func outsourcingPlanGetPage(c echo.Context) error {
+	var paramMap map[string]interface{}
+	if err := c.Bind(&paramMap); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	result, err := services.JdbcClient.GetJdbcPage(paramMap, models.OutsourcingPlan{})
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+	}
+	list := utils.ConvertInterface[[]models.OutsourcingPlan](result.Records)
+	if len(list) == 0 {
+		list = []models.OutsourcingPlan{}
+	}
+	for i := range list {
+		model := list[i]
+		minioList, err := services.JdbcClient.GetMinioFile(model)
+		if err != nil {
+			utils.PrintSearchFileErr(err)
+		}
+		model.FileList = &minioList
+
+		sale_order := new(models.SaleOrder)
+		sale_order.ID = model.SaleOrderId
+		err = services.JdbcClient.GetJdbcModelById(sale_order)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+		}
+		model.SaleOrder = sale_order
+
+		detailParam := new(models.OutsourcingPlanDetail)
+		detailParam.PlanId = model.ID
+		result, err := services.JdbcClient.GetJdbcListByObject(detailParam)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+		}
+		detail_list := utils.ConvertInterface[[]models.OutsourcingPlanDetail](result)
+
+		for j := range detail_list {
+			detail := detail_list[j]
+
+			material := new(models.ProductMaterial)
+			material.Code = detail.MaterialCode
+			err = services.JdbcClient.GetJdbcModel(material)
+			if err != nil {
+				utils.PrintSqlErr(err)
+				return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+			}
+
+			detail.ProductMaterial = material
+
+			detail_list[j] = detail
+		}
+		model.ChildrenList = &detail_list
+
+		list[i] = model
+	}
+	result.Records = list
+	return c.JSON(http.StatusOK, result)
+
+}
+
+func outsourcingPlanGetList(c echo.Context) error {
+	var paramMap map[string]interface{}
+	if err := c.Bind(&paramMap); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	result, err := services.JdbcClient.GetJdbcList(paramMap, models.OutsourcingPlan{})
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+	}
+	list := utils.ConvertInterface[[]models.OutsourcingPlan](result)
+	if len(list) == 0 {
+		return c.JSON(http.StatusOK, []string{})
+	}
+	return c.JSON(http.StatusOK, list)
+}
+
+func outsourcingPlanSave(c echo.Context) error {
+	tx, _ := services.MYSQL_DB.Beginx()
+
+	sess, _ := session.Get("auth_session", c)
+	user_id, ok := sess.Values["user_id"].(int64)
+	if !ok {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("用户请先登录", ""))
+	}
+	plan := new(models.OutsourcingPlan)
+	if err := c.Bind(plan); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	err_msg := outsourcingPlan_generateCode(plan, tx)
+	if err_msg != "" {
+		tx.Rollback()
+		return c.JSON(http.StatusBadRequest, utils.ErrorResponse(err_msg, ""))
+	}
+	status := models.Outsourcing_Plan_Status_Pending
+	plan.Status = &status
+	plan.CreateId = &user_id
+	services.JdbcClient.JdbcInsert(plan, tx)
+
+	detail_list := *plan.ChildrenList
+	for _, detail := range detail_list {
+		detail.PlanId = plan.ID
+		err := services.JdbcClient.JdbcInsert(&detail, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+	}
+
+	if plan.FileList == nil {
+		tx.Commit()
+		return c.JSON(http.StatusOK, plan)
+	}
+	fileList := *plan.FileList
+	for _, file := range fileList {
+		file.RefId = plan.ID
+		refType := models.File_Ref_Type_Outsourcing_Plan
+		file.RefType = &refType
+
+		err := services.JdbcClient.JdbcInsert(&file, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+	}
+	tx.Commit()
+	return c.JSON(http.StatusOK, plan)
+
+}
+
+func outsourcingPlanUpdate(c echo.Context) error {
+	tx, _ := services.MYSQL_DB.Beginx()
+
+	plan := new(models.OutsourcingPlan)
+	if err := c.Bind(plan); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	sess, _ := session.Get("auth_session", c)
+	user_id, ok := sess.Values["user_id"].(int64)
+	if !ok {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("用户请先登录", ""))
+	}
+	plan.UpdateId = &user_id
+	err := services.JdbcClient.JdbcUpdateById(plan, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	detail := new(models.OutsourcingPlanDetail)
+	detail.PlanId = plan.ID
+	err = services.JdbcClient.JdbcRemove(detail, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	detail_list := *plan.ChildrenList
+	for _, detail := range detail_list {
+		detail.PlanId = plan.ID
+		err := services.JdbcClient.JdbcInsert(&detail, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+	}
+
+	if plan.FileList == nil {
+		tx.Commit()
+		return c.JSON(http.StatusOK, plan)
+	}
+
+	fileList := *plan.FileList
+	for _, file := range fileList {
+		file.RefId = plan.ID
+		refType := models.File_Ref_Type_Outsourcing_Plan
+		file.RefType = &refType
+
+		err := services.JdbcClient.JdbcInsert(&file, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+	}
+
+	tx.Commit()
+	return c.JSON(http.StatusOK, plan)
+}
+
+func outsourcingPlanRemove(c echo.Context) error {
+	tx, _ := services.MYSQL_DB.Beginx()
+
+	plan := new(models.OutsourcingPlan)
+	if err := c.Bind(plan); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+
+	err := services.JdbcClient.JdbcRemoveById(plan, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	minio_file := new(models.MinioFile)
+	minio_file.RefId = plan.ID
+	refType := models.File_Ref_Type_Outsourcing_Plan
+	minio_file.RefType = &refType
+	err = services.JdbcClient.JdbcRemove(minio_file, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	detail := new(models.OutsourcingPlanDetail)
+	detail.PlanId = plan.ID
+	err = services.JdbcClient.JdbcRemove(detail, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	tx.Commit()
+	return c.JSON(http.StatusOK, plan)
+}
+
+func outsourcingPlan_generateCode(model *models.OutsourcingPlan, tx *sqlx.Tx) string {
+	sync := new(models.Synchronized)
+	sync.Sync.Lock()
+	defer sync.Sync.Unlock()
+
+	if model.Code != nil && len(*model.Code) > 0 {
+		modelParam := new(models.OutsourcingPlan)
+		modelParam.Code = model.Code
+		modelParam.TenantId = model.TenantId
+		count, err := services.JdbcClient.GetJdbcCount(modelParam, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return "SQL执行失败"
+		}
+		if count > 0 {
+			return "编号已存在,请重新填写"
+		}
+	} else {
+		for {
+			code, err := services.GetFlowNo(models.Flow_No_Type_Outsourcing_Plan, *model.TenantId, tx)
+			if err != nil {
+				return "自动生成编码失败"
+			}
+			modelParam := new(models.OutsourcingPlan)
+			modelParam.Code = &code
+			modelParam.TenantId = model.TenantId
+			count, err := services.JdbcClient.GetJdbcCount(modelParam, tx)
+			if err != nil {
+				utils.PrintSqlErr(err)
+				return "SQL执行失败"
+			}
+			if count == 0 {
+				model.Code = &code
+				break
+			}
+		}
+
+	}
+	return ""
+}

+ 515 - 0
handlers/process_route.go

@@ -0,0 +1,515 @@
+package handlers
+
+import (
+	"net/http"
+	"sort"
+
+	"easydo-echo_win7/models"
+	"easydo-echo_win7/services"
+	"easydo-echo_win7/utils"
+
+	"easydo-echo_win7/middleware"
+
+	"github.com/jmoiron/sqlx"
+	"github.com/labstack/echo-contrib/session"
+	"github.com/labstack/echo/v4"
+)
+
+func Add_process_route_to_routes(e *echo.Echo) {
+	group := e.Group("/processRoute")
+	group.Use(middleware.AuthMiddleware)
+	group.POST("/getPage", processRouteGetPage)
+	group.POST("/getList", processRouteGetList)
+	group.POST("/save", processRouteSave)
+	group.POST("/update", processRouteUpdate)
+	group.POST("/remove", processRouteRemove)
+	group.POST("/upgrade", processRouteUpgrade)
+	group.POST("/regrade", processRouteRegrade)
+
+}
+
+func processRouteGetPage(c echo.Context) error {
+	var paramMap map[string]interface{}
+	if err := c.Bind(&paramMap); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	result, err := services.JdbcClient.GetJdbcPage(paramMap, models.ProcessRoute{})
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+	}
+	list := utils.ConvertInterface[[]models.ProcessRoute](result.Records)
+	if len(list) == 0 {
+		list = []models.ProcessRoute{}
+	}
+	for i := range list {
+		model := list[i]
+		minioList, err := services.JdbcClient.GetMinioFile(model)
+		if err != nil {
+			utils.PrintSearchFileErr(err)
+		}
+		model.FileList = &minioList
+
+		detailParam := new(models.ProcessRouteDetail)
+		detailParam.RouteId = model.ID
+		result, err := services.JdbcClient.GetJdbcListByObject(detailParam)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+		}
+		detail_list := utils.ConvertInterface[[]models.ProcessRouteDetail](result)
+
+		for j := range detail_list {
+			detail := detail_list[j]
+
+			stage := new(models.ProcessStage)
+			stage.ID = detail.StageId
+			err = services.JdbcClient.GetJdbcModelById(stage)
+			if err != nil {
+				utils.PrintSqlErr(err)
+				return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+			}
+
+			detail.ProcessStage = stage
+
+			detail_list[j] = detail
+		}
+		model.DetailList = &detail_list
+
+		routeParam := new(models.ProcessRoute)
+		routeParam.ParentId = model.ID
+		count, err := services.JdbcClient.GetJdbcCount(routeParam)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+		}
+		if count > 0 {
+			isHaveHistory := models.Common_Value_True_Value
+			model.IsHaveHistory = &isHaveHistory
+		} else {
+			isHaveHistory := models.Common_Value_False_Value
+			model.IsHaveHistory = &isHaveHistory
+		}
+
+		if model.InspectProgramId != nil && len(*model.InspectProgramId) > 0 {
+			inspectProgram := new(models.QualityInspectProgram)
+			inspectProgram.ID = model.InspectProgramId
+			err = services.JdbcClient.GetJdbcModelById(inspectProgram)
+			if err != nil {
+				utils.PrintSqlErr(err)
+				return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+			}
+			model.InspectProgram = inspectProgram
+		}
+
+		list[i] = model
+	}
+	result.Records = list
+	return c.JSON(http.StatusOK, result)
+
+}
+
+func processRouteGetList(c echo.Context) error {
+	var paramMap map[string]interface{}
+	if err := c.Bind(&paramMap); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	result, err := services.JdbcClient.GetJdbcList(paramMap, models.ProcessRoute{})
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+	}
+	list := utils.ConvertInterface[[]models.ProcessRoute](result)
+	if len(list) == 0 {
+		return c.JSON(http.StatusOK, []string{})
+	}
+	return c.JSON(http.StatusOK, list)
+}
+
+func processRouteSave(c echo.Context) error {
+	tx, _ := services.MYSQL_DB.Beginx()
+
+	sess, _ := session.Get("auth_session", c)
+	user_id, ok := sess.Values["user_id"].(int64)
+	if !ok {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("用户请先登录", ""))
+	}
+	route := new(models.ProcessRoute)
+	if err := c.Bind(route); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	err_msg := processRoute_generateCode(route, tx)
+	if err_msg != "" {
+		tx.Rollback()
+		return c.JSON(http.StatusBadRequest, utils.ErrorResponse(err_msg, ""))
+	}
+
+	status := models.Status_Enable
+	route.Status = &status
+	route.CreateId = &user_id
+	parentId := models.Common_Value_Zero_String
+	route.ParentId = &parentId
+	services.JdbcClient.JdbcInsert(route, tx)
+
+	detail_list := *route.DetailList
+	for _, detail := range detail_list {
+		detail.RouteId = route.ID
+		err := services.JdbcClient.JdbcInsert(&detail, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+	}
+
+	if route.FileList == nil {
+		tx.Commit()
+		return c.JSON(http.StatusOK, route)
+	}
+	fileList := *route.FileList
+	for _, file := range fileList {
+		file.RefId = route.ID
+		refType := models.File_Ref_Type_Process_Route
+		file.RefType = &refType
+
+		err := services.JdbcClient.JdbcInsert(&file, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+	}
+	tx.Commit()
+	return c.JSON(http.StatusOK, route)
+
+}
+
+func processRouteUpdate(c echo.Context) error {
+	tx, _ := services.MYSQL_DB.Beginx()
+
+	route := new(models.ProcessRoute)
+	if err := c.Bind(route); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	sess, _ := session.Get("auth_session", c)
+	user_id, ok := sess.Values["user_id"].(int64)
+	if !ok {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("用户请先登录", ""))
+	}
+	route.UpdateId = &user_id
+	err := services.JdbcClient.JdbcUpdateById(route, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	route_detial := new(models.ProcessRouteDetail)
+	route_detial.RouteId = route.ID
+	err = services.JdbcClient.JdbcRemove(route_detial, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	detail_list := *route.DetailList
+	for _, detail := range detail_list {
+		detail.RouteId = route.ID
+		err := services.JdbcClient.JdbcInsert(&detail, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+	}
+
+	if route.FileList == nil {
+		tx.Commit()
+		return c.JSON(http.StatusOK, route)
+	}
+
+	fileList := *route.FileList
+	for _, file := range fileList {
+		file.RefId = route.ID
+		refType := models.File_Ref_Type_Process_Route
+		file.RefType = &refType
+
+		err := services.JdbcClient.JdbcInsert(&file, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+	}
+
+	tx.Commit()
+	return c.JSON(http.StatusOK, route)
+}
+
+func processRouteRemove(c echo.Context) error {
+	tx, _ := services.MYSQL_DB.Beginx()
+
+	route := new(models.ProcessRoute)
+	if err := c.Bind(route); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+
+	err := services.JdbcClient.JdbcRemoveById(route, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	minio_file := new(models.MinioFile)
+	minio_file.RefId = route.ID
+	refType := models.File_Ref_Type_Process_Route
+	minio_file.RefType = &refType
+	err = services.JdbcClient.JdbcRemove(minio_file, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	route_detial := new(models.ProcessRouteDetail)
+	route_detial.RouteId = route.ID
+	err = services.JdbcClient.JdbcRemove(route_detial, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	history := new(models.ProcessRoute)
+	history.ParentId = route.ID
+	history_result, _ := services.JdbcClient.GetJdbcListByObject(history, tx)
+	history_list := utils.ConvertInterface[[]models.ProcessRoute](history_result)
+	for _, history := range history_list {
+		err := services.JdbcClient.JdbcRemoveById(history, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+
+		route_detial := new(models.ProcessRouteDetail)
+		route_detial.RouteId = history.ID
+		err = services.JdbcClient.JdbcRemove(route_detial, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+	}
+
+	tx.Commit()
+	return c.JSON(http.StatusOK, route)
+}
+
+func processRouteUpgrade(c echo.Context) error {
+	tx, _ := services.MYSQL_DB.Beginx()
+
+	route := new(models.ProcessRoute)
+	if err := c.Bind(route); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	old_uuid := route.ID
+
+	sess, _ := session.Get("auth_session", c)
+	user_id, ok := sess.Values["user_id"].(int64)
+	if !ok {
+		return c.JSON(http.StatusBadRequest, utils.ErrorResponse("用户请先登录", ""))
+	}
+	err_msg := processRoute_generateCode(route, tx)
+	if err_msg != "" {
+		tx.Rollback()
+		return c.JSON(http.StatusBadRequest, utils.ErrorResponse(err_msg, ""))
+	}
+	route.ID = nil
+	status := models.Status_Enable
+	route.Status = &status
+	route.CreateId = &user_id
+	parentId := models.Common_Value_Zero_String
+	route.ParentId = &parentId
+	err := services.JdbcClient.JdbcInsert(route, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	exist_route := new(models.ProcessRoute)
+	exist_route.ID = old_uuid
+	exist_route.ParentId = route.ID
+	status = models.Status_Disable
+	exist_route.Status = &status
+	exist_route.UpdateId = &user_id
+	err = services.JdbcClient.JdbcUpdateById(exist_route, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	child_route := new(models.ProcessRoute)
+	child_route.ParentId = route.ID
+	child_route.UpdateId = &user_id
+	var paramMap = map[string]interface{}{
+		"parentId": old_uuid,
+	}
+	err = services.JdbcClient.JdbcUpdate(child_route, paramMap, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	detail_list := *route.DetailList
+	for _, detail := range detail_list {
+		detail.RouteId = route.ID
+		err := services.JdbcClient.JdbcInsert(&detail, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+	}
+
+	if route.FileList == nil {
+		tx.Commit()
+		return c.JSON(http.StatusOK, route)
+	}
+	fileList := *route.FileList
+	for _, file := range fileList {
+		file.RefId = route.ID
+		refType := models.File_Ref_Type_Process_Route
+		file.RefType = &refType
+
+		err := services.JdbcClient.JdbcInsert(&file, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+	}
+
+	tx.Commit()
+	return c.JSON(http.StatusOK, route)
+}
+
+func processRouteRegrade(c echo.Context) error {
+	tx, _ := services.MYSQL_DB.Beginx()
+
+	route := new(models.ProcessRoute)
+	if err := c.Bind(route); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	children := new(models.ProcessRoute)
+	children.ParentId = route.ID
+	result, err := services.JdbcClient.GetJdbcListByObject(children, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+	}
+	children_list := utils.ConvertInterface[[]models.ProcessRoute](result)
+
+	sess, _ := session.Get("auth_session", c)
+	user_id, ok := sess.Values["user_id"].(int64)
+	if !ok {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("用户请先登录", ""))
+	}
+	if len(children_list) > 0 {
+		//倒序
+		sort.Slice(children_list, func(i, j int) bool {
+			return *children_list[i].CreateTime > *children_list[j].CreateTime
+		})
+
+		child := &children_list[0]
+		parentId := models.Common_Value_Zero_String
+		child.ParentId = &parentId
+		status := models.Status_Enable
+		child.Status = &status
+		child.UpdateId = &user_id
+		err := services.JdbcClient.JdbcUpdateById(child, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+
+		children = new(models.ProcessRoute)
+		children.ParentId = child.ID
+		paramMap := map[string]interface{}{
+			"parentId": route.ID,
+		}
+		children.UpdateId = &user_id
+		err = services.JdbcClient.JdbcUpdate(children, paramMap, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+
+		err = services.JdbcClient.JdbcRemoveById(route, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+
+		route_detail := new(models.ProcessRouteDetail)
+		route_detail.RouteId = route.ID
+		err = services.JdbcClient.JdbcRemove(route_detail, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+	}
+
+	tx.Commit()
+	return c.JSON(http.StatusOK, route)
+}
+
+func processRoute_generateCode(model *models.ProcessRoute, tx *sqlx.Tx) string {
+	sync := new(models.Synchronized)
+	sync.Sync.Lock()
+	defer sync.Sync.Unlock()
+
+	if model.Code != nil && len(*model.Code) > 0 {
+		modelParam := new(models.ProcessRoute)
+		modelParam.Code = model.Code
+		modelParam.TenantId = model.TenantId
+		count, err := services.JdbcClient.GetJdbcCount(modelParam, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return "SQL执行失败"
+		}
+		if count > 0 {
+			return "编号已存在,请重新填写"
+		}
+	} else {
+		for {
+			code, err := services.GetFlowNo(models.Flow_No_Type_Process_Route, *model.TenantId, tx)
+			if err != nil {
+				return "自动生成编码失败"
+			}
+			modelParam := new(models.ProcessRoute)
+			modelParam.Code = &code
+			modelParam.TenantId = model.TenantId
+			count, err := services.JdbcClient.GetJdbcCount(modelParam, tx)
+			if err != nil {
+				utils.PrintSqlErr(err)
+				return "SQL执行失败"
+			}
+			if count == 0 {
+				model.Code = &code
+				break
+			}
+		}
+
+	}
+	return ""
+}

+ 246 - 0
handlers/process_stage.go

@@ -0,0 +1,246 @@
+package handlers
+
+import (
+	"net/http"
+
+	"easydo-echo_win7/models"
+	"easydo-echo_win7/services"
+	"easydo-echo_win7/utils"
+
+	"easydo-echo_win7/middleware"
+
+	"github.com/jmoiron/sqlx"
+	"github.com/labstack/echo-contrib/session"
+	"github.com/labstack/echo/v4"
+)
+
+func Add_process_stage_to_routes(e *echo.Echo) {
+	group := e.Group("/processStage")
+	group.Use(middleware.AuthMiddleware)
+	group.POST("/getPage", processStageGetPage)
+	group.POST("/getList", processStageGetList)
+	group.POST("/save", processStageSave)
+	group.POST("/update", processStageUpdate)
+	group.POST("/remove", processStageRemove)
+
+}
+
+func processStageGetPage(c echo.Context) error {
+	var paramMap map[string]interface{}
+	if err := c.Bind(&paramMap); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	result, err := services.JdbcClient.GetJdbcPage(paramMap, models.ProcessStage{})
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+	}
+	list := utils.ConvertInterface[[]models.ProcessStage](result.Records)
+	if len(list) == 0 {
+		list = []models.ProcessStage{}
+	}
+	for i := range list {
+		model := list[i]
+		minioList, err := services.JdbcClient.GetMinioFile(model)
+		if err != nil {
+			utils.PrintSearchFileErr(err)
+		}
+		model.FileList = &minioList
+
+		list[i] = model
+	}
+	result.Records = list
+	return c.JSON(http.StatusOK, result)
+
+}
+
+func processStageGetList(c echo.Context) error {
+	var paramMap map[string]interface{}
+	if err := c.Bind(&paramMap); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	result, err := services.JdbcClient.GetJdbcList(paramMap, models.ProcessStage{})
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+	}
+	list := utils.ConvertInterface[[]models.ProcessStage](result)
+	if len(list) == 0 {
+		return c.JSON(http.StatusOK, []string{})
+	}
+	return c.JSON(http.StatusOK, list)
+}
+
+func processStageSave(c echo.Context) error {
+	tx, _ := services.MYSQL_DB.Beginx()
+
+	sess, _ := session.Get("auth_session", c)
+	user_id, ok := sess.Values["user_id"].(int64)
+	if !ok {
+		return c.JSON(http.StatusBadRequest, utils.ErrorResponse("用户请先登录", ""))
+	}
+	stage := new(models.ProcessStage)
+	if err := c.Bind(stage); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	err_msg := processStage_generateCode(stage, tx)
+	if err_msg != "" {
+		tx.Rollback()
+		return c.JSON(http.StatusBadRequest, utils.ErrorResponse(err_msg, ""))
+	}
+	status := models.Status_Enable
+	stage.Status = &status
+	stage.CreateId = &user_id
+	err := services.JdbcClient.JdbcInsert(stage, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	if stage.FileList == nil {
+		tx.Commit()
+		return c.JSON(http.StatusOK, stage)
+	}
+	fileList := *stage.FileList
+	for _, file := range fileList {
+		file.RefId = stage.ID
+		refType := models.File_Ref_Type_Process_Stage
+		file.RefType = &refType
+
+		err := services.JdbcClient.JdbcInsert(&file, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+	}
+	tx.Commit()
+	return c.JSON(http.StatusOK, stage)
+
+}
+
+func processStageUpdate(c echo.Context) error {
+	tx, _ := services.MYSQL_DB.Beginx()
+
+	stage := new(models.ProcessStage)
+	if err := c.Bind(stage); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	sess, _ := session.Get("auth_session", c)
+	user_id, ok := sess.Values["user_id"].(int64)
+	if !ok {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("用户请先登录", ""))
+	}
+	stage.UpdateId = &user_id
+	err := services.JdbcClient.JdbcUpdateById(stage, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	if stage.FileList == nil {
+		tx.Commit()
+		return c.JSON(http.StatusOK, stage)
+	}
+
+	fileList := *stage.FileList
+	for _, file := range fileList {
+		file.RefId = stage.ID
+		refType := models.File_Ref_Type_Process_Stage
+		file.RefType = &refType
+
+		err := services.JdbcClient.JdbcInsert(&file, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+	}
+	tx.Commit()
+	return c.JSON(http.StatusOK, stage)
+}
+
+func processStageRemove(c echo.Context) error {
+	tx, _ := services.MYSQL_DB.Beginx()
+
+	stage := new(models.ProcessStage)
+	if err := c.Bind(stage); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+
+	route_detail := new(models.ProcessRouteDetail)
+	route_detail.StageId = stage.ID
+	count, err := services.JdbcClient.GetJdbcCount(route_detail, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	if count > 0 {
+		tx.Rollback()
+		return c.JSON(http.StatusBadRequest, utils.ErrorResponse("当前工序已被绑定到工艺路线,请勿删除", ""))
+	}
+
+	err = services.JdbcClient.JdbcRemoveById(stage, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	minio_file := new(models.MinioFile)
+	minio_file.RefId = stage.ID
+	refType := models.File_Ref_Type_Process_Stage
+	minio_file.RefType = &refType
+	err = services.JdbcClient.JdbcRemove(minio_file, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	tx.Commit()
+	return c.JSON(http.StatusOK, stage)
+}
+
+func processStage_generateCode(model *models.ProcessStage, tx *sqlx.Tx) string {
+	sync := new(models.Synchronized)
+	sync.Sync.Lock()
+	defer sync.Sync.Unlock()
+
+	if model.Code != nil && len(*model.Code) > 0 {
+		modelParam := new(models.ProcessStage)
+		modelParam.Code = model.Code
+		modelParam.TenantId = model.TenantId
+		count, err := services.JdbcClient.GetJdbcCount(modelParam, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return "SQL执行失败"
+		}
+		if count > 0 {
+			return "编号已存在,请重新填写"
+		}
+	} else {
+		for {
+			code, err := services.GetFlowNo(models.Flow_No_Type_Process_Stage, *model.TenantId, tx)
+			if err != nil {
+				return "自动生成编码失败"
+			}
+			modelParam := new(models.ProcessStage)
+			modelParam.Code = &code
+			modelParam.TenantId = model.TenantId
+			count, err := services.JdbcClient.GetJdbcCount(modelParam, tx)
+			if err != nil {
+				utils.PrintSqlErr(err)
+				return "SQL执行失败"
+			}
+			if count == 0 {
+				model.Code = &code
+				break
+			}
+		}
+
+	}
+	return ""
+}

+ 356 - 0
handlers/product_bom.go

@@ -0,0 +1,356 @@
+package handlers
+
+import (
+	"net/http"
+
+	"easydo-echo_win7/middleware"
+	"easydo-echo_win7/models"
+	"easydo-echo_win7/services"
+	"easydo-echo_win7/utils"
+
+	"github.com/jmoiron/sqlx"
+	"github.com/labstack/echo-contrib/session"
+	"github.com/labstack/echo/v4"
+)
+
+func Add_product_bom_to_routes(e *echo.Echo) {
+	group := e.Group("/productBom")
+	group.Use(middleware.AuthMiddleware)
+	group.POST("/getPage", productBomGetPage)
+	group.POST("/getChildrenList", productBomGetChildrenList)
+	group.POST("/save", productBomSave)
+	group.POST("/update", productBomUpdate)
+	group.POST("/remove", productBomRemove)
+
+}
+
+func productBomGetPage(c echo.Context) error {
+	var paramMap map[string]interface{}
+	if err := c.Bind(&paramMap); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	result, err := services.JdbcClient.GetJdbcPage(paramMap, models.ProductBom{})
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+	}
+	list := utils.ConvertInterface[[]models.ProductBom](result.Records)
+	if len(list) == 0 {
+		list = []models.ProductBom{}
+	}
+	for i := range list {
+		model := list[i]
+		material := new(models.ProductMaterial)
+		material.Code = model.MaterialCode
+		err = services.JdbcClient.GetJdbcModel(material)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+		}
+
+		model.Material = material
+
+		bomParam := new(models.ProductBom)
+		bomParam.ParentId = model.ID
+		count, err := services.JdbcClient.GetJdbcCount(bomParam)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+		}
+
+		if count > 0 {
+			isHaveChildren := models.Common_Value_True_Value
+			model.IsHaveChildren = &isHaveChildren
+		} else {
+			isHaveChildren := models.Common_Value_False_Value
+			model.IsHaveChildren = &isHaveChildren
+		}
+
+		if utils.IsNotEmpty(model.RouteId) {
+			route := new(models.ProcessRoute)
+			route.ID = model.RouteId
+			err = services.JdbcClient.GetJdbcModelById(route)
+			if err != nil {
+				utils.PrintSqlErr(err)
+				return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+			}
+			model.RouteName = route.Name
+		}
+		
+
+		list[i] = model
+	}
+	result.Records = list
+	return c.JSON(http.StatusOK, result)
+
+}
+
+func productBomGetChildrenList(c echo.Context) error {
+	var paramMap map[string]interface{}
+	if err := c.Bind(&paramMap); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	result, err := services.JdbcClient.GetJdbcList(paramMap, models.ProductBom{})
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+	}
+	list := utils.ConvertInterface[[]models.ProductBom](result)
+	if len(list) == 0 {
+		return c.JSON(http.StatusOK, []string{})
+	}
+	for i := range list {
+		model := list[i]
+		material := new(models.ProductMaterial)
+		material.Code = model.MaterialCode
+		err = services.JdbcClient.GetJdbcModel(material)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+		}
+
+		model.Material = material
+
+		bomParam := new(models.ProductBom)
+		bomParam.ParentId = model.ID
+		count, err := services.JdbcClient.GetJdbcCount(bomParam)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+		}
+
+		if count > 0 {
+			isHaveChildren := models.Common_Value_True_Value
+			model.IsHaveChildren = &isHaveChildren
+		} else {
+			isHaveChildren := models.Common_Value_False_Value
+			model.IsHaveChildren = &isHaveChildren
+		}
+
+		if utils.IsNotEmpty(model.RouteId) {
+			route := new(models.ProcessRoute)
+			route.ID = model.RouteId
+			err = services.JdbcClient.GetJdbcModelById(route)
+			if err != nil {
+				utils.PrintSqlErr(err)
+				return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+			}
+			model.RouteName = route.Name
+		}
+
+		list[i] = model
+	}
+	return c.JSON(http.StatusOK, list)
+}
+
+func productBomSave(c echo.Context) error {
+	tx, _ := services.MYSQL_DB.Beginx()
+
+	bom := new(models.ProductBom)
+	if err := c.Bind(bom); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	if *bom.ParentId == "0" {
+		exist_bom := new(models.ProductBom)
+		exist_bom.MaterialCode = bom.MaterialCode
+		exist_bom.ParentId = bom.ParentId
+		count, err := services.JdbcClient.GetJdbcCount(exist_bom, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+		if count > 0 {
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("该物料已经添加过BOM,请勿重复添加", ""))
+		}
+	}
+
+	status := models.Status_Enable
+	bom.Status = &status
+	if bom.ID == nil {
+		sess, _ := session.Get("auth_session", c)
+		err_msg := productBom_generateCode(bom, tx)
+		if err_msg != "" {
+			tx.Rollback()
+			return c.JSON(http.StatusBadRequest, utils.ErrorResponse(err_msg, ""))
+		}
+		user_id, ok := sess.Values["user_id"].(int64)
+		if !ok {
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("用户请先登录", ""))
+		}
+		bom.CreateId = &user_id
+		err := services.JdbcClient.JdbcInsert(bom, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+	} else {
+		err := services.JdbcClient.JdbcUpdateById(bom, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+	}
+
+	if bom.ChildrenList == nil {
+		tx.Commit()
+		return c.JSON(http.StatusOK, bom)
+	}
+	detail_list := *bom.ChildrenList
+	for _, detail := range detail_list {
+		detail.ParentId = bom.ID
+		detail.BomCode = bom.BomCode
+		err := services.JdbcClient.JdbcInsert(&detail, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+	}
+	tx.Commit()
+	return c.JSON(http.StatusOK, bom)
+
+}
+
+func productBomUpdate(c echo.Context) error {
+	tx, _ := services.MYSQL_DB.Beginx()
+
+	bom := new(models.ProductBom)
+	if err := c.Bind(bom); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	sess, _ := session.Get("auth_session", c)
+	user_id, ok := sess.Values["user_id"].(int64)
+	if !ok {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("用户请先登录", ""))
+	}
+	bom.UpdateId = &user_id
+	err := services.JdbcClient.JdbcUpdateById(bom, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	bomParam := new(models.ProductBom)
+	bomParam.ParentId = bom.ID
+	result, err := services.JdbcClient.GetJdbcListByObject(bomParam)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+	}
+	exist_list := utils.ConvertInterface[[]models.ProductBom](result)
+
+	remove_id_list := utils.Map(exist_list, func(product_bom models.ProductBom) string {
+		return *product_bom.ID
+	})
+
+	if bom.ChildrenList == nil {
+		tx.Commit()
+		return c.JSON(http.StatusOK, bom)
+	}
+	detail_list := *bom.ChildrenList
+	insert_list := utils.Filter(detail_list, func(item models.ProductBom) bool {
+		return item.ID == nil || len(*item.ID) == 0
+	})
+	update_list := utils.Filter(detail_list, func(item models.ProductBom) bool {
+		return item.ID != nil && len(*item.ID) > 0
+	})
+	update_id_list := utils.Map(update_list, func(product_bom models.ProductBom) string {
+		return *product_bom.ID
+	})
+
+	remove_id_list = utils.SliceSubtract(remove_id_list, update_id_list)
+
+	for _, insert_model := range insert_list {
+		insert_model.ParentId = bom.ID
+		insert_model.BomCode = bom.BomCode
+		err := services.JdbcClient.JdbcInsert(&insert_model, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+	}
+
+	for _, update_model := range update_list {
+		err := services.JdbcClient.JdbcUpdateById(&update_model, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+	}
+
+	for _, remove_id := range remove_id_list {
+		remove_bom := new(models.ProductBom)
+		remove_bom.ID = &remove_id
+		err := services.JdbcClient.JdbcRemoveById(remove_bom, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+	}
+
+	tx.Commit()
+	return c.JSON(http.StatusOK, bom)
+}
+
+func productBomRemove(c echo.Context) error {
+	bom := new(models.ProductBom)
+	if err := c.Bind(bom); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+
+	err := services.JdbcClient.JdbcRemoveById(bom)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+	}
+	return c.JSON(http.StatusOK, bom)
+}
+
+func productBom_generateCode(model *models.ProductBom, tx *sqlx.Tx) string {
+	sync := new(models.Synchronized)
+	sync.Sync.Lock()
+	defer sync.Sync.Unlock()
+
+	if model.BomCode != nil && len(*model.BomCode) > 0 {
+		modelParam := new(models.ProductBom)
+		modelParam.BomCode = model.BomCode
+		modelParam.TenantId = model.TenantId
+		count, err := services.JdbcClient.GetJdbcCount(modelParam, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return "SQL执行失败"
+		}
+		if count > 0 {
+			return "编号已存在,请重新填写"
+		}
+	} else {
+		for {
+			code, err := services.GetFlowNo(models.Flow_No_Type_Product_Bom, *model.TenantId, tx)
+			if err != nil {
+				return "自动生成编码失败"
+			}
+			modelParam := new(models.ProductBom)
+			modelParam.BomCode = &code
+			modelParam.TenantId = model.TenantId
+			count, err := services.JdbcClient.GetJdbcCount(modelParam, tx)
+			if err != nil {
+				utils.PrintSqlErr(err)
+				return "SQL执行失败"
+			}
+			if count == 0 {
+				model.BomCode = &code
+				break
+			}
+		}
+
+	}
+	return ""
+}

+ 202 - 0
handlers/product_material.go

@@ -0,0 +1,202 @@
+package handlers
+
+import (
+	"net/http"
+
+	"easydo-echo_win7/middleware"
+	"easydo-echo_win7/models"
+	"easydo-echo_win7/services"
+	"easydo-echo_win7/utils"
+
+	"github.com/jmoiron/sqlx"
+	"github.com/labstack/echo-contrib/session"
+	"github.com/labstack/echo/v4"
+)
+
+func Add_product_material_to_routes(e *echo.Echo) {
+	group := e.Group("/processMaterial")
+	group.Use(middleware.AuthMiddleware)
+	group.POST("/getPage", productMaterialGetPage)
+	group.POST("/getList", productMaterialGetList)
+	group.POST("/save", productMaterialSave)
+	group.POST("/update", productMaterialUpdate)
+	group.POST("/remove", productMaterialRemove)
+
+}
+
+func productMaterialGetPage(c echo.Context) error {
+	var paramMap map[string]interface{}
+	if err := c.Bind(&paramMap); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	result, err := services.JdbcClient.GetJdbcPage(paramMap, models.ProductMaterial{})
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	list := utils.ConvertInterface[[]models.ProductMaterial](result.Records)
+	if len(list) == 0 {
+		list = []models.ProductMaterial{}
+	}
+
+	result.Records = list
+	return c.JSON(http.StatusOK, result)
+
+}
+
+func productMaterialGetList(c echo.Context) error {
+	var paramMap map[string]interface{}
+	if err := c.Bind(&paramMap); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	result, err := services.JdbcClient.GetJdbcList(paramMap, models.ProductMaterial{})
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	list := utils.ConvertInterface[[]models.ProductMaterial](result)
+	if len(list) == 0 {
+		return c.JSON(http.StatusOK, []string{})
+	}
+	return c.JSON(http.StatusOK, list)
+}
+
+func productMaterialSave(c echo.Context) error {
+	tx, _ := services.MYSQL_DB.Beginx()
+
+	material := new(models.ProductMaterial)
+	if err := c.Bind(material); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	sess, _ := session.Get("auth_session", c)
+	err_msg := productMaterial_generateCode(material, tx)
+	if err_msg != "" {
+		tx.Rollback()
+		return c.JSON(http.StatusBadRequest, utils.ErrorResponse(err_msg, ""))
+	}
+
+	user_id, ok := sess.Values["user_id"].(int64)
+	if !ok {
+		return c.JSON(http.StatusBadRequest, utils.ErrorResponse("用户请先登录", ""))
+	}
+
+	status := models.Status_Enable
+	material.Status = &status
+	material.CreateId = &user_id
+	err := services.JdbcClient.JdbcInsert(material, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	tx.Commit()
+	return c.JSON(http.StatusOK, material)
+}
+
+func productMaterialUpdate(c echo.Context) error {
+	tx, _ := services.MYSQL_DB.Beginx()
+
+	material := new(models.ProductMaterial)
+	if err := c.Bind(material); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	sess, _ := session.Get("auth_session", c)
+	user_id, ok := sess.Values["user_id"].(int64)
+	if !ok {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("用户请先登录", ""))
+	}
+	material.UpdateId = &user_id
+	err := services.JdbcClient.JdbcUpdateById(material, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	bom := new(models.ProductBom)
+	bom.MaterialName = material.Name
+	paramMap := map[string]interface{}{
+		"materialCode": material.Code,
+	}
+	err = services.JdbcClient.JdbcUpdate(bom, paramMap, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	tx.Commit()
+	return c.JSON(http.StatusOK, material)
+}
+
+func productMaterialRemove(c echo.Context) error {
+	material := new(models.ProductMaterial)
+	if err := c.Bind(material); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	err := services.JdbcClient.GetJdbcModelById(material)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	bom := new(models.ProductBom)
+	bom.MaterialCode = material.Code
+	count, err := services.JdbcClient.GetJdbcCount(bom)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	if count > 0 {
+		return c.JSON(http.StatusBadRequest, utils.ErrorResponse("该物料在bom管理中存在,无法删除", ""))
+	}
+
+	err = services.JdbcClient.JdbcRemoveById(material)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	return c.JSON(http.StatusOK, material)
+}
+
+func productMaterial_generateCode(model *models.ProductMaterial, tx *sqlx.Tx) string {
+	sync := new(models.Synchronized)
+	sync.Sync.Lock()
+	defer sync.Sync.Unlock()
+
+	if model.Code != nil && len(*model.Code) > 0 {
+		modelParam := new(models.ProductMaterial)
+		modelParam.Code = model.Code
+		modelParam.TenantId = model.TenantId
+		count, err := services.JdbcClient.GetJdbcCount(modelParam, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return "SQL执行失败"
+		}
+		if count > 0 {
+			return "编号已存在,请重新填写"
+		}
+	} else {
+		for {
+			code, err := services.GetFlowNo(models.Flow_No_Type_Product_Material, *model.TenantId, tx)
+			if err != nil {
+				return "自动生成编码失败"
+			}
+			modelParam := new(models.ProductMaterial)
+			modelParam.Code = &code
+			modelParam.TenantId = model.TenantId
+			count, err := services.JdbcClient.GetJdbcCount(modelParam, tx)
+			if err != nil {
+				utils.PrintSqlErr(err)
+				return "SQL执行失败"
+			}
+			if count == 0 {
+				model.Code = &code
+				break
+			}
+		}
+
+	}
+	return ""
+}

+ 479 - 0
handlers/product_plan.go

@@ -0,0 +1,479 @@
+package handlers
+
+import (
+	"net/http"
+
+	"easydo-echo_win7/models"
+	"easydo-echo_win7/services"
+	"easydo-echo_win7/utils"
+
+	"easydo-echo_win7/middleware"
+
+	"github.com/jmoiron/sqlx"
+	"github.com/labstack/echo-contrib/session"
+	"github.com/labstack/echo/v4"
+)
+
+func Add_product_plan_to_routes(e *echo.Echo) {
+	group := e.Group("/productPlan")
+	group.Use(middleware.AuthMiddleware)
+	group.POST("/getPage", productPlanGetPage)
+	group.POST("/getList", productPlanGetList)
+	group.POST("/save", productPlanSave)
+	group.POST("/update", productPlanUpdate)
+	group.POST("/remove", productPlanRemove)
+}
+
+func productPlanGetPage(c echo.Context) error {
+	var paramMap map[string]interface{}
+	if err := c.Bind(&paramMap); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	result, err := services.JdbcClient.GetJdbcPage(paramMap, models.ProductPlan{})
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+	}
+	list := utils.ConvertInterface[[]models.ProductPlan](result.Records)
+	if len(list) == 0 {
+		list = []models.ProductPlan{}
+	}
+	for i := range list {
+		model := list[i]
+		minioList, err := services.JdbcClient.GetMinioFile(model)
+		if err != nil {
+			utils.PrintSearchFileErr(err)
+		}
+		model.FileList = &minioList
+
+		sale_order := new(models.SaleOrder)
+		sale_order.ID = model.SaleOrderId
+		err = services.JdbcClient.GetJdbcModelById(sale_order)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+		}
+		model.SaleOrder = sale_order
+
+		saleOrder_minioList, err := services.JdbcClient.GetMinioFile(sale_order)
+		if err != nil {
+			utils.PrintSearchFileErr(err)
+		}
+		sale_order.FileList = &saleOrder_minioList
+
+		saleOrder_detailParam := new(models.SaleOrderDetail)
+		saleOrder_detailParam.OrderId = sale_order.ID
+		saleOrder_result, err := services.JdbcClient.GetJdbcListByObject(saleOrder_detailParam)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+		}
+		saleOrder_detailList := utils.ConvertInterface[[]models.SaleOrderDetail](saleOrder_result)
+		for j := range saleOrder_detailList {
+			detail := saleOrder_detailList[j]
+
+			material := new(models.ProductMaterial)
+			material.Code = detail.MaterialCode
+			err = services.JdbcClient.GetJdbcModel(material)
+			if err != nil {
+				utils.PrintSqlErr(err)
+				return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+			}
+			detail.Material = material
+			
+			saleOrder_detailList[j] = detail
+		}
+		sale_order.ChildrenList = &saleOrder_detailList
+
+		bomParam := new(models.ProductPlanBom)
+		bomParam.PlanId = model.ID
+		result, err := services.JdbcClient.GetJdbcListByObject(bomParam)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+		}
+		bom_list := utils.ConvertInterface[[]models.ProductPlanBom](result)
+
+		for j := range bom_list {
+			plan_bom := bom_list[j]
+
+			route := new(models.ProcessRoute)
+			route.ID = plan_bom.RouteId
+			err = services.JdbcClient.GetJdbcModelById(route)
+			if err != nil {
+				utils.PrintSqlErr(err)
+				return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+			}
+			plan_bom.ProcessRoute = route
+
+			inspect_program := new(models.QualityInspectProgram)
+			inspect_program.ID = plan_bom.InspectProgramId
+			err = services.JdbcClient.GetJdbcModelById(inspect_program)
+			if err != nil {
+				utils.PrintSqlErr(err)
+				return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+			}
+			plan_bom.QualityInspectProgram = inspect_program
+
+			bom := new(models.ProductBom)
+			bom.ID = plan_bom.BomId
+			err = services.JdbcClient.GetJdbcModelById(bom)
+			if err != nil {
+				utils.PrintSqlErr(err)
+				return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+			}
+			plan_bom.ProductBom = bom
+
+			bom_list[j] = plan_bom
+		}
+		model.BomList = &bom_list
+
+		deviceParam := new(models.ProductPlanDevice)
+		deviceParam.PlanId = model.ID
+		device_result, err := services.JdbcClient.GetJdbcListByObject(deviceParam)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+		}
+		device_list := utils.ConvertInterface[[]models.ProductPlanDevice](device_result)
+
+		for j := range device_list {
+			plan_device := device_list[j]
+
+			device := new(models.Device)
+			device.ID = plan_device.DeviceId
+			err = services.JdbcClient.GetJdbcModelById(device)
+			if err != nil {
+				utils.PrintSqlErr(err)
+				return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+			}
+			plan_device.Device = device
+
+			device_list[j] = plan_device
+		}
+
+		userParam := new(models.ProductPlanUser)
+		userParam.PlanId = model.ID
+		user_result, err := services.JdbcClient.GetJdbcListByObject(userParam)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+		}
+		user_list := utils.ConvertInterface[[]models.ProductPlanUser](user_result)
+
+		for j := range user_list {
+			plan_user := user_list[j]
+
+			user := new(models.SysUser)
+			user.ID = plan_user.UserId
+			err = services.JdbcClient.GetJdbcModelById(user)
+			if err != nil {
+				utils.PrintSqlErr(err)
+				return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+			}
+			plan_user.User = user
+
+			user_list[j] = plan_user
+		}
+
+		list[i] = model
+	}
+	result.Records = list
+	return c.JSON(http.StatusOK, result)
+
+}
+
+func productPlanGetList(c echo.Context) error {
+	var paramMap map[string]interface{}
+	if err := c.Bind(&paramMap); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	result, err := services.JdbcClient.GetJdbcList(paramMap, models.ProductPlan{})
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+	}
+	list := utils.ConvertInterface[[]models.ProductPlan](result)
+	if len(list) == 0 {
+		return c.JSON(http.StatusOK, []string{})
+	}
+	return c.JSON(http.StatusOK, list)
+}
+
+func productPlanSave(c echo.Context) error {
+	tx, _ := services.MYSQL_DB.Beginx()
+
+	sess, _ := session.Get("auth_session", c)
+	user_id, ok := sess.Values["user_id"].(int64)
+	if !ok {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("用户请先登录", ""))
+	}
+	plan := new(models.ProductPlan)
+	if err := c.Bind(plan); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	err_msg := productPlan_generateCode(plan, tx)
+	if err_msg != "" {
+		tx.Rollback()
+		return c.JSON(http.StatusBadRequest, utils.ErrorResponse(err_msg, ""))
+	}
+	status := models.Product_Plan_Status_Pending
+	plan.Status = &status
+	plan.CreateId = &user_id
+	err := services.JdbcClient.JdbcInsert(plan, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	bom_list := *plan.BomList
+	for _, detail := range bom_list {
+		detail.PlanId = plan.ID
+		err := services.JdbcClient.JdbcInsert(&detail, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+	}
+	device_list := *plan.DeviceList
+	for _, detail := range device_list {
+		detail.PlanId = plan.ID
+		err := services.JdbcClient.JdbcInsert(&detail, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+	}
+	user_list := *plan.UserList
+	for _, detail := range user_list {
+		detail.PlanId = plan.ID
+		err := services.JdbcClient.JdbcInsert(&detail, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+	}
+
+	if plan.FileList == nil {
+		tx.Commit()
+		return c.JSON(http.StatusOK, plan)
+	}
+	fileList := *plan.FileList
+	for _, file := range fileList {
+		file.RefId = plan.ID
+		refType := models.File_Ref_Type_Product_Plan
+		file.RefType = &refType
+
+		err := services.JdbcClient.JdbcInsert(&file, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+	}
+	tx.Commit()
+	return c.JSON(http.StatusOK, plan)
+
+}
+
+func productPlanUpdate(c echo.Context) error {
+	tx, _ := services.MYSQL_DB.Beginx()
+
+	plan := new(models.ProductPlan)
+	if err := c.Bind(plan); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	sess, _ := session.Get("auth_session", c)
+	user_id, ok := sess.Values["user_id"].(int64)
+	if !ok {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("用户请先登录", ""))
+	}
+	plan.UpdateId = &user_id
+	err := services.JdbcClient.JdbcUpdateById(plan, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	plan_bom := new(models.ProductPlanBom)
+	plan_bom.PlanId = plan.ID
+	err = services.JdbcClient.JdbcRemove(plan_bom, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	bom_list := *plan.BomList
+	for _, detail := range bom_list {
+		detail.PlanId = plan.ID
+		err := services.JdbcClient.JdbcInsert(&detail, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+	}
+
+	plan_device := new(models.ProductPlanDevice)
+	plan_device.PlanId = plan.ID
+	err = services.JdbcClient.JdbcRemove(plan_device, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	device_list := *plan.DeviceList
+	for _, detail := range device_list {
+		detail.PlanId = plan.ID
+		err := services.JdbcClient.JdbcInsert(&detail, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+	}
+
+	plan_user := new(models.ProductPlanUser)
+	plan_user.PlanId = plan.ID
+	err = services.JdbcClient.JdbcRemove(plan_user, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	user_list := *plan.UserList
+	for _, detail := range user_list {
+		detail.PlanId = plan.ID
+		err := services.JdbcClient.JdbcInsert(&detail, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+	}
+
+	if plan.FileList == nil {
+		tx.Commit()
+		return c.JSON(http.StatusOK, plan)
+	}
+
+	fileList := *plan.FileList
+	for _, file := range fileList {
+		file.RefId = plan.ID
+		refType := models.File_Ref_Type_Product_Plan
+		file.RefType = &refType
+
+		err := services.JdbcClient.JdbcInsert(&file, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+	}
+
+	tx.Commit()
+	return c.JSON(http.StatusOK, plan)
+}
+
+func productPlanRemove(c echo.Context) error {
+	tx, _ := services.MYSQL_DB.Beginx()
+
+	plan := new(models.ProductPlan)
+	if err := c.Bind(plan); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+
+	err := services.JdbcClient.JdbcRemoveById(plan, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	minio_file := new(models.MinioFile)
+	minio_file.RefId = plan.ID
+	refType := models.File_Ref_Type_Product_Plan
+	minio_file.RefType = &refType
+	err = services.JdbcClient.JdbcRemove(minio_file, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	plan_bom := new(models.ProductPlanBom)
+	plan_bom.PlanId = plan.ID
+	err = services.JdbcClient.JdbcRemove(plan_bom, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	plan_device := new(models.ProductPlanDevice)
+	plan_device.PlanId = plan.ID
+	err = services.JdbcClient.JdbcRemove(plan_device, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	plan_user := new(models.ProductPlanUser)
+	plan_user.PlanId = plan.ID
+	err = services.JdbcClient.JdbcRemove(plan_user, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	tx.Commit()
+	return c.JSON(http.StatusOK, plan)
+}
+
+func productPlan_generateCode(model *models.ProductPlan, tx *sqlx.Tx) string {
+	sync := new(models.Synchronized)
+	sync.Sync.Lock()
+	defer sync.Sync.Unlock()
+
+	if model.Code != nil && len(*model.Code) > 0 {
+		modelParam := new(models.ProductPlan)
+		modelParam.Code = model.Code
+		modelParam.TenantId = model.TenantId
+		count, err := services.JdbcClient.GetJdbcCount(modelParam, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return "SQL执行失败"
+		}
+		if count > 0 {
+			return "编号已存在,请重新填写"
+		}
+	} else {
+		for {
+			code, err := services.GetFlowNo(models.Flow_No_Type_Product_Plan, *model.TenantId, tx)
+			if err != nil {
+				return "自动生成编码失败"
+			}
+			modelParam := new(models.ProductPlan)
+			modelParam.Code = &code
+			modelParam.TenantId = model.TenantId
+			count, err := services.JdbcClient.GetJdbcCount(modelParam, tx)
+			if err != nil {
+				utils.PrintSqlErr(err)
+				return "SQL执行失败"
+			}
+			if count == 0 {
+				model.Code = &code
+				break
+			}
+		}
+
+	}
+	return ""
+}

+ 555 - 0
handlers/product_preplan.go

@@ -0,0 +1,555 @@
+package handlers
+
+import (
+	"net/http"
+
+	"easydo-echo_win7/models"
+	"easydo-echo_win7/services"
+	"easydo-echo_win7/utils"
+
+	"easydo-echo_win7/middleware"
+
+	"github.com/labstack/echo-contrib/session"
+	"github.com/labstack/echo/v4"
+)
+
+func Add_product_pre_plan_to_routes(e *echo.Echo) {
+	group := e.Group("/productPrePlan")
+	group.Use(middleware.AuthMiddleware)
+	group.POST("/getPage", productPrePlanGetPage)
+	group.POST("/save", productPrePlanSave)
+}
+
+func productPrePlanGetPage(c echo.Context) error {
+	var paramMap map[string]interface{}
+	if err := c.Bind(&paramMap); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	result, err := services.JdbcClient.GetJdbcPage(paramMap, models.SaleOrder{})
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+	}
+	list := utils.ConvertInterface[[]models.SaleOrder](result.Records)
+	if len(list) == 0 {
+		list = []models.SaleOrder{}
+	}
+	for i := range list {
+		model := list[i]
+		minioList, err := services.JdbcClient.GetMinioFile(model)
+		if err != nil {
+			utils.PrintSearchFileErr(err)
+		}
+		model.FileList = &minioList
+
+		detailParam := new(models.SaleOrderDetail)
+		detailParam.OrderId = model.ID
+		result, err := services.JdbcClient.GetJdbcListByObject(detailParam)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+		}
+		detail_list := utils.ConvertInterface[[]models.SaleOrderDetail](result)
+
+		for j := range detail_list {
+			detail := detail_list[j]
+
+			material := new(models.ProductMaterial)
+			material.Code = detail.MaterialCode
+			material.TenantId = model.TenantId
+			err = services.JdbcClient.GetJdbcModel(material)
+			if err != nil {
+				utils.PrintSqlErr(err)
+				return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+			}
+
+			detail.Material = material
+
+			bom := new(models.ProductBom)
+			parentId := models.Common_Value_Zero_String
+			bom.ParentId = &parentId
+			bom.MaterialCode = material.Code
+			bom.TenantId = model.TenantId
+			status := models.Status_Enable
+			bom.Status = &status
+			err = services.JdbcClient.GetJdbcModel(bom)
+			if err != nil {
+				utils.PrintSqlErr(err)
+				return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+			}
+
+			if bom.ID != nil && len(*bom.ID) > 0 {
+				//对于有bom的物料,查询出bom下所有组成(bom_list)和库存
+				bomParam := new(models.ProductBom)
+				bomParam.BomCode = bom.BomCode
+				bomParam.TenantId = model.TenantId
+				result, err := services.JdbcClient.GetJdbcListByObject(bomParam)
+				if err != nil {
+					utils.PrintSqlErr(err)
+					return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+				}
+				bom_list := utils.ConvertInterface[[]models.ProductBom](result)
+
+				for k := range bom_list {
+					bom_model := bom_list[k]
+
+					material := new(models.ProductMaterial)
+					material.Code = bom_model.MaterialCode
+					material.TenantId = model.TenantId
+					err = services.JdbcClient.GetJdbcModel(material)
+					if err != nil {
+						utils.PrintSqlErr(err)
+						return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+					}
+					bom_model.Material = material
+
+					warehouseMaterial := new(models.WarehouseMaterial)
+					warehouseMaterial.MaterialCode = material.Code
+					warehouseMaterial.TenantId = model.TenantId
+					warehouse_material_status := models.Status_Enable
+					warehouseMaterial.Status = &warehouse_material_status
+					warehouse_material_result, err := services.JdbcClient.GetJdbcListByObject(warehouseMaterial)
+					if err != nil {
+						utils.PrintSqlErr(err)
+						return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+					}
+					warehouse_material_list := utils.ConvertInterface[[]models.WarehouseMaterial](warehouse_material_result)
+					if warehouse_material_list != nil {
+						for l := range warehouse_material_list {
+							warehouse_material_model := warehouse_material_list[l]
+
+							warehouse := new(models.Warehouse)
+							warehouse.ID = warehouse_material_model.WarehouseId
+							err = services.JdbcClient.GetJdbcModelById(warehouse)
+							if err != nil {
+								utils.PrintSqlErr(err)
+								return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+							}
+							warehouse_material_model.Warehouse = warehouse
+
+							warehouse_material_list[l] = warehouse_material_model
+						}
+
+						bom_model.WarehouseMaterialList = &warehouse_material_list
+					}
+
+					bom_list[k] = bom_model
+				}
+				detail.BomList = &bom_list
+			} else {
+				//对于没有bom的物料,只需要查询出库存
+				warehouseMaterial := new(models.WarehouseMaterial)
+				warehouseMaterial.MaterialCode = material.Code
+				warehouseMaterial.TenantId = model.TenantId
+				warehouse_material_status := models.Status_Enable
+				warehouseMaterial.Status = &warehouse_material_status
+				warehouse_material_result, err := services.JdbcClient.GetJdbcListByObject(warehouseMaterial)
+				if err != nil {
+					utils.PrintSqlErr(err)
+					return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+				}
+				warehouse_material_list := utils.ConvertInterface[[]models.WarehouseMaterial](warehouse_material_result)
+
+				if warehouse_material_list != nil {
+					for k := range warehouse_material_list {
+						warehouse_material_model := warehouse_material_list[k]
+
+						warehouse := new(models.Warehouse)
+						warehouse.ID = warehouse_material_model.WarehouseId
+						err = services.JdbcClient.GetJdbcModelById(warehouse)
+						if err != nil {
+							utils.PrintSqlErr(err)
+							return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+						}
+						warehouse_material_model.Warehouse = warehouse
+
+						warehouse_material_list[k] = warehouse_material_model
+					}
+
+					detail.WarehouseMaterialList = &warehouse_material_list
+				}
+
+			}
+
+			detail_list[j] = detail
+		}
+		model.ChildrenList = &detail_list
+
+		if model.ManagerId != nil && *model.ManagerId != 0 {
+			manager := new(models.SysUser)
+			manager.ID = model.ManagerId
+			err = services.JdbcClient.GetJdbcModelById(manager)
+			if err != nil {
+				utils.PrintSqlErr(err)
+				return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+			}
+			model.ManagerName = manager.NickName
+		}
+		if model.CustomerId != nil && *model.CustomerId != "" {
+			customer := new(models.Customer)
+			customer.ID = model.CustomerId
+			err = services.JdbcClient.GetJdbcModelById(customer)
+			if err != nil {
+				utils.PrintSqlErr(err)
+				return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+			}
+			model.CustomerName = customer.Name
+		}
+
+		list[i] = model
+	}
+	result.Records = list
+	return c.JSON(http.StatusOK, result)
+
+}
+
+func productPrePlanSave(c echo.Context) error {
+	tx, _ := services.MYSQL_DB.Beginx()
+
+	sess, _ := session.Get("auth_session", c)
+	user_id, ok := sess.Values["user_id"].(int64)
+	if !ok {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("用户请先登录", ""))
+	}
+
+	planVo := new(models.ProductPrePlanVo)
+	if err := c.Bind(planVo); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+
+	sale_order := new(models.SaleOrder)
+	sale_order.ID = planVo.SaleOrderId
+	err := services.JdbcClient.GetJdbcModelById(sale_order, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	saleOrderStatus := models.Sale_Order_Status_Processing
+	sale_order.Status = &saleOrderStatus
+	err = services.JdbcClient.JdbcUpdateById(sale_order, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	product_plan_vo_list := *planVo.ProductPlanVoList
+
+	if len(product_plan_vo_list) > 0 {
+		plan_filter_list := utils.Filter(product_plan_vo_list, func(item models.ProductPlanVo) bool {
+			return *item.PlanProductNumber > 0
+		})
+		plan_warehouse_filter_list := utils.Filter(product_plan_vo_list, func(item models.ProductPlanVo) bool {
+			return len(*item.WarehouseMaterialVoList) > 0
+		})
+		product_plan := new(models.ProductPlan)
+		if len(plan_filter_list) > 0 {
+			product_plan.TenantId = sale_order.TenantId
+			product_plan.SaleOrderId = sale_order.ID
+			err_msg := productPlan_generateCode(product_plan, tx)
+			if err_msg != "" {
+				tx.Rollback()
+				return c.JSON(http.StatusBadRequest, utils.ErrorResponse(err_msg, ""))
+			}
+
+			if planVo.ProductPlanName != nil && len(*planVo.ProductPlanName) > 0 {
+				product_plan.Name = planVo.ProductPlanName
+			} else {
+				product_plan.Name = product_plan.Code
+			}
+
+			status := models.Product_Plan_Status_Pending
+			product_plan.Status = &status
+			product_plan.CreateId = &user_id
+			product_plan.BeginDate = planVo.PlanBeginDate
+			product_plan.EndDate = planVo.PlanEndDate
+			err = services.JdbcClient.JdbcInsert(product_plan, tx)
+			if err != nil {
+				utils.PrintSqlErr(err)
+				tx.Rollback()
+				return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+			}
+		}
+
+		for _, detail := range plan_filter_list {
+			product_plan_bom := new(models.ProductPlanBom)
+			product_plan_bom.BomId = detail.BomId
+			product_plan_bom.PlanId = product_plan.ID
+			product_plan_bom.Number = detail.PlanProductNumber
+
+			bom := new(models.ProductBom)
+			bom.ID = detail.BomId
+			err = services.JdbcClient.GetJdbcModelById(bom, tx)
+			if err != nil {
+				utils.PrintSqlErr(err)
+				tx.Rollback()
+				return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+			}
+			product_plan_bom.RouteId = bom.RouteId
+
+			route := new(models.ProcessRoute)
+			route.ID = bom.RouteId
+			err = services.JdbcClient.GetJdbcModelById(route, tx)
+			if err != nil {
+				utils.PrintSqlErr(err)
+				tx.Rollback()
+				return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+			}
+			product_plan_bom.InspectProgramId = route.InspectProgramId
+
+			err := services.JdbcClient.JdbcInsert(product_plan_bom, tx)
+			if err != nil {
+				utils.PrintSqlErr(err)
+				tx.Rollback()
+				return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+			}
+		}
+		for _, detail := range plan_warehouse_filter_list {
+
+			warehouseMaterialVoList := *detail.WarehouseMaterialVoList
+			for _, warehouseMaterial := range warehouseMaterialVoList {
+				warehouse_record_model := new(models.WarehouseRecord)
+				recordType := models.Warehouse_Record_Type_Lock
+				warehouse_record_model.Type = &recordType
+				warehouse_record_model.MaterialCode = detail.MaterialCode
+				warehouse_record_model.Number = warehouseMaterial.LockedNumber
+				warehouse_record_model.FromWarehouseId = warehouseMaterial.WarehouseId
+				warehouse_record_model.TenantId = sale_order.TenantId
+				warehouse_record_model.CreateId = &user_id
+				recordRefType := models.Warehouse_Record_Ref_Type_Product
+				warehouse_record_model.RefType = &recordRefType
+				warehouse_record_model.RefId = product_plan.ID
+				warehouse_record_model.SaleOrderId = sale_order.ID
+				err := services.JdbcClient.JdbcInsert(warehouse_record_model, tx)
+				if err != nil {
+					utils.PrintSqlErr(err)
+					tx.Rollback()
+					return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+				}
+
+				warehouse_material_model := new(models.WarehouseMaterial)
+				warehouse_material_model.WarehouseId = warehouseMaterial.WarehouseId
+				warehouse_material_model.MaterialCode = detail.MaterialCode
+				err = services.JdbcClient.GetJdbcModel(warehouse_material_model, tx)
+				if err != nil {
+					utils.PrintSqlErr(err)
+					tx.Rollback()
+					return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+				}
+				warehouseMaterialNumber := *warehouse_material_model.Number - *warehouseMaterial.LockedNumber
+				warehouse_material_model.Number = &warehouseMaterialNumber
+				warehouseMaterialLockedNumber := *warehouse_material_model.LockedNumber + *warehouseMaterial.LockedNumber
+				warehouse_material_model.LockedNumber = &warehouseMaterialLockedNumber
+				warehouse_material_model.UpdateId = &user_id
+				err = services.JdbcClient.JdbcUpdateById(warehouse_material_model, tx)
+				if err != nil {
+					utils.PrintSqlErr(err)
+					tx.Rollback()
+					return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+				}
+
+			}
+		}
+	}
+
+	purchase_plan_vo_list := *planVo.PurchasePlanVoList
+	if len(purchase_plan_vo_list) > 0 {
+		plan_filter_list := utils.Filter(purchase_plan_vo_list, func(item models.PurchasePlanVo) bool {
+			return *item.PlanPurchaseNumber > 0
+		})
+		warehouse_filter_list := utils.Filter(purchase_plan_vo_list, func(item models.PurchasePlanVo) bool {
+			return len(*item.WarehouseMaterialVoList) > 0
+		})
+
+		purchase_plan := new(models.PurchasePlan)
+		if len(plan_filter_list) > 0 {
+			purchase_plan.TenantId = sale_order.TenantId
+			err_msg := purchasePlan_generateCode(purchase_plan, tx)
+			if err_msg != "" {
+				tx.Rollback()
+				return c.JSON(http.StatusBadRequest, utils.ErrorResponse(err_msg, ""))
+			}
+			if planVo.PurchasePlanName != nil && len(*planVo.PurchasePlanName) > 0 {
+				purchase_plan.Name = planVo.PurchasePlanName
+			} else {
+				purchase_plan.Name = purchase_plan.Code
+			}
+			purchase_plan.SaleOrderId = planVo.SaleOrderId
+			purchasePlanStatus := models.Purchase_Plan_Status_Pending
+			purchase_plan.Status = &purchasePlanStatus
+			purchase_plan.TenantId = sale_order.TenantId
+			purchase_plan.BeginDate = planVo.PlanBeginDate
+			purchase_plan.EndDate = planVo.PlanEndDate
+			purchase_plan.CreateId = &user_id
+			err := services.JdbcClient.JdbcInsert(purchase_plan, tx)
+			if err != nil {
+				utils.PrintSqlErr(err)
+				tx.Rollback()
+				return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+			}
+
+		}
+
+		for _, detail := range plan_filter_list {
+			purchase_plan_detail := new(models.PurchasePlanDetail)
+			purchase_plan_detail.MaterialCode = detail.MaterialCode
+			purchase_plan_detail.PlanId = purchase_plan.ID
+			purchase_plan_detail.Number = detail.PlanPurchaseNumber
+
+			err := services.JdbcClient.JdbcInsert(purchase_plan_detail, tx)
+			if err != nil {
+				utils.PrintSqlErr(err)
+				tx.Rollback()
+				return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+			}
+		}
+
+		for _, detail := range warehouse_filter_list {
+			warehouseMaterialVoList := *detail.WarehouseMaterialVoList
+			for _, warehouseMaterial := range warehouseMaterialVoList {
+				warehouse_record_model := new(models.WarehouseRecord)
+				recordType := models.Warehouse_Record_Type_Lock
+				warehouse_record_model.Type = &recordType
+				warehouse_record_model.MaterialCode = detail.MaterialCode
+				warehouse_record_model.Number = warehouseMaterial.LockedNumber
+				warehouse_record_model.FromWarehouseId = warehouseMaterial.WarehouseId
+				warehouse_record_model.TenantId = sale_order.TenantId
+				warehouse_record_model.CreateId = &user_id
+				recordRefType := models.Warehouse_Record_Ref_Type_Purchase
+				warehouse_record_model.RefType = &recordRefType
+				warehouse_record_model.RefId = purchase_plan.ID
+				warehouse_record_model.SaleOrderId = sale_order.ID
+				err := services.JdbcClient.JdbcInsert(warehouse_record_model, tx)
+				if err != nil {
+					utils.PrintSqlErr(err)
+					tx.Rollback()
+					return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+				}
+
+				warehouse_material_model := new(models.WarehouseMaterial)
+				warehouse_material_model.WarehouseId = warehouseMaterial.WarehouseId
+				warehouse_material_model.MaterialCode = detail.MaterialCode
+				err = services.JdbcClient.GetJdbcModel(warehouse_material_model, tx)
+				if err != nil {
+					utils.PrintSqlErr(err)
+					tx.Rollback()
+					return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+				}
+				warehouseMaterialNumber := *warehouse_material_model.Number - *warehouseMaterial.LockedNumber
+				warehouse_material_model.Number = &warehouseMaterialNumber
+				warehouseMaterialLockedNumber := *warehouse_material_model.LockedNumber + *warehouseMaterial.LockedNumber
+				warehouse_material_model.LockedNumber = &warehouseMaterialLockedNumber
+				warehouse_material_model.UpdateId = &user_id
+				err = services.JdbcClient.JdbcUpdateById(warehouse_material_model, tx)
+				if err != nil {
+					utils.PrintSqlErr(err)
+					tx.Rollback()
+					return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+				}
+
+			}
+		}
+	}
+
+	outsourcing_plan_vo_list := *planVo.OutsourcingPlanVoList
+	if len(outsourcing_plan_vo_list) > 0 {
+		plan_filter_list := utils.Filter(outsourcing_plan_vo_list, func(item models.OutsourcingPlanVo) bool {
+			return *item.PlanOutsourcingNumber > 0
+		})
+		warehouse_filter_list := utils.Filter(outsourcing_plan_vo_list, func(item models.OutsourcingPlanVo) bool {
+			return len(*item.WarehouseMaterialVoList) > 0
+		})
+
+		outsourcing_plan := new(models.OutsourcingPlan)
+		if len(plan_filter_list) > 0 {
+			outsourcing_plan.TenantId = sale_order.TenantId
+			err_msg := outsourcingPlan_generateCode(outsourcing_plan, tx)
+			if err_msg != "" {
+				tx.Rollback()
+				return c.JSON(http.StatusBadRequest, utils.ErrorResponse(err_msg, ""))
+			}
+			if planVo.OutsourcingPlanName != nil && len(*planVo.OutsourcingPlanName) > 0 {
+				outsourcing_plan.Name = planVo.OutsourcingPlanName
+			} else {
+				outsourcing_plan.Name = outsourcing_plan.Code
+			}
+			outsourcing_plan.SaleOrderId = planVo.SaleOrderId
+			purchasePlanStatus := models.Outsourcing_Plan_Status_Pending
+			outsourcing_plan.Status = &purchasePlanStatus
+			outsourcing_plan.TenantId = sale_order.TenantId
+			outsourcing_plan.BeginDate = planVo.PlanBeginDate
+			outsourcing_plan.EndDate = planVo.PlanEndDate
+			outsourcing_plan.CreateId = &user_id
+			err := services.JdbcClient.JdbcInsert(outsourcing_plan, tx)
+			if err != nil {
+				utils.PrintSqlErr(err)
+				tx.Rollback()
+				return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+			}
+		}
+		
+		for _, detail := range plan_filter_list {
+			outsourcing_plan_detail := new(models.OutsourcingPlanDetail)
+			outsourcing_plan_detail.MaterialCode = detail.MaterialCode
+			outsourcing_plan_detail.PlanId = outsourcing_plan.ID
+			outsourcing_plan_detail.Number = detail.PlanOutsourcingNumber
+			err := services.JdbcClient.JdbcInsert(outsourcing_plan_detail, tx)
+			if err != nil {
+				utils.PrintSqlErr(err)
+				tx.Rollback()
+				return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+			}
+		}
+
+		for _, detail := range warehouse_filter_list {
+			warehouseMaterialVoList := *detail.WarehouseMaterialVoList
+			for _, warehouseMaterial := range warehouseMaterialVoList {
+				warehouse_record_model := new(models.WarehouseRecord)
+				recordType := models.Warehouse_Record_Type_Lock
+				warehouse_record_model.Type = &recordType
+				warehouse_record_model.MaterialCode = detail.MaterialCode
+				warehouse_record_model.Number = warehouseMaterial.LockedNumber
+				warehouse_record_model.FromWarehouseId = warehouseMaterial.WarehouseId
+				warehouse_record_model.TenantId = sale_order.TenantId
+				warehouse_record_model.CreateId = &user_id
+				recordRefType := models.Warehouse_Record_Ref_Type_Outsourcing
+				warehouse_record_model.RefType = &recordRefType
+				warehouse_record_model.RefId = outsourcing_plan.ID
+				warehouse_record_model.SaleOrderId = sale_order.ID
+				err := services.JdbcClient.JdbcInsert(warehouse_record_model, tx)
+				if err != nil {
+					utils.PrintSqlErr(err)
+					tx.Rollback()
+					return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+				}
+
+				warehouse_material_model := new(models.WarehouseMaterial)
+				warehouse_material_model.WarehouseId = warehouseMaterial.WarehouseId
+				warehouse_material_model.MaterialCode = detail.MaterialCode
+				err = services.JdbcClient.GetJdbcModel(warehouse_material_model, tx)
+				if err != nil {
+					utils.PrintSqlErr(err)
+					tx.Rollback()
+					return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+				}
+				warehouseMaterialNumber := *warehouse_material_model.Number - *warehouseMaterial.LockedNumber
+				warehouse_material_model.Number = &warehouseMaterialNumber
+				warehouseMaterialLockedNumber := *warehouse_material_model.LockedNumber + *warehouseMaterial.LockedNumber
+				warehouse_material_model.LockedNumber = &warehouseMaterialLockedNumber
+				warehouse_material_model.UpdateId = &user_id
+				err = services.JdbcClient.JdbcUpdateById(warehouse_material_model, tx)
+				if err != nil {
+					utils.PrintSqlErr(err)
+					tx.Rollback()
+					return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+				}
+
+			}
+		}
+	}
+
+	tx.Commit()
+	return c.JSON(http.StatusOK, planVo)
+
+}

+ 357 - 0
handlers/purchase_order.go

@@ -0,0 +1,357 @@
+package handlers
+
+import (
+	"net/http"
+
+	"easydo-echo_win7/models"
+	"easydo-echo_win7/services"
+	"easydo-echo_win7/utils"
+
+	"easydo-echo_win7/middleware"
+
+	"github.com/jmoiron/sqlx"
+	"github.com/labstack/echo-contrib/session"
+	"github.com/labstack/echo/v4"
+)
+
+func Add_purchase_order_to_routes(e *echo.Echo) {
+	group := e.Group("/purchaseOrder")
+	group.Use(middleware.AuthMiddleware)
+	group.POST("/getPage", purchaseOrderGetPage)
+	group.POST("/getList", purchaseOrderGetList)
+	group.POST("/save", purchaseOrderSave)
+	group.POST("/update", purchaseOrderUpdate)
+	group.POST("/remove", purchaseOrderRemove)
+
+}
+
+func purchaseOrderGetPage(c echo.Context) error {
+	var paramMap map[string]interface{}
+	if err := c.Bind(&paramMap); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	result, err := services.JdbcClient.GetJdbcPage(paramMap, models.PurchaseOrder{})
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+	}
+	list := utils.ConvertInterface[[]models.PurchaseOrder](result.Records)
+	if len(list) == 0 {
+		list = []models.PurchaseOrder{}
+	}
+	for i := range list {
+		model := list[i]
+		minioList, err := services.JdbcClient.GetMinioFile(model)
+		if err != nil {
+			utils.PrintSearchFileErr(err)
+		}
+		model.FileList = &minioList
+
+		sale_order := new(models.SaleOrder)
+		sale_order.ID = model.SaleOrderId
+		err = services.JdbcClient.GetJdbcModelById(sale_order)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+		}
+		model.SaleOrder = sale_order
+
+		purchase_plan := new(models.PurchasePlan)
+		purchase_plan.ID = model.PurchasePlanId
+		err = services.JdbcClient.GetJdbcModelById(purchase_plan)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+		}
+		model.PurchasePlan = purchase_plan
+
+		customer := new(models.Customer)
+		customer.ID = model.CustomerId
+		err = services.JdbcClient.GetJdbcModelById(customer)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+		}
+		model.Customer = customer
+
+		detailParam := new(models.PurchaseOrderDetail)
+		detailParam.OrderId = model.ID
+		result, err := services.JdbcClient.GetJdbcListByObject(detailParam)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+		}
+		detail_list := utils.ConvertInterface[[]models.PurchaseOrderDetail](result)
+
+		for j := range detail_list {
+			detail := detail_list[j]
+
+			material := new(models.ProductMaterial)
+			material.Code = detail.MaterialCode
+			err = services.JdbcClient.GetJdbcModel(material)
+			if err != nil {
+				utils.PrintSqlErr(err)
+				return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+			}
+
+			detail.ProductMaterial = material
+
+			detail_list[j] = detail
+		}
+		model.ChildrenList = &detail_list
+
+		list[i] = model
+	}
+	result.Records = list
+	return c.JSON(http.StatusOK, result)
+
+}
+
+func purchaseOrderGetList(c echo.Context) error {
+	var paramMap map[string]interface{}
+	if err := c.Bind(&paramMap); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	result, err := services.JdbcClient.GetJdbcList(paramMap, models.PurchaseOrder{})
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+	}
+	list := utils.ConvertInterface[[]models.PurchaseOrder](result)
+	if len(list) == 0 {
+		return c.JSON(http.StatusOK, []string{})
+	}
+	return c.JSON(http.StatusOK, list)
+}
+
+func purchaseOrderSave(c echo.Context) error {
+	tx, _ := services.MYSQL_DB.Beginx()
+
+	sess, _ := session.Get("auth_session", c)
+	user_id, ok := sess.Values["user_id"].(int64)
+	if !ok {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("用户请先登录", ""))
+	}
+	order := new(models.PurchaseOrder)
+	if err := c.Bind(order); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	err_msg := purchaseOrder_generateCode(order, tx)
+	if err_msg != "" {
+		tx.Rollback()
+		return c.JSON(http.StatusBadRequest, utils.ErrorResponse(err_msg, ""))
+	}
+	status := models.Purchase_Order_Status_Pending
+	order.Status = &status
+	order.CreateId = &user_id
+	services.JdbcClient.JdbcInsert(order, tx)
+
+	detail_list := *order.ChildrenList
+	for _, detail := range detail_list {
+		detail.OrderId = order.ID
+		err := services.JdbcClient.JdbcInsert(&detail, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+	}
+
+	purchase_plan := new(models.PurchasePlan)
+	purchase_plan.ID = order.PurchasePlanId
+	plan_status := models.Purchase_Plan_Status_Processing
+	purchase_plan.Status = &plan_status
+	err := services.JdbcClient.JdbcUpdateById(purchase_plan, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	if order.FileList == nil {
+		tx.Commit()
+		return c.JSON(http.StatusOK, order)
+	}
+	fileList := *order.FileList
+	for _, file := range fileList {
+		file.RefId = order.ID
+		refType := models.File_Ref_Type_Purchase_Order
+		file.RefType = &refType
+
+		err := services.JdbcClient.JdbcInsert(&file, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+	}
+
+	tx.Commit()
+	return c.JSON(http.StatusOK, order)
+}
+
+func purchaseOrderUpdate(c echo.Context) error {
+	tx, _ := services.MYSQL_DB.Beginx()
+
+	order := new(models.PurchaseOrder)
+	if err := c.Bind(order); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	sess, _ := session.Get("auth_session", c)
+	user_id, ok := sess.Values["user_id"].(int64)
+	if !ok {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("用户请先登录", ""))
+	}
+	order.UpdateId = &user_id
+	if order.ExpressNo != nil && *order.ExpressNo != "" {
+		status := models.Purchase_Order_Status_Shipped
+		order.Status = &status
+	}
+	err := services.JdbcClient.JdbcUpdateById(order, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	detail := new(models.PurchaseOrderDetail)
+	detail.OrderId = order.ID
+	err = services.JdbcClient.JdbcRemove(detail, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	detail_list := *order.ChildrenList
+	for _, detail := range detail_list {
+		detail.OrderId = order.ID
+		err := services.JdbcClient.JdbcInsert(&detail, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+	}
+
+	if order.Status != nil && *order.Status == models.Purchase_Order_Status_Complete {
+		err = services.JdbcClient.GetJdbcModel(order, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+		purchase_plan := new(models.PurchasePlan)
+		purchase_plan.ID = order.PurchasePlanId
+		plan_status := models.Purchase_Plan_Status_Complete
+		purchase_plan.Status = &plan_status
+		err = services.JdbcClient.JdbcUpdateById(purchase_plan, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+	}
+
+	if order.FileList == nil {
+		tx.Commit()
+		return c.JSON(http.StatusOK, order)
+	}
+
+	fileList := *order.FileList
+	for _, file := range fileList {
+		file.RefId = order.ID
+		refType := models.File_Ref_Type_Purchase_Order
+		file.RefType = &refType
+
+		err := services.JdbcClient.JdbcInsert(&file, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+	}
+
+	tx.Commit()
+	return c.JSON(http.StatusOK, order)
+}
+
+func purchaseOrderRemove(c echo.Context) error {
+	tx, _ := services.MYSQL_DB.Beginx()
+
+	order := new(models.PurchaseOrder)
+	if err := c.Bind(order); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+
+	err := services.JdbcClient.JdbcRemoveById(order, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	minio_file := new(models.MinioFile)
+	minio_file.RefId = order.ID
+	refType := models.File_Ref_Type_Purchase_Order
+	minio_file.RefType = &refType
+	err = services.JdbcClient.JdbcRemove(minio_file, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	detail := new(models.PurchaseOrderDetail)
+	detail.OrderId = order.ID
+	err = services.JdbcClient.JdbcRemove(detail, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	tx.Commit()
+	return c.JSON(http.StatusOK, order)
+}
+
+func purchaseOrder_generateCode(model *models.PurchaseOrder, tx *sqlx.Tx) string {
+	sync := new(models.Synchronized)
+	sync.Sync.Lock()
+	defer sync.Sync.Unlock()
+
+	if model.Code != nil && len(*model.Code) > 0 {
+		modelParam := new(models.PurchaseOrder)
+		modelParam.Code = model.Code
+		modelParam.TenantId = model.TenantId
+		count, err := services.JdbcClient.GetJdbcCount(modelParam, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return "SQL执行失败"
+		}
+		if count > 0 {
+			return "编号已存在,请重新填写"
+		}
+	} else {
+		for {
+			code, err := services.GetFlowNo(models.Flow_No_Type_Purchase_Order, *model.TenantId, tx)
+			if err != nil {
+				return "自动生成编码失败"
+			}
+			modelParam := new(models.PurchaseOrder)
+			modelParam.Code = &code
+			modelParam.TenantId = model.TenantId
+			count, err := services.JdbcClient.GetJdbcCount(modelParam, tx)
+			if err != nil {
+				utils.PrintSqlErr(err)
+				return "SQL执行失败"
+			}
+			if count == 0 {
+				model.Code = &code
+				break
+			}
+		}
+
+	}
+	return ""
+}

+ 314 - 0
handlers/purchase_plan.go

@@ -0,0 +1,314 @@
+package handlers
+
+import (
+	"net/http"
+
+	"easydo-echo_win7/models"
+	"easydo-echo_win7/services"
+	"easydo-echo_win7/utils"
+
+	"easydo-echo_win7/middleware"
+
+	"github.com/jmoiron/sqlx"
+	"github.com/labstack/echo-contrib/session"
+	"github.com/labstack/echo/v4"
+)
+
+func Add_purchase_plan_to_routes(e *echo.Echo) {
+	group := e.Group("/purchasePlan")
+	group.Use(middleware.AuthMiddleware)
+	group.POST("/getPage", purchasePlanGetPage)
+	group.POST("/getList", purchasePlanGetList)
+	group.POST("/save", purchasePlanSave)
+	group.POST("/update", purchasePlanUpdate)
+	group.POST("/remove", purchasePlanRemove)
+
+}
+
+func purchasePlanGetPage(c echo.Context) error {
+	var paramMap map[string]interface{}
+	if err := c.Bind(&paramMap); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	result, err := services.JdbcClient.GetJdbcPage(paramMap, models.PurchasePlan{})
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+	}
+	list := utils.ConvertInterface[[]models.PurchasePlan](result.Records)
+	if len(list) == 0 {
+		list = []models.PurchasePlan{}
+	}
+	for i := range list {
+		model := list[i]
+		minioList, err := services.JdbcClient.GetMinioFile(model)
+		if err != nil {
+			utils.PrintSearchFileErr(err)
+		}
+		model.FileList = &minioList
+
+		sale_order := new(models.SaleOrder)
+		sale_order.ID = model.SaleOrderId
+		err = services.JdbcClient.GetJdbcModelById(sale_order)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+		}
+		model.SaleOrder = sale_order
+
+		purchaseUser := new(models.SysUser)
+		purchaseUser.ID = model.PurchaseUserId
+		err = services.JdbcClient.GetJdbcModelById(purchaseUser)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+		}
+		model.PurchaseUserName = purchaseUser.NickName
+
+		detailParam := new(models.PurchasePlanDetail)
+		detailParam.PlanId = model.ID
+		result, err := services.JdbcClient.GetJdbcListByObject(detailParam)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+		}
+		detail_list := utils.ConvertInterface[[]models.PurchasePlanDetail](result)
+
+		for j := range detail_list {
+			detail := detail_list[j]
+
+			material := new(models.ProductMaterial)
+			material.Code = detail.MaterialCode
+			err = services.JdbcClient.GetJdbcModel(material)
+			if err != nil {
+				utils.PrintSqlErr(err)
+				return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+			}
+
+			detail.ProductMaterial = material
+
+			detail_list[j] = detail
+		}
+		model.ChildrenList = &detail_list
+
+		list[i] = model
+	}
+	result.Records = list
+	return c.JSON(http.StatusOK, result)
+
+}
+
+func purchasePlanGetList(c echo.Context) error {
+	var paramMap map[string]interface{}
+	if err := c.Bind(&paramMap); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	result, err := services.JdbcClient.GetJdbcList(paramMap, models.PurchasePlan{})
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+	}
+	list := utils.ConvertInterface[[]models.PurchasePlan](result)
+	if len(list) == 0 {
+		return c.JSON(http.StatusOK, []string{})
+	}
+	return c.JSON(http.StatusOK, list)
+}
+
+func purchasePlanSave(c echo.Context) error {
+	tx, _ := services.MYSQL_DB.Beginx()
+
+	sess, _ := session.Get("auth_session", c)
+	user_id, ok := sess.Values["user_id"].(int64)
+	if !ok {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("用户请先登录", ""))
+	}
+	plan := new(models.PurchasePlan)
+	if err := c.Bind(plan); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	err_msg := purchasePlan_generateCode(plan, tx)
+	if err_msg != "" {
+		tx.Rollback()
+		return c.JSON(http.StatusBadRequest, utils.ErrorResponse(err_msg, ""))
+	}
+	status := models.Purchase_Plan_Status_Pending
+	plan.Status = &status
+	plan.CreateId = &user_id
+	services.JdbcClient.JdbcInsert(plan, tx)
+
+	detail_list := *plan.ChildrenList
+	for _, detail := range detail_list {
+		detail.PlanId = plan.ID
+		err := services.JdbcClient.JdbcInsert(&detail, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+	}
+
+	if plan.FileList == nil {
+		tx.Commit()
+		return c.JSON(http.StatusOK, plan)
+	}
+	fileList := *plan.FileList
+	for _, file := range fileList {
+		file.RefId = plan.ID
+		refType := models.File_Ref_Type_Purchase_Plan
+		file.RefType = &refType
+
+		err := services.JdbcClient.JdbcInsert(&file, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+	}
+	tx.Commit()
+	return c.JSON(http.StatusOK, plan)
+
+}
+
+func purchasePlanUpdate(c echo.Context) error {
+	tx, _ := services.MYSQL_DB.Beginx()
+
+	plan := new(models.PurchasePlan)
+	if err := c.Bind(plan); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	sess, _ := session.Get("auth_session", c)
+	user_id, ok := sess.Values["user_id"].(int64)
+	if !ok {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("用户请先登录", ""))
+	}
+	plan.UpdateId = &user_id
+	err := services.JdbcClient.JdbcUpdateById(plan, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	detail := new(models.PurchasePlanDetail)
+	detail.PlanId = plan.ID
+	err = services.JdbcClient.JdbcRemove(detail, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	detail_list := *plan.ChildrenList
+	for _, detail := range detail_list {
+		detail.PlanId = plan.ID
+		err := services.JdbcClient.JdbcInsert(&detail, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+	}
+
+	if plan.FileList == nil {
+		tx.Commit()
+		return c.JSON(http.StatusOK, plan)
+	}
+
+	fileList := *plan.FileList
+	for _, file := range fileList {
+		file.RefId = plan.ID
+		refType := models.File_Ref_Type_Purchase_Plan
+		file.RefType = &refType
+
+		err := services.JdbcClient.JdbcInsert(&file, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+	}
+
+	tx.Commit()
+	return c.JSON(http.StatusOK, plan)
+}
+
+func purchasePlanRemove(c echo.Context) error {
+	tx, _ := services.MYSQL_DB.Beginx()
+
+	plan := new(models.PurchasePlan)
+	if err := c.Bind(plan); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+
+	err := services.JdbcClient.JdbcRemoveById(plan, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	minio_file := new(models.MinioFile)
+	minio_file.RefId = plan.ID
+	refType := models.File_Ref_Type_Purchase_Plan
+	minio_file.RefType = &refType
+	err = services.JdbcClient.JdbcRemove(minio_file, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	detail := new(models.PurchasePlanDetail)
+	detail.PlanId = plan.ID
+	err = services.JdbcClient.JdbcRemove(detail, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	tx.Commit()
+	return c.JSON(http.StatusOK, plan)
+}
+
+func purchasePlan_generateCode(model *models.PurchasePlan, tx *sqlx.Tx) string {
+	sync := new(models.Synchronized)
+	sync.Sync.Lock()
+	defer sync.Sync.Unlock()
+
+	if model.Code != nil && len(*model.Code) > 0 {
+		modelParam := new(models.PurchasePlan)
+		modelParam.Code = model.Code
+		modelParam.TenantId = model.TenantId
+		count, err := services.JdbcClient.GetJdbcCount(modelParam, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return "SQL执行失败"
+		}
+		if count > 0 {
+			return "编号已存在,请重新填写"
+		}
+	} else {
+		for {
+			code, err := services.GetFlowNo(models.Flow_No_Type_Purchase_Plan, *model.TenantId, tx)
+			if err != nil {
+				return "自动生成编码失败"
+			}
+			modelParam := new(models.PurchasePlan)
+			modelParam.Code = &code
+			modelParam.TenantId = model.TenantId
+			count, err := services.JdbcClient.GetJdbcCount(modelParam, tx)
+			if err != nil {
+				utils.PrintSqlErr(err)
+				return "SQL执行失败"
+			}
+			if count == 0 {
+				model.Code = &code
+				break
+			}
+		}
+
+	}
+	return ""
+}

+ 232 - 0
handlers/quality_inspect_program.go

@@ -0,0 +1,232 @@
+package handlers
+
+import (
+	"net/http"
+
+	"easydo-echo_win7/middleware"
+	"easydo-echo_win7/models"
+	"easydo-echo_win7/services"
+	"easydo-echo_win7/utils"
+
+	"github.com/jmoiron/sqlx"
+	"github.com/labstack/echo-contrib/session"
+	"github.com/labstack/echo/v4"
+)
+
+func Add_quality_inspect_program_to_routes(e *echo.Echo) {
+	group := e.Group("/qualityInspectProgram")
+	group.Use(middleware.AuthMiddleware)
+	group.POST("/getPage", qualityInspectProgramGetPage)
+	group.POST("/getList", qualityInspectProgramGetList)
+	group.POST("/save", qualityInspectProgramSave)
+	group.POST("/update", qualityInspectProgramUpdate)
+	group.POST("/remove", qualityInspectProgramRemove)
+
+}
+
+func qualityInspectProgramGetPage(c echo.Context) error {
+	var paramMap map[string]interface{}
+	if err := c.Bind(&paramMap); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	result, _ := services.JdbcClient.GetJdbcPage(paramMap, models.QualityInspectProgram{})
+	list := utils.ConvertInterface[[]models.QualityInspectProgram](result.Records)
+	if len(list) == 0 {
+		list = []models.QualityInspectProgram{}
+	}
+	for i := range list {
+		model := list[i]
+		minioList, err := services.JdbcClient.GetMinioFile(model)
+		if err != nil {
+			utils.PrintSearchFileErr(err)
+		}
+		model.FileList = &minioList
+
+		inspect_user := new(models.SysUser)
+		inspect_user.ID = model.InspectUserId
+		services.JdbcClient.GetJdbcModelById(inspect_user)
+		model.InspectUserName = inspect_user.NickName
+
+		review_user := new(models.SysUser)
+		review_user.ID = model.ReviewUserId
+		services.JdbcClient.GetJdbcModelById(review_user)
+		model.ReviewUserName = review_user.NickName
+
+		list[i] = model
+	}
+
+	result.Records = list
+	return c.JSON(http.StatusOK, result)
+
+}
+
+func qualityInspectProgramGetList(c echo.Context) error {
+	var paramMap map[string]interface{}
+	if err := c.Bind(&paramMap); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	result, _ := services.JdbcClient.GetJdbcList(paramMap, models.QualityInspectProgram{})
+	list := utils.ConvertInterface[[]models.QualityInspectProgram](result)
+	if len(list) == 0 {
+		return c.JSON(http.StatusOK, []string{})
+	}
+	return c.JSON(http.StatusOK, list)
+}
+
+func qualityInspectProgramSave(c echo.Context) error {
+	tx, _ := services.MYSQL_DB.Beginx()
+
+	program := new(models.QualityInspectProgram)
+	if err := c.Bind(program); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	sess, _ := session.Get("auth_session", c)
+	err_msg := qualityInspectProgram_generateCode(program, tx)
+	if err_msg != "" {
+		tx.Rollback()
+		return c.JSON(http.StatusBadRequest, utils.ErrorResponse(err_msg, ""))
+	}
+	user_id, ok := sess.Values["user_id"].(int64)
+	if !ok {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("用户请先登录", ""))
+	}
+	program.CreateId = &user_id
+	err := services.JdbcClient.JdbcInsert(program, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	if program.FileList == nil {
+		tx.Commit()
+		return c.JSON(http.StatusOK, program)
+	}
+	fileList := *program.FileList
+	for _, file := range fileList {
+		file.RefId = program.ID
+		refType := models.File_Ref_Type_Quality_Inspect_Program
+		file.RefType = &refType
+
+		err := services.JdbcClient.JdbcInsert(&file, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+	}
+	tx.Commit()
+	return c.JSON(http.StatusOK, program)
+}
+
+func qualityInspectProgramUpdate(c echo.Context) error {
+	tx, _ := services.MYSQL_DB.Beginx()
+
+	program := new(models.QualityInspectProgram)
+	if err := c.Bind(program); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	sess, _ := session.Get("auth_session", c)
+	user_id, ok := sess.Values["user_id"].(int64)
+	if !ok {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("用户请先登录", ""))
+	}
+	program.UpdateId = &user_id
+	err := services.JdbcClient.JdbcUpdateById(program, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	if program.FileList == nil {
+		tx.Commit()
+		return c.JSON(http.StatusOK, program)
+	}
+
+	fileList := *program.FileList
+	for _, file := range fileList {
+		file.RefId = program.ID
+		refType := models.File_Ref_Type_Quality_Inspect_Program
+		file.RefType = &refType
+
+		err := services.JdbcClient.JdbcInsert(&file, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+	}
+	tx.Commit()
+	return c.JSON(http.StatusOK, program)
+}
+
+func qualityInspectProgramRemove(c echo.Context) error {
+	tx, _ := services.MYSQL_DB.Beginx()
+
+	program := new(models.QualityInspectProgram)
+	if err := c.Bind(program); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+
+	err := services.JdbcClient.JdbcRemoveById(program, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	minio_file := new(models.MinioFile)
+	minio_file.RefId = program.ID
+	refType := models.File_Ref_Type_Quality_Inspect_Program
+	minio_file.RefType = &refType
+	err = services.JdbcClient.JdbcRemove(minio_file, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	tx.Commit()
+	return c.JSON(http.StatusOK, program)
+}
+
+func qualityInspectProgram_generateCode(model *models.QualityInspectProgram, tx *sqlx.Tx) string {
+	sync := new(models.Synchronized)
+	sync.Sync.Lock()
+	defer sync.Sync.Unlock()
+
+	if model.Code != nil && len(*model.Code) > 0 {
+		modelParam := new(models.QualityInspectProgram)
+		modelParam.Code = model.Code
+		modelParam.TenantId = model.TenantId
+		count, err := services.JdbcClient.GetJdbcCount(modelParam, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return "SQL执行失败"
+		}
+		if count > 0 {
+			return "编号已存在,请重新填写"
+		}
+	} else {
+		for {
+			code, err := services.GetFlowNo(models.Flow_No_Type_Quality_Inspect_Program, *model.TenantId, tx)
+			if err != nil {
+				return "自动生成编码失败"
+			}
+			modelParam := new(models.QualityInspectProgram)
+			modelParam.Code = &code
+			modelParam.TenantId = model.TenantId
+			count, err := services.JdbcClient.GetJdbcCount(modelParam, tx)
+			if err != nil {
+				utils.PrintSqlErr(err)
+				return "SQL执行失败"
+			}
+			if count == 0 {
+				model.Code = &code
+				break
+			}
+		}
+
+	}
+	return ""
+}

+ 68 - 0
handlers/role_menu.go

@@ -0,0 +1,68 @@
+package handlers
+
+import (
+	"net/http"
+
+	"easydo-echo_win7/models"
+	"easydo-echo_win7/services"
+	"easydo-echo_win7/utils"
+
+	"easydo-echo_win7/middleware"
+
+	"github.com/labstack/echo/v4"
+)
+
+func Add_role_menu_to_routes(e *echo.Echo) {
+	group := e.Group("/sysRolesMenus")
+	group.Use(middleware.AuthMiddleware)
+	group.POST("/getList", rolesMenusGetList)
+	group.POST("/batchSave", rolesMenusBatchSave)
+
+}
+
+func rolesMenusGetList(c echo.Context) error {
+	var paramMap map[string]interface{}
+	if err := c.Bind(&paramMap); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	result, err := services.JdbcClient.GetJdbcList(paramMap, models.SysRolesMenus{})
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	list := utils.ConvertInterface[[]models.SysRolesMenus](result)
+	if len(list) == 0 {
+		return c.JSON(http.StatusOK, []string{})
+	}
+	return c.JSON(http.StatusOK, list)
+}
+
+func rolesMenusBatchSave(c echo.Context) error {
+	tx, _ := services.MYSQL_DB.Beginx()
+
+	role_menu := new(models.SysRolesMenus)
+	if err := c.Bind(role_menu); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	err := services.JdbcClient.JdbcRemove(role_menu, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	menu_id_list := *role_menu.MenuIdList
+	for _, menu_id := range menu_id_list {
+		role_menu_c := new(models.SysRolesMenus)
+		role_menu_c.RoleID = role_menu.RoleID
+		role_menu_c.MenuID = &menu_id
+		err = services.JdbcClient.JdbcInsert(role_menu_c, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+	}
+	tx.Commit()
+	return c.JSON(http.StatusOK, role_menu)
+}

+ 113 - 0
handlers/roles.go

@@ -0,0 +1,113 @@
+package handlers
+
+import (
+	"net/http"
+
+	"easydo-echo_win7/models"
+	"easydo-echo_win7/services"
+	"easydo-echo_win7/utils"
+
+	// "github.com/labstack/echo-contrib/session"
+	"easydo-echo_win7/middleware"
+
+	"github.com/labstack/echo/v4"
+)
+
+func Add_role_to_routes(e *echo.Echo) {
+	group := e.Group("/sysRole")
+	group.Use(middleware.AuthMiddleware)
+	group.POST("/getPage", roleGetPage)
+	group.POST("/getList", roleGetList)
+	group.POST("/save", roleSave)
+	group.POST("/update", roleUpdate)
+	group.POST("/remove", roleRemove)
+}
+
+func roleGetPage(c echo.Context) error {
+	var paramMap map[string]interface{}
+	if err := c.Bind(&paramMap); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	result, err := services.JdbcClient.GetJdbcPage(paramMap, models.SysRole{})
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	list := utils.ConvertInterface[[]models.SysRole](result.Records)
+	if len(list) == 0 {
+		list = []models.SysRole{}
+	}
+	result.Records = list
+	return c.JSON(http.StatusOK, result)
+
+}
+
+func roleGetList(c echo.Context) error {
+	var paramMap map[string]interface{}
+	if err := c.Bind(&paramMap); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	result, err := services.JdbcClient.GetJdbcList(paramMap, models.SysRole{})
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	list := utils.ConvertInterface[[]models.SysRole](result)
+	if len(list) == 0 {
+		return c.JSON(http.StatusOK, []models.SysRole{})
+	}
+	return c.JSON(http.StatusOK, list)
+}
+
+func roleSave(c echo.Context) error {
+	role := new(models.SysRole)
+	if err := c.Bind(role); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	err := services.JdbcClient.JdbcInsert(role)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	return c.JSON(http.StatusOK, role)
+}
+
+func roleUpdate(c echo.Context) error {
+	role := new(models.SysRole)
+	if err := c.Bind(role); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	err := services.JdbcClient.JdbcUpdateById(role)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	return c.JSON(http.StatusOK, role)
+}
+
+func roleRemove(c echo.Context) error {
+	tx, _ := services.MYSQL_DB.Beginx()
+
+	role := new(models.SysRole)
+	if err := c.Bind(role); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+
+	roleMenuParam := new(models.SysRolesMenus)
+	roleMenuParam.RoleID = role.ID
+	err := services.JdbcClient.JdbcRemove(roleMenuParam, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	err = services.JdbcClient.JdbcRemoveById(role, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	tx.Commit()
+	return c.JSON(http.StatusOK, role)
+}

+ 300 - 0
handlers/sale_order.go

@@ -0,0 +1,300 @@
+package handlers
+
+import (
+	"net/http"
+
+	// "sort"
+
+	"easydo-echo_win7/models"
+	"easydo-echo_win7/services"
+	"easydo-echo_win7/utils"
+
+	"easydo-echo_win7/middleware"
+
+	"github.com/jmoiron/sqlx"
+	"github.com/labstack/echo-contrib/session"
+	"github.com/labstack/echo/v4"
+)
+
+func Add_sale_order_to_routes(e *echo.Echo) {
+	group := e.Group("/saleOrder")
+	group.Use(middleware.AuthMiddleware)
+	group.POST("/getPage", saleOrderGetPage)
+	group.POST("/save", saleOrderSave)
+	group.POST("/update", saleOrderUpdate)
+	group.POST("/remove", saleOrderRemove)
+}
+
+func saleOrderGetPage(c echo.Context) error {
+	var paramMap map[string]interface{}
+	if err := c.Bind(&paramMap); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	result, err := services.JdbcClient.GetJdbcPage(paramMap, models.SaleOrder{})
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+	}
+	list := utils.ConvertInterface[[]models.SaleOrder](result.Records)
+	if len(list) == 0 {
+		list = []models.SaleOrder{}
+	}
+	for i := range list {
+		model := list[i]
+		minioList, err := services.JdbcClient.GetMinioFile(model)
+		if err != nil {
+			utils.PrintSearchFileErr(err)
+		}
+		model.FileList = &minioList
+
+		detailParam := new(models.SaleOrderDetail)
+		detailParam.OrderId = model.ID
+		result, err := services.JdbcClient.GetJdbcListByObject(detailParam)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+		}
+		detail_list := utils.ConvertInterface[[]models.SaleOrderDetail](result)
+
+		for j := range detail_list {
+			detail := detail_list[j]
+
+			material := new(models.ProductMaterial)
+			material.Code = detail.MaterialCode
+			err = services.JdbcClient.GetJdbcModel(material)
+			if err != nil {
+				utils.PrintSqlErr(err)
+				return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+			}
+
+			detail.Material = material
+
+			detail_list[j] = detail
+		}
+		model.ChildrenList = &detail_list
+
+		if model.ManagerId != nil && *model.ManagerId != 0 {
+			manager := new(models.SysUser)
+			manager.ID = model.ManagerId
+			err = services.JdbcClient.GetJdbcModelById(manager)
+			if err != nil {
+				utils.PrintSqlErr(err)
+				return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+			}
+			model.ManagerName = manager.NickName
+		}
+		if model.CustomerId != nil && *model.CustomerId != "" {
+			customer := new(models.Customer)
+			customer.ID = model.CustomerId
+			err = services.JdbcClient.GetJdbcModelById(customer)
+			if err != nil {
+				utils.PrintSqlErr(err)
+				return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+			}
+			model.CustomerName = customer.Name
+		}
+
+		list[i] = model
+	}
+	result.Records = list
+	return c.JSON(http.StatusOK, result)
+}
+
+func saleOrderSave(c echo.Context) error {
+	tx, _ := services.MYSQL_DB.Beginx()
+
+	order := new(models.SaleOrder)
+	if err := c.Bind(order); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	sess, _ := session.Get("auth_session", c)
+	err_msg := saleOrder_generateCode(order, tx)
+	if err_msg != "" {
+		tx.Rollback()
+		return c.JSON(http.StatusBadRequest, utils.ErrorResponse(err_msg, ""))
+	}
+	user_id, ok := sess.Values["user_id"].(int64)
+	if !ok {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("用户请先登录", ""))
+	}
+	order.CreateId = &user_id
+	status := models.Sale_Order_Status_Pending
+	order.Status = &status
+	services.JdbcClient.JdbcInsert(order, tx)
+
+	detail_list := *order.ChildrenList
+	for _, detail := range detail_list {
+		detail.OrderId = order.ID
+		err := services.JdbcClient.JdbcInsert(&detail, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+	}
+
+	if order.FileList == nil {
+		tx.Commit()
+		return c.JSON(http.StatusOK, order)
+	}
+	fileList := *order.FileList
+	for _, file := range fileList {
+		file.RefId = order.ID
+		refType := models.File_Ref_Type_Sale_Order
+		file.RefType = &refType
+
+		err := services.JdbcClient.JdbcInsert(&file, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+	}
+
+	tx.Commit()
+	return c.JSON(http.StatusOK, order)
+
+}
+
+func saleOrderUpdate(c echo.Context) error {
+	tx, _ := services.MYSQL_DB.Beginx()
+
+	order := new(models.SaleOrder)
+	if err := c.Bind(order); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	sess, _ := session.Get("auth_session", c)
+	user_id, ok := sess.Values["user_id"].(int64)
+	if !ok {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("用户请先登录", ""))
+	}
+	order.UpdateId = &user_id
+	err := services.JdbcClient.JdbcUpdateById(order, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	order_detial := new(models.SaleOrderDetail)
+	order_detial.OrderId = order.ID
+	err = services.JdbcClient.JdbcRemove(order_detial, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	detail_list := *order.ChildrenList
+	for _, detail := range detail_list {
+		detail.OrderId = order.ID
+		err := services.JdbcClient.JdbcInsert(&detail, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+	}
+
+	if order.FileList == nil {
+		tx.Commit()
+		return c.JSON(http.StatusOK, order)
+	}
+
+	fileList := *order.FileList
+	for _, file := range fileList {
+		file.RefId = order.ID
+		refType := models.File_Ref_Type_Sale_Order
+		file.RefType = &refType
+
+		err := services.JdbcClient.JdbcInsert(&file, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+	}
+
+	tx.Commit()
+	return c.JSON(http.StatusOK, order)
+}
+
+func saleOrderRemove(c echo.Context) error {
+	tx, _ := services.MYSQL_DB.Beginx()
+
+	order := new(models.SaleOrder)
+	if err := c.Bind(order); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+
+	err := services.JdbcClient.JdbcRemoveById(order, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	minio_file := new(models.MinioFile)
+	minio_file.RefId = order.ID
+	refType := models.File_Ref_Type_Sale_Order
+	minio_file.RefType = &refType
+	err = services.JdbcClient.JdbcRemove(minio_file, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	order_detial := new(models.SaleOrderDetail)
+	order_detial.OrderId = order.ID
+	err = services.JdbcClient.JdbcRemove(order_detial, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	tx.Commit()
+	return c.JSON(http.StatusOK, order)
+}
+
+func saleOrder_generateCode(model *models.SaleOrder, tx *sqlx.Tx) string {
+	sync := new(models.Synchronized)
+	sync.Sync.Lock()
+	defer sync.Sync.Unlock()
+
+	if model.Code != nil && len(*model.Code) > 0 {
+		modelParam := new(models.SaleOrder)
+		modelParam.Code = model.Code
+		modelParam.TenantId = model.TenantId
+		count, err := services.JdbcClient.GetJdbcCount(modelParam, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return "SQL执行失败"
+		}
+		if count > 0 {
+			return "编号已存在,请重新填写"
+		}
+	} else {
+		for {
+			code, err := services.GetFlowNo(models.Flow_No_Type_Sale_Order, *model.TenantId, tx)
+			if err != nil {
+				return "自动生成编码失败"
+			}
+			modelParam := new(models.SaleOrder)
+			modelParam.Code = &code
+			modelParam.TenantId = model.TenantId
+			count, err := services.JdbcClient.GetJdbcCount(modelParam, tx)
+			if err != nil {
+				utils.PrintSqlErr(err)
+				return "SQL执行失败"
+			}
+			if count == 0 {
+				model.Code = &code
+				break
+			}
+		}
+
+	}
+	return ""
+}

+ 259 - 0
handlers/sale_performance.go

@@ -0,0 +1,259 @@
+package handlers
+
+import (
+	"fmt"
+	"net/http"
+	"time"
+
+	"easydo-echo_win7/models"
+	"easydo-echo_win7/services"
+	"easydo-echo_win7/utils"
+
+	"easydo-echo_win7/middleware"
+
+	"github.com/labstack/echo/v4"
+)
+
+func Add_sale_performance_to_routes(e *echo.Echo) {
+	group := e.Group("/salePerformance")
+	group.Use(middleware.AuthMiddleware)
+	group.POST("/getTotalPrice", salePerformanceGetTotalPrice)
+	group.POST("/getEcharts", salePerformanceGetEcharts)
+}
+
+func salePerformanceGetTotalPrice(c echo.Context) error {
+	var paramMap map[string]interface{}
+	if err := c.Bind(&paramMap); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	tenantId := paramMap["tenantId"]
+
+	now := time.Now()
+	begin_date_by_year := time.Date(now.Year(), 1, 1, 0, 0, 0, 0, now.Location())
+	end_date_by_year := time.Date(now.Year(), 12, 31, 0, 0, 0, 0, now.Location())
+	begin_date_by_year_str := begin_date_by_year.Format(time.DateOnly)
+	end_date_by_year_str := end_date_by_year.Format(time.DateOnly)
+	begin_date_by_month := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
+	end_date_by_month := time.Date(now.Year(), now.Month()+1, 1, 0, 0, 0, 0, now.Location()).AddDate(0, 0, -1)
+	begin_date_by_month_str := begin_date_by_month.Format(time.DateOnly)
+	end_date_by_month_str := end_date_by_month.Format(time.DateOnly)
+
+	sale_performance := new(models.SalePerformance)
+
+	sqlStr := fmt.Sprintf(`
+	select ifnull(sum(sale_amount),0) as sale_amount from sale_plan 
+	where begin_date = '%s' and end_date = '%s' and type = 'year' 
+	and tenant_id =  '%s'`,
+		begin_date_by_year_str, end_date_by_year_str, tenantId)
+	row := services.MYSQL_DB.QueryRowx(sqlStr)
+	sale_plan := new(models.SalePlan)
+	err := services.JdbcClient.ScanRowToModel(row, sale_plan)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	sale_performance.PlanSalePriceByYear = sale_plan.SaleAmount
+
+	sqlStr = fmt.Sprintf(`
+	select ifnull(sum(sale_amount),0) as sale_amount 
+	from sale_plan 
+	where begin_date = '%s' and end_date = '%s' 
+	and type = 'month' and tenant_id =  '%s'`,
+		begin_date_by_month_str, end_date_by_month_str, tenantId)
+	row = services.MYSQL_DB.QueryRowx(sqlStr)
+	sale_plan = new(models.SalePlan)
+	err = services.JdbcClient.ScanRowToModel(row, sale_plan)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	sale_performance.PlanSalePriceByMonth = sale_plan.SaleAmount
+
+	sqlStr = fmt.Sprintf(`
+	select ifnull(sum(actual_price),0) as actual_price 
+	from sale_order 
+	where order_date >= '%s' and order_date <= '%s'
+	and tenant_id =  '%s'`,
+		begin_date_by_year_str, end_date_by_year_str, tenantId)
+	row = services.MYSQL_DB.QueryRowx(sqlStr)
+	sale_order := new(models.SaleOrder)
+	err = services.JdbcClient.ScanRowToModel(row, sale_order)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	sale_performance.ActualSalePriceByYear = sale_order.ActualPrice
+
+	sqlStr = fmt.Sprintf(`
+	select ifnull(sum(actual_price),0) as actual_price 
+	from sale_order 
+	where order_date >= '%s' and order_date <= '%s'
+	and tenant_id =  '%s'`,
+		begin_date_by_month_str, end_date_by_month_str, tenantId)
+	row = services.MYSQL_DB.QueryRowx(sqlStr)
+	sale_order = new(models.SaleOrder)
+	err = services.JdbcClient.ScanRowToModel(row, sale_order)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	sale_performance.ActualSalePriceByMonth = sale_order.ActualPrice
+
+	return c.JSON(http.StatusOK, sale_performance)
+}
+
+func salePerformanceGetEcharts(c echo.Context) error {
+	var paramMap map[string]interface{}
+	if err := c.Bind(&paramMap); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	sale_performance := new(models.SalePerformance)
+
+	search_type := paramMap["type"]
+	begin_date := paramMap["beginDate"]
+	end_date := paramMap["endDate"]
+	tenantId := paramMap["tenantId"]
+	if search_type == "month" {
+		sqlStr := fmt.Sprintf(`
+		select left(begin_date,7) as datestr, ifnull(sum(sale_amount),0) as price 
+		from sale_plan where begin_date >= '%s' and end_date <= '%s' 
+		and tenant_id =  '%s'
+		and type = 'month' group by begin_date`,
+			begin_date, end_date, tenantId)
+		rows, err := services.MYSQL_DB.Queryx(sqlStr)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+		sale_price_by_day := new(models.SalePriceByDay)
+		result, err := services.JdbcClient.ScanRowToList(rows, sale_price_by_day)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+		plan_list := utils.ConvertInterface[[]models.SalePriceByDay](result)
+		sale_performance.PlanList = &plan_list
+
+		sqlStr = fmt.Sprintf(`
+		select left(order_date,7) as datestr, ifnull(sum(actual_price),0) as price 
+		from sale_order where order_date >= '%s' and order_date <= '%s' 
+		and tenant_id =  '%s'
+		group by order_date`,
+			begin_date, end_date, tenantId)
+		rows, err = services.MYSQL_DB.Queryx(sqlStr)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+		sale_price_by_day = new(models.SalePriceByDay)
+		result, err = services.JdbcClient.ScanRowToList(rows, sale_price_by_day)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+		actual_list := utils.ConvertInterface[[]models.SalePriceByDay](result)
+		sale_performance.ActualList = &actual_list
+	} else if search_type == "quarter" {
+		sqlStr := fmt.Sprintf(`
+		select CONCAT(YEAR(begin_date), '-Q', QUARTER(begin_date)) as datestr, ifnull(sum(sale_amount),0) as price 
+		from sale_plan where begin_date >= '%s' and end_date <= '%s' 
+		and tenant_id =  '%s'
+		and type = 'quarter' group by QUARTER(begin_date),YEAR(begin_date)`,
+			begin_date, end_date, tenantId)
+		rows, err := services.MYSQL_DB.Queryx(sqlStr)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+		sale_price_by_day := new(models.SalePriceByDay)
+		result, err := services.JdbcClient.ScanRowToList(rows, sale_price_by_day)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+		plan_list := utils.ConvertInterface[[]models.SalePriceByDay](result)
+		sale_performance.PlanList = &plan_list
+
+		sqlStr = fmt.Sprintf(`
+		select CONCAT(YEAR(order_date), '-Q', QUARTER(order_date)) as datestr, ifnull(sum(actual_price),0) as price 
+		from sale_order where order_date >= '%s' and order_date <= '%s' 
+		and tenant_id =  '%s'
+		group by QUARTER(order_date),YEAR(order_date)`,
+			begin_date, end_date, tenantId)
+		rows, err = services.MYSQL_DB.Queryx(sqlStr)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+		sale_price_by_day = new(models.SalePriceByDay)
+		result, err = services.JdbcClient.ScanRowToList(rows, sale_price_by_day)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+		actual_list := utils.ConvertInterface[[]models.SalePriceByDay](result)
+		sale_performance.ActualList = &actual_list
+	} else if search_type == "year" {
+		sqlStr := fmt.Sprintf(`
+		select YEAR(begin_date) as datestr, ifnull(sum(sale_amount),0) as price 
+		from sale_plan where begin_date >= '%s' and end_date <= '%s' 
+		and tenant_id =  '%s'
+		and type = 'year' group by YEAR(begin_date)`,
+			begin_date, end_date, tenantId)
+		rows, err := services.MYSQL_DB.Queryx(sqlStr)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+		sale_price_by_day := new(models.SalePriceByDay)
+		result, err := services.JdbcClient.ScanRowToList(rows, sale_price_by_day)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+		plan_list := utils.ConvertInterface[[]models.SalePriceByDay](result)
+		sale_performance.PlanList = &plan_list
+
+		sqlStr = fmt.Sprintf(`
+		select YEAR(order_date) as datestr, ifnull(sum(actual_price),0) as price 
+		from sale_order where order_date >= '%s' and order_date <= '%s' 
+		and tenant_id =  '%s'
+		group by YEAR(order_date)`,
+			begin_date, end_date, tenantId)
+		rows, err = services.MYSQL_DB.Queryx(sqlStr)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+		sale_price_by_day = new(models.SalePriceByDay)
+		result, err = services.JdbcClient.ScanRowToList(rows, sale_price_by_day)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+		actual_list := utils.ConvertInterface[[]models.SalePriceByDay](result)
+		sale_performance.ActualList = &actual_list
+	} else if search_type == "date" {
+		sqlStr := fmt.Sprintf(`
+		select order_date as date, ifnull(sum(actual_price),0) as price 
+		from sale_order where order_date >= '%s' and order_date <= '%s' 
+		and tenant_id =  '%s'
+		group by order_date`,
+			begin_date, end_date, tenantId)
+		rows, err := services.MYSQL_DB.Queryx(sqlStr)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+		sale_price_by_day := new(models.SalePriceByDay)
+		result, err := services.JdbcClient.ScanRowToList(rows, sale_price_by_day)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+		list := utils.ConvertInterface[[]models.SalePriceByDay](result)
+		sale_performance.ActualList = &list
+	}
+
+	return c.JSON(http.StatusOK, sale_performance)
+}

+ 221 - 0
handlers/sale_plan.go

@@ -0,0 +1,221 @@
+package handlers
+
+import (
+	"fmt"
+	"net/http"
+
+	"easydo-echo_win7/models"
+	"easydo-echo_win7/services"
+	"easydo-echo_win7/utils"
+
+	"easydo-echo_win7/middleware"
+
+	"github.com/jmoiron/sqlx"
+	"github.com/labstack/echo-contrib/session"
+	"github.com/labstack/echo/v4"
+)
+
+func Add_sale_plan_to_routes(e *echo.Echo) {
+	group := e.Group("/salePlan")
+	group.Use(middleware.AuthMiddleware)
+	group.POST("/getPage", salePlanGetPage)
+	group.POST("/getList", salePlanGetList)
+	group.POST("/save", salePlanSave)
+	group.POST("/update", salePlanUpdate)
+	group.POST("/remove", salePlanRemove)
+}
+
+func salePlanGetPage(c echo.Context) error {
+	var paramMap map[string]interface{}
+	if err := c.Bind(&paramMap); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	result, err := services.JdbcClient.GetJdbcPage(paramMap, models.SalePlan{})
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	list := utils.ConvertInterface[[]models.SalePlan](result.Records)
+	if len(list) == 0 {
+		list = []models.SalePlan{}
+	}
+	for i := range list {
+		model := list[i]
+
+		sqlStr := fmt.Sprintf("SELECT sum(actual_price) as actual_price FROM sale_order where order_date > '%s' and order_date < '%s' and status = '%s'",
+			*model.BeginDate, *model.EndDate, models.Sale_Order_Status_Complete)
+		row := services.MYSQL_DB.QueryRowx(sqlStr)
+		sale_order := new(models.SaleOrder)
+		err = services.JdbcClient.ScanRowToModel(row, sale_order)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+		if sale_order.ActualPrice == nil {
+			totalActualPrice := models.Common_Value_Zere_Float
+			model.TotalActualPrice = &totalActualPrice
+		} else {
+			model.TotalActualPrice = sale_order.ActualPrice
+		}
+
+		list[i] = model
+	}
+
+	result.Records = list
+	return c.JSON(http.StatusOK, result)
+
+}
+
+func salePlanGetList(c echo.Context) error {
+	var paramMap map[string]interface{}
+	if err := c.Bind(&paramMap); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	result, err := services.JdbcClient.GetJdbcList(paramMap, models.SalePlan{})
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	list := utils.ConvertInterface[[]models.SalePlan](result)
+	if len(list) == 0 {
+		return c.JSON(http.StatusOK, []models.SalePlan{})
+	}
+	for i := range list {
+		model := list[i]
+
+		sqlStr := fmt.Sprintf("SELECT sum(actual_price) as actual_price FROM sale_order where order_date > '%s' and order_date < '%s' and status = '%s'",
+			*model.BeginDate, *model.EndDate, models.Sale_Order_Status_Complete)
+		row := services.MYSQL_DB.QueryRowx(sqlStr)
+		sale_order := new(models.SaleOrder)
+		err = services.JdbcClient.ScanRowToModel(row, sale_order)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+		if sale_order.ActualPrice == nil {
+			totalActualPrice := models.Common_Value_Zere_Float
+			model.TotalActualPrice = &totalActualPrice
+		} else {
+			model.TotalActualPrice = sale_order.ActualPrice
+		}
+
+		list[i] = model
+	}
+	return c.JSON(http.StatusOK, list)
+}
+
+func salePlanSave(c echo.Context) error {
+	tx, _ := services.MYSQL_DB.Beginx()
+
+	plan := new(models.SalePlan)
+	if err := c.Bind(plan); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	essit_plan := new(models.SalePlan)
+	essit_plan.BeginDate = plan.BeginDate
+	essit_plan.EndDate = plan.EndDate
+	essit_plan.Type = plan.Type
+	count, err := services.JdbcClient.GetJdbcCount(essit_plan, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	if count > 0 {
+		return c.JSON(http.StatusBadRequest, utils.ErrorResponse("该计划已存在,请勿重复添加", ""))
+	}
+	sess, _ := session.Get("auth_session", c)
+	err_msg := salePlan_generateCode(plan, tx)
+	if err_msg != "" {
+		tx.Rollback()
+		return c.JSON(http.StatusBadRequest, utils.ErrorResponse(err_msg, ""))
+	}
+	user_id, ok := sess.Values["user_id"].(int64)
+	if !ok {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("用户请先登录", ""))
+	}
+	plan.CreateId = &user_id
+	err = services.JdbcClient.JdbcInsert(plan, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	tx.Commit()
+	return c.JSON(http.StatusOK, plan)
+}
+
+func salePlanUpdate(c echo.Context) error {
+	sale_plan := new(models.SalePlan)
+	if err := c.Bind(sale_plan); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	sess, _ := session.Get("auth_session", c)
+	user_id, ok := sess.Values["user_id"].(int64)
+	if !ok {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("用户请先登录", ""))
+	}
+	sale_plan.UpdateId = &user_id
+	err := services.JdbcClient.JdbcUpdateById(sale_plan)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	return c.JSON(http.StatusOK, sale_plan)
+}
+
+func salePlanRemove(c echo.Context) error {
+
+	customer := new(models.SalePlan)
+	if err := c.Bind(customer); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+
+	err := services.JdbcClient.JdbcRemoveById(customer)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	return c.JSON(http.StatusOK, customer)
+}
+
+func salePlan_generateCode(model *models.SalePlan, tx *sqlx.Tx) string {
+	sync := new(models.Synchronized)
+	sync.Sync.Lock()
+	defer sync.Sync.Unlock()
+
+	if model.Code != nil && len(*model.Code) > 0 {
+		modelParam := new(models.SalePlan)
+		modelParam.Code = model.Code
+		modelParam.TenantId = model.TenantId
+		count, err := services.JdbcClient.GetJdbcCount(modelParam, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return "SQL执行失败"
+		}
+		if count > 0 {
+			return "编号已存在,请重新填写"
+		}
+	} else {
+		for {
+			code, err := services.GetFlowNo(models.Flow_No_Type_Sale_Plan, *model.TenantId, tx)
+			if err != nil {
+				return "自动生成编码失败"
+			}
+			modelParam := new(models.SalePlan)
+			modelParam.Code = &code
+			modelParam.TenantId = model.TenantId
+			count, err := services.JdbcClient.GetJdbcCount(modelParam, tx)
+			if err != nil {
+				utils.PrintSqlErr(err)
+				return "SQL执行失败"
+			}
+			if count == 0 {
+				model.Code = &code
+				break
+			}
+		}
+
+	}
+	return ""
+}

+ 161 - 0
handlers/tenant.go

@@ -0,0 +1,161 @@
+package handlers
+
+import (
+	"net/http"
+
+	"easydo-echo_win7/models"
+	"easydo-echo_win7/services"
+	"easydo-echo_win7/utils"
+
+	// "github.com/labstack/echo-contrib/session"
+	"easydo-echo_win7/middleware"
+
+	"github.com/labstack/echo/v4"
+)
+
+func Add_tenant_to_routes(e *echo.Echo) {
+	group := e.Group("/tenant")
+	group.Use(middleware.AuthMiddleware)
+	group.POST("/getPage", tenantGetPage)
+	group.POST("/getList", tenantGetList)
+	group.POST("/save", tenantSave)
+	group.POST("/update", tenantUpdate)
+	group.POST("/remove", tenantRemove)
+}
+
+func tenantGetPage(c echo.Context) error {
+	var paramMap map[string]interface{}
+	if err := c.Bind(&paramMap); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	result, err := services.JdbcClient.GetJdbcPage(paramMap, models.Tenant{})
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	list := utils.ConvertInterface[[]models.Tenant](result.Records)
+	if len(list) == 0 {
+		list = []models.Tenant{}
+	}
+	result.Records = list
+	return c.JSON(http.StatusOK, result)
+
+}
+
+func tenantGetList(c echo.Context) error {
+	var paramMap map[string]interface{}
+	if err := c.Bind(&paramMap); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	result, err := services.JdbcClient.GetJdbcList(paramMap, models.Tenant{})
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	list := utils.ConvertInterface[[]models.Tenant](result)
+	if len(list) == 0 {
+		return c.JSON(http.StatusOK, []models.Tenant{})
+	}
+	return c.JSON(http.StatusOK, list)
+}
+
+func tenantSave(c echo.Context) error {
+	tx, _ := services.MYSQL_DB.Beginx()
+
+	tenant := new(models.Tenant)
+	if err := c.Bind(tenant); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	status := models.Status_Enable
+	tenant.Status = &status
+	err := services.JdbcClient.JdbcInsert(tenant, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	dept := new(models.SysDept)
+	dept.Name = tenant.Name
+	pid := models.Common_Value_One_Int64
+	dept.Pid = &pid
+	dept.FirmFunctionary = tenant.ManagerName
+	dept.FirmFunctionaryPhone = tenant.ManagerPhone
+	deptSort := models.Common_Value_One_Int32
+	dept.DeptSort = &deptSort
+	dept.TenantId = tenant.ID
+	err = services.JdbcClient.JdbcInsert(dept, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	tx.Commit()
+	return c.JSON(http.StatusOK, tenant)
+}
+
+func tenantUpdate(c echo.Context) error {
+	tx, _ := services.MYSQL_DB.Beginx()
+
+	tenant := new(models.Tenant)
+	if err := c.Bind(tenant); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	err := services.JdbcClient.JdbcUpdateById(tenant, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	dept := new(models.SysDept)
+	pid := models.Common_Value_One_Int64
+	dept.Pid = &pid
+	dept.TenantId = tenant.ID
+	err = services.JdbcClient.GetJdbcModel(dept, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	dept.Name = tenant.Name
+	dept.FirmFunctionary = tenant.ManagerName
+	dept.FirmFunctionaryPhone = tenant.ManagerPhone
+	err = services.JdbcClient.JdbcUpdateById(dept, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	tx.Commit()
+	return c.JSON(http.StatusOK, tenant)
+}
+
+func tenantRemove(c echo.Context) error {
+	tx, _ := services.MYSQL_DB.Beginx()
+
+	tenant := new(models.Tenant)
+	if err := c.Bind(tenant); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+
+	err := services.JdbcClient.JdbcRemoveById(tenant, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	// dept := new(models.SysDept)
+	// pid := models.Common_Value_One_Int64
+	// dept.Pid = &pid
+	// dept.TenantId = tenant.ID
+	// err = services.JdbcClient.JdbcRemove(dept,tx)
+	// if err != nil{
+	// 	utils.PrintSqlErr(err)
+	// 	tx.Rollback()
+	// 	return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	// }
+
+	tx.Commit()
+	return c.JSON(http.StatusOK, tenant)
+}

+ 325 - 0
handlers/user.go

@@ -0,0 +1,325 @@
+package handlers
+
+import (
+	"net/http"
+
+	"easydo-echo_win7/models"
+	"easydo-echo_win7/services"
+	"easydo-echo_win7/utils"
+
+	"easydo-echo_win7/middleware"
+
+	"github.com/labstack/echo-contrib/session"
+	"github.com/labstack/echo/v4"
+)
+
+func Add_user_to_routes(e *echo.Echo) {
+	group := e.Group("/sysUser")
+	group.Use(middleware.AuthMiddleware)
+	group.POST("/getPage", userGetPage)
+	group.POST("/getList", userGetList)
+	group.POST("/save", userSave)
+	group.POST("/update", userUpdate)
+	group.POST("/remove", userRemove)
+	group.POST("/updatePass", userUpdatePass)
+	group.POST("/resetPass", userResetPass)
+}
+
+func userGetPage(c echo.Context) error {
+	var paramMap map[string]interface{}
+	if err := c.Bind(&paramMap); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	result, err := services.JdbcClient.GetJdbcPage(paramMap, models.SysUser{})
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	list := utils.ConvertInterface[[]models.SysUser](result.Records)
+	if len(list) == 0 {
+		list = []models.SysUser{}
+	}
+
+	for i := range list {
+		user := list[i]
+
+		dept := new(models.SysDept)
+		dept.ID = user.DeptId
+		err := services.JdbcClient.GetJdbcModelById(dept)
+		if err != nil {
+			continue
+		}
+
+		user.Dept = dept
+
+		for k := range paramMap {
+			delete(paramMap, k)
+		}
+		paramMap["userId"] = user.ID
+		p_result, err := services.JdbcClient.GetJdbcList(paramMap, models.SysUsersRoles{})
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+		p_list := utils.ConvertInterface[[]models.SysUsersRoles](p_result)
+		role_id_list := utils.Map(p_list, func(user_role models.SysUsersRoles) int64 {
+			return *user_role.RoleID
+		})
+
+		for k := range paramMap {
+			delete(paramMap, k)
+		}
+		paramMap["idIn"] = role_id_list
+		r_result, err := services.JdbcClient.GetJdbcList(paramMap, models.SysRole{})
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+		role_list := utils.ConvertInterface[[]models.SysRole](r_result)
+		user.RoleList = &role_list
+
+		tenant := new(models.Tenant)
+		tenant.ID = user.TenantId
+		err = services.JdbcClient.GetJdbcModelById(tenant)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+		user.Tenant = tenant
+
+		list[i] = user
+	}
+	result.Records = list
+	return c.JSON(http.StatusOK, result)
+}
+
+func userGetList(c echo.Context) error {
+	var paramMap map[string]interface{}
+	if err := c.Bind(&paramMap); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	result, err := services.JdbcClient.GetJdbcList(paramMap, models.SysUser{})
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	list := utils.ConvertInterface[[]models.SysUser](result)
+
+	for i := range list {
+		user := list[i]
+
+		dept := new(models.SysDept)
+		dept.ID = user.DeptId
+		err := services.JdbcClient.GetJdbcModel(dept)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+
+		user.Dept = dept
+
+		for k := range paramMap {
+			delete(paramMap, k)
+		}
+		paramMap["userId"] = user.ID
+		p_result, err := services.JdbcClient.GetJdbcList(paramMap, models.SysUsersRoles{})
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+		p_list := utils.ConvertInterface[[]models.SysUsersRoles](p_result)
+		role_id_list := utils.Map(p_list, func(user_role models.SysUsersRoles) int64 {
+			return *user_role.RoleID
+		})
+
+		for k := range paramMap {
+			delete(paramMap, k)
+		}
+		paramMap["idIn"] = role_id_list
+		r_result, err := services.JdbcClient.GetJdbcList(paramMap, models.SysRole{})
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+		role_list := utils.ConvertInterface[[]models.SysRole](r_result)
+		user.RoleList = &role_list
+
+		tenant := new(models.Tenant)
+		tenant.ID = user.TenantId
+		err = services.JdbcClient.GetJdbcModelById(tenant)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+		user.Tenant = tenant
+
+		list[i] = user
+	}
+	return c.JSON(http.StatusOK, list)
+}
+
+func userSave(c echo.Context) error {
+	tx, _ := services.MYSQL_DB.Beginx()
+
+	user := new(models.SysUser)
+	if err := c.Bind(user); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	userParam := new(models.SysUser)
+	userParam.Username = user.Username
+	count, err := services.JdbcClient.GetJdbcCount(userParam, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	if count > 0 {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("用户名已存在", ""))
+	}
+	password, _ := utils.EncodePassword("123456")
+	user.Password = &password
+
+	err = services.JdbcClient.JdbcInsert(user, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	role_list := *user.RoleList
+	for _, role := range role_list {
+		user_role := new(models.SysUsersRoles)
+		user_role.RoleID = role.ID
+		user_role.UserID = user.ID
+		err = services.JdbcClient.JdbcInsert(user_role, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+	}
+	tx.Commit()
+	return c.JSON(http.StatusOK, user)
+}
+
+func userUpdate(c echo.Context) error {
+	tx, _ := services.MYSQL_DB.Beginx()
+
+	user := new(models.SysUser)
+	if err := c.Bind(user); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	err := services.JdbcClient.JdbcUpdateById(user, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	user_role := new(models.SysUsersRoles)
+	user_role.UserID = user.ID
+
+	err = services.JdbcClient.JdbcRemove(user_role, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	role_list := *user.RoleList
+	for _, role := range role_list {
+		user_role := new(models.SysUsersRoles)
+		user_role.RoleID = role.ID
+		user_role.UserID = user.ID
+		err = services.JdbcClient.JdbcInsert(user_role, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			tx.Rollback()
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+	}
+	tx.Commit()
+	return c.JSON(http.StatusOK, user)
+}
+
+func userRemove(c echo.Context) error {
+	tx, _ := services.MYSQL_DB.Beginx()
+
+	user := new(models.SysUser)
+	if err := c.Bind(user); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	err := services.JdbcClient.JdbcRemoveById(user, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	user_role := new(models.SysUsersRoles)
+	user_role.UserID = user.ID
+
+	err = services.JdbcClient.JdbcRemove(user_role, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	tx.Commit()
+	return c.JSON(http.StatusOK, user)
+}
+
+func userUpdatePass(c echo.Context) error {
+	sess, _ := session.Get("auth_session", c)
+	if user_id, ok := sess.Values["user_id"].(int64); ok {
+		var paramMap map[string]interface{}
+		if err := c.Bind(&paramMap); err != nil {
+			return c.JSON(http.StatusOK, [0]string{})
+		}
+		oldPass := paramMap["oldPass"]
+		newPass := paramMap["newPass"]
+
+		user := new(models.SysUser)
+		user.ID = &user_id
+		services.JdbcClient.GetJdbcModelById(user)
+
+		oldPassStr := utils.FormatToString(oldPass)
+		newPassStr := utils.FormatToString(newPass)
+		oldPassStr, _ = utils.DecryptByPrivateKey(oldPassStr)
+		newPassStr, _ = utils.DecryptByPrivateKey(newPassStr)
+
+		result := utils.VerifyPassword(oldPassStr, *user.Password)
+		if !result {
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("修改失败,旧密码错误", ""))
+		}
+		result = utils.VerifyPassword(newPassStr, *user.Password)
+		if result {
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("新密码不能与旧密码相同", ""))
+		}
+
+		password, _ := utils.EncodePassword(newPassStr)
+		user.Password = &password
+		err := services.JdbcClient.JdbcUpdateById(user)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+		}
+		return c.JSON(http.StatusOK, user)
+	}
+	return c.JSON(http.StatusOK, nil)
+}
+
+func userResetPass(c echo.Context) error {
+	user := new(models.SysUser)
+	if err := c.Bind(user); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	password, _ := utils.EncodePassword("123456")
+	user.Password = &password
+	err := services.JdbcClient.JdbcUpdateById(user)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	return c.JSON(http.StatusOK, user)
+}

+ 155 - 0
handlers/warehouse.go

@@ -0,0 +1,155 @@
+package handlers
+
+import (
+	"net/http"
+
+	"easydo-echo_win7/models"
+	"easydo-echo_win7/services"
+	"easydo-echo_win7/utils"
+
+	"github.com/jmoiron/sqlx"
+	// "github.com/labstack/echo-contrib/session"
+	"easydo-echo_win7/middleware"
+
+	"github.com/labstack/echo/v4"
+)
+
+func Add_warehouse_to_routes(e *echo.Echo) {
+	group := e.Group("/warehouse")
+	group.Use(middleware.AuthMiddleware)
+	group.POST("/getPage", warehouseGetPage)
+	group.POST("/getList", warehouseGetList)
+	group.POST("/save", warehouseSave)
+	group.POST("/update", warehouseUpdate)
+	group.POST("/remove", warehouseRemove)
+}
+
+func warehouseGetPage(c echo.Context) error {
+	var paramMap map[string]interface{}
+	if err := c.Bind(&paramMap); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	result, err := services.JdbcClient.GetJdbcPage(paramMap, models.Warehouse{})
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	list := utils.ConvertInterface[[]models.Warehouse](result.Records)
+	if len(list) == 0 {
+		list = []models.Warehouse{}
+	}
+	result.Records = list
+	return c.JSON(http.StatusOK, result)
+
+}
+
+func warehouseGetList(c echo.Context) error {
+	var paramMap map[string]interface{}
+	if err := c.Bind(&paramMap); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	result, err := services.JdbcClient.GetJdbcList(paramMap, models.Warehouse{})
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	list := utils.ConvertInterface[[]models.Warehouse](result)
+	if len(list) == 0 {
+		return c.JSON(http.StatusOK, []models.Warehouse{})
+	}
+	return c.JSON(http.StatusOK, list)
+}
+
+func warehouseSave(c echo.Context) error {
+	tx, _ := services.MYSQL_DB.Beginx()
+
+	warehouse := new(models.Warehouse)
+	if err := c.Bind(warehouse); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	err_msg := warehouse_generateCode(warehouse, tx)
+	if err_msg != "" {
+		tx.Rollback()
+		return c.JSON(http.StatusBadRequest, utils.ErrorResponse(err_msg, ""))
+	}
+	err := services.JdbcClient.JdbcInsert(warehouse, tx)
+	if err != nil {
+		tx.Rollback()
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	tx.Commit()
+	return c.JSON(http.StatusOK, warehouse)
+}
+
+func warehouseUpdate(c echo.Context) error {
+	warehouse := new(models.Warehouse)
+	if err := c.Bind(warehouse); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	err := services.JdbcClient.JdbcUpdateById(warehouse)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	return c.JSON(http.StatusOK, warehouse)
+}
+
+func warehouseRemove(c echo.Context) error {
+	tx, _ := services.MYSQL_DB.Beginx()
+
+	warehouse := new(models.Warehouse)
+	if err := c.Bind(warehouse); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+
+	err := services.JdbcClient.JdbcRemoveById(warehouse, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	tx.Commit()
+	return c.JSON(http.StatusOK, warehouse)
+}
+
+func warehouse_generateCode(model *models.Warehouse, tx *sqlx.Tx) string {
+	sync := new(models.Synchronized)
+	sync.Sync.Lock()
+	defer sync.Sync.Unlock()
+
+	if model.Code != nil && len(*model.Code) > 0 {
+		modelParam := new(models.Warehouse)
+		modelParam.Code = model.Code
+		modelParam.TenantId = model.TenantId
+		count, err := services.JdbcClient.GetJdbcCount(modelParam, tx)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return "SQL执行失败"
+		}
+		if count > 0 {
+			return "编号已存在,请重新填写"
+		}
+	} else {
+		for {
+			code, err := services.GetFlowNo(models.Flow_No_Type_Warehouse, *model.TenantId, tx)
+			if err != nil {
+				return "自动生成编码失败"
+			}
+			modelParam := new(models.Warehouse)
+			modelParam.Code = &code
+			modelParam.TenantId = model.TenantId
+			count, err := services.JdbcClient.GetJdbcCount(modelParam, tx)
+			if err != nil {
+				utils.PrintSqlErr(err)
+				return "SQL执行失败"
+			}
+			if count == 0 {
+				model.Code = &code
+				break
+			}
+		}
+
+	}
+	return ""
+}

+ 213 - 0
handlers/warehouse_material.go

@@ -0,0 +1,213 @@
+package handlers
+
+import (
+	"net/http"
+
+	"easydo-echo_win7/models"
+	"easydo-echo_win7/services"
+	"easydo-echo_win7/utils"
+
+	"easydo-echo_win7/middleware"
+
+	"github.com/labstack/echo-contrib/session"
+	"github.com/labstack/echo/v4"
+)
+
+func Add_warehouse_material_to_routes(e *echo.Echo) {
+	group := e.Group("/warehouseMaterial")
+	group.Use(middleware.AuthMiddleware)
+	group.POST("/getPage", warehouseMaterialGetPage)
+	group.POST("/getList", warehouseMaterialGetList)
+	group.POST("/save", warehouseMaterialSave)
+	group.POST("/update", warehouseMaterialUpdate)
+	// group.POST("/remove", warehouseMaterialRemove)
+
+}
+
+func warehouseMaterialGetPage(c echo.Context) error {
+	var paramMap map[string]interface{}
+	if err := c.Bind(&paramMap); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	result, err := services.JdbcClient.GetJdbcPage(paramMap, models.WarehouseMaterial{})
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+	}
+	list := utils.ConvertInterface[[]models.WarehouseMaterial](result.Records)
+	if len(list) == 0 {
+		list = []models.WarehouseMaterial{}
+	}
+	for i := range list {
+		model := list[i]
+
+		material := new(models.ProductMaterial)
+		material.Code = model.MaterialCode
+		err = services.JdbcClient.GetJdbcModel(material)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+		}
+		model.ProductMaterial = material
+
+		warehouse := new(models.Warehouse)
+		warehouse.ID = model.WarehouseId
+		err = services.JdbcClient.GetJdbcModelById(warehouse)
+		if err != nil {
+			utils.PrintSqlErr(err)
+			return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+		}
+		model.Warehouse = warehouse
+
+		list[i] = model
+	}
+	result.Records = list
+	return c.JSON(http.StatusOK, result)
+
+}
+
+func warehouseMaterialGetList(c echo.Context) error {
+	var paramMap map[string]interface{}
+	if err := c.Bind(&paramMap); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	result, err := services.JdbcClient.GetJdbcList(paramMap, models.WarehouseMaterial{})
+	if err != nil {
+		utils.PrintSqlErr(err)
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+	}
+	list := utils.ConvertInterface[[]models.WarehouseMaterial](result)
+	if len(list) == 0 {
+		return c.JSON(http.StatusOK, []string{})
+	}
+	return c.JSON(http.StatusOK, list)
+}
+
+func warehouseMaterialSave(c echo.Context) error {
+	tx, _ := services.MYSQL_DB.Beginx()
+
+	sess, _ := session.Get("auth_session", c)
+	user_id, ok := sess.Values["user_id"].(int64)
+	if !ok {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("用户请先登录", ""))
+	}
+	warehouse_material := new(models.WarehouseMaterial)
+	if err := c.Bind(warehouse_material); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+
+	status := models.Status_Enable
+	warehouse_material.Status = &status
+	warehouse_material.CreateId = &user_id
+	services.JdbcClient.JdbcInsert(warehouse_material, tx)
+
+	warehouse_record := new(models.WarehouseRecord)
+	warehouse_record.MaterialCode = warehouse_material.MaterialCode
+	record_type := models.Warehouse_Record_Type_In
+	warehouse_record.Type = &record_type
+	warehouse_record.CreateId = &user_id
+	warehouse_record.Number = warehouse_material.Number
+	warehouse_record.TenantId = warehouse_material.TenantId
+	warehouse_record.ToWarehouseId = warehouse_material.WarehouseId
+	err := services.JdbcClient.JdbcInsert(warehouse_record, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+	}
+	tx.Commit()
+	return c.JSON(http.StatusOK, warehouse_material)
+}
+
+func warehouseMaterialUpdate(c echo.Context) error {
+	tx, _ := services.MYSQL_DB.Beginx()
+
+	warehouse_material := new(models.WarehouseMaterial)
+	if err := c.Bind(warehouse_material); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	exist_warehouse_material := new(models.WarehouseMaterial)
+	exist_warehouse_material.ID = warehouse_material.ID
+	err := services.JdbcClient.GetJdbcModelById(exist_warehouse_material, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+	sess, _ := session.Get("auth_session", c)
+	user_id, ok := sess.Values["user_id"].(int64)
+	if !ok {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("用户请先登录", ""))
+	}
+	warehouse_material.UpdateId = &user_id
+	err = services.JdbcClient.JdbcUpdateById(warehouse_material, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	record := new(models.WarehouseRecord)
+	record.MaterialCode = warehouse_material.MaterialCode
+	record_type := ""
+	if *exist_warehouse_material.LockedNumber < *warehouse_material.LockedNumber {
+		record_type = models.Warehouse_Record_Type_Lock
+	} else if *exist_warehouse_material.Number < *warehouse_material.Number {
+		record_type = models.Warehouse_Record_Type_In
+	} else if *exist_warehouse_material.Number > *warehouse_material.Number {
+		record_type = models.Warehouse_Record_Type_Out
+	} else {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("记录类型出现异常", ""))
+	}
+
+	record.Type = &record_type
+	record.CreateId = &user_id
+	record.Number = warehouse_material.Number
+	record.TenantId = warehouse_material.TenantId
+	record.ToWarehouseId = warehouse_material.WarehouseId
+	err = services.JdbcClient.JdbcInsert(record, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+	}
+	tx.Commit()
+	return c.JSON(http.StatusOK, warehouse_material)
+}
+
+func warehouseMaterialRemove(c echo.Context) error {
+	tx, _ := services.MYSQL_DB.Beginx()
+
+	warehouse_material := new(models.WarehouseMaterial)
+	if err := c.Bind(warehouse_material); err != nil {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("参数解析失败", err.Error()))
+	}
+	sess, _ := session.Get("auth_session", c)
+	user_id, ok := sess.Values["user_id"].(int64)
+	if !ok {
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("用户请先登录", ""))
+	}
+	err := services.JdbcClient.JdbcRemoveById(warehouse_material, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", ""))
+	}
+
+	record := new(models.WarehouseRecord)
+	record.MaterialCode = warehouse_material.MaterialCode
+	record_type := models.Warehouse_Record_Type_Out
+	record.Type = &record_type
+	record.CreateId = &user_id
+	record.Number = warehouse_material.Number
+	record.TenantId = warehouse_material.TenantId
+	record.ToWarehouseId = warehouse_material.WarehouseId
+	err = services.JdbcClient.JdbcInsert(record, tx)
+	if err != nil {
+		utils.PrintSqlErr(err)
+		tx.Rollback()
+		return c.JSON(http.StatusInternalServerError, utils.ErrorResponse("系统错误", err.Error()))
+	}
+	tx.Commit()
+	return c.JSON(http.StatusOK, warehouse_material)
+}

BIN
mes_win7.exe


+ 53 - 0
middleware/auth.go

@@ -0,0 +1,53 @@
+package middleware
+
+import (
+	"net/http"
+	"time"
+	
+	"easydo-echo_win7/utils"
+	
+	"github.com/labstack/echo-contrib/session"
+	"github.com/labstack/echo/v4"
+)
+
+// AuthMiddleware 认证中间件
+func AuthMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
+	return func(c echo.Context) error {
+		sess, _ := session.Get("auth_session", c)
+		
+		// 检查是否已认证
+		if auth, ok := sess.Values["is_authenticated"].(bool); !ok || !auth {
+			return c.JSON(http.StatusUnauthorized, utils.ErrorResponse("请先登录", ""))
+		}
+		
+		// 检查会话是否过期(超过7天)
+		if loginTime, ok := sess.Values["login_time"].(int64); ok {
+			if time.Now().Unix()-loginTime > 7*24*60*60 {
+				// 清除过期会话
+				sess.Options.MaxAge = -1
+				sess.Save(c.Request(), c.Response())
+				
+				return c.JSON(http.StatusUnauthorized, utils.ErrorResponse("会话已过期,请重新登录", ""))
+			}
+		}
+		
+		// 将用户信息存入上下文
+		c.Set("user_id", sess.Values["user_id"])
+		c.Set("username", sess.Values["username"])
+		
+		return next(c)
+	}
+}
+
+// AdminMiddleware 管理员中间件
+func AdminMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
+	return func(c echo.Context) error {
+		role := c.Get("role").(string)
+		
+		if role != "admin" {
+			return c.JSON(http.StatusForbidden, utils.ErrorResponse("权限不足", ""))
+		}
+		
+		return next(c)
+	}
+}

+ 107 - 0
models/basic_data.go

@@ -0,0 +1,107 @@
+package models
+
+type QualityInspectProgram struct {
+	ID            *string `json:"id" db:"id" id_type:"UUID"`
+	Name          *string `json:"name" db:"name"`
+	Code          *string `json:"code" db:"code"`
+	InspectUserId *int64  `json:"inspectUserId" db:"inspect_user_id"`
+	ReviewUserId  *int64  `json:"reviewUserId" db:"review_user_id"`
+	Type          *string `json:"type" db:"type"`
+	ReviewStatus  *string `json:"reviewStatus" db:"review_status"`
+	ReviewTime    *string `json:"reviewTime" db:"review_time"`
+	ReviewReason  *string `json:"reviewReason" db:"review_reason"`
+	Remark        *string `json:"remark" db:"remark"`
+	CreateTime    *string `json:"createTime" db:"create_time"`
+	SampleRate    *int32  `json:"sampleRate" db:"sample_rate"`
+	PassedRate    *int32  `json:"passedRate" db:"passed_rate"`
+	TenantId      *string `json:"tenantId" db:"tenant_id"`
+	CreateId      *int64  `json:"createId" db:"create_id"`
+	UpdateId      *int64  `json:"updateId" db:"update_id"`
+	UpdateTime    *string `json:"updateTime" db:"update_time"`
+
+	InspectUserName *string      `json:"inspectUserName" db:"-"`
+	ReviewUserName  *string      `json:"reviewUserName" db:"-"`
+	EmptyField      *[]string    `json:"emptyField" db:"-"`
+	FileList        *[]MinioFile `json:"fileList" db:"-"`
+}
+
+type ProductMaterial struct {
+	ID            *string  `json:"id" db:"id" id_type:"UUID"`
+	Name          *string  `json:"name" db:"name"`
+	Code          *string  `json:"code" db:"code"`
+	Unit          *string  `json:"unit" db:"unit"`
+	Specification *string  `json:"specification" db:"specification"`
+	NeedType      *string  `json:"needType" db:"need_type"`
+	MaterialType  *string  `json:"materialType" db:"material_type"`
+	Status        *string  `json:"status" db:"status"`
+	WarehouseId   *string  `json:"warehouseId" db:"warehouse_id"`
+	Price         *float64 `json:"price" db:"price"`
+	Remark        *string  `json:"remark" db:"remark"`
+	CreateId      *int64   `json:"createId" db:"create_id"`
+	CreateTime    *string  `json:"createTime" db:"create_time"`
+	TenantId      *string  `json:"tenantId" db:"tenant_id"`
+	UpdateId      *int64   `json:"updateId" db:"update_id"`
+	UpdateTime    *string  `json:"updateTime" db:"update_time"`
+
+	EmptyField *[]string `json:"emptyField" db:"-"`
+}
+
+type ProductBom struct {
+	ID           *string  `json:"id" db:"id" id_type:"UUID"`
+	MaterialCode *string  `json:"materialCode" db:"material_code"`
+	MaterialName *string  `json:"materialName" db:"material_name"`
+	CreateId     *int64   `json:"createId" db:"create_id"`
+	CreateTime   *string  `json:"createTime" db:"create_time"`
+	Status       *string  `json:"status" db:"status"`
+	Remark       *string  `json:"remark" db:"remark"`
+	BomCode      *string  `json:"bomCode" db:"bom_code"`
+	ParentId     *string  `json:"parentId" db:"parent_id"`
+	Quantity     *float64 `json:"quantity" db:"quantity"`
+	TenantId     *string  `json:"tenantId" db:"tenant_id"`
+	UpdateId     *int64   `json:"updateId" db:"update_id"`
+	UpdateTime   *string  `json:"updateTime" db:"update_time"`
+	RouteId      *string  `json:"routeId" db:"route_id"`
+	RouteName    *string  `json:"routeName" db:"-"`
+
+	EmptyField            *[]string            `json:"emptyField" db:"-"`
+	Material              *ProductMaterial     `json:"material" db:"-"`
+	ChildrenList          *[]ProductBom        `json:"childrenList" db:"-"`
+	IsHaveChildren        *bool                `json:"isHaveChildren" db:"-"`
+	WarehouseMaterialList *[]WarehouseMaterial `json:"warehouseMaterialList" db:"-"`
+}
+
+type Customer struct {
+	ID           *string `json:"id" db:"id" id_type:"UUID"`
+	Name         *string `json:"name" db:"name"`
+	Code         *string `json:"code" db:"code"`
+	CreditNo     *string `json:"creditNo" db:"credit_no"`
+	Address      *string `json:"address" db:"address"`
+	Type         *string `json:"type" db:"type"`
+	CustomerType *string `json:"customerType" db:"customer_type"`
+	Phone        *string `json:"phone" db:"phone"`
+	CreateTime   *string `json:"createTime" db:"create_time"`
+	ManagerName  *string `json:"managerName" db:"manager_name"`
+	ManagerPhone *string `json:"managerPhone" db:"manager_phone"`
+	Status       *string `json:"status" db:"status"`
+	Email        *string `json:"email" db:"email"`
+	CreditLevel  *string `json:"creditLevel" db:"credit_level"`
+	ValueLevel   *string `json:"valueLevel" db:"value_level"`
+	TenantId     *string `json:"tenantId" db:"tenant_id"`
+	CreateId     *int64  `json:"createId" db:"create_id"`
+	UpdateId     *int64  `json:"updateId" db:"update_id"`
+	UpdateTime   *string `json:"updateTime" db:"update_time"`
+
+	EmptyField *[]string    `json:"emptyField" db:"-"`
+	FileList   *[]MinioFile `json:"fileList" db:"-"`
+}
+
+type Device struct {
+	ID         *string `json:"id" db:"id" id_type:"UUID"`
+	Name       *string `json:"name" db:"name"`
+	Code       *string `json:"code" db:"code"`
+	Remark     *string `json:"remark" db:"remark"`
+	CreateTime *string `json:"createTime" db:"create_time"`
+	TenantId   *string `json:"tenantId" db:"tenant_id"`
+
+	EmptyField *[]string `json:"emptyField" db:"-"`
+}

+ 92 - 0
models/enum.go

@@ -0,0 +1,92 @@
+package models
+
+import (
+	"sync"
+)
+
+type Synchronized struct {
+	Sync sync.Mutex // 互斥锁,相当于 synchronized
+}
+
+const (
+	Flow_No_Type_Process_Stage           string = "process_stage"
+	Flow_No_Type_Process_Route           string = "process_route"
+	Flow_No_Type_Quality_Inspect_Program string = "quality_inspect_program"
+	Flow_No_Type_Product_Material        string = "product_material"
+	Flow_No_Type_Product_Bom             string = "product_bom"
+	Flow_No_Type_Customer                string = "customer"
+	Flow_No_Type_Sale_Plan               string = "sale_plan"
+	Flow_No_Type_Sale_Order              string = "sale_order"
+	Flow_No_Type_Purchase_Plan           string = "purchase_plan"
+	Flow_No_Type_Purchase_Order          string = "purchase_order"
+	Flow_No_Type_Warehouse               string = "warehouse"
+	Flow_No_Type_Product_Plan            string = "product_plan"
+	Flow_No_Type_Outsourcing_Plan        string = "outsourcing_plan"
+
+	Common_Value_True_Value  bool    = true
+	Common_Value_False_Value bool    = false
+	Common_Value_Zero_String string  = "0"
+	Common_Value_Zere_Float  float64 = 0.0
+	Common_Value_One_Int64   int64   = 1
+	Common_Value_One_Int32   int32   = 1
+
+	File_Ref_Type_Process_Route           string = "process_route"
+	File_Ref_Type_Process_Stage           string = "process_stage"
+	File_Ref_Type_Quality_Inspect_Program string = "quality_inspect_program"
+	File_Ref_Type_Sale_Order              string = "sale_order"
+	File_Ref_Type_Purchase_Plan           string = "purchase_plan"
+	File_Ref_Type_Purchase_Order          string = "purchase_order"
+	File_Ref_Type_Product_Plan            string = "product_plan"
+	File_Ref_Type_Customer                string = "customer"
+	File_Ref_Type_Outsourcing_Plan        string = "outsourcing_plan"
+
+	Status_Enable  string = "enable"
+	Status_Disable string = "disable"
+
+	Sale_Order_Status_Pending    string = "pending"
+	Sale_Order_Status_Processing string = "processing"
+	Sale_Order_Status_Shipped    string = "shipped"
+	Sale_Order_Status_Partially  string = "partially"
+	Sale_Order_Status_Complete   string = "complete"
+
+	Purchase_Plan_Status_Pending    string = "pending"
+	Purchase_Plan_Status_Processing string = "processing"
+	Purchase_Plan_Status_Complete   string = "complete"
+
+	Purchase_Order_Status_Pending    string = "pending"
+	Purchase_Order_Status_Processing string = "processing"
+	Purchase_Order_Status_Shipped    string = "shipped"
+	Purchase_Order_Status_Complete   string = "complete"
+
+	Warehouse_Record_Type_In     string = "in"
+	Warehouse_Record_Type_Out    string = "out"
+	Warehouse_Record_Type_Move   string = "move"
+	Warehouse_Record_Type_Lock   string = "lock"
+	Warehouse_Record_Type_Unlock string = "unlock"
+
+	Product_Plan_Status_Pending    string = "pending"
+	Product_Plan_Status_Processing string = "processing"
+	Product_Plan_Status_Complete   string = "complete"
+
+	Outsourcing_Plan_Status_Pending    string = "pending"
+	Outsourcing_Plan_Status_Processing string = "processing"
+	Outsourcing_Plan_Status_Complete   string = "complete"
+
+	Warehouse_Record_Ref_Type_Product     string = "product"
+	Warehouse_Record_Ref_Type_Purchase    string = "purchase"
+	Warehouse_Record_Ref_Type_Outsourcing string = "outsourcing"
+)
+
+// type: {
+// 	raw_material: "原材料供应商",
+// 	module: "零部件/模块供应商",
+// 	mro: "MRO供应商",
+// 	service: "服务供应商",
+// 	fixed_asset: "固定资产供应商"
+// },
+
+// valueLevel: {
+// 	strategic: "战略供应商",
+// 	core: "核心供应商",
+// 	normal: "一般供应商"
+// }

+ 41 - 0
models/extend_vo.go

@@ -0,0 +1,41 @@
+package models
+
+type ProductPrePlanVo struct {
+	SaleOrderId         *string `json:"saleOrderId"`
+	ProductPlanName     *string `json:"productPlanName"`
+	PurchasePlanName    *string `json:"purchasePlanName"`
+	OutsourcingPlanName *string `json:"outsourcingPlanName"`
+	PlanBeginDate       *string `json:"planBeginDate"`
+	PlanEndDate         *string `json:"planEndDate"`
+
+	ProductPlanVoList     *[]ProductPlanVo     `json:"productPlanVoList"`
+	PurchasePlanVoList    *[]PurchasePlanVo    `json:"purchasePlanVoList"`
+	OutsourcingPlanVoList *[]OutsourcingPlanVo `json:"outsourcingPlanVoList"`
+}
+
+type ProductPlanVo struct {
+	MaterialCode *string `json:"materialCode"`
+	BomId        *string `json:"bomId"`
+
+	WarehouseMaterialVoList *[]WarehouseMaterialVo `json:"warehouseMaterialVoList"`
+	PlanProductNumber       *float64               `json:"allocateNum"`
+}
+
+type PurchasePlanVo struct {
+	MaterialCode *string `json:"materialCode"`
+
+	WarehouseMaterialVoList *[]WarehouseMaterialVo `json:"warehouseMaterialVoList"`
+	PlanPurchaseNumber      *float64               `json:"allocateNum"`
+}
+
+type OutsourcingPlanVo struct {
+	MaterialCode *string `json:"materialCode"`
+
+	WarehouseMaterialVoList *[]WarehouseMaterialVo `json:"warehouseMaterialVoList"`
+	PlanOutsourcingNumber   *float64               `json:"allocateNum"`
+}
+
+type WarehouseMaterialVo struct {
+	LockedNumber *float64 `json:"lockedNumber"`
+	WarehouseId  *string  `json:"warehouseId"`
+}

+ 10 - 0
models/flow_no.go

@@ -0,0 +1,10 @@
+package models
+
+type FlowNo struct {
+	Type     *string `json:"type" db:"type"`
+	Prefix   *string `json:"prefix" db:"prefix"`
+	CurrDate *string `json:"currDate" db:"curr_date"`
+	CurrNo   *string `json:"currNo" db:"curr_no"`
+	CurrSeq  *int32  `json:"currSeq" db:"curr_seq"`
+	TenantId *string `json:"tenantId" db:"tenant_id"`
+}

+ 12 - 0
models/minio_file.go

@@ -0,0 +1,12 @@
+package models
+
+type MinioFile struct {
+	ID          *string `json:"id" db:"id" id_type:"UUID"`
+	RefType     *string `json:"refType" db:"ref_type"`
+	RefId       *string `json:"refId" db:"ref_id"`
+	Path        *string `json:"path" db:"path"`
+	ContentType *string `json:"contentType"  db:"content_type"`
+	FileType    *string `json:"fileType" db:"file_type"`
+	FileName    *string `json:"fileName" db:"file_name"`
+	CreateTime  *string `json:"createTime" db:"create_time"`
+}

+ 30 - 0
models/outsourcing.go

@@ -0,0 +1,30 @@
+package models
+
+type OutsourcingPlan struct {
+	ID          *string `json:"id" db:"id" id_type:"UUID"`
+	Code        *string `json:"code" db:"code"`
+	Name        *string `json:"name" db:"name"`
+	SaleOrderId *string `json:"saleOrderId" db:"sale_order_id"`
+	CreateTime  *string `json:"createTime" db:"create_time"`
+	CreateId    *int64  `json:"createId" db:"create_id"`
+	UpdateId    *int64  `json:"updateId" db:"update_id"`
+	Status      *string `json:"status" db:"status"`
+	TenantId    *string `json:"tenantId" db:"tenant_id"`
+	UpdateTime  *string `json:"updateTime" db:"update_time"`
+	BeginDate   *string `json:"beginDate" db:"begin_date"`
+	EndDate     *string `json:"endDate" db:"end_date"`
+
+	EmptyField   *[]string                `json:"emptyField" db:"-"`
+	FileList     *[]MinioFile             `json:"fileList" db:"-"`
+	ChildrenList *[]OutsourcingPlanDetail `json:"childrenList" db:"-"`
+	SaleOrder    *SaleOrder               `json:"saleOrder" db:"-"`
+}
+
+type OutsourcingPlanDetail struct {
+	ID           *string  `json:"id" db:"id" id_type:"UUID"`
+	PlanId       *string  `json:"planId" db:"plan_id"`
+	MaterialCode *string  `json:"materialCode" db:"material_code"`
+	Number       *float64 `json:"number" db:"number"`
+
+	ProductMaterial *ProductMaterial `json:"material" db:"-"`
+}

+ 64 - 0
models/process.go

@@ -0,0 +1,64 @@
+package models
+
+type ProcessStage struct {
+	ID              *string `json:"id" db:"id" id_type:"UUID"`
+	Name            *string `json:"name" db:"name"`
+	Code            *string `json:"code" db:"code"`
+	DirectorName    *string `json:"directorName" db:"director_name"`
+	DirectorPhone   *string `json:"directorPhone" db:"director_phone"`
+	ProcessType     *string `json:"processType" db:"process_type"`
+	Category        *string `json:"category" db:"category"`
+	CalculateMethod *string `json:"calculateMethod" db:"calculate_method"`
+	Remark          *string `json:"remark" db:"remark"`
+	CreateTime      *string `json:"createTime" db:"create_time"`
+	CreateId        *int64  `json:"createId" db:"create_id"`
+	Status          *string `json:"status" db:"status"`
+	TenantId        *string `json:"tenantId" db:"tenant_id"`
+	UpdateId        *int64  `json:"updateId" db:"update_id"`
+	UpdateTime      *string `json:"updateTime" db:"update_time"`
+
+	EmptyField *[]string    `json:"emptyField" db:"-"`
+	FileList   *[]MinioFile `json:"fileList" db:"-"`
+}
+
+type ProcessRoute struct {
+	ID               *string `json:"id" db:"id" id_type:"UUID"`
+	Name             *string `json:"name" db:"name"`
+	Code             *string `json:"code" db:"code"`
+	ParentId         *string `json:"parentId" db:"parent_id"`
+	TimeUnit         *string `json:"timeUnit" db:"time_unit"`
+	CreateTime       *string `json:"createTime" db:"create_time"`
+	CreateId         *int64  `json:"createId" db:"create_id"`
+	Status           *string `json:"status" db:"status"`
+	Remark           *string `json:"remark" db:"remark"`
+	Version          *string `json:"version" db:"version"`
+	InspectProgramId *string `json:"inspectProgramId" db:"inspect_program_id"`
+	TenantId         *string `json:"tenantId" db:"tenant_id"`
+	UpdateId         *int64  `json:"updateId" db:"update_id"`
+	UpdateTime       *string `json:"updateTime" db:"update_time"`
+
+	EmptyField     *[]string              `json:"emptyField" db:"-"`
+	FileList       *[]MinioFile           `json:"fileList" db:"-"`
+	IsHaveHistory  *bool                  `json:"isHaveHistory" db:"-"`
+	DetailList     *[]ProcessRouteDetail  `json:"detailList" db:"-"`
+	InspectProgram *QualityInspectProgram `json:"inspectProgram" db:"-"`
+}
+
+type ProcessRouteDetail struct {
+	ID              *string   `json:"id" db:"id" id_type:"UUID"`
+	RouteId         *string   `json:"routeId" db:"route_id"`
+	StageId         *string   `json:"stageId" db:"stage_id"`
+	OrderNum        *int32    `json:"orderNum" db:"order_num"`
+	ReadyTimeHour   *float64  `json:"readyTimeHour" db:"ready_time_hour"`
+	ProcessNum      *float64  `json:"processNum" db:"process_num"`
+	ProcessTimeHour *float64  `json:"processTimeHour" db:"process_time_hour"`
+	MoveNum         *float64  `json:"moveNum" db:"move_num"`
+	MoveTimeHour    *float64  `json:"moveTimeHour" db:"move_time_hour"`
+	IsReview        *bool     `json:"isReview" db:"is_review"`
+	IsReport        *bool     `json:"isReport" db:"is_report"`
+	IsRound         *bool     `json:"isRound" db:"is_round"`
+	Remark          *string   `json:"remark" db:"remark"`
+	DeviceIdList    *string `json:"deviceList" db:"device_list"`
+
+	ProcessStage *ProcessStage `json:"stage" db:"-"`
+}

+ 62 - 0
models/product.go

@@ -0,0 +1,62 @@
+package models
+
+type ProductPlan struct {
+	ID          *string `json:"id" db:"id" id_type:"UUID"`
+	Name        *string `json:"name" db:"name"`
+	Code        *string `json:"code" db:"code"`
+	SaleOrderId *string `json:"saleOrderId" db:"sale_order_id"`
+	BeginDate   *string `json:"beginDate" db:"begin_date"`
+	EndDate     *string `json:"endDate" db:"end_date"`
+	Remark      *string `json:"remark" db:"remark"`
+	CreateTime  *string `json:"createTime" db:"create_time"`
+	CreateId    *int64  `json:"createId" db:"create_id"`
+	Status      *string `json:"status" db:"status"`
+	TenantId    *string `json:"tenantId" db:"tenant_id"`
+	UpdateId    *int64  `json:"updateId" db:"update_id"`
+	UpdateTime  *string `json:"updateTime" db:"update_time"`
+
+	EmptyField *[]string            `json:"emptyField" db:"-"`
+	FileList   *[]MinioFile         `json:"fileList" db:"-"`
+	SaleOrder  *SaleOrder           `json:"saleOrder" db:"-"`
+	BomList    *[]ProductPlanBom    `json:"bomList" db:"-"`
+	DeviceList *[]ProductPlanDevice `json:"deviceList" db:"-"`
+	UserList   *[]ProductPlanUser   `json:"userList" db:"-"`
+}
+
+type ProductPlanBom struct {
+	ID               *string  `json:"id" db:"id" id_type:"UUID"`
+	PlanId           *string  `json:"planId" db:"plan_id"`
+	BomId            *string  `json:"bomId" db:"bom_id"`
+	RouteId          *string  `json:"routeId" db:"route_id"`
+	Number           *float64 `json:"number" db:"number"`
+	CreateTime       *string  `json:"createTime" db:"create_time"`
+	InspectProgramId *string  `json:"inspectProgramId" db:"inspect_program_id"`
+
+	ProductPlan           *ProductPlan           `json:"productPlan" db:"-"`
+	ProductBom            *ProductBom            `json:"productBom" db:"-"`
+	ProcessRoute          *ProcessRoute          `json:"processRoute" db:"-"`
+	QualityInspectProgram *QualityInspectProgram `json:"inspectProgram" db:"-"`
+}
+
+type ProductPlanDevice struct {
+	ID         *string `json:"id" db:"id" id_type:"UUID"`
+	PlanId     *string `json:"planId" db:"plan_id"`
+	DeviceId   *string `json:"deviceId" db:"device_id"`
+	CreateTime *string `json:"createTime" db:"create_time"`
+
+	ProductPlan *ProductPlan `json:"productPlan" db:"-"`
+	Device      *Device      `json:"device" db:"-"`
+}
+
+type ProductPlanUser struct {
+	ID        *string `json:"id" db:"id" id_type:"UUID"`
+	PlanId    *string `json:"planId" db:"plan_id"`
+	BeginTime *string `json:"beginTime" db:"begin_time"`
+	EndTime   *string `json:"endTime" db:"end_time"`
+	RouteId   *string `json:"routeId" db:"route_id"`
+	StageId   *string `json:"stageId" db:"stage_id"`
+	UserId    *int64  `json:"userId" db:"user_id"`
+
+	ProductPlan *ProductPlan `json:"productPlan" db:"-"`
+	User        *SysUser     `json:"user" db:"-"`
+}

+ 66 - 0
models/purchase.go

@@ -0,0 +1,66 @@
+package models
+
+type PurchasePlan struct {
+	ID             *string `json:"id" db:"id" id_type:"UUID"`
+	Code           *string `json:"code" db:"code"`
+	Name           *string `json:"name" db:"name"`
+	SaleOrderId    *string `json:"saleOrderId" db:"sale_order_id"`
+	CreateTime     *string `json:"createTime" db:"create_time"`
+	PurchaseUserId *int64  `json:"purchaseUserId" db:"purchase_user_id"`
+	CreateId       *int64  `json:"createId" db:"create_id"`
+	UpdateId       *int64  `json:"updateId" db:"update_id"`
+	Status         *string `json:"status" db:"status"`
+	TenantId       *string `json:"tenantId" db:"tenant_id"`
+	UpdateTime     *string `json:"updateTime" db:"update_time"`
+	BeginDate      *string `json:"beginDate" db:"begin_date"`
+	EndDate        *string `json:"endDate" db:"end_date"`
+
+	EmptyField       *[]string             `json:"emptyField" db:"-"`
+	FileList         *[]MinioFile          `json:"fileList" db:"-"`
+	ChildrenList     *[]PurchasePlanDetail `json:"childrenList" db:"-"`
+	SaleOrder        *SaleOrder            `json:"saleOrder" db:"-"`
+	PurchaseUserName *string               `json:"purchaseUserName" db:"-"`
+}
+
+type PurchasePlanDetail struct {
+	ID           *string  `json:"id" db:"id" id_type:"UUID"`
+	PlanId       *string  `json:"planId" db:"plan_id"`
+	MaterialCode *string  `json:"materialCode" db:"material_code"`
+	Number       *float64 `json:"number" db:"number"`
+
+	ProductMaterial *ProductMaterial `json:"material" db:"-"`
+}
+
+type PurchaseOrder struct {
+	ID                *string `json:"id" db:"id" id_type:"UUID"`
+	Code              *string `json:"code" db:"code"`
+	SaleOrderId       *string `json:"saleOrderId" db:"sale_order_id"`
+	CustomerId        *string `json:"customerId" db:"customer_id"`
+	CreateTime        *string `json:"createTime" db:"create_time"`
+	Status            *string `json:"status" db:"status"`
+	PlanReceiveDate   *string `json:"planReceiveDate" db:"plan_receive_date"`
+	ActualReceiveDate *string `json:"actualReceiveDate" db:"actual_receive_date"`
+	PurchasePlanId    *string `json:"purchasePlanId" db:"purchase_plan_id"`
+	CreateId          *int64  `json:"createId" db:"create_id"`
+	UpdateId          *int64  `json:"updateId" db:"update_id"`
+	TenantId          *string `json:"tenantId" db:"tenant_id"`
+	UpdateTime        *string `json:"updateTime" db:"update_time"`
+	ExpressNo         *string `json:"expressNo" db:"express_no"`
+
+	EmptyField   *[]string              `json:"emptyField" db:"-"`
+	FileList     *[]MinioFile           `json:"fileList" db:"-"`
+	ChildrenList *[]PurchaseOrderDetail `json:"childrenList" db:"-"`
+	SaleOrder    *SaleOrder             `json:"saleOrder" db:"-"`
+	Customer     *Customer              `json:"customer" db:"-"`
+	PurchasePlan *PurchasePlan          `json:"purchasePlan" db:"-"`
+}
+
+type PurchaseOrderDetail struct {
+	ID           *string  `json:"id" db:"id" id_type:"UUID"`
+	OrderId      *string  `json:"orderId" db:"order_id"`
+	MaterialCode *string  `json:"materialCode" db:"material_code"`
+	Number       *float64 `json:"number" db:"number"`
+	Status       *string  `json:"status" db:"status"`
+
+	ProductMaterial *ProductMaterial `json:"material" db:"-"`
+}

+ 80 - 0
models/sale.go

@@ -0,0 +1,80 @@
+package models
+
+type SaleOrder struct {
+	ID                *string  `json:"id" db:"id" id_type:"UUID"`
+	Code              *string  `json:"code" db:"code"`
+	CustomerId        *string  `json:"customerId" db:"customer_id"`
+	ManagerId         *int64   `json:"managerId" db:"manager_id"`
+	IncomePrice       *float64 `json:"incomePrice" db:"income_price"`
+	FreePrice         *float64 `json:"freePrice" db:"free_price"`
+	ActualPrice       *float64 `json:"actualPrice" db:"actual_price"`
+	CreateTime        *string  `json:"createTime" db:"create_time"`
+	Status            *string  `json:"status" db:"status"`
+	PlanReceiveDate   *string  `json:"planReceiveDate" db:"plan_receive_date"`
+	ActualReceiveDate *string  `json:"actualReceiveDate" db:"actual_receive_date"`
+	DeliveryDate      *string  `json:"deliveryDate" db:"delivery_date"`
+	DeliveryAddress   *string  `json:"deliveryAddress" db:"delivery_address"`
+	ContractNo        *string  `json:"contractNo" db:"contract_no"`
+	OrderDate         *string  `json:"orderDate" db:"order_date"`
+	Remark            *string  `json:"remark" db:"remark"`
+	TenantId          *string  `json:"tenantId" db:"tenant_id"`
+	CreateId          *int64   `json:"createId" db:"create_id"`
+	UpdateId          *int64   `json:"updateId" db:"update_id"`
+	UpdateTime        *string  `json:"updateTime" db:"update_time"`
+
+	ManagerName  *string            `json:"managerName" db:"-"`
+	CustomerName *string            `json:"customerName" db:"-"`
+	EmptyField   *[]string          `json:"emptyField" db:"-"`
+	FileList     *[]MinioFile       `json:"fileList" db:"-"`
+	ChildrenList *[]SaleOrderDetail `json:"childrenList" db:"-"`
+}
+
+type SaleOrderDetail struct {
+	ID               *string  `json:"id" db:"id" id_type:"UUID"`
+	MaterialCode     *string  `json:"materialCode" db:"material_code"`
+	MaterialQuantity *int32   `json:"materialQuantity" db:"material_quantity"`
+	OrderId          *string  `json:"orderId" db:"order_id"`
+	CreateTime       *string  `json:"createTime" db:"create_time"`
+	MaterialPrice    *float64 `json:"materialPrice" db:"material_price"`
+
+	Material              *ProductMaterial     `json:"material" db:"-"`
+	BomList               *[]ProductBom        `json:"bomList" db:"-"`
+	WarehouseMaterialList *[]WarehouseMaterial `json:"warehouseMaterialList" db:"-"`
+}
+
+type SalePlan struct {
+	ID         *string  `json:"id" db:"id" id_type:"UUID"`
+	Code       *string  `json:"code" db:"code"`
+	Name       *string  `json:"name" db:"name"`
+	Type       *string  `json:"type" db:"type"`
+	ParentId   *string  `json:"parentId" db:"parent_id"`
+	BeginDate  *string  `json:"beginDate" db:"begin_date"`
+	EndDate    *string  `json:"endDate" db:"end_date"`
+	SaleAmount *float64 `json:"saleAmount" db:"sale_amount"`
+	CreateTime *string  `json:"createTime" db:"create_time"`
+	Remark     *string  `json:"remark" db:"remark"`
+	TenantId   *string  `json:"tenantId" db:"tenant_id"`
+	CreateId   *int64   `json:"createId" db:"create_id"`
+	UpdateId   *int64   `json:"updateId" db:"update_id"`
+	UpdateTime *string  `json:"updateTime" db:"update_time"`
+
+	EmptyField       *[]string    `json:"emptyField" db:"-"`
+	FileList         *[]MinioFile `json:"fileList" db:"-"`
+	TotalActualPrice *float64     `json:"totalActualPrice" db:"-"`
+}
+
+type SalePriceByDay struct {
+	Date    *string  `json:"date" db:"date"`
+	DateStr *string  `json:"date2" db:"datestr"`
+	Price   *float64 `json:"price" db:"price"`
+}
+
+type SalePerformance struct {
+	PlanSalePriceByMonth   *float64 `json:"planSalePriceByMonth" db:"plan_sale_price_by_month"`
+	PlanSalePriceByYear    *float64 `json:"planSalePriceByYear" db:"plan_sale_price_by_year"`
+	ActualSalePriceByMonth *float64 `json:"actualSalePriceByMonth" db:"actual_sale_price_by_month"`
+	ActualSalePriceByYear  *float64 `json:"actualSalePriceByYear" db:"actual_sale_price_by_year"`
+
+	PlanList   *[]SalePriceByDay `json:"planList" db:"-"`
+	ActualList *[]SalePriceByDay `json:"actualList" db:"-"`
+}

+ 63 - 0
models/system.go

@@ -0,0 +1,63 @@
+package models
+
+// Dept 用户模型
+type SysDept struct {
+	ID                   *int64  `json:"id" db:"id" id_type:"AUTO"`
+	Pid                  *int64  `json:"pid" db:"pid"`
+	Name                 *string `json:"name" db:"name"`
+	DeptSort             *int32  `json:"deptSort" db:"dept_sort"`
+	FirmFunctionary      *string `json:"firmFunctionary"  db:"firm_functionary"`
+	FirmFunctionaryPhone *string `json:"firmFunctionaryPhone" db:"firm_functionary_phone"`
+	Remark               *string `json:"remark" db:"remark"`
+	TenantId             *string `json:"tenantId" db:"tenant_id"`
+
+	EmptyField *[]string `json:"emptyField" db:"-"`
+}
+
+type SysMenu struct {
+	ID        *int64  `json:"id" db:"id" id_type:"AUTO"`
+	Pid       *int64  `json:"pid" db:"pid"` // 假设pid为0表示根节点
+	Type      *int32  `json:"type" db:"type"`
+	Title     *string `json:"title" db:"title"`
+	Component *string `json:"component" db:"component"`
+	MenuSort  *int32  `json:"menuSort" db:"menu_sort"`
+	Icon      *string `json:"icon" db:"icon"`
+	Path      *string `json:"path" db:"path"`
+	Iframe    *bool   `json:"iframe" db:"iframe"`
+	Hidden    *bool   `json:"hidden" db:"hidden"`
+
+	EmptyField *[]string `json:"emptyField" db:"-"`
+}
+
+type SysRolesMenus struct {
+	RoleID *int64 `json:"roleId" db:"role_id"`
+	MenuID *int64 `json:"menuId" db:"menu_id"`
+
+	MenuIdList *[]int64 `json:"menuIdList" db:"-"`
+}
+
+type SysRole struct {
+	ID          *int64  `json:"id" db:"id" id_type:"AUTO"`
+	Name        *string `json:"name" db:"name"`
+	Level       *int32  `json:"level" db:"level"`
+	Description *string `json:"description" db:"description"`
+	DataScope   *string `json:"dataScope" db:"data_scope"`
+	TenantId    *string `json:"tenantId" db:"tenant_id"`
+
+	EmptyField *[]string `json:"emptyField" db:"-"`
+}
+
+type SysUsersRoles struct {
+	UserID *int64 `json:"userId" db:"user_id"`
+	RoleID *int64 `json:"roleId" db:"role_id"`
+}
+
+type Tenant struct {
+	ID           *string `json:"id" db:"id"`
+	Name         *string `json:"name" db:"name"`
+	Status       *string `json:"status" db:"status"`
+	ManagerName  *string `json:"managerName" db:"manager_name"`
+	ManagerPhone *string `json:"managerPhone" db:"manager_phone"`
+
+	EmptyField *[]string `json:"emptyField" db:"-"`
+}

+ 47 - 0
models/user.go

@@ -0,0 +1,47 @@
+package models
+
+import (
+	"fmt"
+
+	"easydo-echo_win7/utils"
+)
+
+// User 用户模型
+type SysUser struct {
+	ID       *int64  `json:"id" db:"id" id_type:"AUTO"`
+	DeptId   *int64  `json:"deptId" db:"dept_id"`
+	NickName *string `json:"nickName" db:"nick_name"`
+	Gender   *string `json:"gender" db:"gender"`
+	Phone    *string `json:"phone" db:"phone"`
+	Username *string `json:"username"  db:"username"`
+	Password *string `json:"-" db:"password"` // 不序列化密码
+	Email    *string `json:"email" db:"email"`
+	Features *string `json:"features" db:"features"`
+	TenantId *string `json:"tenantId" db:"tenant_id"`
+
+	Dept       *SysDept   `json:"dept" db:"-"`
+	Tenant     *Tenant    `json:"tenant" db:"-"`
+	RoleList   *[]SysRole `json:"roleList" db:"-"`
+	EmptyField *[]string  `json:"emptyField" db:"-"`
+}
+
+// CheckPassword 验证密码
+func (u *SysUser) CheckPassword(password string) bool {
+	plainText, err := utils.DecryptByPrivateKey(password)
+	if err != nil {
+		fmt.Printf("解密失败: %v\n", err)
+		return false
+	}
+	fmt.Printf("解密结果: %s\n", plainText)
+	result := utils.VerifyPassword(plainText, *u.Password)
+
+	return result
+}
+
+// UserLoginRequest 登录请求
+type UserLoginRequest struct {
+	Username    string `json:"username" validate:"required,min=3,max=50"`
+	Password    string `json:"password" validate:"required,min=6,max=100"`
+	CaptchaID   string `json:"uuid" validate:"required"`
+	CaptchaCode string `json:"code" validate:"required"`
+}

+ 53 - 0
models/warehouse.go

@@ -0,0 +1,53 @@
+package models
+
+type Warehouse struct {
+	ID         *string `json:"id" db:"id" id_type:"UUID"`
+	Name       *string `json:"name" db:"name"`
+	Code       *string `json:"code" db:"code"`
+	CreateId   *int64  `json:"createId" db:"create_id"`
+	CreateTime *string `json:"createTime" db:"create_time"`
+	TenantId   *string `json:"tenantId" db:"tenant_id"`
+	UpdateId   *int64  `json:"updateId" db:"update_id"`
+	UpdateTime *string `json:"updateTime" db:"update_time"`
+
+	EmptyField *[]string `json:"emptyField" db:"-"`
+}
+
+type WarehouseMaterial struct {
+	ID            *string  `json:"id" db:"id" id_type:"UUID"`
+	WarehouseId   *string  `json:"warehouseId" db:"warehouse_id"`
+	MaterialCode  *string  `json:"materialCode" db:"material_code"`
+	Number        *float64 `json:"number" db:"number"`
+	Status        *string  `json:"status" db:"status"`
+	FrozenNumber  *float64 `json:"frozenNumber" db:"frozen_number"`
+	NormalNumber  *float64 `json:"normalNumber" db:"normal_number"`
+	LockedNumber  *float64 `json:"lockedNumber" db:"locked_number"`
+	AbandonNumber *float64 `json:"abandonNumber" db:"abandon_number"`
+	CreateId      *int64   `json:"createId" db:"create_id"`
+	CreateTime    *string  `json:"createTime" db:"create_time"`
+	TenantId      *string  `json:"tenantId" db:"tenant_id"`
+	UpdateId      *int64   `json:"updateId" db:"update_id"`
+	UpdateTime    *string  `json:"updateTime" db:"update_time"`
+
+	EmptyField      *[]string        `json:"emptyField" db:"-"`
+	ProductMaterial *ProductMaterial `json:"material" db:"-"`
+	Warehouse       *Warehouse       `json:"warehouse" db:"-"`
+}
+
+type WarehouseRecord struct {
+	ID              *string  `json:"id" db:"id" id_type:"UUID"`
+	MaterialCode    *string  `json:"materialCode" db:"material_code"`
+	Type            *string  `json:"type" db:"type"`
+	Number          *float64 `json:"number" db:"number"`
+	CreateId        *int64   `json:"createId" db:"create_id"`
+	CreateTime      *string  `json:"createTime" db:"create_time"`
+	Remark          *string  `json:"remark" db:"remark"`
+	TenantId        *string  `json:"tenantId" db:"tenant_id"`
+	FromWarehouseId *string  `json:"fromWarehouseId" db:"from_warehouse_id"`
+	ToWarehouseId   *string  `json:"toWarehouseId" db:"to_warehouse_id"`
+	RefId           *string  `json:"refId" db:"ref_id"`
+	RefType         *string  `json:"refType" db:"ref_type"`
+	SaleOrderId     *string  `json:"saleOrderId" db:"sale_order_id"`
+
+	EmptyField *[]string `json:"emptyField" db:"-"`
+}

+ 63 - 0
services/captcha.go

@@ -0,0 +1,63 @@
+package services
+
+import (
+	"context"
+	"strings"
+	"time"
+
+	"github.com/mojocn/base64Captcha"
+)
+
+// CaptchaResponse 验证码响应结构体
+type CaptchaResponse struct {
+	CaptchaID    string `json:"uuid"`
+	CaptchaImage string `json:"img"`
+}
+
+// GenerateCaptcha 生成验证码,并将结果显式存入Redis
+func GenerateCaptcha() (*CaptchaResponse, error) {
+	// 初始化验证码实例(使用base64Captcha的内存存储临时存ID映射,仅用于生成,实际结果存Redis)
+	driver := GetCaptchaDriver()
+	memoryStore := base64Captcha.DefaultMemStore // 临时内存存储(仅生成阶段用)
+	cp := base64Captcha.NewCaptcha(driver, memoryStore)
+
+	// 生成验证码:ID、Base64图片、答案、错误
+	captchaID, b64Image, answer, err := cp.Generate()
+	if err != nil {
+		return nil, err
+	}
+	// ------------------------------
+	// 显式将验证码结果存入Redis(核心步骤)
+	// ------------------------------
+	redisKey := CaptchaRedisPrefix + captchaID
+	ctx := context.Background()
+	// 存入Redis并设置过期时间
+	err = RedisClient.Set(ctx, redisKey, answer, CaptchaExpireMin*time.Minute).Err()
+	if err != nil {
+		return nil, err
+	}
+
+	return &CaptchaResponse{
+		CaptchaID:    redisKey,
+		CaptchaImage: b64Image,
+	}, nil
+}
+
+// VerifyCaptcha 验证验证码:从Redis读取结果对比
+func VerifyCaptcha(captchaID, userInput string) bool {
+	if captchaID == "" || userInput == "" {
+		return false
+	}
+	// ------------------------------
+	// 从Redis读取验证码结果
+	// ------------------------------
+	ctx := context.Background()
+	// 获取结果(验证后立即删除,防止重复使用)
+	storedAnswer, err := RedisClient.Get(ctx, captchaID).Result()
+	if err != nil { // Redis中无该键或读取失败
+		return false
+	}
+
+	// 忽略大小写对比
+	return strings.ToLower(storedAnswer) == strings.ToLower(userInput)
+}

+ 77 - 0
services/flow_no.go

@@ -0,0 +1,77 @@
+package services
+
+import (
+	"easydo-echo_win7/models"
+	"fmt"
+	"time"
+
+	"github.com/jmoiron/sqlx"
+)
+
+func GetFlowNo(flow_type string, tenant_id string, conn ...*sqlx.Tx) (string, error) {
+	flow_no := new(models.FlowNo)
+	flow_no.Type = &flow_type
+	flow_no.TenantId = &tenant_id
+
+	if len(conn) > 0 {
+		tx := conn[0]
+		err := JdbcClient.GetJdbcModel(flow_no, tx)
+		if err != nil {
+			return "", fmt.Errorf("SQL执行失败: %v", err)
+		}
+
+	} else {
+		err := JdbcClient.GetJdbcModel(flow_no)
+		if err != nil {
+			return "", fmt.Errorf("SQL执行失败: %v", err)
+		}
+	}
+
+	// 获取当前时间
+	now := time.Now()
+
+	// 格式化日期
+	currDate := now.Format("2006-01-02")
+	currDate2 := now.Format("20060102")
+
+	var no string
+
+	// 判断是否需要重置序列号(新的一天)
+	if flow_no.CurrDate == nil || len(*flow_no.CurrDate) == 0 || (len(*flow_no.CurrDate) > 0 && *flow_no.CurrDate != currDate) {
+		flow_no.CurrDate = &currDate
+		*flow_no.CurrSeq = 1
+	} else {
+		*flow_no.CurrSeq = *flow_no.CurrSeq + 1
+	}
+
+	// 生成流水号
+	if len(*flow_no.CurrDate) == 0 {
+		// 没有日期的情况,使用6位序列号
+		currSeq := fmt.Sprintf("%06d", *flow_no.CurrSeq)
+		no = *flow_no.Prefix + currSeq
+	} else {
+		// 有日期的情况,使用5位序列号
+		currSeq := fmt.Sprintf("%05d", *flow_no.CurrSeq)
+		no = *flow_no.Prefix + currDate2 + currSeq
+	}
+
+	flow_no.CurrNo = &no
+
+	paramMap := map[string]interface{}{
+		"type": flow_type,
+	}
+
+	if len(conn) > 0 {
+		tx := conn[0]
+		err := JdbcClient.JdbcUpdate(flow_no, paramMap, tx)
+		if err != nil {
+			return "", fmt.Errorf("SQL执行失败: %v", err)
+		}
+	} else {
+		err := JdbcClient.JdbcUpdate(flow_no, paramMap)
+		if err != nil {
+			return "", fmt.Errorf("SQL执行失败: %v", err)
+		}
+	}
+	return no, nil
+}

+ 165 - 0
services/init_service.go

@@ -0,0 +1,165 @@
+package services
+
+import (
+	"context"
+	"fmt"
+	"image/color"
+	"sync"
+	"time"
+
+	"github.com/jmoiron/sqlx"
+
+	"github.com/go-redis/redis/v8"
+	"github.com/gorilla/sessions"
+	"github.com/mojocn/base64Captcha"
+)
+
+var (
+	// 配置信息
+	AppConfig = struct {
+		SessionSecret string
+		SessionName   string
+		CaptchaType   string
+	}{
+		SessionSecret: "your-very-secret-key-change-in-production",
+		SessionName:   "auth_session",
+		CaptchaType:   "digit", // math, digit, audio, string
+	}
+
+	// 全局服务实例
+	sessionStore  *sessions.CookieStore
+	captchaDriver base64Captcha.Driver
+	initOnce      sync.Once
+
+	RedisClient *redis.Client
+	MYSQL_DB    *sqlx.DB
+	JdbcClient  *DBClient
+	MinioClient *MinioService
+)
+
+// 验证码配置
+const (
+	CaptchaExpireMin   = 5           // 验证码过期时间(分钟)
+	CaptchaRedisPrefix = "code-key-" // 验证码结果存储键前缀
+)
+
+const (
+	BucketName = "process"
+)
+
+// InitConfig 初始化配置
+func InitConfig() {
+	// 这里可以读取配置文件或环境变量
+	// 例如:AppConfig.SessionSecret = os.Getenv("SESSION_SECRET")
+}
+
+// SessionStore 获取会话存储
+func SessionStore() *sessions.CookieStore {
+	initOnce.Do(func() {
+		sessionStore = sessions.NewCookieStore([]byte(AppConfig.SessionSecret))
+		sessionStore.Options = &sessions.Options{
+			Path:     "/",
+			MaxAge:   86400 * 7, // 7天
+			HttpOnly: true,
+			Secure:   false, // 开发环境设为false,生产环境设为true
+		}
+	})
+	return sessionStore
+}
+
+// InitServices 初始化服务
+func InitCaptcha() {
+	// 初始化验证码驱动
+	switch AppConfig.CaptchaType {
+	case "digit":
+		captchaDriver = base64Captcha.NewDriverDigit(80, 240, 4, 0.2, 10)
+	case "math":
+		captchaDriver = base64Captcha.NewDriverMath(
+			70,  // 高度 - 适当高度,保证清晰度
+			240, // 宽度 - 足够宽度显示数学公式
+			0,   // 干扰线数量 - 设置为0提高可读性
+			0,   // 干扰点数量 - 设置为0提高可读性
+			&color.RGBA{R: 255, G: 255, B: 255, A: 255}, // 白色背景
+			nil,                       // 字体列表 - 使用默认
+			[]string{"RitaSmith.ttf"}, // 使用清晰字体
+		)
+	case "audio":
+		captchaDriver = base64Captcha.NewDriverAudio(4, "en")
+	default:
+		captchaDriver = base64Captcha.NewDriverString(80, 240, 4, 0, 4, "1234567890", nil, nil, nil)
+	}
+}
+
+func InitMyRedis(addr string, password string, dbn int) {
+	RedisClient = redis.NewClient(&redis.Options{
+		Addr:     addr,     // Redis地址
+		Password: password, // Redis密码(无则留空)
+		DB:       dbn,      // 使用的数据库
+	})
+	ctx := context.Background()
+	err := RedisClient.Set(ctx, "mes", "mes-redis", 10*time.Second).Err()
+	if err == nil {
+		build_time := time.Now()
+		build_time_str := build_time.Format(time.DateTime)
+		fmt.Printf("%s redis客户端启动成功\n", build_time_str)
+	} else {
+		fmt.Printf("redis.err=====%v\n", err)
+	}
+}
+
+func InitMysqlPool(user string, password string, host string, port int, dbname string, log bool) {
+	db_config := DefaultConfig(user, password, host, port, dbname)
+	MYSQL_DB = NewPool(db_config)
+	JdbcClient = NewDBClient(MYSQL_DB, log)
+
+	// // 准备预编译语句
+	// stmt, err := MYSQL_DB.Preparex("SELECT VERSION()")
+	// if err != nil {
+	//     print(err)
+	// }
+	// defer stmt.Close()
+
+	// var version string
+	// err = stmt.QueryRowx().Scan(&version)
+	// if err != nil {
+	//     print(err)
+	// }
+
+	// print("Database Version: ", version,"\n")
+}
+
+func InitMinioServie(addr string, user string, password string, usessl bool) {
+	MinioClient = NewMinioService(
+		addr,
+		user,
+		password,
+		usessl,
+	)
+	build_time := time.Now()
+	build_time_str := build_time.Format(time.DateTime)
+	ctx := context.Background()
+	_, err := MinioClient.client.BucketExists(ctx, "process")
+	if err == nil {
+		fmt.Printf("%s minio客户端初始化成功\n", build_time_str)
+	} else {
+		fmt.Printf("minio客户端.err=====%v\n", err)
+	}
+}
+
+// GetCaptchaDriver 获取验证码驱动
+func GetCaptchaDriver() base64Captcha.Driver {
+	return captchaDriver
+}
+
+// GetRedisClient 获取Redis客户端
+func GetRedisClient() *redis.Client {
+	return RedisClient
+}
+
+// func GetMysqlPool() *sqlx.DB {
+// 	return MYSQL_DB
+// }
+
+func GetMinioClient() *MinioService {
+	return MinioClient
+}

Dosya farkı çok büyük olduğundan ihmal edildi
+ 1184 - 0
services/jdbc_client.go


+ 142 - 0
services/minio_service.go

@@ -0,0 +1,142 @@
+package services
+
+import (
+	"context"
+	"fmt"
+	"io"
+	"time"
+
+	"github.com/minio/minio-go/v7"
+	"github.com/minio/minio-go/v7/pkg/credentials"
+)
+
+// MinioService 结构体,持有Minio客户端和配置
+type MinioService struct {
+	client   *minio.Client
+	endpoint string
+}
+
+// NewMinioService 构造函数,用于初始化MinioService
+func NewMinioService(endpoint, accessKeyID, secretAccessKey string, useSSL bool) *MinioService {
+	// 初始化minio client对象[citation:2]
+	client, err := minio.New(endpoint, &minio.Options{
+		Creds:  credentials.NewStaticV4(accessKeyID, secretAccessKey, ""),
+		Secure: useSSL,
+	})
+	if err != nil {
+		fmt.Printf("failed to create MinIO client: %w", err)
+		return nil
+	}
+
+	return &MinioService{
+		client:   client,
+		endpoint: endpoint,
+	}
+}
+
+// BucketExists 检查存储桶是否存在[citation:2]
+func (s *MinioService) BucketExists(bucketName string) (bool, error) {
+	exists, err := s.client.BucketExists(context.Background(), bucketName)
+	if err != nil {
+		return false, fmt.Errorf("failed to check if bucket '%s' exists: %w", bucketName, err)
+	}
+	return exists, nil
+}
+
+// MakeBucket 创建存储桶(如果不存在)[citation:2]
+func (s *MinioService) MakeBucket(bucketName string) error {
+	exists, err := s.BucketExists(bucketName)
+	if err != nil {
+		return err
+	}
+	if !exists {
+		// 创建一个存储桶[citation:2]
+		err = s.client.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{})
+		if err != nil {
+			return fmt.Errorf("failed to create bucket '%s': %w", bucketName, err)
+		}
+	}
+	return nil
+}
+
+// UploadFile 上传文件(使用默认日期目录)
+func (s *MinioService) UploadFile(bucketName string, reader io.Reader, objectName string) (string, error) {
+	// 使用当前日期生成目录,格式 yyyy-MM-dd
+	dir := time.Now().Format("2006-01-02")
+	return s.uploadFileToDir(bucketName, reader, objectName, dir)
+}
+
+// UploadFileToDir 上传文件到指定目录
+func (s *MinioService) UploadFileToDir(bucketName string, reader io.Reader, objectName, dir string) (string, error) {
+	return s.uploadFileToDir(bucketName, reader, objectName, dir)
+}
+
+// uploadFileToDir 内部上传方法
+func (s *MinioService) uploadFileToDir(bucketName string, reader io.Reader, objectName, dir string) (string, error) {
+	// 确保存储桶存在
+	if err := s.MakeBucket(bucketName); err != nil {
+		return "", err
+	}
+
+	// 构建对象路径
+	objectPath := fmt.Sprintf("%s/%s", dir, objectName)
+
+	// 上传对象。PutObject内部已支持分块上传[citation:7],partSize参数(10MiB)与你的Java代码对应。
+	uploadInfo, err := s.client.PutObject(context.Background(), bucketName, objectPath, reader, -1, minio.PutObjectOptions{
+		ContentType: "application/octet-stream",
+		PartSize:    10 * 1024 * 1024, // 10 MiB
+	})
+	if err != nil {
+		return "", fmt.Errorf("failed to upload file '%s' to bucket '%s': %w", objectPath, bucketName, err)
+	}
+
+	// 返回路径,这里返回对象在桶内的完整键(Key),你也可以选择返回一个可访问的URL
+	// 格式类似于Java版本返回的 "/bucket/dir/objectName"
+	return fmt.Sprintf("/%s/%s", bucketName, uploadInfo.Key), nil
+}
+
+// GetFileURL 获取文件的预签名URL(用于临时访问)
+func (s *MinioService) GetFileURL(bucketName, objectName string, expiry time.Duration) (string, error) {
+	// 生成一个 presigned GET URL,默认7天有效[citation:2]
+	if expiry == 0 {
+		expiry = 7 * 24 * time.Hour
+	}
+	url, err := s.client.PresignedGetObject(context.Background(), bucketName, objectName, expiry, nil)
+	if err != nil {
+		return "", fmt.Errorf("failed to generate URL for '%s/%s': %w", bucketName, objectName, err)
+	}
+	return url.String(), nil
+}
+
+// DownloadFile 下载文件,返回一个读取流[citation:8]
+func (s *MinioService) DownloadFile(bucketName, objectName string) (*minio.Object, error) {
+	reader, err := s.client.GetObject(context.Background(), bucketName, objectName, minio.GetObjectOptions{})
+	if err != nil {
+		return nil, fmt.Errorf("failed to download file '%s' from bucket '%s': %w", objectName, bucketName, err)
+	}
+	// 注意:调用者需要负责关闭这个 reader (defer reader.Close())
+	return reader, nil
+}
+
+// RemoveFile 删除文件
+func (s *MinioService) RemoveFile(bucketName, objectName string) error {
+	err := s.client.RemoveObject(context.Background(), bucketName, objectName, minio.RemoveObjectOptions{})
+	if err != nil {
+		return fmt.Errorf("failed to remove file '%s' from bucket '%s': %w", objectName, bucketName, err)
+	}
+	return nil
+}
+
+// IsObjectExist 检查对象是否存在
+func (s *MinioService) IsObjectExist(bucketName, objectName string) (bool, error) {
+	_, err := s.client.StatObject(context.Background(), bucketName, objectName, minio.StatObjectOptions{})
+	if err != nil {
+		// 尝试将错误转换为MinIO的错误响应
+		errResp := minio.ToErrorResponse(err)
+		if errResp.Code == "NoSuchKey" {
+			return false, nil
+		}
+		return false, fmt.Errorf("failed to stat object '%s' in bucket '%s': %w", objectName, bucketName, err)
+	}
+	return true, nil
+}

+ 126 - 0
services/pool.go

@@ -0,0 +1,126 @@
+package services
+
+import (
+	// "context"
+	"fmt"
+	"net/url"
+	"sync"
+	"time"
+
+	"github.com/jmoiron/sqlx"
+
+	_ "github.com/go-sql-driver/mysql" // MySQL驱动(匿名导入)
+)
+
+// Config 数据库连接配置
+type Config struct {
+	User            string        // 用户名
+	Password        string        // 密码
+	Host            string        // 主机地址(如localhost)
+	Port            int           // 端口(默认3306)
+	DBName          string        // 数据库名
+	Charset         string        // 字符集(默认utf8mb4)
+	ParseTime       bool          // 是否解析时间(必须为true,否则time.Time无法解析)
+	Loc             string        // 时区(默认Local或Asia/Shanghai)
+	MaxOpenConns    int           // 最大打开连接数(默认100)
+	MaxIdleConns    int           // 最大空闲连接数(默认20)
+	ConnMaxLifetime time.Duration // 连接最大存活时间(默认1小时)
+	ConnMaxIdleTime time.Duration // 连接最大空闲时间(默认30分钟)
+}
+
+// DefaultConfig 返回默认配置(可直接修改字段覆盖)
+func DefaultConfig(user string, password string, host string, port int, dbname string) *Config {
+
+	escapedPassword := url.QueryEscape(password)
+	return &Config{
+		User:            user,
+		Password:        escapedPassword, // 替换为你的MySQL密码
+		Host:            host,
+		DBName:          dbname,
+		Port:            port,
+		Charset:         "utf8mb4",
+		ParseTime:       true,
+		Loc:             "Local",
+		MaxOpenConns:    20,
+		MaxIdleConns:    5,
+		ConnMaxLifetime: 1 * time.Hour,
+		ConnMaxIdleTime: 30 * time.Minute,
+	}
+}
+
+// DSN 生成MySQL连接字符串(Data Source Name)
+func (c *Config) DSN() string {
+	return fmt.Sprintf(
+		"%s:%s@tcp(%s:%d)/%s?charset=%s&parseTime=%t&loc=%s",
+		c.User,
+		c.Password,
+		c.Host,
+		c.Port,
+		c.DBName,
+		c.Charset,
+		c.ParseTime,
+		c.Loc,
+	)
+}
+
+// NewPool 初始化MySQL连接池
+// 返回*sql.DB(Go内置连接池),使用后需调用Close()释放
+func NewPool(config *Config) *sqlx.DB {
+
+	// 打开数据库连接(不会立即建立连接,只是初始化连接池)
+	db, err := sqlx.Open("mysql", config.DSN())
+	if err != nil {
+		build_time_str := time.Now().Format(time.DateTime)
+		fmt.Printf("%s open db failed:======> %v\n", build_time_str, err)
+		return nil
+	}
+
+	// 设置连接池参数
+	db.SetMaxOpenConns(config.MaxOpenConns)       // 控制并发连接数
+	db.SetMaxIdleConns(config.MaxIdleConns)       // 保持空闲连接,减少创建开销
+	db.SetConnMaxLifetime(config.ConnMaxLifetime) // 避免连接被MySQL主动断开
+	db.SetConnMaxIdleTime(config.ConnMaxIdleTime) // 清理长时间空闲的连接
+
+	// 测试连接有效性
+	if err := db.Ping(); err != nil {
+		db.Close() // 测试失败时关闭连接池
+		build_time_str := time.Now().Format(time.DateTime)
+		fmt.Printf("%s ping db failed:======> %v\n", build_time_str, err)
+		return nil
+	}
+	build_time := time.Now()
+	build_time_str := build_time.Format(time.DateTime)
+	fmt.Printf("%s mysql数据库连接池初始化成功\n", build_time_str)
+	warmUpConnectionPoolConcurrently(db, 5)
+	return db
+}
+
+func warmUpConnectionPoolConcurrently(db *sqlx.DB, warmUpCount int) error {
+	var wg sync.WaitGroup
+	errors := make(chan error, warmUpCount)
+
+	for i := 0; i < warmUpCount; i++ {
+		wg.Add(1)
+		go func() {
+			defer wg.Done()
+
+			var dummy int
+			err := db.QueryRow("SELECT 1").Scan(&dummy)
+			if err != nil {
+				errors <- fmt.Errorf("连接预热失败: %v", err)
+			}
+		}()
+	}
+
+	wg.Wait()
+	close(errors)
+
+	// 检查是否有错误
+	for err := range errors {
+		return err
+	}
+	build_time := time.Now()
+	build_time_str := build_time.Format(time.DateTime)
+	fmt.Printf("%s 并发预热 %d 个连接成功\n", build_time_str, warmUpCount)
+	return nil
+}

+ 94 - 0
services/user.go

@@ -0,0 +1,94 @@
+package services
+
+import (
+	"fmt"
+	"sync"
+	"time"
+)
+
+var (
+
+	// 登录尝试记录
+	loginAttempts = struct {
+		sync.RWMutex
+		m map[string]AttemptInfo
+	}{m: make(map[string]AttemptInfo)}
+)
+
+// AttemptInfo 登录尝试信息
+type AttemptInfo struct {
+	Count       int
+	FirstTry    time.Time
+	LockedUntil *time.Time
+}
+
+// CheckLoginAttempts 检查登录尝试次数
+func CheckLoginAttempts(identifier string) (bool, string) {
+	loginAttempts.RLock()
+	info, exists := loginAttempts.m[identifier]
+	loginAttempts.RUnlock()
+
+	if !exists {
+		return true, ""
+	}
+
+	// 检查是否被锁定
+	if info.LockedUntil != nil && time.Now().Before(*info.LockedUntil) {
+		remaining := info.LockedUntil.Sub(time.Now())
+		return false, fmt.Sprintf("账户已锁定,请%.0f分钟后再试", remaining.Minutes())
+	}
+
+	// 检查尝试次数
+	if info.Count >= 5 {
+		// 锁定30分钟
+		lockedUntil := time.Now().Add(30 * time.Minute)
+		info.LockedUntil = &lockedUntil
+		loginAttempts.Lock()
+		loginAttempts.m[identifier] = info
+		loginAttempts.Unlock()
+
+		return false, "尝试次数过多,账户已锁定30分钟"
+	}
+
+	return true, ""
+}
+
+// RecordLoginAttempt 记录登录尝试
+func RecordLoginAttempt(identifier string, success bool) {
+	loginAttempts.Lock()
+	defer loginAttempts.Unlock()
+
+	info, exists := loginAttempts.m[identifier]
+
+	if success {
+		// 登录成功,清除记录
+		delete(loginAttempts.m, identifier)
+		return
+	}
+
+	if !exists {
+		// 第一次失败
+		loginAttempts.m[identifier] = AttemptInfo{
+			Count:    1,
+			FirstTry: time.Now(),
+		}
+	} else {
+		// 增加失败次数
+		info.Count++
+		loginAttempts.m[identifier] = info
+
+		// 如果30分钟内失败5次,锁定账户
+		if info.Count >= 5 && time.Since(info.FirstTry) <= 30*time.Minute {
+			lockedUntil := time.Now().Add(30 * time.Minute)
+			info.LockedUntil = &lockedUntil
+			loginAttempts.m[identifier] = info
+		}
+	}
+
+	// 清理30分钟前的记录
+	for key, attemptInfo := range loginAttempts.m {
+		if time.Since(attemptInfo.FirstTry) > 30*time.Minute {
+			delete(loginAttempts.m, key)
+		}
+	}
+}

+ 169 - 0
utils/a_test.go

@@ -0,0 +1,169 @@
+package utils
+
+import (
+    "context"
+    "fmt"
+    "time"
+
+    "github.com/apache/rocketmq-client-go/v2"
+    "github.com/apache/rocketmq-client-go/v2/primitive"
+    "github.com/apache/rocketmq-client-go/v2/producer"
+)
+
+// RocketMQProducer RocketMQ生产者结构体
+type RocketMQProducer struct {
+    producer rocketmq.Producer
+    options  []producer.Option
+}
+
+// NewRocketMQProducer 创建生产者
+func NewRocketMQProducer(nameServer []string, groupName string) (*RocketMQProducer, error) {
+    // 生产者配置选项
+    opts := []producer.Option{
+        producer.WithNameServer(nameServer),
+        producer.WithRetry(2), // 重试次数
+        producer.WithGroupName(groupName),
+    }
+
+    // 创建生产者实例
+    p, err := rocketmq.NewProducer(opts...)
+    if err != nil {
+        return nil, fmt.Errorf("创建生产者失败: %v", err)
+    }
+
+    return &RocketMQProducer{
+        producer: p,
+        options:  opts,
+    }, nil
+}
+
+// Start 启动生产者
+func (p *RocketMQProducer) Start() error {
+    if err := p.producer.Start(); err != nil {
+        return fmt.Errorf("启动生产者失败: %v", err)
+    }
+    fmt.Println("RocketMQ生产者启动成功")
+    return nil
+}
+
+// Shutdown 关闭生产者
+func (p *RocketMQProducer) Shutdown() error {
+    if err := p.producer.Shutdown(); err != nil {
+        return fmt.Errorf("关闭生产者失败: %v", err)
+    }
+    fmt.Println("RocketMQ生产者已关闭")
+    return nil
+}
+
+// SendSyncMessage 同步发送消息
+func (p *RocketMQProducer) SendSyncMessage(topic, body string, tags ...string) (*primitive.SendResult, error) {
+    tag := "default"
+    if len(tags) > 0 {
+        tag = tags[0]
+    }
+
+    msg := &primitive.Message{
+        Topic: topic,
+        Body:  []byte(body),
+    }
+    msg.WithTag(tag)
+
+    // 设置消息属性(可选)
+    msg.WithProperties(map[string]string{
+        "source": "go-producer",
+        "time":   time.Now().Format("2006-01-02 15:04:05"),
+    })
+
+    // 同步发送消息
+    res, err := p.producer.SendSync(context.Background(), msg)
+    if err != nil {
+        return nil, fmt.Errorf("发送消息失败: %v", err)
+    }
+
+    return res, nil
+}
+
+// SendAsyncMessage 异步发送消息
+func (p *RocketMQProducer) SendAsyncMessage(topic, body string, callback func(*primitive.SendResult, error), tags ...string) error {
+    tag := "default"
+    if len(tags) > 0 {
+        tag = tags[0]
+    }
+
+    msg := &primitive.Message{
+        Topic: topic,
+        Body:  []byte(body),
+    }
+    msg.WithTag(tag)
+
+    // 异步发送消息
+    err := p.producer.SendAsync(context.Background(), func(ctx context.Context, result *primitive.SendResult, err error) {
+        if callback != nil {
+            callback(result, err)
+        }
+    }, msg)
+
+    if err != nil {
+        return fmt.Errorf("异步发送消息失败: %v", err)
+    }
+
+    return nil
+}
+
+// SendDelayMessage 发送延迟消息
+func (p *RocketMQProducer) SendDelayMessage(topic, body string, delayLevel int, tags ...string) (*primitive.SendResult, error) {
+    tag := "default"
+    if len(tags) > 0 {
+        tag = tags[0]
+    }
+
+    msg := &primitive.Message{
+        Topic: topic,
+        Body:  []byte(body),
+    }
+    msg.WithTag(tag)
+    
+    // 设置延迟级别(1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h)
+    msg.WithDelayTimeLevel(delayLevel)
+
+    res, err := p.producer.SendSync(context.Background(), msg)
+    if err != nil {
+        return nil, fmt.Errorf("发送延迟消息失败: %v", err)
+    }
+
+    return res, nil
+}
+
+// SendOrderMessage 发送顺序消息
+func (p *RocketMQProducer) SendOrderMessage(topic, body, shardingKey string, tags ...string) (*primitive.SendResult, error) {
+    tag := "default"
+    if len(tags) > 0 {
+        tag = tags[0]
+    }
+
+    msg := &primitive.Message{
+        Topic: topic,
+        Body:  []byte(body),
+    }
+    msg.WithTag(tag)
+	msg.WithShardingKey(shardingKey)
+
+	// 添加顺序相关属性
+    properties := msg.GetProperties()
+    if properties == nil {
+        properties = make(map[string]string)
+    }
+    properties["ORDER_KEY"] = shardingKey
+    msg.WithProperties(properties)
+
+    // 使用队列选择器保证消息顺序
+	// res, err := p.producer.SendSync(context.Background(), msg, 
+    //     producer.WithQueueSelector(p.selector))
+    res, err := p.producer.SendSync(context.Background(), msg)
+    
+    if err != nil {
+        return nil, fmt.Errorf("发送顺序消息失败: %v", err)
+    }
+
+    return res, nil
+}

+ 99 - 0
utils/b_test.go

@@ -0,0 +1,99 @@
+package utils
+
+import (
+	"fmt"
+	"log"
+	"os"
+	"os/signal"
+	"syscall"
+	"testing"
+	"time"
+
+	"github.com/apache/rocketmq-client-go/v2/primitive"
+)
+
+func aaaaa(t *testing.T) {
+    // 配置RocketMQ NameServer地址
+    nameServer := []string{"172.16.1.150:9876"} // 替换为实际地址
+    groupName := "test-producer-group"
+
+    // 创建生产者
+    producer, err := NewRocketMQProducer(nameServer, groupName)
+    if err != nil {
+        log.Fatalf("创建生产者失败: %v", err)
+    }
+
+    // 启动生产者
+    if err := producer.Start(); err != nil {
+        log.Fatalf("启动生产者失败: %v", err)
+    }
+    defer producer.Shutdown()
+
+    // 示例1:同步发送消息
+    fmt.Println("=== 同步发送消息 ===")
+    result, err := producer.SendSyncMessage("TestTopic", "Hello RocketMQ!", "TestTag")
+    if err != nil {
+        log.Printf("同步发送失败: %v", err)
+    } else {
+        fmt.Printf("同步发送成功: %+v\n", result)
+    }
+
+    // 示例2:异步发送消息
+    fmt.Println("\n=== 异步发送消息 ===")
+    err = producer.SendAsyncMessage("TestTopic", "Hello Async RocketMQ!", func(result *primitive.SendResult, err error) {
+        if err != nil {
+            fmt.Printf("异步发送失败: %v\n", err)
+        } else {
+            fmt.Printf("异步发送成功: %+v\n", result)
+        }
+    }, "AsyncTag")
+    
+    if err != nil {
+        log.Printf("启动异步发送失败: %v", err)
+    }
+
+    // 示例3:发送延迟消息(延迟10秒)
+    fmt.Println("\n=== 发送延迟消息 ===")
+    result, err = producer.SendDelayMessage("DelayTopic", "This is a delayed message", 3, "DelayTag")
+    if err != nil {
+        log.Printf("发送延迟消息失败: %v", err)
+    } else {
+        fmt.Printf("延迟消息发送成功: %+v\n", result)
+    }
+
+    // 示例4:发送顺序消息
+    fmt.Println("\n=== 发送顺序消息 ===")
+    for i := 1; i <= 5; i++ {
+        body := fmt.Sprintf("Order message %d", i)
+        result, err = producer.SendOrderMessage("OrderTopic", body, "order-key-1", "OrderTag")
+        if err != nil {
+            log.Printf("发送顺序消息失败: %v", err)
+        } else {
+            fmt.Printf("顺序消息发送成功 [%d]: %+v\n", i, result)
+        }
+        time.Sleep(100 * time.Millisecond)
+    }
+
+    // 等待中断信号
+    waitForInterruptA()
+}
+
+// waitForInterrupt 等待中断信号
+func waitForInterruptA() {
+    sigChan := make(chan os.Signal, 1)
+    signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
+    <-sigChan
+    fmt.Println("\n接收到中断信号,程序退出...")
+}
+
+
+
+
+
+
+
+
+
+
+
+

+ 89 - 0
utils/c_test.go

@@ -0,0 +1,89 @@
+package utils
+
+
+
+import (
+	"fmt"
+	"log"
+	"testing"
+	"time"
+	"context"
+
+	"github.com/apache/rocketmq-client-go/v2/primitive"
+	"github.com/apache/rocketmq-client-go/v2/consumer"
+)
+
+
+func bbbb(t *testing.T) {
+    // 示例1:创建并发消费者
+    fmt.Println("=== 示例1:并发消费者 ===")
+    exampleConcurrentConsumer()
+    
+    // 示例2:创建顺序消费者
+    // fmt.Println("\n=== 示例2:顺序消费者 ===")
+    // exampleOrderlyConsumer()
+    
+    // 示例3:使用消费者管理器
+    // fmt.Println("\n=== 示例3:消费者管理器 ===")
+    // exampleConsumerManager()
+    
+    // 等待中断信号
+    // waitForInterruptB()
+}
+
+// 示例1:并发消费者
+func exampleConcurrentConsumer() {
+    // 直接使用rocketmq-client-go的API创建消费者
+    c, err := consumer.NewPushConsumer(
+        // consumer.WithGroupName(fmt.Sprintf("consumer-group-%d", time.Now().Unix())), // 使用唯一组名
+        consumer.WithGroupName(fmt.Sprintf("consumer-group")), // 使用唯一组名
+        consumer.WithNameServer([]string{"172.16.1.150:9876"}),
+        consumer.WithConsumerModel(consumer.Clustering),
+        consumer.WithConsumeFromWhere(consumer.ConsumeFromFirstOffset), // 从第一条消息开始消费
+        consumer.WithConsumerOrder(false),
+    )
+    if err != nil {
+        log.Fatalf("创建消费者失败: %v", err)
+    }
+    
+    // 创建信号量来控制消费者生命周期
+    ctx, cancel := context.WithCancel(context.Background())
+    defer cancel()
+    
+    // 消息计数器
+    messageCount := 0
+    
+    // 订阅主题
+    err = c.Subscribe("TestTopic", consumer.MessageSelector{
+        Type:       consumer.TAG,
+        Expression: "*", // 接收所有标签的消息
+    }, func(ctx context.Context, msgs ...*primitive.MessageExt) (consumer.ConsumeResult, error) {
+        for _, msg := range msgs {
+            messageCount++
+            fmt.Printf("[%s] 收到消息[%d]: Topic=%s, Tag=%s, Body=%s\n", 
+                time.Now().Format("15:04:05"), messageCount, 
+                msg.Topic, msg.GetTags(), string(msg.Body))
+        }
+        return consumer.ConsumeSuccess, nil
+    })
+    if err != nil {
+        log.Fatalf("订阅失败: %v", err)
+    }
+    
+    // 启动消费者
+    err = c.Start()
+    if err != nil {
+        log.Fatalf("启动失败: %v", err)
+    }
+    
+    defer c.Shutdown()
+    fmt.Println("消费者已启动...")
+    
+    // 等待上下文取消或超时
+    select {
+    case <-ctx.Done():
+        fmt.Println("上下文取消,退出消费者")
+    case <-time.After(120 * time.Second): // 等待2分钟
+        fmt.Printf("超时退出,共收到 %d 条消息\n", messageCount)
+    }
+}

+ 75 - 0
utils/common.go

@@ -0,0 +1,75 @@
+package utils
+
+import "strings"
+
+func Map[T any, R any](slice []T, mapper func(T) R) []R {
+	result := make([]R, len(slice))
+	for i, item := range slice {
+		result[i] = mapper(item)
+	}
+	return result
+}
+
+func ConvertInterface[T any](data interface{}) T {
+	var zero T
+
+	// 检查 nil
+	if data == nil {
+		println("数据为空")
+		return zero
+	}
+
+	// 类型断言
+	if result, ok := data.(T); ok {
+		// println("断言成功")
+		return result
+	}
+
+	return zero
+}
+
+// 通用的Filter函数
+func Filter[T any](slice []T, predicate func(T) bool) []T {
+	var result []T
+	for _, v := range slice {
+		if predicate(v) {
+			result = append(result, v)
+		}
+	}
+	return result
+}
+
+func SliceSubtract[T comparable](slice1, slice2 []T) []T {
+	exclude := make(map[T]bool)
+	for _, v := range slice2 {
+		exclude[v] = true
+	}
+
+	var result []T
+	for _, v := range slice1 {
+		if !exclude[v] {
+			result = append(result, v)
+		}
+	}
+	return result
+}
+
+func IsEmpty(value *string) bool {
+	if value == nil {
+		return true
+	}
+	if *value == "" {
+		return true
+	}
+	if len(*value) == 0 {
+		return true
+	}
+	if len(strings.TrimSpace(*value)) == 0 {
+		return true
+	}
+	return false
+}
+
+func IsNotEmpty(value *string) bool {
+	return !IsEmpty(value)
+}

+ 19 - 0
utils/log.go

@@ -0,0 +1,19 @@
+package utils
+
+import (
+	"fmt"
+	"runtime"
+	"time"
+)
+
+func PrintSqlErr(err error) {
+	build_time_str := time.Now().Format(time.DateTime)
+	_, file, line, _ := runtime.Caller(1)
+	fmt.Printf("%s SQL执行失败: %s:%d======> \033[31m%v\033[0m\n", build_time_str, file, line, err)
+}
+
+func PrintSearchFileErr(err error) {
+	build_time_str := time.Now().Format(time.DateTime)
+	_, file, line, _ := runtime.Caller(1)
+	fmt.Printf("%s 文件查询失败: %s:%d======> \033[31m%v\033[0m\n", build_time_str, file, line, err)
+}

+ 11 - 0
utils/network_devices.txt

@@ -0,0 +1,11 @@
+IP Address,MAC Address,Hostname,Vendor
+192.168.101.1,FC-94-35-FA-49-6D,N/A,Unknown
+192.168.101.12,54-EF-33-FD-DE-8D,N/A,Unknown
+192.168.101.46,5E-23-7D-66-A5-97,N/A,Unknown
+192.168.101.135,00-F1-F3-12-FB-25,PC-20190603FSMT,Unknown
+192.168.101.152,50-81-40-D1-82-17,N/A,Unknown
+192.168.101.171,54-8C-81-B9-AB-D2,N/A,Unknown
+192.168.101.188,82-8F-4E-3F-37-2E,N/A,Unknown
+192.168.101.224,8C-85-90-AA-2F-19,N/A,Unknown
+192.168.101.233,4A-B6-76-9F-2C-24,N/A,Unknown
+192.168.101.255,FF-FF-FF-FF-FF-FF,N/A,Unknown

+ 2 - 0
utils/postgres_test.go

@@ -0,0 +1,2 @@
+package utils
+

+ 46 - 0
utils/response.go

@@ -0,0 +1,46 @@
+package utils
+
+// APIResponse API响应结构
+type APIResponse struct {
+	Success bool        `json:"success"`
+	Message string      `json:"message"`
+	Data    interface{} `json:"data,omitempty"`
+	Error   string      `json:"error,omitempty"`
+}
+
+// SuccessResponse 成功响应
+func SuccessResponse(message string) APIResponse {
+	return APIResponse{
+		Success: true,
+		Message: message,
+	}
+}
+
+// SuccessWithDataResponse 成功响应(带数据)
+func SuccessWithDataResponse(message string, data interface{}) APIResponse {
+	return APIResponse{
+		Success: true,
+		Message: message,
+		Data:    data,
+	}
+}
+
+// ErrorResponse 错误响应
+func ErrorResponse(message, errorDetail string) APIResponse {
+	resp := APIResponse{
+		Success: false,
+		Message: message,
+	}
+
+	if errorDetail != "" {
+		resp.Error = errorDetail
+	}
+
+	return resp
+}
+
+// // SuccessWithDataResponse 成功响应(带数据)
+// func SuccessWithNoDataResponse(message string, data interface{}) J {
+// 	return data
+
+// }

Dosya farkı çok büyük olduğundan ihmal edildi
+ 1631 - 0
utils/result_set.go


+ 69 - 0
utils/rsa.go

@@ -0,0 +1,69 @@
+package utils
+
+import (
+	"crypto/rand"
+	"crypto/rsa"
+	"crypto/x509"
+	"encoding/base64"
+	"errors"
+	"fmt"
+
+	"golang.org/x/crypto/bcrypt"
+)
+
+const (
+	privateKeyText = "MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEA0vfvyTdGJkdbHkB8mp0f3FE0GYP3AYPaJF7jUd1M0XxFSE2ceK3k2kw20YvQ09NJKk+OMjWQl9WitG9pB6tSCQIDAQABAkA2SimBrWC2/wvauBuYqjCFwLvYiRYqZKThUS3MZlebXJiLB+Ue/gUifAAKIg1avttUZsHBHrop4qfJCwAI0+YRAiEA+W3NK/RaXtnRqmoUUkb59zsZUBLpvZgQPfj1MhyHDz0CIQDYhsAhPJ3mgS64NbUZmGWuuNKp5coY2GIj/zYDMJp6vQIgUueLFXv/eZ1ekgz2Oi67MNCk5jeTF2BurZqNLR3MSmUCIFT3Q6uHMtsB9Eha4u7hS31tj1UWE+D+ADzp59MGnoftAiBeHT7gDMuqeJHPL4b+kC+gzV4FGTfhR9q3tTbklZkD2A=="
+)
+
+// privateKeyText: Base64编码的PKCS#8格式RSA私钥字符串
+// text: Base64编码的密文字符串
+func DecryptByPrivateKey(text string) (string, error) {
+	// 1. Base64解码私钥文本
+	privateKeyBytes, err := base64.StdEncoding.DecodeString(privateKeyText)
+	if err != nil {
+		return "", fmt.Errorf("私钥Base64解码失败: %w", err)
+	}
+
+	// 2. 解析PKCS#8格式的私钥(对应Java的PKCS8EncodedKeySpec)
+	key, err := x509.ParsePKCS8PrivateKey(privateKeyBytes)
+	if err != nil {
+		return "", fmt.Errorf("解析PKCS#8私钥失败: %w", err)
+	}
+
+	// 3. 断言为RSA私钥
+	privateKey, ok := key.(*rsa.PrivateKey)
+	if !ok {
+		return "", errors.New("私钥类型不是RSA")
+	}
+
+	// 4. Base64解密密文
+	cipherText, err := base64.StdEncoding.DecodeString(text)
+	if err != nil {
+		return "", fmt.Errorf("密文Base64解码失败: %w", err)
+	}
+
+	// 5. RSA解密(Java默认是RSA/ECB/PKCS1Padding,对应Go的PKCS1v15填充)
+	plainText, err := rsa.DecryptPKCS1v15(rand.Reader, privateKey, cipherText)
+	if err != nil {
+		return "", fmt.Errorf("RSA解密失败: %w", err)
+	}
+
+	// 6. 转换为字符串返回
+	return string(plainText), nil
+}
+
+// EncodePassword 等价于Java的passwordEncoder.encode(),返回哈希后的密码字符串
+func EncodePassword(rawPassword string) (string, error) {
+	// 生成盐值(cost取值4-31,值越高加密越慢越安全,默认10,与Java BCryptPasswordEncoder一致)
+	salt, err := bcrypt.GenerateFromPassword([]byte(rawPassword), bcrypt.DefaultCost)
+	if err != nil {
+		return "", fmt.Errorf("encode password failed: %w", err)
+	}
+	return string(salt), nil
+}
+
+// VerifyPassword 等价于Java的passwordEncoder.matches(),验证密码是否匹配
+func VerifyPassword(rawPassword, encodedPassword string) bool {
+	err := bcrypt.CompareHashAndPassword([]byte(encodedPassword), []byte(rawPassword))
+	return err == nil
+}

+ 457 - 0
utils/scan_test.go

@@ -0,0 +1,457 @@
+package utils
+
+import (
+    "fmt"
+    "log"
+    "net"
+    "os"
+    "os/exec"
+    "runtime"
+    "sort"
+    "strings"
+    "sync"
+    "time"
+	"testing"
+)
+
+// DeviceInfo 存储设备信息
+type DeviceInfo struct {
+    IP      string
+    MAC     string
+    Hostname string
+    Vendor  string
+}
+
+// MAC厂商前缀映射(部分常见厂商)
+var macVendors = map[string]string{
+    "00:0C:29": "VMware",
+    "00:50:56": "VMware",
+    "00:1A:2B": "Cisco",
+    "00:1C:42": "Apple",
+    "00:1D:4F": "Apple",
+    "00:23:DF": "Apple",
+    "00:25:BC": "Apple",
+    "08:00:27": "VirtualBox",
+    "00:1B:21": "Intel",
+    "00:21:5A": "Intel",
+    "00:22:FA": "Intel",
+    "B8:27:EB": "Raspberry Pi",
+    "DC:A6:32": "Raspberry Pi",
+    "28:16:AD": "TP-Link",
+    "14:CC:20": "TP-Link",
+    "B0:95:8E": "TP-Link",
+    "C8:3A:35": "Tenda",
+    "80:89:17": "Tenda",
+    "A0:F3:C1": "TP-Link",
+    "D4:61:DA": "TP-Link",
+    "C0:56:27": "TP-Link",
+    "F8:1A:67": "TP-Link",
+    "F8:1A:2B": "TP-Link",
+    "E4:8B:7F": "HUAWEI",
+    "00:9A:CD": "HUAWEI",
+    "34:29:8F": "HUAWEI",
+    "80:E6:50": "HUAWEI",
+    "B4:0B:44": "Xiaomi",
+    "E4:46:DA": "Xiaomi",
+    "0C:1D:AF": "Xiaomi",
+    "D8:63:75": "Xiaomi",
+    "D0:C7:C0": "Xiaomi",
+    "FC:64:BA": "Samsung",
+    "5C:49:79": "Samsung",
+    "D0:33:11": "Samsung",
+    "38:48:4C": "Samsung",
+    "88:36:6C": "Samsung",
+    "00:1E:65": "Samsung",
+    "C0:BD:D1": "D-Link",
+    "1C:AF:05": "D-Link",
+    "BC:F6:85": "D-Link",
+}
+
+// getLocalIP 获取本地IP地址
+func getLocalIP() (net.IP, *net.IPNet) {
+    conn, err := net.Dial("udp", "8.8.8.8:80")
+    if err != nil {
+        log.Fatal(err)
+    }
+    defer conn.Close()
+    
+    localAddr := conn.LocalAddr().(*net.UDPAddr)
+    
+    interfaces, err := net.Interfaces()
+    if err != nil {
+        log.Fatal(err)
+    }
+    
+    for _, iface := range interfaces {
+        addrs, err := iface.Addrs()
+        if err != nil {
+            continue
+        }
+        
+        for _, addr := range addrs {
+            switch v := addr.(type) {
+            case *net.IPNet:
+                if v.IP.To4() != nil && v.IP.Equal(localAddr.IP) {
+                    return v.IP, v
+                }
+            }
+        }
+    }
+    
+    return nil, nil
+}
+
+// getIPRange 获取IP范围
+func getIPRange(ipNet *net.IPNet) ([]string, error) {
+    var ips []string
+    
+    // 获取网络地址和掩码
+    mask := ipNet.Mask
+    network := ipNet.IP.To4()
+    
+    if network == nil {
+        return nil, fmt.Errorf("IPv6 not supported")
+    }
+    
+    // 计算网络大小
+    ones, bits := mask.Size()
+    totalIPs := 1 << (bits - ones)
+    
+    // 排除网络地址和广播地址
+    start := 1
+    end := totalIPs - 2
+    
+    if totalIPs <= 2 { // /31 或 /32 网络
+        start = 0
+        end = totalIPs - 1
+    }
+    
+    // 生成IP列表
+    for i := start; i <= end; i++ {
+        ip := make(net.IP, len(network))
+        copy(ip, network)
+        
+        // 计算当前IP
+        for j := len(ip) - 1; j >= 0; j-- {
+            ip[j] += byte(i >> uint((len(ip)-1-j)*8))
+        }
+        
+        ips = append(ips, ip.String())
+    }
+    
+    return ips, nil
+}
+
+// pingIP 使用ping检测主机是否在线
+func pingIP(ip string) bool {
+    var cmd *exec.Cmd
+    
+    switch runtime.GOOS {
+    case "windows":
+        cmd = exec.Command("ping", "-n", "1", "-w", "1000", ip)
+    case "darwin":
+        cmd = exec.Command("ping", "-c", "1", "-W", "1", ip)
+    default: // Linux
+        cmd = exec.Command("ping", "-c", "1", "-W", "1", ip)
+    }
+    
+    err := cmd.Run()
+    return err == nil
+}
+
+// getARPInfo 获取ARP信息(跨平台)
+func getARPInfo(ip string) (string, string, error) {
+    var mac, hostname string
+    
+    // 尝试获取主机名
+    names, err := net.LookupAddr(ip)
+    if err == nil && len(names) > 0 {
+        hostname = strings.TrimSuffix(names[0], ".")
+    }
+    
+    // 获取MAC地址(不同系统的实现)
+    switch runtime.GOOS {
+    case "windows":
+        mac = getMACWindows(ip)
+    case "linux":
+        mac = getMACLinux(ip)
+    case "darwin":
+        mac = getMACDarwin(ip)
+    default:
+        // 通用方法:尝试读取系统ARP表
+        mac = getMACFromARPTable(ip)
+    }
+    
+    return mac, hostname, nil
+}
+
+// getMACFromARPTable 从系统ARP表获取MAC
+func getMACFromARPTable(ip string) string {
+    var cmd *exec.Cmd
+    
+    switch runtime.GOOS {
+    case "windows":
+        cmd = exec.Command("arp", "-a", ip)
+    case "linux", "darwin":
+        cmd = exec.Command("arp", "-n", ip)
+    default:
+        return ""
+    }
+    
+    output, err := cmd.Output()
+    if err != nil {
+        return ""
+    }
+    
+    outputStr := string(output)
+    
+    // 解析输出获取MAC地址
+    lines := strings.Split(outputStr, "\n")
+    for _, line := range lines {
+        if strings.Contains(line, ip) {
+            parts := strings.Fields(line)
+            if len(parts) >= 3 {
+                mac := strings.ToUpper(parts[1])
+                // 验证MAC地址格式
+                if isValidMAC(mac) {
+                    return mac
+                }
+            }
+        }
+    }
+    
+    return ""
+}
+
+// 平台特定的MAC获取函数
+func getMACWindows(ip string) string {
+    cmd := exec.Command("arp", "-a", ip)
+    output, err := cmd.Output()
+    if err != nil {
+        return ""
+    }
+    
+    lines := strings.Split(string(output), "\n")
+    for _, line := range lines {
+        if strings.Contains(line, ip) {
+            parts := strings.Fields(line)
+            if len(parts) >= 2 {
+                mac := strings.ToUpper(parts[1])
+                if isValidMAC(mac) {
+                    return mac
+                }
+            }
+        }
+    }
+    
+    return ""
+}
+
+func getMACLinux(ip string) string {
+    cmd := exec.Command("arp", "-n", ip)
+    output, err := cmd.Output()
+    if err != nil {
+        return ""
+    }
+    
+    lines := strings.Split(string(output), "\n")
+    for _, line := range lines {
+        if strings.Contains(line, ip) {
+            parts := strings.Fields(line)
+            if len(parts) >= 3 {
+                mac := strings.ToUpper(parts[2])
+                if isValidMAC(mac) {
+                    return mac
+                }
+            }
+        }
+    }
+    
+    return ""
+}
+
+func getMACDarwin(ip string) string {
+    cmd := exec.Command("arp", "-n", ip)
+    output, err := cmd.Output()
+    if err != nil {
+        return ""
+    }
+    
+    lines := strings.Split(string(output), "\n")
+    for _, line := range lines {
+        if strings.Contains(line, ip) {
+            parts := strings.Fields(line)
+            if len(parts) >= 4 {
+                mac := strings.ToUpper(parts[3])
+                if isValidMAC(mac) {
+                    return mac
+                }
+            }
+        }
+    }
+    
+    return ""
+}
+
+// isValidMAC 验证MAC地址格式
+func isValidMAC(mac string) bool {
+    // 简单的MAC地址格式验证
+    return len(mac) == 17 && strings.Count(mac, ":") == 5 ||
+           len(mac) == 17 && strings.Count(mac, "-") == 5
+}
+
+// getVendorFromMAC 根据MAC地址前缀获取厂商信息
+func getVendorFromMAC(mac string) string {
+    if len(mac) < 8 {
+        return "Unknown"
+    }
+    
+    prefix := strings.ToUpper(mac[:8])
+    for vendorPrefix, vendor := range macVendors {
+        if strings.HasPrefix(prefix, vendorPrefix) {
+            return vendor
+        }
+    }
+    
+    return "Unknown"
+}
+
+// scanIP 扫描单个IP
+func scanIP(ip string, results chan<- DeviceInfo, wg *sync.WaitGroup) {
+    defer wg.Done()
+    
+    // Ping检测
+    if !pingIP(ip) {
+        return
+    }
+    
+    // 获取MAC和主机名
+    mac, hostname, err := getARPInfo(ip)
+    if err != nil || mac == "" {
+        return
+    }
+    
+    // 获取厂商信息
+    vendor := getVendorFromMAC(mac)
+    
+    // 格式化MAC地址
+    mac = strings.ToUpper(mac)
+    
+    results <- DeviceInfo{
+        IP:       ip,
+        MAC:      mac,
+        Hostname: hostname,
+        Vendor:   vendor,
+    }
+}
+
+// printTable 打印结果表格
+func printTable(devices []DeviceInfo) {
+    fmt.Printf("\n%-18s %-20s %-40s %s\n", "IP Address", "MAC Address", "Hostname", "Vendor")
+    fmt.Println(strings.Repeat("-", 100))
+    
+    for _, device := range devices {
+        hostname := device.Hostname
+        if hostname == "" {
+            hostname = "N/A"
+        }
+        fmt.Printf("%-18s %-20s %-40s %s\n", device.IP, device.MAC, hostname, device.Vendor)
+    }
+    
+    fmt.Printf("\nTotal devices found: %d\n", len(devices))
+}
+
+func TestS(t *testing.T) {
+    fmt.Println("=== Network Scanner ===")
+    fmt.Println("Scanning local network...")
+    
+    // 获取本地IP和网络
+    localIP, ipNet := getLocalIP()
+    if localIP == nil {
+        log.Fatal("Failed to get local IP address")
+    }
+    
+    fmt.Printf("Local IP: %s\n", localIP)
+    fmt.Printf("Network: %s\n", ipNet.String())
+    
+    // 获取要扫描的IP列表
+    ips, err := getIPRange(ipNet)
+    if err != nil {
+        log.Fatal(err)
+    }
+    
+    fmt.Printf("Scanning %d IP addresses...\n", len(ips))
+    
+    // 创建结果通道和等待组
+    results := make(chan DeviceInfo, 100)
+    var wg sync.WaitGroup
+    
+    // 启动扫描协程
+    startTime := time.Now()
+    maxConcurrent := 50 // 最大并发数
+    semaphore := make(chan struct{}, maxConcurrent)
+    
+    for _, ip := range ips {
+        wg.Add(1)
+        semaphore <- struct{}{}
+        
+        go func(targetIP string) {
+            defer func() { <-semaphore }()
+            scanIP(targetIP, results, &wg)
+        }(ip)
+    }
+    
+    // 等待所有扫描完成
+    go func() {
+        wg.Wait()
+        close(results)
+    }()
+    
+    // 收集结果
+    var devices []DeviceInfo
+    for device := range results {
+        devices = append(devices, device)
+    }
+    
+    // 按IP排序
+    sort.Slice(devices, func(i, j int) bool {
+        ip1 := net.ParseIP(devices[i].IP)
+        ip2 := net.ParseIP(devices[j].IP)
+        return ip1.To4()[0] < ip2.To4()[0] ||
+               (ip1.To4()[0] == ip2.To4()[0] && ip1.To4()[1] < ip2.To4()[1]) ||
+               (ip1.To4()[0] == ip2.To4()[0] && ip1.To4()[1] == ip2.To4()[1] && ip1.To4()[2] < ip2.To4()[2]) ||
+               (ip1.To4()[0] == ip2.To4()[0] && ip1.To4()[1] == ip2.To4()[1] && ip1.To4()[2] == ip2.To4()[2] && ip1.To4()[3] < ip2.To4()[3])
+    })
+    
+    // 显示结果
+    printTable(devices)
+    
+    duration := time.Since(startTime)
+    fmt.Printf("Scan completed in %.2f seconds\n", duration.Seconds())
+    
+    // 保存结果到文件(可选)
+    if len(devices) > 0 {
+        saveToFile(devices)
+    }
+}
+
+// saveToFile 保存结果到文件
+func saveToFile(devices []DeviceInfo) {
+    file, err := os.Create("network_devices.txt")
+    if err != nil {
+        log.Printf("Failed to create file: %v", err)
+        return
+    }
+    defer file.Close()
+    
+    file.WriteString("IP Address,MAC Address,Hostname,Vendor\n")
+    for _, device := range devices {
+        hostname := device.Hostname
+        if hostname == "" {
+            hostname = "N/A"
+        }
+        file.WriteString(fmt.Sprintf("%s,%s,%s,%s\n", device.IP, device.MAC, hostname, device.Vendor))
+    }
+    
+    fmt.Println("Results saved to network_devices.txt")
+}