Compare commits
25 Commits
44f1b5d4b5
...
main
Author | SHA1 | Date | |
---|---|---|---|
9f220dc025 | |||
7951f77f47 | |||
bb2a68fb61 | |||
8fd1968cf6 | |||
666527b976 | |||
502a9b82d7 | |||
7a729211d1 | |||
f0a67822ef | |||
70f65c96bd | |||
48ddb72cf2 | |||
73e97ba151 | |||
c3d5d532cd | |||
f86b56a79d | |||
fa0234d385 | |||
8106282c8b | |||
aac13d369c | |||
cf0f60d221 | |||
a5ae680f94 | |||
5faff4afa0 | |||
e074395859 | |||
bf220076dd | |||
7903175c91 | |||
ad6773c575 | |||
159ef59749 | |||
ed3c15fbb6 |
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,12 +1,15 @@
|
||||
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"
|
||||
)
|
||||
|
||||
var ApiGroupApp = new(ApiGroup)
|
||||
@@ -14,8 +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,102 +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"
|
||||
"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 待完善微信登录
|
||||
}
|
||||
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)
|
||||
@@ -117,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)
|
||||
@@ -130,3 +47,111 @@ func (*APPUserApi) SetBalance(ctx *gin.Context) {
|
||||
}
|
||||
r.OkWithMessage("设置用户余额成功", ctx)
|
||||
}
|
||||
|
||||
// Register 注册-后台用
|
||||
func (*UserApi) Register(ctx *gin.Context) {
|
||||
var p request.RegisterReq
|
||||
if err := ctx.ShouldBind(&p); err != nil {
|
||||
r.FailWithMessage(err.Error(), ctx)
|
||||
global.GVA_LOG.Error("参数错误,注册失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
if err := userService.Register(p); err != nil {
|
||||
r.FailWithMessage("注册失败", ctx)
|
||||
return
|
||||
}
|
||||
|
||||
r.OkWithMessage("注册成功", ctx)
|
||||
}
|
||||
|
||||
// SetUserStatus 设置用户状态
|
||||
func (*UserApi) SetUserStatus(ctx *gin.Context) {
|
||||
id := ctx.Param("id")
|
||||
if id == "" {
|
||||
r.FailWithMessage("参数错误", ctx)
|
||||
return
|
||||
}
|
||||
|
||||
if err := userService.SetUserStatus(id); err != nil {
|
||||
r.FailWithMessage("设置用户状态失败", ctx)
|
||||
return
|
||||
}
|
||||
r.OkWithMessage("设置用户状态成功", ctx)
|
||||
}
|
||||
|
||||
// GetUserById 根据id获取用户信息
|
||||
func (*UserApi) GetUserById(ctx *gin.Context) {
|
||||
id := ctx.Param("id")
|
||||
if id == "" {
|
||||
r.FailWithMessage("参数错误", ctx)
|
||||
return
|
||||
}
|
||||
user, err := userService.GetUserById(id)
|
||||
if err != nil {
|
||||
r.FailWithMessage("获取用户信息失败", ctx)
|
||||
return
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
7
api/v1/vip/enter.go
Normal file
7
api/v1/vip/enter.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package vip
|
||||
|
||||
import "git.echol.cn/loser/lckt/service"
|
||||
|
||||
type ApiGroup struct{ VipApi }
|
||||
|
||||
var vipService = service.ServiceGroupApp.VipServiceGroup.VipService
|
88
api/v1/vip/vip.go
Normal file
88
api/v1/vip/vip.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package vip
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/lckt/model/common/request"
|
||||
"git.echol.cn/loser/lckt/model/common/response"
|
||||
"git.echol.cn/loser/lckt/model/vip"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type VipApi struct{}
|
||||
|
||||
// GetVipList 获取会员列表
|
||||
func (v *VipApi) GetVipList(ctx *gin.Context) {
|
||||
var p request.PageInfo
|
||||
if err := ctx.ShouldBind(&p); err != nil {
|
||||
response.FailWithMessage("参数有误", ctx)
|
||||
return
|
||||
}
|
||||
|
||||
vipList, total, err := vipService.GetVipList(p)
|
||||
if err != nil {
|
||||
response.FailWithMessage("获取VIP列表失败", ctx)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(
|
||||
response.PageResult{
|
||||
List: vipList,
|
||||
Total: total,
|
||||
Page: p.Page,
|
||||
PageSize: p.PageSize,
|
||||
},
|
||||
"获取VIP列表成功",
|
||||
ctx,
|
||||
)
|
||||
}
|
||||
|
||||
// Create 创建会员
|
||||
func (v *VipApi) Create(ctx *gin.Context) {
|
||||
var p vip.Vip
|
||||
if err := ctx.ShouldBind(&p); err != nil {
|
||||
response.FailWithMessage("参数有误", ctx)
|
||||
return
|
||||
}
|
||||
if err := vipService.CreateVip(p); err != nil {
|
||||
response.FailWithMessage("创建会员失败", ctx)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("创建会员成功", ctx)
|
||||
}
|
||||
|
||||
// Update 更新会员
|
||||
func (v *VipApi) Update(ctx *gin.Context) {
|
||||
var p vip.Vip
|
||||
if err := ctx.ShouldBind(&p); err != nil {
|
||||
response.FailWithMessage("参数有误", ctx)
|
||||
return
|
||||
}
|
||||
if err := vipService.UpdateVip(p); err != nil {
|
||||
response.FailWithMessage("更新会员失败", ctx)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("更新会员成功", ctx)
|
||||
}
|
||||
|
||||
// Delete 删除会员
|
||||
func (v *VipApi) Delete(ctx *gin.Context) {
|
||||
var p vip.Vip
|
||||
if err := ctx.ShouldBind(&p); err != nil {
|
||||
response.FailWithMessage("参数有误", ctx)
|
||||
return
|
||||
}
|
||||
if err := vipService.DeleteVip(p); err != nil {
|
||||
response.FailWithMessage("删除会员失败", ctx)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("删除会员成功", ctx)
|
||||
}
|
||||
|
||||
// GetVipById 获取会员详情
|
||||
func (v *VipApi) GetVipById(ctx *gin.Context) {
|
||||
id := ctx.Param("id")
|
||||
vip, err := vipService.GetVipById(id)
|
||||
if err != nil {
|
||||
response.FailWithMessage("获取会员详情失败", ctx)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(vip, "获取会员详情成功", ctx)
|
||||
}
|
56
config.yaml
56
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: ""
|
||||
@@ -178,9 +178,9 @@ qiniu:
|
||||
use-https: false
|
||||
use-cdn-domains: false
|
||||
redis:
|
||||
name: ""
|
||||
addr: 127.0.0.1:6379
|
||||
password: ""
|
||||
name: "hw"
|
||||
addr: 120.46.165.63:6379
|
||||
password: "loser765911"
|
||||
db: 0
|
||||
useCluster: false
|
||||
clusterAddrs:
|
||||
@@ -189,9 +189,9 @@ redis:
|
||||
- 172.21.0.2:7002
|
||||
redis-list:
|
||||
- name: cache
|
||||
addr: 127.0.0.1:6379
|
||||
password: ""
|
||||
db: 0
|
||||
addr: 120.46.165.63:6379
|
||||
password: "loser765911"
|
||||
db: 1
|
||||
useCluster: false
|
||||
clusterAddrs:
|
||||
- 172.21.0.3:7000
|
||||
@@ -213,13 +213,13 @@ sqlite:
|
||||
log-zap: false
|
||||
system:
|
||||
db-type: mysql
|
||||
oss-type: local
|
||||
oss-type: aliyun-oss
|
||||
router-prefix: ""
|
||||
addr: 8888
|
||||
iplimit-count: 15000
|
||||
iplimit-time: 3600
|
||||
use-multipoint: false
|
||||
use-redis: false
|
||||
use-redis: true
|
||||
use-mongo: false
|
||||
use-strict-auth: false
|
||||
tencent-cos:
|
||||
@@ -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,7 +5,9 @@ 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"
|
||||
)
|
||||
|
||||
func bizModel() error {
|
||||
@@ -15,6 +17,8 @@ func bizModel() error {
|
||||
bot.Bot{},
|
||||
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)
|
||||
@@ -29,4 +29,12 @@ func initBizRouter(routers ...*gin.RouterGroup) {
|
||||
userRouter := router.RouterGroupApp.User
|
||||
userRouter.InitUserRouter(privateGroup, publicGroup)
|
||||
}
|
||||
{
|
||||
vipRouter := router.RouterGroupApp.Vip
|
||||
vipRouter.InitVipRouter(privateGroup, publicGroup)
|
||||
}
|
||||
{
|
||||
noticeRouter := router.RouterGroupApp.Notice
|
||||
noticeRouter.InitNoticeRouter(privateGroup, publicGroup) // 占位方法,保证文件可以正确加载,避免go空变量检测报错,请勿删除。
|
||||
}
|
||||
}
|
||||
|
@@ -1,72 +0,0 @@
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"github.com/ArtisanCloud/PowerWeChat/v3/src/kernel"
|
||||
"github.com/ArtisanCloud/PowerWeChat/v3/src/officialAccount"
|
||||
"github.com/ArtisanCloud/PowerWeChat/v3/src/payment"
|
||||
"log"
|
||||
)
|
||||
|
||||
var WeOfficial *officialAccount.OfficialAccount
|
||||
var WechatPay *payment.Payment
|
||||
|
||||
// InitWeOfficial 初始化微信公众号
|
||||
func InitWeOfficial() {
|
||||
OfficialAccountApp, err := officialAccount.NewOfficialAccount(&officialAccount.UserConfig{
|
||||
AppID: "[appid]", // 公众号、小程序的appid
|
||||
Secret: "[app secret]", //
|
||||
|
||||
Log: officialAccount.Log{
|
||||
Level: "debug",
|
||||
// 可以重定向到你的目录下,如果设置File和Error,默认会在当前目录下的wechat文件夹下生成日志
|
||||
File: "/Users/user/wechat/official-account/info.log",
|
||||
Error: "/Users/user/wechat/official-account/error.log",
|
||||
Stdout: false, // 是否打印在终端
|
||||
},
|
||||
|
||||
HttpDebug: true,
|
||||
Debug: false,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Println("初始化微信公众号 SDK失败", err)
|
||||
}
|
||||
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
|
||||
}
|
4
main.go
4
main.go
@@ -4,6 +4,7 @@ import (
|
||||
"git.echol.cn/loser/lckt/core"
|
||||
"git.echol.cn/loser/lckt/global"
|
||||
"git.echol.cn/loser/lckt/initialize"
|
||||
"git.echol.cn/loser/lckt/utils/wechat"
|
||||
_ "go.uber.org/automaxprocs"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
@@ -41,5 +42,8 @@ func main() {
|
||||
db, _ := global.GVA_DB.DB()
|
||||
defer db.Close()
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
@@ -4,6 +4,7 @@ import (
|
||||
"errors"
|
||||
"git.echol.cn/loser/lckt/global"
|
||||
"git.echol.cn/loser/lckt/utils"
|
||||
"git.echol.cn/loser/lckt/utils/user_jwt"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"strconv"
|
||||
"time"
|
||||
@@ -15,7 +16,7 @@ import (
|
||||
func UserJWTAuth() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// 我们这里jwt鉴权取头部信息 x-token 登录时回返回token信息 这里前端需要把token存储到cookie或者本地localStorage中 不过需要跟后端协商过期时间 可以约定刷新令牌或者重新登录
|
||||
token := utils.GetToken(c)
|
||||
token := user_jwt.GetToken(c)
|
||||
if token == "" {
|
||||
response.NoAuth("未登录或非法访问", c)
|
||||
c.Abort()
|
||||
@@ -28,12 +29,12 @@ func UserJWTAuth() gin.HandlerFunc {
|
||||
if err != nil {
|
||||
if errors.Is(err, utils.TokenExpired) {
|
||||
response.NoAuth("授权已过期", c)
|
||||
utils.ClearToken(c)
|
||||
user_jwt.ClearToken(c)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
response.NoAuth(err.Error(), c)
|
||||
utils.ClearToken(c)
|
||||
user_jwt.ClearToken(c)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
@@ -46,7 +47,7 @@ func UserJWTAuth() gin.HandlerFunc {
|
||||
newClaims, _ := j.ParseToken(newToken)
|
||||
c.Header("new-token", newToken)
|
||||
c.Header("new-expires-at", strconv.FormatInt(newClaims.ExpiresAt.Unix(), 10))
|
||||
utils.SetToken(c, newToken, int(dr.Seconds()))
|
||||
user_jwt.SetToken(c, newToken, int(dr.Seconds()))
|
||||
if global.GVA_CONFIG.System.UseMultipoint {
|
||||
// 记录新的活跃jwt
|
||||
_ = jwtService.SetRedisJWT(newToken, newClaims.Username)
|
||||
|
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"
|
||||
}
|
@@ -3,49 +3,82 @@ package request
|
||||
import "git.echol.cn/loser/lckt/model/common/request"
|
||||
|
||||
type SendCodeReq struct {
|
||||
Phone string `json:"phone" vd:"@:len($)>0; msg:'手机号码不能为空'"`
|
||||
Phone string `json:"phone" form:"phone" vd:"@:len($)>0; msg:'手机号码不能为空'"`
|
||||
}
|
||||
|
||||
type CodeLoginReq struct {
|
||||
Phone string `json:"phone" vd:"@:len($)>0; msg:'请输入手机号码'"`
|
||||
Code string `json:"code" vd:"@:len($)>0; msg:'请输入短信验证码'"`
|
||||
Phone string `json:"phone" form:"phone"`
|
||||
Code string `json:"code" form:"code" vd:"@:len($)>0; msg:'请输入短信验证码'"`
|
||||
}
|
||||
|
||||
type InviteLoginReq struct {
|
||||
Phone string `json:"phone" vd:"@:len($)>0; msg:'手机号码不能为空'"`
|
||||
Code string `json:"code" vd:"@:len($)>0; msg:'验证码不能为空'"`
|
||||
Type string `json:"type" vd:"@:len($)>0; msg:'邀请类型不能为空'"`
|
||||
Token string `json:"token" vd:"@:len($)>0; msg:'token不能为空'"`
|
||||
Phone string `json:"phone" form:"phone" vd:"@:len($)>0; msg:'手机号码不能为空'"`
|
||||
Code string `json:"code" form:"code" vd:"@:len($)>0; msg:'验证码不能为空'"`
|
||||
Type string `json:"type" form:"type" vd:"@:len($)>0; msg:'邀请类型不能为空'"`
|
||||
Token string `json:"token" form:"token" vd:"@:len($)>0; msg:'token不能为空'"`
|
||||
}
|
||||
|
||||
type WxLoginReq struct {
|
||||
OpenId string `json:"openId" binding:"required"`
|
||||
NickName string `json:"nickName"`
|
||||
Gender int `json:"gender"`
|
||||
UnionId string `json:"unionId" binding:"required"`
|
||||
AvatarUrl string `json:"avatarUrl"`
|
||||
Province string `json:"province"`
|
||||
City string `json:"city"`
|
||||
OpenId string `json:"openId" form:"openId" binding:"required"`
|
||||
NickName string `json:"nickName" form:"nickName"`
|
||||
Gender int `json:"gender" form:"gender"`
|
||||
UnionId string `json:"unionId" form:"unionId" binding:"required"`
|
||||
AvatarUrl string `json:"avatarUrl" form:"avatarUrl"`
|
||||
Province string `json:"province" form:"province"`
|
||||
City string `json:"city" form:"city"`
|
||||
}
|
||||
|
||||
type BindWechatReq struct {
|
||||
Id int `json:"id" vd:"@:len($)>0; msg:'id不能为空'"`
|
||||
Openid string `json:"openid" vd:"@:len($)>0; msg:'OpenId不能为空'"`
|
||||
UnionId string `json:"unionId" gorm:"type:varchar(64);comment:用户统一标识。针对一个微信开放平台帐号下的应用,同一用户的 unionid 是唯一的。"`
|
||||
Id int `json:"id" form:"id" vd:"@:len($)>0; msg:'id不能为空'"`
|
||||
Openid string `json:"openid" form:"openid" vd:"@:len($)>0; msg:'OpenId不能为空'"`
|
||||
UnionId string `json:"unionId" form:"unionId" gorm:"type:varchar(64);comment:用户统一标识。针对一个微信开放平台帐号下的应用,同一用户的 unionid 是唯一的。"`
|
||||
}
|
||||
|
||||
type BindPhoneReq struct {
|
||||
Id int `json:"id" vd:"@:len($)>0; msg:'用户ID不能为空'"`
|
||||
Phone string `json:"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 {
|
||||
request.PageInfo
|
||||
Type int `json:"type"`
|
||||
UserLabel string `json:"user_label" `
|
||||
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 {
|
||||
Id int `json:"id" vd:"@:len($)>0; msg:'用户ID不能为空'"`
|
||||
Balance float32 `json:"balance" vd:"@:len($)>0; msg:'余额不能为空'"`
|
||||
Id int `json:"id" form:"id" vd:"@:len($)>0; msg:'用户ID不能为空'"`
|
||||
Balance float32 `json:"balance" form:"balance" vd:"@:len($)>0; msg:'余额不能为空'"`
|
||||
}
|
||||
|
||||
type PwdLoginReq 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"`
|
||||
}
|
||||
|
||||
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"`
|
||||
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"`
|
||||
}
|
||||
|
@@ -4,18 +4,21 @@ import "git.echol.cn/loser/lckt/global"
|
||||
|
||||
type User struct {
|
||||
global.GVA_MODEL
|
||||
NickName string `json:"nick_name" gorm:"comment:用户昵称"`
|
||||
Phone string `json:"phone" gorm:"comment:用户手机号"`
|
||||
UnionId string `json:"union_id" gorm:"type:varchar(255) comment '微信UnionId'"`
|
||||
NickName string `json:"nick_name" form:"nick_name" gorm:"comment:用户昵称"`
|
||||
Phone string `json:"phone" form:"phone" gorm:"comment:用户手机号"`
|
||||
UnionId string `json:"union_id" form:"union_id" gorm:"type:varchar(255) comment '微信UnionId'"`
|
||||
OpenId string `gorm:"column:open_id;default:'';comment:'openId'" json:"-"`
|
||||
Avatar string `json:"avatar" gorm:"comment:用户头像"`
|
||||
Status int8 `gorm:"column:status;default:1;NOT NULL;comment:'用户状态 0 封禁 1 正常'" json:"status"`
|
||||
Password string `json:"password" form:"password" gorm:"comment:用户密码"`
|
||||
Avatar string `json:"avatar" form:"avatar" gorm:"comment:用户头像"`
|
||||
Status int8 `gorm:"column:status;default:1;NOT NULL;comment:'用户状态 0 封禁 1 正常'" json:"status" form:"status"`
|
||||
//邀请码
|
||||
InviteCode *string `json:"invite_code" gorm:"type:varchar(255) comment '用户专属邀请码'"`
|
||||
Balance float32 `json:"balance" gorm:"type:decimal(10,2);comment:学员余额"`
|
||||
CommenderId int `json:"commender_id" gorm:"comment:推荐人ID"`
|
||||
UserLabel string `json:"user_label" gorm:"type:varchar(255) comment '用户标签 普通/VIP/SVIP'"`
|
||||
UserType int8 `gorm:"column:user_type;default:1;NOT NULL;comment:'用户类型 1 用户 2 讲师'" json:"user_type"`
|
||||
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"`
|
||||
UserLabel int64 `json:"user_label" form:"user_label" gorm:"comment:用户标签 1 普通用户 2 Vip 3 Svip"`
|
||||
UserType int8 `gorm:"column:user_type;default:1;NOT NULL;comment:'用户类型 1 用户 2 讲师'" json:"user_type" form:"user_type" `
|
||||
IsVip int8 `gorm:"column:is_vip;default:0;NOT NULL;comment:'是否是VIP 0 否 1 是'" json:"is_vip" form:"is_vip"`
|
||||
VipExpireTime string `json:"vip_expire_time" form:"vip_expire_time" gorm:"comment:VIP过期时间"`
|
||||
}
|
||||
|
||||
func (User) TableName() string {
|
||||
|
16
model/vip/vip.go
Normal file
16
model/vip/vip.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package vip
|
||||
|
||||
import "git.echol.cn/loser/lckt/global"
|
||||
|
||||
type Vip struct {
|
||||
global.GVA_MODEL
|
||||
Name string `json:"name" form:"name" gorm:"comment:会员名称"` // 会员名称
|
||||
Level int `json:"level" form:"level" gorm:"comment:会员等级 1 Vip 2 Svip"` // 会员等级
|
||||
Price float64 `json:"price" form:"price" gorm:"comment:会员价格"` // 会员价格
|
||||
Expiration int64 `json:"expiration" form:"expiration" gorm:"comment:会员有效期"` // 会员过期时间
|
||||
}
|
||||
|
||||
// TableName 设置表名
|
||||
func (Vip) TableName() string {
|
||||
return "lckt_vip"
|
||||
}
|
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,12 +1,15 @@
|
||||
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"
|
||||
)
|
||||
|
||||
var RouterGroupApp = new(RouterGroup)
|
||||
@@ -15,7 +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) // 通知开放接口
|
||||
}
|
||||
}
|
@@ -10,14 +10,14 @@ type UserRouter struct{}
|
||||
// InitUserRouter 初始化 用户相关 路由信息
|
||||
func (s *UserRouter) InitUserRouter(Router *gin.RouterGroup, PublicRouter *gin.RouterGroup) {
|
||||
userRouter := Router.Group("app_user").Use(middleware.OperationRecord())
|
||||
userRouterWithoutAuth := PublicRouter.Group("app_user")
|
||||
{
|
||||
userRouter.GET("list", userApi.GetUserList) // 获取用户列表
|
||||
userRouter.PUT("setBalance", userApi.SetBalance) // 更新用户余额
|
||||
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) // 更新教师申请状态
|
||||
}
|
||||
{
|
||||
userRouterWithoutAuth.DELETE("login", userApi.Login) // 短信验证码登录
|
||||
userRouterWithoutAuth.POST("sms", userApi.SendCode) // 发送短信验证码
|
||||
}
|
||||
|
||||
}
|
||||
|
7
router/vip/enter.go
Normal file
7
router/vip/enter.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package vip
|
||||
|
||||
import api "git.echol.cn/loser/lckt/api/v1"
|
||||
|
||||
type RouterGroup struct{ VipRouter }
|
||||
|
||||
var vipApi = api.ApiGroupApp.VipApiGroup
|
23
router/vip/vip.go
Normal file
23
router/vip/vip.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package vip
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/lckt/middleware"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type VipRouter struct{}
|
||||
|
||||
// InitVipRouter 初始化会员路由
|
||||
func (s *VipRouter) InitVipRouter(Router *gin.RouterGroup, PublicRouter *gin.RouterGroup) {
|
||||
vipRouter := Router.Group("vip").Use(middleware.OperationRecord())
|
||||
vipNoAuthRouter := PublicRouter.Group("vip").Use(middleware.OperationRecord())
|
||||
{
|
||||
vipRouter.POST("", vipApi.Create) // 获取用户列表
|
||||
vipRouter.DELETE("", vipApi.Delete) // 更新用户余额
|
||||
vipRouter.PUT("", vipApi.Update) // 注册
|
||||
}
|
||||
{
|
||||
vipNoAuthRouter.GET(":id", vipApi.GetVipById) // 根据获取vip信息
|
||||
vipNoAuthRouter.GET("list", vipApi.GetVipList) // 获取会员列表
|
||||
}
|
||||
}
|
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,12 +1,15 @@
|
||||
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"
|
||||
)
|
||||
|
||||
var ServiceGroupApp = new(ServiceGroup)
|
||||
@@ -14,8 +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,18 +4,26 @@ 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"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
type UserService struct{}
|
||||
|
||||
func (u *UserService) GetUserById(id int) (user user.User, err error) {
|
||||
|
||||
func (u *UserService) GetUserById(id string) (user user.User, err error) {
|
||||
err = global.GVA_DB.Where("id = ?", id).First(&user).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询用户失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -29,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
|
||||
}
|
||||
@@ -48,38 +57,8 @@ 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,
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
func (u *UserService) GetUserList(p request.GetUserListReq) (userList user.User, total int64, err error) {
|
||||
// GetUserList 获取用户列表
|
||||
func (u *UserService) GetUserList(p request.GetUserListReq) (userList []user.User, total int64, err error) {
|
||||
limit := p.PageSize
|
||||
offset := p.PageSize * (p.Page - 1)
|
||||
// 创建db
|
||||
@@ -91,6 +70,13 @@ func (u *UserService) GetUserList(p request.GetUserListReq) (userList user.User,
|
||||
if p.UserLabel != "" {
|
||||
db = db.Where("user_label = ?", p.UserLabel)
|
||||
}
|
||||
if p.Status != 0 {
|
||||
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 {
|
||||
@@ -105,6 +91,7 @@ func (u *UserService) GetUserList(p request.GetUserListReq) (userList user.User,
|
||||
|
||||
}
|
||||
|
||||
// SetBalance 设置用户余额
|
||||
func (u *UserService) SetBalance(p request.SetBalanceReq) (err error) {
|
||||
err = global.GVA_DB.Model(&user.User{}).Where("id = ?", p.Id).Update("balance", p.Balance).Error
|
||||
if err != nil {
|
||||
@@ -113,3 +100,131 @@ func (u *UserService) SetBalance(p request.SetBalanceReq) (err error) {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Register 用户注册-后台用
|
||||
func (u *UserService) Register(req request.RegisterReq) (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
|
||||
}
|
||||
if count == 0 {
|
||||
var password []byte
|
||||
password, err = bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("密码加密失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
user := user.User{
|
||||
Phone: req.Phone,
|
||||
Password: string(password),
|
||||
NickName: req.NickName,
|
||||
UserLabel: req.UserLabel,
|
||||
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))
|
||||
return
|
||||
}
|
||||
return
|
||||
} else {
|
||||
err = fmt.Errorf("用户已存在")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SetUserStatus 设置用户状态
|
||||
func (u *UserService) SetUserStatus(id string) (err error) {
|
||||
user := user.User{}
|
||||
|
||||
err = global.GVA_DB.Model(&user).Where("id = ?", id).First(&user).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询用户状态失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
if user.Status == 1 {
|
||||
err = global.GVA_DB.Model(&user).Where("id = ?", id).Update("status", 0).Error
|
||||
} else {
|
||||
err = global.GVA_DB.Model(&user).Where("id = ?", id).Update("status", 1).Error
|
||||
}
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("设置用户状态失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
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
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user