Compare commits

...

25 Commits

Author SHA1 Message Date
9f220dc025 🎨 添加微信支付证书 2025-07-25 23:03:11 +08:00
7951f77f47 🎨 优化文章相关接口 2025-07-25 23:02:57 +08:00
bb2a68fb61 🎨 完善订单和微信支付功能 2025-07-25 23:02:43 +08:00
8fd1968cf6 🎨 优化微信登录流程 2025-07-23 02:54:43 +08:00
666527b976 🎨 修改临时数据库,新增跨域中间件 2025-07-23 02:38:53 +08:00
502a9b82d7 🎨 新增短信宝发送平台接口 2025-07-23 02:35:04 +08:00
7a729211d1 🎨 优化微信登录流程&新增手机和微信绑定接口 2025-07-23 02:33:39 +08:00
f0a67822ef 🎨 完善用户讲师申请接口,新增order模块 2025-07-20 21:52:11 +08:00
70f65c96bd 🎨 新增讲师相关接口(待完善) 2025-07-20 03:07:42 +08:00
48ddb72cf2 🎨 完善注册逻辑 2025-07-20 02:11:35 +08:00
73e97ba151 🎨 完善注册逻辑 2025-07-20 02:11:27 +08:00
c3d5d532cd 🎨 完善注册逻辑 2025-07-20 02:11:19 +08:00
f86b56a79d 🎨 新增随机邀请码工具 2025-07-20 02:10:55 +08:00
fa0234d385 🎨 优化路由跨域,修改微信商户配置 2025-07-19 06:19:09 +08:00
8106282c8b 🎨 新增订单模块 2025-07-19 06:18:44 +08:00
aac13d369c 🎨 优化文章相关接口,新增app专用接口 2025-07-19 06:18:33 +08:00
cf0f60d221 🎨 优化用户和分类相关接口,新增banner接口 2025-06-23 17:04:44 +08:00
a5ae680f94 🎨 添加图库和客服插件(有问题-待修改) 2025-05-14 16:51:31 +08:00
5faff4afa0 🎨 优化部分用户管理接口 2025-05-10 08:42:01 +08:00
e074395859 🎨 新增公告通知模块 2025-05-10 03:34:25 +08:00
bf220076dd 🎨 新增vip模块 2025-05-09 11:49:44 +08:00
7903175c91 🎨 新增redis配置 2025-05-09 11:17:17 +08:00
ad6773c575 🎨 完善微信登录相关工具类 2025-05-09 11:17:04 +08:00
159ef59749 🎨 完善用户相关接口 2025-05-09 11:16:38 +08:00
ed3c15fbb6 🎨 修改鉴权相关中间件 2025-05-09 11:16:18 +08:00
109 changed files with 5558 additions and 385 deletions

96
api/v1/app/banner.go Normal file
View 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
View 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
View 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
View 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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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
View 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
View 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(&not)
if err != nil {
response.FailWithMessage(err.Error(), c)
return
}
err = notService.CreateNotice(ctx, &not)
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(&not)
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)
}

View File

@@ -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

View File

@@ -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
View 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
View 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)
}

View File

@@ -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
View File

@@ -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
View File

@@ -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=

View File

@@ -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))

View File

@@ -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

View File

@@ -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")

View File

@@ -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空变量检测报错请勿删除。
}
}

View File

@@ -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
}

View File

@@ -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()
}

View File

@@ -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()
}
}

View File

@@ -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
View 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
View 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"
}

View 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:'订单编号参数不能为空'"`
}

View 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
View 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过期时间
}

View File

@@ -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 文章表

View File

@@ -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 {

View File

@@ -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-是
}

View File

@@ -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
View 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"
}

View 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"`
}

View File

@@ -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"
}

View File

@@ -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"`
}

View File

@@ -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
View 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"
}

View 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

View 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)
}

View File

@@ -0,0 +1,8 @@
package api
type ApiGroup struct {
CustomerServiceApi
AdminServiceApi
}
var ApiGroupApp = new(ApiGroup)

View 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)
}

View File

@@ -0,0 +1 @@
package config

View File

@@ -0,0 +1 @@
package global

View 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 ""
}

View 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()
}
}
}

View 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"`
}

View 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"
}

View 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"
}

View 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:是否游客01是;"`
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"
}

View 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"
}

View 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"
}

View 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"
}

View File

@@ -0,0 +1,7 @@
package router
type RouterGroup struct {
CustomerServiceRouter
}
var RouterGroupApp = new(RouterGroup)

View 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)
}

View File

@@ -0,0 +1,7 @@
package service
type ServiceGroup struct {
CustomerServiceService
}
var ServiceGroupApp = new(ServiceGroup)

View 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
}

View 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
}
}
}
}

View 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
}

View 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
}

View 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")
}
}

View 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

View 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)
}

View 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

View File

@@ -0,0 +1 @@
package config

View File

@@ -0,0 +1 @@
package global

View 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"
}

View 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"`
}

View 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"
}

View 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"
}

View File

@@ -0,0 +1,7 @@
package router
type RouterGroup struct {
PictureLibraryRouter
}
var RouterGroupApp = new(RouterGroup)

View 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)
}
}

View File

@@ -0,0 +1,7 @@
package service
type ServiceGroup struct {
PictureLibraryService
}
var ServiceGroupApp = new(ServiceGroup)

View 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 + "/"
}

Binary file not shown.

View 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-----

View 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
View 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
View 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
View 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
View 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) // 注册
}
}

View File

@@ -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) // 文章开放接口
}
}

View File

@@ -24,5 +24,6 @@ func (s *CategoryRouter) InitCategoryRouter(Router *gin.RouterGroup, PublicRoute
}
{
catRouterWithoutAuth.GET("getCategoryPublic", catApi.GetCategoryPublic) // 类别开放接口
catRouterWithoutAuth.GET("/index", catApi.GetCategoryListPublic) // 获取类别列表公开接口
}
}

View File

@@ -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
View 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
View 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) // 通知开放接口
}
}

View File

@@ -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
View 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
View 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
View 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
View File

@@ -0,0 +1,7 @@
package app
type ServiceGroup struct {
AppUserService
BannerService
OrderService
}

160
service/app/order.go Normal file
View 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
View 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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
View File

@@ -0,0 +1,3 @@
package notice
type ServiceGroup struct{ NoticeService }

84
service/notice/notice.go Normal file
View 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(&notice.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(&notice.Notice{}).Where("id = ?", not.ID).Updates(&not).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(&not).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(&notice.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(&nots).Error
return nots, total, err
}
func (notService *NoticeService) GetNoticePublic(ctx context.Context) {
// 此方法为获取数据源定义的数据
// 请自行实现
}

View File

@@ -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