{"id":24295315,"url":"https://github.com/goflower-io/crud","last_synced_at":"2025-09-25T21:31:24.343Z","repository":{"id":120111339,"uuid":"609010380","full_name":"happycrud/crud","owner":"happycrud","description":"a mysql,mariadb,postgresql,sqlite3 golang crud code generate tool from table DDL , mongodb crud code generate form struct. maybe it can reduce a lot of work ","archived":false,"fork":false,"pushed_at":"2024-10-16T15:02:08.000Z","size":189,"stargazers_count":10,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-11-15T21:44:25.915Z","etag":null,"topics":["codegenerate","golang","mariadb","mongodb","mysql","orm","postgresql","sqlite3"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/happycrud.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-03-03T07:23:51.000Z","updated_at":"2024-10-16T15:02:13.000Z","dependencies_parsed_at":null,"dependency_job_id":"2218478a-dfa0-4473-a28d-1054cb725d31","html_url":"https://github.com/happycrud/crud","commit_stats":null,"previous_names":["cleancrud/crud"],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/happycrud%2Fcrud","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/happycrud%2Fcrud/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/happycrud%2Fcrud/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/happycrud%2Fcrud/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/happycrud","download_url":"https://codeload.github.com/happycrud/crud/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":234252666,"owners_count":18803243,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["codegenerate","golang","mariadb","mongodb","mysql","orm","postgresql","sqlite3"],"created_at":"2025-01-16T18:40:34.561Z","updated_at":"2025-09-25T21:31:24.336Z","avatar_url":"https://github.com/happycrud.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n# crud is a crud code generate tool support mysql,mariadb,postgresql,sqlite3,monogdb\n\n## [中文文档](README_zh.md)\n\n\n## Overview\n\nCrud is a very easy to learn and easy to use ORM framework. Using crud can enable you to complete business requirements quickly, gracefully and with high performance. Currently, mysql,mariadb,postgresql,sqlite3,monogdb are supported.\n\n\n- From SQL DDL table structure design to corresponding model and service generation, it conforms to the process of creating tables before writing code\n\n- Supports transactions, row-level locking, for update, lock in share mode\n\n- Elegant API, no ugly hard coding, SQL fragments, all static method calls, and automatic prompt of IDE\n\n- It supports batch insertion, upsert, and automatic assignment of self incrementing ID to structure\n\n- Support context\n\n- High performance. When querying all fields in the table, no reflection is used to create objects, and the performance is consistent with that of native\n\n- Query support forceindex\n\n- Query supports flexible setting of query criteria\n\n- Query supports group by and having\n\n- Query supports scan query results to user-defined structures (using reflection)\n\n- Server code standardization\n\n- Support the generation of proto files and service semi implementation codes containing grpc interface definitions according to SQL DDL table structure definition files\n\n\n## [example](https://github.com/goflower-io/crud-example)\n## [mysql,postgresql,sqlite3 examples](./example)\n## Getting Started \n\n### install\n\n```bash\n\ngo install  github.com/goflower-io/crud@main\n\n```\n### Using the command line\n\n```bash\ncrud -h \n\nUsage of crud:\n  -dialect string\n    \t-dialete only support mysql postgres sqlite3, default mysql  (default \"mysql\")\n  -protopkg string\n    \t-protopkg  proto package field value\n  -service\n    \t-service  generate GRPC proto message and service implementation\n```\n\n```mysql example\n#  generation crud directory\ncrud init\n\n# Put user.sql In the crud directory sql\n\n\n# According to the table structure, generate the proto file of grpc interface and service semi implementation code for the CRUD of the table\ncrud  -service -protopkg example\n\n```\n\n## Init\n\n\n### Init db\n```go\n\ndb, _ = sql.Open(\"mysql\",\"user:pwd@tcp(127.0.0.1:3306)/example?timeout=1s\u0026readTimeout=1s\u0026writeTimeout=1s\u0026parseTime=true\u0026loc=Local\u0026charset=utf8mb4,utf8\")\n\n```\n\n### Or the client wrapped in curd has read-write separation and context read-write timeout configuration ability\n\n```go\n\nvar client *crud.Client\n\nvar dsn = \"root:123456@tcp(127.0.0.1:3306)/test?parseTime=true\"\n\nfunc InitDB2() {\n\tclient, _ = crud.NewClient(\u0026xsql.Config{\n\t\tDSN:          dsn,\n\t\tReadDSN:      []string{dsn},\n\t\tActive:       10,\n\t\tIdle:         10,\n\t\tIdleTimeout:  time.Hour,\n\t\tQueryTimeout: time.Second,\n\t\tExecTimeout:  time.Second,\n\t})\n}\n```\n\n\n### As user SQL table creation file as an example\n\n```SQL\nCREATE TABLE `user` (\n  `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id字段',\n  `name` varchar(100) NOT NULL COMMENT '名称',\n  `age` int(11) NOT NULL DEFAULT '0' COMMENT '年龄',\n  `ctime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',\n  `mtime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',\n  PRIMARY KEY (`id`),\n  KEY `ix_name` (`name`) USING BTREE,\n  KEY `ix_mtime` (`mtime`) USING BTREE\n) ENGINE=InnoDB  DEFAULT CHARSET=utf8mb4\n```\n\n```bash\n# exec crud under example\ncrud \n\n# The following user directories and files will be generated\nmysql/\n├── crud\n│   ├── aa_client.go\n│   ├── user\n│   │   └── user.go\n│   └── user.sql\n\n```\n\u003e The user directory is generated above, and the package name is user.\n\n## CRUD API\n\n### Create\n\n#### Single insert\n```go\nu := \u0026user.User{\n\tID:    0,\n\tName:  \"shengjie\",\n\tAge:   18,\n\tCtime: time.Now(),\n\tMtime: time.Now(),\n}\neffect, err := user.\n\tCreate(db).\n\tSetUser(u).\n\tSave(ctx)\n\nfmt.Println(err, u, effect)\n```\n\u003e Insert a single record. Before inserting the above code, set id = 0 and ID field as auto_increment, crud will assign the self increasing ID generated by the database to u.ID, and the u.ID after insertion is the ID generated by DB.\n\n\n#### Batch insert\n\n```go\nu1 := \u0026user.User{\n\tID:   0,\n\tName: \"shengjie\",\n\tAge:  22,\n\tCtime: time.Now(),\n\tMtime: time.Now(),\n}\nu2 := \u0026user.User{\n\tID:   0,\n\tName: \"shengjie2\",\n\tAge:  22,\n\tCtime: time.Now(),\n\tMtime: time.Now(),\n}\neffect, err = user.\n\tCreate(db).\n\tSetUser(u1,u2).\n\tSave(ctx)\nfmt.Println(effect, err, u1, u2)\n```\n\u003e The above two records will be inserted. The lastinsertid returned by each record cannot be obtained during batch insertion, so the ID of U1 and U2 after insertion are 0.\n\n#### Upsert\n\n```go\na := \u0026user.User{\n\tID:   1,\n\tName: \"shengjie\",\n\tAge:  19,\n}\neffect, err := user.\n\tCreate(db).\n\tSetUser(a).\n\tUpsert(ctx)\n\nfmt.Println(effect, err, a)\n```\n\n\u003e If a unique key conflict is encountered during insertion, all fields will be updated with the new value passed in.\n\n#### Attention\n1. During batch insertion, the structure will not take the lastinsertid returned by the database.\n\n2. If the default value of the database is not the zero value of its type, and the corresponding structure does not set the value of this field in the insertion operation, crud will insert dB with the zero value of its type.\n\n3. It is strongly recommended that the value type must use: not null default 0, and the string type must use: not null default \"\"\n\n\n### Query\n\n#### Query a single record\n```go\nu, err = user.\n\tFind(db).\n\tWhere(user.IdOp.EQ(1)).\n\tOne(ctx)\n\nfmt.Println(u, err)\n```\n\u003e One(ctx) will automatically set the query statement limit = 1.\n\n\n#### Query multiple records\n```go\nlist, err := user.\n\tFind(db).\n\tWhere(\n\t\tuser.AgeOp.In(18, 20, 30),\n\t\t).\n\tAll(ctx)\n\nliststr, _ := json.Marshal(list)\nfmt.Printf(\"%+v %+v \\n\", string(liststr), err)\n```\n\u003e Query all records with ages of 18, 20 and 30, and All(ctx) returns []*user.User .\n\n```go\nlist, err := user.Find(db)).\n\tWhere(user.Or(\n\t\tuser.IdOp.GT(97),\n\t\tuser.AgeOp.In(10, 20, 30),\n\t\t)).\n\tOrderAsc(user.Age).\n\tOffset(2).\n\tLimit(20).\n\tAll(ctx)\nfmt.Printf(\"%+v %+v \\n\", list, err)\n```\n\u003e Rich query criteria expression support\n\n```go\nlist, err := user.\n\tFind(db).\n\tWhere(\n\t\tuser.NameOp.Contains(\"java\"),\n\t\t).\n\tAll(ctx)\n\nlist, err = user.\n\tFind(db).\n\tWhere(\n\t\tuser.NameOp.HasPrefix(\"java\"),\n\t\t).\n\tAll(ctx)\n```\n\u003e String field fuzzy query and prefix matching.\n\n\n#### The query result is a single column\n```go\ncount, err := user.\n\tFind(db).\n\tCount().\n\tWhere(user.IdOp.GT(0)).\n\tInt64(ctx)\n\nfmt.Println(count, err)\n\nnames, err := user.\n\tFind(db).\n\tSelect(user.Name).\n\tLimit(2).\n\tWhere(\n\t\tuser.IdOp.In(1, 2, 3, 4),\n\t\t).\n\tStrings(ctx)\nfmt.Println(names, err)\n```\n\u003e Count() query the quantity of qualified records; If the returned result contains only one column and only one row, Int64 and String can be used; If the returned result contains only one column and multiple rows, you can use Int64s and Strings to get the list.\n\n#### Select () parameter description\n\n```go\nus, _ := user.Find(db).\n\tSelect().\n\tWhere(\n\t\tuser.AgeOp.GT(10),\n\t).\n\tAll(ctx)\n\nus2, _ := user.Find(db).\n\tSelect(user.Columns()...).\n\tWhere(\n\t\tuser.AgeOp.GT(10),\n\t).\n\tAll(ctx)\n\n```\n\u003e The SQL statements and results generated by the above two queries are the same, but they are very different internally.\n\u003e When Select() does not specify parameters, crud will find all fields corresponding to the model. When returning results, it does not use reflection to create objects, If the return value has a null value, an error will be returned.\n\u003e When Select(user.Columns()...) When all column names are specified, the returned results will use reflection to create objects. If the return value has a null value, no error will be reported, and the default value of this field is zero\n\n### Transaction support\n\n```go\ntx, err := db.Begin(ctx)\nif err != nil {\n\treturn err\n}\nu1 := \u0026user.User{\n\tID:   0,\n\tName: \"shengjie\",\n\tAge:  18,\n}\n_, err = user.\n\tCreate(tx).\n\tSetUser(u1).\n\tSave(ctx)\nif err != nil {\n\treturn tx.Rollback()\n}\neffect, err := user.\n\tUpdate(tx).\n\tSetAge(100).\n\tWhere(\n\t\tuser.IdOp.EQ(u1.ID)\n\t\t).\n\tSave(ctx)\n\nif err != nil {\n\treturn tx.Rollback()\n}\nfmt.Println(effect, err)\nreturn tx.Commit()\n```\n\n\n\n### Advanced Query\n\n#### Custom query result acquisition\n```go\ntype GroupResutl struct {\n\tName string `json:\"name\"` \n\tCnt  int64  `json:\"cnt\"`\n}\n\nresult := []*GroupResutl{}\nerr := user.Find(db).\n\tSelect(\n\t\tuser.Name,\n\t\txsql.As(xsql.Count(\"*\"), \"cnt\"),\n\t\t).\n\tForceIndex(`ix_name`).\n\tGroupBy(user.Name).\n\tHaving(xsql.GT(`cnt`, 1)).\n\tSlice(ctx, \u0026result)\n// SELECT `name`, COUNT(*) AS `cnt` FROM `user` FORCE INDEX (`ix_name`) GROUP BY `name` HAVING `cnt` \u003e ? \nfmt.Println(err, result)\nb, _ := json.Marshal(result)\nfmt.Println(string(b))\n\n```\n\u003e The above uses force index, groupby, having, count and as to scan the user-defined query results into the user-defined structure. The JSON tag of the structure needs to be consistent with the column name returned from the query results, and the fields in the structure need to be capitalized.\n\n\u003e Slice(context,interface{}):The second parameter of the method needs to be passed in: a pointer to a structure slice\n\n\n### Update\n```go\n\neffect, err := user.\n\tUpdate(db).\n\tSetAge(10).\n\tWhere(user.NameOp.EQ(\"java\")).\n\tSave(ctx)\n\nfmt.Println(effect, err)\n\n\neffect, err = user.\n\tUpdate(db).\n\tSetAge(100).\n\tSetName(\"java\").\n\tSetName(\"python\").\n\tWhere(user.IDOp.EQ(97)).\n\tSave(ctx)\n\nfmt.Println(effect, err)\n\n// update `user` set `age` = COALESCE(`age`, 0) + -100, `name` = 'java' where `id` = 5\neffect, err = user.\n\tUpdate(db).\n\tAddAge(-100).\n\tSetName(\"java\").\n\tWhere(user.IDOp.EQ(97)).\n\tSave(ctx)\nfmt.Println(effect, err)\n\n```\n### Delete\n```go\n\neffect, err = user.\n\tDelete(db).\n\tWhere(\n\t\tuser.And(\n\t\t\tuser.IdOp.EQ(3), \n\t\t\tuser.IdOp.In(1, 3),\n\t\t)).\n\tExec(ctx)\n\n```\n\u003e It is only executed when the Exec method is called\n\n\n### Debug Log\n\n```go\n_, err := user.\n\tCreate(xsql.Debug(db)).\n\tSetUser(u).\n\tSave(ctx)\n\nfmt.Println(err)\n```\n\u003e The generated SQL statement and parameters will be printed\n\n## Generate grpc interface definition proto file and service implementation code\n\nThis function helps us generate a lot of cumbersome code that needs to be written by ourselves. For example, a project needs to manage the background, and the interfaces for adding, deleting, modifying and querying need to be built. If we can complete the interface writing with a little modification on the basis of the generated code, the business interface will be realized quickly and with quality.\n\n### Dependencies\n\n1. protoc\n2. protoc-gen-go\n3. protoc-gen-go-grpc\n4. make sure /usr/local/include have google/protobuf/empty.proto file\n\n\n```\ngo install google.golang.org/protobuf/cmd/protoc-gen-go\ngo install google.golang.org/grpc/cmd/protoc-gen-go-grpc\n```\n\n### usage\n```bash\n\ncrud -service -protopkg example\n\n\nexample/\n├── api\n│   ├── user.api_grpc.pb.go\n│   └── user.api.pb.go\n├── crud\n│   ├── aa_client.go\n│   ├── user\n│   │   ├── user.go\n│   └── user.sql\n├── proto\n│   └── user.api.proto\n└── service\n    └── user.service.go\n\n```\n\u003e There are more api and service directories and proto files.\n\n### proto example \nusr.api.proto\n```proto\nsyntax=\"proto3\";\noption go_package = \"/api\";\n\nimport \"google/protobuf/empty.proto\";\n\nservice UserService { \n    rpc CreateUser(User)returns(User);\n    rpc DeleteUser(UserId)returns(google.protobuf.Empty);\n    rpc UpdateUser(UpdateUserReq)returns(User);\n    rpc GetUser(UserId)returns(User);\n    rpc ListUsers(ListUsersReq)returns(ListUsersResp);\n}\n\nmessage User {\n    //id字段\n    int64\tid = 1 ; // @gotags: json:\"id\"\n    //名称\n    string\tname = 2 ; // @gotags: json:\"name\"\n    //年龄\n    int64\tage = 3 ; // @gotags: json:\"age\"\n    //创建时间\n    string\tctime = 4 ; // @gotags: json:\"ctime\"\n    //更新时间\n    string\tmtime = 5 ; // @gotags: json:\"mtime\"  \n}\n\nenum UserField{\n    User_unknow = 0;\n    User_id = 1;\n    User_name = 2;\n    User_age = 3;\n    User_ctime = 4;\n    User_mtime = 5;   \n}\n\nmessage UserId{\n    int64 id = 1 ; // @gotags: form:\"id\"\n}\n\nmessage UpdateUserReq{\n\n    User user = 1 ;\n\n    repeated string update_mask  = 2 ;\n}\n\n\nmessage ListUsersReq{\n    // number of page\n    int32 page = 1 ;// @gotags: form:\"page\"\n    // default 20\n    int32 page_size = 2 ;// @gotags: form:\"page_size\"\n    // order by field\n    UserField order_by_field = 3 ; // @gotags: form:\"order_by_field\"\n    // ASC DESC\n    bool order_by_desc = 4; //@gotags: form:\"order_by_desc\"\n     // filter\n    repeated UserFilter filters = 5 ; //@gotags: form:\"filters\"\n}\n\nmessage UserFilter{\n     UserField field = 1;\n    string op = 2;\n    string value = 3;\n}\n\nmessage ListUsersResp{\n\n    repeated User users = 1 ; // @gotags: json:\"users\"\n\n    int32 total_count = 2 ; // @gotags: json:\"total_count\"\n    \n    int32 page_count = 3 ; // @gotags: json:\"page_count\"\n}\n\n\n\n```\n\u003e Generate a proto message corresponding to the table structure, and the generated API file conforms to Google API design specification.\n\n### service example \nuser.service.go\n```go\npackage service\n\nimport (\n\t\"context\"\n\t\"github.com/goflower-io/crud/example/api\"\n\t\"github.com/goflower-io/crud/example/crud\"\n\t\"github.com/goflower-io/crud/example/crud/user\"\n\t\"math\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/goflower-io/xsql\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n\t\"google.golang.org/protobuf/types/known/emptypb\"\n)\n\n// UserServiceImpl UserServiceImpl\ntype UserServiceImpl struct {\n\tapi.UnimplementedUserServiceServer\n\tClient *crud.Client\n}\n\ntype IValidateUser interface {\n\tValidateUser(a *api.User) error\n}\n\n// CreateUser CreateUser\nfunc (s *UserServiceImpl) CreateUser(ctx context.Context, req *api.User) (*api.User, error) {\n\tif checker, ok := interface{}(s).(IValidateUser); ok {\n\t\tif err := checker.ValidateUser(req); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\ta := \u0026user.User{\n\t\tId:    0,\n\t\tName:  req.GetName(),\n\t\tAge:   req.GetAge(),\n\t\tCtime: time.Now(),\n\t\tMtime: time.Now(),\n\t}\n\tvar err error\n\t_, err = s.Client.User.\n\t\tCreate().\n\t\tSetUser(a).\n\t\tSave(ctx)\n\tif err != nil {\n\t\treturn nil, status.Error(codes.Internal, err.Error())\n\t}\n\t// query after create and return\n\ta2, err := s.Client.Master.User.\n\t\tFind().\n\t\tWhere(\n\t\t\tuser.IdOp.EQ(a.Id),\n\t\t).\n\t\tOne(ctx)\n\tif err != nil {\n\t\treturn nil, status.Error(codes.Internal, err.Error())\n\t}\n\treturn convertUser(a2), nil\n}\n\n// DeleteUser DeleteUser\nfunc (s *UserServiceImpl) DeleteUser(ctx context.Context, req *api.UserId) (*emptypb.Empty, error) {\n\t_, err := s.Client.User.\n\t\tDelete().\n\t\tWhere(\n\t\t\tuser.IdOp.EQ(req.GetId()),\n\t\t).\n\t\tExec(ctx)\n\tif err != nil {\n\t\treturn nil, status.Error(codes.Internal, err.Error())\n\t}\n\treturn \u0026emptypb.Empty{}, nil\n}\n\n// Updateuser UpdateUser\nfunc (s *UserServiceImpl) UpdateUser(ctx context.Context, req *api.UpdateUserReq) (*api.User, error) {\n\tif checker, ok := interface{}(s).(IValidateUser); ok {\n\t\tif err := checker.ValidateUser(req.User); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tif len(req.GetUpdateMask()) == 0 {\n\t\treturn nil, status.Error(codes.InvalidArgument, \"empty filter condition\")\n\t}\n\tupdate := s.Client.User.Update()\n\tfor _, v := range req.GetUpdateMask() {\n\t\tswitch v {\n\t\tcase user.Name:\n\t\t\tupdate.SetName(req.GetUser().GetName())\n\t\tcase user.Age:\n\t\t\tupdate.SetAge(req.GetUser().GetAge())\n\t\tcase user.Ctime:\n\t\t\tt, err := time.ParseInLocation(\"2006-01-02 15:04:05\", req.GetUser().GetCtime(), time.Local)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, status.Error(codes.InvalidArgument, err.Error())\n\t\t\t}\n\t\t\tupdate.SetCtime(t)\n\t\tcase user.Mtime:\n\t\t\tt, err := time.ParseInLocation(\"2006-01-02 15:04:05\", req.GetUser().GetMtime(), time.Local)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, status.Error(codes.InvalidArgument, err.Error())\n\t\t\t}\n\t\t\tupdate.SetMtime(t)\n\t\t}\n\t}\n\t_, err := update.\n\t\tWhere(\n\t\t\tuser.IdOp.EQ(req.GetUser().GetId()),\n\t\t).\n\t\tSave(ctx)\n\tif err != nil {\n\t\treturn nil, status.Error(codes.Internal, err.Error())\n\t}\n\t// query after update and return\n\ta, err := s.Client.Master.User.\n\t\tFind().\n\t\tWhere(\n\t\t\tuser.IdOp.EQ(req.GetUser().GetId()),\n\t\t).\n\t\tOne(ctx)\n\tif err != nil {\n\t\treturn nil, status.Error(codes.Internal, err.Error())\n\t}\n\treturn convertUser(a), nil\n}\n\n// GetUser GetUser\nfunc (s *UserServiceImpl) GetUser(ctx context.Context, req *api.UserId) (*api.User, error) {\n\ta, err := s.Client.User.\n\t\tFind().\n\t\tWhere(\n\t\t\tuser.IdOp.EQ(req.GetId()),\n\t\t).\n\t\tOne(ctx)\n\tif err != nil {\n\t\treturn nil, status.Error(codes.NotFound, err.Error())\n\t}\n\treturn convertUser(a), nil\n}\n\n// ListUsers ListUsers\nfunc (s *UserServiceImpl) ListUsers(ctx context.Context, req *api.ListUsersReq) (*api.ListUsersResp, error) {\n\tpage := req.GetPage()\n\tsize := req.GetPageSize()\n\tif size \u003c= 0 {\n\t\tsize = 20\n\t}\n\toffset := size * (page - 1)\n\tif offset \u003c 0 {\n\t\toffset = 0\n\t}\n\tfinder := s.Client.User.\n\t\tFind().\n\t\tOffset(offset).\n\t\tLimit(size)\n\n\tif req.GetOrderByField() == api.UserField_User_unknow {\n\t\treq.OrderByField = api.UserField_User_id\n\t}\n\todb := strings.TrimPrefix(req.GetOrderByField().String(), \"User_\")\n\tif req.GetOrderByDesc() {\n\t\tfinder.OrderDesc(odb)\n\t} else {\n\t\tfinder.OrderAsc(odb)\n\t}\n\tcounter := s.Client.User.\n\t\tFind().\n\t\tCount()\n\n\tvar ps []*xsql.Predicate\n\tfor _, v := range req.GetFilters() {\n\t\tp, err := xsql.GenP(strings.TrimPrefix(v.Field.String(), \"User_\"), v.Op, v.Value)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tps = append(ps, p)\n\t}\n\tif len(ps) \u003e 0 {\n\t\tp := xsql.And(ps...)\n\t\tfinder.WhereP(p)\n\t\tcounter.WhereP(p)\n\t}\n\tlist, err := finder.All(ctx)\n\tif err != nil {\n\t\treturn nil, status.Error(codes.Internal, err.Error())\n\t}\n\tcount, err := counter.Int64(ctx)\n\tif err != nil {\n\t\treturn nil, status.Error(codes.Internal, err.Error())\n\t}\n\tpageCount := int32(math.Ceil(float64(count) / float64(size)))\n\n\treturn \u0026api.ListUsersResp{Users: convertUserList(list), TotalCount: int32(count), PageCount: pageCount}, nil\n}\n\nfunc convertUser(a *user.User) *api.User {\n\treturn \u0026api.User{\n\t\tId:    a.Id,\n\t\tName:  a.Name,\n\t\tAge:   a.Age,\n\t\tCtime: a.Ctime.Format(\"2006-01-02 15:04:05\"),\n\t\tMtime: a.Mtime.Format(\"2006-01-02 15:04:05\"),\n\t}\n}\n\nfunc convertUserList(list []*user.User) []*api.User {\n\tret := make([]*api.User, 0, len(list))\n\tfor _, v := range list {\n\t\tret = append(ret, convertUser(v))\n\t}\n\treturn ret\n}\n\n\n```\n\u003e The semi implementation code of the above service only needs to add some parameter verification, or automatically generate the message conversion code from the DB layer model structure to the API layer according to the code of the condition filter, which is convenient and flexible.\n\n\n\n\n\u003e The project is inspired by [facebook/ent](https://github.com/ent/ent) \n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgoflower-io%2Fcrud","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgoflower-io%2Fcrud","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgoflower-io%2Fcrud/lists"}