Compare commits

...

19 Commits

Author SHA1 Message Date
Bobo
bed43b3576 feat: query_parser support new one filter parse pattern. 2025-06-24 10:48:26 +08:00
Bobo
b9665d4f9b feat: entgo. 2025-06-24 00:25:03 +08:00
Bobo
1d53d3af4c feat: query_parser. 2025-06-23 23:46:37 +08:00
Bobo
a502539417 feat: query_parser + stringcase. 2025-06-23 23:44:49 +08:00
Bobo
0cd2eb95f2 feat: query_parser + stringcase. 2025-06-23 23:44:34 +08:00
Bobo
2e38fe77d7 feat: query_parser + stringcase. 2025-06-23 23:44:00 +08:00
Bobo
55c80024c2 feat: query_parser + stringcase. 2025-06-23 23:26:59 +08:00
Bobo
de36ab695d feat: refactor type mapper. 2025-06-16 14:49:21 +08:00
Bobo
a1b8326783 feat: fix mapper bug. 2025-06-16 14:17:19 +08:00
Bobo
38f3cb8a3b feat: fix mapper bug. 2025-06-16 11:59:09 +08:00
Bobo
be5aa063df feat: password. 2025-06-14 23:28:15 +08:00
Bobo
3ca4745bac feat: refactor. 2025-06-14 21:44:33 +08:00
Bobo
83c2ec5048 feat: mapper. 2025-06-12 08:59:14 +08:00
Bobo
f48c7373d7 feat: mapper. 2025-06-11 23:03:10 +08:00
Bobo
41c8b6d9dd feat: name generator. 2025-06-11 15:31:59 +08:00
Bobo
b2efe51e40 feat: id utils 2025-06-04 22:21:48 +08:00
Bobo
9c450415a2 feat: id utils 2025-06-04 22:17:05 +08:00
Bobo
99c8f6e4c1 feat: id utils 2025-06-04 15:52:46 +08:00
Bobo
3f713e489b feat: time util 2025-05-29 00:15:37 +08:00
97 changed files with 196060 additions and 487 deletions

View File

