├── .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 | 
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 | 
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 | [](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 | 
40 |
41 | ## 🔨部分页面截图
42 |
43 | |  |  |  |
44 | | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
45 | |  |  |  |
46 | |  |  |  |
47 | |  |  |  |
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 | 
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 | 
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 | [](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 | 
39 |
40 | ## 🔨Screenshot of part of the page
41 |
42 | |  |  |  |
43 | | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
44 | |  |  |  |
45 | |  |  |  |
46 | |  |  |  |
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 |
--------------------------------------------------------------------------------