├── .github └── workflows │ └── main.yaml ├── .gitignore ├── CHANGELOG.md ├── CLA.md ├── LICENSE ├── Logger.go ├── README.md ├── README.zh-CN.md ├── const.go ├── eth_test.go ├── funcMap.go ├── funcmap_test.go ├── go.mod ├── go.sum ├── goldmark_test.go ├── gpressEntity.go ├── gpressdatadir ├── dict.zip ├── fts5 │ ├── libsimple.dll │ ├── libsimple.dylib │ ├── libsimple.so │ └── libsimple.so-aarch64 ├── gpress.sql ├── install_config.json ├── libgcc_s_seh-1.dll ├── locales │ ├── en-US.json │ └── zh-CN.json ├── post │ └── 01-nginx-config.md ├── public │ ├── doc │ │ ├── image │ │ │ ├── 000.png │ │ │ ├── 001.png │ │ │ ├── 002.png │ │ │ ├── 003.png │ │ │ ├── 004.png │ │ │ ├── 005.png │ │ │ ├── 006.png │ │ │ ├── 007.png │ │ │ ├── 008.png │ │ │ ├── 009.png │ │ │ ├── 010.png │ │ │ ├── 011.png │ │ │ ├── 012.png │ │ │ ├── 013.png │ │ │ ├── 014.png │ │ │ ├── 015.png │ │ │ ├── 016.png │ │ │ ├── 017.png │ │ │ ├── 018.png │ │ │ ├── 019.png │ │ │ └── 020.png │ │ └── index.md │ ├── favicon.png │ ├── gongan.png │ ├── gpress-logo.png │ ├── gpress-logo1.png │ ├── index.png │ ├── logo.png │ ├── mqtt3.1.1.html │ ├── search-config.json │ ├── upload │ │ └── upload.txt │ └── wallet │ │ ├── TrustWallet.png │ │ ├── bitkeep.png │ │ ├── metamask.png │ │ ├── okx.png │ │ ├── tokenproket.png │ │ └── x-pocket.png ├── statichtml │ └── readme.html ├── template │ ├── admin │ │ ├── bodyend.html │ │ ├── bodystart.html │ │ ├── category │ │ │ ├── save.html │ │ │ └── update.html │ │ ├── chainlogin.html │ │ ├── config │ │ │ └── update.html │ │ ├── content │ │ │ ├── list.html │ │ │ ├── save.html │ │ │ └── update.html │ │ ├── css │ │ │ └── tree.css │ │ ├── error.html │ │ ├── header.html │ │ ├── index.html │ │ ├── install.html │ │ ├── js │ │ │ ├── cherry-markdown │ │ │ │ ├── cherry-markdown.min.css │ │ │ │ ├── cherry-markdown.min.js │ │ │ │ └── fonts │ │ │ │ │ ├── ch-icon.eot │ │ │ │ │ ├── ch-icon.svg │ │ │ │ │ ├── ch-icon.ttf │ │ │ │ │ ├── ch-icon.woff │ │ │ │ │ └── ch-icon.woff2 │ │ │ ├── layui │ │ │ │ ├── css │ │ │ │ │ ├── layui.css │ │ │ │ │ └── layui.css.map │ │ │ │ ├── font │ │ │ │ │ ├── iconfont.eot │ │ │ │ │ ├── iconfont.svg │ │ │ │ │ ├── iconfont.ttf │ │ │ │ │ ├── iconfont.woff │ │ │ │ │ └── iconfont.woff2 │ │ │ │ ├── layui.js │ │ │ │ ├── layui.js.map │ │ │ │ ├── test.html │ │ │ │ ├── 免责声明.html │ │ │ │ └── 官方文档.html │ │ │ ├── pinyin │ │ │ │ ├── pinyin-pro.js │ │ │ │ └── 说明.txt │ │ │ ├── sha3.min.js │ │ │ └── wangeditor │ │ │ │ ├── wangeditor5.min.css │ │ │ │ └── wangeditor5.min.js │ │ ├── login.html │ │ ├── site │ │ │ └── update.html │ │ ├── themeTemplate │ │ │ └── list.html │ │ └── user │ │ │ └── update.html │ └── theme │ │ └── default │ │ ├── bodyend.html │ │ ├── bodystart.html │ │ ├── category-single.html │ │ ├── category.html │ │ ├── content.html │ │ ├── css │ │ ├── even.css │ │ └── fonts │ │ │ ├── chancery │ │ │ ├── apple-chancery-webfont.eot │ │ │ ├── apple-chancery-webfont.svg │ │ │ ├── apple-chancery-webfont.ttf │ │ │ ├── apple-chancery-webfont.woff │ │ │ └── apple-chancery-webfont.woff2 │ │ │ └── iconfont │ │ │ ├── iconfont.eot │ │ │ ├── iconfont.svg │ │ │ ├── iconfont.ttf │ │ │ └── iconfont.woff │ │ ├── error.html │ │ ├── index.html │ │ ├── js │ │ ├── classList.min.js │ │ ├── even.js │ │ ├── html5shiv.min.js │ │ ├── jquery.fancybox.min.css │ │ ├── jquery.fancybox.min.js │ │ ├── jquery.min.js │ │ ├── manifest.json │ │ ├── mermaid.min.js │ │ ├── respond.min.js │ │ └── slideout.min.js │ │ └── tag.html └── wasm │ ├── add.go │ ├── add.wasm │ └── readme.md ├── hugo_test.go ├── main.go ├── routeAdmin.go ├── routeWeb.go ├── serviceCategory.go ├── serviceConfig.go ├── serviceUser.go ├── utilBase58.go ├── utilCaptcha.go ├── utilGenStaticFile.go ├── utilGoldmark.go ├── utilHertz.go ├── utilJWT.go ├── utilLocale.go ├── utilSQLite.go ├── utilSignature.go ├── utilSignature_test.go ├── utilZIP.go ├── utilzip_test.go └── wasm_test.go /.github/workflows/main.yaml: -------------------------------------------------------------------------------- 1 | name: gpress-darwin 2 | ### 手动触发,目前Linux glibc版本不兼容,需要手动编译.两层压缩包 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | strategy: 10 | matrix: 11 | #goos: [windows, linux, darwin] 12 | goos: [windows, darwin] 13 | goarch: [amd64, arm64] 14 | exclude: 15 | - goarch: arm64 16 | goos: windows 17 | fail-fast: false 18 | env: 19 | GOOS: ${{ matrix.goos }} 20 | GOARCH: ${{ matrix.goarch }} 21 | 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v4 25 | with: 26 | submodules: recursive 27 | 28 | - name: Build 29 | uses: crazy-max/ghaction-xgo@v3 30 | with: 31 | xgo_version: latest 32 | go_version: 1.21 33 | v: true 34 | x: false 35 | race: false 36 | prefix: gpress 37 | dest: build 38 | ldflags: -w -s 39 | tags: fts5 40 | targets: ${{matrix.goos}}/${{matrix.goarch}} 41 | buildmode: default 42 | trimpath: true 43 | 44 | - name: Package 45 | run: | 46 | mkdir -p ./build/temp/gpress/ 47 | cd ./gpressdatadir && unzip dict.zip && rm -rf ./dict.zip && cd .. 48 | 49 | if [ "${{ matrix.goos }}" == "darwin" ]; then 50 | if [ "${{ matrix.arch }}" == "arm64" ]; then 51 | mv ./gpressdatadir/fts5/libsimple.dylib ./gpressdatadir/fts5/temp.dylib 52 | else 53 | mv ./gpressdatadir/fts5/libsimple.dylib-amd64 ./gpressdatadir/fts5/temp.dylib 54 | fi 55 | rm -rf ./gpressdatadir/fts5/libsimple* 56 | mv ./gpressdatadir/fts5/temp.dylib ./gpressdatadir/fts5/libsimple.dylib 57 | mv ./build/gpress-${{matrix.goos}}-${{matrix.goarch}} ./build/temp/gpress/gpress 58 | fi 59 | 60 | if [ "${{ matrix.goos }}" == "linux" ]; then 61 | if [ "${{ matrix.arch }}" == "arm64" ]; then 62 | mv ./gpressdatadir/fts5/libsimple.so-aarch64 ./gpressdatadir/fts5/temp.so 63 | else 64 | mv ./gpressdatadir/fts5/libsimple.so ./gpressdatadir/fts5/temp.so 65 | fi 66 | rm -rf ./gpressdatadir/fts5/libsimple* 67 | mv ./gpressdatadir/fts5/temp.so ./gpressdatadir/fts5/libsimple.so 68 | mv ./build/gpress-${{matrix.goos}}-${{matrix.goarch}} ./build/temp/gpress/gpress 69 | fi 70 | 71 | if [ "${{ matrix.goos }}" == "windows" ]; then 72 | mv ./gpressdatadir/fts5/libsimple.dll ./gpressdatadir/fts5/temp.dll 73 | rm -rf ./gpressdatadir/fts5/libsimple* 74 | mv ./gpressdatadir/fts5/temp.dll ./gpressdatadir/fts5/libsimple.dll 75 | mv ./build/gpress-${{matrix.goos}}-${{matrix.goarch}}.exe ./build/temp/gpress/gpress.exe 76 | fi 77 | 78 | mv ./gpressdatadir ./build/temp/gpress/ 79 | cd ./build/temp && zip -r gpress.zip ./gpress 80 | 81 | - name: Upload 82 | uses: actions/upload-artifact@v4 83 | with: 84 | name: gpress-${{matrix.goos}}-${{matrix.goarch}} 85 | path: ${{ github.workspace }}/build/temp/gpress.zip 86 | env: 87 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 88 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # idea ignore 2 | .idea/ 3 | *.ipr 4 | *.iml 5 | *.iws 6 | upload/ 7 | install_config.json 8 | search-data.json 9 | install.html 10 | install.html.bak 11 | statichtml 12 | testTable/ 13 | dict/ 14 | natsdata/ 15 | *.db 16 | *.db.fail 17 | .vscode/ 18 | 19 | *.swp 20 | 21 | # temp ignore 22 | *.hertz.gz 23 | *.gz 24 | *.log 25 | *.cache 26 | *.diff 27 | *.exe 28 | *.exe~ 29 | *.patch 30 | *.tmp 31 | *debug.test 32 | debug.test 33 | __debug_bin 34 | 35 | # system ignore 36 | .DS_Store 37 | Thumbs.db 38 | 39 | # project 40 | *.cert 41 | *.key 42 | .test 43 | iprepo.txt 44 | 45 | 46 | _output -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | v1.1.2 2 | - 完善文档,注释 3 | 4 | v1.1.1 5 | - 感谢@soldier_of_love,拼音生成内容的路径标识 6 | - 去掉路径里的空格 7 | - 完善文档,注释 8 | 9 | v1.1.0 10 | - 增加自定义标签,支持解析视频/音频标签: !video[描述](视频地址) , !audio[描述](音频地址) 11 | - 完善文档,注释 12 | 13 | v1.0.9 14 | - 升级FTS5分词组件 15 | - 依赖Go 1.24 16 | - 增强windows系统的兼容性 17 | - 优化上传文件名 18 | - 优化加载fts5分词扩展 19 | - 完善文档,注释 20 | 21 | v1.0.8 22 | - 后台管理支持多语言 23 | - 上传文件单独目录隔离,避免互相影响 24 | - 后台管理页面增加 更新SQL 功能 25 | - 内容表增加 txID 字段,记录上链交易的Hash;配置表增加 locale 字段,设置语言 26 | - 修改错误的categorys拼写 27 | - 主题管理过滤掉.gz压缩文件 28 | - 动态增加路由映射,去掉routeCategoryMap的处理逻辑 29 | - 完善文档,注释 30 | 31 | v1.0.7 32 | - URI作为导航和内容的ID,例如:/web/是导航ID,/web/nginx-use-hsts是内容ID 33 | - 去掉comCode,moduleID字段,增加signature,signAddress和signChain字段 34 | - 统一映射静态文件,兼容项目前缀路径 35 | - 完善文档,注释 36 | 37 | v1.0.6 38 | - 使用本地的js资源文件 39 | - Nginx 1.26+ 不需要再进行302重定向到目录下的index.html,gzip_static也会生效 40 | - 根据Cookie和User-Agent请求头,为pc,wap,weixin配置不同的主题,并支持HarmonyOS 41 | - site表中siteThemeWEIXIN字段名修改为themeWX 42 | - 修复上传文件路径异常的问题 43 | - 调整后台管理功能实现 44 | - 完善文档,注释 45 | 46 | v1.0.5 47 | - 修复修改项目前缀造成访问异常的bug 48 | - 感谢 @soldier_of_love 的[pr](https://gitee.com/gpress/gpress/pulls/8),eth验签从go-ethereum库切换到dcrd库 49 | - 感谢 @soldier_of_love 的[pr](https://gitee.com/gpress/gpress/pulls/7),优化静态路由配置,不用动态在更新路由 50 | - 感谢 @soldier_of_love 的[pr](https://gitee.com/gpress/gpress/pulls/6),解压失败的情况 合理删除目录 51 | - 感谢 @soldier_of_love 的[pr](https://gitee.com/gpress/gpress/pulls/5),解决sliceCategory2Tree三层及以上层数异常问题 52 | - 感谢 @soldier_of_love 的[pr](https://gitee.com/gpress/gpress/pulls/4),解决AES-CBC模式加解密 部分文本异常 53 | - 感谢 @soldier_of_love 的[pr](https://gitee.com/gpress/gpress/pulls/3),解决非法压缩包上传未删除问题 54 | - 感谢 @soldier_of_love 的[pr](https://gitee.com/gpress/gpress/pulls/2),解决初始化DB副作用 55 | - 完善文档,注释 56 | 57 | v1.0.4 58 | - 统一类型转换方法 convertType, 方便扩展 59 | - 感谢 @lifj22 的[issue](https://gitee.com/gpress/gpress/issues/I9J1RH),导航模板category开头,内容模板content开头 60 | - status字段增加置顶(2),原私密(2)修改为私密(3) 61 | - 完善文档,注释 62 | 63 | v1.0.3 64 | - 栏目页自定义keyword,description,静态化生成sitemap.xml 65 | - 增加seq标签,用于循环数字 66 | - 迁移WordPress主题:[generatepress](https://gitee.com/gpress/wp-generatepress)、[astra](https://wpastra.com) 67 | - 完善文档,注释 68 | 69 | v1.0.2 70 | - templateID 更名为 templateFile 71 | - 修改示例数据 72 | - 优化后台管理页面 73 | - 完善文档,注释 74 | 75 | v1.0.1 76 | - 主题模板支持上传和文件修改 77 | - 增加GitHub Actions文件,用于编译MacOS版本,感谢 @CDCDDCDC 编译libsimple.dylib 78 | - 部署阿里云计算巢服务,方便云平台使用 79 | - 完善文档,注释 80 | 81 | v1.0.0 82 | - 初始化版本 -------------------------------------------------------------------------------- /CLA.md: -------------------------------------------------------------------------------- 1 | 2 | **gpress贡献者许可协议** 3 | 4 | 感谢您对gpress社区(“社区”或者“我们”)拥有及/或管理的开源项目(“项目”)的关注,请完整、仔细、充分阅读本《贡献者许可协议》(“协议”)后,在对协议项下条款均无异议的前提下,签署本协议。 5 | 6 | **提交贡献即认为签署了本协议,若对本协议有异议,请勿提交贡献。** 7 | 8 | 为确保社区及项目合法合规,同时也避免侵犯任何人士的合法权益,我们希望确保: 9 | (1)您对您所提交的贡献具有完整充分的合法权利; 10 | (2)您的提交行为合法且不侵权。 11 | 12 | 您在提交贡献后,仍然对贡献享有应有的合法权利。您不会因为提交贡献而承担任何未约定的义务。 13 | 14 | **一、定义** 15 | 为本协议之目的,除非上下文另有说明,本协议中用语分别具有本条所指含义: 16 | 1.1 “中国”指中华人民共和国。 17 | 1.2 “法律”包括但不限于任何适用的法典、法律、法规、规章、司法解释及规范性文件,包括但不限于《中华人民共和国著作权法》(“著作权法”)、《中华人民共和国计算机软件保护条例》、《中华人民共和国专利法》(“专利法”)。如无特别说明或者其他约定,本协议项下争议优先适用中国法律。 18 | 1.3 “社区”或“我们”指gpress社区。 19 | 1.4 “项目”指社区拥有及/或管理的项目,包括但不限于软件、软件所包含的程序、代码、文字、图片、图形、文档或者其他信息。于本协议签署之日,项目托管于Gitee [https://gitee.com/gpress](https://gitee.com/gpress),托管地址今后可能随实际情况发生变化,具体访问地址以社区实时公布为准。 20 | 1.5 “贡献者”指签署本协议并向社区提交贡献的任何自然人、法人、其他组织以及法律规定的其他主体。 21 | 1.6 “贡献”指由贡献者根据本协议向社区提交的任何程序、代码、文字、图片、图形、文档或者其他作品。 22 | 1.7 “提交”指将“贡献”通过电子邮件或者其他形式发送给“项目”或者“社区”,其他形式包括但不限于讨论、在“项目”相关的电子邮件列表上的交流、在“项目”相关的源代码修订控制、问题跟踪等系统中的操作,但不包括以书面方式明确标记为“非贡献”的交流。 23 | 1.8 “著作权”指《著作权法》规定的各项权利;在任何不适用中国法律的争议中,应指包括《保护文学艺术作品伯尔尼公约》、《世界知识产权组织版权公约》、《与贸易有关的知识产权协定》等国际公约及争议准据法项下规定在内的全部可适用权利。 24 | 1.9 “专利权”指《专利法》规定的各项权利;在任何不适用中国法律的争议中,应指包括《保护工业产权巴黎公约》、《与贸易有关的知识产权协定》等国际公约及争议准据法项下规定在内的全部可适用权利。 25 | 1.10 “合法权利”包括但不限于著作权、版权、专利权、商标权、隐私权以及法律规定的其他权利。 26 | 1.11 “作品”指《著作权法》规定的在文学、艺术和科学领域内具有独创性并能以一定形式表现的智力成果以及《专利法》规定的产品、方法或者其改进所提出的新的技术方案、对产品的形状、构造或者其结合所提出的适于实用的新的技术方案、对产品的整体或者局部的形状、图案或者其结合以及色彩与形状、图案的结合所作出的富有美感并适于工业应用的新设计。在任何不适用中国法律的争议中,应指与著作权及专利权相对应的全部可适用标的。 27 | 1.12 “原创作品”指贡献者作为作者创作、开发的作品或者发明创造。 28 | 1.13 “职务作品”指贡献者作为作者创作、开发但依法属于《著作权法》规定的职务作品的作品或者《专利法》规定的职务发明创造。 29 | 1.14 “合作作品”指贡献者作为作者之一创作、开发但依法属于《著作权法》规定的合作作品的作品或者《专利法》规定的合作完成的发明创造。 30 | 1.15 “委托作品”指贡献者作为作者创作、开发但依法属于《著作权法》规定的委托作品的作品或者《专利法》规定的委托完成的发明创造。 31 | 1.16 “非原创作品”指贡献者以外的人创作、开发的作品。为免歧义,贡献者作为作者之一参与的合作作品属于原创作品,但作为贡献提交时,贡献者应取得合作作者的合法授权。 32 | 33 | **二、著作权许可授权** 34 | 2.1 贡献一经提交,即视为贡献者免费且不可撤销地授予社区、项目及其全部或者部分的接收者永久、非排他、全球性的著作权许可,修改、复制、发行、传播、改编、注释、整理、汇编、开发、展示、分发、再许可或以其他合法方式使用贡献及使用贡献产生的成果。 35 | 36 | **三、专利权许可授权** 37 | 3.1 贡献一经提交,即视为贡献者免费且不可撤销地授予社区、项目及其全部或者部分的接收者永久、非排他、全球性的专利权许可,制造、委托制造、销售、许诺销售、进口或以其他合法方式使用贡献及使用贡献产生的成果。 38 | 39 | **四、稳定运行承诺** 40 | 4.1 社区承诺绝不会主动关闭项目,具体指社区绝不会主动对外关闭项目的访问权限或者清空项目所包含的全部信息。 41 | 42 | **五、原创及不侵权承诺** 43 | 5.1 除严格按第5.4条要求明确标记为非原创的情形之外,贡献者承诺所提交的贡献完全为原创作品,不侵害他人的合法权利,且无以下任何一种情况: 44 |   5.1.1 属于职务作品,单位未签署本协议及/或未同意贡献者提交; 45 |   5.1.2 属于合作作品,合作作者未签署本协议及/或未同意贡献者提交; 46 |   5.1.3 属于委托作品或者著作权、专利权已经对外转让,著作权、专利权的实际持有人未签署本协议及/或未同意贡献者提交。 47 | 5.2 如存在第5.1条任何一种情形的,贡献者应先取得相应授权后再进行提交: 48 |   5.2.1 如有第5.1.1条的情形,贡献者应事先取得单位的授权; 49 |   5.2.2 如有第5.1.2条的情形,贡献者应事先取得合作作者的授权; 50 |   5.2.3 如有第5.1条的情形,贡献者应事先取得著作权、专利权实际持有人的授权。 51 | 5.3 在遵守本第五条约定的基础上,单位作为贡献者签署本协议的,可以授权指定人士以单位名义、单位员工名义或者个人名义提交贡献。 52 | 5.4 如贡献者提交的贡献为非原创作品,则贡献者应当明确标记出非原创部分,并完整列出原创者及/或著作权、专利权持有者的名称。 53 | 5.5 除其他条款约定外,如贡献者提交的贡献在使用时可能涉及任何第三方权利,则贡献者还应当在提交时予以说明,并披露该等限制的完整详细信息。 54 | 5.6 请留意:社区希望保护贡献者的合法权益,免受任何人士的侵犯;社区也倡导尊重原创、保护知识产权,不允许侵犯他人的合法权益。如贡献者提交贡献时,冒名、盗用或者擅自提交他人享有著作权或其他合法权利的作品或者以其他方式侵犯他人合法权益,导致社区承担责任和损失的,社区保留追究责任的权利。 55 | 56 | **六、贡献者权利保护** 57 | 6.1 贡献者签署本协议不应视为放弃原创者身份或署名权。本协议不影响贡献者将其贡献合法用于其他任何目的的权利,社区或者任何其他人士亦不应凭借本协议要求贡献者放弃其他任何合法权利。 58 | 6.2 贡献者签署本协议不应视为承诺对其提交的贡献承担后续支持、维保、适用性、品质保证等义务。社区或者任何其他人士亦不应凭借本协议要求贡献者承担任何未约定的义务。 -------------------------------------------------------------------------------- /Logger.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 gpress Authors. 2 | // 3 | // This file is part of gpress. 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU Affero General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU Affero General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | package main 19 | 20 | import ( 21 | "context" 22 | 23 | "github.com/cloudwego/hertz/pkg/common/hlog" 24 | ) 25 | 26 | func init() { 27 | // 设置默认的日志显示信息,显示文件和行号 28 | // Set the default log display information, display file and line number. 29 | // log.SetFlags(log.Llongfile | log.LstdFlags) 30 | // 设置日志级别 31 | hlog.SetLevel(hlog.LevelError) 32 | } 33 | 34 | // LogCallDepth 记录日志调用层级,用于定位到业务层代码 35 | // Log Call Depth Record the log call level, used to locate the business layer code 36 | var LogCallDepth = 4 37 | 38 | // FuncLogError 记录error日志 39 | // FuncLogError Record error log 40 | var FuncLogError func(ctx context.Context, err error) = defaultLogError 41 | 42 | // FuncLogPanic 记录panic日志,默认使用"defaultLogPanic"实现 43 | // FuncLogPanic Record panic log, using "defaultLogPanic" by default 44 | var FuncLogPanic func(ctx context.Context, err error) = defaultLogPanic 45 | 46 | func defaultLogError(ctx context.Context, err error) { 47 | //log.Output(LogCallDepth, fmt.Sprintln(err)) 48 | hlog.Error(err) 49 | } 50 | 51 | func defaultLogPanic(ctx context.Context, err error) { 52 | defaultLogError(ctx, err) 53 | } 54 | -------------------------------------------------------------------------------- /const.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 gpress Authors. 2 | // 3 | // This file is part of gpress. 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU Affero General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU Affero General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | package main 19 | 20 | const ( 21 | // 默认名称 22 | appName = "gpress" 23 | 24 | // 基本目录 25 | datadir = "gpressdatadir/" 26 | // 数据目录,如果不存在认为是第一次安装启动,会创建默认的数据 27 | sqliteDBfile = datadir + "gpress.db" 28 | // 表信息的名称 29 | //tableInfoName = "tableInfo" 30 | // 表字段的名称 31 | //tableFieldName = "tableField" 32 | 33 | // config 配置的表名称 34 | tableConfigName = "config" 35 | 36 | // user 用户的表名称 37 | tableUserName = "user" 38 | // site 站点信息 39 | tableSiteName = "site" 40 | 41 | // 导航菜单 42 | tableCategoryName = "category" 43 | 44 | // 默认模型 45 | // tableModuleDefaultName = "module_default" 46 | // 文章内容 47 | tableContentName = "content" 48 | //---------------------------// 49 | 50 | // 模板的路径 51 | templateDir = datadir + "template/" 52 | 53 | // 主题的路径 54 | themeDir = templateDir + "theme/" 55 | 56 | // 静态化文件目录,网站生成的静态html 57 | staticHtmlDir = datadir + "statichtml/" 58 | 59 | // 数据默认的创建用户 60 | createUser = "system" 61 | 62 | tokenUserId = "userId" 63 | 64 | letters = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 65 | 66 | userTypeKey = "userType" //0访客,1管理员 67 | 68 | defaultPageSize = 10 69 | 70 | searchDataJsonFile = datadir + "public/search-data.json" 71 | 72 | // 静态文件压缩后缀,兼容Nginx gzip_static 73 | compressedFileSuffix = ".gz" 74 | 75 | //版本号 76 | version = "v1.1.2" 77 | ) 78 | -------------------------------------------------------------------------------- /funcmap_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 gpress Authors. 2 | // 3 | // This file is part of gpress. 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU Affero General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU Affero General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | package main 19 | 20 | import ( 21 | "reflect" 22 | "testing" 23 | ) 24 | 25 | func TestDataSliceCategory2Tree(t *testing.T) { 26 | type args struct { 27 | categories []*Category 28 | } 29 | tests := []struct { 30 | name string 31 | args args 32 | want []*Category 33 | }{ 34 | { 35 | name: "无节点", 36 | args: args{ 37 | categories: nil, 38 | }, 39 | want: []*Category{}, 40 | }, 41 | { 42 | name: "两级节点", 43 | args: args{ 44 | categories: []*Category{ 45 | {Id: "1", Name: "Category 1", Pid: ""}, 46 | {Id: "2", Name: "Category 2", Pid: "1"}, 47 | {Id: "3", Name: "Category 3", Pid: ""}, 48 | {Id: "4", Name: "Category 4", Pid: "3"}, 49 | }, 50 | }, 51 | want: []*Category{ 52 | { 53 | Id: "1", 54 | Name: "Category 1", 55 | Leaf: []*Category{{Id: "2", Name: "Category 2", Pid: "1"}}, 56 | }, 57 | { 58 | Id: "3", 59 | Name: "Category 3", 60 | Leaf: []*Category{{Id: "4", Name: "Category 4", Pid: "3"}}, 61 | }, 62 | }, 63 | }, 64 | { 65 | name: "多级节点", 66 | args: args{ 67 | categories: []*Category{ 68 | {Id: "1", Name: "Category 1", Pid: ""}, 69 | {Id: "2", Name: "Category 2", Pid: "1"}, 70 | {Id: "3", Name: "Category 3", Pid: "1"}, 71 | {Id: "4", Name: "Category 4", Pid: "2"}, 72 | {Id: "5", Name: "Category 5", Pid: "2"}, 73 | {Id: "6", Name: "Category 6", Pid: "3"}, 74 | }, 75 | }, 76 | want: []*Category{ 77 | { 78 | Id: "1", 79 | Name: "Category 1", 80 | Leaf: []*Category{ 81 | { 82 | Id: "2", 83 | Pid: "1", 84 | Name: "Category 2", 85 | Leaf: []*Category{ 86 | { 87 | Id: "4", 88 | Pid: "2", 89 | Name: "Category 4", 90 | }, 91 | { 92 | Id: "5", 93 | Pid: "2", 94 | Name: "Category 5", 95 | }, 96 | }, 97 | }, 98 | { 99 | Id: "3", 100 | Pid: "1", 101 | Name: "Category 3", 102 | Leaf: []*Category{ 103 | { 104 | Id: "6", 105 | Pid: "3", 106 | Name: "Category 6", 107 | }, 108 | }, 109 | }, 110 | }, 111 | }, 112 | }, 113 | }, 114 | { 115 | name: "多颗树", 116 | args: args{ 117 | categories: []*Category{ 118 | {Id: "1", Name: "Category 1", Pid: ""}, 119 | {Id: "2", Name: "Category 2", Pid: "1"}, 120 | {Id: "3", Name: "Category 3", Pid: ""}, 121 | {Id: "4", Name: "Category 4", Pid: "3"}, 122 | {Id: "5", Name: "Category 5", Pid: ""}, 123 | {Id: "6", Name: "Category 6", Pid: "5"}, 124 | }, 125 | }, 126 | want: []*Category{ 127 | { 128 | Id: "1", 129 | Name: "Category 1", 130 | Leaf: []*Category{ 131 | { 132 | Id: "2", 133 | Pid: "1", 134 | Name: "Category 2", 135 | }, 136 | }, 137 | }, 138 | { 139 | Id: "3", 140 | Name: "Category 3", 141 | Leaf: []*Category{ 142 | { 143 | Id: "4", 144 | Pid: "3", 145 | Name: "Category 4", 146 | }, 147 | }, 148 | }, 149 | { 150 | Id: "5", 151 | Name: "Category 5", 152 | Leaf: []*Category{ 153 | { 154 | Id: "6", 155 | Pid: "5", 156 | Name: "Category 6", 157 | }, 158 | }, 159 | }, 160 | }, 161 | }, 162 | } 163 | for _, tt := range tests { 164 | t.Run(tt.name, func(t *testing.T) { 165 | if got := sliceCategory2Tree(tt.args.categories); !reflect.DeepEqual(got, tt.want) { 166 | t.Errorf("sliceCategory2Tree() = %v, want %v", got, tt.want) 167 | } 168 | }) 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module gitee.com/gpress/gpress 2 | 3 | go 1.24 4 | 5 | require ( 6 | gitee.com/chunanyong/zorm v1.7.8 7 | github.com/alecthomas/chroma/v2 v2.18.0 8 | github.com/cloudwego/hertz v0.10.0 9 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 10 | github.com/mattn/go-sqlite3 v1.14.28 11 | github.com/mojocn/base64Captcha v1.3.8 12 | github.com/tetratelabs/wazero v1.9.0 13 | github.com/yuin/goldmark v1.7.12 14 | github.com/yuin/goldmark-emoji v1.0.6 15 | github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc 16 | github.com/yuin/goldmark-meta v1.1.0 17 | go.abhg.dev/goldmark/mermaid v0.5.0 18 | go.abhg.dev/goldmark/toc v0.12.0 19 | golang.org/x/crypto v0.38.0 20 | ) 21 | 22 | require ( 23 | github.com/bytedance/gopkg v0.1.1 // indirect 24 | github.com/bytedance/sonic v1.13.2 // indirect 25 | github.com/bytedance/sonic/loader v0.2.4 // indirect 26 | github.com/cloudwego/base64x v0.1.5 // indirect 27 | github.com/cloudwego/gopkg v0.1.4 // indirect 28 | github.com/cloudwego/netpoll v0.7.0 // indirect 29 | github.com/dlclark/regexp2 v1.11.5 // indirect 30 | github.com/fsnotify/fsnotify v1.5.4 // indirect 31 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect 32 | github.com/golang/protobuf v1.5.0 // indirect 33 | github.com/klauspost/cpuid/v2 v2.0.9 // indirect 34 | github.com/nyaruka/phonenumbers v1.0.55 // indirect 35 | github.com/tidwall/gjson v1.14.4 // indirect 36 | github.com/tidwall/match v1.1.1 // indirect 37 | github.com/tidwall/pretty v1.2.0 // indirect 38 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 39 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect 40 | golang.org/x/image v0.23.0 // indirect 41 | golang.org/x/sys v0.33.0 // indirect 42 | google.golang.org/protobuf v1.27.1 // indirect 43 | gopkg.in/yaml.v2 v2.3.0 // indirect 44 | ) 45 | -------------------------------------------------------------------------------- /goldmark_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 gpress Authors. 2 | // 3 | // This file is part of gpress. 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU Affero General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU Affero General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | package main 19 | 20 | import ( 21 | "fmt" 22 | "os" 23 | "testing" 24 | ) 25 | 26 | func TestGoldmarkMeta(t *testing.T) { 27 | source, err := os.ReadFile(datadir + "post/34-es-config.md") 28 | if err != nil { 29 | t.Error(err) 30 | return 31 | } 32 | metaData, tocHtml, html, _ := conver2Html(source) 33 | fmt.Println(metaData["categories"]) 34 | fmt.Println(metaData["tags"]) 35 | fmt.Println(metaData) 36 | fmt.Println(*tocHtml) 37 | fmt.Println(*html) 38 | } 39 | -------------------------------------------------------------------------------- /gpressdatadir/dict.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/dict.zip -------------------------------------------------------------------------------- /gpressdatadir/fts5/libsimple.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/fts5/libsimple.dll -------------------------------------------------------------------------------- /gpressdatadir/fts5/libsimple.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/fts5/libsimple.dylib -------------------------------------------------------------------------------- /gpressdatadir/fts5/libsimple.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/fts5/libsimple.so -------------------------------------------------------------------------------- /gpressdatadir/fts5/libsimple.so-aarch64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/fts5/libsimple.so-aarch64 -------------------------------------------------------------------------------- /gpressdatadir/install_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "serverPort":":660", 3 | "timeout":7200, 4 | "jwttokenKey":"jwttoken", 5 | "jwtSecret":"", 6 | "basePath":"/", 7 | "proxy":"", 8 | "maxRequestBodySize":20971520, 9 | "locale":"zh-CN" 10 | } -------------------------------------------------------------------------------- /gpressdatadir/libgcc_s_seh-1.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/libgcc_s_seh-1.dll -------------------------------------------------------------------------------- /gpressdatadir/locales/en-US.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /gpressdatadir/locales/zh-CN.json: -------------------------------------------------------------------------------- 1 | { 2 | "Install": "安装", 3 | "Congratulations, you have successfully installed GPRESS. Please log in now": "恭喜您,成功安装gpress,现在请登录", 4 | "Please fill in the administrator account": "请填写管理员账号", 5 | "Please fill in the administrator password": "请填写管理员密码", 6 | "Please re-enter the administrator password": "请再次填写管理员密码", 7 | "Use blockchain wallet": "使用区块链钱包", 8 | "Blockchain Address": "区块链Address", 9 | "Blockchain Type": "区块链类型", 10 | "Please select blockchain type": "请选择区块链类型", 11 | "Please select": "请选择", 12 | "XuperChain": "百度超级链", 13 | "Ethereum": "以太坊", 14 | "Address": "Address", 15 | "Please fill in the blockchain Address": "请填写区块链Address", 16 | "Install(using blockchain Address)": "安装(使用区块链Address)", 17 | "Use account and password": "使用账号密码", 18 | "Passwords do not match": "两次密码填写不一致", 19 | "Content Navigation": "内容导航", 20 | "Site Information": "站点信息", 21 | "User Information": "用户信息", 22 | "Theme Template": "主题模板", 23 | "Refresh Site": "刷新站点", 24 | "Settings": "设置", 25 | "Logout": "退出", 26 | "Reload successful": "刷新成功", 27 | "Blockchain wallet login": "区块链钱包登录", 28 | "Blockchain login": "区块链登录", 29 | "Account password login": "账号密码登录", 30 | "Contact us": "联系我们", 31 | "1. Rename gpressdatadir/template/install.html.bak to install.html
2. Settings updated successfully, restart GPRESS to take effect!, reinitialize administrator account and password, data will not be lost": "1.重命名 gpressdatadir/template/install.html.bak 为 install.html
2.重启gpress,重新安装初始化管理员账号密码,数据不会丢失", 32 | "Forgot password": "忘记密码", 33 | "Please install the corresponding wallet": "请安装对应的钱包", 34 | "Info": "信息", 35 | "Login": "登录", 36 | "Account": "账 户", 37 | "Please fill in the account": "请填写账户", 38 | "Password": "密 码", 39 | "Please fill in the password": "请填写密码", 40 | "CAPTCHA": "验证码", 41 | "Please fill in the CAPTCHA": "请填写验证码", 42 | "Blockchain Login": "区块链登录", 43 | "Update Settings": "更新设置", 44 | "Base Path": "基础路径", 45 | "Please fill in the JWT Key": "请填写JWT Key", 46 | "Please fill in the JWT Secret": "请填写JWT Secret", 47 | "Please fill in the IP:Port": "请填写IP:Port", 48 | "Language": "语言", 49 | "Timeout": "超时时间", 50 | "Please fill in the timeout": "请填写超时时间", 51 | "Max Request Size": "最大的请求", 52 | "Submit Changes": "提交修改", 53 | "Update SQL": "更新SQL", 54 | "Please enter the update SQL": "请输入更新SQL", 55 | "SQL execution failed!": "SQL执行失败!", 56 | "SQL executed successfully!": "SQL执行成功!", 57 | "Update error!": "更新错误!", 58 | "Settings updated successfully, restart GPRESS to take effect!": "设置修改成功,重启gpress才能生效!", 59 | "Update failed!": "修改失败!", 60 | "OK": "确定", 61 | "Cancel": "取消", 62 | "Update User Information": "更新用户信息", 63 | "Username": "用户名", 64 | "Update successfully!": "修改成功!", 65 | "Update Site Information": "更新站点信息", 66 | "Name": "名称", 67 | "Please fill in the name": "请填写名称", 68 | "Title": "标题", 69 | "Please fill in the title": "请填写标题", 70 | "Domain": "域名", 71 | "Used to generate the sitemap.xml": "用于生成网站地图sitemap.xml", 72 | "PC Theme": "PC主题", 73 | "Used when accessed by PC browsers": "PC浏览器访问时,使用此主题渲染", 74 | "Mobile Theme": "手机主题", 75 | "Used when accessed by mobile browsers": "手机浏览器访问时,使用此主题渲染", 76 | "WeChat Theme": "微信主题", 77 | "Used when accessed by WeChat": "微信访问时,使用此主题渲染", 78 | "Default Theme": "默认主题", 79 | "Default rendering theme, all themes are located in gpressdatadir/template/theme directory": "默认渲染的主题,所有主题都在gpressdatadir/template/theme目录下", 80 | "Upload Image": "上传图片", 81 | "Footer": "底部信息", 82 | "Site information updated successfully, please Refresh Site": "站点信息修改成功,请刷新站点", 83 | "Only zip format is supported": "仅支持zip格式", 84 | "Upload Theme": "上传主题", 85 | "Theme Market": "主题市场", 86 | "Save File": "保存文件", 87 | "Select the file to modify on the left, display the file content here, edit and modify, click the Save File button to save the changes.": "左侧选择要修改的文件,这里显示文件内容,进行编辑修改,点击保存文件按钮,保存修改的内容。", 88 | "Upload successful": "上传成功", 89 | "Upload error!": "上传错误!", 90 | "Please select the file to modify": "请选择要修改的文件", 91 | "Update error, please try again!": "修改错误,请重试!", 92 | "Theme Name": "主题名称", 93 | "Version": "版本", 94 | "Theme Category": "主题分类", 95 | "Compatible Version": "兼容版本", 96 | "Preview": "预览图", 97 | "Download": "下载", 98 | "Add Navigation Menu": "新增导航菜单", 99 | "Path Identifier": "路径标识", 100 | "Cannot update after saved": "保存后不允许修改", 101 | "Please fill in the path identifier": "请填写路径标识", 102 | "Navigation Name": "导航名称", 103 | "Please fill in the navigation name": "请填写导航名称", 104 | "Redirect Path": "跳转路径", 105 | "Redirect Method": "跳转方式", 106 | "Default": "默认", 107 | "Parent Navigation": "父级导航", 108 | "Page Template": "页面模版", 109 | "Sort": "排序", 110 | "Please fill in the sort number": "请填写排序", 111 | "Status": "状态", 112 | "Public": "公开", 113 | "Link Access": "链接访问", 114 | "Top": "置顶", 115 | "Private": "私密", 116 | "Submit": "提交保存", 117 | "Path identifier can contain letters, numbers, hyphens (-), underscores (_), vertical bars (|), and commas (,)": "路径标识可以包含英文、数字、中划线(-)、下划线(_)、竖线(|)和逗号(,)", 118 | "Save error!": "保存错误!", 119 | "Save successful, continue adding?": "保存成功,是否继续添加?", 120 | "Confirm": "确认", 121 | "Return to List": "返回列表", 122 | "Continue Adding": "继续添加", 123 | "Save failed!": "保存失败!", 124 | "Update Navigation Menu": "更新导航菜单", 125 | "URL Path": "URL路径", 126 | "Update successful, continue editing?": "修改成功,是否继续修改?", 127 | "Continue Editing": "继续修改", 128 | "prev": "上一页", 129 | "next": "下一页", 130 | "first": "首页", 131 | "last": "尾页", 132 | "Total": "共", 133 | "Go to": "到第", 134 | "records": "条", 135 | "pages": "页", 136 | "Add Top Navigation": "新增一级导航", 137 | "All Content": "全部内容", 138 | "Search": "搜索", 139 | "Add Content": "新增内容", 140 | "Navigation Menu": "导航菜单", 141 | "Actions": "操作", 142 | "Unknown": "未知", 143 | "Edit": "编辑", 144 | "Delete": "删除", 145 | "Add Sub Navigation": "新增子导航", 146 | "Edit Navigation": "编辑导航", 147 | "Delete Navigation": "删除导航", 148 | "Confirm deletion?": "确认删除?", 149 | "Delete successful": "删除成功", 150 | "Delete failed!": "删除失败!", 151 | "Please fill in the content title": "请填写文章标题", 152 | "Summary": "摘要", 153 | "Please select a navigation menu": "请选择导航菜单", 154 | "Description": "description", 155 | "Cover Image": "封面图", 156 | "Upload Cover": "上传封面", 157 | "Author": "作者", 158 | "Tag": "tag标签", 159 | "Content format":"内容格式", 160 | "Rich Text": "富文本", 161 | "Path identifier and navigation menu cannot be empty": "路径标识和导航菜单不能为空", 162 | "Please enter content...": "请输入内容...", 163 | "Edit Content": "修改内容", 164 | "Navigation menu cannot be empty": "导航菜单不能为空", 165 | "Incorrect verification code": "验证码错误", 166 | "Account or password cannot be empty": "账户或密码不能为空", 167 | "Account or password is incorrect": "账户或密码错误", 168 | "Address anomaly": "地址异常", 169 | "We currently do not support this type of blockchain account": "暂不支持此类型区块链账户", 170 | "Signature verification failed": "签名校验失败", 171 | "Markdown to HTML conversion error": "Markdown转html错误", 172 | "ID does not exist": "Id不存在", 173 | "JSON data conversion error": "转换json数据错误", 174 | "ID cannot be empty": "id不能为空", 175 | "Failed to update data": "更新数据失败", 176 | "URL path is duplicated, please modify the path identifier": "URL路径重复,请修改路径标识", 177 | "Failed to save data": "保存数据失败", 178 | "Saved successfully!": "保存成功!", 179 | "Cannot delete a navigation item with child elements!": "无法删除有子级的导航!", 180 | "Failed to delete data": "删除数据失败", 181 | "Data deleted successfully": "删除数据成功", 182 | "Updated %d records": "修改 %d 条数据", 183 | "Failed to load install_config.json, using default configuration": "install_config.json加载失败,使用默认配置", 184 | "Load failed, using default":"加载失败,使用默认", 185 | "Please confirm if [%s] needs to be manually renamed to [gpress.db]. If not, please manually delete [%s]": "请确认[%s]是否需要手动重命名为[gpress.db],不需要请手动删除[%s]", 186 | "File anomaly": "文件异常", 187 | "Table check failed,sqliteStatus is false": "表检查失败,sqliteStatus状态为false", 188 | "Table does not exist!": "表不存在!", 189 | "SQL statement error": "sql语句错误", 190 | "Template initialization anomaly": "初始化模板异常", 191 | "Open the front-end in the browse": "浏览器打开前端", 192 | "Open the back-end in the browser": "浏览器打开后台", 193 | "Original data hash does not match": "原始数据Hash不一致", 194 | "The public key in the signature does not match the address":"签名中的公钥和address不匹配", 195 | "Backend management":"后台管理" 196 | } -------------------------------------------------------------------------------- /gpressdatadir/post/01-nginx-config.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "01.Nginx安装配置" 3 | date: 2017-01-10T17:11:00+08:00 4 | draft: false 5 | tags: ["web"] 6 | categories: ["web"] 7 | author: "springrain" 8 | --- 9 | 10 | ## 1. 准备环境 11 | ```shell 12 | yum -y install openssl-devel zlib-devel libtool automake autoconf make 13 | yum -y install gcc gcc-c++ 14 | 15 | #下载Nginx 16 | wget http://nginx.org/download/nginx-1.22.1.tar.gz 17 | #下载PCRE 18 | wget https://jaist.dl.sourceforge.net/project/pcre/pcre/8.45/pcre-8.45.tar.gz 19 | ``` 20 | ## 2. 安装nginx 21 | ```shell 22 | 23 | tar -zxf nginx-1.22.1.tar.gz 24 | tar -zxf pcre-8.45.tar.gz 25 | 26 | #进入nginx解压后的目录 27 | cd nginx-1.22.1 28 | 29 | #执行检查 30 | ./configure --with-http_stub_status_module --with-http_ssl_module --with-http_sub_module --with-http_v2_module --with-http_realip_module --with-pcre=/root/pcre-8.45 31 | 32 | #然后执行: 33 | make 34 | make install 35 | ``` 36 | 运行一下,查看是否正常,Nginx默认监听的是80端口 37 | 启动:```/usr/local/nginx/sbin/nginx``` 38 | 重启:```/usr/local/nginx/sbin/nginx -s reload``` 39 | 40 | ![nginx](/01/01-nginx-config-01.jpg) 41 | 42 | **[Windows版本的Nginx](/01/nginx-windows.zip)** 43 | 44 | ## 3. 配置Nginx 45 | 一般是把nginx主文件和server文件分开处理,这样便于管理. 46 | 主文件是:```/usr/local/nginx/conf/nginx.conf``` 47 | 给server文件创建一个单独的www文件夹 48 | ```shell 49 | mkdir /usr/local/nginx/www 50 | ``` 51 | server配置文件名以 .conf为后缀. 52 | 53 | 54 | **[下载范例配置文件](/01/conf.zip)** 55 | 56 | 57 | ## 4. 开机启动 58 | 编辑 ```vi /etc/rc.d/rc.local``` 在最后加上一行 59 | ```/usr/local/nginx/sbin/nginx``` 60 | 建议把nginx目录迁移至数据盘.例如数据库挂载为/data目录 61 | 关闭nginx服务:```/usr/local/nginx/sbin/nginx -s stop``` 62 | 迁移目录:```mv /usr/local/nginx /data/nginx``` 63 | 建立软链接:```ln -s /usr/local/nginx /usr/local/nginx``` 64 | 启动nginx服务:```/usr/local/nginx/sbin/nginx``` 65 | 66 | ## 5. 切割日志 67 | Nginx默认并没有实现日志切割,这样所有的日志都在一个文件里,文件很大时会影响访问的性能.通过每天零点调用日志切割的脚本,实现Nginx日志切割. 68 | 69 | cut_nginx_logs.sh 脚本如下: 70 | ```shell 71 | #!/bin/bash 72 | #function:cut nginx log files 73 | #author: http://www.jiagou.com 74 | 75 | ###设置日志文件的路径#### 76 | log_files_path="/usr/local/nginx/logs/" 77 | ###日志文件备份的路径#### 78 | log_files_dir=${log_files_path}history/$(date -d "yesterday" +"%Y")/$(date -d "yesterday" +"%m") 79 | ###设置日志文件名称### 80 | log_files_name=(domain1.access domain2.access error) 81 | ###nginx主程序### 82 | nginx_sbin="/usr/local/nginx/sbin/nginx" 83 | ###保留日志天数### 84 | save_days=100 85 | 86 | ############################################ 87 | #Please do not modify the following script # 88 | ############################################ 89 | mkdir -p $log_files_dir 90 | 91 | log_files_num=${#log_files_name[@]} 92 | 93 | #cut nginx log files 94 | for((i=0;i<$log_files_num;i++));do 95 | mv ${log_files_path}${log_files_name[i]}.log ${log_files_dir}/${log_files_name[i]}_$(date -d "yesterday" +"%Y%m%d").log 96 | done 97 | 98 | #delete save_days ago nginx log files 99 | find $log_files_path -mtime +$save_days -exec rm -rf {} \; 100 | 101 | ##reload会重启nginx,重新加载配置文件,reopen只会重建日志文件## 102 | #$nginx_sbin -s reload 103 | $nginx_sbin -s reopen 104 | ``` 105 | 把cut_nginx_logs.sh脚本放到```/usr/local/nginx/sbin/``` 目录下,修改可执行权限: 106 | ```shell 107 | chmod 755 /usr/local/nginx/sbin/cut_nginx_logs.sh 108 | ``` 109 | 添加定时调用:crontab -e 编辑输入 110 | ```shell 111 | 00 00 * * * /bin/bash /usr/local/nginx/sbin/cut_nginx_logs.sh 112 | ``` 113 | 114 | 115 | -------------------------------------------------------------------------------- /gpressdatadir/public/doc/image/000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/public/doc/image/000.png -------------------------------------------------------------------------------- /gpressdatadir/public/doc/image/001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/public/doc/image/001.png -------------------------------------------------------------------------------- /gpressdatadir/public/doc/image/002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/public/doc/image/002.png -------------------------------------------------------------------------------- /gpressdatadir/public/doc/image/003.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/public/doc/image/003.png -------------------------------------------------------------------------------- /gpressdatadir/public/doc/image/004.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/public/doc/image/004.png -------------------------------------------------------------------------------- /gpressdatadir/public/doc/image/005.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/public/doc/image/005.png -------------------------------------------------------------------------------- /gpressdatadir/public/doc/image/006.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/public/doc/image/006.png -------------------------------------------------------------------------------- /gpressdatadir/public/doc/image/007.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/public/doc/image/007.png -------------------------------------------------------------------------------- /gpressdatadir/public/doc/image/008.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/public/doc/image/008.png -------------------------------------------------------------------------------- /gpressdatadir/public/doc/image/009.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/public/doc/image/009.png -------------------------------------------------------------------------------- /gpressdatadir/public/doc/image/010.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/public/doc/image/010.png -------------------------------------------------------------------------------- /gpressdatadir/public/doc/image/011.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/public/doc/image/011.png -------------------------------------------------------------------------------- /gpressdatadir/public/doc/image/012.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/public/doc/image/012.png -------------------------------------------------------------------------------- /gpressdatadir/public/doc/image/013.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/public/doc/image/013.png -------------------------------------------------------------------------------- /gpressdatadir/public/doc/image/014.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/public/doc/image/014.png -------------------------------------------------------------------------------- /gpressdatadir/public/doc/image/015.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/public/doc/image/015.png -------------------------------------------------------------------------------- /gpressdatadir/public/doc/image/016.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/public/doc/image/016.png -------------------------------------------------------------------------------- /gpressdatadir/public/doc/image/017.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/public/doc/image/017.png -------------------------------------------------------------------------------- /gpressdatadir/public/doc/image/018.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/public/doc/image/018.png -------------------------------------------------------------------------------- /gpressdatadir/public/doc/image/019.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/public/doc/image/019.png -------------------------------------------------------------------------------- /gpressdatadir/public/doc/image/020.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/public/doc/image/020.png -------------------------------------------------------------------------------- /gpressdatadir/public/doc/index.md: -------------------------------------------------------------------------------- 1 | # gpress帮助文档 2 | 3 | ## 简介 4 | Web3内容平台,Hertz + Go template + FTS5全文检索,支持以太坊和百度超级链,兼容Hugo、WordPress生态,使用Wasm扩展插件,只需200M内存 5 | 6 | ## 安装 7 | 运行gpress,会输出访问的路径,根据提示使用浏览器访问 ```http://127.0.0.1:660/admin/login``` 首次运行会进入安装界面. 8 | 9 | 首次运行gpress,一定要先进行安装设置管理员的账号密码.安装界面如下图: 10 | 11 | 输入管理员账号和密码,即完成安装,同时支持区块链账号Address作为管理员用户 12 | 13 | 目前支持以太坊和百度超级链,推荐使用[X-Pocket钱包](https://github.com/shengjian-tech/X-Pocket) 14 | 15 | 16 | ## 登录 17 | 浏览器访问 ```http://127.0.0.1:660/admin/login```,正常显示登录页面,输入安装时设置的账号密码. 18 | 19 | 同时支持使用区块链钱包进行授权登录 20 | 21 | 22 | ## 内容导航 23 | 登录之后,默认查看内容导航功能,左侧是导航菜单,右侧是文章内容 24 | 25 | 新增一级导航,就是新增上级为空的导航,会跳转到新增导航界面. 26 | 选中导航,字体颜色为蓝色,右侧显示改导航下的内容,如下图示例,选中```BlockChain```导航 27 | 28 | 导航后面有功能提示的图标,鼠标悬浮显示功能菜单. 29 | 30 | 有以下四个功能: 31 | - 新增内容:为该导航新增文章内容 32 | - 新增子导航:在该导航下新增子导航 33 | - 编辑导航: 编辑该导航 34 | - 删除导航: 删除该导航 35 | 新增子导航的功能界面如下: 36 | 37 | 编辑导航的功能界面如下: 38 | 39 | 删除导航的功能界面如下: 40 | 41 | 42 | ## 新增/修改内容 43 | 内容管理是gpress的重要功能,新增内容主要设置内容的ID,标题和归属的导航菜单,ID会出现在URL中,且不可更改,例如 ID为```gpress```,访问路径为```http://127.0.0.1:660/web/gpress```.内容有```Markdown```和```富文本```两种格式,根据场景选择使用. 44 | 45 | ## 内容列表 46 | 界面左侧为内容列表,点击```ID```会跳转到前台界面,操作有```预览```,```编辑```和```删除```.```预览```是管理员查看实时数据,通过```ID```跳转的前台界面可能有静态化缓存. 47 | 内容列表有基于```fts5```实现的全文检索功能,输入关键字,点击搜索图标,完成搜索. 48 | 49 | ## 站点信息 50 | 设置站点的基础内容信息,其中主题是读取```gpressdatadir/template/theme```目录下的主题文件夹,更换主题之后,需要点击```刷新站点```功能,刷新前台界面. ```logo```是前台界面显示的logo,后台的logo固定为```gpress```的logo,暂时不支持更改. 51 | 52 | ## 用户信息 53 | 修改管理员的账号信息,功能界面如下: 54 | 55 | ## 主题模板 56 | 主题模板的增删改查功能,管理主题中的自定义模板文件,修改内容和导航菜单时,可以应用自定义的模板文件. 57 | 58 | 支持上传主题zip压缩包,用于新增主题. 59 | 60 | 可以从```主题市场```中下载主题,进行安装 61 | 62 | 63 | ## 刷新站点 64 | 模板更改时需要刷新站点才能生效,同时刷新站点会生成静态文件,用于```Nginx```静态化访问. 65 | ```刷新站点``` 功能会生成静态html文件到 ```statichtml``` 目录,同时生成```gzip_static```文件,需要把正在使用的主题的 ```css,js,image```和```gpressdatadir/public```目录复制到 ```statichtml```目录下,也可以用Nginx反向代理指定目录. 66 | nginx 配置示例如下: 67 | ```conf 68 | ### 当前在用主题(default)的css文件 69 | location ~ ^/css/ { 70 | #gzip_static on; 71 | root /data/gpress/gpressdatadir/template/theme/default; 72 | } 73 | ### 当前在用主题(default)的js文件 74 | location ~ ^/js/ { 75 | #gzip_static on; 76 | root /data/gpress/gpressdatadir/template/theme/default; 77 | } 78 | ### 当前在用主题(default)的image文件 79 | location ~ ^/image/ { 80 | root /data/gpress/gpressdatadir/template/theme/default; 81 | } 82 | ### search-data.json FlexSearch搜索的JSON数据 83 | location ~ ^/public/search-data.json { 84 | #gzip_static on; 85 | root /data/gpress/gpressdatadir; 86 | } 87 | ### public 公共文件 88 | location ~ ^/public/ { 89 | root /data/gpress/gpressdatadir; 90 | } 91 | 92 | ### admin 后台管理,请求动态服务 93 | location ~ ^/admin/ { 94 | proxy_redirect off; 95 | proxy_set_header Host $host; 96 | proxy_set_header X-Real-IP $remote_addr; 97 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 98 | proxy_set_header X-Forwarded-Proto $scheme; 99 | proxy_pass http://127.0.0.1:660; 100 | } 101 | ### 静态html目录 102 | location / { 103 | proxy_redirect off; 104 | proxy_set_header Host $host; 105 | proxy_set_header X-Real-IP $remote_addr; 106 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 107 | proxy_set_header X-Forwarded-Proto $scheme; 108 | ## 存在q查询参数,使用动态服务.也支持FlexSearch解析public/search-data.json 109 | if ($arg_q) { 110 | proxy_pass http://127.0.0.1:660; 111 | break; 112 | } 113 | 114 | ### 开启gzip静态压缩 115 | #gzip_static on; 116 | 117 | ### Nginx 1.26+ 不需要再进行302重定向到目录下的index.html,gzip_static也会生效.这段配置留作记录. 118 | ##if ( -d $request_filename ) { 119 | ## 不是 / 结尾 120 | ## rewrite [^\/]$ $uri/index.html redirect; 121 | ##以 / 结尾的 122 | ## rewrite ^(.*) ${uri}index.html redirect; 123 | ##} 124 | 125 | ### 当前在用主题(default)的静态文件目录 126 | root /data/gpress/gpressdatadir/statichtml/default; 127 | 128 | ## 避免目录 301 重定向,例如 /about 会301到 /about/ 129 | try_files $uri $uri/index.html; 130 | 131 | index index.html index.htm; 132 | } 133 | 134 | ``` 135 | 136 | 137 | ## 设置 138 | 用于系统设置,需要重启gpress才能生效. 139 | 140 | 141 | ## 退出 142 | 退出管理后台 143 | 144 | ## 阿里云计算巢 145 | [点击部署gpress到阿里云计算巢](https://computenest.console.aliyun.com/service/instance/create/cn-hangzhou?type=user&ServiceId=service-d4000c9b22c54e5cbffe),也可以单独购买阿里云最低配服务器,进行部署.选择```张家口机房```,规格```ecs.t6-c4m1.large```,配置```2核CPU 0.5G内存 20G高效云盘 RockyLinux9 按使用流量-带宽峰值80M```,一年100元,五年200元左右. -------------------------------------------------------------------------------- /gpressdatadir/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/public/favicon.png -------------------------------------------------------------------------------- /gpressdatadir/public/gongan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/public/gongan.png -------------------------------------------------------------------------------- /gpressdatadir/public/gpress-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/public/gpress-logo.png -------------------------------------------------------------------------------- /gpressdatadir/public/gpress-logo1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/public/gpress-logo1.png -------------------------------------------------------------------------------- /gpressdatadir/public/index.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/public/index.png -------------------------------------------------------------------------------- /gpressdatadir/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/public/logo.png -------------------------------------------------------------------------------- /gpressdatadir/public/mqtt3.1.1.html: -------------------------------------------------------------------------------- 1 | 2 | 107 | 108 | 109 | 110 | 111 | 112 |
服务器地址:
113 | 114 |
  115 |                                  116 |
