Compare commits

..

11 Commits

Author SHA1 Message Date
Eg
569e6618a8 🎨 新增路由||优化返回数据结构 2022-06-02 16:22:36 +08:00
Eg
fb5f8ca2a5 🎨 新增获取用户详情接口 2022-06-02 16:22:01 +08:00
Eg
090acb0454 🎨 新增获取提交列表接口 2022-06-02 16:21:42 +08:00
Eg
991165125f 🎨 优化问题列表接口 2022-06-02 16:21:08 +08:00
Eg
6945e6db93 🎨 优化model和日志,完善配置文件 2022-05-26 23:53:18 +08:00
Eg
8c5fa60081 🎨 优化model 2022-05-26 15:40:21 +08:00
Eg
2d467c710f Merge remote-tracking branch 'origin/master' 2022-05-26 00:39:47 +08:00
Eg
326118eefb 完善基础架构,新增部分问题接口 2022-05-26 00:39:39 +08:00
2e5aa77e25 更新 'README.md' 2022-05-25 18:57:46 +08:00
Eg
761c24efbf 完成项目配置初始化 2022-05-25 18:51:57 +08:00
Eg
af58c03c7c 完成nacos和数据库等配置文件 2022-05-25 18:43:49 +08:00
45 changed files with 779 additions and 62 deletions

11
.env Normal file
View File

@@ -0,0 +1,11 @@
LOG_FILE_ENABLE=1
# Nacos配置
NACOS_HOST=127.0.0.1
NACOS_PORT=8848
# 命名空间用默认的public的话这儿就不写
NACOS_NAMESPACE=41f4f415-970b-4839-8f71-d4fa4c6d6c30
# 除本文件以外的其他托管到Nacos的配置文件 Ps. 后面可以把NACOS_*和系统启动相关的留着其他的全部托管到Nacos
NACOS_CONFIG_NAME=mysql.yaml
# 是否关闭gorm自动更新表结构
DISABLE_SYNC_MODELS=false

3
.gitignore vendored
View File

@@ -16,7 +16,8 @@
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
vendor/
logs/
# Go workspace file
go.work

View File

@@ -1,3 +1,4 @@
# online_code
练习系统
一个类似于LeetCode的刷题系统
开发进度:完成基础框架

31
api/problem.go Normal file
View File

@@ -0,0 +1,31 @@
package api
import (
"github.com/gin-gonic/gin"
"online_code/core"
"online_code/models/param"
"online_code/repository"
)
type problemApi struct{}
func ProblemApi() *problemApi {
return &problemApi{}
}
func (problemApi) GetProbleList(ctx *gin.Context) {
var p param.GetProblemList
if err := ctx.ShouldBind(&p); err != nil {
core.R(ctx).FailWithMessage("参数错误: " + err.Error())
return
}
records, count, err := repository.ProblemService().GetList(p)
if err != nil {
core.R(ctx).FailWithMessage("获取题目列表失败: " + err.Error())
return
}
// 返回结果
core.R(ctx).OkDataPage(count, core.PageData{Current: p.Current, Size: p.Size, Total: count, Records: records})
}

31
api/submit.go Normal file
View File

@@ -0,0 +1,31 @@
package api
import (
"github.com/gin-gonic/gin"
"online_code/core"
"online_code/models/param"
"online_code/repository"
)
type submitApi struct{}
func SubmitApi() *submitApi {
return &submitApi{}
}
func (submitApi) GetSubmitList(c *gin.Context) {
var p param.GetSubmitList
if err := c.ShouldBind(&p); err != nil {
core.R(c).FailWithMessage("参数错误: " + err.Error())
return
}
records, count, err := repository.SubmitService().GetList(p)
if err != nil {
core.R(c).FailWithMessage("获取提交列表失败: " + err.Error())
return
}
// 返回结果
core.R(c).OkDataPage(count, core.PageData{Current: p.Current, Size: p.Size, Total: count, Records: records})
}

32
api/user.go Normal file
View File

@@ -0,0 +1,32 @@
package api
import (
"github.com/gin-gonic/gin"
"online_code/client"
"online_code/core"
"online_code/models/entity"
)
type userApi struct{}
func UserApi() *userApi {
return &userApi{}
}
// GetUserInfo 获取用户详情
func (userApi) GetUserInfo(ctx *gin.Context) {
// 获取用户信息
identity := ctx.Param("identity")
if identity == "" {
core.R(ctx).FailWithMessage("参数不能为空: identity")
return
}
userData := entity.UserBasic{}
err := client.MySQL.Omit("password").Where("identity = ?", identity).Take(&userData).Error
if err != nil {
core.R(ctx).FailWithMessage("获取用户信息失败: " + err.Error())
return
}
core.R(ctx).OkWithData(userData)
}

