gitea项目比较复杂,初入手比较难上手,这里我简要对gitea这个项目做一个介绍。
目录
启动服务
`gitea`项目比较庞大,所以启动时尽量避免虚拟环境,否则会有相当大的性能损耗。
进入根目录,使用`make watch`就可以启动`gitea`服务。但可能会遇到这样的问题。
是因为你的终端不能同时打开过多数量的文件,这个时候就需要设置你终端的文件打开数量限制,如果是mac的话。使用这条命令`ulimit -n 5000`(设置最大同时打开5000个文件)
还有其他比较常用的make命令,`make clean`会清理缓存。`gitea`在启动后,会热更新,但是其热更新有时会无法生效。通用解决方法是:关闭后`gitea`服务,`sudo killall air;sudo killall gitea;`杀死所有相关进程,接着运行`make clean`。
有时前端的效果不能实时热更新,是因为浏览器有缓存,这个时候往往上面的方法没有效果,就需要重新打开一个隐私窗口。
代码提交前,可以使用`make lint`进行代码规范检
配置数据库
`gitea`在第一次启动时需要配置数据库,`gitea`支持多种数据库,`mysql`,`postgresql`等。第一次启动后能看到如下界面。
我们最少只需要配置数据库设置这里的几个参数,填写你数据库的种类,地址,用户名,密码和库名就可以初始化了。初始化成功后,会进入注册界面,第一个注册的用户就会变成超级管理员账号。
如果你选错了要使用的库,或者你需要重新使用另外一个数据库,只需要删除`custom/conf/app.ini`下的文件(删前最好备份下),重新`make watch`就能生成新的配置文件。
结构解析
这里大致来看一下gitea的整个项目结构,首先大致看下文件夹。
文件名功能templates前端页面前端页面前端jsrouters路由跟handlermodels数据库相关操作custom配置文件docs文档vendorgo包node_modules前端包后端
路由实现
gitea的项目通过`chi`和`macaron`来实现路由功能,相关内容在`routers/routes`下的`chi.go`和`macaron.go`下
路由基本是由`macaron`实现,`chi.go`文件下有`RegisterInstallRout`函数,其中调用到了`macaron`的路由,还包括`chi`的相关路由。可以看做是,原先项目想从`macaron`迁移到`chi`,但是挖了坑一直没填完。所以基本上所有路由相关都在`macaron.go`文件下。
handler
`handler`相关文件都放在了`routers`下,例如其中的`repo`文件夹,其中包含`repo`相关的`handler`。
`gitea`项目重新包装了`Context`内容,新增了诸如`User`,`Repo`内容。当登录后,`ctx.User`就会记录登录用户的相关信息。当进入组织或是项目中时,就会记录在`http://ctx.Org`或是`ctx.Repo`中。`ctx`还有一些常用的方法,例如`ctx.NotFound()`和`ServerError()`
`handler`包含两类,一类是`get`,一类是`post`。<br>
`get`请求与前端的渲染有关。渲染的前端文件就储存在`templates`中,一般会在最后执行`ctx.Html(200,tplName)`,需要渲染到前端的数据,通过`ctx.Data["name"]`来传送。如果需要获取url参数,例如`/notifications?q=read`这个路由,q的参数可以使用c.Query。
还可以使用路由匹配的方式
m.Get("/hello/:name", func(ctx *macaron.Context) string { return "Hello " + ctx.Params(":name") })`post`请求则是完成一些操作。`gitea`项目原本的`post`接口基本都是用`form`来一揽子传值。
`form`来传值都会在函数参数中包含`form`。例如`InstallPost(ctx *context.Context, form auth.InstallForm)`,直接使用`http://form.xxx` 就可以使用,如果需要添加内容,则需要在`form`结构体中新加。
也可以通过其他方式,例如通过`json`传值,通过固定的一套流程拿取前端传来的参数。
var params map[string]interface{} req, _ := ctx.Req.Body().Bytes() err := json.Unmarshal(req, ¶ms) if err != nil { ctx.ServerError("Unmarshal", err) } num, _ := strconv.Atoi(params["num"].(string)) waitingNum := int64(num)前端
前端主要是使用`jquery`,同时夹杂着部分页面使用的是`react`
template
`tmpl`文件使用后端数据有以下的一些常见用法。
{{if .xxx}}...{{else}}...{{end}} #判断 {{range .xxx}} {{end}} #遍历一个参数 {{$.xxx}} #全局参数 {{if and .xxx .xxx}} {{if or .xxx .xxx}} #与或 {{if eq .xxx 0}} #比较在`get`接口中设置的,例如`ctx.Data["IsEstimateOn"]=true`就可以在这里使用`{{if .IsEstimateOn}}<div>...</div>{{end}}`
前端发起请求的两种方式
前端发起请求时传值有两种方式<br>
一种是`form`方式 通过`form`内的元素值传值,方法是简单快捷,可扩展性不强。
注意要加上`{{.CsrfTokenHtml}}`相当于加上`token`
# 前端 templates/org/settings/options.tmpl <form class="ui form" action="{{.Link}}" method="post"> {{.CsrfTokenHtml}} </form>// 后端 // SettingsPost response for settings change submited func SettingsPost(ctx *context.Context, form auth.UpdateOrgSettingForm) { ...... } type UpdateOrgSettingForm struct { Namestring `binding:"Required;AlphaDashDot;MaxSize(40)" locale:"org.org_name_holder"` FullNamestring `binding:"MaxSize(100)"` Description string `binding:"MaxSize(255)"` Website string `binding:"ValidUrl;MaxSize(255)"` Locationstring `binding:"MaxSize(50)"` Visibilitystructs.VisibleType MaxRepoCreation int RepoAdminChangeTeamAccess bool }还可以使用`json`方式传值的方式<br>
为元素设置一个独一无二的`id`。使用`data-url`记录需要传的值。
<div id="waiting_line_on" data-url="{{.WaitingLineOnUrl}}" class="ui toggle checkbox"> <input type="checkbox" name="public" {{if .IsWaitingLineOn}}checked{{end}}> </div>在`js`里使用`jquery`语法来获取`data-url`内容,同时也可以对其他元素进行修改。
// waiting_line_onweb_src/js/index.js $(#waiting_line_on).click(function (e) { ┊ e.preventDefault(); ┊ if ($(#wait_line_num_hide).hasClass(hide)) { ┊ ┊ $(#wait_line_num_hide).removeClass(hide); ┊ } else { ┊ ┊ $(#wait_line_num_hide).addClass(hide); ┊ } ┊ $.ajax({ ┊ ┊ url: $(this).data(url), ┊ ┊ data: JSON.stringify({}), ┊ ┊ headers: { ┊ ┊ ┊ X-Csrf-Token: csrf, ┊ ┊ ┊ X-Remote: true, ┊ ┊ }, ┊ ┊ contentType: application/json, ┊ ┊ method: POST, ┊ }).done((res) => { ┊ ┊ if (res.status === success) {} ┊ }); });react用法
如果想要使用`react`,依然需要在`templates`里引用,在`tmpl`文件里,使用一个`div`和唯一的`id`
<div id="your-id" ></div>接着在`web_src/js/index.js`下来新建文件夹和文件来编写
import React, { useState, useEffect } from react; import ReactDOM from react-dom import Retrieval from ./Retrieval; function initRetrieval() { const el = document.getElementById(my-retrieval); if (!el) return; ReactDOM.render(<Retrieval />, el); } export default initRetrieval数据库
数据库通过`models/migrations`下进行`migrate`操作。每次更新数据库,例如加`column`,加表之类的操作,都会更新一个版本。
// v179 -> v180 migrations.go NewMigration("Create ProjectAction Table", createProjectAction),具体操作记录在`vxxx.go`下
数据库有`version`表,会记录下当前数据库版本,如果版本低于`migrations`内的版本,会自动更新。
数据库表
如果想知道相关内容保存在哪个表中,可以先进行操作,接着使用`pg_dump --data-only --inserts -U [用户名] [database名] > a.tmps`dump下记录,接着用`grep [要搜索的内容] b.tmps`来查找具体在哪里操作。
一些需要注意的内容
- postgresql 里查看user表时,user必须加引号`select * from "user"`
- user表中,用户跟组织都记录在其中
xorm操作注意
- Insert()方法需要使用指针,虽然非指针不会报错,但会带来隐含的错误
- Exec()可以执行sql语句,但是里面不能使用切片变量,需要使用切片的时候需要用In()方法
- Update()方法更新时,如果是0或者是false,无法更新,这时需要Exec执行sql语句来更新
相关链接
[app.ini配置]
go-ini/inigithub.com/go-ini/ini[xorm文档]
首页 |gobook.io/read/gitea.com/xorm/manual-zh-CN/[前端框架-Semantic UI]
Semantic UIsemantic-ui.com/[macaron文档]
简体中文go-macaron.com/zh-cn