├── src
├── application
│ ├── modules
│ │ └── README.md
│ ├── models
│ │ ├── tables
│ │ │ ├── admin_role_priv.go
│ │ │ ├── search_m_a_e.go
│ │ │ ├── category_priv.go
│ │ │ ├── advert_space.go
│ │ │ ├── attachments.go
│ │ │ ├── admin_role.go
│ │ │ ├── position.go
│ │ │ ├── level.go
│ │ │ ├── wechat_material.go
│ │ │ ├── wechat_qrcode.go
│ │ │ ├── district.go
│ │ │ ├── page.go
│ │ │ ├── tags.go
│ │ │ ├── department.go
│ │ │ ├── wechat_account.go
│ │ │ ├── request_log.go
│ │ │ ├── log.go
│ │ │ ├── attachment_type.go
│ │ │ ├── wechat_log.go
│ │ │ ├── advert.go
│ │ │ ├── wechat_msg_reply_rule.go
│ │ │ ├── wechat_user.go
│ │ │ ├── link.go
│ │ │ ├── document_model.go
│ │ │ ├── setting.go
│ │ │ ├── admin.go
│ │ │ ├── document_model_field.go
│ │ │ ├── content.go
│ │ │ ├── dict_category.go
│ │ │ ├── wechat_template.go
│ │ │ ├── menu.go
│ │ │ ├── category.go
│ │ │ ├── plugin.go
│ │ │ ├── todo.go
│ │ │ ├── member.go
│ │ │ └── define.go
│ │ ├── base_model.go
│ │ ├── wechat_member_model.go
│ │ ├── document_field_model.go
│ │ ├── document_dsl_model.go
│ │ ├── page_model.go
│ │ ├── attachments_model.go
│ │ ├── log_model.go
│ │ ├── member_model.go
│ │ ├── ad_model.go
│ │ ├── link_model.go
│ │ ├── adspace_model.go
│ │ ├── admin_model.go
│ │ ├── admin_role_model.go
│ │ └── menu_model.go
│ ├── controllers
│ │ ├── backend
│ │ │ ├── webssh
│ │ │ │ ├── common
│ │ │ │ │ ├── core
│ │ │ │ │ │ ├── server.go
│ │ │ │ │ │ └── helper.go
│ │ │ │ │ ├── jwt.go
│ │ │ │ │ └── aes.go
│ │ │ │ ├── Apiform
│ │ │ │ │ ├── Response.go
│ │ │ │ │ └── Api.go
│ │ │ │ ├── tables
│ │ │ │ │ ├── user.go
│ │ │ │ │ └── server.go
│ │ │ │ ├── errcode
│ │ │ │ │ └── Err.go
│ │ │ │ ├── define.go
│ │ │ │ ├── route.go
│ │ │ │ ├── ssh_controller.go
│ │ │ │ └── middleware
│ │ │ │ │ └── Auth.go
│ │ │ ├── wechat
│ │ │ │ ├── dto
│ │ │ │ │ └── dto.go
│ │ │ │ ├── README.md
│ │ │ │ ├── response.go
│ │ │ │ ├── cacher.go
│ │ │ │ ├── route.go
│ │ │ │ ├── rule_controller.go
│ │ │ │ ├── menu_controller.go
│ │ │ │ ├── common.go
│ │ │ │ ├── account_controller.go
│ │ │ │ ├── qrcode_ controller.go
│ │ │ │ └── define.go
│ │ │ ├── filemanager
│ │ │ │ ├── route.go
│ │ │ │ └── tables
│ │ │ │ │ └── file_manager_account.go
│ │ │ ├── link_controller.go
│ │ │ ├── tags_controller.go
│ │ │ ├── position_controller.go
│ │ │ ├── im_session_controller.go
│ │ │ ├── attachment_type_controller.go
│ │ │ ├── level_controller.go
│ │ │ ├── errlog_controller.go
│ │ │ ├── ad_controller.go
│ │ │ ├── log_controller.go
│ │ │ ├── menu_controller.go
│ │ │ ├── member_controller.go
│ │ │ ├── ad_space_controller.go
│ │ │ ├── member_group_controller.go
│ │ │ ├── request_params.go
│ │ │ ├── helper.go
│ │ │ ├── department_controller.go
│ │ │ ├── attachment_controller.go
│ │ │ ├── dict_category_controller.go
│ │ │ ├── database_backup_controller.go
│ │ │ ├── login_controller.go
│ │ │ ├── const.go
│ │ │ └── dict_controller.go
│ │ ├── middleware
│ │ │ ├── apidoc
│ │ │ │ ├── export
│ │ │ │ │ ├── intf.go
│ │ │ │ │ └── swag.go
│ │ │ │ ├── entity.go
│ │ │ │ └── parse.go
│ │ │ ├── rbac_models.conf
│ │ │ ├── pprof.go
│ │ │ ├── limiter.go
│ │ │ ├── statsviz.go
│ │ │ ├── data.go
│ │ │ ├── cors.go
│ │ │ └── verify_token.go
│ │ ├── taglibs
│ │ │ ├── hotwords.go
│ │ │ ├── tags.go
│ │ │ ├── TopType.go
│ │ │ ├── myad.go
│ │ │ ├── type.go
│ │ │ ├── flink.go
│ │ │ ├── query.go
│ │ │ ├── pagelist.go
│ │ │ ├── position.go
│ │ │ ├── adlist.go
│ │ │ ├── likearticle.go
│ │ │ ├── helper.go
│ │ │ ├── channel.go
│ │ │ ├── channelartlist.go
│ │ │ └── list.go
│ │ ├── helper.go
│ │ ├── frontend
│ │ │ ├── fesc_controller.go
│ │ │ ├── index.go
│ │ │ ├── click.go
│ │ │ ├── page.go
│ │ │ └── list.go
│ │ ├── service_key.go
│ │ ├── cache_key.go
│ │ └── tplfun
│ │ │ └── func.go
│ └── plugins
│ │ └── task
│ │ ├── cmd
│ │ ├── make_task.go
│ │ └── task.go
│ │ ├── manager
│ │ └── logger.go
│ │ ├── config.json
│ │ └── table
│ │ └── table.go
├── common
│ ├── search
│ │ ├── search.go
│ │ ├── nullsearch.go
│ │ └── zincsearch.go
│ ├── enum
│ │ ├── enum_test.go
│ │ └── enum.go
│ ├── message
│ │ ├── message_intf.go
│ │ ├── null.go
│ │ ├── email.go
│ │ └── sms.go
│ ├── oncer
│ │ └── oncer.go
│ ├── storage
│ │ ├── storage_intf.go
│ │ └── storage_ftp_test.go
│ ├── helper
│ │ └── service.go
│ ├── template
│ │ └── gktemplate.go
│ └── logger
│ │ └── pinecms_logger.go
├── router
│ ├── web.go
│ └── module.go
└── server
│ └── server.go
├── resources
├── themes
│ └── default
│ │ ├── thumb.png
│ │ ├── footer.jet
│ │ ├── page.jet
│ │ ├── error.jet
│ │ ├── right.jet
│ │ ├── head2.jet
│ │ └── head.jet
└── configs
│ ├── database.yml.dist
│ └── application.yml
├── .gitattributes
├── cmd
├── plugin
│ ├── plugin.go
│ └── build_plugin.go
├── server
│ ├── run.go
│ ├── remove.go
│ ├── stop.go
│ ├── install.go
│ ├── start.go
│ ├── status.go
│ ├── serve.go
│ └── service.go
├── version
│ └── version.go
├── annotation.go
├── root.go
├── crud
│ ├── crud_format_stub.go
│ └── crud_stub.go
├── example.go
├── dede
│ └── import.go
└── util
│ └── util.go
├── tasks
└── echo.sh
├── main.go
├── .vscode
└── launch.json
├── .gitignore
└── tests
└── ast_test.go
/src/application/modules/README.md:
--------------------------------------------------------------------------------
1 | # 模块目录 #
--------------------------------------------------------------------------------
/resources/themes/default/thumb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiusin/pinecms/HEAD/resources/themes/default/thumb.png
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.css linguist-language=go
2 | *.js linguist-language=go
3 | *.sql linguist-language=go
4 | *.html linguist-language=go
--------------------------------------------------------------------------------
/src/application/models/tables/admin_role_priv.go:
--------------------------------------------------------------------------------
1 | package tables
2 |
3 | type AdminRolePriv struct {
4 | RoleId int64
5 | MenuId int64
6 | }
7 |
--------------------------------------------------------------------------------
/src/application/models/base_model.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "xorm.io/builder"
5 | )
6 |
7 | type BaseModel struct {
8 | builder.Between
9 | }
10 |
--------------------------------------------------------------------------------
/src/application/controllers/backend/webssh/common/core/server.go:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | type Server struct {
4 | Ip string
5 | Port int
6 | User string
7 | Passwd string
8 | }
--------------------------------------------------------------------------------
/src/application/models/tables/search_m_a_e.go:
--------------------------------------------------------------------------------
1 | package tables
2 |
3 | type SearchMAE struct {
4 | Mid int64 `json:"mid"`
5 | Aid int64 `json:"aid"`
6 | Eid string `json:"eid"`
7 | }
8 |
--------------------------------------------------------------------------------
/src/application/controllers/middleware/apidoc/export/intf.go:
--------------------------------------------------------------------------------
1 | package export
2 |
3 | import "github.com/xiusin/pine"
4 |
5 | type ExportIntf interface {
6 | SetContext(*pine.Context)
7 | Export()
8 | }
9 |
--------------------------------------------------------------------------------
/src/application/models/tables/category_priv.go:
--------------------------------------------------------------------------------
1 | package tables
2 |
3 | // CategoryPriv 分类权限
4 | type CategoryPriv struct {
5 | Catid int64 `xorm:"pk"`
6 | Roleid int64
7 | IsAdmin int64
8 | Action string
9 | }
10 |
--------------------------------------------------------------------------------
/src/application/models/tables/advert_space.go:
--------------------------------------------------------------------------------
1 | package tables
2 |
3 | type AdvertSpace struct {
4 | Id int64 `json:"id"`
5 | Name string `json:"name"`
6 | Key string `json:"key"`
7 | Remark string `json:"remark"`
8 | }
9 |
--------------------------------------------------------------------------------
/src/application/models/tables/attachments.go:
--------------------------------------------------------------------------------
1 | package tables
2 |
3 | type AttachmentType struct {
4 | Id int64 `json:"id"`
5 | Name string `json:"name"`
6 | CreatedAt LocalTime `json:"created_at" xorm:"created"`
7 | }
8 |
--------------------------------------------------------------------------------
/src/application/controllers/backend/webssh/Apiform/Response.go:
--------------------------------------------------------------------------------
1 | package Apiform
2 |
3 | type Resp struct {
4 | Code int `json:"code"`
5 | Data any `json:"data"`
6 | Msg string `json:"msg"`
7 | Token string `json:"token"`
8 | }
9 |
--------------------------------------------------------------------------------
/src/application/controllers/backend/wechat/dto/dto.go:
--------------------------------------------------------------------------------
1 | package dto
2 |
3 | type RuleSearch struct {
4 | Page int `json:"page"`
5 | PageSize int `json:"page_size"`
6 | Param struct {
7 | Appid string `json:"appid"`
8 | } `json:"param"`
9 | }
10 |
--------------------------------------------------------------------------------
/cmd/plugin/plugin.go:
--------------------------------------------------------------------------------
1 | package plugin
2 |
3 | import (
4 | "github.com/spf13/cobra"
5 | )
6 |
7 | var Cmd = &cobra.Command{
8 | Use: "plugin",
9 | Short: "插件模块管理",
10 | }
11 |
12 | func init() {
13 | Cmd.AddCommand(makePluginCmd, buildPluginCmd)
14 | }
15 |
--------------------------------------------------------------------------------
/cmd/server/run.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import "github.com/spf13/cobra"
4 |
5 | var runCmd = &cobra.Command{
6 | Use: "run",
7 | Short: "启动后端服务",
8 | Run: func(cmd *cobra.Command, args []string) {
9 | serv.Manage([]string{"run"}, cmd.UsageString())
10 | },
11 | }
12 |
--------------------------------------------------------------------------------
/src/application/plugins/task/cmd/make_task.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "github.com/spf13/cobra"
5 | )
6 |
7 | var makeTaskCmd = &cobra.Command{
8 | Use: "make",
9 | Short: "创建任务源文件",
10 | Run: func(cmd *cobra.Command, args []string) {
11 |
12 | },
13 | }
14 |
--------------------------------------------------------------------------------
/src/application/controllers/backend/webssh/tables/user.go:
--------------------------------------------------------------------------------
1 | package tables
2 |
3 | type SSHUser struct {
4 | Id int64 `xorm:"pk autoincr"`
5 | Phone int64 `xorm:"unique"`
6 | Email *string `xorm:"unique"`
7 | Password string
8 | Servers []SSHServer
9 | }
10 |
--------------------------------------------------------------------------------
/cmd/server/remove.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import "github.com/spf13/cobra"
4 |
5 | var stopCmd = &cobra.Command{
6 | Use: "stop",
7 | Short: "停止守护进程",
8 | Run: func(cmd *cobra.Command, args []string) {
9 | serv.Manage([]string{"stop"}, cmd.UsageString())
10 | },
11 | }
12 |
--------------------------------------------------------------------------------
/cmd/server/stop.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import "github.com/spf13/cobra"
4 |
5 | var removeCmd = &cobra.Command{
6 | Use: "remove",
7 | Short: "移除服务",
8 | Run: func(cmd *cobra.Command, args []string) {
9 | serv.Manage([]string{"remove"}, cmd.UsageString())
10 | },
11 | }
12 |
--------------------------------------------------------------------------------
/cmd/server/install.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import "github.com/spf13/cobra"
4 |
5 | var installCmd = &cobra.Command{
6 | Use: "install",
7 | Short: "安装服务",
8 | Run: func(cmd *cobra.Command, args []string) {
9 | serv.Manage([]string{"install"}, cmd.UsageString())
10 | },
11 | }
12 |
--------------------------------------------------------------------------------
/cmd/server/start.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import "github.com/spf13/cobra"
4 |
5 | var startCmd = &cobra.Command{
6 | Use: "start",
7 | Short: "守护进程方式打开服务",
8 | Run: func(cmd *cobra.Command, args []string) {
9 | serv.Manage([]string{"start"}, cmd.UsageString())
10 | },
11 | }
12 |
--------------------------------------------------------------------------------
/cmd/server/status.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import "github.com/spf13/cobra"
4 |
5 | var statusCmd = &cobra.Command{
6 | Use: "status",
7 | Short: "查看守护进程状态",
8 | Run: func(cmd *cobra.Command, args []string) {
9 | serv.Manage([]string{"status"}, cmd.UsageString())
10 | },
11 | }
12 |
--------------------------------------------------------------------------------
/src/common/search/search.go:
--------------------------------------------------------------------------------
1 | package search
2 |
3 |
4 | type ISearch interface {
5 | Search(index string, query any) (any, error)
6 | Index(index string, doc map[string]any) (string, error)
7 | Update(index, id string, doc map[string]any) error
8 | Delete(index string, id string) error
9 | }
10 |
--------------------------------------------------------------------------------
/src/application/controllers/backend/wechat/README.md:
--------------------------------------------------------------------------------
1 | # 微信模块
2 |
3 | 开发期间本地可以使用 `FRP` 或 `花生壳` 软件做内网穿透代理
4 |
5 | ```ini
6 | [common]
7 | server_addr = 100.100.100.100 # 服务端IP
8 | server_port = 7000 # 服务端端口
9 |
10 | [web]
11 | type = http
12 | local_port = 2019
13 | custom_domains = wechat.xxx.com
14 | ```
15 |
--------------------------------------------------------------------------------
/src/application/controllers/middleware/apidoc/export/swag.go:
--------------------------------------------------------------------------------
1 | package export
2 |
3 | import "github.com/xiusin/pine"
4 |
5 | type Swag struct {
6 | template string
7 | }
8 |
9 | func (s *Swag) SetContext(ctx *pine.Context) {
10 | s.template = ""
11 | }
12 |
13 | func (s *Swag) Export() {
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/tasks/echo.sh:
--------------------------------------------------------------------------------
1 | package tasks
2 |
3 | import (
4 | "encoding/json"
5 | "pinecms"
6 | )
7 |
8 | // 脚本样例
9 | func Run(orm *pinecms.DB) (string, error) {
10 | var data = map[string]interface{}{}
11 | orm.Table("pinecms_task_info").Get(&data)
12 | d, _ := json.Marshal(&data)
13 | return string(d), nil
14 | }
15 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | //go:generate go run main.go src
2 | package main
3 |
4 | import (
5 | "github.com/bytedance/sonic"
6 | "github.com/xiusin/pine/cache"
7 | "github.com/xiusin/pinecms/cmd"
8 | )
9 |
10 | func init() {
11 | cache.SetTranscoderFunc(sonic.Marshal, sonic.Unmarshal)
12 | }
13 |
14 | func main() {
15 | cmd.Execute()
16 | }
17 |
--------------------------------------------------------------------------------
/cmd/version/version.go:
--------------------------------------------------------------------------------
1 | package version
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/spf13/cobra"
7 | )
8 |
9 | const Version = "dev 0.2.0"
10 |
11 | var Cmd = &cobra.Command{
12 | Use: "version",
13 | Short: "显示版本号",
14 | Run: func(cmd *cobra.Command, args []string) {
15 | fmt.Println("version: " + Version)
16 | },
17 | }
18 |
--------------------------------------------------------------------------------
/src/application/controllers/middleware/rbac_models.conf:
--------------------------------------------------------------------------------
1 | [request_definition]
2 | r = sub, obj, act
3 |
4 | [policy_definition]
5 | p = sub, obj, act
6 |
7 | [role_definition]
8 | g = _, _
9 |
10 | [policy_effect]
11 | e = some(where (p.eft == allow))
12 |
13 | [matchers]
14 | m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act || r.sub == "1"
15 |
--------------------------------------------------------------------------------
/src/application/plugins/task/cmd/task.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "github.com/spf13/cobra"
5 | )
6 |
7 | const taskDir = "tasks"
8 |
9 | var TaskCmd = &cobra.Command{
10 | Use: "task",
11 | Short: "任务管理模块",
12 | Run: func(cmd *cobra.Command, args []string) {
13 | },
14 | }
15 |
16 | func init() {
17 | TaskCmd.AddCommand(makeTaskCmd)
18 | }
19 |
--------------------------------------------------------------------------------
/src/application/controllers/backend/webssh/tables/server.go:
--------------------------------------------------------------------------------
1 | package tables
2 |
3 | import (
4 | "time"
5 | )
6 |
7 | type SSHServer struct {
8 | Id int64 `xorm:"pk autoincr"`
9 | Nickname string
10 | Ip string
11 | Port int
12 | Username string
13 | Password string `json:"-"`
14 | BindUser int64 `json:"-"`
15 | BeforeTime time.Time
16 | }
17 |
--------------------------------------------------------------------------------
/cmd/annotation.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "github.com/spf13/cobra"
5 |
6 | annotation "github.com/xiusin/go-annotation/pkg"
7 | _ "github.com/xiusin/pinecms/cmd/util/annotations/rest"
8 | )
9 |
10 | var annotationsCmd = &cobra.Command{
11 | Use: "src",
12 | Short: "注解路由生成",
13 | Run: func(cmd *cobra.Command, args []string) {
14 | annotation.Process()
15 | },
16 | }
17 |
--------------------------------------------------------------------------------
/src/application/models/tables/admin_role.go:
--------------------------------------------------------------------------------
1 | package tables
2 |
3 | type AdminRole struct {
4 | Id int64 `xorm:"pk autoincr" json:"id"`
5 | Rolename string `json:"rolename"`
6 | Description string `json:"description"`
7 | Listorder int64 `json:"listorder"`
8 | Disabled int64 `json:"disabled"`
9 | MenuIdList []int64 `json:"menuIdList" xorm:"json menu_ids"`
10 | }
11 |
--------------------------------------------------------------------------------
/src/application/models/tables/position.go:
--------------------------------------------------------------------------------
1 | package tables
2 |
3 | //Position 职位管理
4 | type Position struct {
5 | Id int64 `json:"id"`
6 | Name string `json:"name" schema:"name" xorm:"varchar(25)"`
7 | Code string `json:"code" xorm:"varchar(25)"`
8 | Listorder uint `json:"listorder"`
9 | Status bool `json:"status"`
10 | Remark string `json:"remark" xorm:"text"`
11 | }
12 |
--------------------------------------------------------------------------------
/src/application/models/tables/level.go:
--------------------------------------------------------------------------------
1 | package tables
2 |
3 | // Level 职级
4 | type Level struct {
5 | Id int64 `json:"id"`
6 | Name string `json:"name" schema:"name" xorm:"varchar(25)"`
7 | Listorder uint `json:"listorder"`
8 | Status bool `json:"status"`
9 | CreatedAt LocalTime `json:"created" xorm:"created"`
10 | UpdatedAt *LocalTime `json:"updated" xorm:"updated"`
11 | }
12 |
--------------------------------------------------------------------------------
/src/application/controllers/backend/webssh/errcode/Err.go:
--------------------------------------------------------------------------------
1 | package errcode
2 |
3 | const (
4 | C_nil_err = 200 //成功
5 | C_from_err = 100 //表单错误
6 | C_phone_err = 101 //手机号错误
7 | C_code_err = 102 //验证码错误
8 | S_send_err = 300 //发送验证码错误
9 | S_auth_err = 301 //Token权限校验失败
10 | S_auth_fmt_err = 302 //Header Token格式错误
11 | S_Verify_err = 303 //验证码校验失败
12 | S_Db_err = 304
13 | )
14 |
--------------------------------------------------------------------------------
/src/common/enum/enum_test.go:
--------------------------------------------------------------------------------
1 | package enum
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 | )
7 |
8 | type Ttt struct {
9 | CustomValue Type[string] `enum:"name_custom_value"`
10 | KeyAsValue Type[string]
11 | }
12 |
13 | var vv = New[Ttt](nil)
14 |
15 | func TestEnum(_ *testing.T) {
16 | EnumAsParam(vv.CustomValue)
17 | }
18 |
19 | func EnumAsParam(dist TypeContract[string]) {
20 | fmt.Println(dist.Value())
21 | }
22 |
--------------------------------------------------------------------------------
/src/application/plugins/task/manager/logger.go:
--------------------------------------------------------------------------------
1 | package manager
2 |
3 | import (
4 | "github.com/xiusin/pine/contracts"
5 | )
6 |
7 | type logger struct {
8 | contracts.Logger
9 | }
10 |
11 | func (l logger) Info(msg string, keysAndValues ...any) {
12 | l.Logger.Warn(msg, keysAndValues)
13 | }
14 |
15 | func (l logger) Error(err error, msg string, keysAndValues ...any) {
16 | l.Logger.Error("%s: 错误: %s, 参数: %s", msg, err, keysAndValues)
17 | }
18 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // 使用 IntelliSense 了解相关属性。
3 | // 悬停以查看现有属性的描述。
4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "Launch Package",
9 | "type": "go",
10 | "request": "launch",
11 | "mode": "auto",
12 | "cwd": "${workspaceFolder}",
13 | "program": "main.go"
14 | }
15 | ]
16 | }
--------------------------------------------------------------------------------
/src/application/controllers/backend/filemanager/route.go:
--------------------------------------------------------------------------------
1 | package filemanager
2 |
3 | import (
4 | "github.com/xiusin/pine"
5 | )
6 |
7 | func InitRouter(app *pine.Application, router *pine.Router) {
8 | InitInstall(app, "/uploads/", "./resources/assets/uploads/")
9 | //app.StaticFS("/fm/ui", assets, "dist", "index.html")
10 | app.Static("/fm/ui", "src/application/controllers/backend/filemanager/dist", 2)
11 | app.Handle(new(FileManagerController), "/filemanager")
12 | }
13 |
--------------------------------------------------------------------------------
/src/application/plugins/task/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "任务管理",
3 | "author": "xiusin",
4 | "contact": "18818818818",
5 | "description": "实现任务管理功能",
6 | "version": "dev 0.0.1",
7 | "page": "# 任务管理模块教程\n\n## 安装 \n1. 下载编译完成的**task.so**放置到程序同级目录**plugins**下\n2. 程序每隔10秒扫描一次目录,自动注册程序信息到插件系统\n3. 本模块需要在开发环境时导入, 需要自动导出**pages.zip** 放到前端开发目录下使用\n\n## 修改路由前缀\n1. 默认路由前缀和插件名称一致,如果修改需要重启程序以载入.so实现. \n2. 需要重新导入router.ts注入修改后的路由地址 (不建议修改)\n\n## 结束语\n如有问题望请各位PR或提交到个人邮箱或电话"
8 | }
9 |
--------------------------------------------------------------------------------
/src/application/models/tables/wechat_material.go:
--------------------------------------------------------------------------------
1 | package tables
2 |
3 | type WechatMaterial struct {
4 | Id int64 `json:"id" xorm:"pk autoincr"`
5 | Appid string `json:"appid" xorm:"char(20) not null comment('appid')"`
6 | Type string `json:"type" xorm:"varchar(5) comment('媒体素材类型')"`
7 | MediaId string `json:"media_id" xorm:"varchar(40) comment('媒体ID')"`
8 | Url string `json:"url"`
9 | UpdateTime LocalTime `json:"update_time"`
10 | }
11 |
--------------------------------------------------------------------------------
/src/application/controllers/middleware/pprof.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "github.com/valyala/fasthttp/pprofhandler"
5 | "github.com/xiusin/pine"
6 | )
7 |
8 | func Pprof(shouldUsePprofHandlerFn func(ctx *pine.Context) bool) pine.Handler {
9 | return func(ctx *pine.Context) {
10 | if shouldUsePprofHandlerFn != nil && shouldUsePprofHandlerFn(ctx) {
11 | pprofhandler.PprofHandler(ctx.RequestCtx)
12 | ctx.Stop()
13 | return
14 | }
15 | ctx.Next()
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/common/search/nullsearch.go:
--------------------------------------------------------------------------------
1 | package search
2 |
3 | type NullSearch struct {
4 | }
5 |
6 | func (n *NullSearch) Search(index string, query any) (any, error) {
7 | return nil, nil
8 | }
9 |
10 | func (n *NullSearch) Index(index string, doc map[string]any) (string, error) {
11 | return "", nil
12 | }
13 |
14 | func (n *NullSearch) Update(index string, id string, doc map[string]any) error {
15 | return nil
16 | }
17 |
18 | func (n *NullSearch) Delete(index string, id string) error {
19 | return nil
20 | }
21 |
--------------------------------------------------------------------------------
/src/application/controllers/backend/link_controller.go:
--------------------------------------------------------------------------------
1 | package backend
2 |
3 | import "github.com/xiusin/pinecms/src/application/models/tables"
4 |
5 | type LinkController struct {
6 | BaseController
7 | }
8 |
9 | func (c *LinkController) Construct() {
10 | c.KeywordsSearch = []SearchFieldDsl{
11 | {Field: "name", Op: "LIKE", DataExp: "%$?%"},
12 | }
13 | c.Table = &tables.Link{}
14 | c.Entries = &[]tables.Link{}
15 | c.ApiEntityName = "友链"
16 | c.Group = "友情链接管理"
17 | c.BaseController.Construct()
18 | }
19 |
--------------------------------------------------------------------------------
/src/application/controllers/backend/tags_controller.go:
--------------------------------------------------------------------------------
1 | package backend
2 |
3 | import "github.com/xiusin/pinecms/src/application/models/tables"
4 |
5 | type TagsController struct {
6 | BaseController
7 | }
8 |
9 | func (c *TagsController) Construct() {
10 | c.KeywordsSearch = []SearchFieldDsl{
11 | {Field: "name", Op: "LIKE", DataExp: "%$?%"},
12 | }
13 | c.Table = &tables.Tags{}
14 | c.Entries = &[]*tables.Tags{}
15 | c.ApiEntityName = "标签"
16 | c.Group = "标签管理"
17 | c.BaseController.Construct()
18 | }
19 |
--------------------------------------------------------------------------------
/src/application/controllers/middleware/limiter.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "net/http"
5 | "time"
6 |
7 | "github.com/xiusin/pine"
8 | "golang.org/x/time/rate"
9 | )
10 |
11 | func Limiter(maxBurstSize int) pine.Handler {
12 | limiter := rate.NewLimiter(rate.Every(time.Second), maxBurstSize)
13 | return func(ctx *pine.Context) {
14 | if limiter.Allow() {
15 | ctx.Next()
16 | } else {
17 | ctx.Abort(http.StatusTooManyRequests, "The service is temporarily unavailable")
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/router/web.go:
--------------------------------------------------------------------------------
1 | package router
2 |
3 | import (
4 | "path/filepath"
5 |
6 | "github.com/xiusin/pinecms/src/application/controllers/frontend"
7 |
8 | "github.com/xiusin/pine"
9 | "github.com/xiusin/pinecms/src/config"
10 | )
11 |
12 | func InitStatics(app *pine.Application) {
13 | for _, static := range config.App().Statics {
14 | app.Static(static.Route, filepath.FromSlash(static.Path), 1)
15 | }
16 | }
17 |
18 | func InitRouter(app *pine.Application) {
19 | app.Handle(new(frontend.IndexController))
20 | }
21 |
--------------------------------------------------------------------------------
/src/application/models/tables/wechat_qrcode.go:
--------------------------------------------------------------------------------
1 | package tables
2 |
3 | type WechatQrcode struct {
4 | Id int64 `json:"id" xorm:"pk autoincr"`
5 | AppId string `json:"appid" xorm:"char(20) appid"`
6 | IsTemp bool `json:"is_temp" xorm:"comment('是否为临时二维码')"`
7 | SceneStr string `json:"scene_str"`
8 | Ticket string `json:"ticket"`
9 | Url string `json:"url"`
10 | ExpireTime LocalTime `json:"expire_time"`
11 | CreatedAt LocalTime `json:"created_at" xorm:"created"`
12 | }
13 |
--------------------------------------------------------------------------------
/src/application/controllers/taglibs/hotwords.go:
--------------------------------------------------------------------------------
1 | package taglibs
2 |
3 | import (
4 | "github.com/CloudyKit/jet"
5 | "github.com/xiusin/pine"
6 | "reflect"
7 | "strings"
8 | )
9 |
10 | func Tags(args jet.Arguments) reflect.Value {
11 | if !checkArgType(&args) {
12 | return defaultArrReturnVal
13 | }
14 | defer func() {
15 | if err := recover(); err != nil {
16 | pine.Logger().Error("HotWords Failed", err)
17 | }
18 | }()
19 | tags := strings.Split(args.Get(0).String(), ",")
20 | return reflect.ValueOf(tags)
21 | }
22 |
--------------------------------------------------------------------------------
/src/application/models/tables/district.go:
--------------------------------------------------------------------------------
1 | package tables
2 |
3 | type District struct {
4 | Id int64 `xorm:"pk autoincr" json:"id"`
5 | Name string `json:"name"`
6 | ParentId int64 `json:"parent_id"`
7 | Initial string `json:"initial"`
8 | Initials string `json:"initials"`
9 | PinYin string `json:"pinyin" xorm:"pinyin"`
10 | Extra string `json:"extra"`
11 | Suffix string `json:"suffix"`
12 | Code string `json:"code"`
13 | AreaCode string `json:"area_code" xorm:"area_code"`
14 | OrderNum uint `json:"order" xorm:"order"`
15 | }
16 |
--------------------------------------------------------------------------------
/resources/configs/database.yml.dist:
--------------------------------------------------------------------------------
1 | db:
2 | dsn: "root:+3ak9,XhB&oD@tcp(127.0.0.1:3306)/pinecms?charset=utf8mb4"
3 | prefix: "pinecms_"
4 | driver: "mysql" # 目前只支持Mysql
5 |
6 | orm:
7 | show_sql: true
8 | show_exec_time: true
9 | max_open_conns: 10
10 | max_idle_conns: 10
11 |
12 | redis:
13 | host: 127.0.0.1
14 | port: 6379
15 | password:
16 | index: 1
17 | max_active: 100
18 | max_idle: 5
19 | idle_timeout: 60
20 |
21 | elastic:
22 | url: "http://127.0.0.1:9200"
23 | username: "elastic"
24 | password: "elastic"
--------------------------------------------------------------------------------
/src/application/controllers/helper.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "github.com/gbrlsnchs/jwt/v3"
5 | "github.com/xiusin/pine/di"
6 | )
7 |
8 | // LoginAdminPayload 登录JWT载体
9 | type LoginAdminPayload struct {
10 | jwt.Payload
11 | Id int64 `json:"id"`
12 | AdminId int64 `json:"admin_id"`
13 | AdminName string `json:"admin_name"`
14 | RoleID []int64 `json:"role_id"`
15 | }
16 |
17 | // GetTableName 获取表名
18 | func GetTableName(name string) string {
19 | tablePrefix := di.MustGet(ServiceTablePrefix).(string)
20 | return tablePrefix + name
21 | }
22 |
--------------------------------------------------------------------------------
/src/application/controllers/backend/position_controller.go:
--------------------------------------------------------------------------------
1 | package backend
2 |
3 | import (
4 | "github.com/xiusin/pinecms/src/application/models/tables"
5 | )
6 |
7 | type PositionController struct {
8 | BaseController
9 | }
10 |
11 | func (c *PositionController) Construct() {
12 | c.Table = &tables.Position{}
13 | c.Entries = &[]tables.Position{}
14 | c.Group = "系统管理"
15 | c.SubGroup = "岗位管理"
16 | c.ApiEntityName = "岗位"
17 | c.BaseController.Construct()
18 | c.KeywordsSearch = []SearchFieldDsl{
19 | {Field: "name", Op: "="},
20 | {Field: "code", Op: "="},
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/application/models/tables/page.go:
--------------------------------------------------------------------------------
1 | package tables
2 |
3 | type Page struct {
4 | Id int64 `xorm:"pk comment('栏目ID')" json:"id"`
5 | Title string `json:"title" xorm:"comment('页面标题') varchar(100)"`
6 | Keywords string `json:"keywords" xorm:"comment('页面关键字') varchar(255)"`
7 | Description string `json:"description" xorm:"comment('页面描述') varchar(255)"`
8 | Content string `json:"content" xorm:"comment('页面内容') text"`
9 | CreatedAt LocalTime `json:"created_at" xorm:"created"`
10 | UpdatedAt LocalTime `json:"updated_at" xorm:"updated"`
11 | }
12 |
--------------------------------------------------------------------------------
/src/application/models/tables/tags.go:
--------------------------------------------------------------------------------
1 | package tables
2 |
3 | type Tags struct {
4 | Id int64 `xorm:"pk autoincr id" json:"id"`
5 | Name string `json:"name"`
6 | RefNum uint `json:"ref_num"`
7 | Clicks uint `json:"clicks"`
8 | SeoTitle string `json:"seo_title"`
9 | SeoKeywords string `json:"seo_keywords"`
10 | SeoDescription string `json:"seo_description"`
11 | Listorder uint `json:"listorder"`
12 | Status uint `json:"status"`
13 | CreatedAt LocalTime `json:"created_at"`
14 | }
15 |
--------------------------------------------------------------------------------
/resources/themes/default/footer.jet:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
{{global["site_copyright"]}}
10 |
{{global["site_icp"]}}
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/application/controllers/backend/wechat/response.go:
--------------------------------------------------------------------------------
1 | package wechat
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/valyala/fasthttp"
7 | )
8 |
9 | /**
10 | 实现Response的响应对象
11 | */
12 | type wechatResponseWrapper struct {
13 | *fasthttp.RequestCtx
14 | }
15 |
16 | func (w wechatResponseWrapper) Header() http.Header {
17 | return map[string][]string{}
18 | }
19 |
20 | func (w wechatResponseWrapper) Write(bytes []byte) (int, error) {
21 | return w.RequestCtx.Write(bytes)
22 | }
23 |
24 | func (w wechatResponseWrapper) WriteHeader(statusCode int) {
25 | w.Response.SetStatusCode(statusCode)
26 | }
27 |
--------------------------------------------------------------------------------
/src/application/models/tables/department.go:
--------------------------------------------------------------------------------
1 | package tables
2 |
3 | type Department struct {
4 | Id int64 `json:"id"`
5 | Name string `json:"name" schema:"name" xorm:"varchar(25)"`
6 | LeaderName string `json:"leader_name" xorm:"varchar(25)"`
7 | LeaderPhone string `json:"leader_phone" xorm:"varchar(35)"`
8 | Email string `json:"email" xorm:"varchar(100)"`
9 | Status bool `json:"status" schema:"status"` // 状态
10 | Listorder uint `json:"listorder"`
11 | ParentId uint `json:"parent_id"`
12 | CreatedAt LocalTime `json:"created_at"`
13 | }
14 |
--------------------------------------------------------------------------------
/src/application/models/tables/wechat_account.go:
--------------------------------------------------------------------------------
1 | package tables
2 |
3 | type WechatAccount struct {
4 | Id int64 `json:"id" xorm:"pk autoincr"`
5 | AppId string `json:"appid" xorm:"char(20) not null comment('appid')"`
6 | Name string `json:"name" xorm:"varchar(50) not null comment('公众号名称')"`
7 | Type uint `json:"type" xorm:"tinyint(1) comment('账号类型')"`
8 | Verified bool `json:"verified" xorm:"tinyint(1) comment('认证状态')"`
9 | Secret string `json:"secret" xorm:"char(32)"`
10 | Token string `json:"token" xorm:"varchar(32)"`
11 | AesKey string `json:"aesKey" xorm:"varchar(43)"`
12 | }
13 |
--------------------------------------------------------------------------------
/src/router/module.go:
--------------------------------------------------------------------------------
1 | package router
2 |
3 | import (
4 | "github.com/xiusin/pine"
5 | "github.com/xiusin/pinecms/src/application/controllers/backend/filemanager"
6 | "github.com/xiusin/pinecms/src/application/controllers/backend/webssh"
7 | "github.com/xiusin/pinecms/src/application/controllers/backend/wechat"
8 | )
9 |
10 | func InitModuleRouter(backendRouter *pine.Router, app *pine.Application) {
11 | filemanager.InitRouter(app, backendRouter)
12 | webssh.InitRouter(app, backendRouter)
13 | }
14 |
15 | func InitSubModuleRouter(app *pine.Application, admin *pine.Router) {
16 | wechat.InitRouter(app, admin)
17 | }
18 |
--------------------------------------------------------------------------------
/src/application/models/tables/request_log.go:
--------------------------------------------------------------------------------
1 | package tables
2 |
3 | type RequestLog struct {
4 | Id int64 `json:"id"`
5 | Params string `json:"params" xorm:"comment('参数') varchar(255)"`
6 | Uri string `json:"uri" xorm:"comment('请求uri') varchar(255)"`
7 | Userid int64 `json:"userid" xorm:"comment('操作用户ID') int(6)"`
8 | Username string `json:"username" xorm:"comment('操作用户名') varchar(100)"`
9 | Ip string `json:"ip" xorm:"comment('操作IP') varchar(15)"`
10 | Time LocalTime `json:"time" xorm:"comment('操作时间')"`
11 | Method string `json:"method" xorm:"comment('请求方法') varchar(10)"`
12 | }
13 |
--------------------------------------------------------------------------------
/src/application/controllers/backend/im_session_controller.go:
--------------------------------------------------------------------------------
1 | package backend
2 |
3 | import "github.com/xiusin/pine"
4 |
5 | type ImSessionController struct {
6 | pine.Controller
7 | }
8 |
9 | func (c *ImSessionController) RegisterRoute(b pine.IRouterWrapper) {
10 | b.POST("/im/session/page", "SessionPage")
11 | b.POST("/im/session/unreadCount", "SessionUnreadCount")
12 | b.POST("/im/message/page", "MessagePage")
13 | }
14 |
15 | func (c *ImSessionController) SessionPage() {
16 |
17 | }
18 |
19 | func (c *ImSessionController) SessionUnreadCount() {
20 |
21 | }
22 |
23 | func (c *ImSessionController) MessagePage() {
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/src/common/message/message_intf.go:
--------------------------------------------------------------------------------
1 | package message
2 |
3 | const (
4 | TypeDefault = iota
5 | TypeRegister
6 | TypeLogin
7 | TypeModifyProfile
8 | TypeFindPwd
9 | TypeNotice
10 | )
11 |
12 | // AbstractMessage 发送接口
13 | type AbstractMessage interface {
14 | Init() error
15 | Send(receiver []string, subject string, body string) error
16 | // receiver 接收人数组 params 模板内数据
17 | Notice(receiver []string, params []any, templateId int) error
18 | // 更新单例配置
19 | UpdateCfg() error
20 | }
21 |
22 | var MessageServiceDict = map[string]string{
23 | ServiceNullMessage: "空短信",
24 | //ServiceSmsMessage: "阿里信息",
25 | ServiceEmailMessage: "邮箱",
26 | }
27 |
--------------------------------------------------------------------------------
/src/application/controllers/taglibs/tags.go:
--------------------------------------------------------------------------------
1 | package taglibs
2 |
3 | import (
4 | "github.com/CloudyKit/jet"
5 | "github.com/xiusin/pine"
6 | "github.com/xiusin/pinecms/src/config"
7 | "reflect"
8 | "runtime/debug"
9 | "strings"
10 | )
11 |
12 | func HotWords(args jet.Arguments) reflect.Value {
13 | if !checkArgType(&args) {
14 | return defaultArrReturnVal
15 | }
16 | defer func() {
17 | if err := recover(); err != nil {
18 | pine.Logger().Error("HotWords Failed", err, string(debug.Stack()))
19 | }
20 | }()
21 | conf, _ := config.SiteConfig()
22 | words := strings.Split(conf["SITE_HOTWORDS"], ",")
23 | return reflect.ValueOf(words)
24 | }
25 |
--------------------------------------------------------------------------------
/src/application/models/tables/log.go:
--------------------------------------------------------------------------------
1 | package tables
2 |
3 | type Log struct {
4 | Id int64 `json:"id"`
5 | Level uint8 `json:"level" xorm:"comment('日志类型') tinyint(3)"`
6 | //Uri string `json:"uri" xorm:"comment('请求uri') varchar(255)"`
7 | //Method string `json:"method" xorm:"comment('请求方法') varchar(10)"`
8 | //Params any `json:"params" xorm:"comment('请求参数') json"`
9 | Message string `json:"message" xorm:"comment('操作用户名') text"`
10 | //Stack string `json:"stack" xorm:"comment('调用堆栈') text"`
11 | //Ip string `json:"ip" xorm:"comment('操作IP') varchar(15)"`
12 | Time LocalTime `json:"time" xorm:"comment('操作时间')"`
13 | }
14 |
--------------------------------------------------------------------------------
/src/common/message/null.go:
--------------------------------------------------------------------------------
1 | package message
2 |
3 | import "github.com/xiusin/pine/di"
4 |
5 | type NullMessage struct{}
6 |
7 | var ServiceNullMessage = "pinecms.message.service.null"
8 |
9 | func (n NullMessage) Init() error { return nil }
10 |
11 | func (n NullMessage) Notice(receiver []string, params []any, templateId int) error {
12 | return nil
13 | }
14 |
15 | func (n NullMessage) Send(receiver []string, msg string, typo int) error { return nil }
16 |
17 | func (n NullMessage) UpdateCfg() error { return nil }
18 |
19 | func init() {
20 | di.Set(ServiceSmsMessage, func(builder di.AbstractBuilder) (any, error) {
21 | return &NullMessage{}, nil
22 | }, true)
23 | }
24 |
--------------------------------------------------------------------------------
/src/application/models/tables/attachment_type.go:
--------------------------------------------------------------------------------
1 | package tables
2 |
3 | type Attachments struct {
4 | Id int64 `json:"id"`
5 | Name string `json:"name" xorm:"comment('附件名称')"`
6 | Url string `json:"url" xorm:"comment('完整的链接地址')"`
7 | OriginName string `json:"original" xorm:"comment('原始名称')"`
8 | Size int64 `json:"size" xorm:"comment('附件大小')"`
9 | CreatedAt LocalTime `json:"upload_time" xorm:"created"`
10 | Type string `json:"type" xorm:"comment('类型') varchar(30)"`
11 | ClassifyId int64 `json:"classifyId" xorm:"comment('归属分类ID') int(5)"`
12 | Md5 string `json:"md5" xorm:"comment('附件的md5值') unique varchar(32)"`
13 | }
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.log
2 | runtime
3 | .idea
4 | *.js___jb_tmp___
5 | go.sum
6 | vendor
7 | node_modules
8 | uploads
9 | resources/configs/database.yml
10 | resources/assets/example/
11 | resources/themes/example/
12 | resources/html/
13 | *.exe
14 | /apidoc.html
15 | /apidoc.html.json
16 | /apidoc/
17 | /admin/dist
18 | *.so
19 | /admin/public/Ueditor/
20 | /apidoc-ui/public/UEditor/
21 | /admin/wx-manage/
22 | /main
23 | tests/*.png
24 | tests/*.jpg
25 | tests/*.h
26 | .lh
27 | /plugins/
28 | ./pinecms
29 | pinecms
30 | admin/.yarnclean
31 | publish
32 | publish.zip
33 | pinecms-web
34 | dist
35 | pinecms-web-ui
36 | example
37 | __debug*
38 | *.gen.go
39 | data/_metadata.bolt
40 | .history/
41 | *.DS_Store
42 |
--------------------------------------------------------------------------------
/src/application/controllers/middleware/statsviz.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "github.com/arl/statsviz"
5 | "github.com/valyala/fasthttp/fasthttpadaptor"
6 | "github.com/xiusin/pine"
7 | )
8 |
9 | func StatesViz(app *pine.Application) pine.Handler {
10 | sev, _ := statsviz.NewServer()
11 | indexHandler := fasthttpadaptor.NewFastHTTPHandler(sev.Index())
12 | wsHandler := fasthttpadaptor.NewFastHTTPHandler(sev.Ws())
13 |
14 | app.GET("/debug/statsviz/*filepath", func(ctx *pine.Context) {
15 | if ctx.Params().Get("filepath") == "ws" {
16 | wsHandler(ctx.RequestCtx)
17 | } else {
18 | indexHandler(ctx.RequestCtx)
19 | }
20 | })
21 | return func(ctx *pine.Context) {
22 | ctx.Next()
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/application/controllers/backend/webssh/define.go:
--------------------------------------------------------------------------------
1 | package webssh
2 |
3 | import (
4 | "github.com/xiusin/pinecms/src/application/controllers/backend/webssh/tables"
5 | "sync"
6 |
7 | "github.com/xiusin/pine"
8 | "github.com/xiusin/pinecms/src/common/helper"
9 | )
10 |
11 | var once sync.Once
12 |
13 | func InitInstall(app *pine.Application, urlPrefix, dir string) {
14 | once.Do(func() {
15 | app.Static(urlPrefix, dir, 2)
16 | orm := helper.GetORM()
17 | defer func() {
18 | if err := recover(); err != nil {
19 | pine.Logger().Warn("初始化安装失败", err)
20 | }
21 | }()
22 |
23 | if err := orm.Sync2(&tables.SSHServer{}, &tables.SSHUser{}); err != nil {
24 | pine.Logger().Warn(err.Error())
25 | }
26 | })
27 | }
28 |
--------------------------------------------------------------------------------
/src/common/oncer/oncer.go:
--------------------------------------------------------------------------------
1 | package oncer
2 |
3 | import (
4 | "fmt"
5 | "runtime"
6 | "sync"
7 | )
8 |
9 | var caller = &sync.Map{}
10 |
11 | func Do(fn func(), withline ...bool) {
12 | var (
13 | file string
14 | line int
15 | ok bool
16 | once *sync.Once
17 | )
18 |
19 | if len(withline) > 0 && withline[0] {
20 | _, file, line, ok = runtime.Caller(1)
21 | if !ok {
22 | return
23 | }
24 | file = fmt.Sprintf("%s:%d", file, line)
25 | } else {
26 | _, file, _, ok = runtime.Caller(1)
27 | if !ok {
28 | return
29 | }
30 | }
31 | if v, ok := caller.Load(file); ok {
32 | once = v.(*sync.Once)
33 | } else {
34 | once = new(sync.Once)
35 | caller.Store(file, once)
36 | }
37 | once.Do(fn)
38 | }
39 |
--------------------------------------------------------------------------------
/src/application/controllers/backend/attachment_type_controller.go:
--------------------------------------------------------------------------------
1 | package backend
2 |
3 | import (
4 | "github.com/xiusin/pinecms/src/application/models/tables"
5 | "github.com/xiusin/pinecms/src/common/helper"
6 | )
7 |
8 | type AttachmentTypeController struct {
9 | BaseController
10 | }
11 |
12 | func (c *AttachmentTypeController) Construct() {
13 | c.SearchFields = []SearchFieldDsl{
14 | {Op: "=", Field: "type"},
15 | }
16 | c.Table = &tables.AttachmentType{}
17 | c.Entries = &[]tables.AttachmentType{}
18 | c.Group = "附件分类"
19 | c.ApiEntityName = "分类"
20 | c.BaseController.Construct()
21 | }
22 |
23 | func (c *AttachmentTypeController) PostList() {
24 | c.Orm.Find(c.Entries)
25 | helper.Ajax(c.Entries, 0, c.Ctx())
26 | }
27 |
--------------------------------------------------------------------------------
/src/application/models/tables/wechat_log.go:
--------------------------------------------------------------------------------
1 | package tables
2 |
3 | import "github.com/silenceper/wechat/v2/officialaccount/message"
4 |
5 | type WechatLog struct {
6 | Id int64 `json:"id" xorm:"pk autoincr"`
7 | AppId string `json:"appid" xorm:"char(20)"`
8 | OpenId string `json:"openid" xorm:"varchar(32)"`
9 | Inout uint `json:"in_out" xorm:"tinyint(1)" xorm:"comment('1=来自公众号的回复,0=来自粉丝的消息')"`
10 | MsgType string `json:"msg_type" xorm:"varchar(50)"`
11 | Detail *message.MixMessage `json:"detail" xorm:"json"`
12 | CreatedAt LocalTime `json:"created_at" xorm:"created"`
13 | FansInfo *WechatMember `json:"fans_info" xrom:"-"`
14 | }
15 |
--------------------------------------------------------------------------------
/src/application/controllers/backend/wechat/cacher.go:
--------------------------------------------------------------------------------
1 | package wechat
2 |
3 | import (
4 | "github.com/xiusin/pine/contracts"
5 | "time"
6 | )
7 |
8 | type WechatTokenCacher struct {
9 | contracts.Cache
10 | }
11 |
12 | func (w WechatTokenCacher) Get(key string) any {
13 | byts, err := w.Cache.Get(key)
14 | if err != nil {
15 | return nil
16 | }
17 | return string(byts)
18 | }
19 |
20 | func (w WechatTokenCacher) Set(key string, val any, timeout time.Duration) error {
21 | return w.Cache.Set(key, []byte(val.(string)), int(timeout.Seconds()))
22 | }
23 |
24 | func (w WechatTokenCacher) IsExist(key string) bool {
25 | return w.Cache.Exists(key)
26 | }
27 |
28 | func (w WechatTokenCacher) Delete(key string) error {
29 | return w.Cache.Delete(key)
30 | }
31 |
--------------------------------------------------------------------------------
/src/application/controllers/middleware/data.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "github.com/xiusin/pine"
5 | "github.com/xiusin/pinecms/src/application/controllers"
6 | "github.com/xiusin/pinecms/src/config"
7 | "strings"
8 | )
9 |
10 | func SetGlobalConfigData() pine.Handler {
11 | return func(ctx *pine.Context) {
12 | settingData, err := config.SiteConfig()
13 | if err != nil {
14 | pine.Logger().Error("无法读取到配置内容:" + err.Error())
15 | return
16 | }
17 | settingData["site_url"] = string(ctx.Host())
18 | ctx.Set(controllers.CacheSetting, settingData)
19 | lower := map[string]string{}
20 | for k, v := range settingData {
21 | lower[strings.ToLower(k)] = v
22 | }
23 | ctx.Render().ViewData("global", lower)
24 | ctx.Next()
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/cmd/server/serve.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "runtime"
5 |
6 | "github.com/spf13/cobra"
7 | "github.com/takama/daemon"
8 | "github.com/xiusin/pinecms/src/common/helper"
9 | )
10 |
11 | var ServeCmd = &cobra.Command{
12 | Use: "serve",
13 | Short: "启动pinecms服务器",
14 | }
15 |
16 | func init() {
17 | ServeCmd.AddCommand(installCmd)
18 | ServeCmd.AddCommand(removeCmd)
19 | ServeCmd.AddCommand(runCmd)
20 | ServeCmd.AddCommand(startCmd)
21 | ServeCmd.AddCommand(statusCmd)
22 | ServeCmd.AddCommand(stopCmd)
23 |
24 | daemonKind := daemon.SystemDaemon
25 | if runtime.GOOS == "darwin" {
26 | daemonKind = daemon.UserAgent
27 | }
28 | srv, err := daemon.New("pinecms", "pinecms 内容管理系统服务", daemonKind)
29 | helper.PanicErr(err)
30 | serv = &Service{Daemon: srv}
31 | }
32 |
--------------------------------------------------------------------------------
/src/application/controllers/middleware/cors.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "github.com/xiusin/pine"
5 | "strings"
6 | )
7 |
8 | func Cors() pine.Handler {
9 | return func(ctx *pine.Context) {
10 | hostOrigin := strings.TrimRight(string(ctx.RequestCtx.Referer()), "/")
11 | ctx.Response.Header.Set("Access-Control-Allow-Origin", hostOrigin)
12 | ctx.Response.Header.Set("Access-Control-Allow-Headers", "X-TOKEN, Content-Type, Origin, Referer, Content-Length, Access-Control-Allow-Headers, Authorization, x-requested-with")
13 | ctx.Response.Header.Set("Access-Control-Allow-Credentials", "true")
14 | ctx.Response.Header.Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
15 | if !ctx.IsOptions() {
16 | ctx.Next()
17 | } else {
18 | ctx.Stop()
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/application/models/tables/advert.go:
--------------------------------------------------------------------------------
1 | package tables
2 |
3 | type Advert struct {
4 | Id int64 `json:"id"`
5 | Name string `json:"name" schema:"name"`
6 | SpaceID int64 `xorm:"space_id" json:"space_id" schema:"space_id"`
7 | SpaceName string `xorm:"-" json:"space_name" schema:"-"`
8 | LinkUrl string `json:"link_url" schema:"link_url"`
9 | Image string `json:"image" schema:"image"`
10 | ListOrder uint `xorm:"listorder default 0" json:"listorder" schema:"listorder"`
11 | StartTime *LocalTime `xorm:"datetime 'start_time' default NULL " json:"startTime"`
12 | EndTime *LocalTime `xorm:"datetime 'end_time' default NULL " json:"endTime"`
13 | DateRange []string `json:"date_range" schema:"-"` // 展示日期范围
14 | Status bool `json:"status" schema:"status"` // 状态
15 | }
16 |
--------------------------------------------------------------------------------
/src/application/controllers/backend/wechat/route.go:
--------------------------------------------------------------------------------
1 | package wechat
2 |
3 | import (
4 | "github.com/xiusin/pine"
5 | )
6 |
7 | func InitRouter(app *pine.Application, router *pine.Router) {
8 | app.ANY("/api/wechat/msg/:appid", msgHandler)
9 | router.Handle(new(WechatAccountController), "/wechat/account")
10 | router.Handle(new(WechatUserController), "/wechat/user")
11 | router.Handle(new(WechatMagController), "/wechat/msg")
12 | router.Handle(new(WechatQrcodeController), "/wechat/qrcode")
13 | router.Handle(new(WechatRuleController), "/wechat/rule")
14 | router.Handle(new(WechatMaterialController), "/wechat/material")
15 | router.Handle(new(WechatMsgTemplateController), "/wechat/template")
16 | router.Handle(new(WechatUserTagsController), "/wechat/user/tags")
17 | router.Handle(new(WechatMenuController), "/wechat/menu")
18 | }
19 |
--------------------------------------------------------------------------------
/src/application/controllers/backend/webssh/route.go:
--------------------------------------------------------------------------------
1 | package webssh
2 |
3 | import (
4 | "github.com/xiusin/pine"
5 | "github.com/xiusin/pine/render/engine/ptemplate"
6 | "github.com/xiusin/pinecms/src/application/controllers/backend/webssh/middleware"
7 | "strings"
8 | )
9 |
10 | func InitRouter(app *pine.Application, router *pine.Router) {
11 | InitInstall(app, urlPath("static"), "webssh/static")
12 | engine := ptemplate.New("webssh/view", ".html", true)
13 |
14 | pine.RegisterViewEngine(engine)
15 |
16 | webssh := app.Group(urlPath(), middleware.Auth())
17 | {
18 | webssh.Handle(new(SshController), "/ui")
19 | webssh.Handle(new(ApiController), "/v1")
20 | }
21 | }
22 |
23 | func urlPath(str ...string) string {
24 | if len(str) == 0 {
25 | return "/webssh"
26 | }
27 | return "/webssh/" + strings.TrimPrefix(str[0], "/")
28 | }
--------------------------------------------------------------------------------
/src/application/controllers/frontend/fesc_controller.go:
--------------------------------------------------------------------------------
1 | package frontend
2 |
3 | import (
4 | "github.com/xiusin/pine"
5 | "github.com/xiusin/pinecms/src/application/models"
6 | )
7 |
8 | type FescController struct {
9 | pine.Controller
10 | }
11 |
12 | func (c *FescController) RegisterRoute(b pine.IRouterWrapper) {
13 | // 必须放到最后 否则搜索路由时会优先被此路由拦截到
14 | b.GET("/fesc/down-list.go", "DownList")
15 | }
16 |
17 | func (c *FescController) DownList() {
18 | aid, _ := c.Ctx().Input().GetInt("id", 0)
19 | tid, _ := c.Ctx().Input().GetInt64("tid", 0)
20 | cat, _ := models.NewCategoryModel().GetCategoryFByIdForBE(tid)
21 | if cat == nil {
22 | c.Ctx().Abort(404)
23 | return
24 | }
25 | sess := getOrmSess(cat.Model).Where("id = ?", aid).Limit(1)
26 | data, _ := sess.QueryString()
27 | c.ViewData("data", data[0])
28 | c.View(template("down_list.jet"))
29 | }
30 |
--------------------------------------------------------------------------------
/src/server/server.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "fmt"
5 |
6 | _ "github.com/go-sql-driver/mysql"
7 | "github.com/gorilla/securecookie"
8 | "github.com/xiusin/pine"
9 | "github.com/xiusin/pinecms/src/application/plugins"
10 | "github.com/xiusin/pinecms/src/router"
11 | )
12 |
13 | func Server() {
14 | InitCache()
15 |
16 | pine.SetControllerDefaultAction("Index")
17 | router.InitApiRouter(app)
18 | router.InitStatics(app)
19 |
20 | go plugins.Init()
21 |
22 | // 内部托管任意路由
23 | router.InitRouter(app)
24 |
25 | app.Run(
26 | pine.Addr(fmt.Sprintf("%s:%d", "127.0.0.1", conf.Port)),
27 | pine.WithCookieTranscoder(securecookie.New([]byte(conf.HashKey), []byte(conf.BlockKey))),
28 | pine.WithServerName("xiusin/pinecms"),
29 | pine.WithoutStartupLog(false),
30 | pine.WithCookie(true),
31 | pine.WithMaxMultipartMemory(100*1024*1024),
32 | )
33 | }
34 |
--------------------------------------------------------------------------------
/src/application/controllers/taglibs/TopType.go:
--------------------------------------------------------------------------------
1 | package taglibs
2 |
3 | import (
4 | "fmt"
5 | "reflect"
6 |
7 | "github.com/CloudyKit/jet"
8 | "github.com/xiusin/pinecms/src/application/models"
9 | "github.com/xiusin/pinecms/src/common/helper"
10 | )
11 |
12 | /**
13 | * 标签:{{yield toptype() content}}{{field.Catname}}{{end}}
14 | * 作用:获取当前页面面包屑只可适用于列表页和详情页
15 | */
16 | func TopType(args jet.Arguments) reflect.Value {
17 | fmt.Println("arclist")
18 |
19 | if !checkArgType(&args) {
20 | return defaultArrReturnVal
21 | }
22 | typeid := getNumber(args.Get(0))
23 | cat, err := models.NewCategoryModel().GetCategoryFByIdForBE(typeid)
24 | helper.PanicErr(err)
25 | if cat.Topid != 0 {
26 | cat, err = models.NewCategoryModel().GetCategoryFByIdForBE(cat.Topid)
27 | helper.PanicErr(err)
28 | }
29 | cat.Content = ""
30 | cat.Page = nil
31 | return reflect.ValueOf(cat)
32 | }
33 |
--------------------------------------------------------------------------------
/src/application/models/wechat_member_model.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "github.com/xiusin/pinecms/src/application/models/tables"
5 | "github.com/xiusin/pinecms/src/common/helper"
6 | "xorm.io/xorm"
7 | )
8 |
9 | type WechatMemberModel struct {
10 | orm *xorm.Engine
11 | }
12 |
13 | func NewWechatMemberModel() *WechatMemberModel {
14 | return &WechatMemberModel{orm: helper.GetORM()}
15 | }
16 |
17 | func (w *WechatMemberModel) GetList(page, limit int64) (list []tables.WechatMember, total int64) {
18 | offset := (page - 1) * limit
19 | total, _ = w.orm.Limit(int(limit), int(offset)).FindAndCount(&list)
20 | if list == nil {
21 | list = []tables.WechatMember{}
22 | }
23 | return list, total
24 | }
25 |
26 | func (w *WechatMemberModel) GetInfo(id int64) tables.WechatMember {
27 | var member tables.WechatMember
28 | w.orm.Where("id = ?", id).Get(&member)
29 | return member
30 | }
31 |
--------------------------------------------------------------------------------
/src/application/models/tables/wechat_msg_reply_rule.go:
--------------------------------------------------------------------------------
1 | package tables
2 |
3 | type WechatMsgReplyRule struct {
4 | Id int64 `json:"id"`
5 | AppId string `json:"appid" xorm:"char(20) appid"`
6 | RuleName string `json:"ruleName"`
7 | MatchValue string `json:"matchValue"`
8 | ExactMatch bool `json:"exactMatch" xorm:"comment('是否精确匹配')"`
9 | ReplyType string `json:"replyType"`
10 | ReplyContent string `json:"replyContent"`
11 | Status bool `json:"status"`
12 | Desc string `json:"desc"`
13 | EffectTimeStart string `json:"effectTimeStart" xorm:"time"`
14 | EffectTimeEnd string `json:"effectTimeEnd" xorm:"time"`
15 | Priority uint `json:"priority"`
16 | CreatedAt LocalTime `json:"created_at" xorm:"created"`
17 | UpdatedAt LocalTime `json:"updated_at" xorm:"updated"`
18 | }
19 |
--------------------------------------------------------------------------------
/src/application/controllers/backend/level_controller.go:
--------------------------------------------------------------------------------
1 | package backend
2 |
3 | import (
4 | "github.com/xiusin/pinecms/src/application/models/tables"
5 | "xorm.io/xorm"
6 | )
7 |
8 | type LevelController struct {
9 | BaseController
10 | }
11 |
12 | func (c *LevelController) Construct() {
13 | c.Table = &tables.Level{}
14 | c.Entries = &[]tables.Level{}
15 | c.Group = "系统管理"
16 | c.SubGroup = "职级管理"
17 | c.ApiEntityName = "职级"
18 | c.BaseController.Construct()
19 | c.KeywordsSearch = []SearchFieldDsl{
20 | {Field: "name", Op: "="},
21 | }
22 | c.SelectOp = func(session *xorm.Session) {
23 | session.Where("status = ?", 1)
24 | }
25 |
26 | c.UniqCheckOp = func(i int, session *xorm.Session) {
27 | entity := c.Table.(*tables.Level)
28 | if i == OpAdd {
29 | session.Where("name = ?", entity.Name)
30 | } else {
31 | session.Where("name = ?", entity.Name).Where("id <> ?", entity.Id)
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/application/models/tables/wechat_user.go:
--------------------------------------------------------------------------------
1 | package tables
2 |
3 | // WechatMember 微信粉丝
4 | type WechatMember struct {
5 | Id int64 `xorm:"pk autoincr" json:"id"`
6 | Appid string `json:"appid"`
7 | Openid string `json:"openid"`
8 | Phone string `json:"phone"`
9 | Nickname string `json:"nickname"`
10 | Sex int `json:"sex"`
11 | City string `json:"city"`
12 | Province string `json:"province"`
13 | Headimgurl string `json:"headimgurl"`
14 | SubscribeTime string `json:"subscribe_time"`
15 | Subscribe bool `json:"subscribe"`
16 | Unionid string `json:"unionid"`
17 | Remark string `json:"remark"`
18 | TagidList []int32 `json:"tagid_list" xorm:"json"`
19 | SubscribeScene string `json:"subscribe_scene"`
20 | QrSceneStr string `json:"qr_scene_str"`
21 | Poster string `json:"poster"`
22 | }
23 |
--------------------------------------------------------------------------------
/src/application/models/tables/link.go:
--------------------------------------------------------------------------------
1 | package tables
2 |
3 | type Link struct {
4 | Id int64 `xorm:"int(11) autoincr not null pk 'id'" json:"id" schema:"id"`
5 | Linktype int `xorm:"tinyint(3) not null 'linktype'" json:"linktype" schema:"linktype"`
6 | Name string `xorm:"varchar(50) not null 'name'" json:"name" schema:"name"`
7 | Url string `xorm:"varchar(255) not null 'url'" json:"url" schema:"url"`
8 | Logo string `xorm:"varchar(100) not null 'logo'" json:"logo" schema:"logo"`
9 | Introduce string `xorm:"varchar(255) not null 'introduce'" json:"introduce" schema:"introduce"`
10 | Listorder int64 `xorm:"int(11) not null 'listorder'" json:"listorder" schema:"listorder"`
11 | Passed int `xorm:"tinyint(1) not null default '0' 'passed'" json:"passed" schema:"passed"`
12 | Addtime LocalTime `xorm:"datetime default 'null' 'addtime'" json:"addtime" schema:"addtime"`
13 | }
14 |
--------------------------------------------------------------------------------
/src/application/controllers/backend/errlog_controller.go:
--------------------------------------------------------------------------------
1 | package backend
2 |
3 | import (
4 | "github.com/xiusin/pinecms/src/application/controllers/middleware/apidoc"
5 | "github.com/xiusin/pinecms/src/application/models/tables"
6 | "github.com/xiusin/pinecms/src/common/helper"
7 | )
8 |
9 | type ErrorLogController struct {
10 | BaseController
11 | }
12 |
13 | func (c *ErrorLogController) Construct() {
14 | c.Group = "系统日志"
15 | c.KeywordsSearch = []SearchFieldDsl{
16 | {Field: "message", Op: "LIKE", DataExp: "%$?%"},
17 | }
18 | c.Table = &tables.Log{}
19 | c.Entries = &[]tables.Log{}
20 | c.apiEntities = map[string]apidoc.Entity{
21 | "list": {Title: "日志列表", Desc: "查询所有系统日志列表"},
22 | "clear": {Title: "清空日志", Desc: "一键清理所有日志"},
23 | }
24 | c.BaseController.Construct()
25 | }
26 |
27 | func (c *ErrorLogController) PostClear() {
28 | _, _ = c.Orm.Where("id > 0").Delete(c.Table)
29 | helper.Ajax("清理成功", 0, c.Ctx())
30 | }
31 |
--------------------------------------------------------------------------------
/src/application/controllers/backend/webssh/common/core/helper.go:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | import (
4 | "log"
5 | "time"
6 |
7 | "github.com/fasthttp/websocket"
8 | "github.com/xiusin/pine"
9 | )
10 |
11 | func JsonError(c *pine.Context, msg any) {
12 | c.WriteJSON(pine.H{"ok": false, "msg": msg})
13 | }
14 |
15 | func HandleError(c *pine.Context, err error) bool {
16 | if err != nil {
17 | //logrus.WithError(err).Error("gin context http handler error")
18 | JsonError(c, err.Error())
19 | return true
20 | }
21 | return false
22 | }
23 |
24 | func WshandleError(ws *websocket.Conn, err error) bool {
25 | if err != nil {
26 | log.Println("handler ws ERROR:", err.Error())
27 | dt := time.Now().Add(time.Second)
28 | if err := ws.WriteControl(websocket.CloseMessage, []byte(err.Error()), dt); err != nil {
29 | log.Println("websocket writes control message failed:", err.Error())
30 | }
31 | return true
32 | }
33 | return false
34 | }
35 |
--------------------------------------------------------------------------------
/src/application/controllers/taglibs/myad.go:
--------------------------------------------------------------------------------
1 | package taglibs
2 |
3 | import (
4 | "github.com/CloudyKit/jet"
5 | "github.com/xiusin/pinecms/src/application/models/tables"
6 | "github.com/xiusin/pinecms/src/common/helper"
7 | "reflect"
8 | "time"
9 | )
10 |
11 | /**
12 | 广告标签
13 | {{yield myad(id="", name="")}}
14 | 返回一组广告
15 | */
16 | func MyAd(args jet.Arguments) reflect.Value {
17 | if !checkArgType(&args) {
18 | return defaultSignalVal
19 | }
20 | id := int(getNumber(args.Get(0)))
21 | name := args.Get(1).String()
22 | now := time.Now().In(helper.GetLocation()).Format(helper.TimeFormat)
23 | orm := helper.GetORM().Where("status = 1").Where("start_time <= ?", now).Where("end_time >= ?", now).Select("id, name, image, link_url")
24 | if id > 0 {
25 | orm.ID(id)
26 | }
27 | if id == 0 && name != "" {
28 | orm.Where("name = ?", name)
29 | }
30 | var advs = tables.Advert{}
31 | orm.Get(&advs)
32 | return reflect.ValueOf(advs)
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/src/application/controllers/backend/webssh/ssh_controller.go:
--------------------------------------------------------------------------------
1 | package webssh
2 |
3 | import (
4 | "github.com/xiusin/pinecms/src/application/controllers/backend"
5 | )
6 |
7 | type SshController struct {
8 | backend.BaseController
9 | }
10 |
11 | func (c *SshController) Index() {
12 | c.Render().HTML("index.html")
13 | }
14 |
15 | func (c *SshController) GetLogin() {
16 | c.Render().HTML("index.html")
17 | }
18 |
19 | func (c *SshController) GetConsole() {
20 | c.Render().HTML("console.html")
21 | }
22 |
23 | func (c *SshController) GetServers() {
24 | c.Render().HTML("s_list.html")
25 | }
26 |
27 | func (c *SshController) GetAdd() {
28 | c.Render().HTML("add.html")
29 | }
30 |
31 | func (c *SshController) GetSetpass() {
32 | c.Render().HTML("reset.html")
33 | }
34 |
35 | func (c *SshController) GetOpenterm() {
36 | c.Render().HTML("open_term.html")
37 | }
38 |
39 | func (c *SshController) GetTerm() {
40 | c.Render().HTML("term.html")
41 | }
42 |
--------------------------------------------------------------------------------
/src/application/controllers/service_key.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import "xorm.io/xorm"
4 |
5 | var ServiceXorm = &xorm.Engine{}
6 |
7 | const ServiceConfig = "pinecms.config"
8 | const ServiceSiteConfig = "pinecms.site.config"
9 | const ServiceICache = "cache.AbstractCache"
10 | const ServiceTablePrefix = "pinecms.table_prefix"
11 | const ServiceJetEngine = "pinecms.jet"
12 | const ServiceCasbinEnforcer = "pinecms.casbin.enforcer"
13 | const ServiceCasbinClearPolicy = "pinecms.casbin.enforcer.policy.clear"
14 | const ServiceCasbinAddPolicy = "pinecms.casbin.enforcer.policy.add"
15 | const ServiceUploader = "pinecms.uploader"
16 | const ServiceUploaderEngine = "pinecms.uploader.%s"
17 | const ServiceApplication = "pinecms.application"
18 | const ServiceBackendRouter = "pinecms.router.backend"
19 | const ServiceCatUrlPrefixFunc = "pinecms.cat.url.prefix.func"
20 | const ServiceSearchName = "pinecms.search.engine"
21 |
22 | // 允许插件自动注册上传驱动 并注册服务进DI
23 | // DI内自动获取选中 (根据驱动名称) 驱动
24 |
--------------------------------------------------------------------------------
/src/common/storage/storage_intf.go:
--------------------------------------------------------------------------------
1 | package storage
2 |
3 | import (
4 | "io"
5 | "runtime"
6 | "strings"
7 | "time"
8 | )
9 |
10 | type Uploader interface {
11 | Upload(storageName string, LocalFile io.Reader) (string, error)
12 | List(dir string) (list []File, err error)
13 | Exists(name string) (bool, error)
14 | GetFullUrl(name string) string
15 | Remove(name string) error
16 | GetEngineName() string
17 | Content(string) ([]byte, error)
18 | Rename(string, string) error
19 | Mkdir(string) error
20 | Rmdir(string) error
21 | }
22 |
23 | type File struct {
24 | Id string `json:"id"`
25 | FullPath string `json:"full_path"`
26 | Name string `json:"name"`
27 | Size int64 `json:"size"`
28 | Ctime time.Time `json:"ctime"`
29 | IsDir bool `json:"is_dir"`
30 | }
31 |
32 | func getAvailableUrl(path string) string {
33 | if runtime.GOOS == "windows" {
34 | path = strings.ReplaceAll(path, "\\", "/")
35 | }
36 | return path
37 | }
38 |
--------------------------------------------------------------------------------
/src/application/controllers/backend/ad_controller.go:
--------------------------------------------------------------------------------
1 | package backend
2 |
3 | import (
4 | "github.com/xiusin/pine/pointer"
5 | "github.com/xiusin/pinecms/src/application/models/tables"
6 | "github.com/xiusin/pinecms/src/common/helper"
7 | )
8 |
9 | type AdController struct {
10 | BaseController
11 | }
12 |
13 | func (c *AdController) Construct() {
14 | c.Table = &tables.Advert{}
15 | c.Entries = &[]tables.Advert{}
16 |
17 | c.KeywordsSearch = []SearchFieldDsl{
18 | {Field: "name", Op: "LIKE", DataExp: "%$?%"},
19 | }
20 |
21 | c.AppId = "admin"
22 | c.Group = "广告管理"
23 | c.SubGroup = "广告管理"
24 | c.ApiEntityName = "广告"
25 | c.BaseController.Construct()
26 |
27 | c.OpBefore = func(i int, a any) error {
28 | if c.IsOperate(i) {
29 | t := a.(*tables.Advert)
30 | if len(t.DateRange) == 2 {
31 | t.StartTime = pointer.To(helper.ToTableTime(t.DateRange[0]))
32 | t.EndTime = pointer.To(helper.ToTableTime(t.DateRange[1]))
33 | }
34 | }
35 | return nil
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/application/models/document_field_model.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "github.com/xiusin/pinecms/src/application/models/tables"
5 | "github.com/xiusin/pinecms/src/common/helper"
6 | "xorm.io/xorm"
7 | )
8 |
9 | type DocumentModelFieldModel struct {
10 | orm *xorm.Engine
11 | }
12 |
13 | func NewDocumentModelFieldModel() *DocumentModelFieldModel {
14 | return &DocumentModelFieldModel{orm: helper.GetORM()}
15 | }
16 |
17 | func (w *DocumentModelFieldModel) GetList(page, limit int64) (list []*tables.DocumentModelField, total int64) {
18 | offset := (page - 1) * limit
19 | total, _ = w.orm.Limit(int(limit), int(offset)).FindAndCount(&list)
20 | return list, total
21 | }
22 |
23 | func (w *DocumentModelFieldModel) GetMap() map[int64]*tables.DocumentModelField {
24 | var list []*tables.DocumentModelField
25 | var mapList = map[int64]*tables.DocumentModelField{}
26 | _ = w.orm.Find(&list)
27 | for _, v := range list {
28 | mapList[v.Id] = v
29 | }
30 | return mapList
31 | }
32 |
--------------------------------------------------------------------------------
/src/application/models/tables/document_model.go:
--------------------------------------------------------------------------------
1 | package tables
2 |
3 | type DocumentModel struct {
4 | Id int64 `json:"id"`
5 | Name string `json:"table_name" xorm:"comment('模型名') varchar(100)"`
6 | Table string `json:"table" xorm:"comment('模型表名') varchar(100)"`
7 | Enabled int `json:"enabled"`
8 | ModelType int64 `json:"model_type" xorm:"comment('模型类型: 1=系统模型 0=普通模型')"`
9 | FeTplIndex string `json:"fe_tpl_index" xorm:"comment('前端列表主页模板') varchar(70)"`
10 | FeTplList string `json:"fe_tpl_list" xorm:"comment('前端列表模板') varchar(70)"`
11 | FeTplDetail string `json:"fe_tpl_detail" xorm:"comment('前端详情模板') varchar(70)"`
12 | Remark string `json:"remark" api:"remark:备注" xorm:"comment('备注') text"`
13 | CreatedAt *LocalTime `json:"created"`
14 | UpdatedAt *LocalTime `json:"updated"`
15 | Execed bool `json:"execed" xorm:"comment('是否已执行') tinyint(1)"`
16 | DeletedAt *LocalTime `xorm:"deleted" json:"-"`
17 | }
18 |
--------------------------------------------------------------------------------
/src/application/controllers/backend/filemanager/tables/file_manager_account.go:
--------------------------------------------------------------------------------
1 | package tables
2 |
3 | import (
4 | "crypto/md5"
5 | "encoding/hex"
6 | "fmt"
7 |
8 | "github.com/xiusin/pinecms/src/common/helper"
9 | )
10 |
11 | type FileManagerAccount struct {
12 | Id int64 `json:"id"`
13 | Nickname string `json:"nickname" xorm:"nickname"`
14 | Username string `json:"username" xorm:"username"`
15 | Password string `json:"pwd" xorm:"pwd"`
16 | Salt string `json:"-" xorm:"salt"`
17 | Disk string `json:"disk" xorm:"disk"`
18 | Engine string `json:"engine" xorm:"engine"`
19 | }
20 |
21 | func (c *FileManagerAccount) Init() error {
22 | if c.Username == "" {
23 | return fmt.Errorf("用户不能为空")
24 | }
25 | if c.Salt == "" {
26 | c.Salt = helper.GetRandomString(4)
27 | }
28 | return nil
29 | }
30 |
31 | func (c *FileManagerAccount) GetMd5Pwd(pwd string) string {
32 | hash := md5.New()
33 | hash.Write([]byte(pwd + c.Salt))
34 | return hex.EncodeToString(hash.Sum(nil))
35 | }
36 |
--------------------------------------------------------------------------------
/resources/configs/application.yml:
--------------------------------------------------------------------------------
1 | debug: true
2 | port: 2019
3 | view:
4 | reload: false
5 | fedirname: "./resources/themes/"
6 | bedirname: "./resources/views/"
7 | theme: "default"
8 | session:
9 | name: "gosessionid"
10 | expires: 0
11 |
12 | runtime_path: "runtime"
13 | cache_db: "cache.db"
14 | log_path: "logs"
15 |
16 | plugin_enable: false
17 | plugin_path: "plugins"
18 |
19 | favicon: "./resources/assets/favicon.ico"
20 | charset: "UTF-8"
21 | jwtkey: "jwt_token_you_need_set_again"
22 | hashkey: "the-big-and-secret-fash-key-here" # 只支持固定大小到字节, AES only supports key sizes of 16, 24 or 32 bytes.
23 | blockkey: "lot-secret-of-characters-big-too"
24 | max_bodysize: 32 # MB
25 |
26 | upload:
27 | engine: "oss" #oss or file
28 | base_path: "uploads" # 基本路径
29 |
30 | statics:
31 | - { route: "/assets/", path: "./resources/assets/"}
32 | - { route: "/uploads/", path: "./resources/assets/uploads/"}
33 |
34 |
35 | search:
36 | url: http://127.0.0.1:4080
37 | username: admin
38 | password: 123456
39 |
--------------------------------------------------------------------------------
/src/application/models/tables/setting.go:
--------------------------------------------------------------------------------
1 | package tables
2 |
3 | type Setting struct {
4 | Id uint `json:"id" xorm:"pk autoincr"`
5 | Key string `xorm:"unique comment('配置KEY') varchar(50)" json:"key" schema:"key"`
6 | FormName string `json:"form_name" xorm:"comment('名称') varchar(100)" schema:"form_name"`
7 | Value string `json:"value" schema:"value" xorm:"comment('配置值') text"`
8 | Group string `json:"group" schema:"group" xorm:"comment('所属分组')"`
9 | Default string `json:"default" schema:"default" xorm:"comment('默认值')"`
10 | Listorder uint `json:"listorder" xorm:"comment('列表排序')"`
11 | Remark string `json:"remark" xorm:"comment('配置描述')"`
12 | Editor string `json:"editor" schema:"editor"`
13 | Extra string `json:"extra" xorm:"-" schema:"extra"`
14 | Options map[string]any `json:"options" xorm:"json"`
15 | }
16 |
17 | type KV struct {
18 | Label string `json:"label"`
19 | Value any `json:"value"`
20 | }
21 |
--------------------------------------------------------------------------------
/src/application/models/document_dsl_model.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "github.com/xiusin/pine"
5 | "github.com/xiusin/pinecms/src/application/models/tables"
6 | "github.com/xiusin/pinecms/src/common/helper"
7 | "xorm.io/xorm"
8 | )
9 |
10 | type DocumentModelDslModel struct {
11 | orm *xorm.Engine
12 | }
13 |
14 | func NewDocumentFieldDslModel() *DocumentModelDslModel {
15 | return &DocumentModelDslModel{orm: helper.GetORM()}
16 | }
17 |
18 | func (w *DocumentModelDslModel) GetList(mid int64) []tables.DocumentModelDsl {
19 | var list []tables.DocumentModelDsl
20 | err := w.orm.Where("mid = ?", mid).Asc("listorder").Asc("id").Find(&list)
21 | if err != nil {
22 | pine.Logger().Error("NewDocumentFieldDslModel: ", err)
23 | }
24 | return list
25 | }
26 |
27 | func (w *DocumentModelDslModel) DeleteByMID(mid int64) bool {
28 | _, err := w.orm.Where("mid=?", mid).Delete(&tables.DocumentModelDsl{})
29 | if err != nil {
30 | pine.Logger().Error("DocumentModelDslModel.DeleteByMID", err)
31 | return false
32 | }
33 | return true
34 | }
35 |
--------------------------------------------------------------------------------
/src/application/controllers/taglibs/type.go:
--------------------------------------------------------------------------------
1 | package taglibs
2 |
3 | import (
4 | "fmt"
5 | "github.com/CloudyKit/jet"
6 | "github.com/xiusin/pine"
7 | "github.com/xiusin/pinecms/src/application/models"
8 | "github.com/xiusin/pinecms/src/application/models/tables"
9 | "github.com/xiusin/pinecms/src/common/helper"
10 | "reflect"
11 | "runtime/debug"
12 | )
13 |
14 | func Type(args jet.Arguments) reflect.Value {
15 | fmt.Println("arclist")
16 |
17 | if !checkArgType(&args) {
18 | return defaultArrReturnVal
19 | }
20 | defer func() {
21 | if err := recover(); err != nil {
22 | pine.Logger().Error("Type Failed", string(debug.Stack()))
23 | }
24 | }()
25 | catid := getNumber(args.Get(0))
26 | if catid < 0 {
27 | panic("typeid参数不能小于1")
28 | }
29 | orm := helper.GetORM()
30 | var data = &tables.Category{}
31 | sess := orm.Table(data)
32 | defer sess.Close()
33 | exists, _ := sess.ID(catid).Get(data)
34 | if exists && data.Type != 2 {
35 | data.Url = fmt.Sprintf("/%s/", models.NewCategoryModel().GetUrlPrefix(data.Catid))
36 | }
37 | return reflect.ValueOf(data)
38 | }
39 |
--------------------------------------------------------------------------------
/src/application/models/page_model.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "github.com/xiusin/pine"
5 | "github.com/xiusin/pinecms/src/application/models/tables"
6 | "github.com/xiusin/pinecms/src/common/helper"
7 | "xorm.io/xorm"
8 | )
9 |
10 | type PageModel struct {
11 | orm *xorm.Engine
12 | }
13 |
14 | func NewPageModel() *PageModel {
15 | return &PageModel{orm: helper.GetORM()}
16 | }
17 |
18 | func (p *PageModel) AddPage(page *tables.Page) bool {
19 | res, _ := p.orm.Insert(page)
20 | return res != 0
21 | }
22 |
23 | func (p *PageModel) UpdatePage(page *tables.Page) bool {
24 | res, _ := p.orm.Where("catid=?", page.Id).Update(page)
25 | return res != 0
26 | }
27 |
28 | func (p *PageModel) DelPage(catid int64) bool {
29 | res, _ := p.orm.Delete(&tables.Page{Id: catid})
30 | return res != 0
31 | }
32 |
33 | func (p *PageModel) GetPage(catid int64) *tables.Page {
34 | page := &tables.Page{Id: catid}
35 | exists, err := p.orm.Get(page)
36 | if err != nil {
37 | pine.Logger().Error("获取page信息失败:", err)
38 | }
39 | if exists {
40 | return page
41 | }
42 | return nil
43 | }
44 |
--------------------------------------------------------------------------------
/src/application/models/tables/admin.go:
--------------------------------------------------------------------------------
1 | package tables
2 |
3 | type Admin struct {
4 | Userid int64 `xorm:"pk autoincr 'id'" json:"id"`
5 | Username string `json:"username"`
6 | Password string `json:"password,omitempty"`
7 | Roleid []int64 `json:"roleid" xorm:"-"`
8 | Encrypt string `json:"-"`
9 | Lastloginip string `json:"lastloginip"`
10 | Lastlogintime int64 `json:"lastlogintime"`
11 | Email string `json:"email"`
12 | Realname string `json:"realname"`
13 | Avatar string `json:"avatar"`
14 | Remark string `json:"remark"`
15 | RoleName string `json:"roleName" xorm:"-"`
16 | Phone string `json:"phone"`
17 | Status uint `json:"status"`
18 | PositionId uint `json:"position_id"`
19 | LevelId uint `json:"level_id"`
20 | DepartmentId uint `json:"department_id"`
21 | Birthday *LocalTime `json:"birthday"`
22 | Sex uint `json:"sex"`
23 |
24 | RoleIdList []int64 `json:"roleIdList" xorm:"json roles"`
25 | }
26 |
--------------------------------------------------------------------------------
/src/application/controllers/frontend/index.go:
--------------------------------------------------------------------------------
1 | package frontend
2 |
3 | import (
4 | "os"
5 | "path/filepath"
6 |
7 | "github.com/xiusin/pine"
8 | "github.com/xiusin/pine/render/engine/pjet"
9 | "github.com/xiusin/pinecms/src/application/controllers"
10 | )
11 |
12 | func (c *IndexController) Index() {
13 | c.setTemplateData()
14 | indexPage := "editor.tpl"
15 | pageFilePath := GetStaticFile(indexPage)
16 | _ = os.MkdirAll(filepath.Dir(pageFilePath), os.ModePerm)
17 | f, err := os.OpenFile(pageFilePath, os.O_CREATE|os.O_TRUNC|os.O_RDWR, os.ModePerm)
18 | if err != nil {
19 | c.Logger().Error(err.Error())
20 | return
21 | }
22 | defer f.Close()
23 | jet := pine.Make(controllers.ServiceJetEngine).(*pjet.PineJet)
24 | temp, err := jet.GetTemplate(template("index.jet"))
25 | if err != nil {
26 | c.Logger().Error(err.Error())
27 | return
28 | }
29 | err = temp.Execute(f, viewDataToJetMap(c.Render().GetViewData()), nil)
30 | if err != nil {
31 | c.Logger().Error(err.Error())
32 | return
33 | }
34 | data, _ := os.ReadFile(pageFilePath)
35 |
36 | _ = c.Ctx().WriteHTMLBytes(data)
37 | }
38 |
--------------------------------------------------------------------------------
/src/application/models/tables/document_model_field.go:
--------------------------------------------------------------------------------
1 | package tables
2 |
3 | type DocumentModelField struct {
4 | Id int64 `json:"id" xorm:"id pk autoincr"`
5 | Name string `json:"name" xorm:"comment('组件名称') varchar(30)"`
6 | Type string `json:"type" xorm:"comment('数据库字段类型') varchar(20)"`
7 | Desc string `json:"desc" xorm:"comment('组件使用场景描述') text"`
8 | ListComp string `json:"list_comp" xorm:"comment('列表渲染组件') text"`
9 | FormComp string `json:"form_comp" xorm:"comment('对应vue组件') varchar(30)"`
10 | Props string `json:"props" xorm:"comment('属性配置') text"`
11 | }
12 |
13 | const (
14 | FieldTypeNull = iota
15 | FieldTypeInput
16 | FieldTypeMulInput
17 | FieldTypeEditorQuill
18 | FieldTypeAttachment
19 | FieldTypeSelect
20 | FieldTypeCascader
21 | FieldTypeRadio
22 | FieldTypeCheckbox
23 | FieldTypeInputNumberInt
24 | FieldTypeInputNumberFloat
25 | FieldTypeImageUpload
26 | FieldTypeImageMulUpload
27 | FieldTypeSwitch
28 | FieldTypeDate
29 | FieldTypeTags
30 | FieldTypeFlag
31 | FieldTypeMarkdown
32 | FieldTypeCodeEditor
33 | FieldTypeRate
34 | FieldTypeUeditor
35 | )
36 |
--------------------------------------------------------------------------------
/src/application/controllers/cache_key.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | const CacheTheme = "theme"
4 | const CacheStatistics = "pinecms_persist.statistics"
5 | const CacheRefer = "statistics_refer"
6 | const CacheMemCollect = "pinecms.mem.collect"
7 | const CacheAdminMenuByRoleIdAndMenuId = "pinecms.admin_menu_%d_%d"
8 | const CacheAdminPriv = "pinecms.admin_priv_%d"
9 | const CacheSetting = "pinecms.setting"
10 | const CacheFeTplList = "pinecms.fe_tpl_list"
11 | const CacheDocumentModelPrefix = "pinecms.document_model.%d"
12 | const CacheModels = "pinecms.models"
13 | const CacheCategories = "pinecms.categories"
14 | const CacheCategoryPosPrefix = "pinecms.category.pos.%d"
15 | const CacheCategoryInfoPrefix = "pinecms.category.%d"
16 | const CacheCategoryContentPrefix = "pinecms.content.%d_%d"
17 | const CacheModelTablePrefix = "pinecms.model.table.%d"
18 | const CacheMysqlVersion = "pinecms.mysql.version"
19 | const CacheAdminRoles = "pinecms.admin.roles"
20 | const CacheTableNameFields = "pinecms.orm.tbl.%s.fields"
21 | const CacheAdminRolesList = "pinecms.admin.roles.%d"
22 | const CacheTableNames = "pinecms.orm.table.name"
23 |
24 |
--------------------------------------------------------------------------------
/src/application/controllers/middleware/apidoc/entity.go:
--------------------------------------------------------------------------------
1 | package apidoc
2 |
3 | import (
4 | "github.com/fatih/structs"
5 | "github.com/xiusin/pine"
6 | )
7 |
8 | type Entity struct {
9 | Title string // 标题
10 | Desc string // 描述
11 | ApiParam any // 参数
12 | AppId string // 应用id
13 | Group string // 分组名称
14 | SubGroup string // 子分组名称
15 | Tag []string // 标签
16 | }
17 |
18 | // SetApiEntity 设置接口实体信息
19 | func SetApiEntity(ctx *pine.Context, entity *Entity, configures ...Configure) {
20 | if entity.ApiParam != nil && !structs.IsStruct(entity.ApiParam) {
21 | ctx.Logger().Warn("不支持非struct类型的请求参数")
22 | } else if defaultConfig.Enable {
23 | e, ok := ctx.Value(apiDocKey).(*apiEntity)
24 | if ok {
25 | e.Title = entity.Title
26 | e.Desc = entity.Desc
27 | e.configured = true
28 | e.Group = apiGroup{Title: entity.Group, Name: entity.Group}
29 | e.SubGroup = entity.SubGroup
30 | for _, configure := range configures {
31 | configure(e)
32 | }
33 | e.AppId = entity.AppId
34 | e.Param, _ = parseInterface(entity.ApiParam)
35 | e.FilterParams()
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/application/controllers/backend/webssh/common/jwt.go:
--------------------------------------------------------------------------------
1 | package common
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/golang-jwt/jwt"
7 | )
8 |
9 | var jwtKet = []byte("config.Config.Jwt.Key")
10 |
11 | type Claims struct {
12 | Userid int64
13 | jwt.StandardClaims
14 | }
15 |
16 | func ReleaseToken(id int64) (token string, err error) {
17 | expireTime := time.Now().Add(3 * 24 * time.Hour)
18 | claims := &Claims{
19 | Userid: id,
20 | StandardClaims: jwt.StandardClaims{
21 | ExpiresAt: expireTime.Unix(),
22 | IssuedAt: time.Now().Unix(),
23 | Issuer: "admin",
24 | Subject: "user",
25 | },
26 | }
27 | tokenObj := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
28 | token, err = tokenObj.SignedString(jwtKet)
29 | return
30 | }
31 |
32 | func ParseToken(token string) (*Claims, error) {
33 | tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (any, error) {
34 | return jwtKet, nil
35 | })
36 |
37 | if tokenClaims != nil {
38 | if claims, ok := tokenClaims.Claims.(*Claims); ok && tokenClaims.Valid {
39 | return claims, nil
40 | }
41 | }
42 | return nil, err
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/src/application/controllers/taglibs/flink.go:
--------------------------------------------------------------------------------
1 | package taglibs
2 |
3 | import (
4 | "fmt"
5 | "reflect"
6 | "strings"
7 |
8 | "github.com/CloudyKit/jet"
9 | "github.com/xiusin/pine"
10 | "github.com/xiusin/pinecms/src/application/models/tables"
11 | "github.com/xiusin/pinecms/src/common/helper"
12 | )
13 |
14 | func Flink(args jet.Arguments) reflect.Value {
15 | if !checkArgType(&args) {
16 | return defaultArrReturnVal
17 | }
18 | defer func() {
19 | if err := recover(); err != nil {
20 | pine.Logger().Warn(fmt.Sprintf("flink panic: %v", err))
21 | }
22 | }()
23 | orm := helper.GetORM()
24 | sess := orm.Table(&tables.Link{})
25 | defer sess.Close()
26 | row := int(args.Get(0).Float())
27 | if row == 0 {
28 | row = 10
29 | sess.Limit(row)
30 | }
31 |
32 | idParam := args.Get(1).String()
33 | if len(idParam) != 0 {
34 | ids := strings.Split(idParam, ",")
35 | sess.In("id", ids)
36 | }
37 |
38 | sort := args.Get(2).String()
39 | if len(sort) != 0 {
40 | sess.OrderBy(sort)
41 | } else {
42 | sess.Desc("id")
43 | }
44 | data := []tables.Link{}
45 | helper.PanicErr(sess.Find(&data))
46 | return reflect.ValueOf(data)
47 | }
48 |
--------------------------------------------------------------------------------
/src/application/controllers/backend/log_controller.go:
--------------------------------------------------------------------------------
1 | package backend
2 |
3 | import (
4 | "github.com/xiusin/pinecms/src/application/controllers/middleware/apidoc"
5 | "github.com/xiusin/pinecms/src/application/models/tables"
6 | "github.com/xiusin/pinecms/src/common/helper"
7 | )
8 |
9 | type LogController struct {
10 | BaseController
11 | }
12 |
13 | func (c *LogController) Construct() {
14 | c.Group = "操作日志"
15 |
16 | c.KeywordsSearch = []SearchFieldDsl{
17 | {Field: "username", Op: "LIKE", DataExp: "%$?%"},
18 | {Field: "ip", Op: "LIKE", DataExp: "%$?%"},
19 | {Field: "uri", Op: "LIKE", DataExp: "%$?%"},
20 | {Field: "params", Op: "LIKE", DataExp: "%$?%"},
21 | }
22 | c.SearchFields = []SearchFieldDsl{
23 | {Field: "method"},
24 | }
25 | c.Table = &tables.RequestLog{}
26 | c.Entries = &[]tables.RequestLog{}
27 | c.apiEntities = map[string]apidoc.Entity{
28 | "list": {Title: "日志列表", Desc: "查询系统接口请求日志列表"},
29 | "clear": {Title: "清空日志", Desc: "一键清理系统所有日志"},
30 | }
31 | c.BaseController.Construct()
32 | }
33 |
34 | func (c *LogController) PostClear() {
35 | _, _ = c.Orm.Where("id > 0").Delete(c.Table)
36 | helper.Ajax("清理成功", 0, c.Ctx())
37 | }
38 |
--------------------------------------------------------------------------------
/src/application/controllers/frontend/click.go:
--------------------------------------------------------------------------------
1 | package frontend
2 |
3 | func (c *IndexController) Click() {
4 |
5 | //aid, _ := c.Ctx().Input().GetInt64("aid")
6 | //tid, _ := c.Ctx().Input().GetInt64("tid")
7 | //if aid < 1 || tid < 1 {
8 | // c.Ctx().Abort(http.StatusNotFound)
9 | // return
10 | //}
11 | //clickCache := fmt.Sprintf("click_%d_%d", tid, aid)
12 | //info := c.Ctx().GetCookie(clickCache)
13 | //if len(info) == 0 {
14 | // res, err := di.MustGet("orm").(*xorm.Engine).Table(models.NewCategoryModel().GetTable(tid)).ID(aid).Incr("visit_count").Exec()
15 | // if err != nil {
16 | // logger.Error("无法更新点击数据", err)
17 | // return
18 | // }
19 | // if affe, _ := res.RowsAffected(); affe > 0 {
20 | // c.Ctx().SetCookie(clickCache, "1", 0)
21 | // }
22 | //}
23 | }
24 |
25 | func (c *IndexController) GetClick() {
26 | //aid, _ := c.Ctx().Input().GetInt64("aid")
27 | //tid, _ := c.Ctx().Input().GetInt64("tid")
28 | //if aid < 1 || tid < 1 {
29 | // c.Ctx().Abort(http.StatusNotFound)
30 | // return
31 | //}
32 | //fmt.Println(di.MustGet("orm").(*xorm.Engine).Table(models.NewCategoryModel().GetTable(tid)).ID(aid).Select("visit_count").QueryString())
33 | }
34 |
--------------------------------------------------------------------------------
/tests/ast_test.go:
--------------------------------------------------------------------------------
1 | package tests
2 |
3 | import (
4 | "bytes"
5 | "go/ast"
6 | "go/parser"
7 | "go/printer"
8 | "go/token"
9 | "os"
10 | "testing"
11 | )
12 |
13 | func TestAST(t *testing.T) {
14 | filePath := "../src/router/module.go"
15 | set := token.NewFileSet()
16 | content, _ := os.ReadFile(filePath)
17 |
18 | node, err := parser.ParseFile(set, filePath, content, parser.AllErrors)
19 | if err != nil {
20 | t.Fatal(err)
21 | }
22 |
23 | // TODO 动态解析代码,找到 InitModuleRouter 函数,然后在函数体中添加新的代码
24 | ast.Inspect(node, func(n ast.Node) bool {
25 | // 查找函数声明节点
26 | if fn, ok := n.(*ast.FuncDecl); ok && fn.Name.Name == "InitModuleRouter" {
27 | // 创建新的语句
28 | newStmt := &ast.ExprStmt{
29 | X: &ast.CallExpr{
30 | Fun: ast.NewIdent("fmt.Println"),
31 | Args: []ast.Expr{ast.NewIdent("\"New code added to InitModuleRouter\"")},
32 | },
33 | }
34 | // 将新语句追加到函数体中
35 | fn.Body.List = append(fn.Body.List, newStmt)
36 |
37 | // 打印修改后的代码
38 | var buf bytes.Buffer
39 | printer.Fprint(&buf, set, node)
40 | os.WriteFile(filePath, buf.Bytes(), 0644) // webssh.InitRouter(app, backendRouter)
41 | }
42 |
43 | return true
44 | })
45 | }
46 |
--------------------------------------------------------------------------------
/src/application/models/tables/content.go:
--------------------------------------------------------------------------------
1 | package tables
2 |
3 | type Content struct {
4 | Id int64 `form:"id" json:"id"`
5 | Catid int64 `form:"catid" json:"catid"`
6 | Title string `form:"title" json:"title"`
7 | Thumb string `form:"thumb" json:"thumb"`
8 | Keywords string `form:"keywords" json:"keywords"`
9 | Description string `form:"description" json:"description"`
10 | Content string `form:"content" json:"content"`
11 | Listorder int64 `form:"listorder" json:"listorder"`
12 | Status int64 `form:"status" json:"status"`
13 | Recommend int64 `form:"recommend" json:"recommend"`
14 | PwdType int64 `form:"pwd_type" json:"pwd_type"`
15 | Money int64 `form:"money" json:"money"`
16 | Userid int64 `form:"userid" json:"userid"`
17 | CreatedAt int64 `form:"created_at" json:"created_at"`
18 | UpdatedAt int64 `form:"updated_at" json:"updated_at"`
19 | DeletedAt int64 `form:"deleted_at" json:"deleted_at"`
20 | SourceUrl string `form:"source_url" json:"source_url"`
21 | SourcePwd string `form:"source_pwd" json:"source_pwd"`
22 | Catids string `form:"catids" json:"catids"`
23 | Tags string `form:"tags" json:"tags"`
24 | }
25 |
--------------------------------------------------------------------------------
/src/application/controllers/taglibs/query.go:
--------------------------------------------------------------------------------
1 | package taglibs
2 |
3 | import (
4 | "reflect"
5 | "strings"
6 |
7 | "github.com/xiusin/pinecms/src/common/helper"
8 |
9 | "github.com/CloudyKit/jet"
10 | "github.com/xiusin/pine"
11 | "github.com/xiusin/pinecms/src/config"
12 | )
13 |
14 | /**
15 | * 标签:{{yield query(sql="") content}} {{end}}
16 | * 作用:特殊标签,SQL查询标签
17 | * 用法示例: {{yield query(sql="SELECT * FROM #@_tables") content}} .. HTML ..{{end}}
18 | * 参数说明:
19 | * sql SQL语句,只用于select类型语句
20 | */
21 | func Query(args jet.Arguments) reflect.Value {
22 | if !checkArgType(&args) {
23 | return defaultArrReturnVal
24 | }
25 | defer func() {
26 | if err := recover(); err != nil {
27 | pine.Logger().Error("Query Failed", err)
28 | }
29 | }()
30 | sess := helper.GetORM()
31 | query := strings.Trim(args.Get(0).String(), " \n\t")
32 | // 只允许查询操作
33 | conf := config.DB()
34 | if strings.HasPrefix(query, "SELECT") || strings.HasPrefix(query, "select") {
35 | rest, err := sess.QueryString(strings.ReplaceAll(query, "#@_", conf.Db.DbPrefix))
36 | helper.PanicErr(err)
37 | if rest != nil {
38 | return reflect.ValueOf(rest)
39 | }
40 | }
41 | return reflect.ValueOf([]map[string]string{})
42 | }
43 |
--------------------------------------------------------------------------------
/src/application/controllers/backend/menu_controller.go:
--------------------------------------------------------------------------------
1 | package backend
2 |
3 | import (
4 | "github.com/xiusin/pinecms/src/application/controllers/middleware/apidoc"
5 | "github.com/xiusin/pinecms/src/application/models/tables"
6 | "github.com/xiusin/pinecms/src/common/helper"
7 | )
8 |
9 | type MenuController struct {
10 | BaseController
11 | }
12 |
13 | func (c *MenuController) Construct() {
14 | c.KeywordsSearch = []SearchFieldDsl{
15 | {Field: "name", Op: "LIKE", DataExp: "%$?%"},
16 | }
17 | c.Table = &tables.Menu{}
18 | c.Entries = &[]tables.Menu{}
19 |
20 | c.AppId = "admin"
21 | c.Group = "菜单管理"
22 | c.SubGroup = "菜单管理"
23 |
24 | c.apiEntities = map[string]apidoc.Entity{
25 | "list": {Title: "菜单列表", Desc: "获悉系统内配置的菜单列表"},
26 | "add": {Title: "新增菜单", Desc: "新增一个菜单,可基于任何菜单添加子菜单"},
27 | "edit": {Title: "编辑菜单", Desc: "修改给定菜单信息"},
28 | "delete": {Title: "删除菜单", Desc: "删除指定菜单"},
29 | "info": {Title: "菜单详情", Desc: "获取指定菜单的详情信息"},
30 | }
31 |
32 | c.BaseController.Construct()
33 | }
34 |
35 | func (c *MenuController) PostList() {
36 | if err := c.Orm.Find(c.Entries); err != nil {
37 | helper.Ajax(err, 1, c.Ctx())
38 | return
39 | }
40 | helper.Ajax(c.Entries, 0, c.Ctx())
41 | }
42 |
--------------------------------------------------------------------------------
/src/application/models/tables/dict_category.go:
--------------------------------------------------------------------------------
1 | package tables
2 |
3 | import "time"
4 |
5 | type DictCategory struct {
6 | Id uint `xorm:"pk autoincr 'id'" json:"id" api:"remark:分类ID"`
7 | Key string `json:"key" xorm:"key unique notnull" api:"remark:分类标识,唯一|require:true"`
8 | Name string `json:"name" xorm:"notnull" api:"remark:分类名称|require:true"`
9 | Remark string `json:"remark" api:"remark:分类备注信息"`
10 | Status bool `json:"status" api:"remark:状态|require:true"`
11 | CreatedAt time.Time `xorm:"created"`
12 | UpdatedAt time.Time `xorm:"updated"`
13 | }
14 |
15 | type Dict struct {
16 | Id uint `xorm:"pk autoincr 'id'" json:"id" api:"remark:字典属性ID"`
17 | Cid uint `json:"cid" xorm:"notnull" api:"remark:分类ID"`
18 | CatName string `json:"cat_name" xorm:"-"`
19 | Name string `json:"name" xorm:"unique notnull" api:"remark:字典名称|require:true"`
20 | Value string `json:"value" api:"remark:字典值|require:true"`
21 | Remark string `json:"remark" api:"remark:分类备注信息"`
22 | Sort uint `json:"sort"`
23 | Status bool `json:"status"`
24 | CreatedAt time.Time `xorm:"created"`
25 | UpdatedAt time.Time `xorm:"updated"`
26 | }
27 |
--------------------------------------------------------------------------------
/src/application/controllers/backend/member_controller.go:
--------------------------------------------------------------------------------
1 | package backend
2 |
3 | import (
4 | "errors"
5 |
6 | "github.com/xiusin/pinecms/src/application/models/tables"
7 | "xorm.io/builder"
8 | )
9 |
10 | type MemberController struct {
11 | BaseController
12 | }
13 |
14 | func (c *MemberController) Construct() {
15 | c.KeywordsSearch = []SearchFieldDsl{
16 | {Field: "name", Op: "LIKE", DataExp: "%$?%"},
17 | }
18 | c.Table = &tables.Member{}
19 | c.Entries = &[]tables.Member{}
20 | c.ApiEntityName = "会员"
21 | c.Group = "会员管理"
22 |
23 | c.BaseController.Construct()
24 | c.OpBefore = c.before
25 | }
26 |
27 | func (c *MemberController) before(act int, params any) error {
28 | switch act {
29 | case OpAdd:
30 | data := c.Table.(*tables.Member)
31 | if exist, _ := c.Orm.Table(c.Table).Where("account = ?", data.Account).Or("email = ?", data.Email).Exist(); exist {
32 | return errors.New("账号或邮箱已存在")
33 | }
34 |
35 | case OpEdit:
36 | data := c.Table.(*tables.Member)
37 | if exist, _ := c.Orm.Table(c.Table).Where("id <> ?", data.Id).
38 | Where(builder.Eq{"account": data.Account}.Or(builder.Eq{"email": data.Email})).Exist(); exist {
39 | return errors.New("账号或邮箱已存在")
40 | }
41 | }
42 | return nil
43 | }
44 |
--------------------------------------------------------------------------------
/src/application/models/tables/wechat_template.go:
--------------------------------------------------------------------------------
1 | package tables
2 |
3 | type WechatMsgTemplate struct {
4 | Id int64 `json:"id" xorm:"pk autoincr"`
5 | Appid string `json:"appid" xorm:"char(20) not null comment('appid')"`
6 | TemplateId string `json:"template_id" xorm:"varchar(50) not null comment('模板ID')"`
7 | Title string `json:"title" xorm:"varchar(20) comment('模板标题')"`
8 | Name string `json:"name" xorm:"varchar(20) comment('模板名称')"`
9 | PrimaryIndustry string `json:"primary_industry"`
10 | DeputyIndustry string `json:"deputy_industry"`
11 | Content string `json:"content" xorm:"text"`
12 | Data []map[string]any `json:"data" xorm:"json"`
13 | Url string `json:"url" xorm:"varchar(255)"`
14 | MiniProgram map[string]any `json:"miniprogram" xorm:"json"`
15 | Status bool `json:"status" xorm:"comment('是否有效0=无效,1=有效')"`
16 | Example string `json:"example" xorm:"comment('模板示例')"`
17 | CreatedAt LocalTime `json:"created_at" xorm:"created"`
18 | UpdatedAt LocalTime `json:"updated_at" xom:"updated"`
19 | }
20 |
--------------------------------------------------------------------------------
/src/application/models/attachments_model.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "github.com/xiusin/pine"
5 | "github.com/xiusin/pinecms/src/application/models/tables"
6 | "github.com/xiusin/pinecms/src/common/helper"
7 | "xorm.io/xorm"
8 | )
9 |
10 | const (
11 | IMG_TYPE = "img"
12 | FILE_TYPE = "file"
13 | )
14 |
15 | type AttachmentsModel struct {
16 | orm *xorm.Engine
17 | }
18 |
19 | func NewAttachmentsModel() *AttachmentsModel {
20 | return &AttachmentsModel{orm: helper.GetORM()}
21 | }
22 |
23 | func (a *AttachmentsModel) GetList(keywords string, page, limit int64) (list []tables.Attachments, total int64) {
24 | offset := (page - 1) * limit
25 | var err error
26 | sess := a.orm.Limit(int(limit), int(offset)).Desc("id")
27 | if len(keywords) != 0 {
28 | likePrtten := "%" + keywords + "%"
29 | sess.Where("origin_name like ?", likePrtten).Or("name like ?", likePrtten)
30 | }
31 | total, err = sess.FindAndCount(&list)
32 | if err != nil {
33 | pine.Logger().Error(err.Error())
34 | }
35 | if list == nil {
36 | list = []tables.Attachments{}
37 | }
38 | return list, total
39 | }
40 |
41 | func (a *AttachmentsModel) Delete(id int64) bool {
42 | res, _ := a.orm.ID(id).Delete(&tables.Attachments{})
43 | return res > 0
44 | }
45 |
--------------------------------------------------------------------------------
/cmd/server/service.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "os/signal"
7 | "syscall"
8 |
9 | "github.com/takama/daemon"
10 | "github.com/xiusin/pinecms/src/common/helper"
11 | "github.com/xiusin/pinecms/src/config"
12 | "github.com/xiusin/pinecms/src/server"
13 | )
14 |
15 | type Service struct{ daemon.Daemon }
16 |
17 | var serv *Service
18 |
19 | func (service *Service) Manage(args []string, usage string) (string, error) {
20 | if len(args) >= 1 && args[0] != "run" {
21 | switch args[0] {
22 | case "install":
23 | return service.Install()
24 | case "remove":
25 | return service.Remove()
26 | case "start":
27 | return service.Start()
28 | case "stop":
29 | return service.Stop()
30 | case "status":
31 | return service.Status()
32 | default:
33 | return usage, nil
34 | }
35 | }
36 |
37 | interrupt := make(chan os.Signal, 1)
38 | signal.Notify(interrupt, os.Interrupt, syscall.SIGTERM)
39 |
40 | go func() {
41 | defer helper.Recover()
42 | config.InitDB()
43 | fmt.Println("start server...")
44 | server.Server()
45 | }()
46 |
47 | killSignal := <-interrupt
48 | if killSignal == os.Interrupt {
49 | return "Daemon was interrupted by system signal", nil
50 | }
51 | return "Daemon was killed", nil
52 | }
53 |
--------------------------------------------------------------------------------
/src/common/helper/service.go:
--------------------------------------------------------------------------------
1 | package helper
2 |
3 | import (
4 | "context"
5 | "log/slog"
6 |
7 | "github.com/xiusin/pine"
8 | "github.com/xiusin/pine/contracts"
9 | "github.com/xiusin/pine/di"
10 | "github.com/xiusin/pine/middlewares/traceid"
11 | "github.com/xiusin/pinecms/src/application/controllers"
12 | )
13 |
14 | // Inject 注入依赖
15 | func Inject(key any, v any, single ...bool) {
16 | if len(single) == 0 {
17 | single = append(single, true)
18 | }
19 | if vi, ok := v.(di.BuildHandler); ok {
20 | di.Set(key, vi, single[0])
21 | } else {
22 | di.Set(key, func(_ di.AbstractBuilder) (i any, e error) {
23 | return v, nil
24 | }, single[0])
25 | }
26 | }
27 |
28 | // Cache 获取缓存服务
29 | func Cache() contracts.Cache {
30 | return pine.Make(controllers.ServiceICache).(contracts.Cache)
31 | }
32 |
33 | // App 获取应用实例
34 | func App() *pine.Application {
35 | return pine.Make(controllers.ServiceApplication).(*pine.Application)
36 | }
37 |
38 | // Slog 获取slog对象
39 | func Slog(ctxs ...context.Context) *slog.Logger {
40 | logger := pine.Logger().(*slog.Logger)
41 |
42 | if len(ctxs) > 0 {
43 | requestID := ctxs[0].Value(traceid.Key)
44 | if requestID != nil {
45 | logger = logger.With(traceid.Key, requestID)
46 | }
47 | }
48 | return logger
49 | }
50 |
--------------------------------------------------------------------------------
/src/common/template/gktemplate.go:
--------------------------------------------------------------------------------
1 | package template
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "strings"
7 |
8 | "github.com/gokeeptech/gktemplate"
9 | "github.com/xiusin/pinecms/src/common/helper"
10 | )
11 |
12 | type GkTemplate struct {
13 | *GkTemplateConf
14 | }
15 |
16 | type GkTemplateConf struct {
17 | NameSpace string
18 | L string
19 | R string
20 | Path string
21 | Ext string
22 | }
23 |
24 | func NewGkTemplate(conf *GkTemplateConf) *GkTemplate {
25 | if conf == nil {
26 | conf = &GkTemplateConf{"pinecms", "{", "}", "resources/themes", ".html"}
27 | }
28 | helper.PanicErr(gktemplate.LoadDir(strings.TrimRight(conf.Path, "/") + "/*" + conf.Ext))
29 | return &GkTemplate{conf}
30 | }
31 |
32 | func (g GkTemplate) Ext() string {
33 | return g.GkTemplateConf.Ext
34 | }
35 |
36 | func (g GkTemplate) AddFunc(s string, i any) {
37 | switch i := i.(type) {
38 | case gktemplate.TagFunc:
39 | fun := map[string]gktemplate.TagFunc{s: i}
40 | gktemplate.ExtFuncs(&fun)
41 | }
42 | }
43 |
44 | func (g GkTemplate) HTML(writer io.Writer, name string, binding map[string]any) error {
45 | var str string
46 | var err error
47 | if str, err = gktemplate.ParseFile(name, binding); err == nil {
48 | _, err = fmt.Fprintf(writer, str)
49 | }
50 | return err
51 | }
52 |
--------------------------------------------------------------------------------
/src/application/controllers/backend/ad_space_controller.go:
--------------------------------------------------------------------------------
1 | package backend
2 |
3 | import (
4 | "errors"
5 |
6 | "github.com/xiusin/pinecms/src/application/models/tables"
7 | )
8 |
9 | type AdSpaceController struct {
10 | BaseController
11 | }
12 |
13 | func (c *AdSpaceController) Construct() {
14 | c.Table = &tables.AdvertSpace{}
15 | c.Entries = &[]tables.AdvertSpace{}
16 | c.AppId = "admin"
17 | c.Group = "广告管理"
18 | c.SubGroup = "广告位管理"
19 | c.ApiEntityName = "广告位"
20 | c.BaseController.Construct()
21 | c.OpBefore = c.before
22 | }
23 |
24 | func (c *AdSpaceController) before(act int, params any) error {
25 | if act == OpDel {
26 | ids := params.(*idParams).Ids
27 | count, _ := c.Orm.In("space_id", ids).Count(&tables.Advert{})
28 | if count > 0 {
29 | return errors.New("广告位下还有广告,无法直接删除")
30 | }
31 | }
32 | if act == OpEdit || act == OpAdd {
33 | t, p := &tables.AdvertSpace{}, params.(*tables.AdvertSpace)
34 | if act == OpAdd {
35 | if exists, _ := c.Orm.Where("name = ? or `key` = ?", p.Name, p.Key).Exist(t); exists {
36 | return errors.New("广告位名称或标识已经存在")
37 | }
38 | } else {
39 | if exists, _ := c.Orm.Where("id <> ? and (name = ? or `key` = ?)", p.Id, p.Name, p.Key).Exist(t); exists {
40 | return errors.New("广告位名称或标识已经存在")
41 | }
42 | }
43 | }
44 | return nil
45 | }
46 |
--------------------------------------------------------------------------------
/src/application/controllers/backend/member_group_controller.go:
--------------------------------------------------------------------------------
1 | package backend
2 |
3 | import (
4 | "errors"
5 |
6 | "github.com/xiusin/pinecms/src/application/models/tables"
7 | "github.com/xiusin/pinecms/src/common/helper"
8 | "xorm.io/xorm"
9 | )
10 |
11 | type MemberGroupController struct {
12 | BaseController
13 | }
14 |
15 | func (c *MemberGroupController) Construct() {
16 | c.Table = &tables.MemberGroup{}
17 | c.Entries = &[]tables.MemberGroup{}
18 | c.ApiEntityName = "会员分组"
19 | c.Group = "会员分组管理"
20 | c.BaseController.Construct()
21 | c.OpBefore = c.before
22 | }
23 |
24 | func (c *MemberGroupController) before(act int, params any) error {
25 | switch act {
26 | case OpList:
27 | (params.(*xorm.Session)).Asc("listorder", "id")
28 | case OpAdd:
29 | data := c.Table.(*tables.MemberGroup)
30 | if exist, _ := c.Orm.Table(c.Table).Where("name = ?", data.Name).Exist(); exist {
31 | return errors.New("分组已存在")
32 | }
33 |
34 | case OpEdit:
35 | data := c.Table.(*tables.MemberGroup)
36 | if exist, _ := c.Orm.Table(c.Table).Where("id <> ?", data.Id).Where("name = ?", data.Name).Exist(); exist {
37 | return errors.New("分组已存在")
38 | }
39 | }
40 | return nil
41 | }
42 |
43 | func (c *MemberGroupController) GetSelect() {
44 | c.Orm.Find(c.Entries)
45 | helper.Ajax(c.Entries, 0, c.Ctx())
46 | }
47 |
--------------------------------------------------------------------------------
/src/application/models/log_model.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "github.com/xiusin/pine"
5 | "github.com/xiusin/pinecms/src/application/models/tables"
6 | "github.com/xiusin/pinecms/src/common/helper"
7 | "xorm.io/xorm"
8 | )
9 |
10 | type LogModel struct {
11 | orm *xorm.Engine
12 | }
13 |
14 | func NewLogModel() *LogModel {
15 | return &LogModel{orm: helper.GetORM()}
16 | }
17 |
18 | func (l *LogModel) GetList(page, limit int64) ([]tables.Log, int64) {
19 | offset := (page - 1) * limit
20 | var list = []tables.Log{}
21 | var total int64
22 | total, _ = l.orm.Count(&tables.Log{})
23 | if err := l.orm.Desc("logid").Limit(int(limit), int(offset)).Find(&list); err != nil {
24 | pine.Logger().Error(err.Error())
25 | }
26 | return list, total
27 | }
28 |
29 | func (l *LogModel) DeleteAll() bool {
30 | res, err := l.orm.Where("1=1").Delete(&tables.Log{})
31 | if err != nil {
32 | pine.Logger().Error(err.Error())
33 | return false
34 | }
35 | if res > 0 {
36 | return true
37 | }
38 | return false
39 | }
40 |
41 | func (l *LogModel) DeleteBeforeByDate(date string) bool {
42 | res, err := l.orm.Where("`time` <= ? ", date).Delete(&tables.Log{})
43 | if err != nil {
44 | pine.Logger().Error(err.Error())
45 | return false
46 | }
47 | if res > 0 {
48 | return true
49 | }
50 | return false
51 | }
52 |
--------------------------------------------------------------------------------
/src/application/models/tables/menu.go:
--------------------------------------------------------------------------------
1 | package tables
2 |
3 | type Menu struct {
4 | Id int64 `xorm:"pk autoincr" json:"id" api:"remark:菜单ID"`
5 | Name string `json:"name" api:"remark:菜单名称"`
6 | Parentid int64 `json:"parentId" api:"remark:父菜单ID"`
7 | PluginId int64 `json:"plugin_id" api:"remark: 插件ID,用于标记插件创建菜单,卸载时使用"`
8 | Listorder int64 `json:"orderNum" api:"remark:排序号"`
9 | Display bool `json:"isShow" api:"remark:是否显示"`
10 | Type int64 `json:"type" api:"remark:菜单类型" xorm:"comment('类型 0:目录 1:菜单 2:按钮')"`
11 | Children []Menu `json:"children" xorm:"-" form:"-"`
12 | Icon string `json:"icon" api:"remark:图标"`
13 | ViewPath string `json:"viewPath" api:"remark:视图路径"`
14 | KeepAlive bool `json:"keepAlive" api:"remark:路由缓存"`
15 | Router string `json:"router" api:"remark:路由地址"`
16 | Perms string `json:"perms" xorm:"comment('权限标识')"`
17 | Identification string `json:"identification" xorm:"comment('权限标识, 查询时唯一索引') unique"`
18 |
19 | //C string `json:"c" form:"c" api:"remark:控制器(旧版)"`
20 | //A string `json:"a" form:"a" api:"remark:操作(旧版)"`
21 | //Data string `json:"data" form:"data" api:"remark:菜单附加参数"`
22 | //IsSystem uint64 `json:"is_system" form:"is_system" api:"remark:是否为系统菜单"`
23 | }
24 |
--------------------------------------------------------------------------------
/cmd/root.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "github.com/spf13/cobra"
5 |
6 | "github.com/xiusin/pinecms/cmd/crud"
7 | "github.com/xiusin/pinecms/cmd/dede"
8 | "github.com/xiusin/pinecms/cmd/plugin"
9 | servCmd "github.com/xiusin/pinecms/cmd/server"
10 | "github.com/xiusin/pinecms/cmd/version"
11 | "github.com/xiusin/pinecms/src/config"
12 | "github.com/xiusin/pinecms/src/server"
13 | )
14 |
15 | var rootCmd = &cobra.Command{
16 | Use: "pinecms",
17 | CompletionOptions: cobra.CompletionOptions{DisableDefaultCmd: true, DisableNoDescFlag: true},
18 | Long: ` _
19 | (_)
20 | ____ _ ____ ____ ____ ____ ___
21 | | _ \| | _ \ / _ ) ___) \ /___)
22 | | | | | | | | ( (/ ( (___| | | |___ |
23 | | ||_/|_|_| |_|\____)____)_|_|_(___/
24 | |_| version: ` + version.Version,
25 |
26 | Run: func(cmd *cobra.Command, args []string) {
27 | config.InitDB()
28 | server.Server()
29 | },
30 | }
31 |
32 | func Execute() {
33 | _ = rootCmd.Execute()
34 | }
35 |
36 | func init() {
37 | rootCmd.AddCommand(initCmd)
38 | rootCmd.AddCommand(plugin.Cmd)
39 | rootCmd.AddCommand(version.Cmd)
40 | rootCmd.AddCommand(servCmd.ServeCmd)
41 | rootCmd.AddCommand(crud.Cmd)
42 | rootCmd.AddCommand(menuCmd)
43 | rootCmd.AddCommand(dede.Cmd)
44 | rootCmd.AddCommand(annotationsCmd)
45 |
46 | server.InitApp()
47 | }
48 |
--------------------------------------------------------------------------------
/src/application/controllers/backend/request_params.go:
--------------------------------------------------------------------------------
1 | package backend
2 |
3 | // loginUserParam 登录参数
4 | type loginUserParam struct {
5 | Username string `json:"username" api:"remark:登录账号|require:true"`
6 | Password string `json:"password" api:"remark:登录密码|require:true"`
7 | CaptchaId string `json:"captchaId" api:"remark:验证码ID|require:true"`
8 | CaptchaValue string `json:"verifyCode" api:"remark:验证码|require:true"`
9 | }
10 |
11 | type idParams struct {
12 | Id int64 `json:"id" api:"remark:删除单个记录"`
13 | Ids []int64 `json:"ids" api:"remark:删除多个记录"`
14 | }
15 |
16 | type listParam struct {
17 | Page int `json:"page" api:"remark:分页数|default:1|require:true"` // 分页数
18 | Size int `json:"size" api:"remark:分页条数|default:10|require:true"` // 页码
19 | OrderField string `json:"order" api:"remark:排序字段"` // 排序字段
20 | Sort string `json:"sort" api:"remark:排序方法desc=逆序,asc=正序"` // 排序规则
21 | Keywords string `json:"keyWord" api:"remark:查询关键字"` // 搜索关键字
22 | Export bool `json:"_isExport" api:"remark:是否导出"` // 是否导出
23 | Params map[string]any `json:"params" api:"remark:额外参数用于非配置字段导出"` // 额外附加参数
24 | Param map[string]any `json:"param" api:"remark:cl-filter组件参数"` // 额外附加参数
25 | }
26 |
--------------------------------------------------------------------------------
/src/application/models/tables/category.go:
--------------------------------------------------------------------------------
1 | package tables
2 |
3 | // Category 分类
4 | type Category struct {
5 | Catid int64 `xorm:"pk autoincr id" json:"id"`
6 | Parentid int64 `json:"parentId" xorm:"comment('所属栏目ID')"`
7 | Topid int64 `json:"topid" xorm:"comment('顶级栏目ID')"`
8 | ModelId int64 `json:"model_id" xorm:"comment('绑定模型ID')"`
9 | Catname string `json:"name" xorm:"comment('分类ID')"`
10 | Type int64 `json:"type"`
11 | Keywords string `json:"keywords"`
12 | Description string `json:"description"`
13 | Content string `xorm:"-"`
14 | Thumb string `json:"thumb"`
15 | Dir string `json:"dir"`
16 | Url string `json:"url"`
17 | Listorder int64 `json:"listorder"`
18 | Ismenu bool `json:"ismenu"`
19 | ListTpl string `json:"list_tpl"`
20 | DetailTpl string `json:"detail_tpl"`
21 | UrlPrefix string `xorm:"-" json:"url_prefix"`
22 | CreatedAt *LocalTime `json:"created_at" xorm:"created"`
23 | UpdatedAt *LocalTime `json:"updated_at" xorm:"updated"`
24 | Active bool `xorm:"-"`
25 | HasSon bool `xorm:"-"`
26 | Model *DocumentModel `xorm:"-" json:"model"`
27 | Page *Page `xorm:"-"`
28 | }
29 |
--------------------------------------------------------------------------------
/cmd/crud/crud_format_stub.go:
--------------------------------------------------------------------------------
1 | package crud
2 |
3 | // public/assets/js/require-table.js:403
4 | import (
5 | "bytes"
6 | "encoding/json"
7 | )
8 |
9 | func FormatEnum(field string, opts []map[string]any, item map[string]any) {
10 | item["type"] = "tpl"
11 | vmap := map[string]any{}
12 | for _, opt := range opts {
13 | vmap[opt["value"].(string)] = opt["label"]
14 | }
15 | enumsInfo, _ := json.Marshal(vmap)
16 | if len(enumsInfo) == 0 {
17 | enumsInfo = []byte("[]")
18 | }
19 | topCode = append(topCode, `let _`+field+` =`+string(enumsInfo)+`;`)
20 | item["tpl"] = "<%=formatterEnum(data." + field + ", _" + field + ")%>"
21 | }
22 |
23 | func FormatSet(field string, opts []map[string]any, item map[string]any) {
24 | item["type"] = "tpl"
25 | vmap := map[string]any{}
26 | for _, opt := range opts {
27 | vmap[opt["value"].(string)] = opt["label"]
28 | }
29 | enumsInfo, _ := json.Marshal(vmap)
30 | if len(enumsInfo) == 0 {
31 | enumsInfo = []byte("[]")
32 | }
33 | topCode = append(topCode, `let _`+field+` =`+string(enumsInfo)+`;`)
34 | item["tpl"] = "<%=formatterSet(data." + field + ", _" + field + ")%>"
35 | }
36 |
37 | // JSONMarshal 不转义字符串编码
38 | func JSONMarshal(t any) ([]byte, error) {
39 | buffer := &bytes.Buffer{}
40 | encoder := json.NewEncoder(buffer)
41 | encoder.SetEscapeHTML(false)
42 | err := encoder.Encode(t)
43 | return buffer.Bytes(), err
44 | }
45 |
--------------------------------------------------------------------------------
/src/application/models/member_model.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "github.com/xiusin/pine"
5 | "github.com/xiusin/pinecms/src/application/models/tables"
6 | "github.com/xiusin/pinecms/src/common/helper"
7 | "xorm.io/xorm"
8 | )
9 |
10 | type MemberModel struct {
11 | orm *xorm.Engine
12 | }
13 |
14 | func NewMemberModel() *MemberModel {
15 | return &MemberModel{orm: helper.GetORM()}
16 | }
17 |
18 | func (m *MemberModel) GetList(page, limit int64) (list []tables.Member, total int64) {
19 | offset := (page - 1) * limit
20 | total, _ = m.orm.Limit(int(limit), int(offset)).FindAndCount(&list)
21 | return list, total
22 | }
23 |
24 | func (m *MemberModel) GetInfo(id int64) *tables.Member {
25 | var member tables.Member
26 | m.orm.ID(id).Get(&member)
27 | return &member
28 | }
29 |
30 | func (m *MemberModel) Add(members *tables.Member) int64 {
31 | res, err := m.orm.InsertOne(members)
32 | if err != nil {
33 | pine.Logger().Error(err.Error())
34 | return 0
35 | }
36 | return res
37 | }
38 |
39 | func (m *MemberModel) Exist(account string) bool {
40 | count, _ := m.orm.Where("account = ?", account).Count(&tables.Member{})
41 | return count > 0
42 | }
43 |
44 | func (m *MemberModel) Edit(id int64, members *tables.Member) bool {
45 | res, err := m.orm.ID(id).MustCols("enabled", "integral").Update(members)
46 | if err != nil {
47 | return false
48 | }
49 | return res > 0
50 | }
51 |
--------------------------------------------------------------------------------
/src/application/models/tables/plugin.go:
--------------------------------------------------------------------------------
1 | package tables
2 |
3 | type Plugin struct {
4 | Id int64 `json:"id"`
5 | Name string `json:"name" schema:"name" xorm:"varchar(100) comment('插件名称')"`
6 | Author string `json:"author" xorm:"varchar(100) comment('作者')"`
7 | Contact string `json:"contact" xorm:"varchar(100) comment('联系方式')"`
8 | Description string `json:"description" xorm:"text comment('功能描述')"`
9 | Version string `json:"version" xorm:"varchar(100) comment('版本号')"`
10 | Sign string `json:"sign" xorm:"comment('标志') unique"`
11 | Path string `json:"path" xorm:"comment('插件本地路径') unique"`
12 | Enable bool `json:"enable" xorm:"comment('是否启用 0:否 1:是')"`
13 | Status uint `json:"status" xorm:"comment('状态 0:缺少配置 1:可用 2: 配置错误 3:未知错误')"`
14 | View []map[string]any `json:"view" xorm:"json comment('页面配置信息')"`
15 | Page string `json:"page" xorm:"-"`
16 | Config map[string]any `json:"config" xorm:"json comment('插件绑定配置')"`
17 | Prefix string `json:"prefix" xorm:"comment('插件访问前缀')"`
18 | NoInstall bool `json:"no_install" xorm:"-"`
19 | ErrMsg string `json:"errmsg" xorm:"-"`
20 | CreatedAt LocalTime `json:"created_at"`
21 | UpdatedAt LocalTime `json:"updated_at"`
22 | }
23 |
--------------------------------------------------------------------------------
/src/common/storage/storage_ftp_test.go:
--------------------------------------------------------------------------------
1 | package storage
2 |
3 | import (
4 | "io"
5 | "io/ioutil"
6 | "os"
7 | "testing"
8 | )
9 |
10 | func TestFtpUploader(t *testing.T) {
11 | uploader := NewFtpUploader(map[string]string{
12 | "FTP_SERVER_URL": "124.222.103.232",
13 | "FTP_SERVER_PORT": "21",
14 | "FTP_USER_NAME": "test",
15 | "FTP_USER_PWD": "",
16 | "SITE_URL": "http://localhost:2019/xxx/",
17 | "FTP_URL_PREFIX": "", // 如果配置则使用此配置拼接地址, 否则使用系统接口
18 | })
19 |
20 | t.Log(uploader.Mkdir("goftp"))
21 | t.Log(uploader.Mkdir("goftp2/subftpdir"))
22 |
23 | tmp, _ := ioutil.TempFile("", "")
24 | t.Log(io.WriteString(tmp, "hello world"))
25 |
26 | tmp.Close()
27 | tmp, _ = os.Open(tmp.Name())
28 | defer func() {
29 | tmp.Close()
30 | os.Remove(tmp.Name())
31 | }()
32 |
33 | t.Log("delete", uploader.Remove("goftp/index.html"))
34 | t.Log(uploader.Upload("goftp/index.html", tmp))
35 | byts, err := uploader.Content("goftp/index.html")
36 | if err != nil {
37 | t.Log(err)
38 | }
39 | t.Log("byts", string(byts))
40 |
41 | t.Log("exist: ")
42 | t.Log(uploader.Exists("goftp/index.html"))
43 |
44 | t.Log("rename", uploader.Rename("goftp/index.html", "goftp2/index_1.html"))
45 |
46 | t.Log("exist: ")
47 | t.Log(uploader.Exists("goftp/index.html"))
48 |
49 | t.Log("rmdir", uploader.Rmdir("goftp2"))
50 |
51 | t.Log(uploader.List("113123123/111111/22222"))
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/resources/themes/default/page.jet:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{.Field.Title}}_{{global["site_name"]}}
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
26 |
27 |
28 |
29 | {{include "head2.jet"}}
30 |
31 | {{.Field.Content | unsafe}}
32 |
33 |
34 |
35 |
36 |
37 |
38 | {{include "footer.jet"}}
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/application/controllers/taglibs/pagelist.go:
--------------------------------------------------------------------------------
1 | package taglibs
2 |
3 | import (
4 | "fmt"
5 | "github.com/xiusin/pine"
6 | "github.com/xiusin/pinecms/src/application/models"
7 | "github.com/xiusin/pinecms/src/common/helper"
8 | "github.com/xiusin/pinecms/src/config"
9 | "math"
10 | "reflect"
11 | "strconv"
12 |
13 | "github.com/CloudyKit/jet"
14 | )
15 |
16 | // PageList {{pagelist(row, .TypeID, .ArtCount, .PageNum, .QP) | unsafe}}
17 | func PageList(args jet.Arguments) reflect.Value {
18 | defer func() {
19 | if err := recover(); err != nil {
20 | pine.Logger().Error(fmt.Sprintf("pagelist Failed %s", err))
21 | }
22 | }()
23 | limit := int(getNumber(args.Get(0)))
24 | tid := getNumber(args.Get(1))
25 | total := int(getNumber(args.Get(2)))
26 | page := int(getNumber(args.Get(3)))
27 | qp, _ := args.Get(4).Interface().(map[string][]string)
28 | conf, _ := config.SiteConfig()
29 | if limit == 0 {
30 | limit, _ = strconv.Atoi(conf["SITE_PAGE_SIZE"])
31 | if limit == 0 {
32 | limit = 15
33 | }
34 | }
35 | totalPage := int(math.Ceil(float64(total) / float64(limit)))
36 | if page > totalPage {
37 | page = totalPage
38 | }
39 | if len(qp) == 0 {
40 | return reflect.ValueOf(helper.NewPage("/"+models.NewCategoryModel().GetUrlPrefix(tid), page, limit, total, nil, false).String())
41 | } else {
42 | // todo 地址后期path
43 | return reflect.ValueOf(helper.NewPage("/search.go", page, limit, total, qp, true).String())
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/cmd/example.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "os/exec"
7 | "strings"
8 |
9 | "github.com/spf13/cobra"
10 | "github.com/xiusin/pine"
11 | )
12 |
13 | var exampleCmd = &cobra.Command{
14 | Use: "example",
15 | Short: "获取案例demo数据库以及资源",
16 | Run: func(_ *cobra.Command, _ []string) {
17 | var input string
18 | fmt.Print("下载demo会覆盖本地数据资源以及清理本地缓存,确定要执行吗?[Y/n]")
19 | _, _ = fmt.Scanln(&input)
20 | if strings.ToLower(input) == "n" || input == "" {
21 | return
22 | }
23 | _ = os.RemoveAll("pinecms_demo")
24 | gitCmd := exec.Command("git", "clone", "https://github.com/xiusin/pinecms_demo.git")
25 | gitCmd.Stdout = os.Stdout
26 | gitCmd.Stdin = os.Stdin
27 | if err := gitCmd.Run(); err != nil {
28 | pine.Logger().Error(err.Error())
29 | return
30 | }
31 | _ = os.Rename("pinecms_demo/data.db.demo", "data.db.demo")
32 | _ = os.Rename("pinecms_demo/resources/themes/example", "resources/themes/example")
33 | _ = os.Rename("pinecms_demo/resources/assets/example", "resources/assets/example")
34 | _ = os.Rename("pinecms_demo/resources/uploads", "resources/uploads")
35 | _ = os.RemoveAll("pinecms_demo")
36 | fmt.Println(`1. 请修改配置文件database.yml的数据源为: data.db.demo`)
37 | fmt.Println(`2. 请配置application.yml主题为: example`)
38 | err := os.RemoveAll("runtime/cache.db")
39 | if err != nil {
40 | pine.Logger().Warn("删除缓存文件失败, 请手动删除缓存文件")
41 | return
42 | }
43 | },
44 | }
45 |
46 | func init() {
47 | rootCmd.AddCommand(exampleCmd)
48 | }
49 |
--------------------------------------------------------------------------------
/cmd/plugin/build_plugin.go:
--------------------------------------------------------------------------------
1 | package plugin
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "os/exec"
7 | "path/filepath"
8 |
9 | "github.com/xiusin/pinecms/src/common/helper"
10 |
11 | "github.com/spf13/cobra"
12 | )
13 |
14 | var buildPluginCmd = &cobra.Command{
15 | Use: "build",
16 | Short: "构建插件",
17 | Run: func(cmd *cobra.Command, args []string) {
18 | name, _ := cmd.Flags().GetString("name")
19 | if len(name) == 0 {
20 | _ = cmd.Usage()
21 | return
22 | }
23 | buildPluginDir := filepath.Join(outputPluginDir, name)
24 | _ = os.MkdirAll(buildPluginDir, os.ModePerm)
25 |
26 | scriptName := filepath.Join(sourcePluginDir, name, name+".go")
27 |
28 | _, err := os.Stat(scriptName)
29 | helper.PanicErr(err)
30 |
31 | if conf, err := os.ReadFile(filepath.Join(sourcePluginDir, name, configName)); err != nil {
32 | helper.PanicErr(err)
33 | } else {
34 | helper.PanicErr(os.WriteFile(filepath.Join(buildPluginDir, configName), conf, os.ModePerm))
35 | }
36 |
37 | outPluginName := filepath.Join(buildPluginDir, name+".so")
38 | buildCmd := exec.Command("go", "build", "-buildmode=plugin", "-o", outPluginName, scriptName)
39 | buildCmd.Stdout = os.Stdout
40 | buildCmd.Stderr = os.Stdout
41 | buildCmd.Env = os.Environ()
42 | buildCmd.Dir = helper.AppPath()
43 |
44 | helper.PanicErr(buildCmd.Run())
45 |
46 | fmt.Println("构建插件", outPluginName, "成功")
47 | },
48 | }
49 |
50 | func init() {
51 | buildPluginCmd.Flags().String("name", "", "传入需要构建的插件文件名称")
52 | }
53 |
--------------------------------------------------------------------------------
/resources/themes/default/error.jet:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
{{global["site_name"]}}
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
26 |
27 |
28 |
29 | {{include "head2.jet"}}
30 |
31 |
32 | 您访问的页面不存在或功能未开发
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | {{include "footer.jet"}}
41 |
42 |
43 |
--------------------------------------------------------------------------------
/resources/themes/default/right.jet:
--------------------------------------------------------------------------------
1 | {{ import "tags.jet" }}
2 |
3 |
4 |
5 |
6 | {{yield myad(name="right1")}}
7 |
8 |
9 | 本类最热新闻
10 | {{yield artlist(row=2, orderby="visit_count") content}}
11 |
{{field["title"]}}
12 | {{end}}
13 | {{yield artlist(offset=2, row=10, orderby="visit_count") content}}
14 | - • {{field["title"]}}
15 | {{end}}
16 |
17 |
18 |
19 |
20 | {{yield myad(name="right2")}}
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/application/models/ad_model.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "github.com/xiusin/pine"
5 | "github.com/xiusin/pinecms/src/application/models/tables"
6 | "github.com/xiusin/pinecms/src/common/helper"
7 | "xorm.io/xorm"
8 | )
9 |
10 | type AdModel struct {
11 | orm *xorm.Engine
12 | }
13 |
14 | func NewAdModel() *AdModel {
15 | return &AdModel{orm: helper.GetORM()}
16 | }
17 |
18 | func (l *AdModel) GetList(page, limit int) ([]tables.Advert, int64) {
19 | offset := (page - 1) * limit
20 | var list = []tables.Advert{}
21 | var total int64
22 | var err error
23 | if total, err = l.orm.Desc("listorder").Limit(limit, offset).FindAndCount(&list); err != nil {
24 | pine.Logger().Error(err.Error())
25 | }
26 | return list, total
27 | }
28 |
29 | func (l *AdModel) Add(data *tables.Advert) int64 {
30 | id, err := l.orm.InsertOne(data)
31 | if err != nil {
32 | pine.Logger().Error(err.Error())
33 | }
34 | return id
35 | }
36 |
37 | func (l *AdModel) Delete(id []int64) bool {
38 | res, err := l.orm.ID(id).Delete(&tables.Advert{})
39 | if err != nil {
40 | pine.Logger().Error(err.Error())
41 | }
42 | return res > 0
43 | }
44 |
45 | func (l *AdModel) Get(id int64) *tables.Advert {
46 | var link = &tables.Advert{}
47 | ok, _ := l.orm.ID(id).Get(link)
48 | if !ok {
49 | return nil
50 | }
51 | return link
52 | }
53 |
54 | func (l *AdModel) Update(data *tables.Advert) bool {
55 | id, err := l.orm.ID(data.Id).Update(data)
56 | if err != nil {
57 | pine.Logger().Error(err.Error())
58 | }
59 |
60 | return id > 0
61 | }
62 |
--------------------------------------------------------------------------------
/cmd/dede/import.go:
--------------------------------------------------------------------------------
1 | package dede
2 |
3 | import (
4 | "github.com/spf13/cobra"
5 | )
6 |
7 | var Cmd = &cobra.Command{
8 | Use: "dede",
9 | Short: "导入织梦CMS数据",
10 | Long: `从织梦CMS源中匹配已知数据`,
11 | TraverseChildren: true,
12 | }
13 | var SqlFieldTypeMap = map[string]string{
14 | "varchar": "varchar(100)",
15 | "int": "int(10)",
16 | }
17 | var SqlLite3FieldTypeMap = map[string]string{
18 | "varchar": "TEXT",
19 | "int": "INTEGER",
20 | }
21 | var ExtraFields = []map[string]string{
22 | {
23 | "COLUMN_NAME": "catid",
24 | "EXTRA": "",
25 | "COLUMN_TYPE": "int",
26 | "IS_NULLABLE": "NO",
27 | "COLUMN_COMMENT": "所属栏目ID",
28 | "COLUMN_DEFAULT": "0",
29 | },
30 | {
31 | "COLUMN_NAME": "mid",
32 | "EXTRA": "",
33 | "COLUMN_TYPE": "int",
34 | "IS_NULLABLE": "NO",
35 | "COLUMN_COMMENT": "模型ID",
36 | "COLUMN_DEFAULT": "0",
37 | },
38 | {
39 | "COLUMN_NAME": "created_time",
40 | "EXTRA": "",
41 | "COLUMN_TYPE": "datetime",
42 | "IS_NULLABLE": "YES",
43 | "COLUMN_COMMENT": "",
44 | "COLUMN_DEFAULT": "",
45 | },
46 | {
47 | "COLUMN_NAME": "updated_time",
48 | "EXTRA": "",
49 | "COLUMN_TYPE": "datetime",
50 | "IS_NULLABLE": "YES",
51 | "COLUMN_COMMENT": "",
52 | "COLUMN_DEFAULT": "",
53 | },
54 | {
55 | "COLUMN_NAME": "deleted_time",
56 | "EXTRA": "",
57 | "COLUMN_TYPE": "datetime",
58 | "IS_NULLABLE": "YES",
59 | "COLUMN_COMMENT": "",
60 | "COLUMN_DEFAULT": "",
61 | },
62 | }
63 |
--------------------------------------------------------------------------------
/src/application/models/tables/todo.go:
--------------------------------------------------------------------------------
1 | package tables
2 |
3 | type Todo struct {
4 | Id int64 `xorm:"int(11) autoincr not null pk 'id'" json:"id"`
5 | Type string `xorm:"varchar(30) not null default '1' 'type' comment('字符串多选:1=外部链接,2=内部链接,3=通用链接')" json:"type" validate:"required"`
6 | Name string `xorm:"varchar(50) not null 'name' comment('普通输入框')" json:"name" validate:"required"`
7 | Introduce string `xorm:"varchar(255) not null 'introduce' comment('普通多行输入框::cms-textarea')" json:"introduce" validate:"required"`
8 | Listorder int64 `xorm:"int(11) not null 'listorder' comment('不可为空数字')" json:"listorder" validate:"required"`
9 | Status int `xorm:"tinyint(1) not null default 0 'status' comment('tinyint单选:0=待审核,1=通过,2=拒绝:cms-radio')" json:"status" validate:"required"`
10 | PutDate LocalTime `xorm:"date default null 'put_date' comment('日期')" json:"put_date" validate:"required"`
11 | PutDatetime LocalTime `xorm:"datetime default null 'put_datetime' comment('时间日期')" json:"put_datetime" validate:"required"`
12 | StartTime LocalTime `xorm:"datetime default null 'start_time' comment('开始时间$end=end_time')" json:"start_time" validate:"required"`
13 | EndTime LocalTime `xorm:"datetime default null 'end_time' comment('结束时间被引用隐藏到代码区间选择器')" json:"end_time" validate:"required"`
14 | Logo string `xorm:"varchar(30) default null 'logo' comment('单图上传')" json:"logo" validate:"required"`
15 | Logos string `xorm:"varchar(255) default null 'logos' comment('多图上传')" json:"logos" validate:"required"`
16 | }
17 |
--------------------------------------------------------------------------------
/src/application/controllers/backend/wechat/rule_controller.go:
--------------------------------------------------------------------------------
1 | package wechat
2 |
3 | import (
4 | "errors"
5 |
6 | "github.com/xiusin/pinecms/src/application/controllers/backend"
7 | "github.com/xiusin/pinecms/src/application/controllers/backend/wechat/dto"
8 | "github.com/xiusin/pinecms/src/application/models/tables"
9 | "github.com/xiusin/pinecms/src/common/helper"
10 | "xorm.io/xorm"
11 | )
12 |
13 | type WechatRuleController struct {
14 | backend.BaseController
15 | }
16 |
17 | func (c *WechatRuleController) Construct() {
18 | c.Table = &tables.WechatMsgReplyRule{}
19 | c.Entries = &[]tables.WechatMsgReplyRule{}
20 | c.SearchFields = []backend.SearchFieldDsl{
21 | {Field: "appid"},
22 | }
23 | c.BaseController.Construct()
24 | c.OpBefore = c.before
25 | }
26 |
27 | func (c WechatRuleController) before(act int, params any) error {
28 | if act == backend.OpEdit || act == backend.OpAdd {
29 | //sess := params.(*xorm.Session).Clone()
30 | sess := params.(*xorm.Session)
31 | data := c.Table.(*tables.WechatMsgReplyRule)
32 | sess.Where("Match_Value = ?", data.MatchValue).Where("appid = ?", data.AppId)
33 | if act == backend.OpEdit {
34 | sess.Where("id = ?", data.Id)
35 | }
36 | if exist, _ := sess.Exist(&tables.WechatMsgReplyRule{}); exist {
37 | return errors.New("规则匹配值已经存在")
38 | }
39 | } else if act == backend.OpList {
40 | var search dto.RuleSearch
41 | helper.PanicErr(c.BindParse(&search))
42 | sess := params.(*xorm.Session)
43 | if search.Param.Appid != "" {
44 | sess.Where("appid = ?", search.Param.Appid)
45 | }
46 | }
47 | return nil
48 | }
49 |
--------------------------------------------------------------------------------
/src/application/controllers/backend/wechat/menu_controller.go:
--------------------------------------------------------------------------------
1 | package wechat
2 |
3 | import (
4 | "fmt"
5 | "github.com/silenceper/wechat/v2/officialaccount/menu"
6 | "github.com/xiusin/pinecms/src/application/controllers/backend"
7 | "github.com/xiusin/pinecms/src/common/helper"
8 | )
9 |
10 | type WechatMenuController struct {
11 | backend.BaseController
12 | key string
13 | p menuParam
14 | }
15 |
16 | func (c *WechatMenuController) Construct() {
17 | c.BaseController.Construct()
18 | c.p = menuParam{}
19 | }
20 |
21 | func (c *WechatMenuController) PostEdit() {
22 | c.Ctx().BindJSON(&c.p)
23 | if len(c.p.Appid) == 0 || len(c.p.Menu.Button) == 0 {
24 | helper.Ajax("发布参数错误", 1, c.Ctx())
25 | return
26 | }
27 | account, _ := GetOfficialAccount(c.p.Appid)
28 | if err := account.GetMenu().SetMenu(c.p.Menu.Button); err != nil {
29 | helper.Ajax("设置菜单失败: "+err.Error(), 1, c.Ctx())
30 | } else {
31 | helper.Ajax("发布菜单成功", 0, c.Ctx())
32 | }
33 | }
34 |
35 | func (c *WechatMenuController) GetInfo() {
36 | appid, _ := c.Ctx().Input().GetString("appid")
37 | if len(appid) == 0 {
38 | helper.Ajax("请选择公众号", 1, c.Ctx())
39 | return
40 | }
41 | c.key = fmt.Sprintf(CacheKeyWechatMenu, appid)
42 | account, _ := GetOfficialAccount(appid)
43 | retMenu, err := account.GetMenu().GetMenu()
44 | if err != nil {
45 | helper.Ajax(err, 1, c.Ctx())
46 | return
47 | }
48 | for i, btn := range retMenu.Menu.Button {
49 | if len(btn.SubButtons) == 0 {
50 | btn.SubButtons = []*menu.Button{} // 初始化子菜单结构
51 | }
52 | retMenu.Menu.Button[i] = btn
53 | }
54 | helper.Ajax(retMenu.Menu, 0, c.Ctx())
55 | }
56 |
--------------------------------------------------------------------------------
/src/application/controllers/taglibs/position.go:
--------------------------------------------------------------------------------
1 | package taglibs
2 |
3 | import (
4 | "fmt"
5 | "github.com/CloudyKit/jet"
6 | "github.com/xiusin/pine/contracts"
7 | "github.com/xiusin/pine/di"
8 | "github.com/xiusin/pinecms/src/application/controllers"
9 | "github.com/xiusin/pinecms/src/application/models"
10 | "github.com/xiusin/pinecms/src/application/models/tables"
11 | "reflect"
12 | "strings"
13 | )
14 |
15 | /**
16 | * 标签:{{yield position() }}
17 | * 作用:获取当前页面面包屑只可适用于列表页和详情页
18 | */
19 | func Position(args jet.Arguments) reflect.Value {
20 | if !checkArgType(&args) {
21 | return defaultArrReturnVal
22 | }
23 | typeid := getNumber(args.Get(0))
24 | return reflect.ValueOf(getCategoryPos(typeid))
25 | }
26 |
27 | func getCategoryPos(tid int64) string {
28 | var position []string
29 | var res string
30 | var data = struct {
31 | Arr []tables.Category
32 | Pos string
33 | }{}
34 | key := fmt.Sprintf(controllers.CacheCategoryPosPrefix, tid)
35 | icache := di.MustGet(controllers.ServiceICache).(contracts.Cache)
36 | err := icache.GetWithUnmarshal(key, &data)
37 | if err != nil {
38 | m := models.NewCategoryModel()
39 | data.Arr = m.GetPosArr(tid)
40 | for _, cat := range data.Arr {
41 | if cat.Type != 2 {
42 | position = append(position, ""+cat.Catname+"")
43 | } else {
44 | position = append(position, ""+cat.Catname+"")
45 | }
46 | }
47 | if len(data.Arr) > 0 {
48 | res = strings.Join(position, " > ")
49 | data.Pos = res
50 | _ = icache.SetWithMarshal(key, res)
51 | }
52 | }
53 | return data.Pos
54 | }
55 |
--------------------------------------------------------------------------------
/src/application/models/link_model.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "github.com/xiusin/pine"
5 | "github.com/xiusin/pinecms/src/application/models/tables"
6 | "github.com/xiusin/pinecms/src/common/helper"
7 | "xorm.io/xorm"
8 | )
9 |
10 | type LinkModel struct {
11 | orm *xorm.Engine
12 | }
13 |
14 | func NewLinkModel() *LinkModel {
15 | return &LinkModel{orm: helper.GetORM()}
16 | }
17 |
18 | func (l *LinkModel) GetList(page, limit int64) ([]tables.Link, int64) {
19 | offset := (page - 1) * limit
20 | var list = []tables.Link{}
21 | var total int64
22 | var err error
23 | if total, err = l.orm.Desc("listorder").Limit(int(limit), int(offset)).FindAndCount(&list); err != nil {
24 | pine.Logger().Error(helper.GetCallerFuncName(), err)
25 | }
26 | return list, total
27 | }
28 |
29 | func (l *LinkModel) Add(data *tables.Link) int64 {
30 | id, err := l.orm.InsertOne(data)
31 | if err != nil {
32 | pine.Logger().Error(helper.GetCallerFuncName(), err)
33 | }
34 | return id
35 | }
36 |
37 | func (l *LinkModel) Delete(id int64) bool {
38 | res, err := l.orm.ID(id).Delete(&tables.Link{})
39 | if err != nil {
40 | pine.Logger().Error(helper.GetCallerFuncName(), err)
41 | }
42 | return res > 0
43 | }
44 |
45 | func (l *LinkModel) Get(id int64) *tables.Link {
46 | var link = &tables.Link{}
47 | ok, _ := l.orm.ID(id).Get(link)
48 | if !ok {
49 | return nil
50 | }
51 | return link
52 | }
53 |
54 | func (l *LinkModel) Update(data *tables.Link) bool {
55 | id, err := l.orm.ID(data.Id).Update(data)
56 | if err != nil {
57 | pine.Logger().Error(helper.GetCallerFuncName(), err)
58 | }
59 | return id > 0
60 | }
61 |
--------------------------------------------------------------------------------
/src/application/controllers/backend/wechat/common.go:
--------------------------------------------------------------------------------
1 | package wechat
2 |
3 | import (
4 | "errors"
5 | "github.com/xiusin/pine/contracts"
6 |
7 | "github.com/silenceper/wechat/v2"
8 | "github.com/silenceper/wechat/v2/officialaccount"
9 | offConfig "github.com/silenceper/wechat/v2/officialaccount/config"
10 | "github.com/xiusin/pine/di"
11 | "github.com/xiusin/pinecms/src/application/controllers"
12 | "github.com/xiusin/pinecms/src/application/models/tables"
13 | "github.com/xiusin/pinecms/src/common/helper"
14 | )
15 |
16 | func GetOfficialAccount(appid string) (*officialaccount.OfficialAccount, *tables.WechatAccount) {
17 | accountData := &tables.WechatAccount{}
18 | orm := helper.GetORM()
19 | orm.Where("app_id = ?", appid).Get(accountData)
20 | if accountData.Id == 0 {
21 | panic(errors.New("公众号" + appid + "不存在"))
22 | }
23 | wc, memory := wechat.NewWechat(), &WechatTokenCacher{Cache: di.MustGet(controllers.ServiceICache).(contracts.Cache)}
24 | cfg := &offConfig.Config{
25 | AppID: accountData.AppId,
26 | AppSecret: accountData.Secret,
27 | Token: accountData.Token,
28 | EncodingAESKey: accountData.AesKey,
29 | Cache: memory,
30 | }
31 | account := wc.GetOfficialAccount(cfg)
32 | return account, accountData
33 | }
34 |
35 | func SaveCacheMaterialListKey(key string, cacher contracts.Cache) {
36 | var keys []string
37 | cacher.GetWithUnmarshal(CacheKeyWechatMaterialListKeys, &keys)
38 | for _, cacheKey := range keys {
39 | if cacheKey == key {
40 | return
41 | }
42 | }
43 | keys = append(keys, key)
44 | cacher.SetWithMarshal(CacheKeyWechatMaterialListKeys, &keys, CacheTimeSecs)
45 | }
46 |
--------------------------------------------------------------------------------
/src/application/controllers/backend/helper.go:
--------------------------------------------------------------------------------
1 | package backend
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "reflect"
7 |
8 | "github.com/xiusin/pine"
9 | "github.com/xiusin/pine/di"
10 | "github.com/xiusin/pinecms/src/application/controllers"
11 | "github.com/xiusin/pinecms/src/common/storage"
12 | )
13 |
14 | func getStorageEngine(settingData map[string]string) storage.Uploader {
15 | engine := settingData["UPLOAD_ENGINE"]
16 | var uploadEngine storage.Uploader
17 | uploader, err := di.Get(fmt.Sprintf(controllers.ServiceUploaderEngine, engine))
18 | if err != nil {
19 | pine.Logger().Warn("缺少存储驱动, 自动转换为本地存储", err)
20 | uploadEngine = storage.NewFileUploader(settingData)
21 | } else {
22 | uploadEngine = uploader.(storage.Uploader)
23 | }
24 | return uploadEngine
25 | }
26 |
27 | func parseParam(ctx *pine.Context, param any) error {
28 | if ctx.Input().IsJson() && len(ctx.RequestCtx.PostBody()) > 0 {
29 | return ctx.BindJSON(param)
30 | }
31 | return nil
32 | }
33 |
34 | func ArrayCol(arr any, col string) []any {
35 | val := reflect.ValueOf(arr)
36 | if val.Kind() != reflect.Slice {
37 | panic(errors.New("ArrayCol第一个参数必须为切片类型"))
38 | }
39 | var cols []any
40 | for i := range val.Len() {
41 | cols = append(cols, val.Index(i).FieldByName(col).Interface())
42 | }
43 | return cols
44 | }
45 |
46 | func ArrayColMap(arr any, col string) map[any]any {
47 | var maps = map[any]any{}
48 | val := reflect.ValueOf(arr)
49 | if val.Kind() != reflect.Slice {
50 | panic(errors.New("ArrayCol第一个参数必须为切片类型"))
51 | }
52 | for i := range val.Len() {
53 | maps[val.Index(i).FieldByName(col).Interface()] = val.Index(i).Interface()
54 | }
55 | return maps
56 | }
57 |
--------------------------------------------------------------------------------
/src/application/controllers/taglibs/adlist.go:
--------------------------------------------------------------------------------
1 | package taglibs
2 |
3 | import (
4 | "reflect"
5 | "strconv"
6 | "strings"
7 |
8 | "github.com/CloudyKit/jet"
9 | "github.com/xiusin/pinecms/src/application/models/tables"
10 | "github.com/xiusin/pinecms/src/common/helper"
11 | )
12 |
13 | /**
14 | 广告标签
15 | {{yield adlist(id=3, pos="首页banner图")}}
16 | 返回一组广告
17 | */
18 | func AdList(args jet.Arguments) reflect.Value {
19 | if !checkArgType(&args) {
20 | return defaultArrReturnVal
21 | }
22 | id := args.Get(0)
23 | var ids []string
24 | if isNumber(id) {
25 | ids = append(ids, strconv.Itoa(int(getNumber(id))))
26 | } else if id.String() != "" {
27 | ids = strings.Split(id.String(), ",")
28 | }
29 | order := args.Get(2).String()
30 | if order == "" {
31 | order = "listorder desc"
32 | }
33 | orm := helper.GetORM().OrderBy(order)
34 | // 获取广告位信息
35 | var pos = args.Get(1)
36 | switch pos.Type().String() {
37 | case "string":
38 | if len(pos.String()) > 0 {
39 | advPos := &tables.AdvertSpace{}
40 | exists, _ := helper.GetORM().Table(advPos).Where("name = ?", pos.String()).Get(advPos)
41 | if !exists {
42 | return reflect.ValueOf([]tables.Advert{})
43 | }
44 | orm.Where("space_id = ?", advPos.Id)
45 | }
46 | default:
47 | if pid := getNumber(pos); pid > 0 {
48 | orm.Where("space_id = ?", pid)
49 | }
50 | }
51 | if len(ids) > 0 {
52 | orm.In("id", ids)
53 | }
54 |
55 | now := helper.NowDate(helper.TimeFormat)
56 | orm.Where("status = 1").Where("start_time <= ?", now).Where("end_time >= ?", now).Select("id, name, image, link_url")
57 | var advs = []tables.Advert{}
58 | orm.Find(&advs)
59 | return reflect.ValueOf(advs)
60 | }
61 |
--------------------------------------------------------------------------------
/src/application/controllers/backend/department_controller.go:
--------------------------------------------------------------------------------
1 | package backend
2 |
3 | import (
4 | "errors"
5 |
6 | "github.com/xiusin/pinecms/src/application/models/tables"
7 | "github.com/xiusin/pinecms/src/common/helper"
8 | )
9 |
10 | type DepartmentController struct {
11 | BaseController
12 | }
13 |
14 | func (c *DepartmentController) Construct() {
15 | c.Table = &tables.Department{}
16 | c.Entries = &[]tables.Department{}
17 | c.Group = "系统管理"
18 | c.SubGroup = "部门管理"
19 | c.ApiEntityName = "部门"
20 | c.BaseController.Construct()
21 | c.OpBefore = c.before
22 | }
23 |
24 | func (c *DepartmentController) before(act int, params any) error {
25 | if act == OpEdit || act == OpAdd {
26 | p := params.(*tables.Department)
27 | sess := c.Orm.NewSession()
28 | if act == OpEdit {
29 | if p.Id < 1 {
30 | return errors.New("ID不能为空")
31 | }
32 | if uint(p.Id) == p.ParentId {
33 | return errors.New("父级部门不能为自己")
34 | } else {
35 | ids := []any{p.Id}
36 | for len(ids) > 0 {
37 | var subs []tables.Department
38 | _ = c.Orm.In("parent_id", ids).Cols("id").Find(&subs)
39 | ids = ArrayCol(subs, "Id")
40 | for _, pid := range ids {
41 | if uint(pid.(int64)) == p.ParentId {
42 | return errors.New("不可设置自己下级作为父级部门")
43 | }
44 | }
45 | }
46 | }
47 | sess.Where("id <> ? and name = ?", p.Id, p.Name)
48 | } else {
49 | sess.Where("name = ?", p.Name)
50 | }
51 | if exist, _ := sess.Exist(&tables.Department{}); exist {
52 | return errors.New("部门已存在")
53 | }
54 | }
55 |
56 | return nil
57 | }
58 |
59 | func (c *DepartmentController) GetSelect() {
60 | c.Orm.Find(c.Entries)
61 | helper.Ajax(c.Entries, 0, c.Ctx())
62 | }
63 |
--------------------------------------------------------------------------------
/src/application/controllers/tplfun/func.go:
--------------------------------------------------------------------------------
1 | package tplfun
2 |
3 | import (
4 | "reflect"
5 | "strings"
6 | "time"
7 | "unicode/utf8"
8 |
9 | "github.com/CloudyKit/jet"
10 | "github.com/xiusin/pine"
11 | )
12 |
13 | func format(str string) string {
14 | str = strings.Replace(str, "Y", "2006", 1)
15 | str = strings.Replace(str, "m", "01", 1)
16 | str = strings.Replace(str, "d", "02", 1)
17 | str = strings.Replace(str, "H", "13", 1)
18 | str = strings.Replace(str, "i", "04", 1)
19 | str = strings.Replace(str, "s", "05", 1)
20 | return str
21 | }
22 |
23 | func FormatTime(args jet.Arguments) reflect.Value {
24 | arg := strings.ReplaceAll("T", " ", args.Get(0).String())
25 | arg = strings.ReplaceAll("Z", "", arg)
26 | t, err := time.Parse(time.DateTime, arg)
27 | if err != nil {
28 | return reflect.ValueOf("")
29 | }
30 | format := time.DateTime
31 | if args.NumOfArguments() > 1 {
32 | format = args.Get(1).String()
33 | }
34 | return reflect.ValueOf(t.Format(format))
35 | }
36 |
37 | func MyDate(args jet.Arguments) reflect.Value {
38 | t, err := time.Parse("2006-01-02T15:04:05Z", args.Get(1).String())
39 | if err != nil {
40 | pine.Logger().Error("解析时间错误", err)
41 | return reflect.ValueOf("")
42 | }
43 | format := format(args.Get(0).String())
44 | return reflect.ValueOf(t.Format(format))
45 | }
46 |
47 | func GetDateTimeMK(args jet.Arguments) reflect.Value {
48 | return FormatTime(args)
49 | }
50 |
51 | func CnSubstr(args jet.Arguments) reflect.Value {
52 | me := args.Get(0).String()
53 | length := int(args.Get(1).Float())
54 |
55 | if utf8.RuneCountInString(me) > length {
56 | titleRune := []rune(me)
57 | me = string(titleRune[:length])
58 | }
59 | return reflect.ValueOf(me)
60 | }
61 |
--------------------------------------------------------------------------------
/src/application/controllers/middleware/verify_token.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "fmt"
5 | "github.com/gbrlsnchs/jwt/v3"
6 | "github.com/xiusin/pine"
7 | "github.com/xiusin/pine/di"
8 | "github.com/xiusin/pinecms/src/application/controllers"
9 | "github.com/xiusin/pinecms/src/application/models/tables"
10 | "github.com/xiusin/pinecms/src/config"
11 | "strings"
12 | "xorm.io/xorm"
13 | )
14 |
15 | // VerifyJwtToken 验证jwt
16 | // excludePrefix 需要排除认证的路由前缀
17 | func VerifyJwtToken() pine.Handler {
18 | return func(ctx *pine.Context) {
19 | uri := ctx.Path()
20 | if !strings.Contains(uri, "login") &&
21 | !strings.Contains(uri, "captcha") &&
22 | !strings.Contains(uri, "thumb") {
23 | token := ctx.Header("Authorization")
24 | if token == "" {
25 | token, _ = ctx.Input().GetString("token")
26 | }
27 | var hs = jwt.NewHS256([]byte(config.App().JwtKey))
28 | // 验证token
29 | var pl controllers.LoginAdminPayload
30 | _, err := jwt.Verify([]byte(token), hs, &pl)
31 | if err != nil {
32 | _ = ctx.Render().JSON(pine.H{"code": 1, "msg": "授权失败, 请重新登录"})
33 | return
34 | }
35 |
36 | ctx.SetUserValue("adminid", pl.AdminId)
37 | ctx.SetUserValue("roleid", pl.RoleID)
38 |
39 | if strings.Contains(uri, "user/info") {
40 | ctx.QueryArgs().Set("id", fmt.Sprintf("%d", pl.AdminId))
41 | }
42 | if !strings.Contains(uri, "/log/list") {
43 | di.MustGet(&xorm.Engine{}).(*xorm.Engine).Insert(&tables.RequestLog{
44 | Uri: string(ctx.RequestURI()),
45 | Userid: pl.AdminId,
46 | Params: string(ctx.PostBody()),
47 | Username: pl.AdminName,
48 | Ip: ctx.ClientIP(),
49 | Method: string(ctx.Method()),
50 | })
51 | }
52 | }
53 | ctx.Next()
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/common/search/zincsearch.go:
--------------------------------------------------------------------------------
1 | package search
2 |
3 | import (
4 | "context"
5 | "errors"
6 |
7 | "github.com/xiusin/pinecms/src/config"
8 | client "github.com/zinclabs/sdk-go-zincsearch"
9 | )
10 |
11 | type PineZincSearch struct {
12 | client *client.APIClient
13 | ctx context.Context
14 | }
15 |
16 | func (p *PineZincSearch) Search(index string, _query any) (any, error) {
17 | var query client.MetaZincQuery
18 | var ok bool
19 | if query, ok = _query.(client.MetaZincQuery); !ok {
20 | return nil, errors.New("invalid query type: expect client.MetaQuery")
21 | }
22 |
23 | resp, _, err := p.client.Search.Search(p.ctx, index).Query(query).Execute()
24 | return resp, err
25 | }
26 |
27 | func (p *PineZincSearch) Update(index, id string, doc map[string]any) error {
28 | _, _, err := p.client.Document.Update(p.ctx, index, id).Document(doc).Execute()
29 | return err
30 | }
31 |
32 | func (p *PineZincSearch) Delete(index, id string) error {
33 | _, _, err := p.client.Document.Delete(p.ctx, index, id).Execute()
34 | return err
35 | }
36 |
37 | func (p *PineZincSearch) Index(index string, doc map[string]any) (string, error) {
38 | resp, _, err := p.client.Document.Index(p.ctx, index).Document(doc).Execute()
39 | if err != nil {
40 | return "", err
41 | }
42 | return resp.GetId(), nil
43 | }
44 |
45 | func NewZincSearch() ISearch {
46 | cfg := config.DB()
47 | ctx := context.WithValue(context.Background(), client.ContextBasicAuth, client.BasicAuth{
48 | UserName: cfg.Elastic.UserName,
49 | Password: cfg.Elastic.Password,
50 | })
51 | configuration := client.NewConfiguration()
52 | configuration.Servers = client.ServerConfigurations{
53 | client.ServerConfiguration{URL: cfg.Elastic.Url},
54 | }
55 | return &PineZincSearch{client: client.NewAPIClient(configuration), ctx: ctx}
56 | }
57 |
--------------------------------------------------------------------------------
/src/application/controllers/backend/wechat/account_controller.go:
--------------------------------------------------------------------------------
1 | package wechat
2 |
3 | import (
4 | "github.com/xiusin/pinecms/src/application/controllers"
5 | "github.com/xiusin/pinecms/src/application/controllers/backend"
6 | "github.com/xiusin/pinecms/src/application/models/tables"
7 | "github.com/xiusin/pinecms/src/common/helper"
8 | )
9 |
10 | type WechatAccountController struct {
11 | backend.BaseController
12 | }
13 |
14 | func (c *WechatAccountController) Construct() {
15 | c.Table = &tables.WechatAccount{}
16 | c.Entries = &[]tables.WechatAccount{}
17 | c.BaseController.Construct()
18 | }
19 |
20 | func (c *WechatAccountController) PostClear() {
21 | appid, _ := c.Input().GetString("appid")
22 | account, _ := GetOfficialAccount(appid)
23 |
24 | if err := account.GetBasic().ClearQuota(); err != nil {
25 | helper.Ajax(err, 1, c.Ctx())
26 | } else {
27 | helper.Ajax("清除请求限制成功", 0, c.Ctx())
28 | }
29 | }
30 |
31 | func (c *WechatAccountController) PostSelect() {
32 | _ = c.Orm.Find(c.Entries)
33 | m := c.Entries.(*[]tables.WechatAccount)
34 | var kv []tables.KV
35 | for _, model := range *m {
36 | kv = append(kv, tables.KV{
37 | Label: model.Name,
38 | Value: model.AppId,
39 | })
40 | }
41 | helper.Ajax(kv, 0, c.Ctx())
42 | }
43 |
44 | // PostDistribution 会员分布
45 | func (c *WechatAccountController) PostDistribution() {
46 | appid, _ := c.Input().GetString("appid")
47 | if len(appid) == 0 {
48 | helper.Ajax("请选择公众号", 1, c.Ctx())
49 | return
50 | }
51 |
52 | typ, _ := c.Input().GetInt("type")
53 | field := "province"
54 | if typ == 2 {
55 | field = "city"
56 | }
57 |
58 | data, _ := c.Orm.QueryString("SELECT " + field + ", COUNT(*) AS total FROM " +
59 | controllers.GetTableName("wechat_member") + " WHERE appid='" + appid + "' GROUP BY " + field)
60 |
61 | helper.Ajax(data, 0, c.Ctx())
62 | }
63 |
--------------------------------------------------------------------------------
/src/common/logger/pinecms_logger.go:
--------------------------------------------------------------------------------
1 | package logger
2 |
3 | import (
4 | "bytes"
5 | "strings"
6 | "time"
7 |
8 | "github.com/xiusin/pine"
9 | "github.com/xiusin/pinecms/src/application/models/tables"
10 | "github.com/xiusin/pinecms/src/common/helper"
11 | "xorm.io/xorm"
12 | )
13 |
14 | var firstLineChar = "pine.(*routerWrapper).result"
15 | var _sp = "\n"
16 |
17 | type pineCmsLoggerWriter struct {
18 | orm *xorm.Engine
19 | logCh chan []byte
20 | closed bool
21 | errorFlag []byte
22 | }
23 |
24 | func NewPineCmsLogger(orm *xorm.Engine, len uint) *pineCmsLoggerWriter {
25 | l := &pineCmsLoggerWriter{orm: orm, logCh: make(chan []byte, len), errorFlag: []byte("[ERRO]")}
26 | go l.BeginConsume()
27 | return l
28 | }
29 |
30 | func (p *pineCmsLoggerWriter) BeginConsume() {
31 | for {
32 | log, isCloser := <-p.logCh
33 | if !isCloser {
34 | return
35 | }
36 | if _, err := p.orm.InsertOne(p.parseLog(log)); err != nil {
37 | pine.Logger().Warn("日志入库失败", err)
38 | }
39 | }
40 | }
41 |
42 | func (p *pineCmsLoggerWriter) Write(data []byte) (int, error) {
43 | defer func() {
44 | if err := recover(); err != nil {
45 | p.closed = true
46 | }
47 | }()
48 | if !p.closed && bytes.Contains(data, p.errorFlag) {
49 | p.logCh <- data
50 | }
51 | return 0, nil
52 | }
53 |
54 | func (p *pineCmsLoggerWriter) parseLog(log []byte) *tables.Log {
55 | lines := strings.Split(*helper.Bytes2String(log), _sp)
56 | var index = -1
57 | for i, v := range lines {
58 | if strings.Contains(v, firstLineChar) {
59 | index = i
60 | break
61 | }
62 | }
63 | if index-3 > 3 {
64 | lines = append(lines[0:1], lines[3:index-3]...)
65 | }
66 | return &tables.Log{Level: 3, Message: strings.Join(lines, _sp), Time: tables.LocalTime(time.Now())}
67 | }
68 |
69 | func (p *pineCmsLoggerWriter) Close() {
70 | close(p.logCh)
71 | }
72 |
--------------------------------------------------------------------------------
/src/common/message/email.go:
--------------------------------------------------------------------------------
1 | package message
2 |
3 | import (
4 | "bytes"
5 | "encoding/base64"
6 | "io"
7 | "strconv"
8 |
9 | "github.com/kataras/go-mailer"
10 | "github.com/xiusin/pine/di"
11 | "github.com/xiusin/pinecms/src/config"
12 | "golang.org/x/text/encoding/simplifiedchinese"
13 | "golang.org/x/text/transform"
14 | )
15 |
16 | type EmailMessage struct {
17 | client *mailer.Mailer
18 | }
19 |
20 | var ServiceEmailMessage = "pinecms.message.service.email"
21 |
22 | func (n *EmailMessage) Init() error {
23 | conf, err := config.SiteConfig()
24 | if err != nil {
25 | return err
26 | }
27 | port, err := strconv.Atoi(conf.Get("EMAIL_PORT"))
28 | if err != nil {
29 | port = 25
30 | }
31 | cfg := mailer.Config{
32 | Host: conf.Get("EMAIL_SMTP"),
33 | Username: conf.Get("EMAIL_USER"),
34 | Password: conf.Get("EMAIL_PWD"),
35 | Port: port,
36 | FromAddr: conf.Get("EMAIL_EMAIL"),
37 | FromAlias: conf.Get("EMAIL_SEND_NAME"),
38 | }
39 |
40 | n.client = mailer.New(cfg)
41 | return nil
42 | }
43 |
44 | func (n *EmailMessage) Notice(receiver []string, params []any, templateId int) error {
45 | return nil
46 | }
47 |
48 | func (n *EmailMessage) Send(receiver []string, subject string, body string) error {
49 | subject = "=?UTF-8?B?" + base64.StdEncoding.EncodeToString([]byte(subject)) + "?="
50 |
51 | data, _ := io.ReadAll(transform.NewReader(bytes.NewReader([]byte(body)), simplifiedchinese.GBK.NewEncoder()))
52 | return n.client.SendWithBytes(subject, []byte(data), receiver...)
53 | }
54 |
55 | func (n *EmailMessage) UpdateCfg() error { return n.Init() }
56 |
57 | func init() {
58 | di.Set(ServiceEmailMessage, func(builder di.AbstractBuilder) (any, error) {
59 | email := &EmailMessage{}
60 | if err := email.Init(); err != nil {
61 | return nil, err
62 | }
63 | return email, nil
64 | }, true)
65 | }
66 |
--------------------------------------------------------------------------------
/src/application/models/adspace_model.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "github.com/xiusin/pine"
5 | "github.com/xiusin/pinecms/src/application/models/tables"
6 | "github.com/xiusin/pinecms/src/common/helper"
7 | "xorm.io/xorm"
8 | )
9 |
10 | type AdSpaceModel struct {
11 | orm *xorm.Engine
12 | }
13 |
14 | func NewAdSpaceModel() *AdSpaceModel {
15 | return &AdSpaceModel{orm: helper.GetORM()}
16 | }
17 |
18 | func (l *AdSpaceModel) All() []tables.AdvertSpace {
19 | var list = []tables.AdvertSpace{}
20 | if err := l.orm.Desc("id").Find(&list); err != nil {
21 | pine.Logger().Error(err.Error())
22 | }
23 | return list
24 | }
25 |
26 | func (l *AdSpaceModel) GetList(page, limit int64) ([]tables.AdvertSpace, int64) {
27 | offset := (page - 1) * limit
28 | var list = []tables.AdvertSpace{}
29 | var total int64
30 | var err error
31 | if total, err = l.orm.Desc("id").Limit(int(limit), int(offset)).FindAndCount(&list); err != nil {
32 | pine.Logger().Error(err.Error())
33 | }
34 | return list, total
35 | }
36 |
37 | func (l *AdSpaceModel) Add(data *tables.AdvertSpace) int64 {
38 | id, err := l.orm.Insert(data)
39 | if err != nil {
40 | pine.Logger().Error(err.Error())
41 | }
42 | return id
43 | }
44 |
45 | func (l *AdSpaceModel) Delete(id int64) bool {
46 | res, err := l.orm.ID(id).Delete(&tables.AdvertSpace{})
47 | if err != nil {
48 | pine.Logger().Error(err.Error())
49 | }
50 | return res > 0
51 | }
52 |
53 | func (l *AdSpaceModel) Get(id int64) *tables.AdvertSpace {
54 | var link = &tables.AdvertSpace{}
55 | ok, _ := l.orm.ID(id).Get(link)
56 | if !ok {
57 | return nil
58 | }
59 | return link
60 | }
61 |
62 | func (l *AdSpaceModel) Update(data *tables.AdvertSpace) bool {
63 | id, err := l.orm.ID(data.Id).Update(data)
64 | if err != nil {
65 | pine.Logger().Error(err.Error())
66 | }
67 |
68 | return id > 0
69 | }
70 |
--------------------------------------------------------------------------------
/src/application/plugins/task/table/table.go:
--------------------------------------------------------------------------------
1 | package table
2 |
3 | import (
4 | "time"
5 | )
6 |
7 | type TaskInfo struct {
8 | Id int64 `json:"id" xorm:"autoincr"`
9 | EntityId int `json:"entity_id" xorm:"comment('任务ID')"`
10 | RepeatConf string `json:"repeatConf" xorm:"comment('任务配置') text"`
11 | Name string `json:"name" xorm:"comment('任务名称') varchar(50)"`
12 | Cron string `json:"cron" xorm:"comment('cron配置') varchar(50)"`
13 | Every uint `json:"every" xorm:"comment('间隔执行时间,task_type=1时生效')"`
14 | Limit uint `json:"limit" xorm:"comment('最大执行次数 不传为无限次')"`
15 | Remark string `json:"remark" xorm:"comment('备注') text"`
16 | Status uint `json:"status" xorm:"comment('状态 0:停止 1:运行')"`
17 | StartDate *time.Time `json:"startDate" xorm:"comment('开始时间')"`
18 | EndDate *time.Time `json:"endDate" xorm:"comment('结束时间')"`
19 | Data string `json:"data" xorm:"comment('数据') text"`
20 | Service string `json:"service" xorm:"comment('执行的service实例ID') varchar(100)"`
21 | Type uint8 `json:"type" xorm:"comment('类型 0:系统 1:用户')"`
22 | NextRunTime *time.Time `json:"nextRunTime" xorm:"comment('下一次执行时间')"`
23 | TaskType uint `json:"taskType" xorm:"comment('状态 0:cron 1:时间间隔')"`
24 | Error string `json:"error"`
25 | UpdatedAt time.Time `json:"updated_at"`
26 | }
27 |
28 | type TaskLog struct {
29 | Id int64 `json:"id" xorm:"autoincr"`
30 | TaskId int64 `json:"taskId" xorm:"comment('任务ID')"`
31 | Status bool `json:"status" xorm:"comment('状态 0:失败 1:成功')"`
32 | Detail string `json:"detail" xorm:"text comment('详情')"`
33 | ExecTime int64 `json:"exec_time" xorm:"int(11) default 0 comment('执行时长')"`
34 | CreatedAt time.Time `json:"created_at"`
35 | UpdatedAt time.Time `json:"updated_at"`
36 | }
37 |
--------------------------------------------------------------------------------
/src/application/models/tables/member.go:
--------------------------------------------------------------------------------
1 | package tables
2 |
3 | type Member struct {
4 | Id int64 `xorm:"pk autoincr" json:"id"`
5 | Account string `json:"account" xorm:"comment('账号') varchar(40)"`
6 | Password string `json:"password" xorm:"comment('密码') varchar(32)"`
7 | Avatar string `json:"avatar" xorm:"comment('头像') varchar(40)"`
8 | Nickname string `json:"nickname" xorm:"comment('昵称') varchar(40)"`
9 | Integral uint `json:"integral" xorm:"comment('积分') int(11)"`
10 | Telphone string `json:"telphone" xorm:"comment('电话') varchar(30)"`
11 | Qq string `json:"qq" xorm:"comment('QQ') varchar(15)"`
12 | Description string `json:"description" xorm:"comment('个人简介')"`
13 | CreatedAt LocalTime `json:"created" xorm:"created"`
14 | UpdatedAt *LocalTime `json:"updated" xorm:"updated"`
15 | LoginTime LocalTime `json:"login_time" xorm:"datetime comment('最后登录时间')"`
16 | LoginIp string `json:"login_ip" xorm:"varchar(15) comment('最后登录IP')"`
17 | Email string `json:"email" xorm:"comment('邮箱') varchar(30)"`
18 | Status uint `json:"status" xorm:"comment('状态: 0=禁用 1=待验证 2=正常')"`
19 | Sex uint `json:"sex" xorm:"comment('性别: 0=保密 1=男 2=女') tinyint(3)"`
20 | GroupId uint `json:"group_id" xorm:"comment('分组ID') int(6)"`
21 | VerifyToken string `json:"-" xorm:"comment('验证token')"`
22 | }
23 |
24 | type MemberGroup struct {
25 | Id int64 `xorm:"pk autoincr" json:"id"`
26 | Name string `json:"name" xorm:"comment('名称') varchar(40)"`
27 | Description string `json:"description" xorm:"comment('介绍')"`
28 | Status uint `json:"status" xorm:"comment('状态: 0=禁用 1=正常')"`
29 | Listorder uint `json:"listorder"`
30 | CreatedAt LocalTime `json:"created" xorm:"created"`
31 | UpdatedAt *LocalTime `json:"updated" xorm:"updated"`
32 | }
33 |
--------------------------------------------------------------------------------
/src/application/models/tables/define.go:
--------------------------------------------------------------------------------
1 | package tables
2 |
3 | import (
4 | "database/sql/driver"
5 | "time"
6 | )
7 |
8 | const localDateTimeFormat string = time.DateTime
9 |
10 | type LocalTime time.Time
11 |
12 | func (l LocalTime) MarshalJSON() ([]byte, error) {
13 | if time.Time(l).IsZero() {
14 | return []byte(`""`), nil
15 | }
16 | b := make([]byte, 0, len(localDateTimeFormat)+2)
17 | b = append(b, '"')
18 | b = time.Time(l).AppendFormat(b, localDateTimeFormat)
19 | b = append(b, '"')
20 | return b, nil
21 | }
22 |
23 | func (l *LocalTime) UnmarshalJSON(b []byte) error {
24 | now, err := time.ParseInLocation(`"`+localDateTimeFormat+`"`, string(b), time.Local)
25 | *l = LocalTime(now)
26 | return err
27 | }
28 |
29 | func (l LocalTime) String() string {
30 | return time.Time(l).Format(localDateTimeFormat)
31 | }
32 |
33 | func (l LocalTime) Now() LocalTime {
34 | return LocalTime(time.Now())
35 | }
36 |
37 | func (l LocalTime) ParseTime(t time.Time) LocalTime {
38 | return LocalTime(t)
39 | }
40 |
41 | func (l LocalTime) format() string {
42 | return time.Time(l).Format(localDateTimeFormat)
43 | }
44 |
45 | func (l LocalTime) MarshalText() ([]byte, error) {
46 | return []byte(l.format()), nil
47 | }
48 |
49 | func (l *LocalTime) FromDB(b []byte) error {
50 | if nil == b || len(b) == 0 {
51 | return nil
52 | }
53 | var now time.Time
54 | var err error
55 | if now, err = time.ParseInLocation(localDateTimeFormat, string(b), time.Local); err != nil {
56 | return err
57 | }
58 | *l = LocalTime(now)
59 | return nil
60 | }
61 |
62 | func (l *LocalTime) ToDB() ([]byte, error) {
63 | if nil == l {
64 | return nil, nil
65 | }
66 | return []byte(time.Time(*l).Format(localDateTimeFormat)), nil
67 | }
68 |
69 | func (l *LocalTime) Value() (driver.Value, error) {
70 | if nil == l {
71 | return nil, nil
72 | }
73 | return time.Time(*l).Format(localDateTimeFormat), nil
74 | }
75 |
--------------------------------------------------------------------------------
/src/application/controllers/backend/wechat/qrcode_ controller.go:
--------------------------------------------------------------------------------
1 | package wechat
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/silenceper/wechat/v2/officialaccount/basic"
7 | "github.com/xiusin/pine"
8 | "github.com/xiusin/pinecms/src/application/controllers/backend"
9 | "github.com/xiusin/pinecms/src/application/models/tables"
10 | "xorm.io/xorm"
11 | )
12 |
13 | type WechatQrcodeController struct {
14 | backend.BaseController
15 | }
16 |
17 | func (c *WechatQrcodeController) Construct() {
18 | c.Table = &tables.WechatQrcode{}
19 | c.Entries = &[]tables.WechatQrcode{}
20 | c.BaseController.Construct()
21 | c.SearchFields = []backend.SearchFieldDsl{
22 | {Field: "appid"},
23 | }
24 |
25 | c.OpBefore = func(act int, intf any) error {
26 | if act == backend.OpList {
27 | sess := intf.(*xorm.Session)
28 |
29 | if appid, ok := c.P.Param["appid"]; ok && len(appid.(string)) > 0 {
30 | sess.Where("appid = ?", appid)
31 | }
32 | }
33 | return nil
34 | }
35 |
36 | c.OpAfter = c.after
37 | }
38 |
39 | func (c *WechatQrcodeController) after(act int, params any) error {
40 | if act == backend.OpAdd {
41 | account, _ := GetOfficialAccount(params.(*tables.WechatQrcode).AppId)
42 |
43 | data := c.Table.(*tables.WechatQrcode)
44 | var req *basic.Request
45 | if data.IsTemp {
46 | var t time.Duration
47 | if time.Time(data.ExpireTime).Before(time.Now()) {
48 | t = 0
49 | } else {
50 | t = time.Until(time.Time(data.ExpireTime))
51 | }
52 | req = basic.NewTmpQrRequest(t, data.SceneStr)
53 | } else {
54 | req = basic.NewLimitQrRequest(data.SceneStr)
55 | }
56 | ticket, err := account.GetBasic().GetQRTicket(req)
57 | if err != nil {
58 | pine.Logger().Error("获取传参二维码失败", err)
59 | return err
60 | }
61 | data.Ticket = ticket.Ticket
62 |
63 | data.Url = ticket.URL
64 |
65 | c.Orm.Where("id = ?", data.Id).Update(data)
66 |
67 | }
68 | return nil
69 | }
70 |
--------------------------------------------------------------------------------
/src/application/controllers/backend/attachment_controller.go:
--------------------------------------------------------------------------------
1 | package backend
2 |
3 | import (
4 | "xorm.io/xorm"
5 |
6 | "github.com/xiusin/pinecms/src/application/controllers/middleware/apidoc"
7 | "github.com/xiusin/pinecms/src/application/models/tables"
8 | "github.com/xiusin/pinecms/src/common/helper"
9 | )
10 |
11 | // @Rest(path = "/api/v1/11/{container_id}")
12 | type AttachmentController struct {
13 | BaseController
14 | }
15 |
16 | func (c *AttachmentController) Construct() {
17 | c.SearchFields = []SearchFieldDsl{
18 | //{Field: "`type`", Op: "="},
19 | }
20 | c.Table = &tables.Attachments{}
21 | c.Entries = &[]*tables.Attachments{}
22 | c.Group = "系统配置"
23 | c.SubGroup = "附件管理"
24 |
25 | c.apiEntities = map[string]apidoc.Entity{
26 | "list": {Title: "附件列表", Desc: "查询已上传系统的附件列表"},
27 | "add": {Title: "新增配置", Desc: "新增上传附件"},
28 | "del": {Title: "删除配置", Desc: "删除一个附件"},
29 | }
30 | c.OpBefore = c.before
31 | c.BaseController.Construct()
32 | }
33 |
34 | func (c *AttachmentController) before(act int, params any) error {
35 | if act == OpList {
36 | cid, _ := c.Input().GetInt64("classifyId")
37 | if cid > 0 {
38 | params.(*xorm.Session).Where("classify_id = ?", cid)
39 | }
40 | params.(*xorm.Session).Desc("id")
41 | }
42 | return nil
43 | }
44 |
45 | // @Rest(method = "GET", route = "/gaa")
46 | func (c *AttachmentController) PostAdd() {
47 | if err := c.BindParse(); err != nil {
48 | helper.Ajax(err.Error(), 1, c.Ctx())
49 | return
50 | }
51 |
52 | md5, _ := c.Input().GetString("md5")
53 | if len(md5) == 0 {
54 | helper.Ajax("缺少必要的MD5参数", 1, c.Ctx())
55 | return
56 | }
57 | data := &tables.Attachments{}
58 |
59 | if exist, _ := c.Orm.Where("md5 = ?", md5).Get(data); exist {
60 | helper.Ajax(data, 0, c.Ctx())
61 | return
62 | } else if err := c.add(); err == nil {
63 | helper.Ajax(c.Table, 0, c.Ctx())
64 | } else {
65 | helper.Ajax(err, 1, c.Ctx())
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/application/controllers/backend/dict_category_controller.go:
--------------------------------------------------------------------------------
1 | package backend
2 |
3 | import (
4 | "errors"
5 |
6 | "github.com/xiusin/pinecms/src/application/models/tables"
7 | "github.com/xiusin/pinecms/src/common/helper"
8 | )
9 |
10 | type DictCategoryController struct {
11 | BaseController
12 | }
13 |
14 | func (c *DictCategoryController) Construct() {
15 | c.AppId = "admin"
16 | c.Group = "字典管理"
17 | c.KeywordsSearch = []SearchFieldDsl{
18 | {Field: "key", Op: "LIKE", DataExp: "%$?%"},
19 | {Field: "name", Op: "LIKE", DataExp: "%$?%"},
20 | }
21 | c.SearchFields = []SearchFieldDsl{
22 | {Field: "status"},
23 | }
24 | c.Table = &tables.DictCategory{}
25 | c.Entries = &[]*tables.DictCategory{}
26 | c.ApiEntityName = "字典分类"
27 | c.BaseController.Construct()
28 | c.OpBefore = c.before
29 | }
30 |
31 | func (c *DictCategoryController) before(act int, param any) error {
32 | switch act {
33 | case OpAdd, OpEdit:
34 | key := param.(*tables.DictCategory).Key
35 | id := param.(*tables.DictCategory).Id
36 | if len(key) == 0 {
37 | return errors.New("字典分类标识不存在")
38 | }
39 | sess := c.Orm.Table(c.Table).Where("`key` = ?", key)
40 | if OpEdit == act {
41 | sess = sess.Where("id <> ?", id)
42 | }
43 | exist, err := sess.Exist()
44 | if err != nil {
45 | return err
46 | }
47 | if exist {
48 | return errors.New("该字典分类标识已经存在")
49 | }
50 | case OpDel:
51 | exist, err := c.Orm.Table(&tables.Dict{}).In("cid", param.(*idParams).Ids).Exist()
52 | if err != nil {
53 | return err
54 | }
55 | if exist {
56 | return errors.New("分类下存在字典数据,无法删除")
57 | }
58 | }
59 | return nil
60 | }
61 |
62 | func (c *DictCategoryController) GetSelect() {
63 | _ = c.Orm.Where("status = 1").Find(c.Entries)
64 | m := c.Entries.(*[]*tables.DictCategory)
65 | var kv []tables.KV
66 | for _, model := range *m {
67 | kv = append(kv, tables.KV{Label: model.Name, Value: model.Key})
68 | }
69 | helper.Ajax(kv, 0, c.Ctx())
70 | }
71 |
--------------------------------------------------------------------------------
/src/application/controllers/backend/database_backup_controller.go:
--------------------------------------------------------------------------------
1 | package backend
2 |
3 | import (
4 | "path/filepath"
5 |
6 | "github.com/xiusin/pine"
7 | "github.com/xiusin/pinecms/src/application/controllers"
8 | "github.com/xiusin/pinecms/src/common/helper"
9 | )
10 |
11 | type DatabaseBackupController struct {
12 | BaseController
13 | }
14 |
15 | // 定时备份任务功能
16 |
17 | func (c *DatabaseBackupController) RegisterRoute(b pine.IRouterWrapper) {
18 | b.ANY("/backup/list", "BackupList")
19 | b.POST("/backup/delete", "BackupDelete")
20 | b.POST("/backup/download", "BackupDownload")
21 | }
22 |
23 | func (c *DatabaseBackupController) BackupList() {
24 | settingData := c.Ctx().Value(controllers.CacheSetting).(map[string]string)
25 | uploader := getStorageEngine(settingData)
26 | list, err := uploader.List(baseBackupDir)
27 | if err != nil {
28 | c.Logger().Error(err.Error())
29 | }
30 | helper.Ajax(list, 0, c.Ctx())
31 | }
32 |
33 | func (c *DatabaseBackupController) BackupDownload() {
34 | settingData := c.Ctx().Value(controllers.CacheSetting).(map[string]string)
35 | name, _ := c.Input().GetString("name")
36 | if len(name) == 0 {
37 | helper.Ajax("参数错误", 1, c.Ctx())
38 | return
39 | }
40 | relName := filepath.Join(baseBackupDir, name)
41 | uploader := getStorageEngine(settingData)
42 | exists, _ := uploader.Exists(relName)
43 | if !exists {
44 | helper.Ajax("文件不存在或已经被删除", 1, c.Ctx())
45 | return
46 | }
47 | helper.Ajax(uploader.GetFullUrl(relName), 0, c.Ctx())
48 | }
49 |
50 | func (c *DatabaseBackupController) BackupDelete() {
51 | settingData := c.Ctx().Value(controllers.CacheSetting).(map[string]string)
52 | names := c.Input().Get("ids")
53 | if len(names.([]any)) == 0 {
54 | helper.Ajax("参数错误", 1, c.Ctx())
55 | return
56 | }
57 | relName := names.([]any)[0].(string)
58 | uploader := getStorageEngine(settingData)
59 | if err := uploader.Remove(relName); err != nil {
60 | helper.Ajax(err, 1, c.Ctx())
61 | return
62 | }
63 | helper.Ajax("删除文件成功", 0, c.Ctx())
64 | }
65 |
--------------------------------------------------------------------------------
/src/common/message/sms.go:
--------------------------------------------------------------------------------
1 | package message
2 |
3 | // import (
4 | //
5 | // "sync"
6 | //
7 | // openapi "github.com/alibabacloud-go/darabonba-openapi/client"
8 | // dysmsapi20170525 "github.com/alibabacloud-go/dysmsapi-20170525/v2/client"
9 | // "github.com/alibabacloud-go/tea/tea"
10 | // "xorm.io/xorm"
11 | // "github.com/xiusin/pine/di"
12 | //
13 | // )
14 | const ServiceSmsMessage = "pinecms.message.service.sms"
15 |
16 | //
17 | //type SmsMessage struct {
18 | // client *dysmsapi20170525.Client
19 | // orm *xorm.Engine
20 | // sync.Mutex
21 | //}
22 | //
23 | //func (n *SmsMessage) UpdateCfg() error {
24 | // return n.Init()
25 | //}
26 | //
27 | //func (n *SmsMessage) Init() error {
28 | // n.Lock()
29 | // defer n.Unlock()
30 | // var err error
31 | // var client *dysmsapi20170525.Client
32 | // config := &openapi.Config{
33 | // AccessKeyId: nil,
34 | // AccessKeySecret: nil,
35 | // }
36 | // config.Endpoint = tea.String("dysmsapi.aliyuncs.com")
37 | // if client, err = dysmsapi20170525.NewClient(config); err == nil {
38 | // n.client = client
39 | // }
40 | // _orm , err := di.Get(&xorm.Engine{})
41 | // if err != nil {
42 | // return err
43 | // }
44 | // n.orm = _orm.(*xorm.Engine)
45 | // return err
46 | //}
47 | //
48 | //func (n *SmsMessage) Notice(receiver []string, params []any, templateId int) error {
49 | // return nil
50 | //}
51 | //
52 | //func (n *SmsMessage) Send(receiver []string, msg string, typo int) error {
53 | //
54 | // for _, v := range receiver {
55 | // sendSmsRequest := &dysmsapi20170525.SendSmsRequest{
56 | // PhoneNumbers: tea.String(v),
57 | // }
58 | // if _, err := n.client.SendSms(sendSmsRequest); err != nil {
59 | // return err
60 | // }
61 | // }
62 | //
63 | // return nil
64 | //}
65 | //
66 | //func init() {
67 | // di.Set(ServiceSmsMessage, func(builder di.AbstractBuilder) (any, error) {
68 | // msgService := &SmsMessage{}
69 | // err := msgService.Init()
70 | // if err != nil {
71 | // return nil, err
72 | // }
73 | // return msgService, nil
74 | // }, true)
75 | //}
76 |
--------------------------------------------------------------------------------
/src/application/models/admin_model.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "errors"
5 |
6 | "github.com/xiusin/pinecms/src/application/models/tables"
7 | "github.com/xiusin/pinecms/src/common/helper"
8 | "xorm.io/xorm"
9 | )
10 |
11 | type AdminModel struct {
12 | orm *xorm.Engine
13 | }
14 |
15 | func NewAdminModel() *AdminModel {
16 | return &AdminModel{orm: helper.GetORM()}
17 | }
18 |
19 | // Login 登录用户
20 | func (a *AdminModel) Login(username, password, ip string) (tables.Admin, error) {
21 | admin := tables.Admin{Username: username}
22 | exist, _ := a.orm.Get(&admin)
23 | if !exist {
24 | return admin, errors.New("管理员不存在")
25 | }
26 | //password = helper.Password(password, admin.Encrypt)
27 | //if password != admin.Password {
28 | // return admin, errors.New("密码错误,请再次尝试!")
29 | //}
30 | admin.Lastloginip = ip
31 | a.orm.ID(admin.Userid).Update(admin)
32 | return admin, nil
33 | }
34 |
35 | //获取用户信息
36 | func (a *AdminModel) GetUserInfo(userid int64) (tables.Admin, error) {
37 | admin := tables.Admin{Userid: userid}
38 | has, _ := a.orm.Get(&admin)
39 | if has {
40 |
41 | return admin, nil
42 | }
43 | return admin, errors.New("没有找到用户")
44 | }
45 |
46 | //编辑密码
47 | func (a *AdminModel) EditPassword(userid int64, password string) bool {
48 | admin := tables.Admin{Userid: userid}
49 | res, _ := a.orm.Get(&admin)
50 | encrypt := string(helper.Krand(8, 3))
51 | if res {
52 | admin.Password = helper.Password(password, encrypt)
53 | admin.Encrypt = encrypt
54 | result, _ := a.orm.ID(admin.Userid).Update(&admin)
55 | if result == 0 {
56 | return false
57 | } else {
58 | return true
59 | }
60 | }
61 | return false
62 | }
63 |
64 | //获取管理员列表
65 | func (a *AdminModel) GetList(where string, page, rows int, order string, sortType string) []tables.Admin {
66 | start := (page - 1) * rows
67 | admins := []tables.Admin{}
68 | if sortType == "asc" {
69 | a.orm.Where(where).Asc(order).Limit(rows, start).Find(&admins)
70 | } else {
71 | a.orm.Where(where).Desc(order).Limit(rows, start).Find(&admins)
72 | }
73 | return admins
74 | }
75 |
--------------------------------------------------------------------------------
/src/application/controllers/taglibs/likearticle.go:
--------------------------------------------------------------------------------
1 | package taglibs
2 |
3 | import (
4 | "fmt"
5 | "reflect"
6 | "strings"
7 |
8 | "github.com/CloudyKit/jet"
9 | "github.com/xiusin/pine"
10 | "github.com/xiusin/pinecms/src/application/controllers"
11 | "github.com/xiusin/pinecms/src/application/models"
12 | "github.com/xiusin/pinecms/src/common/helper"
13 | )
14 |
15 | func LikeArticle(args jet.Arguments) reflect.Value {
16 | defer func() {
17 | if err := recover(); err != nil {
18 | pine.Logger().Error(fmt.Sprintf("likearticle Failed %s", err))
19 | }
20 | }()
21 | if !checkArgType(&args) {
22 | return defaultArrReturnVal
23 | }
24 | var kws = []string{args.Get(1).String(), args.Get(2).String(), args.Get(3).String()}
25 | limit := int(getNumber(args.Get(0)))
26 | catid := getNumber(args.Get(4))
27 | titlelen := int(getNumber(args.Get(6)))
28 | var keywords []any
29 | if limit < 1 {
30 | limit = 10
31 | }
32 | var cond []string
33 | m := models.NewCategoryModel()
34 | category, _ := m.GetCategoryFByIdForBE(catid)
35 | modelTable := controllers.GetTableName(category.Model.Table)
36 | sess := getOrmSess(category.Model.Table).Where(modelTable+".id <> ?", getNumber(args.Get(5)))
37 | for _, kw := range kws {
38 | splitKeywords := strings.Split(kw, ",")
39 | for _, keyword := range splitKeywords {
40 | if keyword == "" {
41 | continue
42 | }
43 | cond = append(cond, fmt.Sprintf("%s.keywords LIKE ? OR %s.title LIKE ? OR %s.tags LIKE ?", modelTable, modelTable, modelTable))
44 | keywords = append(keywords, "%"+strings.Trim(keyword, "")+"%", "%"+strings.Trim(keyword, "")+"%", "%"+strings.Trim(keyword, "")+"%")
45 | }
46 | }
47 | categoryTable := getCategoryTable()
48 | sess.And(strings.Join(cond, " OR "), keywords...)
49 | sess.Join("LEFT", categoryTable, categoryTable+".id = "+modelTable+".catid")
50 | sess.Select(fmt.Sprintf("%s.*, %s.catname as typename", modelTable, categoryTable))
51 | list, _ := sess.Limit(limit).Desc(modelTable + ".id").QueryString()
52 | helper.HandleArtListInfo(list, titlelen)
53 | return reflect.ValueOf(list)
54 | }
55 |
--------------------------------------------------------------------------------
/src/application/controllers/backend/webssh/middleware/Auth.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "github.com/xiusin/pine"
5 | "github.com/xiusin/pinecms/src/application/controllers/backend/webssh/Apiform"
6 | "github.com/xiusin/pinecms/src/application/controllers/backend/webssh/common"
7 | "github.com/xiusin/pinecms/src/application/controllers/backend/webssh/errcode"
8 | "github.com/xiusin/pinecms/src/application/controllers/backend/webssh/tables"
9 | "github.com/xiusin/pinecms/src/common/helper"
10 | "strings"
11 | "time"
12 | )
13 |
14 | func Auth() pine.Handler {
15 | return func(c *pine.Context) {
16 | for _, s := range []string{"ui", "login", "sftp", "term"} {
17 | if strings.Contains(c.Path(), "/" + s) {
18 | c.Next()
19 | return
20 | }
21 | }
22 |
23 | var resp Apiform.Resp
24 | jwtToken := c.Header("Authorization")
25 | if jwtToken == "" || !strings.HasPrefix(jwtToken, "Bearer ") {
26 | resp.Code = errcode.S_auth_fmt_err
27 | resp.Msg = "Token不正确"
28 | c.Render().JSON(resp)
29 | return
30 | }
31 | jwtToken = jwtToken[7:]
32 | claims, err := common.ParseToken(jwtToken)
33 | if err != nil {
34 | resp.Code = errcode.S_auth_err
35 | resp.Msg = "Token错误,请重新登录"
36 | c.Render().JSON(resp)
37 | return
38 | }
39 | valid := claims.Valid()
40 | if valid != nil {
41 | resp.Code = errcode.S_auth_err
42 | resp.Msg = "用户登录超时,请重新登录"
43 | c.Render().JSON(resp)
44 | return
45 | }
46 | var userInfo tables.SSHUser
47 |
48 | helper.GetORM().Where("id = ?", claims.Userid).Get(&userInfo)
49 | if userInfo.Phone == 0 {
50 | resp.Code = errcode.S_auth_err
51 | resp.Msg = "用户不存在,请重新登录"
52 | c.Render().JSON(resp)
53 | return
54 | }
55 | c.Set("uid", claims.Userid)
56 | c.Set("token", "")
57 | newToken, err := common.ReleaseToken(claims.Userid)
58 | if err != nil {
59 | resp.Code = errcode.S_auth_err
60 | resp.Msg = err.Error()
61 | c.Render().JSON(resp)
62 | return
63 | }
64 | if time.Now().Add(24*time.Hour).Unix() > claims.ExpiresAt {
65 | c.Set("token", newToken)
66 | }
67 | c.Next()
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/common/enum/enum.go:
--------------------------------------------------------------------------------
1 | // Package enum 枚举值类型
2 | package enum
3 |
4 | import (
5 | "reflect"
6 | "strings"
7 | "unsafe"
8 | )
9 |
10 | // TypeContract 枚举值类型接口
11 | type TypeContract[T any] interface {
12 | Value() T
13 | }
14 |
15 | // Type 枚举值类型
16 | type Type[T any] struct {
17 | value *T
18 | }
19 |
20 | func (t Type[T]) Value() T {
21 | return *t.value
22 | }
23 |
24 | // New 枚举值类型
25 | func New[T any](t *T) *T {
26 | if t == nil {
27 | t = new(T)
28 | }
29 | value := reflect.ValueOf(t).Elem()
30 | typ := reflect.TypeOf(t).Elem()
31 |
32 | for i := 0; i < value.NumField(); i++ {
33 | field := typ.Field(i)
34 | fv := value.Field(i)
35 |
36 | // 非枚举定类型忽略
37 | if !strings.Contains(field.Type.String(), "enum.Type") {
38 | continue
39 | }
40 |
41 | // 判断具体反射类型
42 | valueType, ok := field.Type.FieldByName("value")
43 | if !ok {
44 | continue
45 | }
46 |
47 | rf := reflect.Indirect(fv).FieldByName("value")
48 | ptr := unsafe.Pointer(rf.UnsafeAddr())
49 |
50 | fieldValue := field.Tag.Get("enum")
51 | switch valueType.Type.Elem().Kind() {
52 | case reflect.Int:
53 | *(**int)(ptr) = toPtr(i + 1)
54 | case reflect.Int8:
55 | *(**int8)(ptr) = toPtr(int8(i + 1))
56 | case reflect.Int16:
57 | *(**int16)(ptr) = toPtr(int16(i + 1))
58 | case reflect.Int32:
59 | *(**int32)(ptr) = toPtr(int32(i + 1))
60 | case reflect.Int64:
61 | *(**int64)(ptr) = toPtr(int64(i + 1))
62 | case reflect.Uint:
63 | *(**uint)(ptr) = toPtr(uint(i + 1))
64 | case reflect.Uint8:
65 | *(**uint8)(ptr) = toPtr(uint8(i + 1))
66 | case reflect.Uint16:
67 | *(**uint16)(ptr) = toPtr(uint16(i + 1))
68 | case reflect.Uint32:
69 | *(**uint32)(ptr) = toPtr(uint32(i + 1))
70 | case reflect.Uint64:
71 | *(**uint64)(ptr) = toPtr(uint64(i + 1))
72 | case reflect.String:
73 | if fieldValue == "" {
74 | fieldValue = field.Name
75 | }
76 | *(**string)(ptr) = toPtr(fieldValue)
77 | default:
78 | panic("enum value type not support: " + valueType.Type.String())
79 | }
80 | }
81 | return t
82 |
83 | }
84 |
85 | func toPtr[T any](v T) *T {
86 | return &v
87 | }
88 |
--------------------------------------------------------------------------------
/src/application/controllers/middleware/apidoc/parse.go:
--------------------------------------------------------------------------------
1 | package apidoc
2 |
3 | import (
4 | "reflect"
5 | "strings"
6 |
7 | "github.com/fatih/structs"
8 | )
9 |
10 | func parseInterface(reqParams any) ([]apiParam, []apiReturn) {
11 | if reqParams != nil {
12 | s := structs.Fields(reqParams)
13 | var apiReqParams []apiParam
14 | var apiReturns []apiReturn
15 | for _, field := range s {
16 | apiData := field.Tag("api")
17 | if len(apiData) == 0 {
18 | continue
19 | }
20 | fieldType := "any"
21 | switch field.Kind() {
22 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
23 | fieldType = "number"
24 | case reflect.Bool:
25 | fieldType = "bool"
26 | case reflect.String:
27 | fieldType = "string"
28 | case reflect.Struct, reflect.Map:
29 | fieldType = "object"
30 | case reflect.Slice, reflect.Array:
31 | fieldType = "array"
32 | case reflect.Float32, reflect.Float64:
33 | fieldType = "float"
34 | }
35 | apiDatas := strings.Split(apiData, "|")
36 | name := field.Tag("json")
37 | if len(name) == 0 {
38 | name = field.Name()
39 | }
40 | apiReqParam := apiParam{Type: fieldType, Name: name}
41 | apiReturn := apiReturn{Type: fieldType, Name: name, Params: nil}
42 | for _, data := range apiDatas {
43 | kv := strings.Split(strings.ToLower(data), ":")
44 | v := ""
45 | if len(kv) > 1 {
46 | v = kv[1]
47 | }
48 | switch kv[0] {
49 | case "require":
50 | if strings.ToLower(v) == "true" {
51 | apiReqParam.Require = true
52 | }
53 | case "remark":
54 | apiReqParam.Desc = v
55 | apiReturn.Desc = v
56 | case "main":
57 | if strings.ToLower(v) == "true" {
58 | apiReturn.Main = true
59 | }
60 | case "default":
61 | apiReqParam.Default = v
62 | apiReturn.Default = v
63 | }
64 | }
65 | apiReqParams = append(apiReqParams, apiReqParam)
66 | apiReturns = append(apiReturns, apiReturn)
67 | }
68 | return apiReqParams, apiReturns
69 | }
70 | return nil, nil
71 | }
72 |
--------------------------------------------------------------------------------
/src/application/controllers/frontend/page.go:
--------------------------------------------------------------------------------
1 | package frontend
2 |
3 | import (
4 | "net/http"
5 | "os"
6 | "path/filepath"
7 |
8 | "github.com/xiusin/pine"
9 | "github.com/xiusin/pine/render/engine/pjet"
10 | "github.com/xiusin/pinecms/src/application/controllers"
11 | "github.com/xiusin/pinecms/src/application/models"
12 | "github.com/xiusin/pinecms/src/application/models/tables"
13 | )
14 |
15 | func (c *IndexController) Page(pathname string) {
16 | c.setTemplateData()
17 | pageFilePath := GetStaticFile(pathname)
18 | tid, _ := c.Ctx().Params().GetInt64("tid")
19 | if tid < 1 {
20 | c.Ctx().Abort(404, "tid failed")
21 | return
22 | }
23 | category, err := models.NewCategoryModel().GetCategoryFByIdForBE(tid)
24 | if err != nil {
25 | pine.Logger().Error(err.Error())
26 | c.Ctx().Abort(404)
27 | return
28 | }
29 | page := category.Page
30 | if page == nil {
31 | page = &tables.Page{Title: category.Catname}
32 | }
33 | if page.Keywords == "" {
34 | page.Keywords = category.Keywords
35 | }
36 | if len(page.Description) > 0 {
37 | page.Description = category.Description
38 | }
39 | _ = os.MkdirAll(filepath.Dir(pageFilePath), os.ModePerm)
40 | f, err := os.OpenFile(pageFilePath, os.O_CREATE|os.O_TRUNC|os.O_RDWR, os.ModePerm)
41 | if err != nil {
42 | pine.Logger().Error(err.Error())
43 | c.Ctx().Abort(http.StatusNotFound)
44 | return
45 | }
46 | defer f.Close()
47 | pineJet := pine.Make(controllers.ServiceJetEngine).(*pjet.PineJet)
48 | tpl := "page.jet"
49 | if len(category.DetailTpl) > 0 {
50 | tpl = category.DetailTpl
51 | }
52 | temp, err := pineJet.GetTemplate(template(tpl))
53 | if err != nil {
54 | _ = c.Ctx().WriteString(err.Error())
55 | return
56 | }
57 | err = temp.Execute(f, viewDataToJetMap(c.Render().GetViewData()), struct {
58 | Field *tables.Page // 单页信息
59 | Position string
60 | TypeID int64
61 | ArtID int64
62 | TopCategory *tables.Category // 顶级栏目信息
63 | }{
64 | Field: page,
65 | TypeID: tid,
66 | })
67 | if err != nil {
68 | _ = c.Ctx().WriteString(err.Error())
69 | return
70 | }
71 | data, _ := os.ReadFile(pageFilePath)
72 | _ = c.Ctx().WriteHTMLBytes(data)
73 | }
74 |
--------------------------------------------------------------------------------
/src/application/controllers/taglibs/helper.go:
--------------------------------------------------------------------------------
1 | package taglibs
2 |
3 | import (
4 | "crypto/md5"
5 | "encoding/json"
6 | "fmt"
7 | "reflect"
8 | "strconv"
9 | "strings"
10 |
11 | "github.com/CloudyKit/jet"
12 | "github.com/xiusin/pine"
13 | "github.com/xiusin/pinecms/src/application/controllers"
14 | "github.com/xiusin/pinecms/src/application/models/tables"
15 | "github.com/xiusin/pinecms/src/common/helper"
16 | "xorm.io/xorm"
17 | )
18 |
19 | var defaultArrReturnVal = reflect.ValueOf([]any{})
20 |
21 | var defaultSignalVal = reflect.ValueOf(nil)
22 |
23 | func checkArgType(args *jet.Arguments) bool {
24 | l := args.NumOfArguments()
25 | for i := 0; i < l; i++ {
26 | t := args.Get(i)
27 | if !isNumber(t) && t.Type().String() != "string" && t.Type().String() != "bool" {
28 | pine.Logger().Error(fmt.Sprintf("参数类型不支持: idx: %d -> type: %s -> val: %s", i, t, args.Get(i)))
29 | return false
30 | }
31 | }
32 | return true
33 | }
34 |
35 | func isNumber(val reflect.Value) bool {
36 | return strings.Contains(val.String(), "float") || strings.Contains(val.String(), "int")
37 | }
38 |
39 | func getNumber(val reflect.Value) int64 {
40 | t := val.Type().String()
41 | if strings.Contains(t, "float") {
42 | return int64(val.Float())
43 | } else if strings.Contains(t, "int") {
44 | return val.Int()
45 | } else if t == "string" {
46 | v, _ := strconv.Atoi(val.String())
47 | return int64(v)
48 | }
49 | return 0
50 | }
51 |
52 | func getOrmSess(table ...string) *xorm.Session {
53 | if len(table) == 0 {
54 | table = []string{"articles"}
55 | }
56 | return helper.GetORM().Table(controllers.GetTableName(table[0]))
57 | }
58 |
59 | func getCategoryOrm() *xorm.Session {
60 | return helper.GetORM().Table(&tables.Category{}).Where("ismenu = 1")
61 | }
62 |
63 | func getCategoryTable() string {
64 | return controllers.GetTableName("category")
65 | }
66 |
67 | func getTagHash(args jet.Arguments) string {
68 | var arr []any
69 | for i := 0; i < args.NumOfArguments(); i++ {
70 | arr = append(arr, args.Get(i).Interface())
71 | }
72 | byts, _ := json.Marshal(&arr)
73 | md := md5.New()
74 | md.Write(byts)
75 |
76 | return fmt.Sprintf("%x", md.Sum(nil))
77 | }
78 |
--------------------------------------------------------------------------------
/src/application/models/admin_role_model.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "log"
5 |
6 | "xorm.io/xorm"
7 |
8 | "github.com/xiusin/pinecms/src/application/models/tables"
9 | "github.com/xiusin/pinecms/src/common/helper"
10 | )
11 |
12 | type AdminRoleModel struct {
13 | orm *xorm.Engine
14 | }
15 |
16 | func NewAdminRoleModel() *AdminRoleModel {
17 | return &AdminRoleModel{orm: helper.GetORM()}
18 | }
19 |
20 | func (a *AdminRoleModel) List(page, rows int) ([]tables.AdminRole, int64) {
21 | start := (page - 1) * rows
22 | roles := []tables.AdminRole{}
23 | total, _ := a.orm.Limit(rows, start).FindAndCount(&roles)
24 | return roles, total
25 | }
26 |
27 | func (a *AdminRoleModel) All() map[int64]*tables.AdminRole {
28 | var roles = map[int64]*tables.AdminRole{}
29 | var _roles []*tables.AdminRole
30 | a.orm.Find(&_roles)
31 | for _, role := range _roles {
32 | roles[role.Id] = role
33 | }
34 | return roles
35 | }
36 |
37 | func (a *AdminRoleModel) CheckRoleName(id int64, rolename string) bool {
38 | exists, _ := a.orm.Where("roleid <> ?", id).Where("rolename = ?", rolename).Exist()
39 | return exists
40 | }
41 |
42 | func (a *AdminRoleModel) AddRole(rolename, description string, disabled, listorder int64) bool {
43 | newRole := tables.AdminRole{
44 | Rolename: rolename,
45 | Description: description,
46 | Disabled: disabled,
47 | Listorder: listorder,
48 | }
49 | insertId, err := a.orm.Insert(&newRole)
50 | if err != nil || insertId == 0 {
51 | log.Println(err, insertId)
52 | return false
53 | }
54 | return true
55 | }
56 |
57 | func (a *AdminRoleModel) GetRoleById(id int64) (tables.AdminRole, error) {
58 | role := tables.AdminRole{Id: id}
59 | _, err := a.orm.Get(&role)
60 | return role, err
61 | }
62 |
63 | func (a *AdminRoleModel) UpdateRole(role tables.AdminRole) bool {
64 | res, err := a.orm.Where("id=?", role.Id).MustCols("disabled").Update(&role)
65 | if err != nil || res == 0 {
66 | return false
67 | }
68 | return true
69 | }
70 |
71 | func (a *AdminRoleModel) DeleteRole(role tables.AdminRole) bool {
72 | res, err := a.orm.Delete(&role)
73 | if err != nil || res == 0 {
74 | return false
75 | }
76 | return true
77 | }
78 |
--------------------------------------------------------------------------------
/src/application/controllers/backend/login_controller.go:
--------------------------------------------------------------------------------
1 | package backend
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/gbrlsnchs/jwt/v3"
7 |
8 | "github.com/xiusin/pine"
9 | "github.com/xiusin/pinecms/src/application/controllers"
10 | "github.com/xiusin/pinecms/src/application/controllers/middleware/apidoc"
11 | "github.com/xiusin/pinecms/src/application/models"
12 | "github.com/xiusin/pinecms/src/common/helper"
13 | "github.com/xiusin/pinecms/src/config"
14 | )
15 |
16 | type LoginController struct {
17 | pine.Controller
18 | }
19 |
20 | func (c *LoginController) RegisterRoute(b pine.IRouterWrapper) {
21 | b.ANY("/login", "Login")
22 | }
23 |
24 | func (c *LoginController) Login() {
25 | var p loginUserParam
26 | apidoc.SetApiEntity(c.Ctx(), &apidoc.Entity{
27 | ApiParam: &p,
28 | AppId: "admin",
29 | Group: "登录模块",
30 | SubGroup: "系统登录",
31 | Title: "登录系统",
32 | Desc: "账号密码登录系统, 并且返回JWT凭证",
33 | })
34 |
35 | helper.PanicErr(parseParam(c.Ctx(), &p))
36 |
37 | if p.Password == "" || p.Username == "" {
38 | helper.Ajax("参数不能为空", 1, c.Ctx())
39 | return
40 | }
41 |
42 | // if !captcha.Verify(p.CaptchaId, p.CaptchaValue) {
43 | // helper.Ajax("验证码错误, 请重新输入", 1, c.Ctx())
44 | // return
45 | // }
46 |
47 | // 读取登录人信息
48 | admin, err := models.NewAdminModel().Login(p.Username, p.Password, c.Ctx().ClientIP())
49 | if err != nil {
50 | helper.Ajax(err, 1, c.Ctx())
51 | return
52 | }
53 | var hs = jwt.NewHS256([]byte(config.App().JwtKey))
54 | now := time.Now()
55 | pl := controllers.LoginAdminPayload{
56 | Payload: jwt.Payload{
57 | Subject: "PineCMS",
58 | ExpirationTime: jwt.NumericDate(now.Add(24 * 30 * 12 * time.Hour)),
59 | },
60 | Id: admin.Userid,
61 | AdminId: admin.Userid,
62 | RoleID: admin.RoleIdList,
63 | AdminName: admin.Username,
64 | }
65 | if token, err := jwt.Sign(pl, hs); err != nil {
66 | helper.Ajax("登录失败", 1, c.Ctx())
67 | } else {
68 | helper.Ajax(pine.H{
69 | "role_id": admin.RoleIdList,
70 | "admin_id": admin.Userid,
71 | "id": admin.Userid,
72 | "admin_name": admin.Username,
73 | "token": string(token),
74 | }, 0, c.Ctx())
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/application/controllers/backend/wechat/define.go:
--------------------------------------------------------------------------------
1 | package wechat
2 |
3 | import (
4 | "github.com/silenceper/wechat/v2/officialaccount/menu"
5 | "github.com/silenceper/wechat/v2/officialaccount/message"
6 | )
7 |
8 | const CacheKeyWechatMaterialCount = "pinecms.wechat.material.count.%s"
9 |
10 | const CacheKeyWechatMaterialListKeys = "pinecms.wechat.material.list.key"
11 |
12 | const CacheKeyWechatUserTags = "pinecms.wechat.user.tags.%s"
13 |
14 | const CacheKeyWechatMenu = "pinecms.wechat.menu.%s"
15 |
16 | const CacheTimeSecs = 30 * 24 * 3600
17 |
18 | // Plugin TODO 回复插件
19 | type Plugin struct {
20 | }
21 |
22 | // WechatMsg 微信自动回复混合消息结构
23 | type WechatMsg struct {
24 | Title string `json:"title"`
25 | // 小程序
26 | AppID string `json:"appid"`
27 | PagePath string `json:"pagePath"`
28 | ThumbMediaID string `json:"thumb_media_id"`
29 |
30 | // 音乐 视频
31 | Description string `json:"description"`
32 | MusicURL string `json:"music_url"`
33 | HQMusicURL string `json:"hq_music_url"`
34 |
35 | MediaID string `json:"media_id"`
36 |
37 | KfAccount string `json:"kf_account"`
38 |
39 | Articles []*message.Article
40 | }
41 |
42 | // MaterialUploadForm 素材上传表单结构
43 | type MaterialUploadForm struct {
44 | Appid string `json:"appid"`
45 | MediaID string `json:"mediaId"`
46 | FileName string `json:"fileName"`
47 | Title string `json:"title"`
48 | Introduction string `json:"introduction"`
49 | MediaType string `json:"mediaType"`
50 | }
51 |
52 | // menuParam 菜单编辑参数
53 | type menuParam struct {
54 | Appid string `json:"appid"`
55 | Menu struct {
56 | Button []*menu.Button `json:"button"`
57 | MenuID int64 `json:"menuid"`
58 | } `json:"menu"`
59 | }
60 |
61 | // replyMsg 回复消息结构体
62 | type replyMsg struct {
63 | AppId string `json:"appid"`
64 | OpenId string `json:"openid"`
65 | ReplyContent string `json:"replyContent"`
66 | ReplyType string `json:"replyType"`
67 | }
68 |
69 | // UserTags 用户标签结构
70 | type UserTags struct {
71 | Appid string `json:"appid"`
72 | Id int64 `json:"id"`
73 | Name string `json:"name"`
74 | Openids []string `json:"openids"`
75 | Action string `json:"action"`
76 | }
77 |
--------------------------------------------------------------------------------
/src/application/models/menu_model.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "strconv"
5 |
6 | "github.com/xiusin/pinecms/src/application/models/tables"
7 | "github.com/xiusin/pinecms/src/common/helper"
8 | "xorm.io/xorm"
9 | )
10 |
11 | type MenuModel struct {
12 | orm *xorm.Engine
13 | }
14 |
15 | func NewMenuModel() *MenuModel {
16 | return &MenuModel{orm: helper.GetORM()}
17 | }
18 |
19 | func (m MenuModel) GetTree(menus []tables.Menu, parentid int64) []tables.Menu {
20 | res := []tables.Menu{}
21 | if len(menus) != 0 {
22 | for _, menu := range menus {
23 | if parentid == menu.Parentid {
24 | menu.Children = m.GetTree(menus, menu.Id)
25 | res = append(res, menu)
26 | }
27 | }
28 | }
29 | return res
30 | }
31 |
32 | func (m MenuModel) GetAll(menuIdList []int64) []tables.Menu {
33 | menus := []tables.Menu{}
34 | if len(menuIdList) > 0 {
35 | m.orm.Asc("listorder").In("id", menuIdList).Desc("id").Find(&menus)
36 | }
37 | return menus
38 | }
39 |
40 | // 获取菜单父级id
41 | func (m MenuModel) GetParentIds(id int64, result string) string {
42 | menu := tables.Menu{Id: id}
43 | has, _ := m.orm.Get(&menu)
44 | var parentid int64 = 0
45 | if has {
46 | parentid = menu.Parentid
47 | }
48 | res := ""
49 | if parentid != 0 {
50 | if result == "" {
51 | res = strconv.Itoa(int(parentid))
52 | } else {
53 | res = "," + result
54 | }
55 | res = m.GetParentIds(parentid, res)
56 | }
57 | return res
58 | }
59 |
60 | // 检查菜单名称是否存在
61 | func (m MenuModel) CheckName(name string) bool {
62 | has, _ := m.orm.Get(&tables.Menu{Name: name})
63 | return has
64 | }
65 |
66 | func (m MenuModel) GetInfo(id int64) (*tables.Menu, bool) {
67 | im := &tables.Menu{Id: id}
68 | has, _ := m.orm.Get(im)
69 | return im, has
70 | }
71 |
72 | // 获取selectTree
73 | func (m MenuModel) GetSelectTree(menus []tables.Menu, parentid int64) []map[string]any {
74 | res := []map[string]any{}
75 | if len(menus) != 0 {
76 | for _, v := range menus {
77 | if parentid == v.Parentid {
78 | menu := map[string]any{
79 | "value": v.Id,
80 | "label": v.Name,
81 | "children": m.GetSelectTree(menus, v.Id),
82 | }
83 | res = append(res, menu)
84 | }
85 | }
86 | }
87 | return res
88 | }
89 |
--------------------------------------------------------------------------------
/src/application/controllers/taglibs/channel.go:
--------------------------------------------------------------------------------
1 | package taglibs
2 |
3 | import (
4 | "fmt"
5 | "reflect"
6 | "time"
7 |
8 | "github.com/CloudyKit/jet"
9 | "github.com/xiusin/pinecms/src/application/models"
10 | "github.com/xiusin/pinecms/src/application/models/tables"
11 | "github.com/xiusin/pinecms/src/common/helper"
12 | )
13 |
14 | /*
15 | *
16 | typeid = "son | top | self"
17 | top: 顶级栏目 : parentid 为 0
18 | son: 父id 为 reid的下级分类
19 | self: 同级 父ID为reid的同级栏目
20 |
21 | row = "10" 调用数量
22 |
23 | channel(typeid, reid, type, row)
24 | */
25 | func Channel(args jet.Arguments) reflect.Value {
26 | var arr = []tables.Category{}
27 | helper.Cache().Remember("pine:tag:channel:"+getTagHash(args), &arr, func() (any, error) {
28 | if !checkArgType(&args) {
29 | return &arr, nil
30 | }
31 | startTime := time.Now()
32 |
33 | _typeid := getNumber(args.Get(0))
34 | _reid := getNumber(args.Get(1))
35 | _type := args.Get(2).String()
36 | _row := int(getNumber(args.Get(3)))
37 | _noself := args.Get(4).String()
38 | if _row == 0 {
39 | _row = 10
40 | }
41 | m := models.NewCategoryModel()
42 | if _typeid != 0 {
43 | cat := m.GetCategory(_typeid)
44 | if cat == nil {
45 | return &arr, nil
46 | }
47 | _reid = cat.Parentid
48 | }
49 | orm := getCategoryOrm().Limit(_row).Asc("listorder")
50 | switch _type {
51 | case "top":
52 | _reid = 0
53 | orm.Where("parentid = 0")
54 | case "son":
55 | if _typeid == 0 { // 没有设置typeid 返回空
56 | return &arr, nil
57 | }
58 | orm.Where("parentid = ?", _typeid)
59 | case "self":
60 | orm.Where("parentid = ?", _reid)
61 | if _noself == "yes" {
62 | orm.Where("catid <> ?", _typeid)
63 | }
64 | }
65 |
66 | orm.Find(&arr)
67 |
68 | if len(arr) == 0 && _type == "son" && _reid != 0 {
69 | //如果用子栏目模式,当没有子栏目时显示同级栏目
70 | getCategoryOrm().Limit(_row).Asc("listorder").Where("parentid = ?", _reid).Find(&arr)
71 | }
72 | for k, v := range arr {
73 | if v.Type != 2 {
74 | cat1s := m.GetPosArr(v.Catid)
75 | arr[k].Url = fmt.Sprintf("/%s/", m.GetUrlPrefixWithCategoryArr(cat1s))
76 | }
77 | }
78 | fmt.Println("channel 耗时", time.Now().Sub(startTime))
79 |
80 | return &arr, nil
81 | })
82 | return reflect.ValueOf(arr)
83 | }
84 |
--------------------------------------------------------------------------------
/src/application/controllers/backend/const.go:
--------------------------------------------------------------------------------
1 | package backend
2 |
3 | type MenuV2 struct {
4 | Path string `json:"path"`
5 | Label string `json:"label"`
6 | NodePath string `json:"nodePath"`
7 | Exact bool `json:"exact"`
8 | Icon string `json:"icon"`
9 | PathToComponent string `json:"pathToComponent"`
10 | SideVisible bool `json:"sideVisible"`
11 | Children []MenuV2 `json:"children"`
12 | }
13 |
14 | type ThemeConfig struct {
15 | Name string `json:"name"`
16 | Author string `json:"author"`
17 | Description string `json:"description"`
18 | Extra map[string]any `json:"extra"`
19 | IsDefault bool `json:"is_default"`
20 | Dir string `json:"dir"`
21 | }
22 |
23 | type FieldShowInPageList struct {
24 | Forms []KV `json:"forms"`
25 | List []KV `json:"list"`
26 | }
27 |
28 | type KV struct {
29 | Label string `json:"label"`
30 | Value any `json:"value"`
31 | Name string `json:"-"`
32 | Checked bool `json:"-"`
33 | }
34 |
35 | type TabsSchema struct {
36 | Title string `json:"title"`
37 | Hash string `json:"hash"`
38 | Body FormController `json:"body"`
39 | }
40 |
41 | type FormController struct {
42 | Title string `json:"title"`
43 | Api string `json:"api"`
44 | Type string `json:"type"`
45 | Mode string `json:"mode"`
46 | Controls []FormControl `json:"controls"`
47 | }
48 |
49 | type FormControl struct {
50 | Type string `json:"type"`
51 | Name string `json:"name"`
52 | Label string `json:"label"`
53 | Value any `json:"value"`
54 | Options []KV `json:"options"`
55 | Validations string `json:"validations"`
56 | Required bool `json:"required"`
57 | Description string `json:"description"`
58 | Placeholder string `json:"placeholder"`
59 | ValidationErrors string `json:"validationErrors"`
60 | Multiple bool `json:"multiple"`
61 | Precision int `json:"precision"`
62 | Inline bool `json:"inline"`
63 | Buttons []any `json:"buttons"`
64 | Limits []string `json:"limits"`
65 | LimitsLogic string `json:"limitsLogic"`
66 | }
67 |
--------------------------------------------------------------------------------
/resources/themes/default/head2.jet:
--------------------------------------------------------------------------------
1 | {{ import "tags.jet" }}
2 |
3 |
4 |
5 |
6 |

7 |
8 |
9 | {{yield channelartlist(typeid="top", sons=true) content}}
10 |
18 | {{end}}
19 |
20 |
21 |
22 |
23 |
33 |
34 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/src/application/controllers/taglibs/channelartlist.go:
--------------------------------------------------------------------------------
1 | package taglibs
2 |
3 | import (
4 | "fmt"
5 | "reflect"
6 | "strings"
7 |
8 | "github.com/CloudyKit/jet"
9 | "github.com/xiusin/pinecms/src/application/models"
10 | "github.com/xiusin/pinecms/src/application/models/tables"
11 | "xorm.io/xorm"
12 | )
13 |
14 | /*
15 | *
16 | sons 是否附带直属子分类数据, 可用于下级数据的迭代, 可取代channel的sun功能
17 | active 是否附带当前活动状态逻辑, 一般用于下级菜单激活上级菜单的高亮样式
18 | topid 记录当前所处页面的tid
19 | {{channellist = channelartlist(typeid, row, topid, sons, active)}}
20 | */
21 | func ChannelArtList(args jet.Arguments) reflect.Value {
22 | if !checkArgType(&args) {
23 | return defaultArrReturnVal
24 | }
25 | _row := int(getNumber(args.Get(1)))
26 | if _row <= 0 {
27 | _row = 20
28 | }
29 | var _typeid string
30 | if isNumber(args.Get(0)) {
31 | _typeid = fmt.Sprintf("%d", getNumber(args.Get(0)))
32 | } else {
33 | _typeid = args.Get(0).String()
34 | }
35 |
36 | var orm *xorm.Session
37 | if _typeid == "0" || _typeid == "top" {
38 | orm = getCategoryOrm().Where("parentid = 0")
39 | } else if strings.Contains(_typeid, ",") {
40 | orm = getCategoryOrm().In("catid", strings.Split(_typeid, ","))
41 | } else {
42 | orm = getCategoryOrm().Where("parentid = ?", _typeid)
43 | }
44 |
45 | _topId := getNumber(args.Get(2)) // 当前页面的ID
46 |
47 | var cats []tables.Category
48 |
49 | orm.Limit(_row).Select("id, parentid, catname, type, model_id, description, thumb, url").Asc("listorder").Find(&cats)
50 |
51 | if len(cats) == 0 {
52 | return defaultArrReturnVal
53 | }
54 | m := models.NewCategoryModel()
55 |
56 | withSons := args.Get(3).Bool()
57 | withActive := args.Get(4).Bool()
58 |
59 | for k, v := range cats {
60 | if v.Type != 2 {
61 | if withSons {
62 | var sons tables.Category
63 | count, _ := getCategoryOrm().Where("parentid = ?", v.Catid).Count(&sons)
64 | if count > 0 {
65 | cats[k].HasSon = true
66 | }
67 | }
68 | if withActive {
69 | cat1s := m.GetPosArr(v.Catid)
70 | cats[k].Url = fmt.Sprintf("/%s/", m.GetUrlPrefixWithCategoryArr(cat1s))
71 | for _, v := range cat1s {
72 | if v.Catid == _topId {
73 | cats[k].Active = true
74 | break
75 | }
76 | }
77 | }
78 | cats[k].Url = fmt.Sprintf("/%s/", m.GetUrlPrefixWithCategoryArr(m.GetPosArr(v.Catid)))
79 | }
80 | }
81 | return reflect.ValueOf(cats)
82 | }
83 |
--------------------------------------------------------------------------------
/src/application/controllers/backend/dict_controller.go:
--------------------------------------------------------------------------------
1 | package backend
2 |
3 | import (
4 | "errors"
5 |
6 | "github.com/xiusin/pinecms/src/application/models/tables"
7 | "github.com/xiusin/pinecms/src/common/helper"
8 | )
9 |
10 | type DictController struct {
11 | BaseController
12 | }
13 |
14 | func (c *DictController) Construct() {
15 | c.Group = "字典管理"
16 | c.SubGroup = "字典列表管理"
17 | c.KeywordsSearch = []SearchFieldDsl{
18 | {Field: "value", Op: "LIKE", DataExp: "%$?%"},
19 | {Field: "name", Op: "LIKE", DataExp: "%$?%"},
20 | }
21 | c.SearchFields = []SearchFieldDsl{{Field: "status"}, {Field: "cid"}}
22 |
23 | c.Table = &tables.Dict{}
24 | c.Entries = &[]*tables.Dict{}
25 | c.ApiEntityName = "字典属性"
26 |
27 | c.BaseController.Construct()
28 | c.OpBefore = c.before
29 | c.OpAfter = c.after
30 | }
31 |
32 | func (c *DictController) before(act int, param any) error {
33 | switch act {
34 | case OpAdd, OpEdit:
35 | p := param.(*tables.Dict)
36 | id, cid, name := p.Id, p.Cid, p.Name
37 | if cid == 0 {
38 | return errors.New("请选择所属字典分类")
39 | }
40 | sess := c.Orm.Table(c.Table).Where("name = ?", name).Where("cid = ?", cid)
41 | if OpEdit == act {
42 | sess = sess.Where("id <> ?", id)
43 | }
44 | exist, err := sess.Exist()
45 | if err != nil {
46 | return err
47 | }
48 | if exist {
49 | return errors.New("该分类下已经存在此标识")
50 | }
51 | }
52 | return nil
53 | }
54 |
55 | func (c *DictController) after(act int, param any) error {
56 | switch act {
57 | case OpList:
58 | entities := c.Entries.(*[]*tables.Dict)
59 | var ids []uint
60 | for _, dict := range *entities {
61 | ids = append(ids, dict.Cid)
62 | }
63 | if len(ids) > 0 {
64 | var cats []tables.DictCategory
65 | c.Orm.In("id", ids).Find(&cats)
66 | var m = map[uint]string{}
67 | for _, cat := range cats {
68 | m[cat.Id] = cat.Name
69 | }
70 | for _, dict := range *entities {
71 | dict.CatName, _ = m[dict.Cid]
72 | }
73 | }
74 | }
75 | return nil
76 | }
77 |
78 | func (c *DictController) GetSelect() {
79 | cid, _ := c.Ctx().Input().GetInt64("cid")
80 | if cid == 0 {
81 | helper.Ajax("请传入字典分类ID", 1, c.Ctx())
82 | return
83 | }
84 | var dicts []tables.KV
85 | _ = c.Orm.Where("status = 1").Where("cid = ?", cid).Find(c.Entries)
86 | m := c.Entries.(*[]tables.Dict)
87 | for _, model := range *m {
88 | dicts = append(dicts, tables.KV{Label: model.Name, Value: model.Value})
89 | }
90 | helper.Ajax(dicts, 0, c.Ctx())
91 | }
92 |
--------------------------------------------------------------------------------
/src/application/controllers/taglibs/list.go:
--------------------------------------------------------------------------------
1 | package taglibs
2 |
3 | import (
4 | "fmt"
5 | "reflect"
6 | "strconv"
7 | "strings"
8 |
9 | "github.com/CloudyKit/jet"
10 | "github.com/xiusin/pine"
11 | "github.com/xiusin/pinecms/src/application/controllers"
12 | "github.com/xiusin/pinecms/src/application/models"
13 | "github.com/xiusin/pinecms/src/common/helper"
14 | "github.com/xiusin/pinecms/src/config"
15 | )
16 |
17 | /**
18 | list(typeid, page, pagesize, modelname, titlelen, orderby, orderway)
19 | */
20 |
21 | func List(args jet.Arguments) reflect.Value {
22 | if !checkArgType(&args) {
23 | return defaultArrReturnVal
24 | }
25 | defer func() {
26 | if err := recover(); err != nil {
27 | pine.Logger().Error(fmt.Sprintf("ArcList Failed %s", err))
28 | }
29 | }()
30 | catid := getNumber(args.Get(0))
31 | page := getNumber(args.Get(1))
32 | pagesize := getNumber(args.Get(2))
33 | if pagesize == 0 {
34 | conf, _ := config.SiteConfig()
35 | ps, _ := strconv.Atoi(conf["SITE_PAGE_SIZE"])
36 | if ps == 0 {
37 | pagesize = 15
38 | } else {
39 | pagesize = int64(ps)
40 | }
41 | }
42 | if page < 1 {
43 | page = 1
44 | }
45 | offset := (page - 1) * pagesize
46 | tableName := args.Get(3).String()
47 | if tableName == "" || catid < 1 {
48 | return defaultArrReturnVal
49 | }
50 | titlelen := int(getNumber(args.Get(4)))
51 | orderBy := args.Get(5).String()
52 | orderWay := args.Get(6).String()
53 | if orderWay == "" {
54 | orderWay = "desc"
55 | }
56 | if orderBy == "" {
57 | orderBy = "listorder"
58 | }
59 | if orderBy == "rand" {
60 | if strings.ToLower(config.DB().Db.DbDriver) == "mysql" {
61 | orderBy = "RAND()"
62 | } else {
63 | orderBy = "RANDOM()"
64 | }
65 | } else {
66 | orderBy = fmt.Sprintf("%s %s", orderBy, orderWay)
67 | }
68 | categoryTable := getCategoryTable()
69 | modelTable := controllers.GetTableName(tableName)
70 | m := models.NewCategoryModel()
71 | ids := m.GetNextCategoryOnlyCatids(catid, true)
72 | sess := getOrmSess(tableName).Where(modelTable+".deleted_time IS NULL").Where(modelTable+".status = 1").In(modelTable+".catid", ids).Limit(int(pagesize), int(offset)).OrderBy(orderBy)
73 | defer sess.Close()
74 | sess.Join("LEFT", categoryTable, categoryTable+".id = "+modelTable+".catid")
75 | sess.Select(fmt.Sprintf("%s.*, %s.catname as typename", modelTable, categoryTable))
76 | list, _ := sess.QueryString()
77 | helper.HandleArtListInfo(list, titlelen)
78 | return reflect.ValueOf(list)
79 | }
80 |
--------------------------------------------------------------------------------
/src/application/controllers/backend/webssh/Apiform/Api.go:
--------------------------------------------------------------------------------
1 | package Apiform
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 |
7 | uuid "github.com/satori/go.uuid"
8 | "github.com/xiusin/pinecms/src/application/controllers/backend/webssh/common"
9 | "github.com/xiusin/pinecms/src/application/controllers/backend/webssh/tables"
10 | "github.com/xiusin/pinecms/src/common/helper"
11 | )
12 |
13 | type Login struct {
14 | Phone int64 `form:"phone" binding:"required"`
15 | Code string `form:"code" binding:"required"`
16 | }
17 |
18 | type Send struct {
19 | Phone string `form:"phone" binding:"required"`
20 | }
21 |
22 | type Slist struct {
23 | Page int `form:"page" binding:"required"`
24 | Limit int `form:"limit" binding:"required"`
25 | }
26 |
27 | type List_resp struct {
28 | List []tables.SSHServer
29 | Count uint
30 | }
31 |
32 | type GetTerm struct {
33 | ID int64 `form:"id" binding:"required"`
34 | Password string `form:"password"`
35 | Setpass string `from:"setpass"`
36 | }
37 |
38 | type WsAuth struct {
39 | Sid string `uri:"sid" binding:"required,uuid"`
40 | }
41 |
42 | type Edit struct {
43 | ID int64 `form:"id" binding:"required"`
44 | Nickname string `form:"nickname"`
45 | Ip string `form:"ip"`
46 | Port int `form:"port"`
47 | Username string `form:"username"`
48 | }
49 |
50 | type EditPass struct {
51 | ID int64 `form:"id" binding:"required"`
52 | Password string `form:"password" binding:"required"`
53 | }
54 |
55 | type Addser struct {
56 | Nickname string `form:"nickname"`
57 | Ip string `form:"ip" binding:"required"`
58 | Port int `form:"port" binding:"required"`
59 | Username string `form:"username" binding:"required"`
60 | Password string `form:"password" binding:"required"`
61 | Desc string `form:"desc"`
62 | }
63 |
64 | type SerInfo struct {
65 | ID int64
66 | Ip string
67 | Port int
68 | Username string
69 | Password string
70 | BindUser int64
71 | }
72 |
73 | func (t *GetTerm) Decode(server tables.SSHServer) (sid string, err error) {
74 | sid = uuid.Must(uuid.NewV4(), nil).String()
75 | sPass, err := common.AesDecryptCBC(server.Password, []byte(t.Setpass))
76 | if err != nil {
77 | return "", err
78 | }
79 | if sPass == "" {
80 | return "", errors.New("秘钥验证失败")
81 | } else {
82 | var serinfo = SerInfo{server.Id, server.Ip, server.Port, server.Username, sPass, server.BindUser}
83 | sInfo, _ := json.Marshal(serinfo)
84 | helper.Cache().Set(sid, sInfo, 10)
85 | }
86 |
87 | return sid, nil
88 | }
89 |
--------------------------------------------------------------------------------
/cmd/crud/crud_stub.go:
--------------------------------------------------------------------------------
1 | package crud
2 |
3 | const (
4 | controllerDir = "src/application/controllers/backend/"
5 | tableDir = "src/application/models/tables/"
6 | feModuleDir = "src/cool/modules/"
7 | routerFile = "src/router/module.go"
8 | theme = "vim"
9 | goExt = ".go"
10 | controllerTpl = `package backend
11 | import (
12 | "github.com/xiusin/pinecms/src/application/models/tables"
13 | )
14 |
15 | type [ctrl] struct {
16 | BaseController
17 | }
18 |
19 | func (c *[ctrl]) Construct() {
20 | c.SearchFields = map[string]searchFieldDsl{
21 | [searchFieldDsl]
22 | }
23 | c.Group = "[table]管理"
24 | c.ApiEntityName = "[table]"
25 |
26 | c.Table = &tables.[table]{}
27 | c.Entries = &[]tables.[table]{}
28 | }`
29 | tableTpl = `package tables
30 |
31 | [struct]
32 | `
33 |
34 | indexVueTpl = `
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | `
91 |
92 | serviceTsTpl = `import Router from "./router"; export default {[table]: new Router()};`
93 |
94 | serviceRouterTpl = `import { BaseService, Service, Permission } from "/@/core";
95 | @Service("[table]")
96 | class Sys[table] extends BaseService {}
97 | export default Sys[table];`
98 |
99 | serviceIndexTsTpl = `import service from "./service"; export default { service };`
100 | )
101 |
--------------------------------------------------------------------------------
/resources/themes/default/head.jet:
--------------------------------------------------------------------------------
1 | {{ import "tags.jet" }}
2 |
3 |
4 |
5 |

