mirror of
https://gitee.com/johng/gf.git
synced 2024-12-02 12:17:53 +08:00
改进
This commit is contained in:
parent
569be7e773
commit
6f403d48c2
5
TODO
5
TODO
@ -21,6 +21,11 @@ ghttp路由功能增加分组路由特性;
|
||||
解决glog串日志情况;
|
||||
ghttp增加返回数据压缩机制;
|
||||
*any模糊匹配路由改进支持不带名字的*路由规则;
|
||||
检查windows下的平滑重启失效问题;
|
||||
gview中的template标签失效问题;
|
||||
ghttp静态文件服务改进(特别是403返回状态的修改);
|
||||
|
||||
|
||||
|
||||
DONE:
|
||||
1. gconv完善针对不同类型的判断,例如:尽量减少sprintf("%v", xxx)来执行string类型的转换;
|
||||
|
@ -148,7 +148,7 @@ func (this *IntBoolMap) Size() int {
|
||||
// 哈希表是否为空
|
||||
func (this *IntBoolMap) IsEmpty() bool {
|
||||
this.mu.RLock()
|
||||
empty := (len(this.m) == 0)
|
||||
empty := len(this.m) == 0
|
||||
this.mu.RUnlock()
|
||||
return empty
|
||||
}
|
||||
@ -163,13 +163,13 @@ func (this *IntBoolMap) Clear() {
|
||||
// 使用自定义方法执行加锁修改操作
|
||||
func (this *IntBoolMap) LockFunc(f func(m map[int]bool)) {
|
||||
this.mu.Lock()
|
||||
defer this.mu.Unlock()
|
||||
f(this.m)
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
// 使用自定义方法执行加锁读取操作
|
||||
func (this *IntBoolMap) RLockFunc(f func(m map[int]bool)) {
|
||||
this.mu.RLock()
|
||||
defer this.mu.RUnlock()
|
||||
f(this.m)
|
||||
this.mu.RUnlock()
|
||||
}
|
@ -163,13 +163,13 @@ func (this *IntIntMap) Clear() {
|
||||
// 使用自定义方法执行加锁修改操作
|
||||
func (this *IntIntMap) LockFunc(f func(m map[int]int)) {
|
||||
this.mu.Lock()
|
||||
defer this.mu.Unlock()
|
||||
f(this.m)
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
// 使用自定义方法执行加锁读取操作
|
||||
func (this *IntIntMap) RLockFunc(f func(m map[int]int)) {
|
||||
this.mu.RLock()
|
||||
defer this.mu.RUnlock()
|
||||
f(this.m)
|
||||
this.mu.RUnlock()
|
||||
}
|
||||
|
@ -148,7 +148,7 @@ func (this *IntInterfaceMap) Size() int {
|
||||
// 哈希表是否为空
|
||||
func (this *IntInterfaceMap) IsEmpty() bool {
|
||||
this.mu.RLock()
|
||||
empty := (len(this.m) == 0)
|
||||
empty := len(this.m) == 0
|
||||
this.mu.RUnlock()
|
||||
return empty
|
||||
}
|
||||
@ -163,13 +163,13 @@ func (this *IntInterfaceMap) Clear() {
|
||||
// 使用自定义方法执行加锁修改操作
|
||||
func (this *IntInterfaceMap) LockFunc(f func(m map[int]interface{})) {
|
||||
this.mu.Lock()
|
||||
defer this.mu.Unlock()
|
||||
f(this.m)
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
// 使用自定义方法执行加锁读取操作
|
||||
func (this *IntInterfaceMap) RLockFunc(f func(m map[int]interface{})) {
|
||||
this.mu.RLock()
|
||||
defer this.mu.RUnlock()
|
||||
f(this.m)
|
||||
this.mu.RUnlock()
|
||||
}
|
||||
|
@ -148,7 +148,7 @@ func (this *IntStringMap) Size() int {
|
||||
// 哈希表是否为空
|
||||
func (this *IntStringMap) IsEmpty() bool {
|
||||
this.mu.RLock()
|
||||
empty := (len(this.m) == 0)
|
||||
empty := len(this.m) == 0
|
||||
this.mu.RUnlock()
|
||||
return empty
|
||||
}
|
||||
@ -170,6 +170,6 @@ func (this *IntStringMap) LockFunc(f func(m map[int]string)) {
|
||||
// 使用自定义方法执行加锁读取操作
|
||||
func (this *IntStringMap) RLockFunc(f func(m map[int]string)) {
|
||||
this.mu.RLock()
|
||||
defer this.mu.RUnlock()
|
||||
f(this.m)
|
||||
this.mu.RUnlock()
|
||||
}
|
||||
|
@ -148,7 +148,7 @@ func (this *InterfaceInterfaceMap) Size() int {
|
||||
// 哈希表是否为空
|
||||
func (this *InterfaceInterfaceMap) IsEmpty() bool {
|
||||
this.mu.RLock()
|
||||
empty := (len(this.m) == 0)
|
||||
empty := len(this.m) == 0
|
||||
this.mu.RUnlock()
|
||||
return empty
|
||||
}
|
||||
@ -163,13 +163,13 @@ func (this *InterfaceInterfaceMap) Clear() {
|
||||
// 使用自定义方法执行加锁修改操作
|
||||
func (this *InterfaceInterfaceMap) LockFunc(f func(m map[interface{}]interface{})) {
|
||||
this.mu.Lock()
|
||||
defer this.mu.Unlock()
|
||||
f(this.m)
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
// 使用自定义方法执行加锁读取操作
|
||||
func (this *InterfaceInterfaceMap) RLockFunc(f func(m map[interface{}]interface{})) {
|
||||
this.mu.RLock()
|
||||
defer this.mu.RUnlock()
|
||||
f(this.m)
|
||||
this.mu.RUnlock()
|
||||
}
|
||||
|
@ -148,7 +148,7 @@ func (this *StringBoolMap) Size() int {
|
||||
// 哈希表是否为空
|
||||
func (this *StringBoolMap) IsEmpty() bool {
|
||||
this.mu.RLock()
|
||||
empty := (len(this.m) == 0)
|
||||
empty := len(this.m) == 0
|
||||
this.mu.RUnlock()
|
||||
return empty
|
||||
}
|
||||
@ -163,13 +163,13 @@ func (this *StringBoolMap) Clear() {
|
||||
// 使用自定义方法执行加锁修改操作
|
||||
func (this *StringBoolMap) LockFunc(f func(m map[string]bool)) {
|
||||
this.mu.Lock()
|
||||
defer this.mu.Unlock()
|
||||
f(this.m)
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
// 使用自定义方法执行加锁读取操作
|
||||
func (this *StringBoolMap) RLockFunc(f func(m map[string]bool)) {
|
||||
this.mu.RLock()
|
||||
defer this.mu.RUnlock()
|
||||
f(this.m)
|
||||
this.mu.RUnlock()
|
||||
}
|
||||
|
@ -148,7 +148,7 @@ func (this *StringIntMap) Size() int {
|
||||
// 哈希表是否为空
|
||||
func (this *StringIntMap) IsEmpty() bool {
|
||||
this.mu.RLock()
|
||||
empty := (len(this.m) == 0)
|
||||
empty := len(this.m) == 0
|
||||
this.mu.RUnlock()
|
||||
return empty
|
||||
}
|
||||
@ -163,13 +163,13 @@ func (this *StringIntMap) Clear() {
|
||||
// 使用自定义方法执行加锁修改操作
|
||||
func (this *StringIntMap) LockFunc(f func(m map[string]int)) {
|
||||
this.mu.Lock()
|
||||
defer this.mu.Unlock()
|
||||
f(this.m)
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
// 使用自定义方法执行加锁读取操作
|
||||
func (this *StringIntMap) RLockFunc(f func(m map[string]int)) {
|
||||
this.mu.RLock()
|
||||
defer this.mu.RUnlock()
|
||||
f(this.m)
|
||||
this.mu.RUnlock()
|
||||
}
|
||||
|
@ -148,7 +148,7 @@ func (this *StringInterfaceMap) Size() int {
|
||||
// 哈希表是否为空
|
||||
func (this *StringInterfaceMap) IsEmpty() bool {
|
||||
this.mu.RLock()
|
||||
empty := (len(this.m) == 0)
|
||||
empty := len(this.m) == 0
|
||||
this.mu.RUnlock()
|
||||
return empty
|
||||
}
|
||||
@ -163,13 +163,13 @@ func (this *StringInterfaceMap) Clear() {
|
||||
// 使用自定义方法执行加锁修改操作
|
||||
func (this *StringInterfaceMap) LockFunc(f func(m map[string]interface{})) {
|
||||
this.mu.Lock()
|
||||
defer this.mu.Unlock()
|
||||
f(this.m)
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
// 使用自定义方法执行加锁读取操作
|
||||
func (this *StringInterfaceMap) RLockFunc(f func(m map[string]interface{})) {
|
||||
this.mu.RLock()
|
||||
defer this.mu.RUnlock()
|
||||
f(this.m)
|
||||
this.mu.RUnlock()
|
||||
}
|
||||
|
@ -148,7 +148,7 @@ func (this *StringStringMap) Size() int {
|
||||
// 哈希表是否为空
|
||||
func (this *StringStringMap) IsEmpty() bool {
|
||||
this.mu.RLock()
|
||||
empty := (len(this.m) == 0)
|
||||
empty := len(this.m) == 0
|
||||
this.mu.RUnlock()
|
||||
return empty
|
||||
}
|
||||
@ -163,13 +163,13 @@ func (this *StringStringMap) Clear() {
|
||||
// 使用自定义方法执行加锁修改操作
|
||||
func (this *StringStringMap) LockFunc(f func(m map[string]string)) {
|
||||
this.mu.Lock()
|
||||
defer this.mu.Unlock()
|
||||
f(this.m)
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
// 使用自定义方法执行加锁读取操作
|
||||
func (this *StringStringMap) RLockFunc(f func(m map[string]string)) {
|
||||
this.mu.RLock()
|
||||
defer this.mu.RUnlock()
|
||||
f(this.m)
|
||||
this.mu.RUnlock()
|
||||
}
|
||||
|
@ -147,7 +147,7 @@ func (this *UintInterfaceMap) Size() int {
|
||||
// 哈希表是否为空
|
||||
func (this *UintInterfaceMap) IsEmpty() bool {
|
||||
this.mu.RLock()
|
||||
empty := (len(this.m) == 0)
|
||||
empty := len(this.m) == 0
|
||||
this.mu.RUnlock()
|
||||
return empty
|
||||
}
|
||||
@ -162,13 +162,13 @@ func (this *UintInterfaceMap) Clear() {
|
||||
// 使用自定义方法执行加锁修改操作
|
||||
func (this *UintInterfaceMap) LockFunc(f func(m map[uint]interface{})) {
|
||||
this.mu.Lock()
|
||||
defer this.mu.Unlock()
|
||||
f(this.m)
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
// 使用自定义方法执行加锁读取操作
|
||||
func (this *UintInterfaceMap) RLockFunc(f func(m map[uint]interface{})) {
|
||||
this.mu.RLock()
|
||||
defer this.mu.RUnlock()
|
||||
f(this.m)
|
||||
this.mu.RUnlock()
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// 并发安全的动态队列.
|
||||
// 优点:
|
||||
// 特点:
|
||||
// 1、队列初始化速度快;
|
||||
// 2、可以向队头/队尾进行Push/Pop操作;
|
||||
// 3、取数据时如果队列为空那么会阻塞等待;
|
||||
|
@ -53,6 +53,8 @@ func View() *gview.View {
|
||||
if p := gfile.MainPkgPath(); gfile.Exists(p) {
|
||||
view.AddPath(p)
|
||||
}
|
||||
// 框架内置函数
|
||||
view.BindFunc("config", funcConfig)
|
||||
Set(gFRAME_CORE_COMPONENT_NAME_VIEW, view)
|
||||
return view
|
||||
}
|
||||
@ -84,3 +86,9 @@ func Config() *gcfg.Config {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 模板内置方法:config
|
||||
func funcConfig(pattern string, file...string) gview.HTML {
|
||||
return gview.HTML(Config().GetString(pattern, file...))
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,8 @@ import (
|
||||
type View struct {
|
||||
mu sync.RWMutex // 并发互斥锁
|
||||
view *gview.View // 底层视图对象
|
||||
data map[string]interface{} // 视图数据/模板变量
|
||||
data gview.Params // 视图数据/模板变量
|
||||
fmap gview.FuncMap // 绑定的模板函数
|
||||
response *ghttp.Response // 数据返回对象
|
||||
}
|
||||
|
||||
@ -25,13 +26,14 @@ type View struct {
|
||||
func NewView(w *ghttp.Response) *View {
|
||||
return &View {
|
||||
view : gins.View(),
|
||||
data : make(map[string]interface{}),
|
||||
data : make(gview.Params),
|
||||
fmap : make(gview.FuncMap),
|
||||
response : w,
|
||||
}
|
||||
}
|
||||
|
||||
// 批量绑定模板变量,即调用之后每个线程都会生效,因此有并发安全控制
|
||||
func (view *View) Assigns(data map[string]interface{}) {
|
||||
func (view *View) Assigns(data gview.Params) {
|
||||
view.mu.Lock()
|
||||
for k, v := range data {
|
||||
view.data[k] = v
|
||||
@ -46,34 +48,41 @@ func (view *View) Assign(key string, value interface{}) {
|
||||
view.mu.Unlock()
|
||||
}
|
||||
|
||||
// 绑定自定义模板函数
|
||||
func (view *View) BindFunc(name string, function interface{}){
|
||||
view.mu.Lock()
|
||||
view.fmap[name] = function
|
||||
view.mu.Unlock()
|
||||
}
|
||||
|
||||
// 解析模板,并返回解析后的内容
|
||||
func (view *View) Parse(file string) ([]byte, error) {
|
||||
view.mu.RLock()
|
||||
buffer, err := view.view.Parse(file, view.data)
|
||||
view.mu.RUnlock()
|
||||
defer view.mu.RUnlock()
|
||||
buffer, err := view.view.Parse(file, view.data, view.fmap)
|
||||
return buffer, err
|
||||
}
|
||||
|
||||
// 直接解析模板内容,并返回解析后的内容
|
||||
func (view *View) ParseContent(content string) ([]byte, error) {
|
||||
view.mu.RLock()
|
||||
buffer, err := view.view.ParseContent(content, view.data)
|
||||
view.mu.RUnlock()
|
||||
defer view.mu.RUnlock()
|
||||
buffer, err := view.view.ParseContent(content, view.data, view.fmap)
|
||||
return buffer, err
|
||||
}
|
||||
|
||||
// 使用自定义方法对模板变量执行加锁修改操作
|
||||
func (view *View) LockFunc(f func(vars map[string]interface{})) {
|
||||
func (view *View) LockFunc(f func(data gview.Params)) {
|
||||
view.mu.Lock()
|
||||
defer view.mu.Unlock()
|
||||
f(view.data)
|
||||
view.mu.Unlock()
|
||||
}
|
||||
|
||||
// 使用自定义方法对模板变量执行加锁读取操作
|
||||
func (view *View) RLockFunc(f func(vars map[string]interface{})) {
|
||||
func (view *View) RLockFunc(f func(data gview.Params)) {
|
||||
view.mu.RLock()
|
||||
defer view.mu.RUnlock()
|
||||
f(view.data)
|
||||
view.mu.RUnlock()
|
||||
}
|
||||
|
||||
// 解析并显示指定模板
|
||||
|
@ -13,7 +13,6 @@ import (
|
||||
"gitee.com/johng/gf/g/encoding/gparser"
|
||||
"strconv"
|
||||
"fmt"
|
||||
"gitee.com/johng/gf/g/frame/gins"
|
||||
)
|
||||
|
||||
// 服务端请求返回对象。
|
||||
@ -119,15 +118,6 @@ func (r *Response) WriteXml(content interface{}, rootTag...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Response) Template(tpl string, params...map[string]interface{}) error {
|
||||
if b, err := gins.View().Parse(tpl, params...); err != nil {
|
||||
return err
|
||||
} else {
|
||||
r.Write(b)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 允许AJAX跨域访问
|
||||
func (r *Response) SetAllowCrossDomainRequest(allowOrigin string, allowMethods string, maxAge...int) {
|
||||
age := 3628800
|
||||
|
47
g/net/ghttp/ghttp_response_view.go
Normal file
47
g/net/ghttp/ghttp_response_view.go
Normal file
@ -0,0 +1,47 @@
|
||||
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
//
|
||||
|
||||
package ghttp
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g/os/gview"
|
||||
"gitee.com/johng/gf/g/frame/gins"
|
||||
)
|
||||
|
||||
// 展示模板,可以给定模板参数,及临时的自定义模板函数
|
||||
func (r *Response) Template(tpl string, params map[string]interface{}, funcmap...map[string]interface{}) error {
|
||||
fmap := make(gview.FuncMap)
|
||||
if len(funcmap) > 0 {
|
||||
fmap = funcmap[0]
|
||||
}
|
||||
// 内置函数
|
||||
fmap["get"] = r.funcGet
|
||||
fmap["post"] = r.funcPost
|
||||
fmap["request"] = r.funcRequest
|
||||
if b, err := gins.View().Parse(tpl, params, fmap); err != nil {
|
||||
r.Write("Tpl Parsing Error: " + err.Error())
|
||||
return err
|
||||
} else {
|
||||
r.Write(b)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 模板内置函数: get
|
||||
func (r *Response) funcGet(key string, def...string) gview.HTML {
|
||||
return gview.HTML(r.request.GetQueryString(key, def...))
|
||||
}
|
||||
|
||||
// 模板内置函数: post
|
||||
func (r *Response) funcPost(key string, def...string) gview.HTML {
|
||||
return gview.HTML(r.request.GetPostString(key, def...))
|
||||
}
|
||||
|
||||
// 模板内置函数: request
|
||||
func (r *Response) funcRequest(key string, def...string) gview.HTML {
|
||||
return gview.HTML(r.request.Get(key, def...))
|
||||
}
|
@ -27,17 +27,21 @@ type View struct {
|
||||
delimiters []string // 模板变量分隔符号
|
||||
}
|
||||
|
||||
// 输出到模板页面时保留HTML标签原意,不做自动escape处理
|
||||
type HTML = template.HTML
|
||||
|
||||
// 模板变量
|
||||
type Params = map[string]interface{}
|
||||
|
||||
// 函数映射表
|
||||
type FuncMap = map[string]interface{}
|
||||
|
||||
// 视图表
|
||||
var viewMap = gmap.NewStringInterfaceMap()
|
||||
|
||||
// 默认的视图对象
|
||||
var viewObj = Get(".")
|
||||
|
||||
// 输出到模板页面时保留HTML标签原意,不做自动escape处理
|
||||
func HTML(content string) template.HTML {
|
||||
return template.HTML(content)
|
||||
}
|
||||
|
||||
// 直接解析模板内容,返回解析后的内容
|
||||
func ParseContent(content string, params map[string]interface{}) ([]byte, error) {
|
||||
return viewObj.ParseContent(content, params)
|
||||
@ -63,6 +67,7 @@ func New(path string) *View {
|
||||
delimiters : make([]string, 2),
|
||||
}
|
||||
view.SetDelimiters("{{", "}}")
|
||||
// 内置方法
|
||||
view.BindFunc("include", view.funcInclude)
|
||||
return view
|
||||
}
|
||||
@ -78,26 +83,24 @@ func (view *View) AddPath(path string) error {
|
||||
}
|
||||
|
||||
// 解析模板,返回解析后的内容
|
||||
func (view *View) Parse(file string, params...map[string]interface{}) ([]byte, error) {
|
||||
func (view *View) Parse(file string, params map[string]interface{}, funcmap...map[string]interface{}) ([]byte, error) {
|
||||
path := view.paths.Search(file)
|
||||
if path == "" {
|
||||
return nil, errors.New("tpl \"" + file + "\" not found")
|
||||
}
|
||||
content := gfcache.GetContents(path)
|
||||
|
||||
// 模板参数
|
||||
data := (map[string]interface{})(nil)
|
||||
if len(params) > 0 {
|
||||
data = params[0]
|
||||
}
|
||||
// 执行模板解析,互斥锁主要是用于funcmap
|
||||
view.mu.RLock()
|
||||
defer view.mu.RUnlock()
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
if tpl, err := template.New(path).Delims(view.delimiters[0], view.delimiters[1]).Funcs(view.funcmap).Parse(content); err != nil {
|
||||
tplobj := template.New(path).Delims(view.delimiters[0], view.delimiters[1]).Funcs(view.funcmap)
|
||||
if len(funcmap) > 0 {
|
||||
tplobj = tplobj.Funcs(funcmap[0])
|
||||
}
|
||||
if tpl, err := tplobj.Parse(content); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
if err := tpl.Execute(buffer, data); err != nil {
|
||||
if err := tpl.Execute(buffer, params); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
@ -105,12 +108,16 @@ func (view *View) Parse(file string, params...map[string]interface{}) ([]byte, e
|
||||
}
|
||||
|
||||
// 直接解析模板内容,返回解析后的内容
|
||||
func (view *View) ParseContent(content string, params map[string]interface{}) ([]byte, error) {
|
||||
func (view *View) ParseContent(content string, params map[string]interface{}, funcmap...map[string]interface{}) ([]byte, error) {
|
||||
view.mu.RLock()
|
||||
defer view.mu.RUnlock()
|
||||
name := gconv.String(ghash.BKDRHash64([]byte(content)))
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
if tpl, err := template.New(name).Delims(view.delimiters[0], view.delimiters[1]).Funcs(view.funcmap).Parse(content); err != nil {
|
||||
tplobj := template.New(name).Delims(view.delimiters[0], view.delimiters[1]).Funcs(view.funcmap)
|
||||
if len(funcmap) > 0 {
|
||||
tplobj = tplobj.Funcs(funcmap[0])
|
||||
}
|
||||
if tpl, err := tplobj.Parse(content); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
if err := tpl.Execute(buffer, params); err != nil {
|
||||
|
22
geg/os/gview/bind_func/gview_func.go
Normal file
22
geg/os/gview/bind_func/gview_func.go
Normal file
@ -0,0 +1,22 @@
|
||||
package main
|
||||
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitee.com/johng/gf/g"
|
||||
"gitee.com/johng/gf/g/os/gview"
|
||||
)
|
||||
|
||||
// 用于测试的内置函数
|
||||
func funcTest() string {
|
||||
return "test"
|
||||
}
|
||||
|
||||
func main() {
|
||||
view := g.View()
|
||||
b, err := view.Parse("gview.tpl", nil, gview.FuncMap{
|
||||
"test" : funcTest,
|
||||
})
|
||||
fmt.Println(err)
|
||||
fmt.Println(string(b))
|
||||
}
|
1
geg/os/gview/bind_func/gview_func.tpl
Normal file
1
geg/os/gview/bind_func/gview_func.tpl
Normal file
@ -0,0 +1 @@
|
||||
{{test}}
|
Loading…
Reference in New Issue
Block a user