├── .gitattributes ├── .github └── workflows │ └── deploy.yml ├── .gitignore ├── Makefile ├── README.MD ├── api ├── analytics │ ├── analytics.go │ └── v1 │ │ ├── clicks-devices.go │ │ ├── clicks-region.go │ │ └── clicks-time.go ├── common │ ├── common.go │ └── v1 │ │ ├── get-captcha.go │ │ └── upload-file.go ├── login │ ├── login.go │ └── v1 │ │ ├── email-login.go │ │ ├── github-login.go │ │ ├── refresh-token.go │ │ ├── registration.go │ │ ├── sign-out.go │ │ ├── user-reg-check.go │ │ ├── verification-code-login.go │ │ ├── we-chat-mini-program-login.go │ │ └── ws.go ├── short_url │ ├── short_url.go │ └── v1 │ │ ├── batch-export.go │ │ ├── batch-import.go │ │ ├── create.go │ │ ├── delete.go │ │ ├── get-list.go │ │ ├── get-url.go │ │ └── template-download.go ├── short_url_code │ ├── short_url_code.go │ └── v1 │ │ └── batch-create.go ├── short_url_visits │ ├── short_url_visits.go │ └── v1 │ │ └── get-list.go ├── user │ ├── user.go │ └── v1 │ │ ├── create.go │ │ ├── delete.go │ │ ├── get-list.go │ │ ├── get-one.go │ │ └── update.go └── weChatMiniProgram │ ├── v1 │ ├── get-mini-program-code.go │ └── get-openId.go │ └── weChatMiniProgram.go ├── go.mod ├── go.sum ├── hack ├── config.yaml ├── hack-cli.mk └── hack.mk ├── internal ├── cmd │ ├── cmd.go │ ├── cron.go │ └── short_url.go ├── consts │ └── consts.go ├── controller │ ├── analytics │ │ ├── analytics.go │ │ ├── analytics_new.go │ │ ├── analytics_v1_clicks_devices.go │ │ ├── analytics_v1_clicks_region.go │ │ └── analytics_v1_clicks_time.go │ ├── common │ │ ├── common.go │ │ ├── common_new.go │ │ ├── common_v1_get_captcha.go │ │ └── common_v1_upload_file.go │ ├── login │ │ ├── login.go │ │ ├── login_new.go │ │ ├── login_v1_email_login.go │ │ ├── login_v1_github_login.go │ │ ├── login_v1_refresh_token.go │ │ ├── login_v1_registration.go │ │ ├── login_v1_sign_out.go │ │ ├── login_v1_user_reg_check.go │ │ ├── login_v1_verification_code_login.go │ │ ├── login_v1_ws.go │ │ └── login_v1_wx_mini_program_login.go │ ├── short_url │ │ ├── short_url.go │ │ ├── short_url_new.go │ │ ├── short_url_v1_batch_export.go │ │ ├── short_url_v1_batch_import.go │ │ ├── short_url_v1_create.go │ │ ├── short_url_v1_delete.go │ │ ├── short_url_v1_get_list.go │ │ ├── short_url_v1_get_url.go │ │ └── short_url_v1_template_download.go │ ├── short_url_code │ │ ├── short_url_code.go │ │ ├── short_url_code_new.go │ │ └── short_url_code_v1_batch_create.go │ ├── short_url_visits │ │ ├── short_url_visits.go │ │ ├── short_url_visits_new.go │ │ └── short_url_visits_v1_get_list.go │ ├── user │ │ ├── user.go │ │ ├── user_new.go │ │ ├── user_v1_create.go │ │ ├── user_v1_delete.go │ │ ├── user_v1_get_list.go │ │ ├── user_v1_get_one.go │ │ └── user_v1_update.go │ └── weChatMiniProgram │ │ ├── weChatMiniProgram.go │ │ ├── weChatMiniProgram_new.go │ │ ├── weChatMiniProgram_v1_get_mini_program_code.go │ │ └── weChatMiniProgram_v1_get_open_id.go ├── dao │ ├── internal │ │ ├── short_url.go │ │ ├── short_url_code.go │ │ ├── short_url_visits.go │ │ └── user.go │ ├── short_url.go │ ├── short_url_code.go │ ├── short_url_visits.go │ └── user.go ├── logic │ ├── analytics │ │ ├── analytics.go │ │ ├── clicks-devices.go │ │ ├── clicks-region.go │ │ └── clicks-time.go │ ├── auth │ │ └── auth.go │ ├── common │ │ ├── common.go │ │ ├── send-verification-code-email.go │ │ └── upload_file.go │ ├── logic.go │ ├── short_url │ │ ├── batch-import.go │ │ └── short_url.go │ ├── short_url_code │ │ └── short_url_code.go │ ├── short_url_visits │ │ └── short_url_visits.go │ ├── user │ │ └── user.go │ └── weChatMiniProgram │ │ ├── get-openId.go │ │ ├── get-wx-token.go │ │ └── weChatMiniProgram.go ├── middlewares │ ├── admin.go │ └── auth.go ├── model │ ├── common.go │ ├── do │ │ ├── short_url.go │ │ ├── short_url_code.go │ │ ├── short_url_visits.go │ │ └── user.go │ ├── entity │ │ ├── short_url.go │ │ ├── short_url_code.go │ │ ├── short_url_visits.go │ │ └── user.go │ ├── login.go │ ├── shortURL.go │ └── user.go ├── packed │ └── packed.go └── service │ ├── analytics.go │ ├── auth.go │ ├── common.go │ ├── login.go │ ├── short_url.go │ ├── short_url_code.go │ ├── short_url_visits.go │ ├── user.go │ └── we_chat_mini_program.go ├── main.go ├── manifest ├── config │ └── exampleConfig.yaml ├── deploy │ ├── kustomize │ │ ├── base │ │ │ ├── deployment.yaml │ │ │ ├── kustomization.yaml │ │ │ └── service.yaml │ │ └── overlays │ │ │ └── develop │ │ │ ├── configmap.yaml │ │ │ ├── deployment.yaml │ │ │ └── kustomization.yaml │ └── sql │ │ ├── short_url.sql │ │ ├── short_url_code.sql │ │ ├── short_url_visits.sql │ │ └── user.sql └── docker │ ├── Dockerfile │ └── docker.sh ├── resource ├── public │ ├── html │ │ └── .gitkeep │ ├── plugin │ │ └── .gitkeep │ └── resource │ │ ├── css │ │ └── .gitkeep │ │ ├── image │ │ └── .gitkeep │ │ └── js │ │ └── .gitkeep └── template │ ├── .gitkeep │ └── shortUrl │ └── template.xlsx ├── run.sh └── utility ├── IntToBase62.go └── utils.go /.gitattributes: -------------------------------------------------------------------------------- 1 | * linguist-language=GO -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Build and Deploy 2 | 3 | on: 4 | push: 5 | branches: 6 | - dev 7 | 8 | jobs: 9 | build-and-deploy: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v4.1.7 15 | 16 | - name: Set up Go 17 | uses: actions/setup-go@v4 18 | with: 19 | go-version: '1.20' # 设置 Go 版本为 1.20 20 | 21 | - name: Install gf-tool 22 | run: | 23 | wget -O gf https://github.com/gogf/gf/releases/latest/download/gf_$(go env GOOS)_$(go env GOARCH) 24 | chmod +x gf 25 | ./gf install -y 26 | rm ./gf 27 | 28 | - name: Build Go application 29 | run: gf build 30 | 31 | - name: Setup SSH 32 | run: | 33 | mkdir -p ~/.ssh 34 | echo "${{ secrets.SSH_KEY }}" > ~/.ssh/id_rsa 35 | chmod 600 ~/.ssh/id_rsa 36 | 37 | - name: build upload 38 | run: | 39 | cd ./linux_amd64 40 | tar -czf main.tar.gz main 41 | scp -o StrictHostKeyChecking=no -P 22 main.tar.gz ${{ secrets.SERVER_USERNAME }}@${{ secrets.SERVER_HOST }}:/usr/local/kk/kkdl/main.tar.gz 42 | 43 | - name: Deploy to server 44 | uses: appleboy/ssh-action@master 45 | with: 46 | host: ${{ secrets.SERVER_HOST }} 47 | username: ${{ secrets.SERVER_USERNAME }} 48 | password: ${{ secrets.SERVER_PASSWORD }} 49 | port: 22 50 | script: | 51 | if [ -e /usr/local/kk/kkdl/main ]; then 52 | mv /usr/local/kk/kkdl/main /usr/local/kk/kkdl/oldman 53 | fi 54 | cd /usr/local/kk/kkdl/ 55 | tar -xzf main.tar.gz 56 | sh run.sh 57 | if [ -e /usr/local/kk/kkdl/oldman ]; then 58 | rm -rf oldman main.tar.gz 59 | fi -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .buildpath 2 | .hgignore.swp 3 | .project 4 | .orig 5 | .swp 6 | .idea/ 7 | .settings/ 8 | .vscode/ 9 | bin/ 10 | **/.DS_Store 11 | gf 12 | main 13 | main.exe 14 | output/ 15 | manifest/output/ 16 | temp/ 17 | temp.yaml 18 | bin 19 | **/config/config.yaml 20 | **/config/config.pro.yaml 21 | mysql-data 22 | log -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ROOT_DIR = $(shell pwd) 2 | NAMESPACE = "default" 3 | DEPLOY_NAME = "kkdl" 4 | DOCKER_NAME = "kkdl" 5 | 6 | include ./hack/hack.mk -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # kkdl-go 2 | 3 | # 简介 4 | 一个使用 [goframe](https://goframe.org/display/gf) 创建的短链生成服务。 5 | # 详细介绍 6 | 7 | [猛击查看掘金文章](https://juejin.cn/post/7345310754470887458) 8 | 9 | ![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e6d9b9e7cb8f420e80cd5d48839aa4ea~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=2174&h=828&s=193679&e=png&b=ffffff) 10 | 11 | # 配置 12 | ## 指定配置文件启动 13 | `go run main.go --gf.gcfg.file=manifest/config/config.pro.yaml` 14 | 15 | ## 数据库统计索引优化 16 | ```sql 17 | -- 创建索引以优化查询性能 18 | CREATE INDEX idx_short_url_visits_on_created_at ON short_url_visits(created_at); 19 | CREATE INDEX idx_short_url_visits_on_short_url ON short_url_visits(short_url); 20 | ``` 21 | 22 | # todo 23 | 24 | ## 统计分析 25 | 1. 根据区域、设备统计 增加 时间过滤条件 26 | 27 | ## login 28 | 梳理所有登录相关的逻辑,目前写的不太严谨 29 | -------------------------------------------------------------------------------- /api/analytics/analytics.go: -------------------------------------------------------------------------------- 1 | // ================================================================================= 2 | // Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. 3 | // ================================================================================= 4 | 5 | package analytics 6 | 7 | import ( 8 | "context" 9 | 10 | "compressURL/api/analytics/v1" 11 | ) 12 | 13 | type IAnalyticsV1 interface { 14 | ClicksDevices(ctx context.Context, req *v1.ClicksDevicesReq) (res *v1.ClicksDevicesRes, err error) 15 | ClicksRegion(ctx context.Context, req *v1.ClicksRegionReq) (res *v1.ClicksRegionRes, err error) 16 | ClicksTime(ctx context.Context, req *v1.ClicksTimeReq) (res *v1.ClicksTimeRes, err error) 17 | } 18 | -------------------------------------------------------------------------------- /api/analytics/v1/clicks-devices.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "github.com/gogf/gf/v2/frame/g" 5 | ) 6 | 7 | // ClicksDevicesReq 统计分析接口 按设备 8 | type ClicksDevicesReq struct { 9 | g.Meta `path:"/analytics/clicksDevices" method:"get" summary:"根据设备统计短链访问次数" tags:"统计分析"` 10 | Code string `json:"code" dc:"短链 code"` 11 | DateType string `json:"date" dc:"日期类型"` 12 | Type string `json:"type" v:"required|in:devices,browsers,os" dc:"类型"` 13 | } 14 | 15 | type ClicksDevicesItem struct { 16 | Clicks int `json:"clicks" dc:"访问次数"` 17 | Name string `json:"name" dc:"统计项名称"` 18 | } 19 | 20 | type ClicksDevicesRes []ClicksDevicesItem 21 | -------------------------------------------------------------------------------- /api/analytics/v1/clicks-region.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "github.com/gogf/gf/v2/frame/g" 5 | ) 6 | 7 | // ClicksRegionReq 统计分析接口 按区域统计 8 | type ClicksRegionReq struct { 9 | g.Meta `path:"/analytics/clicksRegion" method:"get" summary:"根据区域统计短链访问次数" tags:"统计分析"` 10 | Code string `json:"code" dc:"短链 code"` 11 | DateType string `json:"date" dc:"日期类型"` 12 | Type string `json:"type" v:"required|in:countries,cities" dc:"类型"` 13 | } 14 | 15 | type ClicksRegionItem struct { 16 | Clicks int `json:"clicks" dc:"访问次数"` 17 | Code string `json:"code" dc:"区域 code"` 18 | Name string `json:"name" dc:"区域名称"` 19 | CountryCode string `json:"countryCode" dc:"国家 code"` 20 | } 21 | 22 | type ClicksRegionRes []ClicksRegionItem 23 | -------------------------------------------------------------------------------- /api/analytics/v1/clicks-time.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "github.com/gogf/gf/v2/frame/g" 5 | ) 6 | 7 | // ClicksTimeReq 统计分析接口 按时间统计 8 | type ClicksTimeReq struct { 9 | g.Meta `path:"/analytics/clicksTime" method:"get" summary:"根据时间统计短链访问次数" tags:"统计分析"` 10 | Code string `json:"code" dc:"短链 code"` 11 | DateType string `json:"dateType" v:"required|in:24h,7d,30d" dc:"日期类型"` 12 | } 13 | 14 | type ClicksTimeItem struct { 15 | Clicks int `json:"clicks" dc:"访问次数"` 16 | Time string `json:"time" dc:"日期"` 17 | } 18 | 19 | type ClicksTimeRes []ClicksTimeItem 20 | -------------------------------------------------------------------------------- /api/common/common.go: -------------------------------------------------------------------------------- 1 | // ================================================================================= 2 | // Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. 3 | // ================================================================================= 4 | 5 | package common 6 | 7 | import ( 8 | "context" 9 | 10 | "compressURL/api/common/v1" 11 | ) 12 | 13 | type ICommonV1 interface { 14 | GetCaptcha(ctx context.Context, req *v1.GetCaptchaReq) (res *v1.GetCaptchaRes, err error) 15 | UploadFile(ctx context.Context, req *v1.UploadFileReq) (res *v1.UploadFileRes, err error) 16 | } 17 | -------------------------------------------------------------------------------- /api/common/v1/get-captcha.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "github.com/gogf/gf/v2/frame/g" 5 | ) 6 | 7 | // GetCaptchaReq 获取验证码 8 | type GetCaptchaReq struct { 9 | g.Meta `path:"/common/getCaptcha" method:"get" summary:"获取验证码" tags:"公共"` 10 | Email string `json:"email" v:"required|email#请输入邮箱|邮箱格式不正确" dc:"邮箱"` 11 | } 12 | 13 | type GetCaptchaRes struct { 14 | } 15 | -------------------------------------------------------------------------------- /api/common/v1/upload-file.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "github.com/gogf/gf/v2/frame/g" 5 | "github.com/gogf/gf/v2/net/ghttp" 6 | ) 7 | 8 | // UploadFileReq 文件上传 req 9 | type UploadFileReq struct { 10 | g.Meta `path:"/common/uploadFile" method:"post" summary:"文件上传" tags:"公共"` 11 | File *ghttp.UploadFile `json:"file" type:"file" dc:"选择上传文件"` 12 | } 13 | 14 | type UploadFileRes struct { 15 | Name string `json:"name" dc:"文件名称"` 16 | Url string `json:"url" dc:"文件 url"` 17 | } 18 | -------------------------------------------------------------------------------- /api/login/login.go: -------------------------------------------------------------------------------- 1 | // ================================================================================= 2 | // Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. 3 | // ================================================================================= 4 | 5 | package login 6 | 7 | import ( 8 | "context" 9 | 10 | "compressURL/api/login/v1" 11 | ) 12 | 13 | type ILoginV1 interface { 14 | EmailLogin(ctx context.Context, req *v1.EmailLoginReq) (res *v1.EmailLoginRes, err error) 15 | GithubLogin(ctx context.Context, req *v1.GithubLoginReq) (res *v1.GithubLoginRes, err error) 16 | RefreshToken(ctx context.Context, req *v1.RefreshTokenReq) (res *v1.RefreshTokenRes, err error) 17 | Registration(ctx context.Context, req *v1.RegistrationReq) (res *v1.RegistrationRes, err error) 18 | SignOut(ctx context.Context, req *v1.SignOutReq) (res *v1.SignOutRes, err error) 19 | UserRegCheck(ctx context.Context, req *v1.UserRegCheckReq) (res *v1.UserRegCheckRes, err error) 20 | VerificationCodeLogin(ctx context.Context, req *v1.VerificationCodeLoginReq) (res *v1.VerificationCodeLoginRes, err error) 21 | WxMiniProgramLogin(ctx context.Context, req *v1.WxMiniProgramLoginReq) (res *v1.WxMiniProgramLoginRes, err error) 22 | Ws(ctx context.Context, req *v1.WsReq) (res *v1.WsRes, err error) 23 | } 24 | -------------------------------------------------------------------------------- /api/login/v1/email-login.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "compressURL/internal/model" 5 | "github.com/gogf/gf/v2/frame/g" 6 | ) 7 | 8 | // EmailLoginReq 邮箱登录 req 9 | type EmailLoginReq struct { 10 | g.Meta `path:"/login/emailLogin" method:"post" summary:"邮箱登录" tags:"登录"` 11 | Email string `json:"email" dc:"邮箱"` 12 | Password string `json:"password" dc:"密码"` 13 | } 14 | 15 | type EmailLoginRes model.LoginRes 16 | -------------------------------------------------------------------------------- /api/login/v1/github-login.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "compressURL/internal/model" 5 | "github.com/gogf/gf/v2/frame/g" 6 | ) 7 | 8 | // GithubLoginReq github 登录授权 9 | type GithubLoginReq struct { 10 | g.Meta `path:"/login/githubLogin" method:"post" summary:"github 登录授权" tags:"登录"` 11 | Code string `json:"code" dc:"授权 code"` 12 | } 13 | 14 | type GithubLoginRes model.LoginRes 15 | -------------------------------------------------------------------------------- /api/login/v1/refresh-token.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "github.com/gogf/gf/v2/frame/g" 5 | ) 6 | 7 | // RefreshTokenReq 刷新 token 8 | type RefreshTokenReq struct { 9 | g.Meta `path:"/login/refreshToken" method:"post" summary:"刷新 token" tags:"登录"` 10 | } 11 | 12 | type RefreshTokenRes struct { 13 | Token string `json:"token"` 14 | Expire string `json:"expire"` 15 | } 16 | -------------------------------------------------------------------------------- /api/login/v1/registration.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import "github.com/gogf/gf/v2/frame/g" 4 | 5 | type RegistrationReq struct { 6 | g.Meta `path:"/user/registration" method:"post" summary:"邮箱注册" tags:"登录"` 7 | Email string `json:"email" v:"required#请输入邮箱" dc:"邮箱"` 8 | Password string `json:"password" v:"required#请输入密码" dc:"密码"` 9 | NickName string `json:"nickName" dc:"昵称"` 10 | VerificationCode string `json:"verificationCode" v:"required#请输入验证码" dc:"验证码"` 11 | } 12 | type RegistrationRes struct { 13 | Id string `json:"id" dc:"用户 id"` 14 | } 15 | -------------------------------------------------------------------------------- /api/login/v1/sign-out.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import "github.com/gogf/gf/v2/frame/g" 4 | 5 | // SignOutReq 退出登录 req 6 | type SignOutReq struct { 7 | g.Meta `path:"/login/signOut" method:"get" summary:"退出登录" tags:"登录"` 8 | } 9 | type SignOutRes struct { 10 | } 11 | -------------------------------------------------------------------------------- /api/login/v1/user-reg-check.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "github.com/gogf/gf/v2/frame/g" 5 | ) 6 | 7 | // UserRegCheckReq 检查用户是否已经注册 req 8 | type UserRegCheckReq struct { 9 | g.Meta `path:"/login/userRegCheck" method:"get" summary:"检查用户是否已经注册" tags:"登录"` 10 | Email string `json:"email" dc:"邮箱"` 11 | } 12 | 13 | type UserRegCheckRes struct { 14 | IsRegistered bool `json:"isRegistered" dc:"用户是否注册"` 15 | } 16 | -------------------------------------------------------------------------------- /api/login/v1/verification-code-login.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "compressURL/internal/model" 5 | "github.com/gogf/gf/v2/frame/g" 6 | ) 7 | 8 | // VerificationCodeLoginReq 验证码登录 req 9 | type VerificationCodeLoginReq struct { 10 | g.Meta `path:"/login/verificationCodeLogin" method:"post" summary:"验证码登录" tags:"登录"` 11 | Email string `json:"email" v:"required#请输入邮箱" dc:"邮箱"` 12 | Code string `json:"code" v:"required#请输入验证码" dc:"验证码"` 13 | } 14 | 15 | type VerificationCodeLoginRes model.LoginRes 16 | -------------------------------------------------------------------------------- /api/login/v1/we-chat-mini-program-login.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "compressURL/internal/model" 5 | "github.com/gogf/gf/v2/frame/g" 6 | ) 7 | 8 | // WxMiniProgramLoginReq 微信小程序登录 req 9 | type WxMiniProgramLoginReq struct { 10 | g.Meta `path:"/login/wxMiniProgramLogin" method:"post" summary:"微信小程序登录" tags:"登录"` 11 | Code string `json:"code" v:"required#请输入code" dc:"登录时获取的 code"` 12 | UserCode string `json:"user_code" v:"required#请输入用户 code" dc:"登录时生成的用户 code"` 13 | } 14 | 15 | type WxMiniProgramLoginRes model.LoginRes 16 | -------------------------------------------------------------------------------- /api/login/v1/ws.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import "github.com/gogf/gf/v2/frame/g" 4 | 5 | type WsReq struct { 6 | g.Meta `path:"/login/ws" method:"get" summary:"webSocket" tags:"登录"` 7 | UserCode string `json:"userCode" dc:"用户code"` 8 | } 9 | 10 | type WsRes struct { 11 | } 12 | -------------------------------------------------------------------------------- /api/short_url/short_url.go: -------------------------------------------------------------------------------- 1 | // ================================================================================= 2 | // Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. 3 | // ================================================================================= 4 | 5 | package short_url 6 | 7 | import ( 8 | "context" 9 | 10 | "compressURL/api/short_url/v1" 11 | ) 12 | 13 | type IShortUrlV1 interface { 14 | BatchExport(ctx context.Context, req *v1.BatchExportReq) (res *v1.BatchExportRes, err error) 15 | BatchImport(ctx context.Context, req *v1.BatchImportReq) (res *v1.BatchImportRes, err error) 16 | Create(ctx context.Context, req *v1.CreateReq) (res *v1.CreateRes, err error) 17 | Delete(ctx context.Context, req *v1.DeleteReq) (res *v1.DeleteRes, err error) 18 | GetList(ctx context.Context, req *v1.GetListReq) (res *v1.GetListRes, err error) 19 | GetUrl(ctx context.Context, req *v1.GetUrlReq) (res *v1.GetUrlRes, err error) 20 | TemplateDownload(ctx context.Context, req *v1.TemplateDownloadReq) (res *v1.TemplateDownloadRes, err error) 21 | } 22 | -------------------------------------------------------------------------------- /api/short_url/v1/batch-export.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "github.com/gogf/gf/v2/frame/g" 5 | ) 6 | 7 | // BatchExportReq 批量导出 8 | type BatchExportReq struct { 9 | g.Meta `path:"/shortUrl/batchExport" method:"post" summary:"批量导出" tags:"短链"` 10 | Title string `json:"title" dc:"短链标题"` 11 | RawUrl string `json:"rawUrl" dc:"跳转链接"` 12 | } 13 | 14 | type BatchExportRes struct { 15 | } 16 | -------------------------------------------------------------------------------- /api/short_url/v1/batch-import.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "github.com/gogf/gf/v2/frame/g" 5 | "github.com/gogf/gf/v2/net/ghttp" 6 | ) 7 | 8 | // BatchImportReq 批量导入 9 | type BatchImportReq struct { 10 | g.Meta `path:"/shortUrl/batchImport" method:"post" mime:"multipart/form-data" summary:"批量导入短链" tags:"短链"` 11 | File *ghttp.UploadFile `json:"file" type:"file" dc:"请选择文件"` 12 | } 13 | 14 | type BatchImportRes struct { 15 | } 16 | -------------------------------------------------------------------------------- /api/short_url/v1/create.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import "github.com/gogf/gf/v2/frame/g" 4 | 5 | type CreateReq struct { 6 | g.Meta `path:"/shortUrl/create" method:"post" summary:"创建短链" tags:"短链"` 7 | Title string `json:"title" v:"required#请输入短链标题" dc:"短链标题"` 8 | RawUrl string `json:"rawUrl" v:"required#请输入跳转链接" dc:"跳转链接"` 9 | ExpirationTime string `json:"expirationTime" v:"required#请输入过期时间" dc:"过期时间"` 10 | GroupId int `json:"group_id" dc:"分组id"` 11 | } 12 | type CreateRes struct { 13 | ShortUrl string `json:"shortUrl" dc:"短链"` 14 | } 15 | -------------------------------------------------------------------------------- /api/short_url/v1/delete.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import "github.com/gogf/gf/v2/frame/g" 4 | 5 | type DeleteReq struct { 6 | g.Meta `path:"/shortUrl/delete" method:"delete" summary:"根据 id 删除对应的短链" tags:"短链"` 7 | Id string `json:"id" v:"required#请输入 id" dc:"短链 id"` 8 | } 9 | type DeleteRes struct { 10 | } 11 | -------------------------------------------------------------------------------- /api/short_url/v1/get-list.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "compressURL/internal/model" 5 | "compressURL/internal/model/entity" 6 | "github.com/gogf/gf/v2/frame/g" 7 | ) 8 | 9 | type GetListReq struct { 10 | g.Meta `path:"/shortUrl/getList" method:"post" summary:"获取短链列表" tags:"短链"` 11 | model.PageParams 12 | Title string `json:"title" dc:"短链标题"` 13 | RawUrl string `json:"rawUrl" dc:"跳转链接"` 14 | } 15 | type GetListRes struct { 16 | List []entity.ShortUrl `json:"list" dc:"短链数据"` 17 | model.PageParams 18 | } 19 | -------------------------------------------------------------------------------- /api/short_url/v1/get-url.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import "github.com/gogf/gf/v2/frame/g" 4 | 5 | type GetUrlReq struct { 6 | g.Meta `path:"/shortUrl/getUrl" method:"get" summary:"根据短链获取 url" tags:"短链"` 7 | ShortUrl string `json:"shortUrl" v:"required#请输入 url" dc:"shortUrl"` 8 | } 9 | type GetUrlRes struct { 10 | RawUrl string `json:"rawUrl" dc:"原始 url"` 11 | } 12 | -------------------------------------------------------------------------------- /api/short_url/v1/template-download.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "github.com/gogf/gf/v2/frame/g" 5 | ) 6 | 7 | // TemplateDownloadReq 导入模版下载 8 | type TemplateDownloadReq struct { 9 | g.Meta `path:"/shortUrl/templateDownload" method:"get" summary:"导入模版下载" tags:"短链"` 10 | } 11 | 12 | type TemplateDownloadRes struct { 13 | } 14 | -------------------------------------------------------------------------------- /api/short_url_code/short_url_code.go: -------------------------------------------------------------------------------- 1 | // ================================================================================= 2 | // Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. 3 | // ================================================================================= 4 | 5 | package short_url_code 6 | 7 | import ( 8 | "context" 9 | 10 | "compressURL/api/short_url_code/v1" 11 | ) 12 | 13 | type IShortUrlCodeV1 interface { 14 | BatchCreate(ctx context.Context, req *v1.BatchCreateReq) (res *v1.BatchCreateRes, err error) 15 | } 16 | -------------------------------------------------------------------------------- /api/short_url_code/v1/batch-create.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import "github.com/gogf/gf/v2/frame/g" 4 | 5 | type BatchCreateReq struct { 6 | g.Meta `path:"/shortUrlCode/batchCreate" method:"get" summary:"批量生成短链code" tags:"短链code"` 7 | Num int `json:"num" v:"required#请输入生成数量" dc:"num"` 8 | } 9 | type BatchCreateRes struct { 10 | } 11 | -------------------------------------------------------------------------------- /api/short_url_visits/short_url_visits.go: -------------------------------------------------------------------------------- 1 | // ================================================================================= 2 | // Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. 3 | // ================================================================================= 4 | 5 | package short_url_visits 6 | 7 | import ( 8 | "context" 9 | 10 | "compressURL/api/short_url_visits/v1" 11 | ) 12 | 13 | type IShortUrlVisitsV1 interface { 14 | GetList(ctx context.Context, req *v1.GetListReq) (res *v1.GetListRes, err error) 15 | } 16 | -------------------------------------------------------------------------------- /api/short_url_visits/v1/get-list.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "compressURL/internal/model" 5 | "compressURL/internal/model/entity" 6 | "github.com/gogf/gf/v2/frame/g" 7 | ) 8 | 9 | type GetListReq struct { 10 | g.Meta `path:"/shortUrlVisits/getList" method:"post" summary:"获取短链访问信息列表" tags:"短链访问信息"` 11 | model.PageParams 12 | Code string `json:"code" dc:"短链"` 13 | } 14 | 15 | type GetListRes struct { 16 | List []entity.ShortUrlVisits `json:"list" dc:"短链访问数据"` 17 | model.PageParams 18 | } 19 | -------------------------------------------------------------------------------- /api/user/user.go: -------------------------------------------------------------------------------- 1 | // ================================================================================= 2 | // Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. 3 | // ================================================================================= 4 | 5 | package user 6 | 7 | import ( 8 | "context" 9 | 10 | "compressURL/api/user/v1" 11 | ) 12 | 13 | type IUserV1 interface { 14 | Create(ctx context.Context, req *v1.CreateReq) (res *v1.CreateRes, err error) 15 | Delete(ctx context.Context, req *v1.DeleteReq) (res *v1.DeleteRes, err error) 16 | GetList(ctx context.Context, req *v1.GetListReq) (res *v1.GetListRes, err error) 17 | GetOne(ctx context.Context, req *v1.GetOneReq) (res *v1.GetOneRes, err error) 18 | Update(ctx context.Context, req *v1.UpdateReq) (res *v1.UpdateRes, err error) 19 | } 20 | -------------------------------------------------------------------------------- /api/user/v1/create.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import "github.com/gogf/gf/v2/frame/g" 4 | 5 | type CreateReq struct { 6 | g.Meta `path:"/user/create" method:"post" summary:"创建用户" tags:"用户"` 7 | Email string `json:"email" dc:"邮箱"` 8 | Password string `json:"password" dc:"密码"` 9 | NickName string `json:"nickName" dc:"昵称"` 10 | Role string `json:"role" dc:"角色"` 11 | Avatar string `json:"avatar" dc:"头像"` 12 | } 13 | 14 | type CreateRes struct { 15 | Id string `json:"id" dc:"用户 id"` 16 | } 17 | -------------------------------------------------------------------------------- /api/user/v1/delete.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import "github.com/gogf/gf/v2/frame/g" 4 | 5 | type DeleteReq struct { 6 | g.Meta `path:"/user/delete" method:"delete" summary:"删除用户" tags:"用户"` 7 | Id string `json:"id" v:"required#请输入用户 id" dc:"用户 id"` 8 | } 9 | type DeleteRes struct { 10 | } 11 | -------------------------------------------------------------------------------- /api/user/v1/get-list.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "compressURL/internal/model" 5 | "compressURL/internal/model/entity" 6 | "github.com/gogf/gf/v2/frame/g" 7 | ) 8 | 9 | type GetListReq struct { 10 | g.Meta `path:"/user/getList" method:"post" summary:"获取用户列表" tags:"用户"` 11 | model.PageParams 12 | NickName string `json:"nickName" dc:"昵称"` 13 | Email string `json:"email" dc:"邮箱"` 14 | WxId string `json:"wxId" dc:"微信 id"` 15 | } 16 | type GetListRes struct { 17 | List []entity.User `json:"list" dc:"用户数据"` 18 | model.PageParams 19 | } 20 | -------------------------------------------------------------------------------- /api/user/v1/get-one.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import "github.com/gogf/gf/v2/frame/g" 4 | 5 | type GetOneReq struct { 6 | g.Meta `path:"/user/getOne" method:"get" summary:"获取用户信息详情" tags:"用户"` 7 | UserId string `json:"userId" v:"required#请输入用户 id" dc:"用户 id"` 8 | } 9 | type GetOneRes struct { 10 | Id string `json:"id" dc:"用户 id"` 11 | Email string `json:"email" dc:"邮箱"` 12 | WxId string `json:"wxId" dc:"小程序 id"` 13 | NickName string `json:"nickName" dc:"昵称"` 14 | AccountType string `json:"accountType" dc:"账号类型"` 15 | Role string `json:"role" dc:"角色"` 16 | Avatar string `json:"avatar" dc:"头像"` 17 | } 18 | -------------------------------------------------------------------------------- /api/user/v1/update.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import "github.com/gogf/gf/v2/frame/g" 4 | 5 | type UpdateReq struct { 6 | g.Meta `path:"/user/update" method:"post" summary:"更新用户信息" tags:"用户"` 7 | Id string `json:"id" v:"required#请输入用户 id" dc:"用户 id"` 8 | Email string `json:"email" dc:"邮箱"` 9 | Password string `json:"password" dc:"密码"` 10 | NickName string `json:"nickName" dc:"昵称"` 11 | Role string `json:"role" dc:"角色"` 12 | Avatar string `json:"avatar" dc:"头像"` 13 | } 14 | type UpdateRes struct { 15 | } 16 | -------------------------------------------------------------------------------- /api/weChatMiniProgram/v1/get-mini-program-code.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import "github.com/gogf/gf/v2/frame/g" 4 | 5 | type GetMiniProgramCodeReq struct { 6 | g.Meta `path:"/weChatMiniProgram/getMiniProgramCode" method:"get" summary:"获取小程序码" tags:"微信小程序"` 7 | Scene string `json:"scene" dc:"额外传递的信息"` 8 | Page string `json:"page" dc:"跳转小程序的页面"` 9 | } 10 | 11 | type GetMiniProgramCodeRes struct { 12 | ErrCode int64 `json:"errCode" dc:"错误码"` 13 | ErrMsg string `json:"errMsg" dc:"错误信息"` 14 | } 15 | -------------------------------------------------------------------------------- /api/weChatMiniProgram/v1/get-openId.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import "github.com/gogf/gf/v2/frame/g" 4 | 5 | type GetOpenIdReq struct { 6 | g.Meta `path:"/weChatMiniProgram/getOpenId" method:"get" summary:"获取小程序用户openId" tags:"微信小程序"` 7 | Code string `json:"code" v:"required#请输入code" dc:"登录时获取的 code"` 8 | UserCode string `json:"user_code" v:"required#请输入用户 code" dc:"登录时生成的用户 code"` 9 | } 10 | 11 | type GetOpenIdRes struct { 12 | SessionKey string `json:"session_key" dc:"会话密钥"` 13 | Unionid string `json:"unionid" dc:"用户在开放平台的唯一标识符"` 14 | Openid string `json:"openid" dc:"用户唯一标识"` 15 | ErrCode int64 `json:"errCode" dc:"错误码"` 16 | ErrMsg string `json:"errMsg" dc:"错误信息"` 17 | } 18 | -------------------------------------------------------------------------------- /api/weChatMiniProgram/weChatMiniProgram.go: -------------------------------------------------------------------------------- 1 | // ================================================================================= 2 | // Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. 3 | // ================================================================================= 4 | 5 | package weChatMiniProgram 6 | 7 | import ( 8 | "context" 9 | 10 | "compressURL/api/weChatMiniProgram/v1" 11 | ) 12 | 13 | type IWeChatMiniProgramV1 interface { 14 | GetMiniProgramCode(ctx context.Context, req *v1.GetMiniProgramCodeReq) (res *v1.GetMiniProgramCodeRes, err error) 15 | GetOpenId(ctx context.Context, req *v1.GetOpenIdReq) (res *v1.GetOpenIdRes, err error) 16 | } 17 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module compressURL 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/gogf/gf-jwt/v2 v2.1.0 7 | github.com/gogf/gf/contrib/drivers/mysql/v2 v2.6.3 8 | github.com/gogf/gf/contrib/nosql/redis/v2 v2.6.3 9 | github.com/gogf/gf/v2 v2.6.3 10 | github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible 11 | github.com/qiniu/go-sdk/v7 v7.19.0 12 | github.com/xuri/excelize/v2 v2.8.1 13 | golang.org/x/net v0.22.0 14 | ) 15 | 16 | require ( 17 | github.com/BurntSushi/toml v1.3.2 // indirect 18 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 19 | github.com/clbanning/mxj/v2 v2.7.0 // indirect 20 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect 21 | github.com/fatih/color v1.16.0 // indirect 22 | github.com/fsnotify/fsnotify v1.7.0 // indirect 23 | github.com/go-logr/logr v1.4.1 // indirect 24 | github.com/go-logr/stdr v1.2.2 // indirect 25 | github.com/go-sql-driver/mysql v1.7.1 // indirect 26 | github.com/golang-jwt/jwt/v4 v4.3.0 // indirect 27 | github.com/gorilla/websocket v1.5.1 // indirect 28 | github.com/grokify/html-strip-tags-go v0.1.0 // indirect 29 | github.com/magiconair/properties v1.8.7 // indirect 30 | github.com/mattn/go-colorable v0.1.13 // indirect 31 | github.com/mattn/go-isatty v0.0.20 // indirect 32 | github.com/mattn/go-runewidth v0.0.15 // indirect 33 | github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect 34 | github.com/mssola/useragent v1.0.0 // indirect 35 | github.com/olekukonko/tablewriter v0.0.5 // indirect 36 | github.com/redis/go-redis/v9 v9.5.1 // indirect 37 | github.com/richardlehane/mscfb v1.0.4 // indirect 38 | github.com/richardlehane/msoleps v1.0.3 // indirect 39 | github.com/rivo/uniseg v0.4.7 // indirect 40 | github.com/xuri/efp v0.0.0-20231025114914-d1ff6096ae53 // indirect 41 | github.com/xuri/nfp v0.0.0-20230919160717-d98342af3f05 // indirect 42 | go.opentelemetry.io/otel v1.24.0 // indirect 43 | go.opentelemetry.io/otel/metric v1.24.0 // indirect 44 | go.opentelemetry.io/otel/sdk v1.24.0 // indirect 45 | go.opentelemetry.io/otel/trace v1.24.0 // indirect 46 | golang.org/x/crypto v0.21.0 // indirect 47 | golang.org/x/oauth2 v0.21.0 // indirect 48 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect 49 | golang.org/x/sys v0.18.0 // indirect 50 | golang.org/x/text v0.14.0 // indirect 51 | gopkg.in/yaml.v3 v3.0.1 // indirect 52 | ) 53 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= 2 | github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= 3 | github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= 4 | github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= 5 | github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= 6 | github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 7 | github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= 8 | github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 9 | github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= 10 | github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME= 11 | github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= 12 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 13 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 14 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 15 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 16 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= 17 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= 18 | github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= 19 | github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= 20 | github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= 21 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 22 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 23 | github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= 24 | github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= 25 | github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= 26 | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 27 | github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= 28 | github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 29 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 30 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 31 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 32 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= 33 | github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= 34 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= 35 | github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= 36 | github.com/go-playground/validator/v10 v10.8.0/go.mod h1:9JhgTzTaE31GZDpH/HSvHiRJrJ3iKAgqqH0Bl/Ocjdk= 37 | github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w= 38 | github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 39 | github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= 40 | github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= 41 | github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= 42 | github.com/gogf/gf-jwt/v2 v2.1.0 h1:79/KH6vo3IAtHTvRubE8/NXKDF6/UisItLVpdsTTIjE= 43 | github.com/gogf/gf-jwt/v2 v2.1.0/go.mod h1:AGEJOkG64G8ZYOi50ObYgJ+D7L3W4GcTW65m6VVGbmI= 44 | github.com/gogf/gf/contrib/drivers/mysql/v2 v2.6.3 h1:dRGGKKiT9FEnxhfHFerojy34uCKHgReKgpMbAOtqhsY= 45 | github.com/gogf/gf/contrib/drivers/mysql/v2 v2.6.3/go.mod h1:sGdaCPgN1AY0tho+WYAgYdUHJkXwuDf76M3ASgHXWRQ= 46 | github.com/gogf/gf/contrib/nosql/redis/v2 v2.6.3 h1:tbN3rYVSi5MfCS5qAaZ1Xg3fSsyHeT++tJZqEiH1s4c= 47 | github.com/gogf/gf/contrib/nosql/redis/v2 v2.6.3/go.mod h1:2+evGu1xAlamaYuDdSqa7QCiwPTm1RrGsUFSMc8PyLc= 48 | github.com/gogf/gf/v2 v2.0.0-rc3/go.mod h1:apktt6TleWtCIwpz63vBqUnw8MX8gWKoZyxgDpXFtgM= 49 | github.com/gogf/gf/v2 v2.6.3 h1:DoqeuwU98wotpFoDSQEx8RZbmJdK8KdGiJtzJeqpyIo= 50 | github.com/gogf/gf/v2 v2.6.3/go.mod h1:x2XONYcI4hRQ/4gMNbWHmZrNzSEIg20s2NULbzom5k0= 51 | github.com/golang-jwt/jwt/v4 v4.3.0 h1:kHL1vqdqWNfATmA0FNMdmZNMyZI1U6O31X4rlIPoBog= 52 | github.com/golang-jwt/jwt/v4 v4.3.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= 53 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 54 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 55 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 56 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 57 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 58 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 59 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 60 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 61 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 62 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 63 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 64 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 65 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 66 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 67 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 68 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 69 | github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= 70 | github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= 71 | github.com/grokify/html-strip-tags-go v0.0.1/go.mod h1:2Su6romC5/1VXOQMaWL2yb618ARB8iVo6/DR99A6d78= 72 | github.com/grokify/html-strip-tags-go v0.1.0 h1:03UrQLjAny8xci+R+qjCce/MYnpNXCtgzltlQbOBae4= 73 | github.com/grokify/html-strip-tags-go v0.1.0/go.mod h1:ZdzgfHEzAfz9X6Xe5eBLVblWIxXfYSQ40S/VKrAOGpc= 74 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 75 | github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible h1:jdpOPRN1zP63Td1hDQbZW73xKmzDvZHzVdNYxhnTMDA= 76 | github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A= 77 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 78 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 79 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 80 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= 81 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 82 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 83 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 84 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 85 | github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= 86 | github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= 87 | github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= 88 | github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 89 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 90 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 91 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 92 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 93 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 94 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 95 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 96 | github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 97 | github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= 98 | github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 99 | github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= 100 | github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= 101 | github.com/mssola/useragent v1.0.0 h1:WRlDpXyxHDNfvZaPEut5Biveq86Ze4o4EMffyMxmH5o= 102 | github.com/mssola/useragent v1.0.0/go.mod h1:hz9Cqz4RXusgg1EdI4Al0INR62kP7aPSRNHnpU+b85Y= 103 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= 104 | github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= 105 | github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= 106 | github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= 107 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 108 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= 109 | github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= 110 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= 111 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= 112 | github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= 113 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 114 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 115 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 116 | github.com/qiniu/dyn v1.3.0/go.mod h1:E8oERcm8TtwJiZvkQPbcAh0RL8jO1G0VXJMW3FAWdkk= 117 | github.com/qiniu/go-sdk/v7 v7.19.0 h1:k3AzDPil8QHIQnki6xXt4YRAjE52oRoBUXQ4bV+Wc5U= 118 | github.com/qiniu/go-sdk/v7 v7.19.0/go.mod h1:nqoYCNo53ZlGA521RvRethvxUDvXKt4gtYXOwye868w= 119 | github.com/qiniu/x v1.10.5/go.mod h1:03Ni9tj+N2h2aKnAz+6N0Xfl8FwMEDRC2PAlxekASDs= 120 | github.com/redis/go-redis/v9 v9.5.1 h1:H1X4D3yHPaYrkL5X06Wh6xNVM/pX0Ft4RV0vMGvLBh8= 121 | github.com/redis/go-redis/v9 v9.5.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= 122 | github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM= 123 | github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk= 124 | github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= 125 | github.com/richardlehane/msoleps v1.0.3 h1:aznSZzrwYRl3rLKRT3gUk9am7T/mLNSnJINvN0AQoVM= 126 | github.com/richardlehane/msoleps v1.0.3/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= 127 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 128 | github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= 129 | github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 130 | github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= 131 | github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= 132 | github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= 133 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 134 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 135 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 136 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 137 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 138 | github.com/xuri/efp v0.0.0-20231025114914-d1ff6096ae53 h1:Chd9DkqERQQuHpXjR/HSV1jLZA6uaoiwwH3vSuF3IW0= 139 | github.com/xuri/efp v0.0.0-20231025114914-d1ff6096ae53/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI= 140 | github.com/xuri/excelize/v2 v2.8.1 h1:pZLMEwK8ep+CLIUWpWmvW8IWE/yxqG0I1xcN6cVMGuQ= 141 | github.com/xuri/excelize/v2 v2.8.1/go.mod h1:oli1E4C3Pa5RXg1TBXn4ENCXDV5JUMlBluUhG7c+CEE= 142 | github.com/xuri/nfp v0.0.0-20230919160717-d98342af3f05 h1:qhbILQo1K3mphbwKh1vNm4oGezE1eF9fQWmNiIpSfI4= 143 | github.com/xuri/nfp v0.0.0-20230919160717-d98342af3f05/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ= 144 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 145 | github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 146 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 147 | go.opentelemetry.io/otel v1.0.0/go.mod h1:AjRVh9A5/5DE7S+mZtTR6t8vpKKryam+0lREnfmS4cg= 148 | go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= 149 | go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= 150 | go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= 151 | go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= 152 | go.opentelemetry.io/otel/sdk v1.0.0/go.mod h1:PCrDHlSy5x1kjezSdL37PhbFUMjrsLRshJ2zCzeXwbM= 153 | go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw= 154 | go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= 155 | go.opentelemetry.io/otel/trace v1.0.0/go.mod h1:PXTWqayeFUlJV1YDNhsJYB184+IvAH814St6o6ajzIs= 156 | go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= 157 | go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= 158 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 159 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 160 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 161 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 162 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 163 | golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= 164 | golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= 165 | golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= 166 | golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4= 167 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 168 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 169 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 170 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 171 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 172 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 173 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 174 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 175 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 176 | golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= 177 | golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 178 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 179 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 180 | golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= 181 | golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= 182 | golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= 183 | golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= 184 | golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= 185 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 186 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 187 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 188 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 189 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= 190 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 191 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 192 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 193 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 194 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 195 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 196 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 197 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 198 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 199 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 200 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 201 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 202 | golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 203 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 204 | golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 205 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 206 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 207 | golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 208 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 209 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 210 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 211 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 212 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 213 | golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= 214 | golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 215 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 216 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 217 | golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 218 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 219 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 220 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 221 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 222 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 223 | golang.org/x/text v0.3.8-0.20211105212822-18b340fc7af2/go.mod h1:EFNZuWvGYxIRUEX+K8UmCFwYmZjqcrnq15ZuVldZkZ0= 224 | golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 225 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= 226 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 227 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 228 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 229 | golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 230 | golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= 231 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 232 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 233 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 234 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 235 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 236 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 237 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 238 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 239 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 240 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 241 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 242 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 243 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 244 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 245 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 246 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 247 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 248 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 249 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 250 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 251 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 252 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 253 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 254 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 255 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 256 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 257 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 258 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 259 | -------------------------------------------------------------------------------- /hack/config.yaml: -------------------------------------------------------------------------------- 1 | # CLI tool, only in development environment. 2 | # https://goframe.org/pages/viewpage.action?pageId=3673173 3 | gfcli: 4 | docker: 5 | build: "-a amd64 -s linux -p temp -ew" 6 | tagPrefixes: 7 | - registry.cn-hangzhou.aliyuncs.com/vaebe 8 | gen: 9 | dao: 10 | - link: "mysql:root:123456@tcp(127.0.0.1:3306)/kkdl" 11 | tables: "short_url,short_url_visits,short_url_code,user" 12 | jsonCase: "CamelLower" 13 | # 工具编译配置 14 | build: 15 | name: "main" 16 | arch: "amd64" 17 | system: "linux" 18 | packSrc: "manifest/config,resource/public,resource/template" 19 | mod: "" 20 | cgo: 0 -------------------------------------------------------------------------------- /hack/hack-cli.mk: -------------------------------------------------------------------------------- 1 | 2 | # Install/Update to the latest CLI tool. 3 | .PHONY: cli 4 | cli: 5 | @set -e; \ 6 | wget -O gf https://github.com/gogf/gf/releases/latest/download/gf_$(shell go env GOOS)_$(shell go env GOARCH) && \ 7 | chmod +x gf && \ 8 | ./gf install -y && \ 9 | rm ./gf 10 | 11 | 12 | # Check and install CLI tool. 13 | .PHONY: cli.install 14 | cli.install: 15 | @set -e; \ 16 | gf -v > /dev/null 2>&1 || if [[ "$?" -ne "0" ]]; then \ 17 | echo "GoFame CLI is not installed, start proceeding auto installation..."; \ 18 | make cli; \ 19 | fi; -------------------------------------------------------------------------------- /hack/hack.mk: -------------------------------------------------------------------------------- 1 | include ./hack/hack-cli.mk 2 | 3 | # Update GoFrame and its CLI to latest stable version. 4 | .PHONY: up 5 | up: cli.install 6 | @gf up -a 7 | 8 | # Build binary using configuration from hack/config.yaml. 9 | .PHONY: build 10 | build: cli.install 11 | @gf build -ew 12 | 13 | # Parse api and generate controller/sdk. 14 | .PHONY: ctrl 15 | ctrl: cli.install 16 | @gf gen ctrl 17 | 18 | # Generate Go files for DAO/DO/Entity. 19 | .PHONY: dao 20 | dao: cli.install 21 | @gf gen dao 22 | 23 | # Parse current project go files and generate enums go file. 24 | .PHONY: enums 25 | enums: cli.install 26 | @gf gen enums 27 | 28 | # Generate Go files for Service. 29 | .PHONY: service 30 | service: cli.install 31 | @gf gen service 32 | 33 | 34 | # Build docker image. 35 | .PHONY: image 36 | image: cli.install 37 | $(eval _TAG = $(shell git describe --dirty --always --tags --abbrev=8 --match 'v*' | sed 's/-/./2' | sed 's/-/./2')) 38 | ifneq (, $(shell git status --porcelain 2>/dev/null)) 39 | $(eval _TAG = $(_TAG).dirty) 40 | endif 41 | $(eval _TAG = $(if ${TAG}, ${TAG}, $(_TAG))) 42 | $(eval _PUSH = $(if ${PUSH}, ${PUSH}, )) 43 | @gf docker ${_PUSH} -tn $(DOCKER_NAME):${_TAG}; 44 | 45 | 46 | # Build docker image and automatically push to docker repo. 47 | .PHONY: image.push 48 | image.push: 49 | @make image PUSH=-p; 50 | 51 | 52 | # Deploy image and yaml to current kubectl environment. 53 | .PHONY: deploy 54 | deploy: 55 | $(eval _TAG = $(if ${TAG}, ${TAG}, develop)) 56 | 57 | @set -e; \ 58 | mkdir -p $(ROOT_DIR)/temp/kustomize;\ 59 | cd $(ROOT_DIR)/manifest/deploy/kustomize/overlays/${_ENV};\ 60 | kustomize build > $(ROOT_DIR)/temp/kustomize.yaml;\ 61 | kubectl apply -f $(ROOT_DIR)/temp/kustomize.yaml; \ 62 | if [ $(DEPLOY_NAME) != "" ]; then \ 63 | kubectl patch -n $(NAMESPACE) deployment/$(DEPLOY_NAME) -p "{\"spec\":{\"template\":{\"metadata\":{\"labels\":{\"date\":\"$(shell date +%s)\"}}}}}"; \ 64 | fi; 65 | 66 | 67 | # Parsing protobuf files and generating go files. 68 | .PHONY: pb 69 | pb: cli.install 70 | @gf gen pb 71 | 72 | # Generate protobuf files for database tables. 73 | .PHONY: pbentity 74 | pbentity: cli.install 75 | @gf gen pbentity -------------------------------------------------------------------------------- /internal/cmd/cmd.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "compressURL/internal/controller/analytics" 5 | "compressURL/internal/controller/common" 6 | "compressURL/internal/controller/login" 7 | "compressURL/internal/controller/short_url" 8 | "compressURL/internal/controller/short_url_code" 9 | "compressURL/internal/controller/short_url_visits" 10 | "compressURL/internal/controller/user" 11 | "compressURL/internal/controller/weChatMiniProgram" 12 | "compressURL/internal/middlewares" 13 | "context" 14 | "github.com/gogf/gf/v2/frame/g" 15 | "github.com/gogf/gf/v2/net/ghttp" 16 | "github.com/gogf/gf/v2/os/gcmd" 17 | ) 18 | 19 | func mainFunc(ctx context.Context, parser *gcmd.Parser) (err error) { 20 | 21 | s := g.Server() 22 | 23 | err = GenerateShortLinkScheduledTask(ctx) 24 | 25 | if err != nil { 26 | return err 27 | } 28 | 29 | registerShortURLService(s, ctx) 30 | 31 | s.Group("/api", func(group *ghttp.RouterGroup) { 32 | group.Middleware(ghttp.MiddlewareCORS, ghttp.MiddlewareHandlerResponse) 33 | 34 | // 不需要权限 35 | group.Group("/", func(group *ghttp.RouterGroup) { 36 | group.Bind( 37 | login.NewV1().EmailLogin, 38 | login.NewV1().WxMiniProgramLogin, 39 | login.NewV1().Registration, 40 | login.NewV1().Ws, 41 | login.NewV1().GithubLogin, 42 | login.NewV1().UserRegCheck, 43 | login.NewV1().VerificationCodeLogin, 44 | common.NewV1().GetCaptcha, 45 | weChatMiniProgram.NewV1(), 46 | analytics.NewV1(), 47 | ) 48 | }) 49 | 50 | // 需要权限验证 51 | group.Group("/", func(group *ghttp.RouterGroup) { 52 | group.Middleware(middlewares.Auth) 53 | group.Bind( 54 | login.NewV1().SignOut, 55 | login.NewV1().RefreshToken, 56 | short_url_code.NewV1(), 57 | common.NewV1().UploadFile, 58 | short_url.NewV1(), 59 | short_url_visits.NewV1(), 60 | ) 61 | }) 62 | 63 | group.Group("/", func(group *ghttp.RouterGroup) { 64 | group.Middleware(middlewares.Auth) 65 | group.Middleware(middlewares.UserIsAdmin) 66 | 67 | group.Bind( 68 | user.NewV1(), 69 | ) 70 | }) 71 | }) 72 | s.Run() 73 | return nil 74 | } 75 | 76 | var Main = gcmd.Command{ 77 | Name: "main", 78 | Usage: "main", 79 | Brief: "start http server", 80 | Func: mainFunc, 81 | } 82 | -------------------------------------------------------------------------------- /internal/cmd/cron.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "compressURL/internal/service" 5 | "github.com/gogf/gf/v2/os/gcron" 6 | "github.com/gogf/gf/v2/os/glog" 7 | "golang.org/x/net/context" 8 | ) 9 | 10 | func GenerateShortLinkScheduledTask(ctx context.Context) (err error) { 11 | // 启动一个单例定时任务 12 | _, err = gcron.AddSingleton(ctx, "0 0 3 * * *", func(ctx context.Context) { 13 | 14 | codeCount, _ := service.ShortUrlCode().UnusedCodeCount(ctx) 15 | 16 | if codeCount < 10000 { 17 | _ = service.ShortUrlCode().BatchCreateCode(ctx, 1000) 18 | glog.Info(ctx, "定时任务生成短链 code \r\n") 19 | } else { 20 | glog.Info(ctx, "code 数量:", codeCount, "无需执行生成逻辑 \r\n") 21 | } 22 | }, "GenerateShortLinkScheduledTask") 23 | return err 24 | } 25 | -------------------------------------------------------------------------------- /internal/cmd/short_url.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "compressURL/internal/model/entity" 5 | "compressURL/internal/service" 6 | "context" 7 | "encoding/json" 8 | "fmt" 9 | "github.com/gogf/gf/v2/errors/gerror" 10 | "github.com/gogf/gf/v2/frame/g" 11 | "github.com/gogf/gf/v2/net/ghttp" 12 | "github.com/gogf/gf/v2/text/gstr" 13 | "github.com/mssola/useragent" 14 | "io" 15 | "net/http" 16 | ) 17 | 18 | // IPInfo 结构体用于存储IP地址的详细信息 19 | type IPInfo struct { 20 | Query string `json:"query"` // 查询的IP地址 21 | Status string `json:"status"` // 查询状态,成功或失败 22 | Continent string `json:"continent"` // 大洲名称 23 | ContinentCode string `json:"continentCode"` // 大洲代码 24 | Country string `json:"country"` // 国家名称 25 | CountryCode string `json:"countryCode"` // 国家代码 26 | Region string `json:"region"` // 地区或州的短代码(FIPS或ISO) 27 | RegionName string `json:"regionName"` // 地区或州名称 28 | City string `json:"city"` // 城市名称 29 | District string `json:"district"` // 位置的区 30 | Lat float64 `json:"lat"` // 纬度 31 | Lon float64 `json:"lon"` // 经度 32 | } 33 | 34 | func getIpInfo(ctx context.Context, clientIp string) (IPInfo, error) { 35 | apiUrl := fmt.Sprintf("http://ip-api.com/json/%s?fields=status,message,continent,continentCode,country,countryCode,region,regionName,city,district,lat,lon,query&lang=zh-CN", clientIp) 36 | res, err := g.Client().Get(ctx, apiUrl) 37 | 38 | if err != nil { 39 | return IPInfo{}, gerror.Newf("failed to make request: %s", err) 40 | } 41 | defer res.Body.Close() 42 | 43 | if res.StatusCode != http.StatusOK { 44 | return IPInfo{}, gerror.Newf("unexpected status code: %d", res.StatusCode) 45 | } 46 | 47 | body, err := io.ReadAll(res.Body) 48 | if err != nil { 49 | return IPInfo{}, gerror.Newf("failed to read response body: %s", err) 50 | } 51 | 52 | var ipInfo IPInfo 53 | if err := json.Unmarshal(body, &ipInfo); err != nil { 54 | return IPInfo{}, gerror.Newf("failed to unmarshal JSON: %s", err) 55 | } 56 | 57 | if ipInfo.Status != "success" { 58 | return IPInfo{}, gerror.Newf("IP info retrieval failed: %s", ipInfo.Status) 59 | } 60 | 61 | return ipInfo, nil 62 | } 63 | 64 | func saveVisitsInfo(ctx context.Context, r *ghttp.Request, shortUrlInfo entity.ShortUrl) { 65 | clientIp := r.GetClientIp() 66 | 67 | ipInfo, err := getIpInfo(ctx, clientIp) 68 | 69 | if err != nil { 70 | g.Log().Error(ctx, "获取 ip 信息失败:", err) 71 | } 72 | 73 | curUa := r.Header.Get("User-Agent") 74 | uaInfo := useragent.New(curUa) 75 | 76 | browserName, browserVersion := uaInfo.Browser() 77 | engineName, engineVersion := uaInfo.Engine() 78 | 79 | err = service.ShortUrlVisits().Create(ctx, entity.ShortUrlVisits{ 80 | Id: 0, 81 | UserId: shortUrlInfo.UserId, 82 | ShortUrl: shortUrlInfo.ShortUrl, 83 | RawUrl: shortUrlInfo.RawUrl, 84 | UserAgent: curUa, 85 | BrowserName: browserName, 86 | BrowserVersion: browserVersion, 87 | DeviceModel: uaInfo.Platform(), 88 | EngineName: engineName, 89 | EngineVersion: engineVersion, 90 | OsName: uaInfo.OSInfo().Name, 91 | OsVersion: uaInfo.OSInfo().Version, 92 | Ip: clientIp, 93 | Continent: ipInfo.Continent, 94 | ContinentCode: ipInfo.ContinentCode, 95 | Country: ipInfo.Country, 96 | CountryCode: ipInfo.CountryCode, 97 | Region: ipInfo.Region, 98 | RegionName: ipInfo.RegionName, 99 | City: ipInfo.City, 100 | District: ipInfo.District, 101 | Lat: ipInfo.Lat, 102 | Lon: ipInfo.Lon, 103 | CreatedAt: nil, 104 | UpdatedAt: nil, 105 | DeletedAt: nil, 106 | }) 107 | 108 | if err != nil { 109 | g.Log().Error(ctx, "创建短链访问信息失败:", err) 110 | } 111 | } 112 | 113 | // 注册短链服务 114 | func registerShortURLService(s *ghttp.Server, ctx context.Context) { 115 | s.BindHandler("/:id", func(r *ghttp.Request) { 116 | code := gstr.SubStr(r.Request.RequestURI, 1, len(r.Request.RequestURI)) 117 | 118 | if code == "favicon.ico" { 119 | return 120 | } 121 | 122 | req, err := service.ShortUrl().GenOne(ctx, code) 123 | 124 | if err != nil { 125 | r.Response.Write("未获取到对应的地址,请检查链接是否正确!") 126 | return 127 | } 128 | 129 | go saveVisitsInfo(ctx, r, req) 130 | 131 | http.Redirect(r.Response.ResponseWriter, r.Request, req.RawUrl, http.StatusFound) 132 | }) 133 | } 134 | -------------------------------------------------------------------------------- /internal/consts/consts.go: -------------------------------------------------------------------------------- 1 | package consts 2 | 3 | const ( 4 | WeChatMiniProgramLoginStatusUnLogin = "01" // 小程序用户未登录 5 | WeChatMiniProgramLoginStatusScanning = "02" // 小程序用户扫描中状态 6 | WeChatMiniProgramLoginStatusSuccess = "03" // 小程序用户登录成功 7 | WeChatMiniProgramLoginStatusFailed = "04" // 小程序用户登录失败 8 | WeChatMiniProgramLoginStatusTimeout = "05" // 小程序用户登录超时 9 | ) 10 | -------------------------------------------------------------------------------- /internal/controller/analytics/analytics.go: -------------------------------------------------------------------------------- 1 | // ================================================================================= 2 | // This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. 3 | // ================================================================================= 4 | 5 | package analytics 6 | -------------------------------------------------------------------------------- /internal/controller/analytics/analytics_new.go: -------------------------------------------------------------------------------- 1 | // ================================================================================= 2 | // This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. 3 | // ================================================================================= 4 | 5 | package analytics 6 | 7 | import ( 8 | "compressURL/api/analytics" 9 | ) 10 | 11 | type ControllerV1 struct{} 12 | 13 | func NewV1() analytics.IAnalyticsV1 { 14 | return &ControllerV1{} 15 | } 16 | -------------------------------------------------------------------------------- /internal/controller/analytics/analytics_v1_clicks_devices.go: -------------------------------------------------------------------------------- 1 | package analytics 2 | 3 | import ( 4 | "compressURL/api/analytics/v1" 5 | "compressURL/internal/service" 6 | "context" 7 | ) 8 | 9 | func (c *ControllerV1) ClicksDevices(ctx context.Context, req *v1.ClicksDevicesReq) (res *v1.ClicksDevicesRes, err error) { 10 | loginUserInfo, err := service.Auth().GetLoginUserInfo(ctx) 11 | 12 | userId := "" 13 | // 普通用户只能查询自己的数据, 未登录和管理员查询全部数据 14 | if loginUserInfo.Id != "" && loginUserInfo.Role != "00" { 15 | userId = loginUserInfo.Id 16 | } 17 | 18 | list, err := service.Analytics().GetVisitsByDevice(ctx, *req, userId) 19 | if err != nil { 20 | return res, err 21 | } 22 | 23 | if list == nil { 24 | list = v1.ClicksDevicesRes{} 25 | } 26 | 27 | return &list, nil 28 | } 29 | -------------------------------------------------------------------------------- /internal/controller/analytics/analytics_v1_clicks_region.go: -------------------------------------------------------------------------------- 1 | package analytics 2 | 3 | import ( 4 | "compressURL/internal/service" 5 | "context" 6 | 7 | "compressURL/api/analytics/v1" 8 | ) 9 | 10 | func (c *ControllerV1) ClicksRegion(ctx context.Context, req *v1.ClicksRegionReq) (res *v1.ClicksRegionRes, err error) { 11 | loginUserInfo, err := service.Auth().GetLoginUserInfo(ctx) 12 | 13 | userId := "" 14 | // 普通用户只能查询自己的数据, 未登录和管理员查询全部数据 15 | if loginUserInfo.Id != "" && loginUserInfo.Role != "00" { 16 | userId = loginUserInfo.Id 17 | } 18 | 19 | list, err := service.Analytics().GetVisitsByRegion(ctx, *req, userId) 20 | if err != nil { 21 | return nil, err 22 | } 23 | 24 | if list == nil { 25 | list = v1.ClicksRegionRes{} 26 | } 27 | 28 | return &list, nil 29 | } 30 | -------------------------------------------------------------------------------- /internal/controller/analytics/analytics_v1_clicks_time.go: -------------------------------------------------------------------------------- 1 | package analytics 2 | 3 | import ( 4 | "compressURL/internal/service" 5 | "context" 6 | 7 | "compressURL/api/analytics/v1" 8 | ) 9 | 10 | func (c *ControllerV1) ClicksTime(ctx context.Context, req *v1.ClicksTimeReq) (res *v1.ClicksTimeRes, err error) { 11 | loginUserInfo, err := service.Auth().GetLoginUserInfo(ctx) 12 | 13 | userId := "" 14 | // 普通用户只能查询自己的数据, 未登录和管理员查询全部数据 15 | if loginUserInfo.Id != "" && loginUserInfo.Role != "00" { 16 | userId = loginUserInfo.Id 17 | } 18 | 19 | list, err := service.Analytics().GetVisitsByDate(ctx, *req, userId) 20 | if err != nil { 21 | return nil, err 22 | } 23 | 24 | if list == nil { 25 | list = v1.ClicksTimeRes{} 26 | } 27 | 28 | return &list, err 29 | } 30 | -------------------------------------------------------------------------------- /internal/controller/common/common.go: -------------------------------------------------------------------------------- 1 | // ================================================================================= 2 | // This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. 3 | // ================================================================================= 4 | 5 | package common 6 | -------------------------------------------------------------------------------- /internal/controller/common/common_new.go: -------------------------------------------------------------------------------- 1 | // ================================================================================= 2 | // This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. 3 | // ================================================================================= 4 | 5 | package common 6 | 7 | import ( 8 | "compressURL/api/common" 9 | ) 10 | 11 | type ControllerV1 struct{} 12 | 13 | func NewV1() common.ICommonV1 { 14 | return &ControllerV1{} 15 | } 16 | -------------------------------------------------------------------------------- /internal/controller/common/common_v1_get_captcha.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "compressURL/api/common/v1" 5 | "compressURL/internal/service" 6 | "context" 7 | "fmt" 8 | "github.com/gogf/gf/v2/errors/gerror" 9 | "github.com/gogf/gf/v2/frame/g" 10 | "github.com/gogf/gf/v2/util/grand" 11 | ) 12 | 13 | func (c *ControllerV1) GetCaptcha(ctx context.Context, req *v1.GetCaptchaReq) (res *v1.GetCaptchaRes, err error) { 14 | // 检查验证码是否过期 15 | rdsKey := fmt.Sprintf("verificationCode-%s", req.Email) 16 | ttl, err := g.Redis().TTL(ctx, rdsKey) 17 | if err != nil { 18 | 19 | return nil, err 20 | } 21 | 22 | // 一分钟内只能请求一次 23 | countdown := ttl - 540 24 | 25 | /** 26 | ttl -1 没有设置过期时间,-2 键已经过期或不存在 27 | 且剩余时间大于 1 28 | */ 29 | if ttl != -1 && ttl != -2 && countdown > 1 { 30 | return nil, gerror.Newf("请勿重复请求,请等待 %d 秒后在进行操作!", countdown) 31 | } 32 | 33 | // 发送验证码 34 | code := grand.N(100000, 999999) 35 | err = g.Redis().SetEX(ctx, rdsKey, code, 60*10) 36 | if err != nil { 37 | return nil, gerror.New("redis 缓存邮箱验证码失败!") 38 | } 39 | 40 | err = service.Common().SendVerificationCodeEmail(ctx, code, req.Email) 41 | return nil, err 42 | } 43 | -------------------------------------------------------------------------------- /internal/controller/common/common_v1_upload_file.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "compressURL/internal/service" 5 | "context" 6 | "github.com/gogf/gf/v2/errors/gcode" 7 | "github.com/gogf/gf/v2/errors/gerror" 8 | 9 | "compressURL/api/common/v1" 10 | ) 11 | 12 | func (c *ControllerV1) UploadFile(ctx context.Context, req *v1.UploadFileReq) (res *v1.UploadFileRes, err error) { 13 | if req.File == nil { 14 | return nil, gerror.NewCode(gcode.CodeMissingParameter, "请上传文件") 15 | } 16 | 17 | fileInfo, err := service.Common().UploadFile(ctx, req.File) 18 | if err != nil { 19 | return nil, err 20 | } 21 | 22 | return &v1.UploadFileRes{ 23 | Name: fileInfo.Name, 24 | Url: fileInfo.Url, 25 | }, err 26 | } 27 | -------------------------------------------------------------------------------- /internal/controller/login/login.go: -------------------------------------------------------------------------------- 1 | // ================================================================================= 2 | // This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. 3 | // ================================================================================= 4 | 5 | package login 6 | 7 | import ( 8 | "compressURL/internal/model" 9 | "compressURL/internal/model/entity" 10 | "compressURL/internal/service" 11 | "context" 12 | "github.com/gogf/gf/v2/frame/g" 13 | "github.com/gogf/gf/v2/os/gtime" 14 | ) 15 | 16 | func getLoginRes(ctx context.Context, userInfo entity.User) *model.LoginRes { 17 | // 设置登录用户信息 18 | g.RequestFromCtx(ctx).SetCtxVar("loginInfo", userInfo) 19 | token, expire := service.Auth().AuthInstance().LoginHandler(ctx) 20 | tokenExpire := gtime.NewFromTime(expire).Format("Y-m-d H:i:s") 21 | 22 | return &model.LoginRes{ 23 | Token: token, 24 | TokenExpire: tokenExpire, 25 | UserInfo: entity.User{ 26 | Id: userInfo.Id, 27 | Email: userInfo.Email, 28 | NickName: userInfo.NickName, 29 | AccountType: userInfo.AccountType, 30 | Role: userInfo.Role, 31 | Avatar: userInfo.Avatar, 32 | }, 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /internal/controller/login/login_new.go: -------------------------------------------------------------------------------- 1 | // ================================================================================= 2 | // This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. 3 | // ================================================================================= 4 | 5 | package login 6 | 7 | import ( 8 | "compressURL/api/login" 9 | ) 10 | 11 | type ControllerV1 struct{} 12 | 13 | func NewV1() login.ILoginV1 { 14 | return &ControllerV1{} 15 | } 16 | -------------------------------------------------------------------------------- /internal/controller/login/login_v1_email_login.go: -------------------------------------------------------------------------------- 1 | package login 2 | 3 | import ( 4 | "compressURL/api/login/v1" 5 | "compressURL/internal/model" 6 | "compressURL/internal/service" 7 | "compressURL/utility" 8 | "context" 9 | "github.com/gogf/gf/v2/errors/gerror" 10 | ) 11 | 12 | func (c *ControllerV1) EmailLogin(ctx context.Context, req *v1.EmailLoginReq) (res *v1.EmailLoginRes, err error) { 13 | // 获取用户信息 14 | userInfo, err := service.User().Detail(ctx, model.UserQueryInput{Email: req.Email}) 15 | 16 | if err != nil { 17 | return nil, err 18 | } 19 | 20 | if userInfo == nil { 21 | return nil, gerror.New("您还没有注册!") 22 | } 23 | 24 | if userInfo.Password == "" { 25 | return nil, gerror.New("您没有设置密码,请使用验证码登录!") 26 | } 27 | 28 | if utility.EncryptPassword(req.Password, userInfo.Salt) != userInfo.Password { 29 | return nil, gerror.New("账号或者密码不正确!") 30 | } 31 | 32 | info := getLoginRes(ctx, *userInfo) 33 | return (*v1.EmailLoginRes)(info), nil 34 | } 35 | -------------------------------------------------------------------------------- /internal/controller/login/login_v1_github_login.go: -------------------------------------------------------------------------------- 1 | package login 2 | 3 | import ( 4 | "compressURL/api/login/v1" 5 | "compressURL/internal/model" 6 | "compressURL/internal/model/entity" 7 | "compressURL/internal/service" 8 | "context" 9 | "encoding/json" 10 | "github.com/gogf/gf/v2/errors/gerror" 11 | "github.com/gogf/gf/v2/frame/g" 12 | "golang.org/x/oauth2" 13 | "golang.org/x/oauth2/github" 14 | "net/http" 15 | "strconv" 16 | "time" 17 | ) 18 | 19 | type GithubUserInfo struct { 20 | AvatarURL string `json:"avatar_url"` 21 | Bio string `json:"bio"` 22 | Company string `json:"company"` 23 | CreatedAt time.Time `json:"created_at"` 24 | Email *string `json:"email"` 25 | EventsURL string `json:"events_url"` 26 | Followers int `json:"followers"` 27 | FollowersURL string `json:"followers_url"` 28 | Following int `json:"following"` 29 | FollowingURL string `json:"following_url"` 30 | GistsURL string `json:"gists_url"` 31 | GravatarID string `json:"gravatar_id"` 32 | Hireable *bool `json:"hireable"` 33 | HTMLURL string `json:"html_url"` 34 | ID int `json:"id"` 35 | Location *string `json:"location"` 36 | Login string `json:"login"` 37 | Name string `json:"name"` 38 | NodeID string `json:"node_id"` 39 | OrganizationsURL string `json:"organizations_url"` 40 | PublicGists int `json:"public_gists"` 41 | PublicRepos int `json:"public_repos"` 42 | ReceivedEventsURL string `json:"received_events_url"` 43 | ReposURL string `json:"repos_url"` 44 | SiteAdmin bool `json:"site_admin"` 45 | StarredURL string `json:"starred_url"` 46 | SubscriptionsURL string `json:"subscriptions_url"` 47 | TwitterUsername *string `json:"twitter_username"` 48 | Type string `json:"type"` 49 | UpdatedAt time.Time `json:"updated_at"` 50 | URL string `json:"url"` 51 | } 52 | 53 | func getGithubUserInfo(ctx context.Context, code string) (*GithubUserInfo, error) { 54 | cfg := g.Cfg() 55 | githubOAuthConfig := &oauth2.Config{ 56 | ClientID: cfg.MustGet(ctx, "githubConfig.client_id").String(), 57 | ClientSecret: cfg.MustGet(ctx, "githubConfig.client_secret").String(), 58 | RedirectURL: cfg.MustGet(ctx, "githubConfig.redirect_uri").String(), 59 | Scopes: []string{"user"}, 60 | Endpoint: github.Endpoint, 61 | } 62 | 63 | token, err := githubOAuthConfig.Exchange(ctx, code) 64 | if err != nil { 65 | return nil, gerror.New("获取 github token失败!") 66 | } 67 | 68 | client := githubOAuthConfig.Client(ctx, token) 69 | resp, err := client.Get("https://api.github.com/user") 70 | if err != nil { 71 | return nil, gerror.New("获取 github 用户信息失败!") 72 | } 73 | defer resp.Body.Close() 74 | 75 | if resp.StatusCode != http.StatusOK { 76 | return nil, gerror.New("无法获取 github 用户信息!") 77 | } 78 | 79 | var userInfo GithubUserInfo 80 | if err := json.NewDecoder(resp.Body).Decode(&userInfo); err != nil { 81 | g.Log().Error(ctx, "Failed to parse user info:", err) 82 | return nil, gerror.New("格式化 github 用户信息失败!") 83 | } 84 | 85 | return &userInfo, nil 86 | } 87 | 88 | // GithubLogin github登录 89 | func (c *ControllerV1) GithubLogin(ctx context.Context, req *v1.GithubLoginReq) (*v1.GithubLoginRes, error) { 90 | githubUserInfo, err := getGithubUserInfo(ctx, req.Code) 91 | if err != nil { 92 | return nil, err 93 | } 94 | 95 | // 获取用户信息 96 | userInfo, err := service.User().Detail(ctx, model.UserQueryInput{Id: strconv.Itoa(githubUserInfo.ID)}) 97 | 98 | if err != nil { 99 | return nil, err 100 | } 101 | 102 | // 用户不存在则创建用户 103 | if userInfo == nil { 104 | userInfo = &entity.User{ 105 | Id: strconv.Itoa(githubUserInfo.ID), 106 | NickName: githubUserInfo.Login, 107 | Role: "01", 108 | Avatar: githubUserInfo.AvatarURL, 109 | AccountType: "03", 110 | } 111 | 112 | if _, err = service.User().Create(ctx, *userInfo); err != nil { 113 | return nil, err 114 | } 115 | 116 | if userInfo, err = service.User().Detail(ctx, model.UserQueryInput{Id: userInfo.Id}); err != nil { 117 | return nil, err 118 | } 119 | } 120 | 121 | info := getLoginRes(ctx, *userInfo) 122 | return (*v1.GithubLoginRes)(info), nil 123 | } 124 | -------------------------------------------------------------------------------- /internal/controller/login/login_v1_refresh_token.go: -------------------------------------------------------------------------------- 1 | package login 2 | 3 | import ( 4 | "compressURL/api/login/v1" 5 | "compressURL/internal/service" 6 | "context" 7 | "github.com/gogf/gf/v2/os/gtime" 8 | ) 9 | 10 | func (c *ControllerV1) RefreshToken(ctx context.Context, _ *v1.RefreshTokenReq) (res *v1.RefreshTokenRes, err error) { 11 | token, expire := service.Auth().AuthInstance().RefreshHandler(ctx) 12 | expireStr := gtime.NewFromTime(expire).Format("Y-m-d H:i:s") 13 | 14 | return &v1.RefreshTokenRes{ 15 | Token: token, 16 | Expire: expireStr, 17 | }, nil 18 | } 19 | -------------------------------------------------------------------------------- /internal/controller/login/login_v1_registration.go: -------------------------------------------------------------------------------- 1 | package login 2 | 3 | import ( 4 | "compressURL/api/login/v1" 5 | "compressURL/internal/model/entity" 6 | "compressURL/internal/service" 7 | "context" 8 | "fmt" 9 | "github.com/gogf/gf/v2/errors/gerror" 10 | "github.com/gogf/gf/v2/frame/g" 11 | ) 12 | 13 | func (c *ControllerV1) Registration(ctx context.Context, req *v1.RegistrationReq) (res *v1.RegistrationRes, err error) { 14 | // 获取缓存的验证码 15 | rdsKey := fmt.Sprintf("verificationCode-%s", req.Email) 16 | cacheVerificationCode, err := g.Redis().Get(ctx, rdsKey) 17 | if err != nil { 18 | return nil, gerror.New("获取缓存验证码失败!") 19 | } 20 | 21 | // 验证是否正确 22 | if cacheVerificationCode.String() != req.VerificationCode { 23 | return nil, gerror.New("验证码不正确!") 24 | } 25 | 26 | userinfo := entity.User{ 27 | Email: req.Email, 28 | Password: req.Password, 29 | NickName: req.NickName, 30 | AccountType: "01", 31 | Role: "01", 32 | } 33 | 34 | id, err := service.User().Create(ctx, userinfo) 35 | 36 | if err != nil { 37 | return nil, err 38 | } 39 | return &v1.RegistrationRes{Id: id}, nil 40 | } 41 | -------------------------------------------------------------------------------- /internal/controller/login/login_v1_sign_out.go: -------------------------------------------------------------------------------- 1 | package login 2 | 3 | import ( 4 | "compressURL/internal/service" 5 | "context" 6 | 7 | "compressURL/api/login/v1" 8 | ) 9 | 10 | func (c *ControllerV1) SignOut(ctx context.Context, req *v1.SignOutReq) (res *v1.SignOutRes, err error) { 11 | service.Auth().AuthInstance().LogoutHandler(ctx) 12 | return nil, nil 13 | } 14 | -------------------------------------------------------------------------------- /internal/controller/login/login_v1_user_reg_check.go: -------------------------------------------------------------------------------- 1 | package login 2 | 3 | import ( 4 | "compressURL/internal/model" 5 | "compressURL/internal/service" 6 | "context" 7 | 8 | "compressURL/api/login/v1" 9 | ) 10 | 11 | func (c *ControllerV1) UserRegCheck(ctx context.Context, req *v1.UserRegCheckReq) (res *v1.UserRegCheckRes, err error) { 12 | res = &v1.UserRegCheckRes{} 13 | 14 | info, err := service.User().GetOne(ctx, model.UserQueryInput{Email: req.Email}) 15 | 16 | if info == nil || err != nil { 17 | res.IsRegistered = false 18 | return res, nil 19 | } 20 | 21 | res.IsRegistered = true 22 | 23 | return res, nil 24 | } 25 | -------------------------------------------------------------------------------- /internal/controller/login/login_v1_verification_code_login.go: -------------------------------------------------------------------------------- 1 | package login 2 | 3 | import ( 4 | "compressURL/internal/model" 5 | "compressURL/internal/model/entity" 6 | "compressURL/internal/service" 7 | "context" 8 | "fmt" 9 | "github.com/gogf/gf/v2/errors/gerror" 10 | "github.com/gogf/gf/v2/frame/g" 11 | 12 | "compressURL/api/login/v1" 13 | ) 14 | 15 | func (c *ControllerV1) VerificationCodeLogin(ctx context.Context, req *v1.VerificationCodeLoginReq) (res *v1.VerificationCodeLoginRes, err error) { 16 | // 获取缓存的验证码 17 | rdsKey := fmt.Sprintf("verificationCode-%s", req.Email) 18 | cacheVerificationCode, err := g.Redis().Get(ctx, rdsKey) 19 | 20 | if err != nil { 21 | return nil, gerror.New("获取缓存验证码失败!") 22 | } 23 | 24 | // 验证是否正确 25 | if cacheVerificationCode.String() != req.Code { 26 | return nil, gerror.New("验证码不正确!") 27 | } 28 | 29 | userInfo, err := service.User().Detail(ctx, model.UserQueryInput{Email: req.Email}) 30 | 31 | if err != nil { 32 | return nil, err 33 | } 34 | 35 | // 用户不存在则创建 36 | if userInfo == nil { 37 | userInfo = &entity.User{ 38 | Email: req.Email, 39 | Role: "01", 40 | AccountType: "01", 41 | } 42 | 43 | if _, err = service.User().Create(ctx, *userInfo); err != nil { 44 | return nil, err 45 | } 46 | 47 | if userInfo, err = service.User().Detail(ctx, model.UserQueryInput{Email: userInfo.Email}); err != nil { 48 | return nil, err 49 | } 50 | } 51 | 52 | info := getLoginRes(ctx, *userInfo) 53 | return (*v1.VerificationCodeLoginRes)(info), nil 54 | } 55 | -------------------------------------------------------------------------------- /internal/controller/login/login_v1_ws.go: -------------------------------------------------------------------------------- 1 | package login 2 | 3 | import ( 4 | "context" 5 | "github.com/gogf/gf/v2/frame/g" 6 | "github.com/gogf/gf/v2/net/ghttp" 7 | "github.com/gogf/gf/v2/os/glog" 8 | 9 | "compressURL/api/login/v1" 10 | ) 11 | 12 | var wsClientMap = make(map[string]*ghttp.WebSocket) 13 | 14 | func (c *ControllerV1) Ws(ctx context.Context, req *v1.WsReq) (res *v1.WsRes, err error) { 15 | r := g.RequestFromCtx(ctx) 16 | ws, err := r.WebSocket() 17 | if err != nil { 18 | glog.Error(ctx, err) 19 | r.Exit() 20 | } 21 | 22 | // 用户 code 不存在返回错误信息并关闭连接 23 | if req.UserCode == "" { 24 | if err = ws.WriteMessage(1, []byte(`{"data": "用户code不能为空!"}`)); err != nil { 25 | return nil, err 26 | } 27 | 28 | err = ws.Close() 29 | if err != nil { 30 | return nil, err 31 | } 32 | } 33 | 34 | wsClientMap[req.UserCode] = ws 35 | if err = ws.WriteMessage(1, []byte(`{"status": "ok"}`)); err != nil { 36 | return nil, err 37 | } 38 | 39 | for { 40 | _, _, _ = ws.ReadMessage() 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /internal/controller/login/login_v1_wx_mini_program_login.go: -------------------------------------------------------------------------------- 1 | package login 2 | 3 | import ( 4 | "compressURL/api/login/v1" 5 | "compressURL/internal/model" 6 | "compressURL/internal/model/entity" 7 | "compressURL/internal/service" 8 | "context" 9 | "github.com/gogf/gf/v2/errors/gerror" 10 | ) 11 | 12 | /** 13 | 1. 客户端请求一个 code 14 | 2. 客户端请求生成小程序码接口的时候携带上边获取的 code 15 | 3. 客户端获取到小程序码后,开始轮询改 code 的状态直到返回成功 (验证 code 状态的接口,成功后返回用户信息) 16 | 4. 手机微信扫码-获取小程序码携带的 code 调用接口更新该 code (调用 wx login 后直接登录 or 点击按钮确认) 17 | */ 18 | 19 | func (c *ControllerV1) WxMiniProgramLogin(ctx context.Context, req *v1.WxMiniProgramLoginReq) (res *v1.WxMiniProgramLoginRes, err error) { 20 | // 获取小程序 openId 21 | wxUserInfo, err := service.WeChatMiniProgram().GetOpenId(ctx, req.Code) 22 | if err != nil { 23 | return nil, err 24 | } 25 | 26 | // todo 后续微信小程序登录 Openid 存储到 id 字段 27 | userInfo, err := service.User().Detail(ctx, model.UserQueryInput{Id: wxUserInfo.Openid}) 28 | 29 | if err != nil { 30 | return nil, err 31 | } 32 | 33 | // 用户信息不存在则创建 34 | if userInfo == nil { 35 | userId, err := service.User().Create(ctx, entity.User{ 36 | Id: wxUserInfo.Openid, 37 | AccountType: "02", 38 | Role: "01", 39 | }) 40 | if err != nil { 41 | return nil, gerror.Newf("创建微信用户失败! %s", err) 42 | } 43 | 44 | // 创建完成再次查询用户数据返回 45 | userInfo, _ = service.User().Detail(ctx, model.UserQueryInput{Id: userId}) 46 | } 47 | 48 | info := getLoginRes(ctx, *userInfo) 49 | return (*v1.WxMiniProgramLoginRes)(info), nil 50 | } 51 | -------------------------------------------------------------------------------- /internal/controller/short_url/short_url.go: -------------------------------------------------------------------------------- 1 | // ================================================================================= 2 | // This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. 3 | // ================================================================================= 4 | 5 | package short_url 6 | -------------------------------------------------------------------------------- /internal/controller/short_url/short_url_new.go: -------------------------------------------------------------------------------- 1 | // ================================================================================= 2 | // This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. 3 | // ================================================================================= 4 | 5 | package short_url 6 | 7 | import ( 8 | "compressURL/api/short_url" 9 | ) 10 | 11 | type ControllerV1 struct{} 12 | 13 | func NewV1() short_url.IShortUrlV1 { 14 | return &ControllerV1{} 15 | } 16 | -------------------------------------------------------------------------------- /internal/controller/short_url/short_url_v1_batch_export.go: -------------------------------------------------------------------------------- 1 | package short_url 2 | 3 | import ( 4 | "compressURL/internal/model" 5 | "compressURL/internal/service" 6 | "context" 7 | "fmt" 8 | "github.com/gogf/gf/v2/frame/g" 9 | "github.com/gogf/gf/v2/os/gfile" 10 | "github.com/gogf/gf/v2/os/glog" 11 | "github.com/gogf/gf/v2/os/gtime" 12 | "github.com/xuri/excelize/v2" 13 | 14 | "github.com/gogf/gf/v2/errors/gerror" 15 | 16 | "compressURL/api/short_url/v1" 17 | ) 18 | 19 | func (c *ControllerV1) BatchExport(ctx context.Context, req *v1.BatchExportReq) (res *v1.BatchExportRes, err error) { 20 | loginUserInfo, err := service.Auth().GetLoginUserInfo(ctx) 21 | if err != nil { 22 | return nil, err 23 | } 24 | 25 | userId := loginUserInfo.Id 26 | 27 | // 非管理员用户导出需要携带用户 id 28 | if loginUserInfo.Role == "00" { 29 | userId = "" 30 | if loginUserInfo.Id == "" { 31 | return nil, gerror.Newf("用户id不能为空: %s", err) 32 | } 33 | } 34 | 35 | inParams := v1.GetListReq{ 36 | PageParams: model.PageParams{ 37 | PageSize: 9999, 38 | PageNo: 1, 39 | }, 40 | Title: req.Title, 41 | RawUrl: req.RawUrl, 42 | } 43 | 44 | // 获取数据 45 | dataList, _, err := service.ShortUrl().GetList(ctx, inParams, userId) 46 | if err != nil { 47 | glog.Errorf(ctx, "获取导出数据错误: %s", err) 48 | } 49 | 50 | // 导出 excel 51 | f := excelize.NewFile() 52 | defer func() { 53 | if err := f.Close(); err != nil { 54 | glog.Errorf(ctx, "关闭文件错误: %s", err) 55 | } 56 | }() 57 | 58 | // 创建一个工作表 59 | sheetIndex, err := f.NewSheet("Sheet1") 60 | if err != nil { 61 | return nil, gerror.Newf("创建工作表错误: %s", err) 62 | } 63 | 64 | if err = f.SetColWidth("Sheet1", "A", "H", 20); err != nil { 65 | return nil, gerror.Newf("设置列宽错误: %s", err) 66 | } 67 | 68 | // 设置工作簿的默认工作表 69 | f.SetActiveSheet(sheetIndex) 70 | 71 | // 插入表头 72 | titleList := []string{"短链名称", "短链", "跳转链接", "创建时间", "过期时间", "短链分组id"} 73 | for i, v := range titleList { 74 | cellName, _ := excelize.CoordinatesToCellName(i+1, 1) 75 | if err := f.SetCellValue("Sheet1", cellName, v); err != nil { 76 | return nil, gerror.Newf("设置表头错误: %s", err) 77 | } 78 | } 79 | 80 | // 插入内容 81 | for i, v := range dataList { 82 | row := i + 2 // 从第二行开始插入数据 83 | cells := []interface{}{v.Title, v.ShortUrl, v.RawUrl, v.CreatedAt, v.ExpirationTime, v.GroupId} 84 | if err := f.SetSheetRow("Sheet1", fmt.Sprintf("A%d", row), &cells); err != nil { 85 | return nil, gerror.Newf("设置表数据错误: %s", err) 86 | } 87 | } 88 | 89 | fileName := fmt.Sprintf("短链列表%s.xlsx", gtime.Datetime()) 90 | saveFilePath := "resource/template/shortUrl/" + fileName 91 | if err := f.SaveAs(saveFilePath); err != nil { 92 | return nil, gerror.Newf("保存导出文件错误: %s", err) 93 | } 94 | 95 | g.RequestFromCtx(ctx).Response.ServeFileDownload(saveFilePath, fileName) 96 | 97 | // 完成后删除文件 98 | if err = gfile.Remove(saveFilePath); err != nil { 99 | glog.Error(ctx, "删除文件失败", err) 100 | } 101 | 102 | return nil, nil 103 | } 104 | -------------------------------------------------------------------------------- /internal/controller/short_url/short_url_v1_batch_import.go: -------------------------------------------------------------------------------- 1 | package short_url 2 | 3 | import ( 4 | "compressURL/api/short_url/v1" 5 | "compressURL/internal/model/entity" 6 | "compressURL/internal/service" 7 | "compressURL/utility" 8 | "context" 9 | "fmt" 10 | "github.com/gogf/gf/v2/errors/gerror" 11 | "github.com/gogf/gf/v2/os/gtime" 12 | "github.com/xuri/excelize/v2" 13 | "regexp" 14 | "strings" 15 | ) 16 | 17 | func checkDataFormat(i int, in []string) string { 18 | errStr := "" 19 | 20 | title := in[0] 21 | if title == "" { 22 | errStr += fmt.Sprintf("第 %d 行短链名称不能为空 %s \t", i, title) 23 | } 24 | 25 | url := in[1] 26 | re := regexp.MustCompile(`^https?://`) 27 | if !re.MatchString(url) { 28 | errStr += fmt.Sprintf("第 %d 行跳转链接格式错误 %s \t", i, url) 29 | } 30 | 31 | exTime := in[2] 32 | if gtime.NewFromStrFormat(exTime, "Y-m-d") == nil && gtime.NewFromStrFormat(exTime, "Y-m-d H:i:s") == nil { 33 | errStr += fmt.Sprintf("第 %d 行日期格式错误 %s \t", i, exTime) 34 | } 35 | 36 | return errStr 37 | } 38 | 39 | func (c *ControllerV1) BatchImport(ctx context.Context, req *v1.BatchImportReq) (res *v1.BatchImportRes, err error) { 40 | fileReader, err := req.File.Open() 41 | 42 | if err != nil { 43 | return nil, gerror.New("打开文件失败!") 44 | } 45 | 46 | f, err := excelize.OpenReader(fileReader) 47 | if err != nil { 48 | return nil, gerror.New("读取文件失败!") 49 | } 50 | 51 | // 获取 Sheet1 上所有单元格 52 | rows, err := f.GetRows("Sheet1") 53 | if err != nil { 54 | return nil, gerror.New("获取所有单元格失败!") 55 | } 56 | 57 | // 首行数据不存在返回 58 | if len(rows) == 0 { 59 | return nil, gerror.New("数据不存在!") 60 | } 61 | 62 | firstRowExpected := []string{"短链名称", "跳转链接", "过期时间"} 63 | if !utility.SliceEqual(rows[0], firstRowExpected) { 64 | return nil, gerror.Newf("首列数据不正确!应为: %s", firstRowExpected) 65 | } 66 | 67 | // 获取当前登陆用户信息 68 | loginUserInfo, err := service.Auth().GetLoginUserInfo(ctx) 69 | if err != nil { 70 | return nil, err 71 | } 72 | 73 | errStr := "" 74 | var data []entity.ShortUrl 75 | for i, row := range rows { 76 | if i != 0 { 77 | errStr += checkDataFormat(i, row) + "\r\n" 78 | 79 | data = append(data, entity.ShortUrl{ 80 | RawUrl: row[1], 81 | ExpirationTime: gtime.NewFromStr(row[2]), 82 | UserId: loginUserInfo.Id, 83 | Title: row[0], 84 | }) 85 | } 86 | } 87 | 88 | // 使用 strings.TrimSpace 去除 \r\n 89 | if strings.TrimSpace(errStr) != "" { 90 | return nil, gerror.New(errStr) 91 | } 92 | 93 | _, err = service.ShortUrl().BatchImport(ctx, data) 94 | return nil, err 95 | } 96 | -------------------------------------------------------------------------------- /internal/controller/short_url/short_url_v1_create.go: -------------------------------------------------------------------------------- 1 | package short_url 2 | 3 | import ( 4 | "compressURL/api/short_url/v1" 5 | "compressURL/internal/model/entity" 6 | "compressURL/internal/service" 7 | "context" 8 | "github.com/gogf/gf/v2/os/gtime" 9 | ) 10 | 11 | func (c *ControllerV1) Create(ctx context.Context, req *v1.CreateReq) (res *v1.CreateRes, err error) { 12 | // 获取当前登陆用户信息,获取不到用户信息即视为未登录不赋值用户id 13 | loginUserInfo, _ := service.Auth().GetLoginUserInfo(ctx) 14 | 15 | info := entity.ShortUrl{ 16 | RawUrl: req.RawUrl, 17 | Title: req.Title, 18 | GroupId: req.GroupId, 19 | UserId: loginUserInfo.Id, 20 | ExpirationTime: gtime.NewFromStrFormat(req.ExpirationTime, "Y-m-d H:i:s"), 21 | } 22 | 23 | code, err := service.ShortUrl().Create(ctx, info) 24 | 25 | if err != nil { 26 | return nil, err 27 | } 28 | 29 | return &v1.CreateRes{ShortUrl: code}, nil 30 | } 31 | -------------------------------------------------------------------------------- /internal/controller/short_url/short_url_v1_delete.go: -------------------------------------------------------------------------------- 1 | package short_url 2 | 3 | import ( 4 | "compressURL/internal/service" 5 | "context" 6 | 7 | "compressURL/api/short_url/v1" 8 | ) 9 | 10 | func (c *ControllerV1) Delete(ctx context.Context, req *v1.DeleteReq) (res *v1.DeleteRes, err error) { 11 | loginUserInfo, err := service.Auth().GetLoginUserInfo(ctx) 12 | if err != nil { 13 | return nil, err 14 | } 15 | 16 | userId := "" 17 | if loginUserInfo.Role == "01" { 18 | userId = loginUserInfo.Id 19 | } 20 | 21 | err = service.ShortUrl().Delete(ctx, req.Id, userId) 22 | return 23 | } 24 | -------------------------------------------------------------------------------- /internal/controller/short_url/short_url_v1_get_list.go: -------------------------------------------------------------------------------- 1 | package short_url 2 | 3 | import ( 4 | "compressURL/api/short_url/v1" 5 | "compressURL/internal/model" 6 | "compressURL/internal/service" 7 | "context" 8 | ) 9 | 10 | func (c *ControllerV1) GetList(ctx context.Context, req *v1.GetListReq) (res *v1.GetListRes, err error) { 11 | loginUserInfo, err := service.Auth().GetLoginUserInfo(ctx) 12 | if err != nil { 13 | return nil, err 14 | } 15 | 16 | userId := "" 17 | if loginUserInfo.Role == "01" { 18 | userId = loginUserInfo.Id 19 | } 20 | 21 | list, total, err := service.ShortUrl().GetList(ctx, *req, userId) 22 | 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | return &v1.GetListRes{ 28 | List: list, 29 | PageParams: model.PageParams{ 30 | Total: total, 31 | PageSize: req.PageSize, 32 | PageNo: req.PageNo, 33 | }, 34 | }, nil 35 | } 36 | -------------------------------------------------------------------------------- /internal/controller/short_url/short_url_v1_get_url.go: -------------------------------------------------------------------------------- 1 | package short_url 2 | 3 | import ( 4 | "compressURL/internal/service" 5 | "context" 6 | 7 | "compressURL/api/short_url/v1" 8 | ) 9 | 10 | func (c *ControllerV1) GetUrl(ctx context.Context, req *v1.GetUrlReq) (res *v1.GetUrlRes, err error) { 11 | rawUrl, err := service.ShortUrl().GetShortUrl(ctx, req.ShortUrl) 12 | 13 | println(rawUrl) 14 | if err != nil { 15 | return nil, err 16 | } 17 | 18 | return &v1.GetUrlRes{RawUrl: rawUrl}, nil 19 | } 20 | -------------------------------------------------------------------------------- /internal/controller/short_url/short_url_v1_template_download.go: -------------------------------------------------------------------------------- 1 | package short_url 2 | 3 | import ( 4 | "context" 5 | "github.com/gogf/gf/v2/frame/g" 6 | 7 | "compressURL/api/short_url/v1" 8 | ) 9 | 10 | func (c *ControllerV1) TemplateDownload(ctx context.Context, _ *v1.TemplateDownloadReq) (res *v1.TemplateDownloadRes, err error) { 11 | g.RequestFromCtx(ctx).Response.ServeFileDownload("resource/template/shortUrl/template.xlsx", "批量生成短链模版.xlsx") 12 | return 13 | } 14 | -------------------------------------------------------------------------------- /internal/controller/short_url_code/short_url_code.go: -------------------------------------------------------------------------------- 1 | // ================================================================================= 2 | // This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. 3 | // ================================================================================= 4 | 5 | package short_url_code 6 | -------------------------------------------------------------------------------- /internal/controller/short_url_code/short_url_code_new.go: -------------------------------------------------------------------------------- 1 | // ================================================================================= 2 | // This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. 3 | // ================================================================================= 4 | 5 | package short_url_code 6 | 7 | import ( 8 | "compressURL/api/short_url_code" 9 | ) 10 | 11 | type ControllerV1 struct{} 12 | 13 | func NewV1() short_url_code.IShortUrlCodeV1 { 14 | return &ControllerV1{} 15 | } 16 | -------------------------------------------------------------------------------- /internal/controller/short_url_code/short_url_code_v1_batch_create.go: -------------------------------------------------------------------------------- 1 | package short_url_code 2 | 3 | import ( 4 | "compressURL/internal/service" 5 | "context" 6 | 7 | "github.com/gogf/gf/v2/errors/gerror" 8 | 9 | "compressURL/api/short_url_code/v1" 10 | ) 11 | 12 | func (c *ControllerV1) BatchCreate(ctx context.Context, req *v1.BatchCreateReq) (res *v1.BatchCreateRes, err error) { 13 | // 获取当前登陆用户信息 14 | loginUserInfo, err := service.Auth().GetLoginUserInfo(ctx) 15 | if err != nil { 16 | return nil, err 17 | } 18 | 19 | if loginUserInfo.Role != "00" { 20 | return nil, gerror.New("无权限!") 21 | } 22 | 23 | if req.Num > 1000 { 24 | return nil, gerror.New("每次最多生成 1000 个!") 25 | } 26 | 27 | err = service.ShortUrlCode().BatchCreateCode(ctx, req.Num) 28 | return nil, err 29 | } 30 | -------------------------------------------------------------------------------- /internal/controller/short_url_visits/short_url_visits.go: -------------------------------------------------------------------------------- 1 | // ================================================================================= 2 | // This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. 3 | // ================================================================================= 4 | 5 | package short_url_visits 6 | -------------------------------------------------------------------------------- /internal/controller/short_url_visits/short_url_visits_new.go: -------------------------------------------------------------------------------- 1 | // ================================================================================= 2 | // This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. 3 | // ================================================================================= 4 | 5 | package short_url_visits 6 | 7 | import ( 8 | "compressURL/api/short_url_visits" 9 | ) 10 | 11 | type ControllerV1 struct{} 12 | 13 | func NewV1() short_url_visits.IShortUrlVisitsV1 { 14 | return &ControllerV1{} 15 | } 16 | -------------------------------------------------------------------------------- /internal/controller/short_url_visits/short_url_visits_v1_get_list.go: -------------------------------------------------------------------------------- 1 | package short_url_visits 2 | 3 | import ( 4 | "compressURL/internal/model" 5 | "compressURL/internal/service" 6 | "context" 7 | 8 | "compressURL/api/short_url_visits/v1" 9 | ) 10 | 11 | func (c *ControllerV1) GetList(ctx context.Context, req *v1.GetListReq) (res *v1.GetListRes, err error) { 12 | loginUserInfo, err := service.Auth().GetLoginUserInfo(ctx) 13 | if err != nil { 14 | return nil, err 15 | } 16 | 17 | userId := "" 18 | if loginUserInfo.Role == "01" { 19 | userId = loginUserInfo.Id 20 | } 21 | 22 | list, total, err := service.ShortUrlVisits().GetList(ctx, *req, userId) 23 | 24 | if err != nil { 25 | return nil, err 26 | } 27 | 28 | return &v1.GetListRes{ 29 | List: list, 30 | PageParams: model.PageParams{ 31 | Total: total, 32 | PageSize: req.PageSize, 33 | PageNo: req.PageNo, 34 | }, 35 | }, nil 36 | } 37 | -------------------------------------------------------------------------------- /internal/controller/user/user.go: -------------------------------------------------------------------------------- 1 | // ================================================================================= 2 | // This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. 3 | // ================================================================================= 4 | 5 | package user 6 | -------------------------------------------------------------------------------- /internal/controller/user/user_new.go: -------------------------------------------------------------------------------- 1 | // ================================================================================= 2 | // This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. 3 | // ================================================================================= 4 | 5 | package user 6 | 7 | import ( 8 | "compressURL/api/user" 9 | ) 10 | 11 | type ControllerV1 struct{} 12 | 13 | func NewV1() user.IUserV1 { 14 | return &ControllerV1{} 15 | } 16 | -------------------------------------------------------------------------------- /internal/controller/user/user_v1_create.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | import ( 4 | "compressURL/api/user/v1" 5 | "compressURL/internal/model/entity" 6 | "compressURL/internal/service" 7 | "context" 8 | ) 9 | 10 | func (c *ControllerV1) Create(ctx context.Context, req *v1.CreateReq) (res *v1.CreateRes, err error) { 11 | userinfo := entity.User{ 12 | Email: req.Email, 13 | Password: req.Password, 14 | NickName: req.NickName, 15 | Role: req.Role, 16 | Avatar: req.Avatar, 17 | } 18 | 19 | id, err := service.User().Create(ctx, userinfo) 20 | 21 | if err != nil { 22 | return nil, err 23 | } 24 | return &v1.CreateRes{Id: id}, nil 25 | } 26 | -------------------------------------------------------------------------------- /internal/controller/user/user_v1_delete.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | import ( 4 | "compressURL/internal/service" 5 | "context" 6 | 7 | "compressURL/api/user/v1" 8 | ) 9 | 10 | func (c *ControllerV1) Delete(ctx context.Context, req *v1.DeleteReq) (res *v1.DeleteRes, err error) { 11 | err = service.User().Delete(ctx, req.Id) 12 | return nil, err 13 | } 14 | -------------------------------------------------------------------------------- /internal/controller/user/user_v1_get_list.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | import ( 4 | "compressURL/internal/model" 5 | "compressURL/internal/service" 6 | "context" 7 | 8 | "compressURL/api/user/v1" 9 | ) 10 | 11 | func (c *ControllerV1) GetList(ctx context.Context, req *v1.GetListReq) (res *v1.GetListRes, err error) { 12 | list, total, err := service.User().GetUserList(ctx, *req) 13 | 14 | if err != nil { 15 | return nil, err 16 | } 17 | 18 | for i := range list { 19 | list[i].Password = "" 20 | list[i].Salt = "" 21 | } 22 | 23 | return &v1.GetListRes{ 24 | List: list, 25 | PageParams: model.PageParams{ 26 | Total: total, 27 | PageSize: req.PageSize, 28 | PageNo: req.PageNo, 29 | }, 30 | }, nil 31 | } 32 | -------------------------------------------------------------------------------- /internal/controller/user/user_v1_get_one.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | import ( 4 | "compressURL/internal/model" 5 | "compressURL/internal/service" 6 | "context" 7 | 8 | "compressURL/api/user/v1" 9 | ) 10 | 11 | func (c *ControllerV1) GetOne(ctx context.Context, req *v1.GetOneReq) (res *v1.GetOneRes, err error) { 12 | info, err := service.User().GetOne(ctx, model.UserQueryInput{Id: req.UserId}) 13 | if err != nil { 14 | return nil, err 15 | } 16 | 17 | return info, nil 18 | } 19 | -------------------------------------------------------------------------------- /internal/controller/user/user_v1_update.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | import ( 4 | "compressURL/api/user/v1" 5 | "compressURL/internal/model/entity" 6 | "compressURL/internal/service" 7 | "context" 8 | ) 9 | 10 | func (c *ControllerV1) Update(ctx context.Context, req *v1.UpdateReq) (res *v1.UpdateRes, err error) { 11 | 12 | userInfo := entity.User{ 13 | Id: req.Id, 14 | Email: req.Email, 15 | Password: req.Password, 16 | NickName: req.NickName, 17 | Role: req.Role, 18 | Avatar: req.Avatar, 19 | } 20 | 21 | err = service.User().Update(ctx, userInfo) 22 | 23 | return nil, err 24 | } 25 | -------------------------------------------------------------------------------- /internal/controller/weChatMiniProgram/weChatMiniProgram.go: -------------------------------------------------------------------------------- 1 | // ================================================================================= 2 | // This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. 3 | // ================================================================================= 4 | 5 | package weChatMiniProgram 6 | -------------------------------------------------------------------------------- /internal/controller/weChatMiniProgram/weChatMiniProgram_new.go: -------------------------------------------------------------------------------- 1 | // ================================================================================= 2 | // This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. 3 | // ================================================================================= 4 | 5 | package weChatMiniProgram 6 | 7 | import ( 8 | "compressURL/api/weChatMiniProgram" 9 | ) 10 | 11 | type ControllerV1 struct{} 12 | 13 | func NewV1() weChatMiniProgram.IWeChatMiniProgramV1 { 14 | return &ControllerV1{} 15 | } 16 | -------------------------------------------------------------------------------- /internal/controller/weChatMiniProgram/weChatMiniProgram_v1_get_mini_program_code.go: -------------------------------------------------------------------------------- 1 | package weChatMiniProgram 2 | 3 | import ( 4 | "bytes" 5 | "compressURL/internal/service" 6 | "context" 7 | "encoding/json" 8 | "fmt" 9 | "github.com/gogf/gf/v2/frame/g" 10 | "github.com/gogf/gf/v2/os/glog" 11 | "io" 12 | "log" 13 | 14 | "compressURL/api/weChatMiniProgram/v1" 15 | ) 16 | 17 | func (c *ControllerV1) GetMiniProgramCode(ctx context.Context, req *v1.GetMiniProgramCodeReq) (res *v1.GetMiniProgramCodeRes, err error) { 18 | token, err := service.WeChatMiniProgram().GetWeChatToken(ctx) 19 | if err != nil { 20 | return nil, err 21 | } 22 | 23 | url := fmt.Sprintf("https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=%s", token) 24 | 25 | // 定义要发送的 map 数据 26 | opts := fmt.Sprintf(`{"scene": "%s", "page": "%s"}`, req.Scene, req.Page) 27 | 28 | resData, err := g.Client().Post(ctx, url, opts) 29 | if err != nil { 30 | panic(err) 31 | } 32 | 33 | defer func(Body io.ReadCloser) { 34 | err := Body.Close() 35 | if err != nil { 36 | glog.Error(ctx, "调用获取微信小程序码接口失败", err) 37 | } 38 | }(resData.Body) 39 | 40 | contentType := resData.Header.Get("Content-Type") 41 | 42 | // 判断返回数据是否是 json 类型,json 类型代表响应错误 43 | if "application/json; encoding=utf-8" == contentType { 44 | body, err := io.ReadAll(resData.Body) 45 | if err != nil { 46 | return nil, err 47 | } 48 | 49 | // 解析JSON数据 50 | var resp v1.GetMiniProgramCodeRes 51 | if err := json.Unmarshal(body, &resp); err != nil { 52 | return nil, err 53 | } 54 | return &resp, nil 55 | } 56 | 57 | // 否则读取响应的内容,返回小程序码 58 | body := new(bytes.Buffer) 59 | _, err = body.ReadFrom(resData.Body) 60 | if err != nil { 61 | log.Fatal(err) 62 | } 63 | 64 | g.RequestFromCtx(ctx).Response.Write(body) 65 | return nil, nil 66 | } 67 | -------------------------------------------------------------------------------- /internal/controller/weChatMiniProgram/weChatMiniProgram_v1_get_open_id.go: -------------------------------------------------------------------------------- 1 | package weChatMiniProgram 2 | 3 | import ( 4 | "compressURL/internal/consts" 5 | "compressURL/internal/service" 6 | "compressURL/utility" 7 | "context" 8 | "github.com/gogf/gf/v2/frame/g" 9 | 10 | "github.com/gogf/gf/v2/errors/gerror" 11 | 12 | "compressURL/api/weChatMiniProgram/v1" 13 | ) 14 | 15 | func (c *ControllerV1) GetOpenId(ctx context.Context, req *v1.GetOpenIdReq) (res *v1.GetOpenIdRes, err error) { 16 | info, err := service.WeChatMiniProgram().GetOpenId(ctx, req.Code) 17 | 18 | err = g.Redis().SetEX(ctx, utility.GetWeChatMiniProgramLoginCode(req.UserCode), consts.WeChatMiniProgramLoginStatusSuccess, 60*10) 19 | if err != nil { 20 | return nil, gerror.New("redis 缓存小程序用户登录状态失败!") 21 | } 22 | 23 | return info, err 24 | } 25 | -------------------------------------------------------------------------------- /internal/dao/internal/short_url.go: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. 3 | // ========================================================================== 4 | 5 | package internal 6 | 7 | import ( 8 | "context" 9 | 10 | "github.com/gogf/gf/v2/database/gdb" 11 | "github.com/gogf/gf/v2/frame/g" 12 | ) 13 | 14 | // ShortUrlDao is the data access object for table short_url. 15 | type ShortUrlDao struct { 16 | table string // table is the underlying table name of the DAO. 17 | group string // group is the database configuration group name of current DAO. 18 | columns ShortUrlColumns // columns contains all the column names of Table for convenient usage. 19 | } 20 | 21 | // ShortUrlColumns defines and stores column names for table short_url. 22 | type ShortUrlColumns struct { 23 | Id string // 唯一标识,自增长整数 24 | ShortUrl string // 短链,唯一,不能为空 25 | RawUrl string // 原始 url 不能为空 26 | ExpirationTime string // 过期时间 27 | UserId string // 用户id 28 | CreatedAt string // 创建时间,默认为当前时间戳 29 | Title string // 短链标题 30 | GroupId string // 短链分组id 31 | } 32 | 33 | // shortUrlColumns holds the columns for table short_url. 34 | var shortUrlColumns = ShortUrlColumns{ 35 | Id: "id", 36 | ShortUrl: "shortUrl", 37 | RawUrl: "rawUrl", 38 | ExpirationTime: "expirationTime", 39 | UserId: "userId", 40 | CreatedAt: "created_at", 41 | Title: "title", 42 | GroupId: "group_id", 43 | } 44 | 45 | // NewShortUrlDao creates and returns a new DAO object for table data access. 46 | func NewShortUrlDao() *ShortUrlDao { 47 | return &ShortUrlDao{ 48 | group: "default", 49 | table: "short_url", 50 | columns: shortUrlColumns, 51 | } 52 | } 53 | 54 | // DB retrieves and returns the underlying raw database management object of current DAO. 55 | func (dao *ShortUrlDao) DB() gdb.DB { 56 | return g.DB(dao.group) 57 | } 58 | 59 | // Table returns the table name of current dao. 60 | func (dao *ShortUrlDao) Table() string { 61 | return dao.table 62 | } 63 | 64 | // Columns returns all column names of current dao. 65 | func (dao *ShortUrlDao) Columns() ShortUrlColumns { 66 | return dao.columns 67 | } 68 | 69 | // Group returns the configuration group name of database of current dao. 70 | func (dao *ShortUrlDao) Group() string { 71 | return dao.group 72 | } 73 | 74 | // Ctx creates and returns the Model for current DAO, It automatically sets the context for current operation. 75 | func (dao *ShortUrlDao) Ctx(ctx context.Context) *gdb.Model { 76 | return dao.DB().Model(dao.table).Safe().Ctx(ctx) 77 | } 78 | 79 | // Transaction wraps the transaction logic using function f. 80 | // It rollbacks the transaction and returns the error from function f if it returns non-nil error. 81 | // It commits the transaction and returns nil if function f returns nil. 82 | // 83 | // Note that, you should not Commit or Rollback the transaction in function f 84 | // as it is automatically handled by this function. 85 | func (dao *ShortUrlDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { 86 | return dao.Ctx(ctx).Transaction(ctx, f) 87 | } 88 | -------------------------------------------------------------------------------- /internal/dao/internal/short_url_code.go: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. 3 | // ========================================================================== 4 | 5 | package internal 6 | 7 | import ( 8 | "context" 9 | 10 | "github.com/gogf/gf/v2/database/gdb" 11 | "github.com/gogf/gf/v2/frame/g" 12 | ) 13 | 14 | // ShortUrlCodeDao is the data access object for table short_url_code. 15 | type ShortUrlCodeDao struct { 16 | table string // table is the underlying table name of the DAO. 17 | group string // group is the database configuration group name of current DAO. 18 | columns ShortUrlCodeColumns // columns contains all the column names of Table for convenient usage. 19 | } 20 | 21 | // ShortUrlCodeColumns defines and stores column names for table short_url_code. 22 | type ShortUrlCodeColumns struct { 23 | Id string // 唯一标识,自增长整数 24 | Code string // 短链,唯一,不能为空 25 | Status string // 是否使用 26 | CreatedAt string // 创建时间,默认为当前时间戳 27 | } 28 | 29 | // shortUrlCodeColumns holds the columns for table short_url_code. 30 | var shortUrlCodeColumns = ShortUrlCodeColumns{ 31 | Id: "id", 32 | Code: "code", 33 | Status: "status", 34 | CreatedAt: "created_at", 35 | } 36 | 37 | // NewShortUrlCodeDao creates and returns a new DAO object for table data access. 38 | func NewShortUrlCodeDao() *ShortUrlCodeDao { 39 | return &ShortUrlCodeDao{ 40 | group: "default", 41 | table: "short_url_code", 42 | columns: shortUrlCodeColumns, 43 | } 44 | } 45 | 46 | // DB retrieves and returns the underlying raw database management object of current DAO. 47 | func (dao *ShortUrlCodeDao) DB() gdb.DB { 48 | return g.DB(dao.group) 49 | } 50 | 51 | // Table returns the table name of current dao. 52 | func (dao *ShortUrlCodeDao) Table() string { 53 | return dao.table 54 | } 55 | 56 | // Columns returns all column names of current dao. 57 | func (dao *ShortUrlCodeDao) Columns() ShortUrlCodeColumns { 58 | return dao.columns 59 | } 60 | 61 | // Group returns the configuration group name of database of current dao. 62 | func (dao *ShortUrlCodeDao) Group() string { 63 | return dao.group 64 | } 65 | 66 | // Ctx creates and returns the Model for current DAO, It automatically sets the context for current operation. 67 | func (dao *ShortUrlCodeDao) Ctx(ctx context.Context) *gdb.Model { 68 | return dao.DB().Model(dao.table).Safe().Ctx(ctx) 69 | } 70 | 71 | // Transaction wraps the transaction logic using function f. 72 | // It rollbacks the transaction and returns the error from function f if it returns non-nil error. 73 | // It commits the transaction and returns nil if function f returns nil. 74 | // 75 | // Note that, you should not Commit or Rollback the transaction in function f 76 | // as it is automatically handled by this function. 77 | func (dao *ShortUrlCodeDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { 78 | return dao.Ctx(ctx).Transaction(ctx, f) 79 | } 80 | -------------------------------------------------------------------------------- /internal/dao/internal/short_url_visits.go: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. 3 | // ========================================================================== 4 | 5 | package internal 6 | 7 | import ( 8 | "context" 9 | 10 | "github.com/gogf/gf/v2/database/gdb" 11 | "github.com/gogf/gf/v2/frame/g" 12 | ) 13 | 14 | // ShortUrlVisitsDao is the data access object for table short_url_visits. 15 | type ShortUrlVisitsDao struct { 16 | table string // table is the underlying table name of the DAO. 17 | group string // group is the database configuration group name of current DAO. 18 | columns ShortUrlVisitsColumns // columns contains all the column names of Table for convenient usage. 19 | } 20 | 21 | // ShortUrlVisitsColumns defines and stores column names for table short_url_visits. 22 | type ShortUrlVisitsColumns struct { 23 | Id string // 24 | UserId string // 用户id 25 | ShortUrl string // 短链,唯一,不能为空 26 | RawUrl string // 原始 url 不能为空 27 | UserAgent string // 用户代理字符串,存储提供的完整用户代理 28 | BrowserName string // 浏览器名称 29 | BrowserVersion string // 浏览器版本 30 | DeviceModel string // 设备型号 31 | EngineName string // 浏览器引擎名称 32 | EngineVersion string // 浏览器引擎版本 33 | OsName string // 操作系统名称 34 | OsVersion string // 操作系统版本 35 | Ip string // ip 不能为空 36 | Continent string // 大洲名称 37 | ContinentCode string // 大洲代码 38 | Country string // 国家名称 39 | CountryCode string // 国家代码 40 | Region string // 地区或州的短代码(FIPS或ISO) 41 | RegionName string // 地区或州名称 42 | City string // 城市名称 43 | District string // 位置的区(郡) 44 | Lat string // 纬度 45 | Lon string // 经度 46 | CreatedAt string // 创建时间,默认为当前时间戳 47 | UpdatedAt string // 更新时间 48 | DeletedAt string // 删除时间 49 | } 50 | 51 | // shortUrlVisitsColumns holds the columns for table short_url_visits. 52 | var shortUrlVisitsColumns = ShortUrlVisitsColumns{ 53 | Id: "id", 54 | UserId: "user_id", 55 | ShortUrl: "short_url", 56 | RawUrl: "raw_url", 57 | UserAgent: "user_agent", 58 | BrowserName: "browser_name", 59 | BrowserVersion: "browser_version", 60 | DeviceModel: "device_model", 61 | EngineName: "engine_name", 62 | EngineVersion: "engine_version", 63 | OsName: "os_name", 64 | OsVersion: "os_version", 65 | Ip: "ip", 66 | Continent: "continent", 67 | ContinentCode: "continent_code", 68 | Country: "country", 69 | CountryCode: "country_code", 70 | Region: "region", 71 | RegionName: "region_name", 72 | City: "city", 73 | District: "district", 74 | Lat: "lat", 75 | Lon: "lon", 76 | CreatedAt: "created_at", 77 | UpdatedAt: "updated_at", 78 | DeletedAt: "deleted_at", 79 | } 80 | 81 | // NewShortUrlVisitsDao creates and returns a new DAO object for table data access. 82 | func NewShortUrlVisitsDao() *ShortUrlVisitsDao { 83 | return &ShortUrlVisitsDao{ 84 | group: "default", 85 | table: "short_url_visits", 86 | columns: shortUrlVisitsColumns, 87 | } 88 | } 89 | 90 | // DB retrieves and returns the underlying raw database management object of current DAO. 91 | func (dao *ShortUrlVisitsDao) DB() gdb.DB { 92 | return g.DB(dao.group) 93 | } 94 | 95 | // Table returns the table name of current dao. 96 | func (dao *ShortUrlVisitsDao) Table() string { 97 | return dao.table 98 | } 99 | 100 | // Columns returns all column names of current dao. 101 | func (dao *ShortUrlVisitsDao) Columns() ShortUrlVisitsColumns { 102 | return dao.columns 103 | } 104 | 105 | // Group returns the configuration group name of database of current dao. 106 | func (dao *ShortUrlVisitsDao) Group() string { 107 | return dao.group 108 | } 109 | 110 | // Ctx creates and returns the Model for current DAO, It automatically sets the context for current operation. 111 | func (dao *ShortUrlVisitsDao) Ctx(ctx context.Context) *gdb.Model { 112 | return dao.DB().Model(dao.table).Safe().Ctx(ctx) 113 | } 114 | 115 | // Transaction wraps the transaction logic using function f. 116 | // It rollbacks the transaction and returns the error from function f if it returns non-nil error. 117 | // It commits the transaction and returns nil if function f returns nil. 118 | // 119 | // Note that, you should not Commit or Rollback the transaction in function f 120 | // as it is automatically handled by this function. 121 | func (dao *ShortUrlVisitsDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { 122 | return dao.Ctx(ctx).Transaction(ctx, f) 123 | } 124 | -------------------------------------------------------------------------------- /internal/dao/internal/user.go: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. 3 | // ========================================================================== 4 | 5 | package internal 6 | 7 | import ( 8 | "context" 9 | 10 | "github.com/gogf/gf/v2/database/gdb" 11 | "github.com/gogf/gf/v2/frame/g" 12 | ) 13 | 14 | // UserDao is the data access object for table user. 15 | type UserDao struct { 16 | table string // table is the underlying table name of the DAO. 17 | group string // group is the database configuration group name of current DAO. 18 | columns UserColumns // columns contains all the column names of Table for convenient usage. 19 | } 20 | 21 | // UserColumns defines and stores column names for table user. 22 | type UserColumns struct { 23 | Id string // 唯一标识 24 | Email string // 邮箱,唯一 25 | WxId string // 小程序id,唯一 26 | Password string // 密码, 小程序登录无密码 27 | NickName string // 昵称, 创建默认生成 28 | AccountType string // 账号类型: 01 邮箱 02 小程序 29 | Role string // 角色: 00 admin 01 普通用户 02 vip 30 | DeletedAt string // 删除时间 31 | UpdatedAt string // 更新时间 32 | CreatedAt string // 创建时间 33 | Salt string // 用户盐值 34 | Avatar string // 用户头像 35 | } 36 | 37 | // userColumns holds the columns for table user. 38 | var userColumns = UserColumns{ 39 | Id: "id", 40 | Email: "email", 41 | WxId: "wxId", 42 | Password: "password", 43 | NickName: "nickName", 44 | AccountType: "accountType", 45 | Role: "role", 46 | DeletedAt: "deleted_at", 47 | UpdatedAt: "updated_at", 48 | CreatedAt: "created_at", 49 | Salt: "salt", 50 | Avatar: "avatar", 51 | } 52 | 53 | // NewUserDao creates and returns a new DAO object for table data access. 54 | func NewUserDao() *UserDao { 55 | return &UserDao{ 56 | group: "default", 57 | table: "user", 58 | columns: userColumns, 59 | } 60 | } 61 | 62 | // DB retrieves and returns the underlying raw database management object of current DAO. 63 | func (dao *UserDao) DB() gdb.DB { 64 | return g.DB(dao.group) 65 | } 66 | 67 | // Table returns the table name of current dao. 68 | func (dao *UserDao) Table() string { 69 | return dao.table 70 | } 71 | 72 | // Columns returns all column names of current dao. 73 | func (dao *UserDao) Columns() UserColumns { 74 | return dao.columns 75 | } 76 | 77 | // Group returns the configuration group name of database of current dao. 78 | func (dao *UserDao) Group() string { 79 | return dao.group 80 | } 81 | 82 | // Ctx creates and returns the Model for current DAO, It automatically sets the context for current operation. 83 | func (dao *UserDao) Ctx(ctx context.Context) *gdb.Model { 84 | return dao.DB().Model(dao.table).Safe().Ctx(ctx) 85 | } 86 | 87 | // Transaction wraps the transaction logic using function f. 88 | // It rollbacks the transaction and returns the error from function f if it returns non-nil error. 89 | // It commits the transaction and returns nil if function f returns nil. 90 | // 91 | // Note that, you should not Commit or Rollback the transaction in function f 92 | // as it is automatically handled by this function. 93 | func (dao *UserDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { 94 | return dao.Ctx(ctx).Transaction(ctx, f) 95 | } 96 | -------------------------------------------------------------------------------- /internal/dao/short_url.go: -------------------------------------------------------------------------------- 1 | // ================================================================================= 2 | // This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. 3 | // ================================================================================= 4 | 5 | package dao 6 | 7 | import ( 8 | "compressURL/internal/dao/internal" 9 | ) 10 | 11 | // internalShortUrlDao is internal type for wrapping internal DAO implements. 12 | type internalShortUrlDao = *internal.ShortUrlDao 13 | 14 | // shortUrlDao is the data access object for table short_url. 15 | // You can define custom methods on it to extend its functionality as you wish. 16 | type shortUrlDao struct { 17 | internalShortUrlDao 18 | } 19 | 20 | var ( 21 | // ShortUrl is globally public accessible object for table short_url operations. 22 | ShortUrl = shortUrlDao{ 23 | internal.NewShortUrlDao(), 24 | } 25 | ) 26 | 27 | // Fill with you ideas below. 28 | -------------------------------------------------------------------------------- /internal/dao/short_url_code.go: -------------------------------------------------------------------------------- 1 | // ================================================================================= 2 | // This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. 3 | // ================================================================================= 4 | 5 | package dao 6 | 7 | import ( 8 | "compressURL/internal/dao/internal" 9 | ) 10 | 11 | // internalShortUrlCodeDao is internal type for wrapping internal DAO implements. 12 | type internalShortUrlCodeDao = *internal.ShortUrlCodeDao 13 | 14 | // shortUrlCodeDao is the data access object for table short_url_code. 15 | // You can define custom methods on it to extend its functionality as you wish. 16 | type shortUrlCodeDao struct { 17 | internalShortUrlCodeDao 18 | } 19 | 20 | var ( 21 | // ShortUrlCode is globally public accessible object for table short_url_code operations. 22 | ShortUrlCode = shortUrlCodeDao{ 23 | internal.NewShortUrlCodeDao(), 24 | } 25 | ) 26 | 27 | // Fill with you ideas below. 28 | -------------------------------------------------------------------------------- /internal/dao/short_url_visits.go: -------------------------------------------------------------------------------- 1 | // ================================================================================= 2 | // This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. 3 | // ================================================================================= 4 | 5 | package dao 6 | 7 | import ( 8 | "compressURL/internal/dao/internal" 9 | ) 10 | 11 | // internalShortUrlVisitsDao is internal type for wrapping internal DAO implements. 12 | type internalShortUrlVisitsDao = *internal.ShortUrlVisitsDao 13 | 14 | // shortUrlVisitsDao is the data access object for table short_url_visits. 15 | // You can define custom methods on it to extend its functionality as you wish. 16 | type shortUrlVisitsDao struct { 17 | internalShortUrlVisitsDao 18 | } 19 | 20 | var ( 21 | // ShortUrlVisits is globally public accessible object for table short_url_visits operations. 22 | ShortUrlVisits = shortUrlVisitsDao{ 23 | internal.NewShortUrlVisitsDao(), 24 | } 25 | ) 26 | 27 | // Fill with you ideas below. 28 | -------------------------------------------------------------------------------- /internal/dao/user.go: -------------------------------------------------------------------------------- 1 | // ================================================================================= 2 | // This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. 3 | // ================================================================================= 4 | 5 | package dao 6 | 7 | import ( 8 | "compressURL/internal/dao/internal" 9 | ) 10 | 11 | // internalUserDao is internal type for wrapping internal DAO implements. 12 | type internalUserDao = *internal.UserDao 13 | 14 | // userDao is the data access object for table user. 15 | // You can define custom methods on it to extend its functionality as you wish. 16 | type userDao struct { 17 | internalUserDao 18 | } 19 | 20 | var ( 21 | // User is globally public accessible object for table user operations. 22 | User = userDao{ 23 | internal.NewUserDao(), 24 | } 25 | ) 26 | 27 | // Fill with you ideas below. 28 | -------------------------------------------------------------------------------- /internal/logic/analytics/analytics.go: -------------------------------------------------------------------------------- 1 | package analytics 2 | 3 | import "compressURL/internal/service" 4 | 5 | type sAnalytics struct { 6 | } 7 | 8 | func init() { 9 | service.RegisterAnalytics(New()) 10 | } 11 | 12 | func New() *sAnalytics { return &sAnalytics{} } 13 | -------------------------------------------------------------------------------- /internal/logic/analytics/clicks-devices.go: -------------------------------------------------------------------------------- 1 | package analytics 2 | 3 | import ( 4 | v1 "compressURL/api/analytics/v1" 5 | "compressURL/internal/dao" 6 | "github.com/gogf/gf/v2/frame/g" 7 | "golang.org/x/net/context" 8 | ) 9 | 10 | // GetVisitsByDevice 根据设备统计访问数据 11 | func (s *sAnalytics) GetVisitsByDevice(ctx context.Context, req v1.ClicksDevicesReq, userId string) (list v1.ClicksDevicesRes, err error) { 12 | db := dao.ShortUrlVisits.Ctx(ctx).OmitEmptyWhere(). 13 | Where(dao.ShortUrlVisits.Columns().ShortUrl, req.Code). 14 | Where(dao.ShortUrlVisits.Columns().UserId, userId) 15 | 16 | // 定义一个通用的处理函数 17 | queryAndLog := func(fieldName, groupBy string) error { 18 | return db.Fields(fieldName). 19 | Group(groupBy). 20 | Scan(&list) 21 | } 22 | 23 | switch req.Type { 24 | case "devices": 25 | err = queryAndLog("device_model AS name, COUNT(`short_url`) AS clicks", dao.ShortUrlVisits.Columns().DeviceModel) 26 | case "browsers": 27 | err = queryAndLog("browser_name AS name, COUNT(`short_url`) AS clicks", dao.ShortUrlVisits.Columns().BrowserName) 28 | case "os": 29 | err = queryAndLog("os_name AS name, COUNT(`short_url`) AS clicks", dao.ShortUrlVisits.Columns().OsName) 30 | default: 31 | g.Log().Warning(ctx, "Unsupported type: ", req.Type) 32 | return list, nil 33 | } 34 | 35 | if err != nil { 36 | g.Log().Error(ctx, "Error executing query: ", err) 37 | } 38 | 39 | return 40 | } 41 | -------------------------------------------------------------------------------- /internal/logic/analytics/clicks-region.go: -------------------------------------------------------------------------------- 1 | package analytics 2 | 3 | import ( 4 | v1 "compressURL/api/analytics/v1" 5 | "compressURL/internal/dao" 6 | "github.com/gogf/gf/v2/frame/g" 7 | "golang.org/x/net/context" 8 | ) 9 | 10 | // GetVisitsByRegion 根据区域统计访问数据 11 | func (s *sAnalytics) GetVisitsByRegion(ctx context.Context, req v1.ClicksRegionReq, userId string) (list v1.ClicksRegionRes, err error) { 12 | db := dao.ShortUrlVisits.Ctx(ctx).OmitEmptyWhere(). 13 | Where(dao.ShortUrlVisits.Columns().ShortUrl, req.Code). 14 | Where(dao.ShortUrlVisits.Columns().UserId, userId) 15 | 16 | // 定义一个通用的处理函数 17 | queryAndLog := func(fields, groupBy string) error { 18 | return db.Fields(fields). 19 | Group(groupBy). 20 | Scan(&list) 21 | } 22 | 23 | switch req.Type { 24 | case "countries": 25 | err = queryAndLog( 26 | "country_code, country_code AS code, country AS name, COUNT(short_url) AS clicks", 27 | "country_code, country", 28 | ) 29 | case "cities": 30 | err = queryAndLog( 31 | "country_code, region AS code, city AS name, COUNT(short_url) AS clicks", 32 | "country_code, region, city", 33 | ) 34 | default: 35 | g.Log().Warning(ctx, "Unsupported type: ", req.Type) 36 | return list, nil 37 | } 38 | 39 | if err != nil { 40 | g.Log().Error(ctx, "Error executing query: ", err) 41 | } 42 | 43 | return 44 | } 45 | -------------------------------------------------------------------------------- /internal/logic/analytics/clicks-time.go: -------------------------------------------------------------------------------- 1 | package analytics 2 | 3 | import ( 4 | v1 "compressURL/api/analytics/v1" 5 | "compressURL/internal/dao" 6 | "fmt" 7 | "github.com/gogf/gf/v2/frame/g" 8 | "github.com/gogf/gf/v2/os/gtime" 9 | "golang.org/x/net/context" 10 | ) 11 | 12 | // 按小时统计 13 | func statisticsByHour(ctx context.Context, shortUrl string, userId string) (list v1.ClicksTimeRes, err error) { 14 | now := gtime.Now() 15 | startDate := now.StartOfDay() 16 | endDate := now.EndOfDay() 17 | 18 | // 创建查询对象 19 | db := g.DB().Model("short_url_visits").OmitEmptyWhere(). 20 | Fields("HOUR(created_at) AS hour, COUNT(id) AS visit_count"). 21 | Where("created_at BETWEEN ? AND ?", startDate, endDate). 22 | Where(dao.ShortUrlVisits.Columns().UserId, userId). 23 | Group("HOUR(created_at)"). 24 | Order("HOUR(created_at)") 25 | 26 | // 如果 shortUrl 不为空,添加条件 27 | if shortUrl != "" { 28 | db = db.Where("short_url", shortUrl) 29 | } 30 | 31 | // 执行查询 32 | rows, err := db.All() 33 | if err != nil { 34 | g.Log().Error(ctx, "短链访问根据时间统计 查询数据失败", err) 35 | return 36 | } 37 | 38 | // 初始化每小时点击数为0 39 | hourlyClicks := make(map[int]int) 40 | for i := 0; i < 24; i++ { 41 | hourlyClicks[i] = 0 42 | } 43 | 44 | // 处理结果 45 | for _, row := range rows { 46 | hour := row["hour"].Int() 47 | visitCount := row["visit_count"].Int() 48 | hourlyClicks[hour] = visitCount 49 | } 50 | 51 | // 将结果转换为列表形式并按时间排序 52 | for hour := 0; hour < 24; hour++ { 53 | list = append(list, v1.ClicksTimeItem{ 54 | Clicks: hourlyClicks[hour], 55 | Time: fmt.Sprintf("%02d:00", hour), 56 | }) 57 | } 58 | 59 | return 60 | } 61 | 62 | // statisticsByDays 根据传入天数进行统计 63 | func statisticsByDays(ctx context.Context, shortUrl string, days int, userId string) (list v1.ClicksTimeRes, err error) { 64 | startDate := gtime.Now().AddDate(0, 0, -days).StartOfDay() 65 | endDate := gtime.Now().EndOfDay() 66 | 67 | // 创建查询对象 68 | db := g.DB().Model("short_url_visits").OmitEmptyWhere(). 69 | Fields("DATE(created_at) AS date, COUNT(id) AS visit_count"). 70 | Where("created_at BETWEEN ? AND ?", startDate, endDate). 71 | Where(dao.ShortUrlVisits.Columns().UserId, userId). 72 | Group("DATE(created_at)"). 73 | Order("DATE(created_at)") 74 | 75 | // 如果 shortUrl 不为空,添加条件 76 | if shortUrl != "" { 77 | db = db.Where("short_url", shortUrl) 78 | } 79 | 80 | // 执行查询 81 | rows, err := db.All() 82 | if err != nil { 83 | g.Log().Error(ctx, "根据日期统计失败", err) 84 | return 85 | } 86 | 87 | // 初始化每天点击数为 0 88 | dateClicks := make(map[string]int) 89 | for i := 0; i <= days; i++ { 90 | date := gtime.Now().AddDate(0, 0, -i).Format("Y-m-d") 91 | dateClicks[date] = 0 92 | } 93 | 94 | // 处理结果 95 | for _, row := range rows { 96 | date := row["date"].String() 97 | visitCount := row["visit_count"].Int() 98 | dateClicks[date] = visitCount 99 | } 100 | 101 | // 将结果转换为列表形式并按日期排序 102 | for i := days; i >= 0; i-- { 103 | date := gtime.Now().AddDate(0, 0, -i).Format("Y-m-d") 104 | list = append(list, v1.ClicksTimeItem{ 105 | Clicks: dateClicks[date], 106 | Time: date, 107 | }) 108 | } 109 | 110 | return 111 | } 112 | 113 | // GetVisitsByDate 根据时间统计访问数据 114 | func (s *sAnalytics) GetVisitsByDate(ctx context.Context, req v1.ClicksTimeReq, userId string) (list v1.ClicksTimeRes, err error) { 115 | if req.DateType == "24h" { 116 | return statisticsByHour(ctx, req.Code, userId) 117 | } 118 | 119 | if req.DateType == "7d" { 120 | return statisticsByDays(ctx, req.Code, 7, userId) 121 | } 122 | 123 | if req.DateType == "30d" { 124 | return statisticsByDays(ctx, req.Code, 30, userId) 125 | } 126 | 127 | return 128 | } 129 | -------------------------------------------------------------------------------- /internal/logic/auth/auth.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "compressURL/internal/model" 5 | "compressURL/internal/model/entity" 6 | "compressURL/internal/service" 7 | jwt "github.com/gogf/gf-jwt/v2" 8 | "github.com/gogf/gf/v2/frame/g" 9 | "github.com/gogf/gf/v2/os/glog" 10 | "golang.org/x/net/context" 11 | "time" 12 | ) 13 | 14 | type sAuth struct { 15 | } 16 | 17 | func init() { 18 | initAuth() 19 | service.RegisterAuth(New()) 20 | } 21 | 22 | func New() *sAuth { 23 | return &sAuth{} 24 | } 25 | 26 | var authInstance *jwt.GfJWTMiddleware 27 | 28 | func initAuth() { 29 | authInstance = jwt.New(&jwt.GfJWTMiddleware{ 30 | Realm: "short_url", 31 | Key: []byte("short_url_byd"), 32 | Timeout: time.Hour * 5, 33 | MaxRefresh: time.Hour * 5, 34 | IdentityKey: "LoginUserId", 35 | TokenLookup: "header: Authorization, query: token, cookie: jwt", 36 | TokenHeadName: "Bearer", 37 | TimeFunc: time.Now, 38 | Authenticator: Authenticator, 39 | Unauthorized: Unauthorized, 40 | PayloadFunc: PayloadFunc, 41 | IdentityHandler: IdentityHandler, 42 | }) 43 | } 44 | 45 | func (s sAuth) AuthInstance() *jwt.GfJWTMiddleware { 46 | return authInstance 47 | } 48 | 49 | // GetLoginUserInfo 获取当前登录用户的信息 50 | func (s sAuth) GetLoginUserInfo(ctx context.Context) (model.JWTPayloadInfo, error) { 51 | 52 | jwtInfo, token, err := authInstance.GetClaimsFromJWT(ctx) 53 | 54 | glog.Info(ctx, jwtInfo, token, err) 55 | 56 | // 为空直接返回 57 | if jwtInfo == nil { 58 | return model.JWTPayloadInfo{}, err 59 | } 60 | 61 | info := model.JWTPayloadInfo{ 62 | Id: jwtInfo["LoginUserId"].(string), 63 | AccountType: jwtInfo["LoginUserAccountType"].(string), 64 | Role: jwtInfo["LoginUserRole"].(string), 65 | Token: token, 66 | } 67 | 68 | return info, err 69 | } 70 | 71 | // PayloadFunc is a callback function that will be called during login. 72 | // Using this function it is possible to add additional payload data to the webtoken. 73 | // The data is then made available during requests via c.Get("JWT_PAYLOAD"). 74 | // Note that the payload is not encrypted. 75 | // The attributes mentioned on jwt.io can't be used as keys for the map. 76 | // Optional, by default no additional data will be set. 77 | func PayloadFunc(data interface{}) jwt.MapClaims { 78 | claims := jwt.MapClaims{} 79 | 80 | params := data.(map[string]interface{}) 81 | if len(params) > 0 { 82 | for k, v := range params { 83 | claims[k] = v 84 | } 85 | } 86 | return claims 87 | } 88 | 89 | // IdentityHandler get the identity from JWT and set the identity for every request 90 | // Using this function, by r.GetParam("id") get identity 91 | func IdentityHandler(ctx context.Context) interface{} { 92 | claims := jwt.ExtractClaims(ctx) 93 | return claims[authInstance.IdentityKey] 94 | } 95 | 96 | // Unauthorized is used to define customized Unauthorized callback function. 97 | func Unauthorized(ctx context.Context, code int, message string) { 98 | r := g.RequestFromCtx(ctx) 99 | r.Response.WriteJson(g.Map{ 100 | "code": code, 101 | "message": message, 102 | }) 103 | r.ExitAll() 104 | } 105 | 106 | // Authenticator is used to validate login parameters. 107 | // It must return user data as user identifier, it will be stored in Claim Array. 108 | // if your identityKey is 'id', your user data must have 'id' 109 | // Check error (e) to determine the appropriate error message. 110 | func Authenticator(ctx context.Context) (interface{}, error) { 111 | var loginInfo entity.User 112 | 113 | err := g.RequestFromCtx(ctx).GetCtxVar("loginInfo").Scan(&loginInfo) 114 | 115 | if err != nil { 116 | return nil, err 117 | } 118 | 119 | data := g.Map{ 120 | "LoginUserId": loginInfo.Id, 121 | "LoginUserAccountType": loginInfo.AccountType, 122 | "LoginUserRole": loginInfo.Role, 123 | } 124 | 125 | return data, nil 126 | } 127 | -------------------------------------------------------------------------------- /internal/logic/common/common.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import "compressURL/internal/service" 4 | 5 | type sCommon struct { 6 | } 7 | 8 | func init() { 9 | service.RegisterCommon(New()) 10 | } 11 | 12 | func New() *sCommon { 13 | return &sCommon{} 14 | } 15 | -------------------------------------------------------------------------------- /internal/logic/common/send-verification-code-email.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "crypto/tls" 5 | "fmt" 6 | "github.com/gogf/gf/v2/frame/g" 7 | "github.com/jordan-wright/email" 8 | "golang.org/x/net/context" 9 | "net/smtp" 10 | ) 11 | 12 | // SendVerificationCodeEmail 发送邮箱验证码 13 | func (s *sCommon) SendVerificationCodeEmail(ctx context.Context, VerificationCode int, emailAddress string) (err error) { 14 | mailUserName, _ := g.Cfg().Get(ctx, "emailConfig.email") // 邮箱账号 15 | mailPassword, _ := g.Cfg().Get(ctx, "emailConfig.key") // 邮箱授权码 16 | addr := "smtp.qq.com:465" // TLS地址 17 | host := "smtp.qq.com" // 邮件服务器地址 18 | Subject := "KKDL验证码" // 发送的主题 19 | 20 | e := email.NewEmail() 21 | e.From = fmt.Sprintf("KKDL <%s>", mailUserName) 22 | e.To = []string{emailAddress} 23 | e.Subject = Subject 24 | 25 | // 美化后的HTML内容 26 | htmlContent := fmt.Sprintf(` 27 | 28 | 29 | 30 | 31 | %s 32 | 59 | 60 | 61 |
62 |

