├── .github └── workflows │ └── push.yml ├── .gitignore ├── .golangci.yml ├── Dockerfile ├── LICENSE ├── Makefile ├── README-cn.md ├── README-devport.md ├── README.md ├── app ├── balance │ ├── README.md │ └── cmd │ │ ├── api │ │ ├── .gitignore │ │ ├── etc │ │ │ └── balance.yaml │ │ ├── internal │ │ │ ├── config │ │ │ │ └── config.go │ │ │ ├── handler │ │ │ │ └── routes.go │ │ │ └── svc │ │ │ │ └── serviceContext.go │ │ └── main.go │ │ ├── pb │ │ ├── balance.pb.go │ │ └── balance.proto │ │ └── rpc │ │ ├── .gitignore │ │ ├── etc │ │ └── balance.yaml │ │ ├── internal │ │ ├── config │ │ │ └── config.go │ │ ├── logic │ │ │ └── balance.go │ │ ├── model │ │ │ ├── balance.go │ │ │ └── balance_change_log.go │ │ ├── server │ │ │ └── balanceServer.go │ │ └── svc │ │ │ └── serviceContext.go │ │ └── main.go ├── community │ ├── README.md │ └── cmd │ │ ├── api │ │ ├── .gitignore │ │ ├── etc │ │ │ └── community.yaml │ │ ├── internal │ │ │ ├── config │ │ │ │ └── config.go │ │ │ ├── handler │ │ │ │ ├── controllers │ │ │ │ │ └── community.go │ │ │ │ └── routes.go │ │ │ ├── logic │ │ │ │ └── pushPostLogic.go │ │ │ └── svc │ │ │ │ └── serviceContext.go │ │ └── main.go │ │ ├── pb │ │ ├── community.pb.go │ │ └── community.proto │ │ └── rpc │ │ ├── .gitignore │ │ ├── etc │ │ └── community.yaml │ │ ├── internal │ │ ├── config │ │ │ └── config.go │ │ ├── model │ │ │ ├── comment.go │ │ │ ├── comment_content.go │ │ │ ├── comment_reply.go │ │ │ ├── post.go │ │ │ ├── post_collection.go │ │ │ ├── post_content.go │ │ │ └── post_star.go │ │ ├── server │ │ │ └── communityServer.go │ │ └── svc │ │ │ └── serviceContext.go │ │ └── main.go ├── lottery │ ├── .gitignore │ ├── Dockerfile │ ├── LICENSE │ ├── Makefile │ ├── README.md │ ├── api │ │ ├── buf.lock │ │ ├── buf.yaml │ │ ├── lottery │ │ │ └── v1 │ │ │ │ ├── lottery.pb.go │ │ │ │ ├── lottery.pb.gw.go │ │ │ │ ├── lottery.pb.validate.go │ │ │ │ ├── lottery.proto │ │ │ │ └── lottery_grpc.pb.go │ │ └── openapi.yaml │ ├── buf.gen.yaml │ ├── buf.work.yaml │ ├── cmd │ │ ├── main.go │ │ ├── wire.go │ │ └── wire_gen.go │ ├── etc │ │ └── config.yaml │ ├── go.mod │ ├── go.sum │ └── internal │ │ ├── README.md │ │ ├── config │ │ └── config.go │ │ ├── server │ │ ├── grpc.go │ │ ├── http.go │ │ └── server.go │ │ ├── service │ │ ├── lottery.go │ │ └── service.go │ │ └── svc │ │ └── serviceContext.go ├── merchants │ ├── README.md │ └── cmd │ │ ├── api │ │ ├── .gitignore │ │ ├── etc │ │ │ └── merchants.yaml │ │ ├── internal │ │ │ ├── config │ │ │ │ └── config.go │ │ │ ├── handler │ │ │ │ ├── controllers │ │ │ │ │ └── merchants.go │ │ │ │ └── routes.go │ │ │ ├── logic │ │ │ │ └── merchants.go │ │ │ └── svc │ │ │ │ └── serviceContext.go │ │ └── main.go │ │ ├── pb │ │ ├── merchants.pb.go │ │ └── merchants.proto │ │ └── rpc │ │ ├── .gitignore │ │ ├── etc │ │ └── merchants.yaml │ │ ├── internal │ │ ├── config │ │ │ └── config.go │ │ ├── logic │ │ │ └── merchants.go │ │ ├── model │ │ │ └── merchant.go │ │ ├── server │ │ │ └── merchantServer.go │ │ └── svc │ │ │ └── serviceContext.go │ │ └── main.go ├── mqueue │ └── cmd │ │ └── job │ │ ├── .gitignore │ │ ├── README.md │ │ ├── etc │ │ └── config.yaml │ │ ├── internal │ │ ├── config │ │ │ └── config.go │ │ ├── logic │ │ │ └── routes.go │ │ ├── svc │ │ │ ├── asynqServer.go │ │ │ └── serverContext.go │ │ └── types │ │ │ └── jobtype.go │ │ └── main.go └── user │ ├── README.md │ └── cmd │ ├── api │ ├── .gitignore │ ├── etc │ │ └── user.yaml │ ├── internal │ │ ├── config │ │ │ └── config.go │ │ ├── handler │ │ │ ├── controllers │ │ │ │ └── user.go │ │ │ └── routes.go │ │ ├── logic │ │ │ └── user.go │ │ └── svc │ │ │ └── serviceContext.go │ └── main.go │ ├── buf.gen.yaml │ ├── pb │ ├── buf.yaml │ ├── user.pb.go │ ├── user.proto │ └── user_grpc.pb.go │ └── rpc │ ├── .gitignore │ ├── etc │ └── user.yaml │ ├── internal │ ├── config │ │ └── config.go │ ├── logic │ │ └── user.go │ ├── model │ │ └── user.go │ ├── server │ │ └── userServer.go │ └── svc │ │ └── serviceContext.go │ └── main.go ├── build └── .gitignore ├── common ├── README.md ├── conf │ ├── api.go │ ├── db.go │ ├── jwt.go │ ├── redis.go │ └── viper.go ├── errcode │ ├── error.go │ └── pb │ │ ├── error.pb.go │ │ └── error.proto ├── functions.go ├── middleware │ ├── auth.go │ └── cors.go └── response │ └── response.go ├── data └── .gitkeep ├── deployments ├── .gitignore ├── apisix_conf │ └── config.yaml ├── apisix_log │ └── .keep ├── dashboard_conf │ └── conf.yaml ├── docker-compose-alpine.yml ├── docker-compose-arm64.yml ├── docker-compose.yml ├── etcd_conf │ └── etcd.conf.yml ├── grafana_conf │ ├── config │ │ └── grafana.ini │ ├── dashboards │ │ └── apisix-grafana-dashboard.json │ └── provisioning │ │ ├── dashboards │ │ └── all.yaml │ │ └── datasources │ │ └── all.yaml ├── mkcert │ ├── README.md │ ├── lvh.me+1-key.pem │ ├── lvh.me+1.pem │ ├── rootCA-key.pem │ └── rootCA.pem ├── prometheus_conf │ └── prometheus.yml ├── sql │ ├── mall_go_balance.sql │ ├── mall_go_community.sql │ ├── mall_go_merchants.sql │ └── mall_go_user.sql └── upstream │ ├── web1.conf │ └── web2.conf ├── docker-compose-env.yml ├── docker-compose.yml ├── docs ├── README.md └── mrpcreadme.md ├── go.mod ├── go.sum └── pkg ├── .gitkeep ├── convert ├── convert.go └── convert_test.go ├── di ├── logrus.go ├── server.go └── zap.go ├── gorm.go ├── hash ├── hash.go └── hash_test.go ├── jwtx └── jwtx.go ├── lock └── lock.go ├── redis.go ├── session.go ├── uuid ├── uuid.go └── uuid_test.go ├── validator └── validator.go └── xsql.go /.github/workflows/push.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | jobs: 4 | 5 | build: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v3 9 | 10 | - name: Set up Go 11 | uses: actions/setup-go@v3 12 | with: 13 | go-version: 1.18 14 | 15 | - name: Build 16 | run: docker-compose build 17 | 18 | - name: Test 19 | run: docker-compose up -d -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # goland project files 2 | .idea 3 | .vscode 4 | # netbeans project files 5 | nbproject 6 | 7 | # zend studio for eclipse project files 8 | .buildpath 9 | .project 10 | .settings 11 | 12 | # windows thumbnail cache 13 | Thumbs.db 14 | 15 | # Mac DS_Store Files 16 | .DS_Store 17 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | linters-settings: 2 | golint: 3 | min-confidence: 0.9 4 | gci: 5 | max-linelength: 120 6 | 7 | linters: 8 | enable: 9 | - gci 10 | - golint 11 | disable-all: true 12 | 13 | rules: 14 | # Rule 1: 接口名称使用大驼峰 15 | - id: interface-names 16 | pattern: '^[A-Z][a-zA-Z0-9]*$' 17 | messages: 18 | - "Interface names should use UpperCamelCase." 19 | 20 | # Rule 2: 方法名 public使用大驼峰 private使用小驼峰 21 | - id: method-names 22 | pattern: '(^[A-Z][a-zA-Z0-9]*$)|(^[a-z][a-zA-Z0-9]*$)' 23 | messages: 24 | - "Public method names should use UpperCamelCase, private method names should use lowerCamelCase." 25 | 26 | # Rule 3: 结构体命名 model使用大驼峰 request使用大驼峰 reply使用大驼峰 27 | - id: struct-names 28 | pattern: '(Model$)|(Request$)|(Reply$)' 29 | messages: 30 | - "Struct names should use UpperCamelCase. Models should end with 'Model', requests should end with 'Request', and replies should end with 'Reply'." 31 | 32 | # Rule 4: 变量名称 使用小驼峰 33 | - id: variable-names 34 | pattern: '^[a-z][a-zA-Z0-9]*$' 35 | messages: 36 | - "Variable names should use lowerCamelCase." 37 | 38 | # Rule 5: 常量名称 全大写加下划线分割单词 39 | - id: constant-names 40 | pattern: '^[A-Z][_A-Z0-9]*$' 41 | messages: 42 | - "Constant names should be in uppercase with underscore-separated words." 43 | 44 | # Rule 6: slice 使用 var a []string 不用 a := []string{} 45 | - id: slice-declaration 46 | pattern: '.*:=\s*\[\]([a-zA-Z]+)\{\}' 47 | messages: 48 | - "Use 'var a []Type' instead of 'a := []Type{}' for slice declaration." 49 | 50 | # Rule 7: map初始化方式 m := make(map[string]interface{}) 51 | - id: map-initialization 52 | pattern: '.*:=\s*make\(map\[([a-zA-Z]+)\]([a-zA-Z]+)\)' 53 | messages: 54 | - "Use 'm := make(map[KeyType]ValueType)' for map initialization." 55 | 56 | # Exclude vendor directory from linting 57 | - exclude: 58 | - "**/vendor/**" 59 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.20-alpine AS builder 2 | 3 | LABEL stage=gobuilder 4 | 5 | ARG path=/app/user/cmd/api 6 | 7 | ENV CGO_ENABLED 0 8 | ENV GOPROXY https://goproxy.cn,direct 9 | 10 | RUN apk update --no-cache && apk add --no-cache tzdata 11 | 12 | WORKDIR /build 13 | 14 | COPY . . 15 | 16 | RUN go mod download 17 | 18 | RUN go build -ldflags="-s -w" -o /build .${path}/main.go 19 | 20 | 21 | FROM alpine 22 | 23 | RUN apk update --no-cache && apk add --no-cache ca-certificates 24 | 25 | COPY --from=builder /usr/share/zoneinfo/Asia/Shanghai /usr/share/zoneinfo/Asia/Shanghai 26 | ENV TZ Asia/Shanghai 27 | 28 | WORKDIR /app 29 | 30 | ARG path=/app/user/cmd/api 31 | 32 | COPY --from=builder /build/main /app/bin/main 33 | 34 | COPY ${path}/etc /app/etc 35 | 36 | CMD ["./bin/main", "-f", "./etc/config.yaml"] 37 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | 3 | default: user-api user-rpc balance-api balance-rpc coupon-api coupon-rpc delivery-api delivery-rpc fresh-api fresh-rpc goods-api goods-rpc im-api im-rpc installment-api installment-rpc lottery-api lottery-rpc merchants-api merchants-rpc order-api order-rpc payment-api payment-rpc raise-api raise-rpc seconds-api seconds-rpc spellgroup-api spellgroup-rpc community-api community-rpc 4 | 5 | user-api: 6 | # 编译 user-api 7 | go build -ldflags="-s -w" -o build/user-api app/user/cmd/api/main.go 8 | user-rpc: 9 | # 编译 user-rpc 10 | go build -ldflags="-s -w" -o build/user-rpc app/user/cmd/rpc/main.go 11 | 12 | balance-api: 13 | # 编译 balance-api 14 | go build -ldflags="-s -w" -o build/balance-api app/balance/cmd/api/main.go 15 | balance-rpc: 16 | # 编译 balance-rpc 17 | go build -ldflags="-s -w" -o build/balance-rpc app/balance/cmd/rpc/main.go 18 | 19 | coupon-api: 20 | # 编译 coupon-api 21 | go build -ldflags="-s -w" -o build/coupon-api app/coupon/cmd/api/main.go 22 | coupon-rpc: 23 | # 编译 coupon-rpc 24 | go build -ldflags="-s -w" -o build/coupon-rpc app/coupon/cmd/rpc/main.go 25 | 26 | delivery-api: 27 | # 编译 delivery-api 28 | go build -ldflags="-s -w" -o build/delivery-api app/delivery/cmd/api/main.go 29 | delivery-rpc: 30 | # 编译 delivery-rpc 31 | go build -ldflags="-s -w" -o build/delivery-rpc app/delivery/cmd/rpc/main.go 32 | 33 | fresh-api: 34 | # 编译 fresh-api 35 | go build -ldflags="-s -w" -o build/fresh-api app/fresh/cmd/api/main.go 36 | fresh-rpc: 37 | # 编译 fresh-rpc 38 | go build -ldflags="-s -w" -o build/fresh-rpc app/fresh/cmd/rpc/main.go 39 | 40 | goods-api: 41 | # 编译 goods-api 42 | go build -ldflags="-s -w" -o build/goods-api app/goods/cmd/api/main.go 43 | goods-rpc: 44 | # 编译 goods-rpc 45 | go build -ldflags="-s -w" -o build/goods-rpc app/goods/cmd/rpc/main.go 46 | 47 | im-api: 48 | # 编译 im-api 49 | go build -ldflags="-s -w" -o build/im-api app/im/cmd/api/main.go 50 | im-rpc: 51 | # 编译 im-rpc 52 | go build -ldflags="-s -w" -o build/im-rpc app/im/cmd/rpc/main.go 53 | 54 | installment-api: 55 | # 编译 installment-api 56 | go build -ldflags="-s -w" -o build/installment-api app/installment/cmd/api/main.go 57 | installment-rpc: 58 | # 编译 installment-rpc 59 | go build -ldflags="-s -w" -o build/installment-rpc app/installment/cmd/rpc/main.go 60 | 61 | lottery-api: 62 | # 编译 lottery-api 63 | go build -ldflags="-s -w" -o build/lottery-api app/lottery/cmd/api/main.go 64 | lottery-rpc: 65 | # 编译 lottery-rpc 66 | go build -ldflags="-s -w" -o build/lottery-rpc app/lottery/cmd/rpc/main.go 67 | 68 | merchants-api: 69 | # 编译 merchants-api 70 | go build -ldflags="-s -w" -o build/merchants-api app/merchants/cmd/api/main.go 71 | merchants-rpc: 72 | # 编译 merchants-rpc 73 | go build -ldflags="-s -w" -o build/merchants-rpc app/merchants/cmd/rpc/main.go 74 | 75 | order-api: 76 | # 编译 order-api 77 | go build -ldflags="-s -w" -o build/order-api app/order/cmd/api/main.go 78 | order-rpc: 79 | # 编译 order-rpc 80 | go build -ldflags="-s -w" -o build/order-rpc app/order/cmd/rpc/main.go 81 | 82 | payment-api: 83 | # 编译 payment-api 84 | go build -ldflags="-s -w" -o build/payment-api app/payment/cmd/api/main.go 85 | payment-rpc: 86 | # 编译 payment-rpc 87 | go build -ldflags="-s -w" -o build/payment-rpc app/payment/cmd/rpc/main.go 88 | 89 | raise-api: 90 | # 编译 raise-api 91 | go build -ldflags="-s -w" -o build/raise-api app/raise/cmd/api/main.go 92 | raise-rpc: 93 | # 编译 raise-rpc 94 | go build -ldflags="-s -w" -o build/raise-rpc app/raise/cmd/rpc/main.go 95 | 96 | seconds-api: 97 | # 编译 seconds-api 98 | go build -ldflags="-s -w" -o build/seconds-api app/seconds/cmd/api/main.go 99 | seconds-rpc: 100 | # 编译 seconds-rpc 101 | go build -ldflags="-s -w" -o build/seconds-rpc app/seconds/cmd/rpc/main.go 102 | 103 | spellgroup-api: 104 | # 编译 spellgroup-api 105 | go build -ldflags="-s -w" -o build/spellgroup-api app/spellgroup/cmd/api/main.go 106 | spellgroup-rpc: 107 | # 编译 spellgroup-rpc 108 | go build -ldflags="-s -w" -o build/spellgroup-rpc app/spellgroup/cmd/rpc/main.go 109 | 110 | community-api: 111 | # 编译 spellgroup-api 112 | go build -ldflags="-s -w" -o build/community-api app/community/cmd/api/main.go 113 | community-rpc: 114 | # 编译 spellgroup-rpc 115 | go build -ldflags="-s -w" -o build/community-rpc app/community/cmd/rpc/main.go 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /README-cn.md: -------------------------------------------------------------------------------- 1 | # mall-go 2 | 3 | 从开始工作至今一直想写一款功能全面(市面上热门APP的主要功能)的一款开源项目 (挖个坑) 4 | 5 | 既然是开源项目肯定所有的技术都得用最新的mixgo脚手架 微服务 容器部署 DTM分布式事务 等等 (不定时填坑 6 | 7 | # 使用技术 8 | - gin、hertz 9 | - grpc、kitex 10 | - redis 11 | - mysql 12 | - mongodb 13 | - asynq | go-queue | amqp 14 | - es | gofound | zinc 15 | - prometheus 16 | - grafana 17 | - jaeger 18 | - dtm 19 | - apisix 20 | - wechat/alipay 21 | - zap 22 | - viper 23 | - docker/docker-compose/kubernetes 24 | 25 | # 业务功能 26 | - [x] 会员 27 | - TODO 充值 28 | - [x] 余额 29 | - [x] 多商户 30 | - TODO 提现 31 | - [ ] 社区⌛ 32 | - [ ] 分期 33 | - [ ] 众筹 34 | - [ ] 秒杀 35 | - [ ] 拼团 36 | - [ ] 抽奖 37 | - [ ] 配送 38 | - [ ] 生鲜 39 | - [ ] 优惠券 40 | - [ ] 二手交易 41 | - [ ] IM 42 | - [ ] 直播 43 | - [ ] 悬赏 44 | 45 | 46 | 47 | # 项目简介 48 | 49 | 项目基于mixgo搭建脚手架,可实现灵活拼装组件,现目前为客户端api接口,暂不会实现admin相关代码 50 | 51 | # 目录介绍 52 | 53 | - app: 所有业务代码包含api、rpc以及mq(消息队列、延迟队列、定时任务) 54 | - common: 通用组件 error、middleware、interceptor、tool、ctxdata等 55 | - data: 产生的数据 56 | - deployments: 项目系列配置文件 57 | - docs: 项目系列文档 58 | - pkg: 内部package 59 | 60 | # 网关 61 | 62 | apisix做外网关 网关前面是slb 63 | 64 | # 开发模式 65 | 66 | 本项目使用的是微服务开发,api (http) + rpc(grpc) , api充当聚合服务,复杂、涉及到其他业务调用的统一写在rpc中,如果一些不会被其他服务依赖使用的简单业务,可以直接写在api的logic中 67 | 68 | # 日志 69 | - logstash 70 | - filebeat 71 | 72 | # 监控 73 | 74 | - prometheus 75 | 76 | # 链路追踪 77 | 78 | - jaeger 79 | 80 | # 发布订阅 81 | 82 | - kafka 83 | - mq 84 | 85 | # 消息队列、延迟队列、定时任务 86 | 87 | - 消息队列 88 | - asynq 89 | - amqp 90 | - 延迟队列 91 | - asnyq 92 | - amqp 93 | - 定时任务 94 | - cron 95 | 96 | # 分布式事务 97 | 98 | - dtm 99 | 100 | # 部署 101 | 102 | 本项目开发环境推荐docker-compose,使用直链方式,放弃服务注册发现中间件(etcd、nacos、consul等)带来的麻烦 103 | 104 | 测试、线上部署使用k8s(也不需要etcd、nacos、consul等) 105 | 106 | # License 107 | 108 | Apache License Version 2.0, http://www.apache.org/licenses/ -------------------------------------------------------------------------------- /README-devport.md: -------------------------------------------------------------------------------- 1 | dev port 2 | 3 | 4 | service port 5 | 6 | | service name | api service port(1xxx) | rpc service port(2xxx) | other service port(3xxx) | 7 | |--------------|------------------------|------------------------|--------------------------| 8 | | user | 10001 | 10002 | | 9 | | balance | 10011 | 10012 | | 10 | | coupon | 10021 | 10022 | | 11 | | delivery | 10031 | 10032 | | 12 | | fresh | 10041 | 10042 | | 13 | | goods | 10051 | 10052 | | 14 | | im | 10061 | 10062 | | 15 | | installment | 10071 | 10072 | | 16 | | lottery | 10081 | 10082 | | 17 | | merchants | 10091 | 10092 | | 18 | | order | 10101 | 10102 | | 19 | | payment | 10111 | 10112 | | 20 | | raise | 10121 | 10122 | | 21 | | seconds | 10131 | 10132 | | 22 | | spellgroup | 10141 | 10142 | | 23 | | community | 10151 | 10152 | | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mall-go 2 | 3 | [中文](./README-cn.md) 4 | 5 | Always wanted to complete a fully functional open source project 6 | 7 | Decided to develop a full-featured open source project in Go 8 | 9 | # Use of technology 10 | - gin、hertz 11 | - grpc、kitex 12 | - redis 13 | - mysql 14 | - mongodb 15 | - asynq | go-queue 16 | - amqp 17 | - elasticsearch | gofound | zinc 18 | - prometheus 19 | - grafana 20 | - jaeger 21 | - dtm 22 | - apisix 23 | - wechat/alipay 24 | - zap 25 | - viper 26 | - docker/docker-compose/kubernetes 27 | 28 | # Service Functions 29 | - [x] members 30 | - TODO recharge 31 | - [x] balance 32 | - [x] multi-merchant 33 | - TODO withdraw 34 | - [ ] community⌛ 35 | - [ ] installments 36 | - [ ] crowdfunding 37 | - [ ] spike 38 | - [ ] group buy 39 | - [ ] lottery 40 | - [ ] delivery 41 | - [ ] fresh 42 | - [ ] coupon 43 | - [ ] second-hand transaction ? trade old things 44 | - [ ] IM 45 | - [ ] live streaming 46 | - [ ] reward 47 | 48 | 49 | 50 | # Project Description 51 | 52 | The project builds scaffolding based on mixgo, which can realize flexible assembly of components. It is currently the client API interface, and admin related codes will not be implemented for the time being. 53 | 54 | # Catalog introduction 55 | 56 | - app: Business code Include api grpc mq job 57 | - common: common components error、middleware、interceptor、tool、ctxdata 58 | - data: runtime data 59 | - deployments: Deploy related configuration files 60 | - docs: Project Series Documentation 61 | - pkg: internal package 62 | 63 | # Gateway 64 | 65 | The front is slb followed by apisix 66 | 67 | # Development mode 68 | 69 | Use the microservice development pattern. api(http) --- rpc(grpc) 70 | 71 | rpc provides basic service implementation. 72 | 73 | api implements service aggregation business processing. 74 | 75 | 76 | # Log 77 | 78 | - logstash 79 | - filebeat 80 | 81 | # Monitor 82 | 83 | - prometheus 84 | 85 | # Track 86 | 87 | - jaeger 88 | 89 | # pub/sub 90 | 91 | - kafka 92 | - mq 93 | 94 | # Message queue、Delay queue、Timed task 95 | 96 | - message queue 97 | - asynq 98 | - amqp 99 | - delay queue 100 | - asnyq 101 | - amqp 102 | - timed task 103 | - cron 104 | 105 | # Distributed transaction 106 | 107 | - dtm 108 | 109 | # Deployment 110 | 111 | develop use docker/docker-compose 112 | 113 | deployment use kubernetes 114 | 115 | 116 | # TODO 117 | 118 | 1. Add grpc checksum 119 | 2. permission check 120 | 3. grpc error handling 121 | 122 | 123 | # License 124 | 125 | Apache License Version 2.0, http://www.apache.org/licenses/ 126 | -------------------------------------------------------------------------------- /app/balance/README.md: -------------------------------------------------------------------------------- 1 | # 余额 -------------------------------------------------------------------------------- /app/balance/cmd/api/.gitignore: -------------------------------------------------------------------------------- 1 | # goland project files 2 | .idea 3 | 4 | # netbeans project files 5 | nbproject 6 | 7 | # zend studio for eclipse project files 8 | .buildpath 9 | .project 10 | .settings 11 | 12 | # windows thumbnail cache 13 | Thumbs.db 14 | 15 | # Mac DS_Store Files 16 | .DS_Store 17 | -------------------------------------------------------------------------------- /app/balance/cmd/api/etc/balance.yaml: -------------------------------------------------------------------------------- 1 | Name: balance-api 2 | Addr: ":10011" 3 | Mode: "release" 4 | 5 | JwtAuth: 6 | Secret: mall-go 7 | 8 | BalanceRpcConf: 9 | Target: "balance-rpc:10012" 10 | Timeout: 60 -------------------------------------------------------------------------------- /app/balance/cmd/api/internal/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/mix-plus/go-mixplus/mrpc" 5 | "mall-go/common/conf" 6 | ) 7 | 8 | type Config struct { 9 | conf.ApiConf `mapstructure:",squash"` 10 | conf.JwtAuth 11 | BalanceRpcConf mrpc.RpcClientConf 12 | } 13 | -------------------------------------------------------------------------------- /app/balance/cmd/api/internal/handler/routes.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/gin-gonic/gin/binding" 6 | "mall-go/app/balance/cmd/api/internal/svc" 7 | "mall-go/common/middleware" 8 | validator2 "mall-go/pkg/validator" 9 | ) 10 | 11 | func Load(router *gin.Engine) { 12 | // register validate 13 | binding.Validator = new(validator2.DefaultValidator) 14 | router.GET("/", func(ctx *gin.Context) { 15 | ctx.String(200, "hello") 16 | }) 17 | router.Use(gin.Recovery(), middleware.CorsMiddleware(), middleware.AuthMiddleware(svc.Context.Jwt.Secret)) 18 | } 19 | -------------------------------------------------------------------------------- /app/balance/cmd/api/internal/svc/serviceContext.go: -------------------------------------------------------------------------------- 1 | package svc 2 | 3 | import ( 4 | "github.com/mix-plus/go-mixplus/mrpc" 5 | "mall-go/app/balance/cmd/api/internal/config" 6 | "mall-go/app/balance/cmd/pb" 7 | "mall-go/pkg/jwtx" 8 | ) 9 | 10 | var Context *ServiceContext 11 | 12 | type ServiceContext struct { 13 | Config config.Config 14 | Jwt *jwtx.Jwt 15 | 16 | BalanceRpc pb.BalanceClient 17 | } 18 | 19 | func NewServiceContext(c config.Config) *ServiceContext { 20 | return &ServiceContext{ 21 | Config: c, 22 | Jwt: jwtx.NewJwt(c.JwtAuth), 23 | BalanceRpc: pb.NewBalanceClient(mrpc.MustNewClient(c.BalanceRpcConf).Conn()), 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/balance/cmd/api/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "fmt" 7 | "os" 8 | "os/signal" 9 | "strings" 10 | "syscall" 11 | "time" 12 | 13 | "github.com/gin-gonic/gin" 14 | "mall-go/app/balance/cmd/api/internal/config" 15 | "mall-go/app/balance/cmd/api/internal/handler" 16 | "mall-go/app/balance/cmd/api/internal/svc" 17 | conf "mall-go/common/conf" 18 | "mall-go/pkg/di" 19 | _ "mall-go/pkg/di" 20 | ) 21 | 22 | var configFile = flag.String("f", "etc/balance.yaml", "the config file") 23 | 24 | func main() { 25 | flag.Parse() 26 | 27 | var c config.Config 28 | 29 | if err := conf.MustLoad(*configFile, &c); err != nil { 30 | panic(err) 31 | } 32 | 33 | logger := di.Zap() 34 | server := di.Server() 35 | 36 | svc.Context = svc.NewServiceContext(c) 37 | 38 | gin.SetMode(c.Mode) 39 | 40 | router := gin.New() 41 | if c.Mode != gin.ReleaseMode { 42 | handlerFunc := gin.LoggerWithConfig(gin.LoggerConfig{ 43 | Formatter: func(params gin.LogFormatterParams) string { 44 | return fmt.Sprintf("%s|%s|%d|%s\n", 45 | params.Method, 46 | params.Path, 47 | params.StatusCode, 48 | params.ClientIP, 49 | ) 50 | }, 51 | Output: &di.ZapOutput{Logger: logger}, 52 | }) 53 | router.Use(handlerFunc) 54 | } 55 | 56 | server.Addr = c.Addr 57 | server.Handler = router 58 | 59 | handler.Load(router) 60 | 61 | // signal 62 | ch := make(chan os.Signal) 63 | signal.Notify(ch, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM) 64 | go func() { 65 | <-ch 66 | logger.Info("Server shutdown") 67 | ctx, _ := context.WithTimeout(context.Background(), 10*time.Second) 68 | if err := server.Shutdown(ctx); err != nil { 69 | logger.Errorf("Server shutdown error: %s", err) 70 | } 71 | }() 72 | 73 | logger.Infof("Server start at %s", server.Addr) 74 | if err := server.ListenAndServe(); err != nil && !strings.Contains(err.Error(), "http: Server closed") { 75 | panic(err) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /app/balance/cmd/pb/balance.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package mall.rpc.balance; 4 | 5 | option go_package = "/;pb"; 6 | 7 | service Balance { 8 | // 获取余额 9 | rpc GetBalance(GetBalanceRequest) returns(GetBalanceResponse) {} 10 | // 增加余额 11 | rpc SubBalance(SubBalanceRequest) returns(SubBalanceResponse) {} 12 | // 减少余额 13 | rpc ReduceBalance(ReduceBalanceRequest) returns(ReduceBalanceResponse) {} 14 | // 增加冻结余额 15 | rpc SubFrozenBalance(SubFrozenBalanceRequest) returns(SubFrozenBalanceResponse) {} 16 | // 减少冻结余额 17 | rpc ReduceFrozenBalance(ReduceFrozenBalanceRequest) returns(ReduceFrozenBalanceResponse) {} 18 | // 余额变动记录 19 | rpc GetBalanceChangeList(GetBalanceChangeListRequest) returns(GetBalanceChangeListResponse) {} 20 | } 21 | 22 | message GetBalanceRequest { 23 | int64 user_id = 1; // 用户id 商户id 24 | int32 type = 2; // 1 用户 2 商户 25 | } 26 | 27 | message GetBalanceResponse { 28 | int64 id = 1; 29 | int64 user_id = 2; 30 | int32 type = 3; 31 | uint64 available = 4; 32 | uint64 frozen = 5; 33 | } 34 | 35 | message SubBalanceRequest { 36 | int64 id = 1; 37 | uint64 amount = 2; 38 | string desc = 3; 39 | } 40 | 41 | message SubBalanceResponse { 42 | bool status = 1; 43 | } 44 | 45 | message ReduceBalanceRequest { 46 | int64 id = 1; 47 | uint64 amount = 2; 48 | string desc = 3; 49 | } 50 | 51 | message ReduceBalanceResponse { 52 | bool status = 1; 53 | } 54 | 55 | message SubFrozenBalanceRequest { 56 | int64 id = 1; 57 | uint64 amount = 2; 58 | string desc = 3; 59 | } 60 | 61 | message SubFrozenBalanceResponse { 62 | bool status = 1; 63 | } 64 | 65 | message ReduceFrozenBalanceRequest { 66 | int64 id = 1; 67 | uint64 amount = 2; 68 | string desc = 3; 69 | } 70 | 71 | message ReduceFrozenBalanceResponse { 72 | bool status = 1; 73 | } 74 | 75 | message GetBalanceChangeListRequest { 76 | int64 id = 1; 77 | int32 type = 2; 78 | int32 type_amount = 3; 79 | int32 page = 4; 80 | int32 page_size = 5; 81 | } 82 | 83 | message GetBalanceChangeListResponse { 84 | repeated GetBalanceChangeListResponseList list = 1; 85 | int64 total_count = 2; 86 | int64 total_page = 3; 87 | } 88 | 89 | message GetBalanceChangeListResponseList { 90 | int64 id = 1; 91 | int64 amount = 2; 92 | int64 before_amount = 3; 93 | int64 after_amount = 4; 94 | int32 type = 5; 95 | int32 type_amount = 6; 96 | string created_at = 7; 97 | } -------------------------------------------------------------------------------- /app/balance/cmd/rpc/.gitignore: -------------------------------------------------------------------------------- 1 | # goland project files 2 | .idea 3 | 4 | # netbeans project files 5 | nbproject 6 | 7 | # zend studio for eclipse project files 8 | .buildpath 9 | .project 10 | .settings 11 | 12 | # windows thumbnail cache 13 | Thumbs.db 14 | 15 | # Mac DS_Store Files 16 | .DS_Store 17 | 18 | .env 19 | -------------------------------------------------------------------------------- /app/balance/cmd/rpc/etc/balance.yaml: -------------------------------------------------------------------------------- 1 | Name: balance-rpc 2 | Addr: ":10012" 3 | Mode: "release" 4 | 5 | RedisConf: 6 | Addr: host.docker.internal:6379 7 | Pass: 8 | DataBase: 0 9 | Timeout: 60 10 | 11 | DbConf: 12 | # host.docker.internal 13 | DSN: root:root@tcp(host.docker.internal:3306)/mall_go_balance?charset=utf8&loc=Asia%2FShanghai&parseTime=true&timeout=10s 14 | -------------------------------------------------------------------------------- /app/balance/cmd/rpc/internal/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/mix-plus/go-mixplus/mrpc" 5 | "mall-go/common/conf" 6 | ) 7 | 8 | type Config struct { 9 | mrpc.RpcServerConf `mapstructure:",squash"` 10 | conf.DbConf 11 | conf.RedisConf 12 | } 13 | -------------------------------------------------------------------------------- /app/balance/cmd/rpc/internal/model/balance.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "time" 7 | 8 | "mall-go/app/balance/cmd/rpc/internal/svc" 9 | ) 10 | 11 | // MallBalance 余额表 12 | type MallBalance struct { 13 | ID int64 `xsql:"id"` 14 | UserId int64 `xsql:"user_id"` 15 | Type int8 `xsql:"type"` // 类型\r\n1 用户\r\n2 商户 16 | Available uint64 `xsql:"available"` // 可用 17 | Frozen uint64 `xsql:"frozen"` // 冻结 18 | Status int64 `xsql:"status"` // 状态 \r\n1 正常\r\n2 冻结 19 | CreatedAt time.Time `xsql:"created_at"` 20 | UpdatedAt time.Time `xsql:"updated_at"` 21 | } 22 | 23 | // TableName 表名称 24 | func (MallBalance) TableName() string { 25 | return "mall_balance" 26 | } 27 | 28 | func (m *MallBalance) FindById(id int64) (*MallBalance, error) { 29 | db := svc.Context.Xsql 30 | var balance MallBalance 31 | err := db.First(&balance, fmt.Sprintf("SELECT * FROM %s WHERE `id` = ? LIMIT 1;", m.TableName()), id) 32 | if err != nil { 33 | return nil, err 34 | } 35 | return &balance, nil 36 | } 37 | 38 | func (m *MallBalance) FindByWhere(column []string, where []string, args []any, opts []string) (*MallBalance, error) { 39 | db := svc.Context.Xsql 40 | var balance MallBalance 41 | sql := fmt.Sprintf("SELECT %s FROM `%s` WHERE %s %s LIMIT 1;", 42 | strings.Join(column, ", "), 43 | m.TableName(), 44 | strings.Join(where, " AND "), 45 | strings.Join(opts, " ")) 46 | err := db.First(&balance, sql, 47 | args..., 48 | ) 49 | 50 | if err != nil { 51 | return nil, err 52 | } 53 | return &balance, nil 54 | } 55 | 56 | func (m *MallBalance) CreateOne(balance *MallBalance) (int64, error) { 57 | db := svc.Context.Xsql 58 | row, err := db.Insert(balance) 59 | if err != nil { 60 | return 0, err 61 | } 62 | return row.LastInsertId() 63 | } 64 | 65 | func (m *MallBalance) CreateAll(balance []*MallBalance) (int64, error) { 66 | db := svc.Context.Xsql 67 | row, err := db.BatchInsert(balance) 68 | if err != nil { 69 | return 0, err 70 | } 71 | return row.LastInsertId() 72 | } 73 | 74 | func (m *MallBalance) GetManyByWhere(column []string, where []string, args []any, opts []string) ([]*MallBalance, error) { 75 | db := svc.Context.Xsql 76 | var balance []*MallBalance 77 | err := db.Find(&balance, fmt.Sprintf("SELECT %s FROM `%s` WHERE %s %s;", 78 | strings.Join(column, ", "), 79 | m.TableName(), 80 | strings.Join(where, " AND "), 81 | strings.Join(opts, " ")), 82 | args..., 83 | ) 84 | if err != nil { 85 | return nil, err 86 | } 87 | return balance, nil 88 | } 89 | 90 | func (m *MallBalance) UpdateByWhere(balance *MallBalance, where []string, args []any) (int64, error) { 91 | db := svc.Context.Xsql 92 | rows, err := db.Update(balance, strings.Join(where, ", "), args...) 93 | if err != nil { 94 | return 0, err 95 | } 96 | return rows.RowsAffected() 97 | } 98 | 99 | func (m *MallBalance) DeleteByWhere(where []string, args []any) (int64, error) { 100 | db := svc.Context.Xsql 101 | result, err := db.Exec(fmt.Sprintf("DELETE FROM `%s` WHERE %s;", 102 | m.TableName(), strings.Join(where, " AND ")), args...) 103 | if err != nil { 104 | return 0, err 105 | } 106 | return result.RowsAffected() 107 | } 108 | -------------------------------------------------------------------------------- /app/balance/cmd/rpc/internal/model/balance_change_log.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "time" 4 | 5 | // MallBalanceChangeLog 余额变动记录表 6 | type MallBalanceChangeLog struct { 7 | ID int64 `xorm:"id"` 8 | BalanceId int64 `xorm:"balance_id"` // 余额ID 9 | Amount int64 `xorm:"amount"` // 变动金额 10 | BeforeAmount int64 `xorm:"before_amount"` // 变动前余额 11 | AfterAmount int64 `xorm:"after_amount"` // 变动后余额 12 | Type int8 `xorm:"type"` // 变动类型 \r\n1 增加\r\n2 减少 13 | TypeAmount int8 `xorm:"type_amount"` // 余额类型\r\n1 可用余额\r\n2 冻结余额 14 | Desc string `xorm:"desc"` // 描述 15 | IsDelete int8 `xorm:"is_delete"` // 是否删除 16 | CreatedAt time.Time `xorm:"created_at"` 17 | UpdatedAt time.Time `xorm:"updated_at"` 18 | } 19 | 20 | const ( 21 | // TypeIncrease 增加余额 22 | TypeIncrease = 1 23 | // TypeReduce 减少余额 24 | TypeReduce = 2 25 | 26 | // TypeAvailable 可用余额 27 | TypeAvailable = 1 28 | // TypeAmountFreeze 冻结余额 29 | TypeAmountFreeze = 2 30 | ) 31 | 32 | // TableName 表名称 33 | func (*MallBalanceChangeLog) TableName() string { 34 | return "mall_balance_change_log" 35 | } 36 | -------------------------------------------------------------------------------- /app/balance/cmd/rpc/internal/server/balanceServer.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | 6 | "mall-go/app/balance/cmd/pb" 7 | "mall-go/app/balance/cmd/rpc/internal/logic" 8 | "mall-go/app/balance/cmd/rpc/internal/svc" 9 | "mall-go/common/errcode" 10 | ) 11 | 12 | type BalanceServer struct { 13 | svcCtx *svc.ServiceContext 14 | logic logic.BalanceLogic 15 | } 16 | 17 | func NewBalanceServer(ctx *svc.ServiceContext) *BalanceServer { 18 | return &BalanceServer{} 19 | } 20 | 21 | func (t *BalanceServer) SubFrozenBalance(ctx context.Context, in *pb.SubFrozenBalanceRequest) (*pb.SubFrozenBalanceResponse, error) { 22 | status, err := t.logic.SubFrozenBalance(in) 23 | if err != nil { 24 | return nil, errcode.GrpcError(err, 102) 25 | } 26 | return &pb.SubFrozenBalanceResponse{ 27 | Status: status, 28 | }, nil 29 | } 30 | 31 | func (t *BalanceServer) ReduceFrozenBalance(ctx context.Context, in *pb.ReduceFrozenBalanceRequest) (*pb.ReduceFrozenBalanceResponse, error) { 32 | status, err := t.logic.ReduceFrozenBalance(in) 33 | if err != nil { 34 | return nil, errcode.GrpcError(err, 102) 35 | } 36 | return &pb.ReduceFrozenBalanceResponse{ 37 | Status: status, 38 | }, nil 39 | } 40 | 41 | func (t *BalanceServer) SubBalance(ctx context.Context, in *pb.SubBalanceRequest) (*pb.SubBalanceResponse, error) { 42 | status, err := t.logic.SubBalance(in) 43 | if err != nil { 44 | return nil, errcode.GrpcError(err, 102) 45 | } 46 | return &pb.SubBalanceResponse{ 47 | Status: status, 48 | }, nil 49 | } 50 | 51 | func (t *BalanceServer) ReduceBalance(ctx context.Context, in *pb.ReduceBalanceRequest) (*pb.ReduceBalanceResponse, error) { 52 | status, err := t.logic.ReduceBalance(in) 53 | if err != nil { 54 | return nil, errcode.GrpcError(err, 102) 55 | } 56 | return &pb.ReduceBalanceResponse{ 57 | Status: status, 58 | }, nil 59 | } 60 | 61 | func (t *BalanceServer) GetBalance(ctx context.Context, in *pb.GetBalanceRequest) (*pb.GetBalanceResponse, error) { 62 | balance, err := t.logic.GetBalance(in) 63 | if err != nil { 64 | return nil, errcode.GrpcError(err, 102) 65 | } 66 | return &pb.GetBalanceResponse{ 67 | Id: balance.ID, 68 | UserId: balance.UserId, 69 | Type: int32(balance.Type), 70 | Available: balance.Available, 71 | Frozen: balance.Frozen, 72 | }, nil 73 | } 74 | 75 | func (t *BalanceServer) GetBalanceChangeList(ctx context.Context, in *pb.GetBalanceChangeListRequest) (*pb.GetBalanceChangeListResponse, error) { 76 | result, err := t.logic.GetBalanceChangeList(in) 77 | if err != nil { 78 | return nil, errcode.GrpcError(err, 102) 79 | } 80 | 81 | return &pb.GetBalanceChangeListResponse{ 82 | TotalCount: result.TotalCount, 83 | TotalPage: result.TotalPage, 84 | List: result.List, 85 | }, nil 86 | } 87 | -------------------------------------------------------------------------------- /app/balance/cmd/rpc/internal/svc/serviceContext.go: -------------------------------------------------------------------------------- 1 | package svc 2 | 3 | import ( 4 | "github.com/go-redis/redis" 5 | "github.com/go-redsync/redsync/v4" 6 | "github.com/mix-go/xsql" 7 | "gorm.io/gorm" 8 | "mall-go/app/balance/cmd/rpc/internal/config" 9 | "mall-go/pkg" 10 | "mall-go/pkg/lock" 11 | ) 12 | 13 | var Context *ServiceContext 14 | 15 | type ServiceContext struct { 16 | Config config.Config 17 | 18 | Redis *redis.Client 19 | Db *gorm.DB 20 | Xsql *xsql.DB 21 | RedSync *redsync.Redsync 22 | } 23 | 24 | func NewServiceContext(c config.Config) *ServiceContext { 25 | return &ServiceContext{ 26 | Config: c, 27 | Redis: pkg.NewRedis(c.RedisConf), 28 | Db: pkg.NewGorm(c.DbConf), 29 | Xsql: pkg.NewXsql(c.DbConf), 30 | RedSync: lock.NewLock(c.RedisConf), 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/balance/cmd/rpc/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | 6 | "github.com/mix-plus/go-mixplus/mrpc" 7 | "google.golang.org/grpc" 8 | "mall-go/app/balance/cmd/pb" 9 | "mall-go/app/balance/cmd/rpc/internal/config" 10 | "mall-go/app/balance/cmd/rpc/internal/server" 11 | "mall-go/app/balance/cmd/rpc/internal/svc" 12 | conf "mall-go/common/conf" 13 | _ "mall-go/pkg/di" 14 | ) 15 | 16 | var configFile = flag.String("f", "etc/balance.yaml", "the config file") 17 | 18 | func main() { 19 | flag.Parse() 20 | 21 | var c config.Config 22 | 23 | if err := conf.MustLoad(*configFile, &c); err != nil { 24 | panic(err) 25 | } 26 | 27 | ctx := svc.NewServiceContext(c) 28 | svc.Context = ctx 29 | srv := server.NewBalanceServer(ctx) 30 | 31 | s := mrpc.MustNewServer(c.RpcServerConf, func(g *grpc.Server) { 32 | pb.RegisterBalanceServer(g, srv) 33 | }) 34 | 35 | defer s.Stop() 36 | 37 | s.Start() 38 | } 39 | -------------------------------------------------------------------------------- /app/community/README.md: -------------------------------------------------------------------------------- 1 | # 社区 -------------------------------------------------------------------------------- /app/community/cmd/api/.gitignore: -------------------------------------------------------------------------------- 1 | # goland project files 2 | .idea 3 | 4 | # netbeans project files 5 | nbproject 6 | 7 | # zend studio for eclipse project files 8 | .buildpath 9 | .project 10 | .settings 11 | 12 | # windows thumbnail cache 13 | Thumbs.db 14 | 15 | # Mac DS_Store Files 16 | .DS_Store 17 | -------------------------------------------------------------------------------- /app/community/cmd/api/etc/community.yaml: -------------------------------------------------------------------------------- 1 | Name: community-api 2 | Addr: ":10151" 3 | Mode: release 4 | 5 | JwtAuth: 6 | Secret: mall-go 7 | 8 | CommunityRpcConf: 9 | Target: "community-rpc:10152" 10 | Timeout: 60 -------------------------------------------------------------------------------- /app/community/cmd/api/internal/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/mix-plus/go-mixplus/mrpc" 5 | "mall-go/common/conf" 6 | ) 7 | 8 | type Config struct { 9 | conf.ApiConf `mapstructure:",squash"` 10 | conf.JwtAuth 11 | CommunityRpcConf mrpc.RpcClientConf 12 | } 13 | -------------------------------------------------------------------------------- /app/community/cmd/api/internal/handler/controllers/community.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/cloudwego/hertz/pkg/app" 7 | "mall-go/app/community/cmd/api/internal/svc" 8 | "mall-go/app/community/cmd/pb" 9 | ) 10 | 11 | type CommunityController struct { 12 | } 13 | 14 | func (t *CommunityController) PushPost(ctx context.Context, c *app.RequestContext) { 15 | in := pb.PushPostRequest{} 16 | resp, err := svc.Context.CommunityRpc.PushPost(ctx, &in) 17 | if err != nil { 18 | c.JSON(500, err) 19 | } 20 | 21 | c.JSON(200, resp) 22 | } 23 | -------------------------------------------------------------------------------- /app/community/cmd/api/internal/handler/routes.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/cloudwego/hertz/pkg/app" 8 | "github.com/cloudwego/hertz/pkg/app/server" 9 | "github.com/hertz-contrib/cors" 10 | ) 11 | 12 | func Load(router *server.Hertz) { 13 | router.Use(cors.New(cors.Config{ 14 | AllowOrigins: []string{"https://foo.com"}, // Allowed domains, need to bring schema 15 | AllowMethods: []string{"PUT", "PATCH"}, // Allowed request methods 16 | AllowHeaders: []string{"Origin"}, // Allowed request headers 17 | ExposeHeaders: []string{"Content-Length"}, // Request headers allowed in the upload_file 18 | AllowCredentials: true, // Whether cookies are attached 19 | AllowOriginFunc: func(origin string) bool { // Custom domain detection with lower priority than AllowOrigins 20 | return origin == "https://github.com" 21 | }, 22 | MaxAge: 12 * time.Hour, // Maximum length of upload_file-side cache preflash requests (seconds) 23 | })) 24 | 25 | router.GET("/", func(ctx context.Context, c *app.RequestContext) { 26 | c.String(200, "hello") 27 | }) 28 | //community := controllers.CommunityController{} 29 | //router.GET("/community/list", community.PushPost) 30 | //router.Use(gin.Recovery(), middleware.CorsMiddleware(), middleware.AuthMiddleware()) 31 | } 32 | -------------------------------------------------------------------------------- /app/community/cmd/api/internal/logic/pushPostLogic.go: -------------------------------------------------------------------------------- 1 | package logic 2 | 3 | import ( 4 | "context" 5 | 6 | "mall-go/app/community/cmd/api/internal/svc" 7 | "mall-go/app/community/cmd/pb" 8 | ) 9 | 10 | type PushPostLogic struct { 11 | } 12 | 13 | func (l *PushPostLogic) PushPost(in *pb.PushPostRequest) (*pb.PushPostResponse, error) { 14 | resp, err := svc.Context.CommunityRpc.PushPost(context.Background(), in) 15 | if err != nil { 16 | return nil, err 17 | } 18 | return resp, nil 19 | } 20 | -------------------------------------------------------------------------------- /app/community/cmd/api/internal/svc/serviceContext.go: -------------------------------------------------------------------------------- 1 | package svc 2 | 3 | import ( 4 | "github.com/mix-plus/go-mixplus/mrpc" 5 | "mall-go/app/community/cmd/api/internal/config" 6 | "mall-go/app/community/cmd/pb" 7 | "mall-go/pkg/jwtx" 8 | ) 9 | 10 | var Context *ServiceContext 11 | 12 | type ServiceContext struct { 13 | Config config.Config 14 | Jwt *jwtx.Jwt 15 | 16 | CommunityRpc pb.CommunityClient 17 | } 18 | 19 | func NewServiceContext(c config.Config) *ServiceContext { 20 | return &ServiceContext{ 21 | Config: c, 22 | Jwt: jwtx.NewJwt(c.JwtAuth), 23 | CommunityRpc: pb.NewCommunityClient(mrpc.MustNewClient(c.CommunityRpcConf).Conn()), 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/community/cmd/api/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "fmt" 7 | "time" 8 | 9 | "github.com/bytedance/gopkg/util/logger" 10 | "github.com/cloudwego/hertz/pkg/app" 11 | "github.com/cloudwego/hertz/pkg/app/server" 12 | "mall-go/app/community/cmd/api/internal/config" 13 | "mall-go/app/community/cmd/api/internal/handler" 14 | "mall-go/app/community/cmd/api/internal/svc" 15 | conf "mall-go/common/conf" 16 | _ "mall-go/pkg/di" 17 | ) 18 | 19 | var configFile = flag.String("f", "etc/community.yaml", "the config file") 20 | 21 | func main() { 22 | flag.Parse() 23 | 24 | var c config.Config 25 | 26 | if err := conf.MustLoad(*configFile, &c); err != nil { 27 | panic(err) 28 | } 29 | 30 | svc.Context = svc.NewServiceContext(c) 31 | 32 | s := server.Default( 33 | server.WithHostPorts(c.Addr), 34 | server.WithExitWaitTime(3*time.Second)) 35 | 36 | if c.Mode != "release" { 37 | s.Use(func(c context.Context, ctx *app.RequestContext) { 38 | start := time.Now() 39 | ctx.Next(c) 40 | end := time.Now() 41 | latency := end.Sub(start).Microseconds 42 | logger.Infof(fmt.Sprintf("status=%d cost=%d method=%s full_path=%s client_ip=%s host=%s", ctx.Response.StatusCode(), latency(), ctx.Request.Header.Method(), ctx.Request.URI().PathOriginal(), ctx.ClientIP(), ctx.Request.Host())) 43 | }) 44 | } 45 | 46 | handler.Load(s) 47 | 48 | s.Engine.OnShutdown = append(s.Engine.OnShutdown, func(ctx context.Context) { 49 | logger.Info("Server shutdown") 50 | }) 51 | 52 | s.Spin() 53 | } 54 | -------------------------------------------------------------------------------- /app/community/cmd/pb/community.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package mall.rpc.community; 4 | 5 | option go_package = "/;pb"; 6 | 7 | 8 | service Community { 9 | // 文章列表 10 | rpc GetPostList(GetPostListRequest) returns(GetPostListResponse) {} 11 | // 文章详情 12 | rpc GetPostDetail(GetPostDetailRequest) returns(GetPostDetailResponse) {} 13 | // 发布文章 14 | rpc PushPost(PushPostRequest) returns(PushPostResponse) {} 15 | // 编辑文章 16 | rpc SetPost(SetPostRequest) returns(SetPostResponse) {} 17 | // 删除文章 18 | rpc DeletePost(DeletePostRequest) returns(DeletePostResponse) {} 19 | // 评论列表 20 | rpc GetCommentList(GetCommentListRequest) returns(GetCommentListResponse) {} 21 | // 添加评论 22 | rpc AddComment(AddCommentRequest) returns(AddCommentResponse) {} 23 | // 回复评论 24 | rpc ReplyComment(ReplyCommentRequest) returns(ReplyCommentResponse) {} 25 | // 删除评论 26 | rpc DeleteComment(DeleteCommentRequest) returns(DeleteCommentResponse) {} 27 | // 文章收藏 28 | rpc CollectPost(CollectPostRequest) returns(CollectPostResponse) {} 29 | // 文章收藏列表 30 | rpc GetCollectPostList(GetCollectPostListRequest) returns(GetCollectPostListResponse) {} 31 | // 文章点赞 32 | rpc StarPost(StarPostRequest) returns(StarPostResponse) {} 33 | // 文章点赞列表 34 | rpc GetStarPostList(GetStarPostListRequest) returns(GetStarPostListResponse) {} 35 | } 36 | 37 | message GetPostListRequest { 38 | // 推荐列表 39 | // 关注列表 40 | // 我的发布 41 | } 42 | message GetPostListResponse {} 43 | 44 | message GetPostDetailRequest {} 45 | message GetPostDetailResponse {} 46 | 47 | message PushPostRequest {} 48 | message PushPostResponse {} 49 | 50 | message SetPostRequest {} 51 | message SetPostResponse {} 52 | 53 | message DeletePostRequest {} 54 | message DeletePostResponse {} 55 | 56 | message GetCommentListRequest {} 57 | message GetCommentListResponse {} 58 | 59 | message AddCommentRequest {} 60 | message AddCommentResponse {} 61 | 62 | message ReplyCommentRequest {} 63 | message ReplyCommentResponse {} 64 | 65 | message DeleteCommentRequest {} 66 | message DeleteCommentResponse {} 67 | 68 | message CollectPostRequest {} 69 | message CollectPostResponse {} 70 | 71 | message GetCollectPostListRequest {} 72 | message GetCollectPostListResponse {} 73 | 74 | message StarPostRequest {} 75 | message StarPostResponse {} 76 | 77 | message GetStarPostListRequest {} 78 | message GetStarPostListResponse {} 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /app/community/cmd/rpc/.gitignore: -------------------------------------------------------------------------------- 1 | # goland project files 2 | .idea 3 | 4 | # netbeans project files 5 | nbproject 6 | 7 | # zend studio for eclipse project files 8 | .buildpath 9 | .project 10 | .settings 11 | 12 | # windows thumbnail cache 13 | Thumbs.db 14 | 15 | # Mac DS_Store Files 16 | .DS_Store 17 | 18 | .env 19 | -------------------------------------------------------------------------------- /app/community/cmd/rpc/etc/community.yaml: -------------------------------------------------------------------------------- 1 | Name: community-rpc 2 | Mode: release 3 | Addr: ":10152" 4 | Timeout: 60 5 | 6 | DbConf: 7 | DSN: root:root@tcp(host.docker.internal:3306)/mall_go_community?charset=utf8&loc=Asia%2FShanghai&parseTime=true&timeout=10s 8 | 9 | RedisConf: 10 | Addr: "host.docker.internal:6379" 11 | Pass: 12 | DataBase: 0 13 | Timeout: 60 14 | 15 | -------------------------------------------------------------------------------- /app/community/cmd/rpc/internal/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/mix-plus/go-mixplus/mrpc" 5 | "mall-go/common/conf" 6 | ) 7 | 8 | type Config struct { 9 | mrpc.RpcServerConf `mapstructure:",squash"` 10 | conf.DbConf 11 | conf.RedisConf 12 | CommunityRpcConf mrpc.RpcClientConf 13 | } 14 | -------------------------------------------------------------------------------- /app/community/cmd/rpc/internal/model/comment.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "time" 4 | 5 | // MallComment 评论表 6 | type MallComment struct { 7 | ID int64 `db:"id"` 8 | PostId int64 `db:"post_id"` // 文章ID 9 | UserId int64 `db:"user_id"` // 用户ID 10 | Ip string `db:"ip"` // IP地址 11 | IpLoc string `db:"ip_loc"` // IP城市 12 | CreatedAt time.Time `db:"created_at"` // 创建时间 13 | UpdatedAt time.Time `db:"updated_at"` // 更新时间 14 | IsDelete int8 `db:"is_delete"` // 是否删除 15 | DeletedAt time.Time `db:"deleted_at"` // 删除时间 16 | } 17 | 18 | // TableName 表名称 19 | func (MallComment) TableName() string { 20 | return "mall_comment" 21 | } 22 | -------------------------------------------------------------------------------- /app/community/cmd/rpc/internal/model/comment_content.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "time" 4 | 5 | // MallCommentContent 文章内容表 6 | type MallCommentContent struct { 7 | ID int64 `db:"id"` 8 | CommentId int64 `db:"comment_id"` // 评论ID 9 | UserId int64 `db:"user_id"` // 用户ID 10 | Content string `db:"content"` // 评论内容 11 | Type int8 `db:"type"` // 类型\r\n1. 标题\r\n2. 文章段落\r\n3. 图片地址\r\n4. 视频地址\r\n5. 语音地址\r\n6. 链接地址 12 | Sort int64 `db:"sort"` // 排序 越小越靠前 13 | CreatedAt time.Time `db:"created_at"` // 创建时间 14 | UpdatedAt time.Time `db:"updated_at"` // 更新时间 15 | IsDelete int8 `db:"is_delete"` // 是否删除 16 | DeletedAt time.Time `db:"deleted_at"` // 删除时间 17 | } 18 | 19 | // TableName 表名称 20 | func (MallCommentContent) TableName() string { 21 | return "mall_comment_content" 22 | } 23 | -------------------------------------------------------------------------------- /app/community/cmd/rpc/internal/model/comment_reply.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "time" 4 | 5 | // MallCommentReply 评论回复表 6 | type MallCommentReply struct { 7 | ID int64 `db:"id"` 8 | CommentId int64 `db:"comment_id"` // 评论ID 9 | UserId int64 `db:"user_id"` // 用户ID 10 | AtUserId int64 `db:"at_user_id"` // @用户ID 11 | Content string `db:"content"` // 内容 12 | Ip string `db:"ip"` // IP地址 13 | IpLoc string `db:"ip_loc"` // IP城市 14 | CreatedAt time.Time `db:"created_at"` // 创建时间 15 | UpdatedAt time.Time `db:"updated_at"` // 更新时间 16 | IsDelete int8 `db:"is_delete"` // 是否删除 17 | DeletedAt time.Time `db:"deleted_at"` // 删除时间 18 | } 19 | 20 | // TableName 表名称 21 | func (MallCommentReply) TableName() string { 22 | return "mall_comment_reply" 23 | } 24 | -------------------------------------------------------------------------------- /app/community/cmd/rpc/internal/model/post.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "time" 4 | 5 | // MallPost 文章表 6 | type MallPost struct { 7 | ID int64 `db:"id"` 8 | UserId int64 `db:"user_id"` // 用户ID 9 | CommentCount int64 `db:"comment_count"` // 评论数 10 | UpvoteCount int64 `db:"upvote_count"` // 点赞数 11 | Visibility int8 `db:"visibility"` // 可见性 0公开 1私密 2好友可见 12 | IsTop int8 `db:"is_top"` // 是否置顶 13 | IsEssence int8 `db:"is_essence"` // 是否精华 14 | IsLock int8 `db:"is_lock"` // 是否锁定 15 | LatestRepliedOn int64 `db:"latest_replied_on"` // 最后回复时间 16 | Ip string `db:"ip"` // IP地址 17 | IpLoc string `db:"ip_loc"` // IP城市 18 | CreatedAt time.Time `db:"created_at"` // 创建时间 19 | UpdatedAt time.Time `db:"updated_at"` // 更新时间 20 | IsDelete int8 `db:"is_delete"` // 是否删除 21 | DeletedAt time.Time `db:"deleted_at"` // 删除时间 22 | } 23 | 24 | // TableName 表名称 25 | func (MallPost) TableName() string { 26 | return "mall_post" 27 | } 28 | -------------------------------------------------------------------------------- /app/community/cmd/rpc/internal/model/post_collection.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "time" 4 | 5 | // MallPostCollection 文章收藏表 6 | type MallPostCollection struct { 7 | ID int64 `db:"id"` 8 | PostId int64 `db:"post_id"` // 文章ID 9 | UserId int64 `db:"user_id"` // 用户ID 10 | CreatedAt time.Time `db:"created_at"` // 创建时间 11 | UpdatedAt time.Time `db:"updated_at"` // 更新时间 12 | IsDelete int8 `db:"is_delete"` // 是否删除 13 | DeletedAt time.Time `db:"deleted_at"` // 删除时间 14 | } 15 | 16 | // TableName 表名称 17 | func (MallPostCollection) TableName() string { 18 | return "mall_post_collection" 19 | } 20 | -------------------------------------------------------------------------------- /app/community/cmd/rpc/internal/model/post_content.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "time" 4 | 5 | // MallPostContent 文章内容表 6 | type MallPostContent struct { 7 | ID int64 `db:"id"` 8 | PostId int64 `db:"post_id"` // 文章ID 9 | UserId int64 `db:"user_id"` // 用户ID 10 | Content string `db:"content"` // 内容 11 | Type int8 `db:"type"` // 类型\r\n1. 标题\r\n2. 文字段落\r\n3. 图片地址\r\n4. 视频地址\r\n5. 语音地址\r\n6. 链接地址\r\n7. 附件地址\r\n8. 收费资源\r\n 12 | Sort int64 `db:"sort"` // 排序: 越小越靠前 13 | CreatedAt time.Time `db:"created_at"` // 创建时间 14 | UpdatedAt time.Time `db:"updated_at"` // 更新时间 15 | IsDelete int8 `db:"is_delete"` // 是否删除 16 | DeletedAt time.Time `db:"deleted_at"` // 删除时间 17 | } 18 | 19 | // TableName 表名称 20 | func (MallPostContent) TableName() string { 21 | return "mall_post_content" 22 | } 23 | -------------------------------------------------------------------------------- /app/community/cmd/rpc/internal/model/post_star.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "time" 4 | 5 | // MallPostStar 文章点赞表 6 | type MallPostStar struct { 7 | ID int64 `db:"id"` 8 | PostId int64 `db:"post_id"` // 文章ID 9 | UserId int64 `db:"user_id"` // 用户ID 10 | CreatedAt time.Time `db:"created_at"` // 创建时间 11 | UpdatedAt time.Time `db:"updated_at"` // 更新时间 12 | IsDelete int8 `db:"is_delete"` // 是否删除 13 | DeletedAt time.Time `db:"deleted_at"` // 删除时间 14 | } 15 | 16 | // TableName 表名称 17 | func (MallPostStar) TableName() string { 18 | return "mall_post_star" 19 | } 20 | -------------------------------------------------------------------------------- /app/community/cmd/rpc/internal/server/communityServer.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | 6 | "mall-go/app/community/cmd/pb" 7 | "mall-go/app/community/cmd/rpc/internal/svc" 8 | ) 9 | 10 | type CommunityServer struct { 11 | svcCtx *svc.ServiceContext 12 | } 13 | 14 | func NewCommunityServer(svcCtx *svc.ServiceContext) *CommunityServer { 15 | return &CommunityServer{ 16 | svcCtx: svcCtx, 17 | } 18 | } 19 | 20 | func (s *CommunityServer) GetPostList(ctx context.Context, request *pb.GetPostListRequest) (*pb.GetPostListResponse, error) { 21 | //TODO implement me 22 | return &pb.GetPostListResponse{}, nil 23 | 24 | } 25 | 26 | func (s *CommunityServer) GetPostDetail(ctx context.Context, request *pb.GetPostDetailRequest) (*pb.GetPostDetailResponse, error) { 27 | //TODO implement me 28 | panic("implement me") 29 | } 30 | 31 | func (s *CommunityServer) PushPost(ctx context.Context, request *pb.PushPostRequest) (*pb.PushPostResponse, error) { 32 | //TODO implement me 33 | return &pb.PushPostResponse{}, nil 34 | } 35 | 36 | func (s *CommunityServer) SetPost(ctx context.Context, request *pb.SetPostRequest) (*pb.SetPostResponse, error) { 37 | //TODO implement me 38 | panic("implement me") 39 | } 40 | 41 | func (s *CommunityServer) DeletePost(ctx context.Context, request *pb.DeletePostRequest) (*pb.DeletePostResponse, error) { 42 | //TODO implement me 43 | panic("implement me") 44 | } 45 | 46 | func (s *CommunityServer) GetCommentList(ctx context.Context, request *pb.GetCommentListRequest) (*pb.GetCommentListResponse, error) { 47 | //TODO implement me 48 | panic("implement me") 49 | } 50 | 51 | func (s *CommunityServer) AddComment(ctx context.Context, request *pb.AddCommentRequest) (*pb.AddCommentResponse, error) { 52 | //TODO implement me 53 | panic("implement me") 54 | } 55 | 56 | func (s *CommunityServer) ReplyComment(ctx context.Context, request *pb.ReplyCommentRequest) (*pb.ReplyCommentResponse, error) { 57 | //TODO implement me 58 | panic("implement me") 59 | } 60 | 61 | func (s *CommunityServer) DeleteComment(ctx context.Context, request *pb.DeleteCommentRequest) (*pb.DeleteCommentResponse, error) { 62 | //TODO implement me 63 | panic("implement me") 64 | } 65 | 66 | func (s *CommunityServer) CollectPost(ctx context.Context, request *pb.CollectPostRequest) (*pb.CollectPostResponse, error) { 67 | //TODO implement me 68 | panic("implement me") 69 | } 70 | 71 | func (s *CommunityServer) GetCollectPostList(ctx context.Context, request *pb.GetCollectPostListRequest) (*pb.GetCollectPostListResponse, error) { 72 | //TODO implement me 73 | panic("implement me") 74 | } 75 | 76 | func (s *CommunityServer) StarPost(ctx context.Context, request *pb.StarPostRequest) (*pb.StarPostResponse, error) { 77 | //TODO implement me 78 | panic("implement me") 79 | } 80 | 81 | func (s *CommunityServer) GetStarPostList(ctx context.Context, request *pb.GetStarPostListRequest) (*pb.GetStarPostListResponse, error) { 82 | //TODO implement me 83 | panic("implement me") 84 | } 85 | -------------------------------------------------------------------------------- /app/community/cmd/rpc/internal/svc/serviceContext.go: -------------------------------------------------------------------------------- 1 | package svc 2 | 3 | import ( 4 | "github.com/go-redis/redis" 5 | "gorm.io/gorm" 6 | "mall-go/app/community/cmd/rpc/internal/config" 7 | "mall-go/pkg" 8 | ) 9 | 10 | var Context *ServiceContext 11 | 12 | type ServiceContext struct { 13 | Config config.Config 14 | Redis *redis.Client 15 | Db *gorm.DB 16 | } 17 | 18 | func NewServiceContext(c config.Config) *ServiceContext { 19 | return &ServiceContext{ 20 | Config: c, 21 | Redis: pkg.NewRedis(c.RedisConf), 22 | Db: pkg.NewGorm(c.DbConf), 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/community/cmd/rpc/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | 6 | "github.com/mix-plus/go-mixplus/mrpc" 7 | "google.golang.org/grpc" 8 | "mall-go/app/community/cmd/pb" 9 | "mall-go/app/community/cmd/rpc/internal/config" 10 | "mall-go/app/community/cmd/rpc/internal/server" 11 | "mall-go/app/community/cmd/rpc/internal/svc" 12 | conf "mall-go/common/conf" 13 | _ "mall-go/pkg/di" 14 | ) 15 | 16 | var configFile = flag.String("f", "etc/community.yaml", "the config file") 17 | 18 | func main() { 19 | flag.Parse() 20 | 21 | var c config.Config 22 | 23 | if err := conf.MustLoad(*configFile, &c); err != nil { 24 | panic(err) 25 | } 26 | 27 | ctx := svc.NewServiceContext(c) 28 | svc.Context = ctx 29 | srv := server.NewCommunityServer(ctx) 30 | 31 | s := mrpc.MustNewServer(c.RpcServerConf, func(g *grpc.Server) { 32 | pb.RegisterCommunityServer(g, srv) 33 | 34 | }) 35 | 36 | defer s.Stop() 37 | 38 | s.Start() 39 | } 40 | -------------------------------------------------------------------------------- /app/lottery/.gitignore: -------------------------------------------------------------------------------- 1 | # goland project files 2 | .idea 3 | 4 | # netbeans project files 5 | nbproject 6 | 7 | # zend studio for eclipse project files 8 | .buildpath 9 | .project 10 | .settings 11 | 12 | # windows thumbnail cache 13 | Thumbs.db 14 | 15 | # Mac DS_Store Files 16 | .DS_Store 17 | 18 | .env 19 | -------------------------------------------------------------------------------- /app/lottery/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.18-alpine AS builder 2 | 3 | LABEL stage=gobuilder 4 | 5 | 6 | ENV CGO_ENABLED 0 7 | ENV GOPROXY https://goproxy.cn,direct 8 | 9 | RUN apk update --no-cache && apk add --no-cache tzdata 10 | 11 | WORKDIR /build 12 | 13 | COPY . . 14 | 15 | RUN go mod download 16 | 17 | RUN CGO_ENABLED=0 go build -ldflags="-s -w" -tags wireinject -o service ./cmd/main.go ./cmd/wire_gen.go 18 | 19 | 20 | FROM alpine 21 | 22 | RUN apk update --no-cache && apk add --no-cache ca-certificates 23 | 24 | COPY --from=builder /usr/share/zoneinfo/Asia/Shanghai /usr/share/zoneinfo/Asia/Shanghai 25 | ENV TZ Asia/Shanghai 26 | 27 | WORKDIR /app 28 | 29 | COPY --from=builder /build/service /app/service 30 | 31 | COPY ./etc /app/etc 32 | 33 | CMD ["./service", "-f", "./etc/config.yaml"] 34 | -------------------------------------------------------------------------------- /app/lottery/Makefile: -------------------------------------------------------------------------------- 1 | GOHOSTOS:=$(shell go env GOHOSTOS) 2 | GOPATH:=$(shell go env GOPATH) 3 | VERSION=$(shell git describe --tags --always) 4 | 5 | .PHONY: init 6 | # init env 7 | init: 8 | go install google.golang.org/protobuf/cmd/protoc-gen-go@latest 9 | go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest 10 | go install github.com/google/gnostic/cmd/protoc-gen-openapi@latest 11 | go install github.com/google/wire/cmd/wire@latest 12 | 13 | 14 | .PHONY: run 15 | run: 16 | go run ./... 17 | 18 | .PHONY: build 19 | build: 20 | CGO_ENABLED=0 go build -ldflags "-X main.Version=$(VERSION)" -tags wireinject -o service ./cmd/main.go ./cmd/wire_gen.go 21 | 22 | .PHONY: api 23 | api: 24 | buf generate 25 | 26 | .PHONY: generate 27 | # generate 28 | generate: 29 | go get github.com/google/wire/cmd/wire@latest 30 | go generate ./... 31 | -------------------------------------------------------------------------------- /app/lottery/README.md: -------------------------------------------------------------------------------- 1 | # go-mixplus-layout 2 | 3 | go-mixplus development layout 4 | 5 | 6 | ## TODO 7 | - gorm-gen 8 | - rockscache 9 | -------------------------------------------------------------------------------- /app/lottery/api/buf.lock: -------------------------------------------------------------------------------- 1 | # Generated by buf. DO NOT EDIT. 2 | version: v1 3 | deps: 4 | - remote: buf.build 5 | owner: envoyproxy 6 | repository: protoc-gen-validate 7 | commit: 6607b10f00ed4a3d98f906807131c44a 8 | - remote: buf.build 9 | owner: googleapis 10 | repository: googleapis 11 | commit: 75b4300737fb4efca0831636be94e517 12 | -------------------------------------------------------------------------------- /app/lottery/api/buf.yaml: -------------------------------------------------------------------------------- 1 | version: v1 2 | breaking: 3 | use: 4 | - FILE 5 | deps: 6 | - buf.build/googleapis/googleapis 7 | - buf.build/envoyproxy/protoc-gen-validate 8 | -------------------------------------------------------------------------------- /app/lottery/api/lottery/v1/lottery.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package lottery.api.v1; 4 | 5 | import "google/api/annotations.proto"; 6 | import "validate/validate.proto"; 7 | 8 | option go_package = "lottery.api.hello;v1"; 9 | 10 | service Lottery { 11 | rpc FindLottery(FindLotteryReq) returns(LotteryResp) { 12 | option (google.api.http) = { 13 | get: "/v1/lottery/find" 14 | }; 15 | } 16 | rpc CreateLottery(CreateLotteryReq) returns(LotteryResp) { 17 | option (google.api.http) = { 18 | post: "/v1/lottery/create" 19 | body: "*" 20 | }; 21 | } 22 | } 23 | 24 | 25 | message FindLotteryReq { 26 | int64 id = 1 [(validate.rules).int64.gt = 0]; 27 | } 28 | 29 | message LotteryResp { 30 | int64 id = 1; 31 | string name = 2; 32 | string description = 3; 33 | } 34 | 35 | message CreateLotteryReq { 36 | string name = 1 [(validate.rules).string.min_len = 1]; 37 | string description = 2 [(validate.rules).string.min_len = 1]; 38 | } 39 | -------------------------------------------------------------------------------- /app/lottery/api/lottery/v1/lottery_grpc.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT. 2 | // versions: 3 | // - protoc-gen-go-grpc v1.2.0 4 | // - protoc (unknown) 5 | // source: lottery/v1/lottery.proto 6 | 7 | package v1 8 | 9 | import ( 10 | context "context" 11 | grpc "google.golang.org/grpc" 12 | codes "google.golang.org/grpc/codes" 13 | status "google.golang.org/grpc/status" 14 | ) 15 | 16 | // This is a compile-time assertion to ensure that this generated file 17 | // is compatible with the grpc package it is being compiled against. 18 | // Requires gRPC-Go v1.32.0 or later. 19 | const _ = grpc.SupportPackageIsVersion7 20 | 21 | // LotteryClient is the client API for Lottery service. 22 | // 23 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. 24 | type LotteryClient interface { 25 | FindLottery(ctx context.Context, in *FindLotteryReq, opts ...grpc.CallOption) (*LotteryResp, error) 26 | CreateLottery(ctx context.Context, in *CreateLotteryReq, opts ...grpc.CallOption) (*LotteryResp, error) 27 | } 28 | 29 | type lotteryClient struct { 30 | cc grpc.ClientConnInterface 31 | } 32 | 33 | func NewLotteryClient(cc grpc.ClientConnInterface) LotteryClient { 34 | return &lotteryClient{cc} 35 | } 36 | 37 | func (c *lotteryClient) FindLottery(ctx context.Context, in *FindLotteryReq, opts ...grpc.CallOption) (*LotteryResp, error) { 38 | out := new(LotteryResp) 39 | err := c.cc.Invoke(ctx, "/lottery.api.v1.Lottery/FindLottery", in, out, opts...) 40 | if err != nil { 41 | return nil, err 42 | } 43 | return out, nil 44 | } 45 | 46 | func (c *lotteryClient) CreateLottery(ctx context.Context, in *CreateLotteryReq, opts ...grpc.CallOption) (*LotteryResp, error) { 47 | out := new(LotteryResp) 48 | err := c.cc.Invoke(ctx, "/lottery.api.v1.Lottery/CreateLottery", in, out, opts...) 49 | if err != nil { 50 | return nil, err 51 | } 52 | return out, nil 53 | } 54 | 55 | // LotteryServer is the server API for Lottery service. 56 | // All implementations must embed UnimplementedLotteryServer 57 | // for forward compatibility 58 | type LotteryServer interface { 59 | FindLottery(context.Context, *FindLotteryReq) (*LotteryResp, error) 60 | CreateLottery(context.Context, *CreateLotteryReq) (*LotteryResp, error) 61 | mustEmbedUnimplementedLotteryServer() 62 | } 63 | 64 | // UnimplementedLotteryServer must be embedded to have forward compatible implementations. 65 | type UnimplementedLotteryServer struct { 66 | } 67 | 68 | func (UnimplementedLotteryServer) FindLottery(context.Context, *FindLotteryReq) (*LotteryResp, error) { 69 | return nil, status.Errorf(codes.Unimplemented, "method FindLottery not implemented") 70 | } 71 | func (UnimplementedLotteryServer) CreateLottery(context.Context, *CreateLotteryReq) (*LotteryResp, error) { 72 | return nil, status.Errorf(codes.Unimplemented, "method CreateLottery not implemented") 73 | } 74 | func (UnimplementedLotteryServer) mustEmbedUnimplementedLotteryServer() {} 75 | 76 | // UnsafeLotteryServer may be embedded to opt out of forward compatibility for this service. 77 | // Use of this interface is not recommended, as added methods to LotteryServer will 78 | // result in compilation errors. 79 | type UnsafeLotteryServer interface { 80 | mustEmbedUnimplementedLotteryServer() 81 | } 82 | 83 | func RegisterLotteryServer(s grpc.ServiceRegistrar, srv LotteryServer) { 84 | s.RegisterService(&Lottery_ServiceDesc, srv) 85 | } 86 | 87 | func _Lottery_FindLottery_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 88 | in := new(FindLotteryReq) 89 | if err := dec(in); err != nil { 90 | return nil, err 91 | } 92 | if interceptor == nil { 93 | return srv.(LotteryServer).FindLottery(ctx, in) 94 | } 95 | info := &grpc.UnaryServerInfo{ 96 | Server: srv, 97 | FullMethod: "/lottery.api.v1.Lottery/FindLottery", 98 | } 99 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 100 | return srv.(LotteryServer).FindLottery(ctx, req.(*FindLotteryReq)) 101 | } 102 | return interceptor(ctx, in, info, handler) 103 | } 104 | 105 | func _Lottery_CreateLottery_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 106 | in := new(CreateLotteryReq) 107 | if err := dec(in); err != nil { 108 | return nil, err 109 | } 110 | if interceptor == nil { 111 | return srv.(LotteryServer).CreateLottery(ctx, in) 112 | } 113 | info := &grpc.UnaryServerInfo{ 114 | Server: srv, 115 | FullMethod: "/lottery.api.v1.Lottery/CreateLottery", 116 | } 117 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 118 | return srv.(LotteryServer).CreateLottery(ctx, req.(*CreateLotteryReq)) 119 | } 120 | return interceptor(ctx, in, info, handler) 121 | } 122 | 123 | // Lottery_ServiceDesc is the grpc.ServiceDesc for Lottery service. 124 | // It's only intended for direct use with grpc.RegisterService, 125 | // and not to be introspected or modified (even as a copy) 126 | var Lottery_ServiceDesc = grpc.ServiceDesc{ 127 | ServiceName: "lottery.api.v1.Lottery", 128 | HandlerType: (*LotteryServer)(nil), 129 | Methods: []grpc.MethodDesc{ 130 | { 131 | MethodName: "FindLottery", 132 | Handler: _Lottery_FindLottery_Handler, 133 | }, 134 | { 135 | MethodName: "CreateLottery", 136 | Handler: _Lottery_CreateLottery_Handler, 137 | }, 138 | }, 139 | Streams: []grpc.StreamDesc{}, 140 | Metadata: "lottery/v1/lottery.proto", 141 | } 142 | -------------------------------------------------------------------------------- /app/lottery/api/openapi.yaml: -------------------------------------------------------------------------------- 1 | # Generated with protoc-gen-openapi 2 | # https://github.com/google/gnostic/tree/master/cmd/protoc-gen-openapi 3 | 4 | openapi: 3.0.3 5 | info: 6 | title: Lottery API 7 | version: 0.0.1 8 | paths: 9 | /v1/lottery/create: 10 | post: 11 | tags: 12 | - Lottery 13 | operationId: Lottery_CreateLottery 14 | requestBody: 15 | content: 16 | application/json: 17 | schema: 18 | $ref: '#/components/schemas/CreateLotteryReq' 19 | required: true 20 | responses: 21 | "200": 22 | description: OK 23 | content: 24 | application/json: 25 | schema: 26 | $ref: '#/components/schemas/LotteryResp' 27 | default: 28 | description: Default error response 29 | content: 30 | application/json: 31 | schema: 32 | $ref: '#/components/schemas/Status' 33 | /v1/lottery/find: 34 | get: 35 | tags: 36 | - Lottery 37 | operationId: Lottery_FindLottery 38 | parameters: 39 | - name: id 40 | in: query 41 | schema: 42 | type: integer 43 | format: int64 44 | responses: 45 | "200": 46 | description: OK 47 | content: 48 | application/json: 49 | schema: 50 | $ref: '#/components/schemas/LotteryResp' 51 | default: 52 | description: Default error response 53 | content: 54 | application/json: 55 | schema: 56 | $ref: '#/components/schemas/Status' 57 | components: 58 | schemas: 59 | CreateLotteryReq: 60 | type: object 61 | properties: 62 | name: 63 | type: string 64 | description: 65 | type: string 66 | GoogleProtobufAny: 67 | type: object 68 | properties: 69 | '@type': 70 | type: string 71 | description: The type of the serialized message. 72 | additionalProperties: true 73 | description: Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. 74 | LotteryResp: 75 | type: object 76 | properties: 77 | id: 78 | type: integer 79 | format: int64 80 | name: 81 | type: string 82 | description: 83 | type: string 84 | Status: 85 | type: object 86 | properties: 87 | code: 88 | type: integer 89 | description: The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. 90 | format: int32 91 | message: 92 | type: string 93 | description: A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. 94 | details: 95 | type: array 96 | items: 97 | $ref: '#/components/schemas/GoogleProtobufAny' 98 | description: A list of messages that carry the error details. There is a common set of message types for APIs to use. 99 | description: 'The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors).' 100 | tags: 101 | - name: Lottery 102 | -------------------------------------------------------------------------------- /app/lottery/buf.gen.yaml: -------------------------------------------------------------------------------- 1 | version: v1 2 | plugins: 3 | - plugin: go 4 | out: api 5 | opt: 6 | - paths=source_relative 7 | - plugin: grpc-gateway 8 | out: api 9 | opt: 10 | - paths=source_relative 11 | - generate_unbound_methods=true 12 | - plugin: openapi 13 | out: api 14 | opt: 15 | - paths=source_relative 16 | - plugin: go-grpc 17 | out: api 18 | opt: 19 | - paths=source_relative 20 | - plugin: go-errors 21 | out: api 22 | opt: 23 | - paths=source_relative 24 | - plugin: buf.build/bufbuild/validate-go 25 | out: api 26 | opt: 27 | - paths=source_relative 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/lottery/buf.work.yaml: -------------------------------------------------------------------------------- 1 | version: v1 2 | directories: 3 | - api 4 | -------------------------------------------------------------------------------- /app/lottery/cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | 6 | "mall-go/app/lottery/internal/config" 7 | 8 | "github.com/mix-plus/core/conf" 9 | ) 10 | 11 | var configFile = flag.String("f", "etc/config.yaml", "the config file") 12 | 13 | func main() { 14 | flag.Parse() 15 | 16 | var c config.Config 17 | 18 | if err := conf.MustLoad(*configFile, &c); err != nil { 19 | panic(err) 20 | } 21 | 22 | app, err := initApp(&c) 23 | if err != nil { 24 | panic(err) 25 | } 26 | 27 | app.Run() 28 | } 29 | -------------------------------------------------------------------------------- /app/lottery/cmd/wire.go: -------------------------------------------------------------------------------- 1 | //go:build wireinject 2 | // +build wireinject 3 | 4 | // The build tag makes sure the stub is not built in the final build. 5 | 6 | package main 7 | 8 | import ( 9 | "mall-go/app/lottery/internal/config" 10 | "mall-go/app/lottery/internal/server" 11 | "mall-go/app/lottery/internal/service" 12 | "mall-go/app/lottery/internal/svc" 13 | 14 | "github.com/google/wire" 15 | ) 16 | 17 | // initApp init app application. 18 | func initApp(c *config.Config) (*server.AppServer, error) { 19 | panic(wire.Build(svc.ProviderSet, service.ProviderSet, server.ProviderSet, server.NewApp)) 20 | } 21 | -------------------------------------------------------------------------------- /app/lottery/cmd/wire_gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by Wire. DO NOT EDIT. 2 | 3 | //go:generate go run github.com/google/wire/cmd/wire 4 | //go:build !wireinject 5 | // +build !wireinject 6 | 7 | package main 8 | 9 | import ( 10 | "mall-go/app/lottery/internal/config" 11 | "mall-go/app/lottery/internal/server" 12 | "mall-go/app/lottery/internal/service" 13 | "mall-go/app/lottery/internal/svc" 14 | ) 15 | 16 | // Injectors from wire.go: 17 | 18 | // initApp init app application. 19 | func initApp(c *config.Config) (*server.AppServer, error) { 20 | serviceContext := svc.NewServiceContext(c) 21 | lotteryService := service.NewLotteryService(serviceContext) 22 | httpServer := server.NewHttpServer(c, lotteryService) 23 | rpcServer := server.NewGrpcServer(c, serviceContext) 24 | appServer, err := server.NewApp(serviceContext, lotteryService, httpServer, rpcServer) 25 | if err != nil { 26 | return nil, err 27 | } 28 | return appServer, nil 29 | } 30 | -------------------------------------------------------------------------------- /app/lottery/etc/config.yaml: -------------------------------------------------------------------------------- 1 | DbConf: 2 | DSN: root:root@tcp(127.0.0.1:3306)/lottery?charset=utf8&loc=Asia%2FShanghai&parseTime=true&timeout=10s 3 | 4 | RedisConf: 5 | Addr: 127.0.0.1 6 | Pass: 7 | DataBase: 0 8 | Timeout: 60 9 | 10 | RpcServerConf: 11 | Name: lottery-rpc 12 | Addr: "localhost:8081" 13 | Timeout: 60 14 | Mode: dev 15 | 16 | HttpServerConf: 17 | Name: lottery-http 18 | Addr: "localhost:8080" 19 | Timeout: 60 20 | Mode: dev 21 | 22 | -------------------------------------------------------------------------------- /app/lottery/go.mod: -------------------------------------------------------------------------------- 1 | module mall-go/app/lottery 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/envoyproxy/protoc-gen-validate v0.9.1 7 | github.com/google/wire v0.5.0 8 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2 9 | github.com/mix-plus/core v0.0.1 10 | github.com/mix-plus/go-mixplus v0.0.1 11 | google.golang.org/genproto v0.0.0-20230223222841-637eb2293923 12 | google.golang.org/grpc v1.53.0 13 | google.golang.org/protobuf v1.28.1 14 | ) 15 | 16 | require ( 17 | github.com/beorn7/perks v1.0.1 // indirect 18 | github.com/cenkalti/backoff/v4 v4.2.0 // indirect 19 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 20 | github.com/fatih/color v1.13.0 // indirect 21 | github.com/fsnotify/fsnotify v1.6.0 // indirect 22 | github.com/go-logr/logr v1.2.3 // indirect 23 | github.com/go-logr/stdr v1.2.2 // indirect 24 | github.com/golang/protobuf v1.5.2 // indirect 25 | github.com/hashicorp/hcl v1.0.0 // indirect 26 | github.com/magiconair/properties v1.8.6 // indirect 27 | github.com/mattn/go-colorable v0.1.12 // indirect 28 | github.com/mattn/go-isatty v0.0.16 // indirect 29 | github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect 30 | github.com/mitchellh/mapstructure v1.5.0 // indirect 31 | github.com/openzipkin/zipkin-go v0.4.0 // indirect 32 | github.com/pelletier/go-toml v1.9.5 // indirect 33 | github.com/pelletier/go-toml/v2 v2.0.6 // indirect 34 | github.com/prometheus/client_golang v1.13.0 // indirect 35 | github.com/prometheus/client_model v0.2.0 // indirect 36 | github.com/prometheus/common v0.37.0 // indirect 37 | github.com/prometheus/procfs v0.8.0 // indirect 38 | github.com/spaolacci/murmur3 v1.1.0 // indirect 39 | github.com/spf13/afero v1.9.2 // indirect 40 | github.com/spf13/cast v1.5.0 // indirect 41 | github.com/spf13/jwalterweatherman v1.1.0 // indirect 42 | github.com/spf13/pflag v1.0.5 // indirect 43 | github.com/spf13/viper v1.14.0 // indirect 44 | github.com/subosito/gotenv v1.4.1 // indirect 45 | github.com/zeromicro/go-zero v1.4.4 // indirect 46 | go.opentelemetry.io/otel v1.13.0 // indirect 47 | go.opentelemetry.io/otel/exporters/jaeger v1.13.0 // indirect 48 | go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.13.0 // indirect 49 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.13.0 // indirect 50 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.13.0 // indirect 51 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.10.0 // indirect 52 | go.opentelemetry.io/otel/exporters/zipkin v1.10.0 // indirect 53 | go.opentelemetry.io/otel/sdk v1.13.0 // indirect 54 | go.opentelemetry.io/otel/trace v1.13.0 // indirect 55 | go.opentelemetry.io/proto/otlp v0.19.0 // indirect 56 | go.uber.org/automaxprocs v1.5.1 // indirect 57 | golang.org/x/net v0.8.0 // indirect 58 | golang.org/x/sys v0.6.0 // indirect 59 | golang.org/x/text v0.8.0 // indirect 60 | gopkg.in/ini.v1 v1.67.0 // indirect 61 | gopkg.in/yaml.v2 v2.4.0 // indirect 62 | gopkg.in/yaml.v3 v3.0.1 // indirect 63 | ) 64 | -------------------------------------------------------------------------------- /app/lottery/internal/README.md: -------------------------------------------------------------------------------- 1 | # business structure 2 | 3 | The directory structure within the project can take multiple forms, such as DDD or generated structures. 4 | 5 | 6 | ## DDD 7 | The structure of DDD also varies in different versions, both domestic and foreign. 8 | 9 | `standard version` 10 | ```bash 11 | domain: used to store domain layer code and models. 12 | application: used to store application layer code and services. 13 | interfaces: used to store external interface and adapter code. 14 | ``` 15 | 16 | `kratos` 17 | ``` 18 | biz: used to store code and models related to business logic. 19 | data: used to store code and models related to data access. 20 | service: used to store code and models related to service implementation. 21 | ``` 22 | 23 | 24 | ## Generated 25 | ``` 26 | gen: used to store generated files such as gorm-gen and swagger-gen. 27 | model: used to store database models. 28 | logic: used to write business logic. 29 | service: used to store code and models related to service implementation. 30 | ``` 31 | -------------------------------------------------------------------------------- /app/lottery/internal/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | mpConf "github.com/mix-plus/core/conf" 5 | "github.com/mix-plus/go-mixplus/mrpc" 6 | ) 7 | 8 | type Config struct { 9 | mrpc.RpcServerConf `mapstructure:"RpcServerConf"` 10 | mpConf.ApiConf `mapstructure:"HttpServerConf"` 11 | } 12 | -------------------------------------------------------------------------------- /app/lottery/internal/server/grpc.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "mall-go/app/lottery/internal/config" 5 | "mall-go/app/lottery/internal/service" 6 | "mall-go/app/lottery/internal/svc" 7 | 8 | lottery "mall-go/app/lottery/api/lottery/v1" 9 | 10 | "github.com/mix-plus/go-mixplus/mrpc" 11 | "google.golang.org/grpc" 12 | ) 13 | 14 | func NewGrpcServer(c *config.Config, svc *svc.ServiceContext) *mrpc.RpcServer { 15 | 16 | srv := service.NewLotteryService(svc) 17 | s := mrpc.MustNewServer(c.RpcServerConf, func(g *grpc.Server) { 18 | // grpc register 19 | lottery.RegisterLotteryServer(g, srv) 20 | }) 21 | 22 | return s 23 | } 24 | -------------------------------------------------------------------------------- /app/lottery/internal/server/http.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | 7 | lottery "mall-go/app/lottery/api/lottery/v1" 8 | "mall-go/app/lottery/internal/config" 9 | "mall-go/app/lottery/internal/service" 10 | 11 | "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" 12 | ) 13 | 14 | func NewHttpServer(c *config.Config, srv *service.LotteryService) *http.Server { 15 | httpServer := &http.Server{} 16 | ctx := context.Background() 17 | ctx, cancel := context.WithCancel(ctx) 18 | defer cancel() 19 | // Register gRPC server endpoint 20 | // Note: Make sure the gRPC server is running properly and accessible 21 | mux := runtime.NewServeMux() 22 | err := lottery.RegisterLotteryHandlerServer(ctx, mux, srv) 23 | if err != nil { 24 | panic(err) 25 | } 26 | httpServer.Addr = c.ApiConf.Addr 27 | httpServer.Handler = mux 28 | 29 | return httpServer 30 | } 31 | -------------------------------------------------------------------------------- /app/lottery/internal/server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "net/http" 5 | 6 | "mall-go/app/lottery/internal/service" 7 | "mall-go/app/lottery/internal/svc" 8 | 9 | "github.com/google/wire" 10 | "github.com/mix-plus/go-mixplus/mrpc" 11 | ) 12 | 13 | // ProviderSet is server providers. 14 | var ProviderSet = wire.NewSet(NewGrpcServer, NewHttpServer) 15 | 16 | type AppServer struct { 17 | SvcCtx *svc.ServiceContext 18 | HttpServer *http.Server 19 | GrpcServer *mrpc.RpcServer 20 | 21 | HelloService *service.LotteryService 22 | } 23 | 24 | func NewApp(svcCtx *svc.ServiceContext, helloService *service.LotteryService, hs *http.Server, gs *mrpc.RpcServer) (*AppServer, error) { 25 | return &AppServer{ 26 | SvcCtx: svcCtx, 27 | HelloService: helloService, 28 | HttpServer: hs, 29 | GrpcServer: gs, 30 | }, nil 31 | } 32 | 33 | func (a *AppServer) Run() { 34 | 35 | go func() { 36 | a.HttpServer.ListenAndServe() 37 | }() 38 | 39 | a.GrpcServer.Start() 40 | 41 | defer a.GrpcServer.Stop() 42 | } 43 | -------------------------------------------------------------------------------- /app/lottery/internal/service/lottery.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | lottery "mall-go/app/lottery/api/lottery/v1" 6 | ) 7 | 8 | func (l *LotteryService) FindLottery(ctx context.Context, req *lottery.FindLotteryReq) (*lottery.LotteryResp, error) { 9 | return &lottery.LotteryResp{ 10 | Id: req.Id, 11 | Name: "x", 12 | Description: "x", 13 | }, nil 14 | } 15 | 16 | func (l *LotteryService) CreateLottery(ctx context.Context, req *lottery.CreateLotteryReq) (*lottery.LotteryResp, error) { 17 | return &lottery.LotteryResp{ 18 | Id: 1, 19 | Name: req.Name, 20 | Description: req.Description, 21 | }, nil 22 | } 23 | -------------------------------------------------------------------------------- /app/lottery/internal/service/service.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "mall-go/app/lottery/internal/svc" 5 | 6 | lottery "mall-go/app/lottery/api/lottery/v1" 7 | 8 | "github.com/google/wire" 9 | ) 10 | 11 | // ProviderSet is server providers. 12 | var ProviderSet = wire.NewSet(NewLotteryService) 13 | 14 | type LotteryService struct { 15 | lottery.UnimplementedLotteryServer 16 | 17 | svcCtx *svc.ServiceContext 18 | } 19 | 20 | func NewLotteryService(ctx *svc.ServiceContext) *LotteryService { 21 | return &LotteryService{ 22 | svcCtx: ctx, 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/lottery/internal/svc/serviceContext.go: -------------------------------------------------------------------------------- 1 | package svc 2 | 3 | import ( 4 | "mall-go/app/lottery/internal/config" 5 | 6 | "github.com/google/wire" 7 | ) 8 | 9 | // ProviderSet is server providers. 10 | var ProviderSet = wire.NewSet(NewServiceContext) 11 | 12 | type ServiceContext struct { 13 | Config *config.Config 14 | } 15 | 16 | func NewServiceContext(c *config.Config) *ServiceContext { 17 | return &ServiceContext{ 18 | Config: c, 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/merchants/README.md: -------------------------------------------------------------------------------- 1 | # 多商户 -------------------------------------------------------------------------------- /app/merchants/cmd/api/.gitignore: -------------------------------------------------------------------------------- 1 | # goland project files 2 | .idea 3 | 4 | # netbeans project files 5 | nbproject 6 | 7 | # zend studio for eclipse project files 8 | .buildpath 9 | .project 10 | .settings 11 | 12 | # windows thumbnail cache 13 | Thumbs.db 14 | 15 | # Mac DS_Store Files 16 | .DS_Store 17 | -------------------------------------------------------------------------------- /app/merchants/cmd/api/etc/merchants.yaml: -------------------------------------------------------------------------------- 1 | Name: merchants-api 2 | Addr: ":10091" 3 | Mode: "release" 4 | 5 | JwtAuth: 6 | Secret: mall-go 7 | 8 | MerchantsRpcConf: 9 | Target: "merchants-rpc:10092" 10 | Timeout: 60 11 | 12 | BalanceRpcConf: 13 | Target: "balance-rpc:10012" 14 | Timeout: 60 -------------------------------------------------------------------------------- /app/merchants/cmd/api/internal/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/mix-plus/go-mixplus/mrpc" 5 | "mall-go/common/conf" 6 | ) 7 | 8 | type Config struct { 9 | conf.ApiConf `mapstructure:",squash"` 10 | conf.JwtAuth 11 | MerchantsRpcConf mrpc.RpcClientConf 12 | BalanceRpcConf mrpc.RpcClientConf 13 | } 14 | -------------------------------------------------------------------------------- /app/merchants/cmd/api/internal/handler/controllers/merchants.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | balancepb "mall-go/app/balance/cmd/pb" 8 | "mall-go/app/merchants/cmd/api/internal/logic" 9 | "mall-go/app/merchants/cmd/pb" 10 | "mall-go/common" 11 | "mall-go/common/response" 12 | ) 13 | 14 | type MerchantsController struct { 15 | resp response.Response 16 | logic logic.MerchantsLogic 17 | } 18 | 19 | type JoinMerchantRequest struct { 20 | ShopName string `json:"shop_name" validate:"required"` 21 | ShopLogo string `json:"shop_logo" validate:"required"` 22 | Mobile string `json:"mobile" validate:"required"` 23 | Address string `json:"address" validate:"required"` 24 | Remark string `json:"remark" validate:"min=0"` 25 | } 26 | 27 | func (t *MerchantsController) JoinMerchant(c *gin.Context) { 28 | var req JoinMerchantRequest 29 | if err := c.Bind(&req); err != nil { 30 | c.JSON(http.StatusOK, t.resp.Fail(err.Error())) 31 | return 32 | } 33 | userId, err := common.GetUserId(c) 34 | if err != nil { 35 | c.JSON(http.StatusOK, t.resp.Fail(err.Error())) 36 | return 37 | } 38 | resp, err := t.logic.JoinMerchant(&pb.JoinMerchantRequest{ 39 | ShopName: req.ShopName, 40 | ShopLogo: req.ShopLogo, 41 | Mobile: req.Mobile, 42 | Address: req.Address, 43 | Remark: req.Remark, 44 | UserId: userId, 45 | }) 46 | if err != nil { 47 | c.JSON(http.StatusOK, t.resp.Error(err)) 48 | return 49 | } 50 | 51 | c.JSON(http.StatusOK, t.resp.Success(resp)) 52 | } 53 | 54 | type UpdateMerchantRequest struct { 55 | ShopId int64 `json:"shop_id" validate:"required"` 56 | ShopName string `json:"shop_name" validate:"required"` 57 | ShopLogo string `json:"shop_logo" validate:"required"` 58 | Mobile string `json:"mobile" validate:"required"` 59 | Address string `json:"address" validate:"required"` 60 | Remark string `json:"remark" validate:"min=0"` 61 | } 62 | 63 | func (t *MerchantsController) UpdateMerchant(c *gin.Context) { 64 | var req UpdateMerchantRequest 65 | if err := c.Bind(&req); err != nil { 66 | c.JSON(http.StatusOK, t.resp.Fail(err.Error())) 67 | return 68 | } 69 | userId, err := common.GetUserId(c) 70 | if err != nil { 71 | c.JSON(http.StatusOK, t.resp.Fail(err.Error())) 72 | return 73 | } 74 | resp, err := t.logic.UpdateMerchant(&pb.UpdateMerchantRequest{ 75 | ShopName: req.ShopName, 76 | ShopLogo: req.ShopLogo, 77 | Mobile: req.Mobile, 78 | Address: req.Address, 79 | Remark: req.Remark, 80 | UserId: userId, 81 | ShopId: req.ShopId, 82 | }) 83 | if err != nil { 84 | c.JSON(http.StatusOK, t.resp.Error(err)) 85 | return 86 | } 87 | 88 | c.JSON(http.StatusOK, t.resp.Success(resp)) 89 | } 90 | 91 | func (t *MerchantsController) CloseMerchant(c *gin.Context) { 92 | userId, err := common.GetUserId(c) 93 | if err != nil { 94 | c.JSON(http.StatusOK, t.resp.Fail(err.Error())) 95 | return 96 | } 97 | merchant, err := t.logic.GetMerchant(&pb.GetMerchantRequest{ 98 | UserId: userId, 99 | }) 100 | if err != nil { 101 | c.JSON(http.StatusOK, t.resp.Error(err)) 102 | return 103 | } 104 | resp, err := t.logic.CloseMerchant(&pb.CloseMerchantRequest{ 105 | ShopId: merchant.Id, 106 | }) 107 | if err != nil { 108 | c.JSON(http.StatusOK, t.resp.Error(err)) 109 | return 110 | } 111 | 112 | c.JSON(http.StatusOK, t.resp.Success(resp)) 113 | 114 | } 115 | 116 | func (t *MerchantsController) GetMerchant(c *gin.Context) { 117 | userId, err := common.GetUserId(c) 118 | if err != nil { 119 | c.JSON(http.StatusOK, t.resp.Fail(err.Error())) 120 | return 121 | } 122 | merchant, err := t.logic.GetMerchant(&pb.GetMerchantRequest{ 123 | UserId: userId, 124 | }) 125 | if err != nil { 126 | c.JSON(http.StatusOK, t.resp.Error(err)) 127 | return 128 | } 129 | c.JSON(http.StatusOK, t.resp.Success(merchant)) 130 | } 131 | 132 | func (t *MerchantsController) GetBalance(c *gin.Context) { 133 | userId, err := common.GetUserId(c) 134 | if err != nil { 135 | c.JSON(http.StatusOK, t.resp.Fail(err.Error())) 136 | return 137 | } 138 | balance, err := t.logic.GetBalance(&balancepb.GetBalanceRequest{ 139 | UserId: userId, 140 | Type: 2, 141 | }) 142 | if err != nil { 143 | c.JSON(http.StatusOK, t.resp.Error(err)) 144 | return 145 | } 146 | c.JSON(http.StatusOK, t.resp.Success(balance)) 147 | } 148 | 149 | type GetBalanceChangeListRequest struct { 150 | Type int32 `json:"type" validate:"min=0"` 151 | TypeAmount int32 `json:"type_amount" validate:"min=0"` 152 | Page int32 `json:"page" validate:"required"` 153 | PageSize int32 `json:"page_size" validate:"required"` 154 | } 155 | 156 | func (t *MerchantsController) GetBalanceChangeList(c *gin.Context) { 157 | var req GetBalanceChangeListRequest 158 | if err := c.Bind(&req); err != nil { 159 | c.JSON(http.StatusOK, t.resp.Fail(err.Error())) 160 | return 161 | } 162 | userId, err := common.GetUserId(c) 163 | if err != nil { 164 | c.JSON(http.StatusOK, t.resp.Fail(err.Error())) 165 | return 166 | } 167 | balance, err := t.logic.GetBalance(&balancepb.GetBalanceRequest{ 168 | UserId: userId, 169 | Type: 2, 170 | }) 171 | if err != nil { 172 | c.JSON(http.StatusOK, t.resp.Error(err)) 173 | return 174 | } 175 | item, err := t.logic.GetBalanceChangeList(&balancepb.GetBalanceChangeListRequest{ 176 | Id: balance.Id, 177 | Type: req.Type, 178 | TypeAmount: req.TypeAmount, 179 | Page: req.Page, 180 | PageSize: req.PageSize, 181 | }) 182 | if err != nil { 183 | c.JSON(http.StatusOK, t.resp.Error(err)) 184 | return 185 | } 186 | 187 | c.JSON(http.StatusOK, t.resp.Success(item)) 188 | } 189 | 190 | func (t *MerchantsController) Withdrawals(c *gin.Context) { 191 | 192 | } 193 | -------------------------------------------------------------------------------- /app/merchants/cmd/api/internal/handler/routes.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/gin-gonic/gin/binding" 6 | "mall-go/app/merchants/cmd/api/internal/handler/controllers" 7 | "mall-go/app/merchants/cmd/api/internal/svc" 8 | "mall-go/common/middleware" 9 | validator2 "mall-go/pkg/validator" 10 | ) 11 | 12 | func Load(router *gin.Engine) { 13 | // register validate 14 | binding.Validator = new(validator2.DefaultValidator) 15 | router.GET("/", func(ctx *gin.Context) { 16 | ctx.String(200, "hello") 17 | }) 18 | router.Use(gin.Recovery(), middleware.CorsMiddleware(), middleware.AuthMiddleware(svc.Context.Jwt.Secret)) 19 | 20 | merchant := controllers.MerchantsController{} 21 | r := router.Group("/merchant") 22 | { 23 | // 查询商户信息 24 | r.POST("/getMerchant", func(c *gin.Context) { 25 | merchant.GetMerchant(c) 26 | }) 27 | // 加入商户 28 | r.POST("/join", func(c *gin.Context) { 29 | merchant.JoinMerchant(c) 30 | }) 31 | // 更新商户 32 | r.POST("/update", func(c *gin.Context) { 33 | merchant.UpdateMerchant(c) 34 | }) 35 | // 退出商户 36 | r.POST("/close", func(c *gin.Context) { 37 | merchant.CloseMerchant(c) 38 | }) 39 | // 查询余额 40 | r.POST("/getBalance", func(c *gin.Context) { 41 | merchant.GetBalance(c) 42 | }) 43 | // 查询账单 44 | r.POST("/getBalanceChangeList", func(c *gin.Context) { 45 | merchant.GetBalanceChangeList(c) 46 | }) 47 | // 提现余额 48 | r.POST("/withdrawals", func(c *gin.Context) { 49 | merchant.Withdrawals(c) 50 | }) 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /app/merchants/cmd/api/internal/logic/merchants.go: -------------------------------------------------------------------------------- 1 | package logic 2 | 3 | import ( 4 | "context" 5 | 6 | balancepb "mall-go/app/balance/cmd/pb" 7 | "mall-go/app/merchants/cmd/api/internal/svc" 8 | "mall-go/app/merchants/cmd/pb" 9 | ) 10 | 11 | type MerchantsLogic struct { 12 | } 13 | 14 | func (t *MerchantsLogic) JoinMerchant(in *pb.JoinMerchantRequest) (*pb.JoinMerchantResponse, error) { 15 | 16 | resp, err := svc.Context.MerchantsRpc.JoinMerchant(context.Background(), in) 17 | if err != nil { 18 | return nil, err 19 | } 20 | return resp, nil 21 | } 22 | 23 | func (t *MerchantsLogic) UpdateMerchant(in *pb.UpdateMerchantRequest) (*pb.UpdateMerchantResponse, error) { 24 | resp, err := svc.Context.MerchantsRpc.UpdateMerchant(context.Background(), in) 25 | if err != nil { 26 | return nil, err 27 | } 28 | return resp, nil 29 | } 30 | 31 | func (t *MerchantsLogic) CloseMerchant(in *pb.CloseMerchantRequest) (*pb.CloseMerchantResponse, error) { 32 | resp, err := svc.Context.MerchantsRpc.CloseMerchant(context.Background(), in) 33 | if err != nil { 34 | return nil, err 35 | } 36 | return resp, nil 37 | } 38 | 39 | func (t *MerchantsLogic) GetMerchant(in *pb.GetMerchantRequest) (*pb.GetMerchantResponse, error) { 40 | resp, err := svc.Context.MerchantsRpc.GetMerchant(context.Background(), in) 41 | if err != nil { 42 | return nil, err 43 | } 44 | return resp, nil 45 | } 46 | 47 | type GetBalanceResponse struct { 48 | Id int64 `json:"id"` 49 | UserId int64 `json:"user_id"` 50 | Type int32 `json:"type"` 51 | Available uint64 `json:"available" default:"0"` 52 | Frozen uint64 `json:"frozen" default:"0"` 53 | } 54 | 55 | func (t *MerchantsLogic) GetBalance(in *balancepb.GetBalanceRequest) (*GetBalanceResponse, error) { 56 | resp, err := svc.Context.BalanceRpc.GetBalance(context.Background(), in) 57 | if err != nil { 58 | return nil, err 59 | } 60 | return &GetBalanceResponse{ 61 | Id: resp.Id, 62 | UserId: resp.UserId, 63 | Type: resp.Type, 64 | Available: resp.Available, 65 | Frozen: resp.Frozen, 66 | }, nil 67 | } 68 | 69 | func (t *MerchantsLogic) GetBalanceChangeList(in *balancepb.GetBalanceChangeListRequest) (*balancepb.GetBalanceChangeListResponse, error) { 70 | resp, err := svc.Context.BalanceRpc.GetBalanceChangeList(context.Background(), in) 71 | if err != nil { 72 | return nil, err 73 | } 74 | return resp, nil 75 | } 76 | 77 | type WithdrawalsRequest struct{} 78 | 79 | type WithdrawalsResponse struct{} 80 | 81 | func (t *MerchantsLogic) Withdrawals(in *WithdrawalsRequest) (*WithdrawalsResponse, error) { 82 | // 申请提现 83 | // 扣除余额 84 | // 同意提现 85 | 86 | // 拒绝提现 87 | // 返还余额 88 | return nil, nil 89 | } 90 | -------------------------------------------------------------------------------- /app/merchants/cmd/api/internal/svc/serviceContext.go: -------------------------------------------------------------------------------- 1 | package svc 2 | 3 | import ( 4 | "github.com/mix-plus/go-mixplus/mrpc" 5 | balancepb "mall-go/app/balance/cmd/pb" 6 | "mall-go/app/merchants/cmd/api/internal/config" 7 | "mall-go/app/merchants/cmd/pb" 8 | "mall-go/pkg/jwtx" 9 | ) 10 | 11 | var Context *ServiceContext 12 | 13 | type ServiceContext struct { 14 | Config config.Config 15 | Jwt *jwtx.Jwt 16 | 17 | MerchantsRpc pb.MerchantsClient 18 | BalanceRpc balancepb.BalanceClient 19 | } 20 | 21 | func NewServiceContext(c config.Config) *ServiceContext { 22 | return &ServiceContext{ 23 | Config: c, 24 | Jwt: jwtx.NewJwt(c.JwtAuth), 25 | MerchantsRpc: pb.NewMerchantsClient(mrpc.MustNewClient(c.MerchantsRpcConf).Conn()), 26 | BalanceRpc: balancepb.NewBalanceClient(mrpc.MustNewClient(c.BalanceRpcConf).Conn()), 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/merchants/cmd/api/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "fmt" 7 | "os" 8 | "os/signal" 9 | "strings" 10 | "syscall" 11 | "time" 12 | 13 | "github.com/gin-gonic/gin" 14 | "mall-go/app/merchants/cmd/api/internal/config" 15 | "mall-go/app/merchants/cmd/api/internal/handler" 16 | "mall-go/app/merchants/cmd/api/internal/svc" 17 | conf "mall-go/common/conf" 18 | "mall-go/pkg/di" 19 | _ "mall-go/pkg/di" 20 | ) 21 | 22 | var configFile = flag.String("f", "etc/balance.yaml", "the config file") 23 | 24 | func main() { 25 | flag.Parse() 26 | 27 | var c config.Config 28 | 29 | if err := conf.MustLoad(*configFile, &c); err != nil { 30 | panic(err) 31 | } 32 | 33 | logger := di.Zap() 34 | server := di.Server() 35 | 36 | svc.Context = svc.NewServiceContext(c) 37 | 38 | gin.SetMode(c.Mode) 39 | 40 | router := gin.New() 41 | if c.Mode != gin.ReleaseMode { 42 | handlerFunc := gin.LoggerWithConfig(gin.LoggerConfig{ 43 | Formatter: func(params gin.LogFormatterParams) string { 44 | return fmt.Sprintf("%s|%s|%d|%s\n", 45 | params.Method, 46 | params.Path, 47 | params.StatusCode, 48 | params.ClientIP, 49 | ) 50 | }, 51 | Output: &di.ZapOutput{Logger: logger}, 52 | }) 53 | router.Use(handlerFunc) 54 | } 55 | 56 | server.Addr = c.Addr 57 | server.Handler = router 58 | 59 | handler.Load(router) 60 | 61 | // signal 62 | ch := make(chan os.Signal) 63 | signal.Notify(ch, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM) 64 | go func() { 65 | <-ch 66 | logger.Info("Server shutdown") 67 | ctx, _ := context.WithTimeout(context.Background(), 10*time.Second) 68 | if err := server.Shutdown(ctx); err != nil { 69 | logger.Errorf("Server shutdown error: %s", err) 70 | } 71 | }() 72 | 73 | logger.Infof("Server start at %s", server.Addr) 74 | if err := server.ListenAndServe(); err != nil && !strings.Contains(err.Error(), "http: Server closed") { 75 | panic(err) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /app/merchants/cmd/pb/merchants.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package mall.rpc.merchants; 4 | 5 | option go_package = "/;pb"; 6 | 7 | service Merchants { 8 | // 加入商户 9 | rpc JoinMerchant(JoinMerchantRequest) returns(JoinMerchantResponse) {} 10 | // 更新商户信息 11 | rpc UpdateMerchant(UpdateMerchantRequest) returns(UpdateMerchantResponse) {} 12 | // 关闭商户 13 | rpc CloseMerchant(CloseMerchantRequest) returns(CloseMerchantResponse) {} 14 | // 查询商户 15 | rpc GetMerchant(GetMerchantRequest) returns(GetMerchantResponse) {} 16 | } 17 | 18 | message JoinMerchantRequest { 19 | string shop_name = 1; 20 | string shop_logo = 2; 21 | string mobile = 3; 22 | string address = 4; 23 | string remark = 5; 24 | int64 user_id = 6; 25 | } 26 | 27 | message JoinMerchantResponse { 28 | bool status = 1; 29 | } 30 | 31 | message UpdateMerchantRequest { 32 | string shop_name = 1; 33 | string shop_logo = 2; 34 | string mobile = 3; 35 | string address = 4; 36 | string remark = 5; 37 | int64 user_id = 6; 38 | int64 shop_id = 7; 39 | } 40 | 41 | message UpdateMerchantResponse { 42 | bool status = 1; 43 | } 44 | 45 | message CloseMerchantRequest { 46 | int64 shop_id = 1; 47 | } 48 | 49 | message CloseMerchantResponse { 50 | bool status = 1; 51 | } 52 | 53 | message GetMerchantRequest { 54 | int64 user_id = 1; 55 | } 56 | 57 | message GetMerchantResponse { 58 | int64 id = 1; 59 | int64 user_id = 2; 60 | string shop_name = 3; 61 | string shop_logo = 4; 62 | string mobile = 5; 63 | string address = 6; 64 | string remark = 7; 65 | int32 sort = 8; 66 | int32 is_hide = 9; 67 | int64 status = 10; 68 | } 69 | -------------------------------------------------------------------------------- /app/merchants/cmd/rpc/.gitignore: -------------------------------------------------------------------------------- 1 | # goland project files 2 | .idea 3 | 4 | # netbeans project files 5 | nbproject 6 | 7 | # zend studio for eclipse project files 8 | .buildpath 9 | .project 10 | .settings 11 | 12 | # windows thumbnail cache 13 | Thumbs.db 14 | 15 | # Mac DS_Store Files 16 | .DS_Store 17 | 18 | .env 19 | -------------------------------------------------------------------------------- /app/merchants/cmd/rpc/etc/merchants.yaml: -------------------------------------------------------------------------------- 1 | Name: merchants-rpc 2 | Addr: ":10092" 3 | Mode: "release" 4 | 5 | RedisConf: 6 | Addr: host.docker.internal:6379 7 | Password: 8 | DataBase: 1 9 | Timeout: 60 10 | 11 | 12 | DbConf: 13 | # host.docker.internal 14 | DSN: root:root@tcp(host.docker.internal:3306)/mall_go_merchants?charset=utf8&loc=Asia%2FShanghai&parseTime=true&timeout=10s -------------------------------------------------------------------------------- /app/merchants/cmd/rpc/internal/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/mix-plus/go-mixplus/mrpc" 5 | "mall-go/common/conf" 6 | ) 7 | 8 | type Config struct { 9 | mrpc.RpcServerConf `mapstructure:",squash"` 10 | conf.DbConf 11 | conf.RedisConf 12 | } 13 | -------------------------------------------------------------------------------- /app/merchants/cmd/rpc/internal/logic/merchants.go: -------------------------------------------------------------------------------- 1 | package logic 2 | 3 | import ( 4 | "errors" 5 | "time" 6 | 7 | "gorm.io/gorm" 8 | "mall-go/app/merchants/cmd/pb" 9 | "mall-go/app/merchants/cmd/rpc/internal/model" 10 | "mall-go/app/merchants/cmd/rpc/internal/svc" 11 | ) 12 | 13 | type MerchantsLogic struct { 14 | } 15 | 16 | func (l *MerchantsLogic) JoinMerchant(in *pb.JoinMerchantRequest) (bool, error) { 17 | // 查询是否存在 18 | var merchant model.MallMerchant 19 | db := svc.Context.Db 20 | err := db.First(&merchant, "mobile = ? AND user_id = ?", in.Mobile, in.UserId).Error 21 | if err == gorm.ErrRecordNotFound { 22 | merchant.UserId = in.UserId 23 | merchant.ShopName = in.ShopName 24 | merchant.ShopLogo = in.ShopLogo 25 | merchant.Mobile = in.Mobile 26 | address := in.Address 27 | merchant.Address = address 28 | merchant.Remark = in.Remark 29 | merchant.Sort = 1 30 | merchant.IsHide = 0 31 | merchant.Status = 3 32 | merchant.CreatedAt = time.Now() 33 | if err = db.Create(&merchant).Error; err != nil { 34 | return false, err 35 | } 36 | return true, nil 37 | } 38 | 39 | if merchant.ID != 0 { 40 | return false, errors.New("已经申请入驻了") 41 | } 42 | 43 | return false, err 44 | } 45 | 46 | func (l *MerchantsLogic) UpdateMerchant(in *pb.UpdateMerchantRequest) (bool, error) { 47 | var merchant model.MallMerchant 48 | db := svc.Context.Db 49 | db.First(&merchant, "id = ?", in.ShopId) 50 | if merchant.ID == 0 { 51 | return false, errors.New("商户不存在") 52 | } 53 | if merchant.Status != 1 { 54 | return false, errors.New("商户状态异常") 55 | } 56 | merchant.ID = in.ShopId 57 | merchant.UserId = in.UserId 58 | merchant.ShopName = in.ShopName 59 | merchant.ShopLogo = in.ShopLogo 60 | merchant.Mobile = in.Mobile 61 | merchant.Address = in.Address 62 | merchant.Remark = in.Remark 63 | merchant.UpdatedAt = time.Now() 64 | 65 | db.Save(&merchant) 66 | if db.Error != nil { 67 | return false, db.Error 68 | } 69 | return true, nil 70 | } 71 | 72 | func (l *MerchantsLogic) CloseMerchant(in *pb.CloseMerchantRequest) (bool, error) { 73 | var merchant model.MallMerchant 74 | db := svc.Context.Db 75 | db.First(&merchant, "id = ?", in.ShopId) 76 | if merchant.ID == 0 { 77 | return false, errors.New("商户不存在") 78 | } 79 | merchant.Status = 5 80 | merchant.UpdatedAt = time.Now() 81 | if err := db.Save(&merchant).Error; err != nil { 82 | return false, err 83 | } 84 | 85 | return true, nil 86 | } 87 | 88 | func (l *MerchantsLogic) GetMerchant(in *pb.GetMerchantRequest) (*model.MallMerchant, error) { 89 | var merchant model.MallMerchant 90 | db := svc.Context.Db 91 | if err := db.First(&merchant, "user_id = ?", in.UserId).Error; err != nil { 92 | return nil, err 93 | } 94 | return &merchant, nil 95 | } 96 | -------------------------------------------------------------------------------- /app/merchants/cmd/rpc/internal/model/merchant.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "time" 4 | 5 | // MallMerchant 商户表 6 | type MallMerchant struct { 7 | ID int64 `gorm:"id"` 8 | UserId int64 `gorm:"user_id"` // 用户ID 9 | ShopName string `gorm:"shop_name"` // 店铺名称 10 | ShopLogo string `gorm:"shop_logo"` // 店铺logo 11 | Mobile string `gorm:"mobile"` // 店长手机 12 | Address string `gorm:"address"` // 店铺地址 13 | Remark string `gorm:"remark"` // 店铺描述 14 | Sort int64 `gorm:"sort"` // 店铺排序 15 | IsHide int8 `gorm:"is_hide"` // 是否隐藏 16 | Status int8 `gorm:"status"` // 店铺状态 \r\n1 正常\r\n2 禁用\r\n3 审核中\r\n4 审核拒绝 \r\n5 关闭店铺 17 | CreatedAt time.Time `gorm:"created_at"` 18 | UpdatedAt time.Time `gorm:"updated_at"` 19 | } 20 | 21 | // TableName 表名称 22 | func (*MallMerchant) TableName() string { 23 | return "mall_merchant" 24 | } 25 | -------------------------------------------------------------------------------- /app/merchants/cmd/rpc/internal/server/merchantServer.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | 6 | "mall-go/app/merchants/cmd/pb" 7 | "mall-go/app/merchants/cmd/rpc/internal/logic" 8 | "mall-go/app/merchants/cmd/rpc/internal/svc" 9 | "mall-go/common/errcode" 10 | ) 11 | 12 | type MerchantsServer struct { 13 | svcCtx *svc.ServiceContext 14 | logic logic.MerchantsLogic 15 | } 16 | 17 | func NewMerchantsServer(ctx *svc.ServiceContext) *MerchantsServer { 18 | return &MerchantsServer{ 19 | svcCtx: ctx, 20 | } 21 | } 22 | 23 | func (m *MerchantsServer) GetMerchant(c context.Context, in *pb.GetMerchantRequest) (*pb.GetMerchantResponse, error) { 24 | merchant, err := m.logic.GetMerchant(in) 25 | if err != nil { 26 | return nil, errcode.GrpcError(err, 201) 27 | } 28 | return &pb.GetMerchantResponse{ 29 | Id: merchant.ID, 30 | UserId: merchant.UserId, 31 | ShopName: merchant.ShopName, 32 | ShopLogo: merchant.ShopLogo, 33 | Mobile: merchant.Mobile, 34 | Address: merchant.Address, 35 | Remark: merchant.Remark, 36 | Status: int64(merchant.Status), 37 | }, nil 38 | } 39 | 40 | func (m *MerchantsServer) JoinMerchant(c context.Context, in *pb.JoinMerchantRequest) (*pb.JoinMerchantResponse, error) { 41 | status, err := m.logic.JoinMerchant(in) 42 | if err != nil { 43 | return nil, errcode.GrpcError(err, 201) 44 | } 45 | return &pb.JoinMerchantResponse{ 46 | Status: status, 47 | }, nil 48 | } 49 | 50 | func (m *MerchantsServer) UpdateMerchant(c context.Context, in *pb.UpdateMerchantRequest) (*pb.UpdateMerchantResponse, error) { 51 | status, err := m.logic.UpdateMerchant(in) 52 | if err != nil { 53 | return nil, errcode.GrpcError(err, 201) 54 | } 55 | 56 | return &pb.UpdateMerchantResponse{ 57 | Status: status, 58 | }, nil 59 | } 60 | 61 | func (m *MerchantsServer) CloseMerchant(c context.Context, in *pb.CloseMerchantRequest) (*pb.CloseMerchantResponse, error) { 62 | status, err := m.logic.CloseMerchant(in) 63 | if err != nil { 64 | return nil, errcode.GrpcError(err, 201) 65 | } 66 | 67 | return &pb.CloseMerchantResponse{ 68 | Status: status, 69 | }, nil 70 | } 71 | -------------------------------------------------------------------------------- /app/merchants/cmd/rpc/internal/svc/serviceContext.go: -------------------------------------------------------------------------------- 1 | package svc 2 | 3 | import ( 4 | "github.com/go-redis/redis" 5 | "github.com/mix-go/xsql" 6 | "gorm.io/gorm" 7 | "mall-go/app/merchants/cmd/rpc/internal/config" 8 | "mall-go/pkg" 9 | ) 10 | 11 | var Context *ServiceContext 12 | 13 | type ServiceContext struct { 14 | Config config.Config 15 | Redis *redis.Client 16 | Db *gorm.DB 17 | Xsql *xsql.DB 18 | } 19 | 20 | func NewServiceContext(c config.Config) *ServiceContext { 21 | return &ServiceContext{ 22 | Config: c, 23 | Redis: pkg.NewRedis(c.RedisConf), 24 | Db: pkg.NewGorm(c.DbConf), 25 | Xsql: pkg.NewXsql(c.DbConf), 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/merchants/cmd/rpc/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | 6 | "github.com/mix-plus/go-mixplus/mrpc" 7 | "google.golang.org/grpc" 8 | "mall-go/app/merchants/cmd/pb" 9 | "mall-go/app/merchants/cmd/rpc/internal/config" 10 | "mall-go/app/merchants/cmd/rpc/internal/server" 11 | "mall-go/app/merchants/cmd/rpc/internal/svc" 12 | conf "mall-go/common/conf" 13 | _ "mall-go/pkg/di" 14 | ) 15 | 16 | var configFile = flag.String("f", "etc/merchants.yaml", "the config file") 17 | 18 | func main() { 19 | flag.Parse() 20 | 21 | var c config.Config 22 | 23 | if err := conf.MustLoad(*configFile, &c); err != nil { 24 | panic(err) 25 | } 26 | 27 | ctx := svc.NewServiceContext(c) 28 | svc.Context = ctx 29 | 30 | srv := server.NewMerchantsServer(ctx) 31 | 32 | s := mrpc.MustNewServer(c.RpcServerConf, func(g *grpc.Server) { 33 | pb.RegisterMerchantsServer(g, srv) 34 | }) 35 | 36 | defer s.Stop() 37 | 38 | s.Start() 39 | } 40 | -------------------------------------------------------------------------------- /app/mqueue/cmd/job/.gitignore: -------------------------------------------------------------------------------- 1 | #tmp 2 | tmp 3 | tmp/* 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/mqueue/cmd/job/README.md: -------------------------------------------------------------------------------- 1 | # 队列 -------------------------------------------------------------------------------- /app/mqueue/cmd/job/etc/config.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cexll/mall-go/9a8277c0b34d49a90e48e824e331c0da40f9ad1e/app/mqueue/cmd/job/etc/config.yaml -------------------------------------------------------------------------------- /app/mqueue/cmd/job/internal/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/mix-plus/go-mixplus/mrpc" 5 | "mall-go/common/conf" 6 | ) 7 | 8 | type Config struct { 9 | mrpc.ServiceConf 10 | conf.RedisConf 11 | } 12 | -------------------------------------------------------------------------------- /app/mqueue/cmd/job/internal/logic/routes.go: -------------------------------------------------------------------------------- 1 | package logic 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/hibiken/asynq" 7 | "mall-go/app/mqueue/cmd/job/internal/svc" 8 | ) 9 | 10 | type CronJob struct { 11 | ctx context.Context 12 | svcCtx *svc.ServiceContext 13 | } 14 | 15 | func NewCronJob(ctx context.Context, svcCtx *svc.ServiceContext) *CronJob { 16 | return &CronJob{ 17 | ctx: ctx, 18 | svcCtx: svcCtx, 19 | } 20 | } 21 | 22 | // register job 23 | func (l *CronJob) Register() *asynq.ServeMux { 24 | 25 | mux := asynq.NewServeMux() 26 | 27 | //scheduler job 28 | // mux.Handle(jobtype.ScheduleSettleRecord,NewSettleRecordHandler(l.svcCtx)) 29 | 30 | //defer job 31 | // mux.Handle(jobtype.DeferCloseHomestayOrder,NewCloseHomestayOrderHandler(l.svcCtx)) 32 | 33 | //queue job , asynq support queue job 34 | // wait you fill.. 35 | 36 | return mux 37 | } 38 | -------------------------------------------------------------------------------- /app/mqueue/cmd/job/internal/svc/asynqServer.go: -------------------------------------------------------------------------------- 1 | package svc 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/hibiken/asynq" 7 | "mall-go/app/mqueue/cmd/job/internal/config" 8 | ) 9 | 10 | func newAsynqServer(c config.Config) *asynq.Server { 11 | return asynq.NewServer( 12 | asynq.RedisClientOpt{Addr: c.RedisConf.Addr, Password: c.RedisConf.Pass}, 13 | asynq.Config{ 14 | IsFailure: func(err error) bool { 15 | fmt.Printf("asynq server exec task IsFailure ======== >>>>>>>>>>> err : %+v \n", err) 16 | return true 17 | }, 18 | Concurrency: 20, //max concurrent process job task num 19 | }, 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /app/mqueue/cmd/job/internal/svc/serverContext.go: -------------------------------------------------------------------------------- 1 | package svc 2 | 3 | import ( 4 | "github.com/hibiken/asynq" 5 | "mall-go/app/mqueue/cmd/job/internal/config" 6 | ) 7 | 8 | type ServiceContext struct { 9 | Config config.Config 10 | AsynqServer *asynq.Server 11 | } 12 | 13 | func NewServiceContext(c config.Config) *ServiceContext { 14 | return &ServiceContext{ 15 | Config: c, 16 | AsynqServer: newAsynqServer(c), 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/mqueue/cmd/job/internal/types/jobtype.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | // ScheduleSettleRecord 结算给商家的结算记录 4 | const ScheduleSettleRecord = "schedule:settle_record:settle" 5 | 6 | // DeferCloseHomestayOrder 延迟关闭订单 7 | const DeferCloseHomestayOrder = "defer:homestay_order:close" 8 | 9 | // MsgPaySuccessNotifyUser 支付成功通知用户 10 | const MsgPaySuccessNotifyUser = "msg:pay_success:notify_user" 11 | -------------------------------------------------------------------------------- /app/mqueue/cmd/job/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "os" 7 | 8 | "github.com/zeromicro/go-zero/core/logx" 9 | "mall-go/app/mqueue/cmd/job/internal/config" 10 | "mall-go/app/mqueue/cmd/job/internal/logic" 11 | "mall-go/app/mqueue/cmd/job/internal/svc" 12 | "mall-go/common/conf" 13 | ) 14 | 15 | var configFile = flag.String("f", "etc/mqueue.yaml", "Specify the config file") 16 | 17 | func main() { 18 | flag.Parse() 19 | var c config.Config 20 | conf.MustLoad(*configFile, &c) 21 | 22 | svcContext := svc.NewServiceContext(c) 23 | ctx := context.Background() 24 | cronJob := logic.NewCronJob(ctx, svcContext) 25 | mux := cronJob.Register() 26 | 27 | if err := svcContext.AsynqServer.Run(mux); err != nil { 28 | logx.WithContext(ctx).Errorf("!!!CronJobErr!!! run err:%+v", err) 29 | os.Exit(1) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/user/README.md: -------------------------------------------------------------------------------- 1 | # 会员 -------------------------------------------------------------------------------- /app/user/cmd/api/.gitignore: -------------------------------------------------------------------------------- 1 | # goland project files 2 | .idea 3 | 4 | # netbeans project files 5 | nbproject 6 | 7 | # zend studio for eclipse project files 8 | .buildpath 9 | .project 10 | .settings 11 | 12 | # windows thumbnail cache 13 | Thumbs.db 14 | 15 | # Mac DS_Store Files 16 | .DS_Store 17 | -------------------------------------------------------------------------------- /app/user/cmd/api/etc/user.yaml: -------------------------------------------------------------------------------- 1 | Name: user-api 2 | Addr: ":10001" 3 | Mode: "release" 4 | 5 | UserRpcConf: 6 | Target: "user-rpc:10002" 7 | 8 | BalanceRpcConf: 9 | Target: "balance-rpc:10012" 10 | 11 | JwtAuth: 12 | Secret: mall-go 13 | -------------------------------------------------------------------------------- /app/user/cmd/api/internal/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/mix-plus/go-mixplus/mrpc" 5 | "mall-go/common/conf" 6 | ) 7 | 8 | type Config struct { 9 | conf.ApiConf `mapstructure:",squash"` 10 | conf.JwtAuth 11 | UserRpcConf mrpc.RpcClientConf 12 | BalanceRpcConf mrpc.RpcClientConf 13 | } 14 | -------------------------------------------------------------------------------- /app/user/cmd/api/internal/handler/controllers/user.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "net/http" 5 | "strconv" 6 | 7 | "github.com/gin-gonic/gin" 8 | balancepb "mall-go/app/balance/cmd/pb" 9 | "mall-go/app/user/cmd/api/internal/logic" 10 | "mall-go/app/user/cmd/pb" 11 | "mall-go/common" 12 | "mall-go/common/response" 13 | "mall-go/pkg/di" 14 | ) 15 | 16 | type UserController struct { 17 | resp response.Response 18 | logic logic.UserLogic 19 | } 20 | 21 | type RegisterValidate struct { 22 | Mobile string `json:"mobile" validate:"required"` 23 | Nickname string `json:"nickname" validate:"required"` 24 | Password string `json:"password" validate:"required"` 25 | } 26 | 27 | func (t *UserController) Register(c *gin.Context) { 28 | var req RegisterValidate 29 | err := c.Bind(&req) 30 | if err != nil { 31 | di.Logrus().Error(err) 32 | c.JSON(http.StatusOK, t.resp.Fail("参数异常")) 33 | return 34 | } 35 | 36 | resp, err := t.logic.Register(&pb.RegisterRequest{ 37 | Mobile: req.Mobile, 38 | Nickname: req.Nickname, 39 | Password: req.Password, 40 | }) 41 | if err != nil { 42 | c.JSON(http.StatusOK, t.resp.Error(err)) 43 | return 44 | } 45 | 46 | c.JSON(http.StatusOK, t.resp.Success(resp)) 47 | } 48 | 49 | type LoginValidate struct { 50 | Mobile string `json:"mobile" validate:"required"` 51 | Password string `json:"password" validate:"required"` 52 | } 53 | 54 | // Login 登陆 55 | func (t *UserController) Login(c *gin.Context) { 56 | var req LoginValidate 57 | err := c.Bind(&req) 58 | if err != nil { 59 | di.Logrus().Error(err) 60 | c.JSON(http.StatusOK, t.resp.Fail("参数异常")) 61 | return 62 | } 63 | 64 | resp, err := t.logic.Login(&pb.LoginRequest{ 65 | Mobile: req.Mobile, 66 | Password: req.Password, 67 | }) 68 | 69 | if err != nil { 70 | c.JSON(http.StatusOK, t.resp.Error(err)) 71 | return 72 | } 73 | 74 | c.JSON(http.StatusOK, t.resp.Success(resp)) 75 | } 76 | 77 | type GetUserValidate struct { 78 | Id string `json:"id" validate:"required"` 79 | } 80 | 81 | func (t *UserController) GetUser(c *gin.Context) { 82 | var req GetUserValidate 83 | err := c.Bind(&req) 84 | if err != nil { 85 | c.JSON(http.StatusOK, t.resp.Fail("参数异常")) 86 | return 87 | } 88 | 89 | Id, err := strconv.ParseInt(req.Id, 10, 64) 90 | if err != nil { 91 | c.JSON(http.StatusOK, t.resp.Fail("parse int error")) 92 | return 93 | } 94 | resp, err := t.logic.GetUser(&pb.GetUserRequest{ 95 | Id: Id, 96 | }) 97 | if err != nil { 98 | c.JSON(http.StatusOK, t.resp.Error(err)) 99 | return 100 | } 101 | 102 | c.JSON(http.StatusOK, t.resp.Success(resp)) 103 | } 104 | 105 | type SetUserValidate struct { 106 | Nickname string `json:"nickname" validate:"required"` 107 | AvatarUrl string `json:"avatar_url" validate:"required"` 108 | Mobile string `json:"mobile" validate:"required"` 109 | Signature string `json:"signature" validate:"required"` 110 | Status int64 `json:"status" validate:"required"` 111 | Password string `json:"password" validate:"max=16"` 112 | } 113 | 114 | func (t *UserController) SetUser(c *gin.Context) { 115 | userId, err := common.GetUserId(c) 116 | if err != nil { 117 | c.JSON(http.StatusOK, t.resp.Fail(err.Error())) 118 | return 119 | } 120 | 121 | var req SetUserValidate 122 | err = c.Bind(&req) 123 | if err != nil { 124 | c.JSON(http.StatusOK, t.resp.Fail("参数异常")) 125 | return 126 | } 127 | 128 | resp, err := t.logic.SetUser(&pb.SetUserRequest{ 129 | Id: userId, 130 | Nickname: req.Nickname, 131 | AvatarUrl: req.AvatarUrl, 132 | Mobile: req.Mobile, 133 | Signature: req.Signature, 134 | Status: req.Status, 135 | Password: req.Password, 136 | }) 137 | if err != nil { 138 | c.JSON(http.StatusOK, t.resp.Error(err)) 139 | return 140 | } 141 | 142 | c.JSON(http.StatusOK, t.resp.Success(resp)) 143 | } 144 | 145 | func (t *UserController) Logout(c *gin.Context) { 146 | userId, err := common.GetUserId(c) 147 | if err != nil { 148 | c.JSON(http.StatusOK, t.resp.Fail(err.Error())) 149 | return 150 | } 151 | resp, err := t.logic.Logout(&pb.LogOutRequest{ 152 | Id: userId, 153 | }) 154 | if err != nil { 155 | c.JSON(http.StatusOK, t.resp.Error(err)) 156 | return 157 | } 158 | 159 | c.JSON(http.StatusOK, t.resp.Success(resp)) 160 | } 161 | 162 | func (t *UserController) GetBalance(c *gin.Context) { 163 | userId, err := common.GetUserId(c) 164 | if err != nil { 165 | c.JSON(http.StatusOK, t.resp.Fail(err.Error())) 166 | return 167 | } 168 | resp, err := t.logic.GetBalance(&balancepb.GetBalanceRequest{ 169 | UserId: userId, 170 | Type: 1, 171 | }) 172 | if err != nil { 173 | c.JSON(http.StatusOK, t.resp.Error(err)) 174 | return 175 | } 176 | 177 | c.JSON(http.StatusOK, t.resp.Success(resp)) 178 | } 179 | 180 | type GetBalanceChangeListRequest struct { 181 | Type int32 `json:"type" validate:"min=0"` 182 | TypeAmount int32 `json:"type_amount" validate:"min=0"` 183 | Page int32 `json:"page" validate:"required"` 184 | PageSize int32 `json:"page_size" validate:"required"` 185 | } 186 | 187 | func (t *UserController) GetBalanceChangeList(c *gin.Context) { 188 | userId, err := common.GetUserId(c) 189 | if err != nil { 190 | c.JSON(http.StatusOK, t.resp.Fail(err.Error())) 191 | return 192 | } 193 | balance, err := t.logic.GetBalance(&balancepb.GetBalanceRequest{ 194 | UserId: userId, 195 | Type: 1, 196 | }) 197 | if err != nil { 198 | c.JSON(http.StatusOK, t.resp.Error(err)) 199 | return 200 | } 201 | var req GetBalanceChangeListRequest 202 | if err := c.Bind(&req); err != nil { 203 | c.JSON(http.StatusOK, t.resp.Fail("参数异常")) 204 | return 205 | } 206 | 207 | resp, err := t.logic.GetBalanceChangeList(&balancepb.GetBalanceChangeListRequest{ 208 | Id: balance.Id, 209 | Type: req.Type, 210 | TypeAmount: req.TypeAmount, 211 | Page: req.Page, 212 | PageSize: req.PageSize, 213 | }) 214 | if err != nil { 215 | c.JSON(http.StatusOK, t.resp.Error(err)) 216 | return 217 | } 218 | 219 | c.JSON(http.StatusOK, t.resp.Success(resp)) 220 | } 221 | 222 | func (t *UserController) Recharge(c *gin.Context) { 223 | // TODO 224 | } 225 | -------------------------------------------------------------------------------- /app/user/cmd/api/internal/handler/routes.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/gin-gonic/gin/binding" 6 | "mall-go/app/user/cmd/api/internal/handler/controllers" 7 | "mall-go/app/user/cmd/api/internal/svc" 8 | "mall-go/common/middleware" 9 | validator2 "mall-go/pkg/validator" 10 | ) 11 | 12 | func Load(router *gin.Engine) { 13 | // register validate 14 | binding.Validator = new(validator2.DefaultValidator) 15 | router.GET("/", func(ctx *gin.Context) { 16 | ctx.String(200, "hello") 17 | }) 18 | router.Use(gin.Recovery(), middleware.CorsMiddleware()) 19 | 20 | user := controllers.UserController{} 21 | // 注册接口 22 | router.POST("/user/register", func(ctx *gin.Context) { 23 | user.Register(ctx) 24 | }) 25 | // 查看用户详情 26 | router.POST("/user/getUser", func(ctx *gin.Context) { 27 | user.GetUser(ctx) 28 | }) 29 | // 登陆 30 | router.POST("/user/login", func(ctx *gin.Context) { 31 | user.Login(ctx) 32 | }) 33 | v1 := router.Group("/user", middleware.AuthMiddleware(svc.Context.Jwt.Secret)) 34 | { 35 | // 编辑用户信息 36 | v1.POST("/setUser", func(ctx *gin.Context) { 37 | user.SetUser(ctx) 38 | }) 39 | // 注销用户 40 | v1.POST("/logout", func(ctx *gin.Context) { 41 | user.Logout(ctx) 42 | }) 43 | // 查询余额 44 | v1.POST("/getBalance", func(ctx *gin.Context) { 45 | user.GetBalance(ctx) 46 | }) 47 | // 查询余额变动记录 48 | v1.POST("/getBalanceChangeList", func(ctx *gin.Context) { 49 | user.GetBalanceChangeList(ctx) 50 | }) 51 | // 充值 52 | v1.POST("/recharge", func(ctx *gin.Context) { 53 | user.Recharge(ctx) 54 | }) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/user/cmd/api/internal/logic/user.go: -------------------------------------------------------------------------------- 1 | package logic 2 | 3 | import ( 4 | "context" 5 | 6 | balancepb "mall-go/app/balance/cmd/pb" 7 | "mall-go/app/user/cmd/api/internal/svc" 8 | "mall-go/app/user/cmd/pb" 9 | ) 10 | 11 | type UserLogic struct { 12 | } 13 | 14 | func (t *UserLogic) Register(req *pb.RegisterRequest) (*pb.RegisterResponse, error) { 15 | resp, err := svc.Context.UserRpc.Register(context.Background(), req) 16 | if err != nil { 17 | return nil, err 18 | } 19 | 20 | return resp, nil 21 | } 22 | 23 | func (t *UserLogic) GetUser(req *pb.GetUserRequest) (*pb.GetUserResponse, error) { 24 | resp, err := svc.Context.UserRpc.GetUser(context.Background(), req) 25 | if err != nil { 26 | return nil, err 27 | } 28 | return resp, nil 29 | } 30 | 31 | func (t *UserLogic) SetUser(req *pb.SetUserRequest) (*pb.SetUserResponse, error) { 32 | resp, err := svc.Context.UserRpc.SetUser(context.Background(), req) 33 | if err != nil { 34 | return nil, err 35 | } 36 | return resp, nil 37 | } 38 | 39 | func (t *UserLogic) Logout(req *pb.LogOutRequest) (*pb.LogOutResponse, error) { 40 | resp, err := svc.Context.UserRpc.Logout(context.Background(), req) 41 | if err != nil { 42 | return nil, err 43 | } 44 | 45 | return resp, nil 46 | } 47 | 48 | type LoginResponse struct { 49 | Jwt string `json:"jwt"` 50 | UserId int64 `json:"user_id"` 51 | Mobile string `json:"mobile"` 52 | Nickname string `json:"nickname"` 53 | AvatarUrl string `json:"avatar_url"` 54 | } 55 | 56 | func (t *UserLogic) Login(req *pb.LoginRequest) (res *LoginResponse, err error) { 57 | resp, err := svc.Context.UserRpc.Login(context.Background(), req) 58 | if err != nil { 59 | return nil, err 60 | } 61 | jwt, err := svc.Context.Jwt.GenerateJwt(resp.Id) 62 | if err != nil { 63 | return nil, err 64 | } 65 | 66 | return &LoginResponse{ 67 | Jwt: jwt, 68 | UserId: resp.Id, 69 | Mobile: resp.Mobile, 70 | Nickname: resp.Nickname, 71 | AvatarUrl: resp.AvatarUrl, 72 | }, nil 73 | } 74 | 75 | type GetBalanceResponse struct { 76 | Id int64 `json:"id"` 77 | UserId int64 `json:"user_id"` 78 | Type int32 `json:"type"` 79 | Available uint64 `json:"available" default:"0"` 80 | Frozen uint64 `json:"frozen" default:"0"` 81 | } 82 | 83 | func (t *UserLogic) GetBalance(in *balancepb.GetBalanceRequest) (*GetBalanceResponse, error) { 84 | resp, err := svc.Context.BalanceRpc.GetBalance(context.Background(), in) 85 | if err != nil { 86 | return nil, err 87 | } 88 | return &GetBalanceResponse{ 89 | Id: resp.Id, 90 | UserId: resp.UserId, 91 | Type: resp.Type, 92 | Available: resp.Available, 93 | Frozen: resp.Frozen, 94 | }, nil 95 | } 96 | 97 | func (t *UserLogic) GetBalanceChangeList(in *balancepb.GetBalanceChangeListRequest) (*balancepb.GetBalanceChangeListResponse, error) { 98 | resp, err := svc.Context.BalanceRpc.GetBalanceChangeList(context.Background(), in) 99 | if err != nil { 100 | return nil, err 101 | } 102 | 103 | return resp, nil 104 | } 105 | -------------------------------------------------------------------------------- /app/user/cmd/api/internal/svc/serviceContext.go: -------------------------------------------------------------------------------- 1 | package svc 2 | 3 | import ( 4 | "github.com/mix-plus/go-mixplus/mrpc" 5 | balancepb "mall-go/app/balance/cmd/pb" 6 | "mall-go/app/user/cmd/api/internal/config" 7 | "mall-go/app/user/cmd/pb" 8 | "mall-go/pkg/jwtx" 9 | ) 10 | 11 | var Context *ServiceContext 12 | 13 | type ServiceContext struct { 14 | Config config.Config 15 | Jwt *jwtx.Jwt 16 | 17 | UserRpc pb.UserClient 18 | BalanceRpc balancepb.BalanceClient 19 | } 20 | 21 | func NewServiceContext(c config.Config) *ServiceContext { 22 | return &ServiceContext{ 23 | Config: c, 24 | Jwt: jwtx.NewJwt(c.JwtAuth), 25 | UserRpc: pb.NewUserClient(mrpc.MustNewClient(c.UserRpcConf).Conn()), 26 | BalanceRpc: balancepb.NewBalanceClient(mrpc.MustNewClient(c.BalanceRpcConf).Conn()), 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/user/cmd/api/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "fmt" 7 | "os" 8 | "os/signal" 9 | "strings" 10 | "syscall" 11 | "time" 12 | 13 | "github.com/gin-gonic/gin" 14 | "mall-go/app/user/cmd/api/internal/config" 15 | "mall-go/app/user/cmd/api/internal/handler" 16 | "mall-go/app/user/cmd/api/internal/svc" 17 | conf "mall-go/common/conf" 18 | "mall-go/pkg/di" 19 | _ "mall-go/pkg/di" 20 | ) 21 | 22 | var configFile = flag.String("f", "etc/user.yaml", "the config file") 23 | 24 | func main() { 25 | flag.Parse() 26 | 27 | var c config.Config 28 | 29 | if err := conf.MustLoad(*configFile, &c); err != nil { 30 | panic(err) 31 | } 32 | 33 | logger := di.Zap() 34 | server := di.Server() 35 | 36 | svc.Context = svc.NewServiceContext(c) 37 | 38 | gin.SetMode(c.Mode) 39 | 40 | router := gin.New() 41 | if c.Mode != gin.ReleaseMode { 42 | handlerFunc := gin.LoggerWithConfig(gin.LoggerConfig{ 43 | Formatter: func(params gin.LogFormatterParams) string { 44 | return fmt.Sprintf("%s|%s|%d|%s\n", 45 | params.Method, 46 | params.Path, 47 | params.StatusCode, 48 | params.ClientIP, 49 | ) 50 | }, 51 | Output: &di.ZapOutput{Logger: logger}, 52 | }) 53 | router.Use(handlerFunc) 54 | } 55 | 56 | server.Addr = c.Addr 57 | server.Handler = router 58 | 59 | handler.Load(router) 60 | 61 | // signal 62 | ch := make(chan os.Signal) 63 | signal.Notify(ch, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM) 64 | go func() { 65 | <-ch 66 | logger.Info("Server shutdown") 67 | ctx, _ := context.WithTimeout(context.Background(), 10*time.Second) 68 | if err := server.Shutdown(ctx); err != nil { 69 | logger.Errorf("Server shutdown error: %s", err) 70 | } 71 | }() 72 | 73 | logger.Infof("Server start at %s", server.Addr) 74 | if err := server.ListenAndServe(); err != nil && !strings.Contains(err.Error(), "http: Server closed") { 75 | panic(err) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /app/user/cmd/buf.gen.yaml: -------------------------------------------------------------------------------- 1 | version: v1 2 | plugins: 3 | - plugin: go 4 | out: . 5 | opt: paths=source_relative 6 | - plugin: go-grpc 7 | out: . 8 | opt: paths=source_relative 9 | -------------------------------------------------------------------------------- /app/user/cmd/pb/buf.yaml: -------------------------------------------------------------------------------- 1 | version: v1 2 | breaking: 3 | use: 4 | - FILE 5 | lint: 6 | use: 7 | - DEFAULT 8 | -------------------------------------------------------------------------------- /app/user/cmd/pb/user.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package mall.rpc.user; 4 | 5 | option go_package = "/;pb"; 6 | 7 | service User { 8 | // 注册 9 | rpc Register(RegisterRequest) returns (RegisterResponse) {} 10 | // 详情 11 | rpc GetUser(GetUserRequest) returns (GetUserResponse) {} 12 | // 编辑 13 | rpc SetUser(SetUserRequest) returns (SetUserResponse) {} 14 | // 注销 15 | rpc Logout(LogOutRequest) returns (LogOutResponse) {} 16 | // 登陆 17 | rpc Login(LoginRequest) returns (LoginResponse) {} 18 | } 19 | 20 | // 注册用户请求结构 21 | message RegisterRequest { 22 | string mobile = 1; 23 | string nickname = 2; 24 | string password = 3; 25 | } 26 | 27 | // 注册用户返回结构 28 | message RegisterResponse { 29 | int64 id = 1; 30 | } 31 | 32 | // 获取用户详情请求结构 33 | message GetUserRequest { 34 | int64 id = 1; 35 | } 36 | 37 | // 获取用户详情返回结构 38 | message GetUserResponse { 39 | int64 id = 1; 40 | string nickname = 2; 41 | string avatar_url = 3; 42 | string mobile = 4; 43 | string signature = 5; 44 | int64 status = 6; 45 | int64 is_delete = 7; 46 | string created_at = 8; 47 | string updated_at = 9; 48 | } 49 | 50 | // 编辑用户请求结构 51 | message SetUserRequest { 52 | int64 id = 1; 53 | string nickname = 2; 54 | string avatar_url = 3; 55 | string mobile = 4; 56 | string signature = 5; 57 | int64 status = 6; 58 | int64 is_delete = 7; 59 | string updated_at = 9; 60 | string password = 10; 61 | } 62 | 63 | // 编辑用户返回结构 64 | message SetUserResponse { 65 | bool status = 1; 66 | } 67 | 68 | // 注销用户请求结构 69 | message LogOutRequest { 70 | int64 id = 1; 71 | } 72 | 73 | // 注销用户返回结构 74 | message LogOutResponse { 75 | bool status = 1; 76 | } 77 | 78 | // 登陆请求结构 79 | message LoginRequest { 80 | string mobile = 1; 81 | string password = 2; 82 | } 83 | 84 | // 登陆返回结构 85 | message LoginResponse { 86 | int64 id = 1; 87 | string nickname = 2; 88 | string avatar_url = 3; 89 | string mobile = 4; 90 | } 91 | -------------------------------------------------------------------------------- /app/user/cmd/rpc/.gitignore: -------------------------------------------------------------------------------- 1 | # goland project files 2 | .idea 3 | 4 | -------------------------------------------------------------------------------- /app/user/cmd/rpc/etc/user.yaml: -------------------------------------------------------------------------------- 1 | Name: user-rpc 2 | Addr: ":10002" 3 | Mode: "release" 4 | 5 | RedisConf: 6 | Addr: host.docker.internal:6379 7 | Password: 8 | DataBase: 1 9 | Timeout: 60 10 | 11 | 12 | DbConf: 13 | # host.docker.internal 14 | DSN: root:root@tcp(host.docker.internal:3306)/mall_go_user?charset=utf8&loc=Asia%2FShanghai&parseTime=true&timeout=10s 15 | -------------------------------------------------------------------------------- /app/user/cmd/rpc/internal/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/mix-plus/go-mixplus/mrpc" 5 | "mall-go/common/conf" 6 | ) 7 | 8 | type Config struct { 9 | mrpc.RpcServerConf `mapstructure:",squash"` 10 | conf.DbConf 11 | conf.RedisConf 12 | } 13 | -------------------------------------------------------------------------------- /app/user/cmd/rpc/internal/logic/user.go: -------------------------------------------------------------------------------- 1 | package logic 2 | 3 | import ( 4 | "errors" 5 | "time" 6 | 7 | "mall-go/app/user/cmd/pb" 8 | "mall-go/app/user/cmd/rpc/internal/model" 9 | "mall-go/pkg/hash" 10 | ) 11 | 12 | type UserLogic struct { 13 | model model.MallUser 14 | } 15 | 16 | func (l *UserLogic) GetUser(id int64) (*model.MallUser, error) { 17 | user, err := l.model.FindByWhere([]string{ 18 | "id", "nickname", "avatar_url", "mobile", "signature", "status", "is_delete", "created_at", "updated_at", 19 | }, []string{ 20 | "id = ?", 21 | }, []any{ 22 | id, 23 | }, []string{}) 24 | if err != nil { 25 | return user, errors.New("查询出错") 26 | } 27 | if user.IsDelete == 1 { 28 | return user, errors.New("用户已注销") 29 | } 30 | return user, nil 31 | } 32 | 33 | func (l *UserLogic) SetUser(in *pb.SetUserRequest) (bool, error) { 34 | user, err := l.model.FindById(in.Id) 35 | if err != nil { 36 | return false, err 37 | } 38 | if in.Nickname != "" { 39 | user.Nickname = in.Nickname 40 | } 41 | if in.AvatarUrl != "" { 42 | user.AvatarUrl = in.AvatarUrl 43 | } 44 | if in.Status != 0 { 45 | user.Status = int8(in.Status) 46 | } 47 | if in.Mobile != "" { 48 | user.Mobile = in.Mobile 49 | } 50 | if in.Signature != "" { 51 | user.Signature = in.Signature 52 | } 53 | if in.Password != "" { 54 | hashPass, err := hash.PasswordHash(in.Password) 55 | if err != nil { 56 | return false, err 57 | } 58 | user.Password = hashPass 59 | } 60 | user.UpdatedAt = time.Now() 61 | rows, err := l.model.UpdateByWhere(user, []string{ 62 | "id = ?", 63 | }, []any{ 64 | in.Id, 65 | }) 66 | if err != nil { 67 | return false, nil 68 | } 69 | if rows == 0 { 70 | return true, nil 71 | } 72 | return true, nil 73 | } 74 | 75 | func (l *UserLogic) Logout(id int64) (bool, error) { 76 | user, err := l.model.FindById(id) 77 | if err != nil { 78 | return false, nil 79 | } 80 | 81 | user.IsDelete = 1 82 | user.UpdatedAt = time.Now() 83 | 84 | rows, err := l.model.UpdateByWhere(user, []string{ 85 | "id = ?", 86 | }, []any{ 87 | id, 88 | }) 89 | if err != nil { 90 | return false, err 91 | } 92 | if rows == 0 { 93 | return false, nil 94 | } 95 | return true, nil 96 | } 97 | 98 | func (l *UserLogic) Register(in *pb.RegisterRequest) (int64, error) { 99 | if in.Password != "" { 100 | hashPass, err := hash.PasswordHash(in.Password) 101 | if err != nil { 102 | return 0, err 103 | } 104 | in.Password = hashPass 105 | } 106 | 107 | return l.model.CreateOne(&model.MallUser{ 108 | Nickname: in.Nickname, 109 | Password: in.Password, 110 | Mobile: in.Mobile, 111 | Status: 1, 112 | CreatedAt: time.Now(), 113 | UpdatedAt: time.Now(), 114 | }) 115 | } 116 | 117 | func (l *UserLogic) Login(in *pb.LoginRequest) (*model.MallUser, error) { 118 | user, err := l.model.FindByWhere([]string{ 119 | "id", "nickname", "mobile", "password", "avatar_url", 120 | }, []string{ 121 | "mobile = ?", 122 | }, []any{ 123 | in.Mobile, 124 | }, []string{}) 125 | 126 | if err != nil { 127 | return nil, err 128 | } 129 | if user.ID == 0 { 130 | return nil, errors.New("用户不存在") 131 | } 132 | if user.IsDelete == 1 { 133 | return nil, errors.New("用户已经注销") 134 | } 135 | 136 | if hash.PasswordVerify(in.Password, user.Password) { 137 | return nil, errors.New("密码错误") 138 | } 139 | 140 | return user, nil 141 | } 142 | -------------------------------------------------------------------------------- /app/user/cmd/rpc/internal/model/user.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "time" 7 | 8 | "mall-go/app/user/cmd/rpc/internal/svc" 9 | ) 10 | 11 | // MallUser 用户表 12 | type MallUser struct { 13 | ID int64 `xsql:"id"` 14 | Nickname string `xsql:"nickname"` // 用户名 15 | AvatarUrl string `xsql:"avatar_url"` // 头像 16 | Password string `xsql:"password"` // 密码 17 | Mobile string `xsql:"mobile"` // 手机号 18 | Signature string `xsql:"signature"` // 个性签名 19 | Status int8 `xsql:"status"` // 状态 1正常 2禁用 20 | IsDelete int8 `xsql:"is_delete"` // 是否删除 1删除 21 | CreatedAt time.Time `xsql:"created_at"` 22 | UpdatedAt time.Time `xsql:"updated_at"` 23 | } 24 | 25 | // TableName 表名称 26 | func (MallUser) TableName() string { 27 | return "mall_user" 28 | } 29 | 30 | func (m *MallUser) FindById(id int64) (*MallUser, error) { 31 | db := svc.Context.Xsql 32 | var user MallUser 33 | err := db.First(&user, fmt.Sprintf("SELECT * FROM %s WHERE `id` = ? LIMIT 1;", m.TableName()), id) 34 | if err != nil { 35 | return nil, err 36 | } 37 | return &user, nil 38 | } 39 | 40 | func (m *MallUser) FindByWhere(column []string, where []string, args []any, opts []string) (*MallUser, error) { 41 | db := svc.Context.Xsql 42 | var user MallUser 43 | sql := fmt.Sprintf("SELECT %s FROM `%s` WHERE %s %s LIMIT 1;", 44 | strings.Join(column, ", "), 45 | m.TableName(), 46 | strings.Join(where, " AND "), 47 | strings.Join(opts, " ")) 48 | err := db.First(&user, sql, 49 | args..., 50 | ) 51 | if err != nil { 52 | return nil, err 53 | } 54 | return &user, nil 55 | } 56 | 57 | func (m *MallUser) CreateOne(user *MallUser) (int64, error) { 58 | db := svc.Context.Xsql 59 | row, err := db.Insert(user) 60 | if err != nil { 61 | return 0, err 62 | } 63 | return row.LastInsertId() 64 | } 65 | 66 | func (m *MallUser) CreateAll(user []*MallUser) (int64, error) { 67 | db := svc.Context.Xsql 68 | row, err := db.BatchInsert(user) 69 | if err != nil { 70 | return 0, err 71 | } 72 | return row.LastInsertId() 73 | } 74 | 75 | func (m *MallUser) GetManyByWhere(column []string, where []string, args []any, opts []string) ([]*MallUser, error) { 76 | db := svc.Context.Xsql 77 | var user []*MallUser 78 | err := db.Find(&user, fmt.Sprintf("SELECT %s FROM `%s` WHERE %s %s;", 79 | strings.Join(column, ", "), 80 | m.TableName(), 81 | strings.Join(where, " AND "), 82 | strings.Join(opts, " ")), 83 | args..., 84 | ) 85 | if err != nil { 86 | return nil, err 87 | } 88 | return user, nil 89 | } 90 | 91 | func (m *MallUser) UpdateByWhere(user *MallUser, where []string, args []any) (int64, error) { 92 | db := svc.Context.Xsql 93 | rows, err := db.Update(user, strings.Join(where, ", "), args...) 94 | if err != nil { 95 | return 0, err 96 | } 97 | return rows.RowsAffected() 98 | } 99 | 100 | func (m *MallUser) DeleteByWhere(where []string, args []any) (int64, error) { 101 | db := svc.Context.Xsql 102 | result, err := db.Exec(fmt.Sprintf("DELETE FROM `%s` WHERE %s;", 103 | m.TableName(), strings.Join(where, " AND ")), args...) 104 | if err != nil { 105 | return 0, err 106 | } 107 | return result.RowsAffected() 108 | } 109 | -------------------------------------------------------------------------------- /app/user/cmd/rpc/internal/server/userServer.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | 7 | "mall-go/app/user/cmd/pb" 8 | "mall-go/app/user/cmd/rpc/internal/logic" 9 | "mall-go/app/user/cmd/rpc/internal/svc" 10 | "mall-go/common/errcode" 11 | ) 12 | 13 | // UserServer 用户接口实现 14 | type UserServer struct { 15 | svcCtx *svc.ServiceContext 16 | logic logic.UserLogic 17 | } 18 | 19 | func NewUserServer(svcCtx *svc.ServiceContext) *UserServer { 20 | return &UserServer{ 21 | svcCtx: svcCtx, 22 | } 23 | } 24 | 25 | func (t *UserServer) GetUser(ctx context.Context, in *pb.GetUserRequest) (*pb.GetUserResponse, error) { 26 | if in.Id == 0 { 27 | return nil, errcode.GrpcError(errors.New("id不能为空"), 100) 28 | } 29 | user, err := t.logic.GetUser(in.Id) 30 | if err != nil { 31 | return nil, errcode.GrpcError(err, 100) 32 | } 33 | return &pb.GetUserResponse{ 34 | Id: user.ID, 35 | Nickname: user.Nickname, 36 | AvatarUrl: user.AvatarUrl, 37 | Mobile: user.Mobile, 38 | Signature: user.Signature, 39 | Status: int64(user.Status), 40 | IsDelete: int64(user.IsDelete), 41 | CreatedAt: user.CreatedAt.String(), 42 | UpdatedAt: user.UpdatedAt.String(), 43 | }, nil 44 | } 45 | 46 | func (t *UserServer) SetUser(ctx context.Context, in *pb.SetUserRequest) (*pb.SetUserResponse, error) { 47 | user, err := t.logic.SetUser(in) 48 | if err != nil { 49 | return nil, errcode.GrpcError(err, 100) 50 | } 51 | 52 | return &pb.SetUserResponse{ 53 | Status: user, 54 | }, nil 55 | } 56 | 57 | func (t *UserServer) Logout(ctx context.Context, in *pb.LogOutRequest) (*pb.LogOutResponse, error) { 58 | logout, err := t.logic.Logout(in.Id) 59 | if err != nil { 60 | return nil, errcode.GrpcError(err, 100) 61 | } 62 | 63 | return &pb.LogOutResponse{ 64 | Status: logout, 65 | }, nil 66 | } 67 | 68 | func (t *UserServer) Register(ctx context.Context, in *pb.RegisterRequest) (*pb.RegisterResponse, error) { 69 | id, err := t.logic.Register(in) 70 | if err != nil { 71 | return nil, errcode.GrpcError(err, 100) 72 | } 73 | 74 | return &pb.RegisterResponse{ 75 | Id: id, 76 | }, nil 77 | } 78 | 79 | func (t *UserServer) Login(ctx context.Context, in *pb.LoginRequest) (*pb.LoginResponse, error) { 80 | user, err := t.logic.Login(in) 81 | if err != nil { 82 | return nil, errcode.GrpcError(err, 100) 83 | } 84 | return &pb.LoginResponse{ 85 | Id: user.ID, 86 | Nickname: user.Nickname, 87 | AvatarUrl: user.AvatarUrl, 88 | Mobile: user.Mobile, 89 | }, nil 90 | } 91 | -------------------------------------------------------------------------------- /app/user/cmd/rpc/internal/svc/serviceContext.go: -------------------------------------------------------------------------------- 1 | package svc 2 | 3 | import ( 4 | "github.com/go-redis/redis" 5 | "github.com/mix-go/xsql" 6 | "gorm.io/gorm" 7 | "mall-go/app/user/cmd/rpc/internal/config" 8 | "mall-go/pkg" 9 | ) 10 | 11 | var Context *ServiceContext 12 | 13 | type ServiceContext struct { 14 | Config config.Config 15 | Redis *redis.Client 16 | Db *gorm.DB 17 | Xsql *xsql.DB 18 | } 19 | 20 | func NewServiceContext(c config.Config) *ServiceContext { 21 | return &ServiceContext{ 22 | Config: c, 23 | Redis: pkg.NewRedis(c.RedisConf), 24 | Db: pkg.NewGorm(c.DbConf), 25 | Xsql: pkg.NewXsql(c.DbConf), 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/user/cmd/rpc/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "mall-go/app/user/cmd/pb" 6 | "mall-go/app/user/cmd/rpc/internal/config" 7 | "mall-go/app/user/cmd/rpc/internal/server" 8 | "mall-go/app/user/cmd/rpc/internal/svc" 9 | "mall-go/common/conf" 10 | 11 | "github.com/mix-plus/go-mixplus/mrpc" 12 | "google.golang.org/grpc" 13 | 14 | _ "mall-go/pkg/di" 15 | ) 16 | 17 | var configFile = flag.String("f", "etc/user.yaml", "the config file") 18 | 19 | func main() { 20 | flag.Parse() 21 | 22 | var c config.Config 23 | 24 | if err := conf.MustLoad(*configFile, &c); err != nil { 25 | panic(err) 26 | } 27 | 28 | ctx := svc.NewServiceContext(c) 29 | srv := server.NewUserServer(ctx) 30 | svc.Context = ctx 31 | 32 | s := mrpc.MustNewServer(c.RpcServerConf, func(g *grpc.Server) { 33 | pb.RegisterUserServer(g, srv) 34 | }) 35 | 36 | defer s.Stop() 37 | 38 | s.Start() 39 | } 40 | -------------------------------------------------------------------------------- /build/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /common/README.md: -------------------------------------------------------------------------------- 1 | 通用组件 error、middleware、interceptor、tool、ctxdata等 -------------------------------------------------------------------------------- /common/conf/api.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | type ApiConf struct { 4 | ServiceConf `mapstructure:",squash"` 5 | Addr string `json:"Addr,default=:8080"` 6 | Mode string `json:"Mode,default=release"` 7 | Timeout int64 `json:"Timeout,default=60"` 8 | } 9 | 10 | type ServiceConf struct { 11 | Name string `json:"Name"` 12 | Mode string `json:"Mode,default=release"` 13 | MetricsUrl string `json:"MetricsUrl"` 14 | Prometheus string `json:"Prometheus"` 15 | } 16 | -------------------------------------------------------------------------------- /common/conf/db.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | type DbConf struct { 4 | DSN string `json:"dsn"` 5 | } 6 | -------------------------------------------------------------------------------- /common/conf/jwt.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | type JwtAuth struct { 4 | Secret string `json:"Secret"` 5 | } 6 | -------------------------------------------------------------------------------- /common/conf/redis.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | type RedisConf struct { 4 | Addr string `json:"addr,default=127.0.0.1:6379"` 5 | Pass string `json:"pass,default="` 6 | DataBase int64 `json:"database,default=1"` 7 | Timeout int64 `json:"timeout,default=60"` 8 | } 9 | -------------------------------------------------------------------------------- /common/conf/viper.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import ( 4 | "github.com/spf13/viper" 5 | ) 6 | 7 | type ( 8 | // Option defines the method to customize the config options. 9 | Option func(opt *options) 10 | 11 | options struct { 12 | env bool 13 | } 14 | ) 15 | 16 | func MustLoad(path string, v interface{}, opts ...Option) error { 17 | vi := viper.New() 18 | 19 | viper.SetConfigType("yaml") 20 | 21 | vi.SetConfigFile(path) 22 | 23 | if err := vi.ReadInConfig(); err != nil { 24 | return err 25 | } 26 | if err := vi.Unmarshal(&v); err != nil { 27 | return err 28 | } 29 | 30 | return nil 31 | } 32 | -------------------------------------------------------------------------------- /common/errcode/error.go: -------------------------------------------------------------------------------- 1 | package errcode 2 | 3 | import ( 4 | "google.golang.org/grpc/codes" 5 | "google.golang.org/grpc/status" 6 | "mall-go/common/errcode/pb" 7 | ) 8 | 9 | func GrpcError(rpcError error, errCode int32) error { 10 | errStatus, err := status.New(codes.Aborted, rpcError.Error()).WithDetails(&pb.Error{ 11 | Code: errCode, 12 | Message: rpcError.Error(), 13 | }) 14 | if err != nil { 15 | return status.New(codes.Aborted, rpcError.Error()).Err() 16 | } 17 | return errStatus.Err() 18 | } 19 | -------------------------------------------------------------------------------- /common/errcode/pb/error.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.26.0 4 | // protoc v3.17.2 5 | // source: error.proto 6 | 7 | package pb 8 | 9 | import ( 10 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 | reflect "reflect" 13 | sync "sync" 14 | ) 15 | 16 | const ( 17 | // Verify that this generated code is sufficiently up-to-date. 18 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 19 | // Verify that runtime/protoimpl is sufficiently up-to-date. 20 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 21 | ) 22 | 23 | type Error struct { 24 | state protoimpl.MessageState 25 | sizeCache protoimpl.SizeCache 26 | unknownFields protoimpl.UnknownFields 27 | 28 | Code int32 `protobuf:"varint,1,opt,name=code,proto3" json:"code,omitempty"` 29 | Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` 30 | } 31 | 32 | func (x *Error) Reset() { 33 | *x = Error{} 34 | if protoimpl.UnsafeEnabled { 35 | mi := &file_error_proto_msgTypes[0] 36 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 37 | ms.StoreMessageInfo(mi) 38 | } 39 | } 40 | 41 | func (x *Error) String() string { 42 | return protoimpl.X.MessageStringOf(x) 43 | } 44 | 45 | func (*Error) ProtoMessage() {} 46 | 47 | func (x *Error) ProtoReflect() protoreflect.Message { 48 | mi := &file_error_proto_msgTypes[0] 49 | if protoimpl.UnsafeEnabled && x != nil { 50 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 51 | if ms.LoadMessageInfo() == nil { 52 | ms.StoreMessageInfo(mi) 53 | } 54 | return ms 55 | } 56 | return mi.MessageOf(x) 57 | } 58 | 59 | // Deprecated: Use Error.ProtoReflect.Descriptor instead. 60 | func (*Error) Descriptor() ([]byte, []int) { 61 | return file_error_proto_rawDescGZIP(), []int{0} 62 | } 63 | 64 | func (x *Error) GetCode() int32 { 65 | if x != nil { 66 | return x.Code 67 | } 68 | return 0 69 | } 70 | 71 | func (x *Error) GetMessage() string { 72 | if x != nil { 73 | return x.Message 74 | } 75 | return "" 76 | } 77 | 78 | var File_error_proto protoreflect.FileDescriptor 79 | 80 | var file_error_proto_rawDesc = []byte{ 81 | 0x0a, 0x0b, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x35, 0x0a, 82 | 0x05, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 83 | 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 84 | 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 85 | 0x73, 0x61, 0x67, 0x65, 0x42, 0x06, 0x5a, 0x04, 0x2f, 0x3b, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 86 | 0x6f, 0x74, 0x6f, 0x33, 87 | } 88 | 89 | var ( 90 | file_error_proto_rawDescOnce sync.Once 91 | file_error_proto_rawDescData = file_error_proto_rawDesc 92 | ) 93 | 94 | func file_error_proto_rawDescGZIP() []byte { 95 | file_error_proto_rawDescOnce.Do(func() { 96 | file_error_proto_rawDescData = protoimpl.X.CompressGZIP(file_error_proto_rawDescData) 97 | }) 98 | return file_error_proto_rawDescData 99 | } 100 | 101 | var file_error_proto_msgTypes = make([]protoimpl.MessageInfo, 1) 102 | var file_error_proto_goTypes = []interface{}{ 103 | (*Error)(nil), // 0: Error 104 | } 105 | var file_error_proto_depIdxs = []int32{ 106 | 0, // [0:0] is the sub-list for method output_type 107 | 0, // [0:0] is the sub-list for method input_type 108 | 0, // [0:0] is the sub-list for extension type_name 109 | 0, // [0:0] is the sub-list for extension extendee 110 | 0, // [0:0] is the sub-list for field type_name 111 | } 112 | 113 | func init() { file_error_proto_init() } 114 | func file_error_proto_init() { 115 | if File_error_proto != nil { 116 | return 117 | } 118 | if !protoimpl.UnsafeEnabled { 119 | file_error_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 120 | switch v := v.(*Error); i { 121 | case 0: 122 | return &v.state 123 | case 1: 124 | return &v.sizeCache 125 | case 2: 126 | return &v.unknownFields 127 | default: 128 | return nil 129 | } 130 | } 131 | } 132 | type x struct{} 133 | out := protoimpl.TypeBuilder{ 134 | File: protoimpl.DescBuilder{ 135 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 136 | RawDescriptor: file_error_proto_rawDesc, 137 | NumEnums: 0, 138 | NumMessages: 1, 139 | NumExtensions: 0, 140 | NumServices: 0, 141 | }, 142 | GoTypes: file_error_proto_goTypes, 143 | DependencyIndexes: file_error_proto_depIdxs, 144 | MessageInfos: file_error_proto_msgTypes, 145 | }.Build() 146 | File_error_proto = out.File 147 | file_error_proto_rawDesc = nil 148 | file_error_proto_goTypes = nil 149 | file_error_proto_depIdxs = nil 150 | } 151 | -------------------------------------------------------------------------------- /common/errcode/pb/error.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = "/;pb"; 4 | 5 | message Error { 6 | int32 code = 1; 7 | string message = 2; 8 | } -------------------------------------------------------------------------------- /common/functions.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "errors" 5 | "os" 6 | "strconv" 7 | 8 | "github.com/gin-gonic/gin" 9 | ) 10 | 11 | func GetUserId(c *gin.Context) (int64, error) { 12 | value, ok := c.Get("userId") 13 | if !ok { 14 | return 0, errors.New("获取用户ID失败") 15 | } 16 | userId, err := strconv.ParseInt(value.(string), 10, 64) 17 | if err != nil { 18 | return 0, errors.New("解析用户ID失败") 19 | } 20 | 21 | return userId, nil 22 | } 23 | 24 | func GetCWD() string { 25 | cwd, _ := os.Getwd() 26 | return cwd 27 | } 28 | 29 | func SubStr(str string, start int, length int) string { 30 | if length == 0 { 31 | return str[start:] 32 | } 33 | if start == 0 { 34 | return str[:length] 35 | } 36 | return str[start:length] 37 | } 38 | -------------------------------------------------------------------------------- /common/middleware/auth.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "strings" 7 | 8 | "github.com/gin-gonic/gin" 9 | "github.com/golang-jwt/jwt/v4" 10 | ) 11 | 12 | func AuthMiddleware(secret string) gin.HandlerFunc { 13 | return func(c *gin.Context) { 14 | // 获取 token 15 | tokenString := c.GetHeader("Authorization") 16 | if strings.Index(tokenString, "Bearer ") != 0 { 17 | c.JSON(http.StatusInternalServerError, gin.H{ 18 | "status": http.StatusInternalServerError, 19 | "message": "failed to extract token", 20 | }) 21 | c.Abort() 22 | return 23 | } 24 | 25 | // 解码 26 | token, err := jwt.Parse(tokenString[7:], func(token *jwt.Token) (interface{}, error) { 27 | // Don't forget to validate the alg is what you expect: 28 | if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { 29 | return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) 30 | } 31 | 32 | // hmacSampleSecret is a []byte containing your secret, e.g. []byte("my_secret_key") 33 | return []byte(secret), nil 34 | }) 35 | if err != nil { 36 | c.JSON(http.StatusInternalServerError, gin.H{ 37 | "status": http.StatusInternalServerError, 38 | "message": err.Error(), 39 | }) 40 | c.Abort() 41 | return 42 | } 43 | // 保存信息 44 | if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { 45 | c.Set("userId", claims["uid"]) 46 | c.Set("payload", claims) 47 | } 48 | 49 | c.Next() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /common/middleware/cors.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | func CorsMiddleware() gin.HandlerFunc { 10 | return func(c *gin.Context) { 11 | c.Header("Access-Control-Allow-Origin", "*") 12 | c.Header("Access-Control-Allow-Headers", "Origin, Accept, Keep-Alive, User-Agent, Cache-Control, Content-Type, X-Requested-With, Authorization") 13 | c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS") 14 | if c.Request.Method == "OPTIONS" { 15 | c.String(http.StatusOK, "") 16 | c.Abort() 17 | return 18 | } 19 | 20 | c.Next() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /common/response/response.go: -------------------------------------------------------------------------------- 1 | package response 2 | 3 | import ( 4 | "net/http" 5 | 6 | "google.golang.org/grpc/status" 7 | "mall-go/common/errcode/pb" 8 | ) 9 | 10 | type Response struct { 11 | Code uint64 `json:"code"` 12 | Message string `json:"message"` 13 | Data any `json:"data"` 14 | } 15 | 16 | func (t *Response) Success(data any) Response { 17 | return Response{ 18 | Code: http.StatusOK, 19 | Message: "success", 20 | Data: data, 21 | } 22 | } 23 | 24 | func (t *Response) Fail(message string) Response { 25 | return Response{ 26 | Code: http.StatusInternalServerError, 27 | Message: message, 28 | Data: nil, 29 | } 30 | } 31 | 32 | func (t *Response) Error(err error) Response { 33 | if rpcErr, ok := status.FromError(err); ok { 34 | ditails := rpcErr.Details() 35 | pbError := ditails[0].(*pb.Error) 36 | return Response{ 37 | Code: uint64(pbError.Code), 38 | Message: pbError.Message, 39 | Data: nil, 40 | } 41 | } 42 | return Response{ 43 | Code: http.StatusInternalServerError, 44 | Message: err.Error(), 45 | Data: nil, 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /data/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cexll/mall-go/9a8277c0b34d49a90e48e824e331c0da40f9ad1e/data/.gitkeep -------------------------------------------------------------------------------- /deployments/.gitignore: -------------------------------------------------------------------------------- 1 | /apisix_log/* 2 | !/apisix_log/.keep 3 | 4 | /etcd_data/* 5 | !/etcd_data/.keep 6 | -------------------------------------------------------------------------------- /deployments/apisix_conf/config.yaml: -------------------------------------------------------------------------------- 1 | apisix: 2 | node_listen: 9080 # APISIX listening port 3 | enable_ipv6: false 4 | 5 | allow_admin: # http://nginx.org/en/docs/http/ngx_http_access_module.html#allow 6 | - 0.0.0.0/0 # We need to restrict ip access rules for security. 0.0.0.0/0 is for test. 7 | 8 | admin_key: 9 | - name: "admin" 10 | key: edd1c9f034335f136f87ad84b625c8f1 11 | role: admin # admin: manage all configuration data 12 | # viewer: only can view configuration data 13 | - name: "viewer" 14 | key: 4054f7cf07e344346cd3f287985e76a2 15 | role: viewer 16 | 17 | etcd: 18 | host: # it's possible to define multiple etcd hosts addresses of the same etcd cluster. 19 | - "http://etcd:2379" # multiple etcd address 20 | prefix: "/apisix" # apisix configurations prefix 21 | timeout: 30 # 30 seconds 22 | 23 | plugin_attr: 24 | prometheus: 25 | export_addr: 26 | ip: "0.0.0.0" 27 | port: 9091 28 | -------------------------------------------------------------------------------- /deployments/apisix_log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cexll/mall-go/9a8277c0b34d49a90e48e824e331c0da40f9ad1e/deployments/apisix_log/.keep -------------------------------------------------------------------------------- /deployments/dashboard_conf/conf.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed to the Apache Software Foundation (ASF) under one or more 3 | # contributor license agreements. See the NOTICE file distributed with 4 | # this work for additional information regarding copyright ownership. 5 | # The ASF licenses this file to You under the Apache License, Version 2.0 6 | # (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | conf: 19 | listen: 20 | host: 0.0.0.0 # `manager api` listening ip or host name 21 | port: 9000 # `manager api` listening port 22 | allow_list: # If we don't set any IP list, then any IP access is allowed by default. 23 | - 0.0.0.0/0 24 | etcd: 25 | endpoints: # supports defining multiple etcd host addresses for an etcd cluster 26 | - "http://etcd:2379" 27 | # yamllint disable rule:comments-indentation 28 | # etcd basic auth info 29 | # username: "root" # ignore etcd username if not enable etcd auth 30 | # password: "123456" # ignore etcd password if not enable etcd auth 31 | mtls: 32 | key_file: "" # Path of your self-signed client side key 33 | cert_file: "" # Path of your self-signed client side cert 34 | ca_file: "" # Path of your self-signed ca cert, the CA is used to sign callers' certificates 35 | # prefix: /apisix # apisix config's prefix in etcd, /apisix by default 36 | log: 37 | error_log: 38 | level: warn # supports levels, lower to higher: debug, info, warn, error, panic, fatal 39 | file_path: 40 | logs/error.log # supports relative path, absolute path, standard output 41 | # such as: logs/error.log, /tmp/logs/error.log, /dev/stdout, /dev/stderr 42 | access_log: 43 | file_path: 44 | logs/access.log # supports relative path, absolute path, standard output 45 | # such as: logs/access.log, /tmp/logs/access.log, /dev/stdout, /dev/stderr 46 | # log example: 2020-12-09T16:38:09.039+0800 INFO filter/logging.go:46 /apisix/admin/routes/r1 {"status": 401, "host": "127.0.0.1:9000", "query": "asdfsafd=adf&a=a", "requestId": "3d50ecb8-758c-46d1-af5b-cd9d1c820156", "latency": 0, "remoteIP": "127.0.0.1", "method": "PUT", "errs": []} 47 | authentication: 48 | secret: 49 | secret # secret for jwt token generation. 50 | # NOTE: Highly recommended to modify this value to protect `manager api`. 51 | # if it's default value, when `manager api` start, it will generate a random string to replace it. 52 | expire_time: 3600 # jwt token expire time, in second 53 | users: # yamllint enable rule:comments-indentation 54 | - username: admin # username and password for login `manager api` 55 | password: admin 56 | - username: user 57 | password: user 58 | 59 | plugins: # plugin list (sorted in alphabetical order) 60 | - api-breaker 61 | - authz-keycloak 62 | - basic-auth 63 | - batch-requests 64 | - consumer-restriction 65 | - cors 66 | # - dubbo-proxy 67 | - echo 68 | # - error-log-logger 69 | # - example-plugin 70 | - fault-injection 71 | - grpc-transcode 72 | - hmac-auth 73 | - http-logger 74 | - ip-restriction 75 | - jwt-auth 76 | - kafka-logger 77 | - key-auth 78 | - limit-conn 79 | - limit-count 80 | - limit-req 81 | # - log-rotate 82 | # - node-status 83 | - openid-connect 84 | - prometheus 85 | - proxy-cache 86 | - proxy-mirror 87 | - proxy-rewrite 88 | - redirect 89 | - referer-restriction 90 | - request-id 91 | - request-validation 92 | - response-rewrite 93 | - serverless-post-function 94 | - serverless-pre-function 95 | # - skywalking 96 | - sls-logger 97 | - syslog 98 | - tcp-logger 99 | - udp-logger 100 | - uri-blocker 101 | - wolf-rbac 102 | - zipkin 103 | - server-info 104 | - traffic-split 105 | -------------------------------------------------------------------------------- /deployments/docker-compose-alpine.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | apisix: 5 | build: 6 | context: ./.. 7 | dockerfile: alpine/Dockerfile 8 | args: 9 | APISIX_VERSION: master 10 | restart: always 11 | volumes: 12 | - ./apisix_log:/usr/local/apisix/logs 13 | - ./apisix_conf/config.yaml:/usr/local/apisix/conf/config.yaml:ro 14 | depends_on: 15 | - etcd 16 | ports: 17 | - "9080:9080/tcp" 18 | - "9091:9091/tcp" 19 | - "9443:9443/tcp" 20 | networks: 21 | apisix: 22 | 23 | etcd: 24 | image: bitnami/etcd:3.4.9 25 | restart: always 26 | volumes: 27 | - etcd_data:/bitnami/etcd 28 | environment: 29 | ETCD_DATA_DIR: /etcd_data 30 | ETCD_ENABLE_V2: "true" 31 | ALLOW_NONE_AUTHENTICATION: "yes" 32 | ETCD_ADVERTISE_CLIENT_URLS: "http://0.0.0.0:2379" 33 | ETCD_LISTEN_CLIENT_URLS: "http://0.0.0.0:2379" 34 | ports: 35 | - "2379:2379/tcp" 36 | networks: 37 | apisix: 38 | 39 | web1: 40 | image: nginx:1.18.0-alpine 41 | restart: always 42 | volumes: 43 | - ./upstream/web1.conf:/etc/nginx/nginx.conf 44 | ports: 45 | - "9081:80/tcp" 46 | environment: 47 | - NGINX_PORT=80 48 | networks: 49 | apisix: 50 | 51 | web2: 52 | image: nginx:1.18.0-alpine 53 | restart: always 54 | volumes: 55 | - ./upstream/web2.conf:/etc/nginx/nginx.conf 56 | ports: 57 | - "9082:80/tcp" 58 | environment: 59 | - NGINX_PORT=80 60 | networks: 61 | apisix: 62 | 63 | networks: 64 | apisix: 65 | driver: bridge 66 | 67 | volumes: 68 | etcd_data: 69 | driver: local 70 | 71 | -------------------------------------------------------------------------------- /deployments/docker-compose-arm64.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | apisix: 5 | image: apache/apisix:2.9-alpine 6 | restart: always 7 | volumes: 8 | - ./apisix_log:/usr/local/apisix/logs 9 | - ./apisix_conf/config.yaml:/usr/local/apisix/conf/config.yaml:ro 10 | depends_on: 11 | - etcd 12 | ports: 13 | - "9080:9080/tcp" 14 | - "9091:9091/tcp" 15 | - "9443:9443/tcp" 16 | networks: 17 | apisix: 18 | 19 | etcd: 20 | image: rancher/coreos-etcd:v3.4.13-arm64 21 | user: root 22 | restart: always 23 | volumes: 24 | - ./etcd_data:/etcd-data 25 | environment: 26 | ETCD_UNSUPPORTED_ARCH: "arm64" 27 | ETCD_ENABLE_V2: "true" 28 | ALLOW_NONE_AUTHENTICATION: "yes" 29 | ETCD_ADVERTISE_CLIENT_URLS: "http://0.0.0.0:2379" 30 | ETCD_LISTEN_CLIENT_URLS: "http://0.0.0.0:2379" 31 | ports: 32 | - "2379:2379/tcp" 33 | networks: 34 | apisix: 35 | 36 | web1: 37 | image: nginx:1.19.10-alpine 38 | restart: always 39 | volumes: 40 | - ./upstream/web1.conf:/etc/nginx/nginx.conf 41 | ports: 42 | - "9081:80/tcp" 43 | environment: 44 | - NGINX_PORT=80 45 | networks: 46 | apisix: 47 | 48 | web2: 49 | image: nginx:1.19.10-alpine 50 | restart: always 51 | volumes: 52 | - ./upstream/web2.conf:/etc/nginx/nginx.conf 53 | ports: 54 | - "9082:80/tcp" 55 | environment: 56 | - NGINX_PORT=80 57 | networks: 58 | apisix: 59 | 60 | networks: 61 | apisix: 62 | driver: bridge 63 | -------------------------------------------------------------------------------- /deployments/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | apisix-dashboard: 5 | image: apache/apisix-dashboard:2.7 6 | restart: always 7 | volumes: 8 | - ./dashboard_conf/conf.yaml:/usr/local/apisix-dashboard/conf/conf.yaml 9 | ports: 10 | - "9000:9000" 11 | networks: 12 | apisix: 13 | 14 | apisix: 15 | image: apache/apisix:2.6-alpine 16 | restart: always 17 | volumes: 18 | - ./apisix_log:/usr/local/apisix/logs 19 | - ./apisix_conf/config.yaml:/usr/local/apisix/conf/config.yaml:ro 20 | depends_on: 21 | - etcd 22 | ##network_mode: host 23 | ports: 24 | - "9080:9080/tcp" 25 | - "9091:9091/tcp" 26 | - "9443:9443/tcp" 27 | networks: 28 | apisix: 29 | 30 | etcd: 31 | image: bitnami/etcd:3.4.15 32 | restart: always 33 | volumes: 34 | - etcd_data:/bitnami/etcd 35 | environment: 36 | ETCD_ENABLE_V2: "true" 37 | ALLOW_NONE_AUTHENTICATION: "yes" 38 | ETCD_ADVERTISE_CLIENT_URLS: "http://0.0.0.0:2379" 39 | ETCD_LISTEN_CLIENT_URLS: "http://0.0.0.0:2379" 40 | ports: 41 | - "2379:2379/tcp" 42 | networks: 43 | apisix: 44 | 45 | web1: 46 | image: nginx:1.19.0-alpine 47 | restart: always 48 | volumes: 49 | - ./upstream/web1.conf:/etc/nginx/nginx.conf 50 | ports: 51 | - "9181:80/tcp" 52 | environment: 53 | - NGINX_PORT=80 54 | networks: 55 | apisix: 56 | 57 | web2: 58 | image: nginx:1.19.0-alpine 59 | restart: always 60 | volumes: 61 | - ./upstream/web2.conf:/etc/nginx/nginx.conf 62 | ports: 63 | - "9182:80/tcp" 64 | environment: 65 | - NGINX_PORT=80 66 | networks: 67 | apisix: 68 | 69 | prometheus: 70 | image: prom/prometheus:v2.25.0 71 | restart: always 72 | volumes: 73 | - ./prometheus_conf/prometheus.yml:/etc/prometheus/prometheus.yml 74 | ports: 75 | - "9090:9090" 76 | networks: 77 | apisix: 78 | 79 | grafana: 80 | image: grafana/grafana:7.3.7 81 | restart: always 82 | ports: 83 | - "13000:3000" 84 | volumes: 85 | - "./grafana_conf/provisioning:/etc/grafana/provisioning" 86 | - "./grafana_conf/dashboards:/var/lib/grafana/dashboards" 87 | - "./grafana_conf/config/grafana.ini:/etc/grafana/grafana.ini" 88 | networks: 89 | apisix: 90 | 91 | networks: 92 | apisix: 93 | driver: bridge 94 | 95 | volumes: 96 | etcd_data: 97 | driver: local 98 | 99 | -------------------------------------------------------------------------------- /deployments/etcd_conf/etcd.conf.yml: -------------------------------------------------------------------------------- 1 | # This is the configuration file for the etcd server. 2 | 3 | # Human-readable name for this member. 4 | name: 'default' 5 | 6 | # Path to the data directory. 7 | data-dir: 8 | 9 | # Path to the dedicated wal directory. 10 | wal-dir: 11 | 12 | # Number of committed transactions to trigger a snapshot to disk. 13 | snapshot-count: 10000 14 | 15 | # Time (in milliseconds) of a heartbeat interval. 16 | heartbeat-interval: 100 17 | 18 | # Time (in milliseconds) for an election to timeout. 19 | election-timeout: 1000 20 | 21 | # Raise alarms when backend size exceeds the given quota. 0 means use the 22 | # default quota. 23 | quota-backend-bytes: 0 24 | 25 | # List of comma separated URLs to listen on for peer traffic. 26 | listen-peer-urls: http://localhost:2380 27 | 28 | # List of comma separated URLs to listen on for client traffic. 29 | listen-client-urls: http://localhost:2379 30 | 31 | # Maximum number of snapshot files to retain (0 is unlimited). 32 | max-snapshots: 5 33 | 34 | # Maximum number of wal files to retain (0 is unlimited). 35 | max-wals: 5 36 | 37 | # Comma-separated white list of origins for CORS (cross-origin resource sharing). 38 | cors: 39 | 40 | # List of this member's peer URLs to advertise to the rest of the cluster. 41 | # The URLs needed to be a comma-separated list. 42 | initial-advertise-peer-urls: http://localhost:2380 43 | 44 | # List of this member's client URLs to advertise to the public. 45 | # The URLs needed to be a comma-separated list. 46 | advertise-client-urls: http://localhost:2379 47 | 48 | # Discovery URL used to bootstrap the cluster. 49 | discovery: 50 | 51 | # Valid values include 'exit', 'proxy' 52 | discovery-fallback: 'proxy' 53 | 54 | # HTTP proxy to use for traffic to discovery service. 55 | discovery-proxy: 56 | 57 | # DNS domain used to bootstrap initial cluster. 58 | discovery-srv: 59 | 60 | # Initial cluster configuration for bootstrapping. 61 | initial-cluster: 62 | 63 | # Initial cluster token for the etcd cluster during bootstrap. 64 | initial-cluster-token: 'etcd-cluster' 65 | 66 | # Initial cluster state ('new' or 'existing'). 67 | initial-cluster-state: 'new' 68 | 69 | # Reject reconfiguration requests that would cause quorum loss. 70 | strict-reconfig-check: false 71 | 72 | # Accept etcd V2 client requests 73 | enable-v2: true 74 | 75 | # Enable runtime profiling data via HTTP server 76 | enable-pprof: true 77 | 78 | # Valid values include 'on', 'readonly', 'off' 79 | proxy: 'off' 80 | 81 | # Time (in milliseconds) an endpoint will be held in a failed state. 82 | proxy-failure-wait: 5000 83 | 84 | # Time (in milliseconds) of the endpoints refresh interval. 85 | proxy-refresh-interval: 30000 86 | 87 | # Time (in milliseconds) for a dial to timeout. 88 | proxy-dial-timeout: 1000 89 | 90 | # Time (in milliseconds) for a write to timeout. 91 | proxy-write-timeout: 5000 92 | 93 | # Time (in milliseconds) for a read to timeout. 94 | proxy-read-timeout: 0 95 | 96 | client-transport-security: 97 | # Path to the client server TLS cert file. 98 | cert-file: 99 | 100 | # Path to the client server TLS key file. 101 | key-file: 102 | 103 | # Enable client cert authentication. 104 | client-cert-auth: false 105 | 106 | # Path to the client server TLS trusted CA cert file. 107 | trusted-ca-file: 108 | 109 | # Client TLS using generated certificates 110 | auto-tls: false 111 | 112 | peer-transport-security: 113 | # Path to the peer server TLS cert file. 114 | cert-file: 115 | 116 | # Path to the peer server TLS key file. 117 | key-file: 118 | 119 | # Enable peer client cert authentication. 120 | client-cert-auth: false 121 | 122 | # Path to the peer server TLS trusted CA cert file. 123 | trusted-ca-file: 124 | 125 | # Peer TLS using generated certificates. 126 | auto-tls: false 127 | 128 | # Enable debug-level logging for etcd. 129 | debug: false 130 | 131 | logger: zap 132 | 133 | # Specify 'stdout' or 'stderr' to skip journald logging even when running under systemd. 134 | log-outputs: [stderr] 135 | 136 | # Force to create a new one member cluster. 137 | force-new-cluster: false 138 | 139 | auto-compaction-mode: periodic 140 | auto-compaction-retention: "1" 141 | -------------------------------------------------------------------------------- /deployments/grafana_conf/provisioning/dashboards/all.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed to the Apache Software Foundation (ASF) under one or more 3 | # contributor license agreements. See the NOTICE file distributed with 4 | # this work for additional information regarding copyright ownership. 5 | # The ASF licenses this file to You under the Apache License, Version 2.0 6 | # (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | apiVersion: 1 18 | 19 | providers: 20 | - name: 'default' 21 | orgId: 1 22 | folder: '' 23 | type: file 24 | disableDeletion: false 25 | editable: false 26 | options: 27 | path: /var/lib/grafana/dashboards 28 | -------------------------------------------------------------------------------- /deployments/grafana_conf/provisioning/datasources/all.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed to the Apache Software Foundation (ASF) under one or more 3 | # contributor license agreements. See the NOTICE file distributed with 4 | # this work for additional information regarding copyright ownership. 5 | # The ASF licenses this file to You under the Apache License, Version 2.0 6 | # (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | datasources: 18 | - access: 'proxy' 19 | editable: true 20 | is_default: true 21 | name: 'apisix' 22 | org_id: 1 23 | type: 'prometheus' 24 | url: 'http://prometheus:9090' 25 | version: 1 26 | -------------------------------------------------------------------------------- /deployments/mkcert/README.md: -------------------------------------------------------------------------------- 1 | ### Ref 2 | 3 | 4 | 5 | ### Copy CA 6 | 7 | ``` 8 | cp $(mkcert -CAROOT)/rootCA.pem . 9 | 10 | cp $(mkcert -CAROOT)/rootCA-key.pem . 11 | ``` 12 | 13 | ### Create certificate 14 | 15 | ``` 16 | $ mkcert lvh.me "*.lvh.me" 17 | ``` 18 | -------------------------------------------------------------------------------- /deployments/mkcert/lvh.me+1-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCq5TJwEHgVNaft 3 | kU+mytN8gNInFJCU05YfZO8w6yIbeYR4dvdUgfXhDjxb8ZcWfzC7qfpKFAeA0X0l 4 | yHdOYmI6ONhuYW4C4ssaaiIJjnUI0ogHKYtCKPNppU0cRjdnSKWopv9QXZ9UHoRX 5 | zDcHcUnRhL0I057tfZrPAr1X0eMVEKTSUoFVS/nRvuQiOYitiSnSONEpYEAdmGG3 6 | vWxvW1BI9qi4UxXK6IcNF3HUwM20oAAF+5r+3vT+zG04tKfoJfwHoevWKS5V2Rz1 7 | xVW/O8LWKzE1Hl5GHGPXuhZUVDEIIFIV8D67h6dT4BGMS6pUjm0HyfNAdIEHHB4d 8 | RX+RDK75AgMBAAECggEBAKP0Lhabhkl657ghMBSqBIovIO+DaE6QTDekY2JAQ8Gu 9 | LKxSHmmCIX4gZUIknrpjnkJ9CfZmDujEktdb4zJdazXGccY8TQoRwZ9+8VbPyHKF 10 | YSHV9perqBPUFg2pQ+AgL2aFiO72UzSl7nw5HRZT1ule+ujr9k6MsagnTbZiVAVR 11 | F0Lxi7QZc0PQH4tLItkAM3eujUUR+lM0pNhdVqvm2XrNANb423uz1TUvDCXe/1Wb 12 | b7hRYIQEdXY4b3+Emi3SNMtGGBIOaNSQIgXC6s9AezTno5oWj3I5B+AGSD9VmWoK 13 | 89iQtFtW1xXjCWM0ZAqH+4b7pmlbUFuv40BqLpUYiAECgYEAw6HfyDoMXaU1q9Yg 14 | xprSQQOrflk24ngCO5HISyEH2ZDeUWkqsOECFhkslJuJewkQfvjBza3Iee3IpJPQ 15 | CnvOtSfnV0YQM7RctVVXmwwgVGd35GeqIM5un3r1LgRslBWpsBkOYQVrPSHnHy7v 16 | f1t/RQHcil08pCl6kyzBS/EGefkCgYEA36Ez8vPjUoWyoFcpFJha/vC8o7Ay4SQ6 17 | kSUhWOLQaA8Oy3BLIH0YI6EpZvHYTtQFTCFhMfTLJpD/M0hmFHQvbhGpvB4Zi6G6 18 | +4GtIi+hgZ7DB86MdKRpQzELPy9JuxYavOUMlMsel2UTAPCfGn3SIJjEkfxo/t0E 19 | 8Ct0Z2sdHQECgYALosMmq500rLDmiZPlfCvpRgibOT80dSLc3Cznmw1WeXFfsjuh 20 | zaBMJC8sn5urv7xFcRJF44I7DlOSxl/nX7nJuJff7wDjsmSZPHw7cpsxqf3NjgTm 21 | cqDNx4hxtj2nCSrQmIzsZGAegLe4eRgxoQWO2y9841LKCNWLj2vn0mwqMQKBgQCr 22 | /6qbBGYlBFvc4uzfwEbMqpAMPesEKv84v5wkZ17vihVQ929w74XvcXcMjJpYFs50 23 | PYAqEiNl6EPAR8DrnmkUeVVZMLVpJd1Qr+5fys6niVpr2LtCw2mKmmASGubUlC3A 24 | d1Lz5j4DJ2Q0Zt2YXImPNLCLr915mLjBmEqReFsOAQKBgQCtuwlhmOE43jGKP5RJ 25 | Ow5KhZze1gX4EA3HQOYRUE3psuzIesUEcDOvoCE/iWo22uNieqpBbXKi0CrtR6/6 26 | SopgMSPkNrrKDvbrM2azvdD85hoPmoYjEuyWTDJReqCzNxbHeri5Yo0/pcDqWSb3 27 | 49RDuXS9aIj3PjFtsqQFXbfugg== 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /deployments/mkcert/lvh.me+1.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEKjCCApKgAwIBAgIQJ7+tTZRECIp1/rZvizFsADANBgkqhkiG9w0BAQsFADBl 3 | MR4wHAYDVQQKExVta2NlcnQgZGV2ZWxvcG1lbnQgQ0ExHTAbBgNVBAsMFHJvb3RA 4 | dmtpbGwtYXJjaGxpbnV4MSQwIgYDVQQDDBtta2NlcnQgcm9vdEB2a2lsbC1hcmNo 5 | bGludXgwHhcNMTkwNzE3MDA1NTMwWhcNMjkwNzE3MDA1NTMwWjBIMScwJQYDVQQK 6 | Ex5ta2NlcnQgZGV2ZWxvcG1lbnQgY2VydGlmaWNhdGUxHTAbBgNVBAsMFHJvb3RA 7 | dmtpbGwtYXJjaGxpbnV4MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA 8 | quUycBB4FTWn7ZFPpsrTfIDSJxSQlNOWH2TvMOsiG3mEeHb3VIH14Q48W/GXFn8w 9 | u6n6ShQHgNF9Jch3TmJiOjjYbmFuAuLLGmoiCY51CNKIBymLQijzaaVNHEY3Z0il 10 | qKb/UF2fVB6EV8w3B3FJ0YS9CNOe7X2azwK9V9HjFRCk0lKBVUv50b7kIjmIrYkp 11 | 0jjRKWBAHZhht71sb1tQSPaouFMVyuiHDRdx1MDNtKAABfua/t70/sxtOLSn6CX8 12 | B6Hr1ikuVdkc9cVVvzvC1isxNR5eRhxj17oWVFQxCCBSFfA+u4enU+ARjEuqVI5t 13 | B8nzQHSBBxweHUV/kQyu+QIDAQABo3MwcTAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0l 14 | BAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAfBgNVHSMEGDAWgBSYvVgP4gOX 15 | oeisL5WsQbGN4B9BZTAbBgNVHREEFDASggZsdmgubWWCCCoubHZoLm1lMA0GCSqG 16 | SIb3DQEBCwUAA4IBgQBAuqUCOe4dYs5Wx6R99c2W+OoeyxN8RYLK8IJkUnuqzLIk 17 | REzYuAzO99McMe61nc0s5obsVGOyhchj101Y22Roq9W6IMo5xMxtnjHBuFhMHUKL 18 | np6U2hcd73u68q/dgq9+tQk1UhTJGVkZKr7E5FAr5Lh9ge3p+6xoc2czMGaoHKge 19 | jcCT+nPE73Nec17LUSZhqcxlzzXIrLubi0W8IrkCoDWQYnV8HjuigpSuclAI2Pet 20 | GD/fi6w0sERsSG2VFRA56j17l27LqFfHtz1GWMJGh7cQ+GOGe8Wviiskz5tZEQTw 21 | y2q2CFwx9MxzBk5mO8zG4pnYhB/UMeLHiTJO4WeLP8uVkEyzckc1INsgw6Kit2oZ 22 | hQDoHunZ7y/RfL/8xj78nya58YZ6oaf+EDVqA+JPhbAZEFC+U9+BYLmr5eB31DNc 23 | NfeqlIybQ1xrj42QRUTGPIwfl6lwGu4L63WKVfSOOEWkx1yB/x5vUYmWK4Vub+Gs 24 | rwqoluFp44oNMY+uy9w= 25 | -----END CERTIFICATE----- 26 | -------------------------------------------------------------------------------- /deployments/mkcert/rootCA-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIG/AIBADANBgkqhkiG9w0BAQEFAASCBuYwggbiAgEAAoIBgQCz/Ya3wSY9CAzL 3 | 78eJ6bugV8UEm9T/an0UDY+r9x8zSVsvz1rgqoxE1tFwc2KCdGoXe9qFc7M5HOuF 4 | N7sbr1CusgoRbLO/zzmd/IUCkG6iLdo3dY6JcIFUQcerbSGLbqLQ54uixxsXG5l/ 5 | 5K/rS0SboxQQWXRt/+FLB3TZpSA9eNiI9WGUdtEsiZIu0H7UFjgIEer7ucFXFJPn 6 | Yfc6zZcY25mkraIJR6+e4DA8liTaC8MzLGgpvqlGr8z/5f5Fhi8LWGJbDZKvpCyW 7 | lsWWE7tgk7yj8tTScFtjMHynT74YNUP2SaUvgDFFSf7hUyapSpkg9dtykELSO5HC 8 | 8yK+XAXhiwuk/lwh4y9CokiX2rldQvgiuvw3eSxGhGmfAOGbn2mhHI8t+xjHzXNr 9 | ZgYH+PluFiZ2sC8Y0eY+OWdqU0+pMa+6/6fJbZVX1eCkKNNYyv+YJlbcmVEeFUwb 10 | Bkx/K1lJIVwWvpaEOQja8CkOImxQ68KaisNF/RHC+OFkhUMXfmMCAwEAAQKCAYAt 11 | /FA3Enoajbv3PsPUUItHZCVKECZTO3nJbc2POgwWpl9Nz8SAMhK0Y84O1OwOOIAC 12 | j/o8rlrT9LeXya1cXWZXu9UmroceMmueGa5CAXTCqsRd9wL7ymrAhC3ndnJjlViz 13 | 1vSuGgCp3rtRGbR4gTp+KSQvKSu+DuoYqUcJdE7QZdes5kSQEetPXbajj1FYDnUy 14 | WqGWvtbO0MsZCsaHl49LIpAWquYDSPVtugmY8SNpMiglIutZ/wRwsunFV5WnXaip 15 | eXOlD85lbbRzIuuydGn15aD1svQMOKyiWuA3Fr2nG7uPxgb6O5KV/cTfaKNjrJzp 16 | lo7RF1S6Y4piXlrzDFCKc6LGjAk41kMOpjkrRVSHBtvWCFX8vNOkq09mGnGMhzSR 17 | bA4Qj11sbVM4IGy49FCHPCw9sLZWzpjtWXFipDkuzGRedGLjuURr5iKpA5B+jVkH 18 | iGUHfbjfU/eWiHRuDSr3UzMJ8GiSpLbZ5x1+2lUSUksvZ5ACDHXUd31Z4uGI1UEC 19 | gcEAyvPTSaC+e3SCfKpMazfTICh/nminglKud5y5d35lU3sezw94WIJ0DJSpYVXA 20 | rR6n2MTIku44fr53Yq25jyxjnhQ273G9QVVDfnkpHnH2+9PTLaRF8xmFhPiQBKOX 21 | rdJK3YDgJ96aHZ2/un+kqEJ9dWetioy8zie1zYlHq0H4fjbc0MwaUC+Oi8Ys84xX 22 | H2W9OxhDhBqhwH6eP3XJgXf3ov6PznqKJtMOO9IMJNmN067U30mVulFcVNN40fLh 23 | up9dAoHBAOMJPEkGRzhIOHXKPmAQMIDC9DHhHU15pDCRpNLBAbcBx4fUbiaT3Xz0 24 | CVeqLuDlW/dKQ48t/8ftEXqt6B+KlFa5xzivulKuB5QSf3d5pDmhfksCtq3DkynF 25 | w5CsbiYs/xNYqg+RlCJBUgCXiXimElyJ+FfxPJqU6TGpK+pH1Vkg79wAZ8y32Pkn 26 | 0cqw42xuzlYvB7pNgoWiEh2c0efISARYNl72lgrBsMQWS0x7kZsXigVVfaK4L7qC 27 | lTIQT1N4vwKBwAl54P+rFbnF8uHHQdIvxRfXTD4lPM0E90h2dOJzcF+5e/LHEiNv 28 | 0+NSfaYhzuFGcSfZ6FOT8+dXFVPyMJvSwsP6xaXgdam7RW9+UMEvKQ0REucqqGl1 29 | Y5qV0IOm78oZ64z0G4NBDYkceHtIwnNox1rYAG5bq+UkTYTPSB1i8ytRfHXzUbc0 30 | wT8dfbPRda/tnCxKMbzVUYAfaVwlL8dvxoxBvo0xg4nUiPGaGR8PWWPs4dubH/aX 31 | HeyIAzgqUwfXsQKBwA+12OUYuR7s6HNGLnDJTgHq+6jj8fole7YcrfIjhvHZ/quo 32 | iILE4qO941OcpCfN10JSckBbJ4L3Jal+lTxxg3hI3L9Qca08+6tEaGulEDcotKuO 33 | IYP+r5VJaRH+zJR5sqDtFr+DDGQebwU5dzrRCx46MeQr/kyYF1bnZPGOTPYg8Fgh 34 | 8wzYf/wlxD+pY+Nh+4c9M+SxbmG+6FACRQgr9MfQLtr9Zz6RTqETBdziBB5eT5+0 35 | 3b+/zRWz6Y0QUdzt0wKBwEqkBkMAdT/tKN9ZM9g/FseisAfwBtmqdvZsXj9Fx0A7 36 | PHlEbh6G91nhXwg2zeBrakCU4YY4NY5qiz80ykwJ0uwbUUb628ai9OGqjS3LGhI3 37 | kYIrxQpv9mdNkJsISsYfhvMjiPt5LvxdCynC3w2FGd/pgBZwi14zkusW86pPGjVc 38 | o5UXcZLgR68O3tfsr8+EpWVCnS7Ln2UcrWfnrBHFRCiwFCFfwO9JfgnEXNquxBGX 39 | 2Hqwp0dr/cxfVFXcD8fa9Q== 40 | -----END PRIVATE KEY----- 41 | -------------------------------------------------------------------------------- /deployments/mkcert/rootCA.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEmTCCAwGgAwIBAgIQdbSoj8s/j7Hgten9A8CKBTANBgkqhkiG9w0BAQsFADBl 3 | MR4wHAYDVQQKExVta2NlcnQgZGV2ZWxvcG1lbnQgQ0ExHTAbBgNVBAsMFHJvb3RA 4 | dmtpbGwtYXJjaGxpbnV4MSQwIgYDVQQDDBtta2NlcnQgcm9vdEB2a2lsbC1hcmNo 5 | bGludXgwHhcNMTkwNzE1MTQ0MDA0WhcNMjkwNzE1MTQ0MDA0WjBlMR4wHAYDVQQK 6 | ExVta2NlcnQgZGV2ZWxvcG1lbnQgQ0ExHTAbBgNVBAsMFHJvb3RAdmtpbGwtYXJj 7 | aGxpbnV4MSQwIgYDVQQDDBtta2NlcnQgcm9vdEB2a2lsbC1hcmNobGludXgwggGi 8 | MA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQCz/Ya3wSY9CAzL78eJ6bugV8UE 9 | m9T/an0UDY+r9x8zSVsvz1rgqoxE1tFwc2KCdGoXe9qFc7M5HOuFN7sbr1CusgoR 10 | bLO/zzmd/IUCkG6iLdo3dY6JcIFUQcerbSGLbqLQ54uixxsXG5l/5K/rS0SboxQQ 11 | WXRt/+FLB3TZpSA9eNiI9WGUdtEsiZIu0H7UFjgIEer7ucFXFJPnYfc6zZcY25mk 12 | raIJR6+e4DA8liTaC8MzLGgpvqlGr8z/5f5Fhi8LWGJbDZKvpCyWlsWWE7tgk7yj 13 | 8tTScFtjMHynT74YNUP2SaUvgDFFSf7hUyapSpkg9dtykELSO5HC8yK+XAXhiwuk 14 | /lwh4y9CokiX2rldQvgiuvw3eSxGhGmfAOGbn2mhHI8t+xjHzXNrZgYH+PluFiZ2 15 | sC8Y0eY+OWdqU0+pMa+6/6fJbZVX1eCkKNNYyv+YJlbcmVEeFUwbBkx/K1lJIVwW 16 | vpaEOQja8CkOImxQ68KaisNF/RHC+OFkhUMXfmMCAwEAAaNFMEMwDgYDVR0PAQH/ 17 | BAQDAgIEMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFJi9WA/iA5eh6Kwv 18 | laxBsY3gH0FlMA0GCSqGSIb3DQEBCwUAA4IBgQAQajEfAgQHrjHCaVcFskq8p7sw 19 | FRDMyWVBB7FdtXIwZELib2oqDh3dTOamCYbRzRy2GHYGRp2mqDpMIEeomL475pSl 20 | /Kqk9vRZgNHoEzKChPnbYJpZXEqlvxQzmS5Pu4ijAcP/+DFxMo400WbpNB9YQeeP 21 | x/aeOP4RqBdtBx+yXr9iHiaYH0ZKmvQiGOAGUhw1WPd1lvv5gmrXsAJ7/QGSAuWM 22 | KHyYIx6pfO4fq3OFONrDWPBAEoW+ZvDVi0mBEhksBm5H6+APD3qfU6ZGI2kHGMR4 23 | oL5+K6Qd8W8rljrtYW0yk3OHZ5ZlP3XhXs3dBnrXRzsQN2FCMJ+tV+iqcLll0Gzq 24 | 9Fnx0qKT8NQS1FkP49W1Zqeoxof9iruXpQ1cMTEy/XvkNGw76MBubZZ1ZQwKlG8/ 25 | 3lTENUnr9nvwpwZpjjfut96KgpbcOnhM8q8dn60YWgARXa88uRVbw3IJ0T2A+RGu 26 | KEYT0ZZQQMFUvobozBi3XdWM33FjHoMyCzth0DY= 27 | -----END CERTIFICATE----- 28 | -------------------------------------------------------------------------------- /deployments/prometheus_conf/prometheus.yml: -------------------------------------------------------------------------------- 1 | global: 2 | scrape_interval: 1s # By default, scrape targets every 15 seconds. 3 | 4 | # Attach these labels to any time series or alerts when communicating with 5 | # external systems (federation, remote storage, Alertmanager). 6 | external_labels: 7 | stack: "apisix" 8 | 9 | 10 | # A scrape configuration containing exactly one endpoint to scrape: 11 | # Here it's Prometheus itself. 12 | scrape_configs: 13 | # The job name is added as a label `job=` to any timeseries scraped from this config. 14 | - job_name: "prometheus" 15 | # Override the global default and scrape targets from this job every 5 seconds. 16 | scrape_interval: 5s 17 | static_configs: 18 | - targets: ["localhost:9090"] 19 | - job_name: "apisix" 20 | scrape_interval: 5s 21 | metrics_path: "/apisix/prometheus/metrics" 22 | static_configs: 23 | - targets: ["apisix:9091"] 24 | -------------------------------------------------------------------------------- /deployments/sql/mall_go_balance.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Navicat Premium Data Transfer 3 | 4 | Source Server : localhost 5 | Source Server Type : MySQL 6 | Source Server Version : 50730 7 | Source Host : localhost:3306 8 | Source Schema : mall_go_balance 9 | 10 | Target Server Type : MySQL 11 | Target Server Version : 50730 12 | File Encoding : 65001 13 | 14 | Date: 13/09/2022 15:07:59 15 | */ 16 | 17 | SET NAMES utf8mb4; 18 | SET FOREIGN_KEY_CHECKS = 0; 19 | 20 | -- ---------------------------- 21 | -- Table structure for mall_balance 22 | -- ---------------------------- 23 | DROP TABLE IF EXISTS `mall_balance`; 24 | CREATE TABLE `mall_balance` ( 25 | `id` int(11) NOT NULL AUTO_INCREMENT, 26 | `user_id` int(11) NOT NULL, 27 | `type` tinyint(1) NOT NULL COMMENT '类型\r\n1 用户\r\n2 商户', 28 | `available` bigint(22) NOT NULL DEFAULT 0 COMMENT '可用', 29 | `frozen` bigint(22) NOT NULL DEFAULT 0 COMMENT '冻结', 30 | `status` int(1) NOT NULL DEFAULT 1 COMMENT '状态 \r\n1 正常\r\n2 冻结', 31 | `created_at` datetime(0) NULL DEFAULT NULL, 32 | `updated_at` datetime(0) NULL DEFAULT NULL, 33 | PRIMARY KEY (`id`) USING BTREE 34 | ) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '余额表' ROW_FORMAT = Dynamic; 35 | 36 | -- ---------------------------- 37 | -- Table structure for mall_balance_change_log 38 | -- ---------------------------- 39 | DROP TABLE IF EXISTS `mall_balance_change_log`; 40 | CREATE TABLE `mall_balance_change_log` ( 41 | `id` int(11) NOT NULL AUTO_INCREMENT, 42 | `balance_id` int(11) NOT NULL COMMENT '余额ID', 43 | `amount` int(22) NOT NULL COMMENT '变动金额', 44 | `before_amount` int(22) NOT NULL COMMENT '变动前余额', 45 | `after_amount` int(22) NOT NULL COMMENT '变动后余额', 46 | `type` tinyint(1) NOT NULL COMMENT '变动类型 \r\n1 增加\r\n2 减少', 47 | `type_amount` tinyint(1) NOT NULL COMMENT '余额类型\r\n1 可用余额\r\n2 冻结余额', 48 | `desc` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '描述', 49 | `is_delete` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否删除 ', 50 | `created_at` datetime(0) NULL DEFAULT NULL, 51 | `updated_at` datetime(0) NULL DEFAULT NULL, 52 | PRIMARY KEY (`id`) USING BTREE 53 | ) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '余额变动记录表' ROW_FORMAT = Dynamic; 54 | 55 | SET FOREIGN_KEY_CHECKS = 1; 56 | -------------------------------------------------------------------------------- /deployments/sql/mall_go_merchants.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Navicat Premium Data Transfer 3 | 4 | Source Server : localhost 5 | Source Server Type : MySQL 6 | Source Server Version : 50730 7 | Source Host : localhost:3306 8 | Source Schema : mall_go_merchants 9 | 10 | Target Server Type : MySQL 11 | Target Server Version : 50730 12 | File Encoding : 65001 13 | 14 | Date: 13/09/2022 15:08:12 15 | */ 16 | 17 | SET NAMES utf8mb4; 18 | SET FOREIGN_KEY_CHECKS = 0; 19 | 20 | -- ---------------------------- 21 | -- Table structure for mall_merchant 22 | -- ---------------------------- 23 | DROP TABLE IF EXISTS `mall_merchant`; 24 | CREATE TABLE `mall_merchant` ( 25 | `id` int(11) NOT NULL AUTO_INCREMENT, 26 | `user_id` int(11) NOT NULL COMMENT '用户ID', 27 | `shop_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '店铺名称', 28 | `shop_logo` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '店铺logo', 29 | `mobile` varchar(25) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '店长手机', 30 | `address` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '店铺地址', 31 | `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '店铺描述', 32 | `sort` int(1) NOT NULL COMMENT '店铺排序', 33 | `is_hide` tinyint(1) NOT NULL COMMENT '是否隐藏', 34 | `status` tinyint(1) NOT NULL COMMENT '店铺状态 \r\n1 正常\r\n2 禁用\r\n3 审核中\r\n4 审核拒绝\r\n5 关闭店铺', 35 | `created_at` datetime(0) NOT NULL, 36 | `updated_at` datetime(0) NOT NULL, 37 | PRIMARY KEY (`id`) USING BTREE 38 | ) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '商户表' ROW_FORMAT = Dynamic; 39 | 40 | SET FOREIGN_KEY_CHECKS = 1; 41 | -------------------------------------------------------------------------------- /deployments/sql/mall_go_user.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Navicat Premium Data Transfer 3 | 4 | Source Server : localhost 5 | Source Server Type : MySQL 6 | Source Server Version : 50730 7 | Source Host : localhost:3306 8 | Source Schema : mall_go_user 9 | 10 | Target Server Type : MySQL 11 | Target Server Version : 50730 12 | File Encoding : 65001 13 | 14 | Date: 14/09/2022 12:00:43 15 | */ 16 | 17 | SET NAMES utf8mb4; 18 | SET FOREIGN_KEY_CHECKS = 0; 19 | 20 | -- ---------------------------- 21 | -- Table structure for mall_user 22 | -- ---------------------------- 23 | DROP TABLE IF EXISTS `mall_user`; 24 | CREATE TABLE `mall_user` ( 25 | `id` int(11) NOT NULL AUTO_INCREMENT, 26 | `nickname` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户名', 27 | `avatar_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '头像', 28 | `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '密码', 29 | `mobile` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '手机号', 30 | `signature` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '未填写' COMMENT '个性签名', 31 | `balance` bigint(20) NOT NULL COMMENT '余额 (分)', 32 | `status` tinyint(1) NOT NULL DEFAULT 1 COMMENT '状态 1正常 2禁用', 33 | `is_delete` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否删除 1删除', 34 | `created_at` datetime(0) NULL DEFAULT NULL, 35 | `updated_at` datetime(0) NULL DEFAULT NULL, 36 | PRIMARY KEY (`id`) USING BTREE 37 | ) ENGINE = InnoDB AUTO_INCREMENT = 14 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户表' ROW_FORMAT = Dynamic; 38 | 39 | -- ---------------------------- 40 | -- Table structure for mall_user_follow 41 | -- ---------------------------- 42 | DROP TABLE IF EXISTS `mall_user_follow`; 43 | CREATE TABLE `mall_user_follow` ( 44 | `id` int(11) NOT NULL AUTO_INCREMENT, 45 | `user_id` int(11) NOT NULL COMMENT '用户ID', 46 | `to_user_id` int(11) NOT NULL COMMENT '关注用户ID', 47 | `created_at` datetime(0) NOT NULL COMMENT '关注时间', 48 | `is_delete` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否删除', 49 | `deleted_at` datetime(0) NULL DEFAULT NULL COMMENT '删除时间', 50 | PRIMARY KEY (`id`) USING BTREE, 51 | INDEX `idx_user`(`user_id`, `to_user_id`, `is_delete`) USING BTREE 52 | ) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户关注表' ROW_FORMAT = Dynamic; 53 | 54 | SET FOREIGN_KEY_CHECKS = 1; 55 | -------------------------------------------------------------------------------- /deployments/upstream/web1.conf: -------------------------------------------------------------------------------- 1 | worker_processes 1; 2 | error_log stderr notice; 3 | events { 4 | worker_connections 1024; 5 | } 6 | 7 | http { 8 | variables_hash_max_size 1024; 9 | access_log off; 10 | real_ip_header X-Real-IP; 11 | charset utf-8; 12 | 13 | server { 14 | listen 80; 15 | 16 | location /web 17 | { 18 | return 200 "hello web1"; 19 | } 20 | 21 | 22 | 23 | location /static/ { 24 | alias static/; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /deployments/upstream/web2.conf: -------------------------------------------------------------------------------- 1 | worker_processes 1; 2 | error_log stderr notice; 3 | events { 4 | worker_connections 1024; 5 | } 6 | 7 | http { 8 | variables_hash_max_size 1024; 9 | access_log off; 10 | real_ip_header X-Real-IP; 11 | charset utf-8; 12 | 13 | server { 14 | listen 80; 15 | 16 | location /web { 17 | return 200 "hello web2"; 18 | } 19 | 20 | location /static/ { 21 | alias static/; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /docker-compose-env.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | ######## 项目依赖的环境,启动项目之前要先启动此环境 ####### 4 | ######## The environment that the project depends on, starting this environment before starting the project ####### 5 | 6 | services: 7 | #jaeger链路追踪 — Jaeger for tracing 8 | jaeger: 9 | image: jaegertracing/all-in-one:latest 10 | container_name: jaeger 11 | restart: always 12 | ports: 13 | - "5775:5775/udp" 14 | - "6831:6831/udp" 15 | - "6832:6832/udp" 16 | - "5778:5778" 17 | - "16686:16686" 18 | - "14268:14268" 19 | - "9411:9411" 20 | environment: 21 | - SPAN_STORAGE_TYPE=elasticsearch 22 | - ES_SERVER_URLS=http://elasticsearch:9200 23 | - LOG_LEVEL=debug 24 | networks: 25 | - mall-net 26 | 27 | #prometheus监控 — Prometheus for monitoring 28 | prometheus: 29 | image: prom/prometheus:v2.28.1 30 | container_name: prometheus 31 | environment: 32 | # 时区上海 - Time zone Shanghai (Change if needed) 33 | TZ: Asia/Shanghai 34 | volumes: 35 | - ./deploy/prometheus/server/prometheus.yml:/etc/prometheus/prometheus.yml 36 | - ./data/prometheus/data:/prometheus 37 | command: 38 | - '--config.file=/etc/prometheus/prometheus.yml' 39 | - '--storage.tsdb.path=/prometheus' 40 | restart: always 41 | user: root 42 | ports: 43 | - 9090:9090 44 | networks: 45 | - mall-net 46 | 47 | #查看prometheus监控数据 - Grafana to view Prometheus monitoring data 48 | grafana: 49 | image: grafana/grafana:8.0.6 50 | container_name: grafana 51 | hostname: grafana 52 | user: root 53 | environment: 54 | # 时区上海 - Time zone Shanghai (Change if needed) 55 | TZ: Asia/Shanghai 56 | restart: always 57 | volumes: 58 | - ./data/grafana/data:/var/lib/grafana 59 | ports: 60 | - "3001:3000" 61 | networks: 62 | - mall-net 63 | 64 | # #搜集kafka业务日志、存储prometheus监控数据 - Kafka for collecting business logs and storing Prometheus monitoring data 65 | elasticsearch: 66 | image: docker.elastic.co/elasticsearch/elasticsearch:7.13.4 67 | container_name: elasticsearch 68 | user: root 69 | environment: 70 | - discovery.type=single-node 71 | - "ES_JAVA_OPTS=-Xms512m -Xmx512m" 72 | - TZ=Asia/Shanghai 73 | volumes: 74 | - ./data/elasticsearch/data:/usr/share/elasticsearch/data 75 | restart: always 76 | ports: 77 | - 9200:9200 78 | - 9300:9300 79 | networks: 80 | - mall-net 81 | 82 | #查看elasticsearch数据 - Kibana to view Elasticsearch data 83 | kibana: 84 | image: docker.elastic.co/kibana/kibana:7.13.4 85 | container_name: kibana 86 | environment: 87 | - elasticsearch.hosts=http://elasticsearch:9200 88 | - TZ=Asia/Shanghai 89 | restart: always 90 | networks: 91 | - mall-net 92 | ports: 93 | - "5601:5601" 94 | depends_on: 95 | - elasticsearch 96 | 97 | #收集业务数据 - Collect business data 98 | filebeat: 99 | image: elastic/filebeat:7.13.4 100 | container_name: filebeat 101 | environment: 102 | # 时区上海 - Time zone Shanghai (Change if needed) 103 | TZ: Asia/Shanghai 104 | user: root 105 | restart: always 106 | entrypoint: "filebeat -e -strict.perms=false" #解决配置文件权限问题 - Solving the configuration file permissions 107 | volumes: 108 | - ./deploy/filebeat/conf/filebeat.yml:/usr/share/filebeat/filebeat.yml 109 | # 此处需指定docker的containers目录,取决于你docker的配置 - The containers directory of docker needs to be specified here, depending on your docker configuration 110 | # 如snap安装的docker,则为/var/snap/docker/common/var-lib-docker/containers - Example if docker is installed by Snap /var/snap/docker/common/var-lib-docker/containers 111 | # - /var/snap/docker/common/var-lib-docker/containers:/var/lib/docker/containers 112 | - /var/lib/docker/containers:/var/lib/docker/containers 113 | networks: 114 | - mall-net 115 | depends_on: 116 | - kafka 117 | 118 | #zookeeper是kafka的依赖 - Zookeeper is the dependencies of Kafka 119 | zookeeper: 120 | image: wurstmeister/zookeeper 121 | container_name: zookeeper 122 | environment: 123 | # 时区上海 - Time zone Shanghai (Change if needed) 124 | TZ: Asia/Shanghai 125 | restart: always 126 | ports: 127 | - 2181:2181 128 | networks: 129 | - mall-net 130 | 131 | #消息队列 - Message queue 132 | kafka: 133 | image: wurstmeister/kafka 134 | container_name: kafka 135 | ports: 136 | - 9092:9092 137 | environment: 138 | - KAFKA_ADVERTISED_HOST_NAME=kafka 139 | - KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181 140 | - KAFKA_AUTO_CREATE_TOPICS_ENABLE=false 141 | - TZ=Asia/Shanghai 142 | restart: always 143 | volumes: 144 | - /var/run/docker.sock:/var/run/docker.sock 145 | networks: 146 | - mall-net 147 | depends_on: 148 | - zookeeper 149 | 150 | #asynqmon asynq延迟队列、定时队列的webui - Asynqmon asynq delay queue, timing queue's webUI 151 | asynqmon: 152 | image: hibiken/asynqmon:latest 153 | container_name: asynqmon 154 | ports: 155 | - 8980:8080 156 | command: 157 | - '--redis-addr=redis:6379' 158 | - '--redis-password=G62m50oigInC30sf' 159 | restart: always 160 | networks: 161 | - mall-net 162 | depends_on: 163 | - redis 164 | 165 | mysql: 166 | image: mysql/mysql-server:8.0.28 167 | container_name: mysql 168 | environment: 169 | # 时区上海 - Time zone Shanghai (Change if needed) 170 | TZ: Asia/Shanghai 171 | # root 密码 - root password 172 | MYSQL_ROOT_PASSWORD: PXDN93VRKUm8TeE7 173 | ports: 174 | - 33069:3306 175 | volumes: 176 | # 数据挂载 - Data mounting 177 | - ./data/mysql/data:/var/lib/mysql 178 | # 日志 179 | command: 180 | # 将mysql8.0默认密码策略 修改为 原先 策略 (mysql8.0对其默认策略做了更改 会导致密码无法匹配) 181 | # Modify the Mysql 8.0 default password strategy to the original strategy (MySQL8.0 to change its default strategy will cause the password to be unable to match) 182 | --default-authentication-plugin=mysql_native_password 183 | --character-set-server=utf8mb4 184 | --collation-server=utf8mb4_general_ci 185 | --explicit_defaults_for_timestamp=true 186 | --lower_case_table_names=1 187 | privileged: true 188 | restart: always 189 | networks: 190 | - mall-net 191 | 192 | #redis容器 - Redis container 193 | redis: 194 | image: redis:6.2.5 195 | container_name: redis 196 | ports: 197 | - 36379:6379 198 | environment: 199 | # 时区上海 - Time zone Shanghai (Change if needed) 200 | TZ: Asia/Shanghai 201 | volumes: 202 | # 数据文件 - data files 203 | - ./data/redis/data:/data:rw 204 | command: "redis-server --requirepass G62m50oigInC30sf --appendonly yes" 205 | privileged: true 206 | restart: always 207 | networks: 208 | - mall-net 209 | # apisix容器 210 | apisix: 211 | image: "apache/apisix:${APISIX_DOCKER_TAG}" 212 | restart: always 213 | volumes: 214 | - ./deploy/apisix_log:/usr/local/apisix/logs 215 | - ./deploy/apisix_conf/config.yaml:/usr/local/apisix/conf/config.yaml:ro 216 | depends_on: 217 | - etcd 218 | ports: 219 | - "9080:9080/tcp" 220 | - "9091:9091/tcp" 221 | - "9443:9443/tcp" 222 | networks: 223 | - mall-net 224 | 225 | etcd: 226 | image: bitnami/etcd:3.4.9 227 | user: root 228 | restart: always 229 | volumes: 230 | - ./deploy/etcd_data:/etcd_data 231 | environment: 232 | ETCD_DATA_DIR: /etcd_data 233 | ETCD_ENABLE_V2: "true" 234 | ALLOW_NONE_AUTHENTICATION: "yes" 235 | ETCD_ADVERTISE_CLIENT_URLS: "http://0.0.0.0:2379" 236 | ETCD_LISTEN_CLIENT_URLS: "http://0.0.0.0:2379" 237 | ports: 238 | - "2379:2379/tcp" 239 | networks: 240 | - mall-net 241 | 242 | 243 | networks: 244 | mall-net: 245 | driver: bridge 246 | ipam: 247 | config: 248 | - subnet: 172.20.0.0/16 -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # 文档 -------------------------------------------------------------------------------- /docs/mrpcreadme.md: -------------------------------------------------------------------------------- 1 | # MRpc 2 | 3 | 参考 zrpc 同属 grpc 4 | 5 | 配置要素 6 | 7 | 1. 超时 8 | 2. 重试 9 | 3. 负载均衡 10 | 4. 多路复用 11 | 5. 连接池 12 | 6. 异常封装 13 | 7. 传输压缩 14 | 8. 鉴权 15 | 9. 自动重连 16 | 17 | 18 | 微服务需要支持功能 19 | 1. 负载均衡 20 | 2. 限流 21 | 3. 自适应熔断 22 | 4. 自适应降载 23 | 5. 自动触发,自动恢复 24 | 6. 超时级联控制 25 | 7. 自动缓存控制 26 | 8. 链路跟踪、统计报警等 27 | 9. 高并发支撑,稳定保障流量洪峰下的服务稳定 -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module mall-go 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/bytedance/gopkg v0.0.0-20230512060433-7f5f1dee0b1e 7 | github.com/cloudwego/hertz v0.6.3 8 | github.com/gin-gonic/gin v1.9.0 9 | github.com/go-playground/validator/v10 v10.13.0 10 | github.com/go-redis/redis v6.15.9+incompatible 11 | github.com/go-redis/redis/v8 v8.11.5 12 | github.com/go-redsync/redsync/v4 v4.8.1 13 | github.com/go-session/redis v3.0.1+incompatible 14 | github.com/go-session/session v3.1.2+incompatible 15 | github.com/golang-jwt/jwt/v4 v4.5.0 16 | github.com/golang/protobuf v1.5.3 17 | github.com/google/uuid v1.3.0 18 | github.com/hertz-contrib/cors v0.0.0-20230423034624-2bc83a8400f0 19 | github.com/hibiken/asynq v0.24.1 20 | github.com/mix-go/xcli v1.1.21 21 | github.com/mix-go/xdi v1.1.17 22 | github.com/mix-go/xsql v1.1.11 23 | github.com/mix-plus/go-mixplus v1.0.2 24 | github.com/sirupsen/logrus v1.9.0 25 | github.com/spf13/viper v1.15.0 26 | github.com/zeromicro/go-zero v1.5.2 27 | go.uber.org/zap v1.24.0 28 | golang.org/x/crypto v0.9.0 29 | google.golang.org/grpc v1.55.0 30 | google.golang.org/protobuf v1.30.0 31 | gopkg.in/natefinch/lumberjack.v2 v2.2.1 32 | gorm.io/driver/mysql v1.5.0 33 | gorm.io/gorm v1.25.1 34 | ) 35 | 36 | require ( 37 | github.com/andeya/ameda v1.5.3 // indirect 38 | github.com/andeya/goutil v1.0.1 // indirect 39 | github.com/beorn7/perks v1.0.1 // indirect 40 | github.com/bytedance/go-tagexpr/v2 v2.9.8 // indirect 41 | github.com/bytedance/sonic v1.8.8 // indirect 42 | github.com/cenkalti/backoff/v4 v4.2.1 // indirect 43 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 44 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect 45 | github.com/cloudwego/netpoll v0.3.2 // indirect 46 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect 47 | github.com/fatih/color v1.15.0 // indirect 48 | github.com/fsnotify/fsnotify v1.6.0 // indirect 49 | github.com/gin-contrib/sse v0.1.0 // indirect 50 | github.com/go-logr/logr v1.2.4 // indirect 51 | github.com/go-logr/stdr v1.2.2 // indirect 52 | github.com/go-playground/locales v0.14.1 // indirect 53 | github.com/go-playground/universal-translator v0.18.1 // indirect 54 | github.com/go-sql-driver/mysql v1.7.1 // indirect 55 | github.com/goccy/go-json v0.10.2 // indirect 56 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2 // indirect 57 | github.com/hashicorp/errwrap v1.1.0 // indirect 58 | github.com/hashicorp/go-multierror v1.1.1 // indirect 59 | github.com/hashicorp/hcl v1.0.0 // indirect 60 | github.com/jinzhu/inflection v1.0.0 // indirect 61 | github.com/jinzhu/now v1.1.5 // indirect 62 | github.com/json-iterator/go v1.1.12 // indirect 63 | github.com/klauspost/cpuid/v2 v2.2.4 // indirect 64 | github.com/leodido/go-urn v1.2.4 // indirect 65 | github.com/magiconair/properties v1.8.7 // indirect 66 | github.com/mattn/go-colorable v0.1.13 // indirect 67 | github.com/mattn/go-isatty v0.0.18 // indirect 68 | github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect 69 | github.com/mitchellh/mapstructure v1.5.0 // indirect 70 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 71 | github.com/modern-go/reflect2 v1.0.2 // indirect 72 | github.com/nyaruka/phonenumbers v1.1.7 // indirect 73 | github.com/openzipkin/zipkin-go v0.4.1 // indirect 74 | github.com/pelletier/go-toml/v2 v2.0.7 // indirect 75 | github.com/prometheus/client_golang v1.15.1 // indirect 76 | github.com/prometheus/client_model v0.4.0 // indirect 77 | github.com/prometheus/common v0.43.0 // indirect 78 | github.com/prometheus/procfs v0.9.0 // indirect 79 | github.com/redis/go-redis/v9 v9.0.4 // indirect 80 | github.com/robfig/cron/v3 v3.0.1 // indirect 81 | github.com/sijms/go-ora/v2 v2.7.6 // indirect 82 | github.com/spaolacci/murmur3 v1.1.0 // indirect 83 | github.com/spf13/afero v1.9.5 // indirect 84 | github.com/spf13/cast v1.5.0 // indirect 85 | github.com/spf13/jwalterweatherman v1.1.0 // indirect 86 | github.com/spf13/pflag v1.0.5 // indirect 87 | github.com/subosito/gotenv v1.4.2 // indirect 88 | github.com/tidwall/match v1.1.1 // indirect 89 | github.com/tidwall/pretty v1.2.1 // indirect 90 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 91 | github.com/ugorji/go/codec v1.2.11 // indirect 92 | go.opentelemetry.io/otel v1.15.1 // indirect 93 | go.opentelemetry.io/otel/exporters/jaeger v1.15.1 // indirect 94 | go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.15.1 // indirect 95 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.15.1 // indirect 96 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.15.1 // indirect 97 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.15.1 // indirect 98 | go.opentelemetry.io/otel/exporters/zipkin v1.15.1 // indirect 99 | go.opentelemetry.io/otel/sdk v1.15.1 // indirect 100 | go.opentelemetry.io/otel/trace v1.15.1 // indirect 101 | go.opentelemetry.io/proto/otlp v0.19.0 // indirect 102 | go.uber.org/atomic v1.11.0 // indirect 103 | go.uber.org/automaxprocs v1.5.2 // indirect 104 | go.uber.org/multierr v1.11.0 // indirect 105 | golang.org/x/arch v0.3.0 // indirect 106 | golang.org/x/net v0.10.0 // indirect 107 | golang.org/x/sys v0.8.0 // indirect 108 | golang.org/x/text v0.9.0 // indirect 109 | golang.org/x/time v0.3.0 // indirect 110 | google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect 111 | gopkg.in/ini.v1 v1.67.0 // indirect 112 | gopkg.in/yaml.v3 v3.0.1 // indirect 113 | ) 114 | -------------------------------------------------------------------------------- /pkg/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cexll/mall-go/9a8277c0b34d49a90e48e824e331c0da40f9ad1e/pkg/.gitkeep -------------------------------------------------------------------------------- /pkg/convert/convert.go: -------------------------------------------------------------------------------- 1 | package convert 2 | 3 | import "strconv" 4 | 5 | type StringTo string 6 | 7 | func (s StringTo) String() string { 8 | return string(s) 9 | } 10 | 11 | func (s StringTo) Int() (int, error) { 12 | v, err := strconv.Atoi(s.String()) 13 | return v, err 14 | } 15 | 16 | func (s StringTo) MustInt() int { 17 | v, _ := s.Int() 18 | return v 19 | } 20 | 21 | func (s StringTo) UInt32() (uint32, error) { 22 | v, err := strconv.Atoi(s.String()) 23 | return uint32(v), err 24 | } 25 | 26 | func (s StringTo) MustUInt32() uint32 { 27 | v, _ := s.UInt32() 28 | return v 29 | } 30 | 31 | func (s StringTo) Int64() (int64, error) { 32 | v, err := strconv.ParseInt(s.String(), 10, 64) 33 | return v, err 34 | } 35 | 36 | func (s StringTo) MustInt64() int64 { 37 | v, _ := s.Int64() 38 | return v 39 | } 40 | 41 | func (s StringTo) Float64() (float64, error) { 42 | return strconv.ParseFloat(s.String(), 64) 43 | } 44 | 45 | func (s StringTo) MustFloat64() float64 { 46 | v, _ := strconv.ParseFloat(s.String(), 64) 47 | return v 48 | } 49 | -------------------------------------------------------------------------------- /pkg/convert/convert_test.go: -------------------------------------------------------------------------------- 1 | package convert 2 | 3 | import "testing" 4 | 5 | func TestStrTo_String(t *testing.T) { 6 | type fields struct { 7 | Str string 8 | } 9 | tests := []struct { 10 | name string 11 | fields fields 12 | want string 13 | }{ 14 | { 15 | name: "test", 16 | fields: fields{Str: "test"}, 17 | want: "test", 18 | }, 19 | } 20 | for _, tt := range tests { 21 | t.Run(tt.name, func(t *testing.T) { 22 | s := StringTo(tt.fields.Str) 23 | if got := s.String(); got != tt.want { 24 | t.Errorf("StrTo.String() = %v, want %v", got, tt.want) 25 | } 26 | }) 27 | } 28 | } 29 | 30 | func TestStrTo_Int(t *testing.T) { 31 | var s StringTo = "123" 32 | i, err := s.Int() 33 | if err != nil { 34 | t.Errorf("s.Int() error: %v", err) 35 | } 36 | if i != 123 { 37 | t.Errorf("s.Int() should be 123") 38 | } 39 | } 40 | 41 | func TestStrTo_MustInt(t *testing.T) { 42 | var s StringTo = "123" 43 | i := s.MustInt() 44 | if i != 123 { 45 | t.Errorf("s.MustInt() should be 123") 46 | } 47 | } 48 | 49 | func TestStrTo_UInt32(t *testing.T) { 50 | var s StringTo = "123" 51 | i, err := s.UInt32() 52 | if err != nil { 53 | t.Errorf("s.UInt32() error: %v", err) 54 | } 55 | if i != 123 { 56 | t.Errorf("s.UInt32() should be 123") 57 | } 58 | } 59 | 60 | func TestStrTo_UInt32_2(t *testing.T) { 61 | var s StringTo = "-123" 62 | i, err := s.UInt32() 63 | if err != nil { 64 | t.Errorf("s.UInt32() error: %v", err) 65 | } 66 | if i == 123 { 67 | t.Errorf("s.UInt32() should not be 123") 68 | } 69 | } 70 | 71 | func TestStrTo_MustUInt32(t *testing.T) { 72 | var s StringTo = "123" 73 | i := s.MustUInt32() 74 | if i != 123 { 75 | t.Errorf("s.MustUInt32() should be 123") 76 | } 77 | } 78 | 79 | func TestStrTo_Int64(t *testing.T) { 80 | var s StringTo = "123" 81 | i, err := s.Int64() 82 | if err != nil { 83 | t.Errorf("s.Int64() error: %v", err) 84 | } 85 | if i != 123 { 86 | t.Errorf("s.Int64() should be 123") 87 | } 88 | } 89 | 90 | func TestStrTo_MustInt64(t *testing.T) { 91 | var s StringTo = "123" 92 | i := s.MustInt64() 93 | if i != 123 { 94 | t.Errorf("s.MustInt64() should be 123") 95 | } 96 | } 97 | 98 | func TestStrTo_Float64(t *testing.T) { 99 | var s StringTo = "123.456" 100 | f, err := s.Float64() 101 | if err != nil { 102 | t.Errorf("s.Float64() error: %v", err) 103 | } 104 | if f != 123.456 { 105 | t.Errorf("s.Float64() should be 123.456") 106 | } 107 | } 108 | 109 | func TestStrTo_MustFloat64(t *testing.T) { 110 | var s StringTo = "123.456" 111 | f := s.MustFloat64() 112 | if f != 123.456 { 113 | t.Errorf("s.MustFloat64() should be 123.456") 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /pkg/di/logrus.go: -------------------------------------------------------------------------------- 1 | package di 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | "path/filepath" 8 | "runtime" 9 | 10 | "github.com/mix-go/xcli" 11 | "github.com/mix-go/xdi" 12 | "github.com/sirupsen/logrus" 13 | "gopkg.in/natefinch/lumberjack.v2" 14 | ) 15 | 16 | func init() { 17 | obj := xdi.Object{ 18 | Name: "logrus", 19 | New: func() (i interface{}, e error) { 20 | logger := logrus.New() 21 | logger.ReportCaller = true // 显示调用信息 22 | formatter := new(logrus.TextFormatter) 23 | formatter.FullTimestamp = true 24 | formatter.TimestampFormat = "2006-01-02 15:04:05.000" 25 | formatter.DisableQuote = true // 不转义换行符,为了保存错误堆栈到日志文件 26 | formatter.CallerPrettyfier = func(frame *runtime.Frame) (function string, file string) { 27 | return "", fmt.Sprintf("%s:%d", filepath.Base(frame.File), frame.Line) 28 | } 29 | logger.Formatter = formatter 30 | filename := fmt.Sprintf("%s/../logs/mix.log", xcli.App().BasePath) 31 | fileRotate := &lumberjack.Logger{ 32 | Filename: filename, 33 | MaxBackups: 7, 34 | } 35 | writer := io.MultiWriter(os.Stdout, fileRotate) 36 | logger.SetOutput(writer) 37 | if xcli.App().Debug { 38 | logger.SetLevel(logrus.DebugLevel) 39 | } 40 | return logger, nil 41 | }, 42 | } 43 | if err := xdi.Provide(&obj); err != nil { 44 | panic(err) 45 | } 46 | } 47 | 48 | func Logrus() (logger *logrus.Logger) { 49 | if err := xdi.Populate("logrus", &logger); err != nil { 50 | panic(err) 51 | } 52 | return 53 | } 54 | -------------------------------------------------------------------------------- /pkg/di/server.go: -------------------------------------------------------------------------------- 1 | package di 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/mix-go/xdi" 7 | ) 8 | 9 | func init() { 10 | obj := xdi.Object{ 11 | Name: "server", 12 | New: func() (i interface{}, e error) { 13 | return &http.Server{}, nil 14 | }, 15 | } 16 | if err := xdi.Provide(&obj); err != nil { 17 | panic(err) 18 | } 19 | } 20 | 21 | func Server() (s *http.Server) { 22 | if err := xdi.Populate("server", &s); err != nil { 23 | panic(err) 24 | } 25 | return 26 | } 27 | -------------------------------------------------------------------------------- /pkg/di/zap.go: -------------------------------------------------------------------------------- 1 | package di 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "time" 7 | 8 | "github.com/mix-go/xcli" 9 | "github.com/mix-go/xdi" 10 | "go.uber.org/zap" 11 | "go.uber.org/zap/zapcore" 12 | "gopkg.in/natefinch/lumberjack.v2" 13 | ) 14 | 15 | func init() { 16 | obj := xdi.Object{ 17 | Name: "zap", 18 | New: func() (i interface{}, e error) { 19 | filename := fmt.Sprintf("%s/../logs/mix.log", xcli.App().BasePath) 20 | fileRotate := &lumberjack.Logger{ 21 | Filename: filename, 22 | MaxBackups: 7, 23 | } 24 | atomicLevel := zap.NewAtomicLevelAt(zap.InfoLevel) 25 | core := zapcore.NewCore( 26 | zapcore.NewConsoleEncoder(zapcore.EncoderConfig{ 27 | TimeKey: "T", 28 | LevelKey: "L", 29 | NameKey: "N", 30 | CallerKey: "C", 31 | MessageKey: "M", 32 | StacktraceKey: "S", 33 | LineEnding: zapcore.DefaultLineEnding, 34 | EncodeLevel: zapcore.CapitalLevelEncoder, 35 | EncodeTime: func(t time.Time, enc zapcore.PrimitiveArrayEncoder) { 36 | enc.AppendString(t.Format("2006-01-02 15:04:05.000")) 37 | }, 38 | EncodeDuration: zapcore.StringDurationEncoder, 39 | EncodeCaller: zapcore.ShortCallerEncoder, 40 | }), 41 | zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(fileRotate)), 42 | atomicLevel, 43 | ) 44 | logger := zap.New(core, zap.AddCaller()) 45 | if xcli.App().Debug { 46 | atomicLevel.SetLevel(zap.DebugLevel) 47 | } 48 | return logger.Sugar(), nil 49 | }, 50 | } 51 | if err := xdi.Provide(&obj); err != nil { 52 | panic(err) 53 | } 54 | } 55 | 56 | func Zap() (logger *zap.SugaredLogger) { 57 | if err := xdi.Populate("zap", &logger); err != nil { 58 | panic(err) 59 | } 60 | return 61 | } 62 | 63 | type ZapOutput struct { 64 | Logger *zap.SugaredLogger 65 | } 66 | 67 | func (t *ZapOutput) Write(p []byte) (n int, err error) { 68 | t.Logger.Info(string(p)) 69 | return len(p), nil 70 | } 71 | -------------------------------------------------------------------------------- /pkg/gorm.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "gorm.io/driver/mysql" 5 | "gorm.io/gorm" 6 | "mall-go/common/conf" 7 | ) 8 | 9 | func NewGorm(c conf.DbConf) *gorm.DB { 10 | db, err := gorm.Open(mysql.Open(c.DSN)) 11 | if err != nil { 12 | panic(err) 13 | } 14 | return db 15 | } 16 | -------------------------------------------------------------------------------- /pkg/hash/hash.go: -------------------------------------------------------------------------------- 1 | package hash 2 | 3 | import ( 4 | "golang.org/x/crypto/bcrypt" 5 | ) 6 | 7 | func PasswordHash(password string) (string, error) { 8 | bts, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) 9 | return string(bts), err 10 | } 11 | 12 | func PasswordVerify(password, hash string) bool { 13 | err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) 14 | return err != nil 15 | } 16 | -------------------------------------------------------------------------------- /pkg/hash/hash_test.go: -------------------------------------------------------------------------------- 1 | package hash 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestGenerateHash(t *testing.T) { 9 | hashPass, err := PasswordHash("123456") 10 | if err != nil { 11 | t.Error(err) 12 | return 13 | } 14 | t.Log(hashPass) 15 | } 16 | 17 | func TestVerifyHash(t *testing.T) { 18 | hashPass, err := PasswordHash("123456") 19 | if err != nil { 20 | t.Error(err) 21 | return 22 | } 23 | t.Log(hashPass) 24 | very := PasswordVerify("123456", hashPass) 25 | if !very { 26 | fmt.Println(true) 27 | return 28 | } 29 | fmt.Println(false) 30 | } 31 | -------------------------------------------------------------------------------- /pkg/jwtx/jwtx.go: -------------------------------------------------------------------------------- 1 | package jwtx 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "time" 7 | 8 | "github.com/golang-jwt/jwt/v4" 9 | "mall-go/common/conf" 10 | ) 11 | 12 | type Jwt struct { 13 | Secret string 14 | } 15 | 16 | func NewJwt(c conf.JwtAuth) *Jwt { 17 | return &Jwt{ 18 | Secret: c.Secret, 19 | } 20 | } 21 | 22 | func (t *Jwt) GenerateJwt(uid int64) (string, error) { 23 | now := time.Now().Unix() 24 | token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ 25 | "iss": "mall-go", // 签发人 26 | "iat": now, // 签发时间 27 | "exp": now + int64(7200), // 过期时间 28 | "nbf": time.Date(2015, 10, 10, 12, 0, 0, 0, time.UTC).Unix(), // 什么时间之前不可用 29 | "uid": strconv.FormatInt(uid, 10), 30 | }) 31 | tokenString, err := token.SignedString([]byte(t.Secret)) 32 | if err != nil { 33 | return "", err 34 | } 35 | 36 | return tokenString, err 37 | } 38 | 39 | var hmacSampleSecret []byte 40 | 41 | func (t *Jwt) CheckJwt(tokenString string) bool { 42 | token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { 43 | if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { 44 | return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) 45 | } 46 | return hmacSampleSecret, nil 47 | }) 48 | 49 | if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { 50 | fmt.Println(claims["foo"], claims["nbf"]) 51 | return true 52 | } 53 | fmt.Println(err) 54 | return false 55 | } 56 | -------------------------------------------------------------------------------- /pkg/lock/lock.go: -------------------------------------------------------------------------------- 1 | package lock 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | 7 | goredislib "github.com/go-redis/redis/v8" 8 | "github.com/go-redsync/redsync/v4" 9 | "github.com/go-redsync/redsync/v4/redis/goredis/v8" 10 | "mall-go/common/conf" 11 | ) 12 | 13 | var ( 14 | once sync.Once 15 | rs *redsync.Redsync 16 | ) 17 | 18 | // NewLock 获取分布式锁 19 | func NewLock(c conf.RedisConf) *redsync.Redsync { 20 | once.Do(func() { 21 | client := goredislib.NewClient(&goredislib.Options{ 22 | Addr: c.Addr, 23 | Password: c.Pass, 24 | DB: int(c.DataBase), 25 | DialTimeout: time.Duration(c.Timeout) * time.Second, 26 | }) 27 | pool := goredis.NewPool(client) 28 | rs = redsync.New(pool) 29 | }) 30 | 31 | return rs 32 | } 33 | -------------------------------------------------------------------------------- /pkg/redis.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/go-redis/redis" 7 | "mall-go/common/conf" 8 | ) 9 | 10 | func NewRedis(c conf.RedisConf) (client *redis.Client) { 11 | return redis.NewClient(&redis.Options{ 12 | Addr: c.Addr, 13 | Password: c.Pass, 14 | DB: int(c.DataBase), 15 | DialTimeout: time.Duration(c.Timeout) * time.Second, 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /pkg/session.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/go-session/redis" 7 | "github.com/go-session/session" 8 | "mall-go/common/conf" 9 | ) 10 | 11 | func NewSession(c conf.RedisConf) *session.Manager { 12 | opts := redis.Options{ 13 | Addr: c.Addr, 14 | Password: c.Pass, 15 | DB: int(c.DataBase), 16 | DialTimeout: time.Duration(c.Timeout) * time.Second, 17 | } 18 | opt := redis.NewRedisStore(&opts) 19 | return session.NewManager(session.SetStore(opt)) 20 | } 21 | -------------------------------------------------------------------------------- /pkg/uuid/uuid.go: -------------------------------------------------------------------------------- 1 | package uuid 2 | 3 | import "github.com/google/uuid" 4 | 5 | func NewUuid() string { 6 | return uuid.New().String() 7 | } 8 | -------------------------------------------------------------------------------- /pkg/uuid/uuid_test.go: -------------------------------------------------------------------------------- 1 | package uuid 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestUuid(t *testing.T) { 9 | for i := 0; i < 100; i++ { 10 | fmt.Println(NewUuid()) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /pkg/validator/validator.go: -------------------------------------------------------------------------------- 1 | package validator 2 | 3 | import ( 4 | "reflect" 5 | "sync" 6 | 7 | "github.com/gin-gonic/gin/binding" 8 | "github.com/go-playground/validator/v10" 9 | ) 10 | 11 | type DefaultValidator struct { 12 | once sync.Once 13 | validate *validator.Validate 14 | } 15 | 16 | var _ binding.StructValidator = &DefaultValidator{} 17 | 18 | // ValidateStruct 如果接收到的类型是一个结构体或指向结构体的指针,则执行验证。 19 | func (v *DefaultValidator) ValidateStruct(obj interface{}) error { 20 | if kindOfData(obj) == reflect.Struct { 21 | 22 | v.lazyinit() 23 | 24 | //如果传递不合规则的值,则返回InvalidValidationError,否则返回nil。 25 | ///如果返回err != nil,可通过err.(validator.ValidationErrors)来访问错误数组。 26 | if err := v.validate.Struct(obj); err != nil { 27 | return err 28 | } 29 | } 30 | return nil 31 | } 32 | 33 | // Engine 返回支持`StructValidator`实现的底层验证引擎 34 | func (v *DefaultValidator) Engine() interface{} { 35 | v.lazyinit() 36 | return v.validate 37 | } 38 | 39 | func (v *DefaultValidator) lazyinit() { 40 | v.once.Do(func() { 41 | v.validate = validator.New() 42 | v.validate.SetTagName("validate") 43 | // //v8版本,v8版本使用"binding" 44 | // v.validate.SetTagName("binding") 45 | }) 46 | } 47 | 48 | func kindOfData(data interface{}) reflect.Kind { 49 | value := reflect.ValueOf(data) 50 | valueType := value.Kind() 51 | 52 | if valueType == reflect.Ptr { 53 | valueType = value.Elem().Kind() 54 | } 55 | return valueType 56 | } 57 | -------------------------------------------------------------------------------- /pkg/xsql.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "database/sql" 5 | 6 | "github.com/mix-go/xsql" 7 | "github.com/zeromicro/go-zero/core/logx" 8 | "mall-go/common/conf" 9 | ) 10 | 11 | func NewXsql(c conf.DbConf) *xsql.DB { 12 | db, err := sql.Open("mysql", c.DSN) 13 | if err != nil { 14 | panic(err) 15 | } 16 | return xsql.New(db, xsql.Options{ 17 | DebugFunc: func(l *xsql.Log) { 18 | logx.Infof("SQL Time: %s Sql: %s Args: %s", l.Time, l.SQL, l.Bindings) 19 | }, 20 | }) 21 | } 22 | --------------------------------------------------------------------------------