mirror of
https://github.com/openimsdk/open-im-server.git
synced 2026-04-29 23:09:19 +08:00
Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 71f62080f3 | |||
| f6ab243d2f | |||
| 8788515576 | |||
| f52a4fe7e5 | |||
| c50b787c58 | |||
| 0274d516e0 | |||
| 6d1062bef8 | |||
| c69522b878 | |||
| 8f218057e4 | |||
| 4ed575a53c | |||
| 4cd42d7e19 | |||
| 7c25c91e9b | |||
| b67c6bacd0 | |||
| 1c2eafce25 | |||
| e795dce696 | |||
| 2d2fa99d2c | |||
| e86d1cd742 | |||
| 8501c66d14 | |||
| cd7f35452e | |||
| 5454c512e0 | |||
| 3a1615795e | |||
| c12f9dca7c | |||
| 572b5acc26 | |||
| 148a2493bb | |||
| 500ebc356d | |||
| fdaad704e0 | |||
| 8d21225107 | |||
| fabcb5317f | |||
| 2acdfde56e | |||
| 057884311a | |||
| b9cf40034c | |||
| 291443dd6b |
@@ -1,3 +1,17 @@
|
||||
# Copyright © 2024 OpenIM. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
directory: ./
|
||||
file_types:
|
||||
- .go
|
||||
|
||||
@@ -25,6 +25,7 @@ jobs:
|
||||
- name: Comment cherry-pick command
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.BOT_GITHUB_TOKEN }}
|
||||
script: |
|
||||
const pr = context.payload.pull_request;
|
||||
if (!pr.merged) {
|
||||
@@ -63,5 +64,4 @@ jobs:
|
||||
repo: context.repo.repo,
|
||||
issue_number: pr.number,
|
||||
body: cherryPickCmd
|
||||
});
|
||||
github-token: ${{ secrets.BOT_GITHUB_TOKEN }}
|
||||
});
|
||||
@@ -12,23 +12,57 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
name: Github Rebot for Cherry Pick On Comment
|
||||
name: Github Robot for Cherry Pick On Comment
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
cherry-pick:
|
||||
name: Cherry Pick
|
||||
# && github.event.comment.user.login=='kubbot'
|
||||
if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '/cherry-pick')
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout the latest code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.BOT_GITHUB_TOKEN }}
|
||||
fetch-depth: 0 # otherwise, you will fail to push refs to dest repo
|
||||
fetch-depth: 0 # To ensure all history is available for cherry-picking
|
||||
|
||||
- name: Automatic Cherry Pick
|
||||
uses: vendoo/gha-cherry-pick@v1
|
||||
with:
|
||||
# Assuming the cherry-pick commit SHA is passed in the comment like '/cherry-pick sha'
|
||||
commit-sha: ${{ github.event.comment.body }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }}
|
||||
|
||||
- name: Create a new branch for PR
|
||||
run: |
|
||||
PR_BRANCH="cherry-pick-${GITHUB_SHA}-to-${{ github.base_ref }}"
|
||||
git checkout -b $PR_BRANCH
|
||||
git push origin $PR_BRANCH
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }}
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: actions/github-script@v5
|
||||
with:
|
||||
script: |
|
||||
const prTitle = "Cherry-pick to ${{ github.base_ref }}"
|
||||
const prBody = "Automated cherry-pick of ${{ github.event.comment.body }}\n\n/cc @kubbot"
|
||||
const base = "${{ github.base_ref }}"
|
||||
const head = "cherry-pick-${{ github.sha }}-to-${{ github.base_ref }}"
|
||||
const createPr = await github.rest.pulls.create({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
title: prTitle,
|
||||
body: prBody,
|
||||
head: head,
|
||||
base: base,
|
||||
maintainer_can_modify: true, // Allows maintainers to edit the PR
|
||||
})
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }}
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
# Copyright © 2024 OpenIM. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
name: Language Check Workflow Test
|
||||
|
||||
on: [pull_request]
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
name: OpenIM Linux System E2E Test
|
||||
name: OpenIM E2E And API Test
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
@@ -82,7 +82,7 @@ jobs:
|
||||
sudo make tidy
|
||||
sudo make tools.verify.go-gitlint
|
||||
|
||||
- name: Build, Start
|
||||
- name: Build, Start(make build && make start)
|
||||
run: |
|
||||
sudo ./scripts/install/install.sh -i
|
||||
|
||||
@@ -90,9 +90,8 @@ jobs:
|
||||
run: |
|
||||
sudo ./scripts/install/install.sh -s
|
||||
|
||||
- name: Exec OpenIM API test
|
||||
- name: Exec OpenIM API test (make test-api)
|
||||
run: |
|
||||
sudo make test-api
|
||||
mkdir -p ./tmp
|
||||
touch ./tmp/test.md
|
||||
echo "# OpenIM Test" >> ./tmp/test.md
|
||||
@@ -103,9 +102,10 @@ jobs:
|
||||
echo "</code></pre>" >> ./tmp/test.md
|
||||
echo "</details>" >> ./tmp/test.md
|
||||
|
||||
- name: Exec OpenIM E2E Test
|
||||
sudo make test-api
|
||||
|
||||
- name: Exec OpenIM E2E Test (make test-e2e)
|
||||
run: |
|
||||
sudo make test-e2e
|
||||
echo "" >> ./tmp/test.md
|
||||
echo "## OpenIM E2E Test" >> ./tmp/test.md
|
||||
echo "<details><summary>Command Output for OpenIM E2E Test</summary>" >> ./tmp/test.md
|
||||
@@ -114,6 +114,8 @@ jobs:
|
||||
echo "</code></pre>" >> ./tmp/test.md
|
||||
echo "</details>" >> ./tmp/test.md
|
||||
|
||||
sudo make test-e2e
|
||||
|
||||
- name: Comment PR with file
|
||||
uses: thollander/actions-comment-pull-request@v2
|
||||
with:
|
||||
@@ -143,4 +145,4 @@ jobs:
|
||||
PUBLISH_BRANCH: gh-pages
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }}
|
||||
continue-on-error: true
|
||||
continue-on-error: true
|
||||
|
||||
@@ -41,6 +41,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/github-script@v7 # v6
|
||||
with:
|
||||
github-token: ${{ secrets.BOT_GITHUB_TOKEN }}
|
||||
script: |
|
||||
if (!context.payload.pull_request.merged) {
|
||||
console.log('PR was not merged, skipping.');
|
||||
@@ -56,9 +57,10 @@ jobs:
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
state: 'open',
|
||||
sort: 'due_on',
|
||||
direction: 'asc'
|
||||
sort: 'title',
|
||||
direction: 'desc'
|
||||
})
|
||||
|
||||
if (milestones.data.length === 0) {
|
||||
console.log('There are no milestones, skipping.');
|
||||
return;
|
||||
|
||||
@@ -57,7 +57,7 @@ jobs:
|
||||
steps:
|
||||
- name: Setup
|
||||
uses: actions/checkout@v4
|
||||
|
||||
|
||||
- name: Set up Go ${{ matrix.go_version }}
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
@@ -70,6 +70,9 @@ jobs:
|
||||
version: '3.x' # If available, use the latest major version that's compatible
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Code Typecheck Detector
|
||||
uses: kubecub/typecheck@main
|
||||
|
||||
- name: Module Operations
|
||||
run: |
|
||||
sudo make tidy
|
||||
|
||||
+1
-1
@@ -745,7 +745,7 @@ linters:
|
||||
- misspell # Spelling mistakes
|
||||
- staticcheck # Static analysis
|
||||
- unused # Checks for unused code
|
||||
- goimports # Checks if imports are correctly sorted and formatted
|
||||
# - goimports # Checks if imports are correctly sorted and formatted
|
||||
- godot # Checks for comment punctuation
|
||||
- bodyclose # Ensures HTTP response body is closed
|
||||
- stylecheck # Style checker for Go code
|
||||
|
||||
@@ -11,6 +11,7 @@ ENV GOPROXY=$GOPROXY
|
||||
# Set up the working directory
|
||||
WORKDIR /openim/openim-server
|
||||
|
||||
|
||||
# Copy all files to the container
|
||||
ADD . .
|
||||
|
||||
|
||||
@@ -186,23 +186,6 @@ services:
|
||||
# server:
|
||||
# ipv4_address: ${OPENIM_SERVER_NETWORK_ADDRESS:-172.28.0.8}
|
||||
|
||||
### TODO: mysql is required to deploy the openim-chat component
|
||||
# mysql:
|
||||
# image: mysql:${MYSQL_IMAGE_VERSION:-5.7}
|
||||
# platform: linux/amd64
|
||||
# ports:
|
||||
# - "${MYSQL_PORT:-13306}:3306"
|
||||
# container_name: mysql
|
||||
# volumes:
|
||||
# - "${DATA_DIR:-./}/components/mysql/data:/var/lib/mysql"
|
||||
# - "/etc/localtime:/etc/localtime"
|
||||
# environment:
|
||||
# MYSQL_ROOT_PASSWORD: "${MYSQL_PASSWORD:-openIM123}"
|
||||
# restart: always
|
||||
# networks:
|
||||
# server:
|
||||
# ipv4_address: ${MYSQL_NETWORK_ADDRESS:-172.28.0.15}
|
||||
|
||||
# openim-chat:
|
||||
# image: ${IMAGE_REGISTRY:-ghcr.io/openimsdk}/openim-chat:${CHAT_IMAGE_VERSION:-main}
|
||||
# container_name: openim-chat
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
**PATH:** `scripts/lib/logging.sh`
|
||||
|
||||
|
||||
|
||||
### Introduction
|
||||
|
||||
OpenIM, an intricate project, requires a robust logging mechanism to diagnose issues, maintain system health, and provide insights. A custom-built logging system embedded within OpenIM ensures consistent and structured logs. Let's delve into the design of this logging system and understand its various functions and their usage scenarios.
|
||||
|
||||
+276
-10
@@ -266,13 +266,61 @@ errors.New("redis connection failed")
|
||||
- When specifying ranges of numbers, use inclusive ranges whenever possible.
|
||||
- Go 1.13 or above is recommended, and the error generation method is `fmt.Errorf("module xxx: %w", err)`.
|
||||
|
||||
### 1.4 panic processing
|
||||
### 1.4 Panic Processing
|
||||
|
||||
The use of `panic` should be carefully controlled in Go applications to ensure program stability and predictable error handling. Following are revised guidelines emphasizing the restriction on using `panic` and promoting alternative strategies for error handling and program termination.
|
||||
|
||||
- **Prohibited in Business Logic:** Using `panic` within business logic processing is strictly prohibited. Business logic should handle errors gracefully and use error returns to propagate issues up the call stack.
|
||||
|
||||
- **Restricted Use in Main Package:** In the main package, the use of `panic` should be reserved for situations where the program is entirely inoperable, such as failure to open essential files, inability to connect to the database, or other critical startup issues. Even in these scenarios, prefer using structured error handling to terminate the program.
|
||||
|
||||
- **Use `log.Fatal` for Critical Errors:** Instead of panicking, use `log.Fatal` to log critical errors that prevent the program from operating normally. This approach allows the program to terminate while ensuring the error is properly logged for troubleshooting.
|
||||
|
||||
- **Prohibition on Exportable Interfaces:** Exportable interfaces must not invoke `panic`. They should handle errors gracefully and return errors as part of their contract.
|
||||
|
||||
- **Prefer Errors Over Panic:** It is recommended to use error returns instead of panic to convey errors within a package. This approach promotes error handling that integrates smoothly with Go's error handling idioms.
|
||||
|
||||
#### Alternative to Panic: Structured Program Termination
|
||||
|
||||
To enforce these guidelines, consider implementing structured functions to terminate the program gracefully in the face of unrecoverable errors, while providing clear error messages. Here are two recommended functions:
|
||||
|
||||
```go
|
||||
// ExitWithError logs an error message and exits the program with a non-zero status.
|
||||
func ExitWithError(err error) {
|
||||
progName := filepath.Base(os.Args[0])
|
||||
fmt.Fprintf(os.Stderr, "%s exit -1: %+v\n", progName, err)
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
// SIGTERMExit logs a warning message when the program receives a SIGTERM signal and exits with status 0.
|
||||
func SIGTERMExit() {
|
||||
progName := filepath.Base(os.Args[0])
|
||||
fmt.Fprintf(os.Stderr, "Warning %s receive process terminal SIGTERM exit 0\n", progName)
|
||||
}
|
||||
```
|
||||
|
||||
#### Example Usage:
|
||||
|
||||
```go
|
||||
import (
|
||||
_ "net/http/pprof"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/cmd"
|
||||
util "github.com/openimsdk/open-im-server/v3/pkg/util/genutil"
|
||||
)
|
||||
|
||||
func main() {
|
||||
apiCmd := cmd.NewApiCmd()
|
||||
apiCmd.AddPortFlag()
|
||||
apiCmd.AddPrometheusPortFlag()
|
||||
if err := apiCmd.Execute(); err != nil {
|
||||
util.ExitWithError(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In this example, `ExitWithError` is used to terminate the program when an unrecoverable error occurs, providing a clear error message to stderr and exiting with a non-zero status. This approach ensures that critical errors are logged and the program exits in a controlled manner, facilitating troubleshooting and maintaining the stability of the application.
|
||||
|
||||
- Panic is prohibited in business logic processing.
|
||||
- In the main package, panic is only used when the program is completely inoperable, for example, the file cannot be opened, the database cannot be connected, and the program cannot run normally.
|
||||
- In the main package, use `log.Fatal` to record errors, so that the program can be terminated by the log, or the exception thrown by the panic can be recorded in the log file, which is convenient for troubleshooting.
|
||||
- An exportable interface must not panic.
|
||||
- It is recommended to use error instead of panic to convey errors in the package.
|
||||
|
||||
### 1.5 Unit Tests
|
||||
|
||||
@@ -439,10 +487,15 @@ var allowGitHook bool
|
||||
- Local variables should be as short as possible, for example, use buf to refer to buffer, and use i to refer to index.
|
||||
- The code automatically generated by the code generation tool can exclude this rule (such as the Id in `xxx.pb.go`)
|
||||
|
||||
### 2.7 Constant naming
|
||||
### 2.7 Constant Naming
|
||||
|
||||
- The constant name must follow the camel case, and the initial letter is uppercase or lowercase according to the access control decision.
|
||||
- If it is a constant of enumeration type, you need to create the corresponding type first:
|
||||
In Go, constants play a critical role in defining values that do not change throughout the execution of a program. Adhering to best practices in naming constants can significantly improve the readability and maintainability of your code. Here are some guidelines for constant naming:
|
||||
|
||||
- **Camel Case Naming:** The name of a constant must follow the camel case notation. The initial letter should be uppercase or lowercase based on the access control requirements. Uppercase indicates that the constant is exported (visible outside the package), while lowercase indicates package-private visibility (visible only within its own package).
|
||||
|
||||
- **Enumeration Type Constants:** For constants that represent a set of enumerated values, it's recommended to define a corresponding type first. This approach not only enhances type safety but also improves code readability by clearly indicating the purpose of the enumeration.
|
||||
|
||||
**Example:**
|
||||
|
||||
```go
|
||||
// Code defines an error code type.
|
||||
@@ -452,11 +505,40 @@ type Code int
|
||||
const (
|
||||
// ErrUnknown - 0: An unknown error occurred.
|
||||
ErrUnknown Code = iota
|
||||
// ErrFatal - 1: An fatal error occurred.
|
||||
// ErrFatal - 1: A fatal error occurred.
|
||||
ErrFatal
|
||||
)
|
||||
```
|
||||
|
||||
In the example above, `Code` is defined as a new type based on `int`. The enumerated constants `ErrUnknown` and `ErrFatal` are then defined with explicit comments to indicate their purpose and values. This pattern is particularly useful for grouping related constants and providing additional context.
|
||||
|
||||
### Global Variables and Constants Across Packages
|
||||
|
||||
- **Use Constants for Global Variables:** When defining variables that are intended to be accessed across packages, prefer using constants to ensure immutability. This practice avoids unintended modifications to the value, which can lead to unpredictable behavior or hard-to-track bugs.
|
||||
|
||||
- **Lowercase for Package-Private Usage:** If a global variable or constant is intended for use only within its own package, it should start with a lowercase letter. This clearly signals its limited scope of visibility, adhering to Go's access control mechanism based on naming conventions.
|
||||
|
||||
**Guideline:**
|
||||
|
||||
- For global constants that need to be accessed across packages, declare them with an uppercase initial letter. This makes them exported, adhering to Go's visibility rules.
|
||||
- For constants used within the same package, start their names with a lowercase letter to limit their scope to the package.
|
||||
|
||||
**Example:**
|
||||
|
||||
```go
|
||||
package config
|
||||
|
||||
// MaxConnections - the maximum number of allowed connections. Visible across packages.
|
||||
const MaxConnections int = 100
|
||||
|
||||
// minIdleTime - the minimum idle time before a connection is considered stale. Only visible within the config package.
|
||||
const minIdleTime int = 30
|
||||
```
|
||||
|
||||
In this example, `MaxConnections` is a global constant meant to be accessed across packages, hence it starts with an uppercase letter. On the other hand, `minIdleTime` is intended for use only within the `config` package, so it starts with a lowercase letter.
|
||||
|
||||
Following these guidelines ensures that your Go code is more readable, maintainable, and consistent with Go's design philosophy and access control mechanisms.
|
||||
|
||||
### 2.8 Error naming
|
||||
|
||||
- The Error type should be written in the form of FooError.
|
||||
@@ -473,6 +555,190 @@ type ExitError struct {
|
||||
var ErrFormat = errors. New("unknown format")
|
||||
```
|
||||
|
||||
For non-standard Err naming, CICD will report an error
|
||||
|
||||
|
||||
### 2.9 Handling Errors Properly
|
||||
|
||||
In Go, proper error handling is crucial for creating reliable and maintainable applications. It's important to ensure that errors are not ignored or discarded, as this can lead to unpredictable behavior and difficult-to-debug issues. Here are the guidelines and examples regarding the proper handling of errors.
|
||||
|
||||
#### Guideline: Do Not Discard Errors
|
||||
|
||||
- **Mandatory Error Propagation:** When calling a function that returns an error, the calling function must handle or propagate the error, instead of ignoring it. This approach ensures that errors are not silently ignored, allowing higher-level logic to make informed decisions about error handling.
|
||||
|
||||
#### Incorrect Example: Discarding an Error
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
)
|
||||
|
||||
func ReadFileContent(filename string) string {
|
||||
content, _ := ioutil.ReadFile(filename) // Incorrect: Error is ignored
|
||||
return string(content)
|
||||
}
|
||||
|
||||
func main() {
|
||||
content := ReadFileContent("example.txt")
|
||||
log.Println(content)
|
||||
}
|
||||
```
|
||||
|
||||
In this incorrect example, the error returned by `ioutil.ReadFile` is ignored. This can lead to situations where the program continues execution even if the file doesn't exist or cannot be accessed, potentially causing more cryptic errors downstream.
|
||||
|
||||
#### Correct Example: Propagating an Error
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
)
|
||||
|
||||
// ReadFileContent attempts to read and return the content of the specified file.
|
||||
// It returns an error if reading fails.
|
||||
func ReadFileContent(filename string) (string, error) {
|
||||
content, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
// Correct: Propagate the error
|
||||
return "", err
|
||||
}
|
||||
return string(content), nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
content, err := ReadFileContent("example.txt")
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to read file: %v", err)
|
||||
}
|
||||
log.Println(content)
|
||||
}
|
||||
```
|
||||
|
||||
In the correct example, the error returned by `ioutil.ReadFile` is propagated back to the caller. The `main` function then checks the error and terminates the program with an appropriate error message if an error occurred. This approach ensures that errors are handled appropriately, and the program does not proceed with invalid state.
|
||||
|
||||
### Best Practices for Error Handling
|
||||
|
||||
1. **Always check the error returned by a function.** Do not ignore it.
|
||||
2. **Propagate errors up the call stack unless they can be handled gracefully at the current level.**
|
||||
3. **Provide context for errors when propagating them, making it easier to trace the source of the error.** This can be achieved using `fmt.Errorf` with the `%w` verb or dedicated wrapping functions provided by some error handling packages.
|
||||
4. **Log the error at the point where it is handled or makes the program to terminate, to provide insight into the failure.**
|
||||
|
||||
By following these guidelines, you ensure that your Go applications handle errors in a consistent and effective manner, improving their reliability and maintainability.
|
||||
|
||||
|
||||
### 2.10 Using Context with IO or Inter-Process Communication (IPC)
|
||||
|
||||
In Go, `context.Context` is a powerful construct for managing deadlines, cancellation signals, and other request-scoped values across API boundaries and between processes. It is particularly important in I/O operations or inter-process communication (IPC), where operations might need to be cancelled or timed out.
|
||||
|
||||
#### Guideline: Use Context for IO and IPC
|
||||
|
||||
- **Mandatory Use of Context:** When performing I/O operations or inter-process communication, it's crucial to use `context.Context` to manage the lifecycle of these operations. This includes setting deadlines, handling cancellation signals, and passing request-scoped values.
|
||||
|
||||
#### Incorrect Example: Ignoring Context in an HTTP Call
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"log"
|
||||
)
|
||||
|
||||
// FetchData makes an HTTP GET request to the specified URL and returns the response body.
|
||||
// This function does not use context, making it impossible to cancel the request or set a deadline.
|
||||
func FetchData(url string) (string, error) {
|
||||
resp, err := http.Get(url) // Incorrect: Ignoring context
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(body), nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
data, err := FetchData("http://example.com")
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to fetch data: %v", err)
|
||||
}
|
||||
log.Println(data)
|
||||
}
|
||||
```
|
||||
|
||||
In this incorrect example, the `FetchData` function makes an HTTP GET request without using a `context`. This approach does not allow the request to be cancelled or a timeout to be set, potentially leading to resources being wasted if the server takes too long to respond or if the operation needs to be aborted for any reason.
|
||||
|
||||
#### Correct Example: Using Context in an HTTP Call
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
|
||||
// FetchDataWithContext makes an HTTP GET request to the specified URL using the provided context.
|
||||
// This allows the request to be cancelled or timed out according to the context's deadline.
|
||||
func FetchDataWithContext(ctx context.Context, url string) (string, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(body), nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Create a context with a 5-second timeout
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
data, err := FetchDataWithContext(ctx, "http://example.com")
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to fetch data: %v", err)
|
||||
}
|
||||
log.Println(data)
|
||||
}
|
||||
```
|
||||
|
||||
In the correct example, `FetchDataWithContext` uses a context to make the HTTP GET request. This allows the operation to be cancelled or subjected to a timeout, as dictated by the context passed to it. The `context.WithTimeout` function is used in `main` to create a context that cancels the request if it takes longer than 5 seconds, demonstrating a practical use of context to manage operation lifecycle.
|
||||
|
||||
### Best Practices for Using Context
|
||||
|
||||
1. **Pass context as the first parameter of a function**, following the convention `func(ctx context.Context, ...)`.
|
||||
2. **Never ignore the context** provided to you in functions that support it. Always use it in your I/O or IPC operations.
|
||||
3. **Avoid storing context in a struct**. Contexts are meant to be passed around within the call stack, not stored.
|
||||
4. **Use context's cancellation and deadline features** to control the lifecycle of blocking operations, especially in network I/O and IPC scenarios.
|
||||
5. **Propagate context down the call stack** to any function that supports it, ensuring that your application can respond to cancellation signals and deadlines effectively.
|
||||
|
||||
By adhering to these guidelines and examples, you can ensure that your Go applications handle I/O and IPC operations more reliably and efficiently, with proper support for cancellation, timeouts, and request-scoped values.
|
||||
|
||||
|
||||
## 3. Comment specification
|
||||
|
||||
- Each exportable name must have a comment, which briefly introduces the exported variables, functions, structures, interfaces, etc.
|
||||
|
||||
+384
-18
@@ -1,20 +1,386 @@
|
||||
## Log Standards
|
||||
# OpenIM Logging and Error Handling Documentation
|
||||
|
||||
### Log Standards
|
||||
## Script Logging Documentation Link
|
||||
|
||||
- The unified log package `github.com/openimsdk/open-im-server/internal/pkg/log` should be used for all logging;
|
||||
- Use structured logging formats: `log.Infow`, `log.Warnw`, `log.Errorw`, etc. For example: `log.Infow("Update post function called")`;
|
||||
- All logs should start with an uppercase letter and should not end with a `.`. For example: `log.Infow("Update post function called")`;
|
||||
- Use past tense. For example, use `Could not delete B` instead of `Cannot delete B`;
|
||||
- Adhere to log level standards:
|
||||
- Debug level logs use `log.Debugw`;
|
||||
- Info level logs use `log.Infow`;
|
||||
- Warning level logs use `log.Warnw`;
|
||||
- Error level logs use `log.Errorw`;
|
||||
- Panic level logs use `log.Panicw`;
|
||||
- Fatal level logs use `log.Fatalw`.
|
||||
- Log settings:
|
||||
- Development and test environments: The log level is set to `debug`, the log format can be set to `console` / `json` as needed, and caller is enabled;
|
||||
- Production environment: The log level is set to `info`, the log format is set to `json`, and caller is enabled. (**Note**: In the early stages of going online, to facilitate troubleshooting, the log level can be set to `debug`)
|
||||
- When logging, avoid outputting sensitive information, such as passwords, keys, etc.
|
||||
- If you are calling a logging function in a function/method with a `context.Context` parameter, it is recommended to use `log.L(ctx).Infow()` for logging.
|
||||
If you wish to view the script's logging documentation, you can click on this link: [Logging Documentation](https://github.com/OpenIMSDK/Open-IM-Server/blob/main/docs/contrib/bash-log.md).
|
||||
|
||||
Below is the documentation for logging and error handling in the OpenIM Go project.
|
||||
|
||||
To create a standard set of documentation that is quick to read and easy to understand, we will highlight key information about the `Logger` interface and the `CodeError` interface. This includes the purpose of each interface, key methods, and their use cases. This will help developers quickly grasp how to effectively use logging and error handling within the project.
|
||||
|
||||
## Logging (`Logger` Interface)
|
||||
|
||||
### Purpose
|
||||
The `Logger` interface aims to provide the OpenIM project with a unified and flexible logging mechanism, supporting structured logging formats for efficient log management and analysis.
|
||||
|
||||
### Key Methods
|
||||
|
||||
- **Debug, Info, Warn, Error**
|
||||
Log messages of different levels to suit various logging needs and scenarios. These methods accept a context (`context.Context`), a message (`string`), and key-value pairs (`...interface{}`), allowing the log to carry rich context information.
|
||||
|
||||
- **WithValues**
|
||||
Append key-value pair information to log messages, returning a new `Logger` instance. This helps in adding consistent context information.
|
||||
|
||||
- **WithName**
|
||||
Set the name of the logger, which helps in identifying the source of the logs.
|
||||
|
||||
- **WithCallDepth**
|
||||
Adjust the call stack depth to accurately identify the source of the log message.
|
||||
|
||||
### Use Cases
|
||||
|
||||
- Developers should choose the appropriate logging level (such as `Debug`, `Info`, `Warn`, `Error`) based on the importance of the information when logging.
|
||||
- Use `WithValues` and `WithName` to add richer context information to logs, facilitating subsequent tracking and analysis.
|
||||
|
||||
## Error Handling (`CodeError` Interface)
|
||||
|
||||
### Purpose
|
||||
The `CodeError` interface is designed to provide a unified mechanism for error handling and wrapping, making error information more detailed and manageable.
|
||||
|
||||
### Key Methods
|
||||
|
||||
- **Code**
|
||||
Return the error code to distinguish between different types of errors.
|
||||
|
||||
- **Msg**
|
||||
Return the error message description to display to the user.
|
||||
|
||||
- **Detail**
|
||||
Return detailed information about the error for further debugging by developers.
|
||||
|
||||
- **WithDetail**
|
||||
Add detailed information to the error, returning a new `CodeError` instance.
|
||||
|
||||
- **Is**
|
||||
Determine whether the current error matches a specified error, supporting a flexible error comparison mechanism.
|
||||
|
||||
- **Wrap**
|
||||
Wrap another error with additional message description, facilitating the tracing of the error's cause.
|
||||
|
||||
### Use Cases
|
||||
|
||||
- When defining errors with specific codes and messages, use error types that implement the `CodeError` interface.
|
||||
- Use `WithDetail` to add additional context information to errors for more accurate problem localization.
|
||||
- Use the `Is` method to judge the type of error for conditional branching.
|
||||
- Use the `Wrap` method to wrap underlying errors while adding more contextual descriptions.
|
||||
|
||||
## Logging Standards and Code Examples
|
||||
|
||||
In the OpenIM project, we use the unified logging package `github.com/OpenIMSDK/tools/log` for logging to achieve efficient log management and analysis. This logging package supports structured logging formats, making it easier for developers to handle log information.
|
||||
|
||||
### Logger Interface and Implementation
|
||||
|
||||
The logger interface is defined as follows:
|
||||
|
||||
```go
|
||||
type Logger interface {
|
||||
Debug(ctx context.Context, msg string, keysAndValues ...interface{})
|
||||
Info(ctx context.Context, msg string, keysAndValues ...interface{})
|
||||
Warn(ctx context.Context, msg string, err error, keysAndValues ...interface{})
|
||||
Error(ctx context.Context, msg string, err error, keysAndValues ...interface{})
|
||||
WithValues(keysAndValues ...interface{}) Logger
|
||||
WithName(name string) Logger
|
||||
WithCallDepth(depth int) Logger
|
||||
}
|
||||
```
|
||||
|
||||
Example code: Using the `Logger` interface to log at the info level.
|
||||
|
||||
```go
|
||||
func main() {
|
||||
logger := log.NewLogger().WithName("MyService")
|
||||
ctx := context.Background()
|
||||
logger.Info(ctx, "Service started", "port", "8080")
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling and Code Examples
|
||||
|
||||
We use the `github.com/OpenIMSDK/tools/errs` package for unified error handling and wrapping.
|
||||
|
||||
### CodeError Interface and Implementation
|
||||
|
||||
The error interface is defined as follows:
|
||||
|
||||
```go
|
||||
type CodeError interface {
|
||||
Code() int
|
||||
Msg() string
|
||||
Detail() string
|
||||
WithDetail(detail string) CodeError
|
||||
Is(err error, loose ...bool) bool
|
||||
Wrap(msg ...string) error
|
||||
error
|
||||
}
|
||||
```
|
||||
|
||||
Example code: Creating and using the `CodeError` interface to handle errors.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/OpenIMSDK/tools/errs"
|
||||
)
|
||||
|
||||
func main() {
|
||||
err := errs.New(404, "Resource not found")
|
||||
err = err.WithDetail("
|
||||
|
||||
More details")
|
||||
if e, ok := err.(errs.CodeError); ok {
|
||||
fmt.Println(e.Code(), e.Msg(), e.Detail())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Detailed Logging Standards and Code Examples
|
||||
|
||||
1. **Print key information at startup**
|
||||
It is crucial to print entry parameters and key process information at program startup. This helps understand the startup state and configuration of the program.
|
||||
|
||||
**Code Example**:
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println("Program startup, version: 1.0.0")
|
||||
fmt.Printf("Connecting to database: %s\n", os.Getenv("DATABASE_URL"))
|
||||
}
|
||||
```
|
||||
|
||||
2. **Use `tools/log` and `fmt` for logging**
|
||||
Logging should be done using a specialized logging library for unified management and formatted log output.
|
||||
|
||||
**Code Example**: Logging an info level message with `tools/log`.
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/OpenIMSDK/tools/log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ctx := context.Background()
|
||||
log.Info(ctx, "Application started successfully")
|
||||
}
|
||||
```
|
||||
|
||||
3. **Use standard error output for startup failures or critical information**
|
||||
Critical error messages or program startup failures should be indicated to the user through standard error output.
|
||||
|
||||
**Code Example**:
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func checkEnvironment() bool {
|
||||
return os.Getenv("REQUIRED_ENV") != ""
|
||||
}
|
||||
|
||||
func main() {
|
||||
if !checkEnvironment() {
|
||||
fmt.Fprintln(os.Stderr, "Missing required environment variable")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We encapsulate it into separate tools, which can output error information through the `tools/log` package.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
util "github.com/openimsdk/open-im-server/v3/pkg/util/genutil"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if err := apiCmd.Execute(); err != nil {
|
||||
util.ExitWithError(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
4. **Use `tools/log` package for runtime logging**
|
||||
This ensures consistency and control over logging.
|
||||
|
||||
**Code Example**: Same as the above example using `tools/log`. When `tools/log` is not initialized, consider using `fmt` for standard output.
|
||||
|
||||
5. **Error logs should be printed by the top-level caller**
|
||||
This is to avoid duplicate logging of errors, typically errors are caught and logged at the application's outermost level.
|
||||
|
||||
**Code Example**:
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/OpenIMSDK/tools/log"
|
||||
"context"
|
||||
)
|
||||
|
||||
func doSomething() error {
|
||||
// An error occurs here
|
||||
return errs.Wrap(errors.New("An error occurred"))
|
||||
}
|
||||
|
||||
func controller() error {
|
||||
err := doSomething()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
err := controller()
|
||||
if err != nil {
|
||||
log.Error(context.Background(), "Operation failed", err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
6. **Handling logs for API RPC calls and non-RPC applications**
|
||||
|
||||
For API RPC calls using gRPC, logs at the information level are printed by middleware on the gRPC server side, reducing the need to manually log in each RPC method. For non-RPC applications, it's recommended to manually log key execution paths to track the application's execution flow.
|
||||
|
||||
**gRPC Server-Side Logging Middleware:**
|
||||
|
||||
In gRPC, `UnaryInterceptor` and `StreamInterceptor` can intercept Unary and Stream type RPC calls, respectively. Here's an example of how to implement a simple Unary RPC logging middleware:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
|
||||
// unaryServerInterceptor returns a new unary server interceptor that logs each request.
|
||||
func unaryServerInterceptor() grpc.UnaryServerInterceptor {
|
||||
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
|
||||
// Record the start time of the request
|
||||
start := time.Now()
|
||||
// Call the actual RPC method
|
||||
resp, err = handler(ctx, req)
|
||||
// After the request ends, log the duration and other information
|
||||
log.Printf("Request method: %s, duration: %s, error status: %v", info.FullMethod, time.Since(start), status.Code(err))
|
||||
return resp, err
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Create a gRPC server and add the middleware
|
||||
s := grpc.NewServer
|
||||
|
||||
(grpc.UnaryInterceptor(unaryServerInterceptor()))
|
||||
// Register your service
|
||||
|
||||
// Start the gRPC server
|
||||
log.Println("Starting gRPC server...")
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
**Logging for Non-RPC Applications:**
|
||||
|
||||
For non-RPC applications, the key is to log at appropriate places in the code to maintain an execution trace. Here's a simple example showing how to log when handling a task:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
)
|
||||
|
||||
func processTask(taskID string) {
|
||||
// Log the start of task processing
|
||||
log.Printf("Starting task processing: %s", taskID)
|
||||
// Suppose this is where the task is processed
|
||||
|
||||
// Log after the task is completed
|
||||
log.Printf("Task processing completed: %s", taskID)
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Example task ID
|
||||
taskID := "task123"
|
||||
processTask(taskID)
|
||||
}
|
||||
```
|
||||
|
||||
In both scenarios, appropriate logging can help developers and operators monitor the health of the system, trace the source of issues, and quickly locate and resolve problems. For gRPC logging, using middleware can effectively centralize log management and control. For non-RPC applications, ensuring logs are placed at critical execution points can help understand the program's operational flow and state changes.
|
||||
|
||||
### When to Wrap Errors?
|
||||
|
||||
1. **Wrap errors generated within the function**
|
||||
When an error occurs within a function, use `errs.Wrap` to add context information to the original error.
|
||||
|
||||
**Code Example**:
|
||||
```go
|
||||
func doSomething() error {
|
||||
// Suppose an error occurs here
|
||||
err, _ := someFunc()
|
||||
if err != nil {
|
||||
return errs.Wrap(err, "doSomething failed")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. **Wrap errors from system calls or other packages**
|
||||
When calling external libraries or system functions that return errors, also add context information to wrap the error.
|
||||
|
||||
**Code Example**:
|
||||
```go
|
||||
func readConfig(file string) error {
|
||||
_, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
return errs.Wrap(err, "Failed to read config file")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
3. **No need to re-wrap errors for internal module calls**
|
||||
|
||||
If an error has been appropriately wrapped with sufficient context information in an internal module call, there's no need to wrap it again.
|
||||
|
||||
**Code Example**:
|
||||
```go
|
||||
func doSomething() error {
|
||||
err := doAnotherThing()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
4. **Ensure comprehensive wrapping of errors with detailed messages**
|
||||
When wrapping errors, ensure to provide ample context information to make the error more understandable and easier to debug.
|
||||
|
||||
**Code Example**:
|
||||
```go
|
||||
func connectDatabase() error {
|
||||
err := db.Connect(config.DatabaseURL)
|
||||
if err != nil {
|
||||
return errs.Wrap(err, fmt.Sprintf("Failed to connect to database, URL: %s", config.DatabaseURL))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
```
|
||||
@@ -96,7 +96,6 @@ We reinforce our approach to branch management and versioning with stringent tes
|
||||
|
||||
This document describes the maximum version skew supported between various openim components. Specific cluster deployment tools may place additional restrictions on version skew.
|
||||
|
||||
|
||||
### Supported version skew
|
||||
|
||||
In highly-available (HA) clusters, the newest and oldest `openim-api` instances must be within one minor version.
|
||||
@@ -210,6 +209,7 @@ git merge release-v3.1
|
||||
# Push the updates to the main branch
|
||||
git push origin main
|
||||
```
|
||||
|
||||
## Release Process
|
||||
|
||||
```
|
||||
@@ -232,5 +232,7 @@ For more details on managing Docker image versions, visit [OpenIM Docker Images
|
||||
|
||||
More on multi-branch version management design and version management design at helm charts:
|
||||
|
||||
+ https://github.com/openimsdk/open-im-server/issues/1695
|
||||
+ https://github.com/openimsdk/open-im-server/issues/1662
|
||||
About Helm's version management strategy for Multiple Apps and multiple Services:
|
||||
|
||||
+ [中文版本管理文档](https://github.com/openimsdk/helm-charts/blob/main/docs/contrib/version-zh.md)
|
||||
+ [English version management documents](https://github.com/openimsdk/helm-charts/blob/main/docs/contrib/version.md)
|
||||
|
||||
@@ -4,7 +4,7 @@ go 1.19
|
||||
|
||||
require (
|
||||
firebase.google.com/go v3.13.0+incompatible
|
||||
github.com/OpenIMSDK/protocol v0.0.55
|
||||
github.com/OpenIMSDK/protocol v0.0.56
|
||||
github.com/OpenIMSDK/tools v0.0.37
|
||||
github.com/bwmarrin/snowflake v0.3.0 // indirect
|
||||
github.com/dtm-labs/rockscache v0.1.1
|
||||
|
||||
@@ -18,8 +18,8 @@ firebase.google.com/go v3.13.0+incompatible/go.mod h1:xlah6XbEyW6tbfSklcfe5FHJIw
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/IBM/sarama v1.42.2 h1:VoY4hVIZ+WQJ8G9KNY/SQlWguBQXQ9uvFPOnrcu8hEw=
|
||||
github.com/IBM/sarama v1.42.2/go.mod h1:FLPGUGwYqEs62hq2bVG6Io2+5n+pS6s/WOXVKWSLFtE=
|
||||
github.com/OpenIMSDK/protocol v0.0.55 h1:eBjg8DyuhxGmuCUjpoZjg6MJJJXU/xJ3xJwFhrn34yA=
|
||||
github.com/OpenIMSDK/protocol v0.0.55/go.mod h1:F25dFrwrIx3lkNoiuf6FkCfxuwf8L4Z8UIsdTHP/r0Y=
|
||||
github.com/OpenIMSDK/protocol v0.0.56 h1:mbVFyDBachEsmJLfYW5AU1z2KL8AUEpoHG8RPCIxjgg=
|
||||
github.com/OpenIMSDK/protocol v0.0.56/go.mod h1:F25dFrwrIx3lkNoiuf6FkCfxuwf8L4Z8UIsdTHP/r0Y=
|
||||
github.com/OpenIMSDK/tools v0.0.37 h1:qvDqmA4RbEJtPjZouWCkVuf/pjm6Y8nUrG5iH2gcnOg=
|
||||
github.com/OpenIMSDK/tools v0.0.37/go.mod h1:wBfR5CYmEyvxl03QJbTkhz1CluK6J4/lX0lviu8JAjE=
|
||||
github.com/QcloudApi/qcloud_sign_golang v0.0.0-20141224014652-e4130a326409/go.mod h1:1pk82RBxDY/JZnPQrtqHlUFfCctgdorsd9M06fMynOM=
|
||||
|
||||
@@ -2,7 +2,6 @@ go 1.19
|
||||
|
||||
use (
|
||||
.
|
||||
./test/typecheck
|
||||
./tools/changelog
|
||||
./tools/component
|
||||
./tools/formitychecker
|
||||
|
||||
+2
-5
@@ -215,25 +215,22 @@ func (m *MessageApi) SendMessage(c *gin.Context) {
|
||||
// Set the receiver ID in the message data.
|
||||
sendMsgReq.MsgData.RecvID = req.RecvID
|
||||
|
||||
// Declare a variable to store the message sending status.
|
||||
var status int
|
||||
|
||||
// Attempt to send the message using the client.
|
||||
respPb, err := m.Client.SendMsg(c, sendMsgReq)
|
||||
if err != nil {
|
||||
// Set the status to failed and respond with an error if sending fails.
|
||||
status = constant.MsgSendFailed
|
||||
apiresp.GinError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Set the status to successful if the message is sent.
|
||||
status = constant.MsgSendSuccessed
|
||||
var status int = constant.MsgSendSuccessed
|
||||
|
||||
// Attempt to update the message sending status in the system.
|
||||
_, err = m.Client.SetSendMsgStatus(c, &msg.SetSendMsgStatusReq{
|
||||
Status: int32(status),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
// Log the error if updating the status fails.
|
||||
apiresp.GinError(c, err)
|
||||
|
||||
@@ -50,7 +50,6 @@ import (
|
||||
)
|
||||
|
||||
func Start(config *config.GlobalConfig, port int, proPort int) error {
|
||||
log.ZDebug(context.Background(), "configAPI1111111111111111111", config, "port", port, "javafdasfs")
|
||||
if port == 0 || proPort == 0 {
|
||||
err := "port or proPort is empty:" + strconv.Itoa(port) + "," + strconv.Itoa(proPort)
|
||||
return errs.Wrap(fmt.Errorf(err))
|
||||
|
||||
@@ -141,7 +141,6 @@ func (c *UserConnContext) GetBackground() bool {
|
||||
b, err := strconv.ParseBool(c.Req.URL.Query().Get(BackgroundStatus))
|
||||
if err != nil {
|
||||
return false
|
||||
} else {
|
||||
return b
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/OpenIMSDK/tools/log"
|
||||
"github.com/OpenIMSDK/tools/utils"
|
||||
)
|
||||
|
||||
type UserMap struct {
|
||||
@@ -71,48 +70,65 @@ func (u *UserMap) Set(key string, v *Client) {
|
||||
}
|
||||
|
||||
func (u *UserMap) delete(key string, connRemoteAddr string) (isDeleteUser bool) {
|
||||
// Attempt to load the clients associated with the key.
|
||||
allClients, existed := u.m.Load(key)
|
||||
if existed {
|
||||
oldClients := allClients.([]*Client)
|
||||
var a []*Client
|
||||
for _, client := range oldClients {
|
||||
if client.ctx.GetRemoteAddr() != connRemoteAddr {
|
||||
a = append(a, client)
|
||||
}
|
||||
}
|
||||
if len(a) == 0 {
|
||||
u.m.Delete(key)
|
||||
return true
|
||||
} else {
|
||||
u.m.Store(key, a)
|
||||
return false
|
||||
if !existed {
|
||||
// Return false immediately if the key does not exist.
|
||||
return false
|
||||
}
|
||||
|
||||
// Convert allClients to a slice of *Client.
|
||||
oldClients := allClients.([]*Client)
|
||||
var remainingClients []*Client
|
||||
for _, client := range oldClients {
|
||||
// Keep clients that do not match the connRemoteAddr.
|
||||
if client.ctx.GetRemoteAddr() != connRemoteAddr {
|
||||
remainingClients = append(remainingClients, client)
|
||||
}
|
||||
}
|
||||
return existed
|
||||
|
||||
// If no clients remain after filtering, delete the key from the map.
|
||||
if len(remainingClients) == 0 {
|
||||
u.m.Delete(key)
|
||||
return true
|
||||
}
|
||||
|
||||
// Otherwise, update the key with the remaining clients.
|
||||
u.m.Store(key, remainingClients)
|
||||
return false
|
||||
}
|
||||
|
||||
func (u *UserMap) deleteClients(key string, clients []*Client) (isDeleteUser bool) {
|
||||
m := utils.SliceToMapAny(clients, func(c *Client) (string, struct{}) {
|
||||
return c.ctx.GetRemoteAddr(), struct{}{}
|
||||
})
|
||||
func (u *UserMap) deleteClients(key string, clientsToDelete []*Client) (isDeleteUser bool) {
|
||||
// Convert the slice of clients to delete into a map for efficient lookup.
|
||||
deleteMap := make(map[string]struct{})
|
||||
for _, client := range clientsToDelete {
|
||||
deleteMap[client.ctx.GetRemoteAddr()] = struct{}{}
|
||||
}
|
||||
|
||||
// Load the current clients associated with the key.
|
||||
allClients, existed := u.m.Load(key)
|
||||
if existed {
|
||||
oldClients := allClients.([]*Client)
|
||||
var a []*Client
|
||||
for _, client := range oldClients {
|
||||
if _, ok := m[client.ctx.GetRemoteAddr()]; !ok {
|
||||
a = append(a, client)
|
||||
}
|
||||
}
|
||||
if len(a) == 0 {
|
||||
u.m.Delete(key)
|
||||
return true
|
||||
} else {
|
||||
u.m.Store(key, a)
|
||||
return false
|
||||
if !existed {
|
||||
// If the key doesn't exist, return false.
|
||||
return false
|
||||
}
|
||||
|
||||
// Filter out clients that are in the deleteMap.
|
||||
oldClients := allClients.([]*Client)
|
||||
var remainingClients []*Client
|
||||
for _, client := range oldClients {
|
||||
if _, shouldBeDeleted := deleteMap[client.ctx.GetRemoteAddr()]; !shouldBeDeleted {
|
||||
remainingClients = append(remainingClients, client)
|
||||
}
|
||||
}
|
||||
return existed
|
||||
|
||||
// Update or delete the key based on the remaining clients.
|
||||
if len(remainingClients) == 0 {
|
||||
u.m.Delete(key)
|
||||
return true
|
||||
}
|
||||
|
||||
u.m.Store(key, remainingClients)
|
||||
return false
|
||||
}
|
||||
|
||||
func (u *UserMap) DeleteAll(key string) {
|
||||
|
||||
@@ -184,12 +184,11 @@ func (och *OnlineHistoryRedisConsumerHandler) getPushStorageMsgList(
|
||||
options2 := msgprocessor.Options(msg.Options)
|
||||
if options2.IsHistory() {
|
||||
return true
|
||||
} else {
|
||||
// if !(!options2.IsSenderSync() && conversationID == msg.MsgData.SendID) {
|
||||
// return false
|
||||
// }
|
||||
return false
|
||||
}
|
||||
// if !(!options2.IsSenderSync() && conversationID == msg.MsgData.SendID) {
|
||||
// return false
|
||||
// }
|
||||
return false
|
||||
}
|
||||
for _, v := range totalMsgs {
|
||||
options := msgprocessor.Options(v.message.Options)
|
||||
|
||||
@@ -90,9 +90,8 @@ func (g *Client) Push(ctx context.Context, userIDs []string, title, content stri
|
||||
for i, v := range s.GetSplitResult() {
|
||||
go func(index int, userIDs []string) {
|
||||
defer wg.Done()
|
||||
if err := g.batchPush(ctx, token, userIDs, pushReq); err != nil {
|
||||
if err = g.batchPush(ctx, token, userIDs, pushReq); err != nil {
|
||||
log.ZError(ctx, "batchPush failed", err, "index", index, "token", token, "req", pushReq)
|
||||
err = err
|
||||
}
|
||||
}(i, v.Item)
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ package push
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/rpccache"
|
||||
|
||||
"github.com/OpenIMSDK/protocol/constant"
|
||||
pbpush "github.com/OpenIMSDK/protocol/push"
|
||||
@@ -26,6 +25,7 @@ import (
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/db/cache"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/db/controller"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/rpccache"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/rpcclient"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
@@ -90,9 +90,8 @@ func (r *pushServer) PushMsg(ctx context.Context, pbData *pbpush.PushMsgReq) (re
|
||||
if err != nil {
|
||||
if err != errNoOfflinePusher {
|
||||
return nil, err
|
||||
} else {
|
||||
log.ZWarn(ctx, "offline push failed", err, "msg", pbData.String())
|
||||
}
|
||||
log.ZWarn(ctx, "offline push failed", err, "msg", pbData.String())
|
||||
}
|
||||
return &pbpush.PushMsgResp{}, nil
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/rpccache"
|
||||
"sync"
|
||||
|
||||
"github.com/OpenIMSDK/protocol/constant"
|
||||
@@ -39,6 +38,7 @@ import (
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/db/controller"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/msgprocessor"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/rpccache"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/rpcclient"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
@@ -26,7 +26,6 @@ func GetContent(msg *sdkws.MsgData) string {
|
||||
_ = proto.Unmarshal(msg.Content, &tips)
|
||||
content := tips.JsonDetail
|
||||
return content
|
||||
} else {
|
||||
return string(msg.Content)
|
||||
}
|
||||
return string(msg.Content)
|
||||
}
|
||||
|
||||
@@ -536,7 +536,10 @@ func (c *conversationServer) getConversationInfo(
|
||||
return conversationMsg, nil
|
||||
}
|
||||
|
||||
func (c *conversationServer) GetConversationNotReceiveMessageUserIDs(ctx context.Context, req *pbconversation.GetConversationNotReceiveMessageUserIDsReq) (*pbconversation.GetConversationNotReceiveMessageUserIDsResp, error) {
|
||||
func (c *conversationServer) GetConversationNotReceiveMessageUserIDs(
|
||||
ctx context.Context,
|
||||
req *pbconversation.GetConversationNotReceiveMessageUserIDsReq,
|
||||
) (*pbconversation.GetConversationNotReceiveMessageUserIDsResp, error) {
|
||||
userIDs, err := c.conversationDatabase.GetConversationNotReceiveMessageUserIDs(ctx, req.ConversationID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -79,9 +79,14 @@ func (s *friendServer) AddBlack(ctx context.Context, req *pbfriend.AddBlackReq)
|
||||
CreateTime: time.Now(),
|
||||
Ex: req.Ex,
|
||||
}
|
||||
|
||||
if err := s.blackDatabase.Create(ctx, []*relation.BlackModel{&black}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.notificationSender.BlackAddedNotification(ctx, req)
|
||||
|
||||
if err := s.notificationSender.BlackAddedNotification(ctx, req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pbfriend.AddBlackResp{}, nil
|
||||
}
|
||||
|
||||
@@ -16,31 +16,26 @@ package friend
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/OpenIMSDK/tools/tx"
|
||||
|
||||
"github.com/OpenIMSDK/protocol/sdkws"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/authverify"
|
||||
|
||||
"github.com/OpenIMSDK/tools/log"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/convert"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/rpcclient"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"github.com/OpenIMSDK/protocol/constant"
|
||||
pbfriend "github.com/OpenIMSDK/protocol/friend"
|
||||
"github.com/OpenIMSDK/protocol/sdkws"
|
||||
registry "github.com/OpenIMSDK/tools/discoveryregistry"
|
||||
"github.com/OpenIMSDK/tools/errs"
|
||||
"github.com/OpenIMSDK/tools/log"
|
||||
"github.com/OpenIMSDK/tools/tx"
|
||||
"github.com/OpenIMSDK/tools/utils"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/authverify"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/convert"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/db/cache"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/db/controller"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/db/mgo"
|
||||
tablerelation "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/db/unrelation"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/rpcclient"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/rpcclient/notification"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
type friendServer struct {
|
||||
@@ -119,26 +114,36 @@ func (s *friendServer) ApplyToAddFriend(ctx context.Context, req *pbfriend.Apply
|
||||
if err := authverify.CheckAccessV3(ctx, req.FromUserID, s.config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if req.ToUserID == req.FromUserID {
|
||||
return nil, errs.ErrCanNotAddYourself.Wrap("req.ToUserID", req.ToUserID)
|
||||
}
|
||||
|
||||
if err = CallbackBeforeAddFriend(ctx, s.config, req); err != nil && err != errs.ErrCallbackContinue {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := s.userRpcClient.GetUsersInfoMap(ctx, []string{req.ToUserID, req.FromUserID}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
in1, in2, err := s.friendDatabase.CheckIn(ctx, req.FromUserID, req.ToUserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if in1 && in2 {
|
||||
return nil, errs.ErrRelationshipAlready.Wrap()
|
||||
}
|
||||
|
||||
if err = s.friendDatabase.AddFriendRequest(ctx, req.FromUserID, req.ToUserID, req.ReqMsg, req.Ex); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.notificationSender.FriendApplicationAddNotification(ctx, req)
|
||||
|
||||
if err = s.notificationSender.FriendApplicationAddNotification(ctx, req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = CallbackAfterAddFriend(ctx, s.config, req); err != nil && err != errs.ErrCallbackContinue {
|
||||
return nil, err
|
||||
}
|
||||
@@ -202,7 +207,9 @@ func (s *friendServer) RespondFriendApply(ctx context.Context, req *pbfriend.Res
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.notificationSender.FriendApplicationAgreedNotification(ctx, req)
|
||||
if err := s.notificationSender.FriendApplicationAgreedNotification(ctx, req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
if req.HandleResult == constant.FriendResponseRefuse {
|
||||
|
||||
@@ -33,10 +33,7 @@ func (s *groupServer) GetGroupInfoCache(
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (s *groupServer) GetGroupMemberCache(
|
||||
ctx context.Context,
|
||||
req *pbgroup.GetGroupMemberCacheReq,
|
||||
) (resp *pbgroup.GetGroupMemberCacheResp, err error) {
|
||||
func (s *groupServer) GetGroupMemberCache(ctx context.Context, req *pbgroup.GetGroupMemberCacheReq) (resp *pbgroup.GetGroupMemberCacheResp, err error) {
|
||||
members, err := s.db.TakeGroupMember(ctx, req.GroupID, req.GroupMemberID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -637,6 +637,7 @@ func (s *groupServer) GetGroupMembersInfo(ctx context.Context, req *pbgroup.GetG
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// GetGroupApplicationList handles functions that get a list of group requests.
|
||||
func (s *groupServer) GetGroupApplicationList(ctx context.Context, req *pbgroup.GetGroupApplicationListReq) (*pbgroup.GetGroupApplicationListResp, error) {
|
||||
groupIDs, err := s.db.FindUserManagedGroupID(ctx, req.FromUserID)
|
||||
if err != nil {
|
||||
@@ -951,6 +952,7 @@ func (s *groupServer) SetGroupInfo(ctx context.Context, req *pbgroup.SetGroupInf
|
||||
return nil, errs.Wrap(errs.ErrDismissedAlready)
|
||||
}
|
||||
resp := &pbgroup.SetGroupInfoResp{}
|
||||
|
||||
count, err := s.db.FindGroupMemberNum(ctx, group.GroupID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -1068,6 +1070,7 @@ func (s *groupServer) GetGroups(ctx context.Context, req *pbgroup.GetGroupsReq)
|
||||
group []*relationtb.GroupModel
|
||||
err error
|
||||
)
|
||||
|
||||
if req.GroupID != "" {
|
||||
group, err = s.db.FindGroup(ctx, []string{req.GroupID})
|
||||
resp.Total = uint32(len(group))
|
||||
@@ -1076,25 +1079,20 @@ func (s *groupServer) GetGroups(ctx context.Context, req *pbgroup.GetGroupsReq)
|
||||
total, group, err = s.db.SearchGroup(ctx, req.GroupName, req.Pagination)
|
||||
resp.Total = uint32(total)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var groups []*relationtb.GroupModel
|
||||
for _, v := range group {
|
||||
if v.Status == constant.GroupStatusDismissed {
|
||||
resp.Total--
|
||||
continue
|
||||
}
|
||||
groups = append(groups, v)
|
||||
}
|
||||
groupIDs := utils.Slice(groups, func(e *relationtb.GroupModel) string {
|
||||
groupIDs := utils.Slice(group, func(e *relationtb.GroupModel) string {
|
||||
return e.GroupID
|
||||
})
|
||||
|
||||
ownerMembers, err := s.db.FindGroupsOwner(ctx, groupIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ownerMemberMap := utils.SliceToMap(ownerMembers, func(e *relationtb.GroupMemberModel) string {
|
||||
return e.GroupID
|
||||
})
|
||||
@@ -1102,7 +1100,7 @@ func (s *groupServer) GetGroups(ctx context.Context, req *pbgroup.GetGroupsReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp.Groups = utils.Slice(groups, func(group *relationtb.GroupModel) *pbgroup.CMSGroup {
|
||||
resp.Groups = utils.Slice(group, func(group *relationtb.GroupModel) *pbgroup.CMSGroup {
|
||||
var (
|
||||
userID string
|
||||
username string
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
package msg
|
||||
|
||||
import (
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/rpccache"
|
||||
|
||||
"github.com/OpenIMSDK/protocol/constant"
|
||||
"github.com/OpenIMSDK/protocol/conversation"
|
||||
"github.com/OpenIMSDK/protocol/msg"
|
||||
@@ -25,6 +23,7 @@ import (
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/db/cache"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/db/controller"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/db/unrelation"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/rpccache"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/rpcclient"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
@@ -131,12 +131,13 @@ func (m *msgServer) SearchMessage(ctx context.Context, req *msg.SearchMessageReq
|
||||
sendIDs = append(sendIDs, chatLog.SendID)
|
||||
}
|
||||
switch chatLog.SessionType {
|
||||
case constant.SingleChatType:
|
||||
case constant.SingleChatType, constant.NotificationChatType:
|
||||
recvIDs = append(recvIDs, chatLog.RecvID)
|
||||
case constant.GroupChatType, constant.SuperGroupChatType:
|
||||
groupIDs = append(groupIDs, chatLog.GroupID)
|
||||
}
|
||||
}
|
||||
// Retrieve sender and receiver information
|
||||
if len(sendIDs) != 0 {
|
||||
sendInfos, err := m.UserLocalCache.GetUsersInfo(ctx, sendIDs)
|
||||
if err != nil {
|
||||
@@ -155,6 +156,8 @@ func (m *msgServer) SearchMessage(ctx context.Context, req *msg.SearchMessageReq
|
||||
recvMap[recvInfo.UserID] = recvInfo.Nickname
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve group information including member counts
|
||||
if len(groupIDs) != 0 {
|
||||
groupInfos, err := m.GroupLocalCache.GetGroupInfos(ctx, groupIDs)
|
||||
if err != nil {
|
||||
@@ -162,8 +165,14 @@ func (m *msgServer) SearchMessage(ctx context.Context, req *msg.SearchMessageReq
|
||||
}
|
||||
for _, groupInfo := range groupInfos {
|
||||
groupMap[groupInfo.GroupID] = groupInfo
|
||||
// Get actual member count
|
||||
memberIDs, err := m.GroupLocalCache.GetGroupMemberIDs(ctx, groupInfo.GroupID)
|
||||
if err == nil {
|
||||
groupInfo.MemberCount = uint32(len(memberIDs)) // Update the member count with actual number
|
||||
}
|
||||
}
|
||||
}
|
||||
// Construct response with updated information
|
||||
for _, chatLog := range chatLogs {
|
||||
pbchatLog := &msg.ChatLog{}
|
||||
utils.CopyStructFields(pbchatLog, chatLog)
|
||||
@@ -173,16 +182,16 @@ func (m *msgServer) SearchMessage(ctx context.Context, req *msg.SearchMessageReq
|
||||
pbchatLog.SenderNickname = sendMap[chatLog.SendID]
|
||||
}
|
||||
switch chatLog.SessionType {
|
||||
case constant.SingleChatType:
|
||||
case constant.SingleChatType, constant.NotificationChatType:
|
||||
pbchatLog.RecvNickname = recvMap[chatLog.RecvID]
|
||||
|
||||
case constant.GroupChatType, constant.SuperGroupChatType:
|
||||
pbchatLog.SenderFaceURL = groupMap[chatLog.GroupID].FaceURL
|
||||
pbchatLog.GroupMemberCount = groupMap[chatLog.GroupID].MemberCount
|
||||
pbchatLog.RecvID = groupMap[chatLog.GroupID].GroupID
|
||||
pbchatLog.GroupName = groupMap[chatLog.GroupID].GroupName
|
||||
pbchatLog.GroupOwner = groupMap[chatLog.GroupID].OwnerUserID
|
||||
pbchatLog.GroupType = groupMap[chatLog.GroupID].GroupType
|
||||
groupInfo := groupMap[chatLog.GroupID]
|
||||
pbchatLog.SenderFaceURL = groupInfo.FaceURL
|
||||
pbchatLog.GroupMemberCount = groupInfo.MemberCount // Reflects actual member count
|
||||
pbchatLog.RecvID = groupInfo.GroupID
|
||||
pbchatLog.GroupName = groupInfo.GroupName
|
||||
pbchatLog.GroupOwner = groupInfo.OwnerUserID
|
||||
pbchatLog.GroupType = groupInfo.GroupType
|
||||
}
|
||||
resp.ChatLogs = append(resp.ChatLogs, pbchatLog)
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ package msg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/OpenIMSDK/tools/log"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"time"
|
||||
@@ -25,6 +24,7 @@ import (
|
||||
"github.com/OpenIMSDK/protocol/msg"
|
||||
"github.com/OpenIMSDK/protocol/sdkws"
|
||||
"github.com/OpenIMSDK/tools/errs"
|
||||
"github.com/OpenIMSDK/tools/log"
|
||||
"github.com/OpenIMSDK/tools/utils"
|
||||
)
|
||||
|
||||
|
||||
@@ -133,6 +133,13 @@ func (t *thirdServer) SearchLogs(ctx context.Context, req *third.SearchLogsReq)
|
||||
if req.StartTime > req.EndTime {
|
||||
return nil, errs.ErrArgs.Wrap("startTime>endTime")
|
||||
}
|
||||
if req.StartTime == 0 && req.EndTime == 0 {
|
||||
t := time.Date(2019, time.January, 1, 0, 0, 0, 0, time.UTC)
|
||||
timestampMills := t.UnixNano() / int64(time.Millisecond)
|
||||
req.StartTime = timestampMills
|
||||
req.EndTime = time.Now().UnixNano() / int64(time.Millisecond)
|
||||
}
|
||||
|
||||
total, logs, err := t.thirdDatabase.SearchLogs(ctx, req.Keyword, time.UnixMilli(req.StartTime), time.UnixMilli(req.EndTime), req.Pagination)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -60,6 +60,7 @@ func CheckAdmin(ctx context.Context, config *config.GlobalConfig) error {
|
||||
}
|
||||
return errs.ErrNoPermission.Wrap(fmt.Sprintf("user %s is not admin userID", mcontext.GetOpUserID(ctx)))
|
||||
}
|
||||
|
||||
func CheckIMAdmin(ctx context.Context, config *config.GlobalConfig) error {
|
||||
if utils.IsContain(mcontext.GetOpUserID(ctx), config.IMAdmin.UserID) {
|
||||
return nil
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// Copyright © 2024 OpenIM. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cachekey
|
||||
|
||||
const (
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// Copyright © 2024 OpenIM. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cachekey
|
||||
|
||||
const (
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// Copyright © 2024 OpenIM. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cachekey
|
||||
|
||||
const (
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// Copyright © 2024 OpenIM. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cachekey
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// Copyright © 2024 OpenIM. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cachekey
|
||||
|
||||
const (
|
||||
|
||||
@@ -17,10 +17,11 @@ package config
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"gopkg.in/yaml.v3"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/msgprocessor"
|
||||
)
|
||||
|
||||
|
||||
Vendored
+3
-3
@@ -16,12 +16,12 @@ package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/OpenIMSDK/tools/log"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/cachekey"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
"time"
|
||||
|
||||
"github.com/OpenIMSDK/tools/log"
|
||||
"github.com/dtm-labs/rockscache"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/cachekey"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
relationtb "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
Vendored
+17
-2
@@ -1,10 +1,25 @@
|
||||
// Copyright © 2024 OpenIM. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cache
|
||||
|
||||
import (
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/cachekey"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/cachekey"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
Vendored
+4
-4
@@ -16,15 +16,15 @@ package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/OpenIMSDK/tools/log"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/cachekey"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
"math/big"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/OpenIMSDK/tools/log"
|
||||
"github.com/OpenIMSDK/tools/utils"
|
||||
"github.com/dtm-labs/rockscache"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/cachekey"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
relationtb "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
@@ -37,7 +37,7 @@ const (
|
||||
//recvMsgOptKey = "RECV_MSG_OPT:"
|
||||
//superGroupRecvMsgNotNotifyUserIDsKey = "SUPER_GROUP_RECV_MSG_NOT_NOTIFY_USER_IDS:"
|
||||
//superGroupRecvMsgNotNotifyUserIDsHashKey = "SUPER_GROUP_RECV_MSG_NOT_NOTIFY_USER_IDS_HASH:"
|
||||
//conversationNotReceiveMessageUserIDsKey = "CONVERSATION_NOT_RECEIVE_MESSAGE_USER_IDS:"
|
||||
//conversationNotReceiveMessageUserIDsKey = "CONVERSATION_NOT_RECEIVE_MESSAGE_USER_IDS:".
|
||||
|
||||
conversationExpireTime = time.Second * 60 * 60 * 12
|
||||
)
|
||||
|
||||
Vendored
+4
-4
@@ -16,13 +16,13 @@ package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/OpenIMSDK/tools/log"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/cachekey"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
"time"
|
||||
|
||||
"github.com/OpenIMSDK/tools/log"
|
||||
"github.com/OpenIMSDK/tools/utils"
|
||||
"github.com/dtm-labs/rockscache"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/cachekey"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
relationtb "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
@@ -31,7 +31,7 @@ const (
|
||||
friendExpireTime = time.Second * 60 * 60 * 12
|
||||
//friendIDsKey = "FRIEND_IDS:"
|
||||
//TwoWayFriendsIDsKey = "COMMON_FRIENDS_IDS:"
|
||||
//friendKey = "FRIEND_INFO:"
|
||||
//friendKey = "FRIEND_INFO:".
|
||||
)
|
||||
|
||||
// FriendCache is an interface for caching friend-related data.
|
||||
|
||||
Vendored
+3
-3
@@ -17,8 +17,6 @@ package cache
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/cachekey"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
"time"
|
||||
|
||||
"github.com/OpenIMSDK/protocol/constant"
|
||||
@@ -26,6 +24,8 @@ import (
|
||||
"github.com/OpenIMSDK/tools/log"
|
||||
"github.com/OpenIMSDK/tools/utils"
|
||||
"github.com/dtm-labs/rockscache"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/cachekey"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
relationtb "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
@@ -38,7 +38,7 @@ const (
|
||||
//groupMemberInfoKey = "GROUP_MEMBER_INFO:"
|
||||
//joinedGroupsKey = "JOIN_GROUPS_KEY:"
|
||||
//groupMemberNumKey = "GROUP_MEMBER_NUM_CACHE:"
|
||||
//groupRoleLevelMemberIDsKey = "GROUP_ROLE_LEVEL_MEMBER_IDS:"
|
||||
//groupRoleLevelMemberIDsKey = "GROUP_ROLE_LEVEL_MEMBER_IDS:".
|
||||
)
|
||||
|
||||
type GroupHash interface {
|
||||
|
||||
Vendored
+1
-1
@@ -18,7 +18,6 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
@@ -27,6 +26,7 @@ import (
|
||||
"github.com/OpenIMSDK/tools/mw/specialerror"
|
||||
"github.com/OpenIMSDK/tools/utils"
|
||||
"github.com/dtm-labs/rockscache"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
Vendored
+2
-2
@@ -38,11 +38,11 @@ const (
|
||||
conversationUserMinSeq = "CON_USER_MIN_SEQ:"
|
||||
hasReadSeq = "HAS_READ_SEQ:"
|
||||
|
||||
//appleDeviceToken = "DEVICE_TOKEN"
|
||||
//appleDeviceToken = "DEVICE_TOKEN".
|
||||
getuiToken = "GETUI_TOKEN"
|
||||
getuiTaskID = "GETUI_TASK_ID"
|
||||
//signalCache = "SIGNAL_CACHE:"
|
||||
//signalListCache = "SIGNAL_LIST_CACHE:"
|
||||
//signalListCache = "SIGNAL_LIST_CACHE:".
|
||||
FCM_TOKEN = "FCM_TOKEN:"
|
||||
|
||||
messageCache = "MESSAGE_CACHE:"
|
||||
|
||||
Vendored
+3
-3
@@ -18,8 +18,6 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/cachekey"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
"hash/crc32"
|
||||
"strconv"
|
||||
"time"
|
||||
@@ -29,13 +27,15 @@ import (
|
||||
"github.com/OpenIMSDK/tools/errs"
|
||||
"github.com/OpenIMSDK/tools/log"
|
||||
"github.com/dtm-labs/rockscache"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/cachekey"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
relationtb "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
const (
|
||||
userExpireTime = time.Second * 60 * 60 * 12
|
||||
//userInfoKey = "USER_INFO:"
|
||||
//userInfoKey = "USER_INFO:".
|
||||
userGlobalRecvMsgOptKey = "USER_GLOBAL_RECV_MSG_OPT_KEY:"
|
||||
olineStatusKey = "ONLINE_STATUS:"
|
||||
userOlineStatusExpireTime = time.Second * 60 * 60 * 24
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/OpenIMSDK/protocol/constant"
|
||||
"github.com/OpenIMSDK/tools/errs"
|
||||
"github.com/OpenIMSDK/tools/mgoutil"
|
||||
"github.com/OpenIMSDK/tools/pagination"
|
||||
@@ -69,7 +70,12 @@ func (g *GroupMgo) Take(ctx context.Context, groupID string) (group *relation.Gr
|
||||
}
|
||||
|
||||
func (g *GroupMgo) Search(ctx context.Context, keyword string, pagination pagination.Pagination) (total int64, groups []*relation.GroupModel, err error) {
|
||||
return mgoutil.FindPage[*relation.GroupModel](ctx, g.coll, bson.M{"group_name": bson.M{"$regex": keyword}}, pagination)
|
||||
opts := options.Find().SetSort(bson.D{{Key: "created_at", Value: -1}})
|
||||
|
||||
return mgoutil.FindPage[*relation.GroupModel](ctx, g.coll, bson.M{
|
||||
"group_name": bson.M{"$regex": keyword},
|
||||
"status": bson.M{"$ne": constant.GroupStatusDismissed},
|
||||
}, pagination, opts)
|
||||
}
|
||||
|
||||
func (g *GroupMgo) CountTotal(ctx context.Context, before *time.Time) (count int64, err error) {
|
||||
|
||||
@@ -1063,8 +1063,8 @@ func (m *MsgMongoDriver) searchMessage(ctx context.Context, req *msg.SearchMessa
|
||||
// Changed to keyed fields for bson.M to avoid govet errors
|
||||
condition = append(condition, bson.M{"$eq": bson.A{bson.M{"$dateToString": bson.M{"format": "%Y-%m-%d", "date": bson.M{"$toDate": "$$item.msg.send_time"}}}, req.SendTime}})
|
||||
}
|
||||
if req.MsgType != 0 {
|
||||
condition = append(condition, bson.M{"$eq": bson.A{"$$item.msg.content_type", req.MsgType}})
|
||||
if req.ContentType != 0 {
|
||||
condition = append(condition, bson.M{"$eq": bson.A{"$$item.msg.content_type", req.ContentType}})
|
||||
}
|
||||
if req.SessionType != 0 {
|
||||
condition = append(condition, bson.M{"$eq": bson.A{"$$item.msg.session_type", req.SessionType}})
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// Copyright © 2024 OpenIM. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package redispubsub
|
||||
|
||||
import "github.com/redis/go-redis/v9"
|
||||
|
||||
@@ -1,7 +1,22 @@
|
||||
// Copyright © 2024 OpenIM. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package redispubsub
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
|
||||
+17
-2
@@ -1,11 +1,26 @@
|
||||
// Copyright © 2024 OpenIM. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package localcache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/openimsdk/localcache/link"
|
||||
"github.com/openimsdk/localcache/lru"
|
||||
"hash/fnv"
|
||||
"unsafe"
|
||||
|
||||
"github.com/openimsdk/localcache/link"
|
||||
"github.com/openimsdk/localcache/lru"
|
||||
)
|
||||
|
||||
type Cache[V any] interface {
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// Copyright © 2024 OpenIM. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package localcache
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// Copyright © 2024 OpenIM. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package link
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// Copyright © 2024 OpenIM. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package link
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// Copyright © 2024 OpenIM. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package lru
|
||||
|
||||
import "github.com/hashicorp/golang-lru/v2/simplelru"
|
||||
|
||||
@@ -1,9 +1,24 @@
|
||||
// Copyright © 2024 OpenIM. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package lru
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/golang-lru/v2/expirable"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/golang-lru/v2/expirable"
|
||||
)
|
||||
|
||||
func NewExpirationLRU[K comparable, V any](size int, successTTL, failedTTL time.Duration, target Target, onEvict EvictCallback[K, V]) LRU[K, V] {
|
||||
|
||||
@@ -1,9 +1,24 @@
|
||||
// Copyright © 2024 OpenIM. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package lru
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/golang-lru/v2/simplelru"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/golang-lru/v2/simplelru"
|
||||
)
|
||||
|
||||
type layLruItem[V any] struct {
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// Copyright © 2024 OpenIM. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package lru
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// Copyright © 2024 OpenIM. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package lru
|
||||
|
||||
func NewSlotLRU[K comparable, V any](slotNum int, hash func(K) uint64, create func() LRU[K, V]) LRU[K, V] {
|
||||
|
||||
@@ -1,9 +1,24 @@
|
||||
// Copyright © 2024 OpenIM. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package localcache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/openimsdk/localcache/lru"
|
||||
"time"
|
||||
|
||||
"github.com/openimsdk/localcache/lru"
|
||||
)
|
||||
|
||||
func defaultOption() *option {
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// Copyright © 2024 OpenIM. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package localcache
|
||||
|
||||
func AnyValue[V any](v any, err error) (V, error) {
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// Copyright © 2024 OpenIM. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rpccache
|
||||
|
||||
func newListMap[V comparable](values []V, err error) (*listMap[V], error) {
|
||||
|
||||
@@ -1,7 +1,22 @@
|
||||
// Copyright © 2024 OpenIM. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rpccache
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
pbconversation "github.com/OpenIMSDK/protocol/conversation"
|
||||
"github.com/OpenIMSDK/tools/errs"
|
||||
"github.com/OpenIMSDK/tools/log"
|
||||
|
||||
+16
-1
@@ -1,7 +1,22 @@
|
||||
// Copyright © 2024 OpenIM. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rpccache
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/OpenIMSDK/tools/log"
|
||||
"github.com/openimsdk/localcache"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/cachekey"
|
||||
@@ -49,7 +64,7 @@ func (f *FriendLocalCache) IsFriend(ctx context.Context, possibleFriendUserID, u
|
||||
}, cachekey.GetFriendIDsKey(possibleFriendUserID)))
|
||||
}
|
||||
|
||||
// IsBlack possibleBlackUserID selfUserID
|
||||
// IsBlack possibleBlackUserID selfUserID.
|
||||
func (f *FriendLocalCache) IsBlack(ctx context.Context, possibleBlackUserID, userID string) (val bool, err error) {
|
||||
log.ZDebug(ctx, "FriendLocalCache IsBlack req", "possibleBlackUserID", possibleBlackUserID, "userID", userID)
|
||||
defer func() {
|
||||
|
||||
@@ -1,7 +1,22 @@
|
||||
// Copyright © 2024 OpenIM. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rpccache
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/OpenIMSDK/protocol/sdkws"
|
||||
"github.com/OpenIMSDK/tools/errs"
|
||||
"github.com/OpenIMSDK/tools/log"
|
||||
|
||||
@@ -1,8 +1,23 @@
|
||||
// Copyright © 2024 OpenIM. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rpccache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/OpenIMSDK/tools/log"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
@@ -1,7 +1,22 @@
|
||||
// Copyright © 2024 OpenIM. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rpccache
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/OpenIMSDK/protocol/sdkws"
|
||||
"github.com/OpenIMSDK/tools/errs"
|
||||
"github.com/OpenIMSDK/tools/log"
|
||||
|
||||
+2
-10
@@ -49,11 +49,7 @@ func NewGroupRpcClient(discov discoveryregistry.SvcDiscoveryRegistry, config *co
|
||||
return GroupRpcClient(*NewGroup(discov, config))
|
||||
}
|
||||
|
||||
func (g *GroupRpcClient) GetGroupInfos(
|
||||
ctx context.Context,
|
||||
groupIDs []string,
|
||||
complete bool,
|
||||
) ([]*sdkws.GroupInfo, error) {
|
||||
func (g *GroupRpcClient) GetGroupInfos(ctx context.Context, groupIDs []string, complete bool) ([]*sdkws.GroupInfo, error) {
|
||||
resp, err := g.Client.GetGroupsInfo(ctx, &group.GetGroupsInfoReq{
|
||||
GroupIDs: groupIDs,
|
||||
})
|
||||
@@ -184,11 +180,7 @@ func (g *GroupRpcClient) GetGroupInfoCache(ctx context.Context, groupID string)
|
||||
return resp.GroupInfo, nil
|
||||
}
|
||||
|
||||
func (g *GroupRpcClient) GetGroupMemberCache(
|
||||
ctx context.Context,
|
||||
groupID string,
|
||||
groupMemberID string,
|
||||
) (*sdkws.GroupMemberFullInfo, error) {
|
||||
func (g *GroupRpcClient) GetGroupMemberCache(ctx context.Context, groupID string, groupMemberID string) (*sdkws.GroupMemberFullInfo, error) {
|
||||
resp, err := g.Client.GetGroupMemberCache(ctx, &group.GetGroupMemberCacheReq{
|
||||
GroupID: groupID,
|
||||
GroupMemberID: groupMemberID,
|
||||
|
||||
@@ -126,10 +126,7 @@ func (f *FriendNotificationSender) UserInfoUpdatedNotification(ctx context.Conte
|
||||
return f.Notification(ctx, mcontext.GetOpUserID(ctx), changedUserID, constant.UserInfoUpdatedNotification, &tips)
|
||||
}
|
||||
|
||||
func (f *FriendNotificationSender) FriendApplicationAddNotification(
|
||||
ctx context.Context,
|
||||
req *pbfriend.ApplyToAddFriendReq,
|
||||
) error {
|
||||
func (f *FriendNotificationSender) FriendApplicationAddNotification(ctx context.Context, req *pbfriend.ApplyToAddFriendReq) error {
|
||||
tips := sdkws.FriendApplicationTips{FromToUserID: &sdkws.FromToUserID{
|
||||
FromUserID: req.FromUserID,
|
||||
ToUserID: req.ToUserID,
|
||||
|
||||
@@ -21,10 +21,6 @@
|
||||
# checks them out to a branch named:
|
||||
# automated-cherry-pick-of-<pr>-<target branch>-<timestamp>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
OPENIM_ROOT=$(dirname "${BASH_SOURCE[0]}")/..
|
||||
source "${OPENIM_ROOT}/scripts/lib/init.sh"
|
||||
|
||||
|
||||
@@ -932,7 +932,7 @@ openim::test::set_group_info() {
|
||||
{
|
||||
"groupInfoForSet": {
|
||||
"groupID": "${1}",
|
||||
"groupName": "new-name",
|
||||
"groupName": "new group name",
|
||||
"notification": "new notification",
|
||||
"introduction": "new introduction",
|
||||
"faceURL": "www.newfaceURL.com",
|
||||
@@ -1076,6 +1076,7 @@ function openim::test::group() {
|
||||
|
||||
local GROUP_ID=$RANDOM
|
||||
local GROUP_ID2=$RANDOM
|
||||
|
||||
# Assumes that TEST_GROUP_ID, USER_ID, and other necessary IDs are set as environment variables before running this suite.
|
||||
# 0. Register a friend user.
|
||||
openim::test::user_register "${USER_ID}" "group00" "new_face_url"
|
||||
@@ -1272,7 +1273,7 @@ openim::test::search_msg() {
|
||||
{
|
||||
"sendID": "${sendID}",
|
||||
"recvID": "${recvID}",
|
||||
"msgType": ${msgType},
|
||||
"contentType": ${msgType},
|
||||
"sendTime": "${sendTime}",
|
||||
"sessionType": ${sessionType},
|
||||
"pagination": {
|
||||
|
||||
@@ -237,6 +237,17 @@ install.richgo:
|
||||
install.rts:
|
||||
@$(GO) install github.com/galeone/rts/cmd/rts@latest
|
||||
|
||||
# ================= kubecub openim tools =========================================
|
||||
## install.typecheck: install kubecub typecheck check for go code
|
||||
.PHONY: install.typecheck
|
||||
install.typecheck:
|
||||
@$(GO) install github.com/kubecub/typecheck@latest
|
||||
|
||||
## install.comment-lang-detector: install kubecub comment-lang-detector check for go code comment language
|
||||
.PHONY: install.comment-lang-detector
|
||||
install.comment-lang-detector:
|
||||
@$(GO) install github.com/kubecub/comment-lang-detector/cmd/cld@latest
|
||||
|
||||
## tools.help: Display help information about the tools package
|
||||
.PHONY: tools.help
|
||||
tools.help: scripts/make-rules/tools.mk
|
||||
|
||||
@@ -20,10 +20,6 @@
|
||||
# the project.
|
||||
# Usage: `scripts/run-in-gopath.sh <command>`.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
OPENIM_ROOT=$(dirname "${BASH_SOURCE[0]}")/..
|
||||
source "${OPENIM_ROOT}/scripts/lib/init.sh"
|
||||
|
||||
|
||||
@@ -18,10 +18,6 @@
|
||||
# immediately before exporting docs. We do not want to check these documents in
|
||||
# by default.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
OPENIM_ROOT=$(dirname "${BASH_SOURCE[0]}")/..
|
||||
source "${OPENIM_ROOT}/scripts/lib/init.sh"
|
||||
|
||||
@@ -33,7 +29,7 @@ BINS=(
|
||||
genman
|
||||
genyaml
|
||||
)
|
||||
make -C "${OPENIM_ROOT}" WHAT="${BINS[*]}"
|
||||
make -C "${OPENIM_ROOT}" BINS="${BINS[*]}"
|
||||
|
||||
openim::util::ensure-temp-dir
|
||||
|
||||
|
||||
@@ -13,11 +13,6 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
OPENIM_ROOT=$(dirname "${BASH_SOURCE[0]}")/..
|
||||
source "${OPENIM_ROOT}/scripts/lib/init.sh"
|
||||
|
||||
|
||||
@@ -17,10 +17,6 @@
|
||||
# This script lints each shell script by `shellcheck`.
|
||||
# Usage: `scripts/verify-shellcheck.sh`.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
OPENIM_ROOT=$(dirname "${BASH_SOURCE[0]}")/..
|
||||
source "${OPENIM_ROOT}/scripts/lib/init.sh"
|
||||
|
||||
|
||||
@@ -17,10 +17,6 @@
|
||||
# working directory by client9/misspell package.
|
||||
# Usage: `scripts/verify-spelling.sh`.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
OPENIM_ROOT=$(dirname "${BASH_SOURCE[0]}")/..
|
||||
export OPENIM_ROOT
|
||||
source "${OPENIM_ROOT}/scripts/lib/init.sh"
|
||||
|
||||
@@ -16,26 +16,19 @@
|
||||
# This script does a fast type check of script srnetes code for all platforms.
|
||||
# Usage: `scripts/verify-typecheck.sh`.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
OPENIM_ROOT=$(dirname "${BASH_SOURCE[0]}")/..
|
||||
source "${OPENIM_ROOT}/scripts/lib/init.sh"
|
||||
|
||||
openim::golang::verify_go_version
|
||||
|
||||
cd "${OPENIM_ROOT}"
|
||||
|
||||
# As of June, 2020 the typecheck tool is written in terms of go/packages, but
|
||||
# that library doesn't work well with multiple modules. Until that is done,
|
||||
# force this tooling to run in a fake GOPATH.
|
||||
ret=0
|
||||
TYPECHECK_SERIAL="${TYPECHECK_SERIAL:-false}"
|
||||
scripts/run-in-gopath.sh \
|
||||
go run test/typecheck/typecheck.go "$@" "--serial=$TYPECHECK_SERIAL" || ret=$?
|
||||
make tools.verify.typecheck
|
||||
${OPENIM_ROOT}/_output/tools/typecheck "$@" "--serial=$TYPECHECK_SERIAL" || ret=$?
|
||||
if [[ $ret -ne 0 ]]; then
|
||||
openim::log::error "Type Check has failed. This may cause cross platform build failures." >&2
|
||||
openim::log::error "Please see https://github.com/openimsdk/open-im-server/tree/main/test/typecheck for more information." >&2
|
||||
openim::log::error "Please see https://github.com/kubecub/typecheck for more information." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -19,10 +19,6 @@
|
||||
#
|
||||
# Usage: `scripts/verify-yamlfmt.sh`.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
OPENIM_ROOT=$(dirname "${BASH_SOURCE[0]}")/..
|
||||
source "${OPENIM_ROOT}/scripts/lib/init.sh"
|
||||
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
# OpenIM Typecheck: Cross-Platform Source Code Type Checking for Go
|
||||
|
||||
## Introduction
|
||||
|
||||
OpenIM Typecheck is a robust tool designed for cross-platform source code type checking across all Go build platforms. This utility leverages Go’s built-in parsing and type-check libraries (`go/parser` and `go/types`) to deliver efficient and reliable code analysis.
|
||||
|
||||
## Advantages
|
||||
|
||||
- **Speed**: A complete compilation with OpenIM can take approximately 3 minutes. In contrast, OpenIM Typecheck achieves this in mere seconds, significantly enhancing productivity.
|
||||
- **Resource Efficiency**: Unlike the typical requirement of over 40GB of RAM for standard processes, Typecheck operates effectively with less than 8GB of RAM. This reduction in resource consumption makes it highly suitable for a variety of systems, reducing overheads and facilitating smoother operations.
|
||||
|
||||
## Implementation
|
||||
|
||||
OpenIM Typecheck employs Go's native parsing and type-checking libraries (`go/parser` and `go/types`). However, it's important to note that these libraries aren't identical to those used by the Go compiler. While occasional mismatches may occur, these libraries generally provide close approximations to the compiler's functionality, offering a reliable basis for type checking.
|
||||
|
||||
## Error Handling
|
||||
|
||||
Typecheck's approach to error handling is pragmatic, focusing on practicality and build continuity.
|
||||
|
||||
**Errors reported by `go/types` but not by `go build`**:
|
||||
- **Actual Errors** (as per the specification):
|
||||
- These should ideally be rectified. If rectification is not feasible, such as in cases of ongoing work or external dependencies in the code, these errors can be overlooked.
|
||||
- Example: Unused variables within a closure.
|
||||
- **False Positives**:
|
||||
- These errors should be ignored and, where appropriate, reported upstream for resolution.
|
||||
- Example: Type mismatches between staging and generated types.
|
||||
|
||||
**Errors reported by `go build` but not by us**:
|
||||
- CGo-related errors, including both syntax and linker issues, are outside our scope.
|
||||
|
||||
## Usage
|
||||
|
||||
### Locally
|
||||
|
||||
To run Typecheck locally, simply use the following command:
|
||||
|
||||
```bash
|
||||
make verify
|
||||
```
|
||||
|
||||
### Continuous Integration (CI)
|
||||
|
||||
In CI environments, Typecheck can be integrated into the workflow as follows:
|
||||
|
||||
```yaml
|
||||
- name: Typecheck
|
||||
run: make verify
|
||||
```
|
||||
|
||||
This streamlined process facilitates efficient error detection and resolution, ensuring a robust and reliable build pipeline.
|
||||
|
||||
More to learn about typecheck [share blog](https://nsddd.top/posts/concurrent-type-checking-and-cross-platform-development-in-go/)
|
||||
@@ -1,10 +0,0 @@
|
||||
module github.com/openimsdk/open-im-server/test/typecheck
|
||||
|
||||
go 1.19
|
||||
|
||||
require golang.org/x/tools v0.12.0
|
||||
|
||||
require (
|
||||
golang.org/x/mod v0.12.0 // indirect
|
||||
golang.org/x/sys v0.11.0 // indirect
|
||||
)
|
||||
@@ -1,7 +0,0 @@
|
||||
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss=
|
||||
golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
|
||||
@@ -1,319 +0,0 @@
|
||||
// Copyright © 2023 OpenIM. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// do a fast type check of openim code, for all platforms.
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
)
|
||||
|
||||
var (
|
||||
verbose = flag.Bool("verbose", false, "print more information")
|
||||
cross = flag.Bool("cross", true, "build for all platforms")
|
||||
platforms = flag.String("platform", "", "comma-separated list of platforms to typecheck")
|
||||
timings = flag.Bool("time", false, "output times taken for each phase")
|
||||
defuses = flag.Bool("defuse", false, "output defs/uses")
|
||||
serial = flag.Bool("serial", false, "don't type check platforms in parallel (equivalent to --parallel=1)")
|
||||
parallel = flag.Int("parallel", 2, "limits how many platforms can be checked in parallel. 0 means no limit.")
|
||||
skipTest = flag.Bool("skip-test", false, "don't type check test code")
|
||||
tags = flag.String("tags", "", "comma-separated list of build tags to apply in addition to go's defaults")
|
||||
ignoreDirs = flag.String("ignore-dirs", "", "comma-separated list of directories to ignore in addition to the default hardcoded list including staging, vendor, and hidden dirs")
|
||||
|
||||
// When processed in order, windows and darwin are early to make
|
||||
// interesting OS-based errors happen earlier.
|
||||
crossPlatforms = []string{
|
||||
"linux/amd64", "windows/386",
|
||||
"darwin/amd64", "darwin/arm64",
|
||||
"linux/386", "linux/arm",
|
||||
"windows/amd64", "linux/arm64",
|
||||
"linux/ppc64le", "linux/s390x",
|
||||
"windows/arm64",
|
||||
}
|
||||
|
||||
// directories we always ignore
|
||||
standardIgnoreDirs = []string{
|
||||
// Staging code is symlinked from vendor/k8s.io, and uses import
|
||||
// paths as if it were inside of vendor/. It fails typechecking
|
||||
// inside of staging/, but works when typechecked as part of vendor/.
|
||||
"staging",
|
||||
"components",
|
||||
"logs",
|
||||
// OS-specific vendor code tends to be imported by OS-specific
|
||||
// packages. We recursively typecheck imported vendored packages for
|
||||
// each OS, but don't typecheck everything for every OS.
|
||||
"vendor",
|
||||
"test",
|
||||
"_output",
|
||||
"*/mw/rpc_server_interceptor.go",
|
||||
// Tools we use for maintaining the code base but not necessarily
|
||||
// ship as part of the release
|
||||
"sopenim::golang::setup_env:tools/yamlfmt/yamlfmt.go:tools",
|
||||
}
|
||||
)
|
||||
|
||||
func newConfig(platform string) *packages.Config {
|
||||
platSplit := strings.Split(platform, "/")
|
||||
goos, goarch := platSplit[0], platSplit[1]
|
||||
mode := packages.NeedName | packages.NeedFiles | packages.NeedTypes | packages.NeedSyntax | packages.NeedDeps | packages.NeedImports | packages.NeedModule
|
||||
if *defuses {
|
||||
mode = mode | packages.NeedTypesInfo
|
||||
}
|
||||
env := append(os.Environ(),
|
||||
"CGO_ENABLED=1",
|
||||
fmt.Sprintf("GOOS=%s", goos),
|
||||
fmt.Sprintf("GOARCH=%s", goarch))
|
||||
tagstr := "selinux"
|
||||
if *tags != "" {
|
||||
tagstr = tagstr + "," + *tags
|
||||
}
|
||||
flags := []string{"-tags", tagstr}
|
||||
|
||||
return &packages.Config{
|
||||
Mode: mode,
|
||||
Env: env,
|
||||
BuildFlags: flags,
|
||||
Tests: !(*skipTest),
|
||||
}
|
||||
}
|
||||
|
||||
type collector struct {
|
||||
dirs []string
|
||||
ignoreDirs []string
|
||||
}
|
||||
|
||||
func newCollector(ignoreDirs string) collector {
|
||||
c := collector{
|
||||
ignoreDirs: append([]string(nil), standardIgnoreDirs...),
|
||||
}
|
||||
if ignoreDirs != "" {
|
||||
c.ignoreDirs = append(c.ignoreDirs, strings.Split(ignoreDirs, ",")...)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *collector) walk(roots []string) error {
|
||||
for _, root := range roots {
|
||||
err := filepath.Walk(root, c.handlePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
sort.Strings(c.dirs)
|
||||
return nil
|
||||
}
|
||||
|
||||
// handlePath walks the filesystem recursively, collecting directories,
|
||||
// ignoring some unneeded directories (hidden/vendored) that are handled
|
||||
// specially later.
|
||||
func (c *collector) handlePath(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.IsDir() {
|
||||
name := info.Name()
|
||||
// Ignore hidden directories (.git, .cache, etc)
|
||||
if (len(name) > 1 && (name[0] == '.' || name[0] == '_')) || name == "testdata" {
|
||||
if *verbose {
|
||||
fmt.Printf("DBG: skipping dir %s\n", path)
|
||||
}
|
||||
return filepath.SkipDir
|
||||
}
|
||||
for _, dir := range c.ignoreDirs {
|
||||
if path == dir {
|
||||
if *verbose {
|
||||
fmt.Printf("DBG: ignoring dir %s\n", path)
|
||||
}
|
||||
return filepath.SkipDir
|
||||
}
|
||||
}
|
||||
// Make dirs into relative pkg names.
|
||||
// NOTE: can't use filepath.Join because it elides the leading "./"
|
||||
pkg := path
|
||||
if !strings.HasPrefix(pkg, "./") {
|
||||
pkg = "./" + pkg
|
||||
}
|
||||
c.dirs = append(c.dirs, pkg)
|
||||
if *verbose {
|
||||
fmt.Printf("DBG: added dir %s\n", path)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *collector) verify(plat string) ([]string, error) {
|
||||
errors := []packages.Error{}
|
||||
start := time.Now()
|
||||
config := newConfig(plat)
|
||||
|
||||
rootPkgs, err := packages.Load(config, c.dirs...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Recursively import all deps and flatten to one list.
|
||||
allMap := map[string]*packages.Package{}
|
||||
for _, pkg := range rootPkgs {
|
||||
if *verbose {
|
||||
serialFprintf(os.Stdout, "pkg %q has %d GoFiles\n", pkg.PkgPath, len(pkg.GoFiles))
|
||||
}
|
||||
allMap[pkg.PkgPath] = pkg
|
||||
if len(pkg.Imports) > 0 {
|
||||
for _, imp := range pkg.Imports {
|
||||
if *verbose {
|
||||
serialFprintf(os.Stdout, "pkg %q imports %q\n", pkg.PkgPath, imp.PkgPath)
|
||||
}
|
||||
allMap[imp.PkgPath] = imp
|
||||
}
|
||||
}
|
||||
}
|
||||
keys := make([]string, 0, len(allMap))
|
||||
for k := range allMap {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
allList := make([]*packages.Package, 0, len(keys))
|
||||
for _, k := range keys {
|
||||
allList = append(allList, allMap[k])
|
||||
}
|
||||
|
||||
for _, pkg := range allList {
|
||||
if len(pkg.GoFiles) > 0 {
|
||||
if len(pkg.Errors) > 0 && (pkg.PkgPath == "main" || strings.Contains(pkg.PkgPath, ".")) {
|
||||
errors = append(errors, pkg.Errors...)
|
||||
}
|
||||
}
|
||||
if *defuses {
|
||||
for id, obj := range pkg.TypesInfo.Defs {
|
||||
serialFprintf(os.Stdout, "%s: %q defines %v\n",
|
||||
pkg.Fset.Position(id.Pos()), id.Name, obj)
|
||||
}
|
||||
for id, obj := range pkg.TypesInfo.Uses {
|
||||
serialFprintf(os.Stdout, "%s: %q uses %v\n",
|
||||
pkg.Fset.Position(id.Pos()), id.Name, obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
if *timings {
|
||||
serialFprintf(os.Stdout, "%s took %.1fs\n", plat, time.Since(start).Seconds())
|
||||
}
|
||||
return dedup(errors), nil
|
||||
}
|
||||
|
||||
func dedup(errors []packages.Error) []string {
|
||||
ret := []string{}
|
||||
|
||||
m := map[string]bool{}
|
||||
for _, e := range errors {
|
||||
es := e.Error()
|
||||
if !m[es] {
|
||||
ret = append(ret, es)
|
||||
m[es] = true
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
var outMu sync.Mutex
|
||||
|
||||
func serialFprintf(w io.Writer, format string, a ...any) (n int, err error) {
|
||||
outMu.Lock()
|
||||
defer outMu.Unlock()
|
||||
return fmt.Fprintf(w, format, a...)
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
args := flag.Args()
|
||||
|
||||
if *verbose {
|
||||
*serial = true // to avoid confusing interleaved logs
|
||||
}
|
||||
|
||||
if len(args) == 0 {
|
||||
args = append(args, ".")
|
||||
}
|
||||
|
||||
c := newCollector(*ignoreDirs)
|
||||
|
||||
if err := c.walk(args); err != nil {
|
||||
log.Fatalf("Error walking: %v", err)
|
||||
}
|
||||
|
||||
plats := crossPlatforms[:]
|
||||
if *platforms != "" {
|
||||
plats = strings.Split(*platforms, ",")
|
||||
} else if !*cross {
|
||||
plats = plats[:1]
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
var failMu sync.Mutex
|
||||
failed := false
|
||||
|
||||
if *serial {
|
||||
*parallel = 1
|
||||
} else if *parallel == 0 {
|
||||
*parallel = len(plats)
|
||||
}
|
||||
throttle := make(chan int, *parallel)
|
||||
|
||||
for _, plat := range plats {
|
||||
wg.Add(1)
|
||||
go func(plat string) {
|
||||
// block until there's room for this task
|
||||
throttle <- 1
|
||||
defer func() {
|
||||
// indicate this task is done
|
||||
<-throttle
|
||||
}()
|
||||
|
||||
f := false
|
||||
serialFprintf(os.Stdout, "type-checking %s\n", plat)
|
||||
errors, err := c.verify(plat)
|
||||
if err != nil {
|
||||
serialFprintf(os.Stderr, "ERROR(%s): failed to verify: %v\n", plat, err)
|
||||
f = true
|
||||
} else if len(errors) > 0 {
|
||||
for _, e := range errors {
|
||||
// Special case CGo errors which may depend on headers we
|
||||
// don't have.
|
||||
if !strings.HasSuffix(e, "could not import C (no metadata for C)") {
|
||||
f = true
|
||||
serialFprintf(os.Stderr, "ERROR(%s): %s\n", plat, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
failMu.Lock()
|
||||
failed = failed || f
|
||||
failMu.Unlock()
|
||||
wg.Done()
|
||||
}(plat)
|
||||
}
|
||||
wg.Wait()
|
||||
if failed {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
// Copyright © 2023 OpenIM. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
)
|
||||
|
||||
// This exists because `go` is not always in the PATH when running CI.
|
||||
var goBinary = flag.String("go", "", "path to a `go` binary")
|
||||
|
||||
func TestVerify(t *testing.T) {
|
||||
// x/tools/packages is going to literally exec `go`, so it needs some
|
||||
// setup.
|
||||
setEnvVars(t)
|
||||
|
||||
tcs := []struct {
|
||||
path string
|
||||
expect int
|
||||
}{
|
||||
// {"./testdata/good", 0},
|
||||
// {"./testdata/bad", 18},
|
||||
}
|
||||
|
||||
for _, tc := range tcs {
|
||||
c := newCollector("")
|
||||
if err := c.walk([]string{tc.path}); err != nil {
|
||||
t.Fatalf("error walking %s: %v", tc.path, err)
|
||||
}
|
||||
|
||||
errs, err := c.verify("linux/amd64")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
} else if len(errs) != tc.expect {
|
||||
t.Errorf("Expected %d errors, got %d: %v", tc.expect, len(errs), errs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setEnvVars(t testing.TB) {
|
||||
t.Helper()
|
||||
if *goBinary != "" {
|
||||
newPath := filepath.Dir(*goBinary)
|
||||
curPath := os.Getenv("PATH")
|
||||
if curPath != "" {
|
||||
newPath = newPath + ":" + curPath
|
||||
}
|
||||
t.Setenv("PATH", newPath)
|
||||
}
|
||||
if os.Getenv("HOME") == "" {
|
||||
t.Setenv("HOME", "/tmp")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlePath(t *testing.T) {
|
||||
c := collector{
|
||||
ignoreDirs: standardIgnoreDirs,
|
||||
}
|
||||
e := errors.New("ex")
|
||||
i, _ := os.Stat(".") // i.IsDir() == true
|
||||
if c.handlePath("foo", nil, e) != e {
|
||||
t.Error("handlePath not returning errors")
|
||||
}
|
||||
if c.handlePath("vendor", i, nil) != filepath.SkipDir {
|
||||
t.Error("should skip vendor")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDedup(t *testing.T) {
|
||||
testcases := []struct {
|
||||
input []packages.Error
|
||||
expected int
|
||||
}{{
|
||||
input: nil,
|
||||
expected: 0,
|
||||
}, {
|
||||
input: []packages.Error{
|
||||
{Pos: "file:7", Msg: "message", Kind: packages.ParseError},
|
||||
},
|
||||
expected: 1,
|
||||
}, {
|
||||
input: []packages.Error{
|
||||
{Pos: "file:7", Msg: "message1", Kind: packages.ParseError},
|
||||
{Pos: "file:8", Msg: "message2", Kind: packages.ParseError},
|
||||
},
|
||||
expected: 2,
|
||||
}, {
|
||||
input: []packages.Error{
|
||||
{Pos: "file:7", Msg: "message1", Kind: packages.ParseError},
|
||||
{Pos: "file:8", Msg: "message2", Kind: packages.ParseError},
|
||||
{Pos: "file:7", Msg: "message1", Kind: packages.ParseError},
|
||||
},
|
||||
expected: 2,
|
||||
}}
|
||||
|
||||
for i, tc := range testcases {
|
||||
out := dedup(tc.input)
|
||||
if len(out) != tc.expected {
|
||||
t.Errorf("[%d] dedup(%v) = '%v', expected %d",
|
||||
i, tc.input, out, tc.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,8 +30,9 @@ import (
|
||||
"github.com/OpenIMSDK/tools/component"
|
||||
"github.com/OpenIMSDK/tools/errs"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
Reference in New Issue
Block a user