浏览代码

登录种cookie,列表筛选

lichangdong 3 周之前
父节点
当前提交
d909ea0f2c

+ 2 - 1
desc/all.api

@@ -45,4 +45,5 @@ import "./wechat/whatsapp.api"
 import "./wechat/whatsapp_channel.api"
 import "./wechat/fastgpt.api"
 import "./wechat/department.api"
-import "./wechat/api_key.api"
+import "./wechat/api_key.api"
+import "./wechat/auth_login.api"

+ 83 - 0
desc/wechat/auth_login.api

@@ -0,0 +1,83 @@
+import "../base.api"
+
+type (
+    // The log in information | 登陆返回的数据信息
+    LoginInfo {
+        // User's UUID | 用户的UUID
+        UserId       string          `json:"userId"`
+
+        // Token for authorization | 验证身份的token
+        Token        string          `json:"token"`
+
+        // Expire timestamp | 过期时间戳
+        Expire       uint64          `json:"expire"`
+    }
+
+
+    // The permission code for front end permission control | 权限码: 用于前端权限控制
+    PermCodeResp {
+        BaseDataInfo
+
+        // Permission code data | 权限码数据
+        Data []string `json:"data"`
+    }
+
+
+    // Login request | 登录参数
+    LoginReq {
+        // User Name | 用户名
+        Username   string `json:"username" validate:"required,alphanum,max=20"`
+
+        // Password | 密码
+        Password   string `json:"password" validate:"required,max=30,min=6"`
+
+        // Captcha ID which store in redis | 验证码编号, 存在redis中
+        CaptchaId  string `json:"captchaId"  validate:"required,len=20"`
+
+        // The Captcha which users input | 用户输入的验证码
+        Captcha    string `json:"captcha" validate:"required,len=5"`
+    }
+
+    // Log in by email request | 邮箱登录参数
+    LoginByEmailReq {
+        // The user's email address | 用户的邮箱
+        Email     string `json:"email" validate:"required,email,max=100"`
+
+        // The Captcha which users input | 用户输入的验证码
+        Captcha    string `json:"captcha,optional" validate:"omitempty,len=5"`
+    }
+
+    // Log in by SMS request | 短信登录参数
+    LoginBySmsReq {
+        // The user's mobile phone number | 用户的手机号码
+        PhoneNumber   string  `json:"phoneNumber"  validate:"required,numeric,max=20"`
+
+        // The Captcha which users input | 用户输入的验证码
+        Captcha    string `json:"captcha,optional" validate:"omitempty,len=5"`
+    }
+
+    // The log in response data | 登录返回数据
+    LoginResp {
+        BaseDataInfo
+
+        // The log in information | 登陆返回的数据信息
+        Data LoginInfo `json:"data"`
+    }
+)
+
+@server(
+    group: auth
+)
+
+service Wechat {
+    // Log in | 登录
+    @handler login
+    post /user/login (LoginReq) returns (LoginResp)
+    // Log in by email | 邮箱登录
+    @handler loginByEmail
+    post /user/login_by_email (LoginByEmailReq) returns (LoginResp)
+    // Log in by SMS | 短信登录
+    @handler loginBySms
+    post /user/login_by_sms (LoginBySmsReq) returns (LoginResp)
+
+}

+ 4 - 0
desc/wechat/contact.api

@@ -64,6 +64,10 @@ type (
 
 		// 内容类型:1-个微 3-企微
 		Ctype *uint64 `json:"ctype,optional"`
+
+		//标签搜索开始结束日期
+		StartDate *string `json:"start_date,optional"`
+        EndDate *string `json:"end_date,optional"`
     }
 
     // Contact information response | Contact信息返回体

+ 7 - 0
go.mod