6 |
7 |
8 |
9 | {{yield channelartlist(typeid="top", sons=true) content}}
10 |
18 | {{end}}
19 |
20 |
21 |
22 |
23 |
33 |
34 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | {{yield myad(name="top_all")}}
43 |
44 |
--------------------------------------------------------------------------------
/src/application/controllers/frontend/list.go:
--------------------------------------------------------------------------------
1 | package frontend
2 |
3 | import (
4 | "net/http"
5 | "os"
6 | "path/filepath"
7 |
8 | "github.com/xiusin/pine"
9 | "github.com/xiusin/pine/render/engine/pjet"
10 | "github.com/xiusin/pinecms/src/application/controllers"
11 | "github.com/xiusin/pinecms/src/application/models"
12 | "github.com/xiusin/pinecms/src/application/models/tables"
13 | )
14 |
15 | func (c *IndexController) List(pageFilePath string) {
16 | c.setTemplateData()
17 | pageFilePath = GetStaticFile(pageFilePath)
18 | queryTid, _ := c.Ctx().Input().GetInt64("tid")
19 | tid, _ := c.Ctx().Params().GetInt64("tid", queryTid)
20 | if tid < 1 {
21 | c.Ctx().Abort(404)
22 | return
23 | }
24 | category, err := models.NewCategoryModel().GetCategoryFByIdForBE(tid)
25 | if err != nil || category.Model.Enabled == 0 {
26 | if err == nil {
27 | pine.Logger().Error("模型禁用,无可查看", c.Ctx().Path())
28 | } else {
29 | pine.Logger().Error(err.Error())
30 | }
31 | c.Ctx().Abort(404)
32 | return
33 | }
34 | page, _ := c.Ctx().Params().GetInt("page", 1)
35 | if page < 1 {
36 | page = 1
37 | }
38 | total, _ := getOrmSess(category.Model).In("id", models.NewCategoryModel().GetNextCategoryOnlyCatids(tid, true)).Count()
39 | tpl := "list_" + category.Model.Table + ".jet" // default tpl
40 | if len(category.Model.FeTplList) > 0 { // model tpl
41 | tpl = category.Model.FeTplList
42 | }
43 | if len(category.ListTpl) > 0 { // category tpl
44 | tpl = category.ListTpl
45 | }
46 | _ = os.MkdirAll(filepath.Dir(pageFilePath), os.ModePerm)
47 | f, err := os.Create(pageFilePath)
48 | if err != nil {
49 | c.Ctx().Abort(http.StatusInternalServerError, err.Error())
50 | return
51 | }
52 | defer f.Close()
53 | jet := pine.Make(controllers.ServiceJetEngine).(*pjet.PineJet)
54 | temp, err := jet.GetTemplate(template(tpl))
55 | if err != nil {
56 | c.Ctx().Abort(http.StatusInternalServerError, err.Error())
57 | return
58 | }
59 |
60 | err = temp.Execute(f, viewDataToJetMap(c.Render().GetViewData()), struct {
61 | Field *tables.Category
62 | TypeID int64
63 | ArtCount int64
64 | ModelName string
65 | QP map[string]any
66 | PageNum int64
67 | }{
68 | Field: category,
69 | TypeID: tid,
70 | ArtCount: total,
71 | PageNum: int64(page),
72 | ModelName: category.Model.Table,
73 | QP: c.Ctx().Input().All(),
74 | })
75 | if err != nil {
76 | c.Ctx().Abort(http.StatusInternalServerError, err.Error())
77 | return
78 | }
79 | data, _ := os.ReadFile(pageFilePath)
80 | _ = c.Ctx().WriteHTMLBytes(data)
81 | }
82 |
--------------------------------------------------------------------------------
/cmd/util/util.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import (
4 | "os"
5 | "slices"
6 | "strings"
7 | )
8 |
9 | func AppPath() string {
10 | curPath, _ := os.Getwd()
11 | return curPath
12 | }
13 |
14 | func ScanDir(dir string, ignoreDirs []string) (list []struct {
15 | Path string
16 | IsDir bool
17 | }, err error) {
18 | fs, err := os.ReadDir(dir)
19 | if err != nil {
20 | return
21 | }
22 | list = append(list, struct {
23 | Path string
24 | IsDir bool
25 | }{Path: dir, IsDir: true})
26 |
27 | for _, f := range fs {
28 | if f.IsDir() {
29 | if InSlice(f.Name(), ignoreDirs) {
30 | continue
31 | }
32 | l, err := ScanDir(dir+"/"+f.Name(), ignoreDirs)
33 | if err != nil {
34 | return nil, err
35 | }
36 | list = append(list, l...)
37 | }
38 | // 添加目录和文件到列表, 提供给监听器
39 | list = append(list, struct {
40 | Path string
41 | IsDir bool
42 | }{Path: dir + "/" + f.Name(), IsDir: f.IsDir()})
43 | }
44 | return
45 | }
46 |
47 | func InSlice(needle string, haystacks []string) bool {
48 | return slices.Contains(haystacks, needle)
49 | }
50 | func SnakeString(s string) string {
51 | data := make([]byte, 0, len(s)*2)
52 | j := false
53 | num := len(s)
54 | for i := range num {
55 | d := s[i]
56 | if i > 0 && d >= 'A' && d <= 'Z' && j {
57 | data = append(data, '_')
58 | }
59 | if d != '_' {
60 | j = true
61 | }
62 | data = append(data, d)
63 | }
64 | return strings.ToLower(string(data[:]))
65 | }
66 |
67 | func StrFirstToUpper(str string) string {
68 | temp := strings.Split(strings.ReplaceAll(str, "_", "-"), "-")
69 | var upperStr string
70 | for y := 0; y < len(temp); y++ {
71 | vv := []rune(temp[y])
72 | if y != 0 {
73 | for i := 0; i < len(vv); i++ {
74 | if i == 0 {
75 | vv[i] -= 32
76 | upperStr += string(vv[i]) // + string(vv[i+1])
77 | } else {
78 | upperStr += string(vv[i])
79 | }
80 | }
81 | }
82 | }
83 | return temp[0] + upperStr
84 | }
85 |
86 | func CamelString(s string) string {
87 | data := make([]byte, 0, len(s))
88 | j := false
89 | k := false
90 | num := len(s) - 1
91 | for i := 0; i <= num; i++ {
92 | d := s[i]
93 | if k == false && d >= 'A' && d <= 'Z' {
94 | k = true
95 | }
96 | if d >= 'a' && d <= 'z' && (j || k == false) {
97 | d = d - 32
98 | j = false
99 | k = true
100 | }
101 | if k && d == '_' && num > i && s[i+1] >= 'a' && s[i+1] <= 'z' {
102 | j = true
103 | continue
104 | }
105 | data = append(data, d)
106 | }
107 | return string(data[:])
108 | }
109 |
--------------------------------------------------------------------------------
/src/application/controllers/backend/webssh/common/aes.go:
--------------------------------------------------------------------------------
1 | package common
2 |
3 | import (
4 | "bytes"
5 | "crypto/aes"
6 | "crypto/cipher"
7 | "crypto/md5"
8 | "encoding/base64"
9 | "encoding/hex"
10 | "errors"
11 | )
12 |
13 | func AesEncryptCBC(origData []byte, key []byte) string {
14 | // 分组秘钥
15 | // NewCipher该函数限制了输入k的长度必须为16, 24或者32
16 | key = Md5(key)[:24]
17 | block, _ := aes.NewCipher(key)
18 | blockSize := block.BlockSize() // 获取秘钥块的长度
19 | origData = pkcs7Padding(origData, blockSize) // 补全码
20 | //fmt.Println(blockSize)
21 | blockMode := cipher.NewCBCEncrypter(block, key[:blockSize]) // 加密模式
22 | //fmt.Println(string(Md5(key)[:blockSize]))
23 | encrypted := make([]byte, len(origData)) // 创建数组
24 | blockMode.CryptBlocks(encrypted, origData) // 加密
25 | return base64.StdEncoding.EncodeToString(encrypted)
26 | }
27 | func AesDecryptCBC(enc string, key []byte) (string, error) {
28 | key = Md5(key)[:24]
29 | encrypted, _ := base64.StdEncoding.DecodeString(enc)
30 | block, _ := aes.NewCipher(key) // 分组秘钥
31 | blockSize := block.BlockSize() // 获取秘钥块的长度
32 | blockMode := cipher.NewCBCDecrypter(block, key[:blockSize]) // 加密模式
33 | decrypted := make([]byte, len(encrypted)) // 创建数组
34 | blockMode.CryptBlocks(decrypted, encrypted) // 解密
35 | decrypted, err := pkcs7UnPadding(decrypted) // 去除补全码
36 | if err != nil {
37 | return "", err
38 | }
39 | return string(decrypted), nil
40 | }
41 | func pkcs5Padding(ciphertext []byte, blockSize int) []byte {
42 | padding := blockSize - len(ciphertext)%blockSize
43 | padtext := bytes.Repeat([]byte{byte(padding)}, padding)
44 | return append(ciphertext, padtext...)
45 | }
46 | func pkcs5UnPadding(origData []byte) []byte {
47 | length := len(origData)
48 | unpadding := int(origData[length-1])
49 | return origData[:(length - unpadding)]
50 | }
51 |
52 | func pkcs7Padding(ciphertext []byte, blockSize int) []byte {
53 | padding := blockSize - len(ciphertext)%blockSize
54 | padtext := bytes.Repeat([]byte{byte(padding)}, padding)
55 | return append(ciphertext, padtext...)
56 | }
57 |
58 | func pkcs7UnPadding(origData []byte) ([]byte, error) {
59 | length := len(origData)
60 | unpadding := int(origData[length-1])
61 | if length-unpadding < 0 {
62 | return []byte{}, errors.New("解密失败")
63 | }
64 | return origData[:(length - unpadding)], nil
65 | }
66 |
67 | func Md5(str []byte) []byte {
68 | h := md5.New()
69 | h.Write(str)
70 | //fmt.Println(hex.EncodeToString(h.Sum(nil)))
71 | return []byte(hex.EncodeToString(h.Sum(nil)))
72 | }
73 |
--------------------------------------------------------------------------------