Compare commits
5 Commits
a9b6816064
...
bda654ec5e
Author | SHA1 | Date | |
---|---|---|---|
bda654ec5e | |||
9070e40f5c | |||
914513b5da | |||
7f066800a8 | |||
19eeb6f0a3 |
@@ -12,7 +12,6 @@ RUN go env -w GO111MODULE=on \
|
||||
|
||||
FROM alpine:latest
|
||||
|
||||
LABEL MAINTAINER="SliverHorn@sliver_horn@qq.com"
|
||||
# 设置时区
|
||||
ENV TZ=Asia/Shanghai
|
||||
RUN apk update && apk add --no-cache tzdata openntpd \
|
||||
|
@@ -110,3 +110,21 @@ func (a *TeacherVip) GetTeacherVip(context *gin.Context) {
|
||||
}
|
||||
r.OkWithDetailed(vip, "获取讲师VIP成功", context)
|
||||
}
|
||||
|
||||
// UpdatePriceBatch 批量更新价格
|
||||
func (a *TeacherVip) UpdatePriceBatch(context *gin.Context) {
|
||||
var p request.UpdateTeacherVipPriceBatch
|
||||
if err := context.ShouldBind(&p); err != nil {
|
||||
global.GVA_LOG.Error("参数错误,批量更新价格失败", zap.Error(err))
|
||||
r.FailWithMessage("参数错误,批量更新价格失败", context)
|
||||
return
|
||||
}
|
||||
|
||||
err := teacherVipService.UpdatePriceBatch(p)
|
||||
if err != nil {
|
||||
r.FailWithMessage("批量更新价格失败", context)
|
||||
return
|
||||
}
|
||||
|
||||
r.OkWithMessage("批量更新价格成功", context)
|
||||
}
|
||||
|
@@ -236,3 +236,39 @@ func (a *UserApi) GetUserVipList(context *gin.Context) {
|
||||
PageSize: p.PageSize,
|
||||
}, "获取用户会员列表成功", context)
|
||||
}
|
||||
|
||||
// SetTeacherWeight 设置讲师权重
|
||||
func (a *UserApi) SetTeacherWeight(context *gin.Context) {
|
||||
var p request.SetTeacherInfo
|
||||
if err := context.ShouldBind(&p); err != nil {
|
||||
global.GVA_LOG.Error("参数错误,修改讲师信息失败", zap.Error(err))
|
||||
r.FailWithMessage("参数错误,修改讲师信息失败", context)
|
||||
return
|
||||
}
|
||||
|
||||
if err := userService.SetTeacherWeight(p); err != nil {
|
||||
global.GVA_LOG.Error("修改讲师信息失败", zap.Error(err))
|
||||
r.FailWithMessage("修改讲师信息失败", context)
|
||||
return
|
||||
}
|
||||
r.OkWithMessage("修改讲师信息成功", context)
|
||||
|
||||
}
|
||||
|
||||
// SetTeacherExpectRate 设置讲师分成比例
|
||||
func (a *UserApi) SetTeacherExpectRate(context *gin.Context) {
|
||||
var p request.SetTeacherInfo
|
||||
if err := context.ShouldBind(&p); err != nil {
|
||||
global.GVA_LOG.Error("参数错误,修改讲师信息失败", zap.Error(err))
|
||||
r.FailWithMessage("参数错误,修改讲师信息失败", context)
|
||||
return
|
||||
}
|
||||
|
||||
if err := userService.SetTeacherExpectRate(p); err != nil {
|
||||
global.GVA_LOG.Error("修改讲师信息失败", zap.Error(err))
|
||||
r.FailWithMessage("修改讲师信息失败", context)
|
||||
return
|
||||
}
|
||||
r.OkWithMessage("修改讲师信息成功", context)
|
||||
|
||||
}
|
||||
|
@@ -16,12 +16,29 @@ zap:
|
||||
encode-level: LowercaseColorLevelEncoder
|
||||
stacktrace-key: stacktrace
|
||||
log-in-console: true
|
||||
retention-day: -1
|
||||
|
||||
# redis configuration
|
||||
redis:
|
||||
name: "hw"
|
||||
addr: 120.46.165.63:6379
|
||||
password: "loser765911"
|
||||
db: 0
|
||||
addr: 177.7.0.14:6379
|
||||
password: ""
|
||||
useCluster: false
|
||||
clusterAddrs:
|
||||
- 172.21.0.3:7000
|
||||
- 172.21.0.4:7001
|
||||
- 172.21.0.2:7002
|
||||
redis-list:
|
||||
- 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
|
||||
|
||||
# mongo configuration
|
||||
mongo:
|
||||
@@ -30,6 +47,7 @@ mongo:
|
||||
database: ''
|
||||
username: ''
|
||||
password: ''
|
||||
auth-source: ''
|
||||
min-pool-size: 0
|
||||
max-pool-size: 100
|
||||
socket-timeout-ms: 0
|
||||
@@ -51,21 +69,25 @@ email:
|
||||
|
||||
# system configuration
|
||||
system:
|
||||
env: public # Change to "develop" to skip authentication for development mode
|
||||
env: local # 修改为public可以关闭路由日志输出
|
||||
addr: 8888
|
||||
db-type: mysql
|
||||
oss-type: local # 控制oss选择走本地还是 七牛等其他仓 自行增加其他oss仓可以在 server/utils/upload/upload.go 中 NewOss函数配置
|
||||
use-redis: false # 使用redis
|
||||
oss-type: aliyun-oss # 控制oss选择走本地还是 七牛等其他仓 自行增加其他oss仓可以在 server/utils/upload/upload.go 中 NewOss函数配置
|
||||
use-redis: true # 使用redis
|
||||
use-mongo: false # 使用mongo
|
||||
use-multipoint: false
|
||||
# IP限制次数 一个小时15000次
|
||||
iplimit-count: 15000
|
||||
# IP限制一个小时
|
||||
iplimit-time: 3600
|
||||
# 路由全局前缀
|
||||
router-prefix: ""
|
||||
# 严格角色模式 打开后权限将会存在上下级关系
|
||||
use-strict-auth: false
|
||||
|
||||
# captcha configuration
|
||||
captcha:
|
||||
key-long: 6
|
||||
key-long: 4
|
||||
img-width: 240
|
||||
img-height: 80
|
||||
open-captcha: 0 # 0代表一直开启,大于0代表限制次数
|
||||
@@ -74,16 +96,19 @@ captcha:
|
||||
# mysql connect configuration
|
||||
# 未初始化之前请勿手动修改数据库信息!!!如果一定要手动初始化请看(https://gin-vue-admin.com/docs/first_master)
|
||||
mysql:
|
||||
path: ""
|
||||
port: ""
|
||||
config: ""
|
||||
db-name: ""
|
||||
username: ""
|
||||
password: ""
|
||||
prefix: ""
|
||||
port: "3366"
|
||||
config: charset=utf8mb4&parseTime=True&loc=Local
|
||||
db-name: lckt
|
||||
username: lckt
|
||||
password: loser765911.
|
||||
path: 219.152.55.29
|
||||
engine: ""
|
||||
log-mode: info
|
||||
max-idle-conns: 10
|
||||
max-open-conns: 100
|
||||
log-mode: ""
|
||||
log-zap: false
|
||||
singular: false
|
||||
log-zap: true
|
||||
|
||||
# pgsql connect configuration
|
||||
# 未初始化之前请勿手动修改数据库信息!!!如果一定要手动初始化请看(https://gin-vue-admin.com/docs/first_master)
|
||||
@@ -98,10 +123,42 @@ pgsql:
|
||||
max-open-conns: 100
|
||||
log-mode: ""
|
||||
log-zap: false
|
||||
|
||||
oracle:
|
||||
path: ""
|
||||
port: ""
|
||||
config: ""
|
||||
db-name: ""
|
||||
username: ""
|
||||
password: ""
|
||||
max-idle-conns: 10
|
||||
max-open-conns: 100
|
||||
log-mode: ""
|
||||
log-zap: false
|
||||
mssql:
|
||||
path: ""
|
||||
port: ""
|
||||
config: ""
|
||||
db-name: ""
|
||||
username: ""
|
||||
password: ""
|
||||
max-idle-conns: 10
|
||||
max-open-conns: 100
|
||||
log-mode: ""
|
||||
log-zap: false
|
||||
sqlite:
|
||||
path: ""
|
||||
port: ""
|
||||
config: ""
|
||||
db-name: ""
|
||||
username: ""
|
||||
password: ""
|
||||
max-idle-conns: 10
|
||||
max-open-conns: 100
|
||||
log-mode: ""
|
||||
log-zap: false
|
||||
db-list:
|
||||
- disable: true # 是否禁用
|
||||
type: "" # 数据库的类型,目前支持mysql、pgsql
|
||||
type: "" # 数据库的类型,目前支持mysql、pgsql、mssql、oracle
|
||||
alias-name: "" # 数据库的名称,注意: alias-name 需要在db-list中唯一
|
||||
path: ""
|
||||
port: ""
|
||||
@@ -114,7 +171,6 @@ db-list:
|
||||
log-mode: ""
|
||||
log-zap: false
|
||||
|
||||
|
||||
# local configuration
|
||||
local:
|
||||
path: uploads/file
|
||||
@@ -122,22 +178,11 @@ local:
|
||||
|
||||
# autocode configuration
|
||||
autocode:
|
||||
transfer-restart: true
|
||||
# root 自动适配项目根目录
|
||||
# 请不要手动配置,他会在项目加载的时候识别出根路径
|
||||
root: ""
|
||||
server: /server
|
||||
server-plug: /plugin/%s
|
||||
server-api: /api/v1/%s
|
||||
server-initialize: /initialize
|
||||
server-model: /model/%s
|
||||
server-request: /model/%s/request/
|
||||
server-router: /router/%s
|
||||
server-service: /service/%s
|
||||
web: /web/src
|
||||
web-api: /api
|
||||
web-form: /view
|
||||
web-table: /view
|
||||
web: web/src
|
||||
root: "" # root 自动适配项目根目录, 请不要手动配置,他会在项目加载的时候识别出根路径
|
||||
server: /lckt-server
|
||||
module: 'git.echol.cn/loser/lckt'
|
||||
ai-path: "https://ai.gin-vue-admin.com/{FUNC}/loser7659/c178e970-6a59-497d-96ed-86fee6b3285a" # AI服务路径
|
||||
|
||||
# qiniu configuration (请自行七牛申请对应的 公钥 私钥 bucket 和 域名地址)
|
||||
qiniu:
|
||||
@@ -149,14 +194,24 @@ qiniu:
|
||||
secret-key: ""
|
||||
use-cdn-domains: false
|
||||
|
||||
# aliyun oss configuration
|
||||
aliyun-oss:
|
||||
# minio oss configuration
|
||||
minio:
|
||||
endpoint: yourEndpoint
|
||||
access-key-id: yourAccessKeyId
|
||||
access-key-secret: yourAccessKeySecret
|
||||
bucket-name: yourBucketName
|
||||
bucket-url: yourBucketUrl
|
||||
base-path: yourBasePath
|
||||
use-ssl: false
|
||||
base-path: ""
|
||||
bucket-url: "http://host:9000/yourBucketName"
|
||||
|
||||
# aliyun oss configuration
|
||||
aliyun-oss:
|
||||
endpoint: oss-cn-hangzhou.aliyuncs.com
|
||||
access-key-id: LTAI5tB3Mn5Y7mVo8h3zkf46
|
||||
access-key-secret: FtuHdFy4NFdVItEiNBnTun3Ddi8BMK
|
||||
bucket-name: lckt
|
||||
bucket-url: https://lckt.oss-cn-hangzhou.aliyuncs.com
|
||||
base-path: lckt
|
||||
|
||||
# tencent cos configuration
|
||||
tencent-cos:
|
||||
@@ -179,6 +234,15 @@ aws-s3:
|
||||
base-url: https://gin.vue.admin
|
||||
path-prefix: git.echol.cn/loser/lckt
|
||||
|
||||
# cloudflare r2 configuration
|
||||
cloudflare-r2:
|
||||
bucket: xxxx0bucket
|
||||
base-url: https://gin.vue.admin.com
|
||||
path: uploads
|
||||
account-id: xxx_account_id
|
||||
access-key-id: xxx_key_id
|
||||
secret-access-key: xxx_secret_key
|
||||
|
||||
# huawei obs configuration
|
||||
hua-wei-obs:
|
||||
path: you-path
|
||||
@@ -191,30 +255,55 @@ hua-wei-obs:
|
||||
excel:
|
||||
dir: ./resource/excel/
|
||||
|
||||
# timer task db clear table
|
||||
Timer:
|
||||
start: true
|
||||
spec: "@daily" # 定时任务详细配置参考 https://pkg.go.dev/github.com/robfig/cron/v3
|
||||
detail:
|
||||
- tableName: sys_operation_records
|
||||
compareField: created_at
|
||||
interval: 2160h
|
||||
- tableName: jwt_blacklists
|
||||
compareField: created_at
|
||||
interval: 168h
|
||||
# disk usage configuration
|
||||
disk-list:
|
||||
- mount-point: "/"
|
||||
|
||||
# 跨域配置
|
||||
# 需要配合 server/initialize/router.go -> `Router.Use(middleware.CorsByRules())` 使用
|
||||
cors:
|
||||
mode: whitelist # 放行模式: allow-all, 放行全部; whitelist, 白名单模式, 来自白名单内域名的请求添加 cors 头; strict-whitelist 严格白名单模式, 白名单外的请求一律拒绝
|
||||
mode: allow-all # 放行模式: allow-all, 放行全部; whitelist, 白名单模式, 来自白名单内域名的请求添加 cors 头; strict-whitelist 严格白名单模式, 白名单外的请求一律拒绝
|
||||
whitelist:
|
||||
- allow-origin: example1.com
|
||||
allow-headers: content-type
|
||||
allow-methods: GET, POST
|
||||
allow-headers: Content-Type,AccessToken,X-CSRF-Token, Authorization, Token,X-Token,X-User-Id
|
||||
allow-methods: POST, GET
|
||||
expose-headers: Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type
|
||||
|
||||
allow-credentials: true # 布尔值
|
||||
- allow-origin: example2.com
|
||||
allow-headers: content-type
|
||||
allow-methods: GET, POST
|
||||
expose-headers: Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type
|
||||
allow-credentials: true # 布尔值
|
||||
allow-credentials: true # 布尔值
|
||||
mcp:
|
||||
name: GVA_MCP
|
||||
version: v1.0.0
|
||||
sse_path: /sse
|
||||
message_path: /message
|
||||
url_prefix: ''
|
||||
|
||||
wechat:
|
||||
app-id: wx3d21df18d7f8f9fc
|
||||
app-secret: 3ab19e9b6a5e155c25ac6457be650047
|
||||
token: kjeldcsdz2phfwfxnevsajnzsboho1ev
|
||||
aes-key: PiqqlGdEblw5Gv1RJ5qcTnhKUjFw9YNkBMAX6CIw6Me
|
||||
callback: https://api.gin-vue-admin.com/wechat/callback
|
||||
pay-list:
|
||||
- type: wxpay
|
||||
alias-name: wxpay-1
|
||||
app-id: wx3d21df18d7f8f9fc
|
||||
mch-id: 1646874753
|
||||
v3-key: 1a3sd8561d5179Df152D4789aD38wG9s
|
||||
cert-path: /resource/cert/apiclient_cert.pem
|
||||
key-path: /resource/cert/apiclient_key.pem
|
||||
notify-url: http://lckt.hnlc5588.cn/app_order/notify
|
||||
serial-no: 59A891FB403EC7A1CF2090DB9C8EC704BD43B101
|
||||
- type: wxpay2
|
||||
alias-name: wxpay-2
|
||||
app-id: 2
|
||||
mch-id: 2
|
||||
v3-key: 2
|
||||
cert-path: 2
|
||||
key-path: 2
|
||||
notify-url: 2
|
||||
serial-no: 2
|
@@ -6,3 +6,8 @@ type GetTeacherVipList struct {
|
||||
common.PageInfo
|
||||
TeacherId uint `json:"teacher_id" form:"teacher_id"` // 讲师ID
|
||||
}
|
||||
|
||||
type UpdateTeacherVipPriceBatch struct {
|
||||
Ids []uint `json:"ids" form:"ids" vd:"@:len($)>0; msg:'请选择要修改的讲师VIP'"`
|
||||
Price float64 `json:"price" form:"price" vd:"@:len($)>0; msg:'请输入讲师VIP价格'"` // 讲师VIP价格
|
||||
}
|
||||
|
@@ -4,12 +4,13 @@ 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"`
|
||||
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 float64 `json:"price" gorm:"comment:VIP价格(单位为分)"`
|
||||
Desc string `json:"desc" gorm:"comment:VIP描述;type:longtext"`
|
||||
CategoryId uint `json:"category_id" gorm:"comment:分类ID;"`
|
||||
}
|
||||
|
||||
func (TeacherVip) TableName() string {
|
||||
|
@@ -22,4 +22,5 @@ type TeacherInfo struct {
|
||||
Avatar string `json:"avatar" form:"avatar"`
|
||||
Des string `json:"des" form:"des"`
|
||||
Follow int64 `json:"follow" form:"follow"` // 粉丝数
|
||||
Weight int `json:"weight" form:"weight"` // 权重
|
||||
}
|
||||
|
@@ -31,6 +31,7 @@ type ArticleVo struct {
|
||||
TeacherAvatar string `json:"teacherAvatar" gorm:"comment:讲师头像"`
|
||||
IsFree int `json:"isFree" gorm:"comment:是否免费;default:0"` // 是否免费 0-否 1-是
|
||||
IsBuy int `json:"isBuy" gorm:"comment:是否购买;default:0"` // 是否购买 0-否 1-是
|
||||
CategoryId uint `json:"categoryId" gorm:"comment:分类ID"`
|
||||
}
|
||||
|
||||
type ArticleTeacherVo struct {
|
||||
|
@@ -90,3 +90,9 @@ type SetUserVipReq struct {
|
||||
VipExpireTime string `json:"vip_expire_time" form:"vip_expire_time" vd:"@:len($)>0; msg:'会员过期时间不能为空'"`
|
||||
UserLabel int64 `json:"user_label" form:"user_label" vd:"@:len($)>0; msg:'用户标签不能为空'"` // 1 普通用户 2 Vip 3 Svip
|
||||
}
|
||||
|
||||
type SetTeacherInfo struct {
|
||||
Id int `json:"id" form:"id" vd:"@:len($)>0; msg:'用户ID不能为空'"`
|
||||
ExpectRate int `json:"expect_rate" form:"expect_rate"`
|
||||
Weight int `json:"weight" form:"weight" `
|
||||
}
|
||||
|
@@ -23,12 +23,15 @@ func (s *UserRouter) InitUserRouter(Router *gin.RouterGroup, PublicRouter *gin.R
|
||||
userRouter.GET("/login/log", userApi.GetLoginLog) // 获取用户登录日志
|
||||
userRouter.DELETE("/vip", userApi.RemoveUserVip) // 删除用户会员
|
||||
userRouter.GET("/vip/list", userApi.GetUserVipList) // 获取用户会员列表
|
||||
userRouter.PUT("/rate", userApi.SetTeacherExpectRate) // 设置讲师分成比例
|
||||
userRouter.PUT("/weight", userApi.SetTeacherWeight) // 设置讲师权重
|
||||
}
|
||||
{
|
||||
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
|
||||
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
|
||||
userRouter.PUT("/teacher_vip/price", teacherVipApi.UpdatePriceBatch) // 批量设置讲师课程价格
|
||||
}
|
||||
}
|
||||
|
@@ -10,7 +10,7 @@ type VipRouter struct{}
|
||||
// InitVipRouter 初始化会员路由
|
||||
func (s *VipRouter) InitVipRouter(Router *gin.RouterGroup, PublicRouter *gin.RouterGroup) {
|
||||
vipRouter := Router.Group("vip").Use(middleware.OperationRecord())
|
||||
vipNoAuthRouter := PublicRouter.Group("vip").Use(middleware.OperationRecord())
|
||||
vipNoAuthRouter := PublicRouter.Group("vip")
|
||||
{
|
||||
vipRouter.POST("", vipApi.Create) // 获取用户列表
|
||||
vipRouter.DELETE("", vipApi.Delete) // 更新用户余额
|
||||
|
@@ -167,14 +167,12 @@ func (s *OrderService) BalancePay(p request.BalancePay) error {
|
||||
|
||||
// 全站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
|
||||
return err
|
||||
}
|
||||
// 计算会员的过期时间
|
||||
if user.VipExpireTime != "" {
|
||||
@@ -190,11 +188,22 @@ func (s *OrderService) BalancePay(p request.BalancePay) error {
|
||||
// 如果没有会员时间,则从当前时间开始计算
|
||||
user.VipExpireTime = time.Now().AddDate(0, 0, int(vipInfo.Expiration)).Format("2006-01-02")
|
||||
}
|
||||
|
||||
// 更新用户的会员状态
|
||||
user.IsVip = 1
|
||||
if vipInfo.Level == 1 {
|
||||
user.UserLabel = 2 // VIP
|
||||
}
|
||||
if vipInfo.Level == 2 {
|
||||
user.UserLabel = 3 // SVIP
|
||||
}
|
||||
|
||||
err = global.GVA_DB.Save(&user).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("更新用户会员状态失败", zap.Error(err))
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// 讲师包月
|
||||
if order.OrderType == 3 {
|
||||
@@ -223,13 +232,14 @@ func (s *OrderService) BalancePay(p request.BalancePay) error {
|
||||
return err
|
||||
}
|
||||
// 计算分成金额
|
||||
amount := float64(order.Price) * float64(teacher.ExpectRate) / 100.0
|
||||
amount := float64(order.Price/100) * 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
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// 计算分成比例,按比例增加讲师余额
|
||||
teacher := userM.User{}
|
||||
@@ -239,7 +249,7 @@ func (s *OrderService) BalancePay(p request.BalancePay) error {
|
||||
return err
|
||||
}
|
||||
// 计算分成金额
|
||||
amount := float64(order.Price) * float64(teacher.ExpectRate) / 100.0
|
||||
amount := float64(order.Price/100) * float64(teacher.ExpectRate) / 100.0
|
||||
teacher.Balance = teacher.Balance + amount
|
||||
err = global.GVA_DB.Save(&teacher).Error
|
||||
if err != nil {
|
||||
|
@@ -97,4 +97,13 @@ func (u *TeacherVipService) GetTeacherVip(id int) (vip app.TeacherVip, err error
|
||||
return vip, nil
|
||||
}
|
||||
|
||||
func (u *TeacherVipService) UpdatePriceBatch(p request.UpdateTeacherVipPriceBatch) (err error) {
|
||||
err = global.GVA_DB.Model(&app.TeacherVip{}).Where("id IN ?", p.Ids).Update("price", p.Price).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("批量更新讲师VIP价格失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ===========================管理后台接口===========================
|
||||
|
@@ -244,20 +244,40 @@ func (u *AppUserService) GetTeacherList(p common.PageInfo) (list []vo.TeacherInf
|
||||
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
|
||||
err = db.Limit(limit).Offset(offset).Select("id, nick_name, avatar,des,weight").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)
|
||||
// 批量查询所有教师的粉丝数
|
||||
var teacherIDs []uint
|
||||
for _, t := range list {
|
||||
teacherIDs = append(teacherIDs, t.ID)
|
||||
}
|
||||
type FansCount struct {
|
||||
TeacherId uint
|
||||
Count int64
|
||||
}
|
||||
var fansCounts []FansCount
|
||||
if len(teacherIDs) > 0 {
|
||||
err = global.GVA_DB.Model(&app.Follow{}).
|
||||
Select("teacher_id, count(*) as count").
|
||||
Where("teacher_id IN ?", teacherIDs).
|
||||
Group("teacher_id").
|
||||
Scan(&fansCounts).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询教师粉丝数失败", zap.Error(err))
|
||||
global.GVA_LOG.Error("批量查询教师粉丝数失败", zap.Error(err))
|
||||
return nil, 0, err
|
||||
}
|
||||
list[i].Follow = followCount
|
||||
}
|
||||
// 映射粉丝数
|
||||
fansMap := make(map[uint]int64)
|
||||
for _, fc := range fansCounts {
|
||||
fansMap[fc.TeacherId] = fc.Count
|
||||
}
|
||||
for i := range list {
|
||||
list[i].Follow = fansMap[list[i].ID]
|
||||
}
|
||||
|
||||
return
|
||||
|
@@ -55,7 +55,7 @@ func (ArticleService) GetArticleList(pageInfo request.GetList) (list []article.A
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = db.Limit(limit).Offset(offset).Find(&list).Error
|
||||
err = db.Limit(limit).Offset(offset).Order("created_at desc").Find(&list).Error
|
||||
return
|
||||
}
|
||||
|
||||
@@ -64,11 +64,8 @@ func (s ArticleService) APPGetArticleList(pageInfo request.GetList) (list []vo.A
|
||||
offset := pageInfo.PageSize * (pageInfo.Page - 1)
|
||||
|
||||
db := global.GVA_DB.Model(&article.Article{}).Where("status = 1") // 只查询已发布的文章
|
||||
err = db.Count(&total).Error
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 优化:先构建所有筛选条件再查询总数和数据
|
||||
if pageInfo.Title != "" {
|
||||
db = db.Where("title LIKE ?", "%"+pageInfo.Title+"%")
|
||||
}
|
||||
@@ -78,14 +75,41 @@ func (s ArticleService) APPGetArticleList(pageInfo request.GetList) (list []vo.A
|
||||
if pageInfo.TeacherId != 0 {
|
||||
db = db.Where("teacher_id = ?", pageInfo.TeacherId)
|
||||
}
|
||||
|
||||
if pageInfo.Keyword != "" {
|
||||
db = db.Where("title LIKE ? OR article.desc LIKE ? OR teacher_name LIKE ?", "%"+pageInfo.Keyword+"%", "%"+pageInfo.Keyword+"%", "%"+pageInfo.Keyword+"%")
|
||||
}
|
||||
|
||||
// 先查总数
|
||||
err = db.Count(&total).Error
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 批量查出teacher_id
|
||||
err = db.Limit(limit).Offset(offset).Omit("teacher_avatar").Order("created_at desc").Find(&list).Error
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 优化:批量查询讲师头像,避免N+1查询
|
||||
teacherIds := make([]int, 0, len(list))
|
||||
for _, a := range list {
|
||||
teacherIds = append(teacherIds, a.TeacherId)
|
||||
}
|
||||
avatars := make(map[int]string)
|
||||
if len(teacherIds) > 0 {
|
||||
type avatarResult struct {
|
||||
ID int
|
||||
Avatar string
|
||||
}
|
||||
var results []avatarResult
|
||||
global.GVA_DB.Table("app_user").Select("id, avatar").Where("id IN ?", teacherIds).Find(&results)
|
||||
for _, r := range results {
|
||||
avatars[r.ID] = r.Avatar
|
||||
}
|
||||
}
|
||||
for i, a := range list {
|
||||
global.GVA_DB.Table("app_user").Select("avatar").Where("id = ?", a.TeacherId).Scan(&list[i].TeacherAvatar)
|
||||
list[i].TeacherAvatar = avatars[a.TeacherId]
|
||||
}
|
||||
|
||||
return
|
||||
@@ -113,9 +137,37 @@ func (s ArticleService) APPGetArticle(id string, userId int) (article *vo.Articl
|
||||
|
||||
// 如果count = 0 或者 TeacherId 不等于 userId,表示用户没有购买过该文章
|
||||
if count == 0 && article.TeacherId != userId {
|
||||
// 用户没有购买过,隐藏Content
|
||||
article.Content = ""
|
||||
article.IsBuy = 0 // 设置为未购买
|
||||
|
||||
// 判断用户是否已经包月该讲师和是否是讲师本人
|
||||
if userId != 0 && userId != article.TeacherId {
|
||||
var userTeacherVip []app.UserTeacherVip
|
||||
err = global.GVA_DB.Model(&app.UserTeacherVip{}).Where("teacher_id = ? AND user_id = ? AND is_expire = 1", article.TeacherId, userId).Find(&userTeacherVip).Count(&count).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询用户包月记录失败", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, vip := range userTeacherVip {
|
||||
// 获取包月的信息 判断文章是否在包月范围内
|
||||
var teacherVipInfo app.TeacherVip
|
||||
err = global.GVA_DB.Model(&app.TeacherVip{}).Where("id = ?", vip.TeacherVipId).First(&teacherVipInfo).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询讲师包月信息失败", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 用户包月了,判断文章是否在包月范围内
|
||||
if count != 0 {
|
||||
if teacherVipInfo.CategoryId == article.CategoryId {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
// 用户没有购买过,隐藏Content
|
||||
article.Content = ""
|
||||
article.IsBuy = 0 // 设置为未购买
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,7 +275,7 @@ func (s ArticleService) BulkUpload(p request.BulkUpload) (err error) {
|
||||
publishTime, _ := time.ParseInLocation("2006-01-02 15:04:05", p.PublishTime, loc)
|
||||
|
||||
articles = append(articles, article.Article{
|
||||
Title: p.Title,
|
||||
Title: teacher.NickName + "--" + p.Title,
|
||||
Desc: p.Desc,
|
||||
CategoryId: p.CategoryId,
|
||||
TeacherId: int(teacher.ID),
|
||||
|
@@ -363,3 +363,23 @@ func (u *UserService) GetUserVipList(p request.GetUserListReq) (list []vo.UserIn
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// SetTeacherWeight 设置讲师权重
|
||||
func (u *UserService) SetTeacherWeight(p request.SetTeacherInfo) (err error) {
|
||||
err = global.GVA_DB.Model(&user.User{}).Where("id = ?", p.Id).Update("weight", p.Weight).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("设置讲师权重失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// SetTeacherExpectRate 设置讲师分成
|
||||
func (u *UserService) SetTeacherExpectRate(p request.SetTeacherInfo) (err error) {
|
||||
err = global.GVA_DB.Model(&user.User{}).Where("id = ?", p.Id).Update("expect_rate", p.ExpectRate).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("设置讲师权重失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
157
utils/upload/local_upload.go
Normal file
157
utils/upload/local_upload.go
Normal file
@@ -0,0 +1,157 @@
|
||||
package upload
|
||||
|
||||
// UploadOSS OSS对象存储接口
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/aliyun/aliyun-oss-go-sdk/oss"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Worker struct {
|
||||
in chan string
|
||||
done chan bool
|
||||
}
|
||||
|
||||
type Uploads struct {
|
||||
}
|
||||
|
||||
const endpoint = "oss-cn-hangzhou.aliyuncs.com" // oss endpoint
|
||||
const accessKeyId = "LTAI5tB3Mn5Y7mVo8h3zkf46" // oss key
|
||||
const accessKeySecret = "FtuHdFy4NFdVItEiNBnTun3Ddi8BMK" //oss secret
|
||||
const bucketName = "lckt" //oss bucket名称
|
||||
const objectName = "avatar/" //oss远程目标地址
|
||||
const workerCount = 100 //设置最大并发数
|
||||
const suffix = "" //筛选目录下需要上传的格式
|
||||
|
||||
// 上传单个文件
|
||||
func (u *Uploads) UploadOne(path string) {
|
||||
// 创建OSSClient实例。
|
||||
localFileName := path
|
||||
split := strings.Split(localFileName, `\`)
|
||||
length := len(split)
|
||||
fileName := split[length-1 : length]
|
||||
|
||||
client, err := oss.New(endpoint, accessKeyId, accessKeySecret)
|
||||
if err != nil {
|
||||
handleError(err)
|
||||
}
|
||||
// 获取存储空间。
|
||||
bucket, err := client.Bucket(bucketName)
|
||||
if err != nil {
|
||||
handleError(err)
|
||||
}
|
||||
up(bucket, objectName+fileName[0], localFileName)
|
||||
}
|
||||
func createWorker(bucket *oss.Bucket, objectName string, id int) Worker {
|
||||
w := Worker{
|
||||
in: make(chan string),
|
||||
done: make(chan bool),
|
||||
}
|
||||
go doWork(id, w.in, w.done, bucket, objectName)
|
||||
return w
|
||||
}
|
||||
|
||||
// 并发上传目录下所有文件
|
||||
func (u *Uploads) UploadMany(path string) {
|
||||
files, err := getAllFiles(path)
|
||||
if err != nil {
|
||||
handleError(err)
|
||||
}
|
||||
fileCount := len(files)
|
||||
if fileCount == 0 {
|
||||
handleError(errors.New("目录下没有指定文件,请重试!"))
|
||||
}
|
||||
client, err := oss.New(endpoint, accessKeyId, accessKeySecret)
|
||||
if err != nil {
|
||||
handleError(err)
|
||||
}
|
||||
bucket, err := client.Bucket(bucketName)
|
||||
if err != nil {
|
||||
handleError(err)
|
||||
}
|
||||
var workers [workerCount]Worker
|
||||
for i := 0; i < fileCount; i++ {
|
||||
workers[i] = createWorker(bucket, objectName, i)
|
||||
}
|
||||
for i := 0; i < fileCount; i++ {
|
||||
workers[i].in <- files[i]
|
||||
}
|
||||
for i := 0; i < fileCount; i++ {
|
||||
<-workers[i].done
|
||||
}
|
||||
}
|
||||
|
||||
func handleError(err error) {
|
||||
fmt.Println("Error:", err)
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
func up(bucket *oss.Bucket, objectName string, localFileName string) {
|
||||
// 上传文件。
|
||||
err := bucket.PutObjectFromFile(objectName, localFileName)
|
||||
if err != nil {
|
||||
handleError(err)
|
||||
}
|
||||
}
|
||||
|
||||
func doWork(id int, c chan string, done chan bool, bucket *oss.Bucket, objectName string) {
|
||||
for n := range c {
|
||||
split := strings.Split(n, `\`)
|
||||
length := len(split)
|
||||
fileName := split[length-1 : length]
|
||||
fmt.Printf("worker : %d, object: %s, uploading file %v \n", id, objectName, n)
|
||||
up(bucket, objectName+fileName[0], n)
|
||||
go func() { done <- true }()
|
||||
}
|
||||
}
|
||||
|
||||
// 获取指定目录下的所有文件,包含子目录下的文件
|
||||
func getAllFiles(dirPth string) (files []string, err error) {
|
||||
var dirs []string
|
||||
dir, err := ioutil.ReadDir(dirPth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
PthSep := string(os.PathSeparator)
|
||||
for _, fi := range dir {
|
||||
if fi.IsDir() { // 目录, 递归遍历
|
||||
dirs = append(dirs, dirPth+PthSep+fi.Name())
|
||||
getAllFiles(dirPth + PthSep + fi.Name())
|
||||
} else {
|
||||
// 过滤指定格式
|
||||
if suffix != "" {
|
||||
ok := strings.HasSuffix(fi.Name(), suffix)
|
||||
if ok {
|
||||
files = append(files, dirPth+PthSep+fi.Name())
|
||||
}
|
||||
} else {
|
||||
files = append(files, dirPth+PthSep+fi.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func InitOss() (*oss.Bucket, error) {
|
||||
// 创建OSSClient实例。
|
||||
AccessKeyId := "LTAI5tB3Mn5Y7mVo8h3zkf46"
|
||||
AccessKeySecret := "FtuHdFy4NFdVItEiNBnTun3Ddi8BMK"
|
||||
Endpoint := "oss-cn-hangzhou.aliyuncs.com"
|
||||
BucketName := "lckt"
|
||||
client, err := oss.New(Endpoint, AccessKeyId, AccessKeySecret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 获取存储空间。
|
||||
bucket, err := client.Bucket(BucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return bucket, nil
|
||||
}
|
@@ -219,7 +219,7 @@ func NotifyHandle(ctx *gin.Context) error {
|
||||
return err
|
||||
}
|
||||
// 计算分成金额
|
||||
amount := float64(order.Price) * float64(teacher.ExpectRate) / 100.0
|
||||
amount := float64(order.Price/100) * float64(teacher.ExpectRate) / 100.0
|
||||
teacher.Balance = teacher.Balance + amount
|
||||
err = global.GVA_DB.Save(&teacher).Error
|
||||
if err != nil {
|
||||
@@ -256,6 +256,15 @@ func NotifyHandle(ctx *gin.Context) error {
|
||||
// 如果没有会员时间,则从当前时间开始计算
|
||||
userInfo.VipExpireTime = time.Now().AddDate(0, 0, int(vipInfo.Expiration)).Format("2006-01-02")
|
||||
}
|
||||
|
||||
if vipInfo.Level == 1 {
|
||||
userInfo.UserLabel = 2 // SVIP
|
||||
}
|
||||
|
||||
if vipInfo.Level == 2 {
|
||||
userInfo.UserLabel = 3 // SVIP
|
||||
}
|
||||
|
||||
err = global.GVA_DB.Save(&userInfo).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("更新用户会员状态失败", zap.Error(err))
|
||||
@@ -287,7 +296,7 @@ func NotifyHandle(ctx *gin.Context) error {
|
||||
return err
|
||||
}
|
||||
// 计算分成金额
|
||||
amount := float64(order.Price) * float64(teacher.ExpectRate) / 100.0
|
||||
amount := float64(order.Price/100) * float64(teacher.ExpectRate) / 100.0
|
||||
teacher.Balance = teacher.Balance + amount
|
||||
err = global.GVA_DB.Save(&teacher).Error
|
||||
if err != nil {
|
||||
|
Reference in New Issue
Block a user