├── .idea ├── .gitignore ├── GoChatCraft.iml ├── modules.xml └── vcs.xml ├── LICENSE ├── README-CN.md ├── README.md ├── assets └── download │ └── fluentui_emoji_icon_data.zip ├── chatcraft.sql ├── common ├── md5.go ├── private.pem ├── resp.go └── rsa.go ├── config-debug.yaml ├── config └── config.go ├── dao ├── community.go ├── email_code.go ├── relation.go ├── user.go └── user_story.go ├── docs ├── docs.go ├── swagger.json └── swagger.yaml ├── global ├── global.go └── source_email.go ├── go.mod ├── go.sum ├── initialize ├── config.go ├── db.go └── logger.go ├── main.go ├── middlewear └── jwt.go ├── models ├── community.go ├── email_code.go ├── message.go ├── relation.go ├── user_basic.go ├── user_collect.go ├── user_story.go ├── user_story_comment.go └── user_story_like.go ├── router └── router.go ├── service ├── attach_upload.go ├── community.go ├── email_code.go ├── message.go ├── relation.go ├── send_file.go ├── user.go └── user_story.go └── test └── main.go /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /.idea/GoChatCraft.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README-CN.md: -------------------------------------------------------------------------------- 1 | ![back.png](https://cdn.nlark.com/yuque/0/2024/png/34940884/1706452639127-13565545-9978-4296-8ee1-ff2f4b982170.png#averageHue=%2394908b&clientId=u311e081c-a6cb-4&from=drop&id=Gtb5o&originHeight=1233&originWidth=1812&originalType=binary&ratio=1&rotation=0&showTitle=false&size=1033034&status=done&style=none&taskId=u22e42aa0-226b-4af7-b773-2f7d827fa68&title=) 2 | 3 | 【[English](https://github.com/taxze6/Go-Chat-Craft/blob/master/README.md) | 中文文档】 4 | 5 | 客户端地址:https://github.com/taxze6/flutter-chat-craft 6 | 7 | 服务端地址:https://github.com/taxze6/Go-Chat-Craft 8 | 9 | Android Apk体验地址(使用蒲公英内测,如果无法下载则表明达到了500次/天的限额): 10 | 11 | https://www.pgyer.com/XxRr8v 12 | 13 | ![XxRr8v.png](https://cdn.nlark.com/yuque/0/2024/png/34940884/1706530855500-2b0da2e8-6a14-40c5-962f-aed1d4bb41e5.png#averageHue=%23fbc403&clientId=u6b1a273d-1ecc-4&from=paste&height=210&id=ud0d638c1&originHeight=210&originWidth=210&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4349&status=done&style=none&taskId=u45f2a28a-f82e-4b21-95ac-5fbba5c4fed&title=&width=210) 14 | 15 | 测试账号1:taxze,密码:123456789 16 | 17 | 测试账号2:taxze2,密码:123456789 18 | 19 | 也可通过邮箱验证码注册账号,建议使用qq邮箱或gmail邮箱。 20 | 21 | Chat-Craft目前已经实现了即时通讯的基本功能,支持Android和IOS。快来下载并体验吧! 22 | 23 | ## 🎬演示视频 24 | 25 | 点击图片查看演示视频。 26 | 27 | 感谢帮助我制作这个视频的朋友。如果你也需要制作一个类似的视频,你可以通过这个电子邮件联系他:tjl2945428088@icloud.com 28 | 29 | [![Demo video](https://i.ytimg.com/vi/S0c2FW29nNg/maxresdefault.jpg)](https://www.youtube.com/watch?v=S0c2FW29nNg "Demo video") 30 | 31 | ## 📖 概述 32 | 33 | 社交应用程序在世界各地都很流行,例如 Facebook、Line、Whatsapp。如果您渴望打造一款独具个性的社交平台,Chat-Craft项目将是您不可或缺的理想之选。Chat-Craft是一款跨平台移动应用,采用了Golang作为后端服务端技术,以及Flutter作为前端客户端技术。该应用旨在提供高效、稳定且跨平台的用户体验,将现代的移动应用开发技术和高性能的后端服务端技术相结合。 34 | 35 | 在Chat-Craft项目中,作者精心雕琢了客户端UI,灵感汲取于各大即时通讯应用及最新的应用设计规范。这独特的设计巧妙地融合了各方优点,呈现出一种令人愉悦的视觉体验。这样的UI不仅为项目赋予了独特的美感,同时也为学习者创造了更为有趣的学习环境。 36 | 37 | ## 🎨使用技术 38 | 39 | ![技术图.png](https://cdn.nlark.com/yuque/0/2024/png/34940884/1706494225234-5d89ecce-70a6-4c6e-86d7-859be4b2d4c0.png#averageHue=%23faf7f4&clientId=u1ec3d590-b191-4&from=paste&height=741&id=u297e2b92&originHeight=741&originWidth=1030&originalType=binary&ratio=1&rotation=0&showTitle=false&size=107597&status=done&style=none&taskId=u0f98aafc-f7ee-4a06-8850-6a39e1fbff5&title=&width=1030) 40 | 41 | ## 🔨部分页面截图 42 | 43 | | ![Splash——欢迎页.png](https://cdn.nlark.com/yuque/0/2024/png/34940884/1706495772905-c591ed63-1056-4734-bd80-d6e96d21e0dd.png#averageHue=%23fcf9f2&clientId=u1ec3d590-b191-4&from=drop&id=uec7d6408&originHeight=812&originWidth=375&originalType=binary&ratio=1&rotation=0&showTitle=false&size=20557&status=done&style=none&taskId=ue698b7df-e4cc-4d8e-8b7b-894db87d866&title=) | ![login——登录页.png](https://cdn.nlark.com/yuque/0/2024/png/34940884/1706495780914-1a2cfd47-00aa-4190-b5c1-23c4d6999b0a.png#averageHue=%23fbfbfb&clientId=u1ec3d590-b191-4&from=drop&id=u2dc4704f&originHeight=812&originWidth=375&originalType=binary&ratio=1&rotation=0&showTitle=false&size=25951&status=done&style=none&taskId=u8b2d6d10-13cd-4950-aa48-ad6d5f1b3ee&title=) | ![login——登录页1 – 1.png](https://cdn.nlark.com/yuque/0/2024/png/34940884/1706495783743-bb86193d-ea72-471c-ac14-a5af86a3f7f5.png#averageHue=%23faf7ef&clientId=u1ec3d590-b191-4&from=drop&id=u19bfacbd&originHeight=812&originWidth=375&originalType=binary&ratio=1&rotation=0&showTitle=false&size=30750&status=done&style=none&taskId=u5369b5a9-5459-4085-a2bc-5a7d4ef36bf&title=) | 44 | | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | 45 | | ![OTP.png](https://cdn.nlark.com/yuque/0/2024/png/34940884/1706495803753-146d624e-7d93-44e5-9014-93f0d98878ef.png#averageHue=%23fcfbf7&clientId=u1ec3d590-b191-4&from=drop&id=ua6e398f2&originHeight=812&originWidth=375&originalType=binary&ratio=1&rotation=0&showTitle=false&size=18940&status=done&style=none&taskId=u11cee891-e301-4bca-9aae-4829550a4a1&title=) | ![home.png](https://cdn.nlark.com/yuque/0/2024/png/34940884/1706495809556-17ead725-7db2-46af-8950-d7579ee47b7e.png#averageHue=%23f3f2f0&clientId=u1ec3d590-b191-4&from=drop&id=u4a604d5e&originHeight=812&originWidth=375&originalType=binary&ratio=1&rotation=0&showTitle=false&size=102104&status=done&style=none&taskId=ube64ad3b-fd7d-4498-a4b0-5a0f39868ca&title=) | ![Chat.png](https://cdn.nlark.com/yuque/0/2024/png/34940884/1706495822312-bcfa98d9-6ef1-4b69-b6be-c2e32eff47d8.png#averageHue=%23f8f1d9&clientId=u1ec3d590-b191-4&from=drop&id=u48eb43a7&originHeight=812&originWidth=375&originalType=binary&ratio=1&rotation=0&showTitle=false&size=36515&status=done&style=none&taskId=u9b1f3b51-c484-479f-9a76-948d9058018&title=) | 46 | | ![Chat – 1.png](https://cdn.nlark.com/yuque/0/2024/png/34940884/1706495825457-822b33df-ec91-43e6-80af-b6e25d644e83.png#averageHue=%23aba89d&clientId=u1ec3d590-b191-4&from=drop&id=vfqjH&originHeight=812&originWidth=375&originalType=binary&ratio=1&rotation=0&showTitle=false&size=66136&status=done&style=none&taskId=uc0d3f3b8-e098-4b97-b12d-2eb81f6bd47&title=) | ![add-friend – 1.png](https://cdn.nlark.com/yuque/0/2024/png/34940884/1706495829943-9baff26d-0ac9-4c11-9ba8-3f40d5c32ad0.png#averageHue=%23fbf2e4&clientId=u1ec3d590-b191-4&from=drop&id=zoswb&originHeight=812&originWidth=375&originalType=binary&ratio=1&rotation=0&showTitle=false&size=80104&status=done&style=none&taskId=u4fe3b98a-de17-4565-a0cd-a191c07cad7&title=) | ![Other-Mine.png](https://cdn.nlark.com/yuque/0/2024/png/34940884/1706495835896-c353bfe3-ae93-48c2-9a95-1f33728d96ff.png#averageHue=%23e2dbd6&clientId=u1ec3d590-b191-4&from=drop&id=LDUnb&originHeight=812&originWidth=375&originalType=binary&ratio=1&rotation=0&showTitle=false&size=147881&status=done&style=none&taskId=ue9f91513-d982-478c-b4e9-5f7993bb610&title=) | 47 | | ![Story details.png](https://cdn.nlark.com/yuque/0/2024/png/34940884/1706496016597-01861233-88f3-4b59-84b7-b62ab931e8c4.png#averageHue=%236b645d&clientId=u1ec3d590-b191-4&from=paste&height=812&id=u8c8244d5&originHeight=812&originWidth=375&originalType=binary&ratio=1&rotation=0&showTitle=false&size=389269&status=done&style=none&taskId=u17db3bd8-852f-4f8b-ac5e-177dfc16b31&title=&width=375) | ![Story details – 2.png](https://cdn.nlark.com/yuque/0/2024/png/34940884/1706496018272-c98730a7-3fe5-428d-93b1-ee2743a2ad4f.png#averageHue=%23766f68&clientId=u1ec3d590-b191-4&from=paste&height=812&id=u60de0232&originHeight=812&originWidth=375&originalType=binary&ratio=1&rotation=0&showTitle=false&size=394531&status=done&style=none&taskId=ub8f93179-c7d7-46df-be5f-139e131ca7c&title=&width=375) | ![Story details – 1.png](https://cdn.nlark.com/yuque/0/2024/png/34940884/1706496020425-cdf17e85-8001-4576-87fa-fffbe2c201c4.png#averageHue=%236f6d6b&clientId=u1ec3d590-b191-4&from=paste&height=812&id=u151ac970&originHeight=812&originWidth=375&originalType=binary&ratio=1&rotation=0&showTitle=false&size=235525&status=done&style=none&taskId=uce344479-5488-43a2-9762-9f213bd3751&title=&width=375) | 48 | 49 | ## 🖥️运行项目 50 | 51 | 首先,请按照下列步骤操作: 52 | 53 | 1.克隆 GitHub 存储库:首先使用以下命令克隆存储库。 54 | 55 | ``` 56 | //拉取客户端项目 57 | git clone https://github.com/taxze6/flutter-chat-craft.git 58 | 59 | //拉取服务端项目 60 | gie clone https://github.com/taxze6/Go-Chat-Craft 61 | ``` 62 | 63 | 2.检查本地开发环境:确保您拥有 `3.16.9` 或更高版本的 Flutter 环境和`1.19.3`或更高版本的 golang。 64 | 65 | ``` 66 | flutter doctor 67 | [!] Flutter (Channel stable, 3.16.9, on Microsoft Windows [版本 10.0.19045.3930], locale zh-CN) 68 | [√] Windows Version (Installed version of Windows is version 10 or higher) 69 | [√] Android toolchain - develop for Android devices (Android SDK version 30.0.3) 70 | [√] Chrome - develop for the web 71 | [√] Visual Studio - develop Windows apps (Visual Studio Professional 2022 17.6.4) 72 | [√] Android Studio (version 4.0) 73 | [√] Android Studio (version 2021.2) 74 | [√] IntelliJ IDEA Ultimate Edition (version 2020.2) 75 | [√] Connected device (3 available) 76 | 77 | go version 78 | go version go1.19.3 windows/386 79 | ``` 80 | 81 | 3.加载项目依赖:分别加载flutter客户端的依赖和golang服务端的依赖。 82 | 83 | ``` 84 | //加载flutter项目的依赖 85 | flutter pub get 86 | //查看flutter项目的依赖关系 87 | flutter pub deps 88 | 89 | //加载golang项目的依赖 90 | go mod download 91 | //查看golang当前项目的所有依赖 92 | go list -m all 93 | ``` 94 | 95 | 4.进行项目配置:配置数据库、中间件、ip、运行端口。 96 | 97 | ``` 98 | //先配置golang项目中的config-debug.yaml 99 | //配置项目运行端口、数据库和中间件对应的ip、端口和账号密码 100 | port: '8889' 101 | host: '127.0.0.1' 102 | mysql: 103 | host: '127.0.0.1' 104 | port: '3306' 105 | name: 'chatcraft' 106 | user: 'root' 107 | password: 'root@123321' 108 | redis: 109 | host: '127.0.0.1' 110 | port: '6379' 111 | rabbitmq: 112 | host: "127.0.0.1" 113 | port: "5672" 114 | user: "guest" 115 | password: "guest" 116 | 117 | //再配置flutter项目flutter_chat_craft\lib\common下的ip_config.dart 118 | class IpConfig { 119 | //Replace with server address 120 | static const ip = "127.0.0.1:8889"; 121 | } 122 | ``` 123 | 124 | 5.对本地的数据库执行SQL:对mysql执行Go-Chat-Craft下的chatcraft.sql 125 | 126 | 6.运行客户端与服务端项目 127 | 128 | ## 🎉未来 129 | 130 | 一个好的开源项目,一定是长时间的迭代出来的,Chat Craft作为一个”婴儿“还有着很长的成长道路。然而,正是这种挑战使得它有机会成为一个优秀的开源项目。在接下来的旅程中,Chat Craft将不断迭代和改进,让我们一起见证它的成长吧!非常欢迎大家参与这款开源项目,有任何改进项目的想法都可以通过github的issuse告诉我~ 131 | 132 | ## 🎈最后 133 | 134 | 如果你在这个项目中遇到任何问题,请随时加我微信。顺便说一下,这个项目有一个微信交流群!欢迎加入~ 135 | 136 | 🌱我正在寻找一个在杭州或远程Flutter软件工程师的职位。请随时与我联系。 137 | 138 | 联系方式 139 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![back.png](https://cdn.nlark.com/yuque/0/2024/png/34940884/1706452639127-13565545-9978-4296-8ee1-ff2f4b982170.png#averageHue=%2394908b&clientId=u311e081c-a6cb-4&from=drop&id=Gtb5o&originHeight=1233&originWidth=1812&originalType=binary&ratio=1&rotation=0&showTitle=false&size=1033034&status=done&style=none&taskId=u22e42aa0-226b-4af7-b773-2f7d827fa68&title=) 2 | 3 | 【English | [中文文档](https://github.com/taxze6/Go-Chat-Craft/blob/master/README-CN.md)】 4 | 5 | Client project address:[https://github.com/taxze6/flutter-chat-craft](https://github.com/taxze6/flutter-chat-craft) 6 | 7 | Server project address:[https://github.com/taxze6/Go-Chat-Craft](https://github.com/taxze6/Go-Chat-Craft) 8 | 9 | Android Apk trial address (using Dandelion internal testing, if it cannot be downloaded, it means the limit of 500 times/day has been reached): 10 | 11 | [https://www.pgyer.com/XxRr8v](https://www.pgyer.com/XxRr8v) 12 | 13 | ![XxRr8v.png](https://cdn.nlark.com/yuque/0/2024/png/34940884/1706530855500-2b0da2e8-6a14-40c5-962f-aed1d4bb41e5.png#averageHue=%23fbc403&clientId=u6b1a273d-1ecc-4&from=paste&height=210&id=ud0d638c1&originHeight=210&originWidth=210&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4349&status=done&style=none&taskId=u45f2a28a-f82e-4b21-95ac-5fbba5c4fed&title=&width=210) 14 | 15 | Test account 1:taxze password:123456789 16 | 17 | Test account 2:taxze2 password:123456789 18 | 19 | You can also register an account through email verification code. It is recommended to use qq email or gmail email. 20 | 21 | Chat-Craft has currently implemented the basic functions of instant messaging and supports Android and IOS. Come download and experience it! 22 | 23 | ## 🎬Demo video 24 | 25 | Click on the picture to view the demonstration video. 26 | 27 | Thanks to the friend who helped me make this video. If you also need to make a similar video, you can contact him through this email: tjl2945428088@icloud.com 28 | 29 | [![Demo video](https://i.ytimg.com/vi/S0c2FW29nNg/maxresdefault.jpg)](https://www.youtube.com/watch?v=S0c2FW29nNg "Demo video") 30 | 31 | ## 📖 Overview 32 | 33 | - Social apps are popular all over the world such as Facebook, Line, Whatsapp. If you are eager to create a unique social platform, the Chat-Craft project will be an indispensable ideal choice for you. Chat-Craft is a cross-platform mobile application that uses Golang as the back-end server technology and Flutter as the front-end client technology. The application is designed to provide an efficient, stable and cross-platform user experience, combining modern mobile application development technology with high-performance back-end server technology. 34 | - In the Chat-Craft project, the author carefully crafted the client UI, drawing inspiration from major instant messaging applications and the latest application design specifications. This unique design cleverly combines the advantages of all aspects to present a pleasing visual experience. Such a UI not only gives the project a unique aesthetic, but also creates a more interesting learning environment for learners. 35 | 36 | ## 🎨Technology used 37 | 38 | ![技术图.png](https://cdn.nlark.com/yuque/0/2024/png/34940884/1706494225234-5d89ecce-70a6-4c6e-86d7-859be4b2d4c0.png#averageHue=%23faf7f4&clientId=u1ec3d590-b191-4&from=paste&height=741&id=u297e2b92&originHeight=741&originWidth=1030&originalType=binary&ratio=1&rotation=0&showTitle=false&size=107597&status=done&style=none&taskId=u0f98aafc-f7ee-4a06-8850-6a39e1fbff5&title=&width=1030) 39 | 40 | ## 🔨Screenshot of part of the page 41 | 42 | | ![Splash——欢迎页.png](https://cdn.nlark.com/yuque/0/2024/png/34940884/1706495772905-c591ed63-1056-4734-bd80-d6e96d21e0dd.png#averageHue=%23fcf9f2&clientId=u1ec3d590-b191-4&from=drop&id=uec7d6408&originHeight=812&originWidth=375&originalType=binary&ratio=1&rotation=0&showTitle=false&size=20557&status=done&style=none&taskId=ue698b7df-e4cc-4d8e-8b7b-894db87d866&title=) | ![login——登录页.png](https://cdn.nlark.com/yuque/0/2024/png/34940884/1706495780914-1a2cfd47-00aa-4190-b5c1-23c4d6999b0a.png#averageHue=%23fbfbfb&clientId=u1ec3d590-b191-4&from=drop&id=u2dc4704f&originHeight=812&originWidth=375&originalType=binary&ratio=1&rotation=0&showTitle=false&size=25951&status=done&style=none&taskId=u8b2d6d10-13cd-4950-aa48-ad6d5f1b3ee&title=) | ![login——登录页1 – 1.png](https://cdn.nlark.com/yuque/0/2024/png/34940884/1706495783743-bb86193d-ea72-471c-ac14-a5af86a3f7f5.png#averageHue=%23faf7ef&clientId=u1ec3d590-b191-4&from=drop&id=u19bfacbd&originHeight=812&originWidth=375&originalType=binary&ratio=1&rotation=0&showTitle=false&size=30750&status=done&style=none&taskId=u5369b5a9-5459-4085-a2bc-5a7d4ef36bf&title=) | 43 | | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | 44 | | ![OTP.png](https://cdn.nlark.com/yuque/0/2024/png/34940884/1706495803753-146d624e-7d93-44e5-9014-93f0d98878ef.png#averageHue=%23fcfbf7&clientId=u1ec3d590-b191-4&from=drop&id=ua6e398f2&originHeight=812&originWidth=375&originalType=binary&ratio=1&rotation=0&showTitle=false&size=18940&status=done&style=none&taskId=u11cee891-e301-4bca-9aae-4829550a4a1&title=) | ![home.png](https://cdn.nlark.com/yuque/0/2024/png/34940884/1706495809556-17ead725-7db2-46af-8950-d7579ee47b7e.png#averageHue=%23f3f2f0&clientId=u1ec3d590-b191-4&from=drop&id=u4a604d5e&originHeight=812&originWidth=375&originalType=binary&ratio=1&rotation=0&showTitle=false&size=102104&status=done&style=none&taskId=ube64ad3b-fd7d-4498-a4b0-5a0f39868ca&title=) | ![Chat.png](https://cdn.nlark.com/yuque/0/2024/png/34940884/1706495822312-bcfa98d9-6ef1-4b69-b6be-c2e32eff47d8.png#averageHue=%23f8f1d9&clientId=u1ec3d590-b191-4&from=drop&id=u48eb43a7&originHeight=812&originWidth=375&originalType=binary&ratio=1&rotation=0&showTitle=false&size=36515&status=done&style=none&taskId=u9b1f3b51-c484-479f-9a76-948d9058018&title=) | 45 | | ![Chat – 1.png](https://cdn.nlark.com/yuque/0/2024/png/34940884/1706495825457-822b33df-ec91-43e6-80af-b6e25d644e83.png#averageHue=%23aba89d&clientId=u1ec3d590-b191-4&from=drop&id=vfqjH&originHeight=812&originWidth=375&originalType=binary&ratio=1&rotation=0&showTitle=false&size=66136&status=done&style=none&taskId=uc0d3f3b8-e098-4b97-b12d-2eb81f6bd47&title=) | ![add-friend – 1.png](https://cdn.nlark.com/yuque/0/2024/png/34940884/1706495829943-9baff26d-0ac9-4c11-9ba8-3f40d5c32ad0.png#averageHue=%23fbf2e4&clientId=u1ec3d590-b191-4&from=drop&id=zoswb&originHeight=812&originWidth=375&originalType=binary&ratio=1&rotation=0&showTitle=false&size=80104&status=done&style=none&taskId=u4fe3b98a-de17-4565-a0cd-a191c07cad7&title=) | ![Other-Mine.png](https://cdn.nlark.com/yuque/0/2024/png/34940884/1706495835896-c353bfe3-ae93-48c2-9a95-1f33728d96ff.png#averageHue=%23e2dbd6&clientId=u1ec3d590-b191-4&from=drop&id=LDUnb&originHeight=812&originWidth=375&originalType=binary&ratio=1&rotation=0&showTitle=false&size=147881&status=done&style=none&taskId=ue9f91513-d982-478c-b4e9-5f7993bb610&title=) | 46 | | ![Story details.png](https://cdn.nlark.com/yuque/0/2024/png/34940884/1706496016597-01861233-88f3-4b59-84b7-b62ab931e8c4.png#averageHue=%236b645d&clientId=u1ec3d590-b191-4&from=paste&height=812&id=u8c8244d5&originHeight=812&originWidth=375&originalType=binary&ratio=1&rotation=0&showTitle=false&size=389269&status=done&style=none&taskId=u17db3bd8-852f-4f8b-ac5e-177dfc16b31&title=&width=375) | ![Story details – 2.png](https://cdn.nlark.com/yuque/0/2024/png/34940884/1706496018272-c98730a7-3fe5-428d-93b1-ee2743a2ad4f.png#averageHue=%23766f68&clientId=u1ec3d590-b191-4&from=paste&height=812&id=u60de0232&originHeight=812&originWidth=375&originalType=binary&ratio=1&rotation=0&showTitle=false&size=394531&status=done&style=none&taskId=ub8f93179-c7d7-46df-be5f-139e131ca7c&title=&width=375) | ![Story details – 1.png](https://cdn.nlark.com/yuque/0/2024/png/34940884/1706496020425-cdf17e85-8001-4576-87fa-fffbe2c201c4.png#averageHue=%236f6d6b&clientId=u1ec3d590-b191-4&from=paste&height=812&id=u151ac970&originHeight=812&originWidth=375&originalType=binary&ratio=1&rotation=0&showTitle=false&size=235525&status=done&style=none&taskId=uce344479-5488-43a2-9762-9f213bd3751&title=&width=375) | 47 | 48 | ## 🖥️Run 49 | 50 | To get started, follow these steps: 51 | 52 | **1.Clone the GitHub Repository:** Begin by cloning the repository using the command. 53 | 54 | ``` 55 | //Pull client project 56 | git clone https://github.com/taxze6/flutter-chat-craft.git 57 | 58 | //Pull server project 59 | gie clone https://github.com/taxze6/Go-Chat-Craft 60 | ``` 61 | 62 | **2.Check local development environment:** Make sure you have a Flutter environment of version 3.16.9 or higher and a golang version of 1.19.3 or higher. 63 | 64 | ``` 65 | flutter doctor 66 | [!] Flutter (Channel stable, 3.16.9, on Microsoft Windows [版本 10.0.19045.3930], locale zh-CN) 67 | [√] Windows Version (Installed version of Windows is version 10 or higher) 68 | [√] Android toolchain - develop for Android devices (Android SDK version 30.0.3) 69 | [√] Chrome - develop for the web 70 | [√] Visual Studio - develop Windows apps (Visual Studio Professional 2022 17.6.4) 71 | [√] Android Studio (version 4.0) 72 | [√] Android Studio (version 2021.2) 73 | [√] IntelliJ IDEA Ultimate Edition (version 2020.2) 74 | [√] Connected device (3 available) 75 | 76 | go version 77 | go version go1.19.3 windows/386 78 | ``` 79 | 80 | **3. Load project dependencies:** Load the dependencies of the flutter client and the dependencies of the golang server respectively. 81 | 82 | ``` 83 | //Loading the Flutter project dependencies 84 | flutter pub get 85 | //View the dependencies of the flutter project 86 | flutter pub deps 87 | 88 | //Load golang project dependencies 89 | go mod download 90 | //View all dependencies of golang's current project 91 | go list -m all 92 | ``` 93 | 94 | **4. Configure the project:** Configure database, middleware, IP, and running port. 95 | 96 | ``` 97 | //First configure config-debug.yaml in the golang project 98 | //Configure the project running port, database and middleware corresponding IP, port and account password 99 | port: '8889' 100 | host: '127.0.0.1' 101 | mysql: 102 | host: '127.0.0.1' 103 | port: '3306' 104 | name: 'chatcraft' 105 | user: 'root' 106 | password: 'root@123321' 107 | redis: 108 | host: '127.0.0.1' 109 | port: '6379' 110 | rabbitmq: 111 | host: "127.0.0.1" 112 | port: "5672" 113 | user: "guest" 114 | password: "guest" 115 | 116 | //Then configure ip_config.dart under the flutter project flutter_chat_craft\lib\common 117 | class IpConfig { 118 | //Replace with server address 119 | static const ip = "127.0.0.1:8889"; 120 | } 121 | ``` 122 | 123 | **5. Execute SQL on the local database:** Execute chatcraft.sql under Go-Chat-Craft for mysql. 124 | **6. Run client and server projects** 125 | 126 | ## 🎉Future 127 | 128 | A good open source project must be iterated over a long period of time. As a "baby", Chat Craft still has a long way to grow. However, it is this challenge that gives it a chance to become an excellent open source project. In the next journey, Chat Craft will continue to iterate and improve, let us witness its growth together! Everyone is very welcome to participate in this open source project. If you have any ideas for improving the project, please let me know through the issue on github~ 129 | 130 | ## 🎈At Last 131 | 132 | If you encounter any problems with this project, please feel free to add me on WeChat. By the way, there is a WeChat communication group for this project! Welcome to join~ 133 | 134 | 🌱 I'm looking for a Flutter software engineer position in Hangzhou or remotely. Please feel free to contact me. 135 | 136 | 联系方式 137 | -------------------------------------------------------------------------------- /assets/download/fluentui_emoji_icon_data.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taxze6/Go-Chat-Craft/b1841c51730c5df01df65d90fb43d0a914e1a3de/assets/download/fluentui_emoji_icon_data.zip -------------------------------------------------------------------------------- /chatcraft.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Navicat Premium Data Transfer 3 | 4 | Source Server : chat-craft 5 | Source Server Type : MySQL 6 | Source Server Version : 80300 7 | Source Host : localhost:3306 8 | Source Schema : chatcraft 9 | 10 | Target Server Type : MySQL 11 | Target Server Version : 80300 12 | File Encoding : 65001 13 | 14 | Date: 27/01/2024 22:11:18 15 | */ 16 | 17 | SET NAMES utf8mb4; 18 | SET FOREIGN_KEY_CHECKS = 0; 19 | 20 | -- ---------------------------- 21 | -- Table structure for communities 22 | -- ---------------------------- 23 | DROP TABLE IF EXISTS `communities`; 24 | CREATE TABLE `communities` ( 25 | `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT, 26 | `create_at` datetime(3) NULL DEFAULT NULL, 27 | `update_at` datetime(3) NULL DEFAULT NULL, 28 | `delete_at` datetime(3) NULL DEFAULT NULL, 29 | `name` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL, 30 | `owner_id` bigint UNSIGNED NULL DEFAULT NULL, 31 | `type` bigint NULL DEFAULT NULL, 32 | `image` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL, 33 | `desc` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL, 34 | PRIMARY KEY (`id`) USING BTREE, 35 | INDEX `idx_communities_delete_at`(`delete_at` ASC) USING BTREE 36 | ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Dynamic; 37 | 38 | -- ---------------------------- 39 | -- Table structure for relations 40 | -- ---------------------------- 41 | DROP TABLE IF EXISTS `relations`; 42 | CREATE TABLE `relations` ( 43 | `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT, 44 | `create_at` datetime(3) NULL DEFAULT NULL, 45 | `update_at` datetime(3) NULL DEFAULT NULL, 46 | `delete_at` datetime(3) NULL DEFAULT NULL, 47 | `owner_id` bigint UNSIGNED NULL DEFAULT NULL, 48 | `target_id` bigint UNSIGNED NULL DEFAULT NULL, 49 | `type` bigint NULL DEFAULT NULL, 50 | `desc` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL, 51 | PRIMARY KEY (`id`) USING BTREE, 52 | INDEX `idx_relations_delete_at`(`delete_at` ASC) USING BTREE 53 | ) ENGINE = InnoDB AUTO_INCREMENT = 19 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Dynamic; 54 | 55 | -- ---------------------------- 56 | -- Table structure for user_basics 57 | -- ---------------------------- 58 | DROP TABLE IF EXISTS `user_basics`; 59 | CREATE TABLE `user_basics` ( 60 | `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT, 61 | `create_at` datetime(3) NULL DEFAULT NULL, 62 | `update_at` datetime(3) NULL DEFAULT NULL, 63 | `delete_at` datetime(3) NULL DEFAULT NULL, 64 | `name` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL, 65 | `pass_word` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL, 66 | `avatar` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL, 67 | `gender` varchar(6) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT 'male', 68 | `phone` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL, 69 | `email` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL, 70 | `motto` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL, 71 | `identity` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL, 72 | `client_ip` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL, 73 | `client_port` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL, 74 | `salt` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL, 75 | `login_time` datetime(3) NULL DEFAULT NULL, 76 | `heart_beat_time` datetime(3) NULL DEFAULT NULL, 77 | `login_out_time` datetime(3) NULL DEFAULT NULL, 78 | `is_login_out` tinyint(1) NULL DEFAULT NULL, 79 | `device_info` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL, 80 | PRIMARY KEY (`id`) USING BTREE, 81 | INDEX `idx_user_basics_delete_at`(`delete_at` ASC) USING BTREE 82 | ) ENGINE = InnoDB AUTO_INCREMENT = 16 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Dynamic; 83 | 84 | -- ---------------------------- 85 | -- Table structure for user_stories 86 | -- ---------------------------- 87 | DROP TABLE IF EXISTS `user_stories`; 88 | CREATE TABLE `user_stories` ( 89 | `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT, 90 | `create_at` datetime(3) NULL DEFAULT NULL, 91 | `update_at` datetime(3) NULL DEFAULT NULL, 92 | `delete_at` datetime(3) NULL DEFAULT NULL, 93 | `owner_id` bigint UNSIGNED NULL DEFAULT NULL, 94 | `content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL, 95 | `media` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL, 96 | `type` bigint NULL DEFAULT NULL, 97 | PRIMARY KEY (`id`) USING BTREE, 98 | INDEX `idx_user_stories_delete_at`(`delete_at` ASC) USING BTREE 99 | ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Dynamic; 100 | 101 | -- ---------------------------- 102 | -- Table structure for user_story_comments 103 | -- ---------------------------- 104 | DROP TABLE IF EXISTS `user_story_comments`; 105 | CREATE TABLE `user_story_comments` ( 106 | `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT, 107 | `create_at` datetime(3) NULL DEFAULT NULL, 108 | `update_at` datetime(3) NULL DEFAULT NULL, 109 | `delete_at` datetime(3) NULL DEFAULT NULL, 110 | `user_story_id` bigint UNSIGNED NULL DEFAULT NULL, 111 | `comment_owner_id` bigint UNSIGNED NULL DEFAULT NULL, 112 | `comment_content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL, 113 | `type` bigint NULL DEFAULT NULL, 114 | PRIMARY KEY (`id`) USING BTREE, 115 | INDEX `idx_user_story_comments_delete_at`(`delete_at` ASC) USING BTREE 116 | ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Dynamic; 117 | 118 | -- ---------------------------- 119 | -- Table structure for user_story_likes 120 | -- ---------------------------- 121 | DROP TABLE IF EXISTS `user_story_likes`; 122 | CREATE TABLE `user_story_likes` ( 123 | `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT, 124 | `create_at` datetime(3) NULL DEFAULT NULL, 125 | `update_at` datetime(3) NULL DEFAULT NULL, 126 | `delete_at` datetime(3) NULL DEFAULT NULL, 127 | `user_story_id` bigint UNSIGNED NULL DEFAULT NULL, 128 | `like_owner_id` bigint UNSIGNED NULL DEFAULT NULL, 129 | PRIMARY KEY (`id`) USING BTREE, 130 | INDEX `idx_user_story_likes_delete_at`(`delete_at` ASC) USING BTREE 131 | ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Dynamic; 132 | 133 | SET FOREIGN_KEY_CHECKS = 1; 134 | -------------------------------------------------------------------------------- /common/md5.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/hex" 6 | "fmt" 7 | "io" 8 | "strings" 9 | ) 10 | 11 | // The Md5encoder returns the lowercase value after encryption 12 | func Md5encoder(code string) string { 13 | m := md5.New() 14 | _, _ = io.WriteString(m, code) 15 | return hex.EncodeToString(m.Sum(nil)) 16 | } 17 | 18 | // The Md5StrToUpper returns the uppercase value after encryption. 19 | func Md5StrToUpper(code string) string { 20 | return strings.ToUpper(Md5encoder(code)) 21 | } 22 | 23 | // The SaltPassWord function adds salt to the password. 24 | func SaltPassWord(pw string, salt string) string { 25 | saltPW := fmt.Sprintf("%s$%s", Md5encoder(pw), salt) 26 | return saltPW 27 | } 28 | 29 | // The CheckPassWord function verifies the password 30 | func CheckPassWord(rpw, salt, pw string) bool { 31 | return pw == SaltPassWord(rpw, salt) 32 | } 33 | -------------------------------------------------------------------------------- /common/private.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC/L+xGsKHSZKto 3 | T/vUKCTtQ+QJuhcr6Cp1DL8Zgm2sCdLTdDkSKLhvqasbVoFKk3zWLXVbo/7PHw7p 4 | zqM2T/psnLa1Nco6bBf2AtF9jKvxcDDMd9AAyYK/nR89qrE5xSa4wy5oacjrCMtH 5 | QppjEALgjGWZC9AT8B34UEUd5QZb8B3zYgRdwyr40UFNxtUTPv96Zj4ZTWaCYBsW 6 | F75/xbcOLogJGCB3MI52C9tCIzTct+ntraGEzAZsG1OWQP9PodmuVYjv9bhWQvou 7 | CxxBZ7E7hPu3YcbzzTy7MuTBC7yJeoYZpuDiEU1c8vdUlCFV3yJnbhUVYReEVXJT 8 | +EuHrrLrAgMBAAECggEATmLhpuIIlvB7bptMjuGeskDhoDNSCMh5j+AYAed9+8lT 9 | TSsbsxxK8k1IQD8pZ2AtEMs2kMoeUOFVBfuY403LSPi/2HAjZ+ylrlHdfnJMlQMA 10 | nvSIranxF4reIjR30U3J4Hr17g1hOSwz3BWsou0PoO0vQp6bQ3FxeDvA+s7J2qtU 11 | kwPQjA+pzfBFc3Ws1SMhRP075e6ujOGhAzEi6rYLPcYE/hqTSOUKr1906XBfWup0 12 | ugnR3QIGUX+9Xc1FDIzklKoYgpMcdxGygT9pDfq5T++y/m/qPQcuF6DlOgbwIYt3 13 | wiRgrEFbD1RxyFAPjbYGB/Yxk/ZZB2Bu4XtXwLlBsQKBgQDpIRbvWf0IMb4zlhaN 14 | QJskcQNlyFIdIJ2k70pgD9rDsHobhtiBPRfczilTGUgFtwAICQfkOunT3VccbsKL 15 | azfdOcpHUS+k1s5Wx2D1v2jEbtOaKCelLPOpvQiPxXm1Va6OsVsJCLdZ5nhIBs7B 16 | Ckb9M+sHE/pFs3izU2mhOGS5VwKBgQDR8XttlQuIzOKpErikjej7cpUVc6fXa3nO 17 | k59LJ7+aBS8DucwPnd0iCVkuzbSNfnU2n+5R2avl4tXVMsmO6d08wANilBZwY0qM 18 | 7TES7OUTEv7A3q0yZwxh5nOvN42WPc3lyRfHJqzTk+eIonOdGd2VR/l77ju6OpBa 19 | c4gA3+eSjQKBgD7WlXkEt4h65/q9rOx8CpVjBPhta3zAat2nRlPoUh5HNlKvITKM 20 | xh7VTxxAB0LLFe4UiRUCsD5UPgPC8xtjVhaCWjyo7H+xCsMnUMJMyq+P8+dshWsn 21 | V7Jb6q/eGbVAGCIMOi6QM+O+lJNNinNMEuBzGxm4T9Bz1Mf5ZtJ3AedXAoGAKHZp 22 | OoKKWZBbcwiXq6dqKuQ3lY3X4G3naQgMoHiFthKfsBEkIjJjx0aT+LBOxyKDRsbm 23 | MMJKHiFs4w1JyZCLQkUjoeE2vex1Fto0JhigQd/cV6HTMa6otUmsLC4BzN4Wi94J 24 | BioPDywnaK8epz2v9jCMBfWxb0AQSE6R/NEqmZUCgYAQ1Onr1+VV/cqxQLjgZHjx 25 | 0kxCVO2+Yk3ksiM2uZzOb0eeXCtxL2kfmWUJ2IWFD+HlVuodEn1/eDbT+9o5Brfy 26 | Hin4x3muVLVSQe0veIJcBkfVftwemxJMmmgEl07zB1F/FmuxfKD3SED9wHNhMeEv 27 | WLL82bDGoBjDGCyy+aRSwA== 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /common/resp.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | ) 7 | 8 | type H struct { 9 | Code int 10 | Message string 11 | Data interface{} 12 | Rows interface{} 13 | Total interface{} 14 | } 15 | 16 | func Resp(w http.ResponseWriter, code int, data interface{}, message string) { 17 | w.Header().Set("Content-Type", "application/json") 18 | w.WriteHeader(http.StatusOK) 19 | h := H{ 20 | Code: code, 21 | Data: data, 22 | Message: message, 23 | } 24 | ret, err := json.Marshal(h) 25 | if err != nil { 26 | 27 | } 28 | _, _ = w.Write(ret) 29 | } 30 | 31 | func RespList(w http.ResponseWriter, code int, data interface{}, message string, total interface{}) { 32 | w.Header().Set("Content-Type", "application/json") 33 | w.WriteHeader(http.StatusOK) 34 | h := H{ 35 | Code: code, 36 | Data: data, 37 | Message: message, 38 | Total: total, 39 | } 40 | ret, err := json.Marshal(h) 41 | if err != nil { 42 | 43 | } 44 | _, _ = w.Write(ret) 45 | } 46 | 47 | func RespFail(w http.ResponseWriter, data string, message string) { 48 | //Resp(w, -1, data, message) 49 | 50 | Resp(w, -1, nil, message) 51 | } 52 | func RespOk(w http.ResponseWriter, data interface{}, message string) { 53 | Resp(w, 0, data, message) 54 | } 55 | func RespOkList(w http.ResponseWriter, data interface{}, message string, total interface{}) { 56 | RespList(w, 0, data, message, total) 57 | } 58 | -------------------------------------------------------------------------------- /common/rsa.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "crypto/rsa" 5 | "crypto/x509" 6 | "encoding/base64" 7 | "encoding/pem" 8 | "errors" 9 | "os" 10 | ) 11 | 12 | func RsaDecoder(data string) (string, error) { 13 | //privateKeyPEM, err := os.ReadFile("./private.pem") 14 | privateKeyPEM, err := os.ReadFile("common/private.pem") 15 | if err != nil { 16 | return "", errors.New("unable to read private key file") 17 | } 18 | 19 | // Parse PEM data 20 | block, _ := pem.Decode(privateKeyPEM) 21 | if block == nil { 22 | return "", errors.New("invalid PEM data") 23 | } 24 | 25 | var key interface{} 26 | if block.Type == "PRIVATE KEY" { 27 | // Parse PKCS8 private key 28 | key, err = x509.ParsePKCS8PrivateKey(block.Bytes) 29 | if err != nil { 30 | return "", errors.New("unable to parse private key") 31 | } 32 | } else { 33 | return "", errors.New("unsupported private key format") 34 | } 35 | 36 | // Convert the encrypted string to a byte array 37 | encryptedData, err := base64.StdEncoding.DecodeString(data) 38 | if err != nil { 39 | return "", errors.New("unable to decode data") 40 | } 41 | // Decrypt the data using the private key 42 | privateKey, ok := key.(*rsa.PrivateKey) 43 | if !ok { 44 | return "", errors.New("invalid private key type") 45 | } 46 | decryptedData, err := rsa.DecryptPKCS1v15(nil, privateKey, encryptedData) 47 | if err != nil { 48 | return "", errors.New("decryption failed") 49 | } 50 | 51 | return string(decryptedData), nil 52 | } 53 | -------------------------------------------------------------------------------- /config-debug.yaml: -------------------------------------------------------------------------------- 1 | port: '8889' 2 | host: '127.0.0.1' 3 | mysql: 4 | host: '127.0.0.1' 5 | port: '3306' 6 | name: 'chatcraft' 7 | user: 'root' 8 | password: 'root@123321' 9 | redis: 10 | host: '127.0.0.1' 11 | port: '6379' 12 | 13 | rabbitmq: 14 | host: "127.0.0.1" 15 | port: "5672" 16 | user: "guest" 17 | password: "guest" 18 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | // MysqlConfig mysql信息配置 4 | type MysqlConfig struct { 5 | Host string `mapstructure:"host" json:"host"` 6 | Port int `mapstructure:"port" json:"port"` 7 | Name string `mapstructure:"name" json:"Name"` 8 | User string `mapstructure:"user" json:"user"` 9 | Password string `mapstructure:"password" json:"password"` 10 | } 11 | 12 | type RedisConfig struct { 13 | Host string `mapstructure:"host" json:"host"` 14 | Port int `mapstructure:"port" json:"port"` 15 | } 16 | 17 | type RabbitMQConfig struct { 18 | Host string `mapstructure:"host" json:"host"` 19 | Port string `mapstructure:"port" json:"port"` 20 | User string `mapstructure:"user" json:"user"` 21 | Password string `mapstructure:"password" json:"password"` 22 | } 23 | 24 | // 对应yaml文件结构 25 | type ServiceConfig struct { 26 | Port int `mapstructure:"port" json:"port"` 27 | Host string `mapstructure:"host" json:"host"` 28 | DB MysqlConfig `mapstructure:"mysql" json:"mysql"` 29 | RedisDB RedisConfig `mapstructure:"redis" json:"redis"` 30 | RabbitMQConfig RabbitMQConfig `mapstructure:"rabbitmq" json:"rabbitmq"` 31 | } 32 | -------------------------------------------------------------------------------- /dao/community.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "GoChatCraft/global" 5 | "GoChatCraft/models" 6 | "errors" 7 | ) 8 | 9 | func CreateCommunity(community models.Community) (int, error) { 10 | com := models.Community{} 11 | if tx := global.DB.Where("name = ?", community.Name).First(&com); tx.RowsAffected == 1 { 12 | return -1, errors.New("the group record already exists") 13 | } 14 | tx := global.DB.Begin() 15 | if t := tx.Create(&community); t.RowsAffected == 0 { 16 | tx.Rollback() 17 | return -1, errors.New("failed to create group record") 18 | } 19 | relation := models.Relation{} 20 | relation.OwnerId = community.OwnerId 21 | relation.TargetId = community.ID 22 | relation.Type = 2 23 | if t := tx.Create(&relation); t.RowsAffected == 0 { 24 | tx.Rollback() 25 | return -1, errors.New("failed to create group record") 26 | } 27 | tx.Commit() 28 | return 0, nil 29 | } 30 | 31 | func GetCommunityList(ownerId uint) (*[]models.Community, error) { 32 | relation := make([]models.Relation, 0) 33 | if tx := global.DB.Where("owner_id = ? and type = 2", ownerId).Find(&relation); tx.RowsAffected == 0 { 34 | return nil, errors.New("the group record does not exist") 35 | } 36 | communityID := make([]uint, 0) 37 | for _, v := range relation { 38 | cid := v.TargetId 39 | communityID = append(communityID, cid) 40 | } 41 | community := make([]models.Community, 0) 42 | if tx := global.DB.Where("id in ?", communityID).Find(&community); tx.RowsAffected == 0 { 43 | return nil, errors.New("failed to retrieve group data") 44 | } 45 | return &community, nil 46 | } 47 | 48 | // JoinCommunity Search and join the group based on the group nickname. 49 | func JoinCommunity(ownerId uint, cname string) (int, error) { 50 | community := models.Community{} 51 | if tx := global.DB.Where("name = ?", cname).First(&community); tx.RowsAffected == 0 { 52 | return -1, errors.New("the group record does not exist") 53 | } 54 | relation := models.Relation{} 55 | if tx := global.DB.Where("owner_id = ? and target_id = ? and type = 2", ownerId, community.ID).First(&relation); tx.RowsAffected == 1 { 56 | return -1, errors.New("the group has already been joined") 57 | } 58 | relation = models.Relation{} 59 | relation.OwnerId = ownerId 60 | relation.TargetId = community.ID 61 | relation.Type = 2 62 | 63 | if tx := global.DB.Create(&relation); tx.RowsAffected == 0 { 64 | return -1, errors.New("failed to join") 65 | } 66 | return 0, nil 67 | } 68 | -------------------------------------------------------------------------------- /dao/email_code.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "GoChatCraft/global" 5 | "GoChatCraft/models" 6 | "context" 7 | "crypto/tls" 8 | "errors" 9 | "fmt" 10 | "github.com/go-redis/redis/v8" 11 | "github.com/jordan-wright/email" 12 | "go.uber.org/zap" 13 | "math/rand" 14 | "net/smtp" 15 | "time" 16 | ) 17 | 18 | var ctx = context.Background() 19 | 20 | func GenerateRandomCode(codeLen int) string { 21 | s := "1234567890" 22 | code := "" 23 | // import random seed, or that random code will always be the same one 24 | rand.Seed(time.Now().UnixNano()) 25 | for i := 0; i < codeLen; i++ { 26 | code += string(s[rand.Intn(len(s))]) 27 | } 28 | return code 29 | } 30 | 31 | // GenMailCodeKey Where does the captcha business come from 32 | func GenMailCodeKey(mailAddr, from string) string { 33 | return "MAIL-CODE-" + from + "-" + mailAddr 34 | } 35 | 36 | func SendMailCode(mailOption *models.MailOptions, from string, ttl int) error { 37 | // generate random validation code 38 | code := GenerateRandomCode(6) 39 | // 先插入redis,再发邮件 40 | key := GenMailCodeKey(mailOption.MailTo, from) 41 | // 如果code没有过期,是不允许再发送的 42 | success, err := global.RedisDB.SetNX(ctx, key, code, time.Duration(ttl)*time.Minute).Result() 43 | if err != nil { 44 | return err 45 | } 46 | if !success { 47 | return errors.New("the code already exists") 48 | } 49 | 50 | // 发邮件 51 | options := &models.MailOptions{ 52 | MailHost: "smtp.qq.com", 53 | MailPort: 465, 54 | MailUser: mailOption.MailUser, 55 | MailPass: mailOption.MailPass, 56 | MailTo: mailOption.MailTo, 57 | Subject: mailOption.Subject, 58 | Body: mailOption.Body, 59 | } 60 | err = MailSend(options, code) 61 | if err != nil { 62 | return err 63 | } 64 | return nil 65 | } 66 | 67 | func MailSend(options *models.MailOptions, code string) error { 68 | e := email.NewEmail() 69 | e.From = "Chat Craft<1929509811@qq.com>" 70 | e.To = []string{options.MailTo} 71 | e.Subject = options.Subject 72 | e.HTML = []byte("

Your Validation Code is " + code + "

") 73 | err := e.SendWithTLS("smtp.qq.com:465", smtp.PlainAuth("", options.MailUser, options.MailPass, "smtp.qq.com"), 74 | &tls.Config{InsecureSkipVerify: true, ServerName: "smtp.qq.com"}) 75 | if err != nil { 76 | zap.S().Info("email delivery failed: %w", err) 77 | return errors.New("email delivery failed") 78 | } 79 | return nil 80 | } 81 | 82 | func ValidateMailCode(tag, inputCode, from string) error { 83 | key := GenMailCodeKey(tag, from) 84 | code, err := global.RedisDB.Get(ctx, key).Result() 85 | if err != nil { 86 | if err == redis.Nil { 87 | return errors.New("") 88 | } 89 | return err 90 | } 91 | 92 | // 对比后马上删除 93 | err = global.RedisDB.Del(ctx, key).Err() 94 | if err != nil { 95 | fmt.Printf("redis del fail %v\n", err) 96 | return err 97 | } 98 | 99 | if inputCode != code { 100 | return errors.New("验证码不对") 101 | } 102 | 103 | return nil 104 | } 105 | -------------------------------------------------------------------------------- /dao/relation.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "GoChatCraft/global" 5 | "GoChatCraft/models" 6 | "errors" 7 | "go.uber.org/zap" 8 | ) 9 | 10 | func FriendList(userId uint) (*[]models.UserBasic, error) { 11 | relation := make([]models.Relation, 0) 12 | if tx := global.DB.Where("owner_id = ? and type = 1", userId).Find(&relation); tx.RowsAffected == 0 { 13 | zap.S().Info("relation data found") 14 | return nil, errors.New("no friend relationship found") 15 | } 16 | userID := make([]uint, 0) 17 | for _, v := range relation { 18 | userID = append(userID, v.TargetId) 19 | } 20 | user := make([]models.UserBasic, 0) 21 | if tx := global.DB.Where("id in ?", userID).Find(&user); tx.RowsAffected == 0 { 22 | zap.S().Info("no friend relationship found in the relation data") 23 | return nil, errors.New("no friends found") 24 | } 25 | return &user, nil 26 | } 27 | 28 | // AddFriend Add friend using QR code on mobile device. 29 | func AddFriend(userID, TargetId uint) (int, error) { 30 | if userID == TargetId { 31 | return -2, errors.New("the userID and TargetID are equal") 32 | } 33 | //Querying user by ID 34 | targetUser, err := FindUserId(TargetId) 35 | if err != nil { 36 | return -1, errors.New("no user found") 37 | } 38 | if targetUser.ID == 0 { 39 | zap.S().Info("no user found") 40 | return -1, errors.New("no user found") 41 | } 42 | relation := models.Relation{} 43 | //The purpose of these two query statements is to ensure that when adding friends, 44 | //the friend relationship can be checked correctly regardless of who initiates the request. 45 | //If only one query is performed, some situations may be missed, 46 | //such as when the friend relationship already exists but the query conditions do not match. 47 | if tx := global.DB.Where("owner_id = ? and target_id = ? and type = 1", userID, TargetId).First(&relation); tx.RowsAffected == 1 { 48 | zap.S().Info("the friend exists") 49 | return 0, errors.New("the friend exists") 50 | } 51 | 52 | if tx := global.DB.Where("owner_id = ? and target_id = ? and type = 1", TargetId, userID).First(&relation); tx.RowsAffected == 1 { 53 | zap.S().Info("the friend exists") 54 | return 0, errors.New("the friend exists") 55 | } 56 | 57 | tx := global.DB.Begin() 58 | relation.OwnerId = userID 59 | relation.TargetId = targetUser.ID 60 | relation.Type = 1 61 | if t := tx.Create(&relation); t.RowsAffected == 0 { 62 | zap.S().Info("failed to create friend record") 63 | //Transaction rollback 64 | tx.Rollback() 65 | return -1, errors.New("failed to create friend record") 66 | } 67 | relation = models.Relation{} 68 | relation.OwnerId = TargetId 69 | relation.TargetId = userID 70 | relation.Type = 1 71 | 72 | if t := tx.Create(&relation); t.RowsAffected == 0 { 73 | zap.S().Info("failed to create friend record") 74 | 75 | //Transaction rollback 76 | tx.Rollback() 77 | return -1, errors.New("failed to create friend record") 78 | } 79 | 80 | tx.Commit() 81 | return 1, nil 82 | } 83 | 84 | func AddFriendByName(userId uint, targetName string) (int, error) { 85 | user, err := FindUserByName(targetName) 86 | if err != nil { 87 | return -1, errors.New("the user does not exist") 88 | } 89 | if user.ID == 0 { 90 | zap.S().Info("user not found") 91 | return -1, errors.New("the user does not exist") 92 | } 93 | return AddFriend(userId, user.ID) 94 | } 95 | 96 | func AddFriendByUserId(userId uint, targetUserId uint) (int, error) { 97 | user, err := FindUserId(targetUserId) 98 | if err != nil { 99 | return -1, errors.New("the user does not exist") 100 | } 101 | if user.ID == 0 { 102 | zap.S().Info("user not found") 103 | return -1, errors.New("the user does not exist") 104 | } 105 | return AddFriend(userId, user.ID) 106 | } 107 | -------------------------------------------------------------------------------- /dao/user.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "GoChatCraft/common" 5 | "GoChatCraft/global" 6 | "GoChatCraft/models" 7 | "errors" 8 | "go.uber.org/zap" 9 | "strconv" 10 | "time" 11 | ) 12 | 13 | func GetUserList() ([]*models.UserBasic, error) { 14 | var list []*models.UserBasic 15 | //tx.RowsAffected is a method that returns the number of affected rows, used to check if a query has returned any results. 16 | //Use if tx.RowsAffected == 0 to check if the query result is empty. If it is empty, it means no user records were found. 17 | if tx := global.DB.Find(&list); tx.RowsAffected == 0 { 18 | zap.S().Info("failed to retrieve user list") 19 | return nil, errors.New("failed to retrieve user list") 20 | } 21 | return list, nil 22 | } 23 | 24 | func FindUserByNameAndPwd(name string, password string) (*models.UserBasic, error) { 25 | user := models.UserBasic{} 26 | if tx := global.DB.Where("name = ? and pass_word = ?", name, password).First(&user); tx.RowsAffected == 0 { 27 | zap.S().Info("the user was not found") 28 | return nil, errors.New("the user was not found") 29 | } 30 | //Get the current timestamp and convert it to a string type. 31 | t := strconv.Itoa(int(time.Now().Unix())) 32 | //Perform MD5 encryption. 33 | temp := common.Md5encoder(t) 34 | if tx := global.DB.Model(&user).Where("id = ?", user.ID).Update("identity", temp); tx.RowsAffected == 0 { 35 | zap.S().Info("failed to write identity") 36 | return nil, errors.New("failed to write identity") 37 | } 38 | return &user, nil 39 | } 40 | 41 | func FindUserByName(name string) (*models.UserBasic, error) { 42 | user := models.UserBasic{} 43 | if tx := global.DB.Where("name = ?", name).First(&user); tx.RowsAffected == 0 { 44 | zap.S().Info("couldn't find any information about this user") 45 | return nil, errors.New("couldn't find any information about this user") 46 | } 47 | return &user, nil 48 | } 49 | 50 | func FindUserByNameWithRegister(name string) (*models.UserBasic, error) { 51 | user := models.UserBasic{} 52 | if tx := global.DB.Where("name = ?", name).First(&user); tx.RowsAffected == 1 { 53 | zap.S().Info("the current username already exists") 54 | return nil, errors.New("the current username already exists") 55 | } 56 | return &user, nil 57 | } 58 | 59 | func FindUserByEmailWithLogin(email string) (*models.UserBasic, error) { 60 | user := models.UserBasic{} 61 | if tx := global.DB.Where("email = ?", email).First(&user); tx.RowsAffected == 0 { 62 | zap.S().Info("couldn't find any information about this email") 63 | return nil, errors.New("couldn't find any information about this email") 64 | } 65 | return &user, nil 66 | } 67 | 68 | func FindUserByEmailWithRegister(email string) (*models.UserBasic, error) { 69 | user := models.UserBasic{} 70 | if tx := global.DB.Where("email = ?", email).First(&user); tx.RowsAffected == 1 { 71 | zap.S().Info("the current email already exists") 72 | return nil, errors.New("the current email already exists") 73 | } 74 | return &user, nil 75 | } 76 | 77 | func FindUserId(ID uint) (*models.UserBasic, error) { 78 | user := models.UserBasic{} 79 | if tx := global.DB.Where(ID).First(&user); tx.RowsAffected == 0 { 80 | zap.S().Info("the user was not found") 81 | return nil, errors.New("the user was not found") 82 | } 83 | return &user, nil 84 | } 85 | 86 | func CreateUser(user models.UserBasic) (*models.UserBasic, error) { 87 | tx := global.DB.Create(&user) 88 | if tx.RowsAffected == 0 { 89 | zap.S().Info("failed to add a new user") 90 | return nil, errors.New("failed to add a new user") 91 | } 92 | return &user, nil 93 | } 94 | 95 | func UpdateUser(user models.UserBasic) (*models.UserBasic, error) { 96 | tx := global.DB.Model(&user).Updates(models.UserBasic{ 97 | Name: user.Name, 98 | PassWord: user.PassWord, 99 | Avatar: user.Avatar, 100 | Gender: user.Gender, 101 | Phone: user.Phone, 102 | Email: user.Email, 103 | Salt: user.Salt, 104 | }) 105 | if tx.RowsAffected == 0 { 106 | zap.S().Info("failed to update the user") 107 | return nil, errors.New("failed to update the user") 108 | } 109 | return &user, nil 110 | } 111 | 112 | func DeleteUser(user models.UserBasic) error { 113 | if tx := global.DB.Delete(&user); tx.RowsAffected == 0 { 114 | zap.S().Info("failed to delete the user") 115 | return errors.New("failed to delete the user") 116 | } 117 | return nil 118 | } 119 | -------------------------------------------------------------------------------- /dao/user_story.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "GoChatCraft/global" 5 | "GoChatCraft/models" 6 | "errors" 7 | "go.uber.org/zap" 8 | ) 9 | 10 | func GetStoryList(userId uint, page int, pageSize int) (*[]models.UserStory, error) { 11 | offset := (page - 1) * pageSize 12 | story := make([]models.UserStory, 0) 13 | if tx := global.DB.Where("owner_id = ?", userId).Offset(offset).Limit(pageSize).Find(&story); tx.RowsAffected == 0 { 14 | zap.S().Info("story data found") 15 | return nil, errors.New("story data found") 16 | } 17 | return &story, nil 18 | } 19 | 20 | func GetUserShowStoryList(userId uint) (*[]models.ResponseUserStory, int, error) { 21 | story := make([]models.UserStory, 0) 22 | if tx := global.DB.Where("owner_id = ?", userId).Find(&story); tx.RowsAffected == 0 { 23 | zap.S().Info("story data found") 24 | //return nil, 0, errors.New("story data found") 25 | } 26 | likeStory := make([]models.UserStoryLike, 0) 27 | responseStory := make([]models.ResponseUserStory, 0) 28 | 29 | for _, s := range story { 30 | currentLikeStory := make([]models.UserStoryLike, 0) 31 | if tx := global.DB.Where("user_story_id = ?", s.ID).Find(¤tLikeStory); tx.RowsAffected == 0 { 32 | zap.S().Info("story like data found") 33 | //return nil, 0, errors.New("story like data found") 34 | } else { 35 | //...Used to pass the elements of a slice or array to a function one by one. 36 | likeStory = append(likeStory, currentLikeStory...) 37 | } 38 | currentCommentStory := make([]models.UserStoryComment, 0) 39 | responseCommentStory := make([]models.ResponseUserStoryComment, 0) 40 | if tx := global.DB.Where("user_story_id = ?", s.ID).Find(¤tCommentStory); tx.RowsAffected == 0 { 41 | zap.S().Info("story like data found") 42 | //return nil, 0, errors.New("story like data found") 43 | } 44 | // Loop through currentCommentStory 45 | for _, comment := range currentCommentStory { 46 | // Find the user's avatar in the user_basic table. 47 | var userAvatar string 48 | if err := global.DB.Table("user_basics").Select("avatar").Where("id = ?", comment.CommentOwnerId).Scan(&userAvatar).Error; err != nil { 49 | zap.S().Error("Failed to get user avatar: ", err) 50 | 51 | } 52 | //由于切片中存储的是指向 comment 变量的指针,当循环结束时,responseCommentStory 中的所有元素实际上都指向了 currentCommentStory 的最后一个元素。 53 | //要解决这个问题,你需要在每次迭代中创建一个新的 comment 对象 54 | // Create a new comment object for each iteration 55 | currentComment := comment 56 | // Create a ResponseUserStoryComment object and assign values to it. 57 | response := models.ResponseUserStoryComment{ 58 | StoryComment: ¤tComment, 59 | UserAvatar: userAvatar, 60 | } 61 | 62 | // Add the response to the responseCommentStory slice. 63 | responseCommentStory = append(responseCommentStory, response) 64 | } 65 | responseStory = append(responseStory, models.ResponseUserStory{ 66 | Story: s, 67 | StoryLikes: ¤tLikeStory, 68 | StoryComments: &responseCommentStory, 69 | }) 70 | } 71 | 72 | likeStoryCount := len(likeStory) 73 | //Get the latest three data of story. 74 | var latestStories []models.ResponseUserStory 75 | if len(responseStory) <= 3 { 76 | latestStories = make([]models.ResponseUserStory, len(responseStory)) 77 | copy(latestStories, responseStory) 78 | } else { 79 | latestStories = make([]models.ResponseUserStory, 3) 80 | copy(latestStories, responseStory[len(responseStory)-3:]) 81 | } 82 | return &latestStories, likeStoryCount, nil 83 | } 84 | 85 | func AddStory(story *models.UserStory) (*models.UserStory, error) { 86 | tx := global.DB.Create(&story) 87 | if tx.RowsAffected == 0 { 88 | zap.S().Info("failed to add a new story") 89 | return nil, errors.New("failed to add a new story") 90 | } 91 | return story, nil 92 | } 93 | 94 | func AddOrRemoveStoryLike(likeStory *models.UserStoryLike) error { 95 | likes := make([]models.UserStoryLike, 0) 96 | if tx := global.DB.Where("like_owner_id = ? AND user_story_id = ?", likeStory.LikeOwnerId, likeStory.UserStoryId).Find(&likes); tx.RowsAffected == 0 { 97 | t := global.DB.Create(&likeStory) 98 | if t.RowsAffected == 0 { 99 | zap.S().Info("failed to add a new story like") 100 | return errors.New("failed to add a new story like") 101 | } 102 | } else { 103 | if t := global.DB.Where("like_owner_id = ? AND user_story_id = ?", likeStory.LikeOwnerId, likeStory.UserStoryId).Delete(&likeStory); t.RowsAffected == 0 { 104 | zap.S().Info("can not to delete story like") 105 | return errors.New("can not to delete story like") 106 | } 107 | } 108 | return nil 109 | } 110 | 111 | func AddStoryComment(commentStory *models.UserStoryComment) (*models.UserStoryComment, error) { 112 | comment := models.UserStoryComment{} 113 | tx := global.DB.Create(&commentStory).Where("user_story_id = ? And comment_owner_id = ?", commentStory.UserStoryId, commentStory.CommentOwnerId). 114 | First(&comment) 115 | if tx.RowsAffected == 0 { 116 | zap.S().Info("failed to add a new story like") 117 | return nil, errors.New("failed to add a new story like") 118 | } 119 | return &comment, nil 120 | } 121 | -------------------------------------------------------------------------------- /docs/docs.go: -------------------------------------------------------------------------------- 1 | // Package docs Code generated by swaggo/swag. DO NOT EDIT 2 | package docs 3 | 4 | import "github.com/swaggo/swag" 5 | 6 | const docTemplate = `{ 7 | "schemes": {{ marshal .Schemes }}, 8 | "swagger": "2.0", 9 | "info": { 10 | "description": "{{escape .Description}}", 11 | "title": "{{.Title}}", 12 | "termsOfService": "https://github.com/taxze6", 13 | "contact": { 14 | "name": "Taxze", 15 | "email": "taxze.xiaoyan@gmail.com" 16 | }, 17 | "version": "{{.Version}}" 18 | }, 19 | "host": "{{.Host}}", 20 | "basePath": "{{.BasePath}}", 21 | "paths": { 22 | "/user/list": { 23 | "get": { 24 | "description": "用户列表", 25 | "consumes": [ 26 | "application/json" 27 | ], 28 | "tags": [ 29 | "测试" 30 | ], 31 | "summary": "List 获取用户列表", 32 | "responses": {} 33 | } 34 | } 35 | } 36 | }` 37 | 38 | // SwaggerInfo holds exported Swagger Info so clients can modify it 39 | var SwaggerInfo = &swag.Spec{ 40 | Version: "1.0", 41 | Host: "127.0.0.1:8889", 42 | BasePath: "", 43 | Schemes: []string{}, 44 | Title: "接囗文档", 45 | Description: "ChatCraft", 46 | InfoInstanceName: "swagger", 47 | SwaggerTemplate: docTemplate, 48 | LeftDelim: "{{", 49 | RightDelim: "}}", 50 | } 51 | 52 | func init() { 53 | swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) 54 | } 55 | -------------------------------------------------------------------------------- /docs/swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "description": "ChatCraft", 5 | "title": "接囗文档", 6 | "termsOfService": "https://github.com/taxze6", 7 | "contact": { 8 | "name": "Taxze", 9 | "email": "taxze.xiaoyan@gmail.com" 10 | }, 11 | "version": "1.0" 12 | }, 13 | "host": "127.0.0.1:8889", 14 | "paths": { 15 | "/user/list": { 16 | "get": { 17 | "description": "用户列表", 18 | "consumes": [ 19 | "application/json" 20 | ], 21 | "tags": [ 22 | "测试" 23 | ], 24 | "summary": "List 获取用户列表", 25 | "responses": {} 26 | } 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /docs/swagger.yaml: -------------------------------------------------------------------------------- 1 | host: 127.0.0.1:8889 2 | info: 3 | contact: 4 | email: taxze.xiaoyan@gmail.com 5 | name: Taxze 6 | description: ChatCraft 7 | termsOfService: https://github.com/taxze6 8 | title: 接囗文档 9 | version: "1.0" 10 | paths: 11 | /user/list: 12 | get: 13 | consumes: 14 | - application/json 15 | description: 用户列表 16 | responses: {} 17 | summary: List 获取用户列表 18 | tags: 19 | - 测试 20 | swagger: "2.0" 21 | -------------------------------------------------------------------------------- /global/global.go: -------------------------------------------------------------------------------- 1 | package global 2 | 3 | import ( 4 | "GoChatCraft/config" 5 | "github.com/go-redis/redis/v8" 6 | "gorm.io/gorm" 7 | ) 8 | 9 | var ( 10 | DB *gorm.DB 11 | RedisDB *redis.Client 12 | ServiceConfig *config.ServiceConfig 13 | ) 14 | -------------------------------------------------------------------------------- /global/source_email.go: -------------------------------------------------------------------------------- 1 | package global 2 | 3 | const ( 4 | LoginEmail = "LoginEmail" 5 | Register = "Register" 6 | ) 7 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module GoChatCraft 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/dgrijalva/jwt-go v3.2.0+incompatible 7 | github.com/gin-gonic/gin v1.9.1 8 | github.com/go-redis/redis/v8 v8.11.5 9 | github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible 10 | github.com/swaggo/files v1.0.1 11 | github.com/swaggo/gin-swagger v1.6.0 12 | github.com/swaggo/swag v1.16.2 13 | go.uber.org/zap v1.26.0 14 | gorm.io/driver/mysql v1.5.2 15 | gorm.io/gorm v1.25.5 16 | ) 17 | 18 | require ( 19 | github.com/KyleBanks/depth v1.2.1 // indirect 20 | github.com/bytedance/sonic v1.10.2 // indirect 21 | github.com/cespare/xxhash/v2 v2.1.2 // indirect 22 | github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect 23 | github.com/chenzhuoyu/iasm v0.9.1 // indirect 24 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect 25 | github.com/fsnotify/fsnotify v1.7.0 // indirect 26 | github.com/gabriel-vasile/mimetype v1.4.3 // indirect 27 | github.com/gin-contrib/sse v0.1.0 // indirect 28 | github.com/go-openapi/jsonpointer v0.20.0 // indirect 29 | github.com/go-openapi/jsonreference v0.20.2 // indirect 30 | github.com/go-openapi/spec v0.20.11 // indirect 31 | github.com/go-openapi/swag v0.22.4 // indirect 32 | github.com/go-playground/locales v0.14.1 // indirect 33 | github.com/go-playground/universal-translator v0.18.1 // indirect 34 | github.com/go-playground/validator/v10 v10.16.0 // indirect 35 | github.com/go-sql-driver/mysql v1.7.0 // indirect 36 | github.com/goccy/go-json v0.10.2 // indirect 37 | github.com/gorilla/websocket v1.5.1 // indirect 38 | github.com/hashicorp/hcl v1.0.0 // indirect 39 | github.com/jinzhu/inflection v1.0.0 // indirect 40 | github.com/jinzhu/now v1.1.5 // indirect 41 | github.com/josharian/intern v1.0.0 // indirect 42 | github.com/json-iterator/go v1.1.12 // indirect 43 | github.com/klauspost/cpuid/v2 v2.2.6 // indirect 44 | github.com/leodido/go-urn v1.2.4 // indirect 45 | github.com/magiconair/properties v1.8.7 // indirect 46 | github.com/mailru/easyjson v0.7.7 // indirect 47 | github.com/mattn/go-isatty v0.0.20 // indirect 48 | github.com/mitchellh/mapstructure v1.5.0 // indirect 49 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 50 | github.com/modern-go/reflect2 v1.0.2 // indirect 51 | github.com/pelletier/go-toml/v2 v2.1.0 // indirect 52 | github.com/rogpeppe/go-internal v1.9.0 // indirect 53 | github.com/sagikazarmark/locafero v0.4.0 // indirect 54 | github.com/sagikazarmark/slog-shim v0.1.0 // indirect 55 | github.com/sourcegraph/conc v0.3.0 // indirect 56 | github.com/spf13/afero v1.11.0 // indirect 57 | github.com/spf13/cast v1.6.0 // indirect 58 | github.com/spf13/pflag v1.0.5 // indirect 59 | github.com/spf13/viper v1.18.2 // indirect 60 | github.com/streadway/amqp v1.1.0 // indirect 61 | github.com/subosito/gotenv v1.6.0 // indirect 62 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 63 | github.com/ugorji/go/codec v1.2.12 // indirect 64 | go.uber.org/multierr v1.10.0 // indirect 65 | golang.org/x/arch v0.6.0 // indirect 66 | golang.org/x/crypto v0.16.0 // indirect 67 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect 68 | golang.org/x/net v0.19.0 // indirect 69 | golang.org/x/sys v0.15.0 // indirect 70 | golang.org/x/text v0.14.0 // indirect 71 | golang.org/x/tools v0.16.0 // indirect 72 | google.golang.org/protobuf v1.31.0 // indirect 73 | gopkg.in/fatih/set.v0 v0.2.1 // indirect 74 | gopkg.in/ini.v1 v1.67.0 // indirect 75 | gopkg.in/yaml.v3 v3.0.1 // indirect 76 | ) 77 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= 2 | github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= 3 | github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= 4 | github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= 5 | github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZFE= 6 | github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= 7 | github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= 8 | github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 9 | github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= 10 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= 11 | github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= 12 | github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA= 13 | github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= 14 | github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0= 15 | github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= 16 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 17 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 18 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 19 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 20 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 21 | github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= 22 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 23 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= 24 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= 25 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= 26 | github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= 27 | github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= 28 | github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= 29 | github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= 30 | github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= 31 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 32 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 33 | github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= 34 | github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= 35 | github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= 36 | github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= 37 | github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= 38 | github.com/go-openapi/jsonpointer v0.20.0 h1:ESKJdU9ASRfaPNOPRx12IUyA1vn3R9GiE3KYD14BXdQ= 39 | github.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA= 40 | github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= 41 | github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= 42 | github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= 43 | github.com/go-openapi/spec v0.20.11 h1:J/TzFDLTt4Rcl/l1PmyErvkqlJDncGvPTMnCI39I4gY= 44 | github.com/go-openapi/spec v0.20.11/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= 45 | github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= 46 | github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= 47 | github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= 48 | github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= 49 | github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= 50 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 51 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 52 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 53 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 54 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 55 | github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE= 56 | github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= 57 | github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= 58 | github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= 59 | github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= 60 | github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= 61 | github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= 62 | github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 63 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 64 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 65 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 66 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 67 | github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= 68 | github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= 69 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 70 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 71 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= 72 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= 73 | github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= 74 | github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 75 | github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible h1:jdpOPRN1zP63Td1hDQbZW73xKmzDvZHzVdNYxhnTMDA= 76 | github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A= 77 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 78 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 79 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 80 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 81 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 82 | github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= 83 | github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= 84 | github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= 85 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 86 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 87 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 88 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 89 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 90 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 91 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 92 | github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= 93 | github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= 94 | github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= 95 | github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= 96 | github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 97 | github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 98 | github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 99 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= 100 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 101 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 102 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 103 | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= 104 | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 105 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 106 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 107 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 108 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 109 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 110 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 111 | github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= 112 | github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= 113 | github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= 114 | github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= 115 | github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= 116 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 117 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 118 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 119 | github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= 120 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 121 | github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= 122 | github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= 123 | github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= 124 | github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= 125 | github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= 126 | github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= 127 | github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= 128 | github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= 129 | github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= 130 | github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= 131 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 132 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 133 | github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= 134 | github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= 135 | github.com/streadway/amqp v1.1.0 h1:py12iX8XSyI7aN/3dUT8DFIDJazNJsVJdxNVEpnQTZM= 136 | github.com/streadway/amqp v1.1.0/go.mod h1:WYSrTEYHOXHd0nwFeUXAe2G2hRnQT+deZJJf88uS9Bg= 137 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 138 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 139 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 140 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 141 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 142 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 143 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 144 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 145 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 146 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 147 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 148 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 149 | github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= 150 | github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= 151 | github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE= 152 | github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg= 153 | github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M= 154 | github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo= 155 | github.com/swaggo/swag v1.16.2 h1:28Pp+8DkQoV+HLzLx8RGJZXNGKbFqnuvSbAAtoxiY04= 156 | github.com/swaggo/swag v1.16.2/go.mod h1:6YzXnDcpr0767iOejs318CwYkCQqyGer6BizOg03f+E= 157 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 158 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 159 | github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= 160 | github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 161 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 162 | go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= 163 | go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= 164 | go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 165 | go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= 166 | go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= 167 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 168 | golang.org/x/arch v0.6.0 h1:S0JTfE48HbRj80+4tbvZDYsJ3tGv6BUU3XxyZ7CirAc= 169 | golang.org/x/arch v0.6.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= 170 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 171 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 172 | golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= 173 | golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= 174 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= 175 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= 176 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 177 | golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= 178 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 179 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 180 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 181 | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 182 | golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= 183 | golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= 184 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 185 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 186 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 187 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 188 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 189 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 190 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 191 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 192 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 193 | golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= 194 | golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 195 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 196 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 197 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 198 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 199 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 200 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 201 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 202 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= 203 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 204 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 205 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 206 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 207 | golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM= 208 | golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= 209 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 210 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 211 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 212 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 213 | google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= 214 | google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 215 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 216 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 217 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 218 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 219 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 220 | gopkg.in/fatih/set.v0 v0.2.1 h1:Xvyyp7LXu34P0ROhCyfXkmQCAoOUKb1E2JS9I7SE5CY= 221 | gopkg.in/fatih/set.v0 v0.2.1/go.mod h1:5eLWEndGL4zGGemXWrKuts+wTJR0y+w+auqUJZbmyBg= 222 | gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= 223 | gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 224 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 225 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 226 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 227 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 228 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 229 | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 230 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 231 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 232 | gorm.io/driver/mysql v1.5.2 h1:QC2HRskSE75wBuOxe0+iCkyJZ+RqpudsQtqkp+IMuXs= 233 | gorm.io/driver/mysql v1.5.2/go.mod h1:pQLhh1Ut/WUAySdTHwBpBv6+JKcj+ua4ZFx1QQTBzb8= 234 | gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= 235 | gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= 236 | gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= 237 | nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= 238 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 239 | -------------------------------------------------------------------------------- /initialize/config.go: -------------------------------------------------------------------------------- 1 | package initialize 2 | 3 | import ( 4 | "GoChatCraft/global" 5 | "github.com/spf13/viper" 6 | "go.uber.org/zap" 7 | ) 8 | 9 | func InitConfig() { 10 | //Instantiate an object 11 | v := viper.New() 12 | 13 | //configFile := "./config-release.yaml" 14 | configFile := "../GoChatCraft/config-debug.yaml" 15 | //Read configuration file 16 | v.SetConfigFile(configFile) 17 | 18 | //Read file 19 | if err := v.ReadInConfig(); err != nil { 20 | panic(err) 21 | } 22 | 23 | //Put the data into global.ServerConfig. 24 | if err := v.Unmarshal(&global.ServiceConfig); err != nil { 25 | panic(err) 26 | } 27 | 28 | zap.S().Info("Configuration information.", global.ServiceConfig) 29 | } 30 | -------------------------------------------------------------------------------- /initialize/db.go: -------------------------------------------------------------------------------- 1 | package initialize 2 | 3 | import ( 4 | "GoChatCraft/global" 5 | "fmt" 6 | "github.com/go-redis/redis/v8" 7 | "gorm.io/driver/mysql" 8 | "gorm.io/gorm" 9 | "gorm.io/gorm/logger" 10 | "log" 11 | "os" 12 | "time" 13 | ) 14 | 15 | func InitDB() { 16 | dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local", global.ServiceConfig.DB.User, 17 | global.ServiceConfig.DB.Password, global.ServiceConfig.DB.Host, global.ServiceConfig.DB.Port, global.ServiceConfig.DB.Name) 18 | newLogger := logger.New(log.New(os.Stdout, "\r\n", log.Lshortfile), 19 | logger.Config{ 20 | SlowThreshold: time.Second, //慢Sql阈值(当执行的 SQL 查询或操作的执行时间超过该阈值时,会被认为是慢 SQL。 慢 SQL是指执行时间较长的 SQL 查询或操作,可能会对系统性能产生影响。通过设置慢 SQL 的阈值,可以在日志中标记并记录执行时间超过阈值的 SQL 查询或操作,以便进行性能分析和优化。) 21 | LogLevel: logger.Info, //日志级别(logger.Info表示只输出信息级别及以上的日志) 22 | IgnoreRecordNotFoundError: true, //忽略ErrRecordNotFound(记录未找到)错误 23 | Colorful: true, //彩色打印 24 | }, 25 | ) 26 | var err error 27 | //将获取到的连接赋值到global.DB 28 | global.DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{ 29 | Logger: newLogger, 30 | }) 31 | if err != nil { 32 | panic(err) 33 | } 34 | } 35 | 36 | func InitRedis() { 37 | opt := redis.Options{ 38 | Addr: fmt.Sprintf("%s:%d", global.ServiceConfig.RedisDB.Host, global.ServiceConfig.RedisDB.Port), // redis地址 39 | Password: "", // no password set 40 | DB: 0, // if you want to use default DB,set to 0 41 | } 42 | global.RedisDB = redis.NewClient(&opt) 43 | } 44 | -------------------------------------------------------------------------------- /initialize/logger.go: -------------------------------------------------------------------------------- 1 | package initialize 2 | 3 | import ( 4 | "go.uber.org/zap" 5 | "log" 6 | ) 7 | 8 | func InitLogger() { 9 | //Initialize logging. 10 | logger, err := zap.NewDevelopment() 11 | if err != nil { 12 | log.Fatal("The initialization of the logging failed.", err.Error()) 13 | } 14 | //Use global logger. 15 | zap.ReplaceGlobals(logger) 16 | } 17 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | _ "GoChatCraft/docs" 5 | "GoChatCraft/global" 6 | "GoChatCraft/initialize" 7 | "GoChatCraft/models" 8 | router "GoChatCraft/router" 9 | "fmt" 10 | swaggerFiles "github.com/swaggo/files" 11 | ginSwagger "github.com/swaggo/gin-swagger" 12 | ) 13 | 14 | // @title 接囗文档 15 | // @version 1.0 16 | // @description ChatCraft 17 | // @termsofservice https://github.com/taxze6 18 | // @contact.name Taxze 19 | // @contact.email taxze.xiaoyan@gmail.com 20 | // @host 127.0.0.1:8889 21 | func main() { 22 | //Initialize logging. 23 | initialize.InitLogger() 24 | 25 | fmt.Println(global.ServiceConfig.Port) 26 | //Initialize database. 27 | initialize.InitDB() 28 | //Initialize redis. 29 | initialize.InitRedis() 30 | r := router.Router() 31 | r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) 32 | err := r.Run(fmt.Sprintf(":%d", global.ServiceConfig.Port)) 33 | if err != nil { 34 | return 35 | } 36 | } 37 | 38 | func init() { 39 | //Initialize the configuration. 40 | initialize.InitConfig() 41 | 42 | //UDP 43 | //go UdpSendProc() 44 | //go UdpRecProc() 45 | 46 | //rabbitMQ 47 | models.RabbitmqCreateExchange() 48 | go models.RabbitmqRecProc() 49 | go models.RabbitmqSendProc() 50 | } 51 | -------------------------------------------------------------------------------- /middlewear/jwt.go: -------------------------------------------------------------------------------- 1 | package middlewear 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net/http" 7 | "strconv" 8 | "time" 9 | 10 | "github.com/dgrijalva/jwt-go" 11 | "github.com/gin-gonic/gin" 12 | ) 13 | 14 | var ( 15 | TokenExpired = errors.New("token is expired") 16 | ) 17 | 18 | // Specify Encryption Key 19 | var jwtSecret = []byte("taxze_chat_craft") 20 | 21 | // Claims 是一些实体(通常指的用户)的状态和额外的元数据 22 | type Claims struct { 23 | UserID uint `json:"userId"` 24 | jwt.StandardClaims 25 | } 26 | 27 | func JWY() gin.HandlerFunc { 28 | return func(c *gin.Context) { 29 | token := c.GetHeader("Authorization") 30 | user := c.GetHeader("UserId") 31 | userId, err := strconv.Atoi(user) 32 | if err != nil { 33 | c.JSON(http.StatusUnauthorized, map[string]string{ 34 | "message": "Your userId is not valid.", 35 | }) 36 | c.Abort() 37 | return 38 | } 39 | if token == "" { 40 | c.JSON(http.StatusUnauthorized, map[string]string{ 41 | "message": "Please log in.", 42 | }) 43 | c.Abort() 44 | return 45 | } else { 46 | claims, err := ParseToken(token) 47 | if err != nil { 48 | c.JSON(http.StatusUnauthorized, map[string]string{ 49 | "message": "Token expired.", 50 | }) 51 | c.Abort() 52 | return 53 | } else if time.Now().Unix() > claims.ExpiresAt { 54 | err = TokenExpired 55 | c.JSON(http.StatusUnauthorized, map[string]string{ 56 | "message": "授权已过期", 57 | }) 58 | c.Abort() 59 | return 60 | } 61 | 62 | //To achieve dual authentication, both the token and the user ID are transmitted simultaneously. 63 | if claims.UserID != uint(userId) { 64 | c.JSON(http.StatusUnauthorized, map[string]string{ 65 | "message": "Your login is not valid.", 66 | }) 67 | c.Abort() 68 | return 69 | } 70 | 71 | fmt.Println("Token authentication successful.") 72 | c.Next() 73 | } 74 | } 75 | } 76 | 77 | // GenerateToken generates a token based on the user's username and password. 78 | func GenerateToken(userId uint, cc string) (string, error) { 79 | //Set Token Expiration Time 80 | nowTime := time.Now() 81 | //Seven-day validity period 82 | expireTime := nowTime.Add(7 * 24 * time.Hour) 83 | 84 | claims := Claims{ 85 | UserID: userId, 86 | StandardClaims: jwt.StandardClaims{ 87 | // Expiration date 88 | ExpiresAt: expireTime.Unix(), 89 | // Designated token issuer 90 | Issuer: cc, 91 | }, 92 | } 93 | 94 | tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) 95 | //The method generates a signature string internally, which is then used to obtain a complete and signed token. 96 | token, err := tokenClaims.SignedString(jwtSecret) 97 | return token, err 98 | } 99 | 100 | // ParseToken 根据传入的token值获取到Claims对象信息(进而获取其中的用户id) 101 | func ParseToken(token string) (*Claims, error) { 102 | 103 | //用于解析鉴权的声明,方法内部主要是具体的解码和校验的过程,最终返回*Token 104 | tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) { 105 | return jwtSecret, nil 106 | }) 107 | 108 | if tokenClaims != nil { 109 | // 从tokenClaims中获取到Claims对象,并使用断言,将该对象转换为我们自己定义的Claims 110 | // 要传入指针,项目中结构体都是用指针传递,节省空间。 111 | if claims, ok := tokenClaims.Claims.(*Claims); ok && tokenClaims.Valid { 112 | return claims, nil 113 | } 114 | } 115 | return nil, err 116 | } 117 | -------------------------------------------------------------------------------- /models/community.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "GoChatCraft/global" 5 | "errors" 6 | ) 7 | 8 | type Community struct { 9 | Model 10 | Name string 11 | OwnerId uint 12 | Type int 13 | Image string 14 | Desc string 15 | } 16 | 17 | func (r *Community) ComTableName() string { 18 | return "community" 19 | } 20 | 21 | func FindUsersId(groupId uint) (*[]uint, error) { 22 | relation := make([]Relation, 0) 23 | if tx := global.DB.Where("target_id = ? and type = 2", groupId).Find(&relation); tx.RowsAffected == 0 { 24 | return nil, errors.New("no member information found") 25 | } 26 | userIDs := make([]uint, 0) 27 | for _, v := range relation { 28 | userId := v.OwnerId 29 | userIDs = append(userIDs, userId) 30 | } 31 | return &userIDs, nil 32 | } 33 | -------------------------------------------------------------------------------- /models/email_code.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type MailOptions struct { 4 | MailHost string 5 | MailPort uint 6 | MailUser string 7 | MailPass string 8 | MailTo string 9 | Subject string 10 | Body string 11 | } 12 | -------------------------------------------------------------------------------- /models/message.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "GoChatCraft/global" 5 | "context" 6 | "encoding/json" 7 | "fmt" 8 | "github.com/go-redis/redis/v8" 9 | "github.com/gorilla/websocket" 10 | "github.com/streadway/amqp" 11 | "go.uber.org/zap" 12 | "gopkg.in/fatih/set.v0" 13 | "log" 14 | "net" 15 | "net/http" 16 | "strconv" 17 | "sync" 18 | "time" 19 | ) 20 | 21 | type Message struct { 22 | Model 23 | MsgId string `json:"msgId"` 24 | FormId int64 `json:"userId"` 25 | TargetId int64 `json:"targetId"` 26 | Type int `json:"type"` 27 | ContentType int `json:"contentType"` 28 | Status int `json:"status"` 29 | Content string `json:"content"` 30 | MessageSenderName string `json:"messageSenderName"` 31 | MessageSenderFaceUrl string `json:"messageSenderFaceUrl"` 32 | Pic string `json:"pic"` 33 | Url string `json:"url"` 34 | Image ImageModel `json:"image"` 35 | Sound SoundModel `json:"sound"` 36 | QuoteMessage *Message `json:"quoteMessage"` 37 | ReplyEmojis []string `json:"replyEmojis"` 38 | Desc string 39 | Amount int 40 | } 41 | 42 | type ImageModel struct { 43 | ImageUrl string `json:"imageUrl"` 44 | ImageWidth float64 `json:"imageWidth"` 45 | ImageHeight float64 `json:"imageHeight"` 46 | ImageSize float64 `json:"fileSize"` 47 | } 48 | 49 | type SoundModel struct { 50 | SourceUrl string `json:"sourceUrl"` 51 | 52 | SoundPath string `json:"soundPath"` 53 | 54 | DataSize float64 `json:"dataSize"` 55 | 56 | Duration int `json:"duration"` 57 | } 58 | 59 | // CustomError is a custom error type. 60 | type CustomError struct { 61 | message string 62 | } 63 | 64 | // Error implements the error interface for CustomError. 65 | func (e *CustomError) Error() string { 66 | return e.message 67 | } 68 | 69 | const ( 70 | Sending = 1 71 | Succeeded = 2 72 | Failed = 3 73 | Deleted = 4 74 | ) 75 | 76 | func (m *Message) MsgTableName() string { 77 | return "message" 78 | } 79 | 80 | type Node struct { 81 | Conn *websocket.Conn 82 | Addr string 83 | DataQueue chan []byte //消息 84 | GroupSets set.Interface 85 | } 86 | 87 | var clientMap map[int64]*Node = make(map[int64]*Node) //Mapping relationship table (the key of the map is the userId, and the value is the Node, a global map shared by all coroutines) 88 | var rwlocker sync.RWMutex //Read-write lock is needed to ensure thread safety when binding a Node. 89 | 90 | func Chat(w http.ResponseWriter, r *http.Request, Id string) { 91 | //query := r.URL.Query() 92 | //Id := query.Get("userId") 93 | userId, err := strconv.ParseInt(Id, 10, 64) 94 | if err != nil { 95 | zap.S().Info("Type conversion failed", err) 96 | return 97 | } 98 | //Upgrade to socket 99 | var isvalida = true 100 | conn, err := (&websocket.Upgrader{ 101 | CheckOrigin: func(r *http.Request) bool { 102 | return isvalida 103 | }, 104 | }).Upgrade(w, r, nil) 105 | if err != nil { 106 | fmt.Println(err) 107 | return 108 | } 109 | //Get socket connection, construct message node 110 | node := &Node{ 111 | Conn: conn, 112 | DataQueue: make(chan []byte, 50), 113 | GroupSets: set.New(set.ThreadSafe), 114 | } 115 | // Bind userId and Node together 116 | rwlocker.Lock() 117 | clientMap[userId] = node 118 | rwlocker.Unlock() 119 | 120 | //Service sends message 121 | go sendProc(node) 122 | 123 | //Service receives message 124 | go recProc(node) 125 | 126 | //test 127 | //sendMsgAndSave(userId, []byte("{\"msgId\":\"11111\",\"userId\":7,\"targetId\":1,\"type\":101,\"contentType\":101,\"content\":\"hello\",\"CreateAt\":\"2023-12-20 11:13:56.71999 +0800 CST\"}")) 128 | } 129 | 130 | // sendProc retrieves information from the node and writes it into the WebSocket. 131 | func sendProc(node *Node) { 132 | for { 133 | select { 134 | case data := <-node.DataQueue: 135 | err := node.Conn.WriteMessage(websocket.TextMessage, data) 136 | if err != nil { 137 | zap.S().Info("Failed to write the message", err) 138 | return 139 | } 140 | fmt.Println("The data has been successfully sent through the socket.") 141 | } 142 | } 143 | } 144 | 145 | // recProc retrieves the message body from the WebSocket, 146 | // then parses it, performs message type identification, and finally sends the message to the destination user's node. 147 | func recProc(node *Node) { 148 | for { 149 | // Retrieve information 150 | _, data, err := node.Conn.ReadMessage() 151 | if err != nil { 152 | zap.S().Info("Failed to read the message.", err) 153 | return 154 | } 155 | 156 | // Put the message body into the global channel. 157 | // brodMsg(data) 158 | // Handle the received message 159 | handleReceivedMessage(node, data) 160 | var jsonData map[string]interface{} 161 | err = json.Unmarshal(data, &jsonData) 162 | 163 | if err != nil { 164 | zap.S().Info("Failed to parse JSON data.", err) 165 | } 166 | msgID := jsonData["msgId"] 167 | if msgID == "-1" { 168 | //This is a heartbeat message not stored in Redis 169 | } else { 170 | pushMsg(data) 171 | } 172 | } 173 | } 174 | 175 | // handleReceivedMessage processes the received message and sends a response. 176 | func handleReceivedMessage(node *Node, receivedData []byte) { 177 | // Your message processing logic here 178 | // For demonstration, let's assume echoing the received message as a response. 179 | 180 | // Process the received data (you can replace this with your own logic) 181 | processedData, err := processReceivedData(receivedData) 182 | 183 | if err != nil { 184 | return 185 | } 186 | // Send a response back to the client 187 | err = node.Conn.WriteMessage(websocket.TextMessage, processedData) 188 | if err != nil { 189 | zap.S().Info("Failed to write the response message", err) 190 | return 191 | } 192 | 193 | fmt.Println("Response sent to the client.") 194 | } 195 | 196 | func processReceivedData(data []byte) ([]byte, error) { 197 | // For demonstration, let's simply echo the received data. 198 | // Update data with timestamp and status 199 | var jsonData map[string]interface{} 200 | err := json.Unmarshal(data, &jsonData) 201 | //if msgID, ok := jsonData["msgId"]; ok && msgID == "-1" { 202 | // // If msgId is present and equals -1, return a custom error 203 | // return nil, &CustomError{"Invalid msgId: -1"} 204 | //} 205 | 206 | if err != nil { 207 | zap.S().Info("Failed to parse JSON data.", err) 208 | } 209 | // Modify the data 210 | jsonData["createAt"] = time.Now() 211 | jsonData["status"] = Succeeded 212 | // Marshal the updated data 213 | updatedData, err := json.Marshal(jsonData) 214 | if err != nil { 215 | zap.S().Info("Failed to marshal JSON data.", err) 216 | } 217 | // Parse the updated data into a Message struct (optional) 218 | msg := Message{} 219 | err = json.Unmarshal(updatedData, &msg) 220 | if err != nil { 221 | zap.S().Info("Failed to parse the message.", err) 222 | } 223 | // Print the parsed data (optional) 224 | fmt.Println("Parse the data:", "msg.FormId", msg.FormId, "targetId:", msg.TargetId, "type:", msg.Type, "time:", msg.CreateAt) 225 | // Return the updated data 226 | return updatedData, nil 227 | } 228 | 229 | var upSendChan chan []byte = make(chan []byte, 2048) 230 | var mqSendChan chan []byte = make(chan []byte, 2048) 231 | 232 | func brodMsg(data []byte) { 233 | upSendChan <- data 234 | } 235 | 236 | func pushMsg(data []byte) { 237 | mqSendChan <- data 238 | } 239 | 240 | // The UdpSendProc completes UDP data sending by connecting to the UDP server and writing the message body from the global channel to the UDP server. 241 | func UdpSendProc() { 242 | udpConn, err := net.DialUDP("udp", nil, &net.UDPAddr{ 243 | IP: net.IPv4(127, 0, 0, 1), 244 | Port: 3000, 245 | Zone: "", 246 | }) 247 | if err != nil { 248 | zap.S().Info("Failed to dial UDP port.") 249 | return 250 | } 251 | defer udpConn.Close() 252 | for { 253 | select { 254 | case data := <-upSendChan: 255 | _, err := udpConn.Write(data) 256 | if err != nil { 257 | zap.S().Info("Failed to write UDP message.", err) 258 | return 259 | } 260 | fmt.Println("The data has been successfully sent to the UDP server:", string(data)) 261 | } 262 | } 263 | } 264 | 265 | // UdpRecProc completes the reception of UDP data, starts the UDP service, and retrieves the messages written by UDP clients. 266 | func UdpRecProc() { 267 | udpConn, err := net.ListenUDP("udp", &net.UDPAddr{ 268 | IP: net.IPv4(127, 0, 0, 1), 269 | Port: 3000, 270 | }) 271 | if err != nil { 272 | zap.S().Info("Failed to listen on UDP port.", err) 273 | return 274 | } 275 | defer udpConn.Close() 276 | for { 277 | var buf [1024]byte 278 | n, err := udpConn.Read(buf[0:]) 279 | if err != nil { 280 | zap.S().Info("Failed to read UDP data.", err) 281 | return 282 | } 283 | fmt.Println("UDP server receives UDP data:", buf[0:n]) 284 | dispatch(buf[0:n]) 285 | } 286 | } 287 | 288 | // Creating an Exchange in RabbitMQ 289 | func RabbitmqCreateExchange() { 290 | //rabbitmqUser := "guest" 291 | //rabbitmqPassword := "guest" 292 | //rabbitmqIp := "127.0.0.1" 293 | //rabbitmqPort := "5672" 294 | // 295 | //conn, err := amqp.Dial("amqp://" + rabbitmqUser + ":" + rabbitmqPassword + "@" + rabbitmqIp + ":" + rabbitmqPort + "/") 296 | 297 | rabbitmqHost := global.ServiceConfig.RabbitMQConfig.Host 298 | rabbitmqPort := global.ServiceConfig.RabbitMQConfig.Port 299 | rabbitmqUser := global.ServiceConfig.RabbitMQConfig.User 300 | rabbitmqPassword := global.ServiceConfig.RabbitMQConfig.Password 301 | 302 | connString := fmt.Sprintf("amqp://%s:%s@%s:%s/", rabbitmqUser, rabbitmqPassword, rabbitmqHost, rabbitmqPort) 303 | conn, err := amqp.Dial(connString) 304 | if err != nil { 305 | log.Println(err) 306 | zap.S().Info("Failed to connect to RabbitMQ", err) 307 | } 308 | defer conn.Close() 309 | 310 | ch, err := conn.Channel() 311 | zap.S().Info("Failed to open a channel", err) 312 | defer ch.Close() 313 | err = ch.ExchangeDeclare( 314 | "chat-craft-exchange", // name 315 | "fanout", // typed 316 | true, // durable 317 | false, // auto-deleted 318 | false, // internal 319 | false, // no-wait 320 | nil, // arguments 321 | ) 322 | zap.S().Info("Failed to declare an exchange", err) 323 | } 324 | 325 | func RabbitmqRecProc() { 326 | //rabbitmqUser := "guest" 327 | //rabbitmqPassword := "guest" 328 | //rabbitmqIp := "127.0.0.1" 329 | //rabbitmqPort := "5672" 330 | // 331 | //conn, err := amqp.Dial("amqp://" + rabbitmqUser + ":" + rabbitmqPassword + "@" + rabbitmqIp + ":" + rabbitmqPort + "/") 332 | connString := fmt.Sprintf("amqp://%s:%s@%s:%s/", global.ServiceConfig.RabbitMQConfig.User, global.ServiceConfig.RabbitMQConfig.Password, global.ServiceConfig.RabbitMQConfig.Host, global.ServiceConfig.RabbitMQConfig.Port) 333 | conn, err := amqp.Dial(connString) 334 | defer conn.Close() 335 | if err != nil { 336 | zap.S().Info("Failed to connect to the message queue.", err) 337 | } else { 338 | log.Println("Successfully connected to RabbitMQ!") 339 | } 340 | ch, err := conn.Channel() 341 | if err != nil { 342 | zap.S().Info("Failed to create the MQ channel.", err) 343 | } else { 344 | log.Println("Successfully created the MQ channel.") 345 | } 346 | defer ch.Close() 347 | 348 | err = ch.ExchangeDeclare( 349 | "chat-craft-exchange", // name 350 | "fanout", // type 351 | true, // durable 352 | false, // auto-deleted 353 | false, // internal 354 | false, // no-wait 355 | nil, // arguments 356 | ) 357 | if err != nil { 358 | zap.S().Info("Failed to declare the exchange.", err) 359 | } else { 360 | log.Println("Successfully declared the exchange.") 361 | } 362 | q, err := ch.QueueDeclare( 363 | "chat-craft-queue-2", // name 364 | false, // durable 365 | false, // delete when unused 366 | true, // exclusive 367 | false, // no-wait 368 | nil, // arguments 369 | ) 370 | if err != nil { 371 | zap.S().Info("Failed to declare the queue.", err) 372 | } else { 373 | log.Println("Successfully declared the queue.") 374 | } 375 | err = ch.QueueBind( 376 | q.Name, // queue name 377 | "", // routing key 378 | "chat-craft-exchange", // exchange 379 | false, 380 | nil) 381 | if err != nil { 382 | zap.S().Info("Failed to bind to the exchange.", err) 383 | } else { 384 | log.Println("Successfully bound") 385 | } 386 | 387 | msgs, err := ch.Consume( 388 | q.Name, // queue 389 | "", // consumer 390 | true, // auto-ack 391 | false, // exclusive 392 | false, // no-local 393 | false, // no-wait 394 | nil, // args 395 | ) 396 | if err != nil { 397 | zap.S().Info("Failed to consume the MQ message.", err) 398 | } else { 399 | log.Println("Successfully consumed the MQ message.") 400 | } 401 | forever := make(chan bool) 402 | 403 | go func() { 404 | for d := range msgs { 405 | //接收到别的机器发送来的消息 406 | log.Printf(" [x] %s", d.Body) 407 | dispatch(d.Body) 408 | } 409 | }() 410 | log.Printf(" [*] Waiting for logs. To exit press CTRL+C") 411 | <-forever 412 | } 413 | 414 | // rabbitmq发送协程 415 | func RabbitmqSendProc() { 416 | //rabbitmqUser := "guest" 417 | //rabbitmqPassword := "guest" 418 | //rabbitmqIp := "127.0.0.1" 419 | //rabbitmqPort := "5672" 420 | //conn, err := amqp.Dial("amqp://" + rabbitmqUser + ":" + rabbitmqPassword + "@" + rabbitmqIp + ":" + rabbitmqPort + "/") 421 | connString := fmt.Sprintf("amqp://%s:%s@%s:%s/", global.ServiceConfig.RabbitMQConfig.User, global.ServiceConfig.RabbitMQConfig.Password, global.ServiceConfig.RabbitMQConfig.Host, global.ServiceConfig.RabbitMQConfig.Port) 422 | conn, err := amqp.Dial(connString) 423 | if err != nil { 424 | zap.S().Info("Failed to connect to RabbitMQ", err) 425 | } 426 | defer conn.Close() 427 | 428 | ch, err := conn.Channel() 429 | if err != nil { 430 | zap.S().Info("Failed to open a channel", err) 431 | } 432 | defer ch.Close() 433 | 434 | err = ch.ExchangeDeclare( 435 | "chat-craft-exchange", // name 436 | "fanout", // type 437 | true, // durable 438 | false, // auto-deleted 439 | false, // internal 440 | false, // no-wait 441 | nil, // arguments 442 | ) 443 | 444 | if err != nil { 445 | zap.S().Info("Failed to declare an exchange", err) 446 | } 447 | //RabbitMQ Coroutine for Sending: Continuously read from mqsendchan and deliver the message to MQ when there is a message. 448 | for { 449 | select { 450 | case body := <-mqSendChan: 451 | //If a message is delivered, send this message to the exchange. 452 | err = ch.Publish( 453 | "chat-craft-exchange", // exchange 454 | "", // routing key 455 | false, // mandatory 456 | false, // immediate 457 | amqp.Publishing{ 458 | ContentType: "text/plain", 459 | Body: []byte(body), 460 | }) 461 | if err != nil { 462 | zap.S().Info("Failed to publish a message", err) 463 | } else { 464 | log.Printf(" [x] Sent %s", body) 465 | } 466 | } 467 | } 468 | } 469 | 470 | // Dispatch: Parsing the message and determining the chat type. 471 | func dispatch(data []byte) { 472 | // Update the time field in the data. 473 | var jsonData map[string]interface{} 474 | err := json.Unmarshal(data, &jsonData) 475 | if err != nil { 476 | zap.S().Info("Failed to parse JSON data.", err) 477 | return 478 | } 479 | jsonData["createAt"] = time.Now() 480 | jsonData["status"] = Succeeded 481 | updatedData, err := json.Marshal(jsonData) 482 | if err != nil { 483 | zap.S().Info("Failed to marshal JSON data.", err) 484 | return 485 | } 486 | msg := Message{} 487 | err = json.Unmarshal(updatedData, &msg) 488 | if err != nil { 489 | zap.S().Info("Failed to parse the message.", err) 490 | return 491 | } 492 | fmt.Println("Parse the data:", "msg.FormId", msg.FormId, "targetId:", msg.TargetId, "type:", msg.Type, "time:", msg.CreateAt) 493 | switch msg.Type { 494 | case 1: 495 | sendMsgAndSave(msg.TargetId, updatedData) 496 | case 2: 497 | sendGroupMsg(uint(msg.FormId), uint(msg.TargetId), updatedData) 498 | } 499 | } 500 | 501 | func sendGroupMsg(formId, target uint, data []byte) (int, error) { 502 | //Get all users in the group, and then send a message to each user except yourself. 503 | userIDs, err := FindUsersId(target) 504 | if err != nil { 505 | return -1, err 506 | } 507 | for _, userId := range *userIDs { 508 | //Do not forward the message to the member who is currently sending the message. 509 | if formId != userId { 510 | //Multiple calls to the individual chat function have turned the group chat into multiple individual chats. 511 | sendMsgAndSave(int64(userId), data) 512 | } 513 | } 514 | return 0, nil 515 | } 516 | 517 | func sendMsgAndSave(userId int64, msg []byte) { 518 | rwlocker.RLock() //Ensure thread safety by locking. 519 | node, ok := clientMap[userId] //Whether the other party is online 520 | rwlocker.RUnlock() 521 | jsonMsg := Message{} 522 | json.Unmarshal(msg, &jsonMsg) 523 | ctx := context.Background() 524 | targetIdStr := strconv.Itoa(int(userId)) 525 | userIdStr := strconv.Itoa(int(jsonMsg.FormId)) 526 | if ok { 527 | //If the current user is online, forward the message to the current user's WebSocket connection and then store it. 528 | node.DataQueue <- msg 529 | } 530 | msgID := jsonMsg.MsgId 531 | if msgID == "typingId" { 532 | //This is an input status message and is not stored in Redis 533 | } else { 534 | //Concatenate userIdStr and targetIdStr to create a unique key. 535 | var key string 536 | if userId > jsonMsg.FormId { 537 | key = "msg_" + userIdStr + "_" + targetIdStr 538 | } else { 539 | key = "msg_" + targetIdStr + "_" + userIdStr 540 | } 541 | fmt.Println(key) 542 | // 543 | res, err := global.RedisDB.ZRevRange(ctx, key, 0, -1).Result() 544 | if err != nil { 545 | fmt.Println(err) 546 | return 547 | } 548 | //Write the chat records into Redis cache. 549 | score := float64(cap(res)) + 1 550 | ress, e := global.RedisDB.ZAdd(ctx, key, &redis.Z{Score: score, Member: msg}).Result() 551 | if e != nil { 552 | zap.S().Info(e) 553 | return 554 | } 555 | fmt.Println(ress) 556 | } 557 | } 558 | 559 | // isRev is a boolean parameter used to indicate whether to retrieve chat records from the cache in reverse order (from largest to smallest). 560 | // If isRev is true, the ZRange function is used to retrieve records in ascending order. 561 | // If isRev is false, the ZRevRange function is used to retrieve records in reverse order. 562 | func RedisMsg(userIdA int64, userIdB int64, start int64, end int64, isRev bool) []string { 563 | ctx := context.Background() 564 | userIdStr := strconv.Itoa(int(userIdA)) 565 | targetIdStr := strconv.Itoa(int(userIdB)) 566 | 567 | var key string 568 | if userIdA > userIdB { 569 | key = "msg_" + targetIdStr + "_" + userIdStr 570 | } else { 571 | key = "msg_" + userIdStr + "_" + targetIdStr 572 | } 573 | fmt.Println(key) 574 | var rels []string 575 | var err error 576 | if isRev { 577 | rels, err = global.RedisDB.ZRange(ctx, key, start, end).Result() 578 | } else { 579 | rels, err = global.RedisDB.ZRevRange(ctx, key, start, end).Result() 580 | } 581 | if err != nil { 582 | //The message could not be found. 583 | zap.S().Info(err) 584 | fmt.Println(err) 585 | } 586 | return rels 587 | } 588 | -------------------------------------------------------------------------------- /models/relation.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type Relation struct { 4 | Model 5 | OwnerId uint //Users corresponding to the relationship. 6 | TargetId uint //It corresponds to whom 7 | Type int //Relationship types: 1 represents friendship, 2 represents group relationship. 8 | Desc string //Description 9 | } 10 | 11 | func (r *Relation) RelTableName() string { 12 | return "relation" 13 | } 14 | -------------------------------------------------------------------------------- /models/user_basic.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "gorm.io/gorm" 5 | "time" 6 | ) 7 | 8 | type Model struct { 9 | ID uint `gorm:"primaryKey"` 10 | CreateAt time.Time `gorm:"autoCreateTime"` 11 | UpdateAt time.Time `gorm:"autoUpdateTime"` 12 | DeleteAt gorm.DeletedAt `gorm:"index"` 13 | } 14 | 15 | type UserBasic struct { 16 | Model 17 | Name string 18 | PassWord string 19 | Avatar string 20 | Gender string `gorm:"column:gender;default:male;type:varchar(6)"` 21 | Phone string `valid:"matches(^1[3-9]{1}\\d{9}$)"` 22 | Email string `valid:"email"` 23 | Motto string 24 | Identity string 25 | ClientIp string `valid:"ipv4"` 26 | ClientPort string 27 | Salt string 28 | LoginTime *time.Time `gorm:"column:login_time"` 29 | HeartBeatTime *time.Time `gorm:"column:heart_beat_time"` 30 | LoginOutTime *time.Time `gorm:"column:login_out_time"` 31 | IsLoginOut bool 32 | DeviceInfo string 33 | } 34 | 35 | type UserResponse struct { 36 | ID uint `json:"id"` 37 | Name string `json:"name"` 38 | Email string `json:"email"` 39 | Phone string `json:"phone"` 40 | Avatar string `json:"avatar"` 41 | Motto string `json:"motto"` 42 | Identity string `json:"identity"` 43 | ClientIp string `json:"client_ip"` 44 | ClientPort string `json:"client_port"` 45 | } 46 | 47 | func (table *UserBasic) UserTableName() string { 48 | return "user_basic" 49 | } 50 | -------------------------------------------------------------------------------- /models/user_collect.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type UserCollect struct { 4 | Model 5 | CollectOwnerId uint `json:"collect_owner_id"` 6 | CollectContent string `json:"collect_content"` 7 | Type int `json:"type"` 8 | } 9 | 10 | func (table *UserCollect) UserCollectTableName() string { 11 | return "user_collect" 12 | } 13 | -------------------------------------------------------------------------------- /models/user_story.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type UserStory struct { 4 | Model 5 | OwnerId uint `json:"owner_id"` 6 | Content string `json:"content"` 7 | Media string `json:"media"` 8 | Type int `json:"type"` 9 | } 10 | 11 | type ResponseUserStory struct { 12 | Story UserStory `json:"story"` 13 | StoryLikes *[]UserStoryLike `json:"story_likes"` 14 | StoryComments *[]ResponseUserStoryComment `json:"story_comments"` 15 | } 16 | type UserShowStoryListResponse struct { 17 | StoryList *[]ResponseUserStory 18 | Count int 19 | } 20 | 21 | func (table *UserStory) UserStoryTableName() string { 22 | return "user_story" 23 | } 24 | -------------------------------------------------------------------------------- /models/user_story_comment.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type UserStoryComment struct { 4 | Model 5 | UserStoryId uint `json:"user_story_id"` 6 | CommentOwnerId uint `json:"comment_owner_id"` 7 | CommentContent string `json:"comment_content"` 8 | Type int `json:"type"` 9 | } 10 | 11 | type ResponseUserStoryComment struct { 12 | StoryComment *UserStoryComment `json:"story_comment"` 13 | UserAvatar string `json:"user_avatar"` 14 | } 15 | 16 | func (table *UserStoryComment) UserStoryCommentTableName() string { 17 | return "user_story_comment" 18 | } 19 | -------------------------------------------------------------------------------- /models/user_story_like.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type UserStoryLike struct { 4 | Model 5 | UserStoryId uint `json:"user_story_id"` 6 | LikeOwnerId uint `json:"like_owner_id"` 7 | } 8 | 9 | func (table *UserStoryLike) UserStoryLikeTableName() string { 10 | return "user_story_like" 11 | } 12 | -------------------------------------------------------------------------------- /router/router.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "GoChatCraft/middlewear" 5 | "GoChatCraft/service" 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | func Router() *gin.Engine { 10 | router := gin.Default() 11 | v1 := router.Group("v1") 12 | //assets 13 | router.Static("/assets", "assets/") 14 | 15 | user := v1.Group("user") 16 | { 17 | user.GET("/user_list", middlewear.JWY(), service.GetUserList) 18 | user.POST("/login", service.LoginByNameAndPassWord) 19 | user.POST("/register", service.NewUser) 20 | user.POST("/register_email_code_check", service.CheckRegisterEmailCode) 21 | user.POST("/email_login", service.EmailLogin) 22 | user.POST("/email_login_code_check", service.CheckLoginEmailCode) 23 | user.POST("/find_user_with_name", middlewear.JWY(), service.FindUserWithUserName) 24 | user.POST("/get_story_list", middlewear.JWY(), service.GetStoryList) 25 | user.POST("/get_user_show_story_list", middlewear.JWY(), service.GetUserShowStoryList) 26 | user.POST("/add_story", middlewear.JWY(), service.AddStory) 27 | user.POST("/add_story_like", middlewear.JWY(), service.AddStoryLike) 28 | user.POST("/add_story_comment", middlewear.JWY(), service.AddStoryComment) 29 | user.POST("/user_info_update", middlewear.JWY(), service.UpdateUser) 30 | user.POST("/user_info_password_update", middlewear.JWY(), service.UpdateUserPassword) 31 | } 32 | relation := v1.Group("relation").Use(middlewear.JWY()) 33 | { 34 | relation.POST("/list", service.FriendList) 35 | relation.POST("/add_username", service.AddFriendByName) 36 | relation.POST("/add_userid", service.AddFriendByUserId) 37 | } 38 | group := v1.Group("group").Use(middlewear.JWY()) 39 | { 40 | group.POST("/new_group", service.NewGroup) 41 | } 42 | message := v1.Group("message").Use(middlewear.JWY()) 43 | { 44 | message.GET("/send_user_msg", service.SendUserMsg) 45 | message.POST("/get_redis_msg", service.GetRedisMsg) 46 | } 47 | 48 | upload := v1.Group("upload") 49 | { 50 | upload.POST("/file", middlewear.JWY(), service.File) 51 | upload.GET("/getEmojiZip", service.DownloadEmojiZip) 52 | } 53 | return router 54 | } 55 | -------------------------------------------------------------------------------- /service/attach_upload.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "GoChatCraft/common" 5 | "GoChatCraft/global" 6 | "fmt" 7 | "github.com/gin-gonic/gin" 8 | "io" 9 | "math/rand" 10 | "os" 11 | "strconv" 12 | "time" 13 | ) 14 | 15 | func File(ctx *gin.Context) { 16 | w := ctx.Writer 17 | req := ctx.Request 18 | srcFile, head, err := req.FormFile("file") 19 | fileType := req.FormValue("type") 20 | if err != nil { 21 | common.RespFail(w, err.Error(), "Unable to read the file.") 22 | return 23 | } 24 | //default folder name 25 | folderName := "image/" 26 | switch fileType { 27 | case "102": 28 | // processing picture types 29 | folderName = "image/" 30 | case "103": 31 | // processing speech type 32 | folderName = "voice/" 33 | case "104": 34 | // processing video type 35 | folderName = "video/" 36 | default: 37 | // handling unknown types 38 | folderName = "image/" 39 | } 40 | //suffix := ".png" 41 | //ofName := head.Filename 42 | //tem := strings.Split(ofName, ".") 43 | //if len(tem) > 1 { 44 | // suffix = "." + tem[len(tem)-1] 45 | //} 46 | fileName := fmt.Sprintf("%d%04d%s", time.Now().Unix(), rand.Int31(), head.Filename) 47 | 48 | //fileName := fmt.Sprintf("%s", head.Filename) 49 | dstFile, err := os.Create("./assets/upload/" + folderName + fileName) 50 | if err != nil { 51 | common.RespFail(w, err.Error(), "Failed to create the file.") 52 | return 53 | } 54 | _, err = io.Copy(dstFile, srcFile) 55 | if err != nil { 56 | common.RespFail(w, err.Error(), "Upload failed.") 57 | } 58 | url := "http://" + global.ServiceConfig.Host + ":" + strconv.Itoa(global.ServiceConfig.Port) + "/assets/upload/" + folderName + fileName 59 | common.RespOk(w, url, "Sent successfully.") 60 | } 61 | -------------------------------------------------------------------------------- /service/community.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "GoChatCraft/common" 5 | "GoChatCraft/dao" 6 | "GoChatCraft/models" 7 | "encoding/json" 8 | "github.com/gin-gonic/gin" 9 | "go.uber.org/zap" 10 | "strconv" 11 | ) 12 | 13 | func NewGroup(ctx *gin.Context) { 14 | getData, _ := ctx.GetRawData() 15 | var body map[string]string 16 | _ = json.Unmarshal(getData, &body) 17 | img := body["group_img"] 18 | name := body["group_name"] 19 | desc := body["group_desc"] 20 | user := ctx.GetHeader("UserId") 21 | userId, err := strconv.Atoi(user) 22 | if err != nil { 23 | zap.S().Info("failed to convert data type", err) 24 | return 25 | } 26 | community := models.Community{} 27 | 28 | community.Name = name 29 | community.Type = 2 30 | community.Image = img 31 | community.Desc = desc 32 | community.OwnerId = uint(userId) 33 | code, err := dao.CreateCommunity(community) 34 | if err != nil { 35 | var res string 36 | switch code { 37 | case -1: 38 | res = err.Error() 39 | case 0: 40 | res = "The friend already exists." 41 | case -2: 42 | res = "You cannot add yourself." 43 | default: 44 | res = "Unknown error." 45 | } 46 | common.RespFail(ctx.Writer, res, res) 47 | return 48 | } 49 | 50 | ctx.JSON(200, gin.H{ 51 | "code": 0, // 0成功 -1失败 52 | "message": "键群成功", 53 | }) 54 | } 55 | -------------------------------------------------------------------------------- /service/email_code.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "GoChatCraft/dao" 5 | "GoChatCraft/models" 6 | ) 7 | 8 | func GetEmailCode(mailTo string, from string) error { 9 | option := &models.MailOptions{ 10 | MailHost: "smtp.qq.com", 11 | MailPort: 465, 12 | MailUser: "1929509811@qq.com", 13 | MailPass: "aqvgewdgxhygdecf", 14 | MailTo: mailTo, 15 | Subject: "chat craft email code test", 16 | Body: "", 17 | } 18 | err := dao.SendMailCode(option, from, 1) 19 | if err != nil { 20 | return err 21 | } 22 | return nil 23 | } 24 | 25 | func CheckEmailCode(email string, code string, from string) error { 26 | err := dao.ValidateMailCode(email, code, from) 27 | if err != nil { 28 | return err 29 | } 30 | return nil 31 | } 32 | -------------------------------------------------------------------------------- /service/message.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "GoChatCraft/common" 5 | "GoChatCraft/models" 6 | "encoding/json" 7 | "fmt" 8 | "github.com/gin-gonic/gin" 9 | "strconv" 10 | ) 11 | 12 | func SendUserMsg(ctx *gin.Context) { 13 | models.Chat(ctx.Writer, ctx.Request, ctx.GetHeader("UserId")) 14 | } 15 | 16 | func GetRedisMsg(ctx *gin.Context) { 17 | getData, _ := ctx.GetRawData() 18 | var body map[string]interface{} 19 | _ = json.Unmarshal(getData, &body) 20 | test := body["targetId"] 21 | fmt.Println(test) 22 | user := ctx.GetHeader("UserId") 23 | userId, _ := strconv.Atoi(user) 24 | targetId := 0 25 | if val, ok := body["targetId"].(float64); ok { 26 | targetId = int(val) 27 | } 28 | 29 | start := 0 30 | if val, ok := body["start"].(float64); ok { 31 | start = int(val) 32 | } 33 | 34 | end := 0 35 | if val, ok := body["end"].(float64); ok { 36 | end = int(val) 37 | } 38 | 39 | isRev := false 40 | if val, ok := body["isRev"].(bool); ok { 41 | isRev = val 42 | } 43 | res := models.RedisMsg(int64(userId), int64(targetId), int64(start), int64(end), isRev) 44 | fmt.Println(res) 45 | common.RespOkList(ctx.Writer, res, "Get Msg Ok", len(res)) 46 | } 47 | -------------------------------------------------------------------------------- /service/relation.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "GoChatCraft/common" 5 | "GoChatCraft/dao" 6 | "GoChatCraft/models" 7 | "encoding/json" 8 | "github.com/gin-gonic/gin" 9 | "go.uber.org/zap" 10 | "strconv" 11 | ) 12 | 13 | func FriendList(ctx *gin.Context) { 14 | id, _ := strconv.Atoi(ctx.GetHeader("UserId")) 15 | users, err := dao.FriendList(uint(id)) 16 | if err != nil { 17 | zap.S().Info("failed to retrieve friend list.", err) 18 | common.RespOk(ctx.Writer, nil, "Friend list is empty.") 19 | return 20 | } 21 | infos := make([]models.UserResponse, 0) 22 | for _, v := range *users { 23 | info := models.UserResponse{ 24 | ID: v.ID, 25 | Name: v.Name, 26 | Email: v.Email, 27 | Phone: v.Phone, 28 | Avatar: v.Avatar, 29 | Motto: v.Motto, 30 | Identity: v.Identity, 31 | ClientIp: v.ClientIp, 32 | ClientPort: v.ClientPort, 33 | } 34 | infos = append(infos, info) 35 | } 36 | common.RespOkList(ctx.Writer, infos, "Successfully retrieved friend list.", len(infos)) 37 | } 38 | 39 | func AddFriendByName(ctx *gin.Context) { 40 | getData, _ := ctx.GetRawData() 41 | var body map[string]string 42 | _ = json.Unmarshal(getData, &body) 43 | user := ctx.GetHeader("UserId") 44 | userId, err := strconv.Atoi(user) 45 | if err != nil { 46 | zap.S().Info("failed to convert data type", err) 47 | return 48 | } 49 | tar := body["targetName"] 50 | code, err := dao.AddFriendByName(uint(userId), tar) 51 | if err != nil { 52 | var res string 53 | switch code { 54 | case -1: 55 | res = err.Error() 56 | case 0: 57 | res = "The friend already exists." 58 | case -2: 59 | res = "You cannot add yourself." 60 | default: 61 | res = "Unknown error." 62 | } 63 | common.RespFail(ctx.Writer, res, res) 64 | return 65 | } 66 | common.RespOk(ctx.Writer, "Successfully added friend.", "Successfully added friend.") 67 | } 68 | 69 | func AddFriendByUserId(ctx *gin.Context) { 70 | getData, _ := ctx.GetRawData() 71 | var body map[string]int 72 | _ = json.Unmarshal(getData, &body) 73 | user := ctx.GetHeader("UserId") 74 | userId, err := strconv.Atoi(user) 75 | if err != nil { 76 | zap.S().Info("failed to convert data type", err) 77 | return 78 | } 79 | tar := body["targetUserId"] 80 | code, err := dao.AddFriendByUserId(uint(userId), uint(tar)) 81 | if err != nil { 82 | var res string 83 | switch code { 84 | case -1: 85 | res = err.Error() 86 | case 0: 87 | res = "The friend already exists." 88 | case -2: 89 | res = "You cannot add yourself." 90 | default: 91 | res = "Unknown error." 92 | } 93 | common.RespFail(ctx.Writer, res, res) 94 | return 95 | } 96 | common.RespOk(ctx.Writer, "Successfully added friend.", "Successfully added friend.") 97 | } 98 | -------------------------------------------------------------------------------- /service/send_file.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | "os" 7 | "strconv" 8 | ) 9 | 10 | func DownloadEmojiZip(c *gin.Context) { 11 | // Obtain the path of the zip file 12 | filePath := "./assets/download/fluentui_emoji_icon_data.zip" 13 | 14 | // open-file 15 | file, err := os.Open(filePath) 16 | if err != nil { 17 | c.String(http.StatusInternalServerError, "Error opening file") 18 | return 19 | } 20 | defer file.Close() 21 | 22 | //Gets file information, including size 23 | fileInfo, err := file.Stat() 24 | if err != nil { 25 | c.String(http.StatusInternalServerError, "Error getting file info") 26 | return 27 | } 28 | 29 | // Set the response header to tell the browser that this is a zip file 30 | c.Header("Content-Type", "application/zip") 31 | 32 | c.Header("Content-Disposition", "attachment; filename=emoji_icon_data.zip") 33 | 34 | c.Header("Content-Length", strconv.FormatInt(fileInfo.Size(), 10)) 35 | 36 | // Send the zip file to the client 37 | c.File(filePath) 38 | } 39 | -------------------------------------------------------------------------------- /service/user.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "GoChatCraft/common" 5 | "GoChatCraft/dao" 6 | "GoChatCraft/global" 7 | "GoChatCraft/middlewear" 8 | "GoChatCraft/models" 9 | "encoding/json" 10 | "fmt" 11 | "github.com/gin-gonic/gin" 12 | "go.uber.org/zap" 13 | "math/rand" 14 | "net/http" 15 | "strconv" 16 | "time" 17 | ) 18 | 19 | // GetUserList Get All User List 20 | // 21 | // @Summary List Get User List 22 | // @Description User List 23 | // @Tags test 24 | // @Accept json 25 | // @Router /user/list [get] 26 | func GetUserList(ctx *gin.Context) { 27 | list, err := dao.GetUserList() 28 | if err != nil { 29 | ctx.JSON(200, gin.H{ 30 | "code": -1, 31 | "message": "Failed to retrieve the user list.", 32 | }) 33 | return 34 | } 35 | ctx.JSON(http.StatusOK, list) 36 | } 37 | 38 | func LoginByNameAndPassWord(ctx *gin.Context) { 39 | getData, _ := ctx.GetRawData() 40 | var body map[string]string 41 | _ = json.Unmarshal(getData, &body) 42 | name := body["name"] 43 | encryptionPassword := body["password"] 44 | //First, check if the username exists, then proceed to check the password. 45 | data, err := dao.FindUserByName(name) 46 | if err != nil { 47 | common.RespFail(ctx.Writer, "Login Failed", "Login Failed.") 48 | return 49 | } 50 | if data.Name == "" { 51 | common.RespFail(ctx.Writer, "Username Does Not Exist.", "Username Does Not Exist.") 52 | return 53 | } 54 | password, err := common.RsaDecoder(encryptionPassword) 55 | if err != nil { 56 | zap.S().Info("RSA Decryption Failed") 57 | common.RespFail(ctx.Writer, "Password parsing error!", "Password parsing error!") 58 | return 59 | } 60 | ok := common.CheckPassWord(password, data.Salt, data.PassWord) 61 | if !ok { 62 | common.RespFail(ctx.Writer, "Incorrect Password.", "Incorrect Password.") 63 | return 64 | } 65 | userInfo, err := dao.FindUserByNameAndPwd(name, data.PassWord) 66 | if err != nil { 67 | zap.S().Info("Login Failed.", err) 68 | common.RespFail(ctx.Writer, "Login Failed.", "Login Failed.") 69 | return 70 | } 71 | //Using JWT for authentication. 72 | token, err := middlewear.GenerateToken(userInfo.ID, "cc") 73 | if err != nil { 74 | zap.S().Info("Failed to Generate Token", err) 75 | common.RespFail(ctx.Writer, "Failed to Generate Token.", "Failed to Generate Token.") 76 | return 77 | } 78 | response := models.UserResponse{ 79 | ID: userInfo.ID, 80 | Name: userInfo.Name, 81 | Email: userInfo.Email, 82 | Phone: userInfo.Phone, 83 | Avatar: userInfo.Avatar, 84 | Motto: userInfo.Motto, 85 | Identity: userInfo.Identity, 86 | ClientIp: userInfo.ClientIp, 87 | ClientPort: userInfo.ClientPort, 88 | } 89 | common.RespOk(ctx.Writer, gin.H{ 90 | "token": token, 91 | "user": response}, "Login Successful.") 92 | } 93 | 94 | func NewUser(ctx *gin.Context) { 95 | user := models.UserBasic{} 96 | getData, _ := ctx.GetRawData() 97 | var body map[string]string 98 | _ = json.Unmarshal(getData, &body) 99 | user.Name = body["name"] 100 | user.Email = body["email"] 101 | encryptionPassword := body["password"] 102 | encryptionRePassword := body["repassword"] 103 | password, err := common.RsaDecoder(encryptionPassword) 104 | if err != nil { 105 | zap.S().Info("RSA Decryption Failed") 106 | common.RespFail(ctx.Writer, "Password parsing error!", "Password parsing error!") 107 | return 108 | } 109 | repassword, err := common.RsaDecoder(encryptionRePassword) 110 | if err != nil { 111 | zap.S().Info("RSA Decryption Failed") 112 | common.RespFail(ctx.Writer, "RePassword parsing error!", "RePassword parsing error!") 113 | return 114 | } 115 | if user.Name == "" || password == "" || repassword == "" { 116 | common.RespFail(ctx.Writer, "Username or password cannot be empty!", "Username or password cannot be empty!") 117 | return 118 | } 119 | if user.Email == "" { 120 | common.RespFail(ctx.Writer, "Email cannot be empty!", "Email cannot be empty!") 121 | return 122 | } 123 | //查询用户是否存在 124 | _, err = dao.FindUserByNameWithRegister(user.Name) 125 | if err != nil { 126 | common.RespFail(ctx.Writer, "The user has already registered!", "The user has already registered!") 127 | return 128 | } 129 | //查询邮箱是否已被注册 130 | _, err = dao.FindUserByEmailWithRegister(user.Name) 131 | if err != nil { 132 | common.RespFail(ctx.Writer, "The email has already registered!", "The email has already registered!") 133 | return 134 | } 135 | 136 | if password != repassword { 137 | common.RespFail(ctx.Writer, "The passwords entered do not match!", "The passwords entered do not match!") 138 | return 139 | } 140 | err = GetEmailCode(user.Email, global.Register) 141 | if err != nil { 142 | zap.S().Info("failed to send verification code") 143 | common.RespFail(ctx.Writer, "failed to send verification code!", "failed to send verification code!") 144 | return 145 | } 146 | common.RespOk(ctx.Writer, "Verification code sent successfully.", "Verification code sent successfully.") 147 | } 148 | 149 | func CheckRegisterEmailCode(ctx *gin.Context) { 150 | user := models.UserBasic{} 151 | getData, _ := ctx.GetRawData() 152 | var body map[string]string 153 | _ = json.Unmarshal(getData, &body) 154 | user.Name = body["name"] 155 | user.Email = body["email"] 156 | password := body["password"] 157 | ps, err := common.RsaDecoder(password) 158 | code := body["code"] 159 | err = CheckEmailCode(user.Email, code, global.Register) 160 | if err != nil { 161 | zap.S().Info("incorrect verification code") 162 | common.RespFail(ctx.Writer, "Incorrect verification code!", "Incorrect verification code!") 163 | return 164 | } 165 | salt := fmt.Sprintf("%d", rand.Int31()) 166 | //加密密码 167 | user.PassWord = common.SaltPassWord(ps, salt) 168 | user.Salt = salt 169 | t := time.Now() 170 | user.LoginTime = &t 171 | user.LoginOutTime = &t 172 | user.HeartBeatTime = &t 173 | _, err = dao.CreateUser(user) 174 | if err != nil { 175 | return 176 | } 177 | info, _ := dao.FindUserByName(user.Name) 178 | //Using JWT for authentication. 179 | token, err := middlewear.GenerateToken(info.ID, "cc") 180 | if err != nil { 181 | zap.S().Info("Failed to Generate Token", err) 182 | common.RespFail(ctx.Writer, "Failed to Generate Token", "Failed to Generate Token") 183 | return 184 | } 185 | userInfo := models.UserResponse{ 186 | ID: info.ID, 187 | Name: info.Name, 188 | Email: info.Email, 189 | Phone: info.Phone, 190 | Avatar: info.Avatar, 191 | Motto: info.Motto, 192 | Identity: info.Identity, 193 | ClientIp: info.ClientIp, 194 | ClientPort: info.ClientPort, 195 | } 196 | common.RespOk(ctx.Writer, gin.H{ 197 | "token": token, 198 | "user": userInfo, 199 | }, "New user added successfully!") 200 | } 201 | 202 | func EmailLogin(ctx *gin.Context) { 203 | getData, _ := ctx.GetRawData() 204 | var body map[string]string 205 | _ = json.Unmarshal(getData, &body) 206 | email := body["email"] 207 | _, err := dao.FindUserByEmailWithLogin(email) 208 | if err != nil { 209 | common.RespFail(ctx.Writer, "Couldn't find any information about this email.", "Couldn't find any information about this email.") 210 | return 211 | } 212 | err = GetEmailCode(email, global.LoginEmail) 213 | if err != nil { 214 | zap.S().Info("failed to send verification code") 215 | common.RespFail(ctx.Writer, "failed to send verification code!", "failed to send verification code!") 216 | return 217 | } 218 | common.RespOk(ctx.Writer, "Verification code sent successfully.", "Verification code sent successfully.") 219 | } 220 | 221 | func CheckLoginEmailCode(ctx *gin.Context) { 222 | getData, _ := ctx.GetRawData() 223 | var body map[string]string 224 | _ = json.Unmarshal(getData, &body) 225 | email := body["email"] 226 | code := body["code"] 227 | err := CheckEmailCode(email, code, global.LoginEmail) 228 | if err != nil { 229 | zap.S().Info("incorrect verification code") 230 | common.RespFail(ctx.Writer, "Incorrect verification code!", "Incorrect verification code!") 231 | return 232 | } 233 | //查询用户数据 234 | user, err := dao.FindUserByEmailWithLogin(email) 235 | if err != nil { 236 | common.RespFail(ctx.Writer, "Couldn't find any information about this email.", "Couldn't find any information about this email.") 237 | return 238 | } 239 | t := time.Now() 240 | user.LoginTime = &t 241 | user.LoginOutTime = &t 242 | user.HeartBeatTime = &t 243 | //Using JWT for authentication. 244 | token, err := middlewear.GenerateToken(user.ID, "cc") 245 | if err != nil { 246 | zap.S().Info("Failed to Generate Token", err) 247 | common.RespFail(ctx.Writer, "Failed to Generate Token", "Failed to Generate Token") 248 | return 249 | } 250 | userInfo := models.UserResponse{ 251 | ID: user.ID, 252 | Name: user.Name, 253 | Email: user.Email, 254 | Phone: user.Phone, 255 | Avatar: user.Avatar, 256 | Motto: user.Motto, 257 | Identity: user.Identity, 258 | ClientIp: user.ClientIp, 259 | ClientPort: user.ClientPort, 260 | } 261 | common.RespOk(ctx.Writer, gin.H{ 262 | "token": token, 263 | "user": userInfo, 264 | }, "Login Successful.") 265 | } 266 | 267 | func FindUserWithUserName(ctx *gin.Context) { 268 | getData, _ := ctx.GetRawData() 269 | var body map[string]string 270 | _ = json.Unmarshal(getData, &body) 271 | name := body["name"] 272 | userInfo, err := dao.FindUserByName(name) 273 | if err != nil { 274 | common.RespFail(ctx.Writer, "couldn't find any information about this user", "couldn't find any information about this user") 275 | return 276 | } 277 | reUserInfo := models.UserResponse{ 278 | ID: userInfo.ID, 279 | Name: userInfo.Name, 280 | Email: userInfo.Email, 281 | Phone: userInfo.Phone, 282 | Avatar: userInfo.Avatar, 283 | Motto: userInfo.Motto, 284 | Identity: userInfo.Identity, 285 | ClientIp: userInfo.ClientIp, 286 | ClientPort: userInfo.ClientPort, 287 | } 288 | common.RespOk(ctx.Writer, reUserInfo, "The user has been found.") 289 | } 290 | 291 | func UpdateUser(ctx *gin.Context) { 292 | userId, _ := strconv.Atoi(ctx.GetHeader("UserId")) 293 | getData, _ := ctx.GetRawData() 294 | var body map[string]string 295 | _ = json.Unmarshal(getData, &body) 296 | avatar := body["avatar"] 297 | motto := body["motto"] 298 | name := body["name"] 299 | phone := body["phone"] 300 | email := body["email"] 301 | userInfo, err := dao.FindUserId(uint(userId)) 302 | if userInfo.Avatar != avatar { 303 | userInfo.Avatar = avatar 304 | } 305 | 306 | if userInfo.Motto != motto { 307 | userInfo.Motto = motto 308 | } 309 | 310 | if userInfo.Name != name { 311 | data, _ := dao.FindUserByName(name) 312 | if data != nil { 313 | common.RespFail(ctx.Writer, "The user name already exists!", "The user name already exists!") 314 | return 315 | } 316 | userInfo.Name = name 317 | } 318 | 319 | if userInfo.Phone != phone { 320 | userInfo.Phone = phone 321 | } 322 | 323 | if userInfo.Email != email { 324 | userInfo.Email = email 325 | } 326 | newUserInfo, err := dao.UpdateUser(*userInfo) 327 | if err != nil { 328 | common.RespFail(ctx.Writer, "Fail to modify.", "Fail to modify.") 329 | return 330 | } 331 | 332 | common.RespOk(ctx.Writer, newUserInfo, "The account information is successfully modified!") 333 | } 334 | 335 | func UpdateUserPassword(ctx *gin.Context) { 336 | userId, _ := strconv.Atoi(ctx.GetHeader("UserId")) 337 | getData, _ := ctx.GetRawData() 338 | var body map[string]string 339 | _ = json.Unmarshal(getData, &body) 340 | encryptionPassword := body["password"] 341 | encryptionNewPassword := body["newPassword"] 342 | password, err := common.RsaDecoder(encryptionPassword) 343 | newPassword, err := common.RsaDecoder(encryptionNewPassword) 344 | if err != nil { 345 | zap.S().Info("Cryptographic error") 346 | common.RespFail(ctx.Writer, "Cryptographic error!", "Cryptographic error!") 347 | return 348 | } 349 | userInfo, err := dao.FindUserId(uint(userId)) 350 | ok := common.CheckPassWord(password, userInfo.Salt, userInfo.PassWord) 351 | if !ok { 352 | common.RespFail(ctx.Writer, "The old password is incorrect.", "The old password is incorrect.") 353 | return 354 | } 355 | userInfo.PassWord = common.SaltPassWord(newPassword, userInfo.Salt) 356 | newUserInfo, err := dao.UpdateUser(*userInfo) 357 | if err != nil { 358 | common.RespFail(ctx.Writer, "Fail to modify.", "Fail to modify.") 359 | return 360 | } 361 | 362 | common.RespOk(ctx.Writer, newUserInfo, "The account information is successfully modified!") 363 | } 364 | -------------------------------------------------------------------------------- /service/user_story.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "GoChatCraft/common" 5 | "GoChatCraft/dao" 6 | "GoChatCraft/models" 7 | "encoding/json" 8 | "github.com/gin-gonic/gin" 9 | "strconv" 10 | ) 11 | 12 | func GetStoryList(ctx *gin.Context) { 13 | getData, _ := ctx.GetRawData() 14 | var body map[string]string 15 | _ = json.Unmarshal(getData, &body) 16 | userId, _ := strconv.Atoi(body["userId"]) 17 | page, _ := strconv.Atoi(body["page"]) 18 | pageSize, _ := strconv.Atoi(body["pageSize"]) 19 | 20 | storyList, err := dao.GetStoryList(uint(userId), page, pageSize) 21 | if err != nil { 22 | common.RespFail(ctx.Writer, "couldn't find any information about this user story", "couldn't find any information about this user story") 23 | return 24 | } 25 | common.RespOkList(ctx.Writer, storyList, "The user story has been found.", len(*storyList)) 26 | } 27 | 28 | func GetUserShowStoryList(ctx *gin.Context) { 29 | getData, _ := ctx.GetRawData() 30 | var body map[string]int 31 | _ = json.Unmarshal(getData, &body) 32 | userId := body["userId"] 33 | storyList, count, err := dao.GetUserShowStoryList(uint(userId)) 34 | if err != nil { 35 | common.RespFail(ctx.Writer, "couldn't find any information about this user story", "couldn't find any information about this user story") 36 | return 37 | } 38 | response := models.UserShowStoryListResponse{ 39 | StoryList: storyList, 40 | Count: count, 41 | } 42 | common.RespOk(ctx.Writer, response, "Successfully obtained user Story data.") 43 | } 44 | 45 | func AddStory(ctx *gin.Context) { 46 | getData, _ := ctx.GetRawData() 47 | var body map[string]string 48 | _ = json.Unmarshal(getData, &body) 49 | userId, _ := strconv.Atoi(body["userId"]) 50 | content := body["content"] 51 | media := body["media"] 52 | storyType, _ := strconv.Atoi(body["type"]) 53 | story := &models.UserStory{ 54 | OwnerId: uint(userId), 55 | Content: content, 56 | Media: media, 57 | Type: storyType, 58 | } 59 | storyRes, err := dao.AddStory(story) 60 | if err != nil { 61 | common.RespFail(ctx.Writer, "failed to add the Story!", "failed to add the Story!") 62 | return 63 | } 64 | common.RespOk(ctx.Writer, storyRes, "successfully added the Story.") 65 | } 66 | 67 | func AddStoryLike(ctx *gin.Context) { 68 | getData, _ := ctx.GetRawData() 69 | var body map[string]int 70 | _ = json.Unmarshal(getData, &body) 71 | storyId, _ := body["storyId"] 72 | likeOwnerId, _ := body["ownerId"] 73 | userStoryLike := &models.UserStoryLike{ 74 | UserStoryId: uint(storyId), 75 | LikeOwnerId: uint(likeOwnerId), 76 | } 77 | err := dao.AddOrRemoveStoryLike(userStoryLike) 78 | if err != nil { 79 | common.RespFail(ctx.Writer, "Failed to like the post!", "Failed to like the post!") 80 | return 81 | } 82 | common.RespOk(ctx.Writer, "Successfully!", "Successfully liked the post!") 83 | } 84 | 85 | func AddStoryComment(ctx *gin.Context) { 86 | getData, _ := ctx.GetRawData() 87 | var body map[string]string 88 | _ = json.Unmarshal(getData, &body) 89 | storyId, _ := strconv.Atoi(body["storyId"]) 90 | commentOwnerId, _ := strconv.Atoi(body["ownerId"]) 91 | commentContent := body["content"] 92 | commentType, _ := strconv.Atoi(body["type"]) 93 | userStoryComment := &models.UserStoryComment{ 94 | UserStoryId: uint(storyId), 95 | CommentOwnerId: uint(commentOwnerId), 96 | CommentContent: commentContent, 97 | Type: commentType, 98 | } 99 | comment, err := dao.AddStoryComment(userStoryComment) 100 | if err != nil { 101 | common.RespFail(ctx.Writer, "Failed to comment the post!", "Failed to comment the post!") 102 | return 103 | } 104 | common.RespOk(ctx.Writer, comment, "Successfully comment the post!") 105 | } 106 | -------------------------------------------------------------------------------- /test/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "GoChatCraft/models" 5 | "crypto/tls" 6 | "fmt" 7 | "gorm.io/driver/mysql" 8 | "gorm.io/gorm" 9 | "net/smtp" 10 | "testing" 11 | 12 | "github.com/jordan-wright/email" 13 | ) 14 | 15 | func main() { 16 | //TestSendMail(&testing.T{}) 17 | dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local", "root", 18 | "root@123321", "127.0.0.1", 3306, "chatcraft") 19 | db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) 20 | if err != nil { 21 | panic(err) 22 | } 23 | err = db.AutoMigrate(&models.Community{}) 24 | if err != nil { 25 | panic(err) 26 | } 27 | } 28 | func TestSendMail(t *testing.T) { 29 | e := email.NewEmail() 30 | 31 | mailUserName := "1929509811@qq.com" //邮箱账号 32 | mailPassword := "erxdycnbcbjrbcfg" //邮箱授权码 33 | code := "123456" //发送的验证码 34 | Subject := "验证码发送测试" //发送的主题 35 | 36 | e.From = "Get <1929509811@qq.com>" 37 | e.To = []string{"3265804672@qq.com"} 38 | e.Subject = Subject 39 | e.HTML = []byte("你的验证码为:

" + code + "

") 40 | err := e.SendWithTLS("smtp.qq.com:465", smtp.PlainAuth("", mailUserName, mailPassword, "smtp.qq.com"), 41 | &tls.Config{InsecureSkipVerify: true, ServerName: "smtp.qq.com"}) 42 | if err != nil { 43 | t.Fatal(err) 44 | } 45 | } 46 | 47 | //func TestSendMailQQ(t *testing.T) { 48 | // 49 | // mailUserName := "whm2416@qq.com" //邮箱账号 50 | // mailPassword := define.MailPassword //邮箱授权码 51 | // addr := "smtp.qq.com:465" //TLS地址 52 | // host := "smtp.qq.com" //邮件服务器地址 53 | // code := "12345678" //发送的验证码 54 | // Subject := "验证码发送测试" //发送的主题 55 | // 56 | // e := email.NewEmail() 57 | // e.From = "Get " 58 | // e.To = []string{"228654416@qq.com"} 59 | // e.Subject = Subject 60 | // e.HTML = []byte("你的验证码为:

" + code + "

") 61 | // err := e.SendWithTLS(addr, smtp.PlainAuth("", mailUserName, mailPassword, host), 62 | // &tls.Config{InsecureSkipVerify: true, ServerName: "smtp.qq.com"}) 63 | // if err != nil { 64 | // t.Fatal(err) 65 | // } 66 | //} 67 | --------------------------------------------------------------------------------