├── template ├── .gitignore └── default │ ├── data.db │ ├── errors │ ├── close.html │ ├── 404.html │ └── 500.html │ ├── partial │ ├── breadcrumb.html │ ├── pagination.html │ └── sidebar.html │ ├── config.json │ ├── tag │ ├── index.html │ └── list.html │ ├── page │ ├── detail.html │ ├── about.html │ └── contact.html │ ├── article │ ├── index.html │ ├── list.html │ └── detail.html │ ├── case │ ├── list.html │ ├── index.html │ └── detail.html │ ├── search │ └── index.html │ └── product │ ├── list.html │ ├── index.html │ └── detail.html ├── cache └── .gitignore ├── public ├── static │ ├── .gitignore │ └── default │ │ ├── img │ │ ├── 1.webp │ │ ├── 2.webp │ │ ├── 3.webp │ │ ├── n.webp │ │ ├── p.webp │ │ ├── bg.webp │ │ ├── case.webp │ │ ├── jt.webp │ │ ├── about.webp │ │ ├── banner.webp │ │ ├── about-1.webp │ │ ├── about-2.webp │ │ ├── about-bg.webp │ │ ├── sidebar.webp │ │ └── contact-bg.webp │ │ └── layui │ │ ├── font │ │ ├── iconfont.eot │ │ ├── iconfont.ttf │ │ ├── iconfont.woff │ │ └── iconfont.woff2 │ │ └── css │ │ └── modules │ │ ├── layer │ │ └── default │ │ │ ├── icon.png │ │ │ ├── icon-ext.png │ │ │ ├── loading-0.gif │ │ │ ├── loading-1.gif │ │ │ └── loading-2.gif │ │ └── code.css ├── uploads │ └── .gitignore └── .gitignore ├── app.ico ├── start.bat ├── stop.bat ├── anqicms.syso ├── error.log ├── clientFiles └── train2anqicms.wpm ├── response ├── article.go ├── backup.go ├── webLink.go ├── baidu.go ├── category.go ├── webInfo.go ├── statistic.go ├── design.go └── response.go ├── request ├── weapp.go ├── collector.go ├── transfer.go ├── attachment.go ├── withdraw.go ├── install.go ├── setting.go ├── module.go ├── website.go ├── wechat.go ├── category.go ├── admin.go ├── anqi.go ├── design.go ├── archive.go └── user.go ├── config.sample.json ├── config ├── user.go ├── server.go ├── rewrite.go ├── wechat.go ├── keyword.go ├── anqi.go ├── mysql.go └── pay.go ├── .gitignore ├── model ├── setting.go ├── moduleTable.go ├── redirect.go ├── website.go ├── statistic.go ├── spiderInclude.go ├── base.go ├── guestbook.go ├── withdraw.go ├── finance.go ├── tag.go ├── commission.go ├── keyword.go ├── anchor.go ├── link.go ├── nav.go ├── comment.go ├── material.go └── wechat.go ├── library ├── pinyin_test.go ├── word_test.go ├── logger.go ├── content_test.go ├── charset.go ├── word.go ├── pinyin.go ├── request_test.go ├── file.go ├── image.go ├── webp.go ├── verifyCode.go └── math.go ├── provider ├── design_test.go ├── spiderInclude_test.go ├── plugin_test.go ├── sendmail_test.go ├── uri_test.go ├── backup_test.go ├── attachment_test.go ├── transfer_test.go ├── finance.go ├── common.go ├── commission.go ├── collector_test.go ├── categoryTree.go ├── comment.go ├── weapp.go ├── link.go └── guestbook.go ├── main └── main.go ├── start.sh ├── stop.sh ├── middleware ├── options.go ├── recover.go └── userAuth.go ├── main.manifest ├── controller ├── manageController │ ├── pluginReplace.go │ ├── pluginFinance.go │ ├── pluginCommission.go │ ├── pluginRobots.go │ ├── pluginFulltext.go │ ├── pluginWeapp.go │ ├── pluginRewrite.go │ ├── pluginImportapi.go │ ├── pluginPush.go │ ├── pluginTransfer.go │ ├── sensitiveWords.go │ ├── pluginWithdraw.go │ ├── pluginSendmail.go │ └── pluginTag.go ├── apiWeapp.go ├── attachment.go ├── user.go ├── index.go ├── account.go ├── tag.go └── page.go ├── darwinMake.sh ├── windowsMake.sh ├── Makefile ├── License ├── tags ├── common.go ├── userGroupDetail.go ├── bannerList.go ├── linkList.go ├── guestbook.go ├── tagDetail.go ├── pageList.go ├── userDetail.go ├── nextArchive.go ├── prevArchive.go ├── system.go ├── contact.go └── categoryDetail.go └── view └── fs.go /template/.gitignore: -------------------------------------------------------------------------------- 1 | /anqitpl -------------------------------------------------------------------------------- /cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /public/static/.gitignore: -------------------------------------------------------------------------------- 1 | /anqitpl -------------------------------------------------------------------------------- /public/uploads/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /public/.gitignore: -------------------------------------------------------------------------------- 1 | robots.txt 2 | /*.xml 3 | /*.txt 4 | /system -------------------------------------------------------------------------------- /app.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rockyzsu/goblog/master/app.ico -------------------------------------------------------------------------------- /start.bat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rockyzsu/goblog/master/start.bat -------------------------------------------------------------------------------- /stop.bat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rockyzsu/goblog/master/stop.bat -------------------------------------------------------------------------------- /anqicms.syso: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rockyzsu/goblog/master/anqicms.syso -------------------------------------------------------------------------------- /error.log: -------------------------------------------------------------------------------- 1 | 2023-03-02 21:20:33 启动服务出错 listen tcp :8001: bind: address already in use 2 | -------------------------------------------------------------------------------- /template/default/data.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rockyzsu/goblog/master/template/default/data.db -------------------------------------------------------------------------------- /clientFiles/train2anqicms.wpm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rockyzsu/goblog/master/clientFiles/train2anqicms.wpm -------------------------------------------------------------------------------- /public/static/default/img/1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rockyzsu/goblog/master/public/static/default/img/1.webp -------------------------------------------------------------------------------- /public/static/default/img/2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rockyzsu/goblog/master/public/static/default/img/2.webp -------------------------------------------------------------------------------- /public/static/default/img/3.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rockyzsu/goblog/master/public/static/default/img/3.webp -------------------------------------------------------------------------------- /public/static/default/img/n.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rockyzsu/goblog/master/public/static/default/img/n.webp -------------------------------------------------------------------------------- /public/static/default/img/p.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rockyzsu/goblog/master/public/static/default/img/p.webp -------------------------------------------------------------------------------- /public/static/default/img/bg.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rockyzsu/goblog/master/public/static/default/img/bg.webp -------------------------------------------------------------------------------- /public/static/default/img/case.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rockyzsu/goblog/master/public/static/default/img/case.webp -------------------------------------------------------------------------------- /public/static/default/img/jt.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rockyzsu/goblog/master/public/static/default/img/jt.webp -------------------------------------------------------------------------------- /response/article.go: -------------------------------------------------------------------------------- 1 | package response 2 | 3 | type CacheArticleCount struct { 4 | Day int 5 | Count int64 6 | } 7 | -------------------------------------------------------------------------------- /public/static/default/img/about.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rockyzsu/goblog/master/public/static/default/img/about.webp -------------------------------------------------------------------------------- /public/static/default/img/banner.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rockyzsu/goblog/master/public/static/default/img/banner.webp -------------------------------------------------------------------------------- /public/static/default/img/about-1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rockyzsu/goblog/master/public/static/default/img/about-1.webp -------------------------------------------------------------------------------- /public/static/default/img/about-2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rockyzsu/goblog/master/public/static/default/img/about-2.webp -------------------------------------------------------------------------------- /public/static/default/img/about-bg.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rockyzsu/goblog/master/public/static/default/img/about-bg.webp -------------------------------------------------------------------------------- /public/static/default/img/sidebar.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rockyzsu/goblog/master/public/static/default/img/sidebar.webp -------------------------------------------------------------------------------- /public/static/default/img/contact-bg.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rockyzsu/goblog/master/public/static/default/img/contact-bg.webp -------------------------------------------------------------------------------- /public/static/default/layui/font/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rockyzsu/goblog/master/public/static/default/layui/font/iconfont.eot -------------------------------------------------------------------------------- /public/static/default/layui/font/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rockyzsu/goblog/master/public/static/default/layui/font/iconfont.ttf -------------------------------------------------------------------------------- /public/static/default/layui/font/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rockyzsu/goblog/master/public/static/default/layui/font/iconfont.woff -------------------------------------------------------------------------------- /public/static/default/layui/font/iconfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rockyzsu/goblog/master/public/static/default/layui/font/iconfont.woff2 -------------------------------------------------------------------------------- /request/weapp.go: -------------------------------------------------------------------------------- 1 | package request 2 | 3 | type WeappQrcodeRequest struct { 4 | Path string `json:"path"` 5 | Scene string `json:"scene"` 6 | } 7 | -------------------------------------------------------------------------------- /config.sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "server": { 3 | "site_name": "安企内容管理系统(AnqiCMS)", 4 | "env": "production", 5 | "port": 8001, 6 | "log_level": "release" 7 | } 8 | } -------------------------------------------------------------------------------- /public/static/default/layui/css/modules/layer/default/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rockyzsu/goblog/master/public/static/default/layui/css/modules/layer/default/icon.png -------------------------------------------------------------------------------- /public/static/default/layui/css/modules/layer/default/icon-ext.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rockyzsu/goblog/master/public/static/default/layui/css/modules/layer/default/icon-ext.png -------------------------------------------------------------------------------- /public/static/default/layui/css/modules/layer/default/loading-0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rockyzsu/goblog/master/public/static/default/layui/css/modules/layer/default/loading-0.gif -------------------------------------------------------------------------------- /public/static/default/layui/css/modules/layer/default/loading-1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rockyzsu/goblog/master/public/static/default/layui/css/modules/layer/default/loading-1.gif -------------------------------------------------------------------------------- /public/static/default/layui/css/modules/layer/default/loading-2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rockyzsu/goblog/master/public/static/default/layui/css/modules/layer/default/loading-2.gif -------------------------------------------------------------------------------- /response/backup.go: -------------------------------------------------------------------------------- 1 | package response 2 | 3 | type BackupInfo struct { 4 | Name string `json:"name"` 5 | Size int64 `json:"size"` 6 | LastMod int64 `json:"last_mod"` 7 | } 8 | -------------------------------------------------------------------------------- /config/user.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type PluginUserConfig struct { 4 | Fields []*CustomField `json:"fields"` 5 | DefaultGroupId uint `json:"default_group_id"` 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .DS_Store 3 | .vscode 4 | vendor 5 | /config.json 6 | goblog 7 | /doc 8 | /docs 9 | /test 10 | /data 11 | /system 12 | /release 13 | collector.json 14 | keyword.json -------------------------------------------------------------------------------- /request/collector.go: -------------------------------------------------------------------------------- 1 | package request 2 | 3 | type KeywordRequest struct { 4 | Id uint `json:"id"` 5 | Title string `json:"title"` 6 | Demand string `json:"demand,omitempty"` // AI 的额外要求 7 | } 8 | -------------------------------------------------------------------------------- /request/transfer.go: -------------------------------------------------------------------------------- 1 | package request 2 | 3 | type TransferWebsite struct { 4 | Name string `json:"name"` 5 | BaseUrl string `json:"base_url"` 6 | Token string `json:"token"` 7 | Provider string `json:"provider"` 8 | } 9 | -------------------------------------------------------------------------------- /model/setting.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type Setting struct { 4 | Key string `json:"key" gorm:"column:key;type:varchar(190) not null;primaryKey"` 5 | Value string `json:"value" gorm:"column:value;type:longtext default null"` 6 | } 7 | -------------------------------------------------------------------------------- /config/server.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type ServerConfig struct { 4 | Env string `json:"env"` 5 | Port int `json:"port"` 6 | LogLevel string `json:"log_level"` 7 | TokenSecret string `json:"token_secret"` 8 | } 9 | -------------------------------------------------------------------------------- /library/pinyin_test.go: -------------------------------------------------------------------------------- 1 | package library 2 | 3 | import ( 4 | "log" 5 | "testing" 6 | ) 7 | 8 | func TestGetPinyin(t *testing.T) { 9 | result := GetPinyin("Electronic Water Bath", true) 10 | 11 | log.Println(result) 12 | } 13 | -------------------------------------------------------------------------------- /response/webLink.go: -------------------------------------------------------------------------------- 1 | package response 2 | 3 | type WebLink struct { 4 | Name string `json:"name"` 5 | Url string `json:"url"` 6 | OriginUrl string `json:"origin_url"` 7 | Content string `json:"content"` 8 | } 9 | 10 | -------------------------------------------------------------------------------- /provider/design_test.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "log" 5 | "testing" 6 | ) 7 | 8 | func (w *Website) TestGetDesignList(t *testing.T) { 9 | designList := w.GetDesignList() 10 | 11 | log.Printf("%#v", designList) 12 | } 13 | -------------------------------------------------------------------------------- /library/word_test.go: -------------------------------------------------------------------------------- 1 | package library 2 | 3 | import ( 4 | "log" 5 | "testing" 6 | ) 7 | 8 | func TestWordSplit(t *testing.T) { 9 | s := "Golang 在线教程" 10 | 11 | result := WordSplit(s, false) 12 | 13 | log.Printf("%#v", result) 14 | } 15 | -------------------------------------------------------------------------------- /provider/spiderInclude_test.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import "testing" 4 | 5 | func TestQuerySpiderInclude(t *testing.T) { 6 | dbSite, _ := GetDBWebsiteInfo(1) 7 | InitWebsite(dbSite) 8 | w := GetWebsite(1) 9 | w.QuerySpiderInclude() 10 | } 11 | -------------------------------------------------------------------------------- /provider/plugin_test.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "log" 5 | "testing" 6 | ) 7 | 8 | func (w *Website) TestPushBing(t *testing.T) { 9 | urls := []string{"https://www.anqicms.com/help-basic/112.html"} 10 | 11 | err := w.PushBing(urls) 12 | log.Println(err) 13 | } 14 | -------------------------------------------------------------------------------- /model/moduleTable.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type ModuleTable struct { 4 | Id uint `json:"id" gorm:"column:id;type:int(10) unsigned not null AUTO_INCREMENT;primaryKey"` 5 | Table string `json:"table_name" gorm:"-"` 6 | } 7 | 8 | func (m ModuleTable) TableName() string { 9 | return m.Table 10 | } 11 | -------------------------------------------------------------------------------- /provider/sendmail_test.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func (w *Website) TestSendMail(t *testing.T) { 8 | subject := "测试邮件" 9 | content := "这是一封测试邮件。收到邮件表示配置正常" 10 | 11 | err := w.SendMail(subject, content) 12 | if err != nil { 13 | t.Fatal(err) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /response/baidu.go: -------------------------------------------------------------------------------- 1 | package response 2 | 3 | type BaiduJson struct { 4 | Feed BaiduFeeJson `json:"feed"` 5 | } 6 | 7 | type BaiduFeeJson struct { 8 | Entry []BaiduEntryJson `json:"entry"` 9 | } 10 | 11 | type BaiduEntryJson struct { 12 | Title string `json:"title"` 13 | Url string `json:"url"` 14 | } 15 | -------------------------------------------------------------------------------- /model/redirect.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type Redirect struct { 4 | Model 5 | FromUrl string `json:"from_url" gorm:"column:from_url;type:varchar(190) not null;default:'';unique"` 6 | ToUrl string `json:"to_url" gorm:"column:to_url;type:varchar(250) not null;default:''"` 7 | SiteId uint `json:"-" gorm:"-"` 8 | } 9 | -------------------------------------------------------------------------------- /provider/uri_test.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "log" 5 | "testing" 6 | ) 7 | 8 | func (w *Website) TestGetUrl(t *testing.T) { 9 | archive, err := w.GetArchiveById(12) 10 | if err != nil { 11 | t.Fatal(err) 12 | } 13 | 14 | link := w.GetUrl("archive", archive, 0) 15 | log.Println(link) 16 | } 17 | -------------------------------------------------------------------------------- /template/default/errors/close.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block container %} 4 |
5 |
6 |
7 |

Close

8 |

{{closeTips}}

9 |
10 |
11 |
12 | {% endblock %} -------------------------------------------------------------------------------- /response/category.go: -------------------------------------------------------------------------------- 1 | package response 2 | 3 | type CategoryTemplate struct { 4 | Template string `json:"template"` 5 | DetailTemplate string `json:"detail_template"` 6 | } 7 | 8 | type ApiCategory struct { 9 | Id uint `json:"id"` 10 | ParentId uint `json:"parent_id"` 11 | Title string `json:"title"` 12 | } 13 | -------------------------------------------------------------------------------- /response/webInfo.go: -------------------------------------------------------------------------------- 1 | package response 2 | 3 | type WebInfo struct { 4 | Title string `json:"title"` 5 | Keywords string `json:"keywords"` 6 | Description string `json:"description"` 7 | NavBar uint `json:"nav_bar"` 8 | PageName string `json:"page_name"` 9 | CanonicalUrl string `json:"canonical_url"` // 当前页面的规范URL 10 | } 11 | -------------------------------------------------------------------------------- /template/default/errors/404.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block container %} 4 |
5 |
6 |
7 |

404

8 |

页面似乎丢失了,您可以 返回首页

9 |
10 |
11 |
12 | {% endblock %} -------------------------------------------------------------------------------- /config/rewrite.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | const ( 4 | RewriteNumberMode = 0 //数字模式 5 | RewriteStringMode1 = 1 //命名模式1 6 | RewriteStringMode2 = 2 //命名模式2 7 | RewriteStringMode3 = 3 //命名模式3 8 | RewritePattenMode = 4 //正则模式 9 | ) 10 | 11 | type PluginRewriteConfig struct { 12 | Mode int `json:"mode"` 13 | Patten string `json:"patten"` 14 | } 15 | -------------------------------------------------------------------------------- /config/wechat.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type PluginWeappConfig struct { 4 | AppID string `json:"app_id"` 5 | AppSecret string `json:"app_secret"` 6 | //公众号存在的部分 7 | Token string `json:"token"` 8 | EncodingAESKey string `json:"encoding_aes_key"` 9 | VerifyKey string `json:"verify_key"` 10 | VerifyMsg string `json:"verify_msg"` 11 | } 12 | -------------------------------------------------------------------------------- /template/default/errors/500.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block container %} 4 |
5 |
6 |
7 |

500

8 |

页面出错了:{{errMessage}},您可以 返回首页

9 |
10 |
11 |
12 | {% endblock %} -------------------------------------------------------------------------------- /main/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "kandaoni.com/anqicms" 6 | "kandaoni.com/anqicms/config" 7 | ) 8 | 9 | func main() { 10 | port := flag.Int("port", config.Server.Server.Port, "运行端口号") 11 | flag.Parse() 12 | config.Server.Server.Port = *port 13 | b := anqicms.New(config.Server.Server.Port, config.Server.Server.LogLevel) 14 | b.Serve() 15 | } 16 | -------------------------------------------------------------------------------- /request/attachment.go: -------------------------------------------------------------------------------- 1 | package request 2 | 3 | type Attachment struct { 4 | Id uint `json:"id"` 5 | FileName string `json:"file_name"` 6 | } 7 | 8 | type AttachmentCategory struct { 9 | Id uint `json:"id"` 10 | Title string `json:"title"` 11 | } 12 | 13 | type ChangeAttachmentCategory struct { 14 | CategoryId uint `json:"category_id"` 15 | Ids []uint `json:"ids"` 16 | } 17 | -------------------------------------------------------------------------------- /template/default/partial/breadcrumb.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | {% breadcrumb crumbs with title=false %} 5 | {% for item in crumbs %} 6 | {{item.Name}} 7 | {% if forloop.Revcounter > 1 %} / {% endif %} 8 | {% endfor %} 9 | {% endbreadcrumb %} 10 |
11 |
12 |
-------------------------------------------------------------------------------- /provider/backup_test.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import "testing" 4 | 5 | func (w *Website) TestBackupData(t *testing.T) { 6 | err := w.BackupData() 7 | 8 | if err != nil { 9 | t.Fatal(err) 10 | } 11 | } 12 | 13 | func (w *Website) TestRestoreData(t *testing.T) { 14 | fileName := "20221111180220.sql" 15 | 16 | err := w.RestoreData(fileName) 17 | if err != nil { 18 | t.Fatal(err) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /template/default/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "template_id": 179, 3 | "auth_id": 1000, 4 | "name": "默认模板", 5 | "package": "default", 6 | "version": "1.0.1", 7 | "description": "安企cms官方网站", 8 | "author": "anqicms", 9 | "homepage": "https://www.anqicms.com", 10 | "created": "2022-08-01 09:26:46", 11 | "template_type": 0, 12 | "status": 0, 13 | "tpl_files": null, 14 | "static_files": null, 15 | "preview_data": false 16 | } -------------------------------------------------------------------------------- /provider/attachment_test.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "log" 5 | "testing" 6 | ) 7 | 8 | func (w *Website) TestDownloadRemoteImage(t *testing.T) { 9 | link := "https://mmbiz.qpic.cn/mmbiz_jpg/YNoY3yGicTIRicbeSpTCnzxK1icJ0vBLlnMwibl9icyZcNnL4ml0ic3YI1Yp3RyeK8FicBu9OFVvmibRuK89ky5u2faCnw/640?wx_fmt=jpeg" 10 | alt := "" 11 | 12 | result, err := w.DownloadRemoteImage(link, alt) 13 | if err != nil { 14 | t.Fatal(err) 15 | } 16 | 17 | log.Printf("%#v", result) 18 | } 19 | -------------------------------------------------------------------------------- /config/keyword.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type KeywordJson struct { 4 | AutoDig bool `json:"auto_dig"` //关键词是否自动拓词 5 | Language string `json:"language"` // zh|en|cr 6 | MaxCount int64 `json:"max_count"` 7 | TitleExclude []string `json:"title_exclude"` 8 | TitleReplace []ReplaceKeyword `json:"title_replace"` 9 | } 10 | 11 | var DefaultKeywordConfig = KeywordJson{ 12 | AutoDig: false, 13 | Language: LanguageZh, 14 | MaxCount: 100000, 15 | } 16 | -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ### check 502 3 | # author fesion 4 | # the bin name is anqicms 5 | BINNAME=anqicms 6 | BINPATH="$( cd "$( dirname "$0" )" && pwd )" 7 | 8 | # check the pid if exists 9 | exists=`ps -ef | grep '\' |grep -v grep |wc -l` 10 | echo "$(date +'%Y%m%d %H:%M:%S') $BINNAME PID check: $exists" >> $BINPATH/check.log 11 | echo "PID $BINNAME check: $exists" 12 | if [ $exists -eq 0 ]; then 13 | echo "$BINNAME NOT running" 14 | cd $BINPATH && nohup $BINPATH/$BINNAME >> $BINPATH/running.log 2>&1 & 15 | fi -------------------------------------------------------------------------------- /library/logger.go: -------------------------------------------------------------------------------- 1 | package library 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "sync" 7 | ) 8 | 9 | var gRWLock *sync.RWMutex 10 | 11 | func DebugLog(cachePath, name string, v ...interface{}) { 12 | filePath := cachePath + name 13 | logFile, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0766) 14 | if nil != err { 15 | //打开失败,不做记录 16 | return 17 | } 18 | defer logFile.Close() 19 | gRWLock.Lock() 20 | logFile.WriteString(fmt.Sprintln(v...)) 21 | gRWLock.Unlock() 22 | } 23 | 24 | func init() { 25 | gRWLock = new(sync.RWMutex) 26 | } 27 | -------------------------------------------------------------------------------- /stop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ### stop 3 | # author fesion 4 | # the bin name is anqicms 5 | BINNAME=anqicms 6 | BINPATH="$( cd "$( dirname "$0" )" && pwd )" 7 | 8 | # check the pid if exists 9 | exists=`ps -ef | grep '\' |grep -v grep |awk '{printf $2}'` 10 | echo "$(date +'%Y%m%d %H:%M:%S') $BINNAME PID check: $exists" >> $BINPATH/check.log 11 | echo "PID $BINNAME check: $exists" 12 | if [ $exists -eq 0 ]; then 13 | echo "$BINNAME NOT running" 14 | else 15 | echo "$BINNAME is running" 16 | kill -9 $exists 17 | echo "$BINNAME is stop" 18 | fi -------------------------------------------------------------------------------- /request/withdraw.go: -------------------------------------------------------------------------------- 1 | package request 2 | 3 | type UserWithdrawRequest struct { 4 | Id uint `json:"id"` 5 | UserId uint `json:"user_id"` 6 | Amount int64 `json:"amount"` // 提现金额 7 | SuccessTime int64 `json:"success_time"` // 成功时间 8 | WithdrawWay int `json:"withdraw_way"` // 提现去向,1 微信提现, 9 | Status int `json:"status"` //0-等待处理 1-已提现,-1 提现错误 10 | ErrorTimes int `json:"error_times"` // 执行错误次数 11 | LastTime int64 `json:"last_time"` // 上次执行时间 12 | Remark string `json:"remark"` 13 | UserName string `json:"user_name"` 14 | } 15 | -------------------------------------------------------------------------------- /request/install.go: -------------------------------------------------------------------------------- 1 | package request 2 | 3 | type Install struct { 4 | Database string `json:"database" validate:"required"` 5 | User string `json:"user" validate:"required"` 6 | Password string `json:"password" validate:"required"` 7 | Host string `json:"host" validate:"required"` 8 | Port int `json:"port" validate:"required"` 9 | AdminUser string `json:"admin_user" validate:"required"` 10 | AdminPassword string `json:"admin_password" validate:"required"` 11 | BaseUrl string `json:"base_url"` 12 | PreviewData bool `json:"preview_data"` 13 | } 14 | -------------------------------------------------------------------------------- /config/anqi.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type AnqiUserConfig struct { 4 | UserName string `json:"user_name"` 5 | AuthId uint `json:"auth_id"` 6 | LoginTime int64 `json:"login_time"` 7 | CheckTime int64 `json:"check_time"` 8 | ExpireTime int64 `json:"expire_time"` 9 | PseudoRemain int64 `json:"pseudo_remain"` 10 | TranslateRemain int64 `json:"translate_remain"` 11 | AiRemain int64 `json:"ai_remain"` 12 | Integral int64 `json:"integral"` 13 | Status int `json:"status"` 14 | Token string `json:"token"` 15 | 16 | Valid bool `json:"valid"` 17 | } 18 | -------------------------------------------------------------------------------- /request/setting.go: -------------------------------------------------------------------------------- 1 | package request 2 | 3 | type NavConfig struct { 4 | Id uint `json:"id"` 5 | Title string `json:"title"` 6 | SubTitle string `json:"sub_title"` 7 | Description string `json:"description"` 8 | ParentId uint `json:"parent_id"` 9 | NavType uint `json:"nav_type"` 10 | PageId uint `json:"page_id"` 11 | TypeId uint `json:"type_id"` 12 | Link string `json:"link"` 13 | Sort uint `json:"sort"` 14 | Status uint `json:"status"` 15 | } 16 | 17 | type NavTypeRequest struct { 18 | Id uint `json:"id"` 19 | Title string `json:"title"` 20 | } 21 | -------------------------------------------------------------------------------- /library/content_test.go: -------------------------------------------------------------------------------- 1 | package library 2 | 3 | import ( 4 | "log" 5 | "testing" 6 | ) 7 | 8 | func TestParseDescription(t *testing.T) { 9 | str := "广西玉林市博白县凤山中学一学生因点外卖,被校务人员按倒在地训斥教育,登上网络热搜。3月21日上午,博白县教育局回应记者时表示,有工作人员在调查处理此事,并将适时公布调查情况。网络视频显示,一名男生被一名黑衣男子按倒在地,该黑衣男子大声说道:“点多少次了啊,还这样搞……”随后,记者与广西玉林市博白县凤山中学取得联系,相关负责人称,视频中的男生是因为点外卖被批评,学校对安全管理很严格,外面食品不能带进学校的,另外已针对该校务人员的工作方法进行了批评教育。据央视网报道,3月20日上午,牙冠竞价挂网于在四川成都举行并产生入围结果。口腔牙齿种植的费用大致包括种植体、牙冠和医疗服务费用三部分。其中,种植体集中带量采购已于今年1月开展,中选产品价格平均降至900多元,平均降幅55%;医疗服务方面,此前国家医保局发文要求,三级公立医院单颗常规种植牙医疗服务价格调控目标为4500元,多地已根据要求出台或落实了相关政策;叠加此次牙冠竞价挂网,预计种植一颗牙的整体费用有望降低50%左右。" 10 | 11 | desc := ParseDescription(str) 12 | 13 | log.Println(desc) 14 | } 15 | -------------------------------------------------------------------------------- /model/website.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "kandaoni.com/anqicms/config" 4 | 5 | type Website struct { 6 | Model 7 | RootPath string `json:"root_path" gorm:"column:root_path;type:varchar(190) not null;default:''"` 8 | Name string `json:"name" gorm:"column:name;type:varchar(128) not null;default:''"` 9 | Mysql config.MysqlConfig `json:"mysql" gorm:"column:mysql;type:text;default null"` 10 | Status uint `json:"status" gorm:"column:status;type:tinyint(1) unsigned not null;default:0"` 11 | BaseUrl string `json:"base_url" gorm:"-"` 12 | ErrorMsg string `json:"error_msg" gorm:"-"` 13 | } 14 | -------------------------------------------------------------------------------- /request/module.go: -------------------------------------------------------------------------------- 1 | package request 2 | 3 | import "kandaoni.com/anqicms/config" 4 | 5 | type ModuleRequest struct { 6 | Id uint `json:"id"` 7 | TableName string `json:"table_name"` 8 | UrlToken string `json:"url_token"` 9 | Title string `json:"title"` 10 | Fields []config.CustomField `json:"fields"` 11 | IsSystem int `json:"is_system"` 12 | TitleName string `json:"title_name"` 13 | Status uint `json:"status"` 14 | } 15 | 16 | type ModuleFieldRequest struct { 17 | Id uint `json:"id"` 18 | FieldName string `json:"field_name"` 19 | } 20 | -------------------------------------------------------------------------------- /request/website.go: -------------------------------------------------------------------------------- 1 | package request 2 | 3 | import "kandaoni.com/anqicms/config" 4 | 5 | type WebsiteRequest struct { 6 | Id uint `json:"id"` 7 | RootPath string `json:"root_path"` 8 | Name string `json:"name"` 9 | Status uint `json:"status"` 10 | Mysql config.MysqlConfig `json:"mysql"` 11 | AdminUser string `json:"admin_user" validate:"required"` 12 | AdminPassword string `json:"admin_password" validate:"required"` 13 | BaseUrl string `json:"base_url"` 14 | PreviewData bool `json:"preview_data"` 15 | Initialed bool `json:"initialed"` 16 | } 17 | -------------------------------------------------------------------------------- /request/wechat.go: -------------------------------------------------------------------------------- 1 | package request 2 | 3 | type WechatMessageRequest struct { 4 | Id uint `json:"id"` 5 | Openid string `json:"openid"` 6 | Content string `json:"content"` 7 | Reply string `json:"reply"` 8 | ReplyTime int `json:"reply_time"` 9 | } 10 | 11 | type WechatReplyRuleRequest struct { 12 | Id uint `json:"id"` 13 | Keyword string `json:"keyword"` 14 | Content string `json:"content"` 15 | IsDefault int `json:"is_default"` 16 | } 17 | 18 | type WechatMenuRequest struct { 19 | Id uint `json:"id"` 20 | ParentId uint `json:"parent_id"` 21 | Name string `json:"name"` 22 | Type string `json:"type"` 23 | Value string `json:"value"` 24 | Sort uint `json:"sort"` 25 | } 26 | -------------------------------------------------------------------------------- /model/statistic.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type Statistic struct { 4 | Model 5 | Spider string `json:"spider" gorm:"column:spider;type:varchar(20) not null;default:'';index"` 6 | Host string `json:"host" gorm:"column:host;type:varchar(100) not null;default:'';index"` 7 | Url string `json:"url" gorm:"column:url;type:varchar(250) not null;default:''"` 8 | Ip string `json:"ip" gorm:"column:ip;type:varchar(15) not null;default:''"` 9 | Device string `json:"device" gorm:"column:device;type:varchar(20) not null;default:'';index"` 10 | HttpCode int `json:"http_code" gorm:"column:http_code;type:int(3) not null;default:0"` 11 | UserAgent string `json:"user_agent" gorm:"column:user_agent;type:varchar(255) not null;default:''"` 12 | } 13 | -------------------------------------------------------------------------------- /middleware/options.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "github.com/kataras/iris/v12" 5 | ) 6 | 7 | func Cors(ctx iris.Context) { 8 | origin := ctx.GetHeader("Origin") 9 | if origin == "" { 10 | origin = ctx.GetHeader("Referer") 11 | if origin == "" { 12 | origin = "*" 13 | } 14 | } 15 | ctx.Header("Access-Control-Allow-Origin", origin) 16 | ctx.Header("Access-Control-Allow-Credentials", "true") 17 | ctx.Header("Access-Control-Expose-Headers", "Content-Disposition") 18 | ctx.Header("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,PATCH,OPTIONS") 19 | ctx.Header("Access-Control-Allow-Headers", "Content-Type, Api, Accept, Authorization, Version, Admin, Token, Key, Site-Id") 20 | if ctx.Request().Method == "OPTIONS" { 21 | ctx.StatusCode(204) 22 | return 23 | } 24 | ctx.Next() 25 | } 26 | -------------------------------------------------------------------------------- /main.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /controller/manageController/pluginReplace.go: -------------------------------------------------------------------------------- 1 | package manageController 2 | 3 | import ( 4 | "fmt" 5 | "github.com/kataras/iris/v12" 6 | "kandaoni.com/anqicms/config" 7 | "kandaoni.com/anqicms/provider" 8 | "kandaoni.com/anqicms/request" 9 | ) 10 | 11 | func PluginReplaceValues(ctx iris.Context) { 12 | currentSite := provider.CurrentSite(ctx) 13 | var req request.PluginReplaceRequest 14 | if err := ctx.ReadJSON(&req); err != nil { 15 | ctx.JSON(iris.Map{ 16 | "code": config.StatusFailed, 17 | "msg": err.Error(), 18 | }) 19 | return 20 | } 21 | 22 | total := currentSite.ReplaceValues(&req) 23 | 24 | currentSite.AddAdminLog(ctx, fmt.Sprintf("全站替换 %v, %v", req.Places, req.Keywords)) 25 | 26 | ctx.JSON(iris.Map{ 27 | "code": config.StatusOK, 28 | "msg": "替换已完成", 29 | "data": total, 30 | }) 31 | } 32 | -------------------------------------------------------------------------------- /darwinMake.sh: -------------------------------------------------------------------------------- 1 | rm -rf ./release/darwin 2 | mkdir -p -v ./release/darwin/cache 3 | cp -r ./doc ./release/darwin/ 4 | cp -r ./public ./release/darwin/ 5 | rm -rf ./release/darwin/public/uploads 6 | rm -rf ./release/darwin/public/*.txt 7 | rm -rf ./release/darwin/public/*.xml 8 | cp -r ./template ./release/darwin/ 9 | cp -r ./system ./release/darwin/ 10 | cp -r ./language ./release/darwin/ 11 | cp -r ./CHANGELOG.md ./release/darwin/ 12 | find ./release/darwin -name '.DS_Store' | xargs rm -f 13 | cp -r ./start.sh ./release/darwin/ 14 | cp -r ./stop.sh ./release/darwin/ 15 | cp -r ./License ./release/darwin/ 16 | cp -r ./clientFiles ./release/darwin/ 17 | cp -r ./README.md ./release/darwin/ 18 | cp -r ./dictionary.txt ./release/darwin/ 19 | GOOS=darwin GOARCH=amd64 go build -ldflags '-w -s' -o ./release/darwin/anqicms kandaoni.com/anqicms/main -------------------------------------------------------------------------------- /model/spiderInclude.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | // SpiderInclude 网站收录情况 4 | type SpiderInclude struct { 5 | Id uint `json:"id" gorm:"column:id;type:int(10) unsigned not null AUTO_INCREMENT;primaryKey"` 6 | CreatedTime int64 `json:"created_time" gorm:"column:created_time;type:int(11);index;autoCreateTime"` 7 | BaiduCount int `json:"baidu_count" gorm:"column:baidu_count;type:int(10);default:0"` //百度收录情况 8 | SogouCount int `json:"sogou_count" gorm:"column:sogou_count;type:int(10);default:0"` //搜狗收录情况 9 | SoCount int `json:"so_count" gorm:"column:so_count;type:int(10);default:0"` //360收录情况 10 | BingCount int `json:"bing_count" gorm:"column:bing_count;type:int(10);default:0"` //必应收录情况 11 | GoogleCount int `json:"google_count" gorm:"column:google_count;type:int(10);default:0"` //谷歌收录情况 12 | } 13 | -------------------------------------------------------------------------------- /windowsMake.sh: -------------------------------------------------------------------------------- 1 | rm -rf ./release/windows 2 | mkdir -p -v ./release/windows/cache 3 | cp -r ./doc ./release/windows/ 4 | cp -r ./public ./release/windows/ 5 | rm -rf ./release/windows/public/uploads 6 | rm -rf ./release/windows/public/*.txt 7 | rm -rf ./release/windows/public/*.xml 8 | cp -r ./template ./release/windows/ 9 | cp -r ./system ./release/windows/ 10 | cp -r ./language ./release/windows/ 11 | find ./release/windows -name '.DS_Store' | xargs rm -f 12 | cp -r ./CHANGELOG.md ./release/windows/ 13 | cp -r ./stop.bat ./release/windows/ 14 | cp -r ./License ./release/windows/ 15 | cp -r ./clientFiles ./release/windows/ 16 | cp -r ./README.md ./release/windows/ 17 | cp -r ./dictionary.txt ./release/windows/ 18 | GOOS=windows GOARCH=amd64 go build -ldflags '-w -s -H=windowsgui' -o ./release/windows/anqicms.exe kandaoni.com/anqicms/main 19 | -------------------------------------------------------------------------------- /request/category.go: -------------------------------------------------------------------------------- 1 | package request 2 | 3 | type Category struct { 4 | Id uint `json:"id"` 5 | Title string `json:"title"` 6 | SeoTitle string `json:"seo_title"` 7 | Keywords string `json:"keywords"` 8 | Description string `json:"description"` 9 | Content string `json:"content"` 10 | ModuleId uint `json:"module_id"` 11 | ParentId uint `json:"parent_id"` 12 | Sort uint `json:"sort"` 13 | Status uint `json:"status"` 14 | Type uint `json:"type"` 15 | Template string `json:"template"` 16 | DetailTemplate string `json:"detail_template"` 17 | UrlToken string `json:"url_token"` 18 | Images []string `json:"images"` 19 | Logo string `json:"logo"` 20 | IsInherit uint `json:"is_inherit"` 21 | } 22 | -------------------------------------------------------------------------------- /model/base.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "gorm.io/gorm" 5 | ) 6 | 7 | const ( 8 | StatusWait = uint(0) 9 | StatusOk = uint(1) 10 | ) 11 | 12 | /** 13 | * 说明 改用soft delete 14 | */ 15 | type Model struct { 16 | //默认字段 17 | Id uint `json:"id" gorm:"column:id;type:int(10) unsigned not null AUTO_INCREMENT;primaryKey"` 18 | CreatedTime int64 `json:"created_time" gorm:"column:created_time;type:int(11);autoCreateTime;index:idx_created_time"` 19 | UpdatedTime int64 `json:"updated_time" gorm:"column:updated_time;type:int(11);autoUpdateTime;index:idx_updated_time"` 20 | //删除字段不包含在json中 21 | DeletedAt gorm.DeletedAt `json:"-" gorm:"index"` 22 | } 23 | 24 | type CustomField struct { 25 | Name string `json:"name"` 26 | Value interface{} `json:"value"` 27 | Default interface{} `json:"default"` 28 | FollowLevel bool `json:"follow_level"` 29 | } 30 | -------------------------------------------------------------------------------- /provider/transfer_test.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "kandaoni.com/anqicms/request" 5 | "log" 6 | "testing" 7 | ) 8 | 9 | func (w *Website) TestCreateTransferTask(t *testing.T) { 10 | website := request.TransferWebsite{ 11 | Name: "ncwordpress", 12 | BaseUrl: "https://www.nokiipx.com/", 13 | Token: "anqicms", 14 | Provider: "wordpress", 15 | } 16 | task, err := w.CreateTransferTask(&website) 17 | if err != nil { 18 | t.Fatal(err) 19 | } 20 | 21 | task.TransferWebData() 22 | log.Println(task.Current, task.ErrorMsg) 23 | } 24 | 25 | func (w *Website) TestParseContent(t *testing.T) { 26 | conten := `func(){ echo 'aaa';}
` 27 | 28 | result := ParseContent(conten) 29 | 30 | log.Println(result) 31 | } 32 | -------------------------------------------------------------------------------- /controller/apiWeapp.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "github.com/kataras/iris/v12" 5 | "kandaoni.com/anqicms/config" 6 | "kandaoni.com/anqicms/provider" 7 | "kandaoni.com/anqicms/request" 8 | ) 9 | 10 | func ApiCreateWeappQrcode(ctx iris.Context) { 11 | currentSite := provider.CurrentSite(ctx) 12 | var req request.WeappQrcodeRequest 13 | if err := ctx.ReadJSON(&req); err != nil { 14 | ctx.JSON(iris.Map{ 15 | "code": config.StatusFailed, 16 | "msg": err.Error(), 17 | }) 18 | return 19 | } 20 | 21 | userId := ctx.Values().GetUintDefault("userId", 0) 22 | qrcode, err := currentSite.GetWeappQrcode(req.Path, req.Scene, userId) 23 | if err != nil { 24 | ctx.JSON(iris.Map{ 25 | "code": config.StatusFailed, 26 | "msg": err.Error(), 27 | }) 28 | return 29 | } 30 | 31 | ctx.JSON(iris.Map{ 32 | "code": config.StatusOK, 33 | "msg": "", 34 | "data": qrcode, 35 | }) 36 | } 37 | -------------------------------------------------------------------------------- /config/mysql.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "database/sql/driver" 5 | "encoding/json" 6 | "fmt" 7 | ) 8 | 9 | type MysqlConfig struct { 10 | Database string `json:"database"` 11 | User string `json:"user"` 12 | Password string `json:"password"` 13 | Host string `json:"host"` 14 | Port int `json:"port"` 15 | UseDefault bool `json:"use_default"` // 使用 default 的账号密码 16 | } 17 | 18 | // Value implements the driver.Valuer interface. 19 | func (m MysqlConfig) Value() (driver.Value, error) { 20 | return json.Marshal(m) 21 | } 22 | 23 | // Scan implements the sql.Scanner interface. 24 | func (m *MysqlConfig) Scan(src interface{}) error { 25 | switch src := src.(type) { 26 | case []byte: 27 | return json.Unmarshal(src, &m) 28 | case string: 29 | return json.Unmarshal([]byte(src), &m) 30 | case nil: 31 | *m = MysqlConfig{} 32 | return nil 33 | } 34 | 35 | return fmt.Errorf("pq: cannot convert %T", src) 36 | } 37 | -------------------------------------------------------------------------------- /template/default/partial/pagination.html: -------------------------------------------------------------------------------- 1 | {% pagination pages with show="5" %} 2 |
3 | 4 | {% if pages.PrevPage %} 5 | 6 | {% endif %} 7 | {% for item in pages.Pages %} 8 | {% if item.IsCurrent %} 9 | {{item.Name}} 10 | {% else %} 11 | {{item.Name}} 12 | {% endif %} 13 | {% endfor %} 14 | {% if pages.NextPage %} 15 | 16 | {% endif %} 17 | 18 |
19 | {% endpagination %} -------------------------------------------------------------------------------- /template/default/tag/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block container %} 4 | {% include "partial/breadcrumb.html" %} 5 |
6 |

标签中心

7 |
8 |
9 |
    10 | {% tagList tags with type="page" limit="20" %} 11 | {% for item in tags %} 12 |
  • 13 |
    14 |
    15 |

    {{item.Title}}

    16 |

    {{item.Description}}

    17 | 了解详情 18 |
    19 |
    20 |
  • 21 | {% endfor %} 22 | {% endtagList %} 23 |
24 | {% include "partial/pagination.html" %} 25 |
26 | {% include "partial/sidebar.html" %} 27 |
28 |
29 | {% endblock %} -------------------------------------------------------------------------------- /model/guestbook.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "database/sql/driver" 5 | "encoding/json" 6 | ) 7 | 8 | type Guestbook struct { 9 | Model 10 | UserName string `json:"user_name" gorm:"column:user_name;type:varchar(250) not null;default:''"` 11 | Contact string `json:"contact" gorm:"column:contact;type:varchar(250) not null;default:''"` 12 | Content string `json:"content" gorm:"column:content;type:text default null"` 13 | Ip string `json:"ip" gorm:"column:ip;type:varchar(32) not null;default:''"` 14 | Refer string `json:"refer" gorm:"column:refer;type:varchar(250) not null;default:''"` 15 | ExtraData extraData `json:"extra_data" gorm:"column:extra_data;type:longtext default null"` 16 | } 17 | 18 | type extraData map[string]interface{} 19 | 20 | func (e extraData) Value() (driver.Value, error) { 21 | return json.Marshal(e) 22 | } 23 | 24 | func (e *extraData) Scan(data interface{}) error { 25 | return json.Unmarshal(data.([]byte), &e) 26 | } 27 | -------------------------------------------------------------------------------- /library/charset.go: -------------------------------------------------------------------------------- 1 | package library 2 | 3 | import ( 4 | "bytes" 5 | "golang.org/x/text/encoding" 6 | "golang.org/x/text/encoding/simplifiedchinese" 7 | "golang.org/x/text/encoding/traditionalchinese" 8 | "golang.org/x/text/encoding/unicode" 9 | "golang.org/x/text/transform" 10 | "io" 11 | ) 12 | 13 | // CharsetMap 字符集映射 14 | var CharsetMap = map[string]encoding.Encoding{ 15 | "utf-8": unicode.UTF8, 16 | "gbk": simplifiedchinese.GBK, 17 | "gb2312": simplifiedchinese.GB18030, 18 | "gb18030": simplifiedchinese.GB18030, 19 | "big5": traditionalchinese.Big5, 20 | } 21 | 22 | // DecodeToUTF8 从输入的byte数组中按照指定的字符集解析出对应的utf8格式的内容并返回. 23 | func DecodeToUTF8(input []byte, charset encoding.Encoding) (output []byte, err error) { 24 | if charset == unicode.UTF8 { 25 | output = input 26 | return 27 | } 28 | reader := transform.NewReader(bytes.NewReader(input), charset.NewDecoder()) 29 | output, err = io.ReadAll(reader) 30 | if err != nil { 31 | return 32 | } 33 | return 34 | } 35 | -------------------------------------------------------------------------------- /library/word.go: -------------------------------------------------------------------------------- 1 | package library 2 | 3 | import ( 4 | "fmt" 5 | "github.com/huichen/sego" 6 | "kandaoni.com/anqicms/config" 7 | "strings" 8 | ) 9 | 10 | var segmenter sego.Segmenter 11 | var dictLoaded = false 12 | const removeWord = " !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~。?!,、;:“ ” ‘ ’「」『』()〔〕【】《》〈〉—…·~" 13 | 14 | func WordSplit(s string, searchMode bool) []string { 15 | if !dictLoaded { 16 | initDict() 17 | } 18 | segments := segmenter.Segment([]byte(s)) 19 | 20 | words := sego.SegmentsToSlice(segments, searchMode) 21 | // 移除标点、空格等 22 | for i := 0; i < len(words); i++ { 23 | if len(words[i]) == 1 && strings.ContainsAny(words[i], removeWord) { 24 | words = append(words[:i], words[i+1:]...) 25 | i-- 26 | } 27 | } 28 | 29 | return words 30 | } 31 | 32 | func DictClose() { 33 | segmenter.Close() 34 | } 35 | 36 | func initDict() { 37 | dictFile := fmt.Sprintf("%s%s.txt", config.ExecPath, "dictionary") 38 | segmenter.LoadDictionary(dictFile) 39 | dictLoaded = true 40 | } 41 | -------------------------------------------------------------------------------- /model/withdraw.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type UserWithdraw struct { 4 | Model 5 | UserId uint `json:"user_id" gorm:"column:user_id;type:int(10) unsigned not null;default:0;index"` 6 | Amount int64 `json:"amount" gorm:"column:amount;type:bigint(20) not null;default:0"` // 提现金额 7 | SuccessTime int64 `json:"success_time" gorm:"column:success_time;type:int(11);default:0"` // 成功时间 8 | WithdrawWay int `json:"withdraw_way" gorm:"column:withdraw_way;type:tinyint(1) not null;default:0"` // 提现去向,1 微信提现, 9 | Status int `json:"status" gorm:"column:status;type:tinyint(1) not null;default:0"` //0-等待处理 1-已同意 2-已提现,-1 提现错误 10 | ErrorTimes int `json:"error_times" gorm:"column:error_times;type:int(10) not null;default:0"` // 执行错误次数 11 | LastTime int64 `json:"last_time" gorm:"column:last_time;type:int(10) not null;default:0"` // 上次执行时间 12 | Remark string `json:"remark" gorm:"column:remark;type:varchar(250) not null;default:''"` 13 | UserName string `json:"user_name" gorm:"-"` 14 | } 15 | -------------------------------------------------------------------------------- /request/admin.go: -------------------------------------------------------------------------------- 1 | package request 2 | 3 | import "kandaoni.com/anqicms/model" 4 | 5 | type AdminInfoRequest struct { 6 | Id uint `json:"id"` 7 | UserName string `json:"user_name"` 8 | Password string `json:"password"` 9 | CaptchaId string `json:"captcha_id"` 10 | Captcha string `json:"captcha"` 11 | Remember bool `json:"remember"` 12 | Status uint `json:"status"` 13 | GroupId uint `json:"group_id"` 14 | OldPassword string `json:"old_password"` 15 | RePassword string `json:"re_password"` 16 | } 17 | 18 | type GroupRequest struct { 19 | Id uint `json:"id"` 20 | Title string `json:"title"` 21 | Description string `json:"description"` 22 | Status int `json:"status"` 23 | Setting model.GroupSetting `json:"setting"` //配置 24 | } 25 | 26 | type FindPasswordChooseRequest struct { 27 | Way string `json:"way"` 28 | } 29 | 30 | type FindPasswordReset struct { 31 | UserName string `json:"user_name"` 32 | Password string `json:"password"` 33 | } 34 | -------------------------------------------------------------------------------- /model/finance.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type Finance struct { 4 | Model 5 | UserId uint `json:"user_id" gorm:"column:user_id;type:int(10) unsigned not null;default:0;index"` // 用户 6 | Direction int `json:"direction" gorm:"column:direction;type:tinyint(1) not null;default:0"` // 方向 7 | Amount int64 `json:"amount" gorm:"column:amount;type:bigint(20) not null;default:0;comment:'金额'"` 8 | AfterAmount int64 `json:"after_amount" gorm:"column:after_amount;type:bigint(20) not null;default:0;comment:'变更后用户金额'"` 9 | Action int `json:"action" gorm:"column:action;type:tinyint(1) not null;default:0;comment:'资金类型,1出售,2购买,3退款,4充值,5提现,6推广,7返现,8佣金'"` 10 | OrderId string `json:"order_id" gorm:"column:order_id;type:varchar(32) not null;default:'';index"` // 关联的OrderId 11 | Status int `json:"status" gorm:"column:status;type:tinyint(1) not null;default:0"` //0-未提现 1-已提现,-1 其他原因导致退款 12 | Remark string `json:"remark" gorm:"column:remark;type:varchar(250) not null;default:''"` 13 | UserName string `json:"user_name" gorm:"-"` 14 | } 15 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 在linux下运行 2 | all: open 3 | 4 | build: clean 5 | mkdir -p -v ./release/linux/cache 6 | cp -r ./doc ./release/linux/ 7 | cp -r ./public ./release/linux/ 8 | rm -rf ./release/linux/public/uploads 9 | rm -rf ./release/linux/public/*.txt 10 | rm -rf ./release/linux/public/*.xml 11 | cp -r ./template ./release/linux/ 12 | cp -r ./system ./release/linux/ 13 | cp -r ./language ./release/linux/ 14 | cp -r ./CHANGELOG.md ./release/linux/ 15 | find ./release/linux -name '.DS_Store' | xargs rm -f 16 | cp -r ./start.sh ./release/linux/ 17 | cp -r ./stop.sh ./release/linux/ 18 | cp -r ./License ./release/linux/ 19 | cp -r ./clientFiles ./release/linux/ 20 | cp -r ./README.md ./release/linux/ 21 | cp -r ./dictionary.txt ./release/linux/ 22 | dos2unix ./release/linux/start.sh 23 | dos2unix ./release/linux/stop.sh 24 | GOOS=linux GOARCH=amd64 go build -ldflags '-w -s' -o ./release/linux/anqicms kandaoni.com/anqicms/main 25 | 26 | open: build 27 | 28 | clean: 29 | rm -rf ./release/linux 30 | 31 | start: 32 | go run kandaoni.com/anqicms/main 33 | 34 | vet: 35 | go vet $(shell glide nv) 36 | -------------------------------------------------------------------------------- /controller/manageController/pluginFinance.go: -------------------------------------------------------------------------------- 1 | package manageController 2 | 3 | import ( 4 | "github.com/kataras/iris/v12" 5 | "kandaoni.com/anqicms/config" 6 | "kandaoni.com/anqicms/provider" 7 | ) 8 | 9 | func PluginFinanceList(ctx iris.Context) { 10 | currentSite := provider.CurrentSite(ctx) 11 | currentPage := ctx.URLParamIntDefault("current", 1) 12 | pageSize := ctx.URLParamIntDefault("pageSize", 20) 13 | 14 | orders, total := currentSite.GetFinanceList(currentPage, pageSize) 15 | 16 | ctx.JSON(iris.Map{ 17 | "code": config.StatusOK, 18 | "msg": "", 19 | "total": total, 20 | "data": orders, 21 | }) 22 | } 23 | 24 | func PluginFinanceDetail(ctx iris.Context) { 25 | currentSite := provider.CurrentSite(ctx) 26 | id := uint(ctx.URLParamIntDefault("id", 0)) 27 | 28 | withdraw, err := currentSite.GetFinanceById(id) 29 | if err != nil { 30 | ctx.JSON(iris.Map{ 31 | "code": config.StatusFailed, 32 | "msg": err.Error(), 33 | }) 34 | return 35 | } 36 | 37 | ctx.JSON(iris.Map{ 38 | "code": config.StatusOK, 39 | "msg": "", 40 | "data": withdraw, 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /controller/manageController/pluginCommission.go: -------------------------------------------------------------------------------- 1 | package manageController 2 | 3 | import ( 4 | "github.com/kataras/iris/v12" 5 | "kandaoni.com/anqicms/config" 6 | "kandaoni.com/anqicms/provider" 7 | ) 8 | 9 | func PluginCommissionList(ctx iris.Context) { 10 | currentSite := provider.CurrentSite(ctx) 11 | currentPage := ctx.URLParamIntDefault("current", 1) 12 | pageSize := ctx.URLParamIntDefault("pageSize", 20) 13 | 14 | orders, total := currentSite.GetCommissionList(currentPage, pageSize) 15 | 16 | ctx.JSON(iris.Map{ 17 | "code": config.StatusOK, 18 | "msg": "", 19 | "total": total, 20 | "data": orders, 21 | }) 22 | } 23 | 24 | func PluginCommissionDetail(ctx iris.Context) { 25 | currentSite := provider.CurrentSite(ctx) 26 | id := uint(ctx.URLParamIntDefault("id", 0)) 27 | 28 | withdraw, err := currentSite.GetCommissionById(id) 29 | if err != nil { 30 | ctx.JSON(iris.Map{ 31 | "code": config.StatusFailed, 32 | "msg": err.Error(), 33 | }) 34 | return 35 | } 36 | 37 | ctx.JSON(iris.Map{ 38 | "code": config.StatusOK, 39 | "msg": "", 40 | "data": withdraw, 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /provider/finance.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "kandaoni.com/anqicms/model" 5 | ) 6 | 7 | func (w *Website) GetFinanceList(page, pageSize int) ([]*model.Finance, int64) { 8 | var finances []*model.Finance 9 | var total int64 10 | offset := (page - 1) * pageSize 11 | w.DB.Model(&model.Finance{}).Count(&total).Order("id desc").Limit(pageSize).Offset(offset).Find(&finances) 12 | if len(finances) > 0 { 13 | var userIds = make([]uint, 0, len(finances)) 14 | for i := range finances { 15 | userIds = append(userIds, finances[i].UserId) 16 | } 17 | users := w.GetUsersInfoByIds(userIds) 18 | for i := range finances { 19 | for u := range users { 20 | if finances[i].UserId == users[u].Id { 21 | finances[i].UserName = users[u].UserName 22 | } 23 | } 24 | } 25 | } 26 | return finances, total 27 | } 28 | 29 | func (w *Website) GetFinanceById(id uint) (*model.Finance, error) { 30 | var finance model.Finance 31 | err := w.DB.Where(&model.Finance{}).Where("`id` = ?", id).Take(&finance).Error 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | return &finance, nil 37 | } 38 | -------------------------------------------------------------------------------- /model/tag.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type Tag struct { 4 | Model 5 | Title string `json:"title" gorm:"column:title;type:varchar(250) not null;default:''"` 6 | SeoTitle string `json:"seo_title" gorm:"column:seo_title;type:varchar(250) not null;default:''"` 7 | Keywords string `json:"keywords" gorm:"column:keywords;type:varchar(250) not null;default:''"` 8 | UrlToken string `json:"url_token" gorm:"column:url_token;type:varchar(190) not null;default:'';index"` 9 | Description string `json:"description" gorm:"column:description;type:varchar(1000) not null;default:''"` 10 | FirstLetter string `json:"first_letter" gorm:"column:first_letter;type:char(1) not null;default:'';index"` 11 | Status uint `json:"status" gorm:"column:status;type:tinyint(1) unsigned not null;default:0;index:idx_status"` 12 | Link string `json:"link" gorm:"-"` 13 | } 14 | 15 | type TagData struct { 16 | Model 17 | TagId uint `json:"tag_id" gorm:"column:tag_id;type:int(10) not null;default:0;index"` 18 | ItemId uint `json:"item_id" gorm:"column:item_id;type:int(10) unsigned not null;default:0;index:idx_item_id"` 19 | } 20 | -------------------------------------------------------------------------------- /model/commission.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type Commission struct { 4 | Model 5 | UserId uint `json:"user_id" gorm:"column:user_id;type:int(10) unsigned not null;default:0;index"` // 收益的用户 6 | OrderId string `json:"order_id" gorm:"column:order_id;type:varchar(32) not null;default:'';index"` // 关联的OrderId 7 | OrderAmount int64 `json:"order_amount" gorm:"column:order_amount;type:bigint(20) not null;default:0"` // 订单金额 8 | Amount int64 `json:"amount" gorm:"column:amount;type:bigint(20) not null;default:0"` // 获得佣金金额 9 | Status int `json:"status" gorm:"column:status;type:tinyint(1) not null;default:0"` //0-未提现 1-已提现,-1 其他原因导致退款 10 | WithdrawId uint `json:"withdraw_id" gorm:"column:withdraw_id;type:int(10) not null;default:0;index:idx_withdraw_id"` // 提现订单id 11 | Remark string `json:"remark" gorm:"column:remark;type:varchar(250) not null;default:''"` 12 | Order *Order `json:"order" gorm:"-"` 13 | UserName string `json:"user_name" gorm:"-"` 14 | CanWithdraw bool `json:"can_withdraw" gorm:"-"` 15 | } 16 | -------------------------------------------------------------------------------- /provider/common.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "kandaoni.com/anqicms/config" 5 | "kandaoni.com/anqicms/library" 6 | "log" 7 | ) 8 | 9 | const ( 10 | IndexCacheKey = "index" 11 | 12 | UserAgentPc = "pc" 13 | UserAgentMobile = "mobile" 14 | ) 15 | 16 | func (w *Website) CacheIndex(ua string, body []byte) { 17 | w.MemCache.Set(IndexCacheKey+ua, body, 3600) 18 | } 19 | 20 | func (w *Website) GetIndexCache(ua string) []byte { 21 | body := w.MemCache.Get(IndexCacheKey + ua) 22 | 23 | if body == nil { 24 | return nil 25 | } 26 | 27 | content, ok := body.([]byte) 28 | if ok { 29 | return content 30 | } 31 | 32 | return nil 33 | } 34 | 35 | func (w *Website) DeleteCacheIndex() { 36 | w.MemCache.Delete(IndexCacheKey + UserAgentPc) 37 | w.MemCache.Delete(IndexCacheKey + UserAgentMobile) 38 | } 39 | 40 | func init() { 41 | // check what if this server can visit google 42 | go func() { 43 | resp, err := library.GetURLData("https://www.google.com", "", 5) 44 | if err != nil { 45 | config.GoogleValid = false 46 | } else { 47 | config.GoogleValid = true 48 | log.Println("google-status", resp.StatusCode) 49 | } 50 | }() 51 | } 52 | -------------------------------------------------------------------------------- /License: -------------------------------------------------------------------------------- 1 | AnqiCMS 最终用户授权协议 2 | 3 | AnqiCMS的官方网址是: www.anqicms.com 4 | 5 | 为了使您正确并合法的使用本软件,请您在使用前务必阅读清楚下面的协议条款: 6 | 7 | 一、本授权协议适用且仅适用于 AnqiCMS2.x 版本,AnqiCMS官方对本授权协议拥有最终解释权。 8 | 9 | 二、协议许可的权利 10 | 11 | 1.您可以在完全遵守本最终用户授权协议的基础上,将本软件应用于非商业和商业用途,而不必支付软件版权授权费用。 12 | 2.您可以在协议规定的约束和限制范围内修改 AnqiCMS源代码或界面风格以适应您的网站要求。 13 | 3.您拥有使用本软件构建的网站全部内容所有权,并独立承担与这些内容的相关法律义务。 14 | 15 | 三、协议规定的约束和限制 16 | 17 | 1.您不需要获取商业授权,便可将本软件用于构建和经营商业用途网站(包括但不限于企业网站、经营性网站、以营利为目的或实现盈利的网站)。 18 | 2.不管您的网站是否整体使用 AnqiCMS,还是部份栏目使用 AnqiCMS,你都可以选择不保留 AnqiCMS 相关版权信息链接。 19 | 3.未经官方许可,禁止在 AnqiCMS 的整体或任何部分基础上以发展任何派生版本、修改版本或第三方版本用于重新分发,包括但不限于基于AnqiCMS开发SAAS平台等相关服务。 20 | 4.如果您未能遵守本协议的条款,您的授权将被终止,所被许可的权利将被收回,并承担相应法律责任。 21 | 22 | 四、有限担保和免责声明 23 | 24 | 1.本软件及所附带的文件是作为不提供任何明确的或隐含的赔偿或担保的形式提供的。 25 | 2.用户出于自愿而使用本软件,您必须了解使用本软件的风险,在尚未购买产品技术服务之前,我们不承诺对免费用户提供任何形式的技术支持、使用担保,也不承担任何因使用本软件而产生问题的相关责任。 26 | 3.电子文本形式的授权协议如同双方书面签署的协议一样,具有完全的和等同的法律效力。您一旦开始确认本协议并安装 AnqiCMS,即被视为完全理解并接受本协议的各项条款,在享有上述条款授予的权力的同时,受到相关的约束和限制。协议许可范围以外的行为,将直接违反本授权协议并构成侵权,我们有权随时终止授权,责令停止损害,并保留追究相关责任的权力。 27 | 4.如果本软件带有其它软件的整合API示范例子包,这些文件版权不属于本软件官方,并且这些文件是没经过授权发布的,请参考相关软件的使用许可合法的使用。 28 | 29 | 版权所有 ©2019-2022,AnqiCMS.com 保留所有权利。 协议发布时间:2022年6月8日 版本最新更新:2022年6月8日 By AnqiCMS.com -------------------------------------------------------------------------------- /provider/commission.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "kandaoni.com/anqicms/model" 5 | ) 6 | 7 | func (w *Website) GetCommissionList(page, pageSize int) ([]*model.Commission, int64) { 8 | var commissions []*model.Commission 9 | var total int64 10 | offset := (page - 1) * pageSize 11 | w.DB.Model(&model.Commission{}).Count(&total).Order("id desc").Limit(pageSize).Offset(offset).Find(&commissions) 12 | if len(commissions) > 0 { 13 | var userIds = make([]uint, 0, len(commissions)) 14 | for i := range commissions { 15 | userIds = append(userIds, commissions[i].UserId) 16 | } 17 | users := w.GetUsersInfoByIds(userIds) 18 | for i := range commissions { 19 | for u := range users { 20 | if commissions[i].UserId == users[u].Id { 21 | commissions[i].UserName = users[u].UserName 22 | } 23 | } 24 | } 25 | } 26 | return commissions, total 27 | } 28 | 29 | func (w *Website) GetCommissionById(id uint) (*model.Commission, error) { 30 | var commission model.Commission 31 | err := w.DB.Where(&model.Commission{}).Where("`id` = ?", id).Take(&commission).Error 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | return &commission, nil 37 | } 38 | -------------------------------------------------------------------------------- /library/pinyin.go: -------------------------------------------------------------------------------- 1 | package library 2 | 3 | import ( 4 | "github.com/mozillazg/go-pinyin" 5 | "strings" 6 | ) 7 | 8 | var py pinyin.Args 9 | 10 | func GetPinyin(hans string, isSort bool) string { 11 | var result = make([]string, 0, len(hans)) 12 | tmpHans := []rune(hans) 13 | var tmp string 14 | for i, r := range tmpHans { 15 | if (r >= 48 && r <= 57) || (r >= 65 && r <= 90) || (r >= 97 && r <= 122) { 16 | tmp += string(r) 17 | if i == len(tmpHans)-1 { 18 | result = append(result, tmp) 19 | } 20 | } else { 21 | if tmp != "" { 22 | result = append(result, tmp) 23 | tmp = "" 24 | } 25 | result = append(result, pinyin.Slug(string(r), py)) 26 | } 27 | } 28 | 29 | var str string 30 | if isSort { 31 | // 采用首字母模式 32 | var bt = make([]byte, 0, len(result)) 33 | for _, v := range result { 34 | if v != "" { 35 | bt = append(bt, v[0]) 36 | } 37 | } 38 | str = string(bt) 39 | } else { 40 | str = strings.Join(result, "-") 41 | str = ParseUrlToken(str) 42 | if len(str) > 100 { 43 | str = str[:100] 44 | } 45 | 46 | str = strings.Trim(str, "-") 47 | } 48 | 49 | return str 50 | } 51 | 52 | func init() { 53 | py = pinyin.NewArgs() 54 | } 55 | -------------------------------------------------------------------------------- /model/keyword.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "gorm.io/gorm" 5 | ) 6 | 7 | type Keyword struct { 8 | Model 9 | Title string `json:"title" gorm:"column:title;type:varchar(190) not null;default:'';unique"` 10 | Status uint `json:"status" gorm:"column:status;type:tinyint(1) unsigned not null;default:0;index:idx_status"` 11 | CategoryId uint `json:"category_id" gorm:"column:category_id;type:int(10) unsigned not null;default:0;index:idx_category_id"` 12 | Level int `json:"level" gorm:"column:level;type:int(10);default:0"` 13 | ArticleCount int64 `json:"article_count" gorm:"column:article_count;type:int(10);default:0;index"` 14 | HasDig int `json:"has_dig" gorm:"column:has_dig;type:tinyint(1);default:0"` 15 | LastTime int64 `json:"last_time" gorm:"column:last_time;type:int(10);default:0;index"` //上次采集文章执行时间 16 | } 17 | 18 | func (keyword *Keyword) Save(db *gorm.DB) error { 19 | if err := db.Save(keyword).Error; err != nil { 20 | return err 21 | } 22 | 23 | return nil 24 | } 25 | 26 | func (keyword *Keyword) Delete(db *gorm.DB) error { 27 | if err := db.Delete(keyword).Error; err != nil { 28 | return err 29 | } 30 | 31 | return nil 32 | } 33 | -------------------------------------------------------------------------------- /controller/manageController/pluginRobots.go: -------------------------------------------------------------------------------- 1 | package manageController 2 | 3 | import ( 4 | "fmt" 5 | "github.com/kataras/iris/v12" 6 | "kandaoni.com/anqicms/config" 7 | "kandaoni.com/anqicms/provider" 8 | "kandaoni.com/anqicms/request" 9 | ) 10 | 11 | func PluginRobots(ctx iris.Context) { 12 | currentSite := provider.CurrentSite(ctx) 13 | robots := currentSite.GetRobots() 14 | 15 | ctx.JSON(iris.Map{ 16 | "code": config.StatusOK, 17 | "msg": "", 18 | "data": iris.Map{ 19 | "robots": robots, 20 | }, 21 | }) 22 | } 23 | 24 | func PluginRobotsForm(ctx iris.Context) { 25 | currentSite := provider.CurrentSite(ctx) 26 | var req request.PluginRobotsConfig 27 | if err := ctx.ReadJSON(&req); err != nil { 28 | ctx.JSON(iris.Map{ 29 | "code": config.StatusFailed, 30 | "msg": err.Error(), 31 | }) 32 | return 33 | } 34 | 35 | err := currentSite.SaveRobots(req.Robots) 36 | if err != nil { 37 | ctx.JSON(iris.Map{ 38 | "code": config.StatusFailed, 39 | "msg": err.Error(), 40 | }) 41 | return 42 | } 43 | 44 | currentSite.AddAdminLog(ctx, fmt.Sprintf("更新Robots信息")) 45 | 46 | ctx.JSON(iris.Map{ 47 | "code": config.StatusOK, 48 | "msg": "配置已更新", 49 | }) 50 | } 51 | -------------------------------------------------------------------------------- /response/statistic.go: -------------------------------------------------------------------------------- 1 | package response 2 | 3 | import "kandaoni.com/anqicms/model" 4 | 5 | type ModuleCount struct { 6 | Name string `json:"name"` 7 | Total int64 `json:"total"` 8 | } 9 | type ArchiveCount struct { 10 | Total int64 `json:"total"` 11 | LastWeek int64 `json:"last_week"` 12 | UnRelease int64 `json:"un_release"` 13 | Today int64 `json:"today"` 14 | } 15 | type SplitCount struct { 16 | Total int64 `json:"total"` 17 | Today int64 `json:"today"` 18 | } 19 | type Statistics struct { 20 | CacheTime int64 21 | ModuleCounts []ModuleCount `json:"archive_counts"` 22 | ArchiveCount ArchiveCount `json:"archive_count"` 23 | CategoryCount int64 `json:"category_count"` 24 | LinkCount int64 `json:"link_count"` 25 | GuestbookCount int64 `json:"guestbook_count"` 26 | TrafficCount SplitCount `json:"traffic_count"` 27 | SpiderCount SplitCount `json:"spider_count"` 28 | IncludeCount model.SpiderInclude `json:"include_count"` 29 | TemplateCount int64 `json:"template_count"` 30 | PageCount int64 `json:"page_count"` 31 | AttachmentCount int64 `json:"attachment_count"` 32 | } 33 | -------------------------------------------------------------------------------- /template/default/page/detail.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block container %} 4 | {% include "partial/breadcrumb.html" %} 5 |
6 | 7 |
8 | 9 |
10 |
11 |
12 |

{{page.Title}}

13 |
14 |
15 | {% pageDetail pageContent with name="Content" %} 16 | {{pageContent|safe}} 17 |
18 | 19 |
20 |

相关页面

21 |
    22 | {% pageList allPages %} 23 | {% for item in allPages %} 24 |
  • 25 |

    {{item.Title}}

    26 | {{stampToDate(item.CreatedTime, "2006-01-02")}} 27 |
  • 28 | {% endfor %} 29 | {% endpageList %} 30 |
31 |
32 | 33 |
34 | {% include "partial/sidebar.html" %} 35 |
36 | 37 |
38 | 39 |
40 | {% endblock %} -------------------------------------------------------------------------------- /library/request_test.go: -------------------------------------------------------------------------------- 1 | package library 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "testing" 7 | ) 8 | 9 | func TestRequest(t *testing.T) { 10 | link := "https://baijiahao.baidu.com/s?id=1672606603627218710&wfr=spider&for=pc" 11 | resp, err := Request(link, &Options{ 12 | Timeout: 5, 13 | IsMobile: false, 14 | Header: map[string]string{ 15 | //"Referer": fmt.Sprintf("https://www.baidu.com/s?wd=%s", "SEO学习教程"), 16 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", 17 | "Accept-Encoding": "gzip, deflate, br", 18 | "Accept-Language": "zh-CN,zh;q=0.9", 19 | "Cache-Control": "no-cache", 20 | "Pragma": "no-cache", 21 | "User-Agent": "Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)", 22 | }, 23 | }) 24 | 25 | log.Println(err) 26 | 27 | log.Println(resp) 28 | } 29 | 30 | func TestGetURLData(t *testing.T) { 31 | resp, err := GetURLData("https://baijiahao.baidu.com/s?id=1672606603627218710&wfr=spider&for=pc", fmt.Sprintf("https://www.baidu.com/s?wd=%s", "SEO学习教程"), 10) 32 | if err != nil { 33 | log.Println(err) 34 | return 35 | } 36 | log.Printf("%#v", resp.Request) 37 | log.Printf("%#v", resp.Body) 38 | } 39 | -------------------------------------------------------------------------------- /request/anqi.go: -------------------------------------------------------------------------------- 1 | package request 2 | 3 | type AnqiLoginRequest struct { 4 | UserName string `json:"user_name"` 5 | Password string `json:"password"` 6 | } 7 | 8 | type AnqiTemplateRequest struct { 9 | TemplateId uint `json:"template_id"` 10 | OnlyTemplate bool `json:"only_template"` 11 | AutoBackup bool `json:"auto_backup"` 12 | Name string `json:"name"` 13 | Price int64 `json:"price"` 14 | Author string `json:"author"` 15 | Package string `json:"package"` 16 | Version string `json:"version"` 17 | Description string `json:"description"` 18 | Homepage string `json:"homepage"` 19 | TemplateType int `json:"template_type"` 20 | PCThumb string `json:"pc_thumb"` 21 | MobileThumb string `json:"mobile_thumb"` 22 | Content string `json:"content"` 23 | PreviewImages []string `json:"preview_images"` 24 | TemplatePath string `json:"template_path"` 25 | } 26 | 27 | type AnqiFeedbackRequest struct { 28 | Title string `json:"title"` 29 | Type string `json:"type"` 30 | Content string `json:"content"` 31 | Domain string `json:"domain"` 32 | Version string `json:"version"` 33 | Platform string `json:"platform"` 34 | Images []string `json:"images"` 35 | } 36 | -------------------------------------------------------------------------------- /template/default/article/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block container %} 4 | {% include "partial/breadcrumb.html" %} 5 |
6 |

新闻中心

7 |
8 |
9 |
    10 | {% archiveList articles with type="page" limit="10" %} 11 | {% for item in articles %} 12 |
  • 13 |
    14 |
    15 | {{item.Title}} 16 |
    17 |
    18 |

    {{item.Title}}

    19 |
    20 | 发布时间:{{stampToDate(item.CreatedTime, "2006-01-02")}} 21 | 作者:{% system with name="SiteName" %} 22 |
    23 |

    {{item.Description}}

    24 | 了解详情 25 |
    26 |
    27 |
  • 28 | {% endfor %} 29 | {% endarchiveList %} 30 |
31 | {% include "partial/pagination.html" %} 32 |
33 | {% include "partial/sidebar.html" %} 34 |
35 |
36 | {% endblock %} -------------------------------------------------------------------------------- /request/design.go: -------------------------------------------------------------------------------- 1 | package request 2 | 3 | type DesignInfoRequest struct { 4 | Name string `json:"name"` 5 | Package string `json:"package"` 6 | Version string `json:"version"` 7 | Description string `json:"description"` 8 | Author string `json:"author"` 9 | Homepage string `json:"homepage"` 10 | Created string `json:"created"` 11 | TemplateType int `json:"template_type"` 12 | } 13 | 14 | type RestoreDesignFileRequest struct { 15 | Hash string `json:"hash"` 16 | Package string `json:"package"` 17 | Filepath string `json:"path"` 18 | Type string `json:"type"` 19 | } 20 | 21 | type SaveDesignFileRequest struct { 22 | Package string `json:"package"` 23 | Path string `json:"path"` 24 | Type string `json:"type"` 25 | RenamePath string `json:"rename_path"` 26 | Content string `json:"content"` 27 | Remark string `json:"remark"` 28 | UpdateContent bool `json:"update_content"` 29 | } 30 | 31 | type CopyDesignFileRequest struct { 32 | Package string `json:"package"` 33 | Path string `json:"path"` 34 | NewPath string `json:"new_path"` 35 | Type string `json:"type"` 36 | Remark string `json:"remark"` 37 | } 38 | 39 | type DesignDataRequest struct { 40 | Package string `json:"package"` 41 | AutoBackup bool `json:"auto_backup"` 42 | } 43 | -------------------------------------------------------------------------------- /controller/attachment.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "fmt" 5 | "github.com/kataras/iris/v12" 6 | "kandaoni.com/anqicms/config" 7 | "kandaoni.com/anqicms/provider" 8 | ) 9 | 10 | func AttachmentUpload(ctx iris.Context) { 11 | currentSite := provider.CurrentSite(ctx) 12 | // 增加分类 13 | categoryId := uint(ctx.PostValueIntDefault("category_id", 0)) 14 | attachId := uint(ctx.PostValueIntDefault("id", 0)) 15 | file, info, err := ctx.FormFile("file") 16 | if err != nil { 17 | ctx.JSON(iris.Map{ 18 | "code": config.StatusFailed, 19 | "msg": err.Error(), 20 | }) 21 | return 22 | } 23 | defer file.Close() 24 | 25 | if attachId > 0 { 26 | _, err := currentSite.GetAttachmentById(attachId) 27 | if err != nil { 28 | ctx.JSON(iris.Map{ 29 | "code": config.StatusFailed, 30 | "msg": currentSite.Lang("需要替换的图片资源不存在"), 31 | }) 32 | return 33 | } 34 | } 35 | 36 | attachment, err := currentSite.AttachmentUpload(file, info, categoryId, attachId) 37 | if err != nil { 38 | ctx.JSON(iris.Map{ 39 | "code": config.StatusFailed, 40 | "msg": err.Error(), 41 | }) 42 | return 43 | } 44 | 45 | currentSite.AddAdminLog(ctx, fmt.Sprintf("上传资源附件:%d => %s", attachment.Id, attachment.FileLocation)) 46 | 47 | ctx.JSON(iris.Map{ 48 | "code": config.StatusOK, 49 | "msg": "", 50 | "data": attachment, 51 | }) 52 | } 53 | -------------------------------------------------------------------------------- /controller/manageController/pluginFulltext.go: -------------------------------------------------------------------------------- 1 | package manageController 2 | 3 | import ( 4 | "fmt" 5 | "github.com/kataras/iris/v12" 6 | "kandaoni.com/anqicms/config" 7 | "kandaoni.com/anqicms/provider" 8 | ) 9 | 10 | func PluginFulltextConfig(ctx iris.Context) { 11 | currentSite := provider.CurrentSite(ctx) 12 | setting := currentSite.PluginFulltext 13 | 14 | ctx.JSON(iris.Map{ 15 | "code": config.StatusOK, 16 | "msg": "", 17 | "data": setting, 18 | }) 19 | } 20 | 21 | func PluginFulltextConfigForm(ctx iris.Context) { 22 | currentSite := provider.CurrentSite(ctx) 23 | var req config.PluginFulltextConfig 24 | if err := ctx.ReadJSON(&req); err != nil { 25 | ctx.JSON(iris.Map{ 26 | "code": config.StatusFailed, 27 | "msg": err.Error(), 28 | }) 29 | return 30 | } 31 | 32 | currentSite.PluginFulltext.Open = req.Open 33 | 34 | err := currentSite.SaveSettingValue(provider.FulltextSettingKey, currentSite.PluginFulltext) 35 | if err != nil { 36 | ctx.JSON(iris.Map{ 37 | "code": config.StatusFailed, 38 | "msg": err.Error(), 39 | }) 40 | return 41 | } 42 | 43 | currentSite.AddAdminLog(ctx, fmt.Sprintf("更新全文索引配置信息")) 44 | if req.Open { 45 | go currentSite.InitFulltext() 46 | } else { 47 | currentSite.CloseFulltext() 48 | } 49 | 50 | ctx.JSON(iris.Map{ 51 | "code": config.StatusOK, 52 | "msg": "配置已更新", 53 | }) 54 | } 55 | -------------------------------------------------------------------------------- /controller/user.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "fmt" 5 | "github.com/kataras/iris/v12" 6 | "kandaoni.com/anqicms/provider" 7 | "kandaoni.com/anqicms/response" 8 | "strings" 9 | ) 10 | 11 | func UserPage(ctx iris.Context) { 12 | currentSite := provider.CurrentSite(ctx) 13 | id := ctx.Params().GetUintDefault("id", 0) 14 | 15 | user, err := currentSite.GetUserInfoById(id) 16 | if err != nil { 17 | NotFound(ctx) 18 | return 19 | } 20 | 21 | ctx.ViewData("user", user) 22 | 23 | if webInfo, ok := ctx.Value("webInfo").(*response.WebInfo); ok { 24 | webInfo.Title = user.UserName 25 | webInfo.NavBar = user.Id 26 | webInfo.PageName = "userDetail" 27 | webInfo.CanonicalUrl = currentSite.GetUrl("user", user, 0) 28 | ctx.ViewData("webInfo", webInfo) 29 | } 30 | tplName := "people/detail.html" 31 | if ViewExists(ctx, "people_detail.html") { 32 | tplName = "people_detail.html" 33 | } 34 | tmpTpl := fmt.Sprintf("people/detail-%d.html", user.Id) 35 | if ViewExists(ctx, tmpTpl) { 36 | tplName = tmpTpl 37 | } else if ViewExists(ctx, fmt.Sprintf("people-%d.html", user.Id)) { 38 | tplName = fmt.Sprintf("people-%d.html", user.Id) 39 | } 40 | if !strings.HasSuffix(tplName, ".html") { 41 | tplName += ".html" 42 | } 43 | 44 | err = ctx.View(GetViewPath(ctx, tplName)) 45 | if err != nil { 46 | ctx.Values().Set("message", err.Error()) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /template/default/case/list.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block container %} 4 | {% include "partial/breadcrumb.html" %} 5 |
6 |

{% categoryDetail with name="Title" %}

7 |
8 |
9 |
    10 | {% archiveList products with type="page" limit="9" %} 11 | {% for item in products %} 12 |
  • 13 |
    14 |
    15 | {{item.Title}} 16 |
    17 |
    18 |

    {{item.Title}}

    19 |
    20 |
    21 |
  • 22 |
  • 23 |
    24 |
    25 | 26 |
    27 |
    28 |

    {{item.Title}}

    29 |

    {{item.Description}}

    30 |
    31 |
    32 |
  • 33 | {% endfor %} 34 | {% endarchiveList %} 35 |
36 | {% include "partial/pagination.html" %} 37 |
38 | {% include "partial/sidebar.html" %} 39 |
40 |
41 | {% endblock %} -------------------------------------------------------------------------------- /tags/common.go: -------------------------------------------------------------------------------- 1 | package tags 2 | 3 | import "github.com/flosch/pongo2/v6" 4 | 5 | func parseArgs(args map[string]pongo2.IEvaluator, ctx *pongo2.ExecutionContext) (map[string]*pongo2.Value, *pongo2.Error) { 6 | parsedArgs := map[string]*pongo2.Value{} 7 | for key, value := range args { 8 | val, err := value.Evaluate(ctx) 9 | if err != nil { 10 | return nil, err 11 | } 12 | parsedArgs[key] = val 13 | } 14 | 15 | return parsedArgs, nil 16 | } 17 | 18 | func parseWith(arguments *pongo2.Parser) (map[string]pongo2.IEvaluator, *pongo2.Error) { 19 | args := make(map[string]pongo2.IEvaluator) 20 | // After having parsed the name we're gonna parse the with options 21 | if arguments.Match(pongo2.TokenIdentifier, "with") != nil { 22 | for arguments.Remaining() > 0 { 23 | // We have at least one key=expr pair (because of starting "with") 24 | keyToken := arguments.MatchType(pongo2.TokenIdentifier) 25 | if keyToken == nil { 26 | return nil, arguments.Error("Expected an identifier", nil) 27 | } 28 | if arguments.Match(pongo2.TokenSymbol, "=") == nil { 29 | return nil, arguments.Error("Expected '='.", nil) 30 | } 31 | valueExpr, err := arguments.ParseExpression() 32 | if err != nil { 33 | return nil, arguments.Error("Can not parse with args.", keyToken) 34 | } 35 | 36 | args[keyToken.Val] = valueExpr 37 | } 38 | } 39 | 40 | return args, nil 41 | } 42 | -------------------------------------------------------------------------------- /template/default/page/about.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block container %} 4 | {% include "partial/breadcrumb.html" %} 5 | 6 |
7 |

公司介绍

8 |
9 |
10 |
11 | 12 |
13 |
14 |

上海安企仪器设备有限公司

15 |

企业愿景:成为世界一流的科学仪器生产服务商。

16 |

企业使命:致力于温湿度,样品前处理的研究与应用,为中国智造提供更精准,安全,可靠的科研设备奋斗终身。

17 |

企业价值观:心无旁骛、精益求精、以客户为中心,其他一切水到渠成。

18 |

品牌价值观:更安全,更精确,更高效。

19 |

经营理念:

20 |

锐意进取,通过优质产品及服务提供最佳解决方案。

21 |

以人为本,实现用户、企业、员工的价值最优化。

22 |

求实上进,不断提高员工专业素质及技术水平。

23 |

我们的成功源自于为您不断提供满意的服务和高效生产力。

24 |
25 |
26 |
27 | {% pageDetail content with name="Content" %} 28 | {{content|safe}} 29 |
30 |
31 |
32 |

安企仪器展览厅

33 |
34 |
35 | 36 |
37 |
38 |
39 |
40 | 41 | {% endblock %} -------------------------------------------------------------------------------- /config/pay.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type PluginPayConfig struct { 4 | AlipayAppId string `json:"alipay_app_id"` 5 | AlipayPrivateKey string `json:"alipay_private_key"` 6 | AlipayCertPath string `json:"alipay_cert_path"` // 应用公钥证书路径 7 | AlipayRootCertPath string `json:"alipay_root_cert_path"` // 支付宝根证书文件路径 8 | AlipayPublicCertPath string `json:"alipay_public_cert_path"` // 支付宝公钥证书文件路径 9 | 10 | WechatAppId string `json:"wechat_app_id"` // 公众号 11 | WechatAppSecret string `json:"wechat_app_secret"` // 公众号 12 | WeappAppId string `json:"weapp_app_id"` // 小程序 13 | WeappAppSecret string `json:"weapp_app_secret"` // 小程序 14 | WechatMchId string `json:"wechat_mch_id"` // 公众号、小程序共用商户ID 15 | WechatApiKey string `json:"wechat_api_key"` // 公众号、小程序共用支付密钥 16 | WechatCertPath string `json:"wechat_cert_path"` // 证书路径 17 | WechatKeyPath string `json:"wechat_key_path"` // 证书路径 18 | } 19 | 20 | type PluginRetailerConfig struct { 21 | AllowSelf int64 `json:"allow_self"` // 允许自购 0,1 22 | BecomeRetailer int64 `json:"become_retailer"` // 成为分销员方式, 0 审核,1 自动 23 | } 24 | 25 | type PluginOrderConfig struct { 26 | NoProcess bool `json:"no_process"` // 是否没有交易流程 27 | AutoFinishDay int `json:"auto_finish_day"` // 自动完成订单时间 28 | AutoCloseMinute int64 `json:"auto_close_minute"` // 自动关闭订单时间 29 | SellerPercent int64 `json:"seller_percent"` // 商家销售获得收益比例 30 | } 31 | -------------------------------------------------------------------------------- /controller/manageController/pluginWeapp.go: -------------------------------------------------------------------------------- 1 | package manageController 2 | 3 | import ( 4 | "fmt" 5 | "github.com/kataras/iris/v12" 6 | "kandaoni.com/anqicms/config" 7 | "kandaoni.com/anqicms/provider" 8 | ) 9 | 10 | func PluginWeappConfig(ctx iris.Context) { 11 | currentSite := provider.CurrentSite(ctx) 12 | pluginRewrite := currentSite.PluginWeapp 13 | 14 | ctx.JSON(iris.Map{ 15 | "code": config.StatusOK, 16 | "msg": "", 17 | "data": pluginRewrite, 18 | }) 19 | } 20 | 21 | func PluginWeappConfigForm(ctx iris.Context) { 22 | currentSite := provider.CurrentSite(ctx) 23 | var req config.PluginWeappConfig 24 | if err := ctx.ReadJSON(&req); err != nil { 25 | ctx.JSON(iris.Map{ 26 | "code": config.StatusFailed, 27 | "msg": err.Error(), 28 | }) 29 | return 30 | } 31 | 32 | currentSite.PluginWeapp.AppID = req.AppID 33 | currentSite.PluginWeapp.AppSecret = req.AppSecret 34 | currentSite.PluginWeapp.Token = req.Token 35 | currentSite.PluginWeapp.EncodingAESKey = req.EncodingAESKey 36 | 37 | err := currentSite.SaveSettingValue(provider.WeappSettingKey, currentSite.PluginWeapp) 38 | if err != nil { 39 | ctx.JSON(iris.Map{ 40 | "code": config.StatusFailed, 41 | "msg": err.Error(), 42 | }) 43 | return 44 | } 45 | // 强制更新信息 46 | currentSite.GetWeappClient(true) 47 | 48 | currentSite.AddAdminLog(ctx, fmt.Sprintf("更新小程序信息")) 49 | 50 | ctx.JSON(iris.Map{ 51 | "code": config.StatusOK, 52 | "msg": "配置已更新", 53 | }) 54 | } 55 | -------------------------------------------------------------------------------- /template/default/search/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block container %} 4 | {% include "partial/breadcrumb.html" %} 5 |
6 |

搜索:{{urlParams.q}}

7 |
8 |
9 |
    10 | {% archiveList articles with type="page" limit="10" %} 11 | {% for item in articles %} 12 |
  • 13 |
    14 |
    15 | {{item.Title}} 16 |
    17 |
    18 |

    {{item.Title}}

    19 |
    20 | 发布时间:{{stampToDate(item.CreatedTime, "2006-01-02")}} 21 | 作者:{% system with name="SiteName" %} 22 |
    23 |

    {{item.Description}}

    24 | 了解详情 25 |
    26 |
    27 |
  • 28 | {% endfor %} 29 | {% endarchiveList %} 30 |
31 | {% include "partial/pagination.html" %} 32 |
33 | {% include "partial/sidebar.html" %} 34 |
35 |
36 | {% endblock %} -------------------------------------------------------------------------------- /template/default/partial/sidebar.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /template/default/tag/list.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block container %} 4 | {% include "partial/breadcrumb.html" %} 5 |
6 |

{% tagDetail with name="Title" %}

7 |
8 |
9 |
    10 | {% tagDataList articles with type="page" limit="10" %} 11 | {% for item in articles %} 12 |
  • 13 |
    14 |
    15 | {{item.Title}} 16 |
    17 |
    18 |

    {{item.Title}}

    19 |
    20 | 发布时间:{{stampToDate(item.CreatedTime, "2006-01-02")}} 21 | 作者:{% system with name="SiteName" %} 22 |
    23 |

    {{item.Description}}

    24 | 了解详情 25 |
    26 |
    27 |
  • 28 | {% endfor %} 29 | {% endtagDataList %} 30 |
31 | {% include "partial/pagination.html" %} 32 |
33 | {% include "partial/sidebar.html" %} 34 |
35 |
36 | {% endblock %} -------------------------------------------------------------------------------- /library/file.go: -------------------------------------------------------------------------------- 1 | package library 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "os" 8 | "path/filepath" 9 | "strings" 10 | ) 11 | 12 | func CopyDir(dstDir string, srcDir string) error { 13 | files, err := os.ReadDir(srcDir) 14 | if err != nil { 15 | return err 16 | } 17 | for _, file := range files { 18 | if strings.HasPrefix(file.Name(), ".") { 19 | continue 20 | } 21 | fullName := srcDir + "/" + file.Name() 22 | dstName := dstDir + "/" + file.Name() 23 | if file.IsDir() { 24 | err = CopyDir(dstName, fullName) 25 | } else { 26 | _, err = CopyFile(dstName, fullName) 27 | } 28 | } 29 | return nil 30 | } 31 | 32 | func CopyFile(dstFileName string, srcFileName string) (written int64, err error) { 33 | srcFile, err := os.Open(srcFileName) 34 | if err != nil { 35 | fmt.Printf("open file error1 = %v\n", err) 36 | } 37 | defer srcFile.Close() 38 | 39 | //通过srcFile,获取到READER 40 | reader := bufio.NewReader(srcFile) 41 | 42 | err = os.MkdirAll(filepath.Dir(dstFileName), os.ModePerm) 43 | if err != nil { 44 | //log.Println(err.Error()) 45 | return 0, err 46 | } 47 | //打开dstFileName 48 | dstFile, err := os.OpenFile(dstFileName, os.O_WRONLY|os.O_CREATE, 0666) 49 | if err != nil { 50 | //fmt.Printf("open file error2 = %v\n", err) 51 | return 52 | } 53 | 54 | //通过dstFile,获取到WRITER 55 | writer := bufio.NewWriter(dstFile) 56 | //writer.Flush() 57 | 58 | defer dstFile.Close() 59 | 60 | return io.Copy(writer, reader) 61 | } 62 | -------------------------------------------------------------------------------- /template/default/article/list.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block container %} 4 | {% include "partial/breadcrumb.html" %} 5 |
6 |

{% categoryDetail with name="Title" %}

7 |
8 |
9 |
    10 | {% archiveList articles with type="page" limit="10" %} 11 | {% for item in articles %} 12 |
  • 13 |
    14 |
    15 | {{item.Title}} 16 |
    17 |
    18 |

    {{item.Title}}

    19 |
    20 | 发布时间:{{stampToDate(item.CreatedTime, "2006-01-02")}} 21 | 作者:{% system with name="SiteName" %} 22 |
    23 |

    {{item.Description}}

    24 | 了解详情 25 |
    26 |
    27 |
  • 28 | {% endfor %} 29 | {% endarchiveList %} 30 |
31 | {% include "partial/pagination.html" %} 32 |
33 | {% include "partial/sidebar.html" %} 34 |
35 |
36 | {% endblock %} -------------------------------------------------------------------------------- /model/anchor.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "gorm.io/gorm" 5 | ) 6 | 7 | type Anchor struct { 8 | Model 9 | Title string `json:"title" gorm:"column:title;type:varchar(190) not null;default:'';unique"` 10 | ArchiveId uint `json:"archive_id" gorm:"column:archive_id;type:int(10) not null;default:0"` 11 | Link string `json:"link" gorm:"column:link;type:varchar(190) not null;default:'';index"` 12 | Weight int `json:"weight" gorm:"column:weight;type:int(10) not null;default:0;index:idx_weight"` 13 | ReplaceCount int64 `json:"replace_count" gorm:"column:replace_count;type:int(10) not null;default:0"` 14 | Status uint `json:"status" gorm:"column:status;type:tinyint(1) unsigned not null;default:0;index:idx_status"` 15 | } 16 | 17 | type AnchorData struct { 18 | Model 19 | AnchorId uint `json:"anchor_id" gorm:"column:anchor_id;type:int(10) not null;default:0;index"` 20 | ItemType string `json:"item_type" gorm:"column:item_type;type:varchar(32) not null;default:'';index:idx_item_type"` 21 | ItemId uint `json:"item_id" gorm:"column:item_id;type:int(10) unsigned not null;default:0;index:idx_item_type"` 22 | } 23 | 24 | func (anchor *Anchor) Save(db *gorm.DB) error { 25 | if err := db.Save(anchor).Error; err != nil { 26 | return err 27 | } 28 | 29 | return nil 30 | } 31 | 32 | func (anchor *Anchor) Delete(db *gorm.DB) error { 33 | if err := db.Delete(anchor).Error; err != nil { 34 | return err 35 | } 36 | 37 | return nil 38 | } 39 | -------------------------------------------------------------------------------- /response/design.go: -------------------------------------------------------------------------------- 1 | package response 2 | 3 | type DesignPackage struct { 4 | TemplateId uint `json:"template_id"` 5 | AuthId uint `json:"auth_id"` 6 | Name string `json:"name"` 7 | Package string `json:"package"` 8 | Version string `json:"version"` 9 | Description string `json:"description"` 10 | Author string `json:"author"` 11 | Homepage string `json:"homepage"` 12 | Created string `json:"created"` 13 | TemplateType int `json:"template_type"` 14 | Status int `json:"status"` 15 | TplFiles []DesignFile `json:"tpl_files"` 16 | StaticFiles []DesignFile `json:"static_files"` 17 | PreviewData bool `json:"preview_data"` 18 | } 19 | 20 | type DesignFile struct { 21 | Path string `json:"path"` 22 | Remark string `json:"remark"` 23 | 24 | Content string `json:"content,omitempty"` 25 | LastMod int64 `json:"last_mod,omitempty"` 26 | Size int64 `json:"size"` 27 | } 28 | 29 | type DesignFileHistory struct { 30 | Hash string `json:"hash"` 31 | Content string `json:"content,omitempty"` 32 | LastMod int64 `json:"last_mod,omitempty"` 33 | Size int64 `json:"size"` 34 | } 35 | type DesignDocGroup struct { 36 | Title string `json:"title"` 37 | Docs []DesignDoc `json:"docs"` 38 | } 39 | 40 | type DesignDoc struct { 41 | Title string `json:"title"` 42 | Link string `json:"link"` 43 | Content string `json:"content"` 44 | } 45 | -------------------------------------------------------------------------------- /controller/manageController/pluginRewrite.go: -------------------------------------------------------------------------------- 1 | package manageController 2 | 3 | import ( 4 | "fmt" 5 | "github.com/kataras/iris/v12" 6 | "kandaoni.com/anqicms/config" 7 | "kandaoni.com/anqicms/provider" 8 | ) 9 | 10 | func PluginRewrite(ctx iris.Context) { 11 | currentSite := provider.CurrentSite(ctx) 12 | pluginRewrite := currentSite.PluginRewrite 13 | 14 | ctx.JSON(iris.Map{ 15 | "code": config.StatusOK, 16 | "msg": "", 17 | "data": pluginRewrite, 18 | }) 19 | } 20 | 21 | func PluginRewriteForm(ctx iris.Context) { 22 | currentSite := provider.CurrentSite(ctx) 23 | var req config.PluginRewriteConfig 24 | if err := ctx.ReadJSON(&req); err != nil { 25 | ctx.JSON(iris.Map{ 26 | "code": config.StatusFailed, 27 | "msg": err.Error(), 28 | }) 29 | return 30 | } 31 | 32 | if currentSite.PluginRewrite.Mode != req.Mode || currentSite.PluginRewrite.Patten != req.Patten { 33 | currentSite.PluginRewrite.Mode = req.Mode 34 | currentSite.PluginRewrite.Patten = req.Patten 35 | err := currentSite.SaveSettingValue(provider.RewriteSettingKey, currentSite.PluginRewrite) 36 | if err != nil { 37 | ctx.JSON(iris.Map{ 38 | "code": config.StatusFailed, 39 | "msg": err.Error(), 40 | }) 41 | return 42 | } 43 | 44 | currentSite.ParsePatten(true) 45 | currentSite.DeleteCacheIndex() 46 | } 47 | 48 | currentSite.AddAdminLog(ctx, fmt.Sprintf("调整伪静态配置:%d", req.Mode)) 49 | 50 | ctx.JSON(iris.Map{ 51 | "code": config.StatusOK, 52 | "msg": "配置已更新", 53 | }) 54 | } 55 | -------------------------------------------------------------------------------- /response/response.go: -------------------------------------------------------------------------------- 1 | package response 2 | 3 | import "time" 4 | 5 | type AuthResponse struct { 6 | HashKey string `json:"hash_key"` 7 | CreatedTime int64 `json:"created_time"` 8 | Code string `json:"code"` 9 | Status int `json:"status"` 10 | } 11 | 12 | type SumAmount struct { 13 | Total int64 `json:"total"` 14 | } 15 | 16 | type LoginError struct { 17 | Times int 18 | LastTime int64 19 | } 20 | 21 | type FilterGroup struct { 22 | Name string `json:"name"` 23 | FieldName string `json:"field_name"` 24 | Items []FilterItem `json:"items"` 25 | } 26 | 27 | type FilterItem struct { 28 | Label string `json:"label"` 29 | Link string `json:"link"` 30 | IsCurrent bool `json:"is_current"` 31 | Total int64 `json:"total"` 32 | } 33 | 34 | type LastVersion struct { 35 | Version string `json:"version"` 36 | Description string `json:"description"` 37 | } 38 | 39 | type ChartData struct { 40 | Date string `json:"date"` 41 | Label string `json:"label"` 42 | Value int `json:"value"` 43 | } 44 | 45 | type PushLog struct { 46 | CreatedTime int64 `json:"created_time"` 47 | Spider string `json:"spider"` 48 | Result string `json:"result"` 49 | } 50 | 51 | type FindPasswordInfo struct { 52 | Way string `json:"way"` 53 | Token string `json:"token"` 54 | Host string `json:"host"` 55 | Verified bool `json:"verified"` 56 | End time.Time `json:"-"` 57 | Timer *time.Timer `json:"-"` 58 | } 59 | -------------------------------------------------------------------------------- /controller/manageController/pluginImportapi.go: -------------------------------------------------------------------------------- 1 | package manageController 2 | 3 | import ( 4 | "fmt" 5 | "github.com/kataras/iris/v12" 6 | "kandaoni.com/anqicms/config" 7 | "kandaoni.com/anqicms/provider" 8 | ) 9 | 10 | func PluginImportApi(ctx iris.Context) { 11 | currentSite := provider.CurrentSite(ctx) 12 | importApi := currentSite.PluginImportApi 13 | 14 | ctx.JSON(iris.Map{ 15 | "code": config.StatusOK, 16 | "msg": "", 17 | "data": iris.Map{ 18 | "token": importApi.Token, 19 | "link_token": importApi.LinkToken, 20 | "base_url": currentSite.System.BaseUrl, 21 | }, 22 | }) 23 | } 24 | 25 | func PluginUpdateApiToken(ctx iris.Context) { 26 | currentSite := provider.CurrentSite(ctx) 27 | var req config.PluginImportApiConfig 28 | if err := ctx.ReadJSON(&req); err != nil { 29 | ctx.JSON(iris.Map{ 30 | "code": config.StatusFailed, 31 | "msg": err.Error(), 32 | }) 33 | return 34 | } 35 | if req.Token != "" { 36 | currentSite.PluginImportApi.Token = req.Token 37 | } 38 | if req.LinkToken != "" { 39 | currentSite.PluginImportApi.LinkToken = req.LinkToken 40 | } 41 | // 回写 42 | err := currentSite.SaveSettingValue(provider.ImportApiSettingKey, currentSite.PluginImportApi) 43 | if err != nil { 44 | ctx.JSON(iris.Map{ 45 | "code": config.StatusFailed, 46 | "msg": err.Error(), 47 | }) 48 | return 49 | } 50 | currentSite.AddAdminLog(ctx, fmt.Sprintf("更新API导入Token")) 51 | 52 | ctx.JSON(iris.Map{ 53 | "code": config.StatusOK, 54 | "msg": "Token已更新", 55 | }) 56 | } 57 | -------------------------------------------------------------------------------- /middleware/recover.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "fmt" 5 | "github.com/kataras/iris/v12/context" 6 | "kandaoni.com/anqicms/config" 7 | "kandaoni.com/anqicms/library" 8 | "runtime" 9 | "strconv" 10 | ) 11 | 12 | func NewRecover() context.Handler { 13 | return func(ctx *context.Context) { 14 | defer func() { 15 | if err := recover(); err != nil { 16 | if ctx.IsStopped() { 17 | return 18 | } 19 | 20 | var stacktrace string 21 | for i := 1; ; i++ { 22 | _, f, l, got := runtime.Caller(i) 23 | if !got { 24 | break 25 | } 26 | 27 | stacktrace += fmt.Sprintf("%s:%d\n", f, l) 28 | } 29 | 30 | // when stack finishes 31 | logMessage := fmt.Sprintf("Recovered from a route's Handler('%s')\n", ctx.HandlerName()) 32 | logMessage += fmt.Sprintf("At Request: %s\n", getRequestLogs(ctx)) 33 | logMessage += fmt.Sprintf("Trace: %s\n", err) 34 | logMessage += fmt.Sprintf("\n%s", stacktrace) 35 | library.DebugLog(config.ExecPath+"cache/", "error.log", logMessage) 36 | ctx.Application().Logger().Warn(logMessage) 37 | ctx.Values().Set("message", err) 38 | ctx.StatusCode(500) 39 | ctx.StopExecution() 40 | } 41 | }() 42 | 43 | ctx.Next() 44 | } 45 | } 46 | 47 | func getRequestLogs(ctx *context.Context) string { 48 | var status, ip, method, path string 49 | status = strconv.Itoa(ctx.GetStatusCode()) 50 | path = ctx.Path() 51 | method = ctx.Method() 52 | ip = ctx.RemoteAddr() 53 | // the date should be logged by iris' Logger, so we skip them 54 | return fmt.Sprintf("%v %s %s %s", status, path, method, ip) 55 | } 56 | -------------------------------------------------------------------------------- /library/image.go: -------------------------------------------------------------------------------- 1 | package library 2 | 3 | import ( 4 | "encoding/hex" 5 | "github.com/disintegration/imaging" 6 | "image" 7 | "image/color" 8 | "image/draw" 9 | "strings" 10 | ) 11 | 12 | func ThumbnailCrop(minWidth, minHeight int, img image.Image, thumbCrop int) image.Image { 13 | if minWidth == 0 { 14 | //默认值 15 | minWidth = 250 16 | } 17 | if minHeight == 0 { 18 | //默认值 19 | minHeight = 250 20 | } 21 | 22 | var thumbImg image.Image 23 | if thumbCrop == 0 { 24 | //等比缩放 25 | thumbImg = imaging.Fit(img, minWidth, minHeight, imaging.Lanczos) 26 | } else if thumbCrop == 1 { 27 | //补白 28 | thumbImg = imaging.Fit(img, minWidth, minHeight, imaging.Lanczos) 29 | thumbImg = ResizeFill(thumbImg, minWidth, minHeight) 30 | } else { 31 | //裁剪 32 | thumbImg = imaging.Thumbnail(img, minWidth, minHeight, imaging.Lanczos) 33 | } 34 | 35 | return thumbImg 36 | } 37 | 38 | func Resize(img image.Image, dstWidth, dstHeight int) image.Image { 39 | return imaging.Resize(img, dstWidth, dstHeight, imaging.Lanczos) 40 | } 41 | 42 | func ResizeFill(img image.Image, width, height int) image.Image { 43 | rgba := image.NewRGBA(image.Rect(0, 0, width, height)) 44 | draw.Draw(rgba, rgba.Bounds(), image.White, image.Point{}, draw.Src) 45 | return imaging.PasteCenter(rgba, img) 46 | } 47 | 48 | func HEXToRGB(h string) color.Color { 49 | h = strings.Trim(h, "#") 50 | if h == "" { 51 | return color.RGBA{} 52 | } 53 | if len(h) == 3 { 54 | h = string(h[0] + h[0] + h[1] + h[1] + h[2] + h[2]) 55 | } 56 | bs, err := hex.DecodeString(h) 57 | if err != nil { 58 | return color.RGBA{} 59 | } 60 | if len(bs) != 3 { 61 | return color.RGBA{} 62 | } 63 | return color.RGBA{R: bs[0], G: bs[1], B: bs[2], A: 255} 64 | } 65 | -------------------------------------------------------------------------------- /public/static/default/layui/css/modules/code.css: -------------------------------------------------------------------------------- 1 | html #layuicss-skincodecss{display:none;position:absolute;width:1989px}.layui-code-view{display:block;position:relative;margin:10px 0;padding:0;border:1px solid #eee;border-left-width:6px;background-color:#fafafa;color:#333;font-family:Courier New;font-size:13px}.layui-code-title{position:relative;padding:0 10px;height:40px;line-height:40px;border-bottom:1px solid #eee;font-size:12px}.layui-code-title>.layui-code-about{position:absolute;right:10px;top:0;color:#b7b7b7}.layui-code-about>a{padding-left:10px}.layui-code-view>.layui-code-ol,.layui-code-view>.layui-code-ul{position:relative;overflow:auto}.layui-code-view>.layui-code-ol>li{position:relative;margin-left:45px;line-height:20px;padding:0 10px;border-left:1px solid #e2e2e2;list-style-type:decimal-leading-zero;*list-style-type:decimal;background-color:#fff}.layui-code-view>.layui-code-ol>li:first-child,.layui-code-view>.layui-code-ul>li:first-child{padding-top:10px}.layui-code-view>.layui-code-ol>li:last-child,.layui-code-view>.layui-code-ul>li:last-child{padding-bottom:10px}.layui-code-view>.layui-code-ul>li{position:relative;line-height:20px;padding:0 10px;list-style-type:none;*list-style-type:none;background-color:#fff}.layui-code-view pre{margin:0}.layui-code-dark{border:1px solid #0c0c0c;border-left-color:#3f3f3f;background-color:#0c0c0c;color:#c2be9e}.layui-code-dark>.layui-code-title{border-bottom:none}.layui-code-dark>.layui-code-ol>li,.layui-code-dark>.layui-code-ul>li{background-color:#3f3f3f;border-left:none}.layui-code-dark>.layui-code-ul>li{margin-left:6px}.layui-code-demo .layui-code{visibility:visible!important;margin:-15px;border-top:none;border-right:none;border-bottom:none}.layui-code-demo .layui-tab-content{padding:15px;border-top:none} -------------------------------------------------------------------------------- /model/link.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "gorm.io/gorm" 5 | ) 6 | 7 | const LinkStatusWait = uint(0) 8 | const LinkStatusOk = uint(1) 9 | const LinkStatusNofollow = uint(2) 10 | const LinkStatusNotTitle = uint(3) 11 | const LinkStatusNotMatch = uint(4) 12 | 13 | type Link struct { 14 | Model 15 | Title string `json:"title" gorm:"column:title;type:varchar(250) not null;default:''"` 16 | Link string `json:"link" gorm:"column:link;type:varchar(250) not null;default:''"` 17 | BackLink string `json:"back_link" gorm:"column:back_link;type:varchar(250) not null;default:''"` 18 | MyTitle string `json:"my_title" gorm:"column:my_title;type:varchar(250) not null;default:''"` 19 | MyLink string `json:"my_link" gorm:"column:my_link;type:varchar(250) not null;default:''"` 20 | Contact string `json:"contact" gorm:"column:contact;type:varchar(250) not null;default:''"` 21 | Remark string `json:"remark" gorm:"column:remark;type:varchar(250) not null;default:''"` 22 | Nofollow uint `json:"nofollow" gorm:"column:nofollow;type:tinyint(1) unsigned not null;default:0"` 23 | Sort uint `json:"sort" gorm:"column:sort;type:int(10) unsigned not null;default:99;index:idx_sort"` 24 | Status uint `json:"status" gorm:"column:status;type:tinyint(1) unsigned not null;default:0;index:idx_status"` 25 | CheckedTime int64 `json:"checked_time" gorm:"column:checked_time;type:int(11) not null;default:0"` 26 | } 27 | 28 | func (link *Link) Save(db *gorm.DB) error { 29 | if err := db.Save(link).Error; err != nil { 30 | return err 31 | } 32 | 33 | return nil 34 | } 35 | 36 | func (link *Link) Delete(db *gorm.DB) error { 37 | if err := db.Delete(link).Error; err != nil { 38 | return err 39 | } 40 | return nil 41 | } 42 | -------------------------------------------------------------------------------- /view/fs.go: -------------------------------------------------------------------------------- 1 | package view 2 | 3 | import ( 4 | "fmt" 5 | "io/fs" 6 | "path/filepath" 7 | 8 | "github.com/kataras/iris/v12/context" 9 | ) 10 | 11 | // walk recursively in "fileSystem" descends "root" path, calling "walkFn". 12 | func walk(fileSystem fs.FS, root string, walkFn filepath.WalkFunc) error { 13 | if root != "" && root != "/" && root != "." { 14 | sub, err := fs.Sub(fileSystem, root) 15 | if err != nil { 16 | return err 17 | } 18 | fileSystem = sub 19 | } 20 | 21 | if root == "" { 22 | root = "." 23 | } 24 | 25 | return fs.WalkDir(fileSystem, root, func(path string, d fs.DirEntry, err error) error { 26 | if err != nil { 27 | return fmt.Errorf("walk: %s: %w", path, err) 28 | } 29 | 30 | info, err := d.Info() 31 | if err != nil { 32 | if err != filepath.SkipDir { 33 | return fmt.Errorf("walk stat: %s: %w", path, err) 34 | } 35 | 36 | return nil 37 | } 38 | 39 | if info.IsDir() { 40 | return nil 41 | } 42 | 43 | walkFnErr := walkFn(path, info, err) 44 | if walkFnErr != nil { 45 | return fmt.Errorf("walk: walkFn: %w", walkFnErr) 46 | } 47 | 48 | return nil 49 | }) 50 | 51 | } 52 | 53 | func asset(fileSystem fs.FS, name string) ([]byte, error) { 54 | data, err := fs.ReadFile(fileSystem, name) 55 | if err != nil { 56 | return nil, fmt.Errorf("asset: read file: %w", err) 57 | } 58 | 59 | return data, nil 60 | } 61 | 62 | func getFS(fsOrDir interface{}) fs.FS { 63 | return context.ResolveFS(fsOrDir) 64 | } 65 | 66 | func getRootDirName(fileSystem fs.FS) string { 67 | rootDirFile, err := fileSystem.Open(".") 68 | if err == nil { 69 | rootDirStat, err := rootDirFile.Stat() 70 | if err == nil { 71 | return rootDirStat.Name() 72 | } 73 | } 74 | 75 | return "" 76 | } 77 | -------------------------------------------------------------------------------- /template/default/case/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block container %} 4 | {% include "partial/breadcrumb.html" %} 5 |
6 |
客户案例
7 |
8 |
9 | {% categoryList productCategories with parentId="0" %} 10 | {% for item in productCategories %} 11 | {{item.Title}} 12 | {% endfor %} 13 | {% endcategoryList %} 14 |
15 |
16 | 17 | {% categoryList productCategories with parentId="0" %} 18 | {% for item in productCategories %} 19 |
20 |
21 |

{{item.Title}}

22 | 查看更多 23 |
24 |
25 |
26 |
27 | 28 |
29 |
30 |
31 |
32 |
    33 | {% archiveList products with categoryId=item.Id type="list" limit="4" %} 34 | {% for inner in products %} 35 |
  • 36 |
    37 |
    38 | {{inner.Title}} 39 |
    40 |
    41 |

    {{inner.Title}}

    42 |
    43 |
    44 |
  • 45 | {% endfor %} 46 | {% endarchiveList %} 47 |
48 |
49 |
50 | {% endfor %} 51 | {% endcategoryList %} 52 | 53 |
54 | {% endblock %} -------------------------------------------------------------------------------- /controller/manageController/pluginPush.go: -------------------------------------------------------------------------------- 1 | package manageController 2 | 3 | import ( 4 | "fmt" 5 | "github.com/kataras/iris/v12" 6 | "kandaoni.com/anqicms/config" 7 | "kandaoni.com/anqicms/provider" 8 | ) 9 | 10 | func PluginPush(ctx iris.Context) { 11 | currentSite := provider.CurrentSite(ctx) 12 | pluginPush := currentSite.PluginPush 13 | 14 | ctx.JSON(iris.Map{ 15 | "code": config.StatusOK, 16 | "msg": "", 17 | "data": pluginPush, 18 | }) 19 | } 20 | 21 | func PluginPushLogList(ctx iris.Context) { 22 | currentSite := provider.CurrentSite(ctx) 23 | //不需要分页,只显示最后20条 24 | list, err := currentSite.GetLastPushList() 25 | if err != nil { 26 | ctx.JSON(iris.Map{ 27 | "code": config.StatusFailed, 28 | "msg": "", 29 | }) 30 | return 31 | } 32 | 33 | ctx.JSON(iris.Map{ 34 | "code": config.StatusOK, 35 | "msg": "", 36 | "data": list, 37 | }) 38 | } 39 | 40 | func PluginPushForm(ctx iris.Context) { 41 | currentSite := provider.CurrentSite(ctx) 42 | var req config.PluginPushConfig 43 | if err := ctx.ReadJSON(&req); err != nil { 44 | ctx.JSON(iris.Map{ 45 | "code": config.StatusFailed, 46 | "msg": err.Error(), 47 | }) 48 | return 49 | } 50 | 51 | currentSite.PluginPush.BaiduApi = req.BaiduApi 52 | currentSite.PluginPush.BingApi = req.BingApi 53 | currentSite.PluginPush.JsCodes = req.JsCodes 54 | 55 | err := currentSite.SaveSettingValue(provider.PushSettingKey, currentSite.PluginPush) 56 | if err != nil { 57 | ctx.JSON(iris.Map{ 58 | "code": config.StatusFailed, 59 | "msg": err.Error(), 60 | }) 61 | return 62 | } 63 | currentSite.DeleteCacheIndex() 64 | 65 | currentSite.AddAdminLog(ctx, fmt.Sprintf("更新搜索引擎推送配置")) 66 | 67 | ctx.JSON(iris.Map{ 68 | "code": config.StatusOK, 69 | "msg": "配置已更新", 70 | }) 71 | } 72 | -------------------------------------------------------------------------------- /provider/collector_test.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "github.com/PuerkitoBio/goquery" 5 | "kandaoni.com/anqicms/model" 6 | "kandaoni.com/anqicms/response" 7 | "log" 8 | "strings" 9 | "testing" 10 | ) 11 | 12 | func (w *Website) TestCollectSingleArticle(t *testing.T) { 13 | link := &response.WebLink{Url: "http://blog.niunan.net/blog/show/1295"} 14 | keyword := &model.Keyword{Title: "PHP 报错"} 15 | result, err := w.CollectSingleArticle(link, keyword) 16 | 17 | if err != nil { 18 | t.Fatal(err) 19 | } 20 | 21 | log.Printf("%#v", result.Content) 22 | } 23 | 24 | func TestCollectArticlesByKeyword(t *testing.T) { 25 | keyword := model.Keyword{ 26 | Title: "golang面试题", 27 | Status: 1, 28 | } 29 | GetDefaultDB() 30 | dbSite, _ := GetDBWebsiteInfo(1) 31 | InitWebsite(dbSite) 32 | w := CurrentSite(nil) 33 | num, err := w.CollectArticlesByKeyword(keyword, true) 34 | if err != nil { 35 | t.Fatal(err) 36 | } 37 | 38 | log.Println(num) 39 | } 40 | 41 | func (w *Website) TestGoquery(t *testing.T) { 42 | str := "

来张图中吧:

\n

\"图0:2017年的golang、python、php、c++、c、java、Nodejs性能对比\"/

\n

总结:

" 43 | 44 | htmlR := strings.NewReader(str) 45 | doc, err := goquery.NewDocumentFromReader(htmlR) 46 | 47 | if err != nil { 48 | t.Fatal(err) 49 | } 50 | 51 | doc.Find("img").Each(func(i int, item *goquery.Selection) { 52 | src, _ := item.Attr("src") 53 | dataSrc, exists2 := item.Attr("data-src") 54 | if exists2 { 55 | src = dataSrc 56 | } 57 | dataSrc, exists2 = item.Attr("data-original") 58 | if exists2 { 59 | src = dataSrc 60 | } 61 | log.Println(src, dataSrc) 62 | log.Println(item.Parent().Html()) 63 | }) 64 | } 65 | -------------------------------------------------------------------------------- /template/default/product/list.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block container %} 4 | {% include "partial/breadcrumb.html" %} 5 |
6 |
{% categoryDetail with name="Title" %}
7 | {% categoryList productCategories with %} 8 | {% if productCategories %} 9 |
10 |
11 | {% for item in productCategories %} 12 | {{item.Title}} 13 | {% endfor %} 14 |
15 |
16 | {% endif %} 17 | {% endcategoryList %} 18 |
19 | 20 |
21 |
22 |
23 | 24 |
25 |
26 |

{% categoryDetail with name="Title" %}

27 |

{% categoryDetail with name="Description" %}

28 |
29 |
30 |
31 | 32 |
33 | 34 |
35 |
    36 | {% archiveList products with type="page" limit="9" %} 37 | {% for item in products %} 38 |
  • 39 |
    40 |
    41 | {{item.Title}} 42 |
    43 |
    44 |

    {{item.Title}}

    45 |
    46 |
    47 |
  • 48 | {% endfor %} 49 | {% endarchiveList %} 50 |
51 | {% include "partial/pagination.html" %} 52 |
53 | 54 |
55 | {% endblock %} -------------------------------------------------------------------------------- /model/nav.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "gorm.io/gorm" 5 | ) 6 | 7 | const ( 8 | NavTypeSystem = 0 9 | NavTypeCategory = 1 10 | NavTypeOutlink = 2 11 | ) 12 | 13 | type Nav struct { 14 | Model 15 | Title string `json:"title" gorm:"column:title;type:varchar(250) not null;default:''"` 16 | SubTitle string `json:"sub_title" gorm:"column:sub_title;type:varchar(250) not null;default:''"` 17 | Description string `json:"description" gorm:"column:description;type:varchar(1000) not null;default:''"` 18 | ParentId uint `json:"parent_id" gorm:"column:parent_id;type:int(10) unsigned not null;default:0;index:idx_parent_id"` 19 | NavType uint `json:"nav_type" gorm:"column:nav_type;type:int(10) unsigned not null;default:0;index:idx_nav_type"` 20 | PageId uint `json:"page_id" gorm:"column:page_id;type:int(10) unsigned not null;default:0;index:idx_page_id"` 21 | TypeId uint `json:"type_id" gorm:"column:type_id;type:int(10) unsigned not null;default:1;index:idx_type_id"` 22 | Link string `json:"link" gorm:"column:link;type:varchar(250) not null;default:''"` 23 | Sort uint `json:"sort" gorm:"column:sort;type:int(10) unsigned not null;default:99;index:idx_sort"` 24 | Status uint `json:"status" gorm:"column:status;type:tinyint(1) unsigned not null;default:0"` 25 | NavList []*Nav `json:"nav_list" gorm:"-"` 26 | IsCurrent bool `json:"is_current" gorm:"-"` 27 | } 28 | 29 | type NavType struct { 30 | Model 31 | Title string `json:"title" gorm:"column:title;type:varchar(250) not null;default:''"` 32 | } 33 | 34 | func (nav *Nav) Save(db *gorm.DB) error { 35 | if err := db.Save(nav).Error; err != nil { 36 | return err 37 | } 38 | 39 | return nil 40 | } 41 | 42 | func (nav *Nav) Delete(db *gorm.DB) error { 43 | if err := db.Delete(nav).Error; err != nil { 44 | return err 45 | } 46 | return nil 47 | } 48 | -------------------------------------------------------------------------------- /template/default/product/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block container %} 4 | {% include "partial/breadcrumb.html" %} 5 |
6 |
产品中心
7 |
8 |
9 | {% categoryList productCategories with parentId="0" %} 10 | {% for item in productCategories %} 11 | {{item.Title}} 12 | {% endfor %} 13 | {% endcategoryList %} 14 |
15 |
16 | 17 | {% categoryList productCategories with parentId="0" %} 18 | {% for item in productCategories %} 19 |
20 |
21 |

{{item.Title}}

22 | 查看更多 23 |
24 |
25 |
26 |
27 | 28 |
29 |
30 |
31 |
32 |
    33 | {% archiveList products with categoryId=item.Id type="list" limit="4" %} 34 | {% for inner in products %} 35 |
  • 36 |
    37 |
    38 | {{inner.Title}} 39 |
    40 |
    41 |

    {{inner.Title}}

    42 |
    43 |
    44 |
  • 45 | {% endfor %} 46 | {% endarchiveList %} 47 |
48 |
49 |
50 | {% endfor %} 51 | {% endcategoryList %} 52 | 53 |
54 | {% endblock %} -------------------------------------------------------------------------------- /controller/index.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "fmt" 5 | "github.com/kataras/iris/v12" 6 | "kandaoni.com/anqicms/provider" 7 | "kandaoni.com/anqicms/response" 8 | ) 9 | 10 | func IndexPage(ctx iris.Context) { 11 | currentSite := provider.CurrentSite(ctx) 12 | userId := ctx.Values().GetUintDefault("userId", 0) 13 | var ua string 14 | if ctx.IsMobile() { 15 | ua = provider.UserAgentMobile 16 | } else { 17 | ua = provider.UserAgentPc 18 | } 19 | currentPage := ctx.Values().GetIntDefault("page", 1) 20 | // 只缓存首页 21 | if currentPage == 1 && ctx.GetHeader("Cache-Control") != "no-cache" && userId == 0 { 22 | body := currentSite.GetIndexCache(ua) 23 | if body != nil { 24 | //log.Println("Load index from cache.") 25 | ctx.ResponseWriter().FlushResponse() 26 | ctx.Write(body) 27 | return 28 | } 29 | } 30 | webTitle := currentSite.Index.SeoTitle 31 | if currentPage > 1 { 32 | webTitle += " - " + fmt.Sprintf(currentSite.Lang("第%d页"), currentPage) 33 | } 34 | 35 | if webInfo, ok := ctx.Value("webInfo").(*response.WebInfo); ok { 36 | webInfo.Title = webTitle 37 | webInfo.Keywords = currentSite.Index.SeoKeywords 38 | webInfo.Description = currentSite.Index.SeoDescription 39 | //设置页面名称,方便tags识别 40 | webInfo.PageName = "index" 41 | webInfo.CanonicalUrl = currentSite.GetUrl("", nil, 0) 42 | ctx.ViewData("webInfo", webInfo) 43 | } 44 | 45 | // 支持2种文件结构,一种是目录式的,一种是扁平式的 46 | tplName := "index/index.html" 47 | if ViewExists(ctx, "index.html") { 48 | tplName = "index.html" 49 | } 50 | recorder := ctx.Recorder() 51 | err := ctx.View(GetViewPath(ctx, tplName)) 52 | if err != nil { 53 | ctx.Values().Set("message", err.Error()) 54 | } else if currentPage == 1 && userId == 0 { 55 | body := recorder.Body() 56 | body = currentSite.ReplaceSensitiveWords(body) 57 | currentSite.CacheIndex(ua, body) 58 | recorder.ResetBody() 59 | ctx.Write(body) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /controller/account.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "github.com/kataras/iris/v12" 5 | "kandaoni.com/anqicms/config" 6 | "kandaoni.com/anqicms/provider" 7 | "kandaoni.com/anqicms/response" 8 | "strings" 9 | ) 10 | 11 | func LoginPage(ctx iris.Context) { 12 | currentSite := provider.CurrentSite(ctx) 13 | userId := ctx.Values().GetUintDefault("userId", 0) 14 | if userId > 0 { 15 | ctx.Redirect("/") 16 | } 17 | if webInfo, ok := ctx.Value("webInfo").(*response.WebInfo); ok { 18 | webInfo.Title = currentSite.Lang("登录") 19 | ctx.ViewData("webInfo", webInfo) 20 | } 21 | err := ctx.View(GetViewPath(ctx, "login.html")) 22 | if err != nil { 23 | ctx.Values().Set("message", err.Error()) 24 | } 25 | } 26 | 27 | func RegisterPage(ctx iris.Context) { 28 | currentSite := provider.CurrentSite(ctx) 29 | if webInfo, ok := ctx.Value("webInfo").(*response.WebInfo); ok { 30 | webInfo.Title = currentSite.Lang("注册") 31 | ctx.ViewData("webInfo", webInfo) 32 | } 33 | err := ctx.View(GetViewPath(ctx, "register.html")) 34 | if err != nil { 35 | ctx.Values().Set("message", err.Error()) 36 | } 37 | } 38 | 39 | func AccountLogout(ctx iris.Context) { 40 | currentSite := provider.CurrentSite(ctx) 41 | returnType := ctx.URLParamDefault("return", "html") 42 | ctx.RemoveCookie("token") 43 | if returnType == "json" { 44 | ctx.JSON(iris.Map{ 45 | "code": config.StatusNoLogin, 46 | "msg": currentSite.Lang("已退出登录"), 47 | }) 48 | return 49 | } 50 | 51 | ShowMessage(ctx, currentSite.Lang("已退出登录"), []Button{{Name: currentSite.Lang("首页"), Link: "/"}}) 52 | } 53 | 54 | func AccountIndexPage(ctx iris.Context) { 55 | route := ctx.Params().Get("route") 56 | if route == "" { 57 | route = "index" 58 | } 59 | if !strings.HasSuffix(route, ".html") { 60 | route += ".html" 61 | } 62 | 63 | err := ctx.View(GetViewPath(ctx, "account/"+route)) 64 | if err != nil { 65 | ctx.StatusCode(404) 66 | ctx.Values().Set("message", err.Error()) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /model/comment.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "gorm.io/gorm" 5 | ) 6 | 7 | type Comment struct { 8 | Model 9 | ArchiveId uint `json:"archive_id" gorm:"column:archive_id;type:int(10) unsigned not null;default:0;index:idx_archive_id"` 10 | UserId uint `json:"user_id" gorm:"column:user_id;type:int(10) unsigned not null;default:0;index:idx_user_id"` 11 | UserName string `json:"user_name" gorm:"column:user_name;type:varchar(32) not null;default:''"` 12 | Ip string `json:"ip" gorm:"column:ip;type:varchar(32) not null;default:''"` 13 | VoteCount int `json:"vote_count" gorm:"column:vote_count;type:int(10) not null;default:0;index:idx_vote_count"` 14 | Content string `json:"content" gorm:"column:content;type:longtext default null"` 15 | ParentId uint `json:"parent_id" gorm:"column:parent_id;type:int(10) unsigned not null;default:0;index:idx_parent_id"` 16 | ToUid uint `json:"to_uid" gorm:"column:to_uid;type:int(10) unsigned not null;default:0;index:idx_to_uid"` 17 | Status uint `json:"status" gorm:"column:status;type:tinyint(1) unsigned not null;default:0"` 18 | ItemTitle string `json:"item_title" gorm:"-"` 19 | Parent *Comment `json:"parent" gorm:"-"` 20 | Active bool `json:"active" gorm:"-"` 21 | } 22 | 23 | func (comment *Comment) Save(db *gorm.DB) error { 24 | if err := db.Save(comment).Error; err != nil { 25 | return err 26 | } 27 | comment.UpdateCommentCount(db) 28 | 29 | return nil 30 | } 31 | 32 | func (comment *Comment) Delete(db *gorm.DB) error { 33 | if err := db.Delete(comment).Error; err != nil { 34 | return err 35 | } 36 | comment.UpdateCommentCount(db) 37 | 38 | return nil 39 | } 40 | 41 | func (comment *Comment) UpdateCommentCount(db *gorm.DB) { 42 | // 更新数量 43 | var total int64 44 | db.Model(&Comment{}).Where("`archive_id` = ?", comment.ArchiveId).Count(&total) 45 | db.Model(&Archive{}).Where("`id` = ?", comment.ArchiveId).UpdateColumn("comment_count", total) 46 | } 47 | -------------------------------------------------------------------------------- /provider/categoryTree.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "kandaoni.com/anqicms/model" 5 | ) 6 | 7 | type CategoryTree struct { 8 | categories []*model.Category 9 | tree []*model.Category 10 | treeKey map[uint]bool 11 | deep int 12 | icons []string 13 | tmp map[uint][]*model.Category 14 | } 15 | 16 | func NewCategoryTree(categories []*model.Category) *CategoryTree { 17 | ct := &CategoryTree{ 18 | categories: categories, 19 | tree: []*model.Category{}, 20 | treeKey: map[uint]bool{}, 21 | deep: 1, 22 | icons: []string{"└  ", "", "", ""}, 23 | tmp: map[uint][]*model.Category{}, 24 | } 25 | 26 | return ct 27 | } 28 | 29 | func (ct *CategoryTree) GetTree(rootId uint, add string) []*model.Category { 30 | isTop := 1 31 | children := ct.getChildren(rootId) 32 | space := ct.icons[3] 33 | if children != nil { 34 | cnt := len(children) 35 | for _, child := range children { 36 | if ct.deep > 1 { 37 | if isTop == 1 { 38 | space = ct.icons[1] 39 | add += ct.icons[0] 40 | } 41 | 42 | if isTop == cnt { 43 | space = ct.icons[2] 44 | } else { 45 | space = ct.icons[1] 46 | } 47 | } 48 | 49 | child.Spacer = add + space 50 | 51 | isTop++ 52 | ct.deep++ 53 | if !ct.treeKey[child.Id] { 54 | ct.treeKey[child.Id] = true 55 | ct.tree = append(ct.tree, child) 56 | } 57 | if ct.getChildren(child.Id) != nil { 58 | child.HasChildren = true 59 | ct.GetTree(child.Id, add) 60 | ct.deep-- 61 | } 62 | } 63 | } 64 | 65 | var categories []*model.Category 66 | for _, v := range ct.tree { 67 | categories = append(categories, v) 68 | } 69 | return categories 70 | } 71 | 72 | func (ct *CategoryTree) getChildren(rootId uint) []*model.Category { 73 | if len(ct.tmp) == 0 { 74 | for _, v := range ct.categories { 75 | ct.tmp[v.ParentId] = append(ct.tmp[v.ParentId], v) 76 | } 77 | } 78 | 79 | if ct.tmp[rootId] != nil { 80 | return ct.tmp[rootId] 81 | } 82 | 83 | return nil 84 | } 85 | -------------------------------------------------------------------------------- /model/material.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "gorm.io/gorm" 4 | 5 | type MaterialCategory struct { 6 | Model 7 | Title string `json:"title" gorm:"column:title;type:varchar(250) not null;default:''"` 8 | MaterialCount uint `json:"material_count" gorm:"column:material_count;type:int(10) unsigned not null;default:0"` 9 | Status uint `json:"status" gorm:"column:status;type:tinyint(1) unsigned not null;default:0"` 10 | } 11 | 12 | type Material struct { 13 | Model 14 | Title string `json:"title" gorm:"column:title;type:varchar(250) not null;default:''"` 15 | CategoryId uint `json:"category_id" gorm:"column:category_id;type:int(10) unsigned not null;default:0;index:idx_category_id"` 16 | Content string `json:"content" gorm:"column:content;type:longtext default null"` 17 | OriginUrl string `json:"origin_url" gorm:"column:origin_url;type:varchar(190) not null;default:'';index"` 18 | Keyword string `json:"keyword" gorm:"column:keyword;type:varchar(250) not null;default:''"` 19 | Status uint `json:"status" gorm:"column:status;type:tinyint(1) unsigned not null;default:0"` 20 | AutoUpdate uint `json:"auto_update" gorm:"column:auto_update;type:tinyint(1) unsigned not null;default:0"` 21 | UseCount uint `json:"use_count" gorm:"column:use_count;type:int(10) unsigned not null;default:0"` 22 | Md5 string `json:"md5" gorm:"column:md5;type:varchar(32) default null;index:idx_md5"` 23 | CategoryTitle string `json:"category_title" gorm:"-"` 24 | } 25 | 26 | type MaterialData struct { 27 | Model 28 | MaterialId uint `json:"material_id" gorm:"column:material_id;type:int(10) not null;default:0;index"` 29 | ItemType string `json:"item_type" gorm:"column:item_type;type:varchar(32) not null;default:'';index:idx_item_type"` 30 | ItemId uint `json:"item_id" gorm:"column:item_id;type:int(10) unsigned not null;default:0;index:idx_item_type"` 31 | } 32 | 33 | func (category *MaterialCategory) Delete(db *gorm.DB) error { 34 | if err := db.Delete(category).Error; err != nil { 35 | return err 36 | } 37 | 38 | return nil 39 | } 40 | -------------------------------------------------------------------------------- /library/webp.go: -------------------------------------------------------------------------------- 1 | package library 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "github.com/chai2010/webp" 8 | "golang.org/x/image/bmp" 9 | "image" 10 | "image/jpeg" 11 | "image/png" 12 | "log" 13 | "os" 14 | "path" 15 | "strings" 16 | ) 17 | 18 | const webpMax = 16383 19 | 20 | func ConvertImage(raw, optimized string) error { 21 | //we need to create dir first 22 | err := os.MkdirAll(path.Dir(optimized), 0755) 23 | if err != nil { 24 | return err 25 | } 26 | 27 | err = webpEncoder(raw, optimized, 80) 28 | 29 | return err 30 | } 31 | 32 | func readRawImage(imgPath string, maxPixel int) (img image.Image, err error) { 33 | data, err := os.ReadFile(imgPath) 34 | if err != nil { 35 | return 36 | } 37 | 38 | imgExtension := strings.ToLower(path.Ext(imgPath)) 39 | if strings.Contains(imgExtension, "jpeg") || strings.Contains(imgExtension, "jpg") { 40 | img, err = jpeg.Decode(bytes.NewReader(data)) 41 | } else if strings.Contains(imgExtension, "png") { 42 | img, err = png.Decode(bytes.NewReader(data)) 43 | } else if strings.Contains(imgExtension, "bmp") { 44 | img, err = bmp.Decode(bytes.NewReader(data)) 45 | } 46 | if err != nil || img == nil { 47 | errInfo := fmt.Sprintf("image file %s is corrupted: %v", imgPath, err) 48 | return nil, errors.New(errInfo) 49 | } 50 | 51 | x, y := img.Bounds().Max.X, img.Bounds().Max.Y 52 | if x > maxPixel || y > maxPixel { 53 | errInfo := fmt.Sprintf("WebP: %s(%dx%d) is too large", imgPath, x, y) 54 | return nil, errors.New(errInfo) 55 | } 56 | 57 | return img, nil 58 | } 59 | 60 | func webpEncoder(p1, p2 string, quality float32) error { 61 | // if convert fails, return error; success nil 62 | var buf bytes.Buffer 63 | var img image.Image 64 | // The maximum pixel dimensions of a WebP image is 16383 x 16383. 65 | img, err := readRawImage(p1, webpMax) 66 | if err != nil { 67 | return err 68 | } 69 | 70 | err = webp.Encode(&buf, img, &webp.Options{Lossless: false, Quality: quality}) 71 | if err != nil { 72 | log.Printf("Can't encode source image: %v to WebP", err) 73 | return err 74 | } 75 | 76 | if err = os.WriteFile(p2, buf.Bytes(), 0644); err != nil { 77 | return err 78 | } 79 | 80 | return nil 81 | } 82 | -------------------------------------------------------------------------------- /request/archive.go: -------------------------------------------------------------------------------- 1 | package request 2 | 3 | import "kandaoni.com/anqicms/config" 4 | 5 | type Archive struct { 6 | Id uint `json:"id"` 7 | Title string `json:"title"` 8 | SeoTitle string `json:"seo_title"` 9 | ModuleId uint `json:"module_id"` 10 | CategoryId uint `json:"category_id"` 11 | Keywords string `json:"keywords"` 12 | Description string `json:"description"` 13 | Content string `json:"content"` 14 | Template string `json:"template"` 15 | Images []string `json:"images"` 16 | Extra map[string]interface{} `json:"extra"` 17 | CreatedTime int64 `json:"created_time"` 18 | UrlToken string `json:"url_token"` 19 | Tags []string `json:"tags"` 20 | CanonicalUrl string `json:"canonical_url"` 21 | FixedLink string `json:"fixed_link"` 22 | Flag string `json:"flag"` 23 | UserId uint `json:"user_id"` 24 | Price int64 `json:"price"` 25 | Stock int64 `json:"stock"` 26 | ReadLevel int `json:"read_level"` // 阅读关联 group level 27 | Draft bool `json:"draft"` // 是否是存草稿 28 | 29 | // 是否强制保存 30 | ForceSave bool `json:"force_save"` 31 | 32 | KeywordId uint `json:"keyword_id"` 33 | OriginUrl string `json:"origin_url"` 34 | OriginTitle string `json:"origin_title"` 35 | ContentText string `json:"-" gorm:"-"` 36 | } 37 | 38 | type ArchiveImageDeleteRequest struct { 39 | Id uint `json:"id"` 40 | ImageIndex int `json:"image_index"` 41 | } 42 | 43 | type ArchiveReplaceRequest struct { 44 | Replace bool `json:"replace"` 45 | ContentReplace []config.ReplaceKeyword `json:"content_replace"` 46 | } 47 | 48 | type ArchivesUpdateRequest struct { 49 | Ids []uint `json:"ids"` 50 | 51 | CategoryId uint `json:"category_id"` 52 | Status uint `json:"status"` 53 | Flag string `json:"flag"` 54 | Time uint `json:"time"` 55 | } 56 | -------------------------------------------------------------------------------- /controller/manageController/pluginTransfer.go: -------------------------------------------------------------------------------- 1 | package manageController 2 | 3 | import ( 4 | "github.com/kataras/iris/v12" 5 | "kandaoni.com/anqicms/config" 6 | "kandaoni.com/anqicms/provider" 7 | "kandaoni.com/anqicms/request" 8 | "os" 9 | "time" 10 | ) 11 | 12 | func GetTransferTask(ctx iris.Context) { 13 | currentSite := provider.CurrentSite(ctx) 14 | task := currentSite.GetTransferTask() 15 | 16 | ctx.JSON(iris.Map{ 17 | "code": config.StatusOK, 18 | "msg": "", 19 | "data": task, 20 | }) 21 | } 22 | 23 | func DownloadClientFile(ctx iris.Context) { 24 | var req request.TransferWebsite 25 | if err := ctx.ReadJSON(&req); err != nil { 26 | ctx.JSON(iris.Map{ 27 | "code": config.StatusFailed, 28 | "msg": err.Error(), 29 | }) 30 | return 31 | } 32 | 33 | // 下载指定的文件 34 | clientFile := config.ExecPath + "clientFiles/" + req.Provider + "2anqicms.php" 35 | if req.Provider == "train" { 36 | clientFile = config.ExecPath + "clientFiles/train2anqicms.wpm" 37 | } 38 | _, err := os.Stat(clientFile) 39 | if err != nil { 40 | ctx.JSON(iris.Map{ 41 | "code": config.StatusFailed, 42 | "msg": err.Error(), 43 | }) 44 | return 45 | } 46 | 47 | ctx.ServeFile(clientFile) 48 | } 49 | 50 | func CreateTransferTask(ctx iris.Context) { 51 | currentSite := provider.CurrentSite(ctx) 52 | var req request.TransferWebsite 53 | if err := ctx.ReadJSON(&req); err != nil { 54 | ctx.JSON(iris.Map{ 55 | "code": config.StatusFailed, 56 | "msg": err.Error(), 57 | }) 58 | return 59 | } 60 | 61 | task, err := currentSite.CreateTransferTask(&req) 62 | if err != nil { 63 | ctx.JSON(iris.Map{ 64 | "code": config.StatusFailed, 65 | "msg": err.Error(), 66 | }) 67 | return 68 | } 69 | 70 | ctx.JSON(iris.Map{ 71 | "code": config.StatusOK, 72 | "msg": "", 73 | "data": task, 74 | }) 75 | } 76 | 77 | func TransferWebData(ctx iris.Context) { 78 | currentSite := provider.CurrentSite(ctx) 79 | task := currentSite.GetTransferTask() 80 | if task == nil { 81 | ctx.JSON(iris.Map{ 82 | "code": config.StatusFailed, 83 | "msg": "没有可执行的任务", 84 | }) 85 | return 86 | } 87 | go task.TransferWebData() 88 | 89 | time.Sleep(1 * time.Second) 90 | ctx.JSON(iris.Map{ 91 | "code": config.StatusOK, 92 | "msg": "任务正在执行中", 93 | }) 94 | } 95 | -------------------------------------------------------------------------------- /library/verifyCode.go: -------------------------------------------------------------------------------- 1 | package library 2 | 3 | import ( 4 | "math/rand" 5 | "strconv" 6 | "sync" 7 | "time" 8 | ) 9 | 10 | type verifyCode struct { 11 | Expire int64 12 | key string 13 | code string 14 | } 15 | 16 | type verifyCodeCache struct { 17 | mu sync.Mutex 18 | list map[string]*verifyCode 19 | size int 20 | } 21 | 22 | var CodeCache verifyCodeCache 23 | 24 | func (v *verifyCodeCache) Generate(key string) string { 25 | expire := time.Now().Unix() + 1800 26 | 27 | code := strconv.Itoa(100000 + rand.Intn(900000)) 28 | if _, ok := v.list[key]; ok { 29 | return v.Generate(key) 30 | } 31 | v.mu.Lock() 32 | node := &verifyCode{ 33 | Expire: expire, 34 | key: key, 35 | code: code, 36 | } 37 | v.list[key] = node 38 | v.size++ 39 | 40 | v.mu.Unlock() 41 | 42 | return code 43 | } 44 | 45 | func (v *verifyCodeCache) Get(key string, clear bool) string { 46 | if _, ok := v.list[key]; !ok { 47 | //数据不存在 48 | return "" 49 | } 50 | if clear { 51 | delete(v.list, key) 52 | } 53 | 54 | return v.list[key].code 55 | } 56 | 57 | func (v *verifyCodeCache) GetByCode(code string, clear bool) (key string) { 58 | for k := range v.list { 59 | if v.list[k].code == code { 60 | key = k 61 | 62 | if clear { 63 | delete(v.list, key) 64 | } 65 | return 66 | } 67 | } 68 | 69 | return 70 | } 71 | 72 | func (v *verifyCodeCache) Verify(key, code string, clear bool) bool { 73 | value := v.Get(key, clear) 74 | 75 | return value == code 76 | } 77 | 78 | func (v *verifyCodeCache) Delete(code string) { 79 | if _, ok := v.list[code]; !ok { 80 | //数据不存在 81 | return 82 | } 83 | v.mu.Lock() 84 | delete(v.list, code) 85 | v.size-- 86 | v.mu.Unlock() 87 | } 88 | 89 | func (v *verifyCodeCache) GC() { 90 | for { 91 | timestamp := time.Now().Unix() 92 | v.mu.Lock() 93 | for k, item := range v.list { 94 | if item.Expire < timestamp { 95 | delete(v.list, k) 96 | v.size-- 97 | } 98 | } 99 | v.mu.Unlock() 100 | time.Sleep(5 * time.Second) 101 | } 102 | } 103 | 104 | func init() { 105 | CodeCache = verifyCodeCache{ 106 | size: 0, 107 | list: map[string]*verifyCode{}, 108 | } 109 | //执行回收 110 | go CodeCache.GC() 111 | } 112 | -------------------------------------------------------------------------------- /middleware/userAuth.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "fmt" 5 | "github.com/golang-jwt/jwt/v4" 6 | "github.com/kataras/iris/v12" 7 | "kandaoni.com/anqicms/config" 8 | "kandaoni.com/anqicms/provider" 9 | "strconv" 10 | "time" 11 | ) 12 | 13 | func ParseUserToken(ctx iris.Context) { 14 | // 允许 API 重新设置siteID 15 | tmpSiteId := ctx.URLParamIntDefault("site_id", 0) 16 | if tmpSiteId > 0 { 17 | tmpSite := provider.GetWebsite(uint(tmpSiteId)) 18 | if tmpSite != nil { 19 | ctx.Values().Set("siteId", tmpSite.Id) 20 | } 21 | } 22 | 23 | currentSite := provider.CurrentSite(ctx) 24 | tokenString := ctx.GetHeader("token") 25 | if tokenString == "" { 26 | // read from cookies 27 | tokenString = ctx.GetCookie("token") 28 | } 29 | 30 | token, tokenErr := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { 31 | if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { 32 | // can not parse the token 33 | return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) 34 | } 35 | return []byte(config.Server.Server.TokenSecret), nil 36 | }) 37 | 38 | if tokenErr == nil { 39 | if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { 40 | userID, ok := claims["userId"].(string) 41 | timeStamp, ok2 := claims["t"].(string) 42 | if ok && ok2 { 43 | sec, _ := strconv.ParseInt(timeStamp, 10, 64) 44 | if sec >= time.Now().Unix() { 45 | // 转换成 int 46 | id, _ := strconv.Atoi(userID) 47 | userInfo, err := currentSite.GetUserInfoById(uint(id)) 48 | if err == nil { 49 | ctx.Values().Set("userId", userID) 50 | ctx.Values().Set("userInfo", userInfo) 51 | 52 | userGroup, _ := currentSite.GetUserGroupInfo(userInfo.GroupId) 53 | ctx.Values().Set("userGroup", userGroup) 54 | // set data to view 55 | ctx.ViewData("userGroup", userGroup) 56 | ctx.ViewData("userInfo", userInfo) 57 | } 58 | } 59 | } 60 | } 61 | } 62 | 63 | ctx.Next() 64 | } 65 | 66 | func UserAuth(ctx iris.Context) { 67 | currentSite := provider.CurrentSite(ctx) 68 | userId := ctx.Values().GetUintDefault("userId", 0) 69 | if userId == 0 { 70 | ctx.JSON(iris.Map{ 71 | "code": config.StatusNoLogin, 72 | "msg": currentSite.Lang("该操作需要登录,请登录后重试"), 73 | }) 74 | return 75 | } 76 | 77 | ctx.Next() 78 | } 79 | -------------------------------------------------------------------------------- /controller/manageController/sensitiveWords.go: -------------------------------------------------------------------------------- 1 | package manageController 2 | 3 | import ( 4 | "fmt" 5 | "github.com/kataras/iris/v12" 6 | "kandaoni.com/anqicms/config" 7 | "kandaoni.com/anqicms/provider" 8 | "kandaoni.com/anqicms/request" 9 | ) 10 | 11 | func SettingSensitiveWords(ctx iris.Context) { 12 | currentSite := provider.CurrentSite(ctx) 13 | sensitiveWords := currentSite.SensitiveWords 14 | 15 | ctx.JSON(iris.Map{ 16 | "code": config.StatusOK, 17 | "msg": "", 18 | "data": sensitiveWords, 19 | }) 20 | } 21 | 22 | func SettingSensitiveWordsForm(ctx iris.Context) { 23 | currentSite := provider.CurrentSite(ctx) 24 | var req []string 25 | if err := ctx.ReadJSON(&req); err != nil { 26 | ctx.JSON(iris.Map{ 27 | "code": config.StatusFailed, 28 | "msg": err.Error(), 29 | }) 30 | return 31 | } 32 | 33 | currentSite.SensitiveWords = req 34 | 35 | err := currentSite.SaveSettingValue(provider.SensitiveWordsKey, currentSite.SensitiveWords) 36 | if err != nil { 37 | ctx.JSON(iris.Map{ 38 | "code": config.StatusFailed, 39 | "msg": err.Error(), 40 | }) 41 | return 42 | } 43 | 44 | currentSite.AddAdminLog(ctx, fmt.Sprintf("更新敏感词配置")) 45 | 46 | ctx.JSON(iris.Map{ 47 | "code": config.StatusOK, 48 | "msg": "配置已更新", 49 | }) 50 | } 51 | 52 | func SettingSensitiveWordsCheck(ctx iris.Context) { 53 | currentSite := provider.CurrentSite(ctx) 54 | var req request.Archive 55 | if err := ctx.ReadJSON(&req); err != nil { 56 | ctx.JSON(iris.Map{ 57 | "code": config.StatusFailed, 58 | "msg": err.Error(), 59 | }) 60 | return 61 | } 62 | 63 | matches := currentSite.MatchSensitiveWords(req.Content) 64 | matches2 := currentSite.MatchSensitiveWords(req.Title) 65 | if len(matches2) > 0 { 66 | matches = append(matches, matches2...) 67 | } 68 | ctx.JSON(iris.Map{ 69 | "code": config.StatusOK, 70 | "msg": "", 71 | "data": matches, 72 | }) 73 | } 74 | 75 | func SettingSensitiveWordsSync(ctx iris.Context) { 76 | currentSite := provider.CurrentSite(ctx) 77 | 78 | err := currentSite.AnqiSyncSensitiveWords() 79 | if err != nil { 80 | ctx.JSON(iris.Map{ 81 | "code": config.StatusFailed, 82 | "msg": err.Error(), 83 | }) 84 | return 85 | } 86 | 87 | currentSite.DeleteCacheIndex() 88 | 89 | ctx.JSON(iris.Map{ 90 | "code": config.StatusOK, 91 | "msg": "配置已更新", 92 | }) 93 | } 94 | -------------------------------------------------------------------------------- /provider/comment.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "kandaoni.com/anqicms/model" 5 | "kandaoni.com/anqicms/request" 6 | ) 7 | 8 | func (w *Website) SaveComment(req *request.PluginComment) (comment *model.Comment, err error) { 9 | if req.Id > 0 { 10 | comment, err = w.GetCommentById(req.Id) 11 | if err != nil { 12 | return nil, err 13 | } 14 | } else { 15 | comment = &model.Comment{ 16 | Status: 0, 17 | ArchiveId: req.ArchiveId, 18 | UserId: req.UserId, 19 | Ip: req.Ip, 20 | ParentId: req.ParentId, 21 | ToUid: req.ToUid, 22 | } 23 | } 24 | 25 | comment.UserName = req.UserName 26 | comment.Content = req.Content 27 | 28 | err = comment.Save(w.DB) 29 | return 30 | } 31 | 32 | func (w *Website) GetCommentList(archiveId, userId uint, order string, currentPage int, pageSize int, offset int) ([]*model.Comment, int64, error) { 33 | var comments []*model.Comment 34 | if currentPage > 1 { 35 | offset = (currentPage - 1) * pageSize 36 | } 37 | var total int64 38 | 39 | builder := w.DB.Model(&model.Comment{}) 40 | if archiveId > 0 { 41 | builder = builder.Where("archive_id = ?", archiveId) 42 | } 43 | if userId > 0 { 44 | builder = builder.Where("user_id = ?", userId) 45 | } 46 | if order != "" { 47 | builder = builder.Order(order) 48 | } 49 | if err := builder.Count(&total).Limit(pageSize).Offset(offset).Find(&comments).Error; err != nil { 50 | return nil, 0, err 51 | } 52 | for i, v := range comments { 53 | if v.ParentId > 0 { 54 | var parent model.Comment 55 | if err := w.DB.Where("id = ?", v.ParentId).First(&parent).Error; err == nil { 56 | comments[i].Parent = &parent 57 | } 58 | } 59 | } 60 | 61 | return comments, total, nil 62 | } 63 | 64 | func (w *Website) GetCommentById(id uint) (*model.Comment, error) { 65 | var comment model.Comment 66 | if err := w.DB.Where("id = ?", id).First(&comment).Error; err != nil { 67 | return nil, err 68 | } 69 | //获取itemItile 70 | archive, err := w.GetArchiveById(comment.ArchiveId) 71 | if err == nil { 72 | comment.ItemTitle = archive.Title 73 | } 74 | 75 | //获取parent 76 | if comment.ParentId > 0 { 77 | var parent model.Comment 78 | if err := w.DB.Where("id = ?", comment.ParentId).First(&parent).Error; err == nil { 79 | comment.Parent = &parent 80 | } 81 | } 82 | 83 | return &comment, nil 84 | } 85 | -------------------------------------------------------------------------------- /controller/manageController/pluginWithdraw.go: -------------------------------------------------------------------------------- 1 | package manageController 2 | 3 | import ( 4 | "github.com/kataras/iris/v12" 5 | "kandaoni.com/anqicms/config" 6 | "kandaoni.com/anqicms/provider" 7 | "kandaoni.com/anqicms/request" 8 | ) 9 | 10 | func PluginWithdrawList(ctx iris.Context) { 11 | currentSite := provider.CurrentSite(ctx) 12 | currentPage := ctx.URLParamIntDefault("current", 1) 13 | pageSize := ctx.URLParamIntDefault("pageSize", 20) 14 | 15 | orders, total := currentSite.GetWithdrawList(currentPage, pageSize) 16 | 17 | ctx.JSON(iris.Map{ 18 | "code": config.StatusOK, 19 | "msg": "", 20 | "total": total, 21 | "data": orders, 22 | }) 23 | } 24 | 25 | func PluginWithdrawDetail(ctx iris.Context) { 26 | currentSite := provider.CurrentSite(ctx) 27 | id := uint(ctx.URLParamIntDefault("id", 0)) 28 | 29 | withdraw, err := currentSite.GetWithdrawById(id) 30 | if err != nil { 31 | ctx.JSON(iris.Map{ 32 | "code": config.StatusFailed, 33 | "msg": err.Error(), 34 | }) 35 | return 36 | } 37 | 38 | ctx.JSON(iris.Map{ 39 | "code": config.StatusOK, 40 | "msg": "", 41 | "data": withdraw, 42 | }) 43 | } 44 | 45 | func PluginWithdrawSetApproval(ctx iris.Context) { 46 | currentSite := provider.CurrentSite(ctx) 47 | var req request.UserWithdrawRequest 48 | if err := ctx.ReadJSON(&req); err != nil { 49 | ctx.JSON(iris.Map{ 50 | "code": config.StatusFailed, 51 | "msg": err.Error(), 52 | }) 53 | return 54 | } 55 | 56 | err := currentSite.SetUserWithdrawApproval(&req) 57 | if err != nil { 58 | ctx.JSON(iris.Map{ 59 | "code": config.StatusFailed, 60 | "msg": err.Error(), 61 | }) 62 | return 63 | } 64 | 65 | ctx.JSON(iris.Map{ 66 | "code": config.StatusOK, 67 | "msg": "已设置成功", 68 | }) 69 | } 70 | 71 | func PluginWithdrawSetFinished(ctx iris.Context) { 72 | currentSite := provider.CurrentSite(ctx) 73 | var req request.UserWithdrawRequest 74 | if err := ctx.ReadJSON(&req); err != nil { 75 | ctx.JSON(iris.Map{ 76 | "code": config.StatusFailed, 77 | "msg": err.Error(), 78 | }) 79 | return 80 | } 81 | 82 | err := currentSite.SetUserWithdrawFinished(&req) 83 | if err != nil { 84 | ctx.JSON(iris.Map{ 85 | "code": config.StatusFailed, 86 | "msg": err.Error(), 87 | }) 88 | return 89 | } 90 | 91 | ctx.JSON(iris.Map{ 92 | "code": config.StatusOK, 93 | "msg": "已设置成功", 94 | }) 95 | } 96 | -------------------------------------------------------------------------------- /controller/tag.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "fmt" 5 | "github.com/kataras/iris/v12" 6 | "kandaoni.com/anqicms/model" 7 | "kandaoni.com/anqicms/provider" 8 | "kandaoni.com/anqicms/response" 9 | ) 10 | 11 | func TagIndexPage(ctx iris.Context) { 12 | currentSite := provider.CurrentSite(ctx) 13 | if webInfo, ok := ctx.Value("webInfo").(*response.WebInfo); ok { 14 | currentPage := ctx.Values().GetIntDefault("page", 1) 15 | webInfo.Title = currentSite.Lang("标签列表") 16 | if currentPage > 1 { 17 | webInfo.Title += " - " + fmt.Sprintf(currentSite.Lang("第%d页"), currentPage) 18 | } 19 | webInfo.PageName = "tagIndex" 20 | webInfo.CanonicalUrl = currentSite.GetUrl("tagIndex", nil, currentPage) 21 | ctx.ViewData("webInfo", webInfo) 22 | } 23 | 24 | tplName := "tag/index.html" 25 | if ViewExists(ctx, "tag_index.html") { 26 | tplName = "tag_index.html" 27 | } 28 | err := ctx.View(GetViewPath(ctx, tplName)) 29 | if err != nil { 30 | ctx.Values().Set("message", err.Error()) 31 | } 32 | } 33 | 34 | func TagPage(ctx iris.Context) { 35 | currentSite := provider.CurrentSite(ctx) 36 | tagId := ctx.Params().GetUintDefault("id", 0) 37 | urlToken := ctx.Params().GetString("filename") 38 | var tag *model.Tag 39 | var err error 40 | if urlToken != "" { 41 | //优先使用urlToken 42 | tag, err = currentSite.GetTagByUrlToken(urlToken) 43 | } else { 44 | tag, err = currentSite.GetTagById(tagId) 45 | } 46 | if err != nil { 47 | NotFound(ctx) 48 | return 49 | } 50 | 51 | if webInfo, ok := ctx.Value("webInfo").(*response.WebInfo); ok { 52 | currentPage := ctx.Values().GetIntDefault("page", 1) 53 | webInfo.Title = tag.Title 54 | if tag.SeoTitle != "" { 55 | webInfo.Title = tag.SeoTitle 56 | } 57 | if currentPage > 1 { 58 | webInfo.Title += " - " + fmt.Sprintf(currentSite.Lang("第%d页"), currentPage) 59 | } 60 | webInfo.Keywords = tag.Keywords 61 | webInfo.Description = tag.Description 62 | webInfo.NavBar = tag.Id 63 | webInfo.PageName = "tag" 64 | webInfo.CanonicalUrl = currentSite.GetUrl("tag", tag, currentPage) 65 | ctx.ViewData("webInfo", webInfo) 66 | } 67 | 68 | ctx.ViewData("tag", tag) 69 | 70 | var tplName string 71 | 72 | tplName = "tag/list.html" 73 | if ViewExists(ctx, "tag_list.html") { 74 | tplName = "tag_list.html" 75 | } 76 | 77 | err = ctx.View(GetViewPath(ctx, tplName)) 78 | if err != nil { 79 | ctx.Values().Set("message", err.Error()) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /controller/page.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "fmt" 5 | "github.com/kataras/iris/v12" 6 | "kandaoni.com/anqicms/config" 7 | "kandaoni.com/anqicms/model" 8 | "kandaoni.com/anqicms/provider" 9 | "kandaoni.com/anqicms/response" 10 | "strings" 11 | ) 12 | 13 | func PagePage(ctx iris.Context) { 14 | currentSite := provider.CurrentSite(ctx) 15 | categoryId := ctx.Params().GetUintDefault("id", 0) 16 | urlToken := ctx.Params().GetString("filename") 17 | catId := ctx.Params().GetUintDefault("catid", 0) 18 | if catId > 0 { 19 | categoryId = catId 20 | } 21 | var category *model.Category 22 | var err error 23 | if urlToken != "" { 24 | //优先使用urlToken 25 | category, err = currentSite.GetCategoryByUrlToken(urlToken) 26 | } else { 27 | category, err = currentSite.GetCategoryById(categoryId) 28 | } 29 | if err != nil { 30 | NotFound(ctx) 31 | return 32 | } 33 | 34 | //修正,如果这里读到的的category,则跳到category中 35 | if category.Type != config.CategoryTypePage { 36 | ctx.StatusCode(301) 37 | ctx.Redirect(currentSite.GetUrl("category", category, 0)) 38 | return 39 | } 40 | 41 | ctx.ViewData("page", category) 42 | 43 | if webInfo, ok := ctx.Value("webInfo").(*response.WebInfo); ok { 44 | webInfo.Title = category.Title 45 | if category.SeoTitle != "" { 46 | webInfo.Title = category.SeoTitle 47 | } 48 | webInfo.Keywords = category.Keywords 49 | webInfo.Description = category.Description 50 | webInfo.NavBar = category.Id 51 | webInfo.PageName = "pageDetail" 52 | webInfo.CanonicalUrl = currentSite.GetUrl("page", category, 0) 53 | ctx.ViewData("webInfo", webInfo) 54 | } 55 | //模板优先级:1、设置的template;2、存在分类id为名称的模板;3、继承的上级模板;4、默认模板 56 | tplName := "page/detail.html" 57 | if ViewExists(ctx, "page_detail.html") { 58 | tplName = "page_detail.html" 59 | } 60 | tmpTpl := fmt.Sprintf("page/detail-%d.html", category.Id) 61 | if ViewExists(ctx, tmpTpl) { 62 | tplName = tmpTpl 63 | } else if ViewExists(ctx, fmt.Sprintf("page-%d.html", category.Id)) { 64 | tplName = fmt.Sprintf("page-%d.html", category.Id) 65 | } else { 66 | categoryTemplate := currentSite.GetCategoryTemplate(category) 67 | if categoryTemplate != nil { 68 | tplName = categoryTemplate.Template 69 | } 70 | } 71 | if !strings.HasSuffix(tplName, ".html") { 72 | tplName += ".html" 73 | } 74 | 75 | err = ctx.View(GetViewPath(ctx, tplName)) 76 | if err != nil { 77 | ctx.Values().Set("message", err.Error()) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /provider/weapp.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "crypto/tls" 5 | "errors" 6 | "fmt" 7 | "github.com/medivhzhan/weapp/v3" 8 | "io" 9 | "kandaoni.com/anqicms/library" 10 | "kandaoni.com/anqicms/model" 11 | "net/http" 12 | "time" 13 | ) 14 | 15 | func (w *Website) GetWeappClient(focus bool) *weapp.Client { 16 | if w.weappClient == nil || focus { 17 | httpCli := &http.Client{ 18 | Timeout: 10 * time.Second, 19 | Transport: &http.Transport{ 20 | // 跳过校验 21 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 22 | }, 23 | } 24 | 25 | w.weappClient = weapp.NewClient( 26 | w.PluginWeapp.AppID, 27 | w.PluginWeapp.AppSecret, 28 | weapp.WithHttpClient(httpCli), 29 | ) 30 | } 31 | 32 | return w.weappClient 33 | } 34 | 35 | func (w *Website) GetWeappQrcode(weappPath, scene string, userId uint) (string, error) { 36 | var qrcode model.WeappQrcode 37 | err := w.DB.Where("`user_id` = ? and `path` = ?", userId, weappPath).Take(&qrcode).Error 38 | if err != nil { 39 | // 没有 40 | codeUrl, err := w.CreateWeappQrcode(weappPath, scene) 41 | if err != nil { 42 | return "", err 43 | } 44 | qrcode = model.WeappQrcode{ 45 | UserId: userId, 46 | Path: weappPath, 47 | CodeUrl: codeUrl, 48 | } 49 | w.DB.Save(&qrcode) 50 | } 51 | 52 | return w.PluginStorage.StorageUrl + "/" + qrcode.CodeUrl, nil 53 | } 54 | 55 | func (w *Website) CreateWeappQrcode(weappPath, scene string) (string, error) { 56 | creator := weapp.QRCode{ 57 | Path: weappPath, 58 | } 59 | resp, commonErr, err := w.GetWeappClient(false).GetQRCode(&creator) 60 | if err != nil { 61 | return "", err 62 | } 63 | defer resp.Body.Close() 64 | if commonErr.ErrCode != 0 { 65 | return "", errors.New(commonErr.ErrMSG) 66 | } 67 | // 写入二维码到数据库 68 | bts, err := io.ReadAll(resp.Body) 69 | if err != nil { 70 | return "", err 71 | } 72 | md5Str := library.Md5Bytes(bts) 73 | tmpName := md5Str + ".png" 74 | filePath := fmt.Sprintf("uploads/qrcode/%s/%s/%s", tmpName[:3], tmpName[3:6], tmpName[6:]) 75 | 76 | _, err = w.Storage.UploadFile(filePath, bts) 77 | if err != nil { 78 | return "", err 79 | } 80 | //文件上传完成 81 | attachment := &model.Attachment{ 82 | FileName: tmpName, 83 | FileLocation: filePath, 84 | FileSize: int64(len(bts)), 85 | FileMd5: md5Str, 86 | CategoryId: 0, 87 | IsImage: 0, 88 | Status: 1, 89 | } 90 | err = attachment.Save(w.DB) 91 | attachment.GetThumb(w.PluginStorage.StorageUrl) 92 | 93 | return attachment.FileLocation, nil 94 | } 95 | -------------------------------------------------------------------------------- /tags/userGroupDetail.go: -------------------------------------------------------------------------------- 1 | package tags 2 | 3 | import ( 4 | "fmt" 5 | "github.com/flosch/pongo2/v6" 6 | "kandaoni.com/anqicms/library" 7 | "kandaoni.com/anqicms/model" 8 | "kandaoni.com/anqicms/provider" 9 | "reflect" 10 | ) 11 | 12 | type tagUserGroupDetailNode struct { 13 | args map[string]pongo2.IEvaluator 14 | name string 15 | } 16 | 17 | func (node *tagUserGroupDetailNode) Execute(ctx *pongo2.ExecutionContext, writer pongo2.TemplateWriter) *pongo2.Error { 18 | currentSite, _ := ctx.Public["website"].(*provider.Website) 19 | if currentSite == nil || currentSite.DB == nil { 20 | return nil 21 | } 22 | args, err := parseArgs(node.args, ctx) 23 | if err != nil { 24 | return err 25 | } 26 | id := uint(0) 27 | 28 | if args["id"] != nil { 29 | id = uint(args["id"].Integer()) 30 | } 31 | fieldName := "" 32 | if args["name"] != nil { 33 | fieldName = args["name"].String() 34 | fieldName = library.Case2Camel(fieldName) 35 | } 36 | 37 | groupDetail, ok := ctx.Public["userGroup"].(*model.UserGroup) 38 | if !ok && id == 0 { 39 | return nil 40 | } 41 | //不是同一个,重新获取 42 | if groupDetail != nil && (id > 0 && groupDetail.Id != id) { 43 | groupDetail = nil 44 | } 45 | 46 | if groupDetail == nil && id > 0 { 47 | groupDetail, _ = currentSite.GetUserGroupInfo(id) 48 | if groupDetail == nil { 49 | return nil 50 | } 51 | } 52 | if groupDetail == nil { 53 | return nil 54 | } 55 | 56 | v := reflect.ValueOf(*groupDetail) 57 | 58 | f := v.FieldByName(fieldName) 59 | 60 | content := fmt.Sprintf("%v", f) 61 | if node.name == "" { 62 | writer.WriteString(content) 63 | } else { 64 | ctx.Private[node.name] = content 65 | } 66 | 67 | return nil 68 | } 69 | 70 | func TagUserGroupDetailParser(doc *pongo2.Parser, start *pongo2.Token, arguments *pongo2.Parser) (pongo2.INodeTag, *pongo2.Error) { 71 | tagNode := &tagPageDetailNode{ 72 | args: make(map[string]pongo2.IEvaluator), 73 | } 74 | 75 | nameToken := arguments.MatchType(pongo2.TokenIdentifier) 76 | if nameToken == nil { 77 | return nil, arguments.Error("userGroupDetail-tag needs a userGroup field name.", nil) 78 | } 79 | 80 | if nameToken.Val == "with" { 81 | //with 需要退回 82 | arguments.ConsumeN(-1) 83 | } else { 84 | tagNode.name = nameToken.Val 85 | } 86 | 87 | args, err := parseWith(arguments) 88 | if err != nil { 89 | return nil, err 90 | } 91 | tagNode.args = args 92 | 93 | for arguments.Remaining() > 0 { 94 | return nil, arguments.Error("Malformed userGroupDetail-tag arguments.", nil) 95 | } 96 | 97 | return tagNode, nil 98 | } 99 | -------------------------------------------------------------------------------- /template/default/article/detail.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block container %} 4 | {% include "partial/breadcrumb.html" %} 5 |
6 | 7 |
8 | 9 |
10 |
11 |
12 |

{% archiveDetail with name="Title" %}

13 |
14 | 发布时间:{% archiveDetail with name="CreatedTime" format="2006-01-02" %} 15 | 作者:{% system with name="SiteName" %} 16 |
17 |
18 |
19 | {% archiveDetail productContent with name="Content" %} 20 | {{productContent|safe}} 21 |
22 | 23 |
24 |

相关产品

25 |
    26 | {% archiveList products with moduleId=2 type="list" limit="4" %} 27 | {% for inner in products %} 28 |
  • 29 |
    30 |
    31 | {{inner.Title}} 32 |
    33 |
    34 |

    {{inner.Title}}

    35 |
    36 |
    37 |
  • 38 | {% endfor %} 39 | {% endarchiveList %} 40 |
41 |
42 | 43 | 44 |
45 |

相关文章

46 |
    47 | {% archiveList articles with moduleId=1 type="related" limit="10" %} 48 | {% for inner in articles %} 49 |
  • 50 |

    {{inner.Title}}

    51 | {{stampToDate(inner.CreatedTime, "2006-01-02")}} 52 |
  • 53 | {% endfor %} 54 | {% endarchiveList %} 55 |
56 |
57 | 58 |
59 | {% include "partial/sidebar.html" %} 60 |
61 | 62 |
63 | 64 |
65 | {% endblock %} -------------------------------------------------------------------------------- /tags/bannerList.go: -------------------------------------------------------------------------------- 1 | package tags 2 | 3 | import ( 4 | "fmt" 5 | "github.com/flosch/pongo2/v6" 6 | "kandaoni.com/anqicms/provider" 7 | "strings" 8 | ) 9 | 10 | type tagBannerListNode struct { 11 | name string 12 | args map[string]pongo2.IEvaluator 13 | wrapper *pongo2.NodeWrapper 14 | } 15 | 16 | func (node *tagBannerListNode) Execute(ctx *pongo2.ExecutionContext, writer pongo2.TemplateWriter) *pongo2.Error { 17 | currentSite, _ := ctx.Public["website"].(*provider.Website) 18 | if currentSite == nil || currentSite.DB == nil { 19 | return nil 20 | } 21 | 22 | bannerList := currentSite.Banner 23 | for i := range bannerList { 24 | if !strings.HasPrefix(bannerList[i].Logo, "http") && !strings.HasPrefix(bannerList[i].Logo, "//") { 25 | bannerList[i].Logo = currentSite.PluginStorage.StorageUrl + "/" + strings.TrimPrefix(bannerList[i].Logo, "/") 26 | } 27 | } 28 | 29 | ctx.Private[node.name] = bannerList 30 | 31 | //execute 32 | node.wrapper.Execute(ctx, writer) 33 | 34 | return nil 35 | } 36 | 37 | func TagBannerListParser(doc *pongo2.Parser, start *pongo2.Token, arguments *pongo2.Parser) (pongo2.INodeTag, *pongo2.Error) { 38 | tagNode := &tagBannerListNode{ 39 | args: make(map[string]pongo2.IEvaluator), 40 | } 41 | 42 | nameToken := arguments.MatchType(pongo2.TokenIdentifier) 43 | if nameToken == nil { 44 | return nil, arguments.Error("bannerList-tag needs a accept name.", nil) 45 | } 46 | 47 | tagNode.name = nameToken.Val 48 | 49 | // After having parsed the name we're gonna parse the with options 50 | args, err := parseWith(arguments) 51 | if err != nil { 52 | return nil, err 53 | } 54 | tagNode.args = args 55 | 56 | for arguments.Remaining() > 0 { 57 | return nil, arguments.Error("Malformed bannerList-tag arguments.", nil) 58 | } 59 | 60 | wrapper, endtagargs, err := doc.WrapUntilTag("endbannerList") 61 | if err != nil { 62 | return nil, err 63 | } 64 | if endtagargs.Remaining() > 0 { 65 | endtagnameToken := endtagargs.MatchType(pongo2.TokenIdentifier) 66 | if endtagnameToken != nil { 67 | if endtagnameToken.Val != nameToken.Val { 68 | return nil, endtagargs.Error(fmt.Sprintf("Name for 'endbannerList' must equal to 'bannerList'-tag's name ('%s' != '%s').", 69 | nameToken.Val, endtagnameToken.Val), nil) 70 | } 71 | } 72 | 73 | if endtagnameToken == nil || endtagargs.Remaining() > 0 { 74 | return nil, endtagargs.Error("Either no or only one argument (identifier) allowed for 'endbannerList'.", nil) 75 | } 76 | } 77 | tagNode.wrapper = wrapper 78 | 79 | return tagNode, nil 80 | } 81 | -------------------------------------------------------------------------------- /tags/linkList.go: -------------------------------------------------------------------------------- 1 | package tags 2 | 3 | import ( 4 | "fmt" 5 | "github.com/flosch/pongo2/v6" 6 | "kandaoni.com/anqicms/provider" 7 | ) 8 | 9 | type tagLinkListNode struct { 10 | name string 11 | args map[string]pongo2.IEvaluator 12 | wrapper *pongo2.NodeWrapper 13 | } 14 | 15 | func (node *tagLinkListNode) Execute(ctx *pongo2.ExecutionContext, writer pongo2.TemplateWriter) *pongo2.Error { 16 | currentSite, _ := ctx.Public["website"].(*provider.Website) 17 | if currentSite == nil || currentSite.DB == nil { 18 | return nil 19 | } 20 | args, err := parseArgs(node.args, ctx) 21 | if err != nil { 22 | return err 23 | } 24 | 25 | if args["site_id"] != nil { 26 | args["siteId"] = args["site_id"] 27 | } 28 | if args["siteId"] != nil { 29 | siteId := args["siteId"].Integer() 30 | currentSite = provider.GetWebsite(uint(siteId)) 31 | } 32 | 33 | linkList, _ := currentSite.GetLinkList() 34 | 35 | ctx.Private[node.name] = linkList 36 | //execute 37 | node.wrapper.Execute(ctx, writer) 38 | 39 | return nil 40 | } 41 | 42 | func TagLinkListParser(doc *pongo2.Parser, start *pongo2.Token, arguments *pongo2.Parser) (pongo2.INodeTag, *pongo2.Error) { 43 | tagNode := &tagLinkListNode{ 44 | args: make(map[string]pongo2.IEvaluator), 45 | } 46 | 47 | nameToken := arguments.MatchType(pongo2.TokenIdentifier) 48 | if nameToken == nil { 49 | return nil, arguments.Error("linkList-tag needs a accept name.", nil) 50 | } 51 | 52 | tagNode.name = nameToken.Val 53 | 54 | // After having parsed the name we're gonna parse the with options 55 | args, err := parseWith(arguments) 56 | if err != nil { 57 | return nil, err 58 | } 59 | tagNode.args = args 60 | 61 | for arguments.Remaining() > 0 { 62 | return nil, arguments.Error("Malformed linkList-tag arguments.", nil) 63 | } 64 | wrapper, endtagargs, err := doc.WrapUntilTag("endlinkList") 65 | if err != nil { 66 | return nil, err 67 | } 68 | if endtagargs.Remaining() > 0 { 69 | endtagnameToken := endtagargs.MatchType(pongo2.TokenIdentifier) 70 | if endtagnameToken != nil { 71 | if endtagnameToken.Val != nameToken.Val { 72 | return nil, endtagargs.Error(fmt.Sprintf("Name for 'endlinkList' must equal to 'linkList'-tag's name ('%s' != '%s').", 73 | nameToken.Val, endtagnameToken.Val), nil) 74 | } 75 | } 76 | 77 | if endtagnameToken == nil || endtagargs.Remaining() > 0 { 78 | return nil, endtagargs.Error("Either no or only one argument (identifier) allowed for 'endlinkList'.", nil) 79 | } 80 | } 81 | tagNode.wrapper = wrapper 82 | 83 | return tagNode, nil 84 | } 85 | -------------------------------------------------------------------------------- /tags/guestbook.go: -------------------------------------------------------------------------------- 1 | package tags 2 | 3 | import ( 4 | "fmt" 5 | "github.com/flosch/pongo2/v6" 6 | "kandaoni.com/anqicms/provider" 7 | ) 8 | 9 | type tagGuestbookNode struct { 10 | name string 11 | args map[string]pongo2.IEvaluator 12 | wrapper *pongo2.NodeWrapper 13 | } 14 | 15 | func (node *tagGuestbookNode) Execute(ctx *pongo2.ExecutionContext, writer pongo2.TemplateWriter) *pongo2.Error { 16 | currentSite, _ := ctx.Public["website"].(*provider.Website) 17 | if currentSite == nil || currentSite.DB == nil { 18 | return nil 19 | } 20 | args, err := parseArgs(node.args, ctx) 21 | if err != nil { 22 | return err 23 | } 24 | 25 | if args["site_id"] != nil { 26 | args["siteId"] = args["site_id"] 27 | } 28 | if args["siteId"] != nil { 29 | siteId := args["siteId"].Integer() 30 | currentSite = provider.GetWebsite(uint(siteId)) 31 | } 32 | 33 | fields := currentSite.GetGuestbookFields() 34 | for i := range fields { 35 | //分割items 36 | fields[i].SplitContent() 37 | } 38 | 39 | ctx.Private[node.name] = fields 40 | //execute 41 | node.wrapper.Execute(ctx, writer) 42 | 43 | return nil 44 | } 45 | 46 | func TagGuestbookParser(doc *pongo2.Parser, start *pongo2.Token, arguments *pongo2.Parser) (pongo2.INodeTag, *pongo2.Error) { 47 | tagNode := &tagGuestbookNode{ 48 | args: make(map[string]pongo2.IEvaluator), 49 | } 50 | 51 | nameToken := arguments.MatchType(pongo2.TokenIdentifier) 52 | if nameToken == nil { 53 | return nil, arguments.Error("guestbook-tag needs a accept name.", nil) 54 | } 55 | 56 | tagNode.name = nameToken.Val 57 | 58 | // After having parsed the name we're gonna parse the with options 59 | args, err := parseWith(arguments) 60 | if err != nil { 61 | return nil, err 62 | } 63 | tagNode.args = args 64 | 65 | for arguments.Remaining() > 0 { 66 | return nil, arguments.Error("Malformed guestbook-tag arguments.", nil) 67 | } 68 | wrapper, endtagargs, err := doc.WrapUntilTag("endguestbook") 69 | if err != nil { 70 | return nil, err 71 | } 72 | if endtagargs.Remaining() > 0 { 73 | endtagnameToken := endtagargs.MatchType(pongo2.TokenIdentifier) 74 | if endtagnameToken != nil { 75 | if endtagnameToken.Val != nameToken.Val { 76 | return nil, endtagargs.Error(fmt.Sprintf("Name for 'endguestbook' must equal to 'guestbook'-tag's name ('%s' != '%s').", 77 | nameToken.Val, endtagnameToken.Val), nil) 78 | } 79 | } 80 | 81 | if endtagnameToken == nil || endtagargs.Remaining() > 0 { 82 | return nil, endtagargs.Error("Either no or only one argument (identifier) allowed for 'endguestbook'.", nil) 83 | } 84 | } 85 | tagNode.wrapper = wrapper 86 | 87 | return tagNode, nil 88 | } 89 | -------------------------------------------------------------------------------- /request/user.go: -------------------------------------------------------------------------------- 1 | package request 2 | 3 | import ( 4 | "kandaoni.com/anqicms/model" 5 | ) 6 | 7 | type UserRequest struct { 8 | Id uint `json:"id"` 9 | UserName string `json:"user_name"` 10 | RealName string `json:"real_name"` 11 | AvatarURL string `json:"avatar_url"` 12 | Phone string `json:"phone"` 13 | Email string `json:"email"` 14 | GroupId uint `json:"group_id"` 15 | Status int `json:"status"` 16 | Balance int64 `json:"balance"` 17 | IsRetailer int `json:"is_retailer"` 18 | ParentId uint `json:"parent_id"` 19 | Password string `json:"password"` 20 | InviteCode string `json:"invite_code"` 21 | ExpireTime int64 `json:"expire_time"` 22 | } 23 | 24 | type UserPasswordRequest struct { 25 | OldPassword string `json:"old_password"` 26 | Password string `json:"password"` 27 | } 28 | 29 | type UserGroupRequest struct { 30 | Id uint `json:"id"` 31 | Title string `json:"title"` 32 | Description string `json:"description"` 33 | Level int `json:"level"` // group level 34 | Price int64 `json:"price"` 35 | Status int `json:"status"` 36 | Setting model.UserGroupSetting `json:"setting"` //配置 37 | } 38 | 39 | type ApiRegisterRequest struct { 40 | InviteId uint `json:"invite_id"` // 邀请用户ID 41 | UserName string `json:"user_name"` 42 | Password string `json:"password"` 43 | RealName string `json:"real_name"` 44 | AvatarURL string `json:"avatar_url"` 45 | Email string `json:"email"` 46 | Phone string `json:"phone"` 47 | CaptchaId string `json:"captcha_id"` 48 | Captcha string `json:"captcha"` 49 | Code string `json:"code"` //phone verify code 50 | } 51 | 52 | type ApiLoginRequest struct { 53 | InviteId uint `json:"invite_id"` // 邀请用户ID 54 | Code string `json:"code"` //微信临时凭证,或者是验证码 55 | AnonymousCode string `json:"anonymousCode"` 56 | Platform string `json:"platform"` 57 | Avatar string `json:"avatar"` 58 | NickName string `json:"nick_name"` 59 | Gender uint `json:"gender"` 60 | Province string `json:"province"` 61 | City string `json:"city"` 62 | County string `json:"county"` 63 | EncryptedData string `json:"encryptedData"` 64 | Iv string `json:"iv"` 65 | Signature string `json:"signature"` 66 | RawData string `json:"rawData"` 67 | 68 | Remember bool `json:"remember"` // keep login state 69 | UserName string `json:"user_name"` 70 | Password string `json:"password"` 71 | CaptchaId string `json:"captcha_id"` 72 | Captcha string `json:"captcha"` 73 | } 74 | -------------------------------------------------------------------------------- /controller/manageController/pluginSendmail.go: -------------------------------------------------------------------------------- 1 | package manageController 2 | 3 | import ( 4 | "fmt" 5 | "github.com/kataras/iris/v12" 6 | "kandaoni.com/anqicms/config" 7 | "kandaoni.com/anqicms/provider" 8 | ) 9 | 10 | func PluginSendmailList(ctx iris.Context) { 11 | currentSite := provider.CurrentSite(ctx) 12 | //不需要分页,只显示最后20条 13 | list, err := currentSite.GetLastSendmailList() 14 | if err != nil { 15 | ctx.JSON(iris.Map{ 16 | "code": config.StatusFailed, 17 | "msg": "", 18 | }) 19 | return 20 | } 21 | 22 | ctx.JSON(iris.Map{ 23 | "code": config.StatusOK, 24 | "msg": "", 25 | "data": list, 26 | }) 27 | } 28 | 29 | func PluginSendmailTest(ctx iris.Context) { 30 | currentSite := provider.CurrentSite(ctx) 31 | setting := currentSite.PluginSendmail 32 | if setting.Account == "" { 33 | ctx.JSON(iris.Map{ 34 | "code": config.StatusFailed, 35 | "msg": "请先设置邮件发送账号", 36 | }) 37 | return 38 | } 39 | 40 | subject := "测试邮件" 41 | content := "这是一封测试邮件。收到邮件表示配置正常" 42 | 43 | err := currentSite.SendMail(subject, content) 44 | if err != nil { 45 | ctx.JSON(iris.Map{ 46 | "code": config.StatusFailed, 47 | "msg": err.Error(), 48 | }) 49 | return 50 | } 51 | 52 | ctx.JSON(iris.Map{ 53 | "code": config.StatusOK, 54 | "msg": "邮件发送成功", 55 | }) 56 | } 57 | 58 | func PluginSendmailSetting(ctx iris.Context) { 59 | currentSite := provider.CurrentSite(ctx) 60 | setting := currentSite.PluginSendmail 61 | 62 | ctx.JSON(iris.Map{ 63 | "code": config.StatusOK, 64 | "msg": "", 65 | "data": setting, 66 | }) 67 | } 68 | 69 | func PluginSendmailSettingForm(ctx iris.Context) { 70 | currentSite := provider.CurrentSite(ctx) 71 | var req config.PluginSendmail 72 | if err := ctx.ReadJSON(&req); err != nil { 73 | ctx.JSON(iris.Map{ 74 | "code": config.StatusFailed, 75 | "msg": err.Error(), 76 | }) 77 | return 78 | } 79 | 80 | currentSite.PluginSendmail.Server = req.Server 81 | currentSite.PluginSendmail.UseSSL = req.UseSSL 82 | currentSite.PluginSendmail.Port = req.Port 83 | currentSite.PluginSendmail.Account = req.Account 84 | currentSite.PluginSendmail.Password = req.Password 85 | currentSite.PluginSendmail.Recipient = req.Recipient 86 | 87 | err := currentSite.SaveSettingValue(provider.SendmailSettingKey, currentSite.PluginSendmail) 88 | if err != nil { 89 | ctx.JSON(iris.Map{ 90 | "code": config.StatusFailed, 91 | "msg": err.Error(), 92 | }) 93 | return 94 | } 95 | 96 | currentSite.AddAdminLog(ctx, fmt.Sprintf("更新发送邮件配置")) 97 | 98 | ctx.JSON(iris.Map{ 99 | "code": config.StatusOK, 100 | "msg": "配置已更新", 101 | }) 102 | } 103 | -------------------------------------------------------------------------------- /tags/tagDetail.go: -------------------------------------------------------------------------------- 1 | package tags 2 | 3 | import ( 4 | "fmt" 5 | "github.com/flosch/pongo2/v6" 6 | "kandaoni.com/anqicms/library" 7 | "kandaoni.com/anqicms/model" 8 | "kandaoni.com/anqicms/provider" 9 | "reflect" 10 | ) 11 | 12 | type tagTagDetailNode struct { 13 | args map[string]pongo2.IEvaluator 14 | name string 15 | } 16 | 17 | func (node *tagTagDetailNode) Execute(ctx *pongo2.ExecutionContext, writer pongo2.TemplateWriter) *pongo2.Error { 18 | currentSite, _ := ctx.Public["website"].(*provider.Website) 19 | if currentSite == nil || currentSite.DB == nil { 20 | return nil 21 | } 22 | args, err := parseArgs(node.args, ctx) 23 | if err != nil { 24 | return err 25 | } 26 | id := uint(0) 27 | 28 | if args["site_id"] != nil { 29 | args["siteId"] = args["site_id"] 30 | } 31 | if args["siteId"] != nil { 32 | siteId := args["siteId"].Integer() 33 | currentSite = provider.GetWebsite(uint(siteId)) 34 | } 35 | 36 | tagDetail, _ := ctx.Public["tag"].(*model.Tag) 37 | if args["id"] != nil { 38 | id = uint(args["id"].Integer()) 39 | tagDetail, _ = currentSite.GetTagById(id) 40 | } 41 | 42 | fieldName := "" 43 | if args["name"] != nil { 44 | fieldName = args["name"].String() 45 | fieldName = library.Case2Camel(fieldName) 46 | } 47 | 48 | var content string 49 | 50 | if tagDetail != nil { 51 | tagDetail.Link = currentSite.GetUrl("tag", tagDetail, 0) 52 | 53 | v := reflect.ValueOf(*tagDetail) 54 | 55 | f := v.FieldByName(fieldName) 56 | 57 | content = fmt.Sprintf("%v", f) 58 | if content == "" && fieldName == "SeoTitle" { 59 | content = tagDetail.Title 60 | } 61 | } 62 | 63 | // output 64 | if node.name == "" { 65 | writer.WriteString(content) 66 | } else { 67 | ctx.Private[node.name] = content 68 | } 69 | 70 | return nil 71 | } 72 | 73 | func TagTagDetailParser(doc *pongo2.Parser, start *pongo2.Token, arguments *pongo2.Parser) (pongo2.INodeTag, *pongo2.Error) { 74 | tagNode := &tagTagDetailNode{ 75 | args: make(map[string]pongo2.IEvaluator), 76 | } 77 | 78 | nameToken := arguments.MatchType(pongo2.TokenIdentifier) 79 | if nameToken == nil { 80 | return nil, arguments.Error("tagDetail-tag needs a accept name.", nil) 81 | } 82 | 83 | if nameToken.Val == "with" { 84 | //with 需要退回 85 | arguments.ConsumeN(-1) 86 | } else { 87 | tagNode.name = nameToken.Val 88 | } 89 | 90 | args, err := parseWith(arguments) 91 | if err != nil { 92 | return nil, err 93 | } 94 | tagNode.args = args 95 | 96 | for arguments.Remaining() > 0 { 97 | return nil, arguments.Error("Malformed tagDetail-tag arguments.", nil) 98 | } 99 | 100 | return tagNode, nil 101 | } 102 | -------------------------------------------------------------------------------- /tags/pageList.go: -------------------------------------------------------------------------------- 1 | package tags 2 | 3 | import ( 4 | "fmt" 5 | "github.com/flosch/pongo2/v6" 6 | "kandaoni.com/anqicms/config" 7 | "kandaoni.com/anqicms/provider" 8 | ) 9 | 10 | type tagPageListNode struct { 11 | name string 12 | args map[string]pongo2.IEvaluator 13 | wrapper *pongo2.NodeWrapper 14 | } 15 | 16 | func (node *tagPageListNode) Execute(ctx *pongo2.ExecutionContext, writer pongo2.TemplateWriter) *pongo2.Error { 17 | currentSite, _ := ctx.Public["website"].(*provider.Website) 18 | if currentSite == nil || currentSite.DB == nil { 19 | return nil 20 | } 21 | args, err := parseArgs(node.args, ctx) 22 | if err != nil { 23 | return err 24 | } 25 | 26 | if args["site_id"] != nil { 27 | args["siteId"] = args["site_id"] 28 | } 29 | if args["siteId"] != nil { 30 | siteId := args["siteId"].Integer() 31 | currentSite = provider.GetWebsite(uint(siteId)) 32 | } 33 | 34 | pageList := currentSite.GetCategoriesFromCache(0, 0, config.CategoryTypePage) 35 | for i := range pageList { 36 | pageList[i].Link = currentSite.GetUrl("page", pageList[i], 0) 37 | } 38 | 39 | ctx.Private[node.name] = pageList 40 | //execute 41 | node.wrapper.Execute(ctx, writer) 42 | 43 | return nil 44 | } 45 | 46 | func TagPageListParser(doc *pongo2.Parser, start *pongo2.Token, arguments *pongo2.Parser) (pongo2.INodeTag, *pongo2.Error) { 47 | tagNode := &tagPageListNode{ 48 | args: make(map[string]pongo2.IEvaluator), 49 | } 50 | 51 | nameToken := arguments.MatchType(pongo2.TokenIdentifier) 52 | if nameToken == nil { 53 | return nil, arguments.Error("pageList-tag needs a accept name.", nil) 54 | } 55 | 56 | tagNode.name = nameToken.Val 57 | 58 | // After having parsed the name we're gonna parse the with options 59 | args, err := parseWith(arguments) 60 | if err != nil { 61 | return nil, err 62 | } 63 | tagNode.args = args 64 | 65 | for arguments.Remaining() > 0 { 66 | return nil, arguments.Error("Malformed pageList-tag arguments.", nil) 67 | } 68 | wrapper, endtagargs, err := doc.WrapUntilTag("endpageList") 69 | if err != nil { 70 | return nil, err 71 | } 72 | if endtagargs.Remaining() > 0 { 73 | endtagnameToken := endtagargs.MatchType(pongo2.TokenIdentifier) 74 | if endtagnameToken != nil { 75 | if endtagnameToken.Val != nameToken.Val { 76 | return nil, endtagargs.Error(fmt.Sprintf("Name for 'endpageList' must equal to 'pageList'-tag's name ('%s' != '%s').", 77 | nameToken.Val, endtagnameToken.Val), nil) 78 | } 79 | } 80 | 81 | if endtagnameToken == nil || endtagargs.Remaining() > 0 { 82 | return nil, endtagargs.Error("Either no or only one argument (identifier) allowed for 'endpageList'.", nil) 83 | } 84 | } 85 | tagNode.wrapper = wrapper 86 | 87 | return tagNode, nil 88 | } 89 | -------------------------------------------------------------------------------- /tags/userDetail.go: -------------------------------------------------------------------------------- 1 | package tags 2 | 3 | import ( 4 | "fmt" 5 | "github.com/flosch/pongo2/v6" 6 | "kandaoni.com/anqicms/library" 7 | "kandaoni.com/anqicms/model" 8 | "kandaoni.com/anqicms/provider" 9 | "reflect" 10 | ) 11 | 12 | type tagUserDetailNode struct { 13 | args map[string]pongo2.IEvaluator 14 | name string 15 | } 16 | 17 | func (node *tagUserDetailNode) Execute(ctx *pongo2.ExecutionContext, writer pongo2.TemplateWriter) *pongo2.Error { 18 | currentSite, _ := ctx.Public["website"].(*provider.Website) 19 | if currentSite == nil || currentSite.DB == nil { 20 | return nil 21 | } 22 | args, err := parseArgs(node.args, ctx) 23 | if err != nil { 24 | return err 25 | } 26 | id := uint(0) 27 | 28 | if args["id"] != nil { 29 | id = uint(args["id"].Integer()) 30 | } 31 | fieldName := "" 32 | if args["name"] != nil { 33 | fieldName = args["name"].String() 34 | fieldName = library.Case2Camel(fieldName) 35 | } 36 | // 规定某些字段不能返回内容 37 | if fieldName == "Password" { 38 | return nil 39 | } 40 | 41 | userDetail, ok := ctx.Public["user"].(*model.User) 42 | if !ok && id == 0 { 43 | return nil 44 | } 45 | //不是同一个,重新获取 46 | if userDetail != nil && (id > 0 && userDetail.Id != id) { 47 | userDetail = nil 48 | } 49 | 50 | if userDetail == nil && id > 0 { 51 | userDetail, _ = currentSite.GetUserInfoById(id) 52 | if userDetail == nil { 53 | return nil 54 | } 55 | } 56 | if userDetail == nil { 57 | return nil 58 | } 59 | 60 | userDetail.Link = currentSite.GetUrl("user", userDetail, 0) 61 | 62 | v := reflect.ValueOf(*userDetail) 63 | 64 | f := v.FieldByName(fieldName) 65 | 66 | content := fmt.Sprintf("%v", f) 67 | if node.name == "" { 68 | writer.WriteString(content) 69 | } else { 70 | ctx.Private[node.name] = content 71 | } 72 | 73 | return nil 74 | } 75 | 76 | func TagUserDetailParser(doc *pongo2.Parser, start *pongo2.Token, arguments *pongo2.Parser) (pongo2.INodeTag, *pongo2.Error) { 77 | tagNode := &tagPageDetailNode{ 78 | args: make(map[string]pongo2.IEvaluator), 79 | } 80 | 81 | nameToken := arguments.MatchType(pongo2.TokenIdentifier) 82 | if nameToken == nil { 83 | return nil, arguments.Error("userDetail-tag needs a user field name.", nil) 84 | } 85 | 86 | if nameToken.Val == "with" { 87 | //with 需要退回 88 | arguments.ConsumeN(-1) 89 | } else { 90 | tagNode.name = nameToken.Val 91 | } 92 | 93 | args, err := parseWith(arguments) 94 | if err != nil { 95 | return nil, err 96 | } 97 | tagNode.args = args 98 | 99 | for arguments.Remaining() > 0 { 100 | return nil, arguments.Error("Malformed userDetail-tag arguments.", nil) 101 | } 102 | 103 | return tagNode, nil 104 | } 105 | -------------------------------------------------------------------------------- /template/default/case/detail.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block container %} 4 | {% include "partial/breadcrumb.html" %} 5 |
6 |
{% categoryDetail with name="Title" %}
7 | 8 |
9 | 10 |
11 |
12 | 13 |
14 |
15 | {% archiveDetail with name='Title' %} 16 |
17 |
18 |

{% archiveDetail with name="Title" %}

19 |

{% archiveDetail with name="Description" %}

20 |
21 | 咨询热线: 22 | 23 | {% contact with name="Cellphone" %} 24 | 25 |
26 |
27 |
28 | 29 |
30 | {% archiveDetail productContent with name="Content" %} 31 | {{productContent|safe}} 32 |
33 | 34 |
35 |

相关案例

36 |
    37 | {% archiveList products with type="related" limit="4" %} 38 | {% for inner in products %} 39 |
  • 40 |
    41 |
    42 | {{inner.Title}} 43 |
    44 |
    45 |

    {{inner.Title}}

    46 |
    47 |
    48 |
  • 49 | {% endfor %} 50 | {% endarchiveList %} 51 |
52 |
53 | 54 | 55 |
56 |

相关文章

57 |
    58 | {% archiveList articles with moduleId=1 type="list" limit="10" %} 59 | {% for inner in articles %} 60 |
  • 61 |

    {{inner.Title}}

    62 | {{stampToDate(inner.CreatedTime, "2006-01-02")}} 63 |
  • 64 | {% endfor %} 65 | {% endarchiveList %} 66 |
67 |
68 | 69 |
70 | {% include "partial/sidebar.html" %} 71 |
72 | 73 | 74 |
75 | 76 |
77 | {% endblock %} -------------------------------------------------------------------------------- /library/math.go: -------------------------------------------------------------------------------- 1 | package library 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/hex" 6 | "fmt" 7 | "math/rand" 8 | "strconv" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | var tenToAny = map[int]string{0: "0", 1: "1", 2: "2", 3: "3", 4: "4", 5: "5", 6: "6", 7: "7", 8: "8", 9: "9", 10: "a", 11: "b", 12: "c", 13: "d", 14: "e", 15: "f", 16: "g", 17: "h", 18: "i", 19: "j", 20: "k", 21: "l", 22: "m", 23: "n", 24: "o", 25: "p", 26: "q", 27: "r", 28: "s", 29: "t", 30: "u", 31: "v", 32: "w", 33: "x", 34: "y", 35: "z", 37: "A", 38: "B", 39: "C", 40: "D", 41: "E", 42: "F", 43: "G", 44: "H", 45: "I", 46: "J", 47: "K", 48: "L", 49: "M", 50: "N", 51: "O", 52: "P", 53: "Q", 54: "R", 55: "S", 56: "T", 57: "U", 58: "V", 59: "W", 60: "X", 61: "Y", 62: "Z", 63: ":", 64: ";", 65: "<", 66: "=", 67: ">", 68: "?", 69: "@", 70: "[", 71: "]", 72: "^", 73: "_", 74: "{", 75: "|", 76: "}"} 14 | 15 | func DecimalToAny(num int64, n int) string { 16 | newNumStr := "" 17 | var remainder int 18 | var remainderString string 19 | for num != 0 { 20 | remainder = int(num % int64(n)) 21 | if 76 > remainder && remainder > 9 { 22 | remainderString = tenToAny[remainder] 23 | } else { 24 | remainderString = strconv.Itoa(remainder) 25 | } 26 | newNumStr = remainderString + newNumStr 27 | num = num / int64(n) 28 | } 29 | return newNumStr 30 | } 31 | 32 | func GenerateRandNumber(length uint) uint { 33 | numberByteArray := [9]byte{1, 2, 3, 4, 5, 6, 7, 9} 34 | numberLength := len(numberByteArray) 35 | rand.Seed(time.Now().UnixNano()) 36 | 37 | var stringBuilder strings.Builder 38 | for i := 0; uint(i) < length; i++ { 39 | fmt.Fprintf(&stringBuilder, "%d", numberByteArray[rand.Intn(numberLength)]) 40 | } 41 | randomNumber, _ := strconv.ParseUint(stringBuilder.String(), 10, 0) 42 | return uint(randomNumber) 43 | } 44 | 45 | func Md5(str string) string { 46 | h := md5.New() 47 | h.Write([]byte(str)) 48 | return hex.EncodeToString(h.Sum(nil)) 49 | } 50 | 51 | func Md5Bytes(bytes []byte) string { 52 | h := md5.New() 53 | h.Write(bytes) 54 | return hex.EncodeToString(h.Sum(nil)) 55 | } 56 | 57 | // VersionCompare 对比两个版本,a > b = 1; a < b = -1; 58 | func VersionCompare(a string, b string) int { 59 | aSplit := strings.Split(a, ".") 60 | bSplit := strings.Split(b, ".") 61 | 62 | aLen := len(aSplit) 63 | bLen := len(bSplit) 64 | length := min(aLen, bLen) 65 | 66 | for i := 0; i < length; i++ { 67 | intA, _ := strconv.ParseInt(aSplit[i], 10, 32) 68 | intB, _ := strconv.ParseInt(bSplit[i], 10, 32) 69 | if intA > intB { 70 | return 1 71 | } else if intB > intA { 72 | return -1 73 | } 74 | } 75 | if aLen > bLen { 76 | return 1 77 | } else if bLen > aLen { 78 | return -1 79 | } 80 | return 0 81 | } 82 | 83 | func min(a, b int) int { 84 | if a < b { 85 | return a 86 | } 87 | return b 88 | } 89 | -------------------------------------------------------------------------------- /provider/link.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "errors" 5 | "github.com/PuerkitoBio/goquery" 6 | "kandaoni.com/anqicms/library" 7 | "kandaoni.com/anqicms/model" 8 | "strings" 9 | "time" 10 | ) 11 | 12 | func (w *Website) GetLinkList() ([]*model.Link, error) { 13 | var links []*model.Link 14 | db := w.DB 15 | err := db.Order("sort asc").Find(&links).Error 16 | if err != nil { 17 | return nil, err 18 | } 19 | 20 | return links, nil 21 | } 22 | 23 | func (w *Website) GetLinkById(id uint) (*model.Link, error) { 24 | var link model.Link 25 | if err := w.DB.Where("id = ?", id).First(&link).Error; err != nil { 26 | return nil, err 27 | } 28 | 29 | return &link, nil 30 | } 31 | 32 | func (w *Website) GetLinkByLink(link string) (*model.Link, error) { 33 | if link == "" { 34 | return nil, errors.New("link必填") 35 | } 36 | 37 | var friendLink model.Link 38 | var err error 39 | err = w.DB.Where("`link` = ?", link).First(&friendLink).Error 40 | if err != nil { 41 | // 增加兼容模式查找 42 | if strings.HasPrefix(link, "https") { 43 | link = strings.ReplaceAll(link, "https://", "http://") 44 | } else { 45 | link = strings.ReplaceAll(link, "http://", "https://") 46 | } 47 | err = w.DB.Where("`link` = ?", link).First(&friendLink).Error 48 | } 49 | 50 | if err != nil { 51 | return nil, err 52 | } 53 | 54 | return &friendLink, nil 55 | } 56 | 57 | func (w *Website) PluginLinkCheck(link *model.Link) (*model.Link, error) { 58 | remoteLink := link.BackLink 59 | if remoteLink == "" { 60 | remoteLink = link.Link 61 | } 62 | 63 | //获取内容 64 | resp, err := library.Request(remoteLink, nil) 65 | if err != nil { 66 | return nil, err 67 | } 68 | 69 | //检查内容 70 | htmlR := strings.NewReader(resp.Body) 71 | doc, err := goquery.NewDocumentFromReader(htmlR) 72 | if err != nil { 73 | return nil, err 74 | } 75 | 76 | myLink := link.MyLink 77 | if myLink == "" { 78 | myLink = w.System.BaseUrl 79 | } 80 | 81 | linkStatus := model.LinkStatusNotMatch 82 | //获取所有link 83 | aLinks := doc.Find("a") 84 | for i := range aLinks.Nodes { 85 | href, exists := aLinks.Eq(i).Attr("href") 86 | title := strings.TrimSpace(aLinks.Eq(i).Text()) 87 | rel, relExists := aLinks.Eq(i).Attr("rel") 88 | if exists { 89 | if href == myLink || href == myLink+"/" { 90 | linkStatus = model.LinkStatusOk 91 | if link.MyTitle != "" && title != link.MyTitle { 92 | linkStatus = model.LinkStatusNotTitle 93 | } 94 | if relExists && rel == "nofollow" { 95 | linkStatus = model.LinkStatusNofollow 96 | } 97 | 98 | break 99 | } 100 | } 101 | } 102 | 103 | link.CheckedTime = time.Now().Unix() 104 | link.Status = linkStatus 105 | link.Save(w.DB) 106 | 107 | return link, nil 108 | } 109 | -------------------------------------------------------------------------------- /tags/nextArchive.go: -------------------------------------------------------------------------------- 1 | package tags 2 | 3 | import ( 4 | "fmt" 5 | "github.com/flosch/pongo2/v6" 6 | "gorm.io/gorm" 7 | "kandaoni.com/anqicms/model" 8 | "kandaoni.com/anqicms/provider" 9 | ) 10 | 11 | type tagNextArchiveNode struct { 12 | name string 13 | args map[string]pongo2.IEvaluator 14 | wrapper *pongo2.NodeWrapper 15 | } 16 | 17 | func (node *tagNextArchiveNode) Execute(ctx *pongo2.ExecutionContext, writer pongo2.TemplateWriter) *pongo2.Error { 18 | currentSite, _ := ctx.Public["website"].(*provider.Website) 19 | if currentSite == nil || currentSite.DB == nil { 20 | return nil 21 | } 22 | args, err := parseArgs(node.args, ctx) 23 | if err != nil { 24 | return err 25 | } 26 | id := uint(0) 27 | 28 | archiveDetail, _ := ctx.Public["archive"].(*model.Archive) 29 | 30 | if args["id"] != nil { 31 | id = uint(args["id"].Integer()) 32 | archiveDetail, _ = currentSite.GetArchiveById(id) 33 | } 34 | 35 | if archiveDetail != nil { 36 | nextArchive, _ := currentSite.GetArchiveByFunc(func(tx *gorm.DB) *gorm.DB { 37 | return tx.Where("`module_id` = ? AND `category_id` = ?", archiveDetail.ModuleId, archiveDetail.CategoryId).Where("`id` > ?", archiveDetail.Id).Where("`status` = 1").Order("`id` ASC") 38 | }) 39 | ctx.Private[node.name] = nextArchive 40 | } 41 | //execute 42 | node.wrapper.Execute(ctx, writer) 43 | 44 | return nil 45 | } 46 | 47 | func TagNextArchiveParser(doc *pongo2.Parser, start *pongo2.Token, arguments *pongo2.Parser) (pongo2.INodeTag, *pongo2.Error) { 48 | tagNode := &tagNextArchiveNode{ 49 | args: make(map[string]pongo2.IEvaluator), 50 | } 51 | 52 | nameToken := arguments.MatchType(pongo2.TokenIdentifier) 53 | if nameToken == nil { 54 | return nil, arguments.Error("nextArchive-tag needs a accept name.", nil) 55 | } 56 | tagNode.name = nameToken.Val 57 | 58 | args, err := parseWith(arguments) 59 | if err != nil { 60 | return nil, err 61 | } 62 | tagNode.args = args 63 | 64 | for arguments.Remaining() > 0 { 65 | return nil, arguments.Error("Malformed nextArchive-tag arguments.", nil) 66 | } 67 | wrapper, endtagargs, err := doc.WrapUntilTag("endnextArchive") 68 | if err != nil { 69 | return nil, err 70 | } 71 | if endtagargs.Remaining() > 0 { 72 | endtagnameToken := endtagargs.MatchType(pongo2.TokenIdentifier) 73 | if endtagnameToken != nil { 74 | if endtagnameToken.Val != nameToken.Val { 75 | return nil, endtagargs.Error(fmt.Sprintf("Name for 'endnextArchive' must equal to 'nextArchive'-tag's name ('%s' != '%s').", 76 | nameToken.Val, endtagnameToken.Val), nil) 77 | } 78 | } 79 | 80 | if endtagnameToken == nil || endtagargs.Remaining() > 0 { 81 | return nil, endtagargs.Error("Either no or only one argument (identifier) allowed for 'endnextArchive'.", nil) 82 | } 83 | } 84 | tagNode.wrapper = wrapper 85 | 86 | return tagNode, nil 87 | } 88 | -------------------------------------------------------------------------------- /tags/prevArchive.go: -------------------------------------------------------------------------------- 1 | package tags 2 | 3 | import ( 4 | "fmt" 5 | "github.com/flosch/pongo2/v6" 6 | "gorm.io/gorm" 7 | "kandaoni.com/anqicms/model" 8 | "kandaoni.com/anqicms/provider" 9 | ) 10 | 11 | type tagPrevArchiveNode struct { 12 | name string 13 | args map[string]pongo2.IEvaluator 14 | wrapper *pongo2.NodeWrapper 15 | } 16 | 17 | func (node *tagPrevArchiveNode) Execute(ctx *pongo2.ExecutionContext, writer pongo2.TemplateWriter) *pongo2.Error { 18 | currentSite, _ := ctx.Public["website"].(*provider.Website) 19 | if currentSite == nil || currentSite.DB == nil { 20 | return nil 21 | } 22 | args, err := parseArgs(node.args, ctx) 23 | if err != nil { 24 | return err 25 | } 26 | id := uint(0) 27 | 28 | archiveDetail, _ := ctx.Public["archive"].(*model.Archive) 29 | 30 | if args["id"] != nil { 31 | id = uint(args["id"].Integer()) 32 | archiveDetail, _ = currentSite.GetArchiveById(id) 33 | } 34 | 35 | if archiveDetail != nil { 36 | prevArchive, _ := currentSite.GetArchiveByFunc(func(tx *gorm.DB) *gorm.DB { 37 | return tx.Where("`module_id` = ? AND `category_id` = ?", archiveDetail.ModuleId, archiveDetail.CategoryId).Where("`id` < ?", archiveDetail.Id).Where("`status` = 1").Order("`id` DESC") 38 | }) 39 | ctx.Private[node.name] = prevArchive 40 | } 41 | 42 | //execute 43 | node.wrapper.Execute(ctx, writer) 44 | 45 | return nil 46 | } 47 | 48 | func TagPrevArchiveParser(doc *pongo2.Parser, start *pongo2.Token, arguments *pongo2.Parser) (pongo2.INodeTag, *pongo2.Error) { 49 | tagNode := &tagPrevArchiveNode{ 50 | args: make(map[string]pongo2.IEvaluator), 51 | } 52 | 53 | nameToken := arguments.MatchType(pongo2.TokenIdentifier) 54 | if nameToken == nil { 55 | return nil, arguments.Error("prevArchive-tag needs a accept name.", nil) 56 | } 57 | tagNode.name = nameToken.Val 58 | 59 | args, err := parseWith(arguments) 60 | if err != nil { 61 | return nil, err 62 | } 63 | tagNode.args = args 64 | 65 | for arguments.Remaining() > 0 { 66 | return nil, arguments.Error("Malformed prevArchive-tag arguments.", nil) 67 | } 68 | wrapper, endtagargs, err := doc.WrapUntilTag("endprevArchive") 69 | if err != nil { 70 | return nil, err 71 | } 72 | if endtagargs.Remaining() > 0 { 73 | endtagnameToken := endtagargs.MatchType(pongo2.TokenIdentifier) 74 | if endtagnameToken != nil { 75 | if endtagnameToken.Val != nameToken.Val { 76 | return nil, endtagargs.Error(fmt.Sprintf("Name for 'endprevArchive' must equal to 'prevArchive'-tag's name ('%s' != '%s').", 77 | nameToken.Val, endtagnameToken.Val), nil) 78 | } 79 | } 80 | 81 | if endtagnameToken == nil || endtagargs.Remaining() > 0 { 82 | return nil, endtagargs.Error("Either no or only one argument (identifier) allowed for 'endprevArchive'.", nil) 83 | } 84 | } 85 | tagNode.wrapper = wrapper 86 | 87 | return tagNode, nil 88 | } 89 | -------------------------------------------------------------------------------- /template/default/product/detail.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block container %} 4 | {% include "partial/breadcrumb.html" %} 5 |
6 |
{% categoryDetail with name="Title" %}
7 | 8 |
9 | 10 |
11 |
12 | 13 |
14 |
15 | 16 |
17 |
18 |

{% archiveDetail with name="Title" %}

19 |

{% archiveDetail with name="Description" %}

20 |

售价:{% archiveDetail archivePrice with name="Price" %}{{archivePrice/100}}

21 |
22 | 咨询热线: 23 | 24 | {% contact with name="Cellphone" %} 25 | 26 |
27 |
28 |
29 | 30 |
31 | {% archiveDetail productContent with name="Content" %} 32 | {{productContent|safe}} 33 |
34 | 35 |
36 |

相关产品

37 |
    38 | {% archiveList products with type="related" limit="4" %} 39 | {% for inner in products %} 40 |
  • 41 |
    42 |
    43 | {{inner.Title}} 44 |
    45 |
    46 |

    {{inner.Title}}

    47 |
    48 |
    49 |
  • 50 | {% endfor %} 51 | {% endarchiveList %} 52 |
53 |
54 | 55 | 56 |
57 |

相关文章

58 |
    59 | {% archiveList articles with moduleId=1 type="list" limit="10" %} 60 | {% for inner in articles %} 61 |
  • 62 |

    {{inner.Title}}

    63 | {{stampToDate(inner.CreatedTime, "2006-01-02")}} 64 |
  • 65 | {% endfor %} 66 | {% endarchiveList %} 67 |
68 |
69 | 70 |
71 | {% include "partial/sidebar.html" %} 72 |
73 | 74 | 75 |
76 | 77 |
78 | {% endblock %} -------------------------------------------------------------------------------- /tags/system.go: -------------------------------------------------------------------------------- 1 | package tags 2 | 3 | import ( 4 | "fmt" 5 | "github.com/flosch/pongo2/v6" 6 | "kandaoni.com/anqicms/library" 7 | "kandaoni.com/anqicms/provider" 8 | "reflect" 9 | "strings" 10 | ) 11 | 12 | type tagSystemNode struct { 13 | name string 14 | args map[string]pongo2.IEvaluator 15 | } 16 | 17 | func (node *tagSystemNode) Execute(ctx *pongo2.ExecutionContext, writer pongo2.TemplateWriter) *pongo2.Error { 18 | currentSite, _ := ctx.Public["website"].(*provider.Website) 19 | if currentSite == nil || currentSite.DB == nil { 20 | return nil 21 | } 22 | args, err := parseArgs(node.args, ctx) 23 | if err != nil { 24 | return err 25 | } 26 | 27 | if args["site_id"] != nil { 28 | args["siteId"] = args["site_id"] 29 | } 30 | if args["siteId"] != nil { 31 | siteId := args["siteId"].Integer() 32 | currentSite = provider.GetWebsite(uint(siteId)) 33 | } 34 | 35 | fieldName := "" 36 | if args["name"] != nil { 37 | fieldName = args["name"].String() 38 | fieldName = library.Case2Camel(fieldName) 39 | } 40 | 41 | var content string 42 | 43 | // TemplateUrl 实时算出来, 它的计算方式是 /static/{TemplateName} 44 | if fieldName == "TemplateUrl" { 45 | content = fmt.Sprintf("%s/static/%s/", strings.TrimRight(currentSite.System.BaseUrl, "/"), currentSite.System.TemplateName) 46 | } else if currentSite.System.ExtraFields != nil { 47 | for i := range currentSite.System.ExtraFields { 48 | if currentSite.System.ExtraFields[i].Name == fieldName { 49 | content = currentSite.System.ExtraFields[i].Value 50 | break 51 | } 52 | } 53 | } 54 | if content == "" { 55 | v := reflect.ValueOf(currentSite.System) 56 | f := v.FieldByName(fieldName) 57 | 58 | content = fmt.Sprintf("%v", f) 59 | } 60 | 61 | // output 62 | if node.name == "" { 63 | writer.WriteString(content) 64 | } else { 65 | ctx.Private[node.name] = content 66 | } 67 | 68 | return nil 69 | } 70 | 71 | func TagSystemParser(doc *pongo2.Parser, start *pongo2.Token, arguments *pongo2.Parser) (pongo2.INodeTag, *pongo2.Error) { 72 | tagNode := &tagSystemNode{ 73 | args: make(map[string]pongo2.IEvaluator), 74 | } 75 | 76 | nameToken := arguments.MatchType(pongo2.TokenIdentifier) 77 | if nameToken == nil { 78 | return nil, arguments.Error("system-tag needs a accept name.", nil) 79 | } 80 | 81 | if nameToken.Val == "with" { 82 | //with 需要退回 83 | arguments.ConsumeN(-1) 84 | } else { 85 | tagNode.name = nameToken.Val 86 | } 87 | 88 | // After having parsed the name we're gonna parse the with options 89 | args, err := parseWith(arguments) 90 | if err != nil { 91 | return nil, err 92 | } 93 | tagNode.args = args 94 | 95 | for arguments.Remaining() > 0 { 96 | return nil, arguments.Error("Malformed system-tag arguments.", nil) 97 | } 98 | 99 | return tagNode, nil 100 | } 101 | -------------------------------------------------------------------------------- /tags/contact.go: -------------------------------------------------------------------------------- 1 | package tags 2 | 3 | import ( 4 | "fmt" 5 | "kandaoni.com/anqicms/provider" 6 | "reflect" 7 | "strings" 8 | 9 | "github.com/flosch/pongo2/v6" 10 | "kandaoni.com/anqicms/library" 11 | ) 12 | 13 | type tagContactNode struct { 14 | name string 15 | args map[string]pongo2.IEvaluator 16 | } 17 | 18 | func (node *tagContactNode) Execute(ctx *pongo2.ExecutionContext, writer pongo2.TemplateWriter) *pongo2.Error { 19 | currentSite, _ := ctx.Public["website"].(*provider.Website) 20 | if currentSite == nil || currentSite.DB == nil { 21 | return nil 22 | } 23 | args, err := parseArgs(node.args, ctx) 24 | if err != nil { 25 | return err 26 | } 27 | 28 | if args["site_id"] != nil { 29 | args["siteId"] = args["site_id"] 30 | } 31 | if args["siteId"] != nil { 32 | siteId := args["siteId"].Integer() 33 | currentSite = provider.GetWebsite(uint(siteId)) 34 | } 35 | 36 | fieldName := "" 37 | if args["name"] != nil { 38 | fieldName = args["name"].String() 39 | fieldName = library.Case2Camel(fieldName) 40 | } 41 | 42 | var content string 43 | 44 | if currentSite.Contact.ExtraFields != nil { 45 | for i := range currentSite.Contact.ExtraFields { 46 | if currentSite.Contact.ExtraFields[i].Name == fieldName { 47 | content = currentSite.Contact.ExtraFields[i].Value 48 | break 49 | } 50 | } 51 | } 52 | if content == "" { 53 | v := reflect.ValueOf(currentSite.Contact) 54 | f := v.FieldByName(fieldName) 55 | 56 | content = fmt.Sprintf("%v", f) 57 | } 58 | if fieldName == "Qrcode" { 59 | if !strings.HasPrefix(content, "http") && !strings.HasPrefix(content, "//") { 60 | content = currentSite.PluginStorage.StorageUrl + "/" + strings.TrimPrefix(content, "/") 61 | } 62 | } 63 | 64 | // output 65 | if node.name == "" { 66 | writer.WriteString(content) 67 | } else { 68 | ctx.Private[node.name] = content 69 | } 70 | 71 | return nil 72 | } 73 | 74 | func TagContactParser(doc *pongo2.Parser, start *pongo2.Token, arguments *pongo2.Parser) (pongo2.INodeTag, *pongo2.Error) { 75 | tagNode := &tagContactNode{ 76 | args: make(map[string]pongo2.IEvaluator), 77 | } 78 | 79 | nameToken := arguments.MatchType(pongo2.TokenIdentifier) 80 | if nameToken == nil { 81 | return nil, arguments.Error("contact-tag needs a accept name.", nil) 82 | } 83 | 84 | if nameToken.Val == "with" { 85 | //with 需要退回 86 | arguments.ConsumeN(-1) 87 | } else { 88 | tagNode.name = nameToken.Val 89 | } 90 | 91 | // After having parsed the name we're gonna parse the with options 92 | args, err := parseWith(arguments) 93 | if err != nil { 94 | return nil, err 95 | } 96 | tagNode.args = args 97 | 98 | for arguments.Remaining() > 0 { 99 | return nil, arguments.Error("Malformed contact-tag arguments.", nil) 100 | } 101 | 102 | return tagNode, nil 103 | } 104 | -------------------------------------------------------------------------------- /provider/guestbook.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "kandaoni.com/anqicms/config" 5 | "kandaoni.com/anqicms/model" 6 | ) 7 | 8 | func (w *Website) GetGuestbookList(keyword string, currentPage, pageSize int) ([]*model.Guestbook, int64, error) { 9 | var guestbooks []*model.Guestbook 10 | offset := (currentPage - 1) * pageSize 11 | var total int64 12 | 13 | builder := w.DB.Model(&model.Guestbook{}).Order("id desc") 14 | if keyword != "" { 15 | //模糊搜索 16 | builder = builder.Where("(`user_name` like ? OR `contact` like ?)", "%"+keyword+"%", "%"+keyword+"%") 17 | } 18 | 19 | err := builder.Count(&total).Limit(pageSize).Offset(offset).Find(&guestbooks).Error 20 | if err != nil { 21 | return nil, 0, err 22 | } 23 | 24 | return guestbooks, total, nil 25 | } 26 | 27 | func (w *Website) GetAllGuestbooks() ([]*model.Guestbook, error) { 28 | var guestbooks []*model.Guestbook 29 | err := w.DB.Model(&model.Guestbook{}).Order("id desc").Find(&guestbooks).Error 30 | if err != nil { 31 | return nil, err 32 | } 33 | 34 | return guestbooks, nil 35 | } 36 | 37 | func (w *Website) GetGuestbookById(id uint) (*model.Guestbook, error) { 38 | var guestbook model.Guestbook 39 | 40 | err := w.DB.Where("`id` = ?", id).First(&guestbook).Error 41 | if err != nil { 42 | return nil, err 43 | } 44 | 45 | return &guestbook, nil 46 | } 47 | 48 | func (w *Website) DeleteGuestbook(guestbook *model.Guestbook) error { 49 | err := w.DB.Delete(guestbook).Error 50 | if err != nil { 51 | return err 52 | } 53 | 54 | return nil 55 | } 56 | 57 | func (w *Website) GetGuestbookFields() []*config.CustomField { 58 | //这里有默认的设置 59 | defaultFields := []*config.CustomField{ 60 | { 61 | Name: w.Lang("用户名"), 62 | FieldName: "user_name", 63 | Type: "text", 64 | Required: true, 65 | IsSystem: true, 66 | }, 67 | { 68 | Name: w.Lang("联系电话"), 69 | FieldName: "contact", 70 | Type: "text", 71 | Required: false, 72 | IsSystem: true, 73 | }, 74 | { 75 | Name: w.Lang("Email"), 76 | FieldName: "email", 77 | Type: "text", 78 | Required: false, 79 | IsSystem: false, 80 | }, 81 | { 82 | Name: w.Lang("WhatsApp"), 83 | FieldName: "whatsapp", 84 | Type: "text", 85 | Required: false, 86 | IsSystem: false, 87 | }, 88 | { 89 | Name: w.Lang("留言内容"), 90 | FieldName: "content", 91 | Type: "textarea", 92 | Required: false, 93 | IsSystem: true, 94 | }, 95 | } 96 | 97 | exists := false 98 | for _, v := range w.PluginGuestbook.Fields { 99 | if v.IsSystem || v.FieldName == "user_name" { 100 | exists = true 101 | break 102 | } 103 | } 104 | var fields []*config.CustomField 105 | if exists { 106 | fields = w.PluginGuestbook.Fields 107 | } else { 108 | fields = append(defaultFields, w.PluginGuestbook.Fields...) 109 | } 110 | 111 | return fields 112 | } 113 | -------------------------------------------------------------------------------- /model/wechat.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type UserWechat struct { 4 | Model 5 | UserId uint `json:"user_id" gorm:"column:user_id;type:int(10) unsigned not null;default:0;index"` 6 | Nickname string `json:"nickname" gorm:"column:nickname;type:varchar(64) not null;default:''"` 7 | AvatarURL string `json:"avatar_url" gorm:"column:avatar_url;type:varchar(255) not null;default:''"` 8 | Gender int `json:"gender" gorm:"column:gender;type:int(10) not null;default:0"` 9 | Openid string `json:"openid" gorm:"column:openid;type:varchar(128) not null;default:'';index"` 10 | UnionId string `json:"union_id" gorm:"column:union_id;type:varchar(128) not null;default:'';index"` 11 | Platform string `json:"platform" gorm:"column:platform;type:varchar(32) not null;default:''"` 12 | Status int `json:"status" gorm:"column:status;type:tinyint(1) not null;default:0"` 13 | } 14 | 15 | type WeappQrcode struct { 16 | Model 17 | UserId uint `json:"user_id" gorm:"column:user_id;type:int(10) unsigned not null;default:0;index"` 18 | Path string `json:"path" gorm:"column:path;type:varchar(190) not null;default:'';index:idx_path"` 19 | CodeUrl string `json:"code_url" gorm:"column:code_url;type:varchar(255) not null;default:''"` 20 | } 21 | 22 | type SubscribedUser struct { 23 | Model 24 | UserId uint `json:"user_id" gorm:"column:user_id;type:int(10) unsigned not null;default:0;index"` 25 | Openid string `json:"openid" gorm:"column:openid;type:varchar(128) not null;default:'';index:idx_openid_template_id"` 26 | TemplateId string `json:"template_id" gorm:"column:template_id;type:varchar(64) not null;default:'';index:idx_openid_template_id"` 27 | } 28 | 29 | type WechatMessage struct { 30 | Model 31 | Openid string `json:"openid" gorm:"column:openid;type:varchar(128) not null;default:''"` 32 | Content string `json:"content" gorm:"column:content;type:varchar(255) not null;default:''"` 33 | Reply string `json:"reply" gorm:"column:reply;type:varchar(255) not null;default:''"` 34 | ReplyTime int64 `json:"reply_time" gorm:"column:reply_time;type:int(10) not null;default:0"` 35 | } 36 | 37 | type WechatReplyRule struct { 38 | Model 39 | Keyword string `json:"keyword" gorm:"column:keyword;type:varchar(128) not null;default:'';index"` 40 | Content string `json:"content" gorm:"column:content;type:text default null;"` 41 | IsDefault int `json:"is_default" gorm:"column:is_default;type:tinyint(1) not null;default:0"` 42 | } 43 | 44 | type WechatMenu struct { 45 | Model 46 | ParentId uint `json:"parent_id" gorm:"column:parent_id;type:int(10) not null;default:0"` 47 | Name string `json:"name" gorm:"column:name;type:varchar(20) not null;default:''"` 48 | Type string `json:"type" gorm:"column:type;type:varchar(20) not null;default:''"` 49 | Value string `json:"value" gorm:"column:value;type:varchar(255) not null;default:''"` 50 | Sort uint `json:"sort" gorm:"column:sort;type:int(10) not null;default:10;index"` 51 | 52 | Children []*WechatMenu `json:"children,omitempty" gorm:"-"` 53 | } 54 | -------------------------------------------------------------------------------- /template/default/page/contact.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block container %} 4 | {% include "partial/breadcrumb.html" %} 5 | 6 |
7 |

联系我们

8 |
9 |
10 |
11 |
12 |

{% system with name="SiteName" %}

13 |

{% contact with name="UserName" %}

14 |

{% contact with name="Cellphone" %}

15 |

{% contact with name="Email" %}

16 |

{% contact with name="Address" %}

17 |
18 |
19 |

扫码加微信

20 | {% system with name='SiteName' %} 21 |
22 |
23 |
24 |

给我们留言

25 |
26 | {% guestbook fields %} 27 | {% for item in fields %} 28 | {% if item.Type == "text" || item.Type == "number" %} 29 | 31 | {% elif item.Type == "textarea" %} 32 | 34 | {% elif item.Type == "radio" %} 35 | {%- for val in item.Items %} 36 | 37 | {%- endfor %} 38 | {% elif item.Type == "checkbox" %} 39 | {%- for val in item.Items %} 40 | 41 | {%- endfor %} 42 | {% elif item.Type == "select" %} 43 | 48 | {% elif item.Type == "image" %} 49 | 51 | {% endif %} 52 | {% endfor %} 53 | {% endguestbook %} 54 | 55 |
56 |
57 |
58 | {% include "partial/sidebar.html" %} 59 |
60 |
61 | 62 | {% endblock %} -------------------------------------------------------------------------------- /tags/categoryDetail.go: -------------------------------------------------------------------------------- 1 | package tags 2 | 3 | import ( 4 | "fmt" 5 | "github.com/flosch/pongo2/v6" 6 | "kandaoni.com/anqicms/library" 7 | "kandaoni.com/anqicms/model" 8 | "kandaoni.com/anqicms/provider" 9 | "reflect" 10 | ) 11 | 12 | type tagCategoryDetailNode struct { 13 | args map[string]pongo2.IEvaluator 14 | name string 15 | } 16 | 17 | func (node *tagCategoryDetailNode) Execute(ctx *pongo2.ExecutionContext, writer pongo2.TemplateWriter) *pongo2.Error { 18 | currentSite, _ := ctx.Public["website"].(*provider.Website) 19 | if currentSite == nil || currentSite.DB == nil { 20 | return nil 21 | } 22 | args, err := parseArgs(node.args, ctx) 23 | if err != nil { 24 | return err 25 | } 26 | id := uint(0) 27 | 28 | if args["site_id"] != nil { 29 | args["siteId"] = args["site_id"] 30 | } 31 | if args["siteId"] != nil { 32 | siteId := args["siteId"].Integer() 33 | currentSite = provider.GetWebsite(uint(siteId)) 34 | } 35 | 36 | fieldName := "" 37 | if args["name"] != nil { 38 | fieldName = args["name"].String() 39 | fieldName = library.Case2Camel(fieldName) 40 | } 41 | 42 | categoryDetail, _ := ctx.Public["category"].(*model.Category) 43 | archiveDetail, ok := ctx.Public["archive"].(*model.Archive) 44 | if ok && archiveDetail != nil { 45 | categoryDetail = currentSite.GetCategoryFromCache(archiveDetail.CategoryId) 46 | } 47 | 48 | if args["id"] != nil { 49 | id = uint(args["id"].Integer()) 50 | categoryDetail = currentSite.GetCategoryFromCache(id) 51 | } 52 | 53 | if categoryDetail != nil { 54 | categoryDetail.Link = currentSite.GetUrl("category", categoryDetail, 0) 55 | 56 | v := reflect.ValueOf(*categoryDetail) 57 | 58 | f := v.FieldByName(fieldName) 59 | 60 | content := fmt.Sprintf("%v", f) 61 | if content == "" && fieldName == "SeoTitle" { 62 | content = categoryDetail.Title 63 | } 64 | // output 65 | if node.name == "" { 66 | writer.WriteString(content) 67 | } else { 68 | if fieldName == "Images" { 69 | ctx.Private[node.name] = categoryDetail.Images 70 | } else { 71 | ctx.Private[node.name] = content 72 | } 73 | } 74 | } 75 | 76 | return nil 77 | } 78 | 79 | func TagCategoryDetailParser(doc *pongo2.Parser, start *pongo2.Token, arguments *pongo2.Parser) (pongo2.INodeTag, *pongo2.Error) { 80 | tagNode := &tagCategoryDetailNode{ 81 | args: make(map[string]pongo2.IEvaluator), 82 | } 83 | 84 | nameToken := arguments.MatchType(pongo2.TokenIdentifier) 85 | if nameToken == nil { 86 | return nil, arguments.Error("categoryDetail-tag needs a accept name.", nil) 87 | } 88 | 89 | if nameToken.Val == "with" { 90 | //with 需要退回 91 | arguments.ConsumeN(-1) 92 | } else { 93 | tagNode.name = nameToken.Val 94 | } 95 | 96 | args, err := parseWith(arguments) 97 | if err != nil { 98 | return nil, err 99 | } 100 | tagNode.args = args 101 | 102 | for arguments.Remaining() > 0 { 103 | return nil, arguments.Error("Malformed categoryDetail-tag arguments.", nil) 104 | } 105 | 106 | return tagNode, nil 107 | } 108 | -------------------------------------------------------------------------------- /controller/manageController/pluginTag.go: -------------------------------------------------------------------------------- 1 | package manageController 2 | 3 | import ( 4 | "fmt" 5 | "github.com/kataras/iris/v12" 6 | "kandaoni.com/anqicms/config" 7 | "kandaoni.com/anqicms/provider" 8 | "kandaoni.com/anqicms/request" 9 | ) 10 | 11 | func PluginTagList(ctx iris.Context) { 12 | currentSite := provider.CurrentSite(ctx) 13 | title := ctx.URLParam("title") 14 | currentPage := ctx.URLParamIntDefault("current", 1) 15 | pageSize := ctx.URLParamIntDefault("pageSize", 20) 16 | tags, total, err := currentSite.GetTagList(0, title, "", currentPage, pageSize, 0) 17 | if err != nil { 18 | ctx.JSON(iris.Map{ 19 | "code": config.StatusFailed, 20 | "msg": err.Error(), 21 | }) 22 | return 23 | } 24 | 25 | // 生成链接 26 | for i := range tags { 27 | tags[i].Link = currentSite.GetUrl("tag", tags[i], 0) 28 | } 29 | 30 | ctx.JSON(iris.Map{ 31 | "code": config.StatusOK, 32 | "msg": "", 33 | "total": total, 34 | "data": tags, 35 | }) 36 | } 37 | 38 | func PluginTagDetail(ctx iris.Context) { 39 | currentSite := provider.CurrentSite(ctx) 40 | id := ctx.Params().GetUintDefault("id", 0) 41 | 42 | tag, err := currentSite.GetTagById(id) 43 | if err != nil { 44 | ctx.JSON(iris.Map{ 45 | "code": config.StatusFailed, 46 | "msg": err.Error(), 47 | }) 48 | return 49 | } 50 | 51 | ctx.JSON(iris.Map{ 52 | "code": config.StatusOK, 53 | "msg": "", 54 | "data": tag, 55 | }) 56 | } 57 | 58 | func PluginTagDetailForm(ctx iris.Context) { 59 | currentSite := provider.CurrentSite(ctx) 60 | var req request.PluginTag 61 | if err := ctx.ReadJSON(&req); err != nil { 62 | ctx.JSON(iris.Map{ 63 | "code": config.StatusFailed, 64 | "msg": err.Error(), 65 | }) 66 | return 67 | } 68 | 69 | tag, err := currentSite.SaveTag(&req) 70 | if err != nil { 71 | ctx.JSON(iris.Map{ 72 | "code": config.StatusFailed, 73 | "msg": err.Error(), 74 | }) 75 | return 76 | } 77 | 78 | currentSite.AddAdminLog(ctx, fmt.Sprintf("更新文档标签:%d => %s", tag.Id, tag.Title)) 79 | 80 | ctx.JSON(iris.Map{ 81 | "code": config.StatusOK, 82 | "msg": "保存成功", 83 | "data": tag, 84 | }) 85 | } 86 | 87 | func PluginTagDelete(ctx iris.Context) { 88 | currentSite := provider.CurrentSite(ctx) 89 | var req request.PluginTag 90 | if err := ctx.ReadJSON(&req); err != nil { 91 | ctx.JSON(iris.Map{ 92 | "code": config.StatusFailed, 93 | "msg": err.Error(), 94 | }) 95 | return 96 | } 97 | tag, err := currentSite.GetTagById(req.Id) 98 | if err != nil { 99 | ctx.JSON(iris.Map{ 100 | "code": config.StatusFailed, 101 | "msg": err.Error(), 102 | }) 103 | return 104 | } 105 | 106 | err = currentSite.DeleteTag(tag.Id) 107 | if err != nil { 108 | ctx.JSON(iris.Map{ 109 | "code": config.StatusFailed, 110 | "msg": err.Error(), 111 | }) 112 | return 113 | } 114 | 115 | currentSite.AddAdminLog(ctx, fmt.Sprintf("删除文档标签:%d => %s", tag.Id, tag.Title)) 116 | 117 | ctx.JSON(iris.Map{ 118 | "code": config.StatusOK, 119 | "msg": "标签已删除", 120 | }) 121 | } 122 | --------------------------------------------------------------------------------