@@ -20,6 +20,7 @@ require (
 	github.com/golang-jwt/jwt/v5 v5.2.1
 	github.com/gorilla/websocket v1.5.0
 	github.com/imroc/req/v3 v3.43.1
+	github.com/mojocn/base64Captcha v1.3.6
 	github.com/openai/openai-go v0.1.0-alpha.62
 	github.com/redis/go-redis/v9 v9.6.1
 	github.com/robfig/cron/v3 v3.0.1
@@ -42,6 +43,8 @@ require (
 	gorm.io/plugin/dbresolver v1.5.0
 )
 
+require github.com/stretchr/testify v1.9.0
+
 require (
 	ariga.io/atlas v0.19.2 // indirect
 	filippo.io/edwards25519 v1.1.0 // indirect
@@ -83,6 +86,7 @@ require (
 	github.com/go-sql-driver/mysql v1.8.0 // indirect
 	github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
 	github.com/gogo/protobuf v1.3.2 // indirect
+	github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
 	github.com/golang/mock v1.6.0 // indirect
 	github.com/golang/protobuf v1.5.4 // indirect
 	github.com/golang/snappy v0.0.4 // indirect
@@ -126,6 +130,7 @@ require (
 	github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
 	github.com/pelletier/go-toml/v2 v2.1.1 // indirect
 	github.com/pkg/errors v0.9.1 // indirect
+	github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
 	github.com/prometheus/client_golang v1.19.0 // indirect
 	github.com/prometheus/client_model v0.5.0 // indirect
 	github.com/prometheus/common v0.48.0 // indirect
@@ -134,6 +139,7 @@ require (
 	github.com/quic-go/quic-go v0.42.0 // indirect
 	github.com/refraction-networking/utls v1.6.3 // indirect
 	github.com/spaolacci/murmur3 v1.1.0 // indirect
+	github.com/stretchr/objx v0.5.2 // indirect
 	github.com/tidwall/gjson v1.17.1 // indirect
 	github.com/tidwall/match v1.1.1 // indirect
 	github.com/tidwall/pretty v1.2.1 // indirect
@@ -162,6 +168,7 @@ require (
 	go.uber.org/multierr v1.11.0 // indirect
 	go.uber.org/zap v1.27.0 // indirect
 	golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 // indirect
+	golang.org/x/image v0.15.0 // indirect
 	golang.org/x/mod v0.17.0 // indirect
 	golang.org/x/net v0.29.0 // indirect
 	golang.org/x/oauth2 v0.18.0 // indirect

+ 9 - 0
go.sum

@@ -252,6 +252,8 @@ github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0kt
 github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
 github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
 github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
+github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
+github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
 github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -499,6 +501,8 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
 github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/mojocn/base64Captcha v1.3.6 h1:gZEKu1nsKpttuIAQgWHO+4Mhhls8cAKyiV2Ew03H+Tw=
+github.com/mojocn/base64Captcha v1.3.6/go.mod h1:i5CtHvm+oMbj1UzEPXaA8IH/xHFZ3DGY3Wh3dBpZ28E=
 github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0=
 github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
@@ -621,6 +625,8 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
 github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
+github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@@ -766,6 +772,9 @@ golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88p
 golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/image v0.13.0/go.mod h1:6mmbMOeV28HuMTgA6OSRkdXKYw/t5W9Uwn2Yv1r3Yxk=
+golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
+golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
 golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=

+ 10 - 1
internal/config/config.go

@@ -3,6 +3,7 @@ package config
 import (
 	"github.com/suyuan32/simple-admin-common/config"
 	"github.com/suyuan32/simple-admin-common/plugins/casbin"
+	"github.com/suyuan32/simple-admin-common/utils/captcha"
 	"github.com/zeromicro/go-zero/rest"
 	"github.com/zeromicro/go-zero/zrpc"
 	"wechat-api/internal/types"
@@ -21,7 +22,15 @@ type Config struct {
 	Aliyun             types.Aliyun
 	CoreRpc            zrpc.RpcClientConf
 	Xiaoice            types.Xiaoice
-	FastgptMongoConf   types.MongoDB
 	WebSocket          []types.WebSocketConfig
 	OpenAI             types.OpenAI
+	FastgptMongoConf   types.MongoDB
+	//验证码设置
+	Captcha captcha.Conf
+	//登录类型设置
+	LoginConf LoginConf
+}
+
+type LoginConf struct {
+	LoginVerify string `json:",default=captcha,options=[captcha,email,sms,sms_or_email,all]"`
 }

+ 44 - 0
internal/handler/auth/login_by_email_handler.go

@@ -0,0 +1,44 @@
+package auth
+
+import (
+	"net/http"
+
+	"github.com/zeromicro/go-zero/rest/httpx"
+
+	"wechat-api/internal/logic/auth"
+	"wechat-api/internal/svc"
+	"wechat-api/internal/types"
+)
+
+// swagger:route post /user/login_by_email auth LoginByEmail
+//
+// Log in by email | 邮箱登录
+//
+// Log in by email | 邮箱登录
+//
+// Parameters:
+//  + name: body
+//    require: true
+//    in: body
+//    type: LoginByEmailReq
+//
+// Responses:
+//  200: LoginResp
+
+func LoginByEmailHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		var req types.LoginByEmailReq
+		if err := httpx.Parse(r, &req, true); err != nil {
+			httpx.ErrorCtx(r.Context(), w, err)
+			return
+		}
+
+		l := auth.NewLoginByEmailLogic(r.Context(), svcCtx, w)
+		resp, err := l.LoginByEmail(&req)
+		if err != nil {
+			httpx.ErrorCtx(r.Context(), w, err)
+		} else {
+			httpx.OkJsonCtx(r.Context(), w, resp)
+		}
+	}
+}

+ 43 - 0
internal/handler/auth/login_by_sms_handler.go

@@ -0,0 +1,43 @@
+package auth
+
+import (
+	"net/http"
+
+	"github.com/zeromicro/go-zero/rest/httpx"
+
+	"wechat-api/internal/logic/auth"
+	"wechat-api/internal/svc"
+	"wechat-api/internal/types"
+)
+
+// swagger:route post /user/login_by_sms auth LoginBySms
+//
+// Log in by SMS | 短信登录
+//
+// Log in by SMS | 短信登录
+//
+// Parameters:
+//  + name: body
+//    require: true
+//    in: body
+//    type: LoginBySmsReq
+//
+// Responses:
+//  200: LoginResp
+
+func LoginBySmsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		var req types.LoginBySmsReq
+		if err := httpx.Parse(r, &req, true); err != nil {
+			httpx.ErrorCtx(r.Context(), w, err)
+			return
+		}
+		l := auth.NewLoginBySmsLogic(r.Context(), svcCtx, w)
+		resp, err := l.LoginBySms(&req)
+		if err != nil {
+			httpx.ErrorCtx(r.Context(), w, err)
+		} else {
+			httpx.OkJsonCtx(r.Context(), w, resp)
+		}
+	}
+}

+ 43 - 0
internal/handler/auth/login_handler.go

@@ -0,0 +1,43 @@
+package auth
+
+import (
+	"net/http"
+
+	"github.com/zeromicro/go-zero/rest/httpx"
+
+	"wechat-api/internal/logic/auth"
+	"wechat-api/internal/svc"
+	"wechat-api/internal/types"
+)
+
+// swagger:route post /user/login auth Login
+//
+// Log in | 登录
+//
+// Log in | 登录
+//
+// Parameters:
+//  + name: body
+//    require: true
+//    in: body
+//    type: LoginReq
+//
+// Responses:
+//  200: LoginResp
+
+func LoginHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		var req types.LoginReq
+		if err := httpx.Parse(r, &req, true); err != nil {
+			httpx.ErrorCtx(r.Context(), w, err)
+			return
+		}
+		l := auth.NewLoginLogic(r.Context(), svcCtx, w)
+		resp, err := l.Login(&req)
+		if err != nil {
+			httpx.ErrorCtx(r.Context(), w, err)
+		} else {
+			httpx.OkJsonCtx(r.Context(), w, resp)
+		}
+	}
+}

+ 22 - 1
internal/handler/routes.go

@@ -1,5 +1,5 @@
 // Code generated by goctl. DO NOT EDIT.
-// goctls v1.10.1
+// goctls v1.10.4
 
 package handler
 
@@ -21,6 +21,7 @@ import (
 	aliyun_avatar "wechat-api/internal/handler/aliyun_avatar"
 	allocagent "wechat-api/internal/handler/allocagent"
 	api_key "wechat-api/internal/handler/api_key"
+	auth "wechat-api/internal/handler/auth"
 	avatar "wechat-api/internal/handler/avatar"
 	base "wechat-api/internal/handler/base"
 	batch_msg "wechat-api/internal/handler/batch_msg"
@@ -2180,4 +2181,24 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
 		),
 		rest.WithJwt(serverCtx.Config.Auth.AccessSecret),
 	)