@@ -6,7 +6,7 @@ toolchain go1.24.3
require (
github.com/jinzhu/copier v0.4.0
github.com/tx7do/go-utils v1.1.25
github.com/tx7do/go-utils v1.1.27
)
require (

View File

@@ -1,27 +1,31 @@
module github.com/tx7do/go-utils/entgo
go 1.23.0
go 1.24
toolchain go1.23.2
toolchain go1.24.4
replace github.com/tx7do/go-utils/id => ../id
require (
entgo.io/contrib v0.6.0
entgo.io/ent v0.14.4
github.com/XSAM/otelsql v0.38.0
github.com/XSAM/otelsql v0.39.0
github.com/go-kratos/kratos/v2 v2.8.4
github.com/google/uuid v1.6.0
github.com/stretchr/testify v1.10.0
github.com/tx7do/go-utils v1.1.24
github.com/tx7do/go-utils v1.1.29
github.com/tx7do/go-utils/id v0.0.2
go.opentelemetry.io/otel v1.36.0
google.golang.org/protobuf v1.36.6
)
require (
ariga.io/atlas v0.33.1 // indirect
ariga.io/atlas v0.35.0 // indirect
github.com/agext/levenshtein v1.2.3 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/bmatcuk/doublestar v1.3.4 // indirect
github.com/bufbuild/protocompile v0.14.1 // indirect
github.com/bwmarrin/snowflake v0.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
@@ -30,20 +34,24 @@ require (
github.com/google/go-cmp v0.7.0 // indirect
github.com/hashicorp/hcl/v2 v2.23.0 // indirect
github.com/jhump/protoreflect v1.17.0 // indirect
github.com/lithammer/shortuuid/v4 v4.2.0 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rs/xid v1.6.0 // indirect
github.com/segmentio/ksuid v1.0.4 // indirect
github.com/sony/sonyflake v1.2.1 // indirect
github.com/zclconf/go-cty v1.16.3 // indirect
github.com/zclconf/go-cty-yaml v1.1.0 // indirect
go.mongodb.org/mongo-driver v1.17.4 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/otel/metric v1.36.0 // indirect
go.opentelemetry.io/otel/trace v1.36.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/mod v0.24.0 // indirect
golang.org/x/sync v0.14.0 // indirect
golang.org/x/text v0.25.0 // indirect
golang.org/x/tools v0.33.0 // indirect
golang.org/x/mod v0.25.0 // indirect
golang.org/x/sync v0.15.0 // indirect
golang.org/x/text v0.26.0 // indirect
golang.org/x/tools v0.34.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View File

@@ -1,13 +1,13 @@
ariga.io/atlas v0.33.1 h1:m3rn+m2jkfOZDqdZrgTDvDWh9G3Ldr77IElFDGEwDPk=
ariga.io/atlas v0.33.1/go.mod h1:WJesu2UCpGQvgUh3oVP94EiRT61nNy1W/VN5g+vqP1I=
ariga.io/atlas v0.35.0 h1:tzco6CEZm1/jGD2ifHhKFlsQB7Bfsc/mty4zwm6Mlbc=
ariga.io/atlas v0.35.0/go.mod h1:9ZAIr/V85596AVxmN8edyVHYKKpnNsDMdnHLsEliW7k=
entgo.io/contrib v0.6.0 h1:xfo4TbJE7sJZWx7BV7YrpSz7IPFvS8MzL3fnfzZjKvQ=
entgo.io/contrib v0.6.0/go.mod h1:3qWIseJ/9Wx2Hu5zVh15FDzv7d/UvKNcYKdViywWCQg=
entgo.io/ent v0.14.4 h1:/DhDraSLXIkBhyiVoJeSshr4ZYi7femzhj6/TckzZuI=
entgo.io/ent v0.14.4/go.mod h1:aDPE/OziPEu8+OWbzy4UlvWmD2/kbRuWfK2A40hcxJM=
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/XSAM/otelsql v0.38.0 h1:zWU0/YM9cJhPE71zJcQ2EBHwQDp+G4AX2tPpljslaB8=
github.com/XSAM/otelsql v0.38.0/go.mod h1:5ePOgcLEkWvZtN9H3GV4BUlPeM3p3pzLDCnRG73X8h8=
github.com/XSAM/otelsql v0.39.0 h1:4o374mEIMweaeevL7fd8Q3C710Xi2Jh/c8G4Qy9bvCY=
github.com/XSAM/otelsql v0.39.0/go.mod h1:uMOXLUX+wkuAuP0AR3B45NXX7E9lJS2mERa8gqdU8R0=
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
@@ -16,13 +16,13 @@ github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQ
github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE=
github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw=
github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c=
github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0=
github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE=
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/go-kratos/kratos/v2 v2.8.4 h1:eIJLE9Qq9WSoKx+Buy2uPyrahtF/lPh+Xf4MTpxhmjs=
github.com/go-kratos/kratos/v2 v2.8.4/go.mod h1:mq62W2101a5uYyRxe+7IdWubu7gZCGYqSNKwGFiiRcw=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
@@ -45,6 +45,8 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lithammer/shortuuid/v4 v4.2.0 h1:LMFOzVB3996a7b8aBuEXxqOBflbfPQAiVzkIcHO0h8c=
github.com/lithammer/shortuuid/v4 v4.2.0/go.mod h1:D5noHZ2oFw/YaKCfGy0YxyE7M0wMbezmMjPdhyEFe6Y=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
@@ -55,6 +57,10 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c=
github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE=
github.com/sony/sonyflake v1.2.1 h1:Jzo4abS84qVNbYamXZdrZF1/6TzNJjEogRfXv7TsG48=
github.com/sony/sonyflake v1.2.1/go.mod h1:LORtCywH/cq10ZbyfhKrHYgAUGH7mOBa76enV9txy/Y=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
@@ -65,32 +71,34 @@ github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
github.com/zclconf/go-cty-yaml v1.1.0 h1:nP+jp0qPHv2IhUVqmQSzjvqAWcObN0KBkUl2rWBdig0=
github.com/zclconf/go-cty-yaml v1.1.0/go.mod h1:9YLUH4g7lOhVWqUbctnVlZ5KLpg7JAprQNgxSZ1Gyxs=
go.mongodb.org/mongo-driver v1.17.4 h1:jUorfmVzljjr0FLzYQsGP8cgN/qzzxlY9Vh0C9KFXVw=
go.mongodb.org/mongo-driver v1.17.4/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=
go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis=
go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4=
go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 h1:YJ5pD9rF8o9Qtta0Cmy9rdBwkSjrTCT6XTiUQVOtIos=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 h1:6G8oQ016D88m1xAKljMlBOOGWDZkes4kMhgGFlf8WcQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU=

View File

@@ -7,7 +7,7 @@ import (
"entgo.io/ent/schema/index"
"entgo.io/ent/schema/mixin"
"github.com/tx7do/go-utils/sonyflake"
"github.com/tx7do/go-utils/id"
)
type SnowflackId struct {
@@ -18,7 +18,7 @@ func (SnowflackId) Fields() []ent.Field {
return []ent.Field{
field.Uint64("id").
Comment("id").
DefaultFunc(sonyflake.GenerateSonyflake).
DefaultFunc(id.GenerateSonyflakeID).
Positive().
Immutable().
StructTag(`json:"id,omitempty"`).

7
go.mod
View File

@@ -7,17 +7,14 @@ toolchain go1.24.1
require (
github.com/gobwas/glob v0.2.3
github.com/google/uuid v1.6.0
github.com/gosimple/slug v1.15.0
github.com/sony/sonyflake v1.2.1
github.com/stretchr/testify v1.10.0
golang.org/x/crypto v0.38.0
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6
golang.org/x/crypto v0.39.0
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b
google.golang.org/protobuf v1.36.6
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gosimple/unidecode v1.0.1 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.10.0 // indirect

14
go.sum
View File

@@ -7,10 +7,6 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gosimple/slug v1.15.0 h1:wRZHsRrRcs6b0XnxMUBM6WK1U1Vg5B0R7VkIf1Xzobo=
github.com/gosimple/slug v1.15.0/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ=
github.com/gosimple/unidecode v1.0.1 h1:hZzFTMMqSswvf0LBJZCZgThIZrpDHFXux9KeGmn6T/o=
github.com/gosimple/unidecode v1.0.1/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
@@ -24,14 +20,12 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/sony/sonyflake v1.2.1 h1:Jzo4abS84qVNbYamXZdrZF1/6TzNJjEogRfXv7TsG48=
github.com/sony/sonyflake v1.2.1/go.mod h1:LORtCywH/cq10ZbyfhKrHYgAUGH7mOBa76enV9txy/Y=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI=
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

28
id/README.md Normal file
View File

@@ -0,0 +1,28 @@
# ID生成器
## 订单ID
- 电商平台202506041234567890时间戳 + 随机数19-20 位)。
- 支付系统PAY20250604123456789业务前缀 + 时间戳 + 序号)。
- 微信支付1589123456789012345类似 Snowflake 的纯数字 ID
- 美团订单202506041234567890123时间戳 + 商户 ID + 随机数)。
## UUID
| 特性 | GUID/UUID | KSUID | ShortUUID | XID | Snowflake |
|-------|--------------|------------|-----------|----------|----------------|
| 长度 | 36/32字符不含- | 27字符 | 22字符 | 20字符 | 19数字位数 |
| 有序性 | 无序UUIDv4 | 严格时序 | 无序 | 趋势有序 | 严格时序 |
| 时间精度 | 无UUIDv4 | 毫秒级 | 无 | 秒级 | 毫秒级 |
| 分布式安全 | 高(随机数) | 高 | 高 | 高 | 高需配置WorkerID |
| 性能 | 中等 | 中等 | 较低(编码开销) | 极高 | 极高 |
| 时钟依赖 | 无 | 有(需处理时钟回拨) | 无 | 有(但影响较小) | 强依赖(需严格同步) |
| 适用场景 | 跨系统兼容 | 时序索引 | 短ID、URL | 高并发、短ID | 分布式时序ID |
## 选择建议
- **GUID/UUID**: 适用于需要跨系统兼容的场景,特别是当不需要有序性时。
- **KSUID**: 适合需要严格时序的应用,如事件日志、时间序列数据。
- **ShortUUID**: 当需要短ID且不关心有序性时的理想选择适用于URL、短链接等。
- **XID**: 高并发场景下的短ID选择适合需要一定有序性的应用。
- **Snowflake**: 适合分布式系统,特别是需要严格时序和高性能的场景,如大规模分布式应用。

27
id/go.mod Normal file
View File

@@ -0,0 +1,27 @@
module github.com/tx7do/go-utils/id
go 1.23.0
toolchain go1.23.2
require (
github.com/bwmarrin/snowflake v0.3.0
github.com/google/uuid v1.6.0
github.com/lithammer/shortuuid/v4 v4.2.0
github.com/rs/xid v1.6.0
github.com/segmentio/ksuid v1.0.4
github.com/stretchr/testify v1.10.0
github.com/tx7do/go-utils v1.1.27
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.13.1 // indirect
github.com/sony/sonyflake v1.2.1 // indirect
go.mongodb.org/mongo-driver v1.17.3 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace github.com/tx7do/go-utils => ../

32
id/go.sum Normal file
View File

@@ -0,0 +1,32 @@
github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0=
github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lithammer/shortuuid/v4 v4.2.0 h1:LMFOzVB3996a7b8aBuEXxqOBflbfPQAiVzkIcHO0h8c=
github.com/lithammer/shortuuid/v4 v4.2.0/go.mod h1:D5noHZ2oFw/YaKCfGy0YxyE7M0wMbezmMjPdhyEFe6Y=
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/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c=
github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE=
github.com/sony/sonyflake v1.2.1 h1:Jzo4abS84qVNbYamXZdrZF1/6TzNJjEogRfXv7TsG48=
github.com/sony/sonyflake v1.2.1/go.mod h1:LORtCywH/cq10ZbyfhKrHYgAUGH7mOBa76enV9txy/Y=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
go.mongodb.org/mongo-driver v1.17.3 h1:TQyXhnsWfWtgAhMtOgtYHMTkZIfBTpMTsMnd9ZBeHxQ=
go.mongodb.org/mongo-driver v1.17.3/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
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/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

84
id/order_id.go Normal file
View File

@@ -0,0 +1,84 @@
package id
import (
"fmt"
"math/rand"
"strings"
"sync/atomic"
"time"
"github.com/tx7do/go-utils/trans"
)
type idCounter uint32
func (c *idCounter) Increase() uint32 {
cur := *c
atomic.AddUint32((*uint32)(c), 1)
atomic.CompareAndSwapUint32((*uint32)(c), 1000, 0)
return uint32(cur)
}
var orderIdIndex idCounter
// GenerateOrderIdWithRandom 生成20位订单号前缀 + 时间戳 + 随机数
func GenerateOrderIdWithRandom(prefix string, tm *time.Time) string {
// 前缀 + 时间戳14位 + 随机数4位
if tm == nil {
tm = trans.Time(time.Now())
}
timestamp := tm.Format("20060102150405")
randNum := rand.Intn(10000) // 生成0-9999之间的随机数
return fmt.Sprintf("%s%s%d", prefix, timestamp, randNum)
}
// GenerateOrderIdWithIncreaseIndex 生成20位订单号前缀+时间+自增长索引
func GenerateOrderIdWithIncreaseIndex(prefix string, tm *time.Time) string {
if tm == nil {
tm = trans.Time(time.Now())
}
timestamp := tm.Format("20060102150405")
index := orderIdIndex.Increase()
return fmt.Sprintf("%s%s%d", prefix, timestamp, index)
}
// GenerateOrderIdWithTenantId 带商户ID的订单ID生成器202506041234567890123
func GenerateOrderIdWithTenantId(tenantID string) string {
// 时间戳14位 + 商户ID固定 5 位) + 随机数4位
// 时间戳部分(精确到毫秒)
now := time.Now()
timestamp := now.Format("20060102150405")
// 商户ID部分截取或补零到5位
tenantPart := tenantID
if len(tenantPart) > 5 {
tenantPart = tenantPart[:5]
} else {
tenantPart = fmt.Sprintf("%-5s", tenantPart)
tenantPart = strings.ReplaceAll(tenantPart, " ", "0")
}
// 随机数部分4位
n := rand.Int31n(10000)
randomPart := fmt.Sprintf("%04d", n)
return timestamp + tenantPart + randomPart
}
func GenerateOrderIdWithPrefixSonyflake(prefix string) string {
id, _ := NewSonyflakeID()
return fmt.Sprintf("%s%d", prefix, id)
}
func GenerateOrderIdWithPrefixSnowflake(workerId int64, prefix string) string {
id, _ := NewSnowflakeID(workerId)
return fmt.Sprintf("%s%d", prefix, id)
}

158
id/order_id_test.go Normal file
View File

@@ -0,0 +1,158 @@
package id
import (
"fmt"
"sync"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestGenerateOrderIdWithRandom(t *testing.T) {
prefix := "PT"
// 测试生成的订单号是否包含前缀
orderID := GenerateOrderIdWithRandom(prefix, nil)
assert.Contains(t, orderID, prefix, "订单号应包含前缀")
t.Logf("GenerateOrderIdWithRandom: %s", orderID)
// 测试生成的订单号长度是否正确
assert.Equal(t, len(prefix)+14+4, len(orderID), "订单号长度应为前缀+时间戳+随机数")
}
func TestGenerateOrderIdWithIndex(t *testing.T) {
prefix := "PT"
tm := time.Now()
fmt.Println(GenerateOrderIdWithIncreaseIndex(prefix, &(tm)))
ids := make(map[string]bool)
count := 100
for i := 0; i < count; i++ {
ids[GenerateOrderIdWithIncreaseIndex(prefix, &(tm))] = true
}
assert.Equal(t, count, len(ids))
}
func TestGenerateOrderIdWithIndexThread(t *testing.T) {
tm := time.Now()
var wg sync.WaitGroup
var ids sync.Map
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
for i := 0; i < 100; i++ {
id := GenerateOrderIdWithIncreaseIndex("PT", &(tm))
ids.Store(id, true)
}
wg.Done()
}()
}
wg.Wait()
aLen := 0
ids.Range(func(k, v interface{}) bool {
aLen++
return true
})
assert.Equal(t, 1000, aLen)
}
func TestGenerateOrderIdWithTenantId(t *testing.T) {
tenantID := "M9876"
orderID := GenerateOrderIdWithTenantId(tenantID)
t.Logf(orderID)
// 验证订单号长度是否正确
assert.Equal(t, 14+5+4, len(orderID))
// 验证时间戳部分是否正确
timestamp := time.Now().Format("20060102150405")
assert.Contains(t, orderID, timestamp)
t.Logf("timestamp %d", len(timestamp))
// 验证商户ID部分是否正确
assert.Contains(t, orderID, tenantID)
// 验证随机数部分是否为4位数字
randomPart := orderID[len(orderID)-4:]
assert.Regexp(t, `^\d{4}$`, randomPart)
}
func TestGenerateOrderIdWithTenantIdCollision(t *testing.T) {
tenantID := "M9876"
count := 1000 // 生成订单号的数量
ids := make(map[string]bool)
for i := 0; i < count; i++ {
orderID := GenerateOrderIdWithTenantId(tenantID)
if ids[orderID] {
t.Errorf("碰撞的订单号: %s", orderID)
}
ids[orderID] = true
}
t.Logf("生成了 %d 个订单号,没有发生碰撞", count)
}
func TestGenerateOrderIdWithPrefixSonyflake(t *testing.T) {
prefix := "ORD"
orderID := GenerateOrderIdWithPrefixSonyflake(prefix)
t.Logf("order id with prefix sonyflake: %s [%d]", orderID, len(orderID))
// 验证订单号是否包含前缀
assert.Contains(t, orderID, prefix, "订单号应包含前缀")
// 验证订单号是否为有效的数字字符串
assert.Regexp(t, `^ORD\d+$`, orderID, "订单号格式应为前缀加数字")
}
func TestGenerateOrderIdWithPrefixSonyflakeCollision(t *testing.T) {
prefix := "ORD"
count := 100000 // 生成订单号的数量
ids := make(map[string]bool)
for i := 0; i < count; i++ {
orderID := GenerateOrderIdWithPrefixSonyflake(prefix)
if ids[orderID] {
t.Errorf("碰撞的订单号: %s", orderID)
}
ids[orderID] = true
}
t.Logf("生成了 %d 个订单号,没有发生碰撞", count)
}
func TestGenerateOrderIdWithPrefixSnowflake(t *testing.T) {
workerId := int64(1) // 假设使用的 workerId
prefix := "ORD"
orderID := GenerateOrderIdWithPrefixSnowflake(workerId, prefix)
t.Logf("order id with prefix snowflake: %s [%d]", orderID, len(orderID))
// 验证订单号是否包含前缀
assert.Contains(t, orderID, prefix, "订单号应包含前缀")
// 验证订单号是否为有效的数字字符串
assert.Regexp(t, `^ORD\d+$`, orderID, "订单号格式应为前缀加数字")
}
func TestGenerateOrderIdWithPrefixSnowflakeCollision(t *testing.T) {
workerId := int64(1) // 假设使用的 workerId
prefix := "ORD"
count := 1000000 // 生成订单号的数量
ids := make(map[string]bool)
for i := 0; i < count; i++ {
orderID := GenerateOrderIdWithPrefixSnowflake(workerId, prefix)
if ids[orderID] {
t.Errorf("碰撞的订单号: %s", orderID)
}
ids[orderID] = true
}
t.Logf("生成了 %d 个订单号,没有发生碰撞", count)
}

66
id/snowflake.go Normal file
View File

@@ -0,0 +1,66 @@
package id
import (
"errors"
"sync"
"github.com/bwmarrin/snowflake"
)
var snowflakeNodeMap = sync.Map{}
type SnowflakeNode struct {
workerId int64
node *snowflake.Node
sync.Mutex
}
func NewSnowflakeNode(workerId int64) (*SnowflakeNode, error) {
node, err := snowflake.NewNode(workerId)
return &SnowflakeNode{
workerId: workerId,
node: node,
Mutex: sync.Mutex{},
}, err
}
func (sfNode *SnowflakeNode) Generate() int64 {
sfNode.Lock()
defer sfNode.Unlock()
return sfNode.node.Generate().Int64()
}
func (sfNode *SnowflakeNode) GenerateString() string {
sfNode.Lock()
defer sfNode.Unlock()
return sfNode.node.Generate().String()
}
func NewSnowflakeID(workerId int64) (int64, error) {
// 64 位 ID = 41 位时间戳 + 10 位工作节点 ID + 12 位序列号
var node *SnowflakeNode
var err error
find, ok := snowflakeNodeMap.Load(workerId)
if ok {
node = find.(*SnowflakeNode)
} else {
node, err = NewSnowflakeNode(workerId)
if err != nil {
//log.Println(err)
return 0, err
}
snowflakeNodeMap.Store(workerId, node)
}
if node == nil {
//log.Println("snowflake node is nil")
return 0, errors.New("snowflake node is nil")
}
return node.Generate(), nil
}
func GenerateSnowflakeID(workerId int64) int64 {
id, _ := NewSnowflakeID(workerId)
return id
}

30
id/sonyflake.go Normal file
View File

@@ -0,0 +1,30 @@
package id
import (
"sync"
"github.com/sony/sonyflake"
)
var (
sf *sonyflake.Sonyflake
sfMu sync.Mutex
)
func NewSonyflakeID() (uint64, error) {
// 64 位 ID = 39 位时间戳 + 8 位机器 ID + 16 位序列号
sfMu.Lock()
defer sfMu.Unlock()
if sf == nil {
sf = sonyflake.NewSonyflake(sonyflake.Settings{})
}
return sf.NextID()
}
func GenerateSonyflakeID() uint64 {
id, _ := NewSonyflakeID()
return id
}

36
id/uuid.go Normal file
View File

@@ -0,0 +1,36 @@
package id
import (
"strings"
"github.com/google/uuid"
"github.com/lithammer/shortuuid/v4"
"github.com/rs/xid"
"github.com/segmentio/ksuid"
"go.mongodb.org/mongo-driver/bson/primitive"
)
func NewGUIDv4(withHyphen bool) string {
id := uuid.NewString()
if !withHyphen {
id = strings.ReplaceAll(id, "-", "")
}
return id
}
func NewShortUUID() string {
return shortuuid.New()
}
func NewKSUID() string {
return ksuid.New().String()
}
func NewXID() string {
return xid.New().String()
}
func NewMongoObjectID() string {
objID := primitive.NewObjectID()
return objID.String()
}

267
id/uuid_test.go Normal file
View File

@@ -0,0 +1,267 @@
package id
import (
"strings"
"sync"
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewGUIDv4(t *testing.T) {
// 测试带有连字符的 GUID
withHyphen := NewGUIDv4(true)
assert.NotEmpty(t, withHyphen)
assert.Equal(t, 4, strings.Count(withHyphen, "-"), "GUID 应包含 4 个连字符")
// 测试不带连字符的 GUID
withoutHyphen := NewGUIDv4(false)
assert.NotEmpty(t, withoutHyphen)
assert.Equal(t, 0, strings.Count(withoutHyphen, "-"), "GUID 不应包含连字符")
// 验证 GUID 的长度
assert.Equal(t, 36, len(withHyphen), "带连字符的 GUID 长度应为 36")
assert.Equal(t, 32, len(withoutHyphen), "不带连字符的 GUID 长度应为 32")
}
func TestNewGUIDv4CollisionRate(t *testing.T) {
const (
testCount = 100000 // 测试生成的GUID数量
withHyphen = true // 是否带连字符
)
ids := make(map[string]struct{})
for i := 0; i < testCount; i++ {
id := NewGUIDv4(withHyphen)
if _, exists := ids[id]; exists {
t.Errorf("碰撞发生: %s 已存在", id)
}
ids[id] = struct{}{}
}
t.Logf("生成了 %d 个GUID无碰撞。", testCount)
}
func TestNewShortUUID(t *testing.T) {
// 测试生成的 ShortUUID 是否非空
id := NewShortUUID()
assert.NotEmpty(t, id, "生成的 ShortUUID 应该非空")
// 测试生成的 ShortUUID 的长度是否符合预期
assert.True(t, len(id) > 0, "生成的 ShortUUID 长度应该大于 0")
}
func TestNewShortUUIDCollisionRate(t *testing.T) {
const testCount = 100000 // 测试生成的ShortUUID数量
ids := make(map[string]struct{})
for i := 0; i < testCount; i++ {
id := NewShortUUID()
if _, exists := ids[id]; exists {
t.Errorf("碰撞发生: %s 已存在", id)
}
ids[id] = struct{}{}
}
t.Logf("生成了 %d 个ShortUUID无碰撞。", testCount)
}
func TestNewKSUID(t *testing.T) {
// 测试生成的 KSUID 是否非空
id := NewKSUID()
assert.NotEmpty(t, id, "生成的 KSUID 应该非空")
// 测试生成的 KSUID 的长度是否符合预期
assert.Equal(t, 27, len(id), "生成的 KSUID 长度应该为 27")
}
func TestNewKSUIDCollisionRate(t *testing.T) {
const testCount = 100000 // 测试生成的KSUID数量
ids := make(map[string]struct{})
for i := 0; i < testCount; i++ {
id := NewKSUID()
if _, exists := ids[id]; exists {
t.Errorf("碰撞发生: %s 已存在", id)
}
ids[id] = struct{}{}
}
t.Logf("生成了 %d 个KSUID无碰撞。", testCount)
}
func TestNewXID(t *testing.T) {
// 测试生成的 XID 是否非空
id := NewXID()
assert.NotEmpty(t, id, "生成的 XID 应该非空")
t.Logf("xid: %s", id)
// 测试生成的 XID 的长度是否符合预期
assert.Equal(t, 20, len(id), "生成的 XID 长度应该为 20")
}
func TestNewXIDCollisionRate(t *testing.T) {
const testCount = 100000 // 测试生成的XID数量
ids := make(map[string]struct{})
for i := 0; i < testCount; i++ {
id := NewXID()
if _, exists := ids[id]; exists {
t.Errorf("碰撞发生: %s 已存在", id)
}
ids[id] = struct{}{}
}
t.Logf("生成了 %d 个XID无碰撞。", testCount)
}
func TestNewSnowflakeID(t *testing.T) {
tests := []struct {
workerId int64
expectErr bool
}{
{0, false}, // 有效的 workerId
{31, false}, // 有效的 workerId
{32, false}, // 有效的 workerId
{-1, true}, // 无效的 workerId
}
for _, tt := range tests {
id, err := NewSnowflakeID(tt.workerId)
if (err != nil) != tt.expectErr {
t.Errorf("NewSnowflakeID(%d) 错误状态不符合预期: %v", tt.workerId, err)
}
if err == nil && id <= 0 {
t.Errorf("NewSnowflakeID(%d) 生成的ID无效: %d", tt.workerId, id)
}
t.Logf("NewSnowflakeID(%d) ID: %d", tt.workerId, id)
}
}
func TestNewSnowflakeIDCollisionRate(t *testing.T) {
const (
workerId = 0
testCount = 100000 // 测试生成的ID数量
)
ids := make(map[int64]struct{})
for i := 0; i < testCount; i++ {
id, err := NewSnowflakeID(workerId)
if err != nil {
t.Errorf("生成ID时出现错误: %v", err)
continue
}
if _, exists := ids[id]; exists {
t.Errorf("碰撞发生: %d 已存在", id)
}
ids[id] = struct{}{}
}
t.Logf("生成了 %d 个Snowflake ID无碰撞。", testCount)
}
func TestConcurrentNewSnowflakeIDCollisionRate(t *testing.T) {
const (
workerId = 0 // Snowflake 工作节点 ID
testCount = 100000 // 测试生成的 ID 数量
workerCount = 10 // 并发工作线程数
)
var mu sync.Mutex
ids := make(map[int64]struct{})
var wg sync.WaitGroup
for w := 0; w < workerCount; w++ {
wg.Add(1)
go func() {
defer wg.Done()
for i := 0; i < testCount/workerCount; i++ {
id, err := NewSnowflakeID(workerId)
if err != nil {
t.Errorf("生成 Snowflake ID 时出现错误: %v", err)
continue
}
mu.Lock()
if _, exists := ids[id]; exists {
t.Errorf("碰撞发生: %d 已存在", id)
}
ids[id] = struct{}{}
mu.Unlock()
}
}()
}
wg.Wait()
t.Logf("生成了 %d 个 Snowflake ID无碰撞。", testCount)
}
func TestNewSonyflakeID(t *testing.T) {
// 测试生成的 Sonyflake ID 是否有效
id, err := NewSonyflakeID()
t.Logf("sonyflake id: %v", id)
assert.NoError(t, err, "生成 Sonyflake ID 时不应出现错误")
assert.True(t, id > 0, "生成的 Sonyflake ID 应该是正数")
}
func TestNewSonyflakeIDCollisionRate(t *testing.T) {
const testCount = 100000 // 测试生成的 Sonyflake ID 数量
ids := make(map[uint64]struct{})
for i := 0; i < testCount; i++ {
id, err := NewSonyflakeID()
if err != nil {
t.Errorf("生成 Sonyflake ID 时出现错误: %v", err)
continue
}
if _, exists := ids[id]; exists {
t.Errorf("碰撞发生: %d 已存在", id)
}
ids[id] = struct{}{}
}
t.Logf("生成了 %d 个 Sonyflake ID无碰撞。", testCount)
}
func TestConcurrentNewSonyflakeIDCollisionRate(t *testing.T) {
const (
testCount = 100000 // 测试生成的 Sonyflake ID 数量
workerCount = 10 // 并发工作线程数
)
var mu sync.Mutex
ids := make(map[uint64]struct{})
var wg sync.WaitGroup
for w := 0; w < workerCount; w++ {
wg.Add(1)
go func() {
defer wg.Done()
for i := 0; i < testCount/workerCount; i++ {
id, err := NewSonyflakeID()
if err != nil {
t.Errorf("生成 Sonyflake ID 时出现错误: %v", err)
continue
}
mu.Lock()
if _, exists := ids[id]; exists {
t.Errorf("碰撞发生: %d 已存在", id)
}
ids[id] = struct{}{}
mu.Unlock()
}
}()
}
wg.Wait()
t.Logf("生成了 %d 个 Sonyflake ID无碰撞。", testCount)
}
func TestNewMongoObjectID(t *testing.T) {
// 测试生成的 ObjectID 是否非空
id := NewMongoObjectID()
assert.NotEmpty(t, id, "生成的 Mongo ObjectID 应该非空")
// 测试生成的 ObjectID 的长度是否符合预期
assert.Equal(t, 36, len(id), "生成的 Mongo ObjectID 长度应该为 36")
}

95
mapper/README.md Normal file
View File

@@ -0,0 +1,95 @@
# 类型映射器
类型映射器的作用是将一个数据结构的字段映射到另一个数据结构的字段,通常用于对象之间的数据转换。它可以简化不同类型或结构之间的数据传递,减少手动赋值的代码量,提高开发效率。例如,在处理数据库实体与业务模型或 API 请求/响应模型之间的转换时,类型映射器非常有用。
## 基于Copier的类型映射器
```go
package main
import (
"github.com/tx7do/go-utils/mapper"
)
func main() {
type DtoType struct {
Name string
Age int
}
type EntityType struct {
Name string
Age int
}
mapper := mapper.NewCopierMapper[DtoType, EntityType]()
// 测试 ToEntity 方法
dto := &DtoType{Name: "Alice", Age: 25}
entity := mapper.ToEntity(dto)
// 测试 ToEntity 方法,传入 nil
entityNil := mapper.ToEntity(nil)
// 测试 ToDTO 方法
entity = &EntityType{Name: "Bob", Age: 30}
dtoResult := mapper.ToDTO(entity)
// 测试 ToDTO 方法,传入 nil
dtoNil := mapper.ToDTO(nil)
}
```
## ent 与 protobuf 的枚举类型映射器
```go
package main
import "github.com/tx7do/go-utils/mapper"
func main() {
type DtoType int32
type EntityType string
const (
DtoTypeOne DtoType = 1
DtoTypeTwo DtoType = 2
)
const (
EntityTypeOne EntityType = "One"
EntityTypeTwo EntityType = "Two"
)
nameMap := map[int32]string{
1: "One",
2: "Two",
}
valueMap := map[string]int32{
"One": 1,
"Two": 2,
}
converter := mapper.NewEnumTypeConverter[DtoType, EntityType](nameMap, valueMap)
// 测试 ToEntity 方法
dto := DtoTypeOne
entity := converter.ToEntity(&dto)
// 测试 ToEntity 方法,传入不存在的值
dtoInvalid := DtoType(3)
entityInvalid := converter.ToEntity(&dtoInvalid)
// 测试 ToDTO 方法
tmpEntityTwo := EntityTypeTwo
entity = &tmpEntityTwo
dtoResult := converter.ToDTO(entity)
// 测试 ToDTO 方法,传入不存在的值
tmpEntityThree := EntityType("Three")
entityInvalid = &tmpEntityThree
dtoInvalidResult := converter.ToDTO(entityInvalid)
}
```

82
mapper/enum_converter.go Normal file
View File

@@ -0,0 +1,82 @@
package mapper
import (
"github.com/jinzhu/copier"
)
type EnumTypeConverter[DTO ~int32, ENTITY ~string] struct {
nameMap map[int32]string
valueMap map[string]int32
}
func NewEnumTypeConverter[DTO ~int32, ENTITY ~string](
nameMap map[int32]string,
valueMap map[string]int32,
) *EnumTypeConverter[DTO, ENTITY] {
return &EnumTypeConverter[DTO, ENTITY]{
valueMap: valueMap,
nameMap: nameMap,
}
}
func (m *EnumTypeConverter[DTO, ENTITY]) ToEntity(dto *DTO) *ENTITY {
if dto == nil {
return nil
}
find, ok := m.nameMap[int32(*dto)]
if !ok {
return nil
}
entity := ENTITY(find)
return &entity
}
func (m *EnumTypeConverter[DTO, ENTITY]) ToDTO(entity *ENTITY) *DTO {
if entity == nil {
return nil
}
find, ok := m.valueMap[string(*entity)]
if !ok {
return nil
}
dto := DTO(find)
return &dto
}
func (m *EnumTypeConverter[DTO, ENTITY]) NewConverterPair() []copier.TypeConverter {
srcType := ENTITY("")
dstType := DTO(0)
fromFn := m.ToDTO
toFn := m.ToEntity
return NewGenericTypeConverterPair(&srcType, &dstType, fromFn, toFn)
}
func NewGenericTypeConverterPair[A interface{}, B interface{}](
srcType A,
dstType B,
fromFn func(src A) B,
toFn func(src B) A,
) []copier.TypeConverter {
return []copier.TypeConverter{
{
SrcType: srcType,
DstType: dstType,
Fn: func(src interface{}) (interface{}, error) {
return fromFn(src.(A)), nil
},
},
{
SrcType: dstType,
DstType: srcType,
Fn: func(src interface{}) (interface{}, error) {
return toFn(src.(B)), nil
},
},
}
}

20
mapper/go.mod Normal file
View File

@@ -0,0 +1,20 @@
module github.com/tx7do/go-utils/mapper
go 1.23.0
toolchain go1.24.3
require github.com/jinzhu/copier v0.4.0
require github.com/stretchr/testify v1.10.0
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.10.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace github.com/tx7do/go-utils => ../

25
mapper/go.sum Normal file
View File

@@ -0,0 +1,25 @@
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
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/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
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/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

10
mapper/interface.go Normal file
View File

@@ -0,0 +1,10 @@
package mapper
// Mapper defines the interface for converting between Data Transfer Objects (DTOs) and Database Entities.
type Mapper[DTO any, ENTITY any] interface {
// ToEntity converts a DTO to a Database Entity.
ToEntity(*DTO) *ENTITY
// ToDTO converts a Database Entity to a DTO.
ToDTO(*ENTITY) *DTO
}

51
mapper/mapper.go Normal file
View File

@@ -0,0 +1,51 @@
package mapper
import (
"github.com/jinzhu/copier"
)
type CopierMapper[DTO any, ENTITY any] struct {
copierOption copier.Option
}
func NewCopierMapper[DTO any, ENTITY any]() *CopierMapper[DTO, ENTITY] {
return &CopierMapper[DTO, ENTITY]{
copierOption: copier.Option{
Converters: []copier.TypeConverter{},
},
}
}
func (m *CopierMapper[DTO, ENTITY]) AppendConverter(converter copier.TypeConverter) {
m.copierOption.Converters = append(m.copierOption.Converters, converter)
}
func (m *CopierMapper[DTO, ENTITY]) AppendConverters(converters []copier.TypeConverter) {
m.copierOption.Converters = append(m.copierOption.Converters, converters...)
}
func (m *CopierMapper[DTO, ENTITY]) ToEntity(dto *DTO) *ENTITY {
if dto == nil {
return nil
}
var entity ENTITY
if err := copier.CopyWithOption(&entity, dto, m.copierOption); err != nil {
panic(err) // Handle error appropriately in production code
}
return &entity
}
func (m *CopierMapper[DTO, ENTITY]) ToDTO(entity *ENTITY) *DTO {
if entity == nil {
return nil
}
var dto DTO
if err := copier.CopyWithOption(&dto, entity, m.copierOption); err != nil {
panic(err) // Handle error appropriately in production code
}
return &dto
}

93
mapper/mapper_test.go Normal file
View File

@@ -0,0 +1,93 @@
package mapper
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestCopierMapper(t *testing.T) {
type DtoType struct {
Name string
Age int
}
type EntityType struct {
Name string
Age int
}
mapper := NewCopierMapper[DtoType, EntityType]()
// 测试 ToEntity 方法
dto := &DtoType{Name: "Alice", Age: 25}
entity := mapper.ToEntity(dto)
assert.NotNil(t, entity)
assert.Equal(t, "Alice", entity.Name)
assert.Equal(t, 25, entity.Age)
// 测试 ToEntity 方法,传入 nil
entityNil := mapper.ToEntity(nil)
assert.Nil(t, entityNil)
// 测试 ToDTO 方法
entity = &EntityType{Name: "Bob", Age: 30}
dtoResult := mapper.ToDTO(entity)
assert.NotNil(t, dtoResult)
assert.Equal(t, "Bob", dtoResult.Name)
assert.Equal(t, 30, dtoResult.Age)
// 测试 ToDTO 方法,传入 nil
dtoNil := mapper.ToDTO(nil)
assert.Nil(t, dtoNil)
}
func TestEnumTypeConverter(t *testing.T) {
type DtoType int32
type EntityType string
const (
DtoTypeOne DtoType = 1
DtoTypeTwo DtoType = 2
)
const (
EntityTypeOne EntityType = "One"
EntityTypeTwo EntityType = "Two"
)
nameMap := map[int32]string{
1: "One",
2: "Two",
}
valueMap := map[string]int32{
"One": 1,
"Two": 2,
}
converter := NewEnumTypeConverter[DtoType, EntityType](nameMap, valueMap)
// 测试 ToEntity 方法
dto := DtoTypeOne
entity := converter.ToEntity(&dto)
assert.NotNil(t, entity)
assert.Equal(t, "One", string(*entity))
// 测试 ToEntity 方法,传入不存在的值
dtoInvalid := DtoType(3)
entityInvalid := converter.ToEntity(&dtoInvalid)
assert.Nil(t, entityInvalid)
// 测试 ToDTO 方法
tmpEntityTwo := EntityTypeTwo
entity = &tmpEntityTwo
dtoResult := converter.ToDTO(entity)
assert.NotNil(t, dtoResult)
assert.Equal(t, DtoType(2), *dtoResult)
// 测试 ToDTO 方法,传入不存在的值
tmpEntityThree := EntityType("Three")
entityInvalid = &tmpEntityThree
dtoInvalidResult := converter.ToDTO(entityInvalid)
assert.Nil(t, dtoInvalidResult)
}

158
name_generator/README.md Normal file
View File

@@ -0,0 +1,158 @@
# 名字生成器
这是一个简单的名字生成器。它可以生成中文名、英文名和日文名。以及游戏的昵称,道具名等。
## 生成中文名
```go
package main
import "github.com/tx7do/go-utils/name_generator"
func main() {
g := name_generator.New()
result := g.GenerateChineseName(1, true, false)
if result == "" {
log.Errorf("result is empty, please check the dictionary data")
} else {
log.Logf("Generated single surname single name (female): %s", result)
}
result = g.GenerateChineseName(2, false, true)
if result == "" {
log.Errorf("result is empty, please check the dictionary data")
} else {
log.Logf("Generated compound surname double name (male): %s", result)
}
}
```
输出效果:
```shell
Generated single surname single name (female): 候影
Generated compound surname double name (male): 宗政辰宁
```
## 生成英文名
```go
package main
import "github.com/tx7do/go-utils/name_generator"
func main() {
g := name_generator.New()
result := g.GenerateEnglishName(1, 0, 1, true)
if result == "" {
log.Errorf("result is empty, please check the dictionary data")
} else {
log.Logf("Generated female English name: %s", result)
}
result = g.GenerateEnglishName(2, 0, 1, false)
if result == "" {
log.Errorf("result is empty, please check the dictionary data")
} else {
log.Logf("Generated male English name: %s", result)
}
}
```
输出效果:
```shell
Generated female English name: Magical Alexander
Generated male English name: Valentine Roderick Hayes
```
## 生成日文名
汉字版
```go
package main
import "github.com/tx7do/go-utils/name_generator"
func main() {
g := name_generator.New()
result := g.GenerateJapaneseNameCN()
if result == "" {
log.Errorf("result is empty, please check the dictionary data")
} else {
log.Logf("Generated Japanese name (CN): %s", result)
}
}
```
输出效果:
```shell
Generated Japanese name (CN): 瀬尾和子
```
日文版
```go
package main
import "github.com/tx7do/go-utils/name_generator"
func main() {
g := name_generator.New()
result := g.GenerateJapaneseName()
if result == "" {
log.Errorf("result is empty, please check the dictionary data")
} else {
log.Logf("Generated Japanese name: %s", result)
}
}
```
输出效果:
```shell
Generated Japanese name: 渋沢洋
```
## 生成游戏昵称
```go
package main
import "github.com/tx7do/go-utils/name_generator"
func main() {
g := name_generator.New()
dictTypes := name_generator.Scheme5
result := g.Generate(dictTypes)
if result == "" {
log.Errorf("result is empty, please check the dictionary data")
} else {
log.Logf("generate`s nickname: %s", result)
}
}
```
输出效果:
```shell
generate`s nickname: 谦逊之讲笑话呼保义
```
## 感谢
- [中文人名语料库Chinese-Names-Corpus](https://github.com/wainshine/Chinese-Names-Corpus)

View File

@@ -0,0 +1,577 @@
幼年
少年
中年
老年
神族
妖怪
亡灵
魔族
血族
变化
地狱
山洞
矿洞
远古
太古
上古
化石
新生
重生
不败
混沌
极品
奥术
魔法
深渊
异世
异界
变异
颜控
腿控
御宅
极大
极巨
压制
火焰
炽焰
烈焰
爆燃
废柴
暗黑
死宅
肥宅
帝都
魔都
妖都
旧都
伪都
古都
神都
腐都
邪都
陪都
雾都
萌豚
声豚
军宅
天朝
高丽
11区
岛国
米帝
米国
白学
哲学
废萌
崩坏
绅士
奇行种
超大
中二
厨二
巨大
傲娇
平原
高原
丘陵
盆地
山地
极地
水星
火星
金星
木星
地球
土星
月球
非洲
欧洲
热带
寒带
温带
腹黑
毒舌
家养
野生
火山
冰原
冰川
小学
中学
大学
博士
脑洞
蠢萌
呆萌
老牌
大触
纯种
纯粹
伦敦
东京
巴黎
雅典
埃及
首尔
平壤
朝鲜
京都
纽约
费城
乡下
城市
魔术
闪电
机械
木头
石头
黑铁
青铜
钻石
铂金
黄金
白银
王者
幽灵
鬼魂
丧尸
僵尸
北京
广州
香港
挪威
大阪
希腊
澳门
上海
深圳
芬兰
德州
襄阳
西安
绵阳
南昌
江苏
福建
福州
厦门
欧气
多兰
银河
太阳
仙女
水瓶
天秤
巨蟹
白羊
金牛
双子
风暴
狮子
天蝎
射手
双鱼
魔蝎
强壮
城堡
结界
长生
摧毁
破坏
精确
主宰
巫术
兼备
惩戒
相位
湮灭
制裁
古老
传说
坚强
史诗
普通
破损
虚弱
迅捷
超级
终结
坚韧
圣佑
狂暴
愤怒
豪华
一流
活泼
颤抖
北风
寒冬
烈火
燃烧
烟雾
静态
发光
弯曲
震撼
锯齿
邪恶
残酷
残忍
阴险
悔恨
野蛮
绝情
凶暴
冷酷
幻影
幻想
冷冽
冰冷
雷心
雷霆
霜冻
霜裔
神邪
穿甲
破甲
破败
蛮族
蛮力
雷击
恶魔
暴怒
巨力
力量
敏捷
智力
审判
抵御
保卫
耐热
耐磨
耐用
虚触
防水
防腐
防火
防护
防偷
防骗
防炸
防盗
磐石
大地
止血
武力
抵抗
灼热
抗性
守护
庇护
引流
优质
侵害
侵扰
贪婪
野火
破碎
破裂
怨恨
虚空
虚无
爆炎
无上
真实
真正
疯狂
破暗
污染
憎恶
无情
密谋
枯萎
怒火
治愈
愈合
隔绝
追踪
操纵
圣贤
强硬
强悍
强劲
坚决
坚信
坚定
坚韧
坚实
坚贞
勇敢
勇猛
刚毅
决断
果敢
果决
坚强
坚忍
决然
毅然
断然
泼辣
断腕
发誓
干脆
爽快
果断
真诚
热诚
至诚
赤诚
诚挚
恳切
纯真
率直
坦率
笃实
热忱
热心
好客
客气
殷勤
和气
和蔼
和善
亲切
过谦
谦卑
谦恭
谦和
谦让
谦虚
谦逊
虚心
外向
开朗
大方
主动
俏皮
敏捷
乐观
调皮
爽脆
爽朗
豪爽
正直
直率
直爽
直言
爽直
刚直
憨直
耿直
公正
公道
公平
公允
正派
开阔
豁达
明朗
率真
怒吼
恐惧
胆怯
畏缩
发慌
心慌
恐慌
激怒
恼火
欢乐
快慰
开心
高兴
愉悦
微笑
舒畅
笑噱
欢闹
欢心
欢欣
欢悦
宽慰
欢舒
狂欢
震怒
气愤
担忧
发愁
犯愁
忧伤
忧愁
忧心
愁闷
悲痛
悲惨
悲凉
哀伤
哀怨
伤感
瘦削
憔悴
快乐
喜悦
愉快
畅快
欢畅
欢喜
欢腾
欢快
欣喜
今天
昨天
明天
后天
上午
下午
过去
未来
去年
前年
散步
漫步
踏步
信步
转悠
闲逛
徜徉
踉跄
蹒跚
小跑
慢跑
飞跑
飞奔
飞翔
啜泣
抽泣
呜咽
哀号
号哭
痛哭
大笑
欢笑
嬉笑
狂笑
嗤笑
憨笑
傻笑
哄笑
苦笑
阴笑
狞笑
奸笑
嘲笑
冷笑
哈腰
猫腰
挺身
挺胸
俯身
躬身
仰卧
蜷曲
倒立
转体
屈体
屈身
欠身
纵身
蹲身
鞠躬
曲背
匍匐
笔挺
腾跃
直立
翻腾
前倾
摇摆
翻跃
扭动
扭转
旋转
好吃
好看
好玩
清白
凛然
无私
刚正
负重
忠心
忠贞
谨慎
廉洁
大度
坦白
勤奋
刻苦
认真
专注
踏实
勤恳
好学
高尚
杰出
超伦
自爱
自尊
自强
宽容
宽宏
律己
朴素
憨厚
诚实
忠诚
诚恳
天真
幼稚
活泼
聪明
圆滑
狡猾
虚伪
自私
任性
骄傲
贪婪
愚蠢
奸诈
高傲
害羞
内向
孤僻
可爱
招烦
阴险
双重
刻薄
宽厚
仁慈
仁厚
尖酸
阴郁
肤浅
浅薄
胆小
乐天
达观

View File

@@ -0,0 +1,54 @@
package assets
import _ "embed"
//go:embed adjective.txt
var Adjective []byte
//go:embed goods.txt
var Goods []byte
//go:embed name.txt
var Name []byte
//go:embed prefix.txt
var Prefix []byte
//go:embed role.txt
var Role []byte
//go:embed verb.txt
var Verb []byte
//go:embed sensitive.txt
var Sensitive []byte
//go:embed chinese_single_surnames.txt
var ChineseSingleSurnames []byte
//go:embed chinese_compound_surnames.txt
var ChineseCompoundSurnames []byte
//go:embed chinese_first_name_female.txt
var ChineseFirstNameFemale []byte
//go:embed chinese_first_name_male.txt
var ChineseFirstNameMale []byte
//go:embed english_first_name_female.txt
var EnglishFirstNameFemale []byte
//go:embed english_first_name_male.txt
var EnglishFirstNameMale []byte
//go:embed english_last_name.txt
var EnglishLastName []byte
//go:embed japanese_names_corpus.txt
var JapaneseNamesCorpus []byte
//go:embed japanese_surnames.txt
var JapaneseSurnames []byte
//go:embed japanese_last_name.txt
var JapaneseLastName []byte

View File

@@ -0,0 +1,126 @@
万俟
司马
上官
欧阳
夏侯
诸葛
闻人
东方
赫连
皇甫
尉迟
公羊
澹台
公冶
宗政
濮阳
淳于
单于
太叔
申屠
公孙
仲孙
轩辕
令狐
钟离
宇文
长孙
慕容
鲜于
闾丘
司徒
司空
亓官
司寇
子车
颛孙
端木
巫马
公西
漆雕
乐正
壤驷
公良
拓跋
夹谷
宰父
谷梁
段干
百里
东郭
南门
呼延
羊舌
微生
梁丘
左丘
东门
西门
南宫
第五
步都
东欧
聂晁
空曾
相查
万俟
司马
上官
欧阳
夏侯
诸葛
闻人
东方
赫连
皇甫
尉迟
公羊
澹台
公冶
宗政
濮阳
淳于
单于
太叔
申屠
公孙
仲孙
轩辕
令狐
钟离
宇文
长孙
慕容
司徒
司空
凃肖
第五
南宫
左丘
梁丘
微生
羊舌
呼延
东郭
百里
谷梁
宰父
夹谷
拓跋
壤驷
乐正
漆雕
公西
巫马
端木
颛孙
子车
司寇
亓官
鲜于
锺离
闾丘
公良
段干
荔菲
辗迟

View File

@@ -0,0 +1,149 @@

View File

@@ -0,0 +1,150 @@
广

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,267 @@
Abby
Adalheid
Adelaide
Ailsa
Aimee
Al
Alexandra
Alexis
Alice
Alina
Alisa
Allison
Amanda
Amber
Amy
Anastasia
Andrea
Angela
Angelia
Angelina
Angle
Anita
Ann
Annabelle
Anne
Annie
April
Ariel
Ashley
Aviva
Avivahc
Avivi
Barbara
Barbie
Beata
Beatrice
Beatrix
Becky
Beenle
Belle
Betty
Blanche
Bonnie
Brandon
Brenda
Brendan
Brianna
Bubles
Bunny
Camille
Candice
Carina
Carmen
Carol
Caroline
Carrie
Carry
Cassandra
Cassie
Catherine
Cathy
Charlene
Charlotte
Cher
Cherie
Cherry
Cheryl
Chris
Christal
Christina
Christine
Christy
Cinderella
Cindy
Claudia
Clement
Cloris
Colorfully
Connie
Constance
Cora
Corrine
Croesus
Crystal
Cynthia
Daisy
Daphne
Darcy
Davis
Debbie
Deborah
Debra
Demi
Diana
Dolores
Donna
Doris
Dreamy
Edith
Editha
El
Elaine
Eleanor
Elizabeth
Ella
Ellen
Ellie
Emily
Emma
Estelle
Esther
Eudora
Eva
Eve
Fairy
Fannie
Fanny
Fern
Fiona
Flower
Frances
Francis
Frederica
Frieda
Gillian
Gina
Girl
Gladys
Gloria
Grace
Greenle
Greta
Gwendolyn
Hailey
Hannah
Hebbe
Hebe
Heidi
Helen
Helena
Hellen
Icey
Ingrid
Irene
Iris
Ishara
Ivy
Jacqueline
James
Jamie
Jane
Janet
Janice
Jasmine
Jean
Jennie
Jennifer
Jenny
Jessee
Jessica
Jessie
Jill
Joan
Joanna
Jocelyn
Jodie
John
Josephine
Josie
Joy
Joyce
Jr
Judith
Judy
Julia
Juliana
Julie
June
Kaitlyn
Kerry
Kitty
Kris
Kristine
Krystal
Lareina
Laura
Lawrence
Lena
Liddy
Lilian
Lillian
Linda
Lisa
Liz
Lovely
Lucinda
Lydia
Madison
Maggie
Magical
Manda
Mandy
Margaret
Maria
Melinda
Miranda
Moon
Names
Nancy
Nicole
Nina
Purplegrape
Qearl
Rebecca
Regina
Rose
S
Sabrina
Samantha
Sammy
Samson
Samuel
Sandra
Sandy
Sarah
Sbrina
Scalett
Selina
Selma
Serena
Sharon
Sheila
Shelby
Shelley
Sherry
Sheryl
Shirley
Silverdew
Silvia
Snowhite
Snowy
Sonia
Stacey
Stacy
Star
Stella
Stephanie
Stephen
Sue
Sunny
Susan
Sweety
Temple
Vanessa
Vicky
Victoria
Vivian
Wanda
Wendy
Winnie
Y
Yilia
Yolanda
Yvette
Yvonne
Zoe
Zoey

View File

@@ -0,0 +1,320 @@
Derrick
Devin
Dick
Dominic
Don
Donahue
Donald
Douglas
Drew
Duke
Duncan
Dunn
Dwight
Dylan
Earl
Ed
Eden
Edgar
Edison
Edmund
Edward
Edwiin
Egbert
Eli
Elijah
Elliot
Ellis
Elmer
Elroy
Elton
Elvis
Emmanuel
Enoch
Eric
Ernest
Eugene
Evan
Everley
Fabian
Felix
Ferdinand
Fitch
Fitzgerald
Ford
Francis
Frank
Franklin
Frederic
Gabriel
Gale
Gary
Gavin
Gene
Geoff
Geoffrey
George
Gerald
Gilbert
Giles
Glenn
Goddard
Godfery
Gordon
Greg
Gregary
Griffith
Grover
Gustave
Guy
Hale
Haley
Hamiltion
Hardy
Harlan
Harley
Harold
Harriet
Harry
Harvey
Hayden
Heather
Henry
Herbert
Herman
Hilary
Hiram
Hobart
Hogan
Horace
Howar
Hubery
Hugh
Hugo
Humphrey
Hunter
Hyman
Ian
Ingemar
Ingram
Ira
Isaac
Isidore
Ivan
Ives
Jack
Jacob
James
Jared
Jason
Jay
Jeff
Jeffrey
Jeremy
Jerome
Jerry
Jesse
Jim
Jo
John
Jonas
Jonathan
Joseph
Joshua
Joyce
Julian
Julius
Justin
Keith
Kelly
Ken
Kennedy
Kenneth
Kent
Kerr
Kerwin
Kevin
Kim
King
Kirk
Kyle
Lambert
Lance
Larry
Lawrence
Leif
Len
Lennon
Leo
Leonard
Leopold
Les
Lester
Levi
Lewis
Lionel
Lou
Louis
Lucien
Luther
Lyle
Lyndon
Lynn
Magee
Malcolm
Mandel
Marcus
Marico
Mark
Marlon
Marsh
Marshall
Martin
Marvin
Matt
Matthew
Maurice
Max
Maximilian
Maxwell
Meredith
Merle
Merlin
Michael
Michell
Mick
Mike
Miles
Milo
Monroe
Montague
Moore
Morgan
Mortimer
Morton
Moses
Murphy
Murray
Myron
Nat
Nathan
Nathaniel
Neil
Nelson
Newman
Nicholas
Nick
Nigel
Noah
Noel
Norman
Norton
Ogden
Oliver
Omar
Orville
Osborn
Oscar
Osmond
Oswald
Otis
Otto
Owen
Paddy
Page
Parker
Patrick
Paul
Philip
Porter
Prescott
Primo
Quennel
Quentin
Quincy
Quinn
Quintion
Rachel
Ralap
Reuben
Rex
Richard
Robert
Robin
Rock
Rod
Roderick
Rodney
Rudolf
Rupert
Ryan
Sam
Sampson
Sandy
Saxon
Scott
Sean
Sebastian
Sid
Sidney
Sidon
Sidon
Silvester
Simon
Spencer
Stan
Stanford
Stanley
Stev
Steven
Steward
Tab
Taylor
Ted
Ternence
Theobald
Theodore
Thomas
Tiffany
Tim
Timothy
Tobias
Toby
Todd
Tom
Tony
Troy
Tyler
Tyrone
Ulysses
Upton
Uriah
Valentine
Valentine
Vic
Victor
Vincent
Virgil
Vito
Vivian
Walter
Ward
Warner
Wayne
Webb
Webster
Wendell
Werner
Wilbur
Will
William
Willie
Winfred
Winston
Wright
Wythe
Yale
York
Yves
Zachary
Ziv

View File

@@ -0,0 +1,100 @@
Adams
Alexander
Allen
Anderson
Bailey
Baker
Barnes
Bell
Bennett
Brooks
Brown
Bryant
Butler
Campbell
Carter
Clark
Coleman
Collins
Cook
Cooper
Cox
Davis
Diaz
Edwards
Evans
Flores
Foster
Garcia
Gonzales
Gonzalez
Gray
Green
Griffin
Hall
Harris
Hayes
Henderson
Hernandez
Hill
Howard
Hughes
Jackson
James
Jenkins
Johnson
Jones
Kelly
King
Lee
Lewis
Long
Lopez
Martin
Martinez
Miller
Mitchell
Moore
Morgan
Morris
Murphy
Nelson
Parker
Patterson
Perez
Perry
Peterson
Phillips
Powell
Price
Ramirez
Reed
Richardson
Rivera
Roberts
Robinson
Rodriguez
Rogers
Ross
Russell
Sanchez
Sanders
Scott
Simmons
Smith
Stewart
Taylor
Thomas
Thompson
Torres
Turner
Walker
Ward
Washington
Watson
White
Williams
Wilson
Wood
Wright
Young

View File

@@ -0,0 +1,421 @@
空气
石头
草方块
泥土
圆石
木板
树苗
基岩
岩浆
沙子
沙砾
金矿
铁矿
煤矿
木头
树叶
海绵
玻璃
青金石
发射器
沙石
音符盒
铁轨
活塞
蜘蛛网
草丛
灌木
活塞臂
羊毛
蒲公英
高粱酒
棕蘑菇
红蘑菇
金块
铁块
石台阶
砖块
TNT
书架
苔石
黑曜石
火把
刷怪箱
橡木梯
箱子
红石线
钻石矿
钻石块
工作台
耕地
梯子
圆石梯
拉杆
红石矿
雪块
仙人掌
唱片机
栅栏
南瓜
地狱岩
灵魂沙
萤石
下界门
南瓜灯
怪物蛋
石砖
巨蘑菇
毒蘑菇
铁栏杆
玻璃板
南瓜梗
西瓜梗
藤蔓
砖楼梯
石楼梯
菌丝
睡莲
地狱砖
附魔台
末地门
末地石
龙蛋
红石灯
木台阶
可可果
末影箱
绊线钩
绊线
绿宝石块
命令方块
信标
圆石墙
花盆
胡萝卜
马铃薯
木按钮
铁砧
陷阱箱
压力板
比较器
传感器
红石块
石英矿
漏斗
石英块
投掷器
粘液块
屏障
活板门
海晶石
海晶灯
干草块
地毯
硬粘土
煤炭块
浮冰
大型花
旗帜
红沙石
栅栏门
铁栅栏
木门
末地烛
紫影花
紫珀块
甜菜种
草径
霜冰
打火石
苹果
煤炭
钻石
铁锭
金锭
铁剑
铁锹
铁镐
铁斧
木剑
木锹
木镐
木斧
石剑
石锹
石镐
石斧
钻石剑
钻石锹
钻石镐
钻石斧
木棍
蘑菇煲
金剑
金锹
金镐
金斧
线
羽毛
火药
木锄
石锄
铁锄
钻石锄
金锄
小麦种子
小麦
面包
皮革帽
皮革甲
皮革裤
皮革靴
铁头盔
铁胸甲
铁护腿
铁靴子
钻石盔
钻石甲
钻石腿
钻石靴
金头盔
金胸甲
金护腿
金靴子
燧石
生猪排
熟猪排
金苹果
告示牌
水桶
岩浆桶
矿车
铁门
红石粉
雪球
皮革
牛奶
红砖
粘土
甘蔗
粘液球
鸡蛋
指南针
钓鱼竿
萤石粉
生鱼
熟鱼
染料
骨头
蛋糕
中继器
曲奇
地图
剪刀
西瓜片
种子
西瓜种
生牛肉
牛排
生鸡肉
熟鸡肉
腐肉
末影珠
烈焰棒
恶魂之泪
金粒
地狱疣
药水
玻璃瓶
蜘蛛眼
烈焰粉
岩浆膏
酿造台
炼药锅
末影眼
西瓜
刷怪蛋
附魔瓶
火焰弹
书与笔
成书
绿宝石
展示框
烤马铃薯
毒马铃薯
空地图
金萝卜
下界星
南瓜派
火箭
烟火星
附魔书
生兔肉
熟兔肉
兔肉煲
兔子脚
兔子皮
盔甲架
铁马铠
金马铠
拴绳
命名牌
生羊肉
熟羊肉
云杉门
白桦门
丛林门
橡木门
紫影果
爆裂果
甜菜根
甜菜汤
龙息
光灵箭
药箭
盾牌
鞘翅
云杉船
白桦船
丛林船
橡木船
唱片
经验球
拴绳结
凋灵头
烟花
爬行者
骷髅
蜘蛛
巨人
僵尸
史莱姆
恶魂
僵尸猪
末影人
蠹虫
烈焰人
岩浆怪
末影龙
凋灵
女巫
末影螨
守卫者
潜影贝
长耳兔
蝙蝠
鱿鱼
哞菇
雪傀儡
豹猫
铁傀儡
兔子
村民
刷怪笼
刷怪塔
发电机
永动机
铁炉
粉碎机
电炉
反应堆
采矿机
工业炉
合成机
变压器
电池
青铜粉
电路板
传送带
枪械
手枪
冲锋枪
机枪
特种枪
步枪
大炮
火炮
加农炮
榴弹炮
迫击炮
高射炮
航炮
舰炮
海岸炮
火箭炮
坦克炮
航母
战列舰
驱逐舰
巡洋舰
护卫舰
潜艇
原子弹
氢弹
中子弹
飞机
轰炸机
运输机
侦察机
歼击机
导弹
核导弹
瓦斯弹
鱼雷
来复枪
神机炮
散弹枪
机炮
狙击枪
内燃机
锅炉
轮船
游艇
激光枪
机车
机床
电灯
木桌
木椅
木床
木柜
铁桌
铁椅
铁床
铁柜
酒柜
书橱
电脑桌
梳妆台
梳妆凳
梳子
木梳
骨梳
毛巾
餐车
茶几
玄关
衣架
沙发
屏风

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,626 @@
强硬的
强悍的
强劲的
坚决的
坚信的
坚定的
坚韧的
坚实的
坚贞的
勇敢的
勇猛的
刚毅的
决断的
果敢的
果决的
坚强的
坚忍的
决然的
毅然的
断然的
泼辣的
断腕的
发誓的
干脆的
爽快的
果断的
真诚的
热诚的
至诚的
赤诚的
诚挚的
恳切的
纯真的
率直的
坦率的
笃实的
热忱的
热心的
好客的
客气的
殷勤的
和气的
和蔼的
和善的
亲切的
过谦的
谦卑的
谦恭的
谦和的
谦让的
谦虚的
谦逊的
虚心的
外向的
开朗的
大方的
主动的
俏皮的
敏捷的
乐观的
调皮的
爽脆的
爽朗的
豪爽的
正直的
直率的
直爽的
直言的
爽直的
刚直的
憨直的
耿直的
公正的
公道的
公平的
公允的
正派的
开阔的
豁达的
明朗的
率真的
怒吼的
恐惧的
胆怯的
畏缩的
发慌的
心慌的
恐慌的
激怒的
恼火的
欢乐的
快慰的
开心的
高兴的
愉悦的
微笑的
舒畅的
不适的
欢闹的
欢心的
欢欣的
欢悦的
宽慰的
欢舒的
狂欢的
震怒的
气愤的
担忧的
发愁的
犯愁的
忧伤的
忧愁的
忧心的
愁闷的
悲痛的
悲惨的
悲凉的
哀伤的
哀怨的
伤感的
瘦削的
憔悴的
快乐的
喜悦的
愉快的
畅快的
欢畅的
欢喜的
欢腾的
欢快的
欣喜的
今天的
昨天的
明天的
后天的
上午的
下午的
过去的
未来的
去年的
前年的
散步的
漫步的
踏步的
信步的
转悠的
闲逛的
徜徉的
踉跄的
蹒跚的
小跑的
慢跑的
飞跑的
飞奔的
飞翔的
啜泣的
抽泣的
呜咽的
哀号的
号哭的
痛哭的
大笑的
欢笑的
嬉笑的
狂笑的
嗤笑的
憨笑的
傻笑的
哄笑的
苦笑的
阴笑的
狞笑的
奸笑的
嘲笑的
冷笑的
哈腰的
猫腰的
挺身的
挺胸的
俯身的
躬身的
仰卧的
蜷曲的
倒立的
转体的
屈体的
屈身的
欠身的
纵身的
蹲身的
鞠躬的
曲背的
匍匐的
笔挺的
腾跃的
直立的
翻腾的
前倾的
摇摆的
翻跃的
扭动的
扭转的
旋转的
好吃的
好看的
好玩的
清白的
凛然的
无私的
刚正的
负重的
忠心的
忠贞的
谨慎的
廉洁的
大度的
坦白的
勤奋的
刻苦的
认真的
专注的
踏实的
勤恳的
好学的
高尚的
杰出的
超伦的
自爱的
自尊的
自强的
宽容的
宽宏的
律己的
朴素的
憨厚的
诚实的
忠诚的
诚恳的
天真的
幼稚的
活泼的
聪明的
圆滑的
狡猾的
虚伪的
自私的
任性的
骄傲的
贪婪的
愚蠢的
奸诈的
高傲的
害羞的
内向的
孤僻的
可爱的
招烦的
阴险的
双重的
刻薄的
宽厚的
仁慈的
仁厚的
尖酸的
阴郁的
肤浅的
浅薄的
胆小的
乐天的
达观的
成熟的
稳重的
淘气的
温柔的
体贴的
强硬之
强悍之
强劲之
坚决之
坚信之
坚定之
坚韧之
坚实之
坚贞之
勇敢之
勇猛之
刚毅之
决断之
果敢之
果决之
坚强之
坚忍之
决然之
毅然之
断然之
泼辣之
断腕之
发誓之
干脆之
爽快之
果断之
真诚之
热诚之
至诚之
赤诚之
诚挚之
恳切之
纯真之
率直之
坦率之
笃实之
热忱之
热心之
好客之
客气之
殷勤之
和气之
和蔼之
和善之
亲切之
过谦之
谦卑之
谦恭之
谦和之
谦让之
谦虚之
谦逊之
虚心之
外向之
开朗之
大方之
主动之
俏皮之
敏捷之
乐观之
调皮之
爽脆之
爽朗之
豪爽之
正直之
直率之
直爽之
直言之
爽直之
刚直之
憨直之
耿直之
公正之
公道之
公平之
公允之
正派之
简捷之
开阔之
豁达之
明朗之
率真之
怒吼之
恐惧之
胆怯之
畏缩之
发慌之
心慌之
恐慌之
激怒之
恼火之
欢乐之
快慰之
开心之
高兴之
愉悦之
微笑之
舒畅之
笑噱之
欢闹之
欢心之
欢欣之
欢悦之
宽慰之
欢舒之
狂欢之
震怒之
气愤之
担忧之
发愁之
犯愁之
忧伤之
忧愁之
忧心之
愁闷之
悲痛之
悲惨之
悲凉之
哀伤之
哀怨之
伤感之
瘦削之
憔悴之
快乐之
喜悦之
愉快之
畅快之
欢畅之
欢喜之
欢腾之
欢快之
欣喜之
今天之
昨天之
明天之
后天之
上午之
下午之
过去之
未来之
去年之
前年之
散步之
漫步之
踏步之
信步之
转悠之
闲逛之
徜徉之
踉跄之
蹒跚之
小跑之
慢跑之
飞跑之
飞奔之
飞翔之
啜泣之
抽泣之
呜咽之
哀号之
号哭之
痛哭之
大笑之
欢笑之
嬉笑之
狂笑之
嗤笑之
憨笑之
傻笑之
哄笑之
苦笑之
阴笑之
狞笑之
奸笑之
嘲笑之
冷笑之
哈腰之
猫腰之
挺身之
挺胸之
俯身之
躬身之
仰卧之
蜷曲之
倒立之
转体之
屈体之
屈身之
欠身之
纵身之
蹲身之
鞠躬之
曲背之
匍匐之
笔挺之
腾跃之
直立之
翻腾之
前倾之
摇摆之
翻跃之
扭动之
扭转之
旋转之
好吃之
好看之
好玩之
清白之
凛然之
无私之
刚正之
负重之
忠心之
忠贞之
谨慎之
廉洁之
大度之
坦白之
勤奋之
刻苦之
认真之
专注之
踏实之
勤恳之
好学之
高尚之
杰出之
超伦之
自爱之
自尊之
自强之
宽容之
宽宏之
律己之
朴素之
憨厚之
诚实之
忠诚之
诚恳之
天真之
幼稚之
活泼之
聪明之
圆滑之
狡猾之
虚伪之
自私之
任性之
骄傲之
贪婪之
愚蠢之
奸诈之
高傲之
害羞之
内向之
孤僻之
可爱之
招烦之
阴险之
双重之
刻薄之
宽厚之
仁慈之
仁厚之
尖酸之
阴郁之
肤浅之
浅薄之
胆小之
乐天之
达观之
成熟之
稳重之
淘气之
温柔之
体贴之
苹果味
香蕉味
橘子味
桃子味
荔枝味
龙眼味
桔子味
李子味
葡萄味
青梅味
椰子味
石榴味
草莓味
栗子味
梨子味
樱桃味
苹果味
木瓜味
芒果味
菠萝味
柠檬味
柿子味
柚子味
西瓜味
南瓜味
甘蔗味
小麦味
蜂蜜味
白菜味
黄瓜味
豌豆味
苦瓜味
菠菜味
冬瓜味
茄子味
竹笋味
蚕豆味
萝卜味
辣椒味
鸡肉味
牛肉味
烤肉味
炸鸡味
番茄味
芝士味
榴莲味
山楂味
水果味
陈皮味
花椒味
莲雾味
杨梅味
泥土味
枇杷味
杨桃味
板栗味
瓜子味
桑葚味
猪蹄味
香瓜味
怪味的
塑料味
简单的
枯燥的
仙气的
酸臭味
蜜柑味
火锅味
泡菜味
抹茶味
蓝莓味
无味
甜味
苦味
酸甜味

View File

@@ -0,0 +1,449 @@
蝙蝠
烈焰人
蜘蛛
鸡骑士
爬行者
守卫者
末影龙
末影人
末影螨
唤魔者
恶魂
巨人
尸壳
幻术师
铁傀儡
兔子
羊驼
吉祥物
岩浆怪
哞菇
豹猫
鹦鹉
北极熊
潜影贝
蠹虫
骷髅
骷髅马
史莱姆
雪傀儡
守卫
鱿鱼
流髑
恼鬼
卫道士
村民
女巫
凋灵
僵尸
僵尸马
鲑鱼
河豚
金枪鱼
鲤鱼
黄鳝
电鳗
泥鳅
巫师
弓手
公主
土豪
工程师
程序员
服主
阿婆主
妹子
精灵
兽人
矮人
龙骑士
天使
恶魔
地狱疣
蘑菇
程序
美术
策划
开发
客服
侍卫
侍从
仆人
宅男
炮姐
侏儒
泰坦
血精灵
牛头人
牧师
圣骑士
猎人
德鲁伊
法师
术士
战士
盗贼
蜗牛
黑猪
国王
王子
女王
阴阳师
宗师
建筑师
特种兵
专家
猪骑士
骑士
狼骑士
君主
郡主
君王
骷髅兵
吉吉怪
苦力怕
蜘蛛娘
苦力娘
僵尸娘
末影娘
哞菇娘
凋零娘
搬运工
版主
汉化组
字幕君
画师
漫画家
动画师
声优
歌手
唱见
舞见
人偶师
主播
编剧
导演
吉他手
监督
贝斯手
主唱
鼓手
房管
贝斯
苹果
香蕉
橘子
桃子
荔枝
龙眼
桔子
李子
葡萄
青梅
椰子
石榴
草莓
栗子
梨子
樱桃
木瓜
芒果
菠萝
柠檬
柿子
柚子
无花果
猕猴桃
西红柿
水蜜桃
西瓜
南瓜
甘蔗
小麦
高粱
胡萝卜
马铃薯
可可豆
仙人掌
白菜
黄瓜
豌豆
苦瓜
菠菜
冬瓜
茄子
竹笋
蚕豆
萝卜
辣椒
火龙
冰龙
野狼
野猪
双头龙
猴子
猎豹
企鹅
青蛙
蝌蚪
猛犸
半兽人
亚龙人
半人马
牛头
牛头怪
食人魔
仙女
小仙女
蛇妖
女妖
妖怪
地精
霍比特
半身人
巫女
萨满
魔王
魔女
地狱犬
甲虫
罗刹
石像
雕塑
飞马
树精
娜迦
狼人
猫人
猫女
巨魔
海豹
蜥蜴
三文鱼
希鲮鱼
纸巾
豹子
狮子
狮子王
妖精
英雄
侠客
老鼠
猫咪
狼狗
哈士奇
金毛
萨摩
斗牛犬
牧羊犬
猎犬
吉娃娃
八哥
腊肠犬
柯基
约克夏
松狮
秋田犬
柴犬
博美
藏獒
牛头梗
比熊
二郎神
玉帝
弼马温
波斯猫
英短
布偶
美短
入殓师
清洁工
教师
清道夫
律师
医生
码农
猛男
学姐
学长
师兄
大兵
网红
帅哥
课代表
班长
组长
跳蛛
蜜柑
痒痒鼠
跳跳鼠
作家
维修工
快递员
蝴蝶
瓢虫
蚂蚱
蚂蚁
毛毛虫
屎壳郎
苍蝇
蜜蜂
独角仙
飞蛾
天牛
鼻涕虫
金龟子
红蚂蚁
蚜虫
甲壳虫
蛾子
跳蚤
兔狲
短毛猫
折耳猫
暹罗猫
无毛猫
卷毛猫
猞猁
云豹
花豹
雪豹
灰狼
鬃狼
沙狐
藏狐
北极狐
苍狐
赤狐
大耳狐
画眉鸟
麻雀
鸽子
文鸟
珍珠鸟
蜂鸟
火烈鸟
海鸥
猫头鹰
苍鹰
秃鹫
布谷鸟
乌鸦
灰鹦鹉
蜡嘴鸟
园丁鸟
孔雀
喜鹊
杜鹃
翠鸟
啄木鸟
主管
经理
监工
规划师
药师
护理
护士
会计
咨询师
翻译
记者
兽医
测量员
面壁者
破壁人
中介
厨师
老板
掌勺
营养师
推销员
司机
售票员
管理
导游
调酒师
美容师
理发师
解说员
交易员
保姆
苗圃工
设计师
模特
售货员
保安
警察
消防员
花匠
水电工
建筑工
电工
钳工
修护工
铸造工
缝纫工
顾问
白领
公务员
文秘
天文学家
大法师
小法师
大牧师
小牧师
剑客
刀客
剑术师
森冠蛇
宽头蛇
眼镜蛇
蟒蛇
竹叶青
草蛇
角马
狒狒
斑马
大象
非洲象
巨像
小象
水牛
瞪羚
尼罗鳄
河马
犀牛
小河马
羚羊
山羊
母羊
公羊
野马
野驴
金猫
盘羊
丹顶鹤
白鹤
金雕
天鹅
扬子鳄
白唇鹿
乌龟
蟾蜍
蝎子
沙蝎
鱼人
鱼怪
蜘蛛精

View File

@@ -0,0 +1,37 @@
sabi
sb
shabi
dafeiji
sese
chuo
http
https
www
jiba
weiguang
zhilang
250
520
nima
niba
woba
woma
woshinima
nijia
giegie
daxiong
xiongda
zhanai
niuniu
jiji
penis
yindao
yingdao
yinhui
yinghui
dadoudou
寄吧
傻子
傻逼

View File

@@ -0,0 +1,324 @@
奔跑
爬行
蹦极
游行
吃土
剁手
飞行
滑翔
背书
学习
思考
度假
啃书
吃鲸
洗脸
刷牙
登基
诞生
挖矿
下矿
游蝶泳
蹦迪
吃糖
跳舞
吸猫
遛狗
遛娃
烧烤
拔线
狗吠
上学
飞升
探险
观光
抽卡
划水
潜水
赏花
赏月
品茶
化妆
煮饭
做饭
觉醒
长跑
吸气
呼气
练功
熬夜
听歌
开车
上车
下车
飘着
开学
放假
画画
弹琴
砍树
挖坑
寻宝
追寻
下坠
浮沉
劈叉
踏雪
睡觉
发梦
做梦
冲浪
跑酷
哭泣
咆哮
穿越
潜行
复习
追番
搁浅
吟诗
葬花
大笑
苦笑
尬笑
羽化
消亡
爆破
求佛
鸟瞰
发芽
开花
滑行
练发声
扮鬼
出击
闯关
解密
交易
听写
摘星
落泪
祈祷
冥想
吹牛
研究
购物
补牙
拔牙
舞剑
灌篮
预习
考试
卖萌
扮猪
吃狗粮
坐飞机
开赛车
吃橙子
吃苹果
吃菠萝
吃榴莲
吃香蕉
吃枇杷
喝圣水
跳热舞
吹喇叭
开飞机
水上漂
跳芭蕾
穿西装
穿裙子
看日出
建房子
说相声
吹短笛
练吉他
吹长笛
泡温泉
深呼吸
肝游戏
看涨潮
刷副本
喝阔咯
看日落
等吃饭
做作业
看直播
说谢谢
打豆豆
看视频
看大海
做自己
讲笑话
背古诗
写作文
写散文
斗蛐蛐
捉蛐蛐
捉昆虫
搞科研
种太阳
喝可乐
玩魔方
解方程
拿高分
影分身
捏泥人
做好事
喝咖啡
开班会
发通报
发牢骚
使性子
学音乐
学美术
吆喝
吃辣条
笑嘻嘻
扮可爱
吃钙片
吃麦片
吃披萨
上网
进观园
搬音响
解等式
玩卡牌
蹦跳跳
开汽车
作演讲
练书法
画漫画
开火车
做手工
读英语
吃牛肉
喝鸡汤
过马路
数绵羊
打电话
发短信
听广播
看漫画
变魔术
猜字谜
听音乐
学雷锋
切蔬菜
想问题
吃零食
想休息
拍气球
买玩具
开轿车
捏橘子
放鞭炮
吃水果
削水果
削苹果
削菠萝
削梨子
倒垃圾
改错误
吃醋
喝醋
上岸
吃面条
学数学
学语文
学地理
学英语
学历史
学生物
学物理
做装备
修钟表
修水管
修汽车
修桌子
修手机
修冰箱
吹口琴
弹琵琶
弹古筝
弹钢琴
吹口哨
弹吉他
吹笛子
弹三弦
敲排鼓
敲木鱼
敲渔鼓
撞铁钟
敲锣
拉二胡
拉马头琴
唱歌
唱山歌
唱高音
唱低音
听鸟叫
听摇滚
听爵士
听民谣
推箱子
看动漫
玩电脑
玩手机
逛街
拧瓶盖
打扫
擦椅子
削铅笔
背课文
背英语
念单词
念课文
打副本
放技能
躲技能
躲雨
跺脚
提水桶
涨工资
吃稀饭
喝冷饮
打篮球
踢足球
瞪眼睛
去砍树
闻花香
修管道
修电脑
擦桌子
咬铅笔
玩跳棋
看电视
看足球
看电影
看攻略
下象棋
下围棋
下棋
悔棋
上楼梯
坐电梯
下楼梯
看海报
喝稀饭
喝饮料
喝豆浆
喝果汁
喝豆奶
扔东西
扔垃圾
扔废纸
扔飞镖
丢飞机
开会
散步
游泳
健身
锻炼
迫降
变身

70
name_generator/consts.go Normal file
View File

@@ -0,0 +1,70 @@
package name_generator
const (
DictionaryTypeAdjective DictionaryType = "adjective"
DictionaryTypeGoods DictionaryType = "goods"
DictionaryTypeName DictionaryType = "name"
DictionaryTypePrefix DictionaryType = "prefix"
DictionaryTypeRole DictionaryType = "role"
DictionaryTypeVerb DictionaryType = "verb"
DictionaryTypeSensitive DictionaryType = "sensitive"
DictionaryTypeSingleSurnames DictionaryType = "single_surnames"
DictionaryTypeCompoundSurnames DictionaryType = "compound_surnames"
DictionaryTypeChineseFirstNameFemale DictionaryType = "chinese_first_name_female"
DictionaryTypeChineseFirstNameMale DictionaryType = "chinese_first_name_male"
DictionaryTypeEnglishFirstNameFemale DictionaryType = "english_first_name_female"
DictionaryTypeEnglishFirstNameMale DictionaryType = "english_first_name_male"
DictionaryTypeEnglishLastName DictionaryType = "english_last_name"
DictionaryTypeJapaneseName DictionaryType = "japanese_name"
DictionaryTypeJapaneseSurnames DictionaryType = "japanese_surnames"
DictionaryTypeJapaneseLastName DictionaryType = "japanese_last_name"
)
var Scheme1 = CombinedDictionaryType{
DictionaryTypePrefix,
DictionaryTypeName,
DictionaryTypeVerb,
}
var Scheme2 = CombinedDictionaryType{
DictionaryTypePrefix,
DictionaryTypeRole,
DictionaryTypeVerb,
}
var Scheme3 = CombinedDictionaryType{
DictionaryTypePrefix,
DictionaryTypeName,
DictionaryTypeAdjective,
}
var Scheme4 = CombinedDictionaryType{
DictionaryTypePrefix,
DictionaryTypeVerb,
DictionaryTypeRole,
}
var Scheme5 = CombinedDictionaryType{
DictionaryTypePrefix,
DictionaryTypeVerb,
DictionaryTypeName,
}
var Scheme6 = CombinedDictionaryType{
DictionaryTypeName,
DictionaryTypePrefix,
DictionaryTypeGoods,
}
var SchemeChineseNameFemale = CombinedDictionaryType{
DictionaryTypeSingleSurnames,
DictionaryTypeChineseFirstNameFemale,
}
var SchemeChineseNameMale = CombinedDictionaryType{
DictionaryTypeSingleSurnames,
DictionaryTypeChineseFirstNameMale,
}

321
name_generator/generator.go Normal file
View File

@@ -0,0 +1,321 @@
package name_generator
import (
"bufio"
"bytes"
"errors"
"fmt"
"math/rand"
"os"
"strings"
"github.com/tx7do/go-utils/name_generator/assets"
)
type Generator struct {
dictionaries DictionaryMap
}
func New() *Generator {
g := &Generator{
dictionaries: make(DictionaryMap),
}
g.init()
return g
}
func (g *Generator) init() {
g.loadAllDict()
}
func (g *Generator) loadAllDict() {
_ = g.LoadDict(DictionaryTypeAdjective, assets.Adjective)
_ = g.LoadDict(DictionaryTypeGoods, assets.Goods)
_ = g.LoadDict(DictionaryTypeName, assets.Name)
_ = g.LoadDict(DictionaryTypePrefix, assets.Prefix)
_ = g.LoadDict(DictionaryTypeRole, assets.Role)
_ = g.LoadDict(DictionaryTypeVerb, assets.Verb)
_ = g.LoadDict(DictionaryTypeSensitive, assets.Sensitive)
_ = g.LoadDict(DictionaryTypeSingleSurnames, assets.ChineseSingleSurnames)
_ = g.LoadDict(DictionaryTypeCompoundSurnames, assets.ChineseCompoundSurnames)
_ = g.LoadDict(DictionaryTypeChineseFirstNameFemale, assets.ChineseFirstNameFemale)
_ = g.LoadDict(DictionaryTypeChineseFirstNameMale, assets.ChineseFirstNameMale)
//_ = g.LoadDict(DictionaryTypeEnglishFirstNameFemale, assets.EnglishFirstNameFemale)
//_ = g.LoadDict(DictionaryTypeEnglishFirstNameMale, assets.EnglishFirstNameMale)
//_ = g.LoadDict(DictionaryTypeEnglishLastName, assets.EnglishLastName)
}
func (g *Generator) LoadDict(dictType DictionaryType, textData []byte) error {
if g.dictionaries == nil {
g.dictionaries = make(DictionaryMap)
}
if _, ok := g.dictionaries[dictType]; ok {
return errors.New("dictionary already exists for type: " + string(dictType))
}
var dict Dictionary
reader := bytes.NewReader(textData)
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
word := strings.TrimSpace(scanner.Text())
if word == "" {
continue // Skip empty lines
}
dict = append(dict, word)
}
if err := scanner.Err(); err != nil {
return err
}
g.dictionaries[dictType] = dict
return nil
}
func (g *Generator) LoadDictFromFile(dictType DictionaryType, filePath string) error {
if g.dictionaries == nil {
g.dictionaries = make(DictionaryMap)
}
if _, ok := g.dictionaries[dictType]; ok {
return errors.New("dictionary already exists for type: " + string(dictType))
}
file, err := os.Open(filePath)
if err != nil {
return err
}
defer func(file *os.File) {
err = file.Close()
if err != nil {
fmt.Println("Error closing file:", err)
}
}(file)
var dict Dictionary
scanner := bufio.NewScanner(file)
for scanner.Scan() {
word := strings.TrimSpace(scanner.Text())
if word == "" {
continue // Skip empty lines
}
dict = append(dict, strings.TrimSpace(word))
}
if err := scanner.Err(); err != nil {
return err
}
g.dictionaries[dictType] = dict
return nil
}
func (g *Generator) ExistDict(dictType DictionaryType) bool {
if g.dictionaries == nil {
return false
}
_, exists := g.dictionaries[dictType]
return exists
}
func (g *Generator) DictCount() int {
if g.dictionaries == nil {
return 0
}
return len(g.dictionaries)
}
func (g *Generator) DictItemCount(dictType DictionaryType) int {
if g.dictionaries == nil {
return 0
}
dict, exists := g.dictionaries[dictType]
if !exists {
return 0
}
return len(dict)
}
func (g *Generator) randomWordFromDict(dictType DictionaryType) string {
dict, exists := g.dictionaries[dictType]
if !exists {
return ""
}
if len(dict) == 0 {
return ""
}
randomIndex := rand.Intn(len(dict))
return dict[randomIndex]
}
func (g *Generator) Generate(dictTypes CombinedDictionaryType) string {
if len(dictTypes) == 0 {
return ""
}
parts := g.GenerateParts(dictTypes)
if len(parts) == 0 {
return ""
}
return strings.Join(parts, "")
}
func (g *Generator) GenerateParts(dictTypes CombinedDictionaryType) []string {
if len(dictTypes) == 0 {
return nil
}
var parts []string
for _, dictType := range dictTypes {
word := g.randomWordFromDict(dictType)
if word != "" {
parts = append(parts, word)
}
}
return parts
}
func (g *Generator) GenerateChineseName(firstNameCount int, isFemale, isCompoundSurname bool) string {
if !g.ExistDict(DictionaryTypeSingleSurnames) {
_ = g.LoadDict(DictionaryTypeSingleSurnames, assets.ChineseSingleSurnames)
}
if !g.ExistDict(DictionaryTypeCompoundSurnames) {
_ = g.LoadDict(DictionaryTypeCompoundSurnames, assets.ChineseCompoundSurnames)
}
if !g.ExistDict(DictionaryTypeChineseFirstNameFemale) {
_ = g.LoadDict(DictionaryTypeChineseFirstNameFemale, assets.ChineseFirstNameFemale)
}
if !g.ExistDict(DictionaryTypeChineseFirstNameMale) {
_ = g.LoadDict(DictionaryTypeChineseFirstNameMale, assets.ChineseFirstNameMale)
}
if firstNameCount < 1 || firstNameCount > 2 {
return ""
}
dictTypes := make(CombinedDictionaryType, 0)
if isCompoundSurname {
dictTypes = append(dictTypes, DictionaryTypeCompoundSurnames)
} else {
dictTypes = append(dictTypes, DictionaryTypeSingleSurnames)
}
for i := 0; i < firstNameCount; i++ {
if isFemale {
dictTypes = append(dictTypes, DictionaryTypeChineseFirstNameFemale)
} else {
dictTypes = append(dictTypes, DictionaryTypeChineseFirstNameMale)
}
}
parts := g.GenerateParts(dictTypes)
if len(parts) == 0 {
return ""
}
return strings.Join(parts, "")
}
func (g *Generator) GenerateEnglishName(firstNameCount, middleNameCount, lastNameCount int, isFemale bool) string {
if !g.ExistDict(DictionaryTypeEnglishFirstNameFemale) {
_ = g.LoadDict(DictionaryTypeEnglishFirstNameFemale, assets.EnglishFirstNameFemale)
}
if !g.ExistDict(DictionaryTypeEnglishFirstNameMale) {
_ = g.LoadDict(DictionaryTypeEnglishFirstNameMale, assets.EnglishFirstNameMale)
}
if !g.ExistDict(DictionaryTypeEnglishLastName) {
_ = g.LoadDict(DictionaryTypeEnglishLastName, assets.EnglishLastName)
}
if firstNameCount < 1 || firstNameCount > 2 ||
lastNameCount < 1 {
return ""
}
dictTypes := make(CombinedDictionaryType, 0)
for i := 0; i < firstNameCount; i++ {
if isFemale {
dictTypes = append(dictTypes, DictionaryTypeEnglishFirstNameFemale)
} else {
dictTypes = append(dictTypes, DictionaryTypeEnglishFirstNameMale)
}
}
for i := 0; i < middleNameCount; i++ {
if isFemale {
dictTypes = append(dictTypes, DictionaryTypeEnglishFirstNameFemale)
} else {
dictTypes = append(dictTypes, DictionaryTypeEnglishFirstNameMale)
}
}
for i := 0; i < lastNameCount; i++ {
dictTypes = append(dictTypes, DictionaryTypeEnglishLastName)
}
parts := g.GenerateParts(dictTypes)
if len(parts) == 0 {
return ""
}
return strings.Join(parts, " ")
}
func (g *Generator) GenerateJapaneseNameCN() string {
if !g.ExistDict(DictionaryTypeJapaneseName) {
_ = g.LoadDict(DictionaryTypeJapaneseName, assets.JapaneseNamesCorpus)
}
dictTypes := CombinedDictionaryType{
DictionaryTypeJapaneseName,
}
parts := g.GenerateParts(dictTypes)
if len(parts) == 0 {
return ""
}
return strings.Join(parts, "")
}
func (g *Generator) GenerateJapaneseName() string {
if !g.ExistDict(DictionaryTypeJapaneseSurnames) {
_ = g.LoadDict(DictionaryTypeJapaneseSurnames, assets.JapaneseSurnames)
}
if !g.ExistDict(DictionaryTypeJapaneseLastName) {
_ = g.LoadDict(DictionaryTypeJapaneseLastName, assets.JapaneseLastName)
}
dictTypes := CombinedDictionaryType{
DictionaryTypeJapaneseSurnames,
DictionaryTypeJapaneseLastName,
}
parts := g.GenerateParts(dictTypes)
if len(parts) == 0 {
return ""
}
return strings.Join(parts, "")
}

View File

@@ -0,0 +1,97 @@
package name_generator
import (
"strings"
"testing"
)
func TestGenerate(t *testing.T) {
g := New()
dictTypes := Scheme5
result := g.Generate(dictTypes)
if result == "" {
t.Errorf("result is empty, please check the dictionary data")
} else {
t.Logf("generate`s nickname: %s", result)
}
}
func TestGenerateParts(t *testing.T) {
g := New()
dictTypes := Scheme6
parts := g.GenerateParts(dictTypes)
if len(parts) == 0 {
t.Errorf("result is empty, please check the dictionary data")
} else {
t.Logf("generate`s parts: %v", parts)
}
parts[0] = parts[0] + "#的"
result := strings.Join(parts, "")
t.Logf("generate`s nickname: %s", result)
}
func TestGenerateChineseName(t *testing.T) {
g := New()
result := g.GenerateChineseName(1, true, false)
if result == "" {
t.Errorf("result is empty, please check the dictionary data")
} else {
t.Logf("Generated single surname single name (female): %s", result)
}
result = g.GenerateChineseName(2, false, true)
if result == "" {
t.Errorf("result is empty, please check the dictionary data")
} else {
t.Logf("Generated compound surname double name (male): %s", result)
}
}
func TestGenerateEnglishName(t *testing.T) {
g := New()
result := g.GenerateEnglishName(1, 0, 1, true)
if result == "" {
t.Errorf("result is empty, please check the dictionary data")
} else {
t.Logf("Generated female English name: %s", result)
}
result = g.GenerateEnglishName(2, 0, 1, false)
if result == "" {
t.Errorf("result is empty, please check the dictionary data")
} else {
t.Logf("Generated male English name: %s", result)
}
}
func TestGenerateJapaneseNameCN(t *testing.T) {
g := New()
result := g.GenerateJapaneseNameCN()
if result == "" {
t.Errorf("result is empty, please check the dictionary data")
} else {
t.Logf("Generated Japanese name (CN): %s", result)
}
}
func TestGenerateJapaneseName(t *testing.T) {
g := New()
result := g.GenerateJapaneseName()
if result == "" {
t.Errorf("result is empty, please check the dictionary data")
} else {
t.Logf("Generated Japanese name: %s", result)
}
}

7
name_generator/go.mod Normal file
View File

@@ -0,0 +1,7 @@
module github.com/tx7do/go-utils/name_generator
go 1.23.0
toolchain go1.23.2
replace github.com/tx7do/go-utils => ../

0
name_generator/go.sum Normal file
View File

View File

@@ -0,0 +1,8 @@
package name_generator
type DictionaryType string
type Dictionary []string
type DictionaryMap map[DictionaryType]Dictionary
type CombinedDictionaryType []DictionaryType

View File

@@ -1,47 +0,0 @@
package order_id
import (
"fmt"
"math/rand"
"sync/atomic"
"time"
"github.com/tx7do/go-utils/trans"
)
type idCounter uint32
func (c *idCounter) Increase() uint32 {
cur := *c
atomic.AddUint32((*uint32)(c), 1)
atomic.CompareAndSwapUint32((*uint32)(c), 1000, 0)
return uint32(cur)
}
var orderIdIndex idCounter
// GenerateOrderIdWithRandom 生成20位订单号前缀+时间+随机数
func GenerateOrderIdWithRandom(prefix string, split string, tm *time.Time) string {
if tm == nil {
tm = trans.Time(time.Now())
}
index := rand.Intn(1000)
return fmt.Sprintf("%s%s%.4d%s%.2d%s%.2d%s%.2d%s%.2d%s%.2d%s%.4d", prefix, split,
tm.Year(), split, tm.Month(), split, tm.Day(), split,
tm.Hour(), split, tm.Minute(), split, tm.Second(), split, index)
}
// GenerateOrderIdWithIncreaseIndex 生成20位订单号前缀+时间+自增长索引
func GenerateOrderIdWithIncreaseIndex(prefix string, split string, tm *time.Time) string {
if tm == nil {
tm = trans.Time(time.Now())
}
index := orderIdIndex.Increase()
return fmt.Sprintf("%s%s%.4d%s%.2d%s%.2d%s%.2d%s%.2d%s%.2d%s%.4d", prefix, split,
tm.Year(), split, tm.Month(), split, tm.Day(), split,
tm.Hour(), split, tm.Minute(), split, tm.Second(), split, index)
}

View File

@@ -1,63 +0,0 @@
package order_id
import (
"fmt"
"sync"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/tx7do/go-utils/trans"
)
func TestGenerateOrderIdWithRandom(t *testing.T) {
fmt.Println(GenerateOrderIdWithRandom("PT", "-", trans.Time(time.Now())))
tm := time.Now()
var ids map[string]bool
ids = make(map[string]bool)
count := 100
for i := 0; i < count; i++ {
ids[GenerateOrderIdWithRandom("PT", "", trans.Time(tm))] = true
}
assert.Equal(t, count, len(ids))
}
func TestGenerateOrderIdWithIndex(t *testing.T) {
tm := time.Now()
fmt.Println(GenerateOrderIdWithIncreaseIndex("PT", "", trans.Time(tm)))
ids := make(map[string]bool)
count := 100
for i := 0; i < count; i++ {
ids[GenerateOrderIdWithIncreaseIndex("PT", "", trans.Time(tm))] = true
}
assert.Equal(t, count, len(ids))
}
func TestGenerateOrderIdWithIndexThread(t *testing.T) {
tm := time.Now()
var wg sync.WaitGroup
var ids sync.Map
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
for i := 0; i < 100; i++ {
id := GenerateOrderIdWithIncreaseIndex("PT", "", trans.Time(tm))
ids.Store(id, true)
}
wg.Done()
}()
}
wg.Wait()
aLen := 0
ids.Range(func(k, v interface{}) bool {
aLen++
return true
})
assert.Equal(t, 1000, aLen)
}

14
password/README.md Normal file
View File

@@ -0,0 +1,14 @@
# 密码加解密
算法列表
| 算法名 | 特点 | 用途 |
|-----------------------------------------------|-------------------------------|------------------------|
| Bcrypt | 基于 Blowfish 算法,内置盐值,支持调整计算成本。 | 密码存储,防止暴力破解。 |
| Argon2 | 内存硬性哈希算法,支持多种参数调整(内存、时间、并行度)。 | 码存储,适合高安全性需求。 |
| PBKDF2 | 基于 HMAC 的密钥派生函数,支持多种哈希算法。 | 密码存储,兼容性好。 |
| SHA-256/SHA-512 | 快速单向哈希算法,无内置盐值。 | 数据完整性校验,需手动添加盐值用于密码存储。 |
| AES (Advanced Encryption Standard) | 对称加密算法,支持 128/192/256 位密钥。 | 数据加密传输或存储。 |
| RSA | 非对称加密算法,基于大整数分解难题。 | 数据加密、数字签名。 |
| ECDSA/ECDH (椭圆曲线算法) | 基于椭圆曲线的非对称加密,密钥更短但安全性高。 | 数据完整性和认证。 |
| HMAC (Hash-based Message Authentication Code) | 基于哈希算法的消息认证码。 | 数据完整性和认证。 |

111
password/argon2.go Normal file
View File

@@ -0,0 +1,111 @@
package password
import (
"crypto/rand"
"crypto/subtle"
"encoding/base64"
"errors"
"fmt"
"strings"
"golang.org/x/crypto/argon2"
)
// Argon2Crypto 实现 Argon2id 密码哈希算法
type Argon2Crypto struct {
// 参数可配置,默认使用推荐值
Memory uint32
Iterations uint32
Parallelism uint8
SaltLength uint32
KeyLength uint32
}
// NewArgon2Crypto 创建带默认参数的 Argon2 加密器
func NewArgon2Crypto() *Argon2Crypto {
return &Argon2Crypto{
Memory: 64 * 1024, // 64MB
Iterations: 3,
Parallelism: 2,
SaltLength: 16,
KeyLength: 32,
}
}
// Encrypt 实现密码加密
func (a *Argon2Crypto) Encrypt(password string) (string, error) {
// 生成随机盐值
salt := make([]byte, a.SaltLength)
if _, err := rand.Read(salt); err != nil {
return "", err
}
// 生成哈希
hash := argon2.IDKey(
[]byte(password),
salt,
a.Iterations,
a.Memory,
a.Parallelism,
a.KeyLength,
)
// 格式化输出(兼容标准格式)
return fmt.Sprintf(
"$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s",
argon2.Version,
a.Memory,
a.Iterations,
a.Parallelism,
base64.RawStdEncoding.EncodeToString(salt),
base64.RawStdEncoding.EncodeToString(hash),
), nil
}
// Verify 验证密码
func (a *Argon2Crypto) Verify(password, encrypted string) (bool, error) {
// 解析哈希字符串
parts := strings.Split(encrypted, "$")
if len(parts) != 6 {
return false, errors.New("无效的 Argon2 哈希格式")
}
// 解析参数
var version int
var memory, iterations uint32
var parallelism uint8
_, err := fmt.Sscanf(parts[2], "v=%d", &version)
if err != nil || version != argon2.Version {
return false, errors.New("不支持的 Argon2 版本")
}
_, err = fmt.Sscanf(parts[3], "m=%d,t=%d,p=%d", &memory, &iterations, &parallelism)
if err != nil {
return false, errors.New("无效的 Argon2 参数")
}
// 解码盐值和哈希
salt, err := base64.RawStdEncoding.DecodeString(parts[4])
if err != nil {
return false, err
}
decodedHash, err := base64.RawStdEncoding.DecodeString(parts[5])
if err != nil {
return false, err
}
keyLength := uint32(len(decodedHash))
// 使用相同参数生成新哈希
newHash := argon2.IDKey(
[]byte(password),
salt,
iterations,
memory,
parallelism,
keyLength,
)
// 安全比较
return subtle.ConstantTimeCompare(newHash, decodedHash) == 1, nil
}

41
password/argon2_test.go Normal file
View File

@@ -0,0 +1,41 @@
package password
import (
"testing"
)
func TestArgon2Crypto_EncryptAndVerify(t *testing.T) {
crypto := NewArgon2Crypto()
// 测试加密
password := "securepassword"
encrypted, err := crypto.Encrypt(password)
if err != nil {
t.Fatalf("加密失败: %v", err)
}
if encrypted == "" {
t.Fatal("加密结果为空")
}
t.Log(encrypted)
// 测试验证成功
isValid, err := crypto.Verify(password, encrypted)
if err != nil {
t.Fatalf("验证失败: %v", err)
}
if !isValid {
t.Fatal("验证未通过,密码应匹配")
}
// 测试验证失败
isValid, err = crypto.Verify("wrongpassword", encrypted)
if err != nil {
t.Fatalf("验证失败: %v", err)
}
if isValid {
t.Fatal("验证通过,但密码不应匹配")
}
}

34
password/bcrypt.go Normal file
View File

@@ -0,0 +1,34 @@
package password
import (
"errors"
"golang.org/x/crypto/bcrypt"
)
type BCryptCrypto struct{}
func NewBCryptCrypto() *BCryptCrypto {
return &BCryptCrypto{}
}
// Encrypt 使用 bcrypt 加密密码,返回加密后的字符串和空盐值
func (b *BCryptCrypto) Encrypt(password string) (encrypted string, err error) {
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return "", err
}
return string(hash), nil
}
// Verify 验证密码是否匹配加密后的字符串
func (b *BCryptCrypto) Verify(password, encrypted string) (bool, error) {
err := bcrypt.CompareHashAndPassword([]byte(encrypted), []byte(password))
if err != nil {
if errors.Is(err, bcrypt.ErrMismatchedHashAndPassword) {
return false, nil
}
return false, err
}
return true, nil
}

