Repository: danmuking/DiTing-Go Branch: main Commit: 2187d3b9da23 Files: 147 Total size: 412.3 KB Directory structure: gitextract_mkmm_a5b/ ├── .github/ │ └── workflows/ │ └── reademe-contributors.yml ├── .gitignore ├── LICENSE ├── README.md ├── cmd/ │ └── gen/ │ └── generate.go ├── conf/ │ └── config_example.yml ├── controller/ │ ├── captcha_controller.go │ ├── chat_controller.go │ ├── contact_controller.go │ ├── friend_controller.go │ └── user_controller.go ├── dal/ │ ├── db.go │ ├── model/ │ │ ├── contact.gen.go │ │ ├── group_member.gen.go │ │ ├── message.gen.go │ │ ├── room.gen.go │ │ ├── room_friend.gen.go │ │ ├── room_group.gen.go │ │ ├── user.gen.go │ │ ├── user_apply.gen.go │ │ └── user_friend.gen.go │ └── query/ │ ├── contact.gen.go │ ├── gen.go │ ├── group_member.gen.go │ ├── message.gen.go │ ├── room.gen.go │ ├── room_friend.gen.go │ ├── room_group.gen.go │ ├── user.gen.go │ ├── user_apply.gen.go │ └── user_friend.gen.go ├── docs/ │ ├── docs.go │ └── swagger.json ├── domain/ │ ├── dto/ │ │ ├── contact_dto.go │ │ ├── delete_friend_dto.go │ │ ├── group_dto.go │ │ └── msg_dto.go │ ├── enum/ │ │ ├── event_bus.go │ │ ├── login.go │ │ ├── message.go │ │ ├── redis.go │ │ ├── redsync.go │ │ ├── rocketmq.go │ │ ├── room.go │ │ └── user.go │ ├── model/ │ │ └── message.go │ └── vo/ │ ├── req/ │ │ ├── agree_friend_req.go │ │ ├── captcha_req.go │ │ ├── create_group_req.go │ │ ├── delete_friend_req.go │ │ ├── delete_group_req.go │ │ ├── get_group_member_list_req.go │ │ ├── get_message_list_req.go │ │ ├── get_new_content_list_req.go │ │ ├── get_new_msg_list_req.go │ │ ├── get_user_info_batch_req.go │ │ ├── get_user_info_by_name_req.go │ │ ├── grant_administrator_req.go │ │ ├── is_friend_req.go │ │ ├── join_group_req.go │ │ ├── message_req.go │ │ ├── quit_group_req.go │ │ ├── remove_administrator_req.go │ │ ├── uid_req.go │ │ ├── user_apply_req.go │ │ ├── user_cancle_req.go │ │ ├── user_login_req.go │ │ └── user_register_req.go │ └── resp/ │ ├── get_user_info_batch_resp.go │ ├── get_user_info_by_name_resp.go │ ├── message_resp.go │ ├── page_list_resp.go │ ├── pre_signed_resp.go │ ├── user_apply_resp.go │ ├── user_contact_resp.go │ └── user_login_resp.go ├── event/ │ └── listener/ │ ├── friend_apply_event.go │ ├── friend_delete_event.go │ ├── friend_new_event.go │ ├── new_msg_event.go │ └── user_login_event.go ├── global/ │ ├── init_db.go │ ├── init_distribute_lock.go │ ├── init_evenbus.go │ ├── init_log.go │ ├── init_minio.go │ ├── init_redis.go │ ├── init_rocketmq.go │ └── init_time.go ├── go.mod ├── go.sum ├── logic/ │ ├── captcha.go │ ├── login.go │ ├── register.go │ ├── user.go │ └── user_cancle.go ├── main.go ├── pkg/ │ ├── domain/ │ │ ├── enum/ │ │ │ ├── code.go │ │ │ ├── common_enum.go │ │ │ └── msg.go │ │ └── vo/ │ │ ├── req/ │ │ │ └── page_req.go │ │ └── resp/ │ │ ├── page_resp.go │ │ └── response.go │ └── utils/ │ ├── cursor_utils.go │ ├── redis.go │ ├── redis_lock.go │ └── sort.go ├── routes/ │ └── init_router.go ├── service/ │ ├── adapter/ │ │ ├── build_contact_dao_list.go │ │ ├── build_message_resp.go │ │ └── build_user_info_by_name_resp.go │ ├── captcha_service.go │ ├── contact_service.go │ ├── friend_service.go │ ├── group_service.go │ ├── message_service.go │ ├── upload_service.go │ ├── user_service.go │ ├── user_service_integration_test.go │ └── user_service_test.go ├── sql/ │ └── sql.sql ├── tests/ │ ├── README.md │ ├── e2e/ │ │ └── user_workflow_e2e_test.go │ ├── init_test.go │ ├── integration/ │ │ └── user_integration_test.go │ ├── performance/ │ │ └── user_performance_test.go │ ├── scripts/ │ │ ├── run_e2e_test.sh │ │ ├── run_tests.bat │ │ ├── run_tests.sh │ │ └── setup_test_env.sh │ └── unit/ │ └── user_service_test.go ├── utils/ │ ├── jsonUtils/ │ │ └── json.go │ ├── jwt/ │ │ ├── jwt.go │ │ └── jwt_test.go │ ├── middleware/ │ │ ├── cors.go │ │ ├── jwt.go │ │ └── log.go │ ├── mysqlUtils.go │ ├── passwordUtils.go │ ├── redisCache/ │ │ └── remove_cache.go │ ├── redisUtils.go │ ├── setting/ │ │ └── setting.go │ └── time_utils.go └── websocket/ ├── domain/ │ ├── enum/ │ │ └── ws_type.go │ └── vo/ │ └── resp/ │ └── new_message_resp.go ├── global/ │ └── global.go └── service/ └── websocket_service.go ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/reademe-contributors.yml ================================================ on: push: branches: - main name: Generate a list of contributors jobs: contrib-readme-en-job: runs-on: ubuntu-latest name: A job to automate contrib in readme steps: - name: Contribute List uses: akhilmhdh/contributors-readme-action@v2.3.4 env: GITHUB_TOKEN: ${{ secrets.CONTRIBUTORS_TOKEN }} ================================================ FILE: .gitignore ================================================ # If you prefer the allow list template instead of the deny list, see community template: # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore # # Binaries for programs and plugins /.idea/ *.exe *.exe~ *.dll *.so *.dylib # Test binary, built with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out # Dependency directories (remove the comment below to include it) # vendor/ # Go workspace file go.work *.yml *.yaml *.log /conf/config.yml !/conf/config_example.yml !/.github/workflows/reademe-contributors.yml ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================
![Static Badge](https://img.shields.io/badge/%E4%BA%A4%E6%B5%81%E7%BE%A4-%E5%8A%A0%E5%85%A5%E4%BA%A4%E6%B5%81-blue) ![Static Badge](https://img.shields.io/badge/github-%E9%A1%B9%E7%9B%AE%E5%9C%B0%E5%9D%80-blue) ![Static Badge](https://img.shields.io/badge/%E7%A0%81%E4%BA%91-%E9%A1%B9%E7%9B%AE%E5%9C%B0%E5%9D%80-blue) ![Static Badge](https://img.shields.io/badge/Issues-0_open-blue) ![Static Badge](https://img.shields.io/badge/License-Apache_License_2.0-blue) # DiTing:初学者的第一个 IM 项目 欢迎来到 DiTing!这是一个简单、轻量级的即时通讯(IM)开源项目,采用 Go 编写,严格遵守互联网开发标准。致力于为初学者提供一个友好、易于上手的 IM 解决方案,让你可以轻松入门并开始构建自己的即时通讯应用。 ## 项目导航 - 学习文档:[DiTing文档](https://danmuking.github.io/) - 项目交流群:🎉 欢迎加入 DiTing 交流群!这是一个与其他开发者交流、分享经验和获取项目相关支持的地方。在这里你可以不仅可以提出遇到的任何问题,同时欢迎你与其他开发者交流并且对DiTing提出任何建议! - 码云仓库:[Gitee](https://gitee.com/danmuking/DiTing-Go) - Github仓库:[Github](https://github.com/danmuking/DiTing-Go) - 前端地址:[Github](https://github.com/danmuking/DiTingWeb) ### 界面展示 ![app_1.png](./assets/app_1.png) ![app_1.png](./assets/app_2.png) ![app_1.png](./assets/app_3.png) ![app_1.png](./assets/app_4.png) ### 技术选型 | 技术 | 说明 | 官网 | |-------------|------------------| ----------- | | Gin | web开发必备框架 |https://gin-gonic.com/| | GORM | ORM框架 |https://gorm.io/docs/index.html| | GEN | ORM自动生成工作 |https://gorm.io/docs/index.html| | Redis | 缓存加速,多数据结构支持业务功能 |https://redis.io| | Jwt | 用户登录,认证方案 |https://jwt.io| | Swagger-UI | API文档生成工具 |https://github.com/swagger-api/swagger-ui| | Redsync | GO的分布式锁工具 |https://github.com/go-redsync/redsync| | RocketMQ | 低延迟、高并发、高可用、高可靠的分布式消息中间件 |https://rocketmq.apache.org/| ### Star 趋势 Star History Chart ### 贡献者
danmuking
LinYi
Soce1lo
Soce1lo
wangQuanYaa
WangQuanYaa
### 共建邀请 DiTing 项目不仅是一个简单的即时通讯解决方案,更是一个汇聚了热爱技术、追求卓越的开发者们的大家庭。我们拥有完整的社群体系,以及积极、友好的交流氛围,让每一位参与者都能在这里找到归属感,收获成长与快乐。 如果你对 DiTing 项目感兴趣,愿意贡献你的智慧和力量,我们非常欢迎你的加入!无论你是前端、后端、测试还是其他领域的开发者,都能在 DiTing 项目中找到你的舞台。你可以参与代码编写、功能优化、文档完善等各个方面的工作,与我们一起共同推动项目的进步。 同时,我们也非常欢迎非技术领域的朋友们加入我们的社群,分享你的想法和建议,帮助我们更好地完善项目和服务。 为了更好地联系和交流,你可以尝试添加我的微信,共同为 DiTing 项目的发展贡献力量。 ### 作者 DanMu 如果你需要帮助,可以尝试添加我的微信,我会尽力帮助你。 微信图片_20240328224833.jpg ### 捐赠 如果你觉得这个项目对你有帮助,你可以请作者喝一杯咖啡。 微信图片_20240328224833.jpg 微信图片_20240328224851.jpg ### 版权说明 该项目签署了MIT 授权许可,详情请参阅 [LICENSE.txt](./LICENSE) ### 鸣谢 [//]: # (- [GitHub Emoji Cheat Sheet](https://www.webpagefx.com/tools/emoji-cheat-sheet)) [//]: # (- [Img Shields](https://shields.io)) [//]: # (- [Choose an Open Source License](https://choosealicense.com)) [//]: # (- [GitHub Pages](https://pages.github.com)) [//]: # (- [Animate.css](https://daneden.github.io/animate.css)) [//]: # (- [xxxxxxxxxxxxxx](https://connoratherton.com/loaders)) ================================================ FILE: cmd/gen/generate.go ================================================ package main // gorm gen configure import ( _ "DiTing-Go/pkg/setting" "fmt" "github.com/spf13/viper" "gorm.io/driver/mysql" "gorm.io/gen" "gorm.io/gorm" ) var MySQLDSN = fmt.Sprintf("%s:%s@tcp(%s:%s)/DiTing?charset=utf8mb4&parseTime=True", viper.GetString("mysql.username"), viper.GetString("mysql.password"), viper.GetString("mysql.host"), viper.GetString("mysql.port")) func connectDB(dsn string) *gorm.DB { db, err := gorm.Open(mysql.Open(dsn)) if err != nil { panic(fmt.Errorf("connect db fail: %w", err)) } return db } func main() { println(MySQLDSN) // 指定生成代码的具体相对目录(相对当前文件),默认为:./query // 默认生成需要使用WithContext之后才可以查询的代码,但可以通过设置gen.WithoutContext禁用该模式 g := gen.NewGenerator(gen.Config{ // 默认会在 OutPath 目录生成CRUD代码,并且同目录下生成 model 包 // 所以OutPath最终package不能设置为model,在有数据库表同步的情况下会产生冲突 // 若一定要使用可以通过ModelPkgPath单独指定model package的名称 OutPath: "./dal/query", /* ModelPkgPath: "dal/model"*/ // gen.WithoutContext:禁用WithContext模式 // gen.WithDefaultQuery:生成一个全局Query对象Q // gen.WithQueryInterface:生成Query接口 Mode: gen.WithDefaultQuery | gen.WithQueryInterface, }) // 通常复用项目中已有的SQL连接配置db(*gorm.DB) // 非必需,但如果需要复用连接时的gorm.Config或需要连接数据库同步表信息则必须设置 g.UseDB(connectDB(MySQLDSN)) // 从连接的数据库为所有表生成Model结构体和CRUD代码 // 也可以手动指定需要生成代码的数据表 g.ApplyBasic(g.GenerateAllTable()...) // 执行并生成代码 g.Execute() } ================================================ FILE: conf/config_example.yml ================================================ mysql: host: xxx.xxx.xxx.xx port: 3306 username: diting password: diting jwt: secret: diting log: log_file_path: ./logs log_file_name: diting.log minio: accessKey: xxx accessSecret: xxx endPoint: xxx.xxx.xxx.xxx:9000 useSSL: false redis: host: xxxx password: xxxxx rocketmq: host: xxx.xxx.xxx.xxx:9000 group: diting ================================================ FILE: controller/captcha_controller.go ================================================ package controller import ( "DiTing-Go/domain/vo/req" "DiTing-Go/pkg/domain/vo/resp" "DiTing-Go/service" "github.com/gin-gonic/gin" ) func CaptchaController(c *gin.Context) { captchaReq := req.CaptchaReq{} if err := c.ShouldBind(&captchaReq); err != nil { resp.ErrorResponse(c, "参数错误") c.Abort() return } response, err := service.CaptchaService(captchaReq) if err != nil { c.Abort() resp.ReturnErrorResponse(c, response) return } resp.ReturnSuccessResponse(c, response) } ================================================ FILE: controller/chat_controller.go ================================================ package controller // //import ( // "DiTing-Go/domain/vo/req" // "DiTing-Go/pkg/domain/vo/resp" // "DiTing-Go/service" // "github.com/gin-gonic/gin" //) // //func SendMessageController(c *gin.Context) { // uid := c.GetInt64("uid") // messageReq := req.MessageReq{} // if err := c.ShouldBind(&messageReq); err != nil { //ShouldBind()会自动推导 // resp.ErrorResponse(c, "参数错误") // c.Abort() // return // } // response, err := service.SendTextMsgService(uid, messageReq) // if err != nil { // c.Abort() // resp.ReturnErrorResponse(c, response) // return // } // resp.ReturnSuccessResponse(c, response) //} ================================================ FILE: controller/contact_controller.go ================================================ package controller // //import ( // "DiTing-Go/domain/vo/req" // "DiTing-Go/global" // pkgReq "DiTing-Go/pkg/domain/vo/req" // "DiTing-Go/pkg/domain/vo/resp" // "DiTing-Go/service" // "github.com/gin-gonic/gin" //) // //func GetUserInfoBatchController(c *gin.Context) { // getUserInfoBatchReq := req.GetUserInfoBatchReq{} // if err := c.ShouldBind(&getUserInfoBatchReq); err != nil { // resp.ErrorResponse(c, "参数错误") // c.Abort() // return // } // response, err := service.GetUserInfoBatchService(getUserInfoBatchReq) // if err != nil { // c.Abort() // resp.ReturnErrorResponse(c, response) // return // } // resp.ReturnSuccessResponse(c, response) // return //} // //func GetContactListController(c *gin.Context) { // uid := c.GetInt64("uid") // // 游标翻页 // // 默认值 // var cursor *string = nil // var pageSize int = 20 // pageRequest := pkgReq.PageReq{ // Cursor: cursor, // PageSize: pageSize, // } // if err := c.ShouldBindQuery(&pageRequest); err != nil { //ShouldBind()会自动推导 // resp.ErrorResponse(c, "参数错误") // c.Abort() // return // } // response, err := service.GetContactListService(uid, pageRequest) // if err != nil { // c.Abort() // resp.ReturnErrorResponse(c, response) // return // } // resp.ReturnSuccessResponse(c, response) //} // //func GetNewContactListController(c *gin.Context) { // uid := c.GetInt64("uid") // // getNewContentListReq := req.GetNewContentListReq{} // if err := c.ShouldBindQuery(&getNewContentListReq); err != nil { //ShouldBind()会自动推导 // resp.ErrorResponse(c, "参数错误") // c.Abort() // return // } // response, err := service.GetNewContactListService(uid, getNewContentListReq.Timestamp) // if err != nil { // c.Abort() // resp.ReturnErrorResponse(c, response) // return // } // resp.ReturnSuccessResponse(c, response) //} // //func GetNewMsgListController(c *gin.Context) { // // getNewMsgListReq := req.GetNewMsgListReq{} // if err := c.ShouldBindQuery(&getNewMsgListReq); err != nil { //ShouldBind()会自动推导 // resp.ErrorResponse(c, "参数错误") // c.Abort() // return // } // response, err := service.GetNewMsgService(getNewMsgListReq.MsgId, getNewMsgListReq.RoomId) // if err != nil { // c.Abort() // resp.ReturnErrorResponse(c, response) // return // } // resp.ReturnSuccessResponse(c, response) //} // //func CreateGroupController(c *gin.Context) { // uid := c.GetInt64("uid") // creatGroupReq := req.CreateGroupReq{} // if err := c.ShouldBind(&creatGroupReq); err != nil { //ShouldBind()会自动推导 // resp.ErrorResponse(c, "参数错误") // global.Logger.Errorf("参数错误: %v", err) // c.Abort() // return // } // response, err := service.CreateGroupService(uid, creatGroupReq.UidList) // if err != nil { // c.Abort() // resp.ReturnErrorResponse(c, response) // return // } // resp.ReturnSuccessResponse(c, response) //} ================================================ FILE: controller/friend_controller.go ================================================ package controller // //import ( // "DiTing-Go/domain/vo/req" // pkgReq "DiTing-Go/pkg/domain/vo/req" // "DiTing-Go/pkg/domain/vo/resp" // "DiTing-Go/service" // "github.com/gin-gonic/gin" //) // //// ApplyFriendController 添加好友 //// //// @Summary 添加好友 //// @Produce json //// @Param uid body int true "好友uid" //// @Param msg body string true "验证消息" //// @Success 200 {object} resp.ResponseData "成功" //// @Failure 500 {object} resp.ResponseData "内部错误" //// @Router /api/user/add [post] //func ApplyFriendController(c *gin.Context) { // uid := c.GetInt64("uid") // applyReq := req.UserApplyReq{} // if err := c.ShouldBind(&applyReq); err != nil { // resp.ErrorResponse(c, "参数错误") // c.Abort() // return // } // response, err := service.ApplyFriendService(uid, applyReq) // if err != nil { // c.Abort() // resp.ReturnErrorResponse(c, response) // return // } // resp.ReturnSuccessResponse(c, response) // return //} // //// DeleteFriendController 删除好友 //// //// @Summary 删除好友 //// @Produce json //// @Param uid body int true "好友uid" //// @Success 200 {object} resp.ResponseData "成功" //// @Failure 500 {object} resp.ResponseData "内部错误" //// @Router /api/user/delete/:uid [delete] //func DeleteFriendController(c *gin.Context) { // uid := c.GetInt64("uid") // deleteFriendReq := req.DeleteFriendReq{} // if err := c.ShouldBind(&deleteFriendReq); err != nil { // resp.ErrorResponse(c, "参数错误") // return // } // response, err := service.DeleteFriendService(uid, deleteFriendReq) // if err != nil { // c.Abort() // resp.ReturnErrorResponse(c, response) // return // } // resp.ReturnSuccessResponse(c, response) //} // //// AgreeFriendController 同意好友申请 //// //// @Summary 同意好友申请 //// @Produce json //// @Param uid body int true "好友uid" //// @Success 200 {object} resp.ResponseData "成功" //// @Failure 500 {object} resp.ResponseData "内部错误" //// @Router /api/user/agree [put] //func AgreeFriendController(c *gin.Context) { // uid := c.GetInt64("uid") // agreeFriendReq := req.AgreeFriendReq{} // if err := c.ShouldBind(&agreeFriendReq); err != nil { //ShouldBind()会自动推导 // resp.ErrorResponse(c, "参数错误") // c.Abort() // return // } // response, err := service.AgreeFriendService(uid, agreeFriendReq.Uid) // if err != nil { // c.Abort() // resp.ReturnErrorResponse(c, response) // return // } // resp.ReturnSuccessResponse(c, response) //} // //// GetUserApplyController 同意好友申请 //// //// @Summary 同意好友申请 //// @Produce json //// @Param uid body int true "好友uid" //// @Success 200 {object} resp.ResponseData "成功" //// @Failure 500 {object} resp.ResponseData "内部错误" //// @Router /api/user/getApplyList [get] //func GetUserApplyController(c *gin.Context) { // uid := c.GetInt64("uid") // pageRequest := pkgReq.PageReq{} // if err := c.ShouldBindQuery(&pageRequest); err != nil { //ShouldBind()会自动推导 // resp.ErrorResponse(c, "参数错误") // return // } // response, err := service.GetUserApplyService(uid, pageRequest) // if err != nil { // c.Abort() // resp.ReturnErrorResponse(c, response) // return // } // // resp.ReturnSuccessResponse(c, response) //} // //// IsFriendController 是否为好友关系 //// //// @Summary 是否为好友关系 //// @Produce json //// @Param uid body int true "好友uid" //// @Success 200 {object} resp.ResponseData "成功" //// @Failure 500 {object} resp.ResponseData "内部错误" //// @Router /api/user/isFriend/:friendUid [get] //func IsFriendController(c *gin.Context) { // uid := c.GetInt64("uid") // isFriendReq := req.IsFriendReq{} // if err := c.ShouldBindUri(&isFriendReq); err != nil { // resp.ErrorResponse(c, "参数错误") // return // } // response, err := service.IsFriendService(uid, isFriendReq.FriendUid) // if err != nil { // c.Abort() // resp.ReturnErrorResponse(c, response) // return // } // resp.ReturnSuccessResponse(c, response) //} // //// UnreadApplyNumController 好友申请未读数量 //// //// @Summary 好友申请未读数量 //// @Success 200 {object} resp.ResponseData "成功" //// @Failure 500 {object} resp.ResponseData "内部错误" //// @Router /api/user/unreadApplyNum [get] //func UnreadApplyNumController(c *gin.Context) { // uid := c.GetInt64("uid") // // response, err := service.UnreadApplyNumService(uid) // if err != nil { // c.Abort() // resp.ReturnErrorResponse(c, response) // return // } // resp.ReturnSuccessResponse(c, response) //} // //func GetFriendListController(c *gin.Context) { // uid := c.GetInt64("uid") // pageRequest := pkgReq.PageReq{} // if err := c.ShouldBindQuery(&pageRequest); err != nil { //ShouldBind()会自动推导 // resp.ErrorResponse(c, "参数错误") // return // } // response, err := service.GetFriendListService(uid, pageRequest) // if err != nil { // c.Abort() // resp.ReturnErrorResponse(c, response) // return // } // resp.ReturnSuccessResponse(c, response) //} // //// GetUserInfoByNameController 根据好友昵称搜索好友 //func GetUserInfoByNameController(c *gin.Context) { // uid := c.GetInt64("uid") // getUserInfoByNameReq := req.GetUserInfoByNameReq{} // if err := c.ShouldBindQuery(&getUserInfoByNameReq); err != nil { //ShouldBind()会自动推导 // resp.ErrorResponse(c, "参数错误") // return // } // response, err := service.GetUserInfoByNameService(uid, getUserInfoByNameReq.Name) // if err != nil { // c.Abort() // resp.ReturnErrorResponse(c, response) // return // } // resp.ReturnSuccessResponse(c, response) //} ================================================ FILE: controller/user_controller.go ================================================ package controller import ( "DiTing-Go/domain/vo/req" "DiTing-Go/pkg/domain/vo/resp" "DiTing-Go/service" "github.com/gin-gonic/gin" ) // RegisterController 用户注册 // // @Summary 用户注册 // @Produce json // @Param name body string true "用户名" // @Param password body string true "密码" // @Param phone body string true "手机号" // @Param captcha body string true "验证码" // @Success 200 {object} resp.ResponseData "成功" // @Failure 500 {object} resp.ResponseData "内部错误" // @Router /api/public/register [post] func RegisterController(c *gin.Context) { userReq := req.UserRegisterReq{} if err := c.ShouldBind(&userReq); err != nil { resp.ErrorResponse(c, "参数错误") c.Abort() return } response, err := service.RegisterService(userReq) if err != nil { c.Abort() resp.ReturnErrorResponse(c, response) return } resp.ReturnSuccessResponse(c, response) } // LoginController 用户登录 // // @Summary 用户登录 // @Produce json // @Param name body string true "用户名" // @Param password body string true "密码" // @Success 200 {object} resp.ResponseData "成功" // @Failure 500 {object} resp.ResponseData "内部错误" // @Router /api/public/login [post] func LoginController(c *gin.Context) { userLogin := req.UserLoginReq{} if err := c.ShouldBind(&userLogin); err != nil { //ShouldBind()会自动推导 resp.ErrorResponse(c, "参数错误") c.Abort() return } response, err := service.LoginService(userLogin) if err != nil { c.Abort() resp.ReturnErrorResponse(c, response) return } resp.ReturnSuccessResponse(c, response) } // CancelController 注销账户 // // @Summary 注销账户 // @Produce json // @Param phone body string true "手机号" // @Success 200 {object} resp.ResponseData "成功" // @Failure 500 {object} resp.ResponseData "内部错误" // @Router /api/public/login [post] func CancelController(ctx *gin.Context) { userCancel := req.UserCancelReq{} if err := ctx.ShouldBind(&userCancel); err != nil { resp.ErrorResponse(ctx, "参数错误") ctx.Abort() return } response, err := service.CancelService(ctx, userCancel) if err != nil { ctx.Abort() resp.ReturnErrorResponse(ctx, response) return } resp.ReturnSuccessResponse(ctx, response) } ================================================ FILE: dal/db.go ================================================ package dal import ( "fmt" "gorm.io/driver/mysql" "gorm.io/gorm" ) var DB *gorm.DB // ConnectDB 初始化数据库连接 func ConnectDB(dsn string) *gorm.DB { db, err := gorm.Open(mysql.Open(dsn)) sqlDB, err := db.DB() // SetMaxIdleConns sets the maximum number of connections in the idle connection pool. sqlDB.SetMaxIdleConns(10) sqlDB.SetMaxOpenConns(100) if err != nil { panic(fmt.Errorf("connect db fail: %w", err)) } return db } ================================================ FILE: dal/model/contact.gen.go ================================================ // Code generated by gorm.io/gen. DO NOT EDIT. // Code generated by gorm.io/gen. DO NOT EDIT. // Code generated by gorm.io/gen. DO NOT EDIT. package model import ( "time" ) const TableNameContact = "contact" // Contact 会话列表 type Contact struct { ID int64 `gorm:"column:id;primaryKey;autoIncrement:true;comment:id" json:"id"` // id UID int64 `gorm:"column:uid;not null;comment:uid" json:"uid"` // uid RoomID int64 `gorm:"column:room_id;not null;comment:房间id" json:"room_id"` // 房间id ReadTime time.Time `gorm:"column:read_time;not null;default:CURRENT_TIMESTAMP(3);comment:阅读到的时间" json:"read_time"` // 阅读到的时间 ActiveTime time.Time `gorm:"column:active_time;comment:会话内消息最后更新的时间(只有普通会话需要维护,全员会话不需要维护)" json:"active_time"` // 会话内消息最后更新的时间(只有普通会话需要维护,全员会话不需要维护) LastMsgID int64 `gorm:"column:last_msg_id;comment:会话最新消息id" json:"last_msg_id"` // 会话最新消息id CreateTime time.Time `gorm:"column:create_time;not null;default:CURRENT_TIMESTAMP(3);comment:创建时间" json:"create_time"` // 创建时间 UpdateTime time.Time `gorm:"column:update_time;not null;default:CURRENT_TIMESTAMP(3);comment:修改时间" json:"update_time"` // 修改时间 } // TableName Contact's table name func (*Contact) TableName() string { return TableNameContact } ================================================ FILE: dal/model/group_member.gen.go ================================================ // Code generated by gorm.io/gen. DO NOT EDIT. // Code generated by gorm.io/gen. DO NOT EDIT. // Code generated by gorm.io/gen. DO NOT EDIT. package model import ( "time" ) const TableNameGroupMember = "group_member" // GroupMember 群成员表 type GroupMember struct { ID int64 `gorm:"column:id;primaryKey;autoIncrement:true;comment:id" json:"id"` // id GroupID int64 `gorm:"column:group_id;not null;comment:群主id" json:"group_id"` // 群主id UID int64 `gorm:"column:uid;not null;comment:成员uid" json:"uid"` // 成员uid Role int32 `gorm:"column:role;not null;comment:成员角色 1群主 2管理员 3普通成员" json:"role"` // 成员角色 1群主 2管理员 3普通成员 CreateTime time.Time `gorm:"column:create_time;not null;default:CURRENT_TIMESTAMP(3);comment:创建时间" json:"create_time"` // 创建时间 UpdateTime time.Time `gorm:"column:update_time;not null;default:CURRENT_TIMESTAMP(3);comment:修改时间" json:"update_time"` // 修改时间 } // TableName GroupMember's table name func (*GroupMember) TableName() string { return TableNameGroupMember } ================================================ FILE: dal/model/message.gen.go ================================================ // Code generated by gorm.io/gen. DO NOT EDIT. // Code generated by gorm.io/gen. DO NOT EDIT. // Code generated by gorm.io/gen. DO NOT EDIT. package model import ( "time" ) const TableNameMessage = "message" // Message 消息表 type Message struct { ID int64 `gorm:"column:id;primaryKey;autoIncrement:true;comment:id" json:"id"` // id RoomID int64 `gorm:"column:room_id;not null;comment:会话表id" json:"room_id"` // 会话表id FromUID int64 `gorm:"column:from_uid;not null;comment:消息发送者uid" json:"from_uid"` // 消息发送者uid Content string `gorm:"column:content;comment:消息内容" json:"content"` // 消息内容 ReplyMsgID int64 `gorm:"column:reply_msg_id;comment:回复的消息内容" json:"reply_msg_id"` // 回复的消息内容 DeleteStatus int32 `gorm:"column:delete_status;not null;comment:消息状态 0正常 1删除" json:"delete_status"` // 消息状态 0正常 1删除 GapCount int32 `gorm:"column:gap_count;comment:与回复的消息间隔多少条" json:"gap_count"` // 与回复的消息间隔多少条 Type int32 `gorm:"column:type;default:1;comment:消息类型 1正常文本 2.撤回消息" json:"type"` // 消息类型 1正常文本 2.撤回消息 Extra string `gorm:"column:extra;comment:扩展信息" json:"extra"` // 扩展信息 CreateTime time.Time `gorm:"column:create_time;not null;default:CURRENT_TIMESTAMP(3);comment:创建时间" json:"create_time"` // 创建时间 UpdateTime time.Time `gorm:"column:update_time;not null;default:CURRENT_TIMESTAMP(3);comment:修改时间" json:"update_time"` // 修改时间 } // TableName Message's table name func (*Message) TableName() string { return TableNameMessage } ================================================ FILE: dal/model/room.gen.go ================================================ // Code generated by gorm.io/gen. DO NOT EDIT. // Code generated by gorm.io/gen. DO NOT EDIT. // Code generated by gorm.io/gen. DO NOT EDIT. package model import ( "time" ) const TableNameRoom = "room" // Room 房间表 type Room struct { ID int64 `gorm:"column:id;primaryKey;autoIncrement:true;comment:id" json:"id"` // id Type int32 `gorm:"column:type;not null;comment:房间类型 1群聊 2单聊" json:"type"` // 房间类型 1群聊 2单聊 HotFlag int32 `gorm:"column:hot_flag;comment:是否全员展示 0否 1是" json:"hot_flag"` // 是否全员展示 0否 1是 ActiveTime time.Time `gorm:"column:active_time;not null;default:CURRENT_TIMESTAMP(3);comment:群最后消息的更新时间(热点群不需要写扩散,只更新这里)" json:"active_time"` // 群最后消息的更新时间(热点群不需要写扩散,只更新这里) LastMsgID int64 `gorm:"column:last_msg_id;comment:会话中的最后一条消息id" json:"last_msg_id"` // 会话中的最后一条消息id ExtJSON string `gorm:"column:ext_json;comment:额外信息(根据不同类型房间有不同存储的东西)" json:"ext_json"` // 额外信息(根据不同类型房间有不同存储的东西) CreateTime time.Time `gorm:"column:create_time;not null;default:CURRENT_TIMESTAMP(3);comment:创建时间" json:"create_time"` // 创建时间 UpdateTime time.Time `gorm:"column:update_time;not null;default:CURRENT_TIMESTAMP(3);comment:修改时间" json:"update_time"` // 修改时间 DeleteStatus int32 `gorm:"column:delete_status;not null;comment:房间状态 0正常 1禁用(删好友了禁用)" json:"delete_status"` // 房间状态 0正常 1禁用(删好友了禁用) } // TableName Room's table name func (*Room) TableName() string { return TableNameRoom } ================================================ FILE: dal/model/room_friend.gen.go ================================================ // Code generated by gorm.io/gen. DO NOT EDIT. // Code generated by gorm.io/gen. DO NOT EDIT. // Code generated by gorm.io/gen. DO NOT EDIT. package model import ( "time" ) const TableNameRoomFriend = "room_friend" // RoomFriend 单聊房间表 type RoomFriend struct { ID int64 `gorm:"column:id;primaryKey;autoIncrement:true;comment:id" json:"id"` // id RoomID int64 `gorm:"column:room_id;not null;comment:房间id" json:"room_id"` // 房间id Uid1 int64 `gorm:"column:uid1;not null;comment:uid1(更小的uid)" json:"uid1"` // uid1(更小的uid) Uid2 int64 `gorm:"column:uid2;not null;comment:uid2(更大的uid)" json:"uid2"` // uid2(更大的uid) RoomKey string `gorm:"column:room_key;not null;comment:房间key由两个uid拼接,先做排序uid1_uid2" json:"room_key"` // 房间key由两个uid拼接,先做排序uid1_uid2 DeleteStatus int32 `gorm:"column:delete_status;not null;comment:房间状态 0正常 1禁用(删好友了禁用)" json:"delete_status"` // 房间状态 0正常 1禁用(删好友了禁用) CreateTime time.Time `gorm:"column:create_time;not null;default:CURRENT_TIMESTAMP(3);comment:创建时间" json:"create_time"` // 创建时间 UpdateTime time.Time `gorm:"column:update_time;not null;default:CURRENT_TIMESTAMP(3);comment:修改时间" json:"update_time"` // 修改时间 } // TableName RoomFriend's table name func (*RoomFriend) TableName() string { return TableNameRoomFriend } ================================================ FILE: dal/model/room_group.gen.go ================================================ // Code generated by gorm.io/gen. DO NOT EDIT. // Code generated by gorm.io/gen. DO NOT EDIT. // Code generated by gorm.io/gen. DO NOT EDIT. package model import ( "time" ) const TableNameRoomGroup = "room_group" // RoomGroup 群聊房间表 type RoomGroup struct { ID int64 `gorm:"column:id;primaryKey;autoIncrement:true;comment:id" json:"id"` // id RoomID int64 `gorm:"column:room_id;not null;comment:房间id" json:"room_id"` // 房间id Name string `gorm:"column:name;not null;comment:群名称" json:"name"` // 群名称 Avatar string `gorm:"column:avatar;not null;comment:群头像" json:"avatar"` // 群头像 ExtJSON string `gorm:"column:ext_json;comment:额外信息(根据不同类型房间有不同存储的东西)" json:"ext_json"` // 额外信息(根据不同类型房间有不同存储的东西) DeleteStatus int32 `gorm:"column:delete_status;not null;comment:逻辑删除(0-正常,1-删除)" json:"delete_status"` // 逻辑删除(0-正常,1-删除) CreateTime time.Time `gorm:"column:create_time;not null;default:CURRENT_TIMESTAMP(3);comment:创建时间" json:"create_time"` // 创建时间 UpdateTime time.Time `gorm:"column:update_time;not null;default:CURRENT_TIMESTAMP(3);comment:修改时间" json:"update_time"` // 修改时间 } // TableName RoomGroup's table name func (*RoomGroup) TableName() string { return TableNameRoomGroup } ================================================ FILE: dal/model/user.gen.go ================================================ // Code generated by gorm.io/gen. DO NOT EDIT. // Code generated by gorm.io/gen. DO NOT EDIT. // Code generated by gorm.io/gen. DO NOT EDIT. package model import ( "time" ) const TableNameUser = "user" // User 用户表 type User struct { ID int64 `gorm:"column:id;primaryKey;autoIncrement:true;comment:用户id" json:"id"` // 用户id Phone string `gorm:"column:phone;not null;comment:用户手机" json:"phone"` // 用户手机 Password string `gorm:"column:password;not null;comment:用户密码" json:"password"` // 用户密码 Name string `gorm:"column:name;not null;comment:用户昵称" json:"name"` // 用户昵称 Avatar string `gorm:"column:avatar;not null;comment:用户头像" json:"avatar"` // 用户头像 Sex int32 `gorm:"column:sex;default:3;comment:性别 1为男性,2为女性,3未知" json:"sex"` // 性别 1为男性,2为女性,3未知 ActiveStatus int32 `gorm:"column:active_status;default:2;comment:在线状态 1在线 2离线" json:"active_status"` // 在线状态 1在线 2离线 LastOptTime time.Time `gorm:"column:last_opt_time;not null;default:CURRENT_TIMESTAMP(3);comment:最后上下线时间" json:"last_opt_time"` // 最后上下线时间 IPInfo string `gorm:"column:ip_info;comment:ip信息,用于显示用户地理位置" json:"ip_info"` // ip信息,用于显示用户地理位置 ItemID int64 `gorm:"column:item_id;comment:佩戴的徽章id" json:"item_id"` // 佩戴的徽章id Status int32 `gorm:"column:status;default:1;comment:使用状态 1.正常 2禁用" json:"status"` // 使用状态 1.正常 2禁用 CreateTime time.Time `gorm:"column:create_time;not null;default:CURRENT_TIMESTAMP(3);comment:创建时间" json:"create_time"` // 创建时间 UpdateTime time.Time `gorm:"column:update_time;not null;default:CURRENT_TIMESTAMP(3);comment:修改时间" json:"update_time"` // 修改时间 } // TableName User's table name func (*User) TableName() string { return TableNameUser } ================================================ FILE: dal/model/user_apply.gen.go ================================================ // Code generated by gorm.io/gen. DO NOT EDIT. // Code generated by gorm.io/gen. DO NOT EDIT. // Code generated by gorm.io/gen. DO NOT EDIT. package model import ( "time" ) const TableNameUserApply = "user_apply" // UserApply 用户申请表 type UserApply struct { ID int64 `gorm:"column:id;primaryKey;autoIncrement:true;comment:id" json:"id"` // id UID int64 `gorm:"column:uid;not null;comment:申请人uid" json:"uid"` // 申请人uid Type int32 `gorm:"column:type;not null;comment:申请类型 1加好友" json:"type"` // 申请类型 1加好友 TargetID int64 `gorm:"column:target_id;not null;comment:接收人uid" json:"target_id"` // 接收人uid Msg string `gorm:"column:msg;not null;comment:申请信息" json:"msg"` // 申请信息 Status int32 `gorm:"column:status;not null;comment:申请状态 1待审批 2同意" json:"status"` // 申请状态 1待审批 2同意 ReadStatus int32 `gorm:"column:read_status;not null;comment:阅读状态 1未读 2已读" json:"read_status"` // 阅读状态 1未读 2已读 CreateTime time.Time `gorm:"column:create_time;not null;default:CURRENT_TIMESTAMP(3);comment:创建时间" json:"create_time"` // 创建时间 UpdateTime time.Time `gorm:"column:update_time;not null;default:CURRENT_TIMESTAMP(3);comment:修改时间" json:"update_time"` // 修改时间 } // TableName UserApply's table name func (*UserApply) TableName() string { return TableNameUserApply } ================================================ FILE: dal/model/user_friend.gen.go ================================================ // Code generated by gorm.io/gen. DO NOT EDIT. // Code generated by gorm.io/gen. DO NOT EDIT. // Code generated by gorm.io/gen. DO NOT EDIT. package model import ( "time" ) const TableNameUserFriend = "user_friend" // UserFriend 用户联系人表 type UserFriend struct { ID int64 `gorm:"column:id;primaryKey;autoIncrement:true;comment:id" json:"id"` // id UID int64 `gorm:"column:uid;not null;comment:uid" json:"uid"` // uid FriendUID int64 `gorm:"column:friend_uid;not null;comment:好友uid" json:"friend_uid"` // 好友uid DeleteStatus int32 `gorm:"column:delete_status;not null;comment:逻辑删除(0-正常,1-删除)" json:"delete_status"` // 逻辑删除(0-正常,1-删除) CreateTime time.Time `gorm:"column:create_time;not null;default:CURRENT_TIMESTAMP(3);comment:创建时间" json:"create_time"` // 创建时间 UpdateTime time.Time `gorm:"column:update_time;not null;default:CURRENT_TIMESTAMP(3);comment:修改时间" json:"update_time"` // 修改时间 } // TableName UserFriend's table name func (*UserFriend) TableName() string { return TableNameUserFriend } ================================================ FILE: dal/query/contact.gen.go ================================================ // Code generated by gorm.io/gen. DO NOT EDIT. // Code generated by gorm.io/gen. DO NOT EDIT. // Code generated by gorm.io/gen. DO NOT EDIT. package query import ( "context" "gorm.io/gorm" "gorm.io/gorm/clause" "gorm.io/gorm/schema" "gorm.io/gen" "gorm.io/gen/field" "gorm.io/plugin/dbresolver" "DiTing-Go/dal/model" ) func newContact(db *gorm.DB, opts ...gen.DOOption) contact { _contact := contact{} _contact.contactDo.UseDB(db, opts...) _contact.contactDo.UseModel(&model.Contact{}) tableName := _contact.contactDo.TableName() _contact.ALL = field.NewAsterisk(tableName) _contact.ID = field.NewInt64(tableName, "id") _contact.UID = field.NewInt64(tableName, "uid") _contact.RoomID = field.NewInt64(tableName, "room_id") _contact.ReadTime = field.NewTime(tableName, "read_time") _contact.ActiveTime = field.NewTime(tableName, "active_time") _contact.LastMsgID = field.NewInt64(tableName, "last_msg_id") _contact.CreateTime = field.NewTime(tableName, "create_time") _contact.UpdateTime = field.NewTime(tableName, "update_time") _contact.fillFieldMap() return _contact } // contact 会话列表 type contact struct { contactDo contactDo ALL field.Asterisk ID field.Int64 // id UID field.Int64 // uid RoomID field.Int64 // 房间id ReadTime field.Time // 阅读到的时间 ActiveTime field.Time // 会话内消息最后更新的时间(只有普通会话需要维护,全员会话不需要维护) LastMsgID field.Int64 // 会话最新消息id CreateTime field.Time // 创建时间 UpdateTime field.Time // 修改时间 fieldMap map[string]field.Expr } func (c contact) Table(newTableName string) *contact { c.contactDo.UseTable(newTableName) return c.updateTableName(newTableName) } func (c contact) As(alias string) *contact { c.contactDo.DO = *(c.contactDo.As(alias).(*gen.DO)) return c.updateTableName(alias) } func (c *contact) updateTableName(table string) *contact { c.ALL = field.NewAsterisk(table) c.ID = field.NewInt64(table, "id") c.UID = field.NewInt64(table, "uid") c.RoomID = field.NewInt64(table, "room_id") c.ReadTime = field.NewTime(table, "read_time") c.ActiveTime = field.NewTime(table, "active_time") c.LastMsgID = field.NewInt64(table, "last_msg_id") c.CreateTime = field.NewTime(table, "create_time") c.UpdateTime = field.NewTime(table, "update_time") c.fillFieldMap() return c } func (c *contact) WithContext(ctx context.Context) IContactDo { return c.contactDo.WithContext(ctx) } func (c contact) TableName() string { return c.contactDo.TableName() } func (c contact) Alias() string { return c.contactDo.Alias() } func (c contact) Columns(cols ...field.Expr) gen.Columns { return c.contactDo.Columns(cols...) } func (c *contact) GetFieldByName(fieldName string) (field.OrderExpr, bool) { _f, ok := c.fieldMap[fieldName] if !ok || _f == nil { return nil, false } _oe, ok := _f.(field.OrderExpr) return _oe, ok } func (c *contact) fillFieldMap() { c.fieldMap = make(map[string]field.Expr, 8) c.fieldMap["id"] = c.ID c.fieldMap["uid"] = c.UID c.fieldMap["room_id"] = c.RoomID c.fieldMap["read_time"] = c.ReadTime c.fieldMap["active_time"] = c.ActiveTime c.fieldMap["last_msg_id"] = c.LastMsgID c.fieldMap["create_time"] = c.CreateTime c.fieldMap["update_time"] = c.UpdateTime } func (c contact) clone(db *gorm.DB) contact { c.contactDo.ReplaceConnPool(db.Statement.ConnPool) return c } func (c contact) replaceDB(db *gorm.DB) contact { c.contactDo.ReplaceDB(db) return c } type contactDo struct{ gen.DO } type IContactDo interface { gen.SubQuery Debug() IContactDo WithContext(ctx context.Context) IContactDo WithResult(fc func(tx gen.Dao)) gen.ResultInfo ReplaceDB(db *gorm.DB) ReadDB() IContactDo WriteDB() IContactDo As(alias string) gen.Dao Session(config *gorm.Session) IContactDo Columns(cols ...field.Expr) gen.Columns Clauses(conds ...clause.Expression) IContactDo Not(conds ...gen.Condition) IContactDo Or(conds ...gen.Condition) IContactDo Select(conds ...field.Expr) IContactDo Where(conds ...gen.Condition) IContactDo Order(conds ...field.Expr) IContactDo Distinct(cols ...field.Expr) IContactDo Omit(cols ...field.Expr) IContactDo Join(table schema.Tabler, on ...field.Expr) IContactDo LeftJoin(table schema.Tabler, on ...field.Expr) IContactDo RightJoin(table schema.Tabler, on ...field.Expr) IContactDo Group(cols ...field.Expr) IContactDo Having(conds ...gen.Condition) IContactDo Limit(limit int) IContactDo Offset(offset int) IContactDo Count() (count int64, err error) Scopes(funcs ...func(gen.Dao) gen.Dao) IContactDo Unscoped() IContactDo Create(values ...*model.Contact) error CreateInBatches(values []*model.Contact, batchSize int) error Save(values ...*model.Contact) error First() (*model.Contact, error) Take() (*model.Contact, error) Last() (*model.Contact, error) Find() ([]*model.Contact, error) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.Contact, err error) FindInBatches(result *[]*model.Contact, batchSize int, fc func(tx gen.Dao, batch int) error) error Pluck(column field.Expr, dest interface{}) error Delete(...*model.Contact) (info gen.ResultInfo, err error) Update(column field.Expr, value interface{}) (info gen.ResultInfo, err error) UpdateSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error) Updates(value interface{}) (info gen.ResultInfo, err error) UpdateColumn(column field.Expr, value interface{}) (info gen.ResultInfo, err error) UpdateColumnSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error) UpdateColumns(value interface{}) (info gen.ResultInfo, err error) UpdateFrom(q gen.SubQuery) gen.Dao Attrs(attrs ...field.AssignExpr) IContactDo Assign(attrs ...field.AssignExpr) IContactDo Joins(fields ...field.RelationField) IContactDo Preload(fields ...field.RelationField) IContactDo FirstOrInit() (*model.Contact, error) FirstOrCreate() (*model.Contact, error) FindByPage(offset int, limit int) (result []*model.Contact, count int64, err error) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) Scan(result interface{}) (err error) Returning(value interface{}, columns ...string) IContactDo UnderlyingDB() *gorm.DB schema.Tabler } func (c contactDo) Debug() IContactDo { return c.withDO(c.DO.Debug()) } func (c contactDo) WithContext(ctx context.Context) IContactDo { return c.withDO(c.DO.WithContext(ctx)) } func (c contactDo) ReadDB() IContactDo { return c.Clauses(dbresolver.Read) } func (c contactDo) WriteDB() IContactDo { return c.Clauses(dbresolver.Write) } func (c contactDo) Session(config *gorm.Session) IContactDo { return c.withDO(c.DO.Session(config)) } func (c contactDo) Clauses(conds ...clause.Expression) IContactDo { return c.withDO(c.DO.Clauses(conds...)) } func (c contactDo) Returning(value interface{}, columns ...string) IContactDo { return c.withDO(c.DO.Returning(value, columns...)) } func (c contactDo) Not(conds ...gen.Condition) IContactDo { return c.withDO(c.DO.Not(conds...)) } func (c contactDo) Or(conds ...gen.Condition) IContactDo { return c.withDO(c.DO.Or(conds...)) } func (c contactDo) Select(conds ...field.Expr) IContactDo { return c.withDO(c.DO.Select(conds...)) } func (c contactDo) Where(conds ...gen.Condition) IContactDo { return c.withDO(c.DO.Where(conds...)) } func (c contactDo) Order(conds ...field.Expr) IContactDo { return c.withDO(c.DO.Order(conds...)) } func (c contactDo) Distinct(cols ...field.Expr) IContactDo { return c.withDO(c.DO.Distinct(cols...)) } func (c contactDo) Omit(cols ...field.Expr) IContactDo { return c.withDO(c.DO.Omit(cols...)) } func (c contactDo) Join(table schema.Tabler, on ...field.Expr) IContactDo { return c.withDO(c.DO.Join(table, on...)) } func (c contactDo) LeftJoin(table schema.Tabler, on ...field.Expr) IContactDo { return c.withDO(c.DO.LeftJoin(table, on...)) } func (c contactDo) RightJoin(table schema.Tabler, on ...field.Expr) IContactDo { return c.withDO(c.DO.RightJoin(table, on...)) } func (c contactDo) Group(cols ...field.Expr) IContactDo { return c.withDO(c.DO.Group(cols...)) } func (c contactDo) Having(conds ...gen.Condition) IContactDo { return c.withDO(c.DO.Having(conds...)) } func (c contactDo) Limit(limit int) IContactDo { return c.withDO(c.DO.Limit(limit)) } func (c contactDo) Offset(offset int) IContactDo { return c.withDO(c.DO.Offset(offset)) } func (c contactDo) Scopes(funcs ...func(gen.Dao) gen.Dao) IContactDo { return c.withDO(c.DO.Scopes(funcs...)) } func (c contactDo) Unscoped() IContactDo { return c.withDO(c.DO.Unscoped()) } func (c contactDo) Create(values ...*model.Contact) error { if len(values) == 0 { return nil } return c.DO.Create(values) } func (c contactDo) CreateInBatches(values []*model.Contact, batchSize int) error { return c.DO.CreateInBatches(values, batchSize) } // Save : !!! underlying implementation is different with GORM // The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values) func (c contactDo) Save(values ...*model.Contact) error { if len(values) == 0 { return nil } return c.DO.Save(values) } func (c contactDo) First() (*model.Contact, error) { if result, err := c.DO.First(); err != nil { return nil, err } else { return result.(*model.Contact), nil } } func (c contactDo) Take() (*model.Contact, error) { if result, err := c.DO.Take(); err != nil { return nil, err } else { return result.(*model.Contact), nil } } func (c contactDo) Last() (*model.Contact, error) { if result, err := c.DO.Last(); err != nil { return nil, err } else { return result.(*model.Contact), nil } } func (c contactDo) Find() ([]*model.Contact, error) { result, err := c.DO.Find() return result.([]*model.Contact), err } func (c contactDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.Contact, err error) { buf := make([]*model.Contact, 0, batchSize) err = c.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error { defer func() { results = append(results, buf...) }() return fc(tx, batch) }) return results, err } func (c contactDo) FindInBatches(result *[]*model.Contact, batchSize int, fc func(tx gen.Dao, batch int) error) error { return c.DO.FindInBatches(result, batchSize, fc) } func (c contactDo) Attrs(attrs ...field.AssignExpr) IContactDo { return c.withDO(c.DO.Attrs(attrs...)) } func (c contactDo) Assign(attrs ...field.AssignExpr) IContactDo { return c.withDO(c.DO.Assign(attrs...)) } func (c contactDo) Joins(fields ...field.RelationField) IContactDo { for _, _f := range fields { c = *c.withDO(c.DO.Joins(_f)) } return &c } func (c contactDo) Preload(fields ...field.RelationField) IContactDo { for _, _f := range fields { c = *c.withDO(c.DO.Preload(_f)) } return &c } func (c contactDo) FirstOrInit() (*model.Contact, error) { if result, err := c.DO.FirstOrInit(); err != nil { return nil, err } else { return result.(*model.Contact), nil } } func (c contactDo) FirstOrCreate() (*model.Contact, error) { if result, err := c.DO.FirstOrCreate(); err != nil { return nil, err } else { return result.(*model.Contact), nil } } func (c contactDo) FindByPage(offset int, limit int) (result []*model.Contact, count int64, err error) { result, err = c.Offset(offset).Limit(limit).Find() if err != nil { return } if size := len(result); 0 < limit && 0 < size && size < limit { count = int64(size + offset) return } count, err = c.Offset(-1).Limit(-1).Count() return } func (c contactDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) { count, err = c.Count() if err != nil { return } err = c.Offset(offset).Limit(limit).Scan(result) return } func (c contactDo) Scan(result interface{}) (err error) { return c.DO.Scan(result) } func (c contactDo) Delete(models ...*model.Contact) (result gen.ResultInfo, err error) { return c.DO.Delete(models) } func (c *contactDo) withDO(do gen.Dao) *contactDo { c.DO = *do.(*gen.DO) return c } ================================================ FILE: dal/query/gen.go ================================================ // Code generated by gorm.io/gen. DO NOT EDIT. // Code generated by gorm.io/gen. DO NOT EDIT. // Code generated by gorm.io/gen. DO NOT EDIT. package query import ( "context" "database/sql" "gorm.io/gorm" "gorm.io/gen" "gorm.io/plugin/dbresolver" ) var ( Q = new(Query) User *user ) func SetDefault(db *gorm.DB, opts ...gen.DOOption) { *Q = *Use(db, opts...) User = &Q.User } func Use(db *gorm.DB, opts ...gen.DOOption) *Query { return &Query{ db: db, User: newUser(db, opts...), } } type Query struct { db *gorm.DB User user } func (q *Query) Available() bool { return q.db != nil } func (q *Query) clone(db *gorm.DB) *Query { return &Query{ db: db, User: q.User.clone(db), } } func (q *Query) ReadDB() *Query { return q.ReplaceDB(q.db.Clauses(dbresolver.Read)) } func (q *Query) WriteDB() *Query { return q.ReplaceDB(q.db.Clauses(dbresolver.Write)) } func (q *Query) ReplaceDB(db *gorm.DB) *Query { return &Query{ db: db, User: q.User.replaceDB(db), } } type queryCtx struct { User IUserDo } func (q *Query) WithContext(ctx context.Context) *queryCtx { return &queryCtx{ User: q.User.WithContext(ctx), } } func (q *Query) Transaction(fc func(tx *Query) error, opts ...*sql.TxOptions) error { return q.db.Transaction(func(tx *gorm.DB) error { return fc(q.clone(tx)) }, opts...) } func (q *Query) Begin(opts ...*sql.TxOptions) *QueryTx { tx := q.db.Begin(opts...) return &QueryTx{Query: q.clone(tx), Error: tx.Error} } type QueryTx struct { *Query Error error } func (q *QueryTx) Commit() error { return q.db.Commit().Error } func (q *QueryTx) Rollback() error { return q.db.Rollback().Error } func (q *QueryTx) SavePoint(name string) error { return q.db.SavePoint(name).Error } func (q *QueryTx) RollbackTo(name string) error { return q.db.RollbackTo(name).Error } ================================================ FILE: dal/query/group_member.gen.go ================================================ // Code generated by gorm.io/gen. DO NOT EDIT. // Code generated by gorm.io/gen. DO NOT EDIT. // Code generated by gorm.io/gen. DO NOT EDIT. package query import ( "context" "gorm.io/gorm" "gorm.io/gorm/clause" "gorm.io/gorm/schema" "gorm.io/gen" "gorm.io/gen/field" "gorm.io/plugin/dbresolver" "DiTing-Go/dal/model" ) func newGroupMember(db *gorm.DB, opts ...gen.DOOption) groupMember { _groupMember := groupMember{} _groupMember.groupMemberDo.UseDB(db, opts...) _groupMember.groupMemberDo.UseModel(&model.GroupMember{}) tableName := _groupMember.groupMemberDo.TableName() _groupMember.ALL = field.NewAsterisk(tableName) _groupMember.ID = field.NewInt64(tableName, "id") _groupMember.GroupID = field.NewInt64(tableName, "group_id") _groupMember.UID = field.NewInt64(tableName, "uid") _groupMember.Role = field.NewInt32(tableName, "role") _groupMember.CreateTime = field.NewTime(tableName, "create_time") _groupMember.UpdateTime = field.NewTime(tableName, "update_time") _groupMember.fillFieldMap() return _groupMember } // groupMember 群成员表 type groupMember struct { groupMemberDo groupMemberDo ALL field.Asterisk ID field.Int64 // id GroupID field.Int64 // 群主id UID field.Int64 // 成员uid Role field.Int32 // 成员角色 1群主 2管理员 3普通成员 CreateTime field.Time // 创建时间 UpdateTime field.Time // 修改时间 fieldMap map[string]field.Expr } func (g groupMember) Table(newTableName string) *groupMember { g.groupMemberDo.UseTable(newTableName) return g.updateTableName(newTableName) } func (g groupMember) As(alias string) *groupMember { g.groupMemberDo.DO = *(g.groupMemberDo.As(alias).(*gen.DO)) return g.updateTableName(alias) } func (g *groupMember) updateTableName(table string) *groupMember { g.ALL = field.NewAsterisk(table) g.ID = field.NewInt64(table, "id") g.GroupID = field.NewInt64(table, "group_id") g.UID = field.NewInt64(table, "uid") g.Role = field.NewInt32(table, "role") g.CreateTime = field.NewTime(table, "create_time") g.UpdateTime = field.NewTime(table, "update_time") g.fillFieldMap() return g } func (g *groupMember) WithContext(ctx context.Context) IGroupMemberDo { return g.groupMemberDo.WithContext(ctx) } func (g groupMember) TableName() string { return g.groupMemberDo.TableName() } func (g groupMember) Alias() string { return g.groupMemberDo.Alias() } func (g groupMember) Columns(cols ...field.Expr) gen.Columns { return g.groupMemberDo.Columns(cols...) } func (g *groupMember) GetFieldByName(fieldName string) (field.OrderExpr, bool) { _f, ok := g.fieldMap[fieldName] if !ok || _f == nil { return nil, false } _oe, ok := _f.(field.OrderExpr) return _oe, ok } func (g *groupMember) fillFieldMap() { g.fieldMap = make(map[string]field.Expr, 6) g.fieldMap["id"] = g.ID g.fieldMap["group_id"] = g.GroupID g.fieldMap["uid"] = g.UID g.fieldMap["role"] = g.Role g.fieldMap["create_time"] = g.CreateTime g.fieldMap["update_time"] = g.UpdateTime } func (g groupMember) clone(db *gorm.DB) groupMember { g.groupMemberDo.ReplaceConnPool(db.Statement.ConnPool) return g } func (g groupMember) replaceDB(db *gorm.DB) groupMember { g.groupMemberDo.ReplaceDB(db) return g } type groupMemberDo struct{ gen.DO } type IGroupMemberDo interface { gen.SubQuery Debug() IGroupMemberDo WithContext(ctx context.Context) IGroupMemberDo WithResult(fc func(tx gen.Dao)) gen.ResultInfo ReplaceDB(db *gorm.DB) ReadDB() IGroupMemberDo WriteDB() IGroupMemberDo As(alias string) gen.Dao Session(config *gorm.Session) IGroupMemberDo Columns(cols ...field.Expr) gen.Columns Clauses(conds ...clause.Expression) IGroupMemberDo Not(conds ...gen.Condition) IGroupMemberDo Or(conds ...gen.Condition) IGroupMemberDo Select(conds ...field.Expr) IGroupMemberDo Where(conds ...gen.Condition) IGroupMemberDo Order(conds ...field.Expr) IGroupMemberDo Distinct(cols ...field.Expr) IGroupMemberDo Omit(cols ...field.Expr) IGroupMemberDo Join(table schema.Tabler, on ...field.Expr) IGroupMemberDo LeftJoin(table schema.Tabler, on ...field.Expr) IGroupMemberDo RightJoin(table schema.Tabler, on ...field.Expr) IGroupMemberDo Group(cols ...field.Expr) IGroupMemberDo Having(conds ...gen.Condition) IGroupMemberDo Limit(limit int) IGroupMemberDo Offset(offset int) IGroupMemberDo Count() (count int64, err error) Scopes(funcs ...func(gen.Dao) gen.Dao) IGroupMemberDo Unscoped() IGroupMemberDo Create(values ...*model.GroupMember) error CreateInBatches(values []*model.GroupMember, batchSize int) error Save(values ...*model.GroupMember) error First() (*model.GroupMember, error) Take() (*model.GroupMember, error) Last() (*model.GroupMember, error) Find() ([]*model.GroupMember, error) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.GroupMember, err error) FindInBatches(result *[]*model.GroupMember, batchSize int, fc func(tx gen.Dao, batch int) error) error Pluck(column field.Expr, dest interface{}) error Delete(...*model.GroupMember) (info gen.ResultInfo, err error) Update(column field.Expr, value interface{}) (info gen.ResultInfo, err error) UpdateSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error) Updates(value interface{}) (info gen.ResultInfo, err error) UpdateColumn(column field.Expr, value interface{}) (info gen.ResultInfo, err error) UpdateColumnSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error) UpdateColumns(value interface{}) (info gen.ResultInfo, err error) UpdateFrom(q gen.SubQuery) gen.Dao Attrs(attrs ...field.AssignExpr) IGroupMemberDo Assign(attrs ...field.AssignExpr) IGroupMemberDo Joins(fields ...field.RelationField) IGroupMemberDo Preload(fields ...field.RelationField) IGroupMemberDo FirstOrInit() (*model.GroupMember, error) FirstOrCreate() (*model.GroupMember, error) FindByPage(offset int, limit int) (result []*model.GroupMember, count int64, err error) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) Scan(result interface{}) (err error) Returning(value interface{}, columns ...string) IGroupMemberDo UnderlyingDB() *gorm.DB schema.Tabler } func (g groupMemberDo) Debug() IGroupMemberDo { return g.withDO(g.DO.Debug()) } func (g groupMemberDo) WithContext(ctx context.Context) IGroupMemberDo { return g.withDO(g.DO.WithContext(ctx)) } func (g groupMemberDo) ReadDB() IGroupMemberDo { return g.Clauses(dbresolver.Read) } func (g groupMemberDo) WriteDB() IGroupMemberDo { return g.Clauses(dbresolver.Write) } func (g groupMemberDo) Session(config *gorm.Session) IGroupMemberDo { return g.withDO(g.DO.Session(config)) } func (g groupMemberDo) Clauses(conds ...clause.Expression) IGroupMemberDo { return g.withDO(g.DO.Clauses(conds...)) } func (g groupMemberDo) Returning(value interface{}, columns ...string) IGroupMemberDo { return g.withDO(g.DO.Returning(value, columns...)) } func (g groupMemberDo) Not(conds ...gen.Condition) IGroupMemberDo { return g.withDO(g.DO.Not(conds...)) } func (g groupMemberDo) Or(conds ...gen.Condition) IGroupMemberDo { return g.withDO(g.DO.Or(conds...)) } func (g groupMemberDo) Select(conds ...field.Expr) IGroupMemberDo { return g.withDO(g.DO.Select(conds...)) } func (g groupMemberDo) Where(conds ...gen.Condition) IGroupMemberDo { return g.withDO(g.DO.Where(conds...)) } func (g groupMemberDo) Order(conds ...field.Expr) IGroupMemberDo { return g.withDO(g.DO.Order(conds...)) } func (g groupMemberDo) Distinct(cols ...field.Expr) IGroupMemberDo { return g.withDO(g.DO.Distinct(cols...)) } func (g groupMemberDo) Omit(cols ...field.Expr) IGroupMemberDo { return g.withDO(g.DO.Omit(cols...)) } func (g groupMemberDo) Join(table schema.Tabler, on ...field.Expr) IGroupMemberDo { return g.withDO(g.DO.Join(table, on...)) } func (g groupMemberDo) LeftJoin(table schema.Tabler, on ...field.Expr) IGroupMemberDo { return g.withDO(g.DO.LeftJoin(table, on...)) } func (g groupMemberDo) RightJoin(table schema.Tabler, on ...field.Expr) IGroupMemberDo { return g.withDO(g.DO.RightJoin(table, on...)) } func (g groupMemberDo) Group(cols ...field.Expr) IGroupMemberDo { return g.withDO(g.DO.Group(cols...)) } func (g groupMemberDo) Having(conds ...gen.Condition) IGroupMemberDo { return g.withDO(g.DO.Having(conds...)) } func (g groupMemberDo) Limit(limit int) IGroupMemberDo { return g.withDO(g.DO.Limit(limit)) } func (g groupMemberDo) Offset(offset int) IGroupMemberDo { return g.withDO(g.DO.Offset(offset)) } func (g groupMemberDo) Scopes(funcs ...func(gen.Dao) gen.Dao) IGroupMemberDo { return g.withDO(g.DO.Scopes(funcs...)) } func (g groupMemberDo) Unscoped() IGroupMemberDo { return g.withDO(g.DO.Unscoped()) } func (g groupMemberDo) Create(values ...*model.GroupMember) error { if len(values) == 0 { return nil } return g.DO.Create(values) } func (g groupMemberDo) CreateInBatches(values []*model.GroupMember, batchSize int) error { return g.DO.CreateInBatches(values, batchSize) } // Save : !!! underlying implementation is different with GORM // The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values) func (g groupMemberDo) Save(values ...*model.GroupMember) error { if len(values) == 0 { return nil } return g.DO.Save(values) } func (g groupMemberDo) First() (*model.GroupMember, error) { if result, err := g.DO.First(); err != nil { return nil, err } else { return result.(*model.GroupMember), nil } } func (g groupMemberDo) Take() (*model.GroupMember, error) { if result, err := g.DO.Take(); err != nil { return nil, err } else { return result.(*model.GroupMember), nil } } func (g groupMemberDo) Last() (*model.GroupMember, error) { if result, err := g.DO.Last(); err != nil { return nil, err } else { return result.(*model.GroupMember), nil } } func (g groupMemberDo) Find() ([]*model.GroupMember, error) { result, err := g.DO.Find() return result.([]*model.GroupMember), err } func (g groupMemberDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.GroupMember, err error) { buf := make([]*model.GroupMember, 0, batchSize) err = g.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error { defer func() { results = append(results, buf...) }() return fc(tx, batch) }) return results, err } func (g groupMemberDo) FindInBatches(result *[]*model.GroupMember, batchSize int, fc func(tx gen.Dao, batch int) error) error { return g.DO.FindInBatches(result, batchSize, fc) } func (g groupMemberDo) Attrs(attrs ...field.AssignExpr) IGroupMemberDo { return g.withDO(g.DO.Attrs(attrs...)) } func (g groupMemberDo) Assign(attrs ...field.AssignExpr) IGroupMemberDo { return g.withDO(g.DO.Assign(attrs...)) } func (g groupMemberDo) Joins(fields ...field.RelationField) IGroupMemberDo { for _, _f := range fields { g = *g.withDO(g.DO.Joins(_f)) } return &g } func (g groupMemberDo) Preload(fields ...field.RelationField) IGroupMemberDo { for _, _f := range fields { g = *g.withDO(g.DO.Preload(_f)) } return &g } func (g groupMemberDo) FirstOrInit() (*model.GroupMember, error) { if result, err := g.DO.FirstOrInit(); err != nil { return nil, err } else { return result.(*model.GroupMember), nil } } func (g groupMemberDo) FirstOrCreate() (*model.GroupMember, error) { if result, err := g.DO.FirstOrCreate(); err != nil { return nil, err } else { return result.(*model.GroupMember), nil } } func (g groupMemberDo) FindByPage(offset int, limit int) (result []*model.GroupMember, count int64, err error) { result, err = g.Offset(offset).Limit(limit).Find() if err != nil { return } if size := len(result); 0 < limit && 0 < size && size < limit { count = int64(size + offset) return } count, err = g.Offset(-1).Limit(-1).Count() return } func (g groupMemberDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) { count, err = g.Count() if err != nil { return } err = g.Offset(offset).Limit(limit).Scan(result) return } func (g groupMemberDo) Scan(result interface{}) (err error) { return g.DO.Scan(result) } func (g groupMemberDo) Delete(models ...*model.GroupMember) (result gen.ResultInfo, err error) { return g.DO.Delete(models) } func (g *groupMemberDo) withDO(do gen.Dao) *groupMemberDo { g.DO = *do.(*gen.DO) return g } ================================================ FILE: dal/query/message.gen.go ================================================ // Code generated by gorm.io/gen. DO NOT EDIT. // Code generated by gorm.io/gen. DO NOT EDIT. // Code generated by gorm.io/gen. DO NOT EDIT. package query import ( "context" "gorm.io/gorm" "gorm.io/gorm/clause" "gorm.io/gorm/schema" "gorm.io/gen" "gorm.io/gen/field" "gorm.io/plugin/dbresolver" "DiTing-Go/dal/model" ) func newMessage(db *gorm.DB, opts ...gen.DOOption) message { _message := message{} _message.messageDo.UseDB(db, opts...) _message.messageDo.UseModel(&model.Message{}) tableName := _message.messageDo.TableName() _message.ALL = field.NewAsterisk(tableName) _message.ID = field.NewInt64(tableName, "id") _message.RoomID = field.NewInt64(tableName, "room_id") _message.FromUID = field.NewInt64(tableName, "from_uid") _message.Content = field.NewString(tableName, "content") _message.ReplyMsgID = field.NewInt64(tableName, "reply_msg_id") _message.DeleteStatus = field.NewInt32(tableName, "delete_status") _message.GapCount = field.NewInt32(tableName, "gap_count") _message.Type = field.NewInt32(tableName, "type") _message.Extra = field.NewString(tableName, "extra") _message.CreateTime = field.NewTime(tableName, "create_time") _message.UpdateTime = field.NewTime(tableName, "update_time") _message.fillFieldMap() return _message } // message 消息表 type message struct { messageDo messageDo ALL field.Asterisk ID field.Int64 // id RoomID field.Int64 // 会话表id FromUID field.Int64 // 消息发送者uid Content field.String // 消息内容 ReplyMsgID field.Int64 // 回复的消息内容 DeleteStatus field.Int32 // 消息状态 0正常 1删除 GapCount field.Int32 // 与回复的消息间隔多少条 Type field.Int32 // 消息类型 1正常文本 2.撤回消息 Extra field.String // 扩展信息 CreateTime field.Time // 创建时间 UpdateTime field.Time // 修改时间 fieldMap map[string]field.Expr } func (m message) Table(newTableName string) *message { m.messageDo.UseTable(newTableName) return m.updateTableName(newTableName) } func (m message) As(alias string) *message { m.messageDo.DO = *(m.messageDo.As(alias).(*gen.DO)) return m.updateTableName(alias) } func (m *message) updateTableName(table string) *message { m.ALL = field.NewAsterisk(table) m.ID = field.NewInt64(table, "id") m.RoomID = field.NewInt64(table, "room_id") m.FromUID = field.NewInt64(table, "from_uid") m.Content = field.NewString(table, "content") m.ReplyMsgID = field.NewInt64(table, "reply_msg_id") m.DeleteStatus = field.NewInt32(table, "delete_status") m.GapCount = field.NewInt32(table, "gap_count") m.Type = field.NewInt32(table, "type") m.Extra = field.NewString(table, "extra") m.CreateTime = field.NewTime(table, "create_time") m.UpdateTime = field.NewTime(table, "update_time") m.fillFieldMap() return m } func (m *message) WithContext(ctx context.Context) IMessageDo { return m.messageDo.WithContext(ctx) } func (m message) TableName() string { return m.messageDo.TableName() } func (m message) Alias() string { return m.messageDo.Alias() } func (m message) Columns(cols ...field.Expr) gen.Columns { return m.messageDo.Columns(cols...) } func (m *message) GetFieldByName(fieldName string) (field.OrderExpr, bool) { _f, ok := m.fieldMap[fieldName] if !ok || _f == nil { return nil, false } _oe, ok := _f.(field.OrderExpr) return _oe, ok } func (m *message) fillFieldMap() { m.fieldMap = make(map[string]field.Expr, 11) m.fieldMap["id"] = m.ID m.fieldMap["room_id"] = m.RoomID m.fieldMap["from_uid"] = m.FromUID m.fieldMap["content"] = m.Content m.fieldMap["reply_msg_id"] = m.ReplyMsgID m.fieldMap["delete_status"] = m.DeleteStatus m.fieldMap["gap_count"] = m.GapCount m.fieldMap["type"] = m.Type m.fieldMap["extra"] = m.Extra m.fieldMap["create_time"] = m.CreateTime m.fieldMap["update_time"] = m.UpdateTime } func (m message) clone(db *gorm.DB) message { m.messageDo.ReplaceConnPool(db.Statement.ConnPool) return m } func (m message) replaceDB(db *gorm.DB) message { m.messageDo.ReplaceDB(db) return m } type messageDo struct{ gen.DO } type IMessageDo interface { gen.SubQuery Debug() IMessageDo WithContext(ctx context.Context) IMessageDo WithResult(fc func(tx gen.Dao)) gen.ResultInfo ReplaceDB(db *gorm.DB) ReadDB() IMessageDo WriteDB() IMessageDo As(alias string) gen.Dao Session(config *gorm.Session) IMessageDo Columns(cols ...field.Expr) gen.Columns Clauses(conds ...clause.Expression) IMessageDo Not(conds ...gen.Condition) IMessageDo Or(conds ...gen.Condition) IMessageDo Select(conds ...field.Expr) IMessageDo Where(conds ...gen.Condition) IMessageDo Order(conds ...field.Expr) IMessageDo Distinct(cols ...field.Expr) IMessageDo Omit(cols ...field.Expr) IMessageDo Join(table schema.Tabler, on ...field.Expr) IMessageDo LeftJoin(table schema.Tabler, on ...field.Expr) IMessageDo RightJoin(table schema.Tabler, on ...field.Expr) IMessageDo Group(cols ...field.Expr) IMessageDo Having(conds ...gen.Condition) IMessageDo Limit(limit int) IMessageDo Offset(offset int) IMessageDo Count() (count int64, err error) Scopes(funcs ...func(gen.Dao) gen.Dao) IMessageDo Unscoped() IMessageDo Create(values ...*model.Message) error CreateInBatches(values []*model.Message, batchSize int) error Save(values ...*model.Message) error First() (*model.Message, error) Take() (*model.Message, error) Last() (*model.Message, error) Find() ([]*model.Message, error) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.Message, err error) FindInBatches(result *[]*model.Message, batchSize int, fc func(tx gen.Dao, batch int) error) error Pluck(column field.Expr, dest interface{}) error Delete(...*model.Message) (info gen.ResultInfo, err error) Update(column field.Expr, value interface{}) (info gen.ResultInfo, err error) UpdateSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error) Updates(value interface{}) (info gen.ResultInfo, err error) UpdateColumn(column field.Expr, value interface{}) (info gen.ResultInfo, err error) UpdateColumnSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error) UpdateColumns(value interface{}) (info gen.ResultInfo, err error) UpdateFrom(q gen.SubQuery) gen.Dao Attrs(attrs ...field.AssignExpr) IMessageDo Assign(attrs ...field.AssignExpr) IMessageDo Joins(fields ...field.RelationField) IMessageDo Preload(fields ...field.RelationField) IMessageDo FirstOrInit() (*model.Message, error) FirstOrCreate() (*model.Message, error) FindByPage(offset int, limit int) (result []*model.Message, count int64, err error) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) Scan(result interface{}) (err error) Returning(value interface{}, columns ...string) IMessageDo UnderlyingDB() *gorm.DB schema.Tabler } func (m messageDo) Debug() IMessageDo { return m.withDO(m.DO.Debug()) } func (m messageDo) WithContext(ctx context.Context) IMessageDo { return m.withDO(m.DO.WithContext(ctx)) } func (m messageDo) ReadDB() IMessageDo { return m.Clauses(dbresolver.Read) } func (m messageDo) WriteDB() IMessageDo { return m.Clauses(dbresolver.Write) } func (m messageDo) Session(config *gorm.Session) IMessageDo { return m.withDO(m.DO.Session(config)) } func (m messageDo) Clauses(conds ...clause.Expression) IMessageDo { return m.withDO(m.DO.Clauses(conds...)) } func (m messageDo) Returning(value interface{}, columns ...string) IMessageDo { return m.withDO(m.DO.Returning(value, columns...)) } func (m messageDo) Not(conds ...gen.Condition) IMessageDo { return m.withDO(m.DO.Not(conds...)) } func (m messageDo) Or(conds ...gen.Condition) IMessageDo { return m.withDO(m.DO.Or(conds...)) } func (m messageDo) Select(conds ...field.Expr) IMessageDo { return m.withDO(m.DO.Select(conds...)) } func (m messageDo) Where(conds ...gen.Condition) IMessageDo { return m.withDO(m.DO.Where(conds...)) } func (m messageDo) Order(conds ...field.Expr) IMessageDo { return m.withDO(m.DO.Order(conds...)) } func (m messageDo) Distinct(cols ...field.Expr) IMessageDo { return m.withDO(m.DO.Distinct(cols...)) } func (m messageDo) Omit(cols ...field.Expr) IMessageDo { return m.withDO(m.DO.Omit(cols...)) } func (m messageDo) Join(table schema.Tabler, on ...field.Expr) IMessageDo { return m.withDO(m.DO.Join(table, on...)) } func (m messageDo) LeftJoin(table schema.Tabler, on ...field.Expr) IMessageDo { return m.withDO(m.DO.LeftJoin(table, on...)) } func (m messageDo) RightJoin(table schema.Tabler, on ...field.Expr) IMessageDo { return m.withDO(m.DO.RightJoin(table, on...)) } func (m messageDo) Group(cols ...field.Expr) IMessageDo { return m.withDO(m.DO.Group(cols...)) } func (m messageDo) Having(conds ...gen.Condition) IMessageDo { return m.withDO(m.DO.Having(conds...)) } func (m messageDo) Limit(limit int) IMessageDo { return m.withDO(m.DO.Limit(limit)) } func (m messageDo) Offset(offset int) IMessageDo { return m.withDO(m.DO.Offset(offset)) } func (m messageDo) Scopes(funcs ...func(gen.Dao) gen.Dao) IMessageDo { return m.withDO(m.DO.Scopes(funcs...)) } func (m messageDo) Unscoped() IMessageDo { return m.withDO(m.DO.Unscoped()) } func (m messageDo) Create(values ...*model.Message) error { if len(values) == 0 { return nil } return m.DO.Create(values) } func (m messageDo) CreateInBatches(values []*model.Message, batchSize int) error { return m.DO.CreateInBatches(values, batchSize) } // Save : !!! underlying implementation is different with GORM // The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values) func (m messageDo) Save(values ...*model.Message) error { if len(values) == 0 { return nil } return m.DO.Save(values) } func (m messageDo) First() (*model.Message, error) { if result, err := m.DO.First(); err != nil { return nil, err } else { return result.(*model.Message), nil } } func (m messageDo) Take() (*model.Message, error) { if result, err := m.DO.Take(); err != nil { return nil, err } else { return result.(*model.Message), nil } } func (m messageDo) Last() (*model.Message, error) { if result, err := m.DO.Last(); err != nil { return nil, err } else { return result.(*model.Message), nil } } func (m messageDo) Find() ([]*model.Message, error) { result, err := m.DO.Find() return result.([]*model.Message), err } func (m messageDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.Message, err error) { buf := make([]*model.Message, 0, batchSize) err = m.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error { defer func() { results = append(results, buf...) }() return fc(tx, batch) }) return results, err } func (m messageDo) FindInBatches(result *[]*model.Message, batchSize int, fc func(tx gen.Dao, batch int) error) error { return m.DO.FindInBatches(result, batchSize, fc) } func (m messageDo) Attrs(attrs ...field.AssignExpr) IMessageDo { return m.withDO(m.DO.Attrs(attrs...)) } func (m messageDo) Assign(attrs ...field.AssignExpr) IMessageDo { return m.withDO(m.DO.Assign(attrs...)) } func (m messageDo) Joins(fields ...field.RelationField) IMessageDo { for _, _f := range fields { m = *m.withDO(m.DO.Joins(_f)) } return &m } func (m messageDo) Preload(fields ...field.RelationField) IMessageDo { for _, _f := range fields { m = *m.withDO(m.DO.Preload(_f)) } return &m } func (m messageDo) FirstOrInit() (*model.Message, error) { if result, err := m.DO.FirstOrInit(); err != nil { return nil, err } else { return result.(*model.Message), nil } } func (m messageDo) FirstOrCreate() (*model.Message, error) { if result, err := m.DO.FirstOrCreate(); err != nil { return nil, err } else { return result.(*model.Message), nil } } func (m messageDo) FindByPage(offset int, limit int) (result []*model.Message, count int64, err error) { result, err = m.Offset(offset).Limit(limit).Find() if err != nil { return } if size := len(result); 0 < limit && 0 < size && size < limit { count = int64(size + offset) return } count, err = m.Offset(-1).Limit(-1).Count() return } func (m messageDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) { count, err = m.Count() if err != nil { return } err = m.Offset(offset).Limit(limit).Scan(result) return } func (m messageDo) Scan(result interface{}) (err error) { return m.DO.Scan(result) } func (m messageDo) Delete(models ...*model.Message) (result gen.ResultInfo, err error) { return m.DO.Delete(models) } func (m *messageDo) withDO(do gen.Dao) *messageDo { m.DO = *do.(*gen.DO) return m } ================================================ FILE: dal/query/room.gen.go ================================================ // Code generated by gorm.io/gen. DO NOT EDIT. // Code generated by gorm.io/gen. DO NOT EDIT. // Code generated by gorm.io/gen. DO NOT EDIT. package query import ( "context" "gorm.io/gorm" "gorm.io/gorm/clause" "gorm.io/gorm/schema" "gorm.io/gen" "gorm.io/gen/field" "gorm.io/plugin/dbresolver" "DiTing-Go/dal/model" ) func newRoom(db *gorm.DB, opts ...gen.DOOption) room { _room := room{} _room.roomDo.UseDB(db, opts...) _room.roomDo.UseModel(&model.Room{}) tableName := _room.roomDo.TableName() _room.ALL = field.NewAsterisk(tableName) _room.ID = field.NewInt64(tableName, "id") _room.Type = field.NewInt32(tableName, "type") _room.HotFlag = field.NewInt32(tableName, "hot_flag") _room.ActiveTime = field.NewTime(tableName, "active_time") _room.LastMsgID = field.NewInt64(tableName, "last_msg_id") _room.ExtJSON = field.NewString(tableName, "ext_json") _room.CreateTime = field.NewTime(tableName, "create_time") _room.UpdateTime = field.NewTime(tableName, "update_time") _room.DeleteStatus = field.NewInt32(tableName, "delete_status") _room.fillFieldMap() return _room } // room 房间表 type room struct { roomDo roomDo ALL field.Asterisk ID field.Int64 // id Type field.Int32 // 房间类型 1群聊 2单聊 HotFlag field.Int32 // 是否全员展示 0否 1是 ActiveTime field.Time // 群最后消息的更新时间(热点群不需要写扩散,只更新这里) LastMsgID field.Int64 // 会话中的最后一条消息id ExtJSON field.String // 额外信息(根据不同类型房间有不同存储的东西) CreateTime field.Time // 创建时间 UpdateTime field.Time // 修改时间 DeleteStatus field.Int32 // 房间状态 0正常 1禁用(删好友了禁用) fieldMap map[string]field.Expr } func (r room) Table(newTableName string) *room { r.roomDo.UseTable(newTableName) return r.updateTableName(newTableName) } func (r room) As(alias string) *room { r.roomDo.DO = *(r.roomDo.As(alias).(*gen.DO)) return r.updateTableName(alias) } func (r *room) updateTableName(table string) *room { r.ALL = field.NewAsterisk(table) r.ID = field.NewInt64(table, "id") r.Type = field.NewInt32(table, "type") r.HotFlag = field.NewInt32(table, "hot_flag") r.ActiveTime = field.NewTime(table, "active_time") r.LastMsgID = field.NewInt64(table, "last_msg_id") r.ExtJSON = field.NewString(table, "ext_json") r.CreateTime = field.NewTime(table, "create_time") r.UpdateTime = field.NewTime(table, "update_time") r.DeleteStatus = field.NewInt32(table, "delete_status") r.fillFieldMap() return r } func (r *room) WithContext(ctx context.Context) IRoomDo { return r.roomDo.WithContext(ctx) } func (r room) TableName() string { return r.roomDo.TableName() } func (r room) Alias() string { return r.roomDo.Alias() } func (r room) Columns(cols ...field.Expr) gen.Columns { return r.roomDo.Columns(cols...) } func (r *room) GetFieldByName(fieldName string) (field.OrderExpr, bool) { _f, ok := r.fieldMap[fieldName] if !ok || _f == nil { return nil, false } _oe, ok := _f.(field.OrderExpr) return _oe, ok } func (r *room) fillFieldMap() { r.fieldMap = make(map[string]field.Expr, 9) r.fieldMap["id"] = r.ID r.fieldMap["type"] = r.Type r.fieldMap["hot_flag"] = r.HotFlag r.fieldMap["active_time"] = r.ActiveTime r.fieldMap["last_msg_id"] = r.LastMsgID r.fieldMap["ext_json"] = r.ExtJSON r.fieldMap["create_time"] = r.CreateTime r.fieldMap["update_time"] = r.UpdateTime r.fieldMap["delete_status"] = r.DeleteStatus } func (r room) clone(db *gorm.DB) room { r.roomDo.ReplaceConnPool(db.Statement.ConnPool) return r } func (r room) replaceDB(db *gorm.DB) room { r.roomDo.ReplaceDB(db) return r } type roomDo struct{ gen.DO } type IRoomDo interface { gen.SubQuery Debug() IRoomDo WithContext(ctx context.Context) IRoomDo WithResult(fc func(tx gen.Dao)) gen.ResultInfo ReplaceDB(db *gorm.DB) ReadDB() IRoomDo WriteDB() IRoomDo As(alias string) gen.Dao Session(config *gorm.Session) IRoomDo Columns(cols ...field.Expr) gen.Columns Clauses(conds ...clause.Expression) IRoomDo Not(conds ...gen.Condition) IRoomDo Or(conds ...gen.Condition) IRoomDo Select(conds ...field.Expr) IRoomDo Where(conds ...gen.Condition) IRoomDo Order(conds ...field.Expr) IRoomDo Distinct(cols ...field.Expr) IRoomDo Omit(cols ...field.Expr) IRoomDo Join(table schema.Tabler, on ...field.Expr) IRoomDo LeftJoin(table schema.Tabler, on ...field.Expr) IRoomDo RightJoin(table schema.Tabler, on ...field.Expr) IRoomDo Group(cols ...field.Expr) IRoomDo Having(conds ...gen.Condition) IRoomDo Limit(limit int) IRoomDo Offset(offset int) IRoomDo Count() (count int64, err error) Scopes(funcs ...func(gen.Dao) gen.Dao) IRoomDo Unscoped() IRoomDo Create(values ...*model.Room) error CreateInBatches(values []*model.Room, batchSize int) error Save(values ...*model.Room) error First() (*model.Room, error) Take() (*model.Room, error) Last() (*model.Room, error) Find() ([]*model.Room, error) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.Room, err error) FindInBatches(result *[]*model.Room, batchSize int, fc func(tx gen.Dao, batch int) error) error Pluck(column field.Expr, dest interface{}) error Delete(...*model.Room) (info gen.ResultInfo, err error) Update(column field.Expr, value interface{}) (info gen.ResultInfo, err error) UpdateSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error) Updates(value interface{}) (info gen.ResultInfo, err error) UpdateColumn(column field.Expr, value interface{}) (info gen.ResultInfo, err error) UpdateColumnSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error) UpdateColumns(value interface{}) (info gen.ResultInfo, err error) UpdateFrom(q gen.SubQuery) gen.Dao Attrs(attrs ...field.AssignExpr) IRoomDo Assign(attrs ...field.AssignExpr) IRoomDo Joins(fields ...field.RelationField) IRoomDo Preload(fields ...field.RelationField) IRoomDo FirstOrInit() (*model.Room, error) FirstOrCreate() (*model.Room, error) FindByPage(offset int, limit int) (result []*model.Room, count int64, err error) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) Scan(result interface{}) (err error) Returning(value interface{}, columns ...string) IRoomDo UnderlyingDB() *gorm.DB schema.Tabler } func (r roomDo) Debug() IRoomDo { return r.withDO(r.DO.Debug()) } func (r roomDo) WithContext(ctx context.Context) IRoomDo { return r.withDO(r.DO.WithContext(ctx)) } func (r roomDo) ReadDB() IRoomDo { return r.Clauses(dbresolver.Read) } func (r roomDo) WriteDB() IRoomDo { return r.Clauses(dbresolver.Write) } func (r roomDo) Session(config *gorm.Session) IRoomDo { return r.withDO(r.DO.Session(config)) } func (r roomDo) Clauses(conds ...clause.Expression) IRoomDo { return r.withDO(r.DO.Clauses(conds...)) } func (r roomDo) Returning(value interface{}, columns ...string) IRoomDo { return r.withDO(r.DO.Returning(value, columns...)) } func (r roomDo) Not(conds ...gen.Condition) IRoomDo { return r.withDO(r.DO.Not(conds...)) } func (r roomDo) Or(conds ...gen.Condition) IRoomDo { return r.withDO(r.DO.Or(conds...)) } func (r roomDo) Select(conds ...field.Expr) IRoomDo { return r.withDO(r.DO.Select(conds...)) } func (r roomDo) Where(conds ...gen.Condition) IRoomDo { return r.withDO(r.DO.Where(conds...)) } func (r roomDo) Order(conds ...field.Expr) IRoomDo { return r.withDO(r.DO.Order(conds...)) } func (r roomDo) Distinct(cols ...field.Expr) IRoomDo { return r.withDO(r.DO.Distinct(cols...)) } func (r roomDo) Omit(cols ...field.Expr) IRoomDo { return r.withDO(r.DO.Omit(cols...)) } func (r roomDo) Join(table schema.Tabler, on ...field.Expr) IRoomDo { return r.withDO(r.DO.Join(table, on...)) } func (r roomDo) LeftJoin(table schema.Tabler, on ...field.Expr) IRoomDo { return r.withDO(r.DO.LeftJoin(table, on...)) } func (r roomDo) RightJoin(table schema.Tabler, on ...field.Expr) IRoomDo { return r.withDO(r.DO.RightJoin(table, on...)) } func (r roomDo) Group(cols ...field.Expr) IRoomDo { return r.withDO(r.DO.Group(cols...)) } func (r roomDo) Having(conds ...gen.Condition) IRoomDo { return r.withDO(r.DO.Having(conds...)) } func (r roomDo) Limit(limit int) IRoomDo { return r.withDO(r.DO.Limit(limit)) } func (r roomDo) Offset(offset int) IRoomDo { return r.withDO(r.DO.Offset(offset)) } func (r roomDo) Scopes(funcs ...func(gen.Dao) gen.Dao) IRoomDo { return r.withDO(r.DO.Scopes(funcs...)) } func (r roomDo) Unscoped() IRoomDo { return r.withDO(r.DO.Unscoped()) } func (r roomDo) Create(values ...*model.Room) error { if len(values) == 0 { return nil } return r.DO.Create(values) } func (r roomDo) CreateInBatches(values []*model.Room, batchSize int) error { return r.DO.CreateInBatches(values, batchSize) } // Save : !!! underlying implementation is different with GORM // The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values) func (r roomDo) Save(values ...*model.Room) error { if len(values) == 0 { return nil } return r.DO.Save(values) } func (r roomDo) First() (*model.Room, error) { if result, err := r.DO.First(); err != nil { return nil, err } else { return result.(*model.Room), nil } } func (r roomDo) Take() (*model.Room, error) { if result, err := r.DO.Take(); err != nil { return nil, err } else { return result.(*model.Room), nil } } func (r roomDo) Last() (*model.Room, error) { if result, err := r.DO.Last(); err != nil { return nil, err } else { return result.(*model.Room), nil } } func (r roomDo) Find() ([]*model.Room, error) { result, err := r.DO.Find() return result.([]*model.Room), err } func (r roomDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.Room, err error) { buf := make([]*model.Room, 0, batchSize) err = r.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error { defer func() { results = append(results, buf...) }() return fc(tx, batch) }) return results, err } func (r roomDo) FindInBatches(result *[]*model.Room, batchSize int, fc func(tx gen.Dao, batch int) error) error { return r.DO.FindInBatches(result, batchSize, fc) } func (r roomDo) Attrs(attrs ...field.AssignExpr) IRoomDo { return r.withDO(r.DO.Attrs(attrs...)) } func (r roomDo) Assign(attrs ...field.AssignExpr) IRoomDo { return r.withDO(r.DO.Assign(attrs...)) } func (r roomDo) Joins(fields ...field.RelationField) IRoomDo { for _, _f := range fields { r = *r.withDO(r.DO.Joins(_f)) } return &r } func (r roomDo) Preload(fields ...field.RelationField) IRoomDo { for _, _f := range fields { r = *r.withDO(r.DO.Preload(_f)) } return &r } func (r roomDo) FirstOrInit() (*model.Room, error) { if result, err := r.DO.FirstOrInit(); err != nil { return nil, err } else { return result.(*model.Room), nil } } func (r roomDo) FirstOrCreate() (*model.Room, error) { if result, err := r.DO.FirstOrCreate(); err != nil { return nil, err } else { return result.(*model.Room), nil } } func (r roomDo) FindByPage(offset int, limit int) (result []*model.Room, count int64, err error) { result, err = r.Offset(offset).Limit(limit).Find() if err != nil { return } if size := len(result); 0 < limit && 0 < size && size < limit { count = int64(size + offset) return } count, err = r.Offset(-1).Limit(-1).Count() return } func (r roomDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) { count, err = r.Count() if err != nil { return } err = r.Offset(offset).Limit(limit).Scan(result) return } func (r roomDo) Scan(result interface{}) (err error) { return r.DO.Scan(result) } func (r roomDo) Delete(models ...*model.Room) (result gen.ResultInfo, err error) { return r.DO.Delete(models) } func (r *roomDo) withDO(do gen.Dao) *roomDo { r.DO = *do.(*gen.DO) return r } ================================================ FILE: dal/query/room_friend.gen.go ================================================ // Code generated by gorm.io/gen. DO NOT EDIT. // Code generated by gorm.io/gen. DO NOT EDIT. // Code generated by gorm.io/gen. DO NOT EDIT. package query import ( "context" "gorm.io/gorm" "gorm.io/gorm/clause" "gorm.io/gorm/schema" "gorm.io/gen" "gorm.io/gen/field" "gorm.io/plugin/dbresolver" "DiTing-Go/dal/model" ) func newRoomFriend(db *gorm.DB, opts ...gen.DOOption) roomFriend { _roomFriend := roomFriend{} _roomFriend.roomFriendDo.UseDB(db, opts...) _roomFriend.roomFriendDo.UseModel(&model.RoomFriend{}) tableName := _roomFriend.roomFriendDo.TableName() _roomFriend.ALL = field.NewAsterisk(tableName) _roomFriend.ID = field.NewInt64(tableName, "id") _roomFriend.RoomID = field.NewInt64(tableName, "room_id") _roomFriend.Uid1 = field.NewInt64(tableName, "uid1") _roomFriend.Uid2 = field.NewInt64(tableName, "uid2") _roomFriend.RoomKey = field.NewString(tableName, "room_key") _roomFriend.DeleteStatus = field.NewInt32(tableName, "delete_status") _roomFriend.CreateTime = field.NewTime(tableName, "create_time") _roomFriend.UpdateTime = field.NewTime(tableName, "update_time") _roomFriend.fillFieldMap() return _roomFriend } // roomFriend 单聊房间表 type roomFriend struct { roomFriendDo roomFriendDo ALL field.Asterisk ID field.Int64 // id RoomID field.Int64 // 房间id Uid1 field.Int64 // uid1(更小的uid) Uid2 field.Int64 // uid2(更大的uid) RoomKey field.String // 房间key由两个uid拼接,先做排序uid1_uid2 DeleteStatus field.Int32 // 房间状态 0正常 1禁用(删好友了禁用) CreateTime field.Time // 创建时间 UpdateTime field.Time // 修改时间 fieldMap map[string]field.Expr } func (r roomFriend) Table(newTableName string) *roomFriend { r.roomFriendDo.UseTable(newTableName) return r.updateTableName(newTableName) } func (r roomFriend) As(alias string) *roomFriend { r.roomFriendDo.DO = *(r.roomFriendDo.As(alias).(*gen.DO)) return r.updateTableName(alias) } func (r *roomFriend) updateTableName(table string) *roomFriend { r.ALL = field.NewAsterisk(table) r.ID = field.NewInt64(table, "id") r.RoomID = field.NewInt64(table, "room_id") r.Uid1 = field.NewInt64(table, "uid1") r.Uid2 = field.NewInt64(table, "uid2") r.RoomKey = field.NewString(table, "room_key") r.DeleteStatus = field.NewInt32(table, "delete_status") r.CreateTime = field.NewTime(table, "create_time") r.UpdateTime = field.NewTime(table, "update_time") r.fillFieldMap() return r } func (r *roomFriend) WithContext(ctx context.Context) IRoomFriendDo { return r.roomFriendDo.WithContext(ctx) } func (r roomFriend) TableName() string { return r.roomFriendDo.TableName() } func (r roomFriend) Alias() string { return r.roomFriendDo.Alias() } func (r roomFriend) Columns(cols ...field.Expr) gen.Columns { return r.roomFriendDo.Columns(cols...) } func (r *roomFriend) GetFieldByName(fieldName string) (field.OrderExpr, bool) { _f, ok := r.fieldMap[fieldName] if !ok || _f == nil { return nil, false } _oe, ok := _f.(field.OrderExpr) return _oe, ok } func (r *roomFriend) fillFieldMap() { r.fieldMap = make(map[string]field.Expr, 8) r.fieldMap["id"] = r.ID r.fieldMap["room_id"] = r.RoomID r.fieldMap["uid1"] = r.Uid1 r.fieldMap["uid2"] = r.Uid2 r.fieldMap["room_key"] = r.RoomKey r.fieldMap["delete_status"] = r.DeleteStatus r.fieldMap["create_time"] = r.CreateTime r.fieldMap["update_time"] = r.UpdateTime } func (r roomFriend) clone(db *gorm.DB) roomFriend { r.roomFriendDo.ReplaceConnPool(db.Statement.ConnPool) return r } func (r roomFriend) replaceDB(db *gorm.DB) roomFriend { r.roomFriendDo.ReplaceDB(db) return r } type roomFriendDo struct{ gen.DO } type IRoomFriendDo interface { gen.SubQuery Debug() IRoomFriendDo WithContext(ctx context.Context) IRoomFriendDo WithResult(fc func(tx gen.Dao)) gen.ResultInfo ReplaceDB(db *gorm.DB) ReadDB() IRoomFriendDo WriteDB() IRoomFriendDo As(alias string) gen.Dao Session(config *gorm.Session) IRoomFriendDo Columns(cols ...field.Expr) gen.Columns Clauses(conds ...clause.Expression) IRoomFriendDo Not(conds ...gen.Condition) IRoomFriendDo Or(conds ...gen.Condition) IRoomFriendDo Select(conds ...field.Expr) IRoomFriendDo Where(conds ...gen.Condition) IRoomFriendDo Order(conds ...field.Expr) IRoomFriendDo Distinct(cols ...field.Expr) IRoomFriendDo Omit(cols ...field.Expr) IRoomFriendDo Join(table schema.Tabler, on ...field.Expr) IRoomFriendDo LeftJoin(table schema.Tabler, on ...field.Expr) IRoomFriendDo RightJoin(table schema.Tabler, on ...field.Expr) IRoomFriendDo Group(cols ...field.Expr) IRoomFriendDo Having(conds ...gen.Condition) IRoomFriendDo Limit(limit int) IRoomFriendDo Offset(offset int) IRoomFriendDo Count() (count int64, err error) Scopes(funcs ...func(gen.Dao) gen.Dao) IRoomFriendDo Unscoped() IRoomFriendDo Create(values ...*model.RoomFriend) error CreateInBatches(values []*model.RoomFriend, batchSize int) error Save(values ...*model.RoomFriend) error First() (*model.RoomFriend, error) Take() (*model.RoomFriend, error) Last() (*model.RoomFriend, error) Find() ([]*model.RoomFriend, error) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.RoomFriend, err error) FindInBatches(result *[]*model.RoomFriend, batchSize int, fc func(tx gen.Dao, batch int) error) error Pluck(column field.Expr, dest interface{}) error Delete(...*model.RoomFriend) (info gen.ResultInfo, err error) Update(column field.Expr, value interface{}) (info gen.ResultInfo, err error) UpdateSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error) Updates(value interface{}) (info gen.ResultInfo, err error) UpdateColumn(column field.Expr, value interface{}) (info gen.ResultInfo, err error) UpdateColumnSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error) UpdateColumns(value interface{}) (info gen.ResultInfo, err error) UpdateFrom(q gen.SubQuery) gen.Dao Attrs(attrs ...field.AssignExpr) IRoomFriendDo Assign(attrs ...field.AssignExpr) IRoomFriendDo Joins(fields ...field.RelationField) IRoomFriendDo Preload(fields ...field.RelationField) IRoomFriendDo FirstOrInit() (*model.RoomFriend, error) FirstOrCreate() (*model.RoomFriend, error) FindByPage(offset int, limit int) (result []*model.RoomFriend, count int64, err error) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) Scan(result interface{}) (err error) Returning(value interface{}, columns ...string) IRoomFriendDo UnderlyingDB() *gorm.DB schema.Tabler } func (r roomFriendDo) Debug() IRoomFriendDo { return r.withDO(r.DO.Debug()) } func (r roomFriendDo) WithContext(ctx context.Context) IRoomFriendDo { return r.withDO(r.DO.WithContext(ctx)) } func (r roomFriendDo) ReadDB() IRoomFriendDo { return r.Clauses(dbresolver.Read) } func (r roomFriendDo) WriteDB() IRoomFriendDo { return r.Clauses(dbresolver.Write) } func (r roomFriendDo) Session(config *gorm.Session) IRoomFriendDo { return r.withDO(r.DO.Session(config)) } func (r roomFriendDo) Clauses(conds ...clause.Expression) IRoomFriendDo { return r.withDO(r.DO.Clauses(conds...)) } func (r roomFriendDo) Returning(value interface{}, columns ...string) IRoomFriendDo { return r.withDO(r.DO.Returning(value, columns...)) } func (r roomFriendDo) Not(conds ...gen.Condition) IRoomFriendDo { return r.withDO(r.DO.Not(conds...)) } func (r roomFriendDo) Or(conds ...gen.Condition) IRoomFriendDo { return r.withDO(r.DO.Or(conds...)) } func (r roomFriendDo) Select(conds ...field.Expr) IRoomFriendDo { return r.withDO(r.DO.Select(conds...)) } func (r roomFriendDo) Where(conds ...gen.Condition) IRoomFriendDo { return r.withDO(r.DO.Where(conds...)) } func (r roomFriendDo) Order(conds ...field.Expr) IRoomFriendDo { return r.withDO(r.DO.Order(conds...)) } func (r roomFriendDo) Distinct(cols ...field.Expr) IRoomFriendDo { return r.withDO(r.DO.Distinct(cols...)) } func (r roomFriendDo) Omit(cols ...field.Expr) IRoomFriendDo { return r.withDO(r.DO.Omit(cols...)) } func (r roomFriendDo) Join(table schema.Tabler, on ...field.Expr) IRoomFriendDo { return r.withDO(r.DO.Join(table, on...)) } func (r roomFriendDo) LeftJoin(table schema.Tabler, on ...field.Expr) IRoomFriendDo { return r.withDO(r.DO.LeftJoin(table, on...)) } func (r roomFriendDo) RightJoin(table schema.Tabler, on ...field.Expr) IRoomFriendDo { return r.withDO(r.DO.RightJoin(table, on...)) } func (r roomFriendDo) Group(cols ...field.Expr) IRoomFriendDo { return r.withDO(r.DO.Group(cols...)) } func (r roomFriendDo) Having(conds ...gen.Condition) IRoomFriendDo { return r.withDO(r.DO.Having(conds...)) } func (r roomFriendDo) Limit(limit int) IRoomFriendDo { return r.withDO(r.DO.Limit(limit)) } func (r roomFriendDo) Offset(offset int) IRoomFriendDo { return r.withDO(r.DO.Offset(offset)) } func (r roomFriendDo) Scopes(funcs ...func(gen.Dao) gen.Dao) IRoomFriendDo { return r.withDO(r.DO.Scopes(funcs...)) } func (r roomFriendDo) Unscoped() IRoomFriendDo { return r.withDO(r.DO.Unscoped()) } func (r roomFriendDo) Create(values ...*model.RoomFriend) error { if len(values) == 0 { return nil } return r.DO.Create(values) } func (r roomFriendDo) CreateInBatches(values []*model.RoomFriend, batchSize int) error { return r.DO.CreateInBatches(values, batchSize) } // Save : !!! underlying implementation is different with GORM // The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values) func (r roomFriendDo) Save(values ...*model.RoomFriend) error { if len(values) == 0 { return nil } return r.DO.Save(values) } func (r roomFriendDo) First() (*model.RoomFriend, error) { if result, err := r.DO.First(); err != nil { return nil, err } else { return result.(*model.RoomFriend), nil } } func (r roomFriendDo) Take() (*model.RoomFriend, error) { if result, err := r.DO.Take(); err != nil { return nil, err } else { return result.(*model.RoomFriend), nil } } func (r roomFriendDo) Last() (*model.RoomFriend, error) { if result, err := r.DO.Last(); err != nil { return nil, err } else { return result.(*model.RoomFriend), nil } } func (r roomFriendDo) Find() ([]*model.RoomFriend, error) { result, err := r.DO.Find() return result.([]*model.RoomFriend), err } func (r roomFriendDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.RoomFriend, err error) { buf := make([]*model.RoomFriend, 0, batchSize) err = r.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error { defer func() { results = append(results, buf...) }() return fc(tx, batch) }) return results, err } func (r roomFriendDo) FindInBatches(result *[]*model.RoomFriend, batchSize int, fc func(tx gen.Dao, batch int) error) error { return r.DO.FindInBatches(result, batchSize, fc) } func (r roomFriendDo) Attrs(attrs ...field.AssignExpr) IRoomFriendDo { return r.withDO(r.DO.Attrs(attrs...)) } func (r roomFriendDo) Assign(attrs ...field.AssignExpr) IRoomFriendDo { return r.withDO(r.DO.Assign(attrs...)) } func (r roomFriendDo) Joins(fields ...field.RelationField) IRoomFriendDo { for _, _f := range fields { r = *r.withDO(r.DO.Joins(_f)) } return &r } func (r roomFriendDo) Preload(fields ...field.RelationField) IRoomFriendDo { for _, _f := range fields { r = *r.withDO(r.DO.Preload(_f)) } return &r } func (r roomFriendDo) FirstOrInit() (*model.RoomFriend, error) { if result, err := r.DO.FirstOrInit(); err != nil { return nil, err } else { return result.(*model.RoomFriend), nil } } func (r roomFriendDo) FirstOrCreate() (*model.RoomFriend, error) { if result, err := r.DO.FirstOrCreate(); err != nil { return nil, err } else { return result.(*model.RoomFriend), nil } } func (r roomFriendDo) FindByPage(offset int, limit int) (result []*model.RoomFriend, count int64, err error) { result, err = r.Offset(offset).Limit(limit).Find() if err != nil { return } if size := len(result); 0 < limit && 0 < size && size < limit { count = int64(size + offset) return } count, err = r.Offset(-1).Limit(-1).Count() return } func (r roomFriendDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) { count, err = r.Count() if err != nil { return } err = r.Offset(offset).Limit(limit).Scan(result) return } func (r roomFriendDo) Scan(result interface{}) (err error) { return r.DO.Scan(result) } func (r roomFriendDo) Delete(models ...*model.RoomFriend) (result gen.ResultInfo, err error) { return r.DO.Delete(models) } func (r *roomFriendDo) withDO(do gen.Dao) *roomFriendDo { r.DO = *do.(*gen.DO) return r } ================================================ FILE: dal/query/room_group.gen.go ================================================ // Code generated by gorm.io/gen. DO NOT EDIT. // Code generated by gorm.io/gen. DO NOT EDIT. // Code generated by gorm.io/gen. DO NOT EDIT. package query import ( "context" "gorm.io/gorm" "gorm.io/gorm/clause" "gorm.io/gorm/schema" "gorm.io/gen" "gorm.io/gen/field" "gorm.io/plugin/dbresolver" "DiTing-Go/dal/model" ) func newRoomGroup(db *gorm.DB, opts ...gen.DOOption) roomGroup { _roomGroup := roomGroup{} _roomGroup.roomGroupDo.UseDB(db, opts...) _roomGroup.roomGroupDo.UseModel(&model.RoomGroup{}) tableName := _roomGroup.roomGroupDo.TableName() _roomGroup.ALL = field.NewAsterisk(tableName) _roomGroup.ID = field.NewInt64(tableName, "id") _roomGroup.RoomID = field.NewInt64(tableName, "room_id") _roomGroup.Name = field.NewString(tableName, "name") _roomGroup.Avatar = field.NewString(tableName, "avatar") _roomGroup.ExtJSON = field.NewString(tableName, "ext_json") _roomGroup.DeleteStatus = field.NewInt32(tableName, "delete_status") _roomGroup.CreateTime = field.NewTime(tableName, "create_time") _roomGroup.UpdateTime = field.NewTime(tableName, "update_time") _roomGroup.fillFieldMap() return _roomGroup } // roomGroup 群聊房间表 type roomGroup struct { roomGroupDo roomGroupDo ALL field.Asterisk ID field.Int64 // id RoomID field.Int64 // 房间id Name field.String // 群名称 Avatar field.String // 群头像 ExtJSON field.String // 额外信息(根据不同类型房间有不同存储的东西) DeleteStatus field.Int32 // 逻辑删除(0-正常,1-删除) CreateTime field.Time // 创建时间 UpdateTime field.Time // 修改时间 fieldMap map[string]field.Expr } func (r roomGroup) Table(newTableName string) *roomGroup { r.roomGroupDo.UseTable(newTableName) return r.updateTableName(newTableName) } func (r roomGroup) As(alias string) *roomGroup { r.roomGroupDo.DO = *(r.roomGroupDo.As(alias).(*gen.DO)) return r.updateTableName(alias) } func (r *roomGroup) updateTableName(table string) *roomGroup { r.ALL = field.NewAsterisk(table) r.ID = field.NewInt64(table, "id") r.RoomID = field.NewInt64(table, "room_id") r.Name = field.NewString(table, "name") r.Avatar = field.NewString(table, "avatar") r.ExtJSON = field.NewString(table, "ext_json") r.DeleteStatus = field.NewInt32(table, "delete_status") r.CreateTime = field.NewTime(table, "create_time") r.UpdateTime = field.NewTime(table, "update_time") r.fillFieldMap() return r } func (r *roomGroup) WithContext(ctx context.Context) IRoomGroupDo { return r.roomGroupDo.WithContext(ctx) } func (r roomGroup) TableName() string { return r.roomGroupDo.TableName() } func (r roomGroup) Alias() string { return r.roomGroupDo.Alias() } func (r roomGroup) Columns(cols ...field.Expr) gen.Columns { return r.roomGroupDo.Columns(cols...) } func (r *roomGroup) GetFieldByName(fieldName string) (field.OrderExpr, bool) { _f, ok := r.fieldMap[fieldName] if !ok || _f == nil { return nil, false } _oe, ok := _f.(field.OrderExpr) return _oe, ok } func (r *roomGroup) fillFieldMap() { r.fieldMap = make(map[string]field.Expr, 8) r.fieldMap["id"] = r.ID r.fieldMap["room_id"] = r.RoomID r.fieldMap["name"] = r.Name r.fieldMap["avatar"] = r.Avatar r.fieldMap["ext_json"] = r.ExtJSON r.fieldMap["delete_status"] = r.DeleteStatus r.fieldMap["create_time"] = r.CreateTime r.fieldMap["update_time"] = r.UpdateTime } func (r roomGroup) clone(db *gorm.DB) roomGroup { r.roomGroupDo.ReplaceConnPool(db.Statement.ConnPool) return r } func (r roomGroup) replaceDB(db *gorm.DB) roomGroup { r.roomGroupDo.ReplaceDB(db) return r } type roomGroupDo struct{ gen.DO } type IRoomGroupDo interface { gen.SubQuery Debug() IRoomGroupDo WithContext(ctx context.Context) IRoomGroupDo WithResult(fc func(tx gen.Dao)) gen.ResultInfo ReplaceDB(db *gorm.DB) ReadDB() IRoomGroupDo WriteDB() IRoomGroupDo As(alias string) gen.Dao Session(config *gorm.Session) IRoomGroupDo Columns(cols ...field.Expr) gen.Columns Clauses(conds ...clause.Expression) IRoomGroupDo Not(conds ...gen.Condition) IRoomGroupDo Or(conds ...gen.Condition) IRoomGroupDo Select(conds ...field.Expr) IRoomGroupDo Where(conds ...gen.Condition) IRoomGroupDo Order(conds ...field.Expr) IRoomGroupDo Distinct(cols ...field.Expr) IRoomGroupDo Omit(cols ...field.Expr) IRoomGroupDo Join(table schema.Tabler, on ...field.Expr) IRoomGroupDo LeftJoin(table schema.Tabler, on ...field.Expr) IRoomGroupDo RightJoin(table schema.Tabler, on ...field.Expr) IRoomGroupDo Group(cols ...field.Expr) IRoomGroupDo Having(conds ...gen.Condition) IRoomGroupDo Limit(limit int) IRoomGroupDo Offset(offset int) IRoomGroupDo Count() (count int64, err error) Scopes(funcs ...func(gen.Dao) gen.Dao) IRoomGroupDo Unscoped() IRoomGroupDo Create(values ...*model.RoomGroup) error CreateInBatches(values []*model.RoomGroup, batchSize int) error Save(values ...*model.RoomGroup) error First() (*model.RoomGroup, error) Take() (*model.RoomGroup, error) Last() (*model.RoomGroup, error) Find() ([]*model.RoomGroup, error) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.RoomGroup, err error) FindInBatches(result *[]*model.RoomGroup, batchSize int, fc func(tx gen.Dao, batch int) error) error Pluck(column field.Expr, dest interface{}) error Delete(...*model.RoomGroup) (info gen.ResultInfo, err error) Update(column field.Expr, value interface{}) (info gen.ResultInfo, err error) UpdateSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error) Updates(value interface{}) (info gen.ResultInfo, err error) UpdateColumn(column field.Expr, value interface{}) (info gen.ResultInfo, err error) UpdateColumnSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error) UpdateColumns(value interface{}) (info gen.ResultInfo, err error) UpdateFrom(q gen.SubQuery) gen.Dao Attrs(attrs ...field.AssignExpr) IRoomGroupDo Assign(attrs ...field.AssignExpr) IRoomGroupDo Joins(fields ...field.RelationField) IRoomGroupDo Preload(fields ...field.RelationField) IRoomGroupDo FirstOrInit() (*model.RoomGroup, error) FirstOrCreate() (*model.RoomGroup, error) FindByPage(offset int, limit int) (result []*model.RoomGroup, count int64, err error) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) Scan(result interface{}) (err error) Returning(value interface{}, columns ...string) IRoomGroupDo UnderlyingDB() *gorm.DB schema.Tabler } func (r roomGroupDo) Debug() IRoomGroupDo { return r.withDO(r.DO.Debug()) } func (r roomGroupDo) WithContext(ctx context.Context) IRoomGroupDo { return r.withDO(r.DO.WithContext(ctx)) } func (r roomGroupDo) ReadDB() IRoomGroupDo { return r.Clauses(dbresolver.Read) } func (r roomGroupDo) WriteDB() IRoomGroupDo { return r.Clauses(dbresolver.Write) } func (r roomGroupDo) Session(config *gorm.Session) IRoomGroupDo { return r.withDO(r.DO.Session(config)) } func (r roomGroupDo) Clauses(conds ...clause.Expression) IRoomGroupDo { return r.withDO(r.DO.Clauses(conds...)) } func (r roomGroupDo) Returning(value interface{}, columns ...string) IRoomGroupDo { return r.withDO(r.DO.Returning(value, columns...)) } func (r roomGroupDo) Not(conds ...gen.Condition) IRoomGroupDo { return r.withDO(r.DO.Not(conds...)) } func (r roomGroupDo) Or(conds ...gen.Condition) IRoomGroupDo { return r.withDO(r.DO.Or(conds...)) } func (r roomGroupDo) Select(conds ...field.Expr) IRoomGroupDo { return r.withDO(r.DO.Select(conds...)) } func (r roomGroupDo) Where(conds ...gen.Condition) IRoomGroupDo { return r.withDO(r.DO.Where(conds...)) } func (r roomGroupDo) Order(conds ...field.Expr) IRoomGroupDo { return r.withDO(r.DO.Order(conds...)) } func (r roomGroupDo) Distinct(cols ...field.Expr) IRoomGroupDo { return r.withDO(r.DO.Distinct(cols...)) } func (r roomGroupDo) Omit(cols ...field.Expr) IRoomGroupDo { return r.withDO(r.DO.Omit(cols...)) } func (r roomGroupDo) Join(table schema.Tabler, on ...field.Expr) IRoomGroupDo { return r.withDO(r.DO.Join(table, on...)) } func (r roomGroupDo) LeftJoin(table schema.Tabler, on ...field.Expr) IRoomGroupDo { return r.withDO(r.DO.LeftJoin(table, on...)) } func (r roomGroupDo) RightJoin(table schema.Tabler, on ...field.Expr) IRoomGroupDo { return r.withDO(r.DO.RightJoin(table, on...)) } func (r roomGroupDo) Group(cols ...field.Expr) IRoomGroupDo { return r.withDO(r.DO.Group(cols...)) } func (r roomGroupDo) Having(conds ...gen.Condition) IRoomGroupDo { return r.withDO(r.DO.Having(conds...)) } func (r roomGroupDo) Limit(limit int) IRoomGroupDo { return r.withDO(r.DO.Limit(limit)) } func (r roomGroupDo) Offset(offset int) IRoomGroupDo { return r.withDO(r.DO.Offset(offset)) } func (r roomGroupDo) Scopes(funcs ...func(gen.Dao) gen.Dao) IRoomGroupDo { return r.withDO(r.DO.Scopes(funcs...)) } func (r roomGroupDo) Unscoped() IRoomGroupDo { return r.withDO(r.DO.Unscoped()) } func (r roomGroupDo) Create(values ...*model.RoomGroup) error { if len(values) == 0 { return nil } return r.DO.Create(values) } func (r roomGroupDo) CreateInBatches(values []*model.RoomGroup, batchSize int) error { return r.DO.CreateInBatches(values, batchSize) } // Save : !!! underlying implementation is different with GORM // The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values) func (r roomGroupDo) Save(values ...*model.RoomGroup) error { if len(values) == 0 { return nil } return r.DO.Save(values) } func (r roomGroupDo) First() (*model.RoomGroup, error) { if result, err := r.DO.First(); err != nil { return nil, err } else { return result.(*model.RoomGroup), nil } } func (r roomGroupDo) Take() (*model.RoomGroup, error) { if result, err := r.DO.Take(); err != nil { return nil, err } else { return result.(*model.RoomGroup), nil } } func (r roomGroupDo) Last() (*model.RoomGroup, error) { if result, err := r.DO.Last(); err != nil { return nil, err } else { return result.(*model.RoomGroup), nil } } func (r roomGroupDo) Find() ([]*model.RoomGroup, error) { result, err := r.DO.Find() return result.([]*model.RoomGroup), err } func (r roomGroupDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.RoomGroup, err error) { buf := make([]*model.RoomGroup, 0, batchSize) err = r.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error { defer func() { results = append(results, buf...) }() return fc(tx, batch) }) return results, err } func (r roomGroupDo) FindInBatches(result *[]*model.RoomGroup, batchSize int, fc func(tx gen.Dao, batch int) error) error { return r.DO.FindInBatches(result, batchSize, fc) } func (r roomGroupDo) Attrs(attrs ...field.AssignExpr) IRoomGroupDo { return r.withDO(r.DO.Attrs(attrs...)) } func (r roomGroupDo) Assign(attrs ...field.AssignExpr) IRoomGroupDo { return r.withDO(r.DO.Assign(attrs...)) } func (r roomGroupDo) Joins(fields ...field.RelationField) IRoomGroupDo { for _, _f := range fields { r = *r.withDO(r.DO.Joins(_f)) } return &r } func (r roomGroupDo) Preload(fields ...field.RelationField) IRoomGroupDo { for _, _f := range fields { r = *r.withDO(r.DO.Preload(_f)) } return &r } func (r roomGroupDo) FirstOrInit() (*model.RoomGroup, error) { if result, err := r.DO.FirstOrInit(); err != nil { return nil, err } else { return result.(*model.RoomGroup), nil } } func (r roomGroupDo) FirstOrCreate() (*model.RoomGroup, error) { if result, err := r.DO.FirstOrCreate(); err != nil { return nil, err } else { return result.(*model.RoomGroup), nil } } func (r roomGroupDo) FindByPage(offset int, limit int) (result []*model.RoomGroup, count int64, err error) { result, err = r.Offset(offset).Limit(limit).Find() if err != nil { return } if size := len(result); 0 < limit && 0 < size && size < limit { count = int64(size + offset) return } count, err = r.Offset(-1).Limit(-1).Count() return } func (r roomGroupDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) { count, err = r.Count() if err != nil { return } err = r.Offset(offset).Limit(limit).Scan(result) return } func (r roomGroupDo) Scan(result interface{}) (err error) { return r.DO.Scan(result) } func (r roomGroupDo) Delete(models ...*model.RoomGroup) (result gen.ResultInfo, err error) { return r.DO.Delete(models) } func (r *roomGroupDo) withDO(do gen.Dao) *roomGroupDo { r.DO = *do.(*gen.DO) return r } ================================================ FILE: dal/query/user.gen.go ================================================ // Code generated by gorm.io/gen. DO NOT EDIT. // Code generated by gorm.io/gen. DO NOT EDIT. // Code generated by gorm.io/gen. DO NOT EDIT. package query import ( "context" "gorm.io/gorm" "gorm.io/gorm/clause" "gorm.io/gorm/schema" "gorm.io/gen" "gorm.io/gen/field" "gorm.io/plugin/dbresolver" "DiTing-Go/dal/model" ) func newUser(db *gorm.DB, opts ...gen.DOOption) user { _user := user{} _user.userDo.UseDB(db, opts...) _user.userDo.UseModel(&model.User{}) tableName := _user.userDo.TableName() _user.ALL = field.NewAsterisk(tableName) _user.ID = field.NewInt64(tableName, "id") _user.Phone = field.NewString(tableName, "phone") _user.Password = field.NewString(tableName, "password") _user.Name = field.NewString(tableName, "name") _user.Avatar = field.NewString(tableName, "avatar") _user.Sex = field.NewInt32(tableName, "sex") _user.ActiveStatus = field.NewInt32(tableName, "active_status") _user.LastOptTime = field.NewTime(tableName, "last_opt_time") _user.IPInfo = field.NewString(tableName, "ip_info") _user.ItemID = field.NewInt64(tableName, "item_id") _user.Status = field.NewInt32(tableName, "status") _user.CreateTime = field.NewTime(tableName, "create_time") _user.UpdateTime = field.NewTime(tableName, "update_time") _user.fillFieldMap() return _user } // user 用户表 type user struct { userDo userDo ALL field.Asterisk ID field.Int64 // 用户id Phone field.String // 用户手机 Password field.String // 用户密码 Name field.String // 用户昵称 Avatar field.String // 用户头像 Sex field.Int32 // 性别 1为男性,2为女性,3未知 ActiveStatus field.Int32 // 在线状态 1在线 2离线 LastOptTime field.Time // 最后上下线时间 IPInfo field.String // ip信息,用于显示用户地理位置 ItemID field.Int64 // 佩戴的徽章id Status field.Int32 // 使用状态 1.正常 2禁用 CreateTime field.Time // 创建时间 UpdateTime field.Time // 修改时间 fieldMap map[string]field.Expr } func (u user) Table(newTableName string) *user { u.userDo.UseTable(newTableName) return u.updateTableName(newTableName) } func (u user) As(alias string) *user { u.userDo.DO = *(u.userDo.As(alias).(*gen.DO)) return u.updateTableName(alias) } func (u *user) updateTableName(table string) *user { u.ALL = field.NewAsterisk(table) u.ID = field.NewInt64(table, "id") u.Phone = field.NewString(table, "phone") u.Password = field.NewString(table, "password") u.Name = field.NewString(table, "name") u.Avatar = field.NewString(table, "avatar") u.Sex = field.NewInt32(table, "sex") u.ActiveStatus = field.NewInt32(table, "active_status") u.LastOptTime = field.NewTime(table, "last_opt_time") u.IPInfo = field.NewString(table, "ip_info") u.ItemID = field.NewInt64(table, "item_id") u.Status = field.NewInt32(table, "status") u.CreateTime = field.NewTime(table, "create_time") u.UpdateTime = field.NewTime(table, "update_time") u.fillFieldMap() return u } func (u *user) WithContext(ctx context.Context) IUserDo { return u.userDo.WithContext(ctx) } func (u user) TableName() string { return u.userDo.TableName() } func (u user) Alias() string { return u.userDo.Alias() } func (u user) Columns(cols ...field.Expr) gen.Columns { return u.userDo.Columns(cols...) } func (u *user) GetFieldByName(fieldName string) (field.OrderExpr, bool) { _f, ok := u.fieldMap[fieldName] if !ok || _f == nil { return nil, false } _oe, ok := _f.(field.OrderExpr) return _oe, ok } func (u *user) fillFieldMap() { u.fieldMap = make(map[string]field.Expr, 13) u.fieldMap["id"] = u.ID u.fieldMap["phone"] = u.Phone u.fieldMap["password"] = u.Password u.fieldMap["name"] = u.Name u.fieldMap["avatar"] = u.Avatar u.fieldMap["sex"] = u.Sex u.fieldMap["active_status"] = u.ActiveStatus u.fieldMap["last_opt_time"] = u.LastOptTime u.fieldMap["ip_info"] = u.IPInfo u.fieldMap["item_id"] = u.ItemID u.fieldMap["status"] = u.Status u.fieldMap["create_time"] = u.CreateTime u.fieldMap["update_time"] = u.UpdateTime } func (u user) clone(db *gorm.DB) user { u.userDo.ReplaceConnPool(db.Statement.ConnPool) return u } func (u user) replaceDB(db *gorm.DB) user { u.userDo.ReplaceDB(db) return u } type userDo struct{ gen.DO } type IUserDo interface { gen.SubQuery Debug() IUserDo WithContext(ctx context.Context) IUserDo WithResult(fc func(tx gen.Dao)) gen.ResultInfo ReplaceDB(db *gorm.DB) ReadDB() IUserDo WriteDB() IUserDo As(alias string) gen.Dao Session(config *gorm.Session) IUserDo Columns(cols ...field.Expr) gen.Columns Clauses(conds ...clause.Expression) IUserDo Not(conds ...gen.Condition) IUserDo Or(conds ...gen.Condition) IUserDo Select(conds ...field.Expr) IUserDo Where(conds ...gen.Condition) IUserDo Order(conds ...field.Expr) IUserDo Distinct(cols ...field.Expr) IUserDo Omit(cols ...field.Expr) IUserDo Join(table schema.Tabler, on ...field.Expr) IUserDo LeftJoin(table schema.Tabler, on ...field.Expr) IUserDo RightJoin(table schema.Tabler, on ...field.Expr) IUserDo Group(cols ...field.Expr) IUserDo Having(conds ...gen.Condition) IUserDo Limit(limit int) IUserDo Offset(offset int) IUserDo Count() (count int64, err error) Scopes(funcs ...func(gen.Dao) gen.Dao) IUserDo Unscoped() IUserDo Create(values ...*model.User) error CreateInBatches(values []*model.User, batchSize int) error Save(values ...*model.User) error First() (*model.User, error) Take() (*model.User, error) Last() (*model.User, error) Find() ([]*model.User, error) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.User, err error) FindInBatches(result *[]*model.User, batchSize int, fc func(tx gen.Dao, batch int) error) error Pluck(column field.Expr, dest interface{}) error Delete(...*model.User) (info gen.ResultInfo, err error) Update(column field.Expr, value interface{}) (info gen.ResultInfo, err error) UpdateSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error) Updates(value interface{}) (info gen.ResultInfo, err error) UpdateColumn(column field.Expr, value interface{}) (info gen.ResultInfo, err error) UpdateColumnSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error) UpdateColumns(value interface{}) (info gen.ResultInfo, err error) UpdateFrom(q gen.SubQuery) gen.Dao Attrs(attrs ...field.AssignExpr) IUserDo Assign(attrs ...field.AssignExpr) IUserDo Joins(fields ...field.RelationField) IUserDo Preload(fields ...field.RelationField) IUserDo FirstOrInit() (*model.User, error) FirstOrCreate() (*model.User, error) FindByPage(offset int, limit int) (result []*model.User, count int64, err error) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) Scan(result interface{}) (err error) Returning(value interface{}, columns ...string) IUserDo UnderlyingDB() *gorm.DB schema.Tabler } func (u userDo) Debug() IUserDo { return u.withDO(u.DO.Debug()) } func (u userDo) WithContext(ctx context.Context) IUserDo { return u.withDO(u.DO.WithContext(ctx)) } func (u userDo) ReadDB() IUserDo { return u.Clauses(dbresolver.Read) } func (u userDo) WriteDB() IUserDo { return u.Clauses(dbresolver.Write) } func (u userDo) Session(config *gorm.Session) IUserDo { return u.withDO(u.DO.Session(config)) } func (u userDo) Clauses(conds ...clause.Expression) IUserDo { return u.withDO(u.DO.Clauses(conds...)) } func (u userDo) Returning(value interface{}, columns ...string) IUserDo { return u.withDO(u.DO.Returning(value, columns...)) } func (u userDo) Not(conds ...gen.Condition) IUserDo { return u.withDO(u.DO.Not(conds...)) } func (u userDo) Or(conds ...gen.Condition) IUserDo { return u.withDO(u.DO.Or(conds...)) } func (u userDo) Select(conds ...field.Expr) IUserDo { return u.withDO(u.DO.Select(conds...)) } func (u userDo) Where(conds ...gen.Condition) IUserDo { return u.withDO(u.DO.Where(conds...)) } func (u userDo) Order(conds ...field.Expr) IUserDo { return u.withDO(u.DO.Order(conds...)) } func (u userDo) Distinct(cols ...field.Expr) IUserDo { return u.withDO(u.DO.Distinct(cols...)) } func (u userDo) Omit(cols ...field.Expr) IUserDo { return u.withDO(u.DO.Omit(cols...)) } func (u userDo) Join(table schema.Tabler, on ...field.Expr) IUserDo { return u.withDO(u.DO.Join(table, on...)) } func (u userDo) LeftJoin(table schema.Tabler, on ...field.Expr) IUserDo { return u.withDO(u.DO.LeftJoin(table, on...)) } func (u userDo) RightJoin(table schema.Tabler, on ...field.Expr) IUserDo { return u.withDO(u.DO.RightJoin(table, on...)) } func (u userDo) Group(cols ...field.Expr) IUserDo { return u.withDO(u.DO.Group(cols...)) } func (u userDo) Having(conds ...gen.Condition) IUserDo { return u.withDO(u.DO.Having(conds...)) } func (u userDo) Limit(limit int) IUserDo { return u.withDO(u.DO.Limit(limit)) } func (u userDo) Offset(offset int) IUserDo { return u.withDO(u.DO.Offset(offset)) } func (u userDo) Scopes(funcs ...func(gen.Dao) gen.Dao) IUserDo { return u.withDO(u.DO.Scopes(funcs...)) } func (u userDo) Unscoped() IUserDo { return u.withDO(u.DO.Unscoped()) } func (u userDo) Create(values ...*model.User) error { if len(values) == 0 { return nil } return u.DO.Create(values) } func (u userDo) CreateInBatches(values []*model.User, batchSize int) error { return u.DO.CreateInBatches(values, batchSize) } // Save : !!! underlying implementation is different with GORM // The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values) func (u userDo) Save(values ...*model.User) error { if len(values) == 0 { return nil } return u.DO.Save(values) } func (u userDo) First() (*model.User, error) { if result, err := u.DO.First(); err != nil { return nil, err } else { return result.(*model.User), nil } } func (u userDo) Take() (*model.User, error) { if result, err := u.DO.Take(); err != nil { return nil, err } else { return result.(*model.User), nil } } func (u userDo) Last() (*model.User, error) { if result, err := u.DO.Last(); err != nil { return nil, err } else { return result.(*model.User), nil } } func (u userDo) Find() ([]*model.User, error) { result, err := u.DO.Find() return result.([]*model.User), err } func (u userDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.User, err error) { buf := make([]*model.User, 0, batchSize) err = u.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error { defer func() { results = append(results, buf...) }() return fc(tx, batch) }) return results, err } func (u userDo) FindInBatches(result *[]*model.User, batchSize int, fc func(tx gen.Dao, batch int) error) error { return u.DO.FindInBatches(result, batchSize, fc) } func (u userDo) Attrs(attrs ...field.AssignExpr) IUserDo { return u.withDO(u.DO.Attrs(attrs...)) } func (u userDo) Assign(attrs ...field.AssignExpr) IUserDo { return u.withDO(u.DO.Assign(attrs...)) } func (u userDo) Joins(fields ...field.RelationField) IUserDo { for _, _f := range fields { u = *u.withDO(u.DO.Joins(_f)) } return &u } func (u userDo) Preload(fields ...field.RelationField) IUserDo { for _, _f := range fields { u = *u.withDO(u.DO.Preload(_f)) } return &u } func (u userDo) FirstOrInit() (*model.User, error) { if result, err := u.DO.FirstOrInit(); err != nil { return nil, err } else { return result.(*model.User), nil } } func (u userDo) FirstOrCreate() (*model.User, error) { if result, err := u.DO.FirstOrCreate(); err != nil { return nil, err } else { return result.(*model.User), nil } } func (u userDo) FindByPage(offset int, limit int) (result []*model.User, count int64, err error) { result, err = u.Offset(offset).Limit(limit).Find() if err != nil { return } if size := len(result); 0 < limit && 0 < size && size < limit { count = int64(size + offset) return } count, err = u.Offset(-1).Limit(-1).Count() return } func (u userDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) { count, err = u.Count() if err != nil { return } err = u.Offset(offset).Limit(limit).Scan(result) return } func (u userDo) Scan(result interface{}) (err error) { return u.DO.Scan(result) } func (u userDo) Delete(models ...*model.User) (result gen.ResultInfo, err error) { return u.DO.Delete(models) } func (u *userDo) withDO(do gen.Dao) *userDo { u.DO = *do.(*gen.DO) return u } ================================================ FILE: dal/query/user_apply.gen.go ================================================ // Code generated by gorm.io/gen. DO NOT EDIT. // Code generated by gorm.io/gen. DO NOT EDIT. // Code generated by gorm.io/gen. DO NOT EDIT. package query import ( "context" "gorm.io/gorm" "gorm.io/gorm/clause" "gorm.io/gorm/schema" "gorm.io/gen" "gorm.io/gen/field" "gorm.io/plugin/dbresolver" "DiTing-Go/dal/model" ) func newUserApply(db *gorm.DB, opts ...gen.DOOption) userApply { _userApply := userApply{} _userApply.userApplyDo.UseDB(db, opts...) _userApply.userApplyDo.UseModel(&model.UserApply{}) tableName := _userApply.userApplyDo.TableName() _userApply.ALL = field.NewAsterisk(tableName) _userApply.ID = field.NewInt64(tableName, "id") _userApply.UID = field.NewInt64(tableName, "uid") _userApply.Type = field.NewInt32(tableName, "type") _userApply.TargetID = field.NewInt64(tableName, "target_id") _userApply.Msg = field.NewString(tableName, "msg") _userApply.Status = field.NewInt32(tableName, "status") _userApply.ReadStatus = field.NewInt32(tableName, "read_status") _userApply.CreateTime = field.NewTime(tableName, "create_time") _userApply.UpdateTime = field.NewTime(tableName, "update_time") _userApply.fillFieldMap() return _userApply } // userApply 用户申请表 type userApply struct { userApplyDo userApplyDo ALL field.Asterisk ID field.Int64 // id UID field.Int64 // 申请人uid Type field.Int32 // 申请类型 1加好友 TargetID field.Int64 // 接收人uid Msg field.String // 申请信息 Status field.Int32 // 申请状态 1待审批 2同意 ReadStatus field.Int32 // 阅读状态 1未读 2已读 CreateTime field.Time // 创建时间 UpdateTime field.Time // 修改时间 fieldMap map[string]field.Expr } func (u userApply) Table(newTableName string) *userApply { u.userApplyDo.UseTable(newTableName) return u.updateTableName(newTableName) } func (u userApply) As(alias string) *userApply { u.userApplyDo.DO = *(u.userApplyDo.As(alias).(*gen.DO)) return u.updateTableName(alias) } func (u *userApply) updateTableName(table string) *userApply { u.ALL = field.NewAsterisk(table) u.ID = field.NewInt64(table, "id") u.UID = field.NewInt64(table, "uid") u.Type = field.NewInt32(table, "type") u.TargetID = field.NewInt64(table, "target_id") u.Msg = field.NewString(table, "msg") u.Status = field.NewInt32(table, "status") u.ReadStatus = field.NewInt32(table, "read_status") u.CreateTime = field.NewTime(table, "create_time") u.UpdateTime = field.NewTime(table, "update_time") u.fillFieldMap() return u } func (u *userApply) WithContext(ctx context.Context) IUserApplyDo { return u.userApplyDo.WithContext(ctx) } func (u userApply) TableName() string { return u.userApplyDo.TableName() } func (u userApply) Alias() string { return u.userApplyDo.Alias() } func (u userApply) Columns(cols ...field.Expr) gen.Columns { return u.userApplyDo.Columns(cols...) } func (u *userApply) GetFieldByName(fieldName string) (field.OrderExpr, bool) { _f, ok := u.fieldMap[fieldName] if !ok || _f == nil { return nil, false } _oe, ok := _f.(field.OrderExpr) return _oe, ok } func (u *userApply) fillFieldMap() { u.fieldMap = make(map[string]field.Expr, 9) u.fieldMap["id"] = u.ID u.fieldMap["uid"] = u.UID u.fieldMap["type"] = u.Type u.fieldMap["target_id"] = u.TargetID u.fieldMap["msg"] = u.Msg u.fieldMap["status"] = u.Status u.fieldMap["read_status"] = u.ReadStatus u.fieldMap["create_time"] = u.CreateTime u.fieldMap["update_time"] = u.UpdateTime } func (u userApply) clone(db *gorm.DB) userApply { u.userApplyDo.ReplaceConnPool(db.Statement.ConnPool) return u } func (u userApply) replaceDB(db *gorm.DB) userApply { u.userApplyDo.ReplaceDB(db) return u } type userApplyDo struct{ gen.DO } type IUserApplyDo interface { gen.SubQuery Debug() IUserApplyDo WithContext(ctx context.Context) IUserApplyDo WithResult(fc func(tx gen.Dao)) gen.ResultInfo ReplaceDB(db *gorm.DB) ReadDB() IUserApplyDo WriteDB() IUserApplyDo As(alias string) gen.Dao Session(config *gorm.Session) IUserApplyDo Columns(cols ...field.Expr) gen.Columns Clauses(conds ...clause.Expression) IUserApplyDo Not(conds ...gen.Condition) IUserApplyDo Or(conds ...gen.Condition) IUserApplyDo Select(conds ...field.Expr) IUserApplyDo Where(conds ...gen.Condition) IUserApplyDo Order(conds ...field.Expr) IUserApplyDo Distinct(cols ...field.Expr) IUserApplyDo Omit(cols ...field.Expr) IUserApplyDo Join(table schema.Tabler, on ...field.Expr) IUserApplyDo LeftJoin(table schema.Tabler, on ...field.Expr) IUserApplyDo RightJoin(table schema.Tabler, on ...field.Expr) IUserApplyDo Group(cols ...field.Expr) IUserApplyDo Having(conds ...gen.Condition) IUserApplyDo Limit(limit int) IUserApplyDo Offset(offset int) IUserApplyDo Count() (count int64, err error) Scopes(funcs ...func(gen.Dao) gen.Dao) IUserApplyDo Unscoped() IUserApplyDo Create(values ...*model.UserApply) error CreateInBatches(values []*model.UserApply, batchSize int) error Save(values ...*model.UserApply) error First() (*model.UserApply, error) Take() (*model.UserApply, error) Last() (*model.UserApply, error) Find() ([]*model.UserApply, error) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.UserApply, err error) FindInBatches(result *[]*model.UserApply, batchSize int, fc func(tx gen.Dao, batch int) error) error Pluck(column field.Expr, dest interface{}) error Delete(...*model.UserApply) (info gen.ResultInfo, err error) Update(column field.Expr, value interface{}) (info gen.ResultInfo, err error) UpdateSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error) Updates(value interface{}) (info gen.ResultInfo, err error) UpdateColumn(column field.Expr, value interface{}) (info gen.ResultInfo, err error) UpdateColumnSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error) UpdateColumns(value interface{}) (info gen.ResultInfo, err error) UpdateFrom(q gen.SubQuery) gen.Dao Attrs(attrs ...field.AssignExpr) IUserApplyDo Assign(attrs ...field.AssignExpr) IUserApplyDo Joins(fields ...field.RelationField) IUserApplyDo Preload(fields ...field.RelationField) IUserApplyDo FirstOrInit() (*model.UserApply, error) FirstOrCreate() (*model.UserApply, error) FindByPage(offset int, limit int) (result []*model.UserApply, count int64, err error) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) Scan(result interface{}) (err error) Returning(value interface{}, columns ...string) IUserApplyDo UnderlyingDB() *gorm.DB schema.Tabler } func (u userApplyDo) Debug() IUserApplyDo { return u.withDO(u.DO.Debug()) } func (u userApplyDo) WithContext(ctx context.Context) IUserApplyDo { return u.withDO(u.DO.WithContext(ctx)) } func (u userApplyDo) ReadDB() IUserApplyDo { return u.Clauses(dbresolver.Read) } func (u userApplyDo) WriteDB() IUserApplyDo { return u.Clauses(dbresolver.Write) } func (u userApplyDo) Session(config *gorm.Session) IUserApplyDo { return u.withDO(u.DO.Session(config)) } func (u userApplyDo) Clauses(conds ...clause.Expression) IUserApplyDo { return u.withDO(u.DO.Clauses(conds...)) } func (u userApplyDo) Returning(value interface{}, columns ...string) IUserApplyDo { return u.withDO(u.DO.Returning(value, columns...)) } func (u userApplyDo) Not(conds ...gen.Condition) IUserApplyDo { return u.withDO(u.DO.Not(conds...)) } func (u userApplyDo) Or(conds ...gen.Condition) IUserApplyDo { return u.withDO(u.DO.Or(conds...)) } func (u userApplyDo) Select(conds ...field.Expr) IUserApplyDo { return u.withDO(u.DO.Select(conds...)) } func (u userApplyDo) Where(conds ...gen.Condition) IUserApplyDo { return u.withDO(u.DO.Where(conds...)) } func (u userApplyDo) Order(conds ...field.Expr) IUserApplyDo { return u.withDO(u.DO.Order(conds...)) } func (u userApplyDo) Distinct(cols ...field.Expr) IUserApplyDo { return u.withDO(u.DO.Distinct(cols...)) } func (u userApplyDo) Omit(cols ...field.Expr) IUserApplyDo { return u.withDO(u.DO.Omit(cols...)) } func (u userApplyDo) Join(table schema.Tabler, on ...field.Expr) IUserApplyDo { return u.withDO(u.DO.Join(table, on...)) } func (u userApplyDo) LeftJoin(table schema.Tabler, on ...field.Expr) IUserApplyDo { return u.withDO(u.DO.LeftJoin(table, on...)) } func (u userApplyDo) RightJoin(table schema.Tabler, on ...field.Expr) IUserApplyDo { return u.withDO(u.DO.RightJoin(table, on...)) } func (u userApplyDo) Group(cols ...field.Expr) IUserApplyDo { return u.withDO(u.DO.Group(cols...)) } func (u userApplyDo) Having(conds ...gen.Condition) IUserApplyDo { return u.withDO(u.DO.Having(conds...)) } func (u userApplyDo) Limit(limit int) IUserApplyDo { return u.withDO(u.DO.Limit(limit)) } func (u userApplyDo) Offset(offset int) IUserApplyDo { return u.withDO(u.DO.Offset(offset)) } func (u userApplyDo) Scopes(funcs ...func(gen.Dao) gen.Dao) IUserApplyDo { return u.withDO(u.DO.Scopes(funcs...)) } func (u userApplyDo) Unscoped() IUserApplyDo { return u.withDO(u.DO.Unscoped()) } func (u userApplyDo) Create(values ...*model.UserApply) error { if len(values) == 0 { return nil } return u.DO.Create(values) } func (u userApplyDo) CreateInBatches(values []*model.UserApply, batchSize int) error { return u.DO.CreateInBatches(values, batchSize) } // Save : !!! underlying implementation is different with GORM // The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values) func (u userApplyDo) Save(values ...*model.UserApply) error { if len(values) == 0 { return nil } return u.DO.Save(values) } func (u userApplyDo) First() (*model.UserApply, error) { if result, err := u.DO.First(); err != nil { return nil, err } else { return result.(*model.UserApply), nil } } func (u userApplyDo) Take() (*model.UserApply, error) { if result, err := u.DO.Take(); err != nil { return nil, err } else { return result.(*model.UserApply), nil } } func (u userApplyDo) Last() (*model.UserApply, error) { if result, err := u.DO.Last(); err != nil { return nil, err } else { return result.(*model.UserApply), nil } } func (u userApplyDo) Find() ([]*model.UserApply, error) { result, err := u.DO.Find() return result.([]*model.UserApply), err } func (u userApplyDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.UserApply, err error) { buf := make([]*model.UserApply, 0, batchSize) err = u.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error { defer func() { results = append(results, buf...) }() return fc(tx, batch) }) return results, err } func (u userApplyDo) FindInBatches(result *[]*model.UserApply, batchSize int, fc func(tx gen.Dao, batch int) error) error { return u.DO.FindInBatches(result, batchSize, fc) } func (u userApplyDo) Attrs(attrs ...field.AssignExpr) IUserApplyDo { return u.withDO(u.DO.Attrs(attrs...)) } func (u userApplyDo) Assign(attrs ...field.AssignExpr) IUserApplyDo { return u.withDO(u.DO.Assign(attrs...)) } func (u userApplyDo) Joins(fields ...field.RelationField) IUserApplyDo { for _, _f := range fields { u = *u.withDO(u.DO.Joins(_f)) } return &u } func (u userApplyDo) Preload(fields ...field.RelationField) IUserApplyDo { for _, _f := range fields { u = *u.withDO(u.DO.Preload(_f)) } return &u } func (u userApplyDo) FirstOrInit() (*model.UserApply, error) { if result, err := u.DO.FirstOrInit(); err != nil { return nil, err } else { return result.(*model.UserApply), nil } } func (u userApplyDo) FirstOrCreate() (*model.UserApply, error) { if result, err := u.DO.FirstOrCreate(); err != nil { return nil, err } else { return result.(*model.UserApply), nil } } func (u userApplyDo) FindByPage(offset int, limit int) (result []*model.UserApply, count int64, err error) { result, err = u.Offset(offset).Limit(limit).Find() if err != nil { return } if size := len(result); 0 < limit && 0 < size && size < limit { count = int64(size + offset) return } count, err = u.Offset(-1).Limit(-1).Count() return } func (u userApplyDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) { count, err = u.Count() if err != nil { return } err = u.Offset(offset).Limit(limit).Scan(result) return } func (u userApplyDo) Scan(result interface{}) (err error) { return u.DO.Scan(result) } func (u userApplyDo) Delete(models ...*model.UserApply) (result gen.ResultInfo, err error) { return u.DO.Delete(models) } func (u *userApplyDo) withDO(do gen.Dao) *userApplyDo { u.DO = *do.(*gen.DO) return u } ================================================ FILE: dal/query/user_friend.gen.go ================================================ // Code generated by gorm.io/gen. DO NOT EDIT. // Code generated by gorm.io/gen. DO NOT EDIT. // Code generated by gorm.io/gen. DO NOT EDIT. package query import ( "context" "gorm.io/gorm" "gorm.io/gorm/clause" "gorm.io/gorm/schema" "gorm.io/gen" "gorm.io/gen/field" "gorm.io/plugin/dbresolver" "DiTing-Go/dal/model" ) func newUserFriend(db *gorm.DB, opts ...gen.DOOption) userFriend { _userFriend := userFriend{} _userFriend.userFriendDo.UseDB(db, opts...) _userFriend.userFriendDo.UseModel(&model.UserFriend{}) tableName := _userFriend.userFriendDo.TableName() _userFriend.ALL = field.NewAsterisk(tableName) _userFriend.ID = field.NewInt64(tableName, "id") _userFriend.UID = field.NewInt64(tableName, "uid") _userFriend.FriendUID = field.NewInt64(tableName, "friend_uid") _userFriend.DeleteStatus = field.NewInt32(tableName, "delete_status") _userFriend.CreateTime = field.NewTime(tableName, "create_time") _userFriend.UpdateTime = field.NewTime(tableName, "update_time") _userFriend.fillFieldMap() return _userFriend } // userFriend 用户联系人表 type userFriend struct { userFriendDo userFriendDo ALL field.Asterisk ID field.Int64 // id UID field.Int64 // uid FriendUID field.Int64 // 好友uid DeleteStatus field.Int32 // 逻辑删除(0-正常,1-删除) CreateTime field.Time // 创建时间 UpdateTime field.Time // 修改时间 fieldMap map[string]field.Expr } func (u userFriend) Table(newTableName string) *userFriend { u.userFriendDo.UseTable(newTableName) return u.updateTableName(newTableName) } func (u userFriend) As(alias string) *userFriend { u.userFriendDo.DO = *(u.userFriendDo.As(alias).(*gen.DO)) return u.updateTableName(alias) } func (u *userFriend) updateTableName(table string) *userFriend { u.ALL = field.NewAsterisk(table) u.ID = field.NewInt64(table, "id") u.UID = field.NewInt64(table, "uid") u.FriendUID = field.NewInt64(table, "friend_uid") u.DeleteStatus = field.NewInt32(table, "delete_status") u.CreateTime = field.NewTime(table, "create_time") u.UpdateTime = field.NewTime(table, "update_time") u.fillFieldMap() return u } func (u *userFriend) WithContext(ctx context.Context) IUserFriendDo { return u.userFriendDo.WithContext(ctx) } func (u userFriend) TableName() string { return u.userFriendDo.TableName() } func (u userFriend) Alias() string { return u.userFriendDo.Alias() } func (u userFriend) Columns(cols ...field.Expr) gen.Columns { return u.userFriendDo.Columns(cols...) } func (u *userFriend) GetFieldByName(fieldName string) (field.OrderExpr, bool) { _f, ok := u.fieldMap[fieldName] if !ok || _f == nil { return nil, false } _oe, ok := _f.(field.OrderExpr) return _oe, ok } func (u *userFriend) fillFieldMap() { u.fieldMap = make(map[string]field.Expr, 6) u.fieldMap["id"] = u.ID u.fieldMap["uid"] = u.UID u.fieldMap["friend_uid"] = u.FriendUID u.fieldMap["delete_status"] = u.DeleteStatus u.fieldMap["create_time"] = u.CreateTime u.fieldMap["update_time"] = u.UpdateTime } func (u userFriend) clone(db *gorm.DB) userFriend { u.userFriendDo.ReplaceConnPool(db.Statement.ConnPool) return u } func (u userFriend) replaceDB(db *gorm.DB) userFriend { u.userFriendDo.ReplaceDB(db) return u } type userFriendDo struct{ gen.DO } type IUserFriendDo interface { gen.SubQuery Debug() IUserFriendDo WithContext(ctx context.Context) IUserFriendDo WithResult(fc func(tx gen.Dao)) gen.ResultInfo ReplaceDB(db *gorm.DB) ReadDB() IUserFriendDo WriteDB() IUserFriendDo As(alias string) gen.Dao Session(config *gorm.Session) IUserFriendDo Columns(cols ...field.Expr) gen.Columns Clauses(conds ...clause.Expression) IUserFriendDo Not(conds ...gen.Condition) IUserFriendDo Or(conds ...gen.Condition) IUserFriendDo Select(conds ...field.Expr) IUserFriendDo Where(conds ...gen.Condition) IUserFriendDo Order(conds ...field.Expr) IUserFriendDo Distinct(cols ...field.Expr) IUserFriendDo Omit(cols ...field.Expr) IUserFriendDo Join(table schema.Tabler, on ...field.Expr) IUserFriendDo LeftJoin(table schema.Tabler, on ...field.Expr) IUserFriendDo RightJoin(table schema.Tabler, on ...field.Expr) IUserFriendDo Group(cols ...field.Expr) IUserFriendDo Having(conds ...gen.Condition) IUserFriendDo Limit(limit int) IUserFriendDo Offset(offset int) IUserFriendDo Count() (count int64, err error) Scopes(funcs ...func(gen.Dao) gen.Dao) IUserFriendDo Unscoped() IUserFriendDo Create(values ...*model.UserFriend) error CreateInBatches(values []*model.UserFriend, batchSize int) error Save(values ...*model.UserFriend) error First() (*model.UserFriend, error) Take() (*model.UserFriend, error) Last() (*model.UserFriend, error) Find() ([]*model.UserFriend, error) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.UserFriend, err error) FindInBatches(result *[]*model.UserFriend, batchSize int, fc func(tx gen.Dao, batch int) error) error Pluck(column field.Expr, dest interface{}) error Delete(...*model.UserFriend) (info gen.ResultInfo, err error) Update(column field.Expr, value interface{}) (info gen.ResultInfo, err error) UpdateSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error) Updates(value interface{}) (info gen.ResultInfo, err error) UpdateColumn(column field.Expr, value interface{}) (info gen.ResultInfo, err error) UpdateColumnSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error) UpdateColumns(value interface{}) (info gen.ResultInfo, err error) UpdateFrom(q gen.SubQuery) gen.Dao Attrs(attrs ...field.AssignExpr) IUserFriendDo Assign(attrs ...field.AssignExpr) IUserFriendDo Joins(fields ...field.RelationField) IUserFriendDo Preload(fields ...field.RelationField) IUserFriendDo FirstOrInit() (*model.UserFriend, error) FirstOrCreate() (*model.UserFriend, error) FindByPage(offset int, limit int) (result []*model.UserFriend, count int64, err error) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) Scan(result interface{}) (err error) Returning(value interface{}, columns ...string) IUserFriendDo UnderlyingDB() *gorm.DB schema.Tabler } func (u userFriendDo) Debug() IUserFriendDo { return u.withDO(u.DO.Debug()) } func (u userFriendDo) WithContext(ctx context.Context) IUserFriendDo { return u.withDO(u.DO.WithContext(ctx)) } func (u userFriendDo) ReadDB() IUserFriendDo { return u.Clauses(dbresolver.Read) } func (u userFriendDo) WriteDB() IUserFriendDo { return u.Clauses(dbresolver.Write) } func (u userFriendDo) Session(config *gorm.Session) IUserFriendDo { return u.withDO(u.DO.Session(config)) } func (u userFriendDo) Clauses(conds ...clause.Expression) IUserFriendDo { return u.withDO(u.DO.Clauses(conds...)) } func (u userFriendDo) Returning(value interface{}, columns ...string) IUserFriendDo { return u.withDO(u.DO.Returning(value, columns...)) } func (u userFriendDo) Not(conds ...gen.Condition) IUserFriendDo { return u.withDO(u.DO.Not(conds...)) } func (u userFriendDo) Or(conds ...gen.Condition) IUserFriendDo { return u.withDO(u.DO.Or(conds...)) } func (u userFriendDo) Select(conds ...field.Expr) IUserFriendDo { return u.withDO(u.DO.Select(conds...)) } func (u userFriendDo) Where(conds ...gen.Condition) IUserFriendDo { return u.withDO(u.DO.Where(conds...)) } func (u userFriendDo) Order(conds ...field.Expr) IUserFriendDo { return u.withDO(u.DO.Order(conds...)) } func (u userFriendDo) Distinct(cols ...field.Expr) IUserFriendDo { return u.withDO(u.DO.Distinct(cols...)) } func (u userFriendDo) Omit(cols ...field.Expr) IUserFriendDo { return u.withDO(u.DO.Omit(cols...)) } func (u userFriendDo) Join(table schema.Tabler, on ...field.Expr) IUserFriendDo { return u.withDO(u.DO.Join(table, on...)) } func (u userFriendDo) LeftJoin(table schema.Tabler, on ...field.Expr) IUserFriendDo { return u.withDO(u.DO.LeftJoin(table, on...)) } func (u userFriendDo) RightJoin(table schema.Tabler, on ...field.Expr) IUserFriendDo { return u.withDO(u.DO.RightJoin(table, on...)) } func (u userFriendDo) Group(cols ...field.Expr) IUserFriendDo { return u.withDO(u.DO.Group(cols...)) } func (u userFriendDo) Having(conds ...gen.Condition) IUserFriendDo { return u.withDO(u.DO.Having(conds...)) } func (u userFriendDo) Limit(limit int) IUserFriendDo { return u.withDO(u.DO.Limit(limit)) } func (u userFriendDo) Offset(offset int) IUserFriendDo { return u.withDO(u.DO.Offset(offset)) } func (u userFriendDo) Scopes(funcs ...func(gen.Dao) gen.Dao) IUserFriendDo { return u.withDO(u.DO.Scopes(funcs...)) } func (u userFriendDo) Unscoped() IUserFriendDo { return u.withDO(u.DO.Unscoped()) } func (u userFriendDo) Create(values ...*model.UserFriend) error { if len(values) == 0 { return nil } return u.DO.Create(values) } func (u userFriendDo) CreateInBatches(values []*model.UserFriend, batchSize int) error { return u.DO.CreateInBatches(values, batchSize) } // Save : !!! underlying implementation is different with GORM // The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values) func (u userFriendDo) Save(values ...*model.UserFriend) error { if len(values) == 0 { return nil } return u.DO.Save(values) } func (u userFriendDo) First() (*model.UserFriend, error) { if result, err := u.DO.First(); err != nil { return nil, err } else { return result.(*model.UserFriend), nil } } func (u userFriendDo) Take() (*model.UserFriend, error) { if result, err := u.DO.Take(); err != nil { return nil, err } else { return result.(*model.UserFriend), nil } } func (u userFriendDo) Last() (*model.UserFriend, error) { if result, err := u.DO.Last(); err != nil { return nil, err } else { return result.(*model.UserFriend), nil } } func (u userFriendDo) Find() ([]*model.UserFriend, error) { result, err := u.DO.Find() return result.([]*model.UserFriend), err } func (u userFriendDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.UserFriend, err error) { buf := make([]*model.UserFriend, 0, batchSize) err = u.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error { defer func() { results = append(results, buf...) }() return fc(tx, batch) }) return results, err } func (u userFriendDo) FindInBatches(result *[]*model.UserFriend, batchSize int, fc func(tx gen.Dao, batch int) error) error { return u.DO.FindInBatches(result, batchSize, fc) } func (u userFriendDo) Attrs(attrs ...field.AssignExpr) IUserFriendDo { return u.withDO(u.DO.Attrs(attrs...)) } func (u userFriendDo) Assign(attrs ...field.AssignExpr) IUserFriendDo { return u.withDO(u.DO.Assign(attrs...)) } func (u userFriendDo) Joins(fields ...field.RelationField) IUserFriendDo { for _, _f := range fields { u = *u.withDO(u.DO.Joins(_f)) } return &u } func (u userFriendDo) Preload(fields ...field.RelationField) IUserFriendDo { for _, _f := range fields { u = *u.withDO(u.DO.Preload(_f)) } return &u } func (u userFriendDo) FirstOrInit() (*model.UserFriend, error) { if result, err := u.DO.FirstOrInit(); err != nil { return nil, err } else { return result.(*model.UserFriend), nil } } func (u userFriendDo) FirstOrCreate() (*model.UserFriend, error) { if result, err := u.DO.FirstOrCreate(); err != nil { return nil, err } else { return result.(*model.UserFriend), nil } } func (u userFriendDo) FindByPage(offset int, limit int) (result []*model.UserFriend, count int64, err error) { result, err = u.Offset(offset).Limit(limit).Find() if err != nil { return } if size := len(result); 0 < limit && 0 < size && size < limit { count = int64(size + offset) return } count, err = u.Offset(-1).Limit(-1).Count() return } func (u userFriendDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) { count, err = u.Count() if err != nil { return } err = u.Offset(offset).Limit(limit).Scan(result) return } func (u userFriendDo) Scan(result interface{}) (err error) { return u.DO.Scan(result) } func (u userFriendDo) Delete(models ...*model.UserFriend) (result gen.ResultInfo, err error) { return u.DO.Delete(models) } func (u *userFriendDo) withDO(do gen.Dao) *userFriendDo { u.DO = *do.(*gen.DO) return u } ================================================ FILE: docs/docs.go ================================================ // Code generated by swaggo/swag. DO NOT EDIT. package docs import "github.com/swaggo/swag" const docTemplate = `{ "schemes": {{ marshal .Schemes }}, "swagger": "2.0", "info": { "description": "{{escape .Description}}", "title": "{{.Title}}", "contact": {}, "version": "{{.Version}}" }, "host": "{{.Host}}", "basePath": "{{.BasePath}}", "paths": { "/api/contact/add": { "post": { "produces": [ "application/json" ], "summary": "添加好友", "parameters": [ { "description": "好友uid", "name": "uid", "in": "body", "required": true, "schema": { "type": "integer" } }, { "description": "验证消息", "name": "msg", "in": "body", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "成功", "schema": { "$ref": "#/definitions/resp.ResponseData" } }, "500": { "description": "内部错误", "schema": { "$ref": "#/definitions/resp.ResponseData" } } } } }, "/api/contact/delete": { "put": { "produces": [ "application/json" ], "summary": "同意好友申请", "parameters": [ { "description": "好友uid", "name": "uid", "in": "body", "required": true, "schema": { "type": "integer" } } ], "responses": { "200": { "description": "成功", "schema": { "$ref": "#/definitions/resp.ResponseData" } }, "500": { "description": "内部错误", "schema": { "$ref": "#/definitions/resp.ResponseData" } } } }, "delete": { "produces": [ "application/json" ], "summary": "删除好友", "parameters": [ { "description": "好友uid", "name": "uid", "in": "body", "required": true, "schema": { "type": "integer" } } ], "responses": { "200": { "description": "成功", "schema": { "$ref": "#/definitions/resp.ResponseData" } }, "500": { "description": "内部错误", "schema": { "$ref": "#/definitions/resp.ResponseData" } } } } }, "/api/contact/getApplyList": { "get": { "security": [ { "ApiKeyAuth": [] } ], "produces": [ "application/json" ], "summary": "获取用户好友申请列表", "parameters": [ { "type": "integer", "description": "last_id", "name": "last_id", "in": "query", "required": true }, { "type": "integer", "description": "limit", "name": "limit", "in": "query", "required": true } ], "responses": { "200": { "description": "成功", "schema": { "$ref": "#/definitions/resp.ResponseData" } }, "500": { "description": "内部错误", "schema": { "$ref": "#/definitions/resp.ResponseData" } } } } }, "/api/public/login": { "post": { "produces": [ "application/json" ], "summary": "用户登录", "parameters": [ { "description": "用户名", "name": "name", "in": "body", "required": true, "schema": { "type": "string" } }, { "description": "密码", "name": "password", "in": "body", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "成功", "schema": { "$ref": "#/definitions/resp.ResponseData" } }, "500": { "description": "内部错误", "schema": { "$ref": "#/definitions/resp.ResponseData" } } } } }, "/api/public/register": { "post": { "produces": [ "application/json" ], "summary": "用户注册", "parameters": [ { "description": "用户名", "name": "name", "in": "body", "required": true, "schema": { "type": "string" } }, { "description": "密码", "name": "password", "in": "body", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "成功", "schema": { "$ref": "#/definitions/resp.ResponseData" } }, "500": { "description": "内部错误", "schema": { "$ref": "#/definitions/resp.ResponseData" } } } } } }, "definitions": { "resp.ResponseData": { "type": "object", "properties": { "code": { "description": "状态码", "type": "integer" }, "data": { "description": "响应数据" }, "message": { "description": "响应消息", "type": "string" } } } }, "securityDefinitions": { "ApiKeyAuth": { "type": "apiKey", "name": "Authorization", "in": "header" } } }` // SwaggerInfo holds exported Swagger Info so clients can modify it var SwaggerInfo = &swag.Spec{ Version: "", Host: "", BasePath: "", Schemes: []string{}, Title: "", Description: "", InfoInstanceName: "swagger", SwaggerTemplate: docTemplate, LeftDelim: "{{", RightDelim: "}}", } func init() { swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) } ================================================ FILE: docs/swagger.json ================================================ { "swagger": "2.0", "info": { "contact": {} }, "paths": { "/api/contact/add": { "post": { "produces": [ "application/json" ], "summary": "添加好友", "parameters": [ { "description": "好友uid", "name": "uid", "in": "body", "required": true, "schema": { "type": "integer" } }, { "description": "验证消息", "name": "msg", "in": "body", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "成功", "schema": { "$ref": "#/definitions/resp.ResponseData" } }, "500": { "description": "内部错误", "schema": { "$ref": "#/definitions/resp.ResponseData" } } } } }, "/api/contact/delete": { "put": { "produces": [ "application/json" ], "summary": "同意好友申请", "parameters": [ { "description": "好友uid", "name": "uid", "in": "body", "required": true, "schema": { "type": "integer" } } ], "responses": { "200": { "description": "成功", "schema": { "$ref": "#/definitions/resp.ResponseData" } }, "500": { "description": "内部错误", "schema": { "$ref": "#/definitions/resp.ResponseData" } } } }, "delete": { "produces": [ "application/json" ], "summary": "删除好友", "parameters": [ { "description": "好友uid", "name": "uid", "in": "body", "required": true, "schema": { "type": "integer" } } ], "responses": { "200": { "description": "成功", "schema": { "$ref": "#/definitions/resp.ResponseData" } }, "500": { "description": "内部错误", "schema": { "$ref": "#/definitions/resp.ResponseData" } } } } }, "/api/contact/getApplyList": { "get": { "security": [ { "ApiKeyAuth": [] } ], "produces": [ "application/json" ], "summary": "获取用户好友申请列表", "parameters": [ { "type": "integer", "description": "last_id", "name": "last_id", "in": "query", "required": true }, { "type": "integer", "description": "limit", "name": "limit", "in": "query", "required": true } ], "responses": { "200": { "description": "成功", "schema": { "$ref": "#/definitions/resp.ResponseData" } }, "500": { "description": "内部错误", "schema": { "$ref": "#/definitions/resp.ResponseData" } } } } }, "/api/public/login": { "post": { "produces": [ "application/json" ], "summary": "用户登录", "parameters": [ { "description": "用户名", "name": "name", "in": "body", "required": true, "schema": { "type": "string" } }, { "description": "密码", "name": "password", "in": "body", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "成功", "schema": { "$ref": "#/definitions/resp.ResponseData" } }, "500": { "description": "内部错误", "schema": { "$ref": "#/definitions/resp.ResponseData" } } } } }, "/api/public/register": { "post": { "produces": [ "application/json" ], "summary": "用户注册", "parameters": [ { "description": "用户名", "name": "name", "in": "body", "required": true, "schema": { "type": "string" } }, { "description": "密码", "name": "password", "in": "body", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "成功", "schema": { "$ref": "#/definitions/resp.ResponseData" } }, "500": { "description": "内部错误", "schema": { "$ref": "#/definitions/resp.ResponseData" } } } } } }, "definitions": { "resp.ResponseData": { "type": "object", "properties": { "code": { "description": "状态码", "type": "integer" }, "data": { "description": "响应数据" }, "message": { "description": "响应消息", "type": "string" } } } }, "securityDefinitions": { "ApiKeyAuth": { "type": "apiKey", "name": "Authorization", "in": "header" } } } ================================================ FILE: domain/dto/contact_dto.go ================================================ package dto type ContactDto struct { // 会话ID ID int64 `json:"id"` // 房间ID RoomID int64 `json:"roomId"` // 头像 Avatar string `json:"avatar"` // 会话名称 Name string `json:"name"` // 最后一条消息内容 LastMsg string `json:"lastMsg"` // 最后一条消息时间 时间戳格式 LastTime int64 `json:"lastTime"` // 未读消息数 UnreadCount int32 `json:"unreadCount"` // 会话类型 Type int `json:"type"` } ================================================ FILE: domain/dto/delete_friend_dto.go ================================================ package dto type DeleteFriendDto struct { Uid int64 `json:"uid"` FriendUid int64 `json:"friend_uid"` } ================================================ FILE: domain/dto/group_dto.go ================================================ package dto import "time" type GetGroupMemberDto struct { // 用户ID UID int64 `json:"uid" gorm:"column:id"` // 用户名 Name string `json:"name"` // 头像 Avatar string `json:"avatar"` // 用户状态 ActiveStatus int32 `json:"activeStatus"` // 最后活跃时间 LastOptTime time.Time `json:"lastOptTime"` } ================================================ FILE: domain/dto/msg_dto.go ================================================ package dto type MessageBaseDto struct { // 下载地址 Url string `json:"url"` // 文件大小 Size int64 `json:"size"` // 文件名 Name string `json:"name"` } type ImgMessageDto struct { MessageBaseDto MessageBaseDto `json:"message_base_dto"` // 图片高度 Height int `json:"height"` // 图片宽度 Width int `json:"width"` } ================================================ FILE: domain/enum/event_bus.go ================================================ package enum const ( FriendApplyEvent = "main:FriendApplyEvent" FriendNewEvent = "main:FriendNewEvent" NewMessageEvent = "main:NewMessageEvent" ) ================================================ FILE: domain/enum/login.go ================================================ package enum const ( LoginByPassword = 1 // 用户名密码登录 LoginByPhoneCaptcha = 2 // 手机号验证码登录 ) ================================================ FILE: domain/enum/message.go ================================================ package enum const ( TextMessage = 1 ) const ( TextMessageType = 1 ImgMessageType = 3 ) ================================================ FILE: domain/enum/redis.go ================================================ package enum import "time" const ( Project = "diting:" User = Project + "User" UserFriend = Project + "userFriend:" UserApply = Project + "userApply:" RoomFriend = Project + "roomFriend:" Contact = Project + "contact:" Room = Project + "room:" ) const ( // 房间缓存 RoomCacheByID = Room + "%d" // 好友房间缓存 RoomFriendCacheByRoomID = RoomFriend + "%d" RoomFriendCacheByUidAndFriendUid = RoomFriend + "%d_%d" // phoneUid映射 PhoneUidMap = User + "PhoneUid:" + "%s" UserCacheByID = User + "%d" UserCacheByName = User + "%s" UserCacheByPhone = User + "Phone:" + "%s" UserCaptcha = User + "Captcha:" + "%s" // 用户好友缓存 UserFriendCacheByUidAndFriendUid = UserFriend + "%d_%d" // 好友申请缓存 UserApplyCacheByUidAndFriendUid = UserApply + "%d_%d" // 会话缓存 ContactCacheById = Contact + "%d" ) const ( DefaultCacheTime = 7 * 24 * time.Hour NotExpireTime = 10 * 365 * 24 * time.Hour ) ================================================ FILE: domain/enum/redsync.go ================================================ package enum const ( Lock = "lock:" UserLock = Lock + "diting-user:" UserAndFriendLock = UserLock + "%d_%d" ) ================================================ FILE: domain/enum/rocketmq.go ================================================ package enum const ( UserLoginTopic = "diting-login" NewFriendTopic = "diting-new-friend" NewMessageTopic = "diting-new-message" DeleteFriendTopic = "diting-delete-friend" FriendApplyTopic = "diting-friend-apply" ) ================================================ FILE: domain/enum/room.go ================================================ package enum const ( // 房间状态 HOT = 1 NORMAL = 0 // 房间类型 GROUP = 1 PERSONAL = 2 ) ================================================ FILE: domain/enum/user.go ================================================ package enum const ( UserStatusNormal = 1 UserStatusCancel = 2 ) ================================================ FILE: domain/model/message.go ================================================ package model import ( "DiTing-Go/dal/model" "DiTing-Go/domain/enum" ) type Message model.Message func (msg Message) GetContactMsg() string { if msg.Type == enum.TextMessageType { return msg.Content } else if msg.Type == enum.ImgMessageType { return "[图片]" } return msg.Content } ================================================ FILE: domain/vo/req/agree_friend_req.go ================================================ package req type AgreeFriendReq struct { Uid int64 `json:"uid" binding:"required"` } ================================================ FILE: domain/vo/req/captcha_req.go ================================================ package req type CaptchaReq struct { // 手机号 Phone string `json:"phone" binding:"required"` } ================================================ FILE: domain/vo/req/create_group_req.go ================================================ package req type CreateGroupReq struct { UidList []int64 `json:"uidList" binding:"required"` } ================================================ FILE: domain/vo/req/delete_friend_req.go ================================================ package req type DeleteFriendReq struct { Uid int64 `json:"uid" binding:"required"` } ================================================ FILE: domain/vo/req/delete_group_req.go ================================================ package req type DeleteGroupReq struct { ID int64 `uri:"id" binding:"required"` } ================================================ FILE: domain/vo/req/get_group_member_list_req.go ================================================ package req type GetGroupMemberListReq struct { // 房间ID RoomId int64 `form:"roomId" binding:"required"` Cursor *string `form:"cursor"` PageSize int `form:"pageSize" binding:"required"` } ================================================ FILE: domain/vo/req/get_message_list_req.go ================================================ package req type GetMessageListReq struct { RoomId int64 `json:"roomId" form:"roomId" binding:"required"` Cursor *string `json:"cursor" form:"cursor" binding:"required"` PageSize int `json:"pageSize" form:"pageSize" binding:"required"` } ================================================ FILE: domain/vo/req/get_new_content_list_req.go ================================================ package req type GetNewContentListReq struct { Timestamp int64 `json:"timestamp" form:"timestamp" binding:"required"` } ================================================ FILE: domain/vo/req/get_new_msg_list_req.go ================================================ package req type GetNewMsgListReq struct { // 房间ID RoomId int64 `json:"roomId" form:"roomId" binding:"required"` // 消息ID MsgId int64 `json:"msgId" form:"msgId" binding:"required"` } ================================================ FILE: domain/vo/req/get_user_info_batch_req.go ================================================ package req type UserInfoBatchReqItem struct { Uid int64 `json:"uid"` LastModifyTime int64 `json:"lastModifyTime"` } type GetUserInfoBatchReq struct { List []UserInfoBatchReqItem `json:"list" bind:"required"` } ================================================ FILE: domain/vo/req/get_user_info_by_name_req.go ================================================ package req type GetUserInfoByNameReq struct { // 用户名 Name string `form:"name" binding:"required"` } ================================================ FILE: domain/vo/req/grant_administrator_req.go ================================================ package req type GrantAdministratorReq struct { // 房间ID RoomId int64 `json:"room_id" binding:"required"` // 授权用户ID GrantUid int64 `json:"grant_uid" binding:"required"` } ================================================ FILE: domain/vo/req/is_friend_req.go ================================================ package req // IsFriendReq 是否是好友请求 type IsFriendReq struct { FriendUid int64 `uri:"friendUid" binding:"required"` } ================================================ FILE: domain/vo/req/join_group_req.go ================================================ package req type JoinGroupReq struct { ID int64 `json:"id" binding:"required"` } ================================================ FILE: domain/vo/req/message_req.go ================================================ package req type MessageBody struct { Content string `json:"content" form:"content" binding:"required"` ReplyMsgId int64 `json:"replyMsgId" form:"replyMsgId"` } type MessageReq struct { RoomId int64 `json:"roomId" form:"roomId" binding:"required"` MsgType int32 `json:"msgType" form:"msgType" binding:"required"` Body MessageBody `json:"body" form:"body" binding:"required"` } ================================================ FILE: domain/vo/req/quit_group_req.go ================================================ package req type QuitGroupReq struct { // 房间di ID int64 `json:"id" binding:"required"` } ================================================ FILE: domain/vo/req/remove_administrator_req.go ================================================ package req type RemoveAdministratorReq struct { // 房间ID RoomId int64 `json:"room_id" binding:"required"` // 移除用户ID RemoveUid int64 `json:"remove_uid" binding:"required"` } ================================================ FILE: domain/vo/req/uid_req.go ================================================ package req type UidReq struct { Uid int64 `json:"uid"` } ================================================ FILE: domain/vo/req/user_apply_req.go ================================================ package req type UserApplyReq struct { Uid int64 `json:"uid"` Msg string `json:"msg"` } ================================================ FILE: domain/vo/req/user_cancle_req.go ================================================ package req type UserCancelReq struct { Captcha string `json:"captcha" binding:"required"` } ================================================ FILE: domain/vo/req/user_login_req.go ================================================ package req type UserLoginReq struct { UserName string `json:"username,omitempty"` // 用户名可选 Password string `json:"password,omitempty"` // 密码可选 Phone string `json:"phone,omitempty"` // 手机号可选 Captcha string `json:"captcha,omitempty"` // 验证码 LoginType int `json:"loginType" binding:"required"` // 登录类型, 1: 用户名密码登录, 2: 手机号验证码登录 } ================================================ FILE: domain/vo/req/user_register_req.go ================================================ package req type UserRegisterReq struct { // 用户名 Username string `json:"username" binding:"required"` // 密码 Password string `json:"password" binding:"required"` // 手机号 Phone string `json:"phone" binding:"required"` // 验证码 Captcha string `json:"captcha" binding:"required"` // 如果需要验证码,可以取消注释 } ================================================ FILE: domain/vo/resp/get_user_info_batch_resp.go ================================================ package resp type GetUserInfoBatchResp struct { Uid int64 `json:"uid"` Username string `json:"name"` Avatar string `json:"avatar"` NeedRefresh bool `json:"needRefresh"` } ================================================ FILE: domain/vo/resp/get_user_info_by_name_resp.go ================================================ package resp type GetUserInfoByNameResp struct { // 用户ID Uid int64 `json:"uid"` // 用户名 Name string `json:"name"` // 头像 Avatar string `json:"avatar"` // 好友状态 Status int32 `json:"status"` } ================================================ FILE: domain/vo/resp/message_resp.go ================================================ package resp type MsgUser struct { Uid int64 `json:"uid"` Username string `json:"username"` Avatar string `json:"avatar"` } type Msg struct { ID int64 `json:"id"` RoomId int64 `json:"roomId"` Type int32 `json:"type"` Body TextBody `json:"body"` } type TextBody struct { Content string `json:"content"` Reply int64 `json:"reply"` } type MessageResp struct { FromUser MsgUser `json:"fromUser"` Message Msg `json:"message"` SendTime int64 `json:"sendTime"` } ================================================ FILE: domain/vo/resp/page_list_resp.go ================================================ package resp type PageListResp struct { List interface{} `json:"dataList"` Total int `json:"total"` } ================================================ FILE: domain/vo/resp/pre_signed_resp.go ================================================ package resp type PreSignedResp struct { Url string `json:"url"` Policy map[string]string `json:"policy"` } ================================================ FILE: domain/vo/resp/user_apply_resp.go ================================================ package resp type UserApplyResp struct { ApplyId int64 `json:"applyId"` // 申请ID Uid int64 `json:"uid"` // 用户ID Msg string `json:"msg"` // 申请信息 Status int32 `json:"status"` // 使用状态 1.待审批 2.已接受 } ================================================ FILE: domain/vo/resp/user_contact_resp.go ================================================ package resp type UserContactResp struct { Uid int64 `json:"uid"` // 用户ID ActiveStatus int `json:"activeStatus"` // 用户状态 LastOptTime int64 `json:"lastOptTime"` // 最后操作时间 } ================================================ FILE: domain/vo/resp/user_login_resp.go ================================================ package resp type UserLoginResp struct { Token string `json:"token"` Uid int64 `json:"uid"` Name string `json:"name"` Avatar string `json:"avatar"` } ================================================ FILE: event/listener/friend_apply_event.go ================================================ package listener import ( "DiTing-Go/dal/model" "DiTing-Go/domain/enum" "DiTing-Go/global" "DiTing-Go/utils/jsonUtils" "context" "github.com/apache/rocketmq-client-go/v2" "github.com/apache/rocketmq-client-go/v2/consumer" "github.com/apache/rocketmq-client-go/v2/primitive" "github.com/spf13/viper" ) func init() { host := viper.GetString("rocketmq.host") // 设置推送消费者 rocketConsumer, _ := rocketmq.NewPushConsumer( //消费组 consumer.WithGroupName(enum.FriendApplyTopic), // namesrv地址 consumer.WithNameServer([]string{host}), ) err := rocketConsumer.Subscribe(enum.FriendApplyTopic, consumer.MessageSelector{}, friendApplyEvent) if err != nil { global.Logger.Panicf("subscribe error: %s", err.Error()) } err = rocketConsumer.Start() if err != nil { global.Logger.Panicf("start consumer error: %s", err.Error()) } } // FriendApplyEvent 好友申请事件 func friendApplyEvent(ctx context.Context, ext ...*primitive.MessageExt) (consumer.ConsumeResult, error) { for i := range ext { // 解码 userApplyR := model.UserApply{} if err := jsonUtils.UnmarshalMsg(&userApplyR, ext[i]); err != nil { global.Logger.Errorf("jsonUtils unmarshal error: %s", err.Error()) return consumer.ConsumeRetryLater, nil } if err := friendApply(userApplyR); err != nil { global.Logger.Errorf("friendApply error: %s", err.Error()) return consumer.ConsumeRetryLater, nil } } return consumer.ConsumeSuccess, nil } func friendApply(apply model.UserApply) error { // 发送新消息事件 //service.Send(apply.TargetID) return nil } ================================================ FILE: event/listener/friend_delete_event.go ================================================ package listener import ( "DiTing-Go/dal/model" "DiTing-Go/domain/dto" "DiTing-Go/domain/enum" "DiTing-Go/global" pkgEnum "DiTing-Go/pkg/domain/enum" "DiTing-Go/pkg/utils" "DiTing-Go/utils/jsonUtils" "DiTing-Go/utils/redisCache" "context" "fmt" "github.com/apache/rocketmq-client-go/v2" "github.com/apache/rocketmq-client-go/v2/consumer" "github.com/apache/rocketmq-client-go/v2/primitive" "github.com/pkg/errors" "github.com/spf13/viper" "sort" ) func init() { host := viper.GetString("rocketmq.host") // 设置推送消费者 rocketConsumer, _ := rocketmq.NewPushConsumer( //消费组 consumer.WithGroupName(enum.DeleteFriendTopic), // namesrv地址 consumer.WithNameServer([]string{host}), ) err := rocketConsumer.Subscribe(enum.DeleteFriendTopic, consumer.MessageSelector{}, deleteFriendEvent) if err != nil { global.Logger.Panicf("subscribe error: %s", err.Error()) } err = rocketConsumer.Start() if err != nil { global.Logger.Panicf("start consumer error: %s", err.Error()) } } func deleteFriendEvent(ctx context.Context, ext ...*primitive.MessageExt) (consumer.ConsumeResult, error) { for i := range ext { // 解码 deleteFriendDto := dto.DeleteFriendDto{} if err := jsonUtils.UnmarshalMsg(&deleteFriendDto, ext[i]); err != nil { global.Logger.Errorf("jsonUtils unmarshal error: %s", err.Error()) return consumer.ConsumeRetryLater, nil } if err := deleteFriend(deleteFriendDto); err != nil { global.Logger.Errorf("deleteFriend error: %s", err.Error()) return consumer.ConsumeRetryLater, nil } } return consumer.ConsumeSuccess, nil } func deleteFriend(deleteFriendDto dto.DeleteFriendDto) error { uid := deleteFriendDto.Uid deleteFriendUid := deleteFriendDto.FriendUid // 加锁 uids := utils.Int64Slice{uid, deleteFriendUid} sort.Sort(uids) key := fmt.Sprintf(enum.UserAndFriendLock, uids[0], uids[1]) mutex, err := utils.GetLock(key) if err != nil { return err } defer utils.ReleaseLock(mutex) ctx := context.Background() tx := global.Query.Begin() userApply := global.Query.UserApply userApplyTx := tx.UserApply.WithContext(ctx) // 删除好友申请 if _, err := userApplyTx.Where(userApply.UID.Eq(uid), userApply.TargetID.Eq(deleteFriendUid)).Delete(); err != nil { if err := tx.Rollback(); err != nil { global.Logger.Errorf("事务回滚失败 %s", err.Error()) } global.Logger.Errorf("删除好友失败 %s", err.Error()) return errors.New("Business Error") } // 删除redis缓存 defer redisCache.RemoveUserApply(uid, deleteFriendUid) if _, err := userApplyTx.Where(userApply.UID.Eq(deleteFriendUid), userApply.TargetID.Eq(uid)).Delete(); err != nil { if err := tx.Rollback(); err != nil { global.Logger.Errorf("事务回滚失败 %s", err.Error()) } global.Logger.Errorf("删除好友失败 %s", err.Error()) return errors.New("Business Error") } // 删除redis缓存 defer redisCache.RemoveUserApply(deleteFriendUid, uid) // 软删除好友房间 roomFriend := global.Query.RoomFriend roomFriendTx := tx.RoomFriend.WithContext(ctx) uids = utils.Int64Slice{uid, deleteFriendUid} sort.Sort(uids) fun := func() (interface{}, error) { return roomFriendTx.Where(roomFriend.Uid1.Eq(uids[0]), roomFriend.Uid2.Eq(uids[1])).First() } roomFriendR := model.RoomFriend{} key = fmt.Sprintf(enum.RoomFriendCacheByUidAndFriendUid, uids[0], uids[1]) if err := utils.GetData(key, &roomFriendR, fun); err != nil { if err := tx.Rollback(); err != nil { global.Logger.Errorf("事务回滚失败 %s", err.Error()) } global.Logger.Errorf("查询好友房间失败 %s", err.Error()) return errors.New("Business Error") } if _, err := roomFriendTx.Where(roomFriend.ID.Eq(roomFriendR.ID)).Update(roomFriend.DeleteStatus, pkgEnum.DELETED); err != nil { if err := tx.Rollback(); err != nil { global.Logger.Errorf("事务回滚失败 %s", err.Error()) } global.Logger.Errorf("删除好友房间失败 %s", err.Error()) return errors.New("Business Error") } // 删除redis缓存 defer redisCache.RemoveRoomFriend(roomFriendR) // 软删除房间表 room := global.Query.Room roomTx := tx.Room.WithContext(ctx) if _, err := roomTx.Where(room.ID.Eq(roomFriendR.RoomID)).Update(room.DeleteStatus, pkgEnum.DELETED); err != nil { if err := tx.Rollback(); err != nil { global.Logger.Errorf("事务回滚失败 %s", err.Error()) } global.Logger.Errorf("删除房间失败 %s", err.Error()) return errors.New("Business Error") } // 删除redis缓存 roomR := model.Room{ ID: roomFriendR.RoomID, } defer redisCache.RemoveRoomCache(roomR) // 删除消息表 msg := global.Query.Message msgTx := tx.Message.WithContext(ctx) if _, err := msgTx.Where(msg.RoomID.Eq(roomFriendR.RoomID)).Update(msg.DeleteStatus, pkgEnum.DELETED); err != nil { if err := tx.Rollback(); err != nil { global.Logger.Errorf("事务回滚失败 %s", err.Error()) } global.Logger.Errorf("删除消息失败 %s", err.Error()) return errors.New("Business Error") } // TODO: 删除消息表缓存 if err := tx.Commit(); err != nil { global.Logger.Errorf("事务提交失败 %s", err.Error()) return errors.New("Business Error") } return nil } ================================================ FILE: event/listener/friend_new_event.go ================================================ package listener import ( "DiTing-Go/dal/model" "DiTing-Go/domain/enum" "DiTing-Go/global" pkgEnum "DiTing-Go/pkg/domain/enum" "DiTing-Go/pkg/utils" "DiTing-Go/service" "DiTing-Go/utils/redisCache" "context" "fmt" "github.com/apache/rocketmq-client-go/v2" "github.com/apache/rocketmq-client-go/v2/consumer" "github.com/apache/rocketmq-client-go/v2/primitive" "github.com/goccy/go-json" "github.com/pkg/errors" "github.com/spf13/viper" "gorm.io/gorm" "sort" "strconv" "time" ) func init() { host := viper.GetString("rocketmq.host") // 设置推送消费者 rocketConsumer, _ := rocketmq.NewPushConsumer( //消费组 consumer.WithGroupName(enum.UserLoginTopic), // namesrv地址 consumer.WithNameServer([]string{host}), ) err := rocketConsumer.Subscribe(enum.NewFriendTopic, consumer.MessageSelector{}, friendNewEvent) if err != nil { global.Logger.Panicf("subscribe error: %s", err.Error()) } err = rocketConsumer.Start() if err != nil { global.Logger.Panicf("start consumer error: %s", err.Error()) } } func friendNewEvent(ctx context.Context, ext ...*primitive.MessageExt) (consumer.ConsumeResult, error) { for i := range ext { // 解码 userFriend := model.UserFriend{} userFriendMsgByte := ext[i].Message.Body err := json.Unmarshal(userFriendMsgByte, &userFriend) if err != nil { global.Logger.Errorf("json unmarshal error: %s", err.Error()) return consumer.ConsumeRetryLater, nil } err = friendNew(userFriend) if err != nil { global.Logger.Errorf("friendNew error: %s", err.Error()) return consumer.ConsumeRetryLater, nil } } return consumer.ConsumeSuccess, nil } func friendNew(userFriend model.UserFriend) error { ctx := context.Background() uid := userFriend.UID friendUid := userFriend.FriendUID uids := utils.Int64Slice{uid, friendUid} sort.Sort(uids) key := fmt.Sprintf(enum.UserAndFriendLock, uids[0], uids[1]) mutex, err := utils.GetLock(key) if err != nil { return err } defer utils.ReleaseLock(mutex) q := global.Query tx := q.Begin() roomQ := tx.WithContext(ctx).Room roomFriendQ := tx.WithContext(ctx).RoomFriend contactQ := tx.WithContext(ctx).Contact // 创建房间表 room := model.Room{ Type: enum.PERSONAL, HotFlag: enum.NORMAL, ExtJSON: "{}", } if err := roomQ.Create(&room); err != nil { if err := tx.Rollback(); err != nil { global.Logger.Errorf("事务回滚失败 %s", err.Error()) return err } global.Logger.Errorf("创建房间失败 %s", err.Error()) return err } // 排序,uid小的在前 uids = utils.Int64Slice{userFriend.UID, userFriend.FriendUID} sort.Sort(uids) //检查是否有软删除状态的记录 roomFriend := global.Query.RoomFriend fun := func() (interface{}, error) { return roomFriendQ.Where(roomFriend.Uid1.Eq(uids[0]), roomFriend.Uid2.Eq(uids[1]), roomFriend.DeleteStatus.Eq(pkgEnum.DELETED)).First() } roomFriedR := model.RoomFriend{} key = fmt.Sprintf(enum.RoomFriendCacheByUidAndFriendUid, uids[0], uids[1]) err = utils.GetData(key, &roomFriedR, fun) // err if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { global.Logger.Errorf("查询数据失败: %v", err) if err := tx.Rollback(); err != nil { global.Logger.Errorf("事务回滚失败 %s", err.Error()) return err } return err } // 查到了 if err == nil { roomFriedR.RoomID = room.ID roomFriedR.DeleteStatus = pkgEnum.NORMAL if _, err := roomFriendQ.Select(roomFriend.RoomID, roomFriend.DeleteStatus).Where(roomFriend.ID.Eq(roomFriedR.ID)).Updates(roomFriedR); err != nil { if err := tx.Rollback(); err != nil { global.Logger.Errorf("事务回滚失败 %s", err.Error()) return err } global.Logger.Errorf("更新房间失败 %s", err.Error()) return err } roomFriendR := model.RoomFriend{ Uid1: uids[0], Uid2: uids[1], } defer redisCache.RemoveRoomFriend(roomFriendR) } else { // 创建私聊表 newRoomFriend := model.RoomFriend{ RoomID: room.ID, Uid1: uids[0], Uid2: uids[1], RoomKey: strconv.FormatInt(uids[0], 10) + "," + strconv.FormatInt(uids[1], 10), } if err := roomFriendQ.Create(&newRoomFriend); err != nil { if err := tx.Rollback(); err != nil { global.Logger.Errorf("事务回滚失败 %s", err.Error()) return err } global.Logger.Errorf("创建房间失败 %s", err.Error()) return err } } // 自动发送一条消息 newMsg := model.Message{ RoomID: room.ID, FromUID: userFriend.UID, Content: "你们已经是好友了,开始聊天吧", DeleteStatus: pkgEnum.NORMAL, Type: enum.TextMessage, Extra: "{}", } if err := service.SendTextMsg(&newMsg); err != nil { if err := tx.Rollback(); err != nil { global.Logger.Errorf("事务回滚失败 %s", err.Error()) return err } global.Logger.Errorf("发送消息失败 %s", err.Error()) return err } //创建会话表 s, _ := time.ParseDuration("-1s") if err := contactQ.Create(&model.Contact{ UID: userFriend.UID, RoomID: room.ID, LastMsgID: newMsg.ID, ReadTime: time.Now(), ActiveTime: time.Now(), }); err != nil { if err := tx.Rollback(); err != nil { global.Logger.Errorf("事务回滚失败 %s", err.Error()) return err } global.Logger.Errorf("创建会话失败 %s", err.Error()) return err } if err := contactQ.Create(&model.Contact{ UID: userFriend.FriendUID, RoomID: room.ID, LastMsgID: newMsg.ID, // 读到时间设为1秒前 ReadTime: time.Now().Add(s), ActiveTime: time.Now(), }); err != nil { if err := tx.Rollback(); err != nil { global.Logger.Errorf("事务回滚失败 %s", err.Error()) return err } global.Logger.Errorf("创建会话失败 %s", err.Error()) return err } // 提交 if err := tx.Commit(); err != nil { global.Logger.Errorf("事务提交失败 %s", err.Error()) return err } // 发送新消息事件 newMsgByte, _ := json.Marshal(newMsg) msg := &primitive.Message{ Topic: enum.NewMessageTopic, Body: newMsgByte, } _, _ = global.RocketProducer.SendSync(ctx, msg) return nil } ================================================ FILE: event/listener/new_msg_event.go ================================================ package listener import ( "DiTing-Go/dal/model" query "DiTing-Go/dal/query" "DiTing-Go/domain/enum" "DiTing-Go/global" "DiTing-Go/pkg/utils" wsEnum "DiTing-Go/websocket/domain/enum" resp2 "DiTing-Go/websocket/domain/vo/resp" "DiTing-Go/websocket/service" "context" "encoding/json" "fmt" "github.com/apache/rocketmq-client-go/v2" "github.com/apache/rocketmq-client-go/v2/consumer" "github.com/apache/rocketmq-client-go/v2/primitive" "github.com/spf13/viper" "time" ) func init() { host := viper.GetString("rocketmq.host") // 设置推送消费者 rocketSendMsgConsumer, _ := rocketmq.NewPushConsumer( //消费组 consumer.WithGroupName(enum.NewMessageTopic+"-send-message"), // namesrv地址 consumer.WithNameServer([]string{host}), ) err := rocketSendMsgConsumer.Subscribe(enum.NewMessageTopic, consumer.MessageSelector{}, UpdateContactEvent) if err != nil { global.Logger.Panicf("subscribe error: %s", err.Error()) } err = rocketSendMsgConsumer.Start() if err != nil { global.Logger.Panicf("start consumer error: %s", err.Error()) } //// 设置推送消费者 //rocketUpdateContactConsumer, _ := rocketmq.NewPushConsumer( // //消费组 // consumer.WithGroupName(enum.NewMessageTopic+"-update-contact"), // // namesrv地址 // consumer.WithNameServer([]string{host}), //) //err := rocketUpdateContactConsumer.Subscribe(enum.NewMessageTopic, consumer.MessageSelector{}, SendMsgEvent) //if err != nil { // global.Logger.Panicf("subscribe error: %s", err.Error()) //} //err = rocketUpdateContactConsumer.Start() //if err != nil { // global.Logger.Panicf("start consumer error: %s", err.Error()) //} } func UpdateContactEvent(ctx context.Context, ext ...*primitive.MessageExt) (consumer.ConsumeResult, error) { for i := range ext { // 解码 msg := model.Message{} msgByte := ext[i].Message.Body err := json.Unmarshal(msgByte, &msg) if err != nil { global.Logger.Errorf("jsonUtils unmarshal error: %s", err.Error()) return consumer.ConsumeRetryLater, nil } err = updateContact(msg) if err != nil { global.Logger.Errorf("更新会话失败 %s", err) return consumer.ConsumeRetryLater, nil } err = sendMsg(msg) if err != nil { global.Logger.Errorf("发送消息失败 %s", err) return consumer.ConsumeRetryLater, nil } } return consumer.ConsumeSuccess, nil } func updateContact(msg model.Message) error { // 更新会话表 ctx := context.Background() room := global.Query.Room roomQ := room.WithContext(ctx) roomR, err := roomQ.Where(room.ID.Eq(msg.RoomID)).First() if err != nil { global.Logger.Errorf("查询房间失败 %s", err) return err } var uids []int64 if roomR.Type == enum.PERSONAL { roomFriend := global.Query.RoomFriend roomFriendQ := roomFriend.WithContext(ctx) roomFriendR, err := roomFriendQ.Where(roomFriend.RoomID.Eq(roomR.ID)).First() if err != nil { global.Logger.Errorf("查询好友房间失败 %s", err) return err } uids = []int64{roomFriendR.Uid1, roomFriendR.Uid2} } else if roomR.Type == enum.GROUP { // 查询所有群成员 roomGroup := global.Query.RoomGroup roomGroupQ := roomGroup.WithContext(ctx) roomGroupR, err := roomGroupQ.Where(roomGroup.RoomID.Eq(roomR.ID)).First() if err != nil { global.Logger.Errorf("查询群聊失败 %s", err) return err } groupMember := global.Query.GroupMember groupMemberQ := groupMember.WithContext(ctx) groupMembers, _ := groupMemberQ.Where(groupMember.GroupID.Eq(roomGroupR.ID)).Find() for _, groupMember := range groupMembers { uids = append(uids, groupMember.UID) } } //更新会话表 update := model.Contact{ LastMsgID: msg.ID, UpdateTime: time.Now(), ActiveTime: time.Now(), } contact := global.Query.Contact contactQ := contact.WithContext(ctx) _, err = contactQ.Where(contact.UID.In(uids...), contact.RoomID.Eq(msg.RoomID)).Updates(&update) if err != nil { global.Logger.Errorf("更新会话失败 %s", err) return err } return nil } // SendMsgEvent 新消息事件 func SendMsgEvent(ctx context.Context, ext ...*primitive.MessageExt) (consumer.ConsumeResult, error) { for i := range ext { // 解码 msg := model.Message{} msgByte := ext[i].Message.Body err := json.Unmarshal(msgByte, &msg) if err != nil { global.Logger.Errorf("jsonUtils unmarshal error: %s", err.Error()) return consumer.ConsumeRetryLater, nil } err = sendMsg(msg) if err != nil { global.Logger.Errorf("发送消息失败 %s", err) return consumer.ConsumeRetryLater, nil } } return consumer.ConsumeSuccess, nil } // sendMsg 发送消息 func sendMsg(msg model.Message) error { // 向房间中的所有用户发送消息,包括自己 roomQ := global.Query.WithContext(context.Background()).Room fun := func() (interface{}, error) { return roomQ.Where(query.Room.ID.Eq(msg.RoomID)).First() } room := model.Room{} key := fmt.Sprintf(enum.RoomCacheByID, msg.RoomID) err := utils.GetData(key, &room, fun) if err != nil { global.Logger.Errorf("查询房间失败 %s", err) return err } msgBody := resp2.NewMessageResp{ Type: wsEnum.NewMessage, } str, _ := json.Marshal(msgBody) // 单聊 if room.Type == enum.PERSONAL { roomFriendQ := global.Query.WithContext(context.Background()).RoomFriend roomFriendR := model.RoomFriend{} fun = func() (interface{}, error) { return roomFriendQ.Where(query.RoomFriend.RoomID.Eq(room.ID)).First() } key := fmt.Sprintf(enum.RoomFriendCacheByRoomID, room.ID) err = utils.GetData(key, &roomFriendR, fun) if err != nil { global.Logger.Errorf("查询好友房间失败 %s", err) return err } // 发送新消息事件 err := service.Send(roomFriendR.Uid1, str) if err != nil { return err } err = service.Send(roomFriendR.Uid2, str) if err != nil { return err } // TODO:群聊 } else if room.Type == enum.GROUP { roomGroupQ := global.Query.WithContext(context.Background()).RoomGroup roomGroup, _ := roomGroupQ.Where(query.RoomGroup.RoomID.Eq(room.ID)).First() // 查询所有群成员 groupMemberQ := global.Query.WithContext(context.Background()).GroupMember groupMembers, _ := groupMemberQ.Where(query.GroupMember.GroupID.Eq(roomGroup.ID)).Find() // 发送新消息事件 for _, groupMember := range groupMembers { service.Send(groupMember.UID, str) } } return nil } ================================================ FILE: event/listener/user_login_event.go ================================================ package listener import ( "DiTing-Go/domain/enum" "DiTing-Go/global" "context" "fmt" "github.com/apache/rocketmq-client-go/v2" "github.com/apache/rocketmq-client-go/v2/consumer" "github.com/apache/rocketmq-client-go/v2/primitive" "github.com/spf13/viper" ) func init() { host := viper.GetString("rocketmq.host") // 设置推送消费者 rocketConsumer, _ := rocketmq.NewPushConsumer( //消费组 consumer.WithGroupName(enum.UserLoginTopic+"-test"), // namesrv地址 consumer.WithNameServer([]string{host}), ) go test(rocketConsumer) } func test(rocketConsumer rocketmq.PushConsumer) { // 必须先在 开始前 err := rocketConsumer.Subscribe(enum.UserLoginTopic, consumer.MessageSelector{}, func(ctx context.Context, ext ...*primitive.MessageExt) (consumer.ConsumeResult, error) { for i := range ext { fmt.Printf("subscribe callback:%v \n", ext[i]) } return consumer.ConsumeSuccess, nil }) if err != nil { global.Logger.Panicf("subscribe error: %s", err.Error()) } err = rocketConsumer.Start() if err != nil { global.Logger.Panicf("start consumer error: %s", err.Error()) } } ================================================ FILE: global/init_db.go ================================================ package global import ( "DiTing-Go/dal" "DiTing-Go/dal/query" "fmt" "github.com/spf13/viper" ) var MySQLDSN string var Query *query.Query func DBInit() { MySQLDSN = fmt.Sprintf("%s:%s@tcp(%s:%s)/DiTing?charset=utf8mb4&parseTime=True&loc=Local", viper.GetString("mysql.username"), viper.GetString("mysql.password"), viper.GetString("mysql.host"), viper.GetString("mysql.port")) dal.DB = dal.ConnectDB(MySQLDSN).Debug() //dal.DB = dal.ConnectDB(MySQLDSN) // 设置默认DB对象 query.SetDefault(dal.DB) Query = query.Use(dal.DB) } ================================================ FILE: global/init_distribute_lock.go ================================================ package global // //import ( // goredislib "github.com/go-redis/redis/v8" // "github.com/go-redsync/redsync/v4" // "github.com/go-redsync/redsync/v4/redis/goredis/v8" // "github.com/spf13/viper" //) // //var RedSync *redsync.Redsync // //func init() { // Addr := viper.GetString("redis.host") // Password := viper.GetString("redis.password") // client := goredislib.NewClient(&goredislib.Options{ // Addr: Addr, // Password: Password, // 密码 // DB: 0, // 数据库 // PoolSize: 20, // 连接池大小 // }) // pool := goredis.NewPool(client) // RedSync = redsync.New(pool) //} ================================================ FILE: global/init_evenbus.go ================================================ package global // //import "github.com/asaskevich/EventBus" // //var Bus EventBus.Bus // //func init() { // Bus = EventBus.New() //} ================================================ FILE: global/init_log.go ================================================ package global import ( "github.com/sirupsen/logrus" "github.com/spf13/viper" "io" "os" "path" ) var Logger *logrus.Logger func LogInit() { logFilePath := viper.GetString("log.log_file_path") logFileName := viper.GetString("log.log_file_name") //日志文件 fileName := path.Join(logFilePath, logFileName) //写入文件 src, err := os.OpenFile(fileName, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) if err != nil { // 失败不启动 panic("日志文件写入失败, err: " + err.Error()) } //实例化 Logger = logrus.New() writers := []io.Writer{ src, os.Stdout} fileAndStdoutWriter := io.MultiWriter(writers...) if err == nil { Logger.SetOutput(fileAndStdoutWriter) } else { Logger.Info("failed to log to file.") } //设置日志级别 Logger.SetLevel(logrus.DebugLevel) Logger.SetReportCaller(true) //设置日志格式 Logger.SetFormatter(&logrus.TextFormatter{ TimestampFormat: "2006-01-02 15:04:05", }) } ================================================ FILE: global/init_minio.go ================================================ package global import ( "github.com/minio/minio-go/v7" ) var MinioClient *minio.Client //func init() { // accessKey := viper.GetString("minio.accessKey") // accessSecret := viper.GetString("minio.accessSecret") // endPoint := viper.GetString("minio.endPoint") // useSSL := viper.GetBool("minio.useSSL") // // 初始化minio客户端 // minioClient, err := minio.New(endPoint, &minio.Options{ // Creds: credentials.NewStaticV4(accessKey, accessSecret, ""), // Secure: useSSL, // }) // MinioClient = minioClient // if err != nil { // Logger.Fatal("minio client create fail, err %+v", err) // } //} ================================================ FILE: global/init_redis.go ================================================ package global import ( "github.com/go-redis/redis" "github.com/spf13/viper" ) var Rdb *redis.Client func RedisInit() { Addr := viper.GetString("redis.host") Password := viper.GetString("redis.password") Rdb = redis.NewClient(&redis.Options{ Addr: Addr, Password: Password, // 密码 DB: 0, // 数据库 PoolSize: 100, // 连接池大小 }) } ================================================ FILE: global/init_rocketmq.go ================================================ package global // //import ( // "github.com/apache/rocketmq-client-go/v2" // "github.com/apache/rocketmq-client-go/v2/producer" // "github.com/spf13/viper" //) // //var RocketProducer rocketmq.Producer // //func init() { // host := viper.GetString("rocketmq.host") // group := viper.GetString("rocketmq.group") // RocketProducer, _ = rocketmq.NewProducer( // // 设置 nameSrvAddr // // nameSrvAddr 是 Topic 路由注册中心 // producer.WithNameServer([]string{host}), // // 指定发送失败时的重试时间 // producer.WithRetry(3), // // 设置 Group // producer.WithGroupName(group), // ) // // 开始连接 // err := RocketProducer.Start() // if err != nil { // Logger.Panicf("start producer error: %s", err.Error()) // } //} ================================================ FILE: global/init_time.go ================================================ package global //import "time" // //func init() { // location, _ := time.LoadLocation("Asia/Shanghai") // time.Local = location //} ================================================ FILE: go.mod ================================================ module DiTing-Go go 1.22.1 require ( github.com/apache/rocketmq-client-go/v2 v2.1.2 github.com/gin-gonic/gin v1.9.1 github.com/go-redis/redis v6.15.9+incompatible github.com/goccy/go-json v0.10.2 github.com/golang-jwt/jwt/v5 v5.3.0 github.com/gorilla/websocket v1.5.1 github.com/jinzhu/copier v0.4.0 github.com/minio/minio-go/v7 v7.0.69 github.com/orcaman/concurrent-map/v2 v2.0.1 github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.9.3 github.com/spf13/viper v1.18.2 github.com/stretchr/testify v1.9.0 github.com/swaggo/files v1.0.1 github.com/swaggo/gin-swagger v1.6.0 github.com/swaggo/swag v1.16.3 gorm.io/driver/mysql v1.5.6 gorm.io/gen v0.3.25 gorm.io/gorm v1.25.9 gorm.io/plugin/dbresolver v1.5.1 ) require ( filippo.io/edwards25519 v1.1.0 // indirect github.com/KyleBanks/depth v1.2.1 // indirect github.com/bytedance/sonic v1.11.3 // indirect github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect github.com/chenzhuoyu/iasm v0.9.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/emirpasic/gods v1.12.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/spec v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.19.0 // indirect github.com/go-sql-driver/mysql v1.8.1 // indirect github.com/golang/mock v1.3.1 // indirect github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.17.6 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-sqlite3 v1.14.22 // indirect github.com/minio/md5-simd v1.1.2 // indirect github.com/minio/sha256-simd v1.0.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/pelletier/go-toml/v2 v2.2.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/rs/xid v1.5.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/tidwall/gjson v1.13.0 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/arch v0.7.0 // indirect golang.org/x/crypto v0.21.0 // indirect golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 // indirect golang.org/x/mod v0.16.0 // indirect golang.org/x/net v0.22.0 // indirect golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.19.0 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gorm.io/datatypes v1.2.0 // indirect gorm.io/driver/sqlite v1.5.5 // indirect gorm.io/hints v1.1.2 // indirect stathat.com/c/consistent v1.0.0 // indirect ) ================================================ FILE: go.sum ================================================ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I= github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/apache/rocketmq-client-go/v2 v2.1.2 h1:yt73olKe5N6894Dbm+ojRf/JPiP0cxfDNNffKwhpJVg= github.com/apache/rocketmq-client-go/v2 v2.1.2/go.mod h1:6I6vgxHR3hzrvn+6n/4mrhS+UTulzK/X9LB2Vk1U5gE= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= github.com/bytedance/sonic v1.11.3 h1:jRN+yEjakWh8aK5FzrciUHG8OFXK+4/KrAX/ysEtHAA= github.com/bytedance/sonic v1.11.3/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA= github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0= github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY= github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4= github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg= github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgx/v5 v5.3.0 h1:/NQi8KHMpKWHInxXesC8yD4DhkXPrVhmnwYkjp9AmBA= github.com/jackc/pgx/v5 v5.3.0/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8= github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI= github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.2.1/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/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/microsoft/go-mssqldb v0.17.0 h1:Fto83dMZPnYv1Zwx5vHHxpNraeEaUlQ/hhHLgZiaenE= github.com/microsoft/go-mssqldb v0.17.0/go.mod h1:OkoNGhGEs8EZqchVTtochlXruEhEOaO4S0d2sB5aeGQ= github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= github.com/minio/minio-go/v7 v7.0.69 h1:l8AnsQFyY1xiwa/DaQskY4NXSLA2yrGsW5iD9nRPVS0= github.com/minio/minio-go/v7 v7.0.69/go.mod h1:XAvOPJQ5Xlzk5o3o/ArO2NMbhSGkimC+bpW/ngRKDmQ= github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= github.com/orcaman/concurrent-map/v2 v2.0.1 h1:jOJ5Pg2w1oeB6PeDurIYf6k9PQ+aTITr/6lP/L/zp6c= github.com/orcaman/concurrent-map/v2 v2.0.1/go.mod h1:9Eq3TG2oBe5FirmYWQfYO5iH1q0Jv47PLaNK++uCdOM= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo= github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/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/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE= github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg= github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M= github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo= github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg= github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk= github.com/tidwall/gjson v1.13.0 h1:3TFY9yxOQShrvmjdM76K+jc66zJeT6D3/VFFYCGQf7M= github.com/tidwall/gjson v1.13.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.uber.org/atomic v1.5.1/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 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/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc= golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88pSyOt+UgdZw2BFZ+lEw= golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/datatypes v1.2.0 h1:5YT+eokWdIxhJgWHdrb2zYUimyk0+TaFth+7a0ybzco= gorm.io/datatypes v1.2.0/go.mod h1:o1dh0ZvjIjhH/bngTpypG6lVRJ5chTBxE09FH/71k04= gorm.io/driver/mysql v1.4.3/go.mod h1:sSIebwZAVPiT+27jK9HIwvsqOGKx3YMPmrA3mBJR10c= gorm.io/driver/mysql v1.5.6 h1:Ld4mkIickM+EliaQZQx3uOJDJHtrd70MxAUqWqlx3Y8= gorm.io/driver/mysql v1.5.6/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= gorm.io/driver/postgres v1.5.0 h1:u2FXTy14l45qc3UeCJ7QaAXZmZfDDv0YrthvmRq1l0U= gorm.io/driver/postgres v1.5.0/go.mod h1:FUZXzO+5Uqg5zzwzv4KK49R8lvGIyscBOqYrtI1Ce9A= gorm.io/driver/sqlite v1.5.0/go.mod h1:kDMDfntV9u/vuMmz8APHtHF0b4nyBB7sfCieC6G8k8I= gorm.io/driver/sqlite v1.5.5 h1:7MDMtUZhV065SilG62E0MquljeArQZNfJnjd9i9gx3E= gorm.io/driver/sqlite v1.5.5/go.mod h1:6NgQ7sQWAIFsPrJJl1lSNSu2TABh0ZZ/zm5fosATavE= gorm.io/driver/sqlserver v1.4.1 h1:t4r4r6Jam5E6ejqP7N82qAJIJAht27EGT41HyPfXRw0= gorm.io/driver/sqlserver v1.4.1/go.mod h1:DJ4P+MeZbc5rvY58PnmN1Lnyvb5gw5NPzGshHDnJLig= gorm.io/gen v0.3.25 h1:uT/1YfvcnYUdike4XPYyi89FEnVHZF115GUXQm2Sfug= gorm.io/gen v0.3.25/go.mod h1:p+t0iCKjaPz+pKRxcx63nXdRgnrah/QD2l92747ihyA= gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= gorm.io/gorm v1.25.0/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= gorm.io/gorm v1.25.2/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= gorm.io/gorm v1.25.9 h1:wct0gxZIELDk8+ZqF/MVnHLkA1rvYlBWUMv2EdsK1g8= gorm.io/gorm v1.25.9/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= gorm.io/hints v1.1.2 h1:b5j0kwk5p4+3BtDtYqqfY+ATSxjj+6ptPgVveuynn9o= gorm.io/hints v1.1.2/go.mod h1:/ARdpUHAtyEMCh5NNi3tI7FsGh+Cj/MIUlvNxCNCFWg= gorm.io/plugin/dbresolver v1.5.1 h1:s9Dj9f7r+1rE3nx/Ywzc85nXptUEaeOO0pt27xdopM8= gorm.io/plugin/dbresolver v1.5.1/go.mod h1:l4Cn87EHLEYuqUncpEeTC2tTJQkjngPSD+lo8hIvcT0= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= stathat.com/c/consistent v1.0.0 h1:ezyc51EGcRPJUxfHGSgJjWzJdj3NiMU9pNfLNGiXV0c= stathat.com/c/consistent v1.0.0/go.mod h1:QkzMWzcbB+yQBL2AttO6sgsQS/JSTapcDISJalmCDS0= ================================================ FILE: logic/captcha.go ================================================ package logic import ( "DiTing-Go/global" "DiTing-Go/utils" "encoding/json" "fmt" "math/rand" "time" ) // CheckCaptcha 检查验证码是否正确 func CheckCaptcha(captchaId, captchaValue string) bool { if captchaId == "" || captchaValue == "" { return false } // 根据captchaId从redis查 captchaByte, err := utils.GetValueFromRedis(captchaId) if err != nil { global.Logger.Errorf("get captcha from redis error: %v", err) return false } var captcha string err = json.Unmarshal(captchaByte, &captcha) if err != nil { global.Logger.Errorf("json unmarshal error: %v", err) return false } if captchaValue == "1234" { global.Logger.Infof("captcha is 1234, pass") return true } // 1234直接放行 return captchaValue == captcha } // GenerateCaptcha 生成验证码 func GenerateCaptcha(captchaId string) (string, error) { // 生成四位随机数 rand.Seed(time.Now().UnixNano()) captchaVal := rand.Intn(10000) // 生成1000~9999之间的随机数 val := fmt.Sprintf("%04d", captchaVal) // 格式化为四位数,不足前面补0 // 将验证码存入redis captchaKey := utils.MakeUserCaptchaKey(captchaId) err := utils.SetValueToRedis(captchaKey, val, 1*time.Minute) if err != nil { return "", err } return val, nil } // SendCaptcha 发送验证码 func SendCaptcha(phone, captcha string) error { return nil } ================================================ FILE: logic/login.go ================================================ package logic import ( "DiTing-Go/dal/model" "DiTing-Go/domain/enum" "DiTing-Go/global" "DiTing-Go/utils" "context" "strconv" "github.com/go-redis/redis" "github.com/goccy/go-json" "github.com/pkg/errors" ) // CheckPassword 校验用户名密码是否匹配 func CheckPassword(ctx context.Context, phone, password string) bool { // 参数验证 if phone == "" || password == "" { global.Logger.Warnf("CheckPassword: phone or password is empty, phone: %s", phone) return false } // 密码长度必须大于6 if len(password) < 6 { global.Logger.Warnf("CheckPassword: password too short, phone: %s, password length: %d", phone, len(password)) return false } // 对密码进行md5加密 encryptedPassword := utils.EncryptPassword(password) // 首先尝试从Redis获取用户ID userPhoneKey := utils.MakeUserPhoneKey(phone) userIdBytes, err := utils.GetValueFromRedis(userPhoneKey) if err == nil && len(userIdBytes) > 0 { userId := string(userIdBytes) global.Logger.Infof("CheckPassword: get userId from redis success, phone: %s, userId: %s", phone, userId) // 从Redis获取用户信息 userInfo, err := GetUserInfo2Redis(userId) if err != nil { if errors.Is(err, redis.Nil) { global.Logger.Infof("CheckPassword: user info not found in redis, phone: %s, userId: %s", phone, userId) } else { global.Logger.Errorf("CheckPassword: get user info from redis failed, phone: %s, userId: %s, err: %v", phone, userId, err) } // Redis获取失败,继续查询数据库 } else { // Redis获取成功,比较密码 global.Logger.Infof("CheckPassword: get user info from redis success, phone: %s, userId: %s", phone, userId) return userInfo.Password == encryptedPassword } } else if errors.Is(err, redis.Nil) { global.Logger.Infof("CheckPassword: userId not found in redis, phone: %s", phone) } else { global.Logger.Errorf("CheckPassword: get userId from redis failed, phone: %s, err: %v", phone, err) } // Redis查询失败,从数据库查询 global.Logger.Infof("CheckPassword: querying user from database, phone: %s", phone) userInfo, err := utils.QueryUserByPhone(ctx, phone) if err != nil { global.Logger.Errorf("CheckPassword: failed to query user by phone, phone: %s, err: %v", phone, err) return false } if userInfo == nil { global.Logger.Warnf("CheckPassword: user not found in database, phone: %s", phone) return false } // 比较密码 passwordMatch := userInfo.Password == encryptedPassword global.Logger.Infof("CheckPassword: password check result, phone: %s, match: %v", phone, passwordMatch) return passwordMatch } func SetUserInfo2Redis(userInfo model.User) error { userInfoByte, err := json.Marshal(userInfo) if err != nil { global.Logger.Errorf("userInfo %v,failed to marshal user info: %v", userInfo, err) return err } if err := utils.SetValueToRedis(strconv.FormatInt(userInfo.ID, 10), string(userInfoByte), enum.DefaultCacheTime); err != nil { global.Logger.Errorf("userInfo %v,failed to set user info to redis: %v", userInfo, err) return err } return nil } func GetUserInfo2Redis(userId string) (model.User, error) { if userId == "" || userId == "0" { return model.User{}, errors.New("userId is zero") } userInfoByte, err := utils.GetValueFromRedis(userId) if err != nil { if errors.Is(err, redis.Nil) { global.Logger.Infof("userId %s, user info not found in redis", userId) return model.User{}, err } global.Logger.Errorf("userId %s, failed to get user info from redis: %v", userId, err) return model.User{}, err } userInfo := model.User{} if err := json.Unmarshal([]byte(userInfoByte), &userInfo); err != nil { global.Logger.Errorf("userId %s, failed to unmarshal user info: %v", userId, err) return model.User{}, err } global.Logger.Infof("userId %s, user info found in redis: %v", userId, userInfo) return userInfo, nil } func GetUserInfo2DB(phone string) (model.User, error) { if phone == "" { return model.User{}, errors.New("phone is empty") } ctx := context.Background() userInfo, err := utils.QueryUserByPhone(ctx, phone) if err != nil { global.Logger.Errorf("phone %s, failed to query user by phone: %v", phone, err) return model.User{}, err } global.Logger.Infof("phone %s, user info found in db: %v", phone, userInfo) return *userInfo, nil } func GetUserInfo2DBById(ctx context.Context, userId string) (model.User, error) { if userId == "" { return model.User{}, errors.New("userId is zero") } userInfo, err := utils.QueryUserByID(ctx, userId) if err != nil { global.Logger.Errorf("userId %s, failed to query user by phone: %v", userId, err) return model.User{}, err } global.Logger.Infof("userId %s, user info found in db: %v", userId, userInfo) return *userInfo, nil } ================================================ FILE: logic/register.go ================================================ package logic import ( "DiTing-Go/domain/enum" "DiTing-Go/global" "DiTing-Go/utils" "context" "errors" "fmt" "github.com/go-redis/redis" "gorm.io/gorm" ) // CheckCaptchaProcess 检查验证码 func CheckCaptchaProcess(phone, captcha string) bool { // 检查验证码 phoneKey := utils.MakeUserCaptchaKey(phone) if !CheckCaptcha(phoneKey, captcha) { global.Logger.Infof("验证码错误") return false } return true } // CheckCaptchaExist 检查验证码是否存在 func CheckCaptchaExist(phone string) bool { // 检查验证码 phoneKey := utils.MakeUserCaptchaKey(phone) _, err := utils.GetValueFromRedis(phoneKey) if errors.Is(err, redis.Nil) { global.Logger.Infof("phoneKey:%s, 验证码不存在", phoneKey) return false } else if err != nil { global.Logger.Errorf("phoneKey:%s, 查询验证码失败: %v", phoneKey, err) return false } global.Logger.Infof("phoneKey:%s, 验证码存在", phoneKey) return true } // CheckPhoneInRedis 检查手机号在redis中是否存在 func CheckPhoneInRedis(phone string) bool { phoneKey := utils.MakeUserPhoneKey(phone) rst, err := utils.GetValueFromRedis(phoneKey) // 如果redis没查到,查数据库 if errors.Is(err, redis.Nil) { return false } // 如果redis查询出错,查数据库 if err != nil { global.Logger.Errorf("phoneKey:%s, 查询手机号失败: %v", phoneKey, err) return false } // 如果redis查到了,直接返回 if rst != nil { return true } return false } // CheckPhoneInDB 检查手机号在db中是否存在 func CheckPhoneInDB(ctx context.Context, phone string) (bool, error) { // 如果redis查不到,查数据库 user, err := utils.QueryUserByPhone(ctx, phone) // 数据库查询出错,返回失败 if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { global.Logger.Errorf("phone:%s, 查询手机号失败: %v", phone, err) return true, err } //数据库查到了,返回失败,且用户状态为正常 if user != nil && user.Status == enum.UserStatusNormal { // 将用户信息存入redis phoneKey := utils.MakeUserPhoneKey(phone) // 将phone->user.ID存入redis if err := utils.SetValueToRedis(phoneKey, fmt.Sprintf("%d", user.ID), enum.DefaultCacheTime); err != nil { global.Logger.Errorf("phone:%s, 设置phoneUid映射 redis失败: %v", phone, err) return true, err } global.Logger.Infof("phone:%s, 用户已存在", phone) return true, err } return false, nil } ================================================ FILE: logic/user.go ================================================ package logic import ( "DiTing-Go/dal/model" "DiTing-Go/dal/query" "DiTing-Go/domain/enum" "DiTing-Go/global" "context" "github.com/pkg/errors" "gorm.io/gorm" ) func CreateUser(ctx context.Context, userInfo model.User) error { user := query.User userQ := user.WithContext(ctx) userId := userInfo.ID // 检查用户是否存在 userResult, err := userQ.Where(user.ID.Eq(userId)).First() if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { global.Logger.Errorf("user:%v, 检查用户是否存在失败: %v", userResult, err) return err } // 将用户状态更新为正常 if userResult != nil && userResult.Status == int32(enum.UserStatusCancel) { global.Logger.Errorf("user:%v, 用户已注销", userResult) userInfo.Status = enum.UserStatusNormal if err := userQ.Save(&userInfo); err != nil { global.Logger.Errorf("user:%v, 更新用户状态失败: %v", userResult, err) return err } } else { // 创建对象 if err := userQ.Create(&userInfo); err != nil { global.Logger.Errorf("user:%v, 创建用户失败: %v", userResult, err) return err } } return nil } ================================================ FILE: logic/user_cancle.go ================================================ package logic import ( "DiTing-Go/dal/query" "DiTing-Go/domain/enum" "DiTing-Go/global" "DiTing-Go/utils" "context" "strconv" ) // DeleteUserInfoFromRedis 删除用户缓存 func DeleteUserInfoFromRedis(userId string) error { if err := utils.DeleteValueFromRedis(userId); err != nil { global.Logger.Errorf("删除用户缓存失败: userId=%s, err=%v", userId, err) return err } return nil } // DeleteUserInfoFromDB 删除用户数据库,软删除 func DeleteUserInfoFromDB(ctx context.Context, userId string) error { user := query.User userQ := user.WithContext(ctx) userIdInt, err := strconv.ParseInt(userId, 10, 64) if err != nil { global.Logger.Errorf("转换用户ID失败: userId=%s, err=%v", userId, err) return err } if _, err := userQ.Where(user.ID.Eq(userIdInt)).Update(user.Status, enum.UserStatusCancel); err != nil { global.Logger.Errorf("删除用户数据库失败: userId=%s, err=%v", userId, err) return err } return nil } ================================================ FILE: main.go ================================================ package main import ( //_ "DiTing-Go/event/listener" "DiTing-Go/global" "DiTing-Go/routes" "DiTing-Go/utils/setting" ) // swagger 中添加header.Authorization:token 校验 token // @securityDefinitions.apikey ApiKeyAuth // @in header // @name Authorization func main() { // 初始化配置 setting.ConfigInit() // 初始化数据库连接 global.DBInit() //初始化redis连接 global.RedisInit() global.LogInit() routes.InitRouter() } ================================================ FILE: pkg/domain/enum/code.go ================================================ package enum const ( SUCCESS = 200 ERROR = 500 INVALID_PARAMS = 400 ERROR_EXIST_TAG = 10001 ERROR_NOT_EXIST_TAG = 10002 ERROR_NOT_EXIST_ARTICLE = 10003 ERROR_AUTH_CHECK_TOKEN_FAIL = 20001 ERROR_AUTH_CHECK_TOKEN_TIMEOUT = 20002 ERROR_AUTH_TOKEN = 20003 ERROR_AUTH = 20004 ) ================================================ FILE: pkg/domain/enum/common_enum.go ================================================ package enum const ( YES = 2 NO = 1 NORMAL = 1 DELETED = 2 ) ================================================ FILE: pkg/domain/enum/msg.go ================================================ package enum var MsgFlags = map[int]string{ SUCCESS: "ok", ERROR: "fail", INVALID_PARAMS: "请求参数错误", ERROR_AUTH_CHECK_TOKEN_FAIL: "Token鉴权失败", ERROR_AUTH_CHECK_TOKEN_TIMEOUT: "Token已超时", ERROR_AUTH_TOKEN: "Token生成失败", ERROR_AUTH: "Token错误", } func GetMsg(code int) string { msg, ok := MsgFlags[code] if ok { return msg } return MsgFlags[ERROR] } ================================================ FILE: pkg/domain/vo/req/page_req.go ================================================ package req type PageReq struct { Cursor *string `json:"cursor" form:"cursor"` PageSize int `json:"pageSize" form:"pageSize" binding:"required"` } ================================================ FILE: pkg/domain/vo/resp/page_resp.go ================================================ package resp type PageResp struct { Cursor *string `json:"cursor" form:"cursor"` IsLast bool `json:"isLast" form:"is_last"` Data any `json:"data" form:"data"` } ================================================ FILE: pkg/domain/vo/resp/response.go ================================================ package resp import ( "DiTing-Go/pkg/domain/enum" "github.com/gin-gonic/gin" ) // ResponseData 表示统一响应的JSON格式 type ResponseData struct { Success bool `json:"success"` // 是否成功 Code int `json:"code"` // 状态码 Message string `json:"message"` // 响应消息 Data interface{} `json:"data"` // 响应数据 } // ErrorResponse 是一个辅助函数,用于创建错误响应 // 参数: // // c *gin.Context:Gin上下文对象,用于处理HTTP请求和响应 // code int:500 // message string:响应消息,用于描述响应的错误信息或提示信息 func ErrorResponse(c *gin.Context, message string) { c.JSON(enum.ERROR, ResponseData{ Code: enum.ERROR, Success: false, Message: message, Data: nil, }) } // SuccessResponse 是一个辅助函数,用于创建成功响应 // 参数: // // c *gin.Context:Gin上下文对象,用于处理HTTP请求和响应 // code int:200 // data interface{}:响应数据,用于描述请求处理成功后返回的具体数据 func SuccessResponse(c *gin.Context, data interface{}) { c.JSON(enum.SUCCESS, ResponseData{ Code: enum.SUCCESS, Success: true, Message: "success", Data: data, }) } func SuccessResponseWithMsg(c *gin.Context, msg string) { c.JSON(enum.SUCCESS, ResponseData{ Code: enum.SUCCESS, Success: true, Message: msg, Data: nil, }) } func ReturnSuccessResponse(c *gin.Context, response ResponseData) { c.JSON(enum.SUCCESS, response) } func ReturnErrorResponse(c *gin.Context, response ResponseData) { c.JSON(enum.ERROR, response) } // ErrorResponseData 是一个辅助函数,用于创建错误响应 func ErrorResponseData(msg string) ResponseData { return ResponseData{ Code: enum.ERROR, Success: false, Message: msg, Data: nil, } } // SuccessResponseData 是一个辅助函数,用于创建成功响应 func SuccessResponseData(data interface{}) ResponseData { return ResponseData{ Code: enum.SUCCESS, Success: true, Message: "success", Data: data, } } // SuccessResponseDataWithMsg 是一个辅助函数,用于创建成功响应 func SuccessResponseDataWithMsg(msg string) ResponseData { return ResponseData{ Code: enum.SUCCESS, Success: true, Message: msg, Data: nil, } } ================================================ FILE: pkg/utils/cursor_utils.go ================================================ package utils import ( pkgReq "DiTing-Go/pkg/domain/vo/req" pkgResp "DiTing-Go/pkg/domain/vo/resp" "fmt" "github.com/pkg/errors" "gorm.io/gorm" "reflect" "regexp" "time" ) // Paginate 是通用的游标分页函数 // TODO: select部分字段 func Paginate(db *gorm.DB, params pkgReq.PageReq, result interface{}, cursorFieldName string, isAsc bool, conditions ...interface{}) (*pkgResp.PageResp, error) { var resp pkgResp.PageResp query := db if len(conditions) > 0 { query = query.Where(conditions[0], conditions[1:]...) } if params.Cursor != nil && *params.Cursor != "" { query = query.Where(fmt.Sprintf("%s < ?", cursorFieldName), *params.Cursor) } if isAsc { query = query.Order(fmt.Sprintf("%s ASC", cursorFieldName)) } else { query = query.Order(fmt.Sprintf("%s DESC", cursorFieldName)) } query = query.Limit(params.PageSize).Find(result) if query.Error != nil { return &resp, query.Error } // 获取查询结果的切片值 slice := reflect.ValueOf(result).Elem() // 根据记录条数是否等于页大小判断是否是最后一页 lastItemIndex := slice.Len() if lastItemIndex < params.PageSize { resp.IsLast = true } else { resp.IsLast = false } // 通过反射获取cursorFieldName对应的值 if lastItemIndex > 0 { lastItem := slice.Index(lastItemIndex - 1) fieldsMap, err := GetTagName(result) if err != nil { return nil, err } cursorValue := lastItem.FieldByName(fieldsMap[cursorFieldName]) //获取cursorValue的类型 cursorType := cursorValue.Type() // 如果是Time.time类型,转换为时间戳 cursorStr := "" if cursorType.String() == "time.Time" { cursorStr = fmt.Sprint(cursorValue.Interface().(time.Time).UnixNano()) } else { cursorStr = fmt.Sprint(cursorValue.Interface()) } resp.Cursor = &cursorStr } resp.Data = result return &resp, nil } // GetTagName 获取结构体中Tag的值,如果没有tag则返回字段值 func GetTagName(structName interface{}) (map[string]string, error) { t := reflect.TypeOf(structName).Elem().Elem() fieldNum := t.NumField() result := make(map[string]string, fieldNum) for i := 0; i < fieldNum; i++ { fieldName := t.Field(i).Name tag := t.Field(i).Tag.Get("gorm") if tag == "" { result[fieldName] = fieldName } else { // 定义正则表达式 re := regexp.MustCompile(`column:([^;]+)`) // 使用正则表达式查找匹配项 match := re.FindStringSubmatch(tag) column := fieldName if len(match) > 1 { column = match[1] } else { return nil, errors.New("without column error") } result[column] = fieldName } } return result, nil } ================================================ FILE: pkg/utils/redis.go ================================================ package utils import ( domainEnum "DiTing-Go/domain/enum" "DiTing-Go/global" "github.com/go-redis/redis" "github.com/goccy/go-json" "github.com/jinzhu/copier" "github.com/pkg/errors" ) // SetString 设置字符串 func SetString(key string, value any) error { valueByte, err := json.Marshal(value) if err = global.Rdb.Set(key, valueByte, domainEnum.DefaultCacheTime).Err(); err != nil { return errors.New("redis set error") } return nil } // GetString 获取字符串 func GetString(key string, value any) error { valueByte, err := global.Rdb.Get(key).Result() if err != nil && errors.Is(err, redis.Nil) { return err } else if err != nil { return errors.New("redis get error") } err = json.Unmarshal([]byte(valueByte), value) if err != nil { return errors.New("jsonUtils unmarshal error") } return nil } // GetData 获取数据 func GetData(cacheKey string, value any, dbQueryFunc func() (interface{}, error)) error { // 1. 从缓存中获取数据 err := GetString(cacheKey, value) // 查询到数据 if err == nil { return nil } else if !errors.Is(err, redis.Nil) { return err } err = QueryAndSet(cacheKey, value, dbQueryFunc) if err != nil { return err } return nil } // QueryAndSet 查询数据库并设置缓存 func QueryAndSet(cacheKey string, value any, dbQueryFunc func() (interface{}, error)) error { // 2. 从数据库中获取数据 result, err := dbQueryFunc() if err != nil { return err } err = copier.Copy(value, result) if err != nil { global.Logger.Errorf("拷贝数据失败: %v", err) return err } // 3. 将查询结果写回缓存 if err = SetString(cacheKey, result); err != nil { global.Logger.Errorf("写入redis失败: %v", err) return err } return err } func RemoveData(key string) { global.Rdb.Del(key) } ================================================ FILE: pkg/utils/redis_lock.go ================================================ package utils //type RedSyncLock struct { // mutex *redsync.Mutex // ctx context.Context // cancel context.CancelFunc //} // //// GetLock 获取分布式锁 //func GetLock(key string) (*RedSyncLock, error) { // ctx, cancel := context.WithCancel(context.Background()) // mutex := global.RedSync.NewMutex(key) // if err := mutex.LockContext(ctx); err != nil { // cancel() // global.Logger.Errorf("加锁失败 %s", err) // return nil, errors.New("Business Error") // } // // 开启一个goroutine,周期性地续租锁 // go func() { // ticker := time.NewTicker(5 * time.Second) // 按照需求调整 // defer ticker.Stop() // for { // select { // case <-ticker.C: // ok, err := mutex.Extend() // //fmt.Printf("续租锁 %s\n", ok) // if err != nil { // global.Logger.Errorf("Failed to extend lock: %s", err) // return // } else if !ok { // global.Logger.Errorf("Failed to extend lock: %s", fmt.Errorf("lock %s is not held", key)) // return // } // case <-ctx.Done(): // return // } // } // }() // // return &RedSyncLock{ // mutex: mutex, // ctx: ctx, // cancel: cancel, // }, nil //} // //// ReleaseLock 释放分布式锁, 释放锁失败不影响业务 //func ReleaseLock(lock *RedSyncLock) { // lock.cancel() // mutex := lock.mutex // _, err := mutex.Unlock() // if err != nil { // global.Logger.Errorf("解锁失败 %s", err) // } //} ================================================ FILE: pkg/utils/sort.go ================================================ package utils type Int64Slice []int64 func (p Int64Slice) Len() int { return len(p) } func (p Int64Slice) Less(i, j int) bool { return p[i] < p[j] } func (p Int64Slice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } ================================================ FILE: routes/init_router.go ================================================ package routes import ( "DiTing-Go/controller" _ "DiTing-Go/docs" "DiTing-Go/pkg/domain/vo/resp" "DiTing-Go/utils/middleware" "DiTing-Go/websocket/global" websocketService "DiTing-Go/websocket/service" "log" "net/http" "github.com/gin-gonic/gin" swaggerFiles "github.com/swaggo/files" ginSwagger "github.com/swaggo/gin-swagger" ) // InitRouter 初始化路由 func InitRouter() { //go initWebSocket() initGin() } // 初始化websocket func initWebSocket() { http.HandleFunc("/websocket", websocketService.Connect) log.Fatal(http.ListenAndServe("localhost:5001", nil)) } // 初始化gin func initGin() { router := gin.Default() router.Use(middleware.LoggerToFile()) router.Use(middleware.Cors()) //添加swagger访问路由 router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) // 不需要身份验证的路由 apiPublic := router.Group("/api/public") { // 用户注册 apiPublic.POST("/register", controller.RegisterController) // 验证码发送 apiPublic.POST("/captcha", controller.CaptchaController) // 用户登录 apiPublic.POST("/login", controller.LoginController) } apiUser := router.Group("/api/user") apiUser.Use(middleware.JWT()) { // 注销账户 apiUser.DELETE("/cancel", controller.CancelController) // //添加好友 // apiUser.POST("/add", controller.ApplyFriendController) // //删除好友 // apiUser.DELETE("/delete/", controller.DeleteFriendController) // //同意好友申请 // apiUser.PUT("/agree", controller.AgreeFriendController) // //获取好友申请列表 // apiUser.GET("/getApplyList", controller.GetUserApplyController) // //获取好友列表 // apiUser.GET("/getFriendList", controller.GetFriendListController) // // 判断是否是好友 // apiUser.GET("/isFriend/:friendUid", controller.IsFriendController) // //好友申请未读数量 // apiUser.GET("/unreadApplyNum", controller.UnreadApplyNumController) // //根据好友昵称搜索好友 // apiUser.GET("/getUserInfoByName", controller.GetUserInfoByNameController) // // TODO:测试使用 // apiUser.GET("/test", test) } //apiGroup := router.Group("/api/group") //apiGroup.Use(middleware.JWT()) //{ // //创建群聊 // apiGroup.POST("/create", controller.CreateGroupController) // apiGroup.DELETE("/:id", service.DeleteGroupService) // apiGroup.POST("/join", service.JoinGroupService) // apiGroup.POST("/quit", service.QuitGroupService) // apiGroup.GET("/getGroupMemberList", service.GetGroupMemberListService) // apiGroup.POST("/grantAdministrator", service.GrantAdministratorService) // apiGroup.POST("/removeAdministrator", service.RemoveAdministratorService) //} // //apiContact := router.Group("/api/contact") //apiContact.Use(middleware.JWT()) //{ // apiContact.GET("getContactList", controller.GetContactListController) // apiContact.GET("getNewContactList", controller.GetNewContactListController) // apiContact.GET("getMessageList", service.GetContactDetailService) // apiContact.GET("getNewMsgList", controller.GetNewMsgListController) // apiContact.POST("userInfo/batch", controller.GetUserInfoBatchController) //} // //apiMsg := router.Group("/api/chat") //apiMsg.Use(middleware.JWT()) //{ // apiMsg.POST("msg", controller.SendMessageController) //} //apiFile := router.Group("/api/file") //apiFile.Use(middleware.JWT()) //{ // apiFile.GET("getPreSigned", service.GetPreSigned) //} err := router.Run(":5000") if err != nil { return } } // TODO:测试使用 func test(c *gin.Context) { msg := new(global.Msg) msg.Uid = 2 websocketService.Send(msg.Uid, []byte("{\"type\":4}")) resp.SuccessResponse(c, nil) } ================================================ FILE: service/adapter/build_contact_dao_list.go ================================================ package adapter import ( "DiTing-Go/dal/model" "DiTing-Go/domain/dto" "DiTing-Go/domain/enum" cmap "github.com/orcaman/concurrent-map/v2" "strconv" ) type RoomDto struct { ID int64 Avatar string Name string Type int } func BuildContactDaoList(contactList []model.Contact, userList []*model.User, messageList []*model.Message, roomList []*model.Room, roomFriendList []*model.RoomFriend, roomGroupList []*model.RoomGroup, countMap cmap.ConcurrentMap[string, int64]) []dto.ContactDto { contactDtoList := make([]dto.ContactDto, 0) userMap := make(map[int64]*model.User) for _, user := range userList { userMap[user.ID] = user } msgMap := make(map[int64]*model.Message) for _, msg := range messageList { msgMap[msg.ID] = msg } roomFriendMap := make(map[int64]*model.RoomFriend) roomGroupMap := make(map[int64]*model.RoomGroup) for _, roomFriend := range roomFriendList { roomFriendMap[roomFriend.RoomID] = roomFriend } for _, roomGroup := range roomGroupList { roomGroupMap[roomGroup.RoomID] = roomGroup } roomMap := make(map[int64]RoomDto) for _, room := range roomList { roomDto := RoomDto{} roomDto.ID = room.ID if room.Type == enum.PERSONAL { userId := roomFriendMap[room.ID].Uid1 if userId == contactList[0].UID { userId = roomFriendMap[room.ID].Uid2 } user := userMap[userId] roomDto.Avatar = user.Avatar roomDto.Name = user.Name roomDto.Type = enum.PERSONAL } else { roomGroup := roomGroupMap[room.ID] roomDto.Avatar = roomGroup.Avatar roomDto.Name = roomGroup.Name roomDto.Type = enum.GROUP } roomMap[room.ID] = roomDto } for _, contact := range contactList { contactDto := dto.ContactDto{} contactDto.ID = contact.ID contactDto.RoomID = contact.RoomID contactDto.Avatar = roomMap[contact.RoomID].Avatar contactDto.Name = roomMap[contact.RoomID].Name if msgMap[contact.LastMsgID] != nil { contactDto.LastMsg = msgMap[contact.LastMsgID].Content } contactDto.LastTime = contact.ActiveTime.UnixMilli() //TODO:统计未读消息数 unreadCount, _ := countMap.Get(strconv.FormatInt(contact.RoomID, 10)) contactDto.UnreadCount = int32(unreadCount) contactDto.Type = roomMap[contact.RoomID].Type contactDtoList = append(contactDtoList, contactDto) } return contactDtoList } ================================================ FILE: service/adapter/build_message_resp.go ================================================ package adapter import ( "DiTing-Go/dal/model" "DiTing-Go/domain/vo/resp" ) func BuildMessageRespByMsgAndUser(msgList *[]model.Message, userMap map[int64]*model.User) []resp.MessageResp { var messageRespList []resp.MessageResp for i := range len(*msgList) { messageResp := resp.MessageResp{} msg := (*msgList)[i] msgUser := resp.MsgUser{} msgUser.Uid = userMap[msg.FromUID].ID msgUser.Username = userMap[msg.FromUID].Name msgUser.Avatar = userMap[msg.FromUID].Avatar messageResp.FromUser = msgUser message := resp.Msg{} message.ID = msg.ID message.RoomId = msg.RoomID message.Type = msg.Type message.Body.Content = msg.Content message.Body.Reply = msg.ReplyMsgID messageResp.Message = message messageResp.SendTime = msg.CreateTime.UnixNano() messageRespList = append(messageRespList, messageResp) } return messageRespList } ================================================ FILE: service/adapter/build_user_info_by_name_resp.go ================================================ package adapter import ( "DiTing-Go/dal/model" "DiTing-Go/domain/vo/resp" ) func BuildUserInfoByNameResp(userList []*model.User, userApply []*model.UserApply, userFriend []*model.UserFriend) []resp.GetUserInfoByNameResp { userApplyMap := make(map[int64]*model.UserApply) for _, apply := range userApply { userApplyMap[apply.TargetID] = apply } userFriendMap := make(map[int64]*model.UserFriend) for _, friend := range userFriend { userFriendMap[friend.FriendUID] = friend } getUserInfoByNameRespList := make([]resp.GetUserInfoByNameResp, 0) for _, user := range userList { getUserInfoByNameResp := resp.GetUserInfoByNameResp{ Uid: user.ID, Name: user.Name, Avatar: user.Avatar, } //TODO:抽象为常量 if userFriendMap[user.ID] != nil { getUserInfoByNameResp.Status = 3 } else if userApplyMap[user.ID] != nil { getUserInfoByNameResp.Status = 2 } else { getUserInfoByNameResp.Status = 1 } getUserInfoByNameRespList = append(getUserInfoByNameRespList, getUserInfoByNameResp) } return getUserInfoByNameRespList } ================================================ FILE: service/captcha_service.go ================================================ package service import ( "DiTing-Go/domain/vo/req" "DiTing-Go/global" "DiTing-Go/logic" pkgResp "DiTing-Go/pkg/domain/vo/resp" ) // CaptchaService 验证码发送 func CaptchaService(captchaReq req.CaptchaReq) (pkgResp.ResponseData, error) { if captchaReq.Phone == "" { global.Logger.Infof("手机号不能为空") return pkgResp.ErrorResponseData("手机号不能为空"), nil } //检查验证码是否未过期,未过期不生成 if logic.CheckCaptchaExist(captchaReq.Phone) { global.Logger.Infof("手机号:%s,验证码未过期", captchaReq.Phone) return pkgResp.ErrorResponseData("发送频率过高,请稍后再试"), nil } // 生成验证码 captcha, err := logic.GenerateCaptcha(captchaReq.Phone) if err != nil { global.Logger.Errorf("phone:%s ,生成验证码失败: %v", captchaReq.Phone, err) return pkgResp.ErrorResponseData("系统繁忙,请稍后再试"), err } // 发送验证码 // TODO:空实现,实际应用中可以使用短信服务商的SDK发送验证码 if err := logic.SendCaptcha(captchaReq.Phone, captcha); err != nil { global.Logger.Errorf("phone:%s ,发送验证码失败: %v", captchaReq.Phone, err) return pkgResp.ErrorResponseData("验证码发送失败,请稍后再试"), err } return pkgResp.SuccessResponseDataWithMsg("验证码发送成功"), nil } ================================================ FILE: service/contact_service.go ================================================ package service // //import ( // "DiTing-Go/dal" // "DiTing-Go/dal/model" // "DiTing-Go/domain/enum" // "DiTing-Go/domain/vo/req" // domainResp "DiTing-Go/domain/vo/resp" // "DiTing-Go/global" // pkgEnum "DiTing-Go/pkg/domain/enum" // pkgReq "DiTing-Go/pkg/domain/vo/req" // "DiTing-Go/pkg/domain/vo/resp" // pkgResp "DiTing-Go/pkg/domain/vo/resp" // "DiTing-Go/pkg/utils" // "DiTing-Go/service/adapter" // "context" // "github.com/gin-gonic/gin" // cmap "github.com/orcaman/concurrent-map/v2" // "github.com/pkg/errors" // "strconv" // "sync" // "time" //) // //func GetContactListService(uid int64, pageReq pkgReq.PageReq) (pkgResp.ResponseData, error) { // db := dal.DB // contact := make([]model.Contact, 0) // condition := []interface{}{"uid=?", strconv.FormatInt(uid, 10)} // if pageReq.Cursor != nil && *pageReq.Cursor != "" { // // 时间戳转时间 // timestamp, err := strconv.ParseInt(*pageReq.Cursor, 10, 64) // if err != nil { // global.Logger.Errorf("时间戳转换失败 %s", err) // return pkgResp.ErrorResponseData("系统繁忙,请稍后再试~"), errors.New("Business Error") // } // cursor := time.Unix(0, timestamp) // cursorStr := cursor.Format(time.RFC3339Nano) // pageReq.Cursor = &cursorStr // } // // pageResp, err := utils.Paginate(db, pageReq, &contact, "active_time", false, condition...) // if err != nil { // global.Logger.Errorf("查询会话列表失败 %s", err) // return pkgResp.ErrorResponseData("系统繁忙,请稍后再试~"), errors.New("Business Error") // } // contactList := pageResp.Data.(*[]model.Contact) // // // 收集会话id // contactRoomIdList := make([]int64, 0) // for _, contact := range *contactList { // contactRoomIdList = append(contactRoomIdList, contact.RoomID) // } // // // 查询出对应的房间信息 // ctx := context.Background() // room := global.Query.Room // roomQ := room.WithContext(ctx) // // 查询房间类型 // roomRList, err := roomQ.Where(room.ID.In(contactRoomIdList...)).Find() // if err != nil { // global.Logger.Errorf("查询房间失败 %s", err) // return pkgResp.ErrorResponseData("系统繁忙,请稍后再试~"), errors.New("Business Error") // } // // 收集单聊房间的id // roomFriendIdList := make([]int64, 0) // // 收集群聊房间的id // roomGroupIdList := make([]int64, 0) // for _, room := range roomRList { // if room.Type == enum.PERSONAL { // roomFriendIdList = append(roomFriendIdList, room.ID) // } else if room.Type == enum.GROUP { // roomGroupIdList = append(roomGroupIdList, room.ID) // } // } // // // 查询好友房间信息 // roomFriend := global.Query.RoomFriend // roomFriendQ := roomFriend.WithContext(ctx) // roomFriendRList, err := roomFriendQ.Where(roomFriend.RoomID.In(roomFriendIdList...)).Find() // if err != nil { // global.Logger.Errorf("查询好友房间失败 %s", err) // return pkgResp.ErrorResponseData("系统繁忙,请稍后再试~"), errors.New("Business Error") // } // // 查询用户信息 // uidList := make([]int64, 0) // for _, roomFriend := range roomFriendRList { // if roomFriend.Uid1 == uid { // uidList = append(uidList, roomFriend.Uid2) // } else { // uidList = append(uidList, roomFriend.Uid1) // } // } // user := global.Query.User // userQ := user.WithContext(ctx) // userRList, err := userQ.Where(user.ID.In(uidList...)).Find() // if err != nil { // global.Logger.Errorf("查询用户失败 %s", err) // return pkgResp.ErrorResponseData("系统繁忙,请稍后再试~"), errors.New("Business Error") // } // // // 查询群聊房间信息 // roomGroup := global.Query.RoomGroup // roomGroupQ := roomGroup.WithContext(ctx) // roomGroupRList, err := roomGroupQ.Where(roomGroup.RoomID.In(roomGroupIdList...)).Find() // if err != nil { // global.Logger.Errorf("查询群聊房间失败 %s", err) // return pkgResp.ErrorResponseData("系统繁忙,请稍后再试~"), errors.New("Business Error") // } // // // 查询最后一条消息 // lastMsgIdList := make([]int64, 0) // for _, contact := range *contactList { // lastMsgIdList = append(lastMsgIdList, contact.LastMsgID) // } // msg := global.Query.Message // msgQ := msg.WithContext(ctx) // msgRList, err := msgQ.Where(msg.ID.In(lastMsgIdList...)).Find() // if err != nil { // global.Logger.Errorf("查询最后一条消息失败 %s", err) // return pkgResp.ErrorResponseData("系统繁忙,请稍后再试~"), errors.New("Business Error") // } // // // 查询未读消息数 // //fixme: 有没有更好的方法 // countMap := cmap.New[int64]() // var wg sync.WaitGroup // err = nil // for i, contact := range *contactList { // wg.Add(1) // go func() { // // defer wg.Done() // var count int64 // count, err = msgQ.Where(msg.RoomID.Eq(contact.RoomID), msg.DeleteStatus.Eq(pkgEnum.NORMAL), msg.CreateTime.Gt(contact.ReadTime)).Limit(99).Count() // if err != nil { // global.Logger.Errorf("统计未读数失败 %s", err) // } // countMap.Set(strconv.FormatInt(contact.RoomID, 10), count) // // }() // if i == 2 { // break // } // // } // wg.Wait() // if err != nil { // return pkgResp.ErrorResponseData("系统繁忙,请稍后再试~"), errors.New("Business Error") // } // // // 拼装结果 // contactDaoList := adapter.BuildContactDaoList(*contactList, userRList, msgRList, roomRList, roomFriendRList, roomGroupRList, countMap) // // pageResp.Data = contactDaoList // return pkgResp.SuccessResponseData(pageResp), nil //} // //// FIXME: 合并GetNewContactListService和GetContactListService //func GetNewContactListService(uid int64, timestamp int64) (pkgResp.ResponseData, error) { // contactTime := time.Unix(0, timestamp*1000*1000) // // ctx := context.Background() // contact := global.Query.Contact // contactQ := contact.WithContext(ctx) // contactRList, err := contactQ.Where(contact.UID.Eq(uid), contact.ActiveTime.Gte(contactTime)).Find() // if err != nil { // global.Logger.Errorf("查询会话列表失败 %s", err) // return pkgResp.ErrorResponseData("系统繁忙,请稍后再试~"), errors.New("Business Error") // } // // // 收集会话id // contactRoomIdList := make([]int64, 0) // for _, contact := range contactRList { // contactRoomIdList = append(contactRoomIdList, contact.RoomID) // } // // // 查询出对应的房间信息 // room := global.Query.Room // roomQ := room.WithContext(ctx) // // 查询房间类型 // roomRList, err := roomQ.Where(room.ID.In(contactRoomIdList...)).Find() // if err != nil { // global.Logger.Errorf("查询房间失败 %s", err) // return pkgResp.ErrorResponseData("系统繁忙,请稍后再试~"), errors.New("Business Error") // } // // 收集单聊房间的id // roomFriendIdList := make([]int64, 0) // // 收集群聊房间的id // roomGroupIdList := make([]int64, 0) // for _, room := range roomRList { // if room.Type == enum.PERSONAL { // roomFriendIdList = append(roomFriendIdList, room.ID) // } else if room.Type == enum.GROUP { // roomGroupIdList = append(roomGroupIdList, room.ID) // } // } // // // 查询好友房间信息 // roomFriend := global.Query.RoomFriend // roomFriendQ := roomFriend.WithContext(ctx) // roomFriendRList, err := roomFriendQ.Where(roomFriend.RoomID.In(roomFriendIdList...)).Find() // if err != nil { // global.Logger.Errorf("查询好友房间失败 %s", err) // return pkgResp.ErrorResponseData("系统繁忙,请稍后再试~"), errors.New("Business Error") // } // // 查询用户信息 // uidList := make([]int64, 0) // for _, roomFriend := range roomFriendRList { // if roomFriend.Uid1 == uid { // uidList = append(uidList, roomFriend.Uid2) // } else { // uidList = append(uidList, roomFriend.Uid1) // } // } // user := global.Query.User // userQ := user.WithContext(ctx) // userRList, err := userQ.Where(user.ID.In(uidList...)).Find() // if err != nil { // global.Logger.Errorf("查询用户失败 %s", err) // return pkgResp.ErrorResponseData("系统繁忙,请稍后再试~"), errors.New("Business Error") // } // // // 查询群聊房间信息 // roomGroup := global.Query.RoomGroup // roomGroupQ := roomGroup.WithContext(ctx) // roomGroupRList, err := roomGroupQ.Where(roomGroup.RoomID.In(roomGroupIdList...)).Find() // if err != nil { // global.Logger.Errorf("查询群聊房间失败 %s", err) // return pkgResp.ErrorResponseData("系统繁忙,请稍后再试~"), errors.New("Business Error") // } // // // 查询最后一条消息 // lastMsgIdList := make([]int64, 0) // for _, contact := range contactRList { // lastMsgIdList = append(lastMsgIdList, contact.LastMsgID) // } // msg := global.Query.Message // msgQ := msg.WithContext(ctx) // msgRList, err := msgQ.Where(msg.ID.In(lastMsgIdList...)).Find() // if err != nil { // global.Logger.Errorf("查询最后一条消息失败 %s", err) // return pkgResp.ErrorResponseData("系统繁忙,请稍后再试~"), errors.New("Business Error") // } // // // 查询未读消息数 // //fixme: 有没有更好的方法 // countMap := cmap.New[int64]() // var wg sync.WaitGroup // err = nil // for _, contact := range contactRList { // wg.Add(1) // go func() { // defer wg.Done() // var count int64 // count, err = msgQ.Where(msg.RoomID.Eq(contact.RoomID), msg.DeleteStatus.Eq(pkgEnum.NORMAL), msg.CreateTime.Gt(contact.ReadTime)).Limit(99).Count() // if err != nil { // global.Logger.Errorf("统计未读数失败 %s", err) // } // countMap.Set(strconv.FormatInt(contact.RoomID, 10), count) // }() // } // wg.Wait() // if err != nil { // return pkgResp.ErrorResponseData("系统繁忙,请稍后再试~"), errors.New("Business Error") // } // // // 类型转换 // temp := make([]model.Contact, 0) // for _, contact := range contactRList { // temp = append(temp, *contact) // } // // // 拼装结果 // contactDaoList := adapter.BuildContactDaoList(temp, userRList, msgRList, roomRList, roomFriendRList, roomGroupRList, countMap) // // return pkgResp.SuccessResponseData(contactDaoList), nil //} // //func GetContactDetailService(c *gin.Context) { // uid := c.GetInt64("uid") // getMessageListReq := req.GetMessageListReq{} // if err := c.ShouldBindQuery(&getMessageListReq); err != nil { //ShouldBind()会自动推导 // resp.ErrorResponse(c, "参数错误") // c.Abort() // return // } // roomId := getMessageListReq.RoomId // cursor, err := timestampToTime(getMessageListReq.Cursor) // if err != nil { // global.Logger.Errorf("时间戳转换失败 %s", err) // resp.ErrorResponse(c, "系统正忙,请稍后再试") // c.Abort() // return // } // pageRequest := pkgReq.PageReq{ // Cursor: cursor, // PageSize: getMessageListReq.PageSize, // } // // 更新会话表 // contact := global.Query.Contact // contactQ := contact.WithContext(context.Background()) // _, err = contactQ.Where(contact.UID.Eq(uid), contact.RoomID.Eq(roomId)).Update(contact.ReadTime, time.Now()) // if err != nil { // global.Logger.Errorf("更新会话失败 %s", err) // resp.ErrorResponse(c, "系统正忙,请稍后再试") // c.Abort() // return // } // // // 获取会话详情 // pageResp, err := GetContactDetail(roomId, pageRequest) // if err != nil { // global.Logger.Errorf("查询会话详情失败 %s", err) // resp.ErrorResponse(c, "系统正忙,请稍后再试") // c.Abort() // return // } // resp.SuccessResponse(c, pageResp) // return //} // //func GetContactDetail(roomID int64, pageRequest pkgReq.PageReq) (*pkgResp.PageResp, error) { // // 查询消息 // db := dal.DB // msgs := make([]model.Message, 0) // condition := []interface{}{"room_id=? AND delete_status=?", strconv.FormatInt(roomID, 10), pkgEnum.NORMAL} // pageResp, err := utils.Paginate(db, pageRequest, &msgs, "create_time", false, condition...) // if err != nil { // global.Logger.Errorf("查询消息失败: %s", err.Error()) // return nil, err // } // msgList := *(pageResp.Data.(*[]model.Message)) // for i, j := 0, len(msgList)-1; i < j; i, j = i+1, j-1 { // msgList[i], msgList[j] = msgList[j], msgList[i] // } // userIdMap := make(map[int64]*int64) // for _, msg := range msgList { // if userIdMap[msg.FromUID] == nil { // userIdMap[msg.FromUID] = &msg.FromUID // } // } // // 转换成列表 // userIdList := make([]int64, 0) // for _, uid := range userIdMap { // userIdList = append(userIdList, *uid) // } // // 查询用户信息 // ctx := context.Background() // user := global.Query.User // userQ := user.WithContext(ctx) // users, err := userQ.Where(user.ID.In(userIdList...)).Find() // if err != nil { // global.Logger.Errorf("查询用户失败: %s", err.Error()) // return nil, err // } // userMap := make(map[int64]*model.User) // for _, user := range users { // userMap[user.ID] = user // } // // // 拼装结果 // pageResp.Data = adapter.BuildMessageRespByMsgAndUser(&msgList, userMap) // return pageResp, nil //} //func GetNewMsgService(msgId int64, roomId int64) (pkgResp.ResponseData, error) { // ctx := context.Background() // // 查询消息 // msg := global.Query.Message // msgQ := msg.WithContext(ctx) // msgRList, err := msgQ.Where(msg.ID.Gt(msgId), msg.RoomID.Eq(roomId)).Find() // if err != nil { // global.Logger.Errorf("查询消息失败: %s", err.Error()) // return pkgResp.ErrorResponseData("接收消息失败"), err // } // // userIdMap := make(map[int64]*int64) // for _, msg := range msgRList { // if userIdMap[msg.FromUID] == nil { // userIdMap[msg.FromUID] = &msg.FromUID // } // } // // // 转换成列表 // userIdList := make([]int64, 0) // for _, uid := range userIdMap { // userIdList = append(userIdList, *uid) // } // // 查询用户信息 // user := global.Query.User // userQ := user.WithContext(ctx) // users, err := userQ.Where(user.ID.In(userIdList...)).Find() // if err != nil { // global.Logger.Errorf("查询用户失败: %s", err.Error()) // return pkgResp.ErrorResponseData("接收消息失败"), err // } // userMap := make(map[int64]*model.User) // for _, user := range users { // userMap[user.ID] = user // } // // temp := make([]model.Message, 0) // for _, msg := range msgRList { // temp = append(temp, *msg) // } // // // 拼装结果 // data := adapter.BuildMessageRespByMsgAndUser(&temp, userMap) // return pkgResp.SuccessResponseData(data), nil //} // //func timestampToTime(timestampStr *string) (*string, error) { // if timestampStr != nil && *timestampStr != "" && *timestampStr != "null" { // // 时间戳转时间 // timestamp, err := strconv.ParseInt(*timestampStr, 10, 64) // if err != nil { // global.Logger.Errorf("时间戳转换失败 %s", err) // return nil, err // } // cursor := time.Unix(0, timestamp) // cursorStr := cursor.Format(time.RFC3339Nano) // return &cursorStr, nil // } // return nil, nil //} // //func GetUserInfoBatchService(reqList req.GetUserInfoBatchReq) (pkgResp.ResponseData, error) { // ctx := context.Background() // user := global.Query.User // userQ := user.WithContext(ctx) // uids := make([]int64, 0) // // userMap := make(map[int64]*req.UserInfoBatchReqItem) // for _, item := range reqList.List { // uids = append(uids, item.Uid) // userMap[item.Uid] = &item // } // users, err := userQ.Where(user.ID.In(uids...)).Find() // if err != nil { // global.Logger.Errorf("查询用户失败 %s", err) // return pkgResp.ErrorResponseData("系统繁忙,请稍后再试~"), errors.New("Business Error") // } // // resultList := make([]domainResp.GetUserInfoBatchResp, 0) // for _, user := range users { // resultItem := domainResp.GetUserInfoBatchResp{ // Uid: user.ID, // } // if user.UpdateTime.UnixMilli() > userMap[user.ID].LastModifyTime { // resultItem.Username = user.Name // resultItem.Avatar = user.Avatar // resultItem.NeedRefresh = true // } else { // resultItem.NeedRefresh = false // } // resultList = append(resultList, resultItem) // } // // return pkgResp.SuccessResponseData(resultList), nil // //} ================================================ FILE: service/friend_service.go ================================================ package service // //import ( // "DiTing-Go/dal" // "DiTing-Go/dal/model" // "DiTing-Go/domain/dto" // domainEnum "DiTing-Go/domain/enum" // "DiTing-Go/domain/vo/req" // domainResp "DiTing-Go/domain/vo/resp" // "DiTing-Go/global" // "DiTing-Go/pkg/domain/enum" // pkgReq "DiTing-Go/pkg/domain/vo/req" // "DiTing-Go/pkg/domain/vo/resp" // "DiTing-Go/pkg/utils" // "DiTing-Go/utils/jsonUtils" // "DiTing-Go/utils/redisCache" // "context" // "fmt" // "github.com/pkg/errors" // "gorm.io/gen" // "gorm.io/gorm" // "sort" // "strconv" // "time" //) // //// ApplyFriendService 添加好友 //func ApplyFriendService(uid int64, applyReq req.UserApplyReq) (resp.ResponseData, error) { // ctx := context.Background() // friendUid := applyReq.Uid // user := global.Query.User // userQ := user.WithContext(ctx) // // uids := utils.Int64Slice{uid, friendUid} // sort.Sort(uids) // key := fmt.Sprintf(domainEnum.UserAndFriendLock, uids[0], uids[1]) // mutex, err := utils.GetLock(key) // if err != nil { // return resp.ErrorResponseData("系统正忙,请稍后再试"), err // } // defer utils.ReleaseLock(mutex) // // //检查用户是否存在 // fun := func() (interface{}, error) { // return userQ.Where(user.ID.Eq(friendUid)).First() // } // userR := model.User{} // key = fmt.Sprintf(domainEnum.UserCacheByID, applyReq.Uid) // err = utils.GetData(key, &userR, fun) // if err != nil { // if errors.Is(err, gorm.ErrRecordNotFound) { // return resp.ErrorResponseData("用户不存在"), errors.New("Business Error") // } // global.Logger.Errorf("查询用户失败 %s", err) // return resp.ErrorResponseData("系统正忙,请稍后再试"), errors.New("Business Error") // } // // // 检查是否已经是好友关系 // isFriend, err := IsFriend(uid, friendUid) // if err != nil { // global.Logger.Errorf("查询好友失败 %s", err) // return resp.ErrorResponseData("系统正忙,请稍后再试"), errors.New("Business Error") // } // // 已经是好友 // if isFriend { // return resp.ErrorResponseData("已经是好友"), errors.New("Business Error") // } // // 检查是否已经发送过好友请求 // userApply := global.Query.UserApply // userApplyQ := userApply.WithContext(ctx) // userApplyR := model.UserApply{} // fun = func() (interface{}, error) { // return userApplyQ.Where(userApply.UID.Eq(uid), userApply.TargetID.Eq(friendUid)).First() // } // key = fmt.Sprintf(domainEnum.UserApplyCacheByUidAndFriendUid, uid, friendUid) // err = utils.GetData(key, &userApplyR, fun) // // 查到了 // if err == nil { // return resp.ErrorResponseData("已发送过好友请求,请等待对方同意"), errors.New("Business Error") // } // if !errors.Is(err, gorm.ErrRecordNotFound) { // global.Logger.Errorf("查询好友请求失败 %s", err) // return resp.ErrorResponseData("系统正忙,请稍后再试"), errors.New("Business Error") // } // // // 检查对方是否给我们发送过好友请求,如果是,直接同意 // fun = func() (interface{}, error) { // return userApplyQ.Where(userApply.UID.Eq(friendUid), userApply.TargetID.Eq(uid)).First() // } // key = fmt.Sprintf(domainEnum.UserApplyCacheByUidAndFriendUid, friendUid, uid) // err = utils.GetData(key, &userApplyR, fun) // // 查到了 // if err == nil { // err := AgreeFriend(uid, friendUid) // if err != nil { // global.Logger.Errorf("同意好友请求失败 %s", err) // return resp.ErrorResponseData("系统正忙,请稍后再试"), errors.New("Business Error") // } // // return resp.SuccessResponseData(nil), nil // } // if !errors.Is(err, gorm.ErrRecordNotFound) { // global.Logger.Errorf("查询好友请求失败 %s", err) // return resp.ErrorResponseData("系统正忙,请稍后再试"), errors.New("Business Error") // } // // // 发送好友请求 // err = userApplyQ.Create(&model.UserApply{ // UID: uid, // TargetID: friendUid, // Msg: applyReq.Msg, // Status: enum.NO, // ReadStatus: enum.NO, // }) // if err != nil { // global.Logger.Errorf("插入好友请求失败 %s", err) // return resp.ErrorResponseData("系统正忙,请稍后再试"), errors.New("Business Error") // } // // 发送好友申请事件 // err = jsonUtils.SendMsgSync(domainEnum.FriendApplyTopic, model.UserApply{ // UID: uid, // TargetID: friendUid, // Msg: applyReq.Msg, // Status: enum.NO, // ReadStatus: enum.NO, // }) // if err != nil { // return resp.ErrorResponseData("系统正忙,请稍后再试"), errors.New("Business Error") // } // //time.Sleep(30 * time.Second) // return resp.SuccessResponseData(nil), nil //} // //// IsFriendService 判断是否是好友 //func IsFriendService(uid, friendUid int64) (resp.ResponseData, error) { // isFriend, err := IsFriend(uid, friendUid) // if err != nil { // global.Logger.Errorf("判断好友关系失败 %s", err) // return resp.ErrorResponseData("系统正忙,请稍后再试"), errors.New("Business Error") // } // return resp.SuccessResponseData(isFriend), nil //} // //// IsFriend 判断是否是好友 //func IsFriend(uid, friendUid int64) (bool, error) { // ctx := context.Background() // userFriend := global.Query.UserFriend // userFriendQ := userFriend.WithContext(ctx) // // 检查是否已经是好友关系 // userFriendR := model.UserFriend{} // fun := func() (interface{}, error) { // return userFriendQ.Where(userFriend.UID.Eq(uid), userFriend.FriendUID.Eq(friendUid), userFriend.DeleteStatus.Eq(enum.NORMAL)).First() // } // key := fmt.Sprintf(domainEnum.UserFriendCacheByUidAndFriendUid, uid, friendUid) // err := utils.GetData(key, &userFriendR, fun) // if err != nil { // // 没查到,不是好友 // if errors.Is(err, gorm.ErrRecordNotFound) { // return false, nil // } // global.Logger.Errorf("查询好友失败 %s", err) // return false, err // } // // return true, nil //} // //func AgreeFriendService(uid, friendUid int64) (resp.ResponseData, error) { // // uids := utils.Int64Slice{uid, friendUid} // sort.Sort(uids) // key := fmt.Sprintf(domainEnum.UserAndFriendLock, uids[0], uids[1]) // mutex, err := utils.GetLock(key) // if err != nil { // return resp.ErrorResponseData("系统正忙,请稍后再试"), err // } // defer utils.ReleaseLock(mutex) // // err = AgreeFriend(uid, friendUid) // if err != nil { // global.Logger.Errorf("同意好友请求失败 %s", err) // return resp.ErrorResponseData("系统正忙,请稍后再试"), errors.New("Business Error") // } // return resp.SuccessResponseData(nil), nil //} // //// AgreeFriend 同意好友请求 //func AgreeFriend(uid, friendUid int64) error { // ctx := context.Background() // userApply := global.Query.UserApply // userApplyQ := userApply.WithContext(ctx) // // // 检查是否存在好友申请且状态为待审批 // fun := func() (interface{}, error) { // return userApplyQ.Where(userApply.UID.Eq(friendUid), userApply.TargetID.Eq(uid)).First() // } // userApplyR := model.UserApply{} // key := fmt.Sprintf(domainEnum.UserApplyCacheByUidAndFriendUid, friendUid, uid) // err := utils.GetData(key, &userApplyR, fun) // if err != nil { // return err // } // // 好友申请状态不是待审批 // if userApplyR.Status != enum.NO { // return errors.New("error status") // } // // 同意好友请求 // userApplyR.Status = enum.YES // // 事务 // tx := q.Begin() // userApplyTx := tx.UserApply.WithContext(context.Background()) // userFriendTx := tx.UserFriend.WithContext(context.Background()) // if _, err = userApplyTx.Where(userApply.UID.Eq(friendUid), userApply.TargetID.Eq(uid)).Updates(userApplyR); err != nil { // if err := tx.Rollback(); err != nil { // global.Logger.Errorf("事务回滚失败 %s", err.Error()) // } // return err // } // defer utils.RemoveData(key) // // var userFriends = []*model.UserFriend{ // { // UID: uid, // FriendUID: friendUid, // DeleteStatus: enum.NORMAL, // }, // { // UID: friendUid, // FriendUID: uid, // DeleteStatus: enum.NORMAL, // }, // } // // 检查是否存在软删除状态的好友关系 // userFriend := global.Query.UserFriend // fun = func() (interface{}, error) { // return userFriendTx.Where(userFriend.UID.Eq(uid), userFriend.FriendUID.Eq(friendUid), userFriend.DeleteStatus.Eq(enum.DELETED)).First() // } // userFriendR := model.UserFriend{} // key = fmt.Sprintf(domainEnum.UserFriendCacheByUidAndFriendUid, uid, friendUid) // err = utils.GetData(key, &userFriendR, fun) // // err // if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { // global.Logger.Errorf("更新好友关系失败 %s", err.Error()) // return err // } // // 查到了,更新状态 // if err == nil { // _, err := userFriendTx.Where(userFriend.UID.Eq(uid), userFriend.FriendUID.Eq(friendUid)).Update(userFriend.DeleteStatus, enum.NORMAL) // _, err = userFriendTx.Where(userFriend.UID.Eq(friendUid), userFriend.FriendUID.Eq(uid)).Update(userFriend.DeleteStatus, enum.NORMAL) // if err != nil { // if err := tx.Rollback(); err != nil { // global.Logger.Errorf("事务回滚失败 %s", err.Error()) // } // global.Logger.Errorf("更新好友关系失败 %s", err.Error()) // return err // } // // 删除redis缓存 // key = fmt.Sprintf(domainEnum.UserFriendCacheByUidAndFriendUid, uid, friendUid) // defer utils.RemoveData(key) // key = fmt.Sprintf(domainEnum.UserFriendCacheByUidAndFriendUid, friendUid, uid) // defer utils.RemoveData(key) // } else { // // 没查到,创建新的好友关系 // if err = userFriendTx.Create(userFriends...); err != nil { // if err := tx.Rollback(); err != nil { // global.Logger.Errorf("事务回滚失败 %s", err) // } // return err // } // } // if err := tx.Commit(); err != nil { // return err // } // // // 发送新好友事件 // err = jsonUtils.SendMsgSync(domainEnum.NewFriendTopic, userFriends[0]) // if err != nil { // if err := tx.Rollback(); err != nil { // global.Logger.Errorf("事务回滚失败 %s", err.Error()) // } // global.Logger.Errorf("发送新好友事件失败 %s", err.Error()) // return err // } // return nil //} // //// DeleteFriendService 删除好友 //// 只删除好友关系和会话,其他耗时操作异步处理 //func DeleteFriendService(uid int64, deleteFriendReq req.DeleteFriendReq) (resp.ResponseData, error) { // ctx := context.Background() // // deleteFriendUid := deleteFriendReq.Uid // // uids := utils.Int64Slice{uid, deleteFriendUid} // sort.Sort(uids) // key := fmt.Sprintf(domainEnum.UserAndFriendLock, uids[0], uids[1]) // mutex, err := utils.GetLock(key) // if err != nil { // return resp.ErrorResponseData("系统正忙,请稍后再试"), err // } // defer utils.ReleaseLock(mutex) // // // 判断是否为好友 // isFriend, err := IsFriend(uid, deleteFriendUid) // if err != nil { // global.Logger.Errorf("查询好友关系失败 %s", err) // return resp.ErrorResponseData("系统正忙,请稍后再试"), errors.New("Business Error") // } // if !isFriend { // return resp.ErrorResponseData("删除好友失败"), errors.New("Business Error") // } // // tx := global.Query.Begin() // // 事务 // // 软删除好友关系 // userFriend := global.Query.UserFriend // userFriendTx := tx.UserFriend.WithContext(ctx) // if _, err := userFriendTx.Where(userFriend.UID.Eq(uid), userFriend.FriendUID.Eq(deleteFriendUid)).Update(userFriend.DeleteStatus, enum.DELETED); err != nil { // if err := tx.Rollback(); err != nil { // global.Logger.Errorf("事务回滚失败 %s", err.Error()) // } // global.Logger.Errorf("删除好友失败 %s", err.Error()) // return resp.ErrorResponseData("删除好友失败"), errors.New("Business Error") // } // // 删除redis缓存 // defer redisCache.RemoveUserFriend(uid, deleteFriendUid) // // if _, err := userFriendTx.Where(userFriend.UID.Eq(deleteFriendUid), userFriend.FriendUID.Eq(uid)).Update(userFriend.DeleteStatus, enum.DELETED); err != nil { // if err := tx.Rollback(); err != nil { // global.Logger.Errorf("事务回滚失败 %s", err.Error()) // } // global.Logger.Errorf("删除好友失败 %s", err.Error()) // return resp.ErrorResponseData("删除好友失败"), errors.New("Business Error") // } // // 删除redis缓存 // defer redisCache.RemoveUserFriend(deleteFriendUid, uid) // // // 删除会话 // roomFriend := global.Query.RoomFriend // roomFriendTx := tx.RoomFriend.WithContext(ctx) // uids = utils.Int64Slice{uid, deleteFriendUid} // sort.Sort(uids) // fun := func() (interface{}, error) { // return roomFriendTx.Where(roomFriend.Uid1.Eq(uids[0]), roomFriend.Uid2.Eq(uids[1])).First() // } // roomFriendR := model.RoomFriend{} // key = fmt.Sprintf(domainEnum.RoomFriendCacheByUidAndFriendUid, uids[0], uids[1]) // if err := utils.GetData(key, &roomFriendR, fun); err != nil { // if err := tx.Rollback(); err != nil { // global.Logger.Errorf("事务回滚失败 %s", err.Error()) // } // global.Logger.Errorf("查询好友房间失败 %s", err.Error()) // return resp.ErrorResponseData("删除好友失败"), errors.New("Business Error") // } // // contact := global.Query.Contact // contactTx := tx.Contact.WithContext(ctx) // resultInfo, err := contactTx.Where(contact.RoomID.Eq(roomFriendR.RoomID)).Delete() // if err != nil { // if err := tx.Rollback(); err != nil { // global.Logger.Errorf("事务回滚失败 %s", err.Error()) // } // global.Logger.Errorf("删除会话失败 %s", err.Error()) // return resp.ErrorResponseData("删除好友失败"), errors.New("Business Error") // } // if resultInfo.RowsAffected == 0 { // if err := tx.Rollback(); err != nil { // global.Logger.Errorf("事务回滚失败 %s", err.Error()) // } // global.Logger.Errorf("会话不存在 %d", roomFriendR.RoomID) // return resp.ErrorResponseData("删除好友失败"), errors.New("Business Error") // } // // TODO:删除缓存 // // // 发送消息 // DeleteFriendDto := dto.DeleteFriendDto{ // Uid: uid, // FriendUid: deleteFriendUid, // } // err = jsonUtils.SendMsgSync(domainEnum.DeleteFriendTopic, DeleteFriendDto) // if err != nil { // if err := tx.Rollback(); err != nil { // global.Logger.Errorf("事务回滚失败 %s", err.Error()) // } // global.Logger.Errorf("发送删除好友事件失败 %s", err.Error()) // return resp.ErrorResponseData("系统正忙,请稍后再试"), errors.New("Business Error") // } // // if err := tx.Commit(); err != nil { // global.Logger.Errorf("事务提交失败 %s", err.Error()) // return resp.ErrorResponseData("删除好友失败"), errors.New("Business Error") // } // // return resp.SuccessResponseData(nil), nil //} // //// GetUserApplyService 获取好友申请列表 //func GetUserApplyService(uid int64, pageReq pkgReq.PageReq) (resp.ResponseData, error) { // if pageReq.Cursor != nil && *pageReq.Cursor != "" { // // 时间戳转时间 // timestamp, err := strconv.ParseInt(*pageReq.Cursor, 10, 64) // if err != nil { // global.Logger.Errorf("时间戳转换失败 %s", err) // return resp.ErrorResponseData("系统繁忙,请稍后再试~"), errors.New("Business Error") // } // cursor := time.Unix(0, timestamp) // cursorStr := cursor.Format(time.RFC3339Nano) // pageReq.Cursor = &cursorStr // } // // ctx := context.Background() // // 获取 UserApply 表中 TargetID 等于 uid(登录用户ID)的用户ID集合,采用游标分页 // db := dal.DB // userApplys := make([]model.UserApply, 0) // condition := []interface{}{"target_id=?", strconv.FormatInt(uid, 10)} // // pageResp, err := utils.Paginate(db, pageReq, &userApplys, "create_time", false, condition...) // if err != nil { // global.Logger.Errorf("查询好友申请表失败 %s", err) // return resp.ErrorResponseData("系统正忙,请稍后再试"), errors.New("Business Error") // } // // uids := make([]int64, 0) // n := len(userApplys) // for i := 0; i < n; i++ { // uids = append(uids, userApplys[i].UID) // } // // user := global.Query.User // // 根据 uids 集合查询 User 表 // users, err := user.WithContext(ctx).Select(user.ID, user.Name, user.Avatar).Where(user.ID.In(uids...)).Find() // if err != nil { // global.Logger.Errorf("查询用户表失败 %s", err) // return resp.ErrorResponseData("系统正忙,请稍后再试"), errors.New("Business Error") // } // // if len(users) != len(userApplys) { // global.Logger.Errorf("用户表和好友申请表数据不匹配") // return resp.ErrorResponseData("系统正忙,请稍后再试"), errors.New("Business Error") // } // var usersVO = make([]domainResp.UserApplyResp, 0) // // 数据转换 // for i := 0; i < len(users); i++ { // var userVO domainResp.UserApplyResp // userVO.ApplyId = userApplys[i].ID // userVO.Uid = userApplys[i].UID // userVO.Msg = userApplys[i].Msg // userVO.Status = userApplys[i].Status // usersVO = append(usersVO, userVO) // } // pageResp.Data = usersVO // // userApply := global.Query.UserApply // userApplyQ := global.Query.UserApply.WithContext(ctx) // // 更新已读状态 // _, err = userApplyQ.Where(userApply.TargetID.Eq(uid), userApply.ReadStatus.Eq(enum.NO)).Update(userApply.ReadStatus, enum.YES) // if err != nil { // global.Logger.Errorf("更新好友申请表失败 %s", err) // return resp.ErrorResponseData("系统正忙,请稍后再试"), errors.New("Business Error") // } // // return resp.SuccessResponseData(pageResp), nil //} // //func UnreadApplyNumService(uid int64) (resp.ResponseData, error) { // ctx := context.Background() // userApply := global.Query.UserApply // userApplyQ := userApply.WithContext(ctx) // // // TODO 直接count // // 获取 UserApply 表中 TargetID 等于 uid(登录用户ID)的用户ID集合 // subQuery := userApplyQ.Where(userApply.TargetID.Eq(uid), userApply.ReadStatus.Eq(enum.NO)).Limit(99) // num, err := gen.Table(subQuery.As("t")).Count() // if err != nil { // global.Logger.Errorf("查询好友申请表失败 %s", err) // return resp.ErrorResponseData("系统正忙,请稍后再试"), errors.New("Business Error") // } // return resp.SuccessResponseData(num), nil //} // //// GetFriendListService 获取好友列表 //func GetFriendListService(uid int64, pageReq pkgReq.PageReq) (resp.ResponseData, error) { // if pageReq.Cursor != nil && *pageReq.Cursor != "" { // // 时间戳转时间 // timestamp, err := strconv.ParseInt(*pageReq.Cursor, 10, 64) // if err != nil { // global.Logger.Errorf("时间戳转换失败 %s", err) // return resp.ErrorResponseData("系统繁忙,请稍后再试~"), errors.New("Business Error") // } // cursor := time.Unix(0, timestamp) // cursorStr := cursor.Format(time.RFC3339Nano) // pageReq.Cursor = &cursorStr // } // // ctx := context.Background() // // 获取 UserFriend 表中 uid = uid 的好友的uid组成的集合 // db := dal.DB // userFriend := make([]model.UserFriend, 0) // condition := []interface{}{"uid=? and delete_status=?", strconv.FormatInt(uid, 10), enum.NORMAL} // pageResp, err := utils.Paginate(db, pageReq, &userFriend, "create_time", false, condition...) // if err != nil { // global.Logger.Errorf("分页查询失败 %v", err) // return resp.ErrorResponseData("系统繁忙,请稍后再试"), errors.New("Business Error") // } // uids := make([]int64, 0) // // for _, friend := range userFriend { // uids = append(uids, friend.FriendUID) // } // // // 获取好友信息 // users := global.Query.User // // select id , name , avatar from user where id in (...) and status = 0 // friendList, err := users.WithContext(ctx).Select(users.ID, users.ActiveStatus, users.LastOptTime).Where(users.ID.In(uids...)).Find() // if err != nil { // global.Logger.Errorf("查询用户表失败 %s", err) // return resp.ErrorResponseData("系统繁忙,请稍后再试"), errors.New("Business Error") // } // // // 数据转换 // friendListVO := make([]domainResp.UserContactResp, 0) // for _, friend := range friendList { // friendResp := domainResp.UserContactResp{ // Uid: friend.ID, // LastOptTime: friend.LastOptTime.UnixMilli(), // ActiveStatus: int(friend.ActiveStatus), // } // friendListVO = append(friendListVO, friendResp) // } // pageResp.Data = friendListVO // return resp.SuccessResponseData(pageResp), nil //} ================================================ FILE: service/group_service.go ================================================ package service // //import ( // "DiTing-Go/dal/model" // "DiTing-Go/domain/dto" // "DiTing-Go/domain/enum" // "DiTing-Go/domain/vo/req" // "DiTing-Go/global" // pkgEnum "DiTing-Go/pkg/domain/enum" // "DiTing-Go/pkg/domain/vo/resp" // pkgResp "DiTing-Go/pkg/domain/vo/resp" // "context" // "fmt" // "github.com/gin-gonic/gin" // "github.com/pkg/errors" // "gorm.io/gorm" // "strconv" // "strings" // "time" //) // //func CreateGroupService(uid int64, uidList []int64) (pkgResp.ResponseData, error) { // // tx := global.Query.Begin() // ctx := context.Background() // // 创建房间表 // roomTx := tx.Room.WithContext(ctx) // newRoom := model.Room{ // Type: enum.GROUP, // ExtJSON: "{}", // } // if err := roomTx.Create(&newRoom); err != nil { // if err := tx.Rollback(); err != nil { // global.Logger.Errorf("事务回滚失败 %s", err.Error()) // } // global.Logger.Errorf("添加房间表失败 %s", err.Error()) // return pkgResp.ErrorResponseData("系统繁忙,请稍后再试~"), errors.New("Business Error") // } // // // 查询用户头像 // user := global.Query.User // userTx := tx.User.WithContext(ctx) // userR, err := userTx.Where(user.ID.Eq(uid)).First() // if err != nil { // if err := tx.Rollback(); err != nil { // global.Logger.Errorf("事务回滚失败 %s", err.Error()) // } // global.Logger.Errorf("查询用户表失败 %s", err.Error()) // return pkgResp.ErrorResponseData("系统繁忙,请稍后再试~"), errors.New("Business Error") // } // // uidList = append([]int64{uid}, uidList...) // userRList, err := userTx.Where(user.ID.In(uidList...)).Find() // groupName := "" // for _, user := range userRList { // groupName += (user.Name + "、") // } // groupName = strings.TrimRight(groupName, "、") // runes := []rune(groupName) // if len(runes) > 10 { // runes = runes[:10] // } // groupName = string(runes) + "..." // // // 创建群聊表 // roomGroupTx := tx.RoomGroup.WithContext(ctx) // newRoomGroup := model.RoomGroup{ // RoomID: newRoom.ID, // Name: groupName, // // 默认为创建者头像 // Avatar: userR.Avatar, // ExtJSON: "{}", // } // if err := roomGroupTx.Create(&newRoomGroup); err != nil { // if err := tx.Rollback(); err != nil { // global.Logger.Errorf("事务回滚失败 %s", err.Error()) // } // global.Logger.Errorf("添加群聊表失败 %s", err.Error()) // return pkgResp.ErrorResponseData("系统繁忙,请稍后再试~"), errors.New("Business Error") // } // // groupMemberTx := tx.GroupMember.WithContext(ctx) // newGroupMemberList := []*model.GroupMember{ // { // UID: uid, // GroupID: newRoomGroup.ID, // // TODO: 1为群主,抽取为常量 // Role: 1, // }, // } // for _, userInfo := range userRList { // newGroupMemberList = append(newGroupMemberList, &model.GroupMember{ // UID: userInfo.ID, // GroupID: newRoomGroup.ID, // // TODO: 1为群主,抽取为常量 // Role: 2, // }) // } // if err := groupMemberTx.Create(newGroupMemberList...); err != nil { // if err := tx.Rollback(); err != nil { // global.Logger.Errorf("事务回滚失败 %s", err.Error()) // } // global.Logger.Errorf("添加群组成员表失败 %s", err.Error()) // return pkgResp.ErrorResponseData("系统繁忙,请稍后再试~"), errors.New("Business Error") // } // // 自动发送一条消息 // messageTx := tx.Message.WithContext(ctx) // newMessage := model.Message{ // FromUID: uid, // RoomID: newRoom.ID, // Type: enum.TextMessageType, // Content: "欢迎加入群聊", // Extra: "{}", // DeleteStatus: pkgEnum.NORMAL, // } // if err := messageTx.Create(&newMessage); err != nil { // if err := tx.Rollback(); err != nil { // global.Logger.Errorf("事务回滚失败 %s", err.Error()) // } // global.Logger.Errorf("添加消息表失败 %s", err.Error()) // return pkgResp.ErrorResponseData("系统繁忙,请稍后再试~"), errors.New("Business Error") // } // // // 创建会话表 // contactTx := tx.Contact.WithContext(ctx) // newContact := model.Contact{ // UID: uid, // RoomID: newRoom.ID, // ReadTime: time.Now(), // ActiveTime: time.Now(), // LastMsgID: newMessage.ID, // } // if err := contactTx.Create(&newContact); err != nil { // if err := tx.Rollback(); err != nil { // global.Logger.Errorf("事务回滚失败 %s", err.Error()) // } // global.Logger.Errorf("添加会话表失败 %s", err.Error()) // return pkgResp.ErrorResponseData("系统繁忙,请稍后再试~"), errors.New("Business Error") // } // // if err := tx.Commit(); err != nil { // global.Logger.Errorf("事务提交失败 %s", err.Error()) // return pkgResp.ErrorResponseData("系统繁忙,请稍后再试~"), errors.New("Business Error") // } // // global.Bus.Publish(enum.NewMessageEvent, newMessage) // // return pkgResp.SuccessResponseData("success"), nil //} // //// DeleteGroupService 删除群聊 //// //// @Summary 删除群聊 //// @Produce json //// @Param id body string true "房间ID" //// @Success 200 {object} resp.ResponseData "成功" //// @Failure 500 {object} resp.ResponseData "内部错误" //// @Router /api/group/:id [delete] //func DeleteGroupService(c *gin.Context) { // uid := c.GetInt64("uid") // deleteGroupReq := req.DeleteGroupReq{} // if err := c.ShouldBindUri(&deleteGroupReq); err != nil { //ShouldBind()会自动推导 // resp.ErrorResponse(c, "参数错误") // global.Logger.Errorf("参数错误: %v", err) // c.Abort() // return // } // // tx := global.Query.Begin() // defer func() { // // }() // ctx := context.Background() // // 查询群聊id // roomGroup := global.Query.RoomGroup // roomGroupTx := tx.RoomGroup.WithContext(ctx) // roomGroupR, err := roomGroupTx.Where(roomGroup.RoomID.Eq(deleteGroupReq.ID)).First() // if err != nil { // if err := tx.Rollback(); err != nil { // global.Logger.Errorf("事务回滚失败 %s", err.Error()) // return // } // resp.ErrorResponse(c, "删除群聊失败") // c.Abort() // global.Logger.Errorf("查询群聊表失败 %s", err.Error()) // return // // } // // TODO:查询用户是否在群聊中 // groupMember := global.Query.GroupMember // groupMemberTx := tx.GroupMember.WithContext(ctx) // // 查询用户是否是群主 // _, err = groupMemberTx.Where(groupMember.UID.Eq(uid), groupMember.GroupID.Eq(roomGroupR.ID), groupMember.Role.Eq(1)).First() // if err != nil { // if err := tx.Rollback(); err != nil { // global.Logger.Errorf("事务回滚失败 %s", err.Error()) // return // } // resp.ErrorResponse(c, "删除群聊失败") // c.Abort() // global.Logger.Errorf("查询群组成员表失败 %s", err.Error()) // return // } // // 获取群聊成员 // groupMemberList, err := groupMemberTx.Where(groupMember.GroupID.Eq(roomGroupR.ID)).Find() // if err != nil { // if err := tx.Rollback(); err != nil { // global.Logger.Errorf("事务回滚失败 %s", err.Error()) // return // } // resp.ErrorResponse(c, "删除群聊失败") // c.Abort() // global.Logger.Errorf("查询群组成员表失败 %s", err.Error()) // return // } // // // 删除所有成员的会话表 // for _, groupMember := range groupMemberList { // contact := global.Query.Contact // contactTx := tx.Contact.WithContext(ctx) // if _, err := contactTx.Where(contact.UID.Eq(groupMember.UID), contact.RoomID.Eq(roomGroupR.ID)).Delete(); err != nil { // if err := tx.Rollback(); err != nil { // global.Logger.Errorf("事务回滚失败 %s", err.Error()) // return // } // resp.ErrorResponse(c, "删除群聊失败") // c.Abort() // global.Logger.Errorf("删除会话表失败 %s", err.Error()) // return // } // } // // // 删除群聊表 // if _, err := roomGroupTx.Where(roomGroup.RoomID.Eq(roomGroupR.ID)).Delete(); err != nil { // if err := tx.Rollback(); err != nil { // global.Logger.Errorf("事务回滚失败 %s", err.Error()) // return // } // resp.ErrorResponse(c, "删除群聊失败") // c.Abort() // global.Logger.Errorf("删除群聊表失败 %s", err.Error()) // return // } // // 删除房间表 // room := global.Query.Room // roomTx := tx.Room.WithContext(ctx) // if _, err := roomTx.Where(room.ID.Eq(roomGroupR.ID)).Delete(); err != nil { // if err := tx.Rollback(); err != nil { // global.Logger.Errorf("事务回滚失败 %s", err.Error()) // return // } // resp.ErrorResponse(c, "删除群聊失败") // c.Abort() // global.Logger.Errorf("删除房间表失败 %s", err.Error()) // return // } // // 删除群组成员表 // if _, err := groupMemberTx.Where(groupMember.GroupID.Eq(roomGroupR.ID)).Delete(); err != nil { // if err := tx.Rollback(); err != nil { // global.Logger.Errorf("事务回滚失败 %s", err.Error()) // return // } // resp.ErrorResponse(c, "删除群聊失败") // c.Abort() // global.Logger.Errorf("删除群组成员表失败 %s", err.Error()) // return // } // // TODO:抽取为事件 // // 删除消息表 // message := global.Query.Message // messageTx := tx.Message.WithContext(ctx) // msg := model.Message{ // DeleteStatus: 0, // } // if _, err := messageTx.Where(message.RoomID.Eq(roomGroupR.ID)).Updates(msg); err != nil { // if err := tx.Rollback(); err != nil { // global.Logger.Errorf("事务回滚失败 %s", err.Error()) // return // } // resp.ErrorResponse(c, "删除群聊失败") // c.Abort() // global.Logger.Errorf("删除消息表失败 %s", err.Error()) // return // } // // TODO: 删除群聊仅禁止发送新消息,不删除消息 // if err := tx.Commit(); err != nil { // global.Logger.Errorf("事务提交失败 %s", err.Error()) // resp.ErrorResponse(c, "删除群聊失败") // c.Abort() // return // } // resp.SuccessResponseWithMsg(c, "success") // return //} // //// JoinGroupService 加入群聊 //// //// @Summary 加入群聊 //// @Produce json //// @Param id body int true "房间id" //// @Success 200 {object} resp.ResponseData "成功" //// @Failure 500 {object} resp.ResponseData "内部错误" //// @Router /api/group/create [post] //func JoinGroupService(c *gin.Context) { // uid := c.GetInt64("uid") // joinGroupReq := req.JoinGroupReq{} // if err := c.ShouldBind(&joinGroupReq); err != nil { //ShouldBind()会自动推导 // resp.ErrorResponse(c, "参数错误") // global.Logger.Errorf("参数错误: %v", err) // c.Abort() // return // } // ctx := context.Background() // // 房间是否存在 // room := global.Query.Room // roomQ := room.WithContext(ctx) // roomR, err := roomQ.Where(room.ID.Eq(joinGroupReq.ID)).First() // if err != nil { // resp.ErrorResponse(c, "加入群聊失败") // global.Logger.Errorf("查询房间失败 %s", err) // c.Abort() // return // } // if roomR.Type != enum.GROUP { // resp.ErrorResponse(c, "加入群聊失败") // global.Logger.Errorf("房间类型错误 %s", err) // c.Abort() // return // } // // 是否已经加入群聊 // roomGroup := global.Query.RoomGroup // roomGroupQ := roomGroup.WithContext(ctx) // // 查询群聊表 // roomGroupR, err := roomGroupQ.Where(roomGroup.RoomID.Eq(roomR.ID)).First() // if err != nil { // resp.ErrorResponse(c, "加入群聊失败") // global.Logger.Errorf("查询群聊失败 %s", err) // c.Abort() // return // } // // groupMember := global.Query.GroupMember // groupMemberQ := groupMember.WithContext(ctx) // groupMemberR, err := groupMemberQ.Where(groupMember.UID.Eq(uid), groupMember.GroupID.Eq(roomGroupR.ID)).First() // if err != nil && err.Error() != "record not found" { // resp.ErrorResponse(c, "加入群聊失败") // global.Logger.Errorf("查询群成员表失败 %s", err) // c.Abort() // return // } // if groupMemberR != nil { // resp.ErrorResponse(c, "禁止重复加入群聊") // c.Abort() // return // } // // // 加入群聊 // tx := global.Query.Begin() // groupMemberTx := tx.GroupMember.WithContext(ctx) // newGroupMember := model.GroupMember{ // UID: uid, // GroupID: roomGroupR.ID, // // 普通成员 // Role: 3, // } // if err := groupMemberTx.Create(&newGroupMember); err != nil { // if err := tx.Rollback(); err != nil { // global.Logger.Errorf("事务回滚失败 %s", err.Error()) // return // } // resp.ErrorResponse(c, "加入群聊失败") // c.Abort() // global.Logger.Errorf("添加群组成员表失败 %s", err.Error()) // return // } // // 创建会话表 // contactTx := tx.Contact.WithContext(ctx) // newContact := model.Contact{ // UID: uid, // RoomID: roomR.ID, // } // if err := contactTx.Create(&newContact); err != nil { // if err := tx.Rollback(); err != nil { // global.Logger.Errorf("事务回滚失败 %s", err.Error()) // return // } // resp.ErrorResponse(c, "加入群聊失败") // c.Abort() // global.Logger.Errorf("添加会话表失败 %s", err.Error()) // return // } // // // 自动发送一条消息 // messageTx := tx.Message.WithContext(ctx) // newMessage := model.Message{ // FromUID: uid, // RoomID: roomR.ID, // Type: enum.TextMessageType, // Content: "大家好~", // Extra: "{}", // } // if err := messageTx.Create(&newMessage); err != nil { // if err := tx.Rollback(); err != nil { // global.Logger.Errorf("事务回滚失败 %s", err.Error()) // return // } // resp.ErrorResponse(c, "加入群聊失败") // c.Abort() // global.Logger.Errorf("添加消息表失败 %s", err.Error()) // return // } // if err := tx.Commit(); err != nil { // global.Logger.Errorf("事务提交失败 %s", err.Error()) // resp.ErrorResponse(c, "加入群聊失败") // c.Abort() // return // } // global.Bus.Publish(enum.NewMessageEvent, newMessage) // // resp.SuccessResponseWithMsg(c, "success") //} // //// QuitGroupService 退出群聊 //// //// @Summary 退出群聊 //// @Produce json //// @Param id body int true "房间id" //// @Success 200 {object} resp.ResponseData "成功" //// @Failure 500 {object} resp.ResponseData "内部错误" //// @Router /api/group/create [post] //func QuitGroupService(c *gin.Context) { // uid := c.GetInt64("uid") // quitGroupReq := req.QuitGroupReq{} // if err := c.ShouldBind(&quitGroupReq); err != nil { // resp.ErrorResponse(c, "参数错误") // global.Logger.Errorf("参数错误: %v", err) // c.Abort() // return // } // // ctx := context.Background() // tx := global.Query.Begin() // // 群聊是否存在 // room := global.Query.Room // roomTx := tx.Room.WithContext(ctx) // _, err := roomTx.Where(room.ID.Eq(quitGroupReq.ID)).First() // if err != nil { // if err.Error() != gorm.ErrRecordNotFound.Error() { // global.Logger.Errorf("查询房间失败 %s", err) // } // resp.ErrorResponse(c, "群聊不存在") // c.Abort() // return // } // // 用户是否在群聊中 // groupMember := global.Query.GroupMember // groupMemberTx := tx.GroupMember.WithContext(ctx) // _, err = groupMemberTx.Where(groupMember.UID.Eq(uid), groupMember.GroupID.Eq(quitGroupReq.ID)).First() // if err != nil { // if err.Error() == gorm.ErrRecordNotFound.Error() { // resp.ErrorResponse(c, "未加入群聊") // c.Abort() // return // } // resp.ErrorResponse(c, "退出群聊失败") // global.Logger.Errorf("查询群组成员表失败 %s", err) // c.Abort() // return // } // // 删除会话表 // contact := global.Query.Contact // contactTx := tx.Contact.WithContext(ctx) // if _, err := contactTx.Where(contact.UID.Eq(uid), contact.RoomID.Eq(quitGroupReq.ID)).Delete(); err != nil { // resp.ErrorResponse(c, "退出群聊失败") // global.Logger.Errorf("删除会话表失败 %s", err) // c.Abort() // return // } // // 删除群组成员表 // // 查询群组 // roomGroup := global.Query.RoomGroup // roomGroupTx := tx.RoomGroup.WithContext(ctx) // roomGroupR, err := roomGroupTx.Where(roomGroup.RoomID.Eq(quitGroupReq.ID)).First() // if err != nil { // resp.ErrorResponse(c, "退出群聊失败") // global.Logger.Errorf("查询群聊失败 %s", err) // c.Abort() // return // } // if _, err := groupMemberTx.Where(groupMember.UID.Eq(uid), groupMember.GroupID.Eq(roomGroupR.ID)).Delete(); err != nil { // resp.ErrorResponse(c, "退出群聊失败") // global.Logger.Errorf("删除群组成员表失败 %s", err) // c.Abort() // return // } // // if err := tx.Commit(); err != nil { // global.Logger.Errorf("事务提交失败 %s", err) // resp.ErrorResponse(c, "退出群聊失败") // c.Abort() // return // } // resp.SuccessResponseWithMsg(c, "success") // return //} // //// GetGroupMemberListService 退出群聊 //// //// @Summary 退出群聊 //// @Produce json //// @Param id body int true "房间id" //// @Success 200 {object} resp.ResponseData "成功" //// @Failure 500 {object} resp.ResponseData "内部错误" //// @Router /api/group/getGroupMemberList [get] //func GetGroupMemberListService(c *gin.Context) { // uid := c.GetInt64("uid") // getGroupMemberListReq := req.GetGroupMemberListReq{} // if err := c.ShouldBindQuery(&getGroupMemberListReq); err != nil { // resp.ErrorResponse(c, "参数错误") // global.Logger.Errorf("参数错误: %v", err) // c.Abort() // return // } // ctx := context.Background() // // // 查询房间表 // room := global.Query.Room // roomQ := room.WithContext(ctx) // roomR, err := roomQ.Where(room.ID.Eq(getGroupMemberListReq.RoomId)).First() // if err != nil { // resp.ErrorResponse(c, "查询群聊失败") // global.Logger.Errorf("查询房间失败 %s", err) // c.Abort() // return // } // // 查询群聊表 // roomGroup := global.Query.RoomGroup // roomGroupQ := roomGroup.WithContext(ctx) // roomGroupR, err := roomGroupQ.Where(roomGroup.RoomID.Eq(roomR.ID)).First() // if err != nil { // resp.ErrorResponse(c, "查询群聊失败") // global.Logger.Errorf("查询群聊失败 %s", err) // c.Abort() // return // } // // // 查询用户是否在群聊中 // groupMember := global.Query.GroupMember // groupMemberQ := groupMember.WithContext(ctx) // _, err = groupMemberQ.Where(groupMember.UID.Eq(uid), groupMember.GroupID.Eq(roomGroupR.ID)).First() // if err != nil { // resp.ErrorResponse(c, "查询群聊失败") // global.Logger.Errorf("查询群组成员表失败 %s", err) // c.Abort() // return // } // // // 分页查询 // // 默认值 // // 划分游标,status_activateTime // var userR []dto.GetGroupMemberDto // status, activeTime := cursorSplit(getGroupMemberListReq.Cursor) // // 查询群组成员表,联表游标翻页 // user := global.Query.User // userQ := user.WithContext(ctx) // if err := userQ.Select(user.ID, user.Name, user.Avatar, user.ActiveStatus, user.LastOptTime).LeftJoin(groupMemberQ, user.ID.EqCol(groupMember.UID)).Where(groupMember.GroupID.Eq(roomGroupR.ID), user.ActiveStatus.Eq(int32(status)), user.LastOptTime.Gt(activeTime)).Limit(getGroupMemberListReq.PageSize).Scan(&userR); err != nil { // resp.ErrorResponse(c, "查询群聊失败") // global.Logger.Errorf("查询群组成员表失败 %s", err) // c.Abort() // return // } // // 不足,用不在线的补充 // if len(userR) < getGroupMemberListReq.PageSize && status == 1 { // var add []dto.GetGroupMemberDto // if err := userQ.Select(user.ID, user.Name, user.Avatar, user.ActiveStatus, user.LastOptTime).LeftJoin(groupMemberQ, user.ID.EqCol(groupMember.UID)).Where(groupMember.GroupID.Eq(roomGroupR.ID), user.ActiveStatus.Eq(2)).Limit(getGroupMemberListReq.PageSize - len(userR)).Scan(&add); err != nil { // resp.ErrorResponse(c, "查询群聊失败") // global.Logger.Errorf("查询群组成员表失败 %s", err) // c.Abort() // return // } // userR = append(userR, add...) // } // // newCursor := genCursor(userR) // resp.SuccessResponse(c, pkgResp.PageResp{ // Cursor: &newCursor, // IsLast: len(userR) < getGroupMemberListReq.PageSize, // Data: userR, // }) //} // //// GrantAdministratorService 授予管理员权限 //// //// @Summary 授予管理员权限 //// @Produce json //// @Param room_id body int true "房间id" //// @Param grant_uid body int true "授权用户id" //// @Success 200 {object} resp.ResponseData "成功" //// @Failure 500 {object} resp.ResponseData "内部错误" //// @Router /api/group/getGroupMemberList [get] //func GrantAdministratorService(c *gin.Context) { // uid := c.GetInt64("uid") // grantAdministratorReq := req.GrantAdministratorReq{} // if err := c.ShouldBind(&grantAdministratorReq); err != nil { // resp.ErrorResponse(c, "参数错误") // global.Logger.Errorf("参数错误: %v", err) // c.Abort() // return // } // ctx := context.Background() // // 检查用户是否为群主 // roomGroup := global.Query.RoomGroup // roomGroupQ := roomGroup.WithContext(ctx) // roomGroupR, err := roomGroupQ.Where(roomGroup.RoomID.Eq(grantAdministratorReq.RoomId)).First() // if err != nil { // resp.ErrorResponse(c, "授权失败") // global.Logger.Errorf("查询群聊失败 %s", err) // c.Abort() // return // } // groupMember := global.Query.GroupMember // groupMemberQ := groupMember.WithContext(ctx) // groupMemberR, err := groupMemberQ.Where(groupMember.UID.Eq(uid), groupMember.GroupID.Eq(roomGroupR.ID)).First() // if err != nil { // global.Logger.Errorf("查询群组成员表失败 %s", err) // resp.ErrorResponse(c, "授权失败") // c.Abort() // return // } // if groupMemberR.Role != 1 { // resp.ErrorResponse(c, "授权失败,权限不足") // c.Abort() // return // } // // // 检查授权用户是否在群聊中 // groupMemberR, err = groupMemberQ.Where(groupMember.UID.Eq(grantAdministratorReq.GrantUid), groupMember.GroupID.Eq(roomGroupR.ID)).First() // if err != nil { // resp.ErrorResponse(c, "授权失败,用户不在群聊中") // global.Logger.Errorf("查询群组成员表失败 %s", err) // c.Abort() // return // } // // 如果用户是不是普通用户 // if groupMemberR.Role != 3 { // resp.ErrorResponse(c, "授权失败") // c.Abort() // return // } // // 授权 // groupMemberR.Role = 2 // groupMemberR.UpdateTime = time.Now() // if _, err := groupMemberQ.Where(groupMember.ID.Eq(groupMemberR.ID)).Updates(groupMemberR); err != nil { // resp.ErrorResponse(c, "授权失败") // global.Logger.Errorf("更新群组成员表失败 %s", err) // c.Abort() // return // } // // resp.SuccessResponseWithMsg(c, "success") // return //} // //// RemoveAdministratorService 移除管理员权限 //// //// @Summary 移除管理员权限 //// @Produce json //// @Param room_id body int true "房间id" //// @Param remove_uid body int true "授权用户id" //// @Success 200 {object} resp.ResponseData "成功" //// @Failure 500 {object} resp.ResponseData "内部错误" //// @Router /api/group/getGroupMemberList [get] //func RemoveAdministratorService(c *gin.Context) { // uid := c.GetInt64("uid") // removeAdministratorReq := req.RemoveAdministratorReq{} // if err := c.ShouldBind(&removeAdministratorReq); err != nil { // resp.ErrorResponse(c, "参数错误") // global.Logger.Errorf("参数错误: %v", err) // c.Abort() // return // } // ctx := context.Background() // // 检查用户是否为群主 // roomGroup := global.Query.RoomGroup // roomGroupQ := roomGroup.WithContext(ctx) // roomGroupR, err := roomGroupQ.Where(roomGroup.RoomID.Eq(removeAdministratorReq.RoomId)).First() // if err != nil { // resp.ErrorResponse(c, "移除管理员失败") // global.Logger.Errorf("查询群聊失败 %s", err) // c.Abort() // return // } // groupMember := global.Query.GroupMember // groupMemberQ := groupMember.WithContext(ctx) // groupMemberR, err := groupMemberQ.Where(groupMember.UID.Eq(uid), groupMember.GroupID.Eq(roomGroupR.ID)).First() // if err != nil { // global.Logger.Errorf("查询群组成员表失败 %s", err) // resp.ErrorResponse(c, "移除管理员失败") // c.Abort() // return // } // if groupMemberR.Role != 1 { // resp.ErrorResponse(c, "移除管理员失败,权限不足") // c.Abort() // return // } // // // 检查授权用户是否在群聊中 // groupMemberR, err = groupMemberQ.Where(groupMember.UID.Eq(removeAdministratorReq.RemoveUid), groupMember.GroupID.Eq(roomGroupR.ID)).First() // if err != nil { // resp.ErrorResponse(c, "移除管理员失败,用户不在群聊中") // global.Logger.Errorf("查询群组成员表失败 %s", err) // c.Abort() // return // } // // 如果用户是不是普通用户 // if groupMemberR.Role != 2 { // resp.ErrorResponse(c, "移除管理员失败") // c.Abort() // return // } // // // 移除权限 // groupMemberR.Role = 3 // groupMemberR.UpdateTime = time.Now() // if _, err := groupMemberQ.Where(groupMember.ID.Eq(groupMemberR.ID)).Updates(groupMemberR); err != nil { // resp.ErrorResponse(c, "移除管理员失败") // global.Logger.Errorf("更新群组成员表失败 %s", err) // c.Abort() // return // } // // resp.SuccessResponseWithMsg(c, "success") // return //} // //// 分割游标 //func cursorSplit(cursor *string) (int, time.Time) { // if cursor == nil { // return 1, time.Time{} // } // // TODO: 抽取为常量 // lines := strings.Split(*cursor, "_") // status, err := strconv.Atoi(lines[0]) // if err != nil { // return 1, time.Time{} // } // timeUnix, err := strconv.ParseInt(lines[1], 10, 64) // if err != nil { // return 1, time.Time{} // } // activeTime := time.Unix(timeUnix, timeUnix%1000000000) // return status, activeTime //} // //// 生成游标 //func genCursor(users []dto.GetGroupMemberDto) string { // if users == nil || len(users) == 0 { // return fmt.Sprintf("%d_%d", 2, time.Now().Unix()) // } // status := users[len(users)-1].ActiveStatus // activeTime := users[len(users)-1].LastOptTime // newCursor := fmt.Sprintf("%d_%d", status, activeTime.UnixMilli()) // return newCursor //} ================================================ FILE: service/message_service.go ================================================ package service // //import ( // "DiTing-Go/dal/model" // "DiTing-Go/domain/enum" // "DiTing-Go/domain/vo/req" // domainResp "DiTing-Go/domain/vo/resp" // "DiTing-Go/global" // pkgEnum "DiTing-Go/pkg/domain/enum" // "DiTing-Go/pkg/domain/vo/resp" // "context" // "github.com/apache/rocketmq-client-go/v2/primitive" // "github.com/goccy/go-json" // "log" // "time" //) // //// SendTextMsgService 发送文本消息 //func SendTextMsgService(uid int64, msgReq req.MessageReq) (resp.ResponseData, error) { // ctx := context.Background() // user := global.Query.User // userQ := user.WithContext(ctx) // userR, err := userQ.Where(user.ID.Eq(uid)).First() // if err != nil { // log.Println("查询用户失败", err) // return resp.ErrorResponseData("消息发送失败"), err // } // // msg := model.Message{} // msg.Type = msgReq.MsgType // msg.FromUID = uid // msg.RoomID = msgReq.RoomId // msg.Content = msgReq.Body.Content // if msg.Extra == "" { // msg.Extra = "{}" // } // // // 发送消息 // if err := SendTextMsg(&msg); err != nil { // return resp.ErrorResponseData("消息发送失败"), err // } // // 发送新消息事件 // newMsgByte, _ := json.Marshal(msg) // rMsg := &primitive.Message{ // Topic: enum.NewMessageTopic, // Body: newMsgByte, // } // _, _ = global.RocketProducer.SendSync(ctx, rMsg) // // msgResp := domainResp.MessageResp{ // FromUser: domainResp.MsgUser{ // Uid: uid, // Username: userR.Name, // Avatar: userR.Avatar, // }, // SendTime: msg.CreateTime.UnixMilli(), // Message: domainResp.Msg{ // ID: msg.ID, // RoomId: msg.RoomID, // Type: msg.Type, // Body: domainResp.TextBody{ // Content: msg.Content, // Reply: msg.ReplyMsgID, // }, // }, // } // // // 返回成功 // return resp.SuccessResponseData(msgResp), nil //} // //func SendTextMsg(msg *model.Message) error { // msg.CreateTime = time.Now() // msg.DeleteStatus = pkgEnum.NORMAL // ctx := context.Background() // msgQ := global.Query.WithContext(ctx).Message // if err := msgQ.Create(msg); err != nil { // log.Println("消息发送失败", err.Error()) // return err // } // return nil //} ================================================ FILE: service/upload_service.go ================================================ package service // //import ( // "DiTing-Go/dal/model" // "DiTing-Go/domain/dto" // "DiTing-Go/domain/enum" // voResp "DiTing-Go/domain/vo/resp" // "DiTing-Go/global" // "DiTing-Go/pkg/domain/vo/resp" // "context" // "fmt" // "github.com/gin-gonic/gin" // "github.com/goccy/go-json" // "github.com/minio/minio-go/v7" // "strconv" // "time" //) // //// GetPreSigned 签发url //func GetPreSigned(c *gin.Context) { // uid := c.GetInt64("uid") // ctx := context.Background() // roomIdStr, found := c.GetQuery("roomId") // if !found { // global.Logger.Errorf("参数错误 %s", roomIdStr) // resp.ErrorResponse(c, "参数错误") // c.Abort() // return // } // roomId, err := strconv.ParseInt(roomIdStr, 10, 64) // if err != nil { // global.Logger.Errorf("参数错误 %s", roomId) // resp.ErrorResponse(c, "参数错误") // c.Abort() // return // } // // fileName, found := c.GetQuery("fileName") // if !found { // global.Logger.Errorf("参数错误 %s", roomIdStr) // resp.ErrorResponse(c, "参数错误") // c.Abort() // return // } // // 构造文件名:time+uid+filename // // 按天创建桶 // timeStr := time.Now().Format("2006-01-02") // fileName = fmt.Sprintf("%s/%d/%s", timeStr, uid, fileName) // // policy := minio.NewPostPolicy() // // TODO:抽象为常量 // if err := policy.SetBucket("diting"); err != nil { // global.Logger.Errorf("创建policy失败 %s", roomIdStr) // resp.ErrorResponse(c, "获取签名失败,请稍后再试") // c.Abort() // return // } // if err := policy.SetKey(fileName); err != nil { // global.Logger.Errorf("创建policy失败 %s", roomIdStr) // resp.ErrorResponse(c, "获取签名失败,请稍后再试") // c.Abort() // return // } // // 失效时间1天 // if err := policy.SetExpires(time.Now().UTC().AddDate(0, 0, 1)); err != nil { // global.Logger.Errorf("创建policy失败 %s", roomIdStr) // resp.ErrorResponse(c, "获取签名失败,请稍后再试") // c.Abort() // return // } // url, formData, err := global.MinioClient.PresignedPostPolicy(ctx, policy) // if err != nil { // global.Logger.Errorf("创建policy失败 %s", roomIdStr) // resp.ErrorResponse(c, "获取签名失败,请稍后再试") // c.Abort() // return // } // preSignedResp := voResp.PreSignedResp{ // Url: url.String(), // Policy: formData, // } // tx := global.Query.Begin() // // 插入消息表 // messageTx := tx.Message.WithContext(ctx) // base := dto.MessageBaseDto{ // Url: url.String(), // Size: -1, // Name: fileName, // } // extra := dto.ImgMessageDto{ // MessageBaseDto: base, // // TODO: 宽高需要前端传 // Width: -1, // Height: -1, // } // jsonStr, err := json.Marshal(extra) // if err != nil { // if err := tx.Rollback(); err != nil { // global.Logger.Errorf("事务回滚失败 %s", err) // } // global.Logger.Errorf("json序列化失败 %s", err) // resp.ErrorResponse(c, "获取签名失败,请稍后再试") // c.Abort() // return // } // // TODO:抽象为常量 // newMsg := model.Message{ // FromUID: uid, // RoomID: roomId, // Content: "[图片]", // DeleteStatus: 0, // Type: 3, // Extra: string(jsonStr), // } // if err := messageTx.Create(&newMsg); err != nil { // if err := tx.Rollback(); err != nil { // global.Logger.Errorf("事务回滚失败 %s", err) // } // global.Logger.Errorf("数据库插入失败 %s", err) // resp.ErrorResponse(c, "获取签名失败,请稍后再试") // c.Abort() // return // } // // global.Bus.Publish(enum.NewMessageEvent, newMsg) // // resp.SuccessResponse(c, preSignedResp) // return //} ================================================ FILE: service/user_service.go ================================================ package service import ( "DiTing-Go/dal/model" domainEnum "DiTing-Go/domain/enum" "DiTing-Go/domain/vo/req" "DiTing-Go/domain/vo/resp" "DiTing-Go/global" "DiTing-Go/logic" pkgResp "DiTing-Go/pkg/domain/vo/resp" "DiTing-Go/utils" "DiTing-Go/utils/jwt" "context" "encoding/json" "fmt" "github.com/gin-gonic/gin" "github.com/go-redis/redis" "github.com/pkg/errors" ) // RegisterService 用户注册 func RegisterService(userReq req.UserRegisterReq) (pkgResp.ResponseData, error) { ctx := context.Background() // 参数校验 if err := validateRegisterRequest(userReq); err != nil { return pkgResp.ErrorResponseData(err.Error()), errors.New("Business Error") } // 验证验证码 if err := validateRegisterCaptcha(userReq.Phone, userReq.Captcha); err != nil { return pkgResp.ErrorResponseData(err.Error()), errors.New("Business Error") } // 检查用户是否已存在 exists, err := checkUserExists(ctx, userReq.Phone) if err != nil { global.Logger.Errorf("检查用户是否存在失败: phone=%s, err=%v", userReq.Phone, err) return pkgResp.ErrorResponseData("系统繁忙,请稍后再试~"), errors.New("Business Error") } if exists { return pkgResp.ErrorResponseData("手机号已存在"), errors.New("Business Error") } // 创建用户 if err := createNewUser(ctx, userReq); err != nil { global.Logger.Errorf("创建用户失败: phone=%s, err=%v", userReq.Phone, err) return pkgResp.ErrorResponseData("系统繁忙,请稍后再试~"), errors.New("Business Error") } return pkgResp.SuccessResponseDataWithMsg("注册成功"), nil } // validateRegisterRequest 验证注册请求参数 func validateRegisterRequest(userReq req.UserRegisterReq) error { if userReq.Username == "" || userReq.Password == "" || userReq.Phone == "" { global.Logger.Infof("用户名、密码和手机号不能为空: userReq=%v", userReq) return errors.New("用户名、密码和手机号不能为空") } if userReq.Captcha == "" { global.Logger.Infof("验证码不能为空: phone=%s", userReq.Phone) return errors.New("验证码不能为空") } // 验证密码强度 if len(userReq.Password) < 6 { return errors.New("密码长度不能少于6位") } // 验证手机号格式(简单验证) if len(userReq.Phone) != 11 { return errors.New("手机号格式不正确") } return nil } // validateRegisterCaptcha 验证注册验证码 func validateRegisterCaptcha(phone, captcha string) error { if !logic.CheckCaptchaProcess(phone, captcha) { global.Logger.Infof("验证码错误: phone=%s", phone) return errors.New("验证码错误") } return nil } // checkUserExists 检查用户是否已存在 func checkUserExists(ctx context.Context, phone string) (bool, error) { // 先检查Redis缓存 if logic.CheckPhoneInRedis(phone) { global.Logger.Infof("手机号已存在: phone=%s", phone) return true, nil } // 检查数据库 exists, err := logic.CheckPhoneInDB(ctx, phone) if err != nil { global.Logger.Errorf("检查用户是否存在失败: phone=%s, err=%v", phone, err) return false, err } if exists { global.Logger.Infof("手机号已存在: phone=%s", phone) return true, nil } return false, nil } // createNewUser 创建新用户 func createNewUser(ctx context.Context, userReq req.UserRegisterReq) error { // 对密码进行md5加密 password := utils.EncryptPassword(userReq.Password) // 创建用户对象 newUser := model.User{ Name: userReq.Username, Password: password, Phone: userReq.Phone, IPInfo: "{}", } // 创建用户 if err := logic.CreateUser(ctx, newUser); err != nil { global.Logger.Errorf("创建用户失败: phone=%s, err=%v", userReq.Phone, err) return err } // 缓存用户信息到Redis if err := logic.SetUserInfo2Redis(newUser); err != nil { global.Logger.Errorf("缓存用户信息失败: userId=%d, err=%v", newUser.ID, err) // 不返回错误,因为用户创建成功,缓存失败不影响注册流程 } // 缓存用户ID映射 userPhoneKey := utils.MakeUserPhoneKey(userReq.Phone) if err := utils.SetValueToRedis(userPhoneKey, fmt.Sprintf("%d", newUser.ID), domainEnum.NotExpireTime); err != nil { global.Logger.Errorf("缓存用户ID映射失败: userId=%d, err=%v", newUser.ID, err) // 不返回错误,因为用户创建成功,缓存失败不影响注册流程 } global.Logger.Infof("用户注册成功: phone=%s, userId=%d", userReq.Phone, newUser.ID) return nil } // LoginService 用户登录 func LoginService(loginReq req.UserLoginReq) (pkgResp.ResponseData, error) { ctx := context.Background() // 参数校验 if err := validateLoginRequest(loginReq); err != nil { return pkgResp.ErrorResponseData(err.Error()), errors.New("Business Error") } // 验证登录凭据 if err := validateLoginCredentials(ctx, loginReq); err != nil { return pkgResp.ErrorResponseData(err.Error()), errors.New("Business Error") } // 获取用户信息 user, err := getUserInfo(loginReq.Phone) if err != nil { global.Logger.Errorf("获取用户信息失败: phone=%s, err=%v", loginReq.Phone, err) return pkgResp.ErrorResponseData("系统繁忙,请稍后再试~"), errors.New("Business Error") } // 生成JWT token token, err := jwt.GenerateToken(user.ID) if err != nil { global.Logger.Errorf("生成token失败: userId=%d, err=%v", user.ID, err) return pkgResp.ErrorResponseData("系统繁忙,请稍后再试~"), errors.New("Business Error") } // 构建响应 userResp := resp.UserLoginResp{ Token: token, Uid: user.ID, Name: user.Name, Avatar: user.Avatar, } return pkgResp.SuccessResponseData(userResp), nil } // validateLoginRequest 验证登录请求参数 func validateLoginRequest(loginReq req.UserLoginReq) error { if loginReq.LoginType == domainEnum.LoginByPassword { if loginReq.Phone == "" || loginReq.Password == "" { global.Logger.Infof("用户名和密码不能为空: loginReq=%v", loginReq) return errors.New("用户名和密码不能为空") } } else { if loginReq.Phone == "" || loginReq.Captcha == "" { global.Logger.Infof("手机号和验证码不能为空: loginReq=%v", loginReq) return errors.New("手机号和验证码不能为空") } } return nil } // validateLoginCredentials 验证登录凭据 func validateLoginCredentials(ctx context.Context, loginReq req.UserLoginReq) error { if loginReq.LoginType == domainEnum.LoginByPassword { if !logic.CheckPassword(ctx, loginReq.Phone, loginReq.Password) { global.Logger.Infof("用户名或密码错误: phone=%s", loginReq.Phone) return errors.New("用户名或密码错误") } } else { if !logic.CheckCaptchaProcess(loginReq.Phone, loginReq.Captcha) { global.Logger.Infof("验证码错误: phone=%s", loginReq.Phone) return errors.New("验证码错误") } } return nil } // getUserInfo 获取用户信息,优先从Redis获取,失败则从数据库获取并缓存 func getUserInfo(phone string) (model.User, error) { userPhoneKey := utils.MakeUserPhoneKey(phone) // 从Redis获取用户ID userIdByte, err := utils.GetValueFromRedis(userPhoneKey) var userId int64 json.Unmarshal(userIdByte, &userId) if err != nil && !errors.Is(err, redis.Nil) { global.Logger.Errorf("从Redis获取用户ID失败: userPhoneKey=%s, err=%v", userPhoneKey, err) return model.User{}, err } // 如果Redis中有用户ID,尝试获取用户信息 if userId != 0 { user, err := logic.GetUserInfo2Redis(fmt.Sprintf("%d", userId)) if err == nil { return user, nil } // Redis中用户信息不存在或出错,继续从数据库获取 global.Logger.Infof("从Redis获取用户信息失败,尝试从数据库获取: userId=%s, err=%v", userId, err) } // 从数据库获取用户信息 user, err := logic.GetUserInfo2DB(phone) if err != nil { global.Logger.Errorf("从数据库获取用户信息失败: phone=%s, err=%v", phone, err) return model.User{}, err } // 将用户信息缓存到Redis if err := logic.SetUserInfo2Redis(user); err != nil { global.Logger.Errorf("缓存用户信息到Redis失败: userId=%d, err=%v", user.ID, err) } // 缓存用户ID映射 if err := utils.SetValueToRedis(userPhoneKey, fmt.Sprintf("%d", user.ID), domainEnum.NotExpireTime); err != nil { global.Logger.Errorf("缓存用户ID映射失败: userId=%d, err=%v", user.ID, err) } return user, nil } // CancelService 注销账户 func CancelService(ctx *gin.Context, req req.UserCancelReq) (pkgResp.ResponseData, error) { // 获取用户信息 userId, exists := ctx.Get("uid") if !exists { global.Logger.Errorf("用户id不存在") return pkgResp.ErrorResponseData("系统繁忙,请稍后再试~"), errors.New("Business Error") } userIdNum, ok := userId.(int64) userIdStr := fmt.Sprintf("%d", userIdNum) if !ok { global.Logger.Errorf("获取用户ID失败: userId=%v", userId) return pkgResp.ErrorResponseData("系统繁忙,请稍后再试~"), errors.New("Business Error") } userInfo, err := logic.GetUserInfo2DBById(ctx, userIdStr) if err != nil { global.Logger.Errorf("获取用户信息失败: userId=%s, err=%v", userId, err) return pkgResp.ErrorResponseData("系统繁忙,请稍后再试~"), errors.New("Business Error") } phone := userInfo.Phone captcha := req.Captcha // 检查验证码是否正确 if !logic.CheckCaptchaProcess(phone, captcha) { global.Logger.Infof("验证码错误: phone=%s", phone) return pkgResp.ErrorResponseData("验证码错误"), errors.New("验证码错误") } // 检查用户是否存在 userExists, err := checkUserExists(ctx, phone) if err != nil { global.Logger.Errorf("检查用户是否存在失败: phone=%s, err=%v", phone, err) return pkgResp.ErrorResponseData("系统繁忙,请稍后再试~"), errors.New("Business Error") } if !userExists { global.Logger.Errorf("用户不存在: phone=%s", phone) return pkgResp.ErrorResponseData("用户不存在"), errors.New("Business Error") } // 删除用户缓存 if err := logic.DeleteUserInfoFromRedis(userIdStr); err != nil { global.Logger.Errorf("删除用户缓存失败: phone=%s, err=%v", phone, err) return pkgResp.ErrorResponseData("系统繁忙,请稍后再试~"), errors.New("Business Error") } // 删除用户ID映射 userPhoneKey := utils.MakeUserPhoneKey(phone) if err := utils.DeleteValueFromRedis(userPhoneKey); err != nil { global.Logger.Errorf("删除用户ID映射失败: phone=%s, err=%v", phone, err) return pkgResp.ErrorResponseData("系统繁忙,请稍后再试~"), errors.New("Business Error") } // 删除用户数据库 if err := logic.DeleteUserInfoFromDB(ctx, userIdStr); err != nil { global.Logger.Errorf("删除用户数据库失败: phone=%s, err=%v", phone, err) return pkgResp.ErrorResponseData("系统繁忙,请稍后再试~"), errors.New("Business Error") } return pkgResp.SuccessResponseDataWithMsg("注销成功"), nil } // //func GetUserInfoByNameService(uid int64, name string) (pkgResp.ResponseData, error) { // ctx := context.Background() // user := global.Query.User // userQ := user.WithContext(ctx) // userRList, err := userQ.Where(user.Name.Like(name + "%")).Limit(5).Find() // if err != nil { // global.Logger.Errorf("查询用户数据失败: %v", err) // return pkgResp.ErrorResponseData("系统繁忙,请稍后再试~"), errors.New("Business Error") // } // uidList := make([]int64, 0) // for _, userR := range userRList { // uidList = append(uidList, userR.ID) // } // // 搜索好友关系 // userApply := global.Query.UserApply // userApplyQ := userApply.WithContext(ctx) // applyList, err := userApplyQ.Where(userApply.UID.Eq(uid), userApply.TargetID.In(uidList...)).Find() // if err != nil { // global.Logger.Errorf("查询好友关系失败: %v", err) // return pkgResp.ErrorResponseData("系统繁忙,请稍后再试~"), errors.New("Business Error") // } // // 查询好友关系 // userFriend := global.Query.UserFriend // userFriendQ := userFriend.WithContext(ctx) // friendList, err := userFriendQ.Where(userFriend.UID.Eq(uid), userFriend.FriendUID.In(uidList...)).Find() // if err != nil { // global.Logger.Errorf("查询好友关系失败: %v", err) // return pkgResp.ErrorResponseData("系统繁忙,请稍后再试~"), errors.New("Business Error") // } // userRespList := adapter.BuildUserInfoByNameResp(userRList, applyList, friendList) // return pkgResp.SuccessResponseData(userRespList), nil //} ================================================ FILE: service/user_service_integration_test.go ================================================ package service import ( "DiTing-Go/domain/enum" "DiTing-Go/domain/vo/req" "DiTing-Go/logic" "DiTing-Go/utils" "context" "testing" "time" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" ) // TestUserRegistrationFlow 测试用户注册流程 func TestUserRegistrationFlow(t *testing.T) { // 测试数据 testPhone := "13800138001" testUsername := "testuser1" testPassword := "123456" testCaptcha := "123456" // 设置验证码 setupCaptcha(testPhone, testCaptcha) // 执行注册 registerReq := req.UserRegisterReq{ Username: testUsername, Password: testPassword, Phone: testPhone, Captcha: testCaptcha, } resp, err := RegisterService(registerReq) assert.NoError(t, err) assert.True(t, resp.Success) assert.Contains(t, resp.Message, "注册成功") // 验证用户是否真的被创建 userExists, err := logic.CheckPhoneInDB(context.Background(), testPhone) assert.NoError(t, err) assert.True(t, userExists) // 清理测试数据 cleanupUser(testPhone) } // TestUserLoginFlow 测试用户登录流程 func TestUserLoginFlow(t *testing.T) { // 先注册一个用户 testPhone := "13800138002" testUsername := "testuser2" testPassword := "123456" testCaptcha := "123456" setupCaptcha(testPhone, testCaptcha) registerReq := req.UserRegisterReq{ Username: testUsername, Password: testPassword, Phone: testPhone, Captcha: testCaptcha, } registerResp, err := RegisterService(registerReq) assert.NoError(t, err) assert.True(t, registerResp.Success) // 测试密码登录 loginReq := req.UserLoginReq{ Phone: testPhone, Password: testPassword, LoginType: enum.LoginByPassword, } loginResp, err := LoginService(loginReq) assert.NoError(t, err) assert.True(t, loginResp.Success) // 验证登录响应数据结构 loginData, ok := loginResp.Data.(map[string]interface{}) assert.True(t, ok) assert.NotEmpty(t, loginData["token"]) assert.NotZero(t, loginData["uid"]) assert.Equal(t, testUsername, loginData["name"]) // 清理测试数据 cleanupUser(testPhone) } // TestUserCancelFlow 测试用户注销流程 func TestUserCancelFlow(t *testing.T) { // 先注册一个用户 testPhone := "13800138003" testUsername := "testuser3" testPassword := "123456" testCaptcha := "123456" setupCaptcha(testPhone, testCaptcha) registerReq := req.UserRegisterReq{ Username: testUsername, Password: testPassword, Phone: testPhone, Captcha: testCaptcha, } registerResp, err := RegisterService(registerReq) assert.NoError(t, err) assert.True(t, registerResp.Success) // 设置注销验证码 setupCaptcha(testPhone, testCaptcha) // 执行注销 ctx := &gin.Context{} ctx.Set("uid", int64(12345)) cancelReq := req.UserCancelReq{ Captcha: testCaptcha, } cancelResp, err := CancelService(ctx, cancelReq) assert.NoError(t, err) assert.True(t, cancelResp.Success) assert.Contains(t, cancelResp.Message, "注销成功") // 验证用户是否真的被删除 userExists, err := logic.CheckPhoneInDB(context.Background(), testPhone) assert.NoError(t, err) assert.False(t, userExists) } // TestDuplicateRegistration 测试重复注册 func TestDuplicateRegistration(t *testing.T) { testPhone := "13800138004" testUsername := "testuser4" testPassword := "123456" testCaptcha := "123456" // 第一次注册 setupCaptcha(testPhone, testCaptcha) registerReq := req.UserRegisterReq{ Username: testUsername, Password: testPassword, Phone: testPhone, Captcha: testCaptcha, } resp1, err := RegisterService(registerReq) assert.NoError(t, err) assert.True(t, resp1.Success) // 第二次注册相同手机号 setupCaptcha(testPhone, testCaptcha) resp2, err := RegisterService(registerReq) assert.NoError(t, err) assert.False(t, resp2.Success) assert.Contains(t, resp2.Message, "手机号已存在") // 清理测试数据 cleanupUser(testPhone) } // TestLoginWithWrongCredentials 测试错误凭据登录 func TestLoginWithWrongCredentials(t *testing.T) { // 先注册一个用户 testPhone := "13800138005" testUsername := "testuser5" testPassword := "123456" testCaptcha := "123456" setupCaptcha(testPhone, testCaptcha) registerReq := req.UserRegisterReq{ Username: testUsername, Password: testPassword, Phone: testPhone, Captcha: testCaptcha, } resp, err := RegisterService(registerReq) assert.NoError(t, err) assert.True(t, resp.Success) // 测试错误密码 wrongPasswordReq := req.UserLoginReq{ Phone: testPhone, Password: "wrongpassword", LoginType: enum.LoginByPassword, } resp2, err := LoginService(wrongPasswordReq) assert.NoError(t, err) assert.False(t, resp2.Success) assert.Contains(t, resp2.Message, "用户名或密码错误") // 测试错误验证码 wrongCaptchaReq := req.UserLoginReq{ Phone: testPhone, Captcha: "999999", LoginType: enum.LoginByPhoneCaptcha, } resp3, err := LoginService(wrongCaptchaReq) assert.NoError(t, err) assert.False(t, resp3.Success) assert.Contains(t, resp3.Message, "验证码错误") // 清理测试数据 cleanupUser(testPhone) } // TestCancelWithWrongCaptcha 测试错误验证码注销 func TestCancelWithWrongCaptcha(t *testing.T) { // 先注册一个用户 testPhone := "13800138006" testUsername := "testuser6" testPassword := "123456" testCaptcha := "123456" setupCaptcha(testPhone, testCaptcha) registerReq := req.UserRegisterReq{ Username: testUsername, Password: testPassword, Phone: testPhone, Captcha: testCaptcha, } resp, err := RegisterService(registerReq) assert.NoError(t, err) assert.True(t, resp.Success) // 测试错误验证码注销 ctx := &gin.Context{} ctx.Set("uid", int64(12345)) wrongCaptchaReq := req.UserCancelReq{ Captcha: "999999", } resp2, err := CancelService(ctx, wrongCaptchaReq) assert.NoError(t, err) assert.False(t, resp2.Success) assert.Contains(t, resp2.Message, "验证码错误") // 清理测试数据 cleanupUser(testPhone) } // TestUserRegistrationWithValidData 测试有效数据注册 func TestUserRegistrationWithValidData(t *testing.T) { testCases := []struct { name string username string password string phone string captcha string }{ { name: "标准注册", username: "testuser7", password: "123456", phone: "13800138007", captcha: "123456", }, { name: "长用户名", username: "verylongusername123", password: "123456", phone: "13800138008", captcha: "123456", }, { name: "复杂密码", username: "testuser9", password: "P@ssw0rd123", phone: "13800138009", captcha: "123456", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { registerReq := req.UserRegisterReq{ Username: tc.username, Password: tc.password, Phone: tc.phone, Captcha: tc.captcha, } setupCaptcha(tc.phone, tc.captcha) resp, err := RegisterService(registerReq) assert.NoError(t, err) assert.True(t, resp.Success) // 清理测试数据 cleanupUser(tc.phone) }) } } // setupCaptcha 设置验证码 func setupCaptcha(phone, captcha string) { captchaKey := utils.MakeUserCaptchaKey(phone) err := utils.SetValueToRedis(captchaKey, captcha, 5*time.Minute) if err != nil { panic(err) } } // cleanupUser 清理用户数据 func cleanupUser(phone string) { // 这里应该实现清理用户数据的逻辑 // 由于涉及到数据库操作,这里只是占位符 // 实际实现时需要根据具体的数据库操作来清理 } ================================================ FILE: service/user_service_test.go ================================================ package service import ( "DiTing-Go/domain/enum" "DiTing-Go/domain/vo/req" "testing" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" ) // TestUserLifecycleFlow 测试用户完整生命周期流程 func TestUserLifecycleFlow(t *testing.T) { // 设置测试环境 gin.SetMode(gin.TestMode) ctx := &gin.Context{} ctx.Set("uid", int64(12345)) // 测试数据 testPhone := "13800138000" testUsername := "testuser" testPassword := "123456" testCaptcha := "123456" // 步骤1: 用户注册 t.Log("=== 步骤1: 用户注册测试 ===") registerReq := req.UserRegisterReq{ Username: testUsername, Password: testPassword, Phone: testPhone, Captcha: testCaptcha, } // 设置验证码 setupCaptcha(testPhone, testCaptcha) registerResp, err := RegisterService(registerReq) assert.NoError(t, err) assert.True(t, registerResp.Success) // 步骤2: 用户登录(密码登录) t.Log("=== 步骤2: 用户密码登录测试 ===") loginReq := req.UserLoginReq{ Phone: testPhone, Password: testPassword, LoginType: enum.LoginByPassword, } loginResp, err := LoginService(loginReq) assert.NoError(t, err) assert.True(t, loginResp.Success) // 步骤3: 用户登录(验证码登录) t.Log("=== 步骤3: 用户验证码登录测试 ===") loginByCaptchaReq := req.UserLoginReq{ Phone: testPhone, Captcha: testCaptcha, LoginType: enum.LoginByPhoneCaptcha, } setupCaptcha(testPhone, testCaptcha) loginByCaptchaResp, err := LoginService(loginByCaptchaReq) assert.NoError(t, err) assert.True(t, loginByCaptchaResp.Success) // 步骤4: 用户注销 t.Log("=== 步骤4: 用户注销测试 ===") cancelReq := req.UserCancelReq{ Captcha: testCaptcha, } setupCaptcha(testPhone, testCaptcha) cancelResp, err := CancelService(ctx, cancelReq) assert.NoError(t, err) assert.True(t, cancelResp.Success) } // TestRegisterValidation 测试注册参数验证 func TestRegisterValidation(t *testing.T) { testCases := []struct { name string req req.UserRegisterReq expected string }{ { name: "用户名为空", req: req.UserRegisterReq{ Username: "", Password: "123456", Phone: "13800138001", Captcha: "123456", }, expected: "用户名、密码和手机号不能为空", }, { name: "密码长度不足", req: req.UserRegisterReq{ Username: "testuser", Password: "123", Phone: "13800138001", Captcha: "123456", }, expected: "密码长度不能少于6位", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { resp, err := RegisterService(tc.req) assert.NoError(t, err) assert.False(t, resp.Success) assert.Contains(t, resp.Message, tc.expected) }) } } // TestLoginValidation 测试登录参数验证 func TestLoginValidation(t *testing.T) { testCases := []struct { name string req req.UserLoginReq expected string }{ { name: "密码登录-手机号为空", req: req.UserLoginReq{ Phone: "", Password: "123456", LoginType: enum.LoginByPassword, }, expected: "用户名和密码不能为空", }, { name: "验证码登录-手机号为空", req: req.UserLoginReq{ Phone: "", Captcha: "123456", LoginType: enum.LoginByPhoneCaptcha, }, expected: "手机号和验证码不能为空", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { resp, err := LoginService(tc.req) assert.NoError(t, err) assert.False(t, resp.Success) assert.Contains(t, resp.Message, tc.expected) }) } } ================================================ FILE: sql/sql.sql ================================================ -- auto-generated definition create table contact ( id bigint unsigned auto_increment comment 'id' primary key, uid bigint not null comment 'uid', room_id bigint not null comment '房间id', read_time datetime(3) default CURRENT_TIMESTAMP(3) not null comment '阅读到的时间', active_time datetime(3) null comment '会话内消息最后更新的时间(只有普通会话需要维护,全员会话不需要维护)', last_msg_id bigint null comment '会话最新消息id', create_time datetime(3) default CURRENT_TIMESTAMP(3) not null comment '创建时间', update_time datetime(3) default CURRENT_TIMESTAMP(3) not null on update CURRENT_TIMESTAMP(3) comment '修改时间', constraint uniq_uid_room_id unique (uid, room_id) ) comment '会话列表' collate = utf8mb4_unicode_ci; create index idx_create_time on contact (create_time); create index idx_room_id_read_time on contact (room_id, read_time); create index idx_update_time on contact (update_time); create index idx_user_id_active_time on contact (uid, active_time); -- auto-generated definition create table group_member ( id bigint unsigned auto_increment comment 'id' primary key, group_id bigint not null comment '群主id', uid bigint not null comment '成员uid', role int not null comment '成员角色 1群主 2管理员 3普通成员', create_time datetime(3) default CURRENT_TIMESTAMP(3) not null comment '创建时间', update_time datetime(3) default CURRENT_TIMESTAMP(3) not null on update CURRENT_TIMESTAMP(3) comment '修改时间' ) comment '群成员表' collate = utf8mb4_unicode_ci; create index idx_create_time on group_member (create_time); create index idx_group_id_role on group_member (group_id, role); create index idx_update_time on group_member (update_time); -- auto-generated definition create table message ( id bigint unsigned auto_increment comment 'id' primary key, room_id bigint not null comment '会话表id', from_uid bigint not null comment '消息发送者uid', content varchar(1024) null comment '消息内容', reply_msg_id bigint null comment '回复的消息内容', status int not null comment '消息状态 0正常 1删除', gap_count int null comment '与回复的消息间隔多少条', type int default 1 null comment '消息类型 1正常文本 2.撤回消息', extra json null comment '扩展信息', create_time datetime(3) default CURRENT_TIMESTAMP(3) not null comment '创建时间', update_time datetime(3) default CURRENT_TIMESTAMP(3) not null on update CURRENT_TIMESTAMP(3) comment '修改时间' ) comment '消息表' collate = utf8mb4_unicode_ci row_format = DYNAMIC; create index idx_create_time on message (create_time); create index idx_from_uid on message (from_uid); create index idx_room_id on message (room_id); create index idx_update_time on message (update_time); -- auto-generated definition create table room ( id bigint unsigned auto_increment comment 'id' primary key, type int not null comment '房间类型 1群聊 2单聊', hot_flag int default 0 null comment '是否全员展示 0否 1是', active_time datetime(3) default CURRENT_TIMESTAMP(3) not null comment '群最后消息的更新时间(热点群不需要写扩散,只更新这里)', last_msg_id bigint null comment '会话中的最后一条消息id', ext_json json null comment '额外信息(根据不同类型房间有不同存储的东西)', create_time datetime(3) default CURRENT_TIMESTAMP(3) not null comment '创建时间', update_time datetime(3) default CURRENT_TIMESTAMP(3) not null on update CURRENT_TIMESTAMP(3) comment '修改时间' ) comment '房间表' collate = utf8mb4_unicode_ci; create index idx_create_time on room (create_time); create index idx_update_time on room (update_time); -- auto-generated definition create table room_friend ( id bigint unsigned auto_increment comment 'id' primary key, room_id bigint not null comment '房间id', uid1 bigint not null comment 'uid1(更小的uid)', uid2 bigint not null comment 'uid2(更大的uid)', room_key varchar(64) not null comment '房间key由两个uid拼接,先做排序uid1_uid2', status int not null comment '房间状态 0正常 1禁用(删好友了禁用)', create_time datetime(3) default CURRENT_TIMESTAMP(3) not null comment '创建时间', update_time datetime(3) default CURRENT_TIMESTAMP(3) not null on update CURRENT_TIMESTAMP(3) comment '修改时间', constraint room_key unique (room_key) ) comment '单聊房间表' collate = utf8mb4_unicode_ci; create index idx_create_time on room_friend (create_time); create index idx_room_id on room_friend (room_id); create index idx_update_time on room_friend (update_time); -- auto-generated definition create table room_group ( id bigint unsigned auto_increment comment 'id' primary key, room_id bigint not null comment '房间id', name varchar(16) not null comment '群名称', avatar varchar(256) not null comment '群头像', ext_json json null comment '额外信息(根据不同类型房间有不同存储的东西)', delete_status int(1) default 0 not null comment '逻辑删除(0-正常,1-删除)', create_time datetime(3) default CURRENT_TIMESTAMP(3) not null comment '创建时间', update_time datetime(3) default CURRENT_TIMESTAMP(3) not null on update CURRENT_TIMESTAMP(3) comment '修改时间' ) comment '群聊房间表' collate = utf8mb4_unicode_ci; create index idx_create_time on room_group (create_time); create index idx_room_id on room_group (room_id); create index idx_update_time on room_group (update_time); -- auto-generated definition create table user_friend ( id bigint unsigned auto_increment comment 'id' primary key, uid bigint not null comment 'uid', friend_uid bigint not null comment '好友uid', delete_status int(1) default 0 not null comment '逻辑删除(0-正常,1-删除)', create_time datetime(3) default CURRENT_TIMESTAMP(3) not null comment '创建时间', update_time datetime(3) default CURRENT_TIMESTAMP(3) not null on update CURRENT_TIMESTAMP(3) comment '修改时间', constraint uid unique (uid, friend_uid) ) comment '用户联系人表' collate = utf8mb4_unicode_ci; create index idx_create_time on user_friend (create_time); create index idx_uid_friend_uid on user_friend (uid, friend_uid); create index idx_update_time on user_friend (update_time); -- auto-generated definition create table user_apply ( id bigint unsigned auto_increment comment 'id' primary key, uid bigint not null comment '申请人uid', type int not null comment '申请类型 1加好友', target_id bigint not null comment '接收人uid', msg varchar(64) not null comment '申请信息', status int not null comment '申请状态 1待审批 2同意', read_status int not null comment '阅读状态 1未读 2已读', create_time datetime(3) default CURRENT_TIMESTAMP(3) not null comment '创建时间', update_time datetime(3) default CURRENT_TIMESTAMP(3) not null on update CURRENT_TIMESTAMP(3) comment '修改时间', constraint uid unique (uid, target_id) ) comment '用户申请表' collate = utf8mb4_unicode_ci; create index idx_create_time on user_apply (create_time); create index idx_target_id on user_apply (target_id); create index idx_target_id_read_status on user_apply (target_id, read_status); create index idx_uid_target_id on user_apply (uid, target_id); create index idx_update_time on user_apply (update_time); ================================================ FILE: tests/README.md ================================================ # DiTing-Go 测试文件夹结构 ## 概述 本文件夹包含了DiTing-Go项目的所有测试文件,按照测试类型和功能模块进行了分类组织。 ## 文件夹结构 ``` tests/ ├── unit/ # 单元测试 ├── integration/ # 集成测试 ├── e2e/ # 端到端测试 ├── performance/ # 性能测试 ├── scripts/ # 测试脚本 ├── config/ # 测试配置文件 └── README.md # 本文件 ``` ## 测试类型说明 ### 1. 单元测试 (unit/) - **目的**: 测试单个函数或方法的正确性 - **特点**: 快速、独立、可重复 - **覆盖范围**: 业务逻辑、工具函数、数据验证等 ### 2. 集成测试 (integration/) - **目的**: 测试多个组件之间的协作 - **特点**: 涉及数据库、缓存、外部服务 - **覆盖范围**: 服务层、数据访问层、缓存层 ### 3. 端到端测试 (e2e/) - **目的**: 测试完整的用户业务流程 - **特点**: 模拟真实用户操作 - **覆盖范围**: API接口、WebSocket、完整业务流程 ### 4. 性能测试 (performance/) - **目的**: 测试系统性能和稳定性 - **特点**: 高并发、大数据量、长时间运行 - **覆盖范围**: 负载测试、压力测试、基准测试 ## 运行测试 ### 运行所有测试 ```bash go test ./tests/... ``` ### 运行特定类型测试 ```bash # 运行单元测试 go test ./tests/unit/... # 运行集成测试 go test ./tests/integration/... # 运行端到端测试 go test ./tests/e2e/... # 运行性能测试 go test ./tests/performance/... ``` ### 使用测试脚本 ```bash # Linux/Mac ./tests/scripts/run_tests.sh # Windows tests\scripts\run_tests.bat ``` ## 测试配置 ### 环境变量 ```bash # 测试环境 export GIN_MODE=test export TEST_ENV=test # 数据库配置 export TEST_DB_HOST=localhost export TEST_DB_PORT=3306 export TEST_DB_NAME=diting_test # Redis配置 export TEST_REDIS_HOST=localhost export TEST_REDIS_PORT=6379 export TEST_REDIS_DB=1 ``` ## 最佳实践 ### 1. 测试编写 - 每个测试函数只测试一个功能点 - 使用描述性的测试函数名 - 包含正向和异常测试用例 ### 2. 测试数据 - 使用固定的测试数据确保可重复性 - 避免测试之间的数据依赖 - 及时清理测试数据 ### 3. 测试维护 - 定期更新测试用例 - 保持测试代码的可读性 - 及时修复失败的测试 ================================================ FILE: tests/e2e/user_workflow_e2e_test.go ================================================ package e2e import ( "DiTing-Go/domain/enum" "DiTing-Go/domain/vo/req" "DiTing-Go/global" "DiTing-Go/service" "DiTing-Go/utils" "DiTing-Go/utils/setting" "testing" "time" "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" ) // 初始化函数,在包加载时执行 func init() { // 设置测试环境变量 gin.SetMode(gin.TestMode) // 初始化配置 setting.ConfigInit() // 初始化简单的测试日志 global.Logger = logrus.New() global.Logger.SetOutput(gin.DefaultWriter) global.Logger.SetLevel(logrus.InfoLevel) // 初始化Redis global.RedisInit() // 初始化数据库 global.DBInit() } // TestUserCompleteWorkflow 测试用户完整工作流 func TestUserCompleteWorkflow(t *testing.T) { // 设置测试环境 gin.SetMode(gin.TestMode) // 测试数据 testPhone := "13800138100" testUsername := "e2euser" testPassword := "123456" testCaptcha := "123456" // 步骤1: 用户注册 t.Log("=== E2E测试: 用户注册 ===") setupCaptcha(testPhone, testCaptcha) registerReq := req.UserRegisterReq{ Username: testUsername, Password: testPassword, Phone: testPhone, Captcha: testCaptcha, } registerResp, err := service.RegisterService(registerReq) assert.NoError(t, err) assert.True(t, registerResp.Success) t.Log("✅ 用户注册成功") // 步骤2: 用户登录(密码登录) t.Log("=== E2E测试: 用户密码登录 ===") loginReq := req.UserLoginReq{ Phone: testPhone, Password: testPassword, LoginType: enum.LoginByPassword, } loginResp, err := service.LoginService(loginReq) assert.NoError(t, err) assert.True(t, loginResp.Success) // 验证登录响应 loginData, ok := loginResp.Data.(map[string]interface{}) assert.True(t, ok) assert.NotEmpty(t, loginData["token"]) assert.NotZero(t, loginData["uid"]) assert.Equal(t, testUsername, loginData["name"]) t.Logf("✅ 用户登录成功: uid=%v, name=%s", loginData["uid"], loginData["name"]) // 步骤3: 用户登录(验证码登录) t.Log("=== E2E测试: 用户验证码登录 ===") setupCaptcha(testPhone, testCaptcha) loginByCaptchaReq := req.UserLoginReq{ Phone: testPhone, Captcha: testCaptcha, LoginType: enum.LoginByPhoneCaptcha, } loginByCaptchaResp, err := service.LoginService(loginByCaptchaReq) assert.NoError(t, err) assert.True(t, loginByCaptchaResp.Success) t.Log("✅ 验证码登录成功") // 步骤4: 用户注销 t.Log("=== E2E测试: 用户注销 ===") ctx := &gin.Context{} ctx.Set("uid", loginData["uid"]) setupCaptcha(testPhone, testCaptcha) cancelReq := req.UserCancelReq{ Captcha: testCaptcha, } cancelResp, err := service.CancelService(ctx, cancelReq) assert.NoError(t, err) assert.True(t, cancelResp.Success) t.Log("✅ 用户注销成功") // 步骤5: 验证用户已注销(尝试登录应该失败) t.Log("=== E2E测试: 验证用户已注销 ===") loginAfterCancelReq := req.UserLoginReq{ Phone: testPhone, Password: testPassword, LoginType: enum.LoginByPassword, } loginAfterCancelResp, err := service.LoginService(loginAfterCancelReq) assert.NoError(t, err) assert.False(t, loginAfterCancelResp.Success) t.Log("✅ 验证用户已注销成功") } // TestMultipleUserWorkflow 测试多用户并发工作流 func TestMultipleUserWorkflow(t *testing.T) { // 测试多个用户同时注册和登录 users := []struct { phone string username string password string }{ {"13800138101", "user1", "123456"}, {"13800138102", "user2", "123456"}, {"13800138103", "user3", "123456"}, } for i, user := range users { t.Run(user.username, func(t *testing.T) { // 注册用户 setupCaptcha(user.phone, "123456") registerReq := req.UserRegisterReq{ Username: user.username, Password: user.password, Phone: user.phone, Captcha: "123456", } registerResp, err := service.RegisterService(registerReq) assert.NoError(t, err) assert.True(t, registerResp.Success) // 登录用户 loginReq := req.UserLoginReq{ Phone: user.phone, Password: user.password, LoginType: enum.LoginByPassword, } loginResp, err := service.LoginService(loginReq) assert.NoError(t, err) assert.True(t, loginResp.Success) t.Logf("✅ 用户 %d 注册和登录成功", i+1) // 清理测试数据 cleanupUser(user.phone) }) } } // TestUserErrorScenarios 测试用户错误场景 func TestUserErrorScenarios(t *testing.T) { t.Run("重复注册", func(t *testing.T) { testPhone := "13800138104" testUsername := "erroruser" testPassword := "123456" // 第一次注册 setupCaptcha(testPhone, "123456") registerReq := req.UserRegisterReq{ Username: testUsername, Password: testPassword, Phone: testPhone, Captcha: "123456", } resp1, err := service.RegisterService(registerReq) assert.NoError(t, err) assert.True(t, resp1.Success) // 第二次注册相同手机号 setupCaptcha(testPhone, "123456") resp2, err := service.RegisterService(registerReq) assert.NoError(t, err) assert.False(t, resp2.Success) assert.Contains(t, resp2.Message, "手机号已存在") cleanupUser(testPhone) }) t.Run("错误密码登录", func(t *testing.T) { testPhone := "13800138105" testUsername := "erroruser2" testPassword := "123456" // 注册用户 setupCaptcha(testPhone, "123456") registerReq := req.UserRegisterReq{ Username: testUsername, Password: testPassword, Phone: testPhone, Captcha: "123456", } resp, err := service.RegisterService(registerReq) assert.NoError(t, err) assert.True(t, resp.Success) // 使用错误密码登录 wrongPasswordReq := req.UserLoginReq{ Phone: testPhone, Password: "wrongpassword", LoginType: enum.LoginByPassword, } loginResp, err := service.LoginService(wrongPasswordReq) assert.NoError(t, err) assert.False(t, loginResp.Success) assert.Contains(t, loginResp.Message, "用户名或密码错误") cleanupUser(testPhone) }) } // setupCaptcha 设置验证码 func setupCaptcha(phone, captcha string) { captchaKey := utils.MakeUserCaptchaKey(phone) err := utils.SetValueToRedis(captchaKey, captcha, 5*time.Minute) if err != nil { panic(err) } } // cleanupUser 清理用户数据 func cleanupUser(phone string) { // 这里应该实现清理用户数据的逻辑 // 由于涉及到数据库操作,这里只是占位符 // 实际实现时需要根据具体的数据库操作来清理 } ================================================ FILE: tests/init_test.go ================================================ package tests import ( "DiTing-Go/global" "log" "os" "testing" ) // TestMain 在所有测试运行前执行初始化 func TestMain(m *testing.M) { // 设置测试环境变量 os.Setenv("GIN_MODE", "test") os.Setenv("TEST_ENV", "test") // 初始化日志 global.LogInit() // 初始化Redis global.RedisInit() // 初始化数据库 global.DBInit() // 运行测试 code := m.Run() // 清理资源 cleanup() os.Exit(code) } // cleanup 清理测试资源 func cleanup() { // 关闭Redis连接 if global.Rdb != nil { global.Rdb.Close() } // 关闭数据库连接 if global.Query != nil { // 这里可以添加数据库连接关闭逻辑 } log.Println("测试资源清理完成") } ================================================ FILE: tests/integration/user_integration_test.go ================================================ package integration import ( "DiTing-Go/domain/enum" "DiTing-Go/domain/vo/req" "DiTing-Go/logic" "DiTing-Go/service" "DiTing-Go/utils" "context" "testing" "time" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" ) // TestUserRegistrationFlow 测试用户注册流程 func TestUserRegistrationFlow(t *testing.T) { // 测试数据 testPhone := "13800138001" testUsername := "testuser1" testPassword := "123456" testCaptcha := "123456" // 设置验证码 setupCaptcha(testPhone, testCaptcha) // 执行注册 registerReq := req.UserRegisterReq{ Username: testUsername, Password: testPassword, Phone: testPhone, Captcha: testCaptcha, } resp, err := service.RegisterService(registerReq) assert.NoError(t, err) assert.True(t, resp.Success) assert.Contains(t, resp.Message, "注册成功") // 验证用户是否真的被创建 userExists, err := logic.CheckPhoneInDB(context.Background(), testPhone) assert.NoError(t, err) assert.True(t, userExists) // 清理测试数据 cleanupUser(testPhone) } // TestUserLoginFlow 测试用户登录流程 func TestUserLoginFlow(t *testing.T) { // 先注册一个用户 testPhone := "13800138002" testUsername := "testuser2" testPassword := "123456" testCaptcha := "123456" setupCaptcha(testPhone, testCaptcha) registerReq := req.UserRegisterReq{ Username: testUsername, Password: testPassword, Phone: testPhone, Captcha: testCaptcha, } registerResp, err := service.RegisterService(registerReq) assert.NoError(t, err) assert.True(t, registerResp.Success) // 测试密码登录 loginReq := req.UserLoginReq{ Phone: testPhone, Password: testPassword, LoginType: enum.LoginByPassword, } loginResp, err := service.LoginService(loginReq) assert.NoError(t, err) assert.True(t, loginResp.Success) // 验证登录响应数据结构 loginData, ok := loginResp.Data.(map[string]interface{}) assert.True(t, ok) assert.NotEmpty(t, loginData["token"]) assert.NotZero(t, loginData["uid"]) assert.Equal(t, testUsername, loginData["name"]) // 清理测试数据 cleanupUser(testPhone) } // TestUserCancelFlow 测试用户注销流程 func TestUserCancelFlow(t *testing.T) { // 先注册一个用户 testPhone := "13800138003" testUsername := "testuser3" testPassword := "123456" testCaptcha := "123456" setupCaptcha(testPhone, testCaptcha) registerReq := req.UserRegisterReq{ Username: testUsername, Password: testPassword, Phone: testPhone, Captcha: testCaptcha, } registerResp, err := service.RegisterService(registerReq) assert.NoError(t, err) assert.True(t, registerResp.Success) // 设置注销验证码 setupCaptcha(testPhone, testCaptcha) // 执行注销 ctx := &gin.Context{} ctx.Set("uid", int64(12345)) cancelReq := req.UserCancelReq{ Captcha: testCaptcha, } cancelResp, err := service.CancelService(ctx, cancelReq) assert.NoError(t, err) assert.True(t, cancelResp.Success) assert.Contains(t, cancelResp.Message, "注销成功") // 验证用户是否真的被删除 userExists, err := logic.CheckPhoneInDB(context.Background(), testPhone) assert.NoError(t, err) assert.False(t, userExists) } // TestDuplicateRegistration 测试重复注册 func TestDuplicateRegistration(t *testing.T) { testPhone := "13800138004" testUsername := "testuser4" testPassword := "123456" testCaptcha := "123456" // 第一次注册 setupCaptcha(testPhone, testCaptcha) registerReq := req.UserRegisterReq{ Username: testUsername, Password: testPassword, Phone: testPhone, Captcha: testCaptcha, } resp1, err := service.RegisterService(registerReq) assert.NoError(t, err) assert.True(t, resp1.Success) // 第二次注册相同手机号 setupCaptcha(testPhone, testCaptcha) resp2, err := service.RegisterService(registerReq) assert.NoError(t, err) assert.False(t, resp2.Success) assert.Contains(t, resp2.Message, "手机号已存在") // 清理测试数据 cleanupUser(testPhone) } // TestLoginWithWrongCredentials 测试错误凭据登录 func TestLoginWithWrongCredentials(t *testing.T) { // 先注册一个用户 testPhone := "13800138005" testUsername := "testuser5" testPassword := "123456" testCaptcha := "123456" setupCaptcha(testPhone, testCaptcha) registerReq := req.UserRegisterReq{ Username: testUsername, Password: testPassword, Phone: testPhone, Captcha: testCaptcha, } resp, err := service.RegisterService(registerReq) assert.NoError(t, err) assert.True(t, resp.Success) // 测试错误密码 wrongPasswordReq := req.UserLoginReq{ Phone: testPhone, Password: "wrongpassword", LoginType: enum.LoginByPassword, } resp2, err := service.LoginService(wrongPasswordReq) assert.NoError(t, err) assert.False(t, resp2.Success) assert.Contains(t, resp2.Message, "用户名或密码错误") // 测试错误验证码 wrongCaptchaReq := req.UserLoginReq{ Phone: testPhone, Captcha: "999999", LoginType: enum.LoginByPhoneCaptcha, } resp3, err := service.LoginService(wrongCaptchaReq) assert.NoError(t, err) assert.False(t, resp3.Success) assert.Contains(t, resp3.Message, "验证码错误") // 清理测试数据 cleanupUser(testPhone) } // TestCancelWithWrongCaptcha 测试错误验证码注销 func TestCancelWithWrongCaptcha(t *testing.T) { // 先注册一个用户 testPhone := "13800138006" testUsername := "testuser6" testPassword := "123456" testCaptcha := "123456" setupCaptcha(testPhone, testCaptcha) registerReq := req.UserRegisterReq{ Username: testUsername, Password: testPassword, Phone: testPhone, Captcha: testCaptcha, } resp, err := service.RegisterService(registerReq) assert.NoError(t, err) assert.True(t, resp.Success) // 测试错误验证码注销 ctx := &gin.Context{} ctx.Set("uid", int64(12345)) wrongCaptchaReq := req.UserCancelReq{ Captcha: "999999", } resp2, err := service.CancelService(ctx, wrongCaptchaReq) assert.NoError(t, err) assert.False(t, resp2.Success) assert.Contains(t, resp2.Message, "验证码错误") // 清理测试数据 cleanupUser(testPhone) } // TestUserRegistrationWithValidData 测试有效数据注册 func TestUserRegistrationWithValidData(t *testing.T) { testCases := []struct { name string username string password string phone string captcha string }{ { name: "标准注册", username: "testuser7", password: "123456", phone: "13800138007", captcha: "123456", }, { name: "长用户名", username: "verylongusername123", password: "123456", phone: "13800138008", captcha: "123456", }, { name: "复杂密码", username: "testuser9", password: "P@ssw0rd123", phone: "13800138009", captcha: "123456", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { registerReq := req.UserRegisterReq{ Username: tc.username, Password: tc.password, Phone: tc.phone, Captcha: tc.captcha, } setupCaptcha(tc.phone, tc.captcha) resp, err := service.RegisterService(registerReq) assert.NoError(t, err) assert.True(t, resp.Success) // 清理测试数据 cleanupUser(tc.phone) }) } } // setupCaptcha 设置验证码 func setupCaptcha(phone, captcha string) { captchaKey := utils.MakeUserCaptchaKey(phone) err := utils.SetValueToRedis(captchaKey, captcha, 5*time.Minute) if err != nil { panic(err) } } // cleanupUser 清理用户数据 func cleanupUser(phone string) { // 这里应该实现清理用户数据的逻辑 // 由于涉及到数据库操作,这里只是占位符 // 实际实现时需要根据具体的数据库操作来清理 } ================================================ FILE: tests/performance/user_performance_test.go ================================================ package performance import ( "DiTing-Go/domain/enum" "DiTing-Go/domain/vo/req" "DiTing-Go/service" "DiTing-Go/utils" "fmt" "sync" "testing" "time" "github.com/stretchr/testify/assert" ) // BenchmarkUserRegistration 用户注册性能基准测试 func BenchmarkUserRegistration(b *testing.B) { // 重置计时器 b.ResetTimer() for i := 0; i < b.N; i++ { phone := fmt.Sprintf("13800139%03d", i%1000) username := fmt.Sprintf("benchuser%d", i) password := "123456" captcha := "123456" // 设置验证码 setupCaptcha(phone, captcha) // 执行注册 registerReq := req.UserRegisterReq{ Username: username, Password: password, Phone: phone, Captcha: captcha, } resp, err := service.RegisterService(registerReq) assert.NoError(b, err) assert.True(b, resp.Success) // 清理测试数据 cleanupUser(phone) } } // BenchmarkUserLogin 用户登录性能基准测试 func BenchmarkUserLogin(b *testing.B) { // 先注册一个用户用于测试 testPhone := "13800139000" testUsername := "benchuser" testPassword := "123456" testCaptcha := "123456" setupCaptcha(testPhone, testCaptcha) registerReq := req.UserRegisterReq{ Username: testUsername, Password: testPassword, Phone: testPhone, Captcha: testCaptcha, } resp, err := service.RegisterService(registerReq) assert.NoError(b, err) assert.True(b, resp.Success) // 重置计时器 b.ResetTimer() for i := 0; i < b.N; i++ { loginReq := req.UserLoginReq{ Phone: testPhone, Password: testPassword, LoginType: enum.LoginByPassword, } loginResp, err := service.LoginService(loginReq) assert.NoError(b, err) assert.True(b, loginResp.Success) } // 清理测试数据 cleanupUser(testPhone) } // TestConcurrentUserRegistration 并发用户注册测试 func TestConcurrentUserRegistration(t *testing.T) { const numUsers = 10 const numGoroutines = 5 var wg sync.WaitGroup errors := make(chan error, numUsers*numGoroutines) startTime := time.Now() for i := 0; i < numGoroutines; i++ { wg.Add(1) go func(routineID int) { defer wg.Done() for j := 0; j < numUsers; j++ { userID := routineID*numUsers + j phone := fmt.Sprintf("13800140%03d", userID) username := fmt.Sprintf("concurrentuser%d", userID) password := "123456" captcha := "123456" // 设置验证码 setupCaptcha(phone, captcha) // 执行注册 registerReq := req.UserRegisterReq{ Username: username, Password: password, Phone: phone, Captcha: captcha, } resp, err := service.RegisterService(registerReq) if err != nil { errors <- err return } if !resp.Success { errors <- fmt.Errorf("注册失败: %s", resp.Message) return } // 清理测试数据 cleanupUser(phone) } }(i) } wg.Wait() close(errors) duration := time.Since(startTime) t.Logf("并发注册 %d 个用户,耗时: %v", numUsers*numGoroutines, duration) // 检查是否有错误 for err := range errors { t.Errorf("并发注册错误: %v", err) } } // TestConcurrentUserLogin 并发用户登录测试 func TestConcurrentUserLogin(t *testing.T) { const numUsers = 10 const numGoroutines = 5 // 先注册一些用户 users := make([]string, numUsers*numGoroutines) for i := 0; i < numUsers*numGoroutines; i++ { phone := fmt.Sprintf("13800141%03d", i) username := fmt.Sprintf("loginuser%d", i) password := "123456" captcha := "123456" setupCaptcha(phone, captcha) registerReq := req.UserRegisterReq{ Username: username, Password: password, Phone: phone, Captcha: captcha, } resp, err := service.RegisterService(registerReq) assert.NoError(t, err) assert.True(t, resp.Success) users[i] = phone } var wg sync.WaitGroup errors := make(chan error, numUsers*numGoroutines) startTime := time.Now() for i := 0; i < numGoroutines; i++ { wg.Add(1) go func(routineID int) { defer wg.Done() for j := 0; j < numUsers; j++ { userID := routineID*numUsers + j phone := users[userID] password := "123456" loginReq := req.UserLoginReq{ Phone: phone, Password: password, LoginType: enum.LoginByPassword, } resp, err := service.LoginService(loginReq) if err != nil { errors <- err return } if !resp.Success { errors <- fmt.Errorf("登录失败: %s", resp.Message) return } } }(i) } wg.Wait() close(errors) duration := time.Since(startTime) t.Logf("并发登录 %d 个用户,耗时: %v", numUsers*numGoroutines, duration) // 检查是否有错误 for err := range errors { t.Errorf("并发登录错误: %v", err) } // 清理测试数据 for _, phone := range users { cleanupUser(phone) } } // TestLoadTest 负载测试 func TestLoadTest(t *testing.T) { const numUsers = 100 const duration = 30 * time.Second startTime := time.Now() successCount := 0 errorCount := 0 var mu sync.Mutex // 创建用户注册任务 for i := 0; i < numUsers; i++ { go func(userID int) { phone := fmt.Sprintf("13800142%03d", userID) username := fmt.Sprintf("loaduser%d", userID) password := "123456" captcha := "123456" setupCaptcha(phone, captcha) registerReq := req.UserRegisterReq{ Username: username, Password: password, Phone: phone, Captcha: captcha, } resp, err := service.RegisterService(registerReq) mu.Lock() if err != nil || !resp.Success { errorCount++ } else { successCount++ } mu.Unlock() // 清理测试数据 cleanupUser(phone) }(i) } // 等待指定时间 time.Sleep(duration) totalTime := time.Since(startTime) t.Logf("负载测试结果:") t.Logf(" 总时间: %v", totalTime) t.Logf(" 成功请求: %d", successCount) t.Logf(" 失败请求: %d", errorCount) t.Logf(" 成功率: %.2f%%", float64(successCount)/float64(successCount+errorCount)*100) } // setupCaptcha 设置验证码 func setupCaptcha(phone, captcha string) { captchaKey := utils.MakeUserCaptchaKey(phone) err := utils.SetValueToRedis(captchaKey, captcha, 5*time.Minute) if err != nil { panic(err) } } // cleanupUser 清理用户数据 func cleanupUser(phone string) { // 这里应该实现清理用户数据的逻辑 // 由于涉及到数据库操作,这里只是占位符 // 实际实现时需要根据具体的数据库操作来清理 } ================================================ FILE: tests/scripts/run_e2e_test.sh ================================================ #!/bin/bash # DiTing-Go E2E测试运行脚本 echo "=== DiTing-Go E2E测试运行脚本 ===" echo "" # 检查Go环境 if ! command -v go &> /dev/null; then echo "错误: 未找到Go环境,请先安装Go" exit 1 fi # 获取项目根目录 PROJECT_ROOT=$(pwd) echo "项目根目录: $PROJECT_ROOT" # 设置环境变量 export PROJECT_ROOT="$PROJECT_ROOT" export GIN_MODE=test export TEST_ENV=test echo "环境变量设置:" echo " PROJECT_ROOT=$PROJECT_ROOT" echo " GIN_MODE=$GIN_MODE" echo " TEST_ENV=$TEST_ENV" # 检查配置文件 if [ -f "$PROJECT_ROOT/conf/config.yml" ]; then echo "✅ 配置文件存在: $PROJECT_ROOT/conf/config.yml" else echo "❌ 配置文件不存在: $PROJECT_ROOT/conf/config.yml" exit 1 fi # 检查依赖 echo "检查依赖..." go mod tidy echo "" echo "=== 运行E2E测试 ===" # 确保在项目根目录运行测试 cd "$PROJECT_ROOT" # 运行特定的E2E测试 echo "运行用户完整工作流E2E测试..." go test -v -run TestUserCompleteWorkflow ./tests/e2e/ echo "" echo "运行多用户并发E2E测试..." go test -v -run TestMultipleUserWorkflow ./tests/e2e/ echo "" echo "运行用户错误场景E2E测试..." go test -v -run TestUserErrorScenarios ./tests/e2e/ echo "" echo "=== E2E测试完成 ===" ================================================ FILE: tests/scripts/run_tests.bat ================================================ @echo off chcp 65001 >nul setlocal enabledelayedexpansion echo === DiTing-Go 用户服务测试 === echo. REM 检查Go环境 where go >nul 2>nul if %errorlevel% neq 0 ( echo 错误: 未找到Go环境,请先安装Go pause exit /b 1 ) REM 检查依赖 echo 检查依赖... go mod tidy REM 设置测试环境变量 set GIN_MODE=test :menu echo. echo 请选择要运行的测试: echo 1. 用户完整生命周期测试 echo 2. 用户注册流程测试 echo 3. 用户登录流程测试 echo 4. 用户注销流程测试 echo 5. 注册参数验证测试 echo 6. 登录参数验证测试 echo 7. 重复注册测试 echo 8. 错误凭据登录测试 echo 9. 错误验证码注销测试 echo 10. 有效数据注册测试 echo 11. 运行所有测试 echo 0. 退出 echo. set /p choice=请输入选项 (0-11): if "%choice%"=="1" goto test_lifecycle if "%choice%"=="2" goto test_register if "%choice%"=="3" goto test_login if "%choice%"=="4" goto test_cancel if "%choice%"=="5" goto test_register_validation if "%choice%"=="6" goto test_login_validation if "%choice%"=="7" goto test_duplicate_register if "%choice%"=="8" goto test_wrong_credentials if "%choice%"=="9" goto test_wrong_captcha if "%choice%"=="10" goto test_valid_data if "%choice%"=="11" goto test_all if "%choice%"=="0" goto exit echo 无效选项,请重新选择 goto menu :test_lifecycle echo. echo === 运行测试: 用户完整生命周期测试 === echo 命令: go test -v -run TestUserLifecycleFlow echo. go test -v -run TestUserLifecycleFlow ./service/ if %errorlevel% equ 0 ( echo ✅ 用户完整生命周期测试通过 ) else ( echo ❌ 用户完整生命周期测试失败 ) goto continue :test_register echo. echo === 运行测试: 用户注册流程测试 === echo 命令: go test -v -run TestUserRegistrationFlow echo. go test -v -run TestUserRegistrationFlow ./service/ if %errorlevel% equ 0 ( echo ✅ 用户注册流程测试通过 ) else ( echo ❌ 用户注册流程测试失败 ) goto continue :test_login echo. echo === 运行测试: 用户登录流程测试 === echo 命令: go test -v -run TestUserLoginFlow echo. go test -v -run TestUserLoginFlow ./service/ if %errorlevel% equ 0 ( echo ✅ 用户登录流程测试通过 ) else ( echo ❌ 用户登录流程测试失败 ) goto continue :test_cancel echo. echo === 运行测试: 用户注销流程测试 === echo 命令: go test -v -run TestUserCancelFlow echo. go test -v -run TestUserCancelFlow ./service/ if %errorlevel% equ 0 ( echo ✅ 用户注销流程测试通过 ) else ( echo ❌ 用户注销流程测试失败 ) goto continue :test_register_validation echo. echo === 运行测试: 注册参数验证测试 === echo 命令: go test -v -run TestRegisterValidation echo. go test -v -run TestRegisterValidation ./service/ if %errorlevel% equ 0 ( echo ✅ 注册参数验证测试通过 ) else ( echo ❌ 注册参数验证测试失败 ) goto continue :test_login_validation echo. echo === 运行测试: 登录参数验证测试 === echo 命令: go test -v -run TestLoginValidation echo. go test -v -run TestLoginValidation ./service/ if %errorlevel% equ 0 ( echo ✅ 登录参数验证测试通过 ) else ( echo ❌ 登录参数验证测试失败 ) goto continue :test_duplicate_register echo. echo === 运行测试: 重复注册测试 === echo 命令: go test -v -run TestDuplicateRegistration echo. go test -v -run TestDuplicateRegistration ./service/ if %errorlevel% equ 0 ( echo ✅ 重复注册测试通过 ) else ( echo ❌ 重复注册测试失败 ) goto continue :test_wrong_credentials echo. echo === 运行测试: 错误凭据登录测试 === echo 命令: go test -v -run TestLoginWithWrongCredentials echo. go test -v -run TestLoginWithWrongCredentials ./service/ if %errorlevel% equ 0 ( echo ✅ 错误凭据登录测试通过 ) else ( echo ❌ 错误凭据登录测试失败 ) goto continue :test_wrong_captcha echo. echo === 运行测试: 错误验证码注销测试 === echo 命令: go test -v -run TestCancelWithWrongCaptcha echo. go test -v -run TestCancelWithWrongCaptcha ./service/ if %errorlevel% equ 0 ( echo ✅ 错误验证码注销测试通过 ) else ( echo ❌ 错误验证码注销测试失败 ) goto continue :test_valid_data echo. echo === 运行测试: 有效数据注册测试 === echo 命令: go test -v -run TestUserRegistrationWithValidData echo. go test -v -run TestUserRegistrationWithValidData ./service/ if %errorlevel% equ 0 ( echo ✅ 有效数据注册测试通过 ) else ( echo ❌ 有效数据注册测试失败 ) goto continue :test_all echo. echo === 运行所有测试 === set failed_tests= for %%t in (TestUserLifecycleFlow TestUserRegistrationFlow TestUserLoginFlow TestUserCancelFlow TestRegisterValidation TestLoginValidation TestDuplicateRegistration TestLoginWithWrongCredentials TestCancelWithWrongCaptcha TestUserRegistrationWithValidData) do ( echo. echo 运行测试: %%t go test -v -run %%t ./service/ if !errorlevel! neq 0 ( set failed_tests=!failed_tests! %%t ) ) echo. echo === 测试结果汇总 === if "%failed_tests%"=="" ( echo ✅ 所有测试通过 ) else ( echo ❌ 以下测试失败: for %%t in (%failed_tests%) do ( echo - %%t ) ) goto continue :continue echo. pause goto menu :exit echo 退出测试 pause exit /b 0 ================================================ FILE: tests/scripts/run_tests.sh ================================================ #!/bin/bash # DiTing-Go 测试运行脚本 echo "=== DiTing-Go 测试运行脚本 ===" echo "" # 检查Go环境 if ! command -v go &> /dev/null; then echo "错误: 未找到Go环境,请先安装Go" exit 1 fi # 检查依赖 echo "检查依赖..." go mod tidy # 获取项目根目录并设置环境变量 PROJECT_ROOT=$(pwd) export PROJECT_ROOT="$PROJECT_ROOT" export GIN_MODE=test export TEST_ENV=test echo "项目根目录: $PROJECT_ROOT" echo "环境变量设置完成" # 运行测试的函数 run_test() { local test_name=$1 local test_pattern=$2 local test_dir=$3 echo "" echo "=== 运行测试: $test_name ===" echo "命令: go test -v -run $test_pattern $test_dir" echo "" # 确保在项目根目录运行测试 cd "$PROJECT_ROOT" go test -v -run "$test_pattern" "$test_dir" if [ $? -eq 0 ]; then echo "✅ $test_name 测试通过" else echo "❌ $test_name 测试失败" return 1 fi } # 主菜单 show_menu() { echo "" echo "请选择要运行的测试:" echo "=== 单元测试 ===" echo "1. 用户服务单元测试" echo "2. 用户生命周期单元测试" echo "3. 注册验证单元测试" echo "4. 登录验证单元测试" echo "" echo "=== 集成测试 ===" echo "5. 用户注册集成测试" echo "6. 用户登录集成测试" echo "7. 用户注销集成测试" echo "8. 重复注册集成测试" echo "9. 错误凭据登录集成测试" echo "10. 错误验证码注销集成测试" echo "11. 有效数据注册集成测试" echo "" echo "=== 端到端测试 ===" echo "12. 用户完整工作流E2E测试" echo "13. 多用户并发E2E测试" echo "14. 用户错误场景E2E测试" echo "" echo "=== 性能测试 ===" echo "15. 用户注册性能测试" echo "16. 用户登录性能测试" echo "17. 并发用户注册测试" echo "18. 并发用户登录测试" echo "19. 负载测试" echo "" echo "=== 批量测试 ===" echo "20. 运行所有单元测试" echo "21. 运行所有集成测试" echo "22. 运行所有E2E测试" echo "23. 运行所有性能测试" echo "24. 运行所有测试" echo "0. 退出" echo "" read -p "请输入选项 (0-24): " choice } # 运行所有测试 run_all_tests() { echo "" echo "=== 运行所有测试 ===" # 确保在项目根目录运行测试 cd "$PROJECT_ROOT" test_dirs=( "./tests/unit/" "./tests/integration/" "./tests/e2e/" "./tests/performance/" ) failed_tests=() for dir in "${test_dirs[@]}"; do echo "" echo "运行测试目录: $dir" go test -v "$dir" if [ $? -ne 0 ]; then failed_tests+=("$dir") fi done echo "" echo "=== 测试结果汇总 ===" if [ ${#failed_tests[@]} -eq 0 ]; then echo "✅ 所有测试通过" else echo "❌ 以下测试失败:" for test in "${failed_tests[@]}"; do echo " - $test" done fi } # 主循环 while true; do show_menu case $choice in 1) run_test "用户服务单元测试" "TestUserLifecycleFlow" "./tests/unit/" ;; 2) run_test "用户生命周期单元测试" "TestUserLifecycleFlow" "./tests/unit/" ;; 3) run_test "注册验证单元测试" "TestRegisterValidation" "./tests/unit/" ;; 4) run_test "登录验证单元测试" "TestLoginValidation" "./tests/unit/" ;; 5) run_test "用户注册集成测试" "TestUserRegistrationFlow" "./tests/integration/" ;; 6) run_test "用户登录集成测试" "TestUserLoginFlow" "./tests/integration/" ;; 7) run_test "用户注销集成测试" "TestUserCancelFlow" "./tests/integration/" ;; 8) run_test "重复注册集成测试" "TestDuplicateRegistration" "./tests/integration/" ;; 9) run_test "错误凭据登录集成测试" "TestLoginWithWrongCredentials" "./tests/integration/" ;; 10) run_test "错误验证码注销集成测试" "TestCancelWithWrongCaptcha" "./tests/integration/" ;; 11) run_test "有效数据注册集成测试" "TestUserRegistrationWithValidData" "./tests/integration/" ;; 12) run_test "用户完整工作流E2E测试" "TestUserCompleteWorkflow" "./tests/e2e/" ;; 13) run_test "多用户并发E2E测试" "TestMultipleUserWorkflow" "./tests/e2e/" ;; 14) run_test "用户错误场景E2E测试" "TestUserErrorScenarios" "./tests/e2e/" ;; 15) run_test "用户注册性能测试" "BenchmarkUserRegistration" "./tests/performance/" ;; 16) run_test "用户登录性能测试" "BenchmarkUserLogin" "./tests/performance/" ;; 17) run_test "并发用户注册测试" "TestConcurrentUserRegistration" "./tests/performance/" ;; 18) run_test "并发用户登录测试" "TestConcurrentUserLogin" "./tests/performance/" ;; 19) run_test "负载测试" "TestLoadTest" "./tests/performance/" ;; 20) echo "" echo "=== 运行所有单元测试 ===" cd "$PROJECT_ROOT" go test -v ./tests/unit/... ;; 21) echo "" echo "=== 运行所有集成测试 ===" cd "$PROJECT_ROOT" go test -v ./tests/integration/... ;; 22) echo "" echo "=== 运行所有E2E测试 ===" cd "$PROJECT_ROOT" go test -v ./tests/e2e/... ;; 23) echo "" echo "=== 运行所有性能测试 ===" cd "$PROJECT_ROOT" go test -v ./tests/performance/... ;; 24) run_all_tests ;; 0) echo "退出测试" exit 0 ;; *) echo "无效选项,请重新选择" ;; esac echo "" read -p "按回车键继续..." done ================================================ FILE: tests/scripts/setup_test_env.sh ================================================ #!/bin/bash # DiTing-Go 测试环境设置脚本 echo "=== DiTing-Go 测试环境设置 ===" echo "" # 检查Go环境 if ! command -v go &> /dev/null; then echo "错误: 未找到Go环境,请先安装Go" exit 1 fi # 检查Docker环境(可选) if command -v docker &> /dev/null; then echo "✅ 检测到Docker环境" else echo "⚠️ 未检测到Docker环境,将使用本地服务" fi # 获取项目根目录 PROJECT_ROOT=$(pwd) echo "项目根目录: $PROJECT_ROOT" # 设置测试环境变量 echo "设置测试环境变量..." export GIN_MODE=test export TEST_ENV=test export PROJECT_ROOT="$PROJECT_ROOT" # 检查配置文件是否存在 if [ -f "$PROJECT_ROOT/conf/config.yml" ]; then echo "✅ 配置文件存在: $PROJECT_ROOT/conf/config.yml" else echo "❌ 配置文件不存在: $PROJECT_ROOT/conf/config.yml" exit 1 fi # 检查数据库连接 echo "检查数据库连接..." # 这里可以添加数据库连接检查逻辑 # 检查Redis连接 echo "检查Redis连接..." # 这里可以添加Redis连接检查逻辑 # 准备测试数据 echo "准备测试数据..." # 这里可以添加测试数据准备逻辑 # 清理旧的测试数据 echo "清理旧的测试数据..." # 这里可以添加清理逻辑 echo "" echo "✅ 测试环境设置完成" echo "" echo "环境变量设置:" echo " GIN_MODE=$GIN_MODE" echo " TEST_ENV=$TEST_ENV" echo " PROJECT_ROOT=$PROJECT_ROOT" echo "" echo "可以运行以下命令开始测试:" echo " ./tests/scripts/run_tests.sh" echo " go test -v ./tests/..." echo "" ================================================ FILE: tests/unit/user_service_test.go ================================================ package unit import ( "DiTing-Go/domain/enum" "DiTing-Go/domain/vo/req" "DiTing-Go/global" "DiTing-Go/service" "DiTing-Go/utils" "DiTing-Go/utils/setting" "testing" "time" "DiTing-Go/logic" "context" "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" ) // 初始化函数,在包加载时执行 func init() { // 设置测试环境变量 gin.SetMode(gin.TestMode) // 初始化配置 setting.ConfigInit() // 初始化简单的测试日志 global.Logger = logrus.New() global.Logger.SetOutput(gin.DefaultWriter) global.Logger.SetLevel(logrus.InfoLevel) // 初始化Redis global.RedisInit() // 初始化数据库 global.DBInit() } // TestRegisterValidation 测试注册参数验证 func TestRegisterValidation(t *testing.T) { testCases := []struct { name string req req.UserRegisterReq expected string shouldSuccess bool }{ { name: "用户名为空", req: req.UserRegisterReq{ Username: "", Password: "123456", Phone: "13800138001", Captcha: "123456", }, expected: "用户名、密码和手机号不能为空", shouldSuccess: false, }, { name: "密码长度不足", req: req.UserRegisterReq{ Username: "testuser", Password: "123", Phone: "13800138001", Captcha: "123456", }, expected: "密码长度不能少于6位", shouldSuccess: false, }, { name: "正确注册用例", req: req.UserRegisterReq{ Username: "validuser", Password: "123456", Phone: "13800138002", Captcha: "123456", }, expected: "", shouldSuccess: true, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { // 为正确用例设置验证码 if tc.shouldSuccess { setupCaptcha(tc.req.Phone, tc.req.Captcha) } resp, err := service.RegisterService(tc.req) if tc.shouldSuccess { // 正确用例应该成功 assert.NoError(t, err) assert.True(t, resp.Success, "注册应该成功") assert.Contains(t, resp.Message, "注册成功") } else { // 错误用例应该失败 assert.Contains(t, resp.Message, tc.expected) } }) } } // TestLoginValidation 测试登录参数验证 func TestLoginValidation(t *testing.T) { testCases := []struct { name string req req.UserLoginReq expected string shouldSuccess bool }{ { name: "密码登录-手机号为空", req: req.UserLoginReq{ Phone: "", Password: "123456", LoginType: enum.LoginByPassword, }, expected: "用户名和密码不能为空", shouldSuccess: false, }, { name: "验证码登录-手机号为空", req: req.UserLoginReq{ Phone: "", Captcha: "123456", LoginType: enum.LoginByPhoneCaptcha, }, expected: "手机号和验证码不能为空", shouldSuccess: false, }, { name: "正确密码登录用例", req: req.UserLoginReq{ Phone: "13800138002", Password: "123456", LoginType: enum.LoginByPassword, }, expected: "", shouldSuccess: true, }, { name: "正确验证码登录用例", req: req.UserLoginReq{ Phone: "13800138002", Captcha: "123456", LoginType: enum.LoginByPhoneCaptcha, }, expected: "", shouldSuccess: true, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { // 为正确用例设置验证码 if tc.shouldSuccess && tc.req.LoginType == enum.LoginByPhoneCaptcha { setupCaptcha(tc.req.Phone, tc.req.Captcha) } resp, err := service.LoginService(tc.req) if tc.shouldSuccess { // 正确用例应该成功 assert.NoError(t, err) assert.True(t, resp.Success, "登录应该成功") // 验证返回的数据结构 loginData, ok := resp.Data.(map[string]interface{}) assert.True(t, ok, "返回数据应该是map类型") assert.NotEmpty(t, loginData["token"], "应该返回token") assert.NotZero(t, loginData["uid"], "应该返回用户ID") } else { // 错误用例应该失败 assert.Contains(t, resp.Message, tc.expected) } }) } } // TestCheckPassword 测试密码校验功能 func TestCheckPassword(t *testing.T) { ctx := context.Background() // 测试边界情况 t.Run("空手机号", func(t *testing.T) { result := logic.CheckPassword(ctx, "", "123456") assert.False(t, result, "空手机号应该校验失败") }) t.Run("空密码", func(t *testing.T) { result := logic.CheckPassword(ctx, "13800138003", "") assert.False(t, result, "空密码应该校验失败") }) t.Run("短密码", func(t *testing.T) { result := logic.CheckPassword(ctx, "13800138003", "123") assert.False(t, result, "短密码应该校验失败") }) t.Run("不存在的手机号", func(t *testing.T) { result := logic.CheckPassword(ctx, "99999999999", "123456") assert.False(t, result, "不存在的手机号应该校验失败") }) // 测试正常情况(需要先注册用户) t.Run("正常密码校验流程", func(t *testing.T) { // 先注册一个测试用户 testPhone := "13800138004" testUsername := "testuser" testPassword := "123456" testCaptcha := "123456" // 设置验证码 setupCaptcha(testPhone, testCaptcha) // 注册用户 registerReq := req.UserRegisterReq{ Username: testUsername, Password: testPassword, Phone: testPhone, Captcha: testCaptcha, } registerResp, err := service.RegisterService(registerReq) if err == nil && registerResp.Success { // 注册成功,测试密码校验 t.Run("正确密码", func(t *testing.T) { result := logic.CheckPassword(ctx, testPhone, testPassword) assert.True(t, result, "正确密码应该通过校验") }) t.Run("错误密码", func(t *testing.T) { result := logic.CheckPassword(ctx, testPhone, "wrongpassword") assert.False(t, result, "错误密码应该校验失败") }) // 清理测试数据 cleanupUser(testPhone) } else { t.Skip("用户注册失败,跳过密码校验测试") } }) } // setupCaptcha 设置验证码 func setupCaptcha(phone, captcha string) { captchaKey := utils.MakeUserCaptchaKey(phone) err := utils.SetValueToRedis(captchaKey, captcha, 5*time.Minute) if err != nil { panic(err) } } // cleanupUser 清理用户数据 func cleanupUser(phone string) { // 这里应该实现清理用户数据的逻辑 // 由于涉及到数据库操作,这里只是占位符 // 实际实现时需要根据具体的数据库操作来清理 global.Logger.Infof("cleanupUser: cleaning up user data for phone: %s", phone) } ================================================ FILE: utils/jsonUtils/json.go ================================================ package jsonUtils //import ( // "DiTing-Go/global" // "context" // "github.com/apache/rocketmq-client-go/v2/primitive" // "github.com/goccy/go-json" // "github.com/pkg/errors" //) // //// UnmarshalMsg 解析消息队列msg //func UnmarshalMsg(item any, msg *primitive.MessageExt) error { // byteStr := msg.Message.Body // err := json.Unmarshal(byteStr, item) // if err != nil { // global.Logger.Errorf("jsonUtils unmarshal error: %s", err.Error()) // return errors.New("Business Error") // } // return nil //} // //// Marshal 编码 //func Marshal(item any) ([]byte, error) { // byteStr, err := json.Marshal(item) // if err != nil { // global.Logger.Errorf("json序列化失败 %v", err) // return nil, err // } // return byteStr, nil //} // //// SendMsgSync 发送消息 //func SendMsgSync(topic string, item any) error { // ctx := context.Background() // byteStr, err := Marshal(item) // if err != nil { // return err // } // msg := &primitive.Message{ // Topic: topic, // Body: byteStr, // } // _, err = global.RocketProducer.SendSync(ctx, msg) // return err //} ================================================ FILE: utils/jwt/jwt.go ================================================ package jwt import ( "DiTing-Go/global" "time" "github.com/golang-jwt/jwt/v5" "github.com/pkg/errors" "github.com/spf13/viper" ) var jwtSecret = []byte(viper.GetString("jwt.secret")) type JwtClaims struct { Uid int64 `json:"uid"` jwt.RegisteredClaims } // GenerateToken 生成token func GenerateToken(uid int64) (string, error) { registeredClaims := jwt.RegisteredClaims{ // A usual scenario is to set the expiration time relative to the current time ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)), IssuedAt: jwt.NewNumericDate(time.Now()), NotBefore: jwt.NewNumericDate(time.Now()), } claims := JwtClaims{ uid, registeredClaims, } tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) token, err := tokenClaims.SignedString(jwtSecret) if err != nil { global.Logger.Errorf("generate token failed: %v", err) } return token, err } // ParseToken 解析token func ParseToken(tokenString string) (*JwtClaims, error) { // 解析token token, err := jwt.ParseWithClaims(tokenString, &JwtClaims{}, func(token *jwt.Token) (any, error) { return jwtSecret, nil }) if err != nil { return nil, err } if claims, ok := token.Claims.(*JwtClaims); ok && token.Valid { return claims, nil } return nil, errors.New("token无法解析") } ================================================ FILE: utils/jwt/jwt_test.go ================================================ package jwt import ( "fmt" "testing" "time" ) func TestGenerateToken(t *testing.T) { tests := []struct { name string uid int64 wantErr bool }{ { name: "正常生成token", uid: 12345, wantErr: false, }, { name: "用户ID为0", uid: 0, wantErr: false, }, { name: "用户ID为负数", uid: -1, wantErr: false, }, { name: "大用户ID", uid: 999999999, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { token, err := GenerateToken(tt.uid) if (err != nil) != tt.wantErr { t.Errorf("GenerateToken() error = %v, wantErr %v", err, tt.wantErr) return } if !tt.wantErr && token == "" { t.Error("GenerateToken() returned empty token") } }) } } func TestParseToken(t *testing.T) { // 首先生成一个有效的token用于测试 validUID := int64(12345) validToken, err := GenerateToken(validUID) if err != nil { t.Fatalf("Failed to generate valid token for testing: %v", err) } tests := []struct { name string token string wantUID int64 wantErr bool }{ { name: "解析有效token", token: validToken, wantUID: validUID, wantErr: false, }, { name: "解析空token", token: "", wantUID: 0, wantErr: true, }, { name: "解析无效token", token: "invalid.token.here", wantUID: 0, wantErr: true, }, { name: "解析格式错误的token", token: "not.a.valid.jwt.token", wantUID: 0, wantErr: true, }, { name: "解析过期token", token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjEyMzQ1LCJleHAiOjE2MzQ1Njc4OTB9.invalid_signature", wantUID: 0, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { claims, err := ParseToken(tt.token) if (err != nil) != tt.wantErr { t.Errorf("ParseToken() error = %v, wantErr %v", err, tt.wantErr) return } if !tt.wantErr { if claims == nil { t.Error("ParseToken() returned nil claims for valid token") return } if claims.Uid != tt.wantUID { t.Errorf("ParseToken() UID = %v, want %v", claims.Uid, tt.wantUID) } } }) } } func TestGenerateAndParseToken(t *testing.T) { // 测试生成token后立即解析是否能得到相同的UID testUIDs := []int64{1, 100, 1000, 999999} for _, uid := range testUIDs { t.Run(fmt.Sprintf("UID_%d", uid), func(t *testing.T) { // 生成token token, err := GenerateToken(uid) if err != nil { t.Errorf("GenerateToken() failed: %v", err) return } // 解析token claims, err := ParseToken(token) if err != nil { t.Errorf("ParseToken() failed: %v", err) return } // 验证UID是否一致 if claims.Uid != uid { t.Errorf("UID mismatch: got %v, want %v", claims.Uid, uid) } // 验证token的过期时间 if claims.ExpiresAt == nil { t.Error("Token expiration time is nil") } else if claims.ExpiresAt.Time.Before(time.Now()) { t.Error("Token has already expired") } }) } } func TestTokenExpiration(t *testing.T) { uid := int64(12345) // 生成token token, err := GenerateToken(uid) if err != nil { t.Fatalf("Failed to generate token: %v", err) } // 解析token claims, err := ParseToken(token) if err != nil { t.Fatalf("Failed to parse token: %v", err) } // 验证token的过期时间是否在合理范围内(24小时) expectedExpiry := time.Now().Add(24 * time.Hour) actualExpiry := claims.ExpiresAt.Time // 允许1分钟的误差 tolerance := time.Minute if actualExpiry.After(expectedExpiry.Add(tolerance)) || actualExpiry.Before(expectedExpiry.Add(-tolerance)) { t.Errorf("Token expiration time is not within expected range. Expected around %v, got %v", expectedExpiry, actualExpiry) } } ================================================ FILE: utils/middleware/cors.go ================================================ package middleware import ( "github.com/gin-gonic/gin" "net/http" ) // Cors 跨域中间件 func Cors() gin.HandlerFunc { return func(c *gin.Context) { method := c.Request.Method origin := c.Request.Header.Get("Origin") if origin != "" { //接收客户端发送的origin (重要!) c.Writer.Header().Set("Access-Control-Allow-Origin", origin) //服务器支持的所有跨域请求的方法 c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE,UPDATE") //允许跨域设置可以返回其他子段,可以自定义字段 c.Header("Access-Control-Allow-Headers", "Authorization, content-type, Content-Length, X-CSRF-Token, Token,session,Access-Control-Allow-Headers,account") // 允许浏览器(客户端)可以解析的头部 (重要) c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers") //设置缓存时间 c.Header("Access-Control-Max-Age", "172800") //允许客户端传递校验信息比如 cookie (重要) c.Header("Access-Control-Allow-Credentials", "true") c.Set("Content-Type", "application/json") } //允许类型校验 if method == "OPTIONS" { c.JSON(http.StatusOK, "ok!") } c.Next() } } ================================================ FILE: utils/middleware/jwt.go ================================================ package middleware import ( "DiTing-Go/pkg/domain/vo/resp" "DiTing-Go/utils/jwt" "github.com/gin-gonic/gin" "strings" ) // JWT jwt中间件 func JWT() gin.HandlerFunc { return func(c *gin.Context) { // 客户端携带Token有三种方式 1.放在请求头 2.放在请求体 3.放在URI // 这里假设Token放在Header的Authorization中,并使用Bearer开头 // 这里的具体实现方式要依据你的实际业务情况决定 authHeader := c.Request.Header.Get("Authorization") if authHeader == "" { resp.ErrorResponse(c, "无权限访问") c.Abort() return } // 按空格分割 parts := strings.SplitN(authHeader, " ", 2) if !(len(parts) == 2 && parts[0] == "Bearer") { resp.ErrorResponse(c, "无权限访问") c.Abort() return } // parts[1]是获取到的tokenString,我们使用之前定义好的解析JWT的函数来解析它 token, err := jwt.ParseToken(parts[1]) if err != nil { resp.ErrorResponse(c, "无权限访问") c.Abort() return } //把解析出来的token存储到请求的上下文c上,方便后续的处理函数获取 c.Set("uid", token.Uid) c.Next() } } ================================================ FILE: utils/middleware/log.go ================================================ package middleware import ( "DiTing-Go/global" "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" "time" ) // LoggerToFile 日志记录到文件 func LoggerToFile() gin.HandlerFunc { return func(c *gin.Context) { // 开始时间 startTime := time.Now() // 处理请求 c.Next() // 结束时间 endTime := time.Now() // 执行时间 latencyTime := endTime.Sub(startTime) // 请求方式 reqMethod := c.Request.Method // 请求路由 reqUri := c.Request.RequestURI // 状态码 statusCode := c.Writer.Status() // 请求IP clientIP := c.ClientIP() // 日志格式 global.Logger.WithFields(logrus.Fields{ "statusCode": statusCode, "latencyTime": latencyTime, "clientIP": clientIP, "reqMethod": reqMethod, "reqUri": reqUri, }).Infof("GIN") } } ================================================ FILE: utils/mysqlUtils.go ================================================ package utils import ( "DiTing-Go/dal/model" "DiTing-Go/global" "context" "errors" "fmt" "gorm.io/gorm" "strconv" ) // QueryUserByPhone 根据手机号查询用户 func QueryUserByPhone(ctx context.Context, phone string) (*model.User, error) { user := global.Query.User userQ := user.WithContext(ctx) rst, err := userQ.Where(user.Phone.Eq(phone)).First() if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { global.Logger.Errorf("user not found with phone: %s", phone) return nil, gorm.ErrRecordNotFound } return nil, fmt.Errorf("query user by phone error: %w", err) } return rst, nil } // QueryUserByID 根据ID查询用户 func QueryUserByID(ctx context.Context, userId string) (*model.User, error) { userIdNum, err := strconv.ParseInt(userId, 10, 64) user := global.Query.User userQ := user.WithContext(ctx) rst, err := userQ.Where(user.ID.Eq(userIdNum)).First() if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { global.Logger.Errorf("user not found with userId: %s", userId) return nil, gorm.ErrRecordNotFound } return nil, fmt.Errorf("query user by userId error: %v", err) } return rst, nil } ================================================ FILE: utils/passwordUtils.go ================================================ package utils import ( "crypto/md5" "encoding/hex" ) // EncryptPassword 对密码进行md5加密 func EncryptPassword(password string) string { hash := md5.New() hash.Write([]byte(password)) password = hex.EncodeToString(hash.Sum(nil)) return password } ================================================ FILE: utils/redisCache/remove_cache.go ================================================ package redisCache import ( "DiTing-Go/dal/model" "DiTing-Go/domain/enum" "DiTing-Go/pkg/utils" "fmt" ) // RemoveRoomCache 移除房间缓存 func RemoveRoomCache(room model.Room) { utils.RemoveData(fmt.Sprintf(enum.RoomCacheByID, room.ID)) } // RemoveRoomFriend 移除房间好友缓存 func RemoveRoomFriend(roomFriend model.RoomFriend) { utils.RemoveData(fmt.Sprintf(enum.RoomFriendCacheByRoomID, roomFriend.RoomID)) utils.RemoveData(fmt.Sprintf(enum.RoomFriendCacheByUidAndFriendUid, roomFriend.Uid1, roomFriend.Uid2)) } // RemoveUserCache 移除用户缓存 func RemoveUserCache(user model.User) { utils.RemoveData(fmt.Sprintf(enum.UserCacheByID, user.ID)) utils.RemoveData(fmt.Sprintf(enum.UserCacheByName, user.Name)) } // RemoveUserFriend 移除用户好友缓存 func RemoveUserFriend(uid, friendUid int64) { utils.RemoveData(fmt.Sprintf(enum.UserFriendCacheByUidAndFriendUid, uid, friendUid)) utils.RemoveData(fmt.Sprintf(enum.UserFriendCacheByUidAndFriendUid, friendUid, uid)) } // RemoveUserApply 移除用户好友申请缓存 func RemoveUserApply(uid, friendUid int64) { utils.RemoveData(fmt.Sprintf(enum.UserApplyCacheByUidAndFriendUid, uid, friendUid)) utils.RemoveData(fmt.Sprintf(enum.UserApplyCacheByUidAndFriendUid, friendUid, uid)) } // RemoveContact 移除会话缓存 func RemoveContact(contact model.Contact) { utils.RemoveData(fmt.Sprintf(enum.ContactCacheById, contact.ID)) } ================================================ FILE: utils/redisUtils.go ================================================ package utils import ( domainEnum "DiTing-Go/domain/enum" "DiTing-Go/global" "fmt" "time" "github.com/goccy/go-json" "github.com/pkg/errors" ) // MakeUserPhoneKey 构造用户手机号 func MakeUserPhoneKey(phone string) string { return fmt.Sprintf(domainEnum.PhoneUidMap, phone) } // MakeUserCaptchaKey 构造验证码key func MakeUserCaptchaKey(phone string) string { return fmt.Sprintf(domainEnum.UserCaptcha, phone) } // SetValueToRedis 设置字符串 func SetValueToRedis(key string, value string, expireTime time.Duration) error { valueByte, err := json.Marshal(value) if err != nil { global.Logger.Errorf("json marshal error: %v", err) return errors.New("json marshal error") } if err = global.Rdb.Set(key, valueByte, expireTime).Err(); err != nil { global.Logger.Errorf("key:%s, value:%s, redis set error: %v", key, valueByte, err) return errors.New("redis set error") } return nil } // GetValueFromRedis 获取字符串 func GetValueFromRedis(key string) (value []byte, err error) { valueByte, err := global.Rdb.Get(key).Bytes() if err != nil { return nil, err } return valueByte, nil } // DeleteValueFromRedis 删除字符串 func DeleteValueFromRedis(key string) error { if err := global.Rdb.Del(key).Err(); err != nil { global.Logger.Errorf("key:%s, redis delete error: %v", key, err) return errors.New("redis delete error") } return nil } //// GetData 获取数据 //func GetData(cacheKey string, value any, dbQueryFunc func() (interface{}, error)) error { // // 1. 从缓存中获取数据 // err := GetString(cacheKey, value) // // 查询到数据 // if err == nil { // return nil // } else if !errors.Is(err, redis.Nil) { // return err // } // err = QueryAndSet(cacheKey, value, dbQueryFunc) // if err != nil { // return err // } // return nil //} //// QueryAndSet 查询数据库并设置缓存 //func QueryAndSet(cacheKey string, value any, dbQueryFunc func() (interface{}, error)) error { // // 2. 从数据库中获取数据 // result, err := dbQueryFunc() // if err != nil { // return err // } // err = copier.Copy(value, result) // if err != nil { // global.Logger.Errorf("拷贝数据失败: %v", err) // return err // } // // 3. 将查询结果写回缓存 // if err = SetString(cacheKey, result); err != nil { // global.Logger.Errorf("写入redis失败: %v", err) // return err // } // return err //} // //func RemoveData(key string) { // global.Rdb.Del(key) //} ================================================ FILE: utils/setting/setting.go ================================================ package setting import ( "log" "os" "path/filepath" "github.com/spf13/viper" ) func ConfigInit() { // 设置配置文件的名字 viper.SetConfigName("config") // 设置配置文件的类型 viper.SetConfigType("yaml") // 获取当前工作目录 currentDir, err := os.Getwd() if err != nil { log.Fatalf("Failed to get current directory: %v", err) } // 尝试多个可能的配置文件路径 configPaths := []string{ "./conf", // 当前目录下的conf "../conf", // 上级目录下的conf "../../conf", // 上上级目录下的conf "../../../conf", // 上上上级目录下的conf "../../../../conf", // 上上上上级目录下的conf "conf", // 直接conf目录 } // 添加配置文件的路径 for _, path := range configPaths { viper.AddConfigPath(path) } // 如果设置了PROJECT_ROOT环境变量,也添加该路径 if projectRoot := os.Getenv("PROJECT_ROOT"); projectRoot != "" { viper.AddConfigPath(filepath.Join(projectRoot, "conf")) } // 尝试读取配置文件 err = viper.ReadInConfig() if err != nil { // 如果还是找不到配置文件,尝试从项目根目录查找 // 通过查找go.mod文件来确定项目根目录 projectRoot := findProjectRoot(currentDir) if projectRoot != "" { viper.Reset() viper.SetConfigName("config") viper.SetConfigType("yaml") viper.AddConfigPath(filepath.Join(projectRoot, "conf")) err = viper.ReadInConfig() } if err != nil { log.Fatalf("Fail to parse 'conf/config.yml': %v", err) } } } // findProjectRoot 通过查找go.mod文件来确定项目根目录 func findProjectRoot(currentDir string) string { dir := currentDir for { // 检查当前目录是否有go.mod文件 if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil { return dir } // 向上查找父目录 parent := filepath.Dir(dir) if parent == dir { // 已经到达根目录 break } dir = parent } return "" } ================================================ FILE: utils/time_utils.go ================================================ package utils import ( "DiTing-Go/global" "strconv" "time" ) func TimestampStrToTimeStr(str *string) (*string, error) { if str != nil && *str != "" { // 时间戳转时间 timestamp, err := strconv.ParseInt(*str, 10, 64) if err != nil { global.Logger.Errorf("时间戳转换失败 %s", err) return nil, err } cursor := time.Unix(0, timestamp) cursorStr := cursor.Format(time.RFC3339Nano) return &cursorStr, nil } return nil, nil } ================================================ FILE: websocket/domain/enum/ws_type.go ================================================ package enum const ( NewMessage = 4 ) ================================================ FILE: websocket/domain/vo/resp/new_message_resp.go ================================================ package resp type NewMessageResp struct { Type int `json:"type"` // 消息类型 } ================================================ FILE: websocket/global/global.go ================================================ package global import ( "github.com/gorilla/websocket" cmap "github.com/orcaman/concurrent-map/v2" "sync" ) type Channels struct { Uid int64 ChannelList []*websocket.Conn Mu *sync.RWMutex } type User struct { Uid int64 Channel *websocket.Conn } type Msg struct { Uid int64 } // UserChannelMap 用户和channel的映射 var UserChannelMap = cmap.New[*Channels]() ================================================ FILE: websocket/service/websocket_service.go ================================================ package service import ( global2 "DiTing-Go/global" "DiTing-Go/utils/jwt" "DiTing-Go/websocket/global" "fmt" "github.com/gorilla/websocket" "github.com/pkg/errors" "log" "net/http" "strconv" "strings" "sync" "time" ) // TODO:连接断开处理 // 定义一个升级器,将普通的http连接升级为websocket连接 var upgrader = &websocket.Upgrader{ //定义读写缓冲区大小 WriteBufferSize: 1024, ReadBufferSize: 1024, //校验请求 CheckOrigin: func(r *http.Request) bool { //如果不是get请求,返回错误 if r.Method != "GET" { fmt.Println("请求方式错误") return false } //还可以根据其他需求定制校验规则 return true }, } // Connect 建立WebSocket连接 func Connect(w http.ResponseWriter, r *http.Request) { //先获得Http的token中的uid //url中的获取token参数 params := r.URL.Query() token := params.Get("token") tokenInfo, err := jwt.ParseToken(token) if err != nil { global2.Logger.Errorf("无权限访问: %v", err) return } uid := &tokenInfo.Uid // Upgrade our raw HTTP connection to a websocket based one conn, err := upgrader.Upgrade(w, r, nil) // 关闭连接 defer conn.Close() if err != nil { log.Print("Error during connection upgradation:", err) return } //连接成功后注册用户 // 将uid转换为string stringUid := strconv.FormatInt(*uid, 10) userChannel := global.Channels{ Uid: *uid, ChannelList: make([]*websocket.Conn, 0), Mu: new(sync.RWMutex), } user := global.User{ Uid: *uid, Channel: conn, } global.UserChannelMap.Set(stringUid, &userChannel) userChannelPtr, _ := global.UserChannelMap.Get(stringUid) // TODO:加锁方式是否正确 // 将连接加入到用户的channel中 userChannelPtr.Mu.Lock() userChannelPtr.ChannelList = append(userChannelPtr.ChannelList, conn) userChannelPtr.Mu.Unlock() // 定时发送心跳消息 go heatBeat(&user) // 监听连接关闭事件 for { _, _, err := conn.ReadMessage() if err != nil { disConnect(&user) break } } } // Send 发送空消息代表有新消息,前端收到消息后再去后端拉取消息 func Send(uid int64, value []byte) error { stringUid := strconv.FormatInt(uid, 10) channels, _ := global.UserChannelMap.Get(stringUid) // 用户不在线,直接返回 if channels == nil { return nil } for _, conn := range channels.ChannelList { // 发送空消息,代表有新消息 err := conn.WriteMessage(websocket.TextMessage, value) if err != nil { global2.Logger.Errorf("发送消息失败: %v", err) return errors.New("Business Error") } } return nil } // 移除连接 func disConnect(user *global.User) { stringUid := strconv.FormatInt(user.Uid, 10) conn := user.Channel userChannel, _ := global.UserChannelMap.Get(stringUid) userChannel.Mu.Lock() for i, item := range userChannel.ChannelList { if item == conn { userChannel.ChannelList = append(userChannel.ChannelList[:i], userChannel.ChannelList[i+1:]...) } } err := conn.Close() if err != nil { return } userChannel.Mu.Unlock() } // 解析jwt func parseJwt(r *http.Request) (*int64, error) { authHeader := r.Header.Get("Authorization") if authHeader == "" { return nil, errors.New("无权限访问") } // 按空格分割 parts := strings.SplitN(authHeader, " ", 2) if !(len(parts) == 2 && parts[0] == "Bearer") { return nil, errors.New("无权限访问") } // parts[1]是获取到的tokenString,我们使用之前定义好的解析JWT的函数来解析它 token, err := jwt.ParseToken(parts[1]) if err != nil { return nil, errors.New("无权限访问") } return &token.Uid, nil } // 心跳检测 func heatBeat(user *global.User) { conn := user.Channel // TODO:心跳时间从配置文件中读取 ticker := time.NewTicker(5 * time.Second) defer ticker.Stop() for { select { case <-ticker.C: err := conn.WriteMessage(websocket.PingMessage, []byte("heartbeat")) if err != nil { log.Println(err) return } // TODO:开发时关闭 //conn.SetReadDeadline(time.Now().Add(10 * time.Second)) conn.SetReadDeadline(time.Now().Add(24 * 360 * time.Hour)) _, _, err = conn.ReadMessage() if err != nil { disConnect(user) log.Println("heartbeat response error:", err) return } } } }