├── .env ├── .gitignore ├── LICENSE ├── README.md ├── api ├── deskApi.go ├── feedbackApi.go ├── goodsApi.go ├── goodsTypeApi.go ├── orderApi.go ├── ossApi.go ├── specificationApi.go ├── storeApi.go ├── sysApi.go └── userApi.go ├── common ├── env.go ├── mail.go ├── mail_test.go ├── mongo.go ├── mongo_test.go ├── redis.go └── redis_test.go ├── dto └── wxDto.go ├── entity ├── desk.go ├── feedback.go ├── goods.go ├── orders.go ├── store.go └── user.go ├── go.mod ├── main.go ├── main_test.go ├── middleware └── jwtAuth.go ├── routes ├── routers.go └── routers_test.go └── service ├── auth.go └── token.go /.env: -------------------------------------------------------------------------------- 1 | PORT="9900" 2 | 3 | #阿里云存储对象,申请连接https://oss.console.aliyun.com/ 4 | OSS_ENDPOINT="https://oss.aliyuncs.com" 5 | OSS_ACCESS_KEY_ID="XXXXXXXXXXXXXXXXXXXXX" 6 | OSS_ACCESS_SECRET="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" 7 | OSS_BUCKET="fish" 8 | 9 | #mongodb 连接 10 | MONGO_HOST="192.168.0.102" 11 | MONGO_PORT="27017" 12 | MONGO_DATABASE="fish" 13 | 14 | #Redis 连接 15 | REDIS_ADDR="192.168.0.102:6379" 16 | #REDIS_PASSWORD="fish" 17 | 18 | #SSL="https" 19 | 20 | #JWT 生成token的秘钥 21 | JwtSecretKey="12s36S1a243126242562sd5fy4" 22 | 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Golang # 2 | ###################### 3 | # `go test -c` 生成的二进制文件 4 | *.test 5 | # go coverage 工具 6 | *.out 7 | *.prof 8 | *.cgo1.go 9 | *.cgo2.c 10 | _cgo_defun.c 11 | _cgo_gotypes.go 12 | _cgo_export.* 13 | 14 | # 编译文件 # 15 | ################### 16 | *.com 17 | *.class 18 | *.dll 19 | *.exe 20 | *.o 21 | *.so 22 | fish-service 23 | 24 | # 压缩包 # 25 | ############ 26 | # Git 自带压缩,如果这些压缩包里有代码,建议解压后 commit 27 | *.7z 28 | *.dmg 29 | *.gz 30 | *.iso 31 | *.jar 32 | *.rar 33 | *.tar 34 | *.zip 35 | 36 | # 日志文件和数据库 # 37 | ###################### 38 | *.log 39 | *.sqlite 40 | *.db 41 | 42 | # 临时文件 # 43 | ###################### 44 | tmp/ 45 | .tmp/ 46 | go.sum 47 | 48 | # 系统生成文件 # 49 | ###################### 50 | .DS_Store 51 | .DS_Store? 52 | .AppleDouble 53 | .LSOverride 54 | ._* 55 | .Spotlight-V100 56 | .Trashes 57 | ehthumbs.db 58 | Thumbs.db 59 | .TemporaryItems 60 | .fseventsd 61 | .VolumeIcon.icns 62 | .com.apple.timemachine.donotpresent 63 | 64 | # IDE 和编辑器 # 65 | ###################### 66 | .idea/ 67 | /go_build_* 68 | out/ 69 | .vscode/ 70 | .vscode/settings.json 71 | *.sublime* 72 | __debug_bin 73 | .project 74 | 75 | 76 | -------------------------------------------------------------------------------- /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.md: -------------------------------------------------------------------------------- 1 | 本项目基于go+gin实现 2 | 3 | 文件服务:阿里oss 4 | 数据库服务:MongoDB 5 | 6 | 下载项目依赖: 7 | go get ./... 8 | 9 | 启动: 10 | go run . 11 | 12 | 13 | 目录说明 14 | -api 接口实现 15 | -common 公共类 16 | -routes 路由 17 | -dto 传输实体对象 18 | -entity 存储对象实体 19 | -middleware gin中间件 20 | 21 | 22 | > 对应前端库 [fish-ui](https://github.com/imtmn/fish-ui) 23 | -------------------------------------------------------------------------------- /api/deskApi.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "context" 5 | "encoding/base64" 6 | "log" 7 | "net/http" 8 | "strconv" 9 | "time" 10 | 11 | "github.com/gin-gonic/gin" 12 | "github.com/skip2/go-qrcode" 13 | "go.mongodb.org/mongo-driver/bson" 14 | "go.mongodb.org/mongo-driver/bson/primitive" 15 | "mtmn.top/fish-service/common" 16 | "mtmn.top/fish-service/entity" 17 | ) 18 | 19 | func SaveDesk(c *gin.Context) { 20 | desk := entity.Desk{} 21 | err := c.ShouldBindJSON(&desk) 22 | if err != nil { 23 | c.JSON(http.StatusBadRequest, gin.H{"msg": err.Error()}) 24 | return 25 | } 26 | storeId := c.MustGet("storeId").(string) 27 | userId := c.MustGet("userId").(string) 28 | if storeId == "" { 29 | c.JSON(http.StatusOK, gin.H{"code": http.StatusBadRequest, "msg": "请先申请成为商家"}) 30 | return 31 | } 32 | if desk.Id == "" { 33 | desk.CreateTime = time.Now() 34 | desk.UpdateTime = time.Now() 35 | desk.StoreId = storeId 36 | desk.CreatorId = userId 37 | } 38 | if desk.Id != "" { 39 | update := bson.D{ 40 | {Key: "$set", Value: bson.D{ 41 | {Key: "name", Value: desk.Name}, 42 | {Key: "updateTime", Value: time.Now()}, 43 | }}, 44 | } 45 | result := common.DB().UpdateByID("desk", desk.Id, update) 46 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "保存成功", "id": result.UpsertedID}) 47 | } else { 48 | result := common.DB().InsertOne("desk", desk) 49 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "保存成功", "id": result.InsertedID}) 50 | } 51 | } 52 | 53 | func GetDesk(c *gin.Context) { 54 | desk := entity.Desk{} 55 | err := common.DB().FindID("desk", c.Param("id")).Decode(&desk) 56 | if err != nil { 57 | c.JSON(http.StatusBadRequest, gin.H{"code": 400, "msg": "未找到对桌号"}) 58 | return 59 | } 60 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "获取成功", "data": desk}) 61 | } 62 | 63 | func FindDeskPage(c *gin.Context) { 64 | log.Println("page=", c.Query("page"), ",size=", c.Query("size")) 65 | page, _ := strconv.ParseInt(c.Param("page"), 10, 64) 66 | size, _ := strconv.ParseInt(c.Param("size"), 10, 64) 67 | skip := (page - 1) * size 68 | storeId := c.MustGet("storeId").(string) 69 | filter := bson.M{"storeId": storeId} 70 | cursor := common.DB().CollectionDocumentsFilter("desk", skip, size, filter, bson.D{primitive.E{Key: "createTime", Value: 1}}) 71 | total := common.DB().CollectionCount("desk") 72 | desk := []entity.Desk{} 73 | cursor.All(context.TODO(), &desk) 74 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "获取数据成功", "data": desk, "total": total}) 75 | } 76 | 77 | func DeleteDesk(c *gin.Context) { 78 | id := c.Param("id") 79 | count := common.DB().DeleteById("desk", id) 80 | if count <= 0 { 81 | c.JSON(http.StatusOK, gin.H{"code": 400, "msg": "删除数据失败", "count": 0}) 82 | return 83 | } 84 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "删除数据成功", "count": count}) 85 | } 86 | 87 | //获取二维码 88 | func GetQrcode(c *gin.Context) { 89 | desk := entity.Desk{} 90 | err := common.DB().FindID("desk", c.Param("id")).Decode(&desk) 91 | if err != nil { 92 | c.JSON(http.StatusBadRequest, gin.H{"code": 400, "msg": "未找到对桌号"}) 93 | return 94 | } 95 | png, err := qrcode.Encode("https://mtmn.top/ucode/"+desk.Id, qrcode.Medium, 256) 96 | if err != nil { 97 | c.JSON(http.StatusBadRequest, gin.H{"code": 400, "msg": "生成二维码失败"}) 98 | return 99 | } 100 | sEnc := base64.StdEncoding.EncodeToString([]byte(png)) 101 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "生成成功", "data": sEnc}) 102 | 103 | } 104 | -------------------------------------------------------------------------------- /api/feedbackApi.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "net/http" 5 | "time" 6 | 7 | "github.com/gin-gonic/gin" 8 | "mtmn.top/fish-service/common" 9 | "mtmn.top/fish-service/entity" 10 | ) 11 | 12 | func SaveFeedBack(c *gin.Context) { 13 | feedback := entity.Feedback{} 14 | err := c.ShouldBindJSON(&feedback) 15 | if err != nil { 16 | c.JSON(http.StatusBadRequest, gin.H{"msg": err.Error()}) 17 | return 18 | } 19 | userId := c.MustGet("userId").(string) 20 | if feedback.Id == "" { 21 | feedback.CreateTime = time.Now() 22 | feedback.UpdateTime = time.Now() 23 | feedback.CreatorId = userId 24 | } 25 | result := common.DB().InsertOne("feedback", feedback) 26 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "保存成功", "id": result.InsertedID}) 27 | } 28 | -------------------------------------------------------------------------------- /api/goodsApi.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "net/http" 7 | "sort" 8 | "strconv" 9 | "time" 10 | 11 | "github.com/gin-gonic/gin" 12 | "go.mongodb.org/mongo-driver/bson" 13 | "go.mongodb.org/mongo-driver/bson/primitive" 14 | "go.mongodb.org/mongo-driver/mongo" 15 | "mtmn.top/fish-service/common" 16 | "mtmn.top/fish-service/entity" 17 | ) 18 | 19 | type ( 20 | // 商品分组 21 | GoodsGroup struct { 22 | Id string `json:"id"` 23 | Name string `json:"name"` 24 | Order float64 `bson:"order" json:"order"` 25 | GoodsList []entity.Goods `json:"goods"` 26 | } 27 | ) 28 | 29 | func SaveGoods(c *gin.Context) { 30 | goods := entity.Goods{} 31 | err := c.ShouldBindJSON(&goods) 32 | if err != nil { 33 | c.JSON(http.StatusBadRequest, gin.H{"msg": err.Error()}) 34 | return 35 | } 36 | storeId := c.MustGet("storeId").(string) 37 | if storeId == "" { 38 | c.JSON(http.StatusBadRequest, gin.H{"msg": "暂无店铺权限"}) 39 | return 40 | } 41 | if goods.Id == "" { 42 | goods.StoreId = storeId 43 | // 默认:上架状态 44 | goods.Status = "on" 45 | goods.CreateTime = time.Now() 46 | } 47 | if goods.Id != "" { 48 | update := bson.D{ 49 | {Key: "$set", Value: bson.D{ 50 | {Key: "name", Value: goods.Name}, 51 | {Key: "remark", Value: goods.Remark}, 52 | {Key: "price", Value: goods.Price}, 53 | {Key: "goodsType", Value: goods.GoodsType}, 54 | {Key: "specification", Value: goods.Specification}, 55 | {Key: "imgs", Value: goods.Imgs}, 56 | {Key: "mainImags", Value: goods.MainImags}, 57 | {Key: "updateTime", Value: time.Now()}}}, 58 | } 59 | result := common.DB().UpdateByID("goods", goods.Id, update) 60 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "保存成功", "id": result.UpsertedID}) 61 | } else { 62 | result := common.DB().InsertOne("goods", goods) 63 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "保存成功", "id": result.InsertedID}) 64 | } 65 | 66 | } 67 | 68 | func GetGoods(c *gin.Context) { 69 | goods := entity.Goods{} 70 | err := common.DB().FindID("goods", c.Param("id")).Decode(&goods) 71 | if err != nil { 72 | log.Fatal(err) 73 | c.JSON(http.StatusBadRequest, gin.H{"code": 400, "msg": "未找到对应的商品"}) 74 | return 75 | } 76 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "获取成功", "data": goods}) 77 | } 78 | 79 | func CountGoods(c *gin.Context) { 80 | storeId := c.MustGet("storeId").(string) 81 | if storeId == "" { 82 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "无店铺信息", "data": 0}) 83 | } 84 | count := common.DB().CountByFilter("goods", bson.M{"storeId": storeId}) 85 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "获取成功", "data": count}) 86 | } 87 | 88 | func FindGoodsGroupByType(c *gin.Context) { 89 | // 默认最多查1000条数据 90 | size, _ := strconv.ParseInt(c.DefaultQuery("size", "1000"), 10, 64) 91 | status := c.Query("status") 92 | storeId := c.Query("storeId") 93 | if storeId == "" { 94 | storeId = c.MustGet("storeId").(string) 95 | } 96 | filter := bson.M{ 97 | "storeId": storeId, 98 | } 99 | if status != "" { 100 | filter["status"] = status 101 | } 102 | cursor := common.DB().CollectionDocumentsFilter("goods", 0, size, filter, bson.D{primitive.E{Key: "_id", Value: -1}}) 103 | total := common.DB().CollectionCount("goods") 104 | goodsList := []entity.Goods{} 105 | cursor.All(context.TODO(), &goodsList) 106 | 107 | goodsGroup := []GoodsGroup{} 108 | 109 | for _, goods := range goodsList { 110 | goodsTypeList := goods.GoodsType 111 | if len(goodsTypeList) > 0 { 112 | // 有商品类型数据根据商品类型进行分组 113 | for _, goodsType := range goodsTypeList { 114 | addGoodsToGroupByType(&goodsGroup, goodsType, goods) 115 | } 116 | } else { 117 | // 没有分组的数据 放到其他类型中 118 | goodsType := entity.GoodsType{Name: "其他", Id: "-1"} 119 | addGoodsToGroupByType(&goodsGroup, goodsType, goods) 120 | } 121 | } 122 | 123 | typeCursor := common.DB().CollectionDocuments("goodsType", 0, size, bson.D{primitive.E{Key: "order", Value: 1}}) 124 | goodstypeArr := []entity.GoodsType{} 125 | typeCursor.All(context.TODO(), &goodstypeArr) 126 | 127 | for i, _ := range goodsGroup { 128 | for _, gt := range goodstypeArr { 129 | if gt.Id == (&goodsGroup[i]).Id { 130 | (&goodsGroup[i]).Order = gt.Order 131 | break 132 | } 133 | } 134 | if (&goodsGroup[i]).Order == 0 { 135 | (&goodsGroup[i]).Order = 999999999999999 136 | } 137 | } 138 | 139 | sort.SliceStable(goodsGroup, func(i, j int) bool { 140 | return goodsGroup[i].Order < goodsGroup[j].Order 141 | }) 142 | 143 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "获取数据成功", "data": goodsGroup, "total": total}) 144 | } 145 | 146 | // 根据商品类型进行分组 147 | func addGoodsToGroupByType(goodsGroup *[]GoodsGroup, goodsType entity.GoodsType, goods entity.Goods) { 148 | isExist := false 149 | var group *GoodsGroup 150 | // 遍历分组 151 | for i := 0; i < len(*goodsGroup); i++ { 152 | if (*goodsGroup)[i].Name == goodsType.Name { 153 | (*goodsGroup)[i].GoodsList = append((*goodsGroup)[i].GoodsList, goods) 154 | isExist = true 155 | break 156 | } 157 | } 158 | if !isExist { 159 | // 如果分组不存在 则新增一个分组 160 | group = &GoodsGroup{} 161 | group.Id = goodsType.Id 162 | group.Name = goodsType.Name 163 | //为分组添加元素 164 | group.GoodsList = append(group.GoodsList, goods) 165 | *goodsGroup = append(*goodsGroup, *group) 166 | } 167 | } 168 | 169 | func FindGoodsPage(c *gin.Context) { 170 | log.Println("page=", c.Query("page"), ",size=", c.Query("size")) 171 | page, _ := strconv.ParseInt(c.Param("page"), 10, 64) 172 | size, _ := strconv.ParseInt(c.Param("size"), 10, 64) 173 | skip := (page - 1) * size 174 | cursor := common.DB().CollectionDocuments("goods", skip, size, bson.D{primitive.E{Key: "_id", Value: -1}}) 175 | total := common.DB().CollectionCount("goods") 176 | goods := []entity.Goods{} 177 | cursor.All(context.TODO(), &goods) 178 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "获取数据成功", "data": goods, "total": total}) 179 | } 180 | 181 | func DeleteGoods(c *gin.Context) { 182 | storeId := c.MustGet("storeId").(string) 183 | if storeId == "" { 184 | c.JSON(http.StatusBadRequest, gin.H{"msg": "暂无店铺权限"}) 185 | return 186 | } 187 | goods := entity.Goods{} 188 | err := common.DB().FindID("goods", c.Param("id")).Decode(&goods) 189 | if err != nil { 190 | log.Fatal(err) 191 | c.JSON(http.StatusBadRequest, gin.H{"code": 400, "msg": "未找到对应的商品"}) 192 | return 193 | } 194 | if goods.StoreId != storeId { 195 | c.JSON(http.StatusBadRequest, gin.H{"msg": "无数据删除权限"}) 196 | return 197 | } 198 | id := c.Param("id") 199 | count := common.DB().DeleteById("goods", id) 200 | if count <= 0 { 201 | c.JSON(http.StatusOK, gin.H{"code": 400, "msg": "删除数据失败", "count": 0}) 202 | return 203 | } 204 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "删除数据成功", "count": count}) 205 | } 206 | 207 | func ChangeStatusOn(c *gin.Context) { 208 | storeId := c.MustGet("storeId").(string) 209 | if storeId == "" { 210 | c.JSON(http.StatusBadRequest, gin.H{"msg": "暂无店铺权限"}) 211 | return 212 | } 213 | id := c.Param("id") 214 | result := changeGoodsStatus(id, "on", storeId) 215 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "上架成功", "id": result.UpsertedID}) 216 | } 217 | 218 | func ChangeStatusOff(c *gin.Context) { 219 | storeId := c.MustGet("storeId").(string) 220 | if storeId == "" { 221 | c.JSON(http.StatusBadRequest, gin.H{"msg": "暂无店铺权限"}) 222 | return 223 | } 224 | id := c.Param("id") 225 | result := changeGoodsStatus(id, "off", storeId) 226 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "下架成功", "id": result.UpsertedID}) 227 | } 228 | 229 | func changeGoodsStatus(id string, status string, storeId string) *mongo.UpdateResult { 230 | update := bson.D{ 231 | {Key: "$set", Value: bson.D{{Key: "status", Value: status}, {Key: "updateTime", Value: time.Now()}}}, 232 | } 233 | oid, _ := primitive.ObjectIDFromHex(id) 234 | return common.DB().UpdateOne("goods", bson.M{ 235 | "_id": oid, 236 | "storeId": storeId, 237 | }, update) 238 | } 239 | -------------------------------------------------------------------------------- /api/goodsTypeApi.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "net/http" 7 | "strconv" 8 | "time" 9 | 10 | "github.com/gin-gonic/gin" 11 | "go.mongodb.org/mongo-driver/bson" 12 | "go.mongodb.org/mongo-driver/bson/primitive" 13 | "mtmn.top/fish-service/common" 14 | "mtmn.top/fish-service/entity" 15 | ) 16 | 17 | func SaveGoodsType(c *gin.Context) { 18 | goodsType := entity.GoodsType{} 19 | err := c.ShouldBindJSON(&goodsType) 20 | if err != nil { 21 | c.JSON(http.StatusBadRequest, gin.H{"msg": err.Error()}) 22 | return 23 | } 24 | storeId := c.MustGet("storeId").(string) 25 | 26 | if storeId == "" { 27 | c.JSON(http.StatusBadRequest, gin.H{"msg": "暂无店铺权限"}) 28 | return 29 | } 30 | 31 | if goodsType.Id == "" { 32 | goodsType.StoreId = storeId 33 | goodsType.CreateTime = time.Now() 34 | goodsType.Order = float64(time.Now().Unix()) 35 | } 36 | if goodsType.Id != "" { 37 | update := bson.D{ 38 | {Key: "$set", Value: bson.D{ 39 | {Key: "name", Value: goodsType.Name}, 40 | {Key: "order", Value: goodsType.Order}, 41 | }}, 42 | } 43 | result := common.DB().UpdateByID("goodsType", goodsType.Id, update) 44 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "保存成功", "id": result.UpsertedID}) 45 | } else { 46 | result := common.DB().InsertOne("goodsType", goodsType) 47 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "保存成功", "id": result.InsertedID}) 48 | } 49 | } 50 | 51 | func GetGoodsType(c *gin.Context) { 52 | goodsType := entity.GoodsType{} 53 | err := common.DB().FindID("goodsType", c.Param("id")).Decode(&goodsType) 54 | if err != nil { 55 | log.Fatal(err) 56 | c.JSON(http.StatusBadRequest, gin.H{"code": 400, "msg": "未找到对应商品类型"}) 57 | } 58 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "获取成功", "data": goodsType}) 59 | } 60 | 61 | func FindGoodsTypePage(c *gin.Context) { 62 | size, _ := strconv.ParseInt(c.DefaultQuery("size", "100"), 10, 64) 63 | storeId := c.MustGet("storeId").(string) 64 | if storeId == "" { 65 | c.JSON(http.StatusBadRequest, gin.H{"msg": "暂无店铺权限"}) 66 | return 67 | } 68 | filter := bson.M{ 69 | "storeId": storeId, 70 | } 71 | cursor := common.DB().CollectionDocumentsFilter("goodsType", 0, size, filter, bson.D{primitive.E{Key: "order", Value: 1}}) 72 | total := common.DB().CollectionCount("goodsType") 73 | goodstype := []entity.GoodsType{} 74 | cursor.All(context.TODO(), &goodstype) 75 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "获取数据成功", "data": goodstype, "total": total}) 76 | } 77 | 78 | func DeleteGoodsType(c *gin.Context) { 79 | storeId := c.MustGet("storeId").(string) 80 | if storeId == "" { 81 | c.JSON(http.StatusBadRequest, gin.H{"msg": "暂无店铺权限"}) 82 | return 83 | } 84 | id := c.Param("id") 85 | goodsType := entity.GoodsType{} 86 | err := common.DB().FindID("goodsType", id).Decode(&goodsType) 87 | if err != nil { 88 | log.Println(err) 89 | c.JSON(http.StatusBadRequest, gin.H{"code": 400, "msg": "未找到对应商品类型"}) 90 | } 91 | if goodsType.StoreId != storeId { 92 | c.JSON(http.StatusBadRequest, gin.H{"msg": "无数据删除权限"}) 93 | return 94 | } 95 | count := common.DB().DeleteById("goodsType", id) 96 | if count <= 0 { 97 | c.JSON(http.StatusOK, gin.H{"code": 400, "msg": "删除数据失败", "count": 0}) 98 | return 99 | } 100 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "删除数据成功", "count": count}) 101 | } 102 | -------------------------------------------------------------------------------- /api/orderApi.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "net/http" 8 | "strconv" 9 | "time" 10 | 11 | "github.com/gin-gonic/gin" 12 | "go.mongodb.org/mongo-driver/bson" 13 | "go.mongodb.org/mongo-driver/bson/primitive" 14 | "go.mongodb.org/mongo-driver/mongo" 15 | "mtmn.top/fish-service/common" 16 | "mtmn.top/fish-service/entity" 17 | ) 18 | 19 | // 保存订单 20 | func SaveOrder(c *gin.Context) { 21 | order := entity.Order{} 22 | err := c.ShouldBindJSON(&order) 23 | if err != nil { 24 | c.JSON(http.StatusBadRequest, gin.H{"msg": err.Error()}) 25 | return 26 | } 27 | goodsList := order.OrderGoods 28 | if len(goodsList) <= 0 { 29 | c.JSON(http.StatusBadRequest, gin.H{"msg": "订单不能为空"}) 30 | return 31 | } 32 | 33 | // 核对订单商品价格 34 | var totalPrice float64 35 | for _, orderGoods := range goodsList { 36 | goods := entity.Goods{} 37 | log.Println("开始获取商品") 38 | err := common.DB().FindID("goods", orderGoods.Id).Decode(&goods) 39 | if err != nil { 40 | c.JSON(http.StatusBadRequest, gin.H{"msg": "获取商品信息失败,请重试"}) 41 | return 42 | } 43 | if orderGoods.Price != goods.Price { 44 | c.JSON(http.StatusBadRequest, gin.H{"msg": "订单商品价格有误"}) 45 | return 46 | } 47 | totalPrice += orderGoods.Price * float64(orderGoods.Number) 48 | } 49 | 50 | // 确认商品订单总价 51 | if order.Price != totalPrice { 52 | c.JSON(http.StatusBadRequest, gin.H{"msg": "订单商品价格有误"}) 53 | return 54 | } 55 | if order.Id == "" { 56 | order.Status = "normal" 57 | order.CreatorId = c.MustGet("userId").(string) 58 | order.CreateTime = time.Now() 59 | order.UpdateTime = time.Now() 60 | } 61 | if order.Id != "" { 62 | update := bson.D{ 63 | {Key: "$set", Value: bson.D{{Key: "remark", Value: order.Remark}}}, 64 | } 65 | result := common.DB().UpdateByID("order", order.Id, update) 66 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "保存成功", "id": result.UpsertedID}) 67 | } else { 68 | result := common.DB().InsertOne("order", order) 69 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "保存成功", "id": result.InsertedID}) 70 | } 71 | } 72 | 73 | func GetOrder(c *gin.Context) { 74 | order := entity.Order{} 75 | log.Println("开始获取商品") 76 | storeId := c.MustGet("storeId").(string) 77 | userId := c.MustGet("userId").(string) 78 | err := common.DB().FindID("order", c.Param("id")).Decode(&order) 79 | if err != nil { 80 | log.Fatal(err) 81 | c.JSON(http.StatusBadRequest, gin.H{"code": 400, "msg": "未找到对于商品类型"}) 82 | } 83 | if order.StoreId != storeId && order.CreatorId != userId { 84 | c.JSON(http.StatusBadRequest, gin.H{"code": 400, "msg": "没有权限查看该订单"}) 85 | return 86 | } 87 | log.Println(order.Id) 88 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "获取成功", "data": order}) 89 | } 90 | 91 | func FindOrderPage(c *gin.Context) { 92 | size, _ := strconv.ParseInt(c.DefaultQuery("size", "100"), 10, 64) 93 | storeId := c.MustGet("storeId").(string) 94 | userId := c.MustGet("userId").(string) 95 | filter := bson.M{"$or": []bson.M{{"storeId": storeId}, {"creatorId": userId}}} 96 | if c.Query("status") != "" { 97 | filter["status"] = c.Query("status") 98 | } 99 | sort := bson.D{primitive.E{Key: "createTime", Value: -1}} 100 | cursor := common.DB().CollectionDocumentsFilter("order", 0, size, filter, sort) 101 | total := common.DB().CollectionCount("order") 102 | order := []entity.Order{} 103 | cursor.All(context.TODO(), &order) 104 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "获取数据成功", "data": order, "total": total}) 105 | } 106 | 107 | func CancelOrder(c *gin.Context) { 108 | storeId := c.MustGet("storeId").(string) 109 | coll := common.DB().Coll("order") 110 | if oid, err := primitive.ObjectIDFromHex(c.Param("id")); err == nil { 111 | result, err := coll.UpdateOne( 112 | context.TODO(), 113 | bson.M{"_id": oid, "status": "normal", "storeId": storeId}, 114 | bson.D{ 115 | {Key: "$set", Value: bson.D{{Key: "status", Value: "cancel"}}}, 116 | }, 117 | ) 118 | if err == nil { 119 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "取消订单成功", "data": result.UpsertedID}) 120 | return 121 | } 122 | } 123 | c.JSON(http.StatusOK, gin.H{"code": -1, "msg": "取消订单失败,请刷新后重试"}) 124 | } 125 | 126 | func RecoverOrder(c *gin.Context) { 127 | storeId := c.MustGet("storeId").(string) 128 | coll := common.DB().Coll("order") 129 | if oid, err := primitive.ObjectIDFromHex(c.Param("id")); err == nil { 130 | result, err := coll.UpdateOne( 131 | context.TODO(), 132 | bson.M{"_id": oid, "status": "cancel", "storeId": storeId}, 133 | bson.D{ 134 | {Key: "$set", Value: bson.D{{Key: "status", Value: "normal"}}}, 135 | }, 136 | ) 137 | if err == nil { 138 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "恢复订单成功", "data": result.UpsertedID}) 139 | return 140 | } 141 | } 142 | c.JSON(http.StatusOK, gin.H{"code": -1, "msg": "恢复订单失败,请刷新后重试"}) 143 | } 144 | 145 | func PayOrder(c *gin.Context) { 146 | storeId := c.MustGet("storeId").(string) 147 | coll := common.DB().Coll("order") 148 | payPrice, err := strconv.ParseFloat(c.Query("payPrice"), 64) 149 | if err != nil { 150 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "结算金额有误,请确认"}) 151 | return 152 | } 153 | log.Println("支付金额", payPrice) 154 | log.Println("订单号", c.Query("id")) 155 | if oid, err := primitive.ObjectIDFromHex(c.Query("id")); err == nil { 156 | result, err := coll.UpdateOne( 157 | context.TODO(), 158 | bson.M{"_id": oid, "status": "normal", "storeId": storeId}, 159 | bson.D{ 160 | {Key: "$set", Value: bson.D{ 161 | {Key: "status", Value: "pay"}, 162 | {Key: "payPrice", Value: payPrice}}}, 163 | }, 164 | ) 165 | if err == nil { 166 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "结算成功", "data": result.UpsertedID}) 167 | return 168 | } 169 | } 170 | c.JSON(http.StatusOK, gin.H{"code": -1, "msg": "结算失败,请刷新后重试"}) 171 | } 172 | 173 | func CountTodayOrder(c *gin.Context) { 174 | storeId := c.MustGet("storeId").(string) 175 | if storeId == "" { 176 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "无店铺信息", "data": 0}) 177 | } 178 | startTime := time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day(), 0, 0, 0, 0, time.Local) 179 | count := common.DB().CountByFilter("order", bson.M{ 180 | "storeId": storeId, 181 | "createTime": bson.M{"$gt": startTime}, 182 | }) 183 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "获取成功", "data": count}) 184 | } 185 | 186 | func IncomeOrder(c *gin.Context) { 187 | storeId := c.MustGet("storeId").(string) 188 | if storeId == "" { 189 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "无店铺信息", "data": 0}) 190 | } 191 | startTime := time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day(), 0, 0, 0, 0, time.Local) 192 | matchStage := bson.D{ 193 | {Key: "$match", Value: bson.D{ 194 | {Key: "storeId", 195 | Value: bson.D{ 196 | {Key: "$eq", Value: storeId}, 197 | }, 198 | }, 199 | {Key: "createTime", 200 | Value: bson.D{ 201 | {Key: "$gt", Value: startTime}, 202 | }, 203 | }, 204 | }}, 205 | } 206 | groupStage := bson.D{ 207 | {Key: "$group", Value: bson.D{ 208 | {Key: "_id", Value: bson.D{ 209 | {Key: "storeId", Value: "$storeId"}, 210 | }}, 211 | {Key: "sum", Value: bson.D{ 212 | {Key: "$sum", Value: "$payPrice"}, 213 | }}, 214 | }}, 215 | } 216 | cursor, err := common.DB().Coll("order").Aggregate(context.Background(), mongo.Pipeline{matchStage, groupStage}) 217 | if err != nil { 218 | panic(err) 219 | } 220 | var results []bson.M 221 | if err = cursor.All(context.TODO(), &results); err != nil { 222 | panic(err) 223 | } 224 | if len(results) == 0 { 225 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "获取成功", "data": 0}) 226 | } 227 | for _, result := range results { 228 | fmt.Printf("Average price of %v \n", result["payPrice"]) 229 | } 230 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "获取成功", "data": results[0]["sum"]}) 231 | } 232 | -------------------------------------------------------------------------------- /api/ossApi.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/http" 7 | "os" 8 | "path/filepath" 9 | "time" 10 | 11 | "github.com/aliyun/aliyun-oss-go-sdk/oss" 12 | "github.com/gin-gonic/gin" 13 | ) 14 | 15 | func OSSUploadFile(c *gin.Context) { 16 | fileHeader, err := c.FormFile("file") 17 | if err != nil { 18 | c.JSON(http.StatusBadGateway, gin.H{"code": 502, "msg": "参数有误", "err": err.Error()}) 19 | return 20 | } 21 | fileExt := filepath.Ext(fileHeader.Filename) 22 | allowExts := []string{".jpg", ".png", ".gif", ".jpeg", ".doc", ".docx", ".ppt", ".pptx", ".xls", ".xlsx", ".pdf"} 23 | allowFlag := false 24 | for _, ext := range allowExts { 25 | if ext == fileExt { 26 | allowFlag = true 27 | break 28 | } 29 | } 30 | if !allowFlag { 31 | c.JSON(http.StatusBadGateway, gin.H{"code": 502, "msg": "不允许的类型", "err": err.Error()}) 32 | return 33 | } 34 | 35 | now := time.Now() 36 | //文件存放路径 37 | fileDir := fmt.Sprintf("upload/%s", now.Format("200601")) 38 | 39 | //文件名称 40 | timeStamp := now.Unix() 41 | fileName := fmt.Sprintf("%d-%s", timeStamp, fileHeader.Filename) 42 | // 文件key 43 | fileKey := filepath.Join(fileDir, fileName) 44 | 45 | src, err := fileHeader.Open() 46 | if err != nil { 47 | c.JSON(http.StatusBadGateway, gin.H{"code": 502, "msg": "上传失败,open oss error", "err": err.Error()}) 48 | return 49 | } 50 | defer src.Close() 51 | 52 | res, err := OssUpload(fileKey, src) 53 | if err != nil { 54 | c.JSON(http.StatusBadGateway, gin.H{"code": 502, "msg": "OssUpload上传失败", "err": err.Error()}) 55 | return 56 | } 57 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "文件上传成功", "path": res}) 58 | } 59 | 60 | func OssUpload(key string, file io.Reader) (string, error) { 61 | client, err := oss.New(os.Getenv("OSS_ENDPOINT"), os.Getenv("OSS_ACCESS_KEY_ID"), os.Getenv("OSS_ACCESS_SECRET")) 62 | if err != nil { 63 | return "client 创建失败", err 64 | } 65 | // 获取存储空间。 66 | bucket, err := client.Bucket(os.Getenv("OSS_BUCKET")) 67 | if err != nil { 68 | return "bucket 创建失败", err 69 | } 70 | // 上传文件。 71 | err = bucket.PutObject(key, file) 72 | if err != nil { 73 | return "文件上传失败", err 74 | } 75 | return "/" + key, nil 76 | } 77 | -------------------------------------------------------------------------------- /api/specificationApi.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "net/http" 7 | "strconv" 8 | "time" 9 | 10 | "github.com/gin-gonic/gin" 11 | "go.mongodb.org/mongo-driver/bson" 12 | "go.mongodb.org/mongo-driver/bson/primitive" 13 | "mtmn.top/fish-service/common" 14 | "mtmn.top/fish-service/entity" 15 | ) 16 | 17 | func SaveSpecification(c *gin.Context) { 18 | specification := entity.Specification{} 19 | err := c.ShouldBindJSON(&specification) 20 | if err != nil { 21 | c.JSON(http.StatusBadRequest, gin.H{"msg": err.Error()}) 22 | return 23 | } 24 | if specification.Id == "" { 25 | specification.CreateTime = time.Now() 26 | } 27 | log.Println(specification) 28 | if specification.Id != "" { 29 | update := bson.D{ 30 | {Key: "$set", Value: bson.D{{Key: "name", Value: specification.Name}, {Key: "groupName", Value: specification.GroupName}}}, 31 | } 32 | result := common.DB().UpdateByID("specification", specification.Id, update) 33 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "保存成功", "id": result.UpsertedID}) 34 | } else { 35 | result := common.DB().InsertOne("specification", specification) 36 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "保存成功", "id": result.InsertedID}) 37 | } 38 | } 39 | 40 | func GetSpecification(c *gin.Context) { 41 | specification := entity.Specification{} 42 | log.Println("开始获取商品") 43 | err := common.DB().FindID("specification", c.Param("id")).Decode(&specification) 44 | if err != nil { 45 | log.Fatal(err) 46 | c.JSON(http.StatusBadRequest, gin.H{"code": 400, "msg": "未找到对应商品类型"}) 47 | } 48 | log.Println(specification.Id) 49 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "获取成功", "data": specification}) 50 | } 51 | 52 | func FindSpecificationPage(c *gin.Context) { 53 | log.Println("page=", c.Query("page"), ",size=", c.Query("size")) 54 | page, _ := strconv.ParseInt(c.Param("page"), 10, 64) 55 | size, _ := strconv.ParseInt(c.Param("size"), 10, 64) 56 | skip := (page - 1) * size 57 | cursor := common.DB().CollectionDocuments("specification", skip, size, bson.D{primitive.E{Key: "order", Value: 1}}) 58 | total := common.DB().CollectionCount("specification") 59 | specification := []entity.Specification{} 60 | cursor.All(context.TODO(), &specification) 61 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "获取数据成功", "data": specification, "total": total}) 62 | } 63 | 64 | func DeleteSpecification(c *gin.Context) { 65 | id := c.Param("id") 66 | count := common.DB().DeleteById("specification", id) 67 | if count <= 0 { 68 | c.JSON(http.StatusOK, gin.H{"code": 400, "msg": "删除数据失败", "count": 0}) 69 | return 70 | } 71 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "删除数据成功", "count": count}) 72 | } 73 | -------------------------------------------------------------------------------- /api/storeApi.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "net/http" 7 | "time" 8 | 9 | "github.com/gin-gonic/gin" 10 | "github.com/google/uuid" 11 | "go.mongodb.org/mongo-driver/bson" 12 | "go.mongodb.org/mongo-driver/bson/primitive" 13 | "mtmn.top/fish-service/common" 14 | "mtmn.top/fish-service/entity" 15 | "mtmn.top/fish-service/service" 16 | ) 17 | 18 | // 保存店铺申请 19 | func SaveApplicationForm(c *gin.Context) { 20 | userId := c.MustGet("userId").(string) 21 | form := &entity.ApplicationForm{} 22 | err := c.ShouldBindJSON(&form) 23 | if err != nil { 24 | c.JSON(http.StatusBadRequest, gin.H{"msg": err.Error()}) 25 | return 26 | } 27 | if form.Id == "" { 28 | // 基础信息 29 | form.Status = "apply" //申请中 30 | form.CreatorId = userId 31 | form.CreateTime = time.Now() 32 | form.UpdateTime = time.Now() 33 | } else { 34 | c.JSON(http.StatusBadRequest, gin.H{"msg": "您已经提交申请请求,等待审核,请勿重复提交"}) 35 | return 36 | } 37 | result := common.DB().InsertOne("applicationForm", form) 38 | if result.InsertedID != nil { 39 | applyId := result.InsertedID.(primitive.ObjectID).Hex() 40 | form.Id = applyId 41 | go common.SendMailApply(form) 42 | } 43 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "保存成功", "id": result.InsertedID}) 44 | } 45 | 46 | // 获取申请 47 | func GetApplicationForm(c *gin.Context) { 48 | userId := c.MustGet("userId").(string) 49 | if userId != "" { 50 | form := &entity.ApplicationForm{} 51 | common.DB().FindOne("applicationForm", "creatorId", userId).Decode(&form) 52 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "获取成功", "data": form}) 53 | return 54 | } 55 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "无申请数据"}) 56 | } 57 | 58 | func SaveStore(c *gin.Context) { 59 | userId := c.MustGet("userId").(string) 60 | store := entity.Store{} 61 | err := c.ShouldBindJSON(&store) 62 | if err != nil { 63 | c.JSON(http.StatusBadRequest, gin.H{"msg": err.Error()}) 64 | return 65 | } 66 | if store.Id == "" { 67 | // 店铺基础信息 68 | store.Creator = userId 69 | store.CreateTime = time.Now() 70 | store.UpdateTime = time.Now() 71 | } 72 | log.Println(store) 73 | if store.Id != "" { 74 | //FIXME 店铺管理员才有权限修改店铺信息 75 | update := bson.D{ 76 | {Key: "$set", Value: bson.D{ 77 | {Key: "name", Value: store.Name}, 78 | {Key: "remark", Value: store.Remark}, 79 | {Key: "avatarPath", Value: store.AvatarPath}, 80 | {Key: "hidePrice", Value: store.HidePrice}, 81 | {Key: "updateTime", Value: store.UpdateTime}, 82 | }}, 83 | } 84 | result := common.DB().UpdateByID("store", store.Id, update) 85 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "保存成功", "id": result.UpsertedID}) 86 | } else { 87 | result := common.DB().InsertOne("store", store) 88 | if result.InsertedID != nil { 89 | storeId := result.InsertedID.(primitive.ObjectID).Hex() 90 | log.Println("创建店铺成功,店铺id:", storeId, "用户id:"+userId) 91 | update := bson.D{ 92 | {Key: "$set", Value: bson.D{ 93 | {Key: "role", Value: "boss"}, 94 | {Key: "storeId", Value: storeId}, 95 | }}, 96 | } 97 | common.DB().UpdateByID("user", userId, update) 98 | } 99 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "保存成功", "id": result.InsertedID}) 100 | } 101 | } 102 | 103 | // 如果有ID 则根据店铺ID 获取店铺信息 如果参数没有ID 是否有创建店铺 104 | func GetStore(c *gin.Context) { 105 | storeId := c.Param("id") 106 | if storeId == "" { 107 | storeId = c.MustGet("storeId").(string) 108 | } 109 | if storeId != "" { 110 | store := &entity.Store{} 111 | common.DB().FindID("store", storeId).Decode(store) 112 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "获取成功", "data": store}) 113 | } 114 | } 115 | 116 | // 生成邀请码 117 | func GenerateInviteCode(c *gin.Context) { 118 | // 只能生成一个邀请连接 -> redis key:店铺主键 value:生成的唯一编码,过期时间设置为10分钟 119 | user := c.MustGet("claims").(*service.UserClaims) 120 | storeId := user.StoreId 121 | if storeId == "" { 122 | c.JSON(http.StatusOK, gin.H{"code": -1, "msg": "获取店铺信息失败,无法生成邀请链接,请确认您是否已经创建店铺成功"}) 123 | return 124 | } 125 | uuid := uuid.New().String() 126 | err := common.RedisClient().Set(context.Background(), storeId, uuid, time.Minute*10).Err() 127 | if err != nil { 128 | log.Println(err) 129 | c.JSON(http.StatusOK, gin.H{"code": -1, "msg": "生成邀请码失败"}) 130 | return 131 | } 132 | log.Println("生成店铺邀请码,", "店铺ID为:", storeId, "邀请编码为:", uuid) 133 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "获取成功", "data": uuid}) 134 | } 135 | 136 | // 根据邀请码加入店铺 137 | func JoinStore(c *gin.Context) { 138 | code := c.Query("code") 139 | storeId := c.Query("storeId") 140 | userId := c.MustGet("userId").(string) 141 | if code == "" || storeId == "" { 142 | c.JSON(http.StatusOK, gin.H{"code": -1, "msg": "加入店铺失败,参数有误"}) 143 | } 144 | log.Println("通过邀请码加入店铺:", "店铺ID为:", storeId, "邀请编码为:", code) 145 | stringCmd := common.RedisClient().Get(context.Background(), storeId) 146 | if stringCmd.Err() != nil { 147 | log.Println(stringCmd.Err()) 148 | c.JSON(http.StatusOK, gin.H{"code": -1, "msg": "加入店铺失败,邀请码获取失败,请尝试重新邀请"}) 149 | return 150 | } 151 | // 根据店铺ID redis 验证编码是否存在,如果编码不存在,提醒前端连接已经过期,需要进行重新邀请 152 | if stringCmd.Val() != code { 153 | c.JSON(http.StatusOK, gin.H{"code": -1, "msg": "加入店铺失败,邀请码已过期"}) 154 | return 155 | } 156 | user := entity.User{} 157 | err := common.DB().FindID("user", userId).Decode(&user) 158 | if err != nil { 159 | log.Fatal(err) 160 | c.JSON(http.StatusBadRequest, gin.H{"code": -1, "msg": "获取用户信息失败"}) 161 | return 162 | } 163 | // 如果编码存在,校验该用户是否已经加入其他店铺,如果已经加入,前端提醒不允许再加入 164 | if user.StoreId != "" { 165 | c.JSON(http.StatusBadRequest, gin.H{"code": -1, "msg": "您已经加入店铺,请退出当前店铺再接受邀请"}) 166 | return 167 | } 168 | update := bson.D{ 169 | {Key: "$set", Value: bson.D{ 170 | {Key: "storeId", Value: storeId}, 171 | {Key: "role", Value: "staff"}, 172 | {Key: "updateTime", Value: time.Now()}, 173 | }}, 174 | } 175 | result := common.DB().UpdateByID("user", userId, update) 176 | 177 | if result.UpsertedCount <= 0 { 178 | c.JSON(http.StatusBadRequest, gin.H{"code": -1, "msg": "个人信息异常,加入店铺失败"}) 179 | return 180 | } 181 | c.JSON(http.StatusBadRequest, gin.H{"code": 200, "msg": "加入店铺成功"}) 182 | } 183 | 184 | // 店铺申请审批通过 185 | func ApplyApss(c *gin.Context) { 186 | id := c.Param("id") 187 | if id == "" { 188 | c.JSON(http.StatusBadRequest, gin.H{"code": 200, "msg": "id为空"}) 189 | } 190 | applyForm := &entity.ApplicationForm{} 191 | common.DB().FindID("applicationForm", id).Decode(applyForm) 192 | if applyForm.Status != "apply" { 193 | c.JSON(http.StatusBadRequest, gin.H{"code": -1, "msg": "为找到对应申请信息"}) 194 | return 195 | } 196 | store := &entity.Store{} 197 | store.Name = applyForm.Name 198 | store.Remark = applyForm.Remark 199 | store.Creator = applyForm.CreatorId 200 | store.AvatarPath = "/upload/202207/1657237593-07FzCle0wdp70ac5e049b118708bd43f057e0567eb2a.jpg" 201 | store.CreateTime = time.Now() 202 | store.UpdateTime = time.Now() 203 | insertOneResult := common.DB().InsertOne("store", store) 204 | common.DB().UpdateByID("user", applyForm.CreatorId, bson.D{ 205 | {Key: "$set", Value: bson.D{ 206 | {Key: "storeId", Value: insertOneResult.InsertedID.(primitive.ObjectID).Hex()}, 207 | {Key: "updateTime", Value: time.Now()}, 208 | }}, 209 | }) 210 | common.DB().UpdateByID("applicationForm", id, bson.D{ 211 | {Key: "$set", Value: bson.D{ 212 | {Key: "status", Value: "pass"}, 213 | {Key: "updateTime", Value: time.Now()}, 214 | }}, 215 | }) 216 | go InitStoreData(applyForm.CreatorId, insertOneResult.InsertedID.(primitive.ObjectID).Hex()) 217 | } 218 | 219 | // 初始化店铺数据 220 | func InitStoreData(userId, storeId string) { 221 | // 初始化商品类型 222 | goodsType := entity.GoodsType{} 223 | goodsType.Name = "示例类型" 224 | goodsType.CreateTime = time.Now() 225 | goodsType.StoreId = storeId 226 | goodsType.Creator = userId 227 | typeResult := common.DB().InsertOne("goodsType", goodsType) 228 | goodsType.Id = typeResult.InsertedID.(primitive.ObjectID).Hex() 229 | // 初始化商品 230 | var typeArr = []entity.GoodsType{goodsType} 231 | goods := entity.Goods{} 232 | goods.Name = "示例菜品" 233 | goods.Remark = "美味佳肴,进店必点" 234 | goods.Status = "on" 235 | goods.MainImags = "/upload/202207/1657237191-bBEpch5HkzN78ccb0f9c179320f8c4b5a8f6547d765c.jpg" 236 | goods.CreateTime = time.Now() 237 | goods.StoreId = storeId 238 | goods.Creator = userId 239 | goods.GoodsType = typeArr 240 | common.DB().InsertOne("goods", goods) 241 | // 初始化桌号 242 | desk := entity.Desk{} 243 | desk.Name = "示例桌号" 244 | desk.CreateTime = time.Now() 245 | desk.CreatorId = userId 246 | desk.StoreId = storeId 247 | common.DB().InsertOne("desk", desk) 248 | } 249 | 250 | // 店铺申请 审批不通过 251 | func ApplyReject(c *gin.Context) { 252 | id := c.Param("id") 253 | if id == "" { 254 | c.JSON(http.StatusBadRequest, gin.H{"code": 200, "msg": "id为空"}) 255 | } 256 | applyForm := &entity.ApplicationForm{} 257 | common.DB().FindID("applicationForm", id).Decode(applyForm) 258 | if applyForm.Status != "apply" { 259 | c.JSON(http.StatusBadRequest, gin.H{"code": -1, "msg": "为找到对应申请信息"}) 260 | return 261 | } 262 | common.DB().UpdateByID("applicationForm", id, bson.D{ 263 | {Key: "$set", Value: bson.D{ 264 | {Key: "status", Value: "reject"}, 265 | {Key: "updateTime", Value: time.Now()}, 266 | }}, 267 | }) 268 | } 269 | -------------------------------------------------------------------------------- /api/sysApi.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "time" 7 | 8 | "github.com/gin-gonic/gin" 9 | "github.com/medivhzhan/weapp" 10 | "go.mongodb.org/mongo-driver/bson" 11 | "mtmn.top/fish-service/common" 12 | "mtmn.top/fish-service/dto" 13 | "mtmn.top/fish-service/service" 14 | ) 15 | 16 | func LoginByWx(c *gin.Context) { 17 | code := c.Query("code") 18 | token, err := service.GetTokenByWxCode(code) 19 | if err != nil { 20 | log.Println(err) 21 | c.JSON(http.StatusOK, gin.H{"code": -1, "msg": "用户登录失败"}) 22 | return 23 | } 24 | c.JSON(http.StatusOK, gin.H{"code": 200, "token": token}) 25 | } 26 | 27 | // 解密微信用户基本信息 28 | func DecodeWxUserInfo(c *gin.Context) { 29 | wxData := dto.WxData{} 30 | err := c.ShouldBindJSON(&wxData) 31 | ssk := c.MustGet("sessionKey").(string) 32 | userId := c.MustGet("userId").(string) 33 | log.Println("sessionKey = ", ssk) 34 | log.Println("userId = ", userId) 35 | if err != nil { 36 | c.JSON(http.StatusOK, gin.H{"code": -1, "msg": "获取参数失败"}) 37 | return 38 | } 39 | wxUserInfo, err := weapp.DecryptUserInfo(wxData.RawData, wxData.EncryptedData, wxData.Signature, wxData.Iv, ssk) 40 | if err != nil { 41 | c.JSON(http.StatusOK, gin.H{"code": -1, "msg": "解析信息失败"}) 42 | return 43 | } 44 | 45 | if wxUserInfo.OpenID != "" { 46 | gender := "未知" 47 | //0:未知、1:男、2:女 48 | if wxUserInfo.Gender == 1 { 49 | gender = "男" 50 | } else if wxUserInfo.Gender == 2 { 51 | gender = "女" 52 | } 53 | update := bson.D{ 54 | {Key: "$set", Value: bson.D{ 55 | {Key: "nickName", Value: wxUserInfo.Nickname}, 56 | {Key: "address", Value: wxUserInfo.Province + wxUserInfo.City + wxUserInfo.Country}, 57 | {Key: "avatarUrl", Value: wxUserInfo.Avatar}, 58 | {Key: "gender", Value: gender}, 59 | {Key: "updateTime", Value: time.Now()}, 60 | }}, 61 | } 62 | common.DB().UpdateByID("user", userId, update) 63 | } 64 | c.JSON(http.StatusOK, gin.H{"code": 200, "token": "解析成功"}) 65 | } 66 | 67 | // 解密微信用户手机号 68 | func DecodeWxPhone(c *gin.Context) { 69 | wxData := dto.WxData{} 70 | err := c.ShouldBindJSON(&wxData) 71 | ssk := c.MustGet("sessionKey").(string) 72 | userId := c.MustGet("userId").(string) 73 | if err != nil { 74 | c.JSON(http.StatusOK, gin.H{"code": -1, "msg": "获取参数失败"}) 75 | return 76 | } 77 | phone, err := weapp.DecryptPhoneNumber(ssk, wxData.EncryptedData, wxData.Iv) 78 | if err != nil { 79 | c.JSON(http.StatusOK, gin.H{"code": -1, "msg": "解析信息失败"}) 80 | return 81 | } 82 | 83 | if phone.PhoneNumber != "" { 84 | update := bson.D{ 85 | {Key: "$set", Value: bson.D{ 86 | {Key: "phone", Value: phone.PhoneNumber}, 87 | {Key: "updateTime", Value: time.Now()}, 88 | }}, 89 | } 90 | common.DB().UpdateByID("user", userId, update) 91 | } 92 | c.JSON(http.StatusOK, gin.H{"code": 200, "token": "解析成功"}) 93 | } 94 | -------------------------------------------------------------------------------- /api/userApi.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "net/http" 7 | "time" 8 | 9 | "github.com/gin-gonic/gin" 10 | "go.mongodb.org/mongo-driver/bson" 11 | "go.mongodb.org/mongo-driver/bson/primitive" 12 | "mtmn.top/fish-service/common" 13 | "mtmn.top/fish-service/entity" 14 | "mtmn.top/fish-service/service" 15 | ) 16 | 17 | // 保存用户信息 18 | func SaveUser(c *gin.Context) { 19 | userId := c.MustGet("userId").(string) 20 | user := entity.User{} 21 | err := c.ShouldBindJSON(&user) 22 | if err != nil { 23 | log.Println(err) 24 | c.JSON(http.StatusBadRequest, gin.H{"msg": err.Error()}) 25 | return 26 | } 27 | update := bson.D{ 28 | {Key: "$set", Value: bson.D{ 29 | {Key: "nickName", Value: user.NickName}, 30 | {Key: "phone", Value: user.Phone}, 31 | {Key: "gender", Value: user.Gender}, 32 | {Key: "address", Value: user.Address}, 33 | {Key: "avatarUrl", Value: user.AvatarUrl}, 34 | {Key: "updateTime", Value: time.Now()}, 35 | }}, 36 | } 37 | result := common.DB().UpdateByID("user", userId, update) 38 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "保存成功", "id": result.UpsertedID}) 39 | } 40 | 41 | // 获取用户信息 42 | func GetUser(c *gin.Context) { 43 | userId := c.MustGet("userId").(string) 44 | user := entity.User{} 45 | err := common.DB().FindID("user", userId).Decode(&user) 46 | if err != nil { 47 | log.Fatal(err) 48 | c.JSON(http.StatusBadRequest, gin.H{"code": 400, "msg": "获取用户信息失败"}) 49 | } 50 | log.Println(user.Id) 51 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "获取成功", "data": user}) 52 | } 53 | 54 | // 查询店铺的所有人员 55 | func FindUserByStore(c *gin.Context) { 56 | storeId := c.MustGet("storeId").(string) 57 | filter := bson.M{"storeId": storeId} 58 | sort := bson.D{primitive.E{Key: "createTime", Value: 1}} 59 | cursor := common.DB().CollectionDocumentsFilter("user", 0, 100, filter, sort) 60 | user := []entity.User{} 61 | cursor.All(context.TODO(), &user) 62 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "获取数据成功", "data": user, "total": len(user)}) 63 | } 64 | 65 | // 将人员从店铺中移除,店长才有权限 66 | func RemoveUserByStore(c *gin.Context) { 67 | userClaims := c.MustGet("claims").(*service.UserClaims) 68 | if userClaims.Role != "boss" { 69 | c.JSON(http.StatusOK, gin.H{"code": -1, "msg": "只有店长才有权限移除员工"}) 70 | } 71 | storeId := c.MustGet("storeId").(string) 72 | userId := c.Param("userId") 73 | if userId == "" { 74 | c.JSON(http.StatusOK, gin.H{"code": -1, "msg": "请选择需要移除的员工"}) 75 | } 76 | oid, _ := primitive.ObjectIDFromHex(userId) 77 | filter := bson.M{"storeId": storeId, "_id": oid} 78 | update := bson.D{ 79 | {Key: "$set", Value: bson.D{ 80 | {Key: "storeId", Value: ""}, 81 | }}, 82 | } 83 | result := common.DB().UpdateOne("user", filter, update) 84 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "人员移除成功", "id": result.UpsertedID}) 85 | } 86 | 87 | // 修改用户权限 88 | func ChangeUserRole(c *gin.Context) { 89 | userClaims := c.MustGet("claims").(*service.UserClaims) 90 | if userClaims.Role != "boss" { 91 | c.JSON(http.StatusOK, gin.H{"code": -1, "msg": "只有店长才有权限移除员工"}) 92 | } 93 | storeId := c.MustGet("storeId").(string) 94 | userId := c.Query("userId") 95 | role := c.Query("role") 96 | oid, _ := primitive.ObjectIDFromHex(userId) 97 | filter := bson.M{"storeId": storeId, "_id": oid} 98 | update := bson.D{ 99 | {Key: "$set", Value: bson.D{ 100 | {Key: "role", Value: role}, 101 | }}, 102 | } 103 | result := common.DB().UpdateOne("user", filter, update) 104 | c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "修改成功", "id": result.UpsertedID}) 105 | } 106 | -------------------------------------------------------------------------------- /common/env.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/joho/godotenv" 7 | ) 8 | 9 | func InitEnv() { 10 | err := godotenv.Load(".env") 11 | if err != nil { 12 | log.Fatalf("Error loading .env file") 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /common/mail.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "net/smtp" 7 | "strings" 8 | 9 | "github.com/jordan-wright/email" 10 | "mtmn.top/fish-service/entity" 11 | ) 12 | 13 | var ( 14 | APPLY_URL string = "https://www.mtmn.top/api/v1/apply" 15 | ) 16 | 17 | func SendMailApply(form *entity.ApplicationForm) { 18 | e := email.NewEmail() 19 | //设置发送方的邮箱 20 | e.From = "xxx" 21 | // 设置接收方的邮箱 22 | e.To = []string{"xxx@qq.com"} 23 | //设置主题 24 | e.Subject = "申请请求:" + form.Name 25 | //设置文件发送的内容 26 | e.HTML = []byte(` 27 |
  • 名称:` + form.Name + `
  • 28 |
  • 说明:` + form.Remark + `
  • 29 |
  • 姓名:` + form.PersonName + `
  • 30 |
  • 电话:` + form.Phone + `
  • 31 |
  • 备注:` + form.Info + `
  • 32 |
  • 图片:` + strings.Join(form.Imgs, "
    ") + `
  • 33 |
  • 34 |
    35 | 审批通过 36 |

    37 | 审批不通过 38 | `) 39 | //设置服务器相关的配置 40 | e.Send("smtp.126.com:25", smtp.PlainAuth("", "xxx@126.com", "RIELLOLXKJEKBKKE", "smtp.126.com")) 41 | 42 | } 43 | 44 | func SendMailObj(suject string, params interface{}) { 45 | paramsStr, err := json.Marshal(params) 46 | if err != nil { 47 | log.Println("发送邮件失败,参数为:", params) 48 | } 49 | SendMail(suject, string(paramsStr)) 50 | } 51 | 52 | func SendMail(suject string, msg string) { 53 | e := email.NewEmail() 54 | //设置发送方的邮箱 55 | e.From = "xxx" 56 | // 设置接收方的邮箱 57 | e.To = []string{"xxx@qq.com"} 58 | //设置主题 59 | e.Subject = suject 60 | //设置文件发送的内容 61 | e.Text = []byte(msg) 62 | //设置服务器相关的配置 63 | err := e.Send("smtp.126.com:25", smtp.PlainAuth("", "xxxx@126.com", "RIELLOLXKJEKBKKE", "smtp.126.com")) 64 | if err != nil { 65 | log.Fatal(err) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /common/mail_test.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestSendMail(t *testing.T) { 8 | SendMail("测试主题", " 这是一个测试内容") 9 | } 10 | -------------------------------------------------------------------------------- /common/mongo.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "os" 8 | "sync" 9 | "time" 10 | 11 | "go.mongodb.org/mongo-driver/bson" 12 | "go.mongodb.org/mongo-driver/bson/primitive" 13 | "go.mongodb.org/mongo-driver/mongo" 14 | "go.mongodb.org/mongo-driver/mongo/options" 15 | ) 16 | 17 | type mgo struct { 18 | Database string 19 | Client *mongo.Client 20 | } 21 | 22 | var instance *mgo 23 | 24 | var mu sync.Mutex 25 | 26 | // public 27 | func DB() *mgo { 28 | mu.Lock() 29 | defer mu.Unlock() 30 | if instance == nil { 31 | instance = &mgo{ 32 | Database: os.Getenv("MONGO_DATABASE"), 33 | } 34 | instance.Client = Connect() 35 | } 36 | return instance 37 | } 38 | 39 | // 连接设置 40 | func Connect() *mongo.Client { 41 | log.Println("获取mongo连接") 42 | user := os.Getenv("MONGO_USER") 43 | pass := os.Getenv("MONGO_PASS") 44 | 45 | var uri string 46 | if user != "" { 47 | uri = fmt.Sprintf("mongodb://%s:%s@%s:%s/%s?w=majority", user, pass, 48 | os.Getenv("MONGO_HOST"), os.Getenv("MONGO_PORT"), os.Getenv("MONGO_DATABASE")) 49 | } else { 50 | uri = fmt.Sprintf("mongodb://%s:%s/%s", os.Getenv("MONGO_HOST"), os.Getenv("MONGO_PORT"), os.Getenv("MONGO_DATABASE")) 51 | log.Println(uri) 52 | } 53 | 54 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 55 | defer cancel() 56 | client, err := mongo.Connect(ctx, options.Client().ApplyURI(uri).SetMaxPoolSize(20)) // 连接池 57 | if err != nil { 58 | fmt.Println(err) 59 | } 60 | return client 61 | } 62 | 63 | func (m *mgo) Coll(collection string) *mongo.Collection { 64 | coll, err := m.Client.Database(m.Database).Collection(collection).Clone() 65 | if err != nil { 66 | log.Fatal("获取mongo集合失败") 67 | } 68 | return coll 69 | } 70 | 71 | // 查询单个 72 | func (m *mgo) FindOne(coll string, key string, value interface{}) *mongo.SingleResult { 73 | collection := m.Coll(coll) 74 | //collection. 75 | filter := bson.D{primitive.E{Key: key, Value: value}} 76 | singleResult := collection.FindOne(context.TODO(), filter) 77 | return singleResult 78 | } 79 | 80 | // 查询单个 81 | func (m *mgo) FindID(coll string, id string) *mongo.SingleResult { 82 | collection := m.Coll(coll) 83 | if oid, err := primitive.ObjectIDFromHex(id); err == nil { 84 | filter := bson.D{primitive.E{Key: "_id", Value: oid}} 85 | singleResult := collection.FindOne(context.TODO(), filter) 86 | return singleResult 87 | } 88 | //collection. 89 | return nil 90 | } 91 | 92 | //插入单个 93 | func (m *mgo) InsertOne(coll string, value interface{}) *mongo.InsertOneResult { 94 | collection := m.Coll(coll) 95 | insertResult, err := collection.InsertOne(context.TODO(), value) 96 | if err != nil { 97 | fmt.Println(err) 98 | } 99 | return insertResult 100 | } 101 | 102 | //根据ID 编辑 103 | func (m *mgo) UpdateByID(coll string, id string, update bson.D) *mongo.UpdateResult { 104 | if oid, err := primitive.ObjectIDFromHex(id); err == nil { 105 | collection := m.Coll(coll) 106 | updateResult, err := collection.UpdateByID(context.TODO(), oid, update) 107 | if err != nil { 108 | fmt.Println(err) 109 | } 110 | return updateResult 111 | } 112 | return &mongo.UpdateResult{} 113 | } 114 | 115 | //根据ID 编辑 116 | func (m *mgo) FindOneAndUpdate(coll string, filter interface{}, update bson.D) *mongo.SingleResult { 117 | return m.Coll(coll).FindOneAndUpdate(context.TODO(), filter, update) 118 | } 119 | 120 | //根据ID 编辑 121 | func (m *mgo) UpdateOne(coll string, filter interface{}, update bson.D) *mongo.UpdateResult { 122 | updateResult, err := m.Coll(coll).UpdateOne(context.TODO(), filter, update) 123 | if err != nil { 124 | fmt.Println(err) 125 | } 126 | return updateResult 127 | } 128 | 129 | //查询集合里有多少数据 130 | func (m *mgo) CollectionCount(coll string) int64 { 131 | collection := m.Coll(coll) 132 | size, _ := collection.EstimatedDocumentCount(context.TODO()) 133 | return size 134 | } 135 | 136 | //根据条件查询集合里有多少数据 137 | func (m *mgo) CountByFilter(coll string, filter bson.M) int64 { 138 | collection := m.Coll(coll) 139 | size, _ := collection.CountDocuments(context.TODO(), filter) 140 | return size 141 | } 142 | 143 | //按选项查询集合 Skip 跳过 Limit 读取数量 sort 1 ,-1 . 1 为最初时间读取 , -1 为最新时间读取 144 | func (m *mgo) CollectionDocumentsFilter(coll string, Skip, Limit int64, filter bson.M, sort bson.D) *mongo.Cursor { 145 | collection := m.Coll(coll) 146 | // SORT := bson.D{primitive.E{Key: "_id", Value: sort}} //filter := bson.D{{key,value}} 147 | findOptions := options.Find().SetSort(sort).SetLimit(Limit).SetSkip(Skip) 148 | //findOptions.SetLimit(i) 149 | temp, _ := collection.Find(context.Background(), filter, findOptions) 150 | return temp 151 | } 152 | 153 | //按选项查询集合 Skip 跳过 Limit 读取数量 sort 1 ,-1 . 1 为最初时间读取 , -1 为最新时间读取 154 | func (m *mgo) CollectionDocuments(coll string, Skip, Limit int64, sort bson.D) *mongo.Cursor { 155 | collection := m.Coll(coll) 156 | // SORT := bson.D{primitive.E{Key: "_id", Value: sort}} //filter := bson.D{{key,value}} 157 | filter := bson.D{{}} 158 | findOptions := options.Find().SetSort(sort).SetLimit(Limit).SetSkip(Skip) 159 | //findOptions.SetLimit(i) 160 | temp, _ := collection.Find(context.Background(), filter, findOptions) 161 | return temp 162 | } 163 | 164 | //删除 165 | func (m *mgo) DeleteById(coll string, id string) int64 { 166 | collection := m.Coll(coll) 167 | if oid, err := primitive.ObjectIDFromHex(id); err == nil { 168 | filter := bson.D{primitive.E{Key: "_id", Value: oid}} 169 | count, err := collection.DeleteOne(context.TODO(), filter) 170 | if err != nil { 171 | return 0 172 | } 173 | return count.DeletedCount 174 | } 175 | return 0 176 | } 177 | 178 | //删除文章 179 | func (m *mgo) Delete(coll string, key string, value interface{}) int64 { 180 | collection := m.Coll(coll) 181 | filter := bson.D{primitive.E{Key: key, Value: value}} 182 | count, err := collection.DeleteOne(context.TODO(), filter, nil) 183 | if err != nil { 184 | fmt.Println(err) 185 | } 186 | return count.DeletedCount 187 | } 188 | 189 | //删除多个 190 | func (m *mgo) DeleteMany(coll string, key string, value interface{}) int64 { 191 | collection := m.Coll(coll) 192 | filter := bson.D{primitive.E{Key: key, Value: value}} 193 | 194 | count, err := collection.DeleteMany(context.TODO(), filter) 195 | if err != nil { 196 | fmt.Println(err) 197 | } 198 | return count.DeletedCount 199 | } 200 | -------------------------------------------------------------------------------- /common/mongo_test.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "os" 8 | "testing" 9 | 10 | "github.com/joho/godotenv" 11 | "go.mongodb.org/mongo-driver/bson" 12 | "go.mongodb.org/mongo-driver/mongo" 13 | "go.mongodb.org/mongo-driver/mongo/readpref" 14 | ) 15 | 16 | //测试环境变量设置 17 | func TestSetup(t *testing.T) { 18 | dir, _ := os.Getwd() 19 | fileName := dir + "/../.env" 20 | fmt.Println(fileName) 21 | err := godotenv.Load(fileName) 22 | if err != nil { 23 | log.Fatalf("Error loading .env file") 24 | } 25 | 26 | } 27 | func TestConnectMongo(t *testing.T) { 28 | // 判断服务是不是可用 29 | err := DB().Client.Ping(context.Background(), readpref.Primary()) 30 | if err != nil { 31 | log.Fatal("mongodb 连接失败") 32 | } 33 | } 34 | 35 | var insertResult *mongo.InsertOneResult 36 | 37 | func TestInsert(t *testing.T) { 38 | insertResult = DB().InsertOne("test", bson.M{"name": "test", "remark": "test insert on"}) 39 | if insertResult.InsertedID == nil { 40 | log.Fatal("inesert error") 41 | } 42 | fmt.Println("Inserted a single document: ", insertResult.InsertedID) 43 | } 44 | 45 | func TestFindOne(t *testing.T) { 46 | result := bson.M{} 47 | singleResult := DB().FindOne("test", "_id", insertResult.InsertedID) 48 | singleResult.Decode(&result) 49 | if result["name"] != "test" { 50 | log.Fatal("find on error") 51 | } 52 | log.Println(result) 53 | } 54 | 55 | func TestDelete(t *testing.T) { 56 | result := DB().Delete("test", "_id", insertResult.InsertedID) 57 | if result <= 0 { 58 | log.Fatal("delete on error") 59 | } 60 | log.Println(result) 61 | } 62 | -------------------------------------------------------------------------------- /common/redis.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "os" 7 | "sync" 8 | "time" 9 | 10 | "github.com/go-redis/redis/v8" 11 | ) 12 | 13 | var ( 14 | ctx = context.Background() 15 | ) 16 | 17 | type RedisHelper struct { 18 | *redis.Client 19 | } 20 | 21 | var redisHelper *RedisHelper 22 | 23 | var redisOnce sync.Once 24 | 25 | func RedisClient() *redis.Client { 26 | return redisHelper.Client 27 | } 28 | 29 | func InitRedisClient() *redis.Client { 30 | addr := os.Getenv("REDIS_ADDR") 31 | password := os.Getenv("REDIS_PASSWORD") 32 | log.Println("redis addr:" + addr) 33 | rdb := redis.NewClient(&redis.Options{ 34 | Addr: addr, 35 | Password: password, 36 | DB: 0, 37 | DialTimeout: 10 * time.Second, 38 | ReadTimeout: 30 * time.Second, 39 | WriteTimeout: 30 * time.Second, 40 | PoolSize: 10, 41 | PoolTimeout: 30 * time.Second, 42 | }) 43 | 44 | redisOnce.Do(func() { 45 | rdh := new(RedisHelper) 46 | rdh.Client = rdb 47 | redisHelper = rdh 48 | }) 49 | 50 | if _, err := rdb.Ping(ctx).Result(); err != nil { 51 | log.Fatal(err.Error()) 52 | return nil 53 | } 54 | 55 | return rdb 56 | } 57 | -------------------------------------------------------------------------------- /common/redis_test.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "log" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestRedisConnect(t *testing.T) { 10 | TestSetup(t) 11 | InitRedisClient() 12 | redisClient := RedisClient() 13 | err := redisClient.Set(ctx, "test", "123", time.Minute*10).Err() 14 | if err != nil { 15 | t.Error(err) 16 | } 17 | stringCmd := redisClient.Get(ctx, "test") 18 | if stringCmd.Err() != nil { 19 | t.Error(stringCmd.Err()) 20 | } 21 | log.Println("get value is :" + stringCmd.Val()) 22 | if stringCmd.Val() != "123" { 23 | t.Error("redis get value error") 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /dto/wxDto.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | type ( 4 | WxData struct { 5 | // 不包括敏感信息的原始数据字符串,用于计算签名。 6 | RawData string `bson:"rawData" json:"rawData"` 7 | // 小程序通过 api 得到的加密数据(encryptedData) 8 | EncryptedData string `bson:"encryptedData" json:"encryptedData"` 9 | // 使用 sha1( rawData + session_key ) 得到字符串,用于校验用户信息 10 | Signature string `bson:"signature" json:"signature"` 11 | // 小程序通过 api 得到的初始向量(iv) 12 | Iv string `bson:"iv" json:"iv"` 13 | } 14 | ) 15 | -------------------------------------------------------------------------------- /entity/desk.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import "time" 4 | 5 | type ( 6 | Desk struct { 7 | Id string `bson:"_id,omitempty" json:"id"` 8 | Name string `bson:"name" binding:"required" json:"name"` 9 | // 店铺Id 10 | StoreId string `bson:"storeId" json:"storeId"` 11 | CreatorId string `bson:"creatorId" json:"creatorId"` 12 | CreateTime time.Time `bson:"createTime" json:"createTime"` 13 | UpdateTime time.Time `bson:"updateTime" json:"updateTime"` 14 | } 15 | ) 16 | -------------------------------------------------------------------------------- /entity/feedback.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import "time" 4 | 5 | type ( 6 | Feedback struct { 7 | Id string `bson:"_id,omitempty" json:"id"` 8 | Content string `bson:"content" binding:"required" json:"content"` 9 | Imgs []string `bson:"imgs" json:"imgs"` 10 | // 姓名 11 | Name string `bson:"name" json:"name"` 12 | // 电话 13 | Phone string `bson:"phone" json:"phone"` 14 | CreatorId string `bson:"creatorId" json:"creatorId"` 15 | CreateTime time.Time `bson:"createTime" json:"createTime"` 16 | UpdateTime time.Time `bson:"updateTime" json:"updateTime"` 17 | } 18 | ) 19 | -------------------------------------------------------------------------------- /entity/goods.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import "time" 4 | 5 | type ( 6 | 7 | // 商品信息 8 | Goods struct { 9 | Id string `bson:"_id,omitempty" json:"id"` 10 | Name string `bson:"name" binding:"required" json:"name"` 11 | Remark string `bson:"remark" json:"remark"` 12 | Price float64 `bson:"price" binding:"gte=1,lte=9999" json:"price,string"` 13 | Specification []Specification `bson:"specification" json:"specification"` 14 | GoodsType []GoodsType `bson:"goodsType" json:"goodsType"` 15 | Imgs []string `bson:"imgs" json:"imgs"` 16 | MainImags string `bson:"mainImags" json:"mainImags"` 17 | // on:已上架 off:已下架 18 | Status string `bson:"status" json:"status"` 19 | // 店铺Id 20 | StoreId string `bson:"storeId" json:"storeId"` 21 | Creator string `bson:"creator" json:"creator"` 22 | CreateTime time.Time `bson:"createTime" json:"createTime"` 23 | UpdateTime time.Time `bson:"updateTime" json:"updateTime"` 24 | } 25 | 26 | // 商品类型 27 | GoodsType struct { 28 | Id string `bson:"_id,omitempty" json:"id" ` 29 | Name string `bson:"name" binding:"required" json:"name"` 30 | Order float64 `bson:"order" json:"order"` 31 | // 店铺Id 32 | StoreId string `bson:"storeId" json:"storeId"` 33 | Creator string `bson:"creator" json:"creator"` 34 | CreateTime time.Time `bson:"createTime" json:"createTime"` 35 | } 36 | 37 | // 规格 38 | Specification struct { 39 | Id string `bson:"_id,omitempty" json:"id" ` 40 | Name string `bson:"name" binding:"required" json:"name"` 41 | Price string `bson:"price" json:"price"` 42 | GroupName string `bson:"groupName" json:"groupName"` 43 | // 店铺Id 44 | StoreId string `bson:"storeId" json:"storeId"` 45 | CreateTime time.Time `bson:"createTime" json:"createTime"` 46 | } 47 | ) 48 | -------------------------------------------------------------------------------- /entity/orders.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import "time" 4 | 5 | type ( 6 | 7 | // 订单 8 | Order struct { 9 | Id string `bson:"_id,omitempty" json:"id"` 10 | // 备注 11 | Remark string `bson:"remark" json:"remark"` 12 | // 订单金额 13 | Price float64 `bson:"price" json:"price,string" binding:"gte=1,lte=9999"` 14 | // 支付金额 15 | PayPrice float64 `bson:"payPrice" json:"payPrice,string"` 16 | // normal:待支付 pay:已支付 cancel:取消 complete:完成 17 | Status string `bson:"status" json:"status"` 18 | // 店铺Id 19 | StoreId string `bson:"storeId" json:"storeId"` 20 | // 店铺名称 21 | StoreName string `bson:"storeName" json:"storeName"` 22 | // 桌号iD 23 | DeskId string `bson:"deskId" json:"deskId"` 24 | // 桌号名称 25 | DeskName string `bson:"deskName" json:"deskName"` 26 | // 人数 27 | PersonNum int16 `bson:"personNum" json:"personNum" binding:"gte=1,lte=99"` 28 | // 创建人ID 29 | CreatorId string `bson:"creatorId" json:"creatorId"` 30 | // 订单商品信息 31 | OrderGoods []OrderGoods `bson:"orderGoods" json:"orderGoods"` 32 | CreateTime time.Time `bson:"createTime" json:"createTime"` 33 | UpdateTime time.Time `bson:"updateTime" json:"updateTime"` 34 | } 35 | 36 | // 订单商品 37 | OrderGoods struct { 38 | Id string `bson:"_id,omitempty" json:"id"` 39 | Name string `bson:"name" json:"name"` 40 | Price float64 `bson:"price" binding:"gte=1,lte=9999" json:"price,string"` 41 | Number int8 `bson:"number" binding:"gte=1,lte=999" json:"number"` 42 | // 店铺Id 43 | StoreId string `bson:"storeId" json:"storeId"` 44 | CreateTime time.Time `bson:"createTime" json:"createTime"` 45 | UpdateTime time.Time `bson:"updateTime" json:"updateTime"` 46 | } 47 | ) 48 | -------------------------------------------------------------------------------- /entity/store.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import "time" 4 | 5 | type ( 6 | // 店铺信息 7 | Store struct { 8 | Id string `bson:"_id,omitempty" json:"id"` 9 | Name string `bson:"name" binding:"required" json:"name"` 10 | Remark string `bson:"remark" json:"remark"` 11 | AvatarPath string `bson:"avatarPath" json:"avatarPath"` 12 | // 如果为true,界面上显示价格 13 | HidePrice bool `bson:"hidePrice" json:"hidePrice"` 14 | Creator string `bson:"creator" json:"creator"` 15 | CreateTime time.Time `bson:"createTime" json:"createTime"` 16 | UpdateTime time.Time `bson:"updateTime" json:"updateTime"` 17 | } 18 | 19 | // 申请表 20 | ApplicationForm struct { 21 | Id string `bson:"_id,omitempty" json:"id"` 22 | // 店铺名称 23 | Name string `bson:"name" binding:"required" json:"name"` 24 | // 店铺简介 25 | Remark string `bson:"remark" json:"remark"` 26 | // 附件列表 27 | Imgs []string `bson:"imgs" json:"imgs"` 28 | // 姓名 29 | PersonName string `bson:"personName" json:"personName"` 30 | // 电话 31 | Phone string `bson:"phone" json:"phone"` 32 | // 补充说明 33 | Info string `bson:"info" json:"info"` 34 | // 审批状态 : apply:申请中 pass:审批通过 reject:审批不通过 35 | Status string `bson:"status" json:"status"` 36 | // 审批结果说明 37 | Result string `bson:"result" json:"result"` 38 | CreatorId string `bson:"creatorId" json:"creatorId"` 39 | CreateTime time.Time `bson:"createTime" json:"createTime"` 40 | UpdateTime time.Time `bson:"updateTime" json:"updateTime"` 41 | } 42 | ) 43 | -------------------------------------------------------------------------------- /entity/user.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import "time" 4 | 5 | // 用户信息 6 | type User struct { 7 | Id string `bson:"_id,omitempty" json:"id"` 8 | // 微信平台 openId 9 | WxOpenid string `bson:"wxOpenid" json:"wxOpenid"` 10 | // 角色:admin 超级管理员 boss 店长 manager 管理员 staff 店员 user 普通用户 11 | Role string `bson:"role" json:"role"` 12 | // 店铺Id 13 | StoreId string `bson:"storeId" json:"storeId"` 14 | // 网名 15 | NickName string `bson:"nickName" json:"nickName"` 16 | // 头像地址 17 | AvatarUrl string `bson:"avatarUrl" json:"avatarUrl"` 18 | // 手机信息 19 | Phone string `bson:"phone" json:"phone"` 20 | // 性别 男 女 21 | Gender string `bson:"gender" json:"gender"` 22 | // 地址信息 23 | Address string `bson:"address" json:"address"` 24 | CreateTime time.Time `bson:"createTime" json:"createTime"` 25 | UpdateTime time.Time `bson:"updateTime" json:"updateTime"` 26 | } 27 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module mtmn.top/fish-service 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/aliyun/aliyun-oss-go-sdk v2.2.1+incompatible 7 | github.com/gin-gonic/gin v1.7.7 8 | github.com/joho/godotenv v1.4.0 9 | github.com/stretchr/testify v1.7.0 10 | go.mongodb.org/mongo-driver v1.8.4 11 | ) 12 | 13 | require ( 14 | github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible // indirect 15 | github.com/casbin/casbin v1.9.1 // indirect 16 | github.com/cespare/xxhash/v2 v2.1.2 // indirect 17 | github.com/cilium/ebpf v0.8.1 // indirect 18 | github.com/cosiner/argv v0.1.0 // indirect 19 | github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect 20 | github.com/davecgh/go-spew v1.1.1 // indirect 21 | github.com/derekparker/trie v0.0.0-20200317170641-1fdf38b7b0e9 // indirect 22 | github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect 23 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect 24 | github.com/gin-contrib/sse v0.1.0 // indirect 25 | github.com/go-delve/delve v1.8.2 // indirect 26 | github.com/go-delve/liner v1.2.2-1 // indirect 27 | github.com/go-playground/locales v0.13.0 // indirect 28 | github.com/go-playground/universal-translator v0.17.0 // indirect 29 | github.com/go-playground/validator/v10 v10.4.1 // indirect 30 | github.com/go-redis/redis/v8 v8.11.5 // indirect 31 | github.com/go-stack/stack v1.8.0 // indirect 32 | github.com/golang/protobuf v1.5.2 // indirect 33 | github.com/golang/snappy v0.0.3 // indirect 34 | github.com/google/go-dap v0.6.0 // indirect 35 | github.com/google/uuid v1.3.0 // indirect 36 | github.com/hashicorp/golang-lru v0.5.4 // indirect 37 | github.com/inconshreveable/mousetrap v1.0.0 // indirect 38 | github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible // indirect 39 | github.com/json-iterator/go v1.1.12 // indirect 40 | github.com/klauspost/compress v1.13.6 // indirect 41 | github.com/konsorten/go-windows-terminal-sequences v1.0.3 // indirect 42 | github.com/leodido/go-urn v1.2.0 // indirect 43 | github.com/mattn/go-colorable v0.1.12 // indirect 44 | github.com/mattn/go-isatty v0.0.14 // indirect 45 | github.com/mattn/go-runewidth v0.0.13 // indirect 46 | github.com/medivhzhan/weapp v1.5.1 // indirect 47 | github.com/medivhzhan/weapp/v2 v2.5.0 // indirect 48 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 49 | github.com/modern-go/reflect2 v1.0.2 // indirect 50 | github.com/pkg/errors v0.9.1 // indirect 51 | github.com/pmezard/go-difflib v1.0.0 // indirect 52 | github.com/rivo/uniseg v0.2.0 // indirect 53 | github.com/russross/blackfriday/v2 v2.1.0 // indirect 54 | github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect 55 | github.com/sirupsen/logrus v1.8.1 // indirect 56 | github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect 57 | github.com/spf13/cobra v1.3.0 // indirect 58 | github.com/spf13/pflag v1.0.5 // indirect 59 | github.com/ugorji/go/codec v1.1.7 // indirect 60 | github.com/xdg-go/pbkdf2 v1.0.0 // indirect 61 | github.com/xdg-go/scram v1.0.2 // indirect 62 | github.com/xdg-go/stringprep v1.0.2 // indirect 63 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect 64 | go.starlark.net v0.0.0-20220302181546-5411bad688d1 // indirect 65 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect 66 | golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect 67 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect 68 | golang.org/x/sys v0.0.0-20220307203707-22a9840ba4d7 // indirect 69 | golang.org/x/text v0.3.7 // indirect 70 | golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 // indirect 71 | google.golang.org/protobuf v1.27.1 // indirect 72 | gopkg.in/yaml.v2 v2.4.0 // indirect 73 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect 74 | ) 75 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | 7 | "mtmn.top/fish-service/common" 8 | "mtmn.top/fish-service/routes" 9 | ) 10 | 11 | func main() { 12 | common.InitEnv() 13 | common.InitRedisClient() 14 | 15 | // gin框架启动 16 | r := routes.SetupRouter() 17 | 18 | // 配置文件读取端口 19 | port := os.Getenv("PORT") 20 | log.Println("port config is :" + port) 21 | ssl := os.Getenv("SSL") 22 | if ssl == "https" { 23 | panic(r.RunTLS(":"+port, "./mtmn.top.pem", "./mtmn.top.key")) 24 | } else { 25 | panic(r.Run(":" + port)) 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "testing" 8 | 9 | "github.com/gin-gonic/gin" 10 | "github.com/joho/godotenv" 11 | ) 12 | 13 | func setup() { 14 | gin.SetMode(gin.TestMode) 15 | err := godotenv.Load(".env") 16 | if err != nil { 17 | log.Fatalf("Error loading .env file") 18 | } 19 | fmt.Println("Before all tests") 20 | } 21 | 22 | func teardown() { 23 | fmt.Println("After all tests") 24 | } 25 | func TestMain(m *testing.M) { 26 | setup() 27 | fmt.Println("Test begins....") 28 | code := m.Run() // 如果不加这句,只会执行Main 29 | teardown() 30 | os.Exit(code) 31 | } 32 | -------------------------------------------------------------------------------- /middleware/jwtAuth.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "os" 7 | 8 | "github.com/gin-gonic/gin" 9 | "mtmn.top/fish-service/common" 10 | "mtmn.top/fish-service/entity" 11 | "mtmn.top/fish-service/service" 12 | ) 13 | 14 | func JwtAuth() gin.HandlerFunc { 15 | return func(c *gin.Context) { 16 | log.Println("请求链接", c.Request.URL) 17 | jwtToken, error := service.VerifyTokenHeader(c) 18 | if error != nil { 19 | c.Abort() 20 | log.Println("未授权访问连接", c.Request.URL) 21 | c.JSON(http.StatusUnauthorized, gin.H{"message": "访问未授权"}) 22 | // return可省略, 只要前面执行Abort()就可以让后面的handler函数不再执行 23 | return 24 | } 25 | // log.Println("accessToken.Claims", accessToken.Claims.(jwt.MapClaims)) 26 | claims := jwtToken.Claims.(*service.UserClaims) 27 | // 验证通过,会继续访问下一个中间件 28 | c.Set("claims", claims) 29 | c.Set("userId", claims.UserId) 30 | c.Set("storeId", claims.StoreId) 31 | c.Set("sessionKey", claims.SessionKey) 32 | c.Next() 33 | } 34 | } 35 | 36 | func DevAuth() gin.HandlerFunc { 37 | return func(ctx *gin.Context) { 38 | devUserId := os.Getenv("DEV_USER_ID") 39 | userInfo := entity.User{} 40 | log.Println(devUserId) 41 | common.DB().FindID("user", devUserId).Decode(&userInfo) 42 | log.Println(userInfo) 43 | if userInfo.Id == "" { 44 | ctx.Abort() 45 | log.Println("未授权访问连接", ctx.Request.URL) 46 | ctx.JSON(http.StatusUnauthorized, gin.H{"message": "访问未授权"}) 47 | // return可省略, 只要前面执行Abort()就可以让后面的handler函数不再执行 48 | return 49 | } 50 | 51 | userClaims := &service.UserClaims{ 52 | UserId: userInfo.Id, 53 | OpenID: userInfo.WxOpenid, 54 | NickName: userInfo.NickName, 55 | Role: userInfo.Role, 56 | StoreId: userInfo.StoreId, 57 | } 58 | ctx.Set("claims", userClaims) 59 | ctx.Set("userId", userInfo.Id) 60 | ctx.Set("storeId", userInfo.StoreId) 61 | ctx.Next() 62 | 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /routes/routers.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/gin-gonic/gin" 7 | "mtmn.top/fish-service/api" 8 | "mtmn.top/fish-service/middleware" 9 | ) 10 | 11 | func SetupRouter() *gin.Engine { 12 | router := gin.Default() 13 | dev := os.Getenv("DEV_USER_ID") 14 | v1 := router.Group("/api/v1") 15 | { 16 | if dev == "" { 17 | v1.Use(middleware.JwtAuth()) 18 | } else { 19 | v1.Use(middleware.DevAuth()) 20 | } 21 | 22 | // 文件管理 23 | v1.POST("/upload/token", api.OSSUploadFile) 24 | 25 | // 菜品管理 26 | v1.POST("/goods/save", api.SaveGoods) 27 | v1.GET("/goods/:id", api.GetGoods) 28 | // v1.GET("/goods", api.FindGoodsPage) 29 | v1.GET("/goods/groups", api.FindGoodsGroupByType) 30 | v1.DELETE("/goods/:id", api.DeleteGoods) 31 | // 下架商品 32 | v1.GET("/goods/off/:id", api.ChangeStatusOff) 33 | // 上架商品 34 | v1.GET("/goods/on/:id", api.ChangeStatusOn) 35 | //菜品数 36 | v1.GET("/goods/count", api.CountGoods) 37 | 38 | // 品类管理 39 | v1.POST("/goodsType/save", api.SaveGoodsType) 40 | v1.GET("/goodsType/:id", api.GetGoodsType) 41 | v1.GET("/goodsType", api.FindGoodsTypePage) 42 | v1.DELETE("/goodsType/:id", api.DeleteGoodsType) 43 | 44 | // 规格管理 45 | v1.POST("/specification/save", api.SaveSpecification) 46 | v1.GET("/specification/:id", api.GetSpecification) 47 | v1.GET("/specification", api.FindSpecificationPage) 48 | v1.DELETE("/specification/:id", api.DeleteSpecification) 49 | 50 | // 订单管理 51 | v1.POST("/order/save", api.SaveOrder) 52 | v1.GET("/order/:id", api.GetOrder) 53 | v1.GET("/order", api.FindOrderPage) 54 | v1.GET("/order/cancel/:id", api.CancelOrder) 55 | v1.GET("/order/recover/:id", api.RecoverOrder) 56 | v1.GET("/order/pay", api.PayOrder) 57 | v1.GET("/order/count/today", api.CountTodayOrder) 58 | v1.GET("/order/income/today", api.IncomeOrder) 59 | 60 | // 店铺信息 61 | v1.POST("/store/save", api.SaveStore) 62 | v1.GET("/store/:id", api.GetStore) 63 | v1.GET("/store", api.GetStore) 64 | v1.GET("/store/code", api.GenerateInviteCode) 65 | v1.GET("/store/join", api.JoinStore) 66 | v1.POST("/store/apply", api.SaveApplicationForm) 67 | v1.GET("/store/apply", api.GetApplicationForm) 68 | 69 | // 用户信息 70 | v1.POST("/user/save", api.SaveUser) 71 | v1.GET("/user/store", api.FindUserByStore) 72 | v1.GET("/user", api.GetUser) 73 | v1.DELETE("/user/remove/:userId", api.RemoveUserByStore) 74 | v1.GET("/user/role", api.ChangeUserRole) 75 | 76 | // 桌号管理 77 | v1.POST("/desk/save", api.SaveDesk) 78 | v1.GET("/desk", api.FindDeskPage) 79 | v1.GET("/desk/qrcode/:id", api.GetQrcode) 80 | v1.GET("/desk/:id", api.GetDesk) 81 | v1.DELETE("/desk/:id", api.DeleteDesk) 82 | 83 | // 问题反馈 84 | v1.POST("/feedback/save", api.SaveFeedBack) 85 | 86 | } 87 | 88 | common := router.Group("/api/v1") 89 | { 90 | //公共接口 91 | common.GET("/login", api.LoginByWx) 92 | // 审批接口 权限后面逐步完善 93 | common.GET("/apply/pass/:id", api.ApplyApss) 94 | common.GET("/apply/reject/:id", api.ApplyReject) 95 | } 96 | 97 | return router 98 | } 99 | -------------------------------------------------------------------------------- /routes/routers_test.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "log" 9 | "net/http" 10 | "net/http/httptest" 11 | "os" 12 | "testing" 13 | 14 | "github.com/gin-gonic/gin" 15 | "github.com/joho/godotenv" 16 | "github.com/stretchr/testify/assert" 17 | "mtmn.top/fish-service/entity" 18 | ) 19 | 20 | func setup() { 21 | dir, _ := os.Getwd() 22 | fileName := dir + "/../.env" 23 | fmt.Println(fileName) 24 | err := godotenv.Load(fileName) 25 | if err != nil { 26 | log.Fatalf("Error loading .env file") 27 | } 28 | } 29 | 30 | func TestSetupRouter(t *testing.T) { 31 | r := SetupRouter() 32 | // other config 33 | 34 | w := httptest.NewRecorder() 35 | req, _ := http.NewRequest("GET", "/api/v1/ping", nil) 36 | r.ServeHTTP(w, req) 37 | 38 | assert.Equal(t, 200, w.Code) 39 | assert.Equal(t, "pong", w.Body.String()) 40 | } 41 | 42 | type ( 43 | GoodsCase struct { 44 | Goods entity.Goods 45 | ExceptCode int 46 | ExceptRemark string 47 | } 48 | ) 49 | 50 | var testIds []string 51 | 52 | func TestSaveGoods(t *testing.T) { 53 | //用例数据 54 | var caseList []GoodsCase 55 | 56 | goods1 := entity.Goods{Name: "清蒸排骨", Remark: "test", Price: 2} 57 | caseList = append(caseList, GoodsCase{Goods: goods1, ExceptCode: 200, ExceptRemark: "正常数据"}) 58 | 59 | goods2 := entity.Goods{Name: "红烧牛肉面", Remark: "test", Price: -1} 60 | caseList = append(caseList, GoodsCase{Goods: goods2, ExceptCode: 400, ExceptRemark: "价格不能为负数"}) 61 | 62 | goods3 := entity.Goods{Name: "炒时蔬", Remark: "test", Price: 10000} 63 | caseList = append(caseList, GoodsCase{Goods: goods3, ExceptCode: 400, ExceptRemark: "价格不能超过1万,禁止杀猪"}) 64 | 65 | goods4 := entity.Goods{Name: "水煮活鱼", Remark: "test", Price: 10} 66 | goods4.Specification = []entity.Specification{{Name: "微辣"}, {Name: "麻辣"}, {Name: "变态辣"}} 67 | caseList = append(caseList, GoodsCase{Goods: goods4, ExceptCode: 200, ExceptRemark: "价格不能为负数"}) 68 | 69 | setup() 70 | r := SetupRouter() 71 | 72 | for _, item := range caseList { 73 | w := PostJson("/api/v1/goods/save", item.Goods, r) 74 | log.Println("body:", w.Body) 75 | assert.Equal(t, item.ExceptCode, w.Code) 76 | bodyBytes, _ := ioutil.ReadAll(w.Body) 77 | var body map[string]string 78 | json.Unmarshal(bodyBytes, &body) 79 | id := body["id"] 80 | if id != "" { 81 | testIds = append(testIds, id) 82 | } 83 | 84 | } 85 | log.Println(testIds) 86 | } 87 | 88 | func TestFindGoodsOne(t *testing.T) { 89 | r := SetupRouter() 90 | 91 | w := httptest.NewRecorder() 92 | req, _ := http.NewRequest("GET", "/api/v1/goods/"+testIds[0], nil) 93 | r.ServeHTTP(w, req) 94 | type Rep struct { 95 | Code int `json:"code" ` 96 | Msg string `json:"msg" ` 97 | Data entity.Goods `json:"data" ` 98 | } 99 | var re Rep 100 | bodyBytes, _ := ioutil.ReadAll(w.Body) 101 | log.Println(bodyBytes) 102 | json.Unmarshal(bodyBytes, &re) 103 | log.Println(re) 104 | assert.Equal(t, 200, w.Code) 105 | assert.Equal(t, testIds[0], re.Data.Id) 106 | } 107 | 108 | func TestFindGoodsPage(t *testing.T) { 109 | r := SetupRouter() 110 | 111 | w := httptest.NewRecorder() 112 | req, _ := http.NewRequest("GET", "/api/v1/goods", nil) 113 | 114 | q := req.URL.Query() 115 | q.Add("page", "1") 116 | q.Add("size", "10") 117 | req.URL.RawQuery = q.Encode() 118 | r.ServeHTTP(w, req) 119 | type Rep struct { 120 | Code int `json:"code" ` 121 | Msg string `json:"msg" ` 122 | Data []entity.Goods `json:"data" ` 123 | Total int `json:"total" ` 124 | Page int `json:"page" ` 125 | } 126 | var re Rep 127 | bodyBytes, _ := ioutil.ReadAll(w.Body) 128 | json.Unmarshal(bodyBytes, &re) 129 | log.Println(re) 130 | assert.Equal(t, 200, w.Code) 131 | //查询条数应大于0 132 | assert.Less(t, 0, re.Total) 133 | } 134 | 135 | func TestDeleteGoods(t *testing.T) { 136 | r := SetupRouter() 137 | 138 | w := httptest.NewRecorder() 139 | 140 | type Rep struct { 141 | Code int `json:"code" ` 142 | Msg string `json:"msg" ` 143 | Count int `json:"count" ` 144 | } 145 | 146 | for _, id := range testIds { 147 | req, _ := http.NewRequest("DELETE", "/api/v1/goods/"+id, nil) 148 | r.ServeHTTP(w, req) 149 | var re Rep 150 | bodyBytes, _ := ioutil.ReadAll(w.Body) 151 | json.Unmarshal(bodyBytes, &re) 152 | log.Println(re) 153 | assert.Equal(t, 200, w.Code) 154 | //查询条数应大于0 155 | assert.Less(t, 0, re.Count) 156 | } 157 | 158 | } 159 | 160 | //PostJson 根据特定请求uri和参数param,以Json形式传递参数,发起post请求返回响应 161 | func PostJson(uri string, param interface{}, router *gin.Engine) *httptest.ResponseRecorder { 162 | jsonByte, err := json.Marshal(param) 163 | if err != nil { 164 | fmt.Println("生成json字符串错误") 165 | } 166 | // 构造post请求,json数据以请求body的形式传递 167 | req := httptest.NewRequest("POST", uri, bytes.NewReader(jsonByte)) 168 | // 初始化响应 169 | w := httptest.NewRecorder() 170 | // 调用相应的handler接口 171 | router.ServeHTTP(w, req) 172 | return w 173 | } 174 | -------------------------------------------------------------------------------- /service/auth.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "time" 8 | 9 | "github.com/medivhzhan/weapp" 10 | "go.mongodb.org/mongo-driver/bson/primitive" 11 | "mtmn.top/fish-service/common" 12 | "mtmn.top/fish-service/entity" 13 | ) 14 | 15 | //后台事件处理方法 16 | func GetTokenByWxCode(code string) (string, error) { 17 | log.Println("code = ", code) 18 | loginResponse, err := OpenIDResolver(code) 19 | if err != nil { 20 | fmt.Println("获取不到openID,code=" + code) 21 | return "", fmt.Errorf("获取不到openID") 22 | } 23 | // //将openID存入数据库,返回对应_id 24 | user := &entity.User{} 25 | common.DB().FindOne("user", "wxOpenid", loginResponse.OpenID).Decode(user) 26 | if user.Id == "" { 27 | //数据库没有找到对应数据,应该将用户信息存储到数据库 28 | user.WxOpenid = loginResponse.OpenID 29 | user.Role = "user" // 默认为普通用户 30 | user.CreateTime = time.Now() 31 | user.UpdateTime = time.Now() 32 | result := common.DB().InsertOne("user", user) 33 | if result.InsertedID != nil { 34 | user.Id = result.InsertedID.(primitive.ObjectID).Hex() 35 | } 36 | } 37 | 38 | //使用accountID生成token 39 | userClaims := &UserClaims{ 40 | UserId: user.Id, 41 | OpenID: user.WxOpenid, 42 | NickName: user.NickName, 43 | Role: user.Role, 44 | StoreId: user.StoreId, 45 | // 微信返回的sessionID 46 | SessionKey: loginResponse.SessionKey, 47 | } 48 | token, err := Sign(userClaims, 3600) 49 | if err != nil { 50 | return "", fmt.Errorf("不能生成token") 51 | } 52 | return token, nil 53 | } 54 | 55 | //将客户端上传的code,和小程序ID和秘钥上传至微信api换取openID 56 | func OpenIDResolver(code string) (*weapp.LoginResponse, error) { 57 | appId := os.Getenv("AppId") 58 | appsecret := os.Getenv("Appsecret") 59 | resp, err := weapp.Login(appId, appsecret, code) 60 | if err != nil { 61 | return &weapp.LoginResponse{}, fmt.Errorf("weapp login: %v", err) 62 | } 63 | return &resp, nil 64 | } 65 | -------------------------------------------------------------------------------- /service/token.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | "time" 8 | 9 | "github.com/dgrijalva/jwt-go" 10 | "github.com/gin-gonic/gin" 11 | ) 12 | 13 | type UserClaims struct { 14 | UserId string 15 | OpenID string 16 | NickName string 17 | Role string 18 | StoreId string 19 | SessionKey string 20 | // StandardClaims结构体实现了Claims接口(Valid()函数) 21 | jwt.StandardClaims 22 | } 23 | 24 | func Sign(claims *UserClaims, ExpiredAt time.Duration) (string, error) { 25 | 26 | expiredAt := time.Now().Add(time.Duration(time.Second) * ExpiredAt).Unix() 27 | 28 | jwtSecretKey := os.Getenv("JwtSecretKey") 29 | 30 | // metadata for your jwt 31 | claims.ExpiresAt = expiredAt 32 | 33 | to := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) 34 | accessToken, err := to.SignedString([]byte(jwtSecretKey)) 35 | 36 | if err != nil { 37 | return accessToken, err 38 | } 39 | 40 | return accessToken, nil 41 | } 42 | 43 | func VerifyTokenHeader(ctx *gin.Context) (*jwt.Token, error) { 44 | tokenHeader := ctx.GetHeader("Authorization") 45 | if tokenHeader == "" { 46 | return nil, fmt.Errorf("Authorization must not empty") 47 | } 48 | 49 | accessToken := strings.SplitAfter(tokenHeader, "Bearer")[1] 50 | jwtSecretKey := os.Getenv("JwtSecretKey") 51 | 52 | token, err := jwt.ParseWithClaims(strings.Trim(accessToken, " "), &UserClaims{}, func(token *jwt.Token) (interface{}, error) { 53 | return []byte(jwtSecretKey), nil 54 | }) 55 | 56 | if err != nil { 57 | return nil, err 58 | } 59 | 60 | return token, nil 61 | } 62 | 63 | func VerifyToken(accessToken string) (*jwt.Token, error) { 64 | jwtSecretKey := os.Getenv("JwtSecretKey") 65 | 66 | token, err := jwt.Parse(accessToken, func(token *jwt.Token) (interface{}, error) { 67 | return []byte(jwtSecretKey), nil 68 | }) 69 | 70 | if err != nil { 71 | return nil, err 72 | } 73 | 74 | return token, nil 75 | } 76 | --------------------------------------------------------------------------------