验证码

63 |

您好,

64 |

您的验证码为:%d

65 |

请在10分钟内使用此验证码。

66 |

谢谢!

67 | 71 |
72 | 73 | 74 | `, Subject, VerificationCode) 75 | 76 | e.HTML = []byte(htmlContent) 77 | return e.SendWithTLS(addr, smtp.PlainAuth("", mailUserName.String(), mailPassword.String(), host), 78 | &tls.Config{InsecureSkipVerify: true, ServerName: "smtp.qq.com"}) 79 | } 80 | -------------------------------------------------------------------------------- /internal/logic/common/upload_file.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | v1 "compressURL/api/common/v1" 5 | "fmt" 6 | "github.com/gogf/gf/v2/frame/g" 7 | "github.com/gogf/gf/v2/net/ghttp" 8 | "github.com/gogf/gf/v2/os/glog" 9 | "github.com/gogf/gf/v2/os/gtime" 10 | "github.com/gogf/gf/v2/util/guid" 11 | "github.com/qiniu/go-sdk/v7/auth/qbox" 12 | "github.com/qiniu/go-sdk/v7/storage" 13 | "golang.org/x/net/context" 14 | ) 15 | 16 | func (s *sCommon) UploadFile(ctx context.Context, file *ghttp.UploadFile) (out v1.UploadFileRes, err error) { 17 | filesReader, _ := file.Open() 18 | 19 | qiNiuConfig := g.Cfg().MustGet(ctx, "qiNiuConfig").Map() 20 | 21 | mac := qbox.NewMac(qiNiuConfig["access"].(string), qiNiuConfig["secret"].(string)) 22 | putPolicy := storage.PutPolicy{ 23 | Scope: qiNiuConfig["bucket"].(string), 24 | } 25 | upToken := putPolicy.UploadToken(mac) 26 | 27 | cfg := storage.Config{ 28 | Region: &storage.ZoneHuadongZheJiang2, // 空间对应的机房 29 | UseHTTPS: true, // 是否使用https域名 30 | UseCdnDomains: false, // 上传是否使用CDN上传加速 31 | } 32 | 33 | // 构建表单上传的对象 34 | formUploader := storage.NewFormUploader(&cfg) 35 | ret := storage.PutRet{} 36 | 37 | // 可选配置 38 | putExtra := storage.PutExtra{ 39 | Params: map[string]string{ 40 | "x:name": "ddl file", 41 | }, 42 | } 43 | 44 | key := fmt.Sprintf("kkdl/%s/%s-%s", gtime.Now().Format("Y-m-d"), guid.S(), file.Filename) 45 | err = formUploader.Put(context.Background(), &ret, upToken, key, filesReader, file.Size, &putExtra) 46 | if err != nil { 47 | return v1.UploadFileRes{}, err 48 | } 49 | 50 | fileUrl := qiNiuConfig["baseUrl"].(string) + key 51 | glog.Info(ctx, "文件访问路径:", fileUrl) 52 | 53 | return v1.UploadFileRes{ 54 | Url: fileUrl, 55 | Name: file.Filename, 56 | }, err 57 | } 58 | -------------------------------------------------------------------------------- /internal/logic/logic.go: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. 3 | // ========================================================================== 4 | 5 | package logic 6 | 7 | import ( 8 | _ "compressURL/internal/logic/analytics" 9 | _ "compressURL/internal/logic/auth" 10 | _ "compressURL/internal/logic/common" 11 | _ "compressURL/internal/logic/short_url" 12 | _ "compressURL/internal/logic/short_url_code" 13 | _ "compressURL/internal/logic/short_url_visits" 14 | _ "compressURL/internal/logic/user" 15 | _ "compressURL/internal/logic/weChatMiniProgram" 16 | ) 17 | -------------------------------------------------------------------------------- /internal/logic/short_url/batch-import.go: -------------------------------------------------------------------------------- 1 | package short_url 2 | 3 | import ( 4 | "compressURL/internal/dao" 5 | "compressURL/internal/model/entity" 6 | "github.com/gogf/gf/v2/errors/gerror" 7 | "github.com/gogf/gf/v2/frame/g" 8 | "golang.org/x/net/context" 9 | ) 10 | 11 | // BatchImport 批量导入 12 | func (s *sShortUrl) BatchImport(ctx context.Context, in []entity.ShortUrl) ([]string, error) { 13 | // 获取一条未使用短链 code 14 | var shortCodeDataList []entity.ShortUrlCode 15 | err := dao.ShortUrlCode.Ctx(ctx).Where("status", 0).Limit(len(in)).Scan(&shortCodeDataList) 16 | if err != nil { 17 | return nil, gerror.Newf("批量获取短链 code 错误 %s", err) 18 | } 19 | 20 | var shortCodeList []string 21 | for i, _ := range in { 22 | in[i].ShortUrl = shortCodeDataList[i].Code 23 | shortCodeList = append(shortCodeList, shortCodeDataList[i].Code) 24 | } 25 | 26 | // 创建事务 27 | db := g.DB() 28 | if tx, err := db.Begin(ctx); err == nil { 29 | 30 | _, err := tx.Model("short_url").Data(in).Save() 31 | 32 | if err != nil { 33 | err := tx.Rollback() 34 | return nil, err 35 | } 36 | 37 | _, err = tx.Model("short_url_code").Data(g.Map{"status": 1}).WhereIn("code", shortCodeList).Update() 38 | if err != nil { 39 | err := tx.Rollback() 40 | return nil, err 41 | } 42 | 43 | // 提交事务 44 | err = tx.Commit() 45 | return nil, err 46 | } 47 | 48 | return nil, err 49 | } 50 | -------------------------------------------------------------------------------- /internal/logic/short_url/short_url.go: -------------------------------------------------------------------------------- 1 | package short_url 2 | 3 | import ( 4 | v1 "compressURL/api/short_url/v1" 5 | "compressURL/internal/dao" 6 | "compressURL/internal/model/entity" 7 | "compressURL/internal/service" 8 | "errors" 9 | "fmt" 10 | "github.com/gogf/gf/v2/errors/gerror" 11 | "github.com/gogf/gf/v2/frame/g" 12 | "golang.org/x/net/context" 13 | ) 14 | 15 | type sShortUrl struct { 16 | } 17 | 18 | func init() { 19 | service.RegisterShortUrl(New()) 20 | } 21 | 22 | func New() *sShortUrl { 23 | return &sShortUrl{} 24 | } 25 | 26 | // Create 创建短链 27 | func (s *sShortUrl) Create(ctx context.Context, in entity.ShortUrl) (string, error) { 28 | // 获取一条未使用短链 code 29 | shortCodeData := entity.ShortUrlCode{} 30 | err := dao.ShortUrlCode.Ctx(ctx).Where("status", 0).Limit(1).Scan(&shortCodeData) 31 | if err != nil { 32 | return "", err 33 | } 34 | 35 | // 创建事务 36 | db := g.DB() 37 | if tx, err := db.Begin(ctx); err == nil { 38 | _, err := tx.Model("short_url"). 39 | Data(g.Map{"shortUrl": shortCodeData.Code, "rawUrl": in.RawUrl, "expirationTime": in.ExpirationTime, "title": in.Title, "groupId": in.GroupId, "userId": in.UserId}). 40 | Save() 41 | 42 | if err != nil { 43 | return "", err 44 | } 45 | 46 | _, err = tx.Model("short_url_code").Data(g.Map{"status": 1}).Where("code", shortCodeData.Code).Update() 47 | if err != nil { 48 | // 回滚事务 49 | err := tx.Rollback() 50 | return "", err 51 | } 52 | 53 | // 提交事务 54 | err = tx.Commit() 55 | if err != nil { 56 | return "", err 57 | } 58 | } 59 | 60 | return shortCodeData.Code, err 61 | } 62 | 63 | // Delete 删除短链 64 | func (s *sShortUrl) Delete(ctx context.Context, id string, userId string) error { 65 | db := dao.ShortUrl.Ctx(ctx).Where(dao.ShortUrl.Columns().Id, id) 66 | 67 | // 用户 id 存在只查询当前用户的数据 68 | if userId != "" { 69 | db = db.Where(dao.ShortUrl.Columns().UserId, userId) 70 | } 71 | 72 | res, err := db.Delete() 73 | 74 | if num, _ := res.RowsAffected(); num == 0 { 75 | return gerror.New("需要删除的数据不存在!") 76 | } 77 | 78 | return err 79 | } 80 | 81 | // GetShortUrl 获取短链 82 | func (s *sShortUrl) GetShortUrl(ctx context.Context, url string) (string, error) { 83 | one, err := dao.ShortUrl.Ctx(ctx).Where("shortUrl", url).One() 84 | if err != nil { 85 | return "", err 86 | } 87 | 88 | if one == nil { 89 | return "", errors.New("未查询到对应的数据") 90 | } 91 | 92 | rawUrl := fmt.Sprintf("%s", one.GMap().Get("rawUrl")) 93 | return rawUrl, nil 94 | } 95 | 96 | // GenOne 获取短链 97 | func (s *sShortUrl) GenOne(ctx context.Context, code string) (entity.ShortUrl, error) { 98 | info := entity.ShortUrl{} 99 | 100 | err := dao.ShortUrl.Ctx(ctx).Where(dao.ShortUrl.Columns().ShortUrl, code).Scan(&info) 101 | 102 | if err != nil { 103 | return info, errors.New("未查询到短链数据!") 104 | } 105 | return info, nil 106 | } 107 | 108 | // GetList 短链列表 109 | func (s *sShortUrl) GetList(ctx context.Context, in v1.GetListReq, userId string) ([]entity.ShortUrl, int, error) { 110 | var list []entity.ShortUrl 111 | 112 | db := dao.ShortUrl.Ctx(ctx).OmitEmptyWhere(). 113 | Where(dao.ShortUrl.Columns().Title, in.Title). 114 | Where(dao.ShortUrl.Columns().RawUrl, in.RawUrl) 115 | 116 | // 用户 id 存在只查询当前用户的数据 117 | if userId != "" { 118 | db = db.Where(dao.ShortUrl.Columns().UserId, userId) 119 | } 120 | 121 | total, _ := db.Count() 122 | 123 | err := db.OrderDesc(dao.ShortUrl.Columns().CreatedAt).Page(in.PageNo, in.PageSize).Scan(&list) 124 | 125 | if err != nil { 126 | return nil, 0, err 127 | } 128 | return list, total, nil 129 | } 130 | -------------------------------------------------------------------------------- /internal/logic/short_url_code/short_url_code.go: -------------------------------------------------------------------------------- 1 | package short_url_code 2 | 3 | import ( 4 | "compressURL/internal/dao" 5 | "compressURL/internal/service" 6 | "compressURL/utility" 7 | "github.com/gogf/gf/v2/frame/g" 8 | "golang.org/x/net/context" 9 | ) 10 | 11 | type sShortUrlCode struct { 12 | } 13 | 14 | func init() { 15 | service.RegisterShortUrlCode(New()) 16 | } 17 | 18 | func New() *sShortUrlCode { 19 | return &sShortUrlCode{} 20 | } 21 | 22 | // BatchCreateCode 批量创建短链 23 | func (s *sShortUrlCode) BatchCreateCode(ctx context.Context, num int) error { 24 | g.Log().Info(ctx, "开始生成短链") 25 | // 记录插入数量 26 | insertDataNum := 0 27 | 28 | // 写一个循环成功插入指定数量的数据后退出 29 | for { 30 | shortUrl := utility.IntToBase62(utility.GenerateRandomNumber(9999999999, 1)) 31 | 32 | _, err := dao.ShortUrlCode.Ctx(ctx).Insert(g.Map{"Code": shortUrl, "Status": 0}) 33 | if err == nil { 34 | insertDataNum++ 35 | 36 | if insertDataNum >= num { 37 | g.Log().Info(ctx, "生成短链完成", shortUrl) 38 | break 39 | } 40 | } else { 41 | g.Log().Info(ctx, "生成短链重复", shortUrl) 42 | } 43 | } 44 | 45 | return nil 46 | } 47 | 48 | // UnusedCodeCount 获取未使用的 code 数量 49 | func (s *sShortUrlCode) UnusedCodeCount(ctx context.Context) (int, error) { 50 | return dao.ShortUrlCode.Ctx(ctx).Where(dao.ShortUrlCode.Columns().Status, 0).Count() 51 | } 52 | -------------------------------------------------------------------------------- /internal/logic/short_url_visits/short_url_visits.go: -------------------------------------------------------------------------------- 1 | package short_url_visits 2 | 3 | import ( 4 | v1 "compressURL/api/short_url_visits/v1" 5 | "compressURL/internal/dao" 6 | "compressURL/internal/model/entity" 7 | "compressURL/internal/service" 8 | "golang.org/x/net/context" 9 | ) 10 | 11 | type sShortUrlVisits struct { 12 | } 13 | 14 | func init() { 15 | service.RegisterShortUrlVisits(New()) 16 | } 17 | 18 | func New() *sShortUrlVisits { 19 | return &sShortUrlVisits{} 20 | } 21 | 22 | func (s *sShortUrlVisits) Create(ctx context.Context, in entity.ShortUrlVisits) error { 23 | _, err := dao.ShortUrlVisits.Ctx(ctx).Data(in).Save() 24 | return err 25 | } 26 | 27 | // GetList 短链列表 28 | func (s *sShortUrlVisits) GetList(ctx context.Context, in v1.GetListReq, userId string) ([]entity.ShortUrlVisits, int, error) { 29 | var list []entity.ShortUrlVisits 30 | 31 | db := dao.ShortUrlVisits.Ctx(ctx).OmitEmptyWhere(). 32 | Where(dao.ShortUrlVisits.Columns().ShortUrl, in.Code) 33 | 34 | // 用户 id 存在只查询当前用户的数据 35 | if userId != "" { 36 | db = db.Where(dao.ShortUrlVisits.Columns().UserId, userId) 37 | } 38 | 39 | total, _ := db.Count() 40 | 41 | err := db.OrderDesc(dao.ShortUrlVisits.Columns().CreatedAt).Page(in.PageNo, in.PageSize).Scan(&list) 42 | 43 | if err != nil { 44 | return nil, 0, err 45 | } 46 | return list, total, nil 47 | } 48 | -------------------------------------------------------------------------------- /internal/logic/user/user.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | import ( 4 | v1 "compressURL/api/user/v1" 5 | "compressURL/internal/dao" 6 | "compressURL/internal/model" 7 | "compressURL/internal/model/entity" 8 | "compressURL/internal/service" 9 | "compressURL/utility" 10 | "errors" 11 | "fmt" 12 | "github.com/gogf/gf/v2/errors/gerror" 13 | "github.com/gogf/gf/v2/frame/g" 14 | "github.com/gogf/gf/v2/util/grand" 15 | "github.com/gogf/gf/v2/util/guid" 16 | "golang.org/x/net/context" 17 | ) 18 | 19 | type sUser struct { 20 | } 21 | 22 | func init() { 23 | service.RegisterUser(New()) 24 | } 25 | 26 | func New() *sUser { 27 | return &sUser{} 28 | } 29 | 30 | // Create todo 创建完用户后应该返回用户信息 31 | func (s *sUser) Create(ctx context.Context, in entity.User) (string, error) { 32 | // 昵称不存在生成默认昵称 33 | if in.NickName == "" { 34 | in.NickName = "kk" + grand.S(4) 35 | } 36 | 37 | // 头像不存在生成随机头像 38 | if in.Avatar == "" { 39 | in.Avatar = fmt.Sprintf("https://api.dicebear.com/7.x/bottts-neutral/svg?seed=%s&size=64", in.NickName) 40 | } 41 | 42 | data := g.Map{ 43 | "Id": in.Id, 44 | "Email": nil, 45 | "Password": nil, 46 | "NickName": in.NickName, 47 | "AccountType": in.AccountType, 48 | "Role": in.Role, 49 | "Salt": nil, 50 | "Avatar": in.Avatar, 51 | } 52 | 53 | if data["AccountType"] == "01" { 54 | 55 | data["Email"] = in.Email 56 | data["Id"] = guid.S() 57 | 58 | // 密码存在才进行加密 59 | if in.Password != "" { 60 | // 生成随机盐值 61 | salt := grand.S(10) 62 | data["Salt"] = salt 63 | data["Password"] = utility.EncryptPassword(in.Password, salt) 64 | } 65 | } 66 | 67 | _, err := dao.User.Ctx(ctx).Data(data).InsertAndGetId() 68 | if err != nil { 69 | return "", err 70 | } 71 | 72 | return data["Id"].(string), nil 73 | } 74 | 75 | // Update 更新用户信息 76 | func (s *sUser) Update(ctx context.Context, in entity.User) error { 77 | // 获取用户信息 78 | userInfo, err := service.User().Detail(ctx, model.UserQueryInput{Id: in.Id}) 79 | 80 | if err != nil { 81 | return err 82 | } 83 | 84 | if in.Password != "" { 85 | // 对修改后的密码加密 86 | in.Password = utility.EncryptPassword(in.Password, userInfo.Salt) 87 | } 88 | 89 | // 账号未更改不做更新 90 | if in.Email == userInfo.Email { 91 | in.Email = "" 92 | } 93 | 94 | // 查询该邮箱是否已经被绑定 95 | count, err := dao.User.Ctx(ctx).Where(dao.User.Columns().Email, in.Email).Count() 96 | if err != nil { 97 | return err 98 | } 99 | 100 | // 存在一个用户且邮箱不为空 101 | if count == 1 && in.Email != "" { 102 | return gerror.New("邮箱已存在!") 103 | } 104 | 105 | // 更新数据 106 | _, err = dao.User.Ctx(ctx).Data(in). 107 | OmitEmpty(). 108 | FieldsEx(dao.User.Columns().Id). 109 | Where(dao.User.Columns().Id, in.Id). 110 | Update() 111 | return err 112 | } 113 | 114 | // Delete 删除用户 115 | func (s *sUser) Delete(ctx context.Context, id string) error { 116 | res, err := dao.User.Ctx(ctx).Where(dao.User.Columns().Id, id).Delete() 117 | 118 | if num, _ := res.RowsAffected(); num == 0 { 119 | return gerror.New("需要删除的数据不存在!") 120 | } 121 | 122 | return err 123 | } 124 | 125 | // Detail 获取用户详情 126 | func (s *sUser) Detail(ctx context.Context, in model.UserQueryInput) (*entity.User, error) { 127 | userInfo := entity.User{} 128 | 129 | db := dao.User.Ctx(ctx). 130 | OmitEmptyWhere(). 131 | Where(dao.User.Columns().Id, in.Id). 132 | Where(dao.User.Columns().Email, in.Email) 133 | 134 | total, err := db.Where(db).Count() 135 | 136 | if total == 0 { 137 | g.Log().Debug(ctx, "user GetOne 未查询到用户,邮箱:", in.Email, "id:", in.Id) 138 | return nil, nil 139 | } 140 | 141 | err = db.Scan(&userInfo) 142 | 143 | if err != nil { 144 | g.Log().Error(ctx, "User Detail error:", err) 145 | return nil, err 146 | } 147 | 148 | return &userInfo, nil 149 | } 150 | 151 | // GetOne 根据 id 获取用户信息,隐藏关键信息 152 | func (s *sUser) GetOne(ctx context.Context, in model.UserQueryInput) (*v1.GetOneRes, error) { 153 | userInfo := v1.GetOneRes{} 154 | 155 | db := dao.User.Ctx(ctx). 156 | OmitEmptyWhere(). 157 | Where(dao.User.Columns().Id, in.Id). 158 | Where(dao.User.Columns().Email, in.Email) 159 | 160 | total, err := db.Where(db).Count() 161 | 162 | if total == 0 { 163 | g.Log().Debug(ctx, "user GetOne 未查询到用户,邮箱:", in.Email, "id:", in.Id) 164 | return nil, nil 165 | } 166 | 167 | err = db.Scan(&userInfo) 168 | 169 | if err != nil { 170 | g.Log().Error(ctx, "User GetOne error:", err) 171 | return nil, err 172 | } 173 | 174 | return &userInfo, nil 175 | } 176 | 177 | // GetUserInfoByWxId 根据 wxIdOpenId 获取用户信息 todo WxId 后期会合并到 id 中到时删除 178 | func (s *sUser) GetUserInfoByWxId(ctx context.Context, wxId string) (*v1.GetOneRes, error) { 179 | userInfo := v1.GetOneRes{} 180 | 181 | err := dao.User.Ctx(ctx).Where(dao.User.Columns().WxId, wxId).Scan(&userInfo) 182 | 183 | if err != nil { 184 | return nil, errors.New("未查询到用户数据!") 185 | } 186 | return &userInfo, nil 187 | } 188 | 189 | // GetUserList 获取用户列表 190 | func (s *sUser) GetUserList(ctx context.Context, in v1.GetListReq) ([]entity.User, int, error) { 191 | var userList []entity.User 192 | 193 | db := dao.User.Ctx(ctx).OmitEmptyWhere(). 194 | Where(dao.User.Columns().WxId, in.WxId). 195 | Where(dao.User.Columns().Email, in.Email). 196 | Where(dao.User.Columns().NickName, in.NickName) 197 | 198 | total, _ := db.Count() 199 | 200 | err := db.Page(in.PageNo, in.PageSize).Scan(&userList) 201 | 202 | if err != nil { 203 | return nil, 0, err 204 | } 205 | return userList, total, nil 206 | } 207 | -------------------------------------------------------------------------------- /internal/logic/weChatMiniProgram/get-openId.go: -------------------------------------------------------------------------------- 1 | package weChatMiniProgram 2 | 3 | import ( 4 | v1 "compressURL/api/weChatMiniProgram/v1" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/gogf/gf/v2/errors/gerror" 8 | "github.com/gogf/gf/v2/frame/g" 9 | "github.com/gogf/gf/v2/os/glog" 10 | "golang.org/x/net/context" 11 | "io" 12 | "net/http" 13 | ) 14 | 15 | // GetOpenId 获取微信小程序用户openId 16 | func (s *sWeChatMiniProgram) GetOpenId(ctx context.Context, Code string) (resInfo *v1.GetOpenIdRes, err error) { 17 | // 获取微信小程序配置 18 | miniProgramsConf := g.Cfg().MustGet(ctx, "weChat.miniPrograms").Map() 19 | url := fmt.Sprintf("https://api.weixin.qq.com/sns/jscode2session?grant_type=client_credential&appid=%s&secret=%s&js_code=%s&grant_type=authorization_code", miniProgramsConf["appid"], miniProgramsConf["secret"], Code) 20 | res, err := http.Get(url) 21 | if err != nil { 22 | return nil, err 23 | } 24 | 25 | defer func(Body io.ReadCloser) { 26 | err := Body.Close() 27 | if err != nil { 28 | glog.Error(ctx, "调用获取微信 openid 接口失败", err) 29 | } 30 | }(res.Body) 31 | 32 | body, err := io.ReadAll(res.Body) 33 | if err != nil { 34 | return nil, err 35 | } 36 | 37 | // 解析JSON数据 38 | err = json.Unmarshal(body, &resInfo) 39 | 40 | if resInfo.ErrCode != 0 { 41 | return nil, gerror.Newf("获取用户openId失败:%s", resInfo.ErrMsg) 42 | } 43 | return resInfo, err 44 | } 45 | -------------------------------------------------------------------------------- /internal/logic/weChatMiniProgram/get-wx-token.go: -------------------------------------------------------------------------------- 1 | package weChatMiniProgram 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/gogf/gf/v2/frame/g" 7 | "github.com/gogf/gf/v2/os/glog" 8 | "golang.org/x/net/context" 9 | "io" 10 | "net/http" 11 | ) 12 | 13 | // WeChatTokenMap 解析接口响应 14 | type WeChatTokenMap struct { 15 | AccessToken string `json:"access_token"` 16 | ExpiresIn int64 `json:"expires_in"` 17 | } 18 | 19 | // 设置微信 token 缓存 20 | func setWeChatToken(ctx context.Context, req WeChatTokenMap) { 21 | err := g.Redis().SetEX(ctx, "weChatToken", req.AccessToken, req.ExpiresIn) 22 | if err != nil { 23 | glog.Error(ctx, "redis 缓存 weChatToken 失败", err) 24 | } 25 | } 26 | 27 | // 获取缓存的 weChatToken 28 | func getWeChatToken(ctx context.Context) string { 29 | weChatToken, err := g.Redis().Get(ctx, "weChatToken") 30 | if err != nil { 31 | glog.Error(ctx, "获取 redis 缓存 weChatToken 失败", err) 32 | return "" 33 | } 34 | 35 | return weChatToken.String() 36 | } 37 | 38 | // GetWeChatToken 获取微信 api token 39 | func (s *sWeChatMiniProgram) GetWeChatToken(ctx context.Context) (string, error) { 40 | // 缓存的 weChatToken 存在直接返回 41 | token := getWeChatToken(ctx) 42 | if token != "" { 43 | return token, nil 44 | } 45 | 46 | // 获取微信小程序配置 47 | miniProgramsConf := g.Cfg().MustGet(ctx, "weChat.miniPrograms").Map() 48 | 49 | url := fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s", miniProgramsConf["appid"], miniProgramsConf["secret"]) 50 | res, err := http.Get(url) 51 | if err != nil { 52 | return "", err 53 | } 54 | 55 | defer func(Body io.ReadCloser) { 56 | err := Body.Close() 57 | if err != nil { 58 | glog.Error(ctx, "调用获取微信 token 接口失败", err) 59 | } 60 | }(res.Body) 61 | 62 | body, err := io.ReadAll(res.Body) 63 | if err != nil { 64 | return "", err 65 | } 66 | 67 | // 解析JSON数据 68 | var resp WeChatTokenMap 69 | if err := json.Unmarshal(body, &resp); err != nil { 70 | return "", err 71 | } 72 | 73 | setWeChatToken(ctx, resp) 74 | 75 | return resp.AccessToken, nil 76 | } 77 | -------------------------------------------------------------------------------- /internal/logic/weChatMiniProgram/weChatMiniProgram.go: -------------------------------------------------------------------------------- 1 | package weChatMiniProgram 2 | 3 | import "compressURL/internal/service" 4 | 5 | type sWeChatMiniProgram struct { 6 | } 7 | 8 | func init() { 9 | service.RegisterWeChatMiniProgram(New()) 10 | } 11 | 12 | func New() *sWeChatMiniProgram { 13 | return &sWeChatMiniProgram{} 14 | } 15 | -------------------------------------------------------------------------------- /internal/middlewares/admin.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "compressURL/internal/service" 5 | "github.com/gogf/gf/v2/net/ghttp" 6 | ) 7 | 8 | // UserIsAdmin 是否是 admin 用户 9 | func UserIsAdmin(r *ghttp.Request) { 10 | // 获取当前登陆用户信息 11 | loginUserInfo, err := service.Auth().GetLoginUserInfo(r.GetCtx()) 12 | if err != nil { 13 | r.Response.WriteJsonExit(map[string]interface{}{ 14 | "code": 500, 15 | "message": "用户未登录不可操作!", 16 | }) 17 | return 18 | } 19 | 20 | // 非管理员不能创建用户 21 | if loginUserInfo.Role != "00" { 22 | r.Response.WriteJsonExit(map[string]interface{}{ 23 | "code": 500, 24 | "message": "非管理员不能操作!", 25 | }) 26 | 27 | return 28 | } 29 | 30 | r.Middleware.Next() 31 | } 32 | -------------------------------------------------------------------------------- /internal/middlewares/auth.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "compressURL/internal/service" 5 | "github.com/gogf/gf/v2/net/ghttp" 6 | ) 7 | 8 | // Auth jwt 鉴权 9 | func Auth(r *ghttp.Request) { 10 | service.Auth().AuthInstance().MiddlewareFunc()(r) 11 | r.Middleware.Next() 12 | } 13 | -------------------------------------------------------------------------------- /internal/model/common.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | // PageParams 分页参数 4 | type PageParams struct { 5 | Total int `json:"total" ` 6 | PageSize int `json:"pageSize" v:"required#请输入 PageSize" dc:"分页条数" d:"1"` 7 | PageNo int `json:"pageNo" v:"required#请输入用户 PageNo" dc:"当前页" d:"10"` 8 | } 9 | -------------------------------------------------------------------------------- /internal/model/do/short_url.go: -------------------------------------------------------------------------------- 1 | // ================================================================================= 2 | // Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. 3 | // ================================================================================= 4 | 5 | package do 6 | 7 | import ( 8 | "github.com/gogf/gf/v2/frame/g" 9 | "github.com/gogf/gf/v2/os/gtime" 10 | ) 11 | 12 | // ShortUrl is the golang structure of table short_url for DAO operations like Where/Data. 13 | type ShortUrl struct { 14 | g.Meta `orm:"table:short_url, do:true"` 15 | Id interface{} // 唯一标识,自增长整数 16 | ShortUrl interface{} // 短链,唯一,不能为空 17 | RawUrl interface{} // 原始 url 不能为空 18 | ExpirationTime *gtime.Time // 过期时间 19 | UserId interface{} // 用户id 20 | CreatedAt *gtime.Time // 创建时间,默认为当前时间戳 21 | Title interface{} // 短链标题 22 | GroupId interface{} // 短链分组id 23 | } 24 | -------------------------------------------------------------------------------- /internal/model/do/short_url_code.go: -------------------------------------------------------------------------------- 1 | // ================================================================================= 2 | // Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. 3 | // ================================================================================= 4 | 5 | package do 6 | 7 | import ( 8 | "github.com/gogf/gf/v2/frame/g" 9 | "github.com/gogf/gf/v2/os/gtime" 10 | ) 11 | 12 | // ShortUrlCode is the golang structure of table short_url_code for DAO operations like Where/Data. 13 | type ShortUrlCode struct { 14 | g.Meta `orm:"table:short_url_code, do:true"` 15 | Id interface{} // 唯一标识,自增长整数 16 | Code interface{} // 短链,唯一,不能为空 17 | Status interface{} // 是否使用 18 | CreatedAt *gtime.Time // 创建时间,默认为当前时间戳 19 | } 20 | -------------------------------------------------------------------------------- /internal/model/do/short_url_visits.go: -------------------------------------------------------------------------------- 1 | // ================================================================================= 2 | // Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. 3 | // ================================================================================= 4 | 5 | package do 6 | 7 | import ( 8 | "github.com/gogf/gf/v2/frame/g" 9 | "github.com/gogf/gf/v2/os/gtime" 10 | ) 11 | 12 | // ShortUrlVisits is the golang structure of table short_url_visits for DAO operations like Where/Data. 13 | type ShortUrlVisits struct { 14 | g.Meta `orm:"table:short_url_visits, do:true"` 15 | Id interface{} // 16 | UserId interface{} // 用户id 17 | ShortUrl interface{} // 短链,唯一,不能为空 18 | RawUrl interface{} // 原始 url 不能为空 19 | UserAgent interface{} // 用户代理字符串,存储提供的完整用户代理 20 | BrowserName interface{} // 浏览器名称 21 | BrowserVersion interface{} // 浏览器版本 22 | DeviceModel interface{} // 设备型号 23 | EngineName interface{} // 浏览器引擎名称 24 | EngineVersion interface{} // 浏览器引擎版本 25 | OsName interface{} // 操作系统名称 26 | OsVersion interface{} // 操作系统版本 27 | Ip interface{} // ip 不能为空 28 | Continent interface{} // 大洲名称 29 | ContinentCode interface{} // 大洲代码 30 | Country interface{} // 国家名称 31 | CountryCode interface{} // 国家代码 32 | Region interface{} // 地区或州的短代码(FIPS或ISO) 33 | RegionName interface{} // 地区或州名称 34 | City interface{} // 城市名称 35 | District interface{} // 位置的区(郡) 36 | Lat interface{} // 纬度 37 | Lon interface{} // 经度 38 | CreatedAt *gtime.Time // 创建时间,默认为当前时间戳 39 | UpdatedAt *gtime.Time // 更新时间 40 | DeletedAt *gtime.Time // 删除时间 41 | } 42 | -------------------------------------------------------------------------------- /internal/model/do/user.go: -------------------------------------------------------------------------------- 1 | // ================================================================================= 2 | // Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. 3 | // ================================================================================= 4 | 5 | package do 6 | 7 | import ( 8 | "github.com/gogf/gf/v2/frame/g" 9 | "github.com/gogf/gf/v2/os/gtime" 10 | ) 11 | 12 | // User is the golang structure of table user for DAO operations like Where/Data. 13 | type User struct { 14 | g.Meta `orm:"table:user, do:true"` 15 | Id interface{} // 唯一标识 16 | Email interface{} // 邮箱,唯一 17 | WxId interface{} // 小程序id,唯一 18 | Password interface{} // 密码, 小程序登录无密码 19 | NickName interface{} // 昵称, 创建默认生成 20 | AccountType interface{} // 账号类型: 01 邮箱 02 小程序 21 | Role interface{} // 角色: 00 admin 01 普通用户 02 vip 22 | DeletedAt *gtime.Time // 删除时间 23 | UpdatedAt *gtime.Time // 更新时间 24 | CreatedAt *gtime.Time // 创建时间 25 | Salt interface{} // 用户盐值 26 | Avatar interface{} // 用户头像 27 | } 28 | -------------------------------------------------------------------------------- /internal/model/entity/short_url.go: -------------------------------------------------------------------------------- 1 | // ================================================================================= 2 | // Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. 3 | // ================================================================================= 4 | 5 | package entity 6 | 7 | import ( 8 | "github.com/gogf/gf/v2/os/gtime" 9 | ) 10 | 11 | // ShortUrl is the golang structure for table short_url. 12 | type ShortUrl struct { 13 | Id int `json:"id" ` // 唯一标识,自增长整数 14 | ShortUrl string `json:"shortUrl" ` // 短链,唯一,不能为空 15 | RawUrl string `json:"rawUrl" ` // 原始 url 不能为空 16 | ExpirationTime *gtime.Time `json:"expirationTime" ` // 过期时间 17 | UserId string `json:"userId" ` // 用户id 18 | CreatedAt *gtime.Time `json:"createdAt" ` // 创建时间,默认为当前时间戳 19 | Title string `json:"title" ` // 短链标题 20 | GroupId int `json:"groupId" ` // 短链分组id 21 | } 22 | -------------------------------------------------------------------------------- /internal/model/entity/short_url_code.go: -------------------------------------------------------------------------------- 1 | // ================================================================================= 2 | // Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. 3 | // ================================================================================= 4 | 5 | package entity 6 | 7 | import ( 8 | "github.com/gogf/gf/v2/os/gtime" 9 | ) 10 | 11 | // ShortUrlCode is the golang structure for table short_url_code. 12 | type ShortUrlCode struct { 13 | Id int `json:"id" ` // 唯一标识,自增长整数 14 | Code string `json:"code" ` // 短链,唯一,不能为空 15 | Status int `json:"status" ` // 是否使用 16 | CreatedAt *gtime.Time `json:"createdAt" ` // 创建时间,默认为当前时间戳 17 | } 18 | -------------------------------------------------------------------------------- /internal/model/entity/short_url_visits.go: -------------------------------------------------------------------------------- 1 | // ================================================================================= 2 | // Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. 3 | // ================================================================================= 4 | 5 | package entity 6 | 7 | import ( 8 | "github.com/gogf/gf/v2/os/gtime" 9 | ) 10 | 11 | // ShortUrlVisits is the golang structure for table short_url_visits. 12 | type ShortUrlVisits struct { 13 | Id uint64 `json:"id" ` // 14 | UserId string `json:"userId" ` // 用户id 15 | ShortUrl string `json:"shortUrl" ` // 短链,唯一,不能为空 16 | RawUrl string `json:"rawUrl" ` // 原始 url 不能为空 17 | UserAgent string `json:"userAgent" ` // 用户代理字符串,存储提供的完整用户代理 18 | BrowserName string `json:"browserName" ` // 浏览器名称 19 | BrowserVersion string `json:"browserVersion" ` // 浏览器版本 20 | DeviceModel string `json:"deviceModel" ` // 设备型号 21 | EngineName string `json:"engineName" ` // 浏览器引擎名称 22 | EngineVersion string `json:"engineVersion" ` // 浏览器引擎版本 23 | OsName string `json:"osName" ` // 操作系统名称 24 | OsVersion string `json:"osVersion" ` // 操作系统版本 25 | Ip string `json:"ip" ` // ip 不能为空 26 | Continent string `json:"continent" ` // 大洲名称 27 | ContinentCode string `json:"continentCode" ` // 大洲代码 28 | Country string `json:"country" ` // 国家名称 29 | CountryCode string `json:"countryCode" ` // 国家代码 30 | Region string `json:"region" ` // 地区或州的短代码(FIPS或ISO) 31 | RegionName string `json:"regionName" ` // 地区或州名称 32 | City string `json:"city" ` // 城市名称 33 | District string `json:"district" ` // 位置的区(郡) 34 | Lat float64 `json:"lat" ` // 纬度 35 | Lon float64 `json:"lon" ` // 经度 36 | CreatedAt *gtime.Time `json:"createdAt" ` // 创建时间,默认为当前时间戳 37 | UpdatedAt *gtime.Time `json:"updatedAt" ` // 更新时间 38 | DeletedAt *gtime.Time `json:"deletedAt" ` // 删除时间 39 | } 40 | -------------------------------------------------------------------------------- /internal/model/entity/user.go: -------------------------------------------------------------------------------- 1 | // ================================================================================= 2 | // Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. 3 | // ================================================================================= 4 | 5 | package entity 6 | 7 | import ( 8 | "github.com/gogf/gf/v2/os/gtime" 9 | ) 10 | 11 | // User is the golang structure for table user. 12 | type User struct { 13 | Id string `json:"id" ` // 唯一标识 14 | Email string `json:"email" ` // 邮箱,唯一 15 | WxId string `json:"wxId" ` // 小程序id,唯一 16 | Password string `json:"password" ` // 密码, 小程序登录无密码 17 | NickName string `json:"nickName" ` // 昵称, 创建默认生成 18 | AccountType string `json:"accountType" ` // 账号类型: 01 邮箱 02 小程序 19 | Role string `json:"role" ` // 角色: 00 admin 01 普通用户 02 vip 20 | DeletedAt *gtime.Time `json:"deletedAt" ` // 删除时间 21 | UpdatedAt *gtime.Time `json:"updatedAt" ` // 更新时间 22 | CreatedAt *gtime.Time `json:"createdAt" ` // 创建时间 23 | Salt string `json:"salt" ` // 用户盐值 24 | Avatar string `json:"avatar" ` // 用户头像 25 | } 26 | -------------------------------------------------------------------------------- /internal/model/login.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "compressURL/internal/model/entity" 4 | 5 | type LoginInput struct { 6 | Email string // 邮箱,唯一 7 | WxId string // 小程序id,唯一 8 | Password string // 密码, 小程序登录无密码 9 | AccountType string // 账号类型: 01 邮箱 02 小程序 10 | } 11 | 12 | type LoginRes struct { 13 | Token string `json:"token" dc:"jwt token"` 14 | TokenExpire string `json:"tokenExpire" dc:"token 过期时间"` 15 | UserInfo entity.User `json:"userInfo" dc:"用户信息"` 16 | } 17 | 18 | type JWTPayloadInfo struct { 19 | Id string `json:"id"` 20 | AccountType string `json:"accountType"` 21 | Role string `json:"role"` 22 | Token string `json:"Token"` 23 | } 24 | -------------------------------------------------------------------------------- /internal/model/shortURL.go: -------------------------------------------------------------------------------- 1 | package model 2 | -------------------------------------------------------------------------------- /internal/model/user.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type UserCreateInput struct { 4 | Email string // 邮箱,唯一 5 | WxId string // 小程序id,唯一 6 | Password string // 密码, 小程序登录无密码 7 | NickName string // 昵称, 创建默认生成 8 | AccountType string // 账号类型: 01 邮箱 02 小程序 9 | Role string // 角色: 00 admin 01 普通用户 02 vip 10 | Salt string // 用户盐值 11 | Avatar string // 用户头像 12 | } 13 | 14 | type UserUpdateInput struct { 15 | Id string // 唯一标识,自增长整数 16 | UserCreateInput 17 | } 18 | 19 | type UserQueryInput struct { 20 | Email string // 邮箱,唯一 21 | Id string // id 唯一 22 | } 23 | -------------------------------------------------------------------------------- /internal/packed/packed.go: -------------------------------------------------------------------------------- 1 | package packed 2 | -------------------------------------------------------------------------------- /internal/service/analytics.go: -------------------------------------------------------------------------------- 1 | // ================================================================================ 2 | // Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. 3 | // You can delete these comments if you wish manually maintain this interface file. 4 | // ================================================================================ 5 | 6 | package service 7 | 8 | import ( 9 | v1 "compressURL/api/analytics/v1" 10 | 11 | "golang.org/x/net/context" 12 | ) 13 | 14 | type ( 15 | IAnalytics interface { 16 | // GetVisitsByDevice 根据设备统计访问数据 17 | GetVisitsByDevice(ctx context.Context, req v1.ClicksDevicesReq, userId string) (list v1.ClicksDevicesRes, err error) 18 | // GetVisitsByRegion 根据区域统计访问数据 19 | GetVisitsByRegion(ctx context.Context, req v1.ClicksRegionReq, userId string) (list v1.ClicksRegionRes, err error) 20 | // GetVisitsByDate 根据时间统计访问数据 21 | GetVisitsByDate(ctx context.Context, req v1.ClicksTimeReq, userId string) (list v1.ClicksTimeRes, err error) 22 | } 23 | ) 24 | 25 | var ( 26 | localAnalytics IAnalytics 27 | ) 28 | 29 | func Analytics() IAnalytics { 30 | if localAnalytics == nil { 31 | panic("implement not found for interface IAnalytics, forgot register?") 32 | } 33 | return localAnalytics 34 | } 35 | 36 | func RegisterAnalytics(i IAnalytics) { 37 | localAnalytics = i 38 | } 39 | -------------------------------------------------------------------------------- /internal/service/auth.go: -------------------------------------------------------------------------------- 1 | // ================================================================================ 2 | // Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. 3 | // You can delete these comments if you wish manually maintain this interface file. 4 | // ================================================================================ 5 | 6 | package service 7 | 8 | import ( 9 | "compressURL/internal/model" 10 | 11 | jwt "github.com/gogf/gf-jwt/v2" 12 | "golang.org/x/net/context" 13 | ) 14 | 15 | type ( 16 | IAuth interface { 17 | AuthInstance() *jwt.GfJWTMiddleware 18 | // GetLoginUserInfo 获取当前登录用户的信息 19 | GetLoginUserInfo(ctx context.Context) (model.JWTPayloadInfo, error) 20 | } 21 | ) 22 | 23 | var ( 24 | localAuth IAuth 25 | ) 26 | 27 | func Auth() IAuth { 28 | if localAuth == nil { 29 | panic("implement not found for interface IAuth, forgot register?") 30 | } 31 | return localAuth 32 | } 33 | 34 | func RegisterAuth(i IAuth) { 35 | localAuth = i 36 | } 37 | -------------------------------------------------------------------------------- /internal/service/common.go: -------------------------------------------------------------------------------- 1 | // ================================================================================ 2 | // Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. 3 | // You can delete these comments if you wish manually maintain this interface file. 4 | // ================================================================================ 5 | 6 | package service 7 | 8 | import ( 9 | v1 "compressURL/api/common/v1" 10 | 11 | "github.com/gogf/gf/v2/net/ghttp" 12 | "golang.org/x/net/context" 13 | ) 14 | 15 | type ( 16 | ICommon interface { 17 | // SendVerificationCodeEmail 发送邮箱验证码 18 | SendVerificationCodeEmail(ctx context.Context, VerificationCode int, emailAddress string) (err error) 19 | UploadFile(ctx context.Context, file *ghttp.UploadFile) (out v1.UploadFileRes, err error) 20 | } 21 | ) 22 | 23 | var ( 24 | localCommon ICommon 25 | ) 26 | 27 | func Common() ICommon { 28 | if localCommon == nil { 29 | panic("implement not found for interface ICommon, forgot register?") 30 | } 31 | return localCommon 32 | } 33 | 34 | func RegisterCommon(i ICommon) { 35 | localCommon = i 36 | } 37 | -------------------------------------------------------------------------------- /internal/service/login.go: -------------------------------------------------------------------------------- 1 | // ================================================================================ 2 | // Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. 3 | // You can delete these comments if you wish manually maintain this interface file. 4 | // ================================================================================ 5 | 6 | package service 7 | 8 | import ( 9 | "compressURL/internal/model" 10 | "compressURL/internal/model/entity" 11 | 12 | "golang.org/x/net/context" 13 | ) 14 | 15 | type ( 16 | ILogin interface { 17 | // GetUserInfo 获取用户信息 18 | GetUserInfo(ctx context.Context, in model.LoginInput) (entity.User, error) 19 | // UserLogin 用户登录 20 | UserLogin(ctx context.Context, in model.LoginInput) (userInfo entity.User, token string, tokenExpire string, err error) 21 | } 22 | ) 23 | 24 | var ( 25 | localLogin ILogin 26 | ) 27 | 28 | func Login() ILogin { 29 | if localLogin == nil { 30 | panic("implement not found for interface ILogin, forgot register?") 31 | } 32 | return localLogin 33 | } 34 | 35 | func RegisterLogin(i ILogin) { 36 | localLogin = i 37 | } 38 | -------------------------------------------------------------------------------- /internal/service/short_url.go: -------------------------------------------------------------------------------- 1 | // ================================================================================ 2 | // Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. 3 | // You can delete these comments if you wish manually maintain this interface file. 4 | // ================================================================================ 5 | 6 | package service 7 | 8 | import ( 9 | v1 "compressURL/api/short_url/v1" 10 | "compressURL/internal/model/entity" 11 | 12 | "golang.org/x/net/context" 13 | ) 14 | 15 | type ( 16 | IShortUrl interface { 17 | // BatchImport 批量导入 18 | BatchImport(ctx context.Context, in []entity.ShortUrl) ([]string, error) 19 | // Create 创建短链 20 | Create(ctx context.Context, in entity.ShortUrl) (string, error) 21 | // GetShortUrl 获取短链 22 | GetShortUrl(ctx context.Context, url string) (string, error) 23 | // GenOne 获取短链 24 | GenOne(ctx context.Context, code string) (entity.ShortUrl, error) 25 | // GetList 短链列表 26 | GetList(ctx context.Context, in v1.GetListReq, userId string) ([]entity.ShortUrl, int, error) 27 | // Delete 删除短链 28 | Delete(ctx context.Context, id string, userId string) error 29 | } 30 | ) 31 | 32 | var ( 33 | localShortUrl IShortUrl 34 | ) 35 | 36 | func ShortUrl() IShortUrl { 37 | if localShortUrl == nil { 38 | panic("implement not found for interface IShortUrl, forgot register?") 39 | } 40 | return localShortUrl 41 | } 42 | 43 | func RegisterShortUrl(i IShortUrl) { 44 | localShortUrl = i 45 | } 46 | -------------------------------------------------------------------------------- /internal/service/short_url_code.go: -------------------------------------------------------------------------------- 1 | // ================================================================================ 2 | // Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. 3 | // You can delete these comments if you wish manually maintain this interface file. 4 | // ================================================================================ 5 | 6 | package service 7 | 8 | import ( 9 | "golang.org/x/net/context" 10 | ) 11 | 12 | type ( 13 | IShortUrlCode interface { 14 | // BatchCreateCode 批量创建短链 15 | BatchCreateCode(ctx context.Context, num int) error 16 | // UnusedCodeCount 获取未使用的 code 数量 17 | UnusedCodeCount(ctx context.Context) (int, error) 18 | } 19 | ) 20 | 21 | var ( 22 | localShortUrlCode IShortUrlCode 23 | ) 24 | 25 | func ShortUrlCode() IShortUrlCode { 26 | if localShortUrlCode == nil { 27 | panic("implement not found for interface IShortUrlCode, forgot register?") 28 | } 29 | return localShortUrlCode 30 | } 31 | 32 | func RegisterShortUrlCode(i IShortUrlCode) { 33 | localShortUrlCode = i 34 | } 35 | -------------------------------------------------------------------------------- /internal/service/short_url_visits.go: -------------------------------------------------------------------------------- 1 | // ================================================================================ 2 | // Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. 3 | // You can delete these comments if you wish manually maintain this interface file. 4 | // ================================================================================ 5 | 6 | package service 7 | 8 | import ( 9 | v1 "compressURL/api/short_url_visits/v1" 10 | "compressURL/internal/model/entity" 11 | 12 | "golang.org/x/net/context" 13 | ) 14 | 15 | type ( 16 | IShortUrlVisits interface { 17 | Create(ctx context.Context, in entity.ShortUrlVisits) error 18 | // GetList 短链列表 19 | GetList(ctx context.Context, in v1.GetListReq, userId string) ([]entity.ShortUrlVisits, int, error) 20 | } 21 | ) 22 | 23 | var ( 24 | localShortUrlVisits IShortUrlVisits 25 | ) 26 | 27 | func ShortUrlVisits() IShortUrlVisits { 28 | if localShortUrlVisits == nil { 29 | panic("implement not found for interface IShortUrlVisits, forgot register?") 30 | } 31 | return localShortUrlVisits 32 | } 33 | 34 | func RegisterShortUrlVisits(i IShortUrlVisits) { 35 | localShortUrlVisits = i 36 | } 37 | -------------------------------------------------------------------------------- /internal/service/user.go: -------------------------------------------------------------------------------- 1 | // ================================================================================ 2 | // Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. 3 | // You can delete these comments if you wish manually maintain this interface file. 4 | // ================================================================================ 5 | 6 | package service 7 | 8 | import ( 9 | v1 "compressURL/api/user/v1" 10 | "compressURL/internal/model" 11 | "compressURL/internal/model/entity" 12 | 13 | "golang.org/x/net/context" 14 | ) 15 | 16 | type ( 17 | IUser interface { 18 | // Create todo 创建完用户后应该返回用户信息 19 | Create(ctx context.Context, in entity.User) (string, error) 20 | // Update 更新用户信息 21 | Update(ctx context.Context, in entity.User) error 22 | // Delete 删除用户 23 | Delete(ctx context.Context, id string) error 24 | // Detail 获取用户详情 25 | Detail(ctx context.Context, in model.UserQueryInput) (*entity.User, error) 26 | // GetOne 根据 id 获取用户信息,隐藏关键信息 27 | GetOne(ctx context.Context, in model.UserQueryInput) (*v1.GetOneRes, error) 28 | // GetUserInfoByWxId 根据 wxIdOpenId 获取用户信息 todo WxId 后期会合并到 id 中到时删除 29 | GetUserInfoByWxId(ctx context.Context, wxId string) (*v1.GetOneRes, error) 30 | // GetUserList 获取用户列表 31 | GetUserList(ctx context.Context, in v1.GetListReq) ([]entity.User, int, error) 32 | } 33 | ) 34 | 35 | var ( 36 | localUser IUser 37 | ) 38 | 39 | func User() IUser { 40 | if localUser == nil { 41 | panic("implement not found for interface IUser, forgot register?") 42 | } 43 | return localUser 44 | } 45 | 46 | func RegisterUser(i IUser) { 47 | localUser = i 48 | } 49 | -------------------------------------------------------------------------------- /internal/service/we_chat_mini_program.go: -------------------------------------------------------------------------------- 1 | // ================================================================================ 2 | // Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. 3 | // You can delete these comments if you wish manually maintain this interface file. 4 | // ================================================================================ 5 | 6 | package service 7 | 8 | import ( 9 | v1 "compressURL/api/weChatMiniProgram/v1" 10 | 11 | "golang.org/x/net/context" 12 | ) 13 | 14 | type ( 15 | IWeChatMiniProgram interface { 16 | // GetOpenId 获取微信小程序用户openId 17 | GetOpenId(ctx context.Context, Code string) (resInfo *v1.GetOpenIdRes, err error) 18 | // GetWeChatToken 获取微信 api token 19 | GetWeChatToken(ctx context.Context) (string, error) 20 | } 21 | ) 22 | 23 | var ( 24 | localWeChatMiniProgram IWeChatMiniProgram 25 | ) 26 | 27 | func WeChatMiniProgram() IWeChatMiniProgram { 28 | if localWeChatMiniProgram == nil { 29 | panic("implement not found for interface IWeChatMiniProgram, forgot register?") 30 | } 31 | return localWeChatMiniProgram 32 | } 33 | 34 | func RegisterWeChatMiniProgram(i IWeChatMiniProgram) { 35 | localWeChatMiniProgram = i 36 | } 37 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | _ "compressURL/internal/packed" 5 | _ "github.com/gogf/gf/contrib/drivers/mysql/v2" 6 | _ "github.com/gogf/gf/contrib/nosql/redis/v2" 7 | 8 | _ "compressURL/internal/logic" 9 | 10 | "github.com/gogf/gf/v2/os/gctx" 11 | 12 | "compressURL/internal/cmd" 13 | ) 14 | 15 | func main() { 16 | cmd.Main.Run(gctx.GetInitCtx()) 17 | } 18 | -------------------------------------------------------------------------------- /manifest/config/exampleConfig.yaml: -------------------------------------------------------------------------------- 1 | server: 2 | address: ":6001" 3 | openapiPath: "/api.json" 4 | swaggerPath: "/swagger" 5 | dumpRouterMap: false # 是否在Server启动时打印所有的路由列表。默认为true 6 | logPath: "log" # 日志文件存储目录路径,建议使用绝对路径。默认为空,表示关闭 7 | logStdout: true # 日志是否输出到终端。默认为true 8 | errorStack: true # 当Server捕获到异常时是否记录堆栈信息到日志中。默认为true 9 | errorLogEnabled: true # 是否记录异常日志信息到日志中。默认为true 10 | errorLogPattern: "error-{Ymd}.log" # 异常错误日志文件格式。默认为"error-{Ymd}.log" 11 | accessLogEnabled: false # 是否记录访问日志。默认为false 12 | accessLogPattern: "access-{Ymd}.log" # 访问日志文件格式。默认为"access-{Ymd}.log" 13 | 14 | # Database 15 | database: 16 | logger: 17 | level: "all" 18 | stdout: true 19 | default: 20 | type: "mysql" 21 | link: "mysql:root:123456@tcp(127.0.0.1:3306)/kkdl?loc=Local&parseTime=true" 22 | debug: true 23 | # Redis 24 | redis: 25 | default: 26 | address: 127.0.0.1:6379 27 | db: 1 28 | pass: 123456 29 | idleTimeout: 600 30 | 31 | # 微信 32 | weChat: 33 | miniPrograms: 34 | appid: wxbd4xxxx 35 | secret: a40a8bxxxxx 36 | 37 | # 七牛云 38 | qiNiuConfig: 39 | access: 'xxxx' 40 | secret: 'xxxxxx' 41 | bucket: 'xxxxxx' 42 | baseUrl: 'xxxxxx' 43 | 44 | # 邮箱配置 45 | emailConfig: 46 | key: 'xxxxx' 47 | email: 'xxxxxx' 48 | 49 | # github 50 | githubConfig: 51 | client_id: 'xxxxx' 52 | client_secret: 'xxxxx' 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /manifest/deploy/kustomize/base/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: kkdl 5 | labels: 6 | app: kkdl 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: kkdl 12 | template: 13 | metadata: 14 | labels: 15 | app: kkdl 16 | spec: 17 | containers: 18 | - name : main 19 | image: kkdl 20 | imagePullPolicy: Always 21 | 22 | -------------------------------------------------------------------------------- /manifest/deploy/kustomize/base/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - deployment.yaml 5 | - service.yaml 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /manifest/deploy/kustomize/base/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: kkdl 5 | spec: 6 | ports: 7 | - port: 80 8 | protocol: TCP 9 | targetPort: 8000 10 | selector: 11 | app: kkdl 12 | 13 | -------------------------------------------------------------------------------- /manifest/deploy/kustomize/overlays/develop/configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: kkdl-configmap 5 | data: 6 | config.yaml: | 7 | server: 8 | address: ":8000" 9 | openapiPath: "/api.json" 10 | swaggerPath: "/swagger" 11 | 12 | logger: 13 | level : "all" 14 | stdout: true 15 | -------------------------------------------------------------------------------- /manifest/deploy/kustomize/overlays/develop/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: kkdl 5 | spec: 6 | template: 7 | spec: 8 | containers: 9 | - name : main 10 | image: kkdl:develop -------------------------------------------------------------------------------- /manifest/deploy/kustomize/overlays/develop/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | 4 | resources: 5 | - ../../base 6 | - configmap.yaml 7 | 8 | patchesStrategicMerge: 9 | - deployment.yaml 10 | 11 | namespace: default 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /manifest/deploy/sql/short_url.sql: -------------------------------------------------------------------------------- 1 | create table short_url 2 | ( 3 | id int auto_increment comment '唯一标识,自增长整数' 4 | primary key, 5 | shortUrl varchar(20) not null comment '短链,唯一,不能为空', 6 | rawUrl varchar(500) not null comment '原始 url 不能为空', 7 | expirationTime timestamp null comment '过期时间', 8 | userId varchar(40) null comment '用户id', 9 | created_at timestamp default CURRENT_TIMESTAMP null comment '创建时间,默认为当前时间戳', 10 | title varchar(100) not null comment '短链标题', 11 | group_id int null comment '短链分组id', 12 | constraint shortUrl 13 | unique (shortUrl) 14 | ); -------------------------------------------------------------------------------- /manifest/deploy/sql/short_url_code.sql: -------------------------------------------------------------------------------- 1 | create table short_url_code 2 | ( 3 | id int auto_increment comment '唯一标识,自增长整数' 4 | primary key, 5 | code varchar(20) not null comment '短链,唯一,不能为空', 6 | status tinyint(1) not null comment '是否使用', 7 | created_at timestamp default CURRENT_TIMESTAMP null comment '创建时间,默认为当前时间戳', 8 | constraint code 9 | unique (code) 10 | ); 11 | 12 | -------------------------------------------------------------------------------- /manifest/deploy/sql/short_url_visits.sql: -------------------------------------------------------------------------------- 1 | -- auto-generated definition 2 | create table short_url_visits 3 | ( 4 | id bigint unsigned auto_increment 5 | primary key, 6 | user_id varchar(40) not null comment '用户id', 7 | short_url varchar(20) not null comment '短链,唯一,不能为空', 8 | raw_url varchar(500) not null comment '原始 url 不能为空', 9 | user_agent varchar(255) not null comment '用户代理字符串,存储提供的完整用户代理', 10 | browser_name varchar(50) null comment '浏览器名称', 11 | browser_version varchar(50) null comment '浏览器版本', 12 | device_model varchar(50) null comment '设备型号', 13 | engine_name varchar(50) null comment '浏览器引擎名称', 14 | engine_version varchar(50) null comment '浏览器引擎版本', 15 | os_name varchar(50) null comment '操作系统名称', 16 | os_version varchar(50) null comment '操作系统版本', 17 | ip varchar(45) not null comment 'ip 不能为空', 18 | continent varchar(50) null comment '大洲名称', 19 | continent_code varchar(10) null comment '大洲代码', 20 | country varchar(50) null comment '国家名称', 21 | country_code varchar(10) null comment '国家代码', 22 | region varchar(10) null comment '地区或州的短代码(FIPS或ISO)', 23 | region_name varchar(50) null comment '地区或州名称', 24 | city varchar(50) null comment '城市名称', 25 | district varchar(50) null comment '位置的区(郡)', 26 | lat double null comment '纬度', 27 | lon double null comment '经度', 28 | created_at timestamp default CURRENT_TIMESTAMP null comment '创建时间,默认为当前时间戳', 29 | updated_at timestamp null comment '更新时间', 30 | deleted_at timestamp null comment '删除时间', 31 | constraint id_unique 32 | unique (id) 33 | ); 34 | 35 | create index idx_short_url_visits_on_created_at 36 | on short_url_visits (created_at); 37 | 38 | create index idx_short_url_visits_on_short_url 39 | on short_url_visits (short_url); 40 | 41 | -------------------------------------------------------------------------------- /manifest/deploy/sql/user.sql: -------------------------------------------------------------------------------- 1 | create table user 2 | ( 3 | id varchar(32) not null comment '唯一标识' 4 | primary key, 5 | email varchar(50) null comment '邮箱,唯一', 6 | wxId varchar(50) null comment '小程序id,唯一', 7 | password varchar(200) null comment '密码, 小程序登录无密码', 8 | nickName varchar(20) not null comment '昵称, 创建默认生成', 9 | accountType varchar(4) default '01' not null comment '账号类型: 01 邮箱 02 小程序', 10 | role varchar(4) default '01' not null comment '角色: 00 admin 01 普通用户 02 vip', 11 | deleted_at timestamp null comment '删除时间', 12 | updated_at timestamp null comment '更新时间', 13 | created_at timestamp default CURRENT_TIMESTAMP null comment '创建时间', 14 | salt varchar(12) not null comment '用户盐值', 15 | avatar varchar(300) null comment '用户头像', 16 | constraint email 17 | unique (email), 18 | constraint wxId 19 | unique (wxId) 20 | ); -------------------------------------------------------------------------------- /manifest/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM loads/alpine:3.8 2 | 3 | ############################################################################### 4 | # INSTALLATION 5 | ############################################################################### 6 | 7 | ENV WORKDIR /app 8 | ADD resource $WORKDIR/ 9 | ADD ./temp/linux_amd64/main $WORKDIR/main 10 | RUN chmod +x $WORKDIR/main 11 | 12 | ############################################################################### 13 | # START 14 | ############################################################################### 15 | EXPOSE 6001 16 | WORKDIR $WORKDIR 17 | CMD ./main 18 | -------------------------------------------------------------------------------- /manifest/docker/docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This shell is executed before docker build. 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /resource/public/html/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaebe/kkdl-go/b8f925fab3bbf2dec2be2f41748e807e0d997e2e/resource/public/html/.gitkeep -------------------------------------------------------------------------------- /resource/public/plugin/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaebe/kkdl-go/b8f925fab3bbf2dec2be2f41748e807e0d997e2e/resource/public/plugin/.gitkeep -------------------------------------------------------------------------------- /resource/public/resource/css/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaebe/kkdl-go/b8f925fab3bbf2dec2be2f41748e807e0d997e2e/resource/public/resource/css/.gitkeep -------------------------------------------------------------------------------- /resource/public/resource/image/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaebe/kkdl-go/b8f925fab3bbf2dec2be2f41748e807e0d997e2e/resource/public/resource/image/.gitkeep -------------------------------------------------------------------------------- /resource/public/resource/js/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaebe/kkdl-go/b8f925fab3bbf2dec2be2f41748e807e0d997e2e/resource/public/resource/js/.gitkeep -------------------------------------------------------------------------------- /resource/template/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaebe/kkdl-go/b8f925fab3bbf2dec2be2f41748e807e0d997e2e/resource/template/.gitkeep -------------------------------------------------------------------------------- /resource/template/shortUrl/template.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaebe/kkdl-go/b8f925fab3bbf2dec2be2f41748e807e0d997e2e/resource/template/shortUrl/template.xlsx -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PORT=6001 4 | 5 | # 停止指定端口上的进程 6 | stop_process() { 7 | # 查找指定端口的进程 8 | PID=$(netstat -nlp | grep ":$PORT " | awk '{print $7}' | awk -F'[/]' '{print $1}') 9 | 10 | if [[ -z $PID ]]; then 11 | echo "端口 $PORT 上没有运行的进程" 12 | else 13 | echo "停止端口 $PORT 上的进程 $PID" 14 | kill $PID 15 | fi 16 | } 17 | 18 | # 更改文件权限 19 | change_permission() { 20 | chmod 777 ./main 21 | echo "修改文件权限" 22 | } 23 | 24 | # 启动进程 25 | start_process() { 26 | nohup ./main --gf.gcfg.file=config.pro.yaml >/dev/null 2>&1 & 27 | echo "部署完成" 28 | } 29 | 30 | # 停止进程 31 | stop_process 32 | 33 | # 更改文件权限 34 | change_permission 35 | 36 | # 启动进程 37 | start_process 38 | -------------------------------------------------------------------------------- /utility/IntToBase62.go: -------------------------------------------------------------------------------- 1 | package utility 2 | 3 | const ( 4 | base62 = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 5 | ) 6 | 7 | func IntToBase62(n int) string { 8 | if n == 0 { 9 | return string(base62[0]) 10 | } 11 | 12 | var result []byte 13 | for n > 0 { 14 | result = append(result, base62[n%62]) 15 | n /= 62 16 | } 17 | 18 | // 反转字符串 19 | for i, j := 0, len(result)-1; i < j; i, j = i+1, j-1 { 20 | result[i], result[j] = result[j], result[i] 21 | } 22 | 23 | return string(result) 24 | } 25 | -------------------------------------------------------------------------------- /utility/utils.go: -------------------------------------------------------------------------------- 1 | package utility 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gogf/gf/v2/crypto/gmd5" 6 | "math/rand" 7 | "strings" 8 | "time" 9 | ) 10 | 11 | // GenerateRandomNumber 生成指定范围随机数 12 | func GenerateRandomNumber(max int, min int) int { 13 | rand.NewSource(time.Now().UnixNano()) 14 | return rand.Intn(max-min+1) + min 15 | } 16 | 17 | // EncryptPassword 密码加密 18 | func EncryptPassword(password, salt string) string { 19 | return gmd5.MustEncryptString(gmd5.MustEncryptString(password) + gmd5.MustEncryptString(salt)) 20 | } 21 | 22 | // GetFileSuffixName 获取文件后缀名称 23 | func GetFileSuffixName(filename string) string { 24 | indexOfDot := strings.LastIndex(filename, ".") //获取文件后缀名前的.的位置 25 | if indexOfDot < 0 { 26 | return "" 27 | } 28 | suffix := filename[indexOfDot+1 : len(filename)] //获取后缀名 29 | return strings.ToLower(suffix) //后缀名统一小写处理 30 | } 31 | 32 | // SliceEqual 切片是否相等 33 | func SliceEqual(slice1, slice2 []string) bool { 34 | if len(slice1) != len(slice2) { 35 | return false 36 | } 37 | 38 | for i, v := range slice1 { 39 | if v != slice2[i] { 40 | return false 41 | } 42 | } 43 | 44 | return true 45 | } 46 | 47 | // GetWeChatMiniProgramLoginCode 获取微信小程序登录用户 code key 48 | func GetWeChatMiniProgramLoginCode(code string) string { 49 | return fmt.Sprintf("weChatMiniProgramLoginCode%s", code) 50 | } 51 | --------------------------------------------------------------------------------