33
client/mysql.go Normal file
View File

@@ -0,0 +1,33 @@
package client
import (
"git.echol.cn/loser/logger/log"
"online_code/config"
"git.echol.cn/loser/logger"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
var MySQL *gorm.DB
func InitMySQLClient() {
// 创建连接对象
mysqlConfig := mysql.Config{
DSN: config.Scd.MySQL.GetDSN(),
DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式
DontSupportRenameColumn: true, // 用 `change` 重命名列
}
conn, err := gorm.Open(mysql.New(mysqlConfig), &gorm.Config{Logger: logger.DefaultGormLogger()})
if err != nil {
log.Panic("初始化MySQL连接失败, 错误信息: %v", err)
} else {
log.Debug("MySQL连接成功")
}
MySQL = conn
//db, err := conn.DB()
//db.SetMaxIdleConns(10) // 用于设置连接池中空闲连接的最大数量
//db.SetMaxOpenConns(100) // 用于设置数据库连接的最大打开数量
//db.SetConnMaxLifetime(time.Hour) // 设置连接的最大存活时间
}

26
client/redis.go Normal file
View File

@@ -0,0 +1,26 @@
package client
import (
"context"
"git.echol.cn/loser/logger/log"
"github.com/go-redis/redis/v8"
"online_code/config"
)
var Redis *redis.Client
func InitRedisClient() {
conf := config.Scd.Redis
// 初始化连接
conn := redis.NewClient(&redis.Options{
Addr: conf.GetDSN(),
Password: conf.Password,
DB: conf.Db,
})
if err := conn.Ping(context.Background()).Err(); err != nil {
log.Panicf("Redis连接初始化失败: %v", err)
} else {
log.Debug("Redis连接初始化成功")
}
Redis = conn
}

View File

@@ -1,20 +1,20 @@
package types
import (
"github.com/google/uuid"
"gorm.io/gorm"
"time"
)
// BaseDbModel 数据库通用字段
type BaseDbModel struct {
Id string `json:"id" gorm:"type:varchar(50);primarykey"`
CreatedAt DateTime `json:"createdAt"`
UpdatedAt DateTime `json:"updatedAt"`
ID uint `gorm:"type:int(11);primarykey;" json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `json:"-" gorm:"index:deleted"`
}
// BeforeCreate 创建数据库对象之前生成UUID
/*// BeforeCreate 创建数据库对象之前生成UUID
func (m *BaseDbModel) BeforeCreate(*gorm.DB) (err error) {
m.Id = uuid.New().String()
return
}
}*/

19
config.yaml Normal file
View File

@@ -0,0 +1,19 @@
api:
port: 8083
name: online_code
version: v1
prefix: /api/v1
mysql:
host: 127.0.0.1
port: 3306
user: root
password: root
db: online_code
redis:
host: 127.0.0.1
port: 6379
password: loser7659
db: 0

9
config/app.go Normal file
View File

@@ -0,0 +1,9 @@
package config
type appInfo struct {
AppName string `mapstructure:"name" yaml:"name"` // 应用名称
Port uint64 `mapstructure:"port" yaml:"port"` // 端口号
Prefix string `mapstructure:"prefix" yaml:"prefix"` // 接口前缀
Version string `mapstructure:"version" yaml:"version"` // 版本号
Monster bool `mapstructure:"monster" yaml:"monster"` // 妖怪模式
}

12
config/config.go Normal file
View File

@@ -0,0 +1,12 @@
package config
var Scd systemConfigData
var Nacos nacosConfig
// 配置信息
type systemConfigData struct {
Admin appInfo `mapstructure:"admin" yaml:"admin"` // 系统配置-Admin
Api appInfo `mapstructure:"api" yaml:"api"` // 系统配置-Api
MySQL mysqlConfig `mapstructure:"mysql" yaml:"mysql"` // MySQL配置
Redis redisConfig `mapstructure:"redis" yaml:"redis"` // Redis配置
}

20
config/mysql.go Normal file
View File

@@ -0,0 +1,20 @@
package config
import (
"fmt"
)
// MySQL配置
type mysqlConfig struct {
Host string `mapstructure:"host" yaml:"host"` // 主机
Port int `mapstructure:"port" yaml:"port"` // 端口
User string `mapstructure:"user" yaml:"user"` // 用户名
Password string `mapstructure:"password" yaml:"password"` // 密码
Db string `mapstructure:"db" yaml:"db"` // 数据库名称
}
// GetDSN 返回 MySQL 连接字符串
func (c mysqlConfig) GetDSN() string {
return fmt.Sprintf("%s:%s@tcp(%s:%v)/%s?charset=utf8mb4&parseTime=True&loc=Local",
c.User, c.Password, c.Host, c.Port, c.Db)
}

9
config/nacos.go Normal file
View File

@@ -0,0 +1,9 @@
package config
// nacosConfig Nacos配置
type nacosConfig struct {
Host string `env:"NACOS_HOST"` // 主机
Port uint64 `env:"NACOS_PORT" envDefault:"8848"` // 端口
NamespaceId string `env:"NACOS_NAMESPACE"` // 命名空间
CenterConfigName string `env:"NACOS_CONFIG_NAME" envDefault:"gtest.yml"` // 外部配置文件名,多个以逗号隔开
}

15
config/redis.go Normal file
View File

@@ -0,0 +1,15 @@
package config
import "fmt"
// Redis配置
type redisConfig struct {
Host string `mapstructure:"host" yaml:"host"` // 主机
Port int `mapstructure:"port" yaml:"port"` // 端口
Password string `mapstructure:"password" yaml:"password"` // 密码
Db int `mapstructure:"db" yaml:"db"` // 数据库名称
}
func (r redisConfig) GetDSN() string {
return fmt.Sprintf("%s:%v", r.Host, r.Port)
}

View File

@@ -3,6 +3,7 @@ package core
import (
"github.com/gin-gonic/gin"
"net/http"
"online_code/utils"
)
// 返回数据包装
@@ -59,6 +60,15 @@ func (r rs) OkDetailed(data interface{}, message string) {
r.Result(SUCCESS, data, message)
}
// OkDataPage 返回分页数据
func (r rs) OkDataPage(count int64, page PageData) {
// 计算总页码
totalPage := utils.GenTotalPage(count, page.Size)
page.TotalPage = totalPage
// 返回结果
r.Result(SUCCESS, page, "操作成功")
}
// Fail 返回默认失败
func (r rs) Fail() {
r.Result(ERROR, nil, "操作失败")

6
define/define.go Normal file
View File

@@ -0,0 +1,6 @@
package define
var (
DefaultCurrent = "1"
DefaultSize = "10"
)

60
go.mod
View File

@@ -4,56 +4,110 @@ go 1.18
require (
git.echol.cn/loser/logger v1.0.14
git.echol.cn/loser/nacos-viper-remote v0.4.1-0.20220525104600-e38430672884
github.com/caarlos0/env/v6 v6.9.2
github.com/fsnotify/fsnotify v1.5.1
github.com/gin-gonic/gin v1.7.7
github.com/google/uuid v1.1.2
github.com/go-redis/redis/v8 v8.11.5
github.com/spf13/viper v1.10.1
golang.org/x/crypto v0.0.0-20220214200702-86341886e292
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
gorm.io/driver/mysql v1.3.2
gorm.io/gorm v1.23.5
)
require (
cloud.google.com/go v0.99.0 // indirect
cloud.google.com/go/firestore v1.6.1 // indirect
gitee.ltd/lxh/logger v1.0.14 // indirect
github.com/aliyun/alibaba-cloud-sdk-go v1.61.18 // indirect
github.com/armon/go-metrics v0.3.10 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/caarlos0/env/v6 v6.9.2 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/census-instrumentation/opencensus-proto v0.3.0 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4 // indirect
github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490 // indirect
github.com/coreos/go-semver v0.3.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1 // indirect
github.com/envoyproxy/protoc-gen-validate v0.6.2 // indirect
github.com/fatih/color v1.13.0 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-errors/errors v1.0.1 // indirect
github.com/go-kit/kit v0.12.0 // indirect
github.com/go-kit/log v0.2.1 // indirect
github.com/go-logfmt/logfmt v0.5.1 // indirect
github.com/go-playground/locales v0.13.0 // indirect
github.com/go-playground/universal-translator v0.17.0 // indirect
github.com/go-playground/validator/v10 v10.4.1 // indirect
github.com/go-sql-driver/mysql v1.6.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/go-cmp v0.5.6 // indirect
github.com/googleapis/gax-go/v2 v2.1.1 // indirect
github.com/hashicorp/consul/api v1.12.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-hclog v1.0.0 // indirect
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hashicorp/serf v0.9.6 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.4 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/jpillora/backoff v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/leodido/go-urn v1.2.0 // indirect
github.com/lixh00/loki-client-go v1.0.1 // indirect
github.com/magiconair/properties v1.8.5 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect
github.com/nacos-group/nacos-sdk-go/v2 v2.0.0 // indirect
github.com/natefinch/lumberjack v2.0.0+incompatible // indirect
github.com/pelletier/go-toml v1.9.4 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/client_golang v1.12.2 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.34.0 // indirect
github.com/prometheus/procfs v0.7.3 // indirect
github.com/prometheus/prometheus v1.8.2-0.20201028100903-3245b3267b24 // indirect
github.com/sagikazarmark/crypt v0.4.0 // indirect
github.com/spf13/afero v1.6.0 // indirect
github.com/spf13/cast v1.4.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.2.0 // indirect
github.com/toolkits/concurrent v0.0.0-20150624120057-a4371d70e3e3 // indirect
github.com/ugorji/go/codec v1.1.7 // indirect
go.etcd.io/etcd/api/v3 v3.5.1 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.1 // indirect
go.etcd.io/etcd/client/v2 v2.305.1 // indirect
go.opencensus.io v0.23.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.8.0 // indirect
go.uber.org/zap v1.21.0 // indirect
golang.org/x/crypto v0.0.0-20210915214749-c084706c2272 // indirect
golang.org/x/net v0.0.0-20220517181318-183a9ca12b87 // indirect
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect
golang.org/x/sys v0.0.0-20220517195934-5e4e11fc645e // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/api v0.63.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3 // indirect
google.golang.org/grpc v1.46.2 // indirect
google.golang.org/protobuf v1.28.0 // indirect
gopkg.in/ini.v1 v1.66.2 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)

11
initialize/clients.go Normal file
View File

@@ -0,0 +1,11 @@
package initialize
import "online_code/client"
// 初始化数据库客户端
func initClient() {
// 初始化MySQL
client.InitMySQLClient()
// 初始化Redis
client.InitRedisClient()
}

68
initialize/config.go Normal file
View File

@@ -0,0 +1,68 @@
package initialize
import (
"git.echol.cn/loser/logger/log"
"online_code/config"
nr "git.echol.cn/loser/nacos-viper-remote"
"github.com/caarlos0/env/v6"
"github.com/fsnotify/fsnotify"
"github.com/spf13/viper"
_ "github.com/spf13/viper/remote"
)
var vp *viper.Viper
// 初始化Nacos
func initNacos() {
// 初始化Nacos配置
if err := env.Parse(&config.Nacos); err != nil {
panic(err)
}
if config.Nacos.Host == "" {
log.Panic("Nacos配置错误")
}
}
// 初始化配置文件
func initConfig() {
vp = viper.New()
// 配置 Viper for Nacos 的远程仓库参数
nr.SetOptions(&nr.Option{
Url: config.Nacos.Host, // nacos server 多地址需要地址用;号隔开,如 Url: "loc1;loc2;loc3"
Port: config.Nacos.Port, // nacos server端口号
NamespaceId: config.Nacos.NamespaceId, // nacos namespace
GroupName: "DEFAULT_GROUP", // nacos group
Config: nr.Config{DataId: config.Nacos.CenterConfigName}, // nacos DataID
Auth: nil, // 如果需要验证登录,需要此参数
})
err := vp.AddRemoteProvider("nacos", config.Nacos.Host, "")
if err != nil {
log.Panicf("%s", err)
}
vp.SetConfigType("yaml")
//尝试进行配置读取
if err = vp.ReadRemoteConfig(); err != nil {
log.Panic(err)
}
//异步监听Nacos中的配置变化如发生配置更改会直接同步到 viper实例中。
err = vp.WatchRemoteConfigOnChannel()
if err != nil {
log.Errorf("监听远程配置变动失败: %v", err)
}
// 解析配置文件为结构体
if err = vp.Unmarshal(&config.Scd); err != nil {
log.Panic(err)
}
vp.WatchConfig() // 监听配置文件变化
vp.OnConfigChange(func(e fsnotify.Event) {
log.Info("配置文件发生变动:", e.Name)
if err = vp.Unmarshal(&config.Scd); err != nil {
log.Panic(err)
}
// 配置文件发生变动,重新初始化一下连接信息
initClient()
})
}

24
initialize/db_table.go Normal file
View File

@@ -0,0 +1,24 @@
package initialize
import (
"git.echol.cn/loser/logger/log"
"online_code/client"
"online_code/models/entity"
)
// 初始化数据库表
func databaseTable() {
dbs := []any{
new(entity.UserBasic),
new(entity.TestCase),
new(entity.CategoryBasic),
new(entity.ProblemCategory),
//new(entity.ProblemBasic),
//new(entity.SubmitBasic),
}
if err := client.MySQL.AutoMigrate(dbs...); err != nil {
log.Panicf("数据库表预初始化处理:%s", err.Error())
}
log.Debugf("数据库表预初始化处理完成")
}

10
initialize/init.go Normal file
View File

@@ -0,0 +1,10 @@
package initialize
// InitSystem 初始化系统
func InitSystem() {
//initNacos() // 初始化nacos
//initConfig() // 初始化配置
initLocaConfig() // 初始化本地配置
initClient() // 初始化连接池等信息
databaseTable() // 初始化数据库表信息
}

33
initialize/locaConfig.go Normal file
View File

@@ -0,0 +1,33 @@
package initialize
import (
"fmt"
"git.echol.cn/loser/logger/log"
"online_code/config"
"github.com/spf13/viper"
)
var localVp *viper.Viper
func initLocaConfig() {
localVp = viper.New()
localVp.SetConfigName("config") // 文件名-无需文件后缀
localVp.SetConfigType("yaml") // 文件扩展名
localVp.AddConfigPath(".")
// 处理读取配置文件的错误
if err := localVp.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
panic(fmt.Errorf("未找到配置文件,请检查路径: %s \n", err))
} else {
panic(fmt.Errorf("文件已找到,但读取文件时出错: %s \n", err))
}
}
// 解析配置文件为结构体
if err := localVp.Unmarshal(&config.Scd); err != nil {
log.Panic(err)
}
}

57
main.go Normal file
View File

@@ -0,0 +1,57 @@
package main
import (
"fmt"
"git.echol.cn/loser/logger"
"git.echol.cn/loser/logger/log"
"github.com/gin-gonic/gin"
"golang.org/x/sync/errgroup"
"net/http"
"online_code/config"
"online_code/core"
"online_code/initialize"
"online_code/route"
"time"
)
var g errgroup.Group
func init() {
logger.InitLogger(logger.LogConfig{Mode: logger.Dev, LokiEnable: false, FileEnable: true})
initialize.InitSystem() // 初始化系统配置
}
// 启动入口
func main() {
// 强制日志颜色化
gin.ForceConsoleColor()
app := &http.Server{
Addr: fmt.Sprintf(":%d", config.Scd.Api.Port),
Handler: api(),
ReadTimeout: 5 * time.Second,
WriteTimeout: 1 * time.Minute,
}
// 启动项目
g.Go(func() error {
return app.ListenAndServe()
})
if err := g.Wait(); err != nil {
log.Panicf("启动失败,错误信息:%s", err.Error())
}
}
// 生成接口服务
func api() http.Handler {
app := gin.New()
app.Use(gin.Recovery())
// 开启自定义请求方式不允许处理函数
app.HandleMethodNotAllowed = true
// 处理请求方式不对
app.NoMethod(core.NoMethodHandler())
// 404返回数据
app.NoRoute(core.NoRouteHandler())
// 初始化路由
route.InitRoute(app.Group(config.Scd.Api.Prefix))
return app
}

18
models/cache/user.go vendored Normal file
View File

@@ -0,0 +1,18 @@
package cache
import "encoding/json"
// UserInfo 登录用的用户信息结构体
type UserInfo struct {
UserType string `json:"userType"` // 用户类型
UserId string `json:"userId"` // 用户Id
}
// String 实现Stringer接口
func (i UserInfo) String() (string, error) {
b, err := json.Marshal(i)
if err != nil {
return "", err
}
return string(b), nil
}

View File

@@ -2,14 +2,14 @@ package entity
import "online_code/common/types"
// Category 分类表
type Category struct {
// CategoryBasic 分类表
type CategoryBasic struct {
types.BaseDbModel
Identity string `json:"identity" gorm:"column:identity;type:varchar(36);comment:'分类的唯一标识'"`
Identity string `json:"identity" gorm:"column:identity;type:varchar(36);comment:'问题表的唯一标识'"`
Name string `json:"name" gorm:"column:name;type:varchar(100);comment:'分类名称'" `
ParentId int `json:"parent_id" gorm:"column:parent_id;type:int(11);comment:'父级ID'" `
}
func (table *Category) TableName() string {
return "category"
func (table *CategoryBasic) TableName() string {
return "category_basic"
}

View File

@@ -1,21 +0,0 @@
package entity
import "online_code/common/types"
// Problem 问题表
type Problem struct {
types.BaseDbModel
Identity string `json:"identity" gorm:"column:identity;type:varchar(36);comment:'问题表的唯一标识'" ` // 问题表的唯一标识
ProblemCategories []*ProblemCategory `json:"problem_categories" gorm:"foreignKey:problem_id;references:id;comment:'关联问题分类表'" ` // 关联问题分类表
Title string `json:"title" gorm:"column:title;type:varchar(255);comment:'文章标题'" ` // 文章标题
Content string `json:"content" gorm:"column:content;type:text;comment:'文章正文'" ` // 文章正文
MaxRuntime int `json:"max_runtime" gorm:"column:max_runtime;type:int(11);comment:'最大运行时长'" ` // 最大运行时长
MaxMem int `json:"max_mem" gorm:"column:max_mem;type:int(11);comment:'最大运行内存'" ` // 最大运行内存
TestCases []*TestCase `json:"test_cases" gorm:"foreignKey:problem_identity;references:identity;comment:'关联测试用例表'" ` // 关联测试用例表
PassNum int64 `json:"pass_num" gorm:"column:pass_num;type:int(11);comment:'通过次数'" ` // 通过次数
SubmitNum int64 `json:"submit_num" gorm:"column:submit_num;type:int(11);comment:'提交次数'" ` // 提交次数
}
func (table *Problem) TableName() string {
return "problem_basic"
}

View File

@@ -0,0 +1,21 @@
package entity
import "online_code/common/types"
// ProblemBasic 问题基础表
type ProblemBasic struct {
types.BaseDbModel
Identity string `json:"identity" gorm:"column:identity;type:varchar(36);comment:'问题表的唯一标识'"` // 问题表的唯一标识
ProblemCategories []ProblemCategory `json:"problem_categories" gorm:"ForeignKey:ProblemId;comment:'关联问题分类表'"` // 关联问题分类表
Title string `json:"title" gorm:"column:title;type:varchar(255);comment:'文章标题'"` // 文章标题
Content string `json:"content" gorm:"column:content;type:text;comment:'文章正文'"` // 文章正文
MaxRuntime int `json:"max_runtime" gorm:"column:max_runtime;type:int(11);comment:'最大运行时长'"` // 最大运行时长
MaxMem int `json:"max_mem" gorm:"column:max_mem;type:int(11);comment:'最大运行内存'"` // 最大运行内存
TestCases []TestCase `json:"test_cases" gorm:"foreignKey:ProblemIdentity;comment:'关联测试用例表'"` // 关联测试用例表
PassNum int64 `json:"pass_num" gorm:"column:pass_num;type:int(11);comment:'通过次数'"` // 通过次数
SubmitNum int64 `json:"submit_num" gorm:"column:submit_num;type:int(11);comment:'提交次数'"` // 提交次数
}
func (table *ProblemBasic) TableName() string {
return "problem_basic"
}

View File

@@ -4,12 +4,12 @@ import (
"online_code/common/types"
)
// ProblemCategory 问题分类表
// ProblemCategory 问题分类关联
type ProblemCategory struct {
types.BaseDbModel
ProblemId uint `json:"problem_id" gorm:"column:problem_id;type:int(11);comment:'问题的ID'" ` // 问题的ID
CategoryId uint `json:"category_id" gorm:"column:category_id;type:int(11);comment:'分类的ID'" ` // 分类的ID
CategoryBasic *Category `json:"category_basic" gorm:"foreignKey:id;references:category_id;comment:'关联分类的基础信息表'" ` // 关联分类的基础信息表
ProblemId uint `json:"problem_id" gorm:"column:problem_id;type:int(11);comment:'问题的ID'" ` // 问题的ID
CategoryId uint `json:"category_id" gorm:"column:category_id;type:int(11);comment:'分类的ID'" ` // 分类的ID
CategoryBasic *CategoryBasic `json:"category_basic" gorm:"foreignKey:CategoryId;comment:'关联分类的基础信息表'" ` // 关联分类的基础信息表
}
func (table *ProblemCategory) TableName() string {

View File

@@ -1,16 +0,0 @@
package entity
// Submit 提交表
type Submit struct {
Identity string `json:"identity" gorm:"column:identity;type:varchar(36);comment:'用户唯一标识'" ` // 唯一标识
ProblemIdentity string `json:"problem_identity" gorm:"column:problem_identity;type:varchar(36);comment:'问题表的唯一标识'" ` // 问题表的唯一标识
ProblemBasic *Problem `json:"problem_basic" gorm:"foreignKey:identity;references:problem_identity;comment:'关联问题基础表'" ` // 关联问题基础表
UserIdentity string `json:"user_identity" gorm:"column:user_identity;type:varchar(36);comment:'用户表的唯一标识'" ` // 用户表的唯一标识
UserBasic *User `json:"user_basic" gorm:"foreignKey:identity;references:user_identity;comment:'联用户基础表'" ` // 关联用户基础表
Path string `json:"path" gorm:"column:path;type:varchar(255);comment:'代码存放路径'" ` // 代码存放路径
Status int `json:"status" gorm:"column:status;type:tinyint(1);comment:'状态'" ` // 【-1-待判断1-答案正确2-答案错误3-运行超时4-运行超内存, 5-编译错误】
}
func (table *Submit) TableName() string {
return "submit"
}

View File

@@ -0,0 +1,19 @@
package entity
import "online_code/common/types"
// SubmitBasic 提交表
type SubmitBasic struct {
types.BaseDbModel
Identity string `json:"identity" gorm:"column:identity;type:varchar(36);comment:'用户唯一标识'" ` // 唯一标识
ProblemIdentity string `json:"problem_identity" gorm:"column:problem_identity;type:varchar(36);comment:'问题表的唯一标识'" ` // 问题表的唯一标识
ProblemBasic *ProblemBasic `json:"problem_basic" gorm:"foreignKey:ProblemIdentity;comment:'关联问题基础表'" ` // 关联问题基础表
UserIdentity string `json:"user_identity" gorm:"column:user_identity;type:varchar(36);comment:'用户表的唯一标识'" ` // 用户表的唯一标识
UserBasic *UserBasic `json:"user_basic" gorm:"foreignKey:UserIdentity;comment:'联用户基础表'" ` // 关联用户基础表
Path string `json:"path" gorm:"column:path;type:varchar(255);comment:'代码存放路径'" ` // 代码存放路径
Status int `json:"status" gorm:"column:status;type:tinyint(1);comment:'状态'" ` // 【-1-待判断1-答案正确2-答案错误3-运行超时4-运行超内存, 5-编译错误】
}
func (table *SubmitBasic) TableName() string {
return "submit_basic"
}

View File

@@ -5,8 +5,8 @@ import "online_code/common/types"
// TestCase 测试用例
type TestCase struct {
types.BaseDbModel
Identity string `json:"identity" gorm:"column:identity;type:varchar(255);comment:'用户唯一标识''" `
ProblemIdentity string `json:"problem_identity" gorm:"column:problem_identity;type:varchar(255);comment:'问题'" `
Identity string `json:"identity" gorm:"column:identity;type:varchar(36);comment:'测试用例唯一标识''" `
ProblemIdentity string `json:"problem_identity" gorm:"column:problem_identity;type:varchar(36);comment:'问题'" `
Input string `json:"input" gorm:"column:input;type:text;" `
Output string `json:"output" gorm:"column:output;type:text;" `
}

View File

@@ -2,7 +2,7 @@ package entity
import "online_code/common/types"
type User struct {
type UserBasic struct {
types.BaseDbModel
Identity string `json:"identity" gorm:"column:identity;type:varchar(36);comment:'用户唯一标识'"`
Name string `json:"name" gorm:"column:name;type:varchar(100);not null;comment:'用户名'"`
@@ -14,6 +14,6 @@ type User struct {
IsAdmin int `json:"is_admin" gorm:"column:is_admin;type:tinyint(1);comment:'是否是管理员【0-否1-是】'"`
}
func (table *User) TableName() string {
return "user"
func (table *UserBasic) TableName() string {
return "user_basic"
}

7
models/param/base.go Normal file
View File

@@ -0,0 +1,7 @@
package param
// 分页通用参数
type page struct {
Current int `json:"current" form:"current" binding:"required"` // 页码
Size int `json:"size" form:"size" binding:"required"` // 每页数量
}

7
models/param/problem.go Normal file
View File

@@ -0,0 +1,7 @@
package param
type GetProblemList struct {
page
Keyword string `json:"keyword" form:"keyword"` // 问题关键字
CategoryIdentity string `json:"category_identity" form:"category_identity"` // 问题分类标识
}

8
models/param/submit.go Normal file
View File

@@ -0,0 +1,8 @@
package param
type GetSubmitList struct {
page
ProblemIdentity string `json:"problemIdentity" form:"problemIdentity"`
UserIdentity string `json:"userIdentity" form:"userIdentity"`
Status int `json:"status" form:"status"`
}

19
repository/base.go Normal file
View File

@@ -0,0 +1,19 @@
package repository
import "gorm.io/gorm"
// 分页组件
func page(current, size int) func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
if current == 0 {
current = 1
}
if size < 1 {
size = 10
}
// 计算偏移量
offset := (current - 1) * size
// 返回组装结果
return db.Offset(offset).Limit(size)
}
}

35
repository/problem.go Normal file
View File

@@ -0,0 +1,35 @@
package repository
import (
"online_code/client"
"online_code/models/entity"
"online_code/models/param"
)
type problemService struct{}
func ProblemService() *problemService {
return &problemService{}
}
// GetList 获取题目列表
func (problemService) GetList(p param.GetProblemList) (records []entity.ProblemBasic, count int64, err error) {
sel := client.MySQL.Scopes(page(p.Current, p.Size)).Preload("ProblemCategories").Preload("ProblemCategories.CategoryBasic")
if p.Keyword != "" {
sel.Where("title LIKE ? OR content like ?", "%"+p.Keyword+"%", "%"+p.Keyword+"%")
}
if p.CategoryIdentity != "" {
sel.Joins("RIGHT JOINN problem_category pc on pc.problem_id = problem.id").
Where("pc.category_id= (SELECT cb.id FROM category cb WHERE cb.identity = ?)", p.CategoryIdentity)
}
err = sel.Order("updated_at DESC").Find(&records).Offset(-1).Limit(-1).Count(&count).Error
return
}
// GetProblemInfo 获取题目详情
func (problemService) GetProblemInfo(id int) (problem entity.ProblemBasic, err error) {
err = client.MySQL.Where("id = ?", id).Find(&problem).Error
return
}

28
repository/submit.go Normal file
View File

@@ -0,0 +1,28 @@
package repository
import (
"online_code/client"
"online_code/models/entity"
"online_code/models/param"
)
type submitService struct{}
func SubmitService() *submitService {
return &submitService{}
}
func (submitService) GetList(p param.GetSubmitList) (records []entity.SubmitBasic, count int64, err error) {
tx := client.MySQL.Scopes(page(p.Current, p.Size))
if p.ProblemIdentity != "" {
tx = tx.Where("problem_identity = ?", p.ProblemIdentity)
}
if p.UserIdentity != "" {
tx = tx.Where("user_identity = ?", p.UserIdentity)
}
if p.Status != 0 && p.Status != 1 && p.Status != 2 {
tx = tx.Where("status = ?", p.Status)
}
err = tx.Offset(-1).Limit(-1).Count(&count).Find(&records).Error
return
}

7
repository/user.go Normal file
View File

@@ -0,0 +1,7 @@
package repository
type userService struct{}
func UserService() *userService {
return &userService{}
}

10
route/problem.go Normal file
View File

@@ -0,0 +1,10 @@
package route
import (
"github.com/gin-gonic/gin"
"online_code/api"
)
func problem(g *gin.RouterGroup) {
g.GET("/list", api.ProblemApi().GetProbleList)
}

8
route/router.go Normal file
View File

@@ -0,0 +1,8 @@
package route
import "github.com/gin-gonic/gin"
func InitRoute(g *gin.RouterGroup) {
problem(g.Group("/problem"))
user(g.Group("/user"))
}

10
route/user.go Normal file
View File

@@ -0,0 +1,10 @@
package route
import (
"github.com/gin-gonic/gin"
"online_code/api"
)
func user(g *gin.RouterGroup) {
g.GET("/:identity", api.UserApi().GetUserInfo) // 获取用户详情
}

14
utils/page.go Normal file
View File

@@ -0,0 +1,14 @@
package utils
// GenTotalPage 计算总页数
func GenTotalPage(count int64, size int) int {
totalPage := 0
if count > 0 {
upPage := 0
if int(count)%size > 0 {
upPage = 1
}
totalPage = (int(count) / size) + upPage
}
return totalPage
}

20
utils/token.go Normal file
View File

@@ -0,0 +1,20 @@
package utils
import (
"golang.org/x/crypto/bcrypt"
)
// 生成Token的密钥写死防止乱改
//var jwtSecret = "qSxw4fCBBBecPsws"
// HashPassword 加密密码
func HashPassword(pass *string) {
bytePass := []byte(*pass)
hPass, _ := bcrypt.GenerateFromPassword(bytePass, bcrypt.DefaultCost)
*pass = string(hPass)
}
// ComparePassword 校验密码
func ComparePassword(dbPass, pass string) bool {
return bcrypt.CompareHashAndPassword([]byte(dbPass), []byte(pass)) == nil
}