+
+	server.AddRoutes(
+		[]rest.Route{
+			{
+				Method:  http.MethodPost,
+				Path:    "/user/login",
+				Handler: auth.LoginHandler(serverCtx),
+			},
+			{
+				Method:  http.MethodPost,
+				Path:    "/user/login_by_email",
+				Handler: auth.LoginByEmailHandler(serverCtx),
+			},
+			{
+				Method:  http.MethodPost,
+				Path:    "/user/login_by_sms",
+				Handler: auth.LoginBySmsHandler(serverCtx),
+			},
+		},
+	)
 }

+ 115 - 0
internal/logic/auth/login_by_email_logic.go

@@ -0,0 +1,115 @@
+package auth
+
+import (
+	"context"
+	"github.com/suyuan32/simple-admin-common/config"
+	"github.com/suyuan32/simple-admin-common/enum/common"
+	"github.com/suyuan32/simple-admin-common/i18n"
+	"github.com/suyuan32/simple-admin-common/utils/jwt"
+	"github.com/suyuan32/simple-admin-common/utils/pointy"
+	"github.com/suyuan32/simple-admin-core/rpc/types/core"
+	"github.com/zeromicro/go-zero/core/errorx"
+	"net/http"
+	"strings"
+	"time"
+	"wechat-api/internal/logic/fastgpt"
+	"wechat-api/internal/svc"
+	"wechat-api/internal/types"
+
+	"github.com/zeromicro/go-zero/core/logx"
+)
+
+type LoginByEmailLogic struct {
+	logx.Logger
+	ctx    context.Context
+	svcCtx *svc.ServiceContext
+	w      http.ResponseWriter
+}
+
+func NewLoginByEmailLogic(ctx context.Context, svcCtx *svc.ServiceContext, w http.ResponseWriter) *LoginByEmailLogic {
+	return &LoginByEmailLogic{
+		Logger: logx.WithContext(ctx),
+		ctx:    ctx,
+		svcCtx: svcCtx,
+		w:      w,
+	}
+}
+
+func (l *LoginByEmailLogic) LoginByEmail(req *types.LoginByEmailReq) (resp *types.LoginResp, err error) {
+	if l.svcCtx.Config.LoginConf.LoginVerify != "email" && l.svcCtx.Config.LoginConf.LoginVerify != "sms_or_email" &&
+		l.svcCtx.Config.LoginConf.LoginVerify != "all" {
+		return nil, errorx.NewCodeAbortedError("login.loginTypeForbidden")
+	}
+
+	captchaData, err := l.svcCtx.Rds.Get(l.ctx, config.RedisCaptchaPrefix+req.Email).Result()
+	if err != nil {
+		logx.Errorw("failed to get captcha data in redis for email validation", logx.Field("detail", err),
+			logx.Field("data", req))
+		return nil, errorx.NewCodeInvalidArgumentError(i18n.Failed)
+	}
+
+	if captchaData != req.Captcha {
+		return nil, errorx.NewCodeInvalidArgumentError("login.wrongCaptcha")
+	}
+	userData, err := l.svcCtx.CoreRpc.GetUserList(l.ctx, &core.UserListReq{
+		Page:     1,
+		PageSize: 1,
+		Email:    &req.Email,
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	if userData.Total == 0 {
+		return nil, errorx.NewCodeInvalidArgumentError("login.userNotExist")
+	}
+
+	token, err := jwt.NewJwtToken(l.svcCtx.Config.Auth.AccessSecret, time.Now().Unix(),
+		l.svcCtx.Config.Auth.AccessExpire, jwt.WithOption("userId", userData.Data[0].Id), jwt.WithOption("roleId",
+			strings.Join(userData.Data[0].RoleCodes, ",")), jwt.WithOption("deptId", userData.Data[0].DepartmentId))
+	if err != nil {
+		return nil, err
+	}
+
+	// add token into database
+	expiredAt := time.Now().Add(time.Second * time.Duration(l.svcCtx.Config.Auth.AccessExpire)).UnixMilli()
+	_, err = l.svcCtx.CoreRpc.CreateToken(l.ctx, &core.TokenInfo{
+		Uuid:      userData.Data[0].Id,
+		Token:     pointy.GetPointer(token),
+		Source:    pointy.GetPointer("core_user"),
+		Status:    pointy.GetPointer(uint32(common.StatusNormal)),
+		Username:  userData.Data[0].Username,
+		ExpiredAt: pointy.GetPointer(expiredAt),
+	})
+	if err != nil {
+		return nil, err
+	}
+	err = l.svcCtx.Rds.Del(l.ctx, config.RedisCaptchaPrefix+req.Email).Err()
+	if err != nil {
+		logx.Errorw("failed to delete captcha in redis", logx.Field("detail", err))
+	}
+
+	//注册cookie 到fastgpt
+	fastgptLogic := fastgpt.NewSetTokenLogic(l.ctx, l.svcCtx, l.w)
+	userId := func(s *string) string {
+		if s == nil {
+			return ""
+		}
+		return *s
+	}(userData.Data[0].Id)
+
+	_, err = fastgptLogic.SetTokenByUserId(userId)
+	if err != nil {
+		return nil, err
+	}
+
+	resp = &types.LoginResp{
+		BaseDataInfo: types.BaseDataInfo{Msg: l.svcCtx.Trans.Trans(l.ctx, "login.loginSuccessTitle")},
+		Data: types.LoginInfo{
+			UserId: userId,
+			Token:  token,
+			Expire: uint64(expiredAt),
+		},
+	}
+	return resp, nil
+}

+ 107 - 0
internal/logic/auth/login_by_sms_logic.go

@@ -0,0 +1,107 @@
+package auth
+
+import (
+	"context"
+	"github.com/suyuan32/simple-admin-common/config"
+	"github.com/suyuan32/simple-admin-common/enum/common"
+	"github.com/suyuan32/simple-admin-common/i18n"
+	"github.com/suyuan32/simple-admin-common/msg/errormsg"
+	"github.com/suyuan32/simple-admin-common/utils/jwt"
+	"github.com/suyuan32/simple-admin-common/utils/pointy"
+	"github.com/suyuan32/simple-admin-core/rpc/types/core"
+	"github.com/zeromicro/go-zero/core/errorx"
+	"net/http"
+	"strings"
+	"time"
+	"wechat-api/internal/logic/fastgpt"
+	"wechat-api/internal/svc"
+	"wechat-api/internal/types"
+
+	"github.com/zeromicro/go-zero/core/logx"
+)
+
+type LoginBySmsLogic struct {
+	logx.Logger
+	ctx    context.Context
+	svcCtx *svc.ServiceContext
+	w      http.ResponseWriter
+}
+
+func NewLoginBySmsLogic(ctx context.Context, svcCtx *svc.ServiceContext, w http.ResponseWriter) *LoginBySmsLogic {
+	return &LoginBySmsLogic{
+		Logger: logx.WithContext(ctx),
+		ctx:    ctx,
+		svcCtx: svcCtx,
+		w:      w,
+	}
+}
+
+func (l *LoginBySmsLogic) LoginBySms(req *types.LoginBySmsReq) (resp *types.LoginResp, err error) {
+	if l.svcCtx.Config.LoginConf.LoginVerify != "sms" && l.svcCtx.Config.LoginConf.LoginVerify != "sms_or_email" &&
+		l.svcCtx.Config.LoginConf.LoginVerify != "all" {
+		return nil, errorx.NewCodeAbortedError("login.loginTypeForbidden")
+	}
+
+	captchaData, err := l.svcCtx.Rds.Get(l.ctx, config.RedisCaptchaPrefix+req.PhoneNumber).Result()
+	if err != nil {
+		logx.Errorw("failed to get captcha data in redis for email validation", logx.Field("detail", err),
+			logx.Field("data", req))
+		return nil, errorx.NewCodeInvalidArgumentError(i18n.Failed)
+	}
+
+	if captchaData != req.Captcha {
+		return nil, errorx.NewCodeInvalidArgumentError("login.wrongCaptcha")
+	}
+	userData, err := l.svcCtx.CoreRpc.GetUserList(l.ctx, &core.UserListReq{
+		Page:     1,
+		PageSize: 1,
+		Mobile:   &req.PhoneNumber,
+	})
+	if err != nil {
+		return nil, err
+	}
+	if userData.Total == 0 {
+		return nil, errorx.NewCodeInvalidArgumentError("login.userNotExist")
+	}
+	token, err := jwt.NewJwtToken(l.svcCtx.Config.Auth.AccessSecret, time.Now().Unix(),
+		l.svcCtx.Config.Auth.AccessExpire, jwt.WithOption("userId", userData.Data[0].Id), jwt.WithOption("roleId",
+			strings.Join(userData.Data[0].RoleCodes, ",")), jwt.WithOption("deptId", userData.Data[0].DepartmentId))
+	if err != nil {
+		return nil, err
+	}
+
+	// add token into database
+	expiredAt := time.Now().Add(time.Second * time.Duration(l.svcCtx.Config.Auth.AccessExpire)).UnixMilli()
+	_, err = l.svcCtx.CoreRpc.CreateToken(l.ctx, &core.TokenInfo{
+		Uuid:      userData.Data[0].Id,
+		Token:     pointy.GetPointer(token),
+		Source:    pointy.GetPointer("core_user"),
+		Status:    pointy.GetPointer(uint32(common.StatusNormal)),
+		Username:  userData.Data[0].Username,
+		ExpiredAt: pointy.GetPointer(expiredAt),
+	})
+	if err != nil {
+		return nil, err
+	}
+	err = l.svcCtx.Rds.Del(l.ctx, config.RedisCaptchaPrefix+req.PhoneNumber).Err()
+	if err != nil {
+		logx.Errorw("failed to delete captcha in redis", logx.Field("detail", err))
+	}
+
+	//注册cookie 到fastgpt
+	fastgptLogic := fastgpt.NewSetTokenLogic(l.ctx, l.svcCtx, l.w)
+	_, err = fastgptLogic.SetTokenByUserId(*userData.Data[0].Id)
+	if err != nil {
+		return nil, err
+	}
+
+	resp = &types.LoginResp{
+		BaseDataInfo: types.BaseDataInfo{Msg: errormsg.Success},
+		Data: types.LoginInfo{
+			UserId: *userData.Data[0].Id,
+			Token:  token,
+			Expire: uint64(expiredAt),
+		},
+	}
+	return resp, nil
+}

+ 98 - 0
internal/logic/auth/login_logic.go

@@ -0,0 +1,98 @@
+package auth
+
+import (
+	"context"
+	"github.com/suyuan32/simple-admin-common/config"
+	"github.com/suyuan32/simple-admin-common/enum/common"
+	"github.com/suyuan32/simple-admin-common/msg/errormsg"
+	"github.com/suyuan32/simple-admin-common/utils/encrypt"
+	"github.com/suyuan32/simple-admin-common/utils/jwt"
+	"github.com/suyuan32/simple-admin-common/utils/pointy"
+	"github.com/suyuan32/simple-admin-core/rpc/types/core"
+	"github.com/zeromicro/go-zero/core/errorx"
+	"github.com/zeromicro/go-zero/core/logx"
+	"net/http"
+	"strings"
+	"time"
+	"wechat-api/internal/logic/fastgpt"
+	"wechat-api/internal/svc"
+	"wechat-api/internal/types"
+)
+
+type LoginLogic struct {
+	logx.Logger
+	ctx    context.Context
+	svcCtx *svc.ServiceContext
+	rw     http.ResponseWriter
+}
+
+func NewLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext, rw http.ResponseWriter) *LoginLogic {
+	return &LoginLogic{
+		Logger: logx.WithContext(ctx),
+		ctx:    ctx,
+		svcCtx: svcCtx,
+		rw:     rw,
+	}
+}
+
+func (l *LoginLogic) Login(req *types.LoginReq) (resp *types.LoginResp, err error) {
+	//判断调用类型
+
+	if l.svcCtx.Config.LoginConf.LoginVerify != "captcha" && l.svcCtx.Config.LoginConf.LoginVerify != "all" {
+		return nil, errorx.NewCodeAbortedError("login.loginTypeForbidden")
+	}
+	ok := l.svcCtx.Captcha.Verify(config.RedisCaptchaPrefix+req.CaptchaId, req.Captcha, true)
+	if !ok {
+		return nil, errorx.NewCodeInvalidArgumentError("login.wrongCaptcha")
+	}
+	user, err := l.svcCtx.CoreRpc.GetUserByUsername(l.ctx,
+		&core.UsernameReq{
+			Username: req.Username,
+		})
+	if err != nil {
+		return nil, err
+	}
+	if !encrypt.BcryptCheck(req.Password, *user.Password) {
+		return nil, errorx.NewCodeInvalidArgumentError("login.wrongUsernameOrPassword")
+	}
+	token, err := jwt.NewJwtToken(l.svcCtx.Config.Auth.AccessSecret, time.Now().Unix(),
+		l.svcCtx.Config.Auth.AccessExpire, jwt.WithOption("userId", user.Id), jwt.WithOption("roleId",
+			strings.Join(user.RoleCodes, ",")), jwt.WithOption("deptId", user.DepartmentId))
+	if err != nil {
+		return nil, err
+	}
+	// add token into database
+	expiredAt := time.Now().Add(time.Second * time.Duration(l.svcCtx.Config.Auth.AccessExpire)).UnixMilli()
+	_, err = l.svcCtx.CoreRpc.CreateToken(l.ctx, &core.TokenInfo{
+		Uuid:      user.Id,
+		Token:     pointy.GetPointer(token),
+		Source:    pointy.GetPointer("core_user"),
+		Status:    pointy.GetPointer(uint32(common.StatusNormal)),
+		Username:  user.Username,
+		ExpiredAt: pointy.GetPointer(expiredAt),
+	})
+	if err != nil {
+		return nil, err
+	}
+	err = l.svcCtx.Rds.Del(l.ctx, config.RedisCaptchaPrefix+req.CaptchaId).Err()
+	if err != nil {
+		logx.Errorw("failed to delete captcha in redis", logx.Field("detail", err))
+	}
+	//请求fastgpt,并种cookie。先同步,后期可以异步
+	//set_cookie.NewFastGpt(l.svcCtx, fastgptLogic)
+	fastgptLogic := fastgpt.NewSetTokenLogic(l.ctx, l.svcCtx, l.rw)
+	//userId := *user.Id
+	_, err = fastgptLogic.SetTokenByUserId(*user.Id)
+	if err != nil {
+		return nil, err
+	}
+	resp = &types.LoginResp{
+		BaseDataInfo: types.BaseDataInfo{Msg: errormsg.Success},
+		Data: types.LoginInfo{
+			UserId: *user.Id,
+			Token:  token,
+			Expire: uint64(expiredAt),
+		},
+	}
+	return resp, nil
+}

+ 48 - 9
internal/logic/contact/get_contact_list_logic.go

@@ -2,6 +2,7 @@ package contact
 
 import (
 	"context"
+	"time"
 	"wechat-api/ent"
 	"wechat-api/ent/label"
 	"wechat-api/ent/labelrelationship"
@@ -57,18 +58,56 @@ func (l *GetContactListLogic) GetContactList(req *types.ContactListReq) (*types.
 		predicates = append(predicates, contact.OrganizationIDEQ(organizationId))
 	}
 
-	var ctype uint64 = 1
-	if req.Ctype != nil {
-		ctype = *req.Ctype
+	layout := "2006-01-02 15:04:05"
+	var startTime, endTime *time.Time
+
+	if req.StartDate != nil && *req.StartDate != "" {
+		startStr := *req.StartDate + " 00:00:00"
+		if t, err := time.Parse(layout, startStr); err == nil {
+			t = t.Add(23*time.Hour + 59*time.Minute + 59*time.Second)
+			startTime = &t
+		} else {
+			l.Logger.Errorf("invalid startDate: %v", err)
+		}
+	}
+
+	if req.EndDate != nil && *req.EndDate != "" {
+		endStr := *req.EndDate + " 23:59:59"
+		if t, err := time.Parse(layout, endStr); err == nil {
+			t = t.Add(23*time.Hour + 59*time.Minute + 59*time.Second)
+			endTime = &t
+		} else {
+			l.Logger.Errorf("invalid endDate: %v", err)
+		}
+	}
+
+	var relPreds []predicate.LabelRelationship
+
+	if startTime != nil {
+		relPreds = append(relPreds, labelrelationship.CreatedAtGTE(*startTime))
+	}
+	if endTime != nil {
+		relPreds = append(relPreds, labelrelationship.CreatedAtLTE(*endTime))
+	}
+	if len(relPreds) > 0 {
+		predicates = append(predicates, contact.HasContactRelationshipsWith(relPreds...))
 	}
-	predicates = append(predicates, contact.Ctype(ctype))
+
 	if len(req.LabelIDs) > 0 {
-		predicates = append(predicates, contact.HasContactRelationshipsWith(
-			labelrelationship.HasLabelsWith(
-				label.IDIn(req.LabelIDs...),
-			),
-		))
+		relPreds = append(relPreds,
+			labelrelationship.HasLabelsWith(label.IDIn(req.LabelIDs...)),
+		)
+	}
+	if len(relPreds) > 0 {
+		predicates = append(predicates, contact.HasContactRelationshipsWith(relPreds...))
 	}
+
+	if req.Ctype != nil {
+		predicates = append(predicates, contact.Ctype(*req.Ctype))
+	} else {
+		predicates = append(predicates, contact.Ctype(1)) // 默认 Ctype = 1
+	}
+
 	if req.WxWxid != nil {
 		predicates = append(predicates, contact.WxWxidContains(*req.WxWxid))
 	}

+ 181 - 0
internal/logic/contact/get_contact_list_logic_test.go

@@ -0,0 +1,181 @@
+package contact
+
+import (
+	"context"
+	"testing"
+	"time"
+
+	"wechat-api/ent"
+	"wechat-api/ent/contact"
+	"wechat-api/ent/label"
+	"wechat-api/ent/labelrelationship"
+	"wechat-api/ent/wx"
+	"wechat-api/internal/svc"
+	"wechat-api/internal/types"
+	"wechat-api/internal/utils/dberrorhandler"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/mock"
+	"github.com/suyuan32/simple-admin-common/msg/errormsg"
+	"github.com/suyuan32/simple-admin-common/utils/pointy"
+)
+
+// MockContactClient is a mock implementation of the Contact client.
+type MockContactClient struct {
+	mock.Mock
+}
+
+func (m *MockContactClient) Query() *ent.ContactQuery {
+	args := m.Called()
+	return args.Get(0).(*ent.ContactQuery)
+}
+
+// MockContactQuery is a mock implementation of the Contact query.
+type MockContactQuery struct {
+	mock.Mock
+}
+
+func (m *MockContactQuery) Where(predicates ...predicate.Contact) *ent.ContactQuery {
+	args := m.Called(predicates)
+	return args.Get(0).(*ent.ContactQuery)
+}
+
+func (m *MockContactQuery) WithContactRelationships(f func(*ent.LabelRelationshipQuery)) *ent.ContactQuery {
+	args := m.Called(f)
+	return args.Get(0).(*ent.ContactQuery)
+}
+
+func (m *MockContactQuery) Page(ctx context.Context, page, pageSize int) (*ent.ContactPage, error) {
+	args := m.Called(ctx, page, pageSize)
+	return args.Get(0).(*ent.ContactPage), args.Error(1)
+}
+
+// MockWxClient is a mock implementation of the Wx client.
+type MockWxClient struct {
+	mock.Mock
+}
+
+func (m *MockWxClient) Query() *ent.WxQuery {
+	args := m.Called()
+	return args.Get(0).(*ent.WxQuery)
+}
+
+// MockWxQuery is a mock implementation of the Wx query.
+type MockWxQuery struct {
+	mock.Mock
+}
+
+func (m *MockWxQuery) Where(predicates ...predicate.Wx) *ent.WxQuery {
+	args := m.Called(predicates)
+	return args.Get(0).(*ent.WxQuery)
+}
+
+func (m *MockWxQuery) All(ctx context.Context) ([]*ent.Wx, error) {
+	args := m.Called(ctx)
+	return args.Get(0).([]*ent.Wx), args.Error(1)
+}
+
+func TestGetContactList(t *testing.T) {
+	ctx := context.Background()
+	mockContactClient := new(MockContactClient)
+	mockWxClient := new(MockWxClient)
+	svcCtx := &svc.ServiceContext{
+		DB: &ent.Client{
+			Contact: mockContactClient,
+			Wx:      mockWxClient,
+		},
+	}
+
+	logic := NewGetContactListLogic(ctx, svcCtx)
+
+	// Mock the ContactQuery and WxQuery
+	mockContactQuery := new(MockContactQuery)
+	mockContactClient.On("Query").Return(mockContactQuery)
+
+	mockWxQuery := new(MockWxQuery)
+	mockWxClient.On("Query").Return(mockWxQuery)
+
+	// Prepare the expected data
+	expectedContact := &ent.Contact{
+		ID:         1,
+		CreatedAt:  time.Now(),
+		UpdatedAt:  time.Now(),
+		Status:     1,
+		WxWxid:     "wxid_1",
+		Type:       1,
+		Wxid:       "wxid_1",
+		Account:    "account_1",
+		Nickname:   "nickname_1",
+		Markname:   "markname_1",
+		Headimg:    "headimg_1",
+		Sex:        1,
+		Starrole:   1,
+		Dontseeit:  1,
+		Dontseeme:  1,
+		Lag:        1,
+		Gid:        "gid_1",
+		Gname:      "gname_1",
+		V3:         1,
+		Ctype:      1,
+		Cname:      "cname_1",
+		Cage:       25,
+		Carea:      "carea_1",
+		Cc:         "cc_1",
+		Phone:      "phone_1",
+		Cbirthday:  "cbirthday_1",
+		Cbirtharea: "cbirtharea_1",
+		CidcardNo:  "cidcardno_1",
+		Ctitle:     "ctitle_1",
+		Edges: ent.ContactEdges{
+			ContactRelationships: []*ent.LabelRelationship{
+				{
+					LabelID: 1,
+					Edges: ent.LabelRelationshipEdges{
+						Labels: &ent.Label{
+							Name: "label_1",
+						},
+					},
+				},
+			},
+		},
+	}
+
+	expectedWx := &ent.Wx{
+		Wxid:           "wxid_1",
+		Nickname:       "wx_nickname_1",
+		BlockList:      []string{"block_1"},
+		GroupBlockList: []string{"group_block_1"},
+	}
+
+	expectedPage := &ent.ContactPage{
+		List: []*ent.Contact{expectedContact},
+		PageDetails: ent.PageDetails{
+			Total: 1,
+		},
+	}
+
+	mockContactQuery.On("Where", mock.Anything).Return(mockContactQuery)
+	mockContactQuery.On("WithContactRelationships", mock.Anything).Return(mockContactQuery)
+	mockContactQuery.On("Page", ctx, 1, 10).Return(expectedPage, nil)
+
+	mockWxQuery.On("Where", mock.Anything).Return(mockWxQuery)
+	mockWxQuery.On("All", ctx).Return([]*ent.Wx{expectedWx}, nil)
+
+	// Call the GetContactList method
+	req := &types.ContactListReq{
+		Page:     1,
+		PageSize: 10,
+	}
+	resp, err := logic.GetContactList(req)
+
+	// Assertions
+	assert.NoError(t, err)
+	assert.Equal(t, errormsg.Success, resp.Msg)
+	assert.Equal(t, int64(1), resp.Data.Total)
+	assert.Len(t, resp.Data.Data, 1)
+	assert.Equal(t, expectedContact.ID, *resp.Data.Data[0].Id)
+	assert.Equal(t, expectedContact.WxWxid, *resp.Data.Data[0].WxWxid)
+	assert.Equal(t, expectedWx.Nickname, *resp.Data.Data[0].WxWxidNickname)
+	assert.Len(t, resp.Data.Data[0].LabelRelationships, 1)
+	assert.Equal(t, "label_1", *resp.Data.Data[0].LabelRelationships[0].Label)
+}

+ 44 - 4
internal/logic/fastgpt/set_token_logic.go

@@ -32,14 +32,15 @@ func NewSetTokenLogic(ctx context.Context, svcCtx *svc.ServiceContext, rw http.R
 }
 
 func (l *SetTokenLogic) SetToken(username string) (resp *types.BaseMsgResp, err error) {
+	//println(username)
 	claims, err := ParseJWT(username, l.svcCtx.Config.Auth.AccessSecret)
 	if err != nil {
-		return nil, fmt.Errorf("invalid token")
+		return nil, error(err)
 	}
 	data, err := l.svcCtx.CoreRpc.GetUserById(context.TODO(), &core.UUIDReq{Id: claims.UserId})
 	token, err := GetToken(strconv.FormatUint(*data.DepartmentId, 10))
 	if err != nil {
-		return nil, fmt.Errorf("invalid token")
+		return nil, fmt.Errorf("invalid token3")
 	}
 	//if err != nil {
 	//	return nil, err
@@ -67,6 +68,45 @@ func (l *SetTokenLogic) SetToken(username string) (resp *types.BaseMsgResp, err
 	return
 }
 
+// SetTokenByUserId 根据用户ID设置token。
+// 参数:
+//
+//	UserId - 用户的唯一标识符。
+//
+// 返回值:
+//
+//	*types.BaseMsgResp - 响应消息,包含设置结果的代码和消息。
+//	error - 如果操作失败,返回错误。
+func (l *SetTokenLogic) SetTokenByUserId(UserId string) (resp *types.BaseMsgResp, err error) {
+	data, err := l.svcCtx.CoreRpc.GetUserById(context.TODO(), &core.UUIDReq{Id: UserId})
+	println(data)
+	token, err := GetToken(strconv.FormatUint(*data.DepartmentId, 10))
+	if err != nil {
+		return nil, fmt.Errorf("invalid token")
+	}
+	// 创建一个新的 Cookie
+	cookie := &http.Cookie{
+		Name:     "fastgpt_token",
+		Value:    token, // 假设 req.Token 是你要设置的 Cookie 值
+		Domain:   ".gkscrm.com",
+		SameSite: http.SameSiteNoneMode,
+		Secure:   true, // 如果 SameSite 设置为 None,必须设置 Secure 为 true
+		HttpOnly: false,
+		Path:     "/",
+	}
+
+	// 设置 Cookie 到响应中
+	http.SetCookie(l.rw, cookie)
+
+	// 返回响应消息
+	resp = &types.BaseMsgResp{
+		Code: 0,
+		Msg:  "Cookie set successfully",
+	}
+
+	return
+}
+
 func GetToken(username string) (string, error) {
 	// 设置请求的 URL 和请求体
 	url := "https://agent.gkscrm.com/api/support/user/account/loginByPassword"
@@ -138,11 +178,11 @@ func ParseJWT(tokenString, accessSecret string) (*Claims, error) {
 	})
 
 	if err != nil {
-		return nil, fmt.Errorf("invalid token")
+		return nil, fmt.Errorf("invalid token1")
 	}
 
 	if !token.Valid {
-		return nil, fmt.Errorf("invalid token")
+		return nil, fmt.Errorf("invalid token2")
 	}
 
 	return claims, nil

+ 12 - 8
internal/svc/service_context.go

@@ -2,7 +2,13 @@ package svc
 
 import (
 	"github.com/bwmarrin/snowflake"
+	"github.com/mojocn/base64Captcha"
+	"github.com/redis/go-redis/v9"
+	"github.com/suyuan32/simple-admin-common/i18n"
+	"github.com/suyuan32/simple-admin-common/utils/captcha"
+	"github.com/suyuan32/simple-admin-core/rpc/coreclient"
 	"github.com/zeromicro/go-zero/core/collection"
+	"github.com/zeromicro/go-zero/core/logx"
 	"gorm.io/gorm"
 	"time"
 	"wechat-api/database"
@@ -12,17 +18,12 @@ import (
 	"wechat-api/internal/pkg/wechat_ws"
 	"wechat-api/mongo_model"
 
-	"github.com/redis/go-redis/v9"
-
-	"wechat-api/ent"
-	_ "wechat-api/ent/runtime"
-
-	"github.com/suyuan32/simple-admin-core/rpc/coreclient"
-	"github.com/zeromicro/go-zero/core/logx"
-
 	"github.com/casbin/casbin/v2"
+
 	"github.com/zeromicro/go-zero/rest"
 	"github.com/zeromicro/go-zero/zrpc"
+	"wechat-api/ent"
+	_ "wechat-api/ent/runtime"
 )
 
 type ServiceContext struct {
@@ -40,6 +41,8 @@ type ServiceContext struct {
 	Cache         *collection.Cache
 	NodeID        *snowflake.Node
 	MongoModel    *mongo_model.AllMongoModel
+	Captcha       *base64Captcha.Captcha
+	Trans         *i18n.Translator
 }
 
 func NewServiceContext(c config.Config) *ServiceContext {
@@ -111,5 +114,6 @@ func NewServiceContext(c config.Config) *ServiceContext {
 		Cache:         cache,
 		NodeID:        node,
 		MongoModel:    all_mongo_model,
+		Captcha:       captcha.MustNewOriginalRedisCaptcha(c.Captcha, rds),
 	}
 }

+ 80 - 0
internal/types/types.go

@@ -1027,6 +1027,9 @@ type ContactListReq struct {
 	Type *int `json:"type,optional"`
 	// 内容类型:1-个微 3-企微
 	Ctype *uint64 `json:"ctype,optional"`
+	//标签搜索开始结束日期
+	StartDate *string `json:"start_date,optional"`
+	EndDate   *string `json:"end_date,optional"`
 }
 
 // Contact information response | Contact信息返回体
@@ -4371,3 +4374,80 @@ type ApiKeyListReq struct {
 	Key            *string `json:"key,optional"`
 	OrganizationId *uint64 `json:"organization_id,optional"`
 }
+
+// The log in information | 登陆返回的数据信息
+// swagger:model LoginInfo
+type LoginInfo struct {
+	// User's UUID | 用户的UUID
+	UserId string `json:"userId"`
+	// Token for authorization | 验证身份的token
+	Token string `json:"token"`
+	// Expire timestamp | 过期时间戳
+	Expire uint64 `json:"expire"`
+}
+
+// The permission code for front end permission control | 权限码: 用于前端权限控制
+// swagger:model PermCodeResp
+type PermCodeResp struct {
+	BaseDataInfo
+	// Permission code data | 权限码数据
+	Data []string `json:"data"`
+}
+
+// Login request | 登录参数
+// swagger:model LoginReq
+type LoginReq struct {
+	// User Name | 用户名
+	// required : true
+	// max length : 20
+	Username string `json:"username" validate:"required,alphanum,max=20"`
+	// Password | 密码
+	// required : true
+	// max length : 30
+	// min length : 6
+	Password string `json:"password" validate:"required,max=30,min=6"`
+	// Captcha ID which store in redis | 验证码编号, 存在redis中
+	// required : true
+	// max length : 20
+	// min length : 20
+	CaptchaId string `json:"captchaId"  validate:"required,len=20"`
+	// The Captcha which users input | 用户输入的验证码
+	// required : true
+	// max length : 5
+	// min length : 5
+	Captcha string `json:"captcha" validate:"required,len=5"`
+}
+
+// Log in by email request | 邮箱登录参数
+// swagger:model LoginByEmailReq
+type LoginByEmailReq struct {
+	// The user's email address | 用户的邮箱
+	// required : true
+	// max length : 100
+	Email string `json:"email" validate:"required,email,max=100"`
+	// The Captcha which users input | 用户输入的验证码
+	// max length : 5
+	// min length : 5
+	Captcha string `json:"captcha,optional" validate:"omitempty,len=5"`
+}
+
+// Log in by SMS request | 短信登录参数
+// swagger:model LoginBySmsReq
+type LoginBySmsReq struct {
+	// The user's mobile phone number | 用户的手机号码
+	// required : true
+	// max length : 20
+	PhoneNumber string `json:"phoneNumber"  validate:"required,numeric,max=20"`
+	// The Captcha which users input | 用户输入的验证码
+	// max length : 5
+	// min length : 5
+	Captcha string `json:"captcha,optional" validate:"omitempty,len=5"`
+}
+
+// The log in response data | 登录返回数据
+// swagger:model LoginResp
+type LoginResp struct {
+	BaseDataInfo
+	// The log in information | 登陆返回的数据信息
+	Data LoginInfo `json:"data"`
+}