117 | 118 |
119 | 订阅消息  120 | QoS  125 |                                  126 |
127 |
128 | 发布消息:   129 |                                  130 |
131 |
132 | 订阅列表 133 |
134 |
135 | 136 | 137 | 138 | 139 | 140 | 141 |
订阅消息QoS操作
142 |
143 |
144 | 接收消息 145 |
146 |
147 | 148 |
149 | 150 | -------------------------------------------------------------------------------- /gpressdatadir/public/search-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "dataFile": "public/search-data.json", 3 | "indexConfig": null, 4 | "showParent": true, 5 | "showDescription": true 6 | } -------------------------------------------------------------------------------- /gpressdatadir/public/upload/upload.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/public/upload/upload.txt -------------------------------------------------------------------------------- /gpressdatadir/public/wallet/TrustWallet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/public/wallet/TrustWallet.png -------------------------------------------------------------------------------- /gpressdatadir/public/wallet/bitkeep.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/public/wallet/bitkeep.png -------------------------------------------------------------------------------- /gpressdatadir/public/wallet/metamask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/public/wallet/metamask.png -------------------------------------------------------------------------------- /gpressdatadir/public/wallet/okx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/public/wallet/okx.png -------------------------------------------------------------------------------- /gpressdatadir/public/wallet/tokenproket.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/public/wallet/tokenproket.png -------------------------------------------------------------------------------- /gpressdatadir/public/wallet/x-pocket.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/public/wallet/x-pocket.png -------------------------------------------------------------------------------- /gpressdatadir/statichtml/readme.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 静态化文件目录,网站生成的静态html 10 | 11 | -------------------------------------------------------------------------------- /gpressdatadir/template/admin/bodyend.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /gpressdatadir/template/admin/bodystart.html: -------------------------------------------------------------------------------- 1 | 43 | 44 | 45 | 46 |
47 | 48 | 101 |
102 | 103 |
-------------------------------------------------------------------------------- /gpressdatadir/template/admin/category/save.html: -------------------------------------------------------------------------------- 1 | {{template "admin/header.html"}} 2 | 10 | {{T "Add Navigation Menu"}} - GPRESS 11 | {{template "admin/bodystart.html"}} 12 | 13 | {{ $themeTemplate := themeTemplate "category" }} 14 | 15 | {{ $category := selectOne "category" "* from category WHERE id=?" .QueryStringMap.pid }} 16 | 17 | {{ $maxSortNo:= selectOne "category" "max(sortNo) as sortNo from category" }} 18 | 19 |
20 |
21 | {{T "Add Navigation Menu"}} 22 |
23 |
24 |
25 |
26 | 27 |
28 | 29 |
30 |
31 | 32 |
33 | 34 |
35 | 36 |
37 |
38 | 39 |
40 | 41 |
42 | 43 |
44 |
45 | 46 |
47 | 48 |
49 | 56 |
57 |
58 | 59 |
60 | 61 |
62 | 63 | 64 |
65 |
66 | 67 |
68 | 69 |
70 | 76 |
77 |
78 | 79 |
80 | 81 |
82 | 83 |
84 |
85 | 86 |
87 | 88 |
89 | 90 |
91 |
92 | 93 |
94 | 95 |
96 | 97 |
98 |
99 | 100 |
101 | 102 |
103 | 109 |
110 |
111 | 112 |
113 |
114 | 115 |
116 |
117 |
118 |
119 |
120 | {{template "admin/bodyend.html"}} 121 | 122 | -------------------------------------------------------------------------------- /gpressdatadir/template/admin/category/update.html: -------------------------------------------------------------------------------- 1 | {{template "admin/header.html"}} 2 | 10 | {{T "Update Navigation Menu"}} - GPRESS 11 | {{template "admin/bodystart.html"}} 12 | 13 | {{ $category := selectOne "category" "* from category WHERE id=?" .Data.Pid }} 14 | {{ $themeTemplate := themeTemplate "category" }} 15 |
16 |
17 | {{T "Update Navigation Menu"}} 18 |
19 |
20 |
21 |
22 | 23 |
24 | 25 | 26 |
27 |
28 |
29 | 30 |
31 | 32 |
33 |
34 |
35 | 36 |
37 | 38 |
39 |
40 | 41 |
42 | 43 |
44 | 45 |
46 |
47 | 48 |
49 | 50 |
51 | 58 |
59 |
60 | 61 |
62 | 63 |
64 | 65 | 66 |
67 |
68 | 69 |
70 | 71 |
72 | 78 |
79 |
80 | 81 |
82 | 83 |
84 | 85 |
86 |
87 | 88 |
89 | 90 |
91 | 92 |
93 |
94 | 95 |
96 | 97 |
98 | 99 |
100 |
101 | 102 |
103 | 104 |
105 | 111 |
112 |
113 | 114 |
115 |
116 | 117 |
118 |
119 |
120 |
121 |
122 | {{template "admin/bodyend.html"}} 123 | 124 | -------------------------------------------------------------------------------- /gpressdatadir/template/admin/chainlogin.html: -------------------------------------------------------------------------------- 1 | {{template "admin/header.html"}} 2 | {{T "Blockchain wallet login"}} - GPRESS 3 | 58 | 59 | 60 | 61 | 90 | 153 | 154 | 155 | 156 | -------------------------------------------------------------------------------- /gpressdatadir/template/admin/config/update.html: -------------------------------------------------------------------------------- 1 | {{template "admin/header.html"}} 2 | 10 | 11 | {{T "Update Settings"}} - GPRESS 12 | 13 | 14 | {{template "admin/bodystart.html"}} 15 |
16 |
17 | {{T "Update Settings"}} 18 |
19 |
20 |
21 |
22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 |
30 |
31 |
32 | 33 |
34 | 35 |
36 |
37 |
38 | 39 |
40 | 41 |
42 |
43 |
44 | 45 |
46 | 47 |
48 |
49 |
50 | 51 |
52 | 53 |
54 |
55 |
56 | 57 |
58 | 62 |
63 |
64 |
65 | 66 |
67 | 68 |
69 |
70 |
71 | 72 |
73 | 74 |
75 |
76 |
77 |
78 | 79 | 80 |
81 |
82 |
83 |
84 |
85 | {{template "admin/bodyend.html"}} 86 | 87 | 159 | -------------------------------------------------------------------------------- /gpressdatadir/template/admin/css/tree.css: -------------------------------------------------------------------------------- 1 | 2 | /* 参考文章 https://segmentfault.com/a/1190000043966941 */ 3 | .tree { 4 | flex: 1; 5 | overflow: auto; 6 | padding: 0px; 7 | position: relative; 8 | } 9 | 10 | .tree summary { 11 | outline: 0; 12 | padding-left: 22px; 13 | list-style: none; 14 | background: repeating-linear-gradient(90deg, #c0c4cc 0 1px, transparent 0px 2px) 0px 50%/22px 1px no-repeat; 15 | /* background: linear-gradient(#c0c4cc,#c0c4cc) 0px 50%/20px 1px no-repeat; */ 16 | } 17 | 18 | .tree details:last-child { 19 | background-size: 1px 14px; 20 | } 21 | 22 | .tree>details:not(:last-child)>details:last-child { 23 | background-size: 1px 100%; 24 | } 25 | 26 | .tree details { 27 | padding-left: 28px; 28 | background: repeating-linear-gradient(#c0c4cc 0 1px, transparent 0px 2px) 28px 0px/1px 100% no-repeat; 29 | /* background: linear-gradient(#c0c4cc, #c0c4cc) 40px 0px/1px 100% no-repeat; */ 30 | } 31 | 32 | .tree>details { 33 | background: none; 34 | padding-left: 0; 35 | } 36 | 37 | .tree>details>summary { 38 | background: none; 39 | } 40 | 41 | .tree summary { 42 | display: flex; 43 | align-items: center; 44 | height: 26px; 45 | font-size: 14px; 46 | line-height: 14px; 47 | color: #000; 48 | cursor: default; 49 | } 50 | 51 | .tree summary::after { 52 | content: ""; 53 | position: absolute; 54 | left: 16px; 55 | right: 16px; 56 | height: 26px; 57 | background: #eef2ff; 58 | border-radius: 8px; 59 | z-index: -1; 60 | opacity: 0; 61 | transition: 0.2s; 62 | } 63 | 64 | .tree summary:hover::after { 65 | opacity: 1; 66 | } 67 | 68 | .tree summary:not(:only-child)::before { 69 | content: ""; 70 | width: 12px; 71 | height: 12px; 72 | flex-shrink: 0; 73 | margin-right: 8px; 74 | border: 1px solid #c0c4cc; 75 | background: linear-gradient(#5f5f5f, #5f5f5f) 50%/1px 8px no-repeat, 76 | linear-gradient(#5f5f5f, #5f5f5f) 50%/8px 1px no-repeat; 77 | } 78 | 79 | .tree details[open]>summary::before { 80 | background: linear-gradient(#5f5f5f, #5f5f5f) 50%/8px 1px no-repeat; 81 | } 82 | 83 | .tree summary:hover .operate { 84 | opacity: 1; 85 | visibility: visible; 86 | } 87 | 88 | .operate{ 89 | padding-left: 5px; 90 | opacity: 0; 91 | visibility: hidden; 92 | transition: opacity 0.2s ease-in-out; 93 | } 94 | 95 | .tips-dropdown { 96 | cursor: pointer; 97 | padding-left: 5px; 98 | font-size: 12px; 99 | } 100 | 101 | .tips-dropdown:hover { 102 | color: #16baaa; 103 | } 104 | 105 | -------------------------------------------------------------------------------- /gpressdatadir/template/admin/error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | error 10 | 11 | -------------------------------------------------------------------------------- /gpressdatadir/template/admin/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 15 | -------------------------------------------------------------------------------- /gpressdatadir/template/admin/index.html: -------------------------------------------------------------------------------- 1 | {{template "admin/header.html"}} 2 | {{T "Backend management"}} - GPRESS 3 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /gpressdatadir/template/admin/install.html: -------------------------------------------------------------------------------- 1 | {{template "admin/header.html"}} 2 | {{T "Install"}} - GPRESS 3 | 4 | 52 | 53 | 54 | 55 |
56 | 57 |

{{T "Install"}}

58 |
59 |
60 |
61 |
62 | 63 |
64 | 65 |
66 |
67 |
68 |
69 |
70 | 71 |
72 | 73 |
74 |
75 |
76 |
77 |
78 | 79 |
80 | 81 |
82 |
83 |
84 | 85 |
86 |
87 | 88 | 91 |
92 | 93 | 123 | 124 | 160 | 161 | 162 | 163 | -------------------------------------------------------------------------------- /gpressdatadir/template/admin/js/cherry-markdown/fonts/ch-icon.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/template/admin/js/cherry-markdown/fonts/ch-icon.eot -------------------------------------------------------------------------------- /gpressdatadir/template/admin/js/cherry-markdown/fonts/ch-icon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/template/admin/js/cherry-markdown/fonts/ch-icon.ttf -------------------------------------------------------------------------------- /gpressdatadir/template/admin/js/cherry-markdown/fonts/ch-icon.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/template/admin/js/cherry-markdown/fonts/ch-icon.woff -------------------------------------------------------------------------------- /gpressdatadir/template/admin/js/cherry-markdown/fonts/ch-icon.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/template/admin/js/cherry-markdown/fonts/ch-icon.woff2 -------------------------------------------------------------------------------- /gpressdatadir/template/admin/js/layui/font/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/template/admin/js/layui/font/iconfont.eot -------------------------------------------------------------------------------- /gpressdatadir/template/admin/js/layui/font/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/template/admin/js/layui/font/iconfont.ttf -------------------------------------------------------------------------------- /gpressdatadir/template/admin/js/layui/font/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/template/admin/js/layui/font/iconfont.woff -------------------------------------------------------------------------------- /gpressdatadir/template/admin/js/layui/font/iconfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/template/admin/js/layui/font/iconfont.woff2 -------------------------------------------------------------------------------- /gpressdatadir/template/admin/js/layui/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 测试 - Layui 7 | 8 | 9 | 10 | 11 |
12 |
13 |
14 |
15 | 16 | 17 |
18 | 19 | 20 |
21 | 22 |
23 |
24 |
    25 |
  • 您当前预览的是:Layui-v
  • 26 |
  • Layui 是一套开源免费的 Web UI(界面)组件库。这是一个极其简洁的演示页面
  • 27 |
28 |
29 |
30 |
31 | 32 | 33 | 34 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /gpressdatadir/template/admin/js/layui/免责声明.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 免责声明 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /gpressdatadir/template/admin/js/layui/官方文档.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 官方文档 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /gpressdatadir/template/admin/js/pinyin/说明.txt: -------------------------------------------------------------------------------- 1 | 2025.4.21 2 | 根据 https://cdn.jsdelivr.net/gh/zh-lx/pinyin-pro@latest/dist/pinyin-pro.js下载 3 | 查看 https://github.com/zh-lx/pinyin-pro 最新版是3.26.0 4 | 参数文档 https://pinyin-pro.cn/use/pinyin.html#%E5%8F%82%E6%95%B0 -------------------------------------------------------------------------------- /gpressdatadir/template/admin/login.html: -------------------------------------------------------------------------------- 1 | {{template "admin/header.html"}} 2 | {{T "Login"}} - GPRESS 3 | 4 | 59 | 60 | 61 | 62 | 110 | 141 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /gpressdatadir/template/admin/themeTemplate/list.html: -------------------------------------------------------------------------------- 1 | {{ $convertJson := convertType $.Data "object" "json" }} 2 | {{template "admin/header.html"}} 3 | {{T "Theme Template"}} - GPRESS 4 | 5 | 6 | {{template "admin/bodystart.html"}} 7 | 8 |
9 |
10 |
11 | 12 |
13 |
14 | 17 |
18 |
19 | 22 |
23 |
24 | 25 |
26 |
27 | 28 |
29 |
30 | 31 |
32 |
33 |
34 |
35 | 36 |
37 |
38 | 39 |
40 | {{template "admin/bodyend.html"}} 41 | 42 | 258 | 259 | -------------------------------------------------------------------------------- /gpressdatadir/template/admin/user/update.html: -------------------------------------------------------------------------------- 1 | {{template "admin/header.html"}} 2 | 10 | {{T "Update User Information"}} - GPRESS 11 | 12 | {{template "admin/bodystart.html"}} 13 |
14 |
15 | {{T "Update User Information"}} 16 |
17 |
18 |
19 |
20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 |
28 |
29 |
30 | 31 |
32 | 33 |
34 |
35 | 36 |
37 | 38 |
39 | 40 |
41 |
42 |
43 | 44 |
45 | 46 |
47 |
48 |
49 | 50 |
51 | 56 |
57 |
58 |
59 | 60 |
61 | 62 |
63 |
64 | 65 |
66 |
67 | 68 |
69 |
70 |
71 |
72 |
73 | {{template "admin/bodyend.html"}} 74 | 75 | -------------------------------------------------------------------------------- /gpressdatadir/template/theme/default/bodyend.html: -------------------------------------------------------------------------------- 1 | {{ $site:=site }} 2 |
3 | 4 |
5 | 6 | 7 |
8 | 9 | {{ safeHTML $site.Footer }} 10 | 11 |
12 | 13 |
14 | 15 |
16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /gpressdatadir/template/theme/default/bodystart.html: -------------------------------------------------------------------------------- 1 | {{ $site := site }} 2 | {{ $basePath := basePath }} 3 | {{ $category := selectList "category" "" 1 1000 "* FROM category WHERE status in (1,2) order by status desc,sortNo desc" }} 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 35 | 36 | 40 | 41 | 42 | 43 |
44 |
45 |
46 | 47 | 48 | 49 |
50 | 53 | 56 |
57 |
58 | 77 |
78 | 103 | 104 |
105 |
106 |
107 | 108 | 109 | -------------------------------------------------------------------------------- /gpressdatadir/template/theme/default/category-single.html: -------------------------------------------------------------------------------- 1 | 2 | {{ $contentSQL := "* FROM content WHERE id=? order by sortNo desc" }} 3 | 4 | {{$contentID := printf "/single/%s" (lastURI .UrlPathParam) }} 5 | 6 | {{ $content := selectOne "content" $contentSQL $contentID }} 7 | 8 | 9 | 10 | 11 | 12 | {{ $content.Title }} 13 | 14 | 15 | 16 | {{template "bodystart.html" }} 17 | 18 | 19 |
20 | 21 |
22 |

{{ $content.Title }}

23 | 24 | 28 |
29 | 30 |
31 | 32 | {{ safeHTML $content.Content }} 33 | 34 |
35 |
36 | 37 |
38 | 39 |
40 | 41 | 42 | 43 | 44 | {{template "bodyend.html" }} -------------------------------------------------------------------------------- /gpressdatadir/template/theme/default/category.html: -------------------------------------------------------------------------------- 1 | 2 | {{ $contentSQL :="* FROM content WHERE status in (1,2) and categoryID=? order by status desc,sortNo desc" }} 3 | {{ $categorySQL := "* FROM category WHERE id=? and status<3 order by status desc, sortNo desc"}} 4 | 5 | 6 | {{ if eq .userType 1}} 7 | {{ $contentSQL ="* FROM content WHERE categoryID=? order by sortNo desc" }} 8 | {{ $categorySQL = "* FROM category WHERE id=? order by sortNo desc"}} 9 | {{end}} 10 | 11 | {{ $selectList := selectList "content" .q .pageNo 20 $contentSQL .UrlPathParam }} 12 | 13 | {{ $nav := selectOne "category" $categorySQL .UrlPathParam }} 14 | {{ $site:=site }} 15 | 16 | 17 | 18 | {{ $nav.Name }} 19 | 20 | 21 | 22 | {{template "bodystart.html" }} 23 | 24 |
25 |
26 |

{{$nav.Name}}

27 |
28 | {{ range $k,$v := $selectList.Data }} 29 |
30 | 31 | {{ slice .UpdateTime 0 10 }} 32 | 33 | 34 | {{ if eq $.userType 1}} 35 | 36 | {{else}} 37 | 38 | {{ end }} 39 | 40 | {{ .Title }} 41 | 42 | 43 |
44 | {{end}} 45 | 46 | 47 |
48 | 49 | {{if gt $selectList.Page.PageCount 1}} 50 | 72 | {{end}} 73 | 74 | {{template "bodyend.html" }} -------------------------------------------------------------------------------- /gpressdatadir/template/theme/default/content.html: -------------------------------------------------------------------------------- 1 | 2 | {{ $contentSQL := "* FROM content WHERE id=? and status<3 order by status desc, sortNo desc" }} 3 | 4 | {{ if eq .userType 1}} 5 | {{ $contentSQL ="* FROM content WHERE id=? order by sortNo desc" }} 6 | {{end}} 7 | 8 | {{ $content := selectOne "content" $contentSQL .UrlPathParam }} 9 | 10 | {{ $prevContent := selectOne "content" "id,title FROM content WHERE status in (1,2) and sortNo < ? order by status desc,sortNo desc " $content.SortNo }} 11 | {{ $nextContent := selectOne "content" "id,title FROM content WHERE status in (1,2) and sortNo > ? order by status desc,sortNo asc " $content.SortNo }} 12 | 13 | 14 | 15 | 16 | {{ $content.Title }} 17 | 18 | 19 | 20 | {{template "bodystart.html" }} 21 | 22 | 23 |
24 | 25 |
26 |

{{ $content.Title }}

27 | 28 | 35 |
36 | 37 | {{ if $content.Toc }} 38 |
39 |

文章目录

40 |
41 | 46 |
47 |
48 | {{ end }} 49 | 50 | 51 |
52 | 53 | {{ safeHTML $content.Content }} 54 | 55 |
56 |
57 | 61 | 69 | 70 | 71 |
72 | 97 |
98 | 99 | 100 | 101 | 102 | {{template "bodyend.html" }} -------------------------------------------------------------------------------- /gpressdatadir/template/theme/default/css/fonts/chancery/apple-chancery-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/template/theme/default/css/fonts/chancery/apple-chancery-webfont.eot -------------------------------------------------------------------------------- /gpressdatadir/template/theme/default/css/fonts/chancery/apple-chancery-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/template/theme/default/css/fonts/chancery/apple-chancery-webfont.ttf -------------------------------------------------------------------------------- /gpressdatadir/template/theme/default/css/fonts/chancery/apple-chancery-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/template/theme/default/css/fonts/chancery/apple-chancery-webfont.woff -------------------------------------------------------------------------------- /gpressdatadir/template/theme/default/css/fonts/chancery/apple-chancery-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/template/theme/default/css/fonts/chancery/apple-chancery-webfont.woff2 -------------------------------------------------------------------------------- /gpressdatadir/template/theme/default/css/fonts/iconfont/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/template/theme/default/css/fonts/iconfont/iconfont.eot -------------------------------------------------------------------------------- /gpressdatadir/template/theme/default/css/fonts/iconfont/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/template/theme/default/css/fonts/iconfont/iconfont.ttf -------------------------------------------------------------------------------- /gpressdatadir/template/theme/default/css/fonts/iconfont/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/template/theme/default/css/fonts/iconfont/iconfont.woff -------------------------------------------------------------------------------- /gpressdatadir/template/theme/default/error.html: -------------------------------------------------------------------------------- 1 | 2 | {{ $site:=site }} 3 | 4 | 5 | 6 | {{.UrlPathParam}} 7 | 8 | 9 | 10 | {{template "bodystart.html" }} 11 | 12 |
13 | 14 | 数据异常 15 | 16 |
17 | 18 | 19 | 20 | 21 | {{template "bodyend.html" }} -------------------------------------------------------------------------------- /gpressdatadir/template/theme/default/index.html: -------------------------------------------------------------------------------- 1 | {{ $site:=site }} 2 | 3 | {{ $selectList := selectList "content" .q .pageNo 20 "* FROM content WHERE status in (1,2) order by status desc,sortNo desc" }} 4 | 5 | 6 | 7 | 8 | {{ $site.Title }} 9 | 10 | 11 | 12 | {{template "bodystart.html" }} 13 | 14 | 15 | 16 |
17 | 18 | {{ range $k,$v := $selectList.Data }} 19 | 39 | {{ end }} 40 | 41 | 42 |
43 | 44 | {{if gt $selectList.Page.PageCount 1}} 45 | 67 | {{end}} 68 | 69 | {{template "bodyend.html" }} -------------------------------------------------------------------------------- /gpressdatadir/template/theme/default/js/classList.min.js: -------------------------------------------------------------------------------- 1 | /*! @source http://purl.eligrey.com/github/classList.js/blob/master/classList.js */ 2 | "document"in self&&("classList"in document.createElement("_")&&(!document.createElementNS||"classList"in document.createElementNS("http://www.w3.org/2000/svg","g"))||!function(t){"use strict";if("Element"in t){var e="classList",n="prototype",i=t.Element[n],s=Object,r=String[n].trim||function(){return this.replace(/^\s+|\s+$/g,"")},o=Array[n].indexOf||function(t){for(var e=0,n=this.length;n>e;e++)if(e in this&&this[e]===t)return e;return-1},c=function(t,e){this.name=t,this.code=DOMException[t],this.message=e},a=function(t,e){if(""===e)throw new c("SYNTAX_ERR","The token must not be empty.");if(/\s/.test(e))throw new c("INVALID_CHARACTER_ERR","The token must not contain space characters.");return o.call(t,e)},l=function(t){for(var e=r.call(t.getAttribute("class")||""),n=e?e.split(/\s+/):[],i=0,s=n.length;s>i;i++)this.push(n[i]);this._updateClassName=function(){t.setAttribute("class",this.toString())}},u=l[n]=[],h=function(){return new l(this)};if(c[n]=Error[n],u.item=function(t){return this[t]||null},u.contains=function(t){return~a(this,t+"")},u.add=function(){var t,e=arguments,n=0,i=e.length,s=!1;do t=e[n]+"",~a(this,t)||(this.push(t),s=!0);while(++nn;n++)t=arguments[n],e.call(this,t)}};e("add"),e("remove")}if(t.classList.toggle("c3",!1),t.classList.contains("c3")){var n=DOMTokenList.prototype.toggle;DOMTokenList.prototype.toggle=function(t,e){return 1 in arguments&&!this.contains(t)==!e?e:n.call(this,t)}}"replace"in document.createElement("_").classList||(DOMTokenList.prototype.replace=function(t,e){var n=this.toString().split(" "),i=n.indexOf(t+"");~i&&(n=n.slice(i),this.remove.apply(this,n),this.add(e),this.add.apply(this,n.slice(1)))}),t=null}()); -------------------------------------------------------------------------------- /gpressdatadir/template/theme/default/js/even.js: -------------------------------------------------------------------------------- 1 | "use strict";const Even={};Even.backToTop=function(){const e=$("#back-to-top");$(window).scroll(function(){$(window).scrollTop()>100?e.fadeIn(1e3):e.fadeOut(1e3)}),e.click(function(){$("body,html").animate({scrollTop:0})})},Even.mobileNavbar=function(){const n=$("#mobile-navbar"),t=$(".mobile-navbar-icon"),e=new Slideout({panel:document.getElementById("mobile-panel"),menu:document.getElementById("mobile-menu"),padding:180,tolerance:70});e.disableTouch(),t.click(function(){e.toggle()}),e.on("beforeopen",function(){n.addClass("fixed-open"),t.addClass("icon-click").removeClass("icon-out")}),e.on("beforeclose",function(){n.removeClass("fixed-open"),t.addClass("icon-out").removeClass("icon-click")}),$("#mobile-panel").on("touchend",function(){e.isOpen()&&t.click()})},Even._initToc=function(){const t=20,e=$(".post-toc"),s=$(".post-footer");if(e.length){const o=e.offset().top-t,i=s.offset().top-e.height()-t,n={start:{position:"absolute",top:o},process:{position:"fixed",top:t},end:{position:"absolute",top:i}};$(window).scroll(function(){const t=$(window).scrollTop();ti?e.css(n.end):e.css(n.process)})}const o=30,n=$(".toc-link"),i=$(".headerlink"),a=$(".post-toc-content li"),r=$.map(i,function(e){return $(e).offset().top}),c=$.map(r,function(e){return e-o}),l=function(e,t){for(let n=0;ne[n]&&t<=e[n+1])return n;return t>e[e.length-1]?e.length-1:-1};$(window).scroll(function(){const t=$(window).scrollTop(),e=l(c,t);if($(n).removeClass("active"),$(a).removeClass("has-active"),e!==-1){$(n[e]).addClass("active");let t=n[e].parentNode;for(;t.tagName!=="NAV";)$(t).addClass("has-active"),t=t.parentNode.parentNode}})},Even.fancybox=function(){$.fancybox&&($(".post-content").each(function(){$(this).find("img").each(function(){$(this).wrap(``)})}),$(".fancybox").fancybox({selector:".fancybox",protect:!0}))},Even.highlight=function(){const e=document.querySelectorAll("pre code");for(let s=0;s${e+1}
`;let c="";for(let e=0;e${t[e]}
`;n.className+=" highlight";const o=document.createElement("figure");o.className=n.className,o.innerHTML=`
${r}
${c}
`,i.parentElement.replaceChild(o,i)}},Even.chroma=function(){const e=document.querySelectorAll(".highlight > .chroma");for(let t=0;t code[data-lang]"),o=s?s.className:"";n.className+=" "+o}},Even.toc=function(){const e=document.getElementById("post-toc");if(e!==null){const t=document.getElementById("TableOfContents");t===null?e.parentNode.removeChild(e):(this._refactorToc(t),this._linkToc(),this._initToc())}},Even._refactorToc=function(e){const n=e.children[0];let t=n,s;for(;t.children.length===1&&(s=t.children[0].children[0]).tagName==="UL";)t=s;t!==n&&e.replaceChild(t,n)},Even._linkToc=function(){const e=document.querySelectorAll("#TableOfContents a:first-child");for(let t=0;th"+e);for(let e=0;e${n.innerHTML}`}}},Even.flowchart=function(){if(!window.flowchart)return;const e=document.querySelectorAll("pre code.language-flowchart, pre code.language-flow");for(let t=0;t",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=t.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=t.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),t.elements=c+" "+a,j(b)}function f(a){var b=s[a[q]];return b||(b={},r++,a[q]=r,s[r]=b),b}function g(a,c,d){if(c||(c=b),l)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():p.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||o.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),l)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return t.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(t,b.frag)}function j(a){a||(a=b);var d=f(a);return!t.shivCSS||k||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),l||i(a,d),a}var k,l,m="3.7.3",n=a.html5||{},o=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,p=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,q="_html5shiv",r=0,s={};!function(){try{var a=b.createElement("a");a.innerHTML="",k="hidden"in a,l=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){k=!0,l=!0}}();var t={elements:n.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:m,shivCSS:n.shivCSS!==!1,supportsUnknownElements:l,shivMethods:n.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=t,j(b),"object"==typeof module&&module.exports&&(module.exports=t)}("undefined"!=typeof window?window:this,document); -------------------------------------------------------------------------------- /gpressdatadir/template/theme/default/js/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "icons": [ 4 | { 5 | "src": "/android-chrome-192x192.png", 6 | "sizes": "192x192", 7 | "type": "image/png" 8 | }, 9 | { 10 | "src": "/android-chrome-512x512.png", 11 | "sizes": "512x512", 12 | "type": "image/png" 13 | } 14 | ], 15 | "theme_color": "#ffffff", 16 | "background_color": "#ffffff", 17 | "display": "standalone" 18 | } -------------------------------------------------------------------------------- /gpressdatadir/template/theme/default/js/respond.min.js: -------------------------------------------------------------------------------- 1 | /*! Respond.js v1.4.2: min/max-width media query polyfill * Copyright 2013 Scott Jehl 2 | * Licensed under https://github.com/scottjehl/Respond/blob/master/LICENSE-MIT 3 | * */ 4 | 5 | !function(a){"use strict";a.matchMedia=a.matchMedia||function(a){var b,c=a.documentElement,d=c.firstElementChild||c.firstChild,e=a.createElement("body"),f=a.createElement("div");return f.id="mq-test-1",f.style.cssText="position:absolute;top:-100em",e.style.background="none",e.appendChild(f),function(a){return f.innerHTML='­',c.insertBefore(e,d),b=42===f.offsetWidth,c.removeChild(e),{matches:b,media:a}}}(a.document)}(this),function(a){"use strict";function b(){u(!0)}var c={};a.respond=c,c.update=function(){};var d=[],e=function(){var b=!1;try{b=new a.XMLHttpRequest}catch(c){b=new a.ActiveXObject("Microsoft.XMLHTTP")}return function(){return b}}(),f=function(a,b){var c=e();c&&(c.open("GET",a,!0),c.onreadystatechange=function(){4!==c.readyState||200!==c.status&&304!==c.status||b(c.responseText)},4!==c.readyState&&c.send(null))};if(c.ajax=f,c.queue=d,c.regex={media:/@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi,keyframes:/@(?:\-(?:o|moz|webkit)\-)?keyframes[^\{]+\{(?:[^\{\}]*\{[^\}\{]*\})+[^\}]*\}/gi,urls:/(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g,findStyles:/@media *([^\{]+)\{([\S\s]+?)$/,only:/(only\s+)?([a-zA-Z]+)\s?/,minw:/\([\s]*min\-width\s*:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/,maxw:/\([\s]*max\-width\s*:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/},c.mediaQueriesSupported=a.matchMedia&&null!==a.matchMedia("only all")&&a.matchMedia("only all").matches,!c.mediaQueriesSupported){var g,h,i,j=a.document,k=j.documentElement,l=[],m=[],n=[],o={},p=30,q=j.getElementsByTagName("head")[0]||k,r=j.getElementsByTagName("base")[0],s=q.getElementsByTagName("link"),t=function(){var a,b=j.createElement("div"),c=j.body,d=k.style.fontSize,e=c&&c.style.fontSize,f=!1;return b.style.cssText="position:absolute;font-size:1em;width:1em",c||(c=f=j.createElement("body"),c.style.background="none"),k.style.fontSize="100%",c.style.fontSize="100%",c.appendChild(b),f&&k.insertBefore(c,k.firstChild),a=b.offsetWidth,f?k.removeChild(c):c.removeChild(b),k.style.fontSize=d,e&&(c.style.fontSize=e),a=i=parseFloat(a)},u=function(b){var c="clientWidth",d=k[c],e="CSS1Compat"===j.compatMode&&d||j.body[c]||d,f={},o=s[s.length-1],r=(new Date).getTime();if(b&&g&&p>r-g)return a.clearTimeout(h),h=a.setTimeout(u,p),void 0;g=r;for(var v in l)if(l.hasOwnProperty(v)){var w=l[v],x=w.minw,y=w.maxw,z=null===x,A=null===y,B="em";x&&(x=parseFloat(x)*(x.indexOf(B)>-1?i||t():1)),y&&(y=parseFloat(y)*(y.indexOf(B)>-1?i||t():1)),w.hasquery&&(z&&A||!(z||e>=x)||!(A||y>=e))||(f[w.media]||(f[w.media]=[]),f[w.media].push(m[w.rules]))}for(var C in n)n.hasOwnProperty(C)&&n[C]&&n[C].parentNode===q&&q.removeChild(n[C]);n.length=0;for(var D in f)if(f.hasOwnProperty(D)){var E=j.createElement("style"),F=f[D].join("\n");E.type="text/css",E.media=D,q.insertBefore(E,o.nextSibling),E.styleSheet?E.styleSheet.cssText=F:E.appendChild(j.createTextNode(F)),n.push(E)}},v=function(a,b,d){var e=a.replace(c.regex.keyframes,"").match(c.regex.media),f=e&&e.length||0;b=b.substring(0,b.lastIndexOf("/"));var g=function(a){return a.replace(c.regex.urls,"$1"+b+"$2$3")},h=!f&&d;b.length&&(b+="/"),h&&(f=1);for(var i=0;f>i;i++){var j,k,n,o;h?(j=d,m.push(g(a))):(j=e[i].match(c.regex.findStyles)&&RegExp.$1,m.push(RegExp.$2&&g(RegExp.$2))),n=j.split(","),o=n.length;for(var p=0;o>p;p++)k=n[p],l.push({media:k.split("(")[0].match(c.regex.only)&&RegExp.$2||"all",rules:m.length-1,hasquery:k.indexOf("(")>-1,minw:k.match(c.regex.minw)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:k.match(c.regex.maxw)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}u()},w=function(){if(d.length){var b=d.shift();f(b.href,function(c){v(c,b.href,b.media),o[b.href]=!0,a.setTimeout(function(){w()},0)})}},x=function(){for(var b=0;bt._tolerance?t.open():t.close()}t._moved=false};this.panel.addEventListener(f.end,this._onTouchEndFn);this._onTouchMoveFn=function(e){if(r||t._preventOpen||typeof e.touches==="undefined"||d(e.target)){return}var n=e.touches[0].clientX-t._startOffsetX;var i=t._currentOffsetX=n;if(Math.abs(i)>t._padding){return}if(Math.abs(n)>20){t._opening=true;var o=n*t._orientation;if(t._opened&&o>0||!t._opened&&o<0){return}if(!t._moved){t.emit("translatestart")}if(o<=0){i=n+t._padding*t._orientation;t._opening=false}if(!(t._moved&&u.classList.contains("slideout-open"))){u.classList.add("slideout-open")}t.panel.style[h+"transform"]=t.panel.style.transform="translateX("+i+"px)";t.emit("translate",i);t._moved=true}};this.panel.addEventListener(f.move,this._onTouchMoveFn);return this};_.prototype.enableTouch=function(){this._touch=true;return this};_.prototype.disableTouch=function(){this._touch=false;return this};_.prototype.destroy=function(){this.close();a.removeEventListener(f.move,this._preventMove);this.panel.removeEventListener(f.start,this._resetTouchFn);this.panel.removeEventListener("touchcancel",this._onTouchCancelFn);this.panel.removeEventListener(f.end,this._onTouchEndFn);this.panel.removeEventListener(f.move,this._onTouchMoveFn);a.removeEventListener("scroll",this._onScrollFn);this.open=this.close=function(){};return this};e.exports=_},{decouple:2,emitter:3}],2:[function(t,e,n){"use strict";var i=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||function(t){window.setTimeout(t,1e3/60)}}();function o(t,e,n){var o,s=false;function r(t){o=t;a()}function a(){if(!s){i(u);s=true}}function u(){n.call(t,o);s=false}t.addEventListener(e,r,false);return r}e.exports=o},{}],3:[function(t,e,n){"use strict";var i=function(t,e){if(!(t instanceof e)){throw new TypeError("Cannot call a class as a function")}};n.__esModule=true;var o=function(){function t(){i(this,t)}t.prototype.on=function e(t,n){this._eventCollection=this._eventCollection||{};this._eventCollection[t]=this._eventCollection[t]||[];this._eventCollection[t].push(n);return this};t.prototype.once=function n(t,e){var n=this;function i(){n.off(t,i);e.apply(this,arguments)}i.listener=e;this.on(t,i);return this};t.prototype.off=function o(t,e){var n=undefined;if(!this._eventCollection||!(n=this._eventCollection[t])){return this}n.forEach(function(t,i){if(t===e||t.listener===e){n.splice(i,1)}});if(n.length===0){delete this._eventCollection[t]}return this};t.prototype.emit=function s(t){var e=this;for(var n=arguments.length,i=Array(n>1?n-1:0),o=1;o 2 | {{ $selectList := selectList "content" .q .pageNo 20 "* FROM content WHERE status in (1,2) and tag=? order by status desc,sortNo desc" .UrlPathParam }} 3 | 4 | {{ $site:=site }} 5 | 6 | 7 | 8 | {{.UrlPathParam}} 9 | 10 | 11 | 12 | {{template "bodystart.html" }} 13 | 14 |
15 |
16 |

{{.UrlPathParam}}

17 |
18 | {{ range $k,$v := $selectList.Data }} 19 |
20 | 21 | {{ slice .UpdateTime 0 10 }} 22 | 23 | 24 | 25 | {{ .Title }} 26 | 27 | 28 |
29 | {{end}} 30 | 31 | 32 |
33 | 34 | 56 | 57 | 58 | {{template "bodyend.html" }} -------------------------------------------------------------------------------- /gpressdatadir/wasm/add.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 gpress Authors. 2 | // 3 | // This file is part of gpress. 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU Affero General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU Affero General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | package main 19 | 20 | //tinygo编译wasm命令: tinygo build -o add.wasm -scheduler=none --no-debug -target=wasi . 21 | // 使用 tinygo go:export add 22 | 23 | // go 1.24+ 编译wasm命令: GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o add.wasm 24 | // go 1.24+ windwos编译wasm命令: set GOOS=wasip1&&set GOARCH=wasm&&go build -buildmode=c-shared -o add.wasm 25 | 26 | //go:wasmexport add 27 | func add(x, y uint32) uint32 { 28 | return x + y 29 | } 30 | 31 | // main is required for the `wasi` target, even if it isn't used. 32 | // See https://wazero.io/languages/tinygo/#why-do-i-have-to-define-main 33 | func main() {} 34 | -------------------------------------------------------------------------------- /gpressdatadir/wasm/add.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springrain/gpress/911234f8dec21e35cd72b323647d77f02b14d671/gpressdatadir/wasm/add.wasm -------------------------------------------------------------------------------- /gpressdatadir/wasm/readme.md: -------------------------------------------------------------------------------- 1 | ```shell 2 | tinygo build -o add.wasm -target=wasi -scheduler=none -no-debug add.go 3 | ``` -------------------------------------------------------------------------------- /hugo_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 gpress Authors. 2 | // 3 | // This file is part of gpress. 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU Affero General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU Affero General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | package main 19 | 20 | import ( 21 | "context" 22 | "fmt" 23 | "os" 24 | "strings" 25 | "testing" 26 | "time" 27 | 28 | "gitee.com/chunanyong/zorm" 29 | ) 30 | 31 | func TestCategory(t *testing.T) { 32 | deleteAll(context.Background(), tableCategoryName) 33 | // 获取当前时间 34 | now := time.Now().Format("2006-01-02 15:04:05") 35 | 36 | navs := []string{"About", "Web", "BlockChain", "CloudNative"} 37 | 38 | for i := 0; i < len(navs); i++ { 39 | nav := navs[i] 40 | menu := zorm.NewEntityMap(tableCategoryName) 41 | menu.Set("id", strings.ToLower(nav)) 42 | menu.Set("name", nav) 43 | menu.Set("status", 1) 44 | menu.Set("sortNo", i+1) 45 | menu.Set("createTime", now) 46 | menu.Set("updateTime", now) 47 | _, err := zorm.Transaction(context.Background(), func(ctx context.Context) (interface{}, error) { 48 | _, err := zorm.InsertEntityMap(ctx, menu) 49 | return nil, err 50 | }) 51 | //_, err := saveEntityMap(context.Background(), menu) 52 | if err != nil { 53 | t.Error(err) 54 | } 55 | } 56 | 57 | siteMap := zorm.NewEntityMap(tableSiteName) 58 | siteMap.PkColumnName = "id" 59 | siteMap.Set("id", "gpress_site") 60 | siteMap.Set("title", "jiagou") 61 | siteMap.Set("name", "架构") 62 | siteMap.Set("domain", "jiagou.com") 63 | err := updateTable(context.Background(), siteMap) 64 | if err != nil { 65 | t.Error(err) 66 | } 67 | 68 | } 69 | 70 | func TestReadmks(t *testing.T) { 71 | deleteAll(context.Background(), "content") 72 | files, err := os.ReadDir("D:/post") 73 | if err != nil { 74 | t.Error("读取错误") 75 | } 76 | lists := make([]zorm.IEntityMap, 0) 77 | for i, file := range files { 78 | fileName := file.Name() 79 | source, err := os.ReadFile("D:/post/" + fileName) 80 | if err != nil { 81 | continue 82 | } 83 | sortNo := i + 1 84 | id := fileName[:strings.LastIndex(fileName, ".")] 85 | 86 | fmt.Println(id) 87 | markdown := strings.TrimSpace(string(source)) 88 | smk := markdown[strings.Index(markdown, "---")+3:] 89 | start := strings.Index(smk, "---") + 3 90 | smk = strings.TrimSpace(smk[start:]) 91 | smkRune := []rune(smk) 92 | end := 100 93 | if end > len(smkRune) { 94 | end = len(smkRune) 95 | } 96 | summary := string(smkRune[0:end]) 97 | summary = strings.TrimSpace(summary) 98 | cMap := zorm.NewEntityMap(tableContentName) 99 | cMap.Set("id", id) 100 | cMap.Set("summary", summary) 101 | cMap.Set("markdown", markdown) 102 | cMap.Set("sortNo", sortNo) 103 | cMap.Set("status", 1) 104 | 105 | metaData, tocHtml, html, _ := conver2Html([]byte(markdown)) 106 | dateStr := metaData["date"].(string) 107 | date, _ := time.Parse("2006-01-02T15:04:05+08:00", dateStr) 108 | categories := metaData["categories"].([]interface{}) 109 | category := slice2string(categories) 110 | tags := metaData["tags"].([]interface{}) 111 | tag := slice2string(tags) 112 | cMap.Set("title", metaData["title"]) 113 | cMap.Set("author", metaData["author"]) 114 | cMap.Set("updateTime", date) 115 | cMap.Set("createTime", date) 116 | cMap.Set("categoryName", category) 117 | cMap.Set("categoryID", category) 118 | cMap.Set("tag", tag) 119 | 120 | cMap.Set("content", html) 121 | cMap.Set("toc", tocHtml) 122 | lists = append(lists, cMap) 123 | //saveNewTable(context.Background(), "content", cMap) 124 | 125 | } 126 | 127 | fmt.Println("------------") 128 | 129 | var temp zorm.IEntityMap // 定义临时变量,进行数据交换 130 | for j := 0; j < len(lists)-1; j++ { // 外循环 循环次数 131 | for i := 0; i < len(lists)-1; i++ { // 内循环 数组遍历 132 | m1 := lists[i] 133 | m2 := lists[i+1] 134 | d1 := m1.GetDBFieldMap()["updateTime"].(time.Time) 135 | d2 := m2.GetDBFieldMap()["updateTime"].(time.Time) 136 | if d1.After(d2) { 137 | temp = lists[i] 138 | lists[i] = lists[i+1] 139 | lists[i+1] = temp 140 | } 141 | } 142 | } 143 | 144 | for i := 0; i < len(lists); i++ { // 内循环 数组遍历 145 | cMap := lists[i] 146 | cMap.Set("sortNo", i+1) 147 | date := cMap.GetDBFieldMap()["updateTime"].(time.Time) 148 | dateStr := date.Format("2006-01-02 15:04:05") 149 | cMap.Set("updateTime", dateStr) 150 | cMap.Set("createTime", dateStr) 151 | 152 | _, err := zorm.Transaction(context.Background(), func(ctx context.Context) (interface{}, error) { 153 | _, err := zorm.InsertEntityMap(ctx, cMap) 154 | return nil, err 155 | }) 156 | 157 | //_, err := saveEntityMap(context.Background(), cMap) 158 | if err != nil { 159 | t.Error(err) 160 | } 161 | } 162 | 163 | } 164 | 165 | func TestAbout(t *testing.T) { 166 | ctx := context.Background() 167 | source, err := os.ReadFile("D:/about.md") 168 | if err != nil { 169 | t.Error(err) 170 | } 171 | markdown := strings.TrimSpace(string(source)) 172 | cMap := zorm.NewEntityMap(tableContentName) 173 | cMap.Set("id", "about") 174 | cMap.Set("summary", "jiagou.com") 175 | cMap.Set("markdown", markdown) 176 | cMap.Set("sortNo", 100) 177 | cMap.Set("status", 0) 178 | 179 | _, tocHtml, html, _ := conver2Html([]byte(markdown)) 180 | date := time.Now().Format("2006-01-02 15:04:05") 181 | cMap.Set("title", "about") 182 | cMap.Set("author", "springrain") 183 | cMap.Set("updateTime", date) 184 | cMap.Set("createTime", date) 185 | cMap.Set("categoryName", "about") 186 | cMap.Set("categoryID", "about") 187 | cMap.Set("content", html) 188 | cMap.Set("toc", tocHtml) 189 | cMap.Set("summary", `本站服务器配置:1核CPU,512M内存,20G硬盘,AnolisOS(ANCK).使用Hugo和even模板,编译成静态文件,Nginx作为WEB服务器.我所见识过的一切都将消失一空,就如眼泪消逝在雨中...... 190 | 不妨大胆一些,大胆一些......`) 191 | _, err = zorm.Transaction(context.Background(), func(ctx context.Context) (interface{}, error) { 192 | _, err := zorm.InsertEntityMap(ctx, cMap) 193 | return nil, err 194 | }) 195 | //_, err = saveEntityMap(ctx, cMap) 196 | if err != nil { 197 | t.Error(err) 198 | } 199 | 200 | //更新about的hrefURL 201 | finder := zorm.NewUpdateFinder(tableCategoryName).Append("hrefURL=? WHERE id=?", "about/about", "about") 202 | _, err = zorm.Transaction(ctx, func(ctx context.Context) (interface{}, error) { 203 | return zorm.UpdateFinder(ctx, finder) 204 | }) 205 | if err != nil { 206 | t.Error(err) 207 | } 208 | } 209 | 210 | func slice2string(slice []interface{}) string { 211 | len := len(slice) 212 | s := "" 213 | for i := 0; i < len; i++ { 214 | str := slice[i].(string) 215 | s = s + str 216 | if i+1 < len { 217 | s = s + "," 218 | } 219 | } 220 | return s 221 | } 222 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 gpress Authors. 2 | // 3 | // This file is part of gpress. 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU Affero General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU Affero General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | package main 19 | 20 | import ( 21 | "fmt" 22 | "strings" 23 | 24 | "github.com/cloudwego/hertz/pkg/app/server" 25 | //"github.com/hertz-contrib/gzip" 26 | ) 27 | 28 | // 变量的位置不要更改!!!!!,实际是做初始化使用的,优先级高于init函数!!! 29 | 30 | // 是否已经安装过了 31 | var installed = isInstalled() 32 | 33 | // 加载配置文件 34 | var config, site = loadInstallConfig() 35 | 36 | // 服务器url路径 37 | var httpServerPath = "http://" 38 | 39 | // hertz对象,可以在其他地方使用 40 | var h = server.Default(server.WithHostPorts(config.ServerPort), server.WithBasePath(config.BasePath), server.WithMaxRequestBodySize(config.MaxRequestBodySize)) 41 | 42 | func init() { 43 | 44 | // 设置随机种子 45 | //rand.Seed(time.Now().UnixNano()) 46 | 47 | // gzip压缩文件,产生 xxx.html.gz 文件,https://www.cloudwego.io/zh/docs/hertz/tutorials/basic-feature/middleware/gzip/ 48 | // h.Use(gzip.Gzip(gzip.DefaultCompression)) 49 | } 50 | 51 | func main() { 52 | 53 | // 初始化语言包 54 | initLocale() 55 | 56 | //加载页面模板 57 | err := loadTemplate() 58 | if err != nil { // 初始化模板异常 59 | panic(funcT("Template initialization anomaly")) 60 | } 61 | 62 | message := funcT("Open the front-end in the browse") + ": " 63 | if strings.HasPrefix(config.ServerPort, ":") { 64 | httpServerPath += "127.0.0.1" 65 | } 66 | httpServerPath += config.ServerPort + config.BasePath 67 | message += httpServerPath 68 | message += "\n" + funcT("Open the back-end in the browser") + ": " + httpServerPath + "admin/login" 69 | fmt.Println(message) 70 | 71 | // 启动服务 72 | h.Spin() 73 | } 74 | -------------------------------------------------------------------------------- /routeWeb.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 gpress Authors. 2 | // 3 | // This file is part of gpress. 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU Affero General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU Affero General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | package main 19 | 20 | import ( 21 | "context" 22 | "errors" 23 | "fmt" 24 | "net/http" 25 | "strconv" 26 | "strings" 27 | 28 | "github.com/cloudwego/hertz/pkg/app" 29 | ) 30 | 31 | // init 初始化函数 32 | func init() { 33 | 34 | //初始化静态文件 35 | initStaticFS() 36 | 37 | // 异常页面 38 | h.GET("/error", funcError) 39 | 40 | // 默认首页 41 | h.GET("/", funcIndex) 42 | h.GET("/page/:pageNo", funcIndex) 43 | h.GET("/page/:pageNo/", funcIndex) 44 | 45 | // 查看标签 46 | h.GET("/tag/:urlPathParam", funcListTags) 47 | h.GET("/tag/:urlPathParam/", funcListTags) 48 | h.GET("/tag/:urlPathParam/page/:pageNo", funcListTags) 49 | h.GET("/tag/:urlPathParam/page/:pageNo/", funcListTags) 50 | 51 | //初始化导航菜单路由 52 | initCategoryRoute() 53 | 54 | } 55 | 56 | // funcIndex 模板首页 57 | func funcIndex(ctx context.Context, c *app.RequestContext) { 58 | data := warpRequestMap(c) 59 | cHtml(c, http.StatusOK, "index.html", data) 60 | } 61 | 62 | // funcError 错误页面 63 | func funcError(ctx context.Context, c *app.RequestContext) { 64 | cHtml(c, http.StatusOK, "error.html", nil) 65 | } 66 | 67 | // funcListCategory 导航菜单数据列表 68 | func funcListCategory(ctx context.Context, c *app.RequestContext) { 69 | data := warpRequestMap(c) 70 | urlPathParam := c.Param("urlPathParam") 71 | if urlPathParam == "" { //导航菜单路径访问,例如:/web 72 | urlPathParam = c.GetString("urlPathParam") 73 | } 74 | data["UrlPathParam"] = urlPathParam 75 | templateFile, err := findThemeTemplate(ctx, tableCategoryName, urlPathParam) 76 | if err != nil || templateFile == "" { 77 | templateFile = "category.html" 78 | } 79 | cHtml(c, http.StatusOK, templateFile, data) 80 | } 81 | 82 | // funcListTags 标签列表 83 | func funcListTags(ctx context.Context, c *app.RequestContext) { 84 | data := warpRequestMap(c) 85 | urlPathParam := c.Param("urlPathParam") 86 | 87 | data["UrlPathParam"] = urlPathParam 88 | cHtml(c, http.StatusOK, "tag.html", data) 89 | } 90 | 91 | // funcOneContent 查询一篇文章 92 | func funcOneContent(ctx context.Context, c *app.RequestContext) { 93 | data := warpRequestMap(c) 94 | urlPathParam := c.Param("urlPathParam") 95 | if urlPathParam == "" { //导航菜单路径访问,例如:/web/nginx-use-hsts 96 | urlPathParam = c.GetString("urlPathParam") 97 | } 98 | data["UrlPathParam"] = urlPathParam 99 | 100 | templateFile, err := findThemeTemplate(ctx, tableContentName, urlPathParam) 101 | if err != nil || templateFile == "" { 102 | templateFile = "content.html" 103 | } 104 | cHtml(c, http.StatusOK, templateFile, data) 105 | } 106 | 107 | // initCategoryRoute 初始化导航菜单的映射路径 108 | func initCategoryRoute() { 109 | categories, _ := findAllCategory(context.Background()) 110 | for i := 0; i < len(categories); i++ { 111 | category := categories[i] 112 | categoryID := category.Id 113 | addCategoryRoute(categoryID) 114 | } 115 | } 116 | 117 | // addCategoryRoute 增加导航菜单的路由 118 | func addCategoryRoute(categoryID string) { 119 | 120 | // 处理重复注册路由的panic,不对外抛出 121 | defer func() { 122 | if r := recover(); r != nil { 123 | panicMessage := fmt.Sprintf("%s", r) 124 | FuncLogPanic(nil, errors.New(panicMessage)) 125 | } 126 | }() 127 | 128 | //导航菜单的访问映射 129 | h.GET(funcTrimSuffixSlash(categoryID), addListCategoryRoute(categoryID)) 130 | h.GET(categoryID, addListCategoryRoute(categoryID)) 131 | //导航菜单分页数据的访问映射 132 | h.GET(categoryID+"page/:pageNo", addListCategoryRoute(categoryID)) 133 | h.GET(categoryID+"page/:pageNo/", addListCategoryRoute(categoryID)) 134 | //导航菜单下文章的访问映射 135 | h.GET(categoryID+":contentURI", addOneContentRoute(categoryID)) 136 | h.GET(categoryID+":contentURI/", addOneContentRoute(categoryID)) 137 | } 138 | 139 | // addListCategoryRoute 增加导航菜单的GET请求路由,用于自定义设置导航的路由 140 | func addListCategoryRoute(categoryID string) app.HandlerFunc { 141 | return func(ctx context.Context, c *app.RequestContext) { 142 | c.Set("urlPathParam", categoryID) 143 | funcListCategory(ctx, c) 144 | } 145 | } 146 | 147 | // addOneContentRoute 增加内容的GET请求路由 148 | func addOneContentRoute(categoryID string) app.HandlerFunc { 149 | return func(ctx context.Context, c *app.RequestContext) { 150 | contentURI := c.Param("contentURI") 151 | key := categoryID + contentURI 152 | c.Set("urlPathParam", key) 153 | funcOneContent(ctx, c) 154 | } 155 | } 156 | 157 | // warpRequestMap 包装请求参数为map 158 | func warpRequestMap(c *app.RequestContext) map[string]interface{} { 159 | pageNoStr := c.Param("pageNo") 160 | if pageNoStr == "" { 161 | pageNoStr = c.GetString("pageNo") 162 | } 163 | if pageNoStr == "" { 164 | //pageNoStr = c.DefaultQuery("pageNo", "1") 165 | pageNoStr = "1" 166 | } 167 | 168 | pageNo, _ := strconv.Atoi(pageNoStr) 169 | q := strings.TrimSpace(c.Query("q")) 170 | 171 | data := make(map[string]interface{}, 0) 172 | data["pageNo"] = pageNo 173 | data["q"] = q 174 | //设置用户角色,0是访客,1是管理员 175 | userType, ok := c.Get(userTypeKey) 176 | if ok { 177 | data[userTypeKey] = userType 178 | } else { 179 | data[userTypeKey] = 0 180 | } 181 | return data 182 | } 183 | -------------------------------------------------------------------------------- /serviceCategory.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 gpress Authors. 2 | // 3 | // This file is part of gpress. 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU Affero General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU Affero General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | package main 19 | 20 | import ( 21 | "context" 22 | 23 | "gitee.com/chunanyong/zorm" 24 | ) 25 | 26 | // findAllCategory 查找所有的导航菜单 27 | func findAllCategory(ctx context.Context) ([]Category, error) { 28 | finder := zorm.NewSelectFinder(tableCategoryName) 29 | categories := make([]Category, 0) 30 | err := zorm.Query(ctx, finder, &categories, nil) 31 | return categories, err 32 | } 33 | 34 | // validateIDExists 校验ID是否已经存在 35 | func validateIDExists(ctx context.Context, id string) bool { 36 | id = funcTrimSuffixSlash(id) 37 | if id == "" { 38 | return true 39 | } 40 | 41 | f1 := zorm.NewSelectFinder(tableContentName, "id").Append("Where id=?", id) 42 | cid := "" 43 | zorm.QueryRow(ctx, f1, &cid) 44 | if cid != "" { 45 | return true 46 | } 47 | id = id + "/" 48 | f2 := zorm.NewSelectFinder(tableCategoryName, "id").Append("Where id=?", id) 49 | zorm.QueryRow(ctx, f2, &cid) 50 | return cid != "" 51 | 52 | } 53 | -------------------------------------------------------------------------------- /serviceConfig.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 gpress Authors. 2 | // 3 | // This file is part of gpress. 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU Affero General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU Affero General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | package main 19 | 20 | import ( 21 | "context" 22 | "encoding/json" 23 | "errors" 24 | "io" 25 | "os" 26 | 27 | "gitee.com/chunanyong/zorm" 28 | ) 29 | 30 | // loadInstallConfig 加载配置文件,只有初始化安装时需要读取配置文件,读取后,就写入表,通过后台管理,然后重命名为 install_config.json_配置已失效_请通过后台设置管理 31 | func loadInstallConfig() (Config, Site) { 32 | var site = Site{Theme: "default"} 33 | defaultErr := errors.New(funcT("Failed to load install_config.json, using default configuration")) 34 | if installed { // 已经安装,需要表读取配置 35 | var err error 36 | site, err = funcSite() 37 | if err != nil { 38 | return defaultConfig, site 39 | } 40 | config, err := findConfig() 41 | if err != nil { 42 | return defaultConfig, site 43 | } 44 | return config, site 45 | } 46 | // 打开文件 47 | jsonFile, err := os.Open(datadir + "install_config.json") 48 | if err != nil { 49 | FuncLogError(nil, defaultErr) 50 | return defaultConfig, site 51 | } 52 | // 关闭文件 53 | defer jsonFile.Close() 54 | byteValue, err := io.ReadAll(jsonFile) 55 | if err != nil { 56 | FuncLogError(nil, defaultErr) 57 | return defaultConfig, site 58 | } 59 | configJson := Config{} 60 | // Decode从输入流读取下一个json编码值并保存在v指向的值里 61 | err = json.Unmarshal([]byte(byteValue), &configJson) 62 | if err != nil { 63 | FuncLogError(nil, defaultErr) 64 | return defaultConfig, site 65 | } 66 | 67 | if configJson.JwtSecret == "" { // 如果没有配置jwtSecret,产生随机字符串 68 | configJson.JwtSecret = randStr(32) 69 | } 70 | if configJson.BasePath == "" { 71 | configJson.BasePath = "/" 72 | } 73 | if configJson.Locale == "" { 74 | configJson.Locale = "zh-CN" 75 | } 76 | configJson.Id = defaultConfig.Id 77 | 78 | return configJson, site 79 | } 80 | 81 | var defaultConfig = Config{ 82 | Id: "gpress_config", 83 | BasePath: "/", 84 | // 默认的加密Secret 85 | // JwtSecret: "gpress+jwtSecret-2023", 86 | JwtSecret: randStr(32), 87 | //Theme: "default", 88 | MaxRequestBodySize: 20 * 1024 * 1024, 89 | JwttokenKey: "jwttoken", // jwt的key 90 | Timeout: 7200, // 两小时超时 91 | ServerPort: ":660", // gpress: 103 + 112 + 114 + 101 + 115 + 115 = 660 92 | Locale: "zh-CN", 93 | } 94 | 95 | // insertConfig 插入config 96 | func insertConfig(ctx context.Context) error { 97 | //数据库存在config,不更新数据库,更新config变量 98 | finder := zorm.NewSelectFinder(tableConfigName).Append("WHERE id=?", "gpress_config") 99 | c := Config{} 100 | has, err := zorm.QueryRow(ctx, finder, &c) 101 | if has && err == nil && c.Id != "" { 102 | config = c 103 | return err 104 | } 105 | 106 | // 清空配置,重新创建 107 | deleteAll(ctx, tableConfigName) 108 | _, err = zorm.Transaction(ctx, func(ctx context.Context) (interface{}, error) { 109 | return zorm.Insert(ctx, &config) 110 | }) 111 | 112 | return err 113 | } 114 | 115 | // findConfig 查询配置 116 | func findConfig() (Config, error) { 117 | 118 | finder := zorm.NewSelectFinder(tableConfigName, "*") 119 | 120 | m, err := zorm.QueryRowMap(context.Background(), finder) 121 | 122 | config := defaultConfig 123 | if err != nil { 124 | return config, err 125 | } 126 | b, err := json.Marshal(m) 127 | if err != nil { 128 | return config, err 129 | } 130 | json.Unmarshal(b, &config) 131 | 132 | if config.BasePath == "" { 133 | config.BasePath = "/" 134 | } 135 | if config.MaxRequestBodySize == 0 { 136 | config.MaxRequestBodySize = 20 * 1024 * 1024 137 | } 138 | if config.Locale == "" { 139 | config.Locale = "zh-CN" 140 | } 141 | return config, nil 142 | } 143 | -------------------------------------------------------------------------------- /serviceUser.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 gpress Authors. 2 | // 3 | // This file is part of gpress. 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU Affero General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU Affero General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | package main 19 | 20 | import ( 21 | "context" 22 | 23 | "gitee.com/chunanyong/zorm" 24 | ) 25 | 26 | // insertUser 插入用户 27 | func insertUser(ctx context.Context, user User) error { 28 | // 清空用户,只能有一个管理员 29 | deleteAll(ctx, tableUserName) 30 | // 初始化数据 31 | user.Id = "gpress_admin" 32 | user.SortNo = 1 33 | user.Status = 1 34 | _, err := zorm.Transaction(ctx, func(ctx context.Context) (interface{}, error) { 35 | return zorm.Insert(ctx, &user) 36 | }) 37 | return err 38 | } 39 | 40 | // findUserId 查询用户ID 41 | func findUserId(ctx context.Context, account string, password string) (string, error) { 42 | finder := zorm.NewSelectFinder(tableUserName, "id").Append(" WHERE account=? and password=?", account, password) 43 | userId := "" 44 | _, err := zorm.QueryRow(ctx, finder, &userId) 45 | return userId, err 46 | } 47 | 48 | // findUserAddress 查询用户区块链Address 49 | func findUserAddress(ctx context.Context) (string, string, string, error) { 50 | finder := zorm.NewSelectFinder(tableUserName, "id,chainType,chainAddress") 51 | userMap, err := zorm.QueryRowMap(ctx, finder) 52 | if len(userMap) < 1 || userMap["id"] == nil || userMap["chainType"] == nil || userMap["chainAddress"] == nil { //没有数据 53 | return "", "", "", err 54 | } 55 | return userMap["id"].(string), userMap["chainType"].(string), userMap["chainAddress"].(string), err 56 | } 57 | -------------------------------------------------------------------------------- /utilBase58.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 gpress Authors. 2 | // 3 | // This file is part of gpress. 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU Affero General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU Affero General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | package main 19 | 20 | import ( 21 | "math/big" 22 | ) 23 | 24 | var bigRadix = big.NewInt(58) 25 | var bigZero = big.NewInt(0) 26 | 27 | // base58Decode decodes a modified base58 string to a byte slice. 28 | func base58Decode(b string) []byte { 29 | answer := big.NewInt(0) 30 | j := big.NewInt(1) 31 | 32 | scratch := new(big.Int) 33 | for i := len(b) - 1; i >= 0; i-- { 34 | tmp := b58[b[i]] 35 | if tmp == 255 { 36 | return []byte("") 37 | } 38 | scratch.SetInt64(int64(tmp)) 39 | scratch.Mul(j, scratch) 40 | answer.Add(answer, scratch) 41 | j.Mul(j, bigRadix) 42 | } 43 | 44 | tmpval := answer.Bytes() 45 | 46 | var numZeros int 47 | for numZeros = 0; numZeros < len(b); numZeros++ { 48 | if b[numZeros] != alphabetIdx0 { 49 | break 50 | } 51 | } 52 | flen := numZeros + len(tmpval) 53 | val := make([]byte, flen) 54 | copy(val[numZeros:], tmpval) 55 | 56 | return val 57 | } 58 | 59 | // base58Encode encodes a byte slice to a modified base58 string. 60 | func base58Encode(b []byte) string { 61 | x := new(big.Int) 62 | x.SetBytes(b) 63 | 64 | answer := make([]byte, 0, len(b)*136/100) 65 | for x.Cmp(bigZero) > 0 { 66 | mod := new(big.Int) 67 | x.DivMod(x, bigRadix, mod) 68 | answer = append(answer, alphabet[mod.Int64()]) 69 | } 70 | 71 | // leading zero bytes 72 | for _, i := range b { 73 | if i != 0 { 74 | break 75 | } 76 | answer = append(answer, alphabetIdx0) 77 | } 78 | 79 | // reverse 80 | alen := len(answer) 81 | for i := 0; i < alen/2; i++ { 82 | answer[i], answer[alen-1-i] = answer[alen-1-i], answer[i] 83 | } 84 | 85 | return string(answer) 86 | } 87 | 88 | const ( 89 | // alphabet is the modified base58 alphabet used by Bitcoin. 90 | alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" 91 | 92 | alphabetIdx0 = '1' 93 | ) 94 | 95 | var b58 = [256]byte{ 96 | 255, 255, 255, 255, 255, 255, 255, 255, 97 | 255, 255, 255, 255, 255, 255, 255, 255, 98 | 255, 255, 255, 255, 255, 255, 255, 255, 99 | 255, 255, 255, 255, 255, 255, 255, 255, 100 | 255, 255, 255, 255, 255, 255, 255, 255, 101 | 255, 255, 255, 255, 255, 255, 255, 255, 102 | 255, 0, 1, 2, 3, 4, 5, 6, 103 | 7, 8, 255, 255, 255, 255, 255, 255, 104 | 255, 9, 10, 11, 12, 13, 14, 15, 105 | 16, 255, 17, 18, 19, 20, 21, 255, 106 | 22, 23, 24, 25, 26, 27, 28, 29, 107 | 30, 31, 32, 255, 255, 255, 255, 255, 108 | 255, 33, 34, 35, 36, 37, 38, 39, 109 | 40, 41, 42, 43, 255, 44, 45, 46, 110 | 47, 48, 49, 50, 51, 52, 53, 54, 111 | 55, 56, 57, 255, 255, 255, 255, 255, 112 | 255, 255, 255, 255, 255, 255, 255, 255, 113 | 255, 255, 255, 255, 255, 255, 255, 255, 114 | 255, 255, 255, 255, 255, 255, 255, 255, 115 | 255, 255, 255, 255, 255, 255, 255, 255, 116 | 255, 255, 255, 255, 255, 255, 255, 255, 117 | 255, 255, 255, 255, 255, 255, 255, 255, 118 | 255, 255, 255, 255, 255, 255, 255, 255, 119 | 255, 255, 255, 255, 255, 255, 255, 255, 120 | 255, 255, 255, 255, 255, 255, 255, 255, 121 | 255, 255, 255, 255, 255, 255, 255, 255, 122 | 255, 255, 255, 255, 255, 255, 255, 255, 123 | 255, 255, 255, 255, 255, 255, 255, 255, 124 | 255, 255, 255, 255, 255, 255, 255, 255, 125 | 255, 255, 255, 255, 255, 255, 255, 255, 126 | 255, 255, 255, 255, 255, 255, 255, 255, 127 | 255, 255, 255, 255, 255, 255, 255, 255, 128 | } 129 | -------------------------------------------------------------------------------- /utilCaptcha.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 gpress Authors. 2 | // 3 | // This file is part of gpress. 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU Affero General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU Affero General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | package main 19 | 20 | import ( 21 | "image/color" 22 | "math/rand" 23 | "sync" 24 | "sync/atomic" 25 | 26 | "github.com/mojocn/base64Captcha" 27 | ) 28 | 29 | const errCount = 3 30 | 31 | // 配置验证码的参数 32 | var driverString = base64Captcha.DriverString{ 33 | Height: 60, 34 | Width: 200, 35 | NoiseCount: 0, 36 | ShowLineOptions: 2 | 4, 37 | Length: 4, 38 | Source: "34678acdefghkmnprtuvwxy", 39 | BgColor: &color.RGBA{R: 3, G: 102, B: 214, A: 125}, 40 | Fonts: []string{"wqy-microhei.ttc"}, 41 | } 42 | 43 | // var driver = base64Captcha.NewDriverMath(60, 200, 0, base64Captcha.OptionShowHollowLine, nil, nil, []string{"wqy-microhei.ttc"}) 44 | var driver = driverString.ConvertFonts() 45 | 46 | var captchaQuestion, captchaAnswer, captchaBase64 string 47 | var captchaLock = &sync.Mutex{} 48 | 49 | var chainRandStr string 50 | 51 | var errorLoginCount atomic.Uint32 52 | 53 | // generateCaptcha 生成随机验证码 54 | func generateCaptcha() { 55 | captchaLock.Lock() 56 | defer captchaLock.Unlock() 57 | _, captchaQuestion, captchaAnswer = driver.GenerateIdQuestionAnswer() 58 | item, err := driver.DrawCaptcha(captchaQuestion) 59 | if err != nil { 60 | captchaBase64 = "" 61 | return 62 | } 63 | captchaBase64 = item.EncodeB64string() 64 | } 65 | 66 | // generateChainRandStr 生成区块链登录的随机字符串 67 | func generateChainRandStr() { 68 | captchaLock.Lock() 69 | defer captchaLock.Unlock() 70 | //先记录到全局变量 71 | chainRandStr = randStr(30) 72 | } 73 | 74 | // randStr 生成随机字符串 75 | func randStr(n int) string { 76 | //rand.Seed(time.Now().UnixNano()) 77 | b := make([]byte, n) 78 | for i := range b { 79 | b[i] = letters[rand.Intn(len(letters))] 80 | } 81 | return string(b) 82 | } 83 | -------------------------------------------------------------------------------- /utilJWT.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 gpress Authors. 2 | // 3 | // This file is part of gpress. 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU Affero General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU Affero General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | package main 19 | 20 | import ( 21 | "crypto/hmac" 22 | "crypto/sha512" 23 | "encoding/base64" 24 | "encoding/json" 25 | "errors" 26 | "fmt" 27 | "strings" 28 | "time" 29 | ) 30 | 31 | type Header struct { 32 | Alg string `json:"alg"` 33 | Typ string `json:"typ"` 34 | } 35 | 36 | type Payload struct { 37 | // 定义您需要加入的有效载荷字段 38 | UserId string `json:"userId"` 39 | Expires int64 `json:"exp"` 40 | } 41 | 42 | // 定义头部和有效载荷 43 | var defaultHeader = Header{ 44 | Alg: "HS512", 45 | Typ: "JWT", 46 | } 47 | 48 | // newJWTToken 创建一个jwtToken 49 | func newJWTToken(userId string) (string, error) { 50 | if userId == "" { 51 | return "", errors.New("userId is nil ") 52 | } 53 | 54 | payload := Payload{ 55 | UserId: userId, 56 | Expires: time.Now().Add(time.Duration(config.Timeout) * time.Second).Unix(), // 设置过期时间 57 | } 58 | 59 | // 序列化头部和有效载荷 60 | headerBytes, err := json.Marshal(defaultHeader) 61 | if err != nil { 62 | return "", err 63 | } 64 | 65 | payloadBytes, err := json.Marshal(payload) 66 | if err != nil { 67 | return "", err 68 | } 69 | 70 | // Base64编码头部和有效载荷 71 | headerString := base64.RawURLEncoding.EncodeToString(headerBytes) 72 | payloadString := base64.RawURLEncoding.EncodeToString(payloadBytes) 73 | 74 | // 计算签名 75 | hasher := hmac.New(sha512.New, []byte(config.JwtSecret)) 76 | hasher.Write([]byte(headerString + "." + payloadString)) 77 | signature := base64.RawURLEncoding.EncodeToString(hasher.Sum(nil)) 78 | 79 | // 拼接JWT字符串 80 | jwtString := fmt.Sprintf("%s.%s.%s", headerString, payloadString, signature) 81 | 82 | return jwtString, err 83 | } 84 | 85 | // userIdByToken 根据token字符串,查询UserId 86 | func userIdByToken(tokenString string) (string, error) { 87 | if tokenString == "" { 88 | return "", errors.New("token is nil") 89 | } 90 | 91 | // 分割JWT字符串 92 | parts := strings.Split(tokenString, ".") 93 | if len(parts) != 3 { 94 | return "", errors.New("invalid JWT format") 95 | } 96 | 97 | // 解码头部和有效载荷部分 98 | headerBytes, err := base64.RawURLEncoding.DecodeString(parts[0]) 99 | if err != nil { 100 | return "", err 101 | } 102 | 103 | payloadBytes, err := base64.RawURLEncoding.DecodeString(parts[1]) 104 | if err != nil { 105 | return "", err 106 | } 107 | 108 | // 解析头部 109 | var header Header 110 | err = json.Unmarshal(headerBytes, &header) 111 | if err != nil { 112 | return "", err 113 | } 114 | 115 | // 验证算法 116 | if header.Alg != "HS512" { 117 | return "", err 118 | } 119 | 120 | // 计算签名 121 | hasher := hmac.New(sha512.New, []byte(config.JwtSecret)) 122 | hasher.Write([]byte(parts[0] + "." + parts[1])) 123 | expectedSignature := base64.RawURLEncoding.EncodeToString(hasher.Sum(nil)) 124 | 125 | // 比较签名 126 | if parts[2] != expectedSignature { 127 | return "", errors.New("JWT signature is invalid") 128 | } 129 | 130 | // 解析有效载荷 131 | var payload Payload 132 | err = json.Unmarshal(payloadBytes, &payload) 133 | if err != nil { 134 | return "", err 135 | } 136 | 137 | // 检查有效载荷中的过期时间 138 | if payload.Expires < time.Now().Unix() { 139 | return "", errors.New("JWT signature is expires") 140 | } 141 | return payload.UserId, nil 142 | } 143 | -------------------------------------------------------------------------------- /utilLocale.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 gpress Authors. 2 | // 3 | // This file is part of gpress. 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU Affero General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU Affero General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | package main 19 | 20 | import ( 21 | "encoding/json" 22 | "errors" 23 | "io" 24 | "os" 25 | "strings" 26 | ) 27 | 28 | // localeMap 用于记录翻译的Map 29 | var localeMap = make(map[string]string) 30 | var gpressLocate = "zh-CN" 31 | 32 | // initLocale 要在config初始化之后,需要获取config中的语言配置 33 | func initLocale() { 34 | gpressLocate = config.Locale 35 | defaultErr := errors.New(gpressLocate + ".json " + funcT("Load failed, using default") + " zh-CN.json") 36 | // 打开文件 37 | jsonFile, err := os.Open(datadir + "/locales/" + gpressLocate + ".json") 38 | if err != nil { 39 | FuncLogError(nil, defaultErr) 40 | jsonFile, err = os.Open(datadir + "/locales/zh-CN.json") 41 | if err != nil { 42 | FuncLogError(nil, defaultErr) 43 | return 44 | } 45 | gpressLocate = "zh-CN" 46 | } 47 | // 关闭文件 48 | defer jsonFile.Close() 49 | byteValue, err := io.ReadAll(jsonFile) 50 | if err != nil { 51 | FuncLogError(nil, defaultErr) 52 | return 53 | } 54 | localeMapTemp := make(map[string]string) 55 | // Decode从输入流读取下一个json编码值并保存在v指向的值里 56 | err = json.Unmarshal([]byte(byteValue), &localeMapTemp) 57 | if err != nil { 58 | FuncLogError(nil, defaultErr) 59 | return 60 | } 61 | //不区分大小写 62 | for key, value := range localeMapTemp { 63 | localeMap[strings.ToLower(key)] = value 64 | } 65 | localeMapTemp = nil 66 | } 67 | -------------------------------------------------------------------------------- /utilSQLite.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 gpress Authors. 2 | // 3 | // This file is part of gpress. 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU Affero General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU Affero General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | package main 19 | 20 | import ( 21 | "context" 22 | "database/sql" 23 | "fmt" 24 | "os" 25 | 26 | "gitee.com/chunanyong/zorm" 27 | 28 | // 00.引入数据库驱动 29 | "github.com/mattn/go-sqlite3" 30 | ) 31 | 32 | var dbDao *zorm.DBDao 33 | 34 | var dbDaoConfig = zorm.DataSourceConfig{ 35 | DSN: sqliteDBfile, 36 | DriverName: "sqlite3_simple", // 使用simple分词器会注册这个驱动名 37 | Dialect: "sqlite", 38 | MaxOpenConns: 1, 39 | MaxIdleConns: 1, 40 | ConnMaxLifetimeSecond: 600, 41 | SlowSQLMillis: -1, 42 | } 43 | 44 | // checkSQLiteStatus 初始化sqlite数据库,并检查是否成功 45 | func checkSQLiteStatus() bool { 46 | const failSuffix = ".fail" 47 | if failDB := datadir + "gpress.db" + failSuffix; pathExist(failDB) { 48 | FuncLogError(nil, fmt.Errorf(funcT("Please confirm if [%s] needs to be manually renamed to [gpress.db]. If not, please manually delete [%s]"), failDB, failDB)) 49 | return false 50 | } 51 | 52 | fts5File := datadir + "fts5/libsimple" 53 | //注册fts5的simple分词器,建议使用jieba分词 54 | //需要 --tags "fts5" 55 | sql.Register("sqlite3_simple", &sqlite3.SQLiteDriver{ 56 | Extensions: []string{ 57 | fts5File, //不要加后缀,它会自己处理,这样代码也统一 58 | }, 59 | }) 60 | 61 | var err error 62 | dbDao, err = zorm.NewDBDao(&dbDaoConfig) 63 | if dbDao == nil || err != nil { //数据库初始化失败 64 | if db := datadir + "gpress.db"; pathExist(db) { 65 | _ = os.Rename(db, db+failSuffix) 66 | } 67 | return false 68 | } 69 | 70 | //初始化结巴分词的字典 71 | finder := zorm.NewFinder().Append("SELECT jieba_dict(?)", datadir+"dict") 72 | fts5jieba := "" 73 | _, err = zorm.QueryRow(context.Background(), finder, &fts5jieba) 74 | if err != nil { 75 | return false 76 | } 77 | 78 | finder = zorm.NewFinder().Append("select jieba_query(?)", "让数据自由一点点,让世界美好一点点") 79 | _, err = zorm.QueryRow(context.Background(), finder, &fts5jieba) 80 | if err != nil { 81 | return false 82 | } 83 | //fmt.Println(fts5jieba) 84 | isInit := pathExist(datadir + "gpress.db") 85 | if !isInit { //需要初始化数据库 86 | return isInit 87 | } 88 | 89 | if tableExist(tableContentName) { 90 | return true 91 | } 92 | 93 | sqlByte, err := os.ReadFile("gpressdatadir/gpress.sql") 94 | if err != nil { 95 | panic(err) 96 | } 97 | createTableSQL := string(sqlByte) 98 | if createTableSQL == "" { 99 | panic("gpressdatadir/gpress.sql " + funcT("File anomaly")) 100 | } 101 | 102 | ctx := context.Background() 103 | _, err = execNativeSQL(ctx, createTableSQL) 104 | if err != nil { 105 | panic(err) 106 | } 107 | 108 | return true 109 | } 110 | 111 | // tableExist 数据表是否存在 112 | func tableExist(tableName string) bool { 113 | finder := zorm.NewSelectFinder("sqlite_master", "count(*)").Append("WHERE type=? and name=?", "table", tableName) 114 | count := 0 115 | zorm.QueryRow(context.Background(), finder, &count) 116 | return count > 0 117 | } 118 | 119 | // updateTable 更新表数据 120 | func updateTable(ctx context.Context, newMap zorm.IEntityMap) error { 121 | _, err := zorm.Transaction(ctx, func(ctx context.Context) (interface{}, error) { 122 | _, err := zorm.UpdateEntityMap(ctx, newMap) 123 | return nil, err 124 | }) 125 | return err 126 | } 127 | 128 | // deleteById 根据Id删除数据 129 | func deleteById(ctx context.Context, tableName string, id string) error { 130 | finder := zorm.NewDeleteFinder(tableName).Append(" WHERE id=?", id) 131 | _, err := zorm.Transaction(ctx, func(ctx context.Context) (interface{}, error) { 132 | _, err := zorm.UpdateFinder(ctx, finder) 133 | return nil, err 134 | }) 135 | 136 | return err 137 | } 138 | 139 | // deleteAll 删除所有数据 140 | func deleteAll(ctx context.Context, tableName string) error { 141 | finder := zorm.NewDeleteFinder(tableName) 142 | _, err := zorm.Transaction(ctx, func(ctx context.Context) (interface{}, error) { 143 | _, err := zorm.UpdateFinder(ctx, finder) 144 | return nil, err 145 | }) 146 | 147 | return err 148 | } 149 | 150 | // execNativeSQL 执行SQL语句 151 | func execNativeSQL(ctx context.Context, nativeSQL string) (bool, error) { 152 | finder := zorm.NewFinder().Append(nativeSQL) 153 | finder.InjectionCheck = false 154 | _, err := zorm.Transaction(ctx, func(ctx context.Context) (interface{}, error) { 155 | _, err := zorm.UpdateFinder(ctx, finder) 156 | return nil, err 157 | }) 158 | if err != nil { 159 | return false, err 160 | } 161 | return true, nil 162 | } 163 | -------------------------------------------------------------------------------- /utilSignature_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 gpress Authors. 2 | // 3 | // This file is part of gpress. 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU Affero General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU Affero General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | package main 19 | 20 | import ( 21 | "crypto/ecdsa" 22 | "crypto/elliptic" 23 | "crypto/rand" 24 | "encoding/hex" 25 | "fmt" 26 | "math/big" 27 | "testing" 28 | ) 29 | 30 | func TestRecoverP256PublicKey(t *testing.T) { 31 | privateKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 32 | msg := "hello" 33 | hash := keccak256Hash([]byte(msg)) 34 | 35 | // 签名(强制添加恢复ID) 36 | r, s, _ := ecdsa.Sign(rand.Reader, privateKey, hash[:]) 37 | 38 | // 恢复公钥 39 | recoveryID := new(big.Int).Mod(privateKey.PublicKey.Y, big.NewInt(2)) // 奇偶性 40 | 41 | //EIP-155 v=35+2×ChainID+recoveryID 42 | v := 35 + 2*1 + uint(recoveryID.Int64()) 43 | // TODO 偶尔会恢复失败,原因待查 44 | publicKey, err := recoverP256PublicKey(hash[:], r, s, v) 45 | if err != nil { 46 | panic(err) 47 | } 48 | 49 | fmt.Println(hex.EncodeToString(publicKey.X.Bytes())) 50 | fmt.Println(hex.EncodeToString(privateKey.PublicKey.X.Bytes())) 51 | 52 | // 比较恢复的公钥与原始公钥 53 | if publicKey.X.Cmp(privateKey.PublicKey.X) != 0 || publicKey.Y.Cmp(privateKey.PublicKey.Y) != 0 { 54 | t.Error("恢复失败") 55 | } else { 56 | fmt.Println("恢复成功") 57 | } 58 | 59 | } 60 | func TestVerifySecp256r1Signature(t *testing.T) { 61 | // Example message to sign 62 | msg := "123" 63 | 64 | // Generate a key pair 65 | privateKey, err := GenerateKeyPair() 66 | if err != nil { 67 | fmt.Println("Error generating key pair:", err) 68 | return 69 | } 70 | 71 | prefix := fmt.Sprintf("\x86XuperChain Signed Message:\n%d%s", len(msg), msg) 72 | message := keccak256Hash([]byte(prefix)) 73 | 74 | // Sign the message 75 | signature, err := SignMessage(privateKey, message) 76 | if err != nil { 77 | fmt.Println("Error signing message:", err) 78 | return 79 | } 80 | 81 | // Output the message and signature 82 | fmt.Println("Signature:", signature) 83 | 84 | ok, _, err := verifySecp256r1Signature(msg, signature) 85 | fmt.Println(ok) 86 | if err != nil { 87 | t.Error(err) 88 | } 89 | 90 | } 91 | func TestVerifySecp256k1Signature(t *testing.T) { 92 | address := "0xbe153AE90F5f114EF48A0e4279c565Be726302F6" 93 | sign := "0x812a04f34f988692682412010dee232f7b09e4ce96a6a3a4c5a37373db008312213f882c2248cbfbdf16b75ec595aeb75c4f7fd743e5b061bcdac1cd6e1e64931b" 94 | msg := "123" 95 | 96 | ok, err := verifyEthereumSignature(address, msg, sign) 97 | fmt.Println(ok) 98 | if err != nil { 99 | t.Error(err) 100 | } 101 | 102 | } 103 | 104 | // SignMessage signs a given message using the private key and returns the signature as a base64 string. 105 | func SignMessage(privateKey *ecdsa.PrivateKey, hash []byte) (string, error) { 106 | 107 | // Sign the hash 108 | r, s, err := ecdsa.Sign(rand.Reader, privateKey, hash) 109 | if err != nil { 110 | return "", fmt.Errorf("failed to sign message: %v", err) 111 | } 112 | 113 | // Ensure s is in the lower half of the curve order to prevent malleability 114 | curveOrder := privateKey.Curve.Params().N 115 | halfCurveOrder := new(big.Int).Rsh(curveOrder, 1) 116 | if s.Cmp(halfCurveOrder) > 0 { 117 | s.Sub(curveOrder, s) 118 | } 119 | 120 | // Determine v (recovery identifier) based on the parity of the y-coordinate 121 | isOdd := privateKey.PublicKey.Y.Bit(0) == 1 122 | v := byte(27) // Ethereum uses 27 or 28 for the recovery id 123 | if isOdd { 124 | v = 28 125 | } 126 | 127 | // Prepare signature data: r (32 bytes) + s (32 bytes) + v (1 byte) 128 | rBytes := r.Bytes() 129 | sBytes := s.Bytes() 130 | 131 | fmt.Println("SignMessage-hash:", hex.EncodeToString(hash)) 132 | fmt.Println("SignMessage-r:", hex.EncodeToString(r.Bytes())) 133 | fmt.Println("SignMessage-s:", hex.EncodeToString(s.Bytes())) 134 | fmt.Println("SignMessage-x:", hex.EncodeToString(privateKey.PublicKey.X.Bytes())) 135 | fmt.Println("SignMessage-y:", hex.EncodeToString(privateKey.PublicKey.Y.Bytes())) 136 | 137 | // Ensure r and s are padded to 32 bytes 138 | rPadded := make([]byte, 32) 139 | sPadded := make([]byte, 32) 140 | copy(rPadded[32-len(rBytes):], rBytes) 141 | copy(sPadded[32-len(sBytes):], sBytes) 142 | 143 | // Concatenate r, s, and v into a single byte slice 144 | signature := append(rPadded, sPadded...) 145 | signature = append(signature, v) 146 | 147 | // Encode the signature to base64 for output 148 | return hex.EncodeToString(signature), nil 149 | } 150 | 151 | // GenerateKeyPair generates an ECDSA key pair using the Secp256r1 curve. 152 | func GenerateKeyPair() (*ecdsa.PrivateKey, error) { 153 | privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 154 | if err != nil { 155 | return nil, fmt.Errorf("failed to generate key pair: %v", err) 156 | } 157 | return privateKey, nil 158 | } 159 | -------------------------------------------------------------------------------- /utilZIP.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 gpress Authors. 2 | // 3 | // This file is part of gpress. 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU Affero General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU Affero General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | package main 19 | 20 | import ( 21 | "archive/zip" 22 | "io" 23 | "os" 24 | "path/filepath" 25 | "sync" 26 | ) 27 | 28 | // unzip 用于解压ZIP文件到指定目录 29 | func unzip(zipPath, destDir string) (err error) { 30 | // 打开ZIP文件 31 | r, err := zip.OpenReader(zipPath) 32 | if err != nil { 33 | return err 34 | } 35 | defer r.Close() 36 | 37 | // 创建解压目录,如果不存在 38 | if _, err := os.Stat(destDir); os.IsNotExist(err) { 39 | if err := os.MkdirAll(destDir, 0644); err != nil { 40 | return err 41 | } 42 | } 43 | 44 | var ( 45 | once sync.Once 46 | dir string 47 | ) 48 | defer func() { 49 | if err != nil && dir != "" { 50 | _ = os.RemoveAll(dir) 51 | } 52 | }() 53 | // 遍历ZIP文件中的所有文件 54 | for _, f := range r.File { 55 | // 构造文件路径 56 | filePath := filepath.Join(destDir, f.Name) 57 | 58 | // 如果文件是目录,则创建目录 59 | if f.FileInfo().IsDir() { 60 | once.Do(func() { 61 | if _, err := os.Stat(filePath); err != nil { 62 | // 说明是本次解压创建的文件夹 63 | dir = filePath 64 | } 65 | }) 66 | os.MkdirAll(filePath, 0644) 67 | continue 68 | } 69 | 70 | // 打开ZIP文件中的文件 71 | if err := func(f *zip.File) error { 72 | rc, err := f.Open() 73 | if err != nil { 74 | return err 75 | } 76 | defer rc.Close() 77 | 78 | // 创建解压后的文件,不可执行 79 | outFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) 80 | if err != nil { 81 | return err 82 | } 83 | defer outFile.Close() 84 | 85 | // 将文件内容写入到解压后的文件中 86 | if _, err := io.Copy(outFile, rc); err != nil { 87 | return err 88 | } 89 | return nil 90 | }(f); err != nil { 91 | return err 92 | } 93 | } 94 | 95 | return nil 96 | } 97 | -------------------------------------------------------------------------------- /utilzip_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 gpress Authors. 2 | // 3 | // This file is part of gpress. 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU Affero General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU Affero General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | package main 19 | 20 | import ( 21 | "os" 22 | "testing" 23 | ) 24 | 25 | func Test_unzip(t *testing.T) { 26 | type args struct { 27 | zipPath string 28 | destDir string 29 | } 30 | tests := []struct { 31 | name string 32 | args args 33 | wantExistDir bool 34 | wantDir string 35 | }{ 36 | { 37 | name: "解压成功 -> 创建或者覆盖目录和文件成功", 38 | args: args{ 39 | zipPath: "./gpressdatadir/gpress-geekdoc-master.zip", 40 | destDir: "gpressdatadir/", 41 | }, 42 | wantExistDir: true, 43 | wantDir: "./gpressdatadir/gpress-geekdoc-master/", 44 | }, 45 | { 46 | name: "解压失败-原目录存在 -> 不进行任何处理", 47 | args: args{ 48 | zipPath: "./gpressdatadir/gpress-geekdoc-master2.zip", 49 | destDir: "gpressdatadir/", 50 | }, 51 | wantExistDir: true, 52 | wantDir: "./gpressdatadir/gpress-geekdoc-master/", 53 | }, 54 | { 55 | name: "解压失败-原目录不存在 -> 删除原目录", 56 | args: args{ 57 | zipPath: "./gpressdatadir/gpress-geekdoc-master_副本.zip", // 这个副本 把里面的一个.html后缀的改成.zip 然后压缩 58 | destDir: "tmp/", 59 | }, 60 | wantExistDir: false, 61 | wantDir: "./tmp/gpress-geekdoc-master/", 62 | }, 63 | } 64 | for _, tt := range tests { 65 | t.Run(tt.name, func(t *testing.T) { 66 | _ = unzip(tt.args.zipPath, tt.args.destDir) 67 | if _, err := os.Stat(tt.wantDir); (err == nil) != tt.wantExistDir { 68 | t.Errorf("unzip() error = %v, wantExistDir %v", err, tt.wantExistDir) 69 | } 70 | }) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /wasm_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 gpress Authors. 2 | // 3 | // This file is part of gpress. 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU Affero General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU Affero General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | package main 19 | 20 | import ( 21 | "context" 22 | "fmt" 23 | "os" 24 | "testing" 25 | 26 | "github.com/tetratelabs/wazero" 27 | "github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1" 28 | ) 29 | 30 | func TestWasmAdd(t *testing.T) { 31 | ctx := context.Background() 32 | // JIT模式,性能一般,但是兼容性好 33 | //r := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfigInterpreter()) 34 | // AOT编译模式,性能好,存在操作系统兼容性问题 35 | r := wazero.NewRuntime(ctx) 36 | defer r.Close(ctx) 37 | 38 | bytes, err := os.ReadFile(datadir + "/wasm/add.wasm") 39 | if err != nil { 40 | t.Error(err) 41 | } 42 | // Instantiate WASI, which implements host functions needed for TinyGo to 43 | // implement `panic`. 44 | wasi_snapshot_preview1.MustInstantiate(ctx, r) 45 | 46 | // go 1.24 配置初始化函数 47 | config := wazero.NewModuleConfig().WithStartFunctions("_initialize") 48 | mod, err := r.InstantiateWithConfig(ctx, bytes, config) 49 | 50 | // tinygo 51 | //mod, err := r.Instantiate(ctx, bytes) 52 | if err != nil { 53 | t.Error(err) 54 | } 55 | res, err := mod.ExportedFunction("add").Call(ctx, 100, 200) 56 | //res, err := mod.ExportedFunction("add").Call(ctx, api.EncodeI32(100), api.EncodeI32(200)) 57 | if err != nil { 58 | t.Error(err) 59 | } 60 | fmt.Println(res[0]) 61 | } 62 | --------------------------------------------------------------------------------