Compare commits
15 Commits
c7ed1b6417
...
a9b6816064
Author | SHA1 | Date | |
---|---|---|---|
a9b6816064 | |||
f9b37fb1aa | |||
57289a24e7 | |||
2293ee2463 | |||
b8b859f890 | |||
c588e9efe7 | |||
a30ab925d1 | |||
2bc3eb26fd | |||
f5948a366b | |||
81a266afc7 | |||
a65266d033 | |||
df46c7ab29 | |||
7bcc2370bd | |||
90bd42d488 | |||
45a95be1d8 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -21,4 +21,5 @@
|
||||
log
|
||||
# Go workspace file
|
||||
go.work
|
||||
.kiro
|
||||
|
||||
|
218
README.md
218
README.md
@@ -1,54 +1,174 @@
|
||||
## server项目结构
|
||||
# LCKT 后端服务
|
||||
|
||||
基于 Gin-Vue-Admin 框架开发的全栈开发基础平台后端服务,提供完整的用户管理、权限控制、内容管理等功能。
|
||||
|
||||
## 🚀 项目特性
|
||||
|
||||
- **现代化技术栈**: 基于 Go 1.23 + Gin + GORM + Redis + MySQL
|
||||
- **完整的权限系统**: 基于 Casbin 的 RBAC 权限控制
|
||||
- **丰富的业务模块**: 用户管理、订单系统、内容管理、通知系统等
|
||||
- **多数据库支持**: MySQL、PostgreSQL、SQLite、SQL Server、Oracle
|
||||
- **多种存储方案**: 阿里云OSS、腾讯云COS、七牛云、MinIO等
|
||||
- **微信生态集成**: 微信支付、微信公众号
|
||||
- **API文档**: 集成 Swagger 自动生成API文档
|
||||
- **容器化部署**: 支持 Docker 部署
|
||||
|
||||
## 📋 功能模块
|
||||
|
||||
### 系统管理
|
||||
- 用户管理 (User Management)
|
||||
- 角色权限 (Role & Permission)
|
||||
- 菜单管理 (Menu Management)
|
||||
- API管理 (API Management)
|
||||
- 操作日志 (Operation Log)
|
||||
- 字典管理 (Dictionary)
|
||||
|
||||
### 业务功能
|
||||
- 用户系统 (App User System)
|
||||
- 订单管理 (Order Management)
|
||||
- 兑换码系统 (Redeem Code)
|
||||
- 文章管理 (Article Management)
|
||||
- 分类管理 (Category Management)
|
||||
- 横幅管理 (Banner Management)
|
||||
- 通知系统 (Notice System)
|
||||
- VIP会员 (VIP System)
|
||||
- 机器人管理 (Bot Management)
|
||||
|
||||
### 工具功能
|
||||
- 文件上传下载 (File Upload/Download)
|
||||
- 代码生成器 (Code Generator)
|
||||
- 表单生成器 (Form Builder)
|
||||
- Excel导入导出 (Excel Import/Export)
|
||||
- 验证码 (Captcha)
|
||||
## 🏗️ 项
|
||||
目结构
|
||||
|
||||
```shell
|
||||
├── api
|
||||
│ └── v1
|
||||
├── config
|
||||
├── core
|
||||
├── docs
|
||||
├── global
|
||||
├── initialize
|
||||
│ └── internal
|
||||
├── middleware
|
||||
├── model
|
||||
│ ├── request
|
||||
│ └── response
|
||||
├── packfile
|
||||
├── resource
|
||||
│ ├── excel
|
||||
│ ├── page
|
||||
│ └── template
|
||||
├── router
|
||||
├── service
|
||||
├── source
|
||||
└── utils
|
||||
├── timer
|
||||
└── upload
|
||||
├── api # API接口层
|
||||
│ └── v1 # v1版本API
|
||||
├── config # 配置文件结构体
|
||||
├── core # 核心组件初始化
|
||||
├── docs # Swagger文档
|
||||
├── global # 全局变量
|
||||
├── initialize # 系统初始化
|
||||
├── middleware # 中间件
|
||||
├── model # 数据模型
|
||||
│ ├── request # 请求结构体
|
||||
│ └── response # 响应结构体
|
||||
├── resource # 静态资源
|
||||
│ ├── excel # Excel文件
|
||||
│ ├── page # 页面模板
|
||||
│ └── template # 代码模板
|
||||
├── router # 路由
|
||||
├── service # 业务逻辑层
|
||||
├── source # 初始化数据
|
||||
├── task # 定时任务
|
||||
└── utils # 工具函数
|
||||
├── timer # 定时器
|
||||
└── upload # 文件上传
|
||||
```
|
||||
|
||||
| 文件夹 | 说明 | 描述 |
|
||||
| ------------ | ----------------------- | --------------------------- |
|
||||
| `api` | api层 | api层 |
|
||||
| `--v1` | v1版本接口 | v1版本接口 |
|
||||
| `config` | 配置包 | config.yaml对应的配置结构体 |
|
||||
| `core` | 核心文件 | 核心组件(zap, viper, server)的初始化 |
|
||||
| `docs` | swagger文档目录 | swagger文档目录 |
|
||||
| `global` | 全局对象 | 全局对象 |
|
||||
| `initialize` | 初始化 | router,redis,gorm,validator, timer的初始化 |
|
||||
| `--internal` | 初始化内部函数 | gorm 的 longger 自定义,在此文件夹的函数只能由 `initialize` 层进行调用 |
|
||||
| `middleware` | 中间件层 | 用于存放 `gin` 中间件代码 |
|
||||
| `model` | 模型层 | 模型对应数据表 |
|
||||
| `--request` | 入参结构体 | 接收前端发送到后端的数据。 |
|
||||
| `--response` | 出参结构体 | 返回给前端的数据结构体 |
|
||||
| `packfile` | 静态文件打包 | 静态文件打包 |
|
||||
| `resource` | 静态资源文件夹 | 负责存放静态文件 |
|
||||
| `--excel` | excel导入导出默认路径 | excel导入导出默认路径 |
|
||||
| `--page` | 表单生成器 | 表单生成器 打包后的dist |
|
||||
| `--template` | 模板 | 模板文件夹,存放的是代码生成器的模板 |
|
||||
| `router` | 路由层 | 路由层 |
|
||||
| `service` | service层 | 存放业务逻辑问题 |
|
||||
| `source` | source层 | 存放初始化数据的函数 |
|
||||
| `utils` | 工具包 | 工具函数封装 |
|
||||
| `--timer` | timer | 定时器接口封装 |
|
||||
| `--upload` | oss | oss接口封装 |
|
||||
## 🛠️ 技术栈
|
||||
|
||||
### 后端框架
|
||||
- **Gin**: 高性能的Go Web框架
|
||||
- **GORM**: Go语言ORM库
|
||||
- **Casbin**: 权限管理库
|
||||
- **Viper**: 配置管理
|
||||
- **Zap**: 高性能日志库
|
||||
- **JWT**: 身份认证
|
||||
- **Redis**: 缓存和会话存储
|
||||
|
||||
### 数据库支持
|
||||
- MySQL
|
||||
- PostgreSQL
|
||||
- SQLite
|
||||
- SQL Server
|
||||
- Oracle
|
||||
- MongoDB
|
||||
|
||||
### 对象存储
|
||||
- 阿里云OSS
|
||||
- 腾讯云COS
|
||||
- 七牛云
|
||||
- MinIO
|
||||
- AWS S3
|
||||
- Cloudflare R2
|
||||
- 华为云OBS
|
||||
|
||||
### 第三方集成
|
||||
- 微信支付
|
||||
- 微信公众号
|
||||
- 阿里云短信
|
||||
- 邮件发送#
|
||||
# 🚀 快速开始
|
||||
|
||||
### 环境要求
|
||||
- Go 1.23+
|
||||
- MySQL 5.7+
|
||||
- Redis 5.0+
|
||||
|
||||
### 安装依赖
|
||||
```bash
|
||||
go mod tidy
|
||||
```
|
||||
|
||||
### 配置文件
|
||||
复制配置文件并修改相关配置:
|
||||
```bash
|
||||
cp config.yaml.example config.yaml
|
||||
```
|
||||
|
||||
主要配置项:
|
||||
- 数据库连接信息
|
||||
- Redis连接信息
|
||||
- JWT密钥
|
||||
- 对象存储配置
|
||||
- 微信相关配置
|
||||
|
||||
### 运行项目
|
||||
```bash
|
||||
# 开发环境
|
||||
go run main.go
|
||||
|
||||
# 生产环境
|
||||
go build -o server main.go
|
||||
./server
|
||||
```
|
||||
|
||||
### Docker 部署
|
||||
```bash
|
||||
# 构建镜像
|
||||
docker build -t lckt-server .
|
||||
|
||||
# 运行容器
|
||||
docker run -d -p 8888:8888 --name lckt-server lckt-server
|
||||
```
|
||||
|
||||
## 📖 API 文档
|
||||
|
||||
项目集成了 Swagger 自动生成API文档,启动项目后访问:
|
||||
```
|
||||
http://localhost:8888/swagger/index.html
|
||||
```
|
||||
|
||||
## 🔧 开发指南
|
||||
|
||||
### 代码生成
|
||||
项目内置代码生成器,可以快速生成CRUD代码:
|
||||
1. 配置数据表结构
|
||||
2. 使用代码生成器生成相关文件
|
||||
3. 根据业务需求调整生成的代码
|
||||
|
||||
### 权限控制
|
||||
基于 Casbin 实现的 RBAC 权限控制:
|
||||
- 用户 (User)
|
||||
- 角色 (Role)
|
||||
- 权限 (Permission)
|
||||
- 菜单 (Menu)
|
||||
|
||||
### 中间件
|
||||
- JWT认证中间件
|
||||
- 跨域处理中间件
|
||||
- 操作日志中间件
|
||||
- 限流中间件
|
@@ -1,9 +1,11 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/lckt/global"
|
||||
"git.echol.cn/loser/lckt/model/app"
|
||||
common "git.echol.cn/loser/lckt/model/common/request"
|
||||
"git.echol.cn/loser/lckt/model/common/response"
|
||||
"git.echol.cn/loser/lckt/task"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
@@ -94,3 +96,13 @@ func (b *BannerApi) GetIndexBanners(context *gin.Context) {
|
||||
}
|
||||
response.OkWithData(list, context)
|
||||
}
|
||||
|
||||
func (b *BannerApi) GetVIPBanners(context *gin.Context) {
|
||||
err := task.CheckVip(global.GVA_DB)
|
||||
if err != nil {
|
||||
response.FailWithMessage("获取VIPBanner失败: "+err.Error(), context)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithMessage("检测vip成功", context)
|
||||
}
|
||||
|
105
api/v1/app/domain.go
Normal file
105
api/v1/app/domain.go
Normal file
@@ -0,0 +1,105 @@
|
||||
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"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type DomainApi struct{}
|
||||
|
||||
// Create 创建域名
|
||||
func (d *DomainApi) Create(ctx *gin.Context) {
|
||||
var p app.Domain
|
||||
if err := ctx.ShouldBind(&p); err != nil {
|
||||
global.GVA_LOG.Error("新建域名参数有误", zap.Error(err))
|
||||
r.FailWithMessage("参数有误", ctx)
|
||||
return
|
||||
}
|
||||
|
||||
err := domainService.Create(p)
|
||||
if err != nil {
|
||||
r.FailWithMessage("创建域名失败", ctx)
|
||||
return
|
||||
}
|
||||
|
||||
r.OkWithMessage("创建域名成功", ctx)
|
||||
|
||||
}
|
||||
|
||||
// GetList 获取域名列表
|
||||
func (d *DomainApi) GetList(ctx *gin.Context) {
|
||||
var p request.GetDomainList
|
||||
if err := ctx.ShouldBind(&p); err != nil {
|
||||
global.GVA_LOG.Error("获取域名列表参数有误", zap.Error(err))
|
||||
r.FailWithMessage("参数有误", ctx)
|
||||
return
|
||||
}
|
||||
|
||||
list, total, err := domainService.GetList(p)
|
||||
if err != nil {
|
||||
r.FailWithMessage("获取域名列表失败", ctx)
|
||||
return
|
||||
}
|
||||
|
||||
r.OkWithDetailed(r.PageResult{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: p.Page,
|
||||
PageSize: p.PageSize,
|
||||
}, "获取域名列表成功", ctx)
|
||||
}
|
||||
|
||||
// Update 更新域名
|
||||
func (d *DomainApi) Update(ctx *gin.Context) {
|
||||
var p app.Domain
|
||||
if err := ctx.ShouldBind(&p); err != nil {
|
||||
global.GVA_LOG.Error("更新域名参数有误", zap.Error(err))
|
||||
r.FailWithMessage("参数有误", ctx)
|
||||
return
|
||||
}
|
||||
|
||||
err := domainService.Update(p)
|
||||
if err != nil {
|
||||
r.FailWithMessage("更新域名失败", ctx)
|
||||
return
|
||||
}
|
||||
r.OkWithMessage("更新域名成功", ctx)
|
||||
}
|
||||
|
||||
// Delete 删除域名
|
||||
func (d *DomainApi) Delete(ctx *gin.Context) {
|
||||
var p app.Domain
|
||||
if err := ctx.ShouldBind(&p); err != nil {
|
||||
global.GVA_LOG.Error("删除域名参数有误", zap.Error(err))
|
||||
r.FailWithMessage("参数有误", ctx)
|
||||
return
|
||||
}
|
||||
|
||||
err := domainService.Delete(p.ID)
|
||||
if err != nil {
|
||||
r.FailWithMessage("删除域名失败", ctx)
|
||||
return
|
||||
}
|
||||
r.OkWithMessage("删除域名成功", ctx)
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取域名
|
||||
func (d *DomainApi) GetByID(ctx *gin.Context) {
|
||||
id := ctx.Param("id")
|
||||
if id == "" {
|
||||
global.GVA_LOG.Error("获取域名详情参数错误: ID不能为空")
|
||||
r.FailWithMessage("获取域名详情参数错误: ID不能为空", ctx)
|
||||
return
|
||||
}
|
||||
|
||||
domain, err := domainService.GetByID(id)
|
||||
if err != nil {
|
||||
r.FailWithMessage("获取域名失败", ctx)
|
||||
return
|
||||
}
|
||||
r.OkWithDetailed(domain, "获取域名成功", ctx)
|
||||
}
|
@@ -6,9 +6,17 @@ type ApiGroup struct {
|
||||
AppUserApi
|
||||
BannerApi
|
||||
OrderApi
|
||||
TeacherVip
|
||||
RedeemCodeApi
|
||||
WithApi
|
||||
DomainApi
|
||||
}
|
||||
|
||||
var userService = service.ServiceGroupApp.UserServiceGroup.UserService
|
||||
var appUserService = service.ServiceGroupApp.AppServiceGroup.AppUserService
|
||||
var bannerService = service.ServiceGroupApp.AppServiceGroup.BannerService
|
||||
var orderService = service.ServiceGroupApp.AppServiceGroup.OrderService
|
||||
var teacherVipService = service.ServiceGroupApp.AppServiceGroup.TeacherVipService
|
||||
var redeemCodeService = service.ServiceGroupApp.AppServiceGroup.RedeemCodeService
|
||||
var withService = service.ServiceGroupApp.AppServiceGroup.WithService
|
||||
var domainService = service.ServiceGroupApp.AppServiceGroup.DomainService
|
||||
|
188
api/v1/app/redeem_code.go
Normal file
188
api/v1/app/redeem_code.go
Normal file
@@ -0,0 +1,188 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/lckt/global"
|
||||
"git.echol.cn/loser/lckt/model/app"
|
||||
"git.echol.cn/loser/lckt/model/app/request"
|
||||
common "git.echol.cn/loser/lckt/model/common/request"
|
||||
r "git.echol.cn/loser/lckt/model/common/response"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type RedeemCodeApi struct{}
|
||||
|
||||
// Create 创建兑换码库
|
||||
func (rc *RedeemCodeApi) Create(ctx *gin.Context) {
|
||||
var p app.RedeemCode
|
||||
if err := ctx.ShouldBind(&p); err != nil {
|
||||
r.FailWithMessage("创建兑换码库失败:"+err.Error(), ctx)
|
||||
global.GVA_LOG.Error("创建兑换码库失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
err := redeemCodeService.Create(p)
|
||||
if err != nil {
|
||||
r.FailWithMessage("创建兑换码库失败:"+err.Error(), ctx)
|
||||
global.GVA_LOG.Error("创建兑换码库失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
r.OkWithMessage("创建兑换码库成功", ctx)
|
||||
}
|
||||
|
||||
// Delete 删除兑换码库
|
||||
func (rc *RedeemCodeApi) Delete(ctx *gin.Context) {
|
||||
var p app.RedeemCode
|
||||
if err := ctx.ShouldBind(&p); err != nil {
|
||||
r.FailWithMessage("删除兑换码库失败:"+err.Error(), ctx)
|
||||
global.GVA_LOG.Error("删除兑换码库失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
err := redeemCodeService.Delete(p)
|
||||
if err != nil {
|
||||
r.FailWithMessage("删除兑换码库失败:"+err.Error(), ctx)
|
||||
global.GVA_LOG.Error("删除兑换码库失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
r.OkWithMessage("删除兑换码库成功", ctx)
|
||||
}
|
||||
|
||||
// Update 更新兑换码库
|
||||
func (rc *RedeemCodeApi) Update(ctx *gin.Context) {
|
||||
var p app.RedeemCode
|
||||
if err := ctx.ShouldBind(&p); err != nil {
|
||||
r.FailWithMessage("更新兑换码库失败:"+err.Error(), ctx)
|
||||
global.GVA_LOG.Error("更新兑换码库失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
err := redeemCodeService.Update(p)
|
||||
if err != nil {
|
||||
r.FailWithMessage("更新兑换码库失败:"+err.Error(), ctx)
|
||||
global.GVA_LOG.Error("更新兑换码库失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
r.OkWithMessage("更新兑换码库成功", ctx)
|
||||
}
|
||||
|
||||
// GetList 获取兑换码库列表
|
||||
func (rc *RedeemCodeApi) GetList(ctx *gin.Context) {
|
||||
var p common.PageInfo
|
||||
if err := ctx.ShouldBind(&p); err != nil {
|
||||
r.FailWithMessage("获取兑换码库列表失败:"+err.Error(), ctx)
|
||||
global.GVA_LOG.Error("获取兑换码库列表失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
list, total, err := redeemCodeService.GetList(p)
|
||||
if err != nil {
|
||||
r.FailWithMessage("获取兑换码库列表失败:"+err.Error(), ctx)
|
||||
global.GVA_LOG.Error("获取兑换码库列表失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
r.OkWithDetailed(r.PageResult{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: p.Page,
|
||||
PageSize: p.PageSize,
|
||||
}, "获取兑换码库列表成功", ctx)
|
||||
}
|
||||
|
||||
// GetById 根据ID获取兑换码库
|
||||
func (rc *RedeemCodeApi) GetById(ctx *gin.Context) {
|
||||
var p common.GetById
|
||||
if err := ctx.ShouldBind(&p); err != nil {
|
||||
r.FailWithMessage("获取兑换码库失败:"+err.Error(), ctx)
|
||||
global.GVA_LOG.Error("获取兑换码库失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
redeem, err := redeemCodeService.GetById(p.ID)
|
||||
if err != nil {
|
||||
r.FailWithMessage("获取兑换码库失败:"+err.Error(), ctx)
|
||||
global.GVA_LOG.Error("获取兑换码库失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
r.OkWithDetailed(redeem, "获取兑换码库成功", ctx)
|
||||
}
|
||||
|
||||
// ========================CDK相关========================
|
||||
|
||||
// CreateCDK 生成兑换码
|
||||
func (rc *RedeemCodeApi) CreateCDK(ctx *gin.Context) {
|
||||
var p request.GenerateCDK
|
||||
if err := ctx.ShouldBind(&p); err != nil {
|
||||
r.FailWithMessage("生成兑换码失败:"+err.Error(), ctx)
|
||||
global.GVA_LOG.Error("生成兑换码失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
cdkVo, err := redeemCodeService.CreateCDK(p)
|
||||
if err != nil {
|
||||
r.FailWithMessage("生成兑换码失败:"+err.Error(), ctx)
|
||||
global.GVA_LOG.Error("生成兑换码失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
r.OkWithDetailed(cdkVo, "生成兑换码成功", ctx)
|
||||
}
|
||||
|
||||
// GetCDKList 获取兑换码列表
|
||||
func (rc *RedeemCodeApi) GetCDKList(ctx *gin.Context) {
|
||||
var p request.GetCDKList
|
||||
if err := ctx.ShouldBind(&p); err != nil {
|
||||
r.FailWithMessage("获取兑换码列表失败:"+err.Error(), ctx)
|
||||
global.GVA_LOG.Error("获取兑换码列表失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
list, total, err := redeemCodeService.GetCDKList(p)
|
||||
if err != nil {
|
||||
r.FailWithMessage("获取兑换码列表失败:"+err.Error(), ctx)
|
||||
global.GVA_LOG.Error("获取兑换码列表失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
r.OkWithDetailed(r.PageResult{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: p.Page,
|
||||
PageSize: p.PageSize,
|
||||
}, "获取兑换码列表成功", ctx)
|
||||
}
|
||||
|
||||
// DeleteCDK 删除兑换码
|
||||
func (rc *RedeemCodeApi) DeleteCDK(ctx *gin.Context) {
|
||||
var p app.CDK
|
||||
if err := ctx.ShouldBind(&p); err != nil {
|
||||
r.FailWithMessage("删除兑换码失败:"+err.Error(), ctx)
|
||||
global.GVA_LOG.Error("删除兑换码失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
err := redeemCodeService.DeleteCDK(p)
|
||||
if err != nil {
|
||||
r.FailWithMessage("删除兑换码失败:"+err.Error(), ctx)
|
||||
global.GVA_LOG.Error("删除兑换码失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
r.OkWithMessage("删除兑换码成功", ctx)
|
||||
}
|
||||
|
||||
func (rc *RedeemCodeApi) Redeem(context *gin.Context) {
|
||||
var p request.RedeemCDK
|
||||
if err := context.ShouldBind(&p); err != nil {
|
||||
r.FailWithMessage("兑换失败:"+err.Error(), context)
|
||||
global.GVA_LOG.Error("兑换失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
err := redeemCodeService.Redeem(p, context)
|
||||
if err != nil {
|
||||
r.FailWithMessage(err.Error(), context)
|
||||
global.GVA_LOG.Error("兑换失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
r.OkWithMessage("兑换成功", context)
|
||||
}
|
112
api/v1/app/teacher_vip.go
Normal file
112
api/v1/app/teacher_vip.go
Normal file
@@ -0,0 +1,112 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/lckt/global"
|
||||
"git.echol.cn/loser/lckt/model/app"
|
||||
"git.echol.cn/loser/lckt/model/app/request"
|
||||
common "git.echol.cn/loser/lckt/model/common/request"
|
||||
r "git.echol.cn/loser/lckt/model/common/response"
|
||||
"git.echol.cn/loser/lckt/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type TeacherVip struct{}
|
||||
|
||||
// CreateTeacherVip 创建讲师VIP
|
||||
func (a *TeacherVip) CreateTeacherVip(context *gin.Context) {
|
||||
var p app.TeacherVip
|
||||
if err := context.ShouldBind(&p); err != nil {
|
||||
global.GVA_LOG.Error("参数错误,创建讲师VIP失败", zap.Error(err))
|
||||
r.FailWithMessage("参数错误,创建讲师VIP失败", context)
|
||||
return
|
||||
}
|
||||
|
||||
id := utils.GetUserID(context)
|
||||
|
||||
err := teacherVipService.CreateTeacherVip(p, id)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("创建讲师VIP失败", zap.Error(err))
|
||||
r.FailWithMessage("创建讲师VIP失败", context)
|
||||
return
|
||||
}
|
||||
r.OkWithMessage("创建讲师VIP成功", context)
|
||||
}
|
||||
|
||||
// DeleteTeacherVip 删除讲师VIP
|
||||
func (a *TeacherVip) DeleteTeacherVip(context *gin.Context) {
|
||||
var p app.TeacherVip
|
||||
if err := context.ShouldBindJSON(&p); err != nil {
|
||||
global.GVA_LOG.Error("参数错误,删除讲师VIP失败", zap.Error(err))
|
||||
r.FailWithMessage("参数错误,删除讲师VIP失败", context)
|
||||
return
|
||||
}
|
||||
|
||||
err := teacherVipService.DeleteTeacherVip(p)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("删除讲师VIP失败", zap.Error(err))
|
||||
r.FailWithMessage("删除讲师VIP失败", context)
|
||||
return
|
||||
}
|
||||
r.OkWithMessage("删除讲师VIP成功", context)
|
||||
}
|
||||
|
||||
// Update 更新讲师VIP
|
||||
func (a *TeacherVip) Update(context *gin.Context) {
|
||||
var p app.TeacherVip
|
||||
if err := context.ShouldBindJSON(&p); err != nil {
|
||||
global.GVA_LOG.Error("参数错误,更新讲师VIP失败", zap.Error(err))
|
||||
r.FailWithMessage("参数错误,更新讲师VIP失败", context)
|
||||
return
|
||||
}
|
||||
|
||||
err := teacherVipService.Update(p)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("更新讲师VIP失败", zap.Error(err))
|
||||
r.FailWithMessage("更新讲师VIP失败", context)
|
||||
return
|
||||
}
|
||||
r.OkWithMessage("更新讲师VIP成功", context)
|
||||
}
|
||||
|
||||
// GetTeacherVipList 获取讲师VIP列表
|
||||
func (a *TeacherVip) GetTeacherVipList(context *gin.Context) {
|
||||
var p request.GetTeacherVipList
|
||||
if err := context.ShouldBind(&p); err != nil {
|
||||
global.GVA_LOG.Error("参数错误,获取讲师VIP列表失败", zap.Error(err))
|
||||
r.FailWithMessage("参数错误,获取讲师VIP列表失败", context)
|
||||
return
|
||||
}
|
||||
|
||||
vips, total, err := teacherVipService.GetTeacherVipList(p)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取讲师VIP列表失败", zap.Error(err))
|
||||
r.FailWithMessage("获取讲师VIP列表失败", context)
|
||||
return
|
||||
}
|
||||
|
||||
r.OkWithDetailed(
|
||||
r.PageResult{
|
||||
List: vips,
|
||||
Total: total,
|
||||
Page: p.Page,
|
||||
PageSize: p.PageSize,
|
||||
}, "获取讲师VIP列表成功", context)
|
||||
}
|
||||
|
||||
func (a *TeacherVip) GetTeacherVip(context *gin.Context) {
|
||||
var p common.GetById
|
||||
if err := context.ShouldBind(&p); err != nil {
|
||||
global.GVA_LOG.Error("参数错误,获取讲师VIP失败", zap.Error(err))
|
||||
r.FailWithMessage("参数错误,获取讲师VIP失败", context)
|
||||
return
|
||||
}
|
||||
|
||||
vip, err := teacherVipService.GetTeacherVip(p.ID)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取讲师VIP失败", zap.Error(err))
|
||||
r.FailWithMessage("获取讲师VIP失败", context)
|
||||
return
|
||||
}
|
||||
r.OkWithDetailed(vip, "获取讲师VIP成功", context)
|
||||
}
|
@@ -4,6 +4,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
common "git.echol.cn/loser/lckt/model/common/request"
|
||||
user2 "git.echol.cn/loser/lckt/model/user"
|
||||
"gorm.io/gorm"
|
||||
"strconv"
|
||||
"time"
|
||||
@@ -67,6 +68,24 @@ func (*AppUserApi) Login(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 添加登录日志
|
||||
loginLog := user2.LoginLog{
|
||||
UserId: user.ID,
|
||||
UserName: user.NickName,
|
||||
Phone: user.Phone,
|
||||
Ip: ctx.ClientIP(),
|
||||
Address: utils.GetIPAdcode(ctx.ClientIP()),
|
||||
Device: ctx.Request.Header.Get("sec-ch-ua-platform"),
|
||||
UserAgent: ctx.Request.UserAgent(),
|
||||
Mode: "微信登录",
|
||||
LoginTime: time.Now().Format("2006-01-02 15:04:05"),
|
||||
}
|
||||
|
||||
err = global.GVA_DB.Create(&loginLog).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("添加登录日志失败!", zap.Error(err))
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -109,7 +128,7 @@ func (*AppUserApi) WechatLogin(ctx *gin.Context) {
|
||||
|
||||
user, err := appUserService.WechatLogin(info)
|
||||
if err != nil {
|
||||
r.FailWithMessage("登录失败", ctx)
|
||||
r.FailWithMessage("登录失败:"+err.Error(), ctx)
|
||||
return
|
||||
}
|
||||
// 生成token
|
||||
@@ -132,6 +151,24 @@ func (*AppUserApi) WechatLogin(ctx *gin.Context) {
|
||||
}
|
||||
user_jwt.SetToken(ctx, token, int(claims.RegisteredClaims.ExpiresAt.Unix()-time.Now().Unix()))
|
||||
|
||||
// 添加登录日志
|
||||
loginLog := user2.LoginLog{
|
||||
UserId: user.ID,
|
||||
UserName: user.NickName,
|
||||
Phone: user.Phone,
|
||||
Ip: ctx.ClientIP(),
|
||||
Address: utils.GetIPAdcode(ctx.ClientIP()),
|
||||
Device: ctx.Request.Header.Get("sec-ch-ua-platform"),
|
||||
UserAgent: ctx.Request.UserAgent(),
|
||||
Mode: "微信登录",
|
||||
LoginTime: time.Now().Format("2006-01-02 15:04:05"),
|
||||
}
|
||||
|
||||
err = global.GVA_DB.Create(&loginLog).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("添加登录日志失败!", zap.Error(err))
|
||||
}
|
||||
|
||||
r.OkWithDetailed(gin.H{
|
||||
"User": user,
|
||||
"Token": token,
|
||||
@@ -369,6 +406,38 @@ func (a *AppUserApi) BindPhone(context *gin.Context) {
|
||||
r.OkWithDetailed(user, "绑定手机号成功", context)
|
||||
}
|
||||
|
||||
// GetBalanceLog 获取用户余额变动日志
|
||||
func (a *AppUserApi) GetBalanceLog(context *gin.Context) {
|
||||
var p common.PageInfo
|
||||
if err := context.ShouldBind(&p); err != nil {
|
||||
global.GVA_LOG.Error("参数错误,获取用户余额变动日志失败", zap.Error(err))
|
||||
r.FailWithMessage("参数错误,获取用户余额变动日志失败", context)
|
||||
return
|
||||
}
|
||||
|
||||
id := user_jwt.GetUserID(context)
|
||||
if id == 0 {
|
||||
global.GVA_LOG.Error("获取用户ID失败")
|
||||
r.FailWithMessage("获取用户ID失败", context)
|
||||
return
|
||||
}
|
||||
|
||||
logs, total, err := appUserService.GetBalanceLog(id, p)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取用户余额变动日志失败", zap.Error(err))
|
||||
r.FailWithMessage("获取用户余额变动日志失败", context)
|
||||
return
|
||||
}
|
||||
|
||||
r.OkWithDetailed(
|
||||
r.PageResult{
|
||||
List: logs,
|
||||
Total: total,
|
||||
Page: p.Page,
|
||||
PageSize: p.PageSize,
|
||||
}, "获取用户余额变动日志成功", context)
|
||||
}
|
||||
|
||||
// -----------------讲师相关---------------------
|
||||
|
||||
// GetTeacherList 获取讲师列表
|
||||
@@ -395,3 +464,115 @@ func (a *AppUserApi) GetTeacherList(context *gin.Context) {
|
||||
PageSize: p.PageSize,
|
||||
}, "获取讲师列表成功", context)
|
||||
}
|
||||
|
||||
// GetFollowTeacherList 获取关注的讲师列表
|
||||
func (a *AppUserApi) GetFollowTeacherList(context *gin.Context) {
|
||||
var p common.PageInfo
|
||||
if err := context.ShouldBind(&p); err != nil {
|
||||
global.GVA_LOG.Error("参数错误,获取关注的讲师列表失败", zap.Error(err))
|
||||
r.FailWithMessage("参数错误,获取关注的讲师列表失败", context)
|
||||
return
|
||||
}
|
||||
|
||||
id := user_jwt.GetUserID(context)
|
||||
if id == 0 {
|
||||
global.GVA_LOG.Error("获取用户ID失败")
|
||||
r.FailWithMessage("获取用户ID失败", context)
|
||||
return
|
||||
}
|
||||
|
||||
teachers, total, err := appUserService.GetFollowTeacherList(id, p)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取关注的讲师列表失败", zap.Error(err))
|
||||
r.FailWithMessage("获取关注的讲师列表失败", context)
|
||||
return
|
||||
}
|
||||
|
||||
r.OkWithDetailed(
|
||||
r.PageResult{
|
||||
List: teachers,
|
||||
Total: total,
|
||||
Page: p.Page,
|
||||
PageSize: p.PageSize,
|
||||
}, "获取关注的讲师列表成功", context)
|
||||
}
|
||||
|
||||
// FollowTeacher 关注讲师
|
||||
func (a *AppUserApi) FollowTeacher(context *gin.Context) {
|
||||
var p app.Follow
|
||||
if err := context.ShouldBind(&p); err != nil {
|
||||
global.GVA_LOG.Error("参数错误,关注讲师失败", zap.Error(err))
|
||||
r.FailWithMessage("参数错误,关注讲师失败", context)
|
||||
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.FollowTeacher(p); err != nil {
|
||||
global.GVA_LOG.Error("关注讲师失败", zap.Error(err))
|
||||
r.FailWithMessage("关注讲师失败", context)
|
||||
return
|
||||
}
|
||||
r.OkWithMessage("关注讲师成功", context)
|
||||
}
|
||||
|
||||
// GetFollowStatus 获取用户关注讲师状态
|
||||
func (a *AppUserApi) GetFollowStatus(ctx *gin.Context) {
|
||||
userId := user_jwt.GetUserID(ctx)
|
||||
if userId == 0 {
|
||||
r.FailWithMessage("获取用户ID失败", ctx)
|
||||
return
|
||||
}
|
||||
teacherIdStr := ctx.Query("teacherId")
|
||||
if teacherIdStr == "" {
|
||||
r.FailWithMessage("缺少参数: teacherId", ctx)
|
||||
return
|
||||
}
|
||||
teacherId, err := strconv.ParseUint(teacherIdStr, 10, 64)
|
||||
if err != nil {
|
||||
r.FailWithMessage("teacherId参数格式错误", ctx)
|
||||
return
|
||||
}
|
||||
followed, err := appUserService.IsFollowTeacher(uint(userId), uint(teacherId))
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取关注状态失败", zap.Error(err))
|
||||
r.FailWithMessage("获取关注状态失败", ctx)
|
||||
return
|
||||
}
|
||||
r.OkWithDetailed(followed, "获取关注状态成功", ctx)
|
||||
}
|
||||
|
||||
// GetVipTeacherList 获取包月讲师列表
|
||||
func (a *AppUserApi) GetVipTeacherList(context *gin.Context) {
|
||||
var p common.PageInfo
|
||||
if err := context.ShouldBind(&p); err != nil {
|
||||
global.GVA_LOG.Error("参数错误,获取包月讲师列表失败", zap.Error(err))
|
||||
r.FailWithMessage("参数错误,获取包月讲师列表失败", context)
|
||||
return
|
||||
}
|
||||
|
||||
userId := user_jwt.GetUserID(context)
|
||||
if userId == 0 {
|
||||
global.GVA_LOG.Error("获取用户ID失败")
|
||||
r.FailWithMessage("获取用户ID失败", context)
|
||||
return
|
||||
}
|
||||
|
||||
teachers, total, err := appUserService.GetVipTeacherList(p, userId)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取包月讲师列表失败", zap.Error(err))
|
||||
r.FailWithMessage("获取包月讲师列表失败", context)
|
||||
return
|
||||
}
|
||||
r.OkWithDetailed(
|
||||
r.PageResult{
|
||||
List: teachers,
|
||||
Total: total,
|
||||
Page: p.Page,
|
||||
PageSize: p.PageSize,
|
||||
}, "获取包月讲师列表成功", context)
|
||||
}
|
||||
|
142
api/v1/app/with.go
Normal file
142
api/v1/app/with.go
Normal file
@@ -0,0 +1,142 @@
|
||||
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"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// WithApi 提现接口
|
||||
type WithApi struct{}
|
||||
|
||||
// Create 创建提现请求
|
||||
func (w *WithApi) Create(c *gin.Context) {
|
||||
var p app.With
|
||||
if err := c.ShouldBind(&p); err != nil {
|
||||
r.FailWithMessage("申请提现参数有误:", c)
|
||||
global.GVA_LOG.Error("申请提现参数有误:", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
userId := utils.GetUserID(c)
|
||||
p.UserID = userId
|
||||
p.Status = 1 // 设置状态为待处理
|
||||
|
||||
err := withService.Create(p)
|
||||
if err != nil {
|
||||
r.FailWithMessage("创建提现请求失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
r.OkWithMessage("申请提现请求成功", c)
|
||||
}
|
||||
|
||||
// GetList 获取提现列表
|
||||
func (w *WithApi) GetList(c *gin.Context) {
|
||||
var p request.GetWithList
|
||||
if err := c.ShouldBind(&p); err != nil {
|
||||
r.FailWithMessage("获取提现列表参数有误:", c)
|
||||
global.GVA_LOG.Error("获取提现列表参数有误:", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
userId := utils.GetUserID(c)
|
||||
p.UserId = userId
|
||||
|
||||
list, total, err := withService.GetList(p)
|
||||
if err != nil {
|
||||
r.FailWithMessage("获取提现列表失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
r.OkWithDetailed(r.PageResult{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: p.Page,
|
||||
PageSize: p.PageSize,
|
||||
}, "获取提现列表成功", c)
|
||||
}
|
||||
|
||||
// Cancel 取消提现请求
|
||||
func (w *WithApi) Cancel(c *gin.Context) {
|
||||
var p app.With
|
||||
if err := c.ShouldBind(&p); err != nil {
|
||||
r.FailWithMessage("取消提现请求参数有误:", c)
|
||||
global.GVA_LOG.Error("取消提现请求参数有误:", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
userId := utils.GetUserID(c)
|
||||
p.UserID = userId
|
||||
|
||||
if p.ID == 0 {
|
||||
r.FailWithMessage("提现ID不能为空", c)
|
||||
return
|
||||
}
|
||||
|
||||
p.Status = 4 // 设置状态为取消
|
||||
|
||||
err := withService.UpdateStatus(p)
|
||||
if err != nil {
|
||||
r.FailWithMessage("取消提现请求失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
r.OkWithMessage("取消提现请求成功", c)
|
||||
}
|
||||
|
||||
// =============管理后台接口=================
|
||||
|
||||
// GetAdminList 获取提现列表(管理员)
|
||||
func (w *WithApi) GetAdminList(c *gin.Context) {
|
||||
var p request.GetWithList
|
||||
if err := c.ShouldBind(&p); err != nil {
|
||||
r.FailWithMessage("获取提现列表参数有误:", c)
|
||||
global.GVA_LOG.Error("获取提现列表参数有误:", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
list, total, err := withService.GetList(p)
|
||||
if err != nil {
|
||||
r.FailWithMessage("获取提现列表失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
r.OkWithDetailed(r.PageResult{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: p.Page,
|
||||
PageSize: p.PageSize,
|
||||
}, "获取提现列表成功", c)
|
||||
}
|
||||
|
||||
// UpdateStatus 更新提现状态(管理员)
|
||||
func (w *WithApi) UpdateStatus(c *gin.Context) {
|
||||
var p app.With
|
||||
if err := c.ShouldBind(&p); err != nil {
|
||||
r.FailWithMessage("更新提现状态参数有误:", c)
|
||||
global.GVA_LOG.Error("更新提现状态参数有误:", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
if p.ID == 0 {
|
||||
r.FailWithMessage("提现ID不能为空", c)
|
||||
return
|
||||
}
|
||||
if p.Status == 0 {
|
||||
r.FailWithMessage("提现状态不能为空", c)
|
||||
return
|
||||
}
|
||||
|
||||
err := withService.UpdateStatus(p)
|
||||
if err != nil {
|
||||
r.FailWithMessage("更新提现状态失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
r.OkWithMessage("更新提现状态成功", c)
|
||||
}
|
@@ -8,6 +8,7 @@ import (
|
||||
"git.echol.cn/loser/lckt/utils/user_jwt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ArticleApi struct{}
|
||||
@@ -90,6 +91,26 @@ func (ArticleApi) ById(ctx *gin.Context) {
|
||||
r.OkWithData(article, ctx)
|
||||
}
|
||||
|
||||
// BulkUpload 批量上传文章
|
||||
func (ArticleApi) BulkUpload(ctx *gin.Context) {
|
||||
var p request.BulkUpload
|
||||
if err := ctx.ShouldBind(&p); err != nil {
|
||||
r.FailWithMessage(err.Error(), ctx)
|
||||
global.GVA_LOG.Error("参数有误!", zap.Error(err))
|
||||
return
|
||||
}
|
||||
err := articleService.BulkUpload(p)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("批量上传失败!", zap.Error(err))
|
||||
// 只要返回部分失败的文件列表 删除前面的"部分文件上传失败: "即可
|
||||
r.FailWithDetailed(strings.TrimPrefix(err.Error(), "部分文件上传失败: "), "部分文件上传失败:"+strings.TrimPrefix(err.Error(), "部分文件上传失败: "), ctx)
|
||||
return
|
||||
}
|
||||
r.OkWithMessage("批量上传成功", ctx)
|
||||
}
|
||||
|
||||
// ===================================== APP 端接口 ============================
|
||||
|
||||
func (ArticleApi) APPGetList(ctx *gin.Context) {
|
||||
var p request.GetList
|
||||
if err := ctx.ShouldBind(&p); err != nil {
|
||||
|
@@ -196,3 +196,14 @@ func (catApi *CategoryApi) GetCategoryListPublic(context *gin.Context) {
|
||||
}
|
||||
response.OkWithData(list, context)
|
||||
}
|
||||
|
||||
// GetArticleCategoryList 获取文章类别列表
|
||||
func (catApi *CategoryApi) GetArticleCategoryList(context *gin.Context) {
|
||||
list, err := catService.GetArticleCategoryList()
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取文章类别列表失败!", zap.Error(err))
|
||||
response.FailWithMessage("获取文章类别列表失败:"+err.Error(), context)
|
||||
return
|
||||
}
|
||||
response.OkWithData(list, context)
|
||||
}
|
||||
|
@@ -171,3 +171,68 @@ func (a *UserApi) UpdateTeacherApplyStatus(context *gin.Context) {
|
||||
}
|
||||
r.OkWithMessage("更新教师申请状态成功", context)
|
||||
}
|
||||
|
||||
// GetLoginLog 获取用户登录日志
|
||||
func (a *UserApi) GetLoginLog(context *gin.Context) {
|
||||
var p request.GetUserListReq
|
||||
if err := context.ShouldBind(&p); err != nil {
|
||||
global.GVA_LOG.Error("参数错误,获取登录日志失败", zap.Error(err))
|
||||
r.FailWithMessage("参数错误,获取登录日志失败", context)
|
||||
return
|
||||
}
|
||||
|
||||
logs, total, err := userService.GetLoginLog(p)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取登录日志失败", zap.Error(err))
|
||||
r.FailWithMessage("获取登录日志失败", context)
|
||||
return
|
||||
}
|
||||
|
||||
r.OkWithDetailed(
|
||||
r.PageResult{
|
||||
List: logs,
|
||||
Total: total,
|
||||
Page: p.Page,
|
||||
PageSize: p.PageSize,
|
||||
}, "获取登录日志成功", context)
|
||||
}
|
||||
|
||||
// RemoveUserVip 移除用户Vip
|
||||
func (a *UserApi) RemoveUserVip(context *gin.Context) {
|
||||
var p common.GetById
|
||||
if err := context.ShouldBind(&p); err != nil {
|
||||
global.GVA_LOG.Error("参数错误,移除用户Vip失败", zap.Error(err))
|
||||
r.FailWithMessage("参数错误,移除用户Vip失败", context)
|
||||
return
|
||||
}
|
||||
|
||||
if err := userService.RemoveUserVip(p.ID); err != nil {
|
||||
global.GVA_LOG.Error("移除用户Vip失败", zap.Error(err))
|
||||
r.FailWithMessage("移除用户Vip失败", context)
|
||||
return
|
||||
}
|
||||
r.OkWithMessage("移除用户Vip成功", context)
|
||||
}
|
||||
|
||||
func (a *UserApi) GetUserVipList(context *gin.Context) {
|
||||
var p request.GetUserListReq
|
||||
if err := context.ShouldBind(&p); err != nil {
|
||||
global.GVA_LOG.Error("参数错误,获取用户会员列表失败", zap.Error(err))
|
||||
r.FailWithMessage("参数错误,获取用户会员列表失败", context)
|
||||
return
|
||||
}
|
||||
|
||||
list, total, err := userService.GetUserVipList(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)
|
||||
}
|
||||
|
37
config.yaml
37
config.yaml
@@ -20,28 +20,25 @@ zap:
|
||||
|
||||
# redis configuration
|
||||
redis:
|
||||
#是否使用redis集群模式
|
||||
useCluster: false
|
||||
#使用集群模式addr和db默认无效
|
||||
addr: 127.0.0.1:6379
|
||||
password: ""
|
||||
name: "hw"
|
||||
addr: 120.46.165.63:6379
|
||||
password: "loser765911"
|
||||
db: 0
|
||||
useCluster: false
|
||||
clusterAddrs:
|
||||
- "172.21.0.3:7000"
|
||||
- "172.21.0.4:7001"
|
||||
- "172.21.0.2:7002"
|
||||
|
||||
# redis-list configuration
|
||||
- 172.21.0.3:7000
|
||||
- 172.21.0.4:7001
|
||||
- 172.21.0.2:7002
|
||||
redis-list:
|
||||
- name: cache # 数据库的名称,注意: name 需要在 redis-list 中唯一
|
||||
useCluster: false # 是否使用redis集群模式
|
||||
addr: 127.0.0.1:6379 # 使用集群模式addr和db默认无效
|
||||
password: ""
|
||||
db: 0
|
||||
- name: cache
|
||||
addr: 120.46.165.63:6379
|
||||
password: "loser765911"
|
||||
db: 1
|
||||
useCluster: false
|
||||
clusterAddrs:
|
||||
- "172.21.0.3:7000"
|
||||
- "172.21.0.4:7001"
|
||||
- "172.21.0.2:7002"
|
||||
- 172.21.0.3:7000
|
||||
- 172.21.0.4:7001
|
||||
- 172.21.0.2:7002
|
||||
|
||||
# mongo configuration
|
||||
mongo:
|
||||
@@ -76,7 +73,7 @@ system:
|
||||
addr: 8888
|
||||
db-type: mysql
|
||||
oss-type: aliyun-oss # 控制oss选择走本地还是 七牛等其他仓 自行增加其他oss仓可以在 server/utils/upload/upload.go 中 NewOss函数配置
|
||||
use-redis: false # 使用redis
|
||||
use-redis: true # 使用redis
|
||||
use-mongo: false # 使用mongo
|
||||
use-multipoint: false
|
||||
# IP限制次数 一个小时15000次
|
||||
@@ -265,7 +262,7 @@ disk-list:
|
||||
# 跨域配置
|
||||
# 需要配合 server/initialize/router.go -> `Router.Use(middleware.CorsByRules())` 使用
|
||||
cors:
|
||||
mode: allow-all # 放行模式: allow-all, 放行全部; whitelist, 白名单模式, 来自白名单内域名的请求添加 cors 头; strict-whitelist 严格白名单模式, 白名单外的请求一律拒绝
|
||||
mode: allow-all # 放行模式: allow-all, 放行全部; whitelist, 白名单模式, 来自白名单内域名的请求添加 cors 头; strict-whitelist 严格白名单模式, 白名单外的请求一律拒绝
|
||||
whitelist:
|
||||
- allow-origin: example1.com
|
||||
allow-headers: Content-Type,AccessToken,X-CSRF-Token, Authorization, Token,X-Token,X-User-Id
|
||||
|
@@ -80,6 +80,15 @@ func RegisterTables() {
|
||||
category.Category{},
|
||||
notice.Notice{},
|
||||
vip.Vip{},
|
||||
app.Follow{},
|
||||
app.TeacherVip{},
|
||||
app.UserTeacherVip{},
|
||||
user.LoginLog{},
|
||||
app.RedeemCode{},
|
||||
app.CDK{},
|
||||
app.BalanceLog{},
|
||||
app.With{},
|
||||
app.Domain{},
|
||||
)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("register table failed", zap.Error(err))
|
||||
|
@@ -117,6 +117,9 @@ func Routers() *gin.Engine {
|
||||
appRouter.InitAppUserRouter(AppAuthGroup, PublicGroup)
|
||||
appRouter.InitBannerRouter(PrivateGroup, PublicGroup) // Banner相关路由
|
||||
appRouter.InitOrderRouter(AppAuthGroup, PrivateGroup, PublicGroup) // 订单相关路由
|
||||
appRouter.InitRedeemCodeRouter(AppAuthGroup, PrivateGroup) // 兑换码相关路由
|
||||
appRouter.InitWithRouter(AppAuthGroup, PrivateGroup)
|
||||
appRouter.InitDomainRouter(PrivateGroup)
|
||||
}
|
||||
|
||||
//插件路由安装
|
||||
|
@@ -24,14 +24,39 @@ func Timer() {
|
||||
fmt.Println("add timer error:", err)
|
||||
}
|
||||
|
||||
// 其他定时任务定在这里 参考上方使用方法
|
||||
// 定时清理过期VIP用户
|
||||
_, err = global.GVA_Timer.AddTaskByFunc("ClearVip", "@daily", func() {
|
||||
err2 := task.CheckVip(global.GVA_DB)
|
||||
if err2 != nil {
|
||||
fmt.Println("清理过期VIP定时任务失败:", err2)
|
||||
}
|
||||
}, "定时清理过期VIP日志内容:", option...)
|
||||
if err != nil {
|
||||
fmt.Println("add timer error:", err)
|
||||
}
|
||||
|
||||
//_, err := global.GVA_Timer.AddTaskByFunc("定时任务标识", "corn表达式", func() {
|
||||
// 具体执行内容...
|
||||
// ......
|
||||
//}, option...)
|
||||
//if err != nil {
|
||||
// fmt.Println("add timer error:", err)
|
||||
//}
|
||||
// 定时发布文章
|
||||
fmt.Println("注册定时任务: PublishArticles")
|
||||
_, err = global.GVA_Timer.AddTaskByFunc("PublishArticles", "0 0/1 * * * ?", func() {
|
||||
fmt.Println("执行定时任务: PublishArticles")
|
||||
err3 := task.PublishArticles(global.GVA_DB)
|
||||
if err3 != nil {
|
||||
fmt.Println("定时发布文章失败:", err3)
|
||||
}
|
||||
}, "定时发布文章任务,每分钟检查一次", option...)
|
||||
if err != nil {
|
||||
fmt.Println("add timer error:", err)
|
||||
}
|
||||
|
||||
// 清理超时订单
|
||||
_, err = global.GVA_Timer.AddTaskByFunc("ClearExpiredOrders", "0 0/5 * * * ?", func() {
|
||||
err4 := task.ClearExpiredOrders(global.GVA_DB)
|
||||
if err4 != nil {
|
||||
fmt.Println("清理超时订单失败:", err4)
|
||||
}
|
||||
}, "定时清理超时订单任务,每5分钟检查一次", option...)
|
||||
if err != nil {
|
||||
fmt.Println("add timer error:", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
3
main.go
3
main.go
@@ -7,6 +7,8 @@ import (
|
||||
"git.echol.cn/loser/lckt/utils/wechat"
|
||||
_ "go.uber.org/automaxprocs"
|
||||
"go.uber.org/zap"
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
//go:generate go env -w GO111MODULE=on
|
||||
@@ -48,6 +50,7 @@ func initializeSystem() {
|
||||
|
||||
wechat.InitWechatPay()
|
||||
wechat.InitWeOfficial()
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
initialize.SetupHandlers() // 注册全局函数
|
||||
if global.GVA_DB != nil {
|
||||
|
15
model/app/balance_log.go
Normal file
15
model/app/balance_log.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package app
|
||||
|
||||
import "git.echol.cn/loser/lckt/global"
|
||||
|
||||
type BalanceLog struct {
|
||||
global.GVA_MODEL
|
||||
UserID uint `json:"userId" form:"userId" gorm:"comment:用户ID"`
|
||||
ChangeType int `json:"changeType" form:"changeType" gorm:"comment:变动类型 1 增加 2 减少"`
|
||||
ChangeValue float64 `json:"changeValue" form:"changeValue" gorm:"comment:变动值"`
|
||||
Balance float64 `json:"balance" form:"balance" gorm:"comment:变动后余额"`
|
||||
}
|
||||
|
||||
func (BalanceLog) TableName() string {
|
||||
return "app_balance_log"
|
||||
}
|
16
model/app/domain.go
Normal file
16
model/app/domain.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package app
|
||||
|
||||
import "git.echol.cn/loser/lckt/global"
|
||||
|
||||
type Domain struct {
|
||||
global.GVA_MODEL
|
||||
Name string `json:"name" gorm:"column:name;comment:域名名称"` // 域名名称
|
||||
DomainUrl string `json:"domain_url" gorm:"column:domain;comment:域名"` // 域名
|
||||
Description string `json:"description" gorm:"column:description;comment:域名描述"` // 域名描述
|
||||
Status int `json:"status" gorm:"column:status;comment:状态 1 启用 2 禁用"` // 状态 1 启用 2 禁用
|
||||
Type int `json:"type" gorm:"column:type;comment:类型 1 炮灰域名 2 入口域名"` // 类型 1 炮灰域名 2 入口域名
|
||||
}
|
||||
|
||||
func (Domain) TableName() string {
|
||||
return "app_domain"
|
||||
}
|
13
model/app/follow.go
Normal file
13
model/app/follow.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package app
|
||||
|
||||
import "git.echol.cn/loser/lckt/global"
|
||||
|
||||
type Follow struct {
|
||||
global.GVA_MODEL
|
||||
UserId uint `json:"userId" gorm:"comment:用户ID;uniqueIndex:idx_user_teacher"`
|
||||
TeacherId uint `json:"teacherId" gorm:"comment:讲师ID;uniqueIndex:idx_user_teacher"`
|
||||
}
|
||||
|
||||
func (Follow) TableName() string {
|
||||
return "app_follow"
|
||||
}
|
@@ -4,20 +4,21 @@ 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"`
|
||||
VipId uint `gorm:"column:vip_id;type:bigint(20) unsigned;default:0;comment:会员ID;NOT NULL" json:"vip_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"`
|
||||
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|3 讲师包月;NOT NULL" json:"order_type"`
|
||||
ArticleId uint `gorm:"column:article_id;type:bigint(20) unsigned;default:0;comment:文章ID;NOT NULL" json:"article_id"`
|
||||
VipId uint `gorm:"column:vip_id;type:bigint(20) unsigned;default:0;comment:会员ID;NOT NULL" json:"vip_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"`
|
||||
TeacherVipId string `gorm:"column:teacher_vip_id;unsigned;comment:讲师VIP ID;NOT NULL" json:"teacher_vip_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表
|
||||
|
34
model/app/redeem_code.go
Normal file
34
model/app/redeem_code.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package app
|
||||
|
||||
import "git.echol.cn/loser/lckt/global"
|
||||
|
||||
type RedeemCode struct {
|
||||
global.GVA_MODEL
|
||||
CodeName string `json:"codeName" gorm:"comment:兑换码名称"`
|
||||
Type int `json:"type" gorm:"comment:类型,1-VIP,2-讲师VIP,3-课程"`
|
||||
Item uint `json:"item" gorm:"comment:对应类型的ID"`
|
||||
Num int `json:"num" gorm:"comment:兑换码数量"`
|
||||
No int `json:"no" gorm:"comment:已使用数量"`
|
||||
}
|
||||
|
||||
func (RedeemCode) TableName() string {
|
||||
return "app_redeem_code"
|
||||
}
|
||||
|
||||
type CDK struct {
|
||||
global.GVA_MODEL
|
||||
RedeemId uint `json:"redeemId" gorm:"comment:兑换码库ID"`
|
||||
Code string `json:"code" gorm:"unique;comment:兑换码"`
|
||||
Status int `json:"status" gorm:"default:1;comment:状态,1-未使用,2-已使用,3-已过期"`
|
||||
UseId uint `json:"useId" gorm:"comment:使用用户ID"`
|
||||
UseName string `json:"useName" gorm:"comment:使用用户名"`
|
||||
UseAt string `json:"useAt" gorm:"comment:使用时间"`
|
||||
// 有效期
|
||||
ValidDay int `json:"validDay" gorm:"comment:有效期,单位天 0表示永久有效"`
|
||||
// 过期时间
|
||||
ExpireAt string `json:"expireAt" gorm:"comment:过期时间"`
|
||||
}
|
||||
|
||||
func (CDK) TableName() string {
|
||||
return "app_cdk"
|
||||
}
|
23
model/app/request/cdk.go
Normal file
23
model/app/request/cdk.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package request
|
||||
|
||||
import common "git.echol.cn/loser/lckt/model/common/request"
|
||||
|
||||
type GenerateCDK struct {
|
||||
Eid uint `json:"eid" form:"eid" binding:"required"` // 兑换码库ID
|
||||
Number int `json:"number" from:"number" binding:"required,min=1"` // 生成数量
|
||||
Expirer int `json:"expirer" form:"expirer"` // 有效期,单位天 0表示永久有效
|
||||
}
|
||||
|
||||
type GetCDKList struct {
|
||||
common.PageInfo
|
||||
Eid uint `json:"eid" form:"eid" binding:"required"`
|
||||
Code string `json:"code" form:"code"` // 兑换码
|
||||
UseName string `json:"useName" form:"useName"` // 使用人
|
||||
Status int `json:"status" form:"status"` // 状态
|
||||
}
|
||||
|
||||
type RedeemCDK struct {
|
||||
Code string `json:"code" form:"code" binding:"required"` // 兑换码
|
||||
UseName string `json:"useName" form:"useName"` // 使用人名称
|
||||
UserId uint `json:"userId" form:"userId"` // 用户ID
|
||||
}
|
11
model/app/request/domain.go
Normal file
11
model/app/request/domain.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package request
|
||||
|
||||
import common "git.echol.cn/loser/lckt/model/common/request"
|
||||
|
||||
type GetDomainList struct {
|
||||
common.PageInfo
|
||||
Name string `json:"name" form:"name"` // 域名名称
|
||||
Domain string `json:"domain" form:"domain"` // 域名
|
||||
Status int `json:"status" form:"status"` // 状态 1 启用 2 禁用
|
||||
Type int `json:"type" form:"type"` // 类型 1 炮灰域名 2 业务域名 3 入口域名
|
||||
}
|
8
model/app/request/user.go
Normal file
8
model/app/request/user.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package request
|
||||
|
||||
import common "git.echol.cn/loser/lckt/model/common/request"
|
||||
|
||||
type GetTeacherVipList struct {
|
||||
common.PageInfo
|
||||
TeacherId uint `json:"teacher_id" form:"teacher_id"` // 讲师ID
|
||||
}
|
12
model/app/request/with.go
Normal file
12
model/app/request/with.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package request
|
||||
|
||||
import common "git.echol.cn/loser/lckt/model/common/request"
|
||||
|
||||
type GetWithList struct {
|
||||
common.PageInfo
|
||||
UserId uint `json:"user_id" form:"user_id"` // 用户ID
|
||||
UserName string `json:"user_name" form:"user_name"` // 用户名
|
||||
Status int `json:"status" form:"status"`
|
||||
StartTime string `json:"start_time" form:"start_time"`
|
||||
EndTime string `json:"end_time" form:"end_time"`
|
||||
}
|
17
model/app/teacher_vip.go
Normal file
17
model/app/teacher_vip.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package app
|
||||
|
||||
import "git.echol.cn/loser/lckt/global"
|
||||
|
||||
type TeacherVip struct {
|
||||
global.GVA_MODEL
|
||||
Title string `json:"title" form:"title" gorm:"comment:VIP标题;size:128"`
|
||||
TeacherId uint `json:"teacher_id" gorm:"comment:讲师ID;"`
|
||||
TeacherName string `json:"teacher_name" gorm:"comment:讲师名称"` // 讲师名称
|
||||
Avatar string `json:"avatar" gorm:"comment:讲师头像"`
|
||||
Price int `json:"price" gorm:"comment:VIP价格(单位为分)"`
|
||||
Desc string `json:"desc" gorm:"comment:VIP描述;type:longtext"`
|
||||
}
|
||||
|
||||
func (TeacherVip) TableName() string {
|
||||
return "app_teacher_vip"
|
||||
}
|
17
model/app/user_teacher_vip.go
Normal file
17
model/app/user_teacher_vip.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package app
|
||||
|
||||
import "git.echol.cn/loser/lckt/global"
|
||||
|
||||
type UserTeacherVip struct {
|
||||
global.GVA_MODEL
|
||||
UserId uint `gorm:"column:user_id;comment:用户ID;NOT NULL" json:"user_id"`
|
||||
TeacherId uint `gorm:"column:teacher_id;comment:讲师ID;NOT NULL" json:"teacher_id"`
|
||||
TeacherVipId uint `gorm:"column:teacher_vip_id;comment:讲师VIP ID;NOT NULL" json:"teacher_vip_id"`
|
||||
ExpireAt string `gorm:"column:expire_at;comment:到期时间;NOT NULL" json:"expire_at"`
|
||||
//是否过期
|
||||
IsExpire int `gorm:"column:is_expire;type:int(11);default:1;comment:是否过期 |1 未过期|2 已过期;NOT NULL" json:"is_expire"`
|
||||
}
|
||||
|
||||
func (UserTeacherVip) TableName() string {
|
||||
return "user_teacher_vip"
|
||||
}
|
8
model/app/vo/cdk.go
Normal file
8
model/app/vo/cdk.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package vo
|
||||
|
||||
import "git.echol.cn/loser/lckt/model/app"
|
||||
|
||||
type CDKVo struct {
|
||||
Eid uint `json:"eid"` // 兑换码库ID
|
||||
CDK []app.CDK `json:"cdk"` // 生成的兑换码
|
||||
}
|
@@ -21,4 +21,5 @@ type TeacherInfo struct {
|
||||
NickName string `json:"nick_name" form:"nick_name"`
|
||||
Avatar string `json:"avatar" form:"avatar"`
|
||||
Des string `json:"des" form:"des"`
|
||||
Follow int64 `json:"follow" form:"follow"` // 粉丝数
|
||||
}
|
||||
|
23
model/app/with.go
Normal file
23
model/app/with.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package app
|
||||
|
||||
import "git.echol.cn/loser/lckt/global"
|
||||
|
||||
// With 提现管理
|
||||
type With struct {
|
||||
global.GVA_MODEL
|
||||
WithType int `json:"withType" form:"withType" gorm:"comment:提现类型 1 微信 2 支付宝 3 银行卡"`
|
||||
UserID uint `json:"userId" form:"userId" gorm:"comment:用户ID"`
|
||||
UserName string `json:"userName" form:"userName" gorm:"comment:用户名称"`
|
||||
Amount float64 `json:"amount" form:"amount" gorm:"comment:提现金额"`
|
||||
RealAmount float64 `json:"realAmount" form:"realAmount" gorm:"comment:实际到账金额"`
|
||||
Status int `json:"status" form:"status" gorm:"comment:状态 1 待处理 2 已完成 3 已拒绝 4 已取消"`
|
||||
// 到账时间
|
||||
ArriveTime string `json:"arriveTime" form:"arriveTime" gorm:"comment:到账时间"`
|
||||
// 账户信息
|
||||
AccountInfo string `json:"accountInfo" form:"accountInfo" gorm:"type:text;comment:账户信息 收款码或银行卡等"`
|
||||
Remark string `json:"remark" form:"remark" gorm:"type:text;comment:备注"`
|
||||
}
|
||||
|
||||
func (With) TableName() string {
|
||||
return "app_with"
|
||||
}
|
@@ -2,21 +2,23 @@ package article
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/lckt/global"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Article struct {
|
||||
global.GVA_MODEL
|
||||
Title string `json:"title" gorm:"comment:文章标题"`
|
||||
Desc string `json:"desc" gorm:"comment:文章描述"`
|
||||
Desc string `json:"desc" gorm:"comment:文章描述;type:longtext"`
|
||||
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-是
|
||||
IsFree *int `json:"isFree" gorm:"comment:是否免费;default:0"` // 是否免费 0-否 1-是
|
||||
// 分类ID
|
||||
CategoryId int `json:"categoryId" gorm:"comment:分类ID"`
|
||||
Status int `json:"status" gorm:"comment:状态 1-已发布 2-待审核 3-审核不通过;default:2"` // 状态 0-草稿 1-已发布 2-待审核 3-审核不通过
|
||||
CategoryId int `json:"categoryId" gorm:"comment:分类ID"`
|
||||
Status int `json:"status" gorm:"comment:状态 1-已发布 2-待审核 3-审核不通过;default:2"` // 状态 0-草稿 1-已发布 2-待审核 3-审核不通过
|
||||
PublishTime *time.Time `json:"publishTime" gorm:"comment:定时发布时间"`
|
||||
}
|
||||
|
||||
// TableName 文章表
|
||||
|
@@ -15,3 +15,15 @@ type GetList struct {
|
||||
type DeleteIds struct {
|
||||
Ids []int `json:"ids" form:"ids" binding:"required"`
|
||||
}
|
||||
|
||||
type BulkUpload struct {
|
||||
Files []string `json:"files" form:"files" binding:"required"`
|
||||
Title string `json:"title" form:"title" binding:"required"`
|
||||
Desc string `json:"desc" form:"desc" binding:"required"`
|
||||
Price float64 `json:"price" form:"price" binding:"required"` // 价格,单位分
|
||||
// 分类ID
|
||||
CategoryId int `json:"categoryId" form:"categoryId" binding:"required"` // 分类ID
|
||||
// 发布时间
|
||||
PublishTime string `json:"publishTime" form:"publishTime"` // 发布时间
|
||||
IsFree *int `json:"isFree" form:"isFree"` // 是否免费
|
||||
}
|
||||
|
@@ -1,18 +1,22 @@
|
||||
package vo
|
||||
|
||||
import "git.echol.cn/loser/lckt/model/app/vo"
|
||||
import (
|
||||
"git.echol.cn/loser/lckt/model/app/vo"
|
||||
"time"
|
||||
)
|
||||
|
||||
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-是
|
||||
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-是
|
||||
CreatedAt time.Time `json:"createdAt" gorm:"comment:创建时间"`
|
||||
}
|
||||
|
||||
type ArticleVo struct {
|
||||
|
@@ -15,6 +15,8 @@ type Category struct {
|
||||
Icon *string `json:"icon" form:"icon" gorm:"column:icon;comment:类别图标;"` //图标
|
||||
IndexTrue *bool `json:"index" form:"index" gorm:"column:index;comment:是否首页显示;"` //是否首页显示
|
||||
Url *string `json:"url" form:"url" gorm:"column:url;comment:类别链接;"` //链接
|
||||
// 是否文章分类
|
||||
IsArticle *int `json:"isArticle" form:"isArticle" gorm:"column:is_article;default:0;comment:是否文章分类;"` //是否文章分类
|
||||
}
|
||||
|
||||
// TableName 类别 Category自定义表名 categories
|
||||
|
22
model/user/login_log.go
Normal file
22
model/user/login_log.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package user
|
||||
|
||||
import "git.echol.cn/loser/lckt/global"
|
||||
|
||||
type LoginLog struct {
|
||||
global.GVA_MODEL
|
||||
UserId uint `json:"user_id" gorm:"comment:用户ID;index"`
|
||||
UserName string `json:"user_name" gorm:"comment:用户登录名"`
|
||||
Phone string `json:"phone" gorm:"comment:用户手机号"`
|
||||
Ip string `json:"ip" gorm:"comment:登录IP"`
|
||||
Address string `json:"address" gorm:"comment:登录地址"`
|
||||
UserAgent string `json:"user_agent" gorm:"comment:用户代理"`
|
||||
// 登录设备
|
||||
Device string `json:"device" gorm:"comment:登录设备"`
|
||||
// 登录方式
|
||||
Mode string `json:"mode" gorm:"comment:登录方式"`
|
||||
LoginTime string `json:"login_time" gorm:"comment:登录时间"`
|
||||
}
|
||||
|
||||
func (LoginLog) TableName() string {
|
||||
return "user_login_log"
|
||||
}
|
@@ -43,6 +43,7 @@ type BindPhoneReq struct {
|
||||
|
||||
type GetUserListReq struct {
|
||||
request.PageInfo
|
||||
UserId uint `json:"user_id" form:"user_id"`
|
||||
Type int `json:"type" form:"type"`
|
||||
UserLabel string `json:"user_label" form:"user_label" `
|
||||
Status int `json:"status" form:"status"`
|
||||
@@ -50,8 +51,9 @@ type GetUserListReq struct {
|
||||
}
|
||||
|
||||
type SetBalanceReq struct {
|
||||
Id int `json:"id" form:"id" vd:"@:len($)>0; msg:'用户ID不能为空'"`
|
||||
Balance float32 `json:"balance" form:"balance" vd:"@:len($)>0; msg:'余额不能为空'"`
|
||||
Id int `json:"id" form:"id" vd:"@:len($)>0; msg:'用户ID不能为空'"`
|
||||
Balance float64 `json:"balance" form:"balance" vd:"@:len($)>0; msg:'余额不能为空'"`
|
||||
ChangeType int `json:"change_type" form:"change_type" vd:"@:len($)>0; msg:'变动类型不能为空'"` // 1 增加 2 减少
|
||||
}
|
||||
|
||||
type PwdLoginReq struct {
|
||||
|
@@ -14,12 +14,15 @@ type User struct {
|
||||
Status int8 `gorm:"column:status;default:1;NOT NULL;comment:'用户状态 0 封禁 1 正常'" json:"status" form:"status"`
|
||||
//邀请码
|
||||
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:学员余额"`
|
||||
Balance float64 `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"`
|
||||
UserLabel int64 `json:"user_label" form:"user_label" gorm:"comment:用户标签 1 普通用户 2 Vip 3 Svip 4 到期会员"`
|
||||
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过期时间"`
|
||||
//权重
|
||||
Weight int `json:"weight" form:"weight" gorm:"comment:用户权重"`
|
||||
ExpectRate int `json:"expect_rate" form:"expect_rate" gorm:"comment:讲师分成比例"`
|
||||
}
|
||||
|
||||
func (User) TableName() string {
|
||||
|
@@ -18,5 +18,6 @@ func (b *BannerRouter) InitBannerRouter(Router, PublicRouter *gin.RouterGroup) {
|
||||
appRouter.GET("/list", bannerApi.GetList) // 获取Banner列表
|
||||
appRouter.GET("", bannerApi.GetByID) // Banner公开接口
|
||||
appRouter.GET("/index", bannerApi.GetIndexBanners) // 获取首页Banner
|
||||
appRouter.GET("vip", bannerApi.GetVIPBanners) // 获取VIP Banner
|
||||
}
|
||||
}
|
||||
|
17
router/app/domain.go
Normal file
17
router/app/domain.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package app
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
|
||||
type DomainRouter struct{}
|
||||
|
||||
func (d *DomainRouter) InitDomainRouter(SysteamRouter *gin.RouterGroup) {
|
||||
domainRouter := SysteamRouter.Group("domain")
|
||||
{
|
||||
domainRouter.POST("", domainApi.Create) // 创建域名
|
||||
domainRouter.GET("list", domainApi.GetList) // 获取域名列表
|
||||
domainRouter.GET("/:id", domainApi.GetByID) // 获取域名列表
|
||||
domainRouter.PUT("", domainApi.Update) // 更新域名
|
||||
domainRouter.DELETE("", domainApi.Delete) // 删除域名
|
||||
}
|
||||
|
||||
}
|
@@ -6,8 +6,15 @@ type RouterGroup struct {
|
||||
UserRouter
|
||||
BannerRouter
|
||||
OrderRouter
|
||||
RedeemCodeRouter
|
||||
WithRouter
|
||||
DomainRouter
|
||||
}
|
||||
|
||||
var userApi = api.ApiGroupApp.AppApiGroup.AppUserApi
|
||||
var bannerApi = api.ApiGroupApp.AppApiGroup.BannerApi
|
||||
var orderApi = api.ApiGroupApp.AppApiGroup.OrderApi
|
||||
var teacherVipApi = api.ApiGroupApp.AppApiGroup.TeacherVip
|
||||
var redeemCodeApi = api.ApiGroupApp.AppApiGroup.RedeemCodeApi
|
||||
var withApi = api.ApiGroupApp.AppApiGroup.WithApi
|
||||
var domainApi = api.ApiGroupApp.AppApiGroup.DomainApi
|
||||
|
31
router/app/redeem_code.go
Normal file
31
router/app/redeem_code.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package app
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
|
||||
type RedeemCodeRouter struct{}
|
||||
|
||||
func (rcr *RedeemCodeRouter) InitRedeemCodeRouter(AppRouter, SysteamRouter *gin.RouterGroup) {
|
||||
AppCDKRouter := AppRouter.Group("/cdk")
|
||||
SysCDKRouter := SysteamRouter.Group("/cdk")
|
||||
|
||||
{
|
||||
// 兑换码库
|
||||
SysCDKRouter.POST("mk", redeemCodeApi.Create) // 创建兑换码库
|
||||
SysCDKRouter.DELETE("mk", redeemCodeApi.Delete) // 删除兑换码库
|
||||
SysCDKRouter.PUT("mk", redeemCodeApi.Update) // 更新兑换码库
|
||||
SysCDKRouter.GET("/mk/list", redeemCodeApi.GetList) // 分页获取兑换码库列表
|
||||
SysCDKRouter.GET("mk/:id", redeemCodeApi.GetById) // 获取单个兑换码库信息
|
||||
}
|
||||
|
||||
{
|
||||
// 兑换码
|
||||
SysCDKRouter.POST("/generate", redeemCodeApi.CreateCDK) // 生成兑换码
|
||||
SysCDKRouter.GET("/list", redeemCodeApi.GetCDKList) // 分页获取兑换码列表
|
||||
SysCDKRouter.DELETE("", redeemCodeApi.DeleteCDK) // 删除兑换码
|
||||
}
|
||||
|
||||
{
|
||||
// 用户兑换码
|
||||
AppCDKRouter.POST("/redeem", redeemCodeApi.Redeem) // 兑换码
|
||||
}
|
||||
}
|
@@ -12,9 +12,14 @@ func (s *UserRouter) InitAppUserRouter(AppAuthGroup, PublicRouter *gin.RouterGro
|
||||
{
|
||||
appUserRouter.GET("/info", userApi.GetUserInfo) // 获取用户信息
|
||||
//申请成为讲师
|
||||
appUserRouter.POST("/applyTeacher", userApi.ApplyTeacher) // 申请成为讲师
|
||||
appUserRouter.GET("/applyTeacher", userApi.GetTeacherApply) // 获取教师申请状态
|
||||
appUserRouter.GET("/teachers", userApi.GetTeacherList) // 获取讲师列表
|
||||
appUserRouter.POST("/applyTeacher", userApi.ApplyTeacher) // 申请成为讲师
|
||||
appUserRouter.GET("/applyTeacher", userApi.GetTeacherApply) // 获取教师申请状态
|
||||
appUserRouter.GET("/teachers", userApi.GetTeacherList) // 获取讲师列表
|
||||
appUserRouter.GET("/follows", userApi.GetFollowTeacherList) // 获取关注的讲师列表
|
||||
appUserRouter.POST("/follow", userApi.FollowTeacher) // 关注/取关讲师
|
||||
appUserRouter.GET("/followStatus", userApi.GetFollowStatus) // 获取关注状态
|
||||
appUserRouter.GET("/vipTeachers", userApi.GetVipTeacherList) // 获取VIP讲师列表
|
||||
appUserRouter.GET("/balanceLog", userApi.GetBalanceLog) // 获取余额变动日志
|
||||
}
|
||||
{
|
||||
publicRouter.POST("wxLogin", userApi.WechatLogin) // 微信登录
|
||||
@@ -25,4 +30,12 @@ func (s *UserRouter) InitAppUserRouter(AppAuthGroup, PublicRouter *gin.RouterGro
|
||||
publicRouter.POST("login", userApi.Login) // 短信验证码登录
|
||||
publicRouter.POST("register", userApi.Register) // 注册
|
||||
}
|
||||
// 讲师包月相关接口
|
||||
{
|
||||
appUserRouter.GET("teacher_vips", teacherVipApi.GetTeacherVipList) // 获取讲师VIP列表
|
||||
appUserRouter.GET("teacher_vip/:id", teacherVipApi.GetTeacherVip) // 获取讲师VIP详情
|
||||
appUserRouter.POST("teacher_vip", teacherVipApi.CreateTeacherVip) // 创建讲师VIP
|
||||
appUserRouter.PUT("teacher_vip", teacherVipApi.Update) // 更新讲师VIP
|
||||
appUserRouter.DELETE("teacher_vip", teacherVipApi.DeleteTeacherVip) // 删除讲师VIP
|
||||
}
|
||||
}
|
||||
|
21
router/app/with.go
Normal file
21
router/app/with.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package app
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
|
||||
type WithRouter struct{}
|
||||
|
||||
// InitWithRouter 初始化 With 路由信息
|
||||
func (s *WithRouter) InitWithRouter(AppRouter, SysteamRouter *gin.RouterGroup) {
|
||||
appRouter := AppRouter.Group("with")
|
||||
sysRouter := SysteamRouter.Group("sys/with")
|
||||
|
||||
{
|
||||
appRouter.POST("", withApi.Create) // 创建提现请求
|
||||
appRouter.GET("list", withApi.GetList) // 获取提现列表
|
||||
appRouter.PUT("cancel", withApi.Cancel) // 取消提现请求
|
||||
}
|
||||
{
|
||||
sysRouter.GET("list", withApi.GetAdminList) // 获取提现列表
|
||||
sysRouter.PUT("", withApi.UpdateStatus) // 更新提现状态
|
||||
}
|
||||
}
|
@@ -14,11 +14,12 @@ func (s *ArticleRouter) InitBotRouter(Router *gin.RouterGroup, PublicRouter *gin
|
||||
articleRouterWithoutAuth := PublicRouter.Group("article")
|
||||
appRouter := AppRouter.Group("article")
|
||||
{
|
||||
articleRouter.POST("", artApi.Create) // 新建文章
|
||||
articleRouter.DELETE("", artApi.Delete) // 批量删除文章
|
||||
articleRouter.PUT("", artApi.Update) // 更新文章
|
||||
articleRouter.GET("list", artApi.List) // 获取文章列表
|
||||
articleRouter.GET("", artApi.ById) // 文章开放接口
|
||||
articleRouter.POST("", artApi.Create) // 新建文章
|
||||
articleRouter.DELETE("", artApi.Delete) // 批量删除文章
|
||||
articleRouter.PUT("", artApi.Update) // 更新文章
|
||||
articleRouter.GET("list", artApi.List) // 获取文章列表
|
||||
articleRouter.GET("", artApi.ById) // 文章开放接口
|
||||
articleRouter.POST("bulk", artApi.BulkUpload) // 批量新建文章
|
||||
}
|
||||
{
|
||||
articleRouterWithoutRecord.GET(":id", artApi.ById) // 根据ID获取文章
|
||||
|
@@ -23,7 +23,8 @@ func (s *CategoryRouter) InitCategoryRouter(Router *gin.RouterGroup, PublicRoute
|
||||
catRouterWithoutRecord.GET("getCategoryList", catApi.GetCategoryList) // 获取类别列表
|
||||
}
|
||||
{
|
||||
catRouterWithoutAuth.GET("getCategoryPublic", catApi.GetCategoryPublic) // 类别开放接口
|
||||
catRouterWithoutAuth.GET("/index", catApi.GetCategoryListPublic) // 获取类别列表公开接口
|
||||
catRouterWithoutAuth.GET("getCategoryPublic", catApi.GetCategoryPublic) // 类别开放接口
|
||||
catRouterWithoutAuth.GET("/index", catApi.GetCategoryListPublic) // 获取类别列表公开接口
|
||||
catRouterWithoutAuth.GET("/article/list", catApi.GetArticleCategoryList) // 获取类别列表公开接口
|
||||
}
|
||||
}
|
||||
|
@@ -7,3 +7,4 @@ import (
|
||||
type RouterGroup struct{ UserRouter }
|
||||
|
||||
var userApi = api.ApiGroupApp.UserApiGroup
|
||||
var teacherVipApi = api.ApiGroupApp.AppApiGroup.TeacherVip
|
||||
|
@@ -20,5 +20,15 @@ func (s *UserRouter) InitUserRouter(Router *gin.RouterGroup, PublicRouter *gin.R
|
||||
userRouter.GET("/teachers", userApi.GetTeachers) // 获取教师列表
|
||||
userRouter.GET("/teacherApplyList", userApi.GetTeacherApplyList) // 获取教师申请列表
|
||||
userRouter.PUT("/teacherApply/status", userApi.UpdateTeacherApplyStatus) // 更新教师申请状态
|
||||
userRouter.GET("/login/log", userApi.GetLoginLog) // 获取用户登录日志
|
||||
userRouter.DELETE("/vip", userApi.RemoveUserVip) // 删除用户会员
|
||||
userRouter.GET("/vip/list", userApi.GetUserVipList) // 获取用户会员列表
|
||||
}
|
||||
{
|
||||
userRouter.GET("teacher_vips", teacherVipApi.GetTeacherVipList) // 获取讲师VIP列表
|
||||
userRouter.GET("teacher_vip", teacherVipApi.GetTeacherVip) // 获取讲师VIP详情
|
||||
userRouter.POST("teacher_vip", teacherVipApi.CreateTeacherVip) // 创建讲师VIP
|
||||
userRouter.PUT("teacher_vip", teacherVipApi.Update) // 更新讲师VIP
|
||||
userRouter.DELETE("teacher_vip", teacherVipApi.DeleteTeacherVip) // 删除讲师VIP
|
||||
}
|
||||
}
|
||||
|
53
service/app/domain.go
Normal file
53
service/app/domain.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/lckt/global"
|
||||
"git.echol.cn/loser/lckt/model/app"
|
||||
"git.echol.cn/loser/lckt/model/app/request"
|
||||
)
|
||||
|
||||
type DomainService struct{}
|
||||
|
||||
func (s DomainService) Create(p app.Domain) error {
|
||||
return global.GVA_DB.Create(&p).Error
|
||||
}
|
||||
|
||||
func (s DomainService) GetList(p request.GetDomainList) (list []app.Domain, total int64, err error) {
|
||||
limit := p.PageSize
|
||||
offset := p.PageSize * (p.Page - 1)
|
||||
db := global.GVA_DB.Model(&app.Domain{})
|
||||
|
||||
if p.Name != "" {
|
||||
db = db.Where("name LIKE ?", "%"+p.Name+"%")
|
||||
}
|
||||
if p.Status != 0 {
|
||||
db = db.Where("status = ?", p.Status)
|
||||
}
|
||||
if p.Domain != "" {
|
||||
db = db.Where("domain_url LIKE ?", "%"+p.Domain+"%")
|
||||
}
|
||||
if p.Type != 0 {
|
||||
db = db.Where("type = ?", p.Type)
|
||||
}
|
||||
|
||||
err = db.Count(&total).Error
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = db.Limit(limit).Offset(offset).Order("created_at DESC").Find(&list).Error
|
||||
return
|
||||
}
|
||||
|
||||
func (s DomainService) Update(p app.Domain) error {
|
||||
return global.GVA_DB.Save(&p).Error
|
||||
}
|
||||
|
||||
func (s DomainService) Delete(id uint) error {
|
||||
return global.GVA_DB.Delete(&app.Domain{}, id).Error
|
||||
}
|
||||
|
||||
func (s DomainService) GetByID(id string) (domain app.Domain, err error) {
|
||||
err = global.GVA_DB.Where("id = ?", id).First(&domain).Error
|
||||
return
|
||||
}
|
@@ -4,4 +4,8 @@ type ServiceGroup struct {
|
||||
AppUserService
|
||||
BannerService
|
||||
OrderService
|
||||
TeacherVipService
|
||||
RedeemCodeService
|
||||
WithService
|
||||
DomainService
|
||||
}
|
||||
|
@@ -2,11 +2,15 @@ package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"git.echol.cn/loser/lckt/model/vip"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"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"
|
||||
userM "git.echol.cn/loser/lckt/model/user"
|
||||
"git.echol.cn/loser/lckt/utils/wechat"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
@@ -53,19 +57,39 @@ func (s *OrderService) Create(o *app.Order) (*app.Order, error) {
|
||||
|
||||
// 查询订单商品价格
|
||||
price := 0
|
||||
if o.OrderType == 1 {
|
||||
switch o.OrderType {
|
||||
case 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 {
|
||||
case 2:
|
||||
err := global.GVA_DB.Table("lckt_vip").Select("price").Where("id = ?", o.VipId).Scan(&price).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询VIP价格失败", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
case 3:
|
||||
// 讲师包月
|
||||
|
||||
//切割TeacherVipId字符串
|
||||
ids := strings.Split(o.TeacherVipId, ",")
|
||||
// 查询每个服务的价格并累加
|
||||
totalPrice := 0
|
||||
for _, id := range ids {
|
||||
var p int
|
||||
err := global.GVA_DB.Table("app_teacher_vip").Select("price").Where("id = ?", id).Scan(&p).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询讲师包月价格失败", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
totalPrice += p
|
||||
}
|
||||
|
||||
price = totalPrice
|
||||
}
|
||||
|
||||
o.Price = int64(price)
|
||||
o.Status = 1 // 设置订单状态为未付款
|
||||
// 设置openid
|
||||
@@ -107,8 +131,8 @@ func (s *OrderService) BalancePay(p request.BalancePay) error {
|
||||
return err
|
||||
}
|
||||
// 检查用户余额是否足够
|
||||
var user user.User
|
||||
err = global.GVA_DB.Where("id = ?", p.UserId).Select("id,balance").First(&user).Error
|
||||
var user userM.User
|
||||
err = global.GVA_DB.Where("id = ?", p.UserId).First(&user).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询用户信息失败", zap.Error(err))
|
||||
return err
|
||||
@@ -118,16 +142,16 @@ func (s *OrderService) BalancePay(p request.BalancePay) error {
|
||||
orderPriceInYuan := float64(order.Price) / 100.0
|
||||
|
||||
// 检查用户余额是否足够(使用float64进行比较,避免精度丢失)
|
||||
if user.Balance < float32(orderPriceInYuan) {
|
||||
if user.Balance < orderPriceInYuan {
|
||||
global.GVA_LOG.Error("用户余额不足",
|
||||
zap.Float32("balance", user.Balance),
|
||||
zap.Float64("balance", user.Balance),
|
||||
zap.Float64("order_price_yuan", orderPriceInYuan),
|
||||
zap.Int64("order_price_cent", order.Price))
|
||||
return fmt.Errorf("用户余额不足")
|
||||
}
|
||||
|
||||
// 扣除用户余额(保持精度)
|
||||
newBalance := user.Balance - float32(orderPriceInYuan)
|
||||
newBalance := user.Balance - orderPriceInYuan
|
||||
err = global.GVA_DB.Model(&user).Where("id = ?", p.UserId).Update("balance", newBalance).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("扣除用户余额失败", zap.Error(err))
|
||||
@@ -141,6 +165,87 @@ func (s *OrderService) BalancePay(p request.BalancePay) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// 全站Vip
|
||||
if order.OrderType == 2 {
|
||||
// 更新用户的会员状态
|
||||
user.IsVip = 1
|
||||
// 查询用户购买的会员信息
|
||||
vipInfo := vip.Vip{}
|
||||
err = global.GVA_DB.Model(&vip.Vip{}).Where("id = ?", order.VipId).First(&vipInfo).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询会员信息失败", zap.Error(err))
|
||||
return nil
|
||||
}
|
||||
// 计算会员的过期时间
|
||||
if user.VipExpireTime != "" {
|
||||
expireTime, _ := time.Parse("2006-01-02", user.VipExpireTime)
|
||||
if expireTime.After(time.Now()) {
|
||||
// 如果会员未过期,则在原有的基础上增加时间
|
||||
user.VipExpireTime = expireTime.AddDate(0, 0, int(vipInfo.Expiration)).Format("2006-01-02")
|
||||
} else {
|
||||
// 如果会员已过期,则从当前时间开始计算
|
||||
user.VipExpireTime = time.Now().AddDate(0, 0, int(vipInfo.Expiration)).Format("2006-01-02")
|
||||
}
|
||||
} else {
|
||||
// 如果没有会员时间,则从当前时间开始计算
|
||||
user.VipExpireTime = time.Now().AddDate(0, 0, int(vipInfo.Expiration)).Format("2006-01-02")
|
||||
}
|
||||
err = global.GVA_DB.Save(&user).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("更新用户会员状态失败", zap.Error(err))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
// 讲师包月
|
||||
if order.OrderType == 3 {
|
||||
// 逗号分割字符串
|
||||
ids := strings.Split(order.TeacherVipId, ",")
|
||||
for _, id := range ids {
|
||||
teacherVip := app.UserTeacherVip{}
|
||||
teacherVip.TeacherId = uint(order.TeacherId)
|
||||
// 将id转为uint
|
||||
teacherVipId, _ := strconv.ParseUint(id, 10, 64)
|
||||
teacherVip.TeacherVipId = uint(teacherVipId)
|
||||
teacherVip.UserId = uint(order.UserId)
|
||||
teacherVip.ExpireAt = time.Now().AddDate(0, 1, 0).Format("2006-01-02") // 会员有效期一个月
|
||||
teacherVip.IsExpire = 1 // 设置为未过期
|
||||
err = global.GVA_DB.Create(&teacherVip).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("购买讲师会员回调处理失败:", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
}
|
||||
// 计算分成比例,按比例增加讲师余额
|
||||
teacher := userM.User{}
|
||||
err = global.GVA_DB.Model(&userM.User{}).Where("id = ?", order.TeacherId).First(&teacher).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询讲师信息失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
// 计算分成金额
|
||||
amount := float64(order.Price) * float64(teacher.ExpectRate) / 100.0
|
||||
teacher.Balance = teacher.Balance + amount
|
||||
err = global.GVA_DB.Save(&teacher).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("更新讲师余额失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
}
|
||||
// 计算分成比例,按比例增加讲师余额
|
||||
teacher := userM.User{}
|
||||
err = global.GVA_DB.Model(&userM.User{}).Where("id = ?", order.TeacherId).First(&teacher).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询讲师信息失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
// 计算分成金额
|
||||
amount := float64(order.Price) * float64(teacher.ExpectRate) / 100.0
|
||||
teacher.Balance = teacher.Balance + amount
|
||||
err = global.GVA_DB.Save(&teacher).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
|
||||
}
|
||||
|
348
service/app/redeem_code.go
Normal file
348
service/app/redeem_code.go
Normal file
@@ -0,0 +1,348 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"git.echol.cn/loser/lckt/global"
|
||||
"git.echol.cn/loser/lckt/model/app"
|
||||
request2 "git.echol.cn/loser/lckt/model/app/request"
|
||||
"git.echol.cn/loser/lckt/model/article"
|
||||
"git.echol.cn/loser/lckt/model/common/request"
|
||||
"git.echol.cn/loser/lckt/model/user"
|
||||
"git.echol.cn/loser/lckt/model/vip"
|
||||
"git.echol.cn/loser/lckt/utils"
|
||||
"git.echol.cn/loser/lckt/utils/wechat"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
"time"
|
||||
)
|
||||
|
||||
type RedeemCodeService struct{}
|
||||
|
||||
// Create 创建兑换码库
|
||||
func (s RedeemCodeService) Create(p app.RedeemCode) (err error) {
|
||||
return global.GVA_DB.Create(&p).Error
|
||||
}
|
||||
|
||||
// Delete 删除兑换码库
|
||||
func (s RedeemCodeService) Delete(p app.RedeemCode) (err error) {
|
||||
return global.GVA_DB.Delete(&p).Error
|
||||
}
|
||||
|
||||
// Update 更新兑换码库
|
||||
func (s RedeemCodeService) Update(p app.RedeemCode) (err error) {
|
||||
return global.GVA_DB.Save(&p).Error
|
||||
}
|
||||
|
||||
// GetList 分页获取兑换码库列表
|
||||
func (s RedeemCodeService) GetList(p request.PageInfo) (list []app.RedeemCode, total int64, err error) {
|
||||
limit := p.PageSize
|
||||
offset := p.PageSize * (p.Page - 1)
|
||||
db := global.GVA_DB.Model(&app.RedeemCode{})
|
||||
|
||||
if p.Keyword != "" {
|
||||
db = db.Where("code_name LIKE ?", "%"+p.Keyword+"%")
|
||||
}
|
||||
|
||||
err = db.Count(&total).Error
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = db.Limit(limit).Offset(offset).Find(&list).Error
|
||||
return
|
||||
}
|
||||
|
||||
// GetById 根据ID获取兑换码库
|
||||
func (s RedeemCodeService) GetById(id int) (redeem app.RedeemCode, err error) {
|
||||
err = global.GVA_DB.Where("id = ?", id).First(&redeem).Error
|
||||
return
|
||||
}
|
||||
|
||||
// ========================CDK相关========================
|
||||
|
||||
// CreateCDK 生成兑换码
|
||||
func (s RedeemCodeService) CreateCDK(p request2.GenerateCDK) ([]app.CDK, error) {
|
||||
codes := utils.GenerateRedeemCodes(p.Number)
|
||||
var cdks []app.CDK
|
||||
for _, code := range codes {
|
||||
var cdk app.CDK
|
||||
cdk.Code = code
|
||||
cdk.RedeemId = p.Eid
|
||||
cdk.ValidDay = p.Expirer
|
||||
cdk.Status = 1
|
||||
if p.Expirer == 0 {
|
||||
cdk.ExpireAt = "永久有效"
|
||||
} else {
|
||||
cdk.ExpireAt = time.Now().AddDate(0, 0, p.Expirer).Format("2006-01-02")
|
||||
}
|
||||
|
||||
cdks = append(cdks, cdk)
|
||||
}
|
||||
|
||||
err := global.GVA_DB.Create(&cdks).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("生成兑换码失败", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 更新兑换码库的数量
|
||||
err = global.GVA_DB.Model(&app.RedeemCode{}).Where("id = ?", p.Eid).Update("num", gorm.Expr("num + ?", p.Number)).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("更新兑换码库数量失败", zap.Error(err))
|
||||
}
|
||||
|
||||
return cdks, nil
|
||||
}
|
||||
|
||||
// GetCDKList 获取兑换码列表
|
||||
func (s RedeemCodeService) GetCDKList(p request2.GetCDKList) (list []app.CDK, total int64, err error) {
|
||||
limit := p.PageSize
|
||||
offset := p.PageSize * (p.Page - 1)
|
||||
db := global.GVA_DB.Model(&app.CDK{}).Where("redeem_id = ?", p.Eid)
|
||||
|
||||
if p.Code != "" {
|
||||
db = db.Where("code LIKE ?", "%"+p.Code+"%")
|
||||
}
|
||||
|
||||
if p.UseName != "" {
|
||||
db = db.Where("use_name LIKE ?", "%"+p.UseName+"%")
|
||||
}
|
||||
if p.Status != 0 {
|
||||
db = db.Where("status = ?", p.Status)
|
||||
}
|
||||
|
||||
err = db.Count(&total).Error
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = db.Limit(limit).Offset(offset).Find(&list).Error
|
||||
return
|
||||
}
|
||||
|
||||
// DeleteCDK 删除兑换码
|
||||
func (s RedeemCodeService) DeleteCDK(p app.CDK) (err error) {
|
||||
return global.GVA_DB.Delete(&p).Error
|
||||
}
|
||||
|
||||
// Redeem 用户兑换码
|
||||
func (s RedeemCodeService) Redeem(p request2.RedeemCDK, ctx *gin.Context) (err error) {
|
||||
var cdk app.CDK
|
||||
err = global.GVA_DB.Where("code = ?", p.Code).First(&cdk).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 判断状态
|
||||
if cdk.Status != 1 {
|
||||
return errors.New("兑换码不可用")
|
||||
}
|
||||
|
||||
// 判断是否过期
|
||||
if cdk.ValidDay != 0 {
|
||||
expireAt, _ := time.Parse("2006-01-02", cdk.ExpireAt)
|
||||
if time.Now().After(expireAt) {
|
||||
// 更新状态为已过期
|
||||
cdk.Status = 3
|
||||
_ = global.GVA_DB.Save(&cdk).Error
|
||||
return errors.New("兑换码已过期")
|
||||
}
|
||||
}
|
||||
|
||||
// 获取兑换码库信息
|
||||
var redeem app.RedeemCode
|
||||
err = global.GVA_DB.Where("id = ?", cdk.RedeemId).First(&redeem).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 获取用户信息
|
||||
userId := utils.GetUserID(ctx)
|
||||
var userInfo user.User
|
||||
err = global.GVA_DB.Where("id = ?", userId).First(&userInfo).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取用户信息出错", zap.Error(err))
|
||||
}
|
||||
|
||||
// 更新兑换码状态
|
||||
cdk.Status = 2
|
||||
cdk.UseId = userInfo.ID
|
||||
cdk.UseName = userInfo.NickName
|
||||
cdk.UseAt = time.Now().Format("2006-01-02 15:04:05")
|
||||
err = global.GVA_DB.Save(&cdk).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("更新兑换码状态失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
// 更新兑换码库已使用数量
|
||||
err = global.GVA_DB.Model(&app.RedeemCode{}).Where("id = ?", cdk.RedeemId).Update("no", gorm.Expr("no + ?", 1)).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("更新兑换码库已使用数量失败", zap.Error(err))
|
||||
}
|
||||
|
||||
// 发放对应的权益
|
||||
switch redeem.Type {
|
||||
case 1:
|
||||
// VIP
|
||||
var vipInfo vip.Vip
|
||||
err = global.GVA_DB.Where("id = ?", redeem.Item).First(&vipInfo).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
vipLevel := 1
|
||||
if vipInfo.Level == 1 {
|
||||
vipLevel = 2
|
||||
} else if vipInfo.Level == 2 {
|
||||
vipLevel = 3
|
||||
}
|
||||
|
||||
// 判断用户是否已经是VIP
|
||||
if userInfo.IsVip == 1 {
|
||||
// 是VIP,判断会员等级
|
||||
if userInfo.UserLabel == int64(vipLevel) {
|
||||
// 等级相同,延长会员时间
|
||||
if userInfo.VipExpireTime != "" {
|
||||
expireTime, _ := time.Parse("2006-01-02", userInfo.VipExpireTime)
|
||||
if expireTime.After(time.Now()) {
|
||||
// 如果会员未过期,则在原有的基础上增加时间
|
||||
userInfo.VipExpireTime = expireTime.AddDate(0, 0, int(vipInfo.Expiration)).Format("2006-01-02")
|
||||
} else {
|
||||
// 如果会员已过期,则从当前时间开始计算
|
||||
userInfo.VipExpireTime = time.Now().AddDate(0, 0, int(vipInfo.Expiration)).Format("2006-01-02")
|
||||
}
|
||||
} else {
|
||||
// 如果没有会员时间,则从当前时间开始计算
|
||||
userInfo.VipExpireTime = time.Now().AddDate(0, 0, int(vipInfo.Expiration)).Format("2006-01-02")
|
||||
}
|
||||
err = global.GVA_DB.Save(&userInfo).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("更新用户VIP信息失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
}
|
||||
if userInfo.UserLabel < int64(vipLevel) {
|
||||
// 等级更高,直接覆盖
|
||||
userInfo.UserLabel = int64(vipLevel)
|
||||
userInfo.VipExpireTime = time.Now().AddDate(0, 0, int(vipInfo.Expiration)).Format("2006-01-02")
|
||||
err = global.GVA_DB.Save(&userInfo).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("更新用户VIP信息失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 不是VIP,直接设置为VIP
|
||||
userInfo.IsVip = 1
|
||||
userInfo.UserLabel = int64(vipLevel)
|
||||
userInfo.VipExpireTime = time.Now().AddDate(0, 0, int(vipInfo.Expiration)).Format("2006-01-02")
|
||||
err = global.GVA_DB.Save(&userInfo).Error
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
case 2:
|
||||
// 讲师VIP
|
||||
var teacherVip app.TeacherVip
|
||||
err = global.GVA_DB.Where("id = ?", redeem.Item).First(&teacherVip).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 判断是否已经拥有该讲师VIP
|
||||
var existingTeacherVip app.UserTeacherVip
|
||||
err = global.GVA_DB.Where("user_id = ? AND teacher_vip_id = ?", userInfo.ID, teacherVip.ID).First(&existingTeacherVip).Error
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
global.GVA_LOG.Error("获取用户讲师VIP信息出错", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
// 判断是否为空
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
userTeacherVip := app.UserTeacherVip{
|
||||
UserId: userInfo.ID,
|
||||
TeacherVipId: teacherVip.ID,
|
||||
TeacherId: teacherVip.TeacherId,
|
||||
IsExpire: 1,
|
||||
ExpireAt: time.Now().AddDate(0, 0, 30).Format("2006-01-02"),
|
||||
}
|
||||
err = global.GVA_DB.Create(&userTeacherVip).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("兑换讲师包月服务失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 默认未过期,获取过期时间+30天
|
||||
// 将 existingTeacherVip.ExpireAt转为time.Time类型
|
||||
parse, err := time.Parse("2006-01-02", existingTeacherVip.ExpireAt)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("转换过期时间出错", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
// 已过期 当前时间开始计算+30天
|
||||
if existingTeacherVip.IsExpire == 2 && time.Now().After(parse) {
|
||||
existingTeacherVip.IsExpire = 1
|
||||
existingTeacherVip.ExpireAt = time.Now().AddDate(0, 0, 30).Format("2006-01-02")
|
||||
} else {
|
||||
// 未过期 在原有时间上+30天
|
||||
existingTeacherVip.ExpireAt = parse.AddDate(0, 0, 30).Format("2006-01-02")
|
||||
}
|
||||
|
||||
err = global.GVA_DB.Save(&existingTeacherVip).Error
|
||||
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("兑换讲师包月服务失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
case 3:
|
||||
// 课程
|
||||
var course article.Article
|
||||
err = global.GVA_DB.Where("id = ?", redeem.Item).First(&course).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 判断用户是否购买过该课程
|
||||
var count int64
|
||||
err = global.GVA_DB.Model(&app.Order{}).Where("article_id = ? AND user_id = ? AND status = 2", course.ID, userInfo.ID).Count(&count).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询用户购买记录失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
// 未购买,新增购买记录
|
||||
order := app.Order{
|
||||
UserId: uint64(userInfo.ID),
|
||||
Name: userInfo.NickName,
|
||||
OpenId: userInfo.OpenId,
|
||||
Phone: userInfo.Phone,
|
||||
TeacherId: uint64(course.TeacherId),
|
||||
ArticleId: course.ID,
|
||||
Price: 0,
|
||||
Status: 2,
|
||||
OrderNo: wechat.GenerateOrderNum(),
|
||||
OrderType: 1,
|
||||
Title: course.Title,
|
||||
Desc: "兑换码兑换" + course.Title,
|
||||
PayType: 4,
|
||||
}
|
||||
err = global.GVA_DB.Create(&order).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("兑换课程失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// 已购买,直接返回
|
||||
return errors.New("您已购买过该课程,无需重复兑换")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
100
service/app/teacher_vip.go
Normal file
100
service/app/teacher_vip.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"git.echol.cn/loser/lckt/global"
|
||||
"git.echol.cn/loser/lckt/model/app"
|
||||
"git.echol.cn/loser/lckt/model/app/request"
|
||||
user2 "git.echol.cn/loser/lckt/model/user"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type TeacherVipService struct{}
|
||||
|
||||
// GetTeacherVipList 获取讲师包月列表
|
||||
func (u *TeacherVipService) GetTeacherVipList(p request.GetTeacherVipList) (list []app.TeacherVip, total int64, err error) {
|
||||
limit := p.PageSize
|
||||
offset := (p.Page - 1) * p.PageSize
|
||||
|
||||
db := global.GVA_DB.Model(&app.TeacherVip{})
|
||||
if p.TeacherId != 0 {
|
||||
db.Where("teacher_id = ? ", p.TeacherId)
|
||||
}
|
||||
|
||||
if p.Keyword != "" {
|
||||
db = db.Where("title LIKE ?", "%"+p.Keyword+"%")
|
||||
}
|
||||
|
||||
err = db.Count(&total).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询讲师包月总数失败", zap.Error(err))
|
||||
return nil, 0, err
|
||||
}
|
||||
err = db.Limit(limit).Offset(offset).Find(&list).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询讲师包月列表失败", zap.Error(err))
|
||||
return nil, 0, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (u *TeacherVipService) CreateTeacherVip(p app.TeacherVip, userId uint) (err error) {
|
||||
// 判断是否是讲师
|
||||
var user user2.User
|
||||
err = global.GVA_DB.Where("id = ?", userId).First(&user).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询用户信息失败:", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
if user.UserType != 2 {
|
||||
// 不是讲师
|
||||
global.GVA_LOG.Error("当前用户不是讲师,无法创建讲师VIP")
|
||||
return errors.New("当前用户不是讲师,无法创建讲师VIP")
|
||||
}
|
||||
p.Avatar = user.Avatar
|
||||
|
||||
err = global.GVA_DB.Create(&p).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("创建讲师VIP失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *TeacherVipService) DeleteTeacherVip(p app.TeacherVip) (err error) {
|
||||
err = global.GVA_DB.Delete(&p).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("删除讲师VIP失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *TeacherVipService) Update(p app.TeacherVip) (err error) {
|
||||
// 查询讲师头像
|
||||
avatar := ""
|
||||
err = global.GVA_DB.Model(&user2.User{}).Where("id = ?", p.TeacherId).Pluck("avatar", &avatar).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询讲师头像失败", zap.Error(err))
|
||||
}
|
||||
p.Avatar = avatar
|
||||
|
||||
err = global.GVA_DB.Save(&p).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("更新讲师VIP失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *TeacherVipService) GetTeacherVip(id int) (vip app.TeacherVip, err error) {
|
||||
err = global.GVA_DB.Where("id = ?", id).First(&vip).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取讲师VIP失败", zap.Error(err))
|
||||
return vip, err
|
||||
}
|
||||
return vip, nil
|
||||
}
|
||||
|
||||
// ===========================管理后台接口===========================
|
@@ -79,6 +79,11 @@ func (u *AppUserService) WechatLogin(info *providers.User) (users user.User, err
|
||||
global.GVA_LOG.Error("查询用户失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
if users.Status != 1 {
|
||||
err = fmt.Errorf("用户已被封禁")
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -244,5 +249,192 @@ func (u *AppUserService) GetTeacherList(p common.PageInfo) (list []vo.TeacherInf
|
||||
global.GVA_LOG.Error("查询教师列表失败", zap.Error(err))
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 获取每个教师的粉丝数
|
||||
for i := range list {
|
||||
followCount, err := u.GetTeacherFansCount(list[i].ID)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询教师粉丝数失败", zap.Error(err))
|
||||
return nil, 0, err
|
||||
}
|
||||
list[i].Follow = followCount
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (u *AppUserService) GetFollowTeacherList(id uint, p common.PageInfo) (list []vo.TeacherInfo, total int64, err error) {
|
||||
limit := p.PageSize
|
||||
offset := (p.Page - 1) * p.PageSize
|
||||
|
||||
// 1. 获取用户关注的教师ID列表
|
||||
var followings []app.Follow
|
||||
err = global.GVA_DB.Model(&app.Follow{}).Where("user_id = ?", id).Find(&followings).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询关注列表失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
if len(followings) == 0 {
|
||||
return
|
||||
}
|
||||
var teacherIDs []uint
|
||||
for _, f := range followings {
|
||||
teacherIDs = append(teacherIDs, f.TeacherId)
|
||||
}
|
||||
|
||||
// 2. 根据教师ID列表获取教师信息
|
||||
db := global.GVA_DB.Model(&user.User{}).Where("id IN ?", teacherIDs)
|
||||
|
||||
if p.Keyword != "" {
|
||||
db = db.Where("nick_name LIKE ?", "%"+p.Keyword+"%")
|
||||
}
|
||||
|
||||
err = db.Count(&total).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询关注教师总数失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
err = db.Limit(limit).Offset(offset).Select("id, nick_name, avatar,des").Find(&list).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询关注教师列表失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
// 获取每个教师的粉丝数
|
||||
for i := range list {
|
||||
followCount, err := u.GetTeacherFansCount(list[i].ID)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询教师粉丝数失败", zap.Error(err))
|
||||
return nil, 0, err
|
||||
}
|
||||
list[i].Follow = followCount
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// FollowTeacher 用户关注/取消关注教师
|
||||
func (u *AppUserService) FollowTeacher(p app.Follow) error {
|
||||
var follow app.Follow
|
||||
// 用 Unscoped 查询所有记录(包括软删除)
|
||||
db := global.GVA_DB.Unscoped().Where("user_id = ? AND teacher_id = ?", p.UserId, p.TeacherId)
|
||||
err := db.First(&follow).Error
|
||||
if err == nil {
|
||||
if follow.DeletedAt.Valid {
|
||||
// 已软删除,恢复关注
|
||||
err = global.GVA_DB.Model(&app.Follow{}).Unscoped().Where("id = ?", follow.ID).Update("deleted_at", nil).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("恢复关注失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
return nil // 恢复关注成功
|
||||
} else {
|
||||
// 已关注,取消关注
|
||||
err = global.GVA_DB.Where("user_id = ? AND teacher_id = ?", p.UserId, p.TeacherId).Delete(&app.Follow{}).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("取消关注失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
return nil // 取消关注成功
|
||||
}
|
||||
} else if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
// 没有任何记录,直接关注
|
||||
err = global.GVA_DB.Create(&p).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("关注教师失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
return nil // 关注成功
|
||||
} else {
|
||||
global.GVA_LOG.Error("查询关注记录失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// GetTeacherFansCount 获取讲师粉丝数
|
||||
func (u *AppUserService) GetTeacherFansCount(teacherId uint) (int64, error) {
|
||||
var count int64
|
||||
err := global.GVA_DB.Model(&app.Follow{}).Where("teacher_id = ?", teacherId).Count(&count).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("统计粉丝数失败", zap.Error(err))
|
||||
return 0, err
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// IsFollowTeacher 判断用户是否关注某讲师
|
||||
func (u *AppUserService) IsFollowTeacher(userId, teacherId uint) (bool, error) {
|
||||
var count int64
|
||||
err := global.GVA_DB.Model(&app.Follow{}).
|
||||
Where("user_id = ? AND teacher_id = ?", userId, teacherId).
|
||||
Count(&count).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询关注状态失败", zap.Error(err))
|
||||
return false, err
|
||||
}
|
||||
return count > 0, nil
|
||||
}
|
||||
|
||||
// GetVipTeacherList 获取用户购买的讲师VIP列表
|
||||
func (u *AppUserService) GetVipTeacherList(p common.PageInfo, userId uint) (list []vo.TeacherInfo, total int64, err error) {
|
||||
limit := p.PageSize
|
||||
offset := (p.Page - 1) * p.PageSize
|
||||
|
||||
// 1. 获取所有购买了讲师VIP的讲师ID
|
||||
var vipTeacherIds []uint
|
||||
err = global.GVA_DB.Model(&app.UserTeacherVip{}).Where("user_id = ? and is_expire = 1", userId).Select("teacher_id").Scan(&vipTeacherIds).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取用户讲师包月信息失败:", zap.Error(err))
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
db := global.GVA_DB.Model(&user.User{}).Where("user_type = ? and id in ?", 2, vipTeacherIds)
|
||||
|
||||
if p.Keyword != "" {
|
||||
db = db.Where("nick_name LIKE ?", "%"+p.Keyword+"%")
|
||||
}
|
||||
|
||||
err = db.Count(&total).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询教师总数失败", zap.Error(err))
|
||||
return nil, 0, err
|
||||
}
|
||||
err = db.Limit(limit).Offset(offset).Select("id, nick_name, avatar,des").Find(&list).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询教师列表失败", zap.Error(err))
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 获取每个教师的粉丝数
|
||||
for i := range list {
|
||||
followCount, err := u.GetTeacherFansCount(list[i].ID)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询教师粉丝数失败", zap.Error(err))
|
||||
return nil, 0, err
|
||||
}
|
||||
list[i].Follow = followCount
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetBalanceLog 获取用户余额变动日志
|
||||
func (u *AppUserService) GetBalanceLog(id uint, p common.PageInfo) (list []app.BalanceLog, total int64, err error) {
|
||||
limit := p.PageSize
|
||||
offset := (p.Page - 1) * p.PageSize
|
||||
|
||||
db := global.GVA_DB.Model(&app.BalanceLog{}).Where("user_id = ?", id)
|
||||
|
||||
err = db.Count(&total).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询余额变动总数失败", zap.Error(err))
|
||||
return nil, 0, err
|
||||
}
|
||||
err = db.Limit(limit).Offset(offset).Order("created_at desc").Find(&list).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询余额变动列表失败", zap.Error(err))
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
164
service/app/with.go
Normal file
164
service/app/with.go
Normal file
@@ -0,0 +1,164 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"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"
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
"time"
|
||||
)
|
||||
|
||||
// WithService 提现服务
|
||||
type WithService struct{}
|
||||
|
||||
func (s WithService) Create(p app.With) (err error) {
|
||||
err = global.GVA_DB.Create(&p).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("创建提现请求失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
// 扣除用户余额
|
||||
err = global.GVA_DB.Model(&user.User{}).Where("id = ?", p.UserID).Update("balance", gorm.Expr("balance - ?", p.Amount)).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("扣除用户余额失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s WithService) GetList(p request.GetWithList) (list []app.With, total int64, err error) {
|
||||
limit := p.PageSize
|
||||
offset := p.PageSize * (p.Page - 1)
|
||||
db := global.GVA_DB.Model(&app.With{})
|
||||
|
||||
if p.UserId != 0 {
|
||||
db = db.Where("user_id = ?", p.UserId)
|
||||
}
|
||||
if p.UserName != "" {
|
||||
db = db.Joins("JOIN app_user ON app_user.id = app_with.user_id").Where("app_user.user_name LIKE ?", "%"+p.UserName+"%")
|
||||
}
|
||||
if p.Status != 0 {
|
||||
db = db.Where("status = ?", p.Status)
|
||||
}
|
||||
if p.StartTime != "" {
|
||||
db = db.Where("created_at >= ?", p.StartTime)
|
||||
}
|
||||
if p.EndTime != "" {
|
||||
db = db.Where("created_at <= ?", p.EndTime)
|
||||
}
|
||||
|
||||
err = db.Count(&total).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取提现列表总数失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
err = db.Limit(limit).Offset(offset).Order("created_at DESC").Find(&list).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取提现列表失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s WithService) UpdateStatus(p app.With) (err error) {
|
||||
var userInfo user.User
|
||||
err = global.GVA_DB.Model(&user.User{}).Where("id = ?", p.UserID).First(&userInfo).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询用户信息失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
if p.Status == 2 {
|
||||
// 通过申请
|
||||
var with app.With
|
||||
err = global.GVA_DB.Model(&app.With{}).Where("id = ?", p.ID).First(&with).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询提现请求失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
if with.Status != 1 {
|
||||
global.GVA_LOG.Error("提现请求状态错误,无法通过", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
with.Status = 2
|
||||
with.ArriveTime = time.Now().Format("2006-01-02 15:04:05")
|
||||
with.RealAmount = p.RealAmount
|
||||
err = global.GVA_DB.Save(&with).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("更新提现请求状态失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
// 增加余额变更记录
|
||||
var record app.BalanceLog
|
||||
record.UserID = with.UserID
|
||||
record.ChangeType = 2 // 提现
|
||||
record.ChangeValue = with.Amount
|
||||
record.Balance = userInfo.Balance
|
||||
|
||||
err = global.GVA_DB.Create(&record).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("创建余额变更记录失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
if p.Status == 3 {
|
||||
// 拒绝申请,退还金额
|
||||
var with app.With
|
||||
err = global.GVA_DB.Model(&app.With{}).Where("id = ?", p.ID).First(&with).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询提现请求失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
if with.Status != 1 {
|
||||
global.GVA_LOG.Error("提现请求状态错误,无法拒绝", zap.Error(err))
|
||||
return
|
||||
}
|
||||
with.Status = 3
|
||||
with.Remark = p.Remark
|
||||
// 退还金额
|
||||
err = global.GVA_DB.Model(&user.User{}).Where("id = ?", with.UserID).Update("balance", gorm.Expr("balance + ?", with.Amount)).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("退还用户余额失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
err = global.GVA_DB.Save(&with).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("更新提现请求状态失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
}
|
||||
if p.Status == 4 {
|
||||
// 用户取消申请,退还金额
|
||||
var with app.With
|
||||
err = global.GVA_DB.Model(&app.With{}).Where("id = ?", p.ID).First(&with).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询提现请求失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
if with.Status != 1 {
|
||||
global.GVA_LOG.Error("提现请求状态错误,无法取消", zap.Error(err))
|
||||
return
|
||||
}
|
||||
with.Status = 4
|
||||
// 退还金额
|
||||
err = global.GVA_DB.Model(&user.User{}).Where("id = ?", with.UserID).Update("balance", gorm.Expr("balance + ?", with.Amount)).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("退还用户余额失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
err = global.GVA_DB.Save(&with).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("更新提现请求状态失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
@@ -1,12 +1,16 @@
|
||||
package article
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"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"
|
||||
"git.echol.cn/loser/lckt/model/user"
|
||||
"go.uber.org/zap"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ArticleService struct{}
|
||||
@@ -25,6 +29,7 @@ func (ArticleService) UpdateArticle(req article.Article) (err error) {
|
||||
err = global.GVA_DB.Model(&article.Article{}).Where("id = ?", req.ID).Updates(&req).Error
|
||||
return
|
||||
}
|
||||
|
||||
func (ArticleService) GetArticle(id string) (article article.Article, err error) {
|
||||
err = global.GVA_DB.Where("id = ?", id).First(&article).Error
|
||||
return
|
||||
@@ -199,3 +204,57 @@ func (s ArticleService) AppDelete(id string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s ArticleService) BulkUpload(p request.BulkUpload) (err error) {
|
||||
var articles []article.Article
|
||||
var failedFiles []string
|
||||
|
||||
for _, a := range p.Files {
|
||||
teacher := user.User{}
|
||||
if dbErr := global.GVA_DB.Model(&teacher).Where("nick_name = ?", getTeacherName(a)).First(&teacher).Error; dbErr != nil {
|
||||
global.GVA_LOG.Error("获取讲师信息失败", zap.Error(dbErr))
|
||||
failedFiles = append(failedFiles, a)
|
||||
continue
|
||||
}
|
||||
content := "<p><img src=" + a + " alt=\"" + a + "\" data-href=\"\" style=\"width: 100%;height: auto;\"/></p>"
|
||||
|
||||
// 将 p.PublishTime转为time.time类型
|
||||
loc, _ := time.LoadLocation("Asia/Shanghai")
|
||||
publishTime, _ := time.ParseInLocation("2006-01-02 15:04:05", p.PublishTime, loc)
|
||||
|
||||
articles = append(articles, article.Article{
|
||||
Title: p.Title,
|
||||
Desc: p.Desc,
|
||||
CategoryId: p.CategoryId,
|
||||
TeacherId: int(teacher.ID),
|
||||
TeacherName: teacher.NickName,
|
||||
CoverImg: teacher.Avatar,
|
||||
Content: content,
|
||||
IsFree: p.IsFree,
|
||||
Price: int64(p.Price),
|
||||
PublishTime: &publishTime,
|
||||
})
|
||||
}
|
||||
|
||||
if len(articles) > 0 {
|
||||
if dbErr := global.GVA_DB.Create(&articles).Error; dbErr != nil {
|
||||
global.GVA_LOG.Error("批量上传文章失败", zap.Error(dbErr))
|
||||
return dbErr
|
||||
}
|
||||
}
|
||||
|
||||
if len(failedFiles) > 0 {
|
||||
global.GVA_LOG.Error("部分文件上传失败", zap.Strings("failedFiles", failedFiles))
|
||||
return fmt.Errorf("部分文件上传失败: %v", failedFiles)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getTeacherName(url string) string {
|
||||
lastSlash := strings.LastIndex(url, "/")
|
||||
underscore := strings.Index(url[lastSlash+1:], "_")
|
||||
if lastSlash == -1 || underscore == -1 {
|
||||
return ""
|
||||
}
|
||||
return url[lastSlash+1 : lastSlash+1+underscore]
|
||||
}
|
||||
|
@@ -86,10 +86,20 @@ 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
|
||||
err = global.GVA_DB.Model(&category.Category{}).Where("categories.index = 1 and categories.is_article=0").Find(&list).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取首页分类失败", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetArticleCategoryList 获取文章分类
|
||||
func (catService *CategoryService) GetArticleCategoryList() (list []category.Category, err error) {
|
||||
err = global.GVA_DB.Model(&category.Category{}).Where("categories.is_article = 1").Find(&list).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取文章分类失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@@ -101,6 +101,9 @@ func (e *FileUploadAndDownloadService) UploadFile(header *multipart.FileHeader,
|
||||
if classId == 2 {
|
||||
header.Filename = utils.GenerateRandomString(12) + "_" + strconv.FormatInt(time.Now().Unix(), 10) + "." + s[len(s)-1]
|
||||
}
|
||||
if classId == 3 {
|
||||
header.Filename = s[0] + "_" + utils.GenerateRandomString(12) + "_" + strconv.FormatInt(time.Now().Unix(), 10) + "." + s[len(s)-1]
|
||||
}
|
||||
filePath, key, uploadErr := oss.UploadFile(header)
|
||||
if uploadErr != nil {
|
||||
return file, uploadErr
|
||||
|
@@ -93,9 +93,47 @@ func (u *UserService) GetUserList(p request.GetUserListReq) (userList []user.Use
|
||||
|
||||
// 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
|
||||
// 1. 查询用户
|
||||
var userInfo user.User
|
||||
err = global.GVA_DB.Model(&userInfo).Where("id = ?", p.Id).First(&userInfo).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("设置用户余额失败", zap.Error(err))
|
||||
global.GVA_LOG.Error("查询用户失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
if p.ChangeType == 1 {
|
||||
// 增加
|
||||
userInfo.Balance += p.Balance
|
||||
err = global.GVA_DB.Save(&userInfo).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("增加用户余额失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// 减少
|
||||
if userInfo.Balance < p.Balance {
|
||||
// 余额不足
|
||||
err = fmt.Errorf("用户余额不足")
|
||||
return
|
||||
}
|
||||
userInfo.Balance -= p.Balance
|
||||
err = global.GVA_DB.Save(&userInfo).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("减少用户余额失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 记录日志
|
||||
balanceLog := app.BalanceLog{
|
||||
UserID: uint(p.Id),
|
||||
ChangeType: p.ChangeType,
|
||||
ChangeValue: p.Balance,
|
||||
Balance: userInfo.Balance,
|
||||
}
|
||||
err = global.GVA_DB.Create(&balanceLog).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("记录用户余额变动日志失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
return
|
||||
@@ -165,6 +203,7 @@ func (u *UserService) SetUserStatus(id string) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// GetTeachers 获取教师列表
|
||||
func (u *UserService) GetTeachers(p common.PageInfo) (list []vo.UserInfo, total int64, err error) {
|
||||
limit := p.PageSize
|
||||
offset := p.PageSize * (p.Page - 1)
|
||||
@@ -180,6 +219,7 @@ func (u *UserService) GetTeachers(p common.PageInfo) (list []vo.UserInfo, total
|
||||
return
|
||||
}
|
||||
|
||||
// GetTeacherApplyList 获取教师申请列表
|
||||
func (u *UserService) GetTeacherApplyList(p request.GetTeacherApplyListReq) (list []app.TeacherApply, total int64, err error) {
|
||||
query := global.GVA_DB.Model(&app.TeacherApply{})
|
||||
|
||||
@@ -204,6 +244,7 @@ func (u *UserService) GetTeacherApplyList(p request.GetTeacherApplyListReq) (lis
|
||||
return
|
||||
}
|
||||
|
||||
// UpdateTeacherApplyStatus 更新教师申请状态
|
||||
func (u *UserService) UpdateTeacherApplyStatus(p app.TeacherApply) (err error) {
|
||||
err = global.GVA_DB.Updates(&p).Error
|
||||
if err != nil {
|
||||
@@ -229,6 +270,7 @@ func (u *UserService) UpdateTeacherApplyStatus(p app.TeacherApply) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// SetUserVip 设置用户为VIP
|
||||
func (u *UserService) SetUserVip(p request.SetUserVipReq) error {
|
||||
var user user.User
|
||||
err := global.GVA_DB.Model(&user).Where("id = ?", p.Id).First(&user).Error
|
||||
@@ -248,3 +290,76 @@ func (u *UserService) SetUserVip(p request.SetUserVipReq) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetLoginLog 获取登录日志
|
||||
func (u *UserService) GetLoginLog(p request.GetUserListReq) (list []user.LoginLog, total int64, err error) {
|
||||
limit := p.PageSize
|
||||
offset := (p.Page - 1) * p.PageSize
|
||||
db := global.GVA_DB.Model(&user.LoginLog{})
|
||||
|
||||
if p.UserId != 0 {
|
||||
db = db.Where("user_id = ?", p.UserId)
|
||||
}
|
||||
if p.Name != "" {
|
||||
db = db.Where("user_name LIKE ?", "%"+p.Name+"%")
|
||||
}
|
||||
err = db.Count(&total).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询登录日志总数失败", zap.Error(err))
|
||||
return nil, 0, err
|
||||
}
|
||||
err = db.Limit(limit).Offset(offset).Order("created_at desc").Find(&list).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询登录日志列表失败", zap.Error(err))
|
||||
return nil, 0, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// RemoveUserVip 移除用户VIP
|
||||
func (u *UserService) RemoveUserVip(id int) error {
|
||||
var 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 err
|
||||
}
|
||||
|
||||
user.IsVip = 0
|
||||
user.UserLabel = 1
|
||||
user.VipExpireTime = ""
|
||||
|
||||
err = global.GVA_DB.Save(&user).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("移除用户VIP失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetUserVipList 获取用户会员列表
|
||||
func (u *UserService) GetUserVipList(p request.GetUserListReq) (list []vo.UserInfo, total int64, err error) {
|
||||
limit := p.PageSize
|
||||
offset := (p.Page - 1) * p.PageSize
|
||||
db := global.GVA_DB.Model(&user.User{}).Where("is_vip = ? and status = 1", 1)
|
||||
|
||||
if p.Name != "" {
|
||||
db = db.Where("nick_name LIKE ?", "%"+p.Name+"%")
|
||||
}
|
||||
|
||||
if p.UserId != 0 {
|
||||
db = db.Where("id = ?", p.UserId)
|
||||
}
|
||||
|
||||
err = db.Count(&total).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询用户会员总数失败", zap.Error(err))
|
||||
return nil, 0, err
|
||||
}
|
||||
err = db.Limit(limit).Offset(offset).Order("created_at desc").Find(&list).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询用户会员列表失败", zap.Error(err))
|
||||
return nil, 0, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
25
task/checkVip.go
Normal file
25
task/checkVip.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/lckt/global"
|
||||
"git.echol.cn/loser/lckt/model/user"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// CheckVip 检查用户VIP是否过期
|
||||
func CheckVip(db *gorm.DB) error {
|
||||
global.GVA_LOG.Info("开始检查用户VIP是否过期...")
|
||||
var users []user.User
|
||||
// 根据当前时间和vip_expire_time对比 查看是否到过期时间
|
||||
db.Where("vip_expire_time < ? AND vip_expire_time IS NOT NULL", gorm.Expr("NOW()")).Find(&users)
|
||||
for _, u := range users {
|
||||
u.VipExpireTime = ""
|
||||
u.IsVip = 0
|
||||
u.UserLabel = 4
|
||||
err := db.Save(&u).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
24
task/clearOrder.go
Normal file
24
task/clearOrder.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/lckt/global"
|
||||
"git.echol.cn/loser/lckt/model/app"
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ClearExpiredOrders 定时清理超时订单(5分钟过期)
|
||||
func ClearExpiredOrders(db *gorm.DB) error {
|
||||
global.GVA_LOG.Info("开始清理超时订单...")
|
||||
fiveMinutesAgo := time.Now().Add(-5 * time.Minute)
|
||||
result := db.Model(&app.Order{}).
|
||||
Where("status = 1 AND created_at < ?", fiveMinutesAgo).
|
||||
Updates(map[string]interface{}{"status": 3})
|
||||
if result.Error != nil {
|
||||
global.GVA_LOG.Error("清理超时订单失败", zap.Error(result.Error))
|
||||
return result.Error
|
||||
}
|
||||
global.GVA_LOG.Info("清理超时订单成功", zap.Int64("处理订单数", result.RowsAffected))
|
||||
return nil
|
||||
}
|
24
task/publishArticcles.go
Normal file
24
task/publishArticcles.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/lckt/global"
|
||||
"git.echol.cn/loser/lckt/model/article"
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
"time"
|
||||
)
|
||||
|
||||
// PublishArticles 定时发布文章
|
||||
func PublishArticles(db *gorm.DB) error {
|
||||
global.GVA_LOG.Info("开始执行定时发布文章任务")
|
||||
now := time.Now()
|
||||
result := db.Model(&article.Article{}).
|
||||
Where("publish_time <= ? AND status != 1", now).
|
||||
Updates(map[string]interface{}{"status": 1})
|
||||
if result.Error != nil {
|
||||
global.GVA_LOG.Error("定时发布文章失败", zap.Error(result.Error))
|
||||
return result.Error
|
||||
}
|
||||
global.GVA_LOG.Info("定时发布文章成功", zap.Int64("处理文章数", result.RowsAffected))
|
||||
return nil
|
||||
}
|
@@ -3,6 +3,11 @@ package test
|
||||
import (
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"git.echol.cn/loser/lckt/core"
|
||||
"git.echol.cn/loser/lckt/global"
|
||||
"git.echol.cn/loser/lckt/initialize"
|
||||
"git.echol.cn/loser/lckt/task"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
@@ -71,3 +76,29 @@ func sendCode(code string) {
|
||||
fmt.Println("请求失败,状态码:", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTime(t *testing.T) {
|
||||
// 默认未过期,获取过期时间+30天
|
||||
// 将 existingTeacherVip.ExpireAt转为time.Time类型
|
||||
parse, err := time.Parse("2006-01-02", "2025-09-10")
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("转换过期时间出错", zap.Error(err))
|
||||
return
|
||||
}
|
||||
after := time.Now().After(parse)
|
||||
fmt.Println(after)
|
||||
}
|
||||
|
||||
func TestTask(t *testing.T) {
|
||||
global.GVA_VP = core.Viper() // 初始化Viper
|
||||
global.GVA_LOG = core.Zap() // 初始化zap日志库
|
||||
zap.ReplaceGlobals(global.GVA_LOG)
|
||||
global.GVA_DB = initialize.Gorm() // gorm连接数据库
|
||||
initialize.DBList()
|
||||
err := task.CheckVip(global.GVA_DB)
|
||||
if err != nil {
|
||||
fmt.Println("清理表失败", err.Error())
|
||||
} else {
|
||||
fmt.Println("清理表成功")
|
||||
}
|
||||
}
|
||||
|
29
utils/ip.go
Normal file
29
utils/ip.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ipAdcodeResp struct {
|
||||
Adcode struct {
|
||||
O string `json:"o"`
|
||||
} `json:"adcode"`
|
||||
}
|
||||
|
||||
func GetIPAdcode(ip string) string {
|
||||
url := fmt.Sprintf("https://api.vore.top/api/IPdata?ip=%s", ip)
|
||||
client := &http.Client{Timeout: 5 * time.Second}
|
||||
resp, err := client.Get(url)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
var result ipAdcodeResp
|
||||
if err = json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
return ""
|
||||
}
|
||||
return result.Adcode.O
|
||||
}
|
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
|
||||
// GenerateInviteCode 生成邀请码,基于用户ID和随机数的MD5哈希
|
||||
func GenerateInviteCode(userID uint) string {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
// 拼接用户ID和随机数
|
||||
@@ -34,3 +35,28 @@ func GenerateRandomString(length int) string {
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
// GenerateRedeemCode 生成单个兑换码,格式为N7DY4kcf5z37hwz,随机大小写字母+数字
|
||||
func GenerateRedeemCode() string {
|
||||
const codeCharset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
codeLen := 15
|
||||
b := make([]byte, codeLen)
|
||||
for i := range b {
|
||||
b[i] = codeCharset[rand.Intn(len(codeCharset))]
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// GenerateRedeemCodes 批量生成唯一兑换码,number为生成数量
|
||||
func GenerateRedeemCodes(number int) []string {
|
||||
codesMap := make(map[string]struct{}, number)
|
||||
for len(codesMap) < number {
|
||||
code := GenerateRedeemCode()
|
||||
codesMap[code] = struct{}{}
|
||||
}
|
||||
codes := make([]string, 0, number)
|
||||
for code := range codesMap {
|
||||
codes = append(codes, code)
|
||||
}
|
||||
return codes
|
||||
}
|
||||
|
@@ -4,6 +4,8 @@ import (
|
||||
gfmt "fmt"
|
||||
"git.echol.cn/loser/lckt/global"
|
||||
"git.echol.cn/loser/lckt/model/app"
|
||||
"git.echol.cn/loser/lckt/model/user"
|
||||
"git.echol.cn/loser/lckt/model/vip"
|
||||
"github.com/ArtisanCloud/PowerLibs/v3/fmt"
|
||||
"github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/models"
|
||||
"github.com/ArtisanCloud/PowerWeChat/v3/src/payment"
|
||||
@@ -13,6 +15,8 @@ import (
|
||||
"go.uber.org/zap"
|
||||
"log"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -198,11 +202,100 @@ func NotifyHandle(ctx *gin.Context) error {
|
||||
return nil
|
||||
}
|
||||
order.Status = 2 // 设置订单状态为已支付
|
||||
err = global.GVA_DB.Save(&order).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("更新订单状态失败", zap.Error(err))
|
||||
return nil
|
||||
|
||||
switch order.OrderType {
|
||||
case 1: // 课程订单
|
||||
err = global.GVA_DB.Save(&order).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("更新订单状态失败", zap.Error(err))
|
||||
return nil
|
||||
}
|
||||
|
||||
// 计算分成比例,按比例增加讲师余额
|
||||
teacher := user.User{}
|
||||
err = global.GVA_DB.Model(&user.User{}).Where("id = ?", order.TeacherId).First(&teacher).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询讲师信息失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
// 计算分成金额
|
||||
amount := float64(order.Price) * float64(teacher.ExpectRate) / 100.0
|
||||
teacher.Balance = teacher.Balance + amount
|
||||
err = global.GVA_DB.Save(&teacher).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("更新讲师余额失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
case 2: // 全站VIP订单
|
||||
userInfo := user.User{}
|
||||
err = global.GVA_DB.Model(&user.User{}).Where("id = ?", order.UserId).First(&userInfo).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询用户信息失败", zap.Error(err))
|
||||
return nil
|
||||
}
|
||||
// 更新用户的会员状态
|
||||
userInfo.IsVip = 1
|
||||
// 查询用户购买的会员信息
|
||||
vipInfo := vip.Vip{}
|
||||
err = global.GVA_DB.Model(&vip.Vip{}).Where("id = ?", order.VipId).First(&vipInfo).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询会员信息失败", zap.Error(err))
|
||||
return nil
|
||||
}
|
||||
// 计算会员的过期时间
|
||||
if userInfo.VipExpireTime != "" {
|
||||
expireTime, _ := time.Parse("2006-01-02", userInfo.VipExpireTime)
|
||||
if expireTime.After(time.Now()) {
|
||||
// 如果会员未过期,则在原有的基础上增加时间
|
||||
userInfo.VipExpireTime = expireTime.AddDate(0, 0, int(vipInfo.Expiration)).Format("2006-01-02")
|
||||
} else {
|
||||
// 如果会员已过期,则从当前时间开始计算
|
||||
userInfo.VipExpireTime = time.Now().AddDate(0, 0, int(vipInfo.Expiration)).Format("2006-01-02")
|
||||
}
|
||||
} else {
|
||||
// 如果没有会员时间,则从当前时间开始计算
|
||||
userInfo.VipExpireTime = time.Now().AddDate(0, 0, int(vipInfo.Expiration)).Format("2006-01-02")
|
||||
}
|
||||
err = global.GVA_DB.Save(&userInfo).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("更新用户会员状态失败", zap.Error(err))
|
||||
return nil
|
||||
}
|
||||
case 3: // 讲师包月订单
|
||||
ids := strings.Split(order.TeacherVipId, ",")
|
||||
for _, id := range ids {
|
||||
teacherVip := app.UserTeacherVip{}
|
||||
teacherVip.TeacherId = uint(order.TeacherId)
|
||||
// 将id转为uint
|
||||
teacherVipId, _ := strconv.ParseUint(id, 10, 64)
|
||||
teacherVip.TeacherVipId = uint(teacherVipId)
|
||||
teacherVip.UserId = uint(order.UserId)
|
||||
teacherVip.ExpireAt = time.Now().AddDate(0, 1, 0).Format("2006-01-02") // 会员有效期一个月
|
||||
teacherVip.IsExpire = 1 // 设置为未过期
|
||||
err = global.GVA_DB.Create(&teacherVip).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("购买讲师会员回调处理失败:", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// 计算分成比例,按比例增加讲师余额
|
||||
teacher := user.User{}
|
||||
err = global.GVA_DB.Model(&user.User{}).Where("id = ?", order.TeacherId).First(&teacher).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询讲师信息失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
// 计算分成金额
|
||||
amount := float64(order.Price) * float64(teacher.ExpectRate) / 100.0
|
||||
teacher.Balance = teacher.Balance + amount
|
||||
err = global.GVA_DB.Save(&teacher).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("更新讲师余额失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("订单号:%s 支付成功", transaction.OutTradeNo)
|
||||
} else {
|
||||
// 因为微信这个回调不存在订单号,所以可以告诉微信我还没处理成功,等会它会重新发起通知
|
||||
|
Reference in New Issue
Block a user