Browse Source

2025411-changdong merger into debug

lichangdong 3 weeks ago
parent
commit
c6291e4fb7

+ 2 - 1
desc/all.api

@@ -48,4 +48,5 @@ import "./wechat/xunji_service.api"
 import "./wp_wecom/send_msg.api"
 import "./wechat/fastgpt.api"
 import "./wechat/department.api"
-import "./wechat/api_key.api"
+import "./wechat/api_key.api"
+import "./wechat/auth_login.api"

+ 84 - 0
desc/wechat/auth_login.api

@@ -0,0 +1,84 @@
+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)
+
+}
+

+ 3 - 0
desc/wechat/contact.api

@@ -64,6 +64,9 @@ type (
 
 		// 内容类型:1-个微 3-企微
 		Ctype *uint64 `json:"ctype,optional"`
+        // 达标的起止时间
+        StartDate *string `json:"start_date,optional"`
+        EndDate *string `json:"end_date,optional"`
     }
 
     // Contact information response | Contact信息返回体

+ 4 - 0
go.mod

@@ -17,9 +17,11 @@ require (
 	github.com/go-resty/resty/v2 v2.14.0
 	github.com/gofrs/uuid/v5 v5.0.0
 	github.com/golang-jwt/jwt/v4 v4.3.0
+
 	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
@@ -83,6 +85,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
@@ -162,6 +165,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

+ 7 - 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=
@@ -500,6 +502,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=
@@ -770,6 +774,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=

+ 9 - 0
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"
@@ -24,4 +25,12 @@ type Config struct {
 	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)
+		}
+	}
+}

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

@@ -0,0 +1,42 @@
+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 /auth/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)
+		}
+	}
+}

+ 31 - 0
internal/handler/auth/logout_handler.go

@@ -0,0 +1,31 @@
+package auth
+
+import (
+	"net/http"
+
+	"github.com/zeromicro/go-zero/rest/httpx"
+
+	"wechat-api/internal/logic/auth"
+	"wechat-api/internal/svc"
+)
+
+// swagger:route get /auth/logout auth Logout
+//
+// Log out | 退出登陆
+//
+// Log out | 退出登陆
+//
+// Responses:
+//  200: BaseMsgResp
+
+func LogoutHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		l := auth.NewLogoutLogic(r.Context(), svcCtx)
+		resp, err := l.Logout()
+		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"
@@ -2271,4 +2272,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
+}

+ 29 - 0
internal/logic/auth/logout_logic.go

@@ -0,0 +1,29 @@
+package auth
+
+import (
+	"context"
+
+	"wechat-api/internal/svc"
+	"wechat-api/internal/types"
+
+	"github.com/zeromicro/go-zero/core/logx"
+)
+
+type LogoutLogic struct {
+	logx.Logger
+	ctx    context.Context
+	svcCtx *svc.ServiceContext
+}
+
+func NewLogoutLogic(ctx context.Context, svcCtx *svc.ServiceContext) *LogoutLogic {
+	return &LogoutLogic{
+		Logger: logx.WithContext(ctx),
+		ctx:    ctx,
+		svcCtx: svcCtx}
+}
+
+func (l *LogoutLogic) Logout() (resp *types.BaseMsgResp, err error) {
+	// todo: add your logic here and delete this line
+
+	return
+}

+ 33 - 0
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"
@@ -56,6 +57,38 @@ func (l *GetContactListLogic) GetContactList(req *types.ContactListReq) (*types.
 	if req.WxWxid == nil || (req.WxWxid != nil && !isAdmin) {
 		predicates = append(predicates, contact.OrganizationIDEQ(organizationId))
 	}
+	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.ParseInLocation(layout, startStr, time.Local); err == nil {
+			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.ParseInLocation(layout, endStr, time.Local); err == nil {
+			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...))
+	}
 
 	var ctype uint64 = 1
 	if req.Ctype != nil {

+ 37 - 3
internal/logic/fastgpt/set_token_logic.go

@@ -32,13 +32,47 @@ 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 token3")
+	}
+	//if err != nil {
+	//	return nil, err
+	//}
+	// 创建一个新的 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 (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")
 	}
 	//if err != nil {
@@ -138,11 +172,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

+ 22 - 16
internal/svc/service_context.go

@@ -2,9 +2,13 @@ package svc
 
 import (
 	"github.com/bwmarrin/snowflake"
-	"github.com/casbin/casbin/v2"
+	"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"
@@ -14,14 +18,12 @@ import (
 	"wechat-api/internal/pkg/wechat_ws"
 	"wechat-api/mongo_model"
 
-	"github.com/suyuan32/simple-admin-core/rpc/coreclient"
-	"github.com/zeromicro/go-zero/core/logx"
-
-	"wechat-api/ent"
-	_ "wechat-api/ent/runtime"
+	"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 {
@@ -39,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 {
@@ -99,15 +103,17 @@ func NewServiceContext(c config.Config) *ServiceContext {
 	return &ServiceContext{
 		Config:        c,
 		Authority:     middleware.NewAuthorityMiddleware(cbn, rds, coreRpc).Handle,
-		OpenAuthority: middleware.NewOpenAuthorityMiddleware(db, rds, c).Handle, Miniprogram: middleware.NewMiniprogramMiddleware(cbn, rds, coreRpc, c).Handle,
-		DB:         db,
-		WechatDB:   wechatDb,
-		WechatQ:    wechatQ,
-		CoreRpc:    coreRpc,
-		Rds:        rds,
-		WechatWs:   wechatWs,
-		Cache:      cache,
-		NodeID:     node,
-		MongoModel: all_mongo_model,
+		OpenAuthority: middleware.NewOpenAuthorityMiddleware(db, rds, c).Handle,
+		Miniprogram:   middleware.NewMiniprogramMiddleware(cbn, rds, coreRpc, c).Handle,
+		DB:            db,
+		WechatDB:      wechatDb,
+		WechatQ:       wechatQ,
+		CoreRpc:       coreRpc,
+		Rds:           rds,
+		WechatWs:      wechatWs,
+		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信息返回体
@@ -4504,3 +4507,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"`
+}