41
password/bcrypt_test.go Normal file
View File

@@ -0,0 +1,41 @@
package password
import (
"testing"
)
func TestBCryptCrypto_EncryptAndVerify(t *testing.T) {
crypto := NewBCryptCrypto()
// 测试加密
password := "securepassword"
encrypted, err := crypto.Encrypt(password)
if err != nil {
t.Fatalf("加密失败: %v", err)
}
if encrypted == "" {
t.Fatal("加密结果为空")
}
t.Log(encrypted)
// 测试验证成功
isValid, err := crypto.Verify(password, encrypted)
if err != nil {
t.Fatalf("验证失败: %v", err)
}
if !isValid {
t.Fatal("验证未通过,密码应匹配")
}
// 测试验证失败
isValid, err = crypto.Verify("wrongpassword", encrypted)
if err != nil {
t.Fatalf("验证失败: %v", err)
}
if isValid {
t.Fatal("验证通过,但密码不应匹配")
}
}

142
password/ecdsa_ecdh.go Normal file
View File

@@ -0,0 +1,142 @@
package password
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"errors"
"math/big"
"strings"
)
// ECDSACrypto 实现基于 ECDSA 的加密和验证
type ECDSACrypto struct {
privateKey *ecdsa.PrivateKey
publicKey *ecdsa.PublicKey
}
// NewECDSACrypto 创建一个新的 ECDSACrypto 实例
func NewECDSACrypto() (*ECDSACrypto, error) {
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, err
}
return &ECDSACrypto{
privateKey: privateKey,
publicKey: &privateKey.PublicKey,
}, nil
}
// Encrypt 使用 ECDSA 对消息进行签名
func (e *ECDSACrypto) Encrypt(plainPassword string) (string, error) {
if plainPassword == "" {
return "", errors.New("密码不能为空")
}
hash := sha256.Sum256([]byte(plainPassword))
r, s, err := ecdsa.Sign(rand.Reader, e.privateKey, hash[:])
if err != nil {
return "", err
}
signature := r.String() + "$" + s.String()
return "ecdsa$" + signature, nil
}
// Verify 验证消息的签名是否有效
func (e *ECDSACrypto) Verify(plainPassword, encrypted string) (bool, error) {
if plainPassword == "" || encrypted == "" {
return false, errors.New("密码或加密字符串不能为空")
}
parts := strings.SplitN(encrypted, "$", 3)
if len(parts) != 3 || parts[0] != "ecdsa" {
return false, errors.New("加密字符串格式无效")
}
r := new(big.Int)
s := new(big.Int)
r.SetString(parts[1], 10)
s.SetString(parts[2], 10)
hash := sha256.Sum256([]byte(plainPassword))
return ecdsa.Verify(e.publicKey, hash[:], r, s), nil
}
// ECDHCrypto 实现基于 ECDH 的密钥交换
type ECDHCrypto struct {
privateKey *ecdsa.PrivateKey
publicKey *ecdsa.PublicKey
}
// NewECDHCrypto 创建一个新的 ECDHCrypto 实例
func NewECDHCrypto() (*ECDHCrypto, error) {
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, err
}
return &ECDHCrypto{
privateKey: privateKey,
publicKey: &privateKey.PublicKey,
}, nil
}
// Encrypt 返回公钥作为加密结果
func (e *ECDHCrypto) Encrypt(plainPassword string) (string, error) {
if plainPassword == "" {
return "", errors.New("密码不能为空")
}
publicKeyBytes := elliptic.Marshal(e.privateKey.Curve, e.publicKey.X, e.publicKey.Y)
return "ecdh$" + base64.StdEncoding.EncodeToString(publicKeyBytes), nil
}
// Verify 验证共享密钥是否一致
func (e *ECDHCrypto) Verify(plainPassword, encrypted string) (bool, error) {
if plainPassword == "" || encrypted == "" {
return false, errors.New("密码或加密字符串不能为空")
}
parts := strings.SplitN(encrypted, "$", 2)
if len(parts) != 2 || parts[0] != "ecdh" {
return false, errors.New("加密字符串格式无效")
}
publicKeyBytes, err := base64.StdEncoding.DecodeString(parts[1])
if err != nil {
return false, err
}
x, y := elliptic.Unmarshal(e.privateKey.Curve, publicKeyBytes)
if x == nil || y == nil {
return false, errors.New("无效的公钥")
}
sharedX, _ := e.privateKey.Curve.ScalarMult(x, y, e.privateKey.D.Bytes())
expectedHash := sha256.Sum256(sharedX.Bytes())
actualHash := sha256.Sum256([]byte(plainPassword))
return expectedHash == actualHash, nil
}
func (e *ECDHCrypto) DeriveSharedSecret(publicKey string) ([]byte, error) {
// 解码对方的公钥
publicKeyBytes, err := base64.StdEncoding.DecodeString(publicKey)
if err != nil {
return nil, err
}
// 反序列化公钥
x, y := elliptic.Unmarshal(e.privateKey.Curve, publicKeyBytes)
if x == nil || y == nil {
return nil, errors.New("无效的公钥")
}
// 计算共享密钥
sharedX, _ := e.privateKey.Curve.ScalarMult(x, y, e.privateKey.D.Bytes())
// 返回共享密钥的字节表示
return sharedX.Bytes(), nil
}

