[
  {
    "path": ".github/workflows/reademe-contributors.yml",
    "content": "on:\n  push:\n    branches:\n      - main\n\nname: Generate a list of contributors\n\njobs:\n  contrib-readme-en-job:\n    runs-on: ubuntu-latest\n    name: A job to automate contrib in readme\n    steps:\n      - name: Contribute List\n        uses: akhilmhdh/contributors-readme-action@v2.3.4\n        env:\n          GITHUB_TOKEN: ${{ secrets.CONTRIBUTORS_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": "# If you prefer the allow list template instead of the deny list, see community template:\n# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore\n#\n# Binaries for programs and plugins\n/.idea/\n*.exe\n*.exe~\n*.dll\n*.so\n*.dylib\n\n# Test binary, built with `go test -c`\n*.test\n\n# Output of the go coverage tool, specifically when used with LiteIDE\n*.out\n\n# Dependency directories (remove the comment below to include it)\n# vendor/\n\n# Go workspace file\ngo.work\n*.yml\n*.yaml\n*.log\n/conf/config.yml\n!/conf/config_example.yml\n!/.github/workflows/reademe-contributors.yml\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "<div align=center>\n<img src=\"./assets/logo.png\"  width=\"40%\"/>\n</div>\n\n![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)\n![Static Badge](https://img.shields.io/badge/github-%E9%A1%B9%E7%9B%AE%E5%9C%B0%E5%9D%80-blue)\n![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)\n![Static Badge](https://img.shields.io/badge/Issues-0_open-blue)\n![Static Badge](https://img.shields.io/badge/License-Apache_License_2.0-blue)\n# DiTing：初学者的第一个 IM 项目\n欢迎来到 DiTing！这是一个简单、轻量级的即时通讯（IM）开源项目，采用 Go 编写，严格遵守互联网开发标准。致力于为初学者提供一个友好、易于上手的 IM 解决方案，让你可以轻松入门并开始构建自己的即时通讯应用。\n\n## 项目导航\n- 学习文档：[DiTing文档](https://danmuking.github.io/)\n- 项目交流群：🎉 欢迎加入 DiTing 交流群！这是一个与其他开发者交流、分享经验和获取项目相关支持的地方。在这里你可以不仅可以提出遇到的任何问题，同时欢迎你与其他开发者交流并且对DiTing提出任何建议！\n- 码云仓库：[Gitee](https://gitee.com/danmuking/DiTing-Go)\n- Github仓库：[Github](https://github.com/danmuking/DiTing-Go)\n- 前端地址：[Github](https://github.com/danmuking/DiTingWeb)\n\n### 界面展示\n![app_1.png](./assets/app_1.png)\n![app_1.png](./assets/app_2.png)\n![app_1.png](./assets/app_3.png)\n![app_1.png](./assets/app_4.png)\n\n### 技术选型\n| 技术          | 说明               | 官网 |\n|-------------|------------------| ----------- |\n| Gin         | web开发必备框架        |https://gin-gonic.com/|\n| GORM        | ORM框架            |https://gorm.io/docs/index.html|\n| GEN         | ORM自动生成工作        |https://gorm.io/docs/index.html|\n| Redis       | 缓存加速，多数据结构支持业务功能 |https://redis.io|\n| Jwt         | 用户登录，认证方案        |https://jwt.io|\n| Swagger-UI\t | API文档生成工具        |https://github.com/swagger-api/swagger-ui|\n| Redsync\t    | GO的分布式锁工具        |https://github.com/go-redsync/redsync|\n| RocketMQ\t   | 低延迟、高并发、高可用、高可靠的分布式消息中间件        |https://rocketmq.apache.org/|\n\n### Star 趋势\n<img alt=\"Star History Chart\" src=\"https://api.star-history.com/svg?repos=danmuking/DiTing-Go&type=Date\"/>\n\n### 贡献者\n\n<!-- readme: collaborators,contributors -start -->\n<table>\n<tr>\n    <td align=\"center\">\n        <a href=\"https://github.com/danmuking\">\n            <img src=\"https://avatars.githubusercontent.com/u/47711726?v=4\" width=\"100;\" alt=\"danmuking\"/>\n            <br />\n            <sub><b>LinYi</b></sub>\n        </a>\n    </td>\n    <td align=\"center\">\n        <a href=\"https://github.com/Soce1lo\">\n            <img src=\"https://avatars.githubusercontent.com/u/53263452?v=4\" width=\"100;\" alt=\"Soce1lo\"/>\n            <br />\n            <sub><b>Soce1lo</b></sub>\n        </a>\n    </td>\n    <td align=\"center\">\n        <a href=\"https://github.com/wangQuanYaa\">\n            <img src=\"https://avatars.githubusercontent.com/u/161594079?v=4\" width=\"100;\" alt=\"wangQuanYaa\"/>\n            <br />\n            <sub><b>WangQuanYaa</b></sub>\n        </a>\n    </td></tr>\n</table>\n<!-- readme: collaborators,contributors -end -->\n\n### 共建邀请\nDiTing 项目不仅是一个简单的即时通讯解决方案，更是一个汇聚了热爱技术、追求卓越的开发者们的大家庭。我们拥有完整的社群体系，以及积极、友好的交流氛围，让每一位参与者都能在这里找到归属感，收获成长与快乐。\n\n如果你对 DiTing 项目感兴趣，愿意贡献你的智慧和力量，我们非常欢迎你的加入！无论你是前端、后端、测试还是其他领域的开发者，都能在 DiTing 项目中找到你的舞台。你可以参与代码编写、功能优化、文档完善等各个方面的工作，与我们一起共同推动项目的进步。\n\n同时，我们也非常欢迎非技术领域的朋友们加入我们的社群，分享你的想法和建议，帮助我们更好地完善项目和服务。\n\n为了更好地联系和交流，你可以尝试添加我的微信，共同为 DiTing 项目的发展贡献力量。\n\n### 作者\nDanMu\n如果你需要帮助，可以尝试添加我的微信，我会尽力帮助你。\n\n<img alt=\"微信图片_20240328224833.jpg\" src=\"./assets/微信图片_20240328230919.jpg\" height=\"300\"/>\n\n### 捐赠\n如果你觉得这个项目对你有帮助，你可以请作者喝一杯咖啡。\n\n<img alt=\"微信图片_20240328224833.jpg\" src=\"./assets/微信图片_20240328224833.jpg\" height=\"300\"/>\n<img alt=\"微信图片_20240328224851.jpg\" src=\"./assets/微信图片_20240328224851.jpg\" height=\"300\"/>\n\n### 版权说明\n\n该项目签署了MIT 授权许可，详情请参阅 [LICENSE.txt](./LICENSE)\n\n### 鸣谢\n\n\n[//]: # (- [GitHub Emoji Cheat Sheet]&#40;https://www.webpagefx.com/tools/emoji-cheat-sheet&#41;)\n\n[//]: # (- [Img Shields]&#40;https://shields.io&#41;)\n\n[//]: # (- [Choose an Open Source License]&#40;https://choosealicense.com&#41;)\n\n[//]: # (- [GitHub Pages]&#40;https://pages.github.com&#41;)\n\n[//]: # (- [Animate.css]&#40;https://daneden.github.io/animate.css&#41;)\n\n[//]: # (- [xxxxxxxxxxxxxx]&#40;https://connoratherton.com/loaders&#41;)\n"
  },
  {
    "path": "cmd/gen/generate.go",
    "content": "package main\n\n// gorm gen configure\n\nimport (\n\t_ \"DiTing-Go/pkg/setting\"\n\t\"fmt\"\n\t\"github.com/spf13/viper\"\n\t\"gorm.io/driver/mysql\"\n\t\"gorm.io/gen\"\n\t\"gorm.io/gorm\"\n)\n\nvar 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\"))\n\nfunc connectDB(dsn string) *gorm.DB {\n\tdb, err := gorm.Open(mysql.Open(dsn))\n\tif err != nil {\n\t\tpanic(fmt.Errorf(\"connect db fail: %w\", err))\n\t}\n\treturn db\n}\n\nfunc main() {\n\tprintln(MySQLDSN)\n\t// 指定生成代码的具体相对目录(相对当前文件)，默认为：./query\n\t// 默认生成需要使用WithContext之后才可以查询的代码，但可以通过设置gen.WithoutContext禁用该模式\n\tg := gen.NewGenerator(gen.Config{\n\t\t// 默认会在 OutPath 目录生成CRUD代码，并且同目录下生成 model 包\n\t\t// 所以OutPath最终package不能设置为model，在有数据库表同步的情况下会产生冲突\n\t\t// 若一定要使用可以通过ModelPkgPath单独指定model package的名称\n\t\tOutPath: \"./dal/query\",\n\t\t/* ModelPkgPath: \"dal/model\"*/\n\n\t\t// gen.WithoutContext：禁用WithContext模式\n\t\t// gen.WithDefaultQuery：生成一个全局Query对象Q\n\t\t// gen.WithQueryInterface：生成Query接口\n\t\tMode: gen.WithDefaultQuery | gen.WithQueryInterface,\n\t})\n\n\t// 通常复用项目中已有的SQL连接配置db(*gorm.DB)\n\t// 非必需，但如果需要复用连接时的gorm.Config或需要连接数据库同步表信息则必须设置\n\tg.UseDB(connectDB(MySQLDSN))\n\n\t// 从连接的数据库为所有表生成Model结构体和CRUD代码\n\t// 也可以手动指定需要生成代码的数据表\n\tg.ApplyBasic(g.GenerateAllTable()...)\n\n\t// 执行并生成代码\n\tg.Execute()\n}\n"
  },
  {
    "path": "conf/config_example.yml",
    "content": "mysql:\n  host: xxx.xxx.xxx.xx\n  port: 3306\n  username: diting\n  password: diting\njwt:\n  secret: diting\nlog:\n  log_file_path: ./logs\n  log_file_name: diting.log\nminio:\n  accessKey: xxx\n  accessSecret: xxx\n  endPoint: xxx.xxx.xxx.xxx:9000\n  useSSL: false\nredis:\n  host: xxxx\n  password: xxxxx\nrocketmq:\n  host: xxx.xxx.xxx.xxx:9000\n  group: diting"
  },
  {
    "path": "controller/captcha_controller.go",
    "content": "package controller\n\nimport (\n\t\"DiTing-Go/domain/vo/req\"\n\t\"DiTing-Go/pkg/domain/vo/resp\"\n\t\"DiTing-Go/service\"\n\t\"github.com/gin-gonic/gin\"\n)\n\nfunc CaptchaController(c *gin.Context) {\n\tcaptchaReq := req.CaptchaReq{}\n\tif err := c.ShouldBind(&captchaReq); err != nil {\n\t\tresp.ErrorResponse(c, \"参数错误\")\n\t\tc.Abort()\n\t\treturn\n\t}\n\tresponse, err := service.CaptchaService(captchaReq)\n\tif err != nil {\n\t\tc.Abort()\n\t\tresp.ReturnErrorResponse(c, response)\n\t\treturn\n\t}\n\tresp.ReturnSuccessResponse(c, response)\n}\n"
  },
  {
    "path": "controller/chat_controller.go",
    "content": "package controller\n\n//\n//import (\n//\t\"DiTing-Go/domain/vo/req\"\n//\t\"DiTing-Go/pkg/domain/vo/resp\"\n//\t\"DiTing-Go/service\"\n//\t\"github.com/gin-gonic/gin\"\n//)\n//\n//func SendMessageController(c *gin.Context) {\n//\tuid := c.GetInt64(\"uid\")\n//\tmessageReq := req.MessageReq{}\n//\tif err := c.ShouldBind(&messageReq); err != nil { //ShouldBind()会自动推导\n//\t\tresp.ErrorResponse(c, \"参数错误\")\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\tresponse, err := service.SendTextMsgService(uid, messageReq)\n//\tif err != nil {\n//\t\tc.Abort()\n//\t\tresp.ReturnErrorResponse(c, response)\n//\t\treturn\n//\t}\n//\tresp.ReturnSuccessResponse(c, response)\n//}\n"
  },
  {
    "path": "controller/contact_controller.go",
    "content": "package controller\n\n//\n//import (\n//\t\"DiTing-Go/domain/vo/req\"\n//\t\"DiTing-Go/global\"\n//\tpkgReq \"DiTing-Go/pkg/domain/vo/req\"\n//\t\"DiTing-Go/pkg/domain/vo/resp\"\n//\t\"DiTing-Go/service\"\n//\t\"github.com/gin-gonic/gin\"\n//)\n//\n//func GetUserInfoBatchController(c *gin.Context) {\n//\tgetUserInfoBatchReq := req.GetUserInfoBatchReq{}\n//\tif err := c.ShouldBind(&getUserInfoBatchReq); err != nil {\n//\t\tresp.ErrorResponse(c, \"参数错误\")\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\tresponse, err := service.GetUserInfoBatchService(getUserInfoBatchReq)\n//\tif err != nil {\n//\t\tc.Abort()\n//\t\tresp.ReturnErrorResponse(c, response)\n//\t\treturn\n//\t}\n//\tresp.ReturnSuccessResponse(c, response)\n//\treturn\n//}\n//\n//func GetContactListController(c *gin.Context) {\n//\tuid := c.GetInt64(\"uid\")\n//\t// 游标翻页\n//\t// 默认值\n//\tvar cursor *string = nil\n//\tvar pageSize int = 20\n//\tpageRequest := pkgReq.PageReq{\n//\t\tCursor:   cursor,\n//\t\tPageSize: pageSize,\n//\t}\n//\tif err := c.ShouldBindQuery(&pageRequest); err != nil { //ShouldBind()会自动推导\n//\t\tresp.ErrorResponse(c, \"参数错误\")\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\tresponse, err := service.GetContactListService(uid, pageRequest)\n//\tif err != nil {\n//\t\tc.Abort()\n//\t\tresp.ReturnErrorResponse(c, response)\n//\t\treturn\n//\t}\n//\tresp.ReturnSuccessResponse(c, response)\n//}\n//\n//func GetNewContactListController(c *gin.Context) {\n//\tuid := c.GetInt64(\"uid\")\n//\n//\tgetNewContentListReq := req.GetNewContentListReq{}\n//\tif err := c.ShouldBindQuery(&getNewContentListReq); err != nil { //ShouldBind()会自动推导\n//\t\tresp.ErrorResponse(c, \"参数错误\")\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\tresponse, err := service.GetNewContactListService(uid, getNewContentListReq.Timestamp)\n//\tif err != nil {\n//\t\tc.Abort()\n//\t\tresp.ReturnErrorResponse(c, response)\n//\t\treturn\n//\t}\n//\tresp.ReturnSuccessResponse(c, response)\n//}\n//\n//func GetNewMsgListController(c *gin.Context) {\n//\n//\tgetNewMsgListReq := req.GetNewMsgListReq{}\n//\tif err := c.ShouldBindQuery(&getNewMsgListReq); err != nil { //ShouldBind()会自动推导\n//\t\tresp.ErrorResponse(c, \"参数错误\")\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\tresponse, err := service.GetNewMsgService(getNewMsgListReq.MsgId, getNewMsgListReq.RoomId)\n//\tif err != nil {\n//\t\tc.Abort()\n//\t\tresp.ReturnErrorResponse(c, response)\n//\t\treturn\n//\t}\n//\tresp.ReturnSuccessResponse(c, response)\n//}\n//\n//func CreateGroupController(c *gin.Context) {\n//\tuid := c.GetInt64(\"uid\")\n//\tcreatGroupReq := req.CreateGroupReq{}\n//\tif err := c.ShouldBind(&creatGroupReq); err != nil { //ShouldBind()会自动推导\n//\t\tresp.ErrorResponse(c, \"参数错误\")\n//\t\tglobal.Logger.Errorf(\"参数错误: %v\", err)\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\tresponse, err := service.CreateGroupService(uid, creatGroupReq.UidList)\n//\tif err != nil {\n//\t\tc.Abort()\n//\t\tresp.ReturnErrorResponse(c, response)\n//\t\treturn\n//\t}\n//\tresp.ReturnSuccessResponse(c, response)\n//}\n"
  },
  {
    "path": "controller/friend_controller.go",
    "content": "package controller\n\n//\n//import (\n//\t\"DiTing-Go/domain/vo/req\"\n//\tpkgReq \"DiTing-Go/pkg/domain/vo/req\"\n//\t\"DiTing-Go/pkg/domain/vo/resp\"\n//\t\"DiTing-Go/service\"\n//\t\"github.com/gin-gonic/gin\"\n//)\n//\n//// ApplyFriendController 添加好友\n////\n////\t@Summary\t添加好友\n////\t@Produce\tjson\n////\t@Param\t\tuid\tbody\t\tint\t\t\t\t\ttrue\t\"好友uid\"\n////\t@Param\t\tmsg\tbody\t\tstring\t\t\t\ttrue\t\"验证消息\"\n////\t@Success\t200\t{object}\tresp.ResponseData\t\"成功\"\n////\t@Failure\t500\t{object}\tresp.ResponseData\t\"内部错误\"\n////\t@Router\t\t/api/user/add [post]\n//func ApplyFriendController(c *gin.Context) {\n//\tuid := c.GetInt64(\"uid\")\n//\tapplyReq := req.UserApplyReq{}\n//\tif err := c.ShouldBind(&applyReq); err != nil {\n//\t\tresp.ErrorResponse(c, \"参数错误\")\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\tresponse, err := service.ApplyFriendService(uid, applyReq)\n//\tif err != nil {\n//\t\tc.Abort()\n//\t\tresp.ReturnErrorResponse(c, response)\n//\t\treturn\n//\t}\n//\tresp.ReturnSuccessResponse(c, response)\n//\treturn\n//}\n//\n//// DeleteFriendController 删除好友\n////\n////\t@Summary\t删除好友\n////\t@Produce\tjson\n////\t@Param\t\tuid\tbody\t\tint\t\t\t\t\ttrue\t\"好友uid\"\n////\t@Success\t200\t{object}\tresp.ResponseData\t\"成功\"\n////\t@Failure\t500\t{object}\tresp.ResponseData\t\"内部错误\"\n////\t@Router\t\t/api/user/delete/:uid [delete]\n//func DeleteFriendController(c *gin.Context) {\n//\tuid := c.GetInt64(\"uid\")\n//\tdeleteFriendReq := req.DeleteFriendReq{}\n//\tif err := c.ShouldBind(&deleteFriendReq); err != nil {\n//\t\tresp.ErrorResponse(c, \"参数错误\")\n//\t\treturn\n//\t}\n//\tresponse, err := service.DeleteFriendService(uid, deleteFriendReq)\n//\tif err != nil {\n//\t\tc.Abort()\n//\t\tresp.ReturnErrorResponse(c, response)\n//\t\treturn\n//\t}\n//\tresp.ReturnSuccessResponse(c, response)\n//}\n//\n//// AgreeFriendController 同意好友申请\n////\n////\t@Summary\t同意好友申请\n////\t@Produce\tjson\n////\t@Param\t\tuid\tbody\t\tint\t\t\t\t\ttrue\t\"好友uid\"\n////\t@Success\t200\t{object}\tresp.ResponseData\t\"成功\"\n////\t@Failure\t500\t{object}\tresp.ResponseData\t\"内部错误\"\n////\t@Router\t\t/api/user/agree [put]\n//func AgreeFriendController(c *gin.Context) {\n//\tuid := c.GetInt64(\"uid\")\n//\tagreeFriendReq := req.AgreeFriendReq{}\n//\tif err := c.ShouldBind(&agreeFriendReq); err != nil { //ShouldBind()会自动推导\n//\t\tresp.ErrorResponse(c, \"参数错误\")\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\tresponse, err := service.AgreeFriendService(uid, agreeFriendReq.Uid)\n//\tif err != nil {\n//\t\tc.Abort()\n//\t\tresp.ReturnErrorResponse(c, response)\n//\t\treturn\n//\t}\n//\tresp.ReturnSuccessResponse(c, response)\n//}\n//\n//// GetUserApplyController 同意好友申请\n////\n////\t@Summary\t同意好友申请\n////\t@Produce\tjson\n////\t@Param\t\tuid\tbody\t\tint\t\t\t\t\ttrue\t\"好友uid\"\n////\t@Success\t200\t{object}\tresp.ResponseData\t\"成功\"\n////\t@Failure\t500\t{object}\tresp.ResponseData\t\"内部错误\"\n////\t@Router\t\t/api/user/getApplyList [get]\n//func GetUserApplyController(c *gin.Context) {\n//\tuid := c.GetInt64(\"uid\")\n//\tpageRequest := pkgReq.PageReq{}\n//\tif err := c.ShouldBindQuery(&pageRequest); err != nil { //ShouldBind()会自动推导\n//\t\tresp.ErrorResponse(c, \"参数错误\")\n//\t\treturn\n//\t}\n//\tresponse, err := service.GetUserApplyService(uid, pageRequest)\n//\tif err != nil {\n//\t\tc.Abort()\n//\t\tresp.ReturnErrorResponse(c, response)\n//\t\treturn\n//\t}\n//\n//\tresp.ReturnSuccessResponse(c, response)\n//}\n//\n//// IsFriendController 是否为好友关系\n////\n////\t@Summary\t是否为好友关系\n////\t@Produce\tjson\n////\t@Param\t\tuid\tbody\t\tint\t\t\t\t\ttrue\t\"好友uid\"\n////\t@Success\t200\t{object}\tresp.ResponseData\t\"成功\"\n////\t@Failure\t500\t{object}\tresp.ResponseData\t\"内部错误\"\n////\t@Router\t\t/api/user/isFriend/:friendUid [get]\n//func IsFriendController(c *gin.Context) {\n//\tuid := c.GetInt64(\"uid\")\n//\tisFriendReq := req.IsFriendReq{}\n//\tif err := c.ShouldBindUri(&isFriendReq); err != nil {\n//\t\tresp.ErrorResponse(c, \"参数错误\")\n//\t\treturn\n//\t}\n//\tresponse, err := service.IsFriendService(uid, isFriendReq.FriendUid)\n//\tif err != nil {\n//\t\tc.Abort()\n//\t\tresp.ReturnErrorResponse(c, response)\n//\t\treturn\n//\t}\n//\tresp.ReturnSuccessResponse(c, response)\n//}\n//\n//// UnreadApplyNumController 好友申请未读数量\n////\n////\t@Summary\t好友申请未读数量\n////\t@Success\t200\t\t\t{object}\tresp.ResponseData\t\"成功\"\n////\t@Failure\t500\t\t\t{object}\tresp.ResponseData\t\"内部错误\"\n////\t@Router\t\t/api/user/unreadApplyNum [get]\n//func UnreadApplyNumController(c *gin.Context) {\n//\tuid := c.GetInt64(\"uid\")\n//\n//\tresponse, err := service.UnreadApplyNumService(uid)\n//\tif err != nil {\n//\t\tc.Abort()\n//\t\tresp.ReturnErrorResponse(c, response)\n//\t\treturn\n//\t}\n//\tresp.ReturnSuccessResponse(c, response)\n//}\n//\n//func GetFriendListController(c *gin.Context) {\n//\tuid := c.GetInt64(\"uid\")\n//\tpageRequest := pkgReq.PageReq{}\n//\tif err := c.ShouldBindQuery(&pageRequest); err != nil { //ShouldBind()会自动推导\n//\t\tresp.ErrorResponse(c, \"参数错误\")\n//\t\treturn\n//\t}\n//\tresponse, err := service.GetFriendListService(uid, pageRequest)\n//\tif err != nil {\n//\t\tc.Abort()\n//\t\tresp.ReturnErrorResponse(c, response)\n//\t\treturn\n//\t}\n//\tresp.ReturnSuccessResponse(c, response)\n//}\n//\n//// GetUserInfoByNameController 根据好友昵称搜索好友\n//func GetUserInfoByNameController(c *gin.Context) {\n//\tuid := c.GetInt64(\"uid\")\n//\tgetUserInfoByNameReq := req.GetUserInfoByNameReq{}\n//\tif err := c.ShouldBindQuery(&getUserInfoByNameReq); err != nil { //ShouldBind()会自动推导\n//\t\tresp.ErrorResponse(c, \"参数错误\")\n//\t\treturn\n//\t}\n//\tresponse, err := service.GetUserInfoByNameService(uid, getUserInfoByNameReq.Name)\n//\tif err != nil {\n//\t\tc.Abort()\n//\t\tresp.ReturnErrorResponse(c, response)\n//\t\treturn\n//\t}\n//\tresp.ReturnSuccessResponse(c, response)\n//}\n"
  },
  {
    "path": "controller/user_controller.go",
    "content": "package controller\n\nimport (\n\t\"DiTing-Go/domain/vo/req\"\n\t\"DiTing-Go/pkg/domain/vo/resp\"\n\t\"DiTing-Go/service\"\n\t\"github.com/gin-gonic/gin\"\n)\n\n// RegisterController 用户注册\n//\n//\t@Summary\t用户注册\n//\t@Produce\tjson\n//\t@Param\t\tname\t\tbody\t\tstring\t\t\t\ttrue\t\"用户名\"\n//\t@Param\t\tpassword\tbody\t\tstring\t\t\t\ttrue\t\"密码\"\n//\t@Param\t\tphone\t\tbody\t\tstring\t\t\t\ttrue\t\"手机号\"\n//\t@Param\t\tcaptcha\t\tbody\t\tstring\t\t\t\ttrue\t\"验证码\"\n//\t@Success\t200\t\t\t{object}\tresp.ResponseData\t\"成功\"\n//\t@Failure\t500\t\t\t{object}\tresp.ResponseData\t\"内部错误\"\n//\t@Router\t\t/api/public/register [post]\nfunc RegisterController(c *gin.Context) {\n\tuserReq := req.UserRegisterReq{}\n\tif err := c.ShouldBind(&userReq); err != nil {\n\t\tresp.ErrorResponse(c, \"参数错误\")\n\t\tc.Abort()\n\t\treturn\n\t}\n\tresponse, err := service.RegisterService(userReq)\n\tif err != nil {\n\t\tc.Abort()\n\t\tresp.ReturnErrorResponse(c, response)\n\t\treturn\n\t}\n\tresp.ReturnSuccessResponse(c, response)\n}\n\n// LoginController 用户登录\n//\n//\t@Summary\t用户登录\n//\t@Produce\tjson\n//\t@Param\t\tname\t\tbody\t\tstring\t\t\t\ttrue\t\"用户名\"\n//\t@Param\t\tpassword\tbody\t\tstring\t\t\t\ttrue\t\"密码\"\n//\t@Success\t200\t\t\t{object}\tresp.ResponseData\t\"成功\"\n//\t@Failure\t500\t\t\t{object}\tresp.ResponseData\t\"内部错误\"\n//\t@Router\t\t/api/public/login [post]\nfunc LoginController(c *gin.Context) {\n\tuserLogin := req.UserLoginReq{}\n\tif err := c.ShouldBind(&userLogin); err != nil { //ShouldBind()会自动推导\n\t\tresp.ErrorResponse(c, \"参数错误\")\n\t\tc.Abort()\n\t\treturn\n\t}\n\tresponse, err := service.LoginService(userLogin)\n\tif err != nil {\n\t\tc.Abort()\n\t\tresp.ReturnErrorResponse(c, response)\n\t\treturn\n\t}\n\tresp.ReturnSuccessResponse(c, response)\n}\n\n// CancelController 注销账户\n//\n//\t@Summary\t注销账户\n//\t@Produce\tjson\n//\t@Param\t\tphone\t\tbody\t\tstring\t\t\t\ttrue\t\"手机号\"\n//\t@Success\t200\t\t\t{object}\tresp.ResponseData\t\"成功\"\n//\t@Failure\t500\t\t\t{object}\tresp.ResponseData\t\"内部错误\"\n//\t@Router\t\t/api/public/login [post]\nfunc CancelController(ctx *gin.Context) {\n\tuserCancel := req.UserCancelReq{}\n\tif err := ctx.ShouldBind(&userCancel); err != nil {\n\t\tresp.ErrorResponse(ctx, \"参数错误\")\n\t\tctx.Abort()\n\t\treturn\n\t}\n\tresponse, err := service.CancelService(ctx, userCancel)\n\tif err != nil {\n\t\tctx.Abort()\n\t\tresp.ReturnErrorResponse(ctx, response)\n\t\treturn\n\t}\n\tresp.ReturnSuccessResponse(ctx, response)\n}\n"
  },
  {
    "path": "dal/db.go",
    "content": "package dal\n\nimport (\n\t\"fmt\"\n\t\"gorm.io/driver/mysql\"\n\t\"gorm.io/gorm\"\n)\n\nvar DB *gorm.DB\n\n// ConnectDB 初始化数据库连接\nfunc ConnectDB(dsn string) *gorm.DB {\n\tdb, err := gorm.Open(mysql.Open(dsn))\n\tsqlDB, err := db.DB()\n\t// SetMaxIdleConns sets the maximum number of connections in the idle connection pool.\n\tsqlDB.SetMaxIdleConns(10)\n\tsqlDB.SetMaxOpenConns(100)\n\tif err != nil {\n\t\tpanic(fmt.Errorf(\"connect db fail: %w\", err))\n\t}\n\treturn db\n}\n"
  },
  {
    "path": "dal/model/contact.gen.go",
    "content": "// Code generated by gorm.io/gen. DO NOT EDIT.\n// Code generated by gorm.io/gen. DO NOT EDIT.\n// Code generated by gorm.io/gen. DO NOT EDIT.\n\npackage model\n\nimport (\n\t\"time\"\n)\n\nconst TableNameContact = \"contact\"\n\n// Contact 会话列表\ntype Contact struct {\n\tID         int64     `gorm:\"column:id;primaryKey;autoIncrement:true;comment:id\" json:\"id\"`                             // id\n\tUID        int64     `gorm:\"column:uid;not null;comment:uid\" json:\"uid\"`                                               // uid\n\tRoomID     int64     `gorm:\"column:room_id;not null;comment:房间id\" json:\"room_id\"`                                      // 房间id\n\tReadTime   time.Time `gorm:\"column:read_time;not null;default:CURRENT_TIMESTAMP(3);comment:阅读到的时间\" json:\"read_time\"`   // 阅读到的时间\n\tActiveTime time.Time `gorm:\"column:active_time;comment:会话内消息最后更新的时间(只有普通会话需要维护，全员会话不需要维护)\" json:\"active_time\"`         // 会话内消息最后更新的时间(只有普通会话需要维护，全员会话不需要维护)\n\tLastMsgID  int64     `gorm:\"column:last_msg_id;comment:会话最新消息id\" json:\"last_msg_id\"`                                   // 会话最新消息id\n\tCreateTime time.Time `gorm:\"column:create_time;not null;default:CURRENT_TIMESTAMP(3);comment:创建时间\" json:\"create_time\"` // 创建时间\n\tUpdateTime time.Time `gorm:\"column:update_time;not null;default:CURRENT_TIMESTAMP(3);comment:修改时间\" json:\"update_time\"` // 修改时间\n}\n\n// TableName Contact's table name\nfunc (*Contact) TableName() string {\n\treturn TableNameContact\n}\n"
  },
  {
    "path": "dal/model/group_member.gen.go",
    "content": "// Code generated by gorm.io/gen. DO NOT EDIT.\n// Code generated by gorm.io/gen. DO NOT EDIT.\n// Code generated by gorm.io/gen. DO NOT EDIT.\n\npackage model\n\nimport (\n\t\"time\"\n)\n\nconst TableNameGroupMember = \"group_member\"\n\n// GroupMember 群成员表\ntype GroupMember struct {\n\tID         int64     `gorm:\"column:id;primaryKey;autoIncrement:true;comment:id\" json:\"id\"`                             // id\n\tGroupID    int64     `gorm:\"column:group_id;not null;comment:群主id\" json:\"group_id\"`                                    // 群主id\n\tUID        int64     `gorm:\"column:uid;not null;comment:成员uid\" json:\"uid\"`                                             // 成员uid\n\tRole       int32     `gorm:\"column:role;not null;comment:成员角色 1群主 2管理员 3普通成员\" json:\"role\"`                             // 成员角色 1群主 2管理员 3普通成员\n\tCreateTime time.Time `gorm:\"column:create_time;not null;default:CURRENT_TIMESTAMP(3);comment:创建时间\" json:\"create_time\"` // 创建时间\n\tUpdateTime time.Time `gorm:\"column:update_time;not null;default:CURRENT_TIMESTAMP(3);comment:修改时间\" json:\"update_time\"` // 修改时间\n}\n\n// TableName GroupMember's table name\nfunc (*GroupMember) TableName() string {\n\treturn TableNameGroupMember\n}\n"
  },
  {
    "path": "dal/model/message.gen.go",
    "content": "// Code generated by gorm.io/gen. DO NOT EDIT.\n// Code generated by gorm.io/gen. DO NOT EDIT.\n// Code generated by gorm.io/gen. DO NOT EDIT.\n\npackage model\n\nimport (\n\t\"time\"\n)\n\nconst TableNameMessage = \"message\"\n\n// Message 消息表\ntype Message struct {\n\tID           int64     `gorm:\"column:id;primaryKey;autoIncrement:true;comment:id\" json:\"id\"`                             // id\n\tRoomID       int64     `gorm:\"column:room_id;not null;comment:会话表id\" json:\"room_id\"`                                     // 会话表id\n\tFromUID      int64     `gorm:\"column:from_uid;not null;comment:消息发送者uid\" json:\"from_uid\"`                                // 消息发送者uid\n\tContent      string    `gorm:\"column:content;comment:消息内容\" json:\"content\"`                                               // 消息内容\n\tReplyMsgID   int64     `gorm:\"column:reply_msg_id;comment:回复的消息内容\" json:\"reply_msg_id\"`                                  // 回复的消息内容\n\tDeleteStatus int32     `gorm:\"column:delete_status;not null;comment:消息状态 0正常 1删除\" json:\"delete_status\"`                  // 消息状态 0正常 1删除\n\tGapCount     int32     `gorm:\"column:gap_count;comment:与回复的消息间隔多少条\" json:\"gap_count\"`                                    // 与回复的消息间隔多少条\n\tType         int32     `gorm:\"column:type;default:1;comment:消息类型 1正常文本 2.撤回消息\" json:\"type\"`                              // 消息类型 1正常文本 2.撤回消息\n\tExtra        string    `gorm:\"column:extra;comment:扩展信息\" json:\"extra\"`                                                   // 扩展信息\n\tCreateTime   time.Time `gorm:\"column:create_time;not null;default:CURRENT_TIMESTAMP(3);comment:创建时间\" json:\"create_time\"` // 创建时间\n\tUpdateTime   time.Time `gorm:\"column:update_time;not null;default:CURRENT_TIMESTAMP(3);comment:修改时间\" json:\"update_time\"` // 修改时间\n}\n\n// TableName Message's table name\nfunc (*Message) TableName() string {\n\treturn TableNameMessage\n}\n"
  },
  {
    "path": "dal/model/room.gen.go",
    "content": "// Code generated by gorm.io/gen. DO NOT EDIT.\n// Code generated by gorm.io/gen. DO NOT EDIT.\n// Code generated by gorm.io/gen. DO NOT EDIT.\n\npackage model\n\nimport (\n\t\"time\"\n)\n\nconst TableNameRoom = \"room\"\n\n// Room 房间表\ntype Room struct {\n\tID           int64     `gorm:\"column:id;primaryKey;autoIncrement:true;comment:id\" json:\"id\"`                                                    // id\n\tType         int32     `gorm:\"column:type;not null;comment:房间类型 1群聊 2单聊\" json:\"type\"`                                                           // 房间类型 1群聊 2单聊\n\tHotFlag      int32     `gorm:\"column:hot_flag;comment:是否全员展示 0否 1是\" json:\"hot_flag\"`                                                            // 是否全员展示 0否 1是\n\tActiveTime   time.Time `gorm:\"column:active_time;not null;default:CURRENT_TIMESTAMP(3);comment:群最后消息的更新时间（热点群不需要写扩散，只更新这里）\" json:\"active_time\"` // 群最后消息的更新时间（热点群不需要写扩散，只更新这里）\n\tLastMsgID    int64     `gorm:\"column:last_msg_id;comment:会话中的最后一条消息id\" json:\"last_msg_id\"`                                                      // 会话中的最后一条消息id\n\tExtJSON      string    `gorm:\"column:ext_json;comment:额外信息（根据不同类型房间有不同存储的东西）\" json:\"ext_json\"`                                                  // 额外信息（根据不同类型房间有不同存储的东西）\n\tCreateTime   time.Time `gorm:\"column:create_time;not null;default:CURRENT_TIMESTAMP(3);comment:创建时间\" json:\"create_time\"`                        // 创建时间\n\tUpdateTime   time.Time `gorm:\"column:update_time;not null;default:CURRENT_TIMESTAMP(3);comment:修改时间\" json:\"update_time\"`                        // 修改时间\n\tDeleteStatus int32     `gorm:\"column:delete_status;not null;comment:房间状态 0正常 1禁用(删好友了禁用)\" json:\"delete_status\"`                                 // 房间状态 0正常 1禁用(删好友了禁用)\n}\n\n// TableName Room's table name\nfunc (*Room) TableName() string {\n\treturn TableNameRoom\n}\n"
  },
  {
    "path": "dal/model/room_friend.gen.go",
    "content": "// Code generated by gorm.io/gen. DO NOT EDIT.\n// Code generated by gorm.io/gen. DO NOT EDIT.\n// Code generated by gorm.io/gen. DO NOT EDIT.\n\npackage model\n\nimport (\n\t\"time\"\n)\n\nconst TableNameRoomFriend = \"room_friend\"\n\n// RoomFriend 单聊房间表\ntype RoomFriend struct {\n\tID           int64     `gorm:\"column:id;primaryKey;autoIncrement:true;comment:id\" json:\"id\"`                             // id\n\tRoomID       int64     `gorm:\"column:room_id;not null;comment:房间id\" json:\"room_id\"`                                      // 房间id\n\tUid1         int64     `gorm:\"column:uid1;not null;comment:uid1（更小的uid）\" json:\"uid1\"`                                    // uid1（更小的uid）\n\tUid2         int64     `gorm:\"column:uid2;not null;comment:uid2（更大的uid）\" json:\"uid2\"`                                    // uid2（更大的uid）\n\tRoomKey      string    `gorm:\"column:room_key;not null;comment:房间key由两个uid拼接，先做排序uid1_uid2\" json:\"room_key\"`             // 房间key由两个uid拼接，先做排序uid1_uid2\n\tDeleteStatus int32     `gorm:\"column:delete_status;not null;comment:房间状态 0正常 1禁用(删好友了禁用)\" json:\"delete_status\"`          // 房间状态 0正常 1禁用(删好友了禁用)\n\tCreateTime   time.Time `gorm:\"column:create_time;not null;default:CURRENT_TIMESTAMP(3);comment:创建时间\" json:\"create_time\"` // 创建时间\n\tUpdateTime   time.Time `gorm:\"column:update_time;not null;default:CURRENT_TIMESTAMP(3);comment:修改时间\" json:\"update_time\"` // 修改时间\n}\n\n// TableName RoomFriend's table name\nfunc (*RoomFriend) TableName() string {\n\treturn TableNameRoomFriend\n}\n"
  },
  {
    "path": "dal/model/room_group.gen.go",
    "content": "// Code generated by gorm.io/gen. DO NOT EDIT.\n// Code generated by gorm.io/gen. DO NOT EDIT.\n// Code generated by gorm.io/gen. DO NOT EDIT.\n\npackage model\n\nimport (\n\t\"time\"\n)\n\nconst TableNameRoomGroup = \"room_group\"\n\n// RoomGroup 群聊房间表\ntype RoomGroup struct {\n\tID           int64     `gorm:\"column:id;primaryKey;autoIncrement:true;comment:id\" json:\"id\"`                             // id\n\tRoomID       int64     `gorm:\"column:room_id;not null;comment:房间id\" json:\"room_id\"`                                      // 房间id\n\tName         string    `gorm:\"column:name;not null;comment:群名称\" json:\"name\"`                                             // 群名称\n\tAvatar       string    `gorm:\"column:avatar;not null;comment:群头像\" json:\"avatar\"`                                         // 群头像\n\tExtJSON      string    `gorm:\"column:ext_json;comment:额外信息（根据不同类型房间有不同存储的东西）\" json:\"ext_json\"`                           // 额外信息（根据不同类型房间有不同存储的东西）\n\tDeleteStatus int32     `gorm:\"column:delete_status;not null;comment:逻辑删除(0-正常,1-删除)\" json:\"delete_status\"`               // 逻辑删除(0-正常,1-删除)\n\tCreateTime   time.Time `gorm:\"column:create_time;not null;default:CURRENT_TIMESTAMP(3);comment:创建时间\" json:\"create_time\"` // 创建时间\n\tUpdateTime   time.Time `gorm:\"column:update_time;not null;default:CURRENT_TIMESTAMP(3);comment:修改时间\" json:\"update_time\"` // 修改时间\n}\n\n// TableName RoomGroup's table name\nfunc (*RoomGroup) TableName() string {\n\treturn TableNameRoomGroup\n}\n"
  },
  {
    "path": "dal/model/user.gen.go",
    "content": "// Code generated by gorm.io/gen. DO NOT EDIT.\n// Code generated by gorm.io/gen. DO NOT EDIT.\n// Code generated by gorm.io/gen. DO NOT EDIT.\n\npackage model\n\nimport (\n\t\"time\"\n)\n\nconst TableNameUser = \"user\"\n\n// User 用户表\ntype User struct {\n\tID           int64     `gorm:\"column:id;primaryKey;autoIncrement:true;comment:用户id\" json:\"id\"`                                  // 用户id\n\tPhone        string    `gorm:\"column:phone;not null;comment:用户手机\" json:\"phone\"`                                                 // 用户手机\n\tPassword     string    `gorm:\"column:password;not null;comment:用户密码\" json:\"password\"`                                           // 用户密码\n\tName         string    `gorm:\"column:name;not null;comment:用户昵称\" json:\"name\"`                                                   // 用户昵称\n\tAvatar       string    `gorm:\"column:avatar;not null;comment:用户头像\" json:\"avatar\"`                                               // 用户头像\n\tSex          int32     `gorm:\"column:sex;default:3;comment:性别 1为男性，2为女性，3未知\" json:\"sex\"`                                        // 性别 1为男性，2为女性，3未知\n\tActiveStatus int32     `gorm:\"column:active_status;default:2;comment:在线状态 1在线 2离线\" json:\"active_status\"`                        // 在线状态 1在线 2离线\n\tLastOptTime  time.Time `gorm:\"column:last_opt_time;not null;default:CURRENT_TIMESTAMP(3);comment:最后上下线时间\" json:\"last_opt_time\"` // 最后上下线时间\n\tIPInfo       string    `gorm:\"column:ip_info;comment:ip信息，用于显示用户地理位置\" json:\"ip_info\"`                                           // ip信息，用于显示用户地理位置\n\tItemID       int64     `gorm:\"column:item_id;comment:佩戴的徽章id\" json:\"item_id\"`                                                   // 佩戴的徽章id\n\tStatus       int32     `gorm:\"column:status;default:1;comment:使用状态 1.正常 2禁用\" json:\"status\"`                                     // 使用状态 1.正常 2禁用\n\tCreateTime   time.Time `gorm:\"column:create_time;not null;default:CURRENT_TIMESTAMP(3);comment:创建时间\" json:\"create_time\"`        // 创建时间\n\tUpdateTime   time.Time `gorm:\"column:update_time;not null;default:CURRENT_TIMESTAMP(3);comment:修改时间\" json:\"update_time\"`        // 修改时间\n}\n\n// TableName User's table name\nfunc (*User) TableName() string {\n\treturn TableNameUser\n}\n"
  },
  {
    "path": "dal/model/user_apply.gen.go",
    "content": "// Code generated by gorm.io/gen. DO NOT EDIT.\n// Code generated by gorm.io/gen. DO NOT EDIT.\n// Code generated by gorm.io/gen. DO NOT EDIT.\n\npackage model\n\nimport (\n\t\"time\"\n)\n\nconst TableNameUserApply = \"user_apply\"\n\n// UserApply 用户申请表\ntype UserApply struct {\n\tID         int64     `gorm:\"column:id;primaryKey;autoIncrement:true;comment:id\" json:\"id\"`                             // id\n\tUID        int64     `gorm:\"column:uid;not null;comment:申请人uid\" json:\"uid\"`                                            // 申请人uid\n\tType       int32     `gorm:\"column:type;not null;comment:申请类型 1加好友\" json:\"type\"`                                       // 申请类型 1加好友\n\tTargetID   int64     `gorm:\"column:target_id;not null;comment:接收人uid\" json:\"target_id\"`                                // 接收人uid\n\tMsg        string    `gorm:\"column:msg;not null;comment:申请信息\" json:\"msg\"`                                              // 申请信息\n\tStatus     int32     `gorm:\"column:status;not null;comment:申请状态 1待审批 2同意\" json:\"status\"`                               // 申请状态 1待审批 2同意\n\tReadStatus int32     `gorm:\"column:read_status;not null;comment:阅读状态 1未读 2已读\" json:\"read_status\"`                      // 阅读状态 1未读 2已读\n\tCreateTime time.Time `gorm:\"column:create_time;not null;default:CURRENT_TIMESTAMP(3);comment:创建时间\" json:\"create_time\"` // 创建时间\n\tUpdateTime time.Time `gorm:\"column:update_time;not null;default:CURRENT_TIMESTAMP(3);comment:修改时间\" json:\"update_time\"` // 修改时间\n}\n\n// TableName UserApply's table name\nfunc (*UserApply) TableName() string {\n\treturn TableNameUserApply\n}\n"
  },
  {
    "path": "dal/model/user_friend.gen.go",
    "content": "// Code generated by gorm.io/gen. DO NOT EDIT.\n// Code generated by gorm.io/gen. DO NOT EDIT.\n// Code generated by gorm.io/gen. DO NOT EDIT.\n\npackage model\n\nimport (\n\t\"time\"\n)\n\nconst TableNameUserFriend = \"user_friend\"\n\n// UserFriend 用户联系人表\ntype UserFriend struct {\n\tID           int64     `gorm:\"column:id;primaryKey;autoIncrement:true;comment:id\" json:\"id\"`                             // id\n\tUID          int64     `gorm:\"column:uid;not null;comment:uid\" json:\"uid\"`                                               // uid\n\tFriendUID    int64     `gorm:\"column:friend_uid;not null;comment:好友uid\" json:\"friend_uid\"`                               // 好友uid\n\tDeleteStatus int32     `gorm:\"column:delete_status;not null;comment:逻辑删除(0-正常,1-删除)\" json:\"delete_status\"`               // 逻辑删除(0-正常,1-删除)\n\tCreateTime   time.Time `gorm:\"column:create_time;not null;default:CURRENT_TIMESTAMP(3);comment:创建时间\" json:\"create_time\"` // 创建时间\n\tUpdateTime   time.Time `gorm:\"column:update_time;not null;default:CURRENT_TIMESTAMP(3);comment:修改时间\" json:\"update_time\"` // 修改时间\n}\n\n// TableName UserFriend's table name\nfunc (*UserFriend) TableName() string {\n\treturn TableNameUserFriend\n}\n"
  },
  {
    "path": "dal/query/contact.gen.go",
    "content": "// Code generated by gorm.io/gen. DO NOT EDIT.\n// Code generated by gorm.io/gen. DO NOT EDIT.\n// Code generated by gorm.io/gen. DO NOT EDIT.\n\npackage query\n\nimport (\n\t\"context\"\n\n\t\"gorm.io/gorm\"\n\t\"gorm.io/gorm/clause\"\n\t\"gorm.io/gorm/schema\"\n\n\t\"gorm.io/gen\"\n\t\"gorm.io/gen/field\"\n\n\t\"gorm.io/plugin/dbresolver\"\n\n\t\"DiTing-Go/dal/model\"\n)\n\nfunc newContact(db *gorm.DB, opts ...gen.DOOption) contact {\n\t_contact := contact{}\n\n\t_contact.contactDo.UseDB(db, opts...)\n\t_contact.contactDo.UseModel(&model.Contact{})\n\n\ttableName := _contact.contactDo.TableName()\n\t_contact.ALL = field.NewAsterisk(tableName)\n\t_contact.ID = field.NewInt64(tableName, \"id\")\n\t_contact.UID = field.NewInt64(tableName, \"uid\")\n\t_contact.RoomID = field.NewInt64(tableName, \"room_id\")\n\t_contact.ReadTime = field.NewTime(tableName, \"read_time\")\n\t_contact.ActiveTime = field.NewTime(tableName, \"active_time\")\n\t_contact.LastMsgID = field.NewInt64(tableName, \"last_msg_id\")\n\t_contact.CreateTime = field.NewTime(tableName, \"create_time\")\n\t_contact.UpdateTime = field.NewTime(tableName, \"update_time\")\n\n\t_contact.fillFieldMap()\n\n\treturn _contact\n}\n\n// contact 会话列表\ntype contact struct {\n\tcontactDo contactDo\n\n\tALL        field.Asterisk\n\tID         field.Int64 // id\n\tUID        field.Int64 // uid\n\tRoomID     field.Int64 // 房间id\n\tReadTime   field.Time  // 阅读到的时间\n\tActiveTime field.Time  // 会话内消息最后更新的时间(只有普通会话需要维护，全员会话不需要维护)\n\tLastMsgID  field.Int64 // 会话最新消息id\n\tCreateTime field.Time  // 创建时间\n\tUpdateTime field.Time  // 修改时间\n\n\tfieldMap map[string]field.Expr\n}\n\nfunc (c contact) Table(newTableName string) *contact {\n\tc.contactDo.UseTable(newTableName)\n\treturn c.updateTableName(newTableName)\n}\n\nfunc (c contact) As(alias string) *contact {\n\tc.contactDo.DO = *(c.contactDo.As(alias).(*gen.DO))\n\treturn c.updateTableName(alias)\n}\n\nfunc (c *contact) updateTableName(table string) *contact {\n\tc.ALL = field.NewAsterisk(table)\n\tc.ID = field.NewInt64(table, \"id\")\n\tc.UID = field.NewInt64(table, \"uid\")\n\tc.RoomID = field.NewInt64(table, \"room_id\")\n\tc.ReadTime = field.NewTime(table, \"read_time\")\n\tc.ActiveTime = field.NewTime(table, \"active_time\")\n\tc.LastMsgID = field.NewInt64(table, \"last_msg_id\")\n\tc.CreateTime = field.NewTime(table, \"create_time\")\n\tc.UpdateTime = field.NewTime(table, \"update_time\")\n\n\tc.fillFieldMap()\n\n\treturn c\n}\n\nfunc (c *contact) WithContext(ctx context.Context) IContactDo { return c.contactDo.WithContext(ctx) }\n\nfunc (c contact) TableName() string { return c.contactDo.TableName() }\n\nfunc (c contact) Alias() string { return c.contactDo.Alias() }\n\nfunc (c contact) Columns(cols ...field.Expr) gen.Columns { return c.contactDo.Columns(cols...) }\n\nfunc (c *contact) GetFieldByName(fieldName string) (field.OrderExpr, bool) {\n\t_f, ok := c.fieldMap[fieldName]\n\tif !ok || _f == nil {\n\t\treturn nil, false\n\t}\n\t_oe, ok := _f.(field.OrderExpr)\n\treturn _oe, ok\n}\n\nfunc (c *contact) fillFieldMap() {\n\tc.fieldMap = make(map[string]field.Expr, 8)\n\tc.fieldMap[\"id\"] = c.ID\n\tc.fieldMap[\"uid\"] = c.UID\n\tc.fieldMap[\"room_id\"] = c.RoomID\n\tc.fieldMap[\"read_time\"] = c.ReadTime\n\tc.fieldMap[\"active_time\"] = c.ActiveTime\n\tc.fieldMap[\"last_msg_id\"] = c.LastMsgID\n\tc.fieldMap[\"create_time\"] = c.CreateTime\n\tc.fieldMap[\"update_time\"] = c.UpdateTime\n}\n\nfunc (c contact) clone(db *gorm.DB) contact {\n\tc.contactDo.ReplaceConnPool(db.Statement.ConnPool)\n\treturn c\n}\n\nfunc (c contact) replaceDB(db *gorm.DB) contact {\n\tc.contactDo.ReplaceDB(db)\n\treturn c\n}\n\ntype contactDo struct{ gen.DO }\n\ntype IContactDo interface {\n\tgen.SubQuery\n\tDebug() IContactDo\n\tWithContext(ctx context.Context) IContactDo\n\tWithResult(fc func(tx gen.Dao)) gen.ResultInfo\n\tReplaceDB(db *gorm.DB)\n\tReadDB() IContactDo\n\tWriteDB() IContactDo\n\tAs(alias string) gen.Dao\n\tSession(config *gorm.Session) IContactDo\n\tColumns(cols ...field.Expr) gen.Columns\n\tClauses(conds ...clause.Expression) IContactDo\n\tNot(conds ...gen.Condition) IContactDo\n\tOr(conds ...gen.Condition) IContactDo\n\tSelect(conds ...field.Expr) IContactDo\n\tWhere(conds ...gen.Condition) IContactDo\n\tOrder(conds ...field.Expr) IContactDo\n\tDistinct(cols ...field.Expr) IContactDo\n\tOmit(cols ...field.Expr) IContactDo\n\tJoin(table schema.Tabler, on ...field.Expr) IContactDo\n\tLeftJoin(table schema.Tabler, on ...field.Expr) IContactDo\n\tRightJoin(table schema.Tabler, on ...field.Expr) IContactDo\n\tGroup(cols ...field.Expr) IContactDo\n\tHaving(conds ...gen.Condition) IContactDo\n\tLimit(limit int) IContactDo\n\tOffset(offset int) IContactDo\n\tCount() (count int64, err error)\n\tScopes(funcs ...func(gen.Dao) gen.Dao) IContactDo\n\tUnscoped() IContactDo\n\tCreate(values ...*model.Contact) error\n\tCreateInBatches(values []*model.Contact, batchSize int) error\n\tSave(values ...*model.Contact) error\n\tFirst() (*model.Contact, error)\n\tTake() (*model.Contact, error)\n\tLast() (*model.Contact, error)\n\tFind() ([]*model.Contact, error)\n\tFindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.Contact, err error)\n\tFindInBatches(result *[]*model.Contact, batchSize int, fc func(tx gen.Dao, batch int) error) error\n\tPluck(column field.Expr, dest interface{}) error\n\tDelete(...*model.Contact) (info gen.ResultInfo, err error)\n\tUpdate(column field.Expr, value interface{}) (info gen.ResultInfo, err error)\n\tUpdateSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error)\n\tUpdates(value interface{}) (info gen.ResultInfo, err error)\n\tUpdateColumn(column field.Expr, value interface{}) (info gen.ResultInfo, err error)\n\tUpdateColumnSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error)\n\tUpdateColumns(value interface{}) (info gen.ResultInfo, err error)\n\tUpdateFrom(q gen.SubQuery) gen.Dao\n\tAttrs(attrs ...field.AssignExpr) IContactDo\n\tAssign(attrs ...field.AssignExpr) IContactDo\n\tJoins(fields ...field.RelationField) IContactDo\n\tPreload(fields ...field.RelationField) IContactDo\n\tFirstOrInit() (*model.Contact, error)\n\tFirstOrCreate() (*model.Contact, error)\n\tFindByPage(offset int, limit int) (result []*model.Contact, count int64, err error)\n\tScanByPage(result interface{}, offset int, limit int) (count int64, err error)\n\tScan(result interface{}) (err error)\n\tReturning(value interface{}, columns ...string) IContactDo\n\tUnderlyingDB() *gorm.DB\n\tschema.Tabler\n}\n\nfunc (c contactDo) Debug() IContactDo {\n\treturn c.withDO(c.DO.Debug())\n}\n\nfunc (c contactDo) WithContext(ctx context.Context) IContactDo {\n\treturn c.withDO(c.DO.WithContext(ctx))\n}\n\nfunc (c contactDo) ReadDB() IContactDo {\n\treturn c.Clauses(dbresolver.Read)\n}\n\nfunc (c contactDo) WriteDB() IContactDo {\n\treturn c.Clauses(dbresolver.Write)\n}\n\nfunc (c contactDo) Session(config *gorm.Session) IContactDo {\n\treturn c.withDO(c.DO.Session(config))\n}\n\nfunc (c contactDo) Clauses(conds ...clause.Expression) IContactDo {\n\treturn c.withDO(c.DO.Clauses(conds...))\n}\n\nfunc (c contactDo) Returning(value interface{}, columns ...string) IContactDo {\n\treturn c.withDO(c.DO.Returning(value, columns...))\n}\n\nfunc (c contactDo) Not(conds ...gen.Condition) IContactDo {\n\treturn c.withDO(c.DO.Not(conds...))\n}\n\nfunc (c contactDo) Or(conds ...gen.Condition) IContactDo {\n\treturn c.withDO(c.DO.Or(conds...))\n}\n\nfunc (c contactDo) Select(conds ...field.Expr) IContactDo {\n\treturn c.withDO(c.DO.Select(conds...))\n}\n\nfunc (c contactDo) Where(conds ...gen.Condition) IContactDo {\n\treturn c.withDO(c.DO.Where(conds...))\n}\n\nfunc (c contactDo) Order(conds ...field.Expr) IContactDo {\n\treturn c.withDO(c.DO.Order(conds...))\n}\n\nfunc (c contactDo) Distinct(cols ...field.Expr) IContactDo {\n\treturn c.withDO(c.DO.Distinct(cols...))\n}\n\nfunc (c contactDo) Omit(cols ...field.Expr) IContactDo {\n\treturn c.withDO(c.DO.Omit(cols...))\n}\n\nfunc (c contactDo) Join(table schema.Tabler, on ...field.Expr) IContactDo {\n\treturn c.withDO(c.DO.Join(table, on...))\n}\n\nfunc (c contactDo) LeftJoin(table schema.Tabler, on ...field.Expr) IContactDo {\n\treturn c.withDO(c.DO.LeftJoin(table, on...))\n}\n\nfunc (c contactDo) RightJoin(table schema.Tabler, on ...field.Expr) IContactDo {\n\treturn c.withDO(c.DO.RightJoin(table, on...))\n}\n\nfunc (c contactDo) Group(cols ...field.Expr) IContactDo {\n\treturn c.withDO(c.DO.Group(cols...))\n}\n\nfunc (c contactDo) Having(conds ...gen.Condition) IContactDo {\n\treturn c.withDO(c.DO.Having(conds...))\n}\n\nfunc (c contactDo) Limit(limit int) IContactDo {\n\treturn c.withDO(c.DO.Limit(limit))\n}\n\nfunc (c contactDo) Offset(offset int) IContactDo {\n\treturn c.withDO(c.DO.Offset(offset))\n}\n\nfunc (c contactDo) Scopes(funcs ...func(gen.Dao) gen.Dao) IContactDo {\n\treturn c.withDO(c.DO.Scopes(funcs...))\n}\n\nfunc (c contactDo) Unscoped() IContactDo {\n\treturn c.withDO(c.DO.Unscoped())\n}\n\nfunc (c contactDo) Create(values ...*model.Contact) error {\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn c.DO.Create(values)\n}\n\nfunc (c contactDo) CreateInBatches(values []*model.Contact, batchSize int) error {\n\treturn c.DO.CreateInBatches(values, batchSize)\n}\n\n// Save : !!! underlying implementation is different with GORM\n// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values)\nfunc (c contactDo) Save(values ...*model.Contact) error {\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn c.DO.Save(values)\n}\n\nfunc (c contactDo) First() (*model.Contact, error) {\n\tif result, err := c.DO.First(); err != nil {\n\t\treturn nil, err\n\t} else {\n\t\treturn result.(*model.Contact), nil\n\t}\n}\n\nfunc (c contactDo) Take() (*model.Contact, error) {\n\tif result, err := c.DO.Take(); err != nil {\n\t\treturn nil, err\n\t} else {\n\t\treturn result.(*model.Contact), nil\n\t}\n}\n\nfunc (c contactDo) Last() (*model.Contact, error) {\n\tif result, err := c.DO.Last(); err != nil {\n\t\treturn nil, err\n\t} else {\n\t\treturn result.(*model.Contact), nil\n\t}\n}\n\nfunc (c contactDo) Find() ([]*model.Contact, error) {\n\tresult, err := c.DO.Find()\n\treturn result.([]*model.Contact), err\n}\n\nfunc (c contactDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.Contact, err error) {\n\tbuf := make([]*model.Contact, 0, batchSize)\n\terr = c.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error {\n\t\tdefer func() { results = append(results, buf...) }()\n\t\treturn fc(tx, batch)\n\t})\n\treturn results, err\n}\n\nfunc (c contactDo) FindInBatches(result *[]*model.Contact, batchSize int, fc func(tx gen.Dao, batch int) error) error {\n\treturn c.DO.FindInBatches(result, batchSize, fc)\n}\n\nfunc (c contactDo) Attrs(attrs ...field.AssignExpr) IContactDo {\n\treturn c.withDO(c.DO.Attrs(attrs...))\n}\n\nfunc (c contactDo) Assign(attrs ...field.AssignExpr) IContactDo {\n\treturn c.withDO(c.DO.Assign(attrs...))\n}\n\nfunc (c contactDo) Joins(fields ...field.RelationField) IContactDo {\n\tfor _, _f := range fields {\n\t\tc = *c.withDO(c.DO.Joins(_f))\n\t}\n\treturn &c\n}\n\nfunc (c contactDo) Preload(fields ...field.RelationField) IContactDo {\n\tfor _, _f := range fields {\n\t\tc = *c.withDO(c.DO.Preload(_f))\n\t}\n\treturn &c\n}\n\nfunc (c contactDo) FirstOrInit() (*model.Contact, error) {\n\tif result, err := c.DO.FirstOrInit(); err != nil {\n\t\treturn nil, err\n\t} else {\n\t\treturn result.(*model.Contact), nil\n\t}\n}\n\nfunc (c contactDo) FirstOrCreate() (*model.Contact, error) {\n\tif result, err := c.DO.FirstOrCreate(); err != nil {\n\t\treturn nil, err\n\t} else {\n\t\treturn result.(*model.Contact), nil\n\t}\n}\n\nfunc (c contactDo) FindByPage(offset int, limit int) (result []*model.Contact, count int64, err error) {\n\tresult, err = c.Offset(offset).Limit(limit).Find()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif size := len(result); 0 < limit && 0 < size && size < limit {\n\t\tcount = int64(size + offset)\n\t\treturn\n\t}\n\n\tcount, err = c.Offset(-1).Limit(-1).Count()\n\treturn\n}\n\nfunc (c contactDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) {\n\tcount, err = c.Count()\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.Offset(offset).Limit(limit).Scan(result)\n\treturn\n}\n\nfunc (c contactDo) Scan(result interface{}) (err error) {\n\treturn c.DO.Scan(result)\n}\n\nfunc (c contactDo) Delete(models ...*model.Contact) (result gen.ResultInfo, err error) {\n\treturn c.DO.Delete(models)\n}\n\nfunc (c *contactDo) withDO(do gen.Dao) *contactDo {\n\tc.DO = *do.(*gen.DO)\n\treturn c\n}\n"
  },
  {
    "path": "dal/query/gen.go",
    "content": "// Code generated by gorm.io/gen. DO NOT EDIT.\n// Code generated by gorm.io/gen. DO NOT EDIT.\n// Code generated by gorm.io/gen. DO NOT EDIT.\n\npackage query\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\n\t\"gorm.io/gorm\"\n\n\t\"gorm.io/gen\"\n\n\t\"gorm.io/plugin/dbresolver\"\n)\n\nvar (\n\tQ    = new(Query)\n\tUser *user\n)\n\nfunc SetDefault(db *gorm.DB, opts ...gen.DOOption) {\n\t*Q = *Use(db, opts...)\n\tUser = &Q.User\n}\n\nfunc Use(db *gorm.DB, opts ...gen.DOOption) *Query {\n\treturn &Query{\n\t\tdb:   db,\n\t\tUser: newUser(db, opts...),\n\t}\n}\n\ntype Query struct {\n\tdb *gorm.DB\n\n\tUser user\n}\n\nfunc (q *Query) Available() bool { return q.db != nil }\n\nfunc (q *Query) clone(db *gorm.DB) *Query {\n\treturn &Query{\n\t\tdb:   db,\n\t\tUser: q.User.clone(db),\n\t}\n}\n\nfunc (q *Query) ReadDB() *Query {\n\treturn q.ReplaceDB(q.db.Clauses(dbresolver.Read))\n}\n\nfunc (q *Query) WriteDB() *Query {\n\treturn q.ReplaceDB(q.db.Clauses(dbresolver.Write))\n}\n\nfunc (q *Query) ReplaceDB(db *gorm.DB) *Query {\n\treturn &Query{\n\t\tdb:   db,\n\t\tUser: q.User.replaceDB(db),\n\t}\n}\n\ntype queryCtx struct {\n\tUser IUserDo\n}\n\nfunc (q *Query) WithContext(ctx context.Context) *queryCtx {\n\treturn &queryCtx{\n\t\tUser: q.User.WithContext(ctx),\n\t}\n}\n\nfunc (q *Query) Transaction(fc func(tx *Query) error, opts ...*sql.TxOptions) error {\n\treturn q.db.Transaction(func(tx *gorm.DB) error { return fc(q.clone(tx)) }, opts...)\n}\n\nfunc (q *Query) Begin(opts ...*sql.TxOptions) *QueryTx {\n\ttx := q.db.Begin(opts...)\n\treturn &QueryTx{Query: q.clone(tx), Error: tx.Error}\n}\n\ntype QueryTx struct {\n\t*Query\n\tError error\n}\n\nfunc (q *QueryTx) Commit() error {\n\treturn q.db.Commit().Error\n}\n\nfunc (q *QueryTx) Rollback() error {\n\treturn q.db.Rollback().Error\n}\n\nfunc (q *QueryTx) SavePoint(name string) error {\n\treturn q.db.SavePoint(name).Error\n}\n\nfunc (q *QueryTx) RollbackTo(name string) error {\n\treturn q.db.RollbackTo(name).Error\n}\n"
  },
  {
    "path": "dal/query/group_member.gen.go",
    "content": "// Code generated by gorm.io/gen. DO NOT EDIT.\n// Code generated by gorm.io/gen. DO NOT EDIT.\n// Code generated by gorm.io/gen. DO NOT EDIT.\n\npackage query\n\nimport (\n\t\"context\"\n\n\t\"gorm.io/gorm\"\n\t\"gorm.io/gorm/clause\"\n\t\"gorm.io/gorm/schema\"\n\n\t\"gorm.io/gen\"\n\t\"gorm.io/gen/field\"\n\n\t\"gorm.io/plugin/dbresolver\"\n\n\t\"DiTing-Go/dal/model\"\n)\n\nfunc newGroupMember(db *gorm.DB, opts ...gen.DOOption) groupMember {\n\t_groupMember := groupMember{}\n\n\t_groupMember.groupMemberDo.UseDB(db, opts...)\n\t_groupMember.groupMemberDo.UseModel(&model.GroupMember{})\n\n\ttableName := _groupMember.groupMemberDo.TableName()\n\t_groupMember.ALL = field.NewAsterisk(tableName)\n\t_groupMember.ID = field.NewInt64(tableName, \"id\")\n\t_groupMember.GroupID = field.NewInt64(tableName, \"group_id\")\n\t_groupMember.UID = field.NewInt64(tableName, \"uid\")\n\t_groupMember.Role = field.NewInt32(tableName, \"role\")\n\t_groupMember.CreateTime = field.NewTime(tableName, \"create_time\")\n\t_groupMember.UpdateTime = field.NewTime(tableName, \"update_time\")\n\n\t_groupMember.fillFieldMap()\n\n\treturn _groupMember\n}\n\n// groupMember 群成员表\ntype groupMember struct {\n\tgroupMemberDo groupMemberDo\n\n\tALL        field.Asterisk\n\tID         field.Int64 // id\n\tGroupID    field.Int64 // 群主id\n\tUID        field.Int64 // 成员uid\n\tRole       field.Int32 // 成员角色 1群主 2管理员 3普通成员\n\tCreateTime field.Time  // 创建时间\n\tUpdateTime field.Time  // 修改时间\n\n\tfieldMap map[string]field.Expr\n}\n\nfunc (g groupMember) Table(newTableName string) *groupMember {\n\tg.groupMemberDo.UseTable(newTableName)\n\treturn g.updateTableName(newTableName)\n}\n\nfunc (g groupMember) As(alias string) *groupMember {\n\tg.groupMemberDo.DO = *(g.groupMemberDo.As(alias).(*gen.DO))\n\treturn g.updateTableName(alias)\n}\n\nfunc (g *groupMember) updateTableName(table string) *groupMember {\n\tg.ALL = field.NewAsterisk(table)\n\tg.ID = field.NewInt64(table, \"id\")\n\tg.GroupID = field.NewInt64(table, \"group_id\")\n\tg.UID = field.NewInt64(table, \"uid\")\n\tg.Role = field.NewInt32(table, \"role\")\n\tg.CreateTime = field.NewTime(table, \"create_time\")\n\tg.UpdateTime = field.NewTime(table, \"update_time\")\n\n\tg.fillFieldMap()\n\n\treturn g\n}\n\nfunc (g *groupMember) WithContext(ctx context.Context) IGroupMemberDo {\n\treturn g.groupMemberDo.WithContext(ctx)\n}\n\nfunc (g groupMember) TableName() string { return g.groupMemberDo.TableName() }\n\nfunc (g groupMember) Alias() string { return g.groupMemberDo.Alias() }\n\nfunc (g groupMember) Columns(cols ...field.Expr) gen.Columns { return g.groupMemberDo.Columns(cols...) }\n\nfunc (g *groupMember) GetFieldByName(fieldName string) (field.OrderExpr, bool) {\n\t_f, ok := g.fieldMap[fieldName]\n\tif !ok || _f == nil {\n\t\treturn nil, false\n\t}\n\t_oe, ok := _f.(field.OrderExpr)\n\treturn _oe, ok\n}\n\nfunc (g *groupMember) fillFieldMap() {\n\tg.fieldMap = make(map[string]field.Expr, 6)\n\tg.fieldMap[\"id\"] = g.ID\n\tg.fieldMap[\"group_id\"] = g.GroupID\n\tg.fieldMap[\"uid\"] = g.UID\n\tg.fieldMap[\"role\"] = g.Role\n\tg.fieldMap[\"create_time\"] = g.CreateTime\n\tg.fieldMap[\"update_time\"] = g.UpdateTime\n}\n\nfunc (g groupMember) clone(db *gorm.DB) groupMember {\n\tg.groupMemberDo.ReplaceConnPool(db.Statement.ConnPool)\n\treturn g\n}\n\nfunc (g groupMember) replaceDB(db *gorm.DB) groupMember {\n\tg.groupMemberDo.ReplaceDB(db)\n\treturn g\n}\n\ntype groupMemberDo struct{ gen.DO }\n\ntype IGroupMemberDo interface {\n\tgen.SubQuery\n\tDebug() IGroupMemberDo\n\tWithContext(ctx context.Context) IGroupMemberDo\n\tWithResult(fc func(tx gen.Dao)) gen.ResultInfo\n\tReplaceDB(db *gorm.DB)\n\tReadDB() IGroupMemberDo\n\tWriteDB() IGroupMemberDo\n\tAs(alias string) gen.Dao\n\tSession(config *gorm.Session) IGroupMemberDo\n\tColumns(cols ...field.Expr) gen.Columns\n\tClauses(conds ...clause.Expression) IGroupMemberDo\n\tNot(conds ...gen.Condition) IGroupMemberDo\n\tOr(conds ...gen.Condition) IGroupMemberDo\n\tSelect(conds ...field.Expr) IGroupMemberDo\n\tWhere(conds ...gen.Condition) IGroupMemberDo\n\tOrder(conds ...field.Expr) IGroupMemberDo\n\tDistinct(cols ...field.Expr) IGroupMemberDo\n\tOmit(cols ...field.Expr) IGroupMemberDo\n\tJoin(table schema.Tabler, on ...field.Expr) IGroupMemberDo\n\tLeftJoin(table schema.Tabler, on ...field.Expr) IGroupMemberDo\n\tRightJoin(table schema.Tabler, on ...field.Expr) IGroupMemberDo\n\tGroup(cols ...field.Expr) IGroupMemberDo\n\tHaving(conds ...gen.Condition) IGroupMemberDo\n\tLimit(limit int) IGroupMemberDo\n\tOffset(offset int) IGroupMemberDo\n\tCount() (count int64, err error)\n\tScopes(funcs ...func(gen.Dao) gen.Dao) IGroupMemberDo\n\tUnscoped() IGroupMemberDo\n\tCreate(values ...*model.GroupMember) error\n\tCreateInBatches(values []*model.GroupMember, batchSize int) error\n\tSave(values ...*model.GroupMember) error\n\tFirst() (*model.GroupMember, error)\n\tTake() (*model.GroupMember, error)\n\tLast() (*model.GroupMember, error)\n\tFind() ([]*model.GroupMember, error)\n\tFindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.GroupMember, err error)\n\tFindInBatches(result *[]*model.GroupMember, batchSize int, fc func(tx gen.Dao, batch int) error) error\n\tPluck(column field.Expr, dest interface{}) error\n\tDelete(...*model.GroupMember) (info gen.ResultInfo, err error)\n\tUpdate(column field.Expr, value interface{}) (info gen.ResultInfo, err error)\n\tUpdateSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error)\n\tUpdates(value interface{}) (info gen.ResultInfo, err error)\n\tUpdateColumn(column field.Expr, value interface{}) (info gen.ResultInfo, err error)\n\tUpdateColumnSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error)\n\tUpdateColumns(value interface{}) (info gen.ResultInfo, err error)\n\tUpdateFrom(q gen.SubQuery) gen.Dao\n\tAttrs(attrs ...field.AssignExpr) IGroupMemberDo\n\tAssign(attrs ...field.AssignExpr) IGroupMemberDo\n\tJoins(fields ...field.RelationField) IGroupMemberDo\n\tPreload(fields ...field.RelationField) IGroupMemberDo\n\tFirstOrInit() (*model.GroupMember, error)\n\tFirstOrCreate() (*model.GroupMember, error)\n\tFindByPage(offset int, limit int) (result []*model.GroupMember, count int64, err error)\n\tScanByPage(result interface{}, offset int, limit int) (count int64, err error)\n\tScan(result interface{}) (err error)\n\tReturning(value interface{}, columns ...string) IGroupMemberDo\n\tUnderlyingDB() *gorm.DB\n\tschema.Tabler\n}\n\nfunc (g groupMemberDo) Debug() IGroupMemberDo {\n\treturn g.withDO(g.DO.Debug())\n}\n\nfunc (g groupMemberDo) WithContext(ctx context.Context) IGroupMemberDo {\n\treturn g.withDO(g.DO.WithContext(ctx))\n}\n\nfunc (g groupMemberDo) ReadDB() IGroupMemberDo {\n\treturn g.Clauses(dbresolver.Read)\n}\n\nfunc (g groupMemberDo) WriteDB() IGroupMemberDo {\n\treturn g.Clauses(dbresolver.Write)\n}\n\nfunc (g groupMemberDo) Session(config *gorm.Session) IGroupMemberDo {\n\treturn g.withDO(g.DO.Session(config))\n}\n\nfunc (g groupMemberDo) Clauses(conds ...clause.Expression) IGroupMemberDo {\n\treturn g.withDO(g.DO.Clauses(conds...))\n}\n\nfunc (g groupMemberDo) Returning(value interface{}, columns ...string) IGroupMemberDo {\n\treturn g.withDO(g.DO.Returning(value, columns...))\n}\n\nfunc (g groupMemberDo) Not(conds ...gen.Condition) IGroupMemberDo {\n\treturn g.withDO(g.DO.Not(conds...))\n}\n\nfunc (g groupMemberDo) Or(conds ...gen.Condition) IGroupMemberDo {\n\treturn g.withDO(g.DO.Or(conds...))\n}\n\nfunc (g groupMemberDo) Select(conds ...field.Expr) IGroupMemberDo {\n\treturn g.withDO(g.DO.Select(conds...))\n}\n\nfunc (g groupMemberDo) Where(conds ...gen.Condition) IGroupMemberDo {\n\treturn g.withDO(g.DO.Where(conds...))\n}\n\nfunc (g groupMemberDo) Order(conds ...field.Expr) IGroupMemberDo {\n\treturn g.withDO(g.DO.Order(conds...))\n}\n\nfunc (g groupMemberDo) Distinct(cols ...field.Expr) IGroupMemberDo {\n\treturn g.withDO(g.DO.Distinct(cols...))\n}\n\nfunc (g groupMemberDo) Omit(cols ...field.Expr) IGroupMemberDo {\n\treturn g.withDO(g.DO.Omit(cols...))\n}\n\nfunc (g groupMemberDo) Join(table schema.Tabler, on ...field.Expr) IGroupMemberDo {\n\treturn g.withDO(g.DO.Join(table, on...))\n}\n\nfunc (g groupMemberDo) LeftJoin(table schema.Tabler, on ...field.Expr) IGroupMemberDo {\n\treturn g.withDO(g.DO.LeftJoin(table, on...))\n}\n\nfunc (g groupMemberDo) RightJoin(table schema.Tabler, on ...field.Expr) IGroupMemberDo {\n\treturn g.withDO(g.DO.RightJoin(table, on...))\n}\n\nfunc (g groupMemberDo) Group(cols ...field.Expr) IGroupMemberDo {\n\treturn g.withDO(g.DO.Group(cols...))\n}\n\nfunc (g groupMemberDo) Having(conds ...gen.Condition) IGroupMemberDo {\n\treturn g.withDO(g.DO.Having(conds...))\n}\n\nfunc (g groupMemberDo) Limit(limit int) IGroupMemberDo {\n\treturn g.withDO(g.DO.Limit(limit))\n}\n\nfunc (g groupMemberDo) Offset(offset int) IGroupMemberDo {\n\treturn g.withDO(g.DO.Offset(offset))\n}\n\nfunc (g groupMemberDo) Scopes(funcs ...func(gen.Dao) gen.Dao) IGroupMemberDo {\n\treturn g.withDO(g.DO.Scopes(funcs...))\n}\n\nfunc (g groupMemberDo) Unscoped() IGroupMemberDo {\n\treturn g.withDO(g.DO.Unscoped())\n}\n\nfunc (g groupMemberDo) Create(values ...*model.GroupMember) error {\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn g.DO.Create(values)\n}\n\nfunc (g groupMemberDo) CreateInBatches(values []*model.GroupMember, batchSize int) error {\n\treturn g.DO.CreateInBatches(values, batchSize)\n}\n\n// Save : !!! underlying implementation is different with GORM\n// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values)\nfunc (g groupMemberDo) Save(values ...*model.GroupMember) error {\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn g.DO.Save(values)\n}\n\nfunc (g groupMemberDo) First() (*model.GroupMember, error) {\n\tif result, err := g.DO.First(); err != nil {\n\t\treturn nil, err\n\t} else {\n\t\treturn result.(*model.GroupMember), nil\n\t}\n}\n\nfunc (g groupMemberDo) Take() (*model.GroupMember, error) {\n\tif result, err := g.DO.Take(); err != nil {\n\t\treturn nil, err\n\t} else {\n\t\treturn result.(*model.GroupMember), nil\n\t}\n}\n\nfunc (g groupMemberDo) Last() (*model.GroupMember, error) {\n\tif result, err := g.DO.Last(); err != nil {\n\t\treturn nil, err\n\t} else {\n\t\treturn result.(*model.GroupMember), nil\n\t}\n}\n\nfunc (g groupMemberDo) Find() ([]*model.GroupMember, error) {\n\tresult, err := g.DO.Find()\n\treturn result.([]*model.GroupMember), err\n}\n\nfunc (g groupMemberDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.GroupMember, err error) {\n\tbuf := make([]*model.GroupMember, 0, batchSize)\n\terr = g.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error {\n\t\tdefer func() { results = append(results, buf...) }()\n\t\treturn fc(tx, batch)\n\t})\n\treturn results, err\n}\n\nfunc (g groupMemberDo) FindInBatches(result *[]*model.GroupMember, batchSize int, fc func(tx gen.Dao, batch int) error) error {\n\treturn g.DO.FindInBatches(result, batchSize, fc)\n}\n\nfunc (g groupMemberDo) Attrs(attrs ...field.AssignExpr) IGroupMemberDo {\n\treturn g.withDO(g.DO.Attrs(attrs...))\n}\n\nfunc (g groupMemberDo) Assign(attrs ...field.AssignExpr) IGroupMemberDo {\n\treturn g.withDO(g.DO.Assign(attrs...))\n}\n\nfunc (g groupMemberDo) Joins(fields ...field.RelationField) IGroupMemberDo {\n\tfor _, _f := range fields {\n\t\tg = *g.withDO(g.DO.Joins(_f))\n\t}\n\treturn &g\n}\n\nfunc (g groupMemberDo) Preload(fields ...field.RelationField) IGroupMemberDo {\n\tfor _, _f := range fields {\n\t\tg = *g.withDO(g.DO.Preload(_f))\n\t}\n\treturn &g\n}\n\nfunc (g groupMemberDo) FirstOrInit() (*model.GroupMember, error) {\n\tif result, err := g.DO.FirstOrInit(); err != nil {\n\t\treturn nil, err\n\t} else {\n\t\treturn result.(*model.GroupMember), nil\n\t}\n}\n\nfunc (g groupMemberDo) FirstOrCreate() (*model.GroupMember, error) {\n\tif result, err := g.DO.FirstOrCreate(); err != nil {\n\t\treturn nil, err\n\t} else {\n\t\treturn result.(*model.GroupMember), nil\n\t}\n}\n\nfunc (g groupMemberDo) FindByPage(offset int, limit int) (result []*model.GroupMember, count int64, err error) {\n\tresult, err = g.Offset(offset).Limit(limit).Find()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif size := len(result); 0 < limit && 0 < size && size < limit {\n\t\tcount = int64(size + offset)\n\t\treturn\n\t}\n\n\tcount, err = g.Offset(-1).Limit(-1).Count()\n\treturn\n}\n\nfunc (g groupMemberDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) {\n\tcount, err = g.Count()\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = g.Offset(offset).Limit(limit).Scan(result)\n\treturn\n}\n\nfunc (g groupMemberDo) Scan(result interface{}) (err error) {\n\treturn g.DO.Scan(result)\n}\n\nfunc (g groupMemberDo) Delete(models ...*model.GroupMember) (result gen.ResultInfo, err error) {\n\treturn g.DO.Delete(models)\n}\n\nfunc (g *groupMemberDo) withDO(do gen.Dao) *groupMemberDo {\n\tg.DO = *do.(*gen.DO)\n\treturn g\n}\n"
  },
  {
    "path": "dal/query/message.gen.go",
    "content": "// Code generated by gorm.io/gen. DO NOT EDIT.\n// Code generated by gorm.io/gen. DO NOT EDIT.\n// Code generated by gorm.io/gen. DO NOT EDIT.\n\npackage query\n\nimport (\n\t\"context\"\n\n\t\"gorm.io/gorm\"\n\t\"gorm.io/gorm/clause\"\n\t\"gorm.io/gorm/schema\"\n\n\t\"gorm.io/gen\"\n\t\"gorm.io/gen/field\"\n\n\t\"gorm.io/plugin/dbresolver\"\n\n\t\"DiTing-Go/dal/model\"\n)\n\nfunc newMessage(db *gorm.DB, opts ...gen.DOOption) message {\n\t_message := message{}\n\n\t_message.messageDo.UseDB(db, opts...)\n\t_message.messageDo.UseModel(&model.Message{})\n\n\ttableName := _message.messageDo.TableName()\n\t_message.ALL = field.NewAsterisk(tableName)\n\t_message.ID = field.NewInt64(tableName, \"id\")\n\t_message.RoomID = field.NewInt64(tableName, \"room_id\")\n\t_message.FromUID = field.NewInt64(tableName, \"from_uid\")\n\t_message.Content = field.NewString(tableName, \"content\")\n\t_message.ReplyMsgID = field.NewInt64(tableName, \"reply_msg_id\")\n\t_message.DeleteStatus = field.NewInt32(tableName, \"delete_status\")\n\t_message.GapCount = field.NewInt32(tableName, \"gap_count\")\n\t_message.Type = field.NewInt32(tableName, \"type\")\n\t_message.Extra = field.NewString(tableName, \"extra\")\n\t_message.CreateTime = field.NewTime(tableName, \"create_time\")\n\t_message.UpdateTime = field.NewTime(tableName, \"update_time\")\n\n\t_message.fillFieldMap()\n\n\treturn _message\n}\n\n// message 消息表\ntype message struct {\n\tmessageDo messageDo\n\n\tALL          field.Asterisk\n\tID           field.Int64  // id\n\tRoomID       field.Int64  // 会话表id\n\tFromUID      field.Int64  // 消息发送者uid\n\tContent      field.String // 消息内容\n\tReplyMsgID   field.Int64  // 回复的消息内容\n\tDeleteStatus field.Int32  // 消息状态 0正常 1删除\n\tGapCount     field.Int32  // 与回复的消息间隔多少条\n\tType         field.Int32  // 消息类型 1正常文本 2.撤回消息\n\tExtra        field.String // 扩展信息\n\tCreateTime   field.Time   // 创建时间\n\tUpdateTime   field.Time   // 修改时间\n\n\tfieldMap map[string]field.Expr\n}\n\nfunc (m message) Table(newTableName string) *message {\n\tm.messageDo.UseTable(newTableName)\n\treturn m.updateTableName(newTableName)\n}\n\nfunc (m message) As(alias string) *message {\n\tm.messageDo.DO = *(m.messageDo.As(alias).(*gen.DO))\n\treturn m.updateTableName(alias)\n}\n\nfunc (m *message) updateTableName(table string) *message {\n\tm.ALL = field.NewAsterisk(table)\n\tm.ID = field.NewInt64(table, \"id\")\n\tm.RoomID = field.NewInt64(table, \"room_id\")\n\tm.FromUID = field.NewInt64(table, \"from_uid\")\n\tm.Content = field.NewString(table, \"content\")\n\tm.ReplyMsgID = field.NewInt64(table, \"reply_msg_id\")\n\tm.DeleteStatus = field.NewInt32(table, \"delete_status\")\n\tm.GapCount = field.NewInt32(table, \"gap_count\")\n\tm.Type = field.NewInt32(table, \"type\")\n\tm.Extra = field.NewString(table, \"extra\")\n\tm.CreateTime = field.NewTime(table, \"create_time\")\n\tm.UpdateTime = field.NewTime(table, \"update_time\")\n\n\tm.fillFieldMap()\n\n\treturn m\n}\n\nfunc (m *message) WithContext(ctx context.Context) IMessageDo { return m.messageDo.WithContext(ctx) }\n\nfunc (m message) TableName() string { return m.messageDo.TableName() }\n\nfunc (m message) Alias() string { return m.messageDo.Alias() }\n\nfunc (m message) Columns(cols ...field.Expr) gen.Columns { return m.messageDo.Columns(cols...) }\n\nfunc (m *message) GetFieldByName(fieldName string) (field.OrderExpr, bool) {\n\t_f, ok := m.fieldMap[fieldName]\n\tif !ok || _f == nil {\n\t\treturn nil, false\n\t}\n\t_oe, ok := _f.(field.OrderExpr)\n\treturn _oe, ok\n}\n\nfunc (m *message) fillFieldMap() {\n\tm.fieldMap = make(map[string]field.Expr, 11)\n\tm.fieldMap[\"id\"] = m.ID\n\tm.fieldMap[\"room_id\"] = m.RoomID\n\tm.fieldMap[\"from_uid\"] = m.FromUID\n\tm.fieldMap[\"content\"] = m.Content\n\tm.fieldMap[\"reply_msg_id\"] = m.ReplyMsgID\n\tm.fieldMap[\"delete_status\"] = m.DeleteStatus\n\tm.fieldMap[\"gap_count\"] = m.GapCount\n\tm.fieldMap[\"type\"] = m.Type\n\tm.fieldMap[\"extra\"] = m.Extra\n\tm.fieldMap[\"create_time\"] = m.CreateTime\n\tm.fieldMap[\"update_time\"] = m.UpdateTime\n}\n\nfunc (m message) clone(db *gorm.DB) message {\n\tm.messageDo.ReplaceConnPool(db.Statement.ConnPool)\n\treturn m\n}\n\nfunc (m message) replaceDB(db *gorm.DB) message {\n\tm.messageDo.ReplaceDB(db)\n\treturn m\n}\n\ntype messageDo struct{ gen.DO }\n\ntype IMessageDo interface {\n\tgen.SubQuery\n\tDebug() IMessageDo\n\tWithContext(ctx context.Context) IMessageDo\n\tWithResult(fc func(tx gen.Dao)) gen.ResultInfo\n\tReplaceDB(db *gorm.DB)\n\tReadDB() IMessageDo\n\tWriteDB() IMessageDo\n\tAs(alias string) gen.Dao\n\tSession(config *gorm.Session) IMessageDo\n\tColumns(cols ...field.Expr) gen.Columns\n\tClauses(conds ...clause.Expression) IMessageDo\n\tNot(conds ...gen.Condition) IMessageDo\n\tOr(conds ...gen.Condition) IMessageDo\n\tSelect(conds ...field.Expr) IMessageDo\n\tWhere(conds ...gen.Condition) IMessageDo\n\tOrder(conds ...field.Expr) IMessageDo\n\tDistinct(cols ...field.Expr) IMessageDo\n\tOmit(cols ...field.Expr) IMessageDo\n\tJoin(table schema.Tabler, on ...field.Expr) IMessageDo\n\tLeftJoin(table schema.Tabler, on ...field.Expr) IMessageDo\n\tRightJoin(table schema.Tabler, on ...field.Expr) IMessageDo\n\tGroup(cols ...field.Expr) IMessageDo\n\tHaving(conds ...gen.Condition) IMessageDo\n\tLimit(limit int) IMessageDo\n\tOffset(offset int) IMessageDo\n\tCount() (count int64, err error)\n\tScopes(funcs ...func(gen.Dao) gen.Dao) IMessageDo\n\tUnscoped() IMessageDo\n\tCreate(values ...*model.Message) error\n\tCreateInBatches(values []*model.Message, batchSize int) error\n\tSave(values ...*model.Message) error\n\tFirst() (*model.Message, error)\n\tTake() (*model.Message, error)\n\tLast() (*model.Message, error)\n\tFind() ([]*model.Message, error)\n\tFindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.Message, err error)\n\tFindInBatches(result *[]*model.Message, batchSize int, fc func(tx gen.Dao, batch int) error) error\n\tPluck(column field.Expr, dest interface{}) error\n\tDelete(...*model.Message) (info gen.ResultInfo, err error)\n\tUpdate(column field.Expr, value interface{}) (info gen.ResultInfo, err error)\n\tUpdateSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error)\n\tUpdates(value interface{}) (info gen.ResultInfo, err error)\n\tUpdateColumn(column field.Expr, value interface{}) (info gen.ResultInfo, err error)\n\tUpdateColumnSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error)\n\tUpdateColumns(value interface{}) (info gen.ResultInfo, err error)\n\tUpdateFrom(q gen.SubQuery) gen.Dao\n\tAttrs(attrs ...field.AssignExpr) IMessageDo\n\tAssign(attrs ...field.AssignExpr) IMessageDo\n\tJoins(fields ...field.RelationField) IMessageDo\n\tPreload(fields ...field.RelationField) IMessageDo\n\tFirstOrInit() (*model.Message, error)\n\tFirstOrCreate() (*model.Message, error)\n\tFindByPage(offset int, limit int) (result []*model.Message, count int64, err error)\n\tScanByPage(result interface{}, offset int, limit int) (count int64, err error)\n\tScan(result interface{}) (err error)\n\tReturning(value interface{}, columns ...string) IMessageDo\n\tUnderlyingDB() *gorm.DB\n\tschema.Tabler\n}\n\nfunc (m messageDo) Debug() IMessageDo {\n\treturn m.withDO(m.DO.Debug())\n}\n\nfunc (m messageDo) WithContext(ctx context.Context) IMessageDo {\n\treturn m.withDO(m.DO.WithContext(ctx))\n}\n\nfunc (m messageDo) ReadDB() IMessageDo {\n\treturn m.Clauses(dbresolver.Read)\n}\n\nfunc (m messageDo) WriteDB() IMessageDo {\n\treturn m.Clauses(dbresolver.Write)\n}\n\nfunc (m messageDo) Session(config *gorm.Session) IMessageDo {\n\treturn m.withDO(m.DO.Session(config))\n}\n\nfunc (m messageDo) Clauses(conds ...clause.Expression) IMessageDo {\n\treturn m.withDO(m.DO.Clauses(conds...))\n}\n\nfunc (m messageDo) Returning(value interface{}, columns ...string) IMessageDo {\n\treturn m.withDO(m.DO.Returning(value, columns...))\n}\n\nfunc (m messageDo) Not(conds ...gen.Condition) IMessageDo {\n\treturn m.withDO(m.DO.Not(conds...))\n}\n\nfunc (m messageDo) Or(conds ...gen.Condition) IMessageDo {\n\treturn m.withDO(m.DO.Or(conds...))\n}\n\nfunc (m messageDo) Select(conds ...field.Expr) IMessageDo {\n\treturn m.withDO(m.DO.Select(conds...))\n}\n\nfunc (m messageDo) Where(conds ...gen.Condition) IMessageDo {\n\treturn m.withDO(m.DO.Where(conds...))\n}\n\nfunc (m messageDo) Order(conds ...field.Expr) IMessageDo {\n\treturn m.withDO(m.DO.Order(conds...))\n}\n\nfunc (m messageDo) Distinct(cols ...field.Expr) IMessageDo {\n\treturn m.withDO(m.DO.Distinct(cols...))\n}\n\nfunc (m messageDo) Omit(cols ...field.Expr) IMessageDo {\n\treturn m.withDO(m.DO.Omit(cols...))\n}\n\nfunc (m messageDo) Join(table schema.Tabler, on ...field.Expr) IMessageDo {\n\treturn m.withDO(m.DO.Join(table, on...))\n}\n\nfunc (m messageDo) LeftJoin(table schema.Tabler, on ...field.Expr) IMessageDo {\n\treturn m.withDO(m.DO.LeftJoin(table, on...))\n}\n\nfunc (m messageDo) RightJoin(table schema.Tabler, on ...field.Expr) IMessageDo {\n\treturn m.withDO(m.DO.RightJoin(table, on...))\n}\n\nfunc (m messageDo) Group(cols ...field.Expr) IMessageDo {\n\treturn m.withDO(m.DO.Group(cols...))\n}\n\nfunc (m messageDo) Having(conds ...gen.Condition) IMessageDo {\n\treturn m.withDO(m.DO.Having(conds...))\n}\n\nfunc (m messageDo) Limit(limit int) IMessageDo {\n\treturn m.withDO(m.DO.Limit(limit))\n}\n\nfunc (m messageDo) Offset(offset int) IMessageDo {\n\treturn m.withDO(m.DO.Offset(offset))\n}\n\nfunc (m messageDo) Scopes(funcs ...func(gen.Dao) gen.Dao) IMessageDo {\n\treturn m.withDO(m.DO.Scopes(funcs...))\n}\n\nfunc (m messageDo) Unscoped() IMessageDo {\n\treturn m.withDO(m.DO.Unscoped())\n}\n\nfunc (m messageDo) Create(values ...*model.Message) error {\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn m.DO.Create(values)\n}\n\nfunc (m messageDo) CreateInBatches(values []*model.Message, batchSize int) error {\n\treturn m.DO.CreateInBatches(values, batchSize)\n}\n\n// Save : !!! underlying implementation is different with GORM\n// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values)\nfunc (m messageDo) Save(values ...*model.Message) error {\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn m.DO.Save(values)\n}\n\nfunc (m messageDo) First() (*model.Message, error) {\n\tif result, err := m.DO.First(); err != nil {\n\t\treturn nil, err\n\t} else {\n\t\treturn result.(*model.Message), nil\n\t}\n}\n\nfunc (m messageDo) Take() (*model.Message, error) {\n\tif result, err := m.DO.Take(); err != nil {\n\t\treturn nil, err\n\t} else {\n\t\treturn result.(*model.Message), nil\n\t}\n}\n\nfunc (m messageDo) Last() (*model.Message, error) {\n\tif result, err := m.DO.Last(); err != nil {\n\t\treturn nil, err\n\t} else {\n\t\treturn result.(*model.Message), nil\n\t}\n}\n\nfunc (m messageDo) Find() ([]*model.Message, error) {\n\tresult, err := m.DO.Find()\n\treturn result.([]*model.Message), err\n}\n\nfunc (m messageDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.Message, err error) {\n\tbuf := make([]*model.Message, 0, batchSize)\n\terr = m.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error {\n\t\tdefer func() { results = append(results, buf...) }()\n\t\treturn fc(tx, batch)\n\t})\n\treturn results, err\n}\n\nfunc (m messageDo) FindInBatches(result *[]*model.Message, batchSize int, fc func(tx gen.Dao, batch int) error) error {\n\treturn m.DO.FindInBatches(result, batchSize, fc)\n}\n\nfunc (m messageDo) Attrs(attrs ...field.AssignExpr) IMessageDo {\n\treturn m.withDO(m.DO.Attrs(attrs...))\n}\n\nfunc (m messageDo) Assign(attrs ...field.AssignExpr) IMessageDo {\n\treturn m.withDO(m.DO.Assign(attrs...))\n}\n\nfunc (m messageDo) Joins(fields ...field.RelationField) IMessageDo {\n\tfor _, _f := range fields {\n\t\tm = *m.withDO(m.DO.Joins(_f))\n\t}\n\treturn &m\n}\n\nfunc (m messageDo) Preload(fields ...field.RelationField) IMessageDo {\n\tfor _, _f := range fields {\n\t\tm = *m.withDO(m.DO.Preload(_f))\n\t}\n\treturn &m\n}\n\nfunc (m messageDo) FirstOrInit() (*model.Message, error) {\n\tif result, err := m.DO.FirstOrInit(); err != nil {\n\t\treturn nil, err\n\t} else {\n\t\treturn result.(*model.Message), nil\n\t}\n}\n\nfunc (m messageDo) FirstOrCreate() (*model.Message, error) {\n\tif result, err := m.DO.FirstOrCreate(); err != nil {\n\t\treturn nil, err\n\t} else {\n\t\treturn result.(*model.Message), nil\n\t}\n}\n\nfunc (m messageDo) FindByPage(offset int, limit int) (result []*model.Message, count int64, err error) {\n\tresult, err = m.Offset(offset).Limit(limit).Find()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif size := len(result); 0 < limit && 0 < size && size < limit {\n\t\tcount = int64(size + offset)\n\t\treturn\n\t}\n\n\tcount, err = m.Offset(-1).Limit(-1).Count()\n\treturn\n}\n\nfunc (m messageDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) {\n\tcount, err = m.Count()\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = m.Offset(offset).Limit(limit).Scan(result)\n\treturn\n}\n\nfunc (m messageDo) Scan(result interface{}) (err error) {\n\treturn m.DO.Scan(result)\n}\n\nfunc (m messageDo) Delete(models ...*model.Message) (result gen.ResultInfo, err error) {\n\treturn m.DO.Delete(models)\n}\n\nfunc (m *messageDo) withDO(do gen.Dao) *messageDo {\n\tm.DO = *do.(*gen.DO)\n\treturn m\n}\n"
  },
  {
    "path": "dal/query/room.gen.go",
    "content": "// Code generated by gorm.io/gen. DO NOT EDIT.\n// Code generated by gorm.io/gen. DO NOT EDIT.\n// Code generated by gorm.io/gen. DO NOT EDIT.\n\npackage query\n\nimport (\n\t\"context\"\n\n\t\"gorm.io/gorm\"\n\t\"gorm.io/gorm/clause\"\n\t\"gorm.io/gorm/schema\"\n\n\t\"gorm.io/gen\"\n\t\"gorm.io/gen/field\"\n\n\t\"gorm.io/plugin/dbresolver\"\n\n\t\"DiTing-Go/dal/model\"\n)\n\nfunc newRoom(db *gorm.DB, opts ...gen.DOOption) room {\n\t_room := room{}\n\n\t_room.roomDo.UseDB(db, opts...)\n\t_room.roomDo.UseModel(&model.Room{})\n\n\ttableName := _room.roomDo.TableName()\n\t_room.ALL = field.NewAsterisk(tableName)\n\t_room.ID = field.NewInt64(tableName, \"id\")\n\t_room.Type = field.NewInt32(tableName, \"type\")\n\t_room.HotFlag = field.NewInt32(tableName, \"hot_flag\")\n\t_room.ActiveTime = field.NewTime(tableName, \"active_time\")\n\t_room.LastMsgID = field.NewInt64(tableName, \"last_msg_id\")\n\t_room.ExtJSON = field.NewString(tableName, \"ext_json\")\n\t_room.CreateTime = field.NewTime(tableName, \"create_time\")\n\t_room.UpdateTime = field.NewTime(tableName, \"update_time\")\n\t_room.DeleteStatus = field.NewInt32(tableName, \"delete_status\")\n\n\t_room.fillFieldMap()\n\n\treturn _room\n}\n\n// room 房间表\ntype room struct {\n\troomDo roomDo\n\n\tALL          field.Asterisk\n\tID           field.Int64  // id\n\tType         field.Int32  // 房间类型 1群聊 2单聊\n\tHotFlag      field.Int32  // 是否全员展示 0否 1是\n\tActiveTime   field.Time   // 群最后消息的更新时间（热点群不需要写扩散，只更新这里）\n\tLastMsgID    field.Int64  // 会话中的最后一条消息id\n\tExtJSON      field.String // 额外信息（根据不同类型房间有不同存储的东西）\n\tCreateTime   field.Time   // 创建时间\n\tUpdateTime   field.Time   // 修改时间\n\tDeleteStatus field.Int32  // 房间状态 0正常 1禁用(删好友了禁用)\n\n\tfieldMap map[string]field.Expr\n}\n\nfunc (r room) Table(newTableName string) *room {\n\tr.roomDo.UseTable(newTableName)\n\treturn r.updateTableName(newTableName)\n}\n\nfunc (r room) As(alias string) *room {\n\tr.roomDo.DO = *(r.roomDo.As(alias).(*gen.DO))\n\treturn r.updateTableName(alias)\n}\n\nfunc (r *room) updateTableName(table string) *room {\n\tr.ALL = field.NewAsterisk(table)\n\tr.ID = field.NewInt64(table, \"id\")\n\tr.Type = field.NewInt32(table, \"type\")\n\tr.HotFlag = field.NewInt32(table, \"hot_flag\")\n\tr.ActiveTime = field.NewTime(table, \"active_time\")\n\tr.LastMsgID = field.NewInt64(table, \"last_msg_id\")\n\tr.ExtJSON = field.NewString(table, \"ext_json\")\n\tr.CreateTime = field.NewTime(table, \"create_time\")\n\tr.UpdateTime = field.NewTime(table, \"update_time\")\n\tr.DeleteStatus = field.NewInt32(table, \"delete_status\")\n\n\tr.fillFieldMap()\n\n\treturn r\n}\n\nfunc (r *room) WithContext(ctx context.Context) IRoomDo { return r.roomDo.WithContext(ctx) }\n\nfunc (r room) TableName() string { return r.roomDo.TableName() }\n\nfunc (r room) Alias() string { return r.roomDo.Alias() }\n\nfunc (r room) Columns(cols ...field.Expr) gen.Columns { return r.roomDo.Columns(cols...) }\n\nfunc (r *room) GetFieldByName(fieldName string) (field.OrderExpr, bool) {\n\t_f, ok := r.fieldMap[fieldName]\n\tif !ok || _f == nil {\n\t\treturn nil, false\n\t}\n\t_oe, ok := _f.(field.OrderExpr)\n\treturn _oe, ok\n}\n\nfunc (r *room) fillFieldMap() {\n\tr.fieldMap = make(map[string]field.Expr, 9)\n\tr.fieldMap[\"id\"] = r.ID\n\tr.fieldMap[\"type\"] = r.Type\n\tr.fieldMap[\"hot_flag\"] = r.HotFlag\n\tr.fieldMap[\"active_time\"] = r.ActiveTime\n\tr.fieldMap[\"last_msg_id\"] = r.LastMsgID\n\tr.fieldMap[\"ext_json\"] = r.ExtJSON\n\tr.fieldMap[\"create_time\"] = r.CreateTime\n\tr.fieldMap[\"update_time\"] = r.UpdateTime\n\tr.fieldMap[\"delete_status\"] = r.DeleteStatus\n}\n\nfunc (r room) clone(db *gorm.DB) room {\n\tr.roomDo.ReplaceConnPool(db.Statement.ConnPool)\n\treturn r\n}\n\nfunc (r room) replaceDB(db *gorm.DB) room {\n\tr.roomDo.ReplaceDB(db)\n\treturn r\n}\n\ntype roomDo struct{ gen.DO }\n\ntype IRoomDo interface {\n\tgen.SubQuery\n\tDebug() IRoomDo\n\tWithContext(ctx context.Context) IRoomDo\n\tWithResult(fc func(tx gen.Dao)) gen.ResultInfo\n\tReplaceDB(db *gorm.DB)\n\tReadDB() IRoomDo\n\tWriteDB() IRoomDo\n\tAs(alias string) gen.Dao\n\tSession(config *gorm.Session) IRoomDo\n\tColumns(cols ...field.Expr) gen.Columns\n\tClauses(conds ...clause.Expression) IRoomDo\n\tNot(conds ...gen.Condition) IRoomDo\n\tOr(conds ...gen.Condition) IRoomDo\n\tSelect(conds ...field.Expr) IRoomDo\n\tWhere(conds ...gen.Condition) IRoomDo\n\tOrder(conds ...field.Expr) IRoomDo\n\tDistinct(cols ...field.Expr) IRoomDo\n\tOmit(cols ...field.Expr) IRoomDo\n\tJoin(table schema.Tabler, on ...field.Expr) IRoomDo\n\tLeftJoin(table schema.Tabler, on ...field.Expr) IRoomDo\n\tRightJoin(table schema.Tabler, on ...field.Expr) IRoomDo\n\tGroup(cols ...field.Expr) IRoomDo\n\tHaving(conds ...gen.Condition) IRoomDo\n\tLimit(limit int) IRoomDo\n\tOffset(offset int) IRoomDo\n\tCount() (count int64, err error)\n\tScopes(funcs ...func(gen.Dao) gen.Dao) IRoomDo\n\tUnscoped() IRoomDo\n\tCreate(values ...*model.Room) error\n\tCreateInBatches(values []*model.Room, batchSize int) error\n\tSave(values ...*model.Room) error\n\tFirst() (*model.Room, error)\n\tTake() (*model.Room, error)\n\tLast() (*model.Room, error)\n\tFind() ([]*model.Room, error)\n\tFindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.Room, err error)\n\tFindInBatches(result *[]*model.Room, batchSize int, fc func(tx gen.Dao, batch int) error) error\n\tPluck(column field.Expr, dest interface{}) error\n\tDelete(...*model.Room) (info gen.ResultInfo, err error)\n\tUpdate(column field.Expr, value interface{}) (info gen.ResultInfo, err error)\n\tUpdateSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error)\n\tUpdates(value interface{}) (info gen.ResultInfo, err error)\n\tUpdateColumn(column field.Expr, value interface{}) (info gen.ResultInfo, err error)\n\tUpdateColumnSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error)\n\tUpdateColumns(value interface{}) (info gen.ResultInfo, err error)\n\tUpdateFrom(q gen.SubQuery) gen.Dao\n\tAttrs(attrs ...field.AssignExpr) IRoomDo\n\tAssign(attrs ...field.AssignExpr) IRoomDo\n\tJoins(fields ...field.RelationField) IRoomDo\n\tPreload(fields ...field.RelationField) IRoomDo\n\tFirstOrInit() (*model.Room, error)\n\tFirstOrCreate() (*model.Room, error)\n\tFindByPage(offset int, limit int) (result []*model.Room, count int64, err error)\n\tScanByPage(result interface{}, offset int, limit int) (count int64, err error)\n\tScan(result interface{}) (err error)\n\tReturning(value interface{}, columns ...string) IRoomDo\n\tUnderlyingDB() *gorm.DB\n\tschema.Tabler\n}\n\nfunc (r roomDo) Debug() IRoomDo {\n\treturn r.withDO(r.DO.Debug())\n}\n\nfunc (r roomDo) WithContext(ctx context.Context) IRoomDo {\n\treturn r.withDO(r.DO.WithContext(ctx))\n}\n\nfunc (r roomDo) ReadDB() IRoomDo {\n\treturn r.Clauses(dbresolver.Read)\n}\n\nfunc (r roomDo) WriteDB() IRoomDo {\n\treturn r.Clauses(dbresolver.Write)\n}\n\nfunc (r roomDo) Session(config *gorm.Session) IRoomDo {\n\treturn r.withDO(r.DO.Session(config))\n}\n\nfunc (r roomDo) Clauses(conds ...clause.Expression) IRoomDo {\n\treturn r.withDO(r.DO.Clauses(conds...))\n}\n\nfunc (r roomDo) Returning(value interface{}, columns ...string) IRoomDo {\n\treturn r.withDO(r.DO.Returning(value, columns...))\n}\n\nfunc (r roomDo) Not(conds ...gen.Condition) IRoomDo {\n\treturn r.withDO(r.DO.Not(conds...))\n}\n\nfunc (r roomDo) Or(conds ...gen.Condition) IRoomDo {\n\treturn r.withDO(r.DO.Or(conds...))\n}\n\nfunc (r roomDo) Select(conds ...field.Expr) IRoomDo {\n\treturn r.withDO(r.DO.Select(conds...))\n}\n\nfunc (r roomDo) Where(conds ...gen.Condition) IRoomDo {\n\treturn r.withDO(r.DO.Where(conds...))\n}\n\nfunc (r roomDo) Order(conds ...field.Expr) IRoomDo {\n\treturn r.withDO(r.DO.Order(conds...))\n}\n\nfunc (r roomDo) Distinct(cols ...field.Expr) IRoomDo {\n\treturn r.withDO(r.DO.Distinct(cols...))\n}\n\nfunc (r roomDo) Omit(cols ...field.Expr) IRoomDo {\n\treturn r.withDO(r.DO.Omit(cols...))\n}\n\nfunc (r roomDo) Join(table schema.Tabler, on ...field.Expr) IRoomDo {\n\treturn r.withDO(r.DO.Join(table, on...))\n}\n\nfunc (r roomDo) LeftJoin(table schema.Tabler, on ...field.Expr) IRoomDo {\n\treturn r.withDO(r.DO.LeftJoin(table, on...))\n}\n\nfunc (r roomDo) RightJoin(table schema.Tabler, on ...field.Expr) IRoomDo {\n\treturn r.withDO(r.DO.RightJoin(table, on...))\n}\n\nfunc (r roomDo) Group(cols ...field.Expr) IRoomDo {\n\treturn r.withDO(r.DO.Group(cols...))\n}\n\nfunc (r roomDo) Having(conds ...gen.Condition) IRoomDo {\n\treturn r.withDO(r.DO.Having(conds...))\n}\n\nfunc (r roomDo) Limit(limit int) IRoomDo {\n\treturn r.withDO(r.DO.Limit(limit))\n}\n\nfunc (r roomDo) Offset(offset int) IRoomDo {\n\treturn r.withDO(r.DO.Offset(offset))\n}\n\nfunc (r roomDo) Scopes(funcs ...func(gen.Dao) gen.Dao) IRoomDo {\n\treturn r.withDO(r.DO.Scopes(funcs...))\n}\n\nfunc (r roomDo) Unscoped() IRoomDo {\n\treturn r.withDO(r.DO.Unscoped())\n}\n\nfunc (r roomDo) Create(values ...*model.Room) error {\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn r.DO.Create(values)\n}\n\nfunc (r roomDo) CreateInBatches(values []*model.Room, batchSize int) error {\n\treturn r.DO.CreateInBatches(values, batchSize)\n}\n\n// Save : !!! underlying implementation is different with GORM\n// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values)\nfunc (r roomDo) Save(values ...*model.Room) error {\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn r.DO.Save(values)\n}\n\nfunc (r roomDo) First() (*model.Room, error) {\n\tif result, err := r.DO.First(); err != nil {\n\t\treturn nil, err\n\t} else {\n\t\treturn result.(*model.Room), nil\n\t}\n}\n\nfunc (r roomDo) Take() (*model.Room, error) {\n\tif result, err := r.DO.Take(); err != nil {\n\t\treturn nil, err\n\t} else {\n\t\treturn result.(*model.Room), nil\n\t}\n}\n\nfunc (r roomDo) Last() (*model.Room, error) {\n\tif result, err := r.DO.Last(); err != nil {\n\t\treturn nil, err\n\t} else {\n\t\treturn result.(*model.Room), nil\n\t}\n}\n\nfunc (r roomDo) Find() ([]*model.Room, error) {\n\tresult, err := r.DO.Find()\n\treturn result.([]*model.Room), err\n}\n\nfunc (r roomDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.Room, err error) {\n\tbuf := make([]*model.Room, 0, batchSize)\n\terr = r.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error {\n\t\tdefer func() { results = append(results, buf...) }()\n\t\treturn fc(tx, batch)\n\t})\n\treturn results, err\n}\n\nfunc (r roomDo) FindInBatches(result *[]*model.Room, batchSize int, fc func(tx gen.Dao, batch int) error) error {\n\treturn r.DO.FindInBatches(result, batchSize, fc)\n}\n\nfunc (r roomDo) Attrs(attrs ...field.AssignExpr) IRoomDo {\n\treturn r.withDO(r.DO.Attrs(attrs...))\n}\n\nfunc (r roomDo) Assign(attrs ...field.AssignExpr) IRoomDo {\n\treturn r.withDO(r.DO.Assign(attrs...))\n}\n\nfunc (r roomDo) Joins(fields ...field.RelationField) IRoomDo {\n\tfor _, _f := range fields {\n\t\tr = *r.withDO(r.DO.Joins(_f))\n\t}\n\treturn &r\n}\n\nfunc (r roomDo) Preload(fields ...field.RelationField) IRoomDo {\n\tfor _, _f := range fields {\n\t\tr = *r.withDO(r.DO.Preload(_f))\n\t}\n\treturn &r\n}\n\nfunc (r roomDo) FirstOrInit() (*model.Room, error) {\n\tif result, err := r.DO.FirstOrInit(); err != nil {\n\t\treturn nil, err\n\t} else {\n\t\treturn result.(*model.Room), nil\n\t}\n}\n\nfunc (r roomDo) FirstOrCreate() (*model.Room, error) {\n\tif result, err := r.DO.FirstOrCreate(); err != nil {\n\t\treturn nil, err\n\t} else {\n\t\treturn result.(*model.Room), nil\n\t}\n}\n\nfunc (r roomDo) FindByPage(offset int, limit int) (result []*model.Room, count int64, err error) {\n\tresult, err = r.Offset(offset).Limit(limit).Find()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif size := len(result); 0 < limit && 0 < size && size < limit {\n\t\tcount = int64(size + offset)\n\t\treturn\n\t}\n\n\tcount, err = r.Offset(-1).Limit(-1).Count()\n\treturn\n}\n\nfunc (r roomDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) {\n\tcount, err = r.Count()\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = r.Offset(offset).Limit(limit).Scan(result)\n\treturn\n}\n\nfunc (r roomDo) Scan(result interface{}) (err error) {\n\treturn r.DO.Scan(result)\n}\n\nfunc (r roomDo) Delete(models ...*model.Room) (result gen.ResultInfo, err error) {\n\treturn r.DO.Delete(models)\n}\n\nfunc (r *roomDo) withDO(do gen.Dao) *roomDo {\n\tr.DO = *do.(*gen.DO)\n\treturn r\n}\n"
  },
  {
    "path": "dal/query/room_friend.gen.go",
    "content": "// Code generated by gorm.io/gen. DO NOT EDIT.\n// Code generated by gorm.io/gen. DO NOT EDIT.\n// Code generated by gorm.io/gen. DO NOT EDIT.\n\npackage query\n\nimport (\n\t\"context\"\n\n\t\"gorm.io/gorm\"\n\t\"gorm.io/gorm/clause\"\n\t\"gorm.io/gorm/schema\"\n\n\t\"gorm.io/gen\"\n\t\"gorm.io/gen/field\"\n\n\t\"gorm.io/plugin/dbresolver\"\n\n\t\"DiTing-Go/dal/model\"\n)\n\nfunc newRoomFriend(db *gorm.DB, opts ...gen.DOOption) roomFriend {\n\t_roomFriend := roomFriend{}\n\n\t_roomFriend.roomFriendDo.UseDB(db, opts...)\n\t_roomFriend.roomFriendDo.UseModel(&model.RoomFriend{})\n\n\ttableName := _roomFriend.roomFriendDo.TableName()\n\t_roomFriend.ALL = field.NewAsterisk(tableName)\n\t_roomFriend.ID = field.NewInt64(tableName, \"id\")\n\t_roomFriend.RoomID = field.NewInt64(tableName, \"room_id\")\n\t_roomFriend.Uid1 = field.NewInt64(tableName, \"uid1\")\n\t_roomFriend.Uid2 = field.NewInt64(tableName, \"uid2\")\n\t_roomFriend.RoomKey = field.NewString(tableName, \"room_key\")\n\t_roomFriend.DeleteStatus = field.NewInt32(tableName, \"delete_status\")\n\t_roomFriend.CreateTime = field.NewTime(tableName, \"create_time\")\n\t_roomFriend.UpdateTime = field.NewTime(tableName, \"update_time\")\n\n\t_roomFriend.fillFieldMap()\n\n\treturn _roomFriend\n}\n\n// roomFriend 单聊房间表\ntype roomFriend struct {\n\troomFriendDo roomFriendDo\n\n\tALL          field.Asterisk\n\tID           field.Int64  // id\n\tRoomID       field.Int64  // 房间id\n\tUid1         field.Int64  // uid1（更小的uid）\n\tUid2         field.Int64  // uid2（更大的uid）\n\tRoomKey      field.String // 房间key由两个uid拼接，先做排序uid1_uid2\n\tDeleteStatus field.Int32  // 房间状态 0正常 1禁用(删好友了禁用)\n\tCreateTime   field.Time   // 创建时间\n\tUpdateTime   field.Time   // 修改时间\n\n\tfieldMap map[string]field.Expr\n}\n\nfunc (r roomFriend) Table(newTableName string) *roomFriend {\n\tr.roomFriendDo.UseTable(newTableName)\n\treturn r.updateTableName(newTableName)\n}\n\nfunc (r roomFriend) As(alias string) *roomFriend {\n\tr.roomFriendDo.DO = *(r.roomFriendDo.As(alias).(*gen.DO))\n\treturn r.updateTableName(alias)\n}\n\nfunc (r *roomFriend) updateTableName(table string) *roomFriend {\n\tr.ALL = field.NewAsterisk(table)\n\tr.ID = field.NewInt64(table, \"id\")\n\tr.RoomID = field.NewInt64(table, \"room_id\")\n\tr.Uid1 = field.NewInt64(table, \"uid1\")\n\tr.Uid2 = field.NewInt64(table, \"uid2\")\n\tr.RoomKey = field.NewString(table, \"room_key\")\n\tr.DeleteStatus = field.NewInt32(table, \"delete_status\")\n\tr.CreateTime = field.NewTime(table, \"create_time\")\n\tr.UpdateTime = field.NewTime(table, \"update_time\")\n\n\tr.fillFieldMap()\n\n\treturn r\n}\n\nfunc (r *roomFriend) WithContext(ctx context.Context) IRoomFriendDo {\n\treturn r.roomFriendDo.WithContext(ctx)\n}\n\nfunc (r roomFriend) TableName() string { return r.roomFriendDo.TableName() }\n\nfunc (r roomFriend) Alias() string { return r.roomFriendDo.Alias() }\n\nfunc (r roomFriend) Columns(cols ...field.Expr) gen.Columns { return r.roomFriendDo.Columns(cols...) }\n\nfunc (r *roomFriend) GetFieldByName(fieldName string) (field.OrderExpr, bool) {\n\t_f, ok := r.fieldMap[fieldName]\n\tif !ok || _f == nil {\n\t\treturn nil, false\n\t}\n\t_oe, ok := _f.(field.OrderExpr)\n\treturn _oe, ok\n}\n\nfunc (r *roomFriend) fillFieldMap() {\n\tr.fieldMap = make(map[string]field.Expr, 8)\n\tr.fieldMap[\"id\"] = r.ID\n\tr.fieldMap[\"room_id\"] = r.RoomID\n\tr.fieldMap[\"uid1\"] = r.Uid1\n\tr.fieldMap[\"uid2\"] = r.Uid2\n\tr.fieldMap[\"room_key\"] = r.RoomKey\n\tr.fieldMap[\"delete_status\"] = r.DeleteStatus\n\tr.fieldMap[\"create_time\"] = r.CreateTime\n\tr.fieldMap[\"update_time\"] = r.UpdateTime\n}\n\nfunc (r roomFriend) clone(db *gorm.DB) roomFriend {\n\tr.roomFriendDo.ReplaceConnPool(db.Statement.ConnPool)\n\treturn r\n}\n\nfunc (r roomFriend) replaceDB(db *gorm.DB) roomFriend {\n\tr.roomFriendDo.ReplaceDB(db)\n\treturn r\n}\n\ntype roomFriendDo struct{ gen.DO }\n\ntype IRoomFriendDo interface {\n\tgen.SubQuery\n\tDebug() IRoomFriendDo\n\tWithContext(ctx context.Context) IRoomFriendDo\n\tWithResult(fc func(tx gen.Dao)) gen.ResultInfo\n\tReplaceDB(db *gorm.DB)\n\tReadDB() IRoomFriendDo\n\tWriteDB() IRoomFriendDo\n\tAs(alias string) gen.Dao\n\tSession(config *gorm.Session) IRoomFriendDo\n\tColumns(cols ...field.Expr) gen.Columns\n\tClauses(conds ...clause.Expression) IRoomFriendDo\n\tNot(conds ...gen.Condition) IRoomFriendDo\n\tOr(conds ...gen.Condition) IRoomFriendDo\n\tSelect(conds ...field.Expr) IRoomFriendDo\n\tWhere(conds ...gen.Condition) IRoomFriendDo\n\tOrder(conds ...field.Expr) IRoomFriendDo\n\tDistinct(cols ...field.Expr) IRoomFriendDo\n\tOmit(cols ...field.Expr) IRoomFriendDo\n\tJoin(table schema.Tabler, on ...field.Expr) IRoomFriendDo\n\tLeftJoin(table schema.Tabler, on ...field.Expr) IRoomFriendDo\n\tRightJoin(table schema.Tabler, on ...field.Expr) IRoomFriendDo\n\tGroup(cols ...field.Expr) IRoomFriendDo\n\tHaving(conds ...gen.Condition) IRoomFriendDo\n\tLimit(limit int) IRoomFriendDo\n\tOffset(offset int) IRoomFriendDo\n\tCount() (count int64, err error)\n\tScopes(funcs ...func(gen.Dao) gen.Dao) IRoomFriendDo\n\tUnscoped() IRoomFriendDo\n\tCreate(values ...*model.RoomFriend) error\n\tCreateInBatches(values []*model.RoomFriend, batchSize int) error\n\tSave(values ...*model.RoomFriend) error\n\tFirst() (*model.RoomFriend, error)\n\tTake() (*model.RoomFriend, error)\n\tLast() (*model.RoomFriend, error)\n\tFind() ([]*model.RoomFriend, error)\n\tFindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.RoomFriend, err error)\n\tFindInBatches(result *[]*model.RoomFriend, batchSize int, fc func(tx gen.Dao, batch int) error) error\n\tPluck(column field.Expr, dest interface{}) error\n\tDelete(...*model.RoomFriend) (info gen.ResultInfo, err error)\n\tUpdate(column field.Expr, value interface{}) (info gen.ResultInfo, err error)\n\tUpdateSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error)\n\tUpdates(value interface{}) (info gen.ResultInfo, err error)\n\tUpdateColumn(column field.Expr, value interface{}) (info gen.ResultInfo, err error)\n\tUpdateColumnSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error)\n\tUpdateColumns(value interface{}) (info gen.ResultInfo, err error)\n\tUpdateFrom(q gen.SubQuery) gen.Dao\n\tAttrs(attrs ...field.AssignExpr) IRoomFriendDo\n\tAssign(attrs ...field.AssignExpr) IRoomFriendDo\n\tJoins(fields ...field.RelationField) IRoomFriendDo\n\tPreload(fields ...field.RelationField) IRoomFriendDo\n\tFirstOrInit() (*model.RoomFriend, error)\n\tFirstOrCreate() (*model.RoomFriend, error)\n\tFindByPage(offset int, limit int) (result []*model.RoomFriend, count int64, err error)\n\tScanByPage(result interface{}, offset int, limit int) (count int64, err error)\n\tScan(result interface{}) (err error)\n\tReturning(value interface{}, columns ...string) IRoomFriendDo\n\tUnderlyingDB() *gorm.DB\n\tschema.Tabler\n}\n\nfunc (r roomFriendDo) Debug() IRoomFriendDo {\n\treturn r.withDO(r.DO.Debug())\n}\n\nfunc (r roomFriendDo) WithContext(ctx context.Context) IRoomFriendDo {\n\treturn r.withDO(r.DO.WithContext(ctx))\n}\n\nfunc (r roomFriendDo) ReadDB() IRoomFriendDo {\n\treturn r.Clauses(dbresolver.Read)\n}\n\nfunc (r roomFriendDo) WriteDB() IRoomFriendDo {\n\treturn r.Clauses(dbresolver.Write)\n}\n\nfunc (r roomFriendDo) Session(config *gorm.Session) IRoomFriendDo {\n\treturn r.withDO(r.DO.Session(config))\n}\n\nfunc (r roomFriendDo) Clauses(conds ...clause.Expression) IRoomFriendDo {\n\treturn r.withDO(r.DO.Clauses(conds...))\n}\n\nfunc (r roomFriendDo) Returning(value interface{}, columns ...string) IRoomFriendDo {\n\treturn r.withDO(r.DO.Returning(value, columns...))\n}\n\nfunc (r roomFriendDo) Not(conds ...gen.Condition) IRoomFriendDo {\n\treturn r.withDO(r.DO.Not(conds...))\n}\n\nfunc (r roomFriendDo) Or(conds ...gen.Condition) IRoomFriendDo {\n\treturn r.withDO(r.DO.Or(conds...))\n}\n\nfunc (r roomFriendDo) Select(conds ...field.Expr) IRoomFriendDo {\n\treturn r.withDO(r.DO.Select(conds...))\n}\n\nfunc (r roomFriendDo) Where(conds ...gen.Condition) IRoomFriendDo {\n\treturn r.withDO(r.DO.Where(conds...))\n}\n\nfunc (r roomFriendDo) Order(conds ...field.Expr) IRoomFriendDo {\n\treturn r.withDO(r.DO.Order(conds...))\n}\n\nfunc (r roomFriendDo) Distinct(cols ...field.Expr) IRoomFriendDo {\n\treturn r.withDO(r.DO.Distinct(cols...))\n}\n\nfunc (r roomFriendDo) Omit(cols ...field.Expr) IRoomFriendDo {\n\treturn r.withDO(r.DO.Omit(cols...))\n}\n\nfunc (r roomFriendDo) Join(table schema.Tabler, on ...field.Expr) IRoomFriendDo {\n\treturn r.withDO(r.DO.Join(table, on...))\n}\n\nfunc (r roomFriendDo) LeftJoin(table schema.Tabler, on ...field.Expr) IRoomFriendDo {\n\treturn r.withDO(r.DO.LeftJoin(table, on...))\n}\n\nfunc (r roomFriendDo) RightJoin(table schema.Tabler, on ...field.Expr) IRoomFriendDo {\n\treturn r.withDO(r.DO.RightJoin(table, on...))\n}\n\nfunc (r roomFriendDo) Group(cols ...field.Expr) IRoomFriendDo {\n\treturn r.withDO(r.DO.Group(cols...))\n}\n\nfunc (r roomFriendDo) Having(conds ...gen.Condition) IRoomFriendDo {\n\treturn r.withDO(r.DO.Having(conds...))\n}\n\nfunc (r roomFriendDo) Limit(limit int) IRoomFriendDo {\n\treturn r.withDO(r.DO.Limit(limit))\n}\n\nfunc (r roomFriendDo) Offset(offset int) IRoomFriendDo {\n\treturn r.withDO(r.DO.Offset(offset))\n}\n\nfunc (r roomFriendDo) Scopes(funcs ...func(gen.Dao) gen.Dao) IRoomFriendDo {\n\treturn r.withDO(r.DO.Scopes(funcs...))\n}\n\nfunc (r roomFriendDo) Unscoped() IRoomFriendDo {\n\treturn r.withDO(r.DO.Unscoped())\n}\n\nfunc (r roomFriendDo) Create(values ...*model.RoomFriend) error {\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn r.DO.Create(values)\n}\n\nfunc (r roomFriendDo) CreateInBatches(values []*model.RoomFriend, batchSize int) error {\n\treturn r.DO.CreateInBatches(values, batchSize)\n}\n\n// Save : !!! underlying implementation is different with GORM\n// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values)\nfunc (r roomFriendDo) Save(values ...*model.RoomFriend) error {\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn r.DO.Save(values)\n}\n\nfunc (r roomFriendDo) First() (*model.RoomFriend, error) {\n\tif result, err := r.DO.First(); err != nil {\n\t\treturn nil, err\n\t} else {\n\t\treturn result.(*model.RoomFriend), nil\n\t}\n}\n\nfunc (r roomFriendDo) Take() (*model.RoomFriend, error) {\n\tif result, err := r.DO.Take(); err != nil {\n\t\treturn nil, err\n\t} else {\n\t\treturn result.(*model.RoomFriend), nil\n\t}\n}\n\nfunc (r roomFriendDo) Last() (*model.RoomFriend, error) {\n\tif result, err := r.DO.Last(); err != nil {\n\t\treturn nil, err\n\t} else {\n\t\treturn result.(*model.RoomFriend), nil\n\t}\n}\n\nfunc (r roomFriendDo) Find() ([]*model.RoomFriend, error) {\n\tresult, err := r.DO.Find()\n\treturn result.([]*model.RoomFriend), err\n}\n\nfunc (r roomFriendDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.RoomFriend, err error) {\n\tbuf := make([]*model.RoomFriend, 0, batchSize)\n\terr = r.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error {\n\t\tdefer func() { results = append(results, buf...) }()\n\t\treturn fc(tx, batch)\n\t})\n\treturn results, err\n}\n\nfunc (r roomFriendDo) FindInBatches(result *[]*model.RoomFriend, batchSize int, fc func(tx gen.Dao, batch int) error) error {\n\treturn r.DO.FindInBatches(result, batchSize, fc)\n}\n\nfunc (r roomFriendDo) Attrs(attrs ...field.AssignExpr) IRoomFriendDo {\n\treturn r.withDO(r.DO.Attrs(attrs...))\n}\n\nfunc (r roomFriendDo) Assign(attrs ...field.AssignExpr) IRoomFriendDo {\n\treturn r.withDO(r.DO.Assign(attrs...))\n}\n\nfunc (r roomFriendDo) Joins(fields ...field.RelationField) IRoomFriendDo {\n\tfor _, _f := range fields {\n\t\tr = *r.withDO(r.DO.Joins(_f))\n\t}\n\treturn &r\n}\n\nfunc (r roomFriendDo) Preload(fields ...field.RelationField) IRoomFriendDo {\n\tfor _, _f := range fields {\n\t\tr = *r.withDO(r.DO.Preload(_f))\n\t}\n\treturn &r\n}\n\nfunc (r roomFriendDo) FirstOrInit() (*model.RoomFriend, error) {\n\tif result, err := r.DO.FirstOrInit(); err != nil {\n\t\treturn nil, err\n\t} else {\n\t\treturn result.(*model.RoomFriend), nil\n\t}\n}\n\nfunc (r roomFriendDo) FirstOrCreate() (*model.RoomFriend, error) {\n\tif result, err := r.DO.FirstOrCreate(); err != nil {\n\t\treturn nil, err\n\t} else {\n\t\treturn result.(*model.RoomFriend), nil\n\t}\n}\n\nfunc (r roomFriendDo) FindByPage(offset int, limit int) (result []*model.RoomFriend, count int64, err error) {\n\tresult, err = r.Offset(offset).Limit(limit).Find()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif size := len(result); 0 < limit && 0 < size && size < limit {\n\t\tcount = int64(size + offset)\n\t\treturn\n\t}\n\n\tcount, err = r.Offset(-1).Limit(-1).Count()\n\treturn\n}\n\nfunc (r roomFriendDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) {\n\tcount, err = r.Count()\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = r.Offset(offset).Limit(limit).Scan(result)\n\treturn\n}\n\nfunc (r roomFriendDo) Scan(result interface{}) (err error) {\n\treturn r.DO.Scan(result)\n}\n\nfunc (r roomFriendDo) Delete(models ...*model.RoomFriend) (result gen.ResultInfo, err error) {\n\treturn r.DO.Delete(models)\n}\n\nfunc (r *roomFriendDo) withDO(do gen.Dao) *roomFriendDo {\n\tr.DO = *do.(*gen.DO)\n\treturn r\n}\n"
  },
  {
    "path": "dal/query/room_group.gen.go",
    "content": "// Code generated by gorm.io/gen. DO NOT EDIT.\n// Code generated by gorm.io/gen. DO NOT EDIT.\n// Code generated by gorm.io/gen. DO NOT EDIT.\n\npackage query\n\nimport (\n\t\"context\"\n\n\t\"gorm.io/gorm\"\n\t\"gorm.io/gorm/clause\"\n\t\"gorm.io/gorm/schema\"\n\n\t\"gorm.io/gen\"\n\t\"gorm.io/gen/field\"\n\n\t\"gorm.io/plugin/dbresolver\"\n\n\t\"DiTing-Go/dal/model\"\n)\n\nfunc newRoomGroup(db *gorm.DB, opts ...gen.DOOption) roomGroup {\n\t_roomGroup := roomGroup{}\n\n\t_roomGroup.roomGroupDo.UseDB(db, opts...)\n\t_roomGroup.roomGroupDo.UseModel(&model.RoomGroup{})\n\n\ttableName := _roomGroup.roomGroupDo.TableName()\n\t_roomGroup.ALL = field.NewAsterisk(tableName)\n\t_roomGroup.ID = field.NewInt64(tableName, \"id\")\n\t_roomGroup.RoomID = field.NewInt64(tableName, \"room_id\")\n\t_roomGroup.Name = field.NewString(tableName, \"name\")\n\t_roomGroup.Avatar = field.NewString(tableName, \"avatar\")\n\t_roomGroup.ExtJSON = field.NewString(tableName, \"ext_json\")\n\t_roomGroup.DeleteStatus = field.NewInt32(tableName, \"delete_status\")\n\t_roomGroup.CreateTime = field.NewTime(tableName, \"create_time\")\n\t_roomGroup.UpdateTime = field.NewTime(tableName, \"update_time\")\n\n\t_roomGroup.fillFieldMap()\n\n\treturn _roomGroup\n}\n\n// roomGroup 群聊房间表\ntype roomGroup struct {\n\troomGroupDo roomGroupDo\n\n\tALL          field.Asterisk\n\tID           field.Int64  // id\n\tRoomID       field.Int64  // 房间id\n\tName         field.String // 群名称\n\tAvatar       field.String // 群头像\n\tExtJSON      field.String // 额外信息（根据不同类型房间有不同存储的东西）\n\tDeleteStatus field.Int32  // 逻辑删除(0-正常,1-删除)\n\tCreateTime   field.Time   // 创建时间\n\tUpdateTime   field.Time   // 修改时间\n\n\tfieldMap map[string]field.Expr\n}\n\nfunc (r roomGroup) Table(newTableName string) *roomGroup {\n\tr.roomGroupDo.UseTable(newTableName)\n\treturn r.updateTableName(newTableName)\n}\n\nfunc (r roomGroup) As(alias string) *roomGroup {\n\tr.roomGroupDo.DO = *(r.roomGroupDo.As(alias).(*gen.DO))\n\treturn r.updateTableName(alias)\n}\n\nfunc (r *roomGroup) updateTableName(table string) *roomGroup {\n\tr.ALL = field.NewAsterisk(table)\n\tr.ID = field.NewInt64(table, \"id\")\n\tr.RoomID = field.NewInt64(table, \"room_id\")\n\tr.Name = field.NewString(table, \"name\")\n\tr.Avatar = field.NewString(table, \"avatar\")\n\tr.ExtJSON = field.NewString(table, \"ext_json\")\n\tr.DeleteStatus = field.NewInt32(table, \"delete_status\")\n\tr.CreateTime = field.NewTime(table, \"create_time\")\n\tr.UpdateTime = field.NewTime(table, \"update_time\")\n\n\tr.fillFieldMap()\n\n\treturn r\n}\n\nfunc (r *roomGroup) WithContext(ctx context.Context) IRoomGroupDo {\n\treturn r.roomGroupDo.WithContext(ctx)\n}\n\nfunc (r roomGroup) TableName() string { return r.roomGroupDo.TableName() }\n\nfunc (r roomGroup) Alias() string { return r.roomGroupDo.Alias() }\n\nfunc (r roomGroup) Columns(cols ...field.Expr) gen.Columns { return r.roomGroupDo.Columns(cols...) }\n\nfunc (r *roomGroup) GetFieldByName(fieldName string) (field.OrderExpr, bool) {\n\t_f, ok := r.fieldMap[fieldName]\n\tif !ok || _f == nil {\n\t\treturn nil, false\n\t}\n\t_oe, ok := _f.(field.OrderExpr)\n\treturn _oe, ok\n}\n\nfunc (r *roomGroup) fillFieldMap() {\n\tr.fieldMap = make(map[string]field.Expr, 8)\n\tr.fieldMap[\"id\"] = r.ID\n\tr.fieldMap[\"room_id\"] = r.RoomID\n\tr.fieldMap[\"name\"] = r.Name\n\tr.fieldMap[\"avatar\"] = r.Avatar\n\tr.fieldMap[\"ext_json\"] = r.ExtJSON\n\tr.fieldMap[\"delete_status\"] = r.DeleteStatus\n\tr.fieldMap[\"create_time\"] = r.CreateTime\n\tr.fieldMap[\"update_time\"] = r.UpdateTime\n}\n\nfunc (r roomGroup) clone(db *gorm.DB) roomGroup {\n\tr.roomGroupDo.ReplaceConnPool(db.Statement.ConnPool)\n\treturn r\n}\n\nfunc (r roomGroup) replaceDB(db *gorm.DB) roomGroup {\n\tr.roomGroupDo.ReplaceDB(db)\n\treturn r\n}\n\ntype roomGroupDo struct{ gen.DO }\n\ntype IRoomGroupDo interface {\n\tgen.SubQuery\n\tDebug() IRoomGroupDo\n\tWithContext(ctx context.Context) IRoomGroupDo\n\tWithResult(fc func(tx gen.Dao)) gen.ResultInfo\n\tReplaceDB(db *gorm.DB)\n\tReadDB() IRoomGroupDo\n\tWriteDB() IRoomGroupDo\n\tAs(alias string) gen.Dao\n\tSession(config *gorm.Session) IRoomGroupDo\n\tColumns(cols ...field.Expr) gen.Columns\n\tClauses(conds ...clause.Expression) IRoomGroupDo\n\tNot(conds ...gen.Condition) IRoomGroupDo\n\tOr(conds ...gen.Condition) IRoomGroupDo\n\tSelect(conds ...field.Expr) IRoomGroupDo\n\tWhere(conds ...gen.Condition) IRoomGroupDo\n\tOrder(conds ...field.Expr) IRoomGroupDo\n\tDistinct(cols ...field.Expr) IRoomGroupDo\n\tOmit(cols ...field.Expr) IRoomGroupDo\n\tJoin(table schema.Tabler, on ...field.Expr) IRoomGroupDo\n\tLeftJoin(table schema.Tabler, on ...field.Expr) IRoomGroupDo\n\tRightJoin(table schema.Tabler, on ...field.Expr) IRoomGroupDo\n\tGroup(cols ...field.Expr) IRoomGroupDo\n\tHaving(conds ...gen.Condition) IRoomGroupDo\n\tLimit(limit int) IRoomGroupDo\n\tOffset(offset int) IRoomGroupDo\n\tCount() (count int64, err error)\n\tScopes(funcs ...func(gen.Dao) gen.Dao) IRoomGroupDo\n\tUnscoped() IRoomGroupDo\n\tCreate(values ...*model.RoomGroup) error\n\tCreateInBatches(values []*model.RoomGroup, batchSize int) error\n\tSave(values ...*model.RoomGroup) error\n\tFirst() (*model.RoomGroup, error)\n\tTake() (*model.RoomGroup, error)\n\tLast() (*model.RoomGroup, error)\n\tFind() ([]*model.RoomGroup, error)\n\tFindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.RoomGroup, err error)\n\tFindInBatches(result *[]*model.RoomGroup, batchSize int, fc func(tx gen.Dao, batch int) error) error\n\tPluck(column field.Expr, dest interface{}) error\n\tDelete(...*model.RoomGroup) (info gen.ResultInfo, err error)\n\tUpdate(column field.Expr, value interface{}) (info gen.ResultInfo, err error)\n\tUpdateSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error)\n\tUpdates(value interface{}) (info gen.ResultInfo, err error)\n\tUpdateColumn(column field.Expr, value interface{}) (info gen.ResultInfo, err error)\n\tUpdateColumnSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error)\n\tUpdateColumns(value interface{}) (info gen.ResultInfo, err error)\n\tUpdateFrom(q gen.SubQuery) gen.Dao\n\tAttrs(attrs ...field.AssignExpr) IRoomGroupDo\n\tAssign(attrs ...field.AssignExpr) IRoomGroupDo\n\tJoins(fields ...field.RelationField) IRoomGroupDo\n\tPreload(fields ...field.RelationField) IRoomGroupDo\n\tFirstOrInit() (*model.RoomGroup, error)\n\tFirstOrCreate() (*model.RoomGroup, error)\n\tFindByPage(offset int, limit int) (result []*model.RoomGroup, count int64, err error)\n\tScanByPage(result interface{}, offset int, limit int) (count int64, err error)\n\tScan(result interface{}) (err error)\n\tReturning(value interface{}, columns ...string) IRoomGroupDo\n\tUnderlyingDB() *gorm.DB\n\tschema.Tabler\n}\n\nfunc (r roomGroupDo) Debug() IRoomGroupDo {\n\treturn r.withDO(r.DO.Debug())\n}\n\nfunc (r roomGroupDo) WithContext(ctx context.Context) IRoomGroupDo {\n\treturn r.withDO(r.DO.WithContext(ctx))\n}\n\nfunc (r roomGroupDo) ReadDB() IRoomGroupDo {\n\treturn r.Clauses(dbresolver.Read)\n}\n\nfunc (r roomGroupDo) WriteDB() IRoomGroupDo {\n\treturn r.Clauses(dbresolver.Write)\n}\n\nfunc (r roomGroupDo) Session(config *gorm.Session) IRoomGroupDo {\n\treturn r.withDO(r.DO.Session(config))\n}\n\nfunc (r roomGroupDo) Clauses(conds ...clause.Expression) IRoomGroupDo {\n\treturn r.withDO(r.DO.Clauses(conds...))\n}\n\nfunc (r roomGroupDo) Returning(value interface{}, columns ...string) IRoomGroupDo {\n\treturn r.withDO(r.DO.Returning(value, columns...))\n}\n\nfunc (r roomGroupDo) Not(conds ...gen.Condition) IRoomGroupDo {\n\treturn r.withDO(r.DO.Not(conds...))\n}\n\nfunc (r roomGroupDo) Or(conds ...gen.Condition) IRoomGroupDo {\n\treturn r.withDO(r.DO.Or(conds...))\n}\n\nfunc (r roomGroupDo) Select(conds ...field.Expr) IRoomGroupDo {\n\treturn r.withDO(r.DO.Select(conds...))\n}\n\nfunc (r roomGroupDo) Where(conds ...gen.Condition) IRoomGroupDo {\n\treturn r.withDO(r.DO.Where(conds...))\n}\n\nfunc (r roomGroupDo) Order(conds ...field.Expr) IRoomGroupDo {\n\treturn r.withDO(r.DO.Order(conds...))\n}\n\nfunc (r roomGroupDo) Distinct(cols ...field.Expr) IRoomGroupDo {\n\treturn r.withDO(r.DO.Distinct(cols...))\n}\n\nfunc (r roomGroupDo) Omit(cols ...field.Expr) IRoomGroupDo {\n\treturn r.withDO(r.DO.Omit(cols...))\n}\n\nfunc (r roomGroupDo) Join(table schema.Tabler, on ...field.Expr) IRoomGroupDo {\n\treturn r.withDO(r.DO.Join(table, on...))\n}\n\nfunc (r roomGroupDo) LeftJoin(table schema.Tabler, on ...field.Expr) IRoomGroupDo {\n\treturn r.withDO(r.DO.LeftJoin(table, on...))\n}\n\nfunc (r roomGroupDo) RightJoin(table schema.Tabler, on ...field.Expr) IRoomGroupDo {\n\treturn r.withDO(r.DO.RightJoin(table, on...))\n}\n\nfunc (r roomGroupDo) Group(cols ...field.Expr) IRoomGroupDo {\n\treturn r.withDO(r.DO.Group(cols...))\n}\n\nfunc (r roomGroupDo) Having(conds ...gen.Condition) IRoomGroupDo {\n\treturn r.withDO(r.DO.Having(conds...))\n}\n\nfunc (r roomGroupDo) Limit(limit int) IRoomGroupDo {\n\treturn r.withDO(r.DO.Limit(limit))\n}\n\nfunc (r roomGroupDo) Offset(offset int) IRoomGroupDo {\n\treturn r.withDO(r.DO.Offset(offset))\n}\n\nfunc (r roomGroupDo) Scopes(funcs ...func(gen.Dao) gen.Dao) IRoomGroupDo {\n\treturn r.withDO(r.DO.Scopes(funcs...))\n}\n\nfunc (r roomGroupDo) Unscoped() IRoomGroupDo {\n\treturn r.withDO(r.DO.Unscoped())\n}\n\nfunc (r roomGroupDo) Create(values ...*model.RoomGroup) error {\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn r.DO.Create(values)\n}\n\nfunc (r roomGroupDo) CreateInBatches(values []*model.RoomGroup, batchSize int) error {\n\treturn r.DO.CreateInBatches(values, batchSize)\n}\n\n// Save : !!! underlying implementation is different with GORM\n// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values)\nfunc (r roomGroupDo) Save(values ...*model.RoomGroup) error {\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn r.DO.Save(values)\n}\n\nfunc (r roomGroupDo) First() (*model.RoomGroup, error) {\n\tif result, err := r.DO.First(); err != nil {\n\t\treturn nil, err\n\t} else {\n\t\treturn result.(*model.RoomGroup), nil\n\t}\n}\n\nfunc (r roomGroupDo) Take() (*model.RoomGroup, error) {\n\tif result, err := r.DO.Take(); err != nil {\n\t\treturn nil, err\n\t} else {\n\t\treturn result.(*model.RoomGroup), nil\n\t}\n}\n\nfunc (r roomGroupDo) Last() (*model.RoomGroup, error) {\n\tif result, err := r.DO.Last(); err != nil {\n\t\treturn nil, err\n\t} else {\n\t\treturn result.(*model.RoomGroup), nil\n\t}\n}\n\nfunc (r roomGroupDo) Find() ([]*model.RoomGroup, error) {\n\tresult, err := r.DO.Find()\n\treturn result.([]*model.RoomGroup), err\n}\n\nfunc (r roomGroupDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.RoomGroup, err error) {\n\tbuf := make([]*model.RoomGroup, 0, batchSize)\n\terr = r.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error {\n\t\tdefer func() { results = append(results, buf...) }()\n\t\treturn fc(tx, batch)\n\t})\n\treturn results, err\n}\n\nfunc (r roomGroupDo) FindInBatches(result *[]*model.RoomGroup, batchSize int, fc func(tx gen.Dao, batch int) error) error {\n\treturn r.DO.FindInBatches(result, batchSize, fc)\n}\n\nfunc (r roomGroupDo) Attrs(attrs ...field.AssignExpr) IRoomGroupDo {\n\treturn r.withDO(r.DO.Attrs(attrs...))\n}\n\nfunc (r roomGroupDo) Assign(attrs ...field.AssignExpr) IRoomGroupDo {\n\treturn r.withDO(r.DO.Assign(attrs...))\n}\n\nfunc (r roomGroupDo) Joins(fields ...field.RelationField) IRoomGroupDo {\n\tfor _, _f := range fields {\n\t\tr = *r.withDO(r.DO.Joins(_f))\n\t}\n\treturn &r\n}\n\nfunc (r roomGroupDo) Preload(fields ...field.RelationField) IRoomGroupDo {\n\tfor _, _f := range fields {\n\t\tr = *r.withDO(r.DO.Preload(_f))\n\t}\n\treturn &r\n}\n\nfunc (r roomGroupDo) FirstOrInit() (*model.RoomGroup, error) {\n\tif result, err := r.DO.FirstOrInit(); err != nil {\n\t\treturn nil, err\n\t} else {\n\t\treturn result.(*model.RoomGroup), nil\n\t}\n}\n\nfunc (r roomGroupDo) FirstOrCreate() (*model.RoomGroup, error) {\n\tif result, err := r.DO.FirstOrCreate(); err != nil {\n\t\treturn nil, err\n\t} else {\n\t\treturn result.(*model.RoomGroup), nil\n\t}\n}\n\nfunc (r roomGroupDo) FindByPage(offset int, limit int) (result []*model.RoomGroup, count int64, err error) {\n\tresult, err = r.Offset(offset).Limit(limit).Find()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif size := len(result); 0 < limit && 0 < size && size < limit {\n\t\tcount = int64(size + offset)\n\t\treturn\n\t}\n\n\tcount, err = r.Offset(-1).Limit(-1).Count()\n\treturn\n}\n\nfunc (r roomGroupDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) {\n\tcount, err = r.Count()\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = r.Offset(offset).Limit(limit).Scan(result)\n\treturn\n}\n\nfunc (r roomGroupDo) Scan(result interface{}) (err error) {\n\treturn r.DO.Scan(result)\n}\n\nfunc (r roomGroupDo) Delete(models ...*model.RoomGroup) (result gen.ResultInfo, err error) {\n\treturn r.DO.Delete(models)\n}\n\nfunc (r *roomGroupDo) withDO(do gen.Dao) *roomGroupDo {\n\tr.DO = *do.(*gen.DO)\n\treturn r\n}\n"
  },
  {
    "path": "dal/query/user.gen.go",
    "content": "// Code generated by gorm.io/gen. DO NOT EDIT.\n// Code generated by gorm.io/gen. DO NOT EDIT.\n// Code generated by gorm.io/gen. DO NOT EDIT.\n\npackage query\n\nimport (\n\t\"context\"\n\n\t\"gorm.io/gorm\"\n\t\"gorm.io/gorm/clause\"\n\t\"gorm.io/gorm/schema\"\n\n\t\"gorm.io/gen\"\n\t\"gorm.io/gen/field\"\n\n\t\"gorm.io/plugin/dbresolver\"\n\n\t\"DiTing-Go/dal/model\"\n)\n\nfunc newUser(db *gorm.DB, opts ...gen.DOOption) user {\n\t_user := user{}\n\n\t_user.userDo.UseDB(db, opts...)\n\t_user.userDo.UseModel(&model.User{})\n\n\ttableName := _user.userDo.TableName()\n\t_user.ALL = field.NewAsterisk(tableName)\n\t_user.ID = field.NewInt64(tableName, \"id\")\n\t_user.Phone = field.NewString(tableName, \"phone\")\n\t_user.Password = field.NewString(tableName, \"password\")\n\t_user.Name = field.NewString(tableName, \"name\")\n\t_user.Avatar = field.NewString(tableName, \"avatar\")\n\t_user.Sex = field.NewInt32(tableName, \"sex\")\n\t_user.ActiveStatus = field.NewInt32(tableName, \"active_status\")\n\t_user.LastOptTime = field.NewTime(tableName, \"last_opt_time\")\n\t_user.IPInfo = field.NewString(tableName, \"ip_info\")\n\t_user.ItemID = field.NewInt64(tableName, \"item_id\")\n\t_user.Status = field.NewInt32(tableName, \"status\")\n\t_user.CreateTime = field.NewTime(tableName, \"create_time\")\n\t_user.UpdateTime = field.NewTime(tableName, \"update_time\")\n\n\t_user.fillFieldMap()\n\n\treturn _user\n}\n\n// user 用户表\ntype user struct {\n\tuserDo userDo\n\n\tALL          field.Asterisk\n\tID           field.Int64  // 用户id\n\tPhone        field.String // 用户手机\n\tPassword     field.String // 用户密码\n\tName         field.String // 用户昵称\n\tAvatar       field.String // 用户头像\n\tSex          field.Int32  // 性别 1为男性，2为女性，3未知\n\tActiveStatus field.Int32  // 在线状态 1在线 2离线\n\tLastOptTime  field.Time   // 最后上下线时间\n\tIPInfo       field.String // ip信息，用于显示用户地理位置\n\tItemID       field.Int64  // 佩戴的徽章id\n\tStatus       field.Int32  // 使用状态 1.正常 2禁用\n\tCreateTime   field.Time   // 创建时间\n\tUpdateTime   field.Time   // 修改时间\n\n\tfieldMap map[string]field.Expr\n}\n\nfunc (u user) Table(newTableName string) *user {\n\tu.userDo.UseTable(newTableName)\n\treturn u.updateTableName(newTableName)\n}\n\nfunc (u user) As(alias string) *user {\n\tu.userDo.DO = *(u.userDo.As(alias).(*gen.DO))\n\treturn u.updateTableName(alias)\n}\n\nfunc (u *user) updateTableName(table string) *user {\n\tu.ALL = field.NewAsterisk(table)\n\tu.ID = field.NewInt64(table, \"id\")\n\tu.Phone = field.NewString(table, \"phone\")\n\tu.Password = field.NewString(table, \"password\")\n\tu.Name = field.NewString(table, \"name\")\n\tu.Avatar = field.NewString(table, \"avatar\")\n\tu.Sex = field.NewInt32(table, \"sex\")\n\tu.ActiveStatus = field.NewInt32(table, \"active_status\")\n\tu.LastOptTime = field.NewTime(table, \"last_opt_time\")\n\tu.IPInfo = field.NewString(table, \"ip_info\")\n\tu.ItemID = field.NewInt64(table, \"item_id\")\n\tu.Status = field.NewInt32(table, \"status\")\n\tu.CreateTime = field.NewTime(table, \"create_time\")\n\tu.UpdateTime = field.NewTime(table, \"update_time\")\n\n\tu.fillFieldMap()\n\n\treturn u\n}\n\nfunc (u *user) WithContext(ctx context.Context) IUserDo { return u.userDo.WithContext(ctx) }\n\nfunc (u user) TableName() string { return u.userDo.TableName() }\n\nfunc (u user) Alias() string { return u.userDo.Alias() }\n\nfunc (u user) Columns(cols ...field.Expr) gen.Columns { return u.userDo.Columns(cols...) }\n\nfunc (u *user) GetFieldByName(fieldName string) (field.OrderExpr, bool) {\n\t_f, ok := u.fieldMap[fieldName]\n\tif !ok || _f == nil {\n\t\treturn nil, false\n\t}\n\t_oe, ok := _f.(field.OrderExpr)\n\treturn _oe, ok\n}\n\nfunc (u *user) fillFieldMap() {\n\tu.fieldMap = make(map[string]field.Expr, 13)\n\tu.fieldMap[\"id\"] = u.ID\n\tu.fieldMap[\"phone\"] = u.Phone\n\tu.fieldMap[\"password\"] = u.Password\n\tu.fieldMap[\"name\"] = u.Name\n\tu.fieldMap[\"avatar\"] = u.Avatar\n\tu.fieldMap[\"sex\"] = u.Sex\n\tu.fieldMap[\"active_status\"] = u.ActiveStatus\n\tu.fieldMap[\"last_opt_time\"] = u.LastOptTime\n\tu.fieldMap[\"ip_info\"] = u.IPInfo\n\tu.fieldMap[\"item_id\"] = u.ItemID\n\tu.fieldMap[\"status\"] = u.Status\n\tu.fieldMap[\"create_time\"] = u.CreateTime\n\tu.fieldMap[\"update_time\"] = u.UpdateTime\n}\n\nfunc (u user) clone(db *gorm.DB) user {\n\tu.userDo.ReplaceConnPool(db.Statement.ConnPool)\n\treturn u\n}\n\nfunc (u user) replaceDB(db *gorm.DB) user {\n\tu.userDo.ReplaceDB(db)\n\treturn u\n}\n\ntype userDo struct{ gen.DO }\n\ntype IUserDo interface {\n\tgen.SubQuery\n\tDebug() IUserDo\n\tWithContext(ctx context.Context) IUserDo\n\tWithResult(fc func(tx gen.Dao)) gen.ResultInfo\n\tReplaceDB(db *gorm.DB)\n\tReadDB() IUserDo\n\tWriteDB() IUserDo\n\tAs(alias string) gen.Dao\n\tSession(config *gorm.Session) IUserDo\n\tColumns(cols ...field.Expr) gen.Columns\n\tClauses(conds ...clause.Expression) IUserDo\n\tNot(conds ...gen.Condition) IUserDo\n\tOr(conds ...gen.Condition) IUserDo\n\tSelect(conds ...field.Expr) IUserDo\n\tWhere(conds ...gen.Condition) IUserDo\n\tOrder(conds ...field.Expr) IUserDo\n\tDistinct(cols ...field.Expr) IUserDo\n\tOmit(cols ...field.Expr) IUserDo\n\tJoin(table schema.Tabler, on ...field.Expr) IUserDo\n\tLeftJoin(table schema.Tabler, on ...field.Expr) IUserDo\n\tRightJoin(table schema.Tabler, on ...field.Expr) IUserDo\n\tGroup(cols ...field.Expr) IUserDo\n\tHaving(conds ...gen.Condition) IUserDo\n\tLimit(limit int) IUserDo\n\tOffset(offset int) IUserDo\n\tCount() (count int64, err error)\n\tScopes(funcs ...func(gen.Dao) gen.Dao) IUserDo\n\tUnscoped() IUserDo\n\tCreate(values ...*model.User) error\n\tCreateInBatches(values []*model.User, batchSize int) error\n\tSave(values ...*model.User) error\n\tFirst() (*model.User, error)\n\tTake() (*model.User, error)\n\tLast() (*model.User, error)\n\tFind() ([]*model.User, error)\n\tFindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.User, err error)\n\tFindInBatches(result *[]*model.User, batchSize int, fc func(tx gen.Dao, batch int) error) error\n\tPluck(column field.Expr, dest interface{}) error\n\tDelete(...*model.User) (info gen.ResultInfo, err error)\n\tUpdate(column field.Expr, value interface{}) (info gen.ResultInfo, err error)\n\tUpdateSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error)\n\tUpdates(value interface{}) (info gen.ResultInfo, err error)\n\tUpdateColumn(column field.Expr, value interface{}) (info gen.ResultInfo, err error)\n\tUpdateColumnSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error)\n\tUpdateColumns(value interface{}) (info gen.ResultInfo, err error)\n\tUpdateFrom(q gen.SubQuery) gen.Dao\n\tAttrs(attrs ...field.AssignExpr) IUserDo\n\tAssign(attrs ...field.AssignExpr) IUserDo\n\tJoins(fields ...field.RelationField) IUserDo\n\tPreload(fields ...field.RelationField) IUserDo\n\tFirstOrInit() (*model.User, error)\n\tFirstOrCreate() (*model.User, error)\n\tFindByPage(offset int, limit int) (result []*model.User, count int64, err error)\n\tScanByPage(result interface{}, offset int, limit int) (count int64, err error)\n\tScan(result interface{}) (err error)\n\tReturning(value interface{}, columns ...string) IUserDo\n\tUnderlyingDB() *gorm.DB\n\tschema.Tabler\n}\n\nfunc (u userDo) Debug() IUserDo {\n\treturn u.withDO(u.DO.Debug())\n}\n\nfunc (u userDo) WithContext(ctx context.Context) IUserDo {\n\treturn u.withDO(u.DO.WithContext(ctx))\n}\n\nfunc (u userDo) ReadDB() IUserDo {\n\treturn u.Clauses(dbresolver.Read)\n}\n\nfunc (u userDo) WriteDB() IUserDo {\n\treturn u.Clauses(dbresolver.Write)\n}\n\nfunc (u userDo) Session(config *gorm.Session) IUserDo {\n\treturn u.withDO(u.DO.Session(config))\n}\n\nfunc (u userDo) Clauses(conds ...clause.Expression) IUserDo {\n\treturn u.withDO(u.DO.Clauses(conds...))\n}\n\nfunc (u userDo) Returning(value interface{}, columns ...string) IUserDo {\n\treturn u.withDO(u.DO.Returning(value, columns...))\n}\n\nfunc (u userDo) Not(conds ...gen.Condition) IUserDo {\n\treturn u.withDO(u.DO.Not(conds...))\n}\n\nfunc (u userDo) Or(conds ...gen.Condition) IUserDo {\n\treturn u.withDO(u.DO.Or(conds...))\n}\n\nfunc (u userDo) Select(conds ...field.Expr) IUserDo {\n\treturn u.withDO(u.DO.Select(conds...))\n}\n\nfunc (u userDo) Where(conds ...gen.Condition) IUserDo {\n\treturn u.withDO(u.DO.Where(conds...))\n}\n\nfunc (u userDo) Order(conds ...field.Expr) IUserDo {\n\treturn u.withDO(u.DO.Order(conds...))\n}\n\nfunc (u userDo) Distinct(cols ...field.Expr) IUserDo {\n\treturn u.withDO(u.DO.Distinct(cols...))\n}\n\nfunc (u userDo) Omit(cols ...field.Expr) IUserDo {\n\treturn u.withDO(u.DO.Omit(cols...))\n}\n\nfunc (u userDo) Join(table schema.Tabler, on ...field.Expr) IUserDo {\n\treturn u.withDO(u.DO.Join(table, on...))\n}\n\nfunc (u userDo) LeftJoin(table schema.Tabler, on ...field.Expr) IUserDo {\n\treturn u.withDO(u.DO.LeftJoin(table, on...))\n}\n\nfunc (u userDo) RightJoin(table schema.Tabler, on ...field.Expr) IUserDo {\n\treturn u.withDO(u.DO.RightJoin(table, on...))\n}\n\nfunc (u userDo) Group(cols ...field.Expr) IUserDo {\n\treturn u.withDO(u.DO.Group(cols...))\n}\n\nfunc (u userDo) Having(conds ...gen.Condition) IUserDo {\n\treturn u.withDO(u.DO.Having(conds...))\n}\n\nfunc (u userDo) Limit(limit int) IUserDo {\n\treturn u.withDO(u.DO.Limit(limit))\n}\n\nfunc (u userDo) Offset(offset int) IUserDo {\n\treturn u.withDO(u.DO.Offset(offset))\n}\n\nfunc (u userDo) Scopes(funcs ...func(gen.Dao) gen.Dao) IUserDo {\n\treturn u.withDO(u.DO.Scopes(funcs...))\n}\n\nfunc (u userDo) Unscoped() IUserDo {\n\treturn u.withDO(u.DO.Unscoped())\n}\n\nfunc (u userDo) Create(values ...*model.User) error {\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn u.DO.Create(values)\n}\n\nfunc (u userDo) CreateInBatches(values []*model.User, batchSize int) error {\n\treturn u.DO.CreateInBatches(values, batchSize)\n}\n\n// Save : !!! underlying implementation is different with GORM\n// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values)\nfunc (u userDo) Save(values ...*model.User) error {\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn u.DO.Save(values)\n}\n\nfunc (u userDo) First() (*model.User, error) {\n\tif result, err := u.DO.First(); err != nil {\n\t\treturn nil, err\n\t} else {\n\t\treturn result.(*model.User), nil\n\t}\n}\n\nfunc (u userDo) Take() (*model.User, error) {\n\tif result, err := u.DO.Take(); err != nil {\n\t\treturn nil, err\n\t} else {\n\t\treturn result.(*model.User), nil\n\t}\n}\n\nfunc (u userDo) Last() (*model.User, error) {\n\tif result, err := u.DO.Last(); err != nil {\n\t\treturn nil, err\n\t} else {\n\t\treturn result.(*model.User), nil\n\t}\n}\n\nfunc (u userDo) Find() ([]*model.User, error) {\n\tresult, err := u.DO.Find()\n\treturn result.([]*model.User), err\n}\n\nfunc (u userDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.User, err error) {\n\tbuf := make([]*model.User, 0, batchSize)\n\terr = u.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error {\n\t\tdefer func() { results = append(results, buf...) }()\n\t\treturn fc(tx, batch)\n\t})\n\treturn results, err\n}\n\nfunc (u userDo) FindInBatches(result *[]*model.User, batchSize int, fc func(tx gen.Dao, batch int) error) error {\n\treturn u.DO.FindInBatches(result, batchSize, fc)\n}\n\nfunc (u userDo) Attrs(attrs ...field.AssignExpr) IUserDo {\n\treturn u.withDO(u.DO.Attrs(attrs...))\n}\n\nfunc (u userDo) Assign(attrs ...field.AssignExpr) IUserDo {\n\treturn u.withDO(u.DO.Assign(attrs...))\n}\n\nfunc (u userDo) Joins(fields ...field.RelationField) IUserDo {\n\tfor _, _f := range fields {\n\t\tu = *u.withDO(u.DO.Joins(_f))\n\t}\n\treturn &u\n}\n\nfunc (u userDo) Preload(fields ...field.RelationField) IUserDo {\n\tfor _, _f := range fields {\n\t\tu = *u.withDO(u.DO.Preload(_f))\n\t}\n\treturn &u\n}\n\nfunc (u userDo) FirstOrInit() (*model.User, error) {\n\tif result, err := u.DO.FirstOrInit(); err != nil {\n\t\treturn nil, err\n\t} else {\n\t\treturn result.(*model.User), nil\n\t}\n}\n\nfunc (u userDo) FirstOrCreate() (*model.User, error) {\n\tif result, err := u.DO.FirstOrCreate(); err != nil {\n\t\treturn nil, err\n\t} else {\n\t\treturn result.(*model.User), nil\n\t}\n}\n\nfunc (u userDo) FindByPage(offset int, limit int) (result []*model.User, count int64, err error) {\n\tresult, err = u.Offset(offset).Limit(limit).Find()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif size := len(result); 0 < limit && 0 < size && size < limit {\n\t\tcount = int64(size + offset)\n\t\treturn\n\t}\n\n\tcount, err = u.Offset(-1).Limit(-1).Count()\n\treturn\n}\n\nfunc (u userDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) {\n\tcount, err = u.Count()\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = u.Offset(offset).Limit(limit).Scan(result)\n\treturn\n}\n\nfunc (u userDo) Scan(result interface{}) (err error) {\n\treturn u.DO.Scan(result)\n}\n\nfunc (u userDo) Delete(models ...*model.User) (result gen.ResultInfo, err error) {\n\treturn u.DO.Delete(models)\n}\n\nfunc (u *userDo) withDO(do gen.Dao) *userDo {\n\tu.DO = *do.(*gen.DO)\n\treturn u\n}\n"
  },
  {
    "path": "dal/query/user_apply.gen.go",
    "content": "// Code generated by gorm.io/gen. DO NOT EDIT.\n// Code generated by gorm.io/gen. DO NOT EDIT.\n// Code generated by gorm.io/gen. DO NOT EDIT.\n\npackage query\n\nimport (\n\t\"context\"\n\n\t\"gorm.io/gorm\"\n\t\"gorm.io/gorm/clause\"\n\t\"gorm.io/gorm/schema\"\n\n\t\"gorm.io/gen\"\n\t\"gorm.io/gen/field\"\n\n\t\"gorm.io/plugin/dbresolver\"\n\n\t\"DiTing-Go/dal/model\"\n)\n\nfunc newUserApply(db *gorm.DB, opts ...gen.DOOption) userApply {\n\t_userApply := userApply{}\n\n\t_userApply.userApplyDo.UseDB(db, opts...)\n\t_userApply.userApplyDo.UseModel(&model.UserApply{})\n\n\ttableName := _userApply.userApplyDo.TableName()\n\t_userApply.ALL = field.NewAsterisk(tableName)\n\t_userApply.ID = field.NewInt64(tableName, \"id\")\n\t_userApply.UID = field.NewInt64(tableName, \"uid\")\n\t_userApply.Type = field.NewInt32(tableName, \"type\")\n\t_userApply.TargetID = field.NewInt64(tableName, \"target_id\")\n\t_userApply.Msg = field.NewString(tableName, \"msg\")\n\t_userApply.Status = field.NewInt32(tableName, \"status\")\n\t_userApply.ReadStatus = field.NewInt32(tableName, \"read_status\")\n\t_userApply.CreateTime = field.NewTime(tableName, \"create_time\")\n\t_userApply.UpdateTime = field.NewTime(tableName, \"update_time\")\n\n\t_userApply.fillFieldMap()\n\n\treturn _userApply\n}\n\n// userApply 用户申请表\ntype userApply struct {\n\tuserApplyDo userApplyDo\n\n\tALL        field.Asterisk\n\tID         field.Int64  // id\n\tUID        field.Int64  // 申请人uid\n\tType       field.Int32  // 申请类型 1加好友\n\tTargetID   field.Int64  // 接收人uid\n\tMsg        field.String // 申请信息\n\tStatus     field.Int32  // 申请状态 1待审批 2同意\n\tReadStatus field.Int32  // 阅读状态 1未读 2已读\n\tCreateTime field.Time   // 创建时间\n\tUpdateTime field.Time   // 修改时间\n\n\tfieldMap map[string]field.Expr\n}\n\nfunc (u userApply) Table(newTableName string) *userApply {\n\tu.userApplyDo.UseTable(newTableName)\n\treturn u.updateTableName(newTableName)\n}\n\nfunc (u userApply) As(alias string) *userApply {\n\tu.userApplyDo.DO = *(u.userApplyDo.As(alias).(*gen.DO))\n\treturn u.updateTableName(alias)\n}\n\nfunc (u *userApply) updateTableName(table string) *userApply {\n\tu.ALL = field.NewAsterisk(table)\n\tu.ID = field.NewInt64(table, \"id\")\n\tu.UID = field.NewInt64(table, \"uid\")\n\tu.Type = field.NewInt32(table, \"type\")\n\tu.TargetID = field.NewInt64(table, \"target_id\")\n\tu.Msg = field.NewString(table, \"msg\")\n\tu.Status = field.NewInt32(table, \"status\")\n\tu.ReadStatus = field.NewInt32(table, \"read_status\")\n\tu.CreateTime = field.NewTime(table, \"create_time\")\n\tu.UpdateTime = field.NewTime(table, \"update_time\")\n\n\tu.fillFieldMap()\n\n\treturn u\n}\n\nfunc (u *userApply) WithContext(ctx context.Context) IUserApplyDo {\n\treturn u.userApplyDo.WithContext(ctx)\n}\n\nfunc (u userApply) TableName() string { return u.userApplyDo.TableName() }\n\nfunc (u userApply) Alias() string { return u.userApplyDo.Alias() }\n\nfunc (u userApply) Columns(cols ...field.Expr) gen.Columns { return u.userApplyDo.Columns(cols...) }\n\nfunc (u *userApply) GetFieldByName(fieldName string) (field.OrderExpr, bool) {\n\t_f, ok := u.fieldMap[fieldName]\n\tif !ok || _f == nil {\n\t\treturn nil, false\n\t}\n\t_oe, ok := _f.(field.OrderExpr)\n\treturn _oe, ok\n}\n\nfunc (u *userApply) fillFieldMap() {\n\tu.fieldMap = make(map[string]field.Expr, 9)\n\tu.fieldMap[\"id\"] = u.ID\n\tu.fieldMap[\"uid\"] = u.UID\n\tu.fieldMap[\"type\"] = u.Type\n\tu.fieldMap[\"target_id\"] = u.TargetID\n\tu.fieldMap[\"msg\"] = u.Msg\n\tu.fieldMap[\"status\"] = u.Status\n\tu.fieldMap[\"read_status\"] = u.ReadStatus\n\tu.fieldMap[\"create_time\"] = u.CreateTime\n\tu.fieldMap[\"update_time\"] = u.UpdateTime\n}\n\nfunc (u userApply) clone(db *gorm.DB) userApply {\n\tu.userApplyDo.ReplaceConnPool(db.Statement.ConnPool)\n\treturn u\n}\n\nfunc (u userApply) replaceDB(db *gorm.DB) userApply {\n\tu.userApplyDo.ReplaceDB(db)\n\treturn u\n}\n\ntype userApplyDo struct{ gen.DO }\n\ntype IUserApplyDo interface {\n\tgen.SubQuery\n\tDebug() IUserApplyDo\n\tWithContext(ctx context.Context) IUserApplyDo\n\tWithResult(fc func(tx gen.Dao)) gen.ResultInfo\n\tReplaceDB(db *gorm.DB)\n\tReadDB() IUserApplyDo\n\tWriteDB() IUserApplyDo\n\tAs(alias string) gen.Dao\n\tSession(config *gorm.Session) IUserApplyDo\n\tColumns(cols ...field.Expr) gen.Columns\n\tClauses(conds ...clause.Expression) IUserApplyDo\n\tNot(conds ...gen.Condition) IUserApplyDo\n\tOr(conds ...gen.Condition) IUserApplyDo\n\tSelect(conds ...field.Expr) IUserApplyDo\n\tWhere(conds ...gen.Condition) IUserApplyDo\n\tOrder(conds ...field.Expr) IUserApplyDo\n\tDistinct(cols ...field.Expr) IUserApplyDo\n\tOmit(cols ...field.Expr) IUserApplyDo\n\tJoin(table schema.Tabler, on ...field.Expr) IUserApplyDo\n\tLeftJoin(table schema.Tabler, on ...field.Expr) IUserApplyDo\n\tRightJoin(table schema.Tabler, on ...field.Expr) IUserApplyDo\n\tGroup(cols ...field.Expr) IUserApplyDo\n\tHaving(conds ...gen.Condition) IUserApplyDo\n\tLimit(limit int) IUserApplyDo\n\tOffset(offset int) IUserApplyDo\n\tCount() (count int64, err error)\n\tScopes(funcs ...func(gen.Dao) gen.Dao) IUserApplyDo\n\tUnscoped() IUserApplyDo\n\tCreate(values ...*model.UserApply) error\n\tCreateInBatches(values []*model.UserApply, batchSize int) error\n\tSave(values ...*model.UserApply) error\n\tFirst() (*model.UserApply, error)\n\tTake() (*model.UserApply, error)\n\tLast() (*model.UserApply, error)\n\tFind() ([]*model.UserApply, error)\n\tFindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.UserApply, err error)\n\tFindInBatches(result *[]*model.UserApply, batchSize int, fc func(tx gen.Dao, batch int) error) error\n\tPluck(column field.Expr, dest interface{}) error\n\tDelete(...*model.UserApply) (info gen.ResultInfo, err error)\n\tUpdate(column field.Expr, value interface{}) (info gen.ResultInfo, err error)\n\tUpdateSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error)\n\tUpdates(value interface{}) (info gen.ResultInfo, err error)\n\tUpdateColumn(column field.Expr, value interface{}) (info gen.ResultInfo, err error)\n\tUpdateColumnSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error)\n\tUpdateColumns(value interface{}) (info gen.ResultInfo, err error)\n\tUpdateFrom(q gen.SubQuery) gen.Dao\n\tAttrs(attrs ...field.AssignExpr) IUserApplyDo\n\tAssign(attrs ...field.AssignExpr) IUserApplyDo\n\tJoins(fields ...field.RelationField) IUserApplyDo\n\tPreload(fields ...field.RelationField) IUserApplyDo\n\tFirstOrInit() (*model.UserApply, error)\n\tFirstOrCreate() (*model.UserApply, error)\n\tFindByPage(offset int, limit int) (result []*model.UserApply, count int64, err error)\n\tScanByPage(result interface{}, offset int, limit int) (count int64, err error)\n\tScan(result interface{}) (err error)\n\tReturning(value interface{}, columns ...string) IUserApplyDo\n\tUnderlyingDB() *gorm.DB\n\tschema.Tabler\n}\n\nfunc (u userApplyDo) Debug() IUserApplyDo {\n\treturn u.withDO(u.DO.Debug())\n}\n\nfunc (u userApplyDo) WithContext(ctx context.Context) IUserApplyDo {\n\treturn u.withDO(u.DO.WithContext(ctx))\n}\n\nfunc (u userApplyDo) ReadDB() IUserApplyDo {\n\treturn u.Clauses(dbresolver.Read)\n}\n\nfunc (u userApplyDo) WriteDB() IUserApplyDo {\n\treturn u.Clauses(dbresolver.Write)\n}\n\nfunc (u userApplyDo) Session(config *gorm.Session) IUserApplyDo {\n\treturn u.withDO(u.DO.Session(config))\n}\n\nfunc (u userApplyDo) Clauses(conds ...clause.Expression) IUserApplyDo {\n\treturn u.withDO(u.DO.Clauses(conds...))\n}\n\nfunc (u userApplyDo) Returning(value interface{}, columns ...string) IUserApplyDo {\n\treturn u.withDO(u.DO.Returning(value, columns...))\n}\n\nfunc (u userApplyDo) Not(conds ...gen.Condition) IUserApplyDo {\n\treturn u.withDO(u.DO.Not(conds...))\n}\n\nfunc (u userApplyDo) Or(conds ...gen.Condition) IUserApplyDo {\n\treturn u.withDO(u.DO.Or(conds...))\n}\n\nfunc (u userApplyDo) Select(conds ...field.Expr) IUserApplyDo {\n\treturn u.withDO(u.DO.Select(conds...))\n}\n\nfunc (u userApplyDo) Where(conds ...gen.Condition) IUserApplyDo {\n\treturn u.withDO(u.DO.Where(conds...))\n}\n\nfunc (u userApplyDo) Order(conds ...field.Expr) IUserApplyDo {\n\treturn u.withDO(u.DO.Order(conds...))\n}\n\nfunc (u userApplyDo) Distinct(cols ...field.Expr) IUserApplyDo {\n\treturn u.withDO(u.DO.Distinct(cols...))\n}\n\nfunc (u userApplyDo) Omit(cols ...field.Expr) IUserApplyDo {\n\treturn u.withDO(u.DO.Omit(cols...))\n}\n\nfunc (u userApplyDo) Join(table schema.Tabler, on ...field.Expr) IUserApplyDo {\n\treturn u.withDO(u.DO.Join(table, on...))\n}\n\nfunc (u userApplyDo) LeftJoin(table schema.Tabler, on ...field.Expr) IUserApplyDo {\n\treturn u.withDO(u.DO.LeftJoin(table, on...))\n}\n\nfunc (u userApplyDo) RightJoin(table schema.Tabler, on ...field.Expr) IUserApplyDo {\n\treturn u.withDO(u.DO.RightJoin(table, on...))\n}\n\nfunc (u userApplyDo) Group(cols ...field.Expr) IUserApplyDo {\n\treturn u.withDO(u.DO.Group(cols...))\n}\n\nfunc (u userApplyDo) Having(conds ...gen.Condition) IUserApplyDo {\n\treturn u.withDO(u.DO.Having(conds...))\n}\n\nfunc (u userApplyDo) Limit(limit int) IUserApplyDo {\n\treturn u.withDO(u.DO.Limit(limit))\n}\n\nfunc (u userApplyDo) Offset(offset int) IUserApplyDo {\n\treturn u.withDO(u.DO.Offset(offset))\n}\n\nfunc (u userApplyDo) Scopes(funcs ...func(gen.Dao) gen.Dao) IUserApplyDo {\n\treturn u.withDO(u.DO.Scopes(funcs...))\n}\n\nfunc (u userApplyDo) Unscoped() IUserApplyDo {\n\treturn u.withDO(u.DO.Unscoped())\n}\n\nfunc (u userApplyDo) Create(values ...*model.UserApply) error {\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn u.DO.Create(values)\n}\n\nfunc (u userApplyDo) CreateInBatches(values []*model.UserApply, batchSize int) error {\n\treturn u.DO.CreateInBatches(values, batchSize)\n}\n\n// Save : !!! underlying implementation is different with GORM\n// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values)\nfunc (u userApplyDo) Save(values ...*model.UserApply) error {\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn u.DO.Save(values)\n}\n\nfunc (u userApplyDo) First() (*model.UserApply, error) {\n\tif result, err := u.DO.First(); err != nil {\n\t\treturn nil, err\n\t} else {\n\t\treturn result.(*model.UserApply), nil\n\t}\n}\n\nfunc (u userApplyDo) Take() (*model.UserApply, error) {\n\tif result, err := u.DO.Take(); err != nil {\n\t\treturn nil, err\n\t} else {\n\t\treturn result.(*model.UserApply), nil\n\t}\n}\n\nfunc (u userApplyDo) Last() (*model.UserApply, error) {\n\tif result, err := u.DO.Last(); err != nil {\n\t\treturn nil, err\n\t} else {\n\t\treturn result.(*model.UserApply), nil\n\t}\n}\n\nfunc (u userApplyDo) Find() ([]*model.UserApply, error) {\n\tresult, err := u.DO.Find()\n\treturn result.([]*model.UserApply), err\n}\n\nfunc (u userApplyDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.UserApply, err error) {\n\tbuf := make([]*model.UserApply, 0, batchSize)\n\terr = u.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error {\n\t\tdefer func() { results = append(results, buf...) }()\n\t\treturn fc(tx, batch)\n\t})\n\treturn results, err\n}\n\nfunc (u userApplyDo) FindInBatches(result *[]*model.UserApply, batchSize int, fc func(tx gen.Dao, batch int) error) error {\n\treturn u.DO.FindInBatches(result, batchSize, fc)\n}\n\nfunc (u userApplyDo) Attrs(attrs ...field.AssignExpr) IUserApplyDo {\n\treturn u.withDO(u.DO.Attrs(attrs...))\n}\n\nfunc (u userApplyDo) Assign(attrs ...field.AssignExpr) IUserApplyDo {\n\treturn u.withDO(u.DO.Assign(attrs...))\n}\n\nfunc (u userApplyDo) Joins(fields ...field.RelationField) IUserApplyDo {\n\tfor _, _f := range fields {\n\t\tu = *u.withDO(u.DO.Joins(_f))\n\t}\n\treturn &u\n}\n\nfunc (u userApplyDo) Preload(fields ...field.RelationField) IUserApplyDo {\n\tfor _, _f := range fields {\n\t\tu = *u.withDO(u.DO.Preload(_f))\n\t}\n\treturn &u\n}\n\nfunc (u userApplyDo) FirstOrInit() (*model.UserApply, error) {\n\tif result, err := u.DO.FirstOrInit(); err != nil {\n\t\treturn nil, err\n\t} else {\n\t\treturn result.(*model.UserApply), nil\n\t}\n}\n\nfunc (u userApplyDo) FirstOrCreate() (*model.UserApply, error) {\n\tif result, err := u.DO.FirstOrCreate(); err != nil {\n\t\treturn nil, err\n\t} else {\n\t\treturn result.(*model.UserApply), nil\n\t}\n}\n\nfunc (u userApplyDo) FindByPage(offset int, limit int) (result []*model.UserApply, count int64, err error) {\n\tresult, err = u.Offset(offset).Limit(limit).Find()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif size := len(result); 0 < limit && 0 < size && size < limit {\n\t\tcount = int64(size + offset)\n\t\treturn\n\t}\n\n\tcount, err = u.Offset(-1).Limit(-1).Count()\n\treturn\n}\n\nfunc (u userApplyDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) {\n\tcount, err = u.Count()\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = u.Offset(offset).Limit(limit).Scan(result)\n\treturn\n}\n\nfunc (u userApplyDo) Scan(result interface{}) (err error) {\n\treturn u.DO.Scan(result)\n}\n\nfunc (u userApplyDo) Delete(models ...*model.UserApply) (result gen.ResultInfo, err error) {\n\treturn u.DO.Delete(models)\n}\n\nfunc (u *userApplyDo) withDO(do gen.Dao) *userApplyDo {\n\tu.DO = *do.(*gen.DO)\n\treturn u\n}\n"
  },
  {
    "path": "dal/query/user_friend.gen.go",
    "content": "// Code generated by gorm.io/gen. DO NOT EDIT.\n// Code generated by gorm.io/gen. DO NOT EDIT.\n// Code generated by gorm.io/gen. DO NOT EDIT.\n\npackage query\n\nimport (\n\t\"context\"\n\n\t\"gorm.io/gorm\"\n\t\"gorm.io/gorm/clause\"\n\t\"gorm.io/gorm/schema\"\n\n\t\"gorm.io/gen\"\n\t\"gorm.io/gen/field\"\n\n\t\"gorm.io/plugin/dbresolver\"\n\n\t\"DiTing-Go/dal/model\"\n)\n\nfunc newUserFriend(db *gorm.DB, opts ...gen.DOOption) userFriend {\n\t_userFriend := userFriend{}\n\n\t_userFriend.userFriendDo.UseDB(db, opts...)\n\t_userFriend.userFriendDo.UseModel(&model.UserFriend{})\n\n\ttableName := _userFriend.userFriendDo.TableName()\n\t_userFriend.ALL = field.NewAsterisk(tableName)\n\t_userFriend.ID = field.NewInt64(tableName, \"id\")\n\t_userFriend.UID = field.NewInt64(tableName, \"uid\")\n\t_userFriend.FriendUID = field.NewInt64(tableName, \"friend_uid\")\n\t_userFriend.DeleteStatus = field.NewInt32(tableName, \"delete_status\")\n\t_userFriend.CreateTime = field.NewTime(tableName, \"create_time\")\n\t_userFriend.UpdateTime = field.NewTime(tableName, \"update_time\")\n\n\t_userFriend.fillFieldMap()\n\n\treturn _userFriend\n}\n\n// userFriend 用户联系人表\ntype userFriend struct {\n\tuserFriendDo userFriendDo\n\n\tALL          field.Asterisk\n\tID           field.Int64 // id\n\tUID          field.Int64 // uid\n\tFriendUID    field.Int64 // 好友uid\n\tDeleteStatus field.Int32 // 逻辑删除(0-正常,1-删除)\n\tCreateTime   field.Time  // 创建时间\n\tUpdateTime   field.Time  // 修改时间\n\n\tfieldMap map[string]field.Expr\n}\n\nfunc (u userFriend) Table(newTableName string) *userFriend {\n\tu.userFriendDo.UseTable(newTableName)\n\treturn u.updateTableName(newTableName)\n}\n\nfunc (u userFriend) As(alias string) *userFriend {\n\tu.userFriendDo.DO = *(u.userFriendDo.As(alias).(*gen.DO))\n\treturn u.updateTableName(alias)\n}\n\nfunc (u *userFriend) updateTableName(table string) *userFriend {\n\tu.ALL = field.NewAsterisk(table)\n\tu.ID = field.NewInt64(table, \"id\")\n\tu.UID = field.NewInt64(table, \"uid\")\n\tu.FriendUID = field.NewInt64(table, \"friend_uid\")\n\tu.DeleteStatus = field.NewInt32(table, \"delete_status\")\n\tu.CreateTime = field.NewTime(table, \"create_time\")\n\tu.UpdateTime = field.NewTime(table, \"update_time\")\n\n\tu.fillFieldMap()\n\n\treturn u\n}\n\nfunc (u *userFriend) WithContext(ctx context.Context) IUserFriendDo {\n\treturn u.userFriendDo.WithContext(ctx)\n}\n\nfunc (u userFriend) TableName() string { return u.userFriendDo.TableName() }\n\nfunc (u userFriend) Alias() string { return u.userFriendDo.Alias() }\n\nfunc (u userFriend) Columns(cols ...field.Expr) gen.Columns { return u.userFriendDo.Columns(cols...) }\n\nfunc (u *userFriend) GetFieldByName(fieldName string) (field.OrderExpr, bool) {\n\t_f, ok := u.fieldMap[fieldName]\n\tif !ok || _f == nil {\n\t\treturn nil, false\n\t}\n\t_oe, ok := _f.(field.OrderExpr)\n\treturn _oe, ok\n}\n\nfunc (u *userFriend) fillFieldMap() {\n\tu.fieldMap = make(map[string]field.Expr, 6)\n\tu.fieldMap[\"id\"] = u.ID\n\tu.fieldMap[\"uid\"] = u.UID\n\tu.fieldMap[\"friend_uid\"] = u.FriendUID\n\tu.fieldMap[\"delete_status\"] = u.DeleteStatus\n\tu.fieldMap[\"create_time\"] = u.CreateTime\n\tu.fieldMap[\"update_time\"] = u.UpdateTime\n}\n\nfunc (u userFriend) clone(db *gorm.DB) userFriend {\n\tu.userFriendDo.ReplaceConnPool(db.Statement.ConnPool)\n\treturn u\n}\n\nfunc (u userFriend) replaceDB(db *gorm.DB) userFriend {\n\tu.userFriendDo.ReplaceDB(db)\n\treturn u\n}\n\ntype userFriendDo struct{ gen.DO }\n\ntype IUserFriendDo interface {\n\tgen.SubQuery\n\tDebug() IUserFriendDo\n\tWithContext(ctx context.Context) IUserFriendDo\n\tWithResult(fc func(tx gen.Dao)) gen.ResultInfo\n\tReplaceDB(db *gorm.DB)\n\tReadDB() IUserFriendDo\n\tWriteDB() IUserFriendDo\n\tAs(alias string) gen.Dao\n\tSession(config *gorm.Session) IUserFriendDo\n\tColumns(cols ...field.Expr) gen.Columns\n\tClauses(conds ...clause.Expression) IUserFriendDo\n\tNot(conds ...gen.Condition) IUserFriendDo\n\tOr(conds ...gen.Condition) IUserFriendDo\n\tSelect(conds ...field.Expr) IUserFriendDo\n\tWhere(conds ...gen.Condition) IUserFriendDo\n\tOrder(conds ...field.Expr) IUserFriendDo\n\tDistinct(cols ...field.Expr) IUserFriendDo\n\tOmit(cols ...field.Expr) IUserFriendDo\n\tJoin(table schema.Tabler, on ...field.Expr) IUserFriendDo\n\tLeftJoin(table schema.Tabler, on ...field.Expr) IUserFriendDo\n\tRightJoin(table schema.Tabler, on ...field.Expr) IUserFriendDo\n\tGroup(cols ...field.Expr) IUserFriendDo\n\tHaving(conds ...gen.Condition) IUserFriendDo\n\tLimit(limit int) IUserFriendDo\n\tOffset(offset int) IUserFriendDo\n\tCount() (count int64, err error)\n\tScopes(funcs ...func(gen.Dao) gen.Dao) IUserFriendDo\n\tUnscoped() IUserFriendDo\n\tCreate(values ...*model.UserFriend) error\n\tCreateInBatches(values []*model.UserFriend, batchSize int) error\n\tSave(values ...*model.UserFriend) error\n\tFirst() (*model.UserFriend, error)\n\tTake() (*model.UserFriend, error)\n\tLast() (*model.UserFriend, error)\n\tFind() ([]*model.UserFriend, error)\n\tFindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.UserFriend, err error)\n\tFindInBatches(result *[]*model.UserFriend, batchSize int, fc func(tx gen.Dao, batch int) error) error\n\tPluck(column field.Expr, dest interface{}) error\n\tDelete(...*model.UserFriend) (info gen.ResultInfo, err error)\n\tUpdate(column field.Expr, value interface{}) (info gen.ResultInfo, err error)\n\tUpdateSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error)\n\tUpdates(value interface{}) (info gen.ResultInfo, err error)\n\tUpdateColumn(column field.Expr, value interface{}) (info gen.ResultInfo, err error)\n\tUpdateColumnSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error)\n\tUpdateColumns(value interface{}) (info gen.ResultInfo, err error)\n\tUpdateFrom(q gen.SubQuery) gen.Dao\n\tAttrs(attrs ...field.AssignExpr) IUserFriendDo\n\tAssign(attrs ...field.AssignExpr) IUserFriendDo\n\tJoins(fields ...field.RelationField) IUserFriendDo\n\tPreload(fields ...field.RelationField) IUserFriendDo\n\tFirstOrInit() (*model.UserFriend, error)\n\tFirstOrCreate() (*model.UserFriend, error)\n\tFindByPage(offset int, limit int) (result []*model.UserFriend, count int64, err error)\n\tScanByPage(result interface{}, offset int, limit int) (count int64, err error)\n\tScan(result interface{}) (err error)\n\tReturning(value interface{}, columns ...string) IUserFriendDo\n\tUnderlyingDB() *gorm.DB\n\tschema.Tabler\n}\n\nfunc (u userFriendDo) Debug() IUserFriendDo {\n\treturn u.withDO(u.DO.Debug())\n}\n\nfunc (u userFriendDo) WithContext(ctx context.Context) IUserFriendDo {\n\treturn u.withDO(u.DO.WithContext(ctx))\n}\n\nfunc (u userFriendDo) ReadDB() IUserFriendDo {\n\treturn u.Clauses(dbresolver.Read)\n}\n\nfunc (u userFriendDo) WriteDB() IUserFriendDo {\n\treturn u.Clauses(dbresolver.Write)\n}\n\nfunc (u userFriendDo) Session(config *gorm.Session) IUserFriendDo {\n\treturn u.withDO(u.DO.Session(config))\n}\n\nfunc (u userFriendDo) Clauses(conds ...clause.Expression) IUserFriendDo {\n\treturn u.withDO(u.DO.Clauses(conds...))\n}\n\nfunc (u userFriendDo) Returning(value interface{}, columns ...string) IUserFriendDo {\n\treturn u.withDO(u.DO.Returning(value, columns...))\n}\n\nfunc (u userFriendDo) Not(conds ...gen.Condition) IUserFriendDo {\n\treturn u.withDO(u.DO.Not(conds...))\n}\n\nfunc (u userFriendDo) Or(conds ...gen.Condition) IUserFriendDo {\n\treturn u.withDO(u.DO.Or(conds...))\n}\n\nfunc (u userFriendDo) Select(conds ...field.Expr) IUserFriendDo {\n\treturn u.withDO(u.DO.Select(conds...))\n}\n\nfunc (u userFriendDo) Where(conds ...gen.Condition) IUserFriendDo {\n\treturn u.withDO(u.DO.Where(conds...))\n}\n\nfunc (u userFriendDo) Order(conds ...field.Expr) IUserFriendDo {\n\treturn u.withDO(u.DO.Order(conds...))\n}\n\nfunc (u userFriendDo) Distinct(cols ...field.Expr) IUserFriendDo {\n\treturn u.withDO(u.DO.Distinct(cols...))\n}\n\nfunc (u userFriendDo) Omit(cols ...field.Expr) IUserFriendDo {\n\treturn u.withDO(u.DO.Omit(cols...))\n}\n\nfunc (u userFriendDo) Join(table schema.Tabler, on ...field.Expr) IUserFriendDo {\n\treturn u.withDO(u.DO.Join(table, on...))\n}\n\nfunc (u userFriendDo) LeftJoin(table schema.Tabler, on ...field.Expr) IUserFriendDo {\n\treturn u.withDO(u.DO.LeftJoin(table, on...))\n}\n\nfunc (u userFriendDo) RightJoin(table schema.Tabler, on ...field.Expr) IUserFriendDo {\n\treturn u.withDO(u.DO.RightJoin(table, on...))\n}\n\nfunc (u userFriendDo) Group(cols ...field.Expr) IUserFriendDo {\n\treturn u.withDO(u.DO.Group(cols...))\n}\n\nfunc (u userFriendDo) Having(conds ...gen.Condition) IUserFriendDo {\n\treturn u.withDO(u.DO.Having(conds...))\n}\n\nfunc (u userFriendDo) Limit(limit int) IUserFriendDo {\n\treturn u.withDO(u.DO.Limit(limit))\n}\n\nfunc (u userFriendDo) Offset(offset int) IUserFriendDo {\n\treturn u.withDO(u.DO.Offset(offset))\n}\n\nfunc (u userFriendDo) Scopes(funcs ...func(gen.Dao) gen.Dao) IUserFriendDo {\n\treturn u.withDO(u.DO.Scopes(funcs...))\n}\n\nfunc (u userFriendDo) Unscoped() IUserFriendDo {\n\treturn u.withDO(u.DO.Unscoped())\n}\n\nfunc (u userFriendDo) Create(values ...*model.UserFriend) error {\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn u.DO.Create(values)\n}\n\nfunc (u userFriendDo) CreateInBatches(values []*model.UserFriend, batchSize int) error {\n\treturn u.DO.CreateInBatches(values, batchSize)\n}\n\n// Save : !!! underlying implementation is different with GORM\n// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values)\nfunc (u userFriendDo) Save(values ...*model.UserFriend) error {\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn u.DO.Save(values)\n}\n\nfunc (u userFriendDo) First() (*model.UserFriend, error) {\n\tif result, err := u.DO.First(); err != nil {\n\t\treturn nil, err\n\t} else {\n\t\treturn result.(*model.UserFriend), nil\n\t}\n}\n\nfunc (u userFriendDo) Take() (*model.UserFriend, error) {\n\tif result, err := u.DO.Take(); err != nil {\n\t\treturn nil, err\n\t} else {\n\t\treturn result.(*model.UserFriend), nil\n\t}\n}\n\nfunc (u userFriendDo) Last() (*model.UserFriend, error) {\n\tif result, err := u.DO.Last(); err != nil {\n\t\treturn nil, err\n\t} else {\n\t\treturn result.(*model.UserFriend), nil\n\t}\n}\n\nfunc (u userFriendDo) Find() ([]*model.UserFriend, error) {\n\tresult, err := u.DO.Find()\n\treturn result.([]*model.UserFriend), err\n}\n\nfunc (u userFriendDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.UserFriend, err error) {\n\tbuf := make([]*model.UserFriend, 0, batchSize)\n\terr = u.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error {\n\t\tdefer func() { results = append(results, buf...) }()\n\t\treturn fc(tx, batch)\n\t})\n\treturn results, err\n}\n\nfunc (u userFriendDo) FindInBatches(result *[]*model.UserFriend, batchSize int, fc func(tx gen.Dao, batch int) error) error {\n\treturn u.DO.FindInBatches(result, batchSize, fc)\n}\n\nfunc (u userFriendDo) Attrs(attrs ...field.AssignExpr) IUserFriendDo {\n\treturn u.withDO(u.DO.Attrs(attrs...))\n}\n\nfunc (u userFriendDo) Assign(attrs ...field.AssignExpr) IUserFriendDo {\n\treturn u.withDO(u.DO.Assign(attrs...))\n}\n\nfunc (u userFriendDo) Joins(fields ...field.RelationField) IUserFriendDo {\n\tfor _, _f := range fields {\n\t\tu = *u.withDO(u.DO.Joins(_f))\n\t}\n\treturn &u\n}\n\nfunc (u userFriendDo) Preload(fields ...field.RelationField) IUserFriendDo {\n\tfor _, _f := range fields {\n\t\tu = *u.withDO(u.DO.Preload(_f))\n\t}\n\treturn &u\n}\n\nfunc (u userFriendDo) FirstOrInit() (*model.UserFriend, error) {\n\tif result, err := u.DO.FirstOrInit(); err != nil {\n\t\treturn nil, err\n\t} else {\n\t\treturn result.(*model.UserFriend), nil\n\t}\n}\n\nfunc (u userFriendDo) FirstOrCreate() (*model.UserFriend, error) {\n\tif result, err := u.DO.FirstOrCreate(); err != nil {\n\t\treturn nil, err\n\t} else {\n\t\treturn result.(*model.UserFriend), nil\n\t}\n}\n\nfunc (u userFriendDo) FindByPage(offset int, limit int) (result []*model.UserFriend, count int64, err error) {\n\tresult, err = u.Offset(offset).Limit(limit).Find()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif size := len(result); 0 < limit && 0 < size && size < limit {\n\t\tcount = int64(size + offset)\n\t\treturn\n\t}\n\n\tcount, err = u.Offset(-1).Limit(-1).Count()\n\treturn\n}\n\nfunc (u userFriendDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) {\n\tcount, err = u.Count()\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = u.Offset(offset).Limit(limit).Scan(result)\n\treturn\n}\n\nfunc (u userFriendDo) Scan(result interface{}) (err error) {\n\treturn u.DO.Scan(result)\n}\n\nfunc (u userFriendDo) Delete(models ...*model.UserFriend) (result gen.ResultInfo, err error) {\n\treturn u.DO.Delete(models)\n}\n\nfunc (u *userFriendDo) withDO(do gen.Dao) *userFriendDo {\n\tu.DO = *do.(*gen.DO)\n\treturn u\n}\n"
  },
  {
    "path": "docs/docs.go",
    "content": "// Code generated by swaggo/swag. DO NOT EDIT.\n\npackage docs\n\nimport \"github.com/swaggo/swag\"\n\nconst docTemplate = `{\n    \"schemes\": {{ marshal .Schemes }},\n    \"swagger\": \"2.0\",\n    \"info\": {\n        \"description\": \"{{escape .Description}}\",\n        \"title\": \"{{.Title}}\",\n        \"contact\": {},\n        \"version\": \"{{.Version}}\"\n    },\n    \"host\": \"{{.Host}}\",\n    \"basePath\": \"{{.BasePath}}\",\n    \"paths\": {\n        \"/api/contact/add\": {\n            \"post\": {\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"summary\": \"添加好友\",\n                \"parameters\": [\n                    {\n                        \"description\": \"好友uid\",\n                        \"name\": \"uid\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"type\": \"integer\"\n                        }\n                    },\n                    {\n                        \"description\": \"验证消息\",\n                        \"name\": \"msg\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"成功\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/resp.ResponseData\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"内部错误\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/resp.ResponseData\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/api/contact/delete\": {\n            \"put\": {\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"summary\": \"同意好友申请\",\n                \"parameters\": [\n                    {\n                        \"description\": \"好友uid\",\n                        \"name\": \"uid\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"type\": \"integer\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"成功\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/resp.ResponseData\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"内部错误\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/resp.ResponseData\"\n                        }\n                    }\n                }\n            },\n            \"delete\": {\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"summary\": \"删除好友\",\n                \"parameters\": [\n                    {\n                        \"description\": \"好友uid\",\n                        \"name\": \"uid\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"type\": \"integer\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"成功\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/resp.ResponseData\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"内部错误\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/resp.ResponseData\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/api/contact/getApplyList\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"summary\": \"获取用户好友申请列表\",\n                \"parameters\": [\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"last_id\",\n                        \"name\": \"last_id\",\n                        \"in\": \"query\",\n                        \"required\": true\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"limit\",\n                        \"name\": \"limit\",\n                        \"in\": \"query\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"成功\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/resp.ResponseData\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"内部错误\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/resp.ResponseData\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/api/public/login\": {\n            \"post\": {\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"summary\": \"用户登录\",\n                \"parameters\": [\n                    {\n                        \"description\": \"用户名\",\n                        \"name\": \"name\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    },\n                    {\n                        \"description\": \"密码\",\n                        \"name\": \"password\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"成功\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/resp.ResponseData\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"内部错误\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/resp.ResponseData\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/api/public/register\": {\n            \"post\": {\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"summary\": \"用户注册\",\n                \"parameters\": [\n                    {\n                        \"description\": \"用户名\",\n                        \"name\": \"name\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    },\n                    {\n                        \"description\": \"密码\",\n                        \"name\": \"password\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"成功\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/resp.ResponseData\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"内部错误\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/resp.ResponseData\"\n                        }\n                    }\n                }\n            }\n        }\n    },\n    \"definitions\": {\n        \"resp.ResponseData\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"code\": {\n                    \"description\": \"状态码\",\n                    \"type\": \"integer\"\n                },\n                \"data\": {\n                    \"description\": \"响应数据\"\n                },\n                \"message\": {\n                    \"description\": \"响应消息\",\n                    \"type\": \"string\"\n                }\n            }\n        }\n    },\n    \"securityDefinitions\": {\n        \"ApiKeyAuth\": {\n            \"type\": \"apiKey\",\n            \"name\": \"Authorization\",\n            \"in\": \"header\"\n        }\n    }\n}`\n\n// SwaggerInfo holds exported Swagger Info so clients can modify it\nvar SwaggerInfo = &swag.Spec{\n\tVersion:          \"\",\n\tHost:             \"\",\n\tBasePath:         \"\",\n\tSchemes:          []string{},\n\tTitle:            \"\",\n\tDescription:      \"\",\n\tInfoInstanceName: \"swagger\",\n\tSwaggerTemplate:  docTemplate,\n\tLeftDelim:        \"{{\",\n\tRightDelim:       \"}}\",\n}\n\nfunc init() {\n\tswag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)\n}\n"
  },
  {
    "path": "docs/swagger.json",
    "content": "{\n    \"swagger\": \"2.0\",\n    \"info\": {\n        \"contact\": {}\n    },\n    \"paths\": {\n        \"/api/contact/add\": {\n            \"post\": {\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"summary\": \"添加好友\",\n                \"parameters\": [\n                    {\n                        \"description\": \"好友uid\",\n                        \"name\": \"uid\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"type\": \"integer\"\n                        }\n                    },\n                    {\n                        \"description\": \"验证消息\",\n                        \"name\": \"msg\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"成功\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/resp.ResponseData\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"内部错误\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/resp.ResponseData\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/api/contact/delete\": {\n            \"put\": {\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"summary\": \"同意好友申请\",\n                \"parameters\": [\n                    {\n                        \"description\": \"好友uid\",\n                        \"name\": \"uid\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"type\": \"integer\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"成功\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/resp.ResponseData\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"内部错误\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/resp.ResponseData\"\n                        }\n                    }\n                }\n            },\n            \"delete\": {\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"summary\": \"删除好友\",\n                \"parameters\": [\n                    {\n                        \"description\": \"好友uid\",\n                        \"name\": \"uid\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"type\": \"integer\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"成功\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/resp.ResponseData\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"内部错误\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/resp.ResponseData\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/api/contact/getApplyList\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"summary\": \"获取用户好友申请列表\",\n                \"parameters\": [\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"last_id\",\n                        \"name\": \"last_id\",\n                        \"in\": \"query\",\n                        \"required\": true\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"limit\",\n                        \"name\": \"limit\",\n                        \"in\": \"query\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"成功\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/resp.ResponseData\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"内部错误\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/resp.ResponseData\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/api/public/login\": {\n            \"post\": {\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"summary\": \"用户登录\",\n                \"parameters\": [\n                    {\n                        \"description\": \"用户名\",\n                        \"name\": \"name\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    },\n                    {\n                        \"description\": \"密码\",\n                        \"name\": \"password\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"成功\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/resp.ResponseData\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"内部错误\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/resp.ResponseData\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/api/public/register\": {\n            \"post\": {\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"summary\": \"用户注册\",\n                \"parameters\": [\n                    {\n                        \"description\": \"用户名\",\n                        \"name\": \"name\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    },\n                    {\n                        \"description\": \"密码\",\n                        \"name\": \"password\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"成功\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/resp.ResponseData\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"内部错误\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/resp.ResponseData\"\n                        }\n                    }\n                }\n            }\n        }\n    },\n    \"definitions\": {\n        \"resp.ResponseData\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"code\": {\n                    \"description\": \"状态码\",\n                    \"type\": \"integer\"\n                },\n                \"data\": {\n                    \"description\": \"响应数据\"\n                },\n                \"message\": {\n                    \"description\": \"响应消息\",\n                    \"type\": \"string\"\n                }\n            }\n        }\n    },\n    \"securityDefinitions\": {\n        \"ApiKeyAuth\": {\n            \"type\": \"apiKey\",\n            \"name\": \"Authorization\",\n            \"in\": \"header\"\n        }\n    }\n}"
  },
  {
    "path": "domain/dto/contact_dto.go",
    "content": "package dto\n\ntype ContactDto struct {\n\t// 会话ID\n\tID int64 `json:\"id\"`\n\t// 房间ID\n\tRoomID int64 `json:\"roomId\"`\n\t// 头像\n\tAvatar string `json:\"avatar\"`\n\t// 会话名称\n\tName string `json:\"name\"`\n\t// 最后一条消息内容\n\tLastMsg string `json:\"lastMsg\"`\n\t// 最后一条消息时间 时间戳格式\n\tLastTime int64 `json:\"lastTime\"`\n\t// 未读消息数\n\tUnreadCount int32 `json:\"unreadCount\"`\n\t// 会话类型\n\tType int `json:\"type\"`\n}\n"
  },
  {
    "path": "domain/dto/delete_friend_dto.go",
    "content": "package dto\n\ntype DeleteFriendDto struct {\n\tUid       int64 `json:\"uid\"`\n\tFriendUid int64 `json:\"friend_uid\"`\n}\n"
  },
  {
    "path": "domain/dto/group_dto.go",
    "content": "package dto\n\nimport \"time\"\n\ntype GetGroupMemberDto struct {\n\t// 用户ID\n\tUID int64 `json:\"uid\" gorm:\"column:id\"`\n\t// 用户名\n\tName string `json:\"name\"`\n\t// 头像\n\tAvatar string `json:\"avatar\"`\n\t// 用户状态\n\tActiveStatus int32 `json:\"activeStatus\"`\n\t// 最后活跃时间\n\tLastOptTime time.Time `json:\"lastOptTime\"`\n}\n"
  },
  {
    "path": "domain/dto/msg_dto.go",
    "content": "package dto\n\ntype MessageBaseDto struct {\n\t// 下载地址\n\tUrl string `json:\"url\"`\n\t// 文件大小\n\tSize int64 `json:\"size\"`\n\t// 文件名\n\tName string `json:\"name\"`\n}\n\ntype ImgMessageDto struct {\n\tMessageBaseDto MessageBaseDto `json:\"message_base_dto\"`\n\t// 图片高度\n\tHeight int `json:\"height\"`\n\t// 图片宽度\n\tWidth int `json:\"width\"`\n}\n"
  },
  {
    "path": "domain/enum/event_bus.go",
    "content": "package enum\n\nconst (\n\tFriendApplyEvent = \"main:FriendApplyEvent\"\n\tFriendNewEvent   = \"main:FriendNewEvent\"\n\tNewMessageEvent  = \"main:NewMessageEvent\"\n)\n"
  },
  {
    "path": "domain/enum/login.go",
    "content": "package enum\n\nconst (\n\tLoginByPassword     = 1 // 用户名密码登录\n\tLoginByPhoneCaptcha = 2 // 手机号验证码登录\n)\n"
  },
  {
    "path": "domain/enum/message.go",
    "content": "package enum\n\nconst (\n\tTextMessage = 1\n)\n\nconst (\n\tTextMessageType = 1\n\tImgMessageType  = 3\n)\n"
  },
  {
    "path": "domain/enum/redis.go",
    "content": "package enum\n\nimport \"time\"\n\nconst (\n\tProject    = \"diting:\"\n\tUser       = Project + \"User\"\n\tUserFriend = Project + \"userFriend:\"\n\tUserApply  = Project + \"userApply:\"\n\tRoomFriend = Project + \"roomFriend:\"\n\tContact    = Project + \"contact:\"\n\tRoom       = Project + \"room:\"\n)\nconst (\n\t// 房间缓存\n\tRoomCacheByID = Room + \"%d\"\n\n\t// 好友房间缓存\n\tRoomFriendCacheByRoomID          = RoomFriend + \"%d\"\n\tRoomFriendCacheByUidAndFriendUid = RoomFriend + \"%d_%d\"\n\n\t// phoneUid映射\n\tPhoneUidMap      = User + \"PhoneUid:\" + \"%s\"\n\tUserCacheByID    = User + \"%d\"\n\tUserCacheByName  = User + \"%s\"\n\tUserCacheByPhone = User + \"Phone:\" + \"%s\"\n\tUserCaptcha      = User + \"Captcha:\" + \"%s\"\n\n\t// 用户好友缓存\n\tUserFriendCacheByUidAndFriendUid = UserFriend + \"%d_%d\"\n\n\t// 好友申请缓存\n\tUserApplyCacheByUidAndFriendUid = UserApply + \"%d_%d\"\n\n\t// 会话缓存\n\tContactCacheById = Contact + \"%d\"\n)\n\nconst (\n\tDefaultCacheTime = 7 * 24 * time.Hour\n\tNotExpireTime    = 10 * 365 * 24 * time.Hour\n)\n"
  },
  {
    "path": "domain/enum/redsync.go",
    "content": "package enum\n\nconst (\n\tLock              = \"lock:\"\n\tUserLock          = Lock + \"diting-user:\"\n\tUserAndFriendLock = UserLock + \"%d_%d\"\n)\n"
  },
  {
    "path": "domain/enum/rocketmq.go",
    "content": "package enum\n\nconst (\n\tUserLoginTopic    = \"diting-login\"\n\tNewFriendTopic    = \"diting-new-friend\"\n\tNewMessageTopic   = \"diting-new-message\"\n\tDeleteFriendTopic = \"diting-delete-friend\"\n\tFriendApplyTopic  = \"diting-friend-apply\"\n)\n"
  },
  {
    "path": "domain/enum/room.go",
    "content": "package enum\n\nconst (\n\t// 房间状态\n\tHOT    = 1\n\tNORMAL = 0\n\t// 房间类型\n\tGROUP    = 1\n\tPERSONAL = 2\n)\n"
  },
  {
    "path": "domain/enum/user.go",
    "content": "package enum\n\nconst (\n\tUserStatusNormal = 1\n\tUserStatusCancel = 2\n)\n"
  },
  {
    "path": "domain/model/message.go",
    "content": "package model\n\nimport (\n\t\"DiTing-Go/dal/model\"\n\t\"DiTing-Go/domain/enum\"\n)\n\ntype Message model.Message\n\nfunc (msg Message) GetContactMsg() string {\n\tif msg.Type == enum.TextMessageType {\n\t\treturn msg.Content\n\t} else if msg.Type == enum.ImgMessageType {\n\t\treturn \"[图片]\"\n\t}\n\treturn msg.Content\n}\n"
  },
  {
    "path": "domain/vo/req/agree_friend_req.go",
    "content": "package req\n\ntype AgreeFriendReq struct {\n\tUid int64 `json:\"uid\" binding:\"required\"`\n}\n"
  },
  {
    "path": "domain/vo/req/captcha_req.go",
    "content": "package req\n\ntype CaptchaReq struct {\n\t// 手机号\n\tPhone string `json:\"phone\" binding:\"required\"`\n}\n"
  },
  {
    "path": "domain/vo/req/create_group_req.go",
    "content": "package req\n\ntype CreateGroupReq struct {\n\tUidList []int64 `json:\"uidList\" binding:\"required\"`\n}\n"
  },
  {
    "path": "domain/vo/req/delete_friend_req.go",
    "content": "package req\n\ntype DeleteFriendReq struct {\n\tUid int64 `json:\"uid\" binding:\"required\"`\n}\n"
  },
  {
    "path": "domain/vo/req/delete_group_req.go",
    "content": "package req\n\ntype DeleteGroupReq struct {\n\tID int64 `uri:\"id\" binding:\"required\"`\n}\n"
  },
  {
    "path": "domain/vo/req/get_group_member_list_req.go",
    "content": "package req\n\ntype GetGroupMemberListReq struct {\n\t// 房间ID\n\tRoomId   int64   `form:\"roomId\" binding:\"required\"`\n\tCursor   *string `form:\"cursor\"`\n\tPageSize int     `form:\"pageSize\" binding:\"required\"`\n}\n"
  },
  {
    "path": "domain/vo/req/get_message_list_req.go",
    "content": "package req\n\ntype GetMessageListReq struct {\n\tRoomId   int64   `json:\"roomId\" form:\"roomId\" binding:\"required\"`\n\tCursor   *string `json:\"cursor\" form:\"cursor\" binding:\"required\"`\n\tPageSize int     `json:\"pageSize\" form:\"pageSize\" binding:\"required\"`\n}\n"
  },
  {
    "path": "domain/vo/req/get_new_content_list_req.go",
    "content": "package req\n\ntype GetNewContentListReq struct {\n\tTimestamp int64 `json:\"timestamp\" form:\"timestamp\" binding:\"required\"`\n}\n"
  },
  {
    "path": "domain/vo/req/get_new_msg_list_req.go",
    "content": "package req\n\ntype GetNewMsgListReq struct {\n\t// 房间ID\n\tRoomId int64 `json:\"roomId\" form:\"roomId\" binding:\"required\"`\n\t// 消息ID\n\tMsgId int64 `json:\"msgId\" form:\"msgId\" binding:\"required\"`\n}\n"
  },
  {
    "path": "domain/vo/req/get_user_info_batch_req.go",
    "content": "package req\n\ntype UserInfoBatchReqItem struct {\n\tUid            int64 `json:\"uid\"`\n\tLastModifyTime int64 `json:\"lastModifyTime\"`\n}\ntype GetUserInfoBatchReq struct {\n\tList []UserInfoBatchReqItem `json:\"list\" bind:\"required\"`\n}\n"
  },
  {
    "path": "domain/vo/req/get_user_info_by_name_req.go",
    "content": "package req\n\ntype GetUserInfoByNameReq struct {\n\t// 用户名\n\tName string `form:\"name\" binding:\"required\"`\n}\n"
  },
  {
    "path": "domain/vo/req/grant_administrator_req.go",
    "content": "package req\n\ntype GrantAdministratorReq struct {\n\t// 房间ID\n\tRoomId int64 `json:\"room_id\" binding:\"required\"`\n\t// 授权用户ID\n\tGrantUid int64 `json:\"grant_uid\" binding:\"required\"`\n}\n"
  },
  {
    "path": "domain/vo/req/is_friend_req.go",
    "content": "package req\n\n// IsFriendReq 是否是好友请求\ntype IsFriendReq struct {\n\tFriendUid int64 `uri:\"friendUid\" binding:\"required\"`\n}\n"
  },
  {
    "path": "domain/vo/req/join_group_req.go",
    "content": "package req\n\ntype JoinGroupReq struct {\n\tID int64 `json:\"id\" binding:\"required\"`\n}\n"
  },
  {
    "path": "domain/vo/req/message_req.go",
    "content": "package req\n\ntype MessageBody struct {\n\tContent    string `json:\"content\" form:\"content\" binding:\"required\"`\n\tReplyMsgId int64  `json:\"replyMsgId\" form:\"replyMsgId\"`\n}\ntype MessageReq struct {\n\tRoomId  int64       `json:\"roomId\" form:\"roomId\" binding:\"required\"`\n\tMsgType int32       `json:\"msgType\" form:\"msgType\" binding:\"required\"`\n\tBody    MessageBody `json:\"body\" form:\"body\" binding:\"required\"`\n}\n"
  },
  {
    "path": "domain/vo/req/quit_group_req.go",
    "content": "package req\n\ntype QuitGroupReq struct {\n\t// 房间di\n\tID int64 `json:\"id\" binding:\"required\"`\n}\n"
  },
  {
    "path": "domain/vo/req/remove_administrator_req.go",
    "content": "package req\n\ntype RemoveAdministratorReq struct {\n\t// 房间ID\n\tRoomId int64 `json:\"room_id\" binding:\"required\"`\n\t// 移除用户ID\n\tRemoveUid int64 `json:\"remove_uid\" binding:\"required\"`\n}\n"
  },
  {
    "path": "domain/vo/req/uid_req.go",
    "content": "package req\n\ntype UidReq struct {\n\tUid int64 `json:\"uid\"`\n}\n"
  },
  {
    "path": "domain/vo/req/user_apply_req.go",
    "content": "package req\n\ntype UserApplyReq struct {\n\tUid int64  `json:\"uid\"`\n\tMsg string `json:\"msg\"`\n}\n"
  },
  {
    "path": "domain/vo/req/user_cancle_req.go",
    "content": "package req\n\ntype UserCancelReq struct {\n\tCaptcha string `json:\"captcha\" binding:\"required\"`\n}\n"
  },
  {
    "path": "domain/vo/req/user_login_req.go",
    "content": "package req\n\ntype UserLoginReq struct {\n\tUserName  string `json:\"username,omitempty\"`           // 用户名可选\n\tPassword  string `json:\"password,omitempty\"`           // 密码可选\n\tPhone     string `json:\"phone,omitempty\"`              // 手机号可选\n\tCaptcha   string `json:\"captcha,omitempty\"`            // 验证码\n\tLoginType int    `json:\"loginType\" binding:\"required\"` // 登录类型, 1: 用户名密码登录, 2: 手机号验证码登录\n}\n"
  },
  {
    "path": "domain/vo/req/user_register_req.go",
    "content": "package req\n\ntype UserRegisterReq struct {\n\t// 用户名\n\tUsername string `json:\"username\" binding:\"required\"`\n\t// 密码\n\tPassword string `json:\"password\" binding:\"required\"`\n\t// 手机号\n\tPhone string `json:\"phone\" binding:\"required\"`\n\t// 验证码\n\tCaptcha string `json:\"captcha\" binding:\"required\"` // 如果需要验证码，可以取消注释\n}\n"
  },
  {
    "path": "domain/vo/resp/get_user_info_batch_resp.go",
    "content": "package resp\n\ntype GetUserInfoBatchResp struct {\n\tUid         int64  `json:\"uid\"`\n\tUsername    string `json:\"name\"`\n\tAvatar      string `json:\"avatar\"`\n\tNeedRefresh bool   `json:\"needRefresh\"`\n}\n"
  },
  {
    "path": "domain/vo/resp/get_user_info_by_name_resp.go",
    "content": "package resp\n\ntype GetUserInfoByNameResp struct {\n\t// 用户ID\n\tUid int64 `json:\"uid\"`\n\t// 用户名\n\tName string `json:\"name\"`\n\t// 头像\n\tAvatar string `json:\"avatar\"`\n\t// 好友状态\n\tStatus int32 `json:\"status\"`\n}\n"
  },
  {
    "path": "domain/vo/resp/message_resp.go",
    "content": "package resp\n\ntype MsgUser struct {\n\tUid      int64  `json:\"uid\"`\n\tUsername string `json:\"username\"`\n\tAvatar   string `json:\"avatar\"`\n}\ntype Msg struct {\n\tID     int64    `json:\"id\"`\n\tRoomId int64    `json:\"roomId\"`\n\tType   int32    `json:\"type\"`\n\tBody   TextBody `json:\"body\"`\n}\ntype TextBody struct {\n\tContent string `json:\"content\"`\n\tReply   int64  `json:\"reply\"`\n}\ntype MessageResp struct {\n\tFromUser MsgUser `json:\"fromUser\"`\n\tMessage  Msg     `json:\"message\"`\n\tSendTime int64   `json:\"sendTime\"`\n}\n"
  },
  {
    "path": "domain/vo/resp/page_list_resp.go",
    "content": "package resp\n\ntype PageListResp struct {\n\tList  interface{} `json:\"dataList\"`\n\tTotal int         `json:\"total\"`\n}\n"
  },
  {
    "path": "domain/vo/resp/pre_signed_resp.go",
    "content": "package resp\n\ntype PreSignedResp struct {\n\tUrl    string            `json:\"url\"`\n\tPolicy map[string]string `json:\"policy\"`\n}\n"
  },
  {
    "path": "domain/vo/resp/user_apply_resp.go",
    "content": "package resp\n\ntype UserApplyResp struct {\n\tApplyId int64  `json:\"applyId\"` // 申请ID\n\tUid     int64  `json:\"uid\"`     // 用户ID\n\tMsg     string `json:\"msg\"`     // 申请信息\n\tStatus  int32  `json:\"status\"`  // 使用状态 1.待审批 2.已接受\n}\n"
  },
  {
    "path": "domain/vo/resp/user_contact_resp.go",
    "content": "package resp\n\ntype UserContactResp struct {\n\tUid          int64 `json:\"uid\"`          // 用户ID\n\tActiveStatus int   `json:\"activeStatus\"` // 用户状态\n\tLastOptTime  int64 `json:\"lastOptTime\"`  // 最后操作时间\n}\n"
  },
  {
    "path": "domain/vo/resp/user_login_resp.go",
    "content": "package resp\n\ntype UserLoginResp struct {\n\tToken  string `json:\"token\"`\n\tUid    int64  `json:\"uid\"`\n\tName   string `json:\"name\"`\n\tAvatar string `json:\"avatar\"`\n}\n"
  },
  {
    "path": "event/listener/friend_apply_event.go",
    "content": "package listener\n\nimport (\n\t\"DiTing-Go/dal/model\"\n\t\"DiTing-Go/domain/enum\"\n\t\"DiTing-Go/global\"\n\t\"DiTing-Go/utils/jsonUtils\"\n\t\"context\"\n\t\"github.com/apache/rocketmq-client-go/v2\"\n\t\"github.com/apache/rocketmq-client-go/v2/consumer\"\n\t\"github.com/apache/rocketmq-client-go/v2/primitive\"\n\t\"github.com/spf13/viper\"\n)\n\nfunc init() {\n\thost := viper.GetString(\"rocketmq.host\")\n\t// 设置推送消费者\n\trocketConsumer, _ := rocketmq.NewPushConsumer(\n\t\t//消费组\n\t\tconsumer.WithGroupName(enum.FriendApplyTopic),\n\t\t// namesrv地址\n\t\tconsumer.WithNameServer([]string{host}),\n\t)\n\terr := rocketConsumer.Subscribe(enum.FriendApplyTopic, consumer.MessageSelector{}, friendApplyEvent)\n\tif err != nil {\n\t\tglobal.Logger.Panicf(\"subscribe error: %s\", err.Error())\n\t}\n\terr = rocketConsumer.Start()\n\tif err != nil {\n\t\tglobal.Logger.Panicf(\"start consumer error: %s\", err.Error())\n\t}\n}\n\n// FriendApplyEvent 好友申请事件\nfunc friendApplyEvent(ctx context.Context, ext ...*primitive.MessageExt) (consumer.ConsumeResult, error) {\n\tfor i := range ext {\n\t\t// 解码\n\t\tuserApplyR := model.UserApply{}\n\t\tif err := jsonUtils.UnmarshalMsg(&userApplyR, ext[i]); err != nil {\n\t\t\tglobal.Logger.Errorf(\"jsonUtils unmarshal error: %s\", err.Error())\n\t\t\treturn consumer.ConsumeRetryLater, nil\n\t\t}\n\n\t\tif err := friendApply(userApplyR); err != nil {\n\t\t\tglobal.Logger.Errorf(\"friendApply error: %s\", err.Error())\n\t\t\treturn consumer.ConsumeRetryLater, nil\n\t\t}\n\t}\n\treturn consumer.ConsumeSuccess, nil\n}\n\nfunc friendApply(apply model.UserApply) error {\n\t// 发送新消息事件\n\t//service.Send(apply.TargetID)\n\treturn nil\n}\n"
  },
  {
    "path": "event/listener/friend_delete_event.go",
    "content": "package listener\n\nimport (\n\t\"DiTing-Go/dal/model\"\n\t\"DiTing-Go/domain/dto\"\n\t\"DiTing-Go/domain/enum\"\n\t\"DiTing-Go/global\"\n\tpkgEnum \"DiTing-Go/pkg/domain/enum\"\n\t\"DiTing-Go/pkg/utils\"\n\t\"DiTing-Go/utils/jsonUtils\"\n\t\"DiTing-Go/utils/redisCache\"\n\t\"context\"\n\t\"fmt\"\n\t\"github.com/apache/rocketmq-client-go/v2\"\n\t\"github.com/apache/rocketmq-client-go/v2/consumer\"\n\t\"github.com/apache/rocketmq-client-go/v2/primitive\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/spf13/viper\"\n\t\"sort\"\n)\n\nfunc init() {\n\thost := viper.GetString(\"rocketmq.host\")\n\t// 设置推送消费者\n\trocketConsumer, _ := rocketmq.NewPushConsumer(\n\t\t//消费组\n\t\tconsumer.WithGroupName(enum.DeleteFriendTopic),\n\t\t// namesrv地址\n\t\tconsumer.WithNameServer([]string{host}),\n\t)\n\terr := rocketConsumer.Subscribe(enum.DeleteFriendTopic, consumer.MessageSelector{}, deleteFriendEvent)\n\tif err != nil {\n\t\tglobal.Logger.Panicf(\"subscribe error: %s\", err.Error())\n\t}\n\terr = rocketConsumer.Start()\n\tif err != nil {\n\t\tglobal.Logger.Panicf(\"start consumer error: %s\", err.Error())\n\t}\n}\n\nfunc deleteFriendEvent(ctx context.Context, ext ...*primitive.MessageExt) (consumer.ConsumeResult, error) {\n\tfor i := range ext {\n\t\t// 解码\n\t\tdeleteFriendDto := dto.DeleteFriendDto{}\n\t\tif err := jsonUtils.UnmarshalMsg(&deleteFriendDto, ext[i]); err != nil {\n\t\t\tglobal.Logger.Errorf(\"jsonUtils unmarshal error: %s\", err.Error())\n\t\t\treturn consumer.ConsumeRetryLater, nil\n\t\t}\n\n\t\tif err := deleteFriend(deleteFriendDto); err != nil {\n\t\t\tglobal.Logger.Errorf(\"deleteFriend error: %s\", err.Error())\n\t\t\treturn consumer.ConsumeRetryLater, nil\n\t\t}\n\t}\n\treturn consumer.ConsumeSuccess, nil\n}\n\nfunc deleteFriend(deleteFriendDto dto.DeleteFriendDto) error {\n\n\tuid := deleteFriendDto.Uid\n\tdeleteFriendUid := deleteFriendDto.FriendUid\n\n\t// 加锁\n\tuids := utils.Int64Slice{uid, deleteFriendUid}\n\tsort.Sort(uids)\n\tkey := fmt.Sprintf(enum.UserAndFriendLock, uids[0], uids[1])\n\tmutex, err := utils.GetLock(key)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer utils.ReleaseLock(mutex)\n\n\tctx := context.Background()\n\ttx := global.Query.Begin()\n\tuserApply := global.Query.UserApply\n\tuserApplyTx := tx.UserApply.WithContext(ctx)\n\n\t// 删除好友申请\n\tif _, err := userApplyTx.Where(userApply.UID.Eq(uid), userApply.TargetID.Eq(deleteFriendUid)).Delete(); err != nil {\n\t\tif err := tx.Rollback(); err != nil {\n\t\t\tglobal.Logger.Errorf(\"事务回滚失败 %s\", err.Error())\n\t\t}\n\t\tglobal.Logger.Errorf(\"删除好友失败 %s\", err.Error())\n\t\treturn errors.New(\"Business Error\")\n\t}\n\t// 删除redis缓存\n\tdefer redisCache.RemoveUserApply(uid, deleteFriendUid)\n\n\tif _, err := userApplyTx.Where(userApply.UID.Eq(deleteFriendUid), userApply.TargetID.Eq(uid)).Delete(); err != nil {\n\t\tif err := tx.Rollback(); err != nil {\n\t\t\tglobal.Logger.Errorf(\"事务回滚失败 %s\", err.Error())\n\t\t}\n\t\tglobal.Logger.Errorf(\"删除好友失败 %s\", err.Error())\n\t\treturn errors.New(\"Business Error\")\n\t}\n\t// 删除redis缓存\n\tdefer redisCache.RemoveUserApply(deleteFriendUid, uid)\n\n\t// 软删除好友房间\n\troomFriend := global.Query.RoomFriend\n\troomFriendTx := tx.RoomFriend.WithContext(ctx)\n\tuids = utils.Int64Slice{uid, deleteFriendUid}\n\tsort.Sort(uids)\n\tfun := func() (interface{}, error) {\n\t\treturn roomFriendTx.Where(roomFriend.Uid1.Eq(uids[0]), roomFriend.Uid2.Eq(uids[1])).First()\n\t}\n\troomFriendR := model.RoomFriend{}\n\tkey = fmt.Sprintf(enum.RoomFriendCacheByUidAndFriendUid, uids[0], uids[1])\n\tif err := utils.GetData(key, &roomFriendR, fun); err != nil {\n\t\tif err := tx.Rollback(); err != nil {\n\t\t\tglobal.Logger.Errorf(\"事务回滚失败 %s\", err.Error())\n\t\t}\n\t\tglobal.Logger.Errorf(\"查询好友房间失败 %s\", err.Error())\n\t\treturn errors.New(\"Business Error\")\n\t}\n\n\tif _, err := roomFriendTx.Where(roomFriend.ID.Eq(roomFriendR.ID)).Update(roomFriend.DeleteStatus, pkgEnum.DELETED); err != nil {\n\t\tif err := tx.Rollback(); err != nil {\n\t\t\tglobal.Logger.Errorf(\"事务回滚失败 %s\", err.Error())\n\t\t}\n\t\tglobal.Logger.Errorf(\"删除好友房间失败 %s\", err.Error())\n\t\treturn errors.New(\"Business Error\")\n\t}\n\t// 删除redis缓存\n\tdefer redisCache.RemoveRoomFriend(roomFriendR)\n\n\t// 软删除房间表\n\troom := global.Query.Room\n\troomTx := tx.Room.WithContext(ctx)\n\tif _, err := roomTx.Where(room.ID.Eq(roomFriendR.RoomID)).Update(room.DeleteStatus, pkgEnum.DELETED); err != nil {\n\t\tif err := tx.Rollback(); err != nil {\n\t\t\tglobal.Logger.Errorf(\"事务回滚失败 %s\", err.Error())\n\t\t}\n\t\tglobal.Logger.Errorf(\"删除房间失败 %s\", err.Error())\n\t\treturn errors.New(\"Business Error\")\n\t}\n\t// 删除redis缓存\n\troomR := model.Room{\n\t\tID: roomFriendR.RoomID,\n\t}\n\tdefer redisCache.RemoveRoomCache(roomR)\n\n\t// 删除消息表\n\tmsg := global.Query.Message\n\tmsgTx := tx.Message.WithContext(ctx)\n\tif _, err := msgTx.Where(msg.RoomID.Eq(roomFriendR.RoomID)).Update(msg.DeleteStatus, pkgEnum.DELETED); err != nil {\n\t\tif err := tx.Rollback(); err != nil {\n\t\t\tglobal.Logger.Errorf(\"事务回滚失败 %s\", err.Error())\n\t\t}\n\t\tglobal.Logger.Errorf(\"删除消息失败 %s\", err.Error())\n\t\treturn errors.New(\"Business Error\")\n\t}\n\t// TODO: 删除消息表缓存\n\n\tif err := tx.Commit(); err != nil {\n\t\tglobal.Logger.Errorf(\"事务提交失败 %s\", err.Error())\n\t\treturn errors.New(\"Business Error\")\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "event/listener/friend_new_event.go",
    "content": "package listener\n\nimport (\n\t\"DiTing-Go/dal/model\"\n\t\"DiTing-Go/domain/enum\"\n\t\"DiTing-Go/global\"\n\tpkgEnum \"DiTing-Go/pkg/domain/enum\"\n\t\"DiTing-Go/pkg/utils\"\n\t\"DiTing-Go/service\"\n\t\"DiTing-Go/utils/redisCache\"\n\t\"context\"\n\t\"fmt\"\n\t\"github.com/apache/rocketmq-client-go/v2\"\n\t\"github.com/apache/rocketmq-client-go/v2/consumer\"\n\t\"github.com/apache/rocketmq-client-go/v2/primitive\"\n\t\"github.com/goccy/go-json\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/spf13/viper\"\n\t\"gorm.io/gorm\"\n\t\"sort\"\n\t\"strconv\"\n\t\"time\"\n)\n\nfunc init() {\n\thost := viper.GetString(\"rocketmq.host\")\n\t// 设置推送消费者\n\trocketConsumer, _ := rocketmq.NewPushConsumer(\n\t\t//消费组\n\t\tconsumer.WithGroupName(enum.UserLoginTopic),\n\t\t// namesrv地址\n\t\tconsumer.WithNameServer([]string{host}),\n\t)\n\terr := rocketConsumer.Subscribe(enum.NewFriendTopic, consumer.MessageSelector{}, friendNewEvent)\n\tif err != nil {\n\t\tglobal.Logger.Panicf(\"subscribe error: %s\", err.Error())\n\t}\n\terr = rocketConsumer.Start()\n\tif err != nil {\n\t\tglobal.Logger.Panicf(\"start consumer error: %s\", err.Error())\n\t}\n}\n\nfunc friendNewEvent(ctx context.Context, ext ...*primitive.MessageExt) (consumer.ConsumeResult, error) {\n\tfor i := range ext {\n\t\t// 解码\n\t\tuserFriend := model.UserFriend{}\n\t\tuserFriendMsgByte := ext[i].Message.Body\n\t\terr := json.Unmarshal(userFriendMsgByte, &userFriend)\n\t\tif err != nil {\n\t\t\tglobal.Logger.Errorf(\"json unmarshal error: %s\", err.Error())\n\t\t\treturn consumer.ConsumeRetryLater, nil\n\t\t}\n\t\terr = friendNew(userFriend)\n\t\tif err != nil {\n\t\t\tglobal.Logger.Errorf(\"friendNew error: %s\", err.Error())\n\t\t\treturn consumer.ConsumeRetryLater, nil\n\t\t}\n\n\t}\n\treturn consumer.ConsumeSuccess, nil\n}\n\nfunc friendNew(userFriend model.UserFriend) error {\n\tctx := context.Background()\n\n\tuid := userFriend.UID\n\tfriendUid := userFriend.FriendUID\n\tuids := utils.Int64Slice{uid, friendUid}\n\tsort.Sort(uids)\n\tkey := fmt.Sprintf(enum.UserAndFriendLock, uids[0], uids[1])\n\tmutex, err := utils.GetLock(key)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer utils.ReleaseLock(mutex)\n\n\tq := global.Query\n\ttx := q.Begin()\n\troomQ := tx.WithContext(ctx).Room\n\troomFriendQ := tx.WithContext(ctx).RoomFriend\n\tcontactQ := tx.WithContext(ctx).Contact\n\n\t// 创建房间表\n\troom := model.Room{\n\t\tType:    enum.PERSONAL,\n\t\tHotFlag: enum.NORMAL,\n\t\tExtJSON: \"{}\",\n\t}\n\tif err := roomQ.Create(&room); err != nil {\n\t\tif err := tx.Rollback(); err != nil {\n\t\t\tglobal.Logger.Errorf(\"事务回滚失败 %s\", err.Error())\n\t\t\treturn err\n\t\t}\n\t\tglobal.Logger.Errorf(\"创建房间失败 %s\", err.Error())\n\t\treturn err\n\t}\n\n\t// 排序，uid小的在前\n\tuids = utils.Int64Slice{userFriend.UID, userFriend.FriendUID}\n\tsort.Sort(uids)\n\n\t//检查是否有软删除状态的记录\n\troomFriend := global.Query.RoomFriend\n\tfun := func() (interface{}, error) {\n\t\treturn roomFriendQ.Where(roomFriend.Uid1.Eq(uids[0]), roomFriend.Uid2.Eq(uids[1]), roomFriend.DeleteStatus.Eq(pkgEnum.DELETED)).First()\n\t}\n\troomFriedR := model.RoomFriend{}\n\tkey = fmt.Sprintf(enum.RoomFriendCacheByUidAndFriendUid, uids[0], uids[1])\n\terr = utils.GetData(key, &roomFriedR, fun)\n\t// err\n\tif err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {\n\t\tglobal.Logger.Errorf(\"查询数据失败: %v\", err)\n\t\tif err := tx.Rollback(); err != nil {\n\t\t\tglobal.Logger.Errorf(\"事务回滚失败 %s\", err.Error())\n\t\t\treturn err\n\t\t}\n\t\treturn err\n\t}\n\t// 查到了\n\tif err == nil {\n\t\troomFriedR.RoomID = room.ID\n\t\troomFriedR.DeleteStatus = pkgEnum.NORMAL\n\t\tif _, err := roomFriendQ.Select(roomFriend.RoomID, roomFriend.DeleteStatus).Where(roomFriend.ID.Eq(roomFriedR.ID)).Updates(roomFriedR); err != nil {\n\t\t\tif err := tx.Rollback(); err != nil {\n\t\t\t\tglobal.Logger.Errorf(\"事务回滚失败 %s\", err.Error())\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tglobal.Logger.Errorf(\"更新房间失败 %s\", err.Error())\n\t\t\treturn err\n\t\t}\n\t\troomFriendR := model.RoomFriend{\n\t\t\tUid1: uids[0],\n\t\t\tUid2: uids[1],\n\t\t}\n\t\tdefer redisCache.RemoveRoomFriend(roomFriendR)\n\t} else {\n\t\t// 创建私聊表\n\t\tnewRoomFriend := model.RoomFriend{\n\t\t\tRoomID:  room.ID,\n\t\t\tUid1:    uids[0],\n\t\t\tUid2:    uids[1],\n\t\t\tRoomKey: strconv.FormatInt(uids[0], 10) + \",\" + strconv.FormatInt(uids[1], 10),\n\t\t}\n\t\tif err := roomFriendQ.Create(&newRoomFriend); err != nil {\n\t\t\tif err := tx.Rollback(); err != nil {\n\t\t\t\tglobal.Logger.Errorf(\"事务回滚失败 %s\", err.Error())\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tglobal.Logger.Errorf(\"创建房间失败 %s\", err.Error())\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// 自动发送一条消息\n\tnewMsg := model.Message{\n\t\tRoomID:       room.ID,\n\t\tFromUID:      userFriend.UID,\n\t\tContent:      \"你们已经是好友了，开始聊天吧\",\n\t\tDeleteStatus: pkgEnum.NORMAL,\n\t\tType:         enum.TextMessage,\n\t\tExtra:        \"{}\",\n\t}\n\tif err := service.SendTextMsg(&newMsg); err != nil {\n\t\tif err := tx.Rollback(); err != nil {\n\t\t\tglobal.Logger.Errorf(\"事务回滚失败 %s\", err.Error())\n\t\t\treturn err\n\t\t}\n\t\tglobal.Logger.Errorf(\"发送消息失败 %s\", err.Error())\n\t\treturn err\n\t}\n\n\t//创建会话表\n\ts, _ := time.ParseDuration(\"-1s\")\n\tif err := contactQ.Create(&model.Contact{\n\t\tUID:        userFriend.UID,\n\t\tRoomID:     room.ID,\n\t\tLastMsgID:  newMsg.ID,\n\t\tReadTime:   time.Now(),\n\t\tActiveTime: time.Now(),\n\t}); err != nil {\n\t\tif err := tx.Rollback(); err != nil {\n\t\t\tglobal.Logger.Errorf(\"事务回滚失败 %s\", err.Error())\n\t\t\treturn err\n\t\t}\n\t\tglobal.Logger.Errorf(\"创建会话失败 %s\", err.Error())\n\t\treturn err\n\t}\n\tif err := contactQ.Create(&model.Contact{\n\t\tUID:       userFriend.FriendUID,\n\t\tRoomID:    room.ID,\n\t\tLastMsgID: newMsg.ID,\n\t\t// 读到时间设为1秒前\n\t\tReadTime:   time.Now().Add(s),\n\t\tActiveTime: time.Now(),\n\t}); err != nil {\n\t\tif err := tx.Rollback(); err != nil {\n\t\t\tglobal.Logger.Errorf(\"事务回滚失败 %s\", err.Error())\n\t\t\treturn err\n\t\t}\n\t\tglobal.Logger.Errorf(\"创建会话失败 %s\", err.Error())\n\t\treturn err\n\t}\n\t// 提交\n\tif err := tx.Commit(); err != nil {\n\t\tglobal.Logger.Errorf(\"事务提交失败 %s\", err.Error())\n\t\treturn err\n\t}\n\t// 发送新消息事件\n\tnewMsgByte, _ := json.Marshal(newMsg)\n\tmsg := &primitive.Message{\n\t\tTopic: enum.NewMessageTopic,\n\t\tBody:  newMsgByte,\n\t}\n\t_, _ = global.RocketProducer.SendSync(ctx, msg)\n\treturn nil\n}\n"
  },
  {
    "path": "event/listener/new_msg_event.go",
    "content": "package listener\n\nimport (\n\t\"DiTing-Go/dal/model\"\n\tquery \"DiTing-Go/dal/query\"\n\t\"DiTing-Go/domain/enum\"\n\t\"DiTing-Go/global\"\n\t\"DiTing-Go/pkg/utils\"\n\twsEnum \"DiTing-Go/websocket/domain/enum\"\n\tresp2 \"DiTing-Go/websocket/domain/vo/resp\"\n\t\"DiTing-Go/websocket/service\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"github.com/apache/rocketmq-client-go/v2\"\n\t\"github.com/apache/rocketmq-client-go/v2/consumer\"\n\t\"github.com/apache/rocketmq-client-go/v2/primitive\"\n\t\"github.com/spf13/viper\"\n\t\"time\"\n)\n\nfunc init() {\n\thost := viper.GetString(\"rocketmq.host\")\n\t// 设置推送消费者\n\trocketSendMsgConsumer, _ := rocketmq.NewPushConsumer(\n\t\t//消费组\n\t\tconsumer.WithGroupName(enum.NewMessageTopic+\"-send-message\"),\n\t\t// namesrv地址\n\t\tconsumer.WithNameServer([]string{host}),\n\t)\n\terr := rocketSendMsgConsumer.Subscribe(enum.NewMessageTopic, consumer.MessageSelector{}, UpdateContactEvent)\n\tif err != nil {\n\t\tglobal.Logger.Panicf(\"subscribe error: %s\", err.Error())\n\t}\n\terr = rocketSendMsgConsumer.Start()\n\tif err != nil {\n\t\tglobal.Logger.Panicf(\"start consumer error: %s\", err.Error())\n\t}\n\n\t//// 设置推送消费者\n\t//rocketUpdateContactConsumer, _ := rocketmq.NewPushConsumer(\n\t//\t//消费组\n\t//\tconsumer.WithGroupName(enum.NewMessageTopic+\"-update-contact\"),\n\t//\t// namesrv地址\n\t//\tconsumer.WithNameServer([]string{host}),\n\t//)\n\t//err := rocketUpdateContactConsumer.Subscribe(enum.NewMessageTopic, consumer.MessageSelector{}, SendMsgEvent)\n\t//if err != nil {\n\t//\tglobal.Logger.Panicf(\"subscribe error: %s\", err.Error())\n\t//}\n\t//err = rocketUpdateContactConsumer.Start()\n\t//if err != nil {\n\t//\tglobal.Logger.Panicf(\"start consumer error: %s\", err.Error())\n\t//}\n}\n\nfunc UpdateContactEvent(ctx context.Context, ext ...*primitive.MessageExt) (consumer.ConsumeResult, error) {\n\tfor i := range ext {\n\t\t// 解码\n\t\tmsg := model.Message{}\n\t\tmsgByte := ext[i].Message.Body\n\t\terr := json.Unmarshal(msgByte, &msg)\n\t\tif err != nil {\n\t\t\tglobal.Logger.Errorf(\"jsonUtils unmarshal error: %s\", err.Error())\n\t\t\treturn consumer.ConsumeRetryLater, nil\n\t\t}\n\t\terr = updateContact(msg)\n\t\tif err != nil {\n\t\t\tglobal.Logger.Errorf(\"更新会话失败 %s\", err)\n\t\t\treturn consumer.ConsumeRetryLater, nil\n\t\t}\n\t\terr = sendMsg(msg)\n\t\tif err != nil {\n\t\t\tglobal.Logger.Errorf(\"发送消息失败 %s\", err)\n\t\t\treturn consumer.ConsumeRetryLater, nil\n\t\t}\n\t}\n\treturn consumer.ConsumeSuccess, nil\n}\nfunc updateContact(msg model.Message) error {\n\t// 更新会话表\n\tctx := context.Background()\n\troom := global.Query.Room\n\troomQ := room.WithContext(ctx)\n\troomR, err := roomQ.Where(room.ID.Eq(msg.RoomID)).First()\n\tif err != nil {\n\t\tglobal.Logger.Errorf(\"查询房间失败 %s\", err)\n\t\treturn err\n\t}\n\tvar uids []int64\n\tif roomR.Type == enum.PERSONAL {\n\t\troomFriend := global.Query.RoomFriend\n\t\troomFriendQ := roomFriend.WithContext(ctx)\n\t\troomFriendR, err := roomFriendQ.Where(roomFriend.RoomID.Eq(roomR.ID)).First()\n\t\tif err != nil {\n\t\t\tglobal.Logger.Errorf(\"查询好友房间失败 %s\", err)\n\t\t\treturn err\n\t\t}\n\t\tuids = []int64{roomFriendR.Uid1, roomFriendR.Uid2}\n\t} else if roomR.Type == enum.GROUP {\n\t\t// 查询所有群成员\n\t\troomGroup := global.Query.RoomGroup\n\t\troomGroupQ := roomGroup.WithContext(ctx)\n\t\troomGroupR, err := roomGroupQ.Where(roomGroup.RoomID.Eq(roomR.ID)).First()\n\t\tif err != nil {\n\t\t\tglobal.Logger.Errorf(\"查询群聊失败 %s\", err)\n\t\t\treturn err\n\t\t}\n\t\tgroupMember := global.Query.GroupMember\n\t\tgroupMemberQ := groupMember.WithContext(ctx)\n\t\tgroupMembers, _ := groupMemberQ.Where(groupMember.GroupID.Eq(roomGroupR.ID)).Find()\n\t\tfor _, groupMember := range groupMembers {\n\t\t\tuids = append(uids, groupMember.UID)\n\t\t}\n\t}\n\t//更新会话表\n\tupdate := model.Contact{\n\t\tLastMsgID:  msg.ID,\n\t\tUpdateTime: time.Now(),\n\t\tActiveTime: time.Now(),\n\t}\n\tcontact := global.Query.Contact\n\tcontactQ := contact.WithContext(ctx)\n\t_, err = contactQ.Where(contact.UID.In(uids...), contact.RoomID.Eq(msg.RoomID)).Updates(&update)\n\tif err != nil {\n\t\tglobal.Logger.Errorf(\"更新会话失败 %s\", err)\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// SendMsgEvent 新消息事件\nfunc SendMsgEvent(ctx context.Context, ext ...*primitive.MessageExt) (consumer.ConsumeResult, error) {\n\tfor i := range ext {\n\t\t// 解码\n\t\tmsg := model.Message{}\n\t\tmsgByte := ext[i].Message.Body\n\t\terr := json.Unmarshal(msgByte, &msg)\n\t\tif err != nil {\n\t\t\tglobal.Logger.Errorf(\"jsonUtils unmarshal error: %s\", err.Error())\n\t\t\treturn consumer.ConsumeRetryLater, nil\n\t\t}\n\t\terr = sendMsg(msg)\n\t\tif err != nil {\n\t\t\tglobal.Logger.Errorf(\"发送消息失败 %s\", err)\n\t\t\treturn consumer.ConsumeRetryLater, nil\n\t\t}\n\t}\n\treturn consumer.ConsumeSuccess, nil\n}\n\n// sendMsg 发送消息\nfunc sendMsg(msg model.Message) error {\n\t// 向房间中的所有用户发送消息，包括自己\n\troomQ := global.Query.WithContext(context.Background()).Room\n\tfun := func() (interface{}, error) {\n\t\treturn roomQ.Where(query.Room.ID.Eq(msg.RoomID)).First()\n\t}\n\troom := model.Room{}\n\tkey := fmt.Sprintf(enum.RoomCacheByID, msg.RoomID)\n\terr := utils.GetData(key, &room, fun)\n\tif err != nil {\n\t\tglobal.Logger.Errorf(\"查询房间失败 %s\", err)\n\t\treturn err\n\t}\n\tmsgBody := resp2.NewMessageResp{\n\t\tType: wsEnum.NewMessage,\n\t}\n\tstr, _ := json.Marshal(msgBody)\n\t// 单聊\n\tif room.Type == enum.PERSONAL {\n\t\troomFriendQ := global.Query.WithContext(context.Background()).RoomFriend\n\t\troomFriendR := model.RoomFriend{}\n\t\tfun = func() (interface{}, error) {\n\t\t\treturn roomFriendQ.Where(query.RoomFriend.RoomID.Eq(room.ID)).First()\n\t\t}\n\t\tkey := fmt.Sprintf(enum.RoomFriendCacheByRoomID, room.ID)\n\t\terr = utils.GetData(key, &roomFriendR, fun)\n\t\tif err != nil {\n\t\t\tglobal.Logger.Errorf(\"查询好友房间失败 %s\", err)\n\t\t\treturn err\n\t\t}\n\t\t// 发送新消息事件\n\t\terr := service.Send(roomFriendR.Uid1, str)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\terr = service.Send(roomFriendR.Uid2, str)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t//\tTODO:群聊\n\t} else if room.Type == enum.GROUP {\n\t\troomGroupQ := global.Query.WithContext(context.Background()).RoomGroup\n\t\troomGroup, _ := roomGroupQ.Where(query.RoomGroup.RoomID.Eq(room.ID)).First()\n\t\t//\t查询所有群成员\n\t\tgroupMemberQ := global.Query.WithContext(context.Background()).GroupMember\n\t\tgroupMembers, _ := groupMemberQ.Where(query.GroupMember.GroupID.Eq(roomGroup.ID)).Find()\n\t\t// 发送新消息事件\n\t\tfor _, groupMember := range groupMembers {\n\t\t\tservice.Send(groupMember.UID, str)\n\t\t}\n\t}\n\treturn nil\n\n}\n"
  },
  {
    "path": "event/listener/user_login_event.go",
    "content": "package listener\n\nimport (\n\t\"DiTing-Go/domain/enum\"\n\t\"DiTing-Go/global\"\n\t\"context\"\n\t\"fmt\"\n\t\"github.com/apache/rocketmq-client-go/v2\"\n\t\"github.com/apache/rocketmq-client-go/v2/consumer\"\n\t\"github.com/apache/rocketmq-client-go/v2/primitive\"\n\t\"github.com/spf13/viper\"\n)\n\nfunc init() {\n\thost := viper.GetString(\"rocketmq.host\")\n\t// 设置推送消费者\n\trocketConsumer, _ := rocketmq.NewPushConsumer(\n\t\t//消费组\n\t\tconsumer.WithGroupName(enum.UserLoginTopic+\"-test\"),\n\t\t// namesrv地址\n\t\tconsumer.WithNameServer([]string{host}),\n\t)\n\tgo test(rocketConsumer)\n\n}\n\nfunc test(rocketConsumer rocketmq.PushConsumer) {\n\t// 必须先在 开始前\n\terr := rocketConsumer.Subscribe(enum.UserLoginTopic, consumer.MessageSelector{}, func(ctx context.Context, ext ...*primitive.MessageExt) (consumer.ConsumeResult, error) {\n\t\tfor i := range ext {\n\t\t\tfmt.Printf(\"subscribe callback:%v \\n\", ext[i])\n\t\t}\n\t\treturn consumer.ConsumeSuccess, nil\n\t})\n\tif err != nil {\n\t\tglobal.Logger.Panicf(\"subscribe error: %s\", err.Error())\n\t}\n\terr = rocketConsumer.Start()\n\tif err != nil {\n\t\tglobal.Logger.Panicf(\"start consumer error: %s\", err.Error())\n\t}\n}\n"
  },
  {
    "path": "global/init_db.go",
    "content": "package global\n\nimport (\n\t\"DiTing-Go/dal\"\n\t\"DiTing-Go/dal/query\"\n\t\"fmt\"\n\t\"github.com/spf13/viper\"\n)\n\nvar MySQLDSN string\nvar Query *query.Query\n\nfunc DBInit() {\n\tMySQLDSN = 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\"))\n\tdal.DB = dal.ConnectDB(MySQLDSN).Debug()\n\t//dal.DB = dal.ConnectDB(MySQLDSN)\n\t// 设置默认DB对象\n\tquery.SetDefault(dal.DB)\n\tQuery = query.Use(dal.DB)\n}\n"
  },
  {
    "path": "global/init_distribute_lock.go",
    "content": "package global\n\n//\n//import (\n//\tgoredislib \"github.com/go-redis/redis/v8\"\n//\t\"github.com/go-redsync/redsync/v4\"\n//\t\"github.com/go-redsync/redsync/v4/redis/goredis/v8\"\n//\t\"github.com/spf13/viper\"\n//)\n//\n//var RedSync *redsync.Redsync\n//\n//func init() {\n//\tAddr := viper.GetString(\"redis.host\")\n//\tPassword := viper.GetString(\"redis.password\")\n//\tclient := goredislib.NewClient(&goredislib.Options{\n//\t\tAddr:     Addr,\n//\t\tPassword: Password, // 密码\n//\t\tDB:       0,        // 数据库\n//\t\tPoolSize: 20,       // 连接池大小\n//\t})\n//\tpool := goredis.NewPool(client)\n//\tRedSync = redsync.New(pool)\n//}\n"
  },
  {
    "path": "global/init_evenbus.go",
    "content": "package global\n\n//\n//import \"github.com/asaskevich/EventBus\"\n//\n//var Bus EventBus.Bus\n//\n//func init() {\n//\tBus = EventBus.New()\n//}\n"
  },
  {
    "path": "global/init_log.go",
    "content": "package global\n\nimport (\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/spf13/viper\"\n\t\"io\"\n\t\"os\"\n\t\"path\"\n)\n\nvar Logger *logrus.Logger\n\nfunc LogInit() {\n\tlogFilePath := viper.GetString(\"log.log_file_path\")\n\tlogFileName := viper.GetString(\"log.log_file_name\")\n\t//日志文件\n\tfileName := path.Join(logFilePath, logFileName)\n\t//写入文件\n\tsrc, err := os.OpenFile(fileName, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)\n\tif err != nil {\n\t\t// 失败不启动\n\t\tpanic(\"日志文件写入失败, err: \" + err.Error())\n\t}\n\n\t//实例化\n\tLogger = logrus.New()\n\twriters := []io.Writer{\n\t\tsrc,\n\t\tos.Stdout}\n\tfileAndStdoutWriter := io.MultiWriter(writers...)\n\tif err == nil {\n\t\tLogger.SetOutput(fileAndStdoutWriter)\n\t} else {\n\t\tLogger.Info(\"failed to log to file.\")\n\t}\n\n\t//设置日志级别\n\tLogger.SetLevel(logrus.DebugLevel)\n\tLogger.SetReportCaller(true)\n\t//设置日志格式\n\tLogger.SetFormatter(&logrus.TextFormatter{\n\t\tTimestampFormat: \"2006-01-02 15:04:05\",\n\t})\n}\n"
  },
  {
    "path": "global/init_minio.go",
    "content": "package global\n\nimport (\n\t\"github.com/minio/minio-go/v7\"\n)\n\nvar MinioClient *minio.Client\n\n//func init() {\n//\taccessKey := viper.GetString(\"minio.accessKey\")\n//\taccessSecret := viper.GetString(\"minio.accessSecret\")\n//\tendPoint := viper.GetString(\"minio.endPoint\")\n//\tuseSSL := viper.GetBool(\"minio.useSSL\")\n//\t// 初始化minio客户端\n//\tminioClient, err := minio.New(endPoint, &minio.Options{\n//\t\tCreds:  credentials.NewStaticV4(accessKey, accessSecret, \"\"),\n//\t\tSecure: useSSL,\n//\t})\n//\tMinioClient = minioClient\n//\tif err != nil {\n//\t\tLogger.Fatal(\"minio client create fail, err %+v\", err)\n//\t}\n//}\n"
  },
  {
    "path": "global/init_redis.go",
    "content": "package global\n\nimport (\n\t\"github.com/go-redis/redis\"\n\t\"github.com/spf13/viper\"\n)\n\nvar Rdb *redis.Client\n\nfunc RedisInit() {\n\tAddr := viper.GetString(\"redis.host\")\n\tPassword := viper.GetString(\"redis.password\")\n\tRdb = redis.NewClient(&redis.Options{\n\t\tAddr:     Addr,\n\t\tPassword: Password, // 密码\n\t\tDB:       0,        // 数据库\n\t\tPoolSize: 100,      // 连接池大小\n\t})\n}\n"
  },
  {
    "path": "global/init_rocketmq.go",
    "content": "package global\n\n//\n//import (\n//\t\"github.com/apache/rocketmq-client-go/v2\"\n//\t\"github.com/apache/rocketmq-client-go/v2/producer\"\n//\t\"github.com/spf13/viper\"\n//)\n//\n//var RocketProducer rocketmq.Producer\n//\n//func init() {\n//\thost := viper.GetString(\"rocketmq.host\")\n//\tgroup := viper.GetString(\"rocketmq.group\")\n//\tRocketProducer, _ = rocketmq.NewProducer(\n//\t\t// 设置  nameSrvAddr\n//\t\t// nameSrvAddr 是 Topic 路由注册中心\n//\t\tproducer.WithNameServer([]string{host}),\n//\t\t// 指定发送失败时的重试时间\n//\t\tproducer.WithRetry(3),\n//\t\t// 设置 Group\n//\t\tproducer.WithGroupName(group),\n//\t)\n//\t// 开始连接\n//\terr := RocketProducer.Start()\n//\tif err != nil {\n//\t\tLogger.Panicf(\"start producer error: %s\", err.Error())\n//\t}\n//}\n"
  },
  {
    "path": "global/init_time.go",
    "content": "package global\n\n//import \"time\"\n//\n//func init() {\n//\tlocation, _ := time.LoadLocation(\"Asia/Shanghai\")\n//\ttime.Local = location\n//}\n"
  },
  {
    "path": "go.mod",
    "content": "module DiTing-Go\n\ngo 1.22.1\n\nrequire (\n\tgithub.com/apache/rocketmq-client-go/v2 v2.1.2\n\tgithub.com/gin-gonic/gin v1.9.1\n\tgithub.com/go-redis/redis v6.15.9+incompatible\n\tgithub.com/goccy/go-json v0.10.2\n\tgithub.com/golang-jwt/jwt/v5 v5.3.0\n\tgithub.com/gorilla/websocket v1.5.1\n\tgithub.com/jinzhu/copier v0.4.0\n\tgithub.com/minio/minio-go/v7 v7.0.69\n\tgithub.com/orcaman/concurrent-map/v2 v2.0.1\n\tgithub.com/pkg/errors v0.9.1\n\tgithub.com/sirupsen/logrus v1.9.3\n\tgithub.com/spf13/viper v1.18.2\n\tgithub.com/stretchr/testify v1.9.0\n\tgithub.com/swaggo/files v1.0.1\n\tgithub.com/swaggo/gin-swagger v1.6.0\n\tgithub.com/swaggo/swag v1.16.3\n\tgorm.io/driver/mysql v1.5.6\n\tgorm.io/gen v0.3.25\n\tgorm.io/gorm v1.25.9\n\tgorm.io/plugin/dbresolver v1.5.1\n)\n\nrequire (\n\tfilippo.io/edwards25519 v1.1.0 // indirect\n\tgithub.com/KyleBanks/depth v1.2.1 // indirect\n\tgithub.com/bytedance/sonic v1.11.3 // indirect\n\tgithub.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect\n\tgithub.com/chenzhuoyu/iasm v0.9.1 // indirect\n\tgithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect\n\tgithub.com/dustin/go-humanize v1.0.1 // indirect\n\tgithub.com/emirpasic/gods v1.12.0 // indirect\n\tgithub.com/fsnotify/fsnotify v1.7.0 // indirect\n\tgithub.com/gabriel-vasile/mimetype v1.4.3 // indirect\n\tgithub.com/gin-contrib/sse v0.1.0 // indirect\n\tgithub.com/go-openapi/jsonpointer v0.21.0 // indirect\n\tgithub.com/go-openapi/jsonreference v0.21.0 // indirect\n\tgithub.com/go-openapi/spec v0.21.0 // indirect\n\tgithub.com/go-openapi/swag v0.23.0 // indirect\n\tgithub.com/go-playground/locales v0.14.1 // indirect\n\tgithub.com/go-playground/universal-translator v0.18.1 // indirect\n\tgithub.com/go-playground/validator/v10 v10.19.0 // indirect\n\tgithub.com/go-sql-driver/mysql v1.8.1 // indirect\n\tgithub.com/golang/mock v1.3.1 // indirect\n\tgithub.com/google/uuid v1.6.0 // indirect\n\tgithub.com/hashicorp/hcl v1.0.0 // indirect\n\tgithub.com/jinzhu/inflection v1.0.0 // indirect\n\tgithub.com/jinzhu/now v1.1.5 // indirect\n\tgithub.com/josharian/intern v1.0.0 // indirect\n\tgithub.com/json-iterator/go v1.1.12 // indirect\n\tgithub.com/klauspost/compress v1.17.6 // indirect\n\tgithub.com/klauspost/cpuid/v2 v2.2.7 // indirect\n\tgithub.com/leodido/go-urn v1.4.0 // indirect\n\tgithub.com/magiconair/properties v1.8.7 // indirect\n\tgithub.com/mailru/easyjson v0.7.7 // indirect\n\tgithub.com/mattn/go-isatty v0.0.20 // indirect\n\tgithub.com/mattn/go-sqlite3 v1.14.22 // indirect\n\tgithub.com/minio/md5-simd v1.1.2 // indirect\n\tgithub.com/minio/sha256-simd v1.0.1 // indirect\n\tgithub.com/mitchellh/mapstructure v1.5.0 // indirect\n\tgithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect\n\tgithub.com/modern-go/reflect2 v1.0.2 // indirect\n\tgithub.com/patrickmn/go-cache v2.1.0+incompatible // indirect\n\tgithub.com/pelletier/go-toml/v2 v2.2.0 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect\n\tgithub.com/rs/xid v1.5.0 // indirect\n\tgithub.com/sagikazarmark/locafero v0.4.0 // indirect\n\tgithub.com/sagikazarmark/slog-shim v0.1.0 // indirect\n\tgithub.com/sourcegraph/conc v0.3.0 // indirect\n\tgithub.com/spf13/afero v1.11.0 // indirect\n\tgithub.com/spf13/cast v1.6.0 // indirect\n\tgithub.com/spf13/pflag v1.0.5 // indirect\n\tgithub.com/subosito/gotenv v1.6.0 // indirect\n\tgithub.com/tidwall/gjson v1.13.0 // indirect\n\tgithub.com/tidwall/match v1.1.1 // indirect\n\tgithub.com/tidwall/pretty v1.2.0 // indirect\n\tgithub.com/twitchyliquid64/golang-asm v0.15.1 // indirect\n\tgithub.com/ugorji/go/codec v1.2.12 // indirect\n\tgo.uber.org/atomic v1.9.0 // indirect\n\tgo.uber.org/multierr v1.11.0 // indirect\n\tgolang.org/x/arch v0.7.0 // indirect\n\tgolang.org/x/crypto v0.21.0 // indirect\n\tgolang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 // indirect\n\tgolang.org/x/mod v0.16.0 // indirect\n\tgolang.org/x/net v0.22.0 // indirect\n\tgolang.org/x/sys v0.18.0 // indirect\n\tgolang.org/x/text v0.14.0 // indirect\n\tgolang.org/x/tools v0.19.0 // indirect\n\tgoogle.golang.org/protobuf v1.33.0 // indirect\n\tgopkg.in/ini.v1 v1.67.0 // indirect\n\tgopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n\tgorm.io/datatypes v1.2.0 // indirect\n\tgorm.io/driver/sqlite v1.5.5 // indirect\n\tgorm.io/hints v1.1.2 // indirect\n\tstathat.com/c/consistent v1.0.0 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=\nfilippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=\ngithub.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=\ngithub.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=\ngithub.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=\ngithub.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=\ngithub.com/apache/rocketmq-client-go/v2 v2.1.2 h1:yt73olKe5N6894Dbm+ojRf/JPiP0cxfDNNffKwhpJVg=\ngithub.com/apache/rocketmq-client-go/v2 v2.1.2/go.mod h1:6I6vgxHR3hzrvn+6n/4mrhS+UTulzK/X9LB2Vk1U5gE=\ngithub.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=\ngithub.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=\ngithub.com/bytedance/sonic v1.11.3 h1:jRN+yEjakWh8aK5FzrciUHG8OFXK+4/KrAX/ysEtHAA=\ngithub.com/bytedance/sonic v1.11.3/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=\ngithub.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=\ngithub.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=\ngithub.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=\ngithub.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=\ngithub.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=\ngithub.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0=\ngithub.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=\ngithub.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=\ngithub.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=\ngithub.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=\ngithub.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=\ngithub.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=\ngithub.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=\ngithub.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=\ngithub.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=\ngithub.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=\ngithub.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=\ngithub.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=\ngithub.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=\ngithub.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=\ngithub.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=\ngithub.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=\ngithub.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=\ngithub.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=\ngithub.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=\ngithub.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=\ngithub.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=\ngithub.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=\ngithub.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=\ngithub.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=\ngithub.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=\ngithub.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=\ngithub.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=\ngithub.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=\ngithub.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=\ngithub.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=\ngithub.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=\ngithub.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=\ngithub.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=\ngithub.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=\ngithub.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=\ngithub.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=\ngithub.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4=\ngithub.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=\ngithub.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=\ngithub.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=\ngithub.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=\ngithub.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=\ngithub.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=\ngithub.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=\ngithub.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=\ngithub.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=\ngithub.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=\ngithub.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=\ngithub.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=\ngithub.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=\ngithub.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=\ngithub.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=\ngithub.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=\ngithub.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=\ngithub.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s=\ngithub.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=\ngithub.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=\ngithub.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=\ngithub.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=\ngithub.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=\ngithub.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=\ngithub.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=\ngithub.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=\ngithub.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=\ngithub.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=\ngithub.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=\ngithub.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=\ngithub.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=\ngithub.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=\ngithub.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=\ngithub.com/jackc/pgx/v5 v5.3.0 h1:/NQi8KHMpKWHInxXesC8yD4DhkXPrVhmnwYkjp9AmBA=\ngithub.com/jackc/pgx/v5 v5.3.0/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8=\ngithub.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=\ngithub.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=\ngithub.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=\ngithub.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=\ngithub.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=\ngithub.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=\ngithub.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=\ngithub.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=\ngithub.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=\ngithub.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=\ngithub.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=\ngithub.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=\ngithub.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=\ngithub.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI=\ngithub.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=\ngithub.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=\ngithub.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=\ngithub.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=\ngithub.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=\ngithub.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=\ngithub.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=\ngithub.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=\ngithub.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=\ngithub.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=\ngithub.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=\ngithub.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=\ngithub.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=\ngithub.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=\ngithub.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=\ngithub.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=\ngithub.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=\ngithub.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=\ngithub.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=\ngithub.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=\ngithub.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=\ngithub.com/microsoft/go-mssqldb v0.17.0 h1:Fto83dMZPnYv1Zwx5vHHxpNraeEaUlQ/hhHLgZiaenE=\ngithub.com/microsoft/go-mssqldb v0.17.0/go.mod h1:OkoNGhGEs8EZqchVTtochlXruEhEOaO4S0d2sB5aeGQ=\ngithub.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=\ngithub.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=\ngithub.com/minio/minio-go/v7 v7.0.69 h1:l8AnsQFyY1xiwa/DaQskY4NXSLA2yrGsW5iD9nRPVS0=\ngithub.com/minio/minio-go/v7 v7.0.69/go.mod h1:XAvOPJQ5Xlzk5o3o/ArO2NMbhSGkimC+bpW/ngRKDmQ=\ngithub.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=\ngithub.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=\ngithub.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=\ngithub.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=\ngithub.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=\ngithub.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=\ngithub.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=\ngithub.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=\ngithub.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=\ngithub.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=\ngithub.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=\ngithub.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=\ngithub.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=\ngithub.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=\ngithub.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=\ngithub.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=\ngithub.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=\ngithub.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=\ngithub.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=\ngithub.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=\ngithub.com/orcaman/concurrent-map/v2 v2.0.1 h1:jOJ5Pg2w1oeB6PeDurIYf6k9PQ+aTITr/6lP/L/zp6c=\ngithub.com/orcaman/concurrent-map/v2 v2.0.1/go.mod h1:9Eq3TG2oBe5FirmYWQfYO5iH1q0Jv47PLaNK++uCdOM=\ngithub.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=\ngithub.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=\ngithub.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo=\ngithub.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=\ngithub.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=\ngithub.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=\ngithub.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=\ngithub.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=\ngithub.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=\ngithub.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=\ngithub.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=\ngithub.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=\ngithub.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=\ngithub.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=\ngithub.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=\ngithub.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=\ngithub.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=\ngithub.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=\ngithub.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=\ngithub.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=\ngithub.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=\ngithub.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=\ngithub.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=\ngithub.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=\ngithub.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=\ngithub.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=\ngithub.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=\ngithub.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=\ngithub.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=\ngithub.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=\ngithub.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=\ngithub.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=\ngithub.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=\ngithub.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=\ngithub.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=\ngithub.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=\ngithub.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=\ngithub.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=\ngithub.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg=\ngithub.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M=\ngithub.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo=\ngithub.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg=\ngithub.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk=\ngithub.com/tidwall/gjson v1.13.0 h1:3TFY9yxOQShrvmjdM76K+jc66zJeT6D3/VFFYCGQf7M=\ngithub.com/tidwall/gjson v1.13.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=\ngithub.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=\ngithub.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=\ngithub.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=\ngithub.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=\ngithub.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=\ngithub.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=\ngithub.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=\ngithub.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=\ngithub.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=\ngo.uber.org/atomic v1.5.1/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=\ngo.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=\ngo.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=\ngo.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=\ngo.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=\ngolang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=\ngolang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc=\ngolang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=\ngolang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=\ngolang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=\ngolang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88pSyOt+UgdZw2BFZ+lEw=\ngolang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ=\ngolang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=\ngolang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=\ngolang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=\ngolang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=\ngolang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\ngolang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=\ngolang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=\ngolang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=\ngolang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=\ngolang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=\ngolang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=\ngolang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=\ngolang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=\ngolang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=\ngolang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=\ngolang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=\ngoogle.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=\ngoogle.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=\ngoogle.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=\ngopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=\ngopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=\ngopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=\ngopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=\ngopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=\ngopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=\ngopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=\ngopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngorm.io/datatypes v1.2.0 h1:5YT+eokWdIxhJgWHdrb2zYUimyk0+TaFth+7a0ybzco=\ngorm.io/datatypes v1.2.0/go.mod h1:o1dh0ZvjIjhH/bngTpypG6lVRJ5chTBxE09FH/71k04=\ngorm.io/driver/mysql v1.4.3/go.mod h1:sSIebwZAVPiT+27jK9HIwvsqOGKx3YMPmrA3mBJR10c=\ngorm.io/driver/mysql v1.5.6 h1:Ld4mkIickM+EliaQZQx3uOJDJHtrd70MxAUqWqlx3Y8=\ngorm.io/driver/mysql v1.5.6/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=\ngorm.io/driver/postgres v1.5.0 h1:u2FXTy14l45qc3UeCJ7QaAXZmZfDDv0YrthvmRq1l0U=\ngorm.io/driver/postgres v1.5.0/go.mod h1:FUZXzO+5Uqg5zzwzv4KK49R8lvGIyscBOqYrtI1Ce9A=\ngorm.io/driver/sqlite v1.5.0/go.mod h1:kDMDfntV9u/vuMmz8APHtHF0b4nyBB7sfCieC6G8k8I=\ngorm.io/driver/sqlite v1.5.5 h1:7MDMtUZhV065SilG62E0MquljeArQZNfJnjd9i9gx3E=\ngorm.io/driver/sqlite v1.5.5/go.mod h1:6NgQ7sQWAIFsPrJJl1lSNSu2TABh0ZZ/zm5fosATavE=\ngorm.io/driver/sqlserver v1.4.1 h1:t4r4r6Jam5E6ejqP7N82qAJIJAht27EGT41HyPfXRw0=\ngorm.io/driver/sqlserver v1.4.1/go.mod h1:DJ4P+MeZbc5rvY58PnmN1Lnyvb5gw5NPzGshHDnJLig=\ngorm.io/gen v0.3.25 h1:uT/1YfvcnYUdike4XPYyi89FEnVHZF115GUXQm2Sfug=\ngorm.io/gen v0.3.25/go.mod h1:p+t0iCKjaPz+pKRxcx63nXdRgnrah/QD2l92747ihyA=\ngorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=\ngorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=\ngorm.io/gorm v1.25.0/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=\ngorm.io/gorm v1.25.2/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=\ngorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=\ngorm.io/gorm v1.25.9 h1:wct0gxZIELDk8+ZqF/MVnHLkA1rvYlBWUMv2EdsK1g8=\ngorm.io/gorm v1.25.9/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=\ngorm.io/hints v1.1.2 h1:b5j0kwk5p4+3BtDtYqqfY+ATSxjj+6ptPgVveuynn9o=\ngorm.io/hints v1.1.2/go.mod h1:/ARdpUHAtyEMCh5NNi3tI7FsGh+Cj/MIUlvNxCNCFWg=\ngorm.io/plugin/dbresolver v1.5.1 h1:s9Dj9f7r+1rE3nx/Ywzc85nXptUEaeOO0pt27xdopM8=\ngorm.io/plugin/dbresolver v1.5.1/go.mod h1:l4Cn87EHLEYuqUncpEeTC2tTJQkjngPSD+lo8hIvcT0=\nnullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=\nrsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=\nstathat.com/c/consistent v1.0.0 h1:ezyc51EGcRPJUxfHGSgJjWzJdj3NiMU9pNfLNGiXV0c=\nstathat.com/c/consistent v1.0.0/go.mod h1:QkzMWzcbB+yQBL2AttO6sgsQS/JSTapcDISJalmCDS0=\n"
  },
  {
    "path": "logic/captcha.go",
    "content": "package logic\n\nimport (\n\t\"DiTing-Go/global\"\n\t\"DiTing-Go/utils\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"math/rand\"\n\t\"time\"\n)\n\n// CheckCaptcha 检查验证码是否正确\nfunc CheckCaptcha(captchaId, captchaValue string) bool {\n\tif captchaId == \"\" || captchaValue == \"\" {\n\t\treturn false\n\t}\n\t//\t根据captchaId从redis查\n\tcaptchaByte, err := utils.GetValueFromRedis(captchaId)\n\tif err != nil {\n\t\tglobal.Logger.Errorf(\"get captcha from redis error: %v\", err)\n\t\treturn false\n\t}\n\tvar captcha string\n\terr = json.Unmarshal(captchaByte, &captcha)\n\tif err != nil {\n\t\tglobal.Logger.Errorf(\"json unmarshal error: %v\", err)\n\t\treturn false\n\t}\n\tif captchaValue == \"1234\" {\n\t\tglobal.Logger.Infof(\"captcha is 1234, pass\")\n\t\treturn true\n\t}\n\t// 1234直接放行\n\treturn captchaValue == captcha\n}\n\n// GenerateCaptcha 生成验证码\nfunc GenerateCaptcha(captchaId string) (string, error) {\n\t//\t生成四位随机数\n\trand.Seed(time.Now().UnixNano())\n\tcaptchaVal := rand.Intn(10000)         // 生成1000~9999之间的随机数\n\tval := fmt.Sprintf(\"%04d\", captchaVal) // 格式化为四位数，不足前面补0\n\t//\t将验证码存入redis\n\tcaptchaKey := utils.MakeUserCaptchaKey(captchaId)\n\terr := utils.SetValueToRedis(captchaKey, val, 1*time.Minute)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn val, nil\n}\n\n// SendCaptcha 发送验证码\nfunc SendCaptcha(phone, captcha string) error {\n\treturn nil\n}\n"
  },
  {
    "path": "logic/login.go",
    "content": "package logic\n\nimport (\n\t\"DiTing-Go/dal/model\"\n\t\"DiTing-Go/domain/enum\"\n\t\"DiTing-Go/global\"\n\t\"DiTing-Go/utils\"\n\t\"context\"\n\t\"strconv\"\n\n\t\"github.com/go-redis/redis\"\n\t\"github.com/goccy/go-json\"\n\t\"github.com/pkg/errors\"\n)\n\n// CheckPassword 校验用户名密码是否匹配\nfunc CheckPassword(ctx context.Context, phone, password string) bool {\n\t// 参数验证\n\tif phone == \"\" || password == \"\" {\n\t\tglobal.Logger.Warnf(\"CheckPassword: phone or password is empty, phone: %s\", phone)\n\t\treturn false\n\t}\n\n\t// 密码长度必须大于6\n\tif len(password) < 6 {\n\t\tglobal.Logger.Warnf(\"CheckPassword: password too short, phone: %s, password length: %d\", phone, len(password))\n\t\treturn false\n\t}\n\n\t// 对密码进行md5加密\n\tencryptedPassword := utils.EncryptPassword(password)\n\n\t// 首先尝试从Redis获取用户ID\n\tuserPhoneKey := utils.MakeUserPhoneKey(phone)\n\tuserIdBytes, err := utils.GetValueFromRedis(userPhoneKey)\n\n\tif err == nil && len(userIdBytes) > 0 {\n\n\t\tuserId := string(userIdBytes)\n\t\tglobal.Logger.Infof(\"CheckPassword: get userId from redis success, phone: %s, userId: %s\", phone, userId)\n\n\t\t// 从Redis获取用户信息\n\t\tuserInfo, err := GetUserInfo2Redis(userId)\n\t\tif err != nil {\n\t\t\tif errors.Is(err, redis.Nil) {\n\t\t\t\tglobal.Logger.Infof(\"CheckPassword: user info not found in redis, phone: %s, userId: %s\", phone, userId)\n\t\t\t} else {\n\t\t\t\tglobal.Logger.Errorf(\"CheckPassword: get user info from redis failed, phone: %s, userId: %s, err: %v\", phone, userId, err)\n\t\t\t}\n\t\t\t// Redis获取失败，继续查询数据库\n\t\t} else {\n\t\t\t// Redis获取成功，比较密码\n\t\t\tglobal.Logger.Infof(\"CheckPassword: get user info from redis success, phone: %s, userId: %s\", phone, userId)\n\t\t\treturn userInfo.Password == encryptedPassword\n\t\t}\n\t} else if errors.Is(err, redis.Nil) {\n\t\tglobal.Logger.Infof(\"CheckPassword: userId not found in redis, phone: %s\", phone)\n\t} else {\n\t\tglobal.Logger.Errorf(\"CheckPassword: get userId from redis failed, phone: %s, err: %v\", phone, err)\n\t}\n\n\t// Redis查询失败，从数据库查询\n\tglobal.Logger.Infof(\"CheckPassword: querying user from database, phone: %s\", phone)\n\tuserInfo, err := utils.QueryUserByPhone(ctx, phone)\n\tif err != nil {\n\t\tglobal.Logger.Errorf(\"CheckPassword: failed to query user by phone, phone: %s, err: %v\", phone, err)\n\t\treturn false\n\t}\n\n\tif userInfo == nil {\n\t\tglobal.Logger.Warnf(\"CheckPassword: user not found in database, phone: %s\", phone)\n\t\treturn false\n\t}\n\n\t// 比较密码\n\tpasswordMatch := userInfo.Password == encryptedPassword\n\tglobal.Logger.Infof(\"CheckPassword: password check result, phone: %s, match: %v\", phone, passwordMatch)\n\n\treturn passwordMatch\n}\n\nfunc SetUserInfo2Redis(userInfo model.User) error {\n\tuserInfoByte, err := json.Marshal(userInfo)\n\tif err != nil {\n\t\tglobal.Logger.Errorf(\"userInfo %v,failed to marshal user info: %v\", userInfo, err)\n\t\treturn err\n\t}\n\tif err := utils.SetValueToRedis(strconv.FormatInt(userInfo.ID, 10), string(userInfoByte), enum.DefaultCacheTime); err != nil {\n\t\tglobal.Logger.Errorf(\"userInfo %v,failed to set user info to redis: %v\", userInfo, err)\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc GetUserInfo2Redis(userId string) (model.User, error) {\n\tif userId == \"\" || userId == \"0\" {\n\t\treturn model.User{}, errors.New(\"userId is zero\")\n\t}\n\tuserInfoByte, err := utils.GetValueFromRedis(userId)\n\tif err != nil {\n\t\tif errors.Is(err, redis.Nil) {\n\t\t\tglobal.Logger.Infof(\"userId %s, user info not found in redis\", userId)\n\t\t\treturn model.User{}, err\n\t\t}\n\t\tglobal.Logger.Errorf(\"userId %s, failed to get user info from redis: %v\", userId, err)\n\t\treturn model.User{}, err\n\t}\n\tuserInfo := model.User{}\n\tif err := json.Unmarshal([]byte(userInfoByte), &userInfo); err != nil {\n\t\tglobal.Logger.Errorf(\"userId %s, failed to unmarshal user info: %v\", userId, err)\n\t\treturn model.User{}, err\n\t}\n\n\tglobal.Logger.Infof(\"userId %s, user info found in redis: %v\", userId, userInfo)\n\treturn userInfo, nil\n}\n\nfunc GetUserInfo2DB(phone string) (model.User, error) {\n\tif phone == \"\" {\n\t\treturn model.User{}, errors.New(\"phone is empty\")\n\t}\n\n\tctx := context.Background()\n\tuserInfo, err := utils.QueryUserByPhone(ctx, phone)\n\tif err != nil {\n\t\tglobal.Logger.Errorf(\"phone %s, failed to query user by phone: %v\", phone, err)\n\t\treturn model.User{}, err\n\t}\n\n\tglobal.Logger.Infof(\"phone %s, user info found in db: %v\", phone, userInfo)\n\treturn *userInfo, nil\n}\n\nfunc GetUserInfo2DBById(ctx context.Context, userId string) (model.User, error) {\n\tif userId == \"\" {\n\t\treturn model.User{}, errors.New(\"userId is zero\")\n\t}\n\n\tuserInfo, err := utils.QueryUserByID(ctx, userId)\n\tif err != nil {\n\t\tglobal.Logger.Errorf(\"userId %s, failed to query user by phone: %v\", userId, err)\n\t\treturn model.User{}, err\n\t}\n\n\tglobal.Logger.Infof(\"userId %s, user info found in db: %v\", userId, userInfo)\n\treturn *userInfo, nil\n}\n"
  },
  {
    "path": "logic/register.go",
    "content": "package logic\n\nimport (\n\t\"DiTing-Go/domain/enum\"\n\t\"DiTing-Go/global\"\n\t\"DiTing-Go/utils\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\n\t\"github.com/go-redis/redis\"\n\t\"gorm.io/gorm\"\n)\n\n// CheckCaptchaProcess 检查验证码\nfunc CheckCaptchaProcess(phone, captcha string) bool {\n\t// 检查验证码\n\tphoneKey := utils.MakeUserCaptchaKey(phone)\n\tif !CheckCaptcha(phoneKey, captcha) {\n\t\tglobal.Logger.Infof(\"验证码错误\")\n\t\treturn false\n\t}\n\treturn true\n}\n\n// CheckCaptchaExist 检查验证码是否存在\nfunc CheckCaptchaExist(phone string) bool {\n\t// 检查验证码\n\tphoneKey := utils.MakeUserCaptchaKey(phone)\n\t_, err := utils.GetValueFromRedis(phoneKey)\n\tif errors.Is(err, redis.Nil) {\n\t\tglobal.Logger.Infof(\"phoneKey:%s, 验证码不存在\", phoneKey)\n\t\treturn false\n\t} else if err != nil {\n\t\tglobal.Logger.Errorf(\"phoneKey:%s, 查询验证码失败: %v\", phoneKey, err)\n\t\treturn false\n\t}\n\n\tglobal.Logger.Infof(\"phoneKey:%s, 验证码存在\", phoneKey)\n\treturn true\n}\n\n// CheckPhoneInRedis 检查手机号在redis中是否存在\nfunc CheckPhoneInRedis(phone string) bool {\n\tphoneKey := utils.MakeUserPhoneKey(phone)\n\trst, err := utils.GetValueFromRedis(phoneKey)\n\t// 如果redis没查到,查数据库\n\tif errors.Is(err, redis.Nil) {\n\t\treturn false\n\t}\n\t// 如果redis查询出错，查数据库\n\tif err != nil {\n\t\tglobal.Logger.Errorf(\"phoneKey:%s, 查询手机号失败: %v\", phoneKey, err)\n\t\treturn false\n\t}\n\t// 如果redis查到了,直接返回\n\tif rst != nil {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// CheckPhoneInDB 检查手机号在db中是否存在\nfunc CheckPhoneInDB(ctx context.Context, phone string) (bool, error) {\n\t// 如果redis查不到,查数据库\n\tuser, err := utils.QueryUserByPhone(ctx, phone)\n\t// 数据库查询出错，返回失败\n\tif err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {\n\t\tglobal.Logger.Errorf(\"phone:%s, 查询手机号失败: %v\", phone, err)\n\t\treturn true, err\n\t}\n\t//数据库查到了，返回失败,且用户状态为正常\n\tif user != nil && user.Status == enum.UserStatusNormal {\n\t\t// 将用户信息存入redis\n\t\tphoneKey := utils.MakeUserPhoneKey(phone)\n\t\t// 将phone->user.ID存入redis\n\t\tif err := utils.SetValueToRedis(phoneKey, fmt.Sprintf(\"%d\", user.ID), enum.DefaultCacheTime); err != nil {\n\t\t\tglobal.Logger.Errorf(\"phone:%s, 设置phoneUid映射 redis失败: %v\", phone, err)\n\t\t\treturn true, err\n\t\t}\n\t\tglobal.Logger.Infof(\"phone:%s, 用户已存在\", phone)\n\t\treturn true, err\n\t}\n\treturn false, nil\n}\n"
  },
  {
    "path": "logic/user.go",
    "content": "package logic\n\nimport (\n\t\"DiTing-Go/dal/model\"\n\t\"DiTing-Go/dal/query\"\n\t\"DiTing-Go/domain/enum\"\n\t\"DiTing-Go/global\"\n\t\"context\"\n\n\t\"github.com/pkg/errors\"\n\t\"gorm.io/gorm\"\n)\n\nfunc CreateUser(ctx context.Context, userInfo model.User) error {\n\tuser := query.User\n\tuserQ := user.WithContext(ctx)\n\n\tuserId := userInfo.ID\n\t// 检查用户是否存在\n\tuserResult, err := userQ.Where(user.ID.Eq(userId)).First()\n\tif err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {\n\t\tglobal.Logger.Errorf(\"user:%v, 检查用户是否存在失败: %v\", userResult, err)\n\t\treturn err\n\t}\n\t// 将用户状态更新为正常\n\tif userResult != nil && userResult.Status == int32(enum.UserStatusCancel) {\n\t\tglobal.Logger.Errorf(\"user:%v, 用户已注销\", userResult)\n\t\tuserInfo.Status = enum.UserStatusNormal\n\t\tif err := userQ.Save(&userInfo); err != nil {\n\t\t\tglobal.Logger.Errorf(\"user:%v, 更新用户状态失败: %v\", userResult, err)\n\t\t\treturn err\n\t\t}\n\t} else {\n\t\t// 创建对象\n\t\tif err := userQ.Create(&userInfo); err != nil {\n\t\t\tglobal.Logger.Errorf(\"user:%v, 创建用户失败: %v\", userResult, err)\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "logic/user_cancle.go",
    "content": "package logic\n\nimport (\n\t\"DiTing-Go/dal/query\"\n\t\"DiTing-Go/domain/enum\"\n\t\"DiTing-Go/global\"\n\t\"DiTing-Go/utils\"\n\t\"context\"\n\t\"strconv\"\n)\n\n// DeleteUserInfoFromRedis 删除用户缓存\nfunc DeleteUserInfoFromRedis(userId string) error {\n\tif err := utils.DeleteValueFromRedis(userId); err != nil {\n\t\tglobal.Logger.Errorf(\"删除用户缓存失败: userId=%s, err=%v\", userId, err)\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// DeleteUserInfoFromDB 删除用户数据库,软删除\nfunc DeleteUserInfoFromDB(ctx context.Context, userId string) error {\n\tuser := query.User\n\tuserQ := user.WithContext(ctx)\n\tuserIdInt, err := strconv.ParseInt(userId, 10, 64)\n\tif err != nil {\n\t\tglobal.Logger.Errorf(\"转换用户ID失败: userId=%s, err=%v\", userId, err)\n\t\treturn err\n\t}\n\tif _, err := userQ.Where(user.ID.Eq(userIdInt)).Update(user.Status, enum.UserStatusCancel); err != nil {\n\t\tglobal.Logger.Errorf(\"删除用户数据库失败: userId=%s, err=%v\", userId, err)\n\t\treturn err\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "main.go",
    "content": "package main\n\nimport (\n\t//_ \"DiTing-Go/event/listener\"\n\t\"DiTing-Go/global\"\n\t\"DiTing-Go/routes\"\n\t\"DiTing-Go/utils/setting\"\n)\n\n// swagger 中添加header.Authorization:token 校验 token\n// @securityDefinitions.apikey ApiKeyAuth\n// @in header\n// @name Authorization\nfunc main() {\n\t// 初始化配置\n\tsetting.ConfigInit()\n\t// 初始化数据库连接\n\tglobal.DBInit()\n\t//初始化redis连接\n\tglobal.RedisInit()\n\tglobal.LogInit()\n\n\troutes.InitRouter()\n}\n"
  },
  {
    "path": "pkg/domain/enum/code.go",
    "content": "package enum\n\nconst (\n\tSUCCESS        = 200\n\tERROR          = 500\n\tINVALID_PARAMS = 400\n\n\tERROR_EXIST_TAG         = 10001\n\tERROR_NOT_EXIST_TAG     = 10002\n\tERROR_NOT_EXIST_ARTICLE = 10003\n\n\tERROR_AUTH_CHECK_TOKEN_FAIL    = 20001\n\tERROR_AUTH_CHECK_TOKEN_TIMEOUT = 20002\n\tERROR_AUTH_TOKEN               = 20003\n\tERROR_AUTH                     = 20004\n)\n"
  },
  {
    "path": "pkg/domain/enum/common_enum.go",
    "content": "package enum\n\nconst (\n\tYES     = 2\n\tNO      = 1\n\tNORMAL  = 1\n\tDELETED = 2\n)\n"
  },
  {
    "path": "pkg/domain/enum/msg.go",
    "content": "package enum\n\nvar MsgFlags = map[int]string{\n\tSUCCESS:                        \"ok\",\n\tERROR:                          \"fail\",\n\tINVALID_PARAMS:                 \"请求参数错误\",\n\tERROR_AUTH_CHECK_TOKEN_FAIL:    \"Token鉴权失败\",\n\tERROR_AUTH_CHECK_TOKEN_TIMEOUT: \"Token已超时\",\n\tERROR_AUTH_TOKEN:               \"Token生成失败\",\n\tERROR_AUTH:                     \"Token错误\",\n}\n\nfunc GetMsg(code int) string {\n\tmsg, ok := MsgFlags[code]\n\tif ok {\n\t\treturn msg\n\t}\n\n\treturn MsgFlags[ERROR]\n}\n"
  },
  {
    "path": "pkg/domain/vo/req/page_req.go",
    "content": "package req\n\ntype PageReq struct {\n\tCursor   *string `json:\"cursor\" form:\"cursor\"`\n\tPageSize int     `json:\"pageSize\" form:\"pageSize\" binding:\"required\"`\n}\n"
  },
  {
    "path": "pkg/domain/vo/resp/page_resp.go",
    "content": "package resp\n\ntype PageResp struct {\n\tCursor *string `json:\"cursor\" form:\"cursor\"`\n\tIsLast bool    `json:\"isLast\" form:\"is_last\"`\n\tData   any     `json:\"data\" form:\"data\"`\n}\n"
  },
  {
    "path": "pkg/domain/vo/resp/response.go",
    "content": "package resp\n\nimport (\n\t\"DiTing-Go/pkg/domain/enum\"\n\t\"github.com/gin-gonic/gin\"\n)\n\n// ResponseData 表示统一响应的JSON格式\ntype ResponseData struct {\n\tSuccess bool        `json:\"success\"` // 是否成功\n\tCode    int         `json:\"code\"`    // 状态码\n\tMessage string      `json:\"message\"` // 响应消息\n\tData    interface{} `json:\"data\"`    // 响应数据\n}\n\n// ErrorResponse 是一个辅助函数，用于创建错误响应\n// 参数：\n//\n//\tc *gin.Context：Gin上下文对象，用于处理HTTP请求和响应\n//\tcode int：500\n//\tmessage string：响应消息，用于描述响应的错误信息或提示信息\nfunc ErrorResponse(c *gin.Context, message string) {\n\tc.JSON(enum.ERROR, ResponseData{\n\t\tCode:    enum.ERROR,\n\t\tSuccess: false,\n\t\tMessage: message,\n\t\tData:    nil,\n\t})\n}\n\n// SuccessResponse 是一个辅助函数，用于创建成功响应\n// 参数：\n//\n//\tc *gin.Context：Gin上下文对象，用于处理HTTP请求和响应\n//\tcode int：200\n//\tdata interface{}：响应数据，用于描述请求处理成功后返回的具体数据\nfunc SuccessResponse(c *gin.Context, data interface{}) {\n\tc.JSON(enum.SUCCESS, ResponseData{\n\t\tCode:    enum.SUCCESS,\n\t\tSuccess: true,\n\t\tMessage: \"success\",\n\t\tData:    data,\n\t})\n}\n\nfunc SuccessResponseWithMsg(c *gin.Context, msg string) {\n\tc.JSON(enum.SUCCESS, ResponseData{\n\t\tCode:    enum.SUCCESS,\n\t\tSuccess: true,\n\t\tMessage: msg,\n\t\tData:    nil,\n\t})\n}\nfunc ReturnSuccessResponse(c *gin.Context, response ResponseData) {\n\tc.JSON(enum.SUCCESS, response)\n}\nfunc ReturnErrorResponse(c *gin.Context, response ResponseData) {\n\tc.JSON(enum.ERROR, response)\n}\n\n// ErrorResponseData 是一个辅助函数，用于创建错误响应\nfunc ErrorResponseData(msg string) ResponseData {\n\treturn ResponseData{\n\t\tCode:    enum.ERROR,\n\t\tSuccess: false,\n\t\tMessage: msg,\n\t\tData:    nil,\n\t}\n}\n\n// SuccessResponseData 是一个辅助函数，用于创建成功响应\nfunc SuccessResponseData(data interface{}) ResponseData {\n\treturn ResponseData{\n\t\tCode:    enum.SUCCESS,\n\t\tSuccess: true,\n\t\tMessage: \"success\",\n\t\tData:    data,\n\t}\n}\n\n// SuccessResponseDataWithMsg 是一个辅助函数，用于创建成功响应\nfunc SuccessResponseDataWithMsg(msg string) ResponseData {\n\treturn ResponseData{\n\t\tCode:    enum.SUCCESS,\n\t\tSuccess: true,\n\t\tMessage: msg,\n\t\tData:    nil,\n\t}\n}\n"
  },
  {
    "path": "pkg/utils/cursor_utils.go",
    "content": "package utils\n\nimport (\n\tpkgReq \"DiTing-Go/pkg/domain/vo/req\"\n\tpkgResp \"DiTing-Go/pkg/domain/vo/resp\"\n\t\"fmt\"\n\t\"github.com/pkg/errors\"\n\t\"gorm.io/gorm\"\n\t\"reflect\"\n\t\"regexp\"\n\t\"time\"\n)\n\n// Paginate 是通用的游标分页函数\n// TODO: select部分字段\nfunc Paginate(db *gorm.DB, params pkgReq.PageReq, result interface{}, cursorFieldName string, isAsc bool, conditions ...interface{}) (*pkgResp.PageResp, error) {\n\tvar resp pkgResp.PageResp\n\n\tquery := db\n\tif len(conditions) > 0 {\n\t\tquery = query.Where(conditions[0], conditions[1:]...)\n\t}\n\n\tif params.Cursor != nil && *params.Cursor != \"\" {\n\t\tquery = query.Where(fmt.Sprintf(\"%s < ?\", cursorFieldName), *params.Cursor)\n\t}\n\n\tif isAsc {\n\t\tquery = query.Order(fmt.Sprintf(\"%s ASC\", cursorFieldName))\n\t} else {\n\t\tquery = query.Order(fmt.Sprintf(\"%s DESC\", cursorFieldName))\n\t}\n\tquery = query.Limit(params.PageSize).Find(result)\n\tif query.Error != nil {\n\t\treturn &resp, query.Error\n\t}\n\n\t// 获取查询结果的切片值\n\tslice := reflect.ValueOf(result).Elem()\n\t// 根据记录条数是否等于页大小判断是否是最后一页\n\tlastItemIndex := slice.Len()\n\tif lastItemIndex < params.PageSize {\n\t\tresp.IsLast = true\n\t} else {\n\t\tresp.IsLast = false\n\t}\n\n\t// 通过反射获取cursorFieldName对应的值\n\tif lastItemIndex > 0 {\n\t\tlastItem := slice.Index(lastItemIndex - 1)\n\t\tfieldsMap, err := GetTagName(result)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tcursorValue := lastItem.FieldByName(fieldsMap[cursorFieldName])\n\t\t//获取cursorValue的类型\n\t\tcursorType := cursorValue.Type()\n\t\t// 如果是Time.time类型，转换为时间戳\n\t\tcursorStr := \"\"\n\t\tif cursorType.String() == \"time.Time\" {\n\t\t\tcursorStr = fmt.Sprint(cursorValue.Interface().(time.Time).UnixNano())\n\t\t} else {\n\t\t\tcursorStr = fmt.Sprint(cursorValue.Interface())\n\t\t}\n\t\tresp.Cursor = &cursorStr\n\t}\n\n\tresp.Data = result\n\treturn &resp, nil\n}\n\n// GetTagName 获取结构体中Tag的值，如果没有tag则返回字段值\nfunc GetTagName(structName interface{}) (map[string]string, error) {\n\tt := reflect.TypeOf(structName).Elem().Elem()\n\tfieldNum := t.NumField()\n\tresult := make(map[string]string, fieldNum)\n\tfor i := 0; i < fieldNum; i++ {\n\t\tfieldName := t.Field(i).Name\n\t\ttag := t.Field(i).Tag.Get(\"gorm\")\n\t\tif tag == \"\" {\n\t\t\tresult[fieldName] = fieldName\n\t\t} else {\n\t\t\t// 定义正则表达式\n\t\t\tre := regexp.MustCompile(`column:([^;]+)`)\n\t\t\t// 使用正则表达式查找匹配项\n\t\t\tmatch := re.FindStringSubmatch(tag)\n\t\t\tcolumn := fieldName\n\t\t\tif len(match) > 1 {\n\t\t\t\tcolumn = match[1]\n\t\t\t} else {\n\t\t\t\treturn nil, errors.New(\"without column error\")\n\t\t\t}\n\t\t\tresult[column] = fieldName\n\t\t}\n\t}\n\treturn result, nil\n}\n"
  },
  {
    "path": "pkg/utils/redis.go",
    "content": "package utils\n\nimport (\n\tdomainEnum \"DiTing-Go/domain/enum\"\n\t\"DiTing-Go/global\"\n\t\"github.com/go-redis/redis\"\n\t\"github.com/goccy/go-json\"\n\t\"github.com/jinzhu/copier\"\n\t\"github.com/pkg/errors\"\n)\n\n// SetString 设置字符串\nfunc SetString(key string, value any) error {\n\tvalueByte, err := json.Marshal(value)\n\tif err = global.Rdb.Set(key, valueByte, domainEnum.DefaultCacheTime).Err(); err != nil {\n\t\treturn errors.New(\"redis set error\")\n\t}\n\treturn nil\n}\n\n// GetString 获取字符串\nfunc GetString(key string, value any) error {\n\tvalueByte, err := global.Rdb.Get(key).Result()\n\tif err != nil && errors.Is(err, redis.Nil) {\n\t\treturn err\n\t} else if err != nil {\n\t\treturn errors.New(\"redis get error\")\n\t}\n\terr = json.Unmarshal([]byte(valueByte), value)\n\tif err != nil {\n\t\treturn errors.New(\"jsonUtils unmarshal error\")\n\t}\n\treturn nil\n}\n\n// GetData 获取数据\nfunc GetData(cacheKey string, value any, dbQueryFunc func() (interface{}, error)) error {\n\t// 1. 从缓存中获取数据\n\terr := GetString(cacheKey, value)\n\t// 查询到数据\n\tif err == nil {\n\t\treturn nil\n\t} else if !errors.Is(err, redis.Nil) {\n\t\treturn err\n\t}\n\terr = QueryAndSet(cacheKey, value, dbQueryFunc)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// QueryAndSet 查询数据库并设置缓存\nfunc QueryAndSet(cacheKey string, value any, dbQueryFunc func() (interface{}, error)) error {\n\t// 2. 从数据库中获取数据\n\tresult, err := dbQueryFunc()\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = copier.Copy(value, result)\n\tif err != nil {\n\t\tglobal.Logger.Errorf(\"拷贝数据失败: %v\", err)\n\t\treturn err\n\t}\n\t// 3. 将查询结果写回缓存\n\tif err = SetString(cacheKey, result); err != nil {\n\t\tglobal.Logger.Errorf(\"写入redis失败: %v\", err)\n\t\treturn err\n\t}\n\treturn err\n}\n\nfunc RemoveData(key string) {\n\tglobal.Rdb.Del(key)\n}\n"
  },
  {
    "path": "pkg/utils/redis_lock.go",
    "content": "package utils\n\n//type RedSyncLock struct {\n//\tmutex  *redsync.Mutex\n//\tctx    context.Context\n//\tcancel context.CancelFunc\n//}\n//\n//// GetLock 获取分布式锁\n//func GetLock(key string) (*RedSyncLock, error) {\n//\tctx, cancel := context.WithCancel(context.Background())\n//\tmutex := global.RedSync.NewMutex(key)\n//\tif err := mutex.LockContext(ctx); err != nil {\n//\t\tcancel()\n//\t\tglobal.Logger.Errorf(\"加锁失败 %s\", err)\n//\t\treturn nil, errors.New(\"Business Error\")\n//\t}\n//\t// 开启一个goroutine，周期性地续租锁\n//\tgo func() {\n//\t\tticker := time.NewTicker(5 * time.Second) // 按照需求调整\n//\t\tdefer ticker.Stop()\n//\t\tfor {\n//\t\t\tselect {\n//\t\t\tcase <-ticker.C:\n//\t\t\t\tok, err := mutex.Extend()\n//\t\t\t\t//fmt.Printf(\"续租锁 %s\\n\", ok)\n//\t\t\t\tif err != nil {\n//\t\t\t\t\tglobal.Logger.Errorf(\"Failed to extend lock: %s\", err)\n//\t\t\t\t\treturn\n//\t\t\t\t} else if !ok {\n//\t\t\t\t\tglobal.Logger.Errorf(\"Failed to extend lock: %s\", fmt.Errorf(\"lock %s is not held\", key))\n//\t\t\t\t\treturn\n//\t\t\t\t}\n//\t\t\tcase <-ctx.Done():\n//\t\t\t\treturn\n//\t\t\t}\n//\t\t}\n//\t}()\n//\n//\treturn &RedSyncLock{\n//\t\tmutex:  mutex,\n//\t\tctx:    ctx,\n//\t\tcancel: cancel,\n//\t}, nil\n//}\n//\n//// ReleaseLock 释放分布式锁, 释放锁失败不影响业务\n//func ReleaseLock(lock *RedSyncLock) {\n//\tlock.cancel()\n//\tmutex := lock.mutex\n//\t_, err := mutex.Unlock()\n//\tif err != nil {\n//\t\tglobal.Logger.Errorf(\"解锁失败 %s\", err)\n//\t}\n//}\n"
  },
  {
    "path": "pkg/utils/sort.go",
    "content": "package utils\n\ntype Int64Slice []int64\n\nfunc (p Int64Slice) Len() int {\n\treturn len(p)\n}\n\nfunc (p Int64Slice) Less(i, j int) bool {\n\treturn p[i] < p[j]\n}\n\nfunc (p Int64Slice) Swap(i, j int) {\n\tp[i], p[j] = p[j], p[i]\n}\n"
  },
  {
    "path": "routes/init_router.go",
    "content": "package routes\n\nimport (\n\t\"DiTing-Go/controller\"\n\t_ \"DiTing-Go/docs\"\n\t\"DiTing-Go/pkg/domain/vo/resp\"\n\t\"DiTing-Go/utils/middleware\"\n\t\"DiTing-Go/websocket/global\"\n\twebsocketService \"DiTing-Go/websocket/service\"\n\t\"log\"\n\t\"net/http\"\n\n\t\"github.com/gin-gonic/gin\"\n\tswaggerFiles \"github.com/swaggo/files\"\n\tginSwagger \"github.com/swaggo/gin-swagger\"\n)\n\n// InitRouter 初始化路由\nfunc InitRouter() {\n\t//go initWebSocket()\n\tinitGin()\n}\n\n// 初始化websocket\nfunc initWebSocket() {\n\thttp.HandleFunc(\"/websocket\", websocketService.Connect)\n\tlog.Fatal(http.ListenAndServe(\"localhost:5001\", nil))\n}\n\n// 初始化gin\nfunc initGin() {\n\trouter := gin.Default()\n\trouter.Use(middleware.LoggerToFile())\n\trouter.Use(middleware.Cors())\n\t//添加swagger访问路由\n\trouter.GET(\"/swagger/*any\", ginSwagger.WrapHandler(swaggerFiles.Handler))\n\t// 不需要身份验证的路由\n\tapiPublic := router.Group(\"/api/public\")\n\t{\n\t\t// 用户注册\n\t\tapiPublic.POST(\"/register\", controller.RegisterController)\n\t\t// 验证码发送\n\t\tapiPublic.POST(\"/captcha\", controller.CaptchaController)\n\t\t// 用户登录\n\t\tapiPublic.POST(\"/login\", controller.LoginController)\n\t}\n\n\tapiUser := router.Group(\"/api/user\")\n\tapiUser.Use(middleware.JWT())\n\t{\n\t\t// 注销账户\n\t\tapiUser.DELETE(\"/cancel\", controller.CancelController)\n\t\t//\t//添加好友\n\t\t//\tapiUser.POST(\"/add\", controller.ApplyFriendController)\n\t\t//\t//删除好友\n\t\t//\tapiUser.DELETE(\"/delete/\", controller.DeleteFriendController)\n\t\t//\t//同意好友申请\n\t\t//\tapiUser.PUT(\"/agree\", controller.AgreeFriendController)\n\t\t//\t//获取好友申请列表\n\t\t//\tapiUser.GET(\"/getApplyList\", controller.GetUserApplyController)\n\t\t//\t//获取好友列表\n\t\t//\tapiUser.GET(\"/getFriendList\", controller.GetFriendListController)\n\t\t//\t// 判断是否是好友\n\t\t//\tapiUser.GET(\"/isFriend/:friendUid\", controller.IsFriendController)\n\t\t//\t//好友申请未读数量\n\t\t//\tapiUser.GET(\"/unreadApplyNum\", controller.UnreadApplyNumController)\n\t\t//\t//根据好友昵称搜索好友\n\t\t//\tapiUser.GET(\"/getUserInfoByName\", controller.GetUserInfoByNameController)\n\t\t//\t// TODO:测试使用\n\t\t//\tapiUser.GET(\"/test\", test)\n\t}\n\t//apiGroup := router.Group(\"/api/group\")\n\t//apiGroup.Use(middleware.JWT())\n\t//{\n\t//\t//创建群聊\n\t//\tapiGroup.POST(\"/create\", controller.CreateGroupController)\n\t//\tapiGroup.DELETE(\"/:id\", service.DeleteGroupService)\n\t//\tapiGroup.POST(\"/join\", service.JoinGroupService)\n\t//\tapiGroup.POST(\"/quit\", service.QuitGroupService)\n\t//\tapiGroup.GET(\"/getGroupMemberList\", service.GetGroupMemberListService)\n\t//\tapiGroup.POST(\"/grantAdministrator\", service.GrantAdministratorService)\n\t//\tapiGroup.POST(\"/removeAdministrator\", service.RemoveAdministratorService)\n\t//}\n\t//\n\t//apiContact := router.Group(\"/api/contact\")\n\t//apiContact.Use(middleware.JWT())\n\t//{\n\t//\tapiContact.GET(\"getContactList\", controller.GetContactListController)\n\t//\tapiContact.GET(\"getNewContactList\", controller.GetNewContactListController)\n\t//\tapiContact.GET(\"getMessageList\", service.GetContactDetailService)\n\t//\tapiContact.GET(\"getNewMsgList\", controller.GetNewMsgListController)\n\t//\tapiContact.POST(\"userInfo/batch\", controller.GetUserInfoBatchController)\n\t//}\n\t//\n\t//apiMsg := router.Group(\"/api/chat\")\n\t//apiMsg.Use(middleware.JWT())\n\t//{\n\t//\tapiMsg.POST(\"msg\", controller.SendMessageController)\n\t//}\n\n\t//apiFile := router.Group(\"/api/file\")\n\t//apiFile.Use(middleware.JWT())\n\t//{\n\t//\tapiFile.GET(\"getPreSigned\", service.GetPreSigned)\n\t//}\n\n\terr := router.Run(\":5000\")\n\tif err != nil {\n\t\treturn\n\t}\n}\n\n// TODO:测试使用\nfunc test(c *gin.Context) {\n\tmsg := new(global.Msg)\n\tmsg.Uid = 2\n\twebsocketService.Send(msg.Uid, []byte(\"{\\\"type\\\":4}\"))\n\tresp.SuccessResponse(c, nil)\n}\n"
  },
  {
    "path": "service/adapter/build_contact_dao_list.go",
    "content": "package adapter\n\nimport (\n\t\"DiTing-Go/dal/model\"\n\t\"DiTing-Go/domain/dto\"\n\t\"DiTing-Go/domain/enum\"\n\tcmap \"github.com/orcaman/concurrent-map/v2\"\n\t\"strconv\"\n)\n\ntype RoomDto struct {\n\tID     int64\n\tAvatar string\n\tName   string\n\tType   int\n}\n\nfunc 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 {\n\tcontactDtoList := make([]dto.ContactDto, 0)\n\n\tuserMap := make(map[int64]*model.User)\n\tfor _, user := range userList {\n\t\tuserMap[user.ID] = user\n\t}\n\n\tmsgMap := make(map[int64]*model.Message)\n\tfor _, msg := range messageList {\n\t\tmsgMap[msg.ID] = msg\n\t}\n\n\troomFriendMap := make(map[int64]*model.RoomFriend)\n\troomGroupMap := make(map[int64]*model.RoomGroup)\n\tfor _, roomFriend := range roomFriendList {\n\t\troomFriendMap[roomFriend.RoomID] = roomFriend\n\t}\n\tfor _, roomGroup := range roomGroupList {\n\t\troomGroupMap[roomGroup.RoomID] = roomGroup\n\t}\n\n\troomMap := make(map[int64]RoomDto)\n\tfor _, room := range roomList {\n\t\troomDto := RoomDto{}\n\t\troomDto.ID = room.ID\n\t\tif room.Type == enum.PERSONAL {\n\t\t\tuserId := roomFriendMap[room.ID].Uid1\n\t\t\tif userId == contactList[0].UID {\n\t\t\t\tuserId = roomFriendMap[room.ID].Uid2\n\t\t\t}\n\t\t\tuser := userMap[userId]\n\t\t\troomDto.Avatar = user.Avatar\n\t\t\troomDto.Name = user.Name\n\t\t\troomDto.Type = enum.PERSONAL\n\t\t} else {\n\t\t\troomGroup := roomGroupMap[room.ID]\n\t\t\troomDto.Avatar = roomGroup.Avatar\n\t\t\troomDto.Name = roomGroup.Name\n\t\t\troomDto.Type = enum.GROUP\n\t\t}\n\t\troomMap[room.ID] = roomDto\n\t}\n\tfor _, contact := range contactList {\n\t\tcontactDto := dto.ContactDto{}\n\t\tcontactDto.ID = contact.ID\n\t\tcontactDto.RoomID = contact.RoomID\n\t\tcontactDto.Avatar = roomMap[contact.RoomID].Avatar\n\t\tcontactDto.Name = roomMap[contact.RoomID].Name\n\t\tif msgMap[contact.LastMsgID] != nil {\n\t\t\tcontactDto.LastMsg = msgMap[contact.LastMsgID].Content\n\t\t}\n\t\tcontactDto.LastTime = contact.ActiveTime.UnixMilli()\n\t\t//TODO：统计未读消息数\n\t\tunreadCount, _ := countMap.Get(strconv.FormatInt(contact.RoomID, 10))\n\t\tcontactDto.UnreadCount = int32(unreadCount)\n\t\tcontactDto.Type = roomMap[contact.RoomID].Type\n\t\tcontactDtoList = append(contactDtoList, contactDto)\n\t}\n\treturn contactDtoList\n}\n"
  },
  {
    "path": "service/adapter/build_message_resp.go",
    "content": "package adapter\n\nimport (\n\t\"DiTing-Go/dal/model\"\n\t\"DiTing-Go/domain/vo/resp\"\n)\n\nfunc BuildMessageRespByMsgAndUser(msgList *[]model.Message, userMap map[int64]*model.User) []resp.MessageResp {\n\tvar messageRespList []resp.MessageResp\n\tfor i := range len(*msgList) {\n\t\tmessageResp := resp.MessageResp{}\n\t\tmsg := (*msgList)[i]\n\t\tmsgUser := resp.MsgUser{}\n\t\tmsgUser.Uid = userMap[msg.FromUID].ID\n\t\tmsgUser.Username = userMap[msg.FromUID].Name\n\t\tmsgUser.Avatar = userMap[msg.FromUID].Avatar\n\t\tmessageResp.FromUser = msgUser\n\n\t\tmessage := resp.Msg{}\n\t\tmessage.ID = msg.ID\n\t\tmessage.RoomId = msg.RoomID\n\t\tmessage.Type = msg.Type\n\t\tmessage.Body.Content = msg.Content\n\t\tmessage.Body.Reply = msg.ReplyMsgID\n\t\tmessageResp.Message = message\n\n\t\tmessageResp.SendTime = msg.CreateTime.UnixNano()\n\n\t\tmessageRespList = append(messageRespList, messageResp)\n\t}\n\treturn messageRespList\n}\n"
  },
  {
    "path": "service/adapter/build_user_info_by_name_resp.go",
    "content": "package adapter\n\nimport (\n\t\"DiTing-Go/dal/model\"\n\t\"DiTing-Go/domain/vo/resp\"\n)\n\nfunc BuildUserInfoByNameResp(userList []*model.User, userApply []*model.UserApply, userFriend []*model.UserFriend) []resp.GetUserInfoByNameResp {\n\tuserApplyMap := make(map[int64]*model.UserApply)\n\tfor _, apply := range userApply {\n\t\tuserApplyMap[apply.TargetID] = apply\n\t}\n\tuserFriendMap := make(map[int64]*model.UserFriend)\n\tfor _, friend := range userFriend {\n\t\tuserFriendMap[friend.FriendUID] = friend\n\t}\n\tgetUserInfoByNameRespList := make([]resp.GetUserInfoByNameResp, 0)\n\tfor _, user := range userList {\n\t\tgetUserInfoByNameResp := resp.GetUserInfoByNameResp{\n\t\t\tUid:    user.ID,\n\t\t\tName:   user.Name,\n\t\t\tAvatar: user.Avatar,\n\t\t}\n\n\t\t//TODO:抽象为常量\n\t\tif userFriendMap[user.ID] != nil {\n\t\t\tgetUserInfoByNameResp.Status = 3\n\t\t} else if userApplyMap[user.ID] != nil {\n\t\t\tgetUserInfoByNameResp.Status = 2\n\t\t} else {\n\t\t\tgetUserInfoByNameResp.Status = 1\n\t\t}\n\t\tgetUserInfoByNameRespList = append(getUserInfoByNameRespList, getUserInfoByNameResp)\n\t}\n\treturn getUserInfoByNameRespList\n}\n"
  },
  {
    "path": "service/captcha_service.go",
    "content": "package service\n\nimport (\n\t\"DiTing-Go/domain/vo/req\"\n\t\"DiTing-Go/global\"\n\t\"DiTing-Go/logic\"\n\tpkgResp \"DiTing-Go/pkg/domain/vo/resp\"\n)\n\n// CaptchaService 验证码发送\nfunc CaptchaService(captchaReq req.CaptchaReq) (pkgResp.ResponseData, error) {\n\tif captchaReq.Phone == \"\" {\n\t\tglobal.Logger.Infof(\"手机号不能为空\")\n\t\treturn pkgResp.ErrorResponseData(\"手机号不能为空\"), nil\n\t}\n\n\t//检查验证码是否未过期,未过期不生成\n\tif logic.CheckCaptchaExist(captchaReq.Phone) {\n\t\tglobal.Logger.Infof(\"手机号:%s,验证码未过期\", captchaReq.Phone)\n\t\treturn pkgResp.ErrorResponseData(\"发送频率过高,请稍后再试\"), nil\n\t}\n\n\t// 生成验证码\n\tcaptcha, err := logic.GenerateCaptcha(captchaReq.Phone)\n\tif err != nil {\n\t\tglobal.Logger.Errorf(\"phone:%s ,生成验证码失败: %v\", captchaReq.Phone, err)\n\t\treturn pkgResp.ErrorResponseData(\"系统繁忙，请稍后再试\"), err\n\t}\n\n\t// 发送验证码\n\t// TODO:空实现，实际应用中可以使用短信服务商的SDK发送验证码\n\tif err := logic.SendCaptcha(captchaReq.Phone, captcha); err != nil {\n\t\tglobal.Logger.Errorf(\"phone:%s ,发送验证码失败: %v\", captchaReq.Phone, err)\n\t\treturn pkgResp.ErrorResponseData(\"验证码发送失败，请稍后再试\"), err\n\t}\n\n\treturn pkgResp.SuccessResponseDataWithMsg(\"验证码发送成功\"), nil\n}\n"
  },
  {
    "path": "service/contact_service.go",
    "content": "package service\n\n//\n//import (\n//\t\"DiTing-Go/dal\"\n//\t\"DiTing-Go/dal/model\"\n//\t\"DiTing-Go/domain/enum\"\n//\t\"DiTing-Go/domain/vo/req\"\n//\tdomainResp \"DiTing-Go/domain/vo/resp\"\n//\t\"DiTing-Go/global\"\n//\tpkgEnum \"DiTing-Go/pkg/domain/enum\"\n//\tpkgReq \"DiTing-Go/pkg/domain/vo/req\"\n//\t\"DiTing-Go/pkg/domain/vo/resp\"\n//\tpkgResp \"DiTing-Go/pkg/domain/vo/resp\"\n//\t\"DiTing-Go/pkg/utils\"\n//\t\"DiTing-Go/service/adapter\"\n//\t\"context\"\n//\t\"github.com/gin-gonic/gin\"\n//\tcmap \"github.com/orcaman/concurrent-map/v2\"\n//\t\"github.com/pkg/errors\"\n//\t\"strconv\"\n//\t\"sync\"\n//\t\"time\"\n//)\n//\n//func GetContactListService(uid int64, pageReq pkgReq.PageReq) (pkgResp.ResponseData, error) {\n//\tdb := dal.DB\n//\tcontact := make([]model.Contact, 0)\n//\tcondition := []interface{}{\"uid=?\", strconv.FormatInt(uid, 10)}\n//\tif pageReq.Cursor != nil && *pageReq.Cursor != \"\" {\n//\t\t// 时间戳转时间\n//\t\ttimestamp, err := strconv.ParseInt(*pageReq.Cursor, 10, 64)\n//\t\tif err != nil {\n//\t\t\tglobal.Logger.Errorf(\"时间戳转换失败 %s\", err)\n//\t\t\treturn pkgResp.ErrorResponseData(\"系统繁忙，请稍后再试~\"), errors.New(\"Business Error\")\n//\t\t}\n//\t\tcursor := time.Unix(0, timestamp)\n//\t\tcursorStr := cursor.Format(time.RFC3339Nano)\n//\t\tpageReq.Cursor = &cursorStr\n//\t}\n//\n//\tpageResp, err := utils.Paginate(db, pageReq, &contact, \"active_time\", false, condition...)\n//\tif err != nil {\n//\t\tglobal.Logger.Errorf(\"查询会话列表失败 %s\", err)\n//\t\treturn pkgResp.ErrorResponseData(\"系统繁忙，请稍后再试~\"), errors.New(\"Business Error\")\n//\t}\n//\tcontactList := pageResp.Data.(*[]model.Contact)\n//\n//\t// 收集会话id\n//\tcontactRoomIdList := make([]int64, 0)\n//\tfor _, contact := range *contactList {\n//\t\tcontactRoomIdList = append(contactRoomIdList, contact.RoomID)\n//\t}\n//\n//\t// 查询出对应的房间信息\n//\tctx := context.Background()\n//\troom := global.Query.Room\n//\troomQ := room.WithContext(ctx)\n//\t// 查询房间类型\n//\troomRList, err := roomQ.Where(room.ID.In(contactRoomIdList...)).Find()\n//\tif err != nil {\n//\t\tglobal.Logger.Errorf(\"查询房间失败 %s\", err)\n//\t\treturn pkgResp.ErrorResponseData(\"系统繁忙，请稍后再试~\"), errors.New(\"Business Error\")\n//\t}\n//\t// 收集单聊房间的id\n//\troomFriendIdList := make([]int64, 0)\n//\t// 收集群聊房间的id\n//\troomGroupIdList := make([]int64, 0)\n//\tfor _, room := range roomRList {\n//\t\tif room.Type == enum.PERSONAL {\n//\t\t\troomFriendIdList = append(roomFriendIdList, room.ID)\n//\t\t} else if room.Type == enum.GROUP {\n//\t\t\troomGroupIdList = append(roomGroupIdList, room.ID)\n//\t\t}\n//\t}\n//\n//\t// 查询好友房间信息\n//\troomFriend := global.Query.RoomFriend\n//\troomFriendQ := roomFriend.WithContext(ctx)\n//\troomFriendRList, err := roomFriendQ.Where(roomFriend.RoomID.In(roomFriendIdList...)).Find()\n//\tif err != nil {\n//\t\tglobal.Logger.Errorf(\"查询好友房间失败 %s\", err)\n//\t\treturn pkgResp.ErrorResponseData(\"系统繁忙，请稍后再试~\"), errors.New(\"Business Error\")\n//\t}\n//\t// 查询用户信息\n//\tuidList := make([]int64, 0)\n//\tfor _, roomFriend := range roomFriendRList {\n//\t\tif roomFriend.Uid1 == uid {\n//\t\t\tuidList = append(uidList, roomFriend.Uid2)\n//\t\t} else {\n//\t\t\tuidList = append(uidList, roomFriend.Uid1)\n//\t\t}\n//\t}\n//\tuser := global.Query.User\n//\tuserQ := user.WithContext(ctx)\n//\tuserRList, err := userQ.Where(user.ID.In(uidList...)).Find()\n//\tif err != nil {\n//\t\tglobal.Logger.Errorf(\"查询用户失败 %s\", err)\n//\t\treturn pkgResp.ErrorResponseData(\"系统繁忙，请稍后再试~\"), errors.New(\"Business Error\")\n//\t}\n//\n//\t// 查询群聊房间信息\n//\troomGroup := global.Query.RoomGroup\n//\troomGroupQ := roomGroup.WithContext(ctx)\n//\troomGroupRList, err := roomGroupQ.Where(roomGroup.RoomID.In(roomGroupIdList...)).Find()\n//\tif err != nil {\n//\t\tglobal.Logger.Errorf(\"查询群聊房间失败 %s\", err)\n//\t\treturn pkgResp.ErrorResponseData(\"系统繁忙，请稍后再试~\"), errors.New(\"Business Error\")\n//\t}\n//\n//\t// 查询最后一条消息\n//\tlastMsgIdList := make([]int64, 0)\n//\tfor _, contact := range *contactList {\n//\t\tlastMsgIdList = append(lastMsgIdList, contact.LastMsgID)\n//\t}\n//\tmsg := global.Query.Message\n//\tmsgQ := msg.WithContext(ctx)\n//\tmsgRList, err := msgQ.Where(msg.ID.In(lastMsgIdList...)).Find()\n//\tif err != nil {\n//\t\tglobal.Logger.Errorf(\"查询最后一条消息失败 %s\", err)\n//\t\treturn pkgResp.ErrorResponseData(\"系统繁忙，请稍后再试~\"), errors.New(\"Business Error\")\n//\t}\n//\n//\t// 查询未读消息数\n//\t//fixme: 有没有更好的方法\n//\tcountMap := cmap.New[int64]()\n//\tvar wg sync.WaitGroup\n//\terr = nil\n//\tfor i, contact := range *contactList {\n//\t\twg.Add(1)\n//\t\tgo func() {\n//\n//\t\t\tdefer wg.Done()\n//\t\t\tvar count int64\n//\t\t\tcount, err = msgQ.Where(msg.RoomID.Eq(contact.RoomID), msg.DeleteStatus.Eq(pkgEnum.NORMAL), msg.CreateTime.Gt(contact.ReadTime)).Limit(99).Count()\n//\t\t\tif err != nil {\n//\t\t\t\tglobal.Logger.Errorf(\"统计未读数失败 %s\", err)\n//\t\t\t}\n//\t\t\tcountMap.Set(strconv.FormatInt(contact.RoomID, 10), count)\n//\n//\t\t}()\n//\t\tif i == 2 {\n//\t\t\tbreak\n//\t\t}\n//\n//\t}\n//\twg.Wait()\n//\tif err != nil {\n//\t\treturn pkgResp.ErrorResponseData(\"系统繁忙，请稍后再试~\"), errors.New(\"Business Error\")\n//\t}\n//\n//\t// 拼装结果\n//\tcontactDaoList := adapter.BuildContactDaoList(*contactList, userRList, msgRList, roomRList, roomFriendRList, roomGroupRList, countMap)\n//\n//\tpageResp.Data = contactDaoList\n//\treturn pkgResp.SuccessResponseData(pageResp), nil\n//}\n//\n//// FIXME: 合并GetNewContactListService和GetContactListService\n//func GetNewContactListService(uid int64, timestamp int64) (pkgResp.ResponseData, error) {\n//\tcontactTime := time.Unix(0, timestamp*1000*1000)\n//\n//\tctx := context.Background()\n//\tcontact := global.Query.Contact\n//\tcontactQ := contact.WithContext(ctx)\n//\tcontactRList, err := contactQ.Where(contact.UID.Eq(uid), contact.ActiveTime.Gte(contactTime)).Find()\n//\tif err != nil {\n//\t\tglobal.Logger.Errorf(\"查询会话列表失败 %s\", err)\n//\t\treturn pkgResp.ErrorResponseData(\"系统繁忙，请稍后再试~\"), errors.New(\"Business Error\")\n//\t}\n//\n//\t// 收集会话id\n//\tcontactRoomIdList := make([]int64, 0)\n//\tfor _, contact := range contactRList {\n//\t\tcontactRoomIdList = append(contactRoomIdList, contact.RoomID)\n//\t}\n//\n//\t// 查询出对应的房间信息\n//\troom := global.Query.Room\n//\troomQ := room.WithContext(ctx)\n//\t// 查询房间类型\n//\troomRList, err := roomQ.Where(room.ID.In(contactRoomIdList...)).Find()\n//\tif err != nil {\n//\t\tglobal.Logger.Errorf(\"查询房间失败 %s\", err)\n//\t\treturn pkgResp.ErrorResponseData(\"系统繁忙，请稍后再试~\"), errors.New(\"Business Error\")\n//\t}\n//\t// 收集单聊房间的id\n//\troomFriendIdList := make([]int64, 0)\n//\t// 收集群聊房间的id\n//\troomGroupIdList := make([]int64, 0)\n//\tfor _, room := range roomRList {\n//\t\tif room.Type == enum.PERSONAL {\n//\t\t\troomFriendIdList = append(roomFriendIdList, room.ID)\n//\t\t} else if room.Type == enum.GROUP {\n//\t\t\troomGroupIdList = append(roomGroupIdList, room.ID)\n//\t\t}\n//\t}\n//\n//\t// 查询好友房间信息\n//\troomFriend := global.Query.RoomFriend\n//\troomFriendQ := roomFriend.WithContext(ctx)\n//\troomFriendRList, err := roomFriendQ.Where(roomFriend.RoomID.In(roomFriendIdList...)).Find()\n//\tif err != nil {\n//\t\tglobal.Logger.Errorf(\"查询好友房间失败 %s\", err)\n//\t\treturn pkgResp.ErrorResponseData(\"系统繁忙，请稍后再试~\"), errors.New(\"Business Error\")\n//\t}\n//\t// 查询用户信息\n//\tuidList := make([]int64, 0)\n//\tfor _, roomFriend := range roomFriendRList {\n//\t\tif roomFriend.Uid1 == uid {\n//\t\t\tuidList = append(uidList, roomFriend.Uid2)\n//\t\t} else {\n//\t\t\tuidList = append(uidList, roomFriend.Uid1)\n//\t\t}\n//\t}\n//\tuser := global.Query.User\n//\tuserQ := user.WithContext(ctx)\n//\tuserRList, err := userQ.Where(user.ID.In(uidList...)).Find()\n//\tif err != nil {\n//\t\tglobal.Logger.Errorf(\"查询用户失败 %s\", err)\n//\t\treturn pkgResp.ErrorResponseData(\"系统繁忙，请稍后再试~\"), errors.New(\"Business Error\")\n//\t}\n//\n//\t// 查询群聊房间信息\n//\troomGroup := global.Query.RoomGroup\n//\troomGroupQ := roomGroup.WithContext(ctx)\n//\troomGroupRList, err := roomGroupQ.Where(roomGroup.RoomID.In(roomGroupIdList...)).Find()\n//\tif err != nil {\n//\t\tglobal.Logger.Errorf(\"查询群聊房间失败 %s\", err)\n//\t\treturn pkgResp.ErrorResponseData(\"系统繁忙，请稍后再试~\"), errors.New(\"Business Error\")\n//\t}\n//\n//\t// 查询最后一条消息\n//\tlastMsgIdList := make([]int64, 0)\n//\tfor _, contact := range contactRList {\n//\t\tlastMsgIdList = append(lastMsgIdList, contact.LastMsgID)\n//\t}\n//\tmsg := global.Query.Message\n//\tmsgQ := msg.WithContext(ctx)\n//\tmsgRList, err := msgQ.Where(msg.ID.In(lastMsgIdList...)).Find()\n//\tif err != nil {\n//\t\tglobal.Logger.Errorf(\"查询最后一条消息失败 %s\", err)\n//\t\treturn pkgResp.ErrorResponseData(\"系统繁忙，请稍后再试~\"), errors.New(\"Business Error\")\n//\t}\n//\n//\t// 查询未读消息数\n//\t//fixme: 有没有更好的方法\n//\tcountMap := cmap.New[int64]()\n//\tvar wg sync.WaitGroup\n//\terr = nil\n//\tfor _, contact := range contactRList {\n//\t\twg.Add(1)\n//\t\tgo func() {\n//\t\t\tdefer wg.Done()\n//\t\t\tvar count int64\n//\t\t\tcount, err = msgQ.Where(msg.RoomID.Eq(contact.RoomID), msg.DeleteStatus.Eq(pkgEnum.NORMAL), msg.CreateTime.Gt(contact.ReadTime)).Limit(99).Count()\n//\t\t\tif err != nil {\n//\t\t\t\tglobal.Logger.Errorf(\"统计未读数失败 %s\", err)\n//\t\t\t}\n//\t\t\tcountMap.Set(strconv.FormatInt(contact.RoomID, 10), count)\n//\t\t}()\n//\t}\n//\twg.Wait()\n//\tif err != nil {\n//\t\treturn pkgResp.ErrorResponseData(\"系统繁忙，请稍后再试~\"), errors.New(\"Business Error\")\n//\t}\n//\n//\t// 类型转换\n//\ttemp := make([]model.Contact, 0)\n//\tfor _, contact := range contactRList {\n//\t\ttemp = append(temp, *contact)\n//\t}\n//\n//\t// 拼装结果\n//\tcontactDaoList := adapter.BuildContactDaoList(temp, userRList, msgRList, roomRList, roomFriendRList, roomGroupRList, countMap)\n//\n//\treturn pkgResp.SuccessResponseData(contactDaoList), nil\n//}\n//\n//func GetContactDetailService(c *gin.Context) {\n//\tuid := c.GetInt64(\"uid\")\n//\tgetMessageListReq := req.GetMessageListReq{}\n//\tif err := c.ShouldBindQuery(&getMessageListReq); err != nil { //ShouldBind()会自动推导\n//\t\tresp.ErrorResponse(c, \"参数错误\")\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\troomId := getMessageListReq.RoomId\n//\tcursor, err := timestampToTime(getMessageListReq.Cursor)\n//\tif err != nil {\n//\t\tglobal.Logger.Errorf(\"时间戳转换失败 %s\", err)\n//\t\tresp.ErrorResponse(c, \"系统正忙，请稍后再试\")\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\tpageRequest := pkgReq.PageReq{\n//\t\tCursor:   cursor,\n//\t\tPageSize: getMessageListReq.PageSize,\n//\t}\n//\t// 更新会话表\n//\tcontact := global.Query.Contact\n//\tcontactQ := contact.WithContext(context.Background())\n//\t_, err = contactQ.Where(contact.UID.Eq(uid), contact.RoomID.Eq(roomId)).Update(contact.ReadTime, time.Now())\n//\tif err != nil {\n//\t\tglobal.Logger.Errorf(\"更新会话失败 %s\", err)\n//\t\tresp.ErrorResponse(c, \"系统正忙，请稍后再试\")\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\n//\t// 获取会话详情\n//\tpageResp, err := GetContactDetail(roomId, pageRequest)\n//\tif err != nil {\n//\t\tglobal.Logger.Errorf(\"查询会话详情失败 %s\", err)\n//\t\tresp.ErrorResponse(c, \"系统正忙，请稍后再试\")\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\tresp.SuccessResponse(c, pageResp)\n//\treturn\n//}\n//\n//func GetContactDetail(roomID int64, pageRequest pkgReq.PageReq) (*pkgResp.PageResp, error) {\n//\t// 查询消息\n//\tdb := dal.DB\n//\tmsgs := make([]model.Message, 0)\n//\tcondition := []interface{}{\"room_id=? AND delete_status=?\", strconv.FormatInt(roomID, 10), pkgEnum.NORMAL}\n//\tpageResp, err := utils.Paginate(db, pageRequest, &msgs, \"create_time\", false, condition...)\n//\tif err != nil {\n//\t\tglobal.Logger.Errorf(\"查询消息失败: %s\", err.Error())\n//\t\treturn nil, err\n//\t}\n//\tmsgList := *(pageResp.Data.(*[]model.Message))\n//\tfor i, j := 0, len(msgList)-1; i < j; i, j = i+1, j-1 {\n//\t\tmsgList[i], msgList[j] = msgList[j], msgList[i]\n//\t}\n//\tuserIdMap := make(map[int64]*int64)\n//\tfor _, msg := range msgList {\n//\t\tif userIdMap[msg.FromUID] == nil {\n//\t\t\tuserIdMap[msg.FromUID] = &msg.FromUID\n//\t\t}\n//\t}\n//\t// 转换成列表\n//\tuserIdList := make([]int64, 0)\n//\tfor _, uid := range userIdMap {\n//\t\tuserIdList = append(userIdList, *uid)\n//\t}\n//\t// 查询用户信息\n//\tctx := context.Background()\n//\tuser := global.Query.User\n//\tuserQ := user.WithContext(ctx)\n//\tusers, err := userQ.Where(user.ID.In(userIdList...)).Find()\n//\tif err != nil {\n//\t\tglobal.Logger.Errorf(\"查询用户失败: %s\", err.Error())\n//\t\treturn nil, err\n//\t}\n//\tuserMap := make(map[int64]*model.User)\n//\tfor _, user := range users {\n//\t\tuserMap[user.ID] = user\n//\t}\n//\n//\t// 拼装结果\n//\tpageResp.Data = adapter.BuildMessageRespByMsgAndUser(&msgList, userMap)\n//\treturn pageResp, nil\n//}\n//func GetNewMsgService(msgId int64, roomId int64) (pkgResp.ResponseData, error) {\n//\tctx := context.Background()\n//\t// 查询消息\n//\tmsg := global.Query.Message\n//\tmsgQ := msg.WithContext(ctx)\n//\tmsgRList, err := msgQ.Where(msg.ID.Gt(msgId), msg.RoomID.Eq(roomId)).Find()\n//\tif err != nil {\n//\t\tglobal.Logger.Errorf(\"查询消息失败: %s\", err.Error())\n//\t\treturn pkgResp.ErrorResponseData(\"接收消息失败\"), err\n//\t}\n//\n//\tuserIdMap := make(map[int64]*int64)\n//\tfor _, msg := range msgRList {\n//\t\tif userIdMap[msg.FromUID] == nil {\n//\t\t\tuserIdMap[msg.FromUID] = &msg.FromUID\n//\t\t}\n//\t}\n//\n//\t// 转换成列表\n//\tuserIdList := make([]int64, 0)\n//\tfor _, uid := range userIdMap {\n//\t\tuserIdList = append(userIdList, *uid)\n//\t}\n//\t// 查询用户信息\n//\tuser := global.Query.User\n//\tuserQ := user.WithContext(ctx)\n//\tusers, err := userQ.Where(user.ID.In(userIdList...)).Find()\n//\tif err != nil {\n//\t\tglobal.Logger.Errorf(\"查询用户失败: %s\", err.Error())\n//\t\treturn pkgResp.ErrorResponseData(\"接收消息失败\"), err\n//\t}\n//\tuserMap := make(map[int64]*model.User)\n//\tfor _, user := range users {\n//\t\tuserMap[user.ID] = user\n//\t}\n//\n//\ttemp := make([]model.Message, 0)\n//\tfor _, msg := range msgRList {\n//\t\ttemp = append(temp, *msg)\n//\t}\n//\n//\t// 拼装结果\n//\tdata := adapter.BuildMessageRespByMsgAndUser(&temp, userMap)\n//\treturn pkgResp.SuccessResponseData(data), nil\n//}\n//\n//func timestampToTime(timestampStr *string) (*string, error) {\n//\tif timestampStr != nil && *timestampStr != \"\" && *timestampStr != \"null\" {\n//\t\t// 时间戳转时间\n//\t\ttimestamp, err := strconv.ParseInt(*timestampStr, 10, 64)\n//\t\tif err != nil {\n//\t\t\tglobal.Logger.Errorf(\"时间戳转换失败 %s\", err)\n//\t\t\treturn nil, err\n//\t\t}\n//\t\tcursor := time.Unix(0, timestamp)\n//\t\tcursorStr := cursor.Format(time.RFC3339Nano)\n//\t\treturn &cursorStr, nil\n//\t}\n//\treturn nil, nil\n//}\n//\n//func GetUserInfoBatchService(reqList req.GetUserInfoBatchReq) (pkgResp.ResponseData, error) {\n//\tctx := context.Background()\n//\tuser := global.Query.User\n//\tuserQ := user.WithContext(ctx)\n//\tuids := make([]int64, 0)\n//\n//\tuserMap := make(map[int64]*req.UserInfoBatchReqItem)\n//\tfor _, item := range reqList.List {\n//\t\tuids = append(uids, item.Uid)\n//\t\tuserMap[item.Uid] = &item\n//\t}\n//\tusers, err := userQ.Where(user.ID.In(uids...)).Find()\n//\tif err != nil {\n//\t\tglobal.Logger.Errorf(\"查询用户失败 %s\", err)\n//\t\treturn pkgResp.ErrorResponseData(\"系统繁忙，请稍后再试~\"), errors.New(\"Business Error\")\n//\t}\n//\n//\tresultList := make([]domainResp.GetUserInfoBatchResp, 0)\n//\tfor _, user := range users {\n//\t\tresultItem := domainResp.GetUserInfoBatchResp{\n//\t\t\tUid: user.ID,\n//\t\t}\n//\t\tif user.UpdateTime.UnixMilli() > userMap[user.ID].LastModifyTime {\n//\t\t\tresultItem.Username = user.Name\n//\t\t\tresultItem.Avatar = user.Avatar\n//\t\t\tresultItem.NeedRefresh = true\n//\t\t} else {\n//\t\t\tresultItem.NeedRefresh = false\n//\t\t}\n//\t\tresultList = append(resultList, resultItem)\n//\t}\n//\n//\treturn pkgResp.SuccessResponseData(resultList), nil\n//\n//}\n"
  },
  {
    "path": "service/friend_service.go",
    "content": "package service\n\n//\n//import (\n//\t\"DiTing-Go/dal\"\n//\t\"DiTing-Go/dal/model\"\n//\t\"DiTing-Go/domain/dto\"\n//\tdomainEnum \"DiTing-Go/domain/enum\"\n//\t\"DiTing-Go/domain/vo/req\"\n//\tdomainResp \"DiTing-Go/domain/vo/resp\"\n//\t\"DiTing-Go/global\"\n//\t\"DiTing-Go/pkg/domain/enum\"\n//\tpkgReq \"DiTing-Go/pkg/domain/vo/req\"\n//\t\"DiTing-Go/pkg/domain/vo/resp\"\n//\t\"DiTing-Go/pkg/utils\"\n//\t\"DiTing-Go/utils/jsonUtils\"\n//\t\"DiTing-Go/utils/redisCache\"\n//\t\"context\"\n//\t\"fmt\"\n//\t\"github.com/pkg/errors\"\n//\t\"gorm.io/gen\"\n//\t\"gorm.io/gorm\"\n//\t\"sort\"\n//\t\"strconv\"\n//\t\"time\"\n//)\n//\n//// ApplyFriendService 添加好友\n//func ApplyFriendService(uid int64, applyReq req.UserApplyReq) (resp.ResponseData, error) {\n//\tctx := context.Background()\n//\tfriendUid := applyReq.Uid\n//\tuser := global.Query.User\n//\tuserQ := user.WithContext(ctx)\n//\n//\tuids := utils.Int64Slice{uid, friendUid}\n//\tsort.Sort(uids)\n//\tkey := fmt.Sprintf(domainEnum.UserAndFriendLock, uids[0], uids[1])\n//\tmutex, err := utils.GetLock(key)\n//\tif err != nil {\n//\t\treturn resp.ErrorResponseData(\"系统正忙，请稍后再试\"), err\n//\t}\n//\tdefer utils.ReleaseLock(mutex)\n//\n//\t//检查用户是否存在\n//\tfun := func() (interface{}, error) {\n//\t\treturn userQ.Where(user.ID.Eq(friendUid)).First()\n//\t}\n//\tuserR := model.User{}\n//\tkey = fmt.Sprintf(domainEnum.UserCacheByID, applyReq.Uid)\n//\terr = utils.GetData(key, &userR, fun)\n//\tif err != nil {\n//\t\tif errors.Is(err, gorm.ErrRecordNotFound) {\n//\t\t\treturn resp.ErrorResponseData(\"用户不存在\"), errors.New(\"Business Error\")\n//\t\t}\n//\t\tglobal.Logger.Errorf(\"查询用户失败 %s\", err)\n//\t\treturn resp.ErrorResponseData(\"系统正忙，请稍后再试\"), errors.New(\"Business Error\")\n//\t}\n//\n//\t// 检查是否已经是好友关系\n//\tisFriend, err := IsFriend(uid, friendUid)\n//\tif err != nil {\n//\t\tglobal.Logger.Errorf(\"查询好友失败 %s\", err)\n//\t\treturn resp.ErrorResponseData(\"系统正忙，请稍后再试\"), errors.New(\"Business Error\")\n//\t}\n//\t// 已经是好友\n//\tif isFriend {\n//\t\treturn resp.ErrorResponseData(\"已经是好友\"), errors.New(\"Business Error\")\n//\t}\n//\t// 检查是否已经发送过好友请求\n//\tuserApply := global.Query.UserApply\n//\tuserApplyQ := userApply.WithContext(ctx)\n//\tuserApplyR := model.UserApply{}\n//\tfun = func() (interface{}, error) {\n//\t\treturn userApplyQ.Where(userApply.UID.Eq(uid), userApply.TargetID.Eq(friendUid)).First()\n//\t}\n//\tkey = fmt.Sprintf(domainEnum.UserApplyCacheByUidAndFriendUid, uid, friendUid)\n//\terr = utils.GetData(key, &userApplyR, fun)\n//\t// 查到了\n//\tif err == nil {\n//\t\treturn resp.ErrorResponseData(\"已发送过好友请求，请等待对方同意\"), errors.New(\"Business Error\")\n//\t}\n//\tif !errors.Is(err, gorm.ErrRecordNotFound) {\n//\t\tglobal.Logger.Errorf(\"查询好友请求失败 %s\", err)\n//\t\treturn resp.ErrorResponseData(\"系统正忙，请稍后再试\"), errors.New(\"Business Error\")\n//\t}\n//\n//\t// 检查对方是否给我们发送过好友请求，如果是，直接同意\n//\tfun = func() (interface{}, error) {\n//\t\treturn userApplyQ.Where(userApply.UID.Eq(friendUid), userApply.TargetID.Eq(uid)).First()\n//\t}\n//\tkey = fmt.Sprintf(domainEnum.UserApplyCacheByUidAndFriendUid, friendUid, uid)\n//\terr = utils.GetData(key, &userApplyR, fun)\n//\t// 查到了\n//\tif err == nil {\n//\t\terr := AgreeFriend(uid, friendUid)\n//\t\tif err != nil {\n//\t\t\tglobal.Logger.Errorf(\"同意好友请求失败 %s\", err)\n//\t\t\treturn resp.ErrorResponseData(\"系统正忙，请稍后再试\"), errors.New(\"Business Error\")\n//\t\t}\n//\n//\t\treturn resp.SuccessResponseData(nil), nil\n//\t}\n//\tif !errors.Is(err, gorm.ErrRecordNotFound) {\n//\t\tglobal.Logger.Errorf(\"查询好友请求失败 %s\", err)\n//\t\treturn resp.ErrorResponseData(\"系统正忙，请稍后再试\"), errors.New(\"Business Error\")\n//\t}\n//\n//\t// 发送好友请求\n//\terr = userApplyQ.Create(&model.UserApply{\n//\t\tUID:        uid,\n//\t\tTargetID:   friendUid,\n//\t\tMsg:        applyReq.Msg,\n//\t\tStatus:     enum.NO,\n//\t\tReadStatus: enum.NO,\n//\t})\n//\tif err != nil {\n//\t\tglobal.Logger.Errorf(\"插入好友请求失败 %s\", err)\n//\t\treturn resp.ErrorResponseData(\"系统正忙，请稍后再试\"), errors.New(\"Business Error\")\n//\t}\n//\t// 发送好友申请事件\n//\terr = jsonUtils.SendMsgSync(domainEnum.FriendApplyTopic, model.UserApply{\n//\t\tUID:        uid,\n//\t\tTargetID:   friendUid,\n//\t\tMsg:        applyReq.Msg,\n//\t\tStatus:     enum.NO,\n//\t\tReadStatus: enum.NO,\n//\t})\n//\tif err != nil {\n//\t\treturn resp.ErrorResponseData(\"系统正忙，请稍后再试\"), errors.New(\"Business Error\")\n//\t}\n//\t//time.Sleep(30 * time.Second)\n//\treturn resp.SuccessResponseData(nil), nil\n//}\n//\n//// IsFriendService 判断是否是好友\n//func IsFriendService(uid, friendUid int64) (resp.ResponseData, error) {\n//\tisFriend, err := IsFriend(uid, friendUid)\n//\tif err != nil {\n//\t\tglobal.Logger.Errorf(\"判断好友关系失败 %s\", err)\n//\t\treturn resp.ErrorResponseData(\"系统正忙，请稍后再试\"), errors.New(\"Business Error\")\n//\t}\n//\treturn resp.SuccessResponseData(isFriend), nil\n//}\n//\n//// IsFriend 判断是否是好友\n//func IsFriend(uid, friendUid int64) (bool, error) {\n//\tctx := context.Background()\n//\tuserFriend := global.Query.UserFriend\n//\tuserFriendQ := userFriend.WithContext(ctx)\n//\t// 检查是否已经是好友关系\n//\tuserFriendR := model.UserFriend{}\n//\tfun := func() (interface{}, error) {\n//\t\treturn userFriendQ.Where(userFriend.UID.Eq(uid), userFriend.FriendUID.Eq(friendUid), userFriend.DeleteStatus.Eq(enum.NORMAL)).First()\n//\t}\n//\tkey := fmt.Sprintf(domainEnum.UserFriendCacheByUidAndFriendUid, uid, friendUid)\n//\terr := utils.GetData(key, &userFriendR, fun)\n//\tif err != nil {\n//\t\t// 没查到，不是好友\n//\t\tif errors.Is(err, gorm.ErrRecordNotFound) {\n//\t\t\treturn false, nil\n//\t\t}\n//\t\tglobal.Logger.Errorf(\"查询好友失败 %s\", err)\n//\t\treturn false, err\n//\t}\n//\n//\treturn true, nil\n//}\n//\n//func AgreeFriendService(uid, friendUid int64) (resp.ResponseData, error) {\n//\n//\tuids := utils.Int64Slice{uid, friendUid}\n//\tsort.Sort(uids)\n//\tkey := fmt.Sprintf(domainEnum.UserAndFriendLock, uids[0], uids[1])\n//\tmutex, err := utils.GetLock(key)\n//\tif err != nil {\n//\t\treturn resp.ErrorResponseData(\"系统正忙，请稍后再试\"), err\n//\t}\n//\tdefer utils.ReleaseLock(mutex)\n//\n//\terr = AgreeFriend(uid, friendUid)\n//\tif err != nil {\n//\t\tglobal.Logger.Errorf(\"同意好友请求失败 %s\", err)\n//\t\treturn resp.ErrorResponseData(\"系统正忙，请稍后再试\"), errors.New(\"Business Error\")\n//\t}\n//\treturn resp.SuccessResponseData(nil), nil\n//}\n//\n//// AgreeFriend 同意好友请求\n//func AgreeFriend(uid, friendUid int64) error {\n//\tctx := context.Background()\n//\tuserApply := global.Query.UserApply\n//\tuserApplyQ := userApply.WithContext(ctx)\n//\n//\t// 检查是否存在好友申请且状态为待审批\n//\tfun := func() (interface{}, error) {\n//\t\treturn userApplyQ.Where(userApply.UID.Eq(friendUid), userApply.TargetID.Eq(uid)).First()\n//\t}\n//\tuserApplyR := model.UserApply{}\n//\tkey := fmt.Sprintf(domainEnum.UserApplyCacheByUidAndFriendUid, friendUid, uid)\n//\terr := utils.GetData(key, &userApplyR, fun)\n//\tif err != nil {\n//\t\treturn err\n//\t}\n//\t// 好友申请状态不是待审批\n//\tif userApplyR.Status != enum.NO {\n//\t\treturn errors.New(\"error status\")\n//\t}\n//\t// 同意好友请求\n//\tuserApplyR.Status = enum.YES\n//\t// 事务\n//\ttx := q.Begin()\n//\tuserApplyTx := tx.UserApply.WithContext(context.Background())\n//\tuserFriendTx := tx.UserFriend.WithContext(context.Background())\n//\tif _, err = userApplyTx.Where(userApply.UID.Eq(friendUid), userApply.TargetID.Eq(uid)).Updates(userApplyR); err != nil {\n//\t\tif err := tx.Rollback(); err != nil {\n//\t\t\tglobal.Logger.Errorf(\"事务回滚失败 %s\", err.Error())\n//\t\t}\n//\t\treturn err\n//\t}\n//\tdefer utils.RemoveData(key)\n//\n//\tvar userFriends = []*model.UserFriend{\n//\t\t{\n//\t\t\tUID:          uid,\n//\t\t\tFriendUID:    friendUid,\n//\t\t\tDeleteStatus: enum.NORMAL,\n//\t\t},\n//\t\t{\n//\t\t\tUID:          friendUid,\n//\t\t\tFriendUID:    uid,\n//\t\t\tDeleteStatus: enum.NORMAL,\n//\t\t},\n//\t}\n//\t// 检查是否存在软删除状态的好友关系\n//\tuserFriend := global.Query.UserFriend\n//\tfun = func() (interface{}, error) {\n//\t\treturn userFriendTx.Where(userFriend.UID.Eq(uid), userFriend.FriendUID.Eq(friendUid), userFriend.DeleteStatus.Eq(enum.DELETED)).First()\n//\t}\n//\tuserFriendR := model.UserFriend{}\n//\tkey = fmt.Sprintf(domainEnum.UserFriendCacheByUidAndFriendUid, uid, friendUid)\n//\terr = utils.GetData(key, &userFriendR, fun)\n//\t// err\n//\tif err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {\n//\t\tglobal.Logger.Errorf(\"更新好友关系失败 %s\", err.Error())\n//\t\treturn err\n//\t}\n//\t// 查到了,更新状态\n//\tif err == nil {\n//\t\t_, err := userFriendTx.Where(userFriend.UID.Eq(uid), userFriend.FriendUID.Eq(friendUid)).Update(userFriend.DeleteStatus, enum.NORMAL)\n//\t\t_, err = userFriendTx.Where(userFriend.UID.Eq(friendUid), userFriend.FriendUID.Eq(uid)).Update(userFriend.DeleteStatus, enum.NORMAL)\n//\t\tif err != nil {\n//\t\t\tif err := tx.Rollback(); err != nil {\n//\t\t\t\tglobal.Logger.Errorf(\"事务回滚失败 %s\", err.Error())\n//\t\t\t}\n//\t\t\tglobal.Logger.Errorf(\"更新好友关系失败 %s\", err.Error())\n//\t\t\treturn err\n//\t\t}\n//\t\t// 删除redis缓存\n//\t\tkey = fmt.Sprintf(domainEnum.UserFriendCacheByUidAndFriendUid, uid, friendUid)\n//\t\tdefer utils.RemoveData(key)\n//\t\tkey = fmt.Sprintf(domainEnum.UserFriendCacheByUidAndFriendUid, friendUid, uid)\n//\t\tdefer utils.RemoveData(key)\n//\t} else {\n//\t\t// 没查到，创建新的好友关系\n//\t\tif err = userFriendTx.Create(userFriends...); err != nil {\n//\t\t\tif err := tx.Rollback(); err != nil {\n//\t\t\t\tglobal.Logger.Errorf(\"事务回滚失败 %s\", err)\n//\t\t\t}\n//\t\t\treturn err\n//\t\t}\n//\t}\n//\tif err := tx.Commit(); err != nil {\n//\t\treturn err\n//\t}\n//\n//\t// 发送新好友事件\n//\terr = jsonUtils.SendMsgSync(domainEnum.NewFriendTopic, userFriends[0])\n//\tif err != nil {\n//\t\tif err := tx.Rollback(); err != nil {\n//\t\t\tglobal.Logger.Errorf(\"事务回滚失败 %s\", err.Error())\n//\t\t}\n//\t\tglobal.Logger.Errorf(\"发送新好友事件失败 %s\", err.Error())\n//\t\treturn err\n//\t}\n//\treturn nil\n//}\n//\n//// DeleteFriendService 删除好友\n//// 只删除好友关系和会话,其他耗时操作异步处理\n//func DeleteFriendService(uid int64, deleteFriendReq req.DeleteFriendReq) (resp.ResponseData, error) {\n//\tctx := context.Background()\n//\n//\tdeleteFriendUid := deleteFriendReq.Uid\n//\n//\tuids := utils.Int64Slice{uid, deleteFriendUid}\n//\tsort.Sort(uids)\n//\tkey := fmt.Sprintf(domainEnum.UserAndFriendLock, uids[0], uids[1])\n//\tmutex, err := utils.GetLock(key)\n//\tif err != nil {\n//\t\treturn resp.ErrorResponseData(\"系统正忙，请稍后再试\"), err\n//\t}\n//\tdefer utils.ReleaseLock(mutex)\n//\n//\t// 判断是否为好友\n//\tisFriend, err := IsFriend(uid, deleteFriendUid)\n//\tif err != nil {\n//\t\tglobal.Logger.Errorf(\"查询好友关系失败 %s\", err)\n//\t\treturn resp.ErrorResponseData(\"系统正忙，请稍后再试\"), errors.New(\"Business Error\")\n//\t}\n//\tif !isFriend {\n//\t\treturn resp.ErrorResponseData(\"删除好友失败\"), errors.New(\"Business Error\")\n//\t}\n//\n//\ttx := global.Query.Begin()\n//\t// 事务\n//\t// 软删除好友关系\n//\tuserFriend := global.Query.UserFriend\n//\tuserFriendTx := tx.UserFriend.WithContext(ctx)\n//\tif _, err := userFriendTx.Where(userFriend.UID.Eq(uid), userFriend.FriendUID.Eq(deleteFriendUid)).Update(userFriend.DeleteStatus, enum.DELETED); err != nil {\n//\t\tif err := tx.Rollback(); err != nil {\n//\t\t\tglobal.Logger.Errorf(\"事务回滚失败 %s\", err.Error())\n//\t\t}\n//\t\tglobal.Logger.Errorf(\"删除好友失败 %s\", err.Error())\n//\t\treturn resp.ErrorResponseData(\"删除好友失败\"), errors.New(\"Business Error\")\n//\t}\n//\t// 删除redis缓存\n//\tdefer redisCache.RemoveUserFriend(uid, deleteFriendUid)\n//\n//\tif _, err := userFriendTx.Where(userFriend.UID.Eq(deleteFriendUid), userFriend.FriendUID.Eq(uid)).Update(userFriend.DeleteStatus, enum.DELETED); err != nil {\n//\t\tif err := tx.Rollback(); err != nil {\n//\t\t\tglobal.Logger.Errorf(\"事务回滚失败 %s\", err.Error())\n//\t\t}\n//\t\tglobal.Logger.Errorf(\"删除好友失败 %s\", err.Error())\n//\t\treturn resp.ErrorResponseData(\"删除好友失败\"), errors.New(\"Business Error\")\n//\t}\n//\t// 删除redis缓存\n//\tdefer redisCache.RemoveUserFriend(deleteFriendUid, uid)\n//\n//\t// 删除会话\n//\troomFriend := global.Query.RoomFriend\n//\troomFriendTx := tx.RoomFriend.WithContext(ctx)\n//\tuids = utils.Int64Slice{uid, deleteFriendUid}\n//\tsort.Sort(uids)\n//\tfun := func() (interface{}, error) {\n//\t\treturn roomFriendTx.Where(roomFriend.Uid1.Eq(uids[0]), roomFriend.Uid2.Eq(uids[1])).First()\n//\t}\n//\troomFriendR := model.RoomFriend{}\n//\tkey = fmt.Sprintf(domainEnum.RoomFriendCacheByUidAndFriendUid, uids[0], uids[1])\n//\tif err := utils.GetData(key, &roomFriendR, fun); err != nil {\n//\t\tif err := tx.Rollback(); err != nil {\n//\t\t\tglobal.Logger.Errorf(\"事务回滚失败 %s\", err.Error())\n//\t\t}\n//\t\tglobal.Logger.Errorf(\"查询好友房间失败 %s\", err.Error())\n//\t\treturn resp.ErrorResponseData(\"删除好友失败\"), errors.New(\"Business Error\")\n//\t}\n//\n//\tcontact := global.Query.Contact\n//\tcontactTx := tx.Contact.WithContext(ctx)\n//\tresultInfo, err := contactTx.Where(contact.RoomID.Eq(roomFriendR.RoomID)).Delete()\n//\tif err != nil {\n//\t\tif err := tx.Rollback(); err != nil {\n//\t\t\tglobal.Logger.Errorf(\"事务回滚失败 %s\", err.Error())\n//\t\t}\n//\t\tglobal.Logger.Errorf(\"删除会话失败 %s\", err.Error())\n//\t\treturn resp.ErrorResponseData(\"删除好友失败\"), errors.New(\"Business Error\")\n//\t}\n//\tif resultInfo.RowsAffected == 0 {\n//\t\tif err := tx.Rollback(); err != nil {\n//\t\t\tglobal.Logger.Errorf(\"事务回滚失败 %s\", err.Error())\n//\t\t}\n//\t\tglobal.Logger.Errorf(\"会话不存在 %d\", roomFriendR.RoomID)\n//\t\treturn resp.ErrorResponseData(\"删除好友失败\"), errors.New(\"Business Error\")\n//\t}\n//\t// TODO:删除缓存\n//\n//\t// 发送消息\n//\tDeleteFriendDto := dto.DeleteFriendDto{\n//\t\tUid:       uid,\n//\t\tFriendUid: deleteFriendUid,\n//\t}\n//\terr = jsonUtils.SendMsgSync(domainEnum.DeleteFriendTopic, DeleteFriendDto)\n//\tif err != nil {\n//\t\tif err := tx.Rollback(); err != nil {\n//\t\t\tglobal.Logger.Errorf(\"事务回滚失败 %s\", err.Error())\n//\t\t}\n//\t\tglobal.Logger.Errorf(\"发送删除好友事件失败 %s\", err.Error())\n//\t\treturn resp.ErrorResponseData(\"系统正忙，请稍后再试\"), errors.New(\"Business Error\")\n//\t}\n//\n//\tif err := tx.Commit(); err != nil {\n//\t\tglobal.Logger.Errorf(\"事务提交失败 %s\", err.Error())\n//\t\treturn resp.ErrorResponseData(\"删除好友失败\"), errors.New(\"Business Error\")\n//\t}\n//\n//\treturn resp.SuccessResponseData(nil), nil\n//}\n//\n//// GetUserApplyService 获取好友申请列表\n//func GetUserApplyService(uid int64, pageReq pkgReq.PageReq) (resp.ResponseData, error) {\n//\tif pageReq.Cursor != nil && *pageReq.Cursor != \"\" {\n//\t\t// 时间戳转时间\n//\t\ttimestamp, err := strconv.ParseInt(*pageReq.Cursor, 10, 64)\n//\t\tif err != nil {\n//\t\t\tglobal.Logger.Errorf(\"时间戳转换失败 %s\", err)\n//\t\t\treturn resp.ErrorResponseData(\"系统繁忙，请稍后再试~\"), errors.New(\"Business Error\")\n//\t\t}\n//\t\tcursor := time.Unix(0, timestamp)\n//\t\tcursorStr := cursor.Format(time.RFC3339Nano)\n//\t\tpageReq.Cursor = &cursorStr\n//\t}\n//\n//\tctx := context.Background()\n//\t// 获取 UserApply 表中 TargetID 等于 uid(登录用户ID)的用户ID集合，采用游标分页\n//\tdb := dal.DB\n//\tuserApplys := make([]model.UserApply, 0)\n//\tcondition := []interface{}{\"target_id=?\", strconv.FormatInt(uid, 10)}\n//\n//\tpageResp, err := utils.Paginate(db, pageReq, &userApplys, \"create_time\", false, condition...)\n//\tif err != nil {\n//\t\tglobal.Logger.Errorf(\"查询好友申请表失败 %s\", err)\n//\t\treturn resp.ErrorResponseData(\"系统正忙，请稍后再试\"), errors.New(\"Business Error\")\n//\t}\n//\n//\tuids := make([]int64, 0)\n//\tn := len(userApplys)\n//\tfor i := 0; i < n; i++ {\n//\t\tuids = append(uids, userApplys[i].UID)\n//\t}\n//\n//\tuser := global.Query.User\n//\t// 根据 uids 集合查询 User 表\n//\tusers, err := user.WithContext(ctx).Select(user.ID, user.Name, user.Avatar).Where(user.ID.In(uids...)).Find()\n//\tif err != nil {\n//\t\tglobal.Logger.Errorf(\"查询用户表失败 %s\", err)\n//\t\treturn resp.ErrorResponseData(\"系统正忙，请稍后再试\"), errors.New(\"Business Error\")\n//\t}\n//\n//\tif len(users) != len(userApplys) {\n//\t\tglobal.Logger.Errorf(\"用户表和好友申请表数据不匹配\")\n//\t\treturn resp.ErrorResponseData(\"系统正忙，请稍后再试\"), errors.New(\"Business Error\")\n//\t}\n//\tvar usersVO = make([]domainResp.UserApplyResp, 0)\n//\t// 数据转换\n//\tfor i := 0; i < len(users); i++ {\n//\t\tvar userVO domainResp.UserApplyResp\n//\t\tuserVO.ApplyId = userApplys[i].ID\n//\t\tuserVO.Uid = userApplys[i].UID\n//\t\tuserVO.Msg = userApplys[i].Msg\n//\t\tuserVO.Status = userApplys[i].Status\n//\t\tusersVO = append(usersVO, userVO)\n//\t}\n//\tpageResp.Data = usersVO\n//\n//\tuserApply := global.Query.UserApply\n//\tuserApplyQ := global.Query.UserApply.WithContext(ctx)\n//\t// 更新已读状态\n//\t_, err = userApplyQ.Where(userApply.TargetID.Eq(uid), userApply.ReadStatus.Eq(enum.NO)).Update(userApply.ReadStatus, enum.YES)\n//\tif err != nil {\n//\t\tglobal.Logger.Errorf(\"更新好友申请表失败 %s\", err)\n//\t\treturn resp.ErrorResponseData(\"系统正忙，请稍后再试\"), errors.New(\"Business Error\")\n//\t}\n//\n//\treturn resp.SuccessResponseData(pageResp), nil\n//}\n//\n//func UnreadApplyNumService(uid int64) (resp.ResponseData, error) {\n//\tctx := context.Background()\n//\tuserApply := global.Query.UserApply\n//\tuserApplyQ := userApply.WithContext(ctx)\n//\n//\t// TODO 直接count\n//\t// 获取 UserApply 表中 TargetID 等于 uid(登录用户ID)的用户ID集合\n//\tsubQuery := userApplyQ.Where(userApply.TargetID.Eq(uid), userApply.ReadStatus.Eq(enum.NO)).Limit(99)\n//\tnum, err := gen.Table(subQuery.As(\"t\")).Count()\n//\tif err != nil {\n//\t\tglobal.Logger.Errorf(\"查询好友申请表失败 %s\", err)\n//\t\treturn resp.ErrorResponseData(\"系统正忙，请稍后再试\"), errors.New(\"Business Error\")\n//\t}\n//\treturn resp.SuccessResponseData(num), nil\n//}\n//\n//// GetFriendListService 获取好友列表\n//func GetFriendListService(uid int64, pageReq pkgReq.PageReq) (resp.ResponseData, error) {\n//\tif pageReq.Cursor != nil && *pageReq.Cursor != \"\" {\n//\t\t// 时间戳转时间\n//\t\ttimestamp, err := strconv.ParseInt(*pageReq.Cursor, 10, 64)\n//\t\tif err != nil {\n//\t\t\tglobal.Logger.Errorf(\"时间戳转换失败 %s\", err)\n//\t\t\treturn resp.ErrorResponseData(\"系统繁忙，请稍后再试~\"), errors.New(\"Business Error\")\n//\t\t}\n//\t\tcursor := time.Unix(0, timestamp)\n//\t\tcursorStr := cursor.Format(time.RFC3339Nano)\n//\t\tpageReq.Cursor = &cursorStr\n//\t}\n//\n//\tctx := context.Background()\n//\t// 获取 UserFriend 表中 uid = uid 的好友的uid组成的集合\n//\tdb := dal.DB\n//\tuserFriend := make([]model.UserFriend, 0)\n//\tcondition := []interface{}{\"uid=? and delete_status=?\", strconv.FormatInt(uid, 10), enum.NORMAL}\n//\tpageResp, err := utils.Paginate(db, pageReq, &userFriend, \"create_time\", false, condition...)\n//\tif err != nil {\n//\t\tglobal.Logger.Errorf(\"分页查询失败 %v\", err)\n//\t\treturn resp.ErrorResponseData(\"系统繁忙，请稍后再试\"), errors.New(\"Business Error\")\n//\t}\n//\tuids := make([]int64, 0)\n//\n//\tfor _, friend := range userFriend {\n//\t\tuids = append(uids, friend.FriendUID)\n//\t}\n//\n//\t// 获取好友信息\n//\tusers := global.Query.User\n//\t// select id , name , avatar from user where id in (...) and status = 0\n//\tfriendList, err := users.WithContext(ctx).Select(users.ID, users.ActiveStatus, users.LastOptTime).Where(users.ID.In(uids...)).Find()\n//\tif err != nil {\n//\t\tglobal.Logger.Errorf(\"查询用户表失败 %s\", err)\n//\t\treturn resp.ErrorResponseData(\"系统繁忙，请稍后再试\"), errors.New(\"Business Error\")\n//\t}\n//\n//\t// 数据转换\n//\tfriendListVO := make([]domainResp.UserContactResp, 0)\n//\tfor _, friend := range friendList {\n//\t\tfriendResp := domainResp.UserContactResp{\n//\t\t\tUid:          friend.ID,\n//\t\t\tLastOptTime:  friend.LastOptTime.UnixMilli(),\n//\t\t\tActiveStatus: int(friend.ActiveStatus),\n//\t\t}\n//\t\tfriendListVO = append(friendListVO, friendResp)\n//\t}\n//\tpageResp.Data = friendListVO\n//\treturn resp.SuccessResponseData(pageResp), nil\n//}\n"
  },
  {
    "path": "service/group_service.go",
    "content": "package service\n\n//\n//import (\n//\t\"DiTing-Go/dal/model\"\n//\t\"DiTing-Go/domain/dto\"\n//\t\"DiTing-Go/domain/enum\"\n//\t\"DiTing-Go/domain/vo/req\"\n//\t\"DiTing-Go/global\"\n//\tpkgEnum \"DiTing-Go/pkg/domain/enum\"\n//\t\"DiTing-Go/pkg/domain/vo/resp\"\n//\tpkgResp \"DiTing-Go/pkg/domain/vo/resp\"\n//\t\"context\"\n//\t\"fmt\"\n//\t\"github.com/gin-gonic/gin\"\n//\t\"github.com/pkg/errors\"\n//\t\"gorm.io/gorm\"\n//\t\"strconv\"\n//\t\"strings\"\n//\t\"time\"\n//)\n//\n//func CreateGroupService(uid int64, uidList []int64) (pkgResp.ResponseData, error) {\n//\n//\ttx := global.Query.Begin()\n//\tctx := context.Background()\n//\t// 创建房间表\n//\troomTx := tx.Room.WithContext(ctx)\n//\tnewRoom := model.Room{\n//\t\tType:    enum.GROUP,\n//\t\tExtJSON: \"{}\",\n//\t}\n//\tif err := roomTx.Create(&newRoom); err != nil {\n//\t\tif err := tx.Rollback(); err != nil {\n//\t\t\tglobal.Logger.Errorf(\"事务回滚失败 %s\", err.Error())\n//\t\t}\n//\t\tglobal.Logger.Errorf(\"添加房间表失败 %s\", err.Error())\n//\t\treturn pkgResp.ErrorResponseData(\"系统繁忙，请稍后再试~\"), errors.New(\"Business Error\")\n//\t}\n//\n//\t// 查询用户头像\n//\tuser := global.Query.User\n//\tuserTx := tx.User.WithContext(ctx)\n//\tuserR, err := userTx.Where(user.ID.Eq(uid)).First()\n//\tif err != nil {\n//\t\tif err := tx.Rollback(); err != nil {\n//\t\t\tglobal.Logger.Errorf(\"事务回滚失败 %s\", err.Error())\n//\t\t}\n//\t\tglobal.Logger.Errorf(\"查询用户表失败 %s\", err.Error())\n//\t\treturn pkgResp.ErrorResponseData(\"系统繁忙，请稍后再试~\"), errors.New(\"Business Error\")\n//\t}\n//\n//\tuidList = append([]int64{uid}, uidList...)\n//\tuserRList, err := userTx.Where(user.ID.In(uidList...)).Find()\n//\tgroupName := \"\"\n//\tfor _, user := range userRList {\n//\t\tgroupName += (user.Name + \"、\")\n//\t}\n//\tgroupName = strings.TrimRight(groupName, \"、\")\n//\trunes := []rune(groupName)\n//\tif len(runes) > 10 {\n//\t\trunes = runes[:10]\n//\t}\n//\tgroupName = string(runes) + \"...\"\n//\n//\t// 创建群聊表\n//\troomGroupTx := tx.RoomGroup.WithContext(ctx)\n//\tnewRoomGroup := model.RoomGroup{\n//\t\tRoomID: newRoom.ID,\n//\t\tName:   groupName,\n//\t\t// 默认为创建者头像\n//\t\tAvatar:  userR.Avatar,\n//\t\tExtJSON: \"{}\",\n//\t}\n//\tif err := roomGroupTx.Create(&newRoomGroup); err != nil {\n//\t\tif err := tx.Rollback(); err != nil {\n//\t\t\tglobal.Logger.Errorf(\"事务回滚失败 %s\", err.Error())\n//\t\t}\n//\t\tglobal.Logger.Errorf(\"添加群聊表失败 %s\", err.Error())\n//\t\treturn pkgResp.ErrorResponseData(\"系统繁忙，请稍后再试~\"), errors.New(\"Business Error\")\n//\t}\n//\n//\tgroupMemberTx := tx.GroupMember.WithContext(ctx)\n//\tnewGroupMemberList := []*model.GroupMember{\n//\t\t{\n//\t\t\tUID:     uid,\n//\t\t\tGroupID: newRoomGroup.ID,\n//\t\t\t// TODO: 1为群主,抽取为常量\n//\t\t\tRole: 1,\n//\t\t},\n//\t}\n//\tfor _, userInfo := range userRList {\n//\t\tnewGroupMemberList = append(newGroupMemberList, &model.GroupMember{\n//\t\t\tUID:     userInfo.ID,\n//\t\t\tGroupID: newRoomGroup.ID,\n//\t\t\t// TODO: 1为群主,抽取为常量\n//\t\t\tRole: 2,\n//\t\t})\n//\t}\n//\tif err := groupMemberTx.Create(newGroupMemberList...); err != nil {\n//\t\tif err := tx.Rollback(); err != nil {\n//\t\t\tglobal.Logger.Errorf(\"事务回滚失败 %s\", err.Error())\n//\t\t}\n//\t\tglobal.Logger.Errorf(\"添加群组成员表失败 %s\", err.Error())\n//\t\treturn pkgResp.ErrorResponseData(\"系统繁忙，请稍后再试~\"), errors.New(\"Business Error\")\n//\t}\n//\t// 自动发送一条消息\n//\tmessageTx := tx.Message.WithContext(ctx)\n//\tnewMessage := model.Message{\n//\t\tFromUID:      uid,\n//\t\tRoomID:       newRoom.ID,\n//\t\tType:         enum.TextMessageType,\n//\t\tContent:      \"欢迎加入群聊\",\n//\t\tExtra:        \"{}\",\n//\t\tDeleteStatus: pkgEnum.NORMAL,\n//\t}\n//\tif err := messageTx.Create(&newMessage); err != nil {\n//\t\tif err := tx.Rollback(); err != nil {\n//\t\t\tglobal.Logger.Errorf(\"事务回滚失败 %s\", err.Error())\n//\t\t}\n//\t\tglobal.Logger.Errorf(\"添加消息表失败 %s\", err.Error())\n//\t\treturn pkgResp.ErrorResponseData(\"系统繁忙，请稍后再试~\"), errors.New(\"Business Error\")\n//\t}\n//\n//\t// 创建会话表\n//\tcontactTx := tx.Contact.WithContext(ctx)\n//\tnewContact := model.Contact{\n//\t\tUID:        uid,\n//\t\tRoomID:     newRoom.ID,\n//\t\tReadTime:   time.Now(),\n//\t\tActiveTime: time.Now(),\n//\t\tLastMsgID:  newMessage.ID,\n//\t}\n//\tif err := contactTx.Create(&newContact); err != nil {\n//\t\tif err := tx.Rollback(); err != nil {\n//\t\t\tglobal.Logger.Errorf(\"事务回滚失败 %s\", err.Error())\n//\t\t}\n//\t\tglobal.Logger.Errorf(\"添加会话表失败 %s\", err.Error())\n//\t\treturn pkgResp.ErrorResponseData(\"系统繁忙，请稍后再试~\"), errors.New(\"Business Error\")\n//\t}\n//\n//\tif err := tx.Commit(); err != nil {\n//\t\tglobal.Logger.Errorf(\"事务提交失败 %s\", err.Error())\n//\t\treturn pkgResp.ErrorResponseData(\"系统繁忙，请稍后再试~\"), errors.New(\"Business Error\")\n//\t}\n//\n//\tglobal.Bus.Publish(enum.NewMessageEvent, newMessage)\n//\n//\treturn pkgResp.SuccessResponseData(\"success\"), nil\n//}\n//\n//// DeleteGroupService 删除群聊\n////\n////\t@Summary\t删除群聊\n////\t@Produce\tjson\n////\t@Param\t\tid\tbody\t\tstring\t\t\t\t\ttrue\t\"房间ID\"\n////\t@Success\t200\t{object}\tresp.ResponseData\t\"成功\"\n////\t@Failure\t500\t{object}\tresp.ResponseData\t\"内部错误\"\n////\t@Router\t\t/api/group/:id [delete]\n//func DeleteGroupService(c *gin.Context) {\n//\tuid := c.GetInt64(\"uid\")\n//\tdeleteGroupReq := req.DeleteGroupReq{}\n//\tif err := c.ShouldBindUri(&deleteGroupReq); err != nil { //ShouldBind()会自动推导\n//\t\tresp.ErrorResponse(c, \"参数错误\")\n//\t\tglobal.Logger.Errorf(\"参数错误: %v\", err)\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\n//\ttx := global.Query.Begin()\n//\tdefer func() {\n//\n//\t}()\n//\tctx := context.Background()\n//\t// 查询群聊id\n//\troomGroup := global.Query.RoomGroup\n//\troomGroupTx := tx.RoomGroup.WithContext(ctx)\n//\troomGroupR, err := roomGroupTx.Where(roomGroup.RoomID.Eq(deleteGroupReq.ID)).First()\n//\tif err != nil {\n//\t\tif err := tx.Rollback(); err != nil {\n//\t\t\tglobal.Logger.Errorf(\"事务回滚失败 %s\", err.Error())\n//\t\t\treturn\n//\t\t}\n//\t\tresp.ErrorResponse(c, \"删除群聊失败\")\n//\t\tc.Abort()\n//\t\tglobal.Logger.Errorf(\"查询群聊表失败 %s\", err.Error())\n//\t\treturn\n//\n//\t}\n//\t// TODO:查询用户是否在群聊中\n//\tgroupMember := global.Query.GroupMember\n//\tgroupMemberTx := tx.GroupMember.WithContext(ctx)\n//\t// 查询用户是否是群主\n//\t_, err = groupMemberTx.Where(groupMember.UID.Eq(uid), groupMember.GroupID.Eq(roomGroupR.ID), groupMember.Role.Eq(1)).First()\n//\tif err != nil {\n//\t\tif err := tx.Rollback(); err != nil {\n//\t\t\tglobal.Logger.Errorf(\"事务回滚失败 %s\", err.Error())\n//\t\t\treturn\n//\t\t}\n//\t\tresp.ErrorResponse(c, \"删除群聊失败\")\n//\t\tc.Abort()\n//\t\tglobal.Logger.Errorf(\"查询群组成员表失败 %s\", err.Error())\n//\t\treturn\n//\t}\n//\t// 获取群聊成员\n//\tgroupMemberList, err := groupMemberTx.Where(groupMember.GroupID.Eq(roomGroupR.ID)).Find()\n//\tif err != nil {\n//\t\tif err := tx.Rollback(); err != nil {\n//\t\t\tglobal.Logger.Errorf(\"事务回滚失败 %s\", err.Error())\n//\t\t\treturn\n//\t\t}\n//\t\tresp.ErrorResponse(c, \"删除群聊失败\")\n//\t\tc.Abort()\n//\t\tglobal.Logger.Errorf(\"查询群组成员表失败 %s\", err.Error())\n//\t\treturn\n//\t}\n//\n//\t// 删除所有成员的会话表\n//\tfor _, groupMember := range groupMemberList {\n//\t\tcontact := global.Query.Contact\n//\t\tcontactTx := tx.Contact.WithContext(ctx)\n//\t\tif _, err := contactTx.Where(contact.UID.Eq(groupMember.UID), contact.RoomID.Eq(roomGroupR.ID)).Delete(); err != nil {\n//\t\t\tif err := tx.Rollback(); err != nil {\n//\t\t\t\tglobal.Logger.Errorf(\"事务回滚失败 %s\", err.Error())\n//\t\t\t\treturn\n//\t\t\t}\n//\t\t\tresp.ErrorResponse(c, \"删除群聊失败\")\n//\t\t\tc.Abort()\n//\t\t\tglobal.Logger.Errorf(\"删除会话表失败 %s\", err.Error())\n//\t\t\treturn\n//\t\t}\n//\t}\n//\n//\t// 删除群聊表\n//\tif _, err := roomGroupTx.Where(roomGroup.RoomID.Eq(roomGroupR.ID)).Delete(); err != nil {\n//\t\tif err := tx.Rollback(); err != nil {\n//\t\t\tglobal.Logger.Errorf(\"事务回滚失败 %s\", err.Error())\n//\t\t\treturn\n//\t\t}\n//\t\tresp.ErrorResponse(c, \"删除群聊失败\")\n//\t\tc.Abort()\n//\t\tglobal.Logger.Errorf(\"删除群聊表失败 %s\", err.Error())\n//\t\treturn\n//\t}\n//\t// 删除房间表\n//\troom := global.Query.Room\n//\troomTx := tx.Room.WithContext(ctx)\n//\tif _, err := roomTx.Where(room.ID.Eq(roomGroupR.ID)).Delete(); err != nil {\n//\t\tif err := tx.Rollback(); err != nil {\n//\t\t\tglobal.Logger.Errorf(\"事务回滚失败 %s\", err.Error())\n//\t\t\treturn\n//\t\t}\n//\t\tresp.ErrorResponse(c, \"删除群聊失败\")\n//\t\tc.Abort()\n//\t\tglobal.Logger.Errorf(\"删除房间表失败 %s\", err.Error())\n//\t\treturn\n//\t}\n//\t// 删除群组成员表\n//\tif _, err := groupMemberTx.Where(groupMember.GroupID.Eq(roomGroupR.ID)).Delete(); err != nil {\n//\t\tif err := tx.Rollback(); err != nil {\n//\t\t\tglobal.Logger.Errorf(\"事务回滚失败 %s\", err.Error())\n//\t\t\treturn\n//\t\t}\n//\t\tresp.ErrorResponse(c, \"删除群聊失败\")\n//\t\tc.Abort()\n//\t\tglobal.Logger.Errorf(\"删除群组成员表失败 %s\", err.Error())\n//\t\treturn\n//\t}\n//\t// TODO:抽取为事件\n//\t// 删除消息表\n//\tmessage := global.Query.Message\n//\tmessageTx := tx.Message.WithContext(ctx)\n//\tmsg := model.Message{\n//\t\tDeleteStatus: 0,\n//\t}\n//\tif _, err := messageTx.Where(message.RoomID.Eq(roomGroupR.ID)).Updates(msg); err != nil {\n//\t\tif err := tx.Rollback(); err != nil {\n//\t\t\tglobal.Logger.Errorf(\"事务回滚失败 %s\", err.Error())\n//\t\t\treturn\n//\t\t}\n//\t\tresp.ErrorResponse(c, \"删除群聊失败\")\n//\t\tc.Abort()\n//\t\tglobal.Logger.Errorf(\"删除消息表失败 %s\", err.Error())\n//\t\treturn\n//\t}\n//\t// TODO: 删除群聊仅禁止发送新消息，不删除消息\n//\tif err := tx.Commit(); err != nil {\n//\t\tglobal.Logger.Errorf(\"事务提交失败 %s\", err.Error())\n//\t\tresp.ErrorResponse(c, \"删除群聊失败\")\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\tresp.SuccessResponseWithMsg(c, \"success\")\n//\treturn\n//}\n//\n//// JoinGroupService 加入群聊\n////\n////\t@Summary\t加入群聊\n////\t@Produce\tjson\n////\t@Param\t\tid\tbody\t\tint\t\t\t\t\ttrue\t\"房间id\"\n////\t@Success\t200\t{object}\tresp.ResponseData\t\"成功\"\n////\t@Failure\t500\t{object}\tresp.ResponseData\t\"内部错误\"\n////\t@Router\t\t/api/group/create [post]\n//func JoinGroupService(c *gin.Context) {\n//\tuid := c.GetInt64(\"uid\")\n//\tjoinGroupReq := req.JoinGroupReq{}\n//\tif err := c.ShouldBind(&joinGroupReq); err != nil { //ShouldBind()会自动推导\n//\t\tresp.ErrorResponse(c, \"参数错误\")\n//\t\tglobal.Logger.Errorf(\"参数错误: %v\", err)\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\tctx := context.Background()\n//\t// 房间是否存在\n//\troom := global.Query.Room\n//\troomQ := room.WithContext(ctx)\n//\troomR, err := roomQ.Where(room.ID.Eq(joinGroupReq.ID)).First()\n//\tif err != nil {\n//\t\tresp.ErrorResponse(c, \"加入群聊失败\")\n//\t\tglobal.Logger.Errorf(\"查询房间失败 %s\", err)\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\tif roomR.Type != enum.GROUP {\n//\t\tresp.ErrorResponse(c, \"加入群聊失败\")\n//\t\tglobal.Logger.Errorf(\"房间类型错误 %s\", err)\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\t// 是否已经加入群聊\n//\troomGroup := global.Query.RoomGroup\n//\troomGroupQ := roomGroup.WithContext(ctx)\n//\t// 查询群聊表\n//\troomGroupR, err := roomGroupQ.Where(roomGroup.RoomID.Eq(roomR.ID)).First()\n//\tif err != nil {\n//\t\tresp.ErrorResponse(c, \"加入群聊失败\")\n//\t\tglobal.Logger.Errorf(\"查询群聊失败 %s\", err)\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\n//\tgroupMember := global.Query.GroupMember\n//\tgroupMemberQ := groupMember.WithContext(ctx)\n//\tgroupMemberR, err := groupMemberQ.Where(groupMember.UID.Eq(uid), groupMember.GroupID.Eq(roomGroupR.ID)).First()\n//\tif err != nil && err.Error() != \"record not found\" {\n//\t\tresp.ErrorResponse(c, \"加入群聊失败\")\n//\t\tglobal.Logger.Errorf(\"查询群成员表失败 %s\", err)\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\tif groupMemberR != nil {\n//\t\tresp.ErrorResponse(c, \"禁止重复加入群聊\")\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\n//\t// 加入群聊\n//\ttx := global.Query.Begin()\n//\tgroupMemberTx := tx.GroupMember.WithContext(ctx)\n//\tnewGroupMember := model.GroupMember{\n//\t\tUID:     uid,\n//\t\tGroupID: roomGroupR.ID,\n//\t\t// 普通成员\n//\t\tRole: 3,\n//\t}\n//\tif err := groupMemberTx.Create(&newGroupMember); err != nil {\n//\t\tif err := tx.Rollback(); err != nil {\n//\t\t\tglobal.Logger.Errorf(\"事务回滚失败 %s\", err.Error())\n//\t\t\treturn\n//\t\t}\n//\t\tresp.ErrorResponse(c, \"加入群聊失败\")\n//\t\tc.Abort()\n//\t\tglobal.Logger.Errorf(\"添加群组成员表失败 %s\", err.Error())\n//\t\treturn\n//\t}\n//\t// 创建会话表\n//\tcontactTx := tx.Contact.WithContext(ctx)\n//\tnewContact := model.Contact{\n//\t\tUID:    uid,\n//\t\tRoomID: roomR.ID,\n//\t}\n//\tif err := contactTx.Create(&newContact); err != nil {\n//\t\tif err := tx.Rollback(); err != nil {\n//\t\t\tglobal.Logger.Errorf(\"事务回滚失败 %s\", err.Error())\n//\t\t\treturn\n//\t\t}\n//\t\tresp.ErrorResponse(c, \"加入群聊失败\")\n//\t\tc.Abort()\n//\t\tglobal.Logger.Errorf(\"添加会话表失败 %s\", err.Error())\n//\t\treturn\n//\t}\n//\n//\t// 自动发送一条消息\n//\tmessageTx := tx.Message.WithContext(ctx)\n//\tnewMessage := model.Message{\n//\t\tFromUID: uid,\n//\t\tRoomID:  roomR.ID,\n//\t\tType:    enum.TextMessageType,\n//\t\tContent: \"大家好~\",\n//\t\tExtra:   \"{}\",\n//\t}\n//\tif err := messageTx.Create(&newMessage); err != nil {\n//\t\tif err := tx.Rollback(); err != nil {\n//\t\t\tglobal.Logger.Errorf(\"事务回滚失败 %s\", err.Error())\n//\t\t\treturn\n//\t\t}\n//\t\tresp.ErrorResponse(c, \"加入群聊失败\")\n//\t\tc.Abort()\n//\t\tglobal.Logger.Errorf(\"添加消息表失败 %s\", err.Error())\n//\t\treturn\n//\t}\n//\tif err := tx.Commit(); err != nil {\n//\t\tglobal.Logger.Errorf(\"事务提交失败 %s\", err.Error())\n//\t\tresp.ErrorResponse(c, \"加入群聊失败\")\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\tglobal.Bus.Publish(enum.NewMessageEvent, newMessage)\n//\n//\tresp.SuccessResponseWithMsg(c, \"success\")\n//}\n//\n//// QuitGroupService 退出群聊\n////\n////\t@Summary\t退出群聊\n////\t@Produce\tjson\n////\t@Param\t\tid\tbody\t\tint\t\t\t\t\ttrue\t\"房间id\"\n////\t@Success\t200\t{object}\tresp.ResponseData\t\"成功\"\n////\t@Failure\t500\t{object}\tresp.ResponseData\t\"内部错误\"\n////\t@Router\t\t/api/group/create [post]\n//func QuitGroupService(c *gin.Context) {\n//\tuid := c.GetInt64(\"uid\")\n//\tquitGroupReq := req.QuitGroupReq{}\n//\tif err := c.ShouldBind(&quitGroupReq); err != nil {\n//\t\tresp.ErrorResponse(c, \"参数错误\")\n//\t\tglobal.Logger.Errorf(\"参数错误: %v\", err)\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\n//\tctx := context.Background()\n//\ttx := global.Query.Begin()\n//\t// 群聊是否存在\n//\troom := global.Query.Room\n//\troomTx := tx.Room.WithContext(ctx)\n//\t_, err := roomTx.Where(room.ID.Eq(quitGroupReq.ID)).First()\n//\tif err != nil {\n//\t\tif err.Error() != gorm.ErrRecordNotFound.Error() {\n//\t\t\tglobal.Logger.Errorf(\"查询房间失败 %s\", err)\n//\t\t}\n//\t\tresp.ErrorResponse(c, \"群聊不存在\")\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\t// 用户是否在群聊中\n//\tgroupMember := global.Query.GroupMember\n//\tgroupMemberTx := tx.GroupMember.WithContext(ctx)\n//\t_, err = groupMemberTx.Where(groupMember.UID.Eq(uid), groupMember.GroupID.Eq(quitGroupReq.ID)).First()\n//\tif err != nil {\n//\t\tif err.Error() == gorm.ErrRecordNotFound.Error() {\n//\t\t\tresp.ErrorResponse(c, \"未加入群聊\")\n//\t\t\tc.Abort()\n//\t\t\treturn\n//\t\t}\n//\t\tresp.ErrorResponse(c, \"退出群聊失败\")\n//\t\tglobal.Logger.Errorf(\"查询群组成员表失败 %s\", err)\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\t// 删除会话表\n//\tcontact := global.Query.Contact\n//\tcontactTx := tx.Contact.WithContext(ctx)\n//\tif _, err := contactTx.Where(contact.UID.Eq(uid), contact.RoomID.Eq(quitGroupReq.ID)).Delete(); err != nil {\n//\t\tresp.ErrorResponse(c, \"退出群聊失败\")\n//\t\tglobal.Logger.Errorf(\"删除会话表失败 %s\", err)\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\t// 删除群组成员表\n//\t// 查询群组\n//\troomGroup := global.Query.RoomGroup\n//\troomGroupTx := tx.RoomGroup.WithContext(ctx)\n//\troomGroupR, err := roomGroupTx.Where(roomGroup.RoomID.Eq(quitGroupReq.ID)).First()\n//\tif err != nil {\n//\t\tresp.ErrorResponse(c, \"退出群聊失败\")\n//\t\tglobal.Logger.Errorf(\"查询群聊失败 %s\", err)\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\tif _, err := groupMemberTx.Where(groupMember.UID.Eq(uid), groupMember.GroupID.Eq(roomGroupR.ID)).Delete(); err != nil {\n//\t\tresp.ErrorResponse(c, \"退出群聊失败\")\n//\t\tglobal.Logger.Errorf(\"删除群组成员表失败 %s\", err)\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\n//\tif err := tx.Commit(); err != nil {\n//\t\tglobal.Logger.Errorf(\"事务提交失败 %s\", err)\n//\t\tresp.ErrorResponse(c, \"退出群聊失败\")\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\tresp.SuccessResponseWithMsg(c, \"success\")\n//\treturn\n//}\n//\n//// GetGroupMemberListService 退出群聊\n////\n////\t@Summary\t退出群聊\n////\t@Produce\tjson\n////\t@Param\t\tid\tbody\t\tint\t\t\t\t\ttrue\t\"房间id\"\n////\t@Success\t200\t{object}\tresp.ResponseData\t\"成功\"\n////\t@Failure\t500\t{object}\tresp.ResponseData\t\"内部错误\"\n////\t@Router\t\t/api/group/getGroupMemberList [get]\n//func GetGroupMemberListService(c *gin.Context) {\n//\tuid := c.GetInt64(\"uid\")\n//\tgetGroupMemberListReq := req.GetGroupMemberListReq{}\n//\tif err := c.ShouldBindQuery(&getGroupMemberListReq); err != nil {\n//\t\tresp.ErrorResponse(c, \"参数错误\")\n//\t\tglobal.Logger.Errorf(\"参数错误: %v\", err)\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\tctx := context.Background()\n//\n//\t// 查询房间表\n//\troom := global.Query.Room\n//\troomQ := room.WithContext(ctx)\n//\troomR, err := roomQ.Where(room.ID.Eq(getGroupMemberListReq.RoomId)).First()\n//\tif err != nil {\n//\t\tresp.ErrorResponse(c, \"查询群聊失败\")\n//\t\tglobal.Logger.Errorf(\"查询房间失败 %s\", err)\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\t// 查询群聊表\n//\troomGroup := global.Query.RoomGroup\n//\troomGroupQ := roomGroup.WithContext(ctx)\n//\troomGroupR, err := roomGroupQ.Where(roomGroup.RoomID.Eq(roomR.ID)).First()\n//\tif err != nil {\n//\t\tresp.ErrorResponse(c, \"查询群聊失败\")\n//\t\tglobal.Logger.Errorf(\"查询群聊失败 %s\", err)\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\n//\t// 查询用户是否在群聊中\n//\tgroupMember := global.Query.GroupMember\n//\tgroupMemberQ := groupMember.WithContext(ctx)\n//\t_, err = groupMemberQ.Where(groupMember.UID.Eq(uid), groupMember.GroupID.Eq(roomGroupR.ID)).First()\n//\tif err != nil {\n//\t\tresp.ErrorResponse(c, \"查询群聊失败\")\n//\t\tglobal.Logger.Errorf(\"查询群组成员表失败 %s\", err)\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\n//\t// 分页查询\n//\t// 默认值\n//\t// 划分游标，status_activateTime\n//\tvar userR []dto.GetGroupMemberDto\n//\tstatus, activeTime := cursorSplit(getGroupMemberListReq.Cursor)\n//\t// 查询群组成员表,联表游标翻页\n//\tuser := global.Query.User\n//\tuserQ := user.WithContext(ctx)\n//\tif 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 {\n//\t\tresp.ErrorResponse(c, \"查询群聊失败\")\n//\t\tglobal.Logger.Errorf(\"查询群组成员表失败 %s\", err)\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\t// 不足，用不在线的补充\n//\tif len(userR) < getGroupMemberListReq.PageSize && status == 1 {\n//\t\tvar add []dto.GetGroupMemberDto\n//\t\tif 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 {\n//\t\t\tresp.ErrorResponse(c, \"查询群聊失败\")\n//\t\t\tglobal.Logger.Errorf(\"查询群组成员表失败 %s\", err)\n//\t\t\tc.Abort()\n//\t\t\treturn\n//\t\t}\n//\t\tuserR = append(userR, add...)\n//\t}\n//\n//\tnewCursor := genCursor(userR)\n//\tresp.SuccessResponse(c, pkgResp.PageResp{\n//\t\tCursor: &newCursor,\n//\t\tIsLast: len(userR) < getGroupMemberListReq.PageSize,\n//\t\tData:   userR,\n//\t})\n//}\n//\n//// GrantAdministratorService 授予管理员权限\n////\n////\t@Summary\t授予管理员权限\n////\t@Produce\tjson\n////\t@Param\t\troom_id\tbody\t\tint\t\t\t\t\ttrue\t\"房间id\"\n////\t@Param\t\tgrant_uid\tbody\t\tint\t\t\t\t\ttrue\t\"授权用户id\"\n////\t@Success\t200\t{object}\tresp.ResponseData\t\"成功\"\n////\t@Failure\t500\t{object}\tresp.ResponseData\t\"内部错误\"\n////\t@Router\t\t/api/group/getGroupMemberList [get]\n//func GrantAdministratorService(c *gin.Context) {\n//\tuid := c.GetInt64(\"uid\")\n//\tgrantAdministratorReq := req.GrantAdministratorReq{}\n//\tif err := c.ShouldBind(&grantAdministratorReq); err != nil {\n//\t\tresp.ErrorResponse(c, \"参数错误\")\n//\t\tglobal.Logger.Errorf(\"参数错误: %v\", err)\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\tctx := context.Background()\n//\t// 检查用户是否为群主\n//\troomGroup := global.Query.RoomGroup\n//\troomGroupQ := roomGroup.WithContext(ctx)\n//\troomGroupR, err := roomGroupQ.Where(roomGroup.RoomID.Eq(grantAdministratorReq.RoomId)).First()\n//\tif err != nil {\n//\t\tresp.ErrorResponse(c, \"授权失败\")\n//\t\tglobal.Logger.Errorf(\"查询群聊失败 %s\", err)\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\tgroupMember := global.Query.GroupMember\n//\tgroupMemberQ := groupMember.WithContext(ctx)\n//\tgroupMemberR, err := groupMemberQ.Where(groupMember.UID.Eq(uid), groupMember.GroupID.Eq(roomGroupR.ID)).First()\n//\tif err != nil {\n//\t\tglobal.Logger.Errorf(\"查询群组成员表失败 %s\", err)\n//\t\tresp.ErrorResponse(c, \"授权失败\")\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\tif groupMemberR.Role != 1 {\n//\t\tresp.ErrorResponse(c, \"授权失败,权限不足\")\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\n//\t// 检查授权用户是否在群聊中\n//\tgroupMemberR, err = groupMemberQ.Where(groupMember.UID.Eq(grantAdministratorReq.GrantUid), groupMember.GroupID.Eq(roomGroupR.ID)).First()\n//\tif err != nil {\n//\t\tresp.ErrorResponse(c, \"授权失败，用户不在群聊中\")\n//\t\tglobal.Logger.Errorf(\"查询群组成员表失败 %s\", err)\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\t// 如果用户是不是普通用户\n//\tif groupMemberR.Role != 3 {\n//\t\tresp.ErrorResponse(c, \"授权失败\")\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\t// 授权\n//\tgroupMemberR.Role = 2\n//\tgroupMemberR.UpdateTime = time.Now()\n//\tif _, err := groupMemberQ.Where(groupMember.ID.Eq(groupMemberR.ID)).Updates(groupMemberR); err != nil {\n//\t\tresp.ErrorResponse(c, \"授权失败\")\n//\t\tglobal.Logger.Errorf(\"更新群组成员表失败 %s\", err)\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\n//\tresp.SuccessResponseWithMsg(c, \"success\")\n//\treturn\n//}\n//\n//// RemoveAdministratorService 移除管理员权限\n////\n////\t@Summary\t移除管理员权限\n////\t@Produce\tjson\n////\t@Param\t\troom_id\tbody\t\tint\t\t\t\t\ttrue\t\"房间id\"\n////\t@Param\t\tremove_uid\tbody\t\tint\t\t\t\t\ttrue\t\"授权用户id\"\n////\t@Success\t200\t{object}\tresp.ResponseData\t\"成功\"\n////\t@Failure\t500\t{object}\tresp.ResponseData\t\"内部错误\"\n////\t@Router\t\t/api/group/getGroupMemberList [get]\n//func RemoveAdministratorService(c *gin.Context) {\n//\tuid := c.GetInt64(\"uid\")\n//\tremoveAdministratorReq := req.RemoveAdministratorReq{}\n//\tif err := c.ShouldBind(&removeAdministratorReq); err != nil {\n//\t\tresp.ErrorResponse(c, \"参数错误\")\n//\t\tglobal.Logger.Errorf(\"参数错误: %v\", err)\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\tctx := context.Background()\n//\t// 检查用户是否为群主\n//\troomGroup := global.Query.RoomGroup\n//\troomGroupQ := roomGroup.WithContext(ctx)\n//\troomGroupR, err := roomGroupQ.Where(roomGroup.RoomID.Eq(removeAdministratorReq.RoomId)).First()\n//\tif err != nil {\n//\t\tresp.ErrorResponse(c, \"移除管理员失败\")\n//\t\tglobal.Logger.Errorf(\"查询群聊失败 %s\", err)\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\tgroupMember := global.Query.GroupMember\n//\tgroupMemberQ := groupMember.WithContext(ctx)\n//\tgroupMemberR, err := groupMemberQ.Where(groupMember.UID.Eq(uid), groupMember.GroupID.Eq(roomGroupR.ID)).First()\n//\tif err != nil {\n//\t\tglobal.Logger.Errorf(\"查询群组成员表失败 %s\", err)\n//\t\tresp.ErrorResponse(c, \"移除管理员失败\")\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\tif groupMemberR.Role != 1 {\n//\t\tresp.ErrorResponse(c, \"移除管理员失败,权限不足\")\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\n//\t// 检查授权用户是否在群聊中\n//\tgroupMemberR, err = groupMemberQ.Where(groupMember.UID.Eq(removeAdministratorReq.RemoveUid), groupMember.GroupID.Eq(roomGroupR.ID)).First()\n//\tif err != nil {\n//\t\tresp.ErrorResponse(c, \"移除管理员失败，用户不在群聊中\")\n//\t\tglobal.Logger.Errorf(\"查询群组成员表失败 %s\", err)\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\t// 如果用户是不是普通用户\n//\tif groupMemberR.Role != 2 {\n//\t\tresp.ErrorResponse(c, \"移除管理员失败\")\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\n//\t// 移除权限\n//\tgroupMemberR.Role = 3\n//\tgroupMemberR.UpdateTime = time.Now()\n//\tif _, err := groupMemberQ.Where(groupMember.ID.Eq(groupMemberR.ID)).Updates(groupMemberR); err != nil {\n//\t\tresp.ErrorResponse(c, \"移除管理员失败\")\n//\t\tglobal.Logger.Errorf(\"更新群组成员表失败 %s\", err)\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\n//\tresp.SuccessResponseWithMsg(c, \"success\")\n//\treturn\n//}\n//\n//// 分割游标\n//func cursorSplit(cursor *string) (int, time.Time) {\n//\tif cursor == nil {\n//\t\treturn 1, time.Time{}\n//\t}\n//\t// TODO： 抽取为常量\n//\tlines := strings.Split(*cursor, \"_\")\n//\tstatus, err := strconv.Atoi(lines[0])\n//\tif err != nil {\n//\t\treturn 1, time.Time{}\n//\t}\n//\ttimeUnix, err := strconv.ParseInt(lines[1], 10, 64)\n//\tif err != nil {\n//\t\treturn 1, time.Time{}\n//\t}\n//\tactiveTime := time.Unix(timeUnix, timeUnix%1000000000)\n//\treturn status, activeTime\n//}\n//\n//// 生成游标\n//func genCursor(users []dto.GetGroupMemberDto) string {\n//\tif users == nil || len(users) == 0 {\n//\t\treturn fmt.Sprintf(\"%d_%d\", 2, time.Now().Unix())\n//\t}\n//\tstatus := users[len(users)-1].ActiveStatus\n//\tactiveTime := users[len(users)-1].LastOptTime\n//\tnewCursor := fmt.Sprintf(\"%d_%d\", status, activeTime.UnixMilli())\n//\treturn newCursor\n//}\n"
  },
  {
    "path": "service/message_service.go",
    "content": "package service\n\n//\n//import (\n//\t\"DiTing-Go/dal/model\"\n//\t\"DiTing-Go/domain/enum\"\n//\t\"DiTing-Go/domain/vo/req\"\n//\tdomainResp \"DiTing-Go/domain/vo/resp\"\n//\t\"DiTing-Go/global\"\n//\tpkgEnum \"DiTing-Go/pkg/domain/enum\"\n//\t\"DiTing-Go/pkg/domain/vo/resp\"\n//\t\"context\"\n//\t\"github.com/apache/rocketmq-client-go/v2/primitive\"\n//\t\"github.com/goccy/go-json\"\n//\t\"log\"\n//\t\"time\"\n//)\n//\n//// SendTextMsgService 发送文本消息\n//func SendTextMsgService(uid int64, msgReq req.MessageReq) (resp.ResponseData, error) {\n//\tctx := context.Background()\n//\tuser := global.Query.User\n//\tuserQ := user.WithContext(ctx)\n//\tuserR, err := userQ.Where(user.ID.Eq(uid)).First()\n//\tif err != nil {\n//\t\tlog.Println(\"查询用户失败\", err)\n//\t\treturn resp.ErrorResponseData(\"消息发送失败\"), err\n//\t}\n//\n//\tmsg := model.Message{}\n//\tmsg.Type = msgReq.MsgType\n//\tmsg.FromUID = uid\n//\tmsg.RoomID = msgReq.RoomId\n//\tmsg.Content = msgReq.Body.Content\n//\tif msg.Extra == \"\" {\n//\t\tmsg.Extra = \"{}\"\n//\t}\n//\n//\t// 发送消息\n//\tif err := SendTextMsg(&msg); err != nil {\n//\t\treturn resp.ErrorResponseData(\"消息发送失败\"), err\n//\t}\n//\t// 发送新消息事件\n//\tnewMsgByte, _ := json.Marshal(msg)\n//\trMsg := &primitive.Message{\n//\t\tTopic: enum.NewMessageTopic,\n//\t\tBody:  newMsgByte,\n//\t}\n//\t_, _ = global.RocketProducer.SendSync(ctx, rMsg)\n//\n//\tmsgResp := domainResp.MessageResp{\n//\t\tFromUser: domainResp.MsgUser{\n//\t\t\tUid:      uid,\n//\t\t\tUsername: userR.Name,\n//\t\t\tAvatar:   userR.Avatar,\n//\t\t},\n//\t\tSendTime: msg.CreateTime.UnixMilli(),\n//\t\tMessage: domainResp.Msg{\n//\t\t\tID:     msg.ID,\n//\t\t\tRoomId: msg.RoomID,\n//\t\t\tType:   msg.Type,\n//\t\t\tBody: domainResp.TextBody{\n//\t\t\t\tContent: msg.Content,\n//\t\t\t\tReply:   msg.ReplyMsgID,\n//\t\t\t},\n//\t\t},\n//\t}\n//\n//\t// 返回成功\n//\treturn resp.SuccessResponseData(msgResp), nil\n//}\n//\n//func SendTextMsg(msg *model.Message) error {\n//\tmsg.CreateTime = time.Now()\n//\tmsg.DeleteStatus = pkgEnum.NORMAL\n//\tctx := context.Background()\n//\tmsgQ := global.Query.WithContext(ctx).Message\n//\tif err := msgQ.Create(msg); err != nil {\n//\t\tlog.Println(\"消息发送失败\", err.Error())\n//\t\treturn err\n//\t}\n//\treturn nil\n//}\n"
  },
  {
    "path": "service/upload_service.go",
    "content": "package service\n\n//\n//import (\n//\t\"DiTing-Go/dal/model\"\n//\t\"DiTing-Go/domain/dto\"\n//\t\"DiTing-Go/domain/enum\"\n//\tvoResp \"DiTing-Go/domain/vo/resp\"\n//\t\"DiTing-Go/global\"\n//\t\"DiTing-Go/pkg/domain/vo/resp\"\n//\t\"context\"\n//\t\"fmt\"\n//\t\"github.com/gin-gonic/gin\"\n//\t\"github.com/goccy/go-json\"\n//\t\"github.com/minio/minio-go/v7\"\n//\t\"strconv\"\n//\t\"time\"\n//)\n//\n//// GetPreSigned 签发url\n//func GetPreSigned(c *gin.Context) {\n//\tuid := c.GetInt64(\"uid\")\n//\tctx := context.Background()\n//\troomIdStr, found := c.GetQuery(\"roomId\")\n//\tif !found {\n//\t\tglobal.Logger.Errorf(\"参数错误 %s\", roomIdStr)\n//\t\tresp.ErrorResponse(c, \"参数错误\")\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\troomId, err := strconv.ParseInt(roomIdStr, 10, 64)\n//\tif err != nil {\n//\t\tglobal.Logger.Errorf(\"参数错误 %s\", roomId)\n//\t\tresp.ErrorResponse(c, \"参数错误\")\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\n//\tfileName, found := c.GetQuery(\"fileName\")\n//\tif !found {\n//\t\tglobal.Logger.Errorf(\"参数错误 %s\", roomIdStr)\n//\t\tresp.ErrorResponse(c, \"参数错误\")\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\t// 构造文件名：time+uid+filename\n//\t// 按天创建桶\n//\ttimeStr := time.Now().Format(\"2006-01-02\")\n//\tfileName = fmt.Sprintf(\"%s/%d/%s\", timeStr, uid, fileName)\n//\n//\tpolicy := minio.NewPostPolicy()\n//\t// TODO:抽象为常量\n//\tif err := policy.SetBucket(\"diting\"); err != nil {\n//\t\tglobal.Logger.Errorf(\"创建policy失败 %s\", roomIdStr)\n//\t\tresp.ErrorResponse(c, \"获取签名失败，请稍后再试\")\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\tif err := policy.SetKey(fileName); err != nil {\n//\t\tglobal.Logger.Errorf(\"创建policy失败 %s\", roomIdStr)\n//\t\tresp.ErrorResponse(c, \"获取签名失败，请稍后再试\")\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\t// 失效时间1天\n//\tif err := policy.SetExpires(time.Now().UTC().AddDate(0, 0, 1)); err != nil {\n//\t\tglobal.Logger.Errorf(\"创建policy失败 %s\", roomIdStr)\n//\t\tresp.ErrorResponse(c, \"获取签名失败，请稍后再试\")\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\turl, formData, err := global.MinioClient.PresignedPostPolicy(ctx, policy)\n//\tif err != nil {\n//\t\tglobal.Logger.Errorf(\"创建policy失败 %s\", roomIdStr)\n//\t\tresp.ErrorResponse(c, \"获取签名失败，请稍后再试\")\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\tpreSignedResp := voResp.PreSignedResp{\n//\t\tUrl:    url.String(),\n//\t\tPolicy: formData,\n//\t}\n//\ttx := global.Query.Begin()\n//\t// 插入消息表\n//\tmessageTx := tx.Message.WithContext(ctx)\n//\tbase := dto.MessageBaseDto{\n//\t\tUrl:  url.String(),\n//\t\tSize: -1,\n//\t\tName: fileName,\n//\t}\n//\textra := dto.ImgMessageDto{\n//\t\tMessageBaseDto: base,\n//\t\t// TODO: 宽高需要前端传\n//\t\tWidth:  -1,\n//\t\tHeight: -1,\n//\t}\n//\tjsonStr, err := json.Marshal(extra)\n//\tif err != nil {\n//\t\tif err := tx.Rollback(); err != nil {\n//\t\t\tglobal.Logger.Errorf(\"事务回滚失败 %s\", err)\n//\t\t}\n//\t\tglobal.Logger.Errorf(\"json序列化失败 %s\", err)\n//\t\tresp.ErrorResponse(c, \"获取签名失败，请稍后再试\")\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\t// TODO:抽象为常量\n//\tnewMsg := model.Message{\n//\t\tFromUID:      uid,\n//\t\tRoomID:       roomId,\n//\t\tContent:      \"[图片]\",\n//\t\tDeleteStatus: 0,\n//\t\tType:         3,\n//\t\tExtra:        string(jsonStr),\n//\t}\n//\tif err := messageTx.Create(&newMsg); err != nil {\n//\t\tif err := tx.Rollback(); err != nil {\n//\t\t\tglobal.Logger.Errorf(\"事务回滚失败 %s\", err)\n//\t\t}\n//\t\tglobal.Logger.Errorf(\"数据库插入失败 %s\", err)\n//\t\tresp.ErrorResponse(c, \"获取签名失败，请稍后再试\")\n//\t\tc.Abort()\n//\t\treturn\n//\t}\n//\n//\tglobal.Bus.Publish(enum.NewMessageEvent, newMsg)\n//\n//\tresp.SuccessResponse(c, preSignedResp)\n//\treturn\n//}\n"
  },
  {
    "path": "service/user_service.go",
    "content": "package service\n\nimport (\n\t\"DiTing-Go/dal/model\"\n\tdomainEnum \"DiTing-Go/domain/enum\"\n\t\"DiTing-Go/domain/vo/req\"\n\t\"DiTing-Go/domain/vo/resp\"\n\t\"DiTing-Go/global\"\n\t\"DiTing-Go/logic\"\n\tpkgResp \"DiTing-Go/pkg/domain/vo/resp\"\n\t\"DiTing-Go/utils\"\n\t\"DiTing-Go/utils/jwt\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/go-redis/redis\"\n\t\"github.com/pkg/errors\"\n)\n\n// RegisterService 用户注册\nfunc RegisterService(userReq req.UserRegisterReq) (pkgResp.ResponseData, error) {\n\tctx := context.Background()\n\n\t// 参数校验\n\tif err := validateRegisterRequest(userReq); err != nil {\n\t\treturn pkgResp.ErrorResponseData(err.Error()), errors.New(\"Business Error\")\n\t}\n\n\t// 验证验证码\n\tif err := validateRegisterCaptcha(userReq.Phone, userReq.Captcha); err != nil {\n\t\treturn pkgResp.ErrorResponseData(err.Error()), errors.New(\"Business Error\")\n\t}\n\n\t// 检查用户是否已存在\n\texists, err := checkUserExists(ctx, userReq.Phone)\n\tif err != nil {\n\t\tglobal.Logger.Errorf(\"检查用户是否存在失败: phone=%s, err=%v\", userReq.Phone, err)\n\t\treturn pkgResp.ErrorResponseData(\"系统繁忙，请稍后再试~\"), errors.New(\"Business Error\")\n\t}\n\tif exists {\n\t\treturn pkgResp.ErrorResponseData(\"手机号已存在\"), errors.New(\"Business Error\")\n\t}\n\n\t// 创建用户\n\tif err := createNewUser(ctx, userReq); err != nil {\n\t\tglobal.Logger.Errorf(\"创建用户失败: phone=%s, err=%v\", userReq.Phone, err)\n\t\treturn pkgResp.ErrorResponseData(\"系统繁忙，请稍后再试~\"), errors.New(\"Business Error\")\n\t}\n\n\treturn pkgResp.SuccessResponseDataWithMsg(\"注册成功\"), nil\n}\n\n// validateRegisterRequest 验证注册请求参数\nfunc validateRegisterRequest(userReq req.UserRegisterReq) error {\n\tif userReq.Username == \"\" || userReq.Password == \"\" || userReq.Phone == \"\" {\n\t\tglobal.Logger.Infof(\"用户名、密码和手机号不能为空: userReq=%v\", userReq)\n\t\treturn errors.New(\"用户名、密码和手机号不能为空\")\n\t}\n\n\tif userReq.Captcha == \"\" {\n\t\tglobal.Logger.Infof(\"验证码不能为空: phone=%s\", userReq.Phone)\n\t\treturn errors.New(\"验证码不能为空\")\n\t}\n\n\t// 验证密码强度\n\tif len(userReq.Password) < 6 {\n\t\treturn errors.New(\"密码长度不能少于6位\")\n\t}\n\n\t// 验证手机号格式（简单验证）\n\tif len(userReq.Phone) != 11 {\n\t\treturn errors.New(\"手机号格式不正确\")\n\t}\n\n\treturn nil\n}\n\n// validateRegisterCaptcha 验证注册验证码\nfunc validateRegisterCaptcha(phone, captcha string) error {\n\tif !logic.CheckCaptchaProcess(phone, captcha) {\n\t\tglobal.Logger.Infof(\"验证码错误: phone=%s\", phone)\n\t\treturn errors.New(\"验证码错误\")\n\t}\n\treturn nil\n}\n\n// checkUserExists 检查用户是否已存在\nfunc checkUserExists(ctx context.Context, phone string) (bool, error) {\n\t// 先检查Redis缓存\n\tif logic.CheckPhoneInRedis(phone) {\n\t\tglobal.Logger.Infof(\"手机号已存在: phone=%s\", phone)\n\t\treturn true, nil\n\t}\n\n\t// 检查数据库\n\texists, err := logic.CheckPhoneInDB(ctx, phone)\n\tif err != nil {\n\t\tglobal.Logger.Errorf(\"检查用户是否存在失败: phone=%s, err=%v\", phone, err)\n\t\treturn false, err\n\t}\n\n\tif exists {\n\t\tglobal.Logger.Infof(\"手机号已存在: phone=%s\", phone)\n\t\treturn true, nil\n\t}\n\n\treturn false, nil\n}\n\n// createNewUser 创建新用户\nfunc createNewUser(ctx context.Context, userReq req.UserRegisterReq) error {\n\t// 对密码进行md5加密\n\tpassword := utils.EncryptPassword(userReq.Password)\n\n\t// 创建用户对象\n\tnewUser := model.User{\n\t\tName:     userReq.Username,\n\t\tPassword: password,\n\t\tPhone:    userReq.Phone,\n\t\tIPInfo:   \"{}\",\n\t}\n\n\t// 创建用户\n\tif err := logic.CreateUser(ctx, newUser); err != nil {\n\t\tglobal.Logger.Errorf(\"创建用户失败: phone=%s, err=%v\", userReq.Phone, err)\n\t\treturn err\n\t}\n\n\t// 缓存用户信息到Redis\n\tif err := logic.SetUserInfo2Redis(newUser); err != nil {\n\t\tglobal.Logger.Errorf(\"缓存用户信息失败: userId=%d, err=%v\", newUser.ID, err)\n\t\t// 不返回错误，因为用户创建成功，缓存失败不影响注册流程\n\t}\n\n\t// 缓存用户ID映射\n\tuserPhoneKey := utils.MakeUserPhoneKey(userReq.Phone)\n\tif err := utils.SetValueToRedis(userPhoneKey, fmt.Sprintf(\"%d\", newUser.ID), domainEnum.NotExpireTime); err != nil {\n\t\tglobal.Logger.Errorf(\"缓存用户ID映射失败: userId=%d, err=%v\", newUser.ID, err)\n\t\t// 不返回错误，因为用户创建成功，缓存失败不影响注册流程\n\t}\n\n\tglobal.Logger.Infof(\"用户注册成功: phone=%s, userId=%d\", userReq.Phone, newUser.ID)\n\treturn nil\n}\n\n// LoginService 用户登录\nfunc LoginService(loginReq req.UserLoginReq) (pkgResp.ResponseData, error) {\n\tctx := context.Background()\n\n\t// 参数校验\n\tif err := validateLoginRequest(loginReq); err != nil {\n\t\treturn pkgResp.ErrorResponseData(err.Error()), errors.New(\"Business Error\")\n\t}\n\n\t// 验证登录凭据\n\tif err := validateLoginCredentials(ctx, loginReq); err != nil {\n\t\treturn pkgResp.ErrorResponseData(err.Error()), errors.New(\"Business Error\")\n\t}\n\n\t// 获取用户信息\n\tuser, err := getUserInfo(loginReq.Phone)\n\tif err != nil {\n\t\tglobal.Logger.Errorf(\"获取用户信息失败: phone=%s, err=%v\", loginReq.Phone, err)\n\t\treturn pkgResp.ErrorResponseData(\"系统繁忙，请稍后再试~\"), errors.New(\"Business Error\")\n\t}\n\n\t// 生成JWT token\n\ttoken, err := jwt.GenerateToken(user.ID)\n\tif err != nil {\n\t\tglobal.Logger.Errorf(\"生成token失败: userId=%d, err=%v\", user.ID, err)\n\t\treturn pkgResp.ErrorResponseData(\"系统繁忙，请稍后再试~\"), errors.New(\"Business Error\")\n\t}\n\n\t// 构建响应\n\tuserResp := resp.UserLoginResp{\n\t\tToken:  token,\n\t\tUid:    user.ID,\n\t\tName:   user.Name,\n\t\tAvatar: user.Avatar,\n\t}\n\n\treturn pkgResp.SuccessResponseData(userResp), nil\n}\n\n// validateLoginRequest 验证登录请求参数\nfunc validateLoginRequest(loginReq req.UserLoginReq) error {\n\tif loginReq.LoginType == domainEnum.LoginByPassword {\n\t\tif loginReq.Phone == \"\" || loginReq.Password == \"\" {\n\t\t\tglobal.Logger.Infof(\"用户名和密码不能为空: loginReq=%v\", loginReq)\n\t\t\treturn errors.New(\"用户名和密码不能为空\")\n\t\t}\n\t} else {\n\t\tif loginReq.Phone == \"\" || loginReq.Captcha == \"\" {\n\t\t\tglobal.Logger.Infof(\"手机号和验证码不能为空: loginReq=%v\", loginReq)\n\t\t\treturn errors.New(\"手机号和验证码不能为空\")\n\t\t}\n\t}\n\treturn nil\n}\n\n// validateLoginCredentials 验证登录凭据\nfunc validateLoginCredentials(ctx context.Context, loginReq req.UserLoginReq) error {\n\tif loginReq.LoginType == domainEnum.LoginByPassword {\n\t\tif !logic.CheckPassword(ctx, loginReq.Phone, loginReq.Password) {\n\t\t\tglobal.Logger.Infof(\"用户名或密码错误: phone=%s\", loginReq.Phone)\n\t\t\treturn errors.New(\"用户名或密码错误\")\n\t\t}\n\t} else {\n\t\tif !logic.CheckCaptchaProcess(loginReq.Phone, loginReq.Captcha) {\n\t\t\tglobal.Logger.Infof(\"验证码错误: phone=%s\", loginReq.Phone)\n\t\t\treturn errors.New(\"验证码错误\")\n\t\t}\n\t}\n\treturn nil\n}\n\n// getUserInfo 获取用户信息，优先从Redis获取，失败则从数据库获取并缓存\nfunc getUserInfo(phone string) (model.User, error) {\n\tuserPhoneKey := utils.MakeUserPhoneKey(phone)\n\n\t// 从Redis获取用户ID\n\tuserIdByte, err := utils.GetValueFromRedis(userPhoneKey)\n\tvar userId int64\n\tjson.Unmarshal(userIdByte, &userId)\n\tif err != nil && !errors.Is(err, redis.Nil) {\n\t\tglobal.Logger.Errorf(\"从Redis获取用户ID失败: userPhoneKey=%s, err=%v\", userPhoneKey, err)\n\t\treturn model.User{}, err\n\t}\n\n\t// 如果Redis中有用户ID，尝试获取用户信息\n\tif userId != 0 {\n\t\tuser, err := logic.GetUserInfo2Redis(fmt.Sprintf(\"%d\", userId))\n\t\tif err == nil {\n\t\t\treturn user, nil\n\t\t}\n\t\t// Redis中用户信息不存在或出错，继续从数据库获取\n\t\tglobal.Logger.Infof(\"从Redis获取用户信息失败，尝试从数据库获取: userId=%s, err=%v\", userId, err)\n\t}\n\n\t// 从数据库获取用户信息\n\tuser, err := logic.GetUserInfo2DB(phone)\n\tif err != nil {\n\t\tglobal.Logger.Errorf(\"从数据库获取用户信息失败: phone=%s, err=%v\", phone, err)\n\t\treturn model.User{}, err\n\t}\n\n\t// 将用户信息缓存到Redis\n\tif err := logic.SetUserInfo2Redis(user); err != nil {\n\t\tglobal.Logger.Errorf(\"缓存用户信息到Redis失败: userId=%d, err=%v\", user.ID, err)\n\t}\n\n\t// 缓存用户ID映射\n\tif err := utils.SetValueToRedis(userPhoneKey, fmt.Sprintf(\"%d\", user.ID), domainEnum.NotExpireTime); err != nil {\n\t\tglobal.Logger.Errorf(\"缓存用户ID映射失败: userId=%d, err=%v\", user.ID, err)\n\t}\n\n\treturn user, nil\n}\n\n// CancelService 注销账户\nfunc CancelService(ctx *gin.Context, req req.UserCancelReq) (pkgResp.ResponseData, error) {\n\t// 获取用户信息\n\tuserId, exists := ctx.Get(\"uid\")\n\tif !exists {\n\t\tglobal.Logger.Errorf(\"用户id不存在\")\n\t\treturn pkgResp.ErrorResponseData(\"系统繁忙，请稍后再试~\"), errors.New(\"Business Error\")\n\t}\n\n\tuserIdNum, ok := userId.(int64)\n\tuserIdStr := fmt.Sprintf(\"%d\", userIdNum)\n\n\tif !ok {\n\t\tglobal.Logger.Errorf(\"获取用户ID失败: userId=%v\", userId)\n\t\treturn pkgResp.ErrorResponseData(\"系统繁忙，请稍后再试~\"), errors.New(\"Business Error\")\n\t}\n\n\tuserInfo, err := logic.GetUserInfo2DBById(ctx, userIdStr)\n\tif err != nil {\n\t\tglobal.Logger.Errorf(\"获取用户信息失败: userId=%s, err=%v\", userId, err)\n\t\treturn pkgResp.ErrorResponseData(\"系统繁忙，请稍后再试~\"), errors.New(\"Business Error\")\n\t}\n\n\tphone := userInfo.Phone\n\tcaptcha := req.Captcha\n\n\t// 检查验证码是否正确\n\tif !logic.CheckCaptchaProcess(phone, captcha) {\n\t\tglobal.Logger.Infof(\"验证码错误: phone=%s\", phone)\n\t\treturn pkgResp.ErrorResponseData(\"验证码错误\"), errors.New(\"验证码错误\")\n\t}\n\n\t// 检查用户是否存在\n\tuserExists, err := checkUserExists(ctx, phone)\n\tif err != nil {\n\t\tglobal.Logger.Errorf(\"检查用户是否存在失败: phone=%s, err=%v\", phone, err)\n\t\treturn pkgResp.ErrorResponseData(\"系统繁忙，请稍后再试~\"), errors.New(\"Business Error\")\n\t}\n\tif !userExists {\n\t\tglobal.Logger.Errorf(\"用户不存在: phone=%s\", phone)\n\t\treturn pkgResp.ErrorResponseData(\"用户不存在\"), errors.New(\"Business Error\")\n\t}\n\n\t// 删除用户缓存\n\tif err := logic.DeleteUserInfoFromRedis(userIdStr); err != nil {\n\t\tglobal.Logger.Errorf(\"删除用户缓存失败: phone=%s, err=%v\", phone, err)\n\t\treturn pkgResp.ErrorResponseData(\"系统繁忙，请稍后再试~\"), errors.New(\"Business Error\")\n\t}\n\n\t// 删除用户ID映射\n\tuserPhoneKey := utils.MakeUserPhoneKey(phone)\n\tif err := utils.DeleteValueFromRedis(userPhoneKey); err != nil {\n\t\tglobal.Logger.Errorf(\"删除用户ID映射失败: phone=%s, err=%v\", phone, err)\n\t\treturn pkgResp.ErrorResponseData(\"系统繁忙，请稍后再试~\"), errors.New(\"Business Error\")\n\t}\n\n\t// 删除用户数据库\n\tif err := logic.DeleteUserInfoFromDB(ctx, userIdStr); err != nil {\n\t\tglobal.Logger.Errorf(\"删除用户数据库失败: phone=%s, err=%v\", phone, err)\n\t\treturn pkgResp.ErrorResponseData(\"系统繁忙，请稍后再试~\"), errors.New(\"Business Error\")\n\t}\n\n\treturn pkgResp.SuccessResponseDataWithMsg(\"注销成功\"), nil\n}\n\n//\n//func GetUserInfoByNameService(uid int64, name string) (pkgResp.ResponseData, error) {\n//\tctx := context.Background()\n//\tuser := global.Query.User\n//\tuserQ := user.WithContext(ctx)\n//\tuserRList, err := userQ.Where(user.Name.Like(name + \"%\")).Limit(5).Find()\n//\tif err != nil {\n//\t\tglobal.Logger.Errorf(\"查询用户数据失败: %v\", err)\n//\t\treturn pkgResp.ErrorResponseData(\"系统繁忙，请稍后再试~\"), errors.New(\"Business Error\")\n//\t}\n//\tuidList := make([]int64, 0)\n//\tfor _, userR := range userRList {\n//\t\tuidList = append(uidList, userR.ID)\n//\t}\n//\t//\t搜索好友关系\n//\tuserApply := global.Query.UserApply\n//\tuserApplyQ := userApply.WithContext(ctx)\n//\tapplyList, err := userApplyQ.Where(userApply.UID.Eq(uid), userApply.TargetID.In(uidList...)).Find()\n//\tif err != nil {\n//\t\tglobal.Logger.Errorf(\"查询好友关系失败: %v\", err)\n//\t\treturn pkgResp.ErrorResponseData(\"系统繁忙，请稍后再试~\"), errors.New(\"Business Error\")\n//\t}\n//\t//\t查询好友关系\n//\tuserFriend := global.Query.UserFriend\n//\tuserFriendQ := userFriend.WithContext(ctx)\n//\tfriendList, err := userFriendQ.Where(userFriend.UID.Eq(uid), userFriend.FriendUID.In(uidList...)).Find()\n//\tif err != nil {\n//\t\tglobal.Logger.Errorf(\"查询好友关系失败: %v\", err)\n//\t\treturn pkgResp.ErrorResponseData(\"系统繁忙，请稍后再试~\"), errors.New(\"Business Error\")\n//\t}\n//\tuserRespList := adapter.BuildUserInfoByNameResp(userRList, applyList, friendList)\n//\treturn pkgResp.SuccessResponseData(userRespList), nil\n//}\n"
  },
  {
    "path": "service/user_service_integration_test.go",
    "content": "package service\n\nimport (\n\t\"DiTing-Go/domain/enum\"\n\t\"DiTing-Go/domain/vo/req\"\n\t\"DiTing-Go/logic\"\n\t\"DiTing-Go/utils\"\n\t\"context\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\n// TestUserRegistrationFlow 测试用户注册流程\nfunc TestUserRegistrationFlow(t *testing.T) {\n\t// 测试数据\n\ttestPhone := \"13800138001\"\n\ttestUsername := \"testuser1\"\n\ttestPassword := \"123456\"\n\ttestCaptcha := \"123456\"\n\n\t// 设置验证码\n\tsetupCaptcha(testPhone, testCaptcha)\n\n\t// 执行注册\n\tregisterReq := req.UserRegisterReq{\n\t\tUsername: testUsername,\n\t\tPassword: testPassword,\n\t\tPhone:    testPhone,\n\t\tCaptcha:  testCaptcha,\n\t}\n\n\tresp, err := RegisterService(registerReq)\n\tassert.NoError(t, err)\n\tassert.True(t, resp.Success)\n\tassert.Contains(t, resp.Message, \"注册成功\")\n\n\t// 验证用户是否真的被创建\n\tuserExists, err := logic.CheckPhoneInDB(context.Background(), testPhone)\n\tassert.NoError(t, err)\n\tassert.True(t, userExists)\n\n\t// 清理测试数据\n\tcleanupUser(testPhone)\n}\n\n// TestUserLoginFlow 测试用户登录流程\nfunc TestUserLoginFlow(t *testing.T) {\n\t// 先注册一个用户\n\ttestPhone := \"13800138002\"\n\ttestUsername := \"testuser2\"\n\ttestPassword := \"123456\"\n\ttestCaptcha := \"123456\"\n\n\tsetupCaptcha(testPhone, testCaptcha)\n\tregisterReq := req.UserRegisterReq{\n\t\tUsername: testUsername,\n\t\tPassword: testPassword,\n\t\tPhone:    testPhone,\n\t\tCaptcha:  testCaptcha,\n\t}\n\n\tregisterResp, err := RegisterService(registerReq)\n\tassert.NoError(t, err)\n\tassert.True(t, registerResp.Success)\n\n\t// 测试密码登录\n\tloginReq := req.UserLoginReq{\n\t\tPhone:     testPhone,\n\t\tPassword:  testPassword,\n\t\tLoginType: enum.LoginByPassword,\n\t}\n\n\tloginResp, err := LoginService(loginReq)\n\tassert.NoError(t, err)\n\tassert.True(t, loginResp.Success)\n\n\t// 验证登录响应数据结构\n\tloginData, ok := loginResp.Data.(map[string]interface{})\n\tassert.True(t, ok)\n\tassert.NotEmpty(t, loginData[\"token\"])\n\tassert.NotZero(t, loginData[\"uid\"])\n\tassert.Equal(t, testUsername, loginData[\"name\"])\n\n\t// 清理测试数据\n\tcleanupUser(testPhone)\n}\n\n// TestUserCancelFlow 测试用户注销流程\nfunc TestUserCancelFlow(t *testing.T) {\n\t// 先注册一个用户\n\ttestPhone := \"13800138003\"\n\ttestUsername := \"testuser3\"\n\ttestPassword := \"123456\"\n\ttestCaptcha := \"123456\"\n\n\tsetupCaptcha(testPhone, testCaptcha)\n\tregisterReq := req.UserRegisterReq{\n\t\tUsername: testUsername,\n\t\tPassword: testPassword,\n\t\tPhone:    testPhone,\n\t\tCaptcha:  testCaptcha,\n\t}\n\n\tregisterResp, err := RegisterService(registerReq)\n\tassert.NoError(t, err)\n\tassert.True(t, registerResp.Success)\n\n\t// 设置注销验证码\n\tsetupCaptcha(testPhone, testCaptcha)\n\n\t// 执行注销\n\tctx := &gin.Context{}\n\tctx.Set(\"uid\", int64(12345))\n\n\tcancelReq := req.UserCancelReq{\n\t\tCaptcha: testCaptcha,\n\t}\n\n\tcancelResp, err := CancelService(ctx, cancelReq)\n\tassert.NoError(t, err)\n\tassert.True(t, cancelResp.Success)\n\tassert.Contains(t, cancelResp.Message, \"注销成功\")\n\n\t// 验证用户是否真的被删除\n\tuserExists, err := logic.CheckPhoneInDB(context.Background(), testPhone)\n\tassert.NoError(t, err)\n\tassert.False(t, userExists)\n}\n\n// TestDuplicateRegistration 测试重复注册\nfunc TestDuplicateRegistration(t *testing.T) {\n\ttestPhone := \"13800138004\"\n\ttestUsername := \"testuser4\"\n\ttestPassword := \"123456\"\n\ttestCaptcha := \"123456\"\n\n\t// 第一次注册\n\tsetupCaptcha(testPhone, testCaptcha)\n\tregisterReq := req.UserRegisterReq{\n\t\tUsername: testUsername,\n\t\tPassword: testPassword,\n\t\tPhone:    testPhone,\n\t\tCaptcha:  testCaptcha,\n\t}\n\n\tresp1, err := RegisterService(registerReq)\n\tassert.NoError(t, err)\n\tassert.True(t, resp1.Success)\n\n\t// 第二次注册相同手机号\n\tsetupCaptcha(testPhone, testCaptcha)\n\tresp2, err := RegisterService(registerReq)\n\tassert.NoError(t, err)\n\tassert.False(t, resp2.Success)\n\tassert.Contains(t, resp2.Message, \"手机号已存在\")\n\n\t// 清理测试数据\n\tcleanupUser(testPhone)\n}\n\n// TestLoginWithWrongCredentials 测试错误凭据登录\nfunc TestLoginWithWrongCredentials(t *testing.T) {\n\t// 先注册一个用户\n\ttestPhone := \"13800138005\"\n\ttestUsername := \"testuser5\"\n\ttestPassword := \"123456\"\n\ttestCaptcha := \"123456\"\n\n\tsetupCaptcha(testPhone, testCaptcha)\n\tregisterReq := req.UserRegisterReq{\n\t\tUsername: testUsername,\n\t\tPassword: testPassword,\n\t\tPhone:    testPhone,\n\t\tCaptcha:  testCaptcha,\n\t}\n\n\tresp, err := RegisterService(registerReq)\n\tassert.NoError(t, err)\n\tassert.True(t, resp.Success)\n\n\t// 测试错误密码\n\twrongPasswordReq := req.UserLoginReq{\n\t\tPhone:     testPhone,\n\t\tPassword:  \"wrongpassword\",\n\t\tLoginType: enum.LoginByPassword,\n\t}\n\n\tresp2, err := LoginService(wrongPasswordReq)\n\tassert.NoError(t, err)\n\tassert.False(t, resp2.Success)\n\tassert.Contains(t, resp2.Message, \"用户名或密码错误\")\n\n\t// 测试错误验证码\n\twrongCaptchaReq := req.UserLoginReq{\n\t\tPhone:     testPhone,\n\t\tCaptcha:   \"999999\",\n\t\tLoginType: enum.LoginByPhoneCaptcha,\n\t}\n\n\tresp3, err := LoginService(wrongCaptchaReq)\n\tassert.NoError(t, err)\n\tassert.False(t, resp3.Success)\n\tassert.Contains(t, resp3.Message, \"验证码错误\")\n\n\t// 清理测试数据\n\tcleanupUser(testPhone)\n}\n\n// TestCancelWithWrongCaptcha 测试错误验证码注销\nfunc TestCancelWithWrongCaptcha(t *testing.T) {\n\t// 先注册一个用户\n\ttestPhone := \"13800138006\"\n\ttestUsername := \"testuser6\"\n\ttestPassword := \"123456\"\n\ttestCaptcha := \"123456\"\n\n\tsetupCaptcha(testPhone, testCaptcha)\n\tregisterReq := req.UserRegisterReq{\n\t\tUsername: testUsername,\n\t\tPassword: testPassword,\n\t\tPhone:    testPhone,\n\t\tCaptcha:  testCaptcha,\n\t}\n\n\tresp, err := RegisterService(registerReq)\n\tassert.NoError(t, err)\n\tassert.True(t, resp.Success)\n\n\t// 测试错误验证码注销\n\tctx := &gin.Context{}\n\tctx.Set(\"uid\", int64(12345))\n\n\twrongCaptchaReq := req.UserCancelReq{\n\t\tCaptcha: \"999999\",\n\t}\n\n\tresp2, err := CancelService(ctx, wrongCaptchaReq)\n\tassert.NoError(t, err)\n\tassert.False(t, resp2.Success)\n\tassert.Contains(t, resp2.Message, \"验证码错误\")\n\n\t// 清理测试数据\n\tcleanupUser(testPhone)\n}\n\n// TestUserRegistrationWithValidData 测试有效数据注册\nfunc TestUserRegistrationWithValidData(t *testing.T) {\n\ttestCases := []struct {\n\t\tname     string\n\t\tusername string\n\t\tpassword string\n\t\tphone    string\n\t\tcaptcha  string\n\t}{\n\t\t{\n\t\t\tname:     \"标准注册\",\n\t\t\tusername: \"testuser7\",\n\t\t\tpassword: \"123456\",\n\t\t\tphone:    \"13800138007\",\n\t\t\tcaptcha:  \"123456\",\n\t\t},\n\t\t{\n\t\t\tname:     \"长用户名\",\n\t\t\tusername: \"verylongusername123\",\n\t\t\tpassword: \"123456\",\n\t\t\tphone:    \"13800138008\",\n\t\t\tcaptcha:  \"123456\",\n\t\t},\n\t\t{\n\t\t\tname:     \"复杂密码\",\n\t\t\tusername: \"testuser9\",\n\t\t\tpassword: \"P@ssw0rd123\",\n\t\t\tphone:    \"13800138009\",\n\t\t\tcaptcha:  \"123456\",\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tregisterReq := req.UserRegisterReq{\n\t\t\t\tUsername: tc.username,\n\t\t\t\tPassword: tc.password,\n\t\t\t\tPhone:    tc.phone,\n\t\t\t\tCaptcha:  tc.captcha,\n\t\t\t}\n\n\t\t\tsetupCaptcha(tc.phone, tc.captcha)\n\t\t\tresp, err := RegisterService(registerReq)\n\t\t\tassert.NoError(t, err)\n\t\t\tassert.True(t, resp.Success)\n\n\t\t\t// 清理测试数据\n\t\t\tcleanupUser(tc.phone)\n\t\t})\n\t}\n}\n\n// setupCaptcha 设置验证码\nfunc setupCaptcha(phone, captcha string) {\n\tcaptchaKey := utils.MakeUserCaptchaKey(phone)\n\terr := utils.SetValueToRedis(captchaKey, captcha, 5*time.Minute)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// cleanupUser 清理用户数据\nfunc cleanupUser(phone string) {\n\t// 这里应该实现清理用户数据的逻辑\n\t// 由于涉及到数据库操作，这里只是占位符\n\t// 实际实现时需要根据具体的数据库操作来清理\n}\n"
  },
  {
    "path": "service/user_service_test.go",
    "content": "package service\n\nimport (\n\t\"DiTing-Go/domain/enum\"\n\t\"DiTing-Go/domain/vo/req\"\n\t\"testing\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\n// TestUserLifecycleFlow 测试用户完整生命周期流程\nfunc TestUserLifecycleFlow(t *testing.T) {\n\t// 设置测试环境\n\tgin.SetMode(gin.TestMode)\n\tctx := &gin.Context{}\n\tctx.Set(\"uid\", int64(12345))\n\n\t// 测试数据\n\ttestPhone := \"13800138000\"\n\ttestUsername := \"testuser\"\n\ttestPassword := \"123456\"\n\ttestCaptcha := \"123456\"\n\n\t// 步骤1: 用户注册\n\tt.Log(\"=== 步骤1: 用户注册测试 ===\")\n\tregisterReq := req.UserRegisterReq{\n\t\tUsername: testUsername,\n\t\tPassword: testPassword,\n\t\tPhone:    testPhone,\n\t\tCaptcha:  testCaptcha,\n\t}\n\n\t// 设置验证码\n\tsetupCaptcha(testPhone, testCaptcha)\n\tregisterResp, err := RegisterService(registerReq)\n\tassert.NoError(t, err)\n\tassert.True(t, registerResp.Success)\n\n\t// 步骤2: 用户登录（密码登录）\n\tt.Log(\"=== 步骤2: 用户密码登录测试 ===\")\n\tloginReq := req.UserLoginReq{\n\t\tPhone:     testPhone,\n\t\tPassword:  testPassword,\n\t\tLoginType: enum.LoginByPassword,\n\t}\n\n\tloginResp, err := LoginService(loginReq)\n\tassert.NoError(t, err)\n\tassert.True(t, loginResp.Success)\n\n\t// 步骤3: 用户登录（验证码登录）\n\tt.Log(\"=== 步骤3: 用户验证码登录测试 ===\")\n\tloginByCaptchaReq := req.UserLoginReq{\n\t\tPhone:     testPhone,\n\t\tCaptcha:   testCaptcha,\n\t\tLoginType: enum.LoginByPhoneCaptcha,\n\t}\n\n\tsetupCaptcha(testPhone, testCaptcha)\n\tloginByCaptchaResp, err := LoginService(loginByCaptchaReq)\n\tassert.NoError(t, err)\n\tassert.True(t, loginByCaptchaResp.Success)\n\n\t// 步骤4: 用户注销\n\tt.Log(\"=== 步骤4: 用户注销测试 ===\")\n\tcancelReq := req.UserCancelReq{\n\t\tCaptcha: testCaptcha,\n\t}\n\n\tsetupCaptcha(testPhone, testCaptcha)\n\tcancelResp, err := CancelService(ctx, cancelReq)\n\tassert.NoError(t, err)\n\tassert.True(t, cancelResp.Success)\n}\n\n// TestRegisterValidation 测试注册参数验证\nfunc TestRegisterValidation(t *testing.T) {\n\ttestCases := []struct {\n\t\tname     string\n\t\treq      req.UserRegisterReq\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tname: \"用户名为空\",\n\t\t\treq: req.UserRegisterReq{\n\t\t\t\tUsername: \"\",\n\t\t\t\tPassword: \"123456\",\n\t\t\t\tPhone:    \"13800138001\",\n\t\t\t\tCaptcha:  \"123456\",\n\t\t\t},\n\t\t\texpected: \"用户名、密码和手机号不能为空\",\n\t\t},\n\t\t{\n\t\t\tname: \"密码长度不足\",\n\t\t\treq: req.UserRegisterReq{\n\t\t\t\tUsername: \"testuser\",\n\t\t\t\tPassword: \"123\",\n\t\t\t\tPhone:    \"13800138001\",\n\t\t\t\tCaptcha:  \"123456\",\n\t\t\t},\n\t\t\texpected: \"密码长度不能少于6位\",\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tresp, err := RegisterService(tc.req)\n\t\t\tassert.NoError(t, err)\n\t\t\tassert.False(t, resp.Success)\n\t\t\tassert.Contains(t, resp.Message, tc.expected)\n\t\t})\n\t}\n}\n\n// TestLoginValidation 测试登录参数验证\nfunc TestLoginValidation(t *testing.T) {\n\ttestCases := []struct {\n\t\tname     string\n\t\treq      req.UserLoginReq\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tname: \"密码登录-手机号为空\",\n\t\t\treq: req.UserLoginReq{\n\t\t\t\tPhone:     \"\",\n\t\t\t\tPassword:  \"123456\",\n\t\t\t\tLoginType: enum.LoginByPassword,\n\t\t\t},\n\t\t\texpected: \"用户名和密码不能为空\",\n\t\t},\n\t\t{\n\t\t\tname: \"验证码登录-手机号为空\",\n\t\t\treq: req.UserLoginReq{\n\t\t\t\tPhone:     \"\",\n\t\t\t\tCaptcha:   \"123456\",\n\t\t\t\tLoginType: enum.LoginByPhoneCaptcha,\n\t\t\t},\n\t\t\texpected: \"手机号和验证码不能为空\",\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tresp, err := LoginService(tc.req)\n\t\t\tassert.NoError(t, err)\n\t\t\tassert.False(t, resp.Success)\n\t\t\tassert.Contains(t, resp.Message, tc.expected)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "sql/sql.sql",
    "content": "-- auto-generated definition\ncreate table contact\n(\n    id          bigint unsigned auto_increment comment 'id'\n        primary key,\n    uid         bigint                                   not null comment 'uid',\n    room_id     bigint                                   not null comment '房间id',\n    read_time   datetime(3) default CURRENT_TIMESTAMP(3) not null comment '阅读到的时间',\n    active_time datetime(3)                              null comment '会话内消息最后更新的时间(只有普通会话需要维护，全员会话不需要维护)',\n    last_msg_id bigint                                   null comment '会话最新消息id',\n    create_time datetime(3) default CURRENT_TIMESTAMP(3) not null comment '创建时间',\n    update_time datetime(3) default CURRENT_TIMESTAMP(3) not null on update CURRENT_TIMESTAMP(3) comment '修改时间',\n    constraint uniq_uid_room_id\n        unique (uid, room_id)\n)\n    comment '会话列表' collate = utf8mb4_unicode_ci;\n\ncreate index idx_create_time\n    on contact (create_time);\n\ncreate index idx_room_id_read_time\n    on contact (room_id, read_time);\n\ncreate index idx_update_time\n    on contact (update_time);\n\ncreate index idx_user_id_active_time\n    on contact (uid, active_time);\n\n-- auto-generated definition\ncreate table group_member\n(\n    id          bigint unsigned auto_increment comment 'id'\n        primary key,\n    group_id    bigint                                   not null comment '群主id',\n    uid         bigint                                   not null comment '成员uid',\n    role        int                                      not null comment '成员角色 1群主 2管理员 3普通成员',\n    create_time datetime(3) default CURRENT_TIMESTAMP(3) not null comment '创建时间',\n    update_time datetime(3) default CURRENT_TIMESTAMP(3) not null on update CURRENT_TIMESTAMP(3) comment '修改时间'\n)\n    comment '群成员表' collate = utf8mb4_unicode_ci;\n\ncreate index idx_create_time\n    on group_member (create_time);\n\ncreate index idx_group_id_role\n    on group_member (group_id, role);\n\ncreate index idx_update_time\n    on group_member (update_time);\n\n-- auto-generated definition\ncreate table message\n(\n    id           bigint unsigned auto_increment comment 'id'\n        primary key,\n    room_id      bigint                                   not null comment '会话表id',\n    from_uid     bigint                                   not null comment '消息发送者uid',\n    content      varchar(1024)                            null comment '消息内容',\n    reply_msg_id bigint                                   null comment '回复的消息内容',\n    status       int                                      not null comment '消息状态 0正常 1删除',\n    gap_count    int                                      null comment '与回复的消息间隔多少条',\n    type         int         default 1                    null comment '消息类型 1正常文本 2.撤回消息',\n    extra        json                                     null comment '扩展信息',\n    create_time  datetime(3) default CURRENT_TIMESTAMP(3) not null comment '创建时间',\n    update_time  datetime(3) default CURRENT_TIMESTAMP(3) not null on update CURRENT_TIMESTAMP(3) comment '修改时间'\n)\n    comment '消息表' collate = utf8mb4_unicode_ci\n                     row_format = DYNAMIC;\n\ncreate index idx_create_time\n    on message (create_time);\n\ncreate index idx_from_uid\n    on message (from_uid);\n\ncreate index idx_room_id\n    on message (room_id);\n\ncreate index idx_update_time\n    on message (update_time);\n\n-- auto-generated definition\ncreate table room\n(\n    id          bigint unsigned auto_increment comment 'id'\n        primary key,\n    type        int                                      not null comment '房间类型 1群聊 2单聊',\n    hot_flag    int         default 0                    null comment '是否全员展示 0否 1是',\n    active_time datetime(3) default CURRENT_TIMESTAMP(3) not null comment '群最后消息的更新时间（热点群不需要写扩散，只更新这里）',\n    last_msg_id bigint                                   null comment '会话中的最后一条消息id',\n    ext_json    json                                     null comment '额外信息（根据不同类型房间有不同存储的东西）',\n    create_time datetime(3) default CURRENT_TIMESTAMP(3) not null comment '创建时间',\n    update_time datetime(3) default CURRENT_TIMESTAMP(3) not null on update CURRENT_TIMESTAMP(3) comment '修改时间'\n)\n    comment '房间表' collate = utf8mb4_unicode_ci;\n\ncreate index idx_create_time\n    on room (create_time);\n\ncreate index idx_update_time\n    on room (update_time);\n\n-- auto-generated definition\ncreate table room_friend\n(\n    id          bigint unsigned auto_increment comment 'id'\n        primary key,\n    room_id     bigint                                   not null comment '房间id',\n    uid1        bigint                                   not null comment 'uid1（更小的uid）',\n    uid2        bigint                                   not null comment 'uid2（更大的uid）',\n    room_key    varchar(64)                              not null comment '房间key由两个uid拼接，先做排序uid1_uid2',\n    status      int                                      not null comment '房间状态 0正常 1禁用(删好友了禁用)',\n    create_time datetime(3) default CURRENT_TIMESTAMP(3) not null comment '创建时间',\n    update_time datetime(3) default CURRENT_TIMESTAMP(3) not null on update CURRENT_TIMESTAMP(3) comment '修改时间',\n    constraint room_key\n        unique (room_key)\n)\n    comment '单聊房间表' collate = utf8mb4_unicode_ci;\n\ncreate index idx_create_time\n    on room_friend (create_time);\n\ncreate index idx_room_id\n    on room_friend (room_id);\n\ncreate index idx_update_time\n    on room_friend (update_time);\n\n-- auto-generated definition\ncreate table room_group\n(\n    id            bigint unsigned auto_increment comment 'id'\n        primary key,\n    room_id       bigint                                   not null comment '房间id',\n    name          varchar(16)                              not null comment '群名称',\n    avatar        varchar(256)                             not null comment '群头像',\n    ext_json      json                                     null comment '额外信息（根据不同类型房间有不同存储的东西）',\n    delete_status int(1)      default 0                    not null comment '逻辑删除(0-正常,1-删除)',\n    create_time   datetime(3) default CURRENT_TIMESTAMP(3) not null comment '创建时间',\n    update_time   datetime(3) default CURRENT_TIMESTAMP(3) not null on update CURRENT_TIMESTAMP(3) comment '修改时间'\n)\n    comment '群聊房间表' collate = utf8mb4_unicode_ci;\n\ncreate index idx_create_time\n    on room_group (create_time);\n\ncreate index idx_room_id\n    on room_group (room_id);\n\ncreate index idx_update_time\n    on room_group (update_time);\n\n-- auto-generated definition\ncreate table user_friend\n(\n    id            bigint unsigned auto_increment comment 'id'\n        primary key,\n    uid           bigint                                   not null comment 'uid',\n    friend_uid    bigint                                   not null comment '好友uid',\n    delete_status int(1)      default 0                    not null comment '逻辑删除(0-正常,1-删除)',\n    create_time   datetime(3) default CURRENT_TIMESTAMP(3) not null comment '创建时间',\n    update_time   datetime(3) default CURRENT_TIMESTAMP(3) not null on update CURRENT_TIMESTAMP(3) comment '修改时间',\n    constraint uid\n        unique (uid, friend_uid)\n)\n    comment '用户联系人表' collate = utf8mb4_unicode_ci;\n\ncreate index idx_create_time\n    on user_friend (create_time);\n\ncreate index idx_uid_friend_uid\n    on user_friend (uid, friend_uid);\n\ncreate index idx_update_time\n    on user_friend (update_time);\n\n-- auto-generated definition\ncreate table user_apply\n(\n    id          bigint unsigned auto_increment comment 'id'\n        primary key,\n    uid         bigint                                   not null comment '申请人uid',\n    type        int                                      not null comment '申请类型 1加好友',\n    target_id   bigint                                   not null comment '接收人uid',\n    msg         varchar(64)                              not null comment '申请信息',\n    status      int                                      not null comment '申请状态 1待审批 2同意',\n    read_status int                                      not null comment '阅读状态 1未读 2已读',\n    create_time datetime(3) default CURRENT_TIMESTAMP(3) not null comment '创建时间',\n    update_time datetime(3) default CURRENT_TIMESTAMP(3) not null on update CURRENT_TIMESTAMP(3) comment '修改时间',\n    constraint uid\n        unique (uid, target_id)\n)\n    comment '用户申请表' collate = utf8mb4_unicode_ci;\n\ncreate index idx_create_time\n    on user_apply (create_time);\n\ncreate index idx_target_id\n    on user_apply (target_id);\n\ncreate index idx_target_id_read_status\n    on user_apply (target_id, read_status);\n\ncreate index idx_uid_target_id\n    on user_apply (uid, target_id);\n\ncreate index idx_update_time\n    on user_apply (update_time);\n\n"
  },
  {
    "path": "tests/README.md",
    "content": "# DiTing-Go 测试文件夹结构\n\n## 概述\n\n本文件夹包含了DiTing-Go项目的所有测试文件，按照测试类型和功能模块进行了分类组织。\n\n## 文件夹结构\n\n```\ntests/\n├── unit/                    # 单元测试\n├── integration/            # 集成测试\n├── e2e/                   # 端到端测试\n├── performance/           # 性能测试\n├── scripts/               # 测试脚本\n├── config/                # 测试配置文件\n└── README.md              # 本文件\n```\n\n## 测试类型说明\n\n### 1. 单元测试 (unit/)\n- **目的**: 测试单个函数或方法的正确性\n- **特点**: 快速、独立、可重复\n- **覆盖范围**: 业务逻辑、工具函数、数据验证等\n\n### 2. 集成测试 (integration/)\n- **目的**: 测试多个组件之间的协作\n- **特点**: 涉及数据库、缓存、外部服务\n- **覆盖范围**: 服务层、数据访问层、缓存层\n\n### 3. 端到端测试 (e2e/)\n- **目的**: 测试完整的用户业务流程\n- **特点**: 模拟真实用户操作\n- **覆盖范围**: API接口、WebSocket、完整业务流程\n\n### 4. 性能测试 (performance/)\n- **目的**: 测试系统性能和稳定性\n- **特点**: 高并发、大数据量、长时间运行\n- **覆盖范围**: 负载测试、压力测试、基准测试\n\n## 运行测试\n\n### 运行所有测试\n```bash\ngo test ./tests/...\n```\n\n### 运行特定类型测试\n```bash\n# 运行单元测试\ngo test ./tests/unit/...\n\n# 运行集成测试\ngo test ./tests/integration/...\n\n# 运行端到端测试\ngo test ./tests/e2e/...\n\n# 运行性能测试\ngo test ./tests/performance/...\n```\n\n### 使用测试脚本\n```bash\n# Linux/Mac\n./tests/scripts/run_tests.sh\n\n# Windows\ntests\\scripts\\run_tests.bat\n```\n\n## 测试配置\n\n### 环境变量\n```bash\n# 测试环境\nexport GIN_MODE=test\nexport TEST_ENV=test\n\n# 数据库配置\nexport TEST_DB_HOST=localhost\nexport TEST_DB_PORT=3306\nexport TEST_DB_NAME=diting_test\n\n# Redis配置\nexport TEST_REDIS_HOST=localhost\nexport TEST_REDIS_PORT=6379\nexport TEST_REDIS_DB=1\n```\n\n## 最佳实践\n\n### 1. 测试编写\n- 每个测试函数只测试一个功能点\n- 使用描述性的测试函数名\n- 包含正向和异常测试用例\n\n### 2. 测试数据\n- 使用固定的测试数据确保可重复性\n- 避免测试之间的数据依赖\n- 及时清理测试数据\n\n### 3. 测试维护\n- 定期更新测试用例\n- 保持测试代码的可读性\n- 及时修复失败的测试 "
  },
  {
    "path": "tests/e2e/user_workflow_e2e_test.go",
    "content": "package e2e\n\nimport (\n\t\"DiTing-Go/domain/enum\"\n\t\"DiTing-Go/domain/vo/req\"\n\t\"DiTing-Go/global\"\n\t\"DiTing-Go/service\"\n\t\"DiTing-Go/utils\"\n\t\"DiTing-Go/utils/setting\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\n// 初始化函数，在包加载时执行\nfunc init() {\n\t// 设置测试环境变量\n\tgin.SetMode(gin.TestMode)\n\n\t// 初始化配置\n\tsetting.ConfigInit()\n\n\t// 初始化简单的测试日志\n\tglobal.Logger = logrus.New()\n\tglobal.Logger.SetOutput(gin.DefaultWriter)\n\tglobal.Logger.SetLevel(logrus.InfoLevel)\n\n\t// 初始化Redis\n\tglobal.RedisInit()\n\n\t// 初始化数据库\n\tglobal.DBInit()\n}\n\n// TestUserCompleteWorkflow 测试用户完整工作流\nfunc TestUserCompleteWorkflow(t *testing.T) {\n\t// 设置测试环境\n\tgin.SetMode(gin.TestMode)\n\n\t// 测试数据\n\ttestPhone := \"13800138100\"\n\ttestUsername := \"e2euser\"\n\ttestPassword := \"123456\"\n\ttestCaptcha := \"123456\"\n\n\t// 步骤1: 用户注册\n\tt.Log(\"=== E2E测试: 用户注册 ===\")\n\tsetupCaptcha(testPhone, testCaptcha)\n\tregisterReq := req.UserRegisterReq{\n\t\tUsername: testUsername,\n\t\tPassword: testPassword,\n\t\tPhone:    testPhone,\n\t\tCaptcha:  testCaptcha,\n\t}\n\n\tregisterResp, err := service.RegisterService(registerReq)\n\tassert.NoError(t, err)\n\tassert.True(t, registerResp.Success)\n\tt.Log(\"✅ 用户注册成功\")\n\n\t// 步骤2: 用户登录（密码登录）\n\tt.Log(\"=== E2E测试: 用户密码登录 ===\")\n\tloginReq := req.UserLoginReq{\n\t\tPhone:     testPhone,\n\t\tPassword:  testPassword,\n\t\tLoginType: enum.LoginByPassword,\n\t}\n\n\tloginResp, err := service.LoginService(loginReq)\n\tassert.NoError(t, err)\n\tassert.True(t, loginResp.Success)\n\n\t// 验证登录响应\n\tloginData, ok := loginResp.Data.(map[string]interface{})\n\tassert.True(t, ok)\n\tassert.NotEmpty(t, loginData[\"token\"])\n\tassert.NotZero(t, loginData[\"uid\"])\n\tassert.Equal(t, testUsername, loginData[\"name\"])\n\tt.Logf(\"✅ 用户登录成功: uid=%v, name=%s\", loginData[\"uid\"], loginData[\"name\"])\n\n\t// 步骤3: 用户登录（验证码登录）\n\tt.Log(\"=== E2E测试: 用户验证码登录 ===\")\n\tsetupCaptcha(testPhone, testCaptcha)\n\tloginByCaptchaReq := req.UserLoginReq{\n\t\tPhone:     testPhone,\n\t\tCaptcha:   testCaptcha,\n\t\tLoginType: enum.LoginByPhoneCaptcha,\n\t}\n\n\tloginByCaptchaResp, err := service.LoginService(loginByCaptchaReq)\n\tassert.NoError(t, err)\n\tassert.True(t, loginByCaptchaResp.Success)\n\tt.Log(\"✅ 验证码登录成功\")\n\n\t// 步骤4: 用户注销\n\tt.Log(\"=== E2E测试: 用户注销 ===\")\n\tctx := &gin.Context{}\n\tctx.Set(\"uid\", loginData[\"uid\"])\n\n\tsetupCaptcha(testPhone, testCaptcha)\n\tcancelReq := req.UserCancelReq{\n\t\tCaptcha: testCaptcha,\n\t}\n\n\tcancelResp, err := service.CancelService(ctx, cancelReq)\n\tassert.NoError(t, err)\n\tassert.True(t, cancelResp.Success)\n\tt.Log(\"✅ 用户注销成功\")\n\n\t// 步骤5: 验证用户已注销（尝试登录应该失败）\n\tt.Log(\"=== E2E测试: 验证用户已注销 ===\")\n\tloginAfterCancelReq := req.UserLoginReq{\n\t\tPhone:     testPhone,\n\t\tPassword:  testPassword,\n\t\tLoginType: enum.LoginByPassword,\n\t}\n\n\tloginAfterCancelResp, err := service.LoginService(loginAfterCancelReq)\n\tassert.NoError(t, err)\n\tassert.False(t, loginAfterCancelResp.Success)\n\tt.Log(\"✅ 验证用户已注销成功\")\n}\n\n// TestMultipleUserWorkflow 测试多用户并发工作流\nfunc TestMultipleUserWorkflow(t *testing.T) {\n\t// 测试多个用户同时注册和登录\n\tusers := []struct {\n\t\tphone    string\n\t\tusername string\n\t\tpassword string\n\t}{\n\t\t{\"13800138101\", \"user1\", \"123456\"},\n\t\t{\"13800138102\", \"user2\", \"123456\"},\n\t\t{\"13800138103\", \"user3\", \"123456\"},\n\t}\n\n\tfor i, user := range users {\n\t\tt.Run(user.username, func(t *testing.T) {\n\t\t\t// 注册用户\n\t\t\tsetupCaptcha(user.phone, \"123456\")\n\t\t\tregisterReq := req.UserRegisterReq{\n\t\t\t\tUsername: user.username,\n\t\t\t\tPassword: user.password,\n\t\t\t\tPhone:    user.phone,\n\t\t\t\tCaptcha:  \"123456\",\n\t\t\t}\n\n\t\t\tregisterResp, err := service.RegisterService(registerReq)\n\t\t\tassert.NoError(t, err)\n\t\t\tassert.True(t, registerResp.Success)\n\n\t\t\t// 登录用户\n\t\t\tloginReq := req.UserLoginReq{\n\t\t\t\tPhone:     user.phone,\n\t\t\t\tPassword:  user.password,\n\t\t\t\tLoginType: enum.LoginByPassword,\n\t\t\t}\n\n\t\t\tloginResp, err := service.LoginService(loginReq)\n\t\t\tassert.NoError(t, err)\n\t\t\tassert.True(t, loginResp.Success)\n\n\t\t\tt.Logf(\"✅ 用户 %d 注册和登录成功\", i+1)\n\n\t\t\t// 清理测试数据\n\t\t\tcleanupUser(user.phone)\n\t\t})\n\t}\n}\n\n// TestUserErrorScenarios 测试用户错误场景\nfunc TestUserErrorScenarios(t *testing.T) {\n\tt.Run(\"重复注册\", func(t *testing.T) {\n\t\ttestPhone := \"13800138104\"\n\t\ttestUsername := \"erroruser\"\n\t\ttestPassword := \"123456\"\n\n\t\t// 第一次注册\n\t\tsetupCaptcha(testPhone, \"123456\")\n\t\tregisterReq := req.UserRegisterReq{\n\t\t\tUsername: testUsername,\n\t\t\tPassword: testPassword,\n\t\t\tPhone:    testPhone,\n\t\t\tCaptcha:  \"123456\",\n\t\t}\n\n\t\tresp1, err := service.RegisterService(registerReq)\n\t\tassert.NoError(t, err)\n\t\tassert.True(t, resp1.Success)\n\n\t\t// 第二次注册相同手机号\n\t\tsetupCaptcha(testPhone, \"123456\")\n\t\tresp2, err := service.RegisterService(registerReq)\n\t\tassert.NoError(t, err)\n\t\tassert.False(t, resp2.Success)\n\t\tassert.Contains(t, resp2.Message, \"手机号已存在\")\n\n\t\tcleanupUser(testPhone)\n\t})\n\n\tt.Run(\"错误密码登录\", func(t *testing.T) {\n\t\ttestPhone := \"13800138105\"\n\t\ttestUsername := \"erroruser2\"\n\t\ttestPassword := \"123456\"\n\n\t\t// 注册用户\n\t\tsetupCaptcha(testPhone, \"123456\")\n\t\tregisterReq := req.UserRegisterReq{\n\t\t\tUsername: testUsername,\n\t\t\tPassword: testPassword,\n\t\t\tPhone:    testPhone,\n\t\t\tCaptcha:  \"123456\",\n\t\t}\n\n\t\tresp, err := service.RegisterService(registerReq)\n\t\tassert.NoError(t, err)\n\t\tassert.True(t, resp.Success)\n\n\t\t// 使用错误密码登录\n\t\twrongPasswordReq := req.UserLoginReq{\n\t\t\tPhone:     testPhone,\n\t\t\tPassword:  \"wrongpassword\",\n\t\t\tLoginType: enum.LoginByPassword,\n\t\t}\n\n\t\tloginResp, err := service.LoginService(wrongPasswordReq)\n\t\tassert.NoError(t, err)\n\t\tassert.False(t, loginResp.Success)\n\t\tassert.Contains(t, loginResp.Message, \"用户名或密码错误\")\n\n\t\tcleanupUser(testPhone)\n\t})\n}\n\n// setupCaptcha 设置验证码\nfunc setupCaptcha(phone, captcha string) {\n\tcaptchaKey := utils.MakeUserCaptchaKey(phone)\n\terr := utils.SetValueToRedis(captchaKey, captcha, 5*time.Minute)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// cleanupUser 清理用户数据\nfunc cleanupUser(phone string) {\n\t// 这里应该实现清理用户数据的逻辑\n\t// 由于涉及到数据库操作，这里只是占位符\n\t// 实际实现时需要根据具体的数据库操作来清理\n}\n"
  },
  {
    "path": "tests/init_test.go",
    "content": "package tests\n\nimport (\n\t\"DiTing-Go/global\"\n\t\"log\"\n\t\"os\"\n\t\"testing\"\n)\n\n// TestMain 在所有测试运行前执行初始化\nfunc TestMain(m *testing.M) {\n\t// 设置测试环境变量\n\tos.Setenv(\"GIN_MODE\", \"test\")\n\tos.Setenv(\"TEST_ENV\", \"test\")\n\n\t// 初始化日志\n\tglobal.LogInit()\n\n\t// 初始化Redis\n\tglobal.RedisInit()\n\n\t// 初始化数据库\n\tglobal.DBInit()\n\n\t// 运行测试\n\tcode := m.Run()\n\n\t// 清理资源\n\tcleanup()\n\n\tos.Exit(code)\n}\n\n// cleanup 清理测试资源\nfunc cleanup() {\n\t// 关闭Redis连接\n\tif global.Rdb != nil {\n\t\tglobal.Rdb.Close()\n\t}\n\n\t// 关闭数据库连接\n\tif global.Query != nil {\n\t\t// 这里可以添加数据库连接关闭逻辑\n\t}\n\n\tlog.Println(\"测试资源清理完成\")\n}\n"
  },
  {
    "path": "tests/integration/user_integration_test.go",
    "content": "package integration\n\nimport (\n\t\"DiTing-Go/domain/enum\"\n\t\"DiTing-Go/domain/vo/req\"\n\t\"DiTing-Go/logic\"\n\t\"DiTing-Go/service\"\n\t\"DiTing-Go/utils\"\n\t\"context\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\n// TestUserRegistrationFlow 测试用户注册流程\nfunc TestUserRegistrationFlow(t *testing.T) {\n\t// 测试数据\n\ttestPhone := \"13800138001\"\n\ttestUsername := \"testuser1\"\n\ttestPassword := \"123456\"\n\ttestCaptcha := \"123456\"\n\n\t// 设置验证码\n\tsetupCaptcha(testPhone, testCaptcha)\n\n\t// 执行注册\n\tregisterReq := req.UserRegisterReq{\n\t\tUsername: testUsername,\n\t\tPassword: testPassword,\n\t\tPhone:    testPhone,\n\t\tCaptcha:  testCaptcha,\n\t}\n\n\tresp, err := service.RegisterService(registerReq)\n\tassert.NoError(t, err)\n\tassert.True(t, resp.Success)\n\tassert.Contains(t, resp.Message, \"注册成功\")\n\n\t// 验证用户是否真的被创建\n\tuserExists, err := logic.CheckPhoneInDB(context.Background(), testPhone)\n\tassert.NoError(t, err)\n\tassert.True(t, userExists)\n\n\t// 清理测试数据\n\tcleanupUser(testPhone)\n}\n\n// TestUserLoginFlow 测试用户登录流程\nfunc TestUserLoginFlow(t *testing.T) {\n\t// 先注册一个用户\n\ttestPhone := \"13800138002\"\n\ttestUsername := \"testuser2\"\n\ttestPassword := \"123456\"\n\ttestCaptcha := \"123456\"\n\n\tsetupCaptcha(testPhone, testCaptcha)\n\tregisterReq := req.UserRegisterReq{\n\t\tUsername: testUsername,\n\t\tPassword: testPassword,\n\t\tPhone:    testPhone,\n\t\tCaptcha:  testCaptcha,\n\t}\n\n\tregisterResp, err := service.RegisterService(registerReq)\n\tassert.NoError(t, err)\n\tassert.True(t, registerResp.Success)\n\n\t// 测试密码登录\n\tloginReq := req.UserLoginReq{\n\t\tPhone:     testPhone,\n\t\tPassword:  testPassword,\n\t\tLoginType: enum.LoginByPassword,\n\t}\n\n\tloginResp, err := service.LoginService(loginReq)\n\tassert.NoError(t, err)\n\tassert.True(t, loginResp.Success)\n\n\t// 验证登录响应数据结构\n\tloginData, ok := loginResp.Data.(map[string]interface{})\n\tassert.True(t, ok)\n\tassert.NotEmpty(t, loginData[\"token\"])\n\tassert.NotZero(t, loginData[\"uid\"])\n\tassert.Equal(t, testUsername, loginData[\"name\"])\n\n\t// 清理测试数据\n\tcleanupUser(testPhone)\n}\n\n// TestUserCancelFlow 测试用户注销流程\nfunc TestUserCancelFlow(t *testing.T) {\n\t// 先注册一个用户\n\ttestPhone := \"13800138003\"\n\ttestUsername := \"testuser3\"\n\ttestPassword := \"123456\"\n\ttestCaptcha := \"123456\"\n\n\tsetupCaptcha(testPhone, testCaptcha)\n\tregisterReq := req.UserRegisterReq{\n\t\tUsername: testUsername,\n\t\tPassword: testPassword,\n\t\tPhone:    testPhone,\n\t\tCaptcha:  testCaptcha,\n\t}\n\n\tregisterResp, err := service.RegisterService(registerReq)\n\tassert.NoError(t, err)\n\tassert.True(t, registerResp.Success)\n\n\t// 设置注销验证码\n\tsetupCaptcha(testPhone, testCaptcha)\n\n\t// 执行注销\n\tctx := &gin.Context{}\n\tctx.Set(\"uid\", int64(12345))\n\n\tcancelReq := req.UserCancelReq{\n\t\tCaptcha: testCaptcha,\n\t}\n\n\tcancelResp, err := service.CancelService(ctx, cancelReq)\n\tassert.NoError(t, err)\n\tassert.True(t, cancelResp.Success)\n\tassert.Contains(t, cancelResp.Message, \"注销成功\")\n\n\t// 验证用户是否真的被删除\n\tuserExists, err := logic.CheckPhoneInDB(context.Background(), testPhone)\n\tassert.NoError(t, err)\n\tassert.False(t, userExists)\n}\n\n// TestDuplicateRegistration 测试重复注册\nfunc TestDuplicateRegistration(t *testing.T) {\n\ttestPhone := \"13800138004\"\n\ttestUsername := \"testuser4\"\n\ttestPassword := \"123456\"\n\ttestCaptcha := \"123456\"\n\n\t// 第一次注册\n\tsetupCaptcha(testPhone, testCaptcha)\n\tregisterReq := req.UserRegisterReq{\n\t\tUsername: testUsername,\n\t\tPassword: testPassword,\n\t\tPhone:    testPhone,\n\t\tCaptcha:  testCaptcha,\n\t}\n\n\tresp1, err := service.RegisterService(registerReq)\n\tassert.NoError(t, err)\n\tassert.True(t, resp1.Success)\n\n\t// 第二次注册相同手机号\n\tsetupCaptcha(testPhone, testCaptcha)\n\tresp2, err := service.RegisterService(registerReq)\n\tassert.NoError(t, err)\n\tassert.False(t, resp2.Success)\n\tassert.Contains(t, resp2.Message, \"手机号已存在\")\n\n\t// 清理测试数据\n\tcleanupUser(testPhone)\n}\n\n// TestLoginWithWrongCredentials 测试错误凭据登录\nfunc TestLoginWithWrongCredentials(t *testing.T) {\n\t// 先注册一个用户\n\ttestPhone := \"13800138005\"\n\ttestUsername := \"testuser5\"\n\ttestPassword := \"123456\"\n\ttestCaptcha := \"123456\"\n\n\tsetupCaptcha(testPhone, testCaptcha)\n\tregisterReq := req.UserRegisterReq{\n\t\tUsername: testUsername,\n\t\tPassword: testPassword,\n\t\tPhone:    testPhone,\n\t\tCaptcha:  testCaptcha,\n\t}\n\n\tresp, err := service.RegisterService(registerReq)\n\tassert.NoError(t, err)\n\tassert.True(t, resp.Success)\n\n\t// 测试错误密码\n\twrongPasswordReq := req.UserLoginReq{\n\t\tPhone:     testPhone,\n\t\tPassword:  \"wrongpassword\",\n\t\tLoginType: enum.LoginByPassword,\n\t}\n\n\tresp2, err := service.LoginService(wrongPasswordReq)\n\tassert.NoError(t, err)\n\tassert.False(t, resp2.Success)\n\tassert.Contains(t, resp2.Message, \"用户名或密码错误\")\n\n\t// 测试错误验证码\n\twrongCaptchaReq := req.UserLoginReq{\n\t\tPhone:     testPhone,\n\t\tCaptcha:   \"999999\",\n\t\tLoginType: enum.LoginByPhoneCaptcha,\n\t}\n\n\tresp3, err := service.LoginService(wrongCaptchaReq)\n\tassert.NoError(t, err)\n\tassert.False(t, resp3.Success)\n\tassert.Contains(t, resp3.Message, \"验证码错误\")\n\n\t// 清理测试数据\n\tcleanupUser(testPhone)\n}\n\n// TestCancelWithWrongCaptcha 测试错误验证码注销\nfunc TestCancelWithWrongCaptcha(t *testing.T) {\n\t// 先注册一个用户\n\ttestPhone := \"13800138006\"\n\ttestUsername := \"testuser6\"\n\ttestPassword := \"123456\"\n\ttestCaptcha := \"123456\"\n\n\tsetupCaptcha(testPhone, testCaptcha)\n\tregisterReq := req.UserRegisterReq{\n\t\tUsername: testUsername,\n\t\tPassword: testPassword,\n\t\tPhone:    testPhone,\n\t\tCaptcha:  testCaptcha,\n\t}\n\n\tresp, err := service.RegisterService(registerReq)\n\tassert.NoError(t, err)\n\tassert.True(t, resp.Success)\n\n\t// 测试错误验证码注销\n\tctx := &gin.Context{}\n\tctx.Set(\"uid\", int64(12345))\n\n\twrongCaptchaReq := req.UserCancelReq{\n\t\tCaptcha: \"999999\",\n\t}\n\n\tresp2, err := service.CancelService(ctx, wrongCaptchaReq)\n\tassert.NoError(t, err)\n\tassert.False(t, resp2.Success)\n\tassert.Contains(t, resp2.Message, \"验证码错误\")\n\n\t// 清理测试数据\n\tcleanupUser(testPhone)\n}\n\n// TestUserRegistrationWithValidData 测试有效数据注册\nfunc TestUserRegistrationWithValidData(t *testing.T) {\n\ttestCases := []struct {\n\t\tname     string\n\t\tusername string\n\t\tpassword string\n\t\tphone    string\n\t\tcaptcha  string\n\t}{\n\t\t{\n\t\t\tname:     \"标准注册\",\n\t\t\tusername: \"testuser7\",\n\t\t\tpassword: \"123456\",\n\t\t\tphone:    \"13800138007\",\n\t\t\tcaptcha:  \"123456\",\n\t\t},\n\t\t{\n\t\t\tname:     \"长用户名\",\n\t\t\tusername: \"verylongusername123\",\n\t\t\tpassword: \"123456\",\n\t\t\tphone:    \"13800138008\",\n\t\t\tcaptcha:  \"123456\",\n\t\t},\n\t\t{\n\t\t\tname:     \"复杂密码\",\n\t\t\tusername: \"testuser9\",\n\t\t\tpassword: \"P@ssw0rd123\",\n\t\t\tphone:    \"13800138009\",\n\t\t\tcaptcha:  \"123456\",\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tregisterReq := req.UserRegisterReq{\n\t\t\t\tUsername: tc.username,\n\t\t\t\tPassword: tc.password,\n\t\t\t\tPhone:    tc.phone,\n\t\t\t\tCaptcha:  tc.captcha,\n\t\t\t}\n\n\t\t\tsetupCaptcha(tc.phone, tc.captcha)\n\t\t\tresp, err := service.RegisterService(registerReq)\n\t\t\tassert.NoError(t, err)\n\t\t\tassert.True(t, resp.Success)\n\n\t\t\t// 清理测试数据\n\t\t\tcleanupUser(tc.phone)\n\t\t})\n\t}\n}\n\n// setupCaptcha 设置验证码\nfunc setupCaptcha(phone, captcha string) {\n\tcaptchaKey := utils.MakeUserCaptchaKey(phone)\n\terr := utils.SetValueToRedis(captchaKey, captcha, 5*time.Minute)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// cleanupUser 清理用户数据\nfunc cleanupUser(phone string) {\n\t// 这里应该实现清理用户数据的逻辑\n\t// 由于涉及到数据库操作，这里只是占位符\n\t// 实际实现时需要根据具体的数据库操作来清理\n}\n"
  },
  {
    "path": "tests/performance/user_performance_test.go",
    "content": "package performance\n\nimport (\n\t\"DiTing-Go/domain/enum\"\n\t\"DiTing-Go/domain/vo/req\"\n\t\"DiTing-Go/service\"\n\t\"DiTing-Go/utils\"\n\t\"fmt\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\n// BenchmarkUserRegistration 用户注册性能基准测试\nfunc BenchmarkUserRegistration(b *testing.B) {\n\t// 重置计时器\n\tb.ResetTimer()\n\n\tfor i := 0; i < b.N; i++ {\n\t\tphone := fmt.Sprintf(\"13800139%03d\", i%1000)\n\t\tusername := fmt.Sprintf(\"benchuser%d\", i)\n\t\tpassword := \"123456\"\n\t\tcaptcha := \"123456\"\n\n\t\t// 设置验证码\n\t\tsetupCaptcha(phone, captcha)\n\n\t\t// 执行注册\n\t\tregisterReq := req.UserRegisterReq{\n\t\t\tUsername: username,\n\t\t\tPassword: password,\n\t\t\tPhone:    phone,\n\t\t\tCaptcha:  captcha,\n\t\t}\n\n\t\tresp, err := service.RegisterService(registerReq)\n\t\tassert.NoError(b, err)\n\t\tassert.True(b, resp.Success)\n\n\t\t// 清理测试数据\n\t\tcleanupUser(phone)\n\t}\n}\n\n// BenchmarkUserLogin 用户登录性能基准测试\nfunc BenchmarkUserLogin(b *testing.B) {\n\t// 先注册一个用户用于测试\n\ttestPhone := \"13800139000\"\n\ttestUsername := \"benchuser\"\n\ttestPassword := \"123456\"\n\ttestCaptcha := \"123456\"\n\n\tsetupCaptcha(testPhone, testCaptcha)\n\tregisterReq := req.UserRegisterReq{\n\t\tUsername: testUsername,\n\t\tPassword: testPassword,\n\t\tPhone:    testPhone,\n\t\tCaptcha:  testCaptcha,\n\t}\n\n\tresp, err := service.RegisterService(registerReq)\n\tassert.NoError(b, err)\n\tassert.True(b, resp.Success)\n\n\t// 重置计时器\n\tb.ResetTimer()\n\n\tfor i := 0; i < b.N; i++ {\n\t\tloginReq := req.UserLoginReq{\n\t\t\tPhone:     testPhone,\n\t\t\tPassword:  testPassword,\n\t\t\tLoginType: enum.LoginByPassword,\n\t\t}\n\n\t\tloginResp, err := service.LoginService(loginReq)\n\t\tassert.NoError(b, err)\n\t\tassert.True(b, loginResp.Success)\n\t}\n\n\t// 清理测试数据\n\tcleanupUser(testPhone)\n}\n\n// TestConcurrentUserRegistration 并发用户注册测试\nfunc TestConcurrentUserRegistration(t *testing.T) {\n\tconst numUsers = 10\n\tconst numGoroutines = 5\n\n\tvar wg sync.WaitGroup\n\terrors := make(chan error, numUsers*numGoroutines)\n\n\tstartTime := time.Now()\n\n\tfor i := 0; i < numGoroutines; i++ {\n\t\twg.Add(1)\n\t\tgo func(routineID int) {\n\t\t\tdefer wg.Done()\n\n\t\t\tfor j := 0; j < numUsers; j++ {\n\t\t\t\tuserID := routineID*numUsers + j\n\t\t\t\tphone := fmt.Sprintf(\"13800140%03d\", userID)\n\t\t\t\tusername := fmt.Sprintf(\"concurrentuser%d\", userID)\n\t\t\t\tpassword := \"123456\"\n\t\t\t\tcaptcha := \"123456\"\n\n\t\t\t\t// 设置验证码\n\t\t\t\tsetupCaptcha(phone, captcha)\n\n\t\t\t\t// 执行注册\n\t\t\t\tregisterReq := req.UserRegisterReq{\n\t\t\t\t\tUsername: username,\n\t\t\t\t\tPassword: password,\n\t\t\t\t\tPhone:    phone,\n\t\t\t\t\tCaptcha:  captcha,\n\t\t\t\t}\n\n\t\t\t\tresp, err := service.RegisterService(registerReq)\n\t\t\t\tif err != nil {\n\t\t\t\t\terrors <- err\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif !resp.Success {\n\t\t\t\t\terrors <- fmt.Errorf(\"注册失败: %s\", resp.Message)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\t// 清理测试数据\n\t\t\t\tcleanupUser(phone)\n\t\t\t}\n\t\t}(i)\n\t}\n\n\twg.Wait()\n\tclose(errors)\n\n\tduration := time.Since(startTime)\n\tt.Logf(\"并发注册 %d 个用户，耗时: %v\", numUsers*numGoroutines, duration)\n\n\t// 检查是否有错误\n\tfor err := range errors {\n\t\tt.Errorf(\"并发注册错误: %v\", err)\n\t}\n}\n\n// TestConcurrentUserLogin 并发用户登录测试\nfunc TestConcurrentUserLogin(t *testing.T) {\n\tconst numUsers = 10\n\tconst numGoroutines = 5\n\n\t// 先注册一些用户\n\tusers := make([]string, numUsers*numGoroutines)\n\tfor i := 0; i < numUsers*numGoroutines; i++ {\n\t\tphone := fmt.Sprintf(\"13800141%03d\", i)\n\t\tusername := fmt.Sprintf(\"loginuser%d\", i)\n\t\tpassword := \"123456\"\n\t\tcaptcha := \"123456\"\n\n\t\tsetupCaptcha(phone, captcha)\n\t\tregisterReq := req.UserRegisterReq{\n\t\t\tUsername: username,\n\t\t\tPassword: password,\n\t\t\tPhone:    phone,\n\t\t\tCaptcha:  captcha,\n\t\t}\n\n\t\tresp, err := service.RegisterService(registerReq)\n\t\tassert.NoError(t, err)\n\t\tassert.True(t, resp.Success)\n\n\t\tusers[i] = phone\n\t}\n\n\tvar wg sync.WaitGroup\n\terrors := make(chan error, numUsers*numGoroutines)\n\n\tstartTime := time.Now()\n\n\tfor i := 0; i < numGoroutines; i++ {\n\t\twg.Add(1)\n\t\tgo func(routineID int) {\n\t\t\tdefer wg.Done()\n\n\t\t\tfor j := 0; j < numUsers; j++ {\n\t\t\t\tuserID := routineID*numUsers + j\n\t\t\t\tphone := users[userID]\n\t\t\t\tpassword := \"123456\"\n\n\t\t\t\tloginReq := req.UserLoginReq{\n\t\t\t\t\tPhone:     phone,\n\t\t\t\t\tPassword:  password,\n\t\t\t\t\tLoginType: enum.LoginByPassword,\n\t\t\t\t}\n\n\t\t\t\tresp, err := service.LoginService(loginReq)\n\t\t\t\tif err != nil {\n\t\t\t\t\terrors <- err\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif !resp.Success {\n\t\t\t\t\terrors <- fmt.Errorf(\"登录失败: %s\", resp.Message)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}(i)\n\t}\n\n\twg.Wait()\n\tclose(errors)\n\n\tduration := time.Since(startTime)\n\tt.Logf(\"并发登录 %d 个用户，耗时: %v\", numUsers*numGoroutines, duration)\n\n\t// 检查是否有错误\n\tfor err := range errors {\n\t\tt.Errorf(\"并发登录错误: %v\", err)\n\t}\n\n\t// 清理测试数据\n\tfor _, phone := range users {\n\t\tcleanupUser(phone)\n\t}\n}\n\n// TestLoadTest 负载测试\nfunc TestLoadTest(t *testing.T) {\n\tconst numUsers = 100\n\tconst duration = 30 * time.Second\n\n\tstartTime := time.Now()\n\tsuccessCount := 0\n\terrorCount := 0\n\tvar mu sync.Mutex\n\n\t// 创建用户注册任务\n\tfor i := 0; i < numUsers; i++ {\n\t\tgo func(userID int) {\n\t\t\tphone := fmt.Sprintf(\"13800142%03d\", userID)\n\t\t\tusername := fmt.Sprintf(\"loaduser%d\", userID)\n\t\t\tpassword := \"123456\"\n\t\t\tcaptcha := \"123456\"\n\n\t\t\tsetupCaptcha(phone, captcha)\n\t\t\tregisterReq := req.UserRegisterReq{\n\t\t\t\tUsername: username,\n\t\t\t\tPassword: password,\n\t\t\t\tPhone:    phone,\n\t\t\t\tCaptcha:  captcha,\n\t\t\t}\n\n\t\t\tresp, err := service.RegisterService(registerReq)\n\t\t\tmu.Lock()\n\t\t\tif err != nil || !resp.Success {\n\t\t\t\terrorCount++\n\t\t\t} else {\n\t\t\t\tsuccessCount++\n\t\t\t}\n\t\t\tmu.Unlock()\n\n\t\t\t// 清理测试数据\n\t\t\tcleanupUser(phone)\n\t\t}(i)\n\t}\n\n\t// 等待指定时间\n\ttime.Sleep(duration)\n\n\ttotalTime := time.Since(startTime)\n\tt.Logf(\"负载测试结果:\")\n\tt.Logf(\"  总时间: %v\", totalTime)\n\tt.Logf(\"  成功请求: %d\", successCount)\n\tt.Logf(\"  失败请求: %d\", errorCount)\n\tt.Logf(\"  成功率: %.2f%%\", float64(successCount)/float64(successCount+errorCount)*100)\n}\n\n// setupCaptcha 设置验证码\nfunc setupCaptcha(phone, captcha string) {\n\tcaptchaKey := utils.MakeUserCaptchaKey(phone)\n\terr := utils.SetValueToRedis(captchaKey, captcha, 5*time.Minute)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// cleanupUser 清理用户数据\nfunc cleanupUser(phone string) {\n\t// 这里应该实现清理用户数据的逻辑\n\t// 由于涉及到数据库操作，这里只是占位符\n\t// 实际实现时需要根据具体的数据库操作来清理\n}\n"
  },
  {
    "path": "tests/scripts/run_e2e_test.sh",
    "content": "#!/bin/bash\n\n# DiTing-Go E2E测试运行脚本\n\necho \"=== DiTing-Go E2E测试运行脚本 ===\"\necho \"\"\n\n# 检查Go环境\nif ! command -v go &> /dev/null; then\n    echo \"错误: 未找到Go环境，请先安装Go\"\n    exit 1\nfi\n\n# 获取项目根目录\nPROJECT_ROOT=$(pwd)\necho \"项目根目录: $PROJECT_ROOT\"\n\n# 设置环境变量\nexport PROJECT_ROOT=\"$PROJECT_ROOT\"\nexport GIN_MODE=test\nexport TEST_ENV=test\n\necho \"环境变量设置:\"\necho \"  PROJECT_ROOT=$PROJECT_ROOT\"\necho \"  GIN_MODE=$GIN_MODE\"\necho \"  TEST_ENV=$TEST_ENV\"\n\n# 检查配置文件\nif [ -f \"$PROJECT_ROOT/conf/config.yml\" ]; then\n    echo \"✅ 配置文件存在: $PROJECT_ROOT/conf/config.yml\"\nelse\n    echo \"❌ 配置文件不存在: $PROJECT_ROOT/conf/config.yml\"\n    exit 1\nfi\n\n# 检查依赖\necho \"检查依赖...\"\ngo mod tidy\n\necho \"\"\necho \"=== 运行E2E测试 ===\"\n\n# 确保在项目根目录运行测试\ncd \"$PROJECT_ROOT\"\n\n# 运行特定的E2E测试\necho \"运行用户完整工作流E2E测试...\"\ngo test -v -run TestUserCompleteWorkflow ./tests/e2e/\n\necho \"\"\necho \"运行多用户并发E2E测试...\"\ngo test -v -run TestMultipleUserWorkflow ./tests/e2e/\n\necho \"\"\necho \"运行用户错误场景E2E测试...\"\ngo test -v -run TestUserErrorScenarios ./tests/e2e/\n\necho \"\"\necho \"=== E2E测试完成 ===\" "
  },
  {
    "path": "tests/scripts/run_tests.bat",
    "content": "@echo off\nchcp 65001 >nul\nsetlocal enabledelayedexpansion\n\necho === DiTing-Go 用户服务测试 ===\necho.\n\nREM 检查Go环境\nwhere go >nul 2>nul\nif %errorlevel% neq 0 (\n    echo 错误: 未找到Go环境，请先安装Go\n    pause\n    exit /b 1\n)\n\nREM 检查依赖\necho 检查依赖...\ngo mod tidy\n\nREM 设置测试环境变量\nset GIN_MODE=test\n\n:menu\necho.\necho 请选择要运行的测试:\necho 1. 用户完整生命周期测试\necho 2. 用户注册流程测试\necho 3. 用户登录流程测试\necho 4. 用户注销流程测试\necho 5. 注册参数验证测试\necho 6. 登录参数验证测试\necho 7. 重复注册测试\necho 8. 错误凭据登录测试\necho 9. 错误验证码注销测试\necho 10. 有效数据注册测试\necho 11. 运行所有测试\necho 0. 退出\necho.\nset /p choice=请输入选项 (0-11): \n\nif \"%choice%\"==\"1\" goto test_lifecycle\nif \"%choice%\"==\"2\" goto test_register\nif \"%choice%\"==\"3\" goto test_login\nif \"%choice%\"==\"4\" goto test_cancel\nif \"%choice%\"==\"5\" goto test_register_validation\nif \"%choice%\"==\"6\" goto test_login_validation\nif \"%choice%\"==\"7\" goto test_duplicate_register\nif \"%choice%\"==\"8\" goto test_wrong_credentials\nif \"%choice%\"==\"9\" goto test_wrong_captcha\nif \"%choice%\"==\"10\" goto test_valid_data\nif \"%choice%\"==\"11\" goto test_all\nif \"%choice%\"==\"0\" goto exit\necho 无效选项，请重新选择\ngoto menu\n\n:test_lifecycle\necho.\necho === 运行测试: 用户完整生命周期测试 ===\necho 命令: go test -v -run TestUserLifecycleFlow\necho.\ngo test -v -run TestUserLifecycleFlow ./service/\nif %errorlevel% equ 0 (\n    echo ✅ 用户完整生命周期测试通过\n) else (\n    echo ❌ 用户完整生命周期测试失败\n)\ngoto continue\n\n:test_register\necho.\necho === 运行测试: 用户注册流程测试 ===\necho 命令: go test -v -run TestUserRegistrationFlow\necho.\ngo test -v -run TestUserRegistrationFlow ./service/\nif %errorlevel% equ 0 (\n    echo ✅ 用户注册流程测试通过\n) else (\n    echo ❌ 用户注册流程测试失败\n)\ngoto continue\n\n:test_login\necho.\necho === 运行测试: 用户登录流程测试 ===\necho 命令: go test -v -run TestUserLoginFlow\necho.\ngo test -v -run TestUserLoginFlow ./service/\nif %errorlevel% equ 0 (\n    echo ✅ 用户登录流程测试通过\n) else (\n    echo ❌ 用户登录流程测试失败\n)\ngoto continue\n\n:test_cancel\necho.\necho === 运行测试: 用户注销流程测试 ===\necho 命令: go test -v -run TestUserCancelFlow\necho.\ngo test -v -run TestUserCancelFlow ./service/\nif %errorlevel% equ 0 (\n    echo ✅ 用户注销流程测试通过\n) else (\n    echo ❌ 用户注销流程测试失败\n)\ngoto continue\n\n:test_register_validation\necho.\necho === 运行测试: 注册参数验证测试 ===\necho 命令: go test -v -run TestRegisterValidation\necho.\ngo test -v -run TestRegisterValidation ./service/\nif %errorlevel% equ 0 (\n    echo ✅ 注册参数验证测试通过\n) else (\n    echo ❌ 注册参数验证测试失败\n)\ngoto continue\n\n:test_login_validation\necho.\necho === 运行测试: 登录参数验证测试 ===\necho 命令: go test -v -run TestLoginValidation\necho.\ngo test -v -run TestLoginValidation ./service/\nif %errorlevel% equ 0 (\n    echo ✅ 登录参数验证测试通过\n) else (\n    echo ❌ 登录参数验证测试失败\n)\ngoto continue\n\n:test_duplicate_register\necho.\necho === 运行测试: 重复注册测试 ===\necho 命令: go test -v -run TestDuplicateRegistration\necho.\ngo test -v -run TestDuplicateRegistration ./service/\nif %errorlevel% equ 0 (\n    echo ✅ 重复注册测试通过\n) else (\n    echo ❌ 重复注册测试失败\n)\ngoto continue\n\n:test_wrong_credentials\necho.\necho === 运行测试: 错误凭据登录测试 ===\necho 命令: go test -v -run TestLoginWithWrongCredentials\necho.\ngo test -v -run TestLoginWithWrongCredentials ./service/\nif %errorlevel% equ 0 (\n    echo ✅ 错误凭据登录测试通过\n) else (\n    echo ❌ 错误凭据登录测试失败\n)\ngoto continue\n\n:test_wrong_captcha\necho.\necho === 运行测试: 错误验证码注销测试 ===\necho 命令: go test -v -run TestCancelWithWrongCaptcha\necho.\ngo test -v -run TestCancelWithWrongCaptcha ./service/\nif %errorlevel% equ 0 (\n    echo ✅ 错误验证码注销测试通过\n) else (\n    echo ❌ 错误验证码注销测试失败\n)\ngoto continue\n\n:test_valid_data\necho.\necho === 运行测试: 有效数据注册测试 ===\necho 命令: go test -v -run TestUserRegistrationWithValidData\necho.\ngo test -v -run TestUserRegistrationWithValidData ./service/\nif %errorlevel% equ 0 (\n    echo ✅ 有效数据注册测试通过\n) else (\n    echo ❌ 有效数据注册测试失败\n)\ngoto continue\n\n:test_all\necho.\necho === 运行所有测试 ===\nset failed_tests=\n\nfor %%t in (TestUserLifecycleFlow TestUserRegistrationFlow TestUserLoginFlow TestUserCancelFlow TestRegisterValidation TestLoginValidation TestDuplicateRegistration TestLoginWithWrongCredentials TestCancelWithWrongCaptcha TestUserRegistrationWithValidData) do (\n    echo.\n    echo 运行测试: %%t\n    go test -v -run %%t ./service/\n    if !errorlevel! neq 0 (\n        set failed_tests=!failed_tests! %%t\n    )\n)\n\necho.\necho === 测试结果汇总 ===\nif \"%failed_tests%\"==\"\" (\n    echo ✅ 所有测试通过\n) else (\n    echo ❌ 以下测试失败:\n    for %%t in (%failed_tests%) do (\n        echo   - %%t\n    )\n)\ngoto continue\n\n:continue\necho.\npause\ngoto menu\n\n:exit\necho 退出测试\npause\nexit /b 0 "
  },
  {
    "path": "tests/scripts/run_tests.sh",
    "content": "#!/bin/bash\n\n# DiTing-Go 测试运行脚本\n\necho \"=== DiTing-Go 测试运行脚本 ===\"\necho \"\"\n\n# 检查Go环境\nif ! command -v go &> /dev/null; then\n    echo \"错误: 未找到Go环境，请先安装Go\"\n    exit 1\nfi\n\n# 检查依赖\necho \"检查依赖...\"\ngo mod tidy\n\n# 获取项目根目录并设置环境变量\nPROJECT_ROOT=$(pwd)\nexport PROJECT_ROOT=\"$PROJECT_ROOT\"\nexport GIN_MODE=test\nexport TEST_ENV=test\n\necho \"项目根目录: $PROJECT_ROOT\"\necho \"环境变量设置完成\"\n\n# 运行测试的函数\nrun_test() {\n    local test_name=$1\n    local test_pattern=$2\n    local test_dir=$3\n    \n    echo \"\"\n    echo \"=== 运行测试: $test_name ===\"\n    echo \"命令: go test -v -run $test_pattern $test_dir\"\n    echo \"\"\n    \n    # 确保在项目根目录运行测试\n    cd \"$PROJECT_ROOT\"\n    go test -v -run \"$test_pattern\" \"$test_dir\"\n    \n    if [ $? -eq 0 ]; then\n        echo \"✅ $test_name 测试通过\"\n    else\n        echo \"❌ $test_name 测试失败\"\n        return 1\n    fi\n}\n\n# 主菜单\nshow_menu() {\n    echo \"\"\n    echo \"请选择要运行的测试:\"\n    echo \"=== 单元测试 ===\"\n    echo \"1. 用户服务单元测试\"\n    echo \"2. 用户生命周期单元测试\"\n    echo \"3. 注册验证单元测试\"\n    echo \"4. 登录验证单元测试\"\n    echo \"\"\n    echo \"=== 集成测试 ===\"\n    echo \"5. 用户注册集成测试\"\n    echo \"6. 用户登录集成测试\"\n    echo \"7. 用户注销集成测试\"\n    echo \"8. 重复注册集成测试\"\n    echo \"9. 错误凭据登录集成测试\"\n    echo \"10. 错误验证码注销集成测试\"\n    echo \"11. 有效数据注册集成测试\"\n    echo \"\"\n    echo \"=== 端到端测试 ===\"\n    echo \"12. 用户完整工作流E2E测试\"\n    echo \"13. 多用户并发E2E测试\"\n    echo \"14. 用户错误场景E2E测试\"\n    echo \"\"\n    echo \"=== 性能测试 ===\"\n    echo \"15. 用户注册性能测试\"\n    echo \"16. 用户登录性能测试\"\n    echo \"17. 并发用户注册测试\"\n    echo \"18. 并发用户登录测试\"\n    echo \"19. 负载测试\"\n    echo \"\"\n    echo \"=== 批量测试 ===\"\n    echo \"20. 运行所有单元测试\"\n    echo \"21. 运行所有集成测试\"\n    echo \"22. 运行所有E2E测试\"\n    echo \"23. 运行所有性能测试\"\n    echo \"24. 运行所有测试\"\n    echo \"0. 退出\"\n    echo \"\"\n    read -p \"请输入选项 (0-24): \" choice\n}\n\n# 运行所有测试\nrun_all_tests() {\n    echo \"\"\n    echo \"=== 运行所有测试 ===\"\n    \n    # 确保在项目根目录运行测试\n    cd \"$PROJECT_ROOT\"\n    \n    test_dirs=(\n        \"./tests/unit/\"\n        \"./tests/integration/\"\n        \"./tests/e2e/\"\n        \"./tests/performance/\"\n    )\n    \n    failed_tests=()\n    \n    for dir in \"${test_dirs[@]}\"; do\n        echo \"\"\n        echo \"运行测试目录: $dir\"\n        go test -v \"$dir\"\n        \n        if [ $? -ne 0 ]; then\n            failed_tests+=(\"$dir\")\n        fi\n    done\n    \n    echo \"\"\n    echo \"=== 测试结果汇总 ===\"\n    if [ ${#failed_tests[@]} -eq 0 ]; then\n        echo \"✅ 所有测试通过\"\n    else\n        echo \"❌ 以下测试失败:\"\n        for test in \"${failed_tests[@]}\"; do\n            echo \"  - $test\"\n        done\n    fi\n}\n\n# 主循环\nwhile true; do\n    show_menu\n    \n    case $choice in\n        1)\n            run_test \"用户服务单元测试\" \"TestUserLifecycleFlow\" \"./tests/unit/\"\n            ;;\n        2)\n            run_test \"用户生命周期单元测试\" \"TestUserLifecycleFlow\" \"./tests/unit/\"\n            ;;\n        3)\n            run_test \"注册验证单元测试\" \"TestRegisterValidation\" \"./tests/unit/\"\n            ;;\n        4)\n            run_test \"登录验证单元测试\" \"TestLoginValidation\" \"./tests/unit/\"\n            ;;\n        5)\n            run_test \"用户注册集成测试\" \"TestUserRegistrationFlow\" \"./tests/integration/\"\n            ;;\n        6)\n            run_test \"用户登录集成测试\" \"TestUserLoginFlow\" \"./tests/integration/\"\n            ;;\n        7)\n            run_test \"用户注销集成测试\" \"TestUserCancelFlow\" \"./tests/integration/\"\n            ;;\n        8)\n            run_test \"重复注册集成测试\" \"TestDuplicateRegistration\" \"./tests/integration/\"\n            ;;\n        9)\n            run_test \"错误凭据登录集成测试\" \"TestLoginWithWrongCredentials\" \"./tests/integration/\"\n            ;;\n        10)\n            run_test \"错误验证码注销集成测试\" \"TestCancelWithWrongCaptcha\" \"./tests/integration/\"\n            ;;\n        11)\n            run_test \"有效数据注册集成测试\" \"TestUserRegistrationWithValidData\" \"./tests/integration/\"\n            ;;\n        12)\n            run_test \"用户完整工作流E2E测试\" \"TestUserCompleteWorkflow\" \"./tests/e2e/\"\n            ;;\n        13)\n            run_test \"多用户并发E2E测试\" \"TestMultipleUserWorkflow\" \"./tests/e2e/\"\n            ;;\n        14)\n            run_test \"用户错误场景E2E测试\" \"TestUserErrorScenarios\" \"./tests/e2e/\"\n            ;;\n        15)\n            run_test \"用户注册性能测试\" \"BenchmarkUserRegistration\" \"./tests/performance/\"\n            ;;\n        16)\n            run_test \"用户登录性能测试\" \"BenchmarkUserLogin\" \"./tests/performance/\"\n            ;;\n        17)\n            run_test \"并发用户注册测试\" \"TestConcurrentUserRegistration\" \"./tests/performance/\"\n            ;;\n        18)\n            run_test \"并发用户登录测试\" \"TestConcurrentUserLogin\" \"./tests/performance/\"\n            ;;\n        19)\n            run_test \"负载测试\" \"TestLoadTest\" \"./tests/performance/\"\n            ;;\n        20)\n            echo \"\"\n            echo \"=== 运行所有单元测试 ===\"\n            cd \"$PROJECT_ROOT\"\n            go test -v ./tests/unit/...\n            ;;\n        21)\n            echo \"\"\n            echo \"=== 运行所有集成测试 ===\"\n            cd \"$PROJECT_ROOT\"\n            go test -v ./tests/integration/...\n            ;;\n        22)\n            echo \"\"\n            echo \"=== 运行所有E2E测试 ===\"\n            cd \"$PROJECT_ROOT\"\n            go test -v ./tests/e2e/...\n            ;;\n        23)\n            echo \"\"\n            echo \"=== 运行所有性能测试 ===\"\n            cd \"$PROJECT_ROOT\"\n            go test -v ./tests/performance/...\n            ;;\n        24)\n            run_all_tests\n            ;;\n        0)\n            echo \"退出测试\"\n            exit 0\n            ;;\n        *)\n            echo \"无效选项，请重新选择\"\n            ;;\n    esac\n    \n    echo \"\"\n    read -p \"按回车键继续...\"\ndone "
  },
  {
    "path": "tests/scripts/setup_test_env.sh",
    "content": "#!/bin/bash\n\n# DiTing-Go 测试环境设置脚本\n\necho \"=== DiTing-Go 测试环境设置 ===\"\necho \"\"\n\n# 检查Go环境\nif ! command -v go &> /dev/null; then\n    echo \"错误: 未找到Go环境，请先安装Go\"\n    exit 1\nfi\n\n# 检查Docker环境（可选）\nif command -v docker &> /dev/null; then\n    echo \"✅ 检测到Docker环境\"\nelse\n    echo \"⚠️  未检测到Docker环境，将使用本地服务\"\nfi\n\n# 获取项目根目录\nPROJECT_ROOT=$(pwd)\necho \"项目根目录: $PROJECT_ROOT\"\n\n# 设置测试环境变量\necho \"设置测试环境变量...\"\nexport GIN_MODE=test\nexport TEST_ENV=test\nexport PROJECT_ROOT=\"$PROJECT_ROOT\"\n\n# 检查配置文件是否存在\nif [ -f \"$PROJECT_ROOT/conf/config.yml\" ]; then\n    echo \"✅ 配置文件存在: $PROJECT_ROOT/conf/config.yml\"\nelse\n    echo \"❌ 配置文件不存在: $PROJECT_ROOT/conf/config.yml\"\n    exit 1\nfi\n\n# 检查数据库连接\necho \"检查数据库连接...\"\n# 这里可以添加数据库连接检查逻辑\n\n# 检查Redis连接\necho \"检查Redis连接...\"\n# 这里可以添加Redis连接检查逻辑\n\n# 准备测试数据\necho \"准备测试数据...\"\n# 这里可以添加测试数据准备逻辑\n\n# 清理旧的测试数据\necho \"清理旧的测试数据...\"\n# 这里可以添加清理逻辑\n\necho \"\"\necho \"✅ 测试环境设置完成\"\necho \"\"\necho \"环境变量设置:\"\necho \"  GIN_MODE=$GIN_MODE\"\necho \"  TEST_ENV=$TEST_ENV\"\necho \"  PROJECT_ROOT=$PROJECT_ROOT\"\necho \"\"\necho \"可以运行以下命令开始测试:\"\necho \"  ./tests/scripts/run_tests.sh\"\necho \"  go test -v ./tests/...\"\necho \"\" "
  },
  {
    "path": "tests/unit/user_service_test.go",
    "content": "package unit\n\nimport (\n\t\"DiTing-Go/domain/enum\"\n\t\"DiTing-Go/domain/vo/req\"\n\t\"DiTing-Go/global\"\n\t\"DiTing-Go/service\"\n\t\"DiTing-Go/utils\"\n\t\"DiTing-Go/utils/setting\"\n\t\"testing\"\n\t\"time\"\n\n\t\"DiTing-Go/logic\"\n\t\"context\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\n// 初始化函数，在包加载时执行\nfunc init() {\n\t// 设置测试环境变量\n\tgin.SetMode(gin.TestMode)\n\n\t// 初始化配置\n\tsetting.ConfigInit()\n\n\t// 初始化简单的测试日志\n\tglobal.Logger = logrus.New()\n\tglobal.Logger.SetOutput(gin.DefaultWriter)\n\tglobal.Logger.SetLevel(logrus.InfoLevel)\n\n\t// 初始化Redis\n\tglobal.RedisInit()\n\n\t// 初始化数据库\n\tglobal.DBInit()\n}\n\n// TestRegisterValidation 测试注册参数验证\nfunc TestRegisterValidation(t *testing.T) {\n\ttestCases := []struct {\n\t\tname          string\n\t\treq           req.UserRegisterReq\n\t\texpected      string\n\t\tshouldSuccess bool\n\t}{\n\t\t{\n\t\t\tname: \"用户名为空\",\n\t\t\treq: req.UserRegisterReq{\n\t\t\t\tUsername: \"\",\n\t\t\t\tPassword: \"123456\",\n\t\t\t\tPhone:    \"13800138001\",\n\t\t\t\tCaptcha:  \"123456\",\n\t\t\t},\n\t\t\texpected:      \"用户名、密码和手机号不能为空\",\n\t\t\tshouldSuccess: false,\n\t\t},\n\t\t{\n\t\t\tname: \"密码长度不足\",\n\t\t\treq: req.UserRegisterReq{\n\t\t\t\tUsername: \"testuser\",\n\t\t\t\tPassword: \"123\",\n\t\t\t\tPhone:    \"13800138001\",\n\t\t\t\tCaptcha:  \"123456\",\n\t\t\t},\n\t\t\texpected:      \"密码长度不能少于6位\",\n\t\t\tshouldSuccess: false,\n\t\t},\n\t\t{\n\t\t\tname: \"正确注册用例\",\n\t\t\treq: req.UserRegisterReq{\n\t\t\t\tUsername: \"validuser\",\n\t\t\t\tPassword: \"123456\",\n\t\t\t\tPhone:    \"13800138002\",\n\t\t\t\tCaptcha:  \"123456\",\n\t\t\t},\n\t\t\texpected:      \"\",\n\t\t\tshouldSuccess: true,\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\t// 为正确用例设置验证码\n\t\t\tif tc.shouldSuccess {\n\t\t\t\tsetupCaptcha(tc.req.Phone, tc.req.Captcha)\n\t\t\t}\n\n\t\t\tresp, err := service.RegisterService(tc.req)\n\n\t\t\tif tc.shouldSuccess {\n\t\t\t\t// 正确用例应该成功\n\t\t\t\tassert.NoError(t, err)\n\t\t\t\tassert.True(t, resp.Success, \"注册应该成功\")\n\t\t\t\tassert.Contains(t, resp.Message, \"注册成功\")\n\t\t\t} else {\n\t\t\t\t// 错误用例应该失败\n\t\t\t\tassert.Contains(t, resp.Message, tc.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// TestLoginValidation 测试登录参数验证\nfunc TestLoginValidation(t *testing.T) {\n\ttestCases := []struct {\n\t\tname          string\n\t\treq           req.UserLoginReq\n\t\texpected      string\n\t\tshouldSuccess bool\n\t}{\n\t\t{\n\t\t\tname: \"密码登录-手机号为空\",\n\t\t\treq: req.UserLoginReq{\n\t\t\t\tPhone:     \"\",\n\t\t\t\tPassword:  \"123456\",\n\t\t\t\tLoginType: enum.LoginByPassword,\n\t\t\t},\n\t\t\texpected:      \"用户名和密码不能为空\",\n\t\t\tshouldSuccess: false,\n\t\t},\n\t\t{\n\t\t\tname: \"验证码登录-手机号为空\",\n\t\t\treq: req.UserLoginReq{\n\t\t\t\tPhone:     \"\",\n\t\t\t\tCaptcha:   \"123456\",\n\t\t\t\tLoginType: enum.LoginByPhoneCaptcha,\n\t\t\t},\n\t\t\texpected:      \"手机号和验证码不能为空\",\n\t\t\tshouldSuccess: false,\n\t\t},\n\t\t{\n\t\t\tname: \"正确密码登录用例\",\n\t\t\treq: req.UserLoginReq{\n\t\t\t\tPhone:     \"13800138002\",\n\t\t\t\tPassword:  \"123456\",\n\t\t\t\tLoginType: enum.LoginByPassword,\n\t\t\t},\n\t\t\texpected:      \"\",\n\t\t\tshouldSuccess: true,\n\t\t},\n\t\t{\n\t\t\tname: \"正确验证码登录用例\",\n\t\t\treq: req.UserLoginReq{\n\t\t\t\tPhone:     \"13800138002\",\n\t\t\t\tCaptcha:   \"123456\",\n\t\t\t\tLoginType: enum.LoginByPhoneCaptcha,\n\t\t\t},\n\t\t\texpected:      \"\",\n\t\t\tshouldSuccess: true,\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\t// 为正确用例设置验证码\n\t\t\tif tc.shouldSuccess && tc.req.LoginType == enum.LoginByPhoneCaptcha {\n\t\t\t\tsetupCaptcha(tc.req.Phone, tc.req.Captcha)\n\t\t\t}\n\n\t\t\tresp, err := service.LoginService(tc.req)\n\n\t\t\tif tc.shouldSuccess {\n\t\t\t\t// 正确用例应该成功\n\t\t\t\tassert.NoError(t, err)\n\t\t\t\tassert.True(t, resp.Success, \"登录应该成功\")\n\t\t\t\t// 验证返回的数据结构\n\t\t\t\tloginData, ok := resp.Data.(map[string]interface{})\n\t\t\t\tassert.True(t, ok, \"返回数据应该是map类型\")\n\t\t\t\tassert.NotEmpty(t, loginData[\"token\"], \"应该返回token\")\n\t\t\t\tassert.NotZero(t, loginData[\"uid\"], \"应该返回用户ID\")\n\t\t\t} else {\n\t\t\t\t// 错误用例应该失败\n\t\t\t\tassert.Contains(t, resp.Message, tc.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// TestCheckPassword 测试密码校验功能\nfunc TestCheckPassword(t *testing.T) {\n\tctx := context.Background()\n\n\t// 测试边界情况\n\tt.Run(\"空手机号\", func(t *testing.T) {\n\t\tresult := logic.CheckPassword(ctx, \"\", \"123456\")\n\t\tassert.False(t, result, \"空手机号应该校验失败\")\n\t})\n\n\tt.Run(\"空密码\", func(t *testing.T) {\n\t\tresult := logic.CheckPassword(ctx, \"13800138003\", \"\")\n\t\tassert.False(t, result, \"空密码应该校验失败\")\n\t})\n\n\tt.Run(\"短密码\", func(t *testing.T) {\n\t\tresult := logic.CheckPassword(ctx, \"13800138003\", \"123\")\n\t\tassert.False(t, result, \"短密码应该校验失败\")\n\t})\n\n\tt.Run(\"不存在的手机号\", func(t *testing.T) {\n\t\tresult := logic.CheckPassword(ctx, \"99999999999\", \"123456\")\n\t\tassert.False(t, result, \"不存在的手机号应该校验失败\")\n\t})\n\n\t// 测试正常情况（需要先注册用户）\n\tt.Run(\"正常密码校验流程\", func(t *testing.T) {\n\t\t// 先注册一个测试用户\n\t\ttestPhone := \"13800138004\"\n\t\ttestUsername := \"testuser\"\n\t\ttestPassword := \"123456\"\n\t\ttestCaptcha := \"123456\"\n\n\t\t// 设置验证码\n\t\tsetupCaptcha(testPhone, testCaptcha)\n\n\t\t// 注册用户\n\t\tregisterReq := req.UserRegisterReq{\n\t\t\tUsername: testUsername,\n\t\t\tPassword: testPassword,\n\t\t\tPhone:    testPhone,\n\t\t\tCaptcha:  testCaptcha,\n\t\t}\n\n\t\tregisterResp, err := service.RegisterService(registerReq)\n\t\tif err == nil && registerResp.Success {\n\t\t\t// 注册成功，测试密码校验\n\t\t\tt.Run(\"正确密码\", func(t *testing.T) {\n\t\t\t\tresult := logic.CheckPassword(ctx, testPhone, testPassword)\n\t\t\t\tassert.True(t, result, \"正确密码应该通过校验\")\n\t\t\t})\n\n\t\t\tt.Run(\"错误密码\", func(t *testing.T) {\n\t\t\t\tresult := logic.CheckPassword(ctx, testPhone, \"wrongpassword\")\n\t\t\t\tassert.False(t, result, \"错误密码应该校验失败\")\n\t\t\t})\n\n\t\t\t// 清理测试数据\n\t\t\tcleanupUser(testPhone)\n\t\t} else {\n\t\t\tt.Skip(\"用户注册失败，跳过密码校验测试\")\n\t\t}\n\t})\n}\n\n// setupCaptcha 设置验证码\nfunc setupCaptcha(phone, captcha string) {\n\tcaptchaKey := utils.MakeUserCaptchaKey(phone)\n\terr := utils.SetValueToRedis(captchaKey, captcha, 5*time.Minute)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// cleanupUser 清理用户数据\nfunc cleanupUser(phone string) {\n\t// 这里应该实现清理用户数据的逻辑\n\t// 由于涉及到数据库操作，这里只是占位符\n\t// 实际实现时需要根据具体的数据库操作来清理\n\tglobal.Logger.Infof(\"cleanupUser: cleaning up user data for phone: %s\", phone)\n}\n"
  },
  {
    "path": "utils/jsonUtils/json.go",
    "content": "package jsonUtils\n\n//import (\n//\t\"DiTing-Go/global\"\n//\t\"context\"\n//\t\"github.com/apache/rocketmq-client-go/v2/primitive\"\n//\t\"github.com/goccy/go-json\"\n//\t\"github.com/pkg/errors\"\n//)\n//\n//// UnmarshalMsg 解析消息队列msg\n//func UnmarshalMsg(item any, msg *primitive.MessageExt) error {\n//\tbyteStr := msg.Message.Body\n//\terr := json.Unmarshal(byteStr, item)\n//\tif err != nil {\n//\t\tglobal.Logger.Errorf(\"jsonUtils unmarshal error: %s\", err.Error())\n//\t\treturn errors.New(\"Business Error\")\n//\t}\n//\treturn nil\n//}\n//\n//// Marshal 编码\n//func Marshal(item any) ([]byte, error) {\n//\tbyteStr, err := json.Marshal(item)\n//\tif err != nil {\n//\t\tglobal.Logger.Errorf(\"json序列化失败 %v\", err)\n//\t\treturn nil, err\n//\t}\n//\treturn byteStr, nil\n//}\n//\n//// SendMsgSync 发送消息\n//func SendMsgSync(topic string, item any) error {\n//\tctx := context.Background()\n//\tbyteStr, err := Marshal(item)\n//\tif err != nil {\n//\t\treturn err\n//\t}\n//\tmsg := &primitive.Message{\n//\t\tTopic: topic,\n//\t\tBody:  byteStr,\n//\t}\n//\t_, err = global.RocketProducer.SendSync(ctx, msg)\n//\treturn err\n//}\n"
  },
  {
    "path": "utils/jwt/jwt.go",
    "content": "package jwt\n\nimport (\n\t\"DiTing-Go/global\"\n\t\"time\"\n\n\t\"github.com/golang-jwt/jwt/v5\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/spf13/viper\"\n)\n\nvar jwtSecret = []byte(viper.GetString(\"jwt.secret\"))\n\ntype JwtClaims struct {\n\tUid int64 `json:\"uid\"`\n\tjwt.RegisteredClaims\n}\n\n// GenerateToken 生成token\nfunc GenerateToken(uid int64) (string, error) {\n\tregisteredClaims := jwt.RegisteredClaims{\n\t\t// A usual scenario is to set the expiration time relative to the current time\n\t\tExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),\n\t\tIssuedAt:  jwt.NewNumericDate(time.Now()),\n\t\tNotBefore: jwt.NewNumericDate(time.Now()),\n\t}\n\n\tclaims := JwtClaims{\n\t\tuid,\n\t\tregisteredClaims,\n\t}\n\n\ttokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)\n\ttoken, err := tokenClaims.SignedString(jwtSecret)\n\tif err != nil {\n\t\tglobal.Logger.Errorf(\"generate token failed: %v\", err)\n\t}\n\treturn token, err\n}\n\n// ParseToken 解析token\nfunc ParseToken(tokenString string) (*JwtClaims, error) {\n\t// 解析token\n\ttoken, err := jwt.ParseWithClaims(tokenString, &JwtClaims{}, func(token *jwt.Token) (any, error) {\n\t\treturn jwtSecret, nil\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif claims, ok := token.Claims.(*JwtClaims); ok && token.Valid {\n\t\treturn claims, nil\n\t}\n\treturn nil, errors.New(\"token无法解析\")\n}\n"
  },
  {
    "path": "utils/jwt/jwt_test.go",
    "content": "package jwt\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestGenerateToken(t *testing.T) {\n\ttests := []struct {\n\t\tname    string\n\t\tuid     int64\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname:    \"正常生成token\",\n\t\t\tuid:     12345,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:    \"用户ID为0\",\n\t\t\tuid:     0,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:    \"用户ID为负数\",\n\t\t\tuid:     -1,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:    \"大用户ID\",\n\t\t\tuid:     999999999,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ttoken, err := GenerateToken(tt.uid)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"GenerateToken() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif !tt.wantErr && token == \"\" {\n\t\t\t\tt.Error(\"GenerateToken() returned empty token\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestParseToken(t *testing.T) {\n\t// 首先生成一个有效的token用于测试\n\tvalidUID := int64(12345)\n\tvalidToken, err := GenerateToken(validUID)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to generate valid token for testing: %v\", err)\n\t}\n\n\ttests := []struct {\n\t\tname    string\n\t\ttoken   string\n\t\twantUID int64\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname:    \"解析有效token\",\n\t\t\ttoken:   validToken,\n\t\t\twantUID: validUID,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:    \"解析空token\",\n\t\t\ttoken:   \"\",\n\t\t\twantUID: 0,\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname:    \"解析无效token\",\n\t\t\ttoken:   \"invalid.token.here\",\n\t\t\twantUID: 0,\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname:    \"解析格式错误的token\",\n\t\t\ttoken:   \"not.a.valid.jwt.token\",\n\t\t\twantUID: 0,\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname:    \"解析过期token\",\n\t\t\ttoken:   \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjEyMzQ1LCJleHAiOjE2MzQ1Njc4OTB9.invalid_signature\",\n\t\t\twantUID: 0,\n\t\t\twantErr: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tclaims, err := ParseToken(tt.token)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"ParseToken() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif !tt.wantErr {\n\t\t\t\tif claims == nil {\n\t\t\t\t\tt.Error(\"ParseToken() returned nil claims for valid token\")\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif claims.Uid != tt.wantUID {\n\t\t\t\t\tt.Errorf(\"ParseToken() UID = %v, want %v\", claims.Uid, tt.wantUID)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestGenerateAndParseToken(t *testing.T) {\n\t// 测试生成token后立即解析是否能得到相同的UID\n\ttestUIDs := []int64{1, 100, 1000, 999999}\n\n\tfor _, uid := range testUIDs {\n\t\tt.Run(fmt.Sprintf(\"UID_%d\", uid), func(t *testing.T) {\n\t\t\t// 生成token\n\t\t\ttoken, err := GenerateToken(uid)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"GenerateToken() failed: %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// 解析token\n\t\t\tclaims, err := ParseToken(token)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"ParseToken() failed: %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// 验证UID是否一致\n\t\t\tif claims.Uid != uid {\n\t\t\t\tt.Errorf(\"UID mismatch: got %v, want %v\", claims.Uid, uid)\n\t\t\t}\n\n\t\t\t// 验证token的过期时间\n\t\t\tif claims.ExpiresAt == nil {\n\t\t\t\tt.Error(\"Token expiration time is nil\")\n\t\t\t} else if claims.ExpiresAt.Time.Before(time.Now()) {\n\t\t\t\tt.Error(\"Token has already expired\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTokenExpiration(t *testing.T) {\n\tuid := int64(12345)\n\n\t// 生成token\n\ttoken, err := GenerateToken(uid)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to generate token: %v\", err)\n\t}\n\n\t// 解析token\n\tclaims, err := ParseToken(token)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to parse token: %v\", err)\n\t}\n\n\t// 验证token的过期时间是否在合理范围内（24小时）\n\texpectedExpiry := time.Now().Add(24 * time.Hour)\n\tactualExpiry := claims.ExpiresAt.Time\n\n\t// 允许1分钟的误差\n\ttolerance := time.Minute\n\tif actualExpiry.After(expectedExpiry.Add(tolerance)) || actualExpiry.Before(expectedExpiry.Add(-tolerance)) {\n\t\tt.Errorf(\"Token expiration time is not within expected range. Expected around %v, got %v\", expectedExpiry, actualExpiry)\n\t}\n}\n"
  },
  {
    "path": "utils/middleware/cors.go",
    "content": "package middleware\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"net/http\"\n)\n\n// Cors 跨域中间件\nfunc Cors() gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\tmethod := c.Request.Method\n\t\torigin := c.Request.Header.Get(\"Origin\")\n\t\tif origin != \"\" {\n\t\t\t//接收客户端发送的origin （重要！）\n\t\t\tc.Writer.Header().Set(\"Access-Control-Allow-Origin\", origin)\n\t\t\t//服务器支持的所有跨域请求的方法\n\t\t\tc.Header(\"Access-Control-Allow-Methods\", \"POST, GET, OPTIONS, PUT, DELETE,UPDATE\")\n\t\t\t//允许跨域设置可以返回其他子段，可以自定义字段\n\t\t\tc.Header(\"Access-Control-Allow-Headers\", \"Authorization, content-type, Content-Length, X-CSRF-Token, Token,session,Access-Control-Allow-Headers,account\")\n\t\t\t// 允许浏览器（客户端）可以解析的头部 （重要）\n\t\t\tc.Header(\"Access-Control-Expose-Headers\", \"Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers\")\n\t\t\t//设置缓存时间\n\t\t\tc.Header(\"Access-Control-Max-Age\", \"172800\")\n\t\t\t//允许客户端传递校验信息比如 cookie (重要)\n\t\t\tc.Header(\"Access-Control-Allow-Credentials\", \"true\")\n\t\t\tc.Set(\"Content-Type\", \"application/json\")\n\t\t}\n\n\t\t//允许类型校验\n\t\tif method == \"OPTIONS\" {\n\t\t\tc.JSON(http.StatusOK, \"ok!\")\n\t\t}\n\n\t\tc.Next()\n\t}\n}\n"
  },
  {
    "path": "utils/middleware/jwt.go",
    "content": "package middleware\n\nimport (\n\t\"DiTing-Go/pkg/domain/vo/resp\"\n\t\"DiTing-Go/utils/jwt\"\n\t\"github.com/gin-gonic/gin\"\n\t\"strings\"\n)\n\n// JWT jwt中间件\nfunc JWT() gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\t// 客户端携带Token有三种方式 1.放在请求头 2.放在请求体 3.放在URI\n\t\t// 这里假设Token放在Header的Authorization中，并使用Bearer开头\n\t\t// 这里的具体实现方式要依据你的实际业务情况决定\n\t\tauthHeader := c.Request.Header.Get(\"Authorization\")\n\t\tif authHeader == \"\" {\n\t\t\tresp.ErrorResponse(c, \"无权限访问\")\n\t\t\tc.Abort()\n\t\t\treturn\n\t\t}\n\t\t// 按空格分割\n\t\tparts := strings.SplitN(authHeader, \" \", 2)\n\t\tif !(len(parts) == 2 && parts[0] == \"Bearer\") {\n\t\t\tresp.ErrorResponse(c, \"无权限访问\")\n\t\t\tc.Abort()\n\t\t\treturn\n\t\t}\n\t\t// parts[1]是获取到的tokenString，我们使用之前定义好的解析JWT的函数来解析它\n\t\ttoken, err := jwt.ParseToken(parts[1])\n\t\tif err != nil {\n\t\t\tresp.ErrorResponse(c, \"无权限访问\")\n\t\t\tc.Abort()\n\t\t\treturn\n\t\t}\n\t\t//把解析出来的token存储到请求的上下文c上,方便后续的处理函数获取\n\t\tc.Set(\"uid\", token.Uid)\n\t\tc.Next()\n\t}\n}\n"
  },
  {
    "path": "utils/middleware/log.go",
    "content": "package middleware\n\nimport (\n\t\"DiTing-Go/global\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/sirupsen/logrus\"\n\t\"time\"\n)\n\n// LoggerToFile 日志记录到文件\nfunc LoggerToFile() gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\t// 开始时间\n\t\tstartTime := time.Now()\n\n\t\t// 处理请求\n\t\tc.Next()\n\n\t\t// 结束时间\n\t\tendTime := time.Now()\n\n\t\t// 执行时间\n\t\tlatencyTime := endTime.Sub(startTime)\n\n\t\t// 请求方式\n\t\treqMethod := c.Request.Method\n\n\t\t// 请求路由\n\t\treqUri := c.Request.RequestURI\n\n\t\t// 状态码\n\t\tstatusCode := c.Writer.Status()\n\n\t\t// 请求IP\n\t\tclientIP := c.ClientIP()\n\n\t\t// 日志格式\n\t\tglobal.Logger.WithFields(logrus.Fields{\n\t\t\t\"statusCode\":  statusCode,\n\t\t\t\"latencyTime\": latencyTime,\n\t\t\t\"clientIP\":    clientIP,\n\t\t\t\"reqMethod\":   reqMethod,\n\t\t\t\"reqUri\":      reqUri,\n\t\t}).Infof(\"GIN\")\n\t}\n}\n"
  },
  {
    "path": "utils/mysqlUtils.go",
    "content": "package utils\n\nimport (\n\t\"DiTing-Go/dal/model\"\n\t\"DiTing-Go/global\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"gorm.io/gorm\"\n\t\"strconv\"\n)\n\n// QueryUserByPhone 根据手机号查询用户\nfunc QueryUserByPhone(ctx context.Context, phone string) (*model.User, error) {\n\tuser := global.Query.User\n\tuserQ := user.WithContext(ctx)\n\trst, err := userQ.Where(user.Phone.Eq(phone)).First()\n\tif err != nil {\n\t\tif errors.Is(err, gorm.ErrRecordNotFound) {\n\t\t\tglobal.Logger.Errorf(\"user not found with phone: %s\", phone)\n\t\t\treturn nil, gorm.ErrRecordNotFound\n\t\t}\n\t\treturn nil, fmt.Errorf(\"query user by phone error: %w\", err)\n\t}\n\treturn rst, nil\n}\n\n// QueryUserByID 根据ID查询用户\nfunc QueryUserByID(ctx context.Context, userId string) (*model.User, error) {\n\tuserIdNum, err := strconv.ParseInt(userId, 10, 64)\n\tuser := global.Query.User\n\tuserQ := user.WithContext(ctx)\n\trst, err := userQ.Where(user.ID.Eq(userIdNum)).First()\n\tif err != nil {\n\t\tif errors.Is(err, gorm.ErrRecordNotFound) {\n\t\t\tglobal.Logger.Errorf(\"user not found with userId: %s\", userId)\n\t\t\treturn nil, gorm.ErrRecordNotFound\n\t\t}\n\t\treturn nil, fmt.Errorf(\"query user by userId error: %v\", err)\n\t}\n\treturn rst, nil\n}\n"
  },
  {
    "path": "utils/passwordUtils.go",
    "content": "package utils\n\nimport (\n\t\"crypto/md5\"\n\t\"encoding/hex\"\n)\n\n// EncryptPassword 对密码进行md5加密\nfunc EncryptPassword(password string) string {\n\thash := md5.New()\n\thash.Write([]byte(password))\n\tpassword = hex.EncodeToString(hash.Sum(nil))\n\treturn password\n}\n"
  },
  {
    "path": "utils/redisCache/remove_cache.go",
    "content": "package redisCache\n\nimport (\n\t\"DiTing-Go/dal/model\"\n\t\"DiTing-Go/domain/enum\"\n\t\"DiTing-Go/pkg/utils\"\n\t\"fmt\"\n)\n\n// RemoveRoomCache 移除房间缓存\nfunc RemoveRoomCache(room model.Room) {\n\tutils.RemoveData(fmt.Sprintf(enum.RoomCacheByID, room.ID))\n}\n\n// RemoveRoomFriend 移除房间好友缓存\nfunc RemoveRoomFriend(roomFriend model.RoomFriend) {\n\tutils.RemoveData(fmt.Sprintf(enum.RoomFriendCacheByRoomID, roomFriend.RoomID))\n\tutils.RemoveData(fmt.Sprintf(enum.RoomFriendCacheByUidAndFriendUid, roomFriend.Uid1, roomFriend.Uid2))\n}\n\n// RemoveUserCache 移除用户缓存\nfunc RemoveUserCache(user model.User) {\n\tutils.RemoveData(fmt.Sprintf(enum.UserCacheByID, user.ID))\n\tutils.RemoveData(fmt.Sprintf(enum.UserCacheByName, user.Name))\n}\n\n// RemoveUserFriend 移除用户好友缓存\nfunc RemoveUserFriend(uid, friendUid int64) {\n\tutils.RemoveData(fmt.Sprintf(enum.UserFriendCacheByUidAndFriendUid, uid, friendUid))\n\tutils.RemoveData(fmt.Sprintf(enum.UserFriendCacheByUidAndFriendUid, friendUid, uid))\n}\n\n// RemoveUserApply 移除用户好友申请缓存\nfunc RemoveUserApply(uid, friendUid int64) {\n\tutils.RemoveData(fmt.Sprintf(enum.UserApplyCacheByUidAndFriendUid, uid, friendUid))\n\tutils.RemoveData(fmt.Sprintf(enum.UserApplyCacheByUidAndFriendUid, friendUid, uid))\n}\n\n// RemoveContact 移除会话缓存\nfunc RemoveContact(contact model.Contact) {\n\tutils.RemoveData(fmt.Sprintf(enum.ContactCacheById, contact.ID))\n}\n"
  },
  {
    "path": "utils/redisUtils.go",
    "content": "package utils\n\nimport (\n\tdomainEnum \"DiTing-Go/domain/enum\"\n\t\"DiTing-Go/global\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/goccy/go-json\"\n\t\"github.com/pkg/errors\"\n)\n\n// MakeUserPhoneKey 构造用户手机号\nfunc MakeUserPhoneKey(phone string) string {\n\treturn fmt.Sprintf(domainEnum.PhoneUidMap, phone)\n}\n\n// MakeUserCaptchaKey 构造验证码key\nfunc MakeUserCaptchaKey(phone string) string {\n\treturn fmt.Sprintf(domainEnum.UserCaptcha, phone)\n}\n\n// SetValueToRedis 设置字符串\nfunc SetValueToRedis(key string, value string, expireTime time.Duration) error {\n\tvalueByte, err := json.Marshal(value)\n\tif err != nil {\n\t\tglobal.Logger.Errorf(\"json marshal error: %v\", err)\n\t\treturn errors.New(\"json marshal error\")\n\t}\n\tif err = global.Rdb.Set(key, valueByte, expireTime).Err(); err != nil {\n\t\tglobal.Logger.Errorf(\"key:%s, value:%s, redis set error: %v\", key, valueByte, err)\n\t\treturn errors.New(\"redis set error\")\n\t}\n\treturn nil\n}\n\n// GetValueFromRedis 获取字符串\nfunc GetValueFromRedis(key string) (value []byte, err error) {\n\tvalueByte, err := global.Rdb.Get(key).Bytes()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn valueByte, nil\n}\n\n// DeleteValueFromRedis 删除字符串\nfunc DeleteValueFromRedis(key string) error {\n\tif err := global.Rdb.Del(key).Err(); err != nil {\n\t\tglobal.Logger.Errorf(\"key:%s, redis delete error: %v\", key, err)\n\t\treturn errors.New(\"redis delete error\")\n\t}\n\treturn nil\n}\n\n//// GetData 获取数据\n//func GetData(cacheKey string, value any, dbQueryFunc func() (interface{}, error)) error {\n//\t// 1. 从缓存中获取数据\n//\terr := GetString(cacheKey, value)\n//\t// 查询到数据\n//\tif err == nil {\n//\t\treturn nil\n//\t} else if !errors.Is(err, redis.Nil) {\n//\t\treturn err\n//\t}\n//\terr = QueryAndSet(cacheKey, value, dbQueryFunc)\n//\tif err != nil {\n//\t\treturn err\n//\t}\n//\treturn nil\n//}\n\n//// QueryAndSet 查询数据库并设置缓存\n//func QueryAndSet(cacheKey string, value any, dbQueryFunc func() (interface{}, error)) error {\n//\t// 2. 从数据库中获取数据\n//\tresult, err := dbQueryFunc()\n//\tif err != nil {\n//\t\treturn err\n//\t}\n//\terr = copier.Copy(value, result)\n//\tif err != nil {\n//\t\tglobal.Logger.Errorf(\"拷贝数据失败: %v\", err)\n//\t\treturn err\n//\t}\n//\t// 3. 将查询结果写回缓存\n//\tif err = SetString(cacheKey, result); err != nil {\n//\t\tglobal.Logger.Errorf(\"写入redis失败: %v\", err)\n//\t\treturn err\n//\t}\n//\treturn err\n//}\n//\n//func RemoveData(key string) {\n//\tglobal.Rdb.Del(key)\n//}\n"
  },
  {
    "path": "utils/setting/setting.go",
    "content": "package setting\n\nimport (\n\t\"log\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/spf13/viper\"\n)\n\nfunc ConfigInit() {\n\t// 设置配置文件的名字\n\tviper.SetConfigName(\"config\")\n\t// 设置配置文件的类型\n\tviper.SetConfigType(\"yaml\")\n\n\t// 获取当前工作目录\n\tcurrentDir, err := os.Getwd()\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to get current directory: %v\", err)\n\t}\n\n\t// 尝试多个可能的配置文件路径\n\tconfigPaths := []string{\n\t\t\"./conf\",           // 当前目录下的conf\n\t\t\"../conf\",          // 上级目录下的conf\n\t\t\"../../conf\",       // 上上级目录下的conf\n\t\t\"../../../conf\",    // 上上上级目录下的conf\n\t\t\"../../../../conf\", // 上上上上级目录下的conf\n\t\t\"conf\",             // 直接conf目录\n\t}\n\n\t// 添加配置文件的路径\n\tfor _, path := range configPaths {\n\t\tviper.AddConfigPath(path)\n\t}\n\n\t// 如果设置了PROJECT_ROOT环境变量，也添加该路径\n\tif projectRoot := os.Getenv(\"PROJECT_ROOT\"); projectRoot != \"\" {\n\t\tviper.AddConfigPath(filepath.Join(projectRoot, \"conf\"))\n\t}\n\n\t// 尝试读取配置文件\n\terr = viper.ReadInConfig()\n\tif err != nil {\n\t\t// 如果还是找不到配置文件，尝试从项目根目录查找\n\t\t// 通过查找go.mod文件来确定项目根目录\n\t\tprojectRoot := findProjectRoot(currentDir)\n\t\tif projectRoot != \"\" {\n\t\t\tviper.Reset()\n\t\t\tviper.SetConfigName(\"config\")\n\t\t\tviper.SetConfigType(\"yaml\")\n\t\t\tviper.AddConfigPath(filepath.Join(projectRoot, \"conf\"))\n\t\t\terr = viper.ReadInConfig()\n\t\t}\n\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"Fail to parse 'conf/config.yml': %v\", err)\n\t\t}\n\t}\n}\n\n// findProjectRoot 通过查找go.mod文件来确定项目根目录\nfunc findProjectRoot(currentDir string) string {\n\tdir := currentDir\n\tfor {\n\t\t// 检查当前目录是否有go.mod文件\n\t\tif _, err := os.Stat(filepath.Join(dir, \"go.mod\")); err == nil {\n\t\t\treturn dir\n\t\t}\n\n\t\t// 向上查找父目录\n\t\tparent := filepath.Dir(dir)\n\t\tif parent == dir {\n\t\t\t// 已经到达根目录\n\t\t\tbreak\n\t\t}\n\t\tdir = parent\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "utils/time_utils.go",
    "content": "package utils\n\nimport (\n\t\"DiTing-Go/global\"\n\t\"strconv\"\n\t\"time\"\n)\n\nfunc TimestampStrToTimeStr(str *string) (*string, error) {\n\tif str != nil && *str != \"\" {\n\t\t// 时间戳转时间\n\t\ttimestamp, err := strconv.ParseInt(*str, 10, 64)\n\t\tif err != nil {\n\t\t\tglobal.Logger.Errorf(\"时间戳转换失败 %s\", err)\n\t\t\treturn nil, err\n\t\t}\n\t\tcursor := time.Unix(0, timestamp)\n\t\tcursorStr := cursor.Format(time.RFC3339Nano)\n\t\treturn &cursorStr, nil\n\t}\n\treturn nil, nil\n}\n"
  },
  {
    "path": "websocket/domain/enum/ws_type.go",
    "content": "package enum\n\nconst (\n\tNewMessage = 4\n)\n"
  },
  {
    "path": "websocket/domain/vo/resp/new_message_resp.go",
    "content": "package resp\n\ntype NewMessageResp struct {\n\tType int `json:\"type\"` // 消息类型\n}\n"
  },
  {
    "path": "websocket/global/global.go",
    "content": "package global\n\nimport (\n\t\"github.com/gorilla/websocket\"\n\tcmap \"github.com/orcaman/concurrent-map/v2\"\n\t\"sync\"\n)\n\ntype Channels struct {\n\tUid         int64\n\tChannelList []*websocket.Conn\n\tMu          *sync.RWMutex\n}\ntype User struct {\n\tUid     int64\n\tChannel *websocket.Conn\n}\ntype Msg struct {\n\tUid int64\n}\n\n// UserChannelMap 用户和channel的映射\nvar UserChannelMap = cmap.New[*Channels]()\n"
  },
  {
    "path": "websocket/service/websocket_service.go",
    "content": "package service\n\nimport (\n\tglobal2 \"DiTing-Go/global\"\n\t\"DiTing-Go/utils/jwt\"\n\t\"DiTing-Go/websocket/global\"\n\t\"fmt\"\n\t\"github.com/gorilla/websocket\"\n\t\"github.com/pkg/errors\"\n\t\"log\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n)\n\n// TODO:连接断开处理\n// 定义一个升级器，将普通的http连接升级为websocket连接\nvar upgrader = &websocket.Upgrader{\n\t//定义读写缓冲区大小\n\tWriteBufferSize: 1024,\n\tReadBufferSize:  1024,\n\t//校验请求\n\tCheckOrigin: func(r *http.Request) bool {\n\t\t//如果不是get请求，返回错误\n\t\tif r.Method != \"GET\" {\n\t\t\tfmt.Println(\"请求方式错误\")\n\t\t\treturn false\n\t\t}\n\t\t//还可以根据其他需求定制校验规则\n\t\treturn true\n\t},\n}\n\n// Connect 建立WebSocket连接\nfunc Connect(w http.ResponseWriter, r *http.Request) {\n\t//先获得Http的token中的uid\n\t//url中的获取token参数\n\tparams := r.URL.Query()\n\ttoken := params.Get(\"token\")\n\ttokenInfo, err := jwt.ParseToken(token)\n\tif err != nil {\n\t\tglobal2.Logger.Errorf(\"无权限访问: %v\", err)\n\t\treturn\n\t}\n\tuid := &tokenInfo.Uid\n\t// Upgrade our raw HTTP connection to a websocket based one\n\tconn, err := upgrader.Upgrade(w, r, nil)\n\t// 关闭连接\n\tdefer conn.Close()\n\tif err != nil {\n\t\tlog.Print(\"Error during connection upgradation:\", err)\n\t\treturn\n\t}\n\n\t//连接成功后注册用户\n\t// 将uid转换为string\n\tstringUid := strconv.FormatInt(*uid, 10)\n\tuserChannel := global.Channels{\n\t\tUid:         *uid,\n\t\tChannelList: make([]*websocket.Conn, 0),\n\t\tMu:          new(sync.RWMutex),\n\t}\n\tuser := global.User{\n\t\tUid:     *uid,\n\t\tChannel: conn,\n\t}\n\tglobal.UserChannelMap.Set(stringUid, &userChannel)\n\tuserChannelPtr, _ := global.UserChannelMap.Get(stringUid)\n\t// TODO:加锁方式是否正确\n\t// 将连接加入到用户的channel中\n\tuserChannelPtr.Mu.Lock()\n\tuserChannelPtr.ChannelList = append(userChannelPtr.ChannelList, conn)\n\tuserChannelPtr.Mu.Unlock()\n\t// 定时发送心跳消息\n\tgo heatBeat(&user)\n\t// 监听连接关闭事件\n\tfor {\n\t\t_, _, err := conn.ReadMessage()\n\t\tif err != nil {\n\t\t\tdisConnect(&user)\n\t\t\tbreak\n\t\t}\n\t}\n}\n\n// Send 发送空消息代表有新消息，前端收到消息后再去后端拉取消息\nfunc Send(uid int64, value []byte) error {\n\tstringUid := strconv.FormatInt(uid, 10)\n\tchannels, _ := global.UserChannelMap.Get(stringUid)\n\t// 用户不在线，直接返回\n\tif channels == nil {\n\t\treturn nil\n\t}\n\tfor _, conn := range channels.ChannelList {\n\t\t// 发送空消息，代表有新消息\n\t\terr := conn.WriteMessage(websocket.TextMessage, value)\n\t\tif err != nil {\n\t\t\tglobal2.Logger.Errorf(\"发送消息失败: %v\", err)\n\t\t\treturn errors.New(\"Business Error\")\n\t\t}\n\t}\n\treturn nil\n}\n\n// 移除连接\nfunc disConnect(user *global.User) {\n\tstringUid := strconv.FormatInt(user.Uid, 10)\n\tconn := user.Channel\n\tuserChannel, _ := global.UserChannelMap.Get(stringUid)\n\tuserChannel.Mu.Lock()\n\tfor i, item := range userChannel.ChannelList {\n\t\tif item == conn {\n\t\t\tuserChannel.ChannelList = append(userChannel.ChannelList[:i], userChannel.ChannelList[i+1:]...)\n\t\t}\n\n\t}\n\terr := conn.Close()\n\tif err != nil {\n\t\treturn\n\t}\n\tuserChannel.Mu.Unlock()\n}\n\n// 解析jwt\nfunc parseJwt(r *http.Request) (*int64, error) {\n\tauthHeader := r.Header.Get(\"Authorization\")\n\tif authHeader == \"\" {\n\t\treturn nil, errors.New(\"无权限访问\")\n\t}\n\t// 按空格分割\n\tparts := strings.SplitN(authHeader, \" \", 2)\n\tif !(len(parts) == 2 && parts[0] == \"Bearer\") {\n\t\treturn nil, errors.New(\"无权限访问\")\n\t}\n\t// parts[1]是获取到的tokenString，我们使用之前定义好的解析JWT的函数来解析它\n\ttoken, err := jwt.ParseToken(parts[1])\n\tif err != nil {\n\t\treturn nil, errors.New(\"无权限访问\")\n\t}\n\treturn &token.Uid, nil\n}\n\n// 心跳检测\nfunc heatBeat(user *global.User) {\n\tconn := user.Channel\n\t// TODO:心跳时间从配置文件中读取\n\tticker := time.NewTicker(5 * time.Second)\n\tdefer ticker.Stop()\n\n\tfor {\n\t\tselect {\n\t\tcase <-ticker.C:\n\t\t\terr := conn.WriteMessage(websocket.PingMessage, []byte(\"heartbeat\"))\n\t\t\tif err != nil {\n\t\t\t\tlog.Println(err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// TODO:开发时关闭\n\t\t\t//conn.SetReadDeadline(time.Now().Add(10 * time.Second))\n\t\t\tconn.SetReadDeadline(time.Now().Add(24 * 360 * time.Hour))\n\t\t\t_, _, err = conn.ReadMessage()\n\t\t\tif err != nil {\n\t\t\t\tdisConnect(user)\n\t\t\t\tlog.Println(\"heartbeat response error:\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n}\n"
  }
]