130 | description: |-
131 | Auto sync github labels
132 | kubbot && openimbot
133 | license: MIT
134 | formats:
135 | - apk
136 | - deb
137 | - rpm
138 | - termux.deb # Since: v1.11
139 | - archlinux # Since: v1.13
140 | dependencies:
141 | - git
142 | recommends:
143 | - golang
144 |
145 |
146 | # The lines beneath this are called `modelines`. See `:help modeline`
147 | # Feel free to remove those if you don't want/use them.
148 | # yaml-language-server: $schema=https://goreleaser.com/static/schema.json
149 | # vim: set ts=2 sw=2 tw=0 fo=cnqoj
150 |
151 | # Default: './dist'
152 | dist: ./_output/dist
153 |
154 | publishers:
155 | - name: "fury.io"
156 | ids:
157 | - packages
158 | dir: "{{ dir .ArtifactPath }}"
159 | cmd: |
160 | bash -c '
161 | if [[ "{{ .Tag }}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
162 | curl -F package=@{{ .ArtifactName }} https://{{ .Env.FURY_TOKEN }}@push.fury.io/openim/
163 | else
164 | echo "Skipping deployment: Non-production release detected"
165 | fi'
166 |
167 | # snapcrafts:
168 | # - name_template: "{{ .ProjectName }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"
169 | # summary: openim is open source instant messaging
170 | # description: |
171 | # OpenIM yyds
172 | # grade: stable
173 | # confinement: classic
174 | # publish: true
175 |
176 | checksum:
177 | name_template: "{{ .ProjectName }}_checksums.txt"
178 | algorithm: sha256
179 |
180 | snapshot:
181 | name_template: "{{ .Tag }}-next"
182 |
183 | release:
184 | prerelease: auto
185 | footer: |
186 |
187 | ## Welcome to the {{ .Tag }} release of [chat](https://github.com/openim-sigs/oimws)!🎉🎉!
188 |
189 | **Full Changelog**: https://github.com/openim-sigs/oimws/compare/{{ .PreviousTag }}...{{ .Tag }}
190 |
191 | ## Helping out
192 |
193 | + We release logs are recorded on [✨ CHANGELOG](https://github.com/OpenIMSDK/Open-IM-Server/blob/main/CHANGELOG/CHANGELOG.md)
194 |
195 | + For information on versions of OpenIM and how to maintain branches, read [📚this article](https://github.com/OpenIMSDK/Open-IM-Server/blob/main/docs/conversions/version.md)
196 |
197 | + If you wish to use mirroring, read OpenIM's [image management policy](https://github.com/OpenIMSDK/Open-IM-Server/blob/main/docs/conversions/images.md)
198 |
199 | **Want to be one of them 😘?**
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 | > **Note**
214 | > @openimbot and @kubbot have made great contributions to the community as community 🤖robots(@openimsdk/bot), respectively.
215 | > Thanks to the @openimsdk/openim team for all their hard work on this release.
216 | > Thank you to all the [💕developers and contributors](https://github.com/openim-sigs/oimws/graphs/contributors), people from all over the world, OpenIM brings us together
217 | > Contributions to this project are welcome! Please see [CONTRIBUTING.md](https://github.com/OpenIMSDK/Open-IM-Server/blob/main/CONTRIBUTING.md) for details.
218 |
219 |
220 | ## Get Involved with OpenIM!
221 |
222 | **Here are some ways to get involved with the OpenIM community:**
223 |
224 | 📢 **Slack Channel**: Join our Slack channels for discussions, communication, and support. Click [here](https://openimsdk.slack.com) to join the chat Slack team channel.
225 |
226 | 📧 **Gmail Contact**: If you have any questions, suggestions, or feedback for our open-source projects, please feel free to [contact us via email](https://mail.google.com/mail/?view=cm&fs=1&tf=1&to=info@openim.io).
227 |
228 | 📖 **Blog**: Stay up-to-date with OpenIM-Server projects and trends by reading our [blog](https://openim.io/). We share the latest developments, tech trends, and other interesting information related to OpenIM.
229 |
230 | 📱 **WeChat**: Add us on WeChat (QR Code) and indicate that you are a user or developer of chat. We'll process your request as soon as possible.
231 |
232 | Remember, your contributions play a vital role in making OpenIM successful, and we look forward to your active participation in our community! 🙌
233 |
234 | # webhook
235 | # announce:
236 | # slack:
237 | # enabled: false
238 | # message_template: "slack {{ .Tag }} is out! Check it out: https://github.com/OpenIMSDK/Open-IM-Server/releases/tag/{{ .Tag }}"
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 openim open source community(public welfare community)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # Copyright 2023 OpenIM. All rights reserved.
2 | # Use of this source code is governed by a MIT style
3 | # license that can be found in the LICENSE file.
4 |
5 | ###################################=> common commands <=#############################################
6 | # ========================== Capture Environment ===============================
7 | # get the repo root and output path
8 | ROOT_PACKAGE=github.com/openim-sigs/oimws
9 | OUT_DIR=$(REPO_ROOT)/_output
10 | # ==============================================================================
11 |
12 | # define the default goal
13 | #
14 |
15 | SHELL := /bin/bash
16 | DIRS=$(shell ls)
17 | GO=go
18 |
19 | .DEFAULT_GOAL := help
20 |
21 | # include the common makefile
22 | COMMON_SELF_DIR := $(dir $(lastword $(MAKEFILE_LIST)))
23 | # ROOT_DIR: root directory of the code base
24 | ifeq ($(origin ROOT_DIR),undefined)
25 | ROOT_DIR := $(abspath $(shell cd $(COMMON_SELF_DIR)/. && pwd -P))
26 | endif
27 | # OUTPUT_DIR: The directory where the build output is stored.
28 | ifeq ($(origin OUTPUT_DIR),undefined)
29 | OUTPUT_DIR := $(ROOT_DIR)/_output
30 | $(shell mkdir -p $(OUTPUT_DIR))
31 | endif
32 |
33 | # BIN_DIR: The directory where the build output is stored.
34 | ifeq ($(origin BIN_DIR),undefined)
35 | BIN_DIR := $(OUTPUT_DIR)/bin
36 | $(shell mkdir -p $(BIN_DIR))
37 | endif
38 |
39 | ifeq ($(origin TOOLS_DIR),undefined)
40 | TOOLS_DIR := $(OUTPUT_DIR)/tools
41 | $(shell mkdir -p $(TOOLS_DIR))
42 | endif
43 |
44 | ifeq ($(origin TMP_DIR),undefined)
45 | TMP_DIR := $(OUTPUT_DIR)/tmp
46 | $(shell mkdir -p $(TMP_DIR))
47 | endif
48 |
49 | ifeq ($(origin VERSION), undefined)
50 | VERSION := $(shell git describe --tags --always --match="v*" --dirty | sed 's/-/./g') #v2.3.3.631.g00abdc9b.dirty
51 | endif
52 |
53 | # Check if the tree is dirty. default to dirty(maybe u should commit?)
54 | GIT_TREE_STATE:="dirty"
55 | ifeq (, $(shell git status --porcelain 2>/dev/null))
56 | GIT_TREE_STATE="clean"
57 | endif
58 | GIT_COMMIT:=$(shell git rev-parse HEAD)
59 |
60 | IMG ?= openim_chat:latest
61 |
62 | BUILDFILE = "./cmd/main.go"
63 | BUILDAPP = "$(OUTPUT_DIR)/"
64 |
65 | # Define the directory you want to copyright
66 | CODE_DIRS := $(ROOT_DIR)/ #$(ROOT_DIR)/pkg $(ROOT_DIR)/core $(ROOT_DIR)/integrationtest $(ROOT_DIR)/lib $(ROOT_DIR)/mock $(ROOT_DIR)/db $(ROOT_DIR)/openapi
67 | FINDS := find $(CODE_DIRS)
68 |
69 | ifndef V
70 | MAKEFLAGS += --no-print-directory
71 | endif
72 |
73 | # The OS must be linux when building docker images
74 | # !WARNING: linux_mips64 linux_mips64le
75 | PLATFORMS ?= linux_s390x darwin_amd64 windows_amd64 linux_amd64 linux_arm64 linux_ppc64le
76 |
77 | # Set a specific PLATFORM
78 | ifeq ($(origin PLATFORM), undefined)
79 | ifeq ($(origin GOOS), undefined)
80 | GOOS := $(shell go env GOOS)
81 | endif
82 | ifeq ($(origin GOARCH), undefined)
83 | GOARCH := $(shell go env GOARCH)
84 | endif
85 | PLATFORM := $(GOOS)_$(GOARCH)
86 | # Use linux as the default OS when building images
87 | IMAGE_PLAT := linux_$(GOARCH)
88 | else
89 | GOOS := $(word 1, $(subst _, ,$(PLATFORM)))
90 | GOARCH := $(word 2, $(subst _, ,$(PLATFORM)))
91 | IMAGE_PLAT := $(PLATFORM)
92 | endif
93 |
94 | # Linux command settings
95 | FIND := find . ! -path './image/*' ! -path './vendor/*' ! -path './bin/*'
96 | XARGS := xargs -r
97 |
98 | # ==============================================================================
99 | # COMMA: Concatenate multiple strings to form a list of strings
100 | COMMA := ,
101 | # SPACE: Used to separate strings
102 | SPACE :=
103 | # SPACE: Replace multiple consecutive Spaces with a single space
104 | SPACE +=
105 |
106 | PID = $(shell lsof -ti:10003)
107 | PORT = 10003
108 |
109 | # ==============================================================================
110 | # Build definition
111 |
112 | GO_SUPPORTED_VERSIONS ?= 1.19|1.20|1.21|1.22
113 | GO_LDFLAGS += -X $(VERSION_PACKAGE).GitVersion=$(VERSION) \
114 | -X $(VERSION_PACKAGE).GitCommit=$(GIT_COMMIT) \
115 | -X $(VERSION_PACKAGE).GitTreeState=$(GIT_TREE_STATE) \
116 | -X $(VERSION_PACKAGE).BuildDate=$(shell date -u +'%Y-%m-%dT%H:%M:%SZ')
117 | ifneq ($(DLV),)
118 | GO_BUILD_FLAGS += -gcflags "all=-N -l"
119 | LDFLAGS = ""
120 | endif
121 | GO_BUILD_FLAGS += -ldflags "$(GO_LDFLAGS)"
122 |
123 | ifeq ($(GOOS),windows)
124 | GO_OUT_EXT := .exe
125 | endif
126 |
127 | ifeq ($(ROOT_PACKAGE),)
128 | $(error the variable ROOT_PACKAGE must be set prior to including golang.mk)
129 | endif
130 |
131 | GOPATH := $(shell go env GOPATH)
132 | ifeq ($(origin GOBIN), undefined)
133 | GOBIN := $(GOPATH)/bin
134 | endif
135 |
136 | EXCLUDE_TESTS=github.com/openim-sigs/oimws/test
137 |
138 | # ==============================================================================
139 | # Build
140 |
141 | ## all: Build all the necessary targets.
142 | .PHONY: all
143 | all: tidy style test build
144 |
145 | ## build: Build binaries by default.
146 | .PHONY: build
147 | build:
148 | @$(GO) build $(GO_BUILD_FLAGS) -o $(BIN_DIR)/oimws $(BUILDFILE)
149 |
150 | ## start: Start the service.
151 | .PHONY: start
152 | start:
153 | @mkdir -p db
154 | @mkdir -p logs
155 | @nohup $(BIN_DIR)/oimws >> logs/oimws.log 2>&1 &
156 |
157 | ## check: Check binaries is
158 | .PHONY: check
159 | check:
160 | @echo "Checking port $(PORT)..."
161 | @if [ -z "$(PID)" ]; then \
162 | echo "Port $(PORT) is not in use."; \
163 | else \
164 | echo "Port $(PORT) is in use by PID $(PID)."; \
165 | ps -p $(PID) -o start,etime,cmd; \
166 | fi
167 |
168 | ## stop: Stop the service.
169 | .PHONY: stop
170 | stop:
171 | @echo "Stopping service with PID $(PID)..."
172 | @if [ -n "$(PID)" ]; then \
173 | kill $(PID); \
174 | else \
175 | echo "No service to stop on port $(PORT)."; \
176 | fi
177 | # ==============================================================================
178 | ## tidy: tidy go.mod
179 | .PHONY: tidy
180 | tidy:
181 | @$(GO) mod tidy
182 |
183 | ## style: Code style -> fmt,vet,lint
184 | .PHONY: style
185 | style: fmt vet lint
186 |
187 | ## fmt: Run go fmt against code.
188 | .PHONY: fmt
189 | fmt:
190 | @$(GO) fmt ./...
191 |
192 | ## vet: Run go vet against code.
193 | .PHONY: vet
194 | vet:
195 | @$(GO) vet ./...
196 |
197 | ## generate: Run go generate against code.
198 | .PHONY: generate
199 | generate:
200 | @$(GO) generate ./...
201 |
202 | ## lint: Run go lint against code.
203 | .PHONY: lint
204 | lint: tools.verify.golangci-lint
205 | @echo "===========> Run golangci to lint source codes"
206 | @$(TOOLS_DIR)/golangci-lint run -c $(ROOT_DIR)/.golangci.yml $(ROOT_DIR)/...
207 |
208 | ## test: Run unit test
209 | .PHONY: test
210 | test:
211 | @$(GO) test ./...
212 |
213 | ## cover: Run unit test with coverage.
214 | .PHONY: cover
215 | cover: test
216 | @$(GO) test -cover
217 |
218 | ## clean: Clean all builds.
219 | .PHONY: clean
220 | clean:
221 | @echo "===========> Cleaning all builds TMP_DIR($(TMP_DIR)) AND BIN_DIR($(BIN_DIR))"
222 | @-rm -vrf $(TMP_DIR) $(BIN_DIR)
223 | @echo "===========> End clean..."
224 |
225 | ## help: Show this help info.
226 | .PHONY: help
227 | help: Makefile
228 | @printf "\n\033[1mUsage: make ...\033[0m\n\n\\033[1mTargets:\\033[0m\n\n"
229 | @sed -n 's/^##//p' $< | awk -F':' '{printf "\033[36m%-28s\033[0m %s\n", $$1, $$2}' | sed -e 's/^/ /'
230 |
231 | ######################################=> common tools<= ############################################
232 | # tools
233 |
234 | BUILD_TOOLS ?= go-gitlint golangci-lint goimports addlicense deepcopy-gen conversion-gen ginkgo go-junit-report
235 |
236 | ## tools.verify.%: Check if a tool is installed and install it
237 | .PHONY: tools.verify.%
238 | tools.verify.%:
239 | @echo "===========> Verifying $* is installed"
240 | @if [ ! -f $(TOOLS_DIR)/$* ]; then GOBIN=$(TOOLS_DIR) $(MAKE) tools.install.$*; fi
241 | @echo "===========> $* is install in $(TOOLS_DIR)/$*"
242 |
243 | # tools: Install a must tools
244 | .PHONY: tools
245 | tools: $(addprefix tools.verify., $(BUILD_TOOLS))
246 |
247 | # tools.install.%: Install a single tool in $GOBIN/
248 | .PHONY: tools.install.%
249 | tools.install.%:
250 | @echo "===========> Installing $,The default installation path is $(GOBIN)/$*"
251 | @$(MAKE) install.$*
252 |
253 | .PHONY: install.golangci-lint
254 | install.golangci-lint:
255 | @$(GO) install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
256 |
257 | .PHONY: install.goimports
258 | install.goimports:
259 | @$(GO) install golang.org/x/tools/cmd/goimports@latest
260 |
261 | .PHONY: install.addlicense
262 | install.addlicense:
263 | @$(GO) install github.com/google/addlicense@latest
264 |
265 | .PHONY: install.deepcopy-gen
266 | install.deepcopy-gen:
267 | @$(GO) install k8s.io/code-generator/cmd/deepcopy-gen@latest
268 |
269 | .PHONY: install.conversion-gen
270 | install.conversion-gen:
271 | @$(GO) install k8s.io/code-generator/cmd/conversion-gen@latest
272 |
273 | .PHONY: install.ginkgo
274 | install.ginkgo:
275 | @$(GO) install github.com/onsi/ginkgo/ginkgo@v1.16.2
276 |
277 | .PHONY: install.go-gitlint
278 | # wget -P _output/tools/ https://openim-1306374445.cos.ap-guangzhou.myqcloud.com/openim/tools/go-gitlint
279 | # go install github.com/antham/go-gitlint/cmd/gitlint@latest
280 | install.go-gitlint:
281 | @wget -q https://openim-1306374445.cos.ap-guangzhou.myqcloud.com/openim/tools/go-gitlint -O ${TOOLS_DIR}/go-gitlint
282 | @chmod +x ${TOOLS_DIR}/go-gitlint
283 |
284 | .PHONY: install.go-junit-report
285 | install.go-junit-report:
286 | @$(GO) install github.com/jstemmer/go-junit-report@latest
287 |
288 | # ==============================================================================
289 | # Tools that might be used include go gvm, cos
290 | #
291 |
292 | ## install.kube-score: Install kube-score, used to check kubernetes yaml files
293 | .PHONY: install.kube-score
294 | install.kube-score:
295 | @$(GO) install github.com/zegl/kube-score/cmd/kube-score@latest
296 |
297 | ## install.kubeconform: Install kubeconform, used to check kubernetes yaml files
298 | .PHONY: install.kubeconform
299 | install.kubeconform:
300 | @$(GO) install github.com/yannh/kubeconform/cmd/kubeconform@latest
301 |
302 | ## install.gsemver: Install gsemver, used to generate semver
303 | .PHONY: install.gsemver
304 | install.gsemver:
305 | @$(GO) install github.com/arnaud-deprez/gsemver@latest
306 |
307 | ## install.git-chglog: Install git-chglog, used to generate changelog
308 | .PHONY: install.git-chglog
309 | install.git-chglog:
310 | @$(GO) install github.com/git-chglog/git-chglog/cmd/git-chglog@latest
311 |
312 | ## install.github-release: Install github-release, used to create github release
313 | .PHONY: install.github-release
314 | install.github-release:
315 | @$(GO) install github.com/github-release/github-release@latest
316 |
317 | ## install.coscli: Install coscli, used to upload files to cos
318 | # example: ./coscli cp/sync -r /root/workspaces/kubecub/chat/ cos://kubecub-1306374445/code/ -e cos.ap-hongkong.myqcloud.com
319 | # https://cloud.tencent.com/document/product/436/71763
320 | # kubecub/*
321 | # - code/
322 | # - docs/
323 | # - images/
324 | # - scripts/
325 | .PHONY: install.coscli
326 | install.coscli:
327 | @wget -q https://github.com/tencentyun/coscli/releases/download/v0.13.0-beta/coscli-linux -O ${TOOLS_DIR}/coscli
328 | @chmod +x ${TOOLS_DIR}/coscli
329 |
330 | ## install.coscmd: Install coscmd, used to upload files to cos
331 | .PHONY: install.coscmd
332 | install.coscmd:
333 | @if which pip &>/dev/null; then pip install coscmd; else pip3 install coscmd; fi
334 |
335 | ## install.delve: Install delve, used to debug go program
336 | .PHONY: install.delve
337 | install.delve:
338 | @$(GO) install github.com/go-delve/delve/cmd/dlv@latest
339 |
340 | ## install.air: Install air, used to hot reload go program
341 | .PHONY: install.air
342 | install.air:
343 | @$(GO) install github.com/cosmtrek/air@latest
344 |
345 | ## install.gvm: Install gvm, gvm is a Go version manager, built on top of the official go tool.
346 | .PHONY: install.gvm
347 | install.gvm:
348 | @echo "===========> Installing gvm,The default installation path is ~/.gvm/scripts/gvm"
349 | @bash < <(curl -s -S -L https://raw.gitee.com/moovweb/gvm/master/binscripts/gvm-installer)
350 | @$(shell source /root/.gvm/scripts/gvm)
351 |
352 | ## install.golines: Install golines, used to format long lines
353 | .PHONY: install.golines
354 | install.golines:
355 | @$(GO) install github.com/segmentio/golines@latest
356 |
357 | ## install.go-mod-outdated: Install go-mod-outdated, used to check outdated dependencies
358 | .PHONY: install.go-mod-outdated
359 | install.go-mod-outdated:
360 | @$(GO) install github.com/psampaz/go-mod-outdated@latest
361 |
362 | ## install.mockgen: Install mockgen, used to generate mock functions
363 | .PHONY: install.mockgen
364 | install.mockgen:
365 | @$(GO) install github.com/golang/mock/mockgen@latest
366 |
367 | ## install.gotests: Install gotests, used to generate test functions
368 | .PHONY: install.gotests
369 | install.gotests:
370 | @$(GO) install github.com/cweill/gotests/gotests@latest
371 |
372 | ## install.protoc-gen-go: Install protoc-gen-go, used to generate go source files from protobuf files
373 | .PHONY: install.protoc-gen-go
374 | install.protoc-gen-go:
375 | @$(GO) install github.com/golang/protobuf/protoc-gen-go@latest
376 |
377 | ## install.cfssl: Install cfssl, used to generate certificates
378 | .PHONY: install.cfssl
379 | install.cfssl:
380 | @$(ROOT_DIR)/scripts/install/install.sh OpenIM::install::install_cfssl
381 |
382 | ## install.depth: Install depth, used to check dependency tree
383 | .PHONY: install.depth
384 | install.depth:
385 | @$(GO) install github.com/KyleBanks/depth/cmd/depth@latest
386 |
387 | ## install.go-callvis: Install go-callvis, used to visualize call graph
388 | .PHONY: install.go-callvis
389 | install.go-callvis:
390 | @$(GO) install github.com/ofabry/go-callvis@latest
391 |
392 | ## install.gothanks: Install gothanks, used to thank go dependencies
393 | .PHONY: install.gothanks
394 | install.gothanks:
395 | @$(GO) install github.com/psampaz/gothanks@latest
396 |
397 | ## install.richgo: Install richgo
398 | .PHONY: install.richgo
399 | install.richgo:
400 | @$(GO) install github.com/kyoh86/richgo@latest
401 |
402 | ## install.rts: Install rts
403 | .PHONY: install.rts
404 | install.rts:
405 | @$(GO) install github.com/galeone/rts/cmd/rts@latest
406 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ⚠️ Project No Longer Maintained
2 | This project is no longer maintained and may contain unpatched vulnerabilities or compatibility issues with modern environments. Feel free to fork and modify the project if needed.
3 | If you need to use jssdk in the new version (v3.8.2+), you can introduce [@openim/client-sdk](https://www.npmjs.com/package/@openim/client-sdk) to directly communicate with IM Server without the need to deploy this project.
4 |
5 | # OIMWS - OpenIM WebSocket Service 😎
6 |
7 | 
8 | [](https://app.codecov.io/github/openim-sigs/oimws)
9 | [](https://goreportcard.com/report/github.com/openim-sigs/oimws)
10 | [](https://godoc.org/github.com/openim-sigs/oimws)
11 |
12 | > :notes: OIMWS (OpenIM WebSocket Service) is a high-performance, scalable WebSocket framework designed specifically for building instant messaging (IM) systems. Harnessing the concurrent capabilities of Go and the real-time communication provided by WebSocket protocol, OIMWS offers a robust backend solution to support modern instant communication needs, ranging from basic message transit to complex session management and network optimization.
13 |
14 | ## Features 🚀
15 |
16 | + **WebSocket Support**: Provides full WebSocket connection handling for high-concurrency client communication.
17 | + **Modular Architecture**: Features a set of decoupled modules for message processing, connection management, and user/group interactions, facilitating development and maintenance.
18 | + **Real-Time Message Processing**: Ensures swift response and distribution of real-time messages for timely and reliable communication.
19 | + **Network Layer Abstraction**: Includes low-level network abstractions allowing for customized protocol and data process flows.
20 | + **Configuration Management**: Simplified configuration management supports rapid deployment and environmental adaptation.
21 | + **Utility Toolkit**: Offers a wide array of utility functions for common operations such as date processing and text manipulation.
22 | + **Automated Testing**: Built-in unit tests ensure the stability of modules and features.
23 | + **Clear Build Scripts**: Makefile support simplifies the build process, enhancing developer productivity.
24 | + **Code Quality Assurance**: Integrates `golangci-lint` to ensure consistency in code quality and style.
25 | + **Sample Code**: Includes examples to demonstrate framework usage, use openim jssdk(10003) accelerating the learning curve for developers.
26 |
27 | ## Quick Start 🚗💨
28 |
29 | Clone the repository to your local machine:
30 | ```bash
31 | git clone https://github.com/openim-sigs/oimws.git
32 | cd oimws
33 | ```
34 |
35 | **Build oimws**
36 |
37 | ```bash
38 | mage
39 | ```
40 |
41 | **Start oimws**
42 |
43 | ```bash
44 | mage start
45 | ```
46 |
47 | **Check oimws status**
48 |
49 | ```bash
50 | mage check
51 | ```
52 |
53 | **Stop oimws Status:**
54 |
55 | ```bash
56 | mage stop
57 | ```
58 |
59 | ### [main](https://github.com/openim-sigs/oimws/tree/main/cmd)
60 |
61 | An encapsulated framework within `jssdk` connecting to `openim-sdk-core`, providing streamlined management and integration of WebSocket, TCP, and HTTP protocols in the OpenIM ecosystem.
62 |
63 |
64 | ### code
65 |
66 | the folders of oimws:
67 |
68 | cmd-------------- the main.go folder
69 |
70 |
71 | common----------- common structures and functions, primarily used by the network frame
72 |
73 |
74 | core_func-------- Some functions encapsulate the interface for calling the JS SDK.
75 |
76 |
77 | gate------------- network frame,functions for websocket
78 |
79 |
80 | module----------- the module codes
81 |
82 |
83 | network---------- network frame
84 |
85 |
86 |
87 | ### Cluster solution(Consistent Hashing)
88 |
89 | Using consistent hashing in Nginx typically involves the hash directive within the upstream module.
90 | Starting from Nginx 1.7.2, it supports consistent hashing based on specified variables.
91 | You can use the request parameter sendId as the key for consistent hashing to distribute requests.
92 |
93 | Here's a configuration example that demonstrates how to use consistent hashing for the /ws endpoint to select backend servers:
94 | ```
95 | http {
96 | upstream backend {
97 | # Use userId as the key for consistent hashing
98 | hash $arg_sendId consistent;
99 |
100 | # Define backend servers
101 | server backend1.example.com;
102 | server backend2.example.com;
103 | # ... More backend servers ...
104 | }
105 |
106 | server {
107 | listen 80;
108 |
109 | location /ws {
110 | proxy_pass http://backend;
111 | # ... Other possible proxy settings ...
112 | }
113 |
114 | # ... Other location definitions ...
115 | }
116 | }
117 |
118 | ```
119 | ## Contribution Ⓜ️
120 |
121 | Feel free to contribute to this project by opening issues or submitting pull requests.
122 |
123 |
124 |
125 |
126 |
127 | ## License 🤝
128 |
129 | This project is licensed under the MIT License - see the [LICENSE](./LICENSE) file for details.
130 |
--------------------------------------------------------------------------------
/bootstrap.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 | SETLOCAL
3 |
4 | mage -version >nul 2>&1
5 | IF %ERRORLEVEL% EQU 0 (
6 | echo Mage is already installed.
7 | GOTO DOWNLOAD
8 | )
9 |
10 | go version >nul 2>&1
11 | IF NOT %ERRORLEVEL% EQU 0 (
12 | echo Go is not installed. Please install Go and try again.
13 | exit /b 1
14 | )
15 |
16 | echo Installing Mage...
17 | go install github.com/magefile/mage@latest
18 |
19 | mage -version >nul 2>&1
20 | IF NOT %ERRORLEVEL% EQU 0 (
21 | echo Mage installation failed.
22 | echo Please ensure that %GOPATH%/bin is in your PATH.
23 | exit /b 1
24 | )
25 |
26 | echo Mage installed successfully.
27 |
28 | :DOWNLOAD
29 | go mod download
30 |
31 | ENDLOCAL
32 |
--------------------------------------------------------------------------------
/bootstrap.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | if [[ ":$PATH:" == *":$HOME/.local/bin:"* ]]; then
4 | TARGET_DIR="$HOME/.local/bin"
5 | else
6 | TARGET_DIR="/usr/local/bin"
7 | echo "Using /usr/local/bin as the installation directory. Might require sudo permissions."
8 | fi
9 |
10 | if ! command -v mage &> /dev/null; then
11 | echo "Installing Mage to $TARGET_DIR ..."
12 | GOBIN=$TARGET_DIR go install github.com/magefile/mage@latest
13 | fi
14 |
15 | if ! command -v mage &> /dev/null; then
16 | echo "Mage installation failed."
17 | echo "Please ensure that $TARGET_DIR is in your \$PATH."
18 | exit 1
19 | fi
20 |
21 | echo "Mage installed successfully."
22 |
23 | go mod download
24 |
--------------------------------------------------------------------------------
/cmd/openim-sdk/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/openim-sigs/oimws/pkg/common/cmd"
6 | "os"
7 | "path/filepath"
8 | )
9 |
10 | func main() {
11 | if err := cmd.NewSdkCmd().Exec(); err != nil {
12 | ExitWithError(err)
13 | }
14 | }
15 |
16 | // TODO
17 | func ExitWithError(err error) {
18 | progName := filepath.Base(os.Args[0])
19 | fmt.Fprintf(os.Stderr, "%s exit -1: %+v\n", progName, err)
20 | os.Exit(-1)
21 | }
22 |
--------------------------------------------------------------------------------
/config/config.yml:
--------------------------------------------------------------------------------
1 | openimApi: "http://127.0.0.1:10002"
2 | openimWs: "ws://127.0.0.1:10001"
3 | sdkWsPort: [ 10003 ]
4 | dbDir: "../../../../db/"
5 |
--------------------------------------------------------------------------------
/config/log.yml:
--------------------------------------------------------------------------------
1 |
2 | # Log storage path, default is acceptable, change to a full path if modification is needed
3 | storageLocation: ../../../../logs/
4 | # Log rotation period (in hours), default is acceptable
5 | rotationTime: 24
6 | # Number of log files to retain, default is acceptable
7 | remainRotationCount: 2
8 | # Log level settings: 3 for production environment; 6 for more verbose logging in debugging environments
9 | remainLogLevel: 6
10 | # Whether to output to standard output, default is acceptable
11 | isStdout: false
12 | # Whether to log in JSON format, default is acceptable
13 | isJson: false
14 |
15 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/openim-sigs/oimws
2 |
3 | go 1.21.2
4 |
5 | require (
6 | github.com/OpenIMSDK/tools v0.0.24
7 | github.com/bwmarrin/snowflake v0.3.0
8 | github.com/gorilla/websocket v1.5.1
9 | github.com/mitchellh/mapstructure v1.5.0
10 | github.com/openimsdk/gomake v0.0.12
11 | github.com/openimsdk/openim-sdk-core/v3 v3.5.1
12 | github.com/spf13/cobra v1.8.0
13 | github.com/spf13/viper v1.18.2
14 | github.com/stretchr/testify v1.9.0
15 | github.com/xuexihuang/new_log15 v1.0.0
16 | )
17 |
18 | require (
19 | github.com/google/go-cmp v0.6.0 // indirect
20 | github.com/jinzhu/copier v0.4.0 // indirect
21 | github.com/mattn/go-sqlite3 v1.14.22 // indirect
22 | github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
23 | golang.org/x/image v0.15.0 // indirect
24 | golang.org/x/net v0.22.0 // indirect
25 | golang.org/x/text v0.14.0 // indirect
26 | google.golang.org/grpc v1.62.1 // indirect
27 | gorm.io/driver/sqlite v1.5.5 // indirect
28 | nhooyr.io/websocket v1.8.10 // indirect
29 | )
30 |
31 | require (
32 | github.com/OpenIMSDK/protocol v0.0.40 // indirect
33 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
34 | github.com/fsnotify/fsnotify v1.7.0 // indirect
35 | github.com/go-ole/go-ole v1.2.6 // indirect
36 | github.com/golang/protobuf v1.5.3 // indirect
37 | github.com/hashicorp/hcl v1.0.0 // indirect
38 | github.com/inconshreveable/mousetrap v1.1.0 // indirect
39 | github.com/jinzhu/inflection v1.0.0 // indirect
40 | github.com/jinzhu/now v1.1.5 // indirect
41 | github.com/json-iterator/go v1.1.12 // indirect
42 | github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible // indirect
43 | github.com/lestrrat-go/strftime v1.0.6 // indirect
44 | github.com/magefile/mage v1.15.0 // indirect
45 | github.com/magiconair/properties v1.8.7 // indirect
46 | github.com/mattn/go-colorable v0.1.13 // indirect
47 | github.com/mattn/go-isatty v0.0.19 // indirect
48 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
49 | github.com/modern-go/reflect2 v1.0.2 // indirect
50 | github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
51 | github.com/pelletier/go-toml/v2 v2.1.0 // indirect
52 | github.com/petermattis/goid v0.0.0-20230904192822-1876fd5063bc // indirect
53 | github.com/pkg/errors v0.9.1 // indirect
54 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
55 | github.com/sagikazarmark/locafero v0.4.0 // indirect
56 | github.com/sagikazarmark/slog-shim v0.1.0 // indirect
57 | github.com/shirou/gopsutil v3.21.11+incompatible // indirect
58 | github.com/sourcegraph/conc v0.3.0 // indirect
59 | github.com/spf13/afero v1.11.0 // indirect
60 | github.com/spf13/cast v1.6.0 // indirect
61 | github.com/spf13/pflag v1.0.5 // indirect
62 | github.com/subosito/gotenv v1.6.0 // indirect
63 | github.com/tklauser/go-sysconf v0.3.13 // indirect
64 | github.com/tklauser/numcpus v0.7.0 // indirect
65 | github.com/yusufpapurcu/wmi v1.2.4 // indirect
66 | go.uber.org/atomic v1.9.0 // indirect
67 | go.uber.org/multierr v1.11.0 // indirect
68 | go.uber.org/zap v1.24.0 // indirect
69 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
70 | golang.org/x/sys v0.19.0 // indirect
71 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect
72 | google.golang.org/protobuf v1.33.0 // indirect
73 | gopkg.in/ini.v1 v1.67.0 // indirect
74 | gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
75 | gopkg.in/yaml.v3 v3.0.1 // indirect
76 | gorm.io/gorm v1.25.10 // indirect
77 | )
78 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/OpenIMSDK/protocol v0.0.40 h1:1/Oij6RSAaePCPrWGwp9Cyz976/8Uxr94hM5M5FXzlg=
2 | github.com/OpenIMSDK/protocol v0.0.40/go.mod h1:F25dFrwrIx3lkNoiuf6FkCfxuwf8L4Z8UIsdTHP/r0Y=
3 | github.com/OpenIMSDK/tools v0.0.24 h1:P8n7ZtsZEbm4W3dQAem29O+bigzy6YPXxFzd/D8Vh3U=
4 | github.com/OpenIMSDK/tools v0.0.24/go.mod h1:eg+q4A34Qmu73xkY0mt37FHGMCMfC6CtmOnm0kFEGFI=
5 | github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
6 | github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
7 | github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0=
8 | github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE=
9 | github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
10 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
11 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
12 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
13 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
14 | github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
15 | github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
16 | github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
17 | github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
18 | github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
19 | github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
20 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
21 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
22 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
23 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
24 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
25 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
26 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
27 | github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
28 | github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
29 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
30 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
31 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
32 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
33 | github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
34 | github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
35 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
36 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
37 | github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
38 | github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
39 | github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4=
40 | github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc=
41 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
42 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
43 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
44 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
45 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
46 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
47 | github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc h1:RKf14vYWi2ttpEmkA4aQ3j4u9dStX2t4M8UM6qqNsG8=
48 | github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc/go.mod h1:kopuH9ugFRkIXf3YoqHKyrJ9YfUFsckUU9S7B+XP+is=
49 | github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible h1:Y6sqxHMyB1D2YSzWkLibYKgg+SwmyFU9dF2hn6MdTj4=
50 | github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible/go.mod h1:ZQnN8lSECaebrkQytbHj4xNgtg8CR7RYXnPok8e0EHA=
51 | github.com/lestrrat-go/strftime v1.0.6 h1:CFGsDEt1pOpFNU+TJB0nhz9jl+K0hZSLE205AhTIGQQ=
52 | github.com/lestrrat-go/strftime v1.0.6/go.mod h1:f7jQKgV5nnJpYgdEasS+/y7EsTb8ykN2z68n3TtcTaw=
53 | github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg=
54 | github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
55 | github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
56 | github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
57 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
58 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
59 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
60 | github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
61 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
62 | github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
63 | github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
64 | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
65 | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
66 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
67 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
68 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
69 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
70 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
71 | github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
72 | github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
73 | github.com/openimsdk/gomake v0.0.12 h1:OL0MzU+1Ks+qAvOuftXEOXz/ka5+4SpqDTgYhb4oNlc=
74 | github.com/openimsdk/gomake v0.0.12/go.mod h1:PndCozNc2IsQIciyn9mvEblYWZwJmAI+06z94EY+csI=
75 | github.com/openimsdk/openim-sdk-core/v3 v3.5.1 h1:lvC8TlouT9zFxjoBeH/S4YCkwbQ5IgSnNRO9NJ9H5v8=
76 | github.com/openimsdk/openim-sdk-core/v3 v3.5.1/go.mod h1:d2aVHT6pSYthQQd+/4JaruHPTLsQV2D+eQisRQkoP3I=
77 | github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
78 | github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
79 | github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
80 | github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
81 | github.com/petermattis/goid v0.0.0-20230904192822-1876fd5063bc h1:8bQZVK1X6BJR/6nYUPxQEP+ReTsceJTKizeuwjWOPUA=
82 | github.com/petermattis/goid v0.0.0-20230904192822-1876fd5063bc/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
83 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
84 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
85 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
86 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
87 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
88 | github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
89 | github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
90 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
91 | github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
92 | github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
93 | github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
94 | github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
95 | github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
96 | github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
97 | github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
98 | github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
99 | github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
100 | github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
101 | github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
102 | github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
103 | github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
104 | github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
105 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
106 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
107 | github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
108 | github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
109 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
110 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
111 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
112 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
113 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
114 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
115 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
116 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
117 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
118 | github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
119 | github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
120 | github.com/tklauser/go-sysconf v0.3.13 h1:GBUpcahXSpR2xN01jhkNAbTLRk2Yzgggk8IM08lq3r4=
121 | github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0=
122 | github.com/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr4=
123 | github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY=
124 | github.com/xuexihuang/new_log15 v1.0.0 h1:rIfjUeC4dgkjDYvbb6zv3q4Jk/aSvBo03UJ1mJCyJvg=
125 | github.com/xuexihuang/new_log15 v1.0.0/go.mod h1:psqerkM8mBbXXWN8DG3NUo33C3Hyr9RsaRjq8VfyA2c=
126 | github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
127 | github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
128 | go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
129 | go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
130 | go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
131 | go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
132 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
133 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
134 | go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
135 | go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
136 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
137 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
138 | golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
139 | golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
140 | golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
141 | golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
142 | golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
143 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
144 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
145 | golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
146 | golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
147 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
148 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
149 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
150 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc=
151 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
152 | google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk=
153 | google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
154 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
155 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
156 | google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
157 | google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
158 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
159 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
160 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
161 | gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
162 | gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
163 | gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
164 | gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
165 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
166 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
167 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
168 | gorm.io/driver/sqlite v1.5.5 h1:7MDMtUZhV065SilG62E0MquljeArQZNfJnjd9i9gx3E=
169 | gorm.io/driver/sqlite v1.5.5/go.mod h1:6NgQ7sQWAIFsPrJJl1lSNSu2TABh0ZZ/zm5fosATavE=
170 | gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s=
171 | gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
172 | nhooyr.io/websocket v1.8.10 h1:mv4p+MnGrLDcPlBoWsvPP7XCzTYMXP9F9eIGoKbgx7Q=
173 | nhooyr.io/websocket v1.8.10/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c=
174 |
--------------------------------------------------------------------------------
/internal/sdk/sdk.go:
--------------------------------------------------------------------------------
1 | package sdk
2 |
3 | import (
4 | "fmt"
5 | "github.com/openim-sigs/oimws/pkg/common/config"
6 | gate2 "github.com/openim-sigs/oimws/pkg/gate"
7 | "github.com/openim-sigs/oimws/pkg/network/tjson"
8 | "sync"
9 | "time"
10 | )
11 |
12 | const (
13 | MaxMsgLen = 1024 * 1024 * 10
14 | MaxConnNum = 100 * 100 * 10
15 | HTTPTimeout = 10 * time.Second
16 | WriterChanLen = 1000
17 | )
18 |
19 | type Config struct {
20 | SdkConfig config.Config
21 | }
22 |
23 | var Processor = tjson.NewProcessor()
24 |
25 | type GateNet struct {
26 | *gate2.Gate
27 | CloseSig chan bool
28 | Wg sync.WaitGroup
29 | }
30 |
31 | // Initsever initializes a new GateNet instance with the given WebSocket port and default configurations.
32 | func Initsever(wsPort int) *GateNet {
33 | gatenet := new(GateNet)
34 | gatenet.Gate = gate2.NewGate(MaxConnNum, MaxMsgLen,
35 | Processor, fmt.Sprintf(":%d", wsPort), HTTPTimeout, WriterChanLen)
36 | gatenet.CloseSig = make(chan bool, 1)
37 | return gatenet
38 | }
39 |
40 | // SetMsgFun sets the functions that will be called on new connection, disconnection, and data reception events.
41 | func (gt *GateNet) SetMsgFun(Fun1 func(gate2.Agent), Fun2 func(gate2.Agent), Fun3 func(interface{}, gate2.Agent)) {
42 | gt.Gate.SetFun(Fun1, Fun2, Fun3)
43 | }
44 |
45 | // Runloop starts the server loop in a new goroutine and ensures the WaitGroup is properly managed.
46 | func (gt *GateNet) Runloop() {
47 | gt.Wg.Add(1)
48 | gt.Run(gt.CloseSig)
49 | gt.Wg.Done()
50 | }
51 |
52 | // CloseGate sends a signal to close the server and waits for all goroutines to finish before calling OnDestroy.
53 | func (gt *GateNet) CloseGate() {
54 | gt.CloseSig <- true
55 | gt.Wg.Wait()
56 | gt.Gate.OnDestroy()
57 | }
58 |
--------------------------------------------------------------------------------
/internal/sdk/start.go:
--------------------------------------------------------------------------------
1 | package sdk
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "github.com/openim-sigs/oimws/pkg/common/config"
7 | "github.com/openim-sigs/oimws/pkg/core_func"
8 | "github.com/openim-sigs/oimws/pkg/module"
9 | log "github.com/xuexihuang/new_log15"
10 | "os"
11 | "os/signal"
12 | "syscall"
13 | "time"
14 | )
15 |
16 | func Start(ctx context.Context, index int, conf *Config, logConf *config.Log) error {
17 | listenPort, err := datautilGetElemByIndex(conf.SdkConfig.SdkWsPort, index)
18 | if err != nil {
19 | return err
20 | }
21 | if err := os.MkdirAll(conf.SdkConfig.DbDir, os.ModePerm); err != nil && !os.IsExist(err) {
22 | return err
23 | }
24 | core_func.Config.WsAddr = conf.SdkConfig.OpenimWs
25 | core_func.Config.ApiAddr = conf.SdkConfig.OpenimApi
26 | core_func.Config.DataDir = conf.SdkConfig.DbDir
27 | core_func.Config.LogLevel = uint32(logConf.RemainLogLevel)
28 | core_func.Config.IsLogStandardOutput = logConf.IsStdout
29 | core_func.Config.LogFilePath = logConf.StorageLocation
30 | core_func.Config.IsExternalExtensions = true
31 | log.SetOutLevel(log.LvlInfo)
32 | fmt.Println("Client starting....")
33 | log.Info("Client starting....")
34 | gatenet := Initsever(listenPort)
35 | gatenet.SetMsgFun(module.NewAgent, module.CloseAgent, module.DataRecv)
36 | go gatenet.Runloop()
37 | module.ProgressStartTime = time.Now().Unix()
38 | c := make(chan os.Signal, 1)
39 | signal.Notify(c, os.Interrupt, os.Kill, syscall.SIGQUIT, syscall.SIGTERM)
40 | sig := <-c
41 | log.Info("wsconn server closing down ", "sig", sig)
42 | gatenet.CloseGate()
43 | return nil
44 | }
45 |
46 | // TODO: datautil.GetElemByIndex
47 | func datautilGetElemByIndex(array []int, index int) (int, error) {
48 | if index < 0 || index >= len(array) {
49 | return 0, fmt.Errorf("index out of range index %d array %+v", index, array)
50 | }
51 | return array[index], nil
52 | }
53 |
--------------------------------------------------------------------------------
/magefile.go:
--------------------------------------------------------------------------------
1 | //go:build mage
2 | // +build mage
3 |
4 | package main
5 |
6 | import (
7 | "github.com/openimsdk/gomake/mageutil"
8 | "os"
9 | "strings"
10 | )
11 |
12 | var Default = Build
13 |
14 | func Build() {
15 | platforms := os.Getenv("PLATFORMS")
16 | if platforms == "" {
17 | platforms = mageutil.DetectPlatform()
18 | }
19 |
20 | for _, platform := range strings.Split(platforms, " ") {
21 | mageutil.CompileForPlatform(platform)
22 | }
23 |
24 | mageutil.PrintGreen("All binaries under cmd and tools were successfully compiled.")
25 | }
26 |
27 | func Start() {
28 | mageutil.InitForSSC()
29 | err := setMaxOpenFiles()
30 | if err != nil {
31 | mageutil.PrintRed("setMaxOpenFiles failed " + err.Error())
32 | os.Exit(1)
33 | }
34 | mageutil.StartToolsAndServices()
35 | }
36 |
37 | func Stop() {
38 | mageutil.StopAndCheckBinaries()
39 | }
40 |
41 | func Check() {
42 | mageutil.CheckAndReportBinariesStatus()
43 | }
44 |
--------------------------------------------------------------------------------
/magefile_unix.go:
--------------------------------------------------------------------------------
1 | //go:build mage && !windows
2 | // +build mage,!windows
3 |
4 | package main
5 |
6 | import (
7 | "github.com/openimsdk/gomake/mageutil"
8 | "syscall"
9 | )
10 |
11 | func setMaxOpenFiles() error {
12 | var rLimit syscall.Rlimit
13 | err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)
14 | if err != nil {
15 | return err
16 | }
17 | rLimit.Max = uint64(mageutil.MaxFileDescriptors)
18 | rLimit.Cur = uint64(mageutil.MaxFileDescriptors)
19 | return syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit)
20 | }
21 |
--------------------------------------------------------------------------------
/magefile_windows.go:
--------------------------------------------------------------------------------
1 | //go:build mage
2 | // +build mage
3 |
4 | package main
5 |
6 | func setMaxOpenFiles() error {
7 | return nil
8 | }
9 |
--------------------------------------------------------------------------------
/pkg/common/cmd/constant.go:
--------------------------------------------------------------------------------
1 | // Copyright © 2023 OpenIM. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package cmd
16 |
17 | import (
18 | "strings"
19 | )
20 |
21 | var (
22 | FileName = "config.yml"
23 | LogConfigFileName = "log.yml"
24 | )
25 |
26 | var ConfigEnvPrefixMap map[string]string
27 |
28 | func init() {
29 | ConfigEnvPrefixMap = make(map[string]string)
30 | fileNames := []string{
31 | FileName,
32 | LogConfigFileName,
33 | }
34 | for _, fileName := range fileNames {
35 | envKey := strings.TrimSuffix(strings.TrimSuffix(fileName, ".yml"), ".yaml")
36 | envKey = "IMENV_" + envKey
37 | envKey = strings.ToUpper(strings.ReplaceAll(envKey, "-", "_"))
38 | ConfigEnvPrefixMap[fileName] = envKey
39 | }
40 | }
41 |
42 | const (
43 | FlagConf = "config_folder_path"
44 | FlagTransferIndex = "index"
45 | )
46 |
--------------------------------------------------------------------------------
/pkg/common/cmd/root.go:
--------------------------------------------------------------------------------
1 | // Copyright © 2023 OpenIM. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package cmd
16 |
17 | import (
18 | "fmt"
19 | "github.com/openim-sigs/oimws/pkg/common/config"
20 | "path/filepath"
21 |
22 | "github.com/OpenIMSDK/tools/errs"
23 | "github.com/OpenIMSDK/tools/log"
24 | "github.com/spf13/cobra"
25 | )
26 |
27 | type RootCmd struct {
28 | Command cobra.Command
29 | processName string
30 | port int
31 | prometheusPort int
32 | log config.Log
33 | index int
34 | }
35 |
36 | func (r *RootCmd) Index() int {
37 | return r.index
38 | }
39 |
40 | func (r *RootCmd) Port() int {
41 | return r.port
42 | }
43 |
44 | type CmdOpts struct {
45 | loggerPrefixName string
46 | configMap map[string]any
47 | }
48 |
49 | func WithCronTaskLogName() func(*CmdOpts) {
50 | return func(opts *CmdOpts) {
51 | opts.loggerPrefixName = "openim-crontask"
52 | }
53 | }
54 |
55 | func WithLogName(logName string) func(*CmdOpts) {
56 | return func(opts *CmdOpts) {
57 | opts.loggerPrefixName = logName
58 | }
59 | }
60 | func WithConfigMap(configMap map[string]any) func(*CmdOpts) {
61 | return func(opts *CmdOpts) {
62 | opts.configMap = configMap
63 | }
64 | }
65 |
66 | func NewRootCmd(processName string, opts ...func(*CmdOpts)) *RootCmd {
67 | rootCmd := &RootCmd{processName: processName}
68 | cmd := cobra.Command{
69 | Use: "Start openIM application",
70 | Long: fmt.Sprintf(`Start %s `, processName),
71 | PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
72 | return rootCmd.persistentPreRun(cmd, opts...)
73 | },
74 | SilenceUsage: true,
75 | SilenceErrors: false,
76 | }
77 | cmd.Flags().StringP(FlagConf, "c", "", "path of config directory")
78 | cmd.Flags().IntP(FlagTransferIndex, "i", 0, "process startup sequence number")
79 |
80 | rootCmd.Command = cmd
81 | return rootCmd
82 | }
83 |
84 | func (r *RootCmd) persistentPreRun(cmd *cobra.Command, opts ...func(*CmdOpts)) error {
85 | cmdOpts := r.applyOptions(opts...)
86 | if err := r.initializeConfiguration(cmd, cmdOpts); err != nil {
87 | return err
88 | }
89 | //TODO: ERRS
90 | if err := r.initializeLogger(cmdOpts); err != nil {
91 | return errs.Wrap(err, "failed to initialize logger")
92 | }
93 |
94 | return nil
95 | }
96 |
97 | func (r *RootCmd) initializeConfiguration(cmd *cobra.Command, opts *CmdOpts) error {
98 | configDirectory, _, err := r.getFlag(cmd)
99 | if err != nil {
100 | return err
101 | }
102 | // Load common configuration file
103 | //opts.configMap[ShareFileName] = StructEnvPrefix{EnvPrefix: shareEnvPrefix, ConfigStruct: &r.share}
104 | for configFileName, configStruct := range opts.configMap {
105 | err := config.LoadConfig(filepath.Join(configDirectory, configFileName),
106 | ConfigEnvPrefixMap[configFileName], configStruct)
107 | if err != nil {
108 | return err
109 | }
110 | }
111 | // Load common log configuration file
112 | return config.LoadConfig(filepath.Join(configDirectory, LogConfigFileName),
113 | ConfigEnvPrefixMap[LogConfigFileName], &r.log)
114 | }
115 |
116 | func (r *RootCmd) applyOptions(opts ...func(*CmdOpts)) *CmdOpts {
117 | cmdOpts := defaultCmdOpts()
118 | for _, opt := range opts {
119 | opt(cmdOpts)
120 | }
121 |
122 | return cmdOpts
123 | }
124 |
125 | func (r *RootCmd) initializeLogger(cmdOpts *CmdOpts) error {
126 | //TODO log
127 | return log.InitFromConfig(
128 | cmdOpts.loggerPrefixName,
129 | r.processName,
130 | r.log.RemainLogLevel,
131 | r.log.IsStdout,
132 | r.log.IsJson,
133 | r.log.StorageLocation,
134 | r.log.RemainRotationCount,
135 | r.log.RotationTime,
136 | //config.Version,
137 | )
138 | }
139 |
140 | func defaultCmdOpts() *CmdOpts {
141 | return &CmdOpts{
142 | loggerPrefixName: "openim-service-log",
143 | }
144 | }
145 |
146 | func (r *RootCmd) getFlag(cmd *cobra.Command) (string, int, error) {
147 | configDirectory, err := cmd.Flags().GetString(FlagConf)
148 | if err != nil {
149 | return "", 0, errs.Wrap(err)
150 | }
151 | index, err := cmd.Flags().GetInt(FlagTransferIndex)
152 | if err != nil {
153 | return "", 0, errs.Wrap(err)
154 | }
155 | r.index = index
156 | return configDirectory, index, nil
157 | }
158 |
159 | func (r *RootCmd) Execute() error {
160 | return r.Command.Execute()
161 | }
162 |
--------------------------------------------------------------------------------
/pkg/common/cmd/sdk.go:
--------------------------------------------------------------------------------
1 | // Copyright © 2023 OpenIM. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package cmd
16 |
17 | import (
18 | "context"
19 | "github.com/openim-sigs/oimws/internal/sdk"
20 | "github.com/openim-sigs/oimws/pkg/common/config"
21 | "github.com/spf13/cobra"
22 | "os"
23 | "strings"
24 | )
25 |
26 | type SdkCmd struct {
27 | *RootCmd
28 | ctx context.Context
29 | configMap map[string]any
30 | sdkConfig sdk.Config
31 | }
32 |
33 | func NewSdkCmd() *SdkCmd {
34 | var ret SdkCmd
35 | ret.configMap = map[string]any{
36 | FileName: &ret.sdkConfig.SdkConfig,
37 | }
38 | // TODO program.GetProcessName()
39 | var processName string
40 | if args := os.Args; len(args) > 0 {
41 | segments := strings.Split(args[0], "/")
42 | processName = segments[len(segments)-1]
43 | }
44 | //TODO
45 | ret.RootCmd = NewRootCmd(processName, WithConfigMap(ret.configMap))
46 | //ret.RootCmd = NewRootCmd(program.GetProcessName(), WithConfigMap(ret.configMap))
47 | ret.ctx = context.WithValue(context.Background(), "version", config.Version)
48 | ret.Command.RunE = func(cmd *cobra.Command, args []string) error {
49 | return ret.runE()
50 | }
51 | return &ret
52 | }
53 |
54 | func (a *SdkCmd) Exec() error {
55 | return a.Execute()
56 | }
57 |
58 | func (a *SdkCmd) runE() error {
59 | return sdk.Start(a.ctx, a.Index(), &a.sdkConfig, &a.RootCmd.log)
60 | }
61 |
--------------------------------------------------------------------------------
/pkg/common/config/config.go:
--------------------------------------------------------------------------------
1 | // Copyright © 2023 OpenIM. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package config
16 |
17 | type Log struct {
18 | StorageLocation string `mapstructure:"storageLocation"`
19 | RotationTime uint `mapstructure:"rotationTime"`
20 | RemainRotationCount uint `mapstructure:"remainRotationCount"`
21 | RemainLogLevel int `mapstructure:"remainLogLevel"`
22 | IsStdout bool `mapstructure:"isStdout"`
23 | IsJson bool `mapstructure:"isJson"`
24 | WithStack bool `mapstructure:"withStack"`
25 | }
26 |
27 | type Config struct {
28 | OpenimApi string `mapstructure:"openimApi"`
29 | OpenimWs string `mapstructure:"openimWs"`
30 | SdkWsPort []int `mapstructure:"sdkWsPort"`
31 | DbDir string `mapstructure:"dbDir"`
32 | }
33 |
--------------------------------------------------------------------------------
/pkg/common/config/constant.go:
--------------------------------------------------------------------------------
1 | // Copyright © 2024 OpenIM. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package config
16 |
17 | const ConfKey = "conf"
18 |
19 | const (
20 | // DefaultDirPerm is used for creating general directories, allowing the owner to read, write, and execute,
21 | // while the group and others can only read and execute.
22 | DefaultDirPerm = 0755
23 |
24 | // PrivateFilePerm is used for sensitive files, allowing only the owner to read and write.
25 | PrivateFilePerm = 0600
26 |
27 | // ExecFilePerm is used for executable files, allowing the owner to read, write, and execute,
28 | // while the group and others can only read.
29 | ExecFilePerm = 0754
30 |
31 | // SharedDirPerm is used for shared directories, allowing the owner and group to read, write, and execute,
32 | // with no permissions for others.
33 | SharedDirPerm = 0770
34 |
35 | // ReadOnlyDirPerm is used for read-only directories, allowing the owner, group, and others to only read.
36 | ReadOnlyDirPerm = 0555
37 | )
38 |
--------------------------------------------------------------------------------
/pkg/common/config/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright © 2024 OpenIM. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package config // import "github.com/openimsdk/open-im-server/v3/pkg/common/config"
16 |
--------------------------------------------------------------------------------
/pkg/common/config/load_config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "github.com/OpenIMSDK/tools/errs"
5 | "github.com/mitchellh/mapstructure"
6 | "github.com/spf13/viper"
7 | "strings"
8 | )
9 |
10 | func LoadConfig(path string, envPrefix string, config any) error {
11 | v := viper.New()
12 | v.SetConfigFile(path)
13 | v.SetEnvPrefix(envPrefix)
14 | v.AutomaticEnv()
15 | v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
16 |
17 | if err := v.ReadInConfig(); err != nil {
18 | return errs.Wrap(err, "failed to read config file", "path", path, "envPrefix", envPrefix)
19 | }
20 |
21 | if err := v.Unmarshal(config, func(config *mapstructure.DecoderConfig) {
22 | config.TagName = "mapstructure"
23 | }); err != nil {
24 | return errs.Wrap(err, "failed to unmarshal config", "path", path, "envPrefix", envPrefix)
25 | }
26 | return nil
27 | }
28 |
--------------------------------------------------------------------------------
/pkg/common/config/version:
--------------------------------------------------------------------------------
1 | 3.7.0
--------------------------------------------------------------------------------
/pkg/common/config/version.go:
--------------------------------------------------------------------------------
1 | // Copyright © 2023 OpenIM. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package config
16 |
17 | import _ "embed"
18 |
19 | //go:embed version
20 | var Version string
21 |
--------------------------------------------------------------------------------
/pkg/common/struct.go:
--------------------------------------------------------------------------------
1 | package common
2 |
3 | // TAppParam defines the configuration parameters related to an application.
4 | type TAppParam struct {
5 | ModuleType string // The type of the module, used to identify different modules or features.
6 | PairSessionId string // The unique identifier for a pairing session.
7 | Robot3dId int // The unique identifier for a 3D robot.
8 | UE_Ip string // The IP address of the User Equipment (UE).
9 | UE_Port int // The port number of the User Equipment (UE).
10 | UE_h5_weith int // The width of the HTML5 element on the User Equipment (UE), likely a typo and should be 'UE_h5_width'.
11 | UE_h5_higth int // The height of the HTML5 element on the User Equipment (UE), likely a typo and should be 'UE_h5_height'.
12 | }
13 |
14 | // TAgentUserData contains user-specific data passed to an agent.
15 | type TAgentUserData struct {
16 | SessionID string // The session ID uniquely identifying the user session.
17 | CookieVal string // The value of the cookie stored for the user.
18 | AppString string // A string related to the application, possibly containing user-specific settings or states.
19 | ProxyBody interface{} // A generic interface to hold different types of data for proxy communication.
20 | UserId string
21 | }
22 |
23 | // TWSData defines the structure for WebSocket data transmission.
24 | type TWSData struct {
25 | MsgType int // The type of the message, used to handle different data or requests.
26 | Msg []byte // The actual message data in bytes.
27 | }
28 |
29 | const (
30 | // MessageText is for UTF-8 encoded text messages like JSON.
31 | MessageText = iota + 1
32 | // MessageBinary is for binary messages like protobufs.
33 | MessageBinary
34 | // CloseMessage denotes a close control message. The optional message
35 | // payload contains a numeric code and text. Use the FormatCloseMessage
36 | // function to format a close message payload.
37 | CloseMessage = 8
38 |
39 | // PingMessage denotes a ping control message. The optional message payload
40 | // is UTF-8 encoded text.
41 | PingMessage = 9
42 |
43 | // PongMessage denotes a pong control message. The optional message payload
44 | // is UTF-8 encoded text.
45 | PongMessage = 10
46 | )
47 |
--------------------------------------------------------------------------------
/pkg/common/utils.go:
--------------------------------------------------------------------------------
1 | package common
2 |
3 | import (
4 | "fmt"
5 | "github.com/bwmarrin/snowflake"
6 | log "github.com/xuexihuang/new_log15"
7 | "runtime/debug"
8 | )
9 |
10 | // Method used to capture panic and print stack information.
11 | func TryRecoverAndDebugPrint() {
12 | errs := recover()
13 | if errs == nil {
14 | return
15 | }
16 | fmt.Printf("panic: %+v\n%s", errs, debug.Stack())
17 | log.Crit("[Panic]", "err", errs, "stackInfo", debug.Stack())
18 |
19 | }
20 |
21 | var G_flakeNode snowflake.Node
22 |
23 | func GetRandomSessionId() string {
24 |
25 | return G_flakeNode.Generate().String()
26 | }
27 |
--------------------------------------------------------------------------------
/pkg/core_func/responder.go:
--------------------------------------------------------------------------------
1 | package core_func
2 |
3 | import (
4 | "fmt"
5 | log "github.com/xuexihuang/new_log15"
6 |
7 | "github.com/OpenIMSDK/tools/errs"
8 | "github.com/openimsdk/openim-sdk-core/v3/pkg/sdkerrs"
9 | )
10 |
11 | type RespMessage struct {
12 | respMessagesChan chan *EventData
13 | }
14 |
15 | // NewRespMessage creates a new instance of RespMessage with the given channel.
16 | func NewRespMessage(respMessagesChan chan *EventData) *RespMessage {
17 | return &RespMessage{respMessagesChan: respMessagesChan}
18 | }
19 |
20 | // sendOnSuccessResp sends a success response message for a given operation.
21 | func (r *RespMessage) sendOnSuccessResp(operationID, event string, data string) {
22 | r.respMessagesChan <- &EventData{
23 | Event: event,
24 | OperationID: operationID,
25 | Data: data,
26 | }
27 | }
28 |
29 | // sendOnErrorResp sends an error response message for a given operation.
30 | func (r *RespMessage) sendOnErrorResp(operationID, event string, err error) {
31 | log.Error("sendOnErrorResp", "operationID", operationID, "event", event, "err", err)
32 | resp := &EventData{
33 | Event: event,
34 | OperationID: operationID,
35 | }
36 | if code, ok := err.(errs.CodeError); ok {
37 | resp.ErrCode = int32(code.Code())
38 | resp.ErrMsg = code.Error()
39 | } else {
40 | resp.ErrCode = sdkerrs.UnknownCode
41 | resp.ErrMsg = fmt.Sprintf("error %T not implement CodeError: %s", err, err)
42 | }
43 | r.respMessagesChan <- resp
44 | }
45 |
46 | // sendEventFailedRespNoErr sends a failed event response without error details.
47 | // event: Name of the event.
48 | func (r *RespMessage) sendEventFailedRespNoErr(event string) {
49 | r.respMessagesChan <- &EventData{
50 | Event: event,
51 | }
52 | }
53 |
54 | // sendEventSuccessRespWithData sends a successful event response with associated data.
55 | func (r *RespMessage) sendEventSuccessRespWithData(event string, data string) {
56 | r.respMessagesChan <- &EventData{
57 | Event: event,
58 | Data: data,
59 | }
60 | }
61 |
62 | // sendEventSuccessRespNoData sends a successful event response without any associated data.
63 | // This is included for completeness but not used in the above callback methods.
64 | func (r *RespMessage) sendEventSuccessRespNoData(event string) {
65 | r.respMessagesChan <- &EventData{
66 | Event: event,
67 | }
68 | }
69 |
70 | // sendEventFailedRespNoData sends a failed event response with error code and message, without any associated data.
71 | // This function may be used if there are any future error handling requirements in the SignalingCallback.
72 | func (r *RespMessage) sendEventFailedRespNoData(event string, errCode int32, errMsg string) {
73 | r.respMessagesChan <- &EventData{
74 | Event: event,
75 | ErrCode: errCode,
76 | ErrMsg: errMsg,
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/pkg/core_func/ws_conversation_msg_test.go:
--------------------------------------------------------------------------------
1 | package core_func
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 | )
7 |
8 | func TestGetAllConversationList(T *testing.T) {
9 |
10 | ev := make(chan *EventData, 10)
11 | fu := NewFuncRouter(ev, "123456")
12 | fu.GetAllConversationList("11111")
13 |
14 | msg := <-fu.respMessage.respMessagesChan
15 | fmt.Println("msg:", msg)
16 | }
17 |
--------------------------------------------------------------------------------
/pkg/core_func/ws_file.go:
--------------------------------------------------------------------------------
1 | package core_func
2 |
3 | // UploadFile handles the file upload process.
4 | func (f *FuncRouter) UploadFile(operationID string, args ...any) {
5 | f.call(operationID, f.userForSDK.File().UploadFile, args)
6 | }
7 |
--------------------------------------------------------------------------------
/pkg/core_func/ws_friend.go:
--------------------------------------------------------------------------------
1 | package core_func
2 |
3 | // CheckFriend checks the friend status between users.
4 | func (f *FuncRouter) CheckFriend(operationID string, args ...any) {
5 | f.call(operationID, f.userForSDK.Friend().CheckFriend, args...)
6 | }
7 |
8 | // GetSpecifiedFriendsInfo gets the information of specific friends.
9 | func (f *FuncRouter) GetSpecifiedFriendsInfo(operationID string, args ...any) {
10 | f.call(operationID, f.userForSDK.Friend().GetSpecifiedFriendsInfo, args...)
11 | }
12 |
13 | // GetFriendList retrieves the user's friend list.
14 | func (f *FuncRouter) GetFriendList(operationID string) {
15 | f.call(operationID, f.userForSDK.Friend().GetFriendList)
16 | }
17 |
18 | // GetFriendListPage retrieves a paginated friend list.
19 | func (f *FuncRouter) GetFriendListPage(operationID string, args ...any) {
20 | f.call(operationID, f.userForSDK.Friend().GetFriendListPage, args...)
21 | }
22 |
23 | // SearchFriends searches for friends based on given criteria.
24 | func (f *FuncRouter) SearchFriends(operationID string, args ...any) {
25 | f.call(operationID, f.userForSDK.Friend().SearchFriends, args...)
26 | }
27 |
28 | // AddFriend sends a friend request to another user.
29 | func (f *FuncRouter) AddFriend(operationID string, args ...any) {
30 | f.call(operationID, f.userForSDK.Friend().AddFriend, args...)
31 | }
32 |
33 | // SetFriendRemark sets a remark for a friend.
34 | func (f *FuncRouter) SetFriendRemark(operationID string, args ...any) {
35 | f.call(operationID, f.userForSDK.Friend().SetFriendRemark, args...)
36 | }
37 |
38 | // PinFriends pins friends to the top of the friend list.
39 | func (f *FuncRouter) PinFriends(operationID string, args ...any) {
40 | f.call(operationID, f.userForSDK.Friend().PinFriends, args...)
41 | }
42 |
43 | // DeleteFriend removes a user from the friend list.
44 | func (f *FuncRouter) DeleteFriend(operationID string, args ...any) {
45 | f.call(operationID, f.userForSDK.Friend().DeleteFriend, args...)
46 | }
47 |
48 | // GetFriendApplicationListAsRecipient retrieves friend requests received.
49 | func (f *FuncRouter) GetFriendApplicationListAsRecipient(operationID string) {
50 | f.call(operationID, f.userForSDK.Friend().GetFriendApplicationListAsRecipient)
51 | }
52 |
53 | // GetFriendApplicationListAsApplicant retrieves friend requests sent.
54 | func (f *FuncRouter) GetFriendApplicationListAsApplicant(operationID string) {
55 | f.call(operationID, f.userForSDK.Friend().GetFriendApplicationListAsApplicant)
56 | }
57 |
58 | // AcceptFriendApplication accepts a friend request.
59 | func (f *FuncRouter) AcceptFriendApplication(operationID string, args ...any) {
60 | f.call(operationID, f.userForSDK.Friend().AcceptFriendApplication, args...)
61 | }
62 |
63 | // RefuseFriendApplication declines a friend request.
64 | func (f *FuncRouter) RefuseFriendApplication(operationID string, args ...any) {
65 | f.call(operationID, f.userForSDK.Friend().RefuseFriendApplication, args...)
66 | }
67 |
68 | // AddBlack adds a user to the blacklist.
69 | func (f *FuncRouter) AddBlack(operationID string, args ...any) {
70 | f.call(operationID, f.userForSDK.Friend().AddBlack, args...)
71 | }
72 |
73 | // GetBlackList retrieves the blacklist.
74 | func (f *FuncRouter) GetBlackList(operationID string) {
75 | f.call(operationID, f.userForSDK.Friend().GetBlackList)
76 | }
77 |
78 | // RemoveBlack removes a user from the blacklist.
79 | func (f *FuncRouter) RemoveBlack(operationID string, args ...any) {
80 | f.call(operationID, f.userForSDK.Friend().RemoveBlack, args...)
81 | }
82 |
83 | // SetFriendsEx sets the friend ex info.
84 | func (f *FuncRouter) SetFriendsEx(operationID string, args ...any) {
85 | f.call(operationID, f.userForSDK.Friend().SetFriendsEx, args...)
86 | }
87 |
--------------------------------------------------------------------------------
/pkg/core_func/ws_group.go:
--------------------------------------------------------------------------------
1 | package core_func
2 |
3 | // CreateGroup creates a new group with specified settings.
4 | func (f *FuncRouter) CreateGroup(operationID string, args ...any) {
5 | f.call(operationID, f.userForSDK.Group().CreateGroup, args...)
6 | }
7 |
8 | // JoinGroup sends a request to join a group.
9 | func (f *FuncRouter) JoinGroup(operationID string, args ...any) {
10 | f.call(operationID, f.userForSDK.Group().JoinGroup, args...)
11 | }
12 |
13 | // QuitGroup leaves a group.
14 | func (f *FuncRouter) QuitGroup(operationID string, args ...any) {
15 | f.call(operationID, f.userForSDK.Group().QuitGroup, args...)
16 | }
17 |
18 | // DismissGroup disbands a group.
19 | func (f *FuncRouter) DismissGroup(operationID string, args ...any) {
20 | f.call(operationID, f.userForSDK.Group().DismissGroup, args...)
21 | }
22 |
23 | // ChangeGroupMute toggles the mute status of the group.
24 | func (f *FuncRouter) ChangeGroupMute(operationID string, args ...any) {
25 | f.call(operationID, f.userForSDK.Group().ChangeGroupMute, args...)
26 | }
27 |
28 | // ChangeGroupMemberMute toggles the mute status of a group member.
29 | func (f *FuncRouter) ChangeGroupMemberMute(operationID string, args ...any) {
30 | f.call(operationID, f.userForSDK.Group().ChangeGroupMemberMute, args...)
31 | }
32 |
33 | // SetGroupMemberRoleLevel changes the role or level of a group member.
34 | func (f *FuncRouter) SetGroupMemberRoleLevel(operationID string, args ...any) {
35 | f.call(operationID, f.userForSDK.Group().SetGroupMemberRoleLevel, args...)
36 | }
37 |
38 | // SetGroupMemberInfo updates information for a group member.
39 | func (f *FuncRouter) SetGroupMemberInfo(operationID string, args ...any) {
40 | f.call(operationID, f.userForSDK.Group().SetGroupMemberInfo, args...)
41 | }
42 |
43 | // GetJoinedGroupList retrieves the list of groups a user has joined.
44 | func (f *FuncRouter) GetJoinedGroupList(operationID string) {
45 | f.call(operationID, f.userForSDK.Group().GetJoinedGroupList)
46 | }
47 |
48 | // GetSpecifiedGroupsInfo gets information of specified groups.
49 | func (f *FuncRouter) GetSpecifiedGroupsInfo(operationID string, args ...any) {
50 | f.call(operationID, f.userForSDK.Group().GetSpecifiedGroupsInfo, args...)
51 | }
52 |
53 | // SearchGroups searches for groups based on criteria.
54 | func (f *FuncRouter) SearchGroups(operationID string, args ...any) {
55 | f.call(operationID, f.userForSDK.Group().SearchGroups, args...)
56 | }
57 |
58 | // SetGroupInfo updates group settings or information.
59 | func (f *FuncRouter) SetGroupInfo(operationID string, args ...any) {
60 | f.call(operationID, f.userForSDK.Group().SetGroupInfo, args...)
61 | }
62 |
63 | // SetGroupVerification sets the group's join request verification method.
64 | func (f *FuncRouter) SetGroupVerification(operationID string, args ...any) {
65 | f.call(operationID, f.userForSDK.Group().SetGroupVerification, args...)
66 | }
67 |
68 | // SetGroupLookMemberInfo toggles whether to allow group members to view each other's information.
69 | func (f *FuncRouter) SetGroupLookMemberInfo(operationID string, args ...any) {
70 | f.call(operationID, f.userForSDK.Group().SetGroupLookMemberInfo, args...)
71 | }
72 |
73 | // SetGroupApplyMemberFriend configures settings related to adding group members as friends.
74 | func (f *FuncRouter) SetGroupApplyMemberFriend(operationID string, args ...any) {
75 | f.call(operationID, f.userForSDK.Group().SetGroupApplyMemberFriend, args...)
76 | }
77 |
78 | // GetGroupMemberList retrieves the member list of a group.
79 | func (f *FuncRouter) GetGroupMemberList(operationID string, args ...any) {
80 | f.call(operationID, f.userForSDK.Group().GetGroupMemberList, args...)
81 | }
82 |
83 | // GetGroupMemberOwnerAndAdmin gets the owner and admin list of the group.
84 | func (f *FuncRouter) GetGroupMemberOwnerAndAdmin(operationID string, args ...any) {
85 | f.call(operationID, f.userForSDK.Group().GetGroupMemberOwnerAndAdmin, args...)
86 | }
87 |
88 | // GetGroupMemberListByJoinTimeFilter gets the group member list filtered by join time.
89 | func (f *FuncRouter) GetGroupMemberListByJoinTimeFilter(operationID string, args ...any) {
90 | f.call(operationID, f.userForSDK.Group().GetGroupMemberListByJoinTimeFilter, args...)
91 | }
92 |
93 | // GetSpecifiedGroupMembersInfo gets information for specified group members.
94 | func (f *FuncRouter) GetSpecifiedGroupMembersInfo(operationID string, args ...any) {
95 | f.call(operationID, f.userForSDK.Group().GetSpecifiedGroupMembersInfo, args...)
96 | }
97 |
98 | // KickGroupMember removes a specific member from the group.
99 | func (f *FuncRouter) KickGroupMember(operationID string, args ...any) {
100 | f.call(operationID, f.userForSDK.Group().KickGroupMember, args...)
101 | }
102 |
103 | // TransferGroupOwner changes the ownership of the group to another member.
104 | func (f *FuncRouter) TransferGroupOwner(operationID string, args ...any) {
105 | f.call(operationID, f.userForSDK.Group().TransferGroupOwner, args...)
106 | }
107 |
108 | // InviteUserToGroup sends an invitation to a user to join the group.
109 | func (f *FuncRouter) InviteUserToGroup(operationID string, args ...any) {
110 | f.call(operationID, f.userForSDK.Group().InviteUserToGroup, args...)
111 | }
112 |
113 | // GetGroupApplicationListAsRecipient retrieves the list of join requests received by the group.
114 | func (f *FuncRouter) GetGroupApplicationListAsRecipient(operationID string) {
115 | f.call(operationID, f.userForSDK.Group().GetGroupApplicationListAsRecipient)
116 | }
117 |
118 | // GetGroupApplicationListAsApplicant retrieves the list of join requests sent by the user.
119 | func (f *FuncRouter) GetGroupApplicationListAsApplicant(operationID string) {
120 | f.call(operationID, f.userForSDK.Group().GetGroupApplicationListAsApplicant)
121 | }
122 |
123 | // AcceptGroupApplication approves a join request to the group.
124 | func (f *FuncRouter) AcceptGroupApplication(operationID string, args ...any) {
125 | f.call(operationID, f.userForSDK.Group().AcceptGroupApplication, args...)
126 | }
127 |
128 | // RefuseGroupApplication denies a join request to the group.
129 | func (f *FuncRouter) RefuseGroupApplication(operationID string, args ...any) {
130 | f.call(operationID, f.userForSDK.Group().RefuseGroupApplication, args...)
131 | }
132 |
133 | // SetGroupMemberNickname sets or changes a group member's nickname.
134 | func (f *FuncRouter) SetGroupMemberNickname(operationID string, args ...any) {
135 | f.call(operationID, f.userForSDK.Group().SetGroupMemberNickname, args...)
136 | }
137 |
138 | // SearchGroupMembers looks for members in the group matching certain criteria.
139 | func (f *FuncRouter) SearchGroupMembers(operationID string, args ...any) {
140 | f.call(operationID, f.userForSDK.Group().SearchGroupMembers, args...)
141 | }
142 |
143 | // IsJoinGroup checks whether the user is a member of the specified group.
144 | func (f *FuncRouter) IsJoinGroup(operationID string, args ...any) {
145 | f.call(operationID, f.userForSDK.Group().IsJoinGroup, args...)
146 | }
147 |
--------------------------------------------------------------------------------
/pkg/core_func/ws_handler.go:
--------------------------------------------------------------------------------
1 | package core_func
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "fmt"
7 | "github.com/openimsdk/openim-sdk-core/v3/open_im_sdk_callback"
8 | "reflect"
9 | "runtime"
10 | "runtime/debug"
11 | "strings"
12 | "time"
13 |
14 | "github.com/OpenIMSDK/tools/log"
15 | "github.com/openimsdk/openim-sdk-core/v3/open_im_sdk"
16 | "github.com/openimsdk/openim-sdk-core/v3/pkg/ccontext"
17 | "github.com/openimsdk/openim-sdk-core/v3/pkg/sdkerrs"
18 | "github.com/openimsdk/openim-sdk-core/v3/pkg/utils"
19 | )
20 |
21 | const (
22 | Success = "OnSuccess"
23 | Failed = "OnError"
24 | )
25 |
26 | type EventData struct {
27 | Event string `json:"event"`
28 | ErrCode int32 `json:"errCode"`
29 | ErrMsg string `json:"errMsg"`
30 | Data string `json:"data"`
31 | OperationID string `json:"operationID"`
32 | }
33 |
34 | type FuncRouter struct {
35 | userForSDK *open_im_sdk.LoginMgr
36 | respMessage *RespMessage
37 | sessionId string
38 | }
39 |
40 | // NewFuncRouter creates a new instance of FuncRouter with the provided session id and channel for event data.
41 | func NewFuncRouter(respMessagesChan chan *EventData, sessionId string) *FuncRouter {
42 | return &FuncRouter{respMessage: NewRespMessage(respMessagesChan),
43 | userForSDK: new(open_im_sdk.LoginMgr), sessionId: sessionId}
44 | }
45 |
46 | // call is an asynchronous wrapper that invokes SDK functions and handles their responses.
47 | func (f *FuncRouter) call(operationID string, fn any, args ...any) {
48 | go func() {
49 | funcPtr := reflect.ValueOf(fn).Pointer()
50 | funcName := runtime.FuncForPC(funcPtr).Name()
51 | parts := strings.Split(funcName, ".")
52 | var trimFuncName string
53 | if trimFuncNameList := strings.Split(parts[len(parts)-1], "-"); len(trimFuncNameList) == 0 {
54 | f.respMessage.sendOnErrorResp(operationID, "FuncError", errors.New("call function trimFuncNameList is empty"))
55 | return
56 | } else {
57 | trimFuncName = trimFuncNameList[0]
58 | }
59 | res, err := f.call_(operationID, fn, funcName, args...)
60 | if err != nil {
61 | f.respMessage.sendOnErrorResp(operationID, trimFuncName, err)
62 | return
63 | }
64 | data, err := json.Marshal(res)
65 | if err != nil {
66 | f.respMessage.sendOnErrorResp(operationID, trimFuncName, err)
67 | return
68 | } else {
69 | f.respMessage.sendOnSuccessResp(operationID, trimFuncName, string(data))
70 | }
71 | }()
72 | }
73 |
74 | // CheckResourceLoad checks the SDK is resource load status.
75 | func CheckResourceLoad(uSDK *open_im_sdk.LoginMgr, funcName string) error {
76 | if uSDK == nil {
77 | return utils.Wrap(errors.New("CheckResourceLoad failed uSDK == nil "), "")
78 | }
79 | if funcName == "" {
80 | return nil
81 | }
82 | parts := strings.Split(funcName, ".")
83 | if parts[len(parts)-1] == "Login-fm" {
84 | return nil
85 | }
86 | if uSDK.Friend() == nil || uSDK.User() == nil || uSDK.Group() == nil || uSDK.Conversation() == nil ||
87 | uSDK.Full() == nil {
88 | return utils.Wrap(errors.New("CheckResourceLoad failed, resource nil "), "")
89 | }
90 | return nil
91 | }
92 |
93 | // call_ is the internal function that actually invokes the SDK functions.
94 | func (f *FuncRouter) call_(operationID string, fn any, funcName string, args ...any) (res any, err error) {
95 | defer func() {
96 | if r := recover(); r != nil {
97 | fmt.Printf("panic: %+v\n%s", r, debug.Stack())
98 | err = fmt.Errorf("call panic: %+v", r)
99 | }
100 | }()
101 | if operationID == "" {
102 | return nil, sdkerrs.ErrArgs.Wrap("call function operationID is empty")
103 | }
104 | if err := CheckResourceLoad(f.userForSDK, funcName); err != nil {
105 | return nil, sdkerrs.ErrResourceLoad.Wrap("not load resource")
106 | }
107 |
108 | ctx := ccontext.WithOperationID(f.userForSDK.BaseCtx(), operationID)
109 |
110 | fnv := reflect.ValueOf(fn)
111 | if fnv.Kind() != reflect.Func {
112 | return nil, sdkerrs.ErrSdkInternal.Wrap(fmt.Sprintf("call function fn is not function, is %T", fn))
113 | }
114 | fnt := fnv.Type()
115 | nin := fnt.NumIn()
116 | if len(args)+1 != nin {
117 | return nil, sdkerrs.ErrSdkInternal.Wrap(fmt.Sprintf("go code error: fn in args num is not match"))
118 | }
119 | t := time.Now()
120 | log.ZInfo(ctx, "input req", "function name", funcName, "args", args)
121 | ins := make([]reflect.Value, 0, nin)
122 | ins = append(ins, reflect.ValueOf(ctx))
123 | for i := 0; i < len(args); i++ {
124 | inFnField := fnt.In(i + 1)
125 | arg := reflect.TypeOf(args[i])
126 | if arg.String() == inFnField.String() || inFnField.Kind() == reflect.Interface {
127 | ins = append(ins, reflect.ValueOf(args[i]))
128 | continue
129 | }
130 | //convert float64 to int when javascript call with number,because javascript only have double
131 | //precision floating-point format
132 | if arg.String() == "float64" && isInteger(inFnField) {
133 | ins = append(ins, reflect.ValueOf(convert(args[i].(float64), inFnField)))
134 | continue
135 | }
136 | if arg.Kind() == reflect.String { // json
137 | var ptr int
138 | for inFnField.Kind() == reflect.Ptr {
139 | inFnField = inFnField.Elem()
140 | ptr++
141 | }
142 | switch inFnField.Kind() {
143 | case reflect.Struct, reflect.Slice, reflect.Array, reflect.Map:
144 | v := reflect.New(inFnField)
145 | if err := json.Unmarshal([]byte(args[i].(string)), v.Interface()); err != nil {
146 | return nil, sdkerrs.ErrSdkInternal.Wrap(fmt.Sprintf("go call json.Unmarshal error: %s",
147 | err))
148 | }
149 | if ptr == 0 {
150 | v = v.Elem()
151 | } else if ptr != 1 {
152 | for i := ptr - 1; i > 0; i-- {
153 | temp := reflect.New(v.Type())
154 | temp.Elem().Set(v)
155 | v = temp
156 | }
157 | }
158 | ins = append(ins, v)
159 | continue
160 | }
161 | }
162 | return nil, sdkerrs.ErrSdkInternal.Wrap(fmt.Sprintf("go code error: fn in args type is not match"))
163 | }
164 | outs := fnv.Call(ins)
165 | if len(outs) == 0 {
166 | return "", nil
167 | }
168 | if fnt.Out(len(outs) - 1).Implements(reflect.ValueOf(new(error)).Elem().Type()) {
169 | if errValueOf := outs[len(outs)-1]; !errValueOf.IsNil() {
170 | log.ZError(ctx, "fn call error", errValueOf.Interface().(error), "function name",
171 | funcName, "cost time", time.Since(t))
172 | return nil, errValueOf.Interface().(error)
173 | }
174 | if len(outs) == 1 {
175 | return "", nil
176 | }
177 | outs = outs[:len(outs)-1]
178 | }
179 | for i := 0; i < len(outs); i++ {
180 | out := outs[i]
181 | switch out.Kind() {
182 | case reflect.Map:
183 | if out.IsNil() {
184 | outs[i] = reflect.MakeMap(out.Type())
185 | }
186 | case reflect.Slice:
187 | if out.IsNil() {
188 | outs[i] = reflect.MakeSlice(out.Type(), 0, 0)
189 | }
190 | }
191 | }
192 | if len(outs) == 1 {
193 | log.ZInfo(ctx, "output resp", "function name", funcName, "resp", outs[0].Interface(),
194 | "cost time", time.Since(t))
195 | return outs[0].Interface(), nil
196 | }
197 | val := make([]any, 0, len(outs))
198 | for i := range outs {
199 | val = append(val, outs[i].Interface())
200 | }
201 | log.ZInfo(ctx, "output resp", "function name", funcName, "resp", val, "cost time", time.Since(t))
202 | return val, nil
203 | }
204 | func isInteger(arg reflect.Type) bool {
205 | switch arg.Kind() {
206 | case reflect.Int:
207 | fallthrough
208 | case reflect.Int8:
209 | fallthrough
210 | case reflect.Int16:
211 | fallthrough
212 | case reflect.Int32:
213 | fallthrough
214 | case reflect.Int64:
215 | return true
216 | default:
217 | return false
218 |
219 | }
220 | }
221 | func convert(arg float64, p reflect.Type) any {
222 | switch p.Kind() {
223 | case reflect.Int:
224 | return int(arg)
225 | case reflect.Int8:
226 | return int8(arg)
227 | case reflect.Int16:
228 | return int16(arg)
229 | case reflect.Int32:
230 | return int32(arg)
231 | case reflect.Int64:
232 | return int64(arg)
233 | default:
234 | return arg
235 |
236 | }
237 | }
238 | func (f *FuncRouter) messageCall(operationID string, fn any, args ...any) {
239 | go func() {
240 | funcPtr := reflect.ValueOf(fn).Pointer()
241 | funcName := runtime.FuncForPC(funcPtr).Name()
242 | parts := strings.Split(funcName, ".")
243 | var trimFuncName string
244 | if trimFuncNameList := strings.Split(parts[len(parts)-1], "-"); len(trimFuncNameList) == 0 {
245 | f.respMessage.sendOnErrorResp(operationID, "FuncError",
246 | errors.New("call function trimFuncNameList is empty"))
247 | return
248 | } else {
249 | trimFuncName = trimFuncNameList[0]
250 | }
251 | sendMessageCallback := NewSendMessageCallback(trimFuncName, f.respMessage)
252 | res, err := f.messageCall_(sendMessageCallback, operationID, fn, funcName, args...)
253 | if err != nil {
254 | f.respMessage.sendOnErrorResp(operationID, trimFuncName, err)
255 | return
256 | }
257 | data, err := json.Marshal(res)
258 | if err != nil {
259 | f.respMessage.sendOnErrorResp(operationID, trimFuncName, err)
260 | return
261 | } else {
262 | f.respMessage.sendOnSuccessResp(operationID, trimFuncName, string(data))
263 | }
264 | }()
265 | }
266 | func (f *FuncRouter) messageCall_(callback open_im_sdk_callback.SendMsgCallBack, operationID string,
267 | fn any, funcName string, args ...any) (res any, err error) {
268 |
269 | defer func() {
270 | if r := recover(); r != nil {
271 | fmt.Printf("panic: %+v\n%s", r, debug.Stack())
272 | err = fmt.Errorf("call panic: %+v", r)
273 | }
274 | }()
275 | if operationID == "" {
276 | return nil, sdkerrs.ErrArgs.Wrap("call function operationID is empty")
277 | }
278 | if err := CheckResourceLoad(f.userForSDK, ""); err != nil {
279 | return nil, sdkerrs.ErrResourceLoad.Wrap("not load resource")
280 | }
281 |
282 | ctx := ccontext.WithOperationID(f.userForSDK.BaseCtx(), operationID)
283 | ctx = ccontext.WithSendMessageCallback(ctx, callback)
284 |
285 | fnv := reflect.ValueOf(fn)
286 | if fnv.Kind() != reflect.Func {
287 | return nil, sdkerrs.ErrSdkInternal.Wrap(fmt.Sprintf("call function fn is not function, is %T", fn))
288 | }
289 | log.ZInfo(ctx, "input req", "function name", funcName, "args", args)
290 | fnt := fnv.Type()
291 | nin := fnt.NumIn()
292 | if len(args)+1 != nin {
293 | return nil, sdkerrs.ErrSdkInternal.Wrap(fmt.Sprintf("go code error: fn in args num is not match"))
294 | }
295 | t := time.Now()
296 | ins := make([]reflect.Value, 0, nin)
297 | ins = append(ins, reflect.ValueOf(ctx))
298 | for i := 0; i < len(args); i++ {
299 | inFnField := fnt.In(i + 1)
300 | arg := reflect.TypeOf(args[i])
301 | if arg.String() == inFnField.String() || inFnField.Kind() == reflect.Interface {
302 | ins = append(ins, reflect.ValueOf(args[i]))
303 | continue
304 | }
305 | if arg.String() == "float64" && isInteger(inFnField) {
306 | ins = append(ins, reflect.ValueOf(convert(args[i].(float64), inFnField)))
307 | continue
308 | }
309 | if arg.Kind() == reflect.String { // json
310 | var ptr int
311 | for inFnField.Kind() == reflect.Ptr {
312 | inFnField = inFnField.Elem()
313 | ptr++
314 | }
315 | switch inFnField.Kind() {
316 | case reflect.Struct, reflect.Slice, reflect.Array, reflect.Map:
317 | v := reflect.New(inFnField)
318 | if err := json.Unmarshal([]byte(args[i].(string)), v.Interface()); err != nil {
319 | return nil, sdkerrs.ErrSdkInternal.Wrap(fmt.Sprintf("go call json.Unmarshal error: %s",
320 | err))
321 | }
322 | if ptr == 0 {
323 | v = v.Elem()
324 | } else if ptr != 1 {
325 | for i := ptr - 1; i > 0; i-- {
326 | temp := reflect.New(v.Type())
327 | temp.Elem().Set(v)
328 | v = temp
329 | }
330 | }
331 | ins = append(ins, v)
332 | continue
333 | }
334 | }
335 | return nil, sdkerrs.ErrSdkInternal.Wrap(fmt.Sprintf("go code error: fn in args type is not match"))
336 | }
337 | outs := fnv.Call(ins)
338 | if len(outs) == 0 {
339 | return "", nil
340 | }
341 | if fnt.Out(len(outs) - 1).Implements(reflect.ValueOf(new(error)).Elem().Type()) {
342 | if errValueOf := outs[len(outs)-1]; !errValueOf.IsNil() {
343 | log.ZError(ctx, "fn call error", errValueOf.Interface().(error), "function name",
344 | funcName, "cost time", time.Since(t))
345 | return nil, errValueOf.Interface().(error)
346 | }
347 | if len(outs) == 1 {
348 | return "", nil
349 | }
350 | outs = outs[:len(outs)-1]
351 | }
352 | for i := 0; i < len(outs); i++ {
353 | out := outs[i]
354 | switch out.Kind() {
355 | case reflect.Map:
356 | if out.IsNil() {
357 | outs[i] = reflect.MakeMap(out.Type())
358 | }
359 | case reflect.Slice:
360 | if out.IsNil() {
361 | outs[i] = reflect.MakeSlice(out.Type(), 0, 0)
362 | }
363 | }
364 | }
365 | if len(outs) == 1 {
366 | log.ZInfo(ctx, "output resp", "function name", funcName, "resp", outs[0].Interface(),
367 | "cost time", time.Since(t))
368 | return outs[0].Interface(), nil
369 | }
370 | val := make([]any, 0, len(outs))
371 | for i := range outs {
372 | val = append(val, outs[i].Interface())
373 | }
374 | log.ZInfo(ctx, "output resp", "function name", funcName, "resp", val, "cost time", time.Since(t))
375 | return val, nil
376 | }
377 |
--------------------------------------------------------------------------------
/pkg/core_func/ws_init_login.go:
--------------------------------------------------------------------------------
1 | package core_func
2 |
3 | import (
4 | "fmt"
5 | "strconv"
6 |
7 | "github.com/OpenIMSDK/tools/log"
8 | "github.com/openimsdk/openim-sdk-core/v3/pkg/sdkerrs"
9 | "github.com/openimsdk/openim-sdk-core/v3/sdk_struct"
10 | )
11 |
12 | var Config sdk_struct.IMConfig
13 |
14 | const (
15 | rotateCount uint = 0
16 | rotationTime uint = 24
17 | )
18 |
19 | // InitSDK initializes the SDK with the given operation ID and platform ID.
20 | func (f *FuncRouter) InitSDK(operationID, platformID string) {
21 | fmt.Println("InitSDK", "data=", platformID, operationID)
22 | callback := NewConnCallback(f.respMessage)
23 | j, err := strconv.ParseInt(platformID, 10, 64)
24 | if err != nil {
25 | f.respMessage.sendOnErrorResp(operationID, "InitSDK", err)
26 | return
27 | }
28 | config := sdk_struct.IMConfig{
29 | PlatformID: int32(j),
30 | ApiAddr: Config.ApiAddr,
31 | WsAddr: Config.WsAddr,
32 | DataDir: Config.DataDir,
33 | LogLevel: Config.LogLevel,
34 | IsLogStandardOutput: Config.IsLogStandardOutput,
35 | LogFilePath: Config.LogFilePath,
36 | IsExternalExtensions: Config.IsExternalExtensions,
37 | }
38 | if err := log.InitFromConfig("open-im-sdk-core", "",
39 | int(config.LogLevel), config.IsLogStandardOutput, false, config.LogFilePath,
40 | rotateCount, rotationTime); err != nil {
41 | f.respMessage.sendOnErrorResp(operationID, "InitSDK", err)
42 | return
43 | }
44 | if f.userForSDK.InitSDK(config, callback) {
45 | f.respMessage.sendOnSuccessResp(operationID, "InitSDK", "")
46 | } else {
47 | f.respMessage.sendOnErrorResp(operationID, "InitSDK", sdkerrs.ErrArgs)
48 | }
49 | }
50 |
51 | // UnInitSDK uninitializes the SDK.
52 | func (f *FuncRouter) UnInitSDK(operationID string) {
53 | if f.userForSDK == nil {
54 | fmt.Println(operationID, "UserForSDK is nil,")
55 | return
56 | }
57 | f.userForSDK.UnInitSDK()
58 | f.userForSDK = nil
59 |
60 | }
61 |
62 | // Login logs in a user using the provided arguments.
63 | func (f *FuncRouter) Login(operationID string, args ...any) {
64 | f.setAllListener()
65 | f.call(operationID, f.userForSDK.Login, args...)
66 | }
67 |
68 | // Logout logs out the current user.
69 | func (f *FuncRouter) Logout(operationID string, args ...any) {
70 | f.call(operationID, f.userForSDK.Logout, args...)
71 | }
72 |
73 | // GetLoginUserID returns the logged-in user's ID.
74 | func (f *FuncRouter) GetLoginUserID() string {
75 | if f.userForSDK == nil {
76 | return ""
77 | }
78 | return f.userForSDK.GetLoginUserID()
79 | }
80 |
81 | // SetAppBackgroundStatus updates the app's background status.
82 | func (f *FuncRouter) SetAppBackgroundStatus(operationID string, args ...any) {
83 | f.call(operationID, f.userForSDK.SetAppBackgroundStatus, args...)
84 | }
85 |
86 | // NetworkStatusChanged handles the change in network status.
87 | func (f *FuncRouter) NetworkStatusChanged(operationID string, args ...any) {
88 | f.call(operationID, f.userForSDK.NetworkStatusChanged, args...)
89 | }
90 |
91 | // GetLoginStatus retrieves the current login status of the user.
92 | func (f *FuncRouter) GetLoginStatus(operationID string, args ...any) {
93 | f.call(operationID, f.userForSDK.GetLoginStatus, args...)
94 | }
95 |
96 | // setAllListener sets all listeners for the SDK to handle various events.
97 | func (f *FuncRouter) setAllListener() {
98 | f.userForSDK.SetConversationListener(NewConversationCallback(f.respMessage))
99 | f.userForSDK.SetGroupListener(NewGroupCallback(f.respMessage))
100 | f.userForSDK.SetUserListener(NewUserCallback(f.respMessage))
101 | f.userForSDK.SetAdvancedMsgListener(NewAdvancedMsgCallback(f.respMessage))
102 | f.userForSDK.SetFriendListener(NewFriendCallback(f.respMessage))
103 | f.userForSDK.SetBatchMsgListener(NewBatchMessageCallback(f.respMessage))
104 |
105 | }
106 |
--------------------------------------------------------------------------------
/pkg/core_func/ws_init_login_test.go:
--------------------------------------------------------------------------------
1 | package core_func
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 | "testing/quick"
7 |
8 | "github.com/stretchr/testify/assert"
9 | )
10 |
11 | type TestServer struct {
12 | fu *FuncRouter
13 | operationID string
14 | sessionID string
15 | platformID string
16 | }
17 |
18 | const (
19 | operationID string = "123456"
20 | sessionID string = "111"
21 | platformID string = "1"
22 | )
23 |
24 | func NewTestServer() *TestServer {
25 | ev := make(chan *EventData, 10)
26 | return &TestServer{
27 | operationID: operationID,
28 | sessionID: sessionID,
29 | platformID: platformID,
30 | fu: NewFuncRouter(ev, sessionID),
31 | }
32 | }
33 |
34 | func TestInitSDK(t *testing.T) {
35 | te := NewTestServer()
36 | fn := func() bool {
37 | te.fu.InitSDK(te.operationID, te.platformID)
38 | msg, err := <-te.fu.respMessage.respMessagesChan
39 |
40 | ret := &EventData{
41 | OperationID: te.operationID,
42 | Event: "InitSDK",
43 | Data: "",
44 | }
45 | assert.Equal(t, true, err)
46 | assert.Equal(t, ret, msg)
47 | return true
48 | }
49 | err := quick.Check(fn, nil)
50 | assert.Nil(t, err)
51 | }
52 |
53 | func TestLogin(t *testing.T) {
54 | te := NewTestServer()
55 | fn := func() bool {
56 | te.fu.Login(te.sessionID, te.operationID)
57 | msg, err := <-te.fu.respMessage.respMessagesChan
58 |
59 | ret := &EventData{
60 | OperationID: te.operationID,
61 | Event: "Login",
62 | Data: "",
63 | }
64 | fmt.Printf("ret,msg:%v", msg)
65 | assert.Equal(t, true, err)
66 | assert.Equal(t, ret, msg)
67 | return true
68 | }
69 | err := quick.Check(fn, nil)
70 | assert.Nil(t, err)
71 | }
72 |
73 | //func (f *FuncRouter) Login(operationID string, args ...any) {
74 | // f.setAllListener()
75 | // fmt.Println(operationID, "Login")
76 | // f.call(operationID, f.userForSDK.Login, args...)
77 | //}
78 |
--------------------------------------------------------------------------------
/pkg/core_func/ws_third.go:
--------------------------------------------------------------------------------
1 | package core_func
2 |
3 | // UpdateFcmToken updates the FCM (Firebase Cloud Messaging) token for the current device session.
4 | func (f *FuncRouter) UpdateFcmToken(operationID string, args ...any) {
5 | f.call(operationID, f.userForSDK.Third().UpdateFcmToken, args...)
6 | }
7 |
8 | // SetAppBadge sets the badge count for the application's icon, typically reflecting the number of unread notifications or messages.
9 | func (f *FuncRouter) SetAppBadge(operationID string, args ...any) {
10 | f.call(operationID, f.userForSDK.Third().SetAppBadge, args...)
11 | }
12 |
13 | // UploadLogs initiates the upload of application logs to a remote server for diagnostic purposes.
14 | func (f *FuncRouter) UploadLogs(operationID string, args ...any) {
15 | f.call(operationID, f.userForSDK.Third().UploadLogs, args...)
16 | }
17 |
--------------------------------------------------------------------------------
/pkg/core_func/ws_user.go:
--------------------------------------------------------------------------------
1 | package core_func
2 |
3 | // GetUsersInfo retrieves information for the specified users.
4 | func (f *FuncRouter) GetUsersInfo(operationID string, args ...any) {
5 | f.call(operationID, f.userForSDK.Full().GetUsersInfo, args...)
6 | }
7 |
8 | // GetUsersInfoWithCache retrieves information for the specified users and uses local cache if available.
9 | func (f *FuncRouter) GetUsersInfoWithCache(operationID string, args ...any) {
10 | f.call(operationID, f.userForSDK.Full().GetUsersInfoWithCache, args...)
11 | }
12 |
13 | // GetUsersInfoFromSrv retrieves user information directly from the server, bypassing any cache.
14 | func (f *FuncRouter) GetUsersInfoFromSrv(operationID string, args ...any) {
15 | f.call(operationID, f.userForSDK.User().GetUsersInfo, args...)
16 | }
17 |
18 | // SetSelfInfo updates the current user's information.
19 | func (f *FuncRouter) SetSelfInfo(operationID string, args ...any) {
20 | f.call(operationID, f.userForSDK.User().SetSelfInfo, args...)
21 | }
22 |
23 | // SetSelfInfoEx updates the current user's information.
24 | func (f *FuncRouter) SetSelfInfoEx(operationID string, args ...any) {
25 | f.call(operationID, f.userForSDK.User().SetSelfInfo, args...)
26 | }
27 |
28 | // SetGlobalRecvMessageOpt sets the global option for receiving messages for the user.
29 | func (f *FuncRouter) SetGlobalRecvMessageOpt(operationID string, args ...any) {
30 | f.call(operationID, f.userForSDK.User().SetGlobalRecvMessageOpt, args...)
31 | }
32 |
33 | // GetSelfUserInfo retrieves the current user's information.
34 | func (f *FuncRouter) GetSelfUserInfo(operationID string) {
35 | f.call(operationID, f.userForSDK.User().GetSelfUserInfo)
36 | }
37 |
38 | // UpdateMsgSenderInfo updates the information of the message sender.
39 | func (f *FuncRouter) UpdateMsgSenderInfo(operationID string, args ...any) {
40 | f.call(operationID, f.userForSDK.User().UpdateMsgSenderInfo, args...)
41 | }
42 |
43 | // SubscribeUsersStatus subscribes to the online status updates of the specified users.
44 | func (f *FuncRouter) SubscribeUsersStatus(operationID string, args ...any) {
45 | f.call(operationID, f.userForSDK.User().SubscribeUsersStatus, args...)
46 | }
47 |
48 | // UnsubscribeUsersStatus unsubscribes from the online status updates of the specified users.
49 | func (f *FuncRouter) UnsubscribeUsersStatus(operationID string, args ...any) {
50 | f.call(operationID, f.userForSDK.User().UnsubscribeUsersStatus, args...)
51 | }
52 |
53 | // GetSubscribeUsersStatus retrieves the subscription status of online status updates for the specified users.
54 | func (f *FuncRouter) GetSubscribeUsersStatus(operationID string) {
55 | f.call(operationID, f.userForSDK.User().GetSubscribeUsersStatus)
56 | }
57 |
58 | // GetUserStatus retrieves the current status of a user.
59 | func (f *FuncRouter) GetUserStatus(operationID string, args ...any) {
60 | f.call(operationID, f.userForSDK.User().GetUserStatus, args...)
61 | }
62 |
--------------------------------------------------------------------------------
/pkg/gate/agent.go:
--------------------------------------------------------------------------------
1 | package gate
2 |
3 | import (
4 | "net"
5 | )
6 |
7 | type Agent interface {
8 | WriteMsg(msg interface{})
9 | LocalAddr() net.Addr
10 | RemoteAddr() net.Addr
11 | Close()
12 | Destroy()
13 | UserData() interface{}
14 | SetUserData(data interface{})
15 | }
16 |
--------------------------------------------------------------------------------
/pkg/gate/gate.go:
--------------------------------------------------------------------------------
1 | package gate
2 |
3 | import (
4 | common2 "github.com/openim-sigs/oimws/pkg/common"
5 | network2 "github.com/openim-sigs/oimws/pkg/network"
6 | "net"
7 | "reflect"
8 | "time"
9 |
10 | log "github.com/xuexihuang/new_log15"
11 | )
12 |
13 | type Gate struct {
14 | MaxConnNum int
15 | PendingWriteNum int
16 | MaxMsgLen uint32
17 | Processor network2.Processor
18 | //AgentChanRPC *chanrpc.Server
19 |
20 | // websocket
21 | WSAddr string
22 | HTTPTimeout time.Duration
23 | CertFile string
24 | KeyFile string
25 |
26 | // tcp
27 | TCPAddr string
28 | LenMsgLen int
29 |
30 | //add by huanglin
31 | FunNewAgent func(Agent)
32 | FunCloseAgent func(Agent)
33 | FuncMsgRecv func(interface{}, Agent)
34 | }
35 |
36 | func NewGate(maxConnNum int, maxMsgLen uint32, processor network2.Processor, WSAddr string,
37 | HTTPTimeout time.Duration, writerChanLen int) *Gate {
38 | return &Gate{MaxConnNum: maxConnNum, MaxMsgLen: maxMsgLen, Processor: processor, WSAddr: WSAddr,
39 | HTTPTimeout: HTTPTimeout, PendingWriteNum: writerChanLen}
40 | }
41 |
42 | // SetFun sets the functions for handling new agents, closing agents, and receiving messages.
43 | func (gate *Gate) SetFun(Fun1 func(Agent), Fun2 func(Agent), Fun3 func(interface{}, Agent)) {
44 | gate.FunNewAgent = Fun1
45 | gate.FunCloseAgent = Fun2
46 | gate.FuncMsgRecv = Fun3
47 | }
48 |
49 | // Run starts the gate service and listens for incoming connections.
50 | func (gate *Gate) Run(closeSig chan bool) {
51 | var wsServer *network2.WSServer
52 | if gate.WSAddr != "" {
53 | wsServer = new(network2.WSServer)
54 | wsServer.Addr = gate.WSAddr
55 | wsServer.MaxConnNum = gate.MaxConnNum
56 | wsServer.PendingWriteNum = gate.PendingWriteNum
57 | wsServer.MaxMsgLen = gate.MaxMsgLen
58 | wsServer.HTTPTimeout = gate.HTTPTimeout
59 | wsServer.CertFile = gate.CertFile
60 | wsServer.KeyFile = gate.KeyFile
61 | wsServer.NewAgent = func(conn *network2.WSConn) network2.Agent {
62 | a := &agent{conn: conn, gate: gate}
63 | /*if gate.AgentChanRPC != nil {
64 | gate.AgentChanRPC.Go("NewAgent", a)
65 | }*/
66 | /////////////////////////////////////////////////////
67 | tagent := common2.TAgentUserData{SessionID: conn.SessionId, AppString: conn.AppURL, CookieVal: conn.CookieVal}
68 | a.SetUserData(&tagent)
69 | gate.FunNewAgent(a)
70 | return a
71 | }
72 | }
73 | /*
74 | var tcpServer *network.TCPServer
75 | if gate.TCPAddr != "" {
76 | tcpServer = new(network.TCPServer)
77 | tcpServer.Addr = gate.TCPAddr
78 | tcpServer.MaxConnNum = gate.MaxConnNum
79 | tcpServer.PendingWriteNum = gate.PendingWriteNum
80 | tcpServer.LenMsgLen = gate.LenMsgLen
81 | tcpServer.MaxMsgLen = gate.MaxMsgLen
82 | tcpServer.UsePacketMode = gate.Processor.UsePacketMode()
83 | tcpServer.NewAgent = func(conn *network.TCPConn) network.Agent {
84 | a := &agent{conn: conn, gate: gate}
85 | if gate.AgentChanRPC != nil {
86 | gate.AgentChanRPC.Go("NewAgent", a)
87 | }
88 | gate.FunNewAgent(a)
89 | return a
90 | }
91 | }*/
92 |
93 | if wsServer != nil {
94 | wsServer.Start()
95 | }
96 | /*if tcpServer != nil {
97 | tcpServer.Start()
98 | }*/
99 | <-closeSig
100 | if wsServer != nil {
101 | wsServer.Close()
102 | }
103 | /*if tcpServer != nil {
104 | tcpServer.Close()
105 | }*/
106 | }
107 |
108 | func (gate *Gate) OnDestroy() {}
109 |
110 | type agent struct {
111 | conn network2.Conn
112 | gate *Gate
113 | userData interface{}
114 | }
115 |
116 | // Run processes incoming messages in a loop.
117 | func (a *agent) Run() {
118 | defer common2.TryRecoverAndDebugPrint()
119 | for {
120 | nType, data, err := a.conn.ReadMsg()
121 | if err != nil {
122 | //log.Debug("read message: %v", err)
123 | log.Info("read message error", "error", err)
124 | break
125 | }
126 | log.Debug("recve one ws msg ", "nType", nType)
127 | if a.gate.Processor != nil {
128 | msg, err := a.gate.Processor.UnmarshalMul(nType, data)
129 | if err != nil {
130 | //log.Debug("unmarshal message error: %v", err)
131 | log.Error("unmarshal message error", "err", err)
132 | break
133 | }
134 | a.gate.FuncMsgRecv(msg, a)
135 | /*err = a.gate.Processor.Route(msg, a)
136 | if err != nil {
137 | log.Debug("route message error: %v", err)
138 | break
139 | }*/
140 | }
141 | }
142 | }
143 |
144 | // OnClose is called when the agent's connection is closed.
145 | func (a *agent) OnClose() {
146 |
147 | /*if a.gate.AgentChanRPC != nil {
148 | err := a.gate.AgentChanRPC.Call0("CloseAgent", a)
149 | if err != nil {
150 | log.Error("chanrpc error: %v", err)
151 | }
152 | }*/
153 | a.gate.FunCloseAgent(a)
154 | }
155 |
156 | // WriteMsg sends a message to the client.
157 | func (a *agent) WriteMsg(msg interface{}) {
158 | if a.gate.Processor != nil {
159 | data, err := a.gate.Processor.Marshal(msg)
160 | if err != nil {
161 | //log.Error("marshal message %v error: %v", reflect.TypeOf(msg), err)
162 | log.Error("marshal message", "reflect.TypeOf(msg)", reflect.TypeOf(msg), "error", err)
163 | return
164 | }
165 | err = a.conn.WriteMsg(data)
166 | if err != nil {
167 | //log.Error("write message %v error: %v", reflect.TypeOf(msg), err)
168 | log.Error("write message error", "reflect.TypeOf(msg)", reflect.TypeOf(msg), "error", err)
169 | }
170 | }
171 | }
172 |
173 | func (a *agent) LocalAddr() net.Addr {
174 | return a.conn.LocalAddr()
175 | }
176 |
177 | func (a *agent) RemoteAddr() net.Addr {
178 | return a.conn.RemoteAddr()
179 | }
180 |
181 | func (a *agent) Close() {
182 | a.conn.Close()
183 | }
184 |
185 | func (a *agent) Destroy() {
186 | a.conn.Destroy()
187 | }
188 |
189 | func (a *agent) UserData() interface{} {
190 | return a.userData
191 | }
192 |
193 | func (a *agent) SetUserData(data interface{}) {
194 | a.userData = data
195 | }
196 |
--------------------------------------------------------------------------------
/pkg/module/MActor.go:
--------------------------------------------------------------------------------
1 | package module
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "errors"
7 | common2 "github.com/openim-sigs/oimws/pkg/common"
8 | "github.com/openim-sigs/oimws/pkg/core_func"
9 | "github.com/openim-sigs/oimws/pkg/gate"
10 | "net/url"
11 | "runtime"
12 | "sync"
13 | "sync/atomic"
14 | "time"
15 |
16 | log "github.com/xuexihuang/new_log15"
17 | )
18 |
19 | const (
20 | WsUserID = "sendID"
21 | OperationID = "operationID"
22 | PlatformID = "platformID"
23 | )
24 | const ProtocolError = "Protocol Error"
25 | const DisconnectGCLimit = 100
26 |
27 | var disConnectNum atomic.Int64
28 |
29 | type ParamStru struct {
30 | UrlPath string
31 | Token string
32 | SessionId string
33 | UserId int64
34 | GroupId int64
35 | OrgId int64
36 | OrgName string
37 | }
38 |
39 | // GetUserID parses the URL to get the UserID parameter.
40 | func (p *ParamStru) GetUserID() string {
41 | u, err := url.Parse(p.UrlPath)
42 | if err != nil {
43 | return ""
44 | }
45 | return u.Query().Get(WsUserID)
46 | }
47 |
48 | // GetOperationID parses the URL to get the OperationID parameter.
49 | func (p *ParamStru) GetOperationID() string {
50 | u, err := url.Parse(p.UrlPath)
51 | if err != nil {
52 | return ""
53 | }
54 | return u.Query().Get(OperationID)
55 | }
56 |
57 | // GetPlatformID parses the URL to get the PlatformID parameter.
58 | func (p *ParamStru) GetPlatformID() string {
59 | u, err := url.Parse(p.UrlPath)
60 | if err != nil {
61 | return ""
62 | }
63 | return u.Query().Get(PlatformID)
64 | }
65 |
66 | type ResReleaseStru struct {
67 | BackSign chan bool
68 | }
69 | type MActorIm struct {
70 | //todo your module ojb values
71 | mJsCore *JsCore
72 | heartTickerSend *time.Ticker //用于心跳send
73 | param *ParamStru
74 | nChanLen int //接收数据网络缓存
75 | wg sync.WaitGroup
76 | a gate.Agent
77 | SessionId string
78 | closeChan chan bool //主动关闭协程的通道
79 | releaseResChan chan *ResReleaseStru
80 | ReceivMsgChan chan interface{} //接收网络层数据通道
81 | heartTicker *time.Ticker //用于心跳监测
82 | heartFlag bool //初始为false,收到心跳pack设置为true
83 | isclosing bool
84 | isReleasedJscore bool
85 | }
86 |
87 | // NewMActor creates a new actor instance.
88 | func NewMActor(a gate.Agent, sessionId string, appParam *ParamStru) (MActor, error) {
89 | ret := &MActorIm{param: appParam, a: a, SessionId: sessionId, releaseResChan: make(chan *ResReleaseStru, 1), closeChan: make(chan bool, 1), nChanLen: 10, ReceivMsgChan: make(chan interface{}, 10), isclosing: false,
90 | heartTicker: time.NewTicker(100 * time.Second), heartFlag: false, heartTickerSend: time.NewTicker(28 * time.Second), isReleasedJscore: false}
91 | ///////////////////////////////////////
92 | ret.mJsCore = NewJsCore(appParam, sessionId) //todo
93 | ///////////////////////////////////////
94 | go ret.run()
95 | return ret, nil
96 | }
97 |
98 | // run contains the main loop for the actor, handling various operations.
99 | func (actor *MActorIm) run() {
100 | actor.wg.Add(1)
101 | defer common2.TryRecoverAndDebugPrint()
102 | defer actor.wg.Done()
103 | for {
104 | select {
105 | case <-actor.heartTickerSend.C: //send the heart pack
106 | if actor.isclosing == true {
107 | continue
108 | }
109 | actor.sendHeart()
110 | case <-actor.closeChan:
111 | log.Info("收到退出信号", "sessionId", actor.SessionId)
112 | if !actor.isReleasedJscore {
113 | actor.mJsCore.Destroy()
114 | }
115 | if disConnectNum.Add(1) > DisconnectGCLimit {
116 | runtime.GC()
117 | disConnectNum.Store(0)
118 | }
119 | return
120 | case resChan := <-actor.releaseResChan:
121 | log.Info("收到释放资源通道消息")
122 | actor.mJsCore.Destroy()
123 | actor.a.Destroy()
124 | actor.isReleasedJscore = true
125 | resChan.BackSign <- true
126 | case recvData := <-actor.ReceivMsgChan:
127 | if actor.isclosing == true {
128 | continue
129 | }
130 | data := recvData.(*common2.TWSData)
131 | _ = actor.doRecvPro(data)
132 | case resp := <-actor.mJsCore.RecvMsg():
133 | actor.sendEventResp(resp)
134 | if resp.Event == LogoutName {
135 | actor.isReleasedJscore = true
136 | actor.isclosing = true
137 | actor.sendClosingResp()
138 | }
139 | //case <-actor.heartTicker.C:
140 | // if actor.heartFlag == true {
141 | // actor.heartFlag = false
142 | // } else {
143 | // log.Error("心跳包超时错误", "sessionId", actor.SessionId)
144 | // actor.isclosing = true
145 | // actor.a.Destroy()
146 | // }
147 | }
148 | }
149 | }
150 | func (actor *MActorIm) ReleaseRes() {
151 | log.Info("get ReleaseRes sign")
152 | ind := &ResReleaseStru{BackSign: make(chan bool, 1)}
153 | actor.releaseResChan <- ind
154 | ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
155 | defer cancel()
156 | select {
157 | case <-ctx.Done():
158 | log.Info("出现超时返回,actor可能已经被异步destroy")
159 | case <-ind.BackSign:
160 | log.Info("通过releaseRes接口回收资源")
161 | }
162 |
163 | }
164 | func (actor *MActorIm) Destroy() {
165 | actor.closeChan <- true
166 | actor.wg.Wait()
167 | actor.a = nil
168 | log.Info("退出MQPushActorIm", "sessionId", actor.SessionId)
169 | }
170 |
171 | // ProcessRecvMsg processes received messages and sends them to the ReceivMsgChan.
172 | func (actor *MActorIm) ProcessRecvMsg(msg interface{}) error {
173 | if len(actor.ReceivMsgChan) == actor.nChanLen {
174 | log.Error("send channel is full", "sessionId", actor.SessionId)
175 | return errors.New("send channel is full")
176 | }
177 | actor.ReceivMsgChan <- msg
178 | return nil
179 | }
180 |
181 | // doRecvPro processes the message received from the network layer.
182 | func (actor *MActorIm) doRecvPro(data *common2.TWSData) error {
183 | log.Info("message come here", "data", data)
184 | if data.MsgType == common2.MessageText {
185 | req := &Req{}
186 | err := json.Unmarshal(data.Msg, req)
187 | if err != nil {
188 | log.Error("parse protocol err", "err", err, "sessionId", actor.SessionId)
189 | actor.sendEventResp(&core_func.EventData{Event: ProtocolError, ErrCode: 20000, ErrMsg: err.Error(),
190 | OperationID: req.OperationID})
191 | return err
192 | }
193 | log.Info("receive req", "req", req, "sessionId", actor.SessionId)
194 | err = actor.mJsCore.SendMsg(req)
195 | if err != nil {
196 | actor.sendEventResp(&core_func.EventData{Event: req.ReqFuncName, ErrCode: 20000, ErrMsg: err.Error(),
197 | OperationID: req.OperationID})
198 | }
199 | }
200 | return nil
201 | }
202 |
203 | // sendResp sends a response message to the WebSocket client.
204 | func (actor *MActorIm) sendHeart() {
205 | //heart := []byte("ping")
206 | resSend := &common2.TWSData{MsgType: common2.PingMessage, Msg: nil}
207 | actor.a.WriteMsg(resSend)
208 | }
209 |
210 | // sendEventResp sends an event response to the WebSocket client.
211 | func (actor *MActorIm) sendEventResp(res *core_func.EventData) {
212 | resb, _ := json.Marshal(res)
213 | resSend := &common2.TWSData{MsgType: common2.MessageText, Msg: resb}
214 | actor.a.WriteMsg(resSend)
215 | }
216 |
217 | func (actor *MActorIm) sendClosingResp() {
218 | resSend := &common2.TWSData{MsgType: common2.CloseMessage, Msg: nil}
219 | actor.a.WriteMsg(resSend)
220 | }
221 |
--------------------------------------------------------------------------------
/pkg/module/jsCore.go:
--------------------------------------------------------------------------------
1 | package module
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "github.com/openim-sigs/oimws/pkg/core_func"
7 | "reflect"
8 |
9 | "github.com/openimsdk/openim-sdk-core/v3/pkg/utils"
10 | )
11 |
12 | const (
13 | LogoutTips = "js sdk socket close"
14 | LogoutName = "Logout"
15 | )
16 |
17 | type JsCore struct {
18 | RespMessagesChan chan *core_func.EventData
19 | funcRouter *core_func.FuncRouter
20 | }
21 |
22 | type Req struct {
23 | ReqFuncName string `json:"reqFuncName" `
24 | OperationID string `json:"operationID"`
25 | Data string `json:"data"`
26 | UserID string `json:"userID"`
27 | Batch int `json:"batchMsg"`
28 | }
29 | type JsInterface interface {
30 | RecvMsg() chan interface{} //todo your sturct,error or response
31 | SendMsg(interface{}) error
32 | Destroy()
33 | }
34 |
35 | // NewJsCore creates a new JsCore instance.
36 | func NewJsCore(para *ParamStru, sessionId string) *JsCore {
37 | respChan := make(chan *core_func.EventData, 100)
38 | funcRouter := core_func.NewFuncRouter(respChan, sessionId)
39 | fmt.Println("NewJsCore", "data=", "sessionId", sessionId)
40 | funcRouter.InitSDK(para.GetOperationID(), para.GetPlatformID())
41 | return &JsCore{RespMessagesChan: respChan, funcRouter: funcRouter}
42 | }
43 |
44 | // RecvMsg returns the channel to receive messages.
45 | func (core *JsCore) RecvMsg() chan *core_func.EventData {
46 | return core.RespMessagesChan
47 | }
48 |
49 | // SendMsg processes the incoming request and calls the corresponding method.
50 | func (core *JsCore) SendMsg(req *Req) error {
51 | fmt.Println("method is valid", "data=", req)
52 | methodValue := reflect.ValueOf(core.funcRouter).MethodByName(req.ReqFuncName)
53 | if !methodValue.IsValid() {
54 | //log.ZWarn(context.Background(), "method is valid", errors.New("method is valid"), "data", req)
55 | fmt.Println("method is valid", "data=", req)
56 | return utils.Wrap(fmt.Errorf("method is valid"), "method is valid")
57 | }
58 | var args []any
59 | if err := json.Unmarshal([]byte(req.Data), &args); err != nil {
60 | return utils.Wrap(err, "json.Unmarshal failed")
61 | }
62 | // Convert args to []reflect.Value
63 | args = append([]any{req.OperationID}, args...)
64 | argsValue := make([]reflect.Value, len(args))
65 | for i, arg := range args {
66 | if arg == nil {
67 | return utils.Wrap(fmt.Errorf("args[%d] is not nil", i), "args has nil")
68 | }
69 | argsValue[i] = reflect.ValueOf(arg)
70 | }
71 | methodValue.Call(argsValue)
72 | return nil
73 | }
74 |
75 | // Destroy performs cleanup when the core is no longer needed.
76 | func (core *JsCore) Destroy() {
77 | core.funcRouter.Logout(LogoutTips)
78 | }
79 |
--------------------------------------------------------------------------------
/pkg/module/netmodule.go:
--------------------------------------------------------------------------------
1 | package module
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "github.com/openim-sigs/oimws/pkg/common"
7 | "github.com/openim-sigs/oimws/pkg/gate"
8 | log "github.com/xuexihuang/new_log15"
9 | "net/url"
10 | "sync"
11 | )
12 |
13 | type JsActorMap struct {
14 | sync.Mutex
15 | uActors map[string]MActor
16 | }
17 |
18 | var GJsActors *JsActorMap
19 |
20 | func init() {
21 | GJsActors = &JsActorMap{uActors: make(map[string]MActor)}
22 | }
23 |
24 | type MActor interface {
25 | ProcessRecvMsg(interface{}) error
26 | Destroy()
27 | //
28 | ReleaseRes()
29 | run()
30 | }
31 |
32 | // NewAgent is called when a new WebSocket connection is established. It initializes agent-related data and checks the token validity.
33 | func NewAgent(a gate.Agent) {
34 | aUerData := a.UserData().(*common.TAgentUserData)
35 | log.Info("one ws connect", "sessionId", aUerData.SessionID)
36 | param, err := checkToken(aUerData)
37 | if err != nil {
38 | log.Error("Token validation failed", "userData", aUerData, "sessionId", aUerData.SessionID)
39 |
40 | res := &ResponseSt{Type: RESP_OP_TYPE, Cmd: CONN_CMD, Success: false, ErrMsg: "check token error"}
41 | resb, _ := json.Marshal(res)
42 | resSend := &common.TWSData{MsgType: common.MessageText, Msg: resb}
43 | a.WriteMsg(resSend)
44 | a.Close()
45 | return
46 | }
47 | log.Info("checkToken info", "param", param, "err", err)
48 | actor, err := NewMActor(a, param.SessionId, param)
49 | if err != nil {
50 | log.Error("NewMQActor error", "err", err, "sessionId", aUerData.SessionID)
51 | res := &ResponseSt{Type: RESP_OP_TYPE, Cmd: CONN_CMD, Success: false, ErrMsg: "NewMQActor error"}
52 | resb, _ := json.Marshal(res)
53 | resSend := &common.TWSData{MsgType: common.MessageText, Msg: resb}
54 | a.WriteMsg(resSend)
55 | a.Close()
56 | return
57 | }
58 | GJsActors.Lock()
59 | v, ok := GJsActors.uActors[param.GetUserID()]
60 | if ok {
61 | v.ReleaseRes()
62 | }
63 | GJsActors.uActors[param.GetUserID()] = actor
64 | GJsActors.Unlock()
65 | aUerData.ProxyBody = actor
66 | aUerData.UserId = param.GetUserID()
67 | a.SetUserData(aUerData)
68 | log.Info("one linked", "param", param, "sessionId", aUerData.SessionID)
69 | }
70 |
71 | // CloseAgent is called when the WebSocket connection is closed. It performs cleanup actions for the agent.
72 | func CloseAgent(a gate.Agent) {
73 | aUerData := a.UserData().(*common.TAgentUserData)
74 | if aUerData.ProxyBody != nil {
75 | aUerData.ProxyBody.(MActor).Destroy()
76 | aUerData.ProxyBody = nil
77 | }
78 | GJsActors.Lock()
79 | _, ok := GJsActors.uActors[aUerData.UserId]
80 | if ok {
81 | delete(GJsActors.uActors, aUerData.UserId)
82 | }
83 | GJsActors.Unlock()
84 | log.Info("one dislinkder", "sessionId", a.UserData().(*common.TAgentUserData).SessionID)
85 | }
86 |
87 | // DataRecv is called when new data is received on the WebSocket connection. It processes the incoming data through the actor.
88 | func DataRecv(data interface{}, a gate.Agent) {
89 | aUerData := a.UserData().(*common.TAgentUserData)
90 | if aUerData.ProxyBody != nil {
91 | err := aUerData.ProxyBody.(MActor).ProcessRecvMsg(data)
92 | if err != nil {
93 | log.Error("Overflow error", "sessionId", aUerData.SessionID)
94 | a.Destroy()
95 | }
96 | }
97 | }
98 |
99 | // checkToken validates the session token contained in the user data.
100 | func checkToken(data *common.TAgentUserData) (*ParamStru, error) {
101 | ret := new(ParamStru)
102 | ret.SessionId = data.SessionID
103 | var token string
104 | if data.CookieVal != "" {
105 | token = data.CookieVal
106 | } else {
107 | /////////////////////
108 | u, err := url.Parse(data.AppString)
109 | if err != nil {
110 | log.Error("ws url path not correct", "sessionId", data.SessionID)
111 | return nil, errors.New("ws url path not correct")
112 | }
113 | q := u.Query()
114 | token = q.Get("token")
115 | //////////////////////
116 | }
117 | if token == "" {
118 | log.Error("Token retrieval is empty", "sessionId", data.SessionID)
119 | return nil, errors.New("Token retrieval is empty")
120 | }
121 | // TODO: Add your token validation logic here to verify the legitimacy of the token
122 | //ret.UserId=""
123 | ret.UrlPath = data.AppString
124 | ret.Token = token
125 | if ret.GetUserID() == "" {
126 | log.Error("userId is empty!")
127 | return nil, errors.New("userId is empty")
128 | }
129 | return ret, nil
130 | }
131 |
--------------------------------------------------------------------------------
/pkg/module/statusActor.go:
--------------------------------------------------------------------------------
1 | package module
2 |
3 | import (
4 | "encoding/json"
5 | common2 "github.com/openim-sigs/oimws/pkg/common"
6 | "github.com/openim-sigs/oimws/pkg/gate"
7 | log "github.com/xuexihuang/new_log15"
8 | "sync"
9 | "time"
10 | )
11 |
12 | type StatusActorIm struct {
13 | nChanLen int //接收数据网络缓存
14 | heartTickerSend *time.Ticker //用于心跳send
15 | wg sync.WaitGroup
16 | a gate.Agent
17 | SessionId string
18 | closeChan chan bool //主动关闭协程的通道
19 | ReceivMsgChan chan interface{} //接收网络层数据通道
20 | isclosing bool
21 | }
22 |
23 | func NewStatusActor(a gate.Agent, sessionId string, appParam *ParamStru) (MActor, error) {
24 |
25 | ret := &StatusActorIm{a: a, SessionId: sessionId, closeChan: make(chan bool, 1), nChanLen: 10, ReceivMsgChan: make(chan interface{}, 10), isclosing: false,
26 | heartTickerSend: time.NewTicker(100 * time.Second)}
27 | go ret.run()
28 | return ret, nil
29 | }
30 | func (actor *StatusActorIm) run() {
31 | actor.wg.Add(1)
32 | defer common2.TryRecoverAndDebugPrint()
33 | defer actor.wg.Done()
34 | for {
35 | select {
36 | case <-actor.heartTickerSend.C: //send the heart pack
37 | if actor.isclosing == true {
38 | continue
39 | }
40 | actor.sendHeart()
41 | case <-actor.closeChan:
42 | log.Info("收到退出信号", "sessionId", actor.SessionId)
43 | return
44 | case recvData := <-actor.ReceivMsgChan:
45 | if actor.isclosing == true {
46 | continue
47 | }
48 | data := recvData.(*common2.TWSData)
49 | _ = actor.doRecvPro(data)
50 | }
51 | }
52 | }
53 | func (actor *StatusActorIm) ProcessRecvMsg(interface{}) error {
54 |
55 | return nil
56 | }
57 | func (actor *StatusActorIm) Destroy() {
58 | actor.closeChan <- true
59 | actor.wg.Wait()
60 | actor.a = nil
61 | log.Info("退出MQPushActorIm", "sessionId", actor.SessionId)
62 | }
63 | func (actor *StatusActorIm) ReleaseRes() {
64 |
65 | }
66 | func (actor *StatusActorIm) sendHeart() {
67 | //heart := []byte("ping")
68 | resSend := &common2.TWSData{MsgType: common2.PingMessage, Msg: nil}
69 | actor.a.WriteMsg(resSend)
70 | }
71 | func (actor *StatusActorIm) doRecvPro(data *common2.TWSData) error {
72 | log.Info("message come here", "data.type", data.MsgType)
73 | if data.MsgType == common2.MessageText {
74 | req := &RequestSt{}
75 | err := json.Unmarshal(data.Msg, req)
76 | if err != nil {
77 | log.Error("解析前端协议出错", "err", err, "sessionId", actor.SessionId)
78 | return err
79 | }
80 | log.Info("收到命令", "req", req, "sessionId", actor.SessionId)
81 | ////////////////////////////////////////////////
82 | res := ResponseSt{Type: RESP_OP_TYPE, Success: true, UserId: genGroupUserIds(), Duration: time.Now().Unix() - ProgressStartTime}
83 | resb, _ := json.Marshal(res)
84 | resSend := &common2.TWSData{MsgType: common2.MessageText, Msg: resb}
85 | actor.a.WriteMsg(resSend)
86 | }
87 | return nil
88 | }
89 | func genGroupUserIds() []string {
90 | var ret []string
91 | GJsActors.Lock()
92 | defer GJsActors.Unlock()
93 | for k, _ := range GJsActors.uActors {
94 | ret = append(ret, k)
95 | }
96 | return ret
97 | }
98 |
--------------------------------------------------------------------------------
/pkg/module/statusNetmodule.go:
--------------------------------------------------------------------------------
1 | package module
2 |
3 | import (
4 | "encoding/json"
5 | "github.com/openim-sigs/oimws/pkg/common"
6 | "github.com/openim-sigs/oimws/pkg/gate"
7 | log "github.com/xuexihuang/new_log15"
8 | )
9 |
10 | var ProgressStartTime int64
11 |
12 | func NewStatusAgent(a gate.Agent) {
13 | aUerData := a.UserData().(*common.TAgentUserData)
14 | log.Info("one status ws connect", "sessionId", aUerData.SessionID)
15 | actor, err := NewStatusActor(a, aUerData.SessionID, nil)
16 | if err != nil {
17 | log.Error("NewStatusActor error", "err", err, "sessionId", aUerData.SessionID)
18 | res := &ResponseSt{Type: RESP_OP_TYPE, Success: false, ErrMsg: "NewMQActor error"}
19 | resb, _ := json.Marshal(res)
20 | resSend := &common.TWSData{MsgType: common.MessageText, Msg: resb}
21 | a.WriteMsg(resSend)
22 | a.Close()
23 | return
24 | }
25 | aUerData.ProxyBody = actor
26 | a.SetUserData(aUerData)
27 | log.Info("one status linked", "sessionId", aUerData.SessionID)
28 |
29 | }
30 | func CloseStatusAgent(a gate.Agent) {
31 | aUerData := a.UserData().(*common.TAgentUserData)
32 | if aUerData.ProxyBody != nil {
33 | aUerData.ProxyBody.(MActor).Destroy()
34 | aUerData.ProxyBody = nil
35 | }
36 | log.Info("one status dislinkder", "sessionId", a.UserData().(*common.TAgentUserData).SessionID)
37 | }
38 | func DataRecvStatus(data interface{}, a gate.Agent) {
39 | aUerData := a.UserData().(*common.TAgentUserData)
40 | if aUerData.ProxyBody != nil {
41 | err := aUerData.ProxyBody.(MActor).ProcessRecvMsg(data)
42 | if err != nil {
43 | log.Error("溢出错误", "sessionId", aUerData.SessionID)
44 | a.Destroy()
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/pkg/module/struct.go:
--------------------------------------------------------------------------------
1 | package module
2 |
3 | const (
4 | RESP_OP_TYPE = "response"
5 | MQ_MSG_TYPE = "mqMessage"
6 | HEART_CONFIG_TYPE = "heartConfig"
7 | CONN_CMD = "connect"
8 | SUB_CMD = "subscribe"
9 | UNSUB_CMD = "unsubscribe"
10 | HEART_CMD = "heart"
11 | )
12 |
13 | type ResponseSt struct {
14 | Type string `json:"type"` //"response" or "mqMessage" or "heartConfig"
15 | Cmd string `json:"cmd"` //"connect" "subscribe" "unsubscribe"
16 | Success bool `json:"success"` //
17 | ErrMsg string `json:"errMsg"`
18 | UserId []string `json:"userIds"`
19 | Duration int64 `json:"duration"` // progress run time ,seconds
20 | RequestId string `json:"requestId"`
21 | Topic string `json:"topic"`
22 | Extra string `json:"extra"`
23 | MsgTimeStamp int64 `json:"msgTimeStamp"`
24 | MsgSeqId int64 `json:"msgSeqId"`
25 | Data string `json:"data"`
26 | Rate int64 `json:"rate"`
27 | }
28 |
29 | type RequestSt struct {
30 | Cmd string `json:"cmd"` // "subscribe" "unsubscribe" or "heart"
31 | RequestId string `json:"requestId"`
32 | Topic string `json:"topic"`
33 | Extra string `json:"extra"`
34 | MsgTimeStamp int64 `json:"msgStartTime"`
35 | MsgSeqId int64 `json:"msgSeqId"`
36 | }
37 |
--------------------------------------------------------------------------------
/pkg/network/agent.go:
--------------------------------------------------------------------------------
1 | package network
2 |
3 | type Agent interface {
4 | Run()
5 | OnClose()
6 | }
7 |
--------------------------------------------------------------------------------
/pkg/network/conn.go:
--------------------------------------------------------------------------------
1 | package network
2 |
3 | import (
4 | "github.com/openim-sigs/oimws/pkg/common"
5 | "net"
6 | )
7 |
8 | type Conn interface {
9 | ReadMsg() (int, []byte, error)
10 | WriteMsg(args *common.TWSData) error
11 | LocalAddr() net.Addr
12 | RemoteAddr() net.Addr
13 | Close()
14 | Destroy()
15 | }
16 |
--------------------------------------------------------------------------------
/pkg/network/defines.go:
--------------------------------------------------------------------------------
1 | package network
2 |
3 | const (
4 | LittleEndian = false
5 | )
6 |
--------------------------------------------------------------------------------
/pkg/network/processor.go:
--------------------------------------------------------------------------------
1 | package network
2 |
3 | import (
4 | "github.com/openim-sigs/oimws/pkg/common"
5 | )
6 |
7 | type Processor interface {
8 | // must goroutine safe
9 | Route(msg interface{}, userData interface{}) error
10 | // must goroutine safe
11 | Unmarshal(data []byte) (interface{}, error)
12 | UnmarshalMul(nType int, data []byte) (interface{}, error)
13 | // must goroutine safe
14 | Marshal(msg interface{}) (*common.TWSData, error)
15 | // Whether to use packet mode for packing/unpacking
16 | UsePacketMode() bool
17 | }
18 |
--------------------------------------------------------------------------------
/pkg/network/tjson/tjson.go:
--------------------------------------------------------------------------------
1 | package tjson
2 |
3 | import (
4 | "github.com/openim-sigs/oimws/pkg/common"
5 | )
6 |
7 | type Login struct {
8 | UserName string
9 | PassWord string
10 | }
11 |
12 | type Processor struct {
13 | }
14 |
15 | // NewProcessor is a constructor for Processor.
16 | func NewProcessor() *Processor {
17 | p := new(Processor)
18 | return p
19 | }
20 |
21 | // UsePacketMode returns false indicating that the processor is likely used in a stream mode and not packet mode.
22 | func (p *Processor) UsePacketMode() bool {
23 | return false
24 | }
25 |
26 | // Marshal takes a message interface and converts it into a WebSocket data structure.
27 | func (p *Processor) Marshal(msg interface{}) (*common.TWSData, error) {
28 | tsend := msg.(*common.TWSData)
29 | //if tsend.MsgType != common.MessageText && tsend.MsgType != common.MessageBinary {
30 | // return nil, errors.New("msg is not correct")
31 | //}
32 | return tsend, nil
33 | }
34 |
35 | // Unmarshal creates a new Login struct with preset credentials, not actually using the input data.
36 | func (p *Processor) Unmarshal(data []byte) (interface{}, error) {
37 | return &Login{UserName: "nihao", PassWord: "huanglin"}, nil
38 | }
39 |
40 | // Route currently does nothing and always returns nil, indicating no error.
41 | func (p *Processor) Route(msg interface{}, userData interface{}) error {
42 | return nil
43 | }
44 |
45 | // UnmarshalMul takes a message type and data, wraps it into a TWSData struct, and returns it.
46 | func (p *Processor) UnmarshalMul(nType int, data []byte) (interface{}, error) {
47 | ret := &common.TWSData{}
48 | if nType == common.MessageText {
49 | ret.MsgType = common.MessageText
50 | ret.Msg = data
51 | } else {
52 | ret.MsgType = common.MessageBinary
53 | ret.Msg = data
54 | }
55 | return ret, nil
56 | }
57 |
--------------------------------------------------------------------------------
/pkg/network/ws_client.go:
--------------------------------------------------------------------------------
1 | package network
2 |
3 | import (
4 | "sync"
5 | "time"
6 |
7 | log "github.com/xuexihuang/new_log15"
8 |
9 | "github.com/gorilla/websocket"
10 | )
11 |
12 | const (
13 | MaxMsgLen = 1024 * 1024 * 10
14 | )
15 |
16 | // WSClient just for client dial to websocket server
17 | type WSClient struct {
18 | sync.Mutex
19 | Addr string
20 | ConnNum int
21 | ConnectInterval time.Duration
22 | PendingWriteNum int
23 | MaxMsgLen uint32
24 | HandshakeTimeout time.Duration
25 | AutoReconnect bool
26 | NewAgent func(*WSConn) Agent
27 | dialer websocket.Dialer
28 | conns WebsocketConnSet
29 | wg sync.WaitGroup
30 | closeFlag bool
31 | }
32 |
33 | // Start initializes and starts the WebSocket client.
34 | func (client *WSClient) Start() {
35 | client.init()
36 |
37 | for i := 0; i < client.ConnNum; i++ {
38 | client.wg.Add(1)
39 | go client.connect()
40 | }
41 | }
42 |
43 | // init prepares the client by setting default values and validating settings.
44 | func (client *WSClient) init() {
45 | client.Lock()
46 | defer client.Unlock()
47 |
48 | if client.ConnNum <= 0 {
49 | client.ConnNum = 1
50 | //log.Release("invalid ConnNum, reset to %v", client.ConnNum)
51 | log.Info("invalid ConnNum, reset", "client.ConnNum", client.ConnNum)
52 | }
53 | if client.ConnectInterval <= 0 {
54 | client.ConnectInterval = 3 * time.Second
55 | //log.Release("invalid ConnectInterval, reset to %v", client.ConnectInterval)
56 | log.Info("invalid ConnectInterval, reset", "client.ConnectInterval", client.ConnectInterval)
57 | }
58 | if client.PendingWriteNum <= 0 {
59 | client.PendingWriteNum = 100
60 | //log.Release("invalid PendingWriteNum, reset to %v", client.PendingWriteNum)
61 | log.Info("invalid PendingWriteNum, reset", "client.PendingWriteNum", client.PendingWriteNum)
62 | }
63 | if client.MaxMsgLen <= 0 {
64 | client.MaxMsgLen = MaxMsgLen
65 | //log.Release("invalid MaxMsgLen, reset to %v", client.MaxMsgLen)
66 | log.Info("invalid MaxMsgLen, reset", "client.MaxMsgLen", client.MaxMsgLen)
67 | }
68 | if client.HandshakeTimeout <= 0 {
69 | client.HandshakeTimeout = 10 * time.Second
70 | //log.Release("invalid HandshakeTimeout, reset to %v", client.HandshakeTimeout)
71 | log.Info("invalid HandshakeTimeout, reset", "client.HandshakeTimeout", client.HandshakeTimeout)
72 | }
73 | if client.NewAgent == nil {
74 | //log.Fatal("NewAgent must not be nil")
75 | log.Crit("NewAgent must not be nil")
76 | }
77 | if client.conns != nil {
78 | //log.Fatal("client is running")
79 | log.Crit("client is running")
80 | }
81 |
82 | client.conns = make(WebsocketConnSet)
83 | client.closeFlag = false
84 | client.dialer = websocket.Dialer{
85 | HandshakeTimeout: client.HandshakeTimeout,
86 | }
87 | }
88 |
89 | // dial creates a new WebSocket connection.
90 | func (client *WSClient) dial() *websocket.Conn {
91 | for {
92 | conn, _, err := client.dialer.Dial(client.Addr, nil)
93 | if err == nil || client.closeFlag {
94 | return conn
95 | }
96 |
97 | //log.Release("connect to %v error: %v", client.Addr, err)
98 | log.Info("connect error", "client.Addr", client.Addr, "err", err)
99 | time.Sleep(client.ConnectInterval)
100 | continue
101 | }
102 | }
103 |
104 | // connect handles the connection lifecycle.
105 | func (client *WSClient) connect() {
106 | defer client.wg.Done()
107 |
108 | reconnect:
109 | conn := client.dial()
110 | if conn == nil {
111 | return
112 | }
113 | conn.SetReadLimit(int64(client.MaxMsgLen))
114 |
115 | client.Lock()
116 | if client.closeFlag {
117 | client.Unlock()
118 | conn.Close()
119 | return
120 | }
121 | client.conns[conn] = struct{}{}
122 | client.Unlock()
123 |
124 | wsConn := newWSConn(conn, client.PendingWriteNum, client.MaxMsgLen, "", "")
125 | agent := client.NewAgent(wsConn)
126 | agent.Run()
127 |
128 | // cleanup
129 | wsConn.Close()
130 | client.Lock()
131 | delete(client.conns, conn)
132 | client.Unlock()
133 | agent.OnClose()
134 |
135 | if client.AutoReconnect {
136 | time.Sleep(client.ConnectInterval)
137 | goto reconnect
138 | }
139 | }
140 |
141 | // Close initiates the shutdown process for the client.
142 | func (client *WSClient) Close() {
143 | client.Lock()
144 | client.closeFlag = true
145 | for conn := range client.conns {
146 | conn.Close()
147 | }
148 | client.conns = nil
149 | client.Unlock()
150 |
151 | client.wg.Wait()
152 | }
153 |
--------------------------------------------------------------------------------
/pkg/network/ws_conn.go:
--------------------------------------------------------------------------------
1 | package network
2 |
3 | import (
4 | "errors"
5 | "github.com/openim-sigs/oimws/pkg/common"
6 | "net"
7 | "sync"
8 |
9 | "github.com/gorilla/websocket"
10 | log "github.com/xuexihuang/new_log15"
11 | )
12 |
13 | type WebsocketConnSet map[*websocket.Conn]struct{}
14 |
15 | type WSConn struct {
16 | sync.Mutex
17 | conn *websocket.Conn
18 | writeChan chan *common.TWSData
19 | maxMsgLen uint32
20 | closeFlag bool
21 | //add by hl
22 | SessionId string
23 | AppParam common.TAppParam
24 | AppURL string
25 | CookieVal string
26 | }
27 |
28 | // newWSConn initializes a new WSConn object.
29 | func newWSConn(conn *websocket.Conn, pendingWriteNum int, maxMsgLen uint32, appurl string, cookieVal string) *WSConn {
30 | log.Error("test4.1", pendingWriteNum)
31 | wsConn := new(WSConn)
32 | wsConn.conn = conn
33 | wsConn.writeChan = make(chan *common.TWSData, pendingWriteNum)
34 | log.Error("test4.1.1", pendingWriteNum)
35 | wsConn.maxMsgLen = maxMsgLen
36 | /////////////////////////////生成唯一sessionid。
37 | var sessionID string
38 | log.Error("test4.1.2", pendingWriteNum)
39 | //sessionID = common.GetRandomSessionId()
40 |
41 | log.Error("test4.1.3", pendingWriteNum)
42 | wsConn.SessionId = sessionID
43 | wsConn.AppURL = appurl
44 | wsConn.CookieVal = cookieVal
45 | log.Error("test4.2")
46 | go func() {
47 | for b := range wsConn.writeChan {
48 | if b == nil {
49 | break
50 | }
51 | var err error
52 | if b.MsgType == common.MessageBinary {
53 | err = conn.WriteMessage(websocket.BinaryMessage, b.Msg)
54 | } else if b.MsgType == common.MessageText {
55 | err = conn.WriteMessage(websocket.TextMessage, b.Msg)
56 | } else if b.MsgType == common.PingMessage {
57 | log.Info("ping message", "b", b)
58 | err = conn.WriteMessage(websocket.PingMessage, b.Msg)
59 | } else if b.MsgType == common.CloseMessage {
60 | log.Info("close message", "b", b)
61 | err = conn.WriteMessage(websocket.CloseMessage, b.Msg)
62 | break
63 | }
64 | if err != nil {
65 | log.Error("send message err", "err", err.Error())
66 | break
67 | }
68 | //fmt.Println("send msg is :", b)
69 | }
70 |
71 | conn.Close()
72 | wsConn.Lock()
73 | wsConn.closeFlag = true
74 | wsConn.Unlock()
75 | }()
76 | log.Error("test4.3")
77 | return wsConn
78 | }
79 |
80 | // doDestroy forcefully closes the connection without waiting for pending writes.
81 | func (wsConn *WSConn) doDestroy() {
82 | wsConn.conn.UnderlyingConn().(*net.TCPConn).SetLinger(0)
83 | wsConn.conn.Close()
84 |
85 | if !wsConn.closeFlag {
86 | close(wsConn.writeChan)
87 | wsConn.closeFlag = true
88 | }
89 | }
90 |
91 | // Destroy cleanly closes the connection.
92 | func (wsConn *WSConn) Destroy() {
93 | wsConn.Lock()
94 | defer wsConn.Unlock()
95 |
96 | wsConn.doDestroy()
97 | }
98 |
99 | // Close initiates a graceful shutdown of the connection.
100 | func (wsConn *WSConn) Close() {
101 | wsConn.Lock()
102 | defer wsConn.Unlock()
103 | if wsConn.closeFlag {
104 | return
105 | }
106 |
107 | wsConn.doWrite(nil)
108 | wsConn.closeFlag = true
109 | }
110 |
111 | // doWrite enqueues a message for writing to the websocket connection.
112 | func (wsConn *WSConn) doWrite(b *common.TWSData) {
113 | if len(wsConn.writeChan) == cap(wsConn.writeChan) {
114 | //log.Debug("close conn: channel full")
115 | log.Error("close conn: channel full")
116 | wsConn.doDestroy()
117 | return
118 | }
119 |
120 | wsConn.writeChan <- b
121 | }
122 |
123 | // LocalAddr returns the local network address.
124 | func (wsConn *WSConn) LocalAddr() net.Addr {
125 | return wsConn.conn.LocalAddr()
126 | }
127 |
128 | // RemoteAddr returns the remote network address.
129 | func (wsConn *WSConn) RemoteAddr() net.Addr {
130 | return wsConn.conn.RemoteAddr()
131 | }
132 |
133 | // ReadMsg reads a message from the websocket connection.(goroutine not safe)
134 | // goroutine not safe.
135 | func (wsConn *WSConn) ReadMsg() (int, []byte, error) {
136 | nTye, b, err := wsConn.conn.ReadMessage()
137 | return nTye, b, err
138 | }
139 |
140 | // WriteMsg writes a message to the websocket connection.(Args must not be modified by the others goroutines)
141 | // args must not be modified by the others goroutines.
142 | func (wsConn *WSConn) WriteMsg(args *common.TWSData) error {
143 | wsConn.Lock()
144 | defer wsConn.Unlock()
145 | if wsConn.closeFlag {
146 | return nil
147 | }
148 |
149 | // get len
150 | var msgLen uint32
151 | msgLen = uint32(len(args.Msg))
152 |
153 | // check len
154 | if msgLen > wsConn.maxMsgLen {
155 | return errors.New("message too long")
156 | }
157 |
158 | wsConn.doWrite(args)
159 | return nil
160 |
161 | }
162 |
--------------------------------------------------------------------------------
/pkg/network/ws_server.go:
--------------------------------------------------------------------------------
1 | package network
2 |
3 | import (
4 | "crypto/tls"
5 | "fmt"
6 | "github.com/openim-sigs/oimws/pkg/common"
7 | "net"
8 | "net/http"
9 | "sync"
10 | "time"
11 |
12 | log "github.com/xuexihuang/new_log15"
13 |
14 | "github.com/gorilla/websocket"
15 | )
16 |
17 | type WSServer struct {
18 | Addr string
19 | MaxConnNum int
20 | PendingWriteNum int
21 | MaxMsgLen uint32
22 | HTTPTimeout time.Duration
23 | CertFile string
24 | KeyFile string
25 | NewAgent func(*WSConn) Agent
26 | ln net.Listener
27 | handler *WSHandler
28 | }
29 |
30 | type WSHandler struct {
31 | maxConnNum int
32 | pendingWriteNum int
33 | maxMsgLen uint32
34 | newAgent func(*WSConn) Agent
35 | upgrader websocket.Upgrader
36 | conns WebsocketConnSet
37 | mutexConns sync.Mutex
38 | wg sync.WaitGroup
39 | }
40 |
41 | // ServeHTTP handles HTTP requests and upgrades them to WebSocket if the request is valid.
42 | func (handler *WSHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
43 | defer common.TryRecoverAndDebugPrint()
44 | if r.Method != "GET" {
45 | http.Error(w, "Method not allowed", 405)
46 | return
47 | }
48 | var cookieVal string
49 | /*
50 | cookieToken, err := r.Cookie("token")
51 | if err != nil {
52 | log.Info("http的cookie中没有对应token", "err", err)
53 | } else {
54 | cookieVal = cookieToken.Value
55 | }*/
56 | token := r.Header.Get("Authorization")
57 | if token != "" && len(token) > 7 {
58 | cookieVal = token[7:]
59 | } else {
60 | log.Info("http的headers Authorization中没有对应token")
61 | }
62 | log.Info("token info", "token", cookieVal)
63 |
64 | fmt.Println("url is:", r.URL.Path)
65 | conn, err := handler.upgrader.Upgrade(w, r, nil)
66 | if err != nil {
67 | log.Error("upgrade error", "err", err, "remoteIp", r.Host)
68 | return
69 | }
70 | conn.SetReadLimit(int64(handler.maxMsgLen))
71 | _ = conn.SetReadDeadline(time.Now().Add(30 * time.Second))
72 | conn.SetPongHandler(func(appData string) error {
73 | conn.SetReadDeadline(time.Now().Add(30 * time.Second))
74 | log.Info("js replying with a pong packet.")
75 | return nil
76 | })
77 | log.Error("test1")
78 | handler.wg.Add(1)
79 | defer handler.wg.Done()
80 |
81 | handler.mutexConns.Lock()
82 | if handler.conns == nil {
83 | handler.mutexConns.Unlock()
84 | conn.Close()
85 | return
86 | }
87 | log.Error("test2")
88 | if len(handler.conns) >= handler.maxConnNum {
89 | handler.mutexConns.Unlock()
90 | conn.Close()
91 | log.Error("too many connections")
92 | return
93 | }
94 | log.Error("test3")
95 | handler.conns[conn] = struct{}{}
96 | handler.mutexConns.Unlock()
97 |
98 | log.Error("test4")
99 | wsConn := newWSConn(conn, handler.pendingWriteNum, handler.maxMsgLen, r.URL.String(), cookieVal)
100 | log.Error("tes5")
101 | agent := handler.newAgent(wsConn)
102 | agent.Run()
103 |
104 | // cleanup
105 | wsConn.Close()
106 | handler.mutexConns.Lock()
107 | delete(handler.conns, conn)
108 | handler.mutexConns.Unlock()
109 | agent.OnClose()
110 | }
111 |
112 | // Start initializes and starts the WebSocket server.
113 | func (server *WSServer) Start() {
114 | ln, err := net.Listen("tcp", server.Addr)
115 | if err != nil {
116 | //log.Fatal("%v", err)
117 | log.Crit("net.listen err", "err", err)
118 | }
119 |
120 | if server.MaxConnNum <= 0 {
121 | server.MaxConnNum = 100
122 | //log.Release("invalid MaxConnNum, reset to %v", server.MaxConnNum)
123 | log.Info("invalid MaxConnNum,reset", "server.MaxConnNum", server.MaxConnNum)
124 | }
125 | if server.PendingWriteNum <= 0 {
126 | server.PendingWriteNum = 100
127 | //log.Release("invalid PendingWriteNum, reset to %v", server.PendingWriteNum)
128 | log.Info("invalid PendingWriteNum,reset", "server.PendingWriteNum", server.PendingWriteNum)
129 | }
130 | if server.MaxMsgLen <= 0 {
131 | server.MaxMsgLen = 4096
132 | //log.Release("invalid MaxMsgLen, reset to %v", server.MaxMsgLen)
133 | log.Info("invalid MaxMsgLen,reset", "server.MaxMsgLen", server.MaxMsgLen)
134 | }
135 | if server.HTTPTimeout <= 0 {
136 | server.HTTPTimeout = 10 * time.Second
137 | //log.Release("invalid HTTPTimeout, reset to %v", server.HTTPTimeout)
138 | log.Info("invalid HTTPTimeout,reset", "server.HTTPTimeout", server.HTTPTimeout)
139 | }
140 | if server.NewAgent == nil {
141 | //log.Fatal("NewAgent must not be nil")
142 | log.Crit("NewAgent must not be nil")
143 | }
144 |
145 | if server.CertFile != "" || server.KeyFile != "" {
146 | config := &tls.Config{}
147 | config.NextProtos = []string{"http/1.1"}
148 |
149 | var err error
150 | config.Certificates = make([]tls.Certificate, 1)
151 | config.Certificates[0], err = tls.LoadX509KeyPair(server.CertFile, server.KeyFile)
152 | if err != nil {
153 | //log.Fatal("%v", err)
154 | log.Crit("cerfiti file error", "err", err)
155 | }
156 |
157 | ln = tls.NewListener(ln, config)
158 | }
159 |
160 | server.ln = ln
161 | server.handler = &WSHandler{
162 | maxConnNum: server.MaxConnNum,
163 | pendingWriteNum: server.PendingWriteNum,
164 | maxMsgLen: server.MaxMsgLen,
165 | newAgent: server.NewAgent,
166 | conns: make(WebsocketConnSet),
167 | upgrader: websocket.Upgrader{
168 | HandshakeTimeout: server.HTTPTimeout,
169 | CheckOrigin: func(_ *http.Request) bool { return true },
170 | },
171 | }
172 |
173 | httpServer := &http.Server{
174 | Addr: server.Addr,
175 | Handler: server.handler,
176 | ReadTimeout: server.HTTPTimeout,
177 | WriteTimeout: server.HTTPTimeout,
178 | MaxHeaderBytes: 1024,
179 | }
180 |
181 | go httpServer.Serve(ln)
182 | }
183 |
184 | // Close shuts down the WebSocket server and closes all active connections.
185 | func (server *WSServer) Close() {
186 | server.ln.Close()
187 |
188 | server.handler.mutexConns.Lock()
189 | for conn := range server.handler.conns {
190 | conn.Close()
191 | }
192 | server.handler.conns = nil
193 | server.handler.mutexConns.Unlock()
194 |
195 | server.handler.wg.Wait()
196 | }
197 |
--------------------------------------------------------------------------------
/start-config.yml:
--------------------------------------------------------------------------------
1 | serviceBinaries:
2 | openim-sdk: 1
3 | toolBinaries:
4 | maxFileDescriptors: 10000
5 |
--------------------------------------------------------------------------------