View File

@@ -0,0 +1,60 @@
package password
import (
"testing"
)
func TestECDSACrypto_EncryptAndVerify(t *testing.T) {
crypto, err := NewECDSACrypto()
if err != nil {
t.Fatalf("创建 ECDSACrypto 实例失败: %v", err)
}
message := "test message"
// 签名消息
encrypted, err := crypto.Encrypt(message)
if err != nil {
t.Fatalf("签名失败: %v", err)
}
// 验证签名
isValid, err := crypto.Verify(message, encrypted)
if err != nil {
t.Fatalf("验证失败: %v", err)
}
if !isValid {
t.Fatal("签名验证未通过")
}
}
func TestECDHCrypto_EncryptAndVerify(t *testing.T) {
crypto1, err := NewECDHCrypto()
if err != nil {
t.Fatalf("创建 ECDHCrypto 实例1失败: %v", err)
}
crypto2, err := NewECDHCrypto()
if err != nil {
t.Fatalf("创建 ECDHCrypto 实例2失败: %v", err)
}
message := "test message"
// 获取公钥
encrypted, err := crypto1.Encrypt(message)
if err != nil {
t.Fatalf("加密失败: %v", err)
}
// 验证共享密钥
isValid, err := crypto2.Verify(message, encrypted)
if err != nil {
t.Fatalf("验证失败: %v", err)
}
if !isValid {
t.Fatal("共享密钥验证未通过")
}
}

