├── .github
└── ISSUE_TEMPLATE
│ └── bug-report.md
├── .gitignore
├── .vscode
├── launch.json
└── settings.json
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── SECURITY.md
├── ascii.conf
├── ascii_art.txt
├── ascii_result.txt
├── assets
├── assets
│ ├── avatar.png
│ ├── dept_avatar.png
│ ├── fileHelper.jpeg
│ ├── g_avatar.jpeg
│ ├── org_avatar.png
│ └── u_10000.png
└── web
│ ├── css
│ ├── index.css
│ ├── join_group.css
│ ├── report.css
│ └── report.dark.css
│ ├── i18n
│ ├── i18n_cn.json
│ └── i18n_en.json
│ ├── images
│ ├── arrow_right.png
│ ├── bottom_arrow_left.png
│ ├── bottom_arrow_left.svg
│ ├── bottom_arrow_right.png
│ ├── img_add.png
│ └── report_success.png
│ ├── invite_detail.html
│ ├── join_group.html
│ ├── js
│ ├── IMJSBridge.js
│ ├── config.js
│ ├── index.js
│ ├── jquery-3.4.1.min.js
│ └── jquery.i18n.min.js
│ ├── privacy_policy.html
│ ├── report.html
│ ├── report_notice.html
│ ├── report_success.html
│ ├── sdkinfo.html
│ ├── success.png
│ └── user_agreement.html
├── configs
├── push
│ └── push_dev.p12
└── tsdd.yaml
├── docker
└── tsdd
│ ├── .env
│ ├── configs
│ ├── tsdd.yaml
│ └── wk.yaml
│ └── docker-compose.yaml
├── docs
├── architecture.png
├── architecture1.png
├── ascci logo.md
├── banner.png
├── download
│ ├── android.png
│ └── iOS.png
├── logo.jpg
├── logo.svg
├── screenshot
│ ├── apm.webp
│ ├── conversationlist.webp
│ ├── forward.webp
│ ├── group.webp
│ ├── messages.webp
│ ├── other.webp
│ ├── others.webp
│ ├── pc11.png
│ ├── pc22.png
│ ├── pc33.png
│ ├── reply.webp
│ ├── robot.webp
│ ├── typing.webp
│ ├── voice.webp
│ └── weblogin.webp
├── tsddwx.png
└── wkwx.png
├── go.mod
├── go.sum
├── internal
└── modules.go
├── main.go
├── modules
├── base
│ ├── 1module.go
│ ├── app
│ │ ├── api.go
│ │ ├── const.go
│ │ ├── db.go
│ │ └── service.go
│ ├── common
│ │ ├── const.go
│ │ ├── service_aliyun.go
│ │ ├── service_sms.go
│ │ ├── service_smsbao.go
│ │ └── service_unisms.go
│ ├── elastic
│ │ ├── db.go
│ │ └── service.go
│ ├── event
│ │ ├── api.go
│ │ ├── db.go
│ │ └── handler.go
│ └── sql
│ │ ├── app-20201103-01.sql
│ │ ├── app-20230912-01.sql
│ │ ├── event-20191106-01.sql
│ │ └── event-20250423-01.sql
├── channel
│ ├── 1module.go
│ ├── api.go
│ ├── db_channel_setting.go
│ ├── service.go
│ ├── service
│ │ └── service.go
│ ├── sql
│ │ ├── channel-20221124-01.sql
│ │ ├── channel-20230920-01.sql
│ │ └── channel-20240515-01.sql
│ └── swagger
│ │ └── api.yaml
├── common
│ ├── 1module.go
│ ├── api.go
│ ├── api_manager.go
│ ├── api_test.go
│ ├── db.go
│ ├── db_app_config.go
│ ├── db_shortno.go
│ ├── service.go
│ ├── sql
│ │ ├── common-20210421-01.sql
│ │ ├── common-20210818-01.sql
│ │ ├── common-20211108-01.sql
│ │ ├── common-20220908-01.sql
│ │ ├── common-20220916-01.sql
│ │ ├── common-20220917-01.sql
│ │ ├── common-20221111-01.sql
│ │ ├── common-20221114-01.sql
│ │ ├── common-20230203-01.sql
│ │ ├── common-20240418-01.sql
│ │ ├── common-20240506-01.sql
│ │ ├── common-20240510-01.sql
│ │ └── common-20240528-01.sql
│ └── swagger
│ │ └── api.yaml
├── file
│ ├── 1module.go
│ ├── api.go
│ ├── const.go
│ ├── service.go
│ ├── service_minio.go
│ ├── service_oss.go
│ ├── service_oss_test.go
│ ├── service_qiniu.go
│ ├── service_seaweedfs.go
│ ├── service_test.go
│ └── swagger
│ │ └── api.yaml
├── group
│ ├── 1module.go
│ ├── api.go
│ ├── api_manager.go
│ ├── api_manager_test.go
│ ├── api_setting_action.go
│ ├── api_test.go
│ ├── const.go
│ ├── db.go
│ ├── db_manager.go
│ ├── db_setting.go
│ ├── event.go
│ ├── invite.go
│ ├── invite_db.go
│ ├── service.go
│ ├── source.go
│ ├── sql
│ │ ├── group_20191106-01.sql
│ │ ├── group_20211202-02.sql
│ │ ├── group_20220411-01.sql
│ │ ├── group_20220815-01.sql
│ │ ├── group_20220818-01.sql
│ │ ├── group_20220830-01.sql
│ │ ├── group_20231123-01.sql
│ │ └── group_20240510-01.sql
│ └── swagger
│ │ └── api.yaml
├── message
│ ├── 1module.go
│ ├── api.go
│ ├── api_conversation.go
│ ├── api_manager.go
│ ├── api_pinned.go
│ ├── api_reminders.go
│ ├── api_test.go
│ ├── api_vo.go
│ ├── common.go
│ ├── const.go
│ ├── db.go
│ ├── db_channel_offset.go
│ ├── db_conversation_extra.go
│ ├── db_device_offset.go
│ ├── db_manager.go
│ ├── db_member_readed.go
│ ├── db_message_extra.go
│ ├── db_message_reaction.go
│ ├── db_message_user_extra.go
│ ├── db_pinned.go
│ ├── db_reminders.go
│ ├── db_user_last_offset.go
│ ├── event.go
│ ├── service.go
│ ├── sql
│ │ ├── message-20210305-01.sql
│ │ ├── message-20210407-01.sql
│ │ ├── message-20210416-01.sql
│ │ ├── message-20210813-01.sql
│ │ ├── message-20211027-01.sql
│ │ ├── message-20220414-01.sql
│ │ ├── message-20220418-01.sql
│ │ ├── message-20220422-01.sql
│ │ ├── message-20220801-01.sql
│ │ ├── message-20220810-01.sql
│ │ ├── message-20221122-01.sql
│ │ └── message-20240510-01.sql
│ └── swagger
│ │ ├── api.yaml
│ │ └── conversation.yaml
├── openapi
│ ├── 1module.go
│ ├── api.go
│ └── swagger
│ │ └── api.yaml
├── qrcode
│ ├── 1module.go
│ ├── api.go
│ └── constant.go
├── report
│ ├── 1module.go
│ ├── api.go
│ ├── api_manager.go
│ ├── api_manager_test.go
│ ├── api_test.go
│ ├── db.go
│ ├── db_manager.go
│ ├── sql
│ │ ├── report-202012221237-01.sql
│ │ └── report-202211291659-01.sql
│ └── swagger
│ │ └── api.yaml
├── robot
│ ├── 1module.go
│ ├── api.go
│ ├── api_manager.go
│ ├── api_test.go
│ ├── const.go
│ ├── db.go
│ ├── event.go
│ ├── model.go
│ ├── sql
│ │ ├── robot-20210926-01.sql
│ │ ├── robot-20211026-01.sql
│ │ └── robot-20211105-01.sql
│ └── swagger
│ │ └── api.yaml
├── source
│ └── source.go
├── statistics
│ ├── 1module.go
│ ├── api.go
│ └── api_test.go
├── user
│ ├── 1module.go
│ ├── api.go
│ ├── api_device.go
│ ├── api_friend.go
│ ├── api_gitee.go
│ ├── api_github.go
│ ├── api_login_log.go
│ ├── api_maillist.go
│ ├── api_manager.go
│ ├── api_manager_test.go
│ ├── api_online.go
│ ├── api_setting.go
│ ├── api_test.go
│ ├── api_usernamelogin.go
│ ├── const.go
│ ├── db.go
│ ├── db_device.go
│ ├── db_device_flag.go
│ ├── db_friend.go
│ ├── db_gitee.go
│ ├── db_github.go
│ ├── db_identities.go
│ ├── db_login_log.go
│ ├── db_maillist.go
│ ├── db_manager.go
│ ├── db_onetime_prekeys.go
│ ├── db_online.go
│ ├── db_setting.go
│ ├── event_friend.go
│ ├── friend_test.go
│ ├── service.go
│ ├── service_online.go
│ ├── source.go
│ ├── sql
│ │ ├── user-20191106-01.sql
│ │ ├── user-20210204-01.sql
│ │ ├── user-20210405-01.sql
│ │ ├── user-20210413-01.sql
│ │ ├── user-20210907-01.sql
│ │ ├── user-20210916-01.sql
│ │ ├── user-20211115-01.sql
│ │ ├── user-20220222-01.sql
│ │ ├── user-20220609-01.sql
│ │ ├── user-20220713-01.sql
│ │ ├── user-20220816-01.sql
│ │ ├── user-20220906-01.sql
│ │ ├── user-20220919-01.sql
│ │ ├── user-20230611-01.sql
│ │ ├── user-20230911-01.sql
│ │ ├── user-20230924-01.sql
│ │ └── user-20231127-01.sql
│ ├── swagger
│ │ ├── api.yaml
│ │ └── friend.yaml
│ └── webhook.go
├── webhook
│ ├── 1module.go
│ ├── api.go
│ ├── api_datasource.go
│ ├── apns.go
│ ├── common.go
│ ├── db.go
│ ├── db_message.go
│ ├── db_test.go
│ ├── github.go
│ ├── push.go
│ ├── push_firebase.go
│ ├── push_hms.go
│ ├── push_iosapns.go
│ ├── push_mi.go
│ ├── push_oppo.go
│ ├── push_test.go
│ ├── push_vivo.go
│ └── sql
│ │ ├── webhook-20210226-01.sql
│ │ ├── webhook-20230920-01.sql
│ │ └── webhook-20241217-01.sql
└── workplace
│ ├── 1module.go
│ ├── api.go
│ ├── api_manager.go
│ ├── api_manager_test.go
│ ├── api_test.go
│ ├── db.go
│ ├── db_manager.go
│ ├── sql
│ ├── workplace-20230823-01.sql
│ ├── workplace-20230906-01.sql
│ └── workplace-20240113-01.sql
│ └── swagger
│ └── api.yaml
├── pkg
├── cache
│ └── cache.go
├── db
│ ├── db.go
│ ├── mysql.go
│ ├── redis.go
│ └── sqlite.go
├── keylock
│ └── keylock.go
├── log
│ ├── logger.go
│ ├── logger_test.go
│ └── options.go
├── markdown
│ ├── markdown.go
│ └── markdown_test.go
├── network
│ └── network.go
├── pool
│ ├── dispatcher.go
│ ├── queue.go
│ └── worker.go
├── redis
│ └── redis.go
├── register
│ └── register.go
├── util
│ ├── aes.go
│ ├── base62.go
│ ├── common.go
│ ├── decimal.go
│ ├── dh.go
│ ├── hash.go
│ ├── ip.go
│ ├── json.go
│ ├── md5.go
│ ├── page.go
│ ├── qreflect.go
│ ├── qreflect_test.go
│ ├── sha.go
│ ├── sign.go
│ ├── string.go
│ ├── stringbuffer.go
│ ├── time.go
│ ├── uuid.go
│ └── yuan_cent.go
├── wait
│ └── wait.go
├── wkevent
│ └── event.go
├── wkhook
│ ├── README.md
│ ├── webhook.pb.go
│ ├── webhook.proto
│ └── webhook_grpc.pb.go
├── wkhttp
│ └── http.go
└── wkrsa
│ ├── rsa.go
│ └── rsa_test.go
└── testenv
└── docker-compose.yaml
/.github/ISSUE_TEMPLATE/bug-report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 提出提问
3 | about: Describe this issue template's purpose here.
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | # 现象
11 |
12 | # 日志
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Binaries for programs and plugins
2 | *.exe
3 | *.dll
4 | *.so
5 | *.dylib
6 |
7 | # Test binary, build with `go test -c`
8 | *.test
9 |
10 | # Output of the go coverage tool, specifically when used with LiteIDE
11 | *.out
12 |
13 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
14 | .glide/
15 |
16 | .idea/
17 |
18 | *.socket
19 |
20 | *.db
21 |
22 | *.sqlite
23 |
24 | *.log
25 | .DS_Store
26 |
27 | .VSCodeCounter/
28 |
29 | logs/
30 |
31 | wukongimdata/
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [],
7 | "name": "Launch",
8 | "type": "go",
9 | "request": "launch",
10 | "mode": "debug",
11 | "program": "${workspaceFolder}/main.go"
12 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "go.alternateTools": {
3 |
4 | },
5 | "go.testFlags": ["-v"]
6 | }
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.20 as build
2 |
3 | ENV GOPROXY https://goproxy.cn,direct
4 | ENV GO111MODULE on
5 |
6 |
7 | WORKDIR /go/cache
8 |
9 |
10 | ADD go.mod .
11 | ADD go.sum .
12 | RUN go mod download
13 |
14 | WORKDIR /go/release
15 |
16 |
17 |
18 | # RUN apt-get update && \
19 | # apt-get -y install ca-certificates
20 |
21 | ADD . .
22 |
23 | # RUN CGO_ENABLED=0 GOOS=linux go build -ldflags='-w -extldflags "-static"' -installsuffix cgo -o app ./main.go
24 |
25 | RUN GIT_COMMIT=$(git rev-parse HEAD) && \
26 | GIT_COMMIT_DATE=$(git log --date=iso8601-strict -1 --pretty=%ct) && \
27 | GIT_VERSION=$(git describe --tags --abbrev=0) && \
28 | GIT_TREE_STATE=$(test -n "`git status --porcelain`" && echo "dirty" || echo "clean") && \
29 | CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -extldflags '-static' -X main.Commit=$GIT_COMMIT -X main.CommitDate=$GIT_COMMIT_DATE -X main.Version=$GIT_VERSION -X main.TreeState=$GIT_TREE_STATE" -installsuffix cgo -o app ./main.go
30 |
31 |
32 | FROM alpine as prod
33 | # Import the user and group files from the builder.
34 | COPY --from=build /etc/passwd /etc/passwd
35 | COPY --from=build /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
36 | RUN \
37 | mkdir -p /usr/share/zoneinfo/Asia && \
38 | ln -s /etc/localtime /usr/share/zoneinfo/Asia/Shanghai
39 | COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
40 | WORKDIR /home
41 | COPY --from=build /go/release/app /home
42 | COPY --from=build /go/release/assets /home/assets
43 | COPY --from=build /go/release/configs /home/configs
44 | RUN echo "Asia/Shanghai" > /etc/timezone
45 | ENV TZ=Asia/Shanghai
46 |
47 | # 不加 apk add ca-certificates apns2推送将请求错误
48 | # RUN apk add ca-certificates
49 |
50 | ENTRYPOINT ["/home/app"]
51 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | build:
2 | docker build -t tangsengdaodaoserver .
3 | push:
4 | docker tag tangsengdaodaoserver registry.cn-shanghai.aliyuncs.com/wukongim/tangsengdaodaoserver:latest
5 | docker push registry.cn-shanghai.aliyuncs.com/wukongim/wukongchatserver:latest
6 | deploy:
7 | docker build -t tangsengdaodaoserver . --platform linux/amd64
8 | docker tag tangsengdaodaoserver registry.cn-shanghai.aliyuncs.com/wukongim/tangsengdaodaoserver:latest
9 | docker push registry.cn-shanghai.aliyuncs.com/wukongim/tangsengdaodaoserver:latest
10 | deploy-v1.5:
11 | docker build -t tangsengdaodaoserver . --platform linux/amd64
12 | docker tag tangsengdaodaoserver registry.cn-shanghai.aliyuncs.com/wukongim/tangsengdaodaoserver:v1.5
13 | docker push registry.cn-shanghai.aliyuncs.com/wukongim/tangsengdaodaoserver:v1.5
14 | run-dev:
15 | docker-compose build;docker-compose up -d
16 | stop-dev:
17 | docker-compose stop
18 | env-test:
19 | docker-compose -f ./testenv/docker-compose.yaml up -d
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Supported Versions
4 |
5 | Use this section to tell people about which versions of your project are
6 | currently being supported with security updates.
7 |
8 | | Version | Supported |
9 | | ------- | ------------------ |
10 | | 5.1.x | :white_check_mark: |
11 | | 5.0.x | :x: |
12 | | 4.0.x | :white_check_mark: |
13 | | < 4.0 | :x: |
14 |
15 | ## Reporting a Vulnerability
16 |
17 | Use this section to tell people how to report a vulnerability.
18 |
19 | Tell them where to go, how often they can expect to get an update on a
20 | reported vulnerability, what to expect if the vulnerability is accepted or
21 | declined, etc.
22 |
--------------------------------------------------------------------------------
/ascii_art.txt:
--------------------------------------------------------------------------------
1 | LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL
2 | LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL
3 | LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL
4 | LLLLLLLLLLLL0CLLLLLLLLLLLLLLLLLLLLLLLLLL
5 | LLLLLLLLLL08@880CfLLLLLLLLLLLLLLLLLLLLLL
6 | LLLLLLLLfL8@8@@8LfLLLLLLLLLLLLLLLLLLLLLL
7 | ffffffffft0@@8@8ffffffffffffffffffffffff
8 | fffffffffCCL8@GLLfLLLfffffffffffffffffff
9 | ffffffffCLLC0@GCCLLLLCffffffffffffffffff
10 | ffffffffG0@@@@@@@8Ltffffffffffffffffffff
11 | ffffffftC888888888Gtffffffffffffffffffff
12 | ffffffftttttttttttttffffffffffffffffffff
13 | fffffffttttttttttttfffffffffffffffffffff
14 | tttttttttttttfftffttttttttttttttttfttttt
15 | tttttttttttttttttttttttttttttttttttttttt
16 | tttttttttttttttttttttttttttttttttttttttt
17 | tttttttttttttttttttttttttttttttttttttttt
18 | tttttttttttttttttttttttttttttttttttttttt
19 | tttttttttttttttttttttttttttttttttttttttt
20 | 111t111111111tt1111111tt1111111t11111111
--------------------------------------------------------------------------------
/ascii_result.txt:
--------------------------------------------------------------------------------
1 | [?25l[?7lLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL
2 | LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL
3 | LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL
4 | LLLLLLLLLLLL0CLLLLLLLLLLLLLLLLLLLLLLLLLL
5 | LLLLLLLLLL08@880CfLLLLLLLLLLLLLLLLLLLLLL
6 | LLLLLLLLfL8@8@@8LfLLLLLLLLLLLLLLLLLLLLLL
7 | ffffffffft0@@8@8ffffffffffffffffffffffff
8 | fffffffffCCL8@GLLfLLLfffffffffffffffffff
9 | ffffffffCLLC0@GCCLLLLCffffffffffffffffff
10 | ffffffffG0@@@@@@@8Ltffffffffffffffffffff
11 | ffffffftC888888888Gtffffffffffffffffffff
12 | ffffffftttttttttttttffffffffffffffffffff
13 | fffffffttttttttttttfffffffffffffffffffff
14 | tttttttttttttfftffttttttttttttttttfttttt
15 | tttttttttttttttttttttttttttttttttttttttt
16 | tttttttttttttttttttttttttttttttttttttttt
17 | tttttttttttttttttttttttttttttttttttttttt
18 | tttttttttttttttttttttttttttttttttttttttt
19 | tttttttttttttttttttttttttttttttttttttttt
20 | 111t111111111tt1111111tt1111111t11111111[0m
21 | [20A[9999999D[43C[0m[0m
22 | [43C[0m[1m[32mWuKongChat is running[0m
23 | [43C[0m---------------------[0m
24 | [43C[0m[1m[33mMode[0m[0m:[0m #mode#[0m
25 | [43C[0m[1m[33mApp name[0m[0m:[0m #appname#[0m
26 | [43C[0m[1m[33mVersion[0m[0m:[0m #version#[0m
27 | [43C[0m[1m[33mGit[0m[0m:[0m #git#[0m
28 | [43C[0m[1m[33mGo build[0m[0m:[0m #gobuild#[0m
29 | [43C[0m[1m[33mIM URL[0m[0m:[0m #imurl#[0m
30 | [43C[0m[1m[33mFile Service[0m[0m:[0m #fileService#[0m
31 | [43C[0m[1m[33mThe API is listening at[0m[0m:[0m #apiAddr#[0m
32 |
33 | [43C[30m[40m [31m[41m [32m[42m [33m[43m [34m[44m [35m[45m [36m[46m [37m[47m [m
34 | [43C[38;5;8m[48;5;8m [38;5;9m[48;5;9m [38;5;10m[48;5;10m [38;5;11m[48;5;11m [38;5;12m[48;5;12m [38;5;13m[48;5;13m [38;5;14m[48;5;14m [38;5;15m[48;5;15m [m
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | [?25h[?7h
--------------------------------------------------------------------------------
/assets/assets/avatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoServer/7ff69f5aba43fae7e8c3408fa8e6cbc88dbc14fb/assets/assets/avatar.png
--------------------------------------------------------------------------------
/assets/assets/dept_avatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoServer/7ff69f5aba43fae7e8c3408fa8e6cbc88dbc14fb/assets/assets/dept_avatar.png
--------------------------------------------------------------------------------
/assets/assets/fileHelper.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoServer/7ff69f5aba43fae7e8c3408fa8e6cbc88dbc14fb/assets/assets/fileHelper.jpeg
--------------------------------------------------------------------------------
/assets/assets/g_avatar.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoServer/7ff69f5aba43fae7e8c3408fa8e6cbc88dbc14fb/assets/assets/g_avatar.jpeg
--------------------------------------------------------------------------------
/assets/assets/org_avatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoServer/7ff69f5aba43fae7e8c3408fa8e6cbc88dbc14fb/assets/assets/org_avatar.png
--------------------------------------------------------------------------------
/assets/assets/u_10000.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoServer/7ff69f5aba43fae7e8c3408fa8e6cbc88dbc14fb/assets/assets/u_10000.png
--------------------------------------------------------------------------------
/assets/web/css/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | }
5 |
6 |
7 | .button {
8 | background-color: white;
9 | padding: 8px 20px;
10 | text-align: center;
11 | border-radius: 4px;
12 | cursor: pointer;
13 |
14 | }
15 |
16 | .primary {
17 | background-color: #2F70F5;
18 | color: white;
19 | }
--------------------------------------------------------------------------------
/assets/web/css/join_group.css:
--------------------------------------------------------------------------------
1 |
2 | .group-box {
3 | display: flex;
4 | flex-direction: column;
5 | height: 100vh;
6 | }
7 |
8 | .top {
9 | width: 100%;
10 | padding: 40px 0px;
11 | background-color: white;
12 | display: flex;
13 | flex-direction: column;
14 | justify-content: center;
15 | border-bottom: 1px #ddd solid;
16 | min-height: 120px;
17 |
18 | }
19 |
20 | .group-info {
21 | display: flex;
22 | flex-direction: column;
23 | justify-content: center;
24 | align-items: center;
25 | background-color: white;
26 | }
27 |
28 | .group-info p {
29 | padding: 0;
30 | margin: 10px 0px 0px 0px;
31 | }
32 |
33 | .group-info .name {
34 | font-size: 18px;
35 | }
36 |
37 | .group-info .num {
38 | font-size: 14px;
39 | color: gray;
40 | }
41 |
42 | .group-info img {
43 | width: 64px;
44 | height: 64px;
45 | border-radius: 6px;
46 | }
47 |
48 | .bottom {
49 | background-color: #eee;
50 | width: 100%;
51 | flex: 1;
52 |
53 | }
54 |
55 | .bottom .box {
56 | display: flex;
57 | margin-top: 20px;
58 | flex-direction: column;
59 | align-items: center;
60 | }
61 |
62 | .bottom .box p {
63 | color: gray;
64 | font-size: 20px;
65 | }
--------------------------------------------------------------------------------
/assets/web/i18n/i18n_cn.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "report.title": "请选择投诉该账号的原因",
4 | "report.notice": "投诉须知"
5 | }
--------------------------------------------------------------------------------
/assets/web/i18n/i18n_en.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "report.title": "Please select the reason",
4 | "report.notice": "Report Notice",
5 | "report.image.evidence": "Photographic evidence (optional)",
6 | "report.content.placeholder": "Complaint content (optional)",
7 | "report.submit": "Submit"
8 | }
--------------------------------------------------------------------------------
/assets/web/images/arrow_right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoServer/7ff69f5aba43fae7e8c3408fa8e6cbc88dbc14fb/assets/web/images/arrow_right.png
--------------------------------------------------------------------------------
/assets/web/images/bottom_arrow_left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoServer/7ff69f5aba43fae7e8c3408fa8e6cbc88dbc14fb/assets/web/images/bottom_arrow_left.png
--------------------------------------------------------------------------------
/assets/web/images/bottom_arrow_left.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/web/images/bottom_arrow_right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoServer/7ff69f5aba43fae7e8c3408fa8e6cbc88dbc14fb/assets/web/images/bottom_arrow_right.png
--------------------------------------------------------------------------------
/assets/web/images/img_add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoServer/7ff69f5aba43fae7e8c3408fa8e6cbc88dbc14fb/assets/web/images/img_add.png
--------------------------------------------------------------------------------
/assets/web/images/report_success.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoServer/7ff69f5aba43fae7e8c3408fa8e6cbc88dbc14fb/assets/web/images/report_success.png
--------------------------------------------------------------------------------
/assets/web/join_group.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 确认加群
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
![]()
24 |
25 |
26 |
27 |
28 |
29 |
30 |
确认加入群聊
31 |
加入该群聊
32 |
33 |
34 |
35 |
36 |
37 |
58 |
59 |
--------------------------------------------------------------------------------
/assets/web/js/config.js:
--------------------------------------------------------------------------------
1 | const apiURL = "http://192.168.3.13:8090/v1/"
--------------------------------------------------------------------------------
/assets/web/js/index.js:
--------------------------------------------------------------------------------
1 |
2 | // TODO: 此页面的 invoke 相关的方法应该作废了 应该使用IMJSBridge这个JS
3 |
4 | /**
5 | * 显示会话界面
6 | * @param {*} param0
7 | */
8 | function showConversation({channelID,channelType}) {
9 | invoke('showConversation',{
10 | 'forward': 'replace',
11 | 'channel_id': channelID,
12 | 'channel_type': channelType
13 | })
14 | }
15 |
16 | function pop() {
17 | invoke('pop')
18 | }
19 |
20 | // 关闭
21 | function closeWebView() {
22 | invoke('pop')
23 | }
24 |
25 | function getQueryString(name)
26 | {
27 | var reg = new RegExp("(^|&)"+ name +"=([^&]*)(&|$)");
28 | var r = window.location.search.substr(1).match(reg);
29 | if(r!=null)return unescape(r[2]); return null;
30 | }
31 |
32 |
33 | function invoke(method,param,callback) {
34 | if(isIOS()) {
35 | invokeIOS(method,param,callback);
36 | }
37 | }
38 |
39 | function invokeIOS(method,param,callback) {
40 | window.WKJSBridge.callNative("LIMCommonPlugin", method, param, function success(res) {
41 | if(callback) {
42 | callback(true,res)
43 | }
44 | }, function fail(res) {
45 | if(callback) {
46 | callback(false,res)
47 | }
48 | });
49 | }
50 |
51 | function isIOS() {
52 | return true
53 | }
54 |
55 | function isAndroid() {
56 | return false
57 | }
58 |
59 | $.extend({
60 | postJSON: function (url, body) {
61 | return $.ajax({
62 | type: 'POST',
63 | url: url,
64 | data: JSON.stringify(body),
65 | contentType: "application/json",
66 | dataType: 'json'
67 | });
68 | }
69 | });
70 |
71 | function uuid() {
72 | var s = [];
73 | var hexDigits = "0123456789abcdef";
74 | for (var i = 0; i < 36; i++) {
75 | s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
76 | }
77 | s[14] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
78 | s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
79 | s[8] = s[13] = s[18] = s[23] = "-";
80 |
81 | var uuid = s.join("");
82 | return uuid;
83 | }
--------------------------------------------------------------------------------
/assets/web/js/jquery.i18n.min.js:
--------------------------------------------------------------------------------
1 | (function($){$.fn.extend({i18n:function(options){var defaults={lang:"",defaultLang:"",filePath:"/i18n/",filePrefix:"i18n_",fileSuffix:"",forever:true,callback:function(){}};function getCookie(name){var arr=document.cookie.split('; ');for(var i=0;i
2 |
3 |
4 |
5 |
6 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | 投诉
15 |
16 |
17 |
18 |
19 |
20 |
21 |

22 |
23 |
投诉已提交
24 |
25 | 我们会尽快核实,并通过消息通知你审核结果。感谢你的支持。
26 |
27 |
关闭
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/assets/web/success.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoServer/7ff69f5aba43fae7e8c3408fa8e6cbc88dbc14fb/assets/web/success.png
--------------------------------------------------------------------------------
/configs/push/push_dev.p12:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoServer/7ff69f5aba43fae7e8c3408fa8e6cbc88dbc14fb/configs/push/push_dev.p12
--------------------------------------------------------------------------------
/docker/tsdd/.env:
--------------------------------------------------------------------------------
1 | # minio
2 | MINIO_ROOT_USER: minio
3 | MINIO_ROOT_PASSWORD: #MINIO_ROOT_PASSWORD#
4 | MINIO_SERVER_URL: #MINIO_SERVER_URL#
5 | MINIO_BROWSER_REDIRECT_URL: #MINIO_BROWSER_REDIRECT_URL#
6 |
7 | # mysql
8 | MYSQL_ROOT_PASSWORD: #MYSQL_ROOT_PASSWORD# # 数据库的密码
9 | MYSQL_DATABASE: im
10 |
11 | ## web
12 | API_URL: #API_URL#
13 |
--------------------------------------------------------------------------------
/docker/tsdd/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | version: '3.1'
2 | services:
3 | wukongim: # 悟空IM通讯服务
4 | image: registry.cn-shanghai.aliyuncs.com/wukongim/wukongim:latest
5 | restart: always
6 | ports:
7 | - "5100:5100"
8 | - "5200:5200"
9 | - "5300:5300"
10 | volumes:
11 | - ./configs/wk.yaml:/root/wukongim/wk.yaml
12 | - ./logs/wk:/root/wukongim/logs
13 | tangsengdaodaoserver: # 唐僧叨叨的业务服务
14 | image: registry.cn-shanghai.aliyuncs.com/wukongim/tangsengdaodaoserver:latest
15 | restart: always
16 | command: "api"
17 | healthcheck:
18 | test: "wget -q -Y off -O /dev/null http://localhost:8090/v1/ping > /dev/null 2>&1"
19 | interval: 10s
20 | timeout: 10s
21 | retries: 3
22 | depends_on:
23 | - redis
24 | - mysql
25 | - wukongim
26 | ports:
27 | - "8090:8090"
28 | volumes:
29 | - ./configs/tsdd.yaml:/home/configs/tsdd.yaml
30 | - ./logs/tsdd:/home/logs
31 | tangsengdaodaoweb: # 唐僧叨叨的web服务
32 | image: registry.cn-shanghai.aliyuncs.com/wukongim/tangsengdaodaoweb:latest
33 | restart: always
34 | environment:
35 | - API_URL
36 | ports:
37 | - "82:80"
38 | minio: # minio文件管理服务
39 | image: minio/minio:latest # use a remote image
40 | expose:
41 | - "9000"
42 | - "9001"
43 | command: "server /data --console-address ':9001'"
44 | ports:
45 | - "9000:9000"
46 | - "9001:9001"
47 | environment:
48 | - MINIO_ROOT_USER
49 | - MINIO_ROOT_PASSWORD
50 | - MINIO_SERVER_URL
51 | - MINIO_BROWSER_REDIRECT_URL
52 | healthcheck:
53 | test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
54 | interval: 30s
55 | timeout: 20s
56 | retries: 3
57 | volumes:
58 | - ./miniodata:/data
59 | mysql: # mysql数据库
60 | image: mysql:latest
61 | command: --default-authentication-plugin=mysql_native_password
62 | healthcheck:
63 | test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"]
64 | environment:
65 | - TZ=Asia/Shanghai
66 | - MYSQL_ROOT_PASSWORD
67 | - MYSQL_DATABASE
68 | volumes:
69 | - ./mysqldata:/var/lib/mysql
70 | redis: # redis
71 | image: redis
72 | restart: always
73 | healthcheck:
74 | test: ["CMD", "redis-cli", "ping"]
75 | interval: 1s
76 | timeout: 3s
77 | retries: 30
78 | adminer: # mysql web管理工具 调试用,为了安全生产不要打开
79 | image: adminer:latest
80 | ports:
81 | - 8306:8080
82 |
--------------------------------------------------------------------------------
/docs/architecture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoServer/7ff69f5aba43fae7e8c3408fa8e6cbc88dbc14fb/docs/architecture.png
--------------------------------------------------------------------------------
/docs/architecture1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoServer/7ff69f5aba43fae7e8c3408fa8e6cbc88dbc14fb/docs/architecture1.png
--------------------------------------------------------------------------------
/docs/ascci logo.md:
--------------------------------------------------------------------------------
1 |
2 | 开源地址:
3 |
4 | 先用image2ascii将图片生成 ascii
5 | https://github.com/qeesung/image2ascii
6 |
7 |
8 | 然后用ascii生成信息
9 | https://github.com/dylanaraps/neofetch
10 |
11 |
12 | // 生成
13 |
14 | image2ascii -f docs/logo.jpg -r 0.08 > ascii_art.txt
15 |
16 | neofetch --config ascii.conf --source ascii_art.txt > ascii_result.txt
--------------------------------------------------------------------------------
/docs/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoServer/7ff69f5aba43fae7e8c3408fa8e6cbc88dbc14fb/docs/banner.png
--------------------------------------------------------------------------------
/docs/download/android.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoServer/7ff69f5aba43fae7e8c3408fa8e6cbc88dbc14fb/docs/download/android.png
--------------------------------------------------------------------------------
/docs/download/iOS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoServer/7ff69f5aba43fae7e8c3408fa8e6cbc88dbc14fb/docs/download/iOS.png
--------------------------------------------------------------------------------
/docs/logo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoServer/7ff69f5aba43fae7e8c3408fa8e6cbc88dbc14fb/docs/logo.jpg
--------------------------------------------------------------------------------
/docs/screenshot/apm.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoServer/7ff69f5aba43fae7e8c3408fa8e6cbc88dbc14fb/docs/screenshot/apm.webp
--------------------------------------------------------------------------------
/docs/screenshot/conversationlist.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoServer/7ff69f5aba43fae7e8c3408fa8e6cbc88dbc14fb/docs/screenshot/conversationlist.webp
--------------------------------------------------------------------------------
/docs/screenshot/forward.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoServer/7ff69f5aba43fae7e8c3408fa8e6cbc88dbc14fb/docs/screenshot/forward.webp
--------------------------------------------------------------------------------
/docs/screenshot/group.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoServer/7ff69f5aba43fae7e8c3408fa8e6cbc88dbc14fb/docs/screenshot/group.webp
--------------------------------------------------------------------------------
/docs/screenshot/messages.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoServer/7ff69f5aba43fae7e8c3408fa8e6cbc88dbc14fb/docs/screenshot/messages.webp
--------------------------------------------------------------------------------
/docs/screenshot/other.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoServer/7ff69f5aba43fae7e8c3408fa8e6cbc88dbc14fb/docs/screenshot/other.webp
--------------------------------------------------------------------------------
/docs/screenshot/others.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoServer/7ff69f5aba43fae7e8c3408fa8e6cbc88dbc14fb/docs/screenshot/others.webp
--------------------------------------------------------------------------------
/docs/screenshot/pc11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoServer/7ff69f5aba43fae7e8c3408fa8e6cbc88dbc14fb/docs/screenshot/pc11.png
--------------------------------------------------------------------------------
/docs/screenshot/pc22.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoServer/7ff69f5aba43fae7e8c3408fa8e6cbc88dbc14fb/docs/screenshot/pc22.png
--------------------------------------------------------------------------------
/docs/screenshot/pc33.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoServer/7ff69f5aba43fae7e8c3408fa8e6cbc88dbc14fb/docs/screenshot/pc33.png
--------------------------------------------------------------------------------
/docs/screenshot/reply.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoServer/7ff69f5aba43fae7e8c3408fa8e6cbc88dbc14fb/docs/screenshot/reply.webp
--------------------------------------------------------------------------------
/docs/screenshot/robot.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoServer/7ff69f5aba43fae7e8c3408fa8e6cbc88dbc14fb/docs/screenshot/robot.webp
--------------------------------------------------------------------------------
/docs/screenshot/typing.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoServer/7ff69f5aba43fae7e8c3408fa8e6cbc88dbc14fb/docs/screenshot/typing.webp
--------------------------------------------------------------------------------
/docs/screenshot/voice.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoServer/7ff69f5aba43fae7e8c3408fa8e6cbc88dbc14fb/docs/screenshot/voice.webp
--------------------------------------------------------------------------------
/docs/screenshot/weblogin.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoServer/7ff69f5aba43fae7e8c3408fa8e6cbc88dbc14fb/docs/screenshot/weblogin.webp
--------------------------------------------------------------------------------
/docs/tsddwx.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoServer/7ff69f5aba43fae7e8c3408fa8e6cbc88dbc14fb/docs/tsddwx.png
--------------------------------------------------------------------------------
/docs/wkwx.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TangSengDaoDao/TangSengDaoDaoServer/7ff69f5aba43fae7e8c3408fa8e6cbc88dbc14fb/docs/wkwx.png
--------------------------------------------------------------------------------
/internal/modules.go:
--------------------------------------------------------------------------------
1 | package modules
2 |
3 | // 引入模块
4 | import (
5 | _ "github.com/TangSengDaoDao/TangSengDaoDaoServer/modules/base"
6 | _ "github.com/TangSengDaoDao/TangSengDaoDaoServer/modules/channel"
7 | _ "github.com/TangSengDaoDao/TangSengDaoDaoServer/modules/common"
8 | _ "github.com/TangSengDaoDao/TangSengDaoDaoServer/modules/file"
9 | _ "github.com/TangSengDaoDao/TangSengDaoDaoServer/modules/group"
10 | _ "github.com/TangSengDaoDao/TangSengDaoDaoServer/modules/message"
11 | _ "github.com/TangSengDaoDao/TangSengDaoDaoServer/modules/openapi"
12 | _ "github.com/TangSengDaoDao/TangSengDaoDaoServer/modules/qrcode"
13 | _ "github.com/TangSengDaoDao/TangSengDaoDaoServer/modules/report"
14 | _ "github.com/TangSengDaoDao/TangSengDaoDaoServer/modules/robot"
15 | _ "github.com/TangSengDaoDao/TangSengDaoDaoServer/modules/statistics"
16 | _ "github.com/TangSengDaoDao/TangSengDaoDaoServer/modules/user"
17 | _ "github.com/TangSengDaoDao/TangSengDaoDaoServer/modules/webhook"
18 | _ "github.com/TangSengDaoDao/TangSengDaoDaoServer/modules/workplace"
19 | )
20 |
--------------------------------------------------------------------------------
/modules/base/1module.go:
--------------------------------------------------------------------------------
1 | package base
2 |
3 | import (
4 | "embed"
5 |
6 | "github.com/TangSengDaoDao/TangSengDaoDaoServer/modules/base/app"
7 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/config"
8 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/pkg/register"
9 | )
10 |
11 | //go:embed sql
12 | var sqlFS embed.FS
13 |
14 | func init() {
15 |
16 | register.AddModule(func(ctx interface{}) register.Module {
17 |
18 | return register.Module{
19 | SetupAPI: func() register.APIRouter {
20 | return app.New(ctx.(*config.Context))
21 | },
22 | SQLDir: register.NewSQLFS(sqlFS),
23 | }
24 | })
25 | }
26 |
--------------------------------------------------------------------------------
/modules/base/app/api.go:
--------------------------------------------------------------------------------
1 | package app
2 |
3 | import (
4 | "errors"
5 | "net/http"
6 |
7 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/config"
8 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/pkg/wkhttp"
9 | )
10 |
11 | type App struct {
12 | service IService
13 | }
14 |
15 | func New(ctx *config.Context) *App {
16 | return &App{
17 | service: NewService(ctx),
18 | }
19 | }
20 |
21 | func (a *App) Route(r *wkhttp.WKHttp) {
22 | r.GET("/v1/apps/:app_id", a.get)
23 | }
24 |
25 | func (a *App) get(c *wkhttp.Context) {
26 | appID := c.Param("app_id")
27 | resp, err := a.service.GetApp(appID)
28 | if err != nil {
29 | c.ResponseError(err)
30 | return
31 | }
32 | if resp.Status == StatusDisable {
33 | c.ResponseError(errors.New("app is disable"))
34 | return
35 | }
36 | c.JSON(http.StatusOK, &appResp{
37 | AppID: resp.AppID,
38 | AppName: resp.AppName,
39 | AppLogo: resp.AppLogo,
40 | })
41 | }
42 |
43 | type appResp struct {
44 | AppID string `json:"app_id,omitempty"`
45 | AppName string `json:"app_name,omitempty"`
46 | AppLogo string `json:"app_logo,omitempty"`
47 | }
48 |
--------------------------------------------------------------------------------
/modules/base/app/const.go:
--------------------------------------------------------------------------------
1 | package app
2 |
3 | // Status app状态
4 | type Status int
5 |
6 | const (
7 | // StatusDisable app被禁用
8 | StatusDisable Status = iota
9 | // StatusEnable app被启用
10 | StatusEnable
11 | )
12 |
13 | func (s Status) Int() int {
14 | return int(s)
15 | }
16 |
17 | func (s Status) String() string {
18 | switch s {
19 | case StatusDisable:
20 | return "disable"
21 | case StatusEnable:
22 | return "enable"
23 | default:
24 | return "unknown"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/modules/base/app/db.go:
--------------------------------------------------------------------------------
1 | package app
2 |
3 | import (
4 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/pkg/db"
5 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/pkg/util"
6 | "github.com/gocraft/dbr/v2"
7 | )
8 |
9 | // DB DB
10 | type DB struct {
11 | session *dbr.Session
12 | }
13 |
14 | func newDB(session *dbr.Session) *DB {
15 | return &DB{
16 | session: session,
17 | }
18 | }
19 |
20 | func (d *DB) queryWithAppID(appID string) (*model, error) {
21 | var m *model
22 | _, err := d.session.Select("*").From("app").Where("app_id=?", appID).Load(&m)
23 | return m, err
24 | }
25 |
26 | func (d *DB) existWithAppID(appID string) (bool, error) {
27 | var count int
28 | _, err := d.session.Select("count(*)").From("app").Where("app_id=?", appID).Load(&count)
29 | return count > 0, err
30 | }
31 |
32 | func (d *DB) insert(m *model) error {
33 | _, err := d.session.InsertInto("app").Columns(util.AttrToUnderscore(m)...).Record(m).Exec()
34 | return err
35 | }
36 |
37 | type model struct {
38 | AppID string
39 | AppKey string
40 | AppName string
41 | AppLogo string
42 | Status int
43 | db.BaseModel
44 | }
45 |
--------------------------------------------------------------------------------
/modules/base/app/service.go:
--------------------------------------------------------------------------------
1 | package app
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "strings"
7 |
8 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/config"
9 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/pkg/util"
10 | )
11 |
12 | // IService 服务接口
13 | type IService interface {
14 | // GetApp 获取app
15 | GetApp(appID string) (*Resp, error)
16 | // 创建app
17 | CreateApp(r Req) (*Resp, error)
18 | }
19 |
20 | // Service app服务
21 | type Service struct {
22 | ctx *config.Context
23 | db *DB
24 | }
25 |
26 | // NewService NewService
27 | func NewService(ctx *config.Context) IService {
28 | return &Service{
29 | ctx: ctx,
30 | db: newDB(ctx.DB()),
31 | }
32 | }
33 |
34 | // GetApp 获取APP信息
35 | func (s *Service) GetApp(appID string) (*Resp, error) {
36 | appM, err := s.db.queryWithAppID(appID)
37 | if err != nil {
38 | return nil, err
39 | }
40 | if appM == nil {
41 | return nil, fmt.Errorf("app[%s]不存在!", appID)
42 | }
43 | return &Resp{
44 | AppID: appM.AppID,
45 | AppName: appM.AppName,
46 | AppLogo: appM.AppLogo,
47 | AppKey: appM.AppKey,
48 | Status: Status(appM.Status),
49 | }, nil
50 | }
51 |
52 | // CreateApp 创建APP 幂等
53 | func (s *Service) CreateApp(r Req) (*Resp, error) {
54 | if err := r.Check(); err != nil {
55 | return nil, err
56 | }
57 | appM, err := s.db.queryWithAppID(r.AppID)
58 | if err != nil {
59 | return nil, err
60 | }
61 |
62 | var appKey string
63 | var appID string
64 | if appM == nil {
65 | appKey = util.GenerUUID()
66 | appID = r.AppID
67 | err = s.db.insert(&model{
68 | AppID: r.AppID,
69 | Status: StatusEnable.Int(),
70 | AppKey: appKey,
71 | })
72 | if err != nil {
73 | return nil, err
74 | }
75 | } else {
76 | appID = appM.AppID
77 | appKey = appM.AppKey
78 | }
79 |
80 | return &Resp{
81 | AppID: appID,
82 | AppKey: appKey,
83 | Status: StatusEnable,
84 | }, nil
85 |
86 | }
87 |
88 | type Resp struct {
89 | AppID string
90 | AppKey string
91 | AppName string
92 | AppLogo string
93 | Status Status
94 | }
95 |
96 | type Req struct {
97 | AppID string
98 | }
99 |
100 | func (r Req) Check() error {
101 | if len(strings.TrimSpace(r.AppID)) <= 0 {
102 | return errors.New("appID不能为空!")
103 | }
104 | return nil
105 | }
106 |
--------------------------------------------------------------------------------
/modules/base/common/const.go:
--------------------------------------------------------------------------------
1 | package common
2 |
3 | // CodeType 验证码类型
4 | type CodeType int
5 |
6 | const (
7 | // CodeTypeRegister 注册
8 | CodeTypeRegister CodeType = iota
9 | // CodeTypePayPWD 支付密码
10 | CodeTypePayPWD
11 | // CodeTypeForgetLoginPWD 忘记登录密码
12 | CodeTypeForgetLoginPWD
13 | // CodeTypeCheckMobile 校验指定手机号是否正确
14 | CodeTypeCheckMobile
15 | // DestroyAccount 注销账号
16 | CodeTypeDestroyAccount
17 | )
18 |
19 | const (
20 | // CacheKeySMSCode 短信验证码的缓存key
21 | CacheKeySMSCode string = "smscode:"
22 | )
23 |
--------------------------------------------------------------------------------
/modules/base/common/service_unisms.go:
--------------------------------------------------------------------------------
1 | package common
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "strings"
7 |
8 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/config"
9 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/pkg/log"
10 | unisms "github.com/apistd/uni-go-sdk/sms"
11 | "go.uber.org/zap"
12 | )
13 |
14 | type UnismsProvider struct {
15 | ctx *config.Context
16 | log.Log
17 | }
18 |
19 | // NewUnismsProvider 创建短信服务
20 | func NewUnismsProvider(ctx *config.Context) ISMSProvider {
21 | return &UnismsProvider{
22 | ctx: ctx,
23 | Log: log.NewTLog("UnismsProvider"),
24 | }
25 | }
26 |
27 | func (u *UnismsProvider) SendSMS(ctx context.Context, zone, phone string, code string) error {
28 | ph := phone
29 | if zone != "0086" {
30 | if len(zone) > 2 {
31 | ph = strings.Replace(zone, "00", "", 1) + phone
32 | }
33 | }
34 |
35 | cli := unisms.NewClient(u.ctx.GetConfig().UniSMS.AccessKeyID, u.ctx.GetConfig().UniSMS.AccessKeySecret)
36 |
37 | // 构建信息
38 | message := unisms.BuildMessage()
39 | message.SetTo(ph)
40 | message.SetSignature(u.ctx.GetConfig().UniSMS.Signature)
41 | message.SetTemplateId(u.ctx.GetConfig().UniSMS.TemplateId)
42 | message.SetTemplateData(map[string]string{"code": code}) // 设置自定义参数 (变量短信)
43 |
44 | // 发送短信
45 | res, err := cli.Send(message)
46 | if err != nil {
47 | u.Error("发送短信失败!", zap.Error(err))
48 | return err
49 | }
50 | if res.Code != "0" {
51 | u.Error("发送短信失败!", zap.String("message", res.Message))
52 | return errors.New(res.Message)
53 | }
54 | return nil
55 | }
56 |
--------------------------------------------------------------------------------
/modules/base/elastic/db.go:
--------------------------------------------------------------------------------
1 | package elastic
2 |
3 | import (
4 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/pkg/db"
5 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/pkg/util"
6 | "github.com/gocraft/dbr/v2"
7 | )
8 |
9 | // DB DB
10 | type DB struct {
11 | session *dbr.Session
12 | }
13 |
14 | // NewDB NewDB
15 | func NewDB(session *dbr.Session) *DB {
16 | return &DB{
17 | session: session,
18 | }
19 | }
20 |
21 | // Insert Insert
22 | func (d *DB) Insert(model *IndexerErrorModel) error {
23 | _, err := d.session.InsertInto("indexer_error").Columns(util.AttrToUnderscore(model)...).Record(model).Exec()
24 | return err
25 | }
26 |
27 | // IndexerErrorModel IndexerErrorModel
28 | type IndexerErrorModel struct {
29 | Index string
30 | Action string
31 | DocumentID string
32 | Body string
33 | Error string
34 | db.BaseModel
35 | }
36 |
--------------------------------------------------------------------------------
/modules/base/elastic/service.go:
--------------------------------------------------------------------------------
1 | package elastic
2 |
3 | import (
4 | "github.com/TangSengDaoDao/TangSengDaoDaoServer/modules/group"
5 | "github.com/TangSengDaoDao/TangSengDaoDaoServer/modules/message"
6 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/common"
7 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/config"
8 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/pkg/log"
9 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/pkg/wkhttp"
10 | "github.com/olivere/elastic"
11 | )
12 |
13 | // Service Service
14 | type Service struct {
15 | ctx *config.Context
16 | log.Log
17 | messageDB *message.DB
18 | db *DB
19 | groupDB *group.DB
20 | }
21 |
22 | // NewService NewService
23 | func NewService(ctx *config.Context) *Service {
24 |
25 | return &Service{
26 | ctx: ctx,
27 | Log: log.NewTLog("Eastic"),
28 | messageDB: message.NewDB(ctx),
29 | db: NewDB(ctx.DB()),
30 | groupDB: group.NewDB(ctx),
31 | }
32 | }
33 |
34 | // Route Route
35 | func (s *Service) Route(r *wkhttp.WKHttp) {
36 |
37 | }
38 |
39 | // PushMessageElasticIndexTask 创建消息索引的任务
40 | func (s *Service) PushMessageElasticIndexTask(resps []msgResp) {
41 | if len(resps) <= 0 {
42 | return
43 | }
44 | for _, resp := range resps {
45 | if resp.ChannelType == common.ChannelTypePerson.Uint8() {
46 | elastic.NewBulkIndexRequest().Index(resp.FromUID).Type("message")
47 | }
48 | }
49 | elastic.NewBulkIndexRequest().Index("message")
50 |
51 | }
52 |
53 | // BulkIndexerItem BulkIndexerItem
54 | type msgResp struct {
55 | MessageID uint64 `json:"message_id"` // 服务端的消息ID(全局唯一)
56 | FromUID string `json:"from_uid"` // 发送者UID
57 | ChannelID string `json:"channel_id"` // 频道ID
58 | ChannelType uint8 `json:"channel_type"` // 频道类型
59 | Timestamp int64 `json:"timestamp"` // 服务器消息时间戳(10位,到秒)
60 | Payload []byte `json:"payload"` // 消息内容
61 | }
62 |
--------------------------------------------------------------------------------
/modules/base/event/db.go:
--------------------------------------------------------------------------------
1 | package event
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/pkg/db"
7 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/pkg/util"
8 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/pkg/wkevent"
9 | "github.com/gocraft/dbr/v2"
10 | )
11 |
12 | // DB 事件的db
13 | type DB struct {
14 | session *dbr.Session
15 | }
16 |
17 | // NewDB 创建DB
18 | func NewDB(session *dbr.Session) *DB {
19 | return &DB{
20 | session: session,
21 | }
22 | }
23 |
24 | // InsertTx 插入事件
25 | func (d *DB) InsertTx(m *Model, tx *dbr.Tx) (int64, error) {
26 | result, err := tx.InsertInto("event").Columns(util.AttrToUnderscore(m)...).Record(m).Exec()
27 | if err != nil {
28 | return 0, err
29 | }
30 | return result.LastInsertId()
31 | }
32 |
33 | // UpdateStatus 更新事件状态
34 | func (d *DB) UpdateStatus(reason string, status int, versionLock int64, id int64) error {
35 | _, err := d.session.Update("event").Set("status", status).Set("reason", reason).Where("id=? and version_lock=?", id, versionLock).Exec()
36 | return err
37 | }
38 |
39 | // QueryWithID 根据id查询事件
40 | func (d *DB) QueryWithID(id int64) (*Model, error) {
41 | var model *Model
42 | _, err := d.session.Select("*").From("event").Where("id=?", id).Load(&model)
43 | return model, err
44 | }
45 |
46 | // QueryAllWait 查询所有等待事件
47 | func (d *DB) QueryAllWait(limit uint64) ([]*Model, error) {
48 | var models []*Model
49 | _, err := d.session.Select("*").From("event").Where("status=? and created_at", wkevent.Wait.Int(), util.ToyyyyMMddHHmmss(time.Now().Add(-time.Second*60))).Limit(limit).Load(&models)
50 | return models, err
51 | }
52 |
53 | // ---------- model ----------
54 |
55 | // Model 数据库对象
56 | type Model struct {
57 | Event string // 事件标示
58 | Type int // 事件类型
59 | Data string // 事件数据
60 | Status int // 事件状态 0.待发布 1.已发布 2.发布失败!
61 | Reason string // 原因 如果状态为2,则有发布失败的原因
62 | VersionLock int64 // 乐观锁
63 | db.BaseModel
64 | }
65 |
--------------------------------------------------------------------------------
/modules/base/sql/app-20201103-01.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Up
2 |
3 |
4 | create table `app`
5 | (
6 | app_id VARCHAR(40) NOT NULL DEFAULT '' COMMENT 'app id',
7 | app_key VARCHAR(40) NOT NULL DEFAULT '' COMMENT 'app key',
8 | status integer NOT NULL DEFAULT 0 COMMENT '状态 0.禁用 1.可用',
9 | created_at timeStamp not null DEFAULT CURRENT_TIMESTAMP,
10 | updated_at timeStamp not null DEFAULT CURRENT_TIMESTAMP
11 | );
12 | CREATE UNIQUE INDEX app_id on `app` (app_id);
13 |
14 | insert into `app`(app_id,app_key,status) VALUES('wukongchat',substring(MD5(RAND()),1,20),1);
--------------------------------------------------------------------------------
/modules/base/sql/app-20230912-01.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Up
2 |
3 | ALTER TABLE `app` ADD COLUMN app_name VARCHAR(40) NOT NULL DEFAULT '' COMMENT 'app名字';
4 |
5 | ALTER TABLE `app` ADD COLUMN app_logo VARCHAR(400) NOT NULL DEFAULT '' COMMENT 'app logo';
--------------------------------------------------------------------------------
/modules/base/sql/event-20191106-01.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Up
2 |
3 | -- 事件表
4 | create table `event`
5 | (
6 | id integer not null primary key AUTO_INCREMENT,
7 | event VARCHAR(40) not null default '', -- 事件标示
8 | `type` smallint not null default 0, -- 事件类型
9 | data VARCHAR(3000) not null default '', -- 事件数据
10 | status smallint NOT NULL DEFAULT 0, -- 事件状态 0.待发布 1.已发布 2.发布失败,
11 | reason VARCHAR(1000) not null default '', -- 失败原因
12 | version_lock integer NOT NULL DEFAULT 0 comment '乐观锁',
13 | created_at timeStamp not null DEFAULT CURRENT_TIMESTAMP, -- 创建时间
14 | updated_at timeStamp not null DEFAULT CURRENT_TIMESTAMP -- 更新时间
15 | );
16 | CREATE INDEX event_key on `event` (event);
17 | CREATE INDEX event_type on `event` (type);
18 |
19 | -- -- +migrate StatementBegin
20 | -- CREATE TRIGGER event_updated_at
21 | -- BEFORE UPDATE
22 | -- ON `event` for each row
23 | -- BEGIN
24 | -- set NEW.updated_at = NOW(),NEW.version_lock = NEW.version_lock + 1;
25 | -- END;
26 | -- -- +migrate StatementEnd
--------------------------------------------------------------------------------
/modules/base/sql/event-20250423-01.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Up
2 |
3 | ALTER TABLE `event` MODIFY COLUMN data VARCHAR(10000) not null default '';
--------------------------------------------------------------------------------
/modules/channel/1module.go:
--------------------------------------------------------------------------------
1 | package channel
2 |
3 | import (
4 | "embed"
5 |
6 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/config"
7 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/pkg/register"
8 | )
9 |
10 | //go:embed sql
11 | var sqlFS embed.FS
12 |
13 | //go:embed swagger/api.yaml
14 | var swaggerContent string
15 |
16 | func init() {
17 | register.AddModule(func(ctx interface{}) register.Module {
18 | return register.Module{
19 | Name: "channel",
20 | SetupAPI: func() register.APIRouter {
21 | return New(ctx.(*config.Context))
22 | },
23 | SQLDir: register.NewSQLFS(sqlFS),
24 | Swagger: swaggerContent,
25 | Service: NewService(ctx.(*config.Context)),
26 | }
27 | })
28 | }
29 |
--------------------------------------------------------------------------------
/modules/channel/db_channel_setting.go:
--------------------------------------------------------------------------------
1 | package channel
2 |
3 | import (
4 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/config"
5 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/pkg/db"
6 | "github.com/gocraft/dbr/v2"
7 | )
8 |
9 | type channelSettingDB struct {
10 | session *dbr.Session
11 | ctx *config.Context
12 | }
13 |
14 | func newChannelSettingDB(ctx *config.Context) *channelSettingDB {
15 | return &channelSettingDB{
16 | session: ctx.DB(),
17 | ctx: ctx,
18 | }
19 | }
20 |
21 | func (c *channelSettingDB) queryWithChannel(channelID string, channelType uint8) (*channelSettingModel, error) {
22 | var m *channelSettingModel
23 | _, err := c.session.Select("*").From("channel_setting").Where("channel_id=? and channel_type=?", channelID, channelType).Load(&m)
24 | return m, err
25 | }
26 |
27 | func (c *channelSettingDB) queryWithChannelIDs(channelIDs []string) ([]*channelSettingModel, error) {
28 | var models []*channelSettingModel
29 | _, err := c.session.Select("*").From("channel_setting").Where("channel_id in ?", channelIDs).Load(&models)
30 | return models, err
31 | }
32 |
33 | func (c *channelSettingDB) insertOrAddMsgAutoDelete(channelID string, channelType uint8, msgAutoDelete int64) error {
34 | _, err := c.session.InsertBySql("insert into channel_setting (channel_id, channel_type, msg_auto_delete) values (?, ?, ?) ON DUPLICATE KEY UPDATE msg_auto_delete=VALUES(msg_auto_delete)", channelID, channelType, msgAutoDelete).Exec()
35 | return err
36 | }
37 |
38 | func (c *channelSettingDB) insertOrAddOffsetMessageSeq(channelID string, channelType uint8, offsetMessageSeq uint32) error {
39 | _, err := c.session.InsertBySql("insert into channel_setting (channel_id, channel_type, offset_message_seq) values (?, ?, ?) ON DUPLICATE KEY UPDATE offset_message_seq=VALUES(offset_message_seq)", channelID, channelType, offsetMessageSeq).Exec()
40 | return err
41 | }
42 |
43 | type channelSettingModel struct {
44 | ChannelID string
45 | ChannelType uint8
46 | ParentChannelID string
47 | ParentChannelType uint8
48 | MsgAutoDelete int64
49 | OffsetMessageSeq uint32
50 | db.BaseModel
51 | }
52 |
--------------------------------------------------------------------------------
/modules/channel/service.go:
--------------------------------------------------------------------------------
1 | package channel
2 |
3 | import (
4 | chservice "github.com/TangSengDaoDao/TangSengDaoDaoServer/modules/channel/service"
5 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/config"
6 | )
7 |
8 | type service struct {
9 | ctx *config.Context
10 | channelSettingDB *channelSettingDB
11 | }
12 |
13 | func NewService(ctx *config.Context) chservice.IService {
14 | return &service{
15 | ctx: ctx,
16 | channelSettingDB: newChannelSettingDB(ctx),
17 | }
18 | }
19 |
20 | func (s *service) GetChannelSettings(channelIDs []string) ([]*chservice.ChannelSettingResp, error) {
21 | if len(channelIDs) == 0 {
22 | return nil, nil
23 | }
24 | channelSettingModels, err := s.channelSettingDB.queryWithChannelIDs(channelIDs)
25 | if err != nil {
26 | return nil, err
27 | }
28 | channelSettingResps := make([]*chservice.ChannelSettingResp, 0, len(channelSettingModels))
29 | if len(channelSettingModels) > 0 {
30 | for _, channelSettingM := range channelSettingModels {
31 | channelSettingResps = append(channelSettingResps, newChannelSettingResp(channelSettingM))
32 | }
33 | }
34 | return channelSettingResps, nil
35 | }
36 |
37 | func (s *service) CreateOrUpdateMsgAutoDelete(channelID string, channelType uint8, msgAutoDelete int64) error {
38 | return s.channelSettingDB.insertOrAddMsgAutoDelete(channelID, channelType, msgAutoDelete)
39 | }
40 |
41 | func newChannelSettingResp(m *channelSettingModel) *chservice.ChannelSettingResp {
42 |
43 | return &chservice.ChannelSettingResp{
44 | ChannelID: m.ChannelID,
45 | ChannelType: m.ChannelType,
46 | ParentChannelID: m.ParentChannelID,
47 | ParentChannelType: m.ParentChannelType,
48 | OffsetMessageSeq: m.OffsetMessageSeq,
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/modules/channel/service/service.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | type IService interface {
4 | // 获取频道设置集合
5 | GetChannelSettings(channelIDs []string) ([]*ChannelSettingResp, error)
6 | // 创建或更新频道消息自动删除时间
7 | CreateOrUpdateMsgAutoDelete(channelID string, channelType uint8, msgAutoDelete int64) error
8 | }
9 |
10 | type ChannelSettingResp struct {
11 | ChannelID string
12 | ChannelType uint8
13 | ParentChannelID string
14 | ParentChannelType uint8
15 | OffsetMessageSeq uint32
16 | }
17 |
--------------------------------------------------------------------------------
/modules/channel/sql/channel-20221124-01.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Up
2 |
3 |
4 | create table `channel_setting`(
5 | id integer not null primary key AUTO_INCREMENT,
6 | channel_id VARCHAR(40) not null default '',
7 | channel_type smallint not null default 0,
8 | parent_channel_id VARCHAR(40) not null default '',
9 | parent_channel_type smallint not null default 0,
10 | created_at timeStamp not null DEFAULT CURRENT_TIMESTAMP, -- 创建时间
11 | updated_at timeStamp not null DEFAULT CURRENT_TIMESTAMP -- 更新时间
12 | );
13 |
14 | CREATE UNIQUE INDEX channel_setting_uidx on `channel_setting` (channel_id,channel_type);
15 |
--------------------------------------------------------------------------------
/modules/channel/sql/channel-20230920-01.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Up
2 |
3 | -- 修改channel_setting表
4 | ALTER TABLE `channel_setting` ADD COLUMN msg_auto_delete integer not null DEFAULT 0 COMMENT '消息定时删除时间';
5 |
6 | -- 修改channel_id字段的长度
7 | ALTER TABLE `channel_setting` MODIFY COLUMN channel_id VARCHAR(80);
8 |
9 |
--------------------------------------------------------------------------------
/modules/channel/sql/channel-20240515-01.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Up
2 |
3 | ALTER TABLE `channel_setting` ADD COLUMN offset_message_seq integer not null DEFAULT 0 COMMENT 'channel消息删除偏移seq';
4 |
--------------------------------------------------------------------------------
/modules/common/1module.go:
--------------------------------------------------------------------------------
1 | package common
2 |
3 | import (
4 | "embed"
5 |
6 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/config"
7 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/pkg/register"
8 | )
9 |
10 | //go:embed sql
11 | var sqlFS embed.FS
12 |
13 | //go:embed swagger/api.yaml
14 | var swaggerContent string
15 |
16 | func init() {
17 | register.AddModule(func(ctx interface{}) register.Module {
18 | return register.Module{
19 | Name: "common",
20 | SetupAPI: func() register.APIRouter {
21 | return New(ctx.(*config.Context))
22 | },
23 | SQLDir: register.NewSQLFS(sqlFS),
24 | Swagger: swaggerContent,
25 | }
26 | })
27 |
28 | register.AddModule(func(ctx interface{}) register.Module {
29 | return register.Module{
30 | SetupAPI: func() register.APIRouter {
31 | return NewManager(ctx.(*config.Context))
32 | },
33 | }
34 | })
35 | }
36 |
--------------------------------------------------------------------------------
/modules/common/api_test.go:
--------------------------------------------------------------------------------
1 | package common
2 |
3 | import (
4 | "bytes"
5 | "net/http"
6 | "net/http/httptest"
7 | "strings"
8 | "testing"
9 |
10 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/pkg/util"
11 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/testutil"
12 | "github.com/stretchr/testify/assert"
13 | )
14 |
15 | func TestAddVersion(t *testing.T) {
16 | s, ctx := testutil.NewTestServer()
17 | f := New(ctx)
18 | f.Route(s.GetRoute())
19 | //清除数据
20 | err := testutil.CleanAllTables(ctx)
21 | assert.NoError(t, err)
22 | w := httptest.NewRecorder()
23 | model := &appVersionReq{
24 | AppVersion: "1.0",
25 | OS: "android",
26 | DownloadURL: "http://www.githubim.com/download/test.apk",
27 | IsForce: 1,
28 | UpdateDesc: "发布新版本",
29 | }
30 | req, _ := http.NewRequest("POST", "/v1/common/appversion", bytes.NewReader([]byte(util.ToJson(model))))
31 | req.Header.Set("token", testutil.Token)
32 | s.GetRoute().ServeHTTP(w, req)
33 | assert.Equal(t, http.StatusOK, w.Code)
34 | }
35 |
36 | func TestGetNewVersion(t *testing.T) {
37 | s, ctx := testutil.NewTestServer()
38 | f := New(ctx)
39 | //清除数据
40 | err := testutil.CleanAllTables(ctx)
41 | assert.NoError(t, err)
42 | _, err = f.db.insertAppVersion(&appVersionModel{
43 | AppVersion: "1.0",
44 | OS: "android",
45 | DownloadURL: "http://www.githubim.com",
46 | IsForce: 1,
47 | UpdateDesc: "发布新版本",
48 | })
49 | assert.NoError(t, err)
50 |
51 | _, err = f.db.insertAppVersion(&appVersionModel{
52 | AppVersion: "1.2",
53 | OS: "android",
54 | DownloadURL: "http://www.githubim.com",
55 | IsForce: 1,
56 | UpdateDesc: "发布新版本",
57 | })
58 | assert.NoError(t, err)
59 |
60 | f.Route(s.GetRoute())
61 | w := httptest.NewRecorder()
62 | req, _ := http.NewRequest("GET", "/v1/common/appversion/android/1.2", nil)
63 | req.Header.Set("token", testutil.Token)
64 | s.GetRoute().ServeHTTP(w, req)
65 | assert.Equal(t, true, strings.Contains(w.Body.String(), `"app_version":1.0`))
66 | }
67 |
68 | func TestGetAppConfig(t *testing.T) {
69 | s, ctx := testutil.NewTestServer()
70 | f := New(ctx)
71 | //清除数据
72 | err := testutil.CleanAllTables(ctx)
73 | assert.NoError(t, err)
74 | err = f.appConfigDB.insert(&appConfigModel{
75 | WelcomeMessage: "欢迎使用唐僧叨叨",
76 | NewUserJoinSystemGroup: 1,
77 | RegisterInviteOn: 1,
78 | InviteSystemAccountJoinGroupOn: 1,
79 | SendWelcomeMessageOn: 1,
80 | })
81 | assert.NoError(t, err)
82 | //f.Route(s.GetRoute())
83 | w := httptest.NewRecorder()
84 | req, _ := http.NewRequest("GET", "/v1/common/appconfig", nil)
85 | req.Header.Set("token", testutil.Token)
86 | s.GetRoute().ServeHTTP(w, req)
87 | assert.Equal(t, true, strings.Contains(w.Body.String(), `"invite_system_account_join_group_on":1`))
88 | }
89 |
--------------------------------------------------------------------------------
/modules/common/db_app_config.go:
--------------------------------------------------------------------------------
1 | package common
2 |
3 | import (
4 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/config"
5 | ldb "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/pkg/db"
6 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/pkg/util"
7 | "github.com/gocraft/dbr/v2"
8 | )
9 |
10 | type appConfigDB struct {
11 | session *dbr.Session
12 | ctx *config.Context
13 | }
14 |
15 | func newAppConfigDB(ctx *config.Context) *appConfigDB {
16 |
17 | return &appConfigDB{
18 | session: ctx.DB(),
19 | ctx: ctx,
20 | }
21 | }
22 |
23 | func (a *appConfigDB) query() (*appConfigModel, error) {
24 | var m *appConfigModel
25 | _, err := a.session.Select("*").From("app_config").OrderDesc("created_at").Load(&m)
26 | return m, err
27 | }
28 |
29 | func (a *appConfigDB) insert(m *appConfigModel) error {
30 | _, err := a.session.InsertInto("app_config").Columns(util.AttrToUnderscore(m)...).Record(m).Exec()
31 | return err
32 | }
33 | func (a *appConfigDB) updateWithMap(configMap map[string]interface{}, id int64) error {
34 | _, err := a.session.Update("app_config").SetMap(configMap).Where("id=?", id).Exec()
35 | return err
36 | }
37 |
38 | type appConfigModel struct {
39 | RSAPrivateKey string
40 | RSAPublicKey string
41 | Version int
42 | SuperToken string
43 | SuperTokenOn int
44 | RevokeSecond int // 消息可撤回时长
45 | WelcomeMessage string // 登录欢迎语
46 | NewUserJoinSystemGroup int // 新用户是否加入系统群聊
47 | SearchByPhone int // 是否可通过手机号搜索
48 | RegisterInviteOn int // 开启注册邀请机制
49 | SendWelcomeMessageOn int // 开启注册登录发送欢迎语
50 | InviteSystemAccountJoinGroupOn int // 开启系统账号加入群聊
51 | RegisterUserMustCompleteInfoOn int // 注册用户是否必须完善个人信息
52 | ChannelPinnedMessageMaxCount int // 频道置顶消息最大数量
53 | CanModifyApiUrl int // 是否可以修改API地址
54 | ldb.BaseModel
55 | }
56 |
--------------------------------------------------------------------------------
/modules/common/db_shortno.go:
--------------------------------------------------------------------------------
1 | package common
2 |
3 | import (
4 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/config"
5 | dbs "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/pkg/db"
6 | "github.com/gocraft/dbr/v2"
7 | )
8 |
9 | type shortnoDB struct {
10 | ctx *config.Context
11 | db *dbr.Session
12 | }
13 |
14 | func newShortnoDB(ctx *config.Context) *shortnoDB {
15 | return &shortnoDB{
16 | ctx: ctx,
17 | db: ctx.DB(),
18 | }
19 | }
20 |
21 | func (s *shortnoDB) inserts(shortnos []string) error {
22 | if len(shortnos) == 0 {
23 | return nil
24 | }
25 | tx, _ := s.db.Begin()
26 | defer func() {
27 | if err := recover(); err != nil {
28 | tx.RollbackUnlessCommitted()
29 | panic(err)
30 | }
31 | }()
32 | for _, st := range shortnos {
33 | _, err := tx.InsertBySql("insert into shortno(shortno) values(?)", st).Exec()
34 | if err != nil {
35 | tx.Rollback()
36 | return err
37 | }
38 | }
39 | if err := tx.Commit(); err != nil {
40 | tx.Rollback()
41 | return err
42 | }
43 | return nil
44 |
45 | }
46 |
47 | func (s *shortnoDB) queryVail() (*shortnoModel, error) {
48 | var m *shortnoModel
49 | _, err := s.db.Select("*").From("shortno").Where("used=0 and hold=0 and locked=0").Limit(1).Load(&m)
50 | return m, err
51 | }
52 |
53 | func (s *shortnoDB) updateLock(shortno string, lock int) error {
54 | _, err := s.db.Update("shortno").Set("locked", lock).Where("shortno=?", shortno).Exec()
55 | return err
56 | }
57 |
58 | func (s *shortnoDB) updateUsed(shortno string, used int, business string) error {
59 | _, err := s.db.Update("shortno").Set("used", used).Set("business", business).Where("shortno=?", shortno).Exec()
60 | return err
61 | }
62 | func (s *shortnoDB) updateHold(shortno string, hold int) error {
63 | _, err := s.db.Update("shortno").Set("hold", hold).Where("shortno=?", shortno).Exec()
64 | return err
65 | }
66 |
67 | func (s *shortnoDB) queryVailCount() (int64, error) {
68 | var cn int64
69 | _, err := s.db.Select("count(*)").From("shortno").Where("used=0 and hold=0 and locked=0").Load(&cn)
70 | return cn, err
71 | }
72 |
73 | type shortnoModel struct {
74 | Shortno string
75 | Used int
76 | Hold int
77 | Locked int
78 | Business string
79 | dbs.BaseModel
80 | }
81 |
--------------------------------------------------------------------------------
/modules/common/sql/common-20210421-01.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Up
2 |
3 | -- app 版本管理
4 | create table `app_version`(
5 | id integer not null primary key AUTO_INCREMENT,
6 | app_version VARCHAR(40) not null default '', -- app 版本
7 | os varchar(40) not null default '', -- 系统 ios|android
8 | is_force smallint not null default 0, -- 是否强制升级
9 | update_desc varchar(100) not null default '', -- 更新说明
10 | download_url varchar(255) not null default '', -- 下载地址
11 | created_at timeStamp not null DEFAULT CURRENT_TIMESTAMP, -- 创建时间
12 | updated_at timeStamp not null DEFAULT CURRENT_TIMESTAMP -- 更新时间
13 | );
14 |
15 |
16 |
--------------------------------------------------------------------------------
/modules/common/sql/common-20210818-01.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Up
2 |
3 | create table `app_config`(
4 | id integer not null primary key AUTO_INCREMENT,
5 | rsa_private_key varchar(4000) not null default '', -- 系统私钥 (使用来加密cmd类消息内容 防止前端模拟发送)
6 | rsa_public_key varchar(4000) not null default '', -- 系统公钥
7 | `version` integer not null default 0, -- 数据版本
8 | super_token varchar(40) not null default '', -- 超级token 用于操作一些系统api的安全校验
9 | super_token_on smallint not null default 0, -- 是否禁用super_token 0.禁用 1.开启 如果禁用 则一些需要super_token的API将不能使用 默认为禁用
10 | created_at timeStamp not null DEFAULT CURRENT_TIMESTAMP, -- 创建时间
11 | updated_at timeStamp not null DEFAULT CURRENT_TIMESTAMP -- 更新时间
12 | );
--------------------------------------------------------------------------------
/modules/common/sql/common-20211108-01.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Up
2 |
3 | create table `seq`(
4 | id integer not null primary key AUTO_INCREMENT,
5 | `key` varchar(100) not null default '', -- seq的key
6 | `min_seq` bigint not null default 1000000, -- 开始序号
7 | step integer not null default 1000, -- 序号步长 每次启动后 当前序号 应该等于min_seq+ step
8 | created_at timeStamp not null DEFAULT CURRENT_TIMESTAMP, -- 创建时间
9 | updated_at timeStamp not null DEFAULT CURRENT_TIMESTAMP -- 更新时间
10 | );
11 | CREATE UNIQUE INDEX `seq_uidx` on `seq` (`key`);
--------------------------------------------------------------------------------
/modules/common/sql/common-20220916-01.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Up
2 |
3 | ALTER TABLE `app_config` ADD COLUMN revoke_second smallint not null DEFAULT 0 COMMENT '消息可撤回时长';
4 | ALTER TABLE `app_config` ADD COLUMN welcome_message varchar(2000) not null DEFAULT '' COMMENT '登录欢迎语';
5 |
--------------------------------------------------------------------------------
/modules/common/sql/common-20220917-01.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Up
2 |
3 | ALTER TABLE `app_config` ADD COLUMN new_user_join_system_group smallint not null DEFAULT 1 COMMENT '注册用户是否默认加入系统群';
4 |
--------------------------------------------------------------------------------
/modules/common/sql/common-20221111-01.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Up
2 |
3 | ALTER TABLE `app_config` ADD COLUMN search_by_phone smallint not null DEFAULT 0 COMMENT '是否可通过手机号搜索';
4 |
--------------------------------------------------------------------------------
/modules/common/sql/common-20221114-01.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Up
2 |
3 | ALTER TABLE `app_version` ADD COLUMN `signature` varchar(1000) not null DEFAULT '' COMMENT '二进制包的签名';
4 |
--------------------------------------------------------------------------------
/modules/common/sql/common-20230203-01.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Up
2 |
3 | -- app 模块管理
4 | create table `app_module`(
5 | id integer not null primary key AUTO_INCREMENT,
6 | sid varchar(40) not null default '', -- 模块ID
7 | name VARCHAR(40) not null default '', -- 模块名称
8 | `desc` varchar(100) not null default '', -- 模块介绍
9 | status smallint not null default 0, -- 模块状态 1.可用 0.不可用
10 | created_at timeStamp not null DEFAULT CURRENT_TIMESTAMP, -- 创建时间
11 | updated_at timeStamp not null DEFAULT CURRENT_TIMESTAMP -- 更新时间
12 | );
13 | CREATE INDEX app_module_sid_idx on `app_module` (sid);
14 |
15 | INSERT INTO `app_module` (sid,name,`desc`,status) VALUES ('base','基础模块','app基础模块,包含基本的文本消息,图片消息,语音消息,名片消息,联系人,用户资料,个人资料,通用设置等等',2);
16 | INSERT INTO `app_module` (sid,name,`desc`,status) VALUES ('login','登录模块','app基础模块,包含用户登录,注册,授权pc/web登录,修改登录密码,还可以在此开发第三方登录等',2);
17 | INSERT INTO `app_module` (sid,name,`desc`,status) VALUES ('scan','扫一扫模块','app基础模块,扫描二维码添加好友,跳转网页等',2);
18 | INSERT INTO `app_module` (sid,name,`desc`,status) VALUES ('advanced','旗舰模块','高级功能,包含消息已读未读,消息点赞,截屏消息,在线状态,查看会话历史消息(非全局历史消息查询)等等',1);
19 | INSERT INTO `app_module` (sid,name,`desc`,status) VALUES ('groupManager','群管理模块','支持群禁言,群管理添加,群头像设置,开启群成员邀请机制等等',1);
20 | INSERT INTO `app_module` (sid,name,`desc`,status) VALUES ('sticker','表情商店','消息可支持矢量表情和GIF动图,用户可制作表情,添加移除表情,排序表情包等',1);
21 | INSERT INTO `app_module` (sid,name,`desc`,status) VALUES ('rich','富文本','聊天中支持富文本消息,包含加粗,图片,强提醒,斜线,变色,下划线,字体颜色等',1);
22 | INSERT INTO `app_module` (sid,name,`desc`,status) VALUES ('video','小视频模块','聊天中支持视频消息,可录视频,发送小视频消息,播放小视频',1);
23 | INSERT INTO `app_module` (sid,name,`desc`,status) VALUES ('map','地图模块','聊天中支持地理位置消息,发朋友圈也支持显示位置信息等',1);
24 | INSERT INTO `app_module` (sid,name,`desc`,status) VALUES ('file','文件模块','添加文件模块可在聊天中发送文件消息,消息搜索支持文件类型筛选,也可查看收到的文件并打开',1);
25 | INSERT INTO `app_module` (sid,name,`desc`,status) VALUES ('rtc','音视频模块','聊天中支持个人音视频通话,群支持会议模式等',1);
26 | INSERT INTO `app_module` (sid,name,`desc`,status) VALUES ('label','标签模块','将好友进行标签分组管理,可在发朋友圈时选择标签用户过滤可见或不可见等',1);
27 | INSERT INTO `app_module` (sid,name,`desc`,status) VALUES ('security','安全与隐私模块','手机号搜索保护,设备锁,黑名单,聊天密码设置,锁屏密码设置,禁止app截屏录屏等等',1);
28 | INSERT INTO `app_module` (sid,name,`desc`,status) VALUES ('customerService','客服','支持客服分配坐席演示',1);
29 | INSERT INTO `app_module` (sid,name,`desc`,status) VALUES ('moment','朋友圈模块','发布朋友圈,查看朋友圈,评论朋友圈,收到评论动态时显示朋友圈消息红点等等',1);
30 | INSERT INTO `app_module` (sid,name,`desc`,status) VALUES ('favorite','收藏模块','可对聊天中的文本消息,图片消息消息进行收藏,查看收藏列表信息等',1);
--------------------------------------------------------------------------------
/modules/common/sql/common-20240418-01.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Up
2 |
3 | ALTER TABLE `app_config` ADD COLUMN register_invite_on smallint not null DEFAULT 0 COMMENT '是否开启注册邀请';
4 | ALTER TABLE `app_config` ADD COLUMN send_welcome_message_on smallint not null DEFAULT 1 COMMENT '是否开启登录欢迎语';
5 | ALTER TABLE `app_config` ADD COLUMN invite_system_account_join_group_on smallint not null DEFAULT 0 COMMENT '是否开启系统账号进入群聊';
6 |
--------------------------------------------------------------------------------
/modules/common/sql/common-20240506-01.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Up
2 |
3 | ALTER TABLE `app_config` ADD COLUMN register_user_must_complete_info_on smallint not null DEFAULT 0 COMMENT '注册用户是否必须完善信息';
4 |
--------------------------------------------------------------------------------
/modules/common/sql/common-20240510-01.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Up
2 |
3 | ALTER TABLE `app_config` ADD COLUMN channel_pinned_message_max_count smallint not null DEFAULT 10 COMMENT '频道最多置顶消息数量';
4 |
--------------------------------------------------------------------------------
/modules/common/sql/common-20240528-01.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Up
2 |
3 | ALTER TABLE `app_config` ADD COLUMN can_modify_api_url smallint not null DEFAULT 0 COMMENT '是否能修改服务器地址';
4 |
--------------------------------------------------------------------------------
/modules/file/1module.go:
--------------------------------------------------------------------------------
1 | package file
2 |
3 | import (
4 | _ "embed"
5 |
6 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/config"
7 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/pkg/register"
8 | )
9 |
10 | //go:embed swagger/api.yaml
11 | var swaggerContent string
12 |
13 | func init() {
14 |
15 | register.AddModule(func(ctx interface{}) register.Module {
16 | return register.Module{
17 | Name: "file",
18 | SetupAPI: func() register.APIRouter {
19 | return New(ctx.(*config.Context))
20 | },
21 | Swagger: swaggerContent,
22 | }
23 | })
24 | }
25 |
--------------------------------------------------------------------------------
/modules/file/const.go:
--------------------------------------------------------------------------------
1 | package file
2 |
3 | // Type 文件类型
4 | type Type string
5 |
6 | const (
7 | // TypeChat 聊天文件
8 | TypeChat Type = "chat"
9 | // TypeMoment 动态文件
10 | TypeMoment Type = "moment"
11 | // TypeMomentCover 动态封面
12 | TypeMomentCover Type = "momentcover"
13 | // TypeSticker 表情
14 | TypeSticker Type = "sticker"
15 | // TypeReport 举报
16 | TypeReport Type = "report"
17 | // TypeCommon 通用
18 | TypeCommon Type = "common"
19 | // TypeChatBg 聊天背景
20 | TypeChatBg Type = "chatbg"
21 | // TypeDownload 下载文件目录
22 | TypeDownload = "download"
23 | // TypeWorkplaceBanner
24 | TypeWorkplaceBanner Type = "workplacebanner"
25 | // TypeWorkplaceAppIcon
26 | TypeWorkplaceAppIcon Type = "workplaceappicon"
27 | )
28 |
--------------------------------------------------------------------------------
/modules/file/service_oss.go:
--------------------------------------------------------------------------------
1 | package file
2 |
3 | import (
4 | "bytes"
5 | "io"
6 | "net/url"
7 |
8 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/config"
9 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/pkg/log"
10 | "github.com/aliyun/aliyun-oss-go-sdk/oss"
11 | "go.uber.org/zap"
12 | )
13 |
14 | type ServiceOSS struct {
15 | log.Log
16 | ctx *config.Context
17 | }
18 |
19 | // NewServiceOSS NewServiceOSS
20 | func NewServiceOSS(ctx *config.Context) *ServiceOSS {
21 |
22 | return &ServiceOSS{
23 | Log: log.NewTLog("ServiceOSS"),
24 | ctx: ctx,
25 | }
26 | }
27 |
28 | // UploadFile 上传文件
29 | func (s *ServiceOSS) UploadFile(filePath string, contentType string, copyFileWriter func(io.Writer) error) (map[string]interface{}, error) {
30 | ossCfg := s.ctx.GetConfig().OSS
31 | client, err := oss.New(ossCfg.Endpoint, ossCfg.AccessKeyID, ossCfg.AccessKeySecret)
32 | if err != nil {
33 | return nil, err
34 | }
35 | bucketName := s.ctx.GetConfig().OSS.BucketName
36 | // strs := strings.Split(filePath, "/")
37 | // if len(strs) > 0 {
38 | // bucketName = strs[0]
39 | // }
40 |
41 | bucket, err := client.Bucket(bucketName)
42 | if err != nil {
43 | return nil, err
44 | }
45 | if bucket == nil {
46 | err = client.CreateBucket(bucketName, oss.ACL(oss.ACLPublicRead))
47 | if err != nil {
48 | return nil, err
49 | }
50 | bucket, err = client.Bucket(bucketName)
51 | if err != nil {
52 | return nil, err
53 | }
54 | }
55 | buff := bytes.NewBuffer(make([]byte, 0))
56 | err = copyFileWriter(buff)
57 | if err != nil {
58 | s.Error("复制文件内容失败!", zap.Error(err))
59 | return nil, err
60 | }
61 | err = bucket.PutObject(filePath, buff, oss.ContentType(contentType), oss.ContentLength(int64(len(buff.Bytes()))))
62 | if err != nil {
63 | return nil, err
64 | }
65 |
66 | return map[string]interface{}{}, nil
67 | }
68 |
69 | func (s *ServiceOSS) DownloadURL(path string, filename string) (string, error) {
70 | ossCfg := s.ctx.GetConfig().OSS
71 |
72 | rpath, _ := url.JoinPath(ossCfg.BucketURL, path)
73 | return rpath, nil
74 | }
75 |
--------------------------------------------------------------------------------
/modules/file/service_oss_test.go:
--------------------------------------------------------------------------------
1 | package file_test
2 |
3 | import (
4 | "bytes"
5 | "io"
6 | "testing"
7 |
8 | "github.com/TangSengDaoDao/TangSengDaoDaoServer/modules/file"
9 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/config"
10 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/testutil"
11 | "github.com/stretchr/testify/assert"
12 | )
13 |
14 | func TestOSSUpload(t *testing.T) {
15 | cfg := config.New()
16 | ctx := testutil.NewTestContext(cfg)
17 | cfg.OSS.Endpoint = "oss-cn-shanghai.aliyuncs.com"
18 | cfg.OSS.AccessKeyID = "xxxx"
19 | cfg.OSS.AccessKeySecret = "xxxxxx"
20 |
21 | service := file.NewServiceOSS(ctx)
22 | _, err := service.UploadFile("chat/zdd/fjj.txt", "*", func(writer io.Writer) error {
23 | _, err := writer.Write(bytes.NewBufferString("this is test content").Bytes())
24 | return err
25 | })
26 | assert.NoError(t, err)
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/modules/file/service_qiniu.go:
--------------------------------------------------------------------------------
1 | package file
2 |
3 | import (
4 | "bytes"
5 | "context"
6 | "fmt"
7 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/config"
8 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/pkg/log"
9 | "github.com/qiniu/go-sdk/v7/auth"
10 | "github.com/qiniu/go-sdk/v7/storage"
11 | "go.uber.org/zap"
12 | "io"
13 | )
14 |
15 | type ServiceQiniu struct {
16 | log.Log
17 | ctx *config.Context
18 | }
19 |
20 | // NewServiceQiniu NewServiceQiniu
21 | func NewServiceQiniu(ctx *config.Context) *ServiceQiniu {
22 |
23 | return &ServiceQiniu{
24 | Log: log.NewTLog("ServiceQiniu"),
25 | ctx: ctx,
26 | }
27 | }
28 |
29 | // UploadFile 上传文件
30 | func (s *ServiceQiniu) UploadFile(filePath string, contentType string, copyFileWriter func(io.Writer) error) (map[string]interface{}, error) {
31 |
32 | qiniuCfg := s.ctx.GetConfig().Qiniu
33 |
34 | bucket := qiniuCfg.BucketName
35 | putPolicy := storage.PutPolicy{
36 | Scope: fmt.Sprintf("%s:%s", bucket, filePath),
37 | }
38 | mac := auth.New(qiniuCfg.AccessKey, qiniuCfg.SecretKey)
39 | upToken := putPolicy.UploadToken(mac)
40 |
41 | cfg := storage.Config{}
42 | // 空间对应的机房
43 | //cfg.Region = &storage.ZoneHuabei
44 | // 是否使用https域名
45 | cfg.UseHTTPS = false
46 | // 上传是否使用CDN上传加速
47 | cfg.UseCdnDomains = false
48 |
49 | formUploader := storage.NewFormUploader(&cfg)
50 | ret := storage.PutRet{}
51 | putExtra := storage.PutExtra{
52 | Params: map[string]string{},
53 | }
54 |
55 | data := bytes.NewBuffer(make([]byte, 0))
56 | err := copyFileWriter(data)
57 | if err != nil {
58 | s.Error("复制文件内容失败!", zap.Error(err))
59 | return nil, err
60 | }
61 | dataLen := int64(len(data.Bytes()))
62 |
63 | err = formUploader.Put(context.Background(), &ret, upToken, filePath, bytes.NewReader(data.Bytes()), dataLen, &putExtra)
64 | if err != nil {
65 | s.Error("上传失败", zap.Error(err))
66 | }
67 | fmt.Println(ret.Key, ret.Hash)
68 | return map[string]interface{}{
69 | "path": ret.Key,
70 | }, err
71 | }
72 |
73 | func (s *ServiceQiniu) DownloadURL(path string, filename string) (string, error) {
74 | qiniuCfg := s.ctx.GetConfig().Qiniu
75 | domain := qiniuCfg.URL
76 | key := path
77 | if key[0:1] == "/" {
78 | key = key[1:]
79 | }
80 | publicAccessURL := storage.MakePublicURL(domain, key)
81 | return publicAccessURL, nil
82 | }
83 |
--------------------------------------------------------------------------------
/modules/file/service_seaweedfs.go:
--------------------------------------------------------------------------------
1 | package file
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "net/url"
7 | "path/filepath"
8 |
9 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/config"
10 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/pkg/log"
11 | "go.uber.org/zap"
12 | )
13 |
14 | type SeaweedFS struct {
15 | log.Log
16 | ctx *config.Context
17 | }
18 |
19 | func NewSeaweedFS(ctx *config.Context) *SeaweedFS {
20 | return &SeaweedFS{
21 | Log: log.NewTLog("SeaweedFS"),
22 | ctx: ctx,
23 | }
24 | }
25 |
26 | // UploadFile 上传文件
27 | func (s *SeaweedFS) UploadFile(filePath string, contentType string, copyFileWriter func(io.Writer) error) (map[string]interface{}, error) {
28 | fileDir, fileName := filepath.Split(filePath)
29 | s.Debug("filePath->", zap.String("filePath", filePath), zap.String("fileDir", fileDir), zap.String("fileName", fileName))
30 | newFileDir := fileDir
31 | if !filepath.IsAbs(fileDir) {
32 | newFileDir = fmt.Sprintf("/%s", newFileDir)
33 | }
34 | seaweedConfig := s.ctx.GetConfig().Seaweed
35 | resultMap, err := uploadFile(fmt.Sprintf("%s%s", seaweedConfig.URL, newFileDir), fileName, copyFileWriter)
36 | return resultMap, err
37 | }
38 |
39 | func (s *SeaweedFS) DownloadURL(path string, filename string) (string, error) {
40 | seaweedConfig := s.ctx.GetConfig().Seaweed
41 | rpath, _ := url.JoinPath(seaweedConfig.URL, path)
42 | return rpath, nil
43 | }
44 |
--------------------------------------------------------------------------------
/modules/file/service_test.go:
--------------------------------------------------------------------------------
1 | package file
2 |
3 | import (
4 | "crypto/sha512"
5 | "encoding/base64"
6 | "image/png"
7 | "io"
8 | "io/ioutil"
9 | "os"
10 | "testing"
11 |
12 | "github.com/stretchr/testify/assert"
13 | )
14 |
15 | func TestMakeCompose(t *testing.T) {
16 | s := &Service{}
17 |
18 | f1, err := os.OpenFile("../../../assets/assets/fileHelper.jpeg", os.O_RDONLY, 0777)
19 | assert.NoError(t, err)
20 |
21 | f2, err := os.OpenFile("../../../assets/assets/u_10000.png", os.O_RDONLY, 0777)
22 | assert.NoError(t, err)
23 |
24 | f3, err := os.OpenFile("../../../assets/assets/u_10000.png", os.O_RDONLY, 0777)
25 | assert.NoError(t, err)
26 |
27 | f4, err := os.OpenFile("../../../assets/assets/u_10000.png", os.O_RDONLY, 0777)
28 | assert.NoError(t, err)
29 |
30 | f5, err := os.OpenFile("../../../assets/assets/u_10000.png", os.O_RDONLY, 0777)
31 | assert.NoError(t, err)
32 |
33 | f6, err := os.OpenFile("../../../assets/assets/u_10000.png", os.O_RDONLY, 0777)
34 | assert.NoError(t, err)
35 |
36 | f7, err := os.OpenFile("../../../assets/assets/u_10000.png", os.O_RDONLY, 0777)
37 | assert.NoError(t, err)
38 |
39 | f8, err := os.OpenFile("../../../assets/assets/u_10000.png", os.O_RDONLY, 0777)
40 | assert.NoError(t, err)
41 |
42 | f9, err := os.OpenFile("../../../assets/assets/u_10000.png", os.O_RDONLY, 0777)
43 | assert.NoError(t, err)
44 |
45 | img, err := s.MakeCompose([]io.ReadCloser{f1, f2, f3, f4, f5, f6, f7, f8, f9})
46 | assert.NoError(t, err)
47 |
48 | result, err := os.OpenFile("test.png", os.O_CREATE|os.O_WRONLY, 0777)
49 | assert.NoError(t, err)
50 | err = png.Encode(result, img)
51 | assert.NoError(t, err)
52 |
53 | }
54 |
55 | func TestUploadPCFile(t *testing.T) {
56 | file, err := os.Open("../../assets/assets/TangSengDaoDao-mac-1.0.5-arm64.zip")
57 | assert.NoError(t, err)
58 | bytes, err := ioutil.ReadAll(file)
59 | assert.NoError(t, err)
60 | hash := sha512.Sum512(bytes)
61 | // hash := sha512.New().Sum(bytes[:1024*1024])
62 | encoded := base64.StdEncoding.EncodeToString(hash[:])
63 | println("编码结果")
64 | println(encoded)
65 | }
66 |
--------------------------------------------------------------------------------
/modules/group/const.go:
--------------------------------------------------------------------------------
1 | package group
2 |
3 | // 群状态
4 | const (
5 | // GroupStatusDisabled 已禁用
6 | GroupStatusDisabled = 0
7 | // GroupStatusNormal 正常
8 | GroupStatusNormal = 1
9 | // GroupStatusDisband 解散
10 | GroupStatusDisband = 2
11 | )
12 |
13 | // 群成员角色
14 | const (
15 | // MemberRoleCommon 普通成员
16 | MemberRoleCommon = 0
17 | // MemberRoleCreator 创建者
18 | MemberRoleCreator = 1
19 | // MemberRoleManager 管理者
20 | MemberRoleManager = 2
21 | )
22 |
23 | const (
24 | // InviteStatusWait 等待确认
25 | InviteStatusWait = 0
26 | // InviteStatusOK 已确认
27 | InviteStatusOK = 1
28 | )
29 |
30 | // 群类型
31 | type GroupType int
32 |
33 | const (
34 | GroupTypeCommon GroupType = iota // 普通群
35 | GroupTypeSuper // 超大群
36 | )
37 |
38 | const (
39 | ChannelServiceName = "channel"
40 | )
41 |
--------------------------------------------------------------------------------
/modules/group/source.go:
--------------------------------------------------------------------------------
1 | package group
2 |
3 | import (
4 | "errors"
5 |
6 | "github.com/TangSengDaoDao/TangSengDaoDaoServer/modules/source"
7 | "go.uber.org/zap"
8 | )
9 |
10 | // GetGroupMemberByVercode 通过vercode获取群成员
11 | func (g *Group) GetGroupMemberByVercode(vercode string) (*source.GroupMember, error) {
12 | if vercode == "" {
13 | return nil, errors.New("vercode不能为空")
14 | }
15 | model, err := g.db.queryMemberWithVercode(vercode)
16 | if err != nil {
17 | g.Error("通过vercode查询群成员错误", zap.Error(err))
18 | return nil, err
19 | }
20 | if model == nil {
21 | return nil, nil
22 | }
23 | return &source.GroupMember{UID: model.UID, GroupNo: model.GroupNo, Vercode: model.Vercode}, nil
24 | }
25 |
26 | func (g *Group) GetGroupMemberByVercodes(vercodes []string) ([]*source.GroupMember, error) {
27 | if len(vercodes) == 0 {
28 | return nil, errors.New("vercodes不能为空")
29 | }
30 | models, err := g.db.queryMemberWithVercodes(vercodes)
31 | if err != nil {
32 | g.Error("通过vercodes查询群成员错误", zap.Error(err))
33 | return nil, err
34 | }
35 | if models == nil {
36 | return nil, nil
37 | }
38 | members := make([]*source.GroupMember, 0, len(models))
39 | for _, model := range models {
40 | members = append(members, &source.GroupMember{
41 | UID: model.UID,
42 | GroupNo: model.GroupNo,
43 | Vercode: model.Vercode,
44 | Name: model.GroupName,
45 | })
46 | }
47 | return members, nil
48 | }
49 |
50 | func (g *Group) GetGroupMemberByUID(uid string, groupNo string) (*source.GroupMember, error) {
51 | if uid == "" {
52 | return nil, errors.New("uid不能为空")
53 | }
54 | if groupNo == "" {
55 | return nil, errors.New("群编号不能为空")
56 | }
57 | model, err := g.db.QueryMemberWithUID(uid, groupNo)
58 | if err != nil {
59 | g.Error("通过用户ID查询群成员错误", zap.Error(err))
60 | return nil, err
61 | }
62 | if model == nil {
63 | return nil, nil
64 | }
65 | return &source.GroupMember{UID: model.UID, GroupNo: model.GroupNo, Vercode: model.Vercode, Role: model.Role}, nil
66 |
67 | }
68 |
69 | // GetGroupByGroupNo 查询群详情
70 | func (g *Group) GetGroupByGroupNo(groupNo string) (*source.GroupModel, error) {
71 | if groupNo == "" {
72 | return nil, errors.New("群ID不能为空")
73 | }
74 | model, err := g.db.QueryWithGroupNo(groupNo)
75 | if err != nil {
76 | g.Error("查询群编号错误", zap.Error(err))
77 | return nil, err
78 | }
79 | if model == nil {
80 | return nil, errors.New("群不存在")
81 | }
82 | return &source.GroupModel{
83 | Name: model.Name,
84 | GroupNo: model.GroupNo,
85 | ForbiddenAddFriend: model.ForbiddenAddFriend,
86 | }, nil
87 | }
88 |
--------------------------------------------------------------------------------
/modules/group/sql/group_20211202-02.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Up
2 |
3 | ALTER TABLE `group_member` ADD COLUMN forbidden_expir_time integer NOT NULL DEFAULT 0 COMMENT '群成员禁言时长';
4 |
--------------------------------------------------------------------------------
/modules/group/sql/group_20220411-01.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Up
2 |
3 | ALTER TABLE `group` ADD COLUMN avatar VARCHAR(255) NOT NULL DEFAULT '' COMMENT '群头像';
4 |
--------------------------------------------------------------------------------
/modules/group/sql/group_20220815-01.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Up
2 |
3 | ALTER TABLE `group_setting` ADD COLUMN `flame` smallint not null default 0 COMMENT '阅后即焚是否开启 1.开启 0.未开启';
4 | ALTER TABLE `group_setting` ADD COLUMN `flame_second` smallint not null default 0 COMMENT '阅后即焚销毁秒数';
--------------------------------------------------------------------------------
/modules/group/sql/group_20220818-01.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Up
2 |
3 | ALTER TABLE `group` ADD COLUMN is_upload_avatar smallint not null DEFAULT 0 COMMENT '群头像是否已经被用户上传';
4 |
--------------------------------------------------------------------------------
/modules/group/sql/group_20220830-01.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Up
2 |
3 | ALTER TABLE `group` ADD COLUMN group_type smallint not null DEFAULT 0 COMMENT '群类型 0.普通群 1.超大群';
4 |
--------------------------------------------------------------------------------
/modules/group/sql/group_20231123-01.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Up
2 |
3 | ALTER TABLE `group` ADD COLUMN category VARCHAR(40) not null DEFAULT 0 COMMENT '群分类';
4 |
--------------------------------------------------------------------------------
/modules/group/sql/group_20240510-01.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Up
2 |
3 | ALTER TABLE `group` ADD COLUMN allow_member_pinned_message smallint not null DEFAULT 0 COMMENT '允许成员置顶聊天消息 0.不允许 1.允许';
4 |
--------------------------------------------------------------------------------
/modules/message/1module.go:
--------------------------------------------------------------------------------
1 | package message
2 |
3 | import (
4 | "embed"
5 |
6 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/config"
7 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/pkg/register"
8 | )
9 |
10 | //go:embed sql
11 | var sqlFS embed.FS
12 |
13 | //go:embed swagger/api.yaml
14 | var swaggerContent string
15 |
16 | //go:embed swagger/conversation.yaml
17 | var conversationSwagger string
18 |
19 | func init() {
20 |
21 | register.AddModule(func(ctx interface{}) register.Module {
22 |
23 | return register.Module{
24 | Name: "message",
25 | SetupAPI: func() register.APIRouter {
26 | return New(ctx.(*config.Context))
27 | },
28 | SQLDir: register.NewSQLFS(sqlFS),
29 | Swagger: swaggerContent,
30 | }
31 | })
32 |
33 | register.AddModule(func(ctx interface{}) register.Module {
34 |
35 | return register.Module{
36 | Name: "conversation",
37 | SetupAPI: func() register.APIRouter {
38 | return NewConversation(ctx.(*config.Context))
39 | },
40 | Swagger: conversationSwagger,
41 | }
42 | })
43 | register.AddModule(func(ctx interface{}) register.Module {
44 |
45 | return register.Module{
46 | SetupAPI: func() register.APIRouter {
47 | return NewManager(ctx.(*config.Context))
48 | },
49 | }
50 | })
51 | }
52 |
--------------------------------------------------------------------------------
/modules/message/api_vo.go:
--------------------------------------------------------------------------------
1 | package message
2 |
3 | import (
4 | "errors"
5 | "strings"
6 | )
7 |
8 | type deleteReq struct {
9 | MessageID string `json:"message_id"`
10 | ChannelID string `json:"channel_id"`
11 | ChannelType uint8 `json:"channel_type"`
12 | MessageSeq uint32 `json:"message_seq"`
13 | }
14 |
15 | func (d *deleteReq) check() error {
16 | if strings.TrimSpace(d.MessageID) == "" {
17 | return errors.New("消息ID不能为空!")
18 | }
19 | if strings.TrimSpace(d.ChannelID) == "" {
20 | return errors.New("频道ID不能为空!")
21 | }
22 | if d.ChannelType == 0 {
23 | return errors.New("频道类型不能为空!")
24 | }
25 | if d.MessageSeq == 0 {
26 | return errors.New("消息序号不能为空!")
27 | }
28 | return nil
29 | }
30 |
31 | type voiceReadedReq struct {
32 | deleteReq
33 | }
34 |
--------------------------------------------------------------------------------
/modules/message/common.go:
--------------------------------------------------------------------------------
1 | package message
2 |
3 |
--------------------------------------------------------------------------------
/modules/message/const.go:
--------------------------------------------------------------------------------
1 | package message
2 |
3 | const (
4 | // 消息已删除
5 | CMDMessageDeleted = "messageDeleted"
6 | // CMDMessageErase 消息擦除
7 | CMDMessageErase = "messageEerase"
8 | sensitiveWordsVersion = 1
9 | )
10 | const CacheReadedCountPrefix = "readedCount:" // 消息已读数量
11 |
12 | type ReminderType int
13 |
14 | const (
15 | ReminderTypeMentionMe = 1 // 有人@我
16 | ReminderTypeApplyJoinGroup = 2 // 申请加群
17 | )
18 |
19 | var sensitive_words = []string{
20 | "银行卡",
21 | "微信",
22 | "qq",
23 | "密码",
24 | "支付宝",
25 | "钱包",
26 | "转账",
27 | "彩票",
28 | "股票",
29 | "人民币",
30 | "RMB",
31 | "兼职",
32 | "网络",
33 | "招聘",
34 | "有意者",
35 | "到货",
36 | "本店",
37 | "代购",
38 | "扣扣",
39 | "微店",
40 | "兼值",
41 | "淘宝",
42 | "小姐",
43 | "妓女",
44 | "包夜",
45 | "3P",
46 | "LY",
47 | "JS",
48 | "狼友",
49 | "技师",
50 | "推油",
51 | "胸推",
52 | "BT",
53 | "毒龙",
54 | "口爆",
55 | "楼凤",
56 | "足交",
57 | "口暴",
58 | "口交",
59 | "全套",
60 | "SM",
61 | "桑拿",
62 | "吞精",
63 | "咪咪",
64 | "婊子",
65 | "乳方",
66 | "操逼",
67 | "全职",
68 | "性伴侣",
69 | "网购",
70 | "网络工作",
71 | "代理",
72 | "专业代理",
73 | "帮忙点一下",
74 | "帮忙点下",
75 | "请点击进入",
76 | "详情请进入",
77 | "私人侦探",
78 | "私家侦探",
79 | "针孔摄象",
80 | "调查婚外情",
81 | "信用卡提现",
82 | "无抵押贷款",
83 | "广告代理",
84 | "原音铃声",
85 | "借腹生子",
86 | "找个妈妈",
87 | "找个爸爸",
88 | "代孕妈妈",
89 | "代生孩子",
90 | "代开发票",
91 | "腾讯客服电话",
92 | "销售热线",
93 | "免费订购热线",
94 | "低价出售",
95 | "款到发货",
96 | "回复可见",
97 | "连锁加盟",
98 | "加盟连锁",
99 | "免费二级域名",
100 | "免费使用",
101 | "免费索取",
102 | "蚁力神",
103 | "婴儿汤",
104 | "售肾",
105 | "刻章办",
106 | "买小车",
107 | "套牌车",
108 | "玛雅网",
109 | "电脑传讯",
110 | "视频来源",
111 | "下载速度",
112 | "高清在线",
113 | "全集在线",
114 | "在线播放",
115 | "txt下载",
116 | "六位qq",
117 | "6位qq",
118 | "位的qq",
119 | "个qb",
120 | "送qb",
121 | "用刀横向切腹",
122 | "完全自杀手册",
123 | "四海帮",
124 | "足球投注",
125 | "地下钱庄",
126 | "中国复兴党",
127 | "阿波罗网",
128 | "曾道人",
129 | "六合彩",
130 | "改卷内幕",
131 | "替考试",
132 | "隐形耳机",
133 | "出售答案",
134 | "考中答案",
135 | "答an",
136 | "da案",
137 | "资金周转",
138 | "救市",
139 | "股市圈钱",
140 | "崩盘",
141 | "资金短缺",
142 | "证监会",
143 | "质押贷款",
144 | "小额贷款",
145 | "周小川",
146 | "刘明康",
147 | "尚福林",
148 | "孔丹",
149 | }
150 |
--------------------------------------------------------------------------------
/modules/message/db_channel_offset.go:
--------------------------------------------------------------------------------
1 | package message
2 |
3 | import (
4 | "fmt"
5 | "hash/crc32"
6 |
7 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/config"
8 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/pkg/db"
9 | "github.com/gocraft/dbr/v2"
10 | )
11 |
12 | type channelOffsetDB struct {
13 | ctx *config.Context
14 | session *dbr.Session
15 | }
16 |
17 | func newChannelOffsetDB(ctx *config.Context) *channelOffsetDB {
18 | return &channelOffsetDB{
19 | ctx: ctx,
20 | session: ctx.DB(),
21 | }
22 | }
23 |
24 | func (c *channelOffsetDB) insertOrUpdate(m *channelOffsetModel) error {
25 | sq := fmt.Sprintf("INSERT INTO %s (uid,channel_id,channel_type,message_seq) VALUES (?,?,?,?) ON DUPLICATE KEY UPDATE message_seq=IF(message_seqbrowse_to,VALUES(browse_to),browse_to),`keep_message_seq`=VALUES(`keep_message_seq`),keep_offset_y=VALUES(keep_offset_y),draft=VALUES(draft),version=VALUES(version)", model.UID, model.ChannelID, model.ChannelType, model.BrowseTo, model.KeepMessageSeq, model.KeepOffsetY, model.Draft, model.Version).Exec()
24 | return err
25 | }
26 |
27 | func (c *conversationExtraDB) sync(uid string, version int64) ([]*conversationExtraModel, error) {
28 | var models []*conversationExtraModel
29 | _, err := c.session.Select("*").From("conversation_extra").Where("uid=? and version>?", uid, version).Load(&models)
30 | return models, err
31 | }
32 |
33 | func (c *conversationExtraDB) queryWithChannelIDs(uid string, channelIDs []string) ([]*conversationExtraModel, error) {
34 | if len(channelIDs) == 0 {
35 | return nil, nil
36 | }
37 | var models []*conversationExtraModel
38 | _, err := c.session.Select("*").From("conversation_extra").Where("uid=? and channel_id in ?", uid, channelIDs).Load(&models)
39 | return models, err
40 | }
41 |
42 | type conversationExtraModel struct {
43 | UID string
44 | ChannelID string
45 | ChannelType uint8
46 | BrowseTo uint32
47 | KeepMessageSeq uint32
48 | KeepOffsetY int
49 | Draft string // 草稿
50 | Version int64
51 | db.BaseModel
52 | }
53 |
--------------------------------------------------------------------------------
/modules/message/db_device_offset.go:
--------------------------------------------------------------------------------
1 | package message
2 |
3 | import (
4 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/pkg/db"
5 | "github.com/gocraft/dbr/v2"
6 | )
7 |
8 | type deviceOffsetDB struct {
9 | session *dbr.Session
10 | }
11 |
12 | func newDeviceOffsetDB(session *dbr.Session) *deviceOffsetDB {
13 | return &deviceOffsetDB{
14 | session: session,
15 | }
16 | }
17 |
18 | func (d *deviceOffsetDB) insertOrUpdateTx(tx *dbr.Tx, model *deviceOffsetModel) error {
19 | sq := "INSERT INTO device_offset (uid,device_uuid,channel_id,channel_type,message_seq) VALUES (?,?,?,?,?) ON DUPLICATE KEY UPDATE message_seq=IF(message_seq?", channelID, channelType, seq).OrderAsc("seq").Limit(limit).Load(&list)
30 | }
31 | return list, err
32 | }
33 |
34 | func (d *messageReactionDB) queryWithMessageIDs(messageIDs []string) ([]*reactionModel, error) {
35 | if len(messageIDs) <= 0 {
36 | return nil, nil
37 | }
38 | var models []*reactionModel
39 | _, err := d.session.Select("*").From("reaction_users").Where("message_id in ?", messageIDs).Load(&models)
40 | return models, err
41 | }
42 |
43 | // 查询某个用户的回应数据
44 | func (d *messageReactionDB) queryReactionWithUIDAndMessageID(uid string, messageID string) (*reactionModel, error) {
45 | var model *reactionModel
46 | _, err := d.session.Select("*").From("reaction_users").Where("uid=? and message_id=?", uid, messageID).Load(&model)
47 | return model, err
48 | }
49 |
50 | // 新增回应
51 | func (d *messageReactionDB) insertReaction(model *reactionModel) error {
52 | _, err := d.session.InsertInto("reaction_users").Columns(util.AttrToUnderscore(model)...).Record(model).Exec()
53 | return err
54 | }
55 |
56 | // 修改某条消息的回应
57 | func (d *messageReactionDB) updateReactionStatus(model *reactionModel) error {
58 | _, err := d.session.Update("reaction_users").SetMap(map[string]interface{}{
59 | "is_deleted": model.IsDeleted,
60 | "seq": model.Seq,
61 | "emoji": model.Emoji,
62 | }).Where("message_id=? and uid=?", model.MessageID, model.UID).Exec()
63 | return err
64 | }
65 |
66 | type reactionModel struct {
67 | MessageID string // 消息唯一ID
68 | Seq int64 // 回复序列号
69 | ChannelID string // 频道唯一ID
70 | ChannelType uint8 // 频道类型
71 | UID string // 用户ID
72 | Name string // 用户名称
73 | Emoji string // 回应表情
74 | IsDeleted int // 是否已删除
75 | db.BaseModel
76 | }
77 |
--------------------------------------------------------------------------------
/modules/message/db_message_user_extra.go:
--------------------------------------------------------------------------------
1 | package message
2 |
3 | import (
4 | "fmt"
5 | "hash/crc32"
6 |
7 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/config"
8 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/pkg/db"
9 | "github.com/gocraft/dbr/v2"
10 | )
11 |
12 | type messageUserExtraDB struct {
13 | ctx *config.Context
14 | session *dbr.Session
15 | }
16 |
17 | func newMessageUserExtraDB(ctx *config.Context) *messageUserExtraDB {
18 | return &messageUserExtraDB{
19 | ctx: ctx,
20 | session: ctx.DB(),
21 | }
22 | }
23 |
24 | func (m *messageUserExtraDB) insertOrUpdateDeletedTx(md *messageUserExtraModel, tx *dbr.Tx) error {
25 | sq := fmt.Sprintf("INSERT INTO %s (uid,message_id,message_seq,channel_id,channel_type,message_is_deleted) VALUES (?,?,?,?,?,?) ON DUPLICATE KEY UPDATE message_is_deleted=VALUES(message_is_deleted)", m.getTable(md.UID))
26 | _, err := tx.InsertBySql(sq, md.UID, md.MessageID, md.MessageSeq, md.ChannelID, md.ChannelType, md.MessageIsDeleted).Exec()
27 | return err
28 | }
29 |
30 | // 插入或更新消息语音已读状态
31 | func (m *messageUserExtraDB) insertOrUpdateVoiceRead(md *messageUserExtraModel) error {
32 | sq := fmt.Sprintf("INSERT INTO %s (uid,message_id,message_seq,channel_id,channel_type,voice_readed) VALUES (?,?,?,?,?,?) ON DUPLICATE KEY UPDATE voice_readed=VALUES(voice_readed)", m.getTable(md.UID))
33 | _, err := m.session.InsertBySql(sq, md.UID, md.MessageID, md.MessageSeq, md.ChannelID, md.ChannelType, md.VoiceReaded).Exec()
34 | return err
35 | }
36 |
37 | // 通过消息id集合和消息拥有者uid查询编辑消息
38 | func (m *messageUserExtraDB) queryWithMessageIDsAndUID(messageIDs []string, uid string) ([]*messageUserExtraModel, error) {
39 | if len(messageIDs) == 0 {
40 | return nil, nil
41 | }
42 | var models []*messageUserExtraModel
43 | _, err := m.session.Select("*").From(m.getTable(uid)).Where("uid=? and message_id in ?", uid, messageIDs).Load(&models)
44 | return models, err
45 | }
46 |
47 | func (m *messageUserExtraDB) getTable(uid string) string {
48 | tableIndex := crc32.ChecksumIEEE([]byte(uid)) % uint32(m.ctx.GetConfig().TablePartitionConfig.MessageUserEditTableCount)
49 | if tableIndex == 0 {
50 | return "message_user_extra"
51 | }
52 | return fmt.Sprintf("message_user_extra%d", tableIndex)
53 | }
54 |
55 | type messageUserExtraModel struct {
56 | UID string
57 | MessageID string
58 | MessageSeq uint32
59 | ChannelID string
60 | ChannelType uint8
61 | VoiceReaded int
62 | MessageIsDeleted int
63 | db.BaseModel
64 | }
65 |
--------------------------------------------------------------------------------
/modules/message/db_user_last_offset.go:
--------------------------------------------------------------------------------
1 | package message
2 |
3 | import (
4 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/config"
5 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/pkg/db"
6 | "github.com/gocraft/dbr/v2"
7 | )
8 |
9 | type userLastOffsetDB struct {
10 | ctx *config.Context
11 | session *dbr.Session
12 | }
13 |
14 | func newUserLastOffsetDB(ctx *config.Context) *userLastOffsetDB {
15 |
16 | return &userLastOffsetDB{
17 | ctx: ctx,
18 | session: ctx.DB(),
19 | }
20 | }
21 |
22 | func (d *userLastOffsetDB) insertOrUpdateTx(tx *dbr.Tx, model *userLastOffsetModel) error {
23 | sq := "INSERT INTO user_last_offset (uid,channel_id,channel_type,message_seq) VALUES (?,?,?,?) ON DUPLICATE KEY UPDATE message_seq=IF(message_seq 0 {
58 | for index, device := range devices {
59 | var selft int
60 | if index == 0 {
61 | selft = 1
62 | }
63 | deviceName := device.DeviceName
64 | if selft == 1 {
65 | deviceName = fmt.Sprintf("%s(本机)", device.DeviceName)
66 | }
67 | deviceResps = append(deviceResps, deviceResp{
68 | ID: device.Id,
69 | DeviceID: device.DeviceID,
70 | DeviceName: deviceName,
71 | DeviceModel: device.DeviceModel,
72 | Self: selft,
73 | LastLogin: util.ToyyyyMMddHHmm(time.Unix(device.LastLogin, 0)),
74 | })
75 | }
76 | }
77 | c.Response(deviceResps)
78 | }
79 |
80 | type deviceResp struct {
81 | ID int64 `json:"id"`
82 | DeviceID string `json:"device_id"` // 设备ID
83 | DeviceName string `json:"device_name"` // 设备名称
84 | DeviceModel string `json:"device_model"` // 设备型号
85 | LastLogin string `json:"last_login"` // 设备最后一次登录时间
86 | Self int `json:"self"` // 是否是本机
87 | }
88 |
--------------------------------------------------------------------------------
/modules/user/api_login_log.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import (
4 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/config"
5 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/pkg/log"
6 | "go.uber.org/zap"
7 | )
8 |
9 | // LoginLog 用户设置
10 | type LoginLog struct {
11 | ctx *config.Context
12 | log.Log
13 | loginLogDB *LoginLogDB
14 | }
15 |
16 | // NewLoginLog 创建
17 | func NewLoginLog(ctx *config.Context) *LoginLog {
18 | return &LoginLog{ctx: ctx, Log: log.NewTLog("loginLog"), loginLogDB: NewLoginLogDB(ctx.DB())}
19 | }
20 |
21 | // add 添加登录日志
22 | func (l *LoginLog) add(uid string, publicIP string) {
23 | err := l.loginLogDB.insert(&LoginLogModel{
24 | UID: uid,
25 | LoginIP: publicIP,
26 | })
27 | if err != nil {
28 | l.Error("添加登录日志错误", zap.Error(err))
29 | }
30 | }
31 |
32 | // getLastLoginIp 获取最后一次登录ip
33 | func (l *LoginLog) getLastLoginIP(uid string) *loginLogResp {
34 | model, err := l.loginLogDB.queryLastLoginIP(uid)
35 | if err != nil {
36 | l.Error("查询登录日志错误", zap.Error(err))
37 | return nil
38 | }
39 | if model != nil {
40 | return &loginLogResp{
41 | UID: model.UID,
42 | CreateAt: model.CreatedAt.String(),
43 | LoginIP: model.LoginIP,
44 | }
45 | }
46 | return nil
47 | }
48 |
49 | // loginLogResp 登录日志
50 | type loginLogResp struct {
51 | UID string
52 | CreateAt string
53 | LoginIP string
54 | }
55 |
--------------------------------------------------------------------------------
/modules/user/const.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | const (
4 | // Web3VerifyLogin 校验登录
5 | Web3VerifyLogin string = "login"
6 | // Web3VerifyPassword 校验密码
7 | Web3VerifyPassword string = "password"
8 | )
9 |
10 | // Status 状态
11 | type Status int
12 |
13 | const (
14 | // StatusDisable 禁用
15 | StatusDisable Status = iota
16 | // StatusEnable 启用
17 | StatusEnable
18 | )
19 | const (
20 | // CacheKeyFriends 好友key
21 | CacheKeyFriends string = "lm-friends:"
22 | )
23 |
24 | // Int Int
25 | func (s Status) Int() int {
26 | return int(s)
27 | }
28 |
29 | type Category string
30 |
31 | const (
32 | // CategoryCustomerService 客服
33 | CategoryCustomerService = "customerService"
34 | // CategorySystem 系统账号
35 | CategorySystem = "system"
36 | )
37 |
38 | // Names 注册用户随机名字
39 | var Names = []string{"龚都", "黄祖", "黄祖", "黄皓", "黄琬", "黄歇", "黄权", "公孙瓒",
40 | "袁绍", "张角", "李儒", "高顺", "马腾", "文丑", "华雄", "颜良", "华佗",
41 | "左慈", "貂蝉", "司马徽", "蔡文姬", "胡车儿", "逢纪", "纪灵", "张绣", "孔融", "张鲁",
42 | "韩遂", "张燕", "张曼成", "审配", "黄甫嵩", "张梁", "张任", "马铁", "沪指", "辟暑大王",
43 | "辟尘大王", "玄鹤老", "玉兔精", "蠹妖", "蛙怪", "麋妖", "古柏老", "灵龟老", "峰五老", "赤蛇精", "虺妖", "蚖妖",
44 | "蝮子怪", "蝎小妖", "狐妖", "凤管娘子", "鸾萧夫人", "七情大王", "六欲大王", "三尸魔王", "阴沉魔王", "独角魔王",
45 | "啸风魔王", "兴云魔王", "六耳魔王", "迷识魔王", "消阳魔王", "铄阴魔王", "耗气魔王", "黑鱼精", "蜂妖", "灵鹊",
46 | "玄武灵", "美蔚君", "福缘君", "善庆君", "孟浪魔王", "慌张魔王", "司视魔", "司听魔", "逐香魔", "具体魔", "驰神魔",
47 | "逐味魔", "千里眼", "顺风耳", "金童", "玉女", "雷公", "电母", "风伯", "雨师", "游奕灵官", "翊圣真君", "大力鬼王",
48 | "七仙女", "太白金星", "赤脚大仙", "嫦娥", "玉兔", "吴刚", "猪八戒", "孙悟空", "唐僧", "沙悟净", "白龙马", "九天玄女",
49 | "九曜星", "日游神", "夜游神", "太阴星君", "太阳星君", "武德星君", "佑圣真君", "李靖", "金吒", "木吒", "哪吒",
50 | "巨灵神", "月老", "左辅右弼", "二郎神杨戬", "萨真人", "文昌帝君", "增长天王", "持国天王", "多闻天王", "广目天王",
51 | "张道陵", "许逊", "邱弘济", "葛洪", "渔人", "林黛玉", "薛宝钗", "贾宝玉", "秦可卿", "贾巧姐", "王熙凤", "史湘云",
52 | "妙玉", "李纨", "贾惜春", "贾探春", "贾迎春", "贾元春", "王妈妈", "西门庆", "武松", "武大郎", "宋江", "鲁智深",
53 | "高俅", "闻太师", "卢俊义", "吴用", "公孙胜", "关胜", "林冲", "秦明", "呼延灼", "花荣", "阮小七", "燕青",
54 | "皇甫端", "扈三娘", "王英", "安道全", "金大坚", "萧峰", "段誉", "童猛", "陶宗旺", "郑天寿", "王定六", "段景住",
55 | "寅将军", "黑熊精", "白衣秀士", "凌虚子", "黄风怪", "白骨精", "奎木狼", "金角大王", "银角大王",
56 | }
57 |
58 | const (
59 | ChannelServiceName = "channel"
60 | )
61 |
62 | const (
63 | UserRedDotCategoryFriendApply = "friendApply"
64 | )
65 |
--------------------------------------------------------------------------------
/modules/user/db_device_flag.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import (
4 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/config"
5 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/pkg/db"
6 | "github.com/gocraft/dbr/v2"
7 | )
8 |
9 | type deviceFlagDB struct {
10 | session *dbr.Session
11 | ctx *config.Context
12 | }
13 |
14 | func newDeviceFlagDB(ctx *config.Context) *deviceFlagDB {
15 | return &deviceFlagDB{
16 | session: ctx.DB(),
17 | ctx: ctx,
18 | }
19 | }
20 |
21 | func (d *deviceFlagDB) queryAll() ([]*deviceFlagModel, error) {
22 | var deviceFlags []*deviceFlagModel
23 | _, err := d.session.Select("*").From("device_flag").Load(&deviceFlags)
24 | return deviceFlags, err
25 | }
26 |
27 | type deviceFlagModel struct {
28 | DeviceFlag uint8
29 | Weight int
30 | Remark string
31 | db.BaseModel
32 | }
33 |
--------------------------------------------------------------------------------
/modules/user/db_gitee.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import (
4 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/config"
5 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/pkg/db"
6 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/pkg/util"
7 | "github.com/gocraft/dbr/v2"
8 | )
9 |
10 | type giteeDB struct {
11 | session *dbr.Session
12 | ctx *config.Context
13 | }
14 |
15 | func newGiteeDB(ctx *config.Context) *giteeDB {
16 |
17 | return &giteeDB{
18 | ctx: ctx,
19 | session: ctx.DB(),
20 | }
21 | }
22 |
23 | func (d *giteeDB) insert(m *gitUserInfoModel) error {
24 | _, err := d.session.InsertInto("gitee_user").Columns(util.AttrToUnderscore(m)...).Record(m).Exec()
25 | return err
26 | }
27 |
28 | func (d *giteeDB) insertTx(m *gitUserInfoModel, tx *dbr.Tx) error {
29 | _, err := tx.InsertInto("gitee_user").Columns(util.AttrToUnderscore(m)...).Record(m).Exec()
30 | return err
31 | }
32 | func (d *giteeDB) queryWithLogin(login string) (*gitUserInfoModel, error) {
33 | var m *gitUserInfoModel
34 | _, err := d.session.Select("*").From("gitee_user").Where("login=?", login).Load(&m)
35 | return m, err
36 | }
37 |
38 | type gitUserInfoModel struct {
39 | Id int64
40 | CreatedAt db.Time
41 | UpdatedAt db.Time
42 | Login string
43 | Name string
44 | Email string
45 | Bio string
46 | AvatarURL string
47 | Blog string
48 | EventsURL string
49 | Followers int
50 | FollowersURL string
51 | Following int
52 | FollowingURL string
53 | GistsURL string
54 | HtmlURL string
55 | MemberRole string
56 | OrganizationsURL string
57 | PublicGists int
58 | PublicRepos int
59 | ReceivedEventsURL string
60 | Remark string
61 | ReposURL string
62 | Stared int
63 | StarredURL string
64 | SubscriptionsURL string
65 | URL string
66 | Watched int
67 | Weibo string
68 | Type string
69 | GiteeCreatedAt string
70 | GiteeUpdatedAt string
71 | }
72 |
--------------------------------------------------------------------------------
/modules/user/db_github.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import (
4 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/config"
5 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/pkg/db"
6 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/pkg/util"
7 | "github.com/gocraft/dbr/v2"
8 | )
9 |
10 | type githubDB struct {
11 | session *dbr.Session
12 | ctx *config.Context
13 | }
14 |
15 | func newGithubDB(ctx *config.Context) *githubDB {
16 |
17 | return &githubDB{
18 | ctx: ctx,
19 | session: ctx.DB(),
20 | }
21 | }
22 |
23 | func (d *githubDB) insert(m *githubUserInfoModel) error {
24 | _, err := d.session.InsertInto("github_user").Columns(util.AttrToUnderscore(m)...).Record(m).Exec()
25 | return err
26 | }
27 |
28 | func (d *githubDB) insertTx(m *githubUserInfoModel, tx *dbr.Tx) error {
29 | _, err := tx.InsertInto("github_user").Columns(util.AttrToUnderscore(m)...).Record(m).Exec()
30 | return err
31 | }
32 | func (d *githubDB) queryWithLogin(login string) (*githubUserInfoModel, error) {
33 | var m *githubUserInfoModel
34 | _, err := d.session.Select("*").From("github_user").Where("login=?", login).Load(&m)
35 | return m, err
36 | }
37 |
38 | type githubUserInfoModel struct {
39 | ID int64
40 | CreatedAt db.Time
41 | UpdatedAt db.Time
42 | Login string
43 | NodeID string
44 | AvatarURL string
45 | GravatarID string
46 | URL string
47 | HtmlUrl string
48 | FollowersURL string
49 | FollowingURL string
50 | GistsURL string
51 | StarredURL string
52 | SubscriptionsURL string
53 | OrganizationsURL string
54 | ReposURL string
55 | EventsURL string
56 | ReceivedEventsURL string
57 | Type string
58 | SiteAdmin bool
59 | Name string
60 | Company string
61 | Blog string
62 | Location string
63 | Email string
64 | Hireable bool
65 | Bio string
66 | TwitterUsername string
67 | PublicRepos int
68 | PublicGists int
69 | Followers int
70 | Following int
71 | GithubCreatedAt string
72 | GithubUpdatedAt string
73 | PrivateGists int
74 | TotalPrivateRepos int
75 | OwnedPrivateRepos int
76 | DiskUsage int
77 | Collaborators int
78 | TwoFactorAuthentication bool
79 | }
80 |
--------------------------------------------------------------------------------
/modules/user/db_identities.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import (
4 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/config"
5 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/pkg/db"
6 | "github.com/gocraft/dbr/v2"
7 | )
8 |
9 | type identitieDB struct {
10 | session *dbr.Session
11 | ctx *config.Context
12 | }
13 |
14 | func newIdentitieDB(ctx *config.Context) *identitieDB {
15 | return &identitieDB{
16 | session: ctx.DB(),
17 | ctx: ctx,
18 | }
19 | }
20 |
21 | func (i *identitieDB) saveOrUpdateTx(m *identitiesModel, tx *dbr.Tx) error {
22 | _, err := tx.InsertBySql("insert into signal_identities(uid,identity_key,signed_prekey_id,signed_pubkey,signed_signature,registration_id) values(?,?,?,?,?,?) ON DUPLICATE KEY UPDATE identity_key=identity_key,signed_prekey_id=signed_prekey_id,signed_pubkey=signed_pubkey,signed_signature=signed_signature,registration_id=registration_id", m.UID, m.IdentityKey, m.SignedPrekeyID, m.SignedPubkey, m.SignedSignature, m.RegistrationID).Exec()
23 | return err
24 | }
25 |
26 | func (i *identitieDB) deleteWithUID(uid string) error {
27 | _, err := i.session.DeleteFrom("signal_identities").Where("uid=?", uid).Exec()
28 | return err
29 | }
30 |
31 | func (i *identitieDB) queryWithUID(uid string) (*identitiesModel, error) {
32 | var model *identitiesModel
33 | _, err := i.session.Select("*").From("signal_identities").Where("uid=?", uid).Load(&model)
34 | return model, err
35 | }
36 |
37 | type identitiesModel struct {
38 | UID string
39 | RegistrationID uint32
40 | IdentityKey string
41 | SignedPrekeyID int
42 | SignedPubkey string
43 | SignedSignature string
44 | db.BaseModel
45 | }
46 |
--------------------------------------------------------------------------------
/modules/user/db_login_log.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import (
4 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/pkg/db"
5 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/pkg/util"
6 | "github.com/gocraft/dbr/v2"
7 | )
8 |
9 | // LoginLogDB 登录日志DB
10 | type LoginLogDB struct {
11 | session *dbr.Session
12 | }
13 |
14 | // NewLoginLogDB NewDB
15 | func NewLoginLogDB(session *dbr.Session) *LoginLogDB {
16 | return &LoginLogDB{
17 | session: session,
18 | }
19 | }
20 |
21 | // insert 添加登录日志
22 | func (l *LoginLogDB) insert(m *LoginLogModel) error {
23 | _, err := l.session.InsertInto("login_log").Columns(util.AttrToUnderscore(m)...).Record(m).Exec()
24 | return err
25 | }
26 |
27 | // queryLastLoginIP 查询最后一次登录日志
28 | func (l *LoginLogDB) queryLastLoginIP(uid string) (*LoginLogModel, error) {
29 | var model *LoginLogModel
30 | _, err := l.session.Select("*").From("login_log").Where("uid=?", uid).OrderDir("created_at", false).Limit(1).Load(&model)
31 | if err != nil {
32 | return nil, err
33 | }
34 | return model, nil
35 | }
36 |
37 | // LoginLogModel 登录日志
38 | type LoginLogModel struct {
39 | LoginIP string //登录IP
40 | UID string
41 | db.BaseModel
42 | }
43 |
--------------------------------------------------------------------------------
/modules/user/db_maillist.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import (
4 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/config"
5 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/pkg/db"
6 | "github.com/gocraft/dbr/v2"
7 | )
8 |
9 | type maillistDB struct {
10 | session *dbr.Session
11 | ctx *config.Context
12 | }
13 |
14 | func newMaillistDB(ctx *config.Context) *maillistDB {
15 | return &maillistDB{
16 | ctx: ctx,
17 | session: ctx.DB(),
18 | }
19 | }
20 |
21 | func (d *maillistDB) insertTx(m *maillistModel, tx *dbr.Tx) error {
22 | _, err := tx.InsertBySql("INSERT INTO user_maillist (zone,phone,name,vercode,uid) VALUES (?,?,?,?,?) ON DUPLICATE KEY UPDATE `phone`=VALUES(`phone`)", m.Zone, m.Phone, m.Name, m.Vercode, m.UID).Exec()
23 | return err
24 | }
25 |
26 | func (d *maillistDB) queryWitchVercode(vercode string) (*maillistModel, error) {
27 | var model *maillistModel
28 | _, err := d.session.Select("*").From("user_maillist").Where("vercode=? ", vercode).Load(&model)
29 | return model, err
30 | }
31 | func (d *maillistDB) query(uid string) ([]*maillistModel, error) {
32 | var models []*maillistModel
33 | _, err := d.session.Select("*").From("user_maillist").Where("uid=?", uid).Load(&models)
34 | return models, err
35 | }
36 |
37 | type maillistModel struct {
38 | UID string
39 | Phone string
40 | Zone string
41 | Name string
42 | Vercode string
43 | db.BaseModel
44 | }
45 |
--------------------------------------------------------------------------------
/modules/user/db_onetime_prekeys.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import (
4 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/config"
5 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/pkg/db"
6 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/pkg/util"
7 | "github.com/gocraft/dbr/v2"
8 | )
9 |
10 | type onetimePrekeysDB struct {
11 | session *dbr.Session
12 | ctx *config.Context
13 | }
14 |
15 | func newOnetimePrekeysDB(ctx *config.Context) *onetimePrekeysDB {
16 | return &onetimePrekeysDB{
17 | session: ctx.DB(),
18 | ctx: ctx,
19 | }
20 | }
21 |
22 | func (o *onetimePrekeysDB) insertTx(m *onetimePrekeysModel, tx *dbr.Tx) error {
23 | _, err := tx.InsertInto("signal_onetime_prekeys").Columns(util.AttrToUnderscore(m)...).Record(m).Exec()
24 | return err
25 | }
26 |
27 | func (o *onetimePrekeysDB) delete(uid string, keyID int) error {
28 | _, err := o.session.DeleteFrom("signal_onetime_prekeys").Where("uid=? and key_id=?", uid, keyID).Exec()
29 | return err
30 | }
31 |
32 | func (o *onetimePrekeysDB) deleteWithUID(uid string) error {
33 | _, err := o.session.DeleteFrom("signal_onetime_prekeys").Where("uid=?", uid).Exec()
34 | return err
35 | }
36 |
37 | // 查询用户最小的onetimePreKey
38 | func (o *onetimePrekeysDB) queryMinWithUID(uid string) (*onetimePrekeysModel, error) {
39 | var m *onetimePrekeysModel
40 | _, err := o.session.Select("*").From("signal_onetime_prekeys").Where("uid=?", uid).OrderAsc("key_id").Limit(1).Load(&m)
41 | return m, err
42 | }
43 |
44 | func (o *onetimePrekeysDB) queryCount(uid string) (int, error) {
45 | var cn int
46 | err := o.session.Select("count(*)").From("signal_onetime_prekeys").Where("uid=?", uid).LoadOne(&cn)
47 | return cn, err
48 | }
49 |
50 | type onetimePrekeysModel struct {
51 | UID string
52 | KeyID int
53 | Pubkey string
54 | db.BaseModel
55 | }
56 |
--------------------------------------------------------------------------------
/modules/user/sql/user-20210204-01.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Up
2 |
3 | ALTER TABLE `user` ADD COLUMN app_id VARCHAR(40) NOT NULL DEFAULT '' COMMENT 'app id';
--------------------------------------------------------------------------------
/modules/user/sql/user-20210405-01.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Up
2 |
3 | ALTER TABLE `user` ADD COLUMN email VARCHAR(100) NOT NULL DEFAULT '' COMMENT 'email地址';
--------------------------------------------------------------------------------
/modules/user/sql/user-20210413-01.sql:
--------------------------------------------------------------------------------
1 |
2 |
3 | -- +migrate Up
4 |
5 | -- 消息表
6 | create table `user_online`
7 | (
8 | id bigint not null primary key AUTO_INCREMENT,
9 | uid VARCHAR(40) not null default '', -- 用户uid
10 | device_flag smallint not null default 0, -- 设备flag 0.APP 1. WEB
11 | last_online integer not null DEFAULT 0, -- 最后一次在线时间
12 | last_offline integer not null DEFAULT 0, -- 最后一次离线时间
13 | online tinyint(1) not null default 0, -- 用户是否在线
14 | `version` bigint not null default 0, -- 数据版本
15 | created_at timeStamp not null DEFAULT CURRENT_TIMESTAMP, -- 创建时间
16 | updated_at timeStamp not null DEFAULT CURRENT_TIMESTAMP -- 更新时间
17 | );
18 |
19 | CREATE UNIQUE INDEX `uid_device` on `user_online` (`uid`,device_flag);
20 |
21 | CREATE INDEX `online_idx` on `user_online` (`online`);
22 | CREATE INDEX `uid_idx` on `user_online` (`uid`);
--------------------------------------------------------------------------------
/modules/user/sql/user-20210907-01.sql:
--------------------------------------------------------------------------------
1 |
2 |
3 | -- +migrate Up
4 |
5 | -- 用户身份表 (signal protocol使用)
6 | create table `signal_identities`
7 | (
8 | id bigint not null primary key AUTO_INCREMENT,
9 | uid varchar(40) not null DEFAULT '', -- 用户uid
10 | registration_id bigint not null DEFAULT 0, -- 身份ID
11 | identity_key text not null, -- 用户身份公钥
12 | signed_prekey_id integer not null DEFAULT 0, -- 签名key的id
13 | signed_pubkey text not null, -- 签名key的公钥
14 | signed_signature text not null, -- 由身份密钥签名的signed_pubkey
15 | created_at timeStamp not null DEFAULT CURRENT_TIMESTAMP, -- 创建时间
16 | updated_at timeStamp not null DEFAULT CURRENT_TIMESTAMP -- 更新时间
17 | );
18 |
19 | CREATE UNIQUE INDEX identities_index_id ON signal_identities(uid);
20 |
21 |
22 | -- 一次性公钥
23 | create table `signal_onetime_prekeys`
24 | (
25 | id bigint not null primary key AUTO_INCREMENT,
26 | uid varchar(40) not null DEFAULT '', -- 用户uid
27 | key_id integer not null DEFAULT 0,
28 | pubkey text not null, -- 公钥
29 | created_at timeStamp not null DEFAULT CURRENT_TIMESTAMP, -- 创建时间
30 | updated_at timeStamp not null DEFAULT CURRENT_TIMESTAMP -- 更新时间
31 | );
32 | CREATE UNIQUE INDEX key_id_uid_index_id ON signal_onetime_prekeys(uid,key_id);
--------------------------------------------------------------------------------
/modules/user/sql/user-20210916-01.sql:
--------------------------------------------------------------------------------
1 |
2 |
3 | -- +migrate Up
4 |
5 | -- 手机联系人
6 | create table `user_maillist`
7 | (
8 | id bigint not null primary key AUTO_INCREMENT,
9 | uid VARCHAR(40) not null default '', -- 用户uid
10 | phone VARCHAR(40) not null default '', -- 手机号
11 | zone VARCHAR(40) not null default '', -- 区号
12 | name VARCHAR(40) not null default '', -- 名字
13 | vercode VARCHAR(100) not null default '', -- 验证码 加好友来源
14 | created_at timeStamp not null DEFAULT CURRENT_TIMESTAMP, -- 创建时间
15 | updated_at timeStamp not null DEFAULT CURRENT_TIMESTAMP -- 更新时间
16 | );
17 |
18 | CREATE UNIQUE INDEX `uid_maillist_index` on `user_maillist` (`uid`,`zone`,`phone`);
--------------------------------------------------------------------------------
/modules/user/sql/user-20211115-01.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Up
2 |
3 | CREATE unique INDEX to_uid_uid on `user_setting` (uid, to_uid);
4 |
5 | CREATE unique INDEX to_uid_uid on `friend` (uid, to_uid);
--------------------------------------------------------------------------------
/modules/user/sql/user-20220222-01.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Up
2 |
3 | ALTER TABLE `user` ADD COLUMN is_destroy smallint not null default 0 COMMENT '是否已销毁';
4 | ALTER TABLE `user` MODIFY COLUMN zone VARCHAR(20);
5 | ALTER TABLE `user` MODIFY COLUMN phone VARCHAR(100);
6 |
--------------------------------------------------------------------------------
/modules/user/sql/user-20220609-01.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Up
2 |
3 | ALTER TABLE `user_setting` ADD COLUMN remark VARCHAR(100) NOT NULL DEFAULT '' COMMENT '用户备注';
4 |
5 | -- 迁移备注数据
6 | insert into user_setting(uid,to_uid,remark) select uid,to_uid,remark from friend where remark<>''
7 | on duplicate key update remark=values(remark);
--------------------------------------------------------------------------------
/modules/user/sql/user-20220713-01.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Up
2 |
3 | ALTER TABLE `user` ADD COLUMN wx_openid VARCHAR(100) NOT NULL DEFAULT '' COMMENT '微信openid';
4 | ALTER TABLE `user` ADD COLUMN wx_unionid VARCHAR(100) NOT NULL DEFAULT '' COMMENT '微信unionid';
5 |
--------------------------------------------------------------------------------
/modules/user/sql/user-20220816-01.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Up
2 |
3 | ALTER TABLE `user_setting` ADD COLUMN `flame` smallint not null default 0 COMMENT '阅后即焚是否开启 1.开启 0.未开启';
4 | ALTER TABLE `user_setting` ADD COLUMN `flame_second` smallint not null default 0 COMMENT '阅后即焚销毁秒数';
--------------------------------------------------------------------------------
/modules/user/sql/user-20220906-01.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Up
2 |
3 | -- 短编号
4 | create table `shortno`
5 | (
6 | id bigint not null primary key AUTO_INCREMENT,
7 | shortno VARCHAR(40) not null default '' COMMENT '唯一短编号',
8 | used smallint not null default 0 COMMENT '是否被用',
9 | hold smallint not null default 0 COMMENT '保留,保留的号码将不会再被分配',
10 | locked smallint not null default 0 COMMENT '是否被锁定,锁定了的短编号将不再被分配,直到解锁',
11 | business VARCHAR(40) not null default '' COMMENT '被使用的业务,比如 user',
12 | created_at timeStamp not null DEFAULT CURRENT_TIMESTAMP, -- 创建时间
13 | updated_at timeStamp not null DEFAULT CURRENT_TIMESTAMP -- 更新时间
14 | );
15 |
16 | CREATE UNIQUE INDEX `udx_shortno` on `shortno` (`shortno`);
--------------------------------------------------------------------------------
/modules/user/sql/user-20220919-01.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Up
2 |
3 | -- 设备标识
4 | create table `device_flag`
5 | (
6 | id bigint not null primary key AUTO_INCREMENT,
7 | device_flag smallint not null default 0 COMMENT '设备标记 0. app 1.Web 2.PC',
8 | `weight` integer not null default 0 COMMENT '设备权重 值越大越优先',
9 | remark VARCHAR(100) not null default '' COMMENT '备注',
10 | created_at timeStamp not null DEFAULT CURRENT_TIMESTAMP, -- 创建时间
11 | updated_at timeStamp not null DEFAULT CURRENT_TIMESTAMP -- 更新时间
12 | );
13 |
14 | CREATE UNIQUE INDEX `udx_device_flag` on `device_flag` (`device_flag`);
15 |
16 |
17 | insert into device_flag(device_flag,`weight`,remark) values(2,'80000','PC');
18 | insert into device_flag(device_flag,`weight`,remark) values(1,'70000','Web');
19 | insert into device_flag(device_flag,`weight`,remark) values(0,'90000','手机');
20 |
--------------------------------------------------------------------------------
/modules/user/sql/user-20230911-01.sql:
--------------------------------------------------------------------------------
1 |
2 | -- +migrate Up
3 |
4 | ALTER TABLE `user` ADD COLUMN web3_public_key VARCHAR(200) NOT NULL DEFAULT '' COMMENT 'web3公钥';
5 |
--------------------------------------------------------------------------------
/modules/user/sql/user-20230924-01.sql:
--------------------------------------------------------------------------------
1 |
2 | -- +migrate Up
3 |
4 | ALTER TABLE `user` ADD COLUMN msg_expire_second bigint NOT NULL DEFAULT 0 COMMENT '消息过期时长(单位秒)';
5 |
--------------------------------------------------------------------------------
/modules/user/sql/user-20231127-01.sql:
--------------------------------------------------------------------------------
1 |
2 |
3 | -- +migrate Up
4 |
5 | -- 好友申请记录
6 | create table `friend_apply_record`
7 | (
8 | id bigint not null primary key AUTO_INCREMENT,
9 | uid VARCHAR(40) not null default '', -- 用户uid
10 | to_uid VARCHAR(40) not null default '', -- 申请者uid
11 | remark VARCHAR(200) not null default '', -- 申请备注
12 | status smallint not null DEFAULT 1, -- 状态 0.未处理 1.通过 2.拒绝
13 | token VARCHAR(200) not null default '', -- 通过好友所需验证
14 | created_at timeStamp not null DEFAULT CURRENT_TIMESTAMP, -- 创建时间
15 | updated_at timeStamp not null DEFAULT CURRENT_TIMESTAMP -- 更新时间
16 | );
17 |
18 | CREATE INDEX `friend_apply_record_uidx` on `friend_apply_record` (`uid`);
19 | CREATE UNIQUE INDEX `friend_apply_record_uid_touidx` on `friend_apply_record` (`uid`,`to_uid`);
20 |
21 | -- 用户业务红点
22 | CREATE table `user_red_dot`(
23 | id bigint not null primary key AUTO_INCREMENT,
24 | uid VARCHAR(40) not null default '', -- 用户uid
25 | count smallint not null default 0, -- 未读数量
26 | category VARCHAR(40) not null default '', -- 红点分类
27 | is_dot smallint not null default 0, -- 是否显示红点 1.是 0.否
28 | created_at timeStamp not null DEFAULT CURRENT_TIMESTAMP, -- 创建时间
29 | updated_at timeStamp not null DEFAULT CURRENT_TIMESTAMP -- 更新时间
30 | );
31 |
32 | CREATE UNIQUE INDEX `user_red_dot_uid_categoryx` on `user_red_dot` (`uid`,`category`);
33 |
--------------------------------------------------------------------------------
/modules/webhook/1module.go:
--------------------------------------------------------------------------------
1 | package webhook
2 |
3 | import (
4 | "embed"
5 |
6 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/config"
7 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/pkg/register"
8 | )
9 |
10 | //go:embed sql
11 | var sqlFS embed.FS
12 |
13 | func init() {
14 |
15 | register.AddModule(func(ctx interface{}) register.Module {
16 | wk := New(ctx.(*config.Context))
17 | return register.Module{
18 | SetupAPI: func() register.APIRouter {
19 |
20 | return wk
21 | },
22 | SQLDir: register.NewSQLFS(sqlFS),
23 | Start: func() error {
24 | return wk.Start()
25 | },
26 | Stop: func() error {
27 | return wk.Stop()
28 | },
29 | }
30 | })
31 | }
32 |
--------------------------------------------------------------------------------
/modules/webhook/apns.go:
--------------------------------------------------------------------------------
1 | package webhook
2 |
3 | import (
4 | "errors"
5 |
6 | "github.com/sideshow/apns2"
7 | "github.com/sideshow/apns2/certificate"
8 | )
9 |
10 | //APNS APNS
11 | type APNS struct {
12 | client *apns2.Client
13 | p12FilePath string
14 | password string
15 | dev bool // 是否是开发环境
16 |
17 | }
18 |
19 | // NewAPNS NewAPNS
20 | func NewAPNS(p12FilePath, password string, dev bool) *APNS {
21 | apns := &APNS{
22 | p12FilePath: p12FilePath,
23 | password: password,
24 | dev: dev,
25 | }
26 | return apns
27 | }
28 |
29 | func (a *APNS) createClient() (*apns2.Client, error) {
30 | cert, err := certificate.FromP12File(a.p12FilePath, a.password)
31 | if err != nil {
32 | return nil, err
33 | }
34 | var client *apns2.Client
35 | if a.dev {
36 | client = apns2.NewClient(cert).Development()
37 | } else {
38 | client = apns2.NewClient(cert).Production()
39 | }
40 | return client, nil
41 | }
42 |
43 | // Push 推送消息
44 | func (a *APNS) Push(notification *apns2.Notification) error {
45 | var err error
46 | if a.client == nil {
47 | a.client, err = a.createClient()
48 | if err != nil {
49 | return err
50 | }
51 | }
52 | res, err := a.client.Push(notification)
53 | if err != nil {
54 | return err
55 | }
56 | if res.StatusCode != 200 {
57 | return errors.New(res.Reason)
58 | }
59 |
60 | return nil
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/modules/webhook/db_message.go:
--------------------------------------------------------------------------------
1 | package webhook
2 |
3 | import (
4 | "fmt"
5 | "hash/crc32"
6 |
7 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/config"
8 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/pkg/db"
9 | "github.com/gocraft/dbr/v2"
10 | )
11 |
12 | type messageDB struct {
13 | ctx *config.Context
14 | db *dbr.Session
15 | }
16 |
17 | func newMessageDB(ctx *config.Context) *messageDB {
18 |
19 | return &messageDB{
20 | ctx: ctx,
21 | db: ctx.DB(),
22 | }
23 | }
24 |
25 | func (m *messageDB) insertOrUpdateTx(model *messageModel, tx *dbr.Tx) error {
26 | tbl := m.getTable(model.ChannelID)
27 | _, err := tx.InsertBySql(fmt.Sprintf("insert into %s(message_id,message_seq,client_msg_no,header,setting,`signal`,from_uid,channel_id,channel_type,expire,expire_at,timestamp,payload,is_deleted) values(?,?,?,?,?,?,?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE payload=payload", tbl), model.MessageID, model.MessageSeq, model.ClientMsgNo, model.Header, model.Setting, model.Signal, model.FromUID, model.ChannelID, model.ChannelType, model.Expire, model.ExpireAt, model.Timestamp, model.Payload, model.IsDeleted).Exec()
28 | return err
29 | }
30 |
31 | // 通过频道ID获取表
32 | func (m *messageDB) getTable(channelID string) string {
33 | tableIndex := crc32.ChecksumIEEE([]byte(channelID)) % uint32(m.ctx.GetConfig().TablePartitionConfig.MessageTableCount)
34 | if tableIndex == 0 {
35 | return "message"
36 | }
37 | return fmt.Sprintf("message%d", tableIndex)
38 | }
39 |
40 | type messageModel struct {
41 | MessageID string
42 | MessageSeq int64
43 | ClientMsgNo string
44 | Header string
45 | Setting uint8
46 | Signal uint8 // 是否signal加密
47 | FromUID string
48 | ChannelID string
49 | ChannelType uint8
50 | Expire uint32
51 | ExpireAt uint32
52 | Timestamp int32
53 | Payload string
54 | IsDeleted int
55 | db.BaseModel
56 | }
57 |
--------------------------------------------------------------------------------
/modules/webhook/db_test.go:
--------------------------------------------------------------------------------
1 | package webhook
2 |
3 | // import (
4 | // "os"
5 | // "testing"
6 |
7 | // "github.com/stretchr/testify/assert"
8 | // "github.com/TangSengDaoDao/TangSengDaoDaoServer/internal/api/group"
9 | // "github.com/TangSengDaoDao/TangSengDaoDaoServer/modules/user"
10 | // "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/pkg/db"
11 | // )
12 |
13 | // func TestGetThirdName(t *testing.T) {
14 | // os.Remove("test.db")
15 | // s := db.NewSqlite("test.db", "../../../assets/sql")
16 | // d := NewDB(s)
17 | // udb := user.NewDB(s)
18 | // err := udb.Insert(&user.Model{
19 | // UID: "1",
20 | // Name: "test1",
21 | // })
22 | // assert.NoError(t, err)
23 |
24 | // err = udb.Insert(&user.Model{
25 | // UID: "2",
26 | // Name: "test2",
27 | // })
28 | // assert.NoError(t, err)
29 |
30 | // fdb := friend.NewDB(s)
31 | // err = fdb.Insert(&friend.Model{
32 | // UID: "1",
33 | // ToUID: "2",
34 | // Remark: "dddd",
35 | // })
36 | // assert.NoError(t, err)
37 |
38 | // err = fdb.Insert(&friend.Model{
39 | // UID: "2",
40 | // ToUID: "1",
41 | // Remark: "11dddd",
42 | // })
43 | // assert.NoError(t, err)
44 |
45 | // gdb := group.NewDB(s)
46 | // gdb.InsertMember(&group.MemberModel{
47 | // GroupNo: "g1",
48 | // UID: "1",
49 | // Remark: "g1_name",
50 | // })
51 |
52 | // name, remark, nameInGroup, err := d.GetThirdName("1", "2", "g1")
53 | // assert.NoError(t, err)
54 |
55 | // assert.Equal(t, "test1", name)
56 | // assert.Equal(t, "11dddd", remark)
57 | // assert.Equal(t, "g1_name", nameInGroup)
58 |
59 | // }
60 |
--------------------------------------------------------------------------------
/modules/webhook/github.go:
--------------------------------------------------------------------------------
1 | package webhook
2 |
3 | import (
4 | "fmt"
5 | "io/ioutil"
6 |
7 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/pkg/wkhttp"
8 | )
9 |
10 | func (w *Webhook) github(c *wkhttp.Context) {
11 | fmt.Println("github webhook-->", c.Params)
12 |
13 | result, _ := ioutil.ReadAll(c.Request.Body)
14 | fmt.Println("github-result-->", result)
15 | }
16 |
--------------------------------------------------------------------------------
/modules/webhook/push.go:
--------------------------------------------------------------------------------
1 | package webhook
2 |
3 | import (
4 | "github.com/TangSengDaoDao/TangSengDaoDaoServer/modules/user"
5 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/common"
6 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/config"
7 | )
8 |
9 | // Payload 推送内容
10 | type Payload interface {
11 | GetTitle() string // 推送标题
12 | GetContent() string // 推送正文
13 | GetBadge() int // 推送红点
14 |
15 | GetRTCPayload() RTCPayload // 获取rtc的payload
16 | }
17 |
18 | type RTCPayload interface {
19 | GetCallType() common.RTCCallType // 音视频呼叫类型
20 | GetOperation() string // 音视频操作 invite: 邀请音视频 cancel:取消邀请
21 | GetFromUID() string // 发起人的uid
22 | }
23 |
24 | // BasePayload 基础负载
25 | type BasePayload struct {
26 | title string
27 | content string
28 | badge int
29 | }
30 |
31 | // GetTitle 推送标题
32 | func (p *BasePayload) GetTitle() string {
33 | return p.title
34 | }
35 |
36 | // GetContent 推送正文
37 | func (p *BasePayload) GetContent() string {
38 | return p.content
39 | }
40 |
41 | // GetBadge 推送红点
42 | func (p *BasePayload) GetBadge() int {
43 | return p.badge
44 | }
45 |
46 | func (p *BasePayload) GetRTCPayload() RTCPayload {
47 | return nil
48 | }
49 |
50 | type BaseRTCPayload struct {
51 | BasePayload
52 | callType common.RTCCallType
53 | operation string
54 | fromUID string
55 | }
56 |
57 | func (b *BaseRTCPayload) GetCallType() common.RTCCallType {
58 | return b.callType
59 | }
60 |
61 | func (b *BaseRTCPayload) GetOperation() string {
62 | return b.operation
63 | }
64 |
65 | func (b *BaseRTCPayload) GetFromUID() string {
66 | return b.fromUID
67 | }
68 |
69 | func (b *BaseRTCPayload) GetRTCPayload() RTCPayload {
70 | return b
71 | }
72 |
73 | // Push Push
74 | type Push interface {
75 | GetPayload(msg msgOfflineNotify, ctx *config.Context, toUser *user.Resp) (Payload, error)
76 | Push(deviceToken string, payload Payload) error
77 | }
78 |
--------------------------------------------------------------------------------
/modules/webhook/push_test.go:
--------------------------------------------------------------------------------
1 | package webhook
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/config"
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func TestHMSPush(t *testing.T) {
11 | hms := NewHMSPush("101827411", "649dedfb617dbd699715c05b9b430ce54013ad404cea0a5a2a16302fb01911a2", "com.xinbida.wukongchat")
12 | accessToken, _, err := hms.GetHMSAccessToken()
13 | assert.NoError(t, err)
14 | payloadInfo := &PayloadInfo{
15 | Title: "title",
16 | Content: "content2222",
17 | Badge: 1,
18 | }
19 | err = hms.Push("ANqYJlGemvmj_H5U8L3629mb-OT7slBYJTdB8-vfpveu-oQzsJH8qtxCmEfzEiUemP1Gc7KV5M32rbiuhafNaZSu2VRPxAASLp3c_1_Ky-kUPN8FU06fZWHxLlA-6tJjCg", NewHMSPayload(payloadInfo, accessToken))
20 | assert.NoError(t, err)
21 | }
22 |
23 | func TestMIPush(t *testing.T) {
24 | mi := NewMIPush("2882303761519001722", "XIf41QWNIBRZPJVKUOOoYQ==", "com.xinbida.wukongchat", "")
25 |
26 | payloadInfo := &PayloadInfo{
27 | Title: "title",
28 | Content: "content",
29 | Badge: 1,
30 | }
31 |
32 | err := mi.Push("deviceToken", NewMIPayload(payloadInfo, "11"))
33 | assert.NoError(t, err)
34 | }
35 |
36 | func TestOPPOPush(t *testing.T) {
37 | oppo := NewOPPOPush("30755393", "aece2f965eb64a9a82e01db87b23030e", "d7205515e1ab4fe6ace46f0f5df1105f", "dd6e2ec2e89e4669bb4afe4433b28ac1", &config.Context{})
38 | payloadInfo := &PayloadInfo{
39 | Title: "标题",
40 | Content: "内容",
41 | Badge: 1,
42 | }
43 | err := oppo.Push("OPPO_CN_5831bbbefd00814c2bd82dbd40382869", NewOPPOPayload(payloadInfo, "11"))
44 | assert.NoError(t, err)
45 | }
46 |
47 | func TestVIVOPush(t *testing.T) {
48 | vivo := NewVIVOPush("105542118", "d7aacd9d36621e75a9efb7ce69b5c567", "be82d800-0078-42cf-91d2-4127781361a9", &config.Context{})
49 | payloadInfo := &PayloadInfo{
50 | Title: "标题",
51 | Content: "内容",
52 | Badge: 1,
53 | }
54 | err := vivo.Push("16569158930074211800064", NewVIVOPayload(payloadInfo, "11"))
55 | assert.NoError(t, err)
56 | }
57 |
58 | func TestFirebasePush(t *testing.T) {
59 | // 请使用你本地的绝对路径进行测试
60 | mi := NewFIREBASEPush("service_Account_json_Path", "bobo", "这个值请从json里面获取", "")
61 |
62 | payloadInfo := &PayloadInfo{
63 | Title: "title",
64 | Content: "content",
65 | Badge: 1,
66 | }
67 | // 这个device token是 firebase的token 不是app的device token,请前端老师帮忙提供即可。
68 | err := mi.Push("请前端开发给你提供这个值", NewFIREBASEPayload(payloadInfo, "11"))
69 | assert.NoError(t, err)
70 | }
71 |
--------------------------------------------------------------------------------
/modules/webhook/sql/webhook-20230920-01.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Up
2 |
3 |
4 |
5 | ALTER TABLE `message` ADD COLUMN expire integer not null DEFAULT 0 COMMENT '消息过期时长';
6 | ALTER TABLE `message` ADD COLUMN expire_at BIGINT not null DEFAULT 0 COMMENT '消息过期时间';
7 |
8 | ALTER TABLE `message1` ADD COLUMN expire integer not null DEFAULT 0 COMMENT '消息过期时长';
9 | ALTER TABLE `message1` ADD COLUMN expire_at BIGINT not null DEFAULT 0 COMMENT '消息过期时间';
10 |
11 | ALTER TABLE `message2` ADD COLUMN expire integer not null DEFAULT 0 COMMENT '消息过期时长';
12 | ALTER TABLE `message2` ADD COLUMN expire_at BIGINT not null DEFAULT 0 COMMENT '消息过期时间';
13 |
14 | ALTER TABLE `message3` ADD COLUMN expire integer not null DEFAULT 0 COMMENT '消息过期时长';
15 | ALTER TABLE `message3` ADD COLUMN expire_at BIGINT not null DEFAULT 0 COMMENT '消息过期时间';
16 |
17 | ALTER TABLE `message4` ADD COLUMN expire integer not null DEFAULT 0 COMMENT '消息过期时长';
18 | ALTER TABLE `message4` ADD COLUMN expire_at BIGINT not null DEFAULT 0 COMMENT '消息过期时间';
--------------------------------------------------------------------------------
/modules/webhook/sql/webhook-20241217-01.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Up
2 |
3 | ALTER TABLE `message` MODIFY COLUMN client_msg_no VARCHAR(100) not null default '';
4 | ALTER TABLE `message1` MODIFY COLUMN client_msg_no VARCHAR(100) not null default '';
5 | ALTER TABLE `message2` MODIFY COLUMN client_msg_no VARCHAR(100) not null default '';
6 | ALTER TABLE `message3` MODIFY COLUMN client_msg_no VARCHAR(100) not null default '';
7 | ALTER TABLE `message4` MODIFY COLUMN client_msg_no VARCHAR(100) not null default '';
--------------------------------------------------------------------------------
/modules/workplace/1module.go:
--------------------------------------------------------------------------------
1 | package workplace
2 |
3 | import (
4 | "embed"
5 |
6 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/config"
7 | "github.com/TangSengDaoDao/TangSengDaoDaoServerLib/pkg/register"
8 | )
9 |
10 | //go:embed sql
11 | var sqlFS embed.FS
12 |
13 | //go:embed swagger/api.yaml
14 | var swaggerContent string
15 |
16 | func init() {
17 | register.AddModule(func(ctx interface{}) register.Module {
18 | return register.Module{
19 | Name: "workplace",
20 | SetupAPI: func() register.APIRouter {
21 | return New(ctx.(*config.Context))
22 | },
23 | SQLDir: register.NewSQLFS(sqlFS),
24 | Swagger: swaggerContent,
25 | }
26 | })
27 |
28 | // 工作台管理模块
29 | register.AddModule(func(ctx interface{}) register.Module {
30 | return register.Module{
31 | Name: "workplace_manager",
32 | SetupAPI: func() register.APIRouter {
33 | return NewManager(ctx.(*config.Context))
34 | },
35 | }
36 | })
37 | }
38 |
--------------------------------------------------------------------------------
/modules/workplace/sql/workplace-20230906-01.sql:
--------------------------------------------------------------------------------
1 |
2 | -- +migrate Up
3 |
4 |
5 | -- 工作台分类下app
6 | create table `workplace_category_app`(
7 | id bigint not null primary key AUTO_INCREMENT,
8 | category_no VARCHAR(40) not null DEFAULT '', -- 分类编号
9 | app_id VARCHAR(40) not null DEFAULT '', -- appid
10 | sort_num integer not null DEFAULT 0, -- 排序编号
11 | created_at timeStamp not null DEFAULT CURRENT_TIMESTAMP, -- 创建时间
12 | updated_at timeStamp not null DEFAULT CURRENT_TIMESTAMP -- 更新时间
13 | );
14 |
15 | CREATE unique INDEX workplace_category_app_cno_aid on `workplace_category_app` (category_no,app_id);
16 |
--------------------------------------------------------------------------------
/modules/workplace/sql/workplace-20240113-01.sql:
--------------------------------------------------------------------------------
1 |
2 | -- +migrate Up
3 | ALTER TABLE `workplace_banner` ADD COLUMN `sort_num` integer not null default 0 COMMENT '排序号';
4 |
--------------------------------------------------------------------------------
/pkg/cache/cache.go:
--------------------------------------------------------------------------------
1 | package cache
2 |
3 | import "time"
4 |
5 | // Cache 缓存接口
6 | type Cache interface {
7 | // Set 设置key value
8 | Set(key string, value string) error
9 | // 删除key
10 | Delete(key string) error
11 | // SetAndExpire 设置key value 并支持过期时间
12 | SetAndExpire(key string, value string, expire time.Duration) error
13 | // 获取key对应的值
14 | Get(key string) (string, error)
15 | }
16 |
--------------------------------------------------------------------------------
/pkg/db/db.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import (
4 | "time"
5 | )
6 |
7 | const (
8 | timeFormart = "2006-01-02 15:04:05"
9 | )
10 |
11 | type Time time.Time
12 |
13 | type BaseModel struct {
14 | Id int64
15 | CreatedAt Time
16 | UpdatedAt Time
17 | }
18 |
19 | func (t *Time) UnmarshalJSON(data []byte) (err error) {
20 | now, err := time.ParseInLocation(`"`+timeFormart+`"`, string(data), time.Local)
21 | *t = Time(now)
22 | return
23 | }
24 |
25 | func (t Time) MarshalJSON() ([]byte, error) {
26 | b := make([]byte, 0, len(timeFormart)+2)
27 | b = append(b, '"')
28 | b = time.Time(t).AppendFormat(b, timeFormart)
29 | b = append(b, '"')
30 | return b, nil
31 | }
32 |
33 | func (t Time) String() string {
34 | return time.Time(t).Format(timeFormart)
35 | }
36 |
--------------------------------------------------------------------------------
/pkg/db/redis.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import "github.com/TangSengDaoDao/TangSengDaoDaoServer/pkg/redis"
4 |
5 | func NewRedis(addr string, password string) *redis.Conn {
6 | return redis.New(addr, password)
7 | }
8 |
--------------------------------------------------------------------------------
/pkg/db/sqlite.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import (
4 | "os"
5 | "path"
6 |
7 | "github.com/gocraft/dbr/v2"
8 | _ "github.com/mattn/go-sqlite3"
9 | migrate "github.com/rubenv/sql-migrate"
10 | )
11 |
12 | // NewSqlite 创建一个sqlite db,[path]db存储路径 [sqlDir]sql脚本目录
13 | func NewSqlite(filepath string, sqlDir string) *dbr.Session {
14 |
15 | err := os.Mkdir(path.Dir(filepath), os.ModePerm)
16 | if err != nil && !os.IsExist(err) {
17 | panic(err)
18 | }
19 | conn, err := dbr.Open("sqlite3", filepath, nil)
20 | if err != nil {
21 | panic(err)
22 | }
23 | session := conn.NewSession(nil)
24 | migrations := &migrate.FileMigrationSource{
25 | Dir: sqlDir,
26 | }
27 | _, err = migrate.Exec(session.DB, "sqlite3", migrations, migrate.Up)
28 | if err != nil {
29 | panic(err)
30 | }
31 | return session
32 | }
33 |
--------------------------------------------------------------------------------
/pkg/keylock/keylock.go:
--------------------------------------------------------------------------------
1 | package keylock
2 |
3 | import (
4 | "sync"
5 | "sync/atomic"
6 | "time"
7 | )
8 |
9 | const (
10 | defaultCleanInterval = 24 * time.Hour //默认24小时清理一次
11 | )
12 |
13 | // KeyLock KeyLock
14 | type KeyLock struct {
15 | locks map[string]*innerLock //关键字锁map
16 | cleanInterval time.Duration //定时清除时间间隔
17 | stopChan chan struct{} //停止信号
18 | mutex sync.RWMutex //全局读写锁
19 | }
20 |
21 | // NewKeyLock NewKeyLock
22 | func NewKeyLock() *KeyLock {
23 | return &KeyLock{
24 | locks: make(map[string]*innerLock),
25 | cleanInterval: defaultCleanInterval,
26 | stopChan: make(chan struct{}),
27 | }
28 | }
29 |
30 | //Lock 根据关键字加锁
31 | func (l *KeyLock) Lock(key string) {
32 | l.mutex.RLock()
33 | keyLock, ok := l.locks[key]
34 | if ok {
35 | keyLock.add()
36 | }
37 | l.mutex.RUnlock()
38 | if !ok {
39 | l.mutex.Lock()
40 | keyLock, ok = l.locks[key]
41 | if !ok {
42 | keyLock = newInnerLock()
43 | l.locks[key] = keyLock
44 | }
45 | keyLock.add()
46 | l.mutex.Unlock()
47 | }
48 | keyLock.Lock()
49 | }
50 |
51 | //Unlock 根据关键字解锁
52 | func (l *KeyLock) Unlock(key string) {
53 | l.mutex.RLock()
54 | keyLock, ok := l.locks[key]
55 | if ok {
56 | keyLock.done()
57 | }
58 | l.mutex.RUnlock()
59 | if ok {
60 | keyLock.Unlock()
61 | }
62 | }
63 |
64 | //Clean 清理空闲锁
65 | func (l *KeyLock) Clean() {
66 | l.mutex.Lock()
67 | for k, v := range l.locks {
68 | if v.count == 0 {
69 | delete(l.locks, k)
70 | }
71 | }
72 | l.mutex.Unlock()
73 | }
74 |
75 | //StartCleanLoop 开启清理协程
76 | func (l *KeyLock) StartCleanLoop() {
77 | go l.cleanLoop()
78 | }
79 |
80 | //StopCleanLoop 停止清理协程
81 | func (l *KeyLock) StopCleanLoop() {
82 | close(l.stopChan)
83 | }
84 |
85 | //清理循环
86 | func (l *KeyLock) cleanLoop() {
87 | ticker := time.NewTicker(l.cleanInterval)
88 | for {
89 | select {
90 | case <-ticker.C:
91 | l.Clean()
92 | case <-l.stopChan:
93 | ticker.Stop()
94 | return
95 | }
96 | }
97 | }
98 |
99 | //内部锁信息
100 | type innerLock struct {
101 | count int64
102 | sync.Mutex
103 | }
104 |
105 | //新建内部锁
106 | func newInnerLock() *innerLock {
107 | return &innerLock{}
108 | }
109 |
110 | func (il *innerLock) add() {
111 | atomic.AddInt64(&il.count, 1)
112 | }
113 |
114 | func (il *innerLock) done() {
115 | atomic.AddInt64(&il.count, -1)
116 | }
117 |
--------------------------------------------------------------------------------
/pkg/log/logger_test.go:
--------------------------------------------------------------------------------
1 | package log
2 |
3 | import (
4 | "testing"
5 |
6 | "go.uber.org/zap"
7 | )
8 |
9 | func TestLogger(t *testing.T) {
10 | opts := NewOptions()
11 | opts.Level = zap.DebugLevel
12 | opts.LineNum = true
13 | Configure(opts)
14 |
15 | Info("this is info")
16 | Debug("this is debug")
17 | Error("this is error", zap.String("key", "value"))
18 | }
19 |
--------------------------------------------------------------------------------
/pkg/log/options.go:
--------------------------------------------------------------------------------
1 | package log
2 |
3 | import "go.uber.org/zap/zapcore"
4 |
5 | type Options struct {
6 | Level zapcore.Level
7 | LogDir string
8 | LineNum bool
9 | }
10 |
11 | func NewOptions() *Options {
12 |
13 | return &Options{}
14 | }
15 |
--------------------------------------------------------------------------------
/pkg/markdown/markdown.go:
--------------------------------------------------------------------------------
1 | package markdown
2 |
3 | import (
4 | "fmt"
5 | "io"
6 |
7 | "github.com/gomarkdown/markdown"
8 | "github.com/gomarkdown/markdown/ast"
9 | "github.com/gomarkdown/markdown/html"
10 | "github.com/sourcegraph/syntaxhighlight"
11 | )
12 |
13 | func ToHtml(v string) string {
14 | if v == "" {
15 | return ""
16 | }
17 | htmlFlags := html.CommonFlags | html.HrefTargetBlank
18 | opts := html.RendererOptions{
19 | Flags: htmlFlags,
20 | RenderNodeHook: renderHookCodeBlock,
21 | }
22 | renderer := html.NewRenderer(opts)
23 |
24 | return string(markdown.ToHTML([]byte(v), nil, renderer))
25 | }
26 |
27 | func renderHookCodeBlock(w io.Writer, node ast.Node, entering bool) (ast.WalkStatus, bool) {
28 |
29 | _, ok := node.(*ast.Code)
30 | if ok {
31 | fmt.Println("code-------------->")
32 | w.Write([]byte(fmt.Sprintf("%s
", string(node.AsLeaf().Literal))))
33 | return ast.GoToNext, true
34 | }
35 |
36 | _, ok = node.(*ast.CodeBlock)
37 | if ok {
38 | syncHtml, _ := syntaxhighlight.AsHTML(node.AsLeaf().Literal, func(options *syntaxhighlight.HTMLConfig) {
39 | options.Type = "pl-en"
40 | options.Keyword = "pl-k"
41 | options.Plaintext = "pl-s1"
42 | options.String = "pl-s1"
43 | options.Comment = "pl-c"
44 | })
45 | w.Write([]byte(fmt.Sprintf("%s
", string(syncHtml))))
46 | return ast.GoToNext, true
47 | }
48 | // test := syntaxhighlight.HTMLConfig{
49 | // String: "str",
50 | // Keyword: "kwd",
51 | // Comment: "com",
52 | // Type: "typ",
53 | // Literal: "lit",
54 | // Punctuation: "pun",
55 | // Plaintext: "pln",
56 | // Tag: "tag",
57 | // HTMLTag: "htm",
58 | // HTMLAttrName: "atn",
59 | // HTMLAttrValue: "atv",
60 | // Decimal: "dec",
61 | // Whitespace: "",
62 | // }
63 | return ast.GoToNext, false
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/pkg/markdown/markdown_test.go:
--------------------------------------------------------------------------------
1 | package markdown
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 | )
7 |
8 | func TestToHtml(t *testing.T) {
9 |
10 | htm := ToHtml("a\n```go\n /** test **/ func Test(v []byte) (error){ fmt.Println(\"zdsdsdsd\")}\n```\nb `测试`")
11 |
12 | fmt.Println("htm--->", htm)
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/pkg/pool/dispatcher.go:
--------------------------------------------------------------------------------
1 | package pool
2 |
3 | import (
4 | "sync/atomic"
5 | )
6 |
7 | var WorkerChannel = make(chan chan *Job)
8 |
9 | type JobStatistics struct {
10 | Executing int64 // Total number of jobs executed
11 | Total int64
12 | }
13 |
14 | type Collector struct {
15 | Work chan *Job // receives jobs to send to workers
16 | End chan bool // when receives bool stops workers
17 | jobS *JobStatistics
18 | queue *Queue
19 | }
20 |
21 | func StartDispatcher(workerCount int64) Collector {
22 | var i int64
23 | var workers []Worker
24 | input := make(chan *Job) // channel to recieve work
25 | end := make(chan bool) // channel to spin down workers
26 | jobFinished := make(chan bool)
27 | collector := Collector{
28 | Work: input,
29 | End: end,
30 | jobS: &JobStatistics{},
31 | queue: NewQueue(),
32 | }
33 |
34 | go collector.loopPop()
35 |
36 | for i < workerCount {
37 | i++
38 | worker := Worker{
39 | ID: i,
40 | Channel: make(chan *Job),
41 | WorkerChannel: WorkerChannel,
42 | End: make(chan struct{}),
43 | jobFinished: jobFinished,
44 | }
45 | worker.Start()
46 | workers = append(workers, worker) // stores worker
47 | }
48 | go func() {
49 | for {
50 | select {
51 | case <-jobFinished: // job finished
52 | atomic.AddInt64(&collector.jobS.Executing, -1)
53 | }
54 | }
55 | }()
56 |
57 | // start collector
58 | go func() {
59 | for {
60 | select {
61 | case <-end:
62 | for _, w := range workers {
63 | w.Stop() // stop worker
64 | }
65 | return
66 | case job := <-input:
67 | collector.queue.Push(job)
68 | }
69 | }
70 | }()
71 |
72 | return collector
73 | }
74 |
75 | func (c Collector) loopPop() {
76 | for {
77 | jobObj := c.queue.Pop()
78 | atomic.AddInt64(&c.jobS.Total, 1)
79 | worker := <-WorkerChannel // wait for available channel
80 | atomic.AddInt64(&c.jobS.Executing, 1)
81 | worker <- jobObj.(*Job) // dispatch work to worker
82 | }
83 |
84 | }
85 |
86 | func (c Collector) GetStatistics() *JobStatistics {
87 | return c.jobS
88 | }
89 |
90 | func (c Collector) Waiting() int {
91 | return c.queue.Len()
92 | }
93 |
--------------------------------------------------------------------------------
/pkg/pool/queue.go:
--------------------------------------------------------------------------------
1 | package pool
2 |
3 | import (
4 | "github.com/eapache/queue"
5 | "runtime"
6 | "sync"
7 | )
8 |
9 |
10 | type Queue struct {
11 | sync.Mutex
12 | popable *sync.Cond
13 | buffer *queue.Queue
14 | closed bool
15 | }
16 |
17 | func NewQueue() *Queue {
18 | e := &Queue{
19 | buffer: queue.New(),
20 | }
21 | e.popable = sync.NewCond(&e.Mutex)
22 | return e
23 | }
24 |
25 | func (e *Queue) Push(v interface{}) {
26 | e.Mutex.Lock()
27 | defer e.Mutex.Unlock()
28 | if !e.closed {
29 | e.buffer.Add(v)
30 | e.popable.Signal()
31 | }
32 | }
33 | func (e *Queue) Close() {
34 | e.Mutex.Lock()
35 | defer e.Mutex.Unlock()
36 | if !e.closed {
37 | e.closed = true
38 | e.popable.Broadcast() //广播
39 | }
40 | }
41 |
42 | //Pop 取出队列,(阻塞模式)
43 | func (e *Queue) Pop() (v interface{}) {
44 | c := e.popable
45 | buffer := e.buffer
46 |
47 | e.Mutex.Lock()
48 | defer e.Mutex.Unlock()
49 |
50 | for buffer.Length() == 0 && !e.closed {
51 | c.Wait()
52 | }
53 |
54 | if e.closed { //已关闭
55 | return
56 | }
57 |
58 | if buffer.Length() > 0 {
59 | v = buffer.Peek()
60 | buffer.Remove()
61 | }
62 | return
63 | }
64 |
65 | //试着取出队列(非阻塞模式)返回ok == false 表示空
66 | func (e *Queue) TryPop() (v interface{}, ok bool) {
67 | buffer := e.buffer
68 |
69 | e.Mutex.Lock()
70 | defer e.Mutex.Unlock()
71 |
72 | if buffer.Length() > 0 {
73 | v = buffer.Peek()
74 | buffer.Remove()
75 | ok = true
76 | } else if e.closed {
77 | ok = true
78 | }
79 |
80 | return
81 | }
82 |
83 | // 获取队列长度
84 | func (e *Queue) Len() int {
85 | return e.buffer.Length()
86 | }
87 |
88 | //Wait 等待队列消费完成
89 | func (e *Queue) Wait() {
90 | for {
91 | if e.closed || e.buffer.Length() == 0 {
92 | break
93 | }
94 |
95 | runtime.Gosched() //出让时间片
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/pkg/pool/worker.go:
--------------------------------------------------------------------------------
1 | package pool
2 |
3 | import (
4 | "log"
5 | )
6 |
7 | type JobFunc func(id int64, data interface{})
8 |
9 | type Job struct {
10 | Data interface{}
11 | JobFunc JobFunc
12 | }
13 |
14 | type Worker struct {
15 | ID int64
16 | WorkerChannel chan chan *Job // used to communicate between dispatcher and workers
17 | Channel chan *Job
18 | End chan struct{}
19 | jobFinished chan bool
20 | }
21 |
22 | // start worker
23 | func (w *Worker) Start() {
24 | go func() {
25 | for {
26 | w.WorkerChannel <- w.Channel // when the worker is available place channel in queue
27 | select {
28 | case job := <-w.Channel: // worker has received job
29 | if job != nil {
30 | job.JobFunc(w.ID, job.Data) // do work
31 | w.jobFinished <- true
32 | }
33 |
34 | case <-w.End:
35 | return
36 | }
37 | }
38 | }()
39 | }
40 |
41 | // end worker
42 | func (w *Worker) Stop() {
43 | log.Printf("worker [%d] is stopping", w.ID)
44 | w.End <- struct{}{}
45 | }
46 |
--------------------------------------------------------------------------------
/pkg/register/register.go:
--------------------------------------------------------------------------------
1 | package register
2 |
3 | import "github.com/TangSengDaoDao/TangSengDaoDaoServer/pkg/wkhttp"
4 |
5 | // APIRouter api路由者
6 | type APIRouter interface {
7 | Route(r *wkhttp.WKHttp)
8 | }
9 |
10 | var apiRoutes = make([]APIRouter, 0)
11 |
12 | // Add 添加api
13 | func Add(r APIRouter) {
14 | apiRoutes = append(apiRoutes, r)
15 | }
16 |
17 | var taskRoutes = make([]TaskRouter, 0)
18 |
19 | // GetRoutes 获取所有路由者
20 | func GetRoutes() []APIRouter {
21 | return apiRoutes
22 | }
23 |
24 | // TaskRouter task路由者
25 | type TaskRouter interface {
26 | RegisterTasks()
27 | }
28 |
29 | // AddTask 添加任务
30 | func AddTask(task TaskRouter) {
31 | taskRoutes = append(taskRoutes, task)
32 | }
33 |
34 | // GetTasks 获取所有任务
35 | func GetTasks() []TaskRouter {
36 | return taskRoutes
37 | }
38 |
--------------------------------------------------------------------------------
/pkg/util/base62.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | // Ten2Hex 十进制转换为62进制
8 | func Ten2Hex(ten int64) string {
9 | hex := ""
10 | var divValue, resValue int64
11 | for ten >= 62 {
12 | divValue = ten / int64(62)
13 | resValue = ten % 62
14 | hex = tenValue2Char(resValue) + hex
15 | ten = divValue
16 | }
17 | if ten != 0 {
18 | hex = tenValue2Char(ten) + hex
19 | }
20 | return hex
21 | }
22 |
23 | func tenValue2Char(ten int64) string {
24 | switch ten {
25 | case 0:
26 | case 1:
27 | case 2:
28 | case 3:
29 | case 4:
30 | case 5:
31 | case 6:
32 | case 7:
33 | case 8:
34 | case 9:
35 | return fmt.Sprintf("%d", ten)
36 | case 10:
37 | return "a"
38 | case 11:
39 | return "b"
40 | case 12:
41 | return "c"
42 | case 13:
43 | return "d"
44 | case 14:
45 | return "e"
46 | case 15:
47 | return "f"
48 | case 16:
49 | return "g"
50 | case 17:
51 | return "h"
52 | case 18:
53 | return "i"
54 | case 19:
55 | return "j"
56 | case 20:
57 | return "k"
58 | case 21:
59 | return "l"
60 | case 22:
61 | return "m"
62 | case 23:
63 | return "n"
64 | case 24:
65 | return "o"
66 | case 25:
67 | return "p"
68 | case 26:
69 | return "q"
70 | case 27:
71 | return "r"
72 | case 28:
73 | return "s"
74 | case 29:
75 | return "t"
76 | case 30:
77 | return "u"
78 | case 31:
79 | return "v"
80 | case 32:
81 | return "w"
82 | case 33:
83 | return "s"
84 | case 34:
85 | return "y"
86 | case 35:
87 | return "z"
88 | case 36:
89 | return "A"
90 | case 37:
91 | return "B"
92 | case 38:
93 | return "C"
94 | case 39:
95 | return "D"
96 | case 40:
97 | return "E"
98 | case 41:
99 | return "F"
100 | case 42:
101 | return "G"
102 | case 43:
103 | return "H"
104 | case 44:
105 | return "I"
106 | case 45:
107 | return "J"
108 | case 46:
109 | return "K"
110 | case 47:
111 | return "L"
112 | case 48:
113 | return "M"
114 | case 49:
115 | return "N"
116 | case 50:
117 | return "O"
118 | case 51:
119 | return "P"
120 | case 52:
121 | return "Q"
122 | case 53:
123 | return "R"
124 | case 54:
125 | return "S"
126 | case 55:
127 | return "T"
128 | case 56:
129 | return "U"
130 | case 57:
131 | return "V"
132 | case 58:
133 | return "W"
134 | case 59:
135 | return "S"
136 | case 60:
137 | return "Y"
138 | case 61:
139 | return "Z"
140 | default:
141 | return ""
142 | }
143 | return ""
144 | }
145 |
--------------------------------------------------------------------------------
/pkg/util/common.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import (
4 | "fmt"
5 | "sort"
6 | )
7 |
8 | // CheckErr CheckErr
9 | func CheckErr(err error) {
10 | if err != nil {
11 | panic(err)
12 | }
13 | }
14 |
15 | // Substr Substr
16 | func Substr(str string, start, length int) string {
17 | if length == 0 {
18 | return ""
19 | }
20 | runeStr := []rune(str)
21 | lenStr := len(runeStr)
22 |
23 | if start < 0 {
24 | start = lenStr + start
25 | }
26 | if start > lenStr {
27 | start = lenStr
28 | }
29 | end := start + length
30 | if end > lenStr {
31 | end = lenStr
32 | }
33 | if length < 0 {
34 | end = lenStr + length
35 | }
36 | if start > end {
37 | start, end = end, start
38 | }
39 | return string(runeStr[start:end])
40 | }
41 |
42 | func objToStr(v interface{}) string {
43 | var strV string
44 | switch v.(type) {
45 |
46 | case int:
47 | strV = fmt.Sprintf("%d", v)
48 | break
49 | case uint:
50 | strV = fmt.Sprintf("%d", v)
51 | break
52 | case int64:
53 | strV = fmt.Sprintf("%d", v)
54 | break
55 | case uint64:
56 | strV = fmt.Sprintf("%d", v)
57 | break
58 | case int8:
59 | strV = fmt.Sprintf("%d", v)
60 | break
61 | case uint8:
62 | strV = fmt.Sprintf("%d", v)
63 | break
64 | case int16:
65 | strV = fmt.Sprintf("%d", v)
66 | break
67 | case uint16:
68 | strV = fmt.Sprintf("%d", v)
69 | break
70 | case int32:
71 | strV = fmt.Sprintf("%d", v)
72 | break
73 | case uint32:
74 | strV = fmt.Sprintf("%s", v)
75 | break
76 | case string:
77 | strV = fmt.Sprintf("%s", v)
78 | break
79 | case float32:
80 | strV = fmt.Sprintf("%s", v)
81 | break
82 | case float64:
83 | strV = fmt.Sprintf("%s", v)
84 | break
85 | default:
86 | strV = fmt.Sprintf("%s", v)
87 |
88 | }
89 | return strV
90 | }
91 |
92 | // Sign Sign
93 | func Sign(params map[string]interface{}, appKey string) string {
94 | signStr := MapToQueryParamSort(params)
95 | return MD5(fmt.Sprintf("%s&key=%s", signStr, appKey))
96 | }
97 |
98 | // MapToQueryParamSort map 以 key1=value1 & key2=value2形式排序拼接
99 | func MapToQueryParamSort(params map[string]interface{}) string {
100 | if len(params) == 0 {
101 | return ""
102 | }
103 | strs := ""
104 | keys := make([]string, 0, len(params))
105 | for k := range params {
106 | keys = append(keys, k)
107 | }
108 | sort.Strings(keys)
109 |
110 | for _, k := range keys {
111 | v := params[k]
112 | if v == "" {
113 | continue
114 | }
115 | //strs = strs+k+"&"+v
116 | strs = fmt.Sprintf("%s%s=%s%s", strs, k, objToStr(v), "&")
117 | }
118 | if len(strs) > 0 {
119 | strs = strs[0 : len(strs)-1]
120 | }
121 | return strs
122 | }
123 |
--------------------------------------------------------------------------------
/pkg/util/dh.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import (
4 | "crypto/rand"
5 | "io"
6 |
7 | "golang.org/x/crypto/curve25519"
8 | )
9 |
10 | // GetCurve25519KeypPair GetCurve25519KeypPair
11 | func GetCurve25519KeypPair() (Aprivate, Apublic [32]byte) {
12 | //产生随机数
13 | if _, err := io.ReadFull(rand.Reader, Aprivate[:]); err != nil {
14 | panic(err)
15 | }
16 | curve25519.ScalarBaseMult(&Apublic, &Aprivate)
17 | return
18 | }
19 |
20 | // GetCurve25519Key GetCurve25519Key
21 | func GetCurve25519Key(private, public [32]byte) (Key [32]byte) {
22 | //产生随机数
23 | curve25519.ScalarMult(&Key, &private, &public)
24 | return
25 | }
26 |
--------------------------------------------------------------------------------
/pkg/util/hash.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import "hash/crc32"
4 |
5 | // HashCrc32 通过字符串获取32位数字
6 | func HashCrc32(str string) uint32 {
7 |
8 | return crc32.ChecksumIEEE([]byte(str))
9 | }
10 |
--------------------------------------------------------------------------------
/pkg/util/json.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | )
7 |
8 | //将对象转换为JSON
9 | func ToJson(obj interface{}) string {
10 | jsonData, err := json.Marshal(obj)
11 | if err != nil {
12 | return ""
13 | }
14 | return string(jsonData)
15 | }
16 |
17 | func JsonToMap(json string) (map[string]interface{}, error) {
18 | var resultMap map[string]interface{}
19 | err := ReadJsonByByte([]byte(json), &resultMap)
20 | return resultMap, err
21 | }
22 | func ReadJsonByByte(body []byte, obj interface{}) error {
23 | mdz := json.NewDecoder(bytes.NewBuffer(body))
24 |
25 | mdz.UseNumber()
26 | err := mdz.Decode(obj)
27 |
28 | if err != nil {
29 | return err
30 | }
31 | return nil
32 | }
33 |
--------------------------------------------------------------------------------
/pkg/util/md5.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import (
4 | "crypto/hmac"
5 | "crypto/md5"
6 | "crypto/sha1"
7 | "encoding/base64"
8 | "encoding/hex"
9 | "fmt"
10 | )
11 |
12 | //MD5 加密
13 | func MD5(str string) string {
14 | h := md5.New()
15 | h.Write([]byte(str)) // 需要加密的字符串
16 | passwordmdsBys := h.Sum(nil)
17 | return hex.EncodeToString(passwordmdsBys)
18 | }
19 |
20 | // SHA1加密
21 | func SHA1(str string) string {
22 | fmt.Println("str:", str)
23 | h := sha1.New()
24 | h.Write([]byte(str))
25 | bs := h.Sum(nil)
26 | return hex.EncodeToString(bs)
27 | }
28 |
29 | func HMACSHA1(keyStr string, data string) string {
30 | //hmac ,use sha1
31 | key := []byte(keyStr)
32 | mac := hmac.New(sha1.New, key)
33 | mac.Write([]byte(data))
34 | srcBytes := mac.Sum(nil)
35 | return base64.StdEncoding.EncodeToString(srcBytes)
36 | }
37 |
--------------------------------------------------------------------------------
/pkg/util/page.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import "strconv"
4 |
5 | // Page Page
6 | type Page struct {
7 | PageSize uint64 `json:"page_size"`
8 | PageIndex uint64 `json:"page_index"`
9 | Total uint64 `json:"total"`
10 | Data interface{} `json:"data"`
11 | }
12 |
13 | // NewPage NewPage
14 | func NewPage(pageIndex uint64, pageSize uint64, total uint64, data interface{}) *Page {
15 |
16 | return &Page{PageIndex: pageIndex, PageSize: pageSize, Data: data, Total: total}
17 | }
18 |
19 | //ToPageNumOrDefault 将字符串转换为数字类型 如果字符串为空 则赋值分页默认参数
20 | func ToPageNumOrDefault(pageIndex string, pageSize string) (pIndex64 uint64, pSize64 uint64) {
21 | var pageIndex64 uint64
22 | var pageSize64 uint64
23 | if pageIndex == "" {
24 | pageIndex64 = 1
25 | } else {
26 | pageIndex64, _ = strconv.ParseUint(pageIndex, 10, 64)
27 | }
28 |
29 | if pageSize == "" {
30 | pageSize64 = 10
31 | } else {
32 | pageSize64, _ = strconv.ParseUint(pageSize, 10, 64)
33 | }
34 |
35 | return pageIndex64, pageSize64
36 | }
37 |
--------------------------------------------------------------------------------
/pkg/util/qreflect.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import (
4 | "reflect"
5 | )
6 |
7 | // AttrToUnderscore 获取struct的所有属性并转为下划线模式
8 | func AttrToUnderscore(st interface{}) []string {
9 | t := reflect.ValueOf(st)
10 | vType := t.Elem().Type()
11 | names := make([]string, 0)
12 | for i := 0; i < vType.NumField(); i++ {
13 | if vType.Field(i).Type.Kind() == reflect.Struct {
14 | continue
15 | }
16 | name := vType.Field(i).Name
17 | if name != "" {
18 | names = append(names, UnderscoreName(name))
19 | }
20 | }
21 | return names
22 | }
23 |
--------------------------------------------------------------------------------
/pkg/util/qreflect_test.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import (
4 | "gopkg.in/go-playground/assert.v1"
5 | "testing"
6 | )
7 |
8 | func TestStructAttrToUnderscore(t *testing.T) {
9 |
10 | names := AttrToUnderscore(&struct {
11 | MessageID uint64
12 | Name string
13 | UserAge int
14 | }{})
15 | assert.Equal(t, "message_id", names[0])
16 | assert.Equal(t, "name", names[1])
17 | assert.Equal(t, "user_age", names[2])
18 | }
19 |
--------------------------------------------------------------------------------
/pkg/util/sha.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import (
4 | "crypto/hmac"
5 | "crypto/sha256"
6 | "encoding/base64"
7 | "encoding/hex"
8 | )
9 |
10 | func HmacSha256(message string, secret string) string {
11 | key := []byte(secret)
12 | h := hmac.New(sha256.New, key)
13 | h.Write([]byte(message))
14 | sha := hex.EncodeToString(h.Sum(nil))
15 | return base64.StdEncoding.EncodeToString([]byte(sha))
16 | }
17 |
--------------------------------------------------------------------------------
/pkg/util/sign.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import (
4 | "fmt"
5 | "sort"
6 | )
7 |
8 | func GetSignStr(params map[string]interface{}) string {
9 | var signStr string
10 | keys := make([]string, 0, len(params))
11 | for k := range params {
12 | keys = append(keys, k)
13 | }
14 | sort.Strings(keys)
15 |
16 | for i, k := range keys {
17 | v := params[k]
18 | if v == "" {
19 | continue
20 | }
21 | vs := ObjToStr(v)
22 |
23 | signStr = fmt.Sprintf("%s=%s", k, vs)
24 |
25 | if i != len(keys)-1 {
26 | signStr += "&"
27 | }
28 | }
29 | return signStr
30 | }
31 |
32 | func ObjToStr(v interface{}) string {
33 | var strV string
34 | switch v.(type) {
35 |
36 | case int:
37 | strV = fmt.Sprintf("%d", v)
38 | case uint:
39 | strV = fmt.Sprintf("%d", v)
40 | case int64:
41 | strV = fmt.Sprintf("%d", v)
42 | case uint64:
43 | strV = fmt.Sprintf("%d", v)
44 | case int8:
45 | strV = fmt.Sprintf("%d", v)
46 | case uint8:
47 | strV = fmt.Sprintf("%d", v)
48 | case int16:
49 | strV = fmt.Sprintf("%d", v)
50 | case uint16:
51 | strV = fmt.Sprintf("%d", v)
52 | case int32:
53 | strV = fmt.Sprintf("%d", v)
54 | case uint32:
55 | strV = fmt.Sprintf("%s", v)
56 | case string:
57 | strV = fmt.Sprintf("%s", v)
58 | case float32:
59 | strV = fmt.Sprintf("%s", v)
60 | case float64:
61 | strV = fmt.Sprintf("%s", v)
62 | default:
63 | strV = fmt.Sprintf("%s", v)
64 | }
65 | return strV
66 | }
67 |
--------------------------------------------------------------------------------
/pkg/util/stringbuffer.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import (
4 | "bytes"
5 | "log"
6 | "strconv"
7 | )
8 |
9 | // 内嵌bytes.Buffer,支持连写
10 | type Buffer struct {
11 | *bytes.Buffer
12 | }
13 |
14 | func NewBuffer() *Buffer {
15 | return &Buffer{Buffer: new(bytes.Buffer)}
16 | }
17 |
18 | func (b *Buffer) Append(i interface{}) *Buffer {
19 | switch val := i.(type) {
20 | case int:
21 | b.append(strconv.Itoa(val))
22 | case int64:
23 | b.append(strconv.FormatInt(val, 10))
24 | case uint:
25 | b.append(strconv.FormatUint(uint64(val), 10))
26 | case uint64:
27 | b.append(strconv.FormatUint(val, 10))
28 | case string:
29 | b.append(val)
30 | case []byte:
31 | b.Write(val)
32 | case rune:
33 | b.WriteRune(val)
34 | }
35 |
36 | return b
37 | }
38 |
39 | func (b *Buffer) append(s string) *Buffer {
40 | defer func() {
41 | if err := recover(); err != nil {
42 | log.Println("*****内存不够了!******")
43 | }
44 | }()
45 |
46 | b.WriteString(s)
47 | return b
48 | }
--------------------------------------------------------------------------------
/pkg/util/time.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import "time"
4 |
5 | func ToyyyyMMddHHmm(tm time.Time) string {
6 |
7 | return tm.Format("2006-01-02 15:04")
8 | }
9 |
10 | func ToyyyyMMddHHmmss(tm time.Time) string {
11 |
12 | return tm.Format("2006-01-02 15:04:05")
13 | }
14 |
15 | func ToyyyyMM2(tm time.Time) string {
16 |
17 | return tm.Format("200601")
18 | }
19 |
20 | func ToyyyyMMdd(tm time.Time) string {
21 |
22 | return tm.Format("20060102")
23 | }
24 |
25 | func PareTimeStrForYYYYMMdd(timeStr string) (time.Time, error) {
26 | return time.Parse("20060102", timeStr)
27 | }
28 |
29 | //带-的日期
30 | func Toyyyy_MM_dd(tm time.Time) string {
31 |
32 | return tm.Format("2006-01-02")
33 | }
34 |
35 | func Toyyyy_MM(tm time.Time) string {
36 |
37 | return tm.Format("2006-01")
38 | }
39 |
40 | func PareTimeStrForYYYY_mm_dd(timeStr string) (time.Time, error) {
41 | return time.Parse("2006-01-02", timeStr)
42 | }
43 |
--------------------------------------------------------------------------------
/pkg/util/yuan_cent.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import "fmt"
4 |
5 | // YuanToCent 元转分
6 | func YuanToCent(yuan float64) int64 {
7 | dec, err := NewFromString(fmt.Sprintf("%0.2f", yuan))
8 | CheckErr(err)
9 | m, err := NewFromString("100")
10 | CheckErr(err)
11 |
12 | return dec.Mul(m).IntPart()
13 | }
14 |
15 | // CentToYuan 分转元
16 | func CentToYuan(cent int64) float64 {
17 | centDec, err := NewFromString(fmt.Sprintf("%d", cent))
18 | CheckErr(err)
19 | mDec, err := NewFromString(fmt.Sprintf("%d", 100))
20 | CheckErr(err)
21 |
22 | result, _ := centDec.Div(mDec).Round(2).Float64()
23 | CheckErr(err)
24 | return result
25 | }
26 |
--------------------------------------------------------------------------------
/pkg/wait/wait.go:
--------------------------------------------------------------------------------
1 | package wait
2 |
3 | import (
4 | "log"
5 | "sync"
6 | )
7 |
8 | const (
9 | // To avoid lock contention we use an array of list struct (rw mutex & map)
10 | // for the id argument, we apply mod operation and uses its remainder to
11 | // index into the array and find the corresponding element.
12 | defaultListElementLength = 64
13 | )
14 |
15 | // Wait Wait
16 | type Wait interface {
17 | // Register waits returns a chan that waits on the given ID.
18 | // The chan will be triggered when Trigger is called with
19 | // the same ID.
20 | Register(id uint64) <-chan interface{}
21 | // Trigger triggers the waiting chans with the given ID.
22 | Trigger(id uint64, x interface{})
23 | IsRegistered(id uint64) bool
24 | }
25 |
26 | type list struct {
27 | e []listElement
28 | }
29 |
30 | type listElement struct {
31 | l sync.RWMutex
32 | m map[uint64]chan interface{}
33 | }
34 |
35 | // New creates a Wait.
36 | func New() Wait {
37 | res := list{
38 | e: make([]listElement, defaultListElementLength),
39 | }
40 | for i := 0; i < len(res.e); i++ {
41 | res.e[i].m = make(map[uint64]chan interface{})
42 | }
43 | return &res
44 | }
45 |
46 | func (w *list) Register(id uint64) <-chan interface{} {
47 | idx := id % defaultListElementLength
48 | newCh := make(chan interface{}, 1)
49 | w.e[idx].l.Lock()
50 | defer w.e[idx].l.Unlock()
51 | if _, ok := w.e[idx].m[id]; !ok {
52 | w.e[idx].m[id] = newCh
53 | } else {
54 | log.Panicf("dup id %x", id)
55 | }
56 | return newCh
57 | }
58 |
59 | func (w *list) Trigger(id uint64, x interface{}) {
60 | idx := id % defaultListElementLength
61 | w.e[idx].l.Lock()
62 | ch := w.e[idx].m[id]
63 | delete(w.e[idx].m, id)
64 | w.e[idx].l.Unlock()
65 | if ch != nil {
66 | ch <- x
67 | close(ch)
68 | }
69 | }
70 |
71 | func (w *list) IsRegistered(id uint64) bool {
72 | idx := id % defaultListElementLength
73 | w.e[idx].l.RLock()
74 | defer w.e[idx].l.RUnlock()
75 | _, ok := w.e[idx].m[id]
76 | return ok
77 | }
78 |
--------------------------------------------------------------------------------
/pkg/wkevent/event.go:
--------------------------------------------------------------------------------
1 | package wkevent
2 |
3 | import "github.com/gocraft/dbr/v2"
4 |
5 | type Type int
6 |
7 | const (
8 | // None 无
9 | None Type = iota
10 | // Message 发送消息事件
11 | Message
12 | // CMD CMD
13 | CMD
14 | )
15 |
16 | func (t Type) Int() int {
17 | return int(t)
18 | }
19 |
20 | type Status int
21 |
22 | const (
23 | Wait Type = iota // 等待发布
24 | Success // 发布重构
25 | Fail
26 | )
27 |
28 | func (s Status) Int() int {
29 | return int(s)
30 | }
31 |
32 | type Data struct {
33 | Event string // 事件标示
34 | Type Type // 事件类型
35 | Data interface{} // 事件数据
36 | }
37 | type Event interface {
38 | // 开启事件
39 | Begin(data *Data, tx *dbr.Tx) (int64, error)
40 | // 提交事件
41 | Commit(eventId int64)
42 | }
43 |
--------------------------------------------------------------------------------
/pkg/wkhook/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative ./pkg/wkhook/webhook.proto
--------------------------------------------------------------------------------
/pkg/wkhook/webhook.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package wkhook;
4 |
5 | option go_package = "./;wkhook";
6 |
7 | service WebhookService {
8 | // 发送webhook事件
9 | rpc SendWebhook (EventReq) returns (EventResp);
10 | }
11 |
12 | enum EventStatus {
13 | Error = 0;
14 | Success = 1;
15 | }
16 |
17 | message EventReq {
18 | string event = 1;
19 | bytes data = 2;
20 | }
21 |
22 | message EventResp {
23 | EventStatus status = 1;
24 | bytes data = 2;
25 | }
--------------------------------------------------------------------------------
/pkg/wkrsa/rsa.go:
--------------------------------------------------------------------------------
1 | package wkrsa
2 |
3 | import (
4 | "crypto"
5 | "crypto/md5"
6 | "crypto/rand"
7 | "crypto/rsa"
8 | "crypto/x509"
9 | "encoding/base64"
10 | "encoding/pem"
11 | "errors"
12 | )
13 |
14 | // Sign rsa签名
15 | // pemPrivKey 私钥key 类似 -----BEGIN RSA PRIVATE KEY-----
16 | // xxxx
17 | // -----END RSA PRIVATE KEY-----
18 | func SignWithMD5(data []byte, pemPrivKey []byte) (string, error) {
19 | hashMd5 := md5.Sum(data)
20 | hashed := hashMd5[:]
21 | block, _ := pem.Decode(pemPrivKey)
22 | if block == nil {
23 | return "", errors.New("private key error")
24 | }
25 | privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
26 | if err != nil {
27 | return "", err
28 | }
29 | signature, err := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.MD5, hashed)
30 | return base64.StdEncoding.EncodeToString(signature), err
31 | }
32 |
--------------------------------------------------------------------------------
/pkg/wkrsa/rsa_test.go:
--------------------------------------------------------------------------------
1 | package wkrsa
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 | )
7 |
8 | func TestSignWithMD5(t *testing.T) {
9 |
10 | dz := `-----BEGIN RSA PRIVATE KEY-----
11 | MIIEowIBAAKCAQEAxGbKnrOumh0r4FfRDL3NIGP7scxxvsVupktTdFqf8TgHLT67
12 | qfMXxM/JSAmNxJVz4zS8zVla4FlTJ4kyh3IJW+65vzXKX/errnI0gNk0p3IME+Wd
13 | LDetwy04Dk40B4GhMrfnomMff6W7UXG4fyLX9gzCIhkAeLNNl6eZS8IOgFULWZRO
14 | pYdlylncY4EQiv6ljXQtKmTIxvi7ivS/9sSFqgS+qnbd0QexrZlM1D2O4abFgrjs
15 | BaFHC4zGrg3TztiWynGUSOpjFv2v92doxDgSxP69xNry9Vq+6kERJpbqvJ3ujUEY
16 | T4bLk8avXNjyMg79/xNeiQ4gxvSHj3v3YOOlIQIDAQABAoIBADbY9fDIARSs3Nnz
17 | 7D+Aqc5H3bxTedhqznHGS3IM9OmqWea6xDG734Fo/a8Oa/bgPdLPoYI/V++bQmui
18 | FuhYYmC4FEtfvDp8sgcvgZYSEnBImzLbRr9YdUAyWps0H7eQ7fF6BkgFIoDFScB+
19 | 36Uxl9nwyi43iTgr6plVhqvvb5lKqPQTZWWI50hs0EYcADngy2st6+sJxJXylCI4
20 | w3vjXhE9WdwBk54QD6FPPrzRHWpqHDjPcuyxbVijEoz0YMdO+tnHweUn+YnQnHxi
21 | rswF7b9/DL8mhIX67SKqF7wM+RCgrweVswNCcEsZyfCtMKgI7uwDjaDkvhI+XaYq
22 | vfNUe9kCgYEAzK+Zb3A4ZxPWhYrubqmGYa7KvmtetRO5LAyWXMFH5vzU+TKzDcJm
23 | qZTxhzH8yt43PgrjOL5C+X2cIkNfEXHl9oIuuAMbMDSqThvox+o8Lsz5gTKy9/rR
24 | sucBRc41N/axLalVpbevD3Q3+2WjH25ap7h0RsZsladshEoxXI8jpCMCgYEA9aOD
25 | SSoywbVrsMXhr2wgxNXUuQGxWH6x3ZbAG86xxerElVwT06SbowDO77xFmiQsUvlW
26 | BpYrVPBg48B/Sgvvb0mKHhXVe8Maegq6bCofVpAU9IhDZokwIGJgEm2MgziZJAe+
27 | 3/2DCFV22olrkqDhllqBTenuSyiIfWcGHr+zM+sCgYB0EWNVgPJK6UHtajH4iKMO
28 | Q1rujd4fmnaXlu+w211Vi6uNQAWu2Lz0juRDQMJTm50BzpS4uZMq/OKLv15qewbn
29 | OT0a1ZAWTtcAAe2HZ7kG5O7bJ4+69PzykPH0zpD5Eie4d9x8Y2OexM11/lV43lAD
30 | 6aHt/FjYqB7uCVBiZzzTtwKBgQDGY+jN9+IEp4UxwbCUYQ1aTKXBQoe8xJ7dLDs+
31 | ekMEaaeaRkLRJdp53VZFM9c3Nk4COdTr/u9Ca96lM7zazib0x/1gbRv+GEbTGMUW
32 | RTMIU9hI46EkOFsBXNLhL09UUCsHeaYE/JiO64/R0zlptLxeFfznM6+9TiBmwAWm
33 | YgfXPwKBgDn1n2aWpGpH/cz8AQapMxqo7YK2Lc/Vwqw7O4mmNQ0TOJOgECHrWJX2
34 | 2UfOnyh8mDNFq9Udt4XMkfH5aPmWF7ejl2ddz3A4kdz5DMaVgRPktJh6nFAllKz/
35 | R0a+FMM2fsqqRjYN4Zf84pJvWnIy7dG/pXKq8AAWkV7iJNVpshGb
36 | -----END RSA PRIVATE KEY-----`
37 | fmt.Println("privateKeyBuff.Bytes()-->", dz)
38 |
39 | _, err := SignWithMD5([]byte("test"), []byte(dz))
40 | if err != nil {
41 | panic(err)
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/testenv/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | version: '3.1'
2 | services:
3 | redis:
4 | image: redis
5 | restart: always
6 | ports:
7 | - 6379:6379
8 | mysql:
9 | image: mysql:8.0.33
10 | command: --default-authentication-plugin=mysql_native_password
11 | restart: always
12 | environment:
13 | MYSQL_ROOT_PASSWORD: demo
14 | MYSQL_DATABASE: test
15 | ports:
16 | - 3306:3306
17 | wukongim: # wukongim底层通讯服务
18 | image: wukongim/wukongim:latest
19 | restart: always
20 | volumes:
21 | - ./wukongimdata:/root/wukongim
22 | environment:
23 | WK_MODE: "debug"
24 | WK_EXTERNAL_IP: "192.168.99.219"
25 | ports:
26 | - 5001:5001 # http api 端口
27 | - 5100:5100 # tcp端口
28 | - 5200:5200 # websocket端口
29 | - 5300:5300 # monitor端口
30 | minio: # minio文件管理服务
31 | image: minio/minio:latest # use a remote image
32 | restart: always
33 | command: "server /data --console-address ':9001'"
34 | ports:
35 | - "9000:9000"
36 | - "9001:9001"
37 | environment:
38 | - MINIO_ROOT_USER=admin
39 | - MINIO_ROOT_PASSWORD=12345678
40 | volumes:
41 | - ./miniodata:/data
--------------------------------------------------------------------------------