Commits (49)
config.yaml
backend/files
.history/
.idea/**
docs/**
**.exe
files/**
**.log
**.sql
mysql
# 众多无视的扩展
*.bak
*.patch
*.diff
*.err
# git冲突合并的临时文件
*.orig
*.log
*.rej
*.swo
*.swp
*.zip
*.vi
*~
*.sass-cache
*.tmp.html
*.dump
#操作系统或编辑器文件夹
.DS_Store
._*
.cache
.project
.settings
.tmproj
*.esproj
*.sublime-project
*.sublime-workspace
nbproject
thumbs.db
*.iml
# F忽略的文件夹
.hg
.svn
.CVS
.idea
node_modules/
jscoverage_lib/
bower_components/
dist/
# pastebin
## Getting started
To make it easy for you to get started with GitLab, here's a list of recommended next steps.
Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
## Add your files
- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command:
```
cd existing_repo
git remote add origin http://xlab.zju.edu.cn/git/chenhan/pastebin.git
git branch -M main
git push -uf origin main
```
## Integrate with your tools
- [ ] [Set up project integrations](http://xlab.zju.edu.cn/git/chenhan/pastebin/-/settings/integrations)
## Collaborate with your team
- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
- [ ] [Automatically merge when pipeline succeeds](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html)
## Test and Deploy
Use the built-in continuous integration in GitLab.
- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html)
- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing(SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
***
# Editing this README
When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thank you to [makeareadme.com](https://www.makeareadme.com/) for this template.
## Suggestions for a good README
Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
## Name
Choose a self-explaining name for your project.
## Description
Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
## Badges
On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
## Visuals
Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
## Installation
Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
## Usage
Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
## Support
Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
## Roadmap
If you have ideas for releases in the future, it is a good idea to list them in the README.
## Contributing
State if you are open to contributions and what your requirements are for accepting them.
For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
## Authors and acknowledgment
Show your appreciation to those who have contributed to the project.
## License
For open source projects, say how it is licensed.
## Project status
If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
# lot-backend
FROM golang:1.19.13-alpine3.18 AS go-builder
ENV \
GO111MODULE=on \
CGO_ENABLED=0
WORKDIR /home/pastebin
COPY ./ backend/
WORKDIR /home/pastebin/backend
# Build your Go code
RUN go build -o ./pastebin .
# Final image for running only the built executable
FROM alpine:latest
# Copy Go backend executable
COPY --from=go-builder /home/pastebin/backend/pastebin /home/pastebin/backend/
COPY --from=go-builder /home/pastebin/backend/config.yaml /home/pastebin/backend/
# 设置工作目录,这里需要根据实际情况修改
WORKDIR /home/pastebin/backend
# 你可以添加其他一些需要的命令或设置,根据实际情况进行调整
# 例如,你可能需要设置启动命令
CMD ["/home/pastebin/backend/pastebin"]
package controller
import (
"backend/model"
"bytes"
"encoding/json"
"io"
"math/rand"
"mime/multipart"
"net/http"
"strconv"
"time"
"github.com/labstack/echo/v4"
"github.com/sirupsen/logrus"
"github.com/spf13/viper"
)
// =======authenticate==========
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
/*
* generate new uuid
*/
func IdGen(n int) string {
b := make([]rune, n)
rand.Seed(time.Now().UnixNano())
//for i:=0;i<n;i++{
for i := range b {
b[i] = letters[rand.Intn(len(letters))]
}
return string(b)
}
/*
func IdGen(n int ) string{
id:=newId(n)
var check bool
// 数据库里查找有无sid与id相同
while (!check){
}
}
*/
/*
* 新建一个sid和url的关联,并返回一个bool表示成功与否
* 若无sessionId,生成一个八位sid并返回
*/
func newAuthenticate(sid string, url string, passwd string, time time.Time, isFirst bool) (rsid string, stat uint) {
if sid == "" {
rsid = IdGen(8)
} else {
rsid = sid
}
if isFirst {
model.CreatelinkFirstTime(rsid, url, time)
stat = 1
} else {
stat = model.Createlink(rsid, passwd, url, time)
}
return rsid, stat
}
/*
* 判断用户有无权限访问
* sid 用cookie 储存传输,如果新生成sid,则保存至cookie
*/
func Autheticate(cookie *http.Cookie, url string, passwd string, time time.Time) uint {
sid := cookie.Value
if model.Find(sid, url) {
return 1 // 鉴权通过
} else {
var stat uint
sid, stat = newAuthenticate(sid, url, passwd, time, false)
cookie.Value = sid
return stat
}
}
// 设置cookie name sid, value link
func SetCookie(c echo.Context, cookie *http.Cookie, sid string, maxAge int, time_ time.Time) error {
cookie.Name = "User" // 标识为user
//cookie.Value = string(uuid) // 通过uuid和数据库,确定user是谁
cookie.Value = sid
cookie.Path = "/"
// cookie有效期为3600秒
if maxAge == 0 {
if time_.IsZero() {
cookie.MaxAge = 3600
} else {
cookie.MaxAge = int(time.Until(time_).Seconds())
if cookie.MaxAge <= 0 {
cookie.MaxAge = 3600
}
}
} else {
cookie.MaxAge = maxAge
}
// 设置cookie
c.SetCookie(cookie)
return nil
}
func FileRead(file *multipart.FileHeader) (string, int, error) {
// 打开用户上传的文件
src, err := file.Open()
if err != nil {
logrus.Println(err)
return "", 0, err
}
defer src.Close()
//读取文件内容
var data []byte
size := 0
buf := make([]byte, 1024)
for {
n, err := src.Read(buf)
if err != nil && err != io.EOF {
logrus.Panic(err)
return "", 0, err
}
//说明读取结束
if n == 0 {
break
}
//读取到最终的缓冲区中
data = append(data, buf[:n]...)
size = size + n
if size > Settings.MaxSize+1024*2 {
break // 超过大小
}
}
str := string(data)
return str, size, nil
}
/*
* 判断文件大小是否超过阈值(threshold,单位B)
*/
func overflow(content string, threshold int) bool {
len := len(content)
// 大小比较
if len > threshold {
return true
} else {
return false
}
}
// / ========== DB related==================
func DBupdate(c echo.Context, info *Upload) (string, string) {
url := "http://pastebin/" + IdGen(8)
cookie, _ := c.Cookie("User")
var sid string
if cookie == nil {
sid = ""
} else {
sid = cookie.Value
}
sid, _ = newAuthenticate(sid, url, url, info.Expiration, true)
if info.MaxView == 0 {
//num,_=strconv.ParseUint(GetSetting("maxDefaultAccess"), 10, 64)
info.MaxView = uint(Settings.MaxDefaultView) // 设置最大默认可访问次数
}
model.Savetext(info.Content, info.MaxView, info.Passwd, info.Expiration, url, info.Type, info.Name, info.HighLight)
return sid, url
}
/*
* 没有设定过期时间,oriTime 为 0
* 此函数将默认过期时间设为当前时间后半小时
*/
func timeAssign(oriTime time.Time) time.Time {
if oriTime.IsZero() {
return time.Now().Add(30 * time.Minute)
} else {
return oriTime
}
}
// ===========sumdry=======================
// 从config读取数据
// 从配置文件里面读取设置
func InitSettings() {
viper.SetConfigName("config")
viper.SetConfigType("yaml")
viper.AddConfigPath("./")
err := viper.ReadInConfig()
if err != nil {
logrus.Panic(err)
}
// connect to database
logInfo := viper.GetStringMapString("setting")
Settings.Url = logInfo["url"]
if Settings.Url == "" {
logrus.Fatal("can not get domain name from config")
}
Settings.MaxDefaultView, err = strconv.Atoi(logInfo["maxdefaultview"])
if err != nil {
logrus.Fatal(err)
}
Settings.MaxSize, err = strconv.Atoi(logInfo["maxsize"])
if err != nil {
logrus.Fatal(err)
}
}
/*
* 通过文件扩展名获取ContentType
*/
func GetFileContentType(fileType string) string {
var StrRet string = ""
switch fileType {
case ".txt":
StrRet = "text/plain"
case ".csv":
StrRet = "text/csv"
case ".tex":
StrRet = "application/x-tex"
case ".md":
StrRet = "text/x-markdown"
default:
StrRet = "text/plain"
}
return StrRet
}
// 格式化后缀,仿止出错
func TypeComplement(typ string) string {
// 类型判断
if typ == "" {
typ = ".txt"
} else if typ[0] != '.' {
typ = "." + typ
}
return typ
}
func myMarshal(data interface{}) (string, error) {
bf := bytes.NewBuffer([]byte{})
jsonEncoder := json.NewEncoder(bf)
jsonEncoder.SetEscapeHTML(false)
if err := jsonEncoder.Encode(data); err != nil {
return "", err
}
return bf.String(), nil
}
package controller
import (
"backend/app/response"
"backend/model"
"net/http"
"time"
"github.com/labstack/echo/v4"
"github.com/sirupsen/logrus"
)
func Ping(c echo.Context) error {
// just test
return response.SendResponse(c, http.StatusOK, "", "pong!")
}
// 申请一个包含uid的cookie
func AskUid(c echo.Context) error {
nsid := IdGen(8)
info := new(Upload)
cookie := new(http.Cookie)
err := SetCookie(c, cookie, nsid, info.MaxAge, info.Expiration)
if err != nil {
return err
} else {
return c.HTML(http.StatusOK, "success")
}
}
// 成功则返回上传成功,否则报错
// sessionId不直接绑定,通过cookie传
func TextUp(c echo.Context) error {
info := new(Upload)
if err := c.Bind(info); err != nil {
return err
}
info.Expiration = timeAssign(info.Expiration) // 默认时间
if overflow(info.Content, 8*1024*1024) {
return response.SendResponse(c, http.StatusForbidden, "error:文件上传失败: 文件大小超过8MB.", "")
}
// 更新数据库
_, url := DBupdate(c, info) // 不分配uid
//return response.SendResponse2(c, http.StatusOK, "文件上传成功", info.TextName, GetTextContentType(info.TextType), url)
return response.SendResponse(c, http.StatusOK, "文件上传成功", url)
}
func FileUp(c echo.Context) error {
info := new(Upload)
if err := c.Bind(info); err != nil {
return err
}
info.Expiration = timeAssign(info.Expiration) // 默认时间
file, err := c.FormFile("file")
if err != nil {
logrus.Println(err)
return err
}
info.Name = file.Filename
data := new(response.Msg2)
info.Content, data.Size, _ = FileRead(file)
info.MaxView = uint(Settings.MaxDefaultView)
if data.Size > 8*1024*1024 {
return response.SendResponse(c, http.StatusForbidden, "error:文件上传失败: 文件大小超过8MB.", "")
}
// 更新数据库
_, data.Url = DBupdate(c, info) // 不分配uid
data.Name = info.Name
//dataj, err := myMarshal(data)
if err != nil {
logrus.Println(err)
return err
}
//return response.SendResponse2(c, http.StatusOK, "文件上传成功", info.TextName, GetTextContentType(info.TextType), url)
return response.SendResponse2(c, http.StatusOK, "文件上传成功", *data)
}
/*
* 输入:前端提供的文件链接,
* 返回:一个可供URL访问的链接(string)
* cookie.Value 传sessionId
*/
func Down(c echo.Context) error {
info := new(Download)
if err := c.Bind(info); err != nil {
logrus.Println(err)
return err
}
//info.Time = timeAssign(info.Time) // 默认时间为当前半小时后
cookie, _ := c.Cookie("User")
cookieMsg := ""
if cookie == nil {
// cookieMsg = "没有cookie,已分配.\n"
cookie = new(http.Cookie)
SetCookie(c, cookie, IdGen(8), 1800, time.Time{})
}
//c.Request().URL.
// 鉴权
stat := Autheticate(cookie, info.Url, info.Passwd, time.Now().Add(1800)) // 包含创建链接Createlink
// response
switch stat {
case 0:
return response.SendResponse(c, http.StatusForbidden, cookieMsg+"error:密码错误", info.Url) //403
case 1: // 鉴权通过
Data := new(response.Msg)
Data.Content = model.Find1(info.Url, "content") // 文件内容
Data.Type = GetFileContentType(model.Find1(info.Url, "TextType")) // 文件类型
Data.Name = model.Find1(info.Url, "TextName")
Data.Url = info.Url
//dataj, _ := myMarshal(Data) // 文件名
return response.SendResponse1(c, http.StatusOK, cookieMsg+"success", *Data) // 返回数据
case 2:
return response.SendResponse(c, http.StatusGone, cookieMsg+"error:内容过期", "") //410
case 3:
return response.SendResponse(c, http.StatusUnauthorized, cookieMsg+"error:内容过期", "") //401
case 4:
return response.SendResponse(c, http.StatusInternalServerError, cookieMsg+"error:Internal Server Error", "") //500
}
return nil
}
package controller
import (
"time"
)
type Upload struct {
Content string `json:"Content" form:"Content" query:"Content"`
Passwd string `json:"Passwd" form:"Passwd" query:"Passwd"`
Name string `json:"Name" form:"Name" query:"Name"`
Type string `json:"Type" form:"Type" query:"Type"`
HighLight bool `json:"HighLight" form:"HighLight" query:"HighLight"`
Expiration time.Time `json:"Expiration" form:"Expiration" query:"Expiration"`
MaxAge int `json:"MaxAge" form:"MaxAge" query:"MaxAge"` // 用户指定的时间期限
MaxView uint `json:"MaxView" form:"MaxView" query:"MaxView"` // 文件最大可访问次数
//Expiry time.Time `json:"expiry"` // 有效期
//Content string `json:"content"`
}
type Download struct {
Passwd string `json:"Passwd" form:"Passwd" query:"Passwd"`
Url string `json:"Url" form:"Url" query:"Url"`
}
// 从config文件获取
type Setting struct {
Url string // 配置的域名
MaxDefaultView int // 单文件默认最大可访问次数
MaxSize int // 文件最大可上传大小(单位B)
}
var Settings Setting
package app
import (
"backend/app/controller"
"backend/utils"
//"net/http"
"github.com/labstack/echo/v4"
"github.com/sirupsen/logrus"
)
var e *echo.Echo
func InitWebFramework() {
e = echo.New()
e.HideBanner = true
addRoutes()
e.Validator = &utils.CustomValidator{}
logrus.Info("echo framework initialized")
}
func StartServer() {
e.Logger.Fatal(e.Start(controller.Settings.Url)) // 启动服务,注意默认端口80不能省略
//e.Logger.Fatal(e.Start("0.0.0.0:8080")) // 监听所有端口
//e.Logger.Fatal(e.Start("127.0.0.1:80")) // 启动服务,注意默认端口80不能省略
//e.Logger.Fatal(e.Start("http://xlab.zju.edu.cn/test/pastebin/group-1:80")) // 启动服务,注意默认端口80不能省略,需要域名解析,config
}
/*
* 初始化logger设置
*/
func InitLogger() {
//自定义日志格式
logrus.SetFormatter(&logrus.TextFormatter{
ForceQuote: true, //键值对加引号
TimestampFormat: "2006-01-02 15:04:05", //时间格式
FullTimestamp: true,
})
logrus.SetReportCaller(true)
}
package middleware
import (
"fmt"
"github.com/labstack/echo/v4"
"github.com/sirupsen/logrus"
)
func Auth(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
mylogger(c)
uid := getUid(c)
c.Set("uid", uid)
return next(c)
}
}
/*
向文件或 stdout 输出详细的日志,记录用户的 User-Agent、IP 地址、访问时间、访问路径等信息
User-Agent,访问路径,暂且无法实现
*/
func mylogger(c echo.Context) {
//c.GET("User-Agent")
userAgent := c.Get("User-Agent")
ip := echo.ExtractIPDirect()(c.Request())
msg := "User-Agent:" + fmt.Sprint(userAgent) + ",ip:" + ip + ",path:" + c.Path()
logrus.Println(msg)
}
// 检测 uid 是否有效
func isUidValid(uid string) bool {
if uid == "" {
return false
}
return true
}
// 从报文中获得uid,给新用户分配uid
func getUid(c echo.Context) interface{} {
cookie, err := c.Cookie("User")
if err != nil || !isUidValid(cookie.Value) {
logrus.Println("uid invalid,分配新uid")
return 114514
} else {
return cookie.Value
}
}
package response
import (
"net/http"
"github.com/labstack/echo/v4"
)
type Msg struct {
Name string
Type string
Content string
Url string
}
type Msg2 struct {
Url string
Name string
Size int
}
type Response struct {
Code int `json:"Code"`
Msg string `json:"Msg"`
Data interface{} `json:"Data"`
}
type Response1 struct {
Code int `json:"Code"`
Msg string `json:"Msg"`
Data Msg `json:"Data"`
}
type Response2 struct {
Code int `json:"Code"`
Msg string `json:"Msg"`
Data Msg2 `json:"Data"`
}
func SendResponse(c echo.Context, code int, msg string, data ...interface{}) error {
return c.JSON(http.StatusOK, Response{
Code: code,
Msg: msg,
Data: data,
})
}
func SendResponse1(c echo.Context, code int, msg string, data Msg) error {
return c.JSON(http.StatusOK, Response1{
Code: code,
Msg: msg,
Data: data,
})
}
func SendResponse2(c echo.Context, code int, msg string, data Msg2) error {
return c.JSON(http.StatusOK, Response2{
Code: code,
Msg: msg,
Data: data,
})
}
// 返回err
func SendResponse4(c echo.Context, err error) error {
return c.JSON(http.StatusBadRequest, err.Error())
}
package app
import (
"backend/app/controller"
"backend/app/middleware"
//"backend/model"
)
func addRoutes() {
api := e.Group("api")
api.Use(middleware.Auth)
api.GET("/ping", controller.Ping) // 测试用
// 前端向后端申请一个包含sessionId的cookie,暂不不支持自定义cookie有效时间,默认半小时
api.GET("/ask/sid", controller.AskUid)
/* 前端用于上传文件的接口,
* 传输文件内容,content:"xxx", 以文本形式传输
* 使用时带上文件类型,如fileType:"application/x-tex",不设置则下载时无法告知前端文件类型,默认为"text/plain"
* 使用时最好带cookie,没有就用申请api申请。
* 其余参数可以不设置,有效期默认半小时后, 最大可访问次数默认30(config文件)
*/
api.POST("/file/upload", controller.FileUp) // 接收文件
/*
* 前端用于下载文件的接口
* 需要cookie,url,passwd
* 使用时带cookie。如果cookie有权限,则直接返回有文件内容的response。没有则返回一个需要密码的状态码
* 前端跳转密码输入界面获取密码后带上密码再次使用此接口,密码正确则给接口权限,返回内容
* 若文件没设密码则不带密码也可访问
* 状态码: 403:密码错误; 410:内容过期; 200:访问成功; 401:请进行身份验证(输密码)
*/
api.POST("/file/download", controller.Down)
api.POST("/text/upload", controller.TextUp)
api.POST("/text/download", controller.Down)
}
func ApiAssign() {
}
module backend
go 1.19
require (
github.com/go-playground/validator/v10 v10.11.1
github.com/labstack/echo/v4 v4.9.1
github.com/sirupsen/logrus v1.9.0
github.com/spf13/viper v1.14.0
gorm.io/driver/mysql v1.4.4
gorm.io/gorm v1.24.2
)
require (
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/go-playground/locales v0.14.0 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/go-sql-driver/mysql v1.6.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/labstack/gommon v0.4.0 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/magiconair/properties v1.8.6 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
github.com/spf13/afero v1.9.2 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.4.1 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.1 // indirect
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect
golang.org/x/net v0.0.0-20221014081412-f15817d10f9b // indirect
golang.org/x/sys v0.0.0-20220908164124-27713097b956 // indirect
golang.org/x/text v0.4.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
This diff is collapsed.
// @title Golang Service Template
// @version 0.1
// @description Golang back-end service template, get started with back-end projects quickly
// @BasePath /api
package main
import (
"backend/app"
"backend/app/controller"
"backend/model"
)
func main() {
app.InitLogger() // 初始化logger设置
controller.InitSettings() // 从配置文件中获取设置
model.Init()
app.InitWebFramework()
app.StartServer()
}
package model
import (
//"fmt"
"time"
// "math/rand"
"github.com/sirupsen/logrus"
"github.com/spf13/viper"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
var DB *gorm.DB
func Init() {
ConnectDatabase()
var err error
// insert auto-table
err = DB.AutoMigrate(&Content{})
if err != nil {
logrus.Fatal(err)
}
err = DB.AutoMigrate(&Users{})
if err != nil {
logrus.Fatal(err)
}
err = DB.AutoMigrate(&Rel{})
if err != nil {
logrus.Fatal(err)
}
}
func ConnectDatabase() {
// config
viper.SetConfigName("config")
viper.SetConfigType("yaml")
viper.AddConfigPath("./")
err := viper.ReadInConfig()
if err != nil {
logrus.Panic(err)
}
// connect to database
logInfo := viper.GetStringMapString("mysql")
sqlInfo := logInfo["user"] + ":" + logInfo["password"] +
"@(" + logInfo["host"] + ")/" + logInfo["database"] + "?charset=utf8mb4&parseTime=True&loc=Local"
DB, err = gorm.Open(mysql.Open(sqlInfo), &gorm.Config{})
if err != nil {
logrus.Panic(err)
}
}
// Backcheck content through link
// 通过链接反查内容
func Findlink(_url string) string {
var p Content
err := DB.First(&p, "Url1 = ?", _url)
if err != nil {
logrus.Error(err)
}
return p.S
}
// save text information
// 保存文本和文件信息
func Savetext(_S string, _Time uint, _Passwd string, _Time1 time.Time, _url string, _Filetype string, _Filename string,_Hlight bool) {
var id1 int64
DB.Model(&Content{}).Count(&id1)
id1++
p := Content{
ID: id1,
S: _S,
Time: _Time,
Passwd: _Passwd,
Date: time.Now(),
Time1: _Time1,
Time2: 0,
Url1: _url,
Filetype: _Filetype,
Filename: _Filename,
Hlight: _Hlight,
}
DB.Create(&p)
}
// Check whether the number of visits exceeds the threshold and the time limit
// 检查是否超过总访问次数和截止时间
func Checkt(p Content) bool {
t := time.Now()
p.Time2++
//fmt.Println(p.Time2)
if p.Time2 > p.Time || t.After(p.Time1) {
DB.Delete(&p)
return true
}
DB.Model(&Content{}).Where("Url1 = ?", p.Url1).Update("Time2", p.Time2)
return false
}
// 这个函数那边有可能需要
// 检查sid_url是否超时
//同时也可以查询sid,_url是否关联
func Find(sid string,_url string) bool {
var s Rel
err := DB.Where(&Rel{Sid: sid,Url: _url}).First(&s).Error
if err != nil {
return false
}
t := time.Now()
if t.After(s.Time) {
DB.Delete(&s)
return false
} else {
return true
}
}
// 通过url查询文件类型
func Find1(_url string, key string) string {
var p Content
DB.First(&p, "Url1 = ?", _url)
switch key {
case "content":
return p.S
case "fileType":
return p.Filetype
case "fileName":
return p.Filename
}
return ""
}
// 通过url查询文件是否高亮
func Find2(_url string) bool {
var p Content
DB.First(&p, "Url1 = ?", _url)
return p.Hlight
}
/*//*随机生成字符串
func randStr(n int) string {
rand.Seed(time.Now().Unix())
b := make([]rune, n)
for i := range b {
b[i] = letters[rand.Intn(len(letters))]
}
return string(b)
}*/
// 新建用户
func Createuser(_User Users) {
//_User.SessionId=randStr(8)
DB.Create(&_User)
}
//新建链接
/* 返回值:
3 表示sid_url已经过期,需要重新分配
2 表示内容过期
1 表示密码正确
0 表示密码不正确
*/
func Createlink(sid string, _passwd string, _url string, _Time1 time.Time) uint {
var p Content
DB.First(&p,"Url1 = ?", _url)
if p.Passwd == _passwd {
if Checkt(p) {
//fmt.Println(sid,_passwd,_url,_Time1)
return 2
}
var p1 Rel
err := DB.Where(&Rel{Sid: sid,Url: _url}).First(&p1).Error
if err != nil {
rel1 := Rel{
Sid: sid,
Url: _url,
Time: _Time1,
}
DB.Create(&rel1)
} else {
p1.Time = _Time1
DB.Save(&p1)
}
return 1
}
return 0
}
// 第一次上传
func CreatelinkFirstTime(sid string, _url string, _Time1 time.Time) {
sid1 := Rel{
Sid: sid,
Url: _url,
Time: _Time1,
}
DB.Create(&sid1)
}
/*
func Test() {
fmt.Println(Find("ab","12345"))
}*/
package model
import (
"time"
"gorm.io/gorm"
)
type Content struct {
ID int64 `gorm:"primarykey"`
S string
Time uint
Passwd string
Date time.Time
Size uint
Time1 time.Time
Time2 uint
Url1 string
Filetype string
Filename string
Hlight bool
}
type Rel struct {
gorm.Model
Sid string
Url string
Time time.Time
}
type Users struct {
SessionId string
Username string
Passwd string
Name string
Size uint
Route string
}
文件结构:
app:
controller:
init:route对应的handle函数
foo: handle函数需要调用的函数
middleware:
login:登录中间件,添加uid,增加日志
response:
response:自定义reseponse,都采用json返回信息
init: 网络初始化,域名在config.yaml获取。
日志自定义格式
routes: 路由分配
model:
init: 数据库相关函数,包含着有关和数据库交互的各种操作,比如连接数据库,通过数据库反查内容,保存文本和文件信息等操作的函数
model: 存储着各表的结构,一共有五个结构体
config.yaml: 修改设置
go.mod
go.sum
main.go
readme
api:
见route.go
\ No newline at end of file
package utils
import (
"sync"
"github.com/go-playground/validator/v10"
)
type CustomValidator struct {
once sync.Once
validate *validator.Validate
}
func (c *CustomValidator) Validate(i interface{}) error {
c.lazyInit()
return c.validate.Struct(i)
}
func (c *CustomValidator) lazyInit() {
c.once.Do(func() {
c.validate = validator.New()
})
}
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Getting Started with Create React App
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
## Available Scripts
In the project directory, you can run:
### `npm start`
Runs the app in the development mode.\
Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
The page will reload when you make changes.\
You may also see any lint errors in the console.
### `npm test`
Launches the test runner in the interactive watch mode.\
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `npm run build`
Builds the app for production to the `build` folder.\
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.\
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `npm run eject`
**Note: this is a one-way operation. Once you `eject`, you can't go back!**
If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).
### Code Splitting
This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
### Analyzing the Bundle Size
This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
### Making a Progressive Web App
This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
### Advanced Configuration
This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
### Deployment
This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
### `npm run build` fails to minify
This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)