11
password/go.mod Normal file
View File

@@ -0,0 +1,11 @@
module github.com/tx7do/go-utils/password
go 1.23.0
toolchain go1.23.2
require golang.org/x/crypto v0.39.0
require golang.org/x/sys v0.33.0 // indirect
replace github.com/tx7do/go-utils => ../

4
password/go.sum Normal file
View File

@@ -0,0 +1,4 @@
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=

50
password/hmac.go Normal file
View File

@@ -0,0 +1,50 @@
package password
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"errors"
)
// HMACCrypto 实现基于 HMAC 的加密和验证
type HMACCrypto struct {
secretKey []byte
}
// NewHMACCrypto 创建一个新的 HMACCrypto 实例
func NewHMACCrypto(secretKey string) *HMACCrypto {
return &HMACCrypto{
secretKey: []byte(secretKey),
}
}
// Encrypt 使用 HMAC-SHA256 对数据进行加密
func (h *HMACCrypto) Encrypt(data string) (string, error) {
if data == "" {
return "", errors.New("数据不能为空")
}
mac := hmac.New(sha256.New, h.secretKey)
_, err := mac.Write([]byte(data))
if err != nil {
return "", err
}
hash := mac.Sum(nil)
return hex.EncodeToString(hash), nil
}
// Verify 验证数据的 HMAC 值是否匹配
func (h *HMACCrypto) Verify(data, encrypted string) (bool, error) {
if data == "" || encrypted == "" {
return false, errors.New("数据或加密字符串不能为空")
}
expectedHash, err := h.Encrypt(data)
if err != nil {
return false, err
}
return hmac.Equal([]byte(expectedHash), []byte(encrypted)), nil
}

43
password/hmac_test.go Normal file
View File

@@ -0,0 +1,43 @@
package password
import (
"testing"
)
func TestHMACCrypto_EncryptAndVerify(t *testing.T) {
secretKey := "mysecretkey"
crypto := NewHMACCrypto(secretKey)
data := "testdata"
// 测试加密
encrypted, err := crypto.Encrypt(data)
if err != nil {
t.Fatalf("加密失败: %v", err)
}
if encrypted == "" {
t.Fatal("加密结果为空")
}
t.Log(encrypted)
// 测试验证
isValid, err := crypto.Verify(data, encrypted)
if err != nil {
t.Fatalf("验证失败: %v", err)
}
if !isValid {
t.Fatal("验证结果不匹配")
}
// 测试验证失败的情况
isValid, err = crypto.Verify("wrongdata", encrypted)
if err != nil {
t.Fatalf("验证失败: %v", err)
}
if isValid {
t.Fatal("验证结果错误,预期验证失败")
}
}

29
password/interface.go Normal file
View File

@@ -0,0 +1,29 @@
package password
import (
"errors"
"strings"
)
// Crypto 密码加解密接口
type Crypto interface {
// Encrypt 加密密码,返回加密后的字符串(包含算法标识和盐值)
Encrypt(plainPassword string) (encrypted string, err error)
// Verify 验证密码是否匹配
Verify(plainPassword, encrypted string) (bool, error)
}
func CreateCrypto(algorithm string) (Crypto, error) {
algorithm = strings.ToLower(algorithm)
switch algorithm {
case "bcrypt":
return NewBCryptCrypto(), nil
case "pbkdf2":
return NewPBKDF2Crypto(), nil
case "argon2":
return NewArgon2Crypto(), nil
default:
return nil, errors.New("不支持的加密算法")
}
}

150
password/pbkdf2.go Normal file
View File

@@ -0,0 +1,150 @@
package password
import (
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"crypto/sha512"
"encoding/base64"
"encoding/binary"
"errors"
"fmt"
"hash"
"strconv"
"strings"
)
// PBKDF2Crypto 实现 PBKDF2-HMAC 密码哈希算法
type PBKDF2Crypto struct {
// 可配置参数,默认使用推荐值
Iterations int
KeyLength int
Hash func() hash.Hash
HashName string
}
// NewPBKDF2Crypto 创建带默认参数的 PBKDF2 加密器 (SHA256)
func NewPBKDF2Crypto() *PBKDF2Crypto {
return &PBKDF2Crypto{
Iterations: 310000, // NIST 推荐最小值
KeyLength: 32, // 256-bit
Hash: sha256.New,
HashName: "sha256",
}
}
// NewPBKDF2WithSHA512 创建使用 SHA512 的 PBKDF2 加密器
func NewPBKDF2WithSHA512() *PBKDF2Crypto {
return &PBKDF2Crypto{
Iterations: 600000, // SHA512 需要更多迭代
KeyLength: 64, // 512-bit
Hash: sha512.New,
HashName: "sha512",
}
}
// Encrypt 实现密码加密
func (p *PBKDF2Crypto) Encrypt(password string) (string, error) {
// 生成随机盐值 (16 bytes 推荐最小值)
salt := make([]byte, 16)
if _, err := rand.Read(salt); err != nil {
return "", err
}
// 生成密钥
key := pbkdf2Key([]byte(password), salt, p.Iterations, p.KeyLength, p.Hash)
// 格式: pbkdf2:<hash>:<iterations>:<base64-salt>:<base64-key>
return fmt.Sprintf(
"pbkdf2:%s:%d:%s:%s",
p.HashName,
p.Iterations,
base64.RawStdEncoding.EncodeToString(salt),
base64.RawStdEncoding.EncodeToString(key),
), nil
}
// Verify 验证密码
func (p *PBKDF2Crypto) Verify(password, encrypted string) (bool, error) {
// 解析哈希字符串
parts := strings.Split(encrypted, ":")
if len(parts) != 5 || parts[0] != "pbkdf2" {
return false, errors.New("无效的 PBKDF2 哈希格式")
}
// 解析参数
hashName := parts[1]
iterations, err := strconv.Atoi(parts[2])
if err != nil {
return false, errors.New("无效的迭代次数")
}
salt, err := base64.RawStdEncoding.DecodeString(parts[3])
if err != nil {
return false, err
}
expectedKey, err := base64.RawStdEncoding.DecodeString(parts[4])
if err != nil {
return false, err
}
// 根据哈希名称选择哈希函数
hashFunc, ok := getHashFunction(hashName)
if !ok {
return false, fmt.Errorf("不支持的哈希算法: %s", hashName)
}
// 生成新密钥
keyLength := len(expectedKey)
newKey := pbkdf2Key([]byte(password), salt, iterations, keyLength, hashFunc)
// 安全比较
return hmac.Equal(newKey, expectedKey), nil
}
// pbkdf2Key 实现 PBKDF2 核心算法
func pbkdf2Key(password, salt []byte, iterations, keyLength int, hashFunc func() hash.Hash) []byte {
prf := hmac.New(hashFunc, password)
hashLength := prf.Size()
blockCount := (keyLength + hashLength - 1) / hashLength
output := make([]byte, 0, blockCount*hashLength)
for i := 1; i <= blockCount; i++ {
// U1 = PRF(password, salt || INT(i))
prf.Reset()
prf.Write(salt)
binary.BigEndian.PutUint32(make([]byte, 4), uint32(i))
prf.Write([]byte{byte(i >> 24), byte(i >> 16), byte(i >> 8), byte(i)})
u := prf.Sum(nil)
// F = U1 ⊕ U2 ⊕ ... ⊕ U_iterations
f := make([]byte, len(u))
copy(f, u)
for j := 1; j < iterations; j++ {
prf.Reset()
prf.Write(u)
u = prf.Sum(nil)
for k := 0; k < len(f); k++ {
f[k] ^= u[k]
}
}
output = append(output, f...)
}
return output[:keyLength]
}
// getHashFunction 根据名称获取哈希函数
func getHashFunction(name string) (func() hash.Hash, bool) {
switch name {
case "sha256":
return sha256.New, true
case "sha512":
return sha512.New, true
default:
return nil, false
}
}

77
password/pbkdf2_test.go Normal file
View File

@@ -0,0 +1,77 @@
package password
import (
"testing"
)
func TestPBKDF2Crypto_EncryptAndVerify(t *testing.T) {
crypto := NewPBKDF2Crypto()
// 测试加密
password := "securepassword"
encrypted, err := crypto.Encrypt(password)
if err != nil {
t.Fatalf("加密失败: %v", err)
}
if encrypted == "" {
t.Fatal("加密结果为空")
}
t.Log(encrypted)
// 测试验证成功
isValid, err := crypto.Verify(password, encrypted)
if err != nil {
t.Fatalf("验证失败: %v", err)
}
if !isValid {
t.Fatal("验证未通过,密码应匹配")
}
// 测试验证失败
isValid, err = crypto.Verify("wrongpassword", encrypted)
if err != nil {
t.Fatalf("验证失败: %v", err)
}
if isValid {
t.Fatal("验证通过,但密码不应匹配")
}
}
func TestPBKDF2WithSHA512_EncryptAndVerify(t *testing.T) {
crypto := NewPBKDF2WithSHA512()
// 测试加密
password := "securepassword"
encrypted, err := crypto.Encrypt(password)
if err != nil {
t.Fatalf("加密失败: %v", err)
}
if encrypted == "" {
t.Fatal("加密结果为空")
}
t.Log(encrypted)
// 测试验证成功
isValid, err := crypto.Verify(password, encrypted)
if err != nil {
t.Fatalf("验证失败: %v", err)
}
if !isValid {
t.Fatal("验证未通过,密码应匹配")
}
// 测试验证失败
isValid, err = crypto.Verify("wrongpassword", encrypted)
if err != nil {
t.Fatalf("验证失败: %v", err)
}
if isValid {
t.Fatal("验证通过,但密码不应匹配")
}
}

73
password/rsa.go Normal file
View File

@@ -0,0 +1,73 @@
package password
import (
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/pem"
)
// RSACrypto 实现 RSA 加密和解密
type RSACrypto struct {
privateKey *rsa.PrivateKey
publicKey *rsa.PublicKey
}
// NewRSACrypto 创建一个新的 RSACrypto 实例
func NewRSACrypto(keySize int) (*RSACrypto, error) {
privateKey, err := rsa.GenerateKey(rand.Reader, keySize)
if err != nil {
return nil, err
}
return &RSACrypto{
privateKey: privateKey,
publicKey: &privateKey.PublicKey,
}, nil
}
// Encrypt 使用公钥加密数据
func (r *RSACrypto) Encrypt(data string) (string, error) {
encryptedBytes, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, r.publicKey, []byte(data), nil)
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(encryptedBytes), nil
}
// Decrypt 使用私钥解密数据
func (r *RSACrypto) Decrypt(encryptedData string) (string, error) {
decodedData, err := base64.StdEncoding.DecodeString(encryptedData)
if err != nil {
return "", err
}
decryptedBytes, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, r.privateKey, decodedData, nil)
if err != nil {
return "", err
}
return string(decryptedBytes), nil
}
// ExportPrivateKey 导出私钥为 PEM 格式
func (r *RSACrypto) ExportPrivateKey() (string, error) {
privateKeyBytes := x509.MarshalPKCS1PrivateKey(r.privateKey)
privateKeyPEM := pem.EncodeToMemory(&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: privateKeyBytes,
})
return string(privateKeyPEM), nil
}
// ExportPublicKey 导出公钥为 PEM 格式
func (r *RSACrypto) ExportPublicKey() (string, error) {
publicKeyBytes, err := x509.MarshalPKIXPublicKey(r.publicKey)
if err != nil {
return "", err
}
publicKeyPEM := pem.EncodeToMemory(&pem.Block{
Type: "RSA PUBLIC KEY",
Bytes: publicKeyBytes,
})
return string(publicKeyPEM), nil
}

63
password/rsa_test.go Normal file
View File

