Compare commits
20 Commits
bf220076dd
...
main
Author | SHA1 | Date | |
---|---|---|---|
9f220dc025 | |||
7951f77f47 | |||
bb2a68fb61 | |||
8fd1968cf6 | |||
666527b976 | |||
502a9b82d7 | |||
7a729211d1 | |||
f0a67822ef | |||
70f65c96bd | |||
48ddb72cf2 | |||
73e97ba151 | |||
c3d5d532cd | |||
f86b56a79d | |||
fa0234d385 | |||
8106282c8b | |||
aac13d369c | |||
cf0f60d221 | |||
a5ae680f94 | |||
5faff4afa0 | |||
e074395859 |
96
api/v1/app/banner.go
Normal file
96
api/v1/app/banner.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/lckt/model/app"
|
||||
common "git.echol.cn/loser/lckt/model/common/request"
|
||||
"git.echol.cn/loser/lckt/model/common/response"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type BannerApi struct{}
|
||||
|
||||
// Create 新建Banner
|
||||
func (b *BannerApi) Create(ctx *gin.Context) {
|
||||
var p app.Banner
|
||||
if err := ctx.ShouldBind(&p); err != nil {
|
||||
response.FailWithMessage("参数错误", ctx)
|
||||
return
|
||||
}
|
||||
if err := bannerService.CreateBanner(p); err != nil {
|
||||
response.FailWithMessage("创建失败: "+err.Error(), ctx)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("创建成功", ctx)
|
||||
}
|
||||
|
||||
// Delete 删除Banner
|
||||
func (b *BannerApi) Delete(ctx *gin.Context) {
|
||||
var p app.Banner
|
||||
if err := ctx.ShouldBind(&p); err != nil {
|
||||
response.FailWithMessage("参数错误", ctx)
|
||||
return
|
||||
}
|
||||
if err := bannerService.DeleteBanner(p.ID); err != nil {
|
||||
response.FailWithMessage("删除失败: "+err.Error(), ctx)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("删除成功", ctx)
|
||||
}
|
||||
|
||||
// Update 更新Banner
|
||||
func (b *BannerApi) Update(ctx *gin.Context) {
|
||||
var p app.Banner
|
||||
if err := ctx.ShouldBind(&p); err != nil {
|
||||
response.FailWithMessage("参数错误", ctx)
|
||||
return
|
||||
}
|
||||
if err := bannerService.UpdateBanner(p); err != nil {
|
||||
response.FailWithMessage("更新失败: "+err.Error(), ctx)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("更新成功", ctx)
|
||||
}
|
||||
|
||||
// GetList 获取Banner列表
|
||||
func (b *BannerApi) GetList(ctx *gin.Context) {
|
||||
var p common.PageInfo
|
||||
if err := ctx.ShouldBind(&p); err != nil {
|
||||
response.FailWithMessage("参数错误", ctx)
|
||||
return
|
||||
}
|
||||
list, total, err := bannerService.GetBannerList(p.Page, p.PageSize)
|
||||
if err != nil {
|
||||
response.FailWithMessage("获取列表失败: "+err.Error(), ctx)
|
||||
return
|
||||
}
|
||||
response.OkWithData(response.PageResult{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: p.Page,
|
||||
PageSize: p.PageSize,
|
||||
}, ctx)
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取Banner
|
||||
func (b *BannerApi) GetByID(ctx *gin.Context) {
|
||||
var p common.GetById
|
||||
if err := ctx.ShouldBind(&p); err != nil {
|
||||
response.FailWithMessage("参数错误", ctx)
|
||||
return
|
||||
}
|
||||
banner, err := bannerService.GetBannerByID(p.ID)
|
||||
if err != nil {
|
||||
response.FailWithMessage("获取失败: "+err.Error(), ctx)
|
||||
return
|
||||
}
|
||||
response.OkWithData(banner, ctx)
|
||||
}
|
||||
|
||||
func (b *BannerApi) GetIndexBanners(context *gin.Context) {
|
||||
list, err := bannerService.GetBannerIndex()
|
||||
if err != nil {
|
||||
response.FailWithMessage("获取首页Banner失败: "+err.Error(), context)
|
||||
return
|
||||
}
|
||||
response.OkWithData(list, context)
|
||||
}
|
14
api/v1/app/enter.go
Normal file
14
api/v1/app/enter.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package app
|
||||
|
||||
import "git.echol.cn/loser/lckt/service"
|
||||
|
||||
type ApiGroup struct {
|
||||
AppUserApi
|
||||
BannerApi
|
||||
OrderApi
|
||||
}
|
||||
|
||||
var userService = service.ServiceGroupApp.UserServiceGroup.UserService
|
||||
var appUserService = service.ServiceGroupApp.AppServiceGroup.AppUserService
|
||||
var bannerService = service.ServiceGroupApp.AppServiceGroup.BannerService
|
||||
var orderService = service.ServiceGroupApp.AppServiceGroup.OrderService
|
131
api/v1/app/order.go
Normal file
131
api/v1/app/order.go
Normal file
@@ -0,0 +1,131 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/lckt/global"
|
||||
"git.echol.cn/loser/lckt/model/app"
|
||||
"git.echol.cn/loser/lckt/model/app/request"
|
||||
r "git.echol.cn/loser/lckt/model/common/response"
|
||||
"git.echol.cn/loser/lckt/utils/user_jwt"
|
||||
"git.echol.cn/loser/lckt/utils/wechat"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type OrderApi struct{}
|
||||
|
||||
// CreateOrder APP新建订单
|
||||
func (o *OrderApi) CreateOrder(c *gin.Context) {
|
||||
var p app.Order
|
||||
if err := c.ShouldBind(&p); err != nil {
|
||||
global.GVA_LOG.Error("支付订单参数错误", zap.Error(err))
|
||||
r.FailWithMessage("支付订单参数错误", c)
|
||||
return
|
||||
}
|
||||
|
||||
order, err := orderService.Create(&p)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("创建订单失败", zap.Error(err))
|
||||
r.FailWithMessage("创建订单失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
r.OkWithData(order, c)
|
||||
}
|
||||
|
||||
func (o *OrderApi) PayOrder(context *gin.Context) {
|
||||
var p request.PayReq
|
||||
if err := context.ShouldBind(&p); err != nil {
|
||||
global.GVA_LOG.Error("支付订单参数错误", zap.Error(err))
|
||||
r.FailWithMessage("支付订单参数错误", context)
|
||||
return
|
||||
}
|
||||
|
||||
pay, err := orderService.Pay(p, context)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("支付订单失败", zap.Error(err))
|
||||
r.FailWithMessage("支付订单失败:"+err.Error(), context)
|
||||
return
|
||||
}
|
||||
|
||||
r.OkWithDetailed(pay, "支付订单成功", context)
|
||||
}
|
||||
|
||||
// NotifyOrder 微信支付回调通知
|
||||
func (o *OrderApi) NotifyOrder(context *gin.Context) {
|
||||
err := wechat.NotifyHandle(context)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("微信支付回调处理失败", zap.Error(err))
|
||||
r.FailWithMessage("微信支付回调处理失败:"+err.Error(), context)
|
||||
return
|
||||
}
|
||||
r.OkWithMessage("微信支付回调处理成功", context)
|
||||
}
|
||||
|
||||
func (o *OrderApi) GetOrderDetail(context *gin.Context) {
|
||||
id := context.Param("id")
|
||||
if id == "" {
|
||||
global.GVA_LOG.Error("获取订单详情参数错误: ID不能为空")
|
||||
r.FailWithMessage("获取订单详情参数错误: ID不能为空", context)
|
||||
return
|
||||
}
|
||||
|
||||
order, err := orderService.GetOrderDetail(id)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取订单详情失败", zap.Error(err))
|
||||
r.FailWithMessage("获取订单详情失败", context)
|
||||
return
|
||||
}
|
||||
|
||||
r.OkWithData(order, context)
|
||||
}
|
||||
|
||||
func (o *OrderApi) GetOrderList(context *gin.Context) {
|
||||
var p request.GetOrderList
|
||||
if err := context.ShouldBind(&p); err != nil {
|
||||
global.GVA_LOG.Error("获取订单列表参数错误", zap.Error(err))
|
||||
r.FailWithMessage("获取订单列表参数错误", context)
|
||||
return
|
||||
}
|
||||
|
||||
// 获取当前用户ID
|
||||
userId := user_jwt.GetUserID(context)
|
||||
if userId == 0 {
|
||||
global.GVA_LOG.Error("获取用户ID失败")
|
||||
r.FailWithMessage("获取用户ID失败", context)
|
||||
return
|
||||
}
|
||||
|
||||
// 获取订单列表
|
||||
orders, total, err := orderService.GetOrderList(p, userId)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取订单列表失败", zap.Error(err))
|
||||
r.FailWithMessage("获取订单列表失败:"+err.Error(), context)
|
||||
return
|
||||
}
|
||||
|
||||
r.OkWithDetailed(
|
||||
r.PageResult{
|
||||
List: orders,
|
||||
Total: total,
|
||||
Page: p.Page,
|
||||
PageSize: p.PageSize,
|
||||
}, "获取订单列表成功", context)
|
||||
}
|
||||
|
||||
func (o *OrderApi) BalancePay(context *gin.Context) {
|
||||
var p request.BalancePay
|
||||
if err := context.ShouldBind(&p); err != nil {
|
||||
global.GVA_LOG.Error("余额支付参数错误", zap.Error(err))
|
||||
r.FailWithMessage("余额支付参数错误", context)
|
||||
return
|
||||
}
|
||||
|
||||
err := orderService.BalancePay(p)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("余额支付失败", zap.Error(err))
|
||||
r.FailWithMessage("余额支付失败:"+err.Error(), context)
|
||||
return
|
||||
}
|
||||
|
||||
r.OkWithMessage("余额支付成功", context)
|
||||
}
|
363
api/v1/app/user.go
Normal file
363
api/v1/app/user.go
Normal file
@@ -0,0 +1,363 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"git.echol.cn/loser/lckt/global"
|
||||
"git.echol.cn/loser/lckt/model/app"
|
||||
r "git.echol.cn/loser/lckt/model/common/response"
|
||||
"git.echol.cn/loser/lckt/model/user/request"
|
||||
"git.echol.cn/loser/lckt/utils"
|
||||
"git.echol.cn/loser/lckt/utils/user_jwt"
|
||||
"git.echol.cn/loser/lckt/utils/wechat"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type AppUserApi struct{}
|
||||
|
||||
// SendCode 发送验证码
|
||||
func (*AppUserApi) SendCode(ctx *gin.Context) {
|
||||
var p request.SendCodeReq
|
||||
if err := ctx.ShouldBind(&p); err != nil {
|
||||
r.FailWithMessage(err.Error(), ctx)
|
||||
global.GVA_LOG.Error("参数错误,发送验证码失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
if err := userService.SendCode(p); err != nil {
|
||||
r.FailWithMessage("发送验证码失败", ctx)
|
||||
return
|
||||
}
|
||||
|
||||
r.OkWithMessage("发送验证码成功", ctx)
|
||||
}
|
||||
|
||||
// Login 用户登录
|
||||
func (*AppUserApi) Login(ctx *gin.Context) {
|
||||
var p request.CodeLoginReq
|
||||
if err := ctx.ShouldBind(&p); err != nil {
|
||||
r.FailWithMessage(err.Error(), ctx)
|
||||
global.GVA_LOG.Error("参数错误,登录失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
if result, _ := global.GVA_REDIS.Get(ctx, fmt.Sprintf("VerifyCode:%s", p.Phone)).Result(); result != p.Code {
|
||||
global.GVA_LOG.Error("验证码错误", zap.String("phone", p.Phone))
|
||||
r.FailWithMessage("验证码错误", ctx)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := appUserService.Login(p)
|
||||
if err != nil {
|
||||
r.FailWithMessage("登录失败", ctx)
|
||||
return
|
||||
}
|
||||
|
||||
// 生成token
|
||||
token, claims, err := user_jwt.LoginToken(user)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取token失败!", zap.Error(err))
|
||||
r.FailWithMessage("获取token失败", ctx)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = global.GVA_REDIS.Get(ctx, strconv.Itoa(int(user.ID))).Result(); errors.Is(err, redis.Nil) {
|
||||
// 此处过期时间等于jwt过期时间
|
||||
dr, err := utils.ParseDuration(global.GVA_CONFIG.JWT.ExpiresTime)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
timer := dr
|
||||
if err := global.GVA_REDIS.Set(ctx, strconv.Itoa(int(user.ID)), token, timer).Err(); err != nil {
|
||||
global.GVA_LOG.Error("设置登录状态失败!", zap.Error(err))
|
||||
r.FailWithMessage("设置登录状态失败", ctx)
|
||||
return
|
||||
}
|
||||
user_jwt.SetToken(ctx, token, int(claims.RegisteredClaims.ExpiresAt.Unix()-time.Now().Unix()))
|
||||
r.OkWithDetailed(gin.H{
|
||||
"User": user,
|
||||
"Token": token,
|
||||
"ExpiresAt": claims.RegisteredClaims.ExpiresAt.Unix() * 1000,
|
||||
}, "登录成功", ctx)
|
||||
} else if err != nil {
|
||||
global.GVA_LOG.Error("设置登录状态失败!", zap.Error(err))
|
||||
r.FailWithMessage("设置登录状态失败", ctx)
|
||||
}
|
||||
}
|
||||
|
||||
// WechatLogin 微信登录
|
||||
func (*AppUserApi) WechatLogin(ctx *gin.Context) {
|
||||
var p request.CodeLoginReq
|
||||
if err := ctx.ShouldBind(&p); err != nil {
|
||||
r.FailWithMessage(err.Error(), ctx)
|
||||
global.GVA_LOG.Error("参数错误,登录失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
//Todo 待完善微信登录
|
||||
info := wechat.GetUserInfo(p.Code)
|
||||
if info == nil {
|
||||
r.FailWithMessage("获取用户信息失败", ctx)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := appUserService.WechatLogin(info)
|
||||
if err != nil {
|
||||
r.FailWithMessage("登录失败", ctx)
|
||||
return
|
||||
}
|
||||
// 生成token
|
||||
token, claims, err := user_jwt.LoginToken(user)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取token失败!", zap.Error(err))
|
||||
r.FailWithMessage("获取token失败", ctx)
|
||||
return
|
||||
}
|
||||
// 此处过期时间等于jwt过期时间
|
||||
dr, err := utils.ParseDuration(global.GVA_CONFIG.JWT.ExpiresTime)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
timer := dr
|
||||
if err := global.GVA_REDIS.Set(ctx, strconv.Itoa(int(user.ID)), token, timer).Err(); err != nil {
|
||||
global.GVA_LOG.Error("设置登录状态失败!", zap.Error(err))
|
||||
r.FailWithMessage("设置登录状态失败", ctx)
|
||||
return
|
||||
}
|
||||
user_jwt.SetToken(ctx, token, int(claims.RegisteredClaims.ExpiresAt.Unix()-time.Now().Unix()))
|
||||
|
||||
r.OkWithDetailed(gin.H{
|
||||
"User": user,
|
||||
"Token": token,
|
||||
"ExpiresAt": claims.RegisteredClaims.ExpiresAt.Unix() * 1000,
|
||||
}, "登录成功", ctx)
|
||||
}
|
||||
|
||||
// GetUserInfo 获取用户信息
|
||||
func (*AppUserApi) GetUserInfo(ctx *gin.Context) {
|
||||
id := user_jwt.GetUserID(ctx)
|
||||
if id == 0 {
|
||||
global.GVA_LOG.Error("获取用户ID失败")
|
||||
r.FailWithMessage("获取用户ID失败", ctx)
|
||||
return
|
||||
}
|
||||
user, err := appUserService.GetUserInfo(id)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取用户信息失败", zap.Error(err))
|
||||
r.FailWithMessage("获取用户信息失败", ctx)
|
||||
return
|
||||
}
|
||||
r.OkWithDetailed(user, "获取用户信息成功", ctx)
|
||||
}
|
||||
|
||||
// PwdLogin 密码登录
|
||||
func (*AppUserApi) PwdLogin(ctx *gin.Context) {
|
||||
var p request.PwdLoginReq
|
||||
if err := ctx.ShouldBind(&p); err != nil {
|
||||
r.FailWithMessage(err.Error(), ctx)
|
||||
global.GVA_LOG.Error("参数错误,登录失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
user, err := appUserService.PwdLogin(p)
|
||||
if err != nil {
|
||||
r.FailWithMessage("手机号或密码错误!", ctx)
|
||||
return
|
||||
}
|
||||
|
||||
// 生成token
|
||||
token, claims, err := user_jwt.LoginToken(user)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取token失败!", zap.Error(err))
|
||||
r.FailWithMessage("获取token失败", ctx)
|
||||
return
|
||||
}
|
||||
|
||||
// 此处过期时间等于jwt过期时间
|
||||
dr, err := utils.ParseDuration(global.GVA_CONFIG.JWT.ExpiresTime)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
timer := dr
|
||||
if err := global.GVA_REDIS.Set(ctx, strconv.Itoa(int(user.ID)), token, timer).Err(); err != nil {
|
||||
global.GVA_LOG.Error("设置登录状态失败!", zap.Error(err))
|
||||
r.FailWithMessage("设置登录状态失败", ctx)
|
||||
return
|
||||
}
|
||||
user_jwt.SetToken(ctx, token, int(claims.RegisteredClaims.ExpiresAt.Unix()-time.Now().Unix()))
|
||||
r.OkWithDetailed(gin.H{
|
||||
"User": user,
|
||||
"Token": token,
|
||||
"ExpiresAt": claims.RegisteredClaims.ExpiresAt.Unix() * 1000,
|
||||
}, "登录成功", ctx)
|
||||
}
|
||||
|
||||
// Register 用户手机注册
|
||||
func (a *AppUserApi) Register(context *gin.Context) {
|
||||
var p request.RegisterReq
|
||||
if err := context.ShouldBind(&p); err != nil {
|
||||
r.FailWithMessage(err.Error(), context)
|
||||
global.GVA_LOG.Error("参数错误,注册失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
if result, _ := global.GVA_REDIS.Get(context, fmt.Sprintf("VerifyCode:%s", p.Phone)).Result(); result != p.Code {
|
||||
global.GVA_LOG.Error("验证码错误", zap.String("phone", p.Phone))
|
||||
r.FailWithMessage("验证码错误", context)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := appUserService.Register(p)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("注册失败", zap.Error(err))
|
||||
r.FailWithMessage("注册失败", context)
|
||||
return
|
||||
}
|
||||
|
||||
// 生成token
|
||||
token, claims, err := user_jwt.LoginToken(user)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取token失败!", zap.Error(err))
|
||||
r.FailWithMessage("获取token失败", context)
|
||||
return
|
||||
}
|
||||
|
||||
// 此处过期时间等于jwt过期时间
|
||||
dr, err := utils.ParseDuration(global.GVA_CONFIG.JWT.ExpiresTime)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
timer := dr
|
||||
if err := global.GVA_REDIS.Set(context, user.Phone, token, timer).Err(); err != nil {
|
||||
global.GVA_LOG.Error("设置登录状态失败!", zap.Error(err))
|
||||
r.FailWithMessage("设置登录状态失败", context)
|
||||
return
|
||||
}
|
||||
user_jwt.SetToken(context, token, int(claims.RegisteredClaims.ExpiresAt.Unix()-time.Now().Unix()))
|
||||
r.OkWithDetailed(gin.H{
|
||||
"User": user,
|
||||
"Token": token,
|
||||
"ExpiresAt": claims.RegisteredClaims.ExpiresAt.Unix() * 1000,
|
||||
}, "注册成功", context)
|
||||
}
|
||||
|
||||
// ApplyTeacher 申请成为教师
|
||||
func (a *AppUserApi) ApplyTeacher(context *gin.Context) {
|
||||
var p app.TeacherApply
|
||||
if err := context.ShouldBind(&p); err != nil {
|
||||
r.FailWithMessage(err.Error(), context)
|
||||
global.GVA_LOG.Error("参数错误,申请失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
id := user_jwt.GetUserID(context)
|
||||
if id == 0 {
|
||||
global.GVA_LOG.Error("获取用户ID失败")
|
||||
r.FailWithMessage("获取用户ID失败", context)
|
||||
return
|
||||
}
|
||||
|
||||
p.UserId = id
|
||||
|
||||
if err := appUserService.ApplyTeacher(p); err != nil {
|
||||
global.GVA_LOG.Error("申请失败", zap.Error(err))
|
||||
r.FailWithMessage("申请失败", context)
|
||||
return
|
||||
}
|
||||
|
||||
r.OkWithMessage("申请成功", context)
|
||||
}
|
||||
|
||||
// GetTeacherApply 获取教师申请状态
|
||||
func (a *AppUserApi) GetTeacherApply(context *gin.Context) {
|
||||
id := user_jwt.GetUserID(context)
|
||||
if id == 0 {
|
||||
global.GVA_LOG.Error("获取用户ID失败")
|
||||
r.FailWithMessage("获取用户ID失败", context)
|
||||
return
|
||||
}
|
||||
|
||||
status, err := appUserService.GetTeacherApplyStatus(id)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取教师申请状态失败", zap.Error(err))
|
||||
r.FailWithMessage("获取教师申请状态失败", context)
|
||||
return
|
||||
}
|
||||
|
||||
r.OkWithDetailed(status, "获取教师申请状态成功", context)
|
||||
}
|
||||
|
||||
// BindWechat 手机登录用户绑定微信
|
||||
func (a *AppUserApi) BindWechat(context *gin.Context) {
|
||||
var p request.BindWechatReq
|
||||
if err := context.ShouldBind(&p); err != nil {
|
||||
global.GVA_LOG.Error("绑定微信请求参数有误")
|
||||
r.FailWithMessage("绑定微信请求参数有误", context)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := appUserService.BindWechat(p)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("绑定微信失败", zap.Error(err))
|
||||
}
|
||||
|
||||
// 生成新token
|
||||
token, claims, err := user_jwt.LoginToken(*user)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取token失败!", zap.Error(err))
|
||||
r.FailWithMessage("获取token失败", context)
|
||||
return
|
||||
}
|
||||
|
||||
// 此处过期时间等于jwt过期时间
|
||||
dr, err := utils.ParseDuration(global.GVA_CONFIG.JWT.ExpiresTime)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("解析JWT过期时间失败", zap.Error(err))
|
||||
r.FailWithMessage("解析JWT过期时间失败", context)
|
||||
return
|
||||
}
|
||||
|
||||
timer := dr
|
||||
if err := global.GVA_REDIS.Set(context, user.Phone, token, timer).Err(); err != nil {
|
||||
global.GVA_LOG.Error("设置登录状态失败!", zap.Error(err))
|
||||
r.FailWithMessage("设置登录状态失败", context)
|
||||
return
|
||||
}
|
||||
|
||||
user_jwt.SetToken(context, token, int(claims.RegisteredClaims.ExpiresAt.Unix()-time.Now().Unix()))
|
||||
r.OkWithDetailed(gin.H{
|
||||
"User": user,
|
||||
"Token": token,
|
||||
"ExpiresAt": claims.RegisteredClaims.ExpiresAt.Unix() * 1000,
|
||||
}, "绑定微信成功", context)
|
||||
}
|
||||
|
||||
// BindPhone 微信登录用户绑定手机号s
|
||||
func (a *AppUserApi) BindPhone(context *gin.Context) {
|
||||
var p request.BindPhoneReq
|
||||
if err := context.ShouldBind(&p); err != nil {
|
||||
global.GVA_LOG.Error("绑定手机号请求参数有误")
|
||||
r.FailWithMessage("绑定手机号请求参数有误", context)
|
||||
return
|
||||
}
|
||||
|
||||
// 验证码检查
|
||||
if result, _ := global.GVA_REDIS.Get(context, fmt.Sprintf("VerifyCode:%s", p.Phone)).Result(); result != p.Code {
|
||||
global.GVA_LOG.Error("验证码错误", zap.String("phone", p.Phone))
|
||||
r.FailWithMessage("验证码错误", context)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := appUserService.BindPhone(p)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("绑定手机号失败", zap.Error(err))
|
||||
r.FailWithMessage("绑定手机号失败", context)
|
||||
return
|
||||
}
|
||||
|
||||
r.OkWithDetailed(user, "绑定手机号成功", context)
|
||||
}
|
@@ -5,6 +5,7 @@ import (
|
||||
"git.echol.cn/loser/lckt/model/article"
|
||||
"git.echol.cn/loser/lckt/model/article/request"
|
||||
r "git.echol.cn/loser/lckt/model/common/response"
|
||||
"git.echol.cn/loser/lckt/utils/user_jwt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
@@ -88,3 +89,37 @@ func (ArticleApi) ById(ctx *gin.Context) {
|
||||
}
|
||||
r.OkWithData(article, ctx)
|
||||
}
|
||||
|
||||
func (ArticleApi) APPGetList(ctx *gin.Context) {
|
||||
var p request.GetList
|
||||
if err := ctx.ShouldBind(&p); err != nil {
|
||||
r.FailWithMessage(err.Error(), ctx)
|
||||
global.GVA_LOG.Error("参数有误!", zap.Error(err))
|
||||
return
|
||||
}
|
||||
list, total, err := articleService.APPGetArticleList(p)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询失败!", zap.Error(err))
|
||||
r.FailWithMessage("查询失败:"+err.Error(), ctx)
|
||||
return
|
||||
}
|
||||
r.OkWithDetailed(gin.H{"list": list, "total": total}, "查询成功", ctx)
|
||||
}
|
||||
|
||||
// AppById 根据ID获取文章
|
||||
func (ArticleApi) AppById(ctx *gin.Context) {
|
||||
id := ctx.Param("id")
|
||||
if id == "" {
|
||||
r.FailWithMessage("参数错误", ctx)
|
||||
return
|
||||
}
|
||||
|
||||
userId := user_jwt.GetUserID(ctx)
|
||||
article, err := articleService.APPGetArticle(id, userId)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询失败!", zap.Error(err))
|
||||
r.FailWithMessage("查询失败:"+err.Error(), ctx)
|
||||
return
|
||||
}
|
||||
r.OkWithData(article, ctx)
|
||||
}
|
||||
|
@@ -186,3 +186,13 @@ func (catApi *CategoryApi) GetCategoryPublic(c *gin.Context) {
|
||||
"info": "不需要鉴权的类别接口信息",
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
func (catApi *CategoryApi) GetCategoryListPublic(context *gin.Context) {
|
||||
list, err := catService.GetIndexCategoryList()
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取类别列表失败!", zap.Error(err))
|
||||
response.FailWithMessage("获取类别列表失败:"+err.Error(), context)
|
||||
return
|
||||
}
|
||||
response.OkWithData(list, context)
|
||||
}
|
||||
|
@@ -1,10 +1,12 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/lckt/api/v1/app"
|
||||
"git.echol.cn/loser/lckt/api/v1/article"
|
||||
"git.echol.cn/loser/lckt/api/v1/bot"
|
||||
"git.echol.cn/loser/lckt/api/v1/category"
|
||||
"git.echol.cn/loser/lckt/api/v1/example"
|
||||
"git.echol.cn/loser/lckt/api/v1/notice"
|
||||
"git.echol.cn/loser/lckt/api/v1/system"
|
||||
"git.echol.cn/loser/lckt/api/v1/user"
|
||||
"git.echol.cn/loser/lckt/api/v1/vip"
|
||||
@@ -15,9 +17,11 @@ var ApiGroupApp = new(ApiGroup)
|
||||
type ApiGroup struct {
|
||||
SystemApiGroup system.ApiGroup
|
||||
ExampleApiGroup example.ApiGroup
|
||||
AppApiGroup app.ApiGroup
|
||||
CategoryApiGroup category.ApiGroup
|
||||
BotApiGroup bot.ApiGroup
|
||||
ArticleApiGroup article.ApiGroup
|
||||
UserApiGroup user.APPUserApi
|
||||
UserApiGroup user.UserApi
|
||||
VipApiGroup vip.ApiGroup
|
||||
NoticeApiGroup notice.ApiGroup
|
||||
}
|
||||
|
12
api/v1/notice/enter.go
Normal file
12
api/v1/notice/enter.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package notice
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/lckt/service"
|
||||
)
|
||||
|
||||
type ApiGroup struct{ NoticeApi }
|
||||
|
||||
var (
|
||||
noticeService = service.ServiceGroupApp.NoticeServiceGroup.NoticeService
|
||||
notService = service.ServiceGroupApp.NoticeServiceGroup.NoticeService
|
||||
)
|
188
api/v1/notice/notice.go
Normal file
188
api/v1/notice/notice.go
Normal file
@@ -0,0 +1,188 @@
|
||||
package notice
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/lckt/global"
|
||||
"git.echol.cn/loser/lckt/model/common/response"
|
||||
"git.echol.cn/loser/lckt/model/notice"
|
||||
noticeReq "git.echol.cn/loser/lckt/model/notice/request"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type NoticeApi struct{}
|
||||
|
||||
// CreateNotice 创建通知
|
||||
// @Tags Notice
|
||||
// @Summary 创建通知
|
||||
// @Security ApiKeyAuth
|
||||
// @Accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body notice.Notice true "创建通知"
|
||||
// @Success 200 {object} response.Response{msg=string} "创建成功"
|
||||
// @Router /not/createNotice [post]
|
||||
func (notApi *NoticeApi) CreateNotice(c *gin.Context) {
|
||||
// 创建业务用Context
|
||||
ctx := c.Request.Context()
|
||||
|
||||
var not notice.Notice
|
||||
err := c.ShouldBindJSON(¬)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
err = notService.CreateNotice(ctx, ¬)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("创建失败!", zap.Error(err))
|
||||
response.FailWithMessage("创建失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("创建成功", c)
|
||||
}
|
||||
|
||||
// DeleteNotice 删除通知
|
||||
// @Tags Notice
|
||||
// @Summary 删除通知
|
||||
// @Security ApiKeyAuth
|
||||
// @Accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body notice.Notice true "删除通知"
|
||||
// @Success 200 {object} response.Response{msg=string} "删除成功"
|
||||
// @Router /not/deleteNotice [delete]
|
||||
func (notApi *NoticeApi) DeleteNotice(c *gin.Context) {
|
||||
// 创建业务用Context
|
||||
ctx := c.Request.Context()
|
||||
|
||||
ID := c.Query("ID")
|
||||
err := notService.DeleteNotice(ctx, ID)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("删除失败!", zap.Error(err))
|
||||
response.FailWithMessage("删除失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("删除成功", c)
|
||||
}
|
||||
|
||||
// DeleteNoticeByIds 批量删除通知
|
||||
// @Tags Notice
|
||||
// @Summary 批量删除通知
|
||||
// @Security ApiKeyAuth
|
||||
// @Accept application/json
|
||||
// @Produce application/json
|
||||
// @Success 200 {object} response.Response{msg=string} "批量删除成功"
|
||||
// @Router /not/deleteNoticeByIds [delete]
|
||||
func (notApi *NoticeApi) DeleteNoticeByIds(c *gin.Context) {
|
||||
// 创建业务用Context
|
||||
ctx := c.Request.Context()
|
||||
|
||||
IDs := c.QueryArray("IDs[]")
|
||||
err := notService.DeleteNoticeByIds(ctx, IDs)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("批量删除失败!", zap.Error(err))
|
||||
response.FailWithMessage("批量删除失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("批量删除成功", c)
|
||||
}
|
||||
|
||||
// UpdateNotice 更新通知
|
||||
// @Tags Notice
|
||||
// @Summary 更新通知
|
||||
// @Security ApiKeyAuth
|
||||
// @Accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body notice.Notice true "更新通知"
|
||||
// @Success 200 {object} response.Response{msg=string} "更新成功"
|
||||
// @Router /not/updateNotice [put]
|
||||
func (notApi *NoticeApi) UpdateNotice(c *gin.Context) {
|
||||
// 从ctx获取标准context进行业务行为
|
||||
ctx := c.Request.Context()
|
||||
|
||||
var not notice.Notice
|
||||
err := c.ShouldBindJSON(¬)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
err = notService.UpdateNotice(ctx, not)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("更新失败!", zap.Error(err))
|
||||
response.FailWithMessage("更新失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("更新成功", c)
|
||||
}
|
||||
|
||||
// FindNotice 用id查询通知
|
||||
// @Tags Notice
|
||||
// @Summary 用id查询通知
|
||||
// @Security ApiKeyAuth
|
||||
// @Accept application/json
|
||||
// @Produce application/json
|
||||
// @Param ID query uint true "用id查询通知"
|
||||
// @Success 200 {object} response.Response{data=notice.Notice,msg=string} "查询成功"
|
||||
// @Router /not/findNotice [get]
|
||||
func (notApi *NoticeApi) FindNotice(c *gin.Context) {
|
||||
// 创建业务用Context
|
||||
ctx := c.Request.Context()
|
||||
|
||||
ID := c.Query("ID")
|
||||
renot, err := notService.GetNotice(ctx, ID)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询失败!", zap.Error(err))
|
||||
response.FailWithMessage("查询失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithData(renot, c)
|
||||
}
|
||||
|
||||
// GetNoticeList 分页获取通知列表
|
||||
// @Tags Notice
|
||||
// @Summary 分页获取通知列表
|
||||
// @Security ApiKeyAuth
|
||||
// @Accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data query noticeReq.NoticeSearch true "分页获取通知列表"
|
||||
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "获取成功"
|
||||
// @Router /not/getNoticeList [get]
|
||||
func (notApi *NoticeApi) GetNoticeList(c *gin.Context) {
|
||||
// 创建业务用Context
|
||||
ctx := c.Request.Context()
|
||||
|
||||
var pageInfo noticeReq.NoticeSearch
|
||||
err := c.ShouldBindQuery(&pageInfo)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
list, total, err := notService.GetNoticeInfoList(ctx, pageInfo)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||
response.FailWithMessage("获取失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(response.PageResult{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: pageInfo.Page,
|
||||
PageSize: pageInfo.PageSize,
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
// GetNoticePublic 不需要鉴权的通知接口
|
||||
// @Tags Notice
|
||||
// @Summary 不需要鉴权的通知接口
|
||||
// @Accept application/json
|
||||
// @Produce application/json
|
||||
// @Success 200 {object} response.Response{data=object,msg=string} "获取成功"
|
||||
// @Router /not/getNoticePublic [get]
|
||||
func (notApi *NoticeApi) GetNoticePublic(c *gin.Context) {
|
||||
// 创建业务用Context
|
||||
ctx := c.Request.Context()
|
||||
|
||||
// 此接口不需要鉴权
|
||||
// 示例为返回了一个固定的消息接口,一般本接口用于C端服务,需要自己实现业务逻辑
|
||||
notService.GetNoticePublic(ctx)
|
||||
response.OkWithDetailed(gin.H{
|
||||
"info": "不需要鉴权的通知接口信息",
|
||||
}, "获取成功", c)
|
||||
}
|
@@ -2,6 +2,6 @@ package user
|
||||
|
||||
import "git.echol.cn/loser/lckt/service"
|
||||
|
||||
type ApiGroup struct{ APPUserApi }
|
||||
type ApiGroup struct{ UserApi }
|
||||
|
||||
var userService = service.ServiceGroupApp.UserServiceGroup.UserService
|
||||
|
@@ -1,141 +1,19 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"git.echol.cn/loser/lckt/global"
|
||||
"git.echol.cn/loser/lckt/model/app"
|
||||
common "git.echol.cn/loser/lckt/model/common/request"
|
||||
r "git.echol.cn/loser/lckt/model/common/response"
|
||||
"git.echol.cn/loser/lckt/model/user/request"
|
||||
"git.echol.cn/loser/lckt/utils"
|
||||
"git.echol.cn/loser/lckt/utils/user_jwt"
|
||||
"git.echol.cn/loser/lckt/utils/wechat"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"go.uber.org/zap"
|
||||
"time"
|
||||
)
|
||||
|
||||
type APPUserApi struct{}
|
||||
|
||||
// SendCode 发送验证码
|
||||
func (*APPUserApi) SendCode(ctx *gin.Context) {
|
||||
var p request.SendCodeReq
|
||||
if err := ctx.ShouldBind(&p); err != nil {
|
||||
r.FailWithMessage(err.Error(), ctx)
|
||||
global.GVA_LOG.Error("参数错误,发送验证码失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
if err := userService.SendCode(p); err != nil {
|
||||
r.FailWithMessage("发送验证码失败", ctx)
|
||||
return
|
||||
}
|
||||
|
||||
r.OkWithMessage("发送验证码成功", ctx)
|
||||
}
|
||||
|
||||
// Login 用户登录
|
||||
func (*APPUserApi) Login(ctx *gin.Context) {
|
||||
var p request.CodeLoginReq
|
||||
if err := ctx.ShouldBind(&p); err != nil {
|
||||
r.FailWithMessage(err.Error(), ctx)
|
||||
global.GVA_LOG.Error("参数错误,登录失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
if result, _ := global.GVA_REDIS.Get(ctx, fmt.Sprintf("VerifyCode:%s", p.Phone)).Result(); result != p.Code {
|
||||
global.GVA_LOG.Error("验证码错误", zap.String("phone", p.Phone))
|
||||
r.FailWithMessage("验证码错误", ctx)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := userService.Login(p)
|
||||
if err != nil {
|
||||
r.FailWithMessage("登录失败", ctx)
|
||||
return
|
||||
}
|
||||
|
||||
// 生成token
|
||||
token, claims, err := user_jwt.LoginToken(user)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取token失败!", zap.Error(err))
|
||||
r.FailWithMessage("获取token失败", ctx)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = global.GVA_REDIS.Get(ctx, user.Phone).Result(); err == redis.Nil {
|
||||
// 此处过期时间等于jwt过期时间
|
||||
dr, err := utils.ParseDuration(global.GVA_CONFIG.JWT.ExpiresTime)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
timer := dr
|
||||
if err := global.GVA_REDIS.Set(ctx, user.Phone, token, timer).Err(); err != nil {
|
||||
global.GVA_LOG.Error("设置登录状态失败!", zap.Error(err))
|
||||
r.FailWithMessage("设置登录状态失败", ctx)
|
||||
return
|
||||
}
|
||||
user_jwt.SetToken(ctx, token, int(claims.RegisteredClaims.ExpiresAt.Unix()-time.Now().Unix()))
|
||||
r.OkWithDetailed(gin.H{
|
||||
"User": user,
|
||||
"Token": token,
|
||||
"ExpiresAt": claims.RegisteredClaims.ExpiresAt.Unix() * 1000,
|
||||
}, "登录成功", ctx)
|
||||
} else if err != nil {
|
||||
global.GVA_LOG.Error("设置登录状态失败!", zap.Error(err))
|
||||
r.FailWithMessage("设置登录状态失败", ctx)
|
||||
}
|
||||
}
|
||||
|
||||
// WechatLogin 微信登录
|
||||
func (*APPUserApi) WechatLogin(ctx *gin.Context) {
|
||||
var p request.CodeLoginReq
|
||||
if err := ctx.ShouldBind(&p); err != nil {
|
||||
r.FailWithMessage(err.Error(), ctx)
|
||||
global.GVA_LOG.Error("参数错误,登录失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
//Todo 待完善微信登录
|
||||
info := wechat.GetUserInfo(p.Code)
|
||||
if info == nil {
|
||||
r.FailWithMessage("获取用户信息失败", ctx)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := userService.WechatLogin(info)
|
||||
if err != nil {
|
||||
r.FailWithMessage("登录失败", ctx)
|
||||
return
|
||||
}
|
||||
// 生成token
|
||||
token, claims, err := user_jwt.LoginToken(user)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取token失败!", zap.Error(err))
|
||||
r.FailWithMessage("获取token失败", ctx)
|
||||
return
|
||||
}
|
||||
if _, err = global.GVA_REDIS.Get(ctx, user.Phone).Result(); err == redis.Nil {
|
||||
// 此处过期时间等于jwt过期时间
|
||||
dr, err := utils.ParseDuration(global.GVA_CONFIG.JWT.ExpiresTime)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
timer := dr
|
||||
if err := global.GVA_REDIS.Set(ctx, user.Phone, token, timer).Err(); err != nil {
|
||||
global.GVA_LOG.Error("设置登录状态失败!", zap.Error(err))
|
||||
r.FailWithMessage("设置登录状态失败", ctx)
|
||||
return
|
||||
}
|
||||
user_jwt.SetToken(ctx, token, int(claims.RegisteredClaims.ExpiresAt.Unix()-time.Now().Unix()))
|
||||
r.OkWithDetailed(gin.H{
|
||||
"User": user,
|
||||
"Token": token,
|
||||
"ExpiresAt": claims.RegisteredClaims.ExpiresAt.Unix() * 1000,
|
||||
}, "登录成功", ctx)
|
||||
}
|
||||
}
|
||||
type UserApi struct{}
|
||||
|
||||
// GetUserList 获取用户列表
|
||||
func (*APPUserApi) GetUserList(ctx *gin.Context) {
|
||||
func (*UserApi) GetUserList(ctx *gin.Context) {
|
||||
var p request.GetUserListReq
|
||||
if err := ctx.ShouldBind(&p); err != nil {
|
||||
r.FailWithMessage(err.Error(), ctx)
|
||||
@@ -156,7 +34,7 @@ func (*APPUserApi) GetUserList(ctx *gin.Context) {
|
||||
}
|
||||
|
||||
// SetBalance 设置用户余额
|
||||
func (*APPUserApi) SetBalance(ctx *gin.Context) {
|
||||
func (*UserApi) SetBalance(ctx *gin.Context) {
|
||||
var p request.SetBalanceReq
|
||||
if err := ctx.ShouldBind(&p); err != nil {
|
||||
r.FailWithMessage(err.Error(), ctx)
|
||||
@@ -170,50 +48,8 @@ func (*APPUserApi) SetBalance(ctx *gin.Context) {
|
||||
r.OkWithMessage("设置用户余额成功", ctx)
|
||||
}
|
||||
|
||||
// PwdLogin 密码登录
|
||||
func (*APPUserApi) PwdLogin(ctx *gin.Context) {
|
||||
var p request.PwdLoginReq
|
||||
if err := ctx.ShouldBind(&p); err != nil {
|
||||
r.FailWithMessage(err.Error(), ctx)
|
||||
global.GVA_LOG.Error("参数错误,登录失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
user, err := userService.PwdLogin(p)
|
||||
if err != nil {
|
||||
r.FailWithMessage("手机号或密码错误!", ctx)
|
||||
return
|
||||
}
|
||||
|
||||
// 生成token
|
||||
token, claims, err := user_jwt.LoginToken(user)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取token失败!", zap.Error(err))
|
||||
r.FailWithMessage("获取token失败", ctx)
|
||||
return
|
||||
}
|
||||
|
||||
// 此处过期时间等于jwt过期时间
|
||||
dr, err := utils.ParseDuration(global.GVA_CONFIG.JWT.ExpiresTime)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
timer := dr
|
||||
if err := global.GVA_REDIS.Set(ctx, user.Phone, token, timer).Err(); err != nil {
|
||||
global.GVA_LOG.Error("设置登录状态失败!", zap.Error(err))
|
||||
r.FailWithMessage("设置登录状态失败", ctx)
|
||||
return
|
||||
}
|
||||
user_jwt.SetToken(ctx, token, int(claims.RegisteredClaims.ExpiresAt.Unix()-time.Now().Unix()))
|
||||
r.OkWithDetailed(gin.H{
|
||||
"User": user,
|
||||
"Token": token,
|
||||
"ExpiresAt": claims.RegisteredClaims.ExpiresAt.Unix() * 1000,
|
||||
}, "登录成功", ctx)
|
||||
}
|
||||
|
||||
// Register 注册-后台用
|
||||
func (*APPUserApi) Register(ctx *gin.Context) {
|
||||
func (*UserApi) Register(ctx *gin.Context) {
|
||||
var p request.RegisterReq
|
||||
if err := ctx.ShouldBind(&p); err != nil {
|
||||
r.FailWithMessage(err.Error(), ctx)
|
||||
@@ -230,7 +66,7 @@ func (*APPUserApi) Register(ctx *gin.Context) {
|
||||
}
|
||||
|
||||
// SetUserStatus 设置用户状态
|
||||
func (*APPUserApi) SetUserStatus(ctx *gin.Context) {
|
||||
func (*UserApi) SetUserStatus(ctx *gin.Context) {
|
||||
id := ctx.Param("id")
|
||||
if id == "" {
|
||||
r.FailWithMessage("参数错误", ctx)
|
||||
@@ -245,7 +81,7 @@ func (*APPUserApi) SetUserStatus(ctx *gin.Context) {
|
||||
}
|
||||
|
||||
// GetUserById 根据id获取用户信息
|
||||
func (*APPUserApi) GetUserById(ctx *gin.Context) {
|
||||
func (*UserApi) GetUserById(ctx *gin.Context) {
|
||||
id := ctx.Param("id")
|
||||
if id == "" {
|
||||
r.FailWithMessage("参数错误", ctx)
|
||||
@@ -258,3 +94,64 @@ func (*APPUserApi) GetUserById(ctx *gin.Context) {
|
||||
}
|
||||
r.OkWithDetailed(user, "获取用户信息成功", ctx)
|
||||
}
|
||||
|
||||
func (a *UserApi) GetTeachers(context *gin.Context) {
|
||||
var p common.PageInfo
|
||||
if err := context.ShouldBind(&p); err != nil {
|
||||
r.FailWithMessage(err.Error(), context)
|
||||
global.GVA_LOG.Error("参数错误,获取教师列表失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
teachers, total, err := userService.GetTeachers(p)
|
||||
if err != nil {
|
||||
r.FailWithMessage("获取教师列表失败", context)
|
||||
return
|
||||
}
|
||||
r.OkWithDetailed(r.PageResult{
|
||||
List: teachers,
|
||||
Total: total,
|
||||
Page: p.Page,
|
||||
PageSize: p.PageSize,
|
||||
}, "讲师列表获取成功", context)
|
||||
}
|
||||
|
||||
// GetTeacherApplyList 获取教师申请列表
|
||||
func (a *UserApi) GetTeacherApplyList(context *gin.Context) {
|
||||
var p request.GetTeacherApplyListReq
|
||||
if err := context.ShouldBind(&p); err != nil {
|
||||
r.FailWithMessage(err.Error(), context)
|
||||
global.GVA_LOG.Error("参数错误,获取教师申请列表失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
list, total, err := userService.GetTeacherApplyList(p)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取教师申请列表失败", zap.Error(err))
|
||||
r.FailWithMessage("获取教师申请列表失败", context)
|
||||
return
|
||||
}
|
||||
r.OkWithDetailed(
|
||||
r.PageResult{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: p.Page,
|
||||
PageSize: p.PageSize,
|
||||
}, "获取教师申请列表成功", context)
|
||||
}
|
||||
|
||||
// UpdateTeacherApplyStatus 更新教师申请状态
|
||||
func (a *UserApi) UpdateTeacherApplyStatus(context *gin.Context) {
|
||||
var p app.TeacherApply
|
||||
if err := context.ShouldBind(&p); err != nil {
|
||||
r.FailWithMessage(err.Error(), context)
|
||||
global.GVA_LOG.Error("参数错误,更新教师申请状态失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
if err := userService.UpdateTeacherApplyStatus(p); err != nil {
|
||||
r.FailWithMessage("更新教师申请状态失败", context)
|
||||
return
|
||||
}
|
||||
r.OkWithMessage("更新教师申请状态成功", context)
|
||||
}
|
||||
|
42
config.yaml
42
config.yaml
@@ -1,14 +1,14 @@
|
||||
aliyun-oss:
|
||||
endpoint: yourEndpoint
|
||||
access-key-id: yourAccessKeyId
|
||||
access-key-secret: yourAccessKeySecret
|
||||
bucket-name: yourBucketName
|
||||
bucket-url: yourBucketUrl
|
||||
base-path: yourBasePath
|
||||
endpoint: oss-cn-hangzhou.aliyuncs.com
|
||||
access-key-id: LTAI5tB3Mn5Y7mVo8h3zkf46
|
||||
access-key-secret: FtuHdFy4NFdVItEiNBnTun3Ddi8BMK
|
||||
bucket-name: lckt
|
||||
bucket-url: https://lckt.oss-cn-hangzhou.aliyuncs.com
|
||||
base-path: lckt
|
||||
autocode:
|
||||
web: web/src
|
||||
root: C:\Users\Administrator\Desktop\lckt
|
||||
server: server
|
||||
root: C:\Users\Administrator\GolandProjects\zb
|
||||
server: lckt-server
|
||||
module: git.echol.cn/loser/lckt
|
||||
ai-path: "https://ai.gin-vue-admin.com/{FUNC}/loser7659/c178e970-6a59-497d-96ed-86fee6b3285a"
|
||||
aws-s3:
|
||||
@@ -129,18 +129,18 @@ mssql:
|
||||
log-zap: false
|
||||
mysql:
|
||||
prefix: ""
|
||||
port: "3307"
|
||||
port: "3366"
|
||||
config: charset=utf8mb4&parseTime=True&loc=Local
|
||||
db-name: lckt
|
||||
username: lckt
|
||||
password: loser765911
|
||||
path: 47.120.70.120
|
||||
password: loser765911.
|
||||
path: 219.152.55.29
|
||||
engine: ""
|
||||
log-mode: error
|
||||
log-mode: info
|
||||
max-idle-conns: 10
|
||||
max-open-conns: 100
|
||||
singular: false
|
||||
log-zap: false
|
||||
log-zap: true
|
||||
oracle:
|
||||
prefix: ""
|
||||
port: ""
|
||||
@@ -213,7 +213,7 @@ sqlite:
|
||||
log-zap: false
|
||||
system:
|
||||
db-type: mysql
|
||||
oss-type: local
|
||||
oss-type: aliyun-oss
|
||||
router-prefix: ""
|
||||
addr: 8888
|
||||
iplimit-count: 15000
|
||||
@@ -242,20 +242,20 @@ zap:
|
||||
|
||||
wechat:
|
||||
app-id: wx3d21df18d7f8f9fc
|
||||
app-secret: ffce59a9a9272c1aaee53950e96617d8
|
||||
app-secret: 3ab19e9b6a5e155c25ac6457be650047
|
||||
token: kjeldcsdz2phfwfxnevsajnzsboho1ev
|
||||
aes-key: PiqqlGdEblw5Gv1RJ5qcTnhKUjFw9YNkBMAX6CIw6Me
|
||||
callback: https://api.gin-vue-admin.com/wechat/callback
|
||||
pay-list:
|
||||
- type: wxpay
|
||||
alias-name: wxpay-1
|
||||
app-id: 1
|
||||
mch-id: 1
|
||||
app-id: wx3d21df18d7f8f9fc
|
||||
mch-id: 1646874753
|
||||
v3-key: 1a3sd8561d5179Df152D4789aD38wG9s
|
||||
cert-path: 1
|
||||
key-path: 1
|
||||
notify-url: 1
|
||||
serial-no: 1
|
||||
cert-path: /resource/cert/apiclient_cert.pem
|
||||
key-path: /resource/cert/apiclient_key.pem
|
||||
notify-url: http://lckt.hnlc5588.cn/app_order/notify
|
||||
serial-no: 59A891FB403EC7A1CF2090DB9C8EC704BD43B101
|
||||
- type: wxpay2
|
||||
alias-name: wxpay-2
|
||||
app-id: 2
|
||||
|
49
go.mod
49
go.mod
@@ -1,13 +1,15 @@
|
||||
module git.echol.cn/loser/lckt
|
||||
|
||||
go 1.23
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.23.2
|
||||
|
||||
require (
|
||||
github.com/ArtisanCloud/PowerWeChat/v3 v3.4.6
|
||||
github.com/ArtisanCloud/PowerSocialite/v3 v3.0.8
|
||||
github.com/ArtisanCloud/PowerWeChat/v3 v3.4.21
|
||||
github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.7
|
||||
github.com/alibabacloud-go/dysmsapi-20170525/v4 v4.1.2
|
||||
github.com/alibabacloud-go/tea v1.3.8
|
||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.7
|
||||
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
|
||||
github.com/aws/aws-sdk-go v1.55.6
|
||||
@@ -15,13 +17,16 @@ require (
|
||||
github.com/casbin/gorm-adapter/v3 v3.32.0
|
||||
github.com/fsnotify/fsnotify v1.8.0
|
||||
github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6
|
||||
github.com/gin-gonic/gin v1.10.0
|
||||
github.com/gin-contrib/cors v1.7.6
|
||||
github.com/gin-gonic/gin v1.10.1
|
||||
github.com/glebarez/sqlite v1.11.0
|
||||
github.com/go-sql-driver/mysql v1.8.1
|
||||
github.com/goccy/go-json v0.10.4
|
||||
github.com/goccy/go-json v0.10.5
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gookit/color v1.5.4
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/huaweicloud/huaweicloud-sdk-go-obs v3.24.9+incompatible
|
||||
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible
|
||||
github.com/mholt/archiver/v4 v4.0.0-alpha.9
|
||||
@@ -46,9 +51,9 @@ require (
|
||||
go.mongodb.org/mongo-driver v1.17.2
|
||||
go.uber.org/automaxprocs v1.6.0
|
||||
go.uber.org/zap v1.27.0
|
||||
golang.org/x/crypto v0.32.0
|
||||
golang.org/x/sync v0.10.0
|
||||
golang.org/x/text v0.21.0
|
||||
golang.org/x/crypto v0.39.0
|
||||
golang.org/x/sync v0.15.0
|
||||
golang.org/x/text v0.26.0
|
||||
gorm.io/datatypes v1.2.5
|
||||
gorm.io/driver/mysql v1.5.7
|
||||
gorm.io/driver/postgres v1.5.11
|
||||
@@ -60,7 +65,6 @@ require (
|
||||
require (
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/ArtisanCloud/PowerLibs/v3 v3.3.2 // indirect
|
||||
github.com/ArtisanCloud/PowerSocialite/v3 v3.0.7 // indirect
|
||||
github.com/BurntSushi/toml v1.4.0 // indirect
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/STARRY-S/zip v0.1.0 // indirect
|
||||
@@ -69,15 +73,14 @@ require (
|
||||
github.com/alibabacloud-go/debug v1.0.1 // indirect
|
||||
github.com/alibabacloud-go/endpoint-util v1.1.0 // indirect
|
||||
github.com/alibabacloud-go/openapi-util v0.1.1 // indirect
|
||||
github.com/alibabacloud-go/tea v1.3.8 // indirect
|
||||
github.com/aliyun/credentials-go v1.4.5 // indirect
|
||||
github.com/andybalholm/brotli v1.1.1 // indirect
|
||||
github.com/bmatcuk/doublestar/v4 v4.8.0 // indirect
|
||||
github.com/bodgit/plumbing v1.3.0 // indirect
|
||||
github.com/bodgit/sevenzip v1.6.0 // indirect
|
||||
github.com/bodgit/windows v1.0.1 // indirect
|
||||
github.com/bytedance/sonic v1.12.7 // indirect
|
||||
github.com/bytedance/sonic/loader v0.2.3 // indirect
|
||||
github.com/bytedance/sonic v1.13.3 // indirect
|
||||
github.com/bytedance/sonic/loader v0.2.4 // indirect
|
||||
github.com/casbin/govaluate v1.3.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/clbanning/mxj v1.8.4 // indirect
|
||||
@@ -87,9 +90,9 @@ require (
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
|
||||
github.com/gammazero/toposort v0.1.1 // indirect
|
||||
github.com/gin-contrib/sse v1.0.0 // indirect
|
||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||
github.com/glebarez/go-sqlite v1.22.0 // indirect
|
||||
github.com/go-ini/ini v1.67.0 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
@@ -99,7 +102,7 @@ require (
|
||||
github.com/go-openapi/swag v0.23.0 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.24.0 // indirect
|
||||
github.com/go-playground/validator/v10 v10.26.0 // indirect
|
||||
github.com/gofrs/flock v0.12.1 // indirect
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
||||
github.com/golang-sql/sqlexp v0.1.0 // indirect
|
||||
@@ -120,7 +123,7 @@ require (
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.17.11 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||
github.com/klauspost/pgzip v1.2.6 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect
|
||||
@@ -139,7 +142,7 @@ require (
|
||||
github.com/nwaples/rardecode/v2 v2.0.1 // indirect
|
||||
github.com/otiai10/mint v1.6.3 // indirect
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.22 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||
@@ -161,7 +164,7 @@ require (
|
||||
github.com/tklauser/go-sysconf v0.3.14 // indirect
|
||||
github.com/tklauser/numcpus v0.9.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
github.com/ugorji/go/codec v1.3.0 // indirect
|
||||
github.com/ulikunitz/xz v0.5.12 // indirect
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||
github.com/xdg-go/scram v1.1.2 // indirect
|
||||
@@ -175,15 +178,15 @@ require (
|
||||
go.opentelemetry.io/otel/trace v1.29.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
|
||||
golang.org/x/arch v0.13.0 // indirect
|
||||
golang.org/x/arch v0.18.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect
|
||||
golang.org/x/image v0.23.0 // indirect
|
||||
golang.org/x/mod v0.22.0 // indirect
|
||||
golang.org/x/net v0.34.0 // indirect
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
golang.org/x/mod v0.25.0 // indirect
|
||||
golang.org/x/net v0.41.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/time v0.9.0 // indirect
|
||||
golang.org/x/tools v0.29.0 // indirect
|
||||
google.golang.org/protobuf v1.36.3 // indirect
|
||||
golang.org/x/tools v0.33.0 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
|
91
go.sum
91
go.sum
@@ -19,10 +19,10 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/ArtisanCloud/PowerLibs/v3 v3.3.2 h1:IInr1YWwkhwOykxDqux1Goym0uFhrYwBjmgLnEwCLqs=
|
||||
github.com/ArtisanCloud/PowerLibs/v3 v3.3.2/go.mod h1:xFGsskCnzAu+6rFEJbGVAlwhrwZPXAny6m7j71S/B5k=
|
||||
github.com/ArtisanCloud/PowerSocialite/v3 v3.0.7 h1:P+erNlErr+X2v7Et+yTWaTfIRhw+HfpAPdvNIEwk9Gw=
|
||||
github.com/ArtisanCloud/PowerSocialite/v3 v3.0.7/go.mod h1:VZQNCvcK/rldF3QaExiSl1gJEAkyc5/I8RLOd3WFZq4=
|
||||
github.com/ArtisanCloud/PowerWeChat/v3 v3.4.6 h1:IOGdt8Lt8EDRl9qBTnX2TxU0Bjx7gdK9Qy+IegyHltY=
|
||||
github.com/ArtisanCloud/PowerWeChat/v3 v3.4.6/go.mod h1:ybM3u4Lhso0X+ZsgoRCF4e5W1KT2fBc6plpjPZ2fop4=
|
||||
github.com/ArtisanCloud/PowerSocialite/v3 v3.0.8 h1:0v/CMFzz5/0K9mEMebyBzlmap1tidv2PaUFSnq/bJhk=
|
||||
github.com/ArtisanCloud/PowerSocialite/v3 v3.0.8/go.mod h1:VZQNCvcK/rldF3QaExiSl1gJEAkyc5/I8RLOd3WFZq4=
|
||||
github.com/ArtisanCloud/PowerWeChat/v3 v3.4.21 h1:ZDLWTLGveYWpCL0wUHF+D8imEo783ux0OFCwczNvgAU=
|
||||
github.com/ArtisanCloud/PowerWeChat/v3 v3.4.21/go.mod h1:boWl2cwbgXt1AbrYTWMXs9Ebby6ecbJ1CyNVRaNVqUY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.1/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1/go.mod h1:RKUqNu35KJYcVG/fqTRqmuXJZYNhYkBrnC/hX7yGbTA=
|
||||
@@ -125,11 +125,11 @@ github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/bytedance/sonic v1.12.7 h1:CQU8pxOy9HToxhndH0Kx/S1qU/CuS9GnKYrGioDcU1Q=
|
||||
github.com/bytedance/sonic v1.12.7/go.mod h1:tnbal4mxOMju17EGfknm2XyYcpyCnIROYOEYuemj13I=
|
||||
github.com/bytedance/sonic v1.13.3 h1:MS8gmaH16Gtirygw7jV91pDCN33NyMrPbN7qiYhEsF0=
|
||||
github.com/bytedance/sonic v1.13.3/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/bytedance/sonic/loader v0.2.3 h1:yctD0Q3v2NOGfSWPLPvG2ggA2kV6TS6s4wioyEqssH0=
|
||||
github.com/bytedance/sonic/loader v0.2.3/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
|
||||
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||
github.com/casbin/casbin/v2 v2.103.0 h1:dHElatNXNrr8XcseUov0ZSiWjauwmZZE6YMV3eU1yic=
|
||||
github.com/casbin/casbin/v2 v2.103.0/go.mod h1:Ee33aqGrmES+GNL17L0h9X28wXuo829wnNUnS0edAco=
|
||||
github.com/casbin/gorm-adapter/v3 v3.32.0 h1:Au+IOILBIE9clox5BJhI2nA3p9t7Ep1ePlupdGbGfus=
|
||||
@@ -177,16 +177,18 @@ github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/
|
||||
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6 h1:6VSn3hB5U5GeA6kQw4TwWIWbOhtvR2hmbBJnTOtqTWc=
|
||||
github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6/go.mod h1:YxOVT5+yHzKvwhsiSIWmbAYM3Dr9AEEbER2dVayfBkg=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
|
||||
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
|
||||
github.com/gammazero/toposort v0.1.1 h1:OivGxsWxF3U3+U80VoLJ+f50HcPU1MIqE1JlKzoJ2Eg=
|
||||
github.com/gammazero/toposort v0.1.1/go.mod h1:H2cozTnNpMw0hg2VHAYsAxmkHXBYroNangj2NTBQDvw=
|
||||
github.com/gin-contrib/cors v1.7.6 h1:3gQ8GMzs1Ylpf70y8bMw4fVpycXIeX1ZemuSQIsnQQY=
|
||||
github.com/gin-contrib/cors v1.7.6/go.mod h1:Ulcl+xN4jel9t1Ry8vqph23a60FwH9xVLd+3ykmTjOk=
|
||||
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
|
||||
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
|
||||
github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
|
||||
github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
|
||||
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
|
||||
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
|
||||
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
|
||||
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
|
||||
@@ -223,16 +225,18 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||
github.com/go-playground/validator/v10 v10.7.0/go.mod h1:xm76BBt941f7yWdGnI2DVPFFg1UK3YY04qifoXU3lOk=
|
||||
github.com/go-playground/validator/v10 v10.24.0 h1:KHQckvo8G6hlWnrPX4NJJ+aBfWNAE/HH+qdL2cBpCmg=
|
||||
github.com/go-playground/validator/v10 v10.24.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
|
||||
github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
|
||||
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM=
|
||||
github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
||||
github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=
|
||||
github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
||||
@@ -273,8 +277,9 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||
@@ -301,6 +306,8 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
@@ -359,8 +366,8 @@ github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90
|
||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
|
||||
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
||||
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
@@ -433,8 +440,8 @@ github.com/otiai10/mint v1.6.3 h1:87qsV/aw1F5as1eH1zS/yqHY85ANKVMgkDrf9rcxbQs=
|
||||
github.com/otiai10/mint v1.6.3/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
|
||||
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
|
||||
@@ -543,8 +550,8 @@ github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPD
|
||||
github.com/tklauser/numcpus v0.9.0/go.mod h1:SN6Nq1O3VychhC1npsWostA+oW+VOQTxZrS604NSRyI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
|
||||
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
|
||||
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
@@ -598,8 +605,8 @@ go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc=
|
||||
go4.org v0.0.0-20230225012048-214862532bf5/go.mod h1:F57wTi5Lrj6WLyswp5EYV1ncrEbFGHD4hhz6S1ZYeaU=
|
||||
golang.org/x/arch v0.13.0 h1:KCkqVVV1kGg0X87TFysjCJ8MxtZEIU4Ja/yXGeoECdA=
|
||||
golang.org/x/arch v0.13.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/arch v0.18.0 h1:WN9poc33zL4AzGxqf8VtpKUnGvMi8O9lhNyBMF/85qc=
|
||||
golang.org/x/arch v0.18.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
@@ -623,8 +630,8 @@ golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -659,8 +666,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
|
||||
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
|
||||
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -694,8 +701,8 @@ golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -714,8 +721,9 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
|
||||
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -754,8 +762,8 @@ golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
@@ -792,8 +800,9 @@ golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
|
||||
@@ -828,8 +837,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
|
||||
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
|
||||
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
|
||||
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -874,8 +883,8 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU=
|
||||
google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/lckt/model/app"
|
||||
"os"
|
||||
|
||||
"git.echol.cn/loser/lckt/global"
|
||||
@@ -62,6 +63,9 @@ func RegisterTables() {
|
||||
example.ExaFileChunk{},
|
||||
example.ExaFileUploadAndDownload{},
|
||||
example.ExaAttachmentCategory{},
|
||||
|
||||
app.Banner{},
|
||||
app.Order{},
|
||||
)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("register table failed", zap.Error(err))
|
||||
|
@@ -5,6 +5,7 @@ import (
|
||||
"git.echol.cn/loser/lckt/model/article"
|
||||
"git.echol.cn/loser/lckt/model/bot"
|
||||
"git.echol.cn/loser/lckt/model/category"
|
||||
"git.echol.cn/loser/lckt/model/notice"
|
||||
"git.echol.cn/loser/lckt/model/user"
|
||||
"git.echol.cn/loser/lckt/model/vip"
|
||||
)
|
||||
@@ -17,6 +18,7 @@ func bizModel() error {
|
||||
article.Article{},
|
||||
user.User{},
|
||||
vip.Vip{},
|
||||
notice.Notice{},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@@ -1,16 +1,19 @@
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"git.echol.cn/loser/lckt/docs"
|
||||
"git.echol.cn/loser/lckt/global"
|
||||
"git.echol.cn/loser/lckt/middleware"
|
||||
"git.echol.cn/loser/lckt/plugin/customerservice"
|
||||
"git.echol.cn/loser/lckt/plugin/picturelibrary"
|
||||
"git.echol.cn/loser/lckt/router"
|
||||
gc "github.com/gin-contrib/cors"
|
||||
"github.com/gin-gonic/gin"
|
||||
swaggerFiles "github.com/swaggo/files"
|
||||
ginSwagger "github.com/swaggo/gin-swagger"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
type justFilesFilesystem struct {
|
||||
@@ -36,12 +39,24 @@ func (fs justFilesFilesystem) Open(name string) (http.File, error) {
|
||||
func Routers() *gin.Engine {
|
||||
Router := gin.New()
|
||||
Router.Use(gin.Recovery())
|
||||
Router.Use(middleware.AllCors())
|
||||
|
||||
Router.Use(gc.New(gc.Config{
|
||||
AllowAllOrigins: true, // 允许所有来源
|
||||
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
|
||||
AllowHeaders: []string{"*"}, // 允许所有自定义header
|
||||
ExposeHeaders: []string{"Content-Length", "Content-Type"},
|
||||
AllowCredentials: true,
|
||||
MaxAge: 12 * time.Hour,
|
||||
}))
|
||||
|
||||
if gin.Mode() == gin.DebugMode {
|
||||
Router.Use(gin.Logger())
|
||||
}
|
||||
|
||||
systemRouter := router.RouterGroupApp.System
|
||||
exampleRouter := router.RouterGroupApp.Example
|
||||
appRouter := router.RouterGroupApp.APP
|
||||
// 如果想要不使用nginx代理前端网页,可以修改 web/.env.production 下的
|
||||
// VUE_APP_BASE_API = /
|
||||
// VUE_APP_BASE_PATH = http://localhost
|
||||
@@ -52,7 +67,7 @@ func Routers() *gin.Engine {
|
||||
|
||||
Router.StaticFS(global.GVA_CONFIG.Local.StorePath, justFilesFilesystem{http.Dir(global.GVA_CONFIG.Local.StorePath)}) // Router.Use(middleware.LoadTls()) // 如果需要使用https 请打开此中间件 然后前往 core/server.go 将启动模式 更变为 Router.RunTLS("端口","你的cre/pem文件","你的key文件")
|
||||
// 跨域,如需跨域可以打开下面的注释
|
||||
// Router.Use(middleware.Cors()) // 直接放行全部跨域请求
|
||||
// Router.Use(middleware.AllCors()) // 直接放行全部跨域请求
|
||||
// Router.Use(middleware.CorsByRules()) // 按照配置的规则放行跨域请求
|
||||
// global.GVA_LOG.Info("use middleware cors")
|
||||
docs.SwaggerInfo.BasePath = global.GVA_CONFIG.System.RouterPrefix
|
||||
@@ -61,10 +76,13 @@ func Routers() *gin.Engine {
|
||||
// 方便统一添加路由组前缀 多服务器上线使用
|
||||
|
||||
PublicGroup := Router.Group(global.GVA_CONFIG.System.RouterPrefix)
|
||||
PublicGroup.Use(middleware.AllCors()) // 直接放行全部跨域请求
|
||||
PrivateGroup := Router.Group(global.GVA_CONFIG.System.RouterPrefix)
|
||||
|
||||
PrivateGroup.Use(middleware.JWTAuth()).Use(middleware.CasbinHandler())
|
||||
|
||||
PrivateGroup.Use(middleware.JWTAuth()).Use(middleware.CasbinHandler()).Use(middleware.AllCors())
|
||||
AppAuthGroup := Router.Group(global.GVA_CONFIG.System.RouterPrefix)
|
||||
AppAuthGroup.Use(middleware.AllCors())
|
||||
//AppAuthGroup.Use(middleware.UserJWTAuth()).Use(middleware.AllCors())
|
||||
{
|
||||
// 健康监测
|
||||
PublicGroup.GET("/health", func(c *gin.Context) {
|
||||
@@ -97,6 +115,12 @@ func Routers() *gin.Engine {
|
||||
exampleRouter.InitAttachmentCategoryRouterRouter(PrivateGroup) // 文件上传下载分类
|
||||
|
||||
}
|
||||
//APP相关路由
|
||||
{
|
||||
appRouter.InitAppUserRouter(AppAuthGroup, PublicGroup)
|
||||
appRouter.InitBannerRouter(PrivateGroup, PublicGroup) // Banner相关路由
|
||||
appRouter.InitOrderRouter(AppAuthGroup) // 订单相关路由
|
||||
}
|
||||
|
||||
//插件路由安装
|
||||
InstallPlugin(PrivateGroup, PublicGroup, Router)
|
||||
@@ -104,6 +128,9 @@ func Routers() *gin.Engine {
|
||||
// 注册业务路由
|
||||
initBizRouter(PrivateGroup, PublicGroup)
|
||||
|
||||
PluginInit(PublicGroup, customerservice.CreateCustomerServicePlug())
|
||||
PluginInit(PrivateGroup, picturelibrary.CreatePictureLibraryPlug())
|
||||
|
||||
global.GVA_ROUTERS = Router.Routes()
|
||||
|
||||
global.GVA_LOG.Info("router register success")
|
||||
|
@@ -16,7 +16,7 @@ func initBizRouter(routers ...*gin.RouterGroup) {
|
||||
{
|
||||
categoryRouter := router.RouterGroupApp.Category
|
||||
categoryRouter.InitCategoryRouter(privateGroup, publicGroup)
|
||||
} // 占位方法,保证文件可以正确加载,避免go空变量检测报错,请勿删除。
|
||||
}
|
||||
{
|
||||
botRouter := router.RouterGroupApp.Bot
|
||||
botRouter.InitBotRouter(privateGroup, publicGroup)
|
||||
@@ -28,10 +28,13 @@ func initBizRouter(routers ...*gin.RouterGroup) {
|
||||
{
|
||||
userRouter := router.RouterGroupApp.User
|
||||
userRouter.InitUserRouter(privateGroup, publicGroup)
|
||||
userRouter.InitAppUserRouter(publicGroup)
|
||||
}
|
||||
{
|
||||
vipRouter := router.RouterGroupApp.Vip
|
||||
vipRouter.InitVipRouter(privateGroup, publicGroup)
|
||||
}
|
||||
{
|
||||
noticeRouter := router.RouterGroupApp.Notice
|
||||
noticeRouter.InitNoticeRouter(privateGroup, publicGroup) // 占位方法,保证文件可以正确加载,避免go空变量检测报错,请勿删除。
|
||||
}
|
||||
}
|
||||
|
1
main.go
1
main.go
@@ -44,5 +44,6 @@ func main() {
|
||||
}
|
||||
|
||||
wechat.InitWeOfficial() // 初始化微信公众号SDK
|
||||
wechat.InitWechatPay() // 初始化微信支付SDK
|
||||
core.RunWindowsServer()
|
||||
}
|
||||
|
@@ -71,3 +71,19 @@ func checkCors(currentOrigin string) *config.CORSWhitelist {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func AllCors() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Headers", "*")
|
||||
c.Writer.Header().Set("Access-Control-Expose-Headers", "Content-Length, Content-Type")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
|
||||
if c.Request.Method == "OPTIONS" {
|
||||
c.AbortWithStatus(204)
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
16
model/app/banner.go
Normal file
16
model/app/banner.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package app
|
||||
|
||||
import "git.echol.cn/loser/lckt/global"
|
||||
|
||||
type Banner struct {
|
||||
global.GVA_MODEL
|
||||
Title string `json:"title" gorm:"comment:标题"` // 标题
|
||||
Link string `json:"link" gorm:"comment:链接"` // 链接
|
||||
Img string `json:"img" gorm:"comment:图片"` // 图片
|
||||
Status int `json:"status" gorm:"comment:状态"` // 状态 0:禁用 1:启用
|
||||
}
|
||||
|
||||
// TableName Banner 表名
|
||||
func (Banner) TableName() string {
|
||||
return "lckt_banner"
|
||||
}
|
25
model/app/order.go
Normal file
25
model/app/order.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package app
|
||||
|
||||
import "git.echol.cn/loser/lckt/global"
|
||||
|
||||
type Order struct {
|
||||
global.GVA_MODEL
|
||||
OrderNo string `gorm:"column:order_no;type:varchar(24);comment:订单编号;NOT NULL" json:"order_no"`
|
||||
UserId uint64 `gorm:"column:user_id;type:bigint(20) unsigned;comment:用户ID;NOT NULL" json:"user_id"`
|
||||
OrderType int `gorm:"column:order_type;type:int(11);comment:订单类型 |1 课程|2 vip|;NOT NULL" json:"order_type"`
|
||||
ArticleId uint `gorm:"column:article_id;type:bigint(20) unsigned;default:0;comment:文章ID;NOT NULL" json:"article_id"`
|
||||
Title string `gorm:"column:title;type:varchar(255);comment:订单标题;NOT NULL" json:"title"`
|
||||
Name string `gorm:"column:name;type:varchar(255);comment:名称;NOT NULL" json:"name"`
|
||||
Price int64 `gorm:"column:price;type:int(11) unsigned;default:0;comment:订单价格;NOT NULL" json:"price"`
|
||||
Phone string `gorm:"column:phone;type:varchar(11);comment:手机号;NOT NULL" json:"phone"`
|
||||
TeacherId uint64 `gorm:"column:teacher_Id;type:bigint(20);comment:教师Id;NOT NULL" json:"teacher_Id"`
|
||||
Status int `gorm:"column:status;type:int(11);default:1;comment:订单状态 |1 未付款|2 已付款|3 已过期|;NOT NULL" json:"status"`
|
||||
Desc string `gorm:"column:desc;type:varchar(24);comment:订单描述;NOT NULL" json:"desc"`
|
||||
OpenId string `gorm:"column:open_id;type:varchar(64);comment:用户OpenId;NOT NULL" json:"open_id"`
|
||||
PayType int `gorm:"column:pay_type;type:int(11);default:1;comment:支付方式 |1 微信|2 支付宝|3 余额|;NOT NULL" json:"pay_type"`
|
||||
}
|
||||
|
||||
// TableName Order表
|
||||
func (Order) TableName() string {
|
||||
return "app_order"
|
||||
}
|
23
model/app/request/order.go
Normal file
23
model/app/request/order.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package request
|
||||
|
||||
import common "git.echol.cn/loser/lckt/model/common/request"
|
||||
|
||||
type BalancePay struct {
|
||||
UserId uint `json:"user_id" binding:"required"` // 用户ID
|
||||
OrderNo string `json:"order_no" binding:"required"` // 订单号
|
||||
OrderId uint `json:"order_id" binding:"required"` // 订单ID
|
||||
}
|
||||
|
||||
type GetOrderList struct {
|
||||
common.PageInfo
|
||||
Status int `json:"status" form:"status"` // 订单状态
|
||||
}
|
||||
|
||||
type PayReq struct {
|
||||
UserId uint64 `json:"user_id"` // 用户ID
|
||||
OrderId uint `json:"order_id"`
|
||||
Mode string `json:"mode"` // h5 jsapi
|
||||
WxCode string `json:"wx_code"`
|
||||
PayMethod int `json:"pay_method" vd:"$>0; msg:'参数不能为空'"`
|
||||
OrderNo string `json:"order_no" vd:"@:len($)>0; msg:'订单编号参数不能为空'"`
|
||||
}
|
18
model/app/teacher_apply.go
Normal file
18
model/app/teacher_apply.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package app
|
||||
|
||||
import "git.echol.cn/loser/lckt/global"
|
||||
|
||||
type TeacherApply struct {
|
||||
global.GVA_MODEL
|
||||
UserId uint `json:"userId"`
|
||||
Reason string `json:"reason" gorm:"type:varchar(255);comment:申请理由"`
|
||||
Status int8 `json:"status" gorm:"default:0;comment:申请状态 0 待审核 1 通过 2 拒绝"`
|
||||
ExpectRate string `json:"expectRate" gorm:"type:varchar(255);comment:期望分成比例"`
|
||||
Phone string `json:"phone" gorm:"type:varchar(20);comment:联系电话"`
|
||||
Nickname string `json:"nickname" gorm:"type:varchar(255);comment:讲师名称"`
|
||||
Note string `json:"note" gorm:"type:varchar(255);comment:审核备注"`
|
||||
}
|
||||
|
||||
func (TeacherApply) TableName() string {
|
||||
return "teacher_apply"
|
||||
}
|
17
model/app/vo/user.go
Normal file
17
model/app/vo/user.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package vo
|
||||
|
||||
type UserInfo struct {
|
||||
ID uint `json:"id" form:"id"`
|
||||
NickName string `json:"nick_name" form:"nick_name"`
|
||||
Phone string `json:"phone" form:"phone" `
|
||||
UnionId string `json:"union_id" form:"union_id"`
|
||||
OpenId string `json:"open_id" form:"open_id"`
|
||||
Avatar string `json:"avatar" form:"avatar"`
|
||||
InviteCode *string `json:"invite_code" form:"invite_code" gorm:"type:varchar(255) comment '用户专属邀请码'"` // 用户专属邀请码
|
||||
Balance float32 `json:"balance" form:"balance" gorm:"type:decimal(10,2);comment:学员余额"` // 学员余额
|
||||
CommenderId int `json:"commender_id" form:"commender_id" gorm:"comment:推荐人ID"` // 推荐人ID
|
||||
UserLabel int64 `json:"user_label" form:"user_label" gorm:"comment:用户标签 1 普通用户 2 Vip 3 Svip"` // 用户标签 1 普通用户 2 Vip 3 Svip
|
||||
UserType int8 `json:"user_type" form:"user_type" ` //用户类型 1 用户 2 讲师
|
||||
IsVip int8 `json:"is_vip" form:"is_vip"` //是否是VIP 0 否 1 是
|
||||
VipExpireTime string `json:"vip_expire_time" form:"vip_expire_time"` // VIP过期时间
|
||||
}
|
@@ -6,12 +6,16 @@ import (
|
||||
|
||||
type Article struct {
|
||||
global.GVA_MODEL
|
||||
Title string `json:"title" gorm:"comment:文章标题"`
|
||||
Desc string `json:"desc" gorm:"comment:文章描述"`
|
||||
Content string `json:"content" gorm:"comment:文章内容"`
|
||||
CoverImg string `json:"coverImg" gorm:"comment:文章封面图"`
|
||||
TeacherId int `json:"teacherId" gorm:"comment:讲师ID"`
|
||||
Price int64 `json:"price" gorm:"comment:文章价格(单位为分)"`
|
||||
Title string `json:"title" gorm:"comment:文章标题"`
|
||||
Desc string `json:"desc" gorm:"comment:文章描述"`
|
||||
Content string `json:"content" gorm:"comment:文章内容;type:longtext"`
|
||||
CoverImg string `json:"coverImg" gorm:"comment:文章封面图"`
|
||||
TeacherId int `json:"teacherId" gorm:"comment:讲师ID"`
|
||||
TeacherName string `json:"teacherName" gorm:"comment:讲师名称"`
|
||||
Price int64 `json:"price" gorm:"comment:文章价格(单位为分)"`
|
||||
IsFree int `json:"isFree" gorm:"comment:是否免费;default:0"` // 是否免费 0-否 1-是
|
||||
// 分类ID
|
||||
CategoryId int `json:"categoryId" gorm:"comment:分类ID"`
|
||||
}
|
||||
|
||||
// TableName 文章表
|
||||
|
@@ -4,6 +4,10 @@ import "git.echol.cn/loser/lckt/model/common/request"
|
||||
|
||||
type GetList struct {
|
||||
request.PageInfo
|
||||
Title string `json:"title" form:"title"` // 文章标题
|
||||
// 分类ID
|
||||
CategoryId int `json:"categoryId" form:"categoryId"` // 分类ID
|
||||
TeacherId int `json:"teacherId" form:"teacherId"` // 讲师ID
|
||||
}
|
||||
|
||||
type DeleteIds struct {
|
||||
|
@@ -1,5 +1,18 @@
|
||||
package vo
|
||||
|
||||
type ArticleListVo struct {
|
||||
ID int `json:"id" gorm:"comment:文章ID"`
|
||||
Title string `json:"title" gorm:"comment:文章标题"`
|
||||
Desc string `json:"desc" gorm:"comment:文章描述"`
|
||||
//Content string `json:"content" gorm:"comment:文章内容"`
|
||||
CoverImg string `json:"coverImg" gorm:"comment:文章封面图"`
|
||||
Price int64 `json:"price" gorm:"comment:文章价格(单位为分)"`
|
||||
TeacherId int `json:"teacherId" gorm:"comment:讲师ID"`
|
||||
TeacherName string `json:"teacherName" gorm:"comment:讲师名称"`
|
||||
TeacherAvatar string `json:"teacherAvatar" gorm:"comment:讲师头像"`
|
||||
IsFree int `json:"isFree" gorm:"comment:是否免费;default:0"` // 是否免费 0-否 1-是
|
||||
}
|
||||
|
||||
type ArticleVo struct {
|
||||
ID int `json:"id" gorm:"comment:文章ID"`
|
||||
Title string `json:"title" gorm:"comment:文章标题"`
|
||||
@@ -10,4 +23,6 @@ type ArticleVo struct {
|
||||
TeacherId int `json:"teacherId" gorm:"comment:讲师ID"`
|
||||
TeacherName string `json:"teacherName" gorm:"comment:讲师名称"`
|
||||
TeacherAvatar string `json:"teacherAvatar" gorm:"comment:讲师头像"`
|
||||
IsFree int `json:"isFree" gorm:"comment:是否免费;default:0"` // 是否免费 0-否 1-是
|
||||
IsBuy int `json:"isBuy" gorm:"comment:是否购买;default:0"` // 是否购买 0-否 1-是
|
||||
}
|
||||
|
@@ -5,13 +5,15 @@ import (
|
||||
"git.echol.cn/loser/lckt/global"
|
||||
)
|
||||
|
||||
// 类别 结构体 Category
|
||||
// Category 分类
|
||||
type Category struct {
|
||||
global.GVA_MODEL
|
||||
Name *string `json:"name" form:"name" gorm:"column:name;comment:类别名称;" binding:"required"` //名称
|
||||
Order *int `json:"order" form:"order" gorm:"column:order;comment:排序字段;"` //排序
|
||||
Active *bool `json:"active" form:"active" gorm:"column:active;comment:是否激活;"` //状态
|
||||
ParentId *int `json:"parentId" form:"parentId" gorm:"column:parent_id;comment:父类别ID;"` //父ID
|
||||
Name *string `json:"name" form:"name" gorm:"column:name;comment:类别名称;" binding:"required"` //名称
|
||||
Order *int `json:"order" form:"order" gorm:"column:order;comment:排序字段;"` //排序
|
||||
Active *bool `json:"active" form:"active" gorm:"column:active;comment:是否激活;"` //状态
|
||||
ParentId *int `json:"parentId" form:"parentId" gorm:"column:parent_id;comment:父类别ID;"` //父ID
|
||||
Icon *string `json:"icon" form:"icon" gorm:"column:icon;comment:类别图标;"` //图标
|
||||
IndexTrue *bool `json:"index" form:"index" gorm:"column:index;comment:是否首页显示;"` //是否首页显示
|
||||
}
|
||||
|
||||
// TableName 类别 Category自定义表名 categories
|
||||
|
19
model/notice/notice.go
Normal file
19
model/notice/notice.go
Normal file
@@ -0,0 +1,19 @@
|
||||
// 自动生成模板Notice
|
||||
package notice
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/lckt/global"
|
||||
)
|
||||
|
||||
// 通知 结构体 Notice
|
||||
type Notice struct {
|
||||
global.GVA_MODEL
|
||||
Title *string `json:"title" form:"title" gorm:"column:title;comment:;" binding:"required"` //标题
|
||||
Content *string `json:"content" form:"content" gorm:"column:content;comment:;type:text;" binding:"required"` //内容
|
||||
Status *int `json:"status" form:"status" gorm:"column:status;comment:状态;"` //状态
|
||||
}
|
||||
|
||||
// TableName 通知 Notice自定义表名 notice
|
||||
func (Notice) TableName() string {
|
||||
return "notice"
|
||||
}
|
14
model/notice/request/notice.go
Normal file
14
model/notice/request/notice.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package request
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/lckt/model/common/request"
|
||||
"time"
|
||||
)
|
||||
|
||||
type NoticeSearch struct {
|
||||
StartCreatedAt *time.Time `json:"startCreatedAt" form:"startCreatedAt"`
|
||||
EndCreatedAt *time.Time `json:"endCreatedAt" form:"endCreatedAt"`
|
||||
request.PageInfo
|
||||
Sort string `json:"sort" form:"sort"`
|
||||
Order string `json:"order" form:"order"`
|
||||
}
|
@@ -1,12 +0,0 @@
|
||||
package system
|
||||
|
||||
import "git.echol.cn/loser/lckt/global"
|
||||
|
||||
type Class struct {
|
||||
global.GVA_MODEL
|
||||
Name string `json:"name" gorm:"comment:分类名"`
|
||||
}
|
||||
|
||||
func (c *Class) TableName() string {
|
||||
return "class"
|
||||
}
|
@@ -7,7 +7,7 @@ type SendCodeReq struct {
|
||||
}
|
||||
|
||||
type CodeLoginReq struct {
|
||||
Phone string `json:"phone" form:"phone" vd:"@:len($)>0; msg:'请输入手机号码'"`
|
||||
Phone string `json:"phone" form:"phone"`
|
||||
Code string `json:"code" form:"code" vd:"@:len($)>0; msg:'请输入短信验证码'"`
|
||||
}
|
||||
|
||||
@@ -35,8 +35,10 @@ type BindWechatReq struct {
|
||||
}
|
||||
|
||||
type BindPhoneReq struct {
|
||||
Id int `json:"id" form:"id" vd:"@:len($)>0; msg:'用户ID不能为空'"`
|
||||
Phone string `json:"phone" form:"phone" vd:"@:len($)>0; msg:'手机号码不能为空'"`
|
||||
Id int `json:"id" form:"id" vd:"@:len($)>0; msg:'用户ID不能为空'"`
|
||||
Phone string `json:"phone" form:"phone" vd:"@:len($)>0; msg:'手机号码不能为空'"`
|
||||
Code string `json:"code" form:"code" vd:"@:len($)>0; msg:'验证码不能为空'"`
|
||||
PassWord string `json:"password" form:"password" vd:"@:len($)>0; msg:'密码不能为空'"`
|
||||
}
|
||||
|
||||
type GetUserListReq struct {
|
||||
@@ -44,6 +46,7 @@ type GetUserListReq struct {
|
||||
Type int `json:"type" form:"type"`
|
||||
UserLabel string `json:"user_label" form:"user_label" `
|
||||
Status int `json:"status" form:"status"`
|
||||
Name string `json:"name" form:"name"`
|
||||
}
|
||||
|
||||
type SetBalanceReq struct {
|
||||
@@ -61,6 +64,21 @@ type RegisterReq struct {
|
||||
Phone string `json:"phone" form:"phone" vd:"@:len($)>0; msg:'手机号码不能为空'"`
|
||||
Password string `json:"password" form:"password" vd:"@:len($)>0; msg:'密码不能为空'"`
|
||||
NickName string `json:"nick_name" form:"nick_name"`
|
||||
UserType int8 `json:"user_type" form:"user_type" vd:"@:len($)>0; msg:'用户类型不能为空'"`
|
||||
UserLabel int64 `json:"user_label" form:"user_label" vd:"@:len($)>0; msg:'用户标签不能为空'"`
|
||||
Code string `json:"code" form:"code" vd:"@:len($)>0; msg:'验证码不能为空'"`
|
||||
UserType int8 `json:"user_type" form:"user_type"`
|
||||
UserLabel int64 `json:"user_label" form:"user_label"`
|
||||
}
|
||||
|
||||
type ApplyTeacherReq struct {
|
||||
UserId int `json:"user_id" form:"user_id" vd:"@:len($)>0; msg:'用户ID不能为空'"`
|
||||
Phone string `json:"phone" form:"phone" vd:"@:len($)>0; msg:'手机号码不能为空'"`
|
||||
NickName string `json:"nick_name" form:"nick_name" vd:"@:len($)>0; msg:'昵称不能为空'"`
|
||||
Desc string `json:"desc" form:"desc" vd:"@:len($)>0; msg:'申请理由不能为空'"`
|
||||
ExpectRate string `json:"expect_rate" form:"expect_rate" vd:"@:len($)>0; msg:'期望分成比例不能为空'"`
|
||||
}
|
||||
|
||||
type GetTeacherApplyListReq struct {
|
||||
request.PageInfo
|
||||
Phone string `json:"phone" form:"phone"`
|
||||
Nickname string `json:"nickname" form:"nickname"`
|
||||
}
|
||||
|
70
plugin/customerservice/README.md
Normal file
70
plugin/customerservice/README.md
Normal file
@@ -0,0 +1,70 @@
|
||||
[## GVA 客服聊天功能[前端用户聊天基于gva-shop的uniapp端]
|
||||
|
||||
### 手动安装方法
|
||||
|
||||
1.解压zip获得customerservice文件夹
|
||||
2.将 customerservice/web/plugin/customerservice 放置在web/plugin下
|
||||
3.将 customerservice/server/plugin/customerservice 放置在server/plugin下
|
||||
4.将 customerservice/uni 下的文件放到gva-shop商城插件下的uni里
|
||||
5.在gva-shop的uni下的pages.json里新增:
|
||||
{
|
||||
"path" : "pages/service/index",
|
||||
"style" :
|
||||
{
|
||||
"navigationBarTitleText" : "客服"
|
||||
}
|
||||
}
|
||||
#### 1. 前往GVA主程序下的initialize/router.go 在Routers 方法最末尾按照你需要的及安全模式添加本插件
|
||||
PluginInit(PublicGroup, customerservice.CreateCustomerServicePlug())
|
||||
到gva系统,角色管理,分配角色的api权限即可,插件会自动注册api,需要手动分配。
|
||||
注:会自动生成如下表:sys_service、sys_service_msg、sys_service_record、sys_service_reply、sys_service_script
|
||||
### 2. 配置说明
|
||||
|
||||
#### 2-1 后台主要功能
|
||||
|
||||
客服管理、客服话术、客服自动回复配置等
|
||||
|
||||
#### 2-2 使用说明
|
||||
|
||||
1、在前端vue部分路由需要手动配置:
|
||||
web/src/router/index.js下新增如下配置:
|
||||
{
|
||||
path: '/kefu/login',
|
||||
name: 'ServiceLogin',
|
||||
component: () => import('@/plugin/customerservice/view/login/index.vue'),
|
||||
meta:{
|
||||
client:true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/kefu/main',
|
||||
name: 'ServiceMain',
|
||||
component: () => import('@/plugin/customerservice/view/chat/index.vue'),
|
||||
meta:{
|
||||
client:true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/kefu/test',
|
||||
name: 'ServiceUserTest',
|
||||
component: () => import('@/plugin/customerservice/view/chat/test.vue'),
|
||||
meta:{
|
||||
client:true
|
||||
}
|
||||
},
|
||||
|
||||
2、后台使用方法:
|
||||
启动gva项目,安装后在客服列表添加客服,然后可以从客服列表的进入工作台进入客服聊天页,或者打开客服登录页
|
||||
http://localhost:8080/#/kefu/login进行登录
|
||||
3、此插件涉及的图片上传使用了插件管理中《图库》插件,可根据自己喜好进行替换
|
||||
4、后台客服websocket连接的地方在插件view/chat/index.vue,连接地址改成自己项目地址,
|
||||
客服:websocket.value = new WebSocket(`ws://localhost:8888/service/serve_ws?token=${token.value}`)
|
||||
|
||||
5、用户websocket连接的地方在uni/pages/service/index.vue下,连接地址改成自己项目地址,websocket.value = new WebSocket(`ws://localhost:8888/service/ws?token=${token.value}`)
|
||||
|
||||
6、项目没进行过啥大的测试,仅供参考学习
|
||||
|
||||
#### 2-3 参数说明
|
||||
|
||||
### 3. 方法API
|
||||
无
|
407
plugin/customerservice/api/api.go
Normal file
407
plugin/customerservice/api/api.go
Normal file
@@ -0,0 +1,407 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"git.echol.cn/loser/lckt/global"
|
||||
"git.echol.cn/loser/lckt/model/common/response"
|
||||
"git.echol.cn/loser/lckt/model/user"
|
||||
sysModel "git.echol.cn/loser/lckt/plugin/customerservice/model"
|
||||
"git.echol.cn/loser/lckt/plugin/customerservice/service"
|
||||
"git.echol.cn/loser/lckt/plugin/customerservice/service/ws"
|
||||
"git.echol.cn/loser/lckt/plugin/customerservice/tools"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/zap"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type CustomerServiceApi struct{}
|
||||
|
||||
func (cus *CustomerServiceApi) ServeWs(ctx *gin.Context) {
|
||||
ws.WsServe(ctx)
|
||||
}
|
||||
|
||||
func (cus *CustomerServiceApi) ServeWsForKefu(ctx *gin.Context) {
|
||||
ws.ServeWsForKefu(ctx)
|
||||
}
|
||||
|
||||
func (cus *CustomerServiceApi) HandleTransfer(c *gin.Context) {
|
||||
var transferReq struct {
|
||||
FromAgent string `json:"from_agent"`
|
||||
ToAgent string `json:"to_agent"`
|
||||
UserID string `json:"user_id"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&transferReq); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 更新用户与客服的映射关系
|
||||
// 例如:userAgentMap[transferReq.UserID] = transferReq.ToAgent
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"status": "success"})
|
||||
}
|
||||
|
||||
func (cus *CustomerServiceApi) GetKefuInfo(c *gin.Context) {
|
||||
userID, _ := c.Get("jwt_user_id")
|
||||
//uidStr := strconv.Itoa(int(userID))
|
||||
var serviceId int64
|
||||
var recordData sysModel.SysServiceRecord
|
||||
result := global.GVA_DB.Where("uid = ?", userID).Order("update_time DESC").Limit(1).Find(&recordData)
|
||||
if result.RowsAffected == 0 || result.Error != nil {
|
||||
//直接查询service表
|
||||
result2 := global.GVA_DB.Model(&sysModel.SysService{}).
|
||||
Select("id").
|
||||
Where("status = ?", 1).
|
||||
Order("add_time DESC").
|
||||
Limit(1).Scan(&serviceId)
|
||||
fmt.Println("sssssssssssss--->>>")
|
||||
fmt.Println(serviceId)
|
||||
if result2.Error != nil {
|
||||
response.FailWithMessage("获取客服信息失败-1", c)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
serviceId = recordData.ServiceId
|
||||
}
|
||||
var serviceData sysModel.SysService
|
||||
result3 := global.GVA_DB.Select("id,uid,online,avatar,nickname,add_time,status").
|
||||
Where("id = ?", serviceId).
|
||||
Where("status = ?", 1).
|
||||
Order("add_time DESC").
|
||||
First(&serviceData)
|
||||
if result3.Error != nil {
|
||||
response.FailWithMessage("获取客服信息失败-2", c)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(serviceData, "获取成功", c)
|
||||
}
|
||||
|
||||
func (cus *CustomerServiceApi) SendMsg(c *gin.Context) {
|
||||
var msgJson ws.Message
|
||||
if jsErr := c.ShouldBindJSON(&msgJson); jsErr != nil {
|
||||
fmt.Println(jsErr)
|
||||
response.FailWithMessage("参数有误-1", c)
|
||||
return
|
||||
}
|
||||
fromIdStr := msgJson.Sender
|
||||
toIdStr := msgJson.Receiver
|
||||
content := msgJson.Content
|
||||
isKf := msgJson.IsKf
|
||||
msgTypeStr := msgJson.MsgType
|
||||
if content == "" || fromIdStr == "" || toIdStr == "" || msgTypeStr == "" {
|
||||
response.FailWithMessage("参数有误-2", c)
|
||||
return
|
||||
}
|
||||
toId, err_1 := strconv.ParseInt(toIdStr, 10, 64)
|
||||
fromId, err_2 := strconv.ParseInt(fromIdStr, 10, 64)
|
||||
msgType, err_3 := strconv.ParseInt(msgTypeStr, 10, 64)
|
||||
if err_1 != nil || err_2 != nil || err_3 != nil {
|
||||
response.FailWithMessage("参数有误", c)
|
||||
return
|
||||
}
|
||||
//限流
|
||||
if !tools.LimitFreqSingle("send_message:"+c.ClientIP(), 1, 2) {
|
||||
response.FailWithMessage("发送频率过快", c)
|
||||
return
|
||||
}
|
||||
var kfInfo sysModel.SysService
|
||||
//var userInfo sysModel.SysTestUser
|
||||
var userInfo user.User
|
||||
var err, err2 error
|
||||
if isKf == 1 {
|
||||
err = global.GVA_DB.Where("id = ?", fromId).First(&kfInfo).Error
|
||||
err2 = global.GVA_DB.Where("id = ?", toId).First(&userInfo).Error
|
||||
|
||||
} else if isKf == 0 {
|
||||
err = global.GVA_DB.Where("id = ?", toId).First(&kfInfo).Error
|
||||
err2 = global.GVA_DB.Where("id = ?", fromId).First(&userInfo).Error
|
||||
}
|
||||
if err != nil || err2 != nil {
|
||||
response.FailWithMessage("获取失败-1", c)
|
||||
return
|
||||
}
|
||||
|
||||
ser := service.ServiceGroupApp
|
||||
cErr := ser.CreateMsg(kfInfo, userInfo, msgType, content, strconv.FormatInt(isKf, 10))
|
||||
if cErr != nil {
|
||||
response.FailWithMessage("发送失败", c)
|
||||
return
|
||||
}
|
||||
message := ws.Message{
|
||||
Sender: fromIdStr,
|
||||
Receiver: toIdStr,
|
||||
Content: content,
|
||||
MsgType: msgTypeStr,
|
||||
Role: "kf",
|
||||
Timestamp: time.Now().Unix(),
|
||||
}
|
||||
var key string
|
||||
if isKf == 1 {
|
||||
//查找指定用户广播消息
|
||||
key = "user" + toIdStr
|
||||
message.AvatarUrl = kfInfo.Avatar
|
||||
message.Nickname = kfInfo.Nickname
|
||||
} else if isKf == 0 {
|
||||
//查找指定客服广播消息
|
||||
key = "kf" + toIdStr
|
||||
message.Role = "user"
|
||||
message.AvatarUrl = userInfo.Avatar
|
||||
message.Nickname = userInfo.NickName
|
||||
}
|
||||
conn, ok := ws.Manager.Clients[key]
|
||||
if conn != nil && ok {
|
||||
sendMsg := ws.TypeMsg{
|
||||
Type: "message",
|
||||
Data: message,
|
||||
}
|
||||
str, _ := json.Marshal(sendMsg)
|
||||
conn.Send <- str
|
||||
|
||||
if isKf == 0 {
|
||||
//客服给用户发送自动回复消息
|
||||
var autoReply sysModel.SysServiceReply
|
||||
autoContent := ""
|
||||
var autoMsgType int64
|
||||
aErr := global.GVA_DB.Where("is_complete = ? AND `status` = ? AND keyword = ?", 1, 1, content).First(&autoReply).Error
|
||||
fmt.Println(aErr)
|
||||
if aErr == nil {
|
||||
fmt.Println(autoReply)
|
||||
autoContent = autoReply.Content
|
||||
autoMsgType = autoReply.ReplyType
|
||||
} else {
|
||||
aErr = global.GVA_DB.Where("is_complete = ? AND `status` = ? AND keyword LIKE ?", 0, 1, "%"+content+"%").First(&autoReply).Error
|
||||
if aErr == nil {
|
||||
autoContent = autoReply.Content
|
||||
autoMsgType = autoReply.ReplyType
|
||||
}
|
||||
}
|
||||
if autoContent != "" {
|
||||
if autoMsgType == 2 {
|
||||
autoMsgType = 3 //图片
|
||||
}
|
||||
aErr = ser.CreateMsg(kfInfo, userInfo, autoMsgType, autoContent, "1")
|
||||
if aErr == nil {
|
||||
autoUidStr := strconv.FormatUint(uint64(userInfo.ID), 10)
|
||||
message.Sender = strconv.FormatInt(kfInfo.Id, 10)
|
||||
message.Receiver = autoUidStr
|
||||
message.MsgType = strconv.FormatInt(autoMsgType, 10)
|
||||
message.Content = autoContent
|
||||
message.IsKf = 1
|
||||
message.Role = "kf"
|
||||
message.AvatarUrl = kfInfo.Avatar
|
||||
message.Nickname = kfInfo.Nickname
|
||||
sendMsg.Data = message
|
||||
autoStr, _ := json.Marshal(sendMsg)
|
||||
kfConn, isOk := ws.Manager.Clients["user"+autoUidStr]
|
||||
if kfConn != nil && isOk {
|
||||
kfConn.Send <- autoStr
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
response.OkWithDetailed(nil, "发送成功", c)
|
||||
}
|
||||
|
||||
func (cus *CustomerServiceApi) GetMsgList(c *gin.Context) {
|
||||
|
||||
uid, ok := c.Get("jwt_user_id") //jwt里解出的
|
||||
jwtServiceId, ok2 := c.Get("service_id") //jwt里解出的
|
||||
if !ok2 {
|
||||
//gva-shop前端用户连接请求消息列表
|
||||
jwtServiceId = c.Query("kf_id")
|
||||
}
|
||||
if !ok {
|
||||
//后台客服连接请求消息列表
|
||||
uid = c.Query("uid")
|
||||
}
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
if pageSize > 20 {
|
||||
pageSize = 20
|
||||
}
|
||||
offset := pageSize * (page - 1)
|
||||
var total int64
|
||||
var list []sysModel.SysServiceMsg
|
||||
db := global.GVA_DB.Model(&sysModel.SysServiceMsg{}).Where("uid = ?", uid).Where("service_id = ?", jwtServiceId)
|
||||
db.Count(&total)
|
||||
err := db.Limit(pageSize).Offset(offset).Order("add_time desc").Find(&list).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||
response.FailWithMessage("获取失败", c)
|
||||
return
|
||||
}
|
||||
if len(list) > 0 {
|
||||
sort.Slice(list, func(i, j int) bool {
|
||||
return list[i].AddTime < list[j].AddTime
|
||||
})
|
||||
for k, v := range list {
|
||||
decoded, _ := base64.StdEncoding.DecodeString(v.Content)
|
||||
v.Content = string(decoded)
|
||||
list[k] = v
|
||||
}
|
||||
}
|
||||
response.OkWithDetailed(response.PageResult{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
func (cus *CustomerServiceApi) GetMsgUser(c *gin.Context) {
|
||||
kfId, _ := c.Get("service_id")
|
||||
var list []sysModel.SysServiceRecord
|
||||
err := global.GVA_DB.Where("service_id=?", kfId).Find(&list).Error
|
||||
if err != nil {
|
||||
response.FailWithMessage("获取失败", c)
|
||||
return
|
||||
}
|
||||
if len(list) > 0 {
|
||||
//判断用户在线状况
|
||||
for k, v := range list {
|
||||
userKey := "user" + strconv.FormatInt(v.Uid, 10)
|
||||
isClent, ok := ws.Manager.Clients[userKey]
|
||||
if ok && isClent != nil {
|
||||
v.Online = 1
|
||||
} else {
|
||||
v.Online = 0
|
||||
}
|
||||
decoded, _ := base64.StdEncoding.DecodeString(v.Message)
|
||||
v.Message = string(decoded)
|
||||
//查找未读消息数
|
||||
var noCount int64
|
||||
global.GVA_DB.Model(&sysModel.SysServiceMsg{}).
|
||||
Where("is_view=?", 0).
|
||||
Where("is_kf=?", 0).
|
||||
Where("service_id=?", kfId).
|
||||
Where("uid=?", v.Uid).Count(&noCount)
|
||||
v.NoRead = noCount
|
||||
v.AddTimeStr = tools.FormatTimestamp(v.UpdateTime)
|
||||
if v.MessageType == 3 {
|
||||
v.Message = "[图片]"
|
||||
}
|
||||
list[k] = v
|
||||
}
|
||||
sort.Slice(list, func(i, j int) bool {
|
||||
if list[i].Online != list[j].Online {
|
||||
return list[i].Online > list[j].Online
|
||||
}
|
||||
return list[i].AddTime > list[j].AddTime
|
||||
})
|
||||
}
|
||||
response.OkWithDetailed(list, "获取成功", c)
|
||||
}
|
||||
|
||||
func (cus *CustomerServiceApi) SetMsgView(c *gin.Context) {
|
||||
kfId, _ := c.Get("service_id")
|
||||
uid := c.Query("uid")
|
||||
global.GVA_DB.Model(&sysModel.SysServiceMsg{}).
|
||||
Where(map[string]interface{}{"is_kf": 0, "service_id": kfId, "is_view": 0, "uid": uid}).
|
||||
Update("is_view", 1)
|
||||
response.Ok(c)
|
||||
}
|
||||
|
||||
func (cus *CustomerServiceApi) UploadFile(c *gin.Context) {
|
||||
file, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
extension := filepath.Ext(file.Filename)
|
||||
newUUID := uuid.New().String()
|
||||
hash := md5.Sum([]byte("gva-service" + newUUID))
|
||||
md5Pwd := hex.EncodeToString(hash[:])
|
||||
filename := md5Pwd + extension
|
||||
if err := c.SaveUploadedFile(file, "./uploads/file/"+filename); err != nil {
|
||||
response.FailWithMessage("上传失败-2", c)
|
||||
return
|
||||
|
||||
}
|
||||
//ser := service.ServiceGroupApp
|
||||
//url := ser.GetUrlHost(c)
|
||||
response.OkWithDetailed("uploads/file/"+filename, "获取成功", c)
|
||||
return
|
||||
}
|
||||
|
||||
func (cus *CustomerServiceApi) GetTestMsgList(c *gin.Context) {
|
||||
uid := c.Query("uid")
|
||||
serviceId := c.Query("service_id")
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))
|
||||
if pageSize > 20 {
|
||||
pageSize = 20
|
||||
}
|
||||
offset := pageSize * (page - 1)
|
||||
var total int64
|
||||
var list []sysModel.SysServiceMsg
|
||||
global.GVA_DB.Model(&sysModel.SysServiceMsg{}).Where("uid=?", uid).Where("service_id=?", serviceId).Count(&total)
|
||||
err := global.GVA_DB.Where("uid=?", uid).Where("service_id=?", serviceId).Limit(pageSize).Offset(offset).Order("add_time desc").Find(&list).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||
response.FailWithMessage("获取失败", c)
|
||||
return
|
||||
}
|
||||
if len(list) > 0 {
|
||||
sort.Slice(list, func(i, j int) bool {
|
||||
return list[i].AddTime < list[j].AddTime
|
||||
})
|
||||
for k, v := range list {
|
||||
decoded, _ := base64.StdEncoding.DecodeString(v.Content)
|
||||
v.Content = string(decoded)
|
||||
list[k] = v
|
||||
}
|
||||
}
|
||||
response.OkWithDetailed(response.PageResult{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
func (cus *CustomerServiceApi) GetUserInfo(c *gin.Context) {
|
||||
//userID := utils.GetUserID(c)
|
||||
userID, ok := c.Get("jwt_user_id")
|
||||
if !ok {
|
||||
//后台客服连接请求
|
||||
userID = c.Query("uid")
|
||||
}
|
||||
var clientUser user.User
|
||||
result := global.GVA_DB.Omit("password").Where("id = ?", userID).Where("deleted_at IS NULL").First(&clientUser)
|
||||
if result.Error != nil {
|
||||
response.FailWithMessage("获取用户信息失败", c)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(clientUser, "获取成功", c)
|
||||
}
|
||||
|
||||
func (cus *CustomerServiceApi) GetServiceScript(c *gin.Context) {
|
||||
rType := c.Query("type")
|
||||
db := global.GVA_DB.Model(&sysModel.SysServiceScript{})
|
||||
if rType == "1" {
|
||||
serviceId, ok := c.Get("service_id")
|
||||
if serviceId != "" && ok {
|
||||
db = db.Where("service_id=?", serviceId)
|
||||
}
|
||||
} else {
|
||||
db = db.Where("service_id=?", 0)
|
||||
}
|
||||
var list []sysModel.SysServiceScript
|
||||
err := db.Order("add_time desc").Limit(20).Offset(0).Find(&list).Error
|
||||
if err != nil {
|
||||
response.FailWithMessage("查询失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(list, "获取成功", c)
|
||||
}
|
8
plugin/customerservice/api/enter.go
Normal file
8
plugin/customerservice/api/enter.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package api
|
||||
|
||||
type ApiGroup struct {
|
||||
CustomerServiceApi
|
||||
AdminServiceApi
|
||||
}
|
||||
|
||||
var ApiGroupApp = new(ApiGroup)
|
484
plugin/customerservice/api/service.go
Normal file
484
plugin/customerservice/api/service.go
Normal file
@@ -0,0 +1,484 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"git.echol.cn/loser/lckt/global"
|
||||
"git.echol.cn/loser/lckt/model/common/response"
|
||||
"git.echol.cn/loser/lckt/plugin/customerservice/model"
|
||||
"git.echol.cn/loser/lckt/plugin/customerservice/service"
|
||||
"git.echol.cn/loser/lckt/plugin/customerservice/tools"
|
||||
"github.com/gin-gonic/gin"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type AdminServiceApi struct{}
|
||||
|
||||
// GetServiceList
|
||||
// @Tags sysService
|
||||
// @Summary 客服列表
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.PageInfo true "页码, 每页大小"
|
||||
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "分页客服列表,返回包括列表,总数,页码,每页数量"
|
||||
// @Router /service/get_service_list [post]
|
||||
func (ad *AdminServiceApi) GetServiceList(c *gin.Context) {
|
||||
var pageInfo model.PageInfo
|
||||
if err := c.ShouldBindQuery(&pageInfo); err != nil {
|
||||
response.FailWithMessage("参数错误:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
limit := pageInfo.Limit
|
||||
offset := pageInfo.Limit * (pageInfo.Page - 1)
|
||||
db := global.GVA_DB.Model(&model.SysService{})
|
||||
var list []model.SysService
|
||||
var total int64
|
||||
db.Count(&total)
|
||||
err := db.Omit("password").Order("add_time desc").Limit(limit).Offset(offset).Find(&list).Error
|
||||
if err != nil {
|
||||
response.FailWithMessage("查询失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(response.PageResult{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: pageInfo.Page,
|
||||
PageSize: pageInfo.Limit,
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
// SaveService
|
||||
// @Tags sysService
|
||||
// @Summary 添加/更新客服
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request true ""
|
||||
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} ""
|
||||
// @Router /service/save_service [post]
|
||||
func (ad *AdminServiceApi) SaveService(c *gin.Context) {
|
||||
var serviceData model.SysService
|
||||
if err := c.ShouldBindJSON(&serviceData); err != nil {
|
||||
response.FailWithMessage("参数错误:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
//校验数据
|
||||
ser := service.ServiceGroupApp
|
||||
if err := ser.ValidateServiceData(&serviceData); err != nil {
|
||||
response.FailWithMessage("操作失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
var msg string
|
||||
if serviceData.Password != "" {
|
||||
hash := md5.Sum([]byte("gva-service" + serviceData.Password))
|
||||
serviceData.Password = hex.EncodeToString(hash[:])
|
||||
}
|
||||
|
||||
if serviceData.Id == 0 {
|
||||
serviceData.AddTime = time.Now().Unix()
|
||||
if err := global.GVA_DB.Create(&serviceData).Error; err != nil {
|
||||
response.FailWithMessage("添加失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
msg = "添加成功"
|
||||
} else {
|
||||
if err := global.GVA_DB.Model(&model.SysService{}).Where("id = ?", serviceData.Id).Updates(serviceData).Error; err != nil {
|
||||
response.FailWithMessage("更新失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
msg = "更新成功"
|
||||
}
|
||||
response.OkWithMessage(msg, c)
|
||||
}
|
||||
|
||||
// DeleteService
|
||||
// @Tags sysService
|
||||
// @Summary 删除客服
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.id true "id"
|
||||
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "成功消息"
|
||||
// @Router /service/delete_service?id=xx [delete]
|
||||
func (ad *AdminServiceApi) DeleteService(c *gin.Context) {
|
||||
idParam := c.Query("id")
|
||||
id, err := strconv.Atoi(idParam)
|
||||
if err != nil {
|
||||
response.FailWithMessage("参数错误:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
var ser model.SysService
|
||||
if err := global.GVA_DB.First(&ser, id).Error; err != nil {
|
||||
//if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
//
|
||||
//}
|
||||
response.FailWithMessage("用户不存在:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
// 删除用户
|
||||
if err := global.GVA_DB.Delete(&model.SysService{}, id).Error; err != nil {
|
||||
response.FailWithMessage("删除失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("删除成功", c)
|
||||
}
|
||||
|
||||
// FindService
|
||||
// @Tags sysService
|
||||
// @Summary 查找客服
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.id true "id"
|
||||
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "成功消息"
|
||||
// @Router /service/find_service?id=xx [get]
|
||||
func (ad *AdminServiceApi) FindService(c *gin.Context) {
|
||||
idParam := c.Query("id")
|
||||
id, err := strconv.Atoi(idParam)
|
||||
if err != nil {
|
||||
response.FailWithMessage("参数错误:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
var ser model.SysService
|
||||
if err := global.GVA_DB.First(&ser, id).Error; err != nil {
|
||||
response.FailWithMessage("客服不存在:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
ser.Password = ""
|
||||
response.OkWithDetailed(ser, "success", c)
|
||||
}
|
||||
|
||||
// AdminServiceLogin
|
||||
// @Tags sysService
|
||||
// @Summary 进入工作台
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.id true "id"
|
||||
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "成功消息"
|
||||
// @Router /service/delete_reply/:id [delete]
|
||||
func (ad *AdminServiceApi) AdminServiceLogin(c *gin.Context) {
|
||||
idParam := c.Query("id")
|
||||
var ser model.SysService
|
||||
if err := global.GVA_DB.First(&ser, idParam).Error; err != nil {
|
||||
response.FailWithMessage("客服不存在:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
data := map[string]interface{}{}
|
||||
|
||||
expTime, token, err := tools.GenerateToken(ser.Id)
|
||||
if err != nil {
|
||||
response.FailWithMessage("登录失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
data["token"] = token
|
||||
data["exp_time"] = expTime
|
||||
|
||||
response.OkWithDetailed(data, "success", c)
|
||||
}
|
||||
|
||||
// AccountServiceLogin
|
||||
// @Tags sysService
|
||||
// @Summary 账户密码登录
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.id true "id"
|
||||
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "成功消息"
|
||||
// @Router /service/admin_login?id=xx [get]
|
||||
func (ad *AdminServiceApi) AccountServiceLogin(c *gin.Context) {
|
||||
var loginInfo model.LoginInfo
|
||||
if err := c.ShouldBindJSON(&loginInfo); err != nil {
|
||||
response.FailWithMessage("参数错误:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
if loginInfo.Account == "" || loginInfo.Password == "" {
|
||||
response.FailWithMessage("账户或密码为空", c)
|
||||
return
|
||||
}
|
||||
var serviceInfo model.SysService
|
||||
if err := global.GVA_DB.Limit(1).Where("account=?", loginInfo.Account).Find(&serviceInfo).Error; err != nil {
|
||||
response.FailWithMessage("客服不存在:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
hash := md5.Sum([]byte("gva-service" + loginInfo.Password))
|
||||
md5Pwd := hex.EncodeToString(hash[:])
|
||||
if md5Pwd != serviceInfo.Password {
|
||||
response.FailWithMessage("密码不正确", c)
|
||||
return
|
||||
}
|
||||
|
||||
data := map[string]interface{}{}
|
||||
expTime, token, err := tools.GenerateToken(serviceInfo.Id)
|
||||
if err != nil {
|
||||
response.FailWithMessage("登录失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
data["token"] = token
|
||||
data["exp_time"] = expTime
|
||||
response.OkWithDetailed(data, "success", c)
|
||||
}
|
||||
|
||||
// GetScriptList
|
||||
// @Tags sysService
|
||||
// @Summary 客服话术列表
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.PageInfo true "页码, 每页大小"
|
||||
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "分页客服列表,返回包括列表,总数,页码,每页数量"
|
||||
// @Router /service/get_script_list [get]
|
||||
func (ad *AdminServiceApi) GetScriptList(c *gin.Context) {
|
||||
var pageInfo model.PageInfo
|
||||
if err := c.ShouldBindQuery(&pageInfo); err != nil {
|
||||
response.FailWithMessage("参数错误:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
limit := pageInfo.Limit
|
||||
offset := pageInfo.Limit * (pageInfo.Page - 1)
|
||||
db := global.GVA_DB.Model(&model.SysServiceScript{})
|
||||
var list []model.SysServiceScript
|
||||
var total int64
|
||||
db.Count(&total)
|
||||
err := db.Order("sort desc,add_time desc").Limit(limit).Offset(offset).Find(&list).Error
|
||||
if err != nil {
|
||||
response.FailWithMessage("查询失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
for k, v := range list {
|
||||
t := time.Unix(v.AddTime, 0)
|
||||
v.AddTimeStr = t.Format("2006-01-02 15:04:05")
|
||||
list[k] = v
|
||||
}
|
||||
response.OkWithDetailed(response.PageResult{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: pageInfo.Page,
|
||||
PageSize: pageInfo.Limit,
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
// SaveScript
|
||||
// @Tags sysService
|
||||
// @Summary 添加/更新客服话术
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request true ""
|
||||
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} ""
|
||||
// @Router /service/save_script [post]
|
||||
func (ad *AdminServiceApi) SaveScript(c *gin.Context) {
|
||||
var scriptData model.SysServiceScript
|
||||
if err := c.ShouldBindJSON(&scriptData); err != nil {
|
||||
response.FailWithMessage("参数错误:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
//校验数据
|
||||
ser := service.ServiceGroupApp
|
||||
if err := ser.ValidateScriptData(&scriptData); err != nil {
|
||||
response.FailWithMessage("操作失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
var msg string
|
||||
if scriptData.Id == 0 {
|
||||
scriptData.AddTime = time.Now().Unix()
|
||||
if err := global.GVA_DB.Create(&scriptData).Error; err != nil {
|
||||
response.FailWithMessage("添加失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
msg = "添加成功"
|
||||
} else {
|
||||
if err := global.GVA_DB.Model(&model.SysServiceScript{}).Where("id = ?", scriptData.Id).Updates(scriptData).Error; err != nil {
|
||||
response.FailWithMessage("更新失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
msg = "更新成功"
|
||||
}
|
||||
response.OkWithMessage(msg, c)
|
||||
}
|
||||
|
||||
// DeleteScript
|
||||
// @Tags sysService
|
||||
// @Summary 删除客服话术
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.id true "id"
|
||||
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "成功消息"
|
||||
// @Router /service/delete_script?id=xxx [delete]
|
||||
func (ad *AdminServiceApi) DeleteScript(c *gin.Context) {
|
||||
idParam := c.Query("id")
|
||||
id, err := strconv.Atoi(idParam)
|
||||
if err != nil {
|
||||
response.FailWithMessage("参数错误:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
var ser model.SysServiceScript
|
||||
if err := global.GVA_DB.First(&ser, id).Error; err != nil {
|
||||
response.FailWithMessage("话术不存在或已删除:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
// 删除
|
||||
if err := global.GVA_DB.Delete(&model.SysServiceScript{}, id).Error; err != nil {
|
||||
response.FailWithMessage("删除失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("删除成功", c)
|
||||
}
|
||||
|
||||
// FindScript
|
||||
// @Tags sysService
|
||||
// @Summary 查找话术
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.id true "id"
|
||||
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "成功消息"
|
||||
// @Router /service/find_script?id=xx [get]
|
||||
func (ad *AdminServiceApi) FindScript(c *gin.Context) {
|
||||
idParam := c.Query("id")
|
||||
id, err := strconv.Atoi(idParam)
|
||||
if err != nil {
|
||||
response.FailWithMessage("参数错误:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
var ser model.SysServiceScript
|
||||
if err := global.GVA_DB.First(&ser, id).Error; err != nil {
|
||||
response.FailWithMessage("话术不存在:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(ser, "success", c)
|
||||
}
|
||||
|
||||
// AutoReplyList
|
||||
// @Tags sysService
|
||||
// @Summary 自动回复列表
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.PageInfo true "页码, 每页大小"
|
||||
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "分页客服列表,返回包括列表,总数,页码,每页数量"
|
||||
// @Router /service/auto_reply_list [get]
|
||||
func (ad *AdminServiceApi) AutoReplyList(c *gin.Context) {
|
||||
var pageInfo model.AutoPageInfo
|
||||
if err := c.ShouldBindQuery(&pageInfo); err != nil {
|
||||
response.FailWithMessage("参数错误:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
limit := pageInfo.Limit
|
||||
offset := pageInfo.Limit * (pageInfo.Page - 1)
|
||||
db := global.GVA_DB.Model(&model.SysServiceReply{})
|
||||
var list []model.SysServiceReply
|
||||
var total int64
|
||||
db.Count(&total)
|
||||
err := db.Order("add_time desc").Limit(limit).Offset(offset).Find(&list).Error
|
||||
if err != nil {
|
||||
response.FailWithMessage("查询失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
for k, v := range list {
|
||||
t := time.Unix(v.AddTime, 0)
|
||||
v.AddTimeStr = t.Format("2006-01-02 15:04:05")
|
||||
list[k] = v
|
||||
}
|
||||
response.OkWithDetailed(response.PageResult{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: pageInfo.Page,
|
||||
PageSize: pageInfo.Limit,
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
// SaveReply
|
||||
// @Tags sysService
|
||||
// @Summary 添加/更新自动回复
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request true ""
|
||||
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} ""
|
||||
// @Router /service/save_reply [post]
|
||||
func (ad *AdminServiceApi) SaveReply(c *gin.Context) {
|
||||
var replyData model.SysServiceReply
|
||||
if err := c.ShouldBindJSON(&replyData); err != nil {
|
||||
response.FailWithMessage("参数错误:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
//校验数据
|
||||
ser := service.ServiceGroupApp
|
||||
if err := ser.ValidateReplyData(&replyData); err != nil {
|
||||
response.FailWithMessage("操作失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
var msg string
|
||||
if replyData.Id == 0 {
|
||||
replyData.AddTime = time.Now().Unix()
|
||||
if err := global.GVA_DB.Create(&replyData).Error; err != nil {
|
||||
response.FailWithMessage("添加失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
msg = "添加成功"
|
||||
} else {
|
||||
if err := global.GVA_DB.Model(&model.SysServiceReply{}).Where("id = ?", replyData.Id).Updates(replyData).Error; err != nil {
|
||||
response.FailWithMessage("更新失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
msg = "更新成功"
|
||||
}
|
||||
response.OkWithMessage(msg, c)
|
||||
}
|
||||
|
||||
// DeleteReply
|
||||
// @Tags sysService
|
||||
// @Summary 删除自动回复
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.id true "id"
|
||||
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "成功消息"
|
||||
// @Router /service/delete_reply?id=xx [delete]
|
||||
func (ad *AdminServiceApi) DeleteReply(c *gin.Context) {
|
||||
idParam := c.Query("id")
|
||||
id, err := strconv.Atoi(idParam)
|
||||
if err != nil {
|
||||
response.FailWithMessage("参数错误:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
var ser model.SysServiceReply
|
||||
if err := global.GVA_DB.First(&ser, id).Error; err != nil {
|
||||
response.FailWithMessage("内容不存在或已删除:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
// 删除数据
|
||||
if err := global.GVA_DB.Delete(&model.SysServiceReply{}, id).Error; err != nil {
|
||||
response.FailWithMessage("删除失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("删除成功", c)
|
||||
}
|
||||
|
||||
// FindReply
|
||||
// @Tags sysService
|
||||
// @Summary 查找自动回复详情
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.id true "id"
|
||||
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "成功消息"
|
||||
// @Router /service/find_reply?id=xxx [get]
|
||||
func (ad *AdminServiceApi) FindReply(c *gin.Context) {
|
||||
idParam := c.Query("id")
|
||||
id, err := strconv.Atoi(idParam)
|
||||
if err != nil {
|
||||
response.FailWithMessage("参数错误:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
var ser model.SysServiceReply
|
||||
if err := global.GVA_DB.First(&ser, id).Error; err != nil {
|
||||
response.FailWithMessage("自动回复内容不存在:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(ser, "success", c)
|
||||
}
|
1
plugin/customerservice/config/config.go
Normal file
1
plugin/customerservice/config/config.go
Normal file
@@ -0,0 +1 @@
|
||||
package config
|
1
plugin/customerservice/global/global.go
Normal file
1
plugin/customerservice/global/global.go
Normal file
@@ -0,0 +1 @@
|
||||
package global
|
214
plugin/customerservice/main.go
Normal file
214
plugin/customerservice/main.go
Normal file
@@ -0,0 +1,214 @@
|
||||
package customerservice
|
||||
|
||||
import (
|
||||
gvaGlobal "git.echol.cn/loser/lckt/global"
|
||||
"git.echol.cn/loser/lckt/model/system"
|
||||
"git.echol.cn/loser/lckt/plugin/customerservice/model"
|
||||
"git.echol.cn/loser/lckt/plugin/customerservice/router"
|
||||
"git.echol.cn/loser/lckt/plugin/customerservice/service/ws"
|
||||
"git.echol.cn/loser/lckt/plugin/plugin-tool/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type CustomerServicePlugin struct {
|
||||
}
|
||||
|
||||
func CreateCustomerServicePlug() *CustomerServicePlugin {
|
||||
go func() {
|
||||
err := gvaGlobal.GVA_DB.AutoMigrate(model.SysService{},
|
||||
model.SysServiceMsg{},
|
||||
model.SysServiceRecord{},
|
||||
model.SysServiceReply{},
|
||||
model.SysServiceScript{},
|
||||
model.SysTestUser{})
|
||||
if err != nil {
|
||||
gvaGlobal.GVA_LOG.Error("自动创建表失败", zap.Error(err))
|
||||
} else {
|
||||
gvaGlobal.GVA_LOG.Info("自动创建表成功")
|
||||
}
|
||||
}() // 此处可以把插件依赖的数据库结构体自动创建表 需要填写对应的结构体
|
||||
// 下方会自动注册菜单 第一个参数为菜单一级路由信息一般为定义好的组名 第二个参数为真实使用的web页面路由信息
|
||||
// 具体值请根据实际情况修改
|
||||
utils.RegisterMenus(
|
||||
system.SysBaseMenu{
|
||||
Path: "service",
|
||||
Name: "客服管理",
|
||||
Hidden: false,
|
||||
Component: "view/routerHolder.vue",
|
||||
Sort: 4,
|
||||
Meta: system.Meta{
|
||||
Title: "客服管理",
|
||||
Icon: "service",
|
||||
},
|
||||
},
|
||||
system.SysBaseMenu{
|
||||
Path: "index",
|
||||
Name: "客服列表",
|
||||
Hidden: false,
|
||||
Component: "plugin/customerservice/view/service/index.vue",
|
||||
Sort: 1,
|
||||
Meta: system.Meta{
|
||||
Title: "客服列表",
|
||||
Icon: "service",
|
||||
},
|
||||
},
|
||||
system.SysBaseMenu{
|
||||
Path: "script/list",
|
||||
Name: "客服话术",
|
||||
Hidden: false,
|
||||
Component: "plugin/customerservice/view/script/index.vue",
|
||||
Sort: 2,
|
||||
Meta: system.Meta{
|
||||
Title: "客服话术",
|
||||
Icon: "document",
|
||||
},
|
||||
},
|
||||
system.SysBaseMenu{
|
||||
Path: "reply/list",
|
||||
Name: "自动回复",
|
||||
Hidden: false,
|
||||
Component: "plugin/customerservice/view/reply/index.vue",
|
||||
Sort: 3,
|
||||
Meta: system.Meta{
|
||||
Title: "自动回复",
|
||||
Icon: "bell-filled",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
// 下方会自动注册api 以下格式为示例格式,请按照实际情况修改
|
||||
utils.RegisterApis(
|
||||
//system.SysApi{
|
||||
// Path: "/service/ws",
|
||||
// Description: "用户连接接口",
|
||||
// ApiGroup: "客服管理",
|
||||
// Method: "GET",
|
||||
//},
|
||||
//system.SysApi{
|
||||
// Path: "/service/serve_ws",
|
||||
// Description: "客服连接接口",
|
||||
// ApiGroup: "客服管理",
|
||||
// Method: "GET",
|
||||
//},
|
||||
//system.SysApi{
|
||||
// Path: "/service/send_msg",
|
||||
// Description: "发送消息接口",
|
||||
// ApiGroup: "客服管理",
|
||||
// Method: "POST",
|
||||
//},
|
||||
//system.SysApi{
|
||||
// Path: "/service/get_msg_list",
|
||||
// Description: "消息列表",
|
||||
// ApiGroup: "客服管理",
|
||||
// Method: "GET",
|
||||
//},
|
||||
//system.SysApi{
|
||||
// Path: "/service/get_msg_user",
|
||||
// Description: "客服聊天用户列表",
|
||||
// ApiGroup: "客服管理",
|
||||
// Method: "GET",
|
||||
//},
|
||||
//system.SysApi{
|
||||
// Path: "/service/get_kf_info",
|
||||
// Description: "当前客服详情",
|
||||
// ApiGroup: "客服管理",
|
||||
// Method: "GET",
|
||||
//},
|
||||
//system.SysApi{
|
||||
// Path: "/service/set_msg_view",
|
||||
// Description: "设置已读",
|
||||
// ApiGroup: "客服管理",
|
||||
// Method: "GET",
|
||||
//},
|
||||
system.SysApi{
|
||||
Path: "/service/get_service_list",
|
||||
Description: "后台客服列表",
|
||||
ApiGroup: "客服管理",
|
||||
Method: "GET",
|
||||
},
|
||||
system.SysApi{
|
||||
Path: "/service/save_service",
|
||||
Description: "后台客服新增/更新",
|
||||
ApiGroup: "客服管理",
|
||||
Method: "POST",
|
||||
},
|
||||
system.SysApi{
|
||||
Path: "/service/delete_service",
|
||||
Description: "删除客服",
|
||||
ApiGroup: "客服管理",
|
||||
Method: "DELETE",
|
||||
},
|
||||
system.SysApi{
|
||||
Path: "/service/find_service",
|
||||
Description: "客服详情",
|
||||
ApiGroup: "客服管理",
|
||||
Method: "GET",
|
||||
},
|
||||
system.SysApi{
|
||||
Path: "/service/get_script_list",
|
||||
Description: "客服话术列表",
|
||||
ApiGroup: "客服管理",
|
||||
Method: "GET",
|
||||
},
|
||||
system.SysApi{
|
||||
Path: "/service/save_script",
|
||||
Description: "客服话术新增/更新",
|
||||
ApiGroup: "客服管理",
|
||||
Method: "POST",
|
||||
},
|
||||
system.SysApi{
|
||||
Path: "/service/delete_script",
|
||||
Description: "删除客服话术",
|
||||
ApiGroup: "客服管理",
|
||||
Method: "DELETE",
|
||||
},
|
||||
system.SysApi{
|
||||
Path: "/service/find_script",
|
||||
Description: "客服话术详情",
|
||||
ApiGroup: "客服管理",
|
||||
Method: "GET",
|
||||
},
|
||||
system.SysApi{
|
||||
Path: "/service/auto_reply_list",
|
||||
Description: "自动回复列表",
|
||||
ApiGroup: "客服管理",
|
||||
Method: "GET",
|
||||
},
|
||||
system.SysApi{
|
||||
Path: "/service/save_reply",
|
||||
Description: "自动回复新增/更新",
|
||||
ApiGroup: "客服管理",
|
||||
Method: "POST",
|
||||
},
|
||||
system.SysApi{
|
||||
Path: "/service/delete_reply",
|
||||
Description: "删除自动回复",
|
||||
ApiGroup: "客服管理",
|
||||
Method: "DELETE",
|
||||
},
|
||||
system.SysApi{
|
||||
Path: "/service/find_reply",
|
||||
Description: "自动回复详情",
|
||||
ApiGroup: "客服管理",
|
||||
Method: "GET",
|
||||
},
|
||||
system.SysApi{
|
||||
Path: "/service/admin_login",
|
||||
Description: "进入客服工作台",
|
||||
ApiGroup: "客服管理",
|
||||
Method: "GET",
|
||||
},
|
||||
)
|
||||
go ws.Manager.Start()
|
||||
go ws.Manager.CheckClientActivity()
|
||||
return &CustomerServicePlugin{}
|
||||
}
|
||||
|
||||
func (*CustomerServicePlugin) Register(group *gin.RouterGroup) {
|
||||
router.RouterGroupApp.InitCustomerServiceRouter(group)
|
||||
}
|
||||
|
||||
func (*CustomerServicePlugin) RouterPath() string {
|
||||
return ""
|
||||
}
|
57
plugin/customerservice/middleware/jwt.go
Normal file
57
plugin/customerservice/middleware/jwt.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/lckt/model/common/response"
|
||||
"git.echol.cn/loser/lckt/plugin/customerservice/tools"
|
||||
"git.echol.cn/loser/lckt/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func JWTAuthMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// 从请求头获取 token
|
||||
authHeader := c.GetHeader("chat-token")
|
||||
userHeader := c.GetHeader("x-token")
|
||||
if userHeader == "" && authHeader == "" {
|
||||
response.FailWithMessage("参数错误:"+"Authorization header is missing", c)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
if authHeader != "" {
|
||||
// 按照格式 "Bearer <token>" 提取 token
|
||||
tokenString := strings.TrimSpace(strings.TrimPrefix(authHeader, "Bearer "))
|
||||
if tokenString == "" {
|
||||
response.FailWithMessage("参数错误:"+"Token is missing", c)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
// 验证 token
|
||||
claims, err := tools.ValidateToken(tokenString)
|
||||
if err != nil {
|
||||
response.FailWithMessage("Invalid token:"+err.Error(), c)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// 将用户信息存储在上下文中,便于后续处理
|
||||
c.Set("service_id", claims.ServiceId)
|
||||
//c.Request.URL.Query().Add("service_id", strconv.FormatInt(claims.ServiceId, 10))
|
||||
c.Next() // 继续处理请求
|
||||
|
||||
} else {
|
||||
//为了方便客服后台和前端客服聊天界面公用方法,共用同一套jwt,前端的jwt的值取的还是gva-shop的x-token
|
||||
j := utils.NewJWT()
|
||||
claims, err := j.ParseToken(userHeader)
|
||||
if err != nil {
|
||||
response.FailWithMessage("参数错误:"+"Token is error", c)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
c.Set("jwt_user_id", claims.BaseClaims.ID)
|
||||
c.Next()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
25
plugin/customerservice/model/model.go
Normal file
25
plugin/customerservice/model/model.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package model
|
||||
|
||||
type PageInfo struct {
|
||||
Page int `json:"page" form:"page"`
|
||||
Limit int `json:"limit" form:"limit"`
|
||||
Keyword string `json:"keyword" form:"keyword"`
|
||||
}
|
||||
|
||||
type MsgPageInfo struct {
|
||||
Page int `json:"page" form:"page"`
|
||||
Limit int `json:"limit" form:"limit"`
|
||||
FromId int `json:"from_id" form:"from_id"`
|
||||
}
|
||||
|
||||
type AutoPageInfo struct {
|
||||
Page int `json:"page" form:"page"`
|
||||
Limit int `json:"limit" form:"limit"`
|
||||
Keyword string `json:"keyword" form:"keyword"`
|
||||
ReplyType int `json:"reply_type" form:"reply_type"`
|
||||
}
|
||||
|
||||
type LoginInfo struct {
|
||||
Account string `json:"account" form:"account"`
|
||||
Password string `json:"password" form:"password"`
|
||||
}
|
18
plugin/customerservice/model/sysService.go
Normal file
18
plugin/customerservice/model/sysService.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package model
|
||||
|
||||
type SysService struct {
|
||||
Id int64 `json:"id" form:"id" gorm:"primarykey"`
|
||||
MerchantId uint `json:"merchant_id" form:"merchant_id" gorm:"default:0;type:int;column:merchant_id;comment:商户id;"`
|
||||
Uid uint `json:"uid" form:"uid" gorm:"default:0;type:int;column:uid;comment:用户id;"`
|
||||
Online uint `json:"online" form:"online" gorm:"default:0;type:tinyint;column:online;comment:客服是否在线;"`
|
||||
Account string `json:"account" form:"account" gorm:"default:'';type:varchar(255);column:account;comment:账户;"`
|
||||
Password string `json:"password" form:"password" gorm:"default:'';type:varchar(255);column:password;comment:密码;"`
|
||||
Avatar string `json:"avatar" form:"avatar" gorm:"default:'';type:varchar(255);column:avatar;comment:头像;"`
|
||||
Nickname string `json:"nickname" form:"nickname" gorm:"default:'';type:varchar(255);column:nickname;comment:客服名称;"`
|
||||
AddTime int64 `json:"add_time" form:"add_time" gorm:"default:0;type:int;column:add_time;comment:添加时间;"`
|
||||
Status *uint `json:"status" form:"status" gorm:"default:0;type:tinyint(1);column:status;comment:是否显示;"`
|
||||
}
|
||||
|
||||
func (SysService) TableName() string {
|
||||
return "sys_service"
|
||||
}
|
18
plugin/customerservice/model/sysServiceMsg.go
Normal file
18
plugin/customerservice/model/sysServiceMsg.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package model
|
||||
|
||||
type SysServiceMsg struct {
|
||||
Id uint `gorm:"primarykey" json:"id"` // 主键ID
|
||||
MerchantId uint `json:"merchant_id" form:"merchant_id" gorm:"default:0;type:int;column:merchant_id;comment:商户id;"`
|
||||
Content string `json:"content" form:"content" gorm:"type:text;column:content;comment:消息内容;"`
|
||||
ServiceId int64 `json:"service_id" form:"service_id" gorm:"default:0;type:int;column:service_id;comment:客服id;"`
|
||||
Uid int64 `json:"uid" form:"uid" gorm:"default:0;type:int;column:uid;comment:用户id;"`
|
||||
IsTourist uint `json:"is_tourist" form:"is_tourist" gorm:"default:0;type:tinyint;column:is_tourist;comment:是否游客;"`
|
||||
IsView uint `json:"is_view" form:"is_view" gorm:"default:0;type:tinyint;column:is_view;comment:是否已读;"`
|
||||
AddTime int `json:"add_time" form:"add_time" gorm:"default:0;type:int;column:add_time;comment:添加时间;"`
|
||||
MsgType int64 `json:"msg_type" form:"msg_type" gorm:"default:1;type:tinyint;column:msg_type;comment:消息类型 1=文字 2=表情 3=图片 4=语音 5=视频 6=商品;"`
|
||||
IsKf int64 `json:"is_kf" form:"is_kf" gorm:"default:0;type:tinyint;column:is_kf;comment:是否客服消息;"`
|
||||
}
|
||||
|
||||
func (SysServiceMsg) TableName() string {
|
||||
return "sys_service_msg"
|
||||
}
|
21
plugin/customerservice/model/sysServiceRecord.go
Normal file
21
plugin/customerservice/model/sysServiceRecord.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package model
|
||||
|
||||
type SysServiceRecord struct {
|
||||
Id uint `json:"id" form:"id" gorm:"primarykey"`
|
||||
ServiceId int64 `json:"service_id" form:"service_id" gorm:"default:0;type:int;column:service_id;comment:客服id;"`
|
||||
Uid int64 `json:"uid" form:"uid" gorm:"default:0;type:int;column:uid;comment:用户id;"`
|
||||
Avatar string `json:"avatar" form:"avatar" gorm:"default:'';type:varchar(255);column:avatar;comment:用户头像;"`
|
||||
Nickname string `json:"nickname" form:"nickname" gorm:"default:'';type:varchar(255);column:nickname;comment:用户昵称;"`
|
||||
Online uint `json:"online" form:"online" gorm:"default:0;type:tinyint;column:online;comment:是否在线;"`
|
||||
IsTourist uint `json:"is_tourist" form:"is_tourist" gorm:"default:0;type:tinyint;column:is_tourist;comment:是否游客0:否;1:是;"`
|
||||
Message string `json:"message" form:"message" gorm:"type:text;column:message;comment:最新一条消息;"`
|
||||
AddTime int64 `json:"add_time" form:"add_time" gorm:"default:0;type:int;column:add_time;comment:添加时间;"`
|
||||
UpdateTime int64 `json:"update_time" form:"update_time" gorm:"default:0;type:int;column:update_time;comment:更新时间;"`
|
||||
MessageType int64 `json:"message_type" form:"message_type" gorm:"default:0;type:tinyint(1);column:message_type;comment:消息类型:1=文字 2=表情 3=图片 4=语音 5=视频 6=商品;"`
|
||||
NoRead int64 `json:"no_read" gorm:"-"`
|
||||
AddTimeStr string `json:"add_time_str" gorm:"-"`
|
||||
}
|
||||
|
||||
func (SysServiceRecord) TableName() string {
|
||||
return "sys_service_record"
|
||||
}
|
16
plugin/customerservice/model/sysServiceReply.go
Normal file
16
plugin/customerservice/model/sysServiceReply.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package model
|
||||
|
||||
type SysServiceReply struct {
|
||||
Id int64 `json:"id" form:"id" gorm:"primarykey"`
|
||||
ReplyType int64 `json:"reply_type" form:"reply_type" gorm:"default:1;type:int;column:reply_type;comment:回复类型1文本,2图片;"`
|
||||
IsComplete int64 `json:"is_complete" form:"is_complete" gorm:"default:0;type:int;column:is_complete;comment:是否完全匹配0否1是;"`
|
||||
Keyword string `json:"keyword" form:"keyword" gorm:"default:'';type:varchar(255);column:keyword;comment:关键字;"`
|
||||
Content string `json:"content" form:"content" gorm:"type:text;column:content;comment:回复内容;"`
|
||||
AddTime int64 `json:"add_time" form:"add_time" gorm:"default:0;type:int;column:add_time;comment:添加时间;"`
|
||||
AddTimeStr string `json:"add_time_str" form:"add_time_str" gorm:"-"`
|
||||
Status int64 `json:"status" form:"status" gorm:"default:0;type:tinyint(1);column:status;comment:是否显示;"`
|
||||
}
|
||||
|
||||
func (SysServiceReply) TableName() string {
|
||||
return "sys_service_reply"
|
||||
}
|
15
plugin/customerservice/model/sysServiceScript.go
Normal file
15
plugin/customerservice/model/sysServiceScript.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package model
|
||||
|
||||
type SysServiceScript struct {
|
||||
Id int64 `json:"id" form:"id" gorm:"primarykey"`
|
||||
ServiceId int64 `json:"service_id" form:"service_id" gorm:"default:0;type:int;column:service_id;comment:客服id为0说明是公共话术;"`
|
||||
Title string `json:"title" form:"title" gorm:"default:'';type:varchar(255);column:title;comment:话术标题;"`
|
||||
Content string `json:"content" form:"content" gorm:"type:text;column:content;comment:话术内容;"`
|
||||
AddTime int64 `json:"add_time" form:"add_time" gorm:"default:0;type:int;column:add_time;comment:添加时间;"`
|
||||
AddTimeStr string `json:"add_time_str" form:"add_time_str" gorm:"-"`
|
||||
Sort int64 `json:"sort" form:"sort" gorm:"default:0;type:int;column:sort;comment:排序;"`
|
||||
}
|
||||
|
||||
func (SysServiceScript) TableName() string {
|
||||
return "sys_service_script"
|
||||
}
|
11
plugin/customerservice/model/sysTestUser.go
Normal file
11
plugin/customerservice/model/sysTestUser.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package model
|
||||
|
||||
type SysTestUser struct {
|
||||
Id int64 `json:"id" form:"id" gorm:"primarykey"`
|
||||
Avatar string `json:"avatar" form:"avatar" gorm:"default:'';type:varchar(255);column:avatar;comment:头像;"`
|
||||
Nickname string `json:"nickname" form:"nickname" gorm:"default:'';type:varchar(255);column:nickname;comment:昵称;"`
|
||||
}
|
||||
|
||||
func (SysTestUser) TableName() string {
|
||||
return "sys_test_user"
|
||||
}
|
7
plugin/customerservice/router/enter.go
Normal file
7
plugin/customerservice/router/enter.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package router
|
||||
|
||||
type RouterGroup struct {
|
||||
CustomerServiceRouter
|
||||
}
|
||||
|
||||
var RouterGroupApp = new(RouterGroup)
|
48
plugin/customerservice/router/router.go
Normal file
48
plugin/customerservice/router/router.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/lckt/middleware"
|
||||
"git.echol.cn/loser/lckt/plugin/customerservice/api"
|
||||
serMiddleware "git.echol.cn/loser/lckt/plugin/customerservice/middleware"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type CustomerServiceRouter struct {
|
||||
}
|
||||
|
||||
func (s *CustomerServiceRouter) InitCustomerServiceRouter(Router *gin.RouterGroup) {
|
||||
wsRouter := Router.Group("")
|
||||
plugServiceRouter := Router.Group("").Use(serMiddleware.JWTAuthMiddleware()).Use(middleware.Cors())
|
||||
//plugRouter := Router.Group("").Use(middleware.JWTAuth())
|
||||
privateRouter := Router.Group("").Use(middleware.JWTAuth()).Use(middleware.CasbinHandler())
|
||||
plugAdminApi := api.ApiGroupApp.AdminServiceApi
|
||||
{
|
||||
privateRouter.GET("/service/get_service_list", plugAdminApi.GetServiceList)
|
||||
privateRouter.POST("/service/save_service", plugAdminApi.SaveService)
|
||||
privateRouter.DELETE("/service/delete_service", plugAdminApi.DeleteService)
|
||||
privateRouter.GET("/service/find_service", plugAdminApi.FindService)
|
||||
privateRouter.GET("/service/admin_login", plugAdminApi.AdminServiceLogin)
|
||||
privateRouter.GET("/service/get_script_list", plugAdminApi.GetScriptList)
|
||||
privateRouter.POST("/service/save_script", plugAdminApi.SaveScript)
|
||||
privateRouter.DELETE("/service/delete_script", plugAdminApi.DeleteScript)
|
||||
privateRouter.GET("/service/find_script", plugAdminApi.FindScript)
|
||||
privateRouter.GET("/service/auto_reply_list", plugAdminApi.AutoReplyList)
|
||||
privateRouter.POST("/service/save_reply", plugAdminApi.SaveReply)
|
||||
privateRouter.DELETE("/service/delete_reply", plugAdminApi.DeleteReply)
|
||||
privateRouter.GET("/service/find_reply", plugAdminApi.FindReply)
|
||||
}
|
||||
plugApi := api.ApiGroupApp.CustomerServiceApi
|
||||
{
|
||||
plugServiceRouter.POST("/service/send_msg", plugApi.SendMsg)
|
||||
plugServiceRouter.GET("/service/get_msg_list", plugApi.GetMsgList)
|
||||
plugServiceRouter.GET("/service/get_kf_info", plugApi.GetKefuInfo)
|
||||
plugServiceRouter.POST("/service/upload_file", plugApi.UploadFile)
|
||||
plugServiceRouter.GET("/service/get_user_info", plugApi.GetUserInfo)
|
||||
plugServiceRouter.GET("/service/get_msg_user", plugApi.GetMsgUser)
|
||||
plugServiceRouter.GET("/service/get_service_script", plugApi.GetServiceScript)
|
||||
plugServiceRouter.GET("/service/set_msg_view", plugApi.SetMsgView)
|
||||
}
|
||||
wsRouter.GET("/service/serve_ws", plugApi.ServeWsForKefu)
|
||||
wsRouter.GET("/service/ws", plugApi.ServeWs)
|
||||
wsRouter.POST("/service/account_login", plugAdminApi.AccountServiceLogin)
|
||||
}
|
7
plugin/customerservice/service/enter.go
Normal file
7
plugin/customerservice/service/enter.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package service
|
||||
|
||||
type ServiceGroup struct {
|
||||
CustomerServiceService
|
||||
}
|
||||
|
||||
var ServiceGroupApp = new(ServiceGroup)
|
130
plugin/customerservice/service/service.go
Normal file
130
plugin/customerservice/service/service.go
Normal file
@@ -0,0 +1,130 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"git.echol.cn/loser/lckt/global"
|
||||
"git.echol.cn/loser/lckt/model/user"
|
||||
"git.echol.cn/loser/lckt/plugin/customerservice/model"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
"time"
|
||||
)
|
||||
|
||||
type CustomerServiceService struct{}
|
||||
|
||||
func (e *CustomerServiceService) PlugService() (err error) {
|
||||
// 写你的业务逻辑
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *CustomerServiceService) ValidateServiceData(sys *model.SysService) error {
|
||||
if sys.Uid == 0 {
|
||||
return errors.New("客服关联的用户id不能为空")
|
||||
} else {
|
||||
db := global.GVA_DB.Model(&model.SysService{})
|
||||
if sys.Id > 0 {
|
||||
db = db.Where("uid=?", sys.Uid).Where("id<>?", sys.Id)
|
||||
} else {
|
||||
db = db.Where("uid=?", sys.Uid)
|
||||
}
|
||||
var dCount int64
|
||||
db.Count(&dCount)
|
||||
if dCount > 0 {
|
||||
return errors.New("用户id已关联其他客服,请重新输入")
|
||||
}
|
||||
}
|
||||
db := global.GVA_DB.Model(&model.SysService{})
|
||||
if sys.Id == 0 {
|
||||
if sys.Password == "" {
|
||||
return errors.New("客服密码必须填写")
|
||||
}
|
||||
db = db.Where("account=?", sys.Account)
|
||||
} else {
|
||||
db = db.Where("account=?", sys.Account).Where("id<>?", sys.Id)
|
||||
var dCount int64
|
||||
db.Count(&dCount)
|
||||
if dCount > 0 {
|
||||
return errors.New("账户已存在,请重新输入")
|
||||
}
|
||||
}
|
||||
if sys.Account == "" {
|
||||
return errors.New("客服账户必须填写")
|
||||
}
|
||||
if sys.Nickname == "" {
|
||||
return errors.New("客服名称必须填写")
|
||||
}
|
||||
if sys.Avatar == "" {
|
||||
return errors.New("客服头像必须选择")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *CustomerServiceService) ValidateScriptData(sys *model.SysServiceScript) error {
|
||||
if sys.Title == "" {
|
||||
return errors.New("话术标题必须填写")
|
||||
}
|
||||
if sys.Content == "" {
|
||||
return errors.New("话术内容必须填写")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *CustomerServiceService) ValidateReplyData(sys *model.SysServiceReply) error {
|
||||
if sys.Keyword == "" {
|
||||
return errors.New("关键字必须填写")
|
||||
}
|
||||
if sys.Content == "" {
|
||||
return errors.New("回复内容必须填写")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *CustomerServiceService) GetUrlHost(c *gin.Context) string {
|
||||
host := c.Request.Host
|
||||
scheme := "http"
|
||||
if c.Request.TLS != nil {
|
||||
scheme = "https"
|
||||
}
|
||||
referer := c.Request.Referer()
|
||||
if referer != "" {
|
||||
return referer
|
||||
}
|
||||
return scheme + "://" + host + "/"
|
||||
}
|
||||
|
||||
func (e *CustomerServiceService) CreateMsg(kfInfo model.SysService, userInfo user.User, msgType int64, content string, isKf string) (err error) {
|
||||
msgRecord := &model.SysServiceRecord{
|
||||
ServiceId: kfInfo.Id,
|
||||
Uid: int64(userInfo.ID),
|
||||
Message: base64.StdEncoding.EncodeToString([]byte(content)),
|
||||
MessageType: msgType,
|
||||
UpdateTime: time.Now().Unix(),
|
||||
Avatar: userInfo.Avatar,
|
||||
Nickname: userInfo.NickName,
|
||||
Online: 1,
|
||||
}
|
||||
var record model.SysServiceRecord
|
||||
|
||||
eErr := global.GVA_DB.Where("service_id = ?", kfInfo.Id).Where("uid = ?", userInfo.ID).First(&record).Error
|
||||
if errors.Is(eErr, gorm.ErrRecordNotFound) {
|
||||
msgRecord.AddTime = time.Now().Unix()
|
||||
global.GVA_DB.Create(msgRecord)
|
||||
} else {
|
||||
global.GVA_DB.Model(&model.SysServiceRecord{}).Where("id = ?", record.Id).Updates(msgRecord)
|
||||
}
|
||||
|
||||
//插入消息记录
|
||||
msg := map[string]interface{}{
|
||||
"service_id": kfInfo.Id,
|
||||
"uid": userInfo.ID,
|
||||
"content": base64.StdEncoding.EncodeToString([]byte(content)),
|
||||
"msg_type": msgType,
|
||||
"is_view": 0,
|
||||
"add_time": time.Now().Unix(),
|
||||
"is_kf": isKf,
|
||||
}
|
||||
err = global.GVA_DB.Model(&model.SysServiceMsg{}).Create(msg).Error
|
||||
return err
|
||||
}
|
271
plugin/customerservice/service/ws/ws.go
Normal file
271
plugin/customerservice/service/ws/ws.go
Normal file
@@ -0,0 +1,271 @@
|
||||
package ws
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"git.echol.cn/loser/lckt/global"
|
||||
"git.echol.cn/loser/lckt/model/common/response"
|
||||
sysModel "git.echol.cn/loser/lckt/plugin/customerservice/model"
|
||||
"git.echol.cn/loser/lckt/plugin/customerservice/tools"
|
||||
"git.echol.cn/loser/lckt/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/websocket"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Message struct {
|
||||
Sender string `json:"sender"`
|
||||
Receiver string `json:"receiver"`
|
||||
Content string `json:"content"`
|
||||
MsgType string `json:"msg_type"` //对应msg表的msg_type
|
||||
Role string `json:"role"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Nickname string `json:"nickname"`
|
||||
AvatarUrl string `json:"avatar_url"`
|
||||
IsKf int64 `json:"is_kf"`
|
||||
}
|
||||
|
||||
type TypeMsg struct {
|
||||
Type string `json:"type"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
UserID string
|
||||
Role string
|
||||
Socket *websocket.Conn
|
||||
Send chan []byte
|
||||
LastPingTime time.Time
|
||||
}
|
||||
|
||||
type ClientManager struct {
|
||||
Clients map[string]*Client
|
||||
Broadcast chan TypeMsg
|
||||
Register chan *Client
|
||||
Unregister chan *Client
|
||||
}
|
||||
|
||||
var Manager = ClientManager{
|
||||
Clients: make(map[string]*Client),
|
||||
Broadcast: make(chan TypeMsg),
|
||||
Register: make(chan *Client),
|
||||
Unregister: make(chan *Client),
|
||||
}
|
||||
|
||||
// 定时检查连接的活动状态
|
||||
func (manager *ClientManager) CheckClientActivity() {
|
||||
ticker := time.NewTicker(5 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
<-ticker.C
|
||||
now := time.Now()
|
||||
|
||||
for ck, client := range manager.Clients {
|
||||
// 如果超过一定时间没有收到ping,则断开连接
|
||||
fmt.Println(ck)
|
||||
fmt.Println(now.Sub(client.LastPingTime))
|
||||
if now.Sub(client.LastPingTime) > 120*time.Second {
|
||||
client.Socket.Close()
|
||||
delete(manager.Clients, ck)
|
||||
//设置离线
|
||||
if client.Role == "user" {
|
||||
setUserOnline("offline", client.UserID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (manager *ClientManager) Start() {
|
||||
for {
|
||||
select {
|
||||
case conn := <-manager.Register:
|
||||
key := conn.Role + conn.UserID
|
||||
if existingConn, ok := manager.Clients[key]; ok {
|
||||
existingConn.Socket.Close()
|
||||
delete(manager.Clients, key)
|
||||
}
|
||||
fmt.Println(key)
|
||||
manager.Clients[key] = conn
|
||||
case conn := <-manager.Unregister:
|
||||
key := conn.Role + conn.UserID
|
||||
if existingConn, ok := manager.Clients[key]; ok && existingConn == conn {
|
||||
delete(manager.Clients, key)
|
||||
}
|
||||
case message := <-manager.Broadcast:
|
||||
data := message.Data.(map[string]interface{})
|
||||
receiver := data["receiver"].(string)
|
||||
receiverKey := "user" + receiver
|
||||
if data["role"].(string) == "user" {
|
||||
receiverKey = "kf" + receiver
|
||||
}
|
||||
if client, ok := manager.Clients[receiverKey]; ok {
|
||||
str, _ := json.Marshal(message)
|
||||
client.Send <- str
|
||||
} else {
|
||||
fmt.Println(receiverKey + "链接不存在")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) Read() {
|
||||
defer func() {
|
||||
Manager.Unregister <- c
|
||||
c.Socket.Close()
|
||||
}()
|
||||
c.Socket.SetReadLimit(512)
|
||||
|
||||
for {
|
||||
_, message, err := c.Socket.ReadMessage()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
var msg TypeMsg
|
||||
if err := json.Unmarshal(message, &msg); err != nil {
|
||||
continue
|
||||
}
|
||||
switch msg.Type {
|
||||
case "ping":
|
||||
// 更新最后一次收到ping消息的时间
|
||||
c.LastPingTime = time.Now()
|
||||
|
||||
// 回复pong消息
|
||||
pongMsg := TypeMsg{
|
||||
Type: "pong",
|
||||
Data: time.Now().Unix(),
|
||||
}
|
||||
pongStr, _ := json.Marshal(pongMsg)
|
||||
c.Send <- pongStr
|
||||
|
||||
case "message":
|
||||
//发送消息走的后台接口去触发广播,改成前端发送消息走这里
|
||||
Manager.Broadcast <- msg
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) Write() {
|
||||
defer func() {
|
||||
c.Socket.Close()
|
||||
}()
|
||||
for {
|
||||
select {
|
||||
case message, ok := <-c.Send:
|
||||
c.Socket.SetWriteDeadline(time.Now().Add(10 * time.Second))
|
||||
if !ok {
|
||||
c.Socket.WriteMessage(websocket.CloseMessage, []byte{})
|
||||
return
|
||||
}
|
||||
if err := c.Socket.WriteMessage(websocket.TextMessage, message); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var Upgrader = websocket.Upgrader{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
|
||||
func WsServe(ctx *gin.Context) {
|
||||
//token := ctx.Query("token")
|
||||
token := ctx.Query("token")
|
||||
j := utils.NewJWT()
|
||||
claims, err := j.ParseToken(token)
|
||||
if err != nil {
|
||||
if errors.Is(err, utils.TokenExpired) {
|
||||
http.NotFound(ctx.Writer, ctx.Request)
|
||||
return
|
||||
}
|
||||
}
|
||||
conn, err := Upgrader.Upgrade(ctx.Writer, ctx.Request, nil)
|
||||
if err != nil {
|
||||
http.NotFound(ctx.Writer, ctx.Request)
|
||||
return
|
||||
}
|
||||
uidStr := strconv.Itoa(int(claims.BaseClaims.ID))
|
||||
client := &Client{
|
||||
UserID: uidStr,
|
||||
Role: "user",
|
||||
Socket: conn,
|
||||
Send: make(chan []byte),
|
||||
LastPingTime: time.Now(),
|
||||
}
|
||||
|
||||
Manager.Register <- client
|
||||
setUserOnline("online", uidStr)
|
||||
go client.Read()
|
||||
go client.Write()
|
||||
}
|
||||
|
||||
func ServeWsForKefu(ctx *gin.Context) {
|
||||
token := ctx.Query("token")
|
||||
claims, err := tools.ValidateToken(token)
|
||||
if err != nil {
|
||||
response.FailWithMessage("token已失效", ctx)
|
||||
return
|
||||
}
|
||||
kfId := claims.ServiceId
|
||||
db := global.GVA_DB.Model(&sysModel.SysService{})
|
||||
var info sysModel.SysService
|
||||
err = db.Find(&info).Error
|
||||
if err != nil {
|
||||
response.FailWithMessage("客服不存在", ctx)
|
||||
return
|
||||
}
|
||||
conn, err2 := Upgrader.Upgrade(ctx.Writer, ctx.Request, nil)
|
||||
if err2 != nil {
|
||||
http.NotFound(ctx.Writer, ctx.Request)
|
||||
return
|
||||
}
|
||||
client := &Client{
|
||||
UserID: fmt.Sprintf("%v", kfId),
|
||||
Role: "kf",
|
||||
Socket: conn,
|
||||
Send: make(chan []byte),
|
||||
LastPingTime: time.Now(),
|
||||
}
|
||||
Manager.Register <- client
|
||||
|
||||
//设置客服在线
|
||||
global.GVA_DB.Model(&sysModel.SysService{}).Where("id = ?", kfId).Update("online", 1)
|
||||
go client.Read()
|
||||
go client.Write()
|
||||
}
|
||||
|
||||
func setUserOnline(cType string, Id string) {
|
||||
//给用户在record表里的客服广播此用户离线
|
||||
var list []sysModel.SysServiceRecord
|
||||
err := global.GVA_DB.Where("uid=?", Id).Find(&list).Error
|
||||
if err == nil && len(list) > 0 {
|
||||
for _, rec := range list {
|
||||
strSerId := strconv.FormatInt(rec.ServiceId, 10)
|
||||
roleKey := "kf" + strSerId
|
||||
fmt.Println(roleKey)
|
||||
serviceClient, ok := Manager.Clients[roleKey]
|
||||
if serviceClient != nil && ok {
|
||||
dataMsg := Message{
|
||||
MsgType: "1",
|
||||
Sender: Id,
|
||||
Receiver: strSerId,
|
||||
Role: "user",
|
||||
}
|
||||
sendMsg := TypeMsg{
|
||||
Type: cType,
|
||||
Data: dataMsg,
|
||||
}
|
||||
str, _ := json.Marshal(sendMsg)
|
||||
serviceClient.Send <- str
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
54
plugin/customerservice/tools/jwt.go
Normal file
54
plugin/customerservice/tools/jwt.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"time"
|
||||
)
|
||||
|
||||
var jwtKey = []byte("your-256-bit-secret")
|
||||
|
||||
// CustomClaims 结构体可以根据需要添加自定义的声明
|
||||
type CustomClaims struct {
|
||||
ServiceId int64 `json:"service_id"`
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
func GenerateToken(serviceId int64) (int64, string, error) {
|
||||
// 设置JWT的声明
|
||||
claims := CustomClaims{
|
||||
ServiceId: serviceId,
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
Audience: jwt.ClaimStrings{"GVA"}, // 受众
|
||||
NotBefore: jwt.NewNumericDate(time.Now().Add(-1000)), // 签名生效时间
|
||||
Issuer: "gva",
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(72 * time.Hour)), // token 72小时后过期
|
||||
},
|
||||
}
|
||||
// 生成JWT token
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
tokenStr, err := token.SignedString(jwtKey)
|
||||
return time.Now().Add(72 * time.Hour).Unix(), tokenStr, err
|
||||
}
|
||||
|
||||
func ValidateToken(tokenString string) (*CustomClaims, error) {
|
||||
claims := &CustomClaims{}
|
||||
|
||||
token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
|
||||
// 验证签名方法
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, errors.New("unexpected signing method")
|
||||
}
|
||||
return jwtKey, nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !token.Valid {
|
||||
return nil, errors.New("invalid token")
|
||||
}
|
||||
|
||||
return claims, nil
|
||||
}
|
77
plugin/customerservice/tools/limits.go
Normal file
77
plugin/customerservice/tools/limits.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type LimitQueeMap struct {
|
||||
sync.RWMutex
|
||||
LimitQueue map[string][]int64
|
||||
}
|
||||
|
||||
func (l *LimitQueeMap) readMap(key string) ([]int64, bool) {
|
||||
l.RLock()
|
||||
value, ok := l.LimitQueue[key]
|
||||
l.RUnlock()
|
||||
return value, ok
|
||||
}
|
||||
|
||||
func (l *LimitQueeMap) writeMap(key string, value []int64) {
|
||||
l.Lock()
|
||||
l.LimitQueue[key] = value
|
||||
l.Unlock()
|
||||
}
|
||||
|
||||
var LimitQueue = &LimitQueeMap{
|
||||
LimitQueue: make(map[string][]int64),
|
||||
}
|
||||
var ok bool
|
||||
|
||||
func NewLimitQueue() {
|
||||
cleanLimitQueue()
|
||||
}
|
||||
func cleanLimitQueue() {
|
||||
go func() {
|
||||
for {
|
||||
log.Println("cleanLimitQueue start...")
|
||||
LimitQueue.LimitQueue = nil
|
||||
now := time.Now()
|
||||
// 计算下一个零点
|
||||
next := now.Add(time.Hour * 24)
|
||||
next = time.Date(next.Year(), next.Month(), next.Day(), 0, 0, 0, 0, next.Location())
|
||||
t := time.NewTimer(next.Sub(now))
|
||||
<-t.C
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
//单机时间滑动窗口限流法
|
||||
func LimitFreqSingle(queueName string, count uint, timeWindow int64) bool {
|
||||
currTime := time.Now().Unix()
|
||||
if LimitQueue.LimitQueue == nil {
|
||||
LimitQueue.LimitQueue = make(map[string][]int64)
|
||||
}
|
||||
if _, ok = LimitQueue.readMap(queueName); !ok {
|
||||
LimitQueue.writeMap(queueName, make([]int64, 0))
|
||||
return true
|
||||
}
|
||||
q, _ := LimitQueue.readMap(queueName)
|
||||
//队列未满
|
||||
if uint(len(q)) < count {
|
||||
LimitQueue.writeMap(queueName, append(q, currTime))
|
||||
return true
|
||||
}
|
||||
//队列满了,取出最早访问的时间
|
||||
earlyTime := q[0]
|
||||
//说明最早期的时间还在时间窗口内,还没过期,所以不允许通过
|
||||
if currTime-earlyTime <= timeWindow {
|
||||
return false
|
||||
} else {
|
||||
//说明最早期的访问应该过期了,去掉最早期的
|
||||
q = q[1:]
|
||||
LimitQueue.writeMap(queueName, append(q, currTime))
|
||||
}
|
||||
return true
|
||||
}
|
20
plugin/customerservice/tools/timeformat.go
Normal file
20
plugin/customerservice/tools/timeformat.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package tools
|
||||
|
||||
import "time"
|
||||
|
||||
func FormatTimestamp(timestamp int64) string {
|
||||
t := time.Unix(timestamp, 0)
|
||||
now := time.Now()
|
||||
|
||||
// 格式化时间
|
||||
if t.Year() == now.Year() && t.YearDay() == now.YearDay() {
|
||||
// 当天,返回 24 小时制的时和分
|
||||
return t.Format("15:04")
|
||||
} else if t.Year() == now.Year() && t.YearDay() == now.YearDay()-1 {
|
||||
// 昨天,返回 "昨天"
|
||||
return "昨天"
|
||||
} else {
|
||||
// 其他时间,返回月和日
|
||||
return t.Format("01-02")
|
||||
}
|
||||
}
|
113
plugin/picturelibrary/README.md
Normal file
113
plugin/picturelibrary/README.md
Normal file
@@ -0,0 +1,113 @@
|
||||
## GVA 图库功能
|
||||
|
||||
### 手动安装方法
|
||||
|
||||
1.解压zip获得picturelibrary文件夹
|
||||
2.将 picturelibrary/web/plugin/picturelibrary 放置在web/plugin下
|
||||
3.将 picturelibrary/server/plugin/picturelibrary 放置在server/plugin下
|
||||
|
||||
#### 执行如下注册方法
|
||||
|
||||
### 注册(手动自动都需要)
|
||||
|
||||
#### 1. 前往GVA主程序下的initialize/router.go 在Routers 方法最末尾按照你需要的及安全模式添加本插件
|
||||
PluginInit(PrivateGroup, picturelibrary.CreatePictureLibraryPlug())
|
||||
到gva系统,角色管理,分配角色的api权限即可,插件会自动注册api,需要手动分配。
|
||||
注:会生成一个表:sys_attachment_category,表exa_file_upload_and_downloads会新增一个cat_id字段
|
||||
### 2. 配置说明
|
||||
|
||||
#### 2-1 全局配置结构体说明
|
||||
|
||||
无配置
|
||||
|
||||
#### 2-2 使用说明
|
||||
|
||||
在你需要图片选择的前端页面引入图库组件,如下:
|
||||
<div class="selected-images">
|
||||
<div class="selected-image" v-for="image in selectedImages" :key="image">
|
||||
<el-image v-if="fileTypeList.includes(image.tag) === true" :src="image.url" style="width: 100%; height: 100%; object-fit: cover;margin-right: 10px;"></el-image>
|
||||
<video v-else controls style="width: 100%; height: 100%;">
|
||||
<source :src="image.url" />
|
||||
</video>
|
||||
<span class="remove-icon" @click="removeSelectedImage(image)"><el-icon><circle-close></circle-close></el-icon></span>
|
||||
</div>
|
||||
<el-icon v-if="isMultiple || selectedImages.length === 0" class="avatar-uploader-icon" @click="openImageLibrary"><Plus /></el-icon>
|
||||
</div>
|
||||
|
||||
图库弹窗:
|
||||
<el-dialog v-model="isDialogVisible" title="图片库" width="950px" destroy-on-close>
|
||||
<ImageLibrary @select="handleImageSelect" :multiple="isMultiple"/>
|
||||
</el-dialog>
|
||||
|
||||
js代码:
|
||||
import {CircleClose, Plus} from '@element-plus/icons-vue'
|
||||
import ImageLibrary from "@/plugin/picturelibrary/view/components/imageLibrary.vue";
|
||||
|
||||
const isDialogVisible = ref(false)
|
||||
const isMultiple = ref(false) // 设置是否允许多选
|
||||
const selectedImages = ref([])
|
||||
|
||||
const openImageLibrary = () => {
|
||||
isDialogVisible.value = true
|
||||
}
|
||||
const fileTypeList = ['png', 'jpg', 'jpeg', 'gif']
|
||||
const handleImageSelect = (images) => {
|
||||
if (isMultiple.value) {
|
||||
selectedImages.value = [...selectedImages.value, ...images]
|
||||
} else {
|
||||
selectedImages.value = Array.isArray(images) ? images : [images]
|
||||
}
|
||||
|
||||
// 此处是测试项目里上传头像的参数,根据实际情况进行修改
|
||||
formData.value.avatar = selectedImages.value[0]
|
||||
isDialogVisible.value = false
|
||||
}
|
||||
|
||||
const removeSelectedImage = (image) => {
|
||||
const index = selectedImages.value.indexOf(image)
|
||||
if (index !== -1) {
|
||||
selectedImages.value.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
style代码:
|
||||
.selected-images {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.selected-image {
|
||||
position: relative;
|
||||
margin-right: 10px;
|
||||
margin-bottom:10px;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.selected-image .remove-icon {
|
||||
position: absolute;
|
||||
top: 0; /* 微调位置 */
|
||||
right: 0; /* 微调位置 */
|
||||
color: black;
|
||||
padding: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 22px;
|
||||
line-height: 22px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.avatar-uploader-icon {
|
||||
font-size: 28px;
|
||||
color: #8c939d;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
text-align: center;
|
||||
border: 1px dashed #d4d9e1;
|
||||
}
|
||||
|
||||
#### 2-3 参数说明
|
||||
isMultiple 是控制能不能选择多张图的参数,false:只能选一张;true:可以选择多张
|
||||
### 3. 方法API
|
||||
无
|
||||
|
230
plugin/picturelibrary/api/api.go
Normal file
230
plugin/picturelibrary/api/api.go
Normal file
@@ -0,0 +1,230 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/lckt/global"
|
||||
"git.echol.cn/loser/lckt/model/common/response"
|
||||
"git.echol.cn/loser/lckt/model/example"
|
||||
picModel "git.echol.cn/loser/lckt/plugin/picturelibrary/model"
|
||||
"git.echol.cn/loser/lckt/plugin/picturelibrary/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type PictureLibraryApi struct{}
|
||||
|
||||
var picService = service.ServiceGroupApp
|
||||
|
||||
// @Tags PictureLibrary
|
||||
// @Summary 请手动填写接口功能
|
||||
// @Produce application/json
|
||||
// @Success 200 {string} string "{"success":true,"data":{},"msg":"发送成功"}"
|
||||
// @Router /图片库/routerName [post]
|
||||
func (p *PictureLibraryApi) ApiName(c *gin.Context) {
|
||||
|
||||
if err := picService.PlugService(); err != nil {
|
||||
global.GVA_LOG.Error("失败!", zap.Error(err))
|
||||
response.FailWithMessage("失败", c)
|
||||
} else {
|
||||
|
||||
response.OkWithData("成功", c)
|
||||
}
|
||||
}
|
||||
|
||||
// GetFileList
|
||||
// @Tags sysAttachment
|
||||
// @Summary 分页文件列表
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.PageInfo true "页码, 每页大小"
|
||||
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "分页文件列表,返回包括列表,总数,页码,每页数量"
|
||||
// @Router /attachment/getFileList [post]
|
||||
func (p *PictureLibraryApi) GetFileList(c *gin.Context) {
|
||||
var pageInfo picModel.PageInfo
|
||||
_ = c.ShouldBindJSON(&pageInfo)
|
||||
list, total, err := picService.GetFileRecordInfoList(pageInfo, c)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||
response.FailWithMessage("获取失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithDetailed(response.PageResult{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: pageInfo.Page,
|
||||
PageSize: pageInfo.Limit,
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
// GetCategoryList
|
||||
// @Tags sysAttachment
|
||||
// @Summary 分类列表
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.PageInfo true "页码, 每页大小"
|
||||
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "分页文件列表,返回包括列表,总数,页码,每页数量"
|
||||
// @Router /attachment/getFileList [post]
|
||||
func (p *PictureLibraryApi) GetCategoryList(c *gin.Context) {
|
||||
db := global.GVA_DB.Model(&picModel.SysAttachmentCategory{})
|
||||
var fileLists []picModel.SysAttachmentCategory
|
||||
err := db.Find(&fileLists).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||
response.FailWithMessage("获取失败", c)
|
||||
return
|
||||
}
|
||||
data := picService.BuildTree(fileLists, 0)
|
||||
response.OkWithDetailed(response.PageResult{
|
||||
List: data,
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
// AddCategory
|
||||
// @Tags sysAttachment
|
||||
// @Summary 添加分类
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.PageInfo true "页码, 每页大小"
|
||||
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "分页文件列表,返回包括列表,总数,页码,每页数量"
|
||||
// @Router /attachment/getFileList [post]
|
||||
func (p *PictureLibraryApi) AddCategory(c *gin.Context) {
|
||||
var input struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
Pid uint `json:"pid"`
|
||||
Id uint `json:"id"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
global.GVA_LOG.Error("参数错误!", zap.Error(err))
|
||||
response.FailWithMessage("参数错误", c)
|
||||
return
|
||||
}
|
||||
|
||||
// 检查是否已存在相同名称的分类
|
||||
var existingCategory picModel.SysAttachmentCategory
|
||||
result := global.GVA_DB.Where("name = ? ", input.Name).First(&existingCategory)
|
||||
if result.Error == nil && existingCategory.Id != input.Id {
|
||||
response.FailWithMessage("分类名称已存在", c)
|
||||
return
|
||||
}
|
||||
|
||||
// 创建新的分类
|
||||
newCategory := picModel.SysAttachmentCategory{Name: input.Name, Pid: input.Pid}
|
||||
var err error
|
||||
var title string
|
||||
if input.Id > 0 {
|
||||
err = global.GVA_DB.Where("id = ?", input.Id).Updates(newCategory).Error
|
||||
title = "更新失败"
|
||||
} else {
|
||||
err = global.GVA_DB.Create(&newCategory).Error
|
||||
title = "创建失败"
|
||||
}
|
||||
if err != nil {
|
||||
response.FailWithMessage(title, c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithDetailed(response.PageResult{}, "创建成功", c)
|
||||
}
|
||||
|
||||
// UploadHandler
|
||||
// @Tags sysAttachment
|
||||
// @Summary 多文件上传
|
||||
// @Security ApiKeyAuth
|
||||
// @accept multipart/form-data
|
||||
// @Produce application/json
|
||||
// @Param file formData [file] true "上传文件示例"
|
||||
// @Success 200 {object} response.Response{data=exampleRes.ExaFileResponse,msg=string} "上传文件示例,返回包括文件详情"
|
||||
// @Router /fileUploadAndDownload/upload [post]
|
||||
func (p *PictureLibraryApi) UploadHandler(c *gin.Context) {
|
||||
form, _ := c.MultipartForm()
|
||||
files := form.File["files"]
|
||||
categoryIDStr := c.PostForm("cat_id")
|
||||
categoryID, _ := strconv.ParseUint(categoryIDStr, 10, 32)
|
||||
noSave := c.DefaultQuery("noSave", "0")
|
||||
for _, file := range files {
|
||||
classId, _ := strconv.Atoi(c.DefaultPostForm("classId", "0"))
|
||||
fileData, err := fileUploadAndDownloadService.UploadFile(file, noSave, classId)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("上传失败!", zap.Error(err))
|
||||
response.FailWithMessage("上传失败", c)
|
||||
return
|
||||
}
|
||||
var attachment picModel.SysAttachment
|
||||
if err := global.GVA_DB.Where("`key` = ? ", fileData.Key).First(&attachment).Error; err != nil {
|
||||
response.FailWithMessage("上传失败", c)
|
||||
return
|
||||
}
|
||||
// 根据key更新数据
|
||||
attachment.CatId = uint(categoryID)
|
||||
if err := global.GVA_DB.Save(&attachment).Error; err != nil {
|
||||
response.FailWithMessage("上传文件失败", c)
|
||||
return
|
||||
}
|
||||
}
|
||||
response.OkWithDetailed(response.PageResult{}, "上传成功", c)
|
||||
}
|
||||
|
||||
// DeleteFile
|
||||
// @Tags sysAttachment
|
||||
// @Summary 删除文件
|
||||
// @Security ApiKeyAuth
|
||||
// @Produce application/json
|
||||
// @Param data body example.ExaFileUploadAndDownload true "传入文件里面id即可"
|
||||
// @Success 200 {object} response.Response{msg=string} "删除文件"
|
||||
// @Router /fileUploadAndDownload/deleteFile [post]
|
||||
func (p *PictureLibraryApi) DeleteFile(c *gin.Context) {
|
||||
var files []example.ExaFileUploadAndDownload
|
||||
err := c.ShouldBindJSON(&files)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
for _, file := range files {
|
||||
if err := fileUploadAndDownloadService.DeleteFile(file); err != nil {
|
||||
global.GVA_LOG.Error("删除失败!", zap.Error(err))
|
||||
response.FailWithMessage("删除失败", c)
|
||||
return
|
||||
}
|
||||
}
|
||||
response.OkWithMessage("删除成功", c)
|
||||
}
|
||||
|
||||
// DeleteCategory
|
||||
// @Tags sysAttachment
|
||||
// @Summary 删除分类
|
||||
// @Security ApiKeyAuth
|
||||
// @Produce application/json
|
||||
// @Param data body example.ExaFileUploadAndDownload true "传入文件里面id即可"
|
||||
// @Success 200 {object} response.Response{msg=string} "删除文件"
|
||||
// @Router /fileUploadAndDownload/deleteFile [post]
|
||||
func (p *PictureLibraryApi) DeleteCategory(c *gin.Context) {
|
||||
var input struct {
|
||||
CatId uint `json:"cat_id"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
response.FailWithMessage("参数错误", c)
|
||||
return
|
||||
}
|
||||
if input.CatId == 0 {
|
||||
response.FailWithMessage("参数错误-1", c)
|
||||
return
|
||||
}
|
||||
var childCount int64
|
||||
global.GVA_DB.Model(&picModel.SysAttachmentCategory{}).Where("pid = ?", input.CatId).Count(&childCount)
|
||||
|
||||
if childCount > 0 {
|
||||
response.FailWithMessage("请先删除子级", c)
|
||||
return
|
||||
}
|
||||
result := global.GVA_DB.Delete(&picModel.SysAttachmentCategory{}, input.CatId)
|
||||
if result.Error != nil {
|
||||
response.FailWithMessage("删除失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithMessage("删除成功", c)
|
||||
}
|
11
plugin/picturelibrary/api/enter.go
Normal file
11
plugin/picturelibrary/api/enter.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package api
|
||||
|
||||
import "git.echol.cn/loser/lckt/service"
|
||||
|
||||
type ApiGroup struct {
|
||||
PictureLibraryApi
|
||||
}
|
||||
|
||||
var ApiGroupApp = new(ApiGroup)
|
||||
|
||||
var fileUploadAndDownloadService = service.ServiceGroupApp.ExampleServiceGroup.FileUploadAndDownloadService
|
1
plugin/picturelibrary/config/config.go
Normal file
1
plugin/picturelibrary/config/config.go
Normal file
@@ -0,0 +1 @@
|
||||
package config
|
1
plugin/picturelibrary/global/global.go
Normal file
1
plugin/picturelibrary/global/global.go
Normal file
@@ -0,0 +1 @@
|
||||
package global
|
77
plugin/picturelibrary/main.go
Normal file
77
plugin/picturelibrary/main.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package picturelibrary
|
||||
|
||||
import (
|
||||
gvaGlobal "git.echol.cn/loser/lckt/global"
|
||||
"git.echol.cn/loser/lckt/model/system"
|
||||
"git.echol.cn/loser/lckt/plugin/picturelibrary/model"
|
||||
"git.echol.cn/loser/lckt/plugin/picturelibrary/router"
|
||||
"git.echol.cn/loser/lckt/plugin/plugin-tool/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type PictureLibraryPlugin struct {
|
||||
}
|
||||
|
||||
func CreatePictureLibraryPlug() *PictureLibraryPlugin {
|
||||
|
||||
gvaGlobal.GVA_DB.AutoMigrate(model.SysAttachment{}, model.SysAttachmentCategory{}) // 此处可以把插件依赖的数据库结构体自动创建表 需要填写对应的结构体
|
||||
|
||||
// 下方会自动注册菜单 第一个参数为菜单一级路由信息一般为定义好的组名 第二个参数为真实使用的web页面路由信息
|
||||
//utils.RegisterMenus(
|
||||
// system.SysBaseMenu{
|
||||
// Path: "picturelibrary",
|
||||
// Name: "picturelibrary",
|
||||
// Hidden: false,
|
||||
// Component: "plugin/picturelibrary/view/index.vue",
|
||||
// Sort: 0,
|
||||
// Meta: system.Meta{
|
||||
// Title: "图片库",
|
||||
// Icon: "folder",
|
||||
// },
|
||||
// },
|
||||
//)
|
||||
|
||||
// 下方会自动注册api 以下格式为示例格式,请按照实际情况修改
|
||||
utils.RegisterApis(
|
||||
system.SysApi{
|
||||
Path: "/pic/pic_library/list",
|
||||
Description: "图片列表",
|
||||
ApiGroup: "图片库",
|
||||
Method: "POST",
|
||||
},
|
||||
system.SysApi{
|
||||
Path: "/pic/pic_library/cat_list",
|
||||
Description: "图片分类列表",
|
||||
ApiGroup: "图片库",
|
||||
Method: "POST",
|
||||
},
|
||||
system.SysApi{
|
||||
Path: "/pic/pic_library/add_cat",
|
||||
Description: "添加分类",
|
||||
ApiGroup: "图片库",
|
||||
Method: "POST",
|
||||
},
|
||||
system.SysApi{
|
||||
Path: "/pic/pic_library/upload_handler",
|
||||
Description: "上传文件",
|
||||
ApiGroup: "图片库",
|
||||
Method: "POST",
|
||||
},
|
||||
system.SysApi{
|
||||
Path: "/pic/pic_library/delete_file",
|
||||
Description: "删除文件",
|
||||
ApiGroup: "图片库",
|
||||
Method: "POST",
|
||||
},
|
||||
)
|
||||
|
||||
return &PictureLibraryPlugin{}
|
||||
}
|
||||
|
||||
func (*PictureLibraryPlugin) Register(group *gin.RouterGroup) {
|
||||
router.RouterGroupApp.InitPictureLibraryRouter(group)
|
||||
}
|
||||
|
||||
func (*PictureLibraryPlugin) RouterPath() string {
|
||||
return "pic"
|
||||
}
|
8
plugin/picturelibrary/model/model.go
Normal file
8
plugin/picturelibrary/model/model.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package model
|
||||
|
||||
type PageInfo struct {
|
||||
Page int `json:"page" form:"page"`
|
||||
Limit int `json:"limit" form:"limit"`
|
||||
Cid int `json:"cid" form:"cid"` //分类id
|
||||
Keyword string `json:"keyword" form:"keyword"`
|
||||
}
|
14
plugin/picturelibrary/model/sysAttachment.go
Normal file
14
plugin/picturelibrary/model/sysAttachment.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/lckt/model/example"
|
||||
)
|
||||
|
||||
type SysAttachment struct {
|
||||
example.ExaFileUploadAndDownload
|
||||
CatId uint `json:"cat_id" form:"cat_id" gorm:"default:0;type:int;column:cat_id;comment:分类id;"`
|
||||
}
|
||||
|
||||
func (SysAttachment) TableName() string {
|
||||
return "exa_file_upload_and_downloads"
|
||||
}
|
13
plugin/picturelibrary/model/sysAttachmentCatategory.go
Normal file
13
plugin/picturelibrary/model/sysAttachmentCatategory.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package model
|
||||
|
||||
type SysAttachmentCategory struct {
|
||||
Id uint `gorm:"primarykey" json:"id"` // 主键ID
|
||||
Name string `json:"name" form:"name" gorm:"default:'';type:varchar(255);column:name;comment:分类名称;"`
|
||||
Pid uint `json:"pid" form:"pid" gorm:"default:0;type:int;column:pid;comment:父节点ID;"`
|
||||
AddTime int `json:"add_time" form:"add_time" gorm:"default:0;type:int;column:add_time;comment:添加时间;"`
|
||||
Children []*SysAttachmentCategory `json:"children" gorm:"-"`
|
||||
}
|
||||
|
||||
func (SysAttachmentCategory) TableName() string {
|
||||
return "sys_attachment_category"
|
||||
}
|
7
plugin/picturelibrary/router/enter.go
Normal file
7
plugin/picturelibrary/router/enter.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package router
|
||||
|
||||
type RouterGroup struct {
|
||||
PictureLibraryRouter
|
||||
}
|
||||
|
||||
var RouterGroupApp = new(RouterGroup)
|
22
plugin/picturelibrary/router/router.go
Normal file
22
plugin/picturelibrary/router/router.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/lckt/plugin/picturelibrary/api"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type PictureLibraryRouter struct {
|
||||
}
|
||||
|
||||
func (s *PictureLibraryRouter) InitPictureLibraryRouter(Router *gin.RouterGroup) {
|
||||
plugRouter := Router.Use()
|
||||
plugApi := api.ApiGroupApp.PictureLibraryApi
|
||||
{
|
||||
plugRouter.POST("/pic_library/list", plugApi.GetFileList)
|
||||
plugRouter.POST("/pic_library/cat_list", plugApi.GetCategoryList)
|
||||
plugRouter.POST("/pic_library/add_cat", plugApi.AddCategory)
|
||||
plugRouter.POST("/pic_library/upload_handler", plugApi.UploadHandler)
|
||||
plugRouter.POST("/pic_library/delete_file", plugApi.DeleteFile)
|
||||
plugRouter.POST("/pic_library/delete_cat", plugApi.DeleteCategory)
|
||||
}
|
||||
}
|
7
plugin/picturelibrary/service/enter.go
Normal file
7
plugin/picturelibrary/service/enter.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package service
|
||||
|
||||
type ServiceGroup struct {
|
||||
PictureLibraryService
|
||||
}
|
||||
|
||||
var ServiceGroupApp = new(ServiceGroup)
|
70
plugin/picturelibrary/service/service.go
Normal file
70
plugin/picturelibrary/service/service.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/lckt/global"
|
||||
picModel "git.echol.cn/loser/lckt/plugin/picturelibrary/model"
|
||||
"github.com/gin-gonic/gin"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type PictureLibraryService struct{}
|
||||
|
||||
func (e *PictureLibraryService) PlugService() (err error) {
|
||||
// 写你的业务逻辑
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *PictureLibraryService) GetFileRecordInfoList(info picModel.PageInfo, c *gin.Context) (list interface{}, total int64, err error) {
|
||||
limit := info.Limit
|
||||
offset := info.Limit * (info.Page - 1)
|
||||
keyword := info.Keyword
|
||||
cid := info.Cid
|
||||
db := global.GVA_DB.Model(&picModel.SysAttachment{})
|
||||
var fileLists []picModel.SysAttachment
|
||||
if len(keyword) > 0 {
|
||||
db = db.Where("`name` LIKE ?", "%"+keyword+"%")
|
||||
}
|
||||
if cid > 0 {
|
||||
db = db.Where("cat_id = ?", cid)
|
||||
}
|
||||
err = db.Count(&total).Error
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = db.Limit(limit).Offset(offset).Order("created_at desc").Find(&fileLists).Error
|
||||
urlHost := e.GetUrlHost(c)
|
||||
for k, v := range fileLists {
|
||||
if !strings.HasPrefix(v.Url, "http://") && !strings.HasPrefix(v.Url, "https://") {
|
||||
v.Url = urlHost + "api/" + v.Url
|
||||
fileLists[k] = v
|
||||
}
|
||||
}
|
||||
return fileLists, total, err
|
||||
}
|
||||
|
||||
// 构建树形结构
|
||||
func (e *PictureLibraryService) BuildTree(categories []picModel.SysAttachmentCategory, parentID uint) []*picModel.SysAttachmentCategory {
|
||||
var tree []*picModel.SysAttachmentCategory
|
||||
for _, category := range categories {
|
||||
if category.Pid == parentID {
|
||||
children := e.BuildTree(categories, category.Id)
|
||||
category.Children = children
|
||||
tree = append(tree, &category)
|
||||
}
|
||||
}
|
||||
return tree
|
||||
}
|
||||
|
||||
func (e *PictureLibraryService) GetUrlHost(c *gin.Context) string {
|
||||
host := c.Request.Host
|
||||
scheme := "http"
|
||||
if c.Request.TLS != nil {
|
||||
scheme = "https"
|
||||
}
|
||||
referer := c.Request.Referer()
|
||||
if referer != "" {
|
||||
return referer
|
||||
}
|
||||
return scheme + "://" + host + "/"
|
||||
}
|
BIN
resource/cert/apiclient_cert.p12
Normal file
BIN
resource/cert/apiclient_cert.p12
Normal file
Binary file not shown.
25
resource/cert/apiclient_cert.pem
Normal file
25
resource/cert/apiclient_cert.pem
Normal file
@@ -0,0 +1,25 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEKzCCAxOgAwIBAgIUWaiR+0A+x6HPIJDbnI7HBL1DsQEwDQYJKoZIhvcNAQEL
|
||||
BQAwXjELMAkGA1UEBhMCQ04xEzARBgNVBAoTClRlbnBheS5jb20xHTAbBgNVBAsT
|
||||
FFRlbnBheS5jb20gQ0EgQ2VudGVyMRswGQYDVQQDExJUZW5wYXkuY29tIFJvb3Qg
|
||||
Q0EwHhcNMjUwNzIzMDU1MDU5WhcNMzAwNzIyMDU1MDU5WjCBhDETMBEGA1UEAwwK
|
||||
MTY0Njg3NDc1MzEbMBkGA1UECgwS5b6u5L+h5ZWG5oi357O757ufMTAwLgYDVQQL
|
||||
DCfmtbflj6PpvpnljY7pk4HlnZrmiJDnlLXlrZDllYbliqHllYbooYwxCzAJBgNV
|
||||
BAYTAkNOMREwDwYDVQQHDAhTaGVuWmhlbjCCASIwDQYJKoZIhvcNAQEBBQADggEP
|
||||
ADCCAQoCggEBAMUD4rf7ct0zUxJKGww+8c38L/IBx9yeNpOc7lav+CEwqZvieesy
|
||||
p1I2hbY+hwOZm23//0Vxcnl6fkr5wdidE/RsN808sGkYsetJ6q7LZsgsxnbaQd7w
|
||||
rBc4GlOh1Q4teErbjNTUMuozYzikEjMjgZMxs7i5WJnFMMVvgJS4681UKH8MQG93
|
||||
BnaaD5zEyofAnOov7mvC0KZ0RpqwiLdUsMaPzYILzccOv7Tj82Z/qLw8AFcI+7L1
|
||||
mq8trZxmSPXP5Fypz+1VKLKR9MaELUuQLPouewDjXkKTBnxkhoWow6huuz3qyUUo
|
||||
i7uJtnmDn6Eu+60ch61BB5ws3rhWDpltR/0CAwEAAaOBuTCBtjAJBgNVHRMEAjAA
|
||||
MAsGA1UdDwQEAwID+DCBmwYDVR0fBIGTMIGQMIGNoIGKoIGHhoGEaHR0cDovL2V2
|
||||
Y2EuaXRydXMuY29tLmNuL3B1YmxpYy9pdHJ1c2NybD9DQT0xQkQ0MjIwRTUwREJD
|
||||
MDRCMDZBRDM5NzU0OTg0NkMwMUMzRThFQkQyJnNnPUhBQ0M0NzFCNjU0MjJFMTJC
|
||||
MjdBOUQzM0E4N0FEMUNERjU5MjZFMTQwMzcxMA0GCSqGSIb3DQEBCwUAA4IBAQBk
|
||||
WmviQM60S/l4I4/RtQWtiZS+85fk2ePVSk5gBUS27W4yTw+aXy5RYaCLcOVXTXje
|
||||
PpAfE7QvkjZxMg/k0SJR0/NbgDNF6xuCbMj78+czWVCkqHwPjY4hWRdsM9s44Orp
|
||||
lwFrG/UNRhPgXcdwNQj8MNzy3nGeO0HWbJFBWcuRhSSwXpxv3Sh6pZviL3QA+lrV
|
||||
7QB7d0fEu8sSGbwnIB0oXl7y78l2/D20p57TKHsq5IPfQfDdExNXKV60ikMt/MdP
|
||||
C4ygbeRpSAobr6qMTXcpWlbH3KfIPOUFny3nkN8C6lm//QUl7ynOXkECcmIHY6XF
|
||||
yMx70ORdoBl1DaZ369P9
|
||||
-----END CERTIFICATE-----
|
28
resource/cert/apiclient_key.pem
Normal file
28
resource/cert/apiclient_key.pem
Normal file
@@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDFA+K3+3LdM1MS
|
||||
ShsMPvHN/C/yAcfcnjaTnO5Wr/ghMKmb4nnrMqdSNoW2PocDmZtt//9FcXJ5en5K
|
||||
+cHYnRP0bDfNPLBpGLHrSequy2bILMZ22kHe8KwXOBpTodUOLXhK24zU1DLqM2M4
|
||||
pBIzI4GTMbO4uViZxTDFb4CUuOvNVCh/DEBvdwZ2mg+cxMqHwJzqL+5rwtCmdEaa
|
||||
sIi3VLDGj82CC83HDr+04/Nmf6i8PABXCPuy9ZqvLa2cZkj1z+Rcqc/tVSiykfTG
|
||||
hC1LkCz6LnsA415CkwZ8ZIaFqMOobrs96slFKIu7ibZ5g5+hLvutHIetQQecLN64
|
||||
Vg6ZbUf9AgMBAAECggEBAI4q66PQM2cj7kI4b6Q6l8sIvKBqYIr3MHL8v5CWkvuA
|
||||
XiQ7Hbd3af6NkZedL1iNs/eAz/iQkQbQOepoqFVjpE6w+OOFc9ejFmCvikZwSM8S
|
||||
YHTLstTp34Ux2u2WzmPYtAFwxQOfzM3sHyF0ZB269Xn+V65pMWJlRXhzqdmoR6B2
|
||||
YYLzHVBZAbgj7IlS+f1WPkkbrb3BIs71tYyPS3Ng6nhGgXrnqmE5fhm93wR9KR5R
|
||||
RcgzFGH/8qD2LndmJz8tYtjvlq016Pp40VrYabIrvX6apglayShLQ+2XUe8VKH2Q
|
||||
dQPIKL3R0ZeDeqwn4PTMclBYd/nXFSbIQndufTDY84ECgYEA8RTcCPOFYkoDeQIr
|
||||
yKqWo1P1cKsB/bGzvLmo+FqFQij5lmj9iDdvpSQJ58Uj+TFy5qNRT6VueF8U4pNd
|
||||
Fmcbw05cH+9NzAXji6aYWCargbmhW4L+rzub+TQmdAfqXbBknVgrLenY4LFnFFm1
|
||||
SIdJRid3vCwiRuhcjecg4FasR+kCgYEA0TTw4ZGxPCX5qt00PUFvuGpqFxFqZ7Ah
|
||||
wapwQTUY48WeC0Pg7Iq6ipnhjggkgYLkGc1klmTwJXKV0acIS5/DNclepMWZPwIc
|
||||
+GcKclQsy8MHm+L2tPGBQosRGiSrXi5LzBIQsYSyhBRDUSqu+yj7TTd2XqpUkLAw
|
||||
qdrWiIjVBvUCgYAOkh0uaVGBfEmzcZ8l1LGgE339HkjThX8AhBQjVo1BT2quXZAd
|
||||
QIR97ayvlmmzMPrp16sdbjk8Czse6pswtHCoID9PKs5/60cydJI2mbe58nc/Ka6s
|
||||
9qRZrn44exX+LaAXJnINp1mVUwOQ5k8foBWcqNwCwoQb1wVpCjQhevuUqQKBgQC1
|
||||
SA+3Fr0iprF6iqWastoxThzSEmhGowwNOjh9eJoxvOsfTdlYfzn3ojIeFhY0F4y6
|
||||
gw03eQ3TFUCXZAq/JRhNwkl9tC//tkAOS5N00FXk1wH/5aLr1h2w4LqYEdBhEvLh
|
||||
SYInoRnjc3+FlNv9jVx9Y6Lxkt0mZ1YzyQp/UzptBQKBgFTOonik05rMHGlEg9Ng
|
||||
unjDAEK+7tXhM/RKxWDs7pZeGbdl1c/NDOX4wIbuK2cCkV0v/qfg6Ex2Obf5rqM4
|
||||
R5lrs9y30rZbwmfms8G3cimTAsOcLqObKkBED+g07t+3bw44JXLwYLPm/z//bIcI
|
||||
fkxyxKL5ZJhP3/bPJJj8UAq3
|
||||
-----END PRIVATE KEY-----
|
22
router/app/banner.go
Normal file
22
router/app/banner.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package app
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
|
||||
type BannerRouter struct{}
|
||||
|
||||
// InitBannerRouter 初始化 Banner 路由
|
||||
func (b *BannerRouter) InitBannerRouter(Router, PublicRouter *gin.RouterGroup) {
|
||||
sysRouter := Router.Group("banner")
|
||||
appRouter := PublicRouter.Group("banner")
|
||||
|
||||
{
|
||||
sysRouter.POST("", bannerApi.Create) // 新建Banner
|
||||
sysRouter.DELETE("", bannerApi.Delete) // 删除Banner
|
||||
sysRouter.PUT("", bannerApi.Update) // 更新Banner
|
||||
}
|
||||
{
|
||||
appRouter.GET("/list", bannerApi.GetList) // 获取Banner列表
|
||||
appRouter.GET("", bannerApi.GetByID) // Banner公开接口
|
||||
appRouter.GET("/index", bannerApi.GetIndexBanners) // 获取首页Banner
|
||||
}
|
||||
}
|
13
router/app/enter.go
Normal file
13
router/app/enter.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package app
|
||||
|
||||
import api "git.echol.cn/loser/lckt/api/v1"
|
||||
|
||||
type RouterGroup struct {
|
||||
UserRouter
|
||||
BannerRouter
|
||||
OrderRouter
|
||||
}
|
||||
|
||||
var userApi = api.ApiGroupApp.AppApiGroup.AppUserApi
|
||||
var bannerApi = api.ApiGroupApp.AppApiGroup.BannerApi
|
||||
var orderApi = api.ApiGroupApp.AppApiGroup.OrderApi
|
18
router/app/order.go
Normal file
18
router/app/order.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package app
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
|
||||
type OrderRouter struct{}
|
||||
|
||||
// InitOrderRouter 初始化订单路由
|
||||
func (r *OrderRouter) InitOrderRouter(AppRouter *gin.RouterGroup) {
|
||||
appRouter := AppRouter.Group("app_order")
|
||||
{
|
||||
appRouter.POST("", orderApi.CreateOrder) // 创建订单
|
||||
appRouter.POST("/wechat/pay", orderApi.PayOrder) // 微信支付订单
|
||||
appRouter.GET("/list", orderApi.GetOrderList) // 获取订单列表
|
||||
appRouter.GET(":id", orderApi.GetOrderDetail) // 获取订单详情
|
||||
appRouter.POST("/balance/pay", orderApi.BalancePay) // 余额支付
|
||||
appRouter.POST("/notify", orderApi.NotifyOrder) // 微信支付回调通知
|
||||
}
|
||||
}
|
27
router/app/user.go
Normal file
27
router/app/user.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type UserRouter struct{}
|
||||
|
||||
func (s *UserRouter) InitAppUserRouter(AppAuthGroup, PublicRouter *gin.RouterGroup) {
|
||||
appUserRouter := AppAuthGroup.Group("h5_user")
|
||||
publicRouter := PublicRouter.Group("h5_user")
|
||||
{
|
||||
appUserRouter.GET("/info", userApi.GetUserInfo) // 获取用户信息
|
||||
//申请成为讲师
|
||||
appUserRouter.POST("/applyTeacher", userApi.ApplyTeacher) // 申请成为讲师
|
||||
appUserRouter.GET("/applyTeacher", userApi.GetTeacherApply) // 获取教师列表
|
||||
}
|
||||
{
|
||||
publicRouter.POST("wxLogin", userApi.WechatLogin) // 微信登录
|
||||
publicRouter.POST("bindWX", userApi.BindWechat) // 绑定微信
|
||||
publicRouter.POST("bindPhone", userApi.BindPhone) // 获取用户信息
|
||||
publicRouter.POST("pwdlogin", userApi.PwdLogin) // 密码登录
|
||||
publicRouter.POST("sms/send", userApi.SendCode) // 发送短信验证码
|
||||
publicRouter.POST("login", userApi.Login) // 短信验证码登录
|
||||
publicRouter.POST("register", userApi.Register) // 注册
|
||||
}
|
||||
}
|
@@ -16,13 +16,15 @@ func (s *ArticleRouter) InitBotRouter(Router *gin.RouterGroup, PublicRouter *gin
|
||||
articleRouter.POST("", artApi.Create) // 新建文章
|
||||
articleRouter.DELETE("", artApi.Delete) // 批量删除文章
|
||||
articleRouter.PUT("", artApi.Update) // 更新文章
|
||||
|
||||
articleRouter.GET("list", artApi.List) // 获取文章列表
|
||||
articleRouter.GET("", artApi.ById) // 文章开放接口
|
||||
}
|
||||
{
|
||||
articleRouterWithoutRecord.GET(":id", artApi.ById) // 根据ID获取文章
|
||||
articleRouter.GET("list", artApi.List) // 获取文章列表
|
||||
|
||||
}
|
||||
{
|
||||
articleRouterWithoutAuth.GET("", artApi.ById) // 文章开放接口
|
||||
articleRouterWithoutAuth.GET("app/list", artApi.APPGetList) // 文章公开接口
|
||||
articleRouterWithoutAuth.GET("app/:id", artApi.AppById) // 文章开放接口
|
||||
}
|
||||
}
|
||||
|
@@ -24,5 +24,6 @@ func (s *CategoryRouter) InitCategoryRouter(Router *gin.RouterGroup, PublicRoute
|
||||
}
|
||||
{
|
||||
catRouterWithoutAuth.GET("getCategoryPublic", catApi.GetCategoryPublic) // 类别开放接口
|
||||
catRouterWithoutAuth.GET("/index", catApi.GetCategoryListPublic) // 获取类别列表公开接口
|
||||
}
|
||||
}
|
||||
|
@@ -1,10 +1,12 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/lckt/router/app"
|
||||
"git.echol.cn/loser/lckt/router/article"
|
||||
"git.echol.cn/loser/lckt/router/bot"
|
||||
"git.echol.cn/loser/lckt/router/category"
|
||||
"git.echol.cn/loser/lckt/router/example"
|
||||
"git.echol.cn/loser/lckt/router/notice"
|
||||
"git.echol.cn/loser/lckt/router/system"
|
||||
"git.echol.cn/loser/lckt/router/user"
|
||||
"git.echol.cn/loser/lckt/router/vip"
|
||||
@@ -16,8 +18,10 @@ type RouterGroup struct {
|
||||
System system.RouterGroup
|
||||
Example example.RouterGroup
|
||||
Category category.RouterGroup
|
||||
APP app.RouterGroup
|
||||
Bot bot.RouterGroup
|
||||
Article article.RouterGroup
|
||||
User user.UserRouter
|
||||
Vip vip.VipRouter
|
||||
Notice notice.NoticeRouter
|
||||
}
|
||||
|
9
router/notice/enter.go
Normal file
9
router/notice/enter.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package notice
|
||||
|
||||
import api "git.echol.cn/loser/lckt/api/v1"
|
||||
|
||||
type RouterGroup struct{ NoticeRouter }
|
||||
|
||||
var (
|
||||
notApi = api.ApiGroupApp.NoticeApiGroup.NoticeApi
|
||||
)
|
28
router/notice/notice.go
Normal file
28
router/notice/notice.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package notice
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/lckt/middleware"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type NoticeRouter struct{}
|
||||
|
||||
// InitNoticeRouter 初始化 通知 路由信息
|
||||
func (s *NoticeRouter) InitNoticeRouter(Router *gin.RouterGroup, PublicRouter *gin.RouterGroup) {
|
||||
notRouter := Router.Group("not").Use(middleware.OperationRecord())
|
||||
notRouterWithoutRecord := Router.Group("not")
|
||||
notRouterWithoutAuth := PublicRouter.Group("not")
|
||||
{
|
||||
notRouter.POST("createNotice", notApi.CreateNotice) // 新建通知
|
||||
notRouter.DELETE("deleteNotice", notApi.DeleteNotice) // 删除通知
|
||||
notRouter.DELETE("deleteNoticeByIds", notApi.DeleteNoticeByIds) // 批量删除通知
|
||||
notRouter.PUT("updateNotice", notApi.UpdateNotice) // 更新通知
|
||||
}
|
||||
{
|
||||
notRouterWithoutRecord.GET("findNotice", notApi.FindNotice) // 根据ID获取通知
|
||||
notRouterWithoutRecord.GET("getNoticeList", notApi.GetNoticeList) // 获取通知列表
|
||||
}
|
||||
{
|
||||
notRouterWithoutAuth.GET("getNoticePublic", notApi.GetNoticePublic) // 通知开放接口
|
||||
}
|
||||
}
|
@@ -11,21 +11,13 @@ type UserRouter struct{}
|
||||
func (s *UserRouter) InitUserRouter(Router *gin.RouterGroup, PublicRouter *gin.RouterGroup) {
|
||||
userRouter := Router.Group("app_user").Use(middleware.OperationRecord())
|
||||
{
|
||||
userRouter.GET("list", userApi.GetUserList) // 获取用户列表
|
||||
userRouter.PUT("setBalance", userApi.SetBalance) // 更新用户余额
|
||||
userRouter.POST("register", userApi.Register) // 注册
|
||||
userRouter.PUT("status/:id", userApi.SetUserStatus) // 更新用户状态
|
||||
userRouter.GET(":id", userApi.GetUserById) // 获取用户信息
|
||||
}
|
||||
}
|
||||
|
||||
func (s *UserRouter) InitAppUserRouter(PublicRouter *gin.RouterGroup) {
|
||||
appUserRouter := PublicRouter.Group("h5_user").Use(middleware.UserJWTAuth())
|
||||
{
|
||||
appUserRouter.DELETE("login", userApi.Login) // 短信验证码登录
|
||||
appUserRouter.POST("sms/send", userApi.SendCode) // 发送短信验证码
|
||||
appUserRouter.POST("wxLogin", userApi.WechatLogin) // 微信登录
|
||||
appUserRouter.POST("pwdlogin", userApi.PwdLogin) // 密码登录
|
||||
appUserRouter.GET(":id", userApi.GetUserById) // 获取用户信息
|
||||
userRouter.GET("list", userApi.GetUserList) // 获取用户列表
|
||||
userRouter.PUT("setBalance", userApi.SetBalance) // 更新用户余额
|
||||
userRouter.POST("register", userApi.Register) // 注册
|
||||
userRouter.PUT("status/:id", userApi.SetUserStatus) // 更新用户状态
|
||||
userRouter.GET(":id", userApi.GetUserById) // 获取用户信息
|
||||
userRouter.GET("/teachers", userApi.GetTeachers) // 获取教师列表
|
||||
userRouter.GET("/teacherApplyList", userApi.GetTeacherApplyList) // 获取教师信息
|
||||
userRouter.PUT("/teacherApply/status", userApi.UpdateTeacherApplyStatus) // 更新教师申请状态
|
||||
}
|
||||
}
|
||||
|
67
service/app/banner.go
Normal file
67
service/app/banner.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/lckt/global"
|
||||
"git.echol.cn/loser/lckt/model/app"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type BannerService struct{}
|
||||
|
||||
// CreateBanner 创建Banner
|
||||
func (b *BannerService) CreateBanner(p app.Banner) (err error) {
|
||||
if err = global.GVA_DB.Create(&p).Error; err != nil {
|
||||
global.GVA_LOG.Error("创建Banner失败!", zap.Error(err))
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeleteBanner 删除Banner
|
||||
func (b *BannerService) DeleteBanner(id uint) (err error) {
|
||||
if err = global.GVA_DB.Delete(&app.Banner{}, id).Error; err != nil {
|
||||
global.GVA_LOG.Error("删除Banner失败!", zap.Error(err))
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// UpdateBanner 更新Banner
|
||||
func (b *BannerService) UpdateBanner(p app.Banner) (err error) {
|
||||
if err = global.GVA_DB.Save(&p).Error; err != nil {
|
||||
global.GVA_LOG.Error("更新Banner失败!", zap.Error(err))
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetBannerList 获取Banner列表
|
||||
func (b *BannerService) GetBannerList(page, pageSize int) (list []app.Banner, total int64, err error) {
|
||||
if err = global.GVA_DB.Model(&app.Banner{}).Count(&total).Error; err != nil {
|
||||
global.GVA_LOG.Error("获取Banner总数失败!", zap.Error(err))
|
||||
return
|
||||
}
|
||||
if err = global.GVA_DB.Limit(pageSize).Offset((page - 1) * pageSize).Find(&list).Error; err != nil {
|
||||
global.GVA_LOG.Error("获取Banner列表失败!", zap.Error(err))
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetBannerByID 根据ID获取Banner
|
||||
func (b *BannerService) GetBannerByID(id int) (banner app.Banner, err error) {
|
||||
if err = global.GVA_DB.First(&banner, id).Error; err != nil {
|
||||
global.GVA_LOG.Error("获取Banner失败!", zap.Error(err))
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (b *BannerService) GetBannerIndex() (list []app.Banner, err error) {
|
||||
if err = global.GVA_DB.Where("status = ?", 1).Order("created_at desc").Find(&list).Error; err != nil {
|
||||
global.GVA_LOG.Error("获取Banner失败!", zap.Error(err))
|
||||
return
|
||||
}
|
||||
return
|
||||
|
||||
}
|
7
service/app/enter.go
Normal file
7
service/app/enter.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package app
|
||||
|
||||
type ServiceGroup struct {
|
||||
AppUserService
|
||||
BannerService
|
||||
OrderService
|
||||
}
|
160
service/app/order.go
Normal file
160
service/app/order.go
Normal file
@@ -0,0 +1,160 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"git.echol.cn/loser/lckt/global"
|
||||
"git.echol.cn/loser/lckt/model/app"
|
||||
"git.echol.cn/loser/lckt/model/app/request"
|
||||
"git.echol.cn/loser/lckt/model/user"
|
||||
"git.echol.cn/loser/lckt/utils/wechat"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type OrderService struct{}
|
||||
|
||||
// Pay 发起支付
|
||||
func (s *OrderService) Pay(p request.PayReq, ctx *gin.Context) (interface{}, error) {
|
||||
order := app.Order{}
|
||||
err := global.GVA_DB.Where("id = ? AND order_no = ?", p.OrderId, p.OrderNo).First(&order).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询订单失败", zap.Error(err))
|
||||
return "", err
|
||||
}
|
||||
|
||||
if order.Status == 3 {
|
||||
global.GVA_LOG.Error("订单已过期", zap.Int64("order_id", int64(order.ID)))
|
||||
return "", fmt.Errorf("订单已过期")
|
||||
}
|
||||
|
||||
if p.Mode == "h5" {
|
||||
payConf, err := wechat.H5Pay(&order, ctx)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("微信支付订单失败", zap.Error(err))
|
||||
return "", err
|
||||
}
|
||||
return payConf, nil
|
||||
} else if p.Mode == "jsapi" {
|
||||
payConf, err := wechat.JSAPIPay(&order, ctx)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("微信支付订单失败", zap.Error(err))
|
||||
return "", err
|
||||
}
|
||||
return payConf, nil
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (s *OrderService) Create(o *app.Order) (*app.Order, error) {
|
||||
// 生成订单号
|
||||
o.OrderNo = wechat.GenerateOrderNum()
|
||||
|
||||
// 查询订单商品价格
|
||||
price := 0
|
||||
if o.OrderType == 1 {
|
||||
err := global.GVA_DB.Table("article").Select("price").Where("id = ?", o.ArticleId).Scan(&price).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询商品价格失败", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
err := global.GVA_DB.Table("lckt_vip").Select("price").Where("id = ?", o.ArticleId).Scan(&price).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询VIP价格失败", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
o.Price = int64(price)
|
||||
o.Status = 1 // 设置订单状态为未付款
|
||||
// 设置openid
|
||||
openId := ""
|
||||
err := global.GVA_DB.Table("app_user").Where("id = ?", o.UserId).Select("open_id").Scan(&openId).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询用户OpenID失败,请检查微信是否绑定", zap.Error(err))
|
||||
return nil, fmt.Errorf("查询用户OpenID失败,请检查微信是否绑定")
|
||||
}
|
||||
o.OpenId = openId
|
||||
|
||||
err = global.GVA_DB.Create(&o).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("创建订单失败", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
return o, nil
|
||||
}
|
||||
|
||||
func (s *OrderService) GetOrderDetail(id string) (order app.Order, err error) {
|
||||
err = global.GVA_DB.Where("id = ?", id).First(&order).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取订单详情失败", zap.Error(err))
|
||||
return app.Order{}, err
|
||||
}
|
||||
return order, nil
|
||||
}
|
||||
|
||||
func (s *OrderService) BalancePay(p request.BalancePay) error {
|
||||
order := app.Order{}
|
||||
err := global.GVA_DB.Where("id = ? AND order_no = ?", p.OrderId, p.OrderNo).First(&order).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询订单失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
if order.Status != 1 {
|
||||
global.GVA_LOG.Error("订单状态错误,无法支付", zap.Int("status", order.Status))
|
||||
return err
|
||||
}
|
||||
// 检查用户余额是否足够
|
||||
var user user.User
|
||||
err = global.GVA_DB.Where("id = ?", p.UserId).Select("id,balance").First(&user).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询用户信息失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
// 将user.Balance转为int64类型进行比较
|
||||
balance := int64(user.Balance)
|
||||
if balance < order.Price/100 { // 订单价格是以分为单位存储的
|
||||
global.GVA_LOG.Error("用户余额不足", zap.Int64("balance", balance), zap.Int64("order_price", order.Price))
|
||||
return fmt.Errorf("用户余额不足")
|
||||
}
|
||||
|
||||
// 扣除用户余额
|
||||
balance -= order.Price / 100
|
||||
err = global.GVA_DB.Model(&user).Where("id = ?", p.UserId).Update("balance", balance).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("扣除用户余额失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
// 更新订单状态为已付款
|
||||
order.Status = 2
|
||||
err = global.GVA_DB.Model(&order).Where("id = ?", order.ID).Update("status", 2).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("更新订单状态失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
global.GVA_LOG.Info("余额支付成功", zap.Int64("user_id", int64(p.UserId)), zap.String("order_no", order.OrderNo))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *OrderService) GetOrderList(p request.GetOrderList, id uint) (orders []app.Order, total int64, err error) {
|
||||
limit := p.PageSize
|
||||
offset := p.PageSize * (p.Page - 1)
|
||||
|
||||
db := global.GVA_DB.Model(&app.Order{}).Where("user_id = ?", id)
|
||||
if p.Keyword != "" {
|
||||
db = db.Where("title LIKE ? ", "%"+p.Keyword+"%")
|
||||
}
|
||||
if p.Status != 0 {
|
||||
db = db.Where("status = ?", p.Status)
|
||||
}
|
||||
|
||||
err = db.Count(&total).Error
|
||||
err = db.Limit(limit).Offset(offset).Order("created_at desc").Find(&orders).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取订单列表失败", zap.Error(err))
|
||||
return nil, 0, err
|
||||
}
|
||||
return
|
||||
}
|
223
service/app/user.go
Normal file
223
service/app/user.go
Normal file
@@ -0,0 +1,223 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"git.echol.cn/loser/lckt/global"
|
||||
"git.echol.cn/loser/lckt/model/app"
|
||||
"git.echol.cn/loser/lckt/model/app/vo"
|
||||
"git.echol.cn/loser/lckt/model/user"
|
||||
"git.echol.cn/loser/lckt/model/user/request"
|
||||
utils2 "git.echol.cn/loser/lckt/utils"
|
||||
"github.com/ArtisanCloud/PowerSocialite/v3/src/providers"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type AppUserService struct{}
|
||||
|
||||
// Login 用户登录
|
||||
func (u *AppUserService) Login(req request.CodeLoginReq) (users user.User, err error) {
|
||||
// 1. 判断用户是否存在
|
||||
var count int64
|
||||
err = global.GVA_DB.Model(&user.User{}).Where("phone = ?", req.Phone).Count(&count).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询用户失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
// 2. 如果用户不存在,则创建用户
|
||||
if count == 0 {
|
||||
user := user.User{
|
||||
Phone: req.Phone,
|
||||
UserLabel: 1,
|
||||
}
|
||||
err = global.GVA_DB.Save(&user).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("创建用户失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
} else {
|
||||
err = global.GVA_DB.Where("phone = ?", req.Phone).First(&users).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询用户失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// WechatLogin 微信登录
|
||||
func (u *AppUserService) WechatLogin(info *providers.User) (users user.User, err error) {
|
||||
openID := info.GetOpenID()
|
||||
var count int64
|
||||
// 1. 判断用户是否存在
|
||||
err = global.GVA_DB.Model(&users).Where("open_id = ?", openID).Count(&count).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询用户失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
newUser := user.User{
|
||||
OpenId: openID,
|
||||
NickName: info.GetNickname(),
|
||||
Avatar: info.GetAvatar(),
|
||||
}
|
||||
err = global.GVA_DB.Save(&newUser).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("创建用户失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
return newUser, nil
|
||||
} else {
|
||||
err = global.GVA_DB.Where("open_id = ?", openID).First(&users).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询用户失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// PwdLogin 用户密码登录
|
||||
func (u *AppUserService) PwdLogin(req request.PwdLoginReq) (users user.User, err error) {
|
||||
// 1. 判断用户是否存在
|
||||
var count int64
|
||||
err = global.GVA_DB.Model(&user.User{}).Where("phone = ?", req.Phone).First(&users).Count(&count).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询用户失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
if count == 0 {
|
||||
global.GVA_LOG.Error("用户不存在")
|
||||
err = fmt.Errorf("用户不存在")
|
||||
return
|
||||
}
|
||||
|
||||
// 2. 判断密码是否正确
|
||||
err = bcrypt.CompareHashAndPassword([]byte(users.Password), []byte(req.Password))
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("密码错误", zap.Error(err))
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (u *AppUserService) GetUserInfo(id uint) (info vo.UserInfo, err error) {
|
||||
err = global.GVA_DB.Model(&user.User{}).Where("id = ?", id).First(&info).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询用户信息失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (u *AppUserService) Register(p request.RegisterReq) (userInfo user.User, err error) {
|
||||
err = global.GVA_DB.Transaction(func(tx *gorm.DB) error {
|
||||
// 1. 判断用户是否存在
|
||||
var count int64
|
||||
if err = tx.Model(&userInfo).Where("phone = ?", p.Phone).Count(&count).Error; err != nil {
|
||||
global.GVA_LOG.Error("查询用户失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
if count > 0 {
|
||||
global.GVA_LOG.Error("用户已存在")
|
||||
return fmt.Errorf("用户已存在")
|
||||
}
|
||||
|
||||
// 2. 如果用户不存在,则创建用户
|
||||
password, _ := bcrypt.GenerateFromPassword([]byte(p.Password), bcrypt.DefaultCost)
|
||||
userInfo = user.User{
|
||||
Phone: p.Phone,
|
||||
Password: string(password),
|
||||
NickName: p.NickName,
|
||||
UserLabel: 1,
|
||||
Status: 1,
|
||||
UserType: p.UserType,
|
||||
Avatar: "https://ui-avatars.com/api/?name=" + p.NickName + "&background=0081ff&color=ffffff&rounded=true",
|
||||
}
|
||||
|
||||
// 保存用户
|
||||
if err = tx.Save(&userInfo).Error; err != nil {
|
||||
global.GVA_LOG.Error("创建用户失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
// 生成邀请码
|
||||
code := utils2.GenerateInviteCode(userInfo.ID)
|
||||
userInfo.InviteCode = &code
|
||||
if err = tx.Model(&userInfo).Where("id = ?", userInfo.ID).Update("invite_code", code).Error; err != nil {
|
||||
global.GVA_LOG.Error("更新邀请码失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (u *AppUserService) ApplyTeacher(p app.TeacherApply) (err error) {
|
||||
if err = global.GVA_DB.Save(&p).Error; err != nil {
|
||||
global.GVA_LOG.Error("保存申请信息失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *AppUserService) GetTeacherApplyStatus(id uint) (teacherApply app.TeacherApply, err error) {
|
||||
// 获取最后一条申请记录
|
||||
err = global.GVA_DB.Model(&app.TeacherApply{}).Where("user_id = ?", id).Last(&teacherApply).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询申请记录失败", zap.Error(err))
|
||||
return teacherApply, err
|
||||
}
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
// BindWechat 手机登录用户绑定微信
|
||||
func (u *AppUserService) BindWechat(p request.BindWechatReq) (*user.User, error) {
|
||||
var userInfo user.User
|
||||
err := global.GVA_DB.Model(&user.User{}).Where("id = ?", p.Id).First(&userInfo).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询用户失败", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if userInfo.OpenId != "" {
|
||||
global.GVA_LOG.Error("用户已绑定微信")
|
||||
return nil, fmt.Errorf("用户已绑定微信")
|
||||
}
|
||||
|
||||
userInfo.OpenId = p.Openid
|
||||
|
||||
err = global.GVA_DB.Save(&userInfo).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("绑定微信失败", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &userInfo, nil
|
||||
}
|
||||
|
||||
// BindPhone 微信登录用户绑定手机号
|
||||
func (u *AppUserService) BindPhone(p request.BindPhoneReq) (*user.User, error) {
|
||||
var userInfo user.User
|
||||
err := global.GVA_DB.Model(&user.User{}).Where("id = ?", p.Id).First(&userInfo).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询用户失败", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userInfo.Phone = p.Phone
|
||||
pwd, _ := bcrypt.GenerateFromPassword([]byte(p.PassWord), bcrypt.DefaultCost)
|
||||
userInfo.Password = string(pwd)
|
||||
err = global.GVA_DB.Save(&userInfo).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("绑定微信失败", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &userInfo, nil
|
||||
}
|
@@ -2,8 +2,11 @@ package article
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/lckt/global"
|
||||
"git.echol.cn/loser/lckt/model/app"
|
||||
"git.echol.cn/loser/lckt/model/article"
|
||||
"git.echol.cn/loser/lckt/model/article/request"
|
||||
"git.echol.cn/loser/lckt/model/article/vo"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type ArticleService struct{}
|
||||
@@ -39,3 +42,64 @@ func (ArticleService) GetArticleList(pageInfo request.GetList) (list []article.A
|
||||
err = db.Limit(limit).Offset(offset).Find(&list).Error
|
||||
return
|
||||
}
|
||||
|
||||
func (s ArticleService) APPGetArticleList(pageInfo request.GetList) (list []vo.ArticleListVo, total int64, err error) {
|
||||
limit := pageInfo.PageSize
|
||||
offset := pageInfo.PageSize * (pageInfo.Page - 1)
|
||||
|
||||
db := global.GVA_DB.Model(&article.Article{})
|
||||
err = db.Count(&total).Error
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if pageInfo.Title != "" {
|
||||
db = db.Where("title LIKE ?", "%"+pageInfo.Title+"%")
|
||||
}
|
||||
if pageInfo.CategoryId != 0 {
|
||||
db = db.Where("category_id = ?", pageInfo.CategoryId)
|
||||
}
|
||||
if pageInfo.TeacherId != 0 {
|
||||
db = db.Where("teacher_id = ?", pageInfo.TeacherId)
|
||||
}
|
||||
|
||||
if pageInfo.Keyword != "" {
|
||||
db = db.Where("title LIKE ? OR article.desc LIKE ? OR teacher_name LIKE ?", "%"+pageInfo.Keyword+"%", "%"+pageInfo.Keyword+"%", "%"+pageInfo.Keyword+"%")
|
||||
}
|
||||
|
||||
err = db.Limit(limit).Offset(offset).Omit("teacher_avatar").Order("created_at desc").Find(&list).Error
|
||||
for i, a := range list {
|
||||
global.GVA_DB.Table("app_user").Select("avatar").Where("id = ?", a.TeacherId).Scan(&list[i].TeacherAvatar)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s ArticleService) APPGetArticle(id string, userId uint) (article *vo.ArticleVo, err error) {
|
||||
err = global.GVA_DB.Table("article").Where("id = ?", id).First(&article).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取文章失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
article.IsBuy = 1
|
||||
|
||||
global.GVA_DB.Table("app_user").Select("avatar").Where("id = ?", article.TeacherId).Scan(&article.TeacherAvatar)
|
||||
|
||||
// 判断是否免费
|
||||
if article.IsFree == 0 {
|
||||
// 如果不是免费文章,判断用户是否购买过
|
||||
var count int64
|
||||
err = global.GVA_DB.Model(&app.Order{}).Where("article_id = ? AND user_id = ? AND status = 2", id, userId).Count(&count).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询用户购买记录失败", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
if count == 0 {
|
||||
// 用户没有购买过,隐藏Content
|
||||
article.Content = ""
|
||||
article.IsBuy = 0 // 设置为未购买
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
@@ -5,6 +5,7 @@ import (
|
||||
"git.echol.cn/loser/lckt/global"
|
||||
"git.echol.cn/loser/lckt/model/category"
|
||||
categoryReq "git.echol.cn/loser/lckt/model/category/request"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type CategoryService struct{}
|
||||
@@ -83,3 +84,12 @@ func (catService *CategoryService) GetCategoryPublic(ctx context.Context) {
|
||||
// 此方法为获取数据源定义的数据
|
||||
// 请自行实现
|
||||
}
|
||||
|
||||
func (catService *CategoryService) GetIndexCategoryList() (list []category.Category, err error) {
|
||||
err = global.GVA_DB.Model(&category.Category{}).Where("categories.index = 1").Find(&list).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取首页分类失败", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@@ -1,10 +1,12 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/lckt/service/app"
|
||||
"git.echol.cn/loser/lckt/service/article"
|
||||
"git.echol.cn/loser/lckt/service/bot"
|
||||
"git.echol.cn/loser/lckt/service/category"
|
||||
"git.echol.cn/loser/lckt/service/example"
|
||||
"git.echol.cn/loser/lckt/service/notice"
|
||||
"git.echol.cn/loser/lckt/service/system"
|
||||
"git.echol.cn/loser/lckt/service/user"
|
||||
"git.echol.cn/loser/lckt/service/vip"
|
||||
@@ -15,9 +17,11 @@ var ServiceGroupApp = new(ServiceGroup)
|
||||
type ServiceGroup struct {
|
||||
SystemServiceGroup system.ServiceGroup
|
||||
ExampleServiceGroup example.ServiceGroup
|
||||
AppServiceGroup app.ServiceGroup
|
||||
CategoryServiceGroup category.ServiceGroup
|
||||
BotServiceGroup bot.ServiceGroup
|
||||
ArticleGroup article.ServiceGroup
|
||||
UserServiceGroup user.ServiceGroup
|
||||
VipServiceGroup vip.ServiceGroup
|
||||
NoticeServiceGroup notice.ServiceGroup
|
||||
}
|
||||
|
3
service/notice/enter.go
Normal file
3
service/notice/enter.go
Normal file
@@ -0,0 +1,3 @@
|
||||
package notice
|
||||
|
||||
type ServiceGroup struct{ NoticeService }
|
84
service/notice/notice.go
Normal file
84
service/notice/notice.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package notice
|
||||
|
||||
import (
|
||||
"context"
|
||||
"git.echol.cn/loser/lckt/global"
|
||||
"git.echol.cn/loser/lckt/model/notice"
|
||||
noticeReq "git.echol.cn/loser/lckt/model/notice/request"
|
||||
)
|
||||
|
||||
type NoticeService struct{}
|
||||
|
||||
// CreateNotice 创建通知记录
|
||||
// Author [yourname](https://github.com/yourname)
|
||||
func (notService *NoticeService) CreateNotice(ctx context.Context, not *notice.Notice) (err error) {
|
||||
err = global.GVA_DB.Create(not).Error
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteNotice 删除通知记录
|
||||
// Author [yourname](https://github.com/yourname)
|
||||
func (notService *NoticeService) DeleteNotice(ctx context.Context, ID string) (err error) {
|
||||
err = global.GVA_DB.Delete(¬ice.Notice{}, "id = ?", ID).Error
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteNoticeByIds 批量删除通知记录
|
||||
// Author [yourname](https://github.com/yourname)
|
||||
func (notService *NoticeService) DeleteNoticeByIds(ctx context.Context, IDs []string) (err error) {
|
||||
err = global.GVA_DB.Delete(&[]notice.Notice{}, "id in ?", IDs).Error
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateNotice 更新通知记录
|
||||
// Author [yourname](https://github.com/yourname)
|
||||
func (notService *NoticeService) UpdateNotice(ctx context.Context, not notice.Notice) (err error) {
|
||||
err = global.GVA_DB.Model(¬ice.Notice{}).Where("id = ?", not.ID).Updates(¬).Error
|
||||
return err
|
||||
}
|
||||
|
||||
// GetNotice 根据ID获取通知记录
|
||||
// Author [yourname](https://github.com/yourname)
|
||||
func (notService *NoticeService) GetNotice(ctx context.Context, ID string) (not notice.Notice, err error) {
|
||||
err = global.GVA_DB.Where("id = ?", ID).First(¬).Error
|
||||
return
|
||||
}
|
||||
|
||||
// GetNoticeInfoList 分页获取通知记录
|
||||
// Author [yourname](https://github.com/yourname)
|
||||
func (notService *NoticeService) GetNoticeInfoList(ctx context.Context, info noticeReq.NoticeSearch) (list []notice.Notice, total int64, err error) {
|
||||
limit := info.PageSize
|
||||
offset := info.PageSize * (info.Page - 1)
|
||||
// 创建db
|
||||
db := global.GVA_DB.Model(¬ice.Notice{})
|
||||
var nots []notice.Notice
|
||||
// 如果有条件搜索 下方会自动创建搜索语句
|
||||
if info.StartCreatedAt != nil && info.EndCreatedAt != nil {
|
||||
db = db.Where("created_at BETWEEN ? AND ?", info.StartCreatedAt, info.EndCreatedAt)
|
||||
}
|
||||
err = db.Count(&total).Error
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var OrderStr string
|
||||
orderMap := make(map[string]bool)
|
||||
orderMap["title"] = true
|
||||
if orderMap[info.Sort] {
|
||||
OrderStr = info.Sort
|
||||
if info.Order == "descending" {
|
||||
OrderStr = OrderStr + " desc"
|
||||
}
|
||||
db = db.Order(OrderStr)
|
||||
}
|
||||
|
||||
if limit != 0 {
|
||||
db = db.Limit(limit).Offset(offset)
|
||||
}
|
||||
|
||||
err = db.Find(¬s).Error
|
||||
return nots, total, err
|
||||
}
|
||||
func (notService *NoticeService) GetNoticePublic(ctx context.Context) {
|
||||
// 此方法为获取数据源定义的数据
|
||||
// 请自行实现
|
||||
}
|
@@ -4,10 +4,12 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"git.echol.cn/loser/lckt/global"
|
||||
"git.echol.cn/loser/lckt/model/app"
|
||||
"git.echol.cn/loser/lckt/model/app/vo"
|
||||
common "git.echol.cn/loser/lckt/model/common/request"
|
||||
"git.echol.cn/loser/lckt/model/user"
|
||||
"git.echol.cn/loser/lckt/model/user/request"
|
||||
"git.echol.cn/loser/lckt/utils/sms"
|
||||
"github.com/ArtisanCloud/PowerSocialite/v3/src/providers"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"math/rand"
|
||||
@@ -35,18 +37,19 @@ func (u *UserService) SendCode(req request.SendCodeReq) (err error) {
|
||||
result, err := rdb.Get(context.Background(), key).Result()
|
||||
if result != "" {
|
||||
global.GVA_LOG.Error("验证码发送过于频繁", zap.String("phone", req.Phone))
|
||||
err = fmt.Errorf("验证码发送过于频繁,请稍后再试")
|
||||
return
|
||||
}
|
||||
|
||||
rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
verifyCode := fmt.Sprintf("%06v", rand.Int31n(1000000))
|
||||
|
||||
if ok := sms.SendSMS(req.Phone, verifyCode); !ok {
|
||||
if ok := sms.DxbSendSMS(req.Phone, verifyCode); !ok {
|
||||
global.GVA_LOG.Error("发送验证码失败")
|
||||
return
|
||||
}
|
||||
//
|
||||
if err = rdb.Set(context.Background(), key, verifyCode, time.Duration(global.GVA_CONFIG.SMS.ExpireTime)*time.Second).Err(); err != nil {
|
||||
|
||||
if err = rdb.Set(context.Background(), key, verifyCode, 5*time.Minute).Err(); err != nil {
|
||||
global.GVA_LOG.Error("设置验证码缓存失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
@@ -54,38 +57,6 @@ func (u *UserService) SendCode(req request.SendCodeReq) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// Login 用户登录
|
||||
func (u *UserService) Login(req request.CodeLoginReq) (users user.User, err error) {
|
||||
// 1. 判断用户是否存在
|
||||
var count int64
|
||||
err = global.GVA_DB.Model(&user.User{}).Where("phone = ?", req.Phone).Count(&count).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询用户失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
// 2. 如果用户不存在,则创建用户
|
||||
if count == 0 {
|
||||
user := user.User{
|
||||
Phone: req.Phone,
|
||||
UserLabel: 1,
|
||||
}
|
||||
err = global.GVA_DB.Save(&user).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("创建用户失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
} else {
|
||||
err = global.GVA_DB.Where("phone = ?", req.Phone).First(&users).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询用户失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetUserList 获取用户列表
|
||||
func (u *UserService) GetUserList(p request.GetUserListReq) (userList []user.User, total int64, err error) {
|
||||
limit := p.PageSize
|
||||
@@ -103,6 +74,10 @@ func (u *UserService) GetUserList(p request.GetUserListReq) (userList []user.Use
|
||||
db = db.Where("status = ?", p.Status)
|
||||
}
|
||||
|
||||
if p.Name != "" {
|
||||
db = db.Where("nick_name like ?", "%"+p.Name+"%")
|
||||
}
|
||||
|
||||
err = db.Count(&total).Error
|
||||
if err != nil {
|
||||
return
|
||||
@@ -126,63 +101,6 @@ func (u *UserService) SetBalance(p request.SetBalanceReq) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// WechatLogin 微信登录
|
||||
func (u *UserService) WechatLogin(info *providers.User) (users user.User, err error) {
|
||||
openID := info.GetOpenID()
|
||||
var count int64
|
||||
// 1. 判断用户是否存在
|
||||
err = global.GVA_DB.Model(&users).Where("open_id = ?", openID).Count(&count).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询用户失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
newUser := user.User{
|
||||
OpenId: openID,
|
||||
NickName: info.GetNickname(),
|
||||
Avatar: info.GetAvatar(),
|
||||
}
|
||||
err = global.GVA_DB.Save(&newUser).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("创建用户失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
return newUser, nil
|
||||
} else {
|
||||
err = global.GVA_DB.Where("open_id = ?", openID).First(&users).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询用户失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// PwdLogin 用户密码登录
|
||||
func (u *UserService) PwdLogin(req request.PwdLoginReq) (users user.User, err error) {
|
||||
// 1. 判断用户是否存在
|
||||
var count int64
|
||||
err = global.GVA_DB.Model(&user.User{}).Where("phone = ?", req.Phone).First(&users).Count(&count).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询用户失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
if count == 0 {
|
||||
global.GVA_LOG.Error("用户不存在")
|
||||
err = fmt.Errorf("用户不存在")
|
||||
return
|
||||
}
|
||||
|
||||
// 2. 判断密码是否正确
|
||||
err = bcrypt.CompareHashAndPassword([]byte(users.Password), []byte(req.Password))
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("密码错误", zap.Error(err))
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Register 用户注册-后台用
|
||||
func (u *UserService) Register(req request.RegisterReq) (err error) {
|
||||
// 1. 判断用户是否存在
|
||||
@@ -207,7 +125,12 @@ func (u *UserService) Register(req request.RegisterReq) (err error) {
|
||||
Status: 1,
|
||||
UserType: req.UserType,
|
||||
}
|
||||
|
||||
if req.UserLabel != 0 {
|
||||
user.IsVip = 1
|
||||
date := time.Now().Truncate(24*time.Hour).AddDate(0, 0, 30)
|
||||
//将date转为string
|
||||
user.VipExpireTime = date.Format("2006-01-02 15:04:05")
|
||||
}
|
||||
err = global.GVA_DB.Save(&user).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("创建用户失败", zap.Error(err))
|
||||
@@ -241,3 +164,67 @@ func (u *UserService) SetUserStatus(id string) (err error) {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (u *UserService) GetTeachers(p common.PageInfo) (list []vo.UserInfo, total int64, err error) {
|
||||
limit := p.PageSize
|
||||
offset := p.PageSize * (p.Page - 1)
|
||||
// 创建db
|
||||
db := global.GVA_DB.Model(&user.User{}).Where("user_type = ?", 2)
|
||||
|
||||
err = db.Count(&total).Error
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = db.Limit(limit).Offset(offset).Find(&list).Error
|
||||
return
|
||||
}
|
||||
|
||||
func (u *UserService) GetTeacherApplyList(p request.GetTeacherApplyListReq) (list []app.TeacherApply, total int64, err error) {
|
||||
query := global.GVA_DB.Model(&app.TeacherApply{})
|
||||
|
||||
if p.Phone != "" {
|
||||
query = query.Where("phone LIKE ?", "%"+p.Phone+"%")
|
||||
}
|
||||
if p.Nickname != "" {
|
||||
query = query.Where("nick_name LIKE ?", "%"+p.Nickname+"%")
|
||||
}
|
||||
|
||||
err = query.Count(&total).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询申请列表总数失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
err = query.Offset((p.Page - 1) * p.PageSize).Limit(p.PageSize).Order("create_at desc").Find(&list).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询申请列表失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (u *UserService) UpdateTeacherApplyStatus(p app.TeacherApply) (err error) {
|
||||
err = global.GVA_DB.Save(&p).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("更新教师申请状态失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
// 如果申请通过,更新用户类型为教师
|
||||
if p.Status == 1 {
|
||||
var user user.User
|
||||
err = global.GVA_DB.Model(&user).Where("id = ?", p.UserId).First(&user).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询用户信息失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
user.UserType = 2 // 设置为教师
|
||||
err = global.GVA_DB.Save(&user).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("更新用户类型失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
41
test/rain_test.go
Normal file
41
test/rain_test.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"git.echol.cn/loser/lckt/utils/sms"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestRain(t *testing.T) {
|
||||
rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
verifyCode := fmt.Sprintf("%06v", rand.Int31n(1000000))
|
||||
fmt.Println(verifyCode)
|
||||
}
|
||||
|
||||
func TestPwd(t *testing.T) {
|
||||
password, _ := bcrypt.GenerateFromPassword([]byte("loser7659"), bcrypt.DefaultCost)
|
||||
fmt.Println(string(password))
|
||||
|
||||
err := bcrypt.CompareHashAndPassword(password, []byte("122456"))
|
||||
if err != nil {
|
||||
fmt.Println("密码错误")
|
||||
} else {
|
||||
fmt.Println("密码正确")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCode(t *testing.T) {
|
||||
// 测试验证码生成
|
||||
rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
verifyCode := fmt.Sprintf("%06v", rand.Int31n(1000000))
|
||||
|
||||
test := sms.SendSMSTest("17754945397", verifyCode)
|
||||
if test {
|
||||
fmt.Println("短信发送成功")
|
||||
} else {
|
||||
fmt.Println("短信发送失败")
|
||||
}
|
||||
}
|
23
utils/rand_code.go
Normal file
23
utils/rand_code.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
|
||||
func GenerateInviteCode(userID uint) string {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
// 拼接用户ID和随机数
|
||||
data := fmt.Sprintf("%d%d", userID, rand.Intn(1000000))
|
||||
hash := md5.Sum([]byte(data))
|
||||
code := ""
|
||||
for i := 0; i < 6; i++ {
|
||||
// 取哈希的前6位,每位映射到字符集
|
||||
code += string(charset[int(hash[i])%len(charset)])
|
||||
}
|
||||
return code
|
||||
}
|
@@ -2,10 +2,15 @@ package sms
|
||||
|
||||
// SMS短信服务
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"git.echol.cn/loser/lckt/global"
|
||||
"github.com/alibabacloud-go/tea/tea"
|
||||
"go.uber.org/zap"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
util "github.com/alibabacloud-go/tea-utils/v2/service"
|
||||
|
||||
@@ -71,3 +76,67 @@ func SendSMS(phone string, code string) bool {
|
||||
global.GVA_LOG.Info("短信[阿里云]", zap.String("发送成功", "手机号: "+phone))
|
||||
return true
|
||||
}
|
||||
|
||||
func SendSMSTest(phone, code string) bool {
|
||||
endpoint := "https://dfsns.market.alicloudapi.com/data/send_sms"
|
||||
templateID := "CST_ptdie100"
|
||||
|
||||
// 构造 POST 表单数据
|
||||
form := url.Values{}
|
||||
form.Set("content", fmt.Sprintf("code:%s", code))
|
||||
form.Set("template_id", templateID)
|
||||
form.Set("phone_number", phone)
|
||||
|
||||
// 创建 HTTP 请求
|
||||
req, err := http.NewRequest("POST", endpoint, strings.NewReader(form.Encode()))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// 添加请求头
|
||||
req.Header.Set("Authorization", "APPCODE "+"b8f46ced154b44c5a40a0a49a91e1634")
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
// 创建 HTTP 客户端(跳过证书校验,模拟 curl -k)
|
||||
client := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
},
|
||||
}
|
||||
|
||||
// 发送请求
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.Status != "ok" {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func DxbSendSMS(phone, code string) bool {
|
||||
// 内容 通过urlEncode编码
|
||||
content := "【海口龙华铁坚成电子商务商行】您的验证码是" + code + "。如非本人操作,请忽略本短信"
|
||||
// urlencode编码内容
|
||||
content = url.QueryEscape(content)
|
||||
|
||||
api := "https://api.smsbao.com/sms?u=lchz5599&p=7ea114c87a224cd38a0d616b9be3faed&g=海口龙华铁坚成电子商务商行&m=" + phone + "&c=" + content
|
||||
|
||||
// 发送GET请求
|
||||
resp, err := http.Get(api)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("发送短信请求失败:", zap.Error(err))
|
||||
return false
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
global.GVA_LOG.Info("发送短信请求成功,手机号:" + phone + ",验证码:" + code)
|
||||
return true
|
||||
} else {
|
||||
global.GVA_LOG.Error("发送短信请求失败:", zap.Error(err))
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
245
utils/wechat/pay.go
Normal file
245
utils/wechat/pay.go
Normal file
@@ -0,0 +1,245 @@
|
||||
package wechat
|
||||
|
||||
import (
|
||||
gfmt "fmt"
|
||||
"git.echol.cn/loser/lckt/global"
|
||||
"git.echol.cn/loser/lckt/model/app"
|
||||
"github.com/ArtisanCloud/PowerLibs/v3/fmt"
|
||||
"github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/models"
|
||||
"github.com/ArtisanCloud/PowerWeChat/v3/src/payment"
|
||||
wxRequst "github.com/ArtisanCloud/PowerWeChat/v3/src/payment/notify/request"
|
||||
"github.com/ArtisanCloud/PowerWeChat/v3/src/payment/order/request"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
"log"
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
var WechatPay *payment.Payment
|
||||
|
||||
// InitWechatPay 初始化微信支付
|
||||
func InitWechatPay() {
|
||||
PaymentService, err := payment.NewPayment(&payment.UserConfig{
|
||||
AppID: "wx3d21df18d7f8f9fc", // 小程序、公众号或者企业微信的appid
|
||||
MchID: global.GVA_CONFIG.Pays[0].MchId, // 商户号 appID
|
||||
MchApiV3Key: "1a3sd8561d5179Df152D4789aD38wG9s", // 微信V3接口调用必填
|
||||
Key: "57s14dFG915486Sd5617f23d45f671Ad",
|
||||
CertPath: `-----BEGIN CERTIFICATE-----
|
||||
MIIEKzCCAxOgAwIBAgIUWaiR+0A+x6HPIJDbnI7HBL1DsQEwDQYJKoZIhvcNAQEL
|
||||
BQAwXjELMAkGA1UEBhMCQ04xEzARBgNVBAoTClRlbnBheS5jb20xHTAbBgNVBAsT
|
||||
FFRlbnBheS5jb20gQ0EgQ2VudGVyMRswGQYDVQQDExJUZW5wYXkuY29tIFJvb3Qg
|
||||
Q0EwHhcNMjUwNzIzMDU1MDU5WhcNMzAwNzIyMDU1MDU5WjCBhDETMBEGA1UEAwwK
|
||||
MTY0Njg3NDc1MzEbMBkGA1UECgwS5b6u5L+h5ZWG5oi357O757ufMTAwLgYDVQQL
|
||||
DCfmtbflj6PpvpnljY7pk4HlnZrmiJDnlLXlrZDllYbliqHllYbooYwxCzAJBgNV
|
||||
BAYTAkNOMREwDwYDVQQHDAhTaGVuWmhlbjCCASIwDQYJKoZIhvcNAQEBBQADggEP
|
||||
ADCCAQoCggEBAMUD4rf7ct0zUxJKGww+8c38L/IBx9yeNpOc7lav+CEwqZvieesy
|
||||
p1I2hbY+hwOZm23//0Vxcnl6fkr5wdidE/RsN808sGkYsetJ6q7LZsgsxnbaQd7w
|
||||
rBc4GlOh1Q4teErbjNTUMuozYzikEjMjgZMxs7i5WJnFMMVvgJS4681UKH8MQG93
|
||||
BnaaD5zEyofAnOov7mvC0KZ0RpqwiLdUsMaPzYILzccOv7Tj82Z/qLw8AFcI+7L1
|
||||
mq8trZxmSPXP5Fypz+1VKLKR9MaELUuQLPouewDjXkKTBnxkhoWow6huuz3qyUUo
|
||||
i7uJtnmDn6Eu+60ch61BB5ws3rhWDpltR/0CAwEAAaOBuTCBtjAJBgNVHRMEAjAA
|
||||
MAsGA1UdDwQEAwID+DCBmwYDVR0fBIGTMIGQMIGNoIGKoIGHhoGEaHR0cDovL2V2
|
||||
Y2EuaXRydXMuY29tLmNuL3B1YmxpYy9pdHJ1c2NybD9DQT0xQkQ0MjIwRTUwREJD
|
||||
MDRCMDZBRDM5NzU0OTg0NkMwMUMzRThFQkQyJnNnPUhBQ0M0NzFCNjU0MjJFMTJC
|
||||
MjdBOUQzM0E4N0FEMUNERjU5MjZFMTQwMzcxMA0GCSqGSIb3DQEBCwUAA4IBAQBk
|
||||
WmviQM60S/l4I4/RtQWtiZS+85fk2ePVSk5gBUS27W4yTw+aXy5RYaCLcOVXTXje
|
||||
PpAfE7QvkjZxMg/k0SJR0/NbgDNF6xuCbMj78+czWVCkqHwPjY4hWRdsM9s44Orp
|
||||
lwFrG/UNRhPgXcdwNQj8MNzy3nGeO0HWbJFBWcuRhSSwXpxv3Sh6pZviL3QA+lrV
|
||||
7QB7d0fEu8sSGbwnIB0oXl7y78l2/D20p57TKHsq5IPfQfDdExNXKV60ikMt/MdP
|
||||
C4ygbeRpSAobr6qMTXcpWlbH3KfIPOUFny3nkN8C6lm//QUl7ynOXkECcmIHY6XF
|
||||
yMx70ORdoBl1DaZ369P9
|
||||
-----END CERTIFICATE-----
|
||||
`, // 微信V2接口调用必填
|
||||
KeyPath: `-----BEGIN PRIVATE KEY-----
|
||||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDFA+K3+3LdM1MS
|
||||
ShsMPvHN/C/yAcfcnjaTnO5Wr/ghMKmb4nnrMqdSNoW2PocDmZtt//9FcXJ5en5K
|
||||
+cHYnRP0bDfNPLBpGLHrSequy2bILMZ22kHe8KwXOBpTodUOLXhK24zU1DLqM2M4
|
||||
pBIzI4GTMbO4uViZxTDFb4CUuOvNVCh/DEBvdwZ2mg+cxMqHwJzqL+5rwtCmdEaa
|
||||
sIi3VLDGj82CC83HDr+04/Nmf6i8PABXCPuy9ZqvLa2cZkj1z+Rcqc/tVSiykfTG
|
||||
hC1LkCz6LnsA415CkwZ8ZIaFqMOobrs96slFKIu7ibZ5g5+hLvutHIetQQecLN64
|
||||
Vg6ZbUf9AgMBAAECggEBAI4q66PQM2cj7kI4b6Q6l8sIvKBqYIr3MHL8v5CWkvuA
|
||||
XiQ7Hbd3af6NkZedL1iNs/eAz/iQkQbQOepoqFVjpE6w+OOFc9ejFmCvikZwSM8S
|
||||
YHTLstTp34Ux2u2WzmPYtAFwxQOfzM3sHyF0ZB269Xn+V65pMWJlRXhzqdmoR6B2
|
||||
YYLzHVBZAbgj7IlS+f1WPkkbrb3BIs71tYyPS3Ng6nhGgXrnqmE5fhm93wR9KR5R
|
||||
RcgzFGH/8qD2LndmJz8tYtjvlq016Pp40VrYabIrvX6apglayShLQ+2XUe8VKH2Q
|
||||
dQPIKL3R0ZeDeqwn4PTMclBYd/nXFSbIQndufTDY84ECgYEA8RTcCPOFYkoDeQIr
|
||||
yKqWo1P1cKsB/bGzvLmo+FqFQij5lmj9iDdvpSQJ58Uj+TFy5qNRT6VueF8U4pNd
|
||||
Fmcbw05cH+9NzAXji6aYWCargbmhW4L+rzub+TQmdAfqXbBknVgrLenY4LFnFFm1
|
||||
SIdJRid3vCwiRuhcjecg4FasR+kCgYEA0TTw4ZGxPCX5qt00PUFvuGpqFxFqZ7Ah
|
||||
wapwQTUY48WeC0Pg7Iq6ipnhjggkgYLkGc1klmTwJXKV0acIS5/DNclepMWZPwIc
|
||||
+GcKclQsy8MHm+L2tPGBQosRGiSrXi5LzBIQsYSyhBRDUSqu+yj7TTd2XqpUkLAw
|
||||
qdrWiIjVBvUCgYAOkh0uaVGBfEmzcZ8l1LGgE339HkjThX8AhBQjVo1BT2quXZAd
|
||||
QIR97ayvlmmzMPrp16sdbjk8Czse6pswtHCoID9PKs5/60cydJI2mbe58nc/Ka6s
|
||||
9qRZrn44exX+LaAXJnINp1mVUwOQ5k8foBWcqNwCwoQb1wVpCjQhevuUqQKBgQC1
|
||||
SA+3Fr0iprF6iqWastoxThzSEmhGowwNOjh9eJoxvOsfTdlYfzn3ojIeFhY0F4y6
|
||||
gw03eQ3TFUCXZAq/JRhNwkl9tC//tkAOS5N00FXk1wH/5aLr1h2w4LqYEdBhEvLh
|
||||
SYInoRnjc3+FlNv9jVx9Y6Lxkt0mZ1YzyQp/UzptBQKBgFTOonik05rMHGlEg9Ng
|
||||
unjDAEK+7tXhM/RKxWDs7pZeGbdl1c/NDOX4wIbuK2cCkV0v/qfg6Ex2Obf5rqM4
|
||||
R5lrs9y30rZbwmfms8G3cimTAsOcLqObKkBED+g07t+3bw44JXLwYLPm/z//bIcI
|
||||
fkxyxKL5ZJhP3/bPJJj8UAq3
|
||||
-----END PRIVATE KEY-----
|
||||
`,
|
||||
SerialNo: global.GVA_CONFIG.Pays[0].SerialNo, // 商户支付证书序列号
|
||||
NotifyURL: global.GVA_CONFIG.Pays[0].NotifyUrl, // 微信支付回调地址
|
||||
HttpDebug: false,
|
||||
Log: payment.Log{
|
||||
Level: "debug",
|
||||
// 可以重定向到你的目录下,如果设置File和Error,默认会在当前目录下的wechat文件夹下生成日志
|
||||
File: "/Users/user/wechat/payment/info.log",
|
||||
Error: "/Users/user/wechat/payment/error.log",
|
||||
Stdout: false, // 是否打印在终端
|
||||
},
|
||||
Http: payment.Http{
|
||||
Timeout: 30.0,
|
||||
BaseURI: "https://api.mch.weixin.qq.com",
|
||||
},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Println("初始化微信支付 SDK失败", err)
|
||||
}
|
||||
WechatPay = PaymentService
|
||||
}
|
||||
|
||||
// H5Pay 发送订单
|
||||
func H5Pay(order *app.Order, ctx *gin.Context) (interface{}, error) {
|
||||
options := &request.RequestH5Prepay{
|
||||
Amount: &request.H5Amount{
|
||||
Total: int(order.Price),
|
||||
Currency: "CNY",
|
||||
},
|
||||
Attach: order.Desc,
|
||||
Description: order.Desc,
|
||||
OutTradeNo: order.OrderNo, // 这里是商户订单号,不能重复提交给微信
|
||||
SceneInfo: &request.H5SceneInfo{
|
||||
PayerClientIP: ctx.ClientIP(), // 用户终端IP
|
||||
H5Info: &request.H5H5Info{
|
||||
Type: "Wap",
|
||||
AppName: "老陈课堂",
|
||||
},
|
||||
},
|
||||
}
|
||||
// 下单
|
||||
response, err := WechatPay.Order.TransactionH5(ctx, options)
|
||||
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("微信支付下单失败", zap.Error(err))
|
||||
return "", err
|
||||
}
|
||||
/*
|
||||
payConf, err := WechatPay.JSSDK.BridgeConfig(response.PrepayID, true)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取微信支付配置失败", zap.Error(err))
|
||||
return "", err
|
||||
}*/
|
||||
global.GVA_LOG.Info("微信支付配置", zap.Any("payConf", response))
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func JSAPIPay(order *app.Order, ctx *gin.Context) (interface{}, error) {
|
||||
options := &request.RequestJSAPIPrepay{
|
||||
Amount: &request.JSAPIAmount{
|
||||
Total: int(order.Price),
|
||||
Currency: "CNY",
|
||||
},
|
||||
Attach: order.Name,
|
||||
Description: order.Desc,
|
||||
OutTradeNo: order.OrderNo, // 这里是商户订单号,不能重复提交给微信
|
||||
Payer: &request.JSAPIPayer{
|
||||
OpenID: order.OpenId, // 用户的openid, 记得也是动态的。
|
||||
},
|
||||
}
|
||||
|
||||
// 下单
|
||||
response, err := WechatPay.Order.JSAPITransaction(ctx, options)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// payConf大致如下:
|
||||
// {
|
||||
// "appId": "ww16143ea0101327c7",
|
||||
// "nonceStr": "jcMfo7lsfM3LPWkLRJb7rQU6WeatStEU",
|
||||
// "package": "prepay_id=xxxxx",
|
||||
// "paySign": "hSPF2wU0aYeTq+DJ14ELM...省略部分数据...RrOmlEkZXVxqCdZmniLdA==",
|
||||
// "signType": "RSA",
|
||||
// "timeStamp": "1634305101"
|
||||
// }
|
||||
payConf, err := WechatPay.JSSDK.BridgeConfig(response.PrepayID, true)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取微信支付配置失败", zap.Error(err))
|
||||
return "", err
|
||||
}
|
||||
|
||||
return payConf, nil
|
||||
}
|
||||
|
||||
// NotifyHandle 微信支付回调处理
|
||||
func NotifyHandle(ctx *gin.Context) error {
|
||||
res, err := WechatPay.HandlePaidNotify(
|
||||
ctx.Request,
|
||||
func(message *wxRequst.RequestNotify, transaction *models.Transaction, fail func(message string)) interface{} {
|
||||
// 看下支付通知事件状态
|
||||
// 这里可能是微信支付失败的通知,所以可能需要在数据库做一些记录,然后告诉微信我处理完成了。
|
||||
if message.EventType != "TRANSACTION.SUCCESS" {
|
||||
fmt.Dump(transaction)
|
||||
return true
|
||||
}
|
||||
|
||||
if transaction.OutTradeNo != "" {
|
||||
// 这里对照自有数据库里面的订单做查询以及支付状态改变
|
||||
// 更新订单状态为已支付
|
||||
order := app.Order{}
|
||||
err := global.GVA_DB.Model(&app.Order{}).Where("order_no = ?", transaction.OutTradeNo).First(&order).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询订单失败", zap.Error(err))
|
||||
return nil
|
||||
}
|
||||
order.Status = 2 // 设置订单状态为已支付
|
||||
err = global.GVA_DB.Save(&order).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("更新订单状态失败", zap.Error(err))
|
||||
return nil
|
||||
}
|
||||
log.Printf("订单号:%s 支付成功", transaction.OutTradeNo)
|
||||
} else {
|
||||
// 因为微信这个回调不存在订单号,所以可以告诉微信我还没处理成功,等会它会重新发起通知
|
||||
// 如果不需要,直接返回true即可
|
||||
fail("payment fail")
|
||||
return nil
|
||||
}
|
||||
return true
|
||||
},
|
||||
)
|
||||
|
||||
// 这里可能是因为不是微信官方调用的,无法正常解析出transaction和message,所以直接抛错。
|
||||
if err != nil {
|
||||
res.StatusCode = 502
|
||||
err = res.Write(ctx.Writer)
|
||||
return err
|
||||
//panic(err)
|
||||
}
|
||||
|
||||
// 这里根据之前返回的是true或者fail,框架这边自动会帮你回复微信
|
||||
err = res.Write(ctx.Writer)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
global.GVA_LOG.Info("微信支付回调处理成功", zap.Any("response", res))
|
||||
return nil
|
||||
}
|
||||
|
||||
func GenerateOrderNum() string {
|
||||
now := time.Now()
|
||||
dateStr := now.Format("060102") // 年的后2位+月+日
|
||||
seconds := int(now.Sub(time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())).Seconds())
|
||||
secondsStr := gfmt.Sprintf("%05d", seconds)
|
||||
nanoStr := gfmt.Sprintf("%06d", now.Nanosecond()/1000) // 微秒级,6位
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
randStr := gfmt.Sprintf("%03d", rand.Intn(1000)) // 3位随机数
|
||||
orderNo := gfmt.Sprintf("%s%s%s%s%s", "5258", dateStr, secondsStr, nanoStr, randStr)
|
||||
return orderNo
|
||||
}
|
@@ -3,21 +3,18 @@ package wechat
|
||||
import (
|
||||
"git.echol.cn/loser/lckt/global"
|
||||
"github.com/ArtisanCloud/PowerSocialite/v3/src/providers"
|
||||
"github.com/ArtisanCloud/PowerWeChat/v3/src/kernel"
|
||||
"github.com/ArtisanCloud/PowerWeChat/v3/src/officialAccount"
|
||||
"github.com/ArtisanCloud/PowerWeChat/v3/src/payment"
|
||||
"go.uber.org/zap"
|
||||
"log"
|
||||
)
|
||||
|
||||
var WeOfficial *officialAccount.OfficialAccount
|
||||
var WechatPay *payment.Payment
|
||||
|
||||
// InitWeOfficial 初始化微信公众号
|
||||
func InitWeOfficial() {
|
||||
OfficialAccountApp, err := officialAccount.NewOfficialAccount(&officialAccount.UserConfig{
|
||||
AppID: "wx3d21df18d7f8f9fc", // 公众号、小程序的appid
|
||||
Secret: "ffce59a9a9272c1aaee53950e96617d8", //
|
||||
Secret: "3ab19e9b6a5e155c25ac6457be650047", //
|
||||
|
||||
Log: officialAccount.Log{
|
||||
Level: "debug",
|
||||
@@ -37,43 +34,6 @@ func InitWeOfficial() {
|
||||
WeOfficial = OfficialAccountApp
|
||||
}
|
||||
|
||||
// InitWechatPay 初始化微信支付
|
||||
func InitWechatPay() {
|
||||
PaymentService, err := payment.NewPayment(&payment.UserConfig{
|
||||
AppID: "[app_id]", // 小程序、公众号或者企业微信的appid
|
||||
MchID: "[mch_id]", // 商户号 appID
|
||||
MchApiV3Key: "[mch_api_v3_key]", // 微信V3接口调用必填
|
||||
Key: "[key]", // 微信V2接口调用必填
|
||||
CertPath: "[wx_cert_path]", // 商户后台支付的Cert证书路径
|
||||
KeyPath: "[wx_key_path]", // 商户后台支付的Key证书路径
|
||||
SerialNo: "[serial_no]", // 商户支付证书序列号
|
||||
NotifyURL: "[notify_url]", // 微信支付回调地址
|
||||
HttpDebug: true,
|
||||
Log: payment.Log{
|
||||
Level: "debug",
|
||||
// 可以重定向到你的目录下,如果设置File和Error,默认会在当前目录下的wechat文件夹下生成日志
|
||||
File: "/Users/user/wechat/payment/info.log",
|
||||
Error: "/Users/user/wechat/payment/error.log",
|
||||
Stdout: false, // 是否打印在终端
|
||||
},
|
||||
Http: payment.Http{
|
||||
Timeout: 30.0,
|
||||
BaseURI: "https://api.mch.weixin.qq.com",
|
||||
},
|
||||
// 可选,不传默认走程序内存
|
||||
Cache: kernel.NewRedisClient(&kernel.UniversalOptions{
|
||||
Addrs: []string{"127.0.0.1:6379"},
|
||||
Password: "",
|
||||
DB: 0,
|
||||
}),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Println("初始化微信支付 SDK失败", err)
|
||||
}
|
||||
WechatPay = PaymentService
|
||||
}
|
||||
|
||||
func GetUserInfo(code string) *providers.User {
|
||||
userFromCode, err := WeOfficial.OAuth.UserFromCode(code)
|
||||
if err != nil {
|
||||
|
Reference in New Issue
Block a user