@@ -0,0 +1,63 @@
package password
import (
"testing"
)
func TestRSACrypto_EncryptAndDecrypt(t *testing.T) {
// 创建 RSACrypto 实例
crypto, err := NewRSACrypto(2048)
if err != nil {
t.Fatalf("创建 RSACrypto 实例失败: %v", err)
}
// 测试加密
originalData := "securedata"
encryptedData, err := crypto.Encrypt(originalData)
if err != nil {
t.Fatalf("加密失败: %v", err)
}
if encryptedData == "" {
t.Fatal("加密结果为空")
}
t.Log(encryptedData)
// 测试解密
decryptedData, err := crypto.Decrypt(encryptedData)
if err != nil {
t.Fatalf("解密失败: %v", err)
}
if decryptedData != originalData {
t.Fatalf("解密结果不匹配,期望: %s实际: %s", originalData, decryptedData)
}
}
func TestRSACrypto_ExportKeys(t *testing.T) {
// 创建 RSACrypto 实例
crypto, err := NewRSACrypto(2048)
if err != nil {
t.Fatalf("创建 RSACrypto 实例失败: %v", err)
}
// 测试导出私钥
privateKey, err := crypto.ExportPrivateKey()
if err != nil {
t.Fatalf("导出私钥失败: %v", err)
}
if privateKey == "" {
t.Fatal("导出的私钥为空")
}
// 测试导出公钥
publicKey, err := crypto.ExportPublicKey()
if err != nil {
t.Fatalf("导出公钥失败: %v", err)
}
if publicKey == "" {
t.Fatal("导出的公钥为空")
}
}

110
password/sha.go Normal file
View File

@@ -0,0 +1,110 @@
package password
import (
"errors"
"fmt"
"hash"
"strings"
"crypto/rand"
"crypto/sha256"
"crypto/sha512"
"encoding/base64"
"encoding/hex"
)
// SHACrypto 实现 SHA-256/SHA-512 密码哈希算法(带盐值)
type SHACrypto struct {
Hash func() hash.Hash
HashName string
SaltLength int
}
// NewSHA256Crypto 创建 SHA-256 加密器
func NewSHA256Crypto() *SHACrypto {
return &SHACrypto{
Hash: sha256.New,
HashName: "sha256",
SaltLength: 16, // 16 字节盐值
}
}
// NewSHA512Crypto 创建 SHA-512 加密器
func NewSHA512Crypto() *SHACrypto {
return &SHACrypto{
Hash: sha512.New,
HashName: "sha512",
SaltLength: 16, // 16 字节盐值
}
}
// Encrypt 实现密码加密(带盐值)
func (s *SHACrypto) Encrypt(password string) (string, error) {
// 生成随机盐值
salt := make([]byte, s.SaltLength)
if _, err := rand.Read(salt); err != nil {
return "", err
}
// 计算哈希
hashValue := s.Hash()
hashValue.Write(salt)
hashValue.Write([]byte(password))
hashBytes := hashValue.Sum(nil)
// 格式: sha256:$salt$hash 或 sha512:$salt$hash
return fmt.Sprintf(
"%s$%s$%s",
s.HashName,
base64.RawStdEncoding.EncodeToString(salt),
hex.EncodeToString(hashBytes),
), nil
}
// Verify 验证密码
func (s *SHACrypto) Verify(password, encrypted string) (bool, error) {
// 解析哈希字符串
parts := strings.Split(encrypted, "$")
if len(parts) != 3 {
return false, errors.New("无效的 SHA 哈希格式")
}
hashName := parts[0]
if hashName != s.HashName {
return false, fmt.Errorf("哈希算法不匹配: 期望 %s, 实际 %s", s.HashName, hashName)
}
// 解码盐值
salt, err := base64.RawStdEncoding.DecodeString(parts[1])
if err != nil {
return false, err
}
// 解码原始哈希值
originalHash, err := hex.DecodeString(parts[2])
if err != nil {
return false, err
}
// 计算新哈希
hashValue := s.Hash()
hashValue.Write(salt)
hashValue.Write([]byte(password))
newHash := hashValue.Sum(nil)
// 安全比较
return compareHash(newHash, originalHash), nil
}
// compareHash 安全比较两个哈希值
func compareHash(h1, h2 []byte) bool {
if len(h1) != len(h2) {
return false
}
result := 0
for i := 0; i < len(h1); i++ {
result |= int(h1[i] ^ h2[i])
}
return result == 0
}

53
password/sha_test.go Normal file
View File

@@ -0,0 +1,53 @@
package password
import (
"testing"
)
func TestSHACrypto_EncryptAndVerify_SHA256(t *testing.T) {
crypto := NewSHA256Crypto()
password := "securepassword"
encrypted, err := crypto.Encrypt(password)
if err != nil {
t.Fatalf("加密失败: %v", err)
}
if encrypted == "" {
t.Fatal("加密结果为空")
}
t.Log(encrypted)
isValid, err := crypto.Verify(password, encrypted)
if err != nil {
t.Fatalf("验证失败: %v", err)
}
if !isValid {
t.Fatal("验证结果不匹配")
}
}
func TestSHACrypto_EncryptAndVerify_SHA512(t *testing.T) {
crypto := NewSHA512Crypto()
password := "securepassword"
encrypted, err := crypto.Encrypt(password)
if err != nil {
t.Fatalf("加密失败: %v", err)
}
if encrypted == "" {
t.Fatal("加密结果为空")
}
t.Log(encrypted)
isValid, err := crypto.Verify(password, encrypted)
if err != nil {
t.Fatalf("验证失败: %v", err)
}
if !isValid {
t.Fatal("验证结果不匹配")
}
}

70
query_parser/README.md Normal file
View File

@@ -0,0 +1,70 @@
# 查询解析器
## 排序规则
排序操作本质上是`SQL`里面的`Order By`条件。
| 序列 | 示例 | 备注 |
|----|--------------------|--------------|
| 升序 | `["type"]` | |
| 降序 | `["-create_time"]` | 字段名前加`-`是为降序 |
## 过滤规则
过滤器操作本质上是`SQL`里面的`WHERE`条件。
过滤器的规则遵循了Python的ORM的规则比如
- [Tortoise ORM Filtering](https://tortoise.github.io/query.html#filtering)。
- [Django Field lookups](https://docs.djangoproject.com/en/4.2/ref/models/querysets/#field-lookups)
如果只是普通的查询,只需要传递`字段名`即可,但是如果需要一些特殊的查询,那么就需要加入`操作符`了。
特殊查询的语法规则其实很简单,就是使用双下划线`__`分割字段名和操作符:
```text
{字段名}__{查找类型} : {值}
{字段名}.{JSON字段名}__{查找类型} : {值}
```
| 查找类型 | 示例 | SQL | 备注 |
|-------------|---------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------|
| not | `{"name__not" : "tom"}` | `WHERE NOT ("name" = "tom")` | |
| in | `{"name__in" : "[\"tom\", \"jimmy\"]"}` | `WHERE name IN ("tom", "jimmy")` | |
| not_in | `{"name__not_in" : "[\"tom\", \"jimmy\"]"}` | `WHERE name NOT IN ("tom", "jimmy")` | |
| gte | `{"create_time__gte" : "2023-10-25"}` | `WHERE "create_time" >= "2023-10-25"` | |
| gt | `{"create_time__gt" : "2023-10-25"}` | `WHERE "create_time" > "2023-10-25"` | |
| lte | `{"create_time__lte" : "2023-10-25"}` | `WHERE "create_time" <= "2023-10-25"` | |
| lt | `{"create_time__lt" : "2023-10-25"}` | `WHERE "create_time" < "2023-10-25"` | |
| range | `{"create_time__range" : "[\"2023-10-25\", \"2024-10-25\"]"}` | `WHERE "create_time" BETWEEN "2023-10-25" AND "2024-10-25"` <br><br> `WHERE "create_time" >= "2023-10-25" AND "create_time" <= "2024-10-25"` | 需要注意的是: <br>1. 有些数据库的BETWEEN实现的开闭区间可能不一样。<br>2. 日期`2005-01-01`会被隐式转换为:`2005-01-01 00:00:00`,两个日期一致就会导致查询不到数据。 |
| isnull | `{"name__isnull" : "True"}` | `WHERE name IS NULL` | |
| not_isnull | `{"name__not_isnull" : "False"}` | `WHERE name IS NOT NULL` | |
| contains | `{"name__contains" : "L"}` | `WHERE name LIKE '%L%';` | |
| icontains | `{"name__icontains" : "L"}` | `WHERE name ILIKE '%L%';` | |
| startswith | `{"name__startswith" : "La"}` | `WHERE name LIKE 'La%';` | |
| istartswith | `{"name__istartswith" : "La"}` | `WHERE name ILIKE 'La%';` | |
| endswith | `{"name__endswith" : "a"}` | `WHERE name LIKE '%a';` | |
| iendswith | `{"name__iendswith" : "a"}` | `WHERE name ILIKE '%a';` | |
| exact | `{"name__exact" : "a"}` | `WHERE name LIKE 'a';` | |
| iexact | `{"name__iexact" : "a"}` | `WHERE name ILIKE 'a';` | |
| regex | `{"title__regex" : "^(An?\|The) +"}` | MySQL: `WHERE title REGEXP BINARY '^(An?\|The) +'` <br> Oracle: `WHERE REGEXP_LIKE(title, '^(An?\|The) +', 'c');` <br> PostgreSQL: `WHERE title ~ '^(An?\|The) +';` <br> SQLite: `WHERE title REGEXP '^(An?\|The) +';` | |
| iregex | `{"title__iregex" : "^(an?\|the) +"}` | MySQL: `WHERE title REGEXP '^(an?\|the) +'` <br> Oracle: `WHERE REGEXP_LIKE(title, '^(an?\|the) +', 'i');` <br> PostgreSQL: `WHERE title ~* '^(an?\|the) +';` <br> SQLite: `WHERE title REGEXP '(?i)^(an?\|the) +';` | |
| search | | | |
以及将日期提取出来的查找类型:
| 查找类型 | 示例 | SQL | 备注 |
|--------------|--------------------------------------|---------------------------------------------------|----------------------|
| date | `{"pub_date__date" : "2023-01-01"}` | `WHERE DATE(pub_date) = '2023-01-01'` | |
| year | `{"pub_date__year" : "2023"}` | `WHERE EXTRACT('YEAR' FROM pub_date) = '2023'` | 哪一年 |
| iso_year | `{"pub_date__iso_year" : "2023"}` | `WHERE EXTRACT('ISOYEAR' FROM pub_date) = '2023'` | ISO 8601 一年中的周数 |
| month | `{"pub_date__month" : "12"}` | `WHERE EXTRACT('MONTH' FROM pub_date) = '12'` | 月份1-12 |
| day | `{"pub_date__day" : "3"}` | `WHERE EXTRACT('DAY' FROM pub_date) = '3'` | 该月的某天(1-31) |
| week | `{"pub_date__week" : "7"}` | `WHERE EXTRACT('WEEK' FROM pub_date) = '7'` | ISO 8601 周编号 一年中的周数 |
| week_day | `{"pub_date__week_day" : "tom"}` | `` | 星期几 |
| iso_week_day | `{"pub_date__iso_week_day" : "tom"}` | `` | |
| quarter | `{"pub_date__quarter" : "1"}` | `WHERE EXTRACT('QUARTER' FROM pub_date) = '1'` | 一年中的季度 |
| time | `{"pub_date__time" : "12:59:59"}` | `` | |
| hour | `{"pub_date__hour" : "12"}` | `WHERE EXTRACT('HOUR' FROM pub_date) = '12'` | 小时(0-23) |
| minute | `{"pub_date__minute" : "59"}` | `WHERE EXTRACT('MINUTE' FROM pub_date) = '59'` | 分钟 (0-59) |
| second | `{"pub_date__second" : "59"}` | `WHERE EXTRACT('SECOND' FROM pub_date) = '59'` | 秒 (0-59) |

180
query_parser/filter.go Normal file
View File

@@ -0,0 +1,180 @@
package query_parser
import (
"strings"
"github.com/go-kratos/kratos/v2/encoding"
_ "github.com/go-kratos/kratos/v2/encoding/json"
"github.com/tx7do/go-utils/stringcase"
)
type FilterOperator string
const (
FilterNot = "not" // 不等于
FilterIn = "in" // 检查值是否在列表中
FilterNotIn = "not_in" // 不在列表中
FilterGTE = "gte" // 大于或等于传递的值
FilterGT = "gt" // 大于传递值
FilterLTE = "lte" // 小于或等于传递值
FilterLT = "lt" // 小于传递值
FilterRange = "range" // 是否介于和给定的两个值之间
FilterIsNull = "isnull" // 是否为空
FilterNotIsNull = "not_isnull" // 是否不为空
FilterContains = "contains" // 是否包含指定的子字符串
FilterInsensitiveContains = "icontains" // 不区分大小写,是否包含指定的子字符串
FilterStartsWith = "startswith" // 以值开头
FilterInsensitiveStartsWith = "istartswith" // 不区分大小写,以值开头
FilterEndsWith = "endswith" // 以值结尾
FilterInsensitiveEndsWith = "iendswith" // 不区分大小写,以值结尾
FilterExact = "exact" // 精确匹配
FilterInsensitiveExact = "iexact" // 不区分大小写,精确匹配
FilterRegex = "regex" // 正则表达式
FilterInsensitiveRegex = "iregex" // 不区分大小写,正则表达式
FilterSearch = "search" // 全文搜索
)
const (
DatePartDate = "date" // 日期
DatePartYear = "year" // 年
DatePartISOYear = "iso_year" // ISO8601 一年中的周数
DatePartQuarter = "quarter" // 季度
DatePartMonth = "month" // 月
DatePartWeek = "week" // ISO8601 周编号 一年中的周数
DatePartWeekDay = "week_day" // 星期几
DatePartISOWeekDay = "iso_week_day" // ISO8601 星期几
DatePartDay = "day" // 日
DatePartTime = "time" // 小时:分钟:秒
DatePartHour = "hour" // 小时
DatePartMinute = "minute" // 分钟
DatePartSecond = "second" // 秒
DatePartMicrosecond = "microsecond" // 微秒
)
const (
JSONFilterFieldOperatorDelimiter = "__" // JSON过滤器 - 字段名和操作符的分隔符
QueryFilterFieldOperatorDelimiter = ":" // 自定义查询字符串过滤器 - 字段名和操作符的分隔符
QueryFilterQueriesDelimiter = "," // 自定义查询字符串过滤器 - 多个键值对的分隔符
QueryFilterValuesDelimiter = "|" // 自定义查询字符串过滤器 - 多个值的分隔符
JsonFieldDelimiter = "." // JSON字段分隔符
)
type FilterHandler func(field, operator, value string)
// ParseFilterJSONString 解析过滤条件的JSON字符串调用处理函数
func ParseFilterJSONString(query string, handler FilterHandler) error {
if query == "" {
return nil
}
codec := encoding.GetCodec("json")
var err error
queryMap := make(map[string]string)
if err = codec.Unmarshal([]byte(query), &queryMap); err == nil {
for k, v := range queryMap {
ParseFilterField(k, v, handler)
}
return nil
}
var queryMapArray []map[string]string
if err = codec.Unmarshal([]byte(query), &queryMapArray); err == nil {
for _, item := range queryMapArray {
for k, v := range item {
ParseFilterField(k, v, handler)
}
}
return nil
}
return err
}
// ParseFilterQueryString 解析过滤条件的查询字符串,调用处理函数
func ParseFilterQueryString(query string, handler FilterHandler) error {
if query == "" {
return nil // 如果查询字符串为空,直接返回
}
// 按逗号分割查询字符串,得到多个键值对
pairs := SplitQueryQueries(query)
for _, pair := range pairs {
// 按冒号分割键值对,提取字段名和值
parts := SplitQueryFieldAndOperator(pair)
if len(parts) != 2 {
continue // 跳过无效的键值对
}
// 解码字段名
key, err := DecodeSpecialCharacters(strings.TrimSpace(parts[0]))
if err != nil {
continue // 跳过解码失败的键值对
}
// 解码字段值
value, err := DecodeSpecialCharacters(strings.TrimSpace(parts[1]))
if err != nil {
continue // 跳过解码失败的键值对
}
// 调用 ParseFilterField 解析字段和操作符
ParseFilterField(key, value, handler)
}
return nil
}
// ParseFilterField 解析过滤条件字符串,调用处理函数
func ParseFilterField(key, value string, handler FilterHandler) {
if key == "" || value == "" {
return // 没有过滤条件
}
// 处理字段和操作符
parts := SplitJsonFieldAndOperator(key)
if len(parts) < 1 {
return // 无效的字段格式
}
field := strings.TrimSpace(parts[0])
if field == "" {
return
}
field = stringcase.ToSnakeCase(parts[0])
op := ""
if len(parts) > 1 {
op = parts[1]
}
handler(field, op, value)
}
// SplitJsonFieldAndOperator JSON过滤器 - 分割“字段名”和“操作符”
func SplitJsonFieldAndOperator(field string) []string {
return strings.Split(field, JSONFilterFieldOperatorDelimiter)
}
// SplitQueryFieldAndOperator 自定义查询字符串过滤器 - 分割“字段名”和“操作符”
func SplitQueryFieldAndOperator(field string) []string {
return strings.Split(field, QueryFilterFieldOperatorDelimiter)
}
// SplitQueryQueries 自定义查询字符串过滤器 - 分割多个键值对
func SplitQueryQueries(field string) []string {
return strings.Split(field, QueryFilterQueriesDelimiter)
}
// SplitQueryValues 自定义查询字符串过滤器 - 分割多个值
func SplitQueryValues(field string) []string {
return strings.Split(field, QueryFilterValuesDelimiter)
}
// SplitJSONField 将JSONB字段字符串按分隔符分割成多个字段
func SplitJSONField(field string) []string {
return strings.Split(field, JsonFieldDelimiter)
}

401
query_parser/filter_test.go Normal file
View File

@@ -0,0 +1,401 @@
package query_parser
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestParseFilterJSONString(t *testing.T) {
var results []struct {
Field string
Operator string
Value string
}
handler := func(field, operator, value string) {
results = append(results, struct {
Field string
Operator string
Value string
}{Field: field, Operator: operator, Value: value})
}
// 测试解析单个过滤条件
results = nil
err := ParseFilterJSONString(`{"name__exact":"John"}`, handler)
assert.NoError(t, err)
assert.Equal(t, 1, len(results))
assert.Equal(t, "name", results[0].Field)
assert.Equal(t, "exact", results[0].Operator)
assert.Equal(t, "John", results[0].Value)
// 测试解析多个过滤条件
results = nil
err = ParseFilterJSONString(`[{"age__gte":"30"},{"status__exact":"active"}]`, handler)
assert.NoError(t, err)
assert.Equal(t, 2, len(results))
assert.Equal(t, "age", results[0].Field)
assert.Equal(t, "gte", results[0].Operator)
assert.Equal(t, "30", results[0].Value)
assert.Equal(t, "status", results[1].Field)
assert.Equal(t, "exact", results[1].Operator)
assert.Equal(t, "active", results[1].Value)
// 测试空字符串
results = nil
err = ParseFilterJSONString("", handler)
assert.NoError(t, err)
assert.Equal(t, 0, len(results))
// 测试无效的JSON字符串
results = nil
err = ParseFilterJSONString(`invalid_json`, handler)
assert.Error(t, err)
assert.Equal(t, 0, len(results))
// 测试包含特殊字符的字段和值
results = nil
err = ParseFilterJSONString(`{"na!me__exact":"Jo@hn"}`, handler)
assert.NoError(t, err)
assert.Equal(t, 1, len(results))
assert.Equal(t, "na_me", results[0].Field)
assert.Equal(t, "exact", results[0].Operator)
assert.Equal(t, "Jo@hn", results[0].Value)
// 测试包含特殊字符的多个过滤条件
results = nil
err = ParseFilterJSONString(`[{"ag#e__gte":"30"},{"sta$tus__exact":"ac^tive"}]`, handler)
assert.NoError(t, err)
assert.Equal(t, 2, len(results))
assert.Equal(t, "ag_e", results[0].Field)
assert.Equal(t, "gte", results[0].Operator)
assert.Equal(t, "30", results[0].Value)
assert.Equal(t, "sta_tus", results[1].Field)
assert.Equal(t, "exact", results[1].Operator)
assert.Equal(t, "ac^tive", results[1].Value)
// 测试包含特殊字符的无效 JSON 字符串
results = nil
err = ParseFilterJSONString(`{"na!me__exact":Jo@hn}`, handler)
assert.Error(t, err)
assert.Equal(t, 0, len(results))
}
func TestParseFilterQueryString(t *testing.T) {
var results []struct {
Field string
Operator string
Value string
}
handler := func(field, operator, value string) {
results = append(results, struct {
Field string
Operator string
Value string
}{Field: field, Operator: operator, Value: value})
}
// 测试解析单个过滤条件
results = nil
err := ParseFilterQueryString("name__exact:John", handler)
assert.NoError(t, err)
assert.Equal(t, 1, len(results))
assert.Equal(t, "name", results[0].Field)
assert.Equal(t, "exact", results[0].Operator)
assert.Equal(t, "John", results[0].Value)
// 测试解析多个过滤条件
results = nil
err = ParseFilterQueryString("age__gte:30,status__exact:active", handler)
assert.NoError(t, err)
assert.Equal(t, 2, len(results))
assert.Equal(t, "age", results[0].Field)
assert.Equal(t, "gte", results[0].Operator)
assert.Equal(t, "30", results[0].Value)
assert.Equal(t, "status", results[1].Field)
assert.Equal(t, "exact", results[1].Operator)
assert.Equal(t, "active", results[1].Value)
// 测试空字符串
results = nil
err = ParseFilterQueryString("", handler)
assert.NoError(t, err)
assert.Equal(t, 0, len(results))
// 测试无效的查询字符串
results = nil
err = ParseFilterQueryString("invalid_query", handler)
assert.NoError(t, err)
assert.Equal(t, 0, len(results))
// 测试包含特殊字符的字段和值
results = nil
err = ParseFilterQueryString("na!me__exact:Jo@hn", handler)
assert.NoError(t, err)
assert.Equal(t, 1, len(results))
assert.Equal(t, "na_me", results[0].Field)
assert.Equal(t, "exact", results[0].Operator)
assert.Equal(t, "Jo@hn", results[0].Value)
// 测试包含特殊字符的多个过滤条件
results = nil
err = ParseFilterQueryString("ag#e__gte:30,sta$tus__exact:ac^tive", handler)
assert.NoError(t, err)
assert.Equal(t, 2, len(results))
assert.Equal(t, "ag_e", results[0].Field)
assert.Equal(t, "gte", results[0].Operator)
assert.Equal(t, "30", results[0].Value)
assert.Equal(t, "sta_tus", results[1].Field)
assert.Equal(t, "exact", results[1].Operator)
assert.Equal(t, "ac^tive", results[1].Value)
// 测试 Field 中包含分隔符
results = nil
err = ParseFilterQueryString("na:me__exact:John", handler)
assert.NoError(t, err)
assert.Equal(t, 0, len(results))
// 测试 Operator 中包含分隔符
results = nil
err = ParseFilterQueryString("name__ex:act:John", handler)
assert.NoError(t, err)
assert.Equal(t, 0, len(results))
// 测试 Value 中包含分隔符
results = nil
err = ParseFilterQueryString("name__exact:Jo|hn", handler)
assert.NoError(t, err)
assert.Equal(t, 1, len(results))
assert.Equal(t, "name", results[0].Field)
assert.Equal(t, "exact", results[0].Operator)
assert.Equal(t, "Jo|hn", results[0].Value)
// 测试多个过滤条件中包含分隔符
results = nil
err = ParseFilterQueryString("ag:e__gte:30,sta|tus__exact:ac|tive", handler)
assert.NoError(t, err)
assert.Equal(t, 1, len(results))
// 测试 Field 中包含编码后的分隔符
results = nil
encodedField := EncodeSpecialCharacters("na:me")
err = ParseFilterQueryString(encodedField+"__exact:John", handler)
assert.NoError(t, err)
assert.Equal(t, 1, len(results))
assert.Equal(t, "na_me", results[0].Field) // 注意:这里的字段名会被转换为 snake_case
assert.Equal(t, "exact", results[0].Operator)
assert.Equal(t, "John", results[0].Value)
// 测试 Operator 中包含编码后的分隔符
results = nil
encodedOperator := EncodeSpecialCharacters("ex:act")
err = ParseFilterQueryString("name__"+encodedOperator+":John", handler)
assert.NoError(t, err)
assert.Equal(t, 1, len(results))
assert.Equal(t, "name", results[0].Field)
assert.Equal(t, "ex:act", results[0].Operator)
assert.Equal(t, "John", results[0].Value)
// 测试 Value 中包含编码后的分隔符
results = nil
encodedValue := EncodeSpecialCharacters("Jo|hn")
err = ParseFilterQueryString("name__exact:"+encodedValue, handler)
assert.NoError(t, err)
assert.Equal(t, 1, len(results))
assert.Equal(t, "name", results[0].Field)
assert.Equal(t, "exact", results[0].Operator)
assert.Equal(t, "Jo|hn", results[0].Value)
// 测试多个过滤条件中包含编码后的分隔符
results = nil
encodedField1 := EncodeSpecialCharacters("ag:e")
encodedValue1 := EncodeSpecialCharacters("30")
encodedField2 := EncodeSpecialCharacters("sta|tus")
encodedValue2 := EncodeSpecialCharacters("ac|tive")
err = ParseFilterQueryString(encodedField1+"__gte:"+encodedValue1+","+encodedField2+"__exact:"+encodedValue2, handler)
assert.NoError(t, err)
assert.Equal(t, 2, len(results))
assert.Equal(t, "ag_e", results[0].Field) // 注意:这里的字段名会被转换为 snake_case
assert.Equal(t, "gte", results[0].Operator)
assert.Equal(t, "30", results[0].Value)
assert.Equal(t, "sta_tus", results[1].Field) // 注意:这里的字段名会被转换为 snake_case
assert.Equal(t, "exact", results[1].Operator)
assert.Equal(t, "ac|tive", results[1].Value)
}
func TestParseFilterField(t *testing.T) {
var results []struct {
Field string
Operator string
Value string
}
handler := func(field, operator, value string) {
results = append(results, struct {
Field string
Operator string
Value string
}{Field: field, Operator: operator, Value: value})
}
// 测试正常解析
results = nil
ParseFilterField("name__exact", "John", handler)
assert.Equal(t, 1, len(results))
assert.Equal(t, "name", results[0].Field)
assert.Equal(t, "exact", results[0].Operator)
assert.Equal(t, "John", results[0].Value)
// 测试无操作符解析
results = nil
ParseFilterField("name", "John", handler)
assert.Equal(t, 1, len(results))
assert.Equal(t, "name", results[0].Field)
assert.Equal(t, "", results[0].Operator)
assert.Equal(t, "John", results[0].Value)
// 测试空字段
results = nil
ParseFilterField("", "John", handler)
assert.Equal(t, 0, len(results))
// 测试空值
results = nil
ParseFilterField("name__exact", "", handler)
assert.Equal(t, 0, len(results))
// 测试无效字段格式
results = nil
ParseFilterField("__exact", "John", handler)
assert.Equal(t, 0, len(results))
}
func TestSplitJsonFieldAndOperator(t *testing.T) {
// 测试正常分割
result := SplitJsonFieldAndOperator("name__exact")
assert.Equal(t, 2, len(result))
assert.Equal(t, "name", result[0])
assert.Equal(t, "exact", result[1])
// 测试无操作符
result = SplitJsonFieldAndOperator("name")
assert.Equal(t, 1, len(result))
assert.Equal(t, "name", result[0])
// 测试空字符串
result = SplitJsonFieldAndOperator("")
assert.Equal(t, 1, len(result))
assert.Equal(t, "", result[0])
// 测试多个分隔符
result = SplitJsonFieldAndOperator("name__exact__extra")
assert.Equal(t, 3, len(result))
assert.Equal(t, "name", result[0])
assert.Equal(t, "exact", result[1])
assert.Equal(t, "extra", result[2])
}
func TestSplitJSONField(t *testing.T) {
// 测试正常分割
result := SplitJSONField("user.address.city")
assert.Equal(t, 3, len(result))
assert.Equal(t, "user", result[0])
assert.Equal(t, "address", result[1])
assert.Equal(t, "city", result[2])
// 测试单个字段
result = SplitJSONField("user")
assert.Equal(t, 1, len(result))
assert.Equal(t, "user", result[0])
// 测试空字符串
result = SplitJSONField("")
assert.Equal(t, 1, len(result))
assert.Equal(t, "", result[0])
// 测试多个分隔符
result = SplitJSONField("user..address.city")
assert.Equal(t, 4, len(result))
assert.Equal(t, "user", result[0])
assert.Equal(t, "", result[1])
assert.Equal(t, "address", result[2])
assert.Equal(t, "city", result[3])
}
func TestSplitQueryFieldAndOperator(t *testing.T) {
// 测试正常分割
result := SplitQueryFieldAndOperator("name:exact")
assert.Equal(t, 2, len(result))
assert.Equal(t, "name", result[0])
assert.Equal(t, "exact", result[1])
// 测试无操作符
result = SplitQueryFieldAndOperator("name")
assert.Equal(t, 1, len(result))
assert.Equal(t, "name", result[0])
// 测试空字符串
result = SplitQueryFieldAndOperator("")
assert.Equal(t, 1, len(result))
assert.Equal(t, "", result[0])
// 测试多个分隔符
result = SplitQueryFieldAndOperator("name:exact:extra")
assert.Equal(t, 3, len(result))
assert.Equal(t, "name", result[0])
assert.Equal(t, "exact", result[1])
assert.Equal(t, "extra", result[2])
}
func TestSplitQueryQueries(t *testing.T) {
// 测试正常分割多个键值对
result := SplitQueryQueries("name:John,age:30,status:active")
assert.Equal(t, 3, len(result))
assert.Equal(t, "name:John", result[0])
assert.Equal(t, "age:30", result[1])
assert.Equal(t, "status:active", result[2])
// 测试单个键值对
result = SplitQueryQueries("name:John")
assert.Equal(t, 1, len(result))
assert.Equal(t, "name:John", result[0])
// 测试空字符串
result = SplitQueryQueries("")
assert.Equal(t, 1, len(result))
assert.Equal(t, "", result[0])
// 测试多个分隔符
result = SplitQueryQueries("name:John,,age:30")
assert.Equal(t, 3, len(result))
assert.Equal(t, "name:John", result[0])
assert.Equal(t, "", result[1])
assert.Equal(t, "age:30", result[2])
}
func TestSplitQueryValues(t *testing.T) {
// 测试正常分割多个值
result := SplitQueryValues("value1|value2|value3")
assert.Equal(t, 3, len(result))
assert.Equal(t, "value1", result[0])
assert.Equal(t, "value2", result[1])
assert.Equal(t, "value3", result[2])
// 测试单个值
result = SplitQueryValues("value1")
assert.Equal(t, 1, len(result))
assert.Equal(t, "value1", result[0])
// 测试空字符串
result = SplitQueryValues("")
assert.Equal(t, 1, len(result))
assert.Equal(t, "", result[0])
// 测试多个分隔符
result = SplitQueryValues("value1||value2")
assert.Equal(t, 3, len(result))
assert.Equal(t, "value1", result[0])
assert.Equal(t, "", result[1])
assert.Equal(t, "value2", result[2])
}

21
query_parser/go.mod Normal file
View File

@@ -0,0 +1,21 @@
module github.com/tx7do/go-utils/query_parser
go 1.23.0
toolchain go1.23.2
require (
github.com/go-kratos/kratos/v2 v2.8.4
github.com/stretchr/testify v1.10.0
github.com/tx7do/go-utils v1.1.29
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace github.com/tx7do/go-utils => ../

26
query_parser/go.sum Normal file
View File

@@ -0,0 +1,26 @@
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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/go-kratos/kratos/v2 v2.8.4 h1:eIJLE9Qq9WSoKx+Buy2uPyrahtF/lPh+Xf4MTpxhmjs=
github.com/go-kratos/kratos/v2 v2.8.4/go.mod h1:mq62W2101a5uYyRxe+7IdWubu7gZCGYqSNKwGFiiRcw=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
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/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

50
query_parser/orderby.go Normal file
View File

@@ -0,0 +1,50 @@
package query_parser
import (
"strings"
)
type OrderByHandler func(field string, desc bool)
// ParseOrderByString 解析排序字符串,调用处理函数。
func ParseOrderByString(orderBy string, handler OrderByHandler) error {
if orderBy == "" {
return nil
}
parts := strings.Split(orderBy, ",")
for _, part := range parts {
part = strings.TrimSpace(part)
ParseOrderByField(part, handler)
}
return nil
}
// ParseOrderByStrings 解析多个排序字符串,调用处理函数。
func ParseOrderByStrings(orderBys []string, handler OrderByHandler) error {
for _, v := range orderBys {
if v == "" {
continue
}
ParseOrderByField(v, handler)
}
return nil
}
// ParseOrderByField 解析单个排序字段,调用处理函数。
func ParseOrderByField(orderBy string, handler OrderByHandler) {
orderBy = strings.TrimSpace(orderBy)
if orderBy == "" {
return // 没有排序条件
}
if strings.HasPrefix(orderBy, "-") {
handler(orderBy[1:], true) // 降序
} else if strings.HasPrefix(orderBy, "+") {
handler(orderBy[1:], false) // 升序
} else {
handler(orderBy, false) // 升序
}
}

View File

@@ -0,0 +1,126 @@
package query_parser
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestParseOrderByString(t *testing.T) {
var results []struct {
Field string
Desc bool
}
handler := func(field string, desc bool) {
results = append(results, struct {
Field string
Desc bool
}{Field: field, Desc: desc})
}
// 测试正常解析
err := ParseOrderByString("name,-age,+created_at", handler)
assert.NoError(t, err)
assert.Equal(t, 3, len(results))
assert.Equal(t, "name", results[0].Field)
assert.False(t, results[0].Desc)
assert.Equal(t, "age", results[1].Field)
assert.True(t, results[1].Desc)
assert.Equal(t, "created_at", results[2].Field)
assert.False(t, results[2].Desc)
// 测试空字符串
results = nil
err = ParseOrderByString("", handler)
assert.NoError(t, err)
assert.Equal(t, 0, len(results))
// 测试只有空格的字符串
results = nil
err = ParseOrderByString(" ", handler)
assert.NoError(t, err)
assert.Equal(t, 0, len(results))
}
func TestParseOrderByStrings(t *testing.T) {
var results []struct {
Field string
Desc bool
}
handler := func(field string, desc bool) {
results = append(results, struct {
Field string
Desc bool
}{Field: field, Desc: desc})
}
// 测试正常解析
err := ParseOrderByStrings([]string{"name", "-age", "+created_at"}, handler)
assert.NoError(t, err)
assert.Equal(t, 3, len(results))
assert.Equal(t, "name", results[0].Field)
assert.False(t, results[0].Desc)
assert.Equal(t, "age", results[1].Field)
assert.True(t, results[1].Desc)
assert.Equal(t, "created_at", results[2].Field)
assert.False(t, results[2].Desc)
// 测试空字符串数组
results = nil
err = ParseOrderByStrings([]string{}, handler)
assert.NoError(t, err)
assert.Equal(t, 0, len(results))
// 测试包含空字符串的数组
results = nil
err = ParseOrderByStrings([]string{"", " "}, handler)
assert.NoError(t, err)
assert.Equal(t, 0, len(results))
}
func TestParseOrderByField(t *testing.T) {
var results []struct {
Field string
Desc bool
}
handler := func(field string, desc bool) {
results = append(results, struct {
Field string
Desc bool
}{Field: field, Desc: desc})
}
// 测试升序解析
results = nil
ParseOrderByField("name", handler)
assert.Equal(t, 1, len(results))
assert.Equal(t, "name", results[0].Field)
assert.False(t, results[0].Desc)
// 测试降序解析
results = nil
ParseOrderByField("-age", handler)
assert.Equal(t, 1, len(results))
assert.Equal(t, "age", results[0].Field)
assert.True(t, results[0].Desc)
// 测试带+的升序解析
results = nil
ParseOrderByField("+created_at", handler)
assert.Equal(t, 1, len(results))
assert.Equal(t, "created_at", results[0].Field)
assert.False(t, results[0].Desc)
// 测试空字符串
results = nil
ParseOrderByField("", handler)
assert.Equal(t, 0, len(results))
// 测试只有空格的字符串
results = nil
ParseOrderByField(" ", handler)
assert.Equal(t, 0, len(results))
}

13
query_parser/utils.go Normal file
View File

@@ -0,0 +1,13 @@
package query_parser
import "net/url"
// EncodeSpecialCharacters 对字符串进行编码
func EncodeSpecialCharacters(input string) string {
return url.QueryEscape(input)
}
// DecodeSpecialCharacters 对字符串进行解码
func DecodeSpecialCharacters(input string) (string, error) {
return url.QueryUnescape(input)
}

22
slug/go.mod Normal file
View File

@@ -0,0 +1,22 @@
module github.com/tx7do/go-utils/slug
go 1.23.0
toolchain go1.23.2
require (
github.com/gosimple/slug v1.15.0
github.com/stretchr/testify v1.10.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gosimple/unidecode v1.0.1 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.13.1 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace github.com/tx7do/go-utils => ../

27
slug/go.sum Normal file
View File

@@ -0,0 +1,27 @@
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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/gosimple/slug v1.15.0 h1:wRZHsRrRcs6b0XnxMUBM6WK1U1Vg5B0R7VkIf1Xzobo=
github.com/gosimple/slug v1.15.0/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ=
github.com/gosimple/unidecode v1.0.1 h1:hZzFTMMqSswvf0LBJZCZgThIZrpDHFXux9KeGmn6T/o=
github.com/gosimple/unidecode v1.0.1/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
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/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
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/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -1,50 +0,0 @@
package sonyflake
import (
"github.com/sony/sonyflake"
"time"
)
var (
sf *sonyflake.Sonyflake
)
func startTime() time.Time {
return time.Now()
}
func getMachineID() (uint16, error) {
return 0, nil
}
func checkMachineID(uint16) bool {
return false
}
// InitSonyflake 初始化Sonyflake节点单体
func InitSonyflake() {
settings := sonyflake.Settings{
/*StartTime: startTime(),
MachineID: getMachineID,
CheckMachineID: checkMachineID,*/
}
sf = sonyflake.NewSonyflake(settings)
if sf == nil {
panic("sonyflake not created")
}
}
// GenerateSonyflake 生成 Sonyflake ID
func GenerateSonyflake() uint64 {
if sf == nil {
InitSonyflake()
}
if sf == nil {
return 0
}
id, err := sf.NextID()
if err != nil {
return 0
}
return id
}

View File

@@ -1,35 +0,0 @@
package sonyflake
import (
"fmt"
"testing"
"time"
)
func init() {
InitSonyflake()
}
func TestGenerateSonyflake(t *testing.T) {
for i := 0; i < 100; i++ {
id := GenerateSonyflake()
fmt.Println(id)
}
}
func TestGenerateTime(t *testing.T) {
// 秒
fmt.Printf("时间戳(秒):%v;\n", time.Now().Unix())
fmt.Printf("时间戳(纳秒转换为秒):%v;\n", time.Now().UnixNano()/1e9)
// 毫秒
fmt.Printf("时间戳(毫秒):%v;\n", time.Now().UnixMilli())
fmt.Printf("时间戳(纳秒转换为毫秒):%v;\n", time.Now().UnixNano()/1e6)
// 微秒
fmt.Printf("时间戳(微秒):%v;\n", time.Now().UnixMicro())
fmt.Printf("时间戳(纳秒转换为微秒):%v;\n", time.Now().UnixNano()/1e3)
// 纳秒
fmt.Printf("时间戳(纳秒):%v;\n", time.Now().UnixNano())
}

80
stringcase/README.md Normal file
View File

@@ -0,0 +1,80 @@
# 命名法转换
- camelCase 驼峰式命名法(大驼峰)
- PascalCase 帕斯卡命名法(小驼峰)
- snake_case 蛇形命名法
- kebab-case 烤肉串命名法
## camelCase 驼峰式命名法(大驼峰)
驼峰式命名法Camel case是一种不使用空格将多个单词连起来形成一个标识符的命名方式其中每个单词的首字母除了第一个单词如果使用小驼峰式命名法都大写就像骆驼的驼峰一样。
驼峰式命名法分为两种首字母小写的“小驼峰式”lowerCamelCase和首字母大写的“大驼峰式”UpperCamelCase也称为帕斯卡命名法PascalCase
- **小驼峰式(lowerCamelCase)**: 第一个单词的首字母小写,后续单词的首字母大写。例如:`myVariableName`
- **大驼峰式(UpperCamelCase)**: 每个单词的首字母都大写。例如MyVariableName也称为帕斯卡命名法PascalCase
在 JavaScript、Java和C#中,驼峰式大小写常用于变量和函数的命名。
```javascript
let firstName = "John";
let lastName = "Doe";
function printFullName(firstName, lastName) {
let fullName = firstName + " " + lastName;
console.log(fullName);
}
```
## PascalCase 帕斯卡命名法(小驼峰)
`PascalCase`,也称为`UpperCamelCase`,是一种在编程中使用的命名约定。它要求每个单词(包括第一个单词)的首字母都大写,并且单词之间没有空格或分隔符(如
`_`)。例如,`ThisIsPascalCase``MyClassName` 都是使用PascalCase的例子。
PascalCase 通常用于在 C#、Java 和TypeScript等语言中命名类、接口和其他类型。
```typescript
class Person {
firstName: string;
lastName: string;
constructor(firstName: string, lastName: string) {
this.firstName = firstName;
this.lastName = lastName;
}
printFullName(): void {
let fullName = this.firstName + " " + this.lastName;
console.log(fullName);
}
}
```
## snake_case 蛇形命名法
蛇形命名法是一种使用下划线 (`_`) 分隔单词的命名方式。之所以叫蛇形命名法,是因为"`snake_case`"的
下划线的形状类似于蛇腹上的鳞片。蛇形命名法通常用于Python、Ruby 和JavaScript等语言的变量名和函数名。
```python
first_name = "John"
last_name = "Doe"
def print_full_name(first_name, last_name):
full_name = first_name + " " + last_name
print(full_name)
```
## kebab-case 烤肉串命名法
`kebab-case烤肉串命名法`,也被称作 `kebab case``dash-case破折号式``hyphen-case连字符式``lisp-caseLisp 式)`
kebab-case 要求短语内的各个单词或缩写之间以`-`(连字符)做间隔。 例如:"`kebab-case`"。
短横线命名法通常用于 URL、文件名和 HTML/CSS 类名。
```html
<div class="user-profile">
<p>This is a user profile.</p>
</div>
```

65
stringcase/camel_case.go Normal file
View File

@@ -0,0 +1,65 @@
package stringcase
import (
"strings"
"unicode"
)
func UpperCamelCase(input string) string {
return camelCase(input, true)
}
func LowerCamelCase(input string) string {
return camelCase(input, false)
}
// ToPascalCase 把字符转换为 帕斯卡命名/大驼峰命名法CamelCase
func ToPascalCase(input string) string {
return camelCase(input, true)
}
func camelCase(input string, upper bool) string {
input = strings.TrimSpace(input)
if input == "" {
return input
}
// 分割字符串
words := Split(input)
if len(words) == 0 {
return ""
}
filteredWords := make([]string, 0, len(words))
for _, word := range words {
if strings.TrimSpace(word) != "" {
filteredWords = append(filteredWords, word)
}
}
words = filteredWords
if len(words) == 0 {
return ""
}
for i, word := range words {
if word == "" {
continue
}
runes := []rune(word)
if len(runes) > 0 {
if i == 0 && !upper {
runes[0] = unicode.ToLower(runes[0]) // LowerCamelCase首单词首字母小写
} else {
runes[0] = unicode.ToUpper(runes[0]) // UpperCamelCase或后续单词首字母大写
}
for j := 1; j < len(runes); j++ {
runes[j] = unicode.ToLower(runes[j]) // 其余字母统一小写
}
words[i] = string(runes)
}
}
// 合并结果
return strings.Join(words, "")
}

View File

@@ -0,0 +1,111 @@
package stringcase
import (
"testing"
)
func TestUpperCamelCase(t *testing.T) {
tests := []struct {
input string
expected string
}{
{"hello world", "HelloWorld"},
{"hello_world", "HelloWorld"},
{"hello-world", "HelloWorld"},
{"hello.world", "HelloWorld"},
{"helloWorld", "HelloWorld"},
{"HelloWorld", "HelloWorld"},
{"HTTPStatusCode", "HttpStatusCode"},
{"ParseURL.DoParse", "ParseUrlDoParse"},
{"ParseUrl.DoParse", "ParseUrlDoParse"},
{"parse_url.do_parse", "ParseUrlDoParse"},
{"convert space", "ConvertSpace"},
{"convert-dash", "ConvertDash"},
{"skip___multiple_underscores", "SkipMultipleUnderscores"},
{"skip multiple spaces", "SkipMultipleSpaces"},
{"skip---multiple-dashes", "SkipMultipleDashes"},
{"", ""},
{"a", "A"},
{"Z", "Z"},
{"special-characters_test", "SpecialCharactersTest"},
{"numbers123test", "Numbers123Test"},
{"hello world!", "HelloWorld"},
{"test@with#symbols", "TestWithSymbols"},
{"complexCase123!@#", "ComplexCase123"},
{"snake_case_string", "SnakeCaseString"},
{"kebab-case-string", "KebabCaseString"},
{"PascalCaseString", "PascalCaseString"},
{"camelCaseString", "CamelCaseString"},
{"HTTPRequest", "HttpRequest"},
{"user ID", "UserId"},
{"UserId", "UserId"},
{"userID", "UserId"},
{"UserID", "UserId"},
{"123NumberPrefix", "123NumberPrefix"},
{"__leading_underscores", "LeadingUnderscores"},
{"trailing_underscores__", "TrailingUnderscores"},
{"multiple___underscores", "MultipleUnderscores"},
{" spaces around ", "SpacesAround"},
}
for _, test := range tests {
result := UpperCamelCase(test.input)
if result != test.expected {
t.Errorf("UpperCamelCase(%q) = %q; expected %q", test.input, result, test.expected)
}
}
}
func TestLowerCamelCase(t *testing.T) {
tests := []struct {
input string
expected string
}{
{"hello world", "helloWorld"},
{"hello_world", "helloWorld"},
{"hello-world", "helloWorld"},
{"hello.world", "helloWorld"},
{"helloWorld", "helloWorld"},
{"HelloWorld", "helloWorld"},
{"HTTPStatusCode", "httpStatusCode"},
{"ParseURL.DoParse", "parseUrlDoParse"},
{"ParseUrl.DoParse", "parseUrlDoParse"},
{"parse_url.do_parse", "parseUrlDoParse"},
{"convert space", "convertSpace"},
{"convert-dash", "convertDash"},
{"skip___multiple_underscores", "skipMultipleUnderscores"},
{"skip multiple spaces", "skipMultipleSpaces"},
{"skip---multiple-dashes", "skipMultipleDashes"},
{"", ""},
{"a", "a"},
{"Z", "z"},
{"special-characters_test", "specialCharactersTest"},
{"numbers123test", "numbers123Test"},
{"hello world!", "helloWorld"},
{"test@with#symbols", "testWithSymbols"},
{"complexCase123!@#", "complexCase123"},
{"snake_case_string", "snakeCaseString"},
{"kebab-case-string", "kebabCaseString"},
{"PascalCaseString", "pascalCaseString"},
{"camelCaseString", "camelCaseString"},
{"HTTPRequest", "httpRequest"},
{"user ID", "userId"},
{"UserId", "userId"},
{"userID", "userId"},
{"UserID", "userId"},
{"123NumberPrefix", "123NumberPrefix"},
{"__leading_underscores", "leadingUnderscores"},
{"trailing_underscores__", "trailingUnderscores"},
{"multiple___underscores", "multipleUnderscores"},
{" spaces around ", "spacesAround"},
}
for _, test := range tests {
result := LowerCamelCase(test.input)
if result != test.expected {
t.Errorf("LowerCamelCase(%q) = %q; expected %q", test.input, result, test.expected)
}
}
}

9
stringcase/kebab_case.go Normal file
View File

@@ -0,0 +1,9 @@
package stringcase
func KebabCase(s string) string {
return delimiterCase(s, '-', false)
}
func UpperKebabCase(s string) string {
return delimiterCase(s, '-', true)
}

View File

@@ -0,0 +1,49 @@
package stringcase
import "testing"
func TestKebabCase(t *testing.T) {
tests := []struct {
input string
expected string
}{
{"HelloWorld", "hello-world"},
{"helloWorld", "hello-world"},
{"Hello World", "hello-world"},
{"hello world!", "hello-world"},
{"Numbers123Test", "numbers-123-test"},
{"", ""},
{"_", ""},
{"__Hello__World__", "hello-world"},
}
for _, test := range tests {
result := KebabCase(test.input)
if result != test.expected {
t.Errorf("KebabCase(%q) = %q; expected %q", test.input, result, test.expected)
}
}
}
func TestUpperKebabCase(t *testing.T) {
tests := []struct {
input string
expected string
}{
{"HelloWorld", "HELLO-WORLD"},
{"helloWorld", "HELLO-WORLD"},
{"Hello World", "HELLO-WORLD"},
{"hello world!", "HELLO-WORLD"},
{"Numbers123Test", "NUMBERS-123-TEST"},
{"", ""},
{"_", ""},
{"__Hello__World__", "HELLO-WORLD"},
}
for _, test := range tests {
result := UpperKebabCase(test.input)
if result != test.expected {
t.Errorf("UpperKebabCase(%q) = %q; expected %q", test.input, result, test.expected)
}
}
}

50
stringcase/snake_case.go Normal file
View File

@@ -0,0 +1,50 @@
package stringcase
import (
"strings"
)
// ToSnakeCase 把字符转换为 蛇形命名法snake_case
func ToSnakeCase(input string) string {
return SnakeCase(input)
}
func SnakeCase(s string) string {
return delimiterCase(s, '_', false)
}
func UpperSnakeCase(s string) string {
return delimiterCase(s, '_', true)
}
func delimiterCase(input string, delimiter rune, upperCase bool) string {
input = strings.TrimSpace(input)
if input == "" {
return input
}
// 使用 Split 分割字符串
words := Split(input)
filteredWords := make([]string, 0, len(words))
for _, word := range words {
if strings.TrimSpace(word) != "" {
filteredWords = append(filteredWords, word)
}
}
adjustCase := toLower
if upperCase {
adjustCase = toUpper
}
for i, word := range filteredWords {
runes := []rune(word)
for j := 0; j < len(runes); j++ {
runes[j] = adjustCase(runes[j])
}
filteredWords[i] = string(runes)
}
// 使用分隔符连接结果
return strings.Join(filteredWords, string(delimiter))
}

View File

@@ -0,0 +1,85 @@
package stringcase
import (
"testing"
)
func TestToSnakeCase(t *testing.T) {
tests := []struct {
input string
expected string
}{
{"snake_case", "snake_case"},
{"CamelCase", "camel_case"},
{"lowerCamelCase", "lower_camel_case"},
{"F", "f"},
{"Foo", "foo"},
{"FooB", "foo_b"},
{"FooID", "foo_id"},
{" FooBar\t", "foo_bar"},
{"HTTPStatusCode", "http_status_code"},
{"ParseURL.DoParse", "parse_url_do_parse"},
{"Convert Space", "convert_space"},
{"Convert-dash", "convert_dash"},
{"Skip___MultipleUnderscores", "skip_multiple_underscores"},
{"Skip MultipleSpaces", "skip_multiple_spaces"},
{"Skip---MultipleDashes", "skip_multiple_dashes"},
{"Hello World", "hello_world"},
{"Multiple Words Example", "multiple_words_example"},
{"", ""},
{"A", "a"},
{"z", "z"},
{"Special-Characters_Test", "special_characters_test"},
{"Numbers123Test", "numbers_123_test"},
{"Hello World!", "hello_world"},
{"Test@With#Symbols", "test_with_symbols"},
{"ComplexCase123!@#", "complex_case_123"},
}
for _, test := range tests {
result := ToSnakeCase(test.input)
if result != test.expected {
t.Errorf("ToSnakeCase(%q) = %q; expected %q", test.input, result, test.expected)
}
}
}
func TestUpperSnakeCase(t *testing.T) {
tests := []struct {
input string
expected string
}{
{"snake_case", "SNAKE_CASE"},
{"CamelCase", "CAMEL_CASE"},
{"lowerCamelCase", "LOWER_CAMEL_CASE"},
{"F", "F"},
{"Foo", "FOO"},
{"FooB", "FOO_B"},
{"FooID", "FOO_ID"},
{" FooBar\t", "FOO_BAR"},
{"HTTPStatusCode", "HTTP_STATUS_CODE"},
{"ParseURL.DoParse", "PARSE_URL_DO_PARSE"},
{"Convert Space", "CONVERT_SPACE"},
{"Convert-dash", "CONVERT_DASH"},
{"Skip___MultipleUnderscores", "SKIP_MULTIPLE_UNDERSCORES"},
{"Skip MultipleSpaces", "SKIP_MULTIPLE_SPACES"},
{"Skip---MultipleDashes", "SKIP_MULTIPLE_DASHES"},
{"Hello World", "HELLO_WORLD"},
{"Multiple Words Example", "MULTIPLE_WORDS_EXAMPLE"},
{"", ""},
{"A", "A"},
{"z", "Z"},
{"Special-Characters_Test", "SPECIAL_CHARACTERS_TEST"},
{"Numbers123Test", "NUMBERS_123_TEST"},
{"Hello World!", "HELLO_WORLD"},
{"Test@With#Symbols", "TEST_WITH_SYMBOLS"},
{"ComplexCase123!@#", "COMPLEX_CASE_123"},
}
for _, test := range tests {
result := UpperSnakeCase(test.input)
if result != test.expected {
t.Errorf("UpperSnakeCase(%q) = %q; expected %q", test.input, result, test.expected)
}
}
}

144
stringcase/split.go Normal file
View File

@@ -0,0 +1,144 @@
package stringcase
import (
"strings"
"unicode"
"unicode/utf8"
)
type runeInfo struct {
r rune
}
// Checks whether or not the rune represented by rInfo is a digit.
func (rInfo *runeInfo) isDigit() bool {
return unicode.IsDigit(rInfo.r)
}
// Checks whether or not the rune represented by rInfo is an uppercase rune.
func (rInfo *runeInfo) isUppercase() bool {
return unicode.IsUpper(rInfo.r)
}
// A reader designed for reading "CamelCase" strings.
type rdr struct {
input string // The data this reader operates on.
pos int // The position of this reader.
hasNextRune bool // A flag indicating if there's a next rune.
rdRune runeInfo // Information about the last rune that was read.
nxtRune runeInfo // Information about the next rune that's about to be read.
}
// Read the next rune from r.
func (r *rdr) readRune() {
r.rdRune = runeInfo{rune(r.input[r.pos])}
r.pos = r.pos + 1
r.hasNextRune = r.pos < len(r.input)
if r.hasNextRune {
r.nxtRune = runeInfo{rune(r.input[r.pos])}
}
}
// Undo the last rune from r.
func (r *rdr) unreadRune() {
r.pos = r.pos - 1
r.nxtRune = r.rdRune
r.rdRune = runeInfo{rune(r.input[r.pos])}
r.hasNextRune = true // NOTE: An undo operation means that there will be always a next rune.
}
// Verify if the word that's currently read by r is a word that should NOT be split.
// If noSplit contains a word that starts with the word that's currently read by r, this function returns true, false
// otherwise.
func (r *rdr) isNoSplitWord(sIdx int, noSplit []string) bool {
return ContainsFn(noSplit, r.input[sIdx:r.pos+1], func(got, want string) bool {
return strings.HasPrefix(got, want)
})
}
// Read the next part from r.
// Each word in noSplit (if provided) is treated as a word that shouldn't be split.
func (r *rdr) readNextPart(noSplit []string) string {
sIdx := r.pos
r.readRune()
if r.rdRune.isDigit() {
return r.readNumber(sIdx, noSplit)
}
return r.readWord(sIdx, noSplit)
}
// Read and return a number from r.
func (r *rdr) readNumber(sIdx int, noSplit []string) string {
if r.hasNextRune && r.nxtRune.isDigit() {
for r.hasNextRune && (r.nxtRune.isDigit() || r.isNoSplitWord(sIdx, noSplit)) {
r.readRune()
}
return r.input[sIdx:r.pos]
}
return r.input[sIdx:r.pos]
}
// Read and return a word from r.
func (r *rdr) readWord(sIdx int, noSplit []string) string {
if r.hasNextRune && r.nxtRune.isUppercase() {
for r.hasNextRune && (r.nxtRune.isUppercase() || r.isNoSplitWord(sIdx, noSplit)) {
r.readRune()
}
if r.hasNextRune && (!r.nxtRune.isUppercase() && !r.nxtRune.isDigit()) {
r.unreadRune()
}
return r.input[sIdx:r.pos]
}
for r.hasNextRune && (r.isNoSplitWord(sIdx, noSplit) || (!r.nxtRune.isUppercase() && !r.nxtRune.isDigit())) {
r.readRune()
}
return r.input[sIdx:r.pos]
}
// Split reads v treating it as a "CamelCase" and returns the different words.
// If v isn't a valid UTF-8 string, or when v is an empty string, a slice with one element (v) is returned.
// Each word in noSplit (if provided) is treated as a word that shouldn't be split.
func Split(input string, noSplit ...string) []string {
if !utf8.ValidString(input) || len(input) == 0 {
return []string{input}
}
output := make([]string, 0)
inputs := SplitByNonAlphanumeric(input)
for _, v := range inputs {
v = strings.TrimSpace(v)
if v == "" {
continue
}
output = append(output, split(v, noSplit...)...)
}
return output
}
func split(input string, noSplit ...string) []string {
if !utf8.ValidString(input) || len(input) == 0 {
return []string{input}
}
vRdr := &rdr{input: input}
output := make([]string, 0)
for vRdr.pos < len(input) {
part := vRdr.readNextPart(noSplit)
output = append(output, part)
}
return output
}

82
stringcase/split_test.go Normal file
View File

@@ -0,0 +1,82 @@
package stringcase
import (
"testing"
)
func TestSplitSingle(t *testing.T) {
input := "URL.DoParse"
result := Split(input)
t.Logf("Split(%q) = %q;", input, result)
t.Log(input[:7])
}
func TestSplit(t *testing.T) {
tests := []struct {
input string
expected []string
}{
{"hello world", []string{"hello", "world"}},
{"hello_world", []string{"hello", "world"}},
{"hello-world", []string{"hello", "world"}},
{"hello.world", []string{"hello", "world"}},
{"helloWorld", []string{"hello", "World"}},
{"HelloWorld", []string{"Hello", "World"}},
{"HTTPStatusCode", []string{"HTTP", "Status", "Code"}},
{"ParseURLDoParse", []string{"Parse", "URL", "Do", "Parse"}},
{"ParseUrlDoParse", []string{"Parse", "Url", "Do", "Parse"}},
{"ParseUrl.DoParse", []string{"Parse", "Url", "Do", "Parse"}},
{"ParseURL.DoParse", []string{"Parse", "URL", "Do", "Parse"}},
{"ParseURL", []string{"Parse", "URL"}},
{"ParseURL.", []string{"Parse", "URL"}},
{"parse_url.do_parse", []string{"parse", "url", "do", "parse"}},
{"convert space", []string{"convert", "space"}},
{"convert-dash", []string{"convert", "dash"}},
{"skip___multiple_underscores", []string{"skip", "multiple", "underscores"}},
{"skip multiple spaces", []string{"skip", "multiple", "spaces"}},
{"skip---multiple-dashes", []string{"skip", "multiple", "dashes"}},
{"", []string{""}},
{"a", []string{"a"}},
{"Z", []string{"Z"}},
{"special-characters_test", []string{"special", "characters", "test"}},
{"numbers123test", []string{"numbers", "123", "test"}},
{"hello world!", []string{"hello", "world"}},
{"test@with#symbols", []string{"test", "with", "symbols"}},
{"complexCase123!@#", []string{"complex", "Case", "123"}},
{"snake_case_string", []string{"snake", "case", "string"}},
{"kebab-case-string", []string{"kebab", "case", "string"}},
{"PascalCaseString", []string{"Pascal", "Case", "String"}},
{"camelCaseString", []string{"camel", "Case", "String"}},
{"HTTPRequest", []string{"HTTP", "Request"}},
{"user ID", []string{"user", "ID"}},
{"UserId", []string{"User", "Id"}},
{"userID", []string{"user", "ID"}},
{"UserID", []string{"User", "ID"}},
{"123NumberPrefix", []string{"123", "Number", "Prefix"}},
{"__leading_underscores", []string{"leading", "underscores"}},
{"trailing_underscores__", []string{"trailing", "underscores"}},
{"multiple___underscores", []string{"multiple", "underscores"}},
{" spaces around ", []string{"spaces", "around"}},
}
for _, test := range tests {
result := Split(test.input)
if !compareStringSlices(result, test.expected) {
t.Errorf("Split(%q) = %q; expected %q", test.input, result, test.expected)
}
}
}
func compareStringSlices(slice1, slice2 []string) bool {
if len(slice1) != len(slice2) {
return false
}
for i := range slice1 {
if slice1[i] != slice2[i] {
return false
}
}
return true
}

View File

@@ -1,119 +0,0 @@
package stringcase
import (
"strings"
"unicode"
)
// ToSnakeCase 把字符转换为 蛇形命名法snake_case
func ToSnakeCase(input string) string {
if input == "" {
return input
}
if len(input) == 1 {
return strings.ToLower(input)
}
input = strings.Replace(input, " ", "", -1)
source := []rune(input)
dist := strings.Builder{}
dist.Grow(len(input) + len(input)/3) // avoid reallocation memory, 33% ~ 50% is recommended
skipNext := false
for i := 0; i < len(source); i++ {
cur := source[i]
switch cur {
case '-', '_':
dist.WriteRune('_')
skipNext = true
continue
}
if unicode.IsLower(cur) || unicode.IsDigit(cur) {
dist.WriteRune(cur)
continue
}
if i == 0 {
dist.WriteRune(unicode.ToLower(cur))
continue
}
last := source[i-1]
if (!unicode.IsLetter(last)) || unicode.IsLower(last) {
if skipNext {
skipNext = false
} else {
dist.WriteRune('_')
}
dist.WriteRune(unicode.ToLower(cur))
continue
}
// last is upper case
if i < len(source)-1 {
next := source[i+1]
if unicode.IsLower(next) {
if skipNext {
skipNext = false
} else {
dist.WriteRune('_')
}
dist.WriteRune(unicode.ToLower(cur))
continue
}
}
dist.WriteRune(unicode.ToLower(cur))
}
return dist.String()
}
// ToPascalCase 把字符转换为 帕斯卡命名/大驼峰命名法CamelCase
func ToPascalCase(input string) string {
return toCamelCase(input, true)
}
// ToLowCamelCase 把字符转换为 小驼峰命名法lowerCamelCase
func ToLowCamelCase(input string) string {
return toCamelCase(input, false)
}
func toCamelCase(s string, initCase bool) string {
s = strings.TrimSpace(s)
if s == "" {
return s
}
var uppercaseAcronym = map[string]string{}
if a, ok := uppercaseAcronym[s]; ok {
s = a
}
n := strings.Builder{}
n.Grow(len(s))
capNext := initCase
for i, v := range []byte(s) {
vIsCap := v >= 'A' && v <= 'Z'
vIsLow := v >= 'a' && v <= 'z'
if capNext {
if vIsLow {
v += 'A'
v -= 'a'
}
} else if i == 0 {
if vIsCap {
v += 'a'
v -= 'A'
}
}
if vIsCap || vIsLow {
n.WriteByte(v)
capNext = false
} else if vIsNum := v >= '0' && v <= '9'; vIsNum {
n.WriteByte(v)
capNext = true
} else {
capNext = v == '_' || v == ' ' || v == '-' || v == '.'
}
}
return n.String()
}

View File

@@ -1,24 +0,0 @@
package stringcase
import (
"fmt"
"testing"
)
func TestToLowCamelCase(t *testing.T) {
fmt.Println(ToLowCamelCase("snake_case"))
fmt.Println(ToLowCamelCase("CamelCase"))
fmt.Println(ToLowCamelCase("lowerCamelCase"))
}
func TestToPascalCase(t *testing.T) {
fmt.Println(ToPascalCase("snake_case"))
fmt.Println(ToPascalCase("CamelCase"))
fmt.Println(ToPascalCase("lowerCamelCase"))
}
func TestToSnakeCase(t *testing.T) {
fmt.Println(ToSnakeCase("snake_case"))
fmt.Println(ToSnakeCase("CamelCase"))
fmt.Println(ToSnakeCase("lowerCamelCase"))
}

132
stringcase/utils.go Normal file
View File

@@ -0,0 +1,132 @@
package stringcase
import (
"regexp"
"strings"
"unicode"
)
func isLower(ch rune) bool {
return ch >= 'a' && ch <= 'z'
}
func toLower(ch rune) rune {
if ch >= 'A' && ch <= 'Z' {
return ch + 32
}
return ch
}
func isUpper(ch rune) bool {
return ch >= 'A' && ch <= 'Z'
}
func toUpper(ch rune) rune {
if ch >= 'a' && ch <= 'z' {
return ch - 32
}
return ch
}
func isSpace(ch rune) bool {
return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r'
}
func isDigit(ch rune) bool {
return ch >= '0' && ch <= '9'
}
func isDelimiter(ch rune) bool {
return ch == '-' || ch == '_' || isSpace(ch)
}
type iterFunc func(prev, curr, next rune)
func stringIter(s string, callback iterFunc) {
var prev rune
var curr rune
for _, next := range s {
if curr == 0 {
prev = curr
curr = next
continue
}
callback(prev, curr, next)
prev = curr
curr = next
}
if len(s) > 0 {
callback(prev, curr, 0)
}
}
func isAlpha(r rune) bool {
return (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z')
}
func ReplaceNonAlphanumeric(s string, replacement string) string {
if replacement == "" {
replacement = "_"
}
// 使用正则表达式匹配非英文字母和数字的字符
re := regexp.MustCompile("[^a-zA-Z0-9]+")
// 替换为指定字符
return re.ReplaceAllString(s, replacement)
}
func SplitByNonAlphanumeric(input string) []string {
var builder strings.Builder
for _, r := range input {
if unicode.IsLetter(r) || unicode.IsDigit(r) {
builder.WriteRune(r)
} else {
builder.WriteRune(' ') // 将非英文字符和数字的字符替换为空格
}
}
processedInput := builder.String()
return strings.Fields(processedInput) // 使用空格分割字符串
}
func SplitAndKeepDelimiters(input string) []string {
var result []string
var builder strings.Builder
for _, r := range input {
if unicode.IsLetter(r) || unicode.IsDigit(r) {
builder.WriteRune(r)
} else {
if builder.Len() > 0 {
result = append(result, builder.String())
builder.Reset()
}
result = append(result, string(r)) // 保留分隔符
}
}
if builder.Len() > 0 {
result = append(result, builder.String())
}
return result
}
func ContainsFn[T any](slice []T, value T, predicate func(got, want T) bool) bool {
for _, item := range slice {
if predicate(item, value) {
return true
}
}
return false
}
func isUpperCaseWord(word string) bool {
for _, r := range word {
if !unicode.IsUpper(r) {
return false
}
}
return true
}

52
stringcase/utils_test.go Normal file
View File

@@ -0,0 +1,52 @@
package stringcase
import (
"reflect"
"testing"
)
func TestSplitByNonAlphanumeric(t *testing.T) {
tests := []struct {
input string
expected []string
}{
{"hello-world", []string{"hello", "world"}},
{"hello_world", []string{"hello", "world"}},
{"hello.world", []string{"hello", "world"}},
{"hello world", []string{"hello", "world"}},
{"hello123world", []string{"hello123world"}},
{"hello123 world", []string{"hello123", "world"}},
{"hello-world_123", []string{"hello", "world", "123"}},
{"!hello@world#", []string{"hello", "world"}},
}
for _, test := range tests {
result := SplitByNonAlphanumeric(test.input)
if !reflect.DeepEqual(result, test.expected) {
t.Errorf("SplitByNonAlphanumeric(%q) = %v; expected %v", test.input, result, test.expected)
}
}
}
func TestSplitAndKeepDelimiters(t *testing.T) {
tests := []struct {
input string
expected []string
}{
{"hello-world", []string{"hello", "-", "world"}},
{"hello_world", []string{"hello", "_", "world"}},
{"hello.world", []string{"hello", ".", "world"}},
{"hello world", []string{"hello", " ", "world"}},
{"hello123world", []string{"hello123world"}},
{"hello123 world", []string{"hello123", " ", "world"}},
{"hello-world_123", []string{"hello", "-", "world", "_", "123"}},
{"!hello@world#", []string{"!", "hello", "@", "world", "#"}},
}
for _, test := range tests {
result := SplitAndKeepDelimiters(test.input)
if !reflect.DeepEqual(result, test.expected) {
t.Errorf("SplitAndKeepDelimiters(%q) = %v; expected %v", test.input, result, test.expected)
}
}
}

12
tag.bat
View File

@@ -1,12 +1,18 @@
git tag v1.1.27
git tag v1.1.29
git tag bank_card/v1.1.5
git tag geoip/v1.1.5
git tag translator/v1.1.2
git tag copierutil/v0.0.4
git tag copierutil/v0.0.5
git tag jwtutil/v0.0.2
git tag id/v0.0.2
git tag slug/v0.0.1
git tag name_generator/v0.0.1
git tag mapper/v0.0.3
git tag password/v0.0.1
git tag query_parser/v0.0.2
git tag entgo/v1.1.29
git tag entgo/v1.1.32
git tag gorm/v1.1.6
git push origin --tags

View File

@@ -1,6 +1,10 @@
package trans
import "time"
import (
"time"
"github.com/google/uuid"
)
func String(a string) *string {
return &a
@@ -527,3 +531,28 @@ func MapValues[TKey mapKeyValueType, TValue mapKeyValueType](source map[TKey]TVa
}
return target
}
func ToUuidPtr(str *string) *uuid.UUID {
var id *uuid.UUID
if str != nil {
_id, err := uuid.Parse(*str)
if err != nil {
return nil
}
id = &_id
}
return id
}
func ToUuid(str string) uuid.UUID {
id, _ := uuid.Parse(str)
return id
}
func ToStringPtr(id *uuid.UUID) *string {
var strUUID *string
if id != nil {
strUUID = Ptr(id.String())
}
return strUUID
}

View File

@@ -4,6 +4,7 @@ import (
"testing"
"time"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
)
@@ -167,3 +168,62 @@ func Test_Trans(t *testing.T) {
assert.Equal(t, time.Now(), tmVal)
assert.Equal(t, time.Now(), TimeValue(nil))
}
func TestUUID(t *testing.T) {
t.Run("ToUuidPtr_NilString", func(t *testing.T) {
var str *string
result := ToUuidPtr(str)
if result != nil {
t.Errorf("expected nil, got %v", result)
}
})
t.Run("ToUuidPtr_ValidString", func(t *testing.T) {
str := "550e8400-e29b-41d4-a716-446655440000"
result := ToUuidPtr(&str)
if result == nil || result.String() != str {
t.Errorf("expected %v, got %v", str, result)
}
})
t.Run("ToUuidPtr_InvalidString", func(t *testing.T) {
str := "invalid-uuid"
result := ToUuidPtr(&str)
if result != nil {
t.Errorf("expected nil, got %v", result)
}
})
t.Run("ToUuid_ValidString", func(t *testing.T) {
str := "550e8400-e29b-41d4-a716-446655440000"
result := ToUuid(str)
if result.String() != str {
t.Errorf("expected %v, got %v", str, result)
}
})
t.Run("ToUuid_InvalidString", func(t *testing.T) {
str := "invalid-uuid"
result := ToUuid(str)
if result.String() == str {
t.Errorf("expected invalid UUID, got %v", result)
}
})
t.Run("ToStringPtr_NilUUID", func(t *testing.T) {
var id *uuid.UUID
result := ToStringPtr(id)
if result != nil {
t.Errorf("expected nil, got %v", result)
}
})
t.Run("ToStringPtr_ValidUUID", func(t *testing.T) {
id := uuid.MustParse("550e8400-e29b-41d4-a716-446655440000")
result := ToStringPtr(&id)
expected := "550e8400-e29b-41d4-a716-446655440000"
if result == nil || *result != expected {
t.Errorf("expected %v, got %v", expected, result)
}
})
}

View File

@@ -1,32 +0,0 @@
package uuid
import (
"github.com/google/uuid"
"github.com/tx7do/go-utils/trans"
)
func ToUuidPtr(str *string) *uuid.UUID {
var id *uuid.UUID
if str != nil {
_id, err := uuid.Parse(*str)
if err != nil {
return nil
}
id = &_id
}
return id
}
func ToUuid(str string) uuid.UUID {
id, _ := uuid.Parse(str)
return id
}
func ToStringPtr(id *uuid.UUID) *string {
var strUUID *string
if id != nil {
strUUID = trans.String(id.String())
}
return strUUID
}

View File

@@ -1,66 +0,0 @@
package uuid
import (
"testing"
"github.com/google/uuid"
)
func TestUUID(t *testing.T) {
t.Run("ToUuidPtr_NilString", func(t *testing.T) {
var str *string
result := ToUuidPtr(str)
if result != nil {
t.Errorf("expected nil, got %v", result)
}
})
t.Run("ToUuidPtr_ValidString", func(t *testing.T) {
str := "550e8400-e29b-41d4-a716-446655440000"
result := ToUuidPtr(&str)
if result == nil || result.String() != str {
t.Errorf("expected %v, got %v", str, result)
}
})
t.Run("ToUuidPtr_InvalidString", func(t *testing.T) {
str := "invalid-uuid"
result := ToUuidPtr(&str)
if result != nil {
t.Errorf("expected nil, got %v", result)
}
})
t.Run("ToUuid_ValidString", func(t *testing.T) {
str := "550e8400-e29b-41d4-a716-446655440000"
result := ToUuid(str)
if result.String() != str {
t.Errorf("expected %v, got %v", str, result)
}
})
t.Run("ToUuid_InvalidString", func(t *testing.T) {
str := "invalid-uuid"
result := ToUuid(str)
if result.String() == str {
t.Errorf("expected invalid UUID, got %v", result)
}
})
t.Run("ToStringPtr_NilUUID", func(t *testing.T) {
var id *uuid.UUID
result := ToStringPtr(id)
if result != nil {
t.Errorf("expected nil, got %v", result)
}
})
t.Run("ToStringPtr_ValidUUID", func(t *testing.T) {
id := uuid.MustParse("550e8400-e29b-41d4-a716-446655440000")
result := ToStringPtr(&id)
expected := "550e8400-e29b-41d4-a716-446655440000"
if result == nil || *result != expected {
t.Errorf("expected %v, got %v", expected